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