一. 概述
1.1 来源及其作用
为什么要使用码率控制?这个问题是由现实产生的。在传输压缩编码视频的时候,必须要考虑两个问题:视频质量和传输带宽,如果带宽大,肯定要尽可能传输质量更好的视频;如果带宽低,则需要提高压缩比,减小码流;如果带宽动态变化,那么码流也需要动态改变来适应带宽,总之,码流与带宽紧密相关。与此同时,我们希望尽可能的得到高清晰的还原图像,所以在能接受的视频质量范围内对视频尽可能的压缩来提高带宽利用率是码率控制的根本。
码率控制的来源知道以后,它的作用也就很明显,就是提高带宽利用率,在保持视频还原清晰度的情形下,尽可能的节省带宽。视频编码(有损)的目标是尽可能多的节省比特(码率)的同时尽量保持视频质量,码率控制是平衡码率和质量的重要工具。
1.2 面临的问题
码率控制是个说起来简单做起来的事情。之前已经总结过,可以从两个方面来理解码率控制,一是从内容的率失真优化角度考虑量化与码率关系,二是从信道传输和转移概率的角度来分析码率和编码模式的关系。这两个方向都有同样的一个问题,与“先有鸡还是先有蛋”类似。以QP和R的关系为例,为了得到可控的码流,我们需要提前控制QP,但是QP又依赖码流来计算给出,所以这个先后问题变成一个死循环,需要找到一个突破点。
1.3 主要发展
为了解决先有鸡还是先有蛋的问题,我们找到一个突破口,既然理论的方法被证明是很难走通,那么可以选择走数值模拟的方法,通过一系列的实验,得到一系列的经验公式,尤其是选取一个合适的初始值,进而破解鸡还是蛋的问题。在选择实验模拟这条路之后,突然有了很多的思路可以去做,首先是和理论结合最紧密的部分:概率分布。通过计算不同概率分布下的率失真函数,得到一系列经验公式和参考模型,在利用DCT变化对亮度和色度系数进行处理后,最优模拟是拉普拉斯分布以及一部分高斯分布。通过计算可以得到常用的6中R-D模型,而这些基础模型又被进一步演化成不同的码率控制算法。
1.4 码率控制主要步骤
最流行的一种方式是:以帧级码率控制为例,首先确定剩余码流,除以帧率求得当前平均每帧可分配的码率,然后通过一个R-Q模型,求得该帧量化参数Q值,再进一步更新剩余码流以便继续进行下去。
上述过程便是最简单的码率控制算法,但是却是存在着很多很多问题:如果剩余码率如果全部平均分配,那么这种分配方式太过粗暴且效果不会太好,考虑到某些细节性的视频帧需要更高的码流来满足清晰度,所以每一帧分配的码流应该不能相同;参数Q的选取也不能直接通过码流获取,还需要考虑参考帧的情况,怎么参考也是问题;还有跳帧的问题,如果某几帧使用的码流较大,后面已经没有足够的码流进行分配,那么则需要跳帧,如何确定是否跳帧也是问题;还有最关键的R-Q模型,如何确定等等等。
以上的每个问题都制约着码率控制算法的实现和效率。一个一个来看,首先考虑R-Q模型,前面已经说过,理论上得到的码率控制算法并不现实,所以这里也可以使用数值模拟的方法来抽象出R-Q的模型,使用二次模型来近似模拟,然后关于如何使用参考帧,则可以使用一个概念“复杂度”,简单的可以使用梯度作为复杂度,并用梯度的倒数引入到R-Q的二次模型中来解决参考问题;此时对于R的估计已经有了一个不同,即不能平均分配,同时也为了解决跳帧问题,可以引入一个“缓冲区”模型,用来调节码率分配和跳帧的判断,另外在确定参数QP时,需要对QP做一个限制,保证其在确定的范围内,比如对于H264来说QP应该在[0,51]区间内。
1.5 可变码率 VS 固定码率
很多人可能更熟悉音频编码器,尤其是那些经历了MP3年代的人。不过从CD的发展史来说,最开始使用固定码率(Constant Bitrate,CBR)编码,后来发展出了可变码率(Variable Bitrate,VBR)。VBR可以确保在给定限制下使用最少的比特情况下保持最高质量。简单说,VBR可以使编码器在难编码的地方花费更多比特,在编码简单的地方花费更少比特。对编码来说“难编码”和“容易编码”代表什么呢?通常有大量运动的视频需要更多比特,空间细节丰富和纹理复杂的视频也较难编码。
编码场景有哪些?
选择哪种码率控制模式往往取决于你的应用场景。通常有以下几种常见场景:
- 存档:压缩一个文件存到硬盘或网盘上。这时你希望文件编码后质量尽可能好同时码率尽可能低,但是你不关心压缩后文件的具体大小。
- 流媒体:你想要通过网络传输一个文件。这是你要确保文件码率不超过网络带宽,或者你需要在不同带宽下提供不同码率的文件。(例如,在网上看视频网络不好时将视频从高清切换到低清)。
- 直播流:和2类似,但是你需要尽快编码(实时),并且直播时你无法提前预知视频内容。
- 面向设备的编码:例如你想向DVD或蓝光碟上存放文件,你想使文件编码后达到特定大小(正好占满碟片空间)。
了解使用场景可以帮助你选择码率控制模式。
二. 码率控制的模式
下面介绍不同的码率控制模式,这些模式基于ffmpeg中的x264,x265和libvpx编码器。你可以在ffmpeg文档找到详细参数介绍。
注意:编码器默认不会“填塞”比特。意味着,当编码简单的帧时,实际使用比特可能低于设定的比特,这时编码器不会浪费比特强行达到设定比特。
2.1 恒定质量(Constant QP, CQP)
量化参数(Quantization Parameter,QP)控制着压缩大小。QP越大压缩率越高同时质量越低,QP越小压缩率越低同时质量越高。在H.264和H.265中,QP的范围是0-51间的整数。你可以很容易的在x264和x265中设置固定QP来编码。注意:libvpx没有固定QP模式。
ffmpeg -i <input> -c:v libx264 -qp 23 <output> ffmpeg -i <input> -c:v libx265 -x265-params qp=23 <output>
代码设置的话,可以通过调用av_opt_set_int() 函数去设置:
av_opt_set_int(c->priv_data, "qp", 10, 0);
其中的c 就是 AVCodecContext; 注意qp值是个数字,所以要调用 av_opt_set_ini() , 如果要设置的参数是字符串的话,则需要调用: av_opt_set() ;
可以参考这个教程了解更多QP的作用原理。事实上,QP只能保证恒定质量的输出,不能控制比特率。根据官方文档,x264默认的qp值是23, 效果较好的值是18; x265的默认值是28, 效果较好的值是25;原则上qp值只要设置得足够低,则视频质量就会很好,所占用的空间就会增大。除非你明确的知道你想要做什么,否则不要使用这个模式。采用CQP模式会导致根据场景复杂度不同比特率波动很大,你无法控制实际比特率。
好处:视频编码研究。
坏处:几乎其他所有应用。
2.2 平均比特率(Average Bitrate,ABR)
给定编码器一个目标码率,编码器计算如何达到这个码率:
ffmpeg -i <input> -c:v libx264 -b:v 1M <output> ffmpeg -i <input> -c:v libx265 -b:v 1M <output> ffmpeg -i <input> -c:v libvpx-vp9 -b:v 1M <output>
代码设置的话可以通过设置编码器上下文参数的方式去设置:
c->bit_rate = 400000;
其中的c就是AVCodecContext;
避免使用这个模式!
x264的主要开发者之一说你应该永远不要使用它。为什么?因为编码器不知道后面还未编码的内容,所以它不得不猜测如何达到给定码率。这意味着码率要一直变化,尤其是在开始时。对于 HAS-type流,这会导致在短时间内质量巨大波动。
ABR不是一种恒定码率模式而是可变码率模式。
好处:快速编码。
坏处:几乎其他所有应用。
2.3 恒定比特率(Constant Bitrate,CBR)
通过设置nal-hrd可以使编码器强制保持在特定码率。
ffmpeg -i <input> -c:v libx264 -x264-params "nal-hrd=cbr:force-cfr=1" -b:v 1M -minrate 1M -maxrate 1M -bufsize 2M <output>
在FFmpeg中可能通过下列方式进行代码设置:
av_opt_set(c->priv_data, "nal-hrd", "cbr", 0); int rate = 400000; c->rc_min_rate = rate; // 最小比特率 c->rc_max_rate = rate; // 最大比特率 c->rc_buffer_size = rate * 2; // 设置缓冲大小, 一般设定为比特率的2倍 c->bit_rate = rate;
输出文件必须是MPEG-2 TS文件,因为mp4不支持NAL填充。注意CBR这种模式对于简单的视频会浪费带宽,但是它会保证整个流的码率一致。为什么说它可能会浪费带宽呢?因为CBR方式它会尽量确保每帧的比特率都是类似的,但是在视频流当中,有些画面可能根本都没有变化,这样如果也采用的相同的比特率,明显就是浪费带宽了,另外,对于画面变化很小的,采用恒定比特率画面自然会很清晰,但是对于画面变化很大的,还是采用这个比特率画面可能就比较模糊了。
你可以在这里找到更多用例。在某些应用中使用这种模式是有意义的,但是你可能希望在可能的时候码率更低。
对于VP9使用CBR的命令如下:
ffmpeg -i <input> -c:v libvpx-vp9 -b:v 1M -maxrate 1M -minrate 1M <output>
好处:保持恒定码率;视频流(例如:Twitch)。
坏处:文档存储;高效使用带宽的场景。
2.4 2-Pass Average Bitrate (2-Pass ABR)
如果允许编码器两遍(或更多)编码那么它就可以预先估计未来还未编码的内容。它可以在第一遍编码是计算编码代价,然后在第二遍编码是更高效的利用比特。这种模式使得在特定码率下输出的质量最好。
对于x264:
ffmpeg -i <input> -c:v libx264 -b:v 1M -pass 1 -f null /dev/null ffmpeg -i <input> -c:v libx264 -b:v 1M -pass 2 <output>.mp4
对于x265:
ffmpeg -i <input> -c:v libx265 -b:v 1M -x265-params pass=1 -f null /dev/null ffmpeg -i <input> -c:v libx265 -b:v 1M -x265-params pass=2 <output>.mp4
对于VP9:
ffmpeg -i <input> -c:v libvpx-vp9 -b:v 1M -pass 1 -f null /dev/null ffmpeg -i <input> -c:v libvpx-vp9 -b:v 1M -pass 2 <output>.webm
这是对流进行编码的最简单的方法。但有两点注意:你不知道最终结果的质量如何,所以你必须进行多次实验以确保给定的码率足够编码复杂内容。另一点是这种模式码率可能出现局部峰值,意味着发送能力可能超过客户端的接收能力。对于码率的选择,你可以参考YouTube的推荐设置,但是注意这些都是为了让你上传高质量的照片而优化的,实际中你可以选择更低的码率。
好处:达到特定码率;面向设备的编码。
坏处:如果你需要快速编码(例如,直播流)。
2.5 恒定速率因子 (Constant Rate Factor, CRF)
CRF可以用于保持整个视频流质量恒定。
ffmpeg -i <input> -c:v libx264 -crf 23 <output> ffmpeg -i <input> -c:v libx265 -crf 28 <output> ffmpeg -i <input> -c:v libvpx-vp9 -crf 30 -b:v 0 <output>
那它与CQP有什么区别呢?我们知道,CQP的话,它的质量控制是在做视频量化的时候把它除以一个值,通过缩减它的精度来达到缩放的一个效果,恒定质量(CQP) 也就是所有的视频画面都是除以一个固定的值,就是每帧的压缩率采用了同样的大小,但是画面是有区别的,有些画面运动多,产生的运动矢量就多,有些画面运动少,产生的运动矢量就少,如果我们每帧画面都除以一个固定的QP值,那肯定是不合理的,此时我们可以根据画面的运动情况动态调整画面的QP值,只要不影响观看者主观的观看体验就行,CRF做是就是这样一个事情,通过动态调整每一帧的qp值,仍然能达到较好的观看效果。
可以通过下列代码进行设置:
av_opt_set_int(c->priv_data, "crf", 23, 0);
在H.264和H.265中,CRF取值为0~51间的整数(和CQP类似)。x264默认值是23,x265默认值是28。CRF增减6会导致码率减半或加倍。对于VP9,CRF取值范围0到63,推荐值为15-35。
这种模式缺点是无法确定最终文件的码率和码率波动。
好处:文档存储;达到尽可能好的质量。
坏处:流媒体;需要特定码率(或文件大小)。
2.6 约束编码VBV(Constrained Encoding, Video Buffering Verifier)
对于VBV可以确保码率不超过某个最大值。这对于流媒体非常有用,你现在可以确定你不会发送比你承诺的更多的比特。VBV可以和2-pass VBR(在两遍编码中都使用)或CRF一起使用。
ffmpeg -i <input> -c:v libx264 -crf 23 -maxrate 1M -bufsize 2M <output> ffmpeg -i <input> -c:v libx265 -crf 28 -x265-params vbv-maxrate=1000:vbv-bufsize=2000 <output>
可以通过代码进行如下设置(当与CRF组合使用时):
av_opt_set_int(c->priv_data, "crf", 23, 0); int br = 400000; c->rc_max_rate = br; // 设置最大比特率 c->rc_buffer_size = br * 2;
VP9有类似的模式,不叫VBV,但是原理一样:
ffmpeg -i <input> -c:v libvpx-vp9 -crf 30 -b:v 2M <output>
如果你在直播流中应用VBV,且你想加速编码过程,你可以使用-tune zerolatency和-preset ultrafast选项。这会牺牲一部分质量来加速编码, 具体可参考:调节x264编码器, 控制编码速度和质量。
在受约束的ABR-VBV中使用这种模式:
ffmpeg -i <input> -c:v libx264 -b:v 1M -maxrate 1M -bufsize 2M -pass 1 -f null /dev/null ffmpeg -i <input> -c:v libx264 -b:v 1M -maxrate 1M -bufsize 2M -pass 2 <output>
对x265:
ffmpeg -i <input> -c:v libx265 -b:v 1M -x265-params pass=1:vbv-maxrate=1000:vbv-bufsize=2000 -f null /dev/null ffmpeg -i <input> -c:v libx265 -b:v 1M -x265-params pass=2:vbv-maxrate=1000:vbv-bufsize=2000 <output>
对VP9:
ffmpeg -i <input> -c:v libvpx-vp9 -b:v 1M -maxrate 1M -bufsize 2M -pass 1 -f null /dev/null ffmpeg -i <input> -c:v libvpx-vp9 -b:v 1M -maxrate 1M -bufsize 2M -pass 2 <output>
如何设置bufsize?这取决于你期望码率的波动情况。一个好的设置方法是将bufsize设为maximum rate的两倍。如果客户端缓存比较小,设置bufsize等于maxrate。如果你想限制码流的码率,设置bufsize为maximum rate的一半或更小。
好处:带宽受限的流媒体;直播流(使用CRF,1-pass);VoD流。
坏处:文档存储。
三. 对比实验
下面是不同码率控制算法的比较。使用Big Buck Bunny和Tears of Steel序列,每个序列截取三段(每段30秒)。使用libx264编码器,除了码率控制模式不同外,其他都是默认设置。设置了不同的目标码率(750,1500,3000,7500kbit/s)和最大码率(针对VBV)和QP/CRF值(17,23,29,35)。
注意这个实验并不充分,你可以尝试更多的序列和使用不同编码器。
下图是使用不同码率控制模式的结果。左边是3000kbit/s的结果,右边是7500kbit/s的结果。另两种结果的图像差不多,这里不再展示。每条线代表不同模式下码流的码率变化情况。
从BBB1看出,ABR(蓝绿色线)和ABR+VBV(紫色线)开始时错估了视频复杂度,实际是BBB视频的开始部分比较平滑,运动比较小只需要很少的比特就可以保证质量。2-pass模式在开始时正确估计了复杂度,起始使用了低码率节省了带宽。视频的后1/3部分,空间细节丰富使得2-pass模式消耗了大量比特,超过了起始节省的比特。
BBB2视频里,不同模式实际上比预期要好。但是2-pass的波动还是比其他模式更多。
下面是CQP和CRF的实验情况,这里只展示了CRF/CQP为17和23的结果。CRF的效果更好。
下面是CRF+VBV在不同码率下的结果。为CRF选择合适的目标码率和最大码率通常需要多次尝试,完全取决于视频源。
翻译自Understanding Rate Control Modes (x264, x265, vpx)
参考链接: