音视频学习(四十):H264码流结构

整体层次结构

H.264 的码流是由一系列 NALU(Network Abstraction Layer Units) 组成。整体结构可分为两个层次:

Video Coding Layer(VCL)

  • 包含视频图像的主要内容数据:帧内/帧间预测、残差、运动矢量等
  • 关键组成:Slice、宏块数据等

Network Abstraction Layer(NAL)

  • 包装 VCL 数据,使之可传输于不同网络或容器(如 MP4、TS、RTP)
  • 每个视频片段(如 SPS、PPS、Slice)都包装为一个 NAL 单元

整体结构图

NALU(Network Abstraction Layer Unit)结构

NALU结构图

NALU Header(1 字节)

pgsql 复制代码
+----------------+------------+------------------+
| forbidden_zero | nal_ref_idc | nal_unit_type   |
|     1 bit      |   2 bits    |     5 bits      |
+----------------+------------+------------------+
  • forbidden_zero_bit:必须为 0
  • nal_ref_idc:表示当前 NAL 单元对参考帧的影响(0:不参考,1~3:参考)
  • nal_unit_type
    • 1:非 IDR 的 slice
    • 5:IDR(关键帧)slice
    • 6:SEI(补充增强信息)
    • 7:SPS(序列参数集)
    • 8:PPS(图像参数集)
    • 9:AUD(访问单元分隔符)

关键类型说明

SPS(序列参数集)

  • 定义整段视频的全局特征
  • 内容包括:
    • 图像宽高、帧率
    • 色度子采样方式(如 4:2:0)
    • 最大参考帧数
    • 编码级别与 profile
    • 码流格式(如是否帧结构、隔行扫描)

PPS(图像参数集)

  • 定义当前图像相关的编码配置
  • 包括:
    • 熵编码方式(CAVLC / CABAC)
    • 去块滤波参数
    • Slice 分区设置

SPS + PPS 是初始化解码器的关键配置,通常在码流开头发送一次。

IDR Slice(关键帧)

  • nal_unit_type = 5
  • 不依赖前后帧即可解码
  • 用于 seek、I 帧插入点

普通 Slice

  • nal_unit_type = 1
  • 需要参考前面帧,进行帧间预测解码

SODB和RBSP

概述

名称 全称 作用
SODB String Of Data Bits 实际编码数据的比特流(不含对齐或防竞争码)
RBSP Raw Byte Sequence Payload 在码流中传输的基础单元,SODB 加填充后形成

SODB(String Of Data Bits)

定义

SODB 是指实际编码数据的原始二进制位串,是熵编码后的裸比特流(如 CABAC/CAVLC 输出),不包括任何字节对齐或停止位。

特点
  • 比特为单位,长度不一定是 8 的倍数;
  • 是最纯粹的编码数据,如残差、预测模式、运动矢量等;
  • 后续会被封装为 RBSP。
示例
text 复制代码
SODB: 10111010011101  (长度: 14 位,不足 2 字节)

RBSP(Raw Byte Sequence Payload)

定义

RBSP 是 H.264 标准中用于存储在 NAL 单元(NALU)内部的编码数据,它是对 SODB 加上适当填充和终止位后形成的"字节对齐"格式。

构成

一个 RBSP 通常 = SODB + rbsp_stop_one_bit + rbsp_alignment_zero_bits

  • rbsp_stop_one_bit

    • 在 SODB 末尾加一个 1 位,用于标记 SODB 的结束。
  • rbsp_alignment_zero_bits

    • 为了字节对齐,在后面添加 0 若干位,使总位数为 8 的倍数。
示例
text 复制代码
SODB:                 10111010011101          (14 位)
+ rbsp_stop_one_bit:  1                       (1 位)
→                   101110100111011
+ 对齐补零:           0                       (1 位)
RBSP:                1011101001110110         (共 16 位,即 2 字节)
转换流程
text 复制代码
[SODB]
  ↓ 加 1 位结束位(rbsp_stop_one_bit)
[SODB + 1]
  ↓ 加 0 填充直到 8 的倍数(rbsp_alignment_zero_bits)
[RBSP]

RBSP 与 NALU 的关系

在 H.264 中,数据最终通过 NAL 单元(NALU) 封装后进行传输或存储:

css 复制代码
[ NAL Header ] + [ RBSP Payload ] → NAL Unit
  • NAL Header:包含类型(帧、SPS、PPS 等)、是否是参考帧、优先级等信息。

  • RBSP Payload:为该单元携带的实际数据,如帧数据、参数集等。

  • 整个 NAL 单元经过**防竞争码插入(emulation prevention)**后生成最终码流。

RBSP与Slice的关系

一个完整的 Slice 包括如下信息:

bash 复制代码
RBSP 内容 = Slice Header + Slice Data + rbsp_trailing_bits
  • Slice Header:包含起始宏块编号、Slice 类型、参考帧列表等信息;

  • Slice Data:包含每个宏块的预测模式、残差系数等编码信息;

  • rbsp_trailing_bits:用于字节对齐的结束标志(包括 rbsp_stop_one_bit 和 rbsp_alignment_zero_bits);

通过一个流程说明:

  • 编码器将帧分成多个 Slice(通常按宏块行切分)
  • 每个 Slice → 被编码为比特流(SODB)
  • SODB → 加 rbsp_stop_one_bit + 对齐 → 生成 RBSP
  • RBSP → 加上 NAL Header → 封装为 NALU
  • NALU → 写入码流,用于传输或存储
text 复制代码
[ Slice ]         ← 最底层,图像内容(预测、宏块、残差)
  ↓ 封装
[ RBSP ]          ← 封装成字节对齐形式
  ↓ 加 NAL Header
[ NAL Unit (NALU) ] ← 可传输、可存储的网络抽象层结构

SODB与Slice的关系

总体流程
bash 复制代码
[Slice]
  ↓
1. 构建 Slice Header
  ↓
2. 编码 Slice Data(宏块等)
  ↓
3. 熵编码(CAVLC / CABAC)
  ↓
[输出的比特串] = SODB(String Of Data Bits)
具体步骤
构建 Slice Header

Slice Header 是每个切片最前面的控制信息,结构中包括:

字段 说明
first_mb_in_slice 当前切片起始宏块的编号
slice_type 当前切片类型(I, P, B)
pic_parameter_set_id 使用的 PPS 编号
frame_num 当前帧编号
idr_pic_id(可选) 是否 IDR 帧,ID
ref_idx_active_override_flag 引用帧控制
slice_qp_delta QP 偏移值
... 其他语法元素(视参数集而定)

→ 所有这些字段以 Exp-Golomb 或固定比特形式编码为比特流。

编码 Slice Data(宏块)

Slice 数据包含当前切片内的所有宏块。每个宏块又包含多个字段:

宏块结构包括:

  • mb_type:宏块类型(Intra, Inter 等)
  • prediction modes:预测模式(帧间、帧内)
  • motion_vector:运动矢量(P、B 帧)
  • coded_block_pattern:是否含残差
  • residual:变换残差系数(DCT)
  • delta_qp:QP 差值(可选)

每个宏块的数据根据其类型(I、P、B)不同,会调用不同的编码模板和语法元素。

→ 这些内容也会转为比特序列,加入最终 SODB 中。

熵编码(Entropy Coding)

此步骤将前两步得到的所有语法元素(包括 header 和宏块数据)进行熵编码,变成压缩后的比特流。

两种方式:

方法 名称 特点
CAVLC Context-Adaptive Variable Length Coding 适合 I 帧或简单场景,解码简单
CABAC Context-Adaptive Binary Arithmetic Coding 压缩率更高,复杂度高,常用于 P/B 帧
  • 所有语法元素(如 mb_type、mv、residual 等)都映射为二进制码字
  • 按语义上下文建模(如残差符号前的 0 数量)

→ 熵编码的结果即为原始比特流 SODB。

最终输出:SODB(String Of Data Bits)

定义:

SODB 是一个纯比特流,不带终止标志、补零、字节对齐或 NAL 头等,是熵编码后最原始的输出。

text 复制代码
SODB = 熵编码后输出的裸比特串

例如(简化后):

text 复制代码
SODB: 010110101100101110111011...
(长度不是 8 的整数倍也可以)
SODB 与后续结构关系

完成 Slice → SODB 之后,编码流程仍会继续:

text 复制代码
[SODB]
  ↓ 添加终止位
[RBSP]
  ↓ 加入 NAL Header
[NALU] → 写入码流
示例

假设一个 I Slice 包含 2 个宏块,经过如下处理:

  1. Header: slice_type = I000
  2. MB1: Intra 16x16 → 00101
  3. MB2: Intra 4x4 → 01010
  4. 熵编码后比特流:
ini 复制代码
SODB = 000001010101010

共 15 位 → 最终封装为 RBSP 时需添加 1 位结束位和 0 补齐

Slice 到 SODB 的关键路径
步骤 内容 编码方式
Slice Header 构建 Slice 参数如起始宏块、类型 Exp-Golomb / 定长编码
宏块数据组织 mb_type, mv, residual 等 依语法元素逐一编码
熵编码 将所有数据压缩为二进制比特 CAVLC / CABAC
最终 得到纯净比特流 SODB

为什么使用 RBSP 封装 Slice

原因 说明
防止起始码混淆 通过 RBSP → NALU 阶段添加防竞争字节(0x03),避免 0x000001 被误识别为 NAL 起始码
支持字节对齐 Slice 编码后长度可能不是 8 的倍数,RBSP 添加停止位和补零确保字节对齐
便于封装 NALU 封装层以"完整 RBSP"为基本单位,更容易打包

VCL(Video Coding Layer)

主要作用

VCL 主要处理以下内容:

  • 帧内预测(Intra Prediction)
    • 压缩当前帧的图像数据时,仅使用该帧的信息进行预测。
    • 提供多个预测模式(4x4、16x16 等)来提高压缩效率。
  • 帧间预测(Inter Prediction)
    • 使用前一帧或多帧的图像信息来预测当前帧的内容(运动估计和运动补偿)。
    • 利用**宏块(Macroblock)运动向量(Motion Vector)**表示运动信息。
  • 变换与量化
    • 使用整数变换(类似 DCT)对残差进行压缩处理。
    • 再通过量化降低数据精度,从而减少码率。
  • 熵编码(Entropy Coding)
    • 使用 CABAC(上下文自适应二进制算术编码)或 CAVLC(上下文自适应可变长编码)压缩剩余数据。
    • CABAC 效率更高,但解码复杂度也更高。

结构层次

VCL 数据通常被封装在一个或多个 NAL 单元(NAL Units) 中,结构如下:

pgsql 复制代码
+------------------+
| NAL Unit Header  | ← 属于 NAL 层
+------------------+
| VCL Data         | ← 属于 VCL 层(slice 数据)
+------------------+

VCL 数据中最核心的单元是:

  • Slice(片)
    • 一帧图像被划分成若干个 slice,每个 slice 是一组宏块的集合。
    • 每个 slice 都可以独立解码,提高容错能力。

Slice

Slice 是由一个或多个宏块(Macroblocks)组成的视频编码单元。它可以是整帧图像的一部分,甚至可以是整帧本身(单 slice 编码)。

每个 slice 可以独立解码,这为错误恢复、并行处理和码流分段等应用提供了支持。

结构

一个 Slice 的大致结构如下:

Slice Header(slice头部)

包含了以下关键信息:

  • slice_type: 表示 slice 的类型(I/P/B)
  • first_mb_in_slice: 第一个宏块的位置(宏块序号)
  • pic_parameter_set_id: PPS 引用索引
  • frame_num: 当前帧的编号
  • num_ref_idx_active: 参考帧数量
  • cabac_init_idc: CABAC 初始化参数
  • 预测参数(如运动向量预测标志等)

Slice 的类型

H.264 中的 slice 类型与帧类型(I/P/B)是对应的,但可更细分:

Slice Type 含义 特点
I-Slice Intra slice(帧内编码) 仅依赖当前图像数据,适合关键帧
P-Slice Predictive slice(前向预测) 参考前面已解码的帧进行运动估计
B-Slice Bi-predictive slice(双向预测) 可参考前后帧,压缩率更高但复杂度更大
SI-Slice Switching Intra slice 为流切换准备,便于解码同步
SP-Slice Switching Predictive slice 支持流的无缝切换与速率调整(可逆预测)

