直播平台如何在香港服务器上利用 FFmpeg GPU 转码,降低高并发推流的 CPU 占用(含完整实操与优化细节)
技术教程 2025-09-15 10:30 229


我第一次把 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 小文件分发 打开 sendfileaio
网络 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=120200ms

十四、版本兼容与建议预设

组件 建议版本线 备注
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 故障→旁路直出原始码流,或迁移到其他卡