ffmpeg filters 间数据流动

参考:
https://www.jianshu.com/p/8f1556341975
https://www.cnblogs.com/TaigaCon/p/10171464.html

1. 滤镜图结构

滤镜图结构大致如下:
image

  • 整个结构看起来是一个流水线结构,数据帧从源滤镜输入,从输出滤镜输出,中间经过一系列处理
  • 一般每个 link 会有一个 fifo queue,上级输出的帧数据会先存入 fifo,下级的输入从 fifo 中取出
  • filter 可能会有多个输入、输出 padpad 会有序号标识,都是从 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 (具体可以看 AVClassAVOption 的关系)

所以:

  • priv_class 就是指向了 MyFilter 对象
  • priv_sizeMyFilter 对象的大小

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 padsinput pads 上支持的像素格式:
image

一个 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_VIDEOAVMEDIA_TYPE_AUDIO

2.5.3 AVFilterPad::config_props

声明如下:

  int (*config_props)(AVFilterLink *link);

这是属于 pad 的一个重要回调函数:

  • 前面说过 avfilter_graph_config() 发起 filters 之间的连接和协商的第一步是调用 AVFilter::query_formats() 回调,此回调会设置本 filter 所有 pad 所支持的像素格式
  • 在某个 pad 协商完毕后,会调用相应 padconfig_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 padoutput pad 中,涉及到数据流传递的两个回调函数 filter_frame()request_fram()。而这里 activate() 函数是第三个,涉及到数据流的回调函数
  • activate() 对比另外两个回调,其内部数据权限更大,例如可以决定是否向上请求数据、是否向下传递数据、是否向下传递错误信号等
  • 更大的权限也意味着很多原本 ffmpeg 内部实现的东西,需要放到这个回调内部由 filter 自行实现

2.7 flags

介绍一个重要的 flags:AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC。当 filter 声明了此标识后,ffmpeg 内部会根据数据帧的时间戳,支持时间线编辑的能力。

3. 数据流动

滤镜图创建好后,后面就是数据帧如何在各个滤镜中流动和处理了,这一块是 libavfilter 的核心部分。
注意下面介绍的数据流动是一些典型情况下的示例,且数据的处理可以看作是: 以 filter 为单位的,流水线式的处理
image

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 输入一帧时,数据便开始流动了:
image

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

此时 crop 滤镜和 overlay 滤镜的 ready 值都被标记为了 300:

  • 如果 overlay 滤镜先被处理,其发现还有一个 pad 没有输入,则会将数据缓存起来,等待 crop 滤镜产生有效的一帧
  • 如果 crop 滤镜先被处理,其会输出有效的一帧到其输出 pad 对应的 fifo

overlay 滤镜发现两个输入的 pad 都有数据时,会将他们都取出来然后进行混合输出:
image

这样就实现了数据帧一级一级的向下流动。

3.2 从后往前,传递数据帧请求

首先需要介绍一下 AVFilterLink::frame_wanted_out 成员变量。声明如下:

  int frame_wanted_out;
  • 此变量,属于 AVFilterLink,而不属于 AVFilterContext
  • 此变量置为 1 时,一般表示下级 filter 想要上级 filter 输出有效的帧数据

例如,当开发者调用 av_buffersink_get_frame() 函数时,sink filter 会置位此变量为 1。
初始状态如下:
image

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

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

这样就实现了数据帧请求一级一级的向上传递,直到某个 filter 成功响应请求,或没有任何 filter 响应请求,结束一轮处理。

3.3 从前往后,传递错误信号

首先需要介绍一下 AVFilterLink::status_inAVFilterLink::status_out 成员变量。声明如下:

  int status_in;

  int status_out;
  • 这两个变量,属于 AVFilterLink,而不属于 AVFilterContext
  • 这两个变量置为 1 时,一般表示有错误发生,例如 EOF 信号也是通过这两个变量来传递的

例如,当开发者调用 av_buffersrc_add_frame_flags() 并传递了一个空的 AVFrame 时,这时会触发 EOF 逻辑,状态如下:
image

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

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

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

posted @ 2025-03-26 17:42  重返科韵路  阅读(10)  评论(0)    收藏  举报