如何用FFMpeg生成视频
前言
FFMpeg读做“FF Mpeg”, “FF”
指的是 “Fast Forward”
,而“Mpeg”指的是 Moving Picture Experts Group
(动态图像专家组)。
根据官方介绍,FFMpeg是一个完整的、跨平台的音频和视频录制、转换和流媒体解决方案。简单来说,只要涉及 音视频开发,基本绕不开这个工具。
一、快速入门
FFMpeg
快速入门的话,建议查看阮一峰老师的《 FFmpeg 视频处理入门教程》,里面讲述了音视频处理的一些基本概念,比如FFMpeg支持的 容器、 编码格式以及 编码器;还有就是讲述 FFMpeg
的常见用法,比如查看文件信息、转换编码格式、提取音频等。
二、音视频基础知识
我自己在使用FFMpeg的时候发现,想要把FFMpeg用得明白,一些基本的音视频基础知识的了解还是很有必要的,所以在这里做下总结。
现在短视频那么火,相信大家也是 常看,而一个视频的构成其实也不复杂,就是 图像、音频、字幕的一个组合。
对于 图像,它有两个概念需要区分好,分别是 图像格式和 色彩空间。图像格式就是图片压缩编码以及存储的方式,比如我们常见的 JPEG
和 PNG
。色彩空间是颜色的数学描述方式,根据不同的表示方法分为不同的色彩模型,最常用的色彩模型有三类, RGB
(用于计算机图形学), YUV
(用于视频系统), CMYK
(用于彩色印刷)。(后面会经常看到YUV)
对于 音频,也有两个概念比较重要,一个是采集到的原始音频数据(比如PCM),另一个是压缩后的音频数据,比如AAC,后面也会经常看到。
对于 字幕,常见的有三种格式,分别是 srt
、 ssa
和 aas
。
srt字幕
即文本格式字幕,它算是最简单的字幕了,因为它仅由 时间和字幕内容构成,比如下面:
# 第一行是编号,表示第几个字幕
# 第二行是时间范围,精确到毫秒
# 第三话就是显示的文本内容
0
00:00:00,000 --> 00:00:01,000
假设张三携带10万美刀进行投资
1
00:00:02,000 --> 00:00:03,000
兑换成人民币后,银行就多了10万美刀的外汇
ssa字幕是比srt字幕更先进的字幕文件格式,而与它比较类似的ass字幕其实就是ssa字幕的plus版本,ass字幕的实质是 SSA v4.00+
,是基于SSA 4.00+编码构建的。下面是ass字幕的具体内容:
# 这是从上面的srt字幕转换得到的ass字幕
# Script Info:包含脚本的头部和总体信息
# V4+ Styles:包含了所有样式的定义
# Events:包含了所有脚本的事件,有字幕、注释、图片等
[Script Info]
; Script generated by FFmpeg/Lavc58.91.100
ScriptType: v4.00+
PlayResX: 384
PlayResY: 288
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,16,&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,0
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:00:01.00,Default,,0,0,0,,假设张三携带10万美刀进行投资
Dialogue: 0,0:00:02.00,0:00:03.00,Default,,0,0,0,,兑换成人民币后,银行就多了10万美刀的外汇
三、一个视频的构建
我之所以要用FFMpeg,源于我想通过图片生成视频,并加上音频和字幕,从而构成一个完成的视频,所以下面我主要说说在构建时的一些心路历程(坑)。
3.1 项目结构
本次实践生成的音视频都会上传到Github,可以点击这里查看:
# 项目结构
$ tree -l -L 1
.
├── add_audio # 添加音频
├── add_caption # 添加字幕
└── img_to_video # 图片转视频
3.2 图片生成视频
为了方便展示,我从网上随便找了一张图片:
图片转视频的命令如下:
$ ffmpeg -r 25 -i img001.jpg -vcodec libx264 -pix_fmt yuv420p one_img_to_video.mp4
...
[libx264 @ 0x7faf5b809200] i8c dc,h,v,p: 65% 19% 9% 7%
[libx264 @ 0x7faf5b809200] kb/s:8960.40
下面是各个参数的逐个解析:
-
-r
:rate,用于设定 视频帧率。视频帧率即每秒显示帧数,常见的有30FPS、25FPS或者24FPS。本次设定为25FPS,即每秒有25张图片。 -
-i
:input,即输入源文件。 -
-vcodec
:video codec,即视频的编码格式,常见的有H.264,即libx264
。 -
-pix_fmt
:pixel formats,即像素格式,yuv420p
是上文提到的YUV中的一种。 -
one_img_to_video.mp4
:最后输出的文件名。
生成之后的视频,可以看到 时长非常短(0秒),这是因为帧率设定是25,但是只输入了一张图片,图片数不够,所以生成的视频时长非常短。
解决办法有两种:一是降低帧率(不推荐),二是增加图片数量( 推荐)。
我一开始是通过降低帧率来提高时长(我的需求是同一张图片要显示10秒左右),因为25FPS就是一秒25张图片,那如果设置为0.1FPS,等同于 1张图片10秒,测试如下:
$ ffmpeg -r 0.1 -i img001.jpg -vcodec libx264 -pix_fmt yuv420p one_img_to_video_small_rate.mp4
通过下图,可以看到 延长时长的目的确实达到了,但是这种方式生成的MP4其实是有问题的,不仅剪辑软件无法支持(比如剪映),在添加音频、字幕的时候也非常奇怪(血的教训)。
第二种方式是增加图片数量,这也是我使用剪映之后发现的,因为 与剪映拖动图片增加视频长度的原理是一致的:
批量增加图片可以随便写个脚本就可以得到,但是图片的数量需要计算一下,比如一个时长10秒,帧率25FPS的视频就需要 10 x 25 = 250张图片:
# 输入为多张图片时,可使用这种写法
# %03d 其实就是 001、002、003...100
$ cd img_to_video
$ ffmpeg -r 25 -i img/img%03d.jpg -vcodec libx264 -pix_fmt yuv420p multi_img_to_video.mp4
这里可能有人会疑惑,为什么每次我都会带上 -pix_fmt yuv420p
参数?这其实也是一个坑,因为如果不加这个参数,有些软件没办法识别生成的MP4文件,比如Mac 的 QuickTime Player
。
原因可以从 官方文档得到,因为我们生成视频的方式其实是通过图像序列(一系列的图片)的方式,对应的编码类型为 image2
,这也是为什么有时在一些文章上可以看到他们的命令比上述命令多了 -f image2
参数(加不加都无所谓)。在这种编码下,默认的 pix_fmt
参数并不是 yuv420p
,而是通过第一张图片得到,而JPG图片用的都是 RGB,所以最终生成的视频无法识别。
3.2 视频添加音频
通过上面的方式生成的视频是没有声音的,所以我们需要通过FFMpeg为其加上音频。
有时候我们得到的音频格式并不是 MP3
,而是 WAV
,这时我们可以通过下面的命令进行转换:
$ ffmpeg -i input.wav -vn -ar 44100 -ac 2 -b:a 192k output.mp3
-i: 上文也提到过,即我们的输入文件
-vn:禁用视频,确保没有视频被包括在内
-ar:设置音频采样频率。对于输出流,它默认设置为相应的输入流的频率。对于输入流,这个选项只对音频抓取设备和原始解复用器有意义,并被映射到相应的解复用器选项中。
-ac:设置音频通道的数量。这里为2是为了确保它是立体声(2个通道)。对于输出流,它默认设置为输入音频通道的数量。对于输入流,这个选项只对音频抓取设备和原始解复用器有意义,并被映射到相应的解复用器选项中。
-b:a:将音频比特率(audio bitrate)转换为精确的192kbit/秒
上面的解释涉及到 解复用这个术语,那什么是解复用呢?当我们打开一个多媒体文件之后,第一步就是解复用,称之为Demux。为什么需要这一步,这一步究竟是做什么的?我们知道在一个多媒体文件中,既包括音频也包括视频,而且音频和视频都是分开进行压缩的,因为音频和视频的压缩算法不一样,既然压缩算法不一样,那么肯定解码也不一样,所以需要对音频和视频分别进行解码。虽然音频和视频是分开进行压缩的,但是为了传输过程的方便,还是将压缩过的音频和视频捆绑在一起进行传输。所以我们解码的第一步就是将这些绑在一起的音频和视频流分开来,也就是传说中的**解复用。**简单来说,解复用这一步就是将音频流和视频流分开,方便后续解码。
转换之后就可以为视频添加音频了,这里使用的视频是上文生成的图片视频(注意添加音频也能用wav格式,只不过我习惯用mp3)
# 拷贝视频
$ cp img_to_video/multi_img_to_video.mp4 add_audio/input.mp4
# 添加音频有多种方式:
# 方式一:流拷贝(不推荐)
# 这种方式没有编解码的过程,只有解复用,所以速度很快,目前亲测不成功,不太建议
$ ffmpeg -i input.mp4 -i input.mp3 -codec copy audio_copy.mp4
# 方式二:手动选择特定流(不推荐,亲测无效)
$ ffmpeg -i input.mp4 -i input.mp3 -map 0:v -map 1:a -c copy audio_manually.mp4
# 方式三:重新编码(亲测有效)
$ ffmpeg -i input.mp4 -i input.mp3 -c:a aac -c:v libx264 audio_recode.mp4
# 有时候我们的音频长度大于视频长度,比如本次音频长度为20s,视频长度为10s,使用上面的命令会把视频长度拉长到20s
# 如果想要音频长度与视频长度保持一致,可加上 -shortest 参数
$ ffmpeg -i input.mp4 -i input.mp3 -c:a aac -c:v libx264 -shortest audio_recode_short.mp4
3.3 视频添加字幕
添加完音频后,就可以添加字幕了,关于字幕转换工具,可以自己手写一个,也可以用现成的,比如下面这个:
使用起来也非常方便,每一行就是一行字幕,最后设置好起始时间就可以了(不一定与实际朗读匹配):
添加srt字幕的命令如下:
# 拷贝之前生成好的视频
$ cp add_audio/audio_recode.mp4 add_caption/input.mp4
# 添加字幕
$ ffmpeg -i input.mp4 -vf subtitles=input.srt video_with_srt.mp4
# 有时候可能会遇到下面的报错:Too many packets buffered for output stream 0:1
# 该异常抛出的原因是有些视频数据有问题,导致视频处理过快,容器封装时队列溢出
# 可以通过增大容器封装队列大小来解决,比如设置最大封装队列的大小为1024
$ ffmpeg -i input.mp4 -vf subtitles=input.srt -max_muxing_queue_size 1024 video_with_srt.mp4
有时候我们需要自定义字幕的样式,或者字幕的位置,这时可以先把srt字幕转换为ass字幕,再做调整。如果你安装了FFMpeg,一行命令就能完成转换,如果没有安装,也可以用一些在线工具实现,比如 字幕酱。
FFMpeg转换命令:
$ ffmpeg -i input.srt output.ass
添加ass字幕命令:
$ ffmpeg -i input.mp4 -vf "ass=output.ass" video_with_ass.mp4
最终效果如下:
如果想要控制字幕使用的文字、文字大小、以及显示位置等,则需要修改 [V4+ Styles]
里面的内容:
# 一共分为两行,第一行是字段名,第二行是字段值
# Fontname:字型
# Fontsize:字体大小
# MarginL:字幕距左边的距离,取值范围是0-PlayResX的数值
# MarginR:字幕距右边的距离,取值范围是0-PlayResX的数值
# MarginV:字幕高度,取值范围是0-PlayResY的数值
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,16,&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,0
注:其他参数的说明可参考 这篇文章
假设我要把字幕大小改为20、且字幕往上移动,则对应的改动如下:
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,20,&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,50,0
最后重新添加即可:
$ ffmpeg -i input.mp4 -vf "ass=new.ass" video_with_new_ass.mp4
最终效果如下:
写在最后
以上就是如何用FFMpeg构建完成视频的全流程了,希望对大家有所帮助!
参考教程
FFmpeg 视频处理入门教程
FFmpeg Formats Documentation
ffmpeg图片视频互转
Convert audio files to mp3 using ffmpeg
FFMPEG深入理解
TXT to SRT Converter
在线字幕格式转换工具
解决FFmpeg抛出的"Too many packets buffered for output stream 0:1."