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

再深入FFMPEG

过滤过程 BufferSink

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

cpp 复制代码
const AVFilter *bufferSinkFilter = avfilter_get_by_name("buffersink");  

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

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

av_buffersink_get_frame

cpp 复制代码
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,可以看下源码:

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

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

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

priv_size的大小是:

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

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

所以*(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)。

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

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

BufferSinkContext->inputs是什么

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

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

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

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

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

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

BUFFERSRC

准备 avfilter_graph_create_filter

将buffersrc的outputs复制进AVFilterContext的outputs_pads中

av_buffersrc_add_frame

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

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

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

后来起作用的地方是: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找到的

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

cpp 复制代码
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;
}

执行结果大致如下:

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

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

其中link的细节

取出过滤好的视频帧

cpp 复制代码
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源码:

这里对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了。

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

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

相关推荐
简鹿办公1 天前
FFmpeg vs 去水印软件:哪种方式更适合你?
ffmpeg·怎样去除视频水印·如何去视频logo视频水印
小狮子安度因1 天前
ffplay数据结构分析
数据结构·ffmpeg
小狮子安度因2 天前
ffplay音频重采样
ffmpeg·音视频
小狮子安度因2 天前
AAC ADTS格式分析
网络·ffmpeg·aac
勘察加熊人2 天前
ffmpeg切割音频
ffmpeg·音视频
xiaohouzi1122333 天前
Python读取视频-硬解和软解
python·opencv·ffmpeg·视频编解码·gstreamer
kimble_xia@oracle4 天前
性能优化笔记
ffmpeg
wang_chao1184 天前
RK3399平台ffmpeg-VPU硬编码录制USB摄像头视频、H264或MJPEG编码
ffmpeg·音视频
鹅毛在路上了6 天前
C++, ffmpeg, libavcodec-RTSP拉流,opencv实时预览
c++·opencv·ffmpeg
Hi202402177 天前
Orin-Apollo园区版本:订阅多个摄像头画面拼接与硬编码RTMP推流
ffmpeg·apollo·orin·图像拼接·图传