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具体实现的代码,并且做下讲解,大家一起学习,感激不尽!

更多相关文章

  1. C语言函数的递归(上)
  2. Android(安卓)Init进程分析
  3. Android(安卓)Looper详解
  4. 转载-Android运行时异常“Binary XML file line # : Error inflat
  5. 开发可统计单词个数的Android驱动程序(3)
  6. Android(安卓)Vold简介(一)
  7. Android(安卓)OpenGL ES
  8. Android与ok6410板子tcp通信
  9. Android(安卓)OkHttp的Cookie自动化管理

随机推荐

  1. android在物联网的应用
  2. android使用include调用内部组件的方法
  3. Android emulator 常用快捷键
  4. android 判断应用程序是否已安装(附带常用
  5. Android使用Ant自动编译签名打包详解
  6. android 控件跟随手指移动
  7. Android发送HTTP POST请求示范
  8. Android 进阶的小技巧整理(整理自第一行代
  9. Android(安卓)View中的setMeasuredDimens
  10. Android中读取assets文件夹中的子文件夹