一、OpenMax簡介
OpenMAX是一個多媒體應用程序的標准。由NVIDIA公司和Khronos™在2006年推出。
它是無授權費的、跨平台的C語言程序接口序列,這些接口對音頻、視頻、靜態圖片的常用操作進行封裝。
它包括三層,分別是應用層(AI)、集成層(IL)和開發層(DL)。其中IL層已經成為了事實上的多媒體框架標准。嵌入式處理器或者多媒體編解碼模塊的硬件生產者,通常提供標准的OpenMax IL層的軟件接口,這樣軟件的開發者就可以基於這個層次的標准化接口進行多媒體程序的開發。
二、OpenMAX/IL: OMX IL - 結構框架
OpenMAX IL 層 API 旨在為媒體組件提供跨平台的可移植能力。這些接口將系統的軟硬件結構進行抽象化。每個組件及其相關的轉換都被封裝在組件接口的內部。OpenMAX IL API 允許用戶去加載,控制,連接以及卸載各獨立的組件。這種極具靈活性的內核結構使得 Intergration Layer 能夠很容易的實現幾乎所有的多媒體應用情形,並且能夠很好的與現有的基於圖像的多媒體框架相結合。
1. 主要的功能和優點
OpenMAX IL API 能夠在應用程序、多媒體框架和編解碼庫,以及其支持的組件(比如,sources 和 sinks)之間建立統一的接口。對於用戶來說,組件自身及其內部的軟硬件結合情況都是完全透明的。其主要功能如下:
• A flexible component-based API core;
• Ability to easily plug in new components ;
• Coverage of targeted domains (audio, video, and imaging) while remaining easily extensible by both the Khronos Group and individual vendors;
• Capable of being implemented as either static or dynamic libraries;
• Retention of key features and configuration options needed by parent software (such as media frameworks);
• Ease of communication between the client and the components and between components themselves;
• Standardized definition of key components so all implementations of such “standard components” expose the same external interface (i.e. same inputs, outputs, and controls).
2. OpenMAX IL 軟件結構
OpenMax的主要概念
客戶端(Client):訪問IL core或IL component的軟件層,可能是位於GUI應用程序的下層,如GStreamer。IL client是一個典型的功能塊,如filter graph multimedia framework, OpenMAX AL, 或application都可以調用它。IL client與OpenMAX IL core進行交互,利用IL core加載和卸載組件、在組件間建立直接通信以及獲得組件方法的入口。
core:相關平台的代碼,具有將IL component載入主存儲器的功能,當應用程序不再需要某組件時,IL core將負責把該組件從存儲器卸去。一般來說,組件一旦載入存儲器,IL core將不在參與應用程序與組件之間的通信。
端口(Port):組件的輸入輸出接口
組件(Component):OpenMax IL的單元,每一個組件實現一種功能。組件按照端口可分類為Source(只有一個輸出端口)、Sink(只有一個輸入端口)和Host組件(一個輸入端口和一個輸出端口),此外有一個Accelerator組件,它具有一個輸入端口,調用了硬件的編解碼器,加速主要體現在這個環節上。
隧道化(Tunneled):讓兩個組件直接連接的方式。通過隧道化可以將不同的組件的一個輸入端口和一個輸出端口連接到一起,在這種情況下,兩個組件的處理過程合並,共同處理。尤其對於單輸入和單輸出的組件,兩個組件將作為類似一個使用。
3. OpenMAX IL 接口
core API:負責動態加載和卸載組件,協助組件間通信。一旦加載組件,API 允許用戶直接與組件進行通信,類似的,core API 允許用戶在組件之間建立tunnel通信通道,一旦建立,core API 不再被需要,其通信直接發生在兩個組件之間。
component API:在IL層,組件代表了獨立的功能模塊。一個組件可以是sources, sinks, codecs, filters, splitters, mixers, or any other data operator.各個組件的參數可以通過一組相關的數據結構,枚舉類型和接口來設置或者獲取。buffer狀態,錯誤信息,以及其他的時間敏感信息會通過回調函數轉發給應用程序。與組件進行數據的交換是通過端口(ports)完成的。類似哦,組件間的tunnel通道也是通過將一個組件的輸出端口連接到另一個組件的輸入端口來建立的。
4. System 組件
OpenMAX IL 定義了三種通信方式:
1)Non-tunneled:用於client 與 component 之間交換data buffers;
2)Tunneling:用於組件之間互相交換data buffers的標准機制;
3)Proprietary communication:用於兩個組件之間直接數據交換的專屬機制,並且可以作為tunneling的備選機制。
openMAX IL的客戶端,通過調用四個OpenMAX IL組件,實現了一個功能。四個組件分別是Source組件、Host組件、Accelerator組件和Sink組件。
· Source組件只有一個輸出端口;
· Host組件有一個輸入端口和一個輸出端口;
· Accelerator組件具有一個輸入端口,調用了硬件的編解碼器,加速主要體現在這個環節上。Accelerator組件和Sink組件通過私有通訊方式在內部進行連接,沒有經過明確的組件端口。
OpenMAL IL在使用的時候,其數據流也有不同的處理方式:既可以經由客戶端,也可以不經由客戶端。 圖中,Source組件到Host組件的數據流就是經過客戶端的; 而Host組件到Accelerator組件的數據流就沒有經過客戶端,使用了隧道化的方式; Accelerator組件和Sink組件甚至可以使用私有的通訊方式。
OpenMAL IL的組件是OpenMax IL實現的核心內容,一個組件以輸入、輸出端口為接口,端口可以被連接到另一個組件上。外部對組件可以發送命令,還進行設置/獲取參數、配置等內容。組件的端口可以包含緩沖區(Buffer)的隊列。 組件的處理的核心內容是:通過輸入端口消耗Buffer,通過輸出端口填充Buffer,由此多組件相聯接可以構成流式的處理。
5. Component 架構
OpenMAL IL中一個組件的結構如圖
組件的功能和其定義的端口類型密切相關,通常情況下:
只有一個輸出端口的,為Source組件; 只有一個輸入端口的,為Sink組件; 有多個輸入端口,一個輸出端口的為Mux組件; 有一個輸入端口,多個輸出端口的為DeMux組件; 輸入輸出端口各一個組件的為中間處理環節,這是最常見的組件。
端口具體支持的數據也有不同的類型。例如,對於一個輸入、輸出端口各一個組件,其輸入端口使用MP3格式的數據,輸出端口使用PCM格式的數據,那么這個組件就是一個MP3解碼組件。
隧道化(Tunneled)是一個關於組件連接方式的概念。通過隧道化可以將不同的組件的一個輸入端口和一個輸出端口連接到一起,在這種情況下,兩個組件的處理過程合並,共同處理。尤其對於單輸入和單輸出的組件,兩個組件將作為類似一個使用。
7. Client與component之間的通信
client通過OMX_EmptyThisBuffer來調用component的輸入端口;
client通過OMX_FillThisBuffer來調用component的輸出端口。
8. Tunneled buffer allocation
對於tunnel的兩個端口,supplier端口會調用UseBuffer函數來要求鄰接的端口來處理buffers;non-supplier端口會接受UseBuffer調用。Component需要遵循以下規則:
1)supplier端口都要提供buffers;
2)在端口上可靠的傳輸buffer配置;
3)通過OMX_EmptyThisBuffer調用將buffer從輸出端口傳遞到另一component的輸入端口;
4)通過OMX_Fill_This_Buffer調用將buffer從輸入端口返回給component的輸出端口。
9. Buffer payload
一般情況下,buffer中可用數據的起始點和范圍由定義在buffer頭中的 pBuffer,nOffset 和 nFilledLen 三個參數來決定。pBuffer指向buffer的起始地址;nOffset代表了buffer起始地址與實際可用數據地址之間的偏移量;nFilledLen表示buffer中連續可用的數據的大小。因此,buffer中可用數據的起始范圍分別為pBuffer + nOffset 和 pBuffer + nOffset + nFilledLen 。
在buffer中數據的存放方式有三種:
1)每個buffer要么填滿,要么部分填滿;
2)每個buffer中存放的壓縮數據都是以完整的幀為單位的;
3)每個buffer中只存放一幀的壓縮數據。
前兩種都要求解碼器在解碼的之前對每幀數據進行解析,第三種情況則不需要解析。
10. Buffer flags and timestamps
Buffer flags 是存放在buffer中的表示特定屬性的數據,比如數據流結束;
Timestamps 是以微秒為單位的存放在buffer中的數據,用來在播放時確定各buffer的播放時刻。
三、接口與頭文件
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標准只有頭文件,沒有標准的庫,設置沒有定義函數接口。對於實現者,需要實現的主要是包含函數指針的結構體
2. 在OMX_Core.h中定義了Core的API函數,應用程序通過它可以進行初始化、處理handle等操作,具體內容如下:
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_Init(void); // 初始化OMX Core,且應該是OMX中第一個被調用的函數;
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_Deinit(void); // 反初始化OMX Core,且應該是OMX中最后一個被;
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_ComponentNameEnum( // 列出系統中所有可用component的名稱;
OMX_OUT OMX_STRING cComponentName,
OMX_IN OMX_U32 nNameLength,
OMX_IN OMX_U32 nIndex);
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_GetHandle( // 根據名稱查找component,並調用component的方法來實例化component;
OMX_OUT OMX_HANDLETYPE* pHandle,
OMX_IN OMX_STRING cComponentName,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_CALLBACKTYPE* pCallBacks);
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_FreeHandle(
OMX_IN OMX_HANDLETYPE hComponent);
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_SetupTunnel( // 在兩個component之間建立tunnel連接
OMX_IN OMX_HANDLETYPE hOutput,
OMX_IN OMX_U32 nPortOutput,
OMX_IN OMX_HANDLETYPE hInput,
OMX_IN OMX_U32 nPortInput);
OMX_API OMX_ERRORTYPE OMX_GetContentPipe(
OMX_OUT OMX_HANDLETYPE *hPipe,
OMX_IN OMX_STRING szURI);
OMX_API OMX_ERRORTYPE OMX_GetComponentsOfRole (
OMX_IN OMX_STRING role,
OMX_INOUT OMX_U32 *pNumComps,
OMX_INOUT OMX_U8 **compNames);
OMX_API OMX_ERRORTYPE OMX_GetRolesOfComponent (
OMX_IN OMX_STRING compName,
OMX_INOUT OMX_U32 *pNumRoles,
OMX_OUT OMX_U8 **roles);
當應用程序需要使用某個component的功能時,其首先需要調用OMX_Init()來對OMX Core進行初始化,然后通過OMX_GetHandle()來實例化component,取得相應的handle。handle實際上是一個指向component對象的void類型指針,其在OMX_Type.h中定義如下,
typedef void* OMX_HANDLETYPE;
OMX_SetupTunnel()用來在兩個component之間建立tunnel連接。
3. 在OMX_Core.h中還定義了數據類型OMX_BUFFERHEADERTYPE,其對象存放在buffer內用來描述該buffer的特性。
具體內容及各字段注釋如下:
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_PTR pPlatformPrivate; /**< pointer to any data the platform wants to associate with this buffer */
OMX_PTR pInputPortPrivate; /**< pointer to any data the input port wants to associate with this buffer */
OMX_PTR pOutputPortPrivate; /**< pointer to any data the output port wants to associate with this buffer */
OMX_HANDLETYPE hMarkTargetComponent; /**< The component that will generate a mark event upon processing this buffer. */
OMX_PTR pMarkData; /**< Application specific data associated with
the mark sent on a mark event to disambiguate this mark from others. */
OMX_U32 nTickCount; /**< Optional entry that the component and
application can update with a tick count
when they access the component. This
value should be in microseconds. Since
this is a value relative to an arbitrary
starting point, this value cannot be used
to determine absolute time. This is an
optional entry and not all components
will update it.*/
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;
4. OMX_Component.h
1)OMX_COMPONENTTYPE類型數據結構,OMX IL 用它來描述一個component,其中包含了可供調用的函數方法。
[cpp] view plain copy
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 (*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)(
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 (*FillThisBuffer)(
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;
EmptyThisBuffer和FillThisBuffer是驅動組件運行的基本的機制,前者表示讓組件消耗緩沖區,表示對應組件輸入的內容;后者表示讓組件填充緩沖區,表示對應組件輸出的內容。
UseBuffer,AllocateBuffer,FreeBuffer為和端口相關的緩沖區管理函數,對於組件的端口有些可以自己分配緩沖區,有些可以使用外部的緩沖區,因此有不同的接口對其進行操作。
SendCommand表示向組件發送控制類的命令。
GetParameter,SetParameter,GetConfig,SetConfig幾個接口用於輔助的參數和配置的設置和獲取。
ComponentTunnelRequest用於組件之間的隧道化連接,其中需要制定兩個組件及其相連的端口。
ComponentDeInit用於組件的反初始化。
提示:OpenMax函數的參數中,經常包含OMX_IN和OMX_OUT等宏,它們的實際內容為空,只是為了標記參數的方向是輸入還是輸出。
四、Openmax 常用函數介紹
1)OMXConfigParser ()The configuration parser API
函數原型
OMX_BOOL OMXConfigParser ( OMX_PTR aInputParameters,OMX_PTR aOutputParameters);
傳遞參數
aInputParameters 指向如下結構
typedef struct
{
OMX_U8* inPtr; //codec 配置頭部指針
OMX_U32 inBytes; //codec 配置頭部長度
OMX_STRING cComponentRole; //OMX codec類型 eg "video_decoder.mpeg4"
OMX_STRING cComponentName; //OMX 組件名稱
} OMXConfigParserInputs;
返回值
OMX_FALSE : 處理codec配置頭部錯誤或不支持該格式
OMX_TURE : 正確處理codec配置頭部
函數作用
填充aOutputParameters,有兩種選擇:audio coded or vedio codec
typedef struct
{
OMX_U16 Channels; //通道:單聲道、立體聲、5.1
OMX_U16 BitsPerSample; //位寬(eg16)
OMX_U32 SamplesPerSec; //采樣率
} AudioOMXConfigParserOutputs;
typedef struct
{
OMX_U32 width; //檢測到的視頻剪輯寬度
OMX_U32 height; //檢測到的視頻剪輯高度
OMX_U32 profile; //參數
OMX_U32 level; //級別?
} VideoOMXConfigParserOutputs;
2)OMX_SetParameter
設定某個參數對應的值
#define OMX_SetParameter( /
hComponent, /
nParamIndex, /
pComponentParameterStructure) /
((OMX_COMPONENTTYPE*)hComponent)->SetParameter( /
hComponent, /
nParamIndex, /
pComponentParameterStructure) /* Macro End */
3)OMX_SetConfig
設定某個config值
#define OMX_SetConfig( /
hComponent, /
nConfigIndex, /
pComponentConfigStructure) /
((OMX_COMPONENTTYPE*)hComponent)->SetConfig( /
hComponent, /
nConfigIndex, /
pComponentConfigStructure) /* Macro End */
注意有時候,需要先停止某個組件(的某些端口),才能設置config 成功
4)OMX_SendCommand
一般的命令有:
OMX_CommandStateSet 、OMX_CommandFlush、OMX_CommandPortDisable" 、 "OMX_CommandPortEnable、CommandMarkBuffer
#define OMX_SendCommand( /
hComponent, /
Cmd, /
nParam, /
pCmdData) /
((OMX_COMPONENTTYPE*)hComponent)->SendCommand( /
hComponent, /
Cmd, /
nParam, /
pCmdData) /* Macro End */
例子:OMXSAFE(OMX_SendCommand(vrenderer, OMX_CommandPortEnable, 1, 0)); // 停下1對應的端口
5)OMX_SetupTunnel
將兩個組件連接起來,實際會引起調用每個組件的ComponentTunnelRequest
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_SetupTunnel(
OMX_IN OMX_HANDLETYPE hOutput,
OMX_IN OMX_U32 nPortOutput,
OMX_IN OMX_HANDLETYPE hInput,
OMX_IN OMX_U32 nPortInput);
例子:
OMXSAFE(OMX_SetupTunnel(reader, 0, vdecoder, 0)); // reader的0端口為出,vdecoder的0端口為入,連接成一個Tunnel
准備好后,就可以設置OMX_StateExecuting,來讓這個流程活動起來了。再以后,就可以通過OMX_StateIdle 來停下。
6)OMX_GetState
#define OMX_GetState( /
hComponent, /
pState) /
((OMX_COMPONENTTYPE*)hComponent)->GetState( /
hComponent, /
pState) /* Macro End */
7)decOutputPortDef
OMX_PARAM_PORTDEFINITIONTYPE decOutputPortDef;
INIT_PARAM(decOutputPortDef);
decOutputPortDef.nPortIndex = 0;
err = OMX_GetParameter(pCtx->hReaderComp,
OMX_IndexParamPortDefinition,
&decOutputPortDef); // 利用IndexParamPortDefinition來得到組件的輸出端口的屬性
videoWidth = decOutputPortDef.format.video.nFrameWidth;
videoHeight = decOutputPortDef.format.video.nFrameHeight;
8) *pOmxBufferHeader
OMX_BUFFERHEADERTYPE *pOmxBufferHeader ;
// tell decoder output port that it will be using our buffer
err = OMX_UseBuffer(hDecodeComp,
&pOmxBufferHeader, //out
OMX_DECODE_OUTPUT_PORT,
NULL,
outSize,
(NvU8*)pOut);
將分配好的pOut指針和他的大小outSize,配成一個OMX_BUF, 並給pOmxBufferHeader,這樣就通過OMX_UseBuffer,來得到一個以后能給他用的Buffer,指針用我們分配的。