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
相关推荐
the3clipse5 天前
H.265全系列总结与H.266/VVC展望:从HEVC核心架构到下一代视频编码标准
视频编码·h.265·hevc·vvc·h.266·视频压缩·ctu
the3clipse5 天前
H.265熵编码核心:CABAC自适应二进制算术编码详解——如何将语法元素高效压缩为比特流
人工智能·算法·视频编码·h.265·hevc·cabac·cavlc
the3clipse6 天前
H.265变换与量化:如何更高效地处理残差数据——可变尺寸DCT/DST与量化矩阵优化
视频编码·h.265·hevc·dct·rqt·变换编码·dst
the3clipse6 天前
H.265运动补偿优化:Merge模式与AMVP技术详解——如何更高效地利用时间冗余
视频编码·h.265·运动补偿·帧间预测·amvp·merge模式·运动矢量预测
the3clipse8 天前
H.265:为什么我们需要新一代视频编码标准?——从H.264到HEVC的技术演进解析
视频编码·h.265·hevc·h.264·视频压缩·流媒体技术·带宽优化
the3clipse9 天前
HEIF/HEIC 技术详解:容器、编码、元数据、转换与 Web 兼容
h.265·hevc·图片压缩·heic·heif·图像容器·libheif
the3clipse9 天前
H.265/HEVC、HEIF、HEIC 关系详解:为什么 HEIC 比 JPEG 更省空间?一文讲清容器、编码与文件后缀
视频编码·h.265·hevc·图片压缩·jpeg·heif·图像容器
the3clipse10 天前
Windows 10 与 Windows 11 视频编解码区别:AV1、HEVC、HDR 与 D3D12 编码支持对比
windows·h.265·hevc·hdr·av1·视频解码·d3d12
hongmai6668881 个月前
告别Wi-Fi,专注Mesh:ESP32-H2-MINI-1U-H2S模组规格与应用
单片机·嵌入式硬件·物联网·5g·h.265
硬件工艺分享君1 个月前
安防摄像头主板 PCB 布局设计:传感器、H.265 与夜视模块的实战指南
科技·制造·h.265·pcb工艺