熵编码是一种无损压缩技术。它的核心任务是将编码器产生的所有语法元素,如预测模式、运动矢量差、量化后的变换系数等,用尽可能少的比特来表示。

其基本原理是信息熵理论:为出现概率高的符号分配更短的码字,为出现概率低的符号分配更长的码字。

H.264 主要提供了两种熵编码方案:

  1. CAVLC (Context-Adaptive Variable-Length Coding):基于上下文自适应的可变长度编码,复杂度较低。
  2. CABAC (Context-Adaptive Binary Arithmetic Coding):基于上下文自适应的二进制算术编码。复杂度更高,但压缩效率也更高。

熵编码的基础

经过量化后,变换系数矩阵呈现出几个非常有利于压缩的统计特性:

  1. 大量的零:大部分高频系数都变成了 0。
  2. 拖尾的 1:非零系数中,有大量的 +1-1
  3. 幅值集中于低值:非零系数的绝对值通常很小。
  4. 非零系数集中在左上角:经过 Zig-zag 扫描后,非零系数大多出现在序列的开头。

熵编码的核心思想就是利用这些统计特性,为出现概率高的符号分配短码字,为出现概率低的符号分配长码字,从而达到压缩数据的目的。

+-------------------+     +---------------------+     +-----------------+
| Quantized Coeffs  | ->  | Zig-zag Scanning    | ->  | Entropy Coding  | -> Bitstream
| (many zeros)      |     | (Group non-zeros)   |     | (CAVLC or CABAC)|
+-------------------+     +---------------------+     +-----------------+

CAVLC

CAVLC 是一种专门为量化后的 4x4 或 2x2 块系数设计的编码方案。它不是一个简单的 VLC(比如Huffman 编码),而是巧妙地结合了多个元素的上下文信息来进行自适应编码。

CAVLC 的编码过程可以分解为以下几个步骤:

  1. 编码非零系数的数量和拖尾 ‘1’ 的数量
    • 首先,从 Zig-zag 扫描后的序列末尾开始反向读取
    • TrailingOnes: 统计末尾有多少个连续的 +1-1。例如,序列 ... 3, -1, 1, -1, 0, 0,则 TrailingOnes 为 3。
    • TotalCoeffs: 统计整个块中非零系数的总数
    • CAVLC使用一个二维查找表 coeff_token,根据 TotalCoeffsTrailingOnes 的组合来查找一个特定的码字。这个 coeff_token 码字非常高效,用一个很短的比特串就同时传递了两个关键信息。之所以这样做,是因为统计发现 TotalCoeffsTrailingOnes 的值之间有很强的相关性
  2. 编码每个拖尾 ‘1’ 的符号
    • 对于每个拖尾 ‘1’,用 1 比特来表示其符号(0 代表 +, 1 代表 -)。
  3. 编码除拖尾 ‘1’ 外的其他非零系数的幅值
    • 从序列末尾(最后一个拖尾 ‘1’ 之后)向前编码。
    • 每个系数的幅值使用一种称为 Golomb-Rice 的编码方式。这种编码方式对幅值小、概率分布有偏的数值特别高效。
  4. 编码最后一个非零系数前 ‘0’ 的个数 (TotalZeros)
    • 统计从序列开头到最后一个非零系数之间所有 ‘0’ 的总数。
    • 使用专门的 VLC 表,根据 TotalCoeffs 来查找对应的码字。
  5. 编码每个非零系数前连续 ‘0’ 的个数 (Runs)
    • 从最后一个非零系数开始,反向编码每个非零系数前面有多少个连续的 ‘0’(Run)。
    • 编码 Run 的 VLC 表是自适应的:它会根据剩下还需要编码的 ‘0’ 的数量来动态选择使用哪个 VLC 表。
    • 例如,如果剩下的 ‘0’ 很少了,那么一个长的 Run 出现的概率就很低,编码器就会选择一个对短 Run 更有利的码表。这就是 CAVLC 中“上下文自适应”的主要体现之一。

