FFmpeg6的滤镜函数解析
觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。
由于能力水平有限,文中的错误或不严谨的地方在所难免,还请批评指正。
目录
1. 先建立直觉:滤镜图就是一条"管道"
2. 滤镜系统的四大件
3. 搭一条最简单的视频滤镜链
3.1 创建滤镜图
3.2 创建输入端(buffer)
3.3 创建输出端(buffersink)
3.4 插入中间的滤镜(scale)
3.5 连接滤镜
3.6 配置并校验整张图
4. 把帧送进去、取出来
4.1 送帧
4.2 取帧
5. 用字符串语法搭图(90% 场景推荐)
6. 音频滤镜:跟视频几乎一模一样
6.1 音频 abuffer 的初始化
6.2 音量滤镜
6.3 变速不变调(播放大杀器)
7. FFmpeg 6 里滤镜的几个实用变化
7.1 音频 channel layout 统一用 AVChannelLayout
7.2 buffersink 支持动态格式查询
7.3 硬件帧滤镜(hwupload/hwdownload)
8. 一个完整的视频滤镜示例(可直接跑骨架)
9. 常见坑速查表
10. 滤镜 + seek 的正确姿势
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. 滤镜系统的四大件
概念 | 结构体 | 作用 |
|---|---|---|
滤镜 |
| 一个算法的描述(如 scale、overlay) |
滤镜上下文 |
| 一个滤镜的实例,带私有状态 |
滤镜图 |
| 所有滤镜和连接的容器 |
滤镜链描述 | 字符串(如 | 人类可读的配置,传给 |
记住一句话: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 | 含义 |
|---|---|
| 不窃取 frame 的引用计数,调用方负责 unref |
| 默认行为,滤镜图接管 frame,你不该再碰它 |
| 跳过格式检查(慎用) |
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 结构和视频完全一致,区别在于:
视频 | 音频 |
|---|---|
|
|
|
|
参数: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. 常见坑速查表
现象 | 原因 | 解决 |
|---|---|---|
| 格式不兼容或参数非法 | 检查 buffer 输入的 pix_fmt/sample_fmt 是否跟解码器输出一致 |
滤镜链没输出(一直 EAGAIN) | 帧的 PTS 有问题 | 确保送入的 frame->pts 合理,或试试 |
内存暴涨 | 忘了 | 每次取完 filtered frame 必须 unref |
硬件解码 + 滤镜黑屏 | 没加 | 硬件帧必须先 download 到内存才能进软件滤镜 |
scale 输出格式不确定 | buffersink 没查询实际格式 | 用 |
音频变速爆音 | 用了错误的滤镜 | 用 |
seek 后滤镜画面异常 | 没 flush 滤镜图 | seek 时 |
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)
