8.3 自定义扩展:写一个自己的滤镜


8.3 自定义扩展:写一个自己的滤镜

本节摘要:FFmpeg 的滤镜系统是可扩展的——通过注册 AVFilter 结构就能把自定义处理插进滤镜图。本节讲透滤镜注册的机制、回调函数的分工,以及「扩展 FFmpeg」与「绕开 FFmpeg」两种路线各自的成本与适用场景。

学习目标

阅读完本节,你应当能够:

  1. 说出 FFmpeg 滤镜扩展的注册机制(AVFilter 结构 + 回调函数)
  2. 理解滤镜回调的分工:init、config、filter_frame、uninit 各管什么
  3. 判断「写滤镜」与「写独立程序」哪个更划算

一、一个反例开场

有团队要给视频加「企业专属的防伪动态水印」,翻遍 FFmpeg 滤镜列表没找到,于是决定「自己写滤镜」。结果写了一个月,踩遍了内存与帧格式的坑。其实他们要的效果,用 overlay + drawtext + 时间参数就能拼出来。

这个反例想说明:扩展 FFmpeg 是最后的手段,不是首选方案。 先确认标准滤镜真做不到(很多人连 drawtext、overlay 组合都没试过),再谈写代码。但确实存在 FFmpeg 做不了的需求——比如把解码帧直接送进某个私有算法再回来——这时扩展机制就值得掌握了。

二、核心原理:滤镜注册机制

FFmpeg 的滤镜系统设计成「注册表」模式:每个滤镜是一个 AVFilter 结构体,登记好名字、输入输出约束、回调函数,FFmpeg 在构建滤镜图时按名字查找并调用。

AVFilter 结构的关键字段

static const AVFilterPad myfilter_inputs[] = { { .name = "default", .type = AVMEDIA_TYPE_VIDEO, // 视频滤镜 }, }; static const AVFilterPad myfilter_outputs[] = { { .name = "default", .type = AVMEDIA_TYPE_VIDEO, }, }; AVFilter ff_vf_myfilter = { .name = "myfilter", // 滤镜名,命令里 -vf "myfilter" 用它 .description = "My custom filter", .init = myfilter_init, // 参数解析后初始化 .config_props = myfilter_config, // 输入输出格式协商 .filter_frame = myfilter_frame, // 核心:处理一帧 .uninit = myfilter_uninit, // 释放资源 };

回调函数的分工

回调 触发时机 干什么
init 滤镜创建时 解析参数,分配持久资源
config_props 输入输出协商时 确定帧格式、分辨率
filter_frame 每来一帧 核心处理逻辑,产出新帧
uninit 滤镜销毁时 释放 init 分配的资源

filter_frame 是灵魂:它接收一帧(AVFrame),处理后生成一帧,通过 ff_filter_frame 推给下游。你的业务逻辑——无论是亮度调整还是调用私有算法——都写在这里。

注册进 FFmpeg

滤镜实现完后,把它挂进注册表:

  • allfilters.c 里加一行 REGISTER_FILTER(MYFILTER, myfilter, vf);
  • 重新编译 FFmpeg

编译后 ffmpeg -filters | grep myfilter 能看到它,于是它和内置滤镜一样可以用在 -vf 里了。

三、工程实践要点

扩展 vs 绕开:两条路线

路线 适合场景 成本
写自定义滤镜 处理逻辑要长驻在管线里、要复用 FFmpeg 的帧搬运与调度 要维护一份自编译 FFmpeg,滤镜生命周期管理复杂
绕开 FFmpeg 只在某环节插自己的算法 只需在 SDK 程序的 receive_frame 后插代码(见 8.2)

关键判断:你的处理是「滤镜形态」还是「程序逻辑」? 如果是「对每帧做同样变换、希望它能和 scale/overlay 一样组合」——写滤镜合理;如果只是「解码后调一下我的算法再继续」——在 8.2 那个程序骨架里插代码,成本低一个数量级。

写滤镜时的三大坑

  1. 帧格式假设:自定义滤镜要处理各种像素格式(yuv420p、nv12…),写死格式会导致其他格式下黑屏或花屏——用 config_props 协商,别硬编码
  2. 引用计数:输入帧是共享数据,别直接改,需要时先 av_frame_clone 或新建;忘了 unref 会内存泄漏
  3. 时间戳传递:输出帧的 pts 要正确设置(通常沿用输入的 pts),否则下游时间轴错乱

扩展机制一图流

扩展机制一图流

图说明:生命周期与决策

滤镜的生命周期四个回调一路推进,filter_frame 是核心。但先别急着写滤镜——上图决策树给出两条路线:滤镜形态还是程序逻辑。多数「私有算法接入」需求,插代码比写滤镜省事得多。

⚠️ 常见坑:写完滤镜忘了在 allfilters.c 注册,-filters 里永远看不到它;像素格式假设错误导致部分输入花屏;帧引用计数没管理好导致内存泄漏或悬空。自编译版本一旦固定,升级 FFmpeg 等于要重新适配自己的滤镜代码。

💡 关键直觉:扩展 FFmpeg 前先问「这是滤镜问题还是程序问题」。滤镜问题(和内置滤镜组合、长驻管线)才值得写滤镜;程序问题(接私有算法)就在 SDK 程序里插代码。用最小的改动解决问题,是工程的第一原则。

要点回顾

  • 注册机制:AVFilter 结构登记名字、垫、回调,注册进 allfilters.c 后即成为内置滤镜
  • 四回调分工:init 分配、config_props 协商格式、filter_frame 核心处理、uninit 释放
  • filter_frame 是灵魂:每帧处理与产出都在这,业务逻辑写这里
  • 两条路线:滤镜形态写滤镜、程序逻辑插代码,后者成本低一个量级
  • 三大坑:帧格式、引用计数、时间戳,一个都不能踩

下一章把这些知识全部用起来——第 9 章实战场景,从短视频到监控安防。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U