1. 概述
FFmpeg是一款用于多媒体处理的自由软件工程,基于GPL许可证发布。FFmpeg提供的最核心的命令行工具是ffmpeg
,ffmpeg
命令行工具的主要特征是输出快速、高品质、文件尺寸小。“FFmpeg”中“FF”表示“Fast Forward”,“mpeg”表示“Moving Pictures Experts Group”。
FFmpeg提供如下四个命令行工具: -- ffmpeg 音视频编码器/解码器 -- ffplay 媒体播放器 -- ffprobe 显示媒体文件信息 -- ffserver 多媒体流广播服务器,使用HTTP和RTSP协议。FFmpeg 4.1版本已经删除ffserver,新的替代者还未添加进来。
FFmpeg提供如下软件开发库: -- libavcodec 多媒体编解码器库 -- libavdevice 设备库 -- libavfilter 滤镜库 -- libavformat 媒体格式库 -- libavutil software library containing various utilities -- libpostproc 后处理库 -- libswresample 音频重采样库 -- libswscale software library for media scaling
2. 命令行格式
命令行基本格式为:
ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...
格式分解如下:
ffmpeg
global_options
input1_options -i input1
input2_options -i input2
...
output1_options output1
output2_options output2
...
ffmpeg
读取任意数量的输入“文件”(可以是常规文件、管道、网络流、录制设备等,由“-i”选项指定),写入任意数量的输出“文件”。命令行中无法被解释为选项(option)的任何元素都会被当作输出文件。
每个输入或输出文件,原则上都可以包含任意数量的流。FFmpeg中流的类型有五种:视频(video)、音频(audio)、字幕(subtitle)、附加数据(attachment)、普通数据(data)。文件中流的数量和(或)流类型种数的极限值由文件封装格式决定。选择哪一路输入的哪一路流输出到哪一路输出,这个选择过程既可以由FFmpeg自动完成,也可以通过“-map”选项指定(后续“Stream selection”章节会深入描述)。
注:关于附加数据(attachment)和普通数据(data)的说明如下:
Attachments could be liner notes, related images, metadata files, fonts, etc. Data tracks would be for things like timecode, navigation items, cmml, streaming tracks. 参考资料[3] “What are the the data and attachment stream type?”
命令行中的输入文件及输入文件中的流都可以通过对应的索引引用,文件、流的索引都是从0开始。例如,2:3表示第3个输入文件中的第4个流。(后续“Stream specifiers”章节会详细描述)。
一个通用规则是:输入/输出选项(options)作用于跟随此选项后的第一个文件。因此,顺序很重要,并且可以在命令行中多次指定同一选项。每个选项仅作用于离此选项最近的下一输入或输出文件。全局选项不受此规则限制。
不要把输入文件和输出文件混在一起———应该先将输入文件写完,再写输出文件。也不要把不同文件的选项混在一起,各选项仅对其下一输入或输出文件有效,一个选项不能跨越一个文件传递到后续文件。
举几个命令行例子:
- 设置输出文件码率为64kbit/s:
ffmpeg -i input.avi -b:v 64k -bufsize 64k output.avi
其中“-b:v 64k”和“-bufsize 64k”是输出选项。 - 强制输入文件帧率(仅对raw格式有效)是1fps,输出文件帧率为24fps:
ffmpeg -r 1 -i input.m2v -r 24 output.avi
其中“-r 1”是输入选项,“-r 24”是输出选项。 - 转封装:将avi格式转为mp4格式,并将视频缩放为vga分辨率
ffmpeg -y -i video.avi -s vga video.mp4
其中“-y”是全局选项,“-s vga”是输出选项。
3. 转码过程
代码语言:javascript复制 _______ ______________
| | | |
| input | demuxer | encoded data | decoder
| file | ---------> | packets | -----
|_______| |______________| |
v
_________
| |
| decoded |
| frames |
|_________|
________ ______________ |
| | | | |
| output | <-------- | encoded data | <----
| file | muxer | packets | encoder
|________| |______________|
ffmpeg
调用libavformat库(包含解复用器demuxer),从输入文件中读取到包含编码数据的包(packet)。如果有多个输入文件,ffmpeg
尝试追踪多个有效输入流的最小时间戳(timestamp),用这种方式实现多个输入文件的同步。
然后编码包(packet)被传递到解码器(decoder),解码器解码后生成原始帧(frame),原始帧可以被滤镜(filter)处理(图中未画滤镜),经滤镜处理后的帧送给编码器,编码器将之编码后输出编码包。最终,由复用器(muxex)将编码包写入特定封装格式的输出文件。
4. 滤镜
在多媒体处理中,术语滤镜(filter)指的是修改未编码的原始音视频数据帧的一种软件工具。滤镜分为音频滤镜和视频滤镜。FFmpeg提供了很多内置滤镜,可以用很多方式将这些滤镜组合使用。通过一些复杂指令,可以将解码后的帧从一个滤镜引向另一个滤镜。这简化了媒体处理,因为有损编解码器对媒体流进行多次解码和编码操作会降低总体质量,而引入滤镜后,不需要多次解码编码操作,相关处理可以使用多个滤镜完成,而滤镜处理的是原始数据,不会造成数据损伤。
4.1 滤镜的使用
FFmpeg的libavfilter库提供了滤镜API,支持多路输入和多路输出。
滤镜(filter)的语法为:
[input_link_lable1][input_link_lable2]... filter_name=parameters [output_link_lable1][output_link_lable12]...
上述语法中,输入输出都有连接标号(link lable),连接符号是可选项,输入连接标号表示滤镜的输入,输出连接标号表示滤镜的输出。连接标号通常用在滤镜图中,通常前一个滤镜的输出标号会作为后一个滤镜的输入标号,通过同名的标号将滤镜及滤镜链连接起来。连接标号的用法参考4.3.2节示例。
示例1:
ffplay -f lavfi -i testsrc -vf transpose=1
“-vf”(同“-filter:v”)选项表示使用视频滤镜,“transpose=1”是滤镜,此行命令表示使用transpose视频滤镜产生一个顺时针旋转90度的测试图案
示例2:
ffmpeg -i input.mp3 -af atempo=0.8 output.mp3
“-af”(同“-filter:a”)选项表示使用音频滤镜,“atempo=0.8”是滤镜,此行命令表示使用atempo音频滤镜将输入音频速率降低到80%后写入输出文件
注意:有些滤镜只会修改帧属性而不会修改帧内容。例如,fps滤镜,setpts滤镜等。
4.2 滤镜链的使用
滤镜链(filterchain)是以逗号分隔的滤镜(filter)序列,语法如下:
filter1,fiter2,filter3,...,filterN-2,filterN-1,filterN
滤镜链中如果有空格,需要将滤镜链用双引号括起来,因为命令行中空格是分隔参数用的。
示例1:
ffmpeg -i input.mpg -vf hqdn3d,pad=2*iw output.mp4
“hqdn3d,pad=2iw”是filterchain,第一个filter是“hqdn3d”(降噪);第二个filter是“pad=2iw”(将图像宽度填充到输入宽度的2倍)。此行命令表示,将输入视频经降噪处理后,再填充视频宽度为输入宽度的2倍。
4.3 滤镜图的使用
滤镜图(filtergraph)通常是以分号分隔的滤镜链(filterchain)序列。滤镜图分为简单滤镜图和复杂滤镜图。
滤镜图(filtergraph)的语法如下:
filter1;fiter2;filter3;...;filterN-2;filterN-1;filterN
4.3.1 简单滤镜图
简单滤镜图(filtergraph)只能处理单路输入流和单路输出流,而且要求输入和输出具有相同的流类型。 简单滤镜图由-filter选项指定。简单滤镜图示意图如下:
代码语言:javascript复制 _______ _____________________ ________
| | | | | |
| input | ---> | simple filter graph | ---> | output |
|_______| |_____________________| |________|
4.3.2 复杂滤镜图
复杂滤镜图(filtergraph)用于简单滤镜图处理不了的场合。比如,多路输入流和(或)多路输出流,或者输出流与输入流类型不同。 有些特殊的滤镜(filter)本身就属于复杂滤镜图,用-filter_complex选项或-lavfi选项指定,如overlay滤镜和amix滤镜就是复杂滤镜图。overlay滤镜有两个视频输入和一个视频输出,将两个输入视频混合在一起。而amix滤镜则是将两个输入音频混合在一起。 复杂滤镜图(filtergraph)示意图如下:
代码语言:javascript复制 _________
| |
| input 0 | __________
|_________| | |
_________ /| output 0 |
| | / |__________|
_________ | complex | /
| | | |/
| input 1 |---->| filter |
|_________| | | __________
/| graph | | |
/ | | | output 1 |
_________ / |_________| |__________|
| | /
| input 2 |/
|_________|
示例1:
ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
上例中"split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2"是复杂滤镜图,由三个滤镜链构成(分号分隔),第二个滤镜链“[tmp] crop=iw:ih/2:0:0, vflip [flip]”由两个滤镜构成(逗号分隔)。第一个滤镜链中:滤镜split产生两个输出[main]和[tmp];第二个滤镜链中:[tmp]作为crop滤镜的输入,[flip]作为vflip滤镜的输出,crop滤镜输出连接到vflip滤镜的输入;第三个滤镜链中:[main]和[flip]作为overlay滤镜的输入。整行命令实现的功能是:将输入分隔为两路,其中一路经过裁剪和垂直翻转后,再与另一路混合,生成输出文件。示意图如下所示:
[main]
input --> split ---------------------> overlay --> output
| ^
|[tmp] [flip]|
-----> crop --> vflip -------
4.3.3 滤镜图中的连接标号
在滤镜图中可以使用连接标号(link lable),连接标号表示特定滤镜/滤镜链的输入或输出,参4.1节。
例如,我们想要把一个经过降噪处理后的输出文件与输入原文件进行比较,如果不使用带连接标号的滤镜图,我们需要至少两条命令:
ffmpeg -i input.mpg -vf hqdn3d,pad=2*iw output.mp4
ffmpeg -i output.mp4 -i input.mpg -filter_complex overlay=w compare.mp4
如果使用带有连接标号的滤镜图,则一条命令就可以了:
ffplay -i i.mpg -vf split[a][b];[a]pad=2*iw[A];[b]hqdn3d[B];[A][B]overlay=w
4.4 滤镜使用总结
滤镜(广义)通常以滤镜链(filterchain, 以逗号分隔的滤镜序列)和滤镜图(filtergraph, 以分号分隔的滤镜序列)的形式使用。滤镜链由滤镜构成,滤镜图由滤镜链构成,这样可以提供复杂多样的组合方式以应对不同的应用场景。 滤镜(狭义)是滤镜链的简单特例,滤镜链是滤镜图的简单特例。注意这里滤镜(狭义)、滤镜链、滤镜图之间不是继承的关系,而是组合的关系,比如,一个滤镜图可以只包含一个滤镜链,而一个滤镜链也可以只包含一个滤镜,这种特例情况下,一个滤镜图仅由单个滤镜构成。FFmpeg的命令行中,滤镜(广义)的出现形式有滤镜(狭义)、滤镜链、滤镜图三种形式,但滤镜(狭义)和滤镜链可以看作是特殊的滤镜图,因此,为了简便,FFmpeg的命令行中滤镜相关选项,只针对滤镜图(filtergraph)概念,分为如下两类: 针对简单滤镜图的选项:“-vf”等同“-filter:v”,“-af”等同“-filter:a” 针对复杂滤镜图的选项:“-lavfi”等价“-filter_complex”
5. 流拷贝
“-codec copy”可使能流拷贝(stream copy)模式。流拷贝直接将输入流拷贝到输出,仅涉及解复用和复用,不涉及解码和编码,因此也不支持滤镜操作。流拷贝对于修改容器格式或容器级别元数据非常有用。因为不涉及编解码操作,整个过程会非常快。示意图如下所示:
代码语言:javascript复制 _______ ______________ ________
| | | | | |
| input | demuxer | encoded data | muxer | output |
| file | ---------> | packets | -------> | file |
|_______| |______________| |________|
6. 流选择
有些容器,如AVI、MP4等,可以包含多种不同类型的流。FFmpeg可以识别5种流类型:音频(audio, a),视频(video, v),字幕(subtitle, s),附加数据(attachment, t)和普通数据(data, d)。 流选择(stream selection)是从输入文件中选定某些流进行处理。流选择有两种模式,1) 使用-map选项手动指定要选择的流;2) 无-map选项时由FFmpeg根据相应规则自动选择流。
6.1 流选择自动模式
自动选择模式下,每种类型的流只选择一路,规则如下: 音频流:选择具有最多通道的流,若多个音频流通道数相同且通道数最多,则选第一个 视频流:选择具有最高分辨率的流,若多个视频流分辨率相同且是最高分辨率,则选第一个 字幕流:选择第一个字幕流。注意:字幕流有文本字幕流和图形字幕流,输出格式默认的字幕编码器仅处理其支持的字幕类型
6.2 流选择手动模式
手动选择模式下,要选定的流由-map选项后的流指定符(stream specifer)指定。stream_specifier语法如下:
[-]file_index:stream_type[:stream_index]
带-
表示排除此流,不带-
表示选中此流。文件序号file_index和流序号stream_index都是从0开始计数。
几个特殊的stream_specifier如下: --map 0 选择所有类型的所有流。 --map i:v 选择文件i中所有的视频流,i:a、i:s等同理。 --map -vn 排除所有视频流,-an、-sn等同理。
示例:
假设ffmpeg命令行如下:
ffmpeg -i file1 -i file2 select_streams output
其中有两个输入文件file1和file2,选择的流位于select_streams
file1的流组成与对应的stream_specifier如下:
file streams stream_specifier
1st video 0:v:0
2nd video 0:v:1
1st audio 0:a:0
2nd audio 0:a:1
1st subtitle 0:s:0
2nd subtitle 0:s:1
3rd subtitle 0:s:2
file2的流组成与对应的stream_specifier如下:
代码语言:javascript复制file streams stream_specifier
1st video 1:v:0
1st audio 1:a:0
1st subtitle 1:s:0
select_streams各种示例说明如下:
-map 0 -map 1
选择两个文件的所有流
-map 0:s:2 -map 1:v:0 -map 1:a:0
选择file1的3rd字幕流,file2的1st视频流和file2的1st音频流
-map 0 -map 1:s:0 -an
选择file1除音频外的所有流和file2的1st字幕流
-map 0 -map 1 -map -0:v:0 -map -0:a:1
选择除file1的1st视频流和2nd音频流外的所有流,选择file2中的所有流
6.3 stream_specifier
有些选项(比如设置码率、设置编解码器)是针对流的。一个选项具体作用于哪些流,由stream_specifier指定。
stream_specifier附在选项后面,由“:”分隔。例如:-codec:a:1 ac3
中a:1
就是stream_specifier。
stream_specifier可以匹配一路流或多路流,对应的选项可作用于stream_specifier匹配的这些流。一个空的stream_specifier将匹配所有的流。例如:-b:a 128k
匹配所有音频流,而-codec copy
或-codec: copy
则匹配所有流。
除上一节所述-map选项外,stream_specifier还可用在很多其他选项中,形式有如下几种:
代码语言:javascript复制specifer形式 描述
stream_index 选择索引为stream_index的流
stream_type[:stream_index] 选择类型为stream_type索引为stream_index的流
p:program_id[:stream_index] 选择节目program_id中索引为stream_index的流
stream_id 选择指定ID的流
例如,使用-b选项设置音频流和视频流的码率:
ffmpeg -i input.mpg -b:a 128k -b:v 1500k output.mp4
6.4 扩展1:复杂滤镜图中的流选择
如果某个复杂filtergraph中的输出流未携带标号,则这些流将被添加到第一个输出文件中。如果封装器格式不支持某种流类型,将会导致致命错误。 如果未使用-map选项,包含这些复杂filtergraph输出流将导致不会对这些流类型启用自动选择。 如果使用了-map选项,除-map选定的流之外,这些filtergraph输出流也会被包含进来。
复杂filtergraph的输出流若带标号,则标号必须被映射一次,且只能被映射一次。
假设有三个输入文件用于示例,其流组成成分如下:
代码语言:javascript复制input file 'A.avi'
stream 0: video 640x360
stream 1: audio 2 channels
input file 'B.mp4'
stream 0: video 1920x1080
stream 1: audio 2 channels
stream 2: subtitles (text)
stream 3: audio 5.1 channels
stream 4: subtitles (text)
input file 'C.mkv'
stream 0: video 1280x720
stream 1: audio 2 channels
stream 2: subtitles (image)
示例1:无标号filtergraph的流选择
ffmpeg -i A.avi -i C.mkv -i B.mp4 -filter_complex "overlay" out1.mp4 out2.srt
-filter_complex选项指定了一个复杂filtergraph,此filtergraph由单个视频滤镜overlay构成。overlay滤镜需要两个视频输入,但此处并未为overlay滤镜指定输入,因此A.avi和C.mkv中头两个有效视频流会被作为overlay滤镜的输入。overlay滤镜输出无标号,因此overlay滤镜的输出会被写入第一个输出文件out1.mp4中。
本来自动选择模式会选中B.mp4中的“stream 0”视频流(最高分辨率真)和B.mp4中的“stream 3”音频流(最多通道数)。但overlay滤镜输出流是视频流,因此,不会对视频流进行自动选择,即不会选择B.mp4中的“stream 0”。
不会选中任何字幕流,因为MP4封装格式未注册默认字幕编码器,用户也未指定字幕编码器,无编码器可用所以不会选择字幕流。
第二个输出文件out2.srt,仅接受文本类型的字幕流。所以,就算C.mkv中的“stream 2"是第一个被找到的字幕流,也会因类型不符合被忽略掉。B.mp4中的“stream 2”会被选中,因为它才是第一个文本字幕流。
示例2:带标号filtergraph的流选择
代码语言:javascript复制ffmpeg -i A.avi -i B.mp4 -i C.mkv -filter_complex "[1:v]hue=s=0[outv];overlay;aresample"
-map '[outv]' -an out1.mp4
out2.mkv
-map '[outv]' -map 1:a:0 out3.mkv
上述命令会执行失败,因为filtergraph的输出标号[outv]被映射了两次。此命令不会生成任何输出文件。
代码语言:javascript复制ffmpeg -i A.avi -i B.mp4 -i C.mkv -filter_complex "[1:v]hue=s=0[outv];overlay;aresample"
-an out1.mp4
out2.mkv
-map 1:a:0 out3.mkv
上述命令也会执行失败,因为hue滤镜有一个输出标号[outv],但此标号未作任何映射。
正确的命令应该写成下面这样:
代码语言:javascript复制ffmpeg -i A.avi -i B.mp4 -i C.mkv -filter_complex "[1:v]hue=s=0,split=2[outv1][outv2];overlay;aresample"
-map '[outv1]' -an out1.mp4
out2.mkv
-map '[outv2]' -map 1:a:0 out3.mkv
“[1:v]”表示B.mp4中的视频流,B.mp4中的视频流被发送到hub滤镜,hub滤镜的输出被split滤镜拷贝了一份,生成两份输出,两份输出用标号[outv1]和[outv2]表示。
overlay滤镜需要两个视频输入,使用头两个未使用的视频流作输入,即A.avi和C.mkv中的视频流。overlay滤镜输出未带标号,所以overlay滤镜输出被发送到第一个输出文件out1.mp4,有没有-map选项对此无影响。
aresample滤镜使用第一个未使用的音频流(A.avi中的“stream 1”)作为输入。aresample滤镜输出也未带标号,所以avresample滤镜输出也被映射到第一个输出文件out1.mp4。-an选项仅仅抑制了音频流的自动或手动流选择,而不会抑制filtergraph的输出。所以,out1.mp4有三个输入流:1)overlay滤镜输出、2)aresample滤镜输出和3)标号outv1,B.mp4中1)2)排序应在3)之前。
映射到out2.mkv的视频、音频和字幕流由自动选择模式选定。
out3.mkv由hue滤镜输出和B.mp4中的“stream 1”构成。
6.5 扩展2:流处理
流处理(stream handling)和流选择是互不影响的(字幕例外)。流处理通过-codec选项设置,-codec选项针对输出文件中的流。FFmpeg对-codec选项的处理是在流选择(stream selection)过程之后的,因此-codec选项(流处理)不会影响流选择。如果某类型的流未指定-codec选项,将会使用输出文件muxer注册的默认编码器。
上述规则不适用于字幕。如果一个输出文件指定了字幕编码器,那么找到的第一个字幕流(文本字幕或图形字幕)总会被包含进来。FFmpeg不会检查编码器是否能转换选定的流或已转换的流能否被输出格式接受。这通常也适用:当用户手动设置编码器时,流选择过程不能检查编码流是否可以复用到输出文件中。如果编码流不能复用到输出文件,FFmpeg会终止,所有的输出文件处理会失败。
7. 参考资料
[1] FFmpeg Basics [2] ffmpeg.html,http://ffmpeg.org/ffmpeg.html [3] ffmpeg-all.html, http://ffmpeg.org/ffmpeg-all.html [4] What are the the data and attachment stream type?, https://ffmpeg.org/pipermail/ffmpeg-user/2015-June/027333.html [5] Solutions to some ffmpeg errors and messages
8. 修改记录
2018-12-15 V1.0 首次整理 2019-02-15 V1.1 完善stream_specifier章节,补充选项章节与示例章节 2019-02-16 V1.1 增加视频截图命令行示例 2019-02-19 V1.2 整理完善滤镜章节 2019-02-23 V1.3 命令行选项与命令行示例拆分出去单独成文