19. Android MultiMedia框架完全解析 - 如何使用OpenMAX组件

这几个例子,我只是在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的源码,去掉繁琐的框架结构,庖丁解牛,看看核心是否就是本文所述。

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值