压缩发送微信的视频文件

20200310 更新:MeGUIBitrate Calculator(快捷键:Ctrl+B)也可以实现计算并配置码率的功能。

这几天需要给我的学生在微信群里发一些视频讲解,但是由于在录播室录制的视频体积较大,无法通过微信发送。5分钟之内的视频倒是可以直接通过微信手机版发送,但是 1080p 的视频自动被压成 640p,清晰度损得太多了。于是就想自己手动压缩,在保证视频大小的基础上,尽可能的提高清晰度。

之前在网上搜过各种视频剪辑、转化工具,诸如爱剪辑之类的,但效果都不是很理想,最主要的问题是没法控制最终生成的文件的大小。

最终,还是决定回归最原始的工具——FFmpeg。

在查阅文档[1]时候发现,有两种比较好的压缩码率的方式,一种是使用 CRF 参数,另一种是 2 pass 模式。

使用 CRF 参数,可以较好的控制生成视频的质量,但不太好控制视频的大小,而使用 2 pass 模式可以通过指定码率来控制文件的大小。因此我最终采用了第二种方法。

基本的思路是,先通过计算确定所需的码率:

总码率 = (文件大小[单位:MB] ×\times 1024 ×\times 8)/(时长)
视频码率 = 总码率 - 音频码率

这里面 ×8 的目的是把单位从 KiB 转化为 kBit

然后使用计算得到的视频码率(例如:400kBit/s)对原视频进行转码。这里采用 H.265 编码,相比原始的 H.264 编码,可以在同等质量下得到更小的文件。

1
2
ffmpeg -y -i input.mp4 -c:v libx265 -b:v 400k -x265-params pass=1 -an -f mp4 /dev/null && \
ffmpeg -i input.mp4 -c:v libx265 -b:v 400k -x265-params pass=2 -c:a copy output.mp4

得到的文件基本就会满足我们对于文件大小的要求。

需要注意的是,在设置文件大小的时候需要设成 24M 而不是 25M,因为这里得到的只是一个平均码率,有可能得到的文件会比设置的稍微大一点,但实测不会大超过 1M

完整的脚本[2]如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env bash

### 设置输出文件大小为 24M
out_size=24

### 获取视频文件的时长,单位为秒
length=$(mediainfo --Inform="Video;%Duration%" "$1")

### 计算输出文件的总码率
bitrate_all=$(echo "${out_size}*8192/(${length}/1000)" | bc)

### 获取视频文件的音频码率
bitrate_audio_orig=$(mediainfo --Inform="Audio;%BitRate%" "$1")
if [ -z ${bitrate_audio_orig} ]; then
bitrate_audio_orig=$(mediainfo --Inform="Audio;%BitRate_Nominal%" "$1")
fi

### 如果音频码率过大,则降为 128k
if [ ${bitrate_audio_orig} -gt 150000 ]; then
convert_audio=true
bitrate_audio=128
else
bitrate_audio=$(echo "${bitrate_audio_orig}/1000" | bc)
fi

### 如果视频时间较长,总码率较低,则进一步降低音频码率至 48k
if [ ${bitrate_all} -lt 250 ]; then
convert_audio=true
bitrate_audio=48
fi

### 计算输出文件的视频码率
bitrate_video=$(echo "${bitrate_all}-${bitrate_audio}" | bc)

### 重命名输出文件
in_file="$1"
out_file=$(echo ${in_file} | sed -e 's/.mp4/_25M.mp4/')

### 使用 h265 编码,2 pass 模式压缩文件
ffmpeg -y -i ${in_file} -c:v libx265 -b:v ${bitrate_video}k -x265-params pass=1 -an -f mp4 /dev/null
if [ ${convert_audio} = true ]; then
ffmpeg -i "$1" -c:v libx265 -b:v ${bitrate_video}k -x265-params pass=2 -c:a aac -b:a ${bitrate_audio}k ${out_file}
else
ffmpeg -i "$1" -c:v libx265 -b:v ${bitrate_video}k -x265-params pass=2 -c:a copy ${out_file}
fi

  1. https://trac.ffmpeg.org/wiki/Encode/H.265 ↩︎

  2. https://gist.github.com/wangjiezhe/576a3820b2519d8684aab39859340cb9 ↩︎