
我第一次把 GPU 转码落到香港机房,RTMP 推流峰值把 CPU 干到了 95% 以上,告警像鞭炮一样一串串炸。楼上运维在抱怨冷气机结霜,我蹲在 42U 柜前,看着 top 里疯狂跑满的 ffmpeg 进程,心里只有一句话:该把活儿交给 GPU 了。
这篇文章,把那一夜以来我在香港节点做 GPU 转码的实操、坑点和优化,完整复刻出来——尽量做到新手能照抄跑起来,老手能看懂细节与取舍。
一、目标与总览架构
目标:
- 把解码+转码从 CPU 挪到 GPU(NVDEC+NVENC),把 CPU 从 80%+ 压到 20% 以下;
- 支撑高并发推流(RTMP/SRT/WHIP),并发路数、码级组合可水平扩展;
- 产出多码率(ABR)HLS/FLV,延迟与画质在可接受区间内(直播场景优先低延迟)。
参考架构(文本示意):
[Ingest层: SRS/NGINX-RTMP多实例] <--RTMP/SRT/WebRTC推流--
| 通过本地Unix或TCP转发(内网)
v
[Transcode层: FFmpeg + NVENC(L4/T4等)] --ABR HLS/FLV--> [Delivery层: NGINX静态/HLS]
|
+--> 监控: node_exporter + DCGM Exporter + 日志
选型说明:我更偏向 SRS 做 Ingest(原生支持 SRT/WHIP,转发灵活),NGINX 做分发(HLS/FLV),FFmpeg 通吃转码。解耦后扩容简单,遇到 GPU 故障也容易旁路切 CPU 兜底。
二、硬件与网络选型(香港机房落地)
| 角色 | 建议机型 | 关键参数 | 适用场景 | 备注 |
|---|---|---|---|---|
| Transcode(GPU) | NVIDIA L4 on 1×Xeon Silver / AMD EPYC | NVENC 8代,能效高;PCIe Gen4 | 4~8 码级 ABR、密度优先 | 单卡 1080p30 H.264 70~90 路(取决于预设与质控);HEVC 路数更高 |
| Transcode(性价比) | NVIDIA T4 on Xeon | NVENC 7代 | 3~4 码级 ABR、性价比 | 单卡 1080p30 H.264 35~45 路 |
| Ingest | 1U 无GPU(高频CPU+万兆网) | 高单核 + 10G NIC | 海量连接/会话 | 不做重转码 |
| Delivery | 1U 无GPU + SSD 阵列 | 高 IOPS + 10G | HLS 小文件分发 | 打开 sendfile、aio |
| 网络 | 10G 内网 + 多线BGP | 香港机房常见 | 低抖动、低丢包 | 内外网分离,RTT 稳定 |
注意:香港机房路由多变,SRT 更抗抖动。公网上行推流建议优先 SRT,其次 RTMP。
三、系统环境(CentOS 7)与前置准备
用户要求 CentOS 7,我也确实在 CentOS 7 上跑过。但提醒:CentOS 7 已 EOL,新项目建议 AlmaLinux 8/9。本文仍给出 CentOS 7 可行路径。
基础包与内核头文件
yum install -y epel-release
yum groupinstall -y "Development Tools"
yum install -y gcc gcc-c++ make git wget pciutils \
kernel-devel-$(uname -r) kernel-headers-$(uname -r) \
cmake nasm yasm pkgconfig
禁用 nouveau,准备安装 NVIDIA 驱动
cat >/etc/modprobe.d/blacklist-nouveau.conf <<'EOF'
blacklist nouveau
options nouveau modeset=0
EOF
dracut --force
reboot
安装 NVIDIA 驱动(ELRepo 稳定)
ELRepo 的 kmod-nvidia 在 CentOS 7 上更省心:
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
yum install -y nvidia-detect
nvidia-detect
# 按提示安装对应驱动,常见为:
yum --enablerepo=elrepo-kernel install -y kmod-nvidia nvidia-x11-drv nvidia-driver
reboot
nvidia-smi
(可选)安装 NVIDIA Container Toolkit(若用容器化)
distribution=rhel7
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | tee /etc/yum.repos.d/nvidia-docker.repo
yum install -y nvidia-docker2 && systemctl restart docker
四、FFmpeg(启用 NVENC/NVDEC)编译安装
CentOS 7 官方 FFmpeg 太老,基本都自行编译。以下是能稳定跑 NVENC/NVDEC 的组合。
安装 nv-codec-headers
git clone https://git.videolan.org/git/ffmpeg/nv-codec-headers.git
cd nv-codec-headers && make && make install && cd ..
准备第三方库(选用)
yum install -y openssl-devel librtmp-devel \
libx264-devel libx265-devel \
libsrt-devel
# 若仓库中无对应devel,请自行编译 x264/x265/SRT
编译 FFmpeg(4.4/5.x 在 CentOS 7 更常见)
git clone https://github.com/FFmpeg/FFmpeg.git
cd FFmpeg && git checkout n5.1 # 或 n4.4 稳定分支
./configure --prefix=/usr/local/ffmpeg \
--pkg-config-flags="--static" \
--extra-cflags="-I/usr/local/include" \
--extra-ldflags="-L/usr/local/lib" \
--enable-gpl --enable-nonfree \
--enable-openssl \
--enable-libx264 --enable-libx265 \
--enable-libsrt \
--enable-cuda-nvcc --enable-nvenc \
--enable-cuvid
make -j$(nproc) && make install
ln -sf /usr/local/ffmpeg/bin/ffmpeg /usr/bin/ffmpeg
ln -sf /usr/local/ffmpeg/bin/ffprobe /usr/bin/ffprobe
ffmpeg -hwaccels
兼容提示:
- 在 FFmpeg 5.x,推荐用 -hwaccel cuda -hwaccel_output_format cuda 搭配 scale_npp;
- 在 4.x 上 h264_cuvid/hevc_cuvid 更常见。两种写法我都给出。
五、Ingest:SRS/NGINX-RTMP 二选一
方案 A:SRS(支持 RTMP/SRT/WHIP)
简化配置 srs.conf(仅关键片段):
listen 1935;
max_connections 100000;
vhost __defaultVhost__ {
# RTMP 推流
ingest rtmp {
enabled on;
input {
type stream;
url rtmp://0.0.0.0:1935/live?vhost=__defaultVhost__;
}
}
# SRT 监听(更抗抖动)
srt_server {
enabled on;
listen 10080;
}
# 转发到转码层(内网)
forward {
enabled on;
destination 10.10.10.50:1935;
}
}
方案 B:NGINX-RTMP(轻量经典)
yum install -y nginx
git clone https://github.com/arut/nginx-rtmp-module.git
# 源码编译 nginx 加上 rtmp 模块(略),或安装带 rtmp 的打包版
nginx.conf 片段:
rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
record off;
# 推到转码层(内网)
push rtmp://10.10.10.50:1935/trans;
}
}
}
六、Transcode:FFmpeg + NVENC/NVDEC(单路→多码率)
思路:
- 通过内网从 Ingest 拉一路源(RTMP/SRT);
- GPU 解码(NVDEC)→ scale_npp 多分辨率 → NVENC 多档码率;
- 输出 HLS(或 FLV/LL-HLS/Chunked-TS),再由 NGINX 分发。
A. 现代写法(FFmpeg 5.x)
ffmpeg -hide_banner -hwaccel cuda -hwaccel_output_format cuda -threads 1 \
-rtbufsize 256M -fflags nobuffer -flags low_delay \
-i "rtmp://10.10.10.40:1935/live/stream001" \
-filter_complex "\
[0:v]split=3[v1080][v720][v480]; \
[v1080]scale_npp=1920:1080:interp_algo=super:format=nv12[v108o]; \
[v720] scale_npp=1280:720:interp_algo=super:format=nv12[v720o]; \
[v480] scale_npp=854:480:interp_algo=super:format=nv12[v480o]" \
-map "[v108o]" -map 0:a:0 -c:v h264_nvenc -preset p5 -tune hq -rc vbr_hq \
-b:v 5200k -maxrate 6000k -bufsize 10M -g 120 -bf 2 -spatial_aq 1 -aq-strength 8 \
-c:a aac -b:a 160k -ar 44100 -ac 2 \
-f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments+independent_segments \
-method PUT "/var/www/hls/stream001/1080p/prog_index.m3u8" \
-map "[v720o]" -map 0:a:0 -c:v h264_nvenc -preset p5 -tune ll -rc vbr_hq \
-b:v 2800k -maxrate 3200k -bufsize 6M -g 120 -bf 2 -spatial_aq 1 -aq-strength 10 \
-c:a aac -b:a 128k -ar 44100 -ac 2 \
-f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments+independent_segments \
-method PUT "/var/www/hls/stream001/720p/prog_index.m3u8" \
-map "[v480o]" -map 0:a:0 -c:v h264_nvenc -preset p4 -tune ll -rc vbr_hq \
-b:v 1200k -maxrate 1500k -bufsize 3M -g 120 -bf 2 -spatial_aq 1 -aq-strength 12 \
-c:a aac -b:a 96k -ar 44100 -ac 2 \
-f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments+independent_segments \
-method PUT "/var/www/hls/stream001/480p/prog_index.m3u8"
要点:
- -hwaccel_output_format cuda + scale_npp 会走 GPU 上的 resize(省 CPU);
- -tune ll/-tune hq 视延迟/质量取舍;
- HLS 切片缩到 2s,兼顾延迟与稳定;
- 多路映射让一进多出,减少解码开销。
B. 经典写法(FFmpeg 4.x,CUVID 解码)
ffmpeg -hide_banner -c:v h264_cuvid -surfaces 16 -extra_hw_frames 8 \
-i "rtmp://10.10.10.40:1935/live/stream001" \
-filter_complex "\
[0:v]split=3[v1080][v720][v480]; \
[v1080]scale_npp=1920:1080:format=nv12[v108o]; \
[v720] scale_npp=1280:720:format=nv12[v720o]; \
[v480] scale_npp=854:480:format=nv12[v480o]" \
-map "[v108o]" -map 0:a:0 -c:v h264_nvenc -preset p5 -rc vbr_hq -cq 23 -g 120 \
-c:a aac -b:a 160k -f flv "rtmp://127.0.0.1:1935/abrlive/stream001_1080" \
-map "[v720o]" -map 0:a:0 -c:v h264_nvenc -preset p5 -rc vbr_hq -cq 24 -g 120 \
-c:a aac -b:a 128k -f flv "rtmp://127.0.0.1:1935/abrlive/stream001_720" \
-map "[v480o]" -map 0:a:0 -c:v h264_nvenc -preset p4 -rc vbr_hq -cq 25 -g 120 \
-c:a aac -b:a 96k -f flv "rtmp://127.0.0.1:1935/abrlive/stream001_480"
七、HLS/分发 NGINX 配置
worker_processes auto;
worker_rlimit_nofile 1048576;
events {
worker_connections 65535;
use epoll;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
aio threads;
open_file_cache max=100000 inactive=20s;
server {
listen 80;
server_name _;
root /var/www/hls;
location /hls/ {
types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; }
add_header Cache-Control no-cache;
}
location /live.flv {
flv_live on; # 若使用 nginx-http-flv-module
}
}
}
八、GPU 资源规划与算力估算(经验值)
| GPU | 编码 H.264 预设 | 单卡 1080p30 转码路数(3码级 ABR) | 备注 |
|---|---|---|---|
| L4 | preset p5/p6 vbr_hq |
20~25 路(每路产 1080/720/480) | 更高效能,P7 画质更好但密度下降 |
| T4 | preset p4/p5 vbr_hq |
10~14 路 | 质量/密度折中 |
| P2000 | p4 |
7~10 路 | 旧但能打,注意驱动版本 |
说明:单路 1080p→ABR(1080/720/480) 约等价 3 路 NVENC 的复杂度;内容复杂度(运动、纹理)会显著影响路数。
CPU 仅做音频与封装,通常 16C 就能扛数十路 ABR。
九、系统调优(网络、文件句柄、IRQ)
/etc/sysctl.d/99-live.conf:
fs.file-max = 2097152
net.core.netdev_max_backlog = 250000
net.core.somaxconn = 65535
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_syncookies = 1
sysctl --system
ulimit -n 1048576
NIC & IRQ 建议
- 开 irqbalance,多队列 NIC 绑定中断到不同 CPU(NUMA 亲和);
- ethtool -G eth0 rx 4096 tx 4096 适度增大环形缓冲;
- Ingest/Delivery 节点开启 reuseport,消化大量连接。
十、进程编排与多卡绑定
多卡绑定:用 CUDA_VISIBLE_DEVICES 最稳:
# 绑定到第0号物理卡
CUDA_VISIBLE_DEVICES=0 ffmpeg ... # your pipeline
# 绑定到第1号物理卡
CUDA_VISIBLE_DEVICES=1 ffmpeg ...
或 FFmpeg 指定 -gpu 0/1(不同版本支持差异,推荐 CUDA_VISIBLE_DEVICES)。
Systemd 服务化(示例 one stream → three variants):
[Unit]
Description=FFmpeg GPU Transcode stream001
After=network.target
[Service]
Environment=CUDA_VISIBLE_DEVICES=0
ExecStart=/usr/local/ffmpeg/bin/ffmpeg -hide_banner -hwaccel cuda -hwaccel_output_format cuda -threads 1 \
-rtbufsize 256M -fflags nobuffer -i rtmp://10.10.10.40:1935/live/stream001 \
-filter_complex "[0:v]split=3[v1080][v720][v480];[v1080]scale_npp=1920:1080:format=nv12[v108o];[v720]scale_npp=1280:720:format=nv12[v720o];[v480]scale_npp=854:480:format=nv12[v480o]" \
-map "[v108o]" -map 0:a:0 -c:v h264_nvenc -preset p5 -rc vbr_hq -b:v 5200k -maxrate 6000k -bufsize 10M -g 120 -c:a aac -b:a 160k \
-f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments+independent_segments /var/www/hls/stream001/1080p/prog_index.m3u8 \
-map "[v720o]" -map 0:a:0 -c:v h264_nvenc -preset p5 -rc vbr_hq -b:v 2800k -maxrate 3200k -bufsize 6M -g 120 -c:a aac -b:a 128k \
-f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments+independent_segments /var/www/hls/stream001/720p/prog_index.m3u8 \
-map "[v480o]" -map 0:a:0 -c:v h264_nvenc -preset p4 -rc vbr_hq -b:v 1200k -maxrate 1500k -bufsize 3M -g 120 -c:a aac -b:a 96k \
-f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments+independent_segments /var/www/hls/stream001/480p/prog_index.m3u8
Restart=always
RestartSec=3
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
十一、监控与告警
- GPU:nvidia-smi dmon -s pucvmet、DCGM Exporter → Prometheus/Grafana;
- 系统:node_exporter、nginx-vts/rtmp 统计;
- FFmpeg:-progress pipe:1 + 解析 QPS/帧率/丢帧;
- 告警:GPU 利用率 > 85% 持续 3 分钟、NVENC 编码失败率 > 0.5%、丢帧 > 1%。
十二、压测方法(可复现)
本地循环推流 100 路(示例素材 30fps):
for i in $(seq -w 1 100); do
ffmpeg -re -stream_loop -1 -i sample_1080p30.mp4 \
-c copy -f flv "rtmp://ingest.local:1935/live/stream$i" \
>/dev/null 2>&1 &
done
观察 CPU/GPU:
- GPU:watch -n1 nvidia-smi
- CPU:htop
HLS 切片目录增长是否平稳,播放器连播是否不卡段。
十三、常见坑与现场解法
| 现象 | 可能原因 | 快速解法 |
|---|---|---|
No NVENC capable devices found |
驱动版本与 nv-codec-headers/FFmpeg 不匹配;容器内没挂载 /dev/nvidia* |
统一驱动+headers 版本;Docker 加 --gpus all |
Device busy / 编码器繁忙 |
同卡并发路数超上限、预设过重 | 降 preset/rc-lookahead,分摊到另一张卡 |
cuvid: no decoder surfaces |
解码队列不足 | 增 -surfaces、-extra_hw_frames |
| HLS 播放卡段 | 切片时间与 GOP 不匹配;磁盘 IOPS 不足 | -g 约等于 fps*segment_time;SSD or tmpfs |
| CPU 仍很高 | scale 走了 CPU;音频滤镜开销 |
使用 scale_npp;音频尽量直通或轻量化 |
| 香港公网抖动大 | RTT 抖动/丢包 | 换 SRT 推流(设置 latency=120~200ms) |
十四、版本兼容与建议预设
| 组件 | 建议版本线 | 备注 |
|---|---|---|
| NVIDIA 驱动 | 470 LTS(CentOS 7 通常友好)/ 525+(新卡) | 与硬件、FFmpeg/nv-codec-headers匹配 |
| FFmpeg | 4.4/5.1 分支 | 5.x 用 -hwaccel cuda 路线;4.x 用 *_cuvid |
| 编码预设 | preset p5/p6 + rc vbr_hq + spatial_aq=1 |
直播质量/密度平衡;体育可上 p6、降低 aq |
十五、成本对比(粗算)
| 方案 | 机器 | 并发 1080p→三档ABR 路数 | 预估月成本(HK) | 单路成本 |
|---|---|---|---|---|
| 纯 CPU(32c) | 无 GPU | 3~5 | ¥X | 高 |
| T4(单卡) | 1×T4 | 10~14 | ¥X+ | 低 |
| L4(单卡) | 1×L4 | 20~25 | ¥X++ | 更低 |
实际成本取决于机房价格与带宽计费。GPU 密度越高,单位路数成本越低。
十六、应急与回滚
- 转码层挂了:Ingest 层临时直推 原始码流 给播放器(降级无 ABR);
- 单卡炸了:Systemd Unit 自动拉起,Prometheus 告警触发把该流迁到其他卡;
- HLS 出错:回滚到 FLV 直出(延迟更低,靠边缘节点抗抖)。
十七、可直接复用的启动脚本(多流批量)
/opt/transcode/run_stream.sh
#!/usr/bin/env bash
set -euo pipefail
STREAM_ID="$1" # e.g. stream001
SRC_URL="rtmp://10.10.10.40:1935/live/${STREAM_ID}"
OUT_DIR="/var/www/hls/${STREAM_ID}"
mkdir -p "${OUT_DIR}"/{1080p,720p,480p}
exec ffmpeg -hide_banner -hwaccel cuda -hwaccel_output_format cuda -threads 1 \
-fflags nobuffer -rtbufsize 256M -i "${SRC_URL}" \
-filter_complex "\
[0:v]split=3[v1080][v720][v480]; \
[v1080]scale_npp=1920:1080:format=nv12[v108o]; \
[v720] scale_npp=1280:720:format=nv12[v720o]; \
[v480] scale_npp=854:480:format=nv12[v480o]" \
-map "[v108o]" -map 0:a:0 -c:v h264_nvenc -preset p5 -rc vbr_hq -b:v 5200k -maxrate 6000k -bufsize 10M -g 120 -bf 2 -spatial_aq 1 -aq-strength 8 \
-c:a aac -b:a 160k -ar 44100 -ac 2 -f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments+independent_segments \
"${OUT_DIR}/1080p/prog_index.m3u8" \
-map "[v720o]" -map 0:a:0 -c:v h264_nvenc -preset p5 -rc vbr_hq -b:v 2800k -maxrate 3200k -bufsize 6M -g 120 -bf 2 -spatial_aq 1 -aq-strength 10 \
-c:a aac -b:a 128k -ar 44100 -ac 2 -f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments+independent_segments \
"${OUT_DIR}/720p/prog_index.m3u8" \
-map "[v480o]" -map 0:a:0 -c:v h264_nvenc -preset p4 -rc vbr_hq -b:v 1200k -maxrate 1500k -bufsize 3M -g 120 -bf 2 -spatial_aq 1 -aq-strength 12 \
-c:a aac -b:a 96k -ar 44100 -ac 2 -f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments+independent_segments \
"${OUT_DIR}/480p/prog_index.m3u8"
批量跑(每卡 10 路示例):
export CUDA_VISIBLE_DEVICES=0
for i in $(seq -w 1 10); do
/opt/transcode/run_stream.sh "stream${i}" >/var/log/ffmpeg_stream${i}.log 2>&1 &
done
十八、额外加分:HEVC、低延迟与 SRT
- HEVC(h265_nvenc):比特率可降 30% 左右,但终端兼容度要看业务;
- 低延迟:-tune ll、GOP 短、HLS part/LL-HLS 或直接走 FLV/WebRTC;
SRT 推流:
srt://your_ip:10080?mode=caller&latency=150&streamid=live/stream001
服务端 SRS 配 srt_server,公网抖动时更稳。
后来我们把香港的 GPU 集群做成了模板:Ingest 按连接扩、Transcode 按码级扩、Delivery 按带宽扩。那台先上车的 T4,贴着我手写的标签“夜班英雄一号”。
直播平台的高并发,跟午夜机房的风一样,时大时小。把 CPU 的苦活脏活交给 GPU,我们从此再也没被凌晨的告警吓醒。
如果你也正要在香港节点落 GPU 转码,希望这篇实操能让你少绕点路。哪怕风再大,只要切得稳,画面就不会抖。
快速清单(拿去即用)
✅ CentOS 7 裸机:ELRepo 装 NVIDIA 驱动(470LTS/匹配卡)
✅ 编译 FFmpeg(启用 --enable-nonfree --enable-nvenc --enable-cuvid/--hwaccel cuda)
✅ Ingest 用 SRS(RTMP/SRT),转发到转码层内网
✅ FFmpeg:NVDEC 解码 + scale_npp + NVENC 多码率
✅ NGINX 分发 HLS(2s 切片、independent_segments)
✅ ulimit、sysctl、NIC/IRQ 调优
✅ DCGM/Grafana 盯 GPU,nvidia-smi 看温度与功耗
✅ 预案:GPU 故障→旁路直出原始码流,或迁移到其他卡