【再深入FFMPEG】过滤器Filter执行流程

703 阅读6分钟

再深入FFMPEG

过滤过程 BufferSink

buffersink作为视频过滤器的终点站,我们可以通过av_buffersink_get_frame获取编辑好的视频帧。


const AVFilter *bufferSinkFilter = avfilter_get_by_name("buffersink");  

avfilter_graph_create_filter(&filterContext_out, bufferSinkFilter, "out",  NULL,  NULL, graph);

先来看下这个函数做了什么?

av_buffersink_get_frame


int attribute_align_arg av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame)
{
    return av_buffersink_get_frame_flags(ctx, frame, 0);
}

int attribute_align_arg av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flags)
{
    return get_frame_internal(ctx, frame, flags, ctx->inputs[0]->min_samples);
}


static int get_frame_internal(AVFilterContext *ctx, AVFrame *frame, int flags, int samples)
{
    BufferSinkContext *buf = ctx->priv;
    AVFilterLink *inlink = ctx->inputs[0];
    int status, ret;
    AVFrame *cur_frame;
    int64_t pts;

    if (buf->peeked_frame)
        return return_or_keep_frame(buf, frame, buf->peeked_frame, flags);

    while (1) {
        ret = samples ? ff_inlink_consume_samples(inlink, samples, samples, &cur_frame) :
                        ff_inlink_consume_frame(inlink, &cur_frame);
        if (ret < 0) {
            return ret;
        } else if (ret) {
            return return_or_keep_frame(buf, frame, cur_frame, flags);
        } else if (ff_inlink_acknowledge_status(inlink, &status, &pts)) {
            return status;
        } else if ((flags & AV_BUFFERSINK_FLAG_NO_REQUEST)) {
            return AVERROR(EAGAIN);
        } else if (inlink->frame_wanted_out) {
            ret = ff_filter_graph_run_once(ctx->graph);
            if (ret < 0)
                return ret;
        } else {
            ff_inlink_request_frame(inlink);
        }
    }
}

这段代码中,先弄清楚buf哪来的?

buf : AVFilterContext->priv是什么

BufferSinkContext *buf = ctx->priv;

对于ctx->priv,这个值是在我们上面的代码中的avfilter_graph_create_filter函数赋值的,这个值作为二层指针,指向了filter的priv_class,可以看下源码:

image.png

image.png

ff_filter_alloc中有这样一段代码,就这样我们的buf有了值。

image.png

对于*(const AVClass**)ret->priv = filter->priv_class;的解释:

这里的ret->priv并不是null,而是ret->priv = av_mallocz(filter->priv_size);

priv_size的大小是:

image.png

是BufferSinkContext的大小,分配一块这样的内存

其中指向BufferSinkContext的首地址也是指向其第一个属性的地址,这里是一个AVClass类型的指针:

image.png

所以*(const AVClass**)ret->priv = filter->priv_class;的意思是把ret->priv所分配的BufferSinkContext大小的内存的首地址赋值为priv_class这个指针,也就是BufferSinkContext的class属性值等于priv_class,也就是class指向了priv_class所指向的对象。

真是好难理解。

关于这里需要去看下cpp中class的内存布局,因为首地址不一定就是第一个属性的地址,因为类中存在虚函数的话,第一个地址是一个指向虚表的指针。但是不要尝试给第二个属性赋值,因为存在内存对齐

可以参考:zoux86.github.io/post/2019-1…

我们可以通过av_opt_set对一些特性设值,对于flags参数一般会置为AV_OPT_SEARCH_CHILDREN,因为设置了这个flags我们就可以获取到和参数一相关联的属性,比如对于AVFilterContext,除了获取自己的av_class外,还会通过AVFilterContext->priv中获取相应的option,至于AVFilterContext为什么会通过AVFilterContext->priv获取属性是因为AVFilterContext中的函数child_class_next返回的AVClass(返回值是这里的AVFilterContext->priv),也就是AVFilter的priv_class,如果我们遇到了setpts=0.5PTS这样的过滤器,这个只有过滤器的名字setpts,和值0.5PTS,这也就意味着这里的过滤器参数表示我是按照setpts的options的顺序设置,所以我可以不用把key名字也写出来,比如avgblur=1:7:10等效于avgblur=sizeX=1:sizeY=10:planes=7。如果带key可以交换顺序写。

所以对于get_frame_internal函数的参数一,一定是一个由buffersink通过avfilter_graph_create_filter创建的context实例

然后是AVFilterLink *inlink = ctx->inputs[0];

BufferSinkContext->inputs是什么

BufferSinkContext->inputs的结果是AVFilterLink **,是一个AVFilterLink *类型的数组。也就是过滤器的链。

同样该属性的初始化也是在avfilter_graph_create_filter中完成

image.png

由此看出该数组的大小和过滤器的inputs的个数有关,inputs是AVFilterPad类型的数组。

