本篇尝试通过API实现Filter Graph功能。 源码请参看 https://andy-zhangtao.github.io/ffmpeg-examples/

FFmpeg提供了很多实用且强大的滤镜,比如:overlay, scale, trim, setpts等等。

通过-filter-complex的表达式功能,可以将多个滤镜组装成一个调用图,实现更为复杂的视频剪辑。如何通过代码实现这个功能呢?

首先按照前面几篇的套路,在开发FFmpeg应用时,大致有三板斧:

  1. 初始化输入设备(初始化解码器及其应用上下文)
  2. 初始化输出设备(初始化编码器及其应用上下文)
  3. 编写帧处理逻辑(对符合要求的帧数据做各种运算处理)

本次需要实现的Filter Graph功能稍有不同,在处理帧之前需要先完成Filter Graph的处理。 处理流程如下:

+------------------------------------------------+
| +---------+ |
| | Input | ----------read --------+ |
| +---------+ | |
| | |
| \|/ |
| +-----------+ |
| +-----------------------| Input | |
| | +-----------| |
| | | |
| | \|/ |
| | +-----------+ +-----------+ |
| +<--| Filter N |<-.N.--| Filter 1 | |
| | +-----------+ +-----------+ |
| | |
| | +-------------+ |
| +------>| Output | |
| +-------------+ |
+------------------------------------------------+

Input读取到视频数据之后,会依次经过Filter 1Filter N,每个Filter会依次根据设定好的参数处理流经的帧数据,当所有Filter都处理完毕之后,再经过编码器编码吸入Output.

从流程可以看出,视频中的每一帧都被处理了N次,这也是视频在应用滤镜时感觉编解码时间有些长的原因。

本次增加了一部分API:

  1. avfilter_get_by_name
  2. avfilter_inout_alloc
  3. avfilter_graph_alloc
  4. avfilter_graph_create_filter
  5. avfilter_graph_parse_ptr
  6. av_buffersink_get_frame
  • 初始化出入设备

和以前的操作一样,这里就不做过多叙述。若有需要可以翻看前几篇文章。这里只增加一个dump函数:

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

Filter Graph API中有两个特殊的Filter:bufferbuffersink

 ----------> |buffer| ---------|Filter ..... Filter N|----------->|buffersink|-------->

buffer表示Filter Graph的开始,buffersink表示Filter Graph的结束。这两中Filter是必须要存在不可缺少。

Filter Graph使用的步骤如下:

  1. 初始化bufferbuffersink
  2. 初始化其它filter
  3. 设定Filter Graph的Input和Output。
  • 初始化bufferbuffersink

通过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

通过``使用指定的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:movieoverlayinputsoutputs表示Graph的输入输出。

  • 设定Filter Graph的Input和Output

这段代码有些不好理解:

    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_flagsav_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功能。

新手学习FFmpeg - 通过API完成filter-complex功能的更多相关文章

  1. 新手学习FFmpeg - 调用API完成录屏

    调用FFMPEG Device API完成Mac录屏功能. 调用FFMPEG提供的API来完成录屏功能,大致的思路是: 打开输入设备. 打开输出设备. 从输入设备读取视频流,然后经过解码->编码 ...

  2. 新手学习FFmpeg - 通过API实现可控的Filter调用链

    虽然通过声明[x][y]avfilter=a=x:b=y;avfilter=xxx的方式可以创建一个可用的Filter调用链,并且在绝大多数场合下这种方式都是靠谱和实用的. 但如果想精细化的管理AVF ...

  3. 新手学习FFmpeg - 调用API编写实现多次淡入淡出效果的滤镜

    前面几篇文章聊了聊FFmpeg的基础知识,我也是接触FFmpeg不久,除了时间处理之外,很多高深(滤镜)操作都没接触到.在学习时间处理的时候,都是通过在ffmpeg目前提供的avfilter基础上面修 ...

  4. 新手学习FFmpeg - 调用API完成录屏并进行H.264编码

    Screen Record H.264 目前在网络传输视频/音频流都一般会采用H.264进行编码,所以尝试调用FFMPEG API完成Mac录屏功能,同时编码为H.264格式. 在上一篇文章中,通过调 ...

  5. 新手学习FFmpeg - 调用API调整视频局部速率

    通过修改setpts代码实现调整视频部分的播放速率. 完整代码可参考: https://andy-zhangtao.github.io/ffmpeg-examples/ 在前面提到了PTS/DTS/T ...

  6. 新手学习FFmpeg - 调用API完成两个视频的任意合并

    本次尝试在视频A中的任意位置插入视频B. 在上一篇中,我们通过调整PTS可以实现视频的加减速.这只是对同一个视频的调转,本次我们尝试对多个视频进行合并处理. Concat如何运行 ffmpeg提供了一 ...

  7. 新手学习FFmpeg - 调用API完成视频的读取和输出

    在写了几个avfilter之后,原本以为对ffmpeg应该算是入门了. 结果今天想对一个视频文件进行转码操作,才发现基本的视频读取,输出都搞不定. 痛定思痛,仔细研究了一下ffmpeg提供的examp ...

  8. 新手学习FFmpeg - 调用API计算关键帧渲染时间点

    通过简单的计算来,线上I帧在视频中出现的时间点. 完整代码请参考 https://andy-zhangtao.github.io/ffmpeg-examples/ 名词解释 首先需要明确以下名词概念: ...

  9. 新手学习FFmpeg - 如何编写Kubernetes资源文件

    Kubernetes API的使用方式 Kubernetes API属于声明式API编程, 它和常用的命令式编程有一些区别. 通俗的说,命令式编程是第一人称,我要做什么,我要怎么做. 操作系统最喜欢这 ...

随机推荐

  1. leetcode 29 两数相除

    问题描述 给定两个整数,被除数 dividend 和除数 divisor.将两数相除,要求不使用乘法.除法和 mod 运算符. 返回被除数 dividend 除以除数 divisor 得到的商. 示例 ...

  2. Layui多文件上传进度条

    Layui原生upload模块不支持文件上传进度条显示,百度,谷歌找了一下不太适用.后面找到一个别人修改好的JS,替换上去,修改一下页面显示即可使用,一下是部分代码 HTML: <div cla ...

  3. xtuils

    xutils的使用必须导入一个依赖 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceS ...

  4. 腾讯物联TencentOS tiny上云初探

    2017年中旬曾写过一篇关于物联网平台的文章<微软最完善,百度最“小气” 看微软阿里百度三大物联网云平台对比>.现在已经过去两年了,物联网的格局又发生了不少的变化.不过针对腾讯来说,其物联 ...

  5. 直击--vue项目微信小程序页面跳转web-view不刷新-根源

    背景 最近项目需要适配小程序,项目是使用了vue开发的网站,其中改造方式是,每个页面都使用小程序创建一个页面通过web-view来显示指定页面的. 在没有使用小程序时,路由跳转时,刷新页面等等,这个是 ...

  6. Liunx之nginx代理

    一.代理 正向代理 正向代理,也就是传说中的代理,他的工作原理就像一个跳板(VPN),简单的说: 我是一个用户,我访问不了某网站,但是我能访问一个代理服务器,这个代理服务器呢,他能访问那个我不能访问的 ...

  7. ReentrantLock源码分析--jdk1.8

    JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...

  8. Sqlserver 查询把多行内容拼成一个字符串

    当使用:SELECT ','+Id FROM dbo.Test FOR XML PATH('')); //这样读取的数据虽然是1,2,3,4,但是仍然是xml格式,所以当数据超过2033时候,用sql ...

  9. appium输入法踩坑解决方案-----中文乱码及输入法搜索无法点击

    一.appium1.7.1 遇到的坑: 1. 在写安卓的搜索用例脚本时,发现输入内容后,搜索出现在输入法键盘原来的确认位置,定位不到手机自带输入法的"搜索"键: 2. 传入中文搜索 ...

  10. idea实现第一个springboot程序

    1.环境准备 JDK:1.8 Apache Maven: 3.6.1 IntelliJ IDEA 2019.1.3 x64 SpringBoot 1.5.9.RELEASE:1.5.9: 1.1.MA ...