这几个例子,我只是在KK443上面测试了,还没有移植到N7上面,原理是相同的,我把源码都放出来,有想学习的,自己移植好了)
如何构建一个FSL的OMX组件,我这里写了一个例子,让大家先对OMX的函数流程有个理性的认识,
之前说过,一个OMX上层,它里面不会关心OMX组件内部是如何实现的,它只是使用。同时,一个OMX组件,它只是会按照OMX的标准去实现OMX头文件中规定的那些函数指针。
先来看一个例子:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "OMX_Core.h"
#include "OMX_ContentPipe.h"
int main(int argc, char *argv[])
{
OMX_ERRORTYPE ret = OMX_ErrorNone;
if(argc < 2)
{
printf("Usage: ./bin <in_file>\n");
return 0;
}
ret = OMX_Init();
if (ret != OMX_ErrorNone)
{
printf("OMX init error.\n");
return 0;
}else{
printf("OMX init success. \n");
}
OMX_U8 compName[3][128];
memset(compName, 0, 3*128);
char role[] = "parser.mp3";
OMX_U32 compCnt = 0;
ret = OMX_GetComponentsOfRole(role, &compCnt, NULL);
if (ret != OMX_ErrorNone)
{
printf("1. OMX get component of role error.\n");
return 0;
}else{
printf("1. OMX get component of role success.\n");
printf("1. role = %s, compCnt = %d.\n", role, compCnt);
}
compCnt = 0; //compCnt is both input and output parameter. So after the OMX_GetComponentOfRole function, should reset it to 0.
ret = OMX_GetComponentsOfRole(role, &compCnt, (OMX_U8 **)compName);
if (ret != OMX_ErrorNone)
{
printf("2. OMX get component of role error.\n");
return 0;
}else{
printf("2. OMX get component of role success.\n");
printf("2. role = %s, compCnt = %d, compName = %s.\n", role, compCnt, compName);
}
CP_PIPETYPE *hPipe;
ret = OMX_GetContentPipe((void **)&hPipe, "LOCAL_FILE_PIPE_NEW");
if (ret != OMX_ErrorNone)
{
printf("OMX get content pipe error.\n");
return 0;
}else{
printf("OMX get content pipe success. \n");
}
return 1;
}
相应的Makefile和Android.mk文件如下:
通过这个例子,我们就可以简单测试一个OMX的情况。
但是,有的人这里会说只是测试了OMX核心,我们想要的是怎么从0写组件,下面这个例子,不仅会写一个测试文件,同时,这个测试文件会对应一个OMX组件,这个组件中,它会去实现OMX头文件中规定的那些函数指针。先来看测试文件的实现(完整代码在附件中,这里只分析):
这个文件的功能是:把一个文件通过OMX组件复制一个。。。
main函数的实现:
int main(int argc, char *argv[])
{
HTEST *hTest = NULL;
OMX_STRING component = NULL;
OMX_STRING in_file = NULL, out_file = NULL;
if(argc < 3) {
printf("Unit test of template component.\n");
printf("This test read data from in_file then store data to out_file.\n");
printf("Usage: ./bin <in_file> <out_file>\n");
return 0;
}
OMX_Init();
component = "OMX.Freescale.std.template.sw-based";
in_file = argv[1];
out_file = argv[2];
hTest = create_test(component, in_file, out_file);
if(hTest == NULL) {
printf("Create test failed.\n");
return 0;
}
cmd_process(hTest);
delete_test(hTest);
OMX_Deinit();
return 1;
}
开始使用OMX的话,首先调用的就是OMX_Init(),我们想要使用的OMX组件名字就叫:"OMX.Freescale.std.template.sw-based",然后通过create_test()函数来构建组件,之后跳入一个cmd_process()中,等待用户输入命令。
来看看create_test()函数中做了什么:
HTEST * create_test(
OMX_STRING component,
OMX_STRING in_file,
OMX_STRING out_file)
{
HTEST *hTest = NULL;
hTest = (HTEST*)fsl_osal_malloc_new(sizeof(HTEST));
if(hTest == NULL) {
printf("Failed to allocate memory for test handle.\n");
return 0;
}
fsl_osal_memset(hTest, 0, sizeof(HTEST));
hTest->name = component;
hTest->pMsgQ = FSL_NEW(Queue, ());
if(hTest->pMsgQ == NULL) {
printf("Create message queue failed.\n");
return 0;
}
hTest->pMsgQ->Create(128, sizeof(MSG), E_FSL_OSAL_TRUE);
hTest->pInFile = fopen(in_file, "rb");
if(hTest->pInFile == NULL) {
printf("Failed to open file: %s\n", in_file);
return 0;
}
hTest->pOutFile = fopen(out_file, "wb");
if(hTest->pOutFile == NULL) {
printf("Failed to open file: %s\n", out_file);
return 0;
}
return hTest;
}
可以看到,它主要是对 hTest 里面的成员变量进行赋值,创建Cmd Queue,打开输入/输出文件。
那么下一个就是重点函数cmd_process()了:
OMX_ERRORTYPE cmd_process(HTEST *hTest)
{
char rep[10];
OMX_BOOL bExit = OMX_FALSE;
while(bExit == OMX_FALSE) {
printf("Input test cmd:\n");
printf("[l]load component\n");
printf("[s]state trans\n");
printf("[f]flush\n");
printf("[d]port disbale\n");
printf("[e]port enable\n");
printf("[x]exit\n");
scanf("%s", rep);
if(rep[0] == 'l')
load_component(hTest);
else if(rep[0] == 's') {
OMX_U32 state = 0;
printf("State trans to:\n");
printf("0 -- Invalid\n");
printf("1 -- Loaded\n");
printf("2 -- Idle\n");
printf("3 -- Executing\n");
printf("4 -- Pause\n");
printf("5 -- WaitForResources\n");
scanf("%d", &state);
StateTrans(hTest, (OMX_STATETYPE)state);
}
else if(rep[0] == 'f') {
OMX_U32 nPortIndex = 0;
printf("Want to flush port number:\n");
scanf("%d", &nPortIndex);
port_flush(hTest, nPortIndex);
}
else if(rep[0] == 'd') {
OMX_U32 nPortIndex = 0;
printf("Want to disbale port number:\n");
scanf("%d", &nPortIndex);
port_disable(hTest, nPortIndex);
}
else if(rep[0] == 'e') {
OMX_U32 nPortIndex = 0;
printf("Want to enable port number:\n");
scanf("%d", &nPortIndex);
port_enable(hTest, nPortIndex);
}
else if(rep[0] == 'x') {
MSG sMsg;
sMsg.type = EXIT;
hTest->pMsgQ->Add(&sMsg);
hTest->bStop = OMX_TRUE;
bExit = OMX_TRUE;
unload_component(hTest);
}
}
return OMX_ErrorNone;
}
这个函数就是核心函数了。本身设计思想是这样的,进入主函数后,等待用户输入命令:
输入“l”时,就通过load_component()来加载组件;
输入“s”时,进入状态转换菜单,用户再次输入数字就通过StateTrans()函数来进行状态转换;
输入“f”时,再次输入端口号,就可以Flush这个端口;
输入“d”时,disable某个端口;
输入“e”时,enable某个端口;
输入“x”时,执行unload_component()函数,退出。
下面一个一个分析这些功能:
1)load_component()来加载组件:
OMX_ERRORTYPE load_component(HTEST *hTest)
{
OMX_ERRORTYPE ret = OMX_ErrorNone;
OMX_HANDLETYPE hComponent = NULL;
OMX_PARAM_PORTDEFINITIONTYPE sPortDef;
OMX_U32 i;
ret = OMX_GetHandle(&hComponent, hTest->name, hTest, &gCallBacks);
if(ret != OMX_ErrorNone) {
printf("Load component %s failed.\n", hTest->name);
return ret;
}
hTest->hComponent = (OMX_COMPONENTTYPE*)hComponent;
hTest->nPorts = get_component_ports(hComponent);
OMX_INIT_STRUCT(&sPortDef, OMX_PARAM_PORTDEFINITIONTYPE);
for(i=0; i<hTest->nPorts; i++) {
sPortDef.nPortIndex = i;
OMX_GetParameter(hComponent, OMX_IndexParamPortDefinition, &sPortDef);
hTest->PortDir[i] = sPortDef.eDir;
if(hTest->PortDir[i] == OMX_DirInput)
hTest->bAllocater[i] = OMX_FALSE;
if(hTest->PortDir[i] == OMX_DirOutput)
hTest->bAllocater[i] = OMX_TRUE;
}
fsl_osal_thread_create(&hTest->pThreadId, NULL, process_thread, hTest);
return OMX_ErrorNone;
}
加载一个组件,首先通过OMX_GetHandle()传入想要获取组件的名字,然后就会获得这个组件的Handler,保存在hComponent中,讲这个组件的Handler也保存到hTest中,方便以后使用。
hTest->nPorts中记录了这个组件有几个端口,组件的端口数肯定是组件中已经写好的,那么如果获得这个组件的端口数呢?我们已经知道了组件的Handler了,就通过这个Handler用OMX_GetParameter函数来获取,这个函数的第二个参数决定了你想要获得的资源,之前的介绍中说了,端口一共有4中:Video,Audio,Image,Other,每个端口用一个OMX_PORT_PARAM_TYPE sPortPara结构体来描述:
OMX_U32 get_component_ports(OMX_HANDLETYPE hComponent)
{
OMX_PORT_PARAM_TYPE sPortPara;
OMX_U32 aPorts, vPorts, iPorts, oPorts;
OMX_INIT_STRUCT(&sPortPara, OMX_PORT_PARAM_TYPE);
aPorts = vPorts = iPorts = oPorts = 0;
if(OMX_ErrorNone == OMX_GetParameter(hComponent, OMX_IndexParamAudioInit, &sPortPara))
aPorts = sPortPara.nPorts;
if(OMX_ErrorNone == OMX_GetParameter(hComponent, OMX_IndexParamVideoInit, &sPortPara))
vPorts = sPortPara.nPorts;
if(OMX_ErrorNone == OMX_GetParameter(hComponent, OMX_IndexParamImageInit, &sPortPara))
iPorts = sPortPara.nPorts;
if(OMX_ErrorNone == OMX_GetParameter(hComponent, OMX_IndexParamOtherInit, &sPortPara))
oPorts = sPortPara.nPorts;
return aPorts + vPorts + iPorts + oPorts;
}
获取到端口的数目后,我们需要知道每个端口的方向。为什么需要这个呢?因为如果为输出端口的话,我们需要外部分配buffer来接收组件输出的数据。还是通过OMX_GetParameter(hComponent, OMX_IndexParamPortDefinition, &sPortDef);来获取每个端口的方向,如果为输出端口的话,我们用一个数组来记录它需要被分配内存。
下面的核心就是开新线程了,这个线程中会做那些内容呢?
void * process_thread(void *ptr)
{
HTEST *hTest = (HTEST*)ptr;
OMX_BUFFERHEADERTYPE *pBufferHdr = NULL;
MSG sMsg;
while(1) {
hTest->pMsgQ->Get(&sMsg);
if(sMsg.type == EVENT) {
process_event(hTest, &sMsg);
}
else if(sMsg.type == EMPTY_DONE) {
pBufferHdr = sMsg.data.buffer.pBuffer;
if(hTest->bHoldBuffers != OMX_TRUE) {
read_data(hTest, pBufferHdr);
OMX_EmptyThisBuffer(sMsg.hComponent, pBufferHdr);
}
}
else if(sMsg.type == FILL_DONE) {
pBufferHdr = sMsg.data.buffer.pBuffer;
if(hTest->bHoldBuffers != OMX_TRUE)
OMX_FillThisBuffer(sMsg.hComponent, pBufferHdr);
}
if(hTest->bStop == OMX_TRUE)
break;
}
return NULL;
}
其实就是处理Cmd Queue,从Queue里面取出来Cmd然后处理。至于数据处理的流程,在后面再分析。
2)StateTrans函数
OMX_ERRORTYPE StateTrans(HTEST *hTest, OMX_STATETYPE eState)
{
OMX_ERRORTYPE ret = OMX_ErrorNone;
OMX_COMPONENTTYPE *hComponent = NULL;
OMX_STATETYPE eCurState = OMX_StateInvalid;
hComponent = hTest->hComponent;
OMX_GetState(hComponent, &eCurState);
ret = SendCommand(hTest, OMX_CommandStateSet,eState,NULL, OMX_FALSE);
if(ret != OMX_ErrorNone) {
printf("State trans to %d failed.\n", eState);
return ret;
}
/* Loaded->Idle */
if(eCurState == OMX_StateLoaded && eState == OMX_StateIdle)
Load2Idle(hTest);
/* Exec->Idle */
else if(eCurState == OMX_StateExecuting && eState == OMX_StateIdle)
hTest->bHoldBuffers = OMX_TRUE;
/* Pause->Idle */
else if(eCurState == OMX_StatePause && eState == OMX_StateIdle)
hTest->bHoldBuffers = OMX_TRUE;
/* Idle->Loaded */
else if(eCurState == OMX_StateIdle && eState == OMX_StateLoaded)
Idle2Load(hTest);
else
printf("Ivalid state trans.\n");
ret = WaitCommand(hTest, OMX_CommandStateSet, eState, NULL);
if(ret != OMX_ErrorNone)
return ret;
printf("State trans: [%x] -> [%d] done.\n", eCurState, eState);
/* Idle->Exec/Idle->Pause done */
if(eCurState == OMX_StateIdle && (eState == OMX_StateExecuting || eState == OMX_StatePause))
start_data_process(hTest);
return ret;
}
首先获取组件当前的状态,通过OMX_GetState()函数即可,然后通过SendCommand()函数向组件发送OMX_CommandStateSet命令,将状态转换成下一个状态。SendCommand()函数是OMX_SendCommand()函数的一个封装,具体实现自己看吧。
之后就是状态转换需要做的,状态转换需要按照规定的转,当前状态为Loaded,下一个状态将为Idle,所以就会跳到Load2Idle()函数中,在这个函数中,会为输出端口分配buffer,通过OMX_AllocateBuffer函数,而对于输入端口,就需要使用OMX_UseBuffer()函数,让组件消耗Buffer。OMX类的函数,都是命令组件怎么做的角度来看的。
之前发送了组件状态转换函数,这时候,也为组件提供好了物质条件,这时候就需要等待组件返回一个命令执行完成的信号,所以组件中需要返回这个OMX_EventCmdComplete指令,具体怎么等待命令完成,就是WaitCommand()函数。
如果只是从Loaded -> Idle状态转换,此时就转换完毕了,他准备好组件运行所需的条件。
下一个就是从Idle -> Exec状态的转换,当执行到Exec状态的话,组件就开始工作了,这时候就会进入start_data_process()函数:
OMX_ERRORTYPE start_data_process(HTEST *hTest)
{
OMX_ERRORTYPE ret = OMX_ErrorNone;
OMX_U32 i;
hTest->bHoldBuffers = OMX_FALSE;
/* Send output buffers */
for(i=0; i<hTest->nBufferHdr[1]; i++) {
hTest->pBufferHdr[1][i]->nFilledLen = 0;
hTest->pBufferHdr[1][i]->nOffset = 0;
OMX_FillThisBuffer(hTest->hComponent, hTest->pBufferHdr[1][i]);
}
/* Send input buffers */
for(i=0; i<hTest->nBufferHdr[0]; i++) {
read_data(hTest, hTest->pBufferHdr[0][i]);
OMX_EmptyThisBuffer(hTest->hComponent, hTest->pBufferHdr[0][i]);
}
return ret;
}
讲到这里,必须讲数据的传输流程了,如下图所示:
3)其他函数
上面已经讲完了大致的流程,剩下的函数就很好理解了。
port_flush()函数:就是对组件通过SendCommand()函数发送OMX_CommandFlush命令,并等待它完成。注意这里使用的是SendCommand()函数的同步模式,最后一个参数是OMX_TRUE。
port_disable()函数:对组件通过SendCommand()函数发送OMX_CommandPortDisable命令,disable一个端口了,就需要把端口分配的buffer都释放掉。
port_enable()函数:对组件通过SendCommand()函数发送OMX_CommandPortEnable命令,为端口准备buffer资源,并开始数据的循环。
小结:上面这个函数基本就是一整套使用OMX组件的函数流程,但是如果想要真正写好并不是很简单,需要认真理解里面各种数据结构,各个函数。这篇文章只能起到一个抛砖引玉的作用,剩下的需要继续努力。同时,这一套流程,就是集成在ACodec中的,可以仔细再读读ACodec的源码,去掉繁琐的框架结构,庖丁解牛,看看核心是否就是本文所述。