海思HI35xx平台软件开发快速入门之MPEG解码实例

前言

  海思平台开发MPEG视频解码需要有HIMPP解码库API相关的知识,如果不具备基础的背景知识,请移步我的博文《海思HI35xx平台软件开发快速入门之背景知识》了解海思平台开发的基本步骤和相关知识基础。遵循海思平台软件开发架构,我们一步一步实现一个MPEG视频解码实例。先科普一下什么是MPEG视频,MPEG(Moving Picture Experts Group,动态图像专家组)是ISO成立针对运动图像和语音压缩制定国际标准的组织,MPEG标准主要有以下五个,MPEG-1、MPEG-2、MPEG-4、MPEG-7及MPEG-21等。目前应用较多的是MPEG-4,我们熟悉的DVD就普遍使用了MPEG-4解码标准,反过来说DVD的普及使用也促成了MPEG-4解码标准的知名度。目前MPEG4在数字电视、实时多媒体监控、低比特率下的移动多媒体通信、网络视频流与可视游戏、网络会议、交互多媒体应用、演播电视等领域应用广泛,更多相关MPEG的知识请参阅其官网。对于音视频开发者来说,有必要学习了解一下,如何在海思平台上实现MPEG解码。

 

知识背景

 

  海思平台音视频编解码架构遵循下图所示的数据处理流程,我们的MPEG解码实例要实现播放MPEG样例视频,故走的是HARD DISK->VDEC->VPSS->VO->显示器的流程,这个流程一定要熟悉牢记,代码实现都是围绕这条主线来编写的。

 

海思HI35xx平台软件开发快速入门之MPEG解码实例

 

实例源码

  实例源码很简单,先来了解一下实现MPEG解码实例的几个函数,以达到了解实例源码大概构造组成,然后再对每个函数进行具体分析。

 
  1. /*

  2. **函数描述:linux标准信号捕捉函数

  3. **函数功用:退出HIMPP调用,销毁缓冲

  4. */

  5. HI_VOID SAMPLE_VDEC_HandleSig(HI_S32 signo)

  6. {

  7. ......

  8. }

  9. /*

  10. **函数描述:用于音视频文件读写推流

  11. **函数功用:用fread等文件操作函数读取音视频文件,并解析后推送HIMPP进行解码

  12. */

  13. int SAMPLE_COMM_VDEC_JPEG_SendStream( VdecThreadParam *pArgs)

  14. {

  15. ......

  16. }

  17. /*

  18. **函数描述:HIMPP系统初始化

  19. **函数功用:配置HIMPP系统的各项参数以满足对目标进行编解码

  20. */

  21. HI_S32 SAMPLE_VDEC_VdhMpeg4(char *filename)

  22. {

  23. ......

  24. }

  25.  
  26. /*

  27. **主函数

  28. */

  29. int main(int argc, char *argv[])

  30. {

  31. ......

  32. }

   main函数讲解,main函数的完成的功能主要有两,一是对信号的初始化,信号的捕捉函数用来接收来之linux的内核消息,如进程退出等;二是将main函数的参数传递给MPEG解码样例函数。以下是main函数的详细内容:

 
  1. int main(int argc, char *argv[])

  2. {

  3. if(argc != 2)

  4. {

  5. printf("Usage: mpeg <vedio source filename>\n");

  6. exit(0);

  7. }

  8. signal(SIGINT, SAMPLE_VDEC_HandleSig);

  9. signal(SIGTERM, SAMPLE_VDEC_HandleSig);

  10. SAMPLE_VDEC_VdhMpeg4(argv[1]);

  11. return 0;

  12. }

   下面重点讲解SAMPLE_VDEC_VdhMpeg4(char *filename)这个函数,它是MPEG解码样例的重点函数。HIMPP系统的API函数是海思提供的SDK开发包,调用它相关的接口,在编译时必须将其提供的相应库文件进行包含编译。下面结合样例程序讲述如何使用HIMPP提供的API实现自己的业务逻辑。MPEG视频解码实例走的是HARDDISK->VDEC->VPSS->VO->显示器流程,这个过程可以细分为八大步骤,这八大步骤在其他类型的音视频编解码样例也类似,可以说这八大步骤是使用海思HIMPP API的灵魂。下面简单介绍这个八大步骤的内容:

Step1:初始化HIMPP SYS和通用VB缓冲,包括设置缓冲区的大小,缓冲区块的数目。需要注意的是,在设置通用VB参数之前,必须确保HIMPP系统已经退出,否则设置失败。

Step2:设置通用缓冲区的公共缓冲池属性。

