以 ffplay -i linmeimei.rm -vf “trim=duration=5[tmp], [tmp]reverse”
详细说明 avfilter的使用步骤, 内部架构,以及数据的流向.
第一步: 注册所有支持的Filters
avfilter_register_all();
注意: 内部有锁保护机制,多个线程同时调用时,外部不需要再加锁保护.
第二步: 创建 filtergraph 的上下文
简单代码如下:
AVFilterGraph *mFilterGraph = avfilter_graph_alloc(void);
图解如下:
注: 彩色部分为新增部分, 黑色部分为已存在部分,下同.
第三步:创建 filtergraph的输入输出filter, 即 buffer 和 buffersink
简单代码如下:
AVFilterContext* mFilterIn;
AVFilterContext* mFilterOut;
avfilter_graph_create_filter(&mFilterIn,
avfilter_get_by_name("buffer"),
"ffplay_buffer", buffersrc_args, NULL, mFilterGraph);
avfilter_graph_create_filter(&mFilterOut,
avfilter_get_by_name("buffersink"),
"ffplay_buffersink", NULL, NULL, mFilterGraph);
注: buffersrc_args 为传递给buffer filter的创建参数字符串,例:
“video_size=352x288:pix_fmt=0:time_base=1/1000:pixel_aspect=0/1”
这四个参数为必传参数,否则会无法创建 buffer filter.
图解如下:
从图中可以看出, 经过此步骤,当前 mFilterGraph中共有两个 AVFilterContext,分别为
ff_vsrc_buffer filter (i.e. buffer, 之后简称 vsrc)和
ff_vsink_buffer filter (i.e. buffersink, 之后简称 vsink);
存放于 mFilterGraph的filters数组中
vsrc只有 outputs, 为avfilter_vsrc_buffer_outputs
(输入默认为解码后的YUV数据,没有上一级的filter, 所以无须设置inputs);
vsink只有 inputs, 为 avfilter_vsink_buffer_inputs
(输出默认为要渲染的YUV数据,没有下一级的filter, 所以无须设置outputs)
其他的filter 均有特定的 inputs 和 outputs;
第四步: 构造 AVFilterInOut,并给解析传给mFilterGraph的字符串参数
简单代码如下:
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
outputs->name = av_strdup("in");
outputs->filter_ctx = mFilterIn;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = mFilterOut;
inputs->pad_idx = 0;
inputs->next = NULL;
avfilter_graph_parse_ptr(mFilterGraph, cmd_args, &inputs, &outputs, NULL);
其中 cmd_args为字符串参数 “trim=duration=5[tmp], [tmp]reverse”
注: outputs->name 必须为 “in”, inputs->name 必须为 “out”, 这是与ffmpeg内部的代码对应的,
否则会导致 avfilter_graph_parse_ptr 报错;
上层传给filtergraph的参数中, 不能出现 name为 “in” 或者 “out” 的 AVFilterInOut,
否则会与这里定义的 outputs, inputs的name冲突, 引起 filtergraph解析失败.
图示如下:
从图中可以以下几点:
- 从字符串中解析出trim和reverse filter,并追加到 mFilterGraph 的 filters数组里
- 解析出字符串参数中的 [tmp] AVFilterInOut 以及先前设置的 [in] 和 [out] AVFilterInOut.
所以传给filtergraph的字符串参数中,不允许出现name为 “in” 或者 “out” 的 AVFilterInOut. - AVFilterLink用来描述前后两个filter之间的关系
以 vsrc和 trim为例来说明:
二者通过 AVFilterLink *Link1来建立关系,
Link1的 src filter 和 dst filter 分别指向 vsrc 和 trim;
Link1 的 srcpad 和 dstpad 分别指向 vsrc的 outputs[0] (即avfilter_vsrc_buffer_outputs[0] )
和 trim 的 inputs[0] (即 trim_inputs[0]);
vsrc中的output数组也会装载 Link1 (vsrc filter只有一个输入,所以此处只有一个AVFilterLink).
trim中的input数组也会装载Link1(trim filter只接受一个输入,所以此处只有一个AVFilterLink).
一个filter可以有一个AVFilterLink 或者 多个 AVFilterLink
但 一个AVFilterLink尽可以关联一个输入filter和一个输入filter
–trim–>[tmp]–reverse–> 以及 --reverse–>[out]–vsink–> 之间的 AVFilterLink类似 Link1, 不再赘述.
综上, avfilter_vsrc_buffer_outputs[0] (AVFilterPad ) 和 trim_inputs[0] (AVFilterPad) 通过 AVFilterLink * Link1 建立关系,
trim_inputs[0] 和 trim_outputs[0] 通过 trim filter建立联系.
而AVFilterPad 结构体中又包含着 filter_frame, request_frame 等回调函数,
可以实现YUV数据的处理和传递,
经过 AVFilterLink 以及 AVFilterContext 建立的关系链,
YUV数据通过此关系链不断被 filter 处理, 直到最后 vsink输出.
第五步, 配置 filterGraph
简单代码如下:
avfilter_graph_config(mFilterGraph, NULL);
mFilterGraph根据之前建立的数据结构,以及传递的配置参数做配置
比如说, 传递给 vsrc的视频宽度w, 高度h, time_base等信息,
通过数据结构传给给各个filter, 直至 vsink
根据传递给 vsrc filter的 像素格式 pix_fmt, 以及 filtergraph中各个 filter所支持的 format,
共同协商出一个最终的format.
第六步, filter处理
简单代码如下:
status_t err = av_buffersrc_add_frame(mFilterIn, mFrame);
while (ret >= 0) {
ret = av_buffersink_get_frame_flags(filt_out, frame, 0);
if (ret < 0) {
ret = 0;
break;
}
}
整体图如下:
可以看出, AVFilterLink中有一缓存fifo, 用于存放前一个filter的输出数据和后一个filter的输入数据;
调用一次 av_buffersrc_add_frame, 会将存储YUV数据的AVFrame放置于 vsrc的 outputs中, 即第一个AVFilterLink的fifo中, 等待接下来的filter去处理.
调用一次AV_buffersink_get_frame_flags后,
在整个FilterGraph中,根据每一个filter的input_pads中的filter_frame函数,
去处理此AVFrame数据,并存放与下一个 AVFilterLink的 fifo中,等待下一个 filter去处理,
直至存放到最后一个AVFilterLink的fifo中
再调用一次AV_buffersink_get_frame_flags, 才可以将最后一个AVFilterLink中的 AVFrame取走,供渲染使用.