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。这个转换开销不小。

优化思路

  1. 能在 YUV 空间处理就别转 RGB(比如缩放、裁剪、大部分滤镜)
  2. 转一次就够了,别来回转
  3. 用 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 识别完整流水线,把前面讲的所有知识串到一个实际项目里。

我是黒漂技术佬。

Logo

一站式 AI 云服务平台

更多推荐