3. H.264编码原理(下):变换与量化、熵编码(CAVLC/CABAC)、去块效应滤波

好,我们接着往下聊。上一节我们把预测和帧内帧间搞清楚了,说白了就是“怎么猜像素”。但猜完了总得有误差吧?这个误差怎么处理,才是今天要聊的重头戏。

我个人习惯把编码器的后半段看作一个“压缩漏斗”——变换、量化、熵编码,一步比一步狠。你想想看,预测残差里还藏着大量空间冗余,不收拾它,码流根本压不下去。

3.1 变换与量化:把能量集中起来

预测做完之后,我们拿到的是残差块。这个块里数值有大有小,但有个特点——相邻像素的残差往往差不多。换句话说,能量分散在整个块里。

变换的目的,就是把能量从空间域转到频率域。低频分量集中到左上角,高频分量散到右下角。这样后面量化的时候,一刀切掉高频,人眼还不太敏感。

3.1.1 整数DCT变换

H.264用的是4×4整数DCT,不是传统的浮点DCT。为什么?因为浮点运算在不同平台上结果不一致,解码端和编码端对不上,那就乱套了。

整数DCT的好处是:

  • 无漂移:正反变换完全匹配,不会累积误差
  • 低复杂度:只用加减法和移位,没有乘法
  • 块效应更小:4×4块比8×8块更贴合图像边缘

我记得第一次在ARM平台上移植H.264编码器时,最头疼的就是浮点精度问题。换成整数DCT后,问题迎刃而解。嗯,这里要注意:整数DCT的系数是固定的,查表就能拿到,不用实时算。

核心公式(简化版):

Y = Cf * X * Cf^T
其中 Cf 是整数变换矩阵,X 是4×4残差块

变换后的系数Y,左上角是DC分量,右下角是高频细节。

3.1.2 量化:真正的“有损”环节

量化才是真正丢信息的地方。变换后的系数除以量化步长(QP),再取整。QP越大,丢得越狠,码率越低,画质越差。

H.264的量化有个巧妙设计——它把除法和乘法结合到一起,用查表和移位来实现。52个QP值对应不同的量化步长,QP每增加6,步长翻倍。

QP值 量化步长 典型应用场景
0 0.625 无损/近无损
12 2.5 广播级质量
24 10 高清视频通话
36 40 移动端短视频
51 224 极低码率监控

我曾经踩过的坑:量化矩阵(Scaling Matrix)在H.264里是可选的,但很多编码器默认不用。如果你做的是专业级编码器,建议加上——它能针对不同频率的系数做差异化量化,主观质量能提升一截。

3.2 熵编码:把符号变成比特流

量化之后,我们得到了一堆系数。这些系数里有很多0,尤其是高频区域。怎么把这些0和少数非0系数高效地写成二进制?这就是熵编码的事。

H.264支持两种熵编码方式:CAVLC和CABAC。选哪个,直接影响压缩率和复杂度。

3.2.1 CAVLC:基于上下文的自适应变长编码

CAVLC是H.264的基线方案。它专门为量化后的4×4块设计,利用了三个特点:

  • 非零系数集中在低频区(左上角)
  • 非零系数绝对值大多为1
  • 拖尾系数(±1)的数量有规律

编码过程分五步:

  1. 编码非零系数个数和拖尾系数个数(查表)
  2. 编码每个拖尾系数的符号(0正1负)
  3. 编码剩余非零系数的幅值(Level)
  4. 编码最后一个非零系数前0的个数(TotalZeros)
  5. 编码每个非零系数前的0的个数(RunBefore)

说实话,CAVLC的查表逻辑有点绕。我建议你直接看标准里的Table 9-5到9-7,对着码流走一遍就明白了。

小技巧:CAVLC的码表是固定的,但上下文会自适应——比如相邻块的非零系数个数会影响当前块的码表选择。这就是“上下文自适应”的含义。

3.2.2 CABAC:基于上下文的自适应二进制算术编码

CABAC比CAVLC复杂得多,但压缩率能提升10%~15%。它不再是查表编码,而是用算术编码器把符号压缩到小数比特里。

CABAC的核心三要素:

  • 二进制化:把非二进制语法元素转成二进制串(如指数哥伦布码)
  • 上下文建模:每个bin(二进制位)都有一个概率模型,根据相邻信息动态更新
  • 算术编码:把整个序列压缩成一个区间,区间越小,比特越少

我刚开始接触CABAC时,觉得这东西太玄乎了。后来自己手写了一个简化版算术编码器,才明白它本质上就是“不断缩小区间”的过程。你想想看,概率高的符号占的区间大,需要的比特就少——这不就是信息论嘛。

CAVLC vs CABAC 对比:

特性 CAVLC CABAC
压缩率 基准 高10%~15%
复杂度 低 高(约2倍)
适用Profile Baseline/Extended Main/High
硬件实现 简单 需要流水线优化

3.3 去块效应滤波:让画面更“干净”

块效应是块编码的“原罪”。每个4×4或16×16块独立编码,边界上的像素因为量化误差不一致,就会出现肉眼可见的方块边界。

去块效应滤波(Deblocking Filter)就是在解码后、显示前,对这些边界做平滑处理。H.264的滤波器是自适应的——它根据块类型、QP、边界强度来决定要不要滤波、滤多强。

3.3.1 滤波边界与强度

滤波器只处理4×4块边界。每个边界有一个边界强度(Bs),取值范围0~4:

  • Bs=0:不滤波(比如两个块都用帧内预测且QP很小)
  • Bs=1~3:弱滤波(运动补偿残差边界)
  • Bs=4:强滤波(帧内预测块边界)

滤波时,会判断边界两侧像素的差异是否超过阈值。如果差异太大,说明是真实边缘,不滤;如果差异小,说明是块效应,滤掉。

我曾经踩过的坑:去块效应滤波是解码器里最耗计算量的模块之一。如果你在做实时解码,千万别对每一帧都全图滤波。可以只对QP较大的区域滤波,或者用SIMD指令并行处理。我当年在DSP上优化时,把滤波循环展开后,速度提升了3倍。

3.4 本章知识体系总览

下面这张图把变换、量化、熵编码、去块效应滤波的流程串起来了。你可以把它当作编码器后半段的“地图”。

H.264编码后半段流程 H.264 编码后半段:变换 → 量化 → 熵编码 → 滤波 预测残差块 整数DCT变换 量化 (QP) Zigzag扫描 熵编码 CAVLC 或 CABAC H.264码流 去块效应滤波 重建图像 注意 滤波在解码环路内 影响后续帧的参考

从图上你能看到,整个流程是串行的。残差块经过变换、量化、扫描后,进入熵编码生成码流。同时,量化后的系数还要反量化、反变换,加上预测值,再经过去块效应滤波,才能作为下一帧的参考图像。

这个环路设计是H.264的精髓——编码器和解码器必须保持完全一致的参考图像,否则误差会越滚越大。我在做码流分析工具时,就遇到过编码器和解码器滤波边界强度计算不一致导致的漂移问题,查了整整两天才找到原因。

好了,这一章的内容就到这儿。变换量化、熵编码、去块效应滤波,这三块是H.264编码效率的关键。理解了它们,你就掌握了H.264压缩的“核心引擎”。