H.264 长期参考帧

1. 参考帧管理概述

1.1 为什么需要参考帧管理

在视频编码中,P 帧和 B 帧需要参考其他已解码帧进行预测。解码器必须维护一个参考帧列表,存储可用于预测的已解码帧。

参考帧的作用:
┌─────────────────────────────────────────────────────┐
│ 当前帧 P 需要预测时:                                  │
│                                                     │
│   ┌─────────┐     ┌─────────┐     ┌─────────┐      │
│   │ 参考帧 0 │     │ 参考帧 1 │     │ 参考帧 2 │      │
│   │ (I 帧)  │     │ (P 帧)  │     │ (P 帧)  │      │
│   └────┬────┘     └────┬────┘     └────┬────┘      │
│        │               │               │           │
│        └───────────────┼───────────────┘           │
│                        ▼                           │
│              ┌─────────────────┐                   │
│              │  运动估计搜索    │                   │
│              │  找到最佳匹配块  │                   │
│              └────────┬────────┘                   │
│                       ▼                            │
│              ┌─────────────────┐                   │
│              │  当前帧 P 的    │                   │
│              │  预测块         │                   │
│              └─────────────────┘                   │
└─────────────────────────────────────────────────────┘

1.2 短期参考帧 vs 长期参考帧

H.264 定义了两类参考帧:

类型 英文 说明 生命周期
短期参考帧 Short-Term Reference 最近解码的帧,按解码顺序排列 有限,会被新帧替换
长期参考帧 Long-Term Reference (LTR) 标记为长期使用的帧 可跨多个 GOP 持续使用
时间线上的参考帧分布:

帧类型:    I     P     P     P     I     P     P     P
          │     │     │     │     │     │     │     │
时间:     0s    1s    2s    3s    4s    5s    6s    7s
          │     │     │     │     │     │     │     │
短期参考: [0]  [1]   [2]   [3]   [4]   [5]   [6]   [7]
          │           │           │           │
长期参考: [0]─────────────────────[4]─────────────────
          └────── 帧间编码时可参考 ──────┘

帧 7 可以参考:
- 短期参考: 帧 4, 5, 6
- 长期参考: 帧 0, 4 (如果被标记为 LTR)

1.3 长期参考帧的价值

1. 场景切换时的压缩效率

场景切换场景:

帧序列: [室内场景] → [室外场景] → [室内场景]
         I    P    P    I    P    P    P
         0    1    2    3    4    5    6

不使用 LTR:
帧 6 只能参考 3,4,5(都是室外场景)
预测效率低,需要大量残差

使用 LTR:
帧 0 被标记为长期参考帧
帧 6 可以参考帧 0(室内场景)
预测效率高,残差少

2. 周期性场景优化

周期性内容示例:
- 旋转的物体
- 重复的动画
- 视频会议中的手势

如果内容在 T 秒后重复出现:
使用 LTR 保存 T 秒前的帧作为参考
可大幅提升压缩效率

3. 错误恢复

网络传输中 P 帧丢失:

正常情况:
I → P → P → [P丢失] → P → P
              ↓
         后续帧无法解码(参考链断裂)

使用 LTR:
I(LTR) → P → P → [P丢失] → P → P
    ↓                        ↑
    └──── 可以跳过丢失帧,参考 I(LTR)

长期参考帧提供了"安全岛",即使中间帧丢失也能恢复

2. 参考帧列表结构

2.1 两个参考帧列表

H.264 维护两个参考帧列表:

参考帧列表结构:

┌─────────────────────────────────────────────────────┐
│                  解码图像缓冲区 (DPB)                │
│  ┌───────────────────────────────────────────────┐  │
│  │                                               │  │
│  │   ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐   │  │
│  │   │ ST  │ │ ST  │ │ ST  │ │ LT  │ │ LT  │   │  │
│  │   │idx=0│ │idx=1│ │idx=2│ │idx=0│ │idx=1│   │  │
│  │   └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘   │  │
│  │      │       │       │       │       │       │  │
│  │  ┌───┴───────┴───────┘       └───────┴───┐   │  │
│  │  │                                      │   │  │
│  │  │   List 0 (前向参考)                   │   │  │
│  │  │   [ST0, ST1, ST2, LT0, LT1]          │   │  │
│  │  │                                      │   │  │
│  │  │   List 1 (后向参考,用于 B 帧)        │   │  │
│  │  │   [ST2, ST1, ST0, LT0, LT1]          │   │  │
│  │  │                                      │   │  │
│  │  └──────────────────────────────────────┘   │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

