本文会介绍一些FFmpeg的常用命令(也包含FFplay,FFprobe)。笔者在一开始接触FFmpeg时也是从网上找一些文章来看,但都是零零散散,也不知为什么是这个命令,最近为了完成一些任务啃了啃FFmpeg的文档,总结了一些常用且实用的命令。
本文介绍的内容不是从零开始的,不会教你去安装,也不会特别难,但是有点长。建议阅读本文之前阅读一下以下2篇文章:
ffprobe 是一个用来收集媒体文件信息的工具,可以以指定的格式打印出媒体文件信息。你可以用ffprobe查看视频,音频甚至是一张图片的信息。
ffprobe -loglevel quiet -of json -show_format -show_streams video.webm
ffprobe -v quiet -of json -show_format -show_streams video.webm
ffprobe -v 8 -of json -show_format -show_streams video.webm
# -v = -loglevel 日志的级别,常用的参数就是 -v quiet / -v 8,这样就可以获得一个比较干净的输出。
# -of = -print_format 输出的格式,json应该算是可读性比较高的,也可以选择 xml, csv 等。
# -show_format 打印格式信息
# -show_streams 打印流信息
这里会用到 vimdiff,如果会用 vim 的同学可以尝试一下。
vimdiff 的基本用法是:
vimdiff file1 file2
所以比较两个媒体文件的差异还是比较简单的:
vimdiff <(ffprobe -v quiet -of json -show_format -show_streams video1.mp4) <(ffprobe -v quiet -of json -show_format -show_streams video2.mp4)
阮一峰老师的文章中讲的不错,不再赘述。
FFmpeg官网中提供了一组分辨率的值:
分辨率名称 | 分辨率(宽 x 高) |
---|---|
hd480 | 852 x 480 |
hd720 | 1280 x 720 |
hd1080 | 1920 x 1080 |
2k | 2048 x 1080 |
4k | 4096 x 2160 |
也有一些文章中提到另外一组分辨率的值:
分辨率名称 | 分辨率(宽 x 高) |
---|---|
SD (Standard Definition) | 720 x 576 |
HD (High Definition) | 1280 x 720 |
Full-HD (High Definition / 2K) | 1920 x 1080 |
UHD (Ultra High Definition) | 3840 x 2160 |
4K (Hi-Vision System (HV)) | 4096 × 2160 |
8K (Super Hi-Vision System (SHV)) | 7680 × 4320 |
其实不必太纠结,一般用视频高度就可以了,本文中后面的处理也是这样做的。
目前比较常用的 44,100 Hz 和 48,000 Hz,即 44.1k 和 48k。
终于进入正题,开始划重点:
$ ffmpeg \
[全局参数] \
[输入文件参数] \
-i [输入文件] \
[输出文件参数] \
[输出文件]
这个命令结构是从阮一峰老师那里拿过来的,对于一般的转码这个命令结构已经够用了。实际上,ffmpeg支持多个输入,多个输出(这里有个印象就好)。
ffmpeg -i video.webm video.mp4
ffmpeg -y -v quiet -i video.webm video.mp4
将webm格式的视频转换成mp4。很简单是不是。
# -y 表示输出结果文件存在时直接覆盖,不会询问
# -n 表示输出结果文件存在时终止转换
# -v 前面说过,是日志级别,-v quiet 就是什么也不输出,包括 banner
# -hide_banner 可以不输出 banner
加一些参数,指定音频编码,视频编码
ffmpeg -i video.webm -c:a aac -c:v h264 video.mp4
# -c = -codec 指定编码器
# -c:a 指定音频编码 -c:a aac 指定音频为 aac
# -c:v 指定视频编码 -c:v h264 指定视频为 h264
# -c:a copy 直接复制 音频编码
# -c:v copy 直接复制 视频编码
# -c copy 直接复制音频视频编码
指定分辨率
ffmpeg -i video.mp4 -vf scale=1280:720 video_1280_720.mp4
ffmpeg -i video.mp4 -vf scale=852:480 video_852_480.mp4
# -vf = -filter:v 这个参数在后面还要用到很多次
当然还可以只指定宽或者高,而对应的高或者宽自动计算出来
ffmpeg -i video.mp4 -vf scale=-1:720 video_1280_720.mp4
ffmpeg -i video.mp4 -vf scale=1280:-1 video_1280_720.mp4
-1 就表示这个值是缺省值,可以自动计算出来
但是这里有个坑,就是yuv编码中,分辨率必须是偶数。所以当缺省的宽或者高计算出来是一个奇数的话,就会报错。这个时候我们可以使用-2来代替-1,这样计算出来的结果会取偶数,实际上,我更推荐从一开始就用-2。
# 比如原始分辨率是 1920 * 1080
ffmpeg -i video.mp4 -vf scale=-1:480 video_480.mp4
# 使用 -1 计算就会报错,因为算出来的是 853 * 480
ffmpeg -i video.mp4 -vf scale=-2:480 video_480.mp4
# 使用 -2 计算就不会报错,因为算出来的是 852 * 480
音频可以调整的参数:音频编码、音频声道数、音频采样率、音频码率
视频可以调整的参数:视频编码、视频码率、视频分辨率、视频每秒帧数
关于码率,有一篇文章 《如何计算视频最佳码率》 可以参考一下,个人不太懂,如果有懂的可以推荐一下。
ffmpeg -i video.webm -c:a aac -ac 2 -ar 48000 -b:a 128k -c:v h264 -b:v 1000k -vf 'scale=1920:1080,fps=30' video.mp4
-c:a aac 指定音频编码aac
-ac 2 声道双声道
-ar 48k 采样率
-b:a 128k = -ab 128k 指定音频码率,推荐使用 -b:a
-c:v h264 指定视频编码h264
-b:v 128k = -vb 1000k 指定视频频码率,推荐使用 -b:v
-vf 'scale=1920:1080,fps=30' 指定分辨率 每秒帧数
合并视频/拼接视频要求视频合适尽可能一致,否则合成的视频可能会无法播放。
常用的方式是将需要拼接的视频文件路径写入一个文件,然后用 FFmpeg concat 方法拼接:
echo "file $PWD/video-1.mp4" >> videos.txt
echo "file $PWD/video-2.mp4" >> videos.txt
echo "file $PWD/video-3.mp4" >> videos.txt
ffmpeg -f concat -safe 0 -i videos.txt -c copy video-concat-123.mp4
当然通过Linux命令的技巧是可以写成一个命令的:
ffmpeg -f concat -safe 0 -i <(echo "file $PWD/video-1.mp4"; echo "file $PWD/video-2.mp4"; echo "file $PWD/video-3.mp4";) -c copy video-concat-123.mp4
合并视频也是可以进行转码的
ffmpeg -f concat -safe 0 -i <(echo "file $PWD/video-1.webm"; echo "file $PWD/video-2.webm"; echo "file $PWD/video-3.webm";) -c:a aac -c:v h264 video-concat-123.mp4
后面会提到如何给拼接并转码成HLS格式的视频
ffmpeg -loop 1 -i cover.jpg -i input.mp3 -c:v libx264 -c:a aac -b:a 192k -shortest output.mp4
上面命令中,有两个输入文件,一个是封面图片 cover.jpg
,另一个是音频文件 input.mp3
。-loop 1
参数表示图片无限循环,-shortest
参数表示音频文件结束,输出视频就结束。
给视频添加水印可真是个比较麻烦的事情,这里也仅仅是列出了一些比较常用的类型,举一反三加阅读文档就可以了。
这种算是比较简单的了
ffmpeg -i video.mp4 -vf "movie=watermark.png,scale=100:100[logo];[in][logo]overlay=main_w-overlay_w-10:10[out]" video-logo-01.mp4
ffmpeg -i video.mp4 -vf "movie=watermark.png,scale=128:128[logo];[in][logo]overlay=W-w-50:50[out]" video-logo-02.mp4
ffmpeg -i video.mp4 -vf "movie=watermark.png,scale=128:128[logo];[in][logo]overlay=W-w-50:H-h-50[out]" video-logo-03.mp4
这里用到了前面用过的 -vf 参数
movie=watermark.png,scale=100:100[logo]
# scale 表示 这个图片会被缩放到 100 x 100 的大小
这个表示我输入一个图片,我把他叫做 logo
[in][logo]overlay=main_w-overlay_w-10:10[out]
这个表示 我用在输入 [in] 上添加 logo,添加的位置为 overlay=main_w-overlay_w-10:10
这里需要解释:overlay=x=50:y=50 这表示给logo设置一个位置,这里的 x 和 y 可以省略,即
overlay=50:50
这表示视频左上角坐标为 (0, 0), 向右,向下 50 像素。
而 main_w 和 W 一样表示 视频的宽度,main_h 和 H 一样表示视频高度,overlay_w-10 和 w-10 一样,表示横向的偏移量,overlay_h-10 和 h-10 一样 表示纵向的偏移量。
overlay=W-w-50:50 就表示,视频左上角坐标为 (0, 0), 向右 视频宽度减去偏移量50的像素,向下 50 像素,即从右上角向左向下 50 像素。
同理:
# 左上角
overlay=50:50
# 右上角
overlay=W-w-50:50
# 左下角
overlay=50:H-h-50
# 右下角
overlay=W-w-50:H-h-50
这里给出一个例子,一个logo三个位置交替出现,读懂需要一点编程的思维
ffmpeg -i video.mp4 -vf "movie=watermark0.png,scale=80:80[watermark0];movie=watermark0.png,scale=80:80[watermark1];movie=watermark0.png,scale=80:80[watermark2];[in][watermark0]overlay=x='if(gte(mod(t,30),0)*lt(mod(t,30),10),W-w-80,NAN)':y=80[tmp],[tmp][watermark1]overlay=x='if(gte(mod(t,30),10)*lt(mod(t,30),20),80,NAN)':y=80[tmp],[tmp][watermark2]overlay=x='if(gte(mod(t,30),20)*lt(mod(t,30),30),W-w-80,NAN)':y=H-h-80[out]" video-watermark.mp4
这里输入的一张图片被分成了三个输入 watermark0 watermark1 watermark2
逻辑是在 0-10 秒显示 watermark0, 10-20 秒显示 watermark1, 20-30 秒显示 watermark2
这个时间可以自己定义,用到的是 ffmpeg 中 if gte lt mod 等运算符,和默认的时间 t
中间的输出用 tmp 表示,同时也作为下一个水印的输入。
有了上面的例子,下面这个应该好理解了
# 三个logo交替出现
ffmpeg -i video.mp4 -vf "movie=watermark0.png,scale=150:150[watermark0];movie=watermark1.png,scale=150:150[watermark1];movie=watermark3.png,scale=150:150[watermark2];[in][watermark0]overlay=x='if(gte(mod(t,30),0)*lt(mod(t,30),10),W-w-80,NAN)':y=80[tmp],[tmp][watermark1]overlay=x='if(gte(mod(t,30),10)*lt(mod(t,30),20),W-w-80,NAN)':y=80[tmp],[tmp][watermark2]overlay=x='if(gte(mod(t,30),20)*lt(mod(t,30),30),W-w-80,NAN)':y=80[out]" video-watermark.mp4
这里输入的三张图片分别为 watermark0 watermark1 watermark2
跑马灯水印实际上就是图片位置x,y随着时间t的变化,可以写成函数
# 从左到右
ffmpeg -i video.mp4 -vf "movie=watermark0.png,scale=128:128[logo];[in][logo]overlay=x='mod(t*20,W)':50[out]" video-logo.mp4
ffmpeg -i video.mp4 -vf "movie=watermark0.png,scale=128:128[logo];[in][logo]overlay=x='mod(t*20,W+128)-128':50[out]" video-logo.mp4
# 从左上到右下
ffmpeg -i video.mp4 -vf "movie=watermark0.png,scale=128:128[logo];[in][logo]overlay=x='mod(t*20,W)':y='mod(t*20,H)'[out]" video-logo.mp4
ffmpeg -i video.mp4 -vf "movie=watermark0.png,scale=128:128[logo];[in][logo]overlay=x='mod(t*20,W+128)-128':y='mod(t*20,H+128)-128'[out]" video-logo.mp4
ffmpeg -i video.mp4 -vf "movie=watermark0.png,scale=128:128,colorkey=0x000000:0.6:1.0[logo];[in][logo]overlay=W-w-50:50[out]" video-logo.mp4
ffmpeg -i video.mp4 -vf "drawtext=fontfile=simhei.ttf:text='Github is Great!':x='mod(t*20,W)':y='mod(t*10,H)':fontsize=24:fontcolor=white:shadowy=2" video-watermark-text.mp4
ffmpeg -i video.mp4 -vf "drawtext=text='Github is Great!':x='mod(t*20,W)':y='mod(t*10,H)':fontsize=24:fontcolor=white:shadowy=2" video-watermark-text.mp4
ffmpeg -i video.mp4 -vf "drawtext=fontfile=SourceHanSerifCN-Regular.otf:text='GitHub真棒':x='mod(t*20,W)':y='mod(t*10,H)':fontsize=24:fontcolor=white:shadowy=2" video-watermark-text.mp4
ffmpeg -i video.mp4 -vf "drawtext=fontfile=SourceHanSerifCN-Regular.otf:text='这个水印真厉害!!!哈哈哈!!!':x='mod(t*20,W)':y='mod(t*10+470,H)':fontsize=24:fontcolor=white:shadowx=2:shadowy=2" video-watermark-text.mp4
ffmpeg -i video.mp4 -vf "movie=watermark0.png,scale=150:150[watermark0];movie=watermark2.png,scale=150:150[watermark1];[in][watermark0]overlay=x='if(lt(mod(t,20),10),50,NAN)':y=50[tmp],[tmp][watermark1]overlay=x='if(gt(mod(t,20),10),W-w-50,NAN)':y=50[tmp],[tmp]drawtext=fontfile=simhei.ttf:text='Github is Great!':x='mod(t*20,W)':y='mod(t*10,H)':fontsize=24:fontcolor=white:shadowx=2:shadowy=2[out]" video-watermark.mp4
M3U8文件是指UTF-8编码格式的M3U文件。M3U文件是记录了一个索引纯文本文件,打开它时播放软件并不是播放它,而是根据它的索引找到对应的音视频文件的网络地址进行在线播放。
M3U是一种播放多媒体列表的文件格式,它的设计初衷是为了播放音频文件,比如MP3,但是越来越多的软件现在用来播放视频文件列表,M3U也可以指定在线流媒体音频源。很多播放器和软件都支持M3U文件格式。
youtube-dl
ffmpeg -i video.webm -c:a aac -ac 2 -c:v h264 -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time+append_list' -hls_time 15 -hls_segment_filename "hls/%06d.ts" hls/index.m3u8
这个命令里面有太多的参数了,实际上平时不会用到这么多。
前面讲过 -c:a aac -ac 2 -c:v h264 是指定音视频格式的参数,实际操作中,如果 音频编码是 aac,视频编码是 h264 可以直接使用 -c copy 参数,不改变编码,加快切片速度。
ffmpeg -i video.mp4 -c copy -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time+append_list' -hls_time 15 "hls/1080/%06d.ts" hls/1080/index.m3u8
ffmpeg -i video.mp4 -vf scale=1280:720 -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time+append_list' -hls_time 15 -hls_segment_filename "hls/720/%06d.ts" hls/720/index.m3u8
ffmpeg -i video.mp4 -vf scale=852:480 -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time+append_list' -hls_time 15 -hls_segment_filename "hls/480/%06d.ts" hls/480/index.m3u8
同时输出多种分辨率
ffmpeg -i video.mp4 \
-c copy -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time' -hls_time 15 -hls_segment_filename "hls/1080/%06d.ts" hls/1080/index.m3u8 \
-vf scale=-2:720 -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time' -hls_time 15 -hls_segment_filename "hls/720/%06d.ts" hls/720/index.m3u8 \
-vf scale=-2:480 -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time' -hls_time 15 -hls_segment_filename "hls/480/%06d.ts" hls/480/index.m3u8
同时输出多种分辨率的另一种方法
ffmpeg -i video.mp4 \
-filter_complex "[v:0]split=3[vtemp001][vtemp002][vout1080];[vtemp001]scale=w=1280:h=720[vout720];[vtemp002]scale=w=852:h=480[vout480]" \
-bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time' \
-hls_time 15 hls/multi/%06d_%v.ts \
-map "[vout1080]" \
-map "[vout720]" \
-map "[vout480]" \
-map a:0 \
-map a:0 \
-map a:0 \
-var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \
-master_pl_name index.m3u8 \
hls/multi/index_%v.m3u8
ffmpeg -ss 0 -t 300 -i video.webm -c:a aac -ac 2 -c:v h264 -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time' -hls_time 15 -hls_segment_filename "hls/%06d.ts" hls/index.m3u8
-ss 0 -t 300 表示从 0 秒开始,-t 时长
ffmpeg -f concat -safe 0 -i <(echo "file $PWD/video-1.mp4"; echo "file $PWD/video-2.mp4"; echo "file $PWD/video-3.mp4";) -c copy -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time' -hls_time 15 -hls_segment_filename "hls/%06d.ts" hls/index-123.m3u8
这种拼接方法在不同格式视频(不同分辨率,码率,采样率)时可能会出现错误,
可以采用另一种方法转换HLS
# 使用追加的模式 append_list
ffmpeg -y -hide_banner -i video.webm -c:a aac -c:v h264 -vf 'scale=720:-2' -movflags faststart -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time+append_list' -hls_time 10 -hls_segment_filename hls/%d.ts hls/index.m3u8
ffmpeg -y -hide_banner -i video.mp4 -c:a aac -c:v h264 -vf 'scale=720:-2' -movflags faststart -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time+append_list' -hls_time 10 -hls_segment_filename hls/%d.ts hls/index.m3u8
ffmpeg -y -hide_banner -i video.mp4 -c:a aac -c:v h264 -vf 'scale=720:-2' -movflags faststart -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_playlist_type vod -hls_flags 'split_by_time+append_list' -hls_time 10 -hls_segment_filename hls/%d.ts hls/index.m3u8
HLS 加密需要一个密钥 key,一个初始向量 iv
openssl rand 16 > enc.key
touch enc.keyinfo
echo "https://example.com/enc.key" > enc.keyinfo
echo "enc.key" >> enc.keyinfo
echo $(openssl rand -hex 16) >> enc.keyinfo
ffmpeg -y -i video.mp4 -c copy -bsf:a aac_adtstoasc -bsf:v h264_mp4toannexb -f hls -hls_key_info_file enc.keyinfo -hls_playlist_type vod -hls_flags 'split_by_time' -hls_time 15 -hls_segment_filename "hls-encrypted/%6d.ts" video-encrypted.m3u8
openssl rand 16 生成的是一个乱码的字符串 openssl rand -hex 16 输出的是转成 16 进制的字符串
如果要存储密钥和向量可以都用 16 进制,在 Java 里可以这样写:
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomUtils;
import java.nio.charset.StandardCharsets;
// 用于存储在数据库中
String key = new String(Hex.encodeHex(RandomUtils.nextBytes(16)));
String iv = new String(Hex.encodeHex(RandomUtils.nextBytes(16)));
// 用于转码时使用
File encKeyFile = new File("enc.key");
File encKeyInfoFile = new File("enc.keyinfo");
FileUtils.writeByteArrayToFile(encKeyFile, Hex.decodeHex(key));
FileUtils.writeStringToFile(encKeyInfoFile, "https://example.com/enc.key\n", StandardCharsets.UTF_8, false);
FileUtils.writeStringToFile(encKeyInfoFile, "enc.key\n", StandardCharsets.UTF_8, true);
FileUtils.writeStringToFile(encKeyInfoFile, iv + "\n", StandardCharsets.UTF_8, true);
用到的一些包:
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
implementation("org.apache.commons:commons-lang3:3.11")
// https://mvnrepository.com/artifact/commons-io/commons-io
implementation("commons-io:commons-io:2.7")
// https://mvnrepository.com/artifact/commons-codec/commons-codec
implementation("commons-codec:commons-codec:1.14")
ffmpeg -i video.mp4 -ss 100 -vframes 1 -q:v 1 pic/video_pic_100.png