image.png

企业微信截图_16992565031124.png

即该过滤器有多少个AVFilterPad就有多少个AVFilterLink *

要理解这些东西的作用是什么,就需要从过滤器过滤的过程入手,找到视频帧是如何流动的。

buffer过滤器中的av_buffersrc_add_frame函数正是处理视频帧的起点。

BUFFERSRC

准备 avfilter_graph_create_filter

将buffersrc的outputs复制进AVFilterContext的outputs_pads中 image.png

av_buffersrc_add_frame

把传进去的frame转存到ctx->priv->fifo中

image.png

后面会执行requet_frame,这里用到了link

image.png

如果flags含有AV_BUFFERSRC_FLAG_PUSH则将frame放进过滤器中

image.png

image.png

后来起作用的地方是:ff_filter_graph_run_once

如果flags没有AV_BUFFERSRC_FLAG_PUSH,这也是大多数情况。

上面都用到了AVFilterLink,在创建完这些filtercontext,我们还有两个函数没有看:avfilter_graph_parse_ptravfilter_graph_config

avfilter_graph_parse_ptr

从这个函数中你可以明白:(filter指AVFilterContext)

  1. 连接两个filter叫做link,也就是这里的AVFilterLink
  2. link的src是前一个filter,dst是后一个filter
  3. 前面的filter的output和后面的filter的input即为连接两个filter的link,是同一个。
  4. 关于filter是该如何和哪个filter使用link进行连接,是通过AVFilterInOut找到的

精简一下代码,关注写注释的地方即可。


int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
                         AVFilterInOut **open_inputs_ptr, AVFilterInOut **open_outputs_ptr,
                         void *log_ctx)
{

    AVFilterInOut *curr_inputs = NULL;
    AVFilterInOut *open_inputs  = open_inputs_ptr  ? *open_inputs_ptr  : NULL;
    AVFilterInOut *open_outputs = open_outputs_ptr ? *open_outputs_ptr : NULL;

        AVFilterContext *filter;
        const char *filterchain = filters;
        filters += strspn(filters, WHITESPACES);

        //重点1
        if ((ret = parse_filter(&filter, &filters, graph, index, log_ctx)) < 0)
            goto end;

        //重点2
        if (filter->nb_inputs == 1 && !curr_inputs && !index) {
            const char *tmp = "[in]";
            if ((ret = parse_inputs(&tmp, &curr_inputs, &open_outputs, log_ctx)) < 0)
                goto end;
        }
        
        //重点3
        if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, log_ctx)) < 0)
            goto end;

    if (curr_inputs) {
      ·//重点4
        const char *tmp = "[out]";
   
        if ((ret = parse_outputs(&tmp, &curr_inputs, &open_inputs, &open_outputs,
                                 log_ctx)) < 0)
            goto end;
    }
    ...
    return ret;
}

执行结果大致如下:

image.png

其中重点3如果open_inputs存在多个inputs,也就是AVFilterInOut存在next指针指向的对象,那么就会沿着next寻找是AVFilterInOut,并将是AVFilterInOut的ctx属性也就是Filter实例和当前Filter进行连接,会的Link。

也就可能存在下图的样子(多个inputs)

image.png

其中link的细节

IMG_20231107_180822(1)(1).jpg

取出过滤好的视频帧

av_buffersrc_add_frame(filterContext_in, frame);  
av_buffersink_get_frame(filterContext_out, frame);

当我们执行av_buffersrc_add_frame后,该过滤器链还没有执行,直到av_buffersink_get_frame

av_buffersink_get_frame源码: image.png

这里对step1、step2、step3做出解释

step1:

av_buffersrc_add_frame执行后,虽然filterContext_in的link的FFFrameQueue不为空,但是filterContext_out的FFFrameQueue仍然是空队列。所以这里的ret的结果是0.

step2:

前面都没有机会执行,执行了最后的else,ff_inlink_request_frameinlink->frame_wanted_out置为1.

step3:

step2中将inlink->frame_wanted_out置为1.又由于整个是while循环,所以还会再次执行,这次就会执行到ff_filter_graph_run_once了。

image.png

ff_filter_graph_run_once源码中可以看到,正是这个函数执行了过滤器的过滤行为 但是每次只会执行当前准备好的过滤器,又由于av_buffersink_get_frame会存在循环,所以会调用ff_filter_graph_run_once多次,每次执行一个filter,并把每次执行完的帧在传递给下一个filter的input中,然后下一个filter就可在下次循环中从input取到帧进行执行,具体过程如下:

image.png

ff_filter_frame_to_filter对link的dst也就是目标filter执行当前帧,然后把执行完成的帧传递给下一个filter的input中的帧队列中(或者叫下一个link的帧队列)。

内置过滤器

FFMPEG内置大量的过滤器:

文件位于:libavfilter/allfilters.c

image.png