介绍

FFmpeg 是一个开源多媒体编解码器库,并提供了命令行前端,其一大用途就是进行音频或视频的编码及格式转换。许多软件都使用了 FFmpeg 来进行音频和视频的编解码,例如MPlayer。

FFmpeg 作为一个遵循 GNU 宽通用公共许可证(简称 LGPL)的软件,任何人都可以获取它的代码并放入自己的程序中,但是有一个限制,那就是这样的程序必须也采用 LGPL ,也意味着必须开放源代码。

但是一些软件并没有这么做,例如暴风影音、QQ 影音、格式工厂等等。

市面上能见到的大多数转码软件都是拿着 FFmpeg 的核心,套了一层自己的皮而已。

那为何不用最纯正的 FFmpeg 呢!不但有着最大的定制性,还有着最轻量的前端。命令行的操作界面,也能够写入脚本中,高效的完成批量任务。

 

安装

CentOS

yum install ffmpeg 

 

Ubuntu

apt-get install ffmpeg

 

MacOS

#安装HomeBrew,已经安装的可以跳过
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 
#通过HomeBrew安装FFmpeg
brew install ffmpeg 

 

Windows

下载地址:http://ffmpeg.zeranoe.com/builds/,下载完成后解压运行ff-prompt.bat。

解压出来的bin文件夹里面的三个exe文件就是接下来要用到的FFmpeg了。

 

媒体文件的结构

一个媒体文件并不像许多人想象的那样,是将媒体内容编码起来直接作为文件的。实际上,它通常是由多个不同种类的媒体流( Stream )组成,再以特定的封装格式封装起来的。

比较常见的媒体流就是视频流跟音频流了,顾名思义,视频流存储的就是视频信息,音频流存储的就是音频信息。一个视频流或者音频流的内容,就是以特定的编码格式所存储的视频或音频信息。

一个文件的里面的媒体流所采用的编码格式跟这个文件的后缀名并没有完全的必然联系。

不同的编码格试的编码效率以及功能不同,要查看一个编码格式的详细信息和参数,运行 ffmpeg -help encoder=编码格式 。

文件的后缀名通常代表这个文件的封装格式,用来将音频流和视频流包裹起来,同时还可以添加一些字幕、附件等。封装格式也有很多种,运行 ffmpeg -formats 就可以看到。

不同的封装格式不一定能够装下同样的东西。有的封装格式可以装下视频流和音频流,有的只能装下音频流,有的只能装下视频流。有的能塞进很多东西,有的只能放特定的几个媒体流。要查看一个封装格式的详细信息,运行 ffmpeg -help muxer=封装格式 。

 

转码

ffmpeg -i input.avi output.mp4

上面这行就是最简单的转码命令。 

-i 选项表示指定输入文件。

 

指定编码器

FFmpeg 会自动判断输入文件的封装格式以及编码,并且根据输出文件的后缀名来判断其封装格式。同时, FFmpeg 也会根据该种封装格式的默认编码来决定输出文件的编码。

而通过使用 -vcodec 选项,可以指定视频编码器。如果指定 copy 将不会重新编码,而是直接把原来的媒体流拷贝过去。

ffmpeg -i Tor_Animation_en.mp4 -vcodec hevc tor.mkv 

-acodec 选项,是用来指定音频编码的。所以如果我想要以 Matroska 作为封装格式, HEVC 作为视频编码, Opus 作为音频编码,使用的命令会是这样的:

ffmpeg -i input.mp4 -vcodec hevc -acodec opus output.mkv

如果我想要直接拷贝媒体流到输出文件中而不重新进行编码(也就是只转换一下封装格式),在编码器的位置上写 copy 。

ffmpeg -i Tor_Animation_en.mp4 -vcodec copy -acodec copy tor.mkv

因为这样不进行编码,所以通常会很快。

 -vcodec 可以缩写为 -c:v , -acodec 可以缩写为 -c:a 。

AAC 是一种被十分广泛使用的音频编码,也是 MP4 封装格式的默认音频编码。 将视频转换为 Matroska 封装格式, HEVC 视频编码, AAC 音频编码,命令如下

ffmpeg -i Tor_Animation_en.mp4 -c:v hevc -c:a aac -strict -2 tor.mkv

 

其他功能

常用音频选项

-ar

 -ar 选项后面还要跟一个rate 参数,这个参数是音频的采样率,以 Hz 为单位。通常来说,采样率越大,音质越好。

电话的采样率通常是 8000 Hz ,普通的录音笔通常是 32000 Hz ,一般的 MP3 音乐是 44100 Hz ,稍高品质的音乐是 48000 Hz ,再高的采样率我们平常就不容易接触到了。

以下是一个将音频文件 input.mp3 转换为 Ogg 封装格式, Vorbis 编码,并且指定音频采样率为 32000 Hz 的例子:

ffmpeg -i input.mp3 -c:a libvorbis -ar 32000 output.ogg 

注意: 在 FFmpeg 中使用 Vorbis 编码时,编码器应指定为 libvorbis 而不是 vorbis ,这是两个不同的编码器,而后者还处于不完善的状态。

数字 32000 可以简写为 32k ,因为 “k” 就相当与“千”。

 
-an

这个就是在进行视频转码的时候,将音频给去除,这样就会得到一个没有声音的视频。

 
-vol

这个选项有一个参数,用来指定相对与原来的文件的音量大小。

注意,标准的音量并不是 100 ,而是 256 ,也就是说,指定了 -vol 256 就是原来的音量不变(跟没指定一样)。

下面的例子是将音量减小为原来的一半:

ffmpeg -i input.mp3 -c:a libvorbis -vol 128 output.ogg 

 

常用视频选项

-r

-r 选项可以指定视频的帧率,其参数的单位是 Hz ,也就是平常所说的“帧每秒”( FPS )。比如 -r 24 即代表输出视频的帧率为每秒 24 帧。

 
-s

这个选项可以指定视频的尺寸,以像素为单位。这个选项的参数由 宽度x高度 的格式填写。比如如果要把视频转为 720P (也就是宽度 1280 像素,高度 720 像素),就写上 -s 1280×720 。

下面的例子会将输出文件视频的帧率指定为 15 帧每秒,画面尺寸 1280×720 :

ffmpeg -i input.mp4 -r 15 -s 1280x720 output.mkv 
 
-vn

与 -an 选项一样,在命令中加入了这个选项之后,视频内容便会被去除。也就是说,只把音频剥离出来。

 

其他常用选项

-c

-c 选项用来指定输出文件的编码,但跟 -c:v 和 -c:a 不同的是,它指定的是全部媒体流的编码而不是单独一个视频流或音频流。在通常情况下,这是行不通的,因为视频编码和音频编码是两种不同的东西,怎么能是同一个呢?

众所周知,大部分视频或音频编码都是有损压缩,重新进行一次编码不但费时费力,还会产生无法挽回的画质/音质损失,损失一两次通常是人类很难判别的,但是次数多了差别就大了。

所以,要尽量避免重新编码,能用 copy 作为“编码器”的时候就尽量使用它。

让视频不重新编码的时候使用 -c:v copy ,让音频不重新编码的时候使用 -c:a copy 。那么在两个都要不重新编码的时候,就不用把这两条选项都写上去了,只要 -c copy 就足够。

例如将视频尺寸转换为 `1280×720` ,其他不变。

因为视频的尺寸变了,所以视频不得不重新编码,但音频不用。

ffmpeg -i input.mp4 -c:v h264 -c:a copy -s 1280x720 output.mp4 

仅仅将 MP4 封装格式转换为 Matroska 封装格式。

只是封装格式变了,视频和音频都不需要重新编码。

ffmpeg -i input.mp4 -c copy output.mkv 
 
-t

这个选项用来指定输出文件的持续时间,以秒为单位,比如想截取 full.mp4 这个视频的前 30 秒并保存为 segment.mp4 ,就可以使用这个命令:

ffmpeg -i full.mp4 -c copy -t 30 segment.mp4

注意: 所以不需要进行重新编码,因为重新转码损失质量以及影响截取速度,能不重新编码就尽量不要重新编码。

这个选项的参数以秒为单位,5:00 可以表示 5 分钟, 1:23:45 可以表示 1 小时 23 分钟 45 秒。也可以写小数点,比如 10.0268 或者 1:23:45.678 。

 
-ss

这个选项用来指定输出文件相对于输入文件的开始时间,比如我想把 full.mp4 这个视频的前 30 秒剪掉并把剩下的保存为 segment.mp4 ,就可以使用这个命令:

ffmpeg -i full.mp4 -c copy -ss 30 segment.mp4

-ss 选项也可以跟 -t 选项配合使用以截取媒体文件的一部分,比如如果我想截取 full.mp4 的 12 分 25 秒至 20 分 27 秒,保存为 segment.mp4 ,使用这条命令。

ffmpeg -i full.mp4 -c copy -ss 12:25 -t 8:02 segment.mp4
 
-metadata

这个选项所更改的是输出文件的元数据,比如一首歌的标题、艺术家、专辑。比如 -metadata title=”我是标题” 就会把输出文件的标题元数据改为 我是标题 。

可以多次指定以更改多个元数据,例如把 `no metadata.mp3` 的标题改成 `一首歌` ,艺术家改成 `一位艺术家` ,专辑改成 `一张专辑` ,然后保存为 `with metadata.mp3` :

ffmpeg -i "no metadata.mp3" -c copy -metadata title="一首歌" -metadata artist="一位艺术家" -metadata album="一张专辑" "with metadata.mp3"

 

字幕

作为字幕流

如媒体文件的结构中所说,媒体文件是由多个媒体流组成的,我们已经对视频流和音频流很熟悉了,但在这一节,我们将认识另一种媒体流——字幕流。字幕流独立于视频流,所以它可以随时开关,就像声音可以被静音一样,也可以有多个进行切换,例如有多国语言字幕的电影资源。

字幕流跟其他媒体流一样,有各种编码,常见的编码有 SubRip (作为独立文件时后缀名为 .srt )和 ASS (作为独立文件时后缀名为 .ass )。

字幕文件其实也能够算是媒体文件,只不过它不包含视频流和音频流,而是只包含了字幕流。

所以,如果我把只有视频流和音频流的媒体文件 Tor_Animation_en.mp4 与只有字幕流的媒体文件 Tor_animation.zh-CN.srt 合并到一起,就会得到一个有视频流、音频流和字幕流的媒体文件了。

在 FFmpeg 中,这样做很简单,只要把要合并的两个文件都作为输入文件就可以了,也就是在 ffmpeg 后面写上 -i Tor_Animation_en.mp4 -i Tor_animation.zh-CN.srt 。 FFmpeg 会将两个输入文件中的媒体流放都到其输出文件中去。

但是并不是所有的封装格式都支持字幕流,比如常见的 MP4 格式就不支持。要查看一个封装格式是否支持字幕流,请运行 ffmpeg -help muxer=封装格式 ,如果输出中有 Default subtitle codec: 这样一行,即代表此封装格式支持字幕流。

如果输出文件的封装格式不支持字幕流, FFmpeg 会忽略掉它或者报错。

因为字幕流也是媒体流,也有各种编码,所以,我们也可以通过 -scodec 或 -c:s 选项来指定字幕流的编码。这个例子可以让 FFmpeg 复制视频流和音频流(不用重新编码,加快了处理速度),而将字幕流转换为 ass 编码:

ffmpeg -i Tor_Animation_en.mp4 -i Tor_animation.zh-CN.srt -c:v copy -c:a copy -c:s ass Tor_Animation_subtitled.mkv

同样的, -c 选项除了会影响到视频流和音频流以外,也会影响到字幕流,也就是说,指定 -c copy 也会让 FFmpeg 不对字幕流进行重新编码。

甚至可以将字幕文件作为单独的输入文件!也就是对字幕文件进行转码,比如 ffmpeg -i Tor_animation.zh-CN.srt Tor_animation.zh-CN.ass 就会将 SubRip 字幕转换为 ASS 字幕。(因为 ASS 封装格式的默认字幕编码就是 ass ,所以这条命令中不用写 -c:s ass )

 

编入视频流

虽然字幕流是一个很方便的东西,但是由于一些差的播放器不支持字幕流,字幕流也的显示也取决于播放器所使用的字体,所以在很多时候,我们需要将字幕放到视频内容中,而不是独立于视频流的字幕流。

注意: 因为这样做会改写视频流,所以原本视频中被字幕覆盖的地方就无法再被显示出来了。

 
-vf 

这个选项被用来给视频添加滤镜,就像图像处理软件的滤镜一样,有颠倒、放大、改变颜色、模糊之类的东西。用 ffmpeg -filters 可以查看 FFmpeg 支持的滤镜。

我们要在视频流上面加上字幕,就得使用一个叫做 subtitles 的滤镜,要使用这个滤镜,在命令中写上 -vf subtitles=字幕文件名 。

ffmpeg -i Tor_Animation_en.mp4 -vf subtitles=Tor_animation.zh-CN.srt -c:a copy Tor_Animation_subtitled.mp4

因为这个操作改写了视频流,所以视频流必须得重新编码。

在上面这个例子中,没有使用 -c:v 选项, FFmpeg 则会使用 MP4 封装格式的默认视频编码 h264 。

如果与此同时,使用了 -ss 选项,被渲染的字幕不会听从 -ss 选项,而是依然从最开始渲染,为了解决这个问题,这个页面供了一些方案。

