FFmpeg视频处理
FFmpeg是一个用于音视频处理的自由软件,被广泛用于音视频开发。FFmpeg功能强大,本文主要介绍如何使用FFmpeg命令行工具进行简单的视频处理。
安装FFmpeg可以在官网下载各平台软件包或者静态编译版本,也可以使用包管理工具安装。
基本概念
容器
我们熟悉的mp4
,rmvb
,mkv
,avi
是多媒体容器文件格式(或称多媒体封装格式),所谓容器是指将不同的数据流(视频流,音频流,字幕流等)封装在一个文件(载体)中。
播放时各种流分别进行解码等处理后,然后输出到显示器和音响等设备进行播放。多媒体容器格式不同于编码格式,一个容器中可以封装多种编码格式的媒体流。
流封装了实际的媒体数据,如视频流,音频流和字幕流等。一般情况下,流中的数据只能使用一种编码格式。
帧率
帧率(frames per second, fps)是每秒画面刷新的次数,帧率越高视频越流畅。一般来说30fps就是可以接受的,60fps则可以明显提升交互感和逼真感,但是一般超过75fps一般就不容易察觉到有明显的流畅度提升了。
分辨率
分辨率表示画面的精细程度,通常用像素密度来表示,常用的单位为ppi(像素每英寸)。通常像素密度越高画面越精细,模糊程度越低。
对于视频文件而言,像素密度是无法控制的(由播放器和显示设备决定)。我们通常用视频的像素数来表示它的分辨率如1080x640, 640x320等。
比特率
比特率(bit rate)又称码率,表示多媒体流每秒输出的字节数,单位为KB/s, Kbps等。同样的压缩算法下,比特率越高音视频的质量越好。
可变码率(Variable Bitrate, VBR)指的是编码器的输出码率可以根据输入源信号的复杂度进行自适应调整,以在输出质量保持不变的条件下尽可能减少数据量。VBR适用于存储,不太适用流式传输。
固定码率(Constant Bitrate, CBR)指的是编码器输出码率固定,CBR不适合存储,对于复杂内容可能没有足够码率进行编码,从而导致质量下降,同时会在简单内容部分浪费一些码率。
采样率
每秒钟对音频信号的采样次数,采样频率越高声音还原度越高,声音更加自然,单位是赫兹 Hz。
音频文件一般使用的采样率是 44.1 kHz,也就是一秒钟采样44100次,实验发现低于这个值就会有较明显的损失,而高于这个值人的耳朵已经很难分辨,而且增大了数字音频所占用的空间。
视频编码
视频流可以看做图片的序列,我们把这个序列中的一张图片称为一帧。若存储视频中所有帧则会数据量过大,不便于存储和传输。
所幸统计表明大多数视频相邻帧之间的区别并不大,所以对于一段变化不大的视频,我们可以先完整编码帧A,其后的B帧只需要编码与A帧不同的部分,B帧后的C帧则只编码与B帧的差异。如此递推,将一段视频编码为一个序列。
当某个图像与之前的图像变化很大无法参考前面的帧来生成,我们就结束上一个序列将该帧完整编码开始一个新的序列。
H264是目前流行的一种视频编码算法,它定义了三种帧:完整编码的I帧,参考I帧生成只包含差异的P帧,以及以及参考前后帧编码的B帧。
H264采用的核心算法是帧内压缩和帧间压缩,帧内压缩是生成I帧的算法,帧间压缩是生成B帧和P帧的算法。
通常,我们也把完整编码的I帧称为关键帧。因为解码非关键帧需要解码其参考的帧,因此在截图等不需要全部解码的操作中,经常截取关键帧以提升性能。
获得音视频信息
ffprobe是FFmpeg项目提供的用于分析视频信息的命令行工具。
随意下载一个测试视频testmp4
, 然后终端中输入指令:
ffprobe -v quiet -print_format json -show_format -show_streams test.mp4
可以获得json格式输出的视频信息:
{
"streams": [ // 文件中包含的流
{
"index": 0, // 流的序号
"codec_name": "h264", // 流的编码格式
"codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", // 编码格式的全名
"profile": "High",
"codec_type": "video", // video表示这是一个视频流
"codec_time_base": "1/60",
"codec_tag_string": "avc1",
"codec_tag": "0x31637661",
"width": 1080, // 视频宽为1080像素
"height": 614, // 视频高为614像素
"coded_width": 1080,
"coded_height": 614,
"has_b_frames": 2,
"sample_aspect_ratio": "0:1",
"display_aspect_ratio": "0:1",
"pix_fmt": "yuv420p",
"level": 31,
"chroma_location": "left",
"refs": 1,
"is_avc": "true",
"nal_length_size": "4",
"r_frame_rate": "30/1", // 实际帧率
"avg_frame_rate": "30/1",
"time_base": "1/15360",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 153093,
"duration": "9.966992", // 以秒为单位的视频时间
"bit_rate": "2077265", // 视频的比特率
"bits_per_raw_sample": "8",
"nb_frames": "299",
"tags": { // 流中的附加信息,其中的字段可能为空
"rotate": 90, // 视频旋转的角度
"language": "und",
"handler_name": "VideoHandler"
}
},
{
"index": 1, // 流编号
"codec_name": "aac", // 流的编码格式
"codec_long_name": "AAC (Advanced Audio Coding)", // 编码格式的全名
"profile": "LC",
"codec_type": "audio", // 这是一个音频流
"codec_time_base": "1/44100",
"codec_tag_string": "mp4a",
"codec_tag": "0x6134706d",
"sample_fmt": "fltp",
"sample_rate": "44100", // 采样率
"channels": 2, // 声道数
"channel_layout": "stereo", // 声道布局,stereo为立体双声道
"bits_per_sample": 0,
"r_frame_rate": "0/0",
"avg_frame_rate": "0/0",
"time_base": "1/44100", // 每帧时长
"start_pts": 0,
"start_time": "0.000000", // 流开始播放时间
"duration_ts": 442367,
"duration": "10.030998", // 流时长
"bit_rate": "129341", // 比特率
"max_bit_rate": "129341",
"nb_frames": "433",
"tags": {
"language": "und",
"handler_name": "SoundHandler"
}
}
],
"format": { // 容器信息
"filename": "test.mp4", // 文件名
"nb_streams": 2,
"nb_programs": 0,
"format_name": "mov,mp4,m4a,3gp,3g2,mj2", // 封装格式名
"format_long_name": "QuickTime / MOV",
"start_time": "0.000000",
"duration": "10.055000",
"size": "2762615", // 文件字节数
"bit_rate": "2198002", // 比特率
"probe_score": 100,
"tags": {
"major_brand": "isom",
"minor_version": "512",
"compatible_brands": "isomiso2avc1mp41",
"encoder": "Lavf57.71.100"
}
}
}
示例中使用-v quiet
选项将日志级别设为quiet
避免日志信息污染json,-show_format
显示文件的容器信息,-show_stream
显示容器中流的信息,-show_frames
则可以显示视频中每一帧的信息。
更多关于ffprobe的内容可以参考官方文档
使用ffmpeg进行视频处理
ffmpeg的命令格式:
ffmpeg \
[global_options] \
[input_file_options] -i input_url \
[actions] \
[output_file_options] output_url
我们可以将ffmpeg的选项分为全局选项和局部选项,局部选项用于设置输入输出或者滤镜等,通常位于被修饰的指令前面。
ffmpeg的基本流程为将容器中的各流进行解码,然后重新编码为指定的格式。在编码之前,可以使用filter对视频进行处理。
选项
选项的详细内容请参考官方文档
-y
/ -n
-y
/-n
为全局选项, -y
表示直接覆盖已经存在的输出文件, -n
表示若某个输出文件已经存在则退出。
若没有设置-y
或-n
选项,且某个输出文件已经存在ffmpeg会询问是否要覆盖输出文件。
ffmpeg -y -i test.mp4 test.mkv
-codec
(-c
)
指定输入输出的解码编码器, 可用的编解码器可以参考官方文档:
fmpeg -y -i test.mp4 -c:v libx264 -c:a copy test.mov
codec指定为copy
则将输入流直接复制到输出流不进行编解码操作。
使用-c:STREAM_INDEX
方式可以指定某一个流的解码器,STREAM_INDEX
为stream对象的index属性。
-c:v
或-vcodec
可以为所有视频流指定编码器,-c:v:1
为第2个视频流指定编解码器。
-c:a
或-acodec
可以为所有音频流指定编码器,-c:a:12
为第13个视频流指定编解码器。
-ss
-ss
选项用于设置流的开始时间,可以设置输入输出或者滤镜。在开始时间之前的帧将被跳过不被处理(输入不被解码,输出不被编码,滤镜不被处理)。
ffmpeg -ss 2 -t 10 -i test.mp4 test.mov
时长有两种方式来表示:
- 秒数: 如
-t 10
,-t 23.167
- 时分秒: 如
-t 10:23
,-t 21:31:00.233
-t
-t
选项用于用于设置输入输出,-t
在-i
前可以限制输入时长,-t
在输出文件前可以限制输出时长。
读入test.mp4文件2s开始10s内的数据,转码后输出到test.mov:
ffmpeg -ss 2 -t 10 -i test.mp4 test.mov
读入test.mp4全部数据,全部转码后输出从第2s开始1min10s内的数据到test.mov:
ffmpeg -i test.mp4 -ss 2 -t 01:10 test.mov
-to
-to
选项类似于-t
选项,不同的是-to
指定结束时刻,-t
指定持续时间。
读入test.mp4文件2s到12s内的数据,转码后输出到test.mov:
ffmpeg -ss 2 -to 12 -i test.mp4 test.mov
读入test.mp4全部数据,全部转码后输出从01:00到01:30内的数据到test.mov:
ffmpeg -i test.mp4 -ss 01:00 -to 01:30 test.mov
-f
强制设置输入输出的文件格式,默认情况下ffmpeg会根据文件后缀名判断文件格式。
ffmpeg -formats
命令会显示所有支持的编码格式。
-filter
/ -filter_complex
使用过滤器对流进行处理,下文将简要介绍filter的相关内容。
可以使用-vf
代替-filter:v
处理视频流, -af
代替-filter:a
处理音频流。
-vframes
设置输出文件中包含的总帧数:
ffmpeg -i test.mp4 -vframes 1 test.mov
-vn
不将视频流写到输出文件中
ffmpeg -i test.mp4 -vn -a:c copy out.mp3
-r
设置某个流的帧率:
ffmpeg -i test.mp4 -r:v 30 test.mov
-s
设置帧的大小:
ffmpeg -i test.mp4 -s 1080x680 out.mp4
-an
不将音频流写到输出文件中:
ffmpeg -i test.mp4 -v:c copy -an out.mp4
-threads
设置处理线程数:
ffmpeg -threads 8 -i test.mp4 out.mp4
可以设置处理
-shortest
当最短的输入流结束后即停止编码和输出。
ffmpeg -i bgm.mp3 -i test.mp4 -shortest output.mp4
filter
过滤器会对已解码的帧进行处理,处理后的帧会被重新编码输出,整个流程可以概括为:
Input -> DecodedFrames -> FilteredFrames -> EncodedData
简单过滤器是单输入单输出的(只能处理一个流),而复杂过滤器(filter_complex)是多输入多输出的可以进行更复杂的操作。
ffmpeg支持的各种滤镜可以参考官方文档-滤镜。
scale
ffmpeg -y -i test.mp4 -vf "scale=2*in_w:2*in_h" test.mov
scale滤镜用于缩放视频, in_w
和in_h
代表输入的宽和高。
crop
ffmpeg -y -i test.mp4 -vf "crop=w=100:h=100:x=in_w/2:y=in_h,scale=400:400" test.mov
crop滤镜用于截取视频中的一个区域。
overlay
ffmpeg -y -i test.mp4 -i logo.png -filter_complex 'overlay=10:main_h-overlay_h-10' out.mp4
overlay滤镜将一个视频叠放在另一个视频上,可用于在视频中添加水印和动画等操作。
overlay的第一个输入为底层视频流,第二个输入为叠加视频流。main_w
和main_h
为底层视频的宽和高,overlay_w
和overlay_h
为叠加视频的宽和高。
drawtext
ffmpeg -y -i test.mp4 -vf "drawtext=fontfile=CourierNew.ttf:text='hello world':x=100:y=50:fontsize=24" out.mp4
drawtext滤镜用于在视频上添加文字。
fade
ffmpeg -y -i test.mp4 -vf "fade=in:st=0:d=5" out.mp4
fade滤镜可以制作淡入淡出效果
fps
ffmpeg -y -i test.mp4 -vf "fps=60" out.mp4
fps滤镜通过删除帧或者复制帧的方法强制设置帧率。
ffmpeg -y -i test.mp4 -vf "fps=1" img%3d.png
ffmpeg -y -i test.mp4 -r 1 img%3d.png
上面两条指令都可以对视频每秒截取一帧图像,-r
选项会截取关键帧并不一定截取0s、1s...处的帧,fps滤镜处理的是已经解码的帧因此可以精确的按照时间截取。
因为fps滤镜会解码要截图的视频片段,因此这种方式截图会慢很多。
应用示例
视频转码
ffmpeg -y \
-i test.mp4 \
-vcodec copy \
-acodec copy \
out.mkv
这条指令将容器格式由MP4转换到MKV,使用ffprobe
检查输出文件可以发现,视频流没有发生变化,但是封装格式改变为mkv格式。
-vcodec
是一个简单过滤器用于处理视频编码,copy
表示将视频流复制到输出文件中。-acodec
是处理音频编码的过滤器。
提取视频流
ffmpeg -y \
-i test.mp4 \
-vcodec copy \
-an \
out.mp4
-an
表示不保留音频流。
提取音频
ffmpeg -y \
-i test.mp4 \
-ar 44100 -ac 2 -ab 192 \
-f mp3 \
output.mp3
分析:
-ar
: 指定输出音频采样率-ac
: 指定输出音频通道(channel)数, 这里设置为双声道-ab
: 指定输出音频比特率,单位kb/s
按帧截取图像
截取第2s开始的10帧图像, 伸缩为352x240:
ffmpeg -y \
-ss 2 -i test.mp4 \
-vframes 10 \
-f image2 \
-s 352x240 \
img%03d.png
分析:
-ss 2 -i test.mp4
:ss
为开始时间,用秒数或者hh:mm:ss[.xxx]格式表示。-i test.mp4
表示输入源-vframes
: 指定截取的帧数, 这里是截取前10帧(从-ss
指定开始时间算起)-f
: 指定输出文件的格式,如: image2, mjpeg, gif-s
: 对输出画面进行缩放img%03d.png
: 格式化输出文件名,本示例中输出img001.png, img002.png等。
-ss
参数也可以放在vframes
前:
ffmpeg -y \
-i test.mp4 \
-ss 2 -vframes 1 \
-f image2 \
-s 352x240 \
img.png
-ss
参数是局部选项用于设置其后的一个命令,-ss 2 -i test.mp4
表示从输入视频的第2s开始处理,忽略前两秒的内容。
而-ss 2 -vframes 1
表示从第2s开始截取,此时前2s的内容已经进行了解码。
对不需要处理的部分进行解码会浪费大量时间,因此建议使用-ss 2 -i test.mp4
来表示截图开始时间。
按时间截取图像
从第2s到第12s内,每秒截取1帧图像:
ffmpeg -y \
-ss 2 -i test.mp4 \
-r 1 -t 10 \
-f image2 \
-s 352x240 \
img%03d.png
分析:
-t
: 指定截取时长,这里截取10s-r 1
:-t
的局部选项设置每秒截取的帧数(截取帧率),若不设置则截取全部帧
和-vframe
一样-t
的开始时间也有两种设置方式,基于同样的理由同样建议将-ss
放在输入前。
截取视频片段
截取视频片段的方法与截图方法类似,只是将输出格式变为视频:
按时间截取:
ffmpeg -y \
-ss 2 -i test.mp4 \
-r 20 -t 10 \
-s 352x240 \
clip.mp4
因为输出为视频,-r
指定的截取帧率即为输出视频帧率。
按帧数截取:
ffmpeg -y \
-ss 2 -i test.mp4 \
-vframes 120 \
-s 352x240 \
clip.mp4
截取视频区域
截取视频区域:
ffmpeg -y \
-ss 2 -i test.mp4 \
-r 1 \
-t 10 \
-filter_complex "[0:v]crop=w=100:h=100:x=12:y=34,scale='400:400'[v]" \
-map "[v]" \
img%03d.png
crop
滤镜可以截取视频部分区域,[0:v]crop=w=100:h=100:x=12:y=34,scale='400:400'[v]
截取了左上角在(12,34)处,宽为100,高为100的矩形框中的内容,并将截图放大到400x400。
拼接视频
ffmpeg -i "concat:1.mp4|2.mp4|3.mp4" -c copy output.mp4
将图片合并为视频
ffmpeg -i img%3d.png output.gif
ffmpeg -i img%3d.png output.mp4
添加音频
ffmpeg -i bgm.mp3 -i test.mp4 output.mp4
添加水印
ffmpeg -y \
-i test.mp4 \
-i 1.png \
-filter_complex "[1]scale=w=480:h=280[s];[0][s]overlay=x=main_w-overlay_w-10:y=main_h-overlay_h-10[ov]" \
-map "[ov]" \
output.mp4
使用filter_complex
先将水印图片(输入1)放大到480x280, 然后使用overlay滤镜将放大后的流[s]覆盖到视频(输入0)上。
若不需要使用scale进行缩放,则可以简化filter_complex
表达式:
ffmpeg -y \
-i test.mp4 \
-i 1.png \
-filter_complex "overlay=x=main_w-overlay_w-10:y=main_h-overlay_h-10"
output.mp4
添加动画
ffmpeg -y -i test.mp4 -t 10 -loop 1 -framerate 6 -i ani%3d.png -filter_complex 'overlay=10:main_h-overlay_h-10' out.mp4
将多张图片(ani001.png, ani002.png...)组成动画, 然后将这个动画叠加在视频的左下角。-t 10 -loop 1
会循环播放动画,持续10s。
该方式也支持gif格式的动画。
添加文字
ffmpeg -y -i test.mp4 -vf "drawtext=fontfile=CourierNew.ttf:text='hello world':x=100:y=50:fontsize=24" out.mp4
添加字幕
添加字幕有两种方式:
- 将字幕添加为独立的流,mkv,avi等封装格式支持此种方式,mp4格式不支持
- 将字幕叠加到视频中
添加字幕流:
ffmpeg -i video.mp4 -vf subtitles=subtitle.srt out.mp4
叠加字幕:
ffmpeg -i test.mp4 -i sub.srt -filter_complex "[0][1]overlay[v]" -map "[v]" out.mp4
HowToBurnSubtitlesIntoVideo详细介绍了烧录字幕的方法,作者建议尽量使用字幕流的方法添加字幕。
旋转视频
旋转视频有两种方式:
- 在视频元信息中添加旋转角度信息,由播放器执行旋转
- 将每帧图像旋转
添加元信息:
ffmpeg -i test.mp4 -metadata:s:v rotate="90" -codec copy out.mp4
逐帧旋转:
ffmpeg -i test.mp4 -vf "transpose=1" out.mp4
transpose
滤镜的文档