再深入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,可以看下源码:
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),也就是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
中完成
由此看出该数组的大小和过滤器的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_ptr
和avfilter_graph_config
avfilter_graph_parse_ptr
从这个函数中你可以明白:(filter指AVFilterContext)
- 连接两个filter叫做link,也就是这里的AVFilterLink
- link的src是前一个filter,dst是后一个filter
- 前面的filter的output和后面的filter的input即为连接两个filter的link,是同一个。
- 关于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;
}
执行结果大致如下:
其中重点3
如果open_inputs存在多个inputs,也就是AVFilterInOut存在next指针指向的对象,那么就会沿着next寻找是AVFilterInOut,并将是AVFilterInOut的ctx属性也就是Filter实例和当前Filter进行连接,会的Link。
也就可能存在下图的样子(多个inputs)
其中link的细节
取出过滤好的视频帧
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_frame
将inlink->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的帧队列)。
内置过滤器
FFMPEG内置大量的过滤器:
文件位于:libavfilter/allfilters.c
中