FFmpeg6的滤镜函数解析

觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。

由于能力水平有限,文中的错误或不严谨的地方在所难免,还请批评指正。

目录

[1. 先建立直觉:滤镜图就是一条"管道"](#1. 先建立直觉:滤镜图就是一条"管道")

[2. 滤镜系统的四大件](#2. 滤镜系统的四大件)

[3. 搭一条最简单的视频滤镜链](#3. 搭一条最简单的视频滤镜链)

[3.1 创建滤镜图](#3.1 创建滤镜图)

[3.2 创建输入端(buffer)](#3.2 创建输入端(buffer))

[3.3 创建输出端(buffersink)](#3.3 创建输出端(buffersink))

[3.4 插入中间的滤镜(scale)](#3.4 插入中间的滤镜(scale))

[3.5 连接滤镜](#3.5 连接滤镜)

[3.6 配置并校验整张图](#3.6 配置并校验整张图)

[4. 把帧送进去、取出来](#4. 把帧送进去、取出来)

[4.1 送帧](#4.1 送帧)

[4.2 取帧](#4.2 取帧)

[5. 用字符串语法搭图(90% 场景推荐)](#5. 用字符串语法搭图(90% 场景推荐))

[6. 音频滤镜:跟视频几乎一模一样](#6. 音频滤镜:跟视频几乎一模一样)

[6.1 音频 abuffer 的初始化](#6.1 音频 abuffer 的初始化)

[6.2 音量滤镜](#6.2 音量滤镜)

[6.3 变速不变调(播放大杀器)](#6.3 变速不变调(播放大杀器))

[7. FFmpeg 6 里滤镜的几个实用变化](#7. FFmpeg 6 里滤镜的几个实用变化)

[7.1 音频 channel layout 统一用 AVChannelLayout](#7.1 音频 channel layout 统一用 AVChannelLayout)

[7.2 buffersink 支持动态格式查询](#7.2 buffersink 支持动态格式查询)

[7.3 硬件帧滤镜(hwupload/hwdownload)](#7.3 硬件帧滤镜(hwupload/hwdownload))

[8. 一个完整的视频滤镜示例(可直接跑骨架)](#8. 一个完整的视频滤镜示例(可直接跑骨架))

[9. 常见坑速查表](#9. 常见坑速查表)

[10. 滤镜 + seek 的正确姿势](#10. 滤镜 + seek 的正确姿势)

[11. 脑内流程图](#11. 脑内流程图)

写在最后


如果你写过 FFmpeg 的解码和编码,大概率会觉得 API 还算规整:Packet 进,Frame 出,send/receive 一套走天下。但第一次翻开 libavfilter​ 的头文件,很多人会愣住------这边的概念密度突然翻倍了。

滤镜(filter)不是"调一个函数处理一帧"那么简单,而是一个微型的有向图系统。 ​ 今天按博客风格,把 FFmpeg 6 里视频/音频滤镜的核心函数和套路捋一遍,尽量让你看完能写出一个能跑的 filter 链。


1. 先建立直觉:滤镜图就是一条"管道"

FFmpeg 的滤镜系统可以理解成这样:

复制代码
buffer → scale → overlay → rotate → buffersink
   ↑                                        ↑
AVFrame 从这里灌入                     AVFrame 从这里取出
  • buffer:图的源头 ,你负责把解码好的 AVFrame塞进来

  • buffersink:图的终点 ,你从这里把处理完的 AVFrame取走

  • 中间每一个节点(scale、overlay、rotate)都是一个 AVFilter

类比:就像 Shell 里的管道 cat input.yuv | ffmpeg -vf "scale=1280:720,rotate=45" > output.yuv,只不过你在 C 代码里手工搭这条管道。


2. 滤镜系统的四大件

概念 结构体 作用
滤镜 AVFilter 一个算法的描述(如 scale、overlay)
滤镜上下文 AVFilterContext 一个滤镜的实例,带私有状态
滤镜图 AVFilterGraph 所有滤镜和连接的容器
滤镜链描述 字符串(如 "scale=1280:720" 人类可读的配置,传给 avfilter_graph_parse_ptr

记住一句话:AVFilter是类,AVFilterContext是对象,AVFilterGraph是对象管理器。


3. 搭一条最简单的视频滤镜链

3.1 创建滤镜图

复制代码
AVFilterGraph *graph = avfilter_graph_alloc();
// 可选:降低日志噪音
graph->nb_threads = 0;  // 让 FFmpeg 自动选线程数

3.2 创建输入端(buffer)

复制代码
const AVFilter *buf_src = avfilter_get_by_name("buffer");
AVFilterContext *buf_src_ctx;

char args[512];
snprintf(args, sizeof(args),
         "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
         dec_ctx->width, dec_ctx->height,
         dec_ctx->pix_fmt,
         1, 25,      // time_base,随便填个合理的
         1, 1);       // sar

avfilter_graph_create_filter(&buf_src_ctx, buf_src, "in",
                             args, NULL, graph);

buffer滤镜的作用是:告诉滤镜图,进来的帧长什么样。它不像别的滤镜那样"处理"帧,它只是个入口。

3.3 创建输出端(buffersink)

复制代码
const AVFilter *buf_sink = avfilter_get_by_name("buffersink");
AVFilterContext *buf_sink_ctx;

avfilter_graph_create_filter(&buf_sink_ctx, buf_sink, "out",
                             NULL, NULL, graph);

buffersink是出口,你不给它配参数,它只负责把处理完的帧递给你

3.4 插入中间的滤镜(scale)

复制代码
const AVFilter *scale = avfilter_get_by_name("scale");
AVFilterContext *scale_ctx;

char scale_args[64];
snprintf(scale_args, sizeof(scale_args), "w=1280:h=720");

avfilter_graph_create_filter(&scale_ctx, scale, "scale",
                             scale_args, NULL, graph);

3.5 连接滤镜

复制代码
// buffer → scale
avfilter_link(buf_src_ctx, 0, scale_ctx, 0);
// scale → buffersink
avfilter_link(scale_ctx, 0, buf_sink_ctx, 0);

avfilter_link(src, src_pad, dst, dst_pad)的 pad 索引通常是 0(大多数滤镜只有一个输入一个输出)。

3.6 配置并校验整张图

复制代码
avfilter_graph_config(graph, NULL);

这一步会:

  • 推导各滤镜的实际参数(比如 scale 的输出 pix_fmt)

  • 分配内部缓冲区

  • 检查连接合法性

失败的常见原因:格式不兼容、参数非法、图中有环。


4. 把帧送进去、取出来

4.1 送帧

复制代码
// decoded_frame 是 avcodec_receive_frame 得到的
av_buffersrc_add_frame_flags(buf_src_ctx, decoded_frame,
                              AV_BUFFERSRC_FLAG_KEEP_REF);

flags 常用选项:

Flag 含义
AV_BUFFERSRC_FLAG_KEEP_REF 不窃取 frame 的引用计数,调用方负责 unref
0 默认行为,滤镜图接管 frame,你不该再碰它
AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT 跳过格式检查(慎用)

4.2 取帧

复制代码
AVFrame *filtered = av_frame_alloc();
int ret = av_buffersink_get_frame(buf_sink_ctx, filtered);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
    av_frame_free(&filtered);
    // EAGAIN = 还没输出,继续送帧
    // EOF = 没更多输出了
} else if (ret >= 0) {
    // filtered 就是处理完的帧
    // 用完记得 av_frame_unref(filtered)
}

跟编解码器的 send/receive 很像对吧?滤镜系统也是个状态机:送进去不一定马上出,得持续抽。


5. 用字符串语法搭图(90% 场景推荐)

上面那种手工 create_filter + link的方式太啰嗦了。FFmpeg 提供了字符串解析 的方式,跟命令行 -vf一样的语法:

复制代码
AVFilterGraph *graph = avfilter_graph_alloc();

const char *filters_desc = "scale=1280:720,transpose=1";
// 或更复杂的:
// "crop=iw/2:ih/2,scale=640:480,hflip"

AVFilterInOut *inputs = NULL;
AVFilterInOut *outputs = NULL;

// 创建输入/输出端点
avfilter_inout_alloc(&inputs);
avfilter_inout_alloc(&outputs);

inputs->name        = av_strdup("in");
inputs->filter_ctx = buf_src_ctx;
inputs->pad_idx    = 0;
inputs->next       = NULL;

outputs->name        = av_strdup("out");
outputs->filter_ctx = buf_sink_ctx;
outputs->pad_idx    = 0;
outputs->next       = NULL;

// 解析字符串并自动创建中间滤镜、自动连接
avfilter_graph_parse_ptr(graph, filters_desc, &inputs, &outputs, NULL);

// 配置
avfilter_graph_config(graph, NULL);

// 清理
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);

这几乎是生产代码的标配写法,比手工 link 少一半代码量,还不容易连错。


6. 音频滤镜:跟视频几乎一模一样

音频滤镜的 API 结构和视频完全一致,区别在于:

视频 音频
buffer abuffer
buffersink abuffersink
参数:width/height/pix_fmt 参数:sample_rate/ch_layout/sample_fmt
scale aresample / volume / atempo

6.1 音频 abuffer 的初始化

复制代码
const AVFilter *abuffer = avfilter_get_by_name("abuffer");
AVFilterContext *abuffer_ctx;

char abuf_args[256];
snprintf(abuf_args, sizeof(abuf_args),
         "time_base=1/44100:"
         "sample_rate=44100:"
         "sample_fmt=%s:"
         "channel_layout=%lld",
         av_get_sample_fmt_name(AV_SAMPLE_FMT_S16),
         AV_CH_LAYOUT_STEREO);

avfilter_graph_create_filter(&abuffer_ctx, abuffer, "in",
                             abuf_args, NULL, graph);

6.2 音量滤镜

复制代码
const AVFilter *volume = avfilter_get_by_name("volume");
AVFilterContext *vol_ctx;
avfilter_graph_create_filter(&vol_ctx, volume, "volume",
                             "volume=0.5", NULL, graph);  // 50% 音量

6.3 变速不变调(播放大杀器)

复制代码
const AVFilter *atempo = avfilter_get_by_name("atempo");
AVFilterContext *atempo_ctx;
avfilter_graph_create_filter(&atempo_ctx, atempo, "atempo",
                             "tempo=1.5", NULL, graph);  // 1.5x 速度

7. FFmpeg 6 里滤镜的几个实用变化

7.1 音频 channel layout 统一用 AVChannelLayout

跟前面聊的 avcodec变化一致,滤镜里也不再推荐用 channel_layout的 uint64 mask:

复制代码
// 旧(deprecated)
"channel_layout=3"  // stereo

// 新(FFmpeg 6)
"channel_layout=stereo"
// 或更复杂的
"channel_layout=5.1"

7.2 buffersink 支持动态格式查询

复制代码
enum AVPixelFormat pix_fmt;
int ret = av_opt_get_int(buf_sink_ctx, "pix_fmt", AV_OPT_SEARCH_CHILDREN, &pix_fmt);
// 或音频
int sample_rate;
av_opt_get_int(buf_sink_ctx, "sample_rate", AV_OPT_SEARCH_CHILDREN, &sample_rate);

这在你不知道滤镜链最终输出什么格式时特别有用(比如 scale 可能输出 NV12 或 YUV420P,取决于显卡和 flags)。

7.3 硬件帧滤镜(hwupload/hwdownload)

FFmpeg 6 对硬件帧的支持更完整。如果你在用 DXVA2 解码,想把帧送到滤镜链处理,需要过桥:

复制代码
// DXVA2 解码出的帧 → hwdownload → 软件滤镜 → hwupload → DXVA2 渲染
const char *filters =
    "hwdownload,"
    "format=nv12,"
    "scale=1280:720,"
    "hwupload";

// hwupload 会自动选择当前硬件上下文

⚠️ 硬件帧不能直接进普通滤镜(scale/rotate 等大多是 CPU 实现),必须先 hwdownload落到系统内存,处理完再 hwupload回去。这一来一回有拷贝开销,是性能热点。


8. 一个完整的视频滤镜示例(可直接跑骨架)

复制代码
int setup_video_filter(AVCodecContext *dec_ctx, AVFilterGraph **out_graph,
                       AVFilterContext **out_src, AVFilterContext **out_sink)
{
    AVFilterGraph *graph = avfilter_graph_alloc();

    // buffer 源
    const AVFilter *buf_src = avfilter_get_by_name("buffer");
    AVFilterContext *src_ctx;
    char args[512];
    snprintf(args, sizeof(args),
             "video_size=%dx%d:pix_fmt=%d:time_base=1/25:sar=1/1",
             dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt);
    avfilter_graph_create_filter(&src_ctx, buf_src, "in", args, NULL, graph);

    // buffersink 汇
    const AVFilter *buf_sink = avfilter_get_by_name("buffersink");
    AVFilterContext *sink_ctx;
    avfilter_graph_create_filter(&sink_ctx, buf_sink, "out", NULL, NULL, graph);

    // 滤镜链:裁剪中心 + 缩放 + 水平翻转
    const char *desc = "crop=iw/2:ih:iw/4:0,scale=1280:720,hflip";

    AVFilterInOut *in = avfilter_inout_alloc();
    AVFilterInOut *out = avfilter_inout_alloc();
    in->name = av_strdup("in");  in->filter_ctx = src_ctx;  in->pad_idx = 0;
    out->name = av_strdup("out"); out->filter_ctx = sink_ctx; out->pad_idx = 0;

    avfilter_graph_parse_ptr(graph, desc, &in, &out, NULL);
    avfilter_graph_config(graph, NULL);

    avfilter_inout_free(&in);
    avfilter_inout_free(&out);

    *out_graph = graph;
    *out_src = src_ctx;
    *out_sink = sink_ctx;
    return 0;
}

// 使用
AVFrame *decoded = ...;  // 从解码器得到
av_buffersrc_add_frame_flags(src_ctx, decoded, AV_BUFFERSRC_FLAG_KEEP_REF);

AVFrame *filtered = av_frame_alloc();
while (av_buffersink_get_frame(sink_ctx, filtered) >= 0) {
    // filtered 就是处理完的帧,送去渲染或编码
    av_frame_unref(filtered);
}
av_frame_free(&filtered);

9. 常见坑速查表

现象 原因 解决
avfilter_graph_config返回 -22 格式不兼容或参数非法 检查 buffer 输入的 pix_fmt/sample_fmt 是否跟解码器输出一致
滤镜链没输出(一直 EAGAIN) 帧的 PTS 有问题 确保送入的 frame->pts 合理,或试试 setpts=PTS-STARTPTS
内存暴涨 忘了 av_frame_unref 每次取完 filtered frame 必须 unref
硬件解码 + 滤镜黑屏 没加 hwdownload 硬件帧必须先 download 到内存才能进软件滤镜
scale 输出格式不确定 buffersink 没查询实际格式 av_opt_get_int查输出 pix_fmt
音频变速爆音 用了错误的滤镜 atempo而不是改 sample_rate
seek 后滤镜画面异常 没 flush 滤镜图 seek 时 avfilter_graph_free(&graph);重建,或在 buffer 端送 NULL 帧触发 drain

10. 滤镜 + seek 的正确姿势

seek 后滤镜图必须重建,原因是:

  • 滤镜内部有状态(如 deinterlace、fps、atempo)

  • PTS 序列跳变会让滤镜混乱

  • 最简单可靠的做法是 seek 时整张图 free 再重建

    // seek 前
    avfilter_graph_free(&graph);

    // seek 后
    setup_video_filter(dec_ctx, &graph, &src_ctx, &sink_ctx);

是的,重建滤镜图有开销,但正确性优先于性能。如果你追求极致性能,可以在特定滤镜组合下只 flush 不重建,但这需要逐个滤镜确认其 reset 行为,不推荐新手尝试。


11. 脑内流程图

复制代码
AVFrame (解码器)
   │ av_buffersrc_add_frame
   ▼
[buffer] → [scale] → [rotate] → [buffersink]
                                   │ av_buffersink_get_frame
                                   ▼
                                AVFrame (渲染/编码)

音频版只是把 box 名字换了:

复制代码
AVFrame (解码器)
   │
   ▼
[abuffer] → [volume] → [aresample] → [abuffersink]
                                          │
                                          ▼
                                       PCM 数据

写在最后

滤镜系统是 FFmpeg 里抽象程度最高的模块,但一旦理解了"图 + 状态机"这个模型,就会发现它其实比编解码还规整------因为所有滤镜都遵循同一套输入输出契约。

对音视频播放器来说,滤镜的典型用途是:

  • 视频:缩放(适配渲染窗口)、旋转(手机竖拍)、裁剪、镜像、色彩调整

  • 音频:音量、均衡器、变速不变调、声道重映射

  • 后期:水印(overlay)、字幕渲染(subtitles)、截图(thumbnail)

相关推荐
FlightYe1 天前
音视频修炼之视频基础(一):视频基础理论
android·c++·vscode·音视频·androidx·android runtime
中微极客1 天前
2026上半年AI视频模型技术突破:从生成到“导演级”控制的工程实践
人工智能·深度学习·音视频
EasyGBS1 天前
国标GB28181视频平台EasyGBS集群SIP网关模式:大并发场景下如何解决信令绑定难题?
音视频
开开心心就好1 天前
内存清理工具定时自动清理开机自启动
java·开发语言·elasticsearch·ocr·excel·音视频·big data
看浪的路人1 天前
第10讲:Python 调用 FFmpeg 批量处理——打造你的视频处理工具箱
python·ffmpeg·音视频
开开心心就好1 天前
电脑内存优化工具一键释放内存超简单
java·开发语言·macos·arcgis·ocr·excel·音视频
byte轻骑兵1 天前
【AVDTP】规范精讲[7]: 从RTP封包到空中传输,蓝牙音频数据是怎么跑起来的
人工智能·音视频·avrcp·蓝牙耳机·蓝牙车机
LearnYard1 天前
视频离线缓存与本地播放技术方案对比:从云端存储到本地解码播放的完整链路分析
音视频
美狐美颜SDK开放平台1 天前
直播app开发如何实现主播级美颜效果?美颜sdk开发方案详解
人工智能·音视频·美颜sdk·视频美颜sdk·美颜api