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)

相关推荐
hflmwl86 小时前
Wise Data Recovery找回误删的文档照片音乐和视频
音视频·datarecovery·找回误删的文档照片音乐和视频·找回误删的文档照片音乐视频
技灵AI6 小时前
AI 视频转场提示词怎么写?让前后画面衔接更自然
人工智能·prompt·aigc·音视频
youandyouyou8 小时前
跨区培训的屏幕共享怎么做才稳?同屏同步与弱网优化实践
音视频·网络通信
开开心心就好8 小时前
课堂随机点名工具,绿色版免安装双击即用
java·服务器·前端·ffmpeg·音视频·perl·symfony
美狐美颜SDK开放平台8 小时前
直播APP集成美颜SDK,如何梳理第三方组件的数据权限?
开发语言·人工智能·深度学习·音视频·第三方美颜sdk
小鹿软件办公9 小时前
不依赖 FFmpeg 也能录屏?ShareX 22 新增录屏等十余款新工具
ffmpeg·sharex·免费录屏工具
l143723326710 小时前
批量视频硬字幕擦除多少钱?影响报价的关键因素全解析
音视频
重生六道10 小时前
B站视频时长统计:一段JS脚本搞定分P/合集总时长(附倍速计算)
开发语言·javascript·音视频
音视频牛哥10 小时前
不换摄像机,也能接入 WebRTC?SmartMediaKit RTSP/RTMP 转 WHIP 技术实践
音视频·webrtc·smartmediakit·rtsp转whip·rtmp转whip·whip推流·rtsp转webrtc播放
lll235269177911 小时前
Android APP短视频音频提取,主流工具怎么选
ffmpeg·音视频