FFmpeg 性能优化最佳实践:多线程、零拷贝与流水线
FFmpeg 性能优化最佳实践:多线程、零拷贝与流水线
FFmpeg 默认参数能用,但不一定快。批量处理视频、实时拉流、嵌入式设备上,性能优化就很重要了。这篇总结我实际项目里用到的优化手段:多线程、硬件加速、减少拷贝、流水线处理、参数调优——从命令行到代码层面都覆盖。
大家好,我是黒漂技术佬。
一开始用 FFmpeg 我也是直接默认参数跑,能用是能用,但批量处理几百个视频的时候慢得离谱,RK3588 上同时拉两路流 CPU 就满了。后来一点点优化,速度提升了好几倍。
这篇把能优化的地方按效果排序,从最简单的参数调到架构层面的流水线设计都讲。
一、优化优先级
先给个结论,按投入产出比排序:
| 优先级 | 优化手段 | 效果 | 难度 |
|---|---|---|---|
| ⭐⭐⭐⭐⭐ | 硬件加速编解码 | 3-10 倍 | 中 |
| ⭐⭐⭐⭐ | 减少编码次数(stream copy) | 几十倍 | 低 |
| ⭐⭐⭐⭐ | 多线程 | 2-4 倍 | 低 |
| ⭐⭐⭐ | 减少格式转换 | 20-50% | 中 |
| ⭐⭐⭐ | 滤镜顺序优化 | 10-30% | 低 |
| ⭐⭐ | 零拷贝 / 硬件帧传递 | 20-40% | 高 |
| ⭐⭐ | 输入输出优化 | 10-20% | 中 |
| ⭐ | 编译优化 | 10-15% | 高 |
硬件加速和 stream copy 是收益最大的,优先搞。
二、第一优先级:能 copy 就别编码
这是最简单但最容易被忽略的优化。
什么情况可以 copy?
只要视频画面不需要改(不缩放、不裁剪、不加滤镜),只是换封装、改音轨、拼接,就可以 -c copy。
# 不好:重新编码,慢
ffmpeg -i input.mp4 output.mkv
# 好:直接复制流,秒级完成
ffmpeg -i input.mp4 -c copy output.mkv
复制流的速度只受硬盘读写限制,比编码快几十上百倍。
部分 copy
视频不用改,音频要转:
ffmpeg -i input.mp4 -c:v copy -c:a aac -b:a 128k output.mp4
视频 copy,音频重新编码。比全重新编码快很多。
常见可以 copy 的场景
- 格式转换(MP4 → MKV → TS)
- 音视频分离、合并
- 拼接视频(参数一致时)
- 提取音频/视频
- 加字幕(软字幕,不是烧录)
💡 一个原则:画面没改就用 copy。加滤镜、改分辨率、加水印——这些改了画面的才需要重新编码。
三、第二优先级:硬件加速
上一篇专门讲过,这里再强调一下优先级:
命令行
# RK3588 全硬件转码
ffmpeg -hwaccel rkmpp -i input.mp4 -c:v h264_rkmpp -b:v 2M output.mp4
# NVIDIA 全硬件转码
ffmpeg -hwaccel cuda -i input.mp4 -c:v h264_nvenc -crf 23 output.mp4
代码里
硬解硬编,零拷贝传递,性能最优。嵌入式项目这是必须做的。
混合方案
解码用硬件,处理用 CPU,编码再用硬件——比全软快很多。
四、第三优先级:多线程
编码多线程
libx264/x265 默认就是多线程的,一般自动检测 CPU 核心数。但可以手动调:
ffmpeg -i input.mp4 -c:v libx264 -threads 8 -crf 23 output.mp4
解码多线程
ffmpeg -threads 8 -i input.mp4 output.mp4
H.264 软解多线程提升有限(一般 2-3 倍),因为解码依赖关系强。
滤镜多线程
部分滤镜支持多线程,比如 scale:
ffmpeg -i input.mp4 -vf "scale=1920:1080:flags=bicubic:threads=4" output.mp4
不是线程越多越快
超过物理核心数之后,再增加线程反而因为上下文切换变慢。一般设为 CPU 核心数就好。
五、第四优先级:减少格式转换
每一次像素格式转换都是一次全图拷贝,开销不小。
YUV → RGB 是大头
解码出来是 YUV420P,图像处理很多要 RGB。这个转换开销不小。
优化思路:
- 能在 YUV 空间处理就别转 RGB(比如缩放、裁剪、大部分滤镜)
- 转一次就够了,别来回转
- 用 swscale 的快速算法(bilinear 比 lanczos 快)
减少中间格式
比如:解码 → 缩放 → 编码。如果缩放前后都是 YUV,就不用转 RGB。
swscale 算法选择
# 最快,质量一般
scale=1280:720:flags=fast_bilinear
# 平衡(默认)
scale=1280:720:flags=bicubic
# 质量最好,最慢
scale=1280:720:flags=lanczos
做预览、缩略图用 fast_bilinear 就行,省时间。
六、第五优先级:滤镜顺序优化
滤镜链的顺序影响性能。原则是尽早减少像素量。
先裁剪后缩放
# 好:先裁掉多余像素,缩放处理的像素少
ffmpeg -i input.mp4 -vf "crop=640:480,scale=320:240" output.mp4
# 不好:先缩再裁,白处理了很多像素
ffmpeg -i input.mp4 -vf "scale=640:480,crop=320:240" output.mp4
先降采样再做复杂滤镜
先缩小再做复杂处理,处理的数据量小很多。
能合并的滤镜合并
有些滤镜可以组合到同一个 filter 里,减少数据拷贝次数。
七、第六优先级:输入输出优化
读文件
- 本地文件:一般没问题
- 网络流:加大缓冲区,减少阻塞
ffmpeg -rtsp_transport tcp -buffer_size 2000000 -i rtsp://地址 ...
写文件
- 输出到 SSD 比 HDD 快很多
- 大量小文件(抽帧)的时候,IO 开销很大,考虑批量写入或内存缓冲
管道代替临时文件
两步处理不要存中间文件,用管道连起来:
# 不好:中间文件占空间,还有 IO 开销
ffmpeg -i input.mp4 -c:v libx264 -crf 23 temp.mp4
ffmpeg -i temp.mp4 -vf scale=720:-1 output.mp4
# 好:管道连接,一次 IO
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -f matroska - | \
ffmpeg -i - -vf scale=720:-1 output.mp4
代码里更明显:解码后直接送滤镜再编码,中间不写文件。
八、第七优先级:流水线与并行处理
多文件并行处理
批量转码的时候,别一个一个来,同时跑几个 FFmpeg 进程。
但注意:
- 软编码 CPU 密集型,同时跑太多反而慢(争抢 CPU)
- 硬件编码有通道数限制
- 硬盘 IO 可能成为瓶颈
一般同时跑 2-4 个比较合适,看机器配置。
流水线处理
一个视频的处理拆成多个阶段,流水线式:
读文件 → 解码 → 滤镜 → 编码 → 写文件
FFmpeg 内部本来就是多线程流水线的,但自己写代码的时候要注意别在某一步阻塞。
多线程设计建议
- 解码线程:读 packet + 解码
- 处理线程:滤镜、AI 识别
- 编码线程:编码 + 写文件
用队列串起来,各阶段并行。
九、第八优先级:编译优化
自己编译 FFmpeg,可以开启一些优化:
编译选项
./configure \
--enable-gpl \
--enable-libx264 \
--enable-librkmpp \ # RK3588 硬件加速
--enable-hardcoded-tables \
--optflags="-O3" \
--enable-pic
针对特定 CPU 优化
--cpu=cortex-a76 # RK3588 的大核
针对 ARM 平台交叉编译的时候,指定 CPU 型号能生成更优化的指令。
裁剪不需要的功能
只编你需要的编码器解码器,体积小、加载快。
一般默认编译的 FFmpeg 就够用了,编译优化提升有限,不到万不得已不用折腾。
十、代码层面的优化
1. 复用对象
循环里不要反复 alloc/free,packet 和 frame 分配一次反复用,unref 清数据就行。
// 好:分配一次,循环里复用
AVPacket *pkt = av_packet_alloc();
while (...) {
av_read_frame(fmt_ctx, pkt);
// 处理...
av_packet_unref(pkt);
}
av_packet_free(&pkt);
2. 减少内存拷贝
- 直接操作 frame 的 data 指针,别自己再 memcpy 一份
- 能用引用计数(ref/unref)就别拷贝数据
av_frame_ref()只是增加引用,不拷贝数据
3. 合理的缓冲区大小
读网络流的时候,缓冲区太小会频繁 IO;太大占内存。根据实际场景调。
4. 跳过不需要的流
文件里有视频、音频、字幕,只需要视频的话,其他流不要打开解码器。
// 只处理视频流,其他直接忽略
if (pkt->stream_index == video_idx) {
// 解码...
}
5. 丢帧策略
实时处理场景(比如直播分析),处理不过来就丢帧,保证延迟。
// 队列太长了就丢旧帧
if (packet_queue_size > MAX_QUEUE_SIZE) {
// 丢一帧...
}
十一、性能分析方法
优化之前先测瓶颈在哪,别瞎优化。
命令行测速度
time ffmpeg -i input.mp4 -c:v libx264 -crf 23 -f null -
看 fps 和总耗时。
看 CPU 占用
top / 任务管理器,看是 CPU 瓶颈还是 IO 瓶颈。
- CPU 100% → 计算瓶颈,优化编码或上硬件加速
- CPU 很低但速度慢 → IO 瓶颈或网络瓶颈
代码里打点
每个阶段加时间统计:
double t0 = av_gettime_relative() / 1000000.0;
// 解码...
double t1 = av_gettime_relative() / 1000000.0;
printf("解码耗时: %.3fms\n", (t1 - t0) * 1000);
找到最慢的那一步,针对性优化。
十二、我项目里的优化路径
售货柜项目(RK3588 + IPC + YOLO)的优化过程:
第一版:软解 + 转 RGB + YOLO
- 一路 1080p 30fps,CPU 占满,只能跑到 10fps
第二版:硬解 + YUV 直接送模型
- 硬解放出 CPU,YOLO 用 RKNN NPU 加速
- 跑到 25fps,CPU 占用 30%
第三版:按需抽帧 + 事件触发
- 没人的时候每秒抽 1 帧做检测
- 开门事件触发才全帧率识别
- 平时 CPU 占用 <10%,功耗也降了
第四版:多流水线并行
- 解码、预处理、推理、后处理流水线
- 同时处理两路摄像头无压力
优化是循序渐进的,先搞大头,再抠细节。
十三、本篇小结
- 最大优化:能 copy 就别编码,速度差几十倍
- 第二大:硬件加速,硬解硬编快 3-10 倍
- 第三:多线程、滤镜顺序优化、减少格式转换
- 代码层面:复用对象、减少拷贝、跳过不需要的流
- 优化前先测瓶颈,别盲目优化
- 嵌入式项目:硬解硬编 + NPU 推理 + 按需处理,是性能最优解
下一篇是本系列最后一篇:售货柜实战——IPC 拉流 → 抽帧 → YOLO 识别完整流水线,把前面讲的所有知识串到一个实际项目里。
我是黒漂技术佬。
更多推荐




所有评论(0)