2. 编码框架:混合编码框架详解、编码块划分与编码流程总览
各位好,欢迎来到第二章。上一章我们聊了 H.265 为什么诞生,以及它到底比 H.264 强在哪。今天咱们直接切入核心——混合编码框架。
说实话,我第一次接触这个框架时,觉得它像个大杂烩:预测、变换、量化、熵编码,啥都有。但干久了才发现,这套框架之所以能统治视频编码二十多年,就是因为每个模块都踩在了点上。
核心观点:H.265/HEVC 的混合编码框架,本质上是“空间冗余靠预测,时间冗余靠运动补偿,视觉冗余靠变换量化,统计冗余靠熵编码”的组合拳。
2.1 混合编码框架详解
先看一张整体框架图,我建议你把它印在脑子里。后面所有章节,都是围绕这张图展开的。
这张图看着复杂,其实就一条主线:预测 → 残差 → 变换量化 → 熵编码。外加一条反馈环路:重建图像用于下一帧的参考。
我个人习惯把编码器拆成三个问题来理解:
- 怎么把画面拆开?—— 编码块划分(CTU/CU/PU/TU)
- 怎么猜下一块长啥样?—— 帧内/帧间预测
- 猜错了怎么补救?—— 残差编码 + 环路滤波
这一章我们先解决第一个问题。后两个问题,后面章节会逐个击破。
2.2 编码块划分:CTU / CU / PU / TU
H.264 里有个宏块(Macroblock),固定 16x16。H.265 把这个概念彻底推翻了。
为什么? 你想想看,现在的视频动不动就是 4K、8K。如果还用 16x16 的块,一块 4K 画面要拆成 8 万多个宏块,编码器光处理块头信息就累死了。
H.265 的做法是:用更大的块起步,按需递归拆分。
2.2.1 CTU —— 编码树单元
CTU(Coding Tree Unit)是 H.265 的顶层块。大小可以是 64x64、32x32 或 16x16。默认是 64x64。
我记得第一次在代码里看到 CTU 这个概念时,第一反应是:这不就是把宏块放大到 64x64 吗?后来才发现,远不止这么简单。
| 概念 | H.264 宏块 | H.265 CTU |
|---|---|---|
| 最大尺寸 | 16x16(固定) | 64x64(可配置) |
| 拆分方式 | 固定 16x16 | 四叉树递归拆分 |
| 亮度/色度 | 统一处理 | 可独立配置 |
| 灵活性 | 低 | 高 |
避坑指南:我曾经在一个 4K 直播项目中,把 CTU 设成了 16x16。结果编码速度慢得离谱。后来改成 64x64,速度提升了 3 倍,画质几乎没变。记住:分辨率越高,CTU 越大越好。
2.2.2 CU —— 编码单元
CTU 是顶层容器,真正干活的是 CU(Coding Unit)。一个 CTU 可以拆成 1 个、4 个、16 个甚至更多 CU。
拆分的规则是四叉树:
- 深度 0:64x64(1 个 CU)
- 深度 1:32x32(4 个 CU)
- 深度 2:16x16(16 个 CU)
- 深度 3:8x8(64 个 CU)
每个 CU 可以选择自己的预测模式:帧内预测还是帧间预测。也就是说,同一帧里,天空部分可以用帧内,运动的人物用帧间,互不干扰。
关键点:CU 是“决策单元”。编码器在 CU 级别决定“用什么方式预测”。这个决策直接影响码率和画质。
2.2.3 PU —— 预测单元
PU(Prediction Unit)是 CU 的“执行单元”。一个 CU 可以包含 1 个或多个 PU。
PU 的划分方式取决于 CU 的预测模式:
- 帧内模式:PU 大小必须等于 CU 大小(正方形)。
- 帧间模式:PU 可以是非正方形的,比如 64x32、16x32、8x16 等。
为什么帧间模式允许非正方形?因为运动物体的边界很少是正方的。你想想,一个人横着跑,用 64x32 的 PU 去匹配,比用 32x32 的 PU 更精准。
2.2.4 TU —— 变换单元
TU(Transform Unit)负责残差数据的变换和量化。TU 的大小可以是 4x4、8x8、16x16 或 32x32。
TU 和 CU 的关系是:一个 CU 可以包含多个 TU。TU 的划分也是四叉树,但 TU 的深度独立于 CU 的深度。
举个例子:一个 32x32 的 CU,如果纹理复杂,可以拆成 4 个 16x16 的 TU;如果纹理平坦,直接用 1 个 32x32 的 TU。
| 块类型 | 全称 | 核心作用 | 尺寸范围 |
|---|---|---|---|
| CTU | Coding Tree Unit | 顶层容器,管理拆分 | 16x16 ~ 64x64 |
| CU | Coding Unit | 决策预测模式 | 8x8 ~ 64x64 |
| PU | Prediction Unit | 执行预测(运动估计) | 4x4 ~ 64x64(可非正方) |
| TU | Transform Unit | 执行变换和量化 | 4x4 ~ 32x32 |
注意:CU、PU、TU 的划分是嵌套的,但不是一一对应的。一个 CU 可以有多个 PU,也可以有多个 TU。PU 和 TU 的边界可以不同。这种灵活性是 H.265 压缩效率高的关键,但也让编码器的复杂度飙升。
2.3 编码流程总览
好了,块划分的概念讲完了。现在我们把它们串起来,看看一帧画面从输入到输出,到底经历了什么。
我习惯把编码流程分成 6 步:
- 块划分:把一帧拆成 CTU,再递归拆成 CU。
- 预测模式决策:对每个 CU,尝试帧内和帧间预测,选率失真最优的。
- 残差计算:原始块减去预测块,得到残差。
- 变换量化:对残差做 DCT 变换,再量化,丢掉人眼不敏感的高频信息。
- 熵编码:把量化后的系数、运动矢量、模式信息等压缩成二进制码流。
- 环路滤波:重建图像,做去块效应滤波和 SAO 滤波,用于后续帧的参考。
下面这张图把流程和块划分的关系画清楚了:
注意看,第⑤步熵编码之后,码流就输出了。但第⑥步环路滤波是给“下一帧”用的,它不参与当前帧的输出。
个人经验:调试编码器时,我经常在“块划分”这一步卡住。因为 CU 的递归拆分是个 NP-hard 问题,编码器要在有限时间内找到最优划分。我的建议是:先用固定 CU 大小跑通流程,再逐步加入四叉树决策。一口吃不成胖子。
2.4 块划分的率失真决策
你可能要问:编码器怎么知道一个 CU 该不该继续拆?
答案是:率失真优化(RDO)。
简单说,编码器会算两笔账:
- 不拆:码率小,但残差大,画质差。
- 拆:码率大(因为要多存块头信息),但残差小,画质好。
编码器会选一个“性价比”最高的方案。这个性价比用公式表示就是:
J = D + λ × R
其中:
- J:率失真代价(越小越好)
- D:失真(比如 MSE)
- R:码率
- λ:拉格朗日乘子(控制码率和画质的平衡)
λ 越大,编码器越倾向于省码率(画质差一点也能接受)。λ 越小,编码器越倾向于保画质(码率大一点没关系)。
实战要点:在低码率场景(比如视频通话),λ 设大一点,让编码器多用大 CU,省下码率给纹理复杂的区域。在高码率场景(比如离线转码),λ 设小一点,让编码器多拆小 CU,保留更多细节。
2.5 本章小结
这一章我们干了三件事:
- 理清了混合编码框架的主线流程:预测 → 残差 → 变换量化 → 熵编码 + 环路反馈。
- 搞懂了四叉树块划分:CTU 是根,CU 是决策节点,PU 负责预测,TU 负责变换。
- 知道了率失真优化是块划分的“裁判”,用 J = D + λR 来判胜负。