ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

多路推流实战:用FFmpeg与systemd实现多平台稳定直播分发

多路推流实战:用FFmpeg与systemd实现多平台稳定直播分发 多路推流这个词做直播久了基本绕不开。我这次的项目就是典型的单源对多平台分发一路直播画面要同时推到几个不同直播间还必须连续稳定运行不能三天两头断。从搭方案到调参再到后端做守护和监控整套东西已经跑满一个月期间踩过的坑比大多数教程里写的都多。这套方案适合谁一是做多平台直播运营想省掉反复开关推流的麻烦二是自建视频站、电商直播间需要把源站内容同步分发三是想搞明白“为什么我的流老断”“为什么画质变糊”这类问题的人。本文不玩虚的讲清楚原理也给可以直接抄的配置和命令。1. 对多路推流的理解不只是一个“转发”动作1.1 什么是多路推流它解决了什么场景多路推流核心动作就是把一路视频流同时分发到多个目的地。常见的场景有三种同一个直播间画面同步到抖音、快手、B站等多个平台同一场活动用不同机位或不同包装画面分别推给不同直播间还有多个独立内容源由一台服务器统一管理、统一对外推送。很多人第一反应是“这不就是转发一下吗”。实际上“转发”背后隐藏着编码、封装、协议、网络、平台策略五层问题。我这次做的是第一种场景一路 RTMP 输入源同时推送到三个目标平台每天直播时长不固定高峰期能到七八个小时整个系统要能自动扛住断网、重启、平台抖动这些意外。“稳定跑一个月”这个目标拆开来看其实是四个子目标进程不能莫名其妙退出退出后要能自动恢复长时间跑音画不能漂移平台侧不能主动掐流。这四个子目标每一个都在后续实操中对应着具体的配置和踩坑。1.2 三种常见实现路线单进程多输出、多进程并发、自建中继多路推流的实现路线我梳理下来无非三种。第一种是单进程多输出用一个 FFmpeg 进程读一路输入然后同时输出到多个目标地址。优点是部署简单、进程数量少、统一管理缺点是如果某个输出平台异常可能会拖累整个进程。第二种是多进程并发每个目标平台起一个独立的 FFmpeg 进程。优点是互相隔离一个挂了不影响其他缺点是资源占用翻倍管理多个进程的复杂度也上来了。第三种是自建中继服务器也就是先把流推到自己的流媒体服务器上比如 SRS、ZLMediaKit 或者 Nginx-RTMP再由服务器向多个平台分发。这种方案适合源端带宽不够、或者需要给多个源端做汇聚的场景但会增加一层转发延迟服务器本身也要维护。我最终选的是“单进程多输出 systemd 进程守护”组合原因很简单目标平台就三个输入源稳定单进程多输出在性能和复杂度之间最平衡。同时我在后端加了守护和监控即使某个输出平台异常导致进程退出也能秒级自动拉起实际效果比我预想的要稳。1.3 一个容易踩的认知误区多平台不等于多倍性能开销这里必须先说清楚一个误区。很多人以为推几个平台CPU 和内存就要翻几倍其实完全不是这么回事。关键在于是“转码”还是“转发”。如果目标平台对码率、分辨率没有特殊要求可以用-c copy直接把原始音视频流复制到多个输出这时候 FFmpeg 不做视频编码计算CPU 占用可能连 5% 都不到。带宽才是真正按路数累加的每推一个平台上行流量就会多一份这个省不了。但如果每个平台要求的分辨率、码率不一样那就必须分别转码每增加一个转码输出CPU 就会多一份真实负载。举个例子一份 1080p 输入A 平台要 1080pB 平台要 720pC 平台只收 480p那等于要做三份不同的编码工作四核小服务器基本跑不动。理解这一点后续做资源规划就不会拍脑袋。很多人一上来就上高端服务器其实纯转发场景一台 2 核 4G 的轻量云主机就足够了真正吃性能的是转码路径而不是多路推流本身。2. 方案落地前的关键选型与参数设计2.1 先算清楚带宽、CPU 和连接数限制这套方案跑了一个月我最大的感触是多路推流出问题绝大多数不是软件不行而是前期资源和参数没算明白。这里分享一个最简单的估算方法。先算带宽。假设视频码率是 4500kbps音频码率是 192kbps一路推流大约需要 4.7Mbps 的上行带宽。推到 N 个平台就是 4.7 乘以 N。推到 3 个平台大概需要 14.1Mbps 上行带宽。这个数字是均值实际还要考虑直播码率的波动建议预留 30% 的余量。我在服务器上实测推 3 路 1080p 内容时峰值上行能到 20Mbps 以上如果带宽买小了第一周就会遇到卡顿和平台拉流超时。再算 CPU。用-c copy纯转发CPU 占用很低但只要做一次转码CPU 占用就会明显上涨。以 libx264 编码 1080p30 为例单个输出大约需要占用 1 到 2 个完整核心。也就是说2 核 4G 的机器如果同时做 3 路转码CPU 基本跑满帧率会掉直播画面就会开始卡顿。最后是连接数。每个平台对推流连接数都有隐含限制同一个推流地址通常只允许一个活跃连接。如果你用同一个 key 推到同一个平台多次或者连接断了但没释放干净就会遇到“连得上但秒断”或者“直接拒绝连接”的情况。这个坑我在后面会详细讲。2.2 推流参数怎么定码率、GOP、采样率这些细节参数设计直接决定能跑多久。我这边最终稳定运行一个月核心参数如下表。参数推荐值原因视频编码H.264平台兼容性最好编码预设veryfast 或 fast平衡 CPU 和画质视频码率4500kbps1080p30多数平台稳定接收最大码率5500kbps给编码波动留余量GOP 大小6030fps 下 2 秒平台拉流、黑屏恢复的关键音频编码AAC-LC兼容性最好音频采样率44100Hz避免部分平台杂音问题音频码率192kbps语言和音乐场景都够用重点说一下 GOP。GOP 就是两个关键帧之间的间隔-g参数控制的是每隔多少帧插入一个关键帧。平台服务器在观众刚进入直播间、或者断网重连的时候需要等到下一个关键帧才能给新观众画面。如果 GOP 太短比如 1 秒画面恢复快但码率浪费多如果 GOP 太长比如 8 秒观众进入直播间就会黑屏很久部分平台甚至会在检测到长时间没有关键帧时主动断开连接。实测下来2 秒的 GOP 是最稳妥的也就是 30 帧率下把-g设为 60。音频采样率也是一个容易被忽略的细节。我之前用默认的 48000Hz 推一个平台跑了两天后平台后台出现了偶发杂音后来统一改成 44100Hz 就再没出现过。这不是编码问题而是平台转码链路对采样率有偏好一味开高参数反而会引入兼容性风险。2.3 不同平台对参数的兼容性差异别用一套参数打天下多路推流最麻烦的不是本地的 FFmpeg 配置而是不同目标平台之间的参数要求存在差异。有的平台限制最大码率 6Mbps你推 8Mbps 就会被强制转码或者掐流有的平台对音频编码格式挑剔HE-AAC 可能不兼容必须用 AAC-LC还有的平台帧率上限是 30fps你推 60fps 它也能接但后台会抽帧画面会出现微卡。所以我的建议是在正式上线前先针对每个目标平台单独推流测试 10 到 15 分钟到平台后台看实际的接收码率、分辨率、音频格式确认无误后再切换到统一的多路推流命令。如果某个平台确实要求特殊参数比如某个平台只允许 720p那就不能只用一个-c copy必须单独给这个平台做一次转码。转码输出加-s 1280x720和-b:v 2500k其他平台保持原始参数复制即可。这个方案我在后面命令里会给出实际例子。3. 实操搭一套能跑一个月的多路推流系统3.1 服务器与源端准备我是跑在 Linux 云服务器上的系统是 Debian 122 核 4G 内存带宽峰值 30Mbps实际推 3 路完全够用。如果你需要推 5 路以上建议带宽预留到 50MbpsCPU 也换成 4 核以上这样遇到码率波动和并发高峰时才不会抓瞎。源端这边我用的是 RTMP 拉流。也就是直播源从上游推送到服务器本机或内网的另一台机器再由这台机器做多路分发。这样设计的好处是即使上游短暂中断只要本机先缓存住一路流恢复后可以自动续上不影响对外多路推流。大家在准备阶段还需要确认几件事FFmpeg 版本不要太老建议 4.4 以上新版本对 RTMP 协议和音频滤镜的兼容性更好服务器时间要校准时间戳异常、鉴权失败这类问题很多都跟系统时间漂移有关推流地址和串流密钥建议写成配置文件不要硬编码在命令行里方便后续改参数。3.2 核心 FFmpeg 命令一路输入多路输出确认环境和输入源之后核心命令其实不长。纯转发场景也就是所有目标平台都能接受相同的分辨率和码率时直接用一个 FFmpeg 进程搞定ffmpeg -re -i rtmp://source/app/stream \ -c:v copy -c:a copy \ -f flv rtmp://platform-a.example.com/live/keyA \ -f flv rtmp://platform-b.example.com/live/keyB \ -f flv rtmp://platform-c.example.com/live/keyC \ -loglevel error逐行解释一下。-re表示按源文件的原始帧率读取输入避免 FFmpeg 以最快速度读取导致推流过快。-c:v copy -c:a copy是纯复制不做转码CPU 占用极低。后面的每个-f flv 推流地址就是一个独立的输出目标FFmpeg 会把同一份音视频数据同时写入这些地址。如果你遇到某个平台要求不同分辨率那就得把那个输出单独转码比如平台 B 只要 720pffmpeg -re -i rtmp://source/app/stream \ -c:v copy -c:a copy \ -f flv rtmp://platform-a.example.com/live/keyA \ -c:v libx264 -preset veryfast -b:v 2500k -s 1280x720 -c:a copy \ -f flv rtmp://platform-b.example.com/live/keyB \ -c:v copy -c:a copy \ -f flv rtmp://platform-c.example.com/live/keyC \ -loglevel error这种写法下平台 A 和 C 是纯转发平台 B 做了一次转码。要注意的是转码输出和复制输出不能共用同一个-c:v参数必须在每个输出段前单独指定编码器。转码路数越多CPU 负载越高这个命令跑起来后一定要用 htop 观察一段时间再决定要不要加机器。3.3 用 systemd 做进程守护崩溃后自动拉起命令能跑起来只是第一步。真实线上环境里FFmpeg 进程会因为网络抖动、平台主动断开、内存波动等原因退出如果没有守护机制可能你第二天早上醒来直播已经断了好几个小时。我用的是 systemd这在大多数 Linux 发行版上都是内置的不用额外安装组件。写一个 service 文件[Unit] DescriptionMulti-Push FFmpeg Service Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Userpusher ExecStart/usr/bin/ffmpeg -re -i rtmp://source/app/stream -c:v copy -c:a copy -f flv rtmp://platform-a.example.com/live/keyA -f flv rtmp://platform-b.example.com/live/keyB -f flv rtmp://platform-c.example.com/live/keyC -loglevel error Restartalways RestartSec5 [Install] WantedBymulti-user.target核心是Restartalways和RestartSec5。这两行的意思是只要进程不是被管理员手动停止的退出后 5 秒内自动重新拉起。我实测过模拟断网的情况FFmpeg 因为网络原因退出后systemd 会在 5 秒后自动重启新进程然后把流重新推起来整个过程不到 10 秒。配置完成后执行sudo systemctl daemon-reload sudo systemctl enable --now multi-push sudo systemctl status multi-push如果有多个不同场景的推流任务可以复制多个 service 文件用不同的名字区分。但注意每个 service 最好只跑一个 FFmpeg 进程不要在一个 ExecStart 里塞太多逻辑否则排障的时候会非常痛苦。3.4 监控、日志与定期体检进程能自愈之后还需要一个能“实时发现问题”的监控机制。不要等到观众在后台反馈直播断了才发现。日志方面我的命令里带了-loglevel error这样 FFmpeg 只在出错时输出日志不会刷屏。查看日志用 systemd 自带的能力journalctl -u multi-push -n 100 journalctl -u multi-push --since 1 hour ago如果发现日志里频繁出现Connection timed out、Broken pipe之类的错误就要小心了这通常是平台侧在主动断开连接或者源端网络有抖动。系统层面我写了一个简单的健康检查脚本每 5 分钟跑一次检查进程是否存活、CPU 和内存使用率是否异常、带宽是否打满#!/bin/bash if ! pgrep -f ffmpeg -re /dev/null; then systemctl restart multi-push echo $(date) FFmpeg not running, restarted /var/log/multi-push-health.log fi CPU$(ps -C ffmpeg -o %cpu --no-headers | awk {s$1} END {print s}) MEM$(ps -C ffmpeg -o %mem --no-headers | awk {s$1} END {print s}) echo $(date) CPU$CPU% MEM$MEM% /var/log/multi-push-health.log这个脚本配合 systemd 使用systemd 管进程自愈脚本提供长周期的健康记录。每周花两分钟看一眼日志文件就能掌握这套系统的长期运行规律。4. 一个月里真实踩过的坑与排查技巧4.1 平台无故断流多半出在关键帧间隔上上线第一天我就踩了一个大坑。当时用了默认的 GOP 参数没有显式设置-g结果 FFmpeg 默认插关键帧间隔比较长大概 250 帧也就是 8 秒左右才出一个关键帧。跑了一天第 2 天早上平台 A 就开始随机断流断流前没有任何报错连接直接被平台侧关闭。一开始还以为是网络问题折腾了半天最后用 ffprobe 看了一下实际推流的帧信息才发现关键帧间隔太长了。平台服务器的逻辑通常是新观众进入直播间要等到关键帧才开始拉流如果长时间没有关键帧平台会判定流质量异常主动断开。排查方法很简单ffprobe -v error -select_streams v -show_entries framepict_type -of csvp0 input.flv | grep -n I | head -20这条命令会输出视频流中关键帧所在的位置你可以直观看到每两个关键帧之间隔了多少帧。把-g 60加上后再跑一周断流问题再没出现过。4.2 音画不同步时间戳才是罪魁祸首第二个坑出现在运行了大概一周之后。观众反馈画面和声音对不上画面比声音慢了半拍而且差距随时间越来越大。重启推流进程后能恢复正常但跑十几个小时后又会复发。这是典型的时间戳漂移问题。源流在长时间编码、传输过程中视频和音频的 DTS/PTS 可能发生偏移FFmpeg 如果严格按照源流时间戳转发漂移会累积最终表现为音画不同步越来越严重。解决方案分两步。第一步如果做的是纯复制转发尽量保证源端时间戳是规整的方法是在输入后加-fflags genpts让 FFmpeg 自己生成时间戳ffmpeg -fflags genpts -re -i rtmp://source/app/stream ...第二步如果转码输出可以给音频加一个重采样滤镜让它自动同步到视频轨道-af aresampleasync1:first_pts0这个滤镜的意思是音频播放时如果和视频时间轴偏差超过阈值就自动补帧或丢帧尽量让音视频对齐。注意-af只对转码输出有效配合-c:v copy时不会生效。4.3 CPU 和带宽告急多路不是免费的跑到第三周的时候我加了一路新的输出本意是让内容覆盖更多平台。结果加了之后 CPU 直接从 30% 飙升到 90%画面上开始出现明显卡顿推上去的流时不时花屏。排查下来问题出在这路新输出需要转码。当时为了省事我直接复制了主输出参数没有注意到新平台只支持 720p所以 FFmpeg 为这路输出重新做了编码。纯转发 3 路可能只需要 5% 的 CPU但 3 路里混入 1 路转码CPU 占用就会暴涨。这个经验告诉我们加路数之前先确认目标平台能不能接受和源流完全一致的参数。如果多数平台都能用 copy只有少数平台需要特殊参数那就把需要转码的路数单独拎出来并且尽量降低转码路数的分辨率比如直接从 1080p 转 720pCPU 开销能少一半。带宽同理。我做了个粗略统计3 路纯转发的情况下峰值上行带宽稳定在 20Mbps 左右一个月下来没出过一次带宽瓶颈但如果我加到 5 路同样的码率峰值就会接近 35Mbps这个量级已经超过单台云服务器的常见带宽峰值了。多路推流的前提是带宽预算要按“路数乘以单路码率”来算千万不能只按一路估算。4.4 排查工具与命令速查最后把这一个月里派上大用场的排查命令整理成一张速查表建议收藏备用。需求命令看服务状态和最近日志systemctl status multi-push和journalctl -u multi-push -n 50看实时 CPU 和内存占用ps -eo pid,comm,%cpu,%mem --sort-%cpu看带宽实时占用iftop -i eth0看关键帧间隔ffprobe -v error -select_streams v -show_entries framepict_type -of csvp0 input.flv单独测一路推流是否正常ffmpeg -re -i test.flv -c copy -f flv rtmp://目标地址抓包分析 RTMP 连接tcpdump -i eth0 -n port 1935验证音频是否同步ffprobe -v error -show_entries streamindex,codec_type,duration -of json input.flv其中iftop需要单独安装但它在排查带宽问题时的作用无可替代。真的怀疑哪一路在“吃”带宽用iftop按连接数排序一眼就能看出占比。最后说句实在话。多路推流跑一个月技术方案本身并不神秘真正的难点在细节关键帧间隔、时间戳、资源余量、进程守护任何一个环节偷懒都会在某个凌晨给你颜色看。我现在维护这套系统时最重视的就是每次改参数只改一个变量并且保留旧配置随时可以回滚。技术是跑出来的坑是踩出来的希望这次分享能让你少走几个弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进