一、引言:
海思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_SAVEADECDumpFile(pHAData, avpkt, avOut, pInBufHdr);
#endifif ((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的运转机制:

海思OMX代码分析---技术片段相关推荐

  1. 海思 ISP 3A开发技术

    海思 ISP 3A开发技术 1.概述 1.1 概述 1.2 功能描述 1.2.1 设计思路 1.2.2 文件组织 1.2.3 开发模式 1.2.4 内部流程 2. 使用者指南 2.1 软件流程 2.2 ...

  2. 雄迈信息联合华为海思发布H.265AI技术,安防视频更智能

    在安防行业急速发展的今天,随着对图像分辨率.帧率.图像智能检测等方面需求的不断提高,进而在编码.传输.存储.解码等一系列过程中,迫使对算法和芯片也提出了更高的要求. 海思在算法和芯片方面有着多年的行业 ...

  3. 海思hi3520dv400 kernel分析(3)——设备树支持

    概念: FDT:Flattened Device Tree (扁平设备树)是一种数据结构,用来描述设备的硬件配置信息,它源自开放固件使用的设备树格式. DTS:Device tree source(设 ...

  4. 雄迈信息联合华为海思发布H.265AI技术,让安防视频更智能

    在安防行业急速发展的今天,随着对图像分辨率.帧率.图像智能检测等方面需求的不断提高,进而在编码.传输.存储.解码等一系列过程中,迫使对算法和芯片也提出了更高的要求. 海思在算法和芯片方面有着多年的行业 ...

  5. 海思Hi3516智能分析引擎应用介绍

    安防监控正在步入高清化.智能化时代,海思推出的Hi3516正是针对高清IPCamera的一款专业高端SOC芯片,除了1080p@30fpsH.264多码流编码.优异的ISP和编码视频质量优势外,其高性 ...

  6. Linux rtsp客户端 海思,海思rtsp软件分析

    rtsp  概念啥的就不介绍了,记不住. 简单分析下.   海思板子是服务器,电脑为客服端. 首先 socket     setsockopt     bind    listen    (注意:po ...

  7. 实验四恶意代码分析技术 201421430029

    学   号:201421430029   中国人民公安大学 Chinese people' public security university 网络对抗技术 实验报告   实验四 恶意代码技术   ...

  8. checkpatch海思SDK代码遇见的常见错误《二》

    17) ERROR:OPEN_BRACE: open brace '{' following enum go on the same line 左大括号要跟enum同一行. 18)WARNING:LO ...

  9. 海思屏幕HAL代码解析

    显示屏幕(LCD)模块提供屏幕相关功能接口,调用者为上层应用模块(含 init.状态机.ui),上下文依赖关系,如图 3-7 所示. 系统框架为 linux+Huawei LiteOS 双系统架构,媒 ...

最新文章

  1. Android宫格动态列,Android实现宫格图片连续滑动效果
  2. 神经网络中的激活函数的比较
  3. 一个例子学懂搜索引擎(lucene)
  4. 【Android】事件传递:向下拦截,向上处理
  5. 准备拉琴的zskame大白菜
  6. javascript 布尔类型
  7. html______1
  8. realmex7pro能用鸿蒙系统吗,realmex7pro有nfc吗-realmex7pro支持红外遥控功能吗
  9. Linux系统录制gif动画
  10. 部署nodejs报No package nodejs available
  11. 如何快速获得Q币(python简单实现)
  12. python 把文件夹压缩成tar的代码
  13. 蓝桥杯java历年真题及答案整理21~40
  14. java macd指标_Java 验证 MACD 底背离是否真的有效
  15. python中怎么画一个机器猫_用python画机器猫--哆啦A梦,开干!
  16. 自己整理的资料 视频格式以及参数含义
  17. C#语言实例源码系列-实现获取机器码-适用于绑定机器功能
  18. IT软件行业用契约锁实现“代理-销售-投标-项目-合作”电子签
  19. oracle如何总计,如何在Oracle中产生分组小计和总计?
  20. 爱运动的人身体都不差----基于墨刀原型工具的健康软件设计

热门文章

  1. 正则表达式匹配空格与换行
  2. 力扣编程能力入门练习
  3. 特征选择算法之卡方检验
  4. Python 爬取东京奥运会奖牌榜!中国原来这么厉害!
  5. 递归遍历文件夹,递归解压,压缩文件,写解压和压缩过程的日志,
  6. 4.CSS层叠样式表一
  7. android 电池20 提醒,当日修冷知识:滴咚!为啥手机电量低于20%就会自动提醒?...
  8. 【Matlab】取整函数
  9. 接入第三方登录(微信、QQ、新浪微博)
  10. SCRT连不上本地虚拟机的linux解决方法