H264码流结构理解整理
本文将带你深入H.264文件的内部,从宏观到微观,逐一剖析其各个组成部分的作用、相互关系以及一些精妙的设计哲学。
在了解H264之前需要有以下的一些基础知识:
宏观结构:从文件到帧
一个H.264原始码流(.h264
或.264
文件)并不是一个简单的“视频文件”,它不包含音频、字幕等元信息。它是一个纯粹的、编码后的视频数据比特流。这个流的结构可以看作一个分层模型,如下图所示,理解这个结构是理解H.264的关键:
网络抽象层单元 (NAL Unit)
H.264设计的一个核心思想是网络友好性。为了实现这一目标,整个码流被分割成一个个独立的包,称为 NAL Unit(网络抽象层单元)。每个NAL Unit都是一个自包含的数据包,包含一个头部和负载数据。这种设计使得H.流非常适合在容易产生包丢失和延迟的网络(如RTP/UDP)中传输,因为一个NAL Unit的丢失通常不会导致整个视频无法解码。
关键概念:帧 (Frame) 与片 (Slice)
在视频编码中,一帧(Frame) 通常对应一张静态图片。H.264对一帧图像进行编码后,其数据可能会被装进一个或多个NAL Unit中。
为什么是一或多个?这是因为一帧数据可以被分割成多个片(Slice)。每个Slice都是一个独立的编码单元,包含了一帧图像中的一部分宏块(Macroblock)。将一帧分割成多个Slice主要有两个好处:
- 错误恢复:在网络传输中,如果一个Slice丢失了,解码器仍然可以利用错误隐藏技术来近似恢复图像,而不是丢失整帧。
- 并行处理:多个Slice可以并行编码或解码,提高效率。
微观结构:NAL Unit的内部世界
现在,让我们打开一个NAL Unit,看看它里面到底有什么。
NAL Unit Header(头部)
每个NAL Unit都以一个1字节(可扩展为2字节)的头部开始。这个头部虽然小,但信息量巨大:
- 禁止位(F):通常为0,如果为1表示该单元出错。
- 重要性指示位(NRI):表示这个NAL Unit的重要性。值越大,解码器越需要优先保护它(如SPS/PPS的NRI值最高)。
- 类型(Type):这是最关键的部分!它定义了该单元负载数据的类型。主要分为两大类:
- VCL(视频编码层)单元:真正携带编码视频数据的单元(如Slice)。
- Non-VCL(非视频编码层)单元:携带元数据和控制信息的单元,是解码的“说明书”。
NAL Unit Payload(负载)
负载部分的数据内容完全由头部中的类型(Type) 决定。
关键的Non-VCL单元(元数据)
这些单元不包含图像像素数据,但没有它们,VCL单元根本无法被解码。它们通常在视频流开始时发送一次,但如果解码器中途加入,也需要重新获取。
SPS(序列参数集 - Type 7)
* 作用:包含了适用于整个视频序列的全局参数。它是解码器的“总纲”。
* 包含信息:视频的档次、级别、分辨率(pic_width_in_mbs_minus1
等)、帧率、色深、比特深度等。没有SPS,解码器连图像该解码成多大都不知道。
PPS(图像参数集 - Type 8)
* 作用:包含了适用于一幅或多幅图像的解码参数。它更像是“章节细则”。
* 包含信息:熵编码模式(CAVLC或CABAC)、量化参数等。PPS可以改变,从而在序列中实现不同的编码配置。
IDR(即时解码刷新 - 属于VCL,但特殊)
* 作用:一个特殊的Slice(通常是I-Slice),它告诉解码器:“从这里开始,可以独立解码,不再需要参考之前的帧了。”
* 意义:IDR帧是随机访问和 seeking 的关键点。当你拖动视频进度条时,播放器总是在寻找最近的IDR帧开始解码,因为它能清空之前的参考帧缓冲区,保证解码正确。
VCL单元(核心数据)
这些单元携带了实际的压缩视频数据,即Slice。
Slice Header(切片头)
* 每个Slice都有自己的头,其中包含了当前Slice解码所需的信息:
* 引用哪个PPS(从而间接引用SPS)。
* 帧类型(I, P, B)。
* 量化参数。
* 根据帧类型,包含运动向量预测所需的信息。
Slice Data(切片数据)
* 这是压缩数据的核心,由一系列宏块(Macroblock) 组成。
* 宏块通常是16x16像素的编码单元,它包含了:
* 预测信息:对于I帧,是帧内预测模式;对于P/B帧,是运动向量(描述当前块是从参考帧的哪个位置移动过来的)。
* 残差数据:经过预测后,当前块与预测块之间的差值。这部分数据会经过变换(DCT)、量化、熵编码(CAVLC/CABAC),从而获得极高的压缩率。
特殊设计点
3.1 参数集(SPS/PPS)机制
这是H.264一个非常巧妙的设计。它将很少改变但至关重要的信息(SPS/PPS)与频繁变化的数据(Slice)分离开。
- 优点一:鲁棒性:即使丢失了一些Slice,只要SPS/PPS还在,解码器就能继续工作。
- 优点二:效率:无需在每一个Slice中都重复这些头部信息,大大节省了码流。
- 优点三:灵活性:一个码流中可以存在多个PPS,并在不同场景下切换使用。
3.2 I, P, B帧与GOP(图像组)
- I帧(Intra):自包含帧,仅使用本帧内的信息进行编码,不参考其他帧。它是压缩率最低但最关键的帧,是P帧和B帧的锚点。
- P帧(Predicted):参考前面的I帧或P帧进行运动补偿预测编码,压缩率高于I帧。
- B帧(Bi-directional):可以同时参考前面和后面的帧,获得最高的压缩率,但会带来编码延迟。
- 一个GOP就是从上一个IDR帧到下一个IDR帧之前的所有帧序列。GOP长度越长,B/P帧越多,压缩率越高,但随机访问的间隔也越长。
3.3 熵编码:CAVLC 与 CABAC
这是压缩过程中的最后一步,将数据转换为二进制码流。
- CAVLC(上下文自适应变长编码):相对简单,压缩效率一般,用于Baseline等档次。
- CABAC(上下文自适应二进制算术编码):非常复杂,但压缩效率比CAVLC高出10%-20%,是Main和High档次效率高的主要原因之一。
总结
H.264的结构是一个分层、模块化的杰作:
- 整体:码流由一个个NAL Unit组成,适合网络传输。
- 局部:NAL Unit分为VCL(携带Slice数据)和Non-VCL(携带SPS/PPS等元数据)。
- 核心:Slice数据由宏块组成,宏块包含了预测信息和残差数据,通过预测和变换编码实现压缩。
- 精妙设计:参数集分离、IDR帧、Slice划分和CABAC等特性共同造就了H.264在效率、鲁棒性和灵活性上的完美平衡。
理解H.264的结构,不仅能帮助我们更好地处理视频数据(如封装、传输、解码问题定位),更能让我们体会到工程师们在标准制定中的智慧和远见。尽管如今H.265/HEVC、AV1等更先进的编码器已经出现,但H.264的基本设计思想和结构仍然深刻地影响着它们。