RV1126画面质量三:QP调节
一.什么是 QP 调节?
QP 参数调节,指的是量化参数调节。它主要是来调节图像的细节,最终达到调节画面质量的作用。QP 值和比特率成反比,QP值越小画面质量越高;反之 QP 值越大,画面质量越低。而且随着视频源复杂度,这种反比的关系会更加明显。QP 调节是改变画面质量最常用的手段之一。
QP调节H264和H265画面最常用的一个手段。
二.QP 调节的基本概念:
QP 调节一般由 QStep、MinQp、MaxQp 这三个参数进行调节。下面我们来看看,这三个参数的作用。下面这个是QP值和QStep对应的一个关键值。假设我的Qset是1,QP值就是4,在开发的时候,一个是直接调Qset就可以了
前面说了QP值越小,画面越好,反之画面越差。 因为QP值和QStep对应的一个关键值。所以我们通过调Qset来设置QP值。
2.2. minqp 最小量化步长(调节禁止画面)
设置最小量化器,限制最好的图像质量(重点在静止画面比如建筑,植物......),当 QP 达到这个值的时候,数值不会变。这就会使得在静止场景下,码率到达一定数量后不会进行调整。minqp 越小,静止时候码率越大,质量越好,建议值[8,20]。简单说就是解决禁止画面的异常,比如呼吸效应。这个值是经验值来的,是经过多调试才能得出一个比较不错的值,8是最好,但是有代价就是码率比较大。20画面质量也还行,但是码效率小。感根据实际情况取舍。如果我填8,这个植物是没有任何呼吸效应和马赛克情况。
比如下面这写球员站着的球员用minqp 最小量化步长,因为画面相对禁止。
2.3. maxqp 最大量化步长(调节运动画面)
设置最大量化器,最大 QP 值,限制最差的画面(重点在运动的时候),在运动画面把宏块和马赛克去掉,maxQp 越小,2同样道理,当运动情况下到达一定码率后就不会调整。建议值[20,50],一样要多试。
比如踢球,因为是运动的,就要调maxqp
QSet调整体画面,minQp调禁止的画面,maxqp调运动画面。
QP调节的有一个数据结构体rvVENC_RC_PARAM_S数据结构
u32ThrdI、u32ThrdP:分别衡量 I 帧、P 帧宏块复杂的一组阈值。这组阈值是从小到大依次排序,每个阈值的取值是[0,255]。这组阈值主要用于宏块级别的码率控制,并根据图像复杂程度对每个宏块进行 QP 调节。这部分大部分也用在 CBR 上面。
u32ThrdI 默认值:[0,0,0,0,3,3,5,5,8,8,8,15,15,20,25,25]
u32ThrdP 默认值:[0,0,0,0,3,3,5,5,8,8,8,15,15,20,25,25]
注意:u32ThrdI、u32ThrdP 在一般情况下不需要设置
下面解析一下:
我们知道每一帧都是一张图片,图片又是是一个一个像素点组成,16X16的像素点,为一个宏快,u32ThrdI、u32ThrdP是对这些宏块调节,可以说是微观调节,但是用处不大,不如整体调节
u32RowQpDeltaI: 基于行的宏块级别码率控制,每一行的宏块的起始 QP 相对于 I 帧的 QP 波动幅度值,主要用在 CBR 控制模式。对于码率波动比较严格的场景下,可以通过调节这个参数使得码率控制更加精确。在高码率时,该值推荐为 0;中码率时推荐该值为 0 或 1;低码率时推荐 该值为 2~5。
意思是每一个小宏块的QP 相对于 I 帧的 QP 波动幅度值,宏块的QP - I 帧的 QP = 码率的幅度
u32RowQpDeltaP: 基于行的宏块级别码率控制,每一行的宏块的起始 QP 相对于 P 帧的 QP 波动幅度值,主要用在 CBR 控制模式。对于码率波动比较严格的场景下,可以通过调节这个参数使得码率控制更加精确。在高码率时,该值推荐为 0;中码率时推荐该值为 0 或 1;低码率时推荐 该值为 2~5。
意思是每一个小宏块的QP 相对于 I 帧的 QP 波动幅度值,宏块的QP - I 帧的 QP = 码率的幅度
下面的表格就是来解释哪些范围是高码率,中码率,低码率
s32FirstFrameStartQp:第一帧 QP 值,默认-1。-1 代表的是第一帧的起始 QP 由编码器内部进行计算。若是其他值,则由用户指定该合法值为第一帧起始 QP。
在数据结果里面,这三个才是重点,实际开发的时候也是调这个
- stParamH264:主要是调节 H264 的 QP 参数
- stParamH265:主要是调节 H265 的 QP 参数
- stParamMjpeg:主要是调节 Mjpeg 的 QP 参数
stParamH264:主要是调节 H264 的 QP 参数:
- u32StepQp: QP 的 STEP 步长,STEP 步长去调节 QP 值,要想画面最好直接填1
- u32MaxQp:QP 的最大值调节非I帧的,对运动画面进行限制,若忽视码率则建议 51;若对质量讲究,则建议设置[20,51]。
- u32MinQp:QP 的最小值,希望在静止画面的时候节省码率,取值范围[0,48]。VBR 建议设置为[24,32],CBR 建议设置[10,20]
- u32MaxIQp: I 帧的 QP 最大值,取值范围[8,51],图像运动的时候节省码率,默认和 u32MaxQP 的值是一致。
- u32MinIQp: I 帧的 QP 最小值,取值范围[0,48],图像静止或小运动的时候节省码率,默认和 u32MinIQp 是一致。
u32MaxQp和u32MinQp还是基于I帧P帧B帧直接整体调整,u32RowQpDeltaI和u32RowQpDeltaP:基于宏块调整。宏块调整的效果远远低于整体调整
stParamH265:主要是调节 H265 的 QP 参数:和264差不多
- u32StepQp: QP 的 STEP 步长
- u32MaxQp:QP 的最大值,对运动画面进行限制,若忽视码率则建议 51;若对质量讲究,则建议设置[40,51]。取值范围[8,51]。
- u32MinQp:QP 的最小值,希望在静止画面的时候节省码率,取值范围[0,48]。VBR 建议设置为[24,32],CBR 建议设置[10,20]
- u32MaxIQp: I 帧的 QP 最大值,取值范围[8,51],图像运动的时候节省码率,默认和 u32MaxQP 的值是一致。
- u32MinIQp: I 帧的 QP 最小值,取值范围[0,48],图像静止或小运动的时候节省码率,默认和 u32MinIQp 是一致。
编码 :
#include <stdio.h>
#include "rkmedia_config.h"
void * collect_venc_thread(void * args)
{
pthread_detach(pthread_self());
MEDIA_BUFFER mb;
FILE * h264_file = fopen("./test_output_smart.h264", "w+");
while (1)
{
mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, 0 , -1);
if(!mb)
{
printf("Get Venc Buffer Break....\n");
break;
}
printf("mmmmmm\n");
fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, h264_file);
RK_MPI_MB_ReleaseBuffer(mb);
}
return NULL;
}
int main()
{
RK_U32 u32Width = 1920;
RK_U32 u32Height = 1080;
RK_CHAR *pDeviceName = "rkispp_scale0";
RK_CHAR *pOutPath = NULL;
RK_CHAR *pIqfilesPath = NULL;
CODEC_TYPE_E enCodecType = RK_CODEC_TYPE_H264;
RK_CHAR *pCodecName = "H264";
RK_S32 s32CamId = 0;
RK_U32 u32BufCnt = 3;
rk_aiq_working_mode_t hdr_mode = RK_AIQ_WORKING_MODE_NORMAL;
SAMPLE_COMM_ISP_Init(hdr_mode, RK_FALSE);
SAMPLE_COMM_ISP_Run();
SAMPLE_COMM_ISP_SetFrameRate(30);
int ret;
RK_MPI_SYS_Init();
VI_CHN_ATTR_S vi_chn_attr;
vi_chn_attr.pcVideoNode = pDeviceName;
vi_chn_attr.u32BufCnt = u32BufCnt;
vi_chn_attr.u32Width = u32Width;
vi_chn_attr.u32Height = u32Height;
vi_chn_attr.enPixFmt = IMAGE_TYPE_NV12;
vi_chn_attr.enBufType = VI_CHN_BUF_TYPE_MMAP;
vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL;
ret = RK_MPI_VI_SetChnAttr(s32CamId, 1, &vi_chn_attr);
ret |= RK_MPI_VI_EnableChn(s32CamId, 1);
if (ret)
{
printf("ERROR: create VI[0] error! ret=%d\n", ret);
return 0;
}
VENC_CHN_ATTR_S venc_chn_attr;
memset(&venc_chn_attr, 0, sizeof(venc_chn_attr));
venc_chn_attr.stVencAttr.enType = RK_CODEC_TYPE_H264;
venc_chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = 30 * 4;
venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate = u32Width * u32Height;
// frame rate: in 30/1, out 30/1.
venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;
venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 30;
venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1;
venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 30;
venc_chn_attr.stVencAttr.imageType = IMAGE_TYPE_NV12;
venc_chn_attr.stVencAttr.u32PicWidth = u32Width;
venc_chn_attr.stVencAttr.u32PicHeight = u32Height;
venc_chn_attr.stVencAttr.u32VirWidth = u32Width;
venc_chn_attr.stVencAttr.u32VirHeight = u32Height;
venc_chn_attr.stVencAttr.u32Profile = 77;
ret = RK_MPI_VENC_CreateChn(0, &venc_chn_attr);
if (ret)
{
printf("ERROR: create VENC[0] error! ret=%d\n", ret);
return 0;
}
RK_U32 u32StepQp = 1; //因为是一个整形,我们尽量填整型
RK_U32 u32MaxQp = 24; //这个只能说一个经验值,可能24还会一点点马赛克的情况,只能说多调试
RK_U32 u32MinQp = 10; //禁止的时候我们把画面质量提高,10是比较高的了
RK_U32 u32MaxIQp = 24;
RK_U32 u32MinIQp = 10;
VENC_RC_PARAM_S venc_rc_param;
venc_rc_param.u32RowQpDeltaI = 1; //因为是中码率所以填1
venc_rc_param.u32RowQpDeltaP = 1; //因为是中码率所以填1
venc_rc_param.s32FirstFrameStartQp = -1; //默认-1
//把值设置进去
venc_rc_param.stParamH264.u32StepQp = u32StepQp;
venc_rc_param.stParamH264.u32MaxQp = u32MaxQp;
venc_rc_param.stParamH264.u32MinQp = u32MinQp;
venc_rc_param.stParamH264.u32MinIQp = u32MinIQp;
venc_rc_param.stParamH264.u32MaxIQp = u32MaxIQp;
ret = RK_MPI_VENC_SetRcParam(0, &venc_rc_param);
if (ret != 0)
{
printf("venc_params failed....\n");
}
else
{
printf("venc_params success....\n");
}
MPP_CHN_S stSrcChn;
stSrcChn.enModId = RK_ID_VI;
stSrcChn.s32DevId = 0;
stSrcChn.s32ChnId = 1;
MPP_CHN_S stDestChn;
stDestChn.enModId = RK_ID_VENC;
stDestChn.s32DevId = 0;
stDestChn.s32ChnId = 0;
ret = RK_MPI_SYS_Bind(&stSrcChn, &stDestChn);
if (ret)
{
printf("ERROR: Bind VI[0] and VENC[0] error! ret=%d\n", ret);
return 0;
}
pthread_t pid;
ret = pthread_create(&pid, NULL, collect_venc_thread, NULL);
if(ret != 0)
{
printf("Create Venc Thread Failed....\n");
}
while (1)
{
sleep(20);
}
return 0;
}