AV1 帧内预测核心文件 reconintra.c 源码深度解析

libaom v3.9 · 1882 行 · 从参考像素到预测值的完整链路 · 含公式推导、图解与 SIMD 优化原理


目录

  1. [帧内预测在 AV1 编码管线中的位置](#帧内预测在 AV1 编码管线中的位置)
  2. [reconintra.c 文件全景地图](#reconintra.c 文件全景地图)
  3. [13 种帧内预测模式与角度映射](#13 种帧内预测模式与角度映射)
  4. 参考像素:邻居可用性判定
  5. 参考像素填充与扩展
  6. [方向性预测的数学原理(Zone 1/2/3)](#方向性预测的数学原理(Zone 1/2/3))
  7. [角度微调(angle_delta)与 dr_intra_derivative 表](#角度微调(angle_delta)与 dr_intra_derivative 表)
  8. 边缘滤波与上采样
  9. [非方向性预测:DC / Smooth / Paeth](#非方向性预测:DC / Smooth / Paeth)
  10. [Filter Intra:基于 7-tap 滤波器的帧内预测](#Filter Intra:基于 7-tap 滤波器的帧内预测)
  11. [CFL(Chroma From Luma)色度预测集成](#CFL(Chroma From Luma)色度预测集成)
  12. [Palette 模式与 HBD(高比特深度)处理](#Palette 模式与 HBD(高比特深度)处理)
  13. [调用入口:从 predict_intra_block_facade 到 dispatch](#调用入口:从 predict_intra_block_facade 到 dispatch)
  14. [总结:reconintra.c 的工程美学](#总结:reconintra.c 的工程美学)

1. 帧内预测在 AV1 编码管线中的位置

在 AV1 编码器中,一个宏块(Coding Unit)的预测值生成是整个编码流程的核心环节之一。帧内预测(Intra Prediction)利用已重建的邻居像素来预测当前块,是 I 帧编码的基础,也是 P/B 帧中备用预测手段。

reconintra.c 这个文件名可以拆解为 recon(struction) + intra ------它负责的是帧内重建/预测,具体来说:

  • 编码端:在 RDO(率失真优化)过程中,对每一种候选帧内模式调用本文件生成预测值,再与原始像素相减得到残差。

  • 解码端:从码流解析出帧内模式后,调用本文件生成预测值,再加回反量化反变换后的残差,完成重建。

    码流解析 ──→ 反量化 ──→ 反变换 (IDCT) ──→ reconintra.c ──→ 重建像素
    (帧内预测值 + 残差)
    ↑ ↑
    帧内模式 + 已重建邻居像素
    angle_delta above / left / above-left /
    above-right / bottom-left

关键理解 :帧内预测的输入是已重建的邻居像素,输出是当前块的预测值。预测值与残差相加即为最终重建像素。整个 reconintra.c 就是实现这条 "邻居像素 → 预测值" 的完整链路。


2. reconintra.c 文件全景地图

这个文件共 1882 行,按照职责可以划分为 6 个逻辑层

行号范围 逻辑模块 核心函数/数据
27--53 邻居需求声明 extend_modes[], NEED_LEFT/ABOVE 等位标志
56--448 邻居可用性查表 has_tr_*, has_bl_* 查找表, has_top_right(), has_bottom_left()
450--791 方向性预测核心 dr_predictor(), av1_dr_prediction_z1/z2/z3(), pred[] 函数指针表
794--1047 Filter Intra & 边缘滤波 av1_filter_intra_taps[][], av1_filter_intra_predictor_c(), av1_filter_intra_edge_c(), av1_upsample_intra_edge_c()
1072--1622 预测值构建(build_*) build_directional_and_filter_intra_predictors(), build_non_directional_intra_predictors() 及 HBD 版本
1676--1882 公共入口 av1_predict_intra_block(), av1_predict_intra_block_facade(), av1_init_intra_predictors()

3. 13 种帧内预测模式与角度映射

AV1 定义了 13 种帧内预测模式INTRA_MODES = 13),其中 8 种为方向性模式,5 种为非方向性模式:

复制代码
         AV1 帧内预测模式方向图
         角度范围 0°--270°,红色=主方向,紫色=对角方向

                        V_PRED (90°)
                              ↑
                    D67↗    │    ↖D113
                   D45 ╲     │    ╱ D135
                         ╲   │   ╱
                          ╲  │  ╱
            ←────────────── ┌──┐ ──────────────→
            left[]          │当│         above[]
                            │前│
                            │块│
            ←────────────── └──┘
                          ╱     ╲
                         ╱       ╲
                    D203↙         ╲↘D157
                               H_PRED (180°)

        ┌────────────────┐
        │   非方向模式    │
        │  ● DC          │
        │  ● Smooth      │
        │  ● Smooth_V    │
        │  ● Smooth_H    │
        │  ● Paeth       │
        └────────────────┘

源码中的关键映射表定义在 av1/common/blockd.h 第 1145 行:

c 复制代码
// blockd.h:1145
static const uint8_t mode_to_angle_map[INTRA_MODES] = {
  0,  90,  180, 45, 135, 113, 157, 203, 67, 0, 0, 0, 0,
};
// DC  V    H    D45  D135 D113 D157 D203 D67 Sm SmV SmH Paeth

而每个模式对邻居像素的需求,由文件开头的 extend_modes[] 数组(第 40--54 行)用位掩码精确声明:

c 复制代码
// reconintra.c:27-33
enum {
  NEED_LEFT      = 1 << 1,  // 需要 left[]
  NEED_ABOVE     = 1 << 2,  // 需要 above[]
  NEED_ABOVERIGHT = 1 << 3,  // 需要 above-right[]
  NEED_ABOVELEFT = 1 << 4,  // 需要 above-left
  NEED_BOTTOMLEFT = 1 << 5,  // 需要 bottom-left[]
};
模式 NEED_LEFT NEED_ABOVE NEED_ABOVERIGHT NEED_ABOVELEFT NEED_BOTTOMLEFT
DC --- --- ---
V (90°) --- --- --- ---
H (180°) --- --- --- ---
D45 --- --- ---
D135 --- ---
D113 / D157 --- ---
D203 --- --- ---
D67 --- --- ---
Smooth 系列 --- --- ---
Paeth --- ---

💡 为什么 D45 只需要 above + above-right 而不需要 left?

因为 D45 方向(接近水平对角线)的投影全部落在上方行上,left 列像素不参与插值。同理 D203 只需要 left + bottom-left。


4. 参考像素:邻居可用性判定

帧内预测的一个核心难题是:当前块的某个邻居可能尚未解码(因为 AV1 采用光栅扫描顺序)。源码用两套精心设计的查找表解决这个问题。

4.1 为什么需要查表?

AV1 的超级块(Superblock,最大 128×128)内部有灵活的分区结构。一个块的 above-right 或 bottom-left 像素可能属于尚未解码的兄弟块,此时不能用作参考。

复制代码
    ┌───────────────────────────────────────┐
    │              Superblock (128×128)      │
    ├──────────────┬───────────────────────┤
    │  0 (已解码)   │  1 (已解码)    →       │
    ├──────────────┼───────────────────────┤
    │  2 (已解码) → │  3 (当前块)            │
    └──────────────┴───────────────────────┘
                              └→ above-right: 不可用! ✗
                              └→ bottom-left: 可用 ✓

4.2 has_tr_* / has_bl_* 查找表的设计

源码为每种块尺寸 预计算了一张位图表。以 has_tr_4x4[128] 为例:

  • 表大小 128 bit = 16 字节
  • 一个 128×128 超级块中最多有 (128/4)² = 1024 个 4×4 子块
  • 用 bit 位表示:bit=1 表示该位置的 4×4 块有可用的 top-right 像素
  • 存取方式:(table[blk_index / 8] >> (blk_index % 8)) & 1
c 复制代码
// reconintra.c:242-249 --- 通用查表逻辑
const int this_blk_index =
    ((blk_row_in_sb + 0) << (MAX_MIB_SIZE_LOG2 - bw_in_mi_log2)) +
    blk_col_in_sb + 0;
const int idx1 = this_blk_index / 8;
const int idx2 = this_blk_index % 8;
const uint8_t *has_tr_table = get_has_tr_table(partition, bsize);
return (has_tr_table[idx1] >> idx2) & 1;

共定义了 26 种块尺寸BLOCK_SIZES_ALL = 26)的 top-right 表和 bottom-left 表,加上对 PARTITION_VERT_A/B 分区类型的特殊处理表,总计约 50+ 个静态数组。这些表在编译期生成,运行时零开销查表。

💡 设计智慧:AV1 的分区类型多达 10 种(PARTITION_NONE, HORZ, VERT, SPLIT, HORZ_A, HORZ_B, VERT_A, VERT_B, HORZ_4, VERT_4),每种分区的子块访问顺序不同。查表法将 O(分区分析) 的运行时判定转化为 O(1) 的查表操作,代价是约 5KB 的静态数据。


5. 参考像素填充与扩展

确定了哪些邻居可用后,下一步是将它们填充到连续缓冲区 中。这个逻辑在 build_directional_and_filter_intra_predictors() 函数(第 1072 行)中实现。

5.1 缓冲区布局

c 复制代码
// reconintra.c:1080-1083
DECLARE_ALIGNED(16, uint8_t, left_data[NUM_INTRA_NEIGHBOUR_PIXELS]);  // 128 + 64 = 192
DECLARE_ALIGNED(16, uint8_t, above_data[NUM_INTRA_NEIGHBOUR_PIXELS]);
uint8_t *const above_row = above_data + 16;  // 留 16 字节前缀给 above-left
uint8_t *const left_col  = left_data + 16;   // 留 16 字节前缀给 above-left
复制代码
参考像素缓冲区内存布局:

above_data[192]:
├── [0..15]   前缀(SIMD 对齐)
│   [-1]      above-left 角落像素 ←──┐
│   [0]       above_ref[0]          │
│   [1]       above_ref[1]          │
│   ...                             │
│   [txw-1]   above 最后一个像素    │
│   [txw..]   above-right 像素     │
└──────────────────────────────────│
                                     │
left_data[192]:                      │
├── [0..15]   前缀(SIMD 对齐)     │
│   [-1]      above-left 角落像素 ──┘
│   [0]       left_ref[0]
│   [1]       left_ref[1]
│   ...
│   [txh-1]   left 最后一个像素
│   [txh..]   bottom-left 像素
└────────────────────────────────────

填充规则:
  1. 不足时用最近有效像素填充 (memset 扩展)
  2. 完全不可用时:above 默认 127, left 默认 129, corner 默认 128

5.2 缺失像素的填充策略

c 复制代码
// reconintra.c:1096-1097 --- 默认值填充
memset(left_data, 129, NUM_INTRA_NEIGHBOUR_PIXELS);
memset(above_data, 127, NUM_INTRA_NEIGHBOUR_PIXELS);

这些默认值看似随意,实则有数学含义:

  • 8-bit 中位值 = 128。above 默认 127 和 left 默认 129 是中位值两侧的偏移,模拟"无参考信息"时的中性预测。
  • above-left 角落默认 128,作为两个方向的中性连接点。
  • 对于 HBD(高比特深度),默认值变为 128 << (bit_depth - 8)

⚠️ 性能细节 :缓冲区使用 DECLARE_ALIGNED(16, ...) 16 字节对齐,这是 SIMD 优化的前提条件。不对齐的内存访问会导致 SSE/AVX 指令触发 #GP 异常。


6. 方向性预测的数学原理(Zone 1/2/3)

方向性预测是 reconintra.c 中最精巧的算法。AV1 将 0°--270° 的角度空间划分为 3 个区域,每个区域有独立的插值公式:

复制代码
方向性预测三区域划分

  0°────────────────────90°────────────────────180°
  │                      │                      │
  │    ┌──────────────┐   │   ┌──────────────────┐│
  │    │   Zone 1     │   │   │    Zone 2        ││
  │    │ 0° < θ < 90° │   │   │ 90° < θ < 180°  ││
  │    │ z1_predictor │   │   │ z2_predictor     ││
  │    │ 仅用 above[] │   │   │ above[]+left[]   ││
  │    └──────────────┘   │   └──────────────────┘│
  │                      │                      │
  │         ┌──────────────┐                    │
  │         │   Zone 3     │                    │
  │         │180°<θ<270°  │                    │
  │         │ z3_predictor │                    │
  │         │ 仅用 left[] │                    │
  │         └──────────────┘                    │
  │                      │                      │

6.1 Zone 1(0° < θ < 90°):av1_dr_prediction_z1

此区域仅使用 above\[\] 行。核心思想是沿投影方向,在 above 行上做亚像素插值:

c 复制代码
// reconintra.c:520-555
void av1_dr_prediction_z1_c(uint8_t *dst, ptrdiff_t stride, int bw, int bh,
                          const uint8_t *above, const uint8_t *left,
                          int upsample_above, int dx, int dy) {
  const int max_base_x = ((bw + bh) - 1) << upsample_above;
  const int frac_bits = 6 - upsample_above;
  int x = dx;
  for (int r = 0; r < bh; ++r, dst += stride, x += dx) {
    int base = x >> frac_bits;        // 整数部分
    int shift = ((x << upsample_above) & 0x3F) >> 1;  // 小数部分(5-bit)
    for (int c = 0; c < bw; ++c, base += base_inc) {
      int val = above[base] * (32 - shift) + above[base + 1] * shift;
      dst[c] = ROUND_POWER_OF_TWO(val, 5);
    }
  }
}

对应的数学公式:

P ( r , c ) = above ⌊ x r c / 2 frac ⌋ × ( 32 − shift r c ) + above ⌊ x r c / 2 frac ⌋ + 1 × shift r c P(r, c) = \text{above}\left\\lfloor x_{rc} / 2\^{\\text{frac}} \\rfloor\\right \times (32 - \text{shift}{rc}) + \text{above}\left\\lfloor x_{rc} / 2\^{\\text{frac}} \\rfloor + 1\\right \times \text{shift}{rc} P(r,c)=above⌊xrc/2frac⌋×(32−shiftrc)+above⌊xrc/2frac⌋+1×shiftrc

其中 x r c = r × d x + c × 2 frac + upsample x_{rc} = r \times dx + c \times 2^{\text{frac}+\text{upsample}} xrc=r×dx+c×2frac+upsample, shift r c = ( x r c   m o d   64 ) / 2 \text{shift}{rc} = (x{rc} \bmod 64) / 2 shiftrc=(xrcmod64)/2,结果右移 5 位(即除以 32)。

本质是双线性插值 的简化版------用 2 个最近邻像素做加权平均,权重比 32:0 到 0:32(6-bit 精度)。代码中使用 dx(x 方向增量)来逐行递增投影位置。

6.2 Zone 2(90° < θ < 180°):av1_dr_prediction_z2

此区域同时使用 above\[\] 和 left\[\]。对于每个像素,先尝试投影到 above 行;若投影超出 above 行范围,则改用 left 列投影:

若 base_x ≥ 0 : P ( r , c ) = lerp ( above base_x , above base_x + 1 , shift_x ) 若 base_x < 0 : P ( r , c ) = lerp ( left base_y , left base_y + 1 , shift_y ) \text{若 base\_x} \geq 0: \quad P(r,c) = \text{lerp}(\text{above}\\text{base\\_x}, \text{above}\\text{base\\_x}+1, \text{shift\_x}) \\ \text{若 base\_x} < 0: \quad P(r,c) = \text{lerp}(\text{left}\\text{base\\_y}, \text{left}\\text{base\\_y}+1, \text{shift\_y}) 若 base_x≥0:P(r,c)=lerp(abovebase_x,abovebase_x+1,shift_x)若 base_x<0:P(r,c)=lerp(leftbase_y,leftbase_y+1,shift_y)

c 复制代码
// reconintra.c:575-589 --- 核心双路径插值
for (int r = 0; r < bh; ++r) {
  for (int c = 0; c < bw; ++c) {
    int y = r + 1;
    int x = (c << 6) - y * dx;    // 投影到 above 坐标系
    if (base_x >= min_base_x) {
      val = lerp(above[base_x], above[base_x+1], shift);  // above 路径
    } else {
      y = (r << 6) - (c+1) * dy;  // 投影到 left 坐标系
      val = lerp(left[base_y], left[base_y+1], shift);    // left 路径
    }
    dst[c] = val;
  }
}

💡 坐标变换的精妙之处 :代码中 x = (c << 6) - y * dx 使用了 6-bit 定点数(<< 6 相当于乘以 64),将浮点坐标运算转换为整数运算,避免了昂贵的浮点除法,同时保持足够的精度。

6.3 Zone 3(180° < θ < 270°):av1_dr_prediction_z3

与 Zone 1 对称,仅使用 left\[\] 列。逻辑完全镜像:

P ( r , c ) = lerp ( left ⌊ y r c / 2 frac ⌋ , left ⌊ y r c / 2 frac ⌋ + 1 , shift r c ) P(r, c) = \text{lerp}(\text{left}\\lfloor y_{rc} / 2\^{\\text{frac}} \\rfloor, \text{left}\\lfloor y_{rc} / 2\^{\\text{frac}} \\rfloor + 1, \text{shift}_{rc}) P(r,c)=lerp(left⌊yrc/2frac⌋,left⌊yrc/2frac⌋+1,shiftrc)

注意 Zone 3 的循环结构是先遍历列,再遍历行for c ... for r ...),而 Zone 1 是先遍历行,再遍历列for r ... for c ...)。这种内存访问模式针对各自的数据局部性做了优化。


7. 角度微调(angle_delta)与 dr_intra_derivative 表

AV1 允许在 8 个基础角度(45°, 67°, 90°, 113°, 135°, 157°, 180°, 203°)之间做 ±3° 的微调 ,由 angle_delta 参数控制。

c 复制代码
// enums.h:469
#define ANGLE_STEP 3

// reconintra.c:1838
const int angle_delta = mbmi->angle_delta[plane != AOM_PLANE_Y] * ANGLE_STEP;
// 最终预测角度
// reconintra.c:1783
p_angle = mode_to_angle_map[mode] + angle_delta;

这样每个方向性模式实际支持 7 个角度 (-3, 0, +3, ..., +18),AV1 总共支持 56 个方向性预测角度(8 base × 7 delta)。

角度到斜率(dx/dy)的转换由 dr_intra_derivative[90] 查表完成:

c 复制代码
// reconintra.h:84-116
static const int16_t dr_intra_derivative[90] = {
  0, 0, 0,                          // 0°
  1023, 0, 0,                       // ~3°
  547, 0, 0,                        // ~6°
  372, 0, 0, 0, 0,                  // ~9°
  273, 0, 0,                        // ~14°
  ...
  3, 0, 0,                          // 87°
};

这个表的数学含义是:

d x ( θ ) = 256 tan ⁡ ( θ ) (Zone 1 & 2) dx(\theta) = \frac{256}{\tan(\theta)} \quad \text{(Zone 1 \& 2)} dx(θ)=tan(θ)256(Zone 1 & 2)

d y ( θ ) = 256 × tan ⁡ ( θ − 90 ° ) (Zone 2 & 3) dy(\theta) = 256 \times \tan(\theta - 90°) \quad \text{(Zone 2 \& 3)} dy(θ)=256×tan(θ−90°)(Zone 2 & 3)

乘以 256 是为了转定点数。索引中 0 值对应的是 90° 的整数倍,此时不需要分量。


8. 边缘滤波与上采样

8.1 边缘滤波(Intra Edge Filter)

参考像素的锐利跳变 会在预测值中引入虚假边缘(方向性预测本质上是"像素复制 + 插值",不连续的参考值会直接传播)。AV1 对参考像素进行可配置的边缘滤波来平滑这些跳变。

3 组 5-tap 滤波核(INTRA_EDGE_FILT = 3):

强度 1: 0 , 4 , 8 , 4 , 0 / 16 ---弱平滑 强度 2: 0 , 5 , 6 , 5 , 0 / 16 ---中等 强度 3: 2 , 4 , 4 , 4 , 2 / 16 ---强平滑(加入端点权重) \text{强度 1: } 0, 4, 8, 4, 0 / 16 --- \text{弱平滑} \\ \text{强度 2: } 0, 5, 6, 5, 0 / 16 --- \text{中等} \\ \text{强度 3: } 2, 4, 4, 4, 2 / 16 --- \text{强平滑(加入端点权重)} 强度 1: 0,4,8,4,0/16---弱平滑强度 2: 0,5,6,5,0/16---中等强度 3: 2,4,4,4,2/16---强平滑(加入端点权重)

滤波强度的判定逻辑在 intra_edge_filter_strength()(第 977 行)中,综合考虑:

  • 块尺寸(bs0 + bs1 越大,越倾向使用滤波)
  • 预测角度与 90° / 180° 的偏差(delta 越大,越倾向滤波)
  • 邻居块是否为 smooth 模式(intra_edge_filter_type

8.2 边缘上采样(Edge Upsample)

对于接近 90° 或 180° 的角度,参考像素行/列被 2 倍上采样以获得更精细的插值精度:

c 复制代码
// reconintra.c:1049-1070
void av1_upsample_intra_edge_c(uint8_t *p, int sz) {
  for (int i = 0; i < sz; i++) {
    // 4-tap 插值: -1, 9, 9, -1
    int s = -in[i] + (9 * in[i+1]) + (9 * in[i+2]) - in[i+3];
    s = clip_pixel((s + 8) >> 4);
    p[2*i - 1] = s;    // 插值位置
    p[2*i]     = in[i+2];  // 原始位置
  }
}

p 2 i − 1 = Clip ( − p i − 1 + 9 ⋅ p i + 9 ⋅ p i + 1 − p i + 2 + 8 16 ) p2i-1 = \text{Clip}\left( \frac{-pi-1 + 9 \cdot pi + 9 \cdot pi+1 - pi+2 + 8}{16} \right) p2i−1=Clip(16−pi−1+9⋅pi+9⋅pi+1−pi+2+8)

这是经典的 4-tap Wiener 滤波器 − 1 , 9 , 9 , − 1 / 16 -1, 9, 9, -1/16 −1,9,9,−1/16,与 H.264/H.265 的半像素插值使用相同的核。

上采样仅在块尺寸足够小且角度偏差在特定范围内时启用,由 av1_use_intra_edge_upsample() 判定:

c 复制代码
// reconintra.h:148-154
static inline int av1_use_intra_edge_upsample(int bs0, int bs1, int delta, int type) {
  const int d = abs(delta);
  const int blk_wh = bs0 + bs1;
  if (d == 0 || d >= 40) return 0;  // 偏差为0或太大时不上采样
  return type ? (blk_wh <= 8) : (blk_wh <= 16);
}

9. 非方向性预测:DC / Smooth / Paeth

这 5 种非方向性模式在 aom_dsp/intrapred.c 中实现,通过 reconintra.c 的函数指针表 pred[][]dc_pred[][] 调度。

9.1 DC 预测

DC(直流分量)取上方和左侧像素的平均值。有 4 种变体,取决于邻居可用性:

变体 条件 计算
dc_128 上方不可用且左侧不可用 全块填充 128
dc_top 仅上方可用 DC = mean(above0...bw-1)
dc_left 仅左侧可用 DC = mean(left0...bh-1)
dc 上方和左侧均可用 DC = mean(above\[\] + left\[\]) / (bw + bh)
c 复制代码
// aom_dsp/intrapred.c:216-234
static inline void dc_predictor(uint8_t *dst, ptrdiff_t stride, int bw, int bh,
                           const uint8_t *above, const uint8_t *left) {
  int sum = 0;
  for (int i = 0; i < bw; i++) sum += above[i];
  for (int i = 0; i < bh; i++) sum += left[i];
  int expected_dc = (sum + ((bw + bh) >> 1)) / (bw + bh);
  for (int r = 0; r < bh; r++)
    memset(dst + r * stride, expected_dc, bw);
}

D C = ∑ i = 0 b w − 1 above i + ∑ j = 0 b h − 1 left j + ⌊ ( b w + b h ) / 2 ⌋ b w + b h DC = \frac{\sum_{i=0}^{bw-1} \text{above}i + \sum_{j=0}^{bh-1} \text{left}j + \lfloor(bw+bh)/2\rfloor}{bw + bh} DC=bw+bh∑i=0bw−1abovei+∑j=0bh−1leftj+⌊(bw+bh)/2⌋

9.2 Smooth 预测

Smooth 系列模式用加权插值在边缘像素之间平滑过渡,避免方向性预测可能出现的锐利边界。

Smooth ( r , c ) = w h r ⋅ above c + ( S − w h r ) ⋅ below_pred + w w c ⋅ left r + ( S − w w c ) ⋅ right_pred \text{Smooth}(r,c) = w_hr \cdot \text{above}c + (S - w_hr) \cdot \text{below\_pred} + w_wc \cdot \text{left}r + (S - w_wc) \cdot \text{right\_pred} Smooth(r,c)=whr⋅abovec+(S−whr)⋅below_pred+wwc⋅leftr+(S−wwc)⋅right_pred

其中 below_pred = left b h − 1 \text{below\_pred} = \text{left}bh-1 below_pred=leftbh−1, right_pred = above b w − 1 \text{right\_pred} = \text{above}bw-1 right_pred=abovebw−1, S = 2 SMOOTH_WEIGHT_LOG2_SCALE S = 2^{\text{SMOOTH\_WEIGHT\_LOG2\_SCALE}} S=2SMOOTH_WEIGHT_LOG2_SCALE。 w h w_h\[\] wh\[\] 和 w w w_w\[\] ww\[\] 由 smooth_weights[] 预计算表提供,权重随距离递减。

Smooth_VSmooth_H 是简化版,分别只在垂直或水平方向插值。

9.3 Paeth 预测

Paeth 预测源自 PNG 的 Paeth 滤波器,对每个像素选择梯度最小的方向:

base = above c + left r − above_left P ( r , c ) = argmin { L , T , T L } ∣ base − pixel ∣ 即选择与 base 差值最小的那个邻居像素 \text{base} = \text{above}c + \text{left}r - \text{above\_left} \\ P(r,c) = \underset{\{L,T,TL\}}{\text{argmin}} |\text{base} - \text{pixel}| \\ \text{即选择与 base 差值最小的那个邻居像素} base=abovec+leftr−above_leftP(r,c)={L,T,TL}argmin∣base−pixel∣即选择与 base 差值最小的那个邻居像素

c 复制代码
// aom_dsp/intrapred.c:47-58
static inline uint16_t paeth_predictor_single(uint16_t left, uint16_t top,
                                                uint16_t top_left) {
  const int base = top + left - top_left;
  const int p_left = abs_diff(base, left);
  const int p_top  = abs_diff(base, top);
  const int p_tl   = abs_diff(base, top_left);
  return (p_left <= p_top && p_left <= p_tl) ? left
       : (p_top  <= p_tl)                  ? top
                                              : top_left;
}

10. Filter Intra:基于 7-tap 滤波器的帧内预测

Filter Intra 是 AV1 相对于 VP9/HEVC 引入的新工具,仅在 DC 模式基础上增加一层7 参考点的二维滤波

10.1 5 种滤波模式

枚举值 含义 特点
FILTER_DC_PRED 类 DC 滤波 4 个上方 + 2 个左侧参考
FILTER_V_PRED 类垂直 水平方向参考点更多
FILTER_H_PRED 类水平 垂直方向参考点更多
FILTER_D157_PRED 类 157° 对角方向参考
FILTER_PAETH_PRED 类 Paeth 混合方向参考

10.2 滤波核:av1_filter_intra_taps\[\]\[\]

滤波核是预计算的系数表,形状为 [5 modes][8 positions][7 taps]。每个输出像素由 7 个输入像素加权求和:

c 复制代码
// reconintra.c:794-846 --- 系数表结构
// 每个输出 2×4 块由 7 个输入像素 p0..p6 加权
// p0: buffer[r-1][c-1]   左上
// p1: buffer[r-1][c]     上方第一个
// p2: buffer[r-1][c+1]   上方第二个
// p3: buffer[r-1][c+2]   上方第三个
// p4: buffer[r-1][c+3]   上方第四个
// p5: buffer[r][c-1]     左侧
// p6: buffer[r+1][c-1]   左下

预测公式:

p r = ∑ k = 0 6 taps k × p k p_r = \sum_{k=0}^{6} \text{taps}k \times p_k pr=k=0∑6tapsk×pk

buffer r + r o c + c o = Clip1 ( p r ≫ FILTER_INTRA_SCALE_BITS ) \text{buffer}r+r_oc+c_o = \text{Clip1}(p_r \gg \text{FILTER\_INTRA\_SCALE\_BITS}) bufferr+roc+co=Clip1(pr≫FILTER_INTRA_SCALE_BITS)

FILTER_INTRA_SCALE_BITS = 4(reconintra.h:49),即除以 16。系数和通常约为 16,保证输出动态范围合理。

复制代码
Filter Intra 参考像素布局 (2×4 输出块)

        c-1    c      c+1    c+2    c+3
  r-1  ┌──────┬──────┬──────┬──────┬──────┐
       │  p0  │  p1  │  p2  │  p3  │  p4  │  ← above 参考行
  r    ├──────┼──────┼──────┼──────┼──────┤
       │  p5  │  ○   │  ○   │  ○   │      │
  r+1  │      │  ○   │  ○   │  ○   │      │  ← 2×4 输出块(8个像素)
       │  p6  │  ○   │  ○   │  ○   │      │
       └──────┴──────┴──────┴──────┴──────┘

💡 Filter Intra vs 方向性预测 :Filter Intra 仅限 ≤32×32 的块且仅在 DC 模式下启用(av1_filter_intra_allowed()),代价是码流中多传一个 filter_intra_mode(2 bit),换来的是对小块平坦区域更好的预测精度。


11. CFL(Chroma From Luma)色度预测集成

CFL 是 AV1 的一个独特特性------用亮度分量的重建值 来预测色度分量。它的集成入口在 av1_predict_intra_block_facade() 函数中:

c 复制代码
// reconintra.c:1841-1872 --- CFL 分支
if (plane != AOM_PLANE_Y && mbmi->uv_mode == UV_CFL_PRED) {
  // 1. 先用 DC 模式预测色度,得到 dc_pred
  av1_predict_intra_block(..., DC_PRED, ...);
  cfl_store_dc_pred(xd, dst, pred_plane, tx_size_wide[tx_size]);

  // 2. 用 luma 重建值 + alpha 缩放预测色度
  av1_cfl_predict_block(xd, dst, dst_stride, tx_size, plane);
  return;
}

CFL 的工作原理:

C pred ( r , c ) = α × L recon ( r , c ) + D C chroma C_{\text{pred}}(r,c) = \alpha \times L_{\text{recon}}(r,c) + DC_{\text{chroma}} Cpred(r,c)=α×Lrecon(r,c)+DCchroma

α 是从码流解析的缩放因子, D C chroma DC_{\text{chroma}} DCchroma 是色度 DC 分量。CFL 特别适合色彩平滑过渡的区域(如天空、皮肤)。

⚠️ 注意 :CFL 预测的 luma 重建值来自 av1_predict_intra_block 的输出(即已经加回残差后的值),而非 luma 的预测值。这是一个容易混淆的设计细节。


12. Palette 模式与 HBD(高比特深度)处理

12.1 Palette 模式

当块使用调色板模式时(use_palette = true),帧内预测被完全绕过,直接用索引映射表查表:

c 复制代码
// reconintra.c:1693-1714
if (use_palette) {
  const uint16_t *const palette =
      mbmi->palette_mode_info.palette_colors + plane * PALETTE_MAX_SIZE;
  for (int r = 0; r < txhpx; ++r)
    for (int c = 0; c < txwpx; ++c)
      dst[r * stride + c] = palette[map[(r+y)*wpx + c+x]];
}

这种模式特别适合屏幕内容(GUI 界面、动画),色阶有限的区域可以大幅节省码率。

12.2 HBD(高比特深度)并行实现

整个文件通过 #if CONFIG_AV1_HIGHBITDEPTH 编译开关维护了一套完整的 16-bit 并行代码。HBD 版本与 8-bit 版本在算法上完全一致,差异仅在:

8-bit HBD (10/12 bit)
uint8_t uint16_t
默认值 127 / 129 默认值 128 << (bd-8)
memset(dst, val, n) aom_memset16(dst, val, n)
clip_pixel() clip_pixel_highbd(val, bd)

这意味着文件的代码量几乎翻倍------8-bit 版本约 900 行,HBD 版本约 500 行,共享约 400 行逻辑。


13. 调用入口:从 predict_intra_block_facade 到 dispatch

整个文件的调用链如下:

复制代码
reconintra.c 函数调用图

  ┌─────────────────────────────────────┐
  │   av1_predict_intra_block_facade()   │  ← 公共入口 (第 1824 行)
  └──────────────┬──────────────────────┘
                 │
            ┌────▼────┐
            │palette? │──Yes──→ Palette 查表 (return)
            └────┬────┘
                 │ No
            ┌────▼────┐
            │  CFL?   │──Yes──→ DC pred → cfl_predict_block() (return)
            └────┬────┘
                 │ No
  ┌──────────────▼──────────────────────┐
  │    av1_predict_intra_block()         │  ← 模式分发 (第 1676 行)
  └──────────────┬──────────────────────┘
                 │
     ┌───────────┼───────────┐
     │           │           │
  filter_intra?  非方向?    方向?
     │           │           │
     └───────────┼───────────┘
                 │
     ┌───────────▼───────────┐
     │  build_non_directional│
     │  _intra_predictors()  │ ──→ dc_pred[][] / pred[][] 函数指针
     └───────────────────────┘
                 │
     ┌───────────▼───────────────────────────┐
     │  build_directional_and_filter_intra   │
     │  _predictors()                        │  ← 参考像素填充 + 预测 (第 1072 行)
     └───────────────┬───────────────────────┘
                     │
          ┌──────────┼──────────┐
          │          │          │
       Filter      Zone 1/2/3  Edge Filter
       Intra       dr_predictor + Upsample

最终的 dispatch 逻辑总结为:

  1. Palette 模式 :直接查表,return
  2. CFL 模式 :先调 DC 预测获取色度 DC,再调 av1_cfl_predict_block()return
  3. 非方向模式 (DC/Smooth/Paeth):调 build_non_directional_intra_predictors()
  4. 方向模式 / Filter Intra:调 build_directional_and_filter_intra_predictors(),内部再分流到 z1/z2/z3 或 filter_intra_predictor。

14. 总结:reconintra.c 的工程美学

这个 1882 行的 C 文件承载了 AV1 帧内预测的完整实现。从工程视角看,有几个值得学习的亮点:

查表法替代运行时计算

has_tr_* / has_bl_* 查找表将复杂的分区-解码顺序分析转化为 O(1) 位运算。26 种块尺寸 × 2 方向 × 特殊分区 = 约 50 个静态数组,总计约 5KB,换来每个像素预测节省数十个条件分支。

定点数运算

所有浮点运算被转换为整数位运算:<< 6 乘法、>> frac_bits 除法、5-bit 插值权重。在嵌入式和 SIMD 环境下至关重要。

零开销抽象(RTCD)

pred[][]dc_pred[][] 函数指针表在 init_intra_predictors_internal() 中初始化。C 版本的实现放在 aom_dsp/intrapred.c,SSE2/AVX2/NEON 版本放在各架构子目录。RTCD(Run-Time CPU Detection)机制在运行时根据 CPU 特性选择最优实现,调用者无需感知。

渐进式降级

邻居像素缺失时有一套完整的降级策略:有 above 用 above,没有就用 left,都没有就用 128/129。边缘滤波和上采样各自有独立的启用条件。这种"优雅降级"设计确保在任何边界条件下都不会 crash。

💡 给读者的建议:如果你想深入理解某个具体算法,建议的阅读顺序是:

  1. 先看 av1_predict_intra_block_facade()(第 1824 行)理解入口调度
  2. 再看 build_directional_and_filter_intra_predictors()(第 1072 行)理解参考像素填充
  3. 然后看 av1_dr_prediction_z2_c()(第 558 行)理解最复杂的双向插值
  4. 最后看 av1_filter_intra_predictor_c()(第 848 行)理解 Filter Intra

相关推荐
不才小强2 小时前
live555源码分析--client流程分析2
音视频
南山有乔木7892 小时前
音频文件怎么从MP3转换成WAV?音频处理、剪辑导入都适用的教程
音视频
AI服务老曹3 小时前
统一安防底座:基于 GB28181 与 RTSP 的边缘计算 AI 视频管理平台架构演进(附 Docker 部署与源码交付机制)
人工智能·音视频·边缘计算
fangcaojushi3 小时前
文创图影 视频生成完整流程
音视频
DogDaoDao4 小时前
深入解析 libaom:AV1 开源编解码库技术分析
google·开源·音视频·视频编解码·hevc·av1·libaom
开开心心就好4 小时前
解决图片无页码添加功能的实用工具
javascript·python·安全·智能手机·pdf·音视频·1024程序员节
EasyCVR14 小时前
国标GB28181视频监控平台EasyCVR行业解决方案深度解读——雪亮工程、智慧城市与智慧交通
人工智能·音视频·智慧城市
“码”力全开17 小时前
打破芯片与协议壁垒:基于 Docker + 边缘计算的 GB28181/RTSP 视频智能管理平台架构设计与源码交付方案
docker·音视频·边缘计算
AI服务老曹1 天前
解密企业级视频中台:基于 GB28181/RTSP 统一接入与边缘计算的 AI 视频管理平台(附 Docker 部署与源码交付方案)
人工智能·音视频·边缘计算