H.265/HEVC 解码知识文档
聚焦 H.265/HEVC 解码原理与核心算法,基于 libde265 1.0.16 源码分析。
1. NAL 单元结构
NAL 单元整体结构
┌─────────────┬────────────┬──────────────────────────┐
│ Start Code │ NAL Header │ RBSP │
│ 00 00 00 01 │ 2 bytes │ 变长 (原始字节序列载荷) │
└─────────────┴────────────┴──────────────────────────┘
Start Code: 00 00 00 01 (4字节) 或 00 00 01 (3字节)
NAL Header: 2 字节 (H.264 是 1 字节,HEVC 扩展为 2 字节)
RBSP: 有效载荷,NAL 类型不同内容不同
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───────────────┬───────────────┬───────────────────────────┐
│ F │ Type (6 bit) │ LayerId(6bit) │ TID (3 bit) │
└───┴───────────────┴───────────────┴───────────────────────────┘
F = forbidden_zero_bit (1 bit): 必须为0
Type = nal_unit_type (6 bit): NAL 单元类型 (0~63)
LayerId = nuh_layer_id (6 bit): 层标识 (非可分级视频始终为0)
TID = nuh_temporal_id_plus1 (3 bit): 时间子层ID+1 (1~7, 0非法)
实际 temporal_id = TID - 1
与 H.264 NAL Header 的对比:
|
H.264 |
H.265 |
| Header 长度 |
1 字节 |
2 字节 |
| Type 位数 |
5 bit |
6 bit |
| 参考优先级 |
nal_ref_idc (2 bit) |
由 Type 隐含 (奇数=参考) |
| 时间层 |
无 |
nuh_temporal_id (3 bit) |
| 层标识 |
无 |
nuh_layer_id (6 bit) |
NAL Unit Type --- H.265 完整类型表
VCL NAL (携带视频编码数据):
| Type |
名称 |
含义 |
参考? |
| 0 |
TRAIL_N |
非IRAP尾随帧,非子层参考 |
否 |
| 1 |
TRAIL_R |
非IRAP尾随帧,子层参考 |
是 |
| 2 |
TSA_N |
时间子层访问点,非参考 |
否 |
| 3 |
TSA_R |
时间子层访问点,参考 |
是 |
| 4 |
STSA_N |
逐步时间子层访问点,非参考 |
否 |
| 5 |
STSA_R |
逐步时间子层访问点,参考 |
是 |
| 6 |
RADL_N |
随机访问可解码前导,非参考 |
否 |
| 7 |
RADL_R |
随机访问可解码前导,参考 |
是 |
| 8 |
RASL_N |
随机访问跳过前导,非参考 |
否 |
| 9 |
RASL_R |
随机访问跳过前导,参考 |
是 |
IRAP NAL (随机访问点):
| Type |
名称 |
含义 |
DPB行为 |
| 16 |
BLA_W_LP |
断链访问,关联低层 |
清空DPB |
| 17 |
BLA_W_RADL |
断链访问,关联RADL |
清空DPB |
| 18 |
BLA_N_LP |
断链访问,无低层关联 |
清空DPB |
| 19 |
IDR_W_RADL |
即时解码刷新,关联RADL |
清空DPB |
| 20 |
IDR_N_LP |
即时解码刷新,无低层关联 |
清空DPB |
| 21 |
CRA |
纯随机访问 |
不清空DPB |
非 VCL NAL:
| Type |
名称 |
含义 |
| 32 |
VPS_NUT |
视频参数集 |
| 33 |
SPS_NUT |
序列参数集 |
| 34 |
PPS_NUT |
图像参数集 |
| 35 |
AUD_NUT |
访问单元分隔符 |
| 36 |
EOS_NUT |
序列结束 |
| 37 |
EOB_NUT |
码流结束 |
| 38 |
FD_NUT |
填充数据 |
| 39 |
PREFIX_SEI_NUT |
前缀SEI |
| 40 |
SUFFIX_SEI_NUT |
后缀SEI |
关键分类函数:
isIDR(): Type == 19 || Type == 20
isBLA(): Type == 16 || Type == 17 || Type == 18
isCRA(): Type == 21
isIRAP(): Type ∈ [16, 23]
isRAP(): isIDR() || isBLA() || isCRA()
isReferenceNALU(): (Type<=15 && 奇数) || Type∈[16,23]
isSublayerNonRef(): Type ∈ {0, 2, 4, 6, 8, 10, 12, 14}
H.264 → H.265 IRAP 概念扩展:
H.264 只有两种:
IDR (Type=5): 清空DPB, 后续帧不能参考IDR之前的帧
非IDR I帧: 不清空DPB
H.265 扩展为六种:
IDR_W_RADL / IDR_N_LP: 同H.264 IDR,清空DPB
CRA: 纯随机访问点,不清空DPB,RASL帧可能无法解码
BLA_W_LP / BLA_W_RADL / BLA_N_LP: 断链访问,码流拼接点
防竞争 (Emulation Prevention)
同 H.264,编码时在 00 00 后插入 03 字节防止与 Start Code 冲突。解码时移除 03 恢复原始数据。
2. 访问单元 (Access Unit)
一个 AU = 一帧图像的所有 NAL 单元:
Access Unit
├── AUD NAL (可选)
├── VPS NAL (新序列开始时)
├── SPS NAL (新序列或参数变化时)
├── PPS NAL (参数变化时)
├── PREFIX SEI NAL (可选)
├── Slice NAL 1 (I/P/B)
├── Slice NAL 2 (可选,一帧可有多个 Slice Segment)
├── SUFFIX SEI NAL (可选)
└── ...
AU 边界检测: 新 VCL NAL 的 first_slice_segment_in_pic_flag=1 → 新 AU 开始。HEVC 比 H.264 简化了很多,主要通过此标志位判断。
3. VPS 关键参数详解
VPS (Video Parameter Set) 是 HEVC 新增的参数集,H.264 中没有。它描述时间子层结构。
VPS RBSP 解析顺序:
基本标识
| 字段 |
编码 |
说明 |
video_parameter_set_id |
u(4) |
VPS 编号 (0~15) |
| (reserved) |
u(2) |
固定 0b11 |
vps_max_layers |
u(6)+1 |
最大层数 (非可分级时=1) |
vps_max_sub_layers |
u(3)+1 |
最大时间子层数 (1~7) |
vps_temporal_id_nesting_flag |
u(1) |
时间层嵌套标志 |
| (reserved) |
u(16) |
固定 0xFFFF |
Profile/Tier/Level
| 字段 |
说明 |
profile_space |
档次空间 (0=标准) |
tier_flag |
层级: 0=Main Tier, 1=High Tier |
profile_idc |
档次: 1=Main, 2=Main10, 3=MainStill, 4=RExt |
level_idc |
level_major×30 + level_minor×3 |
时间子层 DPB 参数
| 字段 |
编码 |
说明 |
vps_max_dec_pic_buffering[i] |
ue(v) |
每层最大解码帧缓冲数 |
vps_max_num_reorder_pics[i] |
ue(v) |
每层最大重排序帧数 |
vps_max_latency_increase_plus1[i] |
ue(v) |
0=无延迟限制 |
时间子层 (Temporal Sub-Layer) 详解
HEVC 的时间子层是时间可分级机制: 一个码流中按重要性分层, 可按层裁剪帧率, 无需重编码。
正常 60fps 码流 (3个时间子层):
TID=0: I P P P P P P P ← 关键帧, 7.5fps
TID=1: B B B B B B ← 中间帧, 15fps
TID=2: b b b b b b b b b b b b ← 增强帧, 30fps (实际可更多)
──────────────────────────────────→
全部解码 = 60fps
带宽不足时只解码 TID≤1:
TID=0: I P P P P P P P ← 7.5fps
TID=1: B B B B B B ← 填充到 15fps
→ 帧率降低但画面不花
只解码 TID=0:
TID=0: I P P P P P P P ← 7.5fps
→ 关键帧级别, 最低帧率
核心规则
1. 低 TID 帧不能参考高 TID 帧
TID=0 的 P 帧只能参考 TID=0 的帧
TID=1 的 B 帧可参考 TID=0 和 TID=1
TID=2 的 b 帧可参考 TID≤2 的任意帧
2. 丢弃高 TID 帧不影响低 TID 帧解码
丢掉 TID=2 的帧 → TID=0/1 的帧参考关系不变, 正常解码
3. TID 信息在 NAL Header 中, 解码前即可判断
nuh_temporal_id_plus1: 3bit, 实际 TID = 此值 - 1
无需解析 Slice 内容就能决定是否跳过此帧
VPS 中的子层参数
VPS 为每个子层声明 DPB 容量:
vps_max_sub_layers = 3 (3个时间子层)
子层0: max_dec_pic_buffering=4, max_reorder_pics=2
子层1: max_dec_pic_buffering=6, max_reorder_pics=3
子层2: max_dec_pic_buffering=8, max_reorder_pics=4
→ 高子层需要更多 DPB 空间 (因为参考帧更多)
实际应用场景
| 场景 |
做法 |
效果 |
| 网络带宽波动 |
动态选择解码到哪个 TID |
自适应帧率, 不花屏 |
| 视频会议 |
低带宽只传 TID=0/1 |
保证基本流畅 |
| 流媒体 ABR |
服务器按带宽截断 TID |
无需转码, 直接裁剪码流 |
| 录像回放 |
快进只解码 TID=0 |
快速浏览, 极低计算量 |
与 H.264 的对比
H.264: 没有标准化的时间子层机制
要做时间可分级需要 SVC 扩展 (显著增加复杂度)
或简单丢B帧 (但参考关系可能被破坏)
H.265: 时间子层是基础规范的一部分
无需 SVC 扩展, 所有 Profile 都支持
NAL Header 直接携带 TID, 解码器可零开销跳帧
4. SPS 关键参数详解
SPS 影响整个视频序列,包含 HEVC 编码树结构的关键参数。
基本标识与图像尺寸
| 字段 |
编码 |
说明 |
seq_parameter_set_id |
ue(v) |
SPS 编号 (0~15) |
chroma_format_idc |
ue(v) |
色度格式: 0=Mono, 1=4:2:0, 2=4:2:2, 3=4:4:4 |
separate_colour_plane_flag |
u(1) |
独立色彩平面 (仅 chroma=3 时编码, 源码: chroma≠3时强制0) |
pic_width_in_luma_samples |
ue(v) |
图像宽度 (亮度像素) |
pic_height_in_luma_samples |
ue(v) |
图像高度 (亮度像素) |
conformance_window_flag |
u(1) |
是否有裁剪窗口 |
bit_depth_luma |
ue(v)+8 |
亮度位深 (8~16) |
bit_depth_chroma |
ue(v)+8 |
色度位深 (8~16) |
色度子采样表:
chroma_format_idc SubWidthC SubHeightC
0 (Mono) 1 1
1 (4:2:0) 2 2
2 (4:2:2) 2 1
3 (4:4:4) 1 1
分辨率计算:
宽度 = pic_width_in_luma_samples - SubWidthC×(conf_win_left + conf_win_right)
高度 = pic_height_in_luma_samples - SubHeightC×(conf_win_top + conf_win_bottom)
位深推导值 (compute_derived_values):
QpBdOffset_Y = 6 × (bit_depth_luma - 8) // 8bit→0, 10bit→12, 12bit→24
QpBdOffset_C = 6 × (bit_depth_chroma - 8)
ChromaArrayType = separate_colour_plane_flag ? 0 : chroma_format_idc
编码树结构 --- HEVC 最核心的改变
H.264: 固定 16×16 宏块, 虽有 8×8 分块但树结构简单
H.265: 灵活的编码树单元 (CTU), 最大 64×64, 四叉树递归分割
CTU (Coding Tree Unit) = CTB + 编码数据
CtbSizeY = 1 << Log2CtbSizeY (16/32/64)
一帧被划分为等大小的 CTU 网格
CTU → 四叉树分割 → CU (Coding Unit)
CU 大小: 8×8 ~ CtbSizeY
最小 CU: MinCbSizeY = 1 << Log2MinCbSizeY (8)
CTU 64×64
┌──┬──┬──┬──┐
│ │ │ │ │ 不分割: 1个 64×64 CU
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
└──┴──┴──┴──┘
┌──┬──┬──┬──┐
│ ┌┬┐│ │ │ 左上分割: 4个 32×32 CU
│ ├┼┤│ │ │ 其中一个继续分割: 4个 16×16 CU
│ └┴┘│ │ │
│ │ │ │ │
└──┴──┴──┴──┘
| 字段 |
编码 |
说明 |
log2_min_luma_coding_block_size |
ue(v) |
最小 CB 大小 log2 (3~6, 即8~64) |
log2_diff_max_min_luma_coding_block_size |
ue(v) |
CTB 与最小 CB 的 log2 差 |
log2_min_transform_block_size |
ue(v) |
最小 TU 大小 log2 (2~5, 即4~32) |
log2_diff_max_min_transform_block_size |
ue(v) |
最大与最小 TU 的 log2 差 |
max_transform_hierarchy_depth_inter |
ue(v) |
帧间 TU 最大递归深度 |
max_transform_hierarchy_depth_intra |
ue(v) |
帧内 TU 最大递归深度 |
推导值:
Log2MinCbSizeY = log2_min_luma_coding_block_size (3~6)
Log2CtbSizeY = Log2MinCbSizeY + log2_diff_max_min_CB (3~6, 即8~64, 源码显式拒绝>6)
CtbSizeY = 1 << Log2CtbSizeY (64)
PicWidthInCtbsY = ceil(宽度 / CtbSizeY)
PicHeightInCtbsY = ceil(高度 / CtbSizeY)
PicSizeInCtbsY = PicWidthInCtbsY × PicHeightInCtbsY
Log2MinTrafoSize = log2_min_transform_block_size (2~5)
Log2MaxTrafoSize = Log2MinTrafoSize + log2_diff_max_min_TU
Log2MaxTrafoSize ≤ min(Log2CtbSizeY, 5) (最大32×32)
Log2MinPUSize = Log2MinCbSizeY - 1 (即最小4×4 PU)
POC 与 DPB/参考帧
| 字段 |
编码 |
说明 |
log2_max_pic_order_cnt_lsb |
ue(v)+4 |
POC LSB 位数 (4~16) |
sps_sub_layer_ordering_info_present_flag |
u(1) |
是否对所有子层编码DPB参数 |
sps_max_sub_layers |
u(3)+1 |
时间子层数 (1~7) |
sps_max_dec_pic_buffering[i] |
ue(v)+1 |
每层DPB容量 (含当前帧) |
sps_max_num_reorder_pics[i] |
ue(v) |
每层最大重排序帧数 |
sps_max_latency_increase_plus1[i] |
ue(v) |
0=无延迟限制 |
子层DPB参数编码规则 (sps.cc line 285-327):
sps_sub_layer_ordering_info_present_flag=1: 从第0层开始编码 (所有层)
sps_sub_layer_ordering_info_present_flag=0: 仅编码最高层, 低层复制最高层值
SpsMaxLatencyPictures[i] = sps_max_num_reorder_pics[i] + sps_max_latency_increase_plus1[i] - 1
RPS (参考帧集) 在 SPS 中的预定义
SPS 中预定义 short_term_ref_pic_set (最多64个):
num_short_term_ref_pic_sets ue(v) (0~64)
for i = 0..num-1:
st_ref_pic_set(i) // 调用 read_short_term_ref_pic_set()
// i>0 时可用预测编码 (基于前一个RPS增量)
长期参考帧在 SPS 中的预定义:
long_term_ref_pics_present_flag u(1)
if (present):
num_long_term_ref_pics_sps ue(v) (0~32)
for i = 0..num-1:
lt_ref_pic_poc_lsb_sps[i] u(log2_max_pic_order_cnt_lsb)
used_by_curr_pic_lt_sps_flag[i] u(1)
编码工具开关
| 字段 |
编码 |
说明 |
amp_enabled_flag |
u(1) |
非对称运动分割 (AMP) |
sample_adaptive_offset_enabled_flag |
u(1) |
SAO 滤波 |
pcm_enabled_flag |
u(1) |
PCM 模式 |
PCM 参数 (pcm_enabled_flag=1 时)
| 字段 |
编码 |
说明 |
pcm_sample_bit_depth_luma |
u(4)+1 |
PCM 亮度位深 |
pcm_sample_bit_depth_chroma |
u(4)+1 |
PCM 色度位深 |
log2_min_pcm_luma_coding_block_size |
ue(v)+3 |
PCM 最小 CB log2 |
log2_diff_max_min_pcm_luma_coding_block_size |
ue(v) |
PCM CB 大小范围 |
pcm_loop_filter_disable_flag |
u(1) |
PCM 是否禁用环路滤波 |
Scaling List (量化矩阵)
| 字段 |
编码 |
说明 |
scaling_list_enable_flag |
u(1) |
启用自定义量化矩阵 |
sps_scaling_list_data_present_flag |
u(1) |
SPS 中是否含量化矩阵数据 |
Scaling List 数据结构 (4种尺寸 × 6个矩阵):
ScalingFactor_Size0[6][4][4] // 4×4 矩阵
ScalingFactor_Size1[6][8][8] // 8×8 矩阵
ScalingFactor_Size2[6][16][16] // 16×16 矩阵
ScalingFactor_Size3[6][32][32] // 32×32 矩阵
6个矩阵: 亮度帧内/色度帧内/亮度帧间/色度帧间/2×32×32特殊
若 sps_scaling_list_data_present_flag=0: 使用默认量化矩阵
编码工具开关 (续)
| 字段 |
编码 |
说明 |
sps_temporal_mvp_enabled_flag |
u(1) |
时间运动矢量预测 |
strong_intra_smoothing_enable_flag |
u(1) |
强帧内平滑 (32×32) |
vui_parameters_present_flag |
u(1) |
VUI 参数是否存在 |
SPS 扩展
| 字段 |
编码 |
说明 |
sps_extension_present_flag |
u(1) |
是否有扩展 |
sps_range_extension_flag |
u(1) |
Range 扩展 |
sps_multilayer_extension_flag |
u(1) |
多层扩展 |
sps_extension_6bits |
u(6) |
保留扩展位 |
Range 扩展字段 (sps_range_extension, RExt Profile):
transform_skip_rotation_enabled_flag u(1) 变换跳过旋转
transform_skip_context_enabled_flag u(1) 变换跳过上下文
implicit_rdpcm_enabled_flag u(1) 隐式RDPCM
explicit_rdpcm_enabled_flag u(1) 显式RDPCM
extended_precision_processing_flag u(1) 扩展精度
intra_smoothing_disabled_flag u(1) 禁用帧内平滑
high_precision_offsets_enabled_flag u(1) 高精度偏移
persistent_rice_adaptation_enabled_flag u(1) 持久Rice自适应
cabac_bypass_alignment_enabled_flag u(1) CABAC旁路对齐
5. PPS 关键参数详解
基本标识
| 字段 |
编码 |
说明 |
pic_parameter_set_id |
ue(v) |
PPS 编号 (0~63) |
seq_parameter_set_id |
ue(v) |
引用的 SPS 编号 |
Slice 控制
| 字段 |
编码 |
说明 |
dependent_slice_segments_enabled_flag |
u(1) |
依赖 Slice Segment |
output_flag_present_flag |
u(1) |
pic_output_flag 是否存在于 Slice Header |
num_extra_slice_header_bits |
u(3) |
额外 Slice Header 比特数 |
sign_data_hiding_flag |
u(1) |
符号隐藏 |
cabac_init_present_flag |
u(1) |
CABAC 初始化表选择 |
参考帧列表与 QP
| 字段 |
编码 |
说明 |
num_ref_idx_l0_default_active |
ue(v)+1 |
P Slice List0 默认长度 |
num_ref_idx_l1_default_active |
ue(v)+1 |
B Slice List1 默认长度 |
pic_init_qp |
se(v)+26 |
初始 QP (26为基准) |
cu_qp_delta_enabled_flag |
u(1) |
CU 级 QP 差值 |
diff_cu_qp_delta_depth |
ue(v) |
QP 差值的量化组深度 |
pic_cb_qp_offset |
se(v) |
Cb QP 偏移 |
pic_cr_qp_offset |
se(v) |
Cr QP 偏移 |
Tiles 与 WPP --- HEVC 并行解码核心
| 字段 |
编码 |
说明 |
tiles_enabled_flag |
u(1) |
启用 Tiles |
num_tile_columns |
ue(v)+1 |
Tile 列数 (1~10) |
num_tile_rows |
ue(v)+1 |
Tile 行数 (1~10) |
uniform_spacing_flag |
u(1) |
均匀间距 |
entropy_coding_sync_enabled_flag |
u(1) |
WPP (波前并行处理) |
Tiles: 将一帧划分为矩形区域, 每个 Tile 独立熵编码
┌──────┬──────┬──────┐
│Tile 0│Tile 1│Tile 2│ 3×2 Tiles
├──────┼──────┼──────┤
│Tile 3│Tile 4│Tile 5│
└──────┴──────┴──────┘
每个 Tile 可独立并行解码
跨 Tile 边界不进行帧间预测
WPP: 波前并行处理, 上一行第2个CTB解码完后即可开始下一行
Row 0: ■■■■■■■■
Row 1: ■■■■■■■■ ← 延迟2个CTB即可开始
Row 2: ■■■■■■■■
WPP 需要传输每行入口点偏移 (entry_point_offset)
CTB 地址映射 (PPS 计算):
CtbAddrRStoTS[]: 光栅扫描 → Tile 扫描
CtbAddrTStoRS[]: Tile 扫描 → 光栅扫描
TileId[]: 每个 CTB 的 Tile 编号
MinTbAddrZS[]: Z 扫描顺序 (用于帧内预测可用性判断)
去块滤波
| 字段 |
编码 |
说明 |
deblocking_filter_control_present_flag |
u(1) |
Slice 是否包含去块参数 |
deblocking_filter_override_enabled_flag |
u(1) |
Slice 是否可覆盖PPS去块参数 |
pic_disable_deblocking_filter_flag |
u(1) |
禁用去块滤波 |
beta_offset |
se(v)×2 |
β 偏移 |
tc_offset |
se(v)×2 |
tc 偏移 |
slice_segment_header() {
─── 基本标识 ───
first_slice_segment_in_pic_flag u(1)
本 Slice 是否是帧的第一个 Slice
if (RAP): no_output_of_prior_pics_flag u(1)
slice_pic_parameter_set_id ue(v)
引用的 PPS 编号
─── 依赖 Slice (PPS允许时) ───
if (!first_slice):
dependent_slice_segment_flag u(1)
1=依赖 Slice, 复制独立 Slice 的头部字段
slice_segment_address u(v)
位宽 = ceil(log2(PicSizeInCtbsY))
─── 独立 Slice ───
if (!dependent):
skip num_extra_slice_header_bits
slice_type ue(v)
0=B, 1=P, 2=I
if (output_flag_present_flag): pic_output_flag u(1)
if (separate_colour_plane): colour_plane_id u(2)
─── POC 与参考帧集 ───
if (!IDR):
slice_pic_order_cnt_lsb u(v)
位宽 = log2_max_pic_order_cnt_lsb
short_term_ref_pic_set_sps_flag u(1)
if (!sps_flag): slice_ref_pic_set() // 内联RPS
long_term_ref_pics...
slice_temporal_mvp_enabled_flag u(1)
─── SAO ───
if (sao_enabled):
slice_sao_luma_flag u(1)
slice_sao_chroma_flag u(1)
─── 参考帧列表 ───
if (P/B):
num_ref_idx_active_override_flag u(1)
if (override): num_ref_idx_l0/l1 ue(v)
ref_pic_list_modification()
if (B): mvd_l1_zero_flag u(1)
cabac_init_flag u(1)
if (temporal_mvp):
collocated_from_l0_flag u(1)
collocated_ref_idx ue(v)
─── 加权预测 ───
if (weighted): pred_weight_table()
─── Merge ───
five_minus_max_num_merge_cand ue(v)
MaxNumMergeCand = 5 - 此值
─── QP ───
slice_qp_delta se(v)
SliceQPY = pic_init_qp + slice_qp_delta
─── 色度 QP 偏移 ───
if (pps_slice_chroma_qp_offsets_present):
slice_cb_qp_offset se(v)
slice_cr_qp_offset se(v)
─── 去块滤波 ───
if (deblocking_filter_control):
deblocking_filter_override_flag u(1)
if (override):
slice_deblocking_filter_disabled_flag u(1)
if (!disabled): slice_beta_offset, slice_tc_offset se(v)
─── 入口点 (Tiles/WPP) ───
if (tiles || WPP):
num_entry_point_offsets ue(v)
offset_len ue(v)
entry_point_offset[] u(v)
}
Slice 类型
| slice_type |
名称 |
说明 |
| 0 |
B |
双向预测 |
| 1 |
P |
前向预测 |
| 2 |
I |
帧内预测 |
注意: H.265 中 B=0, P=1, I=2 (H.264 中 I=2, P=0, B=1, 顺序不同)
7. CTU/CU/PU/TU 编码树结构
四叉树递归分割
CTU (Coding Tree Unit): 固定大小 (16/32/64), 一帧的基本划分单元
└── 四叉树递归分割
CU (Coding Unit): 8×8 ~ 64×64, 编码的基本单元
每个 CU 包含两套独立的划分树, 覆盖同一像素区域但互不约束:
┌─ PU 树 (Prediction Unit): 预测的基本单元
│ 帧内: 2Nx2N / NxN (仅8×8 CU)
│ 帧间: 8种分块模式 (见下)
│
└─ TU 树 (Transform Unit): 变换的基本单元
4×4 ~ 32×32, 独立的变换和量化单元
PU 与 TU 的关系:
1. 两套树覆盖同一 CU 区域, 但划分方式独立
2. TU 可以跨越 PU 边界 (一个 TU 可覆盖两个 PU 的部分像素)
3. PU 边界不约束 TU 划分, 反之亦然
4. 例: 16×16 CU, PartMode=2NxN (两个8×16 PU)
TU 树可以 16×16 不分裂 → 一个 TU 覆盖两个 PU
CU 16×16, PartMode=2NxN:
┌───────────────┐ ┌───────────────┐
│ PU 0 │ │ │
├───────────────┤ │ TU (16×16) │ ← 一个 TU 覆盖两个 PU
│ PU 1 │ │ │
└───────────────┘ └───────────────┘
PU 划分 TU 划分 (可跨越PU边界)
与 H.264 的关键区别:
H.264: 预测和变换在同一个宏块/子块上执行, 预测块=变换块
宏块 16×16 → 整块做预测 + 整块做变换
或 8×8 子块 → 同一个 8×8 既做预测又做变换
H.265: 预测和变换解耦为两套独立的划分树
PU 决定"怎么预测", TU 决定"怎么变换", 两者大小和边界可以不同
灵活性: 大块预测 (减少运动矢量开销) + 小块变换 (更好捕捉残差细节)
或小块预测 (精细运动补偿) + 大块变换 (稀疏残差高效压缩)
实际场景: 32×32 CU, 仅有局部关键细节, 其余区域平坦
PU: 整块 2Nx2N 做一次预测 (1组运动矢量)
TU: 细节区域分裂为 8×8 小块精确编码残差,
平坦区域保持 16×16 或 32×32 大块 (残差接近零, 几乎不占码率)
┌───────────────────────┐ ┌────┬────┬───────────────┐
│ │ │8×8 │8×8 │ │
│ PU (32×32) │ │细节│细节│ 16×16 TU │
│ ▓▓局部细节 │ ──→ ├────┴────┤ (平坦, 残差≈0)│
│ │ │ 16×16 │ │
│ │ │ TU │ │
└───────────────────────┘ └─────────┴───────────────┘
一次预测 TU 按内容自适应分裂
若用 H.264 方式 (预测=变换): 要么整块 32×32 变换丢失细节,
要么全部拆成小块浪费码率编码平坦区域
分块模式 (PartMode)
帧内 CU:
| PartMode |
名称 |
PU 大小 |
适用 CU 大小 |
| PART_2Nx2N |
不分割 |
1个 NxN PU |
所有 |
| PART_NxN |
四等分 |
4个 (N/2)x(N/2) PU |
仅 8×8 CU |
帧间 CU:
| PartMode |
名称 |
PU 布局 |
示意 (16×16 CU) |
| 0 |
PART_2Nx2N |
1个整块 |
整块16×16 |
| 1 |
PART_2NxN |
上下两半 |
上8×16 + 下8×16 |
| 2 |
PART_Nx2N |
左右两半 |
左16×8 + 右16×8 |
| 3 |
PART_NxN |
四等分 (仅8×8) |
4个4×4 |
| 4 |
PART_2NxnU |
上1/4+下3/4 |
上4×16 + 下12×16 |
| 5 |
PART_2NxnD |
上3/4+下1/4 |
上12×16 + 下4×16 |
| 6 |
PART_nLx2N |
左1/4+右3/4 |
左16×4 + 右16×12 |
| 7 |
PART_nRx2N |
左3/4+右1/4 |
左16×12 + 右16×4 |
AMP (Asymmetric Motion Partition): PartMode 4~7, 需要 amp_enabled_flag=1
预测模式
| PredMode |
含义 |
| MODE_INTRA |
帧内预测 |
| MODE_INTER |
帧间预测 |
| MODE_SKIP |
跳过模式 (merge_idx + 无残差) |
CU 解码流程
读取 split_cu_flag
│
├─ 1 (分裂) → 四叉树递归, 4个子CU各占1/4
│
└─ 0 (不分裂) → 当前CU
├─ 读取 PredMode (Intra/Inter/Skip)
├─ 读取 PartMode
├─ 解码 PU (预测信息: 帧内模式 / 运动矢量)
└─ 解码 TU (残差: 变换树递归)
读取 split_transform_flag
├─ 1 → 4个子TU
└─ 0 → 当前TU: 解码残差系数
8. 帧内预测
预测模式
HEVC 帧内预测从 H.264 的 9 种扩展到 35 种:
| 模式值 |
名称 |
方向/说明 |
| 0 |
INTRA_PLANAR |
双线性插值 (面预测) |
| 1 |
INTRA_DC |
均值预测 |
| 2~34 |
INTRA_ANGULAR_2~34 |
33种角度预测 |
角度预测方向示意:
Mode 2~10: 垂直方向 (左参考列投影)
Mode 10: 纯垂直
Mode 11~17: 垂直偏水平
Mode 18: 纯水平 (实际 angle=-32)
Mode 19~26: 水平偏垂直
Mode 26: 纯水平
Mode 27~34: 水平方向 (上参考行投影)
intraPredAngle_table[0..34]:
{0, 0, 32, 26, 21, 17, 13, 9, 5, 2, 0,
-2, -5, -9, -13, -17, -21, -26, -32,
-26, -21, -17, -13, -9, -5, -2, 0,
2, 5, 9, 13, 17, 21, 26, 32}
MPM (Most Probable Mode) 候选
从邻居 A(左) 和 B(上) 推导 3 个 MPM 候选:
if (A == B):
if (A < 2): candModeList = {PLANAR, DC, ANGULAR_26}
else: candModeList = {A, ((A-2-1+32)%32)+2, ((A-2+1)%32)+2}
// A本身 + 两个相邻角度
if (A != B):
candModeList[0] = A
candModeList[1] = B
candModeList[2] = 第一个不在[0][1]中的: {PLANAR, DC, ANGULAR_26}
编码: 若模式在MPM中 → 传 mpm_idx (0/1/2)
否则 → 传 rem_intra_luma_pred_mode (5 bit, 32种剩余模式)
色度帧内预测
5种色度候选模式:
cand[0] = INTRA_PLANAR
cand[1] = INTRA_ANGULAR_26 (水平)
cand[2] = INTRA_ANGULAR_10 (垂直)
cand[3] = INTRA_DC
cand[4] = 对应亮度模式 (LIKE_LUMA)
替换规则: 若亮度模式与cand[0..3]之一相同 → 该候选替换为ANGULAR_34
编码: IntraChromaPredMode (3 bit)
0→cand[0], 1→cand[1], 2→cand[2], 3→cand[3], 4→cand[4]
参考样本滤波
滤波条件 (帧内预测样本滤波, Spec 8.4.4.2.3):
nT=4: DC模式不滤波, 其他看 minDistVerHor
nT=8: minDistVerHor > 7 时滤波
nT=16: minDistVerHor > 1 时滤波
nT=32: minDistVerHor > 0 时滤波 (基本都滤波)
nT=64: 不滤波
minDistVerHor = min(|mode-26|, |mode-10|)
强帧内平滑 (32×32亮度块):
当 strong_intra_smoothing_enable_flag=1 且:
|p[0] + p[64] - 2×p[32]| < (1 << (bit_depth - 5))
|p[0] + p[-64] - 2×p[-32]| < (1 << (bit_depth - 5))
→ 双线性插值代替3抽头滤波
系数扫描顺序
scanIdx 选择 (由帧内模式和TU大小决定):
log2TrafoSize==2 或 (log2TrafoSize==3 且 亮度/4:4:4):
Mode 6~14: 垂直扫描 (scanIdx=2)
Mode 22~30: 水平扫描 (scanIdx=1)
其他: 对角扫描 (scanIdx=0)
更大TU: 始终对角扫描 (scanIdx=0)
三种扫描:
对角 (Diagonal): 右上zigzag
水平 (Horizontal): 行优先
垂直 (Vertical): 列优先
9. 帧间预测
Merge 模式 --- HEVC 新增的核心模式
Merge 候选列表构建 (Spec 8.5.3.1.1):
Step 1: 空间候选 (最多4个)
┌──┐
│B2│ ┌──┬──┐
└──┘ │B1│B0│
└──┴──┘
┌──┐ ┌───────────┐
│A1│ │ 当前 PU │
└──┘ └───────────┘
│A0│
└──┘
优先级: A1 → B1 → B0 → A0 → B2
去重: B1若==A1跳过, B0若==B1跳过, A0若==A1跳过, B2若==A1或B1跳过
Step 2: 时间候选 (最多1个)
从同位置帧 (collocated picture) 取 MV
位置优先: (xP+nPbW, yP+nPbH) 底右角, 回退中心
Step 3: 组合双向候选 (B Slice, 最多5个)
从已有的 L0 和 L1 候选组合, 使用 table_8_19 配对
Step 4: 零运动矢量候选
用零MV + refIdx=0 填充剩余位置
MaxNumMergeCand = 5 - five_minus_max_num_merge_cand (1~5)
编码: merge_flag=1 + merge_idx (0~MaxNumMergeCand-1)
AMVP (Advanced Motion Vector Prediction)
AMVP 候选推导 (每个列表最多2个):
空间候选:
A组: A0(xP-1, yP+nPbH), A1(xP-1, yP+nPbH-1)
B组: B0(xP+nPbW, yP-1), B1(xP+nPbW-1, yP-1), B2(xP-1, yP-1)
推导逻辑:
1. 从A组取第一个可用候选
2. 从B组取第一个可用候选
3. 若两个都可用 → 2个候选
4. 若只有一个可用 → 补充时间候选
5. 若都不可用 → 2个零MV候选
编码:
mvp_l0_flag / mvp_l1_flag: 选择哪个候选 (0或1)
mvd[0..1]: 运动矢量差值 (MVD = MV - MVP)
运动补偿 --- 1/4 像素插值
Luma (同 H.264):
整像素: 直接取值
1/2像素: 8抽头滤波 (H.264 是 6抽头)
1/4像素: 双线性插值
Chroma:
1/8 像素精度 (同 H.264)
关键区别:
H.264 半像素: 6抽头 [1,-5,20,20,-5,1]/32
H.265 半像素: 8抽头滤波器, 更高精度
时间 MV 预测
同位置帧选择:
B Slice + collocated_from_l0_flag==0: L1[collocated_ref_idx]
其他: L0[collocated_ref_idx]
MV 缩放:
td = POC(ColPic) - POC(ColRef)
tb = POC(Cur) - POC(ListX[ref_idx])
tx = (16384 + (|td|>>1)) / td
distScaleFactor = Clip3(-4096, 4095, (tb×tx+32)>>6)
scaledMV = Clip3(-32768, 32767, Sign(distScaleFactor×mv) × ((|distScaleFactor×mv|+127)>>8))
加权预测
P Slice (weighted_pred_flag=1):
pred = ((w0 × ref + (1 << (log2WD-1))) >> log2WD) + offset
B Slice (weighted_bipred_flag=1):
pred = ((w0 × L0 + (1 << (log2WD-1))) >> log2WD) +
((w1 × L1 + (1 << (log2WD-1))) >> log2WD) + offset
log2WD = luma_log2_weight_denom (0~7)
权重限制: sumWeightFlags ≤ 24
加权预测表解析 (pred_weight_table)
luma_log2_weight_denom: ue(v), 范围 [0,7]
delta_chroma_log2_weight_denom: se(v), ChromaLog2WeightDenom = luma_log2_weight_denom + delta
对每个参考帧列表 L0 (P/B) / L1 (仅B):
luma_weight_flag[list][i]: u(1), 是否启用亮度加权
chroma_weight_flag[list][i]: u(1), 是否启用色度加权 (Cb/Cr)
若 luma_weight_flag=1:
delta_luma_weight: se(v), LumaWeight = (1<<log2WD) + delta, 范围 [-128,127]
luma_offset: se(v), 范围 [-WpOffsetHalfRangeY, WpOffsetHalfRangeY-1]
若 luma_weight_flag=0 (默认):
LumaWeight = 1<<log2WD, luma_offset = 0
若 chroma_weight_flag=1:
对 j=0,1 (Cb/Cr):
delta_chroma_weight: se(v), ChromaWeight = (1<<ChromaLog2WD) + delta
delta_chroma_offset: se(v), 含隐式偏移计算
若 chroma_weight_flag=0 (默认):
ChromaWeight = 1<<ChromaLog2WD, ChromaOffset = 0
存储结构: LumaWeight[2][16], luma_offset[2][16]
ChromaWeight[2][16][2], ChromaOffset[2][16][2]
[列表L0/L1][参考帧索引][Cb/Cr]
10. 变换与逆量化
变换类型 --- HEVC 新增 DST
| 块大小 |
变换类型 |
适用场景 |
| 4×4 亮度帧内 |
DST (离散正弦变换) |
trType=1, 仅帧内亮度4×4 |
| 4×4 其他 |
DCT |
trType=0 |
| 8×8 |
DCT |
|
| 16×16 |
DCT |
|
| 32×32 |
DCT |
|
HEVC 关键改进:
H.264: 所有块统一使用 4×4 整数DCT (或8×8, High Profile)
H.265: 帧内4×4亮度块使用 DST, 统计上更匹配帧内残差分布
DCT 支持 4×4 到 32×32 (H.264 最大8×8)
变换跳过 (transform_skip_enabled_flag):
跳过变换, 直接传残差 → 适合屏幕内容编码
QP 推导
SliceQPY = pic_init_qp + slice_qp_delta
量化组 (QG) 级 QP:
Log2MinCuQpDeltaSize = Log2CtbSizeY - diff_cu_qp_delta_depth
QPY = ((qPY_PRED + CuQpDelta + 52 + 2×QpBdOffset_Y) % (52 + QpBdOffset_Y)) - QpBdOffset_Y
色度 QP 映射 (4:2:0, table8_22):
QP: 0..29 30 31 32 33 34 35 36 37 38 39 40 41 42 43..51
CQP: 0..29 29 30 31 32 33 33 34 34 35 35 36 36 37 QP-6
量化步长缩放表:
QP%6: 0 1 2 3 4 5
Scale: 40 45 51 57 64 72
QP每+6 → 步长翻倍
逆量化
无量化矩阵:
bdShift = BitDepth + Log2(nT) - 5 - 4
fact = levelScale[QP%6] << (QP/6)
coeff = Clip3(-32768, 32767, (currCoeff × fact + offset) >> bdShift)
有量化矩阵:
m_x_y 取自 ScalingFactor_Size0..3[matrixID] 中的对应位置
MatrixID: 帧内=cIdx, 帧间=cIdx+3 (nT<32) 或 cIdx+1 (nT==32)
重建
recon = Clip_BitDepth(pred + residual)
pred: 预测值
residual: 逆量化+逆变换后的残差
11. 熵解码:CABAC
HEVC 去掉了 CAVLC,所有 Profile 统一使用 CABAC。
CABAC 上下文模型 (172个)
| 语法元素 |
起始索引 |
数量 |
| SAO merge/type |
0 |
2 |
| split_cu_flag |
2 |
3 |
| cu_skip_flag |
5 |
3 |
| part_mode |
8 |
4 |
| prev_intra_luma_pred_flag |
12 |
1 |
| intra_chroma_pred_mode |
13 |
1 |
| cbf_luma |
14 |
2 |
| cbf_chroma |
16 |
4 |
| split_transform_flag |
20 |
3 |
| cu_chroma_qp_offset_flag |
23 |
1 |
| cu_chroma_qp_offset_idx |
24 |
1 |
| last_significant_coeff_x/y_prefix |
25/43 |
18+18 |
| coded_sub_block_flag |
61 |
4 |
| significant_coeff_flag |
65 |
44 |
| coeff_abs_level_greater1/2 |
109/133 |
24+6 |
| cu_qp_delta_abs |
139 |
2 |
| transform_skip_flag |
141 |
2 |
| rdpcm_flag |
143 |
2 |
| rdpcm_dir |
145 |
2 |
| merge_flag/idx |
147/148 |
2 |
| pred_mode_flag |
149 |
1 |
| abs_mvd_greater0/1 |
150 |
2 |
| mvp_lx_flag |
152 |
1 |
| rqt_root_cbf |
153 |
1 |
| ref_idx_lx |
154 |
2 |
| inter_pred_idc |
156 |
5 |
| cu_transquant_bypass_flag |
161 |
1 |
| log2_res_scale_abs_plus1 |
162 |
8 |
| res_scale_sign_flag |
170 |
2 |
| 总计 |
--- |
172 |
CABAC 初始化
初始化类型 (initType):
I Slice: initType = 0
P Slice: initType = cabac_init_flag + 1 (1 或 2)
B Slice: initType = 2 - cabac_init_flag (1 或 2)
每个上下文: state(7bit) + MPSbit(1bit)
根据 initType + SliceQPY 初始化
残差编码流程
1. rqt_root_cbf → 是否有非零残差
2. split_transform_flag → TU 四叉树递归
3. cbf_luma / cbf_chroma → 是否有非零系数
4. last_significant_coeff_x/y → 最后非零系数位置
5. coded_sub_block_flag → 子块是否有非零系数
6. significant_coeff_flag → 逐位置非零标记
7. coeff_abs_level_greater1/2_flag → 幅值>1/>2 标记
8. coeff_abs_remaining → 剩余幅值 (Exp-Golomb)
9. coeff_sign_flag → 符号 (bypass)
12. 去块滤波
边界强度 bS
| bS |
条件 |
| 2 |
一侧或两侧为帧内预测 |
| 1 |
变换块边界 + 非零系数, 或 MV差≥4, 或 refIdx 不同 |
| 0 |
不滤波 |
注意: HEVC 的 bS 只有 0/1/2 三级 (H.264 有 0~4 五级)
Beta 和 tc 查找表
beta[52]: { 0,0,...,0, 6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,24,
26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64 }
索引: QP + beta_offset, 范围 [0,51]
tc[54]: { 0,0,...,0, 1,1,1,1,1,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,
5,5,6,6,7,8,9,10,11,13,14,16,18,20,22,24 }
索引: QP + 2×(bS-1) + tc_offset, 范围 [0,53]
Luma 滤波
决策: 计算 d, dp0, dp3, dq0, dq3
if (d < beta):
强滤波判断: dSam0 && dSam3 → dE=2 (强滤波)
否则 → dE=1 (弱滤波)
强滤波 (修改3个像素):
p0' = Clip3(p0-2×tc, p0+2×tc, (p2+2×p1+2×p0+2×q0+q1+4)>>3)
p1' = Clip3(p1-2×tc, p1+2×tc, (p2+p1+p0+q0+2)>>2)
p2' = Clip3(p2-2×tc, p2+2×tc, (2×p3+3×p2+p1+p0+q0+4)>>3)
q0' = Clip3(q0-2×tc, q0+2×tc, (p1+2×p0+2×q0+2×q1+q2+4)>>3)
q1' = Clip3(q1-2×tc, q1+2×tc, (p0+q0+q1+q2+2)>>2)
q2' = Clip3(q2-2×tc, q2+2×tc, (p0+q0+q1+3×q2+2×q3+4)>>3)
弱滤波 (修改1~2个像素):
delta = Clip3(-tc, tc, (9×(q0-p0) - 3×(q1-p1) + 8)>>4)
p0' = Clip_BitDepth(p0 + delta)
q0' = Clip_BitDepth(q0 - delta)
if (dEp): p1' = p1 + Clip3(-tc/2, tc/2, ((p2+p0+1)>>1 - p1 + delta)>>1)
if (dEq): q1' = q1 + Clip3(-tc/2, tc/2, ((q2+q0+1)>>1 - q1 - delta)>>1)
Chroma 滤波
仅在 bS==2 (帧内边界) 时才对色度滤波:
delta = Clip3(-tc, tc, (4×(q0-p0) + p1-q1 + 4)>>3)
p0' = Clip_BitDepth(p0 + delta)
q0' = Clip_BitDepth(q0 - delta)
滤波顺序
1. 所有垂直边界 → CTB_PROGRESS_DEBLK_V
2. 所有水平边界 → CTB_PROGRESS_DEBLK_H
3. 逐 CTB 行进行, 支持并行
13. SAO (Sample Adaptive Offset) --- HEVC 新增
SAO 是 HEVC 新增的环路滤波器,H.264 中没有。它对去块滤波后的像素做进一步的样本级补偿。
SAO 类型
| SaoTypeIdx |
名称 |
说明 |
| 0 |
不应用 |
跳过SAO |
| 1 |
Band Offset (BO) |
按像素值区间补偿 |
| 2 |
Edge Offset (EO) |
按边缘方向补偿 |
Edge Offset (EO)
4种边缘方向 (SaoEoClass):
0: 水平 → 比较左右邻居
1: 垂直 → 比较上下邻居
2: 135° → 比较左上右下邻居
3: 45° → 比较右上左下邻居
边缘索引计算:
edgeIdx = Sign(curr - neighbor1) + Sign(curr - neighbor2)
结果: -2(两端都小), -1(一端小), 0(相等), 1(一端大), 2(两端都大)
偏移映射:
edgeIdx: -2 -1 0 1 2
offset: Val[0] Val[1] 0 Val[2] Val[3]
注意: EO 模式符号由 edgeIdx 隐含确定
Val[0] 和 Val[1] >= 0 (提升极小值, edgeIdx=-2/-1)
Val[2] 和 Val[3] <= 0 (抑制极大值, edgeIdx=1/2)
编码时只传绝对值, 符号由解码器按 sign[0,1]=+1, sign[2,3]=-1 还原
Band Offset (BO)
将像素范围分为 32 个 band (bandShift = bitDepth - 5)
sao_band_position: 连续4个 band 被选中
对选中 band 的像素: 加上对应的 saoOffsetVal[0..3]
对未选中 band: 不加偏移
例: 8bit, bandShift=3, sao_band_position=10
pixel >> 3 == 10 → 加 Val[0]
pixel >> 3 == 11 → 加 Val[1]
pixel >> 3 == 12 → 加 Val[2]
pixel >> 3 == 13 → 加 Val[3]
其他 band → 不加
SAO 语法 (per CTB)
sao_merge_left_flag: 是否复用左侧CTB的SAO参数
sao_merge_up_flag: 是否复用上方CTB的SAO参数
SaoTypeIdx: 0/1/2 (按分量编码, 2bit per component)
SaoEoClass: 0~3 (仅EO模式)
sao_band_position: 0~31 (仅BO模式)
saoOffsetVal[cIdx][0..3]: 偏移值 (有符号)
14. 参考帧管理
DPB (Decoded Picture Buffer)
DPB 最大容量:
由 sps_max_dec_pic_buffering[temporal_id] 决定
Level 4.0: 约 4 帧
Level 5.1: 约 16 帧
帧状态:
UnusedForReference: 空闲, 可分配
UsedForShortTermReference: 短期参考
UsedForLongTermReference: 长期参考
参考帧集 (Reference Picture Set)
HEVC 使用 RPS (参考帧集) 替代 H.264 的滑窗/MMCO 机制,更灵活且显式传输。
数据结构
ref_pic_set {
DeltaPocS0[16]: S0列表: POC < 当前帧的参考帧, 按|DeltaPoc|升序
DeltaPocS1[16]: S1列表: POC > 当前帧的参考帧, 按|DeltaPoc|升序
UsedByCurrPicS0[16]: S0中每帧是否被当前帧引用
UsedByCurrPicS1[16]: S1中每帧是否被当前帧引用
NumNegativePics: S0长度 (POC比当前帧小的参考帧数)
NumPositivePics: S1长度 (POC比当前帧大的参考帧数)
NumDeltaPocs: 派生值 = NumNegativePics + NumPositivePics
NumPocTotalCurr_shortterm_only: 短期参考中被当前帧引用的总数
}
DeltaPoc = 参考帧POC - 当前帧POC
S0: DeltaPoc < 0 (过去帧)
S1: DeltaPoc > 0 (未来帧)
编码模式A: 直接编码 (stRpsIdx0 或 inter_ref_pic_set_prediction_flag0)
NumNegativePics // ue(v)
NumPositivePics // ue(v)
for i = 0 .. NumNegativePics-1:
delta_poc_s0_minus1[i] // ue(v), 累积差分编码
used_by_curr_pic_s0_flag[i] // u(1)
for i = 0 .. NumPositivePics-1:
delta_poc_s1_minus1[i] // ue(v), 累积差分编码
used_by_curr_pic_s1_flag[i] // u(1)
DeltaPoc 是累积差分 (每项相对前一项, 非相对当前帧):
DeltaPocS0[0] = -(delta_poc_s0_minus1[0] + 1)
DeltaPocS0[i] = DeltaPocS0[i-1] - (delta_poc_s0_minus1[i] + 1) (i>0)
→ S0 的 DeltaPoc 递减 (绝对值递增)
S1 同理但符号为正, DeltaPoc 递增
编码模式B: 预测编码 (inter_ref_pic_set_prediction_flag==1)
核心思想: 在已有RPS基础上偏移 DeltaRPS, 筛选保留部分条目。
// 选择参考RPS
if (slice header中):
delta_idx_minus1 // 选择哪个SPS RPS作为基础
else (SPS中):
始终基于 RPS[stRpsIdx - 1]
// 偏移量
delta_rps_sign // u(1)
abs_delta_rps_minus1 // ue(v)
DeltaRPS = (1 - 2*delta_rps_sign) * (abs_delta_rps_minus1 + 1)
// 逐条目决定是否保留 (共 NumDeltaPocs_of_ref + 1 个条目)
for j = 0 .. NumDeltaPocs_of_ref:
used_by_curr_pic_flag[j] // u(1)
if (!used_by_curr_pic_flag[j]):
use_delta_flag[j] // u(1)
// 条目j保留条件: used_by_curr_pic_flag[j]==1 || use_delta_flag[j]==1
// 构建新S0 (收集 DeltaPoc+DeltaRPS < 0 的条目):
// 遍历顺序: S1逆序 → 虚拟条目(0) → S0正序
// 构建新S1 (收集 DeltaPoc+DeltaRPS > 0 的条目):
// 遍历顺序: S0逆序 → 虚拟条目(0) → S1正序
// 虚拟条目代表源RPS自身位置偏移后可能产生的新条目
RPS 在码流中的位置
| 位置 |
说明 |
| SPS |
定义 num_short_term_ref_pic_sets 个RPS, 编号0~N-1, 所有slice共享 |
| Slice Header |
short_term_ref_pic_set_sps_flag==0 时内联RPS; 否则用 short_term_ref_pic_set_idx 引用SPS中的 |
| Slice Header |
预测编码时, delta_idx_minus1 选择SPS中哪个RPS作为基础 (仅slice中有效, SPS中固定为stRpsIdx-1) |
示例
当前帧 POC=5, SPS RPS[2] 直接编码:
NumNegativePics=2, NumPositivePics=1
S0: delta_poc=[-1, -3], used=[1, 0]
S1: delta_poc=[2], used=[1]
解码结果:
DeltaPocS0 = [-1, -3] → POC 4, 2
DeltaPocS1 = [2] → POC 7
PocStCurrBefore = {4} (S0中used=1)
PocStCurrAfter = {7} (S1中used=1)
PocStFoll = {2} (S0中used=0, 保留在DPB但当前不参考)
若 RPS[3] 用预测编码基于 RPS[2], DeltaRPS=-1:
原条目偏移: POC 4→3, 2→1, 7→6
加虚拟条目 (DeltaPoc=0 偏移到 POC 4)
根据 use_delta_flag 筛选后分入新的 S0/S1
参考帧列表构建
五类参考帧:
PocStCurrBefore: POC < 当前帧的短期参考 (被当前帧使用)
PocStCurrAfter: POC > 当前帧的短期参考 (被当前帧使用)
PocStFoll: 不被当前帧使用的短期参考 (跟随帧, 保留在DPB供后续帧引用)
PocLtCurr: 被当前帧使用的长期参考
PocLtFoll: 不被当前帧使用的长期参考
List0 构建 (P Slice):
StCurrBefore → StCurrAfter → LtCurr
List0 构建 (B Slice):
StCurrBefore → StCurrAfter → LtCurr
List1 构建 (B Slice):
StCurrAfter → StCurrBefore → LtCurr
(与 List0 顺序相反, 确保前向后向都有最近参考帧)
ref_pic_list_modification: 可重排序列表顺序
参考帧列表修改 (ref_pic_list_modification)
默认构建的 RefPicList 可能不是最优顺序, 编码器可通过修改重排列表:
Slice Header 语法:
ref_pic_list_modification_flag_l0: u(1), 是否修改 List0
ref_pic_list_modification_flag_l1: u(1), 是否修改 List1 (B Slice)
若 flag=1:
while (modification_of_pic_nums_idc != 3): // 3 = 结束标记
modification_of_pic_nums_idc: ue(v)
0: short_term_pic_num_diff → 修改短期参考帧位置
1: long_term_pic_num → 修改长期参考帧位置
2: POC差值 (HEVC新增) → 按POC定位长期参考
修改过程:
1. 从默认列表中取出指定参考帧
2. 放到列表最前面
3. 后续帧依次后移 (去重)
4. 重复直到 idc==3 结束
用途: 将最优参考帧移到列表前端, 减少ref_idx编码位数
PU 级参考帧索引
RPS → RefPicList 构建完成后, 每个 PU 通过 ref_idx_lx 索引从列表中取参考帧:
CU (编码单元)
├── PU 0: inter_pred_idc=PRED_L0 → ref_idx_l0=2, mvd_l0, ...
├── PU 1: inter_pred_idc=PRED_L1 → ref_idx_l1=0, mvd_l1, ...
└── PU 2: inter_pred_idc=PRED_BI → ref_idx_l0=1, ref_idx_l1=3, mvd_l0, mvd_l1
- P Slice: 仅 ref_idx_l0, 指向 RefPicList0
- B Slice: ref_idx_l0 / ref_idx_l1 / 两者 (BI预测), 由 inter_pred_idc 决定
- ref_idx_lx: 告诉解码器去 RefPicListx 的第几个位置取参考帧
H.264 对比: H.264 的参考索引粒度是 mb_part (宏块分区), H.265 是 PU (预测单元);
H.264 用固定16×16宏块, H.265 用 CTU→CU→PU 递归划分。
完整链路:
SPS RPS定义 → Slice Header选RPS → 构建RefPicList0/1 → PU通过ref_idx_lx索引取参考帧 → 运动补偿
POC 计算
HEVC 只有一种 POC 计算方式 (H.264 有 Type 0/1/2 三种):
MaxPicOrderCntLsb = 1 << log2_max_pic_order_cnt_lsb
if (IRAP && NoRaslOutputFlag):
PicOrderCntMsb = 0
else:
if (slice_poc_lsb < prevPicOrderCntLsb &&
prev - slice >= MaxPicOrderCntLsb/2):
PicOrderCntMsb = prevPicOrderCntMsb + MaxPicOrderCntLsb
elif (slice_poc_lsb > prevPicOrderCntLsb &&
slice - prev > MaxPicOrderCntLsb/2):
PicOrderCntMsb = prevPicOrderCntMsb - MaxPicOrderCntLsb
else:
PicOrderCntMsb = prevPicOrderCntMsb
POC = PicOrderCntMsb + slice_pic_order_cnt_lsb
更新 (仅 TID==0 且非子层非参考且非RASL/RADL):
prevPicOrderCntLsb = slice_pic_order_cnt_lsb
prevPicOrderCntMsb = PicOrderCntMsb
15. 解码总体流程
输入码流
│
▼
① NAL 提取 (搜索起始码, 去除防竞争字节)
│
▼
② NAL Header 解析 → 区分 VCL / 非VCL, 提取 Type/LayerId/TID
│
├── VPS/SPS/PPS/SEI → 存入参数集上下文
│
▼ (VCL)
③ Slice Segment Header 解析 → POC, RPS, 参考帧列表
│
▼
④ 逐 CTB 解码 (CTU 循环)
│ ├─ 四叉树分割 → CU
│ ├─ CU 解码: PredMode, PartMode
│ ├─ PU 解码: 帧内模式 / Merge / AMVP + MV
│ └─ TU 解码: 残差系数 (CABAC)
│
▼
⑤ 逆量化 + 逆变换 (DST/DCT)
│
▼
⑥ 重建 = 预测 + 残差
│
▼
⑦ 去块滤波
│
▼
⑧ SAO 滤波 (HEVC 新增)
│
▼
⑨ 参考帧管理 (RPS)
│
▼
⑩ 帧输出 + POC 重排序
15.1 并行解码机制
HEVC 原生支持三种并行解码模式, 无需额外扩展:
WPP (Wavefront Parallel Processing)
PPS 标志: entropy_coding_sync_enabled_flag = 1
每行 CTB 由独立线程解码, 行间仅需 2 个 CTB 延迟:
Thread 0: CTB0 CTB1 CTB2 CTB3 ...
Thread 1: CTB4 CTB5 CTB6 ... ← 等 CTB2 完成后启动
Thread 2: CTB8 CTB9 ... ← 等 CTB6 完成后启动
同步点: 每行第2个CTB解码完成后, 将 CABAC 上下文状态保存,
下一行线程加载此状态作为初始值
入口点: Slice Header 中 num_entry_point_offsets + entry_point_offset[]
指定每行 CTB 数据的起始偏移, 实现行级并行解码
Tile 并行
PPS 标志: tiles_enabled_flag = 1
将图像划分为矩形 Tile, 各 Tile 完全独立编码/解码:
┌──────────┬──────────┐
│ Tile 0 │ Tile 1 │ 各 Tile 拥有独立的 CABAC 上下文
├──────────┼──────────┤ 可完全并行, 无需行间同步
│ Tile 2 │ Tile 3 │
└──────────┴──────────┘
CTB 扫描顺序: Tile 内按光栅扫描, Tile 间按 Tile 编号
地址映射: CtbAddrRStoTS / CtbAddrTStoRS 转换光栅序号和 Tile 扫描序号
并行后处理 (去块 + SAO)
去块滤波: 按 CTB 行并行, 每行独立滤波
add_deblocking_tasks() → 每行一个任务, 提交到线程池
SAO 滤波: 按 CTB 行并行, 同样行级独立
add_sao_tasks() → 每行一个任务
整体: run_postprocessing_filters_parallel() 协调去块+SAO并行
限制: WPP 和 Tile 不能同时启用 (源码: 返回 DE265_WARNING_PPS_HEADER_INVALID)
15.2 错误隐藏与容错
图像完整性追踪
libde265 为每帧维护 5 级完整性标记:
INTEGRITY_CORRECT = 0 正确解码
INTEGRITY_UNAVAILABLE_REFERENCE = 1 缺失参考帧
INTEGRITY_NOT_DECODED = 2 未完成解码
INTEGRITY_DECODING_ERRORS = 3 解码过程出错
INTEGRITY_DERIVED_FROM_FAULTY_REFERENCE = 4 参考了损坏帧
缺失参考帧处理
当 RPS 引用的参考帧不在 DPB 中时:
generate_unavailable_reference_picture():
1. 在 DPB 中分配新帧
2. 用灰色填充 (Y=1<<(BitDepth-1), Cb/Cr同理)
3. 标记为 INTEGRITY_UNAVAILABLE_REFERENCE
4. 标记为 UsedForShortTermReference 或 UsedForLongTermReference
→ 解码不中断, 后续帧仍可参考此灰帧, 但质量下降
容错参数
param_conceal_stream_errors: 默认开启, 允许跳过错误NAL继续解码
param_suppress_faulty_pictures: 开启时不输出完整性!=CORRECT的帧
错误队列 (error_queue): 非致命错误入队而非中止, 解码继续
不完整Slice: 所有未解码CTB标记为已完成, 允许后处理继续
15.3 无损编码 (Transquant Bypass)
PPS 开关: transquant_bypass_enable_flag = 1
CU 级标志: cu_transquant_bypass_flag (CABAC context 161)
当此标志为1时: 跳过变换和量化, 系数直接作为残差
解码路径 (transform.cc):
if (cu_transquant_bypass_flag):
系数直接放入残差缓冲 (transform_bypass)
若启用 RDPCM:
rdpcmMode=1: transform_bypass_rdpcm_h() (水平方向预测残差)
rdpcmMode=2: transform_bypass_rdpcm_v() (垂直方向预测残差)
若有跨分量预测 (ResScaleVal!=0): cross_comp_pred()
加残差到预测: add_residual()
else:
正常逆量化 + 逆变换 + 加残差
RDPCM (Residual DPCM)
RDPCM 仅在 transquant_bypass 模式下使用:
explicit_rdpcm_flag: 是否启用显式 RDPCM
explicit_rdpcm_dir: 方向 (0=水平, 1=垂直)
原理: 对残差系数做差分脉冲编码调制
水平: residual[x] = coeff[x] + residual[x-1]
垂直: residual[y] = coeff[y] + residual[y-1]
→ 利用相邻系数相关性, 进一步压缩无损残差
CABAC 上下文:
rdpcm_flag: contexts 143-144
rdpcm_dir: contexts 145-146
15.4 依赖 Slice Segment
PPS 标志: dependent_slice_segments_enabled_flag = 1
独立 Slice Segment:
包含完整 Slice Segment Header
初始化 CABAC 上下文
定义参考帧列表、QP 等
依赖 Slice Segment:
dependent_slice_segment_flag = 1
继承上一个独立 Slice 的部分 Header 字段:
- 参考帧列表 (RefPicList)
- 参考帧集 (RPS)
- 加权预测表
- SAO 参数
- QP 基准值
仅传输差异部分, 大幅减少 Header 开销
用途:
1. 低延迟编码: 将一帧拆成多个 Slice, 各Slice 可并行编码
2. 细粒度并行: 比 Tiles 更灵活, 不强制空间矩形分割
3. 减少开销: 依赖Slice 无需重复传输完整 Header
15.5 SEI 消息
SEI (Supplementary Enhancement Information) 携带辅助信息, 不影响解码过程
SEI 类型:
PREFIX_SEI_NUT (Type=39): 在 VCL NAL 之前
SUFFIX_SEI_NUT (Type=40): 在 VCL NAL 之后
常见 SEI 消息类型:
| payloadType | 名称 | 用途 |
|-------------|------|------|
| 0 | buffering_period | 缓冲周期, HRD 参数 |
| 1 | pic_timing | 图片定时, CPB/DPB 出队时间 |
| 6 | recovery_point | 恢复点, 指示后续帧可正确解码 |
| 45 | user_data_unregistered | 用户自定义数据 |
| 5 | user_data_registered | 注册用户数据 (如 ATSC 字幕) |
| 16-21 | 各种 HDR/色彩信息 | 10bit/12bit 元数据 |
解码器处理:
大部分 SEI 可忽略, 不影响解码
recovery_point: 用于错误恢复后确定何时画面恢复正常
buffering_period/pic_timing: 用于 HRD 一致性校验
15.6 Scaling List 使用细节
SPS 开关: scaling_list_enabled_flag = 1
SPS 数据: scaling_list_data() 在 SPS 或 PPS 中传输
矩阵结构 (4组):
| 矩阵ID | 大小 | 用途 |
|--------|------|------|
| 0 | 4×4 | 帧内亮度 |
| 1 | 4×4 | 帧内 Cb |
| 2 | 4×4 | 帧内 Cr |
| 3 | 4×4 | 帧间亮度 |
| 4 | 4×4 | 帧间 Cb |
| 5 | 4×4 | 帧间 Cr |
| 6-7 | 8×8 | 亮度帧内/帧间 |
| 8-9 | 8×8 | Cb帧内/帧间 |
| 10-11 | 8×8 | Cr帧内/帧间 |
| 12-13 | 16×16| 亮度帧内/帧间 (仅DC单独缩放) |
| 14-15 | 16×16| Cb帧内/帧间 |
| 16-17 | 16×16| Cr帧内/帧间 |
| 18-19 | 32×32| 亮度帧内/帧间 (仅DC单独缩放) |
| 20-21 | 32×32| Cb/Cr帧内/帧间 |
逆量化时应用:
有缩放矩阵:
coeff = (currCoeff × fact × m[x][y] + offset) >> bdShift
m[x][y] 来自 ScalingFactor_SizeID[MatrixID]
无缩放矩阵:
coeff = (currCoeff × fact + offset) >> bdShift (m=16, 即1<<4)
16×16 和 32×32 矩阵: 除 DC 外, 所有频率使用相同缩放值
DC 单独编码: scaling_list_dc_coef[MatrixID]
非 DC: 从 8×8 矩阵上采样得到
15.7 VUI 参数
SPS 标志: vui_parameters_present_flag = 1
VUI (Video Usability Information) 提供显示和定时元数据:
| 参数组 | 关键字段 | 用途 |
|--------|---------|------|
| 宽高比 | aspect_ratio_info | 像素宽高比 (1:1, 16:9 等) |
| 过扫描 | overscan_info | 是否裁剪显示 |
| 信号类型 | video_signal_type | 色彩原色/传输特性/矩阵系数 |
| 定时 | num_units_in_tick, time_scale | 帧率: fps = time_scale / (2 × num_units_in_tick) |
| HRD | nal_hrd_parameters, vcl_hrd_parameters | 假设参考解码器, 缓冲一致性校验 |
| 位深 | bitstream_restriction | 码流约束标志 |
解码器通常仅需关注:
- 定时信息 (帧率推导)
- 色彩信号类型 (正确色彩空间转换)
- HRD (缓冲区大小配置)
15.8 跨分量预测 (Cross-Component Prediction)
仅 Range Extension Profile 支持
CABAC 上下文: log2_res_scale_abs_plus1 (162-169), res_scale_sign_flag (170-171)
原理: 利用亮度残差预测色度残差, 减少色度系数编码量
语法 (每个 TU):
log2_res_scale_abs_plus1[cIdx-1][i]: 亮度→色度缩放因子绝对值
res_scale_sign_flag[cIdx-1][i]: 缩放因子符号
解码过程:
ResScaleVal = (1 << (log2_res_scale_abs_plus1 - 1)) × (1 - 2 × sign)
residual_chroma[i][j] += Clip3(-128, 127,
(ResScaleVal × residual_luma[i][j] + (1<<3)) >> 4)
即: 色度残差 = 原始色度残差 + 缩放后的亮度残差
利用亮度与色度残差的相关性, 减少需要编码的色度系数
15.9 符号数据隐藏 (Sign Data Hiding)
PPS 标志: sign_data_hiding_enabled_flag = 1
原理: 利用系数幅值奇偶性隐含符号信息
当连续4个非零系数的幅值之和为偶数时, 最后一个系数的符号可从奇偶性推导
→ 无需显式传输该系数的 sign_flag
条件:
1. 只有 scanIdx 扫描顺序中最后的非零系数组 (>4个) 适用
2. 不适用于 coeff_abs_level_remaining > 0 的系数 (已由其他语法隐含)
效果: 平均节省约 1~2% 码率, 几乎无计算开销
15.10 受限帧内预测
PPS 标志: constrained_intra_pred_flag = 1
正常帧内预测: 可使用任何相邻 CU 的参考样本, 包括帧间 CU
受限帧内预测: 只能使用帧内 CU 的参考样本
用途: 错误恢复场景
帧间 CU 可能参考了丢失的帧 → 参考样本可能错误
受限模式下跳过这些不可靠样本 → 帧内预测更鲁棒
代价: 预测精度下降, 码率略增
15.11 Slice 入口点
Slice Header 语法:
num_entry_point_offsets: ue(v) 入口点数量
entry_point_offset[i]: ue(v) 每个入口点的字节偏移
用途: 实现并行解码的数据分割
WPP: 每个入口点对应一行 CTB 的数据起始位置
Tile: 每个入口点对应一个 Tile 的数据起始位置
解码器可根据入口点偏移直接跳转到对应 CTB 行/Tile,
无需顺序解析前面的数据, 实现真正的并行启动
偏移计算:
入口点 i 的绝对偏移 = slice_data起始 + sum(entry_point_offset[0..i-1])
16. H.264 与 H.265 关键技术差异对比
16.1 编码块结构:固定宏块 vs 灵活编码树
H.264: 固定 16×16 宏块
┌────────────────┐
│ MB 16×16 │ 预测和变换绑定在同一块上
│ 预测 = 变换 │ 子宏块: 8×8, 8×4, 4×8, 4×4
└────────────────┘ 最大块 16×16, 无法更大
H.265: CTU 四叉树 + PU/TU 解耦
┌────────────────────────────────┐
│ CTU 64×64 │
│ ┌───────┐ ┌──────────────┐ │
│ │ CU │ │ CU 32×32 │ │ CU 大小灵活: 8×8~64×64
│ │ 32×32 │ │ PU / TU 独立 │ │ PU 决定预测, TU 决定变换
│ └───────┘ └──────────────┘ │
└────────────────────────────────┘
| 差异点 |
H.264 |
H.265 |
技术影响 |
| 最大块 |
16×16 |
64×64 |
大块减少分裂开销, 高分辨率更高效 |
| 最小块 |
4×4 |
4×4 (TU) / 8×8 (CU) |
H.265 CU 最小 8×8, 但 TU 可到 4×4 |
| 预测/变换 |
绑定: 预测块=变换块 |
解耦: PU 树 ≠ TU 树 |
局部细节+整体平坦可分别处理 |
| 块划分 |
固定 16×16 网格 |
自适应四叉树 |
按内容复杂度灵活划分 |
16.2 帧内预测:9 种 vs 35 种
H.264 帧内预测:
4×4 块: 9 种 (DC + 8 方向)
16×16 块: 4 种 (Vertical, Horizontal, DC, Plane)
8×8 色度: 4 种 (同上)
→ 方向少, 对斜纹理/复杂纹理拟合差
H.265 帧内预测:
35 种: Planar + DC + 33 种角度
角度间隔约 6.4°, 近乎连续覆盖 180°
→ 对各种纹理方向都能较好拟合
| 差异点 |
H.264 |
H.265 |
技术影响 |
| 亮度模式数 |
9 (4×4) / 4 (16×16) |
35 (统一) |
H.265 对纹理方向拟合更精确 |
| 角度分辨率 |
45° 间隔 |
~6.4° 间隔 |
细纹理/斜纹理残差更小 |
| 预测参考 |
单侧行/列 |
双侧参考样本 (滤波) |
边界预测更平滑 |
| 平面预测 |
Plane (16×16) |
Planar (所有尺寸) |
渐变区域更精确 |
16.3 变换:整数 DCT vs DST + 多尺寸 DCT
H.264:
4×4 整数 DCT (所有 Profile)
8×8 整数 DCT (High Profile)
→ 块大小固定, 对不同特性残差缺乏适应性
H.265:
4×4 DST (帧内残差, 统计特性偏向左上角能量集中)
4×4~32×32 整数 DCT (按 TU 大小自适应)
→ 小块捕捉细节, 大块压缩平坦残差
| 差异点 |
H.264 |
H.265 |
技术影响 |
| 变换类型 |
整数 DCT |
DST(4×4帧内) + 整数 DCT |
DST 更适配帧内残差统计特性 |
| 变换尺寸 |
4×4, 8×8 |
4×4 ~ 32×32 |
大块变换对平坦残差更高效 |
| 变换选择 |
固定 |
TU 树自适应 |
残差稀疏区用大块, 细节区用小块 |
16.4 运动矢量预测:中值 vs Merge/AMVP
H.264 MV 预测:
P Slice: 中值预测 (上/左/右上 MV 取中值)
B Slice: 直接模式 (Direct Mode, 空间/时间预测)
→ 预测精度有限, 残差 MVD 较大
H.265 MV 预测:
Merge 模式: 从候选列表选一个 MV 直接复用, 传 merge_idx
AMVP: 从候选列表选预测值, 传 MVD (差分)
Skip 模式: Merge 的特例, 无残差, 仅传 merge_idx
| 差异点 |
H.264 |
H.265 |
技术影响 |
| MV 预测 |
中值预测 / 直接模式 |
Merge / AMVP |
H.265 候选列表更丰富, 预测更准 |
| Skip/Merge |
无 Merge |
Merge + Skip |
Skip 仅传 idx, 极低码率 |
| 候选来源 |
空间相邻 3 个 |
空间 5 个 + 时间 2 个 + 组合 |
覆盖更多运动模式 |
| B 帧直接模式 |
空间/时间直接 |
被 Merge 替代 |
Merge 更简洁, 通用性更好 |
16.5 参考帧管理:滑窗/MMCO vs RPS
H.264:
滑窗机制: DPB 满时自动替换最旧参考帧
MMCO: 显式命令管理 (长期参考标记等)
→ 隐式: 解码器须推断哪些帧保留, 出错后难以恢复
H.265:
RPS (参考帧集): 每帧显式传输完整参考帧列表
→ 显式: 编码器明确告诉解码器哪些帧是参考帧, 哪些可释放
→ 鲁棒: 解码错误后可从 IRAP 恢复, RPS 重建参考关系
| 差异点 |
H.264 |
H.265 |
技术影响 |
| 管理方式 |
滑窗 + MMCO 命令 |
RPS 显式传输 |
H.265 编解码器参考帧状态一致 |
| 长期参考 |
MMCO 命令标记 |
RPS 中 LtCurr/LtFoll |
H.265 更简洁, 不需要命令序列 |
| 错误恢复 |
困难 (隐式状态) |
容易 (RPS 显式重建) |
码流拼接/随机访问更鲁棒 |
| 码流拼接 |
需插入 IDR |
BLA 帧即可 |
H.265 拼接不断链 |
16.6 环路滤波:去块 vs 去块 + SAO
H.264: 去块滤波
bS: 0~4 (5 级), 规则复杂
按 4×4 边界滤波, 粒度细但计算量大
H.265: 去块滤波 + SAO
去块: bS 0~2 (3 级), 简化规则, 按 TU/PU 边界滤波 (非 4×4)
SAO: 样本级补偿, 消除振铃效应和色偏
| 差异点 |
H.264 |
H.265 |
技术影响 |
| 去块 bS |
0~4 (5级) |
0~2 (3级) |
H.265 规则简化, 计算量降低 |
| 滤波边界 |
每 4×4 边界 |
TU/PU 边界 |
H.265 大块时滤波次数大幅减少 |
| SAO |
无 |
有 (BO + EO) |
消除振铃/色偏, 主观质量提升 |
| 滤波顺序 |
先垂直后水平 |
同, 但支持并行 |
H.265 CTB 级并行更友好 |
16.7 熵编码:CAVLC + CABAC vs 仅 CABAC
| 差异点 |
H.264 |
H.265 |
技术影响 |
| 编码方式 |
CAVLC (Baseline) + CABAC (Main+) |
仅 CABAC |
H.265 统一, 无双路径维护负担 |
| 上下文数 |
~460 |
172 |
H.265 更精简, 上下文设计更优化 |
| 并行性 |
CABAC 难并行 |
CABAC 仍串行, 但 WPP 缓解 |
WPP 按行并行, 核间通信最小 |
16.8 并行工具:Slice vs Tiles + WPP
H.264: Slice 并行
每个 Slice 独立编码, 可并行
缺点: Slice 边界不能做去块滤波, 边界处质量下降
H.265: Tiles + WPP + Slice
Tiles: 空间矩形分区, 各 Tile 独立编码, 可完全并行
WPP: 按行并行, 每行延迟 2 个 CTB 即可启动, 核间通信极小
Slice: 与 H.264 类似, 但通常配合 Tiles 使用
| 差异点 |
H.264 |
H.265 |
技术影响 |
| 空间并行 |
Slice (质量损失) |
Tiles (无损并行) |
Tiles 可做去块, 无质量损失 |
| 波前并行 |
无 |
WPP |
行级并行, 延迟极低 |
| Slice 问题 |
边界去块丢失 |
可用 Tiles 替代 |
灵活选择并行策略 |
16.9 IRAP 与随机访问
| 差异点 |
H.264 |
H.265 |
技术影响 |
| IDR 类型 |
1 种 (Type=5) |
2 种 (W_RADL, N_LP) |
更精细的 DPB 清空控制 |
| CRA |
无 |
有 |
不清空 DPB 的随机访问点 |
| BLA |
无 |
3 种 |
码流拼接专用, 无需 IDR |
| 前导帧 |
无区分 |
RADL (可解码) / RASL (可能跳过) |
随机访问后可快速恢复 |
16.10 综合对比表
| 特性 |
H.264/AVC |
H.265/HEVC |
| 宏块大小 |
固定 16×16 |
CTU: 16×16~64×64 |
| 帧内预测模式 |
9 种 (4×4), 4 种 (16×16) |
35 种 |
| 变换 |
4×4 整数DCT (8×8 High) |
4×4 DST + 4×4~32×32 DCT |
| 熵编码 |
CAVLC + CABAC |
仅 CABAC |
| 去块滤波 bS |
0~4 (5级) |
0~2 (3级) |
| 环路滤波 |
去块滤波 |
去块滤波 + SAO |
| 参考帧管理 |
滑窗 / MMCO |
RPS (参考帧集) |
| 并行工具 |
Slice / FMO |
Tiles / WPP / Slice |
| NAL Header |
1 字节 |
2 字节 (含时间层) |
| 分块模式 |
7 种 (P/B) |
8 种 (含 AMP) |
| Merge 模式 |
无 |
有 (Skip 基于 Merge) |
| MV 预测 |
中值预测 / 直接模式 |
Merge / AMVP |
| IRAP 类型 |
IDR + 非IDR I帧 (2种) |
IDR(2) / CRA(1) / BLA(3) 共6种 |
| Profile 数 |
3 (Baseline/Main/High) |
3 (Main/Main10/MainStill) |
| 同质量码率 |
基准 |
约节省 40~50% |
16.11 H.265 压缩率更高的原因分析
H.265 压缩率更高的核心原因:每一步编码环节都更精确地适配了视频信号的统计特性,减少了冗余。
大块编码减少开销占比
H.264: 4K(3840×2160) → 240×135 = 32,400 个宏块
每个宏块都要传: split_flag, pred_mode, ref_idx, mv, cbf, ...
当区域平坦时, 这些开销占比极大
H.265: 4K → 60×34 = 2,040 个 CTU
一个 64×64 CTU 可能就一个 CU: 1组预测参数, 1组变换参数
同样平坦区域, 开销是 H.264 的 ~1/16
本质: 高分辨率下, 大片平坦/匀速运动区域占比高, 大块编码把"每像素的语法开销"压到极低
PU/TU 解耦让预测和变换各自最优
H.264 预测=变换绑定:
16×16 宏块做帧间预测 → 16×16 残差做变换
局部有细节?必须拆成小块 → 小块预测也得多传 MV
整块平坦?用大块 → 但大块预测不够精细
H.265 解耦:
大块预测 (1个MV) + 小块变换 (细节精确编码)
→ 预测开销小 + 残差编码效率高, 两头都省
35 种帧内模式拟合更精确
H.264: 9种方向, 45° 间隔
斜纹理用最近方向近似 → 残差大 → 码率高
H.265: 35种方向, ~6.4° 间隔
几乎总能找到接近纹理真实方向的预测
残差更小 → 变换后非零系数更少 → 码率更低
量化: 帧内预测残差减少直接意味着变换后系数更稀疏, 这是码率节省的大头之一
Merge/AMVP 让 MV 编码更高效
H.264: 中值预测 → MVD 残差通常较大
特别是复杂运动场景, 中值预测经常不准
H.265: Merge 直接复用邻居 MV → 0 bit (仅传 idx)
AMVP 从丰富候选中选 → MVD 更小
Merge 占帧间 CU 的 30~50%, 这些块几乎不花 MV 码率
RPS 显式参考帧管理
H.264: 滑窗隐式管理, 编码器无法精确控制解码器 DPB 状态
保守选择参考帧 → 预测效率受限
H.265: RPS 显式传输, 编码器可精确指定参考帧集合
大胆使用非常规参考结构 (如长期参考、非连续帧)
→ 预测更准, 残差更小
SAO 消除主观质量缺陷
去块滤波后仍有: 振铃效应 (Gibbs 现象), 色偏区域
H.264: 这些缺陷存在, 编码器被迫用更多比特来压制
H.265: SAO 样本级补偿, 用极少比特 (每CTB 几个参数)
消除振铃和色偏 → 同主观质量下码率更低
或同码率下主观质量显著提升
各环节码率节省贡献估算
H.265 vs H.264 同质量码率节省 ~40-50%, 各技术贡献:
CTU 大块 + PU/TU 解耦: ~15% (高分辨率场景更大)
帧内 35 模式: ~8% (I帧和帧内 CU)
Merge/AMVP: ~8% (P/B帧运动矢量)
RPS 显式参考: ~3-5% (灵活参考结构)
SAO: ~3-5% (主观质量提升)
去块简化+变换优化: ~3-5% (计算简化, 效率提升)
仅 CABAC: ~2-3% (消除 CAVLC 低效路径)
附录:Profile 与 Level
Profile 功能对比
| 功能 |
Main |
Main10 |
MainStill |
| 色度格式 |
4:2:0 |
4:2:0 |
4:2:0 |
| 位深 |
8 bit |
8~10 bit |
8 bit |
| CTU 大小 |
64×64 |
64×64 |
64×64 |
| I/P/B Slice |
Y |
Y |
仅 I Slice |
| CABAC |
Y |
Y |
Y |
| SAO |
Y |
Y |
Y |
| Tiles/WPP |
Y |
Y |
--- |
| AMP |
Y |
Y |
--- |
Level 约束 (部分)
| Level |
MaxLumaPs |
MaxDpbSize (4K) |
MaxBitrate (Main) |
| 3.1 |
921,600 |
8 |
35 Mbps |
| 4.0 |
2,211,840 |
6 |
60 Mbps |
| 4.1 |
3,686,400 |
6 |
100 Mbps |
| 5.0 |
8,912,896 |
6 |
180 Mbps |
| 5.1 |
17,694,720 |
6 |
300 Mbps |