简介
与笔者在上一篇博客中介绍的内容类似,FFmpeg也是一款开源的媒体处理工具。上一篇博客介绍的GhostScript主要用于处理PDF文件,而Imagemagick主要用于处理各种图片文件。而FFmpeg也可以对图片进行处理与转化,但它同时还具有视频与音频处理的强大功能。有不少视频播放器都以FFmpeg作为后端(例如VLC,以及一大堆国产的乱七糟八的播放器),也有大量的录屏软件使用FFmpeg作为后端(例如OBS Studio,Simple Screen Recoder,以及一大堆国产的乱七糟八的工具),大量的开源视频编辑软件也使用FFmpeg。
基本命令形式
FFmpeg的基本命令形式较为简洁,只需要指定输入与输出文件即可:
ffmpeg -i [输入文件] [输出文件]
在此基础上,我们可以进行一定的拓展,例如,输入多个文件:
ffmpeg -i [输入文件1] -i [输入文件2] [输出文件]
有时候,媒体文件中含有多个部分(例如视频、音频、字幕),我们可能只需要其中一个或几个部分,这时我们可以用-map
参数指定,传递参数的基本格式为:
-map [文件序号]:[媒体类型]:[媒体序号]
其中,文件序号
指的是我们通过-i
参数传递输入文件时该文件出现的序号,从0
开始编号;媒体类型
主要分为视频、音频、字幕三种,分别用v
、a
、s
表示;媒体序号
指的是该媒体流在同类型媒体流中的编号,从0
开始。媒体类型
与媒体序号
这两个参数可选。
默认情况下,FFmpeg将会对媒体文件使用相应格式的默认方式进行重新编码,也可以传递-c
参数手动指定编码方式,除了表示不重新编码而简单复制的-c copy
外,其余的编码方式一般都需要指定媒体流的类型(显然不可能用视频编码器去编码音频吧QwQ),例如-c:v [视频编码器]
、-c:a [音频编码器]
、-c:s [字幕编码器]
。
-map
参数可以多次指定,可以用于包括多个媒体部分,也可以很方便地用于媒体文件的分离与提取(例如提取视频中的音频文件)。
多输出设置
FFmpeg还可以同时输出多个不同编码的文件,这可以用来对比不同编码的质量、大小,或者生成不同要求的视频文件。我们可以多次指定编码方式和输出文件来实现这一功能,例如:
ffmpeg [...] -i [输入文件] -c:v libx264 -c:a aac -b:v 1M -b:a 128k [输出文件1].mp4 -c:v libsvtav1 -c:a opus -b:v 1M -b:a 96k [输出文件2].webm
这里,我们同时输出了一个H.264编码的MP4文件和一个AV1编码的WebM文件,前者的硬件兼容性更好,后者的质量更高。
硬件加速
解码
目前的电脑无论是集显还是独显基本上都会配备专用于视频编解码的ASIC电路,使用ASIC电路进行编解码往往能更加高效省电快速,同时也能降低CPU的占用。
调用显卡的ASIC进行视频编解码的API通常由操作系统封装在API中,这些API在不同的操作系统上不一定相同。
在Windows平台下,一般可以选择微软提供的dxva2
或者d3d11va
API进行硬件解码,其中,dxva2
基于Direct3D 9技术,而d3d11va
基于Direct3D 11技术,一般而言后者效率较高。FFmpeg使用这两个API解码的命令非常简单,只需要传递-hwaccel
参数并在其后指定用于硬件加速的API即可:
ffmpeg -hwaccel dxva2 [...]
ffmpeg -hwaccel d3d11va [...]
Linux平台显然无法使用Direct3D相关API进行硬件解码,在Linux平台下,我们一般使用的是更加Unix风的VAAPI
,VAAPI的使用较Direct3D相关API复杂,除了需要指定硬件加速的API外,还需要指定用于解码的硬件设备的路径,集显一般在/dev/dri/renderD128
,独显一般在/dev/dri/renderD129
,例如:
ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 [...]
ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD129 [...]
通用解码加速方案
除了上述的Windows平台和Linux平台的专用API外,FFmpeg还提供了一种通用的硬件加速方案,即使用-hwaccel
参数指定auto
,让FFmpeg自行选择硬件加速API:
ffmpeg -hwaccel auto [...]
这样,FFmpeg会根据平台和硬件配置自行选择最优的硬件加速API。
编码
FFmpeg不仅支持硬件解码,还支持硬件编码。事实上,硬件解码的解码结果一般与软件解码相同,不会有更多的损失,而硬件编码的编码效果则往往比软件编码差。在对编码码率要求不高时,使用ASIC硬件编码能够大大加快编码速率。
使用硬件编码时,推荐同时也对输入视频使用硬件解码,否则并不能够充分降低CPU的占用与整体功耗,使本来质量就不占优势的硬件编码也失去效率优势,丧失使用的意义。
编码器的API也往往会因操作系统平台或显卡厂商的不同而不同;对于Windows平台,往往可以直接使用显卡厂商提供的API,以AMD为例,可以使用AMF API,例如hevc_amf
、h264_amf
进行编码:
ffmpeg -hwaccel auto [...] -c:v hevc_amf [...]
ffmpeg -hwaccel auto [...] -c:v h264_amf [...]
Linux平台则可以继续使用VAAPI编码,但需要额外使用-hwaccel_output_format
指定编码输出方式为vaapi
,编码器的后缀名为_vaapi
:
ffmpeg -hwaccel auto -hwaccel_output_format vaapi [...] -c:v hevc_vaapi [...]
如果不加入-hwaccel_output_format
参数,则需要手动传递-vf
参数指定将视频流加载到硬件编码器上,同时,也可以指定一个硬件支持的颜色空间,比如nv12
:
ffmpeg -hwaccel auto [...] -vf 'hwupload,scale_vaapi=format=nv12' -c:v hevc_vaapi [...]
除了nv12
外,也可以使用更多硬件支持的颜色空间。与-hwaccel_output_format
相比,-vf 'hwupload'
后者更加独立灵活,可以适用于更多场合,在下文中有仅能够使用-vf 'hwupload'
而不能使用-hwaccel_output_format
的示例。实际上,hwupload
表示加载到硬件加速设备上,hwdownload
则表示从硬件加速设备上下载,这一点在后面有关滤镜的操作上也很重要,凡是使用硬件加速设备的滤镜之前,都需要添加hwupload
,而从硬件设备切换到CPU时,则需要添加hwdownlaod
。
转码涉及的重要参数
tune
参数
这里以libx265中的tune
参数为例,介绍一下转码中的一些重要参数。tune
参数用于指定转码的场景,不同的场景会有不同的参数优化,例如:
- psnr:为提高psnr做了优化的参数。psnr是一种全参考的视频质量评估指标,用于衡量原始视频和压缩视频之间的差异。psnr越高,表示压缩视频与原始视频越相似,质量越高。
- ssim:为提高ssim做了优化的参数。ssim是一种全参考的视频质量评估指标,用于衡量原始视频和压缩视频之间的结构相似性。ssim越接近1,表示压缩视频与原始视频越相似,质量越高。
- vmaf:为提高vmaf做了优化的参数。vmaf是一种全参考的视频质量评估指标,由Netflix开发,用于综合多种基础指标,如VIF, ADM, motion等,通过机器学习算法得到一个最终的分数。vmaf越高,表示压缩视频与原始视频越相似,质量越高。
- vmaf_4k:为提高vmaf做了优化的参数,专门针对4K分辨率的视频。vmaf_4k与vmaf类似,但使用了不同的训练数据和模型。
- ms_ssim:为提高ms_ssim做了优化的参数。ms_ssim是一种全参考的视频质量评估指标,是ssim的改进版本,考虑了多尺度的结构相似性。ms_ssim越接近1,表示压缩视频与原始视频越相似,质量越高。
- animation:为动画类型的视频做了优化的参数。动画类型的视频通常有较少的细节和较多的平坦区域,因此需要特别处理以保持清晰度和颜色。
- grain:为需要保留大量噪点(grain)时用的参数。噪点是一种常见的视频失真现象,有时也被认为是一种风格或者特效。grain参数可以在压缩时尽量保留噪点,而不是平滑掉它们。
- zerolatency:为零延迟场景做了优化的参数。零延迟场景是指需要非常低延迟的情况下,比如实时通信或者直播等。zerolatency参数可以减少编码器使用的缓冲区和预测帧数,以提高编码速度和降低延迟。
ffmpeg默认使用tune参数为psnr。
cqp
与crf
由于转码常常用于视频的压缩保存,往往不需要指定具体的码率,而仅需要指定相应的清晰度指标。
CQP与CRF这两种码率控制方式有些类似,但实际上有所不同:
- CRF (Constant Rate Factor) 是一种恒定质量因子的码率控制模式,它会根据视频内容的复杂度动态调整每一帧的量化参数 (QP),以保持主观感知到的质量恒定。
- CQP (Constant QP) 是一种恒定量化参数的码率控制模式,它会保持每一帧的QP不变,不考虑视频内容的复杂度。
因此,CRF和CQP的区别在于,CRF是一种基于主观质量的自适应码率控制模式,而CQP是一种基于客观量化的固定码率控制模式。CRF模式可以更有效地利用可用的码率,而CQP模式可能会在人眼不敏感的地方浪费码率。大多数的软件编码器(例如libx265
、libsvtav1
等)均支持CRF和CQP两种码率控制方式,但很多硬件编码器往往只支持CQP一种。
对于一般的转码,cqp
或crf
值设置为28 - 35基本上文件大小与视频质量的权衡较好。数字越大,视频体积越小,质量越低;数字越小,视频质量越好,但文件体积也更大。笔者个人常用的值是30或者33。cqp
或crf
值设定非常简单,只需要加上参数-qp xxx
或者-crf xxx
即可。
需要注意的是,AV1编码器(例如libsvtav1
)的crf
值与H.264和H.265编码器的crf
值不同,AV1编码器的crf
值范围为0 - 63
,0往往在crf = 50
时仍有较不错的质量。
使用FFmpeg录屏
FFmpeg可以直接当作录屏工具来适用。实际上,OBS Studio等专业录屏软件的后端即为FFmpeg,手动利用命令调用FFmpeg也能起到类似的效果,并且从软件构成上更加简洁,对各种FFmpeg参数的调控也更方便。
在Windows下,可以使用gdigrab API录屏,还可以在其中指定内放音频,例如:
ffmpeg -f gdigrab -i desktop -f dshow -i audio="virtual-audio-capturer" output.mp4
在Windows下,硬件加速的API也非常简洁,以使用d3d11va加速和amf编码为例:
ffmpeg -hwaccel auto -f gdigrab -i desktop -f dshow -i audio="virtual-audio-capturer" -c:v hevc_amf output.mp4
在Linux下,对于X Window运行模式,可以直接使用x11grab API进行录屏。使用FFmpeg录屏需要自行传递-framerate
参数指定帧率,一般设置为30
即可。录屏时,还可以指定所需要一同捕捉的音频,音频可以用不同的方式捕捉,也可以指定不同的音频来源,例如使用pause
捕捉音频:
ffmpeg -f x11grab -framerate 30 -i :0.0+0,0 -f pulse -i 0 output.mp4
这样一般默认的是从麦克风捕捉音频,如果需要从其他音频来源捕捉音频,可以手动指定,可以先用pactl
查看音频设备的信息:
pactl list short sources
笔者的设备上的输出为:
55 alsa_output.pci-0000_05_00.6.analog-stereo.monitor PipeWire s32le 2ch 48000Hz IDLE
56 alsa_input.pci-0000_05_00.6.analog-stereo PipeWire s32le 2ch 48000Hz SUSPENDED
可以看到,笔者的设备上有两个音频输入,一个是麦克风,一个是系统输出。如果需要从系统输出捕捉音频,可以使用alsa_output.pci-0000_05_00.6.analog-stereo.monitor
作为音频输入:
ffmpeg -f x11grab -framerate 30 -i :0.0+0,0 -f pulse -i alsa_output.pci-0000_05_00.6.analog-stereo.monitor output.mp4
以上命令较为简单基础,默认输出的是在默认预设下用libx264编码的视频,可能对CPU占用较高。对此,可以用-preset
参数指定编码预设降低CPU占用,例如,superfast
对CPU的占用将明显更小:
ffmpeg -f x11grab -framerate 30 -i :0.0+0,0 -f pulse -i alsa_output.pci-0000_05_00.6.analog-stereo.monitor -c:v libx264 -preset superfast output.mp4
当然,这样本质上仍然是使用CPU软编码。类似于转码中的设定,我们也可以使用vaapi实现录屏的硬编码,进一步减轻CPU负担。注意在录屏中使用vaapi编码时需要传递-vf
参数转化颜色空间:
ffmpeg -vaapi_device /dev/dri/renderD128 -f x11grab -framerate 30 -i :0.0+0,0 -f pulse -i alsa_output.pci-0000_05_00.6.analog-stereo.monitor -vf 'hwupload,scale_vaapi=format=nv12' -c:v hevc_vaapi output.mp4
注意:Wayland
下无法使用x11grab
API进行录屏
元数据
我们平时拍摄的一些视频可能含有一些元数据,例如拍摄时间、拍摄地点、拍摄设备等,这些元数据可以用于视频的管理与分类,也可以用于视频的搜索与检索。在默认的转码中,FFmpeg会保留一些元数据,但是有些元数据会丢失,例如拍摄时间、拍摄地点等。
我们可以使用-metadata
参数来指定需要保留的元数据及其内容,例如:
ffmpeg -i [输入文件] -metadata title="标题" -metadata author="作者" -metadata comment="备注" [输出文件]
如果我们想要保留所有元数据,可以使用-metadata a
参数,例如:
ffmpeg -i [输入文件] -metadata a [输出文件]
简单的滤镜操作
视频滤镜
FFmpeg支持给视频加滤镜,这不仅在高级视频处理工作中有用,在给视频加硬字幕时仍然有用。滤镜用-vf
传递参数,以加字幕为例:
ffmpeg [...] -vf subtitles="example.srt" [...]
在Linux下,用vaapi硬件加速加字幕需要指定更多参数:
ffmpeg -hwaccel auto [...] -c:v hevc_vaapi -vf 'subtitles=example.srt,hwupload' [...]
其中,hwupload
后面仅能够跟有关vaapi的滤镜,比如scale_vaapi=format=nv12
。
Windows平台需要额外注意,在传递绝对路径的时候需要在Windows下用于表示盘符的:
前加\\
,并且不能用单个\
表示路径分隔,必须用Unix的/
表示,例如:
ffmpeg [...] -vf subtitles="C\\:/User/xxx/example.srt" [...]
滤镜功能还可以用于HDR到SDR的转化,以及颜色空间的转化,在一定程度上提高视频的兼容性:
ffmpeg [...] -vf zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p [...]
还可以对视频的色彩饱和度、对比度、亮度进行调整,例如:
ffmpeg [...] -vf eq=saturation=0.7:brightness=0.1:contrast=1.1 [...]
这个例子的含义是,将视频的饱和度提高到170%,亮度提高到110%,对比度提高到110%。(注意一些参数以0
为基准,另一些以1
为基准)
滤镜还支持视频分辨率的缩放,以及视频内容的剪裁,这两个功能分别由scale
和crop
提供支持,例如:
ffmpeg [...] -vf scale=[输出宽度]:[输出高度]
ffmpeg [...] -vf crop=[输出宽度]:[输出高度]:[剪切起点横坐标]:[剪切起点纵坐标] # 若不写最后两个参数,默认居中剪切
对于scale
滤镜,如果需要保持宽高比,可以只设定宽度或者高度中的一个参数,另一个参数设置为-1
,FFmpeg将自动按照原视频的宽高比转化:
ffmpeg [...] -vf scale=[输出宽度]:-1
ffmpeg [...] -vf scale=-1:[输出高度]
视频内容的旋转也可以用滤镜实现,例如(角度为顺时针):
ffmpeg [...] -vf rotate=90 [...]
还可以使用transpose
滤镜实现旋转,例如:
ffmpeg [...] -vf transpose=1 [...]
其中,1
表示顺时针旋转90度,2
表示逆时针旋转90度,3
表示顺时针旋转270度,0
表示逆时针旋转270度。
当然,旋转也可以用-metadata:s:v:0 rotate=90
实现,可以免去编码过程,但是这种方式不是所有播放器都支持。
多个滤镜的操作用,
隔开即可。
此外,对于vaapi
,还可以用scale_vaapi
进行缩放,但是要注意用于缩放的参数与指定颜色空间的scale_vaapi=format=nv12
参数需要独立使用:
f
ffmpeg -hwaccel auto [...] -vf 'hwupload,scale_vaapi=format=nv12,scale_vaapi=w=[输出宽度]:h=[输出高度]' [...] -c:v hevc_vaapi [...]
scale_vaapi
可以在硬件加速的情况下对视频或图片进行缩放。
- 需要使用
-hwaccel vaapi
、-vaapi_device /dev/dri/renderD128
(在默认硬件加速API为VAAPI时也可以用-hwaccel auto
代替前两个命令)、-hwaccel_output_format
(或者-vf hwupload,scale_vaapi=format=nv12
)等选项指定硬件加速的设备和格式 - 可以使用w和h参数指定输出的宽度和高度,也可以使用-1来自动保持输入的宽高比
- 可以与其他滤镜结合使用,例如
pad
,setsar
,hwmap
,format
等,来实现不同的效果
在这里,笔者另外推荐几个重要的vaapi
加速的滤镜:
- hstack_vaapi:水平堆叠两个或多个输入视频
- vstack_vaapi:垂直堆叠两个或多个输入视频
- xstack_vaapi:根据指定的布局堆叠多个输入视频
- deinterlace_vaapi:对输入视频进行去交错处理
- denoise_vaapi:对输入视频进行降噪处理
- procamp_vaapi:调整输入视频的色度、亮度、对比度和饱和度
- sharpness_vaapi:对输入视频进行锐化处理
音频滤镜
音频滤镜用-af
传递参数,例如:
ffmpeg [...] -af volume=0.5 [...]
这表示将音频的音量降低到原来的一半。
音频滤镜的参数与视频滤镜的参数类似,可以用,
隔开多个滤镜,例如:
ffmpeg [...] -af volume=0.5,atempo=2.0 [...]
这表示将音频的音量降低到原来的一半,并且将音频的播放速度加快到原来的两倍。
视频图片提取操作
FFmpeg可以将视频逐帧提取为图片,传递-f image2
参数即可;输出图片支持按照C语言风格的printf
格式化方式编号命名,例如:
ffmpeg [...] -i [...] -f image2 output-%5d.png
以上命令将会把视频的每一帧转化为png
格式的图片,%5d
为C语言风格的格式化符号,表示按照序号生成5位整数。
将视频转化为图片时,可以手动传递-r
参数指定帧率,例如,每秒提取5张图片:
ffmpeg [...] -i [...] -r 5 -f image2 output-%5d.png
还可以传递滤镜参数fps
指定帧率:
ffmpeg [...] -i [...] -vf fps=1/5 -f image2 output-%5d.png
其中,1/5
表示每秒取5帧,类似地,5
表示每5秒取一帧。
剪辑
FFmpeg可以对视频进行剪辑,例如,从第10秒开始剪辑,剪辑10秒:
ffmpeg [...] -ss 00:00:10 -i [...] -t 00:00:10 [...]
其中,-ss
参数指定剪辑的起始时间,-t
参数指定剪辑的时长。
这里需要注意的是,虽然理论上-ss
跟在-i [...]
前面与后面都能够正确达到剪辑的目的,但是在实际使用中,如果-ss
跟在-i [...]
后面,则视频从起始到-ss
指定的时间处的内容仍然会被解码,增加了性能开销与剪辑时间,因此,建议将-ss
放在-i [...]
前面。
合并视频
FFmpeg可以将多个视频合并为一个视频。这个操作一般需要创建一个文本文件,文本文件中每一行为一个视频文件的路径,例如:
# `#`表示注释
file 'video1.mp4'
file 'video2.mp4'
file 'video3.mp4'
然后,传递-f concat
参数指定合并方式为concat
,并传递-safe 0
参数指定不检查文件名,最后传递-i
参数指定文本文件的路径即可,例如:
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4