一. 什么是 GOP
GOP 实际上就是两个 I 帧的间隔,比方说分辨率是 1920 * 1080 50 帧,假设 GOP 为 5,那就是大概 2s 插入一个 I 帧。我们再
回顾下,H264/H265 的帧结构。H264/H265 分别分为三种帧类型:I 帧、P 帧、B 帧。
- I 帧:全称是帧内编码图像帧,意思是它不需要其他参考帧,只需要利用本帧就可以知道具体的信息
- P 帧:全称是预测编码图像帧,需要利用前面的 I 帧或者 P 帧,采用运动预测的方式去进行编码。P 帧的压缩比高于 I 帧
- B 帧:全称是双向预测编码图像帧,B 帧提供了最高的压缩比,它既需要参考前面的 P 帧或 I 帧、也需要参考后面的 P 帧、I 帧
按照字节数来说, I 帧> P 帧 > B 帧。在码率不变的情况,一个 GOP 的值越大,P、B 帧的数量就会越多,画面质量就会相对较好。 所以在开发中,我们可以适当增加 GOP 的长度去改善画面质量。但是值得注意的是,GOP 的长度不适合设置太长,由于 P 帧、B 帧复杂度远比 I 帧高。若过长的 GOP 就会使得 P 帧、B 帧数量非常多,从而导致编码器压力较大编码效率降低。通
常来说,GOP_SIZE 要设置成帧率的 1-10 倍。
为什么 GOP 较大时画面质量较好?
-
长时间内的视觉连续性:当 GOP 较大时,I 帧的数量减少,但 P 帧和 B 帧通过差异编码的方式,提供了更多的“相似性”信息,使得视频在相邻的帧之间能够保持较高的画面质量,尤其是在动作较为平稳或者变化不大的场景中。
-
B 帧和 P 帧的高效编码:P 帧和 B 帧通常相对较小,因为它们只是记录和前后帧之间的差异,不需要完整的图像数据。这使得它们相对来说更加节省带宽。因此,GOP 较大时,会有更多的 P 和 B 帧来提高编码效率和压缩效果
-
在网络推流中尽量不要引入b帧,本地的4k视频就可以。
二. RV1126 中的 GOP 模式
在 RV1126 中,GOP 分为两种常见模式。一种是普通 GOP 模式,另外一种是智能编码 GOP 模式。下面我们都分别介绍一下:
2.1. 普通 GOP 模式:
普通的 GOP 模式,也是我们最常见的场景。就是每隔一段 GOP_SIZE 就会插入一个 I 帧,如下图所示:
上图没有B帧,因为网络中传播不需要I帧,I帧的解码时间就,要参考前后的I帧P帧。
假设我的 GOP_SIZE = 5,就相当于每隔 5 个 P 帧(这里假设没有 B 帧)插入一个 I 帧,这种场景下。编码模块只能适用在那种切换不频繁的场景,如一个画面大部分是运动场景,或者大部分是静止,它不能很好处理那种场景切换很好的场景。
2.2. 智能编码 P 模式:
这种模式下,分成两种 I 帧。一种是普通 I 帧,另外一种是虚拟 I 帧(也称之为 SMARTP 模式)。
普通 I 帧主要是检测画面的静止区域,当检测到静止区域的时候,编码器将会利用长参考帧的相关性,大幅度降低码率,并且尽量防止了静止画面的呼吸效应。 假设码率是5M,会把码率拉低到1M。因为禁止的画面远远低于远动画面,因为运动画面的细节比较多。所以要加大码率。上图就是长参考的I帧。
这里的P帧是短的参考帧,参考上一帧,作用就是在这里检查是否有运动画面产生。所以运动画面是有这些普通P帧检测的,如果有运动画面就插入 SMARTP帧
SMARTP并且把码率从5M提高到和合适的码率,检查运动画面
而在运动区域,利用短期参考帧(P帧)进行运动估计,并插入虚拟 I 帧,SMARTP直接参考I帧,就是长参考。这样可最大拉长 I 帧间隔让其提高码率并最大限度提高画面质量。(注意:SMARTP 和虚拟 I 帧是同样的意思)。
但是这样的也有一个问题,就是消耗cpu的能力,因为算力比较高,看实际选择。
三. RV1126 中的 GOP 模式的设置
RK_S32 RK_MPI_VENC_SetGopMode(VENC_CHN VeChn, VENC_GOP_ATTR_S GopMode);
- 第一个传参数:编码通道号
- 第二个传参数:VENC_GOP_ATTR_S 的结构体
- 返回值:0 成功,非 0 失败
VENC_GOP_ATTR_S 数据结构,作用是定义编码器 GOP 属性结构体。
- enGopMode:编码 Gop 类型填SMARTP和NORMALP多
- u32GopSize:编码 Gop 大小
- s32IPQpDelta:I 帧相对 P 帧对 QP 差值。比如这个花,消除马赛克和用于调节呼吸效应,默认值 6。6 代表的是打开纹理级别的编码,比如窗帘上面的一些花纹,如果你不打开这个可能是编码不出来的;当关闭的时候为 2,
- u32BgInterval:长期参考帧的间隔,若选中 SMARTP 则填这个值,否则不填。
- s32ViQpDelta:虚拟 I 帧相对于普通 P 帧的 QP 差值。用于调节呼吸效应,默认值 6。6 代表的是打开纹理级别的编码;当关闭的时候为 2
编码普通模式下参数的配置:
VENC_GOP_ATTR_S venc_gop_attr;
venc_gop_attr.enGopMode = VENC_GOPMODE_NORMALP; //不要虚拟帧,就是普通帧
venc_gop_attr.s32ViQpDelta = 0; //没有虚拟帧这些都不要
venc_gop_attr.s32IPQpDelta = 6; //这个可以减少调节呼吸效应,代表的是打开纹理级别的编码;
venc_gop_attr.u32BgInterval = 0; //没有虚拟I帧,所以不填
venc_gop_attr.u32GopSize = 30; //大小30
ret = RK_MPI_VENC_SetGopMode(venc_id, &venc_gop_attr);
注意:在普通模式下,u32BgInterval = 0,u32GopSize 是我们配置的 GOPSIZE 的值
智能 P 帧参数的配置:
VENC_GOP_ATTR_S venc_gop_attr;
venc_gop_attr.enGopMode = VENC_GOPMODE_SMARTP; //选择虚拟I帧
venc_gop_attr.s32ViQpDelta = 6; //用于调节呼吸效应和纹理级别的编码
venc_gop_attr.s32IPQpDelta = 6; //用于调节呼吸效应和纹理级别的编码 基本一样
venc_gop_attr.u32BgInterval = 180; //一般是Gop_size的6~10倍
venc_gop_attr.u32GopSize = 30; //大小30
ret = RK_MPI_VENC_SetGopMode(venc_id, &venc_gop_attr);
注意:在 SMARTP 模式下,有两个重要的参数配置 u32BgInterval 指的是长参考帧,一般都是 GOP_SIZE 的整数倍。u32GopSize 就是 GOPSIZE。u32BgInterval 具体填多少,要根据经验值进行判断,一般是 6-10 倍。
编码: 这里的编码用普通模式,因为我这里没有频繁切换的场景,改变不同的gop_size,看看画质
#include <stdio.h>
#include "rkmedia_public.h"
void *collect_venc_thread(void *args)
{
pthread_detach(pthread_self());
MEDIA_BUFFER mb;
FILE *h264_file = fopen("./test_output_smart_gop.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;
//isp的配置
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(); //初始化rkmedia
//VI模块
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模块
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 = 5;
venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate = u32Width * u32Height * 3;
// 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;
}
//配置gop模式
VENC_GOP_ATTR_S venc_gop_attr;
venc_gop_attr.enGopMode = VENC_GOPMODE_NORMALP; //普通gop模式
venc_gop_attr.u32BgInterval = 0;
venc_gop_attr.u32GopSize = 5;//5 60 这里大家可以试试一下,分别用60和50,看看效果
venc_gop_attr.s32IPQpDelta = 6;
venc_gop_attr.s32ViQpDelta = 0;
ret = RK_MPI_VENC_SetGopMode(0, &venc_gop_attr);
if (ret != 0)
{
printf("Gop Mode Success.....\n");
return -1;
}
else
{
printf("Gop Mode Failed.....\n");
}
//绑定vi和venc
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;
}
效果演示后面不回来,开发板坏了,要寄回修了,没办法做实验
gop调节效果 < QP调节效果的,因为qp调节直接把编码器的一些细致弄出来。所以效果没有qp那么好,如果在码率不够的情况下也是经常可以用到的。以后得用那一种做开发,应该根据情况。