FFmpeg推流器实战:从RTMP到SRT,解耦延迟与systemd托管
前阵子接手一个车间监控上云的项目甲方给了一台跑Debian的工控机要求把分布在三条产线上的摄像头画面统一推到机房自建的SRS服务器再转给远程大屏。刚开始想用OBS结果那台机器没有显示器也没有图形环境折腾半天连headless模式都配不明白。后来换成FFmpeg推流器一条命令把采集、编码、封装、传输全包圆了跑了三个月没出过大乱子。本期Linux系列就来聊聊我怎么用FFmpeg做推流器以及在这个过程里踩过的协议、延迟、守护进程相关的坑。这篇内容不是给刚接触Linux的小白看的功能罗列而是面向那些已经在搭直播链路、准备自建SRS/RTMP服务、或者正在被“推流延迟”折磨的读者。我会从推流器选型聊起拆解一条推流命令的每个参数对比RTMP和SRT两条链路的实际差异最后把systemd托管、鉴权、多路并发这些工程化问题一起讲清楚。1. 推流器选型生产环境为什么绕不开FFmpeg1.1 推流器到底在解决什么问题从业务视角看推流器干的是“源头到平台”这一段的活。采集端可能是USB摄像头、网络摄像机的RTSP流、本地视频文件也可能是采集卡的SDI/HDMI信号平台端是SRS、Nginx-RTMP、云直播服务或者自研的流媒体网关。这段链路里推流器要同时完成三件事把各种输入源变成统一格式、把码率压到带宽能扛住、把数据包按协议要求送到服务器。如果输入端已经是H.264/AAC封装好的RTSP流理论上可以“转封装不转码”直接透传过去。但现实里设备端给过来的往往是裸流、MJPG或者高码率的原始视频这时候就需要一套完整的转码工具。FFmpeg强就强在把这几个环节集成在一个二进制里而且协议支持面极宽RTMP、SRT、RTSP、HLS基本全覆盖自建流媒体服务会遇到的场景它都能接。1.2 为什么不是GStreamer也不是OBS做方案对比的时候我认真试过GStreamer和OBS。GStreamer的管线表达能力确实强gst-launch-1.0可以拼出各种复杂链路适合自研组件。但它有两个现实问题一是协议插件要单独装SRT、RTMP这类插件在不同发行版里的维护质量参差不齐二是后续维护的人不一定熟悉GStreamer的语法团队交接成本高。FFmpeg的命令语法学一次能用很多年运维同学至少都见过它。OBS的问题更直接——它是为“有人坐在屏幕前操作”设计的。服务器上的无头模式虽然能跑但配置流程绕而且默认偏桌面采集场景做摄像头RTSP上行、定制转码这类事反而不顺手。商业推流SDK主要面向移动端App内嵌服务器端很少用。最后权衡下来FFmpeg就是那个“单文件、全协议、好维护”的答案。这里放一张当时选型时做的对比表方案部署方式协议支持转码能力无头服务器适配维护成本FFmpeg单静态二进制非常全RTMP/SRT/RTSP/HLS强强低GStreamer插件化安装取决于插件强强中高OBS headless带图形库主流RTMP/SRT中一般中商业推流SDK集成到业务程序看SDK通常只做封装不支持高1.3 装机版本的坑apt源里的FFmpeg未必够用这是个每次都要踩的坑。Debian/Ubuntu用apt install ffmpeg装出来的版本有时候会滞后上游一大截而且编译选项未必包含libsrt、libx264这些关键组件。想确认当前实例支持什么用ffmpeg -buildconf看编译参数用ffmpeg -protocols | grep -E rtmp|srt看协议支持。如果发现不支持SRT建议直接去FFmpeg官方下载静态构建版或者用GitHub上维护得比较好的自动构建仓库放到/usr/local/bin/下面不污染系统依赖。一个经验下载静态构建后别急着跑先chmod x再ffmpeg -version验证。ARM设备尤其注意选对架构版本x86_64和aarch64的二进制不能混用选错了启动时直接报Exec format error连FFmpeg的版本信息都打不出来。2. 一条推流命令的完整链路从输入源到服务器2.1 五段式拆解为什么每个参数都有存在的理由拿一条最典型的文件循环推流命令来说ffmpeg -re -stream_loop -1 -i /data/loop.mp4 \ -c:v libx264 -preset veryfast -tune zerolatency \ -b:v 3500k -maxrate 4200k -bufsize 8400k \ -g 60 -keyint_min 60 -sc_threshold 0 \ -c:a aac -b:a 128k -ar 44100 \ -f flv rtmp://10.0.0.5/live/camera-01这条命令可以拆成五个环节来理解输入控制、视频编码、码率控制、GOP控制、封装协议输出。-re是推流能否“看起来像实时”的关键。它让FFmpeg按输入文件的原始帧率去读取每秒只读对应帧数。不加它文件会被机器以最快速度推完服务器接收到的是一段加速视频直播直接变录播快进。-stream_loop -1表示无限循环文件适合7x24循环播放片源的场景。注意这两个选项都必须在-i前面它们属于输入选项写成-i file -re的话-re会被当成输出选项行为完全不对。2.2 编码参数直播场景的取舍和录播转码完全不同视频编码这块libx264配合-preset veryfast是在画质、CPU占用、延迟之间取的平衡点。x264预设从ultrafast到placebo越靠后压缩率越高但CPU消耗越大。直播场景我一般不用比medium更慢的预设因为转码速度跟不上实时节奏veryfast是可用的起点CPU吃紧就上ultrafast画质有硬指标就换回fast或medium。-tune zerolatency容易被忽略但它直接影响延迟。这个选项让编码器尽量避免在内部缓存多个帧来提高压缩率代价是码率波动会稍微大一点但推流延迟能明显降下来。这里要提醒一句zerolatency只适合实时传输场景不要用在录播文件转码上否则码率控制会变得低效。码率控制我习惯用-b:v 3500k -maxrate 4200k -bufsize 8400k这组组合。3500k是目标平均码率4200k是允许的瞬时峰值bufsize是码率控制的缓冲窗口一般取峰值的2倍。这样画面运动剧烈的时候编码器可以短时间冲到4200k但不会因为突发码率太大把上行带宽打满。这个组合在1080p30下是合理的如果实测只有2Mbps上行建议先降到720p、目标码率压到2000k而不是硬顶1080p然后一路丢帧。GOP控制是直播延迟里最隐蔽的一个坑。-g 60 -keyint_min 60 -sc_threshold 0表示每60帧一个关键帧30fps下就是2秒一个GOP。为什么不把GOP设小一点因为播放器通常在收到关键帧后才能开始解码关键帧越稀疏客户端等待时间越长。但GOP也不能太小太小会导致码率暴涨。直播场景一般1到2秒的关键帧间隔都是可接受的。sc_threshold 0的作用是禁用场景切换自动插入关键帧的逻辑让GOP严格按设定值走。不加这个参数的话画面剧烈变化时编码器会“自作主张”插关键帧GOP间隔变得不可控后面排查延迟时参数就乱了。2.3 摄像头采集与测试信号源设备推流的两种常用姿势如果是Linux服务器直接接USB摄像头ffmpeg -f v4l2 -framerate 25 -video_size 1280x720 -i /dev/video0 \ -f alsa -sample_rate 44100 -channels 2 -i default \ -c:v libx264 -preset veryfast -tune zerolatency -b:v 2500k \ -c:a aac -b:a 128k \ -f flv rtmp://10.0.0.5/live/camera-01-f v4l2指定视频采集设备-framerate和-video_size是在驱动层面约束输入规格比推完之后再缩放省CPU。音频用-f alsa抓默认声卡。如果没有实际摄像头先用FFmpeg自带的测试源把链路打通ffmpeg -re -f lavfi -i testsrc2size1280x720:rate25 \ -f lavfi -i sinefrequency440:sample_rate44100 \ -c:v libx264 -preset ultrafast -c:a aac \ -f flv rtmp://127.0.0.1/live/testtestsrc2是带移动画面的测试图sine生成单音音频两者组合能完整测通视频和音频两条链路排障时非常好用。我一般会先在本地起一个SRS或者Nginx-RTMP用这条命令推本地再用播放器去拉流验证确认无误后再换真实输入源。这样能把“源的问题”和“推流的问题”分开不会混在一起。3. RTMP与SRT双链路实测不同场景下的参数差异3.1 协议选择RTMP是兜底SRT是弱网链路的解药推流协议没有万能的。RTMP基于TCP1935端口在绝大多数防火墙、CDN、云直播平台上都是开着的服务器生态最成熟SRS、Nginx-RTMP、各类云直播服务全都支持。但TCP的短板也明显一旦公网出现抖动丢包TCP会重传重传累积起来就是延迟飙升甚至断流。丢包率超过一定阈值之后RTMP连接基本没法稳定工作。SRT走的是UDP之上的UDT协议自带ARQ重传和丢包恢复机制弱网环境下比RTMP稳得多。它的另一个优势是端到端延迟可控RTT在100ms以内的链路端到端做到0.5秒以内有戏。但缺陷也实在UDP端口在部分企业网络会被策略拦截而且双方需要提前协商好听流端口和密码。这里放一张我实测中随身带的对比表维度RTMPSRT传输层TCPUDPUDT默认端口19359000起自定义防火墙友好度高一般UDP易被限制典型端到端延迟1-3秒0.1-1秒丢包表现抖动大、易断重传补偿相对稳定适用场景云平台、内网、生态成熟公网弱网、带宽不稳链路3.2 两条推流命令的实际写法差异RTMP推流的完整命令在第二章节已经给过这里重点说SRT。SRT推流在FFmpeg里改动不大关键点是封装格式改成mpegtsURL走srt://协议ffmpeg -re -i /data/loop.mp4 \ -c:v libx264 -preset veryfast -tune zerolatency \ -b:v 3500k -maxrate 4200k -bufsize 8400k \ -c:a aac -b:a 128k \ -f mpegts srt://10.0.0.5:9000?modecallerlatency200000passphrasemysrtkeyURL里的查询参数是关键。modecaller表示本端主动发起连接latency是SRT接收端的缓冲时间单位是微秒200000就是200mspassphrase是加密口令服务端也要配置同样的口令。这几个参数固定下来之后基本不会出现握手失败的幺蛾子。和RTMP相比SRT的URL更像一份“带配置的合同”两端必须对齐才能正常工作。3.3 一次公网测试的真实记录我做过一次对比测试从一台公网服务器向办公室机房推同一路1080p流链路RTT约80ms人为在中间路由上注入3%的随机丢包。RTMP那条在注入丢包后约5秒出现明显卡顿和延迟增长拉流端画面开始跳帧最后掉线重连SRT那条全程画面稳定端到端延迟从450ms增加到700ms左右但始终没断流。这个结果基本符合预期内网或者专线链路质量稳定RTMP完全够用还能吃到各种现成的云端能力链路远、公网质量不可控的场合SRT是值得优先选择的方案。我现在接公网项目都会先问对方一句出口带宽质量怎么样如果答案是“说不清”协议层就直接定SRT。4. “推流到SRS存在延迟”的排查链路4.1 延迟不是推流器单方面的事链路五段都要查“ffmpeg推流到srs存在延迟”这件事几乎是每个做直播的人都撞过的墙。很多人一上来就怀疑FFmpeg参数不对其实延迟的累积点至少有五个采集延迟、编码缓冲、传输缓冲、服务器缓存、播放器缓冲。FFmpeg推流端能控制的是前三个SRS侧可调的是服务器缓存和播放行为播放器端还有一个独立的缓冲。所以排查延迟的正确姿势是分段定位而不是盲调推流参数。4.2 逐段打点验证把自己变成网络侦探第一步先确认输入源本身是否有延迟。网络摄像机的话可以用FFmpeg直接拉流到本地播放窗口对比画面时间和系统时间文件循环推流不存在这个问题。第二步做本地回环测试在同一台机器上起SRS然后用下面的命令把流拉回来只看解码时间戳把公网因素全部排除ffmpeg -i rtmp://127.0.0.1/live/test -f null --f null -会丢弃所有音视频数据只统计解封装和解码情况异常时终端会打印错误帧信息。这条命令是判断“服务器输出有没有问题”的快速工具本地回环延迟都压不下来那问题大概率在推流端或SRS配置本地回环正常再往播放器方向查。第三步才是看SRS的日志和播放器行为。SRS的控制台能看到当前连接数、推流码率、播放协议类型结合这些信息定位具体卡在哪一段。4.3 SRS侧两个最容易背锅的配置gop_cache与play_bufferSRS的延迟问题十次有八次出在服务器侧的两个缓存配置上。第一个是gop_cache它开启后服务器会缓存最近一个完整的GOP新播放器一进来立刻能拉到画面体验很顺滑。代价是——如果前端GOP是5秒播放器就可能要多等5秒才出画面延迟直接拉满。第二个是play_buffer它控制播放器连接时向服务器要多少缓冲数据设置过大同样会明显增加首帧等待时间。在SRS配置文件的vhost里可以这样显式控制vhost __defaultVhost__ { gop_cache off; play_buffer 300; }gop_cache off之后播放器连接不会等待完整缓存延迟明显下降代价是播放器接入的瞬间可能要多等一会儿才出画面。play_buffer 300表示播放缓冲控制在300ms量级。这两个参数配合前端GOP从5秒缩到2秒是我常用的低延迟组合。这里要特别提醒SRS针对不同协议的行为不一样。RTMP推流进来RTMP播放和HTTP-FLV播放的延迟表现会不同而且延迟优化配置往往要配套播放器设置才能生效。很多播放器默认缓冲好几秒服务器端怎么调都白搭。4.4 一个真实的8秒延迟案例复盘去年有个线上直播项目找到我症状是画面比现实慢8秒。生产环境里用的是SRS默认状态推流端是某厂家编码器GOP固定5秒。排查链路是这样的先用本地回环推流测试发现本地推流拉流延迟不到2秒证明FFmpeg推流端没问题再检查SRS配置发现gop_cache处于开启状态最后看播放器首帧时间确认播放器默认缓冲4秒。三个环节各自贡献了一部分GOP 5秒、播放器缓冲4秒加上传输和服务器缓存叠出8秒。把编码器GOP改为2秒、SRS关闭gop_cache、播放器切换极速模式后端到端延迟降到1.5秒以内。整个过程没有玄学调参就是把链路逐段验证后把每一段的缓存都降下来。5. 把推流器变成常驻服务systemd托管与断线自愈5.1 nohup 不够用的原因很多人第一次做推流服务习惯用nohup ffmpeg ... 把进程丢后台这样终端退出后进程确实还能跑。但问题来了进程意外崩溃、被OOM killer干掉、断网后退出谁来把它拉起来手动重启一次两次可以7x24的推流任务不可能靠人肉值班。Linux上正确的做法是用systemd把FFmpeg管起来——它就是监督者崩溃自动拉起、开机自动启动、日志统一收集一次全搞定。这也是很多人问“怎么让后台运行指令不因界面退出而退出”的标准答案。5.2 一个可以直接抄的unit文件在/etc/systemd/system/ffmpeg-push.service里写[Unit] DescriptionFFmpeg RTMP Pusher Afternetwork-online.target Wantsnetwork-online.target [Service] Userpusher Grouppusher Typesimple ExecStart/usr/local/bin/ffmpeg -re -stream_loop -1 -i /data/loop.mp4 -c:v libx264 -preset veryfast -tune zerolatency -b:v 3500k -maxrate 4200k -bufsize 8400k -c:a aac -b:a 128k -f flv rtmp://10.0.0.5/live/camera-01 Restartalways RestartSec5 StartLimitIntervalSec0 [Install] WantedBymulti-user.target几个关键点逐个说。Userpusher建议新建一个专用低权限用户不要让FFmpeg以root运行视频文件和目录要给这个用户读权限。Restartalways表示无论什么原因退出都重启配合RestartSec5给系统5秒缓冲。StartLimitIntervalSec0很重要它关掉了systemd默认的“5次失败后放弃”限制不然连续重启失败后服务会进入failed状态。Afternetwork-online.target配合Wantsnetwork-online.target确保网络就绪后再启动推流避免开机时网络还没起来导致首推失败。写完文件后执行sudo systemctl daemon-reload sudo systemctl enable --now ffmpeg-push sudo systemctl status ffmpeg-push journalctl -u ffmpeg-push -f5.3 断线重推与日志观察的日常如果推流链路会断比如公网拨号、Wi-Fi桥接这种不稳定环境光靠Restartalways还不够因为网络恢复需要时间FFmpeg立刻重启可能又推不上。我的做法是把启动命令包一层脚本先ping服务器或者检测端口网络通了再执行真正的ffmpeg命令不通就sleep几秒再试。这样systemd负责“进程死了拉起来”脚本负责“网络没就绪别急着跑”两边分工明确。日志观察就靠journalctl。journalctl -u ffmpeg-push -f实时跟踪推流正常时终端只有一条Press [q] to stop的提示看到大量Server error或Connection refused时基本可以断言是服务器端或网络侧出了问题。另外建议在命令里加上-loglevel error日常信息一律不打印只在真出错时才输出日志推送日志会清爽很多。6. 推流端的工程化进阶鉴权、多路并发与ARM设备部署6.1 防止无关人员往你服务器上乱推鉴权三板斧推流器上线后第一个安全问题就是“盗推”——任何人都能拿着推流地址往你的服务器塞视频。SRS最常见的做法是配置on_publish回调推流端带着token参数服务器在回调里校验token不合法就拒绝连接。FFmpeg推流时在RTMP地址尾巴上加参数即可ffmpeg -re -i /data/loop.mp4 -c:v libx264 -preset veryfast -c:a aac -f flv rtmp://10.0.0.5/live/camera-01?tokenxxxSRS侧在http_hooks配置里指定回调地址回调返回非200就拒绝推流。SRT相对省心一点URL里直接带passphrase加密口令口令选足够长的随机串别用123456这种。RTMP和SRT双协议的好处在这里体现得很明显内网设备走RTMP配合URL token公网设备走SRT配合passphrase按场景各取所需。6.2 多路并发先算CPU和带宽再谈优化如果一台机器要同时推多路流工程上有个顺序问题先估算资源再定每路的参数最后才谈优化。以1080p30、libx264 veryfast、3500k码率为基准现代8核Xeon大约能支撑3到5路软件编码具体要看CPU型号和频率。带宽直接按总和估算5路就是5 x 3500k约17.5Mbps上行带宽至少要留20%余量。资源吃紧时优先做两件事把分辨率降到720p、preset调到ultrafast。需要更多路的话就该考虑硬件编码卡或者GPU了。系统层面多路FFmpeg建议用taskset -c 0,1 ffmpeg ...把不同实例绑定到固定CPU核心避免进程在核间漂移。每路一个独立的systemd service命名成ffmpeg-push-camera-01、ffmpeg-push-camera-02排查问题的时候一眼能分清是哪一路在搞事。6.3 嵌入式与ARM场景硬件编码器才是主角最后要说的是越来越常见的场景树莓派、RK3568这类ARM板子做边缘推流。软件x264在ARM上性能有限720p30推到ultrafast都很吃力这时候必须动用硬件编码器。FFmpeg在常见ARM SoC上可用h264_v4l2m2m走V4L2的M2M接口Intel平台是h264_vaapiNVIDIA平台是h264_nvenc。用ffmpeg -encoders | grep 264就能看到当前FFmpeg支持哪些编码器。ARM设备部署优先选静态构建的aarch64版本二进制直接解压到板子上跑不需要在板子上折腾编译依赖。配置上注意部分硬件编码器不支持-tune zerolatency这种x264专属参数GOP、码率这些需要改用-g、-b:v等通用参数控制。硬编本身的延迟通常比x264软件编码低但GOP固定和码率波动控制不如软件编码器灵活踩坑时先查硬编的具体参数说明。我这台推流器在客户机房跑了三个月systemd托管之后基本没再管过它。中途遇到过一次网线松动FFmpeg退出后systemd五秒拉起等网络恢复重推成功全程没影响业务。如果你正在搭自己的推流链路我建议按这个顺序来先用第2章的测试源把链路跑通确认协议和延迟满足需求后再上真实输入源最后才加systemd和鉴权。推流这件事单个环节都不难难的是把每个环节“为什么会这样”想清楚才能在出问题时快速定位。这一期就聊到这里SRS回调鉴权的接口细节、多路推流的负载调度后面有机会再展开。