宏块与 Slice 的关系

  • 每个 Slice 中包含多个宏块(16x16 像素块)。
  • 编码时一帧可被划分为多个 Slice,每个 slice 包含一部分宏块。
makefile 复制代码
帧结构如下(宏块编号):

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

→ 可以划分为两个 Slice:
Slice1: 宏块 0 ~ 7
Slice2: 宏块 8 ~ 15

作用和优势

优势 说明
容错性好 每个 slice 都可独立解码,如果某个 slice 丢失,不影响其他 slice
并行处理 多个 slice 可并行编码和解码,提高速度(如多核 CPU)
码率控制 可根据 Slice 调整不同区域的码率
适应网络传输 每个 slice 可打包为一个 NAL 单元,便于网络封装和丢包恢复

Slice 与 NAL Unit 的关系

  • 每个 slice 通常封装成一个独立的 NAL Unit(Type: 1~5)。
  • 一个帧可以包含多个 NAL Units(即多个 Slice)。
  • Slice 的编码类型(I/P/B)决定它在 NAL Unit 中的类型。

宏块(Macroblock)

宏块的概念

  • 宏块是图像中的一个 16x16 像素区域
  • 每个宏块包含:
    • 亮度(Luma)分量:16×16 像素
    • 色度(Chroma)分量:每个色度通道为 8×8 或 8×16(取决于采样格式,通常是 YUV 4:2:0)

举个例子(YUV 4:2:0):

分量 像素数量
Y(亮度) 16 × 16 = 256
U(色度) 8 × 8 = 64
V(色度) 8 × 8 = 64
合计 384 字节(未压缩)

宏块的划分结构

H.264 中宏块有多种类型,按编码方式可分为:

Intra 宏块(帧内预测)

用于当前帧的内部预测编码,不依赖其他帧。

  • Intra_4x4:16x16 的亮度块被划分为 16 个 4x4 块,每个单独预测,适用于细节丰富区域
  • Intra_8x8:划分为 4 个 8x8 块,每个做8x8预测
  • Intra_16x16:整块统一做帧内预测,适用于平坦区域
  • Chroma Intra:4x4 或 8x8 的色度块帧内预测
Inter 宏块(帧间预测)

通过运动估计引用前后帧的图像数据,实现更高压缩效率。

  • Inter_16x16:整个宏块引用同一运动向量
  • Inter_16x8:上下各一半使用不同运动向量
  • Inter_8x16:左右各一半使用不同运动向量
  • Inter_8x8 :划分为 4 个 8x8,每个8x8还可以进一步划分为:
    • 8x8
    • 8x4
    • 4x8
    • 4x4

这种灵活划分支持亚像素级别的运动补偿,是 H.264 的压缩效率优势之一。

宏块中的预测结构

帧内预测模式(Intra)

预测来自相邻已编码块:

  • Intra_4×4:支持 9 种方向预测模式(水平、垂直、对角等)
  • Intra_16×16:支持 4 种模式(垂直、水平、DC、Plane)

色度(Chroma)预测有以下模式:

  • DC、水平、垂直、Plane(平面)
帧间预测(Inter)
  • 使用运动估计,在参考帧中找到最匹配区域
  • 得到运动向量(Motion Vector, MV)
  • 当前块 = 参考块 + 残差

宏块中的语法元素

一个宏块在码流中由以下信息表示:

字段 含义
mb_type 宏块类型(Intra_4x4、Inter_16x16 等)
sub_mb_type 子宏块类型(8x4、4x4 等)
coded_block_pattern CBP:标识哪些子块有残差数据
motion_vector 每个预测子块的运动向量
ref_idx_l0/l1 参考帧索引
intra_pred_mode 帧内预测模式索引(例如 4x4 的每个块有一个)
residual 残差数据,经变换、量化并用 CAVLC/CABAC 熵编码

残差处理流程

什么是残差?

在 H.264 编码中,"残差"是指:

残差 = 原始图像块像素值 - 预测图像块像素值

预测图像块通过帧内或帧间预测获得,残差表示原始图像与预测图像的"差异信息"。

通过只编码"差异",可显著压缩数据量。

处理流程
预测生成(Intra/Inter)

根据宏块类型采用不同预测方式:

  • 帧内预测(Intra):基于相邻像素块推测当前块
  • 帧间预测(Inter):基于参考帧和运动矢量推测当前块