Step3:配置解码器,包括指定解码类型,这里是MPEG解码样例,当然选PT_MP4VIDEO啦,然后指定视频大小、解码优先级等等。然后创建解码通道,并是能加收解码流。

Step4:配置VPSS参数,VPSS是对VDEC解码后的流进行处理,如裁剪、降噪等,MPEG解码实例从简单应用出发,仅仅按默认的方式配置VPSS。

Step5:配置VO参数,这一步也很关键,因为它指定了画面输出,包括常见的HDMI和VGA,主要是配置输出显示,图层属性设置、输出位置等信息。

Step6:绑定VDEC与VPSS,实现MPEG解码流程。

Step7:绑定VPSS与VO,实现MPEG解码流程。

Step8:推送视频流数据,这一步需要文件读写配合使用。

  完整源码,请参阅SAMPLE_VDEC_VdhMpeg4(char*filename)源码,为了演示各个步骤需要配置的参数信息,不少变量需要时才定义,同时整个函数避免了使用不必要的子函数调用,尽量使用简单顺序结构的程序设计,以达到对这八大步骤一目了然。

 
  1. HI_S32 SAMPLE_VDEC_VdhMpeg4(char *filename)

  2. {

  3. VB_CONF_S stVbConf, stModVbConf;

  4. HI_S32 i, s32Ret = HI_SUCCESS;

  5. VdecThreadParam pstVdecSend;

  6. SIZE_S stSize;

  7. VO_PUB_ATTR_S stVoPubAttr;

  8. VO_VIDEO_LAYER_ATTR_S stVoLayerAttr;

  9. stSize.u32Width = HD_WIDTH;

  10. stSize.u32Height = HD_HEIGHT;

  11.  
  12. /************************************************

  13. step1: init SYS and common VB

  14. *************************************************/

  15. MPP_SYS_CONF_S stSysConf = {0};

  16.  
  17. memset(&stVbConf,0,sizeof(stVbConf));

  18. stSize.u32Width = HD_WIDTH;//1920

  19. stSize.u32Height = HD_HEIGHT;//1080

  20. stVbConf.u32MaxPoolCnt = 64;

  21. stVbConf.astCommPool[0].u32BlkSize = (stSize.u32Width * stSize.u32Height* 4) >> 1;

  22. stVbConf.astCommPool[0].u32BlkCnt = 15;

  23. memset(stVbConf.astCommPool[0].acMmzName,0,sizeof(stVbConf.astCommPool[0].acMmzName));

  24.  
  25. //设置VB属性之前必须先退出HIMPP系统,否则设置失败。

  26. HI_MPI_SYS_Exit();

  27. for(i=0;i<22;i++)

  28. {

  29. HI_MPI_VB_ExitModCommPool(i);

  30. }

  31. for(i=0; i<256; i++)

  32. {

  33. HI_MPI_VB_DestroyPool(i);

  34. }

  35. HI_MPI_VB_Exit();

  36.  
  37. //设置VB属性

  38. s32Ret = HI_MPI_VB_SetConf(&stVbConf);

  39. if (HI_SUCCESS != s32Ret)

  40. {

  41. hidebug("HI_MPI_VB_SetConf failed!\n");

  42. return HI_FAILURE;

  43. }

  44. s32Ret = HI_MPI_VB_Init();

  45. if (HI_SUCCESS != s32Ret)

  46. {

  47. hidebug("HI_MPI_VB_Init failed!\n");

  48. return HI_FAILURE;

  49. }

  50.  
  51. //设置HIMPP系统属性,并初始化

  52. stSysConf.u32AlignWidth = 16;//选择系统图形对齐方式,一般分辨率是16对齐的

  53. /*set config of mpp system*/

  54. s32Ret = HI_MPI_SYS_SetConf(&stSysConf);

  55. if (HI_SUCCESS != s32Ret)

  56. {

  57. hidebug("HI_MPI_SYS_SetConf failed!\n");

  58. return HI_FAILURE;

  59. }

  60. s32Ret = HI_MPI_SYS_Init();

  61. if (HI_SUCCESS != s32Ret)

  62. {

  63. hidebug("HI_MPI_SYS_Init failed!\n");

  64. return HI_FAILURE;

  65. }

  66. /************************************************

  67. step2: init mod common VB

  68. *************************************************/

  69. memset(&stModVbConf, 0, sizeof(VB_CONF_S));

  70. stModVbConf.u32MaxPoolCnt = 64;

  71. stModVbConf.astCommPool[0].u32BlkSize = (stSize.u32Width * stSize.u32Height* 4) >> 1;;

  72. stModVbConf.astCommPool[0].u32BlkCnt = 15;

  73.  
  74. stModVbConf.astCommPool[1].u32BlkSize = (stSize.u32Width * stSize.u32Height* 4) >> 1;;

  75. stModVbConf.astCommPool[1].u32BlkCnt = 15;

  76.  
  77. //设置模块公共缓冲池属性

  78. HI_MPI_VB_ExitModCommPool(VB_UID_VDEC);

  79. HI_MPI_VB_SetModPoolConf(VB_UID_VDEC, &stModVbConf);

  80. HI_MPI_VB_InitModCommPool(VB_UID_VDEC);

  81.  
  82.  
  83. /************************************************

  84. step3: start VDEC

  85. *************************************************/

  86. VDEC_CHN_ATTR_S stVdecChnAttr;

  87.  
  88. stVdecChnAttr.enType = PT_MP4VIDEO; //设置解码方式 PT_JPEG PT_MJPEG

  89. stVdecChnAttr.u32BufSize = 3 * stSize.u32Width * stSize.u32Height;

  90. stVdecChnAttr.u32Priority = 5;

  91. stVdecChnAttr.u32PicWidth = stSize.u32Width;

  92. stVdecChnAttr.u32PicHeight =stSize.u32Height;

  93.  
  94. stVdecChnAttr.stVdecVideoAttr.enMode = VIDEO_MODE_FRAME;

  95. stVdecChnAttr.stVdecVideoAttr.u32RefFrameNum = 2;

  96. stVdecChnAttr.stVdecVideoAttr.bTemporalMvpEnable = 0;

  97.  
  98. //设置解码通道缓冲池VB的个数,这里只需创建一个解码通道

  99. HI_MPI_VDEC_SetChnVBCnt(0, 10);

  100. //创建解码通道

  101. HI_MPI_VDEC_CreateChn(0, &stVdecChnAttr);

  102. //解码器开始接收用户推流

  103. HI_MPI_VDEC_StartRecvStream(0);

  104.  
  105. /************************************************

  106. step4: start VPSS

  107. *************************************************/

  108. VPSS_GRP_PARAM_S stVpssParam = {0};

  109. VPSS_CHN_ATTR_S stChnAttr = {0};

  110. VPSS_GRP_ATTR_S stVpssGrpAttr;

  111.  
  112. stVpssGrpAttr.enDieMode = VPSS_DIE_MODE_NODIE;

  113. stVpssGrpAttr.bIeEn = HI_FALSE;

  114. stVpssGrpAttr.bDciEn = HI_TRUE;

  115. stVpssGrpAttr.bNrEn = HI_FALSE;

  116. stVpssGrpAttr.bHistEn = HI_FALSE;

  117. stVpssGrpAttr.bEsEn = HI_FALSE;

  118. stVpssGrpAttr.enPixFmt = PIXEL_FORMAT_YUV_SEMIPLANAR_420;

  119. stVpssGrpAttr.u32MaxW = 1920;//stSize.u32Width;

  120. stVpssGrpAttr.u32MaxH = 1080;//stSize.u32Height;

  121.  
  122. //创建一个VPSS GROUP 0是一个组号

  123. s32Ret = HI_MPI_VPSS_CreateGrp(0, &stVpssGrpAttr);

  124. if (s32Ret != HI_SUCCESS)

  125. {

  126. hidebug("HI_MPI_VPSS_CreateGrp failed!\n");

  127. return HI_FAILURE;

  128. }

  129. //获取GROUP 0的属性 PS:实用技巧,对于未知的属性,我们一般先获取再设置

  130. s32Ret = HI_MPI_VPSS_GetGrpParam(0, &stVpssParam);

  131. if(s32Ret != HI_SUCCESS)

  132. {

  133. hidebug("HI_MPI_VPSS_GetGrpParam failed!\n");

  134. return HI_FAILURE;

  135. }

  136. stVpssParam.u32IeStrength = 0;

  137. //设置GROUP 0的属性

  138. s32Ret = HI_MPI_VPSS_SetGrpParam(0, &stVpssParam);

  139. if(s32Ret != HI_SUCCESS)

  140. {

  141. hidebug("HI_MPI_VPSS_GetGrpParam failed!\n");

  142. return HI_FAILURE;

  143. }

  144. /*** enable vpss chn, with frame ***/

  145. /* Set Vpss Chn attr */

  146. stChnAttr.bSpEn = HI_FALSE;

  147. stChnAttr.bUVInvert = HI_FALSE;

  148. stChnAttr.bBorderEn = HI_TRUE;

  149. stChnAttr.stBorder.u32Color = 0xffffff;

  150. stChnAttr.stBorder.u32LeftWidth = 2;

  151. stChnAttr.stBorder.u32RightWidth = 2;

  152. stChnAttr.stBorder.u32TopWidth = 2;

  153. stChnAttr.stBorder.u32BottomWidth = 2;

  154.  
  155. //设置VPSS通道设置属性

  156. s32Ret = HI_MPI_VPSS_SetChnAttr(0, 0, &stChnAttr);

  157. if(s32Ret != HI_SUCCESS)

  158. {

  159. hidebug("HI_MPI_VPSS_SetChnAttr failed!\n");

  160. return HI_FAILURE;

  161. }

  162. //启动VPSS通道

  163. s32Ret = HI_MPI_VPSS_EnableChn(0, 0);

  164. if(s32Ret != HI_SUCCESS)

  165. {

  166. hidebug("HI_MPI_VPSS_EnableChn failed!\n");

  167. return HI_FAILURE;

  168. }

  169. //启动VPSS GROUP

  170. s32Ret = HI_MPI_VPSS_StartGrp(0);

  171. if(s32Ret != HI_SUCCESS)

  172. {

  173. hidebug("HI_MPI_VPSS_StartGrp failed!\n");

  174. return HI_FAILURE;

  175. }

  176.  
  177. /************************************************

  178. step5: start VO

  179. *************************************************/

  180. VO_CHN_ATTR_S stChnAttr1;

  181.  
  182. stVoPubAttr.enIntfSync = VO_OUTPUT_1080P60;

  183. stVoPubAttr.enIntfType = VO_INTF_VGA | VO_INTF_HDMI;

  184. //配置视频输出设备公共属性

  185. s32Ret = HI_MPI_VO_SetPubAttr(0, &stVoPubAttr);

  186. if(s32Ret != HI_SUCCESS)

  187. {

  188. hidebug("HI_MPI_VO_SetPubAttr failed!\n");

  189. return HI_FAILURE;

  190. }

  191. //启用解码通道0

  192. s32Ret = HI_MPI_VO_Enable(0);

  193. if (s32Ret != HI_SUCCESS)

  194. {

  195. hidebug("HI_MPI_VO_Enable failed!\n");

  196. return HI_FAILURE;

  197. }

  198. stVoLayerAttr.u32DispFrmRt = 60;//显示帧率

  199. stVoLayerAttr.stDispRect.u32Width = 1920;//显示分辨率

  200. stVoLayerAttr.stDispRect.u32Height = 1080;

  201. stVoLayerAttr.stImageSize.u32Width = stVoLayerAttr.stDispRect.u32Width;//视频层画布大小

  202. stVoLayerAttr.stImageSize.u32Height = stVoLayerAttr.stDispRect.u32Height;

  203. stVoLayerAttr.bClusterMode = HI_FALSE;

  204. stVoLayerAttr.bDoubleFrame = HI_FALSE;

  205. stVoLayerAttr.enPixFormat = PIXEL_FORMAT_YUV_SEMIPLANAR_420; //视屏层使用的像素格式

  206. //设置视频层属性

  207. s32Ret = HI_MPI_VO_SetVideoLayerAttr(0, &stVoLayerAttr);

  208. if(s32Ret != HI_SUCCESS)

  209. {

  210. hidebug("HI_MPI_VO_SetVideoLayerAttr failed!\n");

  211. return HI_FAILURE;

  212. }

  213. //启动视频层

  214. s32Ret = HI_MPI_VO_EnableVideoLayer(0);

  215. if (s32Ret != HI_SUCCESS)

  216. {

  217. hidebug("HI_MPI_VO_EnableVideoLayer failed!\n");

  218. return HI_FAILURE;

  219. }

  220. /*

  221. if (HI_SUCCESS != SAMPLE_COMM_VO_HdmiStart(stVoPubAttr.enIntfSync))

  222. {

  223. hidebug("Start SAMPLE_COMM_VO_HdmiStart failed!\n");

  224. }

  225. */

  226. stChnAttr1.stRect.s32X = 0;//通道显示区域

  227. stChnAttr1.stRect.s32Y = 0;

  228. stChnAttr1.stRect.u32Width = 1920;//通道显示大小

  229. stChnAttr1.stRect.u32Height = 1080;

  230. stChnAttr1.u32Priority = 0;//通道优先级

  231. stChnAttr1.bDeflicker = HI_FALSE;//是否开抗闪烁

  232.  
  233. //配置视频输出通道属性

  234. s32Ret = HI_MPI_VO_SetChnAttr(0, 0, &stChnAttr1);

  235. if (s32Ret != HI_SUCCESS)

  236. {

  237. hidebug("failed with %#x!\n", s32Ret);

  238. }

  239. //启动视频输出通道

  240. s32Ret = HI_MPI_VO_EnableChn(0,0);

  241. if (s32Ret != HI_SUCCESS)

  242. {

  243. hidebug("failed with %#x!\n", s32Ret);

  244.  
  245. }

  246. /************************************************

  247. step6: VDEC bind VPSS

  248. *************************************************/

  249. MPP_CHN_S stSrcChn;

  250. MPP_CHN_S stDestChn;

  251.  
  252. stSrcChn.enModId = HI_ID_VDEC;

  253. stSrcChn.s32DevId = 0;

  254. stSrcChn.s32ChnId = 0;

  255.  
  256. stDestChn.enModId = HI_ID_VPSS;

  257. stDestChn.s32DevId = 0;

  258. stDestChn.s32ChnId = 0;

  259.  
  260. //解码器绑定VPSS

  261. s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);

  262. if(s32Ret != HI_SUCCESS)

  263. {

  264. hidebug("HI_MPI_SYS_Bind failed!\n");

  265. return HI_FAILURE;

  266. }

  267.  
  268. /************************************************

  269. step7: VPSS bind VO

  270. *************************************************/

  271. stSrcChn.enModId = HI_ID_VPSS;

  272. stSrcChn.s32DevId = 0;

  273. stSrcChn.s32ChnId = 0;

  274.  
  275. stDestChn.enModId = HI_ID_VOU;

  276. stDestChn.s32DevId = 0;

  277. stDestChn.s32ChnId = 0;

  278.  
  279. //VPSS绑定VO

  280. s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);

  281. if(s32Ret != HI_SUCCESS)

  282. {

  283. hidebug("HI_MPI_SYS_Bind failed!\n");

  284. return HI_FAILURE;

  285. }

  286.  
  287. /************************************************

  288. step8: send stream to VDEC

  289. *************************************************/

  290. sprintf(pstVdecSend.cFileName,filename);

  291. pstVdecSend.s32MilliSec = 0;

  292. pstVdecSend.s32ChnId = 0;

  293. pstVdecSend.s32IntervalTime = 1;

  294. pstVdecSend.u64PtsInit = 0;

  295. pstVdecSend.u64PtsIncrease = 0;

  296. pstVdecSend.eCtrlSinal = VDEC_CTRL_START;

  297. pstVdecSend.bLoopSend = HI_TRUE;

  298. pstVdecSend.bManuSend = HI_FALSE;

  299. pstVdecSend.enType = PT_MP4VIDEO;

  300. pstVdecSend.s32MinBufSize = 32000000;//(stVdecChnAttr.u32PicWidth *stVdecChnAttr.u32PicHeight * 3)>>1;

  301. pstVdecSend.s32StreamMode = VIDEO_MODE_FRAME;

  302. SAMPLE_COMM_VDEC_JPEG_SendStream(&pstVdecSend);//推送视频流

  303.  
  304. return s32Ret;

  305. }

  SAMPLE_COMM_VDEC_JPEG_SendStream(VdecThreadParam *pArgs)函数实现的功能是读取文件,解析文件的视频帧头(I帧),然后推送至解码器。这一函数实现的功能简单,但是涉及解析视频帧头等内容,代码量有点多,不适合粘贴出来。SAMPLE_VDEC_HandleSig(HI_S32 signo)函数主要作用就是退出程序时,对HIMPP系统资源进行释放,否则显示器上会残留视频图像,并影响下一次使用HIMPP提供的API。

 
  1. HI_VOID SAMPLE_VDEC_HandleSig(HI_S32 signo)

  2. {

  3. HI_S32 i;

  4. if (SIGINT == signo || SIGTSTP == signo || SIGTERM == signo)

  5. {

  6. HI_MPI_SYS_Exit();

  7. for(i=0;i<VB_MAX_USER;i++)

  8. {

  9. HI_MPI_VB_ExitModCommPool(i);

  10. }

  11. for(i=0; i<VB_MAX_POOLS; i++)

  12. {

  13. HI_MPI_VB_DestroyPool(i);

  14. }

  15. HI_MPI_VB_Exit();

  16. printf("\033[0;31mprogram exit abnormally!\033[0;39m\n");

  17. }

  18. exit(0);

  19. }

总结

  MPEG解码实例参考了海思提供的样例及库,程序源码及相关库文件请点击这里,修改不同的编译链工具,即可在不同HI35XX系列平台运行,整个MPEG解码实例提供了最简单的解码实现方式,当然还可以实现快进播放、暂停播放、等常用的视频播放控制逻辑,这需要读者进一步摸索。

海思HI35xx平台软件开发快速入门之MPEG解码实例