ST = Short-Term (短期参考帧)
LT = Long-Term (长期参考帧)

2.2 参考帧索引

编码时通过 ref_idx 指定使用哪个参考帧:

宏块参考帧选择:

mb_type = P_L0_16x16 时:
  ref_idx_l0 = 0  → 使用 List 0 的第一个参考帧
  ref_idx_l0 = 1  → 使用 List 0 的第二个参考帧
  ...

mb_type = B_Bi_16x16 时:
  ref_idx_l0 = 0  → 前向参考 List 0[0]
  ref_idx_l1 = 0  → 后向参考 List 1[0]

3. 长期参考帧句法

3.1 参考帧标记(Reference Picture Marking)

参考帧标记决定了已解码帧如何进入或退出参考帧列表。有两种模式:

参考帧标记模式:

┌─────────────────────────────────────────┐
│ adaptive_ref_pic_marking_mode_flag      │
├─────────────────────────────────────────┤
│ = 0: 滑动窗口模式 (Sliding Window)       │
│     - 自动管理,先进先出                 │
│     - 简单但不够灵活                     │
│                                         │
│ = 1: 自适应模式 (Adaptive MMCO)          │
│     - 通过 MMCO 命令显式控制             │
│     - 支持长期参考帧操作                 │
└─────────────────────────────────────────┘

3.2 MMCO(Memory Management Control Operation)

自适应模式下,通过 MMCO 命令管理参考帧:

dec_ref_pic_marking() {
    if (nal_unit_type == 5) {  // IDR 帧
        no_output_of_prior_pics_flag      // u(1)
        long_term_reference_flag          // u(1)

        if (long_term_reference_flag) {
            // IDR 帧作为长期参考帧
            // LongTermFrameIdx = 0
            // MaxLongTermFrameIdx = 0
        } else {
            // IDR 帧作为短期参考帧
            // 清空所有长期参考帧
        }
    } else {  // 非 IDR 帧
        adaptive_ref_pic_marking_mode_flag // u(1)

        if (adaptive_ref_pic_marking_mode_flag == 1) {
            do {
                memory_management_control_operation_id  // ue(v)

                switch (memory_management_control_operation_id) {
                    case 1:  // 短期参考标记为长期参考
                        difference_of_pic_nums_minus1    // ue(v)
                        long_term_frame_idx              // ue(v)
                        break

                    case 2:  // 短期参考标记为长期参考(使用 LongTermPicNum)
                        long_term_pic_num                // ue(v)
                        break

                    case 3:  // 清空所有短期参考
                        break

                    case 4:  // 清空指定长期参考
                        max_long_term_frame_idx_plus1    // ue(v)
                        break

                    case 5:  // 清空所有参考帧
                        break

                    case 6:  // 当前帧作为长期参考
                        long_term_frame_idx              // ue(v)
                        break
                }
            } while (memory_management_control_operation_id != 0)
        }
    }
}

3.3 MMCO 命令详解

MMCO ID 操作 句法元素 说明
0 结束 表示 MMCO 命令列表结束
1 短期→长期 difference_of_pic_nums_minus1, long_term_frame_idx 将指定短期参考帧标记为长期参考
2 短期→长期(旧方式) long_term_pic_num 使用 LongTermPicNum 标记
3 清空短期 移除所有短期参考帧
4 限制长期 max_long_term_frame_idx_plus1 清除索引 ≥ 该值的长期参考
5 清空所有 清空所有参考帧(相当于 IDR 效果)
6 当前→长期 long_term_frame_idx 当前帧直接作为长期参考
MMCO 命令执行示例:

初始状态:
  短期参考: [帧0, 帧1, 帧2]
  长期参考: []

执行 MMCO=1 (将帧0标记为长期参考):
  短期参考: [帧1, 帧2]
  长期参考: [帧0(LT_idx=0)]