注意: 如果你使用的是Windows系统 , FFmpeg 会在写视频流的时候无法找到字体,而导致输出文件中没有任何字幕,要解决这个问题,请参阅在 Microsoft Windows 下使用 FFmpeg 将字幕编入视频流时的额外说明

 

常见编码器

FFmpeg 支持很多种媒体编码器,这些媒体编码器当然都不会是千篇一律的,有些编码器有着十分丰富的选项,这些选项可以让你调整编码时的压缩比、刷新率、色域等等。你也可以通过调整一些参数来让转码快一些,或者体积更小。

 

H264 和 HEVC

毫无疑问, H264 是目前最流行的视频编码了,现在我们在网上见到的几乎所有视频都是使用 H264 进行视频编码的,不管它的封装格式是 MP4 、 Matroska 还是 FLV 。

而它为什么会变得如此流行呢?除了较高的效率,丰富的编码器选项也是它的优点之一。

HEVC 是 H264 的后继版本,又称 H265 ,它提供了一个更高的压缩比,不过目前有许多差的播放器不支持这种编码,所以请小心使用。

 

恒流量系数 (CRF)

这种方式可以让编码器尽量保持一定的画面质量,而文件的大小就不是那么重要了,这种方式为单通道的方式提供了最大的压缩比。每一帧都会得到能保证其质量等级的比特率。它的坏处是你无法确认最终会得到的具体文件大小或保证它不超过一定的尺寸。

首先你需要选择一个 CRF 值,这个值在 0 至 51 之间。 0 就代表无损, 23 是默认的, 51 就是最差的画质了。数字越小画质越高,不过通常我们选 18 至 28 之间就可以了,就当作 18 是“近乎无损”吧,如果输入文件不是无损的话,通常 18 就跟原来是几乎没区别的了。

CRF 值是倍数,也就是说把 CRF 增大 6 的话,文件就会变成原来的几乎一半大小,减小 6 的话就会变成两倍。通常选择 CRF 值的方式是:选择一个最大的能提供看起来还可以的值,如果出来的效果还不错,就再增大一些试试,如果看起来画质太差了就减小看看。

接下来你要选择一个预设,预设会影响编码的速度和压缩比。

慢的预设会给出一个较好的压缩比(压缩比就是画面质量相对于文件大小的比例),也就是说在文件大小一样的情况下,越慢的预设画质越好,或者说在画质一样的情况下,越慢的预设文件更小。

通常选定预设的方法是使用你能忍受的最慢的预设。目前的这些预设从快到慢排列有: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo 。

默认是 medium , placebo 通常没有用所以请无视它。

决定好了 CRF 值和预设之后,你就可以使用 -crf 选项来指定 CRF 值, -preset 选项来指定预设了。下面的例子是使用 22 作为 CRF 值, slow 作为预设来进行转码:

ffmpeg -i input.mp4 -c:v h264 -crf 22 -preset slow -c:a copy output.mkv 

 

指定平均比特率

使用 -b:v 选项可以让 FFmpeg 为 H264 或 HEVC 编码的视频指定一个平均的比特率,这样就可以控制最终的文件大小了。

比如要让平均比特率为 1000 KBit/s ,就写上 -b:v 1000k 。

不过这个选项不能与 -crf 选项同时使用,因为实际上指定 CRF 值就是让编码器自动评判出一个平均的码率。

 

VP9

VP9 是 Google 为 WebM 格式所开发出的一种视频编码,专门为在线串流视频设计。全世界最大的视频网站 YouTube 默认使用的视频编码就是 VP9 。同样的,它也拥有许多选项来使编码变得更有效。

平均比特率

与 H264 和 HEVC 一样, VP9 默认使用的是动态比特率,同样我们也可以用 -b:v 选项来指定其平均的比特率,比如我想让视频的平均比特率为 1 MBit/s ,就使用这条命令:

ffmpeg -i input.mp4 -c:v vp9 -b:v 1m output.webm 

 

稳定画质

VP9 编码也提供了一种方式让视频保证一定画质,与 H264 和 HEVC 一样使用的是 -crf 参数,不过与 H264 和 HEVC 不同的是, VP9 的 CRF 值范围是 0-63 。

还有一点要格外注意,要让 VP9 编码器保持稳定画质的时候,必须指定 -b:v 0 ,不要忘了。

也就是说,要让转码时使用 VP9 编码并以 10 作为 CRF 值,使用这条命令:

ffmpeg -i input.mp4 -c:v vp9 -crf 10 -b:v 0 output.webm 

 

其他功能

合并两段视频

视频能够被切割,也能够被合并。这里说的“合并”就是指将视频头尾连接起来,比如我有两段视频,一个是 10 秒,另一个 20 秒,合并起来就是 30 秒。

