前言
最近 Bili23 Downloader 项目需要精简程序体积大小,而原先使用的 FFmpeg 大小高达 80 MB,添加了很多无用的功能,因此现在需要重新编译一个 FFmpeg,根据实际需要进行精简。
本文的编译配置已按 FFmpeg 9.0.1 重新整理。旧版配置里有几个参数会导致转码性能大幅下降、甚至让部分功能直接失效,这次一并修正,具体见下文的「几个必须避开的坑」一节。
准备工作
前往官网下载安装 MSYS2,不过多赘述。
配置 MSYS2 环境
安装 MSYS2 后,进入安装目录,运行 ucrt64.exe,进入 Shell。
接下来安装 ucrt64 工具链。
pacman -S mingw-w64-ucrt_x86_64-gcc继续安装 make、pkg-config、nasm 和 diffutils 包。
pacman -S make pkg-config mingw-w64-ucrt_x86_64-nasm mingw-w64-ucrt_x86_64-diffutils目录约定
本文统一把源码和产物放在 D:\build\ 下,对应 MSYS2 里的 /d/build/,目录结构如下:
D:\build\
├── ffmpeg-9.0.1\ # FFmpeg 源码
├── lame-3.100\ # LAME 源码
└── opt\ # 安装产物
├── lame-3.100\
└── ffmpeg\后面脚本里的路径都基于这个约定,实际使用时按自己的目录调整即可。
下载 FFmpeg 源码
前往官网下载 FFmpeg 源码,这里使用的是 9.0.1 版本。
将下载的 ffmpeg-9.0.1.tar.xz 移动到 D:\build\,进入目录解压。
cd /d/build
bsdtar -xf ffmpeg-9.0.1.tar.xz编译 libmp3lame(可选)
如果需要处理 mp3 格式,还需要先编译 libmp3lame。
下载 LAME 源码,同样解压到 D:\build\lame-3.100。
在源码目录下新建 build.sh,内容如下:
#!/bin/bash
set -e
# 必须显式传 CFLAGS,否则编出来的是完全没有优化的 -O0 代码
OPT_CFLAGS="-O3 -fomit-frame-pointer -pipe"
./configure \
--prefix=/d/build/opt/lame-3.100 \
--disable-shared \
--disable-frontend \
--enable-static \
CFLAGS="${OPT_CFLAGS}"
# 确认优化标志已经落到 Makefile 里,下面这行必须能看到 -O3
grep -m1 '^CFLAGS' libmp3lame/Makefile为什么一定要手动传 CFLAGS
这是 lame 3.100 的一个陷阱,不注意的话编出来的库会慢三倍以上。
autoconf 的 AC_PROG_CC 本来已经把 CFLAGS 设成了 -g -O2,但 configure.in 紧接着第 52 行的一句
CFLAGS="${ac_save_CFLAGS}"又把它还原成用户传进来的原值 —— 没传就是空字符串。本该由 OPTIMIZATION 变量补上优化标志,可 --enable-expopt 默认为 no,在 MinGW/x86_64 这条路径上它最终也是空的,于是 libmp3lame/Makefile 里就只剩下
CFLAGS =gcc 命令行不带 -O 时默认就是 -O0,mp3 编码这种浮点密集的活因此慢了三倍多。
实测(30 分钟 44.1kHz 立体声 AAC 转 mp3,-c:a libmp3lame -q:a 2):
| 构建方式 | 耗时 |
|---|---|
| CFLAGS 为空,即 -O0 | 22.7 秒 |
| 优化正常的第三方构建 | 7.4 秒 |
同一组文件只解码不编码(转 flac),两边分别是 1.63 秒和 1.78 秒,解码毫无差距 —— 慢的部分全部落在 libmp3lame 内部,这就是把优化标志补回来的依据。
有两个标志是刻意不加的:
- 不加
-march:产物要分发给别人,锁定基线指令集会牺牲老机器的兼容性,这一点与 FFmpeg 那边的取舍保持一致。 - 不加
-ffast-math:它会放宽浮点语义,而 lame 的心理声学模型依赖可预期的浮点行为。
配置完成后开始编译。(-j$(nproc) 表示按 CPU 核心数并行编译,也可以直接写 -j8)
./build.sh
make clean && make -j$(nproc) && make install需要注意的是,这个库是静态链接进 ffmpeg.exe 的,只重编它并不会让已有的 ffmpeg.exe 变快,装好之后必须回到 FFmpeg 目录再完整编一次。
配置 FFmpeg 编译参数
进入 FFmpeg 源码目录,新建一个 build.sh 文件,文件内容如下:
#!/bin/bash
set -e
export ARCH=x86_64
LAME_PREFIX=/d/build/opt/lame-3.100
./configure \
--prefix=/d/build/opt/ffmpeg \
--disable-doc \
--disable-shared \
--disable-everything \
--disable-programs \
--disable-network \
--disable-avdevice \
--disable-autodetect \
--disable-avx512 \
--enable-demuxer='concat,ffmetadata,mov,mp4,flv,m4a,mp3,matroska,image2,ass' \
--enable-muxer='mp4,flv,mp3,m4a,flac,matroska' \
--enable-decoder='h264,hevc,av1,aac,flac,eac3,ac3,mjpeg,png,webp,ass' \
--enable-encoder='libmp3lame,flac,mjpeg,png,ass' \
--enable-parser='mjpeg,h264,hevc,av1,aac,flac,ac3,eac3' \
--enable-bsf=h264_mp4toannexb \
--enable-swscale \
--enable-filter='scale,format,null,copy,aresample,aformat,anull' \
--enable-static \
--enable-ffmpeg \
--enable-protocol='file,concat,pipe' \
--enable-zlib \
--enable-libmp3lame \
--enable-gpl \
--extra-ldflags='-static -static-libgcc -static-libstdc++' \
--extra-ldflags="-L${LAME_PREFIX}/lib" \
--extra-cflags="-I${LAME_PREFIX}/include"思路是先用 --disable-everything 把所有组件关掉,再按实际需要逐个打开,这样产物才能足够小。各组的作用大致是:
- 封装格式:
mov,mp4,flv,m4a,mp3,matroska覆盖下载和输出用到的容器,concat和ffmetadata用于分片合并与写入章节等元数据,image2和ass分别用于封面图片和字幕。 - 编解码器:视频侧
h264,hevc,av1,音频侧aac,flac,eac3,ac3,mjpeg,png,webp用于处理封面,编码器只保留libmp3lame,flac,mjpeg,png,ass。 - parser 与 bsf:
h264_mp4toannexb在跨容器转封装时必需,缺了会在写入时报错。 - swscale 与 filter:
scale,format用于封面图片的缩放和像素格式转换,aresample,aformat用于音频重采样,其余是滤镜图里的直通节点。
运行下面的命令可以查看所有编译参数的用法:
./configure --help几个必须避开的坑
下面几条是这次更新中修正的地方,也是本文早期版本里给错了的配置。
不要用 --enable-small
该选项把优化级别从 -O3 降到 -Os,并打开 CONFIG_SMALL:改用体积更小但更慢的查表、放弃大量循环展开。对 copy 式的封装合并看不出差别,但只要涉及真正的编解码就会成倍变慢。
实测(30 分钟 44.1kHz 立体声 AAC 转 mp3,-c:a libmp3lame -q:a 2):
| 构建方式 | 耗时 | 速度 |
|---|---|---|
| 带 --enable-small | 22.9 秒 | 79x |
| 完整构建 | 6.4 秒 | 280x |
换成 -q:a 9 这个最快的编码档,两者仍是 18.5 秒对 5.3 秒,说明差距来自整条编解码路径而不是编码参数。
代价是产物变大(5.5 MB 起,实际会增至 7 MB 上下)。对下载工具来说,用户等待的时间比几 MB 体积更值钱。
不要用 --disable-swresample
没有 swresample 就没有 aresample 滤镜,采样格式一旦对不上,转码不是变慢而是直接失败:
'aresample' filter not present, cannot convert formats.早期配置能正常工作,只是因为 B 站的 AAC 解出来恰好是 fltp、正好落在 libmp3lame 支持的格式里。换成 packed 的 s16,或者要编 flac(只收 s16/s32),立刻就会报错。
顺带把 aformat / anull / format / null / copy 都带上,它们是滤镜图里的直通节点,体积可以忽略,加上以免哪条链路缺件。
protocol 里要有 pipe
程序靠 -progress pipe:1 拿合并与转换的实时进度:机器可读的进度写到 stdout,同时用 -nostats 关掉 stderr 上供人看的那份,好让诊断信息不被进度行冲掉。
缺了这个 protocol,FFmpeg 会因为打不开 pipe:1 直接报错退出 —— 不是没有进度,而是所有合并与转换统统失败。
--disable-avx512 保留
部分 CPU 跑 AVX512 会触发降频,反而更慢,而且会显著增大产物体积。关掉它并不影响运行时的 SIMD 派发照常用上 AVX2 及以下的指令集。
同样也没有加 -march。程序还要覆盖 Win7 兼容版可能面对的老机器,锁定基线指令集会牺牲这部分兼容性,而 FFmpeg 本身就有运行时 CPU 特性检测。
编译 FFmpeg
依次执行,等待编译完成。
./build.sh
make clean && make -j$(nproc)改动 build.sh 之后一定要先 make clean,否则残留的 .o 会和新的 config.h 混在一起,链接出来的产物行为不可预期。
编译完成后,在源码目录下就能看到编译出来的 FFmpeg。
./ffmpeg正常输出,FFmpeg 编译完成。
最后把 ffmpeg.exe 复制到 Bili23 Downloader 的 bundle/ 目录即可。
关于 Linux 和 macOS 上编译
基本上大同小异,只不过 Windows 需要一个 MSYS2 环境提供 mingw64。对于 Linux,直接安装相关编译依赖就行。
sudo apt install build-essential pkg-config yasm nasm libx264-dev libx265-dev libvpx-dev libfdk-aac-dev libmp3lame-dev libopus-dev 