预测块 是还原图像的一种"估计版本",本身不需要编码,而只需编码与真实图像的差值。

计算残差块

残差 = 原始像素块 − 预测块

对于每个子块(通常是 4x4 或 8x8),逐像素计算差值。例如:

text 复制代码
原始:     预测:     残差:
[50 52]   [48 51]   [2  1]
[53 55]   [50 54]   [3  1]

残差值包含正负,是一个整数矩阵。

残差变换(整型 DCT)

为了进一步压缩残差块的能量,H.264 使用变换操作:

  • 采用 4x4 或 8x8 的 整数 DCT(变换)
  • 将空域信号转换为频域系数(如低频能量集中)

整数 DCT 变换简化为矩阵乘法操作,避免使用浮点数,适合硬件实现。

变换后,残差矩阵变为如下形式:

text 复制代码
DCT 系数(示例):
[88  3 -1  0]
[ 2 -1  0  0]
[-1  0  0  0]
[ 0  0  0  0]

低频系数集中,高频多为 0。

量化(Quantization)

变换后的系数值范围仍较大,需要通过量化进一步压缩:

  • 每个 DCT 系数 ÷ 量化步长(由 QP 控制)
  • 保留整数部分(舍去小数)

QP(Quantization Parameter) 控制质量与压缩比:

  • QP 越大,压缩越强,质量越差,码率越小
  • QP 越小,保留细节更多,图像质量越高

示例:

text 复制代码
DCT:       88   3  -1
QP=28量化: 6    0   0
Zig-Zag 扫描(ZigZag Scan)

为了配合熵编码器,H.264 将系数按Z字形路径进行一维扫描,排列为序列:

csharp 复制代码
Zig-Zag 顺序示意:

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

举例:

text 复制代码
扫描后:6, 0, 0, 1, 0, 0, 0, 0, ...

零值密集更容易使用熵编码压缩。

熵编码(CAVLC / CABAC)

最后一步是对 Zig-Zag 扫描后的系数使用熵编码

  • CAVLC(Context-Adaptive Variable Length Coding)

    • 基于 VLC 表

    • 针对零数量、非零数值、运行长度编码(Run-Length)

    • Baseline profile 使用

  • CABAC(Context-Adaptive Binary Arithmetic Coding)

    • 更复杂的二进制上下文模型

    • 压缩率更高,计算复杂

    • High profile 使用

熵编码后,残差信息被压缩为比特流(可解码、可传输)。

反向流程:解码端的残差重建

解码时进行逆过程

  1. 熵解码 → 反 Zig-Zag
  2. 反量化(× QP)
  3. 反 DCT 变换 → 残差块
  4. 残差块 + 预测块 → 重建图像块
残差处理对图像质量和码率的影响
参数 影响
QP 值 控制图像质量/压缩比
预测精度 影响残差大小
变换块大小 决定压缩能量集中的效果
熵编码方法 决定码率最终效率

优化残差处理 = 提高压缩率 + 保持清晰度

总结
bash 复制代码
[原始图像块]
      |
      |   ← 帧内/帧间预测块
      ↓
[预测残差] = 原始块 - 预测块
      ↓
[整数 DCT 变换]
      ↓
[量化]
      ↓
[Zig-Zag 扫描]
      ↓
[熵编码(CAVLC/CABAC)]
      ↓
[比特流输出]

H.264 宏块残差处理是视频压缩的核心流程,其关键优势包括:

  • 使用整数 DCT 提高实现效率
  • 结合 Zig-Zag 和熵编码提升压缩率
  • 可调量化参数(QP)实现灵活质量控制
  • 解码过程可逆,确保重建图像的可还原性

残差处理在整个 H.264 编码系统中起到了"连接预测与压缩"的桥梁作用,是性能优化的重点。

宏块的作用

功能 说明
帧内预测(Intra) 使用相邻宏块预测当前宏块内容
帧间预测(Inter) 使用其他帧的宏块通过运动估计进行预测
变换与量化 压缩宏块残差数据(DCT-like 整数变换)
熵编码 对宏块的预测模式、残差、运动向量等进行 CABAC 或 CAVLC 编码