前面几篇文章聊了聊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. Java集合框架之HashSet浅析

    Java集合框架之HashSet浅析 一.HashSet综述: 1.1HashSet简介 位于java.util包下的HashSet是Java集合框架的重要成员,它在jdk1.8中定义如下: publ ...

  2. 《阿里巴巴Java开发手册1.4.0》阅读总结与心得(一)

    前言 下面是阿里对<阿里巴巴 Java 开发手册>(下称<手册>)的介绍: 凝聚了阿里集团很多同学的知识智慧和经验,这些经验甚至是用血淋淋的故障换来的,希望前车之鉴,后车之师, ...

  3. c++学习笔记_4

    前言:本笔记所对应的课程为中国大学mooc中北京大学的程序设计与算法(三)C++面向对象程序设计,主要供自己复习使用,且本笔记建立在会使用c和java的基础上,只针对与c和java的不同来写 运算符重 ...

  4. Mybatis案例升级版——小案例大道理

    纯Mybatis案例升级版——小案例大道理 前言: 这几天看了一本书<原则>,在上面看到了一句话叫“每个人都把自己眼界的局限当成世界的局限”,大学生是

  5. Features Track 2018徐州icpc网络赛 思维

    Morgana is learning computer vision, and he likes cats, too. One day he wants to find the cat moveme ...

  6. codeforces C. Sonya and Problem Wihtout a Legend(dp or 思维)

    题目链接:http://codeforces.com/contest/713/problem/C 题解:这题也算是挺经典的题目了,这里附上3种解法优化程度层层递进,还有这里a[i]-i<=a[i ...

  7. 牛客第五场多校 J plan 思维

    链接:https://www.nowcoder.com/acm/contest/143/J来源:牛客网 There are n students going to travel. And hotel ...

  8. CODE[VS] 1294 全排列

    1294 全排列  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果     题目描述 Description 给出一个n, 请输出n的所有全 ...

  9. bzoj 2726 任务安排 斜率优化DP

    这个题目中 斜率优化DP相当于存在一个 y = kx + z 然后给定 n 个对点 (x,y)  然后给你一个k, 要求你维护出这个z最小是多少. 那么对于给定的点来说 我们可以维护出一个下凸壳,因为 ...

  10. Redis缓存穿透、缓存雪崩、并发问题分析与解决方案

    (一)缓存和数据库间数据一致性问题 分布式环境下(单机就不用说了)非常容易出现缓存和数据库间的数据一致性问题,针对这一点的话,只能说,如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存.我们只能 ...