海思OMX代码分析---流程分析

一、引言:
Omx是非常常用的开源媒体编解码组件,omx的整体架构比较复杂,通常有三个层,而绝大部分实现主要是其中的IL(集成层),因为omx的开发偏向底层,实际上是提供了一个可供上层调用的api,然后由标准组件调入进去实现硬解码,因此,做omx IL层的基本会是各大芯片厂商,显然,现在的工作是不会做到这一块的,因此,本次omx学习的主要目的,是了解omx的框架,以及汲取海思在omx框架中运用的一些关键技术。
Omx的标准头文件对我们而言,最需要关注的两个是OMX_Core.h和OMX_Component.h:
OMX_Core.h:声明了组件需要实现的接口,是上层应用调用到omx组件的入口;
OMX_Component.h:进一步实现OMX_Core.h中对组件的需求,芯片厂商的组件需要实现这套标准;

本次代码分析使用的是Android stb tvos版本,通过gstreamer进行omx组件的调用,调试代码部分为音频代码。

二、代码分析:
在这里插入图片描述
1.omx的初始化工作:
上层代码通过调用OMX_Init进入omx组件:

OMX_ERRORTYPE OMX_Init()
{
    OMX_ERRORTYPE eError = OMX_ErrorNone;

    DEBUG_PRINT("%s :: enter!\n", __func__);

    if (pthread_mutex_lock(&g_Mutex) != 0)
    {
        DEBUG_PRINT_ERROR("%s :: Core: Error in Mutex lock\n",__func__);
        return OMX_ErrorUndefined;
    }

    g_InitCount++;

    if (pthread_mutex_unlock(&g_Mutex) != 0)
    {
        DEBUG_PRINT_ERROR("%s :: Core: Error in Mutex unlock\n",__func__);
        return OMX_ErrorUndefined;
    }

    DEBUG_PRINT("%s :: exit!\n", __func__);

    return eError;
}

代码中记录了omx被初始化的次数,也可以理解为申请的组件的个数;

紧接着,上层代码调用OMX_GetHandle接口:

OMX_ERRORTYPE OMX_GetHandle(
        OMX_HANDLETYPE *pHandle,
        OMX_STRING cComponentName,
        OMX_PTR pAppData,
        OMX_CALLBACKTYPE *pCallBacks)
{
	...
	/* 1.通过组件名拼接需要加载的库名 */
	char buf[sizeof(prefix) + MAX_COMP_NAME_LEN + sizeof(postfix)];

    err = change_component_name(&cComponentName);
    strncpy(buf, prefix, sizeof(prefix));
    strncat(buf, cComponentName, strlen(cComponentName));
    strncat(buf, postfix, sizeof(postfix));

	/* 2.加载动态库 */
	pModules[i] = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL);

	/* 3.通过动态库句柄,获取component_init函数始地址 */
	pComponentInit = dlsym(pModules[i], "component_init");

	/* 4.初始化omx组件 */
	err = (*pComponentInit)(*pHandle, cComponentName);
	/* 5.设置回调函数 */
	err = (componentType->SetCallbacks)(*pHandle, pCallBacks, pAppData);
	...
}

该函数的功能主要有三个,一个是通过上面传下来的组件名去加载对应组件的库,二是通过该库获取组件初始化函数component_init的首地址,然后到对应的库中去调用该函数,最后一个是注册回调函数,函数是由上层传下来的,供omx组件回调;

2.调试代码播放的是MP3格式的音频文件,进入omx_mp3_adec.c文件,从component_init开始分析,注意,统领整个omx层组件的参数是HI_AUDDATATYPE* pHAData:

/*****************************************************************************/
OMX_ERRORTYPE component_init(OMX_HANDLETYPE pHandle, OMX_STRING comp_name)
{
    HI_S32 s32Ret;
    OMX_ERRORTYPE eError = OMX_ErrorNone;
    HI_AUDDATATYPE* pHAData = HI_NULL;
    OMX_COMPONENTTYPE* pComponent;

    HA_UNUSED_PARAMETER(comp_name);
	/* 1.完成omx公共部分的一些初始化 */
    eError = OMX_AUDIO_COMMON_Init(pHandle, &pHAData);
    if (OMX_ErrorNone != eError)
    {
        return eError;
    }
	
	/* 2.mp3私有数据格式的初始化 */
    eError = OMXMP3InitPrivData(pHAData);
    if (OMX_ErrorNone != eError)
    {
        goto INIT_EXIT0;
    }
	
	/* 3.初始化HA_CODEC中MP3解码参数(sdk)*/
    HA_MP3_DecGetDefalutOpenParam(&pHAData->stAdec.sOpenPram);

	/* 4.调用HA_CODEC(sdk)进行初始化 */
    s32Ret = OMX_HAADEC_Init(pHAData, HA_MP3_LIB_NAME);
    if (HI_SUCCESS != s32Ret)
    {
        eError = OMX_ErrorInsufficientResources;
        goto INIT_EXIT1;
    }
	
	/* 5.填充组件的函数指针,上层会直接调用过来 */
    // Fill in function pointers
    pComponent = (OMX_COMPONENTTYPE*)pHandle;
    pComponent->GetParameter = OMXMP3GetParameter;
    pComponent->SetParameter = OMXMP3SetParameter;
    pComponent->ComponentRoleEnum = OMXMP3GetRoleEnum;

    pHAData->sInPortDef.format.audio.pDeprecated0 = (OMX_PTR)"audio/mpeg";
    pHAData->sInPortDef.format.audio.eEncoding = OMX_AUDIO_CodingMP3;

#ifdef ANDROID
    //For Android CTS
    pHAData->CodecFrame = OMXMP3ProcessFrame;
#else
    pHAData->CodecFrame = OMX_HAADEC_ProcessFrame;
#endif

	/* 6.创建一个线程,用于后续的解码和送流工作 */
    // Create the component thread
    s32Ret = pthread_create(&pHAData->thread_id, HI_NULL, HI_OMX_ADEC_ComponentThread, pHAData);
    if (0 != s32Ret)
    {
        eError = OMX_ErrorInsufficientResources;
        goto INIT_EXIT2;
    }

    OAD_PRINT_STATE("MP3 decoder init ok!\n");

    return OMX_ErrorNone;

INIT_EXIT2:
    OMX_HAADEC_DeInit(pHAData);
INIT_EXIT1:
    OMX_HAADEC_PrivDataDeInit(pHAData);
INIT_EXIT0:
    OMX_AUDIO_COMMON_DeInit(pHAData);

    return eError;
}

3.对于重点函数进行分析,首先是OMX_AUDIO_COMMON_Init,函数调用关系及意义如下:
在这里插入图片描述
4.OMX_HAADEC_Init函数:
在这里插入图片描述
这个函数会去注册sdk中的解码库,然后紧接着去申请innerbuffer,这个值在非packet方式的解码中会用到,目前看来大小是65536 * 2,在proc信息中是可以看到这个innerbuffer的使用情况的,最后一点是获取outbuffer的值,这个在前面OMX_AUDIO_COMMON_Init的时候预设了一个,这里去刷新这个值;

5.在component_init这个函数中,我们还看到如下代码:

    // Fill in function pointers
    pComponent = (OMX_COMPONENTTYPE*)pHandle;
    pComponent->GetParameter = OMXMP3GetParameter;
    pComponent->SetParameter = OMXMP3SetParameter;
    pComponent->ComponentRoleEnum = OMXMP3GetRoleEnum;

其中的GetParameter和SetParameter会在后续解码器的配置中频繁使用到;
另外还有一个关键的函数指针和一个线程:

    pHAData->CodecFrame = OMX_HAADEC_ProcessFrame;
    s32Ret = pthread_create(&pHAData->thread_id, HI_NULL, HI_OMX_ADEC_ComponentThread, pHAData);
    if (0 != s32Ret)
    {
        eError = OMX_ErrorInsufficientResources;
        goto INIT_EXIT2;
    }

前者OMX_HAADEC_ProcessFrame就是处理输入输出buffer的核心部分,后者则是创建了一个处理buffer的线程;

6.重点分析下这个线程(omx_audio_base.c):

void* HI_OMX_ADEC_ComponentThread(void* pThreadData)
{
    OMX_S32 nRetValue;
    HI_AUDDATATYPE* pHAData = (HI_AUDDATATYPE*)pThreadData;
    TRP_IN();

    OMX_OSAL_EventCreate(&pHAData->hTimeout);

    while (1)
    {
    	/* 1.命令处理 */
        nRetValue = OMX_CODEC_CommandMgmtProcess(pHAData);
        if (nRetValue == OMX_ErrorUndefined)
        {
            OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "%s --->  OMX_CODEC_CommandMgmtProcess stop,  >>>Exit<<< \n", __func__);
            goto EXIT;
        }
		
		/* 2.数据处理 */
        if (pHAData->state == OMX_StateExecuting)
        {
            OMX_ADEC_BufferMgmtProcess(pHAData);
        }
    }

EXIT:
    OMX_OSAL_EventDestroy(pHAData->hTimeout);
    OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "%s   >>>Exit<<< \n", __func__);
    TRP_OUT();
    return (void*)OMX_ErrorNone;
}

先看下命令的处理:

static OMX_ERRORTYPE OMX_CODEC_CommandMgmtProcess(HI_AUDDATATYPE* pHAData)
{
    OMX_S32 nRetValue = OMX_ErrorNone;
    TRP_IN();

    FD_ZERO(&pHAData->rfds);
    FD_SET(pHAData->cmdspipe[PIPE_READ], &pHAData->rfds);

    // Check for new command
    select(pHAData->cmdspipe[PIPE_READ] + 1, &pHAData->rfds, NULL, NULL, NULL);
    if (FD_ISSET(pHAData->cmdspipe[PIPE_READ], &pHAData->rfds))
    {
        nRetValue = OMX_ACodec_CmpProcess(pHAData);
    }
    else
    {
        OMX_OSAL_Trace(OMX_OSAL_TRACE_ERROR, "%s  ---> Check for new command Failed !\n" , __func__);
    }

    TRP_OUT();
    return nRetValue;
}

确认pipe中有指令之后,调用OMX_ACodec_CmpProcess:

static OMX_ERRORTYPE OMX_ACodec_CmpProcess(HI_AUDDATATYPE* pHAData)
{
    OMX_U32         cmddata;
    OMX_COMMANDTYPE cmd;
    /* Variables related to decoder buffer handling */
    OMX_MARKTYPE*   pMarkBuf = NULL;

    TRP_IN();

    // retrieve command and data from pipe
    read(pHAData->cmdspipe[PIPE_READ], &cmd, sizeof(cmd));
    read(pHAData->datapipe[PIPE_READ], &cmddata, sizeof(cmddata));
	...
	    else if (cmd == (OMX_COMMANDTYPE)OMX_HA_CommandFillBuf)
    {
        OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "%s ---> execute OMX_HA_CommandFillBuf\n" , __func__);

        // Fill buffer
        ListSetEntry(pHAData->sOutBufList, (OMX_BUFFERHEADERTYPE*)cmddata);
    }
    else if (cmd == (OMX_COMMANDTYPE)OMX_HA_CommandEmptyBuf)
    {
        OMX_BUFFERHEADERTYPE* pBuffer = (OMX_BUFFERHEADERTYPE*)cmddata;
        OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "%s  ---> execute OMX_HA_CommandEmptyBuf\n" , __func__);

        // Empty buffer
        OMX_HAADEC_DataAlign(pHAData, pBuffer);
        OMX_PTSQUEUE_Put(pHAData->pstPtsQueue, pBuffer->nTimeStamp, pBuffer->nFilledLen);
        ListSetEntry(pHAData->sInBufList, pBuffer);

        //OMX_OSAL_Trace(OMX_OSAL_TRACE_ERROR, "OMXAUDIO Put Pts %lld Size = %d\n", pBuffer->nTimeStamp, pBuffer->nFilledLen);

        // Mark current buffer if there is outstanding command
        if (pMarkBuf)
        {
            ((OMX_BUFFERHEADERTYPE*)(cmddata))->hMarkTargetComponent = pMarkBuf->hMarkTargetComponent;
            ((OMX_BUFFERHEADERTYPE*)(cmddata))->pMarkData = pMarkBuf->pMarkData;
            pMarkBuf = NULL;
        }
    }
	...
}

先是从pipe中读取cmd和cmddata,然后进入switch去处理cmd,这里摘录一下omx的状态(OMX_Core.h),看了一下大概明白了为什么ACodec里面的状态机处理会是xxxtoxxx了:

typedef enum OMX_STATETYPE
{
    OMX_StateReserved_0x00000000,
    OMX_StateLoaded,
    OMX_StateIdle,
    OMX_StateExecuting,
    OMX_StatePause,
    OMX_StateWaitForResources,
    OMX_StateKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
    OMX_StateVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
    OMX_StateMax = 0X7FFFFFFF
} OMX_STATETYPE;

我们最关心的两个cmd是OMX_HA_CommandFillBuf和OMX_HA_CommandEmptyBuf,具体的分析在后面单独看。

三、具体场景分析:
在这里插入图片描述
经过初始化部分,对omx音频模块的大致框架调用有了一个比较清晰的了解,上图中蓝色部分可以理解为四个模块,上层通过调用omx_init进而初始化omx的组件,然后组件的初始化话会去调用common,common也会去调用base模块,而base部分的核心就是不停地处理上面传下来的指令,同时去处理buffer,buffer的具体处理是去调用ha_adec,这里面会去调用HA_CODEC获得解码后的数据,然后做一些其他的处理。

1.OMX_AllocateBuffer:
上层通过此接口通知下层申请buffer,具体申请工作在HI_OMX_CODEC_AllocateBuffer函数中,buffer的管理是通过buffer列表来的,一共申请了4块buffer,先是通过ListAllocate标记当前申请的是第几个buffer,然后实际申请buffer,最后调用LoadBufferHeader,让上层能够访问到这块buffer,注意,生成访问的时候也是访问的bufflist,而不是真的去访问buff,这样增加各层的耦合性和独立性:

OMX_ERRORTYPE HI_OMX_CODEC_AllocateBuffer(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)
{
	...
	/* 申请输入/输出buffer */
	if (nPortIndex == pHAData->sInPortDef.nPortIndex)
	{
		/* 标记当前申请的是第几块buffer */
		ListAllocate(pHAData->sInBufList, nIndex);
		...
		/* 申请buffer */
		pHAData->sInBufList.pBufHdr[nIndex]->pBuffer = (OMX_U8*)OMX_OSAL_Malloc(nSizeBytes);
		...
	}
	else
	{...}

	/* buffer的指向赋值,确保上层能够访问到这块buffer的编号 */
    LoadBufferHeader(pHAData->sOutBufList, pHAData->sOutBufList.pBufHdr[nIndex],
                         pAppPrivate, nSizeBytes, nPortIndex, *ppBufferHdr, pPortDef);
        pHAData->sOutBufList.addralloclist[nIndex] = (OMX_U32)(*ppBufferHdr);

}

2.omxIL层中pipe的使用:
这里举一个上层设置omx状态为OMX_StateExecuting的例子来说明,首先是应用程序,调用组件的OMX_SendCommand函数,发送设置状态的指令:

    error = OMX_SendCommand(audiodechandle, OMX_CommandStateSet, OMX_StateExecuting, NULL);
    if (error != OMX_ErrorNone)
    {
        APP_DPRINT ("Error from SendCommand-Executing State function\n");
        goto ALL_EXIT;
    }

传入到omx组件中,根据函数指针,由HI_OMX_CODEC_SendCommand进行处理:

OMX_ERRORTYPE HI_OMX_CODEC_SendCommand(OMX_IN OMX_HANDLETYPE  hComponent,
                                       OMX_IN OMX_COMMANDTYPE Cmd,
                                       OMX_IN OMX_U32         nParam1,
                                       OMX_IN OMX_PTR         pCmdData)
{
	...
	write(pHAData->cmdspipe[PIPE_WRITE], &Cmd, sizeof(Cmd));
	...
}

omx中父子进程之间通信使用的是pipe,所以,将指令写入管道中,看一下子线程中的对指令的处理:

static OMX_ERRORTYPE OMX_ACodec_CmpProcess(HI_AUDDATATYPE* pHAData)
{
	...
	case OMX_StateExecuting:
	// Transition can only happen from pause or idle state
	
	OMXProcessStateExecuting(pHAData);
	...
}

继续跟进OMXProcessStateExecuting函数:

		/* 如果之前的状态是idle,则设置pHAData->state */
        pHAData->state = OMX_StateExecuting;
        //pHAData->state = OMX_StateIdle;
        pHAData->pCallbacks->EventHandler(pHAData->hSelf, pHAData->pAppData,
                                          OMX_EventCmdComplete, OMX_CommandStateSet, pHAData->state,
                                          NULL);

代码中,这里就设置了pHAData->state的状态,然后调用了回调通知应用已经设置完成了状态,接下来分两步走,看下子线程和应用各自在做什么,先看应用:

static OMX_ERRORTYPE EventHandler(OMX_OUT OMX_HANDLETYPE aComponent,
                                  OMX_OUT OMX_PTR        aAppData,
                                  OMX_OUT OMX_EVENTTYPE  aEvent,
                                  OMX_OUT OMX_U32        aData1,
                                  OMX_OUT OMX_U32        aData2,
                                  OMX_OUT OMX_PTR        aEventData)
{
	...
	case OMX_StateExecuting:
	printf("OMX_StateExecuting\n");
	break;
	...
}

应用收到这个回调之后仅仅是做了一个打印;
再去看子线程中,首先是while中会去进行buffer处理了,之前仅是处理指令:

void* HI_OMX_ADEC_ComponentThread(void* pThreadData)
{
	while (1)
    {
    	...
    	/* 状态为OMX_StateExecuting开始去处理buffer */
    	if (pHAData->state == OMX_StateExecuting)
        {
            OMX_ADEC_BufferMgmtProcess(pHAData);
        }
        ...
	}
}

4.OMX_FillThisBuffer:
为什么上层会先去调用这个指令,我不是很清楚,但是逻辑还是要跟一下,根据之前的套路,OMX_FillThisBuffer是由HI_OMX_CODEC_FillThisBuffer来处理的:


/*****************************************************************************/
OMX_ERRORTYPE HI_OMX_CODEC_FillThisBuffer(OMX_IN OMX_HANDLETYPE        hComponent,
        OMX_IN OMX_BUFFERHEADERTYPE* pBufferHdr)
{
    HI_AUDDATATYPE* pHAData;
    OMX_COMMANDTYPE eCmd   = (OMX_COMMANDTYPE)OMX_HA_CommandFillBuf;
    OMX_ERRORTYPE   eError = OMX_ErrorNone;
    TRP_IN();

    OMX_CONF_CHECK_NULLPTR(hComponent);

    pHAData = (HI_AUDDATATYPE*)(((OMX_COMPONENTTYPE*)hComponent)->pComponentPrivate);
    OMX_CONF_CHECK_CMD(pHAData, pBufferHdr, NON_ZERO);
    OMX_CONF_CHK_VERSION(pBufferHdr, OMX_BUFFERHEADERTYPE, eError);

    if (!pHAData->sOutPortDef.bEnabled)
    {
        OMX_CONF_SET_ERROR_BAIL(eError, OMX_ErrorIncorrectStateOperation);
    }

    if ((pBufferHdr->nOutputPortIndex != OMX_DirOutput) || (pBufferHdr->nInputPortIndex != OMX_NOPORT))
    {
        OMX_CONF_SET_ERROR_BAIL(eError, OMX_ErrorBadPortIndex);
    }

    if ((pHAData->state != OMX_StateExecuting) && (pHAData->state != OMX_StatePause))
    {
        OMX_CONF_SET_ERROR_BAIL(eError, OMX_ErrorIncorrectStateOperation);
    }

    // Put the command and data in the pipe
    write(pHAData->cmdspipe[PIPE_WRITE], &eCmd, sizeof(eCmd));
    write(pHAData->datapipe[PIPE_WRITE], &pBufferHdr, sizeof(OMX_BUFFERHEADERTYPE*));

OMX_CONF_CMD_BAIL:
    TRP_OUT();
    return eError;
}

还是往管道中写指令,看一下接收端:

else if (cmd == (OMX_COMMANDTYPE)OMX_HA_CommandFillBuf)
{
	OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "%s ---> execute OMX_HA_CommandFillBuf\n" , __func__);
	
	// Fill buffer
	ListSetEntry(pHAData->sOutBufList, (OMX_BUFFERHEADERTYPE*)cmddata);
}

又是用带结构体的宏来实现:

#define ListSetEntry(_pH, _pB) \
    do {                                                     \
        OMX_U32 nIndex;       \
        for (nIndex = 0; nIndex < NUM_OUT_BUFFERS; nIndex++) \
        { \
            if ((HI_U32)_pB == _pH.addralloclist[nIndex])  \
            { \
                _pH.bufferindex[_pH.nWritePos] = nIndex;  \
            } \
        } \
        if (_pH.nSizeOfList < (_pH.nListEnd + 1)){   \
            _pH.nSizeOfList++;                        \
            _pH.pBufHdr[_pH.bufferindex[_pH.nWritePos++]] = _pB;       \
            if (_pH.nReadPos == -1) \
                _pH.nReadPos = 0;\
            if (_pH.nWritePos > _pH.nListEnd) \
                _pH.nWritePos = 0;\
        } \
    } while (0)

这个宏我看个半懂,大概的意思就是确认这个buffer是第几个标号的buffer,然后记录一下,当前写到第几个buffer了;命令处理就完毕了,接下来就就交给数据处理函数了。

5.数据处理流程:
承接上面的函数,这个函数略复杂:

static void OMX_ADEC_BufferMgmtProcess(HI_AUDDATATYPE* pHAData)
{
	...
	/* 1.buffer预处理 */
	s32ResourcesRet = HA_PreBuffer(pHAData);
	/* 2.核心:调用HA_CODEC去解码 */
	if (pHAData->CodecFrame)
	{
		s32Ret = pHAData->CodecFrame(pHAData, pHAData->pInBufHdr, pHAData->pOutBufHdr);
	}
	/* 3.回调FillBufferDone */
	HA_PostOutputBuffer(pHAData);
	/* 4.回调EmptyBufferDone */
	HA_PostInputBuffer(pHAData);
	...
}

a.buffer预处理,其实就是操作bufflist,使用宏ListCacheEntry记录当前输入输出buffer中读写的标号,注意,整个buffer的操作都是在使用bufferlist,没有去操作实际的buffer中读写!!!
b.解码,对应的处理函数为OMX_HAADEC_DecodeFrame(ha_adec.c):



/*****************************************************************************
   The following functions with OMX_HAADEC_XXX can be called by other files
*****************************************************************************/
HI_S32 OMX_HAADEC_DecodeFrame(HI_AUDDATATYPE* pHAData,     HI_HADECODE_INPACKET_S* avpkt,
                              HI_HADECODE_OUTPUT_S* avOut, OMX_BUFFERHEADERTYPE* pInBufHdr)
{
    HI_S32            s32Ret;
    HI_S32            s32PrePkgSize;
    HA_ADEC_S*        pstAdec     = &pHAData->stAdec;
    HI_HA_DECODE_S*   pstHA       = pstAdec->pstHA;
    HA_INTERNALBUF_S* pstInnerBuf = &pstAdec->sInternalBuf;

    if ((HI_FALSE == pstAdec->bPacketDecoder) && (HI_TRUE == pHAData->bInnerBufFlag))
    {
        if (pstInnerBuf->s32Insize + pInBufHdr->nFilledLen > PACKETINSIZE_MAXNUM * pHAData->sInPortDef.nBufferSize)
        {
            OMX_OSAL_Trace(OMX_OSAL_TRACE_ERROR, "FAILED: Inner Buffer OVERFLOW\n");
            return HA_ErrorInBufFull;
        }
        if (pInBufHdr->nFilledLen > 0)
        {
            memcpy((HI_U8*)pstInnerBuf->pInBuffer + pstInnerBuf->s32Insize, pInBufHdr->pBuffer + pInBufHdr->nOffset, pInBufHdr->nFilledLen);
            pstInnerBuf->s32Insize += pInBufHdr->nFilledLen;
            pstInnerBuf->s32Offset  = 0;
            pInBufHdr->nOffset      = pInBufHdr->nFilledLen;
            pInBufHdr->nFilledLen   = 0;
        }
        avpkt->s32Size = pstInnerBuf->s32Insize;
        avpkt->pu8Data = (HI_U8*)pstInnerBuf->pInBuffer + pstInnerBuf->s32Offset;
    }
    else
    {
        avpkt->s32Size = pInBufHdr->nFilledLen;
        avpkt->pu8Data = pInBufHdr->pBuffer + pInBufHdr->nOffset;
    }
    s32PrePkgSize = avpkt->s32Size;

    s32Ret = pstHA->DecDecodeFrame(pHAData->hDecoder, avpkt, avOut);

    pHAData->u64TotalConsumedBytes += (s32PrePkgSize - avpkt->s32Size);
    pHAData->u64ConsumedPos = pHAData->u64TotalConsumedBytes - avOut->stPtsInfo.unPts.u32SwDecoderBytesLeft;

    if (0 == avOut->u32OutSampleRate || avOut->u32PcmOutSamplesPerFrame > OMX_PCM_OUTSIZE_MAX)
    {
        s32Ret = HA_ErrorStreamCorrupt;
    }

#ifdef ADEC_FILE_SAVE
    ADECDumpFile(pHAData, avpkt, avOut, pInBufHdr);
#endif

    if ((HI_FALSE == pstAdec->bPacketDecoder) && (HI_TRUE == pHAData->bInnerBufFlag))
    {
        pstInnerBuf->s32Offset += (pstInnerBuf->s32Insize - avpkt->s32Size);
        pstInnerBuf->s32Insize  = avpkt->s32Size;
    }
    else
    {
        pInBufHdr->nOffset   += (pInBufHdr->nFilledLen - avpkt->s32Size);
        pInBufHdr->nFilledLen = avpkt->s32Size;
    }

    return s32Ret;
}

整个函数比较简单,进来的es流实际会分两种,一种是packet方式,就是每次解码的数据是按照packet为单位进行解码,每一个packet的大小可能是不等的,但是buffer的长度最大就65536,这样就会出现末尾数据凑不够一个完整的packet情况出现,下一次去解码的时候必然报错,所以,这里启用了innerbuffer的机制,innerbuffer用于每次解码完后的数据不够一个packet时,先拷贝到内部的innerbufer中,下一次buffer送过来之后,将整个buffer拷贝到innerbuffer中,然后送innerbuffer去解码,而非packet方式就方便的多了,直接去解码就行了;
c.HA_PostOutputBuffer:

static void  HA_PostOutputBuffer(HI_AUDDATATYPE* pHAData)
{
    //called by decodeframe success
    TRP_IN();

    //ListDump(pHAData->sOutBufList);
    OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "HA_PostOutputBuffer  Out Bufffer & pHAData->pCallbacks->FillBufferDone  size=%ld %p \n",
                   pHAData->pOutBufHdr->nFilledLen, pHAData->pOutBufHdr);

    // releast Output Bufffer
    ListReleaseEntry(pHAData->sOutBufList, pHAData->pOutBufHdr);
    pHAData->pCallbacks->FillBufferDone(pHAData->hSelf, pHAData->pAppData, pHAData->pOutBufHdr);
    pHAData->pOutBufHdr = HI_NULL;
    //ListDump(pHAData->sOutBufList);
    TRP_OUT();
}

就是调用回调FillBufferDone通知上面输出buffer已经被填充满了;
d.HA_PostInputBuffer:

static void  HA_PostInputBuffer(HI_AUDDATATYPE* pHAData)
{
    TRP_IN();
    //inbuf hold by compoent
    if (OWNED_BY_COMPONENT ==  pHAData->enInBufState)
    {
        OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "%s  -->  pHAData->pCallbacks->EmptyBufferDone  %p\n", __func__, pHAData->pInBufHdr);

        // releast input Bufffer
        ListReleaseEntry(pHAData->sInBufList, pHAData->pInBufHdr);
        pHAData->pCallbacks->EmptyBufferDone(pHAData->hSelf, pHAData->pAppData, pHAData->pInBufHdr);
        pHAData->pInBufHdr = HI_NULL;
    }
    TRP_OUT();
}

这个函数也比较好理解,首先是移动bufferlist,然后调用回调告诉上层,这个buffer中的数据被消耗完了,送下一批过来吧;

接下来具体看一下上层收到回调之后做了什么:
首先是FillBufferDone,代码很长,只抄重点:

static OMX_ERRORTYPE FillBufferDone(OMX_OUT OMX_HANDLETYPE        hComponent,
                                    OMX_OUT OMX_PTR               pAppData,
                                    OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
{
	...
	/* 1.将pcm数据送往track */
	s32Ret = HI_UNF_SND_SendTrackData(pAppPriData->hTrack, &stAOFrame);
            if (HI_SUCCESS == s32Ret)
            {
                break;
            }
	...
	/* 2.继续调用OMX_FillThisBuffer */
	error = OMX_FillThisBuffer(hComponent, pBuffer);
    if (error != OMX_ErrorNone)
    {
        APP_DPRINT( "In %s Error %08x Calling FillThisBuffer\n", __func__, error);
        return error;
    }
    ...
}

将获取到的pcm数据播出去,然后继续调用OMX_FillThisBuffer让下层继续填充buffer,下面是EmptyBufferDone;

static OMX_ERRORTYPE EmptyBufferDone(OMX_OUT OMX_HANDLETYPE        hComponent,
                                     OMX_OUT OMX_PTR               pAppData,
                                     OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
{
    OMX_ERRORTYPE error;

    //Check the validity of buffer
    if ((NULL == pBuffer) || (NULL == pAppData))
    {
        return OMX_ErrorBadParameter;
    }
	
    error = send_input_buffer(hComponent, pBuffer, (AudioDecAppDATATYPE*)pAppData);
    if (error != OMX_ErrorNone)
    {
        APP_DPRINT( "In %s Error %08x Calling FillThisBuffer\n", __func__, error);
        return error;
    }

    return OMX_ErrorNone;
}

可以看到,这个函数主要是去调用send_input_buffer,往下追踪:

static OMX_ERRORTYPE send_input_buffer(OMX_HANDLETYPE        pHandle,
                                       OMX_BUFFERHEADERTYPE* pBuffer,
                                       AudioDecAppDATATYPE*  pAppPriData)
{
    HI_U32        nRead;
    OMX_ERRORTYPE error = OMX_ErrorNone;

    nRead = fill_data(pBuffer, pAppPriData->fileIn);
    /*Don't send more buffers after OMX_BUFFERFLAG_EOS*/
    if (pAppPriData->mEndInputBuf)
    {
        pBuffer->nFlags = 0;
        APP_DPRINT("%d : APP:: Entering send_input_buffer finish,pBuffer=%p \n", __LINE__, pBuffer);
        return error;
    }

    if ((nRead < pBuffer->nAllocLen) && (pAppPriData->mEndInputBuf == 0))
    {
        pBuffer->nFlags = OMX_BUFFERFLAG_EOS;
        pAppPriData->mEndInputBuf = 1;
        APP_DPRINT("%d : APP:: Entering send_input_buffer OMX_BUFFERFLAG_EOS, pBuffer=%p \n", __LINE__, pBuffer);
    }
    else
    {
        pBuffer->nFlags = 0;
    }

    /*time stamp & tick count value*/
    pBuffer->nTimeStamp = rand() % 100;
    pBuffer->nTickCount = rand() % 70;

    pBuffer->nFilledLen = nRead;
    OMX_EmptyThisBuffer(pHandle, pBuffer);

    return error;
}

函数就干了两件事,从文件中读取数据,然后调用组件的OMX_EmptyThisBuffer去解码;,看一下fill_data:

static HI_U32 fill_data (OMX_BUFFERHEADERTYPE* pBuf, FILE* fileIn)
{
    HI_U32 nRead;

    nRead = fread(pBuf->pBuffer, 1, pBuf->nAllocLen, fileIn);
    pBuf->nFilledLen = nRead;
    pBuf->nOffset    = 0;
    return nRead;
}

一次读取的数据量为整个buffer的size;
关于omx组件的buffer循环机制,下面给个图说明一下:
在这里插入图片描述

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 西安海思机试题是针对Java编程语言的一道考试题目。海思机试题通常要求考生能够熟练掌握Java语言的基本语法和常用类库,并能够运用这些知识解决简单的编程问题。 在机试题中,通常会涉及到一些基本的编程概念,如变量、循环、条件判断、函数等。考生需要使用Java语言来实现特定的功能或逻辑,并输出正确的结果。 对于西安海思机试题,可能会包含以下一些例子: 1. 写一个程序,输出从1到100的所有偶数。 解题思路:使用循环从1到100遍历,判断每个数字是否为偶数,如果是则输出。 2. 写一个程序,求一个数组中的最大值和最小值。 解题思路:使用循环遍历数组中的每个元素,通过比较更新最大值和最小值。 3. 写一个程序,判断一个字符串是否是回文字符串。 解题思路:使用循环将字符串反转,并与原字符串进行比较,如果相同则是回文字符串。 对于这些机试题,考生应该具备良好的编程思维和逻辑分析能力,善于利用Java语言特性和类库来解决问题。此外,高效的编码能力和代码风格的优化也是考生需要具备的能力。 要准备西安海思机试题,考生可以通过复习Java基本语法、数据类型、循环、条件判断和函数等知识,并通过编写代码实践来提升自己的编程技巧。还可以参考一些教材、教程或者在线编程平台上的题库进行练习和加深理解。 最后,机试题不仅仅是一次考试,更重要的是通过实践来提升自己的编程能力和解决问题的能力。希望以上回答对你有所帮助。 ### 回答2: 西安海思机试题是一道关于Java的题目。Java是一种常见的高级编程语言,具有跨平台的特性,可以用于开发各种类型的软件应用程序。机试题通常用来测试考生在编程领域的技能和知识。 对于这样的机试题,可能会包括一系列的编程题目,考察考生的语法知识、算法和逻辑思维能力等方面。在解答机试题时,需要考生清晰理解题目的要求,并用合适的Java代码进行解答。 解答机试题的关键在于理解题目要求,并用合适的算法和Java编程语言来实现解决方案。在解答过程中,还需要注意代码的规范性、可读性和效率。 对于想要参加西安海思机试的考生,建议他们提前准备,复习并熟悉Java编程语言的基础知识和常见的算法。此外,也可以通过练习机试题,提高自己的解决问题和编程能力。 总之,西安海思机试题-java是一种考察考生Java编程能力和知识技能的方式。参加机试需要考生具备扎实的编程基础和算法思维,同时也需要平时勤加练习和积累,提高自己在编程领域的能力。 ### 回答3: 西安海思机试题是一个关于Java编程的考试题目。在这个机试题中,考生需要完成一些与Java语言相关的编程任务,以展示自己的编程能力。根据题目的要求,考生可能需要使用Java编程语言来实现一些功能,如算法设计、数据结构操作等。 在西安海思机试题中,考生需要具备扎实的Java编程基础以及良好的逻辑思维能力。他们需要理解题目要求,分析问题,并设计出合适的解决方案。在编程过程中,考生需要熟练运用Java的各种语法、类库和工具,以便高效地完成任务。此外,考生还需要注意代码的可读性和可维护性,以便其他程序员理解并维护他们的代码。 西安海思机试题涉及的内容可能包括但不限于以下几个方面: 1. Java基础知识:如基本语法、面向对象编程概念、异常处理、输入输出等。 2. 数据结构与算法:如链表、栈、队列、排序算法等。 3. 多线程编程:如线程创建与启动、同步与互斥、线程池等。 4. 网络编程:如TCP/IP通信、Socket编程等。 5. 数据库操作:如JDBC编程、SQL语句执行等。 在准备西安海思机试题时,考生可以通过以下途径提升自己的编程能力: 1. 学习和掌握Java编程语言的基础知识,包括语法、集合框架、IO操作等。 2. 多进行编程练习,不断提高自己的编码能力。 3. 阅读与Java相关的书籍、文章以及开源项目,学习他人的经验和优秀的编码风格。 4. 加入编程社区或者论坛,与其他程序员交流经验。 5. 参加在线教育平台或培训机构的Java课程,接受系统的培训。 总之,参加西安海思机试题是提升自己Java编程能力的一个很好的机会。通过扎实的基础知识和充分的准备,考生可以在这个机试中展现出自己的编程实力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值