海思OMX代码分析---技术片段

一、引言:
海思stb产品omx框架中使用的一些不错的代码技术,这个博客分析这些代码片段。

二、具体分析:
1.字符串衔接:
海思在通过组件名加载动态库的时候,将组件名与前后缀衔接使用的是strncat函数:

    strncpy(buf, prefix, sizeof(prefix));
    strncat(buf, cComponentName, strlen(cComponentName));
    strncat(buf, postfix, sizeof(postfix));

strncat用于字符串接续,比如“lib” + “OMX.hisi.audio.mp3dec” + “.so”;

2.组件初始化:
海思每个解码器是一个omx组件,里面都会有一个函数为component_init,这是组件初始化函数的入口,在加载动态库成功之后,会调用dlsym这个系统函数来获取component_init的函数指针,然后通过传参调入到组件中;

    /* Get a function pointer to the "OMX_ComponentInit" function.  If
     * there is an error, we can't go on, so set the error code and exit */
    pComponentInit = dlsym(pModules[i], "component_init");

参数一为动态链接库句柄,参数二为函数或者全局变量的名称;

3.二级指针与数组的结合使用:
在看buffer处理相关代码时,遇到如下情况:

pHAData->sInBufList.pBufHdr[nIndex]->pBuffer = (OMX_U8*)OMX_OSAL_Malloc(nSizeBytes);

sInBufList对应类型:

BufferList                     sInBufList;

pBufHdr类型:

typedef struct _BufferList BufferList;
struct _BufferList
{
    OMX_BUFFERHEADERTYPE** pBufHdr;
    OMX_S32                nListEnd;
    OMX_S32                nSizeOfList;
    OMX_U32                nAllocSize;
    OMX_U32                addralloclist[NUM_MAX_BUFFERS];
    OMX_S32                nWritePos;
    OMX_S32                nReadPos;
    OMX_U32                bufferindex[NUM_MAX_BUFFERS];
    OMX_DIRTYPE            eDir;
    OMX_U32                bufferOwner[NUM_MAX_BUFFERS];
};

可以看到pBufHdr是一个二级指针,但是在使用时居然直接操作pBufHdr的下标,让我觉得很神奇,我以为有了二级指针可以不用申请*pBufHdr的内存就能访问了,自己还写了测试代码,结果发现段错误,回过去看代码,只要是使用了指针,就一定需要给指针分配一块内存地址操作,代码的初始化阶段,确实申请了内存:

pstInBufList->pBufHdr = (OMX_BUFFERHEADERTYPE**)OMX_OSAL_Malloc(sizeof(OMX_BUFFERHEADERTYPE*) * NUM_IN_BUFFERS);

给pBufHdr 申请了存放四个指针变量的内存地址,所以后面才能以数组下标的形式来操作这个二级指针;

下面写一个测试demo加深理解:

#include <stdio.h>
#include <stdlib.h>

typedef struct INNER_STRUCT
{
	int inner_num;
	
} INNER_STRUCT;

typedef struct STRUCK
{
	INNER_STRUCT** pInner;
} STRUCK;


int main(void)
{
	STRUCK st;
	/* 给pInner申请了一段能存放四个指针变量的内存 */
	st.pInner = (INNER_STRUCT** )malloc(sizeof(INNER_STRUCT* ) * 4);
	
	/* 给第一个指针变量申请内存,大小为其指向的类型INNER_STRUCT */
	st.pInner[0] = (INNER_STRUCT* )malloc(sizeof(INNER_STRUCT));
	/* 给第二个指针变量申请内存,大小为其指向的类型INNER_STRUCT */
	st.pInner[1] = (INNER_STRUCT* )malloc(sizeof(INNER_STRUCT));

	/* 通过访问第一个变量指向的地址给inner_num赋值 */
	st.pInner[0]->inner_num = 199;
	//int** pp = (int** )malloc(sizeof(int* ) * 4);
	/* 通过访问第二个变量指向的地址给inner_num赋值 */
	st.pInner[1]->inner_num = 954;
	
	/* 验证是否设赋值成功 */
	printf("num1 = %d, num2 = %d\n", st.pInner[0]->inner_num, st.pInner[1]->inner_num);
	return 0;
}

