提示

返回博客列表

开了硬件加速反而更慢:hwdownload/hwupload 的拷贝陷阱与零拷贝实践

我们的转码服务上了 GPU 机器,我把命令改成 -hwaccel cuda -c:v h264_nvenc,满怀期待地跑了一次。

结果:

  • 纯转码(不滤镜):快了 3.2 倍,不错;
  • 加缩放:scale 滤镜 → 快了 1.4 倍,还行;
  • 加字幕(烧录):比纯 CPU 慢 15%,完全没道理。

第三个结果让我很困惑,查了 ffmpeg 的日志才明白:subtitles 滤镜只能在 CPU 上跑,所以帧必须从显存拷回内存(hwdownload),滤镜处理完再拷回显存(hwupload)给 NVENC。这两次拷贝 + 中间的 CPU 处理,开销比省下的解码时间还大。

这篇把这个机制讲清楚:帧到底在哪里、什么时候会被拷贝、怎么避免,以及什么时候干脆别用硬件加速。

TL;DR:硬件加速的三个环节(解码/滤镜/编码)里,滤镜是短板——大部分滤镜只有 CPU 实现,一旦用上就必须在显存和内存之间来回拷贝(hwdownload/hwupload),这个拷贝能吃掉全部收益。三种管线的实测:纯转码(无滤镜)快 3.2 倍;只用 GPU 滤镜(scale_cuda 等)快 2.6 倍;用 CPU 滤镜(如 subtitles)反而慢 15%。另外两个坑:消费级显卡的 NVENC 有并发路数限制(3~8 路);同码率下 NVENC 画质不如 x264(要多给 15~25% 码率)。

目录

一、先搞清楚帧在哪

视频帧有两个"家":

位置 谁用它 特点
系统内存(RAM) CPU 解码器、绝大多数滤镜、CPU 编码器(libx264/libx265) 通用、灵活,但慢
显存(GPU VRAM) 硬件解码器(NVDEC)、GPU 滤镜(scale_cuda 等)、硬件编码器(NVENC) 快,但格式受限、滤镜种类少

关键认知:帧在两边之间搬运是有成本的。

一次 1080p 帧(NV12 格式)大约是 1920×1080×1.5 ≈ 3.1 MB。PCIe 传输本身很快(几 GB/s),但:

  • 每次传输有固定开销(同步、内存分配);
  • 传输是阻塞的(要等 GPU 完成);
  • 每秒 25~50 帧 × 3MB = 75~150 MB/s 的持续搬运。

单次拷贝看起来不多,但如果每帧要拷两次(进去 + 出来),加上 CPU 滤镜的处理时间,总账就不划算了。

二、三种管线的数据流

管线 A:全 CPU(传统)

解码(CPU) → [内存] → 滤镜(CPU) → 编码(CPU)

全程在内存里,没有拷贝。慢,但可预测。

管线 B:GPU 解码 + CPU 滤镜 + GPU 编码(最常见也最容易踩坑)

解码(GPU) → [显存] --hwdownload--> [内存] → 滤镜(CPU) --hwupload--> [显存] → 编码(GPU)
                        ↑ 拷贝 1                                        ↑ 拷贝 2

这就是我踩的坑。省了解码和编码的 CPU 时间,但多了两次拷贝 + 滤镜仍在 CPU。

管线 C:全 GPU(零拷贝)

解码(GPU) → [显存] → 滤镜(GPU) → 编码(GPU)

全程在显存里,没有拷贝。这是理想情况,但要求所有滤镜都有 GPU 版本。

三、hwdownload / hwupload 是什么

这两个是显式的格式转换滤镜:

滤镜 作用
hwupload 把帧从内存上传到显存
hwupload_cuda 同上(指定 CUDA 设备类型)
hwdownload 把帧从显存下载到内存
hwdownload,format=nv12 下载并指定像素格式

ffmpeg 会自动插入它们:当你用 -hwaccel cuda 解码,然后接一个只能跑 CPU 的滤镜时,ffmpeg 会在滤镜链里自动插入 hwdownload(以及后面的 hwupload)。

怎么知道有没有被自动插入?看日志(-v verbose 或者看滤镜图的输出):

ffmpeg -v verbose -hwaccel cuda -i in.mp4 -vf "subtitles=sub.ass" -c:v h264_nvenc out.mp4

会看到滤镜图里出现:

[Parsed_hwdownload_1 @ ...]
[Parsed_hwupload_3 @ ...]

看到这两个就说明发生了拷贝。想避免,就得换掉那个 CPU 滤镜。

四、哪些滤镜有 GPU 版本

这是选管线时的关键清单(CUDA 平台):

功能 CPU 滤镜 GPU 版本
缩放 scale scale_cuda(还有 scale_npp,NPP 版)
裁剪 crop crop_cuda
叠加/水印 overlay overlay_cuda
去隔行 yadif / bwdif yadif_cuda / bwdif_cuda
色调映射(HDR→SDR) tonemap tonemap_cuda
缩略图 thumbnail thumbnail_cuda
格式转换 format format(在显存上下文里可用)
色彩空间 colorspace colorspace_cuda
锐化 unsharp sharpen_npp / 部分 NPP
降噪 hqdn3d / nlmeans nlmeans_npp(部分)
转场/混合 blend blend_cuda

没有 GPU 版本的常见滤镜(用了就必然回 CPU):

  • subtitles(烧字幕)——最常见的元凶;
  • drawtext(画文字/时间码);
  • ass(老的字幕滤镜);
  • 所有音频滤镜(音频本来就不在 GPU 上);
  • concat、trim、fps(部分有替代方案);
  • 大部分"智能"滤镜(场景检测、去水印)。

判断方法:

ffmpeg -filters 2>/dev/null | grep -E '_cuda|_npp|_qsv|_vaapi'

如果找不到你要的滤镜的 GPU 版本,那就要么接受回 CPU,要么换方案。

五、三种管线的实测命令

管线 A:全 CPU(基线)

ffmpeg -i in.mp4 \
  -vf scale=1280:720 \
  -c:v libx264 -crf 23 -preset medium \
  -c:a aac -b:a 128k out_cpu.mp4

管线 B:GPU 解码 + CPU 滤镜 + GPU 编码(有拷贝)

ffmpeg -hwaccel cuda -i in.mp4 \
  -vf "scale=1280:720,subtitles=sub.ass" \      # subtitles 强制回 CPU
  -c:v h264_nvenc -preset p4 -cq 23 \
  -c:a aac -b:a 128k out_mixed.mp4

管线 C:全 GPU(零拷贝)

ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i in.mp4 \
  -vf "scale_cuda=1280:720" \                   # GPU 缩放,不回内存
  -c:v h264_nvenc -preset p4 -cq 23 \
  -c:a aac -b:a 128k out_gpu.mp4

-hwaccel_output_format cuda 这一行很重要:它告诉 ffmpeg"解码后的帧就留在显存里,别拷回内存"。不加的话,默认会把解码结果拷回内存(ffmpeg 的传统行为),白浪费一次拷贝。

折中方案:字幕场景

烧字幕必须用 CPU 滤镜,但仍可以让解码和编码留在 GPU:

ffmpeg -hwaccel cuda -i in.mp4 \
  -vf "hwdownload,format=nv12,subtitles=sub.ass,hwupload" \
  -c:v h264_nvenc -preset p4 -cq 23 out_sub.mp4

显式写出来比让 ffmpeg 自动插入更好(行为可预测)。而且 format=nv12 要明确——hwdownload 之后帧是硬件格式,CPU 滤镜不认,必须转成正常的像素格式。

实测结论(1080p → 720p + 烧字幕,10 分钟素材):

管线 耗时 CPU 占用 结论
A 全 CPU 4 分 12 秒 780% 基线
B 混合(自动拷贝) 4 分 48 秒 320% 更慢
C 全 GPU(无字幕) 1 分 18 秒 110% 快 3.2 倍
折中(显式拷贝 + 字幕) 4 分 35 秒 350% 略慢于纯 CPU

结论很清楚:有 CPU 滤镜时,硬件加速没有收益。什么时候用 GPU,取决于你的滤镜链。

六、解码端:硬解的限制

硬件解码器(NVDEC)不是什么都能解:

限制 说明
格式支持按显卡代次不同 老显卡(比如 GTX 10 系之前)不支持 HEVC 硬解,更不支持 10bit HEVC
10bit / 4:2:2 支持有限 很多消费级卡只支持 8bit 4:2:0 的硬解
分辨率上限 老卡可能不支持 4K 硬解
不支持的格式会静默回退 ffmpeg 会退回软解(不报错,但你以为在用硬解)

查你的显卡支持什么:

ffmpeg -hwaccels                  # 列出支持的 hwaccel 类型
# cuda, vaapi, qsv, ...

# 看 NVENC/NVDEC 的能力矩阵(NVIDIA 官网有表)

验证是否真的在用硬解:

ffmpeg -v verbose -hwaccel cuda -i in.mp4 -f null - 2>&1 | grep -i 'cuda\|nvdec'

或者在转码时看 GPU 利用率:

nvidia-smi dmon -s u        # 持续看 GPU 利用率
# 如果 GPU 利用率是 0%,说明根本没用上

我踩过的坑:一批 HEVC 10bit 的素材在老卡上"硬解失败",ffmpeg 静默回退到软解,CPU 打满但我们还以为在加速。后来加了日志检查才发现的。

七、编码端:NVENC 的参数与画质

NVENC 的常用参数:

-c:v h264_nvenc \
  -preset p1~p7 \            # p1 最快、p7 最慢质量最好
  -tune hq \                 # 高质量模式(还有 ll=低延迟、ull=超低延迟)
  -rc vbr \                  # 码率控制:vbr / cbr / constqp
  -cq 23 \                   # 类似 CRF(constqp 模式下是 QP 值)
  -b:v 5M -maxrate 6M -bufsize 10M \
  -profile:v high -level 4.1

preset 的选择(NVENC 的 preset 跟 x264 的完全不同):

preset 说明
p1 最快("Ultra Fast")
p2~p4 平衡
p4 常用("Medium")
p5~p7 慢,质量更好(p7 是 "Max Quality")

注意:NVENC 的 preset 跟 x264 的 ultrafast/veryslow 不是一回事,不要按名字类比。

画质对比(同码率,VMAF):

编码器 VMAF(5 Mbps 1080p) 说明
libx264 medium 94.2 CPU 软编,画质基准
libx264 slow 94.8
h264_nvenc p4 91.5 同码率下低 2.7 分
h264_nvenc p7 92.8 慢一些,接近但仍不如
h264_nvenc p1 88.1 明显差

实用结论:NVENC 要比 x264 多给 15~25% 的码率才能达到相近的画质。

这意味着"省算力"和"省带宽"是矛盾的:

  • 追求吞吐(大量内容、时效紧)→ NVENC + 多给码率;
  • 追求存储和带宽(长期归档、播放量大)→ CPU 软编(慢但省)。

我的选择:

  • 实时/准实时转码、直播转码 → NVENC(没得选,要时效性);
  • 批量归档、母版 → libx265 CPU(画质和压缩率优先);
  • 中间地带(量大、质量要求一般)→ NVENC + 提高码率。

八、并发路数限制:消费级卡的坑

这是很多人在生产上才发现的坑:

NVIDIA 的消费级(GeForce)驱动对同时进行的 NVENC 会话数有限制(历史上是 2~3 路,后来放宽到 5~8 路,取决于驱动版本和卡型)。专业卡(Tesla / A 系列数据中心卡 / RTX A 系列)没有这个限制。

表现:

[h264_nvenc @ 0x...] OpenEncodeSessionEx failed: out of memory (10)
[h264_nvenc @ 0x...] No capable devices found

不是真的显存不够,是驱动层面的并发限制。

应对:

  1. 控制并发数(最简单):worker 数 ≤ 限制数;
  2. 用专业卡(如果规模大,算下来更划算);
  3. Linux 上有非官方的驱动补丁(不推荐生产用,也不合规)。

查你的卡的限制:

nvidia-smi --query-gpu=name,encoder.stats.sessionCount --format=csv

或者干脆实测:同时起 N 路转码,看第几路开始失败。

我们的做法:一张消费级卡最多跑 4 路并发(留余量),并且给 NVENC 任务单独一个队列(前面分布式转码那篇讲过队列拆分)。

九、QSV / VAAPI 简述

除了 NVIDIA,还有:

方案 平台 说明
NVENC/NVDEC NVIDIA 显卡 生态最好,ffmpeg 支持最全
QSV Intel 核显 / Arc 服务器和桌面上常见,省电,画质一般
VAAPI Linux 通用(Intel/AMD) 开源驱动的统一接口,ffmpeg 支持好
VideoToolbox macOS Apple 平台的硬编
AMF AMD 显卡 Windows 上

QSV 的例子:

ffmpeg -hwaccel qsv -qsv_device /dev/dri/renderD128 -i in.mp4 \
  -vf "scale_qsv=1280:720" \
  -c:v h264_qsv -preset medium out.mp4

选择建议:

  • 有 NVIDIA 卡 → CUDA/NVENC(支持最全);
  • Intel 核显的服务器(很多云主机都有)→ QSV,能省很多 CPU;
  • Linux + AMD → VAAPI。

一个实际情况:云上的很多"入门级"GPU 实例其实是消费级卡,要注意前面说的并发限制。而 Intel 核显的 QSV 虽然画质一般,但没有并发限制、成本低,对"量大、质量要求不高"的批量转码反而更合适。

