H.264 的 NALU(Network Abstraction Layer Unit)是视频数据在网络传输或存储中的基本单元,负责将 VCL(Video Coding Layer)生成的压缩数据封装为适合不同网络环境的格式。下面从 NALU 的结构组成、关键字段、类型分类及封装方式等方面详细解析。
一、NALU 的基本结构
NALU 由1 字节的头部(NAL Header) 和负载数据(RBSP) 组成,结构如下:
markup
+---------------+-------------------------------+
| NAL Header | RBSP (Raw Byte Sequence Payload)|
| (1字节) | |
+---------------+-------------------------------+
1. NAL Header(NAL 头部)
NAL 头部共 8 位,包含三个核心字段:
markup
+---------------+---------------+-----------------+
| forbidden_bit | nal_ref_idc | nal_unit_type |
| (1位) | (2位) | (5位) |
+---------------+---------------+-----------------+
forbidden_zero_bit(1 位) :
禁止位,固定为 0。若在接收端检测到该位为 1,表示 NALU 可能存在传输错误(如比特翻转),需丢弃该 NALU。
nal_ref_idc(2 位) :
参考重要性指示,取值范围为 0-3,值越大表示该 NALU 对解码的重要性越高:
0:表示该 NALU 不用于参考(如 B 帧数据),丢弃后不影响后续帧解码;
1-3:表示该 NALU 用于参考(如 I 帧、P 帧数据),丢弃可能导致错误传播。
nal_unit_type(5 位) :
指示 NALU 的类型,共 32 种(0-31),常见类型如下表:
nal_unit_type 值 | NALU 类型 | 说明 |
---|---|---|
0 | 未指定 | 保留,不使用 |
1 | 非 IDR 图像的片(Slice) | P 帧或 B 帧的 Slice 数据 |
2 | 数据分区 A | 用于分片编码,存放重要的运动信息 |
3 | 数据分区 B | 存放次要的运动信息 |
4 | 数据分区 C | 存放残差数据 |
5 | IDR 图像的片(Slice) | 立即刷新图像(关键帧)的 Slice 数据,解码时需清空参考帧缓冲区 |
6 | SEI(补充增强信息) | 包含额外信息(如时间戳、用户数据),不影响基本解码 |
7 | SPS(序列参数集) | 包含视频序列的全局参数(如分辨率、profile 等) |
8 | PPS(图像参数集) | 包含图像级参数(如量化参数、熵编码方式) |
9 | 访问单元分隔符 | 标记视频帧的开始 |
10 | 序列结束符 | 标记视频序列的结束 |
11 | 流结束符 | 标记整个码流的结束 |
12 | 填充数据 | 用于增加码流长度(如测试场景) |
13-23 | 保留 | 用于 H.264 的扩展功能 |
24-31 | 未指定 | 通常用于 RTP 等网络协议的封装 |
2. RBSP(Raw Byte Sequence Payload)
RBSP 是 NALU 的负载数据,包含 VCL 层的压缩信息(如 Slice 数据、参数集内容)。它由SODB(String of Data Bits) 经过处理后得到:
SODB:VCL 层输出的原始比特流(如预测残差、运动矢量等);
RBSP:在 SODB 末尾添加停止位(1 个 "1" 比特后跟若干 "0" 比特),使其字节对齐,形成 RBSP。
二、NALU 的封装与传输
在实际网络传输或文件存储中,NALU 需要通过特定方式分隔和标识。常见的封装方式有两种:
1. 基于起始码(Annex B 格式)
常用于文件存储(如.264、.avi 文件),特点是每个 NALU 前添加起始码作为分隔符:
短起始码:0x000001(3 字节);
长起始码:0x00000001(4 字节,通常用于 SPS/PPS 或码流开头)。
示例码流结构:
markup
[0x00000001][NAL Header + RBSP(SPS)][0x00000001][NAL Header + RBSP(PPS)]
[0x00000001][NAL Header + RBSP(IDR Slice)][0x000001][NAL Header + RBSP(P Slice)]...
2. 基于长度字段(AVCC 格式)
常用于流媒体传输(如 MP4、TS 文件或 RTP 包),特点是用4 字节的长度字段代替起始码,指示 NALU 的长度:
markup
[NALU长度(4字节)][NAL Header + RBSP][NALU长度(4字节)][NAL Header + RBSP]...
例如:
markup
[0x00000123][NAL Header + RBSP(SPS)][0x00000087][NAL Header + RBSP(PPS)]...
三、关键 NALU 类型详解
1. SPS(序列参数集,nal_unit_type=7)
SPS 是视频序列的全局配置,包含影响整个序列的参数,解析时需优先处理。常见参数如下:
markup
// SPS参数示例(部分关键参数)
profile_idc // 编码profile(如Baseline=66,Main=77,High=100)
level_idc // 编码level(如3.0=30,3.1=31)
seq_parameter_set_id // SPS的ID(用于关联PPS)
chroma_format_idc // 色度格式(如1=4:2:0,2=4:2:2,3=4:4:4)
bit_depth_luma_minus8 // 亮度位深度(通常为8)
bit_depth_chroma_minus8 // 色度位深度(通常为8)
log2_max_frame_num_minus4 // 最大帧号的对数(用于计算帧号范围)
pic_order_cnt_type // 图像顺序计数类型(0-2,控制POC的计算方式)
max_num_ref_frames // 最大参考帧数量
pic_width_in_mbs_minus1 // 视频宽度(以宏块为单位,实际宽度=(值+1)*16)
pic_height_in_map_units_minus1 // 视频高度(以宏块为单位)
frame_mbs_only_flag // 是否仅帧编码(0=支持帧/场混合,1=仅帧)
2. PPS(图像参数集,nal_unit_type=8)
PPS 定义单幅图像的参数,依赖于 SPS,常见参数如下:
markup
// PPS参数示例(部分关键参数)
pic_parameter_set_id // PPS的ID
seq_parameter_set_id // 关联的SPS的ID
entropy_coding_mode_flag // 熵编码方式(0=CAVLC,1=CABAC)
num_ref_idx_l0_default_active_minus1 // 默认的前向参考帧列表长度
num_ref_idx_l1_default_active_minus1 // 默认的后向参考帧列表长度
weighted_pred_flag // 是否使用加权预测(对P帧)
weighted_bipred_idc // 双向预测加权模式(0-2)
pic_init_qp_minus26 // 初始量化参数(QP=值+26)
deblocking_filter_control_present_flag // 是否存在去块滤波控制信息
3. IDR Slice(即时解码刷新,nal_unit_type=5)
IDR Slice 是一种特殊的 I Slice,属于关键帧:
解码 IDR Slice 时,解码器会清空所有参考帧缓冲区,确保后续帧的解码不依赖之前的错误帧,从而终止错误传播。
IDR Slice 必须包含完整的帧内预测信息,不依赖其他帧。
4. 非 IDR Slice(nal_unit_type=1)
包括 P Slice 和 B Slice:
P Slice:依赖前向参考帧(已解码的 I/P 帧)进行预测;
B Slice:依赖双向参考帧(前向和后向的 I/P 帧)进行预测,压缩效率更高。
5. SEI(补充增强信息,nal_unit_type=6)
携带与解码无关的辅助信息,常见类型:
时间戳信息(如 NTP 时间);
用户数据(如字幕、水印);
场景切换标记;
码流统计信息。
四、NALU 解析流程示例
以 Annex B 格式码流为例,解析 NALU 的伪代码如下:
python
def parse_nalu(stream):
nalu_list = []
while not end_of_stream(stream):
# 查找起始码
start_code_len = find_start_code(stream)
if start_code_len == 0:
break # 未找到起始码,结束解析
# 读取NAL头部
nalu_header = stream.read(1)
forbidden_bit = (nalu_header >> 7) & 0x01
nal_ref_idc = (nalu_header >> 5) & 0x03
nal_unit_type = nalu_header & 0x1F
# 读取RBSP(直到下一个起始码)
rbsp = read_until_next_start_code(stream)
# 处理防竞争字节(0x000003 → 0x0000)
rbsp = remove_emulation_prevention_bytes(rbsp)
# 存储NALU信息
nalu = {
'forbidden_bit': forbidden_bit,
'nal_ref_idc': nal_ref_idc,
'nal_unit_type': nal_unit_type,
'rbsp': rbsp
}
nalu_list.append(nalu)
return nalu_list
五、总结
NALU 作为 H.264 的核心数据单元,通过头部标识类型和重要性,负载存储压缩数据,实现了视频编码与网络传输的解耦。理解 NALU 结构是解析 H.264 码流的基础,尤其是 SPS/PPS 参数集和 IDR 帧的处理,直接影响解码器的初始化和错误恢复能力。在实际应用中,还需根据不同场景(如直播、存储)选择合适的封装方式(Annex B 或 AVCC)。