第二季7:创建配置编码通道(step5:VENC部分)

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

前言

本文将详细介绍博文第二季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编码

第二季7:创建配置编码通道(step5:VENC部分)_第1张图片

4、VENC模块的上下文

VENC模块的上下文如下图所示。

第二季7:创建配置编码通道(step5:VENC部分)_第2张图片

编码流程包括:输入图像的接收、图像内容的遮挡和覆盖、图像的编码、以及码流的输出等过程。

(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)具体协议编码通道

具体协议编码通道的功能框图如下,从中可知具体编码通道由码率控制器、编码器协同完成,主要实现图像转化为编码码流的功能。这里的编码器,指的是狭义上的编码器,只完成编码功能。码率控制器,提供对编码参数的控制和调整,从而对输出码率进行控制。
第二季7:创建配置编码通道(step5:VENC部分)_第3张图片

对于 H.264、H.265、MJPEG 协议的编码通道,码率控制器提供 CBR(固定比特率)、VBR(可变比特率)、FIXQP(固定QP) 这三种码率控制模式,对图像质量和码率进行调节。

5、编码码流的帧配置模式

阅读本节内容前,先理解博文第6季1:H264编码原理与基本概念_天糊土的博客的内容。

编码码流帧配置支持两种模式:单包模式和多包模式。也就是说,我们可以将一个帧的数据作为一个码流包发送(即单包模式),也可以将一个帧搞成几个(H.264为4个)码流包进行发送(即多包模式)。注意这里说的多包,特指在不调用分包接口的情况下,也就是说不对一帧数据进行“子帧”划分,或者说,以一帧完整的图像数据作为单位的。

它们的示意图如下(以H.264为例)。

第二季7:创建配置编码通道(step5:VENC部分)_第4张图片

(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_Start
    SAMPLE_COMM_SYS_GetPicSize
    HI_MPI_VENC_CreateChn//创建编码通道
    HI_MPI_VENC_StartRecvPic//接收图片并开始编码
    
SAMPLE_COMM_VENC_BindVpss
    HI_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中的通道0
	    VencChn = 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=0
    stSrcChn.s32ChnId = VpssChn;//GROUP0中的哪个通道,这里是通道VpssChn

    //绑定关系中,接收方的相关信息
    stDestChn.enModId = HI_ID_VENC;/HI3518E中的哪个硬件单元,这里是VENC模块硬件单元
    stDestChn.s32DevId = 0;//VENC模块中的哪个设备,这里是设备0
    stDestChn.s32ChnId = VeChn;//设备0中的哪个通道,这里是通道VeChn

    s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);
    if (s32Ret != HI_SUCCESS)
    {
        SAMPLE_PRT("failed with %#x!\n", s32Ret);
        return HI_FAILURE;
    }

    return s32Ret;
}

你可能感兴趣的:(#,图像编码)