十、什么时候不该用硬件加速

基于上面的实测,我的判断清单:

适合用:

  • ✅ 纯转码(解码 → 编码),没有 CPU-only 滤镜;
  • ✅ 滤镜链里所有滤镜都有 GPU 版本;
  • ✅ 实时/准实时要求(直播转码、低延迟);
  • ✅ 吞吐优先、画质要求可以放宽(多给码率);
  • ✅ 量大、时效紧(批量处理,能多给 20% 码率换 3 倍速度)。

不适合:

  • ❌ 需要烧字幕、画文字水印、复杂 CPU 滤镜;
  • ❌ 归档母版、追求最高画质/压缩率;
  • ❌ 源格式不在硬解支持范围(会静默回退);
  • ❌ 显卡太老(不支持目标格式的硬解/硬编);
  • ❌ 只需要处理几个文件(配置调试的时间比省下的多)。

一个务实的做法:按任务类型选管线。我们的转码服务里有两条队列:

queue:transcode_gpu   → 纯转码任务,全 GPU 管线(快 3 倍)
queue:transcode_cpu   → 带字幕/复杂滤镜的任务,全 CPU 管线

提交任务时根据"有没有 CPU-only 滤镜"决定进哪个队列。这个分流比"全部上 GPU"效率高得多。

十一、坑清单

  1. 以为加了 -hwaccel 就一定加速 → 有 CPU 滤镜时更慢。看滤镜链。
  2. 没加 -hwaccel_output_format cuda → 解码结果被拷回内存,白浪费一次拷贝。
  3. 用了 subtitles/drawtext 还期待加速 → 必然回 CPU。
  4. hwdownload 后忘了 format=nv12 → 后续 CPU 滤镜不认硬件格式,报错。
  5. 不检查是否真的在用硬解 → 格式不支持时 ffmpeg 静默回退到软解。用 nvidia-smi 验证 GPU 利用率。
  6. NVENC 用和 x264 相同的码率期待相同画质 → 要多给 15~25%。
  7. NVENC preset 按 x264 的理解来选 → 两者体系完全不同。
  8. 消费级卡的并发限制没考虑 → 超过限制报 "out of memory" 的假错误。
  9. 多进程共享一张卡没做并发控制 → 同上。
  10. 显存不够(多路并发 + 高分辨率)→ 真的 OOM。要算:每路大约 200~500MB 显存(不含模型)。
  11. 硬件编码器不支持的格式(比如某些 4:2:2)→ 会失败或回退。
  12. 音频也走 GPU → 音频永远在 CPU,无所谓但不要误以为有加速。
  13. -hwaccel 放在 -i 后面 → 必须在输入之前(它是输入选项)。
  14. 多线程/多进程时 CUDA 上下文初始化开销 → 短任务(几秒)反而更慢,因为有初始化开销。短任务用 GPU 不划算。
  15. 驱动/CUDA 版本和 ffmpeg 编译时的版本不匹配 → 报奇怪的错误。升级后要重新验证。

最后说说我对硬件加速的整体看法。

硬件加速不是"开了就快"的开关,它是一条完全不同的流水线,要求你重新设计整个处理链。

最大的认知转变是:不要问"怎么开硬件加速",而要问"我的这条处理链能不能整个留在 GPU 上"。如果答案是"不能"(因为有字幕、有复杂滤镜),那么开硬件加速通常得不偿失。

第二个体会是:硬件加速省的是 CPU,不是时间。它的价值在于"同样的机器能跑更多路",而不是"单个任务更快"。所以在规划容量时应该这么算:

一台 8 核机器:
  CPU 转码:8 路 × 4 分钟/个 = 2 个/分钟
  GPU 转码:4 路(并发限制)× 1.2 分钟/个 = 3.3 个/分钟

整体吞吐提升 65%(不是 3.2 倍),因为并发数受限制。这个数字才是做容量规划时该用的。

第三:画质是真实的代价。NVENC 在同码率下确实不如 x264。如果你的内容要长期存储、播放量大,多出的 20% 带宽/存储会在几个月后超过省下的算力成本。算总账,别只看单次转码的耗时。

我们最后的分工是:直播和实时转码走 GPU,归档母版和对外交付走 CPU,批量处理按"有没有 CPU-only 滤镜"分流。这个组合看着不"技术先进",但它是实测数据支撑的最优解。

想亲手试试?用 VidDown 一键解析下载

粘贴视频链接即可解析,多平台支持、网页端即用;下载桌面客户端解锁海外平台本地解析,开通会员更享不限次下载。

顶部