H.264 句法分析

1. 什么是句法元素

1.1 句法的概念

句法(Syntax) 是 H.264 标准定义的码流组织规则,规定了编码数据如何被序列化为二进制比特流,以及解码器如何从比特流中还原出编码数据。

可以把句法理解为一种”协议”或”格式约定”:

  • 编码端:按照句法规则将图像信息(预测模式、运动矢量、残差等)打包成二进制流
  • 解码端:按照相同的句法规则从二进制流中解析出图像信息

1.2 为什么需要句法

视频编码涉及大量信息需要传输:

编码一帧图像需要表达的信息:
┌─────────────────────────────────────────────────────┐
│ 1. 这是什么帧?(I/P/B 帧)                          │
│ 2. 用了什么预测方式?(帧内/帧间)                   │
│ 3. 参考了哪一帧?(参考帧索引)                      │
│ 4. 物体移动了多少?(运动矢量)                      │
│ 5. 预测误差是多少?(残差系数)                      │
│ 6. 量化参数是多少?(QP)                            │
│ 7. ... 更多参数                                     │
└─────────────────────────────────────────────────────┘

问题:这些信息如何组织成一个有序的二进制流?
答案:通过句法定义每个信息的存储位置和编码方式

句法的作用

作用 说明
标准化 确保不同编码器产生的码流可被任何解码器解码
压缩 通过高效的数据组织方式减少存储空间
容错 通过分层结构支持错误检测和恢复
随机访问 提供关键点标记,支持 Seek 操作

1.3 句法元素的位置

句法元素存在于 H.264 码流的各个层次中:

H.264 文件/传输流
        │
        ▼
┌───────────────────────────────────────────────────────┐
│                    NAL 单元序列                        │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐  │
│  │ NAL #1  │  │ NAL #2  │  │ NAL #3  │  │   ...   │  │
│  │  (SPS)  │  │  (PPS)  │  │ (IDR)   │  │         │  │
│  └────┬────┘  └────┬────┘  └────┬────┘  └─────────┘  │
└───────┼────────────┼────────────┼────────────────────┘
        │            │            │
        ▼            ▼            ▼
┌───────────────┐ ┌────────────┐ ┌──────────────────┐
│  NAL Header   │ │ NAL Header │ │    NAL Header    │
│  (1字节句法)   │ │  (1字节)   │ │     (1字节)      │
├───────────────┤ ├────────────┤ ├──────────────────┤
│  SPS 句法元素  │ │ PPS 句法   │ │  Slice Header    │
│  - profile_idc │ │ 元素       │ │  句法元素        │
│  - level_idc   │ │ - pps_id   │ │  - slice_type    │
│  - width/height│ │ - qp_init  │ │  - frame_num     │
│  - ...         │ │ - ...      │ │  - ...           │
│                │ │            │ ├──────────────────┤
│                │ │            │ │  宏块句法元素    │
│                │ │            │ │  - mb_type       │
│                │ │            │ │  - mvds          │
│                │ │            │ │  - residual      │
└───────────────┘ └────────────┘ └──────────────────┘

1.4 句法的组织形式

H.264 句法采用分层结构,每一层负责不同粒度的信息:

flowchart TB
    subgraph 码流
        A[NAL 单元]
    end

    A --> B{NAL Type}

    B -->|7| C[SPS<br>序列参数]
    B -->|8| D[PPS<br>图像参数]
    B -->|5| E[IDR Slice<br>关键帧]
    B -->|1| F[Non-IDR Slice<br>非关键帧]

    C --> G[整个视频序列的参数<br>分辨率、帧率、Profile...]
    D --> H[图像级参数<br>QP、熵编码模式...]
    E --> I[Slice Header + 宏块数据]
    F --> I

    I --> J[宏块层<br>预测模式、运动矢量、残差]

句法元素编码方式

编码方式 符号 说明
固定长度 u(n) n 位固定长度,直接读取
固定长度(1位) u(1) 单个标志位,0 或 1
无符号指数哥伦布 ue(v) 变长编码,适合小值概率高的情况
有符号指数哥伦布 se(v) 同上,支持负数
指数哥伦布映射 me(v) 映射到预定义表
截断指数哥伦布 te(v) 有上限的指数哥伦布,节省比特
映射指数哥伦布 me(v) CodedBlockPattern 等使用

各编码方式详解

u(n) - 固定长度编码

直接读取 n 位,按二进制解析为无符号整数

示例: u(8) 读取 1 字节
  0x64 → profile_idc = 100

示例: u(1) 读取 1 位标志
  1 → entropy_coding_mode_flag = true

ue(v) - 无符号指数哥伦布编码

编码规则:
codeNum = 0:  编码 1           → 1 bit
codeNum = 1:  编码 010         → 3 bits
codeNum = 2:  编码 011         → 3 bits
codeNum = 3:  编码 00100       → 5 bits
codeNum = 4:  编码 00101       → 5 bits
codeNum = 5:  编码 00110       → 5 bits
codeNum = 6:  编码 00111       → 5 bits
codeNum = 7:  编码 0001000     → 7 bits
...

