以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
前言
本文将详细介绍博文第二季3:sample_venc.c的整体分析提及的“创建配置编码通道”。
我们首先介绍VENC模块相关的概念,然后绘制该模块的函数调用关系图谱,接着分析具体代码。
一、VENC模块的相关概念
1、VENC模块的功能
VENC模块,即视频编码模块。
本模块支持多路实时编码,且每路编码独立,即每路的编码协议和编码profile可以不同。
本模块也支持在视频编码时,调度Region模块对编码图像的内容进行叠加和遮挡。
2、VENC模块的输入源
VENC 模块的输入源包括三类:
(1)用户态读取图像文件向编码模块发送数据。
(2)VI模块采集的图像直接发送到编码模块。
(3)VI模块采集的图像经视频处理子系统(VPSS)发送到编码模块。(我们分析这类!)
3、VENC模块支持的编码格式
不同型号的芯片支持不同的编码格式,由下表可知 HI3518E 支持H.264编码。
4、VENC模块的上下文
VENC模块的上下文如下图所示。
编码流程包括:输入图像的接收、图像内容的遮挡和覆盖、图像的编码、以及码流的输出等过程。
(1)图像接收通道
其中VENC模块包含图像接收通道、具体协议(H.264/H.265/JPEG/MJPEG)编码通道。
图像接收通道支持接收 YUV 格式的图像输入(H.264/H.265 只支持YUV420SP,JPEG/MJPEG 支持YUV420SP或YUV422SP),另外HI3518E也支持单Y分量的输入。图像接收通道接收外部的原始图像数据,而不关心图像数据是来自哪个外部模块。
图像接收通道接收到图像之后,会比较图像尺寸和编码通道尺寸:
-
如果输入图像比编码通道尺寸大,VENC模块将按照编码通道尺寸大小,调用 VGS 对源图像进行缩小,然后对缩小之后的图像进行编码。
-
如果输入图像比编码通道尺寸小,VENC模块将丢弃源图像,因为不支持放大输入图像编码。
-
如果输入图像与编码通道尺寸相当,VENC模块直接接收源图像,进行编码。
(2)REGION模块
该模块支持对图像内容的遮挡和叠加。
(3)具体协议编码通道
具体协议编码通道的功能框图如下,从中可知具体编码通道由码率控制器、编码器协同完成,主要实现图像转化为编码码流的功能。这里的编码器,指的是狭义上的编码器,只完成编码功能。码率控制器,提供对编码参数的控制和调整,从而对输出码率进行控制。
对于 H.264、H.265、MJPEG 协议的编码通道,码率控制器提供 CBR(固定比特率)、VBR(可变比特率)、FIXQP(固定QP) 这三种码率控制模式,对图像质量和码率进行调节。
5、编码码流的帧配置模式
阅读本节内容前,先理解博文第6季1:H264编码原理与基本概念_天糊土的博客的内容。
编码码流帧配置支持两种模式:单包模式和多包模式。也就是说,我们可以将一个帧的数据作为一个码流包发送(即单包模式),也可以将一个帧搞成几个(H.264为4个)码流包进行发送(即多包模式)。注意这里说的多包,特指在不调用分包接口的情况下,也就是说不对一帧数据进行“子帧”划分,或者说,以一帧完整的图像数据作为单位的。
它们的示意图如下(以H.264为例)。
(1)多包模式。对于H.264,当为I帧时,调用HI_MPI_VENC_GetStream接口,一个I帧包含4个NAL包(4个NAL包分别为sps包、pps包、sei包、Islice包,这里假设pps包只有一个,且4个NAL包是独立的,包类型不同);对于JPEG,一帧图像包含2个包(1个图像参数包,1个图像数据包,2个包是独立的,包类型不同)。
(2)单包模式:对于H.264,当为I帧时,调用HI_MPI_VENC_GetStream接口,一个I帧包含1个NAL 包(该NAL包的包类型为Islice包,且包含sps、pps、sei、Islice的数据);对于JPEG,一帧图像只有1个包(该包的包类型为图像数据包,且包含图像参数包的数据)。
两种模式可通过 ko 加载时设置模块参数 OneStreamBuffer 来选择。OneStreamBuffer=1表示单包模式;OneStreamBuffer=0 表示多包模式,系统默认 OneStreamBuffer=0。
(接下来这段话描述的是调用分包接口的情形?)
当用户调用分包接口(例如HI_MPI_VENC_SetH264SliceSplit) 时,一帧会被分成多个slice,如果用户选择单包模式,对于 I 帧来说,该帧第一个 ISlice 包会包含 sps、pps、sei 的数据,该帧的其他 ISlice 则没有。即对于 H.264,sps、pps、sei 的数据只会出现在 I 帧的第一个 Islice 中(Islice中的字母I表示I帧),并合为 1 个包,且包类型为 ISlice;对于 JPEG/MJPEG 来说,图像参数包只会出现在一帧的第一个数据包中并合为 1 个包,且包类型为数据包。
根据上面的描述,案例里使用的是多包模式(没有设置这个参数,所以使用默认的)。
6、编码码流的buffer配置模式
编码码流 buffer 配置支持两种模式:一般模式和省内存模式。
-
一般模式:考虑到超大帧的情况,码流 Buffer 大小配置的下限为:H264 和 H265是通道宽 x 通道高 x3/4,JPEG 和 MJPEG 是通道宽 x 通道高。
-
省内存模式:码流 Buffer 大小配置的下限是 32*1024 bytes,此模式需要用户保证码流 buffer 大小设置合理,否则会出现因码流 buffer 不足而不断重编或者丢帧的情况。
两种模式可通过 ko 加载时设置相应的模块参数来选择。模块参数值为 1 表示省内存模式,模块参数值 0 表示一般模式。
-
hi35xx_h264e.ko 模块参数:H264eMiniBufMode。
-
hi35xx_h265e.ko 模块参数:H265eMiniBufMode。
-
hi35xx_jpege.ko 模块参数:JpegeMiniBufMode。
7、VENC模块的设备和通道
注意VENC模块也有“设备”“通道”的概念。这里的“设备”即VENC模块这个硬件单元,只有一个,编号为0。这个VENC设备的内部也有几个通道(对应着不同协议),将来分别与VPSS模块的通道进行绑定。我们的案例里VENC设备具有3个通道,分别与VPSS模块的3个通道进行绑定。
二、VENC模块的函数调用关系
VENC模块的函数调用关系如下:
SAMPLE_COMM_VENC_StartSAMPLE_COMM_SYS_GetPicSizeHI_MPI_VENC_CreateChn//创建编码通道HI_MPI_VENC_StartRecvPic//接收图片并开始编码SAMPLE_COMM_VENC_BindVpssHI_MPI_SYS_Bind//绑定VPSS和VENC
由此可知,该模块涉及以下几个步骤:
-
开启VENC模块(先创建编码通道,然后接收图片并开始编码)
-
绑定VPSS的通道到编码通道
下面我们将详细介绍这几个步骤涉及到的概念与代码细节。
三、VENC模块代码详解
VENC模块的代码与分析如下。由于三路编码通道的操作基本相同,我们分析通道0即可。
/******************************************step 5: start stream venc******************************************//*** HD1080P **/printf("\t c) cbr.\n");printf("\t v) vbr.\n");printf("\t f) fixQp\n");printf("please input choose rc mode!\n");c = (char)getchar();switch(c)//首先选择码率控制模式,c表示固定码率,v表示可变码率,f表示固定QP{case 'c':enRcMode = SAMPLE_RC_CBR;break;case 'v':enRcMode = SAMPLE_RC_VBR;break;case 'f':enRcMode = SAMPLE_RC_FIXQP;break;default:printf("rc mode! is invaild!\n");goto END_VENC_1080P_CLASSIC_4;}/*** enSize[0] **/if(s32ChnNum >= 1)//开始对编码通道0进行编码{VpssGrp = 0;//VpssChn = 0;//将来绑定VPSS模块的GROUP0中的通道0VencChn = 0;//这个函数我们重点分析s32Ret = SAMPLE_COMM_VENC_Start(VencChn, enPayLoad[0],\gs_enNorm, enSize[0], enRcMode,u32Profile);if (HI_SUCCESS != s32Ret){SAMPLE_PRT("Start Venc failed!\n");goto END_VENC_1080P_CLASSIC_5;}//将VENC模块的通道0,与VPSS模块的GROUP0中的通道0,进行绑定s32Ret = SAMPLE_COMM_VENC_BindVpss(VencChn, VpssGrp, VpssChn);if (HI_SUCCESS != s32Ret){SAMPLE_PRT("Start Venc failed!\n");goto END_VENC_1080P_CLASSIC_5;}}/*** enSize[1] **/if(s32ChnNum >= 2){VpssChn = 1;VencChn = 1;s32Ret = SAMPLE_COMM_VENC_Start(VencChn, enPayLoad[1], \gs_enNorm, enSize[1], enRcMode,u32Profile);if (HI_SUCCESS != s32Ret){SAMPLE_PRT("Start Venc failed!\n");goto END_VENC_1080P_CLASSIC_5;}s32Ret = SAMPLE_COMM_VENC_BindVpss(VencChn, VpssGrp, VpssChn);if (HI_SUCCESS != s32Ret){SAMPLE_PRT("Start Venc failed!\n");goto END_VENC_1080P_CLASSIC_5;}}/*** enSize[2] **/if(s32ChnNum >= 3){VpssChn = 2;VencChn = 2;s32Ret = SAMPLE_COMM_VENC_Start(VencChn, enPayLoad[2], \gs_enNorm, enSize[2], enRcMode,u32Profile);if (HI_SUCCESS != s32Ret){SAMPLE_PRT("Start Venc failed!\n");goto END_VENC_1080P_CLASSIC_5;}s32Ret = SAMPLE_COMM_VENC_BindVpss(VencChn, VpssGrp, VpssChn);if (HI_SUCCESS != s32Ret){SAMPLE_PRT("Start Venc failed!\n");goto END_VENC_1080P_CLASSIC_5;}}
1、SAMPLE_COMM_VENC_Start 函数分析
参数1,表示VENC模块的某个通道。参数2,表示编码格式(是指VENC对视频流的编码压缩方式,并非指图像像素格式YUV这种概念。案例里三路编码通道的编码格式居然都是H264)。参数3,表示图像制式(这里是NTSC)。参数4,表示图像分辨率(枚举变量而已,但对应着具体的分辨率)。参数5,表示码率控制模式(即CBR\VBR\FIXQP)。参数6,表示profle(啥意思)。
该函数内部的流程如下:
(1)首先调用SAMPLE_COMM_SYS_GetPicSize函数获取图像的分辨率(因为需要将这些分辨率信息赋值给某些结构体的成员);
(2)然后switch语句判断参数2(即判断是哪种编码格式),由于参数2是PT_H264,因此接下来填充结构体变量stH264Attr的成员(这个结构体变量包含着H264协议编码通道的属性或者说设置信息)。
(3)接下来根据码率控制模式(我们输入的是“c”或者“v”或者“f”对应着CBR\VBR\FIXQP),来填充结构体变量stH264Cbr或stH264FixQp或stH264Vbr,以及结构体变量stVencChnAttr的成员(具体成员的含义有时间可以看看)。
(4)然后调用HI_MPI_VENC_CreateChn函数来创建通道,传入的参数1是VENC模块的某个通道,参数2是(3)中的结构体变量stVencChnAttr。
(5)最后调用HI_MPI_VENC_StartRecvPic函数,开始进行图像的接收与编码。
2、SAMPLE_COMM_VENC_BindVpss函数分析
这个函数内容与分析如下:
/*****************************************
* function : venc bind vpss
*******************************************/
HI_S32 SAMPLE_COMM_VENC_BindVpss(VENC_CHN VeChn,VPSS_GRP VpssGrp,VPSS_CHN VpssChn)
{HI_S32 s32Ret = HI_SUCCESS;MPP_CHN_S stSrcChn;MPP_CHN_S stDestChn;//绑定关系中,源头的相关信息stSrcChn.enModId = HI_ID_VPSS;//HI3518E中的哪个硬件单元,这里是VPSS模块硬件单元stSrcChn.s32DevId = VpssGrp;//VPSS模块中的哪个GROUP,这里VpssGrp=0stSrcChn.s32ChnId = VpssChn;//GROUP0中的哪个通道,这里是通道VpssChn//绑定关系中,接收方的相关信息stDestChn.enModId = HI_ID_VENC;/HI3518E中的哪个硬件单元,这里是VENC模块硬件单元stDestChn.s32DevId = 0;//VENC模块中的哪个设备,这里是设备0stDestChn.s32ChnId = VeChn;//设备0中的哪个通道,这里是通道VeChns32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);if (s32Ret != HI_SUCCESS){SAMPLE_PRT("failed with %#x!\n", s32Ret);return HI_FAILURE;}return s32Ret;
}