3、视频编解码器硬件模块详解:运动估计(ME)、变换量化(TQ)、熵编码(CABAC/CAVLC)、环路滤波(DBLK/SAO)

各位同学,今天我们来啃硬骨头。视频编解码器的硬件加速,说白了就是把这四个模块伺候好:运动估计(ME)变换量化(TQ)熵编码(CABAC/CAVLC)环路滤波(DBLK/SAO)。它们占了整个编解码器80%以上的计算量和硬件面积。我当年刚入行时,以为把算法看懂就能做硬件,结果被时序和面积教做人。嗯,今天咱们就聊聊这些坑。

核心观点:硬件加速不是简单地把软件代码翻译成Verilog。你需要理解每个模块的数据依赖并行度访存模式。这才是架构师的价值所在。

视频编解码器硬件加速核心模块 原始视频帧 / 码流 运动估计 (ME) SAD/SATD + 搜索窗 变换量化 (TQ) DCT/DST + 量化 熵编码 CABAC / CAVLC 环路滤波 DBLK + SAO 重建帧反馈 压缩码流 / 重建帧 ME TQ 熵编码 环路滤波

3.1 运动估计(ME)—— 最耗资源的模块

运动估计,说白了就是找相似块。编码器把当前帧分成一个个块,然后在参考帧里找最像的那个块。找到之后,只需要记录一个运动矢量(MV)和残差,不用传整块像素。压缩就是这么来的。

但硬件实现时,问题就来了。搜索范围越大,计算量呈指数级增长。我做过一个4K@60fps的编码器,光ME就占了整个芯片面积的40%。你想想看,这得多大。

硬件实现要点

  • SAD/SATD计算单元:这是ME的核心。SAD(绝对差值和)用加法器阵列实现,SATD(哈达玛变换后的绝对差值和)需要加一个变换。我个人习惯用SAD做粗搜,SATD做细搜,平衡面积和精度。
  • 搜索窗管理:参考帧数据要存在片上SRAM里。搜索窗大小一般是64x64或128x128。我曾经遇到一个项目,搜索窗没规划好,导致DDR带宽被ME吃光,其他模块全在等数据。血的教训。
  • 流水线设计:ME可以拆成多级流水线:加载搜索窗 → 计算SAD → 比较最小值 → 输出MV。每一级都要做乒乓缓冲,不然数据流会断。

小技巧:ME的并行度可以做到很高。比如一次计算16个候选点的SAD,用16个SAD单元并行跑。但要注意,搜索窗的读取带宽要跟得上。我一般建议SAD单元数 = 搜索窗读取位宽 / 像素位宽。

3.2 变换量化(TQ)—— 精度与面积的博弈

变换量化,就是把残差数据从空间域转到频域,然后丢掉人眼不敏感的高频信息。H.264用4x4 DCT,H.265用4x4/8x8/16x16/32x32 DCT和DST。模块越大,压缩率越高,但硬件也越复杂。

我记得有一次做H.265编码器,32x32变换的硬件面积比4x4大了将近10倍。但实际码率节省只有5%左右。嗯,这里要权衡。

硬件实现要点

  • 蝶形运算单元:DCT/DST可以用蝶形结构实现。4x4变换只需要8个蝶形单元,32x32需要160个。我建议用可配置的蝶形阵列,支持多种尺寸。
  • 量化表:量化参数(QP)决定了压缩质量。硬件里一般用查找表实现量化系数。注意,QP每增加6,量化步长翻倍。这个规律可以用来简化硬件设计。
  • 反变换反量化:解码端需要反变换反量化。编码器里也要做,因为要重建参考帧。我建议把正反变换的硬件复用,用MUX切换数据流,能省不少面积。
// 4x4 DCT 蝶形运算伪代码
// 输入:4x4残差矩阵
// 输出:4x4变换系数
for (i = 0; i < 4; i++) {
    // 行变换
    a0 = in[i][0] + in[i][3];
    a1 = in[i][1] + in[i][2];
    a2 = in[i][1] - in[i][2];
    a3 = in[i][0] - in[i][3];
    
    out[i][0] = a0 + a1;
    out[i][1] = a2 * 2 + a3;
    out[i][2] = a0 - a1;
    out[i][3] = a3 * 2 - a2;
}
// 列变换同理,需要转置后再做一次

