前面几篇文章聊了聊FFmpeg的基础知识,我也是接触FFmpeg不久,除了时间处理以外,不少高深(滤镜)操做都没接触到。在学习时间处理的时候,都是经过在ffmpeg目前提供的avfilter基础上面修修补补(补充各类debug log)来验证想法。 而此次我将尝试新建立一个avfilter,来实现一个新滤镜。 完整的代码可参考 https://andy-zhangtao.github.io/ffmpeg-examples/git
由于我是新手,因此本着先易后难的原则(实际上是不会其它高深API的操做),从fade滤镜入手来仿制一个new fade(就起名叫作ifade)。github
fade
是一个淡入淡出的滤镜,能够经过参数设置fade type(in表示淡入, out表示淡出),在视频的头部和尾部添加淡入淡出效果。 在使用过程当中,fade有一些使用限制。shell
若是想在一个视频中间设置屡次淡入淡出效果,那么只能先分割视频,分别应该fade以后在合并(可能还有其它方式,可我没找到)。若是想一次实现多个fade效果,那么就要经过-filter-complex来组合多个fade,并合理安排调用顺序,稍显麻烦。学习
此次,ifade就尝试支持在同一个视频中实现屡次fade效果。ifade计划完成的目标是:ui
先看看原版fade
是如何实现的。this
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 }
不想贴代码,但发现不贴代码好像很难表述清楚。-_-!debug
fade
在处理fame时最关键的是三种状态和一个变量因子。code
三种状态:视频
变量因子:ip
假设如今设置的是淡入效果(若是是淡出效果,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
这个宏来生成默认的avclass
和options
,因此必定要注意class名称和options名称要和宏定义中的名字保持一致,不然会致使编译失败。