< 返回博客

FFmpeg命令行


简介

FFmpeg是目前使用最广泛的开源音视频处理库,大多数的多媒体应用都会或多或少、直接或间接使用到FFmpeg。

FFmpeg是一个C写的项目,通常是被按需编译成.so,然后在代码中引用。

除了编程使用,FFmpeg项目也提供了三个特别好用的命令行工具:ffmpegffprobeffplay。这次主要是介绍其中的ffmpeg命令。

(下文小写的ffmpeg指的是命令行工具,开头大写的FFmpeg指的是FFmpeg项目)

基本概念

在介绍之前,先简单介绍下一些音视频/FFmpeg的基本概念。

  • 像素、图像、视频、音频。

    这些都是大家所熟知的概念。

    广义上来讲,所有的光学信号都可以称为图像。计算机世界的图像是离散的,因而一张图像由许多像素组成,每个像素有自己的颜色。

    在时间维度上将一系列图像串联起来就形成了视频,所以原始的视频数据往往存储为连续的、一块一块的像素数据。

    音频则是在时间维度上将一系列震动串联起来,所以原始的音频数据往往存储为PCM数据,也就是每一时刻的震动幅度。

  • 编码(encode)和解码(decode)。

    这些原始的视频和音频数据量是很大的,可以使用一些算法来利用其中的一些重复信息,从而减小数据量,这个压缩过程就是编码;反过来还原这些重复信息,得到原始的视频和音频,用于播放和处理,这个过程功能就是解码。

    一些编解码算法计算量很大,很是耗费CPU,因而SOC或者显卡会使用专用的硬件单元实现一些常见算法,使用这些硬件能极大加速编解码过程,适合音视频播放等场景。

    但硬件实现的可配置性较低,CPU编解码的好处在于可以对编解码的过程进行更为精细的控制。

  • 封装(mux)和解封装(demux)。

    有了多个媒体资源,将他们组合起来的过程通常叫做“封装”(mux);反过来的过程叫做“解封装”(demux)。我们平时接触到的各种音视频文件,往往都是多个资源“混合”之后的产物,比如一个电影文件可能包含:视频、多声道和多语言的音频、字幕,以及一些额外数据,如封面图、标题、年份等等。

    “封装”的方法往往和文件格式对应,常见的比如mov文件、mp4文件、mp3文件、ogg文件等,它们都可以理解为用来存储媒体数据的容器。

    注意和编解码区分开,编解码指的是对一段媒体数据的压缩、解压过程,“封装”指的是对多个媒体数据打包的过程,往往是不带压缩能力的。就比如一个mp4文件,它的视频数据有可能是h264编码的,也有可能是h265/hevc编码的;虽然编码和封装有一些常见的对应关系,但它们并非一回事。

  • 过滤器(filter)。

    这是FFmpeg的一个概念。

    对音视频的处理往往是从文件中读取作为输入,处理之后然后输出。这个数据处理过程经常有一些惯用法,比如缩放、位移、拼接、剪切,或是调整音频音量等等,FFmpeg把这些惯用的操作做成一个个的filter,每个filter可以实现一个单一的功能,通过拼接这些filter就可以实现比较复杂的功能。

安装

FFmpeg提供了三个工具,分别是:

  • ffmpeg 最主要的工具,用于音视频的处理;
  • ffplay 用于播放音视频文件;
  • ffprobe 用于探测音视频的格式和编码等信息。

在Mac上,可以通过brew安装:brew install ffmpeg。完成后即可使用上述三个命令。

其中ffplayffprobe比较简单,后面直接加文件名就行。例如命令行下输入ffplay 1.mp4就可以播放一个视频,ffprobe 1.mp4就可以查看文件的信息。

这里主要讲一下ffmpeg命令的使用方式。

参数结构

ffmpeg的工作方式大致上是将输入的媒体文件按照指定的方式处理,然后输出新的媒体文件(这里的文件也包含网络流等)。

相应地,它的参数也主要由三部分构成:输入参数、输出参数和全局参数。

全局参数

