FFmpeg是许多音视频入门书籍都会推荐学习的一套多媒体框架,其集封装、解封装、编码、解码、播放和滤镜等多项功能于一身,堪称音视频领域的「瑞士军刀」。
今天,我们将不再遵循常规教程的套路,而是将以表情包界名垂青史的名场面、电影《旺角卡门》中的经典片段——「吔屎啦你」为讲解素材,通过GIF表情包创作的场景化教学,来讲解FFmpeg命令行工具的实际运用。
若本教程成功地激起了你对FFmpeg命令行工具的探索兴趣,记得说声多谢乌蝇哥点赞、收藏、评论三连支持一下。
首先,举一个最简单的例子,即,直接把一个任意格式的视频片段转为GIF图像:
ffmpeg -i as_tears_go_by.mp4 as_tears_go_by.gif
-i 选项用于指定任意数量的输入流,可以是本地文件也可以是网络文件。
而输出流也可以是任意数量的,其指定的方式则相对粗暴得多,只要是命令行中无法解释为「选项」的内容,都会被FFmpeg命令行工具视为输出流。
大多数情况下,FFmpeg命令行工具会根据输入输出流的扩展名去自动检测格式,如果需要强制指定输入或输出流的格式,可以使用-f 选项,比如:
ffmpeg -i as_tears_go_by.mp4 -f gif as_tears_go_by.gif
效果都是一样的。
在诞生这些表情包名场面的电影中,你经常会发现有那么一群“合影党”,喜欢把播放进度拖动到表情包画面出现的前后几秒,然后在公屏上打上“合影留念”的弹幕(说的是不是你?),仿佛一开始就是奔着这几秒去看这部电影的。
而我们创作表情包的第一步,自然也是先截取出这些全片中最精彩的片段。
要截取视频中的特定位置与时长的片段,我们可以用以下命令行实现:
ffmpeg -i {input} -ss {position} -t {duration} {output}
其中,
-ss 选项用于定位到指定的视频位置,可以是「HH:MM:SS」这种格式,也可以是「2.3」这种表示第2.3秒的格式。
-t 选项用于表示截取的视频时长,也同样支持以上2种时间格式。
例如,「吔屎啦你」这一名场面发生在上述视频片段的第11秒,持续时间约为2.3秒,那我们就可以这样子编写命令行:
ffmpeg -i as_tears_go_by.mp4 -ss 00:11 -t 2.3 as_tears_go_by-trim.gif
到位了到位了哈!可是有一个问题出现了,尽管经过截取处理,转出的GIF图像文件仍有11.6MB,作为一个表情包来讲,实在是太大了。
而相同时长的MP4视频文件大小却只有664KB,为什么两者能相差这么大呢?这其实跟二者采用的压缩算法有关,后面系列文章中会专门讲到,这里就先不展开说了。
你肯定有过这样的经历,本来和暧昧对象在微信上聊得好好的,不知怎的突然就没话题了。与其继续尬聊下去,我们更多会选择开启「斗图」模式,来缓解和过渡这一尴尬的时刻。
斗图除了考验你表情包弹药库的存量之外,表情包连发的“攻速”也很重要。
想象一下,对方的表情包如机关枪般“哒哒哒”密集地发来,而你的表情包却因为GIF格式下的文件过大,到了对方的聊天面板还要转圈下载好一阵,气势上就输了一大截。
因此,压缩表情包文件的大小,很重要。
ffprobe是FFmpeg提供的多媒体信息查看工具,我们可以先使用ffprobe来查看上一步中截取了名场面,并转换了格式后的GIF图像信息:
ffprobe as_tears_go_by-trim.gif
Input #0, gif, from 'as_tears_go_by-cut.gif':
Duration: 00:00:02.32, start: 0.000000, bitrate: 40101 kb/s
Stream #0:0: Video: gif, bgra, 1920x1080 [SAR 64:64 DAR 16:9], 25 fps, 25 tbr, 100 tbn
可以看到,由于我们是直接将视频片段转为GIF图像的,文件的尺寸大小高达1920x1080!和一般的静态图片一样,GIF格式的文件大小也是受尺寸大小影响的,尺寸越大相应的文件也就越大,但作为表情包我们往往不需要追求如此高清的效果。
因此,我们压缩工作的第一步,就是缩减GIF文件的尺寸。
可以使用以下命令行来实现:
ffmpeg -i {input} -s {WxH} {output}
-s 选项用于指定输出文件的尺寸大小,格式上是「宽度(W)x高度(H)」
例如,我们可以拿到前面的GIF图像文件,将其宽高均缩放至原先的1/6:
ffmpeg -i as_tears_go_by-cut.gif -s 320x180 as_tears_go_by-scale.gif
可以看到,经过缩放后的文件大小已减少至1.6M,约为原先大小的1/7,效果还是比较明显的。
GIF是连续的动态图像,可以视作是一张一张完整的图像按一定速率播放,然后利用人眼的视觉残留效应所形成的效果。每秒钟依次播放的图像数量叫做帧率,单位是fps(frame per second,帧每秒)。
前面我们用ffprobe工具查看后可得知,输出的GIF图像的帧率是25fps,这是由于我们是直接将视频片段转为GIF图像的,而电影中的常见帧率为24/25帧,因此输出的GIF图像也保留了相同的帧率。
帧率越高,GIF图像的动效相对就越流畅,但相应的要储存的图片帧也会更多,可能导致文件大小直线上升。通过减少帧数,牺牲一点连贯性,可以显著优化文件的大小。
可以使用以下命令行来实现:
ffmpeg -i {input} -r {fps} {output}
-r 选项用于指定帧率,支持整数和分数格式。
例如,我们可以将上一步帧率为25fps的GIF图像减少至8fps:
ffmpeg -i as_tears_go_by-scale.gif -r 8 as_tears_go_by-frameextract.gif
可以看到,经过抽帧处理后的GIF图像大小得到了进一步的压缩。
裁剪同样是为了缩减GIF图像的尺寸,只不过和单纯的缩放相比,裁剪还有去除冗余元素、突出目标主体的附加效果。
借助FFmpeg命令行工具实现对图像/视频的裁剪,我们需要用到Filter(滤镜、过滤器)模块。
滤镜模块提供了许多音视频特效处理的功能,比如crop(裁剪)、scale(缩放)、overlay(叠加)、rotate(旋转),trim(截取)等。可以说,前面几个步骤提到的功能,都有相应的滤镜可以实现。
如果要为视频类型的输入流指定使用的滤镜,需要使用-vf 选项。可指定的滤镜按输入输出流的数量和类型可分为「简单滤镜」和「复杂滤镜」两种。
所谓「简单滤镜」,指的是那种刚好只有一个输入流、一个输出流的的滤镜,且两者都是同一类型的情况:
相对的,「复杂滤镜」指的则是那些具有多个输入流/输出流,或者输出流类型与输入流类型不同的情况:
比如我们现在要实现的裁剪功能,就是一个典型的「简单滤镜」:
ffmpeg -i as_tears_go_by-frameextract.gif -vf "crop=180:180:100:0" as_tears_go_by-crop.gif
这条命令行的完整释义如下:
经过裁剪后输出的GIF图像效果如下:
FFmpeg滤镜的强大之处,在于它可以通过不同滤镜的排列组合,实现各种各样复杂的功能。
FFmpeg滤镜共包含以下3个层级:
filter -> filterchain -> filtergraph 也即 滤镜 -> 滤镜链 -> 滤镜图
多个滤镜可以串联成一条滤镜链,多条滤镜链可以组合成一个滤镜图。
我们可以基于FFmpeg的官方示例进行改造,实现“裁剪出视频的左半部分,并镜像叠加到视频的右半部分”的效果,来演示一下三者的关系,以及如何结合使用,流程图如下:
这个流程使用命令行实现如下:
ffmpeg -i as_tears_go_by-crop.gif -vf "split[main][tmp];[tmp]crop=iw/2:ih:0:0,hflip[flip];[main][flip]overlay=W/2:0" as_tears_go_by-graph.gif
是不是看着这么多的参数有点懵圈了?不要怕,这个涉及到滤镜模块的语法,我们一个一个来解释:
首先,[main][tmp][flip]是为输入输出流所打的标签,可以任意命名,打标签是可选的,为了连接其他滤镜时方便使用。
split、crop、hflip、overlay都是具体使用的滤镜,滤镜的各种参数在=号后面指定。
滤镜的参数之间用冒号:分隔,可以是纯值的形式,也可以是“键=值”的形式,还可以是二者混用的形式。
比如crop滤镜的参数也可以这样指定:crop=w=iw/2:h=ih:x=0:y=0
,iw和ih这两个变量分别指的是输入帧的宽度和高度。
同一滤镜链内的不同滤镜之间用逗号,分隔。
比如[tmp]crop=iw/2:ih:0:0,hflip[flip]
这一滤镜链中就包含crop和hflip这两个滤镜,两者之间使用逗号,分隔。
同一滤镜图内的不同滤镜链之间用分号;分隔。
比如上面的滤镜图就包含split;crop,flip;overlay
三条滤镜链,彼此之间使用用分号;分隔。
最终输出的效果如下——忍法·双头嘲讽:
嗯…看着有点诡异呢!
一个表情包之所以经久不衰,除了表情包本身很有“梗”之外,网友们富有想象力的“二次创作”,也是表情包能再次迸发出生命力的原因之一。
这种类似连环画的表情包叙事感很强,如果采用FFmpeg来创作的话,可以分为以下几步进行:
手动截图的工作需要先借助前面提到的-ss 选项,定位到视频片段指定的位置,然后再借助-vframe 选项来输出指定数量的视频帧,最后当然还要再对输出的视频帧的进行一波同样的裁剪和缩放。
具体的命令行示例如下:
ffmpeg -i as_tears_go_by.mp4 -ss 00:13 -vframes 1 -vf "crop=ih:ih:iw/4:0" -s 180x180 as_tears_go_by-screenshot.jpg
但如果你想偷一下懒,不想一张张手动去截,我们也可以借助fps滤镜实现来定时截图,然后再从输出的图片集里找符合预期的截图即可,命令行如下:
ffmpeg -i as_tears_go_by.mp4 -vf "fps=2,crop=ih:ih:iw/4:0" -s 180x180 screenshot/out%d.jpg
此命令行执行后,每过0.5秒就会生成一张JPG格式的图片,并进行一波同样的裁剪和缩放。
从中我们最终挑出以下四张截图作为素材:
添加文字的工作同样可借助滤镜功能完成,使用到的滤镜是「drawtext」滤镜。
我们先拿前面经过裁剪之后生成的GIF图像来做下实验:
ffmpeg -i as_tears_go_by-crop.gif -vf "drawtext=fontsize=30:text='吔屎啦你':fontcolor=white:x=25:y=100:fontfile=JingNanYuanMoTi/KNFONTYUANMO-2.otf" as_tears_go_by-text.gif
添加文字后的效果如下:
fontsize、fontcolor、text等从字面意义就可以知晓其作用的参数我就不再赘述了,这里需要提到的是,如果默认的字体风格不符合预期,drawtext滤镜也支持使用「fontfile」参数来指定所采用的字体文件,比如上面的otf文件。
多宫格处理的工作实际就是前面所提到的「复杂滤镜」的使用场景,我们需要整合多个输入流(此处是静态图),并拼接到一个画布上输出。
先丢出处理的命令行:
ffmpeg -i 1.jpg -i 2.jpg -i 3.jpg -i 4.jpg -filter_complex "nullsrc=size=360x360[base];[base][0:v]overlay=0:0[tmp1];[tmp1][1:v]overlay=180:0[tmp2];[tmp2][2:v]overlay=0:180[tmp3];[tmp3][3:v]overlay=180:180" -frames:v 1 output.jpg
命令行很长,但是了解过前面的滤镜语法以后,相信也不难理解,我们来一步步解释:
最终产出的效果图如下:
可能有人想说了,我干嘛要去写这么一大串晦涩难懂的命令行呢?要创作表情包,使用有可视化界面的软件操作不香吗?
诚然,可视化界面有可视化界面的优势,像前面添加文字的操作,鼠标或手指点点拖拖就可以完成了。但是别忘了,许多所谓的拥有可视化界面的软件,其只不过是在命令行工具上披一层皮而已。
换句话说,只要熟悉了FFmpeg命令行工具的使用之后,我们就完全可以自己做一个拥有可视化界面,而功能底层使用FFmpeg命令行来实现的软件。
比如在Android平台上,我们就可以通过手动将FFmpeg源码编译成so库,并抽取ffmpeg编解码工具的相关文件,借助NDK开发自行封装成一个FFmpeg命令行工具库,然后集成到我们的App中去,从而实现一个功能丰富的视频编辑工具App。(后续文章将讲到,敬请关注)。
看到这里,你还不想打开FFmpeg命令行工具实际操作一番吗?
作者:椎锋陷陈
链接:https://juejin.cn/post/7118617828428611615