前面几篇文章聊了聊FFmpeg的基础知识,我也是接触FFmpeg不久,除了时间处理之外,很多高深(滤镜)操作都没接触到。在学习时间处理的时候,都是通过在ffmpeg目前提供的avfilter基础上面修修补补(补充各种debug log)来验证想法。 而这次我将尝试新创建一个avfilter,来实现一个新滤镜。 完整的代码可参考 https://andy-zhangtao.github.io/ffmpeg-examples/

因为我是新手,所以本着先易后难的原则(其实是不会其它高深API的操作),从fade滤镜入手来仿制一个new fade(就起名叫做ifade)。

目标

fade是一个淡入淡出的滤镜,可以通过参数设置fade type(in表示淡入, out表示淡出),在视频的头部和尾部添加淡入淡出效果。 在使用过程中,fade有一些使用限制。

  • 淡入只能从片头开始设置(00:00:00.0位置起)
  • 淡出只能从片尾开始设置
  • 一次只能设置一个类型

如果想在一个视频中间设置多次淡入淡出效果,那么只能先分割视频,分别应该fade之后在合并(可能还有其它方式,可我没找到)。如果想一次实现多个fade效果,那么就要通过-filter-complex来组合多个fade,并合理安排调用顺序,稍显麻烦。

这次,ifade就尝试支持在同一个视频中实现多次fade效果。ifade计划完成的目标是:

  • 一次支持设置一个类型(淡入/淡出)
  • 一次支持设置多个fade时间点
  • 支持fade时长

分析

先看看原版fade是如何实现的。

     1	static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
2 {
3 AVFilterContext *ctx = inlink->dst;
4 FadeContext *s = ctx->priv;
5 double frame_timestamp = frame->pts == AV_NOPTS_VALUE ? -1 : frame->pts * av_q2d(inlink->time_base);
6
7 // Calculate Fade assuming this is a Fade In
8 if (s->fade_state == VF_FADE_WAITING) {
9 s->factor=0;
10 if (frame_timestamp >= s->start_time/(double)AV_TIME_BASE
11 && inlink->frame_count_out >= s->start_frame) {
12 // Time to start fading
13 s->fade_state = VF_FADE_FADING;
14
15 // Save start time in case we are starting based on frames and fading based on time
16 if (s->start_time == 0 && s->start_frame != 0) {
17 s->start_time = frame_timestamp*(double)AV_TIME_BASE;
18 }
19
20 // Save start frame in case we are starting based on time and fading based on frames
21 if (s->start_time != 0 && s->start_frame == 0) {
22 s->start_frame = inlink->frame_count_out;
23 }
24 }
25 }
26 if (s->fade_state == VF_FADE_FADING) {
27 if (s->duration == 0) {
28 // Fading based on frame count
29 s->factor = (inlink->frame_count_out - s->start_frame) * s->fade_per_frame;
30 if (inlink->frame_count_out > s->start_frame + s->nb_frames) {
31 s->fade_state = VF_FADE_DONE;
32 }
33
34 } else {
35 // Fading based on duration
36 s->factor = (frame_timestamp - s->start_time/(double)AV_TIME_BASE)
37 * (float) UINT16_MAX / (s->duration/(double)AV_TIME_BASE);
38 if (frame_timestamp > s->start_time/(double)AV_TIME_BASE
39 + s->duration/(double)AV_TIME_BASE) {
40 s->fade_state = VF_FADE_DONE;
41 }
42 }
43 }
44 if (s->fade_state == VF_FADE_DONE) {
45 s->factor=UINT16_MAX;
46 }
47
48 s->factor = av_clip_uint16(s->factor);
49
50 // Invert fade_factor if Fading Out
51 if (s->type == FADE_OUT) {
52 s->factor=UINT16_MAX-s->factor;
53 }
54
55 if (s->factor < UINT16_MAX) {
56 if (s->alpha) {
57 ctx->internal->execute(ctx, filter_slice_alpha, frame, NULL,
58 FFMIN(frame->height, ff_filter_get_nb_threads(ctx)));
59 } else if (s->is_packed_rgb && !s->black_fade) {
60 ctx->internal->execute(ctx, filter_slice_rgb, frame, NULL,
61 FFMIN(frame->height, ff_filter_get_nb_threads(ctx)));
62 } else {
63 /* luma, or rgb plane in case of black */
64 ctx->internal->execute(ctx, filter_slice_luma, frame, NULL,
65 FFMIN(frame->height, ff_filter_get_nb_threads(ctx)));
66
67 if (frame->data[1] && frame->data[2]) {
68 /* chroma planes */
69 ctx->internal->execute(ctx, filter_slice_chroma, frame, NULL,
70 FFMIN(frame->height, ff_filter_get_nb_threads(ctx)));
71 }
72 }
73 }
74
75 return ff_filter_frame(inlink->dst->outputs[0], frame);
76 }