规律: value = 2^leadingZeroBits - 1 + read_bits(leadingZeroBits)

特点:
- 小值用短码,大值用长码
- 适合大多数句法元素值较小的情况
- 编码长度 = 2 × floor(log2(codeNum+1)) + 1

se(v) - 有符号指数哥伦布编码

将 ue(v) 的 codeNum 映射为有符号值:

codeNum  →  value
   0     →    0
   1     →    1
   2     →   -1
   3     →    2
   4     →   -2
   5     →    3
   6     →   -3
   ...

映射公式:
  value = (-1)^(codeNum+1) × ceil(codeNum / 2)

用途: 运动矢量差值 (MVD)、QP 差值等可正可负的值

te(v) - 截断指数哥伦布编码

当值的范围有上限时使用,节省比特

如果上限 max = 1:
  直接用 u(1) 编码

如果上限 max > 1:
  使用 ue(v),但如果编码后值 > max,则无效

示例: 帧内预测模式选择,最多 8 种
  用 te(v) 编码,上限为 7

me(v) - 映射指数哥伦布编码

将 codeNum 映射到预定义的表

示例: CodedBlockPattern (CBP) 映射
  不同的宏块类型有不同的 CBP 表

mb_type = I_4x4 时:
  codeNum → CBP_luma + CBP_chroma 组合

编码方式选择原则

数据类型 推荐编码方式 原因
标志位 u(1) 只有 0/1 两种值
固定范围枚举 u(n) 值范围固定,直接编码最高效
小值概率高的整数 ue(v) 变长编码,小值用短码
可正可负的小值 se(v) 支持有符号,小值高效
有上限的值 te(v) 节省比特
复杂映射 me(v) 需要查表映射

1.5 句法拆解实例

下面以一个实际的 H.264 码流片段为例,演示句法分析过程。

原始十六进制数据(一帧 IDR 图像的开头):

00 00 00 01 67 64 00 1F AC D9 40 50 05 BB 01 10
00 00 00 01 68 EE 3C 80
00 00 00 01 65 88 80 40 00 5F FE FD F8 ...
│           │  │
│           │  └─ NAL Header + RBSP
│           └──── NAL 单元起始码
└──────────── 起始码 (4 字节)

第一步:识别 NAL 单元边界

码流结构:
┌─────────────┬───────────────────┐
│  起始码      │    NAL 单元内容    │
│  00 00 00 01│ 67 64 00 1F ...   │  ← SPS
├─────────────┼───────────────────┤
│  00 00 00 01│ 68 EE 3C 80       │  ← PPS
├─────────────┼───────────────────┤
│  00 00 00 01│ 65 88 80 40 ...   │  ← IDR Slice
└─────────────┴───────────────────┘

第二步:解析 NAL Header

SPS 的 NAL Header: 0x67 = 0110 0111
┌─┬────┬─────────┐
│F│NRI │  Type   │
│0│ 11 │ 00111   │
└─┴────┴─────────┘

F = 0      → 正常,无错误
NRI = 3    → 重要,不可丢弃
Type = 7   → SPS(序列参数集)

PPS 的 NAL Header: 0x68 = 0110 1000
Type = 8   → PPS(图像参数集)

IDR 的 NAL Header: 0x65 = 0110 0101
Type = 5   → IDR Slice(关键帧)

第三步:解析 SPS 句法元素

SPS 数据: 64 00 1F AC D9 40 50 05 BB 01 10 ...

字节拆解:
64       → profile_idc = 100 (High Profile)
00       → constraint_set flags = 0
1F       → level_idc = 31 (Level 3.1)

后续字节使用指数哥伦布编码:
AC D9 40 ... → 需要按 ue(v) 逐位解析

解析结果:
┌──────────────────────────┬─────────┬───────────────────┐
│ 句法元素                  │ 值       │ 含义              │
├──────────────────────────┼─────────┼───────────────────┤
│ profile_idc              │ 100     │ High Profile      │
│ level_idc                │ 31      │ Level 3.1         │
│ seq_parameter_set_id     │ 0       │ SPS ID = 0        │
│ chroma_format_idc        │ 1       │ 4:2:0 色度格式    │
│ log2_max_frame_num_minus4│ 4       │ frame_num 用 8 位  │
│ pic_order_cnt_type       │ 0       │ POC 类型 0        │
│ max_num_ref_frames       │ 4       │ 最多 4 个参考帧    │
│ pic_width_in_mbs_minus1  │ 119     │ 宽度 = 1920 像素  │
│ pic_height_in_map_units  │ 67      │ 高度 = 1080 像素  │
│ frame_mbs_only_flag      │ 1       │ 逐行扫描          │
└──────────────────────────┴─────────┴───────────────────┘

