编码流水线并行化:宏块级流水线、帧级流水线、Slice级并行编码、Wavefront并行处理(WPP)的原理与实现

编码延迟,说白了就是“从摄像头采集到码流输出”的时间差。我早年做实时编码器时,最头疼的就是这个。你想想看,如果一帧要等上一帧全部编完才开始,那延迟直接就奔着帧率倒数去了。30fps 就是 33ms 一帧,光串行就得 33ms,再加上传输、显示……用户早就不耐烦了。

怎么破?流水线并行化。把编码拆成多个阶段,让它们重叠执行。这一章我们就来聊聊四种主流方案:宏块级、帧级、Slice 级和 WPP。

1. 宏块级流水线:最细粒度的并行

宏块(Macroblock, MB)是 H.264 时代的基本编码单元。在 HEVC 里叫 CTU,但思路一样。宏块级流水线,就是把一帧内的宏块按行或按列分给多个处理单元。

核心思想: 每个宏块独立完成预测、变换、量化、熵编码。但要注意——相邻宏块有依赖关系,比如帧内预测要用到左边和上边的重建像素。

我在项目中遇到过一个问题:如果流水线深度设得太大,宏块间的依赖会导致频繁的“流水线气泡”。比如第 N 行的宏块要等第 N-1 行的宏块重建完才能开始预测。嗯,这里有个技巧——把依赖路径上的关键数据提前预取,能减少大约 20% 的空泡时间。

流水线阶段 操作内容 依赖数据
Stage 1 运动估计 / 帧内预测模式决策 参考帧 / 左上方重建像素
Stage 2 残差计算 + 变换 + 量化 预测块
Stage 3 反量化 + 反变换 + 重建 量化系数
Stage 4 环路滤波 + 熵编码 重建帧 / 语法元素

宏块级流水线的优势是延迟极低——理论上可以做到“一个宏块周期”的延迟。但缺点也很明显:控制逻辑复杂,而且对硬件多核的调度要求高。我个人习惯在 FPGA 原型验证时用这种方案,因为可以精确控制每个时钟周期。

2. 帧级流水线:简单粗暴但有效

帧级流水线是最容易理解的并行方式。把编码器拆成多个帧处理单元,比如:

  • Unit A 处理第 N 帧的预测和残差
  • Unit B 处理第 N-1 帧的熵编码
  • Unit C 处理第 N-2 帧的码流打包

这样,三帧可以同时处于不同阶段。延迟大约是“帧周期 × 流水线深度”。

避坑指南: 我曾经在帧级流水线里踩过一个坑——B 帧的参考依赖。如果第 N 帧是 B 帧,它需要参考第 N-1 和第 N+1 帧。这时候帧级流水线就得做“帧重排序”,把参考帧提前处理。否则,流水线会卡死。

帧级流水线的实现相对简单,适合软件编码器。但延迟较高,不适合低延迟场景(比如视频通话)。

3. Slice 级并行编码:灵活的分片策略

Slice 是编码流中独立可解码的单元。每个 Slice 可以单独编码、单独解码。Slice 级并行,就是把一帧切成多个 Slice,分给多个线程或核心。

为什么用 Slice?因为它的依赖边界清晰——Slice 之间没有预测依赖。你想想看,如果一帧切成 4 个 Slice,那 4 个核可以同时开工,互不干扰。

注意: Slice 切得太多会降低编码效率。因为每个 Slice 的边界不能做帧内预测和运动矢量预测,相当于“浪费”了边界区域的压缩潜力。我见过有人把一帧切成 64 个 Slice,结果码率暴涨了 15%。

实际项目中,我建议 Slice 数量不超过核心数的 2 倍。比如 4 核处理器,切 4~8 个 Slice 比较合理。再多,收益递减。

4. Wavefront 并行处理(WPP):HEVC 的优雅解法

WPP 是 HEVC 引入的一种并行技术,后来也被 AV1 和 VVC 采纳。它的原理很巧妙:

  • 每一行 CTU 由一个线程处理
  • 第 N 行的线程比第 N-1 行的线程延迟 2 个 CTU 启动
  • 这样,第 N 行需要的左上方依赖数据已经由第 N-1 行准备好了

说白了,WPP 是一种“斜线式”的流水线。它不像 Slice 那样硬切边界,而是通过时间错位来保证依赖可用。

WPP 的关键参数: 启动延迟(通常为 2 个 CTU)。延迟太小,依赖数据没准备好;延迟太大,并行度下降。我调过很多次这个参数,发现 2 是最优的——这是 HEVC 标准推荐值,也是实践验证过的。

下面这张图展示了 WPP 的并行流程:

WPP 并行处理示意图 CTU 0 CTU 1 CTU 2 CTU 3 …… CTU 0 CTU 1 CTU 2 CTU 3 …… CTU 0 CTU 1 CTU 2 CTU 3 …… 延迟2 CTU 说明: 每行CTU由独立线程处理。 第N行比第N-1行延迟2个CTU启动。 依赖数据(左上方)已就绪,无等待。

WPP 的好处是:编码效率几乎不损失(不像 Slice 那样有边界惩罚),而且并行度可以随分辨率线性扩展。4K 视频用 WPP 可以轻松做到 4~8 倍加速。

5. 四种方案的对比与选型

在实际项目中怎么选?我列个表给你参考:

方案 延迟 编码效率损失 实现复杂度 适用场景
宏块级流水线 极低(~1 MB周期) 无 高 硬件编码器、FPGA
帧级流水线 中等(~3帧周期) 无 低 软件编码器、离线转码
Slice级并行 低(~1帧周期) 有(边界损失) 中 多核CPU、GPU
WPP 低(~1帧周期) 几乎无 中高 HEVC/AV1/VVC编码器

我的建议: 如果你在做实时通信(RTC),优先考虑 WPP 或宏块级流水线。如果是离线转码,帧级流水线就够用了。Slice 级并行适合“快速实现”的场景,但要注意码率控制——我曾经因为 Slice 切太多,导致码率波动剧烈,花了三天才调回来。

6. 实现要点与避坑

最后,分享几个我在实现这些并行方案时踩过的坑:

  • 线程同步开销: 并行不是免费的。线程间的锁、条件变量、内存屏障都会增加延迟。我建议用无锁队列或原子操作来传递依赖数据。
  • 负载均衡: 不同宏块/CTU 的编码复杂度不一样(比如运动剧烈的区域耗时更多)。静态分配容易导致某些线程空闲。可以用“任务窃取”来动态平衡。
  • 内存带宽: 并行编码需要同时访问多帧数据。我曾经在 4K 编码器上遇到内存带宽瓶颈——8 个线程同时读参考帧,直接把 DDR 带宽打满了。解决方案是用分块缓存(tile-based caching)。
  • 熵编码的串行瓶颈: CABAC 是串行的,很难并行化。WPP 和 Slice 之所以有效,就是因为每个 Slice/行有独立的 CABAC 引擎。如果你用帧级流水线,注意熵编码阶段可能会成为瓶颈。