摘自《Android系统级深入开发—移植与调试》
18.1 OpenMax系统结构和移植内容
OpenMax是一个多媒体应用程序的框架标准。其中,OpenMax IL(集成层)技术规格定义了媒体组件接口,以便在嵌入式器件的流媒体框架中快速集成加速编解码器。
在Android中,OpenMax IL层,通常可以用于多媒体引擎的插件,Android的多媒体引擎OpenCore和StageFright都可以使用OpenMax作为插件,主要用于编解码(Codec)处理。
在Android的框架层,也定义了由Android封装的OpenMax接口,和标准的接口概念基本相同,但是使用C++类型的接口,并且使用了Android的Binder IPC机制。Android封装OpenMax的接口被StageFright使用,OpenCore没有使用这个接口,而是使用其他形式对OpenMax IL层接口进行封装。
Android OpenMax的基本层次结构如图18-1所示。
图18-1 Android中OpenMax的基本层次结构 |
18.1.1 OpenMax系统的结构(1)
1.OpenMax总体层次结构
OpenMax是一个多媒体应用程序的框架标准,由NVIDIA公司和Khronos在2006年推出。
OpenMax是无授权费的,跨平台的应用程序接口API,通过使媒体加速组件能够在开发、集成和编程环节中实现跨多操作系统和处理器硬件平台,提供全面的流媒体编解码器和应用程序便携化。
OpenMax的官方网站如下所示:
http://www.khronos.org/openmax/
OpenMax实际上分成三个层次,自上而下分别是,OpenMax DL(开发层),OpenMax IL(集成层)和OpenMax AL(应用层)。三个层次的内容分别如下所示。
第一层:OpenMax DL(Development Layer,开发层)
OpenMax DL定义了一个API,它是音频、视频和图像功能的集合。供应商能够在一个新的处理器上实现并优化,然后编解码供应商使用它来编写更广泛的编解码器功能。它包括音频信号的处理功能,如FFT和filter,图像原始处理,如颜色空间转换、视频原始处理,以实现例如MPEG-4、H.264、MP3、AAC和JPEG等编解码器的优化。
第二层:OpenMax IL(Integration Layer,集成层)
OpenMax IL作为音频、视频和图像编解码器能与多媒体编解码器交互,并以统一的行为支持组件(例如,资源和皮肤)。这些编解码器或许是软硬件的混合体,对用户是透明的底层接口应用于嵌入式、移动设备。它提供了应用程序和媒体框架,透明的。S编解码器供应商必须写私有的或者封闭的接口,集成进移动设备。IL的主要目的是使用特征集合为编解码器提供一个系统抽象,为解决多个不同媒体系统之间轻便性的问题。
第三层:OpenMax AL(Appliction Layer,应用层)
OpenMax AL API在应用程序和多媒体中间件之间提供了一个标准化接口,多媒体中间件提供服务以实现被期待的API功能。
---------------------------------------------------------------------------------------------------
OpenMax的三个层次如图18-2所示。
OpenMax API将会与处理器一同提供,以使库和编解码器开发者能够高速有效地利用新器件的完整加速潜能,无须担心其底层的硬件结构。该标准是针对嵌入式设备和移动设备的多媒体软件架构。在架构底层上为多媒体的编解码和数据处理定义了一套统一的编程接口,对多媒体数据的处理功能进行系统级抽象,为用户屏蔽了底层的细节。因此,多媒体应用程序和多媒体框架通过OpenMax IL可以以一种统一的方式来使用编解码和其他多媒体数据处理功能,具有了跨越软硬件平台的移植性。
提示:在实际的应用中,OpenMax的三个层次中使用较多的是OpenMax IL集成层,由于操作系统到硬件的差异和多媒体应用的差异,OpenMax的DL和AL层使用相对较少。
图18-2 OpenMax的三个层次 |
2.OpenMax IL层的结构
OpenMax IL目前已经成为了事实上的多媒体框架标准。嵌入式处理器或者多媒体编解码模块的硬件生产者,通常提供标准的OpenMax IL层的软件接口,这样软件的开发者就可以基于这个层次的标准化接口进行多媒体程序的开发。
OpenMax IL的接口层次结构适中,既不是硬件编解码的接口,也不是应用程序层的接口,因此比较容易实现标准化。
OpenMax IL的层次结构如图18-3所示。
图18-3 OpenMax IL的层次结构 |
18.1.1 OpenMax系统的结构(2)
图18-3中的虚线中的内容是OpenMax IL层的内容,其主要实现了OpenMax IL中的各个组件(Component)。对下层,OpenMax IL可以调用OpenMax DL层的接口,也可以直接调用各种Codec实现。对上层,OpenMax IL可以给OpenMax AL 层等框架层(Middleware)调用,也可以给应用程序直接调用。
OpenMax IL主要内容如下所示。
客户端(Client):OpenMax IL的调用者
组件(Component):OpenMax IL的单元,每一个组件实现一种功能
端口(Port):组件的输入输出接口
隧道化(Tunneled):让两个组件直接连接的方式
OpenMax IL的基本运作过程如图18-4所示。
图18-4 OpenMax IL的基本运作过程 |
如图18-4所示,OpenMAL IL的客户端,通过调用四个OpenMAL IL组件,实现了一个功能。四个组件分别是Source组件、Host组件、Accelerator组件和Sink组件。Source组件只有一个输出端口;而Host组件有一个输入端口和一个输出端口;Accelerator组件具有一个输入端口,调用了硬件的编解码器,加速主要体现在这个环节上。Accelerator组件和Sink组件通过私有通讯方式在内部进行连接,没有经过明确的组件端口。
OpenMAL IL在使用的时候,其数据流也有不同的处理方式:既可以经由客户端,也可以不经由客户端。图18-4中,Source组件到Host组件的数据流就是经过客户端的;而Host组件到Accelerator组件的数据流就没有经过客户端,使用了隧道化的方式;Accelerator组件和Sink组件甚至可以使用私有的通讯方式。
OpenMax Core是辅助各个组件运行的部分,它通常需要完成各个组件的初始化等工作,在真正运行过程中,重点是各个OpenMax IL的组件,OpenMax Core不是重点,也不是标准。
OpenMAL IL的组件是OpenMax IL实现的核心内容,一个组件以输入、输出端口为接口,端口可以被连接到另一个组件上。外部对组件可以发送命令,还进行设置/获取参数、配置等内容。组件的端口可以包含缓冲区(Buffer)的队列。
组件的处理的核心内容是:通过输入端口消耗Buffer,通过输出端口填充Buffer,由此多组件相联接可以构成流式的处理。
OpenMAL IL中一个组件的结构如图18-5所示。
图18-5 OpenMAL IL中一个组件的结构 |
组件的功能和其定义的端口类型密切相关,通常情况下:只有一个输出端口的,为Source组件;只有一个输入端口的,为Sink组件;有多个输入端口,一个输出端口的为Mux组件;有一个输入端口,多个输出端口的为DeMux组件;输入输出端口各一个组件的为中间处理环节,这是最常见的组件。
端口具体支持的数据也有不同的类型。例如,对于一个输入、输出端口各一个组件,其输入端口使用MP3格式的数据,输出端口使用PCM格式的数据,那么这个组件就是一个MP3解码组件。
隧道化(Tunneled)是一个关于组件连接方式的概念。通过隧道化可以将不同的组件的一个输入端口和一个输出端口连接到一起,在这种情况下,两个组件的处理过程合并,共同处理。尤其对于单输入和单输出的组件,两个组件将作为类似一个使用。
3.Android中OpenMax的使用情况
Android系统的一些部分对OpenMax IL层进行使用,基本使用的是标准OpenMax IL层的接口,只是进行了简单的封装。标准的OpenMax IL实现很容易以插件的形式加入到Android系统中。
Android的多媒体引擎OpenCore和StageFright都可以使用OpenMax作为多媒体编解码的插件,只是没有直接使用OpenMax IL层提供的纯C接口,而是对其进行了一定的封装(C++封装)。
在Android2.x版本之后,Android的框架层也对OpenMax IL层的接口进行了封装定义,甚至使用Android中的Binder IPC机制。Stagefright使用了这个层次的接口,OpenCore没有使用。
提示:OpenCore使用OpenMax IL层作为编解码插件在前,Android框架层封装OpenMax接口在后面的版本中才引入。
18.1.2 Android OpenMax实现的内容
Android中使用的主要是OpenMax的编解码功能。虽然OpenMax也可以生成输入、输出、文件解析-构建等组件,但是在各个系统(不仅是Android)中使用的最多的还是编解码组件。媒体的输入、输出环节和系统的关系很大,引入OpenMax标准比较麻烦;文件解析-构建环节一般不需要使用硬件加速。编解码组件也是最能体现硬件加速的环节,因此最常使用。
在Android中实现OpenMax IL层和标准的OpenMax IL层的方式基本,一般需要实现以下两个环节。
编解码驱动程序:位于Linux内核空间,需要通过Linux内核调用驱动程序,通常使用非标准的驱动程序。
OpenMax IL层:根据OpenMax IL层的标准头文件实现不同功能的组件。
Android中还提供了OpenMax的适配层接口(对OpenMax IL的标准组件进行封装适配),它作为Android本地层的接口,可以被Android的多媒体引擎调用。
18.2 OpenMax的接口与实现
18.2.1 OpenMax IL层的接口(1)
OpenMax IL层的接口定义由若干个头文件组成,这也是实现它需要实现的内容,它们的基本描述如下所示。
OMX_Types.h:OpenMax Il的数据类型定义
OMX_Core.h:OpenMax IL核心的API
OMX_Component.h:OpenMax IL 组件相关的 API
OMX_Audio.h:音频相关的常量和数据结构
OMX_IVCommon.h:图像和视频公共的常量和数据结构
OMX_Image.h:图像相关的常量和数据结构
OMX_Video.h:视频相关的常量和数据结构
OMX_Other.h:其他数据结构(包括A/V 同步)
OMX_Index.h:OpenMax IL定义的数据结构索引
OMX_ContentPipe.h:内容的管道定义
提示:OpenMax标准只有头文件,没有标准的库,设置没有定义函数接口。对于实现者,需要实现的主要是包含函数指针的结构体。
其中,OMX_Component.h中定义的OMX_COMPONENTTYPE结构体是OpenMax IL层的核心内容,表示一个组件,其内容如下所示:
typedef struct OMX_COMPONENTTYPE { OMX_U32 nSize; /* 这个结构体的大小 */ OMX_VERSIONTYPE nVersion; /* 版本号 */ OMX_PTR pComponentPrivate; /* 这个组件的私有数据指针. */ /* 调用者(IL client)设置的指针,用于保存它的私有数据,传回给所有的回调函数 */ OMX_PTR pApplicationPrivate; /* 以下的函数指针返回OMX_core.h中的对应内容 */ 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结构的索引 */ 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 (*ComponentTunnelRequest)(/* 用于连接到另一个组件*/ OMX_IN OMX_HANDLETYPE hComp, OMX_IN OMX_U32 nPort, OMX_IN OMX_HANDLETYPE hTunneledComp, OMX_IN OMX_U32 nTunneledPort, OMX_INOUT OMX_TUNNELSETUPTYPE* pTunnelSetup); 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;
18.2.1 OpenMax IL层的接口(2)
OMX_COMPONENTTYPE结构体实现后,其中的各个函数指针就是调用者可以使用的内容。各个函数指针和OMX_core.h中定义的内容相对应。
EmptyThisBuffer和FillThisBuffer是驱动组件运行的基本的机制,前者表示让组件消耗缓冲区,表示对应组件输入的内容;后者表示让组件填充缓冲区,表示对应组件输出的内容。
UseBuffer,AllocateBuffer,FreeBuffer为和端口相关的缓冲区管理函数,对于组件的端口有些可以自己分配缓冲区,有些可以使用外部的缓冲区,因此有不同的接口对其进行操作。
SendCommand表示向组件发送控制类的命令。GetParameter,SetParameter,GetConfig,SetConfig几个接口用于辅助的参数和配置的设置和获取。
ComponentTunnelRequest用于组件之间的隧道化连接,其中需要制定两个组件及其相连的端口。
ComponentDeInit用于组件的反初始化。
提示:OpenMax函数的参数中,经常包含OMX_IN和OMX_OUT等宏,它们的实际内容为空,只是为了标记参数的方向是输入还是输出。
OMX_Component.h中端口类型的定义为OMX_PORTDOMAINTYPE枚举类型,内容如下所示:
typedef enum OMX_PORTDOMAINTYPE { OMX_PortDomainAudio, /* 音频类型端口 */ OMX_PortDomainVideo, /* 视频类型端口 */ OMX_PortDomainImage, /* 图像类型端口 */ OMX_PortDomainOther, /* 其他类型端口 */ OMX_PortDomainKhronosExtensions = 0x6F000000, OMX_PortDomainVendorStartUnused = 0x7F000000 OMX_PortDomainMax = 0x7ffffff } OMX_PORTDOMAINTYPE;
音频类型,视频类型,图像类型,其他类型是OpenMax IL层此所定义的四种端口的类型。
端口具体内容的定义使用OMX_PARAM_PORTDEFINITIONTYPE类(也在OMX_Component.h中定义)来表示,其内容如下所示:
typedef struct OMX_PARAM_PORTDEFINITIONTYPE { OMX_U32 nSize; /* 结构体大小 */ OMX_VERSIONTYPE nVersion; /* 版本*/ OMX_U32 nPortIndex; /* 端口号 */ OMX_DIRTYPE eDir; /* 端口的方向 */ OMX_U32 nBufferCountActual; /* 为这个端口实际分配的Buffer的数目 */ OMX_U32 nBufferCountMin; /* 这个端口最小Buffer的数目*/ OMX_U32 nBufferSize; /* 缓冲区的字节数 */ OMX_BOOL bEnabled; /* 是否使能 */ OMX_BOOL bPopulated; /* 是否在填充 */ OMX_PORTDOMAINTYPE eDomain; /* 端口的类型 */ 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_DIRTYPE):包含OMX_DirInput(输入)和OMX_DirOutput(输出)两种
端口分配的缓冲区数目和最小缓冲区数目
端口的类型(OMX_PORTDOMAINTYPE):可以是四种类型
端口格式的数据结构:使用format联合体来表示,具体由四种不同类型来表示,与端口的类型相对应
OMX_AUDIO_PORTDEFINITIONTYPE,OMX_VIDEO_PORTDEFINITIONTYPE,OMX_IMAGE_PORTDEFINITIONTYPE和OMX_OTHER_PORTDEFINITIONTYPE等几个具体的格式类型,分别在OMX_Audio.h,OMX_Video.h,OMX_Image.h和OMX_Other.h这四个头文件中定义。
OMX_BUFFERHEADERTYPE是在OMX_Core.h中定义的,表示一个缓冲区的头部结构。
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, /* 保留 */ OMX_StateVendorStartUnused = 0x7F000000, /* 保留 */ OMX_StateMax = 0X7FFFFFFF } OMX_STATETYPE;
OpenMax组件的状态机可以由外部的命令改变,也可以由内部发生的情况改变。OpenMax IL组件的状态机的迁移关系如图18-6所示。
图18-6 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, /* 保留 */ OMX_CommandVendorStartUnused = 0x7F000000, /* 保留 */ OMX_CommandMax = 0X7FFFFFFF } OMX_COMMANDTYPE;
OMX_COMMANDTYPE类型在SendCommand调用中作为参数被使用,其中OMX_CommandStateSet就是改变状态机的命令。
18.2.2 OpenMax IL实现的内容
对于OpenMax IL层的实现,一般的方式并不调用OpenMax DL层。具体实现的内容就是各个不同的组件。OpenMax IL组件的实现包含以下两个步骤。
组件的初始化函数:硬件和OpenMax数据结构的初始化,一般分成函数指针初始化、私有数据结构的初始化、端口的初始化等几个步骤,使用其中的pComponentPrivate成员保留本组件的私有数据为上下文,最后获得填充完成OMX_COMPONENTTYPE类型的结构体。
OMX_COMPONENTTYPE类型结构体的各个指针:实现其中的各个函数指针,需要使用私有数据的时候,从其中的pComponentPrivate得到指针,转化成实际的数据结构使用。
端口的定义是OpenMax IL组件对外部的接口。OpenMax IL常用的组件大都是输入和输出端口各一个。对于最常用的编解码(Codec)组件,通常需要在每个组件的实现过程中,调用硬件的编解码接口来实现。在组件的内部处理中,可以建立线程来处理。OpenMax的组件的端口有默认参数,但也可以在运行时设置,因此一个端口也可以支持不同的编码格式。音频编码组件的输出和音频编码组件的输入通常是原始数据格式(PCM格式),视频编码组件的输出和视频编码组件的输入通常是原始数据格式(YUV格式)。
提示:在一种特定的硬件实现中,编解码部分具有相似性,因此通常可以构建一个OpenMax组件的"基类"或者公共函数,来完成公共性的操作。
18.2.3 Android中OpenMax的适配层
Android中的OpenMax适配层的接口在frameworks/base/include/media/目录中的IOMX.h文件定义,其内容如下所示:
class IOMX : public IInterface { public: DECLARE_META_INTERFACE(OMX); typedef void *buffer_id; typedef void *node_id; virtual bool livesLocally(pid_t pid) = 0; struct ComponentInfo {// 组件的信息 String8 mName; List<String8> mRoles; }; virtual status_t listNodes(List<ComponentInfo> *list) = 0; // 节点列表 virtual status_t allocateNode( const char *name, const sp<IOMXObserver> &observer, // 分配节点 node_id *node) = 0; virtual status_t freeNode(node_id node) = 0; // 找到节点 virtual status_t sendCommand(// 发送命令 node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) = 0; virtual status_t getParameter(// 获得参数 node_id node, OMX_INDEXTYPE index, void *params, size_t size) = 0; virtual status_t setParameter(// 设置参数 node_id node, OMX_INDEXTYPE index, const void *params, size_t size) = 0; virtual status_t getConfig(// 获得配置 node_id node, OMX_INDEXTYPE index, void *params, size_t size) = 0; virtual status_t setConfig(// 设置配置 node_id node, OMX_INDEXTYPE index, const void *params, size_t size) = 0; virtual status_t useBuffer(// 使用缓冲区 node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer) = 0; virtual status_t allocateBuffer(// 分配缓冲区 node_id node, OMX_U32 port_index, size_t size, buffer_id *buffer, void **buffer_data) = 0; virtual status_t allocateBufferWithBackup(// 分配带后备缓冲区 node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer) = 0; virtual status_t freeBuffer(// 释放缓冲区 node_id node, OMX_U32 port_index, buffer_id buffer) = 0; virtual status_t fillBuffer(node_id node, buffer_id buffer) = 0; // 填充缓冲区 virtual status_t emptyBuffer(// 消耗缓冲区 node_id node, buffer_id buffer, OMX_U32 range_offset, OMX_U32 range_length, OMX_U32 flags, OMX_TICKS timestamp) = 0; virtual status_t getExtensionIndex( node_id node, const char *parameter_name, OMX_INDEXTYPE *index) = 0; virtual sp<IOMXRenderer> createRenderer(// 创建渲染器(从ISurface) const sp<ISurface> &surface, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, size_t encodedWidth, size_t encodedHeight, size_t displayWidth, size_t displayHeight) = 0; sp<IOMXRenderer> createRenderer(// 创建渲染器(从Surface) const sp<Surface> &surface, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, size_t encodedWidth, size_t encodedHeight, size_t displayWidth, size_t displayHeight); sp<IOMXRenderer> createRendererFromJavaSurface(// 从Java层创建渲染器 JNIEnv *env, jobject javaSurface, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, size_t encodedWidth, size_t encodedHeight, size_t displayWidth, size_t displayHeight); };
IOMX表示的是OpenMax的一个组件,根据Android的Binder IPC机制,BnOMX继承IOMX,实现者需要继承实现BnOMX。IOMX类中,除了和标准的OpenMax的GetParameter,SetParameter,GetConfig,SetConfig,SendCommand,UseBuffer,AllocateBuffer,FreeBuffer,FillThisBuffer和EmptyThisBuffer等接口之外,还包含了创造渲染器的接口createRenderer(),创建的接口为IOMXRenderer类型。
IOMX中只有第一个createRenderer()函数是纯虚函数,第二个的createRenderer()函数和createRendererFromJavaSurface()通过调用第一个createRenderer()函数实现。
IOMXRenderer类表示一个OpenMax的渲染器,其定义如下所示:
class IOMXRenderer : public IInterface { public: DECLARE_META_INTERFACE(OMXRenderer); virtual void render(IOMX::buffer_id buffer) = 0; // 渲染输出函数 };
IOMXRenderer只包含了一个render接口,其参数类型IOMX::buffer_id实际上是void*,根据不同渲染器使用不同的类型。
在IOMX.h文件中,另有表示观察器类的IOMXObserver,这个类表示OpenMax的观察者,其中只包含一个onMessage()函数,其参数为omx_message接口体,其中包含Event事件类型、FillThisBuffer完成和EmptyThisBuffer完成几种类型。
提示:Android中OpenMax的适配层是OpenMAX IL层至上的封装层,在Android系统中被StageFright调用,也可以被其他部分调用。
18.3 OMAP平台OpenMax IL的硬件实现
18.3.1 TI OpenMax IL实现的结构和机制
Android的开源代码中,已经包含了TI的OpenMax IL层的实现代码,其路径如下所示:
hardware/ti/omap3/omx/
其中包含的主要目录如下所示。
system:OpenMax核心和公共部分
audio:音频处理部分的OpenMax IL组件
video:视频处理部分OpenMax IL组件
image:图像处理部分OpenMax IL组件
TI OpenMax IL实现的结构如图18-7所示。
在TI OpenMax IL实现中,最上面的内容是OpenMax的管理者用于管理和初始化,中间层是各个编解码单元的OpenMax IL标准组件,下层是LCML层,供各个OpenMax IL标准组件所调用。
TI OpenMax IL实现的公共部分在system/src/openmax_il/目录中,主要的内容如下所示。
omx_core/src:OpenMax IL的核心,生成动态库libOMX_Core.so
lcml/:LCML的工具库,生成动态库libLCML.so
TI OpenMax IL的视频(Video)相关的组件在video/src/openmax_il/目录中,主要的内容如下所示。
prepost_processor:Video数据的前处理和后处理,生成动态库libOMX.TI.VPP.so
video_decode:Video解码器,生成动态库libOMX.TI.Video.Decoder.so
video_encode:Video编码器,生成动态库libOMX.TI.Video.encoder.so
图18-7 TI OpenMax IL实现的结构 |
TI OpenMax IL的音频(Audio)相关的组件在audio/src/openmax_il/目录中,主要的内容如下所示。
g711_dec:G711解码器,生成动态库libOMX.TI.G711.decode.so
g711_enc:G711编码器,生成动态库libOMX.TI.G711.encode.so
g722_dec:G722解码器,生成动态库libOMX.TI.G722.decode.so
g722_enc:G722编码器,生成动态库libOMX.TI.G722.encode.so
g726_dec:G726解码器,生成动态库libOMX.TI.G726.decode.so
g726_enc:G726编码器,生成动态库libOMX.TI.G726.encode.so
g729_dec:G729解码器,生成动态库libOMX.TI.G729.decode.so
g729_enc:G720编码器,生成动态库libOMX.TI.G729.encode.so
nbamr_dec:AMR窄带解码器,生成动态库libOMX.TI.AMR.decode.so
nbamr_enc:AMR窄带编码器,生成动态库libOMX.TI.AMR.encode.so
wbamr_dec:AMR宽带解码器,生成动态库libOMX.TI.WBAMR.decode.so
wbamr_enc:AMR宽带编码器,生成动态库libOMX.TI.WBAMR.encode.so
mp3_dec:MP3解码器,生成动态库libOMX.TI.MP3.decode.so
aac_dec:AAC解码器,生成动态库libOMX.TI.AAC.decode.so
aac_enc:AAC编码器,生成动态库libOMX.TI.AAC.encode.so
wma_dec:WMA解码器,生成动态库libOMX.TI.WMA.decode.so
TI OpenMax IL的图像(Image)相关的组件在image/src/openmax_il/目录中,主要的内容如下所示。
jpeg_enc:JPEG编码器,生成动态库libOMX.TI.JPEG.Encoder.so
jpeg_dec:JPEG解码器,生成动态库libOMX.TI.JPEG.decoder.so
18.3.2 TI OpenMax IL的核心和公共内容
LCML的全称是"Linux Common Multimedia Layer",是TI的Linux公共多媒体层。在OpenMax IL的实现中,这个内容在system/src/openmax_il/lcml/目录中,主要文件是子目录src中的LCML_DspCodec.c文件。通过调用DSPBridge的内容, 让ARM和DSP进行通信,然DSP进行编解码方面的处理。DSP的运行还需要固件的支持。
TI OpenMax IL的核心实现在system/src/openmax_il/omx_core/目录中,生成TI OpenMax IL的核心库libOMX_Core.so。
其中子目录src中的OMX_Core.c为主要文件,其中定义了编解码器的名称等,其片断如下所示:
char *tComponentName[MAXCOMP][2] = { {"OMX.TI.JPEG.decoder", "image_decoder.jpeg"},/* 图像和视频编解码器 */ {"OMX.TI.JPEG.Encoder", "image_encoder.jpeg"}, {"OMX.TI.Video.Decoder", "video_decoder.avc"}, {"OMX.TI.Video.Decoder", "video_decoder.mpeg4"}, {"OMX.TI.Video.Decoder", "video_decoder.wmv"}, {"OMX.TI.Video.encoder", "video_encoder.mpeg4"}, {"OMX.TI.Video.encoder", "video_encoder.h263"}, {"OMX.TI.Video.encoder", "video_encoder.avc"}, /* ......省略 ,语音相关组件*/ #ifdef BUILD_WITH_TI_AUDIO /* 音频编解码器 */ {"OMX.TI.MP3.decode", "audio_decoder.mp3"}, {"OMX.TI.AAC.encode", "audio_encoder.aac"}, {"OMX.TI.AAC.decode", "audio_decoder.aac"}, {"OMX.TI.WMA.decode", "audio_decoder.wma"}, {"OMX.TI.WBAMR.decode", "audio_decoder.amrwb"}, {"OMX.TI.AMR.decode", "audio_decoder.amrnb"}, {"OMX.TI.AMR.encode", "audio_encoder.amrnb"}, {"OMX.TI.WBAMR.encode", "audio_encoder.amrwb"}, #endif {NULL, NULL}, };
tComponentName数组的各个项中,第一个表示编解码库内容,第二个表示库所实现的功能。
其中,TIOMX_GetHandle()函数用于获得各个组件的句柄,其实现的主要片断如下所示:
OMX_ERRORTYPE TIOMX_GetHandle( OMX_HANDLETYPE* pHandle, OMX_STRING cComponentName, OMX_PTR pAppData, OMX_CALLBACKTYPE* pCallBacks) { static const char prefix[] = "lib"; static const char postfix[] = ".so"; OMX_ERRORTYPE (*pComponentInit)(OMX_HANDLETYPE*); OMX_ERRORTYPE err = OMX_ErrorNone; OMX_COMPONENTTYPE *componentType; const char* pErr = dlerror(); // ...... 省略错误处理内容 int i = 0; for(i=0; i< COUNTOF(pModules); i++) { // 循环查找 if(pModules[i] == NULL) break; } // ...... 省略错误处理内容 int refIndex = 0; for (refIndex=0; refIndex < MAX_TABLE_SIZE; refIndex++) { // 循环查找组件列表 if (strcmp(componentTable[refIndex].name, cComponentName) == 0) { if (componentTable[refIndex].refCount>= MAX_CONCURRENT_INSTANCES) { // ...... 省略错误处理内容 } else { char buf[sizeof(prefix) + MAXNAMESIZE+ sizeof(postfix)]; strcpy(buf, prefix); strcat(buf, cComponentName); strcat(buf, postfix); pModules[i] = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL); // ...... 省略错误处理内容 // 动态取出初始化的符号 pComponentInit = dlsym(pModules[i], "OMX_ComponentInit"); pErr = dlerror(); // ...... 省略错误处理内容 *pHandle = malloc(sizeof(OMX_COMPONENTTYPE)); // ...... 省略错误处理内容 pComponents[i] = *pHandle; componentType = (OMX_COMPONENTTYPE*) *pHandle; componentType->nSize = sizeof(OMX_COMPONENTTYPE); err = (*pComponentInit)(*pHandle); // 执行初始化工作 // ...... 省略部分内容 } } } err = OMX_ErrorComponentNotFound; goto UNLOCK_MUTEX; // ...... 省略部分内容 return (err); }
在TIOMX_GetHandle()函数中,根据tComponentName数组中动态库的名称,动态打开各个编解码实现的动态库,取出其中的OMX_ComponentInit符号来执行各个组件的初始化。
18.3.3 一个TI OpenMax IL组件的实现
TI OpenMax IL中各个组件都是通过调用LCML来实现的,实现的方式基本类似。主要都是实现了名称为OMX_ComponentInit的初始化函数,实现OMX_COMPONENTTYPE类型的结构体中的各个成员。各个组件其目录结构和文件结构也类似。
以MP3解码器的实现为例,在audio/src/openmax_il/mp3_dec/src目录中,主要包含以下文件。
OMX_Mp3Decoder.c:MP3解码器组件实现
OMX_Mp3Dec_CompThread.c:MP3解码器组件的线程循环
OMX_Mp3Dec_Utils.c:MP3解码器的相关工具,调用LCML实现真正的MP3解码的功能
OMX_Mp3Decoder.c中的OMX_ComponentInit()函数负责组件的初始化,返回的内容再从参数中得到,这个函数的主要片断如下所示:
OMX_ERRORTYPE OMX_ComponentInit (OMX_HANDLETYPE hComp) { OMX_ERRORTYPE eError = OMX_ErrorNone; OMX_COMPONENTTYPE *pHandle = (OMX_COMPONENTTYPE*) hComp; OMX_PARAM_PORTDEFINITIONTYPE *pPortDef_ip = NULL, *pPortDef_op = NULL; OMX_AUDIO_PARAM_PORTFORMATTYPE *pPortFormat = NULL; OMX_AUDIO_PARAM_MP3TYPE *mp3_ip = NULL; OMX_AUDIO_PARAM_PCMMODETYPE *mp3_op = NULL; MP3DEC_COMPONENT_PRIVATE *pComponentPrivate = NULL; MP3D_AUDIODEC_PORT_TYPE *pCompPort = NULL; MP3D_BUFFERLIST *pTemp = NULL; int i=0; MP3D_OMX_CONF_CHECK_CMD(pHandle,1,1); /* ......省略,初始化OMX_COMPONENTTYPE类型的指针pHandle */ OMX_MALLOC_GENERIC(pHandle->pComponentPrivate, MP3DEC_COMPONENT_PRIVATE); pComponentPrivate = pHandle->pComponentPrivate; /* 私有指针互相指向 */ pComponentPrivate->pHandlepHandle = pHandle; /* ......略,初始化似有数据指针pComponentPrivate */ /* 设置输入端口(OMX_PARAM_PORTDEFINITIONTYPE类型)的默认值 */ pPortDef_ip->nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE); pPortDef_ip->nPortIndex = MP3D_INPUT_PORT; pPortDef_ip->eDir = OMX_DirInput; pPortDef_ip->nBufferCountActual = MP3D_NUM_INPUT_BUFFERS; pPortDef_ip->nBufferCountMin = MP3D_NUM_INPUT_BUFFERS; pPortDef_ip->nBufferSize = MP3D_INPUT_BUFFER_SIZE; pPortDef_ip->nBufferAlignment = DSP_CACHE_ALIGNMENT; pPortDef_ip->bEnabled = OMX_TRUE; pPortDef_ip->bPopulated = OMX_FALSE; pPortDef_ip->eDomain = OMX_PortDomainAudio; pPortDef_ip->format.audio.eEncoding = OMX_AUDIO_CodingMP3; pPortDef_ip->format.audio.cMIMEType = NULL; pPortDef_ip->format.audio.pNativeRender = NULL; pPortDef_ip->format.audio.bFlagErrorConcealment = OMX_FALSE; /* 设置输出端口(OMX_PARAM_PORTDEFINITIONTYPE类型)的默认值 */ pPortDef_op->nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE); pPortDef_op->nPortIndex = MP3D_OUTPUT_PORT; pPortDef_op->eDir = OMX_DirOutput; pPortDef_op->nBufferCountMin = MP3D_NUM_OUTPUT_BUFFERS; pPortDef_op->nBufferCountActual = MP3D_NUM_OUTPUT_BUFFERS; pPortDef_op->nBufferSize = MP3D_OUTPUT_BUFFER_SIZE; pPortDef_op->nBufferAlignment = DSP_CACHE_ALIGNMENT; pPortDef_op->bEnabled = OMX_TRUE; pPortDef_op->bPopulated = OMX_FALSE; pPortDef_op->eDomain = OMX_PortDomainAudio; pPortDef_op->format.audio.eEncoding = OMX_AUDIO_CodingPCM; pPortDef_op->format.audio.cMIMEType = NULL; pPortDef_op->format.audio.pNativeRender = NULL; pPortDef_op->format.audio.bFlagErrorConcealment = OMX_FALSE; /* ......省略,分配端口 */ /* 设置输入端口的默认格式 */ pPortFormat = pComponentPrivate->pCompPort[MP3D_INPUT_PORT]->pPortFormat; OMX_CONF_INIT_STRUCT(pPortFormat, OMX_AUDIO_PARAM_PORTFORMATTYPE); pPortFormat->nPortIndex = MP3D_INPUT_PORT; pPortFormat->nIndex = OMX_IndexParamAudioMp3; pPortFormat->eEncoding = OMX_AUDIO_CodingMP3; /* 设置输出端口的默认格式 */ pPortFormat = pComponentPrivate->pCompPort[MP3D_OUTPUT_PORT]->pPortFormat; OMX_CONF_INIT_STRUCT(pPortFormat, OMX_AUDIO_PARAM_PORTFORMATTYPE); pPortFormat->nPortIndex = MP3D_OUTPUT_PORT; pPortFormat->nIndex = OMX_IndexParamAudioPcm; pPortFormat->eEncoding = OMX_AUDIO_CodingPCM; /* ......省略部分内容 */ eError = Mp3Dec_StartCompThread(pHandle); // 启动MP3解码线程 /* ......省略部分内容 */ return eError; }
这个组件是OpenMax的标准实现方式,对外的接口的内容只有一个初始化函数。完成OMX_COMPONENTTYPE类型的初始化。输入端口的编号为MP3D_INPUT_PORT(==0),类型为OMX_PortDomainAudio,格式为OMX_AUDIO_CodingMP3。输出端口的编号是MP3D_OUTPUT_PORT(==1),类型为OMX_PortDomainAudio,格式为OMX_AUDIO_ CodingPCM。
OMX_Mp3Dec_CompThread.c中定义了MP3DEC_ComponentThread()函数,用于创建MP3解码的线程的执行函数。
OMX_Mp3Dec_Utils.c中的Mp3Dec_StartCompThread()函数,调用了POSIX的线程库建立MP3解码的线程,如下所示:
nRet = pthread_create (&(pComponentPrivate->ComponentThread), NULL, MP3DEC_ComponentThread, pComponentPrivate);
Mp3Dec_StartCompThread()函数就是在组件初始化函数OMX_ComponentInit()最后调用的内容。MP3线程的开始并不表示解码过程开始,线程需要等待通过pipe机制获得命令和数据(cmdPipe和dataPipe),在适当的时候开始工作。这个pipe在MP3解码组件的SendCommand等实现写操作,在线程中读取其内容。