libaom v3.9 · 1882 行 · 从参考像素到预测值的完整链路 · 含公式推导、图解与 SIMD 优化原理
目录
- [帧内预测在 AV1 编码管线中的位置](#帧内预测在 AV1 编码管线中的位置)
- [reconintra.c 文件全景地图](#reconintra.c 文件全景地图)
- [13 种帧内预测模式与角度映射](#13 种帧内预测模式与角度映射)
- 参考像素:邻居可用性判定
- 参考像素填充与扩展
- [方向性预测的数学原理(Zone 1/2/3)](#方向性预测的数学原理(Zone 1/2/3))
- [角度微调(angle_delta)与 dr_intra_derivative 表](#角度微调(angle_delta)与 dr_intra_derivative 表)
- 边缘滤波与上采样
- [非方向性预测:DC / Smooth / Paeth](#非方向性预测:DC / Smooth / Paeth)
- [Filter Intra:基于 7-tap 滤波器的帧内预测](#Filter Intra:基于 7-tap 滤波器的帧内预测)
- [CFL(Chroma From Luma)色度预测集成](#CFL(Chroma From Luma)色度预测集成)
- [Palette 模式与 HBD(高比特深度)处理](#Palette 模式与 HBD(高比特深度)处理)
- [调用入口:从 predict_intra_block_facade 到 dispatch](#调用入口:从 predict_intra_block_facade 到 dispatch)
- [总结: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_V 和 Smooth_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 逻辑总结为:
- Palette 模式 :直接查表,
return。 - CFL 模式 :先调 DC 预测获取色度 DC,再调
av1_cfl_predict_block()。return。 - 非方向模式 (DC/Smooth/Paeth):调
build_non_directional_intra_predictors()。 - 方向模式 / 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。
💡 给读者的建议:如果你想深入理解某个具体算法,建议的阅读顺序是:
- 先看
av1_predict_intra_block_facade()(第 1824 行)理解入口调度- 再看
build_directional_and_filter_intra_predictors()(第 1072 行)理解参考像素填充- 然后看
av1_dr_prediction_z2_c()(第 558 行)理解最复杂的双向插值- 最后看
av1_filter_intra_predictor_c()(第 848 行)理解 Filter Intra