一 omx概述
OpenMax是一个多媒体应用程序的框架标准。它自上而下分为三层,Application Layer, Integration Layer和Development Layer。应用层规定了应用程序和多媒体中间层的标准接口,使应用程序的移植性更好。集成层定义了多媒体组件的接口,使得多媒体框架能以一种统一的方式访问多媒体Codec和组件,以便在嵌入式流媒体框架中快速集成加速编解码器。开发层为Codec厂商和硬件厂商提供了一套API,使开发更加便捷。
图 OpenMax的分层结构
omx由组件构成:
1. 组件是独立的一个处理模块,可以有内部独立的线程(但并不一定)处理数据。 通常组件类型有:splitter组件、hot组件、sink组件、clock组件等。
2. 组件之间通过端口进行数据通信,每个组件至少要有一个端口,根据数据方向区分:有输入端口 和 输出端口 两种方向。任意一个端口只能是其中一种方向。
OMXMaster 负责OMX中编解码器插件管理,软件解码和硬件解码都使用OMX标准,挂载plugins的方式来进行管理。 软解通过 addPlugin(new SoftOMXPlugin)把这些编解码器的名字都放在PluginByComponentName。 硬件编解码是通过 addVendorPlugin(); 加载 libstagefrighthw.so.各个芯片平台可以遵循openmax 标准,生成libstagefrighthw.so的库来提供android应用。 android定义软编解码 /frameworks/av/media/libstagefright/omx/OMXMaster.cpp
高通定义 /QNX/qnx_ap/AMSS/multimedia/video/source/common/applications/qplayer/inc/OmxPlayer.h // OMX component name #define QCOM_DEMUXER_COMPONENT_NAME "OMX.qcom.file.demuxer" #define QCOM_IVRENDERER_COMPONENT_NAME "OMX.qcom.video.mmirenderer" #define QCOM_CLOCK_COMPONENT_NAME "OMX.qcom.mmiclock" #define QCOM_VIDEODECODER_COMPONENT_NAME "OMX.qcom.video.decoder" #define QCOM_VIDEODECODER_COMPONENT_NAME_AVC "OMX.qcom.video.decoder.avc" #define QCOM_VIDEODECODER_COMPONENT_NAME_MPEG4 "OMX.qcom.video.decoder.mpeg4" #define QCOM_VIDEODECODER_COMPONENT_NAME_H263 "OMX.qcom.video.decoder.h263" #define QCOM_VIDEODECODER_COMPONENT_NAME_WMV "OMX.qcom.video.decoder.wmv" #define QCOM_VIDEODECODER_COMPONENT_NAME_MPEG2 "OMX.qcom.video.decoder.mpeg2" #define QCOM_VIDEODECODER_COMPONENT_NAME_DIVX "OMX.qcom.video.decoder.divx" #define QCOM_VIDEODECODER_COMPONENT_NAME_DIVX311 "OMX.qcom.video.decoder.divx311" #define QCOM_VIDEODECODER_COMPONENT_NAME_VP8 "OMX.qcom.video.decoder.vp8" #define QCOM_VIDEODECODER_COMPONENT_NAME_SPARK "OMX.qcom.video.decoder.spark" #define QCOM_VIDEODECODER_COMPONENT_NAME_VC1 "OMX.qcom.video.decoder.vc1" #define QCOM_VIDEODECODER_COMPONENT_NAME_HEVC "OMX.qcom.video.decoder.hevc" #define QCOM_VIDEODECODER_COMPONENT_NAME_VP9 "OMX.qcom.video.decoder.vp9" #define QCOM_AUDIODECODER_COMPONENT_NAME "OMX.qcom.audiodecoder" #define QCOM_AUDIORENDERER_COMPONENT_NAME "OMX.qcom.audiorenderer" #define QCOM_RTPSOURCE_COMPONENT_NAME "OMX.qcom.rtpSource" #define QCOM_MPEG2TSDEMUXER_COMPONENT_NAME "OMX.qcom.demuxer.mpeg2ts" #define QCOM_VPP_COMPONENT_NAME "OMX.qcom.vpp" |
3. 端口根据通信的数据类型可以分为四种:Video_Port; Audio_Port; Image_Port; Other_Port; 其中除了音视频和图像数据,其他数据都是通过Other_Port来传递,比较典型的就是Clock全局时钟。
4. 每个组件都有自己独立的运行状态机,总共有6个状态。
OMX_StateInvalid : 组件运行中产生无法恢复的错误,不能再继续进行了,只能卸载组件
OMX_StateLoaded : 组件已经加载到系统中,但是还没有进行初始化
OMX_StateWaitForResources:组件正在等待资源,当资源到位后会切换成Idle状态
OMX_StateIdle:组件初始化完成,一切准备就绪
OMX_StateExecuting:组件正常运行,进行相关数据处理
OMX_StatePause:组件暂停运行
5. 组件支持两种不同的Profile:
1)Base Profile: 仅支持 non-tunnel方式的数据通信,也可能支持Proprietary模式
2)Interop Profile: 必须支持tunnel 和 non-tunnel两种数据通信方式,也可能支持Proprietary模式
OMX集成层由Client、Core、Component和Port组成,Client通过Core得到对应Component的Handle,而后通过命令直接和Component进行交互。每个Component至少有一个Port进行数据交互,如Decoder有一个输入Port接收码流,一个输出Port输出YUV序列。Component内部可能通过消息处理机制完成Client要求的任务。
图 StageFright的OMX结构
调用过程
二 omx用法
文件 | 描述 |
---|---|
OMX_Types.h | 数据类型,里面定义了组件类型、输入输出等 |
OMX_Core.h | IL层的核心API,有命令枚举、状态枚举、组件注册/初始化等等 |
OMX_Component.h | 组件相关的API,有端口类型与定义、组件回调函数成员定义等 |
OMX_Audio.h | 与音频相关的常量和结构体定义 |
OMX_IVCommon.h | 图像与视频通用的一些常量和结构体定义 |
OMX_Video.h | 视频相关的常量和结构体定义 |
OMX_VideoExt.h | 视频相关的常量和数据结构,是对OMX_Video.h的补充扩展 |
OMX_Image.h | 图像相关的常量和结构体定义 |
OMX_Other.h | 其它部分的结构体定义 (包含A/V同步) |
OMX_Index.h | OpenMAX定义的数据结构索引 |
OMX_IndexExt.h | OpenMax 定义的数据结构扩展索引 |
OMX_ContentPipe.h | 内容的管道定义 |
-
OMX_BUFFERSUPPLIERTYPE:用于组件内部端口数据流标识。
-
OMX_COMMANDTYPE:命令相关的枚举,用于Client对组件的命令控制。
-
OMX_STATETYPE:组件状态相关的枚举,用于组件状态标识。
-
OMX_ERRORTYPE:错误相关的枚举,类似于C库的错误状态集,用于标识组件内部或者Client的出错状态。
-
OMX_EVENTTYPE:事件相关的枚举,用于组件内部向Client发送事件通知。
1组件库函数:
- IL层支持的库函数主要有两组:一组是针对所有组件库的Core函数; 另外一组是针对单个组件的函数。
- 大部分的函数操作都是同步调用,并且严格限制函数的执行时间(例如:OMX_SetConfig 限制在5ms内;OMX_UseBuffer 限制在20ms内)。
- OMX_FillThisBuffer() 和 OMX_EmptyThisBuffer()是异步调用,组件在实际执行完成后通过 EmptyBufferDone() 和 FillBufferDone() 两个回调通知执行完成。
- OMX_SendCommand() 函数也是异步调用,支持5种命令调用
1)OMX_CommandStateSet: 切换状态机
2)OMX_CommandFlush: 刷新缓冲区
3)OMX_CommandPortDisable:禁用某个端口
4)OMX_CommandPortEnable 启用某个端口
5)OMX_CommandMarkBuffer:标记缓冲区对象(如:仅解码)
这些命令执行完成后通过 EventHandler() 回调来通知完成
通过EmptyThisBuffer传递未解码的buffer给component,component收到该命令后会去读取input port buffer中的数据,将其组装为帧之后进行解码,buffer处理完成后会通过EmptyBufferDone通知上层输入使用完成,上层收到命令可以继续送输入帧流程。输出buffer方面,通过FillThisBuffer传递填充输出的空buffer给component,component在解码之后通过FillBufferDone通知上层输出填写完成,上层可以继续送待填充的输出帧流程。
2缓冲区对象
3组件通信方式
tunnel模式
- IL Client通过OMX_AllocateBuffer() 在组件A的输出端口上创建缓冲区对象,这个缓冲区对象直接返回给 IL Client。
- IL Client再将这个缓冲区对象,通过 OMX_UseBuffer()指定给组件B的输入端口使用这个缓冲区对象。
- IL Client调用 OMX Core的 OMX_SetupTunnel(hCompA, nOutPortIdx, hCompB, nInPortIdx) 函数来将两个组件的输入输出端口建立隧道通信方式。注:这个函数内部实现会调用两个组件的内部函数ComponentTunnelRequest()来传递组件本身的信息。
- Push数据方式:组件A数据准备完毕后,直接调用组件B上的 OMX_EmptyThisBuffer()方法让组件B取数据,组件B获取完数据后,将OMX_EmptyBufferDone()通知直接回调给组件A,通知组件A这个缓冲区已经清空,可以继续使用了。
- Pull数据方式:组件B需要数据的时候,直接调用组件A上的 OMX_FillThisBuffer()方法让组件A填充数据,组件A填充完成后,将OMX_FillBufferDone()通知直接回调给组件B,通知组件B这个缓冲区上数据可以使用了。
与 Non-tunnel方式主要的差异就是:建立隧道后,组件之间的数据通信不需要IL Client参与了,两个组件内部直接进行。 通常支持Tunnel通信方式的组件都有内部线程,方便数据同步处理
non-tunnel模式
- IL Client通过OMX_AllocateBuffer() 在组件A的输出端口上创建缓冲区对象,这个缓冲区对象直接返回给 IL Client。
- IL Client再将这个缓冲区对象,通过 OMX_UseBuffer()指定给组件B的输入端口使用这个缓冲区对象。
- 在循环的数据处理过程中,IL Client调用OMX_FillThisBuffer() 命令组件A将处理好要输出的数据填入这个缓冲区中,注意:这个调用是异步的,这个调用返回后,缓冲区数据可能还没有填充好。
- 组件A内部先进行数据处理,处理完成后将输出数据填入缓冲区,然后通过 OMX_FillBufferDone()回调函数通知IL Client数据已经准备好。
- IL Client接收到回调后,调用OMX_EmptyThisBuffer()命令组件B来取缓冲区中的数据,注意:这个调用也是异步的,这个调用返回后,缓冲区数据可能还没有取走。
- 组件B内部根据优先处理顺序,将缓冲区中的数据全部取走后,然后通过OMX_EmptyBufferDone()回调函数通知IL Client缓冲区已经清空,可以继续使用。 如此反复来传递缓冲区对象,使得两个组件通过一个缓冲区来进行通信。
- 最后在组件A上释放这个缓冲区对象
proprietary模式
- 组件之间有类似DMA之类的直接通信机制,不需要外部提供缓冲区和控制缓冲区对象传递。
- IL Client 只需要调用 OMX Core的 OMX_SetupTunnel(hCompA, nOutPortIdx, hCompB, nInPortIdx) 函数来将两个组件的输入输出端口建立关联即可。
- 通常这种情况下的组件都是由硬件来实现,直接通过硬件或者系统内部的共享缓冲区来访问数据
- 通信时也不再有 OMX_FillBufferDone() 和 OMX_EmptyBufferDone() 的回调