执行 MMCO=6 (当前帧3作为长期参考):
  短期参考: [帧1, 帧2]
  长期参考: [帧0(LT_idx=0), 帧3(LT_idx=1)]

执行 MMCO=4 (max_long_term_frame_idx_plus1=1):
  长期参考索引 ≥ 1 的被清除
  长期参考: [帧0(LT_idx=0)]

3.4 IDR 帧的长期参考标记

IDR 帧有特殊的参考帧标记方式:

IDR 帧标记:

long_term_reference_flag = 0:
  - IDR 帧作为短期参考帧
  - 清空所有长期参考帧
  - 新序列开始的标准方式

long_term_reference_flag = 1:
  - IDR 帧作为长期参考帧
  - LongTermFrameIdx = 0
  - 用于需要 IDR 作为长期锚点的情况

4. 长期参考帧索引

4.1 LongTermFrameIdx

每个长期参考帧有一个 LongTermFrameIdx

长期参考帧索引分配:

┌─────────────────────────────────────────────────────┐
│               长期参考帧列表                         │
│                                                     │
│   LongTermFrameIdx:    0       1       2       3   │
│                     ┌─────┬─────┬─────┬─────┐      │
│                     │ 帧0 │ 帧5 │ 帧10│  -  │      │
│                     │(LTR)│(LTR)│(LTR)│空位 │      │
│                     └─────┴─────┴─────┴─────┘      │
│                                                     │
│   MaxLongTermFrameIdx = 2  (最大有效索引)           │
└─────────────────────────────────────────────────────┘

特点:
- 索引可以不连续
- 新帧可以覆盖旧的长期参考帧
- MaxLongTermFrameIdx 限制最大索引值

4.2 索引管理规则

长期参考帧索引管理:

1. 添加长期参考帧:
   - 分配一个 LongTermFrameIdx
   - 如果该索引已被占用,旧帧被替换

2. 删除长期参考帧:
   - MMCO=4: 删除索引 ≥ max_long_term_frame_idx_plus1 的帧
   - MMCO=5: 删除所有长期参考帧

3. 索引范围限制:
   - 0 ≤ LongTermFrameIdx ≤ MaxLongTermFrameIdx
   - MaxLongTermFrameIdx ≤ max_num_ref_frames - 1

5. 参考帧列表构建

5.1 List 0 构建规则

List 0 构建顺序:

步骤 1: 添加短期参考帧(按 PicNum 降序)
  - 最近的帧排在前面
  - 便于运动估计找到最佳匹配

步骤 2: 添加长期参考帧(按 LongTermPicNum 升序)
  - 长期参考帧排在短期参考帧之后

示例:
  短期参考: 帧10, 帧8, 帧6
  长期参考: 帧0(LT), 帧4(LT)

  List 0 = [帧10, 帧8, 帧6, 帧0(LT), 帧4(LT)]
           └───短期────┘  └───长期────┘

5.2 List 1 构建规则(B 帧)

List 1 构建顺序:

步骤 1: 添加短期参考帧(按 PicNum 升序)
  - 较早的帧排在前面
  - 用于后向预测

步骤 2: 添加长期参考帧(按 LongTermPicNum 升序)

示例:
  短期参考: 帧10, 帧8, 帧6
  长期参考: 帧0(LT), 帧4(LT)
  当前帧: 帧12

  List 1 = [帧6, 帧8, 帧10, 帧0(LT), 帧4(LT)]
           └─短期(升序)─┘  └───长期────┘

5.3 参考帧列表重排序

编码器可以通过 ref_pic_list_modification 调整参考帧顺序:

ref_pic_list_modification() {
    if (slice_type % 5 != 2 && slice_type % 5 != 4) {  // P 或 B 帧
        ref_pic_list_modification_flag_l0  // u(1)
        if (ref_pic_list_modification_flag_l0) {
            do {
                modification_of_pic_nums_idc  // ue(v)
                switch (modification_of_pic_nums_idc) {
                    case 0:  // 短期参考,指定 abs_diff_pic_num_minus1
                    case 1:  // 短期参考,指定 abs_diff_pic_num_minus1
                    case 2:  // 长期参考,指定 long_term_pic_num
                }
            } while (modification_of_pic_nums_idc != 3)
        }
    }
    // List 1 类似处理...
}