计算分辨率:
宽度 = (119 + 1) × 16 = 1920 像素
高度 = (67 + 1) × 16 = 1080 像素

第四步:解析 Slice Header

IDR Slice 数据: 88 80 40 00 5F ...

88 → first_mb_in_slice + slice_type 组合
     first_mb_in_slice = 0 (从第一个宏块开始)
     slice_type = 7 (I 帧, 当前帧编号)

80 → pic_parameter_set_id = 0 (引用 PPS #0)

40 → frame_num = 0 (IDR 帧, frame_num 重置)

解析结果:
┌──────────────────────────┬─────────┬───────────────────┐
│ 句法元素                  │ 值       │ 含义              │
├──────────────────────────┼─────────┼───────────────────┤
│ first_mb_in_slice        │ 0       │ 从帧首部开始      │
│ slice_type               │ 7 (I)   │ I 帧 (帧内编码)   │
│ pic_parameter_set_id     │ 0       │ 引用 PPS #0       │
│ frame_num                │ 0       │ IDR 帧, 编号 0    │
│ idr_pic_id               │ 0       │ IDR 图像标识      │
│ slice_qp_delta           │ X       │ QP 调整值         │
└──────────────────────────┴─────────┴───────────────────┘

第五步:解析宏块数据

Slice Header 之后的字节是宏块数据:

每个宏块包含:
┌────────────────────────────────────────────────┐
│ mb_type (宏块类型)                              │
│   → 决定后续句法元素的种类                      │
├────────────────────────────────────────────────┤
│ 如果是帧内预测:                                 │
│   - Intra4x4PredMode[] 或 Intra16x16PredMode   │
│   - CBP (Coded Block Pattern)                  │
├────────────────────────────────────────────────┤
│ 如果是帧间预测:                                 │
│   - ref_idx (参考帧索引)                        │
│   - mvd (运动矢量差值)                          │
│   - CBP                                        │
├────────────────────────────────────────────────┤
│ 残差数据 (如果 CBP 指示有系数):                  │
│   - residual_block() 系数                      │
└────────────────────────────────────────────────┘

I 帧第一个宏块解析示例:
mb_type = 0 (I_NxN) → 使用 4×4 帧内预测
后续需要解析:
- 16 个 4×4 子块的预测模式
- 残差系数

完整解析流程图

flowchart TB
    A[读取码流] --> B[查找起始码 00 00 00 01]
    B --> C[读取 NAL Header 1 字节]
    C --> D{NAL Type?}

    D -->|7 SPS| E[解析 SPS 句法元素<br>获取分辨率/帧率等]
    D -->|8 PPS| F[解析 PPS 句法元素<br>获取 QP/熵编码模式等]
    D -->|5 IDR| G[解析 IDR Slice]
    D -->|1 Non-IDR| H[解析普通 Slice]

    E --> I[保存参数供后续使用]
    F --> I

    G --> J[解析 Slice Header]
    H --> J

    J --> K[逐个解析宏块]
    K --> L[解析 mb_type]
    L --> M{帧内/帧间?}

    M -->|帧内| N[解析预测模式 + 残差]
    M -->|帧间| O[解析运动矢量 + 残差]

    N --> P[重建像素]
    O --> P

    P --> Q{还有宏块?}
    Q -->|是| K
    Q -->|否| R[完成一帧解码]

通过这个实例可以看到:

  1. 句法元素是分层组织的:从 NAL → Slice → 宏块 → 块
  2. 每个层次有特定的句法定义:规定了该层需要传输哪些信息
  3. 解析是递归的:外层句法元素的值决定内层句法的结构
  4. 编码方式多样:固定长度、指数哥伦布等,根据数据特性选择

2. 句法层次结构

H.264 采用分层句法结构,从上到下依次为:

┌─────────────────────────────────────┐
│           序列层 (Sequence)          │
│  ┌───────────────────────────────┐  │
│  │        图像层 (Picture)        │  │
│  │  ┌─────────────────────────┐  │  │
│  │  │      片层 (Slice)        │  │  │
│  │  │  ┌───────────────────┐  │  │  │
│  │  │  │   宏块层 (MB)      │  │  │  │
│  │  │  │ ┌───────────────┐ │  │  │  │
│  │  │  │ │  子宏块/块层   │ │  │  │  │
│  │  │  │ └───────────────┘ │  │  │  │
│  │  │  └───────────────────┘  │  │  │
│  │  └─────────────────────────┘  │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

2.1 各层作用

层次 作用 主要句法元素
序列层 定义整个视频序列的参数 SPS、PPS
图像层 定义单帧图像的编码方式 Picture Header、Slice Header
片层 定义一组宏块的编码方式 Slice Header、宏块数据
宏块层 定义 16×16 像素块的编码 宏块类型、预测模式、残差
块层 定义子块的变换量化数据 残差系数、CBP

3. NAL 单元结构

3.1 NAL 单元格式

H.264 码流由 NAL(Network Abstraction Layer)单元组成:

NAL 单元结构:
┌────────────┬──────────────────────────┐
│ NAL Header │      RBSP Payload        │
│  (1 字节)   │       (变长)             │
└────────────┴──────────────────────────┘

NAL Header 结构 (1 字节):
┌─┬─────┬───────────┐
│0│1│2│3│4│5│6│7│8│
├─┼─────┼───────────┤
│F│NRI  │  Type     │
└─┴─────┴───────────┘

F (forbidden_zero_bit): 禁止位,必须为 0
NRI (nal_ref_idc): 2 bits,重要性指示
    - 00: 可丢弃(无参考价值)
    - 01-11: 重要性递增,不可随意丢弃
Type (nal_unit_type): 5 bits,NAL 单元类型

3.2 NAL 类型定义

Type 名称 NRI 说明
1 非 IDR 切片 2-3 P/B 帧的编码数据
2 切片数据分区 A 2 数据分区模式(较少使用)
3 切片数据分区 B 2 数据分区模式
4 切片数据分区 C 2 数据分区模式
5 IDR 切片 3 I 帧(即时解码刷新)
6 SEI 0 补充增强信息
7 SPS 3 序列参数集
8 PPS 3 图像参数集
9 AUD 0 访问单元分隔符
10 序列结束 0 表示序列结束
11 流结束 0 表示流结束
12 填充数据 0 填充字节

3.3 起始码与访问单元

Annex B 格式(裸流格式):

起始码 + NAL 单元序列:

┌──────────┬──────────────┬──────────┬──────────────┐
│ 0x000001 │   NAL Unit   │ 0x000001 │   NAL Unit   │
│ (起始码)  │              │ (起始码)  │              │
└──────────┴──────────────┴──────────┴──────────────┘

起始码:
- 0x 00 00 01 (3 字节)
- 0x 00 00 00 01 (4 字节,用于随机访问点)

防竞争 (Emulation Prevention):
- 如果 RBSP 中出现 0x 00 00 00/01/02/03,插入 0x03
- 0x 00 00 03 00 → 解码时移除 0x03 → 0x 00 00 00

AVCC 格式(MP4 容器):

┌────────────┬──────────────┐
│ Length (4B)│   NAL Unit   │
│ NAL 长度    │              │
└────────────┴──────────────┘

每个 NAL 前面是 4 字节大端序长度,无起始码

4. SPS(序列参数集)

4.1 SPS 作用

SPS 包含整个视频序列的公共参数,一个序列中所有图像共享同一 SPS。

4.2 主要句法元素

seq_parameter_set_rbsp() {
    profile_idc                    // u(8)  Profile 标识
    constraint_set0_flag           // u(1)  约束标志 0
    constraint_set1_flag           // u(1)  约束标志 1
    constraint_set2_flag           // u(1)  约束标志 2
    constraint_set3_flag           // u(1)  约束标志 3
    constraint_set4_flag           // u(1)  约束标志 4
    constraint_set5_flag           // u(1)  约束标志 5
    reserved_zero_2bits            // u(2)  保留位
    level_idc                      // u(8)  Level 标识
    seq_parameter_set_id           // ue(v) SPS ID

    // 高级 Profile 扩展
    if (profile_idc == 100 || ...) {
        chroma_format_idc          // ue(v) 色度格式
        bit_depth_luma_minus8      // ue(v) 亮度位深
        bit_depth_chroma_minus8    // ue(v) 色度位深
        qpprime_y_zero_transform_bypass_flag  // u(1)
        seq_scaling_matrix_present_flag       // u(1)
    }

    log2_max_frame_num_minus4      // ue(v) frame_num 位数
    pic_order_cnt_type             // ue(v) POC 类型

    if (pic_order_cnt_type == 0)
        log2_max_pic_order_cnt_lsb_minus4  // ue(v)

    if (pic_order_cnt_type == 1) {
        delta_pic_order_always_zero_flag    // u(1)
        offset_for_non_ref_pic              // se(v)
        offset_for_top_to_bottom_field      // se(v)
        num_ref_frames_in_pic_order_cnt_cycle // ue(v)
        for (i = 0; i < ...; i++)
            offset_for_ref_frame[i]         // se(v)
    }

    max_num_ref_frames             // ue(v) 最大参考帧数
    gaps_in_frame_num_value_allowed_flag  // u(1)
    pic_width_in_mbs_minus1        // ue(v) 图像宽度(MB) - 1
    pic_height_in_map_units_minus1 // ue(v) 图像高度 - 1
    frame_mbs_only_flag            // u(1)  是否只有帧宏块

    if (!frame_mbs_only_flag)
        mb_adaptive_frame_field_palette_flag  // u(1)

    direct_8x8_inference_flag      // u(1)
    frame_cropping_flag            // u(1)  是否裁剪

    if (frame_cropping_flag) {
        frame_crop_left_offset     // ue(v)
        frame_crop_right_offset    // ue(v)
        frame_crop_top_offset      // ue(v)
        frame_crop_bottom_offset   // ue(v)
    }

    vui_parameters_present_flag    // u(1)  VUI 存在标志
    if (vui_parameters_present_flag)
        vui_parameters()           // VUI 参数
}

4.3 关键参数解析

Profile IDC 解读

profile_idc Profile 名称
66 Baseline
77 Main
88 Extended
100 High
110 High 10
122 High 4:2:2
244 High 4:4:4

分辨率计算

图像宽度 (像素) = (pic_width_in_mbs_minus1 + 1) × 16

图像高度 (像素) = (pic_height_in_map_units_minus1 + 1) × 16
                  × (2 - frame_mbs_only_flag)

示例:
pic_width_in_mbs_minus1 = 119  → 宽度 = 120 × 16 = 1920
pic_height_in_map_units_minus1 = 67  → 高度 = 68 × 16 = 1080
frame_mbs_only_flag = 1  → 逐行扫描

Frame Num 位数

frame_num 位数 = log2_max_frame_num_minus4 + 4

示例:
log2_max_frame_num_minus4 = 0  → frame_num 用 4 bits (0-15)
log2_max_frame_num_minus4 = 4  → frame_num 用 8 bits (0-255)
log2_max_frame_num_minus4 = 12 → frame_num 用 16 bits (0-65535)

5. PPS(图像参数集)

5.1 PPS 作用

PPS 包含单帧图像的编码参数,多个图像可共享同一 PPS。

5.2 主要句法元素

pic_parameter_set_rbsp() {
    pic_parameter_set_id           // ue(v) PPS ID
    seq_parameter_set_id           // ue(v) 引用的 SPS ID
    entropy_coding_mode_flag       // u(1)  熵编码模式
                                   //       0=CAVLC, 1=CABAC
    bottom_field_pic_order_in_frame_present_flag  // u(1)
    num_slice_groups_minus1        // ue(v) 片组数 - 1

    if (num_slice_groups_minus1 > 0) {
        slice_group_map_type       // ue(v) 片组映射类型
        // 片组相关参数...
    }

    num_ref_idx_l0_default_active_minus1  // ue(v) List0 参考帧数
    num_ref_idx_l1_default_active_minus1  // ue(v) List1 参考帧数
    weighted_pred_flag             // u(1)  加权预测
    weighted_bipred_idc            // u(2)  B 帧加权预测
    pic_init_qp_minus26            // se(v) 初始 QP - 26
    pic_init_qs_minus26            // se(v) SP/SI 帧初始 QS
    chroma_qp_index_offset         // se(v) 色度 QP 偏移
    deblocking_filter_control_present_flag  // u(1) 去块滤波控制
    constrained_intra_pred_flag    // u(1) 受限帧内预测
    redundant_pic_cnt_present_flag // u(1) 冗余图像计数
}

5.3 关键参数说明

熵编码模式

entropy_coding_mode_flag:
  0 → CAVLC (Baseline Profile 使用)
  1 → CABAC (Main/High Profile 使用)

初始 QP

Slice QP = pic_init_qp_minus26 + 26 + slice_qp_delta

示例:
pic_init_qp_minus26 = 0  → 基准 QP = 26
slice_qp_delta = -4     → 实际 QP = 22

6. Slice Header

6.1 Slice 类型

slice_type 名称 说明
0, 5 P Slice 使用前向预测
1, 6 B Slice 使用双向预测
2, 7 I Slice 只使用帧内预测
3, 8 SP Slice 切换 P 帧
4, 9 SI Slice 切换 I 帧

注:0-4 用于后续帧,5-9 用于当前帧

6.2 Slice Header 句法

slice_header() {
    first_mb_in_slice              // ue(v) 片中第一个宏块地址
    slice_type                     // ue(v) 片类型
    pic_parameter_set_id           // ue(v) 引用的 PPS ID
    frame_num                      // u(v)  帧号

    if (!frame_mbs_only_flag) {
        field_pic_flag             // u(1)  场图像标志
        if (field_pic_flag)
            bottom_field_flag      // u(1)  底场标志
    }

    if (nal_unit_type == 5)        // IDR 帧
        idr_pic_id                 // ue(v) IDR 图像 ID

    if (pic_order_cnt_type == 0) {
        pic_order_cnt_lsb          // u(v)  POC 低位
        if (pic_order_present_flag)
            delta_pic_order_cnt_bottom  // se(v)
    }

    if (pic_order_cnt_type == 1 && !delta_pic_order_always_zero_flag) {
        delta_pic_order_cnt[0]     // se(v)
        delta_pic_order_cnt[1]     // se(v)
    }

    if (redundant_pic_cnt_present_flag)
        redundant_pic_cnt          // ue(v)

    if (slice_type == B)
        direct_spatial_mv_pred_flag  // u(1)

    if (slice_type == P || slice_type == SP || slice_type == B) {
        num_ref_idx_active_override_flag  // u(1)
        if (num_ref_idx_active_override_flag) {
            num_ref_idx_l0_active_minus1  // ue(v)
            if (slice_type == B)
                num_ref_idx_l1_active_minus1  // ue(v)
        }
    }

    // 参考帧列表重排序
    ref_pic_list_modification()

    // 加权预测参数
    if (weighted_pred_flag || (weighted_bipred_idc == 1 && slice_type == B))
        pred_weight_table()

    // 参考帧标记
    if (nal_ref_idc != 0)
        dec_ref_pic_marking()

    if (entropy_coding_mode_flag && slice_type != I && slice_type != SI)
        cabac_init_idc             // ue(v)

    slice_qp_delta                 // se(v) QP 增量

    if (deblocking_filter_control_present_flag) {
        disable_deblocking_filter_idc  // ue(v)
        if (disable_deblocking_filter_idc != 1) {
            slice_alpha_c0_offset_div2  // se(v)
            slice_beta_offset_div2      // se(v)
        }
    }
}

6.3 关键参数解析

first_mb_in_slice

表示该片第一个宏块在一帧中的位置(光栅扫描顺序)

first_mb_in_slice = 0   → 从帧的左上角开始
first_mb_in_slice = 120 → 从第 121 个宏块开始

计算宏块坐标:
mb_x = first_mb_in_slice % (pic_width_in_mbs)
mb_y = first_mb_in_slice / (pic_width_in_mbs)

frame_num

帧号,用于参考帧管理和解码顺序

特性:
- 范围: 0 ~ 2^(log2_max_frame_num_minus4+4) - 1
- IDR 帧后重置为 0
- 循环使用,超过最大值后回到 0

IDR 识别

IDR (Instantaneous Decoding Refresh) 帧:
- nal_unit_type == 5
- 之后的所有帧不参考 IDR 之前的帧
- 用于随机访问点(Seek 点)

7. 宏块层句法

7.1 宏块头

macroblock_layer() {
    mb_type                        // ue(v) 宏块类型

    if (mb_type == I_PCM) {
        // PCM 模式:直接传输原始像素
        pcm_alignment_zero_bit     // 填充对齐
        pcm_byte[i][j]             // 原始像素值
        return
    }

    // 宏块类型决定后续句法
    if (mb_type != I_NxN)
        Intra16x16_Pred_Mode       // u(2)  16x16 帧内预测模式
    else
        Intra4x4_Pred_Mode[i]      // 帧内 4x4 预测模式

    // 参考帧索引(帧间预测)
    if (MbPartPredMode != Intra) {
        for (each reference index)
            ref_idx_l0[i]          // te(v) List0 参考索引
        if (slice_type == B)
            ref_idx_l1[i]          // te(v) List1 参考索引
    }

    // 运动矢量
    if (MbPartPredMode != Intra) {
        for (each motion vector)
            mvd_l0[i][j]           // se(v) 运动矢量差值
        if (slice_type == B)
            mvd_l1[i][j]           // se(v)
    }

    // 残差
    residual()
}

7.2 宏块类型(I 帧)

I_Slice 中的 mb_type

mb_type 名称 说明
0 I_NxN 使用 4×4 或 8×8 帧内预测
1 I_16x16_0_0_0 16×16 预测,CBP=0
2 I_16x16_1_0_0 16×16 预测,模式 1
24 I_PCM 原始像素直接传输

I_16x16 类型编码

I_16x16_X_Y_Z:
  X = 帧内预测模式 (0-3)
  Y = luma 16×16 CBP (0-2)
  Z = chroma CBP (0-1)

预测模式:
  0 = Vertical (垂直)
  1 = Horizontal (水平)
  2 = DC
  3 = Plane (平面)

7.3 宏块类型(P 帧)

P_Slice 中的 mb_type

mb_type 名称 说明
0 P_L0_16x16 1 个 16×16 前向预测
1 P_L0_L0_16x8 2 个 16×8 分割
2 P_L0_L0_8x16 2 个 8×16 分割
3 P_8x8 8×8 分割(进一步细分)
4 P_8x8ref0 8×8 分割(无参考)
5+ Intra 使用帧内预测

7.4 子宏块类型

当 mb_type = P_8x8 时,每个 8×8 块可进一步细分:

sub_mb_type:
┌────────────┬───────────────────────┐
│ sub_type   │ 说明                   │
├────────────┼───────────────────────┤
│ 0          │ P_L0_8x8 (不细分)      │
│ 1          │ P_L0_8x4 (上下)        │
│ 2          │ P_L0_4x8 (左右)        │
│ 3          │ P_L0_4x4 (四等分)      │
└────────────┴───────────────────────┘

8. 残差数据句法

8.1 残差层结构

residual() {
    // 亮度残差
    if (CbPatternLuma & 1)
        residual_block(0, 0)  // 亮度 DC 系数(16x16 模式)

    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++)
            if (CbPatternLuma & (1 << (i*4+j)))
                residual_block(i*4+j, 0)  // 亮度 AC

    // 色度残差
    for (iCbCr = 0; iCbCr < 2; iCbCr++) {
        if (CbPatternChroma & 1)
            residual_block(0, iCbCr+1)  // 色度 DC

        for (i = 0; i < 2; i++)
            for (j = 0; j < 2; j++)
                if (CbPatternChroma & (1 << (i*2+j)))
                    residual_block(i*2+j+1, iCbCr+1)  // 色度 AC
    }
}

8.2 Coded Block Pattern (CBP)

CBP 指示哪些块有非零残差系数:

CBP 编码 (帧内 16x16 以外):
┌────────────────────────────────────┐
│  bits 0-3: 亮度 4 个 8x8 块        │
│  bits 4-5: 色度 Cb/Cr              │
│  bit  6:   色度 DC                 │
└────────────────────────────────────┘

示例:
CBP = 0x1F = 00 011111
       │     │  ││││
       │     │  │││└─ 亮度块 0 有系数
       │     │  ││└── 亮度块 1 有系数
       │     │  │└─── 亮度块 2 有系数
       │     │  └──── 亮度块 3 有系数
       │     └─────── 色度 AC 有系数
       └───────────── 色度 DC 有系数

8.3 残差系数编码(CAVLC)

residual_block_cavlc() {
    // 1. 非零系数总数和拖尾 1 个数
    coeff_token                   // 根据 nC 选择码表

    // 2. 拖尾 1 的符号
    for (i = 0; i < trailing_ones; i++)
        trailing_ones_sign_flag   // u(1)

    // 3. 剩余非零系数的幅值
    for (i = 0; i < total_coeff - trailing_ones; i++) {
        level_prefix              // u(v) 前缀
        level_suffix              // u(v) 后缀
    }

    // 4. 最后一个非零系数前的零个数
    if (total_coeff < block_len)
        total_zeros               // 编码零的总数

    // 5. 每个非零系数前的连续零
    for (i = 0; i < total_coeff - 1; i++)
        run_before[i]             // 编码游程
}

系数扫描顺序

4×4 块的 Zig-Zag 扫描:

  0  1  2  3
  4  5  6  7
  8  9 10 11
 12 13 14 15

扫描顺序: 0→1→4→8→5→2→3→6→9→12→13→10→7→11→14→15

低频系数在前,高频系数在后
便于利用高频系数为零的特性

9. VUI 参数

9.1 VUI 作用

VUI(Video Usability Information)提供视频显示相关的参数,用于播放器正确显示视频。

9.2 主要句法元素

vui_parameters() {
    aspect_ratio_info_present_flag   // u(1)

    if (aspect_ratio_info_present_flag) {
        aspect_ratio_idc             // u(8)
        if (aspect_ratio_idc == 255) {
            sar_width                // u(16)
            sar_height               // u(16)
        }
    }

    overscan_info_present_flag       // u(1)
    if (overscan_info_present_flag)
        overscan_appropriate_flag    // u(1)

    video_signal_type_present_flag   // u(1)
    if (video_signal_type_present_flag) {
        video_format                 // u(3)
        video_full_range_flag        // u(1)
        colour_description_present_flag  // u(1)
        if (colour_description_present_flag) {
            colour_primaries         // u(8)
            transfer_characteristics // u(8)
            matrix_coefficients      // u(8)
        }
    }

    chroma_loc_info_present_flag     // u(1)
    if (chroma_loc_info_present_flag) {
        chroma_sample_loc_type_top_field    // ue(v)
        chroma_sample_loc_type_bottom_field // ue(v)
    }

    timing_info_present_flag         // u(1)
    if (timing_info_present_flag) {
        num_units_in_tick            // u(32)
        time_scale                   // u(32)
        fixed_frame_rate_flag        // u(1)
    }

    // 其他参数...
}

