从码流功能的角度可以分为两层:

  1. 视频编码层 (Video Coding Layer, VCL):
    • 职责: 核心压缩数据
    • 输出: 经过VCL处理后,输出的是一系列Slice数据。Slice是H.264编码的基本单元,包含了一组宏块的压缩信息。
  2. 网络抽象层 (Network Abstraction Layer, NAL):
    • 职责: 负责将VCL产生的Slice数据以及其他附加信息(如参数集)打包成一个个独立的、适合在网络上传输或在文件中存储的单元。
    • 输出: NAL单元 (NALU)。整个H.264码流就是由NALU组成的序列。
+------------------------------------+
|           视频内容 (像素)            |
+-----------------v------------------+
|      视频编码层 (VCL) Process        |
| (预测, 变换, 量化, 滤波...)           |
+-----------------v------------------+
|          Slice Data (切片)          |
+-----------------v------------------+
|     网络抽象层 (NAL) Packaging       |
| (添加NAL, 打包成NALU)              |
+-----------------v------------------+
| H.264 Bitstream ([NALU][NALU]...)  |
+------------------------------------+

NALU

每个NALU由一个NAL头 (NAL Header) 和一个原始字节序列载荷 (RBSP) 组成。

NAL头 (NAL Header) 只有一个字节,包含了三个关键字段:

  • forbidden_zero_bit (1 bit): 必须为0。
  • nal_ref_idc (2 bits): 指示该NALU的重要性。
    • 00: 表示这个NALU不被用作参考,解码器即使丢失它也能继续解码后续帧。
    • 非00: 表示这个NALU是参考帧的一部分,必须被正确解码,否则会影响后续帧。值越大,重要性越高。
  • nal_unit_type (5 bits): 最重要的字段,定义了RBSP中存放的是什么类型的数据。

常见的 nal_unit_type 值:

Type 含义 属于VCL/Non-VCL
7 SPS Non-VCL
8 PPS Non-VCL
5 IDR图像的切片 VCL
1 非IDR图像的切片 VCL
6 SEI (补充增强信息) Non-VCL

SPS与PPS

H.264使用参数集来传递解码所需的全局配置信息,以避免在每个Slice中重复发送。

SPS

(Sequence Parameter Set) - 序列参数集

  • 作用域: 作用于一个连续的视频序列。当分辨率、帧率等核心参数变化时,就需要一个新的SPS。
  • 包含的关键信息:
    • Profile, Level: 码流的档次和级别,决定了解码器需要具备的能力。
    • 图像分辨率: pic_width_in_mbs_minus1, pic_height_in_map_units_minus1
    • 帧率信息
    • 宏块自适应帧场编码 (MBAFF) 开关。
    • 最大参考帧数量 (max_num_ref_frames)

PPS

(Picture Parameter Set) - 图像参数集

  • 作用域: 作用于序列中的一张或多张图像。一个SPS可以被多个PPS引用。
  • 包含的关键信息:
    • 熵编码模式选择: entropy_coding_mode_flag (0: CAVLC, 1: CABAC)。
    • 初始QP值: pic_init_qp_minus26
    • 去块滤波相关参数
    • 依赖的SPS ID: seq_parameter_set_id,指明当前PPS属于哪个SPS。

关系

  • 层级关系: SPS -> PPS -> Slice Header -> Slice Data。解码器首先解析 SPS,然后根据 Slice Header 中指定的 PPS ID 去解析对应的 PPS,最后利用这两个参数集的信息来解码 Slice Data。
  • 传输时机: SPS 和 PPS 通常在视频流的开头,与 IDR 帧(即时解码刷新帧,一种特殊的I帧) 绑定在一起传输。因为 IDR 帧是一个序列的绝对刷新点,解码器可以从任何一个 IDR 帧开始独立解码,所以必须确保解码器在解码 IDR 帧之前已经拥有了正确的 SPS 和 PPS。

Slice

  • 将一帧划分为多个Slice是提高鲁棒性(一个Slice损坏不影响其他)和进行并行处理的基础。
  • 一个Slice则由一系列宏块(Macroblock)组成。宏块是H.264进行运动补偿和变换的基本单元,固定为16x16像素。
  • 每个Slice的开头都有一个Slice头(Slice Header)

Slice Header的关键信息:

  • slice_type: 定义了Slice的类型(I, P, B, SP, SI),决定了其编码方式。
  • frame_num: 帧号,用于参考帧管理。
  • pic_parameter_set_id: 指明当前Slice使用了哪个PPS。
  • 参考帧列表重排序 (Reference Picture List Reordering) 语法。
  • 加权预测 (Weighted Prediction) 参数。

NALU Payload

SODB

String Of Data Bits,称原始数据比特流,就是最原始的编码/压缩得到的VCL数据。

RBSP

Raw Byte Sequence Payload,又称原始字节序列载荷。在SODB的后面填加了结尾比特:一个bit“1”若干比特“0”,以便网络传输或存储时进行字节对齐

RBSP = SODB + RBSP Trailing BitsRBSP尾部补齐字节)

引入 RBSP Trailing Bits 做 8 位字节补齐。

EBSP

Encapsulated Byte Sequence Payload,称为扩展字节序列载荷。和 RBSP 关系如下:

EBSP = RBSP插入防竞争字节(0x03

防止竞争字节(0x03):H264会在NALU前插入StartCode的字节串(三字节或四字节,0x000001或0x00000001)来分割NALU。于是问题来了,如果RBSP中也包括了StartCode,就无法找到NALU的起始和结束,于是就引入了防止竞争字节(0x03):

编码时,扫描 RBSP,如果遇到连续两个0x00字节,就在后面添加防止竞争字节(0x03);解码时,同样扫描 EBSP,进行逆向操作即可。

这样RBSP中就不会存在StartCode了,解码器就能正确找到NALU的边界。

如果RBSP包含了0x000003,同样会插入0x03,变成0x00000303。解码时也能还原出正确的数据。

封装格式

解码器在解析码流时,首先要解决“一个NALU在哪里结束,下一个在哪里开始”的问题。H.264主要有两种封装格式来解决这个问题。

  1. Annex B 格式 (字节流格式)
    • 特征: 在每个NALU的前面插入起始码(Start Code)0x0000010x00000001
    • 应用: 广泛用于实时传输(如RTP流)。
  2. AVCC 格式 (也称MP4格式)
    • 特征: 在每个NALU的前面加上一个N字节(通常是4字节)的长度字段,指明了这个NALU的长度。
    • 应用: 广泛用于文件容器(如MP4, MKV, FLV)。