重排序示例

原始 List 0: [ST0, ST1, ST2, LT0, LT1]

重排序命令:
  modification_of_pic_nums_idc = 2, long_term_pic_num = 0

重排序后: [LT0, ST0, ST1, ST2, LT1]

目的: 将长期参考帧 LT0 移到最前面,优先使用

6. 长期参考帧的典型应用场景

6.1 场景切换优化

应用场景: 视频中存在场景切换

编码策略:
1. 检测到场景切换时,编码一个 I 帧
2. 将该 I 帧标记为长期参考帧
3. 后续 P 帧可以参考这个长期参考帧

时间线:
         场景A              场景B              场景A
帧:   I  P  P  P        I(LTR) P  P  P     P  P  P
      0  1  2  3          4     5  6  7     8  9  10

帧 8-10 编码时:
- 如果场景切换回 A
- 帧 8 可以参考帧 4(I-LTR),而不是帧 7
- 预测效率大幅提升

6.2 视频会议中的 LTR

视频会议场景特点:
- 背景相对稳定
- 人物可能有重复动作
- 需要低延迟

LTR 策略:

┌─────────────────────────────────────────────────────┐
│  编码器决策逻辑:                                     │
│                                                     │
│  if (帧内容与 LTR 相似度高) {                        │
│      优先使用 LTR 作为参考                          │
│      码率降低,质量提升                             │
│  } else if (场景变化) {                             │
│      更新 LTR 为当前 I 帧                           │
│  }                                                  │
└─────────────────────────────────────────────────────┘

WebRTC 中的典型配置:
- 保留 1-2 个长期参考帧
- 周期性更新(如每 4-8 秒)
- 基于内容相似度决策

6.3 错误恢复与抗丢包

网络传输场景:

问题: 连续 P 帧参考链导致丢包后无法恢复

传统方案:
I → P → P → P → P → P → P → I → P → P
              ↑ 丢包
              └─→ 后续帧全部花屏

LTR 方案:
I(LTR) → P → P → P → P → P → P → P → P
    ↓                            ↑
    └──── 丢包后可跳回 I(LTR) ───┘

具体实现:
1. 接收端检测到丢包
2. 发送 FIR (Full Intra Request) 或 PLI
3. 发送端可以:
   - 编码新的 I 帧
   - 或者让后续 P 帧参考 LTR(跳过丢失区域)

6.4 实时通信中的 LTR 使用

WebRTC 编码器中的 LTR 策略:

┌─────────────────────────────────────────────────────┐
│                    编码器                           │
│  ┌───────────────────────────────────────────────┐  │
│  │  LTR 管理模块                                  │  │
│  │                                               │  │
│  │  1. 周期性标记关键帧为 LTR                     │  │
│  │     - 间隔: 根据场景复杂度动态调整            │  │
│  │     - 典型值: 4-10 秒                         │  │
│  │                                               │  │
│  │  2. 场景切换时更新 LTR                        │  │
│  │     - 检测到场景切换 → 编码 I 帧              │  │
│  │     - 将 I 帧标记为新的 LTR                   │  │
│  │                                               │  │
│  │  3. 响应接收端反馈                            │  │
│  │     - 收到 PLI/FIR → 编码 I 帧               │  │
│  │     - 将新 I 帧标记为 LTR                     │  │
│  │                                               │  │
│  │  4. 参考帧选择                                │  │
│  │     - 优先从短期参考中选择                    │  │
│  │     - 短期参考效果差时尝试 LTR                │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

7. 句法元素总结

7.1 SPS 中的相关句法

seq_parameter_set_rbsp() {
    ...
    max_num_ref_frames           // ue(v) 最大参考帧数
    ...
}

- 决定了短期 + 长期参考帧的总数上限
- LTR 数量受此限制

7.2 Slice Header 中的相关句法

slice_header() {
    ...
    frame_num                    // u(v) 短期参考帧的帧号
    ...

    if (nal_unit_type == 5) {    // IDR 帧
        idr_pic_id               // ue(v) IDR 图像标识
        ...
    }

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

    // 参考帧标记(非 IDR 帧且 nal_ref_idc != 0)
    if (nal_ref_idc != 0)
        dec_ref_pic_marking()
    ...
}