注意:量化后的系数要截位。截位策略直接影响编码质量。我见过一个项目,为了省面积把截位做得太狠,结果画面出现块效应。后来不得不重新流片。嗯,这个坑我踩过。

3.3 熵编码(CABAC/CAVLC)—— 串行瓶颈

熵编码是编码器的最后一步,也是硬件加速最头疼的一步。CAVLC(上下文自适应变长编码)相对简单,用查找表就能搞定。CABAC(上下文自适应二进制算术编码)就麻烦了,它是串行的,每个bit的编码都依赖前一个bit的状态。

你想想看,视频编码器其他模块都是并行流水线,到了熵编码这里突然变成串行。这就是所谓的「熵编码瓶颈」。我做过一个4K编码器,CABAC的时钟频率要跑到800MHz才能跟上其他模块的速度。这可不是闹着玩的。

硬件实现要点

  • CAVLC实现:用多级查找表,把常见的系数模式映射成码字。硬件实现简单,面积小。但压缩率不如CABAC。
  • CABAC实现:核心是概率估计和区间划分。硬件里用查找表存概率状态,用算术单元做区间更新。我建议用多bin并行架构,一次处理2-4个bin,能缓解串行瓶颈。
  • 码流打包:熵编码输出的码字要拼成字节流。注意字节对齐和防竞争。我曾经遇到一个bug,码流打包模块的FIFO深度不够,导致数据溢出,画面花屏。查了三天才找到原因。
特性 CAVLC CABAC
压缩率 较低(比CABAC低10-15%) 较高
硬件面积 小(约5K门) 大(约20K门)
吞吐率 高(可并行) 低(串行瓶颈)
适用场景 低功耗/低码率 高质量/高码率

3.4 环路滤波(DBLK/SAO)—— 画质的最后一道防线

环路滤波,包括去块效应滤波(DBLK)和样本自适应偏移(SAO)。DBLK消除块边界上的伪影,SAO补偿像素级的偏差。这两个模块虽然计算量不大,但数据依赖复杂。

DBLK需要等当前块和相邻块都重建完才能做。SAO需要统计整帧的像素分布。我建议把DBLK放在编码流水线的末端,用行缓冲存相邻块的数据。SAO可以放在帧级处理,等整帧重建完再做。

硬件实现要点

  • DBLK滤波强度:根据边界强度和量化参数决定滤波强度。硬件里用查找表存滤波系数。注意,亮度块和色度块的滤波强度不同。
  • SAO类型:H.265支持边带偏移(BO)和边缘偏移(EO)。BO按像素值分段补偿,EO按边缘方向补偿。硬件实现时,BO用累加器,EO用比较器阵列。
  • 行缓冲管理:DBLK需要4行像素的缓冲。SAO需要整帧的统计信息。我建议用双缓冲结构,一个缓冲做滤波,一个缓冲做统计,流水线跑起来。

经验之谈:环路滤波的时序很容易被忽视。DBLK的滤波窗口是8x8,但数据依赖是跨块的。我一般会在DBLK前面加一个重排序缓冲,把数据排成滤波需要的顺序。这样能省不少控制逻辑。

好了,这四个模块讲完了。运动估计是体力活,变换量化是技术活,熵编码是精细活,环路滤波是收尾活。每个模块都有它的脾气。做硬件加速,不是简单地把算法堆上去,而是要理解每个模块的瓶颈在哪里,然后用架构去解决它。

我个人觉得,最难的是熵编码的并行化。CABAC的串行特性跟硬件天生的并行性是对矛盾。但换个角度想,正是这种矛盾,才让硬件架构师有了用武之地。你想想看,如果所有模块都能轻松并行,那还要我们干嘛?


专注资料整理