这个demo就仿造代码中的场景进行了一个还原,在C/C++中,凡是使用指针变量,一定要确认它指向了一个内存地址,而二级指针,就是指向指针的指针,什么意思呢?就是说这个变量,首先其类型是一个指针,只不过,它指向的内存地址中存放的是指针变量,下面给一个图对这个demo做一个解释:
在这里插入图片描述
pInner是我们的二级指针变量,它指向了一段可以存储4个指针变量的内存首地址,而里面的内容(即指针STRUCK*)又指向了另外申请好的内存地址,那个内存地址里面仅存放了一个int行变量,所以二级指针,申请了两次内存,保证“指有所供”,对于pInner的内存申请,因为我们知道只要给四个指针变量的内存空间即可(32位为32字节,64位为64字节),所以,malloc类型随便怎么写都无所谓;

这里额外吐槽一下宏定义中传参为结构体的写法,看起来很黑科技,高大上,但是实际上体验真的很不好,写代码的得一个一个去复制粘贴结构体成员变量,如果有问题调试起来会很麻烦,而看代码的人就更懵逼了,还要去一个一个地对照结构体,另外,宏这种写法长相又像函数,如果涉及到指针的,没注意看宏有时候值为什么会改变都不知道,所以,不推荐这种写法:

#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)

4.回调函数:
omx架构使用了回调函数,让下层能够及时通知上层,这是非常典型的回调函数使用,这里分析一下,先看下回调函数的“挂钩”长什么样:

typedef struct OMX_CALLBACKTYPE
{
    OMX_ERRORTYPE (*EventHandler)(
            OMX_IN OMX_HANDLETYPE hComponent,
            OMX_IN OMX_PTR pAppData,
            OMX_IN OMX_EVENTTYPE eEvent,
            OMX_IN OMX_U32 nData1,
            OMX_IN OMX_U32 nData2,
            OMX_IN OMX_PTR pEventData);
    OMX_ERRORTYPE (*EmptyBufferDone)(
            OMX_IN OMX_HANDLETYPE hComponent,
            OMX_IN OMX_PTR pAppData,
            OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
    OMX_ERRORTYPE (*FillBufferDone)(
            OMX_IN OMX_HANDLETYPE hComponent,
            OMX_IN OMX_PTR pAppData,
            OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);

} OMX_CALLBACKTYPE;

结构体OMX_CALLBACKTYPE中有三个函数指针,分别是EventHandler、EmptyBufferDone和FillBufferDone;
再看一下这三个指针指向的实体函数是什么:

OMX_CALLBACKTYPE AdecCallbacks =
{
    .EventHandler    = EventHandler,
    .EmptyBufferDone = EmptyBufferDone,
    .FillBufferDone  = FillBufferDone
};

三个函数指针分别是指向的EventHandler、EmptyBufferDone和FillBufferDone这三个实体函数;
下面就是将这个结构体传到下层去:

    error = OMX_GetHandle(&audiodechandle, sAppPriData.strDecoder, &sAppPriData, &AdecCallbacks);

可以看到,在调用OMX_GetHandle的时候,将AdecCallbacks的地址传到了omx的IL层中;
IL层扮演的是一个“调用者”的角色,其调用的流程是先设置回调函数,就是记录了AdecCallbacks的地址,然后再去使用:
首先是设置回调函数:

OMX_ERRORTYPE HI_OMX_CODEC_SetCallbacks(OMX_IN OMX_HANDLETYPE    hComponent,
                                        OMX_IN OMX_CALLBACKTYPE* pCallbacks,
                                        OMX_IN OMX_PTR           pAppData)
{
	...
    pHAData->pCallbacks = pCallbacks;
	....
}

然后是去调用,以EmptyBufferDone 为例:

        pHAData->pCallbacks->EmptyBufferDone(pHAData->hSelf, pHAData->pAppData, pHAData->pInBufHdr);

这样就能实现回调上层的函数了,omx这里面使用的机制就是不停地通过回调让应用去填充buffer,然后底层消耗完毕,通知上层,如此循环;

下面写一个demo来加深回调函数的理解:
git源码
回调函数的理解:回调函数三要素,钩子,填充者(被调用者),调用者;
所谓“钩子”,就是函数指针,它需要的是去勾住函数实体,而函数实体就是由被调用者提供,被调用者会先将三个函数实体挂在“挂钩”上,然后传给调用者,调用者适时地调用“钩子”,就能够回到被调用者的函数实体中去了。

5.innerbuffer的妙用:
操作送去解码的esbuffer有两种方式,一种是一包一包的去送数据,类似AVplay那种,好处就是buffer不需要申请太大,每次播放申请的buffer大小不固定(大小需要结合码流的各项参数),另一种方式就是类型omx这种,固定buffer大小(虽然可以通过setparameter去改变buffer的大小),比如海思是4* 65536大小,每填满一次buffer,可以装多帧数据,假如这次装了8.5帧,前面8帧可以正常解码,但是后面0.5帧就会解码出错,之后的解码可能都会出错,所以,为了维持正常的解码,海思音频加入了innerbuffer,就是后备buffer,也就是说,在海思omx音频框架中一共有三类buffer,输入buffer、输出buffer以及innerbuffer,innerbuffer的申请在组件初始化中:

static HI_S32  HAADECAllocInnerbuf(HA_ADEC_S* pstAdec, HI_U32 u32Size)
{
    HA_INTERNALBUF_S* pstInnerBuf = &pstAdec->sInternalBuf;

    if (HI_FALSE == pstAdec->bPacketDecoder)
    {
        pstInnerBuf->pInBuffer = (HI_VOID*)OMX_OSAL_Malloc(PACKETINSIZE_MAXNUM * u32Size);
        if (HI_NULL == pstInnerBuf->pInBuffer)
        {
            return HI_FAILURE;
        }

        OMX_OSAL_Memset(pstInnerBuf->pInBuffer, 0, PACKETINSIZE_MAXNUM * u32Size);

        pstInnerBuf->s32Insize = 0;
        pstInnerBuf->s32Offset = 0;
    }

    return HI_SUCCESS;
}

注意大小为 8 * 65536,一定要比输入buffer大,不然会出现buffer溢出情况;
innerbuffer的启用是在第一次解码时候之后:在ha_adec.c的OMX_HAADEC_ProcessFrame函数中,会一直调用OMX_HAADEC_DecodeFrame,当某次解码失败:

    else
    {
        pHAData->enInBufState = OWNED_BY_COMPONENT;
		/* 启用innerbuffer之后,解码失败都会进入if处理 */
        if ((HI_FALSE == pstAdec->bPacketDecoder) && (HI_TRUE == pHAData->bInnerBufFlag))
        {
            if ((0 != pstInnerBuf->s32Insize) && (0 != pstInnerBuf->s32Offset))
            {
                OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "Inner Buffer NotEnoughData\n");
                memmove((HI_U8*)pstInnerBuf->pInBuffer, (HI_U8*)pstInnerBuf->pInBuffer + pstInnerBuf->s32Offset, pstInnerBuf->s32Insize);
            }
        }
        /* 第一次解码失败,进入else,启用innerbuffer */
        else
        {
            if ((0 != pInBufHdr->nFilledLen) && (HI_FALSE == pstAdec->bPacketDecoder))
            {
                OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "New packet -> Inner (length=%ld)\n", pInBufHdr->nFilledLen);
                memcpy(pstInnerBuf->pInBuffer, pInBufHdr->pBuffer + pInBufHdr->nOffset, pInBufHdr->nFilledLen);
                pstInnerBuf->s32Insize = pInBufHdr->nFilledLen;
                pHAData->bInnerBufFlag = HI_TRUE;
            }
            pInBufHdr->nOffset   += pInBufHdr->nFilledLen;
            pInBufHdr->nFilledLen = 0;
        }

        OMX_OSAL_Trace(OMX_OSAL_TRACE_DEBUG, "ADECDecodeFrame NotEnoughData or Error s32Ret=0x%x\n", s32Ret);
    }

第一次解码失败会进入else,将inbuffer中的数据拷贝到innerbuffer,然后退出,后续解码再次失败进来就不会去到else,而是进入if,这种场景是innerbuffer中的数据也满了,然后末尾数据存不够一帧,这时需要做的操作就是使用memmov,将没解完的数据移到innerbuffer的开头,然后重新循环解码;
再来看一下解码里面是如何使用innerbuffer的:


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;
	
	/* 如果启用了innerbuffer,那么把inbuffer中的数据全部拷进来,然后送去解码 */
    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;
}

很好理解,就是讲inbuffer中的全部数据拷贝到innerbuffer中,然后送innerbuffer去解码,并对innerbuffer的使用情况做一个更新就可以了;
其他几个状态需要对innerbuffer操作的有:
a.存流:
判断是否该从innerbuffer中存储;
b.EOS状态设置:
将innerbuffer进行flush操作,就是归零,但不是memset的意思;
c.释放innerbuffer;

给一个图说明下innerbuffer的运转机制:
在这里插入图片描述

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值