FPGA 实现视频编码器(H.264/H.265)的核心挑战在于高计算密度 与高存储带宽 的平衡。与 CPU 上的软件串行执行不同,FPGA 的优势在于流水线(Pipelining)和并行处理(Parallelism)。
以下是基于硬件思维的实现思路详解:
一、 整体架构设计:宏块/CTU 流水线
FPGA 实现通常采用混合流水线架构 。不能像软件那样等到一帧图像都处理完再处理下一帧,而是以 宏块 (MB, 16x16) 或 编码树单元 (CTU, 64x64) 为单位进行流动。
核心数据流:
- 输入缓冲: 从 DDR 读取原始像素(YUV),存入片上 BRAM(行缓存)。
- 粗粒度流水线 (Coarse-grained Pipeline):
- Stage 1: 运动估计 (ME) / 帧内预测 (Intra)
- Stage 2: 模式决策 (Mode Decision)
- Stage 3: 变换与量化 (T & Q) + 反变换 (IT & IQ)
- Stage 4: 熵编码 (CABAC/CAVLC)
- Stage 5: 环路滤波 (Deblocking/SAO)
- 重建回写: 将重建像素写回 DDR 作为参考帧。
二、 关键模块的硬件实现思路
1. 运动估计 (Motion Estimation, ME) ------ 算力消耗最大的模块
这是 FPGA 相比 CPU 优势最大的地方。软件只能逐个点搜索,FPGA 可以并行计算。
- SAD 阵列 (Systolic Array):
- 原理: 这是一个巨大的加法树。将当前块的像素广播到 PE(处理单元)阵列,参考窗的像素在阵列中流动。
- 并行度: 单个时钟周期内,可以同时计算几十个甚至上百个候选位置的 SAD(绝对差值和)。
- 搜索策略优化:
- 全搜索 (Full Search): 硬件上虽然能做,但功耗和面积太大。
- 分级搜索 (Hierarchical Search): 先在下采样图像上做粗搜索,再在原始分辨率做精细搜索。硬件上需要维护两套分辨率的缓存。
- 数据复用 (Data Reuse):
- 利用搜索窗 (Search Window) 的重叠特性。当处理下一个 CTU 时,搜索窗大部分数据与上一个 CTU 相同,只更新一列/一部分数据,极大降低 DDR 带宽。
2. 帧内预测 (Intra Prediction) ------ 依赖性难题
帧内预测依赖于左边和上边的已重建像素。
- 投影法与并行计算:
- 不像软件尝试一种模式 -> 算残差 -> 下一种模式。
- FPGA 会将参考像素(左侧和上方)加载到寄存器,在一个时钟周期内并发计算所有模式(H.265 的 35 种模式可能需要分组并行)的预测块。
- 随后通过比较器树 (Comparator Tree) 快速选出 SATD 最小的模式。
- 重建环路瓶颈:
- 当前块的预测需要左块的重建像素(Prediction + Residual)。这意味着变换、量化、反变换、反量化必须在处理下一个块之前完成。
- 解决思路: 采用预处理或基于原始像素的粗略模式选择(Rough Mode Decision),只对最可能的几种模式进行完整的重建环路计算。
3. 变换 (DCT) 与量化 (Quantization)
- 蝶形运算 (Butterfly Structure):
- DCT 本质是矩阵乘法。硬件上将其分解为两次一维变换(行变换 -> 转置存储 -> 列变换)。
- 利用蝶形算法减少乘法器数量,利用加减法组合。
- 无除法设计:
- FPGA 非常讨厌除法。量化过程中的除法(/QPstep)在标准中被设计为乘法 + 移位操作。DSP Slice(如 Xilinx DSP48)非常适合执行这种乘加运算 (MAC)。
4. 熵编码 (CABAC) ------ 吞吐率瓶颈
CABAC 是前后强依赖的串行过程(处理第 n 个 bin 需要第 n-1 个的状态),很难并行化,通常是限制编码器最高主频的关键。
- 常规 Bin 处理流水线:
- 上下文读取 -> 概率估计 -> 算术编码核 -> 区间更新 -> 上下文写回。
- 多 Bin 处理 (Multi-bin processing):
- 在一个时钟周期内处理多个 Bin(例如 Bypass 模式的 Bin 可以合并处理)。
- H.265 的 WPP (Wavefront Parallel Processing):
- H.265 标准允许上一行的 CTU 处理完两个之后,下一行就可以开始熵编码。FPGA 可以设计多个 CABAC 引擎并行工作,分别处理不同的 CTU 行。
5. 存储管理与带宽优化 (Memory Architecture)
这是决定 FPGA 方案成败的关键。
- 片上行缓存 (Line Buffers):
- 利用 FPGA 内部的 BRAM/URAM 存储由上至下的像素行(YUV)以及控制信息(MV, Mode)。
- H.265 挑战: 由于 CTU 是 64x64,相比 H.264 的 16x16,行缓存的大小增加了 4 倍,对 BRAM 资源消耗巨大。
- DDR 突发读写 (Burst Access):
- 视频数据必须打包成长的 Burst 进行读写(如 AXI4 协议)。不能零散地读一个像素。
- 通常设计一个 DMA 控制器 专门负责将参考帧的块搬运到片上 Cache。
- 帧压缩 (Frame Buffer Compression):
- 为了减少读写参考帧的带宽,许多商用 IP 会在参考帧写入 DDR 前进行一种私有的无损或微损压缩。
三、 H.264 与 H.265 在 FPGA 实现上的区别
| 特性 | H.264 FPGA 实现 | H.265 FPGA 实现 | 硬件影响 |
|---|---|---|---|
| 流水线粒度 | 16x16 (MB) | 64x64 (CTU) | H.265 需要更大的片上缓存,流水线延迟更高。 |
| 递归结构 | 较浅 (MB -> SubMB) | 深 (CTU -> CU -> TU) | H.265 控制逻辑极其复杂,状态机 (FSM) 难以维护。 |
| 运动补偿 | 6-tap 滤波器 | 8-tap 滤波器 | H.265 需要更多的乘法器资源 (DSP),且插值计算量增大。 |
| 熵编码 | CAVLC (逻辑简单) / CABAC | 仅 CABAC | H.265 必须攻克 CABAC 的时序瓶颈,通常需要更高频率。 |
| SAO | 无 | 有 | 需要额外的统计和滤波 Pass,通常作为独立流水级。 |
四、 总结与设计建议
- 不要试图用 FPGA 实现完整的参考软件代码(如 JM 或 HM): 软件代码充斥着
if-else分支,这是硬件的大忌。硬件需要确定性的数据流。 - 资源换速度: 尽可能展开循环。例如 SAD 计算,软件是两层循环,FPGA 应该是一个 2D 的加法器树。
- 乒乓操作 (Ping-Pong Buffering): 在各级流水线之间使用双缓冲,保证上一级写入时,下一级可以读取,消除等待气泡。
- 定点化验证: 标准中的变换和量化已经是整数运算,但对于中间变量的位宽(Bit-width)需要精确计算,防止溢出且不浪费资源。
最终的 FPGA 架构通常是一个 "多核异构" 系统:由硬核 CPU(ARM)负责配置和帧级控制,由 FPGA 逻辑负责 CTU 级别的像素流处理。