9.3 帧率计算

帧率 = time_scale / (2 × num_units_in_tick)

示例:
num_units_in_tick = 1000  (时间单位为 1/27000000 秒)
time_scale = 27000000

帧率 = 27000000 / (2 × 1000) = 13500 / 1000 = 13.5 fps

常见配置:
┌─────────────┬─────────────┬─────────────┐
│ num_units   │ time_scale  │ 帧率        │
├─────────────┼─────────────┼─────────────┤
│ 1001        │ 60000       │ 29.97 fps   │
│ 1000        │ 60000       │ 30 fps      │
│ 1001        │ 50000       │ 24.975 fps  │
│ 1000        │ 50000       │ 25 fps      │
│ 1001        │ 24000       │ 11.988 fps  │
│ 1000        │ 24000       │ 12 fps      │
└─────────────┴─────────────┴─────────────┘

9.4 宽高比

aspect_ratio_idc 预定义值:
┌──────┬───────────────┐
│ 值   │ 宽高比         │
├──────┼───────────────┤
│ 1    │ 1:1 (方形)     │
│ 2    │ 12:11          │
│ 3    │ 10:11          │
│ 4    │ 16:11          │
│ 5    │ 40:33          │
│ 6    │ 24:11          │
│ 7    │ 20:11          │
│ 8    │ 32:11          │
│ 9    │ 80:33          │
│ 10   │ 18:11          │
│ 11   │ 15:11          │
│ 12   │ 64:33          │
│ 13   │ 160:99         │
│ 14   │ 4:3            │
│ 15   │ 3:2            │
│ 16   │ 2:1            │
│ 255  │ 自定义         │
└──────┴───────────────┘

