3、H264编码原理详解:帧内预测、帧间预测、变换量化、熵编码(CAVLC/CABAC)、环路滤波

好,咱们进入正题。H264的编码原理,说白了就是一套「去冗余」的组合拳。我当年刚接触时,觉得这五个模块各自为战,后来做项目才发现,它们环环相扣,少一个都不行。

你想想看,视频里相邻的像素、相邻的帧,其实长得都差不多。编码器要做的,就是把这些「差不多」的部分去掉,只记录「不一样」的地方。这五个模块,就是干这个的。

3.1 帧内预测:空间上的「抄作业」

帧内预测,说白了就是在一张图里,用已经编好的像素去猜当前块的值。我习惯叫它「空间预测」,因为它只参考同一帧里的邻居。

H264支持两种帧内预测模式:

  • Intra_4x4:适用于细节丰富的区域,有9种预测方向
  • Intra_16x16:适用于平坦区域,有4种预测模式

举个例子,你有一块4x4的像素块,它的上方和左边都已经编好了。编码器会从9种方向里选一个,用这些邻居像素去「猜」当前块的值。猜得越准,残差就越小,码率就越低。

关键点:帧内预测只用于I帧。I帧是视频的「关键帧」,不依赖任何其他帧。所以I帧的压缩率通常最低,但它是解码的起点。

我在项目中遇到过一个问题:某个场景下,画面里全是细密的纹理,Intra_4x4的9种方向都不够用,残差特别大。后来我手动调整了QP(量化参数),才把码率压下来。嗯,这里要注意,帧内预测不是万能的,纹理太复杂时,该加码率就得加。

3.2 帧间预测:时间上的「找相似」

帧间预测,这才是H264的杀手锏。它利用的是视频的时间冗余——相邻帧之间,大部分内容是不变的。

编码器会在参考帧里找一个和当前块最像的块,然后只记录两者的位移(运动矢量)和差值(残差)。你想想看,一个运动缓慢的场景,大部分块都能在参考帧里找到几乎一模一样的匹配,残差接近零,码率自然就低了。

H264支持多种块大小:

块大小适用场景
16x16大面积平坦区域或静止背景
16x8 / 8x16水平或垂直方向的运动
8x8中等复杂度的运动
8x4 / 4x8 / 4x4精细运动或复杂纹理

我个人习惯在运动剧烈的场景里用小块(8x8以下),虽然运动矢量多了,但残差小,整体码率反而更优。反之,静止场景用大块,省矢量开销。

避坑指南:我曾经在低码率场景下强行用4x4块,结果运动矢量太多,码率反而爆了。后来学乖了——块大小不是越小越好,得看场景。

3.3 变换量化:把残差「浓缩」成系数

帧内/帧间预测之后,我们得到了残差。残差里还有冗余吗?有的。相邻像素的残差往往也相似。变换的作用,就是把这种空间冗余去掉。

H264用的是整数DCT变换(4x4和8x8两种)。说白了,就是把像素域的残差转换到频率域。低频分量(人眼敏感)集中在左上角,高频分量(人眼不敏感)在右下角。

量化就更粗暴了——把变换后的系数除以一个值(QP),然后取整。QP越大,高频系数被砍得越狠,码率越低,但画质也越差。

// 伪代码:量化过程
for each coefficient in transformed_block:
    quantized_coeff = round(coefficient / Q_step)

你想想看,量化是H264里唯一有损的步骤。其他模块都是无损的。所以QP的选择,直接决定了视频的质量和码率。

注意:量化后的系数里会有大量零值。尤其是高频区域,几乎全被量化成零。这就是后面熵编码能大幅压缩的原因。

3.4 熵编码:把系数「打包」成比特流

熵编码是最后一步,也是H264里最「烧脑」的部分。它把量化后的系数、运动矢量、预测模式等,编码成二进制比特流。

H264支持两种熵编码方式:

  • CAVLC(上下文自适应变长编码):简单、低复杂度,适合低端嵌入式设备
  • CABAC(上下文自适应二进制算术编码):压缩率高20%-30%,但计算量大

我当年在ARM Cortex-A7上做H264编码器时,CAVLC跑得飞快,但码率总压不下来。换成CABAC后,码率降了25%,但CPU占用率直接飙到90%。嗯,这就是典型的「鱼和熊掌」。

CAVLC的原理,说白了就是给高频出现的符号分配短码字,低频的分配长码字。而CABAC更聪明——它会根据上下文动态调整概率模型,压缩率更高。

我的建议:嵌入式设备如果算力够,优先用CABAC。如果CPU吃紧,用CAVLC配合更小的QP,也能达到不错的压缩效果。

3.5 环路滤波:消除「块效应」

环路滤波,也叫去块效应滤波。它解决的是H264的一个「副作用」——块边界上的像素不连续,看起来像马赛克。

为什么会这样?因为编码是以块为单位的。每个块的量化误差不同,导致块边界两侧的像素值出现跳变。环路滤波的作用,就是把这些跳变「抹平」。

H264的环路滤波是自适应的——它只对真正的块效应进行滤波,不会模糊图像细节。滤波强度由两个参数控制:

  • 边界强度(Bs):0到4,值越大滤波越强
  • 阈值(alpha, beta):决定是否对某个像素进行滤波

我记得有一次调试一个低码率视频,画面里全是方块,客户投诉说「像打码了一样」。我打开环路滤波一看,发现Bs值设得太低了,滤波根本没生效。调高之后,画面平滑了很多。

经验之谈:环路滤波不是越强越好。滤波太强,画面会变「糊」,细节全没了。我一般把Bs控制在2-3之间,既能去块效应,又不损失锐度。

小结

这五个模块,我习惯用一句话总结:帧内/帧间预测去冗余,变换量化去不敏感信息,熵编码去统计冗余,环路滤波擦屁股

你想想看,H264的编码器就像一个流水线。每个模块都有自己的任务,但最终目标只有一个——用最少的比特,还原出最接近原始画面的视频。做嵌入式编码时,我经常要在算力和画质之间做取舍。嗯,这就是H264的魅力所在,也是它至今仍在嵌入式领域占据主导地位的原因。