其中,全局参数控制ffmpeg的一些全局行为,比如日志级别、版本信息、内存限制等等。

输入参数

输入参数用来指定输入文件,以及告知ffmpeg输入文件的一些信息。FFmpeg支持几乎所有媒体格式,一般情况下,ffmpeg会通过文件的扩展名(.mp4、.mp3等)来确定文件的封装格式,进而读取内容以获取编码方式、分辨率、通道数、像素格式、采样率等信息,再基于这些信息进行解码。

比如上图的命令,指定-i input.mp4之后,ffmpeg会把input.mp4作为输入,同时根据它的后缀.mp4得知他是一个mp4封装的文件。

ffmpeg也支持多个输入,例如我们现在有一个视频input.mp4和一个音频input.mp3,要将它俩合到一起,就可以同时指定两个文件作为输入ffmpeg -i input.mp4 -i input.mp3,这样在后续的输出参数中,就可以操作和合成这两个输入。

对每一个输入文件,都有一些参数可以控制输入行为。

例如,ffmpeg -t 3s -i input.mp4 -t 2s -i input.mp3,其中-i input.mp4前面的参数用来控制input.mp4这个输入文件,即-t 3s的作用是让ffmpeg只读取该文件的前3秒;同理,-i input.mp3前面的-t 2s会使得ffmpeg只读取该音频文件的前两秒。

输入序列帧

如果输入并非普通文件,扩展名不正确或不能直接使用,或者是文件中不包含这些信息(如raw格式、序列帧等),就需要我们使用一些额外的输入参数,自行指定编码、宽高、像素格式、帧率等信息。

比如对于一串序列帧,名称分别为0001.png、0002.png、...0100.png,如果要将这个序列帧作为输入,可以使用通配的方法:ffmpeg -r 20 -i %04d.png,这样的话这个序列帧就会被当作一个视频输入;序列帧自身不包含帧率信息,这行命令中的-r 20参数就手动指定了该序列帧的帧率。

输出参数

输入参数告诉了ffmpeg如何读取和解码媒体文件,解码之后就得到了原始的音视频流;接下来需要我们告诉ffmpeg如何处理这些音视频流,以及如何输出结果,这些由输出参数指定。

输入文件需要指定-i开关,输出文件则不用,直接在命令最后加上需要输出的文件就行,例如:ffmpeg -i input.mp4 output.mov就可以将mp4文件转为mov文件。

ffmpeg解析输出参数的逻辑是:在所有-i xxx结束之后,每个不带开关的参数(通常被称做Positional参数)都是输出文件;每个输入文件之前的所有开关和参数都是属于这个输入文件的。

例如,ffmpeg -i input.mp4 -i input2.mp4 -r 20 output.mov -f mp4 output.mp4,其中-r 20就是output.mov的参数,用于指定该输出文件帧率为20;而-f mp4就属于output.mp4,用于指定该输出文件的封装格式为mp4。

一些常见的参数

了解了ffmpeg的参数结构,接下来介绍一些常用的参数;这些参数很大一部分既可以作为输入参数,也可以作为输出参数。

名称 作用 输入/输出 示例
-r 指定帧率
作为输入参数,重新解释输入文件的帧率,会影响播放时长;
作为输出参数,通过丢帧和复制帧控制输出帧率,但不会影响播放时长。
均可 -r 20<br>
通常会用在序列帧作为输入时
-f 指定封装格式 均可 -f mp4 -f mov
-c 指定编解码器
-c:v 指定视频编解码器
-c:a 指定音频编解码器
均可 -c h264<br>
-c:v h264<br>
-c:a aac
-b 指定比特率
-b:v 指定视频比特率
-b:a 指定音频比特率
-b 指定总比特率
仅输出 -b 2500K<br>
-b:v 2500K<br>
-b:a 100K<br>
有的编码器不支持,会被忽略。
-t 指定时长 均可 -t 20s

参数还有很多,可以通过man ffmpeg或者官方文档按需查看。

Filter

除了格式、帧率这些输出参数,最特殊的参数就是-filter-filter_complex,这两个参数可以用来指定该输出文件要使用的filter。

刚才简单介绍过,filter可以对音视频原始数据做一些常见的处理。

ffmpeg自带了大量的filter,比如我电脑上的5.0版本,就有470+个filter(可以通过ffmpeg -filters来列出所有的filter)。这些filter提供了各种各样的功能,小到缩放、剪切,大到物体识别,甚至于可以在其中插入GPU代码,总之五花八门。

我们可以通过-filter参数指定一个filter,来实现需要的功能。

例如,ffmpeg -i i.mp4 -filter scale=100x100 o.mp4就可以将i.mp4缩放到100x100的大小。

filter的语法是这样的:filter名=参数,其中filter名是scale这些,每个filter可以接受不同的参数,参数之间由:分开;上面的scale=100x100也可以写成scale=w=100:h=100

filter中也可以访问一些变量,例如scale=iw/2:ih/2就可以将原视频宽高都缩小为原来的1/2,其中的iw/ih是filter可以访问的变量,iw指的是输入文件的宽度,ih指的是高度。

可以通过ffmpeg -filters命令来查看所有的filter;ffmpeg -h filter=scale命令可以查看某个filter的详细说明,包括介绍、参数等。或者可以去官网上查看网页版本的filter文档

-filter参数只能指定一条“链状”的filter,而-filter_complex参数则可以组合多个filter,形成树状,组合起来的filter一般称作filter graph

例如:

ffmpeg -i i.mp4 -filter_complex \
  'split[v1][v2];[v1]scale=iw/2:ih/2[v1];[v2][v1]overlay' o.mp4

每个filter用;分开;每个filter都有输入和输出,在filter后加上[abc]可以将输出命名为abc;在filter的前面加上[abc]则可以将abc作为输入;通过这种命名的方法,可以实现很复杂的功能。

上面这个命令分开来看的话有三个filter:

  • split``[v1][v2] 将输入文件复制成两份,分别命名为v1和v2(未指定输入的话默认选用上一个输入,在这里就是-i选项指定的文件);
  • [v1]``scale``=``iw/2``:``ih/2``[v1] 将v1缩放为原来的一半,同时将结果再次命名为v1;
  • [v2][v1]``overlay overlay用于将v1和v2堆叠起来,v1位于v2上方。

经过这个命令之后,对于这样的输入:

会得到这样的输出:

示例

图片处理

除了音视频,ffmpeg处理图片当然也不在话下,例如ffmpeg -i input.png output.webp就可以将png转为webp。

Webp动图

处理webp的时候要注意,如果要从视频或者序列帧生成webp动图,需要指定编码为libwebp_anim,例如ffmpeg -i i.mp4 -c libwebp_anim o.webp,直接使用webp编码会出现播放花屏的情况。

PNG压缩

由于PNG是个无损压缩的格式,体积往往比较大,大家平时可能用到TinyPNG这个网站来压缩一些图片,它的原理是将通常每像素24位、64位的PNG图像修改为每像素8位的。

之所以能压缩成8位这么小,是因为它用了pal8这种像素格式,这是一种基于色板的像素格式,在用它对一张图片进行“编码”的时候,需要先指定一张“色板”:这张色板上只有256种颜色,每种颜色有个编号。通过这个色板对一张图片进行“编码”的时候,需要给这张图片上的每个像素在色板上找一个最相近的颜色,然后在该像素处只需要存储这个颜色对应的色板序号就行。最终在封装的时候,把色板也封装到里面,解码的时候就可以大致还原原图。这种方式适合一些色彩不是特别丰富的图像。

比如,一张图片的某一个像素是蓝色#0000FF,通常存储它需要3*8=24位,而如果色板上有这个颜色的话,存储它只需要一个字节,也就是该颜色对应的色板序号。

知道了原理,这个操作通过ffmpeg也很容易完成:

ffmpeg -i 0001.png \
  -filter_complex '[0]palettegen=transparency_color=#00000000[p];[0][p]paletteuse' \
  o.png

其中的关键部分在于中间这两个filter:palettegenpaletteuse,一个用来生成色板,一个用来使用色板。(色板其实也就是一张16x16的图片,也就是这里的[p])

