目录
一、图像编码压缩基本原理
由于网上已有大量优秀的文章讲了这个,这里我就不写了,内容重复无意义!
参考:http://blog.csdn.net/newchenxf/article/details/51693753
压缩和解压缩就是数学运算的过程。
二、MPP手册中图像编码部分解读
码流(DataRate)是指视频文件在单位时间内使用的数据流量,也叫码率,是视频编码中
画面质量控制中最重要的部分。同样分辨率下,视频文件的码流越大,压缩比就越小,画面质
量就越高。
上行带宽就是本地上传信息到网络上的带宽。上行速率是指用户电脑向网络发送信息时的
数据传输速率,比如用FTP上传文件到网上去,影响上传速度的就是“上行速率”。
下行带宽就是从网络上下载信息的带宽。下行速率是指用户电脑从网络下载信息时的数据
传输速率,比如从FTP服务器上文件下载到用户电脑,影响下传速度的就是“下行速率”。
注:监控点的带宽是要求上行的最小限度带宽(监控点将视频信息上传到监控中心);监控
中心的带宽是要求下行的最小限度带宽(将监控点的视频信息下载到监控中心);例:电信2Mbps
的ADSL宽带,理论上其上行带宽是512kbps=64kb/s,其下行带宽是2Mbps=256kb/s
BP:最基本的,传输速率高,但视频质量较差
MP:主流的,传输速度、视频直流相对都可以
详解:https://blog.csdn.net/u014470361/article/details/88713266
典型的编码流程包括了输入图像的接收、图像内容的遮挡和覆盖、图像的编码、以及码流的输出等过程。
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 直接接受源图像,进行编码。
说明:
1、 通道的帧率控制默认不打开,需要用户调用接口设置。RC(码率控制器) 中也具有帧率控
制功能。推荐使用 RC 的帧率控制,这样不会对码率控制造成过大的冲击。
2、 对于 Hi3518EV200 的H.264 编码,输入图像格式由非单分量切换为单分量时,由于存
在帧间预测量化误差,在编码出下一个I 帧之前图像会存有色度残留。建议客户在切换单分
量时调用接口 HI_MPI_VENC_ResetChn 进行通道复位。
REGION 模块支持对图像内容的遮挡和叠加。完成视频区域管理之后,图像被送入具体协议类型编码通道,完成视频编码,输出码流。
码率控制器实现对编码码率进行控制。从信息学的角度分析,图像的压缩比越低,压缩图像的质量越高;图像压缩比例越高,压缩图像的质量越低。对于场景变化的真实场景,图像质量稳定,编码码率会波动;编码码率稳定,图像质量会波动。以 H.264 编码为例,通常图像 Qp 越低,图像的质量越好,码率越高;图像 Qp 越高,图像质量越差,码率越低。码率控制是针对连续的编码码流而言,所以, JPEG 协议编码通道不包括码率控制功能。
Qp参考:http://blog.csdn.net/u013354805/article/details/51988171
码率控制器分别提供了对 H.264\H.265\MJPEG 协议编码通道 CBR、 VBR、 FIXQP 等三种码率控制模式,对图像质量和码率进行调节。Hi3518EV200 不支持 H.265 编码,所以,也不支持 H.265 类型的码率控制。
CBR( Constant Bit Rate)固定比特率。
VBR( Variable Bit Rate)可变比特率,即允许在码率统计时间内编码码率波动,从而保证编码图像质量平稳。
Fix Qp 固定 Qp 值。在码率统计时间内,编码图像所有宏块 Qp 值相同,采用用户设定的图像 Qp 值, I 帧和 P 帧的 QP 值可以分别设置。
更多细节,请阅读文档:
Hi3518E V200R001C01SPC030\01.software\board\document_cn\HiMPP IPC V2.0 媒体处理软件开发参考.pdf
三、sample中venc模块源码解读
/******************************************
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)
{
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)
{
VpssGrp = 0;
VpssChn = 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;
}
s32Ret = SAMPLE_COMM_VENC_BindVpss(VencChn, VpssGrp, VpssChn);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("Start Venc failed!\n");
goto END_VENC_1080P_CLASSIC_5;
}
}
SAMPLE_COMM_VENC_Start
SAMPLE_COMM_SYS_GetPicSize //获取图片的参数、
HI_MPI_VENC_CreateChn 创建编码通道
HI_MPI_VENC_StartRecvPic 开启编码通道接收输入图像。
SAMPLE_COMM_VENC_BindVpss
HI_MPI_SYS_Bind 数据源到数据接收者绑定接口
四、编码后的流文件输出和专栏总结
/******************************************
step 6: stream venc process -- get stream, then save it to file.
******************************************/
s32Ret = SAMPLE_COMM_VENC_StartGetStream(s32ChnNum);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("Start Venc failed!\n");
goto END_VENC_1080P_CLASSIC_5;
}
printf("please press twice ENTER to exit this sample\n");
getchar();
getchar();
/******************************************
step 7: exit process
******************************************/
SAMPLE_COMM_VENC_StopGetStream();
/******************************************************************************
* funciton : start get venc stream process thread
******************************************************************************/
HI_S32 SAMPLE_COMM_VENC_StartGetStream(HI_S32 s32Cnt)
{
gs_stPara.bThreadStart = HI_TRUE;
gs_stPara.s32Cnt = s32Cnt;
return pthread_create(&gs_VencPid, 0, SAMPLE_COMM_VENC_GetVencStreamProc, (HI_VOID*)&gs_stPara);
}
/******************************************************************************
* funciton : get stream from each channels and save them
******************************************************************************/
HI_VOID* SAMPLE_COMM_VENC_GetVencStreamProc(HI_VOID *p)
{
//该函数的内容被我删除一部分,只留了主要部分,用于梳理思路
/******************************************
step 1: check & prepare save-file & venc-fd
******************************************/
for (i = 0; i < s32ChnTotal; i++)
{
/* decide the stream file name, and open file to save stream */
VencChn = i;
s32Ret = HI_MPI_VENC_GetChnAttr(VencChn, &stVencChnAttr);
enPayLoadType[i] = stVencChnAttr.stVeAttr.enType;
s32Ret = SAMPLE_COMM_VENC_GetFilePostfix(enPayLoadType[i], szFilePostfix);
/* Set Venc Fd. */
VencFd[i] = HI_MPI_VENC_GetFd(i);//打开一个空白文件,得到文件描述符,之后
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];
}
}
/******************************************
step 2: Start to get streams of each channel.
******************************************/
while (HI_TRUE == pstPara->bThreadStart)//开始从每个通道得到视频流
{//当连按两下enter键,这个变量pstPara->bThreadStart被赋值为false
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))
{
/*******************************************************
step 2.1 : query how many packs in one-frame stream.
*******************************************************/
memset(&stStream, 0, sizeof(stStream));
s32Ret = HI_MPI_VENC_Query(i, &stStat);//查询编码通道状态
/*******************************************************
step 2.2 :suggest to check both u32CurPacks and u32LeftStreamFrames at the same time,for example:
if(0 == stStat.u32CurPacks || 0 == stStat.u32LeftStreamFrames)
{
SAMPLE_PRT("NOTE: Current frame is NULL!\n");
continue;
}
在编码通道状态结构体中, u32CurPacks 表示当前帧的码流包个数。
在调用HI_MPI_VENC_GetStream 之前应确保 u32CurPacks 大于 0
在编码通道状态结构体中, u32LeftStreamFrames 表示码流buffer
中剩余的帧数目。在复位通道前,可以通过查询是否还有图像的码流没
有被取走来决定复位时机,防止复位时将可能需要的码流清理出去
*******************************************************/
if(0 == stStat.u32CurPacks)
{
SAMPLE_PRT("NOTE: Current frame is NULL!\n");
continue;
}
/*******************************************************
step 2.3 : malloc corresponding number of pack nodes.
码流结构体 VENC_STREAM_S 包含 4 个部分:
− 码流包信息指针 pstPack
指向一组 VENC_PACK_S 的内存空间,该空间由调用者分配。如果是按包获
取,则此空间不小于 sizeof( VENC_PACK_S)的大小;如果按帧获取,则此
空间不小于 N × sizeof( VENC_PACK_S)的大小,其中 N 代表当前帧之中的
包的个数,可以在 select 之后通过查询接口获得。
− 码流包个数 u32PackCount
在输入时,此值指定 pstPack 中 VENC_PACK_S 的个数。按包获取时,
u32PackCount 必须不小于 1;按帧获取时, u32PackCount 必须不小于当前帧的
包个数。在函数调用成功后, u32PackCount 返回实际填充 pstPack 的包的个
数。
− 序列号 u32Seq
按帧获取时是帧序列号;按包获取时为包序列号。
− 码流特征信息,数据类型为联合体,包含了不同编码协议对应的码流特征信息
stH264Info/ stJpegInfo/ stMpeg
*******************************************************/
stStream.pstPack = (VENC_PACK_S*)malloc(sizeof(VENC_PACK_S) * stStat.u32CurPacks);
/*******************************************************
step 2.4 : call mpi to get one-frame stream
*******************************************************/
stStream.u32PackCount = stStat.u32CurPacks;
//获取编码码流
s32Ret = HI_MPI_VENC_GetStream(i, &stStream, HI_TRUE);
/*******************************************************
step 2.5 : save frame to file,
*******************************************************/
s32Ret = SAMPLE_COMM_VENC_SaveStream(enPayLoadType[i], pFile[i], &stStream);
/*******************************************************
step 2.6 : release stream
*******************************************************/
//释放码流缓存。
s32Ret = HI_MPI_VENC_ReleaseStream(i, &stStream);
/*******************************************************
step 2.7 : free pack nodes
*******************************************************/
free(stStream.pstPack);
stStream.pstPack = NULL;
}
}
}
}
/*******************************************************
* step 3 : close save-file
*******************************************************/
for (i = 0; i < s32ChnTotal; i++)
{
fclose(pFile[i]);//关闭打开的文件
}
return NULL;
}
HI_S32 SAMPLE_COMM_VENC_StopGetStream()
{
if (HI_TRUE == gs_stPara.bThreadStart)
{
gs_stPara.bThreadStart = HI_FALSE;//用于停止上边这个函数中的那个While循环
pthread_join(gs_VencPid, 0);
}
return HI_SUCCESS;
}
SAMPLE_COMM_VENC_SaveStream
SAMPLE_COMM_VENC_SaveH264
fwrite
fflush //flush()会强迫将缓冲区内的数据写回参数stream 指定的文件中
如果你想理解更多的函数细节和视频编解码的一些流程及参数设置,请仔细阅读我们本季前几篇文章提到的数据手册。
注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来、参考了华为海思SDK中提供的开发手册,并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。