FFmpeg6的滤镜函数解析

📅 2026/7/29 5:01:48
FFmpeg6的滤镜函数解析
觉得有用就请您帮忙点赞转发收藏吧您的鼓励是我创作的动力多谢看官。由于能力水平有限文中的错误或不严谨的地方在所难免还请批评指正。目录1. 先建立直觉滤镜图就是一条管道2. 滤镜系统的四大件3. 搭一条最简单的视频滤镜链3.1 创建滤镜图3.2 创建输入端buffer3.3 创建输出端buffersink3.4 插入中间的滤镜scale3.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 统一用 AVChannelLayout7.2 buffersink 支持动态格式查询7.3 硬件帧滤镜hwupload/hwdownload8. 一个完整的视频滤镜示例可直接跑骨架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 scale1280:720,rotate45 output.yuv只不过你在 C 代码里手工搭这条管道。2. 滤镜系统的四大件概念结构体作用滤镜​AVFilter一个算法的描述如 scale、overlay滤镜上下文​AVFilterContext一个滤镜的实例带私有状态滤镜图​AVFilterGraph所有滤镜和连接的容器滤镜链描述​字符串如scale1280:720人类可读的配置传给avfilter_graph_parse_ptr记住一句话AVFilter是类AVFilterContext是对象AVFilterGraph是对象管理器。3. 搭一条最简单的视频滤镜链3.1 创建滤镜图AVFilterGraph *graph avfilter_graph_alloc(); // 可选降低日志噪音 graph-nb_threads 0; // 让 FFmpeg 自动选线程数3.2 创建输入端bufferconst 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 创建输出端buffersinkconst 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 插入中间的滤镜scaleconst AVFilter *scale avfilter_get_by_name(scale); AVFilterContext *scale_ctx; char scale_args[64]; snprintf(scale_args, sizeof(scale_args), w1280:h720); 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 的引用计数调用方负责 unref0默认行为滤镜图接管 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 scale1280:720,transpose1; // 或更复杂的 // cropiw/2:ih/2,scale640: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 结构和视频完全一致区别在于视频音频bufferabufferbuffersinkabuffersink参数width/height/pix_fmt参数sample_rate/ch_layout/sample_fmtscalearesample / volume / atempo6.1 音频 abuffer 的初始化const AVFilter *abuffer avfilter_get_by_name(abuffer); AVFilterContext *abuffer_ctx; char abuf_args[256]; snprintf(abuf_args, sizeof(abuf_args), time_base1/44100: sample_rate44100: 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, volume0.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, tempo1.5, NULL, graph); // 1.5x 速度7. FFmpeg 6 里滤镜的几个实用变化7.1 音频 channel layout 统一用AVChannelLayout跟前面聊的avcodec变化一致滤镜里也不再推荐用channel_layout的 uint64 mask// 旧deprecated channel_layout3 // stereo // 新FFmpeg 6 channel_layoutstereo // 或更复杂的 channel_layout5.17.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/hwdownloadFFmpeg 6 对硬件帧的支持更完整。如果你在用 DXVA2 解码想把帧送到滤镜链处理需要过桥// DXVA2 解码出的帧 → hwdownload → 软件滤镜 → hwupload → DXVA2 渲染 const char *filters hwdownload, formatnv12, scale1280: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_base1/25:sar1/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 cropiw/2:ih:iw/4:0,scale1280: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 合理或试试setptsPTS-STARTPTS内存暴涨忘了av_frame_unref每次取完 filtered frame 必须 unref硬件解码 滤镜黑屏没加hwdownload硬件帧必须先 download 到内存才能进软件滤镜scale 输出格式不确定buffersink 没查询实际格式用av_opt_get_int查输出 pix_fmt音频变速爆音用了错误的滤镜用atempo而不是改 sample_rateseek 后滤镜画面异常没 flush 滤镜图seek 时avfilter_graph_free(graph);重建或在 buffer 端送 NULL 帧触发 drain10. 滤镜 seek 的正确姿势seek 后滤镜图必须重建原因是滤镜内部有状态如 deinterlace、fps、atempoPTS 序列跳变会让滤镜混乱最简单可靠的做法是 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