Bootstrap

音视频编解码--编码参数CRF

之前多多少少接触过一些编解码参数,CRF参数也用过,但是最近在和朋友们聊天时,说到使用FFMPEG过程中碰到CRF参数,以及具体作用流程,这个之前一直没有跟踪过,也没有详细记录过,所以吊起了自己的好奇心,于是决定搞清楚一下,便开始了这次CRF的神奇之旅。CRF简介

恒定速率因子(CRF,Constant Rate Factor)是一种编码模式,可以向上或向下调整文件数据速率以达到选定的质量级别,而不是特定的数据速率。

如果要保持最佳质量,而又不怎么担心文件大小,这时候就可以使用CRF速率控制模式。 这是大多数情况下建议的速率控制模式。当输出文件的大小不太重要时,此方法允许编码器尝试为整个文件实现期望目标视频质量的文件输出,即所谓的一次编码便可在预期视频质量下获得最大的视频压缩效率。CRF模式主要原理是在编码过程中通过动态调整每帧视频的QP值,以便可以获得保持所需视频质量水平比特率。

但是CRF缺点是不能告知编码器期望获得特定大小的文件或不超过特定大小或比特率。同时需要注意的是采用CRF时不建议直接用来编码视频以进行流媒体传输。

通常建议一般使用两种速率控制模式:恒定速率因子(CRF)或2-pass ABR。 速率控制决定每个帧将使用多少位。 这将确定文件大小以及质量分配方式。CRF 实操演示

通过FFMPEG二进制文件尝试用参数CRF进行压缩,如下图所示:

FFMPEG采用CRF分别为18、24进行压缩,以及和源文件的比较。

ffmpeg -i test.mp4 -c:v libx264 -crf 18 test18.mp4

实际转码中

转码结束后,会显示具体的编码相关信息,包括ref,crf值,qp量化步长等,以及I帧、P帧、B帧所占比重。还包含了音频相关信息如下图:

用命令ffmpeg -i test.mp4 -c:v libx264 -crf 24 test24.mp4,进行CRF=24的转码,转码结果如下图所示:

转码后分别对三个文件进行参数查看,并形成对比,其结果如下图所示:

上述参数只能大概了解三个视频基本信息,之后通过Elecard eye专业工具查看该变化产生原因的直观图,三个文件码流分析结果:

三个文件对比情况总结如下:

可以看出:CRF参数的使用,I帧数量急剧减少、同时引入B帧;熵编码采用了CABAC方式,这样压缩率就提升很多,文件大小变小。同时随着CRF值变大,P帧和B帧压缩率也变大,文件更小。CRF代码走读

虽然之前走读过FFMPEG代码,但是具体CRF参数的品读还没完全注意到过。为了不是一知半解的明白该问题,还是强迫自己走一遍代码,增强印象,深刻认识,也为关心该参数的小伙伴铺垫一下基础。*•CRF定义

首先在X264中可以看到该值的定义:

typedef struct X264Context {
    AVClass        *class;
    x264_param_t    params;
    ......

    float crf;

    ......
    }

在AVOption具体定义如下:

static const AVOption options[] = {
    { "preset",        "Set the encoding preset (cf. x264 --fullhelp)",   OFFSET(preset),        AV_OPT_TYPE_STRING, { .str = "medium" }, 0, 0, VE},
    { "tune",          "Tune the encoding params (cf. x264 --fullhelp)",  OFFSET(tune),          AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE},
    { "profile",       "Set profile restrictions (cf. x264 --fullhelp) ", OFFSET(profile),       AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE},
......
    {"x264opts", "x264 options", OFFSET(x264opts), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, VE},
    { "crf",           "Select the quality for constant quality mode",    OFFSET(crf),           AV_OPT_TYPE_FLOAT,  {.dbl = -1 }, -1, FLT_MAX, VE },
    { "crf_max",       "In CRF mode, prevents VBV from lowering quality beyond this point.",OFFSET(crf_max), AV_OPT_TYPE_FLOAT, {.dbl = -1 }, -1, FLT_MAX, VE },
......
}

CRF仍然属于Rate control的一中,所以可以看到其RC相关定义如下:

#define X264_RC_CQP                  0
#define X264_RC_CRF                  1
#define X264_RC_ABR                  2

•FFMPEG接口梳理

涉及到FFMPEG代码走读的部分太多了,在此只是简述CRF对应的部分,其他编解码流程大家可以根据网上其他大神的代码走读流程完成即可。此篇文章默认大家有足够基础:X264的编解码入口符合FFMPEG接口定义,对应关系如下图所示:

