当前位置: 首页 > news >正文

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. 滤镜系统的四大件

概念

结构体

作用

滤镜

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)

http://www.cnnetsun.cn/news/3722519.html

相关文章:

  • LENA-R8与STM32F765ZI的全球连接与高精度定位方案
  • Arduino HC-05蓝牙模块完整配置与通信避坑指南
  • 布谷鸟搜索算法:原理、实现与参数调优实战指南
  • C/C++编程中size_t的正确使用:避免内存越界与提升代码健壮性
  • 十一假期玩转Arduino:从氛围灯到物联网的4个创意项目实践
  • 猜数字游戏四语言实现对比:Python/JS/Java/C++核心代码与避坑指南
  • 3分钟上手d2s-editor:免费开源的暗黑破坏神2存档编辑器终极指南
  • VS Code + PlatformIO:ESP32 S3嵌入式开发环境搭建与实战指南
  • Simulink中模糊PI控制器设计:从原理到仿真的自适应控制实践
  • 微服务概述——从零开始理解企业级架构演进
  • AI办公效率提升全攻略:工具链与实战案例
  • STM32与ADC实战:电子设计竞赛核心能力锻造与系统调试心法
  • 兰溪窑神赵王传说的文化解读与非遗传承
  • 嵌入式开发实战:从代码规范到架构设计,构建稳定可靠的嵌入式系统
  • Codex平替Agent推荐:2025年代码生成Agent选型指南
  • TC4056A线性锂电池充电管理芯片:原理、应用与散热设计全解析
  • SpringBoot+Vue3实现制造业质量管理系统全栈开发
  • AXI总线协议信号详解:从通道分离到握手机制与实战调试
  • 密码安全进阶:盐与胡椒在加密存储中的关键作用
  • 基于Arduino与蓝牙BLE的智能氛围灯DIY:从电路设计到3D打印全解析
  • 深度优先搜索与广度优先搜索:原理、实现与应用场景全解析
  • 亚马逊卖家如何用AI技能优化75字符标题,提升转化率
  • C++实战:Windows窗口管理与进程交互技术解析
  • 基于Micro:bit与ESP32的3D打印教育机器人:从设计到编程全解析
  • 基于Romeo控制器的互动搞笑垃圾桶:从硬件选型到状态机编程
  • 2026 年最佳笔记本电脑坞站推荐:多类型适配,满足不同需求!
  • Ventoy 1.1.17 发布:优化启动过程、修复多系统问题,官方为持续发展推订阅服务
  • AR远程协作技术解析与行业应用实践
  • STM32与A5000安全芯片构建物联网安全通信方案
  • Llama 5与GPT-5技术路线深度对比:开源权重与闭源生态的抉择