通过这行命令,就可以得到一张和TinyPNG“压缩”效果差不多的图片(有时候会差很多)。

FFmpeg的色板生成算法比较简单,这方面有些人做了更为深入的探索,有兴趣的同学可以看下PngQuant这个项目。

为音乐添加封面

通常,从音乐网站或音乐app上面下载的音乐文件都会带有封面,在本地播放器播放、或者文件管理器预览时,可以看到该封面,例如使用ffplay:

ffplay with_pic.mp3

ffplay

这个封面的原理是在文件中保存一个图片通道,可以通过ffprobe看到:

ffprobe with_pic.mp3

假设现在有个不带有封面的mp3音乐文件no_pic.mp3,和一个封面图片pic.png,那么可以通过ffmpeg向其中添加一个视频静态的通道:

ffmpeg \
  -i pic.png `# 输入图片` \
  -i nopic.mp3 `# 输入mp3` \
  -codec copy `# 使用copy编码器,不对数据做处理` \
  -map 0:v:0 `# 将第0个输入文件的视频通道,输出到第0个输出文件中` \
  -map 1:a:0 `# 将第1个输入文件的音频通道,输出到第0个输出文件中` \
  output.mp3

这里的关键是-map选项,可以用它来操作文件或filter中的通道。

使用ffprobe检查output.mp3,或者使用ffplay播放该文件,就可以看到填充进去的封面了。

获取视频缩略图

通过以下命令,可以从视频中获取一帧并保存成图片:

ffmpeg -y -ss 00:10:00 -i Sherlock.S01E01.2010.mkv -frames 1 thumbnail.png

其中的关键是-ss 00:10:00-frames 1两个参数:

  • -ss参数用于指定输入文件的偏移时间,这里指定的是十分钟的位置。
  • -frames 1参数用于指定输出文件的帧数,只需要一帧。

官方文档对此也有个简单的介绍,可以参考Seeking - FFmpeg

基于此可以为视频生成一个简单的抽帧预览的小视频:

用到的python脚本如下:

#!/usr/bin/env python3
from os import system
from pathlib import Path

Path('thumbnails/').mkdir(exist_ok=True)

for i in range(10):
    min = str(i * 5).zfill(2) # 每五分钟一张图片
    name = str(i).zfill(2)
    system(f"ffmpeg -y -ss 00:{min}:40 -i 'Sherlock.S01E01.2010.mkv' -frames 1 'thumbnails/{name}.png'")

# 输入一秒一帧,输出时补充为25帧
system(f"ffmpeg -y -r 1 -i 'thumbnails/%02d.png' -r 25 thumbnails/preview.mp4")

Alpha视频

最后简单说一下一个比较复杂的例子,串联一下上述知识。

假设我们有一串序列帧,0001.png、0002.png、...0100.png,

现在想要把它转换成Alpha视频,也就是这样的:

RGB通道在右侧,Alpha通道按照灰度图的形式在左侧。

转换前后

首先来看下转换前后的异同。

转换前的序列帧是PNG格式的,拥有RGBA四个通道,上图魔方的其余部分都是空白的。

转换后的格式为mp4,mp4不支持Alpha通道。

转换后的视频右侧部分,其实就是原序列帧的RGB通道,同时原序列帧的Alpha通道值为0的像素要显示为黑色。