显示宽高比计算:
DAR = (width / height) × (sar_width / sar_height)

10. SEI(补充增强信息)

10.1 SEI 结构

sei_message() {
    payloadType                    // SEI 载荷类型
    payloadSize                    // 载荷大小
    sei_payload(payloadType, payloadSize)
}

10.2 常见 SEI 类型

Type 名称 用途
0 buffer_period 缓冲周期
1 pic_timing 图像时序
2 pan_scan_rect Pan-scan 矩形
3 filler_payload 填充数据
4 user_data_registered_itu_t_t35 用户数据
5 user_data_unregistered 未注册用户数据
6 recovery_point 恢复点
19 sub_layer_info 子层信息

10.3 实时通信中的 SEI

SEI 在 WebRTC 中的应用:

1. 用户数据传输
   - 自定义元数据
   - 时间戳同步信息
   - 端到端延迟测量

2. 恢复点
   - 标识可独立解码的位置
   - 用于错误恢复

3. 帧时间信息
   - 显示时间戳
   - 用于音视频同步

11. 句法分析流程

11.1 解码器初始化流程

flowchart TB
    A[开始] --> B[读取起始码]
    B --> C[解析 NAL Header]
    C --> D{NAL Type?}

    D -->|SPS| E[解析并保存 SPS]
    D -->|PPS| F[解析并保存 PPS]
    D -->|IDR Slice| G[解析 Slice Header]
    D -->|Non-IDR Slice| G
    D -->|SEI| H[处理 SEI]
    D -->|其他| I[跳过]

    E --> B
    F --> B
    G --> J[解析宏块数据]
    J --> K[重建图像]
    K --> B
    H --> B
    I --> B

