H.264码流结构

H.264 码流结构

概述

H.264 码流由一系列 NAL(Network Abstraction Layer,网络抽象层)单元组成。每个 NAL 单元包含一个完整的编码数据单元,可以是视频帧数据、参数集或其他控制信息。理解 H.264 码流结构对于视频编解码、传输和存储都至关重要。

NAL 单元结构

基本结构

NAL 单元是 H.264 码流的基本组成单位,每个 NAL 单元由以下部分组成:

[Start Code] [NAL Header] [NAL Payload]

1. Start Code(起始码)

  • Annex-B 格式:使用起始码分隔 NAL 单元
    • 3 字节起始码:0x00 0x00 0x01
    • 4 字节起始码:0x00 0x00 0x00 0x01(推荐,避免与数据冲突)
  • AVCC 格式:使用长度前缀(Length Prefix)代替起始码
    • 1 字节长度:0x000xFF(最大 255 字节)
    • 2 字节长度:0x00000xFFFF(最大 64KB)
    • 4 字节长度:0x000000000xFFFFFFFF(最大 4GB)

2. NAL Header(NAL 头)

  • 固定 1 字节,包含 NAL 单元类型和标志位
  • 结构:[F] [NRI] [Type]
    • F(Forbidden bit):1 位,通常为 0,表示无错误
    • NRI(NAL Reference IDC):2 位,表示 NAL 单元的重要性
      • 00:非参考帧,可丢弃
      • 01:参考帧,但非关键帧
      • 10:参考帧,重要
      • 11:关键帧(IDR),最重要
    • Type(NAL 单元类型):5 位,表示 NAL 单元类型(0-31)

3. NAL Payload(NAL 负载)

  • 包含实际的编码数据(如视频帧数据、参数集等)
  • 大小可变,取决于编码内容和码率

NAL Header 解析示例

// NAL Header 解析伪代码
uint8_t nal_header = 0x67;  // 示例:SPS 的 NAL Header

uint8_t forbidden_bit = (nal_header >> 7) & 0x01;      // 最高位
uint8_t nri = (nal_header >> 5) & 0x03;                 // 中间2位
uint8_t nal_type = nal_header & 0x1F;                   // 低5位

// 0x67 = 0110 0111
// forbidden_bit = 0
// nri = 3 (11)
// nal_type = 7 (SPS)

NAL 单元类型

H.264 标准定义了多种 NAL 单元类型,每种类型对应不同的数据内容:

NAL Type 类型名称 说明 NRI 典型值 应用场景
0 未指定 保留,不应使用
1 非IDR图像 普通视频帧(I/P/B帧) 01/10/11 视频数据
2 数据分区A 数据分区(已废弃) 已废弃
3 数据分区B 数据分区(已废弃) 已废弃
4 数据分区C 数据分区(已废弃) 已废弃
5 IDR图像 关键帧(Instantaneous Decoder Refresh) 11 随机访问点
6 SEI 补充增强信息 00 元数据、时间戳
7 SPS 序列参数集 11 解码配置
8 PPS 图像参数集 11 解码配置
9 访问单元分隔符 帧边界标记 00 帧同步
10 序列结束 序列结束标记 00 序列结束
11 流结束 流结束标记 00 流结束
12 填充数据 填充字节 00 对齐填充
13-23 保留 保留未使用
24-31 未指定 保留未使用

重要 NAL 单元类型详解

1. SPS(Sequence Parameter Set,序列参数集)

作用:包含整个视频序列的全局参数,解码器需要 SPS 才能正确解码视频。

包含信息

  • 编码 Profile 和 Level
  • 图像尺寸(宽度、高度)
  • 色度格式(4:2:0、4:2:2、4:4:4)
  • 帧率信息
  • GOP 结构参数
  • 参考帧数量
  • 熵编码方式(CAVLC/CABAC)

特点

  • NAL Type = 7
  • NRI = 11(最重要,不可丢弃)
  • 通常出现在码流开头,IDR 帧之前
  • 序列参数变化时需要发送新的 SPS

示例

Start Code: 00 00 00 01
NAL Header: 67 (0110 0111)
  - F = 0
  - NRI = 11 (最重要)
  - Type = 7 (SPS)
Payload: [SPS 数据...]

2. PPS(Picture Parameter Set,图像参数集)

作用:包含图像级别的解码参数,通常与 SPS 配合使用。