不想贴代码,但发现不贴代码好像很难表述清楚。-_-!

fade在处理fame时最关键的是三种状态和一个变量因子。

三种状态:

  • VF_FADE_WAITING 待渲染, 初始状态
  • VF_FADE_FADING 渲染中
  • VF_FADE_DO 渲染结束

变量因子:

  • factor 控制效果强度

假设现在设置的是淡入效果(如果是淡出效果,52行会实现一个反转)): s->fade_state初始化状态是VF_FADE_WAITING,滤镜工作时就会进入第八行的判断,此时将s->factor设置为0。如果我们假设淡入的背景颜色是黑色(默认色),当s->factor==0时,渲染强度最大,此时渲染出的就是一个纯黑的画面。

第八行的if判断是一个全局初始化,一旦进入之后,s->fade_status就会被修改为VF_FADE_FADING状态。

而26到43行的判断,是为了找到渲染结束时间点。通过不停的判断每帧的frame_timestamp和start_time+duration之间的关系(通过start_frame同理),来决定是否结束渲染。start_time是由fade st=xxx来设定的,当到达结束时间点后,将s->fade_status变更为VF_FADE_DO,即可结束渲染(其实是将s->factor置为UINT16-MAX,这样就不会进入到第55行的渲染逻辑)。

fade大致的处理流程如下:

     +------------------------------------------------------------------------------------------------------------- +
| |
| |----------------------------------------------------------|------------------|--------------------> |
|time 0 st st+duration |
| |
|status VF_FADE_WAITING |
| VF_FADE_FADING |
| VF_FADE_DO |
|factor 0 0 0 0 0 0 100 500 4000 ... 65535 65535 65535 65535|
| |
+--------------------------------------------------------------------------------------------------------------+

0->st这段时间内,status一直是VF_FADE_FADING状态,factor是0。 这段时间内渲染出来的全是黑色。到达st点后,开始逐步调整factor的值(不能一次性的调整到UINT16-MAX,要不就没有逐渐明亮的效果了),直到st+duration这个时间后,在将factor调整为UINT16-MAX。以后流经fade的帧就原样流转到ff_filter_frame了。

改造

分析完fade的处理逻辑之后,如果要实现ifade的效果,那么应该是下面的流程图:

     +------------------------------------------------------------------------------------------------------------------+
| A B C D |
| |-----------------------------|------------------|----------------|------------------|-------------------->|
|time 0 st1 st2-duration st2 st2+duration |
| |
|status VF_FADE_FADING |
| VF_FADE_DO |
| |
| VF_FADE_FADING |
| VF_FADE_DO |
|factor 0 0 0 65535 65535 0 0 0 0 0 0 0 0 100 500 4000 ... 65535 |
| |
+------------------------------------------------------------------------------------------------------------------+

0-A点仍然是fade原始逻辑。到达A点之后,将s->fade_status改完VF_FADE_DO表示关闭渲染。 当到达B点时(距离st2还有duration的时间点),开始将s->factor调整为0. 这是为了模拟出画面从暗到亮的效果。同时s->fade_status再次置为VF_FADE_FADING状态,到达C点是开始重新计算s->factor的值,将画面逐渐变亮。

可以看出ifade就是利用s->fade_status重复利用现有的处理逻辑来实现多次淡入的效果。

实现

上面分析完之后,就可以动手写代码了。 具体代码就不贴出来了,可以直接看源码。 下面就说几个在ffmpeg 4.x中需要注意的地方:

  • 添加新avfilter

    • libavfilter/Makefile中添加新filter名称。 OBJS-$(CONFIG_IFADE_FILTER) += vf_ifade.o
    • libavfilter/allfilter.c中添加新filter. extern AVFilter ff_vf_ifade
  • 重新生成makefile

    • 重新根据实际情况执行configure,生成最新的makefile脚本

然后就是漫长的等待。

在编写filter时,ffmpeg提供了AVFILTER_DEFINE_CLASS这个宏来生成默认的avclassoptions,所以一定要注意class名称和options名称要和宏定义中的名字保持一致,否则会导致编译失败。

新手学习FFmpeg - 调用API编写实现多次淡入淡出效果的滤镜的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  8. 新手学习FFmpeg - 通过API完成filter-complex功能

    本篇尝试通过API实现Filter Graph功能. 源码请参看 https://andy-zhangtao.github.io/ffmpeg-examples/ FFmpeg提供了很多实用且强大的滤 ...

  9. 【jQuery】使用JQ来编写面板的淡入淡出效果

    本文与上一篇的<[jQuery]使用JQ来编写最主要的淡入淡出效果>(点击打开链接)为姊妹篇. 但上一篇仅仅是对文本的基本控制,本篇则是对面板元素进行控制. 尽管功能上很类似,可是所用到的 ...

随机推荐

  1. ceph存储基础概念

    一.分布式文件系统: 是指文件系统管理的物理存储资源不一定直接是连接在本地节点上,而是通过计算机网络与节点相连. 分布式文件系统的设计基与C/S架构(客户端/服务器) 常见的分布式文件系统:Ceph. ...

  2. shiro 定义realm

    public class UserRealm extends AuthorizingRealm { private UserService userService = new UserServiceI ...

  3. Python利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法:

    这是一个最简单的自定义函数,自己调用自己,我的理解是这样的: 1.传一个s参数进行判断,如果有空字符它会切掉前后的空字符,返回一个新的s,这个新的s还有的话会继续执行这种重复的操作,类似于递归(博主不 ...

  4. python 05 字典

    [TOC] 字典——dict { } 字典是无序,可变的数据类型. 字典:用于存储数据,存储大量数据,字典要比列表快:将数据和数据之间进行关联. 1. 定义:  dic = {键:值,键:值}  #每 ...

  5. egret之粒子系统

    1.添加粒子库,放在项目同级目录下(必须!!必须!!) 2.添加路径: 3.添加代码: private creatParticle(_pname: string, _target, _x: numbe ...

  6. (八)分布式通信----主机Host

    上节中有谈到的是通信主机(TransportHost),本节中主机(ServiceHost)负责管理服务的生命周期. 项目中将两个主机拆分开,实现不同的功能: 通信主机:用于启动通信监听端口: 生命周 ...

  7. Socket(套接字)在服务器端和客户端之间的基本工作原理

    Socket之间的连接过程主要可以概括为以下三步: 服务器建立监听:客户端初始化Socket动态库后创建套接字,然后指定客户端Socket的地址,循环绑定Socket直至成功,然后开始建立监听,此时客 ...

  8. ASP.NET Core中使用Csp标头对抗Xss攻击

    内容安全策略(CSP)是一个增加的安全层,可帮助检测和缓解某些类型的攻击,包括跨站点脚本(XSS)和数据注入攻击.这些攻击用于从数据窃取到站点破坏或恶意软件分发的所有内容(深入CSP) 简而言之,CS ...

  9. CodeForces - 632E Thief in a Shop 完全背包

    632E:http://codeforces.com/problemset/problem/632/E 参考:https://blog.csdn.net/qq_21057881/article/det ...

  10. hdu6376 度度熊剪纸条 思维

    度度熊剪纸条 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Subm ...