11.2 宏块解码流程

flowchart TB
    A[读取 mb_type] --> B{帧内/帧间?}

    B -->|帧内| C[解析帧内预测模式]
    C --> D[获取预测块]

    B -->|帧间| E[解析运动矢量]
    E --> F[获取参考帧索引]
    F --> G[运动补偿获取预测块]

    D --> H[解析残差系数]
    G --> H

    H --> I[反量化]
    I --> J[反变换 IDCT]
    J --> K[预测 + 残差]
    K --> L[去块滤波]
    L --> M[存储重建帧]

12. 实际码流示例

12.1 SPS 示例解析

SPS 原始数据 (十六进制):
67 64 00 1F AC D9 40 50 05 BB 01 10

解析:
67          → NAL Header: Type=7 (SPS), NRI=3
64          → profile_idc = 100 (High Profile)
00          → constraint_set flags = 0
1F          → level_idc = 31 (Level 3.1)
AC D9 40... → Exp-Golomb 编码的参数

解码后的关键参数:
- Profile: High (100)
- Level: 3.1
- 分辨率: 1280×720
- 帧率: 30fps

12.2 IDR Slice 示例

Slice Header (十六进制):
65 88 80 40 00 5F ...

解析:
65          → NAL Header: Type=5 (IDR), NRI=3
88          → first_mb_in_slice = 0, slice_type = 7 (I)
80          → pic_parameter_set_id = 0
40          → frame_num = 0
00 5F       → idr_pic_id, QP 等

关键信息:
- IDR 帧(关键帧)
- I_Slice 类型
- 第一个宏块地址 = 0
- 引用 PPS ID = 0

13. 总结

13.1 句法层次要点

层次 关键句法元素 作用
NAL NAL Header 标识数据类型和重要性
SPS profile, level, 分辨率 序列级参数
PPS 熵编码模式, QP 图像级参数
Slice slice_type, frame_num 片级参数
MB mb_type, 残差 宏块级编码数据

13.2 解码要点

  1. 参数集优先:必须先解析 SPS/PPS 才能解码 Slice
  2. IDR 是起点:随机访问从 IDR 帧开始
  3. 参考帧管理:正确维护参考帧列表是解码关键
  4. 残差解码:CAVLC/CABAC 选择由 PPS 决定

13.3 调试技巧

  • 使用工具(如 Elecard StreamEye, FFmpeg)分析码流
  • 检查 SPS/PPS 参数是否正确
  • 验证 NAL 单元边界和起始码
  • 确认帧类型和参考关系