新手学习FFmpeg - 通过API完成filter-complex功能
本篇尝试通过API实现Filter Graph功能。 源码请参看 https://andy-zhangtao.github.io/ffmpeg-examples/
FFmpeg提供了很多实用且强大的滤镜,比如:overlay, scale, trim, setpts等等。
通过-filter-complex的表达式功能,可以将多个滤镜组装成一个调用图,实现更为复杂的视频剪辑。如何通过代码实现这个功能呢?
首先按照前面几篇的套路,在开发FFmpeg应用时,大致有三板斧:
- 初始化输入设备(初始化解码器及其应用上下文)
- 初始化输出设备(初始化编码器及其应用上下文)
- 编写帧处理逻辑(对符合要求的帧数据做各种运算处理)
本次需要实现的Filter Graph功能稍有不同,在处理帧之前需要先完成Filter Graph的处理。 处理流程如下:
+------------------------------------------------+
| +---------+ |
| | Input | ----------read --------+ |
| +---------+ | |
| | |
| \|/ |
| +-----------+ |
| +-----------------------| Input | |
| | +-----------| |
| | | |
| | \|/ |
| | +-----------+ +-----------+ |
| +<--| Filter N |<-.N.--| Filter 1 | |
| | +-----------+ +-----------+ |
| | |
| | +-------------+ |
| +------>| Output | |
| +-------------+ |
+------------------------------------------------+
从Input读取到视频数据之后,会依次经过Filter 1和Filter N,每个Filter会依次根据设定好的参数处理流经的帧数据,当所有Filter都处理完毕之后,再经过编码器编码吸入Output.
从流程可以看出,视频中的每一帧都被处理了N次,这也是视频在应用滤镜时感觉编解码时间有些长的原因。
本次增加了一部分API:
- avfilter_get_by_name
- avfilter_inout_alloc
- avfilter_graph_alloc
- avfilter_graph_create_filter
- avfilter_graph_parse_ptr
- 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:buffer和buffersink:
----------> |buffer| ---------|Filter ..... Filter N|----------->|buffersink|-------->
buffer表示Filter Graph的开始,buffersink表示Filter Graph的结束。这两中Filter是必须要存在不可缺少。
Filter Graph使用的步骤如下:
- 初始化
buffer和buffersink。 - 初始化其它filter
- 设定Filter Graph的Input和Output。
- 初始化
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
通过``使用指定的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的输入输出。
- 设定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_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功能。
新手学习FFmpeg - 通过API完成filter-complex功能的更多相关文章
- 新手学习FFmpeg - 调用API完成录屏
调用FFMPEG Device API完成Mac录屏功能. 调用FFMPEG提供的API来完成录屏功能,大致的思路是: 打开输入设备. 打开输出设备. 从输入设备读取视频流,然后经过解码->编码 ...
- 新手学习FFmpeg - 通过API实现可控的Filter调用链
虽然通过声明[x][y]avfilter=a=x:b=y;avfilter=xxx的方式可以创建一个可用的Filter调用链,并且在绝大多数场合下这种方式都是靠谱和实用的. 但如果想精细化的管理AVF ...
- 新手学习FFmpeg - 调用API编写实现多次淡入淡出效果的滤镜
前面几篇文章聊了聊FFmpeg的基础知识,我也是接触FFmpeg不久,除了时间处理之外,很多高深(滤镜)操作都没接触到.在学习时间处理的时候,都是通过在ffmpeg目前提供的avfilter基础上面修 ...
- 新手学习FFmpeg - 调用API完成录屏并进行H.264编码
Screen Record H.264 目前在网络传输视频/音频流都一般会采用H.264进行编码,所以尝试调用FFMPEG API完成Mac录屏功能,同时编码为H.264格式. 在上一篇文章中,通过调 ...
- 新手学习FFmpeg - 调用API调整视频局部速率
通过修改setpts代码实现调整视频部分的播放速率. 完整代码可参考: https://andy-zhangtao.github.io/ffmpeg-examples/ 在前面提到了PTS/DTS/T ...
- 新手学习FFmpeg - 调用API完成两个视频的任意合并
本次尝试在视频A中的任意位置插入视频B. 在上一篇中,我们通过调整PTS可以实现视频的加减速.这只是对同一个视频的调转,本次我们尝试对多个视频进行合并处理. Concat如何运行 ffmpeg提供了一 ...
- 新手学习FFmpeg - 调用API完成视频的读取和输出
在写了几个avfilter之后,原本以为对ffmpeg应该算是入门了. 结果今天想对一个视频文件进行转码操作,才发现基本的视频读取,输出都搞不定. 痛定思痛,仔细研究了一下ffmpeg提供的examp ...
- 新手学习FFmpeg - 调用API计算关键帧渲染时间点
通过简单的计算来,线上I帧在视频中出现的时间点. 完整代码请参考 https://andy-zhangtao.github.io/ffmpeg-examples/ 名词解释 首先需要明确以下名词概念: ...
- 新手学习FFmpeg - 如何编写Kubernetes资源文件
Kubernetes API的使用方式 Kubernetes API属于声明式API编程, 它和常用的命令式编程有一些区别. 通俗的说,命令式编程是第一人称,我要做什么,我要怎么做. 操作系统最喜欢这 ...
随机推荐
- Web开发中的相对路径和绝对路径
在学习HTML的时候一定会遇到引入文件和链接跳转页面,比如:JS文件.CSS文件.Image图片.我们就会考虑是相对路径和绝对路径的问题.下面PHP程序员雷雪松就详细讲解下Web开发中的相对路径和绝对 ...
- 前端插件之Datatables使用--下篇
工欲善其事,必先利其器 本系列文章介绍我在运维系统开发过程中用到的那些顺手的前端插件,上一篇文章介绍了Datatables插件的基本使用,这一篇文章作为上一篇的延续,会介绍Databases的一些高级 ...
- python匿名函数的介绍及用途
匿名函数 用lambda能够创建一个匿名函数,这中函数得名于省略了用def声明函数的标准步骤. 语法 lambda [arg1 [,arg2,.....argn]]:expression 如何使用 我 ...
- 记一次Linux修改MySQL配置不生效的问题
背景 自己手上有一个项目服务用的是AWS EC2,最近从安全性和性能方面考虑,最近打算把腾讯云的MySQL数据库迁移到AWS RDS上,因为AWS的出口规则和安全组等问题,我需要修改默认的3306端口 ...
- python学习笔记(3)--函数、参数、变量、递归
1.函数基本语法和特性 背景摘要 现在老板让你写一个监控程序,监控服务器的系统状况,当cpu\memory\disk等指标的使用量超过阀值时即发邮件报警,你掏出了所有的知识量吗,写出了以下代码 whi ...
- Spring Context 你真的懂了吗
今天介绍一下大家常见的一个单词 context 应该怎么去理解,正确的理解它有助于我们学习 spring 以及计算机系统中的其他知识. 1. context 是什么 我们经常在编程中见到 contex ...
- [Flowable] - 工作流是什么?BPM是什么?
工作流管理系统基本概念 近两年随着电子商务环境不断演进(例如阿里巴巴的B2B电子商务平台),从原来支持企业内部单系统的业务流程.到企业内部应用.服务的集成,再进一步向企业与合作伙伴之间业务交互,工作流 ...
- js 分享一个 时钟效果
<style> *{ margin: 0; padding: 0; } #outLine{ ...
- 企查查app新增企业数据抓取
企查查每日新增企业数据抓取尚未完成的工作: 需要自行抓包获取设备id,appid,sign等等 sign和时间戳保持一致即可 把所有的数据库.redis配置 无法自动登录,账号需要独立 redis数据 ...
- 虚拟机中安装Oracle 12c
1.从本地电脑中拉oracle到Linux 2.安装Linux中的oracle 12C 拉oracle到Linux 1.点击"编辑虚拟机设置" 2.点击"硬盘" ...