包含信息

  • 熵编码方式(CAVLC/CABAC)
  • 量化参数
  • 去块效应滤波器参数
  • 参考帧列表参数
  • 加权预测参数

特点

  • NAL Type = 8
  • NRI = 11(最重要,不可丢弃)
  • 通常出现在码流开头,IDR 帧之前
  • 一个 SPS 可以对应多个 PPS

示例

Start Code: 00 00 00 01
NAL Header: 68 (0110 1000)
  - F = 0
  - NRI = 11 (最重要)
  - Type = 8 (PPS)
Payload: [PPS 数据...]

3. IDR 帧(Instantaneous Decoder Refresh)

作用:关键帧,解码器可以从 IDR 帧开始独立解码,不依赖之前的帧。

特点

  • NAL Type = 5
  • NRI = 11(最重要,不可丢弃)
  • 是随机访问点,可以用于 Seek 操作
  • IDR 帧之前通常会重复发送 SPS/PPS(确保解码器能正确解码)
  • 所有参考帧缓冲区在 IDR 帧后清空

示例

Start Code: 00 00 00 01
NAL Header: 65 (0110 0101)
  - F = 0
  - NRI = 11 (最重要)
  - Type = 5 (IDR)
Payload: [IDR 帧数据...]

4. 非IDR图像(普通视频帧)

作用:包含实际的视频帧数据(I帧、P帧、B帧)。

特点

  • NAL Type = 1
  • NRI 值取决于帧类型:
    • I帧:NRI = 10 或 11
    • P帧:NRI = 01 或 10
    • B帧:NRI = 00 或 01
  • 包含 Slice 数据(一个或多个 Slice)

示例

Start Code: 00 00 00 01
NAL Header: 41 (0100 0001)  // P帧示例
  - F = 0
  - NRI = 01 (参考帧)
  - Type = 1 (非IDR图像)
Payload: [Slice 数据...]

5. SEI(Supplemental Enhancement Information,补充增强信息)

作用:包含额外的元数据信息,不影响解码,但可用于播放优化。

常见 SEI 类型

  • 用户数据 SEI:自定义数据(如时间戳、版权信息)
  • 缓冲周期 SEI:HRD(Hypothetical Reference Decoder)参数
  • 图像时序 SEI:帧率、时间戳信息
  • 恢复点 SEI:随机访问点信息

特点

  • NAL Type = 6
  • NRI = 00(可丢弃,不影响解码)
  • 可选,解码器可以忽略

码流格式

H.264 码流有两种主要格式:

1. Annex-B 格式

特点

  • 使用起始码(Start Code)分隔 NAL 单元
  • 起始码:0x00 0x00 0x010x00 0x00 0x00 0x01
  • 文件格式:.264.h264
  • 传输格式:RTP、TS 流

优点

  • 结构简单,易于解析
  • 支持流式传输
  • 广泛用于实时传输

缺点

  • 需要扫描起始码,解析相对复杂
  • 起始码可能与数据冲突(虽然概率很低)

示例码流

00 00 00 01 67 64 00 1E AC ...  // SPS
00 00 00 01 68 E9 78 2C 8B ...  // PPS
00 00 00 01 65 88 84 00 10 ...  // IDR 帧
00 00 00 01 41 9A 26 01 10 ...  // P 帧
00 00 00 01 01 9A 26 01 10 ...  // B 帧

2. AVCC 格式(MP4 格式)

特点

  • 使用长度前缀(Length Prefix)代替起始码
  • 长度前缀:1/2/4 字节(在容器中配置)
  • 文件格式:.mp4.mov.mkv
  • SPS/PPS 存储在容器元数据中(avcC box)

优点

  • 解析速度快(直接读取长度,无需扫描)
  • 适合随机访问(可以快速定位帧)
  • 容器格式支持完善

缺点

  • 需要容器格式支持
  • 不适合纯流式传输

示例码流(4字节长度前缀):

00 00 00 23 67 64 00 1E AC ...  // SPS (长度 0x23 = 35 字节)
00 00 00 05 68 E9 78 2C 8B ...  // PPS (长度 0x05 = 5 字节)
00 00 01 23 65 88 84 00 10 ...  // IDR 帧 (长度 0x123 = 291 字节)
00 00 00 45 41 9A 26 01 10 ...  // P 帧 (长度 0x45 = 69 字节)

格式转换

Annex-B → AVCC

  1. 移除起始码
  2. 添加长度前缀
  3. 将 SPS/PPS 提取到容器元数据

