x264 的 ABR(平均比特率)码率控制算法是一个基于反馈的复杂系统,旨在实现整个视频序列的目标平均比特率。该算法通过复杂度预测、量化器调整和持续反馈校正相结合的方式进行。

实现

该算法维护了比特率控制的几个关键变量:

  • cplxr_sum:累加bits×qscale/rceq之和,表示复杂度调整后的比特消耗
  • wanted_bits_window:目标比特率乘以时间窗口,表示所需的比特分配
  • expected_bits_sum:速率因子和溢出调整后完成帧的预期比特总和
  • cbr_decay:ABR 速率控制中CBR风格操作的衰减因子。

核心实现机制

ABR码控的核心是rate_estimate_qscale()函数中的单通道ABR算法:

1. 复杂度估算与目标比特分配

  • 使用rcc->wanted_bits_window / rcc->cplxr_sum计算目标量化尺度
  • wanted_bits_window表示目标比特率窗口:rc->wanted_bits_window = 1.0 * rc->bitrate / rc->fps
  • cplxr_sum累积复杂度比率,用于预测帧大小

2. 实时比特率调整 当实际编码比特数偏离目标时,系统计算溢出因子进行补偿:

  • wanted_bits = time_done * rcc->bitrate 计算到当前时间应消耗的目标比特数
  • overflow = (predicted_bits - wanted_bits) / abr_buffer 计算偏差比例
  • 通过q *= overflow调整量化参数,实现比特率纠偏

3. 编码后统计更新 每帧编码完成后更新统计信息:

  • 更新cplxr_sum:累积bits * qscale / rceq
  • 更新wanted_bits_window:添加当前帧的目标比特数
  • 应用cbr_decay衰减因子,使系统更关注近期帧的统计

VBV缓冲区约束

ABR还需要满足VBV(Video Buffering Verifier)约束:

  • 限制帧大小不超过frame_size_maximum
  • 确保解码器缓冲区不会上溢或下溢
  • 通过调整QP使帧大小符合VBV约束

初始化配置

ABR模式在初始化时设置关键参数:

  • rc->b_abr = 1标识ABR模式
  • 初始化复杂度估算cplxr_sum
  • 设置目标比特率窗口

我们先把这两个参数的定义搞清楚:

  • wanted_bits (期望比特数): 这个参数代表了到当前帧为止,编码器期望花费掉的总比特数。它是一个理想化的、线性增长的值。可以想象成一个水池,我们希望以一个恒定的速率往里注水。
    • 计算方式: 它的计算非常简单直观。wanted_bits = (已编码的帧数 / 总帧数) * 总目标比特数。在代码层面,它通常是累加的:每一帧的期望比特数 = 目标平均码率 / 帧率。所以 wanted_bits 会随着编码帧数的增加而稳定增长。
  • cplxr_sum (Complexity Sum / 累计复杂度): 这个参数代表了到当前帧为止,所有已编码帧的实际量化结果的复杂程度总和。它并不是直接的比特数,而是一个与比特数高度相关的、衡量“编码难度”的累加值。
    • 计算方式: 每一帧编码完成后,x264 会得到该帧的实际编码比特数和使用的量化参数(QP)。通过一个复杂的数学模型(主要是基于 λ 域的率失真理论),x264 会计算出一个能够代表该帧“复杂度”的值,这个值约等于 实际比特数 * QP。然后将这个值累加到 cplxr_sum 中。你可以简单地理解为,cplxr_sum 约等于 累积的实际比特数 \* 平均QP

2. 两者如何协作来控制码率?

理解了定义,我们来看看它们如何互动。x264 在决定给下一帧分配多少比特(或者说,用多大的 QP)时,会进行一次关键的比较:比较 cplxr_sumwanted_bits 的增长速率

这个比较的目的是回答一个问题:“我们目前是超支了还是节省了?”

  • wanted_bits 代表了我们的“预算”。
  • cplxr_sum 代表了我们的“实际开销”的一种度量。