此处借用雷神的一张图说明:

  • X264_init()

X264_init函数主要作用就是将之前赋值和初始化的option值依次传递到libx264模块中,进行X264参数初始化,以及RC参数赋值。这些值是从AVCodecContext传递过来,以及X264Context的默认值。熟悉FFMPEG的人都了解,AVCodecContext中包含输入命令行中编解码选项值,以及FFMPEG命令中包含的option值,而X264Context包含x264的相关选项,两者结合构成完整的x264编解码选项值。

在X264_init的最后,进行X264Codec的OPEN动作,以及编码全局header的动作。

  • x264_param_default

x264_param_default设置默认参数,包括其他的选项值,在此只关心CRF相关选项。x264_param_default中将CRF默认开启,同时设置CRF选项f_rf_constant置为23,这也是其他很多文章中讲到的默认值23的原因。

同时注意,观察到在x264_param_default默认参数中B帧是再次设置并置位的,而且cabac默认开启。所以如果用FFMPEG bin文件进行转码出来的文件中cabac是默认开启的,这也是工具端查看时会出现CABAC以及增加B帧的根本原因了。

  • x264_encoder_open

在初始化具体参数后,init函数接下来进行x264_encoder_open(相关代码位于encoder\encoder.c)的操作,这时会具体打开到x264中h264相关编码器。

之后在x264_encoder_open中主要用于打开编码器,其中校验、初始化了libx264编码所需要的各种变量,并完成sps、pps、qm初始化。

  • validate_parameters

调用validate_parameters会进行输入参数的校验,防止输入参数异常导致编码失败。此函数中完成CRF相关参数校验、更新和赋值。

其他流程部分可以参考其他大神的文章,再次不再累述。(雷神的解析非常详尽了,敬请膜拜即可

  • x264_ratecontrol_new

x264_encoder_open最后会调用x264_ratecontrol_new完成码率控制相关变量初始化。

x264_ratecontrol_new,主要设置码率控制的核心参数,需要对x264码率控制比较了解才能真正明白,否则会容易看晕。

x264_ratecontrol_new函数中依据传入参数是CRF模式,以及b_stat_read默认值为0即可将b_abr参数的置位为1,同时b_2pass置位为0,也就是说CRF模式在rate_control中按照abr、非2-pass进行处理的。

在x264_ratecontrol_init_reconfigurable函数中会进行VBV参数初始化,以及CRF相关参数base_cplx、rate_factor_constant的更新。

同时x264_ratecontrol_init_reconfigurable中设置被调用时,传入b_init=1的参数,这时CRF置位了VBV模式,为后续的rate_control做了铺垫。

  • X264_frame

X264_frame()用于依据传入packet数据进行一帧视频数据的完整编码。该函数部分定义如下所示。

  • reconfig_encoder

reconfig_encoder主要作用就是将RC相关的参数和AVCodecContext中参数进行比较,如果不一致,则重新配置编码器。比如CRF值初始设置为24,但是命令行中设置为18,这时两个值不一致,则需要按照命令行中值进行赋值并重新配置编码器,以便最终符合用户预期。具体配置大家简单看一下就好,这里不再展开。

  • x264_encoder_encode

x264_encoder_encode是真正编码的开始,在x264_encoder_encode这个函数里面将一帧完整YUV图像编码成H264视频流,这个过程可以参考雷神的文章,解析非常好,

这边关心的是CRF中涉及到的部分内容,在x264_encoder_encode中和码率控制相关的内容主要是一下接口:

x264_thread_sync_ratecontrol():

x264_ratecontrol_zone_init():

x264_ratecontrol_start():开启码率控制,针对每一帧进行码率控制。在x264_ratecontrol_start中会根据码率控制模式的不同,选择不同的qp进行压缩。之前分析可知,CRF是属于abr模式,同时增加了B帧,所以导致每帧图像的qp都是不同的,这样压缩后相同质量的条件下编码后文件大小就不能确定了。

x264_ratecontrol_qp():

码率控制是一个大块内容,设计的算法也比较复杂,该文只关注了如何将crf模式转换到vbv模式,以及对影响编码的部分参数,整个过程下一篇文章我们再进行分析和跟踪。

以上是个人的一些看法,可能有不正确的地方,欢迎大家一起讨论学习。

如果该文章对您有帮忙,欢迎点赞,收藏,转发、关注,在下持续更新音视频相关内容。