FFmpeg命令行
2023-3-19|2023-3-19
骏龙
本文介绍 FFmpeg 的命令行使用。它可以【快速】地完成音视频的处理,包括剪辑、合并、压制、添加ass字幕;更利于批量生产地,它也可以方便地配合其他编程语言。
FFmpeg 官方网站提供免费下载。在下载之前,请打开命令行输入 ffmpeg,以确认本机是否已安装过 ffmpeg(因为它常常作为一个组件被安装,比如 ImageMagick)。
官方文档页:点击这里。
FFmpeg 命令行
基础语法与流程
由于是命令行操作,因此语法也是很简单的。各参数以空格分隔。
ffmpeg [gl-opt] {[input-file-opt] -i input-url} ... {[output-file-opt] output-url} ...
其中,
-i 参数表示输入参数;之后的参数是输出参数。ffmpeg 的一般工作流,是从源文件开始,依次经过分流器、解码器、编码器、混流器,最后完成输出文件。下图是官网给出的示意:
_______ ______________ | | | | | input | demuxer | encoded data | decoder | file | ---------> | packets | -----+ |_______| |______________| | v _________ | | | decoded | | frames | |_________| ________ ______________ | | | | | | | output | <-------- | encoded data | <----+ | file | muxer | packets | encoder |________| |______________|
一些基本的概念:
- 流(stream):视频文件中,一般具有视频流与音频流,有的具有字幕流。它们需要不同的解码/编码器。想要混合/分离视频与音频,就需要混流/分流器。
- 流复制(stream):如果某种数据流的内容不需要任何改动,那么可以直接跳过该数据流的解码与编码步骤。分离出该数据流后,直接等待参与混流即可。
ffmpeg 在有多个流的情况下,不会全部保留;默认只会选择同类流中质量最佳的。如果质量同样,那么选择索引号靠前的流。你可以选择手动控制流的选择,这需要额外的参数,我们下文介绍。
主要参数
下表中:
- 以
<>包裹的表示由用户具体指定;以[]包裹的表示是可选参数,可以指定也可以省略。
- 括号内的
i表示该参数用于输入流,o表示用于输出流,i/o表示均可,global表示全局参数。
- 关键字
duration,position或offset满足:[-][HH:]MM:SS[.m...]这种时间戳格式。或者以秒为单位的SS[.m...]格式。
常用的如下:
-b[:stream_specifier] (o):输出比特率。
-f <fmt> (i/o):指定fmt作为输入或输出的视频格式。一般会根据文件扩展名自动选择,但有时需要手动指定。
-i <filename> (i):指定filename作为源文件。
-y (global):文件存在时直接覆盖。
-n (global):文件存在时不覆盖并立即退出。
-bsf[:stream_specifier] <bitstream_filters> (o):设置比特流滤镜。bitstream_filters是一个逗号分隔的滤镜列表。
-stream_loop <num> (i):指定输入流的循环次数。0 表示不循环,-1 表示无限循环。
-c[:stream_specifier] <codec> (i/o):-c可写为-codec。选择一个codec,即编码器(输出时)或一个解码器(输入时),参与到stream_specifier指定的一个或多个流的编码/解码中。在输出时,<codec>可以被指定为copy,表示复制数据流。
-t <duration> (i/o):(在参数-i之前指定)工作持续duration时长。一般用于指定剪辑数据流的范围。 它与-to参数相互冲突,但本参数优先。
-to <position> (o):到position位置后,终止输出。与-t参数冲突,本参数优先级低。
-fs <limit_size> (o):输出文件大小达到limit_size后停止输出,单位是 byte.
-ss <position> (i/o):(在参数-i之前指定)从position指定的位置开始工作。注意:大多数情形下,工作起始位置是不精确的。ffmpeg会找到其前部的一个点作为真正的起始,并在结束工作后将该点与用户指定点之间的内容抛弃。然而,如果你使用了 copy 参数,这部分内容却会被保留。
-sseof <position> (i/o):类似-ss参数,只不过是从数据流末端向前寻找position。此时 0 表示数据流末。
-itsoffset <offset> (i):指定输入流以原时间戳加上offset作为其输入时间戳。
-metadata[:metadata_specifier] key=value (o):以键值对的形式设置元数据。
-frames[:stream_specifier] <num> (o):在输出num帧后停止写入。
-qscale[:stream_specifier] q (o):使用固定质量(VBR)。
stats (global):输出编码过程,是系统默认值。可以使用-nostats关闭。
-attach <filename> (o):将filename文件附加到输出文件。附件流作为文件的最后一个流,只有很少的文件类型被支持(例如字体)。
视频参数
-vframes <num>:文件的总帧数。-frames:v的别名。
-r[:steam_specifier] <fps> (i/o):文件的帧率。
-s[:steam_specifier] <size> (i/o):帧尺寸。参数size需要满足格式<width>x<height>,例如320x240。 -aspect[:steam_specifier] <asp> (o):宽高比,例如4:3。如果使用了-vcodec copy,那么指定容器的宽高比而不是视频的。
-vn (o):禁止输出视频。
-vcodec <codec> (o):设置视频编码器。-codec:v的别名。
-pass[:stream_specifier] <n>:选择当前编码数(1或者2),常用于二次编码的情况。在第一次编码中,音频输出往往被设置为 NULL,对于 Windows 与 Unix 系统分别是:
ffmpeg -i foo.mov -c:v libxvid -pass 1 -an -f rawvideo -y NUL ffmpeg -i foo.mov -c:v libxvid -pass 1 -an -f rawvideo -y /dev/null
音频参数
-aframes <num> (o):文件的总帧数。-frames:a的别名。
-ar[:stream_specifier] <freq> (i/o):采样率。默认输出等于输入。仅当输入文件为真实设备或者 raw 数据时,该参数才能用于输入过程。
-aq <q> (o):音频品质(VBR)。-q:a的别名。
-ac[:stream_specifier] <channel> (i/o):设置音频通道数。默认输出等于输入。仅当输入文件为真实设备或者 raw 数据时,该参数才能用于输入过程。
-an (o):禁止输出音频。
-acode <codec> (i/o):设置音频的解码器或编码器。-codec:a的别名。
字幕参数
-scodec <codec> (i/o):字幕解码器或编码器。codec:s的别名。
-sn (o):禁止输出字幕。
canvas_size <size>:设置字幕渲染区域的尺寸。
其他参数
以下直接在
ffmpeg 后使用,例如:ffmpeg -version。-bsfs:可用的比特流滤镜。
-h [arg]:帮助。arg 的内容可以是:decoders:可用的解码器。或特指:decoder=<name>。encoders:可用的编码器。或特指:encoder=<name>。filters:所有滤镜。或特指:filter=<name>。formats:可用的分流器与混流器。或特指分流器:demuxer=<name>,或特指混流器:muxer=<name>。
-protocols:支持的协议。
-version:版本信息。
FFmpeg 实用例子
合并视频
第一种方案:将这几个视频放在一个新文件夹内,Shift 右键运行 cmd,输入(注意:如果要保存为批处理文件,请循环变量的双写百分号。):
(for %i in (*.flv) do @echo file '%i') > mylist.txt ffmpeg -f concat -i mylist.txt -c copy output.flv
这样速度很快也没有中间文件,原则上要求文件规格相近。
另一种方案:先将这几个视频无损地转为 mpegts 文件,再通过 concat 协议合并。以常见的 H.264 视频与 aac 音频为例:
ffmpeg -i "1.flv" -c copy -bsf:v h264_mp4toannexb -f mpegts 1.ts ffmpeg -i "2.flv" -c copy -bsf:v h264_mp4toannexb -f mpegts 2.ts ffmpeg -i "concat:1.ts|2.ts" -c copy -bsf:a aac_adtstoasc "All.mp4"
这种方案是早期方案,支持更广,包括非 mpeg 容器的 mpeg 编码内容(H.264, MPEG4, MPEG2, AAC, MP3等)。不过这样会产生中间文件。
分割视频
指定视频的起始与持续时长就可以分割视频了。下例截取了视频的前 5 秒(00:05:00),注意
-t后接“截取视频段长度”而不是“截取终点时刻”:ffmpeg -i "input.mp4" -ss 00:00:00 -t 5 -c copy "output.mp4"
建议使用规范的 mp4 格式文件,否则可能出现视频无法正常混流的现象。
批量格式转换
比如,对于数据流用 mpeg 编码的一个 flv 文件,可以这样转为 mp4 文件:
ffmpeg -i "input.flv" -c copy "output.mp4"
因此一个批量转换也很容易通过 for 语句实现(
%~n 表示保留不含扩展名的文件名):for %i in (*.mp4) do ffmpeg -i "%i" -c copy "%~ni.flv"
其实我觉得播放器内置的截图可能更好用。
下例添加 png 或其他静态格式的水印,放置在距左侧 20 像素,距顶端 40 像素的地方。水印与视频的基准点都是左上角点。
ffmpeg -i input.mp4 -i wm.png -filter_complex "overlay=20:40" output.mp4
如果要放在右下角使用
overlay= main_w-overlay_w:main_h-overlay_h,参数的含义应该较好理解。如果要指定水印的大小,比如 384x216:
ffmpeg -i input.mp4 -i wm.png -filter_complex "[1:v]scale=384:216[wm];[0:v][wm]overlay=0:0" output.mp4
参数
0:v 表示第1个输入的视频流(本例即input.mp4的视频流),1:v 表示第2个输入的视频流(本例即wm.gif)。分号前的[wm]用于引用。GIF 水印
添加 gif 水印与静态图水印有一些不同之处:
- 需要将
ignore_loop参数指明为 0,表示 gif 无限循环。
- 需要用到复合过滤器
filter_complex。
- 需要过滤器的
shortest=1选项,表示至少在一个视频流循环一次后,再终止输出。如果不加该选项,输出将无法自行停止。
一个指定 50x50 大小 GIF 水印在左上角的例子:
ffmpeg -y -i input.mp4 -ignore_loop 0 -i wm.gif -filter_complex "[1:v]scale=50:50[wm];[0:v][wm]overlay=0:0:shortest=1" output.mp4
外挂字幕
将字幕作为单独的数据流(而不是混入视频流中),封装到容器内。一般对此特性有良好支持的容器是 mkv。在封装时,一般需要转为 ass 格式。
ffmpeg -i input.mp4 -i input.srt -c:v copy -c:a copy -c:s ass output.mkv
一些注意点:
- 字幕文件请用 UTF-8 编码。
- Windows 系统缺少一个字体接口,因此需要自己配置 fonts.conf 文件,放在
%FONTCONFIG_PATH%这个环境用户变量里(往往需要你自己新建)。该变量应该指向C:\Users\用户名\。
网上流传了一份 fonts.conf 文件内容(见附录),请复制后粘贴到你对应文件夹的 fonts.conf 文件中。
内嵌字幕
在播放器不支持独立字幕流的场合,需要将字幕混入视频流中(因此需要重编码)。
ffmpeg -i input.mp4 -vf subtitles=input.srt output.mp4
如果字幕以字幕流的形式位于一个视频文件中,可以直接调用:
ffmpeg -i input.mkv -vf subtitles=input.mkv output.mp4
同样,Windows 用户需要配置 fonts.conf 文件。
附录:Windows 的 fonts.conf
参考页面:该用户的 Github。
<?xml version="1.0"?> <fontconfig> <dir>C:\WINDOWS\Fonts</dir> <match target="pattern"> <test qual="any" name="family"><string>mono</string></test> <edit name="family" mode="assign"><string>monospace</string></edit> </match> <match target="pattern"> <test qual="all" name="family" compare="not_eq"><string>sans-serif</string></test> <test qual="all" name="family" compare="not_eq"><string>serif</string></test> <test qual="all" name="family" compare="not_eq"><string>monospace</string></test> <edit name="family" mode="append_last"><string>sans-serif</string></edit> </match> <alias> <family>Times</family> <prefer><family>Times New Roman</family></prefer> <default><family>serif</family></default> </alias> <alias> <family>Helvetica</family> <prefer><family>Arial</family></prefer> <default><family>sans</family></default> </alias> <alias> <family>Courier</family> <prefer><family>Courier New</family></prefer> <default><family>monospace</family></default> </alias> <alias> <family>serif</family> <prefer><family>Times New Roman</family></prefer> </alias> <alias> <family>sans</family> <prefer><family>Arial</family></prefer> </alias> <alias> <family>monospace</family> <prefer><family>Andale Mono</family></prefer> </alias> <match target="pattern"> <test name="family" compare="eq"> <string>Courier New</string> </test> <edit name="family" mode="prepend"> <string>monospace</string> </edit> </match> <match target="pattern"> <test name="family" compare="eq"> <string>Courier</string> </test> <edit name="family" mode="prepend"> <string>monospace</string> </edit> </match> </fontconfig>