一. 什么是 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和vencMPP_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那么好,如果在码率不够的情况下也是经常可以用到的。以后得用那一种做开发,应该根据情况。