三.海思Hi3516ev100视频编码(VENC)模块

1.简介

  VENC 模块,即视频编码模块。本模块支持多路实时编码,且每路编码独立,编码协议和编码 profile 可以不同。本模块支持视频编码同时,调度 Region 模块对编码图像内容进行叠加和遮挡。

VENC 模块的输入源包括三类:

  • 用户态读取图像文件向编码模块发送数据;
  • 视频输入(VIU)模块采集的图像经视频处理子系统(VPSS)发送到编码模块;
  • 视频输入(VIU)模块采集的图像直接发送到编码模块

不同型号的芯片支持不同的编码规格,芯片支持的编码规格如表所示:

 

2 功能描述

2.1 编码数据流程图

 

 

典型的编码流程包括了输入图像的接收、图像内容的遮挡和覆盖、图像的编码、以及码流的输出等过程。
VENC 模块由编码通道子模块(VENC)和编码协议子模块(H.264/H.265/JPEG/MJPEG)组成。
通道支持接收 YUV 格式图像输入,支持格式为 Semi-planar YUV 4:2:0 或 Semi-planarYUV 4:2:2。

H.264/H.265 只支持 Semi-planar YUV 4:2:0
JPEG/MJPEG 支持 Semiplanar YUV 4:2:0 或 Semi-planar YUV 4:2:2
另外, Hi3518EV200 能够支持单分量输入(只存在 Y 分量)。通道模块接收外部原始图像数据,而不关心图像数据是来自哪个外
部模块。
通道接收到图像之后,比较图像尺寸和编码通道尺寸:

如果输入图像比编码通道尺寸大, VENC 将按照编码通道尺寸大小,调用 VGS 对源图像进行缩小,然后对缩小之后的图像进行编码。
如果输入图像比编码通道尺寸小, VENC 丢弃源图像。 VENC 不支持放大输入图像编码。
如果输入图像与编码通道尺寸相当, VENC 直接接受源图像,进行编码。

 

2.2 编码通道

编码通道作为基本容器,保存编码通道的多种用户设置和管理编码通道的多种内部资源。编码通道完成图像转化为码流的功能,具体由码率控制器和编码器协同完成。这里的编码器指的是狭义上的编码器,只完成编码功能。码率控制器提供了对编码参数的控制和调整,从而对输出码率进行控制。

2.3 码率控制

码率控制器实现对编码码率进行控制。
从信息学的角度分析,图像的压缩比越低,压缩图像的质量越高;图像压缩比例越高,压缩图像的质量越低。对于场景变化的真实场景,图像质量稳定,编码码率会波动;编码码率稳定,图像质量会波动。以 H.264 编码为例,通常图像 Qp 越低,图像的质量越好,码率越高;图像 Qp 越高,图像质量越差,码率越低。
码率控制是针对连续的编码码流而言,所以, JPEG 协议编码通道不包括码率控制功能。
码率控制器分别提供了对 H.264\H.265\MJPEG 协议编码通道 CBR、 VBR、 FIXQP 等三种码率控制模式,对图像质量和码率进行调节。
 

CBR
CBR(Constant Bit Rate)固定比特率。即在码率统计时间内保证编码码率平稳。码率稳定主要由两个量来评估,这两个量都可以由用户在创建编码通道时指定。

码率统计时间 u32StatTime
单位为秒(s),码率统计时间越长,每帧图像的码率波动对于码率调节的影响越弱,码率的调节会更缓慢,图像质量的波动会更轻微;码率统计时间越短,每帧图像的码率波动对于码率调节的影响越强,图像码率的调节会更灵敏,图像质量的波动会更剧烈。
行级码率控制调节幅度 u32RowQpDelta
行级码率控制调节幅度是一帧内行级调节的最大范围,其中行级以宏块行为单位。调节幅度越大,允许行级调整的 QP 范围越大,码率越平稳。对于图像复杂度分布不均匀的场景,行级码率控制调节幅度设置过大会带来图像质量不均匀。

VBR

VBR(Variable Bit Rate)可变比特率,即允许在码率统计时间内编码码率波动,从而保证编码图像质量平稳。以 H.264 编码为例, VENC 模块提供用户可设置 MaxQp,MinQp, MaxBitrate 和 ChangePos。 MaxQp, MinQp 用于控制图像的质量范围,MaxBitrate 用于钳位码率统计时间内的最大编码码率, ChangePos 用于控制开始调整Qp 的码率基准线。当编码码率大于 MaxBitrate*ChangePos 时,图像 qp 会逐步向MaxQp 调整,如果图像 QP 达到 MaxQp, QP 会被钳位到最大值, MaxBitrate 的钳位效果失效,编码码率有可能会超出 MaxBitrate。当编码码率小于 MaxBitrate*ChangePos时,图像 QP 会逐步向 MinQp 调整,如果图像 QP 达到 MinQp,此时编码的码率已经达到最大值,而且图像质量最好。

