多线程与并行:FFmpeg 的 -threads 参数,以及如何手动拆分任务并行转码

转码慢,很多时候不是 CPU 不够强,而是你没让它「全力干活」。我见过太多人,明明机器有 32 个核心,转码时 CPU 占用率却只有 10%。资源在那闲着,你说亏不亏?

这一章,我们就来聊聊怎么让 FFmpeg 真正跑满多核。核心就两件事:用好内置的 -threads 参数,以及学会手动拆任务并行。

先搞懂 FFmpeg 的线程模型

FFmpeg 的并行,分三个层面。我画了张图,你看一眼就明白了。

FFmpeg 并行层次结构 任务级并行(手动拆分) 将一个大文件拆成 N 段,同时启动 N 个 FFmpeg 进程 适合:长视频、批量转码、云端集群 帧级并行(-threads 参数) 编码器内部多线程处理帧切片(slices) 适合:单文件转码、实时流处理 指令级并行(SIMD / 汇编优化) CPU 自动利用 SSE、AVX 等指令集加速像素运算 无需手动配置,编译时开启即可

底层指令级并行,编译器帮你搞定了,不用操心。我们重点说上面两层。

-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。你可以这样拆:

  1. 先拆成 4 段,每段 30 分钟
  2. 同时启动 4 个 FFmpeg 进程,每个处理一段
  3. 最后合并成一个完整文件

具体怎么做?看代码。

第一步:用 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 是甜区
  • 拆分时注意关键帧对齐,合并时注意文件顺序