做视频服务的同学应该都听过 SRS(Simple Realtime Server),江湖人称“世界上最简单的流媒体服务器”。我大概在四五年前第一次接触到它,当时为了给一个在线课堂做直播分发,对比过 Nginx-RTMP、ZLMediaKit、MediaMTX 等一堆方案,最后落地的还是 SRS。为什么?因为它在“开箱即用”和“功能强大”之间找到了一个非常舒服的平衡点——既能像玩具一样三分钟跑起来,又能扛住生产环境的并发和复杂协议需求。而这两年再把 SRS 和 Docker 组合起来,部署难度直接降到“一条命令服务就起来”的地步,连编译的功夫都省了。
这篇文章不搞那些花里胡哨的包装,直接把我的实操过程、配置细节、踩坑记录全部摊开。无论你是刚入门想搭一个测试环境,还是准备在生产环境里切一套真正的流媒体服务,照着往下做,都能得到一个能跑、能推流、能播放、能查问题的完整 SRS 实例。废话不多说,先从选型和原理聊起,知道了“为什么”,后面的“怎么做”才有底气。
在开始之前先说一下我的运行环境,后面所有命令和配置我都验证过:一台 Ubuntu 22.04 服务器,内核 5.15,Docker 版本 24.0,Docker Compose v2.20。SRS 镜像用的是官方ossrs/srs:v5.0,这是目前最稳的 5.x 系列版本。
1. 为什么选 SRS + Docker:先搞懂这套组合到底解决了什么
1.1 SRS 的核心能力和适用场景
SRS 是一个用 C++ 写的高度模块化流媒体服务器,它最核心的定位就是“接入流、转发流、分发流”。它天生支持 RTMP 推流,能输出 RTMP、HTTP-FLV、HLS、WebRTC、SRT 等多种拉流协议,所以你在浏览器里看到的低延迟直播,在手机上用微信扫码看的 HLS 视频,在 OBS 里推出去的 RTMP 流,都可以由一个 SRS 实例承担。这种多协议聚合能力,是它在同类开源方案里脱颖而出的关键原因。
从我的实际项目经验来看,SRS 最适合这几类场景:一是低延迟直播,比如在线教育、电商带货、视频会议补充链路,WebRTC 的延迟可以压到 200~500 毫秒;二是移动端兼容性要求高的场景,HLS 是 Apple 生态的亲儿子,几乎所有手机浏览器都能直接播;三是企业内部做视频分发,SRS 的单机性能足够支撑几千路并发观看,而且社区活跃、文档齐全,真出了问题也不愁找不到人讨论。
1.2 为什么用 Docker 而不是直接编译部署
SRS 早期最劝退新手的就是编译安装。它依赖 OpenSSL、FFmpeg、st 库等一堆组件,不同系统上编译还能遇到各种神奇的链接错误。我记得第一次在 CentOS 7 上编译 SRS 4.0,光./configure的参数就研究了半天,中间还因为 GCC 版本太旧导致编译失败。后来虽然官方提供了 release 压缩包,二进制包在 GLIBC 版本不同的机器上又可能跑不起来。
Docker 完美解决了这个痛点。所有依赖、动态库、可执行文件都被封装在镜像里,宿主机只需要有一个 Docker 环境,一条docker run命令就完成了“编译+安装+启动”全过程。而且版本升级特别干净:旧容器直接删掉,新容器拉起来,配置和数据通过挂载目录保留,不会有残留文件污染系统。对于团队协作来说,一个 docker-compose 文件提交到 Git 仓库,新同事克隆下来一键启动,开发环境和生产环境保持一致,这类隐性收益在排障时尤其明显。
1.3 使用 Docker 部署时的注意事项
Docker 不是万能的,用容器跑 SRS 有一些细节必须提前知道。首先是网络模式,SRS 涉及 UDP 端口(WebRTC 使用),Docker 默认的桥接网络是 NAT 模式,需要手动映射 UDP 端口,如果漏掉就会导致 WebRTC 无法推拉流。其次是性能问题,虽然容器本身几乎不损耗性能,但如果宿主机网络层配置不当(比如未开启内核调优参数),高并发下 UDP 消息处理会有丢包风险。
另外,SRS 的配置文件路径在/usr/local/srs/conf/srs.conf,容器使用时一般不会去改镜像内的文件,而是把宿主机上的配置目录挂载进去。这样做的目的是让配置“可编排、可追踪”,不会因为容器删除配置就丢失。初始化镜像时,官方也提供环境变量覆盖一些简单参数,但复杂场景还是建议直接用挂载的配置文件,后面我也会演示具体做法。
2. 部署前的准备:给 SRS 腾好“房子”
2.1 Docker 环境检查与安装
如果你还没有安装 Docker,先花两分钟把基础环境准备好。我推荐使用 Docker 官方安装脚本,或者使用 apt 仓库安装。这里假设你用的是 Debian/Ubuntu 系,先更新软件包索引,然后安装apt-transport-https、ca-certificates等依赖,添加 Docker 官方 GPG 密钥和仓库源,最后安装 docker-ce 和 docker-compose-plugin。装完后用docker version验证客户端与服务端都在正常工作。
我自己踩过的一个坑是:刚装完 Docker 后需要在 root 用户下才能执行 docker 命令,普通用户会报权限不足。解决办法很简单:把当前用户加入 docker 用户组,然后重新登录即可:
sudo usermod -aG docker $USER newgrp docker之后执行docker run hello-world能看到“Hello from Docker!”就说明环境完全 OK。这里额外提醒一句,如果服务器前面还有防火墙(UFW 或者云厂商安全组),要记得提前把 SRS 需要的端口放行,不然后面推流失败会排查得怀疑人生。
2.2 选择什么版本的 SRS 镜像
SRS 官方镜像仓库是 Docker Hub 上的ossrs/srs,最新的稳定大版本是 5.x,也有 4.0 版本在继续维护。我自己在生产环境用的是ossrs/srs:v5.0,因为 SRS 5 改进了 WebRTC over TCP 的支持,而且增强了鉴权和集群功能。如果你追求绝对稳定,也可以用ossrs/srs:v4.0,它是经过多年验证的“老黄牛”,社区案例最多。
有一个细节:SRS 镜像的 tag 分为v5.0、v5.0.xx这类精确版本,以及v5、latest这类浮动版本。我建议线上环境锁死精确版本,比如ossrs/srs:v5.0.142,避免哪天拉取最新版后行为发生变化。开发环境可以用ossrs/srs:v5方便测试。你可以在 Docker Hub 页面查看可用的 tag 列表,选择适合自己时间节点的版本。
2.3 端口规划和目录结构
SRS 默认会监听好几类端口,部署前最好统一规划,特别是出现端口冲突的时候你才知道为什么要这样分配。
| 端口 | 协议 | 用途 |
|---|---|---|
| 1935 | TCP | RTMP 推流/拉流 |
| 1985 | TCP | HTTP API、WebRTC 信令 |
| 8080 | TCP | HTTP 服务、HTTP-FLV、HLS、控制台 |
| 8000 | UDP | WebRTC 媒体面(SRTP) |
| 8001 | UDP | WebRTC 媒体面,备用 |
| 3868 | TCP | SRT 推流(可选) |
| 8085 | TCP | HTTPS 服务(可选) |
目录方面,我会单独创建/opt/srs/作为 SRS 的宿主机目录,下面分conf、logs、www三个子目录。conf 放自定义的srs.conf,logs 挂载给容器里的日志目录,www 用来存放 HLS 切片和网页文件。这样数据与容器生命周期解耦,容器怎么折腾都不会丢数据。
3. 开始部署:两种方式,从快速验证到生产固化
3.1 第一种方式:一分钟拉起默认配置的 SRS
如果你的目标只是“先跑起来看看”,那什么都不用配置,直接执行下面的命令:
docker run --rm -d \ --name srs \ -p 1935:1935 \ -p 1985:1985 \ -p 8080:8080 \ -p 8000:8000/udp \ -p 8001:8001/udp \ ossrs/srs:v5.0这里的参数我解释一下:-d表示后台运行,--rm表示容器停止后自动删除文件系统层,适合测试。-p把宿主机端口映射到容器端口,注意 WebRTC 用的是 UDP,所以必须写成8000:8000/udp这样的格式,漏掉 /udp 或者漏映射端口,后面 WebRTC 保准抓瞎。
启动后可以验证一下容器状态:
docker ps看到srs容器处于 Up 状态后,访问http://服务器IP:8080/,会看到 SRS 自带的欢迎页和测试播放器。此时 SRS 已经具备完整的直播能力:默认配置启用了 RTMP 接入、HTTP-FLV、HLS、WebRTC 分发。你可以直接打开它的演示页面:http://服务器IP:8080/players/srs_player.html,里面会自动生成一个内置的流地址测试播放。
3.2 第二种方式:挂载自定义配置精确控制
默认配置适合体验,但真要接入自己的业务,就必须使用自定义配置。首先创建宿主机目录和配置文件:
mkdir -p /opt/srs/{conf,logs,www}然后编辑/opt/srs/conf/srs.conf,我贴一份精简但带核心功能的配置。注意,SRS 配置格式类似 nginx,每条指令以分号结尾,遇到不认识的指令是会导致启动失败的:
listen 1935; max_connections 1000; daemon off; srs_log_tank console; http_api { enabled on; listen 1985; } http_server { enabled on; listen 8080; dir /usr/local/srs/objs/nginx/html; } rtc_server { enabled on; listen 8000; protocol udp; # 多 IP 时建议显式指定候选 IP candidate $CANDIDATE; } vhost __defaultVhost__ { tcp_nodelay on; min_latency on; play { gop_cache on; queue_length 10; } hls { enabled on; hls_path /usr/local/srs/objs/nginx/html; hls_fragment 2; hls_window 10; } http_remux { enabled on; mount [vhost]/[app]/[stream].flv; } rtc { enabled on; } }这里有几个地方需要解释,都是新手容易踩的坑:
daemon off必须保留,因为在 Docker 容器里进程必须是前台运行的,如果以 daemon 模式启动,容器启动后立刻就会退出。srs_log_tank console让日志输出到标准输出,这样可以用docker logs srs直接查看,非常方便调错。http_server.dir和hls_path默认指向容器内的/usr/local/srs/objs/nginx/html,这个目录是镜像自带的静态文件目录。如果你想把 HLS 切片输出到宿主机目录,这里应该改为容器内的挂载点路径,比如/data/www,然后在docker run时把宿主机/opt/srs/www挂载到/data/www。- WebRTC 的
candidate是一个容易忽视的参数。在云服务器上跑的时候,SRS 需要识别本机的外网 IP,这时可以把它显式配成candidate 服务器公网IP,否则客户端可能拿不到正确的 ICE 候选地址。我在下面的启动命令里用了环境变量CANDIDATE来动态传入。
准备好配置后,用挂载方式启动容器:
docker run --rm -d \ --name srs \ -e CANDIDATE=$(hostname -I | awk '{print $1}') \ -v /opt/srs/conf/srs.conf:/usr/local/srs/conf/srs.conf \ -v /opt/srs/www:/data/www \ -v /opt/srs/logs:/usr/local/srs/objs/logs \ -p 1935:1935 \ -p 1985:1985 \ -p 8080:8080 \ -p 8000:8000/udp \ -p 8001:8001/udp \ ossrs/srs:v5.0这里我把自定义配置挂载到了容器内 SRS 的默认配置文件路径,把宿主机/opt/srs/www挂载到/data/www(但配置里还没改目录,你切换到 HLS 时注意同步)。如果你想正式使用 HLS,需要把上面配置里的http_server.dir和hls_path都改为/data/www。这种“配置外置、日志外置、切片外置”的方式,是我在多次生产事故后沉淀下来的标准做法,升级镜像时只要docker rm旧容器再拉起新容器,所有数据依然保留。
3.3 使用 docker-compose 固化部署
单条 docker run 命令在复制粘贴时容易漏参数,而且团队成员不易对齐,所以我强烈建议你用 docker-compose。在/opt/srs/下创建docker-compose.yml:
version: "3.8" services: srs: image: ossrs/srs:v5.0 container_name: srs restart: unless-stopped environment: - CANDIDATE=${CANDIDATE} ports: - "1935:1935" - "1985:1985" - "8080:8080" - "8000:8000/udp" - "8001:8001/udp" volumes: - /opt/srs/conf/srs.conf:/usr/local/srs/conf/srs.conf - /opt/srs/www:/data/www - /opt/srs/logs:/usr/local/srs/objs/logs然后提前在/opt/srs下创建一个.env文件:
CANDIDATE=你的服务器公网IP最后一句命令就能启动整套服务:
cd /opt/srs && docker compose up -drestart: unless-stopped是生产环境的关键配置,服务器重启后 Docker 会自动把 SRS 拉起来,省去手动干预。日志查看方式也和单容器一致,用docker compose logs -f srs即可。这种管理方式非常符合现代运维规范:你的“部署脚本”就是这一个 yml 文件和一份 conf,可审计、可回滚、可复制。
4. 测试闭环:推流、拉流、WebRTC 全链路验证
4.1 用 FFmpeg 推流到 SRS
服务跑起来后,第一步就是验证推流是否正常。先在服务器上准备一个小视频文件作为测试源,没有现成素材的话可以直接用 FFmpeg 的 testsrc 源生成。安装 FFmpeg:
apt install ffmpeg -y生成一个 5 秒的测试视频(或使用本地准备好的 mp4):
ffmpeg -f lavfi -i testsrc=size=1280x720:rate=30 -f lavfi -i sine=frequency=440:sample_rate=44100 -t 30 -c:v libx264 -preset ultrafast -c:a aac /tmp/test.mp4然后执行推流命令,将视频推送到 SRS 的 RTMP 端口:
ffmpeg -re -i /tmp/test.mp4 -c copy -f flv rtmp://127.0.0.1/live/livestream如果终端没有任何报错,持续滚动音视频帧信息,说明 RTMP 推流成功。这里的/live是应用名,livestream是流名,你可以在拉流地址里自由替换。
我遇到过的常见问题就是推流命令报Connection refused,这基本是端口映射或防火墙问题,等下第 5 节统一排查。如果报Failed to update header或者Broken pipe,则一般是 SRS 那边主动断开了连接,最常见的原因是带宽不足或者推流时间过长超过了max_connections。
4.2 拉流验证:HTTP-FLV 与 HLS
推流成功以后,我们的注意力转到“播放”。SRS 最有价值的地方就是可以同一条流用多种协议拉取。
HTTP-FLV 是浏览器低延迟播放的首选,地址规则是:
http://服务器IP:8080/live/livestream.flv把这条地址直接塞到 VLC 的“打开网络串流”里,或者浏览器中用 flv.js 播放,立刻就能看到画面。HLS 则把 RTMP 流转成 m3u8 切片,地址规则是:
http://服务器IP:8080/live/livestream.m3u8用 VLC 打开也能播放。HLS 是分段传输的,所以会先缓冲几秒,这是 CDN 时代的折中方案,延迟比 FLV 高,但兼容性最好。
验证时我还习惯在 SRS 的 API 里查一下流状态。直接访问 HTTP API:
curl http://127.0.0.1:1985/api/v1/streams返回值是一串 JSON,里面有流名、协议连接数、创建时间等信息。看到里面有"name": "livestream"这样的记录,说明流已经被 SRS 登记在册了。这个 API 在排查“为什么播放器连不上”时非常好使。
4.3 打开浏览器,体验 WebRTC 亚秒级延迟
SRS 5.x 默认启用了 WebRTC 推拉流支持,这也是它区别于传统 RTMP 服务器的最大亮点。我们用浏览器测试一下 WebRTC 拉流:在服务器上打开 SRS 自带的播放器页面。
http://服务器IP:8080/players/rtc_player.html在页面地址栏输入webrtc://服务器IP/live/livestream(注意协议名是 webrtc),点击播放。WebRTC 播放延迟通常在 500ms 以内,画质和流畅度和浏览器原生能力直接挂钩。前提是 8000/8001 的 UDP 端口必须放行,并且我们将 SRS 的 candidate 配置成了正确的公网 IP,否则浏览器和 SRS 之间的 ICE 协商会失败。
如果你想测试 WebRTC 的推流,可以用rtc_publisher.html页面,它会调用浏览器麦克风或者摄像头,并把流推到 SRS。这些年 WebRTC 在直播里越来越重要,很多互动连麦场景都已经从 RTMP 转向 WebRTC,SRS 的这个能力绝对值得上手验证。
4.4 通过 API 检查服务器健康状态
最后再推荐一个实用 API,/api/v1/versions可以查看 SRS 版本和编译选项,/api/v1/summaries可以看当前的连接数、带宽、CPU 占用等统计信息。这两个接口是我写监控脚本时的主力接口,比如定时抓取 summaries 数据,当并发连接数超过阈值就告警。作为运营指标,SRS 比很多商业流媒体服务透明得多。
5. 部署过程中最常见的坑与排查方法
5.1 端口映射或防火墙导致的推流失败
症状:FFmpeg 推到rtmp://服务器IP/live/livestream时提示连接超时或被拒绝。
排查顺序建议是:先在服务器本机执行docker ps确认容器在运行,再在本机执行curl http://127.0.0.1:1985/api/v1/versions确认 API 能通。如果本机通、外网不通,问题大概率出在防火墙或者云安全组。别忘了检查 Docker 端口映射是否完整:
docker port srs这会列出所有已映射端口。我之前就有过一次“只映射了 TCP 端口,忘记映射 8000/udp”的经历,导致 WebRTC 一直连不上,而用 RTMP 推流却完全正常,排查了快一个小时才想到是 UDP 映射缺失。
5.2 播放黑屏或卡顿的排查
如果推流正常,API 也查得到流,但播放黑屏,首先要区分是协议兼容性问题还是转封装问题。比如用 VLC 播 HTTP-FLV 卡顿,可以换个播放方案测试,用浏览器 flv.js 对比,如果浏览器正常,LVC 异常,多半是 VLC 对 FLV 的缓冲设置不够灵活。如果是 HLS 播放延迟很大,可以调整配置里的hls_fragment和hls_window。我一般把 fragment 设为 2 秒,window 设为 10 秒,在延迟和文件数量之间取个平衡。
还有一种情况是 GOP 缓存导致首屏慢。默认gop_cache on,播放器接入时会从关键帧开始缓存,好处是秒开,坏处是延迟增加;如果对延迟极其敏感,可以把gop_cache off,但代价是首屏可能等很久。
5.3 看日志是定位问题最快的方式
SRS 的日志格式很规范,直接查看容器日志:
docker logs -f --tail 200 srs日志里能看到 RTMP 连接建立、流发布、流播放、WebRTC 会话建立等事件。比如以下这行:
[2025-xx-xx 10:00:00.000] [info] RTMP client ip=1.2.3.4:12345 accept, fd=16说明有 RTMP 客户端连进来了。如果日志出现error级别关键字,比如invalid config,那就直接定位到配置文件哪一行写错。SRS 有个特别贴心的设计:配置出错时会打印出具体的指令和行号,不像某些服务直接退出连个屁都不放。
5.4 常见问题速查表
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 容器启动后立刻退出 | 配置里 daemon off 缺失 | 确保daemon off; srs_log_tank console; |
| RTMP 推流超时 | 1935 端口未映射或防火墙未放行 | 检查docker port放行 1935/TCP |
| HTTP-FLV 可以播放,WebRTC 失败 | 8000/8001 UDP 未映射或 candidate 不对 | 放行 UDP 端口、配置公网 IP |
| API 返回 403 | 启用了 HTTP API 鉴权但没配置 token | 检查 http_api 配置或临时关闭鉴权 |
| HLS 播放 404 | hls_path 与 http_server.dir 不一致 | 让两者都指向同一个挂载目录 |
| 播放卡顿,延迟越来越大 | GOP 缓存或带宽不足 | 调低质量、开启 gop_cache,或增加队列长度 |
这张表是我平时排查问题的第一参考,基本上能覆盖 80% 的 SRS 部署难题。剩下那种“重启解决 99% 问题”的玄学情况,docker restart srs也不妨一试。
6. 进阶能力:鉴权、HTTPS 与集群扩展
6.1 用回调接口给 SRS 加上鉴权
很多业务场景下,你不能让任何人都能往你的服务器推流,也不能让任何人都随意拉流。SRS 提供了 HTTP 回调机制,在推流/播放等事件发生时通知你自己的后端服务,由后端决定“放行”还是“拒绝”。
配置一个简单的推流鉴权回调:
vhost __defaultVhost__ { http_hooks { enabled on; on_publish http://你的后端地址/api/auth/publish; on_play http://你的后端地址/api/auth/play; } }你的后端收到 POST 请求后,返回0表示拒绝,返回 HTTP 200 且 body 为整数0以外的值表示允许。更直观的方式是返回 JSON:{"code": 0}拒绝,{"code": 1}通过。这里要特别留意,SRS 的 hook 请求是异步的,它需要等待后端返回,如果后端处理超时,SRS 也会拒绝连接。所以回调接口的超时时间要尽量短。
我建议在 on_publish 里校验推流密钥,类似?token=xxx这样的参数,后端根据 token 判断是否为合法推流端。播放鉴权则可以根据用户 ID 和流名之间的关系判断访问权限。这套方案已经帮我扛住了多个线上活动的防盗链压力,非常可靠。
6.2 给 SRS 配置 HTTPS 和 443 端口
随着浏览器对权限 API 的收紧,获取摄像头、麦克风或者使用 WebRTC 都要求页面必须是 HTTPS 环境。SRS 自带 HTTP Server 可以加载 SSL 证书,从而对外提供 HTTPS 拉流和 WebRTC 访问。
准备证书文件(可以是 Let‘s Encrypt 签发的免费证书),挂载到容器里,然后配置 HTTPS:
http_server { enabled on; listen 8080; dir /usr/local/srs/objs/nginx/html; https { enabled on; listen 8085; key /usr/local/srs/conf/server.key; cert /usr/local/srs/conf/server.crt; } }启动时多映射一个 8085 端口。浏览器访问https://服务器IP:8085/players/rtc_player.html,只要证书是合法的,就不会再有混合内容的拦截问题。域名解析到服务器 IP 后,你也可以用 Nginx 反代把 443 端口转发到 8085,让最终对外地址更加标准。注意:把证书文件放到/opt/srs/conf/下并挂载到容器内,这样升级容器时证书不会丢。
6.3 用 Origin + Edge 集群模式扩展分发能力
当单台 SRS 的带宽成为瓶颈时,就可以上集群了。SRS 提供了 Origin + Edge 的标准集群方案:Origin 节点接收推流,做转封装和存储;Edge 节点分布在多个地域,从 Origin 拉流后向终端用户提供服务。
Origin 节点配置基本不变,只需确保 Edge 能通过内部地址访问到 Origin 的 1935 端口。Edge 节点配置如下:
listen 1935; max_connections 1000; daemon off; srs_log_tank console; vhost __defaultVhost__ { mode edge; origin origin_node_ip:1935; rtc { enabled on; } }这样终端用户连接 Edge 节点时,Edge 会向 Origin 请求同一路流并缓存转发。我当时做了一个模拟测试:两个 Edge 节点同时拉取 Origin 上的一路 RTMP 流,再各自分发 HTTP-FLV,整体延迟增加不到 100ms,效果符合预期。集群化后,扩容就是“多启动一个 Edge 容器然后加入负载均衡”的事,比在一台机器上不停压榨性能要优雅得多。
6.4 其它值得关注的小功能
SRS 还有几个很容易被忽略的好用功能:一是内置的 HTTP 文件服务可以直接托管你的 Web 页面,省掉额外 Nginx;二是支持录制,推上来的 RTMP 流可以直接存成 FLV 或 MP4 文件;三是支持 SRT 推流,某些采集设备和公网弱网环境下,SRT 的稳定性比 RTMP 好很多。如果你把 SRS 做成“全能流媒体中枢”,它的价值会进一步放大。
写在最后:一点实战体会
这套 Docker + SRS 的组合,我在几台服务器上已经稳定跑了一两年。最大的体会是“容器化让流媒体服务器变成了一个可复制的标准件”,换机器、升级版本都只是十几秒的事,真正需要花心思的反而在业务侧,比如鉴权策略、集群规划和网络调优。如果你打算在生产环境上正式使用,我建议先从单节点配置+回调鉴权开始,把推流、播放、监控跑顺了,再逐步引入集群和 HTTPS。不要一上来就搞大而全的架构,流媒体服务的问题往往出在你不注意的细枝末节——一个防火墙规则、一个 UDP 端口漏映射,都可能折腾你一晚上。最后送各位一句话:把 SRS 当工具,但别只当工具,多看看它的设计思路,很多直播系统的架构难题都能从中找到灵感。