ffmpeg 过滤器 xfade 自定义动画的研究 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jifengg
V2EX    FFmpeg

ffmpeg 过滤器 xfade 自定义动画的研究

  •  
  •   jifengg 2024-07-03 15:42:51 +08:00 3112 次点击
    这是一个创建于 467 天前的主题,其中的信息可能已经有所发展或是发生改变。

    本文无任何推广,请放心阅读。
    本文无任何推广,请放心阅读。
    本文无任何推广,请放心阅读。

    前前言

    hello ,兄弟们,我又来分享我的 ffmpeg 脚本啦。
    上次分享了一个使用 ffmpeg ,将多张图片转换成类似幻灯片的视频,支持多种转场效果,说过在研究自定义效果,目前有点小成果,于有了本文。

    本次没有新增脚本,而是增强了ffmpeg.img2video.js,预置了一些自定义效果,并支持自己添加效果。

    以下的都是本次的技术分享。如果你不感兴趣,可以直接到 GitHub 更新脚本。

    开源地址

    GitHub 地址:https://github.com/jifengg/ffmpeg-script

    前言

    使用xfade过滤器做视频转场切换效果,本身 ffmpeg 已经提供了 56 种效果,能满足大部分需求。不过,更复杂的过渡效果(例如翻页)还没有。
    根据文档,使用 transition=custom+expr ,可以实现自定义的效果。但是,官方文档并没有对expr如何编写做详细说明,也没有 google 到。
    因此,对其进行了一番研究,尝试实现了几种效果。简单做一个使用教程,希望能够帮助到有需要的人。

    效果预览(点击查看视频,视频均小于 1MB )

    水滴

    https://github.com/jifengg/ffmpeg-script/assets/17020523/b3cec5b1-d747-46bd-aae1-924289aaddce

    百叶窗

    https://github.com/jifengg/ffmpeg-script/assets/17020523/1bef9ae3-41c3-4747-ae41-9056ae4e6892

    简易翻页

    https://github.com/jifengg/ffmpeg-script/assets/17020523/30c810a1-7522-4829-8450-4602c8203853

    ffmpeg 官方 wiki

    https://trac.ffmpeg.org/wiki/Xfade

    ffmpeg 官方文档翻译

    以下翻译自FFmpeg xfade 官方文档

    xfade 将淡入淡出从一个输入视频流应用到另一个输入视频流。淡入淡出将持续指定的时间。 两个输入必须是恒定帧速率,并且具有相同的分辨率、像素格式、帧速率和时间基准。 该过滤器接受以下选项: transition 'custom' [忽略] duration 设置交叉淡入淡出持续时间(以秒为单位)。范围为 0 至 60 秒。默认持续时间为 1 秒。 offset 设置相对于第一个输入流的交叉淡入淡出开始时间(以秒为单位)。默认偏移量为 0 。 expr 设置自定义过渡效果的表达式。 表达式可以使用以下变量和函数: X Y 当前样本的坐标。 W H 图像的宽度和高度。 P 过渡效果的进展。 [译注] 过渡开始时,P=1.0 ,过渡结束时,P=0.0 。 PLANE 目前正在处理的平面。 [译注] 这里的平面,其实就是指像素格式的分量。 [译注] 取值范围由输入流的像素格式 pix_fmt 决定,如 yuv420p ,则取值范围是 0 ,1 ,2 ;如 rgba ,则取值范围是 0 ,1 ,2 ,3 。 A 返回第一个输入流在当前位置和平面的值。 B 返回第二个输入流在当前位置和平面的值。 a0(x,y) a1(x,y) a2(x,y) a3(x,y) 返回第一个输入的第一/第二/第三/第四个分量的 位置 (x,y) 处的像素的值。 [译注] 例如,像素格式是 yuv420p ,a0 返回的是 Y 分量。a1 返回的是 U 分量。a2 返回的是 V 分量。没有 a3 b0(x,y) b1(x,y) b2(x,y) b3(x,y) 返回第二个输入的第一/第二/第三/第四个分量的 位置 (x,y) 处的像素的值。 

    理解 P

    一般来说,ffmpeg 中支持时间轴编辑的过滤器,都有tn参数可以用在表达式中,其中t表示时间秒,n表示帧数。
    但是 xfade 里却是用的 P ,它不是tn。如果你理解错了,会发现自定义效果完全没效。
    因为,它表示的是过渡效果的进度,而且,重要的是,它是个递减的数。

    • 过渡动画开始的时候,P=1.0 ;
    • 过渡动画结束的时候,P=0.0 ;
    • 它的值是按帧线性递减的,例如,duration=4 ,fps=25 ,那么第二帧的时候,P=1.0-1/(4*25)=0.99 ;
    • 可以通过数学函数来改变 P 的“线性”,例如 P*P*(3-2P),(Smoothstep函数图)。
      • 注意,P 是从 1.0 到 0.0 ,因此查看函数图的时候要注意从右往左看。
      • 如果你觉得从右往左看不直观,把所有 P 都改成(1-P)吧。
      • win11 自带的计算器有一个“绘图”功能,能够很好的显示各种数学函数的图形,可以用来辅助理解。

    理解 X,Y,W,H

    X,Y 表示坐标,是指“当前正在计算表达式的像素的坐标”,按照我们要实现的效果,决定该像素对应的颜色码。

    W,H 是图像的宽高,这个在整个渐变过程是保持不变的。

    理解 PLANE,A,B,a0(x,y),...,b0(x,y),...

    a0(x,y)表示第一个视频坐标 x,y 处的像素的第一个分量值。 PLANE 表示当前是计算的第几个分量值。 A 是一个简写,当 PLANE=0 时,A=a0(X,Y); PLANE=1 时,A=a1(X,Y); PLANE=2 时,A=a2(X,Y);以此类推。 b 和 B 同 a 和 A 。

    注意,无法通过类似a(plane,x,y)的方法来获得指定坐标指定分量的值,因此在像素有位移的时候,表达式会比较长。如if(eq(PLANE,0),a0(X,Y),if(eq(PLANE,1),a1(X,Y),if(eq(PLANE,2),a2(X,Y),0)))

    理解 expr

    xfadeexpr,返回一个值,但是这个值是什么含义呢,一般人看文档很难理解。
    300x200 的输入源为例,假设其像素格式是 yuv420p ,则其分量个数是 3 。( ffmpeg 支持的像素格式及格式信息,可以通过ffmpeg -pix_fmts查看)。 像素点是60000个,每一帧的像素分量总数就是60000*3=18 万个。
    那么,过渡开始的第一帧,ffmpeg 会遍历每个像素点的每个分量,分别调用expr,并设置 X,Y,PLANE 等值。总共调用18 万次获得对应的值,来完成第一帧的渲染。 如果我们希望每一帧就是显示第一个视频的画面,那么可以写expr=A即可。A表示的就是第一个视频当前像素当前分量的值。

    尝试 1 ,实现渐隐渐显效果

    如果我们希望实现第一个视频渐渐变透明,第二个视频由透明渐渐显现,类似xfade默认的效果fade,那么可以写expr='A*P+B*(1-P)'
    因为 P 是从 1.0 线性变成 0.0 的。所以一开始 P=1 ,表达式计算结果=A,看到的就是只有第一个视频画面,到一半时,P=0.5 ,结果=0.5A+0.5B,画面就是两个视频分别半透明叠加在一起。最后 P=0.0 时,结果=B,就只剩下第二个视频的画面了。

    尝试 2 ,实现擦除效果

    同样的,如果我们希望实现一个从右往左擦除的效果(图片引用自https://trac.ffmpeg.org/wiki/Xfade):
    wipeleft

    分析一下,分割线在画面水平线上的位置 X ,除以宽度 W ,其实就是等于 P ,于是,我们可以让分割线左边的显示画面 A ,右边的显示画面 B 。 expr='if(lt(X/W,P),A,B)':当X/W<P的时候,说明 X 在分割线左边,于是显示 A ,否则显示 B 。

    分割线上显示 A 还是 B ,影响不大。这里是显示了 B ,如果要显示 A ,可以用lte代替lt

    尝试 3 ,实现推走效果

    从上面两个例子你大概能理解 expr 要返回什么内容了。我们接着第三个例子。 如果我们希望实现的是一个从右往左推走的效果:
    slideleft

    你会发现,变得更复杂了。你可以先暂停试试自己能否写出来。

    为什么更复杂了?以坐标(0,0)为例,他显示的像素时刻都在变化(因为画面在往左移动)。
    例如,在 P=0.8 的时候,它(0,0)应该是视频 A X=W*0.2,Y=0 坐标处的像素值。(这里需要好好理解,参考下图帮忙理解)

    image

    X/W>P的地方,应该显示视频 B 的画面,其坐标转换关系是(X-P*W,Y)。
    注意,此时你没法再用值AB了,因为它们是坐标(X,Y)的分量,而我们要在(X,Y)处显示别的坐标的像素,这个我们在上面理解 PLANE,A,B,a0(x,y),...,b0(x,y),...的地方说过了。

    那么这个表达式要怎么写呢?

    expr='if(lt(X/W,P),^ if(eq(PLANE,0),a0(X+(1-P)*W,Y),^ if(eq(PLANE,1),a1(X+(1-P)*W,Y),^ if(eq(PLANE,2),a2(X+(1-P)*W,Y),0)))^ ,^ if(eq(PLANE,0),b0(X-P*W,Y),^ if(eq(PLANE,1),b1(X-P*W,Y),^ if(eq(PLANE,2),b2(X-P*W,Y),0)))^ )' 

    我测试的时候用的是 windows 的 bat 脚本,为了方便理解和修改,用^进行了换行。注意不要有空格,否则会报错。
    测试的时候用的是 yuv420p 像素格式,因此表达式没有用到 a3 ,如果是用了 4 个分量的像素格式需要把 a3 按照上面的格式加进去。

    其中,分割线左边显示视频 A 的画面,且 x 坐标左移了(1-P)*W 个像素,因此其 x 坐标表达式是X+(1-P)*W
    右边显示视频 B 的画面,且 x 坐标右移到了分割线右边,因此其 x 坐标表达式是X-P*W
    因为是水平移动,所以 y 坐标保持Y即可。

    于是,随着 P 从 1.0 渐变到 0.0 ,视频 A 就像被视频 B 从右边推到了左边,完成了一个过渡效果。

    小结

    现在,你已经了解了 expr 要怎么编写来实现过渡效果了。我还实现了一些其它效果,包括示例里的,你可以在 GitHub 上查看

    性能

    在 windows 下创建 2 个 bat 文件,分别输入测试命令:

    @echo off @REM 使用 custom 实现 slideleft 效果 ffmpeg -y -hide_banner ^ -f lavfi -i "pal100bars=r=1/1000" ^ -f lavfi -i "colorchart=r=1/1000" ^ -filter_complex ^ [0:v]format=yuv420p,scale=960:480,fps=25,trim=duration=40[v1];^ [1:v]format=yuv420p,scale=960:480,fps=25,trim=duration=40.04[v2];^ [v1][v2]xfade=duration=40:offset=0:transition=custom:^ expr='if(lt(X/W,P),^ if(eq(PLANE,0),a0(X+(1-P)*W,Y),^ if(eq(PLANE,1),a1(X+(1-P)*W,Y),^ if(eq(PLANE,2),a2(X+(1-P)*W,Y),0)))^ ,^ if(eq(PLANE,0),b0(X-P*W,Y),^ if(eq(PLANE,1),b1(X-P*W,Y),^ if(eq(PLANE,2),b2(X-P*W,Y),0)))^ )' ^ -crf 23 -c:v h264 -pix_fmt yuv420p -movflags +faststart -r 25 -aspect 960:480 ^ out1.mp4 
    @echo off @REM 使用内置的 slideleft 效果 ffmpeg -y -hide_banner ^ -f lavfi -i "pal100bars=r=1/1000" ^ -f lavfi -i "colorchart=r=1/1000" ^ -filter_complex ^ [0:v]format=yuv420p,scale=960:480,fps=25,trim=duration=40[v1];^ [1:v]format=yuv420p,scale=960:480,fps=25,trim=duration=40.04[v2];^ [v1][v2]xfade=duration=40:offset=0:transition=slideleft ^ -crf 23 -c:v h264 -pix_fmt yuv420p -movflags +faststart -r 25 -aspect 960:480 ^ out2.mp4 

    这里使用的动画时长是 40 秒,可以自行修改成 0~60 秒。
    在我电脑上运行,耗时分别是:自定义17.514 秒,内置1.605 秒
    可以看出,使用自定义的效果,远比内置效果更耗时。原因我们在“理解 expr”有提过,因为每一帧需要调用 expr 次数=960×480×3=1,382,400 。一百多万次。而且是纯 CPU 运算,因此效率自然底下。

    好在一般的过场时长是 3 、4 秒左右,影响还在可接受范围内。

    如果你在寻找更高效的自定义效果,可以考虑使用xfade_opencl过滤器,或者自行编译 ffmpeg ,加入gl-transition过滤器。

    其它转场过滤器

    xfade_opencl

    要使用xfade_opencl,需要编译的时候加入--enable-opencl,且运行的机器有支持 opencl 的设备(一般指显卡)。
    要查看当前机器有哪些 opencl 的设备,可以运行以下命令:

    ffmpeg -v debug -init_hw_device opencl 

    打印出类似信息:

    [AVHWDeviceContext @ 0000027894f28400] 1 OpenCL platforms found. [AVHWDeviceContext @ 0000027894f28400] 1 OpenCL devices found on platform "NVIDIA CUDA". [AVHWDeviceContext @ 0000027894f28400] 0.0: NVIDIA CUDA / NVIDIA GeForce RTX ***** 

    其中0.0就是可用的 opencl 设备编号,在 ffmpeg 命令中指定使用该设备:

    ffmpeg -y -hide_banner -init_hw_device opencl=ocldev:0.0 -filter_hw_device ocldev ^ -f lavfi -r 25 -t 40 -i "pal100bars" ^ -f lavfi -r 25 -t 40.04 -i "colorchart" ^ -filter_complex ^ [0:v]format=yuv420p,scale=960:480,hwupload[v0];^ [1:v]format=yuv420p,scale=960:480,hwupload[v1];^ [v0][v1]xfade_opencl=duration=40:offset=0:transition=slideleft,hwdownload,format=yuv420p ^ -c:v h264_nvenc -pix_fmt yuv420p -movflags +faststart -r 25 -aspect 960:480 ^ out3.mp4 

    性能比自定义 xfade 效果好很多,唯一要求就是需要支持 opencl 的设备(一般指显卡)。
    且,xfade_opencl也是支持自定义效果的,官方文档
    内置的几个效果的源码可以查看 GitHub 上 ffmpeg 的源码:https://github.com/FFmpeg/FFmpeg/blob/master/libavfilter/opencl/xfade.cl

    gl-transition

    gl-transitions是由开发者 Gilles Lamothe 创建的,它封装了大量的 GPU 加速过渡效果,包括但不限于溶解、推拉、旋转等多种类型。这些过渡效果可以轻松地整合到你的图形应用程序中,无论你是开发游戏、视频编辑软件还是实验性的艺术项目。
    它使用 OpenGL 进行加速,因此,也需要支持 OpenGL 的设备(一般指显卡)。
    它不是 ffmpeg 专属的,但是可以做为一个过滤器添加到 ffmpeg 中。参考这个 GitHub 项目transitive-bullshit/ffmpeg-gl-transition。 编译后,你将可以使用其官网上的所有效果,当然也可以自己编写自定义的效果。

    性能方面,因为我没有自行编译测试,所以无法给出具体数据。

    它使用 GLSL 语言编写,如果你看了上面 OpenCL 的部分,你会发现它们有很多共同点。
    甚至,我在编写xfade自定义表达式的时候,也参考了它的 GLSL 代码。比如效果预览中的水滴,就是参考了WaterDrop

    结语

    不知道是 ffmpeg 官方觉得 xfade 的 expr 编写太过容易,还是觉得性能不行不建议使用,反正官方文档及 wiki 都没有示例,也没有提及如何编写。
    我自己基本上是自己看着文档猜测、尝试,慢慢的摸索出来一些门道。想着网上没有一个类似的教程,于是变写了这个文章。
    如果你发现文章哪里有问题,欢迎指出,大家共同进步。

    本文存档:https://github.com/jifengg/ffmpeg-script/blob/main/docs/ffmpeg.xfade.md

    第 1 条附言    2024-07-08 15:44:17 +08:00
    在 GitHub 上更新了自定义动画的预览,可以点击查看: https://github.com/jifengg/ffmpeg-script/blob/main/docs/ffmpeg.img2video.custom.transitions.md
    3 条回复    2024-07-03 21:55:57 +08:00
    shinession
        1
    shinession  
       2024-07-03 16:00:44 +08:00
    支持一下
    Arrowing
        2
    Arrowing  
       2024-07-03 16:43:36 +08:00
    之前也研究过这个,用底层方法太蛋疼了,发现有现成的,有 node 包。
    https://gl-transitions.com/gallery
    ixinshang
        3
    ixinshang  
       2024-07-03 21:55:57 +08:00
    不错 不错。 感谢分享
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     968 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 22:25 PVG 06:25 LAX 15:25 JFK 18:25
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86