H.265/HEVC 解码知识文档

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 类型不同内容不同

NAL Header (2 bytes) 详解

复制代码
 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 偏移

6. Slice Segment Header 完整语法

复制代码
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
相关推荐
网管NO.122 天前
视频核心技术 03:H.264 / H.265 / AV1 编码标准对比 —— 压缩率、复杂度、适用场景
音视频·h.265·h.264
老姚---老姚1 个月前
编译支持HEVC/H.265 over RTMP / Enhanced RTMP 的 ffmpeg
ffmpeg·h.265·hevc·rtmp·enhanced
山楂树の1 个月前
H.265 (HEVC) 视频解码转逐帧图像 完整实现方案
学习·音视频·h.265
REDcker3 个月前
Wasm 软解 H.265 方案与原理
wasm·h.265
REDcker5 个月前
WASM 软解 H.265 性能优化详解
性能优化·wasm·h.265
zymill5 个月前
hysAnalyser --- 常见MPEG-TS问题指南
h.265·h.264·分析工具·智能电视·视频分析·mpegts·mpegts分析
hk11245 个月前
【音视频/边缘计算】2025年度H.265/HEVC高并发解码与画质修复(Super-Resolution)基准测试报告(含沙丘/失控玩家核心样本)
ffmpeg·边缘计算·音视频开发·h.265·测试数据集
CodeOfCC5 个月前
C++ 基于kmp解析nalu
c++·音视频·实时音视频·h.265·h.264
lusasky6 个月前
H.264 (AVC) 与 H.265 (HEVC) 全方位对标
h.265·h.264