AVCC → Annex-B

  1. 移除长度前缀
  2. 添加起始码
  3. 将 SPS/PPS 插入到 IDR 帧之前

码流结构示例

完整的 H.264 码流结构(Annex-B 格式)

┌─────────────────────────────────────────┐
│  Start Code: 00 00 00 01               │
│  NAL Header: 67 (SPS)                   │
│  SPS Payload: [序列参数...]              │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│  Start Code: 00 00 00 01               │
│  NAL Header: 68 (PPS)                   │
│  PPS Payload: [图像参数...]              │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│  Start Code: 00 00 00 01               │
│  NAL Header: 06 (SEI, 可选)             │
│  SEI Payload: [补充信息...]              │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│  Start Code: 00 00 00 01               │
│  NAL Header: 65 (IDR 帧)                │
│  IDR Payload: [关键帧数据...]            │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│  Start Code: 00 00 00 01               │
│  NAL Header: 41 (P 帧)                  │
│  P Payload: [P帧数据...]                │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│  Start Code: 00 00 00 01               │
│  NAL Header: 01 (B 帧)                  │
│  B Payload: [B帧数据...]                │
└─────────────────────────────────────────┘
... (更多帧)

访问单元(Access Unit)结构

一个访问单元(Access Unit)对应一个完整的视频帧,可能包含多个 NAL 单元:

访问单元(一帧):
├─ SPS (可选,通常只在序列开始或参数变化时)
├─ PPS (可选,通常只在序列开始或参数变化时)
├─ SEI (可选)
├─ IDR/I/P/B 帧 (必需,一个或多个 Slice)
└─ 访问单元分隔符 (可选,NAL Type 9)

注意

  • IDR 帧前通常会重复发送 SPS/PPS(确保解码器能正确解码)
  • 一个帧可能包含多个 Slice(分片编码),每个 Slice 是一个独立的 NAL 单元
  • 访问单元分隔符用于明确标记帧边界

码流解析流程

Annex-B 格式解析

flowchart TD
    A[开始解析] --> B[查找起始码]
    B --> C{找到起始码?}
    C -->|否| D[继续扫描]
    D --> B
    C -->|是| E[读取 NAL Header]
    E --> F[解析 NAL Type]
    F --> G{类型判断}
    G -->|SPS| H[保存 SPS]
    G -->|PPS| I[保存 PPS]
    G -->|IDR/I/P/B| J[解码视频帧]
    G -->|SEI| K[处理元数据]
    G -->|其他| L[跳过或处理]
    H --> M[继续解析下一个 NAL]
    I --> M
    J --> M
    K --> M
    L --> M
    M --> B

解析步骤详解

1. 查找起始码

// 查找起始码伪代码
uint8_t* find_start_code(uint8_t* data, size_t size) {
    for (size_t i = 0; i < size - 3; i++) {
        // 查找 3 字节起始码
        if (data[i] == 0x00 && data[i+1] == 0x00 && data[i+2] == 0x01) {
            return data + i;
        }
        // 查找 4 字节起始码
        if (i < size - 4 && 
            data[i] == 0x00 && data[i+1] == 0x00 && 
            data[i+2] == 0x00 && data[i+3] == 0x01) {
            return data + i;
        }
    }
    return NULL;
}

2. 解析 NAL Header

// 解析 NAL Header
typedef struct {
    uint8_t forbidden_bit;  // 1 bit
    uint8_t nri;            // 2 bits
    uint8_t type;           // 5 bits
} NALHeader;

NALHeader parse_nal_header(uint8_t header_byte) {
    NALHeader nal;
    nal.forbidden_bit = (header_byte >> 7) & 0x01;
    nal.nri = (header_byte >> 5) & 0x03;
    nal.type = header_byte & 0x1F;
    return nal;
}

3. 处理不同类型的 NAL 单元

  • SPS/PPS:保存到解码器配置中
  • IDR 帧:清空参考帧缓冲区,开始新序列
  • 普通帧:根据帧类型进行解码
  • SEI:提取元数据信息(可选)

AVCC 格式解析

flowchart TD
    A[开始解析] --> B[读取长度前缀]
    B --> C[根据长度读取 NAL 单元]
    C --> D[解析 NAL Header]
    D --> E[处理 NAL 单元]
    E --> F{还有数据?}
    F -->|是| B
    F -->|否| G[解析完成]

