之前多多少少接触过一些编解码参数, 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 接口定义, 对应关系如下图所示:
此处借用雷神的一张图说明:(https://blog.csdn.net/leixiaohua1020/article/details/45960409)
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 源代码简单分析: 编码器主干部分 - 1_雷霄骅 (leixiaohua1020) 的专栏 - CSDN 博客)
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 视频流, 这个过程可以参考雷神的文章, 解析非常好, https://blog.csdn.net/leixiao...
这边关心的是 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 模式, 以及对影响编码的部分参数, 整个过程下一篇文章我们再进行分析和跟踪.
以上是个人的一些看法, 可能有不正确的地方, 欢迎大家一起讨论学习.
来源: https://segmentfault.com/a/1190000040807922