去年给公司做内部培训直播,我一开始用的是Nginx-RTMP,推流倒是挺稳,但后来要接WebRTC低延迟播放,Nginx那边弄了半天还是不顺,最后换成SRS才彻底解决问题。如果你也正琢磨怎么用Docker快速部署一套SRS,把实时音视频流媒体平台跑起来,这篇内容应该能帮你少走几条弯路。本文会按我的实际操作顺序,从为什么选SRS、部署前怎么规划,到跑通RTMP/HLS/WebRTC、再到生产环境避坑,完整过一遍,适合想自建直播服务的开发者和运维同学参考。
1. 在我把SRS塞进Docker之前,先说清楚它到底解决了什么
1.1 SRS是什么,能处理哪些实时音视频场景
SRS全称Simple Realtime Server,是一个用C++写的开源流媒体服务器。简单说,它就是一个“音视频中转站”:主播端把RTMP流推过来,SRS负责把这份内容转封装成不同协议,再分发给网页播放器、手机播放器、电视端播放器。常见的RTMP、HTTP-FLV、HLS、WebRTC、SRT,它都能处理。
我见过很多人把SRS理解成“又一个Nginx模块”,其实不太准确。Nginx-RTMP是给Nginx加了一个RTMP模块,处理简单直播没问题,但SRS是单独一个服务,从架构设计上就偏向直播场景。它不只是转发,还带了很多直播需要的工程能力:比如GOP缓存,让新观众一进来不用等关键帧;比如DVR录制,直接把直播流切片存成文件;比如HTTP回调鉴权,在推流和播放的关键节点告诉你“这个人能不能播、能不能看”;再比如流状态查询,通过HTTP API可以拿到当前有多少路流、多少人在看。
这些能力在实际项目中太重要了。我之前用Nginx-RTMP做一个小型直播站,第一版看起来也能跑,但越往后面越痛苦:HLS切片配置要自己调、WebRTC几乎没法扩展、想统计在线人数得去翻日志。换到SRS之后,很多功能只需要在配置里打开一个开关。
1.2 和Nginx-RTMP、MediaMTX横向对比,为什么选它
很多人在选型时会纠结,我直接给一份当时对比过的结论:
| 对比项 | SRS | Nginx-RTMP | MediaMTX | 云直播服务 |
|---|---|---|---|---|
| 协议覆盖 | RTMP/HTTP-FLV/HLS/WebRTC/SRT | RTMP为主,HLS需额外配置 | RTSP/WebRTC为主,HLS能力弱 | 协议全,但一般需要SDK |
| 部署复杂度 | 单进程,conf文件集中管理 | 需要改Nginx配置,还要额外编译模块 | 单进程,配置也简单 | 零部署,控制台点选 |
| 直播业务能力 | 鉴权、录制、GOP缓存、API统计齐全 | 偏弱,很多功能要自己写 | 偏弱,适合摄像头拉流/推流 | 强,但定制受限 |
| 资源占用 | 中等,适合长期运行 | 较低,但能力也弱 | 较低,但定位不同 | 不占自己的机器,但按量付费 |
| 上手成本 | 中等,官方文档比较系统 | 会Nginx的人上手快,深坑也多 | 低,串流即用 | 最低,但成本不透明 |
MediaMTX不是不好,它的强项是RTSP和摄像头场景,如果你主要是拿IPC摄像头做本地流媒体,MediaMTX可能更合适。但要做多协议直播分发、低延迟WebRTC、在线录制、推流鉴权这一整套,SRS明显更顺手。云服务则适合短期活动,长期挂在云上跑流量费用很高,而且接口锁定之后想迁回自建会很疼。
1.3 为什么一定要用Docker来跑SRS
SRS本身安装也不算难,官方提供了编译脚本,Linux上一顿操作也能跑起来。但用Docker有几个非常现实的好处:
第一,环境隔离。SRS有自己依赖的库、目录结构和配置路径,装在宿主机上容易跟其他服务互相干扰。我遇到过在服务器上编译SRS时,不小心把系统的openssl版本改了,搞到其他服务起不来。用容器就没有这个问题。
第二,干净卸载和快速回滚。想升级SRS,直接换个镜像tag启动新容器;出了问题,再启动旧tag,十几秒就回滚。宿主机方案想回滚通常得重新编译,非常痛苦。
第三,部署动作高度一致。不管在本地开发机还是生产服务器,只要挂载的配置和数据目录不变,一条docker run命令就能还原一个一模一样的环境。对团队协作来说,这比写一篇几十行的安装文档更可靠。
当然,Docker也不是银弹。后面会提到,SRS的WebRTC功能对网络模式比较敏感,如果直接跑bridge网络,需要额外指定candidate地址。这个细节我们放到部署章节详细说。
2. 动手前先规划好端口、目录和网络模式,省得后面反复拆容器
2.1 端口规划:RTMP、HTTP、WebRTC各走哪扇门
很多人稀里糊涂把端口映射一起写上去,容器也能启动,但后面排查问题的时候才发现自己根本不知道哪个端口是干什么的。SRS默认有几个关键端口,这里列一下:
| 端口 | 协议 | 用途 |
|---|---|---|
| 1935 | TCP | RTMP推流和拉流 |
| 8080 | TCP | HTTP服务,提供HLS切片、HTTP-FLV、静态页面 |
| 1985 | TCP | HTTP API,用于查询版本、流状态、发送控制指令 |
| 8000 | UDP | WebRTC媒体传输端口 |
部署前要先把端口规划清楚。如果你是Linux服务器,我建议直接在防火墙上把这些端口放行;如果用了云服务商的安全组,也要在安全组规则里同步放行,不然外部根本连不进来。最容易漏的是UDP 8000端口,很多人只放行了TCP端口,结果WebRTC一直连接失败,还以为是配置写错了。
2.2 目录挂载:把配置、日志和录制文件留在宿主机
容器是一个随时可以删掉重来的东西,所以任何不能丢的数据都要挂载到宿主机。SRS容器里几个关键路径要注意:
- 配置文件:/usr/local/srs/conf/srs.conf
- 日志和临时目录:/usr/local/srs/objs
- 默认HTTP静态目录:/usr/local/srs/objs/nginx/html
我习惯在宿主机建一个 /opt/srs 目录,下面分conf和objs两个子目录。启动容器时这样挂载:
mkdir -p /opt/srs/conf /opt/srs/objs然后把自定义的srs.conf放进去,启动时分别挂载到容器对应路径。这样每次更新容器,配置和数据都还在,不会因为容器删除就一起没了。HLS切片和后续要做的DVR录制文件也会写在这些挂载目录里,方便定期备份。
2.3 网络模式:bridge和host怎么选
这是最容易踩坑的地方。SRS的RTC功能会向客户端返回一个candidate地址,用来建立WebRTC连接。如果你用Docker默认的bridge网络启动容器,容器看到的IP是172.x.x.x,SRS就会把这个内网IP告诉客户端,客户端拿不到这个IP,WebRTC就握手失败。
有两种解决办法:
第一种,Linux服务器上优先用host网络模式。直接复用宿主机网络,SRS拿到的IP就是宿主机IP,不用额外设置candidate。配置也简单,docker run时加--network host即可。
第二种,必须用bridge网络的时候,在srs.conf里指定candidate为宿主机IP,再把UDP 8000端口映射出来。具体配置后面会给出。
Windows和macOS上的Docker Desktop对host网络支持有限,一般还是要用bridge模式,然后把candidate设置成宿主机局域网IP。
2.4 时区、环境变量和资源限制也不能漏
容器默认时区是UTC,如果SRS要录制视频并按时间命名,录出来的文件名会比北京时间晚8小时。我建议在docker run时加-e TZ=Asia/Shanghai,或者在宿主机直接挂载/etc/localtime。
资源限制也建议提前设置。SRS本身不算吃资源,但如果你拿它跑视频流并发,内存和CPU还是会被打满。我一般会加--memory=512m这种限制,防止某一路流异常时拖垮整个宿主机。后面“生产环境避坑”会再细说。
3. 用一条docker run跑通SRS,完成第一次推流与播放
3.1 拉取官方镜像并选择合适的版本tag
Docker Hub上有SRS官方镜像,仓库名是ossrs/srs。版本方面,我建议直接使用ossrs/srs:5,这是目前生产环境最稳定的主版本。也有人用latest,但latest会跟着新版本走,哪天官方发了一个大版本,你毫无准备地重新部署时就可能被升级到不兼容的新版,所以固定tag更稳妥。
docker pull ossrs/srs:5镜像本身不大,如果拉取比较慢,可以给Docker配置镜像加速器。这一步不做详细展开,属于Docker基础配置。
3.2 快速启动一个SRS容器
先用最简单的方式跑通流程。下面命令使用bridge网络,把RTMP、HTTP和HTTP API端口都映射出来:
docker run -d --name srs \ -p 1935:1935 \ -p 8080:8080 \ -p 1985:1985 \ ossrs/srs:5启动后,先在浏览器打开http://localhost:8080/,如果看到SRS的默认页面,说明HTTP服务正常。再执行:
curl http://localhost:1985/api/v1/versions如果返回类似:
{"code":0,"data":{"major":5,"minor":0,"revision":0}}就说明HTTP API也正常了。这一步做完,SRS已经是一个可以接收RTMP推流的服务器了。
3.3 用FFmpeg推一条流,再用三种协议拉流验证
我本地准备了一个test.mp4文件,用FFmpeg转封装成RTMP流推给SRS。注意FFmpeg要支持libx264和aac,标准版本一般都有。
ffmpeg -re -stream_loop -1 -i test.mp4 \ -vcodec libx264 -acodec aac \ -f flv rtmp://localhost/live/test推上去之后,可以用另外几个窗口分别验证拉流:
- RTMP播放:
ffplay rtmp://localhost/live/test - HTTP-FLV播放:在浏览器里访问
http://localhost:8080/live/test.flv - HLS播放:如果是默认配置且开启了HLS,访问
http://localhost:8080/live/test.m3u8
我实际测下来,FFmpeg推流之后大概2到3秒,HLS播放器就能开始播放。HTTP-FLV的延迟比HLS更短,适合PC端网页播放。如果你用的是OBS,推流服务器填rtmp://localhost/live,串流密钥填test,同样能推上来。
3.4 快速体验时最容易忽略的细节
这里有一个很关键的点:用默认配置跑通RTMP和HLS很容易,但如果想验证WebRTC,你会发现播放不了。原因就是前面说的,SRS默认配置没有开启RTC,或者虽然开了但candidate地址不对。所以快速验证阶段,我们不用强求WebRTC,优先确认RTMP、HTTP-FLV、HLS这三条链路能通就行。把这套链路跑通了,再进入后面的自定义配置阶段。
4. 自定义srs.conf:开启WebRTC、鉴权和一路多协议分发
4.1 一个可以直接用的srs.conf配置模板
当我们想真正用于生产或低延迟直播时,就不能光靠默认配置了。我习惯在/opt/srs/conf/srs.conf里放下面这份配置,按我的注释理解即可:
listen 1935; max_connections 1000; daemon off; srs_log_tank console; http_api { enabled on; listen 1985; } http_server { enabled on; listen 8080; dir ./objs/nginx/html; } rtc_server { enabled on; listen 8000; candidate $CANDIDATE; } rtc { enabled on; } vhost __defaultVhost__ { hls { enabled on; hls_path ./objs/nginx/html; hls_fragment 2; hls_window 10; } http_remux { enabled on; mount [vhost]/[app]/[stream].flv; } http_hooks { enabled on; on_publish http://host:8080/api/auth_publish; on_play http://host:8080/api/auth_play; } }这里面有几个设置要特别说明:
daemon off和srs_log_tank console是容器运行的标配,让SRS在前台运行,日志输出到标准输出,这样docker logs srs才能看到日志。rtc_server.candidate我写成了$CANDIDATE,这是为了支持Docker启动时传入环境变量。如果你不想用环境变量,可以直接改成你的公网或局域网IP,比如candidate 203.0.113.10;。注意要填客户端能访问到的IP。http_remux负责把RTMP流转成HTTP-FLV。hls_fragment设置切片时长,这里设成2秒,HLS延迟会低一些。http_hooks是鉴权回调,后面单独讲。
4.2 基于自定义配置重新启动容器
先把配置文件挂载进容器,同时通过环境变量传入candidate地址:
docker run -d --name srs \ -p 1935:1935 \ -p 8080:8080 \ -p 1985:1985 \ -p 8000:8000/udp \ -e CANDIDATE=你的宿主机IP \ -v /opt/srs/conf/srs.conf:/usr/local/srs/conf/srs.conf:ro \ -v /opt/srs/objs:/usr/local/srs/objs \ ossrs/srs:5启动后查看日志:
docker logs srs --tail 50看到类似rtmp listen at port 1935、http api listen at 1985、rtc listen at 8000的日志,说明各个模块都正常起来了。
如果你在Linux上用host网络,上面命令里的-p端口映射就不需要了,直接:
docker run -d --name srs \ --network host \ -e CANDIDATE=你的宿主机IP \ -v /opt/srs/conf/srs.conf:/usr/local/srs/conf/srs.conf:ro \ -v /opt/srs/objs:/usr/local/srs/objs \ ossrs/srs:5host模式下容器直接监听宿主机的1935、8080、1985、8000,所以要确认这些端口没有被其他服务占用。
4.3 WebRTC的低延迟播放验证
启动好后,再用FFmpeg推一条RTMP流。然后找一个支持WebRTC的播放器页面,现在很多开源播放器都支持,比如SRS官方提供的演示播放页,把流地址填进去,协议选择WebRTC。如果一切正常,播放延迟可以做到500毫秒以内,肉眼可以看到画面几乎是实时的。
这个环节最容易出的问题是:WebRTC播放时一直在“connecting”或者直接失败。我排查过很多次,90%的情况是UDP 8000端口没有放行,或者candidate配置成容器内网IP。所以你检查的顺序应该是:先确认容器有没有监听UDP 8000,再确认防火墙和安全组放行了UDP 8000,最后确认candidate是不是宿主机IP。
4.4 用HTTP回调实现推流和播放鉴权
SRS自己不带登录系统,但提供了完整的HTTP回调机制。上面配置里已经写了http_hooks,当有客户端推流时会请求on_publish接口,当有客户端开始播放时会请求on_play接口。你的后端收到请求后,可以校验推流URL里的密钥、token、用户身份等,返回0表示允许,返回非0表示拒绝。
回调请求里会带上app、stream、param等字段,你可以从param里拿到类似?token=xxxx的信息做鉴权。这样设计的好处是:鉴权逻辑完全由你控制,SRS只做转发。想接内部统一登录系统也可以,在回调接口里调一下单点登录服务就行。
我觉得这是自建流媒体最应该加的一层保护。如果不做任何鉴权,你的服务器就变成公网直播服务器,任何人都可以推流,很容易被滥用。
5. 生产环境部署中的那些坑,我替你踩了一遍
5.1 Docker Desktop虚拟化报错:先检查BIOS和WSL2
在Windows上使用Docker Desktop,很多人第一次启动就会遇到“virtualization support not detected”或者“Docker Desktop failed to start because virtualisation support is disabled”的报错。这通常不是Docker的问题,而是系统的虚拟化没有打开。
排查步骤是这样的:
- 重启电脑,进入BIOS/UEFI设置,找到Intel VT-x或AMD-V选项,把它设为Enabled。
- 在Windows功能里确认“虚拟机平台”和“适用于Linux的Windows子系统”已经勾选。
- 以管理员身份打开PowerShell,执行:
bcdedit /set hypervisorlaunchtype auto - 重启电脑,再启动Docker Desktop。
补充一个容易被忽略的点:如果CPU太老,或者用的是某些轻量云主机,本身不支持嵌套虚拟化,那Docker Desktop很难跑起来。这时候更建议直接在Linux服务器上部署,别在Windows上死磕。
5.2 端口映射和防火墙的“看起来通,实际不通”
我在本地Docker Desktop里能播放,但部署到云服务器上就播放不了,这种情况十有八九是防火墙和安全组。检查时一定按三层来排查:
第一层:容器本身是否在监听端口。在宿主机执行:
ss -lntup | grep -E '1935|8080|1985|8000'确认SRS在监听这些端口。
第二层:宿主机防火墙是否放行。如果用了firewalld:
firewall-cmd --permanent --add-port=1935/tcp firewall-cmd --permanent --add-port=8080/tcp firewall-cmd --permanent --add-port=1985/tcp firewall-cmd --permanent --add-port=8000/udp firewall-cmd --reload如果是ufw,则用ufw allow 8080/tcp这类命令。
第三层:云厂商安全组是否放行。阿里云、腾讯云、AWS都有自己的安全组概念,安全组规则没有放行的话,直接在公网无法访问。很多人全压在本地防火墙,最后发现是安全组的问题。
5.3 容器日志水位和内存限制
SRS在推流量大的时候会产生不少日志,如果开了console输出,docker logs会一直累加。Docker默认的日志驱动是json-file,如果不加限制,日志文件能涨到好几个GB,把磁盘打满。
我建议在docker run时加上日志轮转参数:
--log-opt max-size=10m --log-opt max-file=3这样单个日志文件最多10MB,保留3份。内存方面,SRS每路流通常吃不了太多内存,但为了防住突发流量,我一般设置:
--memory=512m --memory-swap=1g如果以后并发规模上来了,再根据监控数据调大。
5.4 升级容器时不要直接删掉数据目录
Docker部署SRS之后,升级是很轻松,但有个地方要小心:挂载目录里的HLS切片和历史录制文件,一定不要随手删掉。我升级时会先备份一下/opt/srs/objs目录,再停容器、换镜像、起新容器。
如果真的担心新版本配置不兼容,可以先在另一台机器或同一台机器的另一个容器里用新镜像挂载新的端口跑一遍,确认没问题之后再切生产。对于SRS这种基础服务,谨慎永远不会多余。
6. 从“能跑”到“好用”:延迟、跨域、并发与实测心得
6.1 不同协议到底能压到多少延迟
很多朋友问:SRS的延迟到底怎么样?这个问题其实要分协议说,因为底层机制就决定了上限:
- WebRTC:端到端延迟通常在200到500毫秒,适合互动连麦、视频通话这种低延迟场景。
- HTTP-FLV:延迟在1到3秒,适合PC端直播,兼容性比WebRTC好。
- RTMP:延迟和HTTP-FLV差不多,但RTMP播放器越来越少了。
- HLS:延迟取决于切片长度,我配置里设了2秒切片,实际端到端大概4到8秒。适合对延迟不敏感的大规模观看场景。
如果你想要低延迟,优先让客户端走WebRTC或HTTP-FLV。HLS不要作为低延迟方案来用,它在移动端兼容性最好,但延迟就是物理特性。
6.2 播放器跨域问题:CORS不配置,页面里一片黑
浏览器播放HTTP-FLV或HLS时,如果播放器页面所在域名和流媒体服务域名不一致,浏览器会触发跨域限制。具体表现是:直接访问流地址能播放,但嵌在网页里就黑屏或者报CORS错误。
SRS本身作为纯流媒体服务,不一定内置复杂的CORS策略。我自己习惯在SRS前面再加一层Nginx反代,由Nginx统一处理CORS请求头和HTTPS证书。配置大概这样:
location / { add_header Access-Control-Allow-Origin *; proxy_pass http://127.0.0.1:8080; }如果你不想引入Nginx,也可以在后端业务服务里设置允许跨域。总之这个问题别忽略,否则上线之后播放器会被CORS卡住。
6.3 并发能力不要拍脑袋,用工具压一压
SRS单机并发能力其实不弱,但具体能支持多少路同时观看,取决于网络带宽、CPU、是否转码、是否录制、协议类型。我见过有人拿8核16G服务器跑SRS,只做转封装不转码,支撑了几千路HLS同时观看;也见过有人在弱机上跑WebRTC,几百路就卡得不行。
所以建议上线前做一次简单压测。SRS官方提供了srs_bench压测工具,可以模拟大量拉流客户端。我当时的做法是用FFmpeg推一路源流,再用srs_bench并发拉流,逐步增加连接数,同时观察CPU、内存和带宽。压测结果会让你对自己这台机器的真实能力心里有底。
6.4 我最后留下的运维习惯
运行一段时间后,我形成了一个固定运维习惯:每次部署都用固定的镜像tag,比如5.0;配置目录里所有CANDIDATE、服务地址、密钥都环境变量化,方便不同环境复用同一份srs.conf;每天对/opt/srs/objs做一次增量备份;每次改动配置后先docker exec -it srs ./objs/srs -t测一遍配置语法,再重启容器。
这些看起来是小事,但生产环境出问题时,能帮你快速定位到底是配置、网络还是系统问题。