ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

多路推流稳定运行实战:SRS+FFmpeg架构详解与避坑指南

多路推流稳定运行实战:SRS+FFmpeg架构详解与避坑指南 1. 项目概述与思路解构先说结论多路推流这事儿真正难的不是把流推上去而是让流一直稳稳地挂着不掉。我这边有一个实际项目7路直播信号同时推送到不同平台连续跑了一个月没出大问题。中间当然也踩了不少坑有些问题藏得特别深不跑个十天半个月根本暴露不出来。1.1 核心需求解析所谓多路推流简单说就是同时把一路或多路视频源推送到多个不同的直播平台或CDN节点。常见的业务场景有这么几类电商直播同时推送到抖音、快手、视频号、淘宝等多个平台一次开播全网覆盖。线下活动、发布会现场一路摄像机信号同时分发到多个渠道。监控场景下多路摄像头的画面需要同时上云给不同终端调用。赛事直播一路节目源推给多个平台的转播通道。我这次的项目属于第三类偏第二类的混合场景一路主信号源加上三路辅助机位共4路原始视频流然后每路需要推送到2到3个不同目标实际并发推流通道接近10路全天24小时不间断运行目标是一个月不重启、不黑屏、不卡死。这个需求听起来不复杂但真跑起来问题就一层一层往外冒。我先简单说一下我最终的架构选型再逐个拆解那些只有长期运行才会遇到的深水区问题。1.2 方案选型的底层逻辑在开始设计前我其实是纠结过几条路线的。这里把当时的对比逻辑展开讲讲相信不少人有同样的经历。方案A纯软件编码推流比如FFmpeg直接推FFmpeg做推流是最常见的做法命令行一行搞定看起来简单。但多路长期运行的话FFmpeg的单进程稳定性、断流重推逻辑、GPU或CPU资源占用都需要额外的守护进程来处理。一旦输入源抖动导致编码器异常退出很快整条链路就断了。方案B硬件编码器 流媒体服务器中转用硬件编码器把信号源编码成RTMP或SRT推到自建的流媒体服务器比如SRS、Nginx-RTMP再由服务器转发到各平台。这个方案的好处是编码稳定、资源可控、转发逻辑可以在服务端灵活配置出问题也好排查。缺点是部署复杂度高一些需要有一台配置还算可以的服务器。方案C商业聚合推流服务市面上有现成的多路推流SaaS功能完善但按路数按流量收费长期运行的账单相当可观而且信号源数据要经过第三方服务器对于部分业务有保密顾虑。我最终选的是方案B自建SRS作为汇聚转发层。原因有三点一是各路信号源五花八门有的走RTSP有的走RTMP有的走SRT统一到SRS再分发格式转换集中在一个节点处理二是转发路径可控推流目标有变更时不用动编码端三是排查问题时有完整的日志和监控指标长期运行最怕黑盒。注意如果只是短期测试或者路数很少比如就两路方案A完全够用没必要上服务器。但如果你也面临7x24小时、多路、多目标这种组合条件建议直接上中转架构省得后期反复折腾。2. 核心细节解析与实操要点多路推流稳定运行的一个月里我总结下来真正决定成败的细节主要集中在四个层面。逐个说。2.1 推流协议选型不同场景的取舍先明确一点多路推流不等于多路都用同一种协议。具体用什么协议取决于信号源类型、网络环境和目标平台的要求。常见协议对比我整理了一张表协议延迟穿透性抗丢包适用场景RTMP3-10秒好弱国内大部分直播平台接入SRT0.3-2秒中强跨国传输、弱网环境RTSP0.5-3秒差中局域网内监控取流WebRTC0.2-1秒中中低延迟互动直播、连麦HLS10-30秒好强点播、延迟不敏感的播放场景我这次项目中信号源是几台网络摄像头和一路采集卡摄像头走RTSP取流采集卡走HDMI进编码器输出SRT给到SRS。SRS再以RTMP协议转发到各个直播平台。这里有一个关键选择为什么从编码器到SRS用SRT而不是直接RTMP答案很简单摄像头到服务器这一段网络环境不可控SRT有完整的ARQ重传机制在轻微丢包的链路上表现远好于RTMP。实测在2%丢包的网络环境下RTMP基本要花屏断流SRT还能稳定传输。目标平台那边因为是机房到机房的专线或优质公网丢包率低所以转发段用RTMP就足够同时也保证了对目标平台最大的兼容性。再说直白一点越是不可控的网络段越要用抗丢包能力强的协议可控的网络段则可以优先考虑兼容性。2.2 编码参数与码率控制的平衡多路推流里编码参数直接决定了服务器压力和平台接收质量。很多人觉得码率越高画质越好就把码率往高了设结果跑几天服务器CPU持续100%或者目标平台报错不断。我最终使用的编码参数如下ffmpeg -re -i rtmp://localhost/live/main -c:v libx264 -preset veryfast -b:v 3500k -maxrate 4000k -bufsize 6000k -g 60 -keyint_min 60 -sc_threshold 0 -c:a aac -b:a 128k -ar 44100 -f flv rtmp://target1/live/stream几个参数背后的考量逐个说清楚-preset veryfast编码预设从ultrafast到placebo共10档。veryfast在画质和CPU占用之间比较均衡。实测preset换成medium画质提升肉眼几乎看不出来CPU占用却能高出20%到30%多路并发时这就是致命的。-g 60 与 -keyint_min 60GOP大小设为60帧。以25fps来算就是每2.4秒一个关键帧。关键帧间隔不能太大否则观众端切换清晰度时等待时间会非常长也不能太小否则码率浪费在I帧上。有些直播平台会强制要求关键帧间隔推流时不时断连先检查GOP是不是不符合平台规定。-sc_threshold 0禁用场景自动切换关键帧。这个参数很容易被忽略。它的作用是场景切换时自动插入I帧看起来是好事但会导致关键帧间隔不规律部分平台拉流端可能出现卡顿。固定GOP反而更稳。这里补充一句如果你用的是硬件编码器比如常见的M7S、L系列采集卡自带编码在编码器管理后台设置同样的参数含义即可。硬编在长期稳定性上有优势CPU占用极低而且不容易受系统负载影响。如果预算允许多路长期推流优先硬件编码。2.3 带宽计算与网络架构规划多路推流一个月没出问题一半的功劳要归给网络规划。这里分享一个非常容易出现误判的点带宽的计算不能只算上行带宽的总和还要考虑网络波动和协议开销的余量。我项目中的实际数据如下4路视频源每路平均码率3500kbps。其中2路推送到2个平台2路推送到3个平台合计10路转发流。上行带宽需求信号源接入到SRS4 × 3500kbps ≈ 14MbpsSRS转发到目标平台10 × 3500kbps ≈ 35Mbps总带宽需求 ≈ 50Mbps看起来50Mbps的宽带就够了。但实际上我预留了一个关键余量带宽使用率不要超过标称带宽的60%。为什么因为公网的带宽不是恒定不变的。ISP实际提供的带宽会在高峰时段出现波动而且TCP传输本身的拥塞控制会导致吞吐量下降。如果长期把带宽顶在90%以上任何一个网络抖动都会导致推流缓冲堆积进而引发延迟增大甚至断连。最终我的服务器选用的是100Mbps带宽的云主机部署位置选择在离目标直播平台同区域的机房。这样做的原因很简单SRS转发到各平台时走的网络路径尽量短延迟低、丢包少稳定性自然好。2.4 重连机制与断流自愈设计长期跑多路推流断连是迟早的事关键不在于会不会断而在于断了能不能在无人干预的情况下自动恢复。第一期我踩过一个坑SRS转发到某个平台因为目标平台临时升级导致RTMP连接被断开。SRS的默认行为是转发失败后重试几次但几次失败后就彻底放弃该路了需要手动重新拉起。解决这个问题我在架构里加了三个层次的保障第一层输入源断流自愈摄像头RTSP断了之后SRS这边会持续等待重连。但编码端需要主动重推。我在每路信号源的前端加了一个循环脚本监听推流进程状态一旦发现进程退出立即重新拉起推流任务。第二层服务端转发自动重试SRS配置里有个关键参数叫forward_retry_times。默认值是1我改成了-1表示无限重试。这个参数对应SRS配置文件中vhost下的forward配置块vhost __defaultVhost__ { forward { enabled on; destination 192.168.1.10:1935; retry_times 999999; } }这里retry_times不是SRS原文里的参数名具体写法要看实际版本。我用的SRS 5.0版本有这个选项的控制逻辑设为很大值后转发断连会一直尝试不会自动放弃。第三层平台端异常自动剔除有些平台会因为上行不稳定对推流连接做静默断开而且断开后短时间内不接收同key的推流。这种情况只能靠上层监控及时发现切换到备用流名重新推。这三层下来整个系统基本实现了无人值守。一个月里实际发生过3次输入源断流和5次目标平台踢连全部在数秒内自动恢复没有造成长时间黑屏。3. 实操过程与核心环节实现讲完原理层面的东西下面记录一下我能直接抄作业的部署和配置细节。这一部分尽量把关键步骤和现场踩坑的情况都列出来。3.1 服务端环境搭建与SRS配置我用的是一台4核8G的云主机操作系统是Ubuntu 22.04。SRS用Docker方式部署版本为5.0。安装命令很简单官方仓库直接拉取docker pull registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5.0容器启动时将配置目录和日志目录挂载出来方便后续修改和排查docker run -d --name srs \ -p 1935:1935 -p 1985:1985 -p 8080:8080 \ -v /data/srs/config:/usr/local/srs/conf \ -v /data/srs/logs:/usr/local/srs/objs \ registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5.0SRS的核心配置文件里我做了几件重要的事情。首先是开启HTTP API和HTTP回调方便监控推流状态listen 1935; max_connections 1000; daemon off; srs_log_tank file; srs_log_file ./logs/srs.log; http_api { enabled on; listen 1985; } http_hooks { enabled on; on_publish http://127.0.0.1:8080/api/publish_start; on_unpublish http://127.0.0.1:8080/api/publish_stop; }max_connections我设的是1000实际并发连接数只有二三十个但这个值影响的是SRS内部的连接池分配设大一点更稳。HTTP回调主要用于监控每路流的推流状态一旦有流中断后端立即推送告警到群机器人。然后是转发配置。我建了4个vhost每个vhost对应一路信号源这样配置隔离一路出问题不会影响其他路vhost source1 { forward { enabled on; destination 目标平台A的RTMP地址:1935; } play { mw_latency 300; } }每个vhost下可以配置多个forward目标如果同一个源需要推送到多个平台就写多个forward块。SRS在转发时会自动从输入流拉流并重新推流相当于服务端做了分发。这里有个细节必须提醒目标平台如果限制了推流IP转发出去的流会来自你服务器的IP而不是原来推流端的IP。我的服务器配置之前没有注意这一点导致某个平台一直鉴权失败排查了半天才发现平台的IP白名单配置问题。3.2 推流端信源接入与FFmpeg封装信号源接入SRS这一步我用了FFmpeg做协议转换。比如一路RTSP摄像头取流转成RTMP推到SRSffmpeg -rtsp_transport tcp -i rtsp://user:pass192.168.1.64:554/stream1 \ -c:v copy -c:a copy -f flv rtmp://127.0.0.1:1935/source1/live这里用到-c:v copy意思是视频编码不重新编码直接复制封装进FLV。这样可以极大降低推流端的CPU消耗。但前提是输入源的编码格式必须是H.264且音频格式是AAC。如果你的源是H.265HEVC经过FLV封装推给大多数国内平台时会有兼容性问题需要先转成H.264。对于采集卡信号HDMI输入走的是硬件编码器输出SRT流。编码器设置目标地址为SRS的SRT端口。SRS 5.0支持SRT协议需要在配置里单独监听UDP端口srt_server { enabled on; listen 9000; }FFmpeg推SRT流的命令也很简单ffmpeg -i /dev/video0 -c:v libx264 -preset veryfast -b:v 3500k -f mpegts srt://127.0.0.1:9000?modecallerlatency2000000latency参数控制SRT缓冲时长单位是微秒2000000就是2秒。这个值设置在1到3秒间比较合适太小了容易因为网络抖动造成丢帧太大了又会让延迟变得很高。3.3 推流监控与告警体系搭建多路推流要长期稳定没有监控盯不住。我的监控方案分为三层第一层进程级别的守护每路推流命令都用systemd服务管理配合Restartalways保证进程崩溃后自动拉起。这里贴一个service文件的示例[Unit] DescriptionFFmpeg push source1 Afternetwork.target [Service] Typesimple ExecStart/usr/bin/ffmpeg -rtsp_transport tcp -i rtsp://user:pass192.168.1.64:554/stream1 -c:v copy -c:a copy -f flv rtmp://127.0.0.1:1935/source1/live Restartalways RestartSec5 [Install] WantedBymulti-user.targetRestartSec5设置5秒后重试避免进程崩溃后瞬间重启导致反复崩溃。第二层流状态的实时检测通过SRS的HTTP API可以拿到当前所有流的连接状态。我用一个简单的Shell脚本每分钟拉一次数据检测各路流是否有对应的publish连接curl -s http://127.0.0.1:1985/api/v1/streams/ | jq .streams[].name然后把结果和期望的流名列表做比对缺少的流名就触发告警。告警推送到企业微信或者钉钉群用webhook就能实现。第三层拉流播放的可用性验证这一层是最容易被忽视的。SRS显示有推流连接不代表确实是正常在推。我用ffprobe定时去探测每路流的实际输出状态ffprobe -v error -show_entries formatbit_rate -of defaultnoprint_wrappers1 -show_streams -i rtmp://127.0.0.1:1935/source1/live -t 10如果ffprobe能解析出音视频流信息说明流是真正可用的如果解析超时或报错说明虽然连接在但实际内容可能已经异常。这个检测我每5分钟跑一次能在观众发现问题之前先发现。3.4 一个月稳定运行的关键参数总结把这次稳定运行的核心参数整理成一个速查表方便直接复制层级关键参数推荐值备注编码GOP大小60帧(约2.4s)过大影响拉流端切换速度编码编码预设veryfast平衡画质和CPU占用编码视频码率3500-4000kbps依据平台上限调整编码音频码率128kbps AAC过高浪费带宽无实际收益传输SRT延迟2s弱网环境下可调至3s传输RTMP缓冲区默认一般无需调整服务端max_connections1000预留足够余量服务端转发重试无限重试避免断流后自动放弃监控流可用性探测每5分钟验证实际播放状态4. 常见问题与排查技巧实录最后这部分我把这一个月里实际遇到的各种问题和对应的排查过程做个复盘这里面有相当多内容是不跑长期根本碰不到的。4.1 问题排查速查表先给一个速查表格后面再逐个展开典型案例问题现象可能原因排查方法解决方案某一路流推了几小时后画质突然变模糊编码器被系统OOM杀后重启默认参数变化查看进程重启时间与系统日志显式固定编码参数禁止默认值某平台偶发断流其他平台正常该平台对推流IP有地域限制或连接数限制查看SRS日志中的断开原因换推流域名、调整目标地址SRS流列表有连接但拉流端黑屏输入源已停止发送数据但TCP连接未断开ffprobe实际拉流验证增加内容级可用性探测平台推流认证偶尔失败时间戳偏差导致鉴权过期对比服务器时间启用NTP时间同步多路并发时CPU使用率飙升编码预设过慢检查ffmpeg进程CPU占用提高preset速度、改用copy推流延迟越来越大编码端输出缓冲堆积观察延迟指标排查源端帧率、码率设置4.2 长期运行中遇到的高频坑位详解坑位一服务器时钟偏移引起的鉴权问题这是一个比较隐蔽的坑。某直播平台的RTMP推流地址带时间戳签名签名有效期只有5分钟。我服务器运行了一周后某一路流突然频繁报鉴权失败。排查过程刚开始我以为是平台规则变更后来发现其他路推流正常。对比了服务器时间和标准时间发现差了将近3分钟。这就是连续运行一周后系统时钟因为没有启用NTP同步而产生的偏移。而3分钟的偏移已经足够让平台的鉴权签名判定为过期。解决方式很简单启用chrony或ntpd进行时间同步systemctl enable --now chrony chronyc tracking这个坑提醒了一点长期运行的服务时间同步不是可选项而是必选项。坑位二系统OOM killer误杀推流进程我的云主机是4G内存正常情况下跑几路FFmpeg没问题。但运行到第9天时某一路流突然消失进程日志里没有任何报错。查了/var/log/syslog发现OOM killer在凌晨4点杀掉了推流进程。原因是有个日志收集服务出现了内存泄漏把系统内存吃满了。OOM killer在寻找可回收内存时选择了一个内存占用较大的推流进程作为牺牲品。这给了一个重要教训靠systemd的Restartyes不够还得保证系统级别的资源不被其他进程耗尽。我后续给推流进程配置了内存限制用systemd的MemoryMax参数[Service] MemoryMax1G同时定位并解决了那个内存泄漏的日志服务。坑位三目标平台静默断流且不重连这是SRS实际转发时遇到的一个问题。某个平台的CDN节点在流量高峰时会主动断开部分非活跃连接但断开后不返回明确错误SRS收到的只是正常的EOF信号。SRS老版本遇到这种情况会认为推流正常结束不再尝试重推。后来我升级了SRS版本并且在转发配置里将重试逻辑改为无限次。这里也建议做一层外置兜底如果某路流对应平台超过10分钟没有收到推流事件后端系统直接调用SRS的HTTP API删除该流强制触发重推流程。坑位四网络摄像头RTSP长时间运行后自动断开部分摄像头固件对RTSP会话有最大持续时间限制到了时间就会主动断开。FFmpeg收到断开信号后会正常退出但systemd此时如果配置了Restartalways就会重新拉起整体影响不大。问题在于有的摄像头断开后短时间内不接受新的RTSP连接而是有一个冷却时间。如果RestartSec设置太短FFmpeg会不断尝试连接失败陷入反复重启循环。解决方式是把RestartSec调大到30秒同时在连接失败时增加退避策略。或者直接从底层改给摄像头设置计划任务定期重启RTSP服务这样断流时间可控。4.3 应急排查的操作路径如果遇到多路推流整体异常建议按下面的路径排查。这套路径能帮你快速定位是编码端问题、传输链路问题还是目标平台问题第一步确认信号源是否正常直接在服务器上尝试拉取源流看能否正常解码ffprobe -v error -show_entries formatduration -i rtsp://user:pass192.168.1.64:554/stream1 -t 10能通说明源没问题不通说明源端断流或者网络不通。第二步确认SRS内部流状态用HTTP API查看所有流的publish连接情况curl -s http://127.0.0.1:1985/api/v1/streams/ | jq .streams[] | {name, vhost, publish}对比配置的流名找出缺失的项目。第三步确认转发目标端接收情况SRS日志会记录所有转发动作和错误信息。查看日志tail -f /data/srs/logs/srs.log | grep forward如果有转发失败的记录日志里会直接显示目标地址和失败原因这一步能省去很多猜测。这三步走完问题基本都能定位到具体层级。最高频的问题通常发生在第一层和第三层中间层在架构设计合理的情况下很少出问题。4.4 一些小技巧分享最后分享几个在实际操作中摸索出来的小技巧都是常规文档里不会写的东西。技巧一推流地址带个随机后缀某些平台对同一推流key的重复连接有限制。如果一次断流后立即用同一个key重新连接平台侧短时间内不接收。我在生成推流地址时加了随机后缀这样即使原key被平台屏蔽新key也能正常推流。代价是观众端拉流地址需要同步更新这在直播场景里影响不大因为你不需要观众端频繁切流。技巧二编码器输入源的参考时钟统一多路信号如果要做人声和画面的同步比如主备机位切换需要保证各路信号源的时钟一致。我用的是NTP统一同步摄像头和编码器的时间这样SRS收到的各流时间戳不会出现过大的偏差。这个在小规模多路推流里影响不明显但一旦上量时间戳偏差会导致很诡异的问题比如画面和声音错位。技巧三定期滚动日志SRS和FFmpeg的日志如果不做轮转一个月能吃掉好几个G的磁盘空间。我用logrotate做了每日轮转保留7天。这个不是小事磁盘写满会引发一连串莫名其妙的故障。技巧四配置变更后先灰度再全量我在第22天调整过一路流的GOP参数。没有直接改所有路而是只改了一路观察了24小时确认目标平台侧拉流正常才同步到其他路。多路推流最怕拍脑袋全局改配置一次错误变更的影响范围是所有路不只是其中一路。这个项目跑了一个月让我最深的一个体会是多路推流的稳定性不是某个单点的优化能解决的而是编码参数、传输协议、服务端配置、监控告警、应急兜底这五件事共同作用的结果。如果你也在做类似的多路推流方案先把监控建起来再谈优化。没有监控之前你根本不知道自己哪里掉了链子有了监控之后很多问题还没来得及影响观众就已经被处理掉了。这一个月跑下来我的告警群大概弹了十余次消息大部分是源端短暂断流都自动恢复了。真正的黑屏事故一次都没有发生。如果你对多路推流的某个环节有疑问或者踩到了我没提到的新坑按着上面这套架构去对照排查大概率能找到答案。
返回列表