3、H265 编码结构:CTU、CU、PU、TU、Slice 与 Tile
这一章咱们聊聊 H.265 的核心骨架。说白了,编码器怎么把一帧画面拆成小块,再一块块地处理。我刚开始接触 HEVC 时,也被 CTU、CU、PU、TU 这些缩写搞得头晕。后来在项目里调过几次码率,才真正理解它们各自扮演的角色。
你想想看,一帧 4K 画面,几百万个像素。编码器不可能一口气全处理完。它得先切块,再分层,最后才是预测和变换。这套切分逻辑,就是 H.265 的编码结构。
一句话总结:CTU 是根,CU 是枝,PU 是预测的视角,TU 是变换的粒度。Slice 和 Tile 则是并行与容错的工具。
3.1 编码树单元(CTU)—— 一切的起点
CTU 是 H.265 引入的第一个大概念。它取代了 H.264 里的宏块(Macroblock)。
H.264 的宏块固定 16x16。H.265 的 CTU 可以大到 64x64,甚至 128x128。为什么?因为高分辨率视频里,大块平坦区域(比如蓝天、白墙)很多。用大块编码,能省下大量比特。
我个人习惯把 CTU 理解成「地皮」。你拿到一块地皮,怎么盖房子,是后面 CU 的事。CTU 只负责划定边界。
| 参数 | H.264 宏块 | H.265 CTU |
|---|---|---|
| 最大尺寸 | 16x16 | 64x64(可配置为 128x128) |
| 亮度块尺寸 | 16x16 | 64x64 / 32x32 / 16x16 |
| 色度块尺寸 | 8x8 | 32x32 / 16x16 / 8x8 |
| 划分方式 | 固定 | 四叉树递归划分 |
CTU 的尺寸在编码器配置里设定。我建议在低延迟场景用 64x64,在离线转码场景可以试试 128x128。曾经有个项目,我们把 CTU 从 64 改成 128,码率直接降了 8%,但编码时间翻了一倍。嗯,这里要注意,不是越大越好。
3.2 编码单元(CU)—— 真正的决策者
CTU 划好地皮后,CU 开始决定怎么盖。CU 是编码决策的基本单位。每个 CU 要么帧内预测,要么帧间预测,二选一。
CU 的划分是递归的。一个 64x64 的 CTU,可以分成 4 个 32x32 的 CU。每个 32x32 的 CU,又可以继续分成 4 个 16x16 的 CU。一直分到最小 8x8。
这就像你拿着一把刀,不断把蛋糕切小。切到哪一层停下来?看率失真代价(RDO)。如果 32x32 的 CU 编码质量够好,就不往下切了。如果细节多,继续切。
避坑指南:我曾经在移动端芯片上踩过坑。CU 划分深度设得太深(比如允许 8x8),编码器会疯狂递归,功耗飙升。后来我限制最大划分深度为 2(即最小 16x16),画质损失很小,但功耗降了 30%。
3.3 预测单元(PU)—— 预测的视角
CU 决定用帧内还是帧间后,PU 负责具体怎么预测。PU 是预测操作的基本单位。
帧内预测的 PU 形状比较规矩。帧间预测就灵活多了。H.265 支持非对称划分(AMP)。比如一个 32x32 的 CU,PU 可以切成 32x8 加 32x24。这在处理运动物体边缘时特别有用。
我记得有一次做监控视频编码,画面里有个行人从右往左走。用对称 PU 编码,边缘总有锯齿。换成非对称 PU 后,锯齿消失了,码率还降了 5%。
| 预测模式 | PU 划分方式 | 典型场景 |
|---|---|---|
| 帧内(Intra) | 2Nx2N, NxN | I 帧、场景切换 |
| 帧间(Inter) | 2Nx2N, 2NxN, Nx2N, NxN, 2NxnU, 2NxnD, nLx2N, nRx2N | P 帧、B 帧 |
PU 的划分信息需要写到码流里。划分越细,码流开销越大。所以编码器会在「预测精度」和「划分开销」之间做权衡。
3.4 变换单元(TU)—— 频域的转换
预测做完后,残差数据需要变换到频域。TU 就是变换的基本单位。
TU 的尺寸可以是 4x4、8x8、16x32、32x32。注意,TU 可以比 CU 大,也可以比 CU 小。TU 的划分独立于 CU 的划分。这叫残差四叉树(RQT)。
为什么 TU 要独立?因为预测和变换的「最佳粒度」不一样。预测希望块大一点,减少开销。变换希望块小一点,捕捉局部纹理。两者分开,各自优化。
注意:TU 的最大尺寸受限于变换块的最大尺寸。H.265 标准规定,变换块最大 32x32。所以即使 CTU 是 128x128,TU 也不能超过 32x32。
在实际编码中,TU 的划分深度会影响高频细节的保留。我建议在低码率场景下,限制 TU 最大为 16x16。因为 32x32 的变换在低码率下容易产生块效应。
3.5 条带(Slice)—— 独立解码的单元
Slice 是一帧图像中若干个 CTU 的集合。每个 Slice 可以独立解码。这意味着,解码器拿到一个 Slice 的数据,不需要依赖同一帧的其他 Slice。
Slice 的引入主要是为了容错。如果网络丢包了,只影响丢包所在的 Slice,其他 Slice 还能正常解码。
H.265 支持三种 Slice 类型:
- I Slice:只使用帧内预测。不依赖任何参考帧。
- P Slice:可以使用帧内预测和单向帧间预测。依赖前面的 I 或 P 帧。
- B Slice:可以使用帧内预测、单向和双向帧间预测。依赖前后的帧。
我见过不少开发者把 Slice 当成并行工具。其实 Slice 的并行能力有限。因为 Slice 之间虽然独立解码,但编码时是串行的。真正适合并行的,是下面要讲的 Tile。
3.6 片(Tile)—— 真正的并行利器
Tile 是 H.265 新增的概念。它把一帧图像分成矩形区域。每个 Tile 包含整数个 CTU。Tile 之间完全独立,可以并行编码和解码。
Tile 和 Slice 的区别在于:
- Slice 是逻辑上的独立单元,形状可以是任意 CTU 集合。
- Tile 是物理上的矩形区域,形状规则。
- Slice 主要用于容错,Tile 主要用于并行。
我曾经在一个 8K 实时编码项目里,把一帧分成 4 个 Tile,每个 Tile 分配给一个编码核心。编码速度直接提升了 3.5 倍。嗯,这里要注意,Tile 边界会有轻微的质量损失。因为跨 Tile 的预测被禁止了。
| 特性 | Slice | Tile |
|---|---|---|
| 形状 | 任意 CTU 集合 | 矩形区域 |
| 并行编码 | 有限(依赖熵编码上下文) | 完全独立 |
| 并行解码 | 完全独立 | 完全独立 |
| 主要用途 | 容错、低延迟 | 并行加速、大分辨率 |
| 码流开销 | 每个 Slice 有独立头 | Tile 边界信息在 PPS 中 |
3.7 整体结构关系图
下面这张图展示了 CTU、CU、PU、TU 之间的层级关系。从一帧图像开始,先切成 CTU,CTU 递归划分成 CU,CU 再决定 PU 和 TU。
3.8 实际编码中的选择策略
在实际编码器里,CTU、CU、PU、TU 的选择不是孤立的。编码器会遍历所有可能的划分方式,计算每种方式的率失真代价,然后选最优的。
这个过程叫率失真优化(RDO)。计算量非常大。所以编码器会做一些快速决策:
- 如果当前 CU 的残差很小,就不继续划分了。
- 如果当前 CU 是平坦区域,优先用大 TU。
- 如果当前 CU 是纹理区域,优先用小 TU。
我的经验:在 x265 编码器里,可以通过 --ctu 参数控制 CTU 大小,--rect 和 --amp 控制 PU 划分,--tu-intra-depth 和 --tu-inter-depth 控制 TU 深度。调参时,先调 CTU,再调 TU,最后调 PU。这个顺序能帮你快速找到性能瓶颈。