FIXQP

Fix Qp 固定 Qp 值。在码率统计时间内,编码图像所有宏块 Qp 值相同,采用用户设定的图像 Qp 值, I 帧和 P 帧的 QP 值可以分别设置。

三.函数图谱

SAMPLE_COMM_VENC_Start(step5)//start stream venc
            SAMPLE_COMM_SYS_GetPicSize
            HI_MPI_VENC_CreateChn
            HI_MPI_VENC_StartRecvPic
        SAMPLE_COMM_VENC_BindVpss
            HI_MPI_SYS_Bind
        SAMPLE_COMM_VENC_StartGetStream(step6)//stream venc process -- get stream, then save it to file.
            pthread_create(&gs_VencPid, 0, SAMPLE_COMM_VENC_GetVencStreamProc, (HI_VOID*)&gs_stPara);
        SAMPLE_COMM_VENC_StopGetStream(step7)//exit process

四.编码器设置

1.venc编码通道设置HI_MPI_VENC_CreatChn()

2开启HI_MPI_VENC_StartRecPic()

3.绑定来源 VPSS通道-->编码通道。

VENC_CHN_ATTR_S stVencChnAttr;
	VENC_ATTR_H264_S stH264Attr;
	VIDEO_NORM_E enNorm = VIDEO_ENCODING_MODE_NTSC;
	VENC_ATTR_H264_CBR_S stH264Cbr;  //定义 H.264 编码通道 CBR 属性结构
	SAMPLE_RC_E enRcMode = SAMPLE_RC_CBR;
	VpssGrp = 0;
	VpssChn = 0;
	VencChn = 0;
 
	SIZE_S stPicSize;
	stPicSize->u32Width  = 1280;
	stPicSize->u32Height = 720;
 
	/******************************************
	*step 1:  Create Venc Channel
	******************************************/
	stVencChnAttr.stVeAttr.enType = enType;
	stH264Attr.u32MaxPicWidth = stPicSize.u32Width;  //编码图像最大宽度
	stH264Attr.u32MaxPicHeight = stPicSize.u32Height;
	stH264Attr.u32PicWidth = stPicSize.u32Width;     //编码图像宽度
	stH264Attr.u32PicHeight = stPicSize.u32Height;
	stH264Attr.u32BufSize  = stPicSize.u32Width * stPicSize.u32Height;//码流 buffer 大小
	stH264Attr.u32Profile  = u32Profile;/*视频等级0: baseline; 1:MP; 2:HP;  3:svc_t */
	stH264Attr.bByFrame = HI_TRUE;/*帧/包模式获取码流 (1:按帧获取 0:按包获取)*/
	stH264Attr.u32BFrameNum = 0;/*编码支持 B 帧的个数 0: not support B frame; >=1: number of B frames */
	stH264Attr.u32RefNum = 1;/*编码支持参考帧的个数 0: default; number of refrence frame*/
	memcpy(&stVencChnAttr.stVeAttr.stAttrH264e, &stH264Attr, sizeof(VENC_ATTR_H264_S));
 
	stVencChnAttr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
	stH264Cbr.u32Gop            = (VIDEO_ENCODING_MODE_PAL== enNorm)?25:30;//H.264 gop 值
	stH264Cbr.u32StatTime       = 1;//CBR 码率统计时间,以秒为单位
	stH264Cbr.u32SrcFrmRate      = (VIDEO_ENCODING_MODE_PAL== enNorm)?25:30;//VI 输入帧率,以 fps 为单位
	stH264Cbr.fr32DstFrmRate = (VIDEO_ENCODING_MODE_PAL== enNorm)?25:30;//编码器输出帧率,以 fps 为单位
 
	stH264Cbr.u32BitRate = 1024*2;//平均 bitrate,以 kbps 为单位
	stH264Cbr.u32FluctuateLevel = 0;  //最大码率相对平均码率的波动等级
	memcpy(&stVencChnAttr.stRcAttr.stAttrH264Cbr, &stH264Cbr, sizeof(VENC_ATTR_H264_CBR_S));
 
	s32Ret = HI_MPI_VENC_CreateChn(VencChn, &stVencChnAttr);//创建编码通道
	if (HI_SUCCESS != s32Ret)
	{
		SAMPLE_PRT("HI_MPI_VENC_CreateChn [%d] faild with %#x!\n",VencChn, s32Ret);
		return s32Ret;
	}
 
	/******************************************
	*step 2:  开启编码通道接收输入图像
	******************************************/
	s32Ret = HI_MPI_VENC_StartRecvPic(VencChn);
	if (HI_SUCCESS != s32Ret)
	{
		SAMPLE_PRT("HI_MPI_VENC_StartRecvPic faild with%#x!\n", s32Ret);
		return HI_FAILURE;
	}
 
	/******************************************
	*step3 :  绑定VPSS通道和编码通道
	******************************************/
	s32Ret = SAMPLE_COMM_VENC_BindVpss(VencChn, VpssGrp, VpssChn);
	if (HI_SUCCESS != s32Ret)
	{
		SAMPLE_PRT("Start Venc failed!\n");
		goto END_VENC_1080P_CLASSIC_5;
	}

