实现一个自定义 FFmpeg Filter
此前在做 ffmpeg+某个第三库作为 filter 的集成,第三库是做AE特效相关的,与 ffmpeg 结合能让视频渲染效果大大提升。整体流程将第三方库作为 ffmpeg 的一个filter 形式进行结合,其中就涉及到 ffmpeg 的 filter 开发,本文即 对ffmpeg 的滤镜开发流程作一个总结。本文以实现一个视频垂直翻转的 filter 为例,ffmpeg 源码基于FFmpeg6.1
实现自定义 Filter 流程
编写 filter.c 文件
一般视频滤镜以 vf_ 为前缀,视频滤镜以 af_ 为前缀,放在libavfilter目录下,参考其他 filter 代码逻辑,模块化配置相关参数,本文例以 vf_flip.c 实现视频的上下翻转
在
libavfilter/allfilters.c
注册例如:extern const AVFilter ff_vf_flip;
ff_vf_flip
就是在vf_flip.c
的 filter 注册名称修改
libavfilter/Makefile
添加编译配置:例如:OBJS-$(CONFIG_FLIP_FILTER) += vf_flip.o
编译打包
编写 filter.c 文件
AVFilter主体
1 | typedef struct AVFilter { |
具体里面的属性作用可以参考:[ffmpeg] 定制滤波器,可以根据需求实现里面的相关函数,接下来以一个最简单的 Filter 和一个较复杂一点的 Filter 举例。
最简单的 AVFilter
1 | typedef struct { |
命令行运行:
1 | ffmpeg -i test.mp4 -vf "noop" noop.mp4 |
正常输出文件(对原片没有做任何更改),这个 filter 的作用是将输入的视频帧不做任何处理地传递给下一个过滤器,在处理每帧的时候会打印处理的 PTS,麻雀虽小五脏俱全,它包含了一个 AVFilter 基础的结构:
NoopContext
这是一个简单的结构体,包含一个指向 AVClass 的指针。在这个例子中,实际上没有使用到 NoopContext 结构体的任何成员,因为这个过滤器没有需要存储的私有数据。
filter_frame
这个函数的作用是处理输入的视频帧。在这个例子中,它只是打印帧的 PTS(Presentation Time Stamp,显示时间戳)并将帧传递给下一个过滤器,不对帧做任何修改。
noop_inputs
和noop_outputs
这两个数组定义了过滤器的输入和输出 Pad。在这个例子中,输入 Pad 类型为 AVMEDIA_TYPE_VIDEO,并关联了
filter_frame
函数。输出 Pad 也是 AVMEDIA_TYPE_VIDEO 类型,但没有关联任何函数,因为输出直接由filter_frame
函数处理。ff_vf_noop
这是一个 AVFilter 结构体实例,包含了过滤器的名称、描述、私有数据大小以及输入和输出 Pad。在这个例子中,过滤器的名称为 “noop”,描述为 “Pass the input video unchanged.”,这也就是在执行:
ffmpeg -filters
看到的 Filter描述内容。
接下来看一个稍微复杂的一个 AVFilter,实现一个视频的上下翻转
复杂一点的 AVFilter
1 | typedef struct FlipContext { |
命令行运行:
1 | ffmpeg -i test.mp4 -filter_complex "[0:v]flip=duration=5[out];" -map "[out]" flip.mp4 |
得到渲染好的视频,前5s是上下翻转的,后面的内容正常。
相比于最简单的 AVFilter 多了几个实现:
AVOption flip_options
用于设置翻转持续时间的选项,外部命令配置可选输入
duration=5
,会自动对数据合法性进行校验。参数类型为AV_OPT_TYPE_INT
,默认值为 0,取值范围为 0 到INT_MAX
。.flags
设置为AV_OPT_FLAG_FILTERING_PARAM
,表示这是一个过滤参数。.priv_class
配置的
flip_class
实际是通过AVFILTER_DEFINE_CLASS(flip);
宏实现的一个声明:见:internal.h#AVFILTER_DEFINE_CLASS_EXT**init
&uninit
滤镜在初始化或者释放资源的时候将会调用
activate
这个函数首先获取输入帧,然后调用
flip_frame
函数进行翻转操作,并将处理后的帧放入输出链接。如果没有输入帧,它会请求一个新的输入帧。最后,它会确认输入链接的状态,并根据需要设置输出链接的状态。
这个例子相比最简单的 filter 使用了 activate
函数 用于帧渲染,而不是使用 filter_frame
去渲染,这两个方法有什么区别于联系呢?查看:filter_frame和activate方法
也能通过 filter_frame
实现,对代码部分逻辑更新更改:
1 | static const AVFilterPad flip_inputs[] = { |
命令行运行,得到的输出结果是一样的。
filter_frame()和activate()函数
对于这点查了相关资料,看看源码相关的实现
参考:https://www.ffmpeg.org/doxygen/5.0/filter__design_8txt.html
The purpose of these rules is to ensure that frames flow in the filter graph without getting stuck and accumulating somewhere. Simple filters that output one frame for each input frame should not have to worry about it. There are two design for filters:one using the filter_frame() and request_frame() callbacks and the other using the activate() callback. The design using filter_frame() and request_frame() is legacy, but it is suitable for filters that have a single input and process one frame at a time. New filters with several inputs, that treat several frames at a time or that require a special treatment at EOF should probably use the design using activate(). activate ——– This method is called when something must be done in a filter
大意,实现滤镜有两种实现方式:
filter_frame()
可以被认为是历史遗留产物。在早期的 AVFilter 设计中,
filter_frame()
和request_frame()
是主要用于处理输入帧和请求输出帧的回调函数。这种设计适用于简单的过滤器,例如单输入且每次处理一个帧的过滤器。activate()
随着 ffmpeg 和 AVFilter 的发展,处理需求变得越来越复杂,例如需要处理多个输入、一次处理多个帧或在文件结束(EOF)时进行特殊处理等。为了满足这些需求,引入了
activate()
函数,它提供了更灵活和强大的处理能力。因此,虽然filter_frame()
在某些简单场景下仍然可以使用,但对于新的或复杂的过滤器,建议使用activate()
函数。
如果两个方法都实现了,那他们谁会先执行呢?
对应的源码处理逻辑: avfilter.c
1 | int ff_filter_activate(AVFilterContext *filter) |
如果配置了activate() 函数则执行,否则执行 ff_filter_activate_default()->ff_filter_frame_to_filter()->ff_filter_frame_framed() 最终执行到配置的 filter_frame() 方法。
1 | static int ff_filter_frame_framed(AVFilterLink *link, AVFrame *frame) |
总结
本文介绍了 FFmpeg 滤镜开发的整体流程,如何编写 filter.c 文件,并以一个最简单的 AVFilter 和一个较为复杂的 AVFilter 为例,解析了滤镜开发的具体步骤和代码实现,并介绍了 filter_frame() 和 activate() 函数的区别与联系。
在滤镜开发过程中,需要注意的是,filter_frame() 和 activate() 函数的使用取决于滤镜的复杂性。对于简单的滤镜,可以使用 filter_frame() 函数;而对于需要处理多个输入、一次处理多个帧或在文件结束(EOF)时进行特殊处理的复杂滤镜,建议使用 activate() 函数。
文中的源码可以查看:add most simplest AVFilter and a simple video flip filter.
参考:
https://www.cnblogs.com/TaigaCon/p/10171464.html