您的位置:首页 > 移动开发 > Android开发

android之媒体硬解OMX的实现

2014-06-15 16:13 316 查看
转自:http://blog.csdn.net/vincent_blog/article/details/7578112

android的多媒体部分采用的编解码标准是OMX,当然这个标准是用于硬件编解码的,软件编解码在这里我就不说了。

直接从stagefright的awesomeplayer开始说起吧,如果看过我前面博客的人知道stagefright使用的三个步骤:

setdatasoure

prepare

start

至于它们的作用在这里就不多说了。

在prepare里面,当MediaExtractor解析文件后会产生一个音频流和一个视频流(可能还有字幕流)对应到stagefright里面就是一个MediaSource的数据结构。

也就是awesomeplayer里面的mVideoTrack和mAudioTrack两个数据成员。

得到音视频流后就要开始构造解码器了(暂且只说解码,编码类似)。请看initVideoDecoder或initAudioDecoder。

status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {

mVideoSource = OMXCodec::Create(

mClient.interface(), mVideoTrack->getFormat(),//mClient.interface() is BnOMX

false, // createEncoder

mVideoTrack,

NULL, flags);

...

...

...

status_t err = mVideoSource->start();

if (err != OK) {

mVideoSource.clear();

return err;

}

}

return mVideoSource != NULL ? OK : UNKNOWN_ERROR;

}

函数一开始就创建了一个OMXCodec,下面我们看下传进来的几个参数的意思:

===========================================================================

mClient.interface()

在awesomeplayer的构造函数里面有这么一句话 CHECK_EQ(mClient.connect(), OK);可以到OMXClient里面去看实际是通过MediaPlayerService创建了一个BnOMX(bnOMX会在后面讲到)然后作为自己的成员变量保存下来,这里我们可以将OMXClient看作OMX的客户端,BnOMX则是OMX的具体实现。

再回到mClient.interface(),就知道它返回的就是前面创建的BnOMX。

mVideoTrack->getFormat()

这个很显然是格式信息,但是这个里面不仅仅是编码格式,还有宽高,是否旋转等等,通称MetaData

false

指的是创建解码器,true则是编码器

mVideoTrack

视频流(解码前的压缩数据)

NULL

不指定解码器,如果不指定就会到现有的解码器中去找,选择第一个找到的

flags

解码器的类型,你可以在这里将解码器指定为软解码

===========================================================================

进入OMXCodec::Create函数

sp<MediaSource> OMXCodec::Create(

const sp<IOMX> &omx,//BnOMX

const sp<MetaData> &meta, bool createEncoder,

const sp<MediaSource> &source,

const char *matchComponentName,//NULL

uint32_t flags) {

const char *mime;

bool success = meta->findCString(kKeyMIMEType, &mime);

CHECK(success);

Vector<String8> matchingCodecs;

findMatchingCodecs(

mime, createEncoder, matchComponentName, flags, &matchingCodecs);

if (matchingCodecs.isEmpty()) {

return NULL;

}

sp<OMXCodecObserver> observer = new OMXCodecObserver;

IOMX::node_id node = 0;

const char *componentName;

for (size_t i = 0; i < matchingCodecs.size(); ++i) {

componentName = matchingCodecs[i].string();

LOGV("componentName is %s",componentName);

sp<MediaSource> softwareCodec = createEncoder?

InstantiateSoftwareEncoder(componentName, source, meta):

InstantiateSoftwareCodec(componentName, source);

if (softwareCodec != NULL) {

LOGV("Successfully allocated software codec '%s'", componentName);

return softwareCodec;

}

LOGV("Attempting to allocate OMX node '%s'", componentName);

uint32_t quirks = getComponentQuirks(componentName, createEncoder);

if (!createEncoder

&& (quirks & kOutputBuffersAreUnreadable)

&& (flags & kClientNeedsFramebuffer)) {

if (strncmp(componentName, "OMX.SEC.", 8)) {

// For OMX.SEC.* decoders we can enable a special mode that

// gives the client access to the framebuffer contents.

LOGW("Component '%s' does not give the client access to "

"the framebuffer contents. Skipping.",

componentName);

continue;

}

}

status_t err = omx->allocateNode(componentName, observer, &node);

if (err == OK) {

LOGV("Successfully allocated OMX node '%s'", componentName);

sp<OMXCodec> codec = new OMXCodec(

omx, node, quirks,

createEncoder, mime, componentName,

source);

observer->setCodec(codec);

err = codec->configureCodec(meta, flags);

if (err == OK) {

return codec;

}

LOGV("Failed to configure codec '%s'", componentName);

}

}

return NULL;

}

这里面有几个关键的函数

findMatchingCodecs

InstantiateSoftwareCodec

omx->allocateNode

下面一一进行说明:

=========================================================================

findMatchingCodecs

这个函数里面实际是会到一个CodecInfo的数组里面去找到符合条件的解码器,这个数组在OMXCodec里面定义的。当然放在前面就被放到数组的前面保存在matchingCodecs里面,

