FFmpeg 使用基础(音视频开发入门)
1. 概述
FFmpeg 是一款用于多媒体处理的自由软件工程,基于 GPL 许可证发布。FFmpeg 提供的最核心的命令行工具是 “ffmpeg”,“ffmpeg” 命令行工具的主要特征是输出过程快、输出品质高、输出文件小。“FFmpeg” 中 “FF” 表示 “Fast Forward”,“mpeg” 表示 “Moving Pictures Experts Group”。
FFmpeg 提供如下四个命令行工具:
ffmpeg 音视频编码器/解码器
fplay 媒体播放器
ffprobe 显示媒体文件信息
ffserver 多媒体流广播服务器,使用 HTTP 和 RTSP 协议。
FFmpeg 提供如下软件开发库:
libavcodec 多媒体编解码器库
libavdevice 设备库
libavfilter 滤镜库
libavformat 媒体格式库
libavutil 实用工具库
libpostproc 后处理库
libswresample 音频重采样库
libswscale 媒体缩放库
2. 命令行格式
命令行基本格式为:
格式分解如下:
“ffmpeg” 读取任意数量的输入 “文件”(可以是常规文件、管道、网络流、录制设备等,由 “-i” 选项指定),写入任意数量的输出 “文件”。命令行中无法被解释为选项(option)的任何元素都会被当作输出文件。
每个输入或输出文件,原则上都可以包含任意数量的流。FFmpeg 中流的类型有五种:视频(video)、音频(audio)、字幕(subtitle)、附加数据(attachment)、普通数据(data)。文件中流的数量和(或)流类型种数的极限值由文件封装格式决定。选择哪一路输入文件的哪一路流输出到哪一路输出,这个选择过程既可以由 FFmpeg 自动完成,也可以通过 “-map” 选项手动指定(后续 “Stream selection” 章节会深入描述)。
命令行中的输入文件及输入文件中的流都可以通过对应的索引引用,文件、流的索引都是从 0 开始。例如,2:4 表示第 3 个输入文件中的第 5 个流。(后面 6.3 节 “stream specifier” 章节会详细介绍)。
一个通用规则是:输入/输出选项(options)作用于跟随此选项后的第一个文件。因此,顺序很重要,并且可以在命令行中多次指定同一选项。每个选项仅作用于离此选项最近的下一输入或输出文件。全局选项不受此规则限制。
不要把输入文件和输出文件混在一起———应该先将输入文件写完,再写输出文件。也不要把不同文件的选项混在一起,各选项仅对其下一输入或输出文件有效,一个选项不能跨越一个文件传递到后续文件。
举几个命令行例子:
■ 设置输出文件码率为 64 kbit/s:
■ 强制输入文件帧率(仅对 raw 格式有效)是 1 fps,输出文件帧率为 24 fps:
■ 转封装:将 avi 格式转为 mp4 格式,并将视频缩放为 vga 分辨率:
其中 “-y” 是全局选项,“-s vga” 是输出选项。
如果觉得学习资料难找的话,可以添加小编的 C/C++交流群:960994558 学习资料已经共享在群里了,期待你的加入~
3. 转码过程
“ffmpeg” 调用 libavformat 库(包含解复用器 demuxer),从输入文件中读取到包含编码数据的包(packet)。如果有多个输入文件,“ffmpeg” 尝试追踪多个有效输入流的最小时间戳(timestamp),用这种方式实现多个输入文件的同步。
然后编码包(packet)被传递到解码器(decoder),解码器解码后生成原始帧(frame),原始帧可以被滤镜(filter)处理(图中未画滤镜),经滤镜处理后的帧送给编码器,编码器将之编码后输出编码包。最终,由复用器(muxex)将编码包写入特定封装格式的输出文件。
4. 滤镜
在多媒体处理中,术语 滤镜(filter) 指的是修改未编码的原始音视频数据帧的一种软件工具。滤镜分为音频滤镜和视频滤镜。FFmpeg 提供了很多内置滤镜,可以用很多方式将这些滤镜组合使用。通过一些复杂指令,可以将解码后的帧从一个滤镜引向另一个滤镜。这简化了媒体处理,因为有损编解码器对媒体流进行多次解码和编码操作会降低总体质量,而引入滤镜后,不需要多次解码编码操作,相关处理可以使用多个滤镜完成,而滤镜处理的是原始数据,不会造成数据损伤。
滤镜的使用
FFmpeg 的 libavfilter 库提供了滤镜 API,支持多路输入和多路输出。
滤镜(filter)的语法为:
上述语法中,输入输出都有连接标号(link lable),连接符号是可选项,输入连接标号表示滤镜的输入,输出连接标号表示滤镜的输出。连接标号通常用在滤镜图中,通常前一个滤镜的输出标号会作为后一个滤镜的输入标号,通过同名的标号将滤镜及滤镜链连接起来。连接标号的用法参考 4.3.2 节示例。
示例 1:
“-vf”(同“-filter:v”)选项表示使用视频滤镜,“transpose=1” 是滤镜名称及参数,此行命令表示使用 transpose 视频滤镜产生一个顺时针旋转 90 度的测试图案
示例 2:
“-af”(同“-filter:a”)选项表示使用音频滤镜,“atempo=0.8” 是滤镜名称及参数,此行命令表示使用 atempo 音频滤镜将输入音频速率降低到 80% 后写入输出文件
注意:有些滤镜只会修改帧属性而不会修改帧内容。例如,fps 滤镜,setpts 滤镜等。
滤镜链的使用
滤镜链(filterchain) 是以逗号分隔的滤镜(filter)序列,语法如下:
滤镜链中如果有空格,需要将滤镜链用双引号括起来,因为命令行中空格用于分隔参数。
示例 1:
“hqdn3d,pad=2*iw” 是 filterchain,此 filterchain 中第一个 filter 是 “hqdn3d”(降噪);第二个 filter 是 “pad=2*iw”(将图像宽度填充到输入宽度的 2 倍)。此行命令表示,将输入视频经降噪处理后,再填充视频宽度为输入宽度的 2 倍。
滤镜图的使用
滤镜图(filtergraph) 通常是以分号分隔的滤镜链(filterchain)序列。滤镜图分为简单滤镜图和复杂滤镜图。
滤镜图(filtergraph)的语法如下:
简单滤镜图
简单滤镜图只能处理单路输入流和单路输出流,而且要求输入和输出具有相同的流类型。
简单滤镜图由 “-filter” 选项指定。简单滤镜图示意图如下:
复杂滤镜图
复杂滤镜图用于简单滤镜图处理不了的场合。比如,多路输入流和(或)多路输出流,或者输出流与输入流类型不同。
有些特殊的滤镜(filter)本身就属于复杂滤镜图,用 “-filter_complex” 选项或 “-lavfi” 选项指定,如 overlay 滤镜和 amix 滤镜就是复杂滤镜图。overlay 滤镜有两个视频输入和一个视频输出,将两个输入视频混合在一起。而 amix 滤镜则是将两个输入音频混合在一起。
复杂滤镜图示意图如下:
■ 示例 1:
上例中 “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 滤镜的输入。整行命令实现的功能是:将输入分隔为两路,其中一路经过裁剪和垂直翻转后,再与另一路混合,生成输出文件。示意图如下所示:
滤镜图中的连接标号
在滤镜图中可以使用连接标号(link lable),连接标号表示特定滤镜/滤镜链的输入或输出
例如,我们想要把一个经过降噪处理后的输出文件与输入原文件进行比较,如果不使用带连接标号的滤镜图,我们需要至少两条命令:
如果使用带有连接标号的滤镜图,则一条命令就可以了:
滤镜使用总结
滤镜(广义)通常以滤镜链(filterchain, 以逗号分隔的滤镜序列)和滤镜图(filtergraph, 以分号分隔的滤镜序列)的形式使用。滤镜链由滤镜构成,滤镜图由滤镜链构成,这样可以提供复杂多样的组合方式以应对不同的应用场景。
滤镜(狭义)是滤镜链的简单特例,滤镜链是滤镜图的简单特例。比如,一个滤镜图可以只包含一个滤镜链,而一个滤镜链也可以只包含一个滤镜,这种特例情况下,一个滤镜图仅由单个滤镜构成。
FFmpeg 的命令行中,滤镜(广义)的出现形式有滤镜(狭义)、滤镜链、滤镜图三种形式,但滤镜(狭义)和滤镜链可以看作是特殊的滤镜图,因此,为了简便,FFmpeg 的命令行中滤镜相关选项,只针对滤镜图(filtergraph)概念,分为如下两类:
针对简单滤镜图的选项:“-vf” 等同 “-filter:v”,“-af” 等同 “-filter:a”
针对复杂滤镜图的选项:“-lavfi” 等价于 “-filter_complex”
流拷贝
“-codec copy” 选项可以使能流拷贝(stream copy)模式。流拷贝直接将输入流拷贝到输出,仅涉及解复用和复用,不涉及解码和编码,因此也不支持滤镜操作。流拷贝对于修改容器格式或容器级别元数据非常有用。因为不涉及编解码操作,整个过程会非常快。
流拷贝示意图如下所示:
流选择
有些容器,如 AVI、MP4 等,可以包含多种不同类型的流。FFmpeg 可以识别 5 种流类型:音频(audio, a),视频(video, v),字幕(subtitle, s),附加数据(attachment, t)和普通数据(data, d)。
流选择(stream selection) 是从输入文件中选定某些流进行处理。流选择有两种模式,1) 使用 “-map” 选项手动指定要选择的流;2) 无 “-map” 选项时由 FFmpeg 根据相应规则自动选择流。
流选择自动模式
自动选择模式下,每种类型的流只选择一路,规则如下:
音频流:选择具有最多通道的流,若多个音频流通道数相同且通道数最多,则选第一个
视频流:选择具有最高分辨率的流,若多个视频流分辨率相同且是最高分辨率,则选第一个
字幕流:选择第一个字幕流。注意:字幕流有文本字幕流和图形字幕流,输出格式默认的字幕编码器仅处理其支持的字幕类型
流选择手动模式
手动选择模式下,要选定的流由 “-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 如下:
select_streams 各种示例说明如下:
-map 0 -map 1
选择两个文件的所有流
选择 file1 的 3rd 字幕流,file2 的 1st 视频流和 file2 的 1st 音频流
选择 file1 除音频外的所有流和 file2 的 1st 字幕流
选择除 file1 的 1st 视频流和 2nd 音频流外的所有流,选择 file2 中的所有流
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 还可用在很多其他选项中,形式有如下几种:
例如,使用 “-b” 选项设置音频流和视频流的码率:
■ 示例 1:-codec 选项和 -map 选项联合使用中的 stream_specifier
上述命令实现:将输入文件中所有视频流、所有音频流、第 1 路字幕流和第 4 路字幕流拷贝到输出文件中。-c:v 等同于 -codec:v 或 -vcodec,-c:a 等同于 -codec:a 或 -acodec,-c:s 等同于 -codec:s 或 -scodec。
注意:字幕流选项 “-c:s:0 copy -map 0:s:0 -c:s:1 copy -map 0:s:3” 中 -map 参数针对的是源文件中的流,而编码器选项 -c:s:0 针对的则是 -map 选项选中的流,因为 -map 选项总共选了两路字幕流,所以编码器选项只能出现 -c:s:0 和 -c:s:1,而不能出现 -c:s:2。如下命令都是错误的:
如下命令则是正确的:
复杂滤镜图中的流选择
如果某个复杂 filtergraph 中的输出流未携带标号,则这些流将被添加到第一个输出文件中。如果封装器格式不支持这些流中的某种流类型,将会导致致命错误。
如果未使用 “-map” 选项,包含这些复杂 filtergraph 输出流将导致不会对这些类型的流启用自动选择。
如果使用了 “-map” 选项,除 “-map” 选定的流之外,这些 filtergraph 输出流也会被包含进来。
复杂 filtergraph 的输出流若带标号,则标号必须被映射一次,且只能被映射一次。
假设有三个输入文件用于示例,其流组成成分如下:
■ 示例 1:无标号 filtergraph 的流选择
“-filter_complex” 选项指定了一个复杂 filtergraph,此 filtergraph 由单个视频滤镜 overlay 构成。overlay 滤镜需要两个视频输入,但此处并未为 overlay 滤镜指定输入,因此输入中的头两个有效视频流(A.avi 中的 stream 0 和 C.mkv 中的 stream0)会被作为 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 的流选择
上述命令会执行失败,因为 filtergraph 的输出标号 [outv] 被映射了两次。此命令不会生成任何输出文件。
上述命令也会执行失败,因为 hue 滤镜有一个输出标号 [outv],但此标号未作任何映射。
正确的命令应该写成下面这样:
“[1:v]” 表示 B.mp4 中的视频流,B.mp4 中的视频流被发送到 hub 滤镜,hub 滤镜的输出被 split 滤镜拷贝了一份,生成两份输出,两份输出用标号 [outv1] 和 [outv2] 表示。
overlay 滤镜需要两个视频输入,但滤镜输入未带标号,所以使用头两个未使用的视频流作输入,即 A.avi 和 C.mkv 中的视频流(B.mp4 中的视频流已被 hub 滤镜使用)。overlay 滤镜输出未带标号,所以 overlay 滤镜输出被发送到第一个输出文件 out1.mp4,有没有 “-map” 选项对此无影响。
aresample 滤镜需要一个音频输入,但滤镜输入未带标号,因此使用第一个未使用的音频流(A.avi 中的 “stream 1”)作为输入。aresample 滤镜输出也未带标号,所以 avresample 滤镜输出也被映射到第一个输出文件 out1.mp4。“-an” 选项仅仅抑制了音频流的自动或手动流选择,而不会抑制 filtergraph 的输出,所以 avresample 滤镜输出的音频流会成功输出到 out1.mp4 中。
所以,out1.mp4 有三个输入流:1) overlay 滤镜输出、2) aresample 滤镜输出 和 3) 标号 outv1,out1.mp4 中 1) 2) 排序应在 3) 之前。
映射到 out2.mkv 的视频、音频和字幕流由自动选择模式选定。
out3.mkv 由 hue 滤镜输出和 B.mp4 中的 “stream 1” 构成。
流处理
流处理(stream handling)和流选择是互不影响的(字幕例外)。流处理通过 “-codec” 选项设置,“-codec” 选项针对输出文件中的流。FFmpeg 对 “-codec” 选项的处理是在流选择(stream selection)过程之后的,因此 -codec 选项(流处理)不会影响流选择。如果某类型的流未指定 “-codec”选项,将会使用输出文件 muxer 注册的默认编码器。
上述规则不适用于字幕。如果一个输出文件指定了字幕编码器,那么找到的第一个字幕流(文本字幕或图形字幕)总会被包含进来。FFmpeg 不会检查编码器是否能转换选定的流或已转换的流能否被输出格式接受。这通常也适用:当用户手动设置编码器时,流选择过程不能检查编码流是否可以复用到输出文件中。如果编码流不能复用到输出文件,FFmpeg 会终止,所有的输出文件处理会失败。
以上有不足的地方欢迎指出讨论,觉得不错的朋友希望能得到您的转发支持,同时可以持续关注我,每天分享 Linux C/C++后台开发干货内容!
评论