OpenMAX IL API通过C语言致力于打造可移植媒体组件的阵列平台。这些组件可以是来源(source)、汇出(sink)、编解码器(codec)、过滤器(filter)、分离器(splitter)、混频器(mixers),或任何其他操作。
OpenMAX IL API允许用户加载,控制,连接和卸载各个组件。Android主要的多媒体引擎StageFright是通过IBinder使用OpenMax,用于编解码(Codec)处理。按照OpenMAX的抽象,Android本身是不关心被构造的Codec到底是硬件解码还是软件解码的。
OpenMAX的官网如下:OpenMAX Overview - The Khronos Group Inc
(一)OMX介绍
OpenMAX IL主要内容如下:
客户端(Client):OpenMax IL的调用者
组件(Component):OpenMax IL的单元,每一个组件实现一种功能
端口(Port):组件的输入输出接口
隧道化(Tunneled):让两个组件直接连接的方式
OpenMAX IL的客户端,通过调用四个OpenMAX IL组件,实现了一个功能。四个组件分别是Source组件、Host组件、Accelerator组件和Sink组件。Source组件只有一个输出端口;而Host组件有一个输入端口和一个输出端口;Accelerator组件具有一个输入端口,调用了硬件的编解码器,加速主要体现在这个环节上。Accelerator组件和Sink组件通过私有通讯方式在内部进行连接,没有经过明确的组件端口。
OpenMAX IL在使用的时候,其数据流也有不同的处理方式:既可以经由客户端,也可以不经由客户端。图中Source组件到Host组件的数据流就是经过客户端的;而Host组件到Accelerator组件的数据流就没有经过客户端,使用了隧道化的方式;Accelerator组件和Sink组件甚至可以使用私有的通讯方式。
OpenMAX Core是辅助各个组件运行的部分,它通常需要完成各个组件的初始化等工作,在真正运行过程中,重点是各个OpenMAX IL的组件,OpenMAX Core不是重点,也不是标准。
OpenMAX IL的组件是OpenMax IL实现的核心内容,一个组件以输入、输出端口为接口,端口可以被连接到另一个组件上。外部对组件可以发送命令,还进行设置/获取参数、配置等内容。组件的端口可以包含缓冲区(Buffer)的队列。
组件的处理的核心内容是:通过输入端口消耗Buffer,通过输出端口填充Buffer,由此多组件相联接可以构成流式的处理。OpenMAX IL中一个组件的结构如下图所示:
OpenMAX的这些组件,可以完全构成一个播放器,但是在Android中使用最多的,还是只用OMX的Accelerator组件来做编解码,因为如果纯靠CPU来做软件的编解码的话,会消耗大量的资源,甚至对于现在的高清4K视频,有的CPU甚至都解不动,所以,一般会将这个任务交给VPU(Video Process Unit)来处理,即所谓的硬解。
下面来看看Android中的OMX
Android中的 NuPlayer 就是用openmax来做(Codec)编解码,其实在OpenMAX接口设计中,他不光能用来当编解码,它的组件可以组成一个完整的播放器,包括source、demux、decode、output。但是为什么android只用他来做code呢?应该有如下方面:
1)在整个播放器中,解码器不得不说是最重要的一部分,而且也是最耗资源的一块。如果全靠软解,直接通过cpu来运算,特别是高清视频。别的事你就可以啥都不干了。所以解码器是最需要硬件提供加速的部分。现在的高清解码芯片都是主芯片+DSP结构,解码的工作都是通过DSP来做,不会在过多的占用主芯片。所有将芯片中DSP硬件编解码的能力通过openmax标准接口呈现出来,提供上层播放器来用。我认为这块是openmax最重要的意义。
2)source 主要是和协议打交道,demux 分解容器部分,大多数的容器格式的分解是不需要通过硬件来支持。只是ts流这种格式最可能用到硬件的支持。因为ts格式比较特殊,单包的大小太小了,只有188字节。所以也是为什么现在常见的解码芯片都会提供硬件ts demux 的支持。
3)音视频输出部分video\audio output 这块和操作系统关系十分紧密。可以看看著名开源播放器vlc。vlc 在mac、linux、Windows都有,功能上差别也不大。所以说他是跨平台的,他跨平台跨在哪?主要的工作量还是在音视频解码完之后的输出模块。因为各个系统的图像渲染和音频输出实现方法不同,所以vlc需要针对每个平台实现不同的output。这部分内容放在openmax来显然不合适。
Android中使用的主要是OpenMax的编解码功能。虽然OpenMax也可以生成输入、输出、文件解析-构建等组件,但是在各个系统(不仅是Android)中使用的最多的还是编解码组件。媒体的输入、输出环节和系统的关系很大,引入OpenMax标准比较麻烦;文件解析-构建环节一般不需要使用硬件加速。编解码组件也是最能体现硬件加速的环节,因此最常使用。
(不知道这里还对不对,有没有使用到OMXCodec,还是说直接使用ACodec了?下面这个图中的OMXCodec也值得商榷)
1. android系统中只用openmax来做codec,所以android向上抽象了一层OMXCodec,提供给上层播放器用,也就是上面所讲述的Accelerator组件。播放器中音视频解码器mVideosource、mAudiosource都是OMXCodec的实例。
2. OMXCodec通过IOMX 依赖binder机制 获得 OMX服务,OMX服务才是openmax在android中实现。
3. OMX把软编解码和硬件编解码统一看作插件的形式管理起来。
4、OMX具体实现
OMXNodeInstance负责创建并维护不同的实例,这些实例以node_id作为唯一标识。这样播放器中每个ACodec在OMX服务端都对应有了自己的OMXNodeInstance实例。
OMXMaster用来维护底层软硬件解码库,根据OMXNodeInstance中想要的解码器来创建解码实体组件。
OMXPluginBase模板负责加载组件库,创建组件实例,由OMXMaster管理。Android原生提供的组件都是由SoftOMXPlugin类来管理,这个SoftOMXPlugin类就是继承自OMXPluginBase。(Android源码提供了一些软件解码和编码的组件,它们被抽象为SoftOMXComponent)
OMXClient是客户端用来与OMX IL进行通信的。
内部结构CallbackDispatcher作用是用来接收回调函数的消息
OMXNodeInstance同 CallbackDispatcher一起完成不同实例的消息处理任务
OMXNodeInstance负责创建并维护不同的实例,这些实例以node作为唯一标识。这样播放器中每个ACodec在OMX服务端都对应有了自己的OMXNodeInstance实例。
OMXMaster用来维护底层软硬件解码库,根据OMXNodeInstance中想要的解码器来创建解码实体组件。
OMXPluginBase负责加载组件库,创建组件实例,由OMXMaster管理。Android原生提供的组件都是由SoftOMXPlugin类来管理,这个类就是继承自OMXPluginBase。(Android源码提供了一些软件解码和编码的组件,它们被抽象为SoftOMXComponent)
OMXClient是客户端用来与OMX IL进行通信的。
内部结构CallbackDispatcher作用是用来接收回调函数的消息
OMXNodeInstance + CallbackDispatcher = 合作完成不同实例的消息处理任务
5、ACodec同OMXNodeInstance的消息传递:
ACodec将CodecObserver observer对象通过omx->allocateNode()传递到OMXNodeInstance。
OMXNodeInstance将kCallbacks(OnEvent,OnEmptyBufferDone,OnFillBufferDone)传递给OMX Component
当OMX Component有消息notify上来时,OMXNodeInstance最先收到,然后调用OMX.cpp。将消息在OMX.cpp里面将OMX Component thread转换到CallbackDispatcher线程中处理。CallbackDispatcher又将消息反调到OMXNodeInstance. 最后调用CodecObserver 的onMessage()回到ACodec中
6、 ACodec与OMX组件的关系
ACodec ,CodecObserver和OMXNodeInstance是一一对应的,简单的可以理解它们3个构成了OpenMAX IL的一个Component,每一个node就是一个codec在OMX服务端的标识。当然还有CallbackDispatcher,用于处理codec过来的消息,通过它的post/loop/dispatch来发起接收,从OMX.cpp发送消息,最终通过OMXNodeInstance::onMessage -> CodecObserver::onMessage -> ACodec::onMessage一路往上,当然消息的来源是因为我们有向codec注册OMXNodeInstance::kCallbacks。
而在OMXPluginBase创建组件实例的时候,需要传递一个callback给组件,这个callback用于接收组件的消息,它的实现是在OMXNodeInstance.cpp中。而kcallbacks是OMXNodeInstance的静态成员变量,它内部的三个函数指针分别指向了OMXNodeInstance的三个静态方法,也即是这三个方法与组件进行着消息传递.
在StagefrightMetadataRetriever()函数中(frameworks/av/media/libstagefright/StagefrightMetadataRetriever.cpp):
StagefrightMetadataRetriever::StagefrightMetadataRetriever()
: mParsedMetaData(false),
mAlbumArt(NULL) {
ALOGV("StagefrightMetadataRetriever()");
DataSource::RegisterDefaultSniffers();
CHECK_EQ(mClient.connect(), (status_t)OK);
}
(在N7的代码中,没有这句话了,到时候再找找,是不是在ACodec::UninitializedState::onAllocateComponent函数这里?)
除了注册一些sniff函数外,还有一个看似很简单,实际上蕴含了很多东西的代码,而且很容易忽略,它跳到 frameworks/av/media/libstagefright/OMXClient.cpp中:
status_t OMXClient::connect() {
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> playerbinder = sm->getService(String16("media.player"));
sp<IMediaPlayerService> mediaservice = interface_cast<IMediaPlayerService>(playerbinder);
if (mediaservice.get() == NULL) {
ALOGE("Cannot obtain IMediaPlayerService");
return NO_INIT;
}
sp<IOMX> mediaServerOMX = mediaservice->getOMX();
if (mediaServerOMX.get() == NULL) {
ALOGE("Cannot obtain mediaserver IOMX");
return NO_INIT;
}
// If we don't want to use the codec process, and the media server OMX
// is local, use it directly instead of going through MuxOMX
if (!sCodecProcessEnabled &&
mediaServerOMX->livesLocally(0 /* node */, getpid())) {
mOMX = mediaServerOMX;
return OK;
}
sp<IBinder> codecbinder = sm->getService(String16("media.codec"));
sp<IMediaCodecService> codecservice = interface_cast<IMediaCodecService>(codecbinder);
if (codecservice.get() == NULL) {
ALOGE("Cannot obtain IMediaCodecService");
return NO_INIT;
}
sp<IOMX> mediaCodecOMX = codecservice->getOMX();
if (mediaCodecOMX.get() == NULL) {
ALOGE("Cannot obtain mediacodec IOMX");
return NO_INIT;
}
mOMX = new MuxOMX(mediaServerOMX, mediaCodecOMX);
return OK;
}
它通过获取mediaplayer和mediacodec这两个service来分别获取OMX服务,而在MediaPlayerService.cpp中,它new出来一个OMX:
sp<IOMX> MediaPlayerService::getOMX() {
Mutex::Autolock autoLock(mLock);
if (mOMX.get() == NULL) {
mOMX = new OMX;
}
return mOMX;
}
同样在MediaCodecService.cpp中,它也是new出来一个OMX:
sp<IOMX> MediaCodecService::getOMX() {
Mutex::Autolock autoLock(mLock);
if (mOMX.get() == NULL) {
mOMX = new OMX;
}
return mOMX;
}
在OMX的构造函数中,创建了一个OMXMaster:
OMX::OMX()
: mMaster(new OMXMaster),
mNodeCounter(0) {
}
那么OpenMAX就从OMXMaster开始讲起:
二,组件的加载和管理
1. OMXMaster
这个OMXMaster是用来管理OMXPlugin的,它是一个管理员的角色,负责软解码和厂商硬解码的维护,先来看看它的构造函数(frameworks/av/media/libstagefright/omx/OMXMaster.cpp):
OMXMaster::OMXMaster()
: mVendorLibHandle(NULL) {
......
addVendorPlugin();
addPlugin(new SoftOMXPlugin);
}
addVenderPlugin用于添加厂商自己的Codec(也就是硬件编解码插件),SoftOMXPlugin通过addPlugin添加。我们这里关注厂商要添加自己OMXPlugin decode的流程:
void OMXMaster::addVendorPlugin() {
addPlugin("libstagefrighthw.so");
}
函数的实现很简单,调用addPlugin,把一个共享库的名字作为参数传入。从这里大概可以猜到,厂商私有的codec实现会封装在动态共享库中。接下来继续看下addPlugin的实现:
void OMXMaster::addPlugin(const char *libname) {
mVendorLibHandle = dlopen(libname, RTLD_NOW);
if (mVendorLibHandle == NULL) {
return;
}
typedef OMXPluginBase *(*CreateOMXPluginFunc)();
CreateOMXPluginFunc createOMXPlugin =
(CreateOMXPluginFunc)dlsym(
mVendorLibHandle, "createOMXPlugin");
if (!createOMXPlugin)
createOMXPlugin = (CreateOMXPluginFunc)dlsym(
mVendorLibHandle, "_ZN7android15createOMXPluginEv");
if (createOMXPlugin) {
addPlugin((*createOMXPlugin)());
}
}
函数的实现中首先看到通过dlopen打开共享库,并通过dlsym找到createOMXPlugin函数symbol,然后调用createOMXPlugin,返回OMXPluginBase类型的子类。
这里打开的lib库是:"libstagefrighthw.so",这个库是由 external/fsl_imx_omx/stagefright 下几个文件编译出来的,可以从Android.mk文件中看到这个库名,所以下面对应使用的dlsym函数就是这些文件中的函数,即OMXFSLPlugin_new.cpp文件。
addPlugin对应的是下面这个函数(这个addPlugin函数与上面一个名字相同,进行了重载):
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) {
ALOGE("A component of name '%s' already exists, ignoring this one.",
name8.string());
continue;
}
mPluginByComponentName.add(name8, plugin);
}
if (err != OMX_ErrorNoMore) {
ALOGE("OMX plugin failed w/ error 0x%08x after registering %zu "
"components", err, mPluginByComponentName.size());
}
}
函数的实现通过while循环枚举该plugin中的所有Components,可以简单理解为一个Component对应一种Codec。此时的plugin对应"libstagefrighthw.so", 即OMXFSLPlugin_new.cpp文件,所以去里面仔细看看各个component是如何对应加入的。
先缕缕,上面过程中,首先调用的是createOMXPlugin()函数,然后调用plugin->enumerateComponents函数来枚举所有的components,将OMXFSLPlugin_new.cpp文件中找到的所有plugin都添加到mPluginByComponentName中去。
那么是如何找到所有的插件的,我们到OMXFSLPlugin_new.cpp文件中看看(external/fsl_imx_omx/stagefright/src/OMXFSLPlugin_new.cpp)。
extern "C" {
OMXPluginBase* createOMXPlugin()
{
LOGV("createOMXPlugin");
return (new FSLOMXPlugin());
}
}
new了一个FSLOMXPlugin(),
FSLOMXPlugin() {
OMX_Init();
};
这里的构造函数中,调用了OMX_Init()函数,这个函数定义在(external/fsl_imx_omx/OpenMAXIL/src/core/OMXCore.cpp)中,它是整个OMX的入口函数:
OMX_ERRORTYPE OMX_Init()
{
OMX_ERRORTYPE ret = OMX_ErrorNone;
if(NULL == gCoreHandle) {
gCoreHandle = FSL_NEW(OMXCore, ());
if(NULL == gCoreHandle)
return OMX_ErrorInsufficientResources;
ret = gCoreHandle->OMX_Init();
if(ret != OMX_ErrorNone) {
FSL_DELETE(gCoreHandle);
return ret;
}
CreatePlatformResMgr();
LOG_DEBUG("OMXCore is Created.\n");
}
refCnt ++;
return ret;
}
这里的OMX core是一个全局变量,使用它来管控整个OMX,在这个函数中,首先new了一个OMXCore类,然后调用gCoreHandle->OMX_Init()函数和CreatePlatformResMgr()。
下面来看看gCoreHandle->OMX_Init()函数(external/fsl_imx_omx/OpenMAXIL/src/core/OMXCore.cpp):
OMX_ERRORTYPE OMXCore::OMX_Init()
{
OMX_ERRORTYPE ret = OMX_ErrorNone;
lock = NULL;
if(E_FSL_OSAL_SUCCESS != fsl_osal_mutex_init(&lock, fsl_osal_mutex_normal)) {
LOG_ERROR("Create mutex for camera device failed.\n");
return OMX_ErrorInsufficientResources;
}
LibMgr = FSL_NEW(ShareLibarayMgr, ());
if(LibMgr == NULL)
return OMX_ErrorInsufficientResources;
/** Get component info of the core */
ret = ComponentRegister();
if (ret != OMX_ErrorNone)
{
return ret;
}
/** Get content pipe info of the core */
ret = ContentPipeRegister();
if (ret != OMX_ErrorNone)
{
return ret;
}
return ret;
}
在这个函数中,有3个重要的地方,第一个是new了一个ShareLibarayMgr,通过这个mgr,就可以使用load和getSymbol来获取库里面暴露出来的函数接口。
第二个重点是ComponentRegister()函数,这个函数同样在OMXCore.cpp中:
OMX_ERRORTYPE OMXCore::ComponentRegister()
{
......
ComponentRegisterFile = fsl_osal_getenv_new("COMPONENT_REGISTER_FILE");
if (ComponentRegisterFile == NULL)
{
LOG_WARNING("Can't get component register file.\n");
ComponentRegisterFile = (OMX_STRING)"/etc/omx_registry/component_register";
}
ret_reg = RegistryAnalyser.Open(ComponentRegisterFile);
if (ret_reg != REG_SUCCESS)
{
LOG_ERROR("Open component register file fail.\n");
return OMX_ErrorUndefined;
}
do
{
RegEntry = RegistryAnalyser.GetNextEntry();
if (RegEntry->GetNodeCnt() == 0)
{
LOG_DEBUG("Read register finished.\n");
break;
}
COMPONENT_INFO *pComponentInfo = FSL_NEW(COMPONENT_INFO, ());
if (pComponentInfo == NULL)
{
LOG_ERROR("Can't get memory.\n");
return OMX_ErrorInsufficientResources;
}
fsl_osal_memset(pComponentInfo, 0, sizeof(COMPONENT_INFO));
......
ComponentList.Add(pComponentInfo);
}while (1);
ret_reg = RegistryAnalyser.Close();
if (ret_reg != REG_SUCCESS)
{
LOG_ERROR("Registry analyser close fail.\n");
return OMX_ErrorUndefined;
}
return ret;
}
这个函数中就是打开external/fsl_imx_omx/OpenMAXIL/release/registry/component_register文件,然后一条一条的解读它,把解读出来的信息保存在COMPONENT_INFO结构体中,最后把这些结构体都统一放在List ComponentList;链表中,这个结构体如下所示:
typedef struct _COMPONENT_INFO {
OMX_S8 ComponentName[COMPONENT_NAME_LEN];
OMX_S8 LibName[LIB_NAME_LEN];
OMX_S8 EntryFunction[FUNCTION_NAME_LEN];
List<ROLE_INFO> RoleList;
}COMPONENT_INFO;
可以看到,这个结构体的形式与component_register文件基本相同,当通过这个函数把这些库都注册到OMX后,就可以找到它们的入口函数,这是后话了。
第三个重点是ContentPipeRegister()函数,这个函数与ComponentRegister()函数的实现基本相同,它是去读 external/fsl_imx_omx/OpenMAXIL/release/registry/contentpipe_register文件,最终把content pipe都添加到List ContentPipeList这个链表中。
以前一直不清楚这些个component都是怎么注册到OMX中,今天到这里终于懂了,至于有哪些component,去component_register文件中查看。
OMXCore.cpp维护着ComponentList和ContentPipeList两个链表,这两个List中保存的是从component_register和contentpipe_register文件中读取出来的OMX 插件的链表。下面再次回到OMXMaster::addPlugin(OMXPluginBase *plugin)函数中:
它通过 plugin->enumerateComponents(name, sizeof(name), index++),这三个参数都是传入的参数,当这个函数返回时,OMXCore就会填充好这三个参数:
OMX_ERRORTYPE FSLOMXPlugin::enumerateComponents(
OMX_STRING name,
size_t size,
OMX_U32 index)
{
return OMX_ComponentNameEnum(name, size, index);
}
这个函数是OMXCore.cpp中的函数,
OMX_ERRORTYPE OMXCore::OMX_ComponentNameEnum(
OMX_STRING cComponentName,
OMX_U32 nNameLength,
OMX_U32 nIndex)
{
OMX_ERRORTYPE ret = OMX_ErrorNone;
OMX_U32 nComponentCnt;
COMPONENT_INFO *pComponentInfoPtr;
nComponentCnt = ComponentList.GetNodeCnt();//GetNodeCnt链表中的元素数目
if (nIndex > nComponentCnt-1)
{
LOG_DEBUG("Component index is out of range.\n");
return OMX_ErrorNoMore;
}
pComponentInfoPtr = ComponentList.GetNode(nIndex);//取出链表的一个元素
if(pComponentInfoPtr->ComponentName == NULL)
return OMX_ErrorInsufficientResources;
if (nNameLength >= fsl_osal_strlen((fsl_osal_char *)pComponentInfoPtr->ComponentName)+1)
{
fsl_osal_strcpy((fsl_osal_char *)cComponentName, (fsl_osal_char *)pComponentInfoPtr->ComponentName);//把名字拷贝到传入的形参中
}
else
{
LOG_ERROR("Component name buffer is too small.\n");
return OMX_ErrorInsufficientResources;
}
return ret;
}
这个ComponentList就是保存着从component_register文件中读取出来的component的名字等等信息,通过这一步,就把这些component的名字/入口函数等等信息拷贝到OMXMaster::addPlugin()函数中了。
3. 实例化component
在ACodec::UninitializedState::onAllocateComponent()函数中,有下面一句代码,component的实例化就是从这里开始的:
omx->allocateNode(componentName.c_str(), observer, &node)
跳转到OMX.cpp中的OMX::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, name);
OMX_COMPONENTTYPE *handle;
OMX_ERRORTYPE err = mMaster->makeComponentInstance(
name, &OMXNodeInstance::kCallbacks,
instance, &handle);
if (err != OMX_ErrorNone) {
ALOGE("FAILED to allocate omx component '%s' err=%s(%#x)", name, asString(err), err);
instance->onGetHandleFailed();
return StatusFromOMXError(err);
}
*node = makeNodeID(instance);
mDispatchers.add(*node, new CallbackDispatcher(instance));
instance->setHandle(*node, handle);
mLiveNodes.add(IInterface::asBinder(observer), instance);
IInterface::asBinder(observer)->linkToDeath(this);
return OK;
}
首先是创建了一个OMXNodeInstance,在OMX中每个component对应一个node,而同样的每一个node对应一个OMXNodeInstance,这个OMXNodeInstance的构造函数在OMXNodeInstance.cpp中,挺简单的,暂时不分析。下面看这个mMaster->makeComponentInstance()函数:
OMX_ERRORTYPE OMXMaster::makeComponentInstance(
const char *name,
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;
}
首先通过调用mPluginByComponentName.indexOfKey(String8(name))找到指定名字解码器的索引,然后调用mPluginByComponentName.valueAt(index);返回解码器实例。这mPluginByComponentName在上面讲了,里面保存了所有component的名字和入口函数等。然后调用对应plugin的makeComponentInstance创建出实例,最后将其添加到mPluginByInstance中。
根据上面的分析,这个makeComponentInstance是通过OMXMaster::addPlugin()函数添加进去的,所以此时对应的是OMXFSLPlugin_new.cpp,也就是说是厂商自己的插件,在上面2. 中也讲了如何构建一个自己的插件,需要实现makeComponentInstance函数,所以看看:
OMX_ERRORTYPE FSLOMXPlugin::makeComponentInstance(
const char *name,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component)
{
OMX_ERRORTYPE ret = OMX_ErrorNone;
LOGV("makeComponentInstance, appData: %p", appData);
OMX_HANDLETYPE handle = NULL;
ret = OMX_GetHandle(&handle, (char*)name, appData, (OMX_CALLBACKTYPE*)callbacks);
if(ret != OMX_ErrorNone)
return ret;
FSLOMXWrapper *pWrapper = new FSLOMXWrapper;
if(pWrapper == NULL)
return OMX_ErrorInsufficientResources;
*component = pWrapper->MakeWapper(handle);
if(*component == NULL)
return OMX_ErrorUndefined;
LOGV("makeComponentInstance done, instance is: %p", *component);
return OMX_ErrorNone;
}
这个OMX_GetHandle函数是OMX中定义的接口函数,它的实现在OMXCore.cpp中,它的含义是根据传入的name来得到component的句柄handle。每个component都是OMX_COMPONENTTYPE类型的,这个类型中包含了SendCommand,GetParameter,GetState,AllocateBuffer,EmptyThisBuffer,FillThisBuffer等等一个component会使用到的函数接口,同时每个component还都会有一个handle,其实这个handle是void * 类型的,它指向OMX_COMPONENTTYPE,下面来具体看看它的实现:
OMX_ERRORTYPE OMXCore::OMX_GetHandle(
OMX_HANDLETYPE *pHandle,
OMX_STRING cComponentName,
OMX_PTR pAppData,
OMX_CALLBACKTYPE *pCallBacks)
{
OMX_ERRORTYPE ret = OMX_ErrorNone;
COMPONENT_INFO *pComponentInfoPtr;
HANDLE_INFO *pHandleInfo = FSL_NEW(HANDLE_INFO, ());
OMX_COMPONENTTYPE *pComp = FSL_NEW(OMX_COMPONENTTYPE, ());
OMX_HANDLETYPE hHandle = (OMX_HANDLETYPE)pComp;
fsl_osal_memset(pHandleInfo, 0, sizeof(HANDLE_INFO));
OMX_INIT_STRUCT(pComp, OMX_COMPONENTTYPE);
/** Search component in component list. */
pComponentInfoPtr = SearchComponent(cComponentName);
if (pComponentInfoPtr == NULL)
{
FSL_DELETE(pHandleInfo);
pHandleInfo = NULL;
FSL_DELETE(pComp);
pComp = NULL;
LOG_ERROR("Can't find component based on component name: %s\n", cComponentName);
return OMX_ErrorComponentNotFound;
}
/** Load library and call entry function */
ret = ConstructComponent(pHandleInfo, hHandle, pComponentInfoPtr);
if (ret != OMX_ErrorNone)
{
FSL_DELETE(pHandleInfo);
pHandleInfo = NULL;
FSL_DELETE(pComp);
pComp = NULL;
LOG_ERROR("Load and call entry function fail.\n");
return ret;
}
/** Set callback function */
ret = pComp->SetCallbacks(hHandle, pCallBacks, pAppData);
if (ret != OMX_ErrorNone)
{
FSL_DELETE(pHandleInfo);
pHandleInfo = NULL;
FSL_DELETE(pComp);
pComp = NULL;
LOG_ERROR("Component set callbacks fail.\n");
return ret;
}
/** Record handle info to handle list */
fsl_osal_mutex_lock(lock);
HandleList.Add(pHandleInfo);
fsl_osal_mutex_unlock(lock);
*pHandle = hHandle;
return ret;
}
里面的注释很清楚,首先SearchComponent函数通过名字来从component list中找到对应的component,然后通过ConstructComponent函数来加载对应的库,然后调用这个库的入口函数,这时候,就设置好hHandle了,这个Handle就是在OMX_COMPONENTTYPE类中使用的handle。之后通过pComp->SetCallbacks来设置回调函数,这个pComp是OMX_COMPONENTTYPE类型的,所以就会调用到OMX_COMPONENTTYPE类中的SetCallbacks函数,最后把这个Handle记录到HandleList链表中。
总之,通过OMX_GetHandle()函数,就会根据传入的名字,来找到对应的lib库,同时获得这个lib库的handle,以后就可以通过调用OMX提供的接口函数,即OMX_COMPONENTTYPE类中的函数,来对这个lib库执行对应的操作。
同时由于OMX_COMPONENTTYPE类中提供的是同样的操作函数接口,那么对于不同的lib库,我们使用同样的接口就行,具体的实现各个lib库中不同就行,这样也就达到了OMX的目的,向lib库的上层提供统一的接口函数。
跳出来,回到FSLOMXPlugin::makeComponentInstance()函数中,从OMX_GetHandle()函数后继续执行,先new一个 FSLOMXWrapper类,这个类中有一个WrapperHandle结构体,也是OMX_COMPONENTTYPE类型的,它把这个结构体中的函数结构进行了一层封装,也就是加了一层wrapper,这样,可以对不用的lib库进行一些额外的操作,同时,每个lib库的接口函数都对应一套wrapper的操作。
这个函数的最后一个传入参数是OMX_COMPONENTTYPE 类型的**component,他就对应这个wrapper。
继续跳出,跳到OMXMaster::makeComponentInstance()函数中,这里没有做什么,然后跳到OMX.cpp中的OMX::allocateNode()函数,就完成了这个component的实例化过程。
看完OpenMAX的实例化过程,也许很多人会纳闷,OMX究竟是啥?
这里我就不去说那些官话,OpenMAX IL 层就是位于external/fsl_imx_omx/OpenMAXIL/ghdr文件夹下面的一堆头文件,它规定了一个组件应该有的功能,应该有的函数,上层不需要懂这些函数是怎么实现的,它只需要会用这些函数就行了。而厂商呢,就是去实现这些头文件中的函数就行,它同样不需要懂上层是如何使用的,它按照OpenMAX IL的标准去实现即可,而这,也就是抽象层存在的意义。
OpenMAX IL在Android中一般就是一个组件(Component),它主要用来做Codec,它有两个端口(输入/输出),看下面的图:
就是如图所示的东西,如何去形容一个组件呢?它抽象出来了一个结构体用来表示一个组件:(external/fsl_imx_omx/OpenMAXIL/ghdr/OMX_Component.h)(我把注释都删掉了,因为篇幅太大,不是不重要,相反,那些注释很重要,想要理解就需要好好看那些注释)
typedef struct OMX_COMPONENTTYPE
{
OMX_U32 nSize; //这个结构体的大小
OMX_VERSIONTYPE nVersion; //版本号
OMX_PTR pComponentPrivate; //这个组件的私有数据指针
OMX_PTR pApplicationPrivate;
OMX_ERRORTYPE (*GetComponentVersion)( //获取这个组件的版本号
OMX_IN OMX_HANDLETYPE hComponent,
OMX_OUT OMX_STRING pComponentName,
OMX_OUT OMX_VERSIONTYPE* pComponentVersion,
OMX_OUT OMX_VERSIONTYPE* pSpecVersion,
OMX_OUT OMX_UUIDTYPE* pComponentUUID);
OMX_ERRORTYPE (*SendCommand)( //发送命令
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_COMMANDTYPE Cmd,
OMX_IN OMX_U32 nParam1,
OMX_IN OMX_PTR pCmdData);
OMX_ERRORTYPE (*GetParameter)( //获取这个组件的参数
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nParamIndex,
OMX_INOUT OMX_PTR pComponentParameterStructure);
OMX_ERRORTYPE (*SetParameter)( //设置组件参数
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nIndex,
OMX_IN OMX_PTR pComponentParameterStructure);
OMX_ERRORTYPE (*GetConfig)( //获得组件的配置
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nIndex,
OMX_INOUT OMX_PTR pComponentConfigStructure);
OMX_ERRORTYPE (*SetConfig)( //设置组件的配置
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nIndex,
OMX_IN OMX_PTR pComponentConfigStructure);
OMX_ERRORTYPE (*GetExtensionIndex)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_STRING cParameterName,
OMX_OUT OMX_INDEXTYPE* pIndexType);
OMX_ERRORTYPE (*GetState)( //获取组件的当前状态
OMX_IN OMX_HANDLETYPE hComponent,
OMX_OUT OMX_STATETYPE* pState);
OMX_ERRORTYPE (*UseBuffer)( //为某个端口使用Buffer
OMX_IN OMX_HANDLETYPE hComponent,
OMX_INOUT OMX_BUFFERHEADERTYPE** ppBufferHdr,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_PTR pAppPrivate,
OMX_IN OMX_U32 nSizeBytes,
OMX_IN OMX_U8* pBuffer);
OMX_ERRORTYPE (*AllocateBuffer)( //在某个端口分配Buffer
OMX_IN OMX_HANDLETYPE hComponent,
OMX_INOUT OMX_BUFFERHEADERTYPE** ppBuffer,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_PTR pAppPrivate,
OMX_IN OMX_U32 nSizeBytes);
OMX_ERRORTYPE (*FreeBuffer)( //释放某个端口的Buffer
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE (*EmptyThisBuffer)( //让组件消耗这个Buffer,一般用于组件的输入端口
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE (*FillThisBuffer)( //让组件填充这个Buffer,一般用于组件的输出端口
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE (*SetCallbacks)( //设置组件的回调函数
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_CALLBACKTYPE* pCallbacks,
OMX_IN OMX_PTR pAppData);
OMX_ERRORTYPE (*ComponentDeInit)(
OMX_IN OMX_HANDLETYPE hComponent);
OMX_ERRORTYPE (*UseEGLImage)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_INOUT OMX_BUFFERHEADERTYPE** ppBufferHdr,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_PTR pAppPrivate,
OMX_IN void* eglImage);
OMX_ERRORTYPE (*ComponentRoleEnum)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_OUT OMX_U8 *cRole,
OMX_IN OMX_U32 nIndex);
} OMX_COMPONENTTYPE;
1)EmptyThisBuffer和FillThisBuffer是驱动组件运行的基本的机制,前者表示让组件消耗缓冲区,表示对应组件输入的内容;后者表示让组件填充缓冲区,表示对应组件输出的内容。
2)UseBuffer,AllocateBuffer,FreeBuffer为和端口相关的缓冲区管理函数,对于组件的端口有些可以自己分配缓冲区,有些可以使用外部的缓冲区,因此有不同的接口对其进行操作。
3)SendCommand表示向组件发送控制类的命令。GetParameter,SetParameter,GetConfig,SetConfig几个接口用于辅助的参数和配置的设置和获取。
4)ComponentTunnelRequest用于组件之间的隧道化连接,其中需要制定两个组件及其相连的端口。
5)ComponentDeInit用于组件的反初始化。
OMX_Component.h中端口类型的定义为OMX_PORTDOMAINTYPE枚举类型,内容如下所示:
typedef enum OMX_PORTDOMAINTYPE {
OMX_PortDomainAudio, //音频类型端口
OMX_PortDomainVideo, //视频类型端口
OMX_PortDomainImage, //图像类型端口
OMX_PortDomainOther, //其他类型端口
OMX_PortDomainMax = 0x7ffffff
} OMX_PORTDOMAINTYPE;
OMX_Core.h中定义的枚举类型OMX_STATETYPE命令表示OpenMax的状态机,内容如下所示:
typedef enum OMX_STATETYPE
{
OMX_StateInvalid, /* 组件监测到内部的数据结构被破坏 */
OMX_StateLoaded, /* 组件被加载但是没有完成初始化 */
OMX_StateIdle, /* 组件初始化完成,准备开始 */
OMX_StateExecuting, /* 组件接受了开始命令,正在树立数据 */
OMX_StatePause, /* 组件接受暂停命令*/
OMX_StateWaitForResources, /* 组件正在等待资源 */
OMX_StateKhronosExtensions = 0x6F000000, /* 保留for Khronos */
OMX_StateVendorStartUnused = 0x7F000000, /* 保留for厂商 */
OMX_StateMax = 0X7FFFFFFF
} OMX_STATETYPE;
OpenMax组件的状态机可以由外部的命令改变,也可以由内部发生的情况改变。OpenMax IL组件的状态机的迁移关系如图所示:
OMX_Core.h中定义的枚举类型OMX_COMMANDTYPE表示对组件的命令类型,内容如下所示:
typedef enum OMX_COMMANDTYPE
{
OMX_CommandStateSet, /* 改变状态机器 */
OMX_CommandFlush, /* 刷新数据队列 */
OMX_CommandPortDisable, /* 禁止端口 */
OMX_CommandPortEnable, /* 使能端口 */
OMX_CommandMarkBuffer, /* 标记组件或Buffer用于观察 */
OMX_CommandKhronosExtensions = 0x6F000000, /* 保留for Khronos */
OMX_CommandVendorStartUnused = 0x7F000000, /* 保留for厂商 */
OMX_CommandMax = 0X7FFFFFFF
} OMX_COMMANDTYPE;
OMX_COMMANDTYPE类型在SendCommand调用中作为参数被使用,其中OMX_CommandStateSet就是改变状态机的命令。
说了这么多,可能还是不太懂这些,那么在后面的内容就,就定制一个新的Component来加深理解。
下面这句话是抄的,需要考证:
我们在创建解码器实例的时候传入的是媒体文件的mimeType,拿着这个mimetype我们去匹配可以处理这个格式的解码器,和什么匹配?就是从/etc/media_codecs.xml和/etc/media_codec_performance.xml这两个xml文件中解析出来的数据中匹配,这里记录了平台所支持的每个编解码器的信息,每个信息封装在一个MediaCodecInfo对象中。
匹配后的所有MediaCodecInfo存放在matchingCodecs列表中,然后再拿着这个列表中的每个解码器的ComponentName到mPluginByComponentName中查找对应的plugin。比如MP3那么我们会找到SoftOMXPlugin,然后再从对应的库中调用库内部的createSoftOMXComponent方法创建出SoftMp3这个component,初始化后加入到mPluginByInstance
这里需要添加例子,要不看半天代码都不理解,就以mp4里面的2个codec为例来讲,音频和视频。
每个component与soft的就不一样了,它们都是通过ComponentBase和状态转换来实现的
class VpuDecoder : public VideoFilter
class VideoFilter : public ComponentBase
需要注意的是,此时虽然在OMXMaster的mPluginByComponentName中已经保存了所有的component的名字和入口函数等等,但是它们都还没有实例化,只有在需要用到的时候才会去实例化它们。那什么时候才能够使用到它们呢?下面就从定制一个插件开始讲讲。
三,组件的定制
上面在讲OMXMaster时,其实嵌入了一个知识点,就是如何定制自己的OMX插件,具体实现方法在OMXFSLPlugin_new.cpp文件中,自己的插件首先要继承OMXPluginBase类,并实现抽象类中的方法,这个类相对来说比较简单,定义在frameworks/native/include/media/hardware/OMXPluginBase.h中:
struct OMXPluginBase {
OMXPluginBase() {}
virtual ~OMXPluginBase() {}
virtual OMX_ERRORTYPE makeComponentInstance(
const char *name,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component) = 0;
virtual OMX_ERRORTYPE destroyComponentInstance(
OMX_COMPONENTTYPE *component) = 0;
virtual OMX_ERRORTYPE enumerateComponents(
OMX_STRING name,
size_t size,
OMX_U32 index) = 0;
virtual OMX_ERRORTYPE getRolesOfComponent(
const char *name,
Vector<String8> *roles) = 0;
private:
OMXPluginBase(const OMXPluginBase &);
OMXPluginBase &operator=(const OMXPluginBase &);
};
这个抽象类中,存在4个需要实现的函数,makeComponentInstance,destroyComponentInstance,enumerateComponents,getRolesOfComponent。
而这个定制的插件就是OMXFSLPlugin_new.cpp, 可以看到FSLOMXPlugin 是继承自OMXPluginBase的,它同样实现了上面4个虚函数。
class FSLOMXPlugin : public OMXPluginBase
这4个函数分别是什么作用呢?enumerateComponents是列举出来所有FSL代码中支持的component的名字和入口函数等等,makeComponentInstance函数就是实例化某一个component,destroyComponentInstance相反就是销毁一个component的实例化对象。getRolesOfComponent目前还不清楚。
四, OMX回调机制
然后组件保存回调函数指针
当组件内部完成相关操作后,就会调用到其notifyXXX函数,对应各自的回调函数
OMXNodeInstance的回调函数会调到OMX的相应函数(owner是OMX类型的指针)
OMXNodeInstance::OnEvent调到instance->owner()->OnEvent
OMXNodeInstance::OnEmptyBufferDone调到instance->owner()->OnEmptyBufferDone
OMXNodeInstance::OnFillBufferDone调到instance->owner()->OnFillBufferDone
以OnEmptyBufferDone为例,在OMX的相应函数中有(另外两个类似)
回调消息分发器处理消息CallbackDispatcher::post,即回调分发器post消息
CallbackDispatcher启动之后一直在循环:OMX::CallbackDispatcher::loop(),其中会调用到dispatch
然后CallbackDispatcher::dispatch处理消息,进入到OMXNodeInstance的onMessages,让OMXNodeInstance继续处理消息
CodecObserver的onMessages处理,即客户端(ACodec)处理OMX返回来的消息。CodecObserver里面dup出kWhatOMXMessageList消息,发往ACodec处理。
Implementing custom codecs
Stagefright comes with built-in software codecs for common media formats, but you can also add your own custom hardware codecs as OpenMAX components. To do this, you must create the OMX components and an OMX plugin that hooks together your custom codecs with the Stagefright framework. For example components, see the hardware/ti/omap4xxx/domx/; for an example plugin for the Galaxy Nexus, see hardware/ti/omap4xx/libstagefrighthw.
To add your own codecs:
1. Create your components according to the OpenMAX IL component standard. The component interface is located in the frameworks/native/include/media/OpenMAX/OMX_Component.h file. To learn more about the OpenMAX IL specification, refer to the OpenMAX website.
2. Create a OpenMAX plugin that links your components with the Stagefright service. For the interfaces to create the plugin, see frameworks/native/include/media/hardware/OMXPluginBase.h and HardwareAPI.h header files.
3. Build your plugin as a shared library with the name libstagefrighthw.so in your product Makefile. For example:
LOCAL_MODULE := libstagefrighthw
In your device's Makefile, ensure you declare the module as a product package:
PRODUCT_PACKAGES += \
libstagefrighthw \
...
Exposing codecs to the framework
The Stagefright service parses the system/etc/media_codecs.xml and system/etc/media_profiles.xml to expose the supported codecs and profiles on the device to app developers via the android.media.MediaCodecList and android.media.CamcorderProfile classes. You must create both files in the device/// directory and copy this over to the system image's system/etc directory in your device's Makefile. For example:
PRODUCT_COPY_FILES += \
device/samsung/tuna/media_profiles.xml:system/etc/media_profiles.xml \
device/samsung/tuna/media_codecs.xml:system/etc/media_codecs.xml \
For complete examples, see device/samsung/tuna/media_codecs.xml and device/samsung/tuna/media_profiles.xml .