然后通过for循环来遍历这个matchingCodecs数组。

InstantiateSoftwareCodec

在看这个函数前我声明一下,我分析的代码是2.3.x的在4.0的代码里面这里会有些区别,这个看我的另一篇blog stagefright之2.3和4.0的区别 就知道了。

在这个函数里面,会将matchingCodecs里面的解码器与一些软解码器进行比较(如果是硬解的话名字当然会不一样)。然而我们一般都是将硬解放在前面的,所以这个函数肯定会返回NULL

所以一般都会走到第三函数

omx->allocateNode

这个函数会调到BnOMX里面来。我们先看下它的三个参数componentName不多说,observer大家得留意了,它可是底层给我们上报消息的东西了,在后面会谈到,node显然是一个输出参数暂时我只能说它就是

一个区分不同解码器的标识。

下面看allocateNode的代码,这个比较重要

status_t OMX::allocateNode(

const char *name, const sp<IOMXObserver> &observer, node_id *node) {

Mutex::Autolock autoLock(mLock);

*node = 0;

OMXNodeInstance *instance = new OMXNodeInstance(this, observer);

OMX_COMPONENTTYPE *handle;

OMX_ERRORTYPE err = mMaster->makeComponentInstance(

name, &OMXNodeInstance::kCallbacks,

instance, &handle);

if (err != OMX_ErrorNone) {

LOGV("FAILED to allocate omx component '%s'", name);

instance->onGetHandleFailed();

return UNKNOWN_ERROR;

}

*node = makeNodeID(instance);

mDispatchers.add(*node, new CallbackDispatcher(instance));//CallbackDispatcher dispatch the callback message from omx hardware

instance->setHandle(*node, handle);

mLiveNodes.add(observer->asBinder(), instance);

observer->asBinder()->linkToDeath(this);

return OK;

}

这里面先是创建了一个OMXNodeInstance,然后就是mMaster->makeComponentInstance再就是mDispatchers.add(*node, new CallbackDispatcher(instance))

最后instance->setHandle(*node, handle);

先简单介绍几个数据结构

OMXNodeInstance某一种类型的OMX,跟nodeid 一一对应

mMaste是OMXMaster这个说白了就是在本地OMX和硬件厂商的OMX之间做管理和协调工作的

mDispatchers是CallbackDispatcher类型的数组,它负责消息的分发(从硬件厂商获取消息分发到具体的某一种类型的解码器OMXNodeInstance,最后到前面讲的observer)

instance->setHandle(*node, handle)这里的这个handle就很关键了,所有的操作都必须通过它来完成

=========================================================================

细说一下OMXMaster

构造函数里面

OMXMaster::OMXMaster()

: mVendorLibHandle(NULL) {

addVendorPlugin();

#ifndef NO_OPENCORE

addPlugin(new OMXPVCodecsPlugin);

#endif

}

在没有Opencore的情况下我们只看addVendorPlugin(),这从函数名就可以看出来就是将厂家的插件加入进来。

void OMXMaster::addVendorPlugin() {

mVendorLibHandle = dlopen("libstagefrighthw.so", RTLD_NOW);

if (mVendorLibHandle == NULL) {

return;

}

typedef OMXPluginBase *(*CreateOMXPluginFunc)();

CreateOMXPluginFunc createOMXPlugin =

(CreateOMXPluginFunc)dlsym(

mVendorLibHandle, "_ZN7android15createOMXPluginEv");

if (createOMXPlugin) {

addPlugin((*createOMXPlugin)());

}

}

看到了吧,这里开始使用动态库来调用了,也就是说厂家自己替换这个库就行了。

至于要实现什么东西是有标准的,这里我就不多说了。

像CreateOMXPluginFunc这个函数是一定得有的,也就是所创建了一个厂家提供的OMX插件。

再看

void OMXMaster::addPlugin(OMXPluginBase *plugin) {

Mutex::Autolock autoLock(mLock);

mPlugins.push_back(plugin);

OMX_U32 index = 0;

char name[128];

OMX_ERRORTYPE err;

while ((err = plugin->enumerateComponents(

name, sizeof(name), index++)) == OMX_ErrorNone) {

String8 name8(name);

if (mPluginByComponentName.indexOfKey(name8) >= 0) {

LOGE("A component of name '%s' already exists, ignoring this one.",

name8.string());

continue;

}

mPluginByComponentName.add(name8, plugin);

}

CHECK_EQ(err, OMX_ErrorNoMore);

}

这里有一个数组保存这些插件,plugin->enumerateComponents这句话的意思是说把厂家提供的这个OMX插件支持的解码器格式一一放入到mPluginByComponentName里面,以后需要的这种格式的解码

器就到这里来找。

回到OMXMaster::makeComponentInstance这个函数,它在OMX::allocateNode中被调用的,

