多线程与并行:FFmpeg 的 -threads 参数,以及如何手动拆分任务并行转码
转码慢,很多时候不是 CPU 不够强,而是你没让它「全力干活」。我见过太多人,明明机器有 32 个核心,转码时 CPU 占用率却只有 10%。资源在那闲着,你说亏不亏?
这一章,我们就来聊聊怎么让 FFmpeg 真正跑满多核。核心就两件事:用好内置的 -threads 参数,以及学会手动拆任务并行。
先搞懂 FFmpeg 的线程模型
FFmpeg 的并行,分三个层面。我画了张图,你看一眼就明白了。
底层指令级并行,编译器帮你搞定了,不用操心。我们重点说上面两层。
-threads 参数:最简单的多核加速
这个参数,直接告诉编码器用几个线程干活。用法很简单:
ffmpeg -i input.mp4 -c:v libx264 -threads 4 output.mp4
但这里有个坑。我踩过,而且不止一次。
我曾经给一台 64 核服务器设了 -threads 64,结果转码速度反而比 8 线程还慢。为什么?线程切换开销太大,缓存也扛不住。
经验值:H.264 编码建议 4~8 线程,H.265/HEVC 建议 6~12 线程。超过这个数,收益递减。
那怎么找到最佳值?我个人的习惯是:先跑一小段测试。
# 测试 4 线程
time ffmpeg -i input.mp4 -c:v libx264 -threads 4 -t 30 -f null /dev/null
# 测试 8 线程
time ffmpeg -i input.mp4 -c:v libx264 -threads 8 -t 30 -f null /dev/null
# 测试 16 线程
time ffmpeg -i input.mp4 -c:v libx264 -threads 16 -t 30 -f null /dev/null
看哪个用时最短,就用哪个。简单粗暴,但有效。
自动线程数:-threads auto
FFmpeg 4.0 之后支持 -threads auto。它会根据 CPU 核心数自动设置。但说实话,我很少用。
原因很简单:auto 通常取的是逻辑核心数。如果你的 CPU 支持超线程,auto 可能设成 16 甚至 32。但前面说了,编码器线程超过 8~12 反而变慢。
手动指定 -threads,别偷懒用 auto。花 5 分钟测试一下,可能省下几小时的转码时间。
手动拆分任务:真正的并行加速
-threads 只能加速单文件的编码过程。但如果你有多个文件,或者一个超长视频,怎么办?
答案是:手动拆。把一个大任务拆成多个小任务,同时跑。
举个例子。你有一个 2 小时的视频,要转成 H.264。你可以这样拆:
- 先拆成 4 段,每段 30 分钟
- 同时启动 4 个 FFmpeg 进程,每个处理一段
- 最后合并成一个完整文件
具体怎么做?看代码。
第一步:用 ffprobe 获取总时长
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4
# 输出:7200.0 (秒)
第二步:用 -ss 和 -t 拆分
# 拆成 4 段,每段 1800 秒(30 分钟)
ffmpeg -i input.mp4 -ss 0 -t 1800 -c copy segment_1.mp4
ffmpeg -i input.mp4 -ss 1800 -t 1800 -c copy segment_2.mp4
ffmpeg -i input.mp4 -ss 3600 -t 1800 -c copy segment_3.mp4
ffmpeg -i input.mp4 -ss 5400 -t 1800 -c copy segment_4.mp4
这里我用的是 -c copy,只做切割,不重新编码。速度极快,几秒钟就切完。
但如果你要转码,必须用 -c:v libx264 之类的编码器。而且要注意:-ss 放在 -i 前面是「关键帧精准切割」,放在后面是「逐帧精准切割」。前者快但可能不准,后者慢但精确。
第三步:并行转码
切完之后,就可以并行跑了。我一般写个简单的 shell 脚本:
#!/bin/bash
# 并行转码 4 段
for i in 1 2 3 4; do
ffmpeg -i segment_$i.mp4 -c:v libx264 -threads 4 segment_${i}_h264.mp4 &
done
wait # 等待所有后台任务完成
echo "所有分段转码完成"
注意那个 & 符号。它把每个 FFmpeg 进程丢到后台,4 个任务同时跑。最后 wait 等全部结束。
第四步:合并
转码完成后,用 concat 协议合并:
# 先创建一个文件列表
echo "file 'segment_1_h264.mp4'" > filelist.txt
echo "file 'segment_2_h264.mp4'" >> filelist.txt
echo "file 'segment_3_h264.mp4'" >> filelist.txt
echo "file 'segment_4_h264.mp4'" >> filelist.txt
# 合并
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
搞定。整个过程,从 2 小时缩短到 30 分钟左右(假设 4 核并行)。
什么时候该用哪种方式?
我整理了一个表格,方便你对照选择。
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 单个短视频(<10 分钟) | -threads 4~8 |
拆分开销太大,不值得 |
| 单个长视频(>30 分钟) | 手动拆分 + 并行 | 充分利用多核,线性加速 |
| 批量转码(多个文件) | GNU Parallel 或 xargs -P | 每个文件一个进程,天然并行 |
| 实时流 / 直播 | -threads auto |
延迟敏感,自动适配 |
避坑指南:我踩过的几个坑
嗯,这里得说说我当年犯过的错。你看了能少走弯路。
- 坑一:拆分时用 -c copy,但合并时发现音画不同步。 后来发现是切割点没对齐关键帧。解决方案:拆分时加
-avoid_negative_ts make_zero。 - 坑二:并行任务太多,内存爆了。 每个 FFmpeg 进程会占用几百 MB 内存。我试过同时跑 32 个,服务器直接 OOM。建议:并行数 = CPU 核心数 - 1,留一个给系统。
- 坑三:合并时文件顺序搞错。 文件名按字典序排列时,
segment_10.mp4会排在segment_2.mp4前面。解决方案:文件名用零填充,比如segment_01.mp4。
-threads控制编码器内部并行,适合单文件加速- 手动拆分 + 并行进程,适合长视频和批量任务
- 线程数不是越多越好,4~8 是甜区
- 拆分时注意关键帧对齐,合并时注意文件顺序