左侧部分其实是将Alpha通道单独提取出来,然后将其转成灰度图,也就是将Alpha通道的值分别赋给RGB三个通道(例如#FF123456就变成了#FFFFFFFF,完全透明的部分就变成了黑色,完全不透明的部分就变成了白色)。

转换前(魔方周围是透明的)

转换后

实现

那我们可以使用这行命令:

ffmpeg -r 25 -i '%04d.png' \
  -filter_complex 'split[rgb][mask];[mask]colorchannelmixer=0:0:0:1:0:0:0:1:0:0:0:1:0:0:0:1[mask];[rgb]split[fg][bg];[bg]drawbox=t=fill:color=black[bg];[bg][fg]overlay[rgb];[mask]pad=iw*2:ih[mask];[mask][rgb]overlay=w:0' \
  o.mp4

将其中的filter拆开可以得到许多个步骤,分别介绍一下:

  • split[mask][rgb];

    将输入复制成两份,名字为mask和rgb,最终分别会用作左边的遮罩部分和右边的彩色部分;

  • [mask]colorchannelmixer=0:0:0:1:0:0:0:1:0:0:0:1:0:0:0:1[mask];

    colorchannelmixer这个filter用于重组rgba通道,这里通过将rgba通道的值都设置为alpha通道的值,来得到左边的遮罩,也就是将alpha通道转化为灰度图。

  • [rgb]split[fg][bg];

    把另外一份再次复制为两份,bg和fg;

  • [bg]drawbox=t=fill:color=black[bg];

    bg作为右侧的背景,所以使用drawbox在其上绘制一个黑色的区域将其盖住;

  • [bg][fg]overlay[rgb];

    然后通过overlay将fg盖在bg上方;

  • [mask]pad=iw*2:ih[mask];

    将遮罩宽度*2,也就是给它右侧增加一块空白;

  • [mask][rgb]overlay=w:0

    最后将rgb放到mask的右侧。

    这里之所以先绘制一个黑色底色,是因为有的软件导出的PNG图片,在Alpha通道为0像素上,RGB三个通道的值可能是随机数;如果不做底色的话,ffmpeg会直接将这些随机数显示出来,就会导致类似于这样的图像:

经过上面几步,就可以完成这个转换。

另外,其中的-r 25选项用来控制序列帧的帧率为25fps。

通过filter反向还原序列帧也是可行的,思路差不多。

获取帮助

如果要对ffmpeg、ffplay、ffprobe这些命令有更详细的认识,有两种方式:

  1. 在Mac或者Linux上使用包管理器安装FFmpeg之后,默认会带上FFmpeg的所有文档,可以通过命令行下执行man ffmpegman ffmpeg-allman ffplayman ffprobe来查看,里面有完整的参数信息。
  2. 或者查看官网上面的在线文档,会更方便一些:ffmpeg Documentation

如果在使用过程中,需要某个编解码器或者filter的文档,也随时可以通过命令行查看:

# 查看所有解码器
ffmpeg -decoders
# 查看所有编码器
ffmpeg -encoders
# 查看所有muxer
ffmpeg -muxers
# 查看所有demuxer
ffmpeg -demuxers
# 查看所有filter
ffmpeg -filters

# 获取某个解码器的文档
ffmpeg -h decoder=hevc
# 获取某个编码器的文档
ffmpeg -h encoder=hevc
# 查看某个muxer的文档
ffmpeg -h muxer=mp4
# 查看某个demuxer的文档
ffmpeg -h demuxer=mp4
# 获取某个filter的文档
ffmpeg -h filter=pad

这些帮助信息中,大多数时候包含了encoder/decoder/muxer/demuxer的可配置参数的解释,或是filter的功能、输入输出、参数类型和说明等。

同样的,也可以去官网上面查:Documentation

例如,如果想要调节输出webp动图的质量,但是不知道怎么调节,可以使用ffmpeg -h encoder=libwebp_anim命令来查看这个编码器支持的参数,会发现其中有一个参数,

因此可以通过这个参数来指定webp质量,例如:

ffmpeg -i i.mp4 -c libwebp_anim -quality 30 o.webp

源码

FFmpeg是一个用C写的项目,它的代码结构很清晰,整体被分成了几个大的模块:

  • libavcodec 编解码器
  • libavformat 封装、解封装库
  • libavfilter Filter库,用于对媒体数据做中间处理
  • libavdevice 用于支持在各个平台上访问媒体硬件(如摄像头、麦克风、屏幕等)
  • libswscale 高性能的图像缩放和色彩空间转换库
  • libswresample 高性能的音频重采样库
  • libavutil 一些通用的工具函数

这些模块都可以编译成.so,如果是要在代码中使用,可以直接引用这些.so。