CAVLC 总结:

  • 优点:实现相对简单,不需要复杂的乘法运算,计算速度快。
  • 缺点
    • 压缩效率不如 CABAC。
    • 它的自适应性是基于一些预定义的规则和查找表,不够灵活。
    • 只能用于残差系数编码,其他语法元素(如 MVD, MB type)需要使用别的VLC 编码。

CABAC

CABAC 是 H.264 中最高效的熵编码方法,它的压缩性能之所以优越,源于其三个核心步骤:

  1. 二值化
    • 算术编码器本身只能处理二进制符号(0 或 1)。因此,在编码任何语法元素之前,必须先将其转换成一个二进制串。这个过程称为二值化。
    • H.264 为不同的语法元素定义了不同的二值化方案,最常用的是一元码 (Unary)指数哥伦布码 (Exp-Golomb) 的组合。
    • 例如,一个正整数 x 可以用 x-1 个 ‘1’ 后面跟一个 ‘0’ 来表示(一元码)。5 -> 11110
  2. 上下文建模
    • 这是 CABAC上下文自适应的核心。对于二值化后的比特串中的每一位 **,CABAC 都会根据已经编码过的、与之相关的语法元素信息,选择一个概率模型 。
    • 这个概率模型本质上是预测当前这位是 ‘0’ 还是 ‘1’ 的概率。例如,在编码一个块的 significant_coeff_flag(表示当前位置的系数是否为零)时,CABAC 会查看其上方左侧相邻块的对应系数是否为零。如果它们都不为零,那么当前系数也不为零的概率就很高。
    • H.264 标准为不同的语法元素和不同的 bin 精心设计了数百个上下文模型。每个模型内部维护着当前 ‘1’ 和 ‘0’ 出现的概率状态。在编码完一个 bin 后,对应的模型会根据实际编码的值(是 ‘0’ 还是 ‘1’)来更新自己的概率状态。这样,概率模型就能动态地适应视频内容的局部统计特性。
  3. 二进制算术编码
    • 这是最后的编码引擎。与 VLC 不同,算术编码不是将一个符号映射到一个固定的码字。
    • 它将整个符号序列映射到 [0, 1) 区间内的一个浮点数
    • 工作原理
      • 开始时,编码范围是 [0, 1)
      • 编码第一个 bin 时,根据上下文模型提供的概率 p_0 和 p_1(p_0+p_1=1),将当前范围分割成两部分。例如,[0, p_0) 对应 ‘0’,[p_0, 1) 对应 ‘1’。
      • 如果实际要编码的 bin 是 ‘0’,就选择 [0, p_0) 作为新的编码范围;如果是 ‘1’,就选择 [p_0, 1)
      • 对后续的每一个 bin,都重复这个过程:根据其概率模型,在当前的编码范围内继续进行分割和选择。
      • 编码完所有 bin 后,最终会得到一个非常小的范围。在这个范围内任选一个小数(用二进制表示),这个二进制小数就是整个序列的编码结果。

ASCII 图解算术编码过程:

Initial Range: [0.0, 1.0)
+-------------------------------------------------------------+

Bin 1 ('1'), P(1)=0.4, P(0)=0.6
          [0.0, 0.6) for '0' | [0.6, 1.0) for '1'
+----------------------------+--------------------------------+
                             ^
                             New Range: [0.6, 1.0)

Bin 2 ('0'), in new range, P(1)=0.5, P(0)=0.5
          [0.6, 0.8) for '0' | [0.8, 1.0) for '1'
+----------------------------+------------------+--------------+
                             ^
                             New Range: [0.6, 0.8)

... and so on. The range gets progressively smaller.

CABAC 总结:

  • 优点
    • 极高的压缩效率:概率估计非常精准,并且算术编码能以接近信息熵理论极限的效率进行编码。
    • 统一的框架:可以对所有语法元素(残差、MVD、宏块类型等)进行统一编码。
  • 缺点
    • 计算复杂度高:涉及到大量的状态更新和条件判断。尤其是在解码端,每个 bin 的解码都依赖于前一个 bin 的解码结果,这使得并行处理变得非常困难。
    • 需要浮点(或模拟浮点)运算:虽然 H.264 的算术编码引擎被设计为可以用整数运算来模拟,但其逻辑仍然比 CAVLC 复杂得多。