第五步:开始获取视频流并并存储到文件

整个系统我们就设置好了,下一步要操作地是开始接收H264码流并将码流保存起来。这里用到多线程,用pthread_create创建个线程函数,在线程函数里用select多路IO复用来获取码流并保存

 gs_stPara.bThreadStart = HI_TRUE;
    gs_stPara.s32Cnt = s32Cnt;
 
    return pthread_create(&gs_VencPid, 0, SAMPLE_COMM_VENC_GetVencStreamProc, (HI_VOID*)&gs_stPara);

线程处理程序:

HI_VOID* VENC_GetVencStreamProc(HI_VOID *p)
{
	SAMPLE_VENC_GETSTREAM_PARA_S *pstPara;
	HI_S32 s32ChnTotal;
	VENC_CHN VencChn;
	HI_S32 s32Ret;
	PAYLOAD_TYPE_E enPayLoadType[VENC_MAX_CHN_NUM];
	HI_CHAR aszFileName[VENC_MAX_CHN_NUM][64];
	VENC_CHN_ATTR_S stVencChnAttr;
	struct timeval TimeoutVal;
	fd_set read_fds;
	HI_S32 VencFd[VENC_MAX_CHN_NUM],maxfd;
	VENC_STREAM_S stStream;
	VENC_CHN_STAT_S stStat;
	FILE *pFile[VENC_MAX_CHN_NUM];
	HI_S32 i;
 
	pstPara = (SAMPLE_VENC_GETSTREAM_PARA_S*)p;
	s32ChnTotal = pstPara->s32Cnt;//pstPara->s32Cnt是由参数传进来的,为1
	for (i = 0; i < s32ChnTotal; i++)
	{
		VencChn = i;
		s32Ret = HI_MPI_VENC_GetChnAttr(VencChn, &stVencChnAttr);
 
		//这里是为了取得是什么编码类型,以便确定保存文件的后缀名
		//比如这里是H264编码,所以保存文件的后缀后就是.h264
		if(s32Ret != HI_SUCCESS)
		{
			SAMPLE_PRT("HI_MPI_VENC_GetChnAttr chn[%d] failed with %#x!\n", \
			VencChn, s32Ret);
			return NULL;
		}
 
		enPayLoadType[i] = stVencChnAttr.stVeAttr.enType;
		sprintf(aszFileName[i], "stream_chn0%d%s",filename, i, ".h264");//20171101085639.h264		
 
		pFile[i] = fopen(aszFileName[i], "wb");
		if (!pFile[i])
		{
			SAMPLE_PRT("open file[%s] failed!\n", aszFileName[i]);
			return NULL;
		}
                //编码好的视频流文件描述符
		VencFd[i] = HI_MPI_VENC_GetFd(i);//获取编码器的文件描述符,以便后面能用select来IO复用
		if (VencFd[i] < 0)
		{
			SAMPLE_PRT("HI_MPI_VENC_GetFd failed with %#x!\n", VencFd[i]);
			return NULL;
		}
		if (maxfd <= VencFd[i])
		{
			maxfd = VencFd[i];
		}
	}
	
	//当main函数所在的线程接收到两个键盘字符或ctrl+c时,pstPara->bThreadStart会为假,跳出while循环
	//然后往下执行关闭前面打开的文件,执行完这个VENC_GetVencStreamProc线程函数,线程结束
	while (HI_TRUE == pstPara->bThreadStart)
	{
	/*IO复用4步骤
		1.清空文件集合FD_ZERO(&read_fds);
		2.将文件加入文件集合FD_SET(VencFd[i], &read_fds);
		3.设置超时时间并用select(maxfd + 1, &read_fds, NULL, NULL, &TimeoutVal)来等待文件状态有变化唤醒线程
		或超时唤醒
		4.FD_ISSET查询文件状态是否有变化,有变化则处理
	*/
 
		FD_ZERO(&read_fds);
		for (i = 0; i < s32ChnTotal; i++)
		{
			FD_SET(VencFd[i], &read_fds);
		}
 
		TimeoutVal.tv_sec  = 2;
		TimeoutVal.tv_usec = 0;
		s32Ret = select(maxfd + 1, &read_fds, NULL, NULL, &TimeoutVal);
		if (s32Ret < 0)
		{
			SAMPLE_PRT("select failed!\n");
			break;
		}
		else if (s32Ret == 0)
		{
			SAMPLE_PRT("get venc stream time out, exit thread\n");
			continue;
		}
		else
		{
			for (i = 0; i < s32ChnTotal; i++)
			{
				if (FD_ISSET(VencFd[i], &read_fds))
				{
					memset(&stStream, 0, sizeof(stStream));
					//查询是否有码流,并将码流信息填充到stStat结构体中
					s32Ret = HI_MPI_VENC_Query(i, &stStat);
					if (HI_SUCCESS != s32Ret)
					{
						SAMPLE_PRT("HI_MPI_VENC_Query chn[%d] failed with %#x!\n", i, s32Ret);
						break;
					}
 
					if(0 == stStat.u32CurPacks)
					{
						SAMPLE_PRT("NOTE: Current  frame is NULL!\n");
						continue;
					}
					
					//分配内存以便保存码流包数据
					stStream.pstPack = (VENC_PACK_S*)malloc(sizeof(VENC_PACK_S) * stStat.u32CurPacks);
					if (NULL == stStream.pstPack)
					{
						SAMPLE_PRT("malloc stream pack failed!\n");
						break;
					}
 
					stStream.u32PackCount = stStat.u32CurPacks;
					//printf("stStream.u32PackCount=%d\n",stStream.u32PackCount);
					//获取码流数据并保存到stStream结构体中
					s32Ret = HI_MPI_VENC_GetStream(i, &stStream, HI_TRUE);
					if (HI_SUCCESS != s32Ret)
					{
						free(stStream.pstPack);//获取失败则要释放前面分配的内存,否则会造成内存溢出
						stStream.pstPack = NULL;
						SAMPLE_PRT("HI_MPI_VENC_GetStream failed with %#x!\n", s32Ret);
						break;
					}
 
					HI_S32 u32PackIndex;
					for (u32PackIndex= 0;u32PackIndex < stStream.u32PackCount; u32PackIndex++)
					{
			fwrite(	stStream.pstPack[u32PackIndex].pu8Addr+stStream.pstPack[u32PackIndex].u32Offset,\
			stStream.pstPack[u32PackIndex].u32Len-  stStream.pstPack[u32PackIndex].u32Offset, \
								1, pFile[i]);
						fflush(pFile[i]);
#if 0
printf("stStream.u32PackCount=%d,stStream.pstPack[%d].pu8Addr=0x%08x,\
stStream.pstPack[%d].u32Offset=%d,stStream.pstPack[%d].u32Len=%d\n",\
stStream.u32PackCount,u32PackIndex,stStream.pstPack[u32PackIndex].pu8Addr,\
u32PackIndex,stStream.pstPack[u32PackIndex].u32Offset,u32PackIndex,stStream.pstPack[u32PackIndex].u32Len);
//添加打印信息,查看保存码流内容的内存是怎么样的
#endif
					}
 
					s32Ret = HI_MPI_VENC_ReleaseStream(i, &stStream);//保存后要释放码流
					if (HI_SUCCESS != s32Ret)
					{
						free(stStream.pstPack);//获取失败则要释放前面分配的内存,否则会造成内存溢出
						stStream.pstPack = NULL;
						break;
					}
 
					free(stStream.pstPack);//释放码流后,也要释放分配的内存,避免内存溢出
					stStream.pstPack = NULL;
				}
			}
		}
	}
 
	for (i = 0; i < s32ChnTotal; i++)
	{
		fclose(pFile[i]);//关闭各个文件
	}
	return NULL;
}

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页