OMX_ERRORTYPE OMXMaster::makeComponentInstance(

const char *name,//name is codec type

const OMX_CALLBACKTYPE *callbacks,

OMX_PTR appData,

OMX_COMPONENTTYPE **component) {

Mutex::Autolock autoLock(mLock);

*component = NULL;

ssize_t index = mPluginByComponentName.indexOfKey(String8(name));

if (index < 0) {

return OMX_ErrorInvalidComponentName;

}

OMXPluginBase *plugin = mPluginByComponentName.valueAt(index);

OMX_ERRORTYPE err =

plugin->makeComponentInstance(name, callbacks, appData, component);

if (err != OMX_ErrorNone) {

return err;

}

mPluginByInstance.add(*component, plugin);

return err;

}

可以看到这里就通过插件创建了实例,这里的component就是handle,也是底下返回的。callbacks就是OMXNodeInstance里面的几个回调函数OnEvent,OnEmptyBufferDone,OnFillBufferDone

总结一下操作的流程:

OMXCodec ---> BnOMX ----> OMXNodeInstance -----> handle(看作是厂家OMX的句柄)

消息回调的流程就是

handle ---->OnEvent(OMXNodeInstance) ------->OnEvent(BnOMX)-------->post(CallbackDispatcher)-------->onMessage(OMXNodeInstance)

----->onMessage(OMXCodecObserver)------->on_message(OMXCodec)

操作的流程大家都清楚了,下面正式进入我们最关心的,OMX是怎么来实现解码,又是怎么把解码后的数据交给我们的:

上面的操作流程里面我们知道了解码器的创建,开始解码我们必须调用OMXCodec的start函数

status_t OMXCodec::start(MetaData *meta) {

...

...

...

status_t err = mSource->start(params.get());

if (err != OK) {

return err;

}

...

...

...

return init();

}

我选择了两个比较重要的地方贴了出来。

mSource->start(params.get());

这句话实际是开启了音视频流Track,也就是说准备好压缩数据给解码器去取。

init()里面会调用一个非常重要的函数allocateBuffers();从字面上看是分配内存,没错它就是分配内存的。

status_t OMXCodec::allocateBuffers() {

status_t err = allocateBuffersOnPort(kPortIndexInput);

if (err != OK) {

return err;

}

return allocateBuffersOnPort(kPortIndexOutput);

}

它分配了两块内存,一块用于输入,一块用于输出。

至于这里面的实现不同的厂家又有所不同了,我之前做过的一个项目是从/dev/pmem_adsp这个设备中映射出来的一块内存。当然,内存的大小跟厂家提供的解码器的能力是相关的可以通过

status_t err = mOMX->getParameter(

mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));

来获取(前面我们讲到了调用的流程,这里就不再多说,总之看到调OMX的最终都会到厂家自定义里面去)。申请的内存在代码中被分为一块一块的(了解camera底层实现的应该知道,这个跟camera的风格很类似)

这些内存块会放到一个叫mPortBuffers[inPort/outPort]的容器中

在awesomeplayer里面开始进行播放后不管是视频还是音频都会调OMXCodec里面的read函数。

status_t OMXCodec::read(

MediaBuffer **buffer, const ReadOptions *options) {

*buffer = NULL;

...

...

...

if (mInitialBufferSubmit) {

mInitialBufferSubmit = false;

...

...

...

drainInputBuffers();//key word

if (mState == EXECUTING) {

// Otherwise mState == RECONFIGURING and this code will trigger

// after the output port is reenabled.

fillOutputBuffers();//key word

}

}

...

...

...

while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {

LOGV("NO MORE OUTPUT DATA=============");

mBufferFilled.wait(mLock);//key word

}

if (mState == ERROR) {

return UNKNOWN_ERROR;

}

if (mFilledBuffers.empty()) {

return mSignalledEOS ? mFinalStatus : ERROR_END_OF_STREAM;

}

if (mOutputPortSettingsHaveChanged) {

mOutputPortSettingsHaveChanged = false;

return INFO_FORMAT_CHANGED;

}

size_t index = *mFilledBuffers.begin();

mFilledBuffers.erase(mFilledBuffers.begin());

BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index);//key word ,we got original data

info->mMediaBuffer->add_ref();

*buffer = info->mMediaBuffer;

return OK;

}

read要做的事就是从Track里面读取数据给解码器解码后返回给awesomeplayer。而其中最关键的就是

mOMX->emptyBuffer(

mNode, info->mBuffer, 0, offset,

flags, timestampUs);//在drainInputBuffers中被调用

和mOMX->fillBuffer(mNode, info->mBuffer);//在fillOutputBuffers中被调用

前面一个函数是将从Track读取来的数据交给OMX解码器解码,而后一个函数就是向OMX解码器请求获取解码后的数据。

这两个操作完成后都是有回调的,至于回调的地方大家自己找吧!上面流程里面已经提到过了。

由于时间的原因后面说的比较简洁,但是大家认真看代码再结合我说的看起来应该没问题的。

先就说这么多吧!希望如果有搞驱动或硬件的能贴出来一些厂家OMX具体实现的代码,并且做下讲解,大家一起学习,感激不尽!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: