ffmpeg filters 间数据流动
参考:
https://www.jianshu.com/p/8f1556341975
https://www.cnblogs.com/TaigaCon/p/10171464.html
1. 滤镜图结构
滤镜图结构大致如下:

- 整个结构看起来是一个流水线结构,数据帧从源滤镜输入,从输出滤镜输出,中间经过一系列处理
- 一般每个
link会有一个fifo queue,上级输出的帧数据会先存入fifo,下级的输入从fifo中取出 filter可能会有多个输入、输出pad,pad会有序号标识,都是从0开始递增。pad序号对于开发者来说,主要用于处理连接规则- 另外一个值得关注的点是,整个流水线是单线程的,虽然开发者可以设置滤镜图的线程数,但是实际上这个值只会在某些滤镜内部使用,即单个滤镜内部可以开启多线程
2. AVFilter 对象结构
如果我们要自定义一个 ffmpeg 过滤器,那么首先要从定义 AVFilter 开始。一个示例如下:
AVFilter ff_vf_overlay = {
.name = "overlay",
.description = NULL_IF_CONFIG_SMALL("Overlay a video source on top of the input."),
.preinit = overlay_framesync_preinit,
.init = init,
.uninit = uninit,
.priv_size = sizeof(OverlayContext),
.priv_class = &overlay_class,
.query_formats = query_formats,
.activate = activate,
.process_command = process_command,
.inputs = avfilter_vf_overlay_inputs,
.outputs = avfilter_vf_overlay_outputs,
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL |
AVFILTER_FLAG_SLICE_THREADS,
};
下面看一下 AVFilter 对象中几个重要的成员。
2.1 name
声明如下:
const char *name;
本 filter 的名字,是其独一无二的可读标识.
2.2 priv_size,priv_class
声明如下:
const AVClass *priv_class;
int priv_size;
一般来说,AVFilter 对象是 ffmpeg 可见的对象,但是每个 filter 需要自己的私有数据,而这两个成员就是为了存储每个 filter 的私有数据的。
私有数据一般也是一个结构体,这个结构体存什么都可以,只有一个限制,即第一个成员必须是 AVClass* 类型:
struct MyFilter {
const AVClass *class;
...
};
这么做的目的有两个:
- 方便
ffmpeg调用filter的回调函数时,将AVClass*强制转为MyFilter* - 外部传递参数给
MyFilter(具体可以看AVClass与AVOption的关系)
所以:
priv_class就是指向了MyFilter对象priv_size即MyFilter对象的大小
2.3 init, uninit
声明如下:
int (*init)(AVFilterContext *ctx);
void (*uninit)(AVFilterContext *ctx);
这是两个回调函数,过滤器一般来说需要注册这两个回调函数:
init回调在外部调用avfilter_graph_create_filter()时会触发uninit回调在外部调用avfilter_graph_free()时会触发
2.4 query_formats
声明如下:
int (*query_formats)(AVFilterContext *);
这也是一个回调函数,过滤器一般来说也需要注册这个回调函数。
一个示例如下:
static int xxx_query_formats(AVFilterContext *avctx)
{
static const enum AVPixelFormat main_pixel_formats[] = {
AV_PIX_FMT_CUDA, AV_PIX_FMT_NONE,
};
static const enum AVPixelFormat overlay_pixel_formats[] = {
AV_PIX_FMT_RGBA, AV_PIX_FMT_NONE,
};
int ret = 0;
AVFilterFormats* formats = ff_make_format_list(main_pixel_formats);
if ((ret = ff_formats_ref(formats, &avctx->inputs[0]->outcfg.formats)) < 0 ||
(ret = ff_formats_ref(formats, &avctx->outputs[0]->incfg.formats)) < 0)
return ret;
return ff_formats_ref(ff_make_format_list(overlay_pixel_formats),
&avctx->inputs[1]->outcfg.formats);
}
注意此回调的时机,在外部调用 avfilter_graph_config() 的时候会被调用。
avfilter_graph_config() 是对所有 filters 发起连接和协商的入口,而协商的第一步就是告诉别人,本 filter output pads 和 input pads 上支持的像素格式:

一个 filter 可以有多个输入和输出 pad,且每个 pad 支持的像素格式也是独立的。
2.5 inputs, outputs
声明如下:
const AVFilterPad *inputs;
const AVFilterPad *outputs;
这是两个 AVFilterPad 对象,filter 被设计为可以拥有输入和输出 pad,而这两个 AVFilterPad 对象即代表了 filter 的输入输出 pad。
下面继续看一下 AVFilterPad 的一些重要内部结构。
2.5.1 AVFilterPad::name
与 AVFilter::name 的作用一样,可以为每个 pad 定义一个可读的名称。
2.5.2 AVFilterPad::type
pad 类型,主要是 ffmpeg 会做一些内部数据的检查,可为 AVMEDIA_TYPE_VIDEO 或 AVMEDIA_TYPE_AUDIO。
2.5.3 AVFilterPad::config_props
声明如下:
int (*config_props)(AVFilterLink *link);
这是属于 pad 的一个重要回调函数:
- 前面说过
avfilter_graph_config()发起filters之间的连接和协商的第一步是调用AVFilter::query_formats()回调,此回调会设置本filter所有pad所支持的像素格式 - 在某个
pad协商完毕后,会调用相应pad的config_props()回调,用于告诉filter当前pad最终的协商格式
2.5.4 AVFilterPad::filter_frame
声明如下:
int (*filter_frame)(AVFilterLink *link, AVFrame *frame);
此回调主要是给 filter input_pad 使用的,当上游有数据包要流向这个 pad 时,此回调会被调用,frame 参数就是传递给此 filter input pad 的数据包。
2.5.5 AVFilterPad::request_frame
声明如下:
int (*request_frame)(AVFilterLink *link);
此回调主要是给 filter output pad 使用的,当下游 filter 需要数据时,会向上请求数据,ffmpeg 内部会调用此方法通知当前 filter,下游需要数据。
2.6 activate
声明如下:
int (*activate)(AVFilterContext *ctx);
- 前面介绍了
filter input pad和output pad中,涉及到数据流传递的两个回调函数filter_frame()和request_fram()。而这里activate()函数是第三个,涉及到数据流的回调函数 activate()对比另外两个回调,其内部数据权限更大,例如可以决定是否向上请求数据、是否向下传递数据、是否向下传递错误信号等- 更大的权限也意味着很多原本
ffmpeg内部实现的东西,需要放到这个回调内部由filter自行实现
2.7 flags
介绍一个重要的 flags:AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC。当 filter 声明了此标识后,ffmpeg 内部会根据数据帧的时间戳,支持时间线编辑的能力。
3. 数据流动
滤镜图创建好后,后面就是数据帧如何在各个滤镜中流动和处理了,这一块是 libavfilter 的核心部分。
注意下面介绍的数据流动是一些典型情况下的示例,且数据的处理可以看作是: 以 filter 为单位的,流水线式的处理

3.1 从前往后,传递数据帧
首先要介绍一下 AVFilterContext::ready 成员变量,声明如下:
unsigned ready;
- 此变量,属于
AVFilterContext,而不属于AVFilterLink - 此变量在 ffmpeg 内部的取值只有
4 种,0, 100, 200, 300。本节主要关注取值300的情况 - 上面说的以 一个
filter为单位,流水线式的处理。这里的ready值就是决定当前处理哪个filter的关键
当外部调用 av_buffersrc_add_frame_flags() 向 src filter 输入一帧时,数据便开始流动了:

此时 split 滤镜的 ready 值被标记为 300,表明下一个要处理的 filter 就是 split 滤镜了。
接着 split 滤镜从输入的 fifo 获取一帧,然后复制一份,分别塞入两个输出 pad 对应的 fifo 里面:

此时 crop 滤镜和 overlay 滤镜的 ready 值都被标记为了 300:
- 如果
overlay滤镜先被处理,其发现还有一个pad没有输入,则会将数据缓存起来,等待crop滤镜产生有效的一帧 - 如果
crop滤镜先被处理,其会输出有效的一帧到其输出pad对应的fifo中
当 overlay 滤镜发现两个输入的 pad 都有数据时,会将他们都取出来然后进行混合输出:

这样就实现了数据帧一级一级的向下流动。
3.2 从后往前,传递数据帧请求
首先需要介绍一下 AVFilterLink::frame_wanted_out 成员变量。声明如下:
int frame_wanted_out;
- 此变量,属于
AVFilterLink,而不属于AVFilterContext - 此变量置为 1 时,一般表示下级
filter想要上级filter输出有效的帧数据
例如,当开发者调用 av_buffersink_get_frame() 函数时,sink filter 会置位此变量为 1。
初始状态如下:

接着中间的 filter ready 值会置为 100,下一轮就会处理中间的 filter:

中间的 filter 也没有数据产生,其可以将数据请求继续向上传递:

这样就实现了数据帧请求一级一级的向上传递,直到某个 filter 成功响应请求,或没有任何 filter 响应请求,结束一轮处理。
3.3 从前往后,传递错误信号
首先需要介绍一下 AVFilterLink::status_in 和 AVFilterLink::status_out 成员变量。声明如下:
int status_in;
int status_out;
- 这两个变量,属于
AVFilterLink,而不属于AVFilterContext - 这两个变量置为 1 时,一般表示有错误发生,例如
EOF信号也是通过这两个变量来传递的
例如,当开发者调用 av_buffersrc_add_frame_flags() 并传递了一个空的 AVFrame 时,这时会触发 EOF 逻辑,状态如下:

中间的 filter ready 值会同时置为 200,下一轮就会处理中间的 filter。
中间的 filter 处理 EOF 一般分为两步,第一步如下,表示获取到了上游传递的 EOF 信号:

接着第二步如下,用于将 EOF 信号继续传递给下一级:

这样就实现了 EOF 信号一级一级的向下传递,每个 filter 都会获取到 EOF 信号并作出相应的处理。

浙公网安备 33010602011771号