整体层次结构
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
:必须为 0nal_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 的结束。
- 在 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 个宏块,经过如下处理:
- Header:
slice_type = I
→000
- MB1: Intra 16x16 →
00101
- MB2: Intra 4x4 →
01010
- 熵编码后比特流:
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 使用
-
熵编码后,残差信息被压缩为比特流(可解码、可传输)。
反向流程:解码端的残差重建
解码时进行逆过程:
- 熵解码 → 反 Zig-Zag
- 反量化(× QP)
- 反 DCT 变换 → 残差块
- 残差块 + 预测块 → 重建图像块
残差处理对图像质量和码率的影响
参数 | 影响 |
---|---|
QP 值 | 控制图像质量/压缩比 |
预测精度 | 影响残差大小 |
变换块大小 | 决定压缩能量集中的效果 |
熵编码方法 | 决定码率最终效率 |
优化残差处理 = 提高压缩率 + 保持清晰度
总结
bash
[原始图像块]
|
| ← 帧内/帧间预测块
↓
[预测残差] = 原始块 - 预测块
↓
[整数 DCT 变换]
↓
[量化]
↓
[Zig-Zag 扫描]
↓
[熵编码(CAVLC/CABAC)]
↓
[比特流输出]
H.264 宏块残差处理是视频压缩的核心流程,其关键优势包括:
- 使用整数 DCT 提高实现效率
- 结合 Zig-Zag 和熵编码提升压缩率
- 可调量化参数(QP)实现灵活质量控制
- 解码过程可逆,确保重建图像的可还原性
残差处理在整个 H.264 编码系统中起到了"连接预测与压缩"的桥梁作用,是性能优化的重点。
宏块的作用
功能 | 说明 |
---|---|
帧内预测(Intra) | 使用相邻宏块预测当前宏块内容 |
帧间预测(Inter) | 使用其他帧的宏块通过运动估计进行预测 |
变换与量化 | 压缩宏块残差数据(DCT-like 整数变换) |
熵编码 | 对宏块的预测模式、残差、运动向量等进行 CABAC 或 CAVLC 编码 |