AV1 环路滤波器深度解析:av1_loopfilter.c 源码原理详解

前言

在视频编解码领域,环路滤波器(Loop Filter) 是提升压缩质量和视觉体验的关键技术之一。作为 AV1 视频编码器的核心模块,环路滤波器位于解码环路内,通过平滑块边界处的像素突变来消除压缩伪影(Blocking Artifacts),从而在相同码率下获得更好的主观质量。

本文将深入剖析 libaom 编码器中 av1_loopfilter.c 文件的源码实现,从设计思想、数据结构、核心算法到 SIMD 优化策略进行全面解读。


一、环路滤波器概述

1.1 为什么需要环路滤波器?

在基于块的视频编码(如 HEVC、AV1)中,编码器将图像划分为多个块(Block)进行独立预测和变换编码。这种处理方式在块边界处会产生人为的边界效应(Blocking Artifacts),表现为:

  • 相邻块之间的像素值不连续
  • 块边缘出现明显的"棋盘格"或"阶梯"状伪影
  • 量化误差在块边界处累积

环路滤波器的作用就是在重建图像输出前,对其进行后处理,消除这些失真,同时尽量保留真实的图像细节。

1.2 AV1 环路滤波器的特点

AV1 的环路滤波器设计具有以下特点:

特性 描述
自适应滤波强度 根据图像内容、块类型、参考帧等因素动态调整
分段线性滤波器 使用不同的滤波器系数处理平滑区域和边缘区域
双重滤波 同时处理亮度(Luma)和色度(Chroma)分量
可变滤波器长度 支持 4-tap、6-tap、8-tap、14-tap 多种滤波器
SIMD 优化 利用 SSE/AVX 指令集实现高效的向量化计算

二、核心数据结构

2.1 环路滤波器参数结构体

c 复制代码
struct loopfilter {
    int filter_level[2];        // 水平和垂直方向的滤波强度
    int filter_level_u;         // U 色度分量的滤波强度
    int filter_level_v;         // V 色度分量的滤波强度
    
    int sharpness_level;       // 锐度级别(0-7),控制滤波器边界保持程度
    
    uint8_t mode_ref_delta_enabled;  // 是否启用模式和参考帧增量
    uint8_t mode_ref_delta_update;   // 是否更新增量值
    
    int8_t ref_deltas[REF_FRAMES];    // 各参考帧的滤波强度增量
    int8_t mode_deltas[MAX_MODE_LF_DELTAS];  // 模式相关的增量
};

2.2 滤波阈值结构体

c 复制代码
typedef struct {
    DECLARE_ALIGNED(SIMD_WIDTH, uint8_t, mblim[SIMD_WIDTH]);  // 滤波决策阈值
    DECLARE_ALIGNED(SIMD_WIDTH, uint8_t, lim[SIMD_WIDTH]);      // 滤波器限幅值
    DECLARE_ALIGNED(SIMD_WIDTH, uint8_t, hev_thr[SIMD_WIDTH]);   // 高方差阈值
} loop_filter_thresh;

其中:

  • mblim(mb limit):决定是否对某像素应用滤波的条件阈值
  • lim(limit):滤波输出的限幅值,防止过度校正
  • hev_thr(high edge variance threshold):区分平坦区域和边缘区域

2.3 滤波参数查询表

c 复制代码
typedef struct {
    loop_filter_thresh lfthr[MAX_LOOP_FILTER + 1];  // 0-63 级滤波阈值
    // lvl[平面][分段ID][方向][参考帧][模式]
    uint8_t lvl[MAX_MB_PLANE][MAX_SEGMENTS][2][REF_FRAMES][MAX_MODE_LF_DELTAS];
} loop_filter_info_n;

2.4 去块参数结构体

c 复制代码
typedef struct AV1_DEBLOCKING_PARAMETERS {
    uint8_t filter_length;           // 滤波器长度(4/6/8/14)
    const loop_filter_thresh *lfthr;  // 指向阈值的指针
} AV1_DEBLOCKING_PARAMETERS;

三、滤波强度计算

3.1 锐度更新函数

update_sharpness() 函数根据锐度级别(sharpness)初始化滤波阈值:

c 复制代码
static void update_sharpness(loop_filter_info_n *lfi, int sharpness_lvl) {
    int lvl;
    
    for (lvl = 0; lvl <= MAX_LOOP_FILTER; lvl++) {
        // block_inside_limit 控制边缘保持程度
        int block_inside_limit = lvl >> ((sharpness_lvl > 0) + (sharpness_lvl > 4));
        
        if (sharpness_lvl > 0) {
            if (block_inside_limit > (9 - sharpness_lvl))
                block_inside_limit = (9 - sharpness_lvl);
        }
        
        if (block_inside_limit < 1) block_inside_limit = 1;
        
        memset(lfi->lfthr[lvl].lim, block_inside_limit, SIMD_WIDTH);
        memset(lfi->lfthr[lvl].mblim, (2 * (lvl + 2) + block_inside_limit),
               SIMD_WIDTH);
    }
}

算法原理:

  • block_inside_limit 决定滤波器对边缘的"保护"程度
  • 锐度越高,block_inside_limit 越小,滤波器越保守,保留更多边缘细节
  • mblim 的计算公式:2 * (lvl + 2) + block_inside_limit

3.2 滤波等级获取

av1_get_filter_level() 函数综合多因素计算最终滤波强度:

c 复制代码
uint8_t av1_get_filter_level(const AV1_COMMON *cm,
                             const loop_filter_info_n *lfi_n, const int dir_idx,
                             int plane, const MB_MODE_INFO *mbmi) {
    const int segment_id = mbmi->segment_id;
    if (cm->delta_q_info.delta_lf_present_flag) {
        int8_t delta_lf;
        // 获取增量值(支持多平面独立增量)
        if (cm->delta_q_info.delta_lf_multi) {
            const int delta_lf_idx = delta_lf_id_lut[plane][dir_idx];
            delta_lf = mbmi->delta_lf[delta_lf_idx];
        } else {
            delta_lf = mbmi->delta_lf_from_base;
        }
        
        // 计算基础等级
        int base_level;
        if (plane == 0)
            base_level = cm->lf.filter_level[dir_idx];
        else if (plane == 1)
            base_level = cm->lf.filter_level_u;
        else
            base_level = cm->lf.filter_level_v;
        
        int lvl_seg = clamp(delta_lf + base_level, 0, MAX_LOOP_FILTER);
        
        // 应用分段特征增量
        const int seg_lf_feature_id = seg_lvl_lf_lut[plane][dir_idx];
        if (segfeature_active(&cm->seg, segment_id, seg_lf_feature_id)) {
            const int data = get_segdata(&cm->seg, segment_id, seg_lf_feature_id);
            lvl_seg = clamp(lvl_seg + data, 0, MAX_LOOP_FILTER);
        }
        
        // 应用参考帧和模式增量
        if (cm->lf.mode_ref_delta_enabled) {
            const int scale = 1 << (lvl_seg >> 5);
            lvl_seg += cm->lf.ref_deltas[mbmi->ref_frame[0]] * scale;
            if (mbmi->ref_frame[0] > INTRA_FRAME)
                lvl_seg += cm->lf.mode_deltas[mode_lf_lut[mbmi->mode]] * scale;
            lvl_seg = clamp(lvl_seg, 0, MAX_LOOP_FILTER);
        }
        return lvl_seg;
    }
    // 兼容旧版本的直接查询方式
    return lfi_n->lvl[plane][segment_id][dir_idx][mbmi->ref_frame[0]]
                     [mode_lf_lut[mbmi->mode]];
}

滤波等级计算公式:

复制代码
lvl = base_level + delta_lf + segment_delta + ref_delta + mode_delta

四、变换尺寸感知的边界检测

4.1 变换尺寸映射

AV1 支持从 4x4 到 64x64 的多种变换尺寸。get_transform_size() 函数根据当前宏块信息确定实际使用的变换大小:

c 复制代码
static AOM_FORCE_INLINE TX_SIZE get_transform_size(
    const MACROBLOCKD *const xd, const MB_MODE_INFO *const mbmi,
    const int mi_row, const int mi_col, const int plane,
    const int ss_x, const int ss_y) {
    
    if (xd && xd->lossless[mbmi->segment_id]) return TX_4X4;
    
    TX_SIZE tx_size = (plane == AOM_PLANE_Y)
                      ? mbmi->tx_size
                      : av1_get_max_uv_txsize(mbmi->bsize, ss_x, ss_y);
    
    // 对于帧间块,考虑分割的变换尺寸
    if ((plane == AOM_PLANE_Y) && is_inter_block(mbmi) && !mbmi->skip_txfm) {
        const BLOCK_SIZE sb_type = mbmi->bsize;
        const int blk_row = mi_row & (mi_size_high[sb_type] - 1);
        const int blk_col = mi_col & (mi_size_wide[sb_type] - 1);
        const TX_SIZE mb_tx_size = mbmi->inter_tx_size[
            av1_get_txb_size_index(sb_type, blk_row, blk_col)];
        tx_size = mb_tx_size;
    }
    
    return tx_size;
}

4.2 滤波器长度表

AV1 使用精心设计的滤波器长度查找表,根据当前块和邻居块的变换尺寸确定最优滤波器长度:

c 复制代码
static const int tx_dim_to_filter_length[TX_SIZES] = { 4, 8, 14, 14, 14 };

对于 Luma 分量:

  • TX_4X4 → 4-tap 滤波
  • TX_8X8 → 8-tap 滤波
  • TX_16X16 及更大 → 14-tap 滤波

对于 Chroma 分量:

  • TX_4X4 → 4-tap 滤波
  • TX_8X8 及更大 → 6-tap 滤波

五、边界检测与滤波决策

5.1 TU 边界检测

set_lpf_parameters() 函数的核心任务是判断当前像素位置是否需要滤波:

c 复制代码
static TX_SIZE set_lpf_parameters(
    AV1_DEBLOCKING_PARAMETERS *const params,
    const AV1_COMMON *const cm, const MACROBLOCKD *const xd,
    const EDGE_DIR edge_dir, const uint32_t x, const uint32_t y,
    const int plane, const struct macroblockd_plane *const plane_ptr) {
    
    params->filter_length = 0;
    
    // 检查图像边界
    const uint32_t width = plane_ptr->dst.width;
    const uint32_t height = plane_ptr->dst.height;
    if ((width <= x) || (height <= y)) return TX_4X4;
    
    // 获取当前块和邻居块的信息
    const TX_SIZE ts = get_transform_size(xd, mi[0], mi_row, mi_col, plane, ...);
    
    // 检测是否为变换单元边界
    const uint32_t transform_masks = 
        (edge_dir == VERT_EDGE) ? tx_size_wide[ts] - 1 : tx_size_high[ts] - 1;
    const int32_t tu_edge = (coord & transform_masks) ? 0 : 1;
    
    if (!tu_edge) return ts;  // 非 TU 边界,跳过
    
    // 获取滤波等级
    const uint32_t curr_level = av1_get_filter_level(cm, &cm->lf_info, 
                                                      edge_dir, plane, mbmi);
    
    // 获取邻居块的滤波等级
    const MB_MODE_INFO *const mi_prev = *(mi - mode_step);
    const uint32_t pv_lvl = av1_get_filter_level(cm, &cm->lf_info, 
                                                  edge_dir, plane, mi_prev);
    
    // 判断是否需要滤波
    if ((curr_level || pv_lvl) && (!pv_skip_txfm || !curr_skipped || pu_edge)) {
        // 确定滤波器长度
        const int dim = AOMMIN(tx_size_wide_unit_log2[ts], 
                               tx_size_wide_unit_log2[pv_ts]);
        params->filter_length = plane ? ((dim == 0) ? 4 : 6) 
                                       : tx_dim_to_filter_length[dim];
    }
}

5.2 滤波决策条件

滤波决策需要同时满足以下条件:

  1. 边界条件:当前像素位于 TU/PU 边界
  2. 等级条件:至少一个块有非零滤波等级
  3. 跳过条件:两个块都跳过了变换编码且不在 PU 边界

六、垂直与水平滤波

6.1 垂直边缘滤波

filter_vert() 函数处理垂直边缘(块左边界和内部垂直边界):

c 复制代码
static AOM_INLINE void filter_vert(uint8_t *dst, int dst_stride,
    const AV1_DEBLOCKING_PARAMETERS *params,
    const SequenceHeader *seq_params,
    USE_FILTER_TYPE use_filter_type) {
    
    const loop_filter_thresh *limits = params->lfthr;
    
    // 根据滤波长度和类型选择对应函数
    switch (params->filter_length) {
        case 4:  // 4-tap 滤波
            if (use_filter_type == USE_QUAD)
                aom_lpf_vertical_4_quad(dst, dst_stride, ...);
            else if (use_filter_type == USE_DUAL)
                aom_lpf_vertical_4_dual(dst, dst_stride, ...);
            else
                aom_lpf_vertical_4(dst, dst_stride, ...);
            break;
        case 6:  // 6-tap 滤波(仅色度)
            ...
        case 8:  // 8-tap 滤波
            ...
        case 14: // 14-tap 滤波(亮度)
            ...
        default: // 无滤波
            break;
    }
}

6.2 水平边缘滤波

filter_horz() 函数处理水平边缘(块上边界和内部水平边界),逻辑与垂直滤波类似。

6.3 滤波类型选择

AV1 实现了三种滤波粒度以平衡质量和性能:

类型 描述 使用场景
USE_SINGLE 逐块处理 默认模式,最通用
USE_DUAL 一次处理2个相邻块 8像素高度的倍数行
USE_QUAD 一次处理4个相邻块 16像素高度的倍数行,需块尺寸一致
c 复制代码
if ((y & 3) == 0 && (y + 3) < y_range && min_block_height >= 16) {
    use_filter_type = USE_QUAD;
    y += 3;
} else if ((y + 1) < y_range && min_block_height >= 8) {
    use_filter_type = USE_DUAL;
    y += 1;
}

七、SIMD 优化策略

7.1 内存对齐

为了充分利用 SIMD 指令,数据结构使用 DECLARE_ALIGNED 进行 16 字节对齐:

c 复制代码
typedef struct {
    DECLARE_ALIGNED(SIMD_WIDTH, uint8_t, mblim[SIMD_WIDTH]);  // SIMD_WIDTH = 16
    DECLARE_ALIGNED(SIMD_WIDTH, uint8_t, lim[SIMD_WIDTH]);
    DECLARE_ALIGNED(SIMD_WIDTH, uint8_t, hev_thr[SIMD_WIDTH]);
} loop_filter_thresh;

7.2 高位深支持

AV1 支持 8-bit、10-bit、12-bit 多种位深,CONFIG_AV1_HIGHBITDEPTH 宏控制条件编译:

c 复制代码
#if CONFIG_AV1_HIGHBITDEPTH
if (use_highbitdepth) {
    uint16_t *dst_shortptr = CONVERT_TO_SHORTPTR(dst);
    aom_highbd_lpf_vertical_4(dst_shortptr, dst_stride, 
                               limits->mblim, limits->lim, 
                               limits->hev_thr, bit_depth);
    return;
}
#endif

7.3 SIMD 函数命名约定

libaom 的 SIMD 函数遵循统一的命名规范:

复制代码
aom_[highbd_]lpf_[方向]_[长度]_[类型]

示例:

  • aom_lpf_vertical_4_quad - 8-bit 4-tap 垂直 Quad 模式
  • aom_highbd_lpf_horizontal_8_dual - 10/12-bit 8-tap 水平 Dual 模式

八、帧级滤波初始化

8.1 滤波器初始化

av1_loop_filter_init() 在解码器创建时调用,初始化全局阈值:

c 复制代码
void av1_loop_filter_init(AV1_COMMON *cm) {
    loop_filter_info_n *lfi = &cm->lf_info;
    struct loopfilter *lf = &cm->lf;
    
    update_sharpness(lfi, lf->sharpness_level);
    
    // 初始化 HEV 阈值
    for (int lvl = 0; lvl <= MAX_LOOP_FILTER; lvl++)
        memset(lfi->lfthr[lvl].hev_thr, (lvl >> 4), SIMD_WIDTH);
}

