写在前面
本文将介绍如何用海思MPP的VDEC来做视频解码,由于视频(编码/压缩)格式有很多,本文只讨论H264视频格式的解码。
在阅读本文之前,你需要对以下几个信息有所了解:
- 海思多媒体开发框架MPP
- H264视频格式,包括I/P/B帧、PTS/DTS
本文将介绍:
- 海思MPP的VDEC做解码的流程,引申介绍H264视频解码的流程
- 海思MPP的VDEC做解码的API介绍
<hr/>I. VDEC介绍
海思MPP的VDEC章节的文档介绍相对较短,海思官方还辅佐提供了样例代码,尽管如此,对于很多同学而言,仍旧很难上手编写。
首先,来看看VDEC是指什么?
VDEC就是纯粹做图像数据解码的模块,这个模块可以单独接收用户送入的数据,也能够送回给用户处理完毕后的数据,最重要的是,这个模块可以和VPSS绑定,直接把处理完毕的数据送到VPSS。(备注:海思MPP的很多模块之间都能绑定,具体的绑定关系在文档中可查)下面这幅图是在VPSS章节中出现的,可以看到,VDEC绑定到VPSS之后,后续可以再绑定到VO/VENC/SVP模块,形成一条完整的处理链路,当然,用户也可以直接从VPSS中获取到数据。
VDEC-VPSS数据流向图
海思VDEC的能力视不同的平台而异,如HI3559AV100最大能够处理H264/265 7680x4320@60fps,HI3619AV100最大处理3840x2160@60fps,HI3516DV300则是2688x1536@30fps。
除去平台能力的差异,VDEC提供的功能是非常丰富的,并且开放很多接口供用户选择不同的解码方案:送入码流数据的方式、图像输出的方式、解码内部buffer的分配等。
VDEC也有通道(channel)的概念,没有组(group)的概念(海思MPP的很多模块都有通道概念,每个通道彼此独立;根据平台的能力不同,最大通道数目也不同。VPSS比较特别,单独还有组的概念)。
<hr/>II. 解码
解码是编码的逆向过程,笔者还没有深究H264编码的过程,但是这个不影响我们对解码的理解:
H264解码过程示意图
解码DPB
由于H264存在大量的P/B帧,而这些帧依赖前序/后续数据才能正常解码(否则就得到花屏的帧数据),因此解码器内部都会有一台buffer用以存储一定量的帧数据,这个buffer专业名词是DPB(Decoded Picture Buffer),中文是解码图片缓存区,也有说法是参考图像列表/缓存区。希望更进一步了解的推荐阅读DPB总结和H.264解码器中参考图像的管理。
DTS和PTS
H264把视频帧数据分为I/P/B三种类型(更严格的来说,不止这三种,还有IDR/I,可做参考帧的P/B以及不可做参考帧的P/B等),B帧较为特殊,因为它需要依赖前后文信息(I帧可以独立解码,而P帧则只依赖前文信息),也就是说,当解码器解到B帧码流的时候,并不能马上得到B帧解码数据。于是,这里就引申出关于DTS和PTS的问题。
DTS(Decoding Time Stamp)是解码时间,这个是用以告诉解码器解析每一帧的时间顺序,以上图为例,按照DTS从小到大排序的话,应该是:I帧<P1帧<P2帧<B帧。也就是说,即时B帧先于P2帧先被送入解码器,但是解码器或是会先解析P2然后再解析B。
PTS(Presentation Time Stamp)是显示时间,这个则是用来告诉播放器播放每一帧的时间顺序,同样以上图为例,按照PTS排序,则是:I帧<P1帧<B帧<P2帧。
于是,我们看到,当H264视频中包括B帧会造成DTS和PTS不同步的情况,需要尤为注意。这两个数值是非常重要的,特别在做视音频同步的问题上,起到关键作用。
当然,解码还有很多需要了解的,但是对于使用一般解码器,如海思MPP的VDEC,以上信息基本可以。
<hr/>III. VDEC做解码
笔者总结海思MPP的VDEC的解码流程图如下图:
海思MPP的VDEC解码流程图
最左侧是VDEC的准备阶段,涉及到的API有:
- 设置模块参数:HI_MPI_VDEC_GetModParam和HI_MPI_VDEC_SetModParam。
前者是获取当前VDEC模块的参数,后者则是将模块设置为用户指定的参数。由于一般模块/通道/组的参数特别多,因此建议首先调用Get类的API获取到当前的参数,然后单独处理指定几个需要的字段,最后调用Set类API进行设置。
在设置VDEC模块参数时,需要指定VB的模式,到底是VB_SOURCE_COMMON(公共池)、VB_SOURCE_MODULE(模块公共池)、VB_SOURCE_PRIVATE(私有池)、或是VB_SOURCE_USER(用户池)。
- 创建VDEC通道:HI_MPI_VDEC_GetChnAttr和HI_MPI_VDEC_SetChnAttr,以及 HI_MPI_VDEC_CreateChn
必须先调用HI_MPI_VDEC_CreateChn创建得到VDEC通道之后,才能使用Get和Set。后面这对API可以省略,因为在Create接口中,用户可以直接把想要的赋值好。不过,更保险起见,推荐先调用Create之后在调用Get,接着设置自个要修改的参数,最后Set。
这里还要注意一点:海思文档中明确说明,SetAPI只能修改个别属性,请仔细对照。
- 设置VDEC通道参数:HI_MPI_VDEC_SetChnParam和HI_MPI_VDEC_GetChnParam
海思的VDEC设置需要注意的是,如果有需要处理B帧的视频,那么需要专门设置VDEC_PARAM_VIDEO_S结构体的enDecMode字段为VIDEO_DEC_MODE_IPB!相应的,这个结构体的enOutputOrder字段需要设置为VIDEO_OUTPUT_ORDER_DISP,理由是带有B帧的视频DTS和PTS不一致!
- 开启VDEC通道:HI_MPI_VDEC_StartRecvStream
一定要记得开启通道,VDEC才能接受数据并开始工作。
- 绑定VPSS:HI_MPI_SYS_Bind
这个函数适用于绑定海思任何两个“合法可绑定”的模块。海思文档第二章有明确说明。
- 送入码流:HI_MPI_VDEC_SendStream
海思VDEC送入码流的方式有3种:
a-draft-node=&#34;block&#34; data-draft-type=&#34;table&#34; data-size=&#34;normal&#34; data-row-style=&#34;normal&#34;><tr>方式解释</th>优势劣势流
VIDEO_MODE_STREAM送入任意长度的码流无需外部做帧识别无法及时解码,要等下一帧码流到达才能开始帧
VIDEO_MODE_FRAME每次送入一帧码流快速解码用户需要外部解码兼容
VIDEO_MODE_COMPAT支持多一帧码流多次发送给解码器-(不讨论)--(不讨论)-
而设置码流送入的方式在创建VDEC通道这步提供的API完成。
- 查询VDEC通道状态:HI_MPI_VDEC_QueryStatus
送入码流后通过该API可以查询VDEC通道是否解码完毕。
- 获取图像:HI_MPI_VDEC_GetFrame或者HI_MPI_VPSS_GetChnFrame
调用哪个API取决于是否绑定了VPSS。
- 释放VDEC通道图像:HI_MPI_VDEC_ReleaseFrame或者HI_MPI_VPSS_ReleaseChnFrame
release步骤是清除通道内部buffer内的数据,否则一方面可能造成内存泄露;另一方面可能造成后续获取不到最新的数据。(笔者没有验证过,有了解的同学欢迎留言)
- 解绑VPSS:HI_MPI_SYS_UnBind
该API对应HI_MPI_SYS_Bind,使用海思MPP进行开发时,一定要注意“有始有终”,创建的资源一定记得销毁,不然底层硬件可能崩溃只能重启解决。
- 停止VDEC通道:HI_MPI_VDEC_StopRecvStream
该API对应HI_MPI_VDEC_StartRecvStream,记得在销毁之前调用。调用之后,该通过就无法接受新的码流数据了。
- 销毁VDEC通道:HI_MPI_VDEC_DestroyChn
该API对应HI_MPI_VDEC_CreateChn,如果决定不再使用,请务必销毁!因为海思每个模块的最大通道数量是一定的,如果一直不销毁,就会占用资源。
<hr/>写在后面
使用海思MPP做H264解码的基本要点都记录下来了,如果文中有任何不正确的问题,欢迎指正。此外,也欢迎交流讨论任何视频媒体、边缘计算相关的问题~ |