解析步骤

  1. 读取长度前缀(1/2/4 字节,由容器配置决定)
  2. 根据长度读取完整的 NAL 单元
  3. 解析 NAL Header 和处理数据
  4. 重复直到数据结束

关键要点

1. SPS/PPS 的重要性

  • 必须保存:解码器必须保存 SPS/PPS 才能正确解码
  • 及时更新:当序列参数变化时,需要发送新的 SPS/PPS
  • IDR 前发送:IDR 帧前通常会重复发送 SPS/PPS,确保解码器能正确解码

2. IDR 帧的作用

  • 随机访问点:可以从 IDR 帧开始独立解码
  • 清空缓冲区:IDR 帧后所有参考帧缓冲区清空
  • Seek 支持:视频播放器的 Seek 操作通常定位到 IDR 帧

3. NRI 值的含义

  • NRI = 11:关键帧(IDR)或参数集(SPS/PPS),不可丢弃
  • NRI = 10:重要参考帧(I帧、重要P帧),不应丢弃
  • NRI = 01:参考帧(P帧),可以丢弃但会影响后续帧
  • NRI = 00:非参考帧(B帧)或可丢弃数据(SEI),可以安全丢弃

4. 码流格式选择

  • 实时传输(RTP、RTMP、WebRTC):使用 Annex-B 格式
  • 文件存储(MP4、MOV、MKV):使用 AVCC 格式
  • 流媒体(HLS、DASH):根据容器格式选择

5. 错误处理

  • 起始码冲突:虽然概率很低,但需要处理(使用 4 字节起始码可降低概率)
  • NAL 单元损坏:根据 NRI 值决定是否丢弃
  • 参数集丢失:无法解码,需要重新获取或使用默认参数

实际应用

FFmpeg 处理 H.264 码流

提取 SPS/PPS

# 从 MP4 文件提取 SPS/PPS
ffmpeg -i input.mp4 -vcodec copy -bsf:v h264_mp4toannexb -f h264 output.264

# 查看 NAL 单元信息
ffprobe -v error -show_entries packet=pts_time,dts_time,flags -select_streams v:0 input.mp4

转换格式

# Annex-B 转 AVCC(MP4)
ffmpeg -i input.264 -c:v copy output.mp4

# AVCC 转 Annex-B
ffmpeg -i input.mp4 -vcodec copy -bsf:v h264_mp4toannexb output.264

代码示例:解析 H.264 码流

// 简化的 H.264 码流解析示例
typedef struct {
    uint8_t* data;
    size_t size;
    size_t pos;
} H264Stream;

int parse_h264_stream(H264Stream* stream) {
    uint8_t* start_code = NULL;
    
    while ((start_code = find_start_code(stream->data + stream->pos, 
                                         stream->size - stream->pos)) != NULL) {
        // 跳过起始码
        stream->pos = (start_code - stream->data) + 4;
        
        // 查找下一个起始码或数据结束
        uint8_t* next_start = find_start_code(stream->data + stream->pos, 
                                              stream->size - stream->pos);
        size_t nal_size = (next_start ? (next_start - stream->data) : stream->size) - stream->pos;
        
        // 读取 NAL Header
        if (nal_size < 1) break;
        uint8_t nal_header = stream->data[stream->pos];
        NALHeader nal = parse_nal_header(nal_header);
        
        // 处理不同类型的 NAL 单元
        switch (nal.type) {
            case 7:  // SPS
                handle_sps(stream->data + stream->pos + 1, nal_size - 1);
                break;
            case 8:  // PPS
                handle_pps(stream->data + stream->pos + 1, nal_size - 1);
                break;
            case 5:  // IDR
                handle_idr(stream->data + stream->pos + 1, nal_size - 1);
                break;
            case 1:  // 非IDR图像
                handle_frame(stream->data + stream->pos + 1, nal_size - 1, nal.nri);
                break;
            default:
                // 其他类型
                break;
        }
        
        stream->pos += nal_size;
    }
    
    return 0;
}

相关资源

标准文档

  • ITU-T H.264:ITU-T Recommendation H.264 (Advanced Video Coding)
  • ISO/IEC 14496-10:ISO/IEC 14496-10 (MPEG-4 Part 10, AVC)

参考实现

  • x264:H.264 编码器参考实现
  • FFmpeg:H.264 编解码和格式转换
  • OpenH264:Cisco 开源的 H.264 编解码器

相关文档


最后更新:2026-01-20

留下评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Index