8.2 帧级滤波参数更新

av1_loop_filter_frame_init() 在每帧开始时计算所有可能的滤波等级组合:

c 复制代码
void av1_loop_filter_frame_init(AV1_COMMON *cm, int plane_start, int plane_end) {
    // 获取各平面的基础滤波等级
    filt_lvl[0] = cm->lf.filter_level[0];  // 水平
    filt_lvl[1] = cm->lf.filter_level_u;
    filt_lvl[2] = cm->lf.filter_level_v;
    
    for (plane = plane_start; plane < plane_end; plane++) {
        for (seg_id = 0; seg_id < MAX_SEGMENTS; seg_id++) {
            for (int dir = 0; dir < 2; ++dir) {
                int lvl_seg = (dir == 0) ? filt_lvl[plane] : filt_lvl_r[plane];
                
                // 应用分段增量
                if (segfeature_active(seg, seg_id, seg_lf_feature_id)) {
                    lvl_seg = clamp(lvl_seg + data, 0, MAX_LOOP_FILTER);
                }
                
                // 构建完整的等级查找表
                if (!lf->mode_ref_delta_enabled) {
                    memset(lfi->lvl[plane][seg_id][dir], lvl_seg, 
                           sizeof(lfi->lvl[plane][seg_id][dir]));
                } else {
                    // 为每个参考帧和模式组合计算等级
                    const int scale = 1 << (lvl_seg >> 5);
                    const int intra_lvl = lvl_seg + lf->ref_deltas[INTRA_FRAME] * scale;
                    lfi->lvl[plane][seg_id][dir][INTRA_FRAME][0] = clamp(intra_lvl, 0, MAX_LOOP_FILTER);
                    
                    for (ref = LAST_FRAME; ref < REF_FRAMES; ++ref) {
                        for (mode = 0; mode < MAX_MODE_LF_DELTAS; ++mode) {
                            const int inter_lvl = lvl_seg + 
                                lf->ref_deltas[ref] * scale + 
                                lf->mode_deltas[mode] * scale;
                            lfi->lvl[plane][seg_id][dir][ref][mode] = 
                                clamp(inter_lvl, 0, MAX_LOOP_FILTER);
                        }
                    }
                }
            }
        }
    }
}

九、滤波执行流程

9.1 平面滤波入口函数

c 复制代码
void av1_filter_block_plane_vert(const AV1_COMMON *const cm,
    const MACROBLOCKD *const xd, const int plane,
    const MACROBLOCKD_PLANE *const plane_ptr,
    const uint32_t mi_row, const uint32_t mi_col) {
    
    uint8_t *const dst_ptr = plane_ptr->dst.buf;
    const int dst_stride = plane_ptr->dst.stride;
    
    // 计算处理范围
    const int y_range = AOMMIN((int)(plane_mi_rows - (mi_row >> scale_vert)),
                               (MAX_MIB_SIZE >> scale_vert));
    const int x_range = AOMMIN((int)(plane_mi_cols - (mi_col >> scale_horz)),
                               (MAX_MIB_SIZE >> scale_horz));
    
    for (int y = 0; y < y_range; y++) {
        uint8_t *p = dst_ptr + y * MI_SIZE * dst_stride;
        for (int x = 0; x < x_range;) {
            AV1_DEBLOCKING_PARAMETERS params;
            memset(&params, 0, sizeof(params));
            
            // 设置滤波参数
            tx_size = set_lpf_parameters(&params, ((ptrdiff_t)1 << scale_horz), 
                                         cm, xd, VERT_EDGE, curr_x, curr_y, 
                                         plane, plane_ptr);
            
            // 执行滤波
            filter_vert(p, dst_stride, &params, cm->seq_params, USE_SINGLE);
            
            // 移动到下一个块
            advance_units = tx_size_wide_unit[tx_size];
            x += advance_units;
            p += advance_units * MI_SIZE;
        }
    }
}

9.2 优化版本滤波函数

av1_filter_block_plane_vert_opt() 系列函数使用预计算的参数缓冲区,减少重复调用 set_lpf_parameters() 的开销:

c 复制代码
void av1_filter_block_plane_vert_opt(
    const AV1_COMMON *const cm, const MACROBLOCKD *const xd,
    const MACROBLOCKD_PLANE *const plane_ptr, const uint32_t mi_row,
    const uint32_t mi_col, 
    AV1_DEBLOCKING_PARAMETERS *params_buf,  // 预计算参数缓冲区
    TX_SIZE *tx_buf,                         // 变换尺寸缓冲区
    int num_mis_in_lpf_unit_height_log2) {
    
    // 批量设置参数
    set_lpf_parameters_for_line_luma(params_buf, tx_buf, cm, xd, VERT_EDGE,
                                     x_start, curr_y, plane_ptr, x_end,
                                     mode_step, &min_block_height);
    
    // 批量执行滤波
    for (int x = 0; x < x_range;) {
        filter_vert(p, dst_stride, params, cm->seq_params, use_filter_type);
        ...
    }
}

十、关键技术总结

10.1 核心技术点

技术 实现文件 作用
自适应滤波 update_sharpness() 根据锐度级别调整边界保护
多因素等级计算 av1_get_filter_level() 综合分段、参考帧、模式计算滤波强度
TX 感知边界 get_transform_size() 根据变换尺寸确定滤波边界
SIMD 向量化 filter_vert/horz() 利用 SSE/AVX 加速滤波计算
双/四块并行 USE_DUAL/QUAD 减少函数调用开销

10.2 性能优化技巧

  1. 预计算查找表 :避免运行时计算,使用 lfthr[]lvl[][][][][]
  2. 批量处理:利用 Dual/Quad 模式一次处理多个块
  3. 边界剪枝:跳过早期的非边界像素,减少无效计算
  4. SIMD 对齐:确保数据对齐以充分发挥向量指令性能

10.3 滤波质量控制

复制代码
高锐度 (sharpness=7) → 弱滤波 → 更多细节,更少平滑
低锐度 (sharpness=0) → 强滤波 → 更多平滑,更少细节

结语

av1_loopfilter.c 实现了 AV1 标准中定义的环路滤波器核心功能,其设计体现了现代视频编解码器在质量和性能之间的精细平衡:

  • 多粒度滤波:4/6/8/14-tap 多种滤波器长度适应不同场景
  • 内容自适应:根据块类型、参考帧、变换尺寸动态调整
  • SIMD 优化:充分利用现代 CPU 的向量化能力
  • 内存效率:通过预计算和批量处理减少访问开销

理解环路滤波器的工作原理,不仅有助于深入掌握视频编码技术,也为在其他编解码器或图像处理场景中实现类似优化提供了宝贵的参考。


参考资源:

相关推荐
ZC跨境爬虫2 小时前
跟着MDN学HTML_day_47:(Document接口)
前端·javascript·ui·html·ecmascript·音视频
YuxuanSys-Regen2 小时前
WMMAV&YUXUANSYS/育轩:Dante主机接入手持发射器:让会议音频进入“无线高保真”时代
音视频·腾讯会议·teams·dante·无线手持·音频设备
kcuwu.2 小时前
博客转抖音视频(文件上传版)Coze工作流实现文档(第一版)
人工智能·音视频·coze
沙振宇3 小时前
【Python】使用YOLO8识别视频中的车与人物
python·yolo·音视频·状态模式·识别
开开心心就好4 小时前
支持添加网址的资源快速打开工具
人工智能·学习·游戏·音视频·hbase·语音识别·storm
AI搅拌机17 小时前
LTX2.3 IC-LORA动作迁移,通过depth、POSE、Canny精准控制生成的视频!
人工智能·音视频
蒋胜山17 小时前
PowerPoint插入音频报错
windows·经验分享·音视频
byte轻骑兵18 小时前
【LE Audio】CAP精讲[5]: 导演上线!Initiator音频协同全流程合规指南
人工智能·音视频·低功耗·le audio
知识领航员21 小时前
2026年精选4款音频处理软件:Adobe Audition领衔,蘑兔AI音乐紧随其后
adobe·音视频