要合并视频,首先得创建一个文本文件,叫什么名字都可以,我这里以 list.txt 为例。用任意一种你喜欢的文本编辑器,按顺序逐行写上 file  <file name> ,比如我要将 01.mp4 与 02.mp4 合并起来,就写上这些内容:

file 01.mp4
file 02.mp4

保存好 list.txt 之后,就可以在命令行中执行 ffmpeg -f concat -i list.txt -c copy 输出文件名 了。如果一切顺利,最后的输出文件就会是 list.txt 中所写的文件按顺序连接起来的成果。

这样做有一条限制,那就是被合并的视频必须都是一样编码、一样封装格式的,如果你想合并不同编码的视频,最好先转换成统一的编码。

 

自己动手分配媒体流

有时候我们要面对复杂的媒体流分配,比如我有两个视频文件, a.mp4 和 b.mp4 ,我想将 a.mp4 的视频流和 b.mp4 的音频流提取出来,做成一个新的新视频文件,要怎么做呢?

我可以先用 ffmpeg -i a.mp4 -c:v copy -an a_video.mp4 将 a.mp4 的视频流单独提取到一个文件上,叫 a_video.mp4 。

然后用 ffmpeg -i b.mp4 -c:a copy -vn b_audio.aac 将 b.mp4 的音频流单独提取到 b_audio.aac 上。

最后用 ffmpeg -i a_video.mp4 -i b_audio.aac -c copy final.mp4 将 a_video.mp4 和 b_audio.aac 放到一起得到最后的成果 final.mp4 。

可是这样需要 3 个步骤,还会产生临时文件。实际上,我们还可以使用手动分配媒体流的方式,用一条命令完成。

这时就要用到 -map 选项了,一旦你使用了这个选项, FFmpeg 就不会自动为你将输入文件的媒体流分配到输出文件上,相对的,你可以手动分配。

-map 选项的参数是 输入文件编号:这个文件的媒体流编号 ,指定一次即代表这个媒体流会被分配到输出文件中,比如 -map 0:0 将会指定 0 号输入文件的 0 号媒体流(注意编号是以 0 开始计数!)。

-map 选项可以指定多次,从而依次指定输出的媒体流顺序,也就是说,第一次 -map 指定的媒体流将会变成输出文件的 0 号媒体流,第二次 -map 就会指定输出文件的 1 号媒体流,以此类推。举个例子,如果写了 -map 0:0 -map 0:1 ,那么 0 号输入文件的 0 号媒体流就会变成输出文件的 0 号媒体流, 0 号输入文件的 1 号媒体流就会变成输出文件的 1 号媒体流。

那么用 -map 该怎样完成这一节开头提到的任务呢?

首先我们需要知道 a.mp4 和 b.mp4 的媒体流有哪些,这可以通过 ffmpeg -i a.mp4 来完成, FFmpeg 会显示出该文件的信息。

我们可以看到 Stream #0:0 ,这代表接下来写的是第 1 个输入文件的第 1 个媒体流的信息,它告诉我们这是一个视频流,用 H264 编码。剩下的就不用管了,我们只需要记住 a.mp4 的视频流的编号是 0 就可以了。

同样的,用 ffmpeg -i b.mp4 查看 b.mp4 的信息,寻找它的音频流的编号并记住它,我这里以 1 为例。

那么现在我们可以写最后的命令了,先指定两个输入文件, ffmpeg -i a.mp4 -i b.mp4 ,这样 a.mp4 就会是第一个输入文件(编号为 0 ), b.mp4 就会是第 2 个输入文件(编号为 1 )。

我们已经知道 a.mp4 的视频流的编号是 0 了,那么就加上 -map 0:0 ,让 FFmpeg 把第 1 个输入文件的第 1 个媒体流输出,作为输出文件的的第 1 个媒体流。 我们也知道 b.mp4 的音频流的编号是 1 ,就再加上 -map 1:1 。

注意: 别忘了 -c copy ,除非你想重新编码。

最后写上输出文件的名字,整条命令就会是 

ffmpeg -i a.mp4 -i b.mp4 -map 0:0 -map 1:1 -c copy final.mp4

FFmpeg这里只是简单入门,它的强大功能可以查询官方文档

本文转载自如何使用 FFmpeg 进行视频转码

另外,python虽然有一个ffmpy3的第三方库可以调用FFmpeg,但仍需要安装FFmpeg的本体,所以何不直接用subprocess调用呢?

 


我不想生活在认为善良是弱点的世界里。

——基努·里维斯