7.3 关键句法元素一览

句法元素 位置 编码方式 说明
max_num_ref_frames SPS ue(v) 最大参考帧数量
frame_num Slice Header u(v) 短期参考帧帧号
idr_pic_id Slice Header ue(v) IDR 图像标识
adaptive_ref_pic_marking_mode_flag Slice Header u(1) 标记模式选择
memory_management_control_operation_id Slice Header ue(v) MMCO 操作码
difference_of_pic_nums_minus1 Slice Header ue(v) 短期参考帧号差值
long_term_frame_idx Slice Header ue(v) 长期参考帧索引
long_term_pic_num Slice Header ue(v) 长期参考帧图像号
max_long_term_frame_idx_plus1 Slice Header ue(v) 最大长期索引+1
long_term_reference_flag Slice Header u(1) IDR 作为长期参考标志
ref_idx_l0 宏块层 te(v) List 0 参考帧索引
ref_idx_l1 宏块层 te(v) List 1 参考帧索引

8. 实现注意事项

8.1 编码器实现要点

编码器 LTR 管理:

1. LTR 选择策略
   - 基于场景复杂度
   - 基于运动分析
   - 基于率失真优化

2. LTR 更新时机
   - 周期性更新(固定间隔)
   - 场景切换时更新
   - 收到接收端请求时更新

3. 参考帧选择
   - 对每个宏块评估最佳参考帧
   - 考虑 LTR 作为备选
   - 率失真优化决策

8.2 解码器实现要点

解码器 LTR 管理:

1. DPB (Decoded Picture Buffer) 管理
   - 维护短期参考帧列表
   - 维护长期参考帧列表
   - 正确执行 MMCO 命令

2. 参考帧有效性检查
   - 检查 ref_idx 是否有效
   - 检查参考帧是否存在
   - 处理参考帧缺失情况

3. 错误恢复
   - 检测到错误时请求新的 LTR
   - 发送 PLI/FIR 反馈

8.3 兼容性考虑

Profile 支持情况:

┌─────────────────┬──────────────────────────────────┐
│ Profile         │ 长期参考帧支持                    │
├─────────────────┼──────────────────────────────────┤
│ Baseline        │ 支持(无 B 帧,List 1 不使用)    │
│ Constrained     │ 支持                              │
│ Main            │ 完整支持                          │
│ High            │ 完整支持                          │
└─────────────────┴──────────────────────────────────┘

WebRTC 中的考虑:
- Constrained Baseline 不使用 B 帧
- 只需要管理 List 0
- LTR 仍然有效且重要

9. 总结

9.1 长期参考帧的核心价值

价值 说明
压缩效率 场景切换、周期性内容时提升预测效率
错误恢复 提供安全参考点,支持丢包恢复
灵活性 MMCO 命令提供细粒度控制
兼容性 所有 Profile 都支持

9.2 关键句法流程

flowchart TB
    A[解码帧] --> B{nal_ref_idc != 0?}
    B -->|否| C[不作为参考帧]
    B -->|是| D{IDR 帧?}

    D -->|是| E{long_term_reference_flag?}
    E -->|0| F[作为短期参考<br>清空长期参考]
    E -->|1| G[作为长期参考<br>Idx=0]

    D -->|否| H{adaptive_ref_pic_marking_mode?}
    H -->|0| I[滑动窗口模式]
    H -->|1| J[执行 MMCO 命令]

    J --> K{MMCO 类型}
    K -->|1| L[短期→长期]
    K -->|3| M[清空短期]
    K -->|4| N[限制长期索引]
    K -->|5| O[清空所有]
    K -->|6| P[当前→长期]

9.3 实践建议

  1. 编码器:合理设置 LTR 更新频率,平衡压缩效率和复杂度
  2. 解码器:正确实现 DPB 管理和 MMCO 命令处理
  3. 实时通信:利用 LTR 提升抗丢包能力,配合 PLI/FIR 机制
  4. 调试:使用工具检查参考帧列表状态,验证 MMCO 执行正确性