在每一帧编码之前,x264 会根据 cplxr_sumwanted_bits 的当前值来预测一个合适的QP。这个过程可以简化为以下逻辑:

  1. 计算一个比率(Rate Factor): 编码器会计算一个比率,我们称之为 r。这个 r 正比于 cplxr_sum / wanted_bits

    r∝wanted_bitscplxr_sum

  2. 调整量化参数 (QP): 根据这个比率 r 来调整下一帧的基础 QP。

    • 如果 cplxr_sum > wanted_bits (即 r > 1): 这意味着到目前为止,我们花费的“复杂度”(实际比特)超出了“预算”。编码器判断,之前的码率偏高了。为了把码率降下来,下一帧就必须“省着点花”。怎么省?提高 QP,让量化更粗糙,从而减少编码产生的比特数。
    • 如果 cplxr_sum < wanted_bits (即 r < 1): 这意味着我们之前“省钱”了,实际花费的比特数低于预算。这说明之前的码率偏低,我们有多余的码率可以分配。为了提升画质,编码器会降低 QP,让量化更精细,从而使用更多的比特来编码下一帧。
    • 如果 cplxr_sum ≈ wanted_bits (即 r ≈ 1): 说明目前为止,实际码率和目标码率基本一致,一切尽在掌握。编码器会维持一个相对稳定的 QP。
  3. 编码与更新: 使用上一步计算出的 QP 对当前帧进行编码。编码完成后,得到该帧的实际比特数,并据此更新 cplxr_sum 的值。同时,wanted_bits 也根据帧率和目标码率进行线性增长。然后,编码器进入下一帧的循环,重复上述过程。

3. 为什么这个机制能保证码率?

这个机制是一个经典的负反馈调节系统,类似于自动巡航或者恒温空调。

  • 目标: 保持 cplxr_sumwanted_bits 的增长速率长期一致。
  • 测量: 通过比较两者的当前值来判断偏差。
  • 调节: 通过调整 QP(即调整编码的“粗糙度”)来修正下一帧的比特消耗。
  • 反馈: 修正后的结果会立刻体现在新的 cplxr_sum 值中,从而影响再下一帧的决策。

这个系统的巧妙之处在于:

  • 长期稳定: 任何短期的码率波动(比如一个突然复杂的场景导致比特数暴增)都会被这个系统在接下来的帧中自动修正。如果一帧超支了,cplxr_sum 就会大幅增加,导致接下来的几帧被迫使用更高的QP(更低的码率)来“还债”,直到 cplxr_sum 重新追上 wanted_bits 的步伐。
  • 允许短期波动: 它并不要求每一帧的码率都严格等于平均码率。相反,它允许简单的场景(如静止画面)用很少的比特(QP较低,但因为画面简单,比特数依然很少),而将节省下来的比特用于编码复杂的场景(如爆炸、追逐),这时QP会适当降低以保证画质,即使瞬时码率超出平均值。
  • 最终收敛: 只要视频的总时长足够,这种不断的“借贷”和“偿还”机制,可以确保在编码结束时,cplxr_sumwanted_bits 的值非常接近。由于 cplxr_sum 与实际总比特数是强相关的,这就意味着最终的实际总比特数会非常接近目标总比特数,从而实现了对平均码率的精确控制。

总结

wanted_bits 像是一条笔直的、代表着理想预算的斜线,而 cplxr_sum 则是代表实际开销的、围绕着这条斜线上下波动的曲线。x264 ABR 码控的核心任务,就是通过不断调整下一帧的 QP,来拉动 cplxr_sum 这条曲线,使其始终紧紧地跟随着 wanted_bits 这条直线。

通过这个动态的、带有反馈的调节循环,x264 能够在保证复杂场景有足够码率分配的同时,从宏观上将整个视频的平均码率精确地限制在你所设定的目标值上。