[注] 本文提供一个OpenMAX的入门指引,着重从整体层面进行介绍,并解释重点与关键内容,对于不重要与冗余的内容将惜墨,简单的英文注释将不作翻译。如有错漏,欢迎拍砖。
一、前言
Khronos Group提出一个标准,以创造一个统一的标准接口,不同厂商与开发者可以通过该标准接口提供自身对于音视频的加速能力,这个标准称之为OpenMAX,英文全称Open Media Acceleration。这是一个不需要授权、跨平台的软件抽象层,以C语言实现的软件界面,用于处理音视频多媒体。Android平台目前通过OMX提供音视频硬件加速的能力,从而要求各大厂商在音视频硬件加速上对接到OMX标准。
二、OMX发展背景
随着消费者对音视频、语音和3D等应用需求的增长,多媒体硬件平台的开发步伐正在加快,通常这类产品需要高性能处理和高数据吞吐量能力。因此出现了各种各样的解决方案,都旨在加速多媒体应用程序。包括:
- 具有特定多媒体扩展的通用处理器
- 底层的硬件加速器
- 包括DSPs在内的多处理器体系结构
- 专用硬件视频解码器
所有这些体系结构变体的关键挑战之一是开发有效的代码。尽管通常都提供编译器,但是很少能够从高级编程语言中充分利用整个体系结构的潜力。其结果是,应用程序的大部分通常是用汇编语言编写的,以专门针对硬件平台。不同的多媒体硬件解决方案的大量涌现意味着软件必须为移植到的每个新平台重新编写和优化。
这种执行效率低下的结果是推迟新产品的引进,增加开发成本,降低产品质量,最终在市场需求不断增长的时候减缓了多媒体领域的创新。[2]
在这种背景下,Khronos Group推出OMX标准。 OMX API提供了音频、视频、静态图片的一些常用处理操作的接口。它的目标是降低将多媒体软件移植到新的处理器和体系结构的成本和复杂性。
-
2004.07,OpenMAX最初宣布,工作组最初由ARM、摩托罗拉、三星等公司的成员组成。由非盈利技术联盟Khronos Group管理。
-
2005.12,1.0版本发布。
-
自2012年以来没有任何产品被宣布为符合标准。OpenMAX规范从来没有更新过以支持最近的编解码器,比如HEVC或VP9,这使得Android实际上成为OpenMAX IL标准的更新源。
三、OMX标准
OMX 分成三层:应用层(Application Layer,AL),整合层(Integration Layer,IL)以及开发层(Development Layer,DL) ,实际上,只有OpenMax的IL层得到广泛应用,这得益于安卓平台的推广。 由于操作系统到硬件的差异和多媒体应用的差异,OpenMax的DL和AL层使用相对较少。
- OpenMAX AL(Application Layer,应用层),提供应用程序(如媒体播放器)和多媒体框架(如Android上的StageFright或MediaCodec API、Windows上的DirectShow、FFmpeg或Libav、GStreamer)之间的标准化接口。
- OpenMAX IL(Integration Layer,集成层),提供多媒体框架和多媒体组件(如硬件或软件的音/视频编解码器)之间的标准化接口。
- OpenMAX DL(Development Layer,开发层),提供软件(如视频编解码器和3D引擎)和物理硬件(如DSP、CPUs、GPUs)之间的标准化接口,它包括音频或视频处理功能如fft、过滤器、颜色空间转换、视频处理等,用于加速编解码等处理。[2]
本文以下只针对OpenMAX IL进行讨论。我们先从API层面认识OMX的头文件、关键概念、主要结构体对象以及接口。这些信息都可以从OpenMAX spec中有选择性地获取。
关键名词定义:
关键词 | 意义 |
---|---|
Buffer Supplier | The entity that “owns” the buffer passed into a port. |
Static resources | Component resources that are allocated as a prerequisite to entering the idle state. Most component resources fall into this category. |
Dynamic resources | Any component resources that are allocated after the initial transition to the idle state. Dynamic resource allocation is discouraged and should only occur when the parameters of the allocation (e.g. the size or number of internal memory buffers) is not known at the preferred times to allocate resources. |
IL Client | The layer of software that invokes the methods of the core or component. The IL Client may be a layer below the GUI application, such as GStreamer, or may be several layers below the GUI layer. In this document, the application refers to any software that invokes the OpenMAX IL methods. |
OpenMAX IL component | A component that is intended to wrap functionality that is required in the target system. The OpenMAX IL wrapper provides a standard interface for the function being wrapped. |
OpenMAX IL core | Platform-specific code that has the functionality necessary to locate and then load an OpenMAX IL component into main memory. The core also is responsible for unloading the component from memory when the application indicates that the component is no longer needed. In general, after the OpenMAX IL core loads a component into memory, the core will not participate in communication between the application and the component. |
OMX的头文件及其解释:[OpenMAX spec, P45]
• OMX_Types.h: Data types used in the OpenMAX IL
• OMX_Core.h: OpenMAX IL core API
• OMX_Component.h: OpenMAX IL component API
• OMX_Audio.h: OpenMAX IL audio domain data structures
• OMX_IVCommon.h: OpenMAX IL structures common to image and video domains
• OMX_Video.h: OpenMAX IL video domain data structures
• OMX_Image.h: OpenMAX IL image domain data structures
• OMX_Other.h: OpenMAX IL other domain data structures (includes A/V synchronization)
• OMX_Index.h: Index of all OpenMAX IL-defined data structures
其中,比较重要的头文件包括OMX_Core.h、 OMX_Component.h以及 OMX_Video.h。 OpenMax标准只有头文件,没有标准的库,没有定义函数接口。对于实现者,需要实现的主要是包含函数指针的结构体。
OMX核心类型一览:
每一个类型的解析均参见OpenMAX spec,注意,OMX不同的运行状态之间是如何切换的,要满足什么样的条件才能切换成功,OpenMAX spec中有详细的图解。
OMX的封装接口:
我们一览一下:
/** The OMX_HANDLETYPE structure defines the component handle. The component
* handle is used to access all of the component's public methods and also
* contains pointers to the component's private data area. The component
* handle is initialized by the OMX core (with help from the component)
* during the process of loading the component. After the component is
* successfully loaded, the application can safely access any of the
* component's public functions (although some may return an error because
* the state is inappropriate for the access).
*
* @ingroup comp
*/
typedef struct OMX_COMPONENTTYPE
{
OMX_U32 nSize;
OMX_VERSIONTYPE nVersion;
OMX_PTR pComponentPrivate;
OMX_PTR pApplicationPrivate;
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)(
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)(
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)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE (*EmptyThisBuffer)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE (*FillThisBuffer)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
/** The SetCallbacks method is used by the core to specify the callback
structure from the application to the component. This is a blocking
call. The component will return from this call within 5 msec.
@param [in] hComponent
Handle of the component to be accessed. This is the component
handle returned by the call to the GetHandle function.
@param [in] pCallbacks
pointer to an OMX_CALLBACKTYPE structure used to provide the
callback information to the component
@param [in] pAppData
pointer to an application defined value. It is anticipated that
the application will pass a pointer to a data structure or a "this
pointer" in this area to allow the callback (in the application)
to determine the context of the call
@return OMX_ERRORTYPE
If the command successfully executes, the return code will be
OMX_ErrorNone. Otherwise the appropriate OMX error will be returned.
*/
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 (*ComponentRoleEnum)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_OUT OMX_U8 *cRole,
OMX_IN OMX_U32 nIndex);
} OMX_COMPONENTTYPE;
四、OMX实现
认识与理解OMX的状态、接口与成员对象是实现OMX的关键。
OMX以Core与组件(Component)的形态呈现给开发者。Core用于动态加载与卸载组件。组件可以是source端,也可以是sink端,可以是编码器,也可以是解码器,过滤器,分离器以及混合器,各个组件的接口与调用均保持一致。组件的形式意味着,新组件很容易添加,客户端与组件、组件与组件之间交互简单便捷,数据的输入输出以及组件控制都是一样的接口[OpenMAX spec, P11]。
通过关联的数据结构、枚举类型以及接口,上层可以对组件进行参数设置与参数撤消。这些参数包括与组件操作相关的数据、组件的运行状态。对于时间敏感的数据,包括buffer与error,均采用回调的方式回调给上层,这样可以实现异步特性。对于数据而言,数据本身以及相关联的信息,是通过组件的端口(Port)进行流动的。上层通过输入端口送数据,通过输出端口取出处理后的数据。组件的端口所拥有的Buffer数量与状态,表示了组件的具体运行状态,是Idle,还是Executing,还是Uninitialize。上层只能在特定的组件运行状态下设置特定参数与配置,不同的运行状态下所能进行的数据操作是不一样的,譬如,上层无法在未初化或者闲置状态下进行数据的输入与输出操作。
组件有若干种运行状态,包括UNLOADED、LOADED、WAIT FOR RESOURCES、IDLE、EXECUTING、PAUSED以及INVALID状态。
组件一开始是UNLOADED状态。当CORE加载组件后,组件就切换为LOADED状态。当组件自身分配好静态资源后,组件就进入了IDLE状态。当IL Client送经过交互而确定下来的足够数量的动态资源,也就是Buffer给组件之后,组件就切换到EXECUTING状态。PAUSED状态表示组件正处于不处理数据的状态中。如果组件在状态切换中接收到无效的必要数据,譬如,送下来的回调指针为空,那么,组件可以切换到INVALID状态。最重要的的状态切换是从IDLE状态切换到EXECUTING状态,如果组件没有获取到Buffer,或者没有获取到足够的Buffer,那么状态切换会失败。
接下来看OMX的内部框架。
IL Client通过句柄对组件进行操作,也就是接口调用。当Client获取句柄后,可以通过Set Parameter/Set Config/Get Parameter/Get config等方式与组件进行参数和配置的交互。Client通常会先获取OMX的当前参数与配置,再进行新的参数与配置设置。Client可以通过EmptyThisBuffer的接口送输入BUFFER给组件,通过FillThisBuffer的方式送输出BUFFER给组件。输入与输出端口可以通过回调的方式将处理过的BUFFER送回给Client,并告知BUFFER状态。如果组件内部发生异常与变化,组件可以通过EventHandler的方式通知Client,这些异常与变化包括以下几类:指令完成、错误发生、BUFFER标记被检测到、组件端口设置发生变化以及其他。具体可以查看OMX_Core.h或者[OpenMAX spec, P33]。
我们看一下Client对组件重要的可用的调用接口。以下代码块可见,IL采用宏展开的方式调用到具体组件的API。
openmax/OMX_Core.h
//发送指令给组件,一般是状态切换与FLUSH操作,具体可见Cmd的枚举类型
#define OMX_SendCommand( \
hComponent, \
Cmd, \
nParam, \
pCmdData) \
((OMX_COMPONENTTYPE*)hComponent)->SendCommand( \
hComponent, \
Cmd, \
nParam, \
pCmdData) /* Macro End */
//Client从组件获取当前组件的参数配置信息,具体可见nParamIndex的枚举类型
#define OMX_GetParameter( \
hComponent, \
nParamIndex, \
pComponentParameterStructure) \
((OMX_COMPONENTTYPE*)hComponent)->GetParameter( \
hComponent, \
nParamIndex, \
pComponentParameterStructure) /* Macro End */
//Client向组件设置参数配置,具体可见nParamIndex的枚举类型
#define OMX_SetParameter( \
hComponent, \
nParamIndex, \
pComponentParameterStructure) \
((OMX_COMPONENTTYPE*)hComponent)->SetParameter( \
hComponent, \
nParamIndex, \
pComponentParameterStructure) /* Macro End */
//非常重要,Client将动态资源信息配置给组件,告诉组件这个BUFFER是可用的,
//信息包括BUFFER的指针、大小、私有信息
//Buffer是在Client端申请的,不是组件内部申请的,通过UseBuffer接口告知组件
//之前通过参数交互说好了我们一共用多少个输入BUFFER,Client就得调用多少次该接口
//如果调用次数没有达到约定的个数,组件是不会切换到EXECUTING状态的
//这个接口只是告知BUFFER信息,并未携带负载数据,送负载数据的BUFFER是通过
//EmptyThisBuffer接口送的
#define OMX_UseBuffer( \
hComponent, \
ppBufferHdr, \
nPortIndex, \
pAppPrivate, \
nSizeBytes, \
pBuffer) \
((OMX_COMPONENTTYPE*)hComponent)->UseBuffer( \
hComponent, \
ppBufferHdr, \
nPortIndex, \
pAppPrivate, \
nSizeBytes, \
pBuffer)
//释放BUFFER资源,通常发生在BUFFER重新申请与组件销毁阶段
#define OMX_FreeBuffer( \
hComponent, \
nPortIndex, \
pBuffer) \
((OMX_COMPONENTTYPE*)hComponent)->FreeBuffer( \
hComponent, \
nPortIndex, \
pBuffer) /* Macro End */
//Client送带有负载数据的BUFFER给组件,让组件异步提取其中的数据去处理
#define OMX_EmptyThisBuffer( \
hComponent, \
pBuffer) \
((OMX_COMPONENTTYPE*)hComponent)->EmptyThisBuffer( \
hComponent, \
pBuffer) /* Macro End */
//Client送不带数据的BUFFER给组件,让组件异步将处理好的数据放在这个BUFFER中处理
#define OMX_FillThisBuffer( \
hComponent, \
pBuffer) \
((OMX_COMPONENTTYPE*)hComponent)->FillThisBuffer( \
hComponent, \
pBuffer) /* Macro End */
组件向Client端的回调方式有三种。
openmax/OMX_Core.h
typedef struct OMX_CALLBACKTYPE
{
OMX_ERRORTYPE (*EventHandler)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_EVENTTYPE eEvent,
OMX_IN OMX_U32 nData1,
OMX_IN OMX_U32 nData2,
OMX_IN OMX_PTR pEventData);
//告诉Client,这个BUFFER的数据我消耗完了,你可以取回去了
OMX_ERRORTYPE (*EmptyBufferDone)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
//告诉Client,这个BUFFER我已经放好处理后的输出数据了,你可以拿了
OMX_ERRORTYPE (*FillBufferDone)(
OMX_OUT OMX_HANDLETYPE hComponent,
OMX_OUT OMX_PTR pAppData,
OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer);
} OMX_CALLBACKTYPE;
我们看一下OMX重要的成员对象。
第一个,OMX_BUFFERHEADERTYPE,表示一个BUFFER的对象。该对象包括了BUFFER的指针,内存大小,存在的数据长度,时间戳,标记,端口序号。pBuffer中可以存放真实的数据,也可以存放封装了数据指向信息的结构体变量,譬如Android平台上的native handle。
openmax/OMX_Core.h
typedef struct OMX_BUFFERHEADERTYPE
{
OMX_U32 nSize; /**< size of the structure in bytes */
OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
OMX_U8* pBuffer; /**< Pointer to actual block of memory
that is acting as the buffer */
OMX_U32 nAllocLen; /**< size of the buffer allocated, in bytes */
OMX_U32 nFilledLen; /**< number of bytes currently in the
buffer */
OMX_U32 nOffset; /**< start offset of valid data in bytes from
the start of the buffer */
OMX_PTR pAppPrivate; /**< pointer to any data the application
wants to associate with this buffer */
......
OMX_TICKS nTimeStamp; /**< Timestamp corresponding to the sample
starting at the first logical sample
boundary in the buffer. Timestamps of
successive samples within the buffer may
be inferred by adding the duration of the
of the preceding buffer to the timestamp
of the preceding buffer.*/
OMX_U32 nFlags; /**< buffer specific flags */
OMX_U32 nOutputPortIndex; /**< The index of the output port (if any) using
this buffer */
OMX_U32 nInputPortIndex; /**< The index of the input port (if any) using
this buffer */
} OMX_BUFFERHEADERTYPE;
第二个,OMX_PARAM_PORTDEFINITIONTYPE,表示组件的端口定义信息,nBufferCountActual表示组件对于这个端口所需要的实际BUFFER数量是多少,nBufferCountMin表示组件对于这个端口所需要的最少BUFFER数量是多少,nBufferSize表示BUFFER的大小,bBuffersContiguous表示这个BUFFER是否物理连续的,nBufferAlignment表示这个BUFFER的对齐是多少位,16位还是32位还是其他。eDomain表示这个PORTDEFINITIONTYPE是针对哪一类多媒体信息的,是音频,视频,图像还是其他。OMX_VIDEO_PORTDEFINITIONTYPE表示视频相关信息。
openmax/OMX_Component.h
typedef struct OMX_PARAM_PORTDEFINITIONTYPE {
OMX_U32 nSize; /**< Size of the structure in bytes */
OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
OMX_U32 nPortIndex; /**< Port number the structure applies to */
OMX_DIRTYPE eDir; /**< Direction (input or output) of this port */
OMX_U32 nBufferCountActual; /**< The actual number of buffers allocated on this port */
OMX_U32 nBufferCountMin; /**< The minimum number of buffers this port requires */
OMX_U32 nBufferSize; /**< Size, in bytes, for buffers to be used for this channel */
OMX_BOOL bEnabled; /**< Ports default to enabled and are enabled/disabled by
OMX_CommandPortEnable/OMX_CommandPortDisable.
When disabled a port is unpopulated. A disabled port
is not populated with buffers on a transition to IDLE. */
OMX_BOOL bPopulated; /**< Port is populated with all of its buffers as indicated by
nBufferCountActual. A disabled port is always unpopulated.
An enabled port is populated on a transition to OMX_StateIdle
and unpopulated on a transition to loaded. */
OMX_PORTDOMAINTYPE eDomain; /**< Domain of the port. Determines the contents of metadata below. */
union {
OMX_AUDIO_PORTDEFINITIONTYPE audio;
OMX_VIDEO_PORTDEFINITIONTYPE video;
OMX_IMAGE_PORTDEFINITIONTYPE image;
OMX_OTHER_PORTDEFINITIONTYPE other;
} format;
OMX_BOOL bBuffersContiguous;
OMX_U32 nBufferAlignment;
} OMX_PARAM_PORTDEFINITIONTYPE;
第三个,OMX_VIDEO_PORTDEFINITIONTYPE,这个表示了与视频相关的媒体信息。具体含义见代码注释。硬件解码对于nStride与nSliceHeight是比较敏感的。
openmax/OMX_Video.h
/**
* Data structure used to define a video path. The number of Video paths for
* input and output will vary by type of the Video component.
*
* Input (aka Source) : zero Inputs, one Output,
* Splitter : one Input, 2 or more Outputs,
* Processing Element : one Input, one output,
* Mixer : 2 or more inputs, one output,
* Output (aka Sink) : one Input, zero outputs.
*
* The PortDefinition structure is used to define all of the parameters
* necessary for the compliant component to setup an input or an output video
* path. If additional vendor specific data is required, it should be
* transmitted to the component using the CustomCommand function. Compliant
* components will prepopulate this structure with optimal values during the
* GetDefaultInitParams command.
*
* STRUCT MEMBERS:
* cMIMEType : MIME type of data for the port
* nFrameWidth : Width of frame to be used on channel if
* uncompressed format is used. Use 0 for unknown,
* don't care or variable
* nFrameHeight : Height of frame to be used on channel if
* uncompressed format is used. Use 0 for unknown,
* don't care or variable
* nStride : Number of bytes per span of an image
* (i.e. indicates the number of bytes to get
* from span N to span N+1, where negative stride
* indicates the image is bottom up
* nSliceHeight : Height used when encoding in slices
* nBitrate : Bit rate of frame to be used on channel if
* compressed format is used. Use 0 for unknown,
* don't care or variable
* xFramerate : Frame rate to be used on channel if uncompressed
* format is used. Use 0 for unknown, don't care or
* variable. Units are Q16 frames per second.
* eCompressionFormat : Compression format used in this instance of the
* component. When OMX_VIDEO_CodingUnused is
* specified, eColorFormat is used
* eColorFormat : Decompressed format used by this component
*/
typedef struct OMX_VIDEO_PORTDEFINITIONTYPE {
OMX_STRING cMIMEType;
OMX_U32 nFrameWidth;
OMX_U32 nFrameHeight;
OMX_S32 nStride;
OMX_U32 nSliceHeight;
OMX_U32 nBitrate;
OMX_U32 xFramerate;
OMX_VIDEO_CODINGTYPE eCompressionFormat;
OMX_COLOR_FORMATTYPE eColorFormat;
} OMX_VIDEO_PORTDEFINITIONTYPE;
五、OMX组件化技术
在Android的frameworks层,ACodec会通过C/S模式调用OMX,从而进行解码或编码。OMX有谷歌提供的插件,也有芯片厂商提供的插件,每一个插件包括多个解码与编码组件,譬如avc.decoder.component,或者avc.encoder.component。一般情况下,系统服务启动的时候,会读取/etc/media_codecs.xml这个厂商所提供的编解码组件支持列表以及/vendor/etc/media_codecs_google_video.xml这个谷歌的提供的编解码组件支持列表,并以一定的排序规则写入到组件支持列表(在代码以数组形式存在)中,一般厂商的组件优先,谷歌的组件置后。在播放视频的时候,根据解析到的媒体类型(MIME),譬如是avc/video,将会从组件支持列表中遍历到首个匹配该媒体类型的组件(厂商组件举例:OMX.vendorname.video.decoder.avc,谷歌组件:OMX.google.avc.decoder)。
在此,我们所要分析的是frameworks层如何加载不同厂商的插件,又如何匹配到具体插件,领略OMX的插件化技术。
在omx/1.0/Omx.cpp中,Omx的初化类型主要是实例化OMXMaster类与解析媒体支持列表的XML文件。
Omx::Omx() :
mMaster(new OMXMaster()),
mParser() {
(void)mParser.parseXmlFilesInSearchDirs();
(void)mParser.parseXmlPath(mParser.defaultProfilingResultsXmlPath);
}
在omx/OMXMaster.cpp中,OMXMaster的初始化主要是加载厂商的插件与平台组件(也就是谷歌的插件)。
OMXMaster::OMXMaster() {
......//省略
addVendorPlugin();
addPlatformPlugin();
}
插件的加载需要指定目标加载库,libstagefrighthw.so是厂商提供的库,libstagefright_softomx_plugin.so是谷歌提供的库,两者都提供创建各自插件的接口。
void OMXMaster::addVendorPlugin() {
addPlugin("libstagefrighthw.so");
}
void OMXMaster::addPlatformPlugin() {
addPlugin("libstagefright_softomx_plugin.so");
}
从libstagefrighthw.so与libstagefright_softomx_plugin.so库中提取出CreateOMXPluginFunc接口的句柄,调用该句柄以创建出插件,然后调用addPlugin(plugin)将对于plugin所支持的组件遍历并加载。
void OMXMaster::addPlugin(const char *libname) {
void *libHandle = android_load_sphal_library(libname, RTLD_NOW);
if (libHandle == NULL) {
return;
}
typedef OMXPluginBase *(*CreateOMXPluginFunc)();
CreateOMXPluginFunc createOMXPlugin =
(CreateOMXPluginFunc)dlsym(
libHandle, "createOMXPlugin");
if (!createOMXPlugin)
createOMXPlugin = (CreateOMXPluginFunc)dlsym(
libHandle, "_ZN7android15createOMXPluginEv");
OMXPluginBase *plugin = nullptr;
if (createOMXPlugin) {
plugin = (*createOMXPlugin)();
}
if (plugin) {
mPlugins.push_back({ plugin, libHandle });
addPlugin(plugin);/*该函数将对于plugin所支持的组件遍历并加载*/
} else {
android_unload_sphal_library(libHandle);
}
}
对于plugin所支持的组件遍历并加载的实现过程如下,这样就把所有的组件,不管是厂商的还是谷歌的,都加载到了。
void OMXMaster::addPlugin(OMXPluginBase *plugin) {
Mutex::Autolock autoLock(mLock);
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());
}
}
makeComponentInstance的入参是name,譬如OMX.vendorname.video.avc.decoder,输出是一个component,也就相应的组件实例,由此上层就可以直接与具体的组件打交道了,譬如avc decoder组件,avc encoder组件以及mp3 decoder等,上层调用标准接口,就相应地调用到具体组件。由此,实现了插件化与组件化。
OMX_ERRORTYPE OMXMaster::makeComponentInstance(
const char *name,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component) {
ALOGI("makeComponentInstance(%s) in %s process", name, mProcessName);
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;
}
六、总结
对于新手而言,之所以难以理解OMX,首先是英文Spec文档读不懂,对于标准的含义缺乏了解,其次是代码过于难以理解,其中要求不少的解码背景知识。解决的办法是,从上层MediaCodec往下看OMX,理解OMX的角色与作用,明白什么是标准,跟函数与接口有什么区别,借助网络资料读懂OMX Spec,在了解OMX代码的框架上,对照着理解OMX文档,遇到问题多思考,不放过细节,往往细节中藏着可靠的解码背景知识。对于老手而言,难点只在于几个涉及到解码背景的点,譬如对齐问题、分辨率切换与BUFFER配置等。对于高手而言,他可能会想着这份代码哪里有问题,应该怎么优化,甚至于这套OMX标准的不足之处在哪里。
参考文献
- https://zh.wikipedia.org/wiki/OpenMAX
- https://www.jianshu.com/p/b4426a128208
- OpenMAX_IL_1_1_2_Specification
- 《OpenMAX框架拆解与实现》 https://blog.youkuaiyun.com/u013904227/category_9272882.html