本篇尝试经过API实现Filter Graph功能。 源码请参看 https://andy-zhangtao.github.io/ffmpeg-examples/git
FFmpeg提供了不少实用且强大的滤镜,好比:overlay, scale, trim, setpts等等。github
经过-filter-complex
的表达式功能,能够将多个滤镜组装成一个调用图,实现更为复杂的视频剪辑。如何经过代码实现这个功能呢?shell
首先按照前面几篇的套路,在开发FFmpeg应用时,大体有三板斧:api
本次须要实现的Filter Graph功能稍有不一样,在处理帧以前须要先完成Filter Graph
的处理。 处理流程以下:ide
+------------------------------------------------+ | +---------+ | | | Input | ----------read --------+ | | +---------+ | | | | | | \|/ | | +-----------+ | | +-----------------------| Input | | | | +-----------| | | | | | | | \|/ | | | +-----------+ +-----------+ | | +<--| Filter N |<-.N.--| Filter 1 | | | | +-----------+ +-----------+ | | | | | | +-------------+ | | +------>| Output | | | +-------------+ | +------------------------------------------------+
从Input
读取到视频数据以后,会依次通过Filter 1
和Filter N
,每一个Filter会依次根据设定好的参数处理流经的帧数据,当全部Filter都处理完毕以后,再通过编码器编码吸入Output
.函数
从流程能够看出,视频中的每一帧都被处理了N次,这也是视频在应用滤镜时感受编解码时间有些长的缘由。编码
本次增长了一部分API:code
和之前的操做同样,这里就不作过多叙述。如有须要能够翻看前几篇文章。这里只增长一个dump函数:orm
av_dump_format(inFormatContext, 0, "1", 0);
av_dump_format
能够输出指定FormatContext的数据,方便定位问题。视频
一样不作过多描述,如有须要可翻看前几篇文章或者直接看源码。 仅仅提醒一下关于time_base的几个坑。
time_base是用来作基准时间转换的,也就是告诉编码器以何种速度来播放帧(也就是pts)。前几篇代码中所使用的time_base是:
outCodecContext->time_base = (AVRational) {1, 25};
1是分子,25是分母。 在进行编码时,编码器须要知道每个关键帧要在哪一个时间点进行展现和渲染(对应的就是pts和dts)。 在没有B帧的状况下,PTS=DTS。 而计算pts时,须要创建编码time_base和解码time_base的对应关系.
假设,time=5. 那么在1/25(编码time_base)的时间刻度下应该等于1/10000(编码time_base)时间刻度下的(5*1/25)/(1/90000) = 3600*5=18000
time_base的详细应用,能够参考setpts
中的实现。
在Filter Graph API
中有两个特殊的Filter:buffer
和buffersink
:
----------> |buffer| ---------|Filter ..... Filter N|----------->|buffersink|-------->
buffer
表示Filter Graph的开始,buffersink
表示Filter Graph的结束。这两中Filter是必需要存在不可缺乏。
Filter Graph使用的步骤以下:
buffer
和buffersink
。buffer
和buffersink
经过avfilter_get_by_name
来查找相符的Filter,例如:
const AVFilter *buffersrc = avfilter_get_by_name("buffer");
表示获取buffer Filter。而后经过avfilter_graph_create_filter来初始化filter,例如初始化buffer:
snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", inCodecContext->width, inCodecContext->height, inCodecContext->pix_fmt, time_base.num, time_base.den, inCodecContext->sample_aspect_ratio.num, inCodecContext->sample_aspect_ratio.den); av_log(NULL, AV_LOG_ERROR, "%s\n", args); ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph);
"in"表示buffer在整个Graph中叫作'in'。 名称能够随便叫,只要保证惟一不重复就好。
经过``使用指定的Filter Graph 语法来初始化剩余的Filter,例如:
const char *filter_descr = "movie=t.png[wm];[in][wm]overlay=10:20[out]"; avfilter_graph_parse_ptr(filter_graph, filter_descr, &inputs, &outputs, NULL)
上面表示使用了两个filter:movie
和overlay
。 inputs
和outputs
表示Graph的输入输出。
这段代码有些很差理解:
outputs->name = av_strdup("in"); outputs->filter_ctx = buffersrc_ctx; outputs->pad_idx = 0; outputs->next = NULL; inputs->name = av_strdup("out"); inputs->filter_ctx = buffersink_ctx; inputs->pad_idx = 0; inputs->next = NULL;
outputs对应的是in
(也就是buffer),in
是Graph第一个Filter,因此它只有输出端(因此对应到了outputs)。 同理out
(buffersink)是Graph最后一个Filter,只有输入端,所以对应到了inputs。
+-------+ +---------------------+ +---------------+ |buffer | |Filter ..... Filter N| | buffersink | ----------> | |output|------>|input| |output|---> |input| |--------> +-------+ +---------------------+ +---------------+
在下一篇中,咱们会经过其它api设定每一个Filter的input和output,那个时候应该会更容易理解一点。
在完成Filter Graph初始化以后,必定要经过avfilter_graph_config
来验证参数配置是否正确。
avfilter_graph_config(filter_graph, NULL)
在处理帧数据时,就和之前的思路基本保持一致了。 从解码器接受帧,而后发送到Filter Graph
中进行滤镜处理,最后再发送给编码器写入到输出文件。
惟一有些不一样的就是增长了两个函数av_buffersrc_add_frame_flags
和av_buffersink_get_frame
. av_buffersrc_add_frame_flags
表示向Filter Graph加入一帧数据,av_buffersink_get_frame
表示从Filter Graph取出一帧数据。
所以上一篇中的编码流程增长了一个while循环:
while av_read_frame | +---> avcodec_send_packet | +----> while avcodec_receive_frame | 对每一数据帧进行解码 | 经过`sws_scale`进行源帧和目标帧的数据转换 | +---->av_buffersrc_add_frame_flags | | +while av_buffersink_get_frame | | +-->avcodec_send_frame | +---> while avcodec_receive_packet | | |+--->av_interleaved_write_frame (写入到输出设备)
至此就完成了经过代码实现-filter-complex
功能。