一、引言:
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循环机制,下面给个图说明一下: