H264的NALU结构

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)。

相关推荐
步、步、为营7 小时前
.NET + WPF框架开发聊天、网盘、信息发布、视频播放功能
.net·wpf·音视频
愿你天黑有灯下雨有伞9 小时前
从数据库到播放器:Java视频续播功能完整实现解析
java·数据库·音视频
甲方求你学点技术吧11 小时前
8:从USB摄像头把声音拿出来--ALSA大佬登场!
linux·图像处理·ffmpeg·音视频
EasyCVR12 小时前
现场设备无法向视频汇聚EasyCVR视频融合平台推流的原因排查与解决过程
音视频
菜包eo1 天前
如何设置直播间的观看门槛,让直播间安全有效地运行?
前端·安全·音视频
王者鳜錸1 天前
使用Selenium自动化获取抖音创作者平台视频数据
selenium·自动化·音视频
却道天凉_好个秋1 天前
音视频学习(三十七):pts和dts
音视频·pts·dts
沐尘而生1 天前
【AI智能体】智能音视频-搭建可视化智能体
数据库·人工智能·ai作画·音视频·娱乐
子时不睡1 天前
【Datawhale AI 夏令营】 用AI做带货视频评论分析(一)
人工智能·深度学习·音视频