上个月朋友公司内部直播项目找到我,需求一句话讲完:会议室的活动要推到全公司,手机电脑都能看,延迟别太夸张。我第一反应不是去翻云厂商的直播套餐,而是先想到了SRS。这个从RTMP单机服务一路做到WebRTC、HLS、HTTP-FLV全支持的开源流媒体服务器,社区活跃、文档也全,加上Docker部署SRS就三条命令的事,基本能满足大多数中小团队的实时音视频流媒体场景。这篇文章把我从拉镜像到真正跑通推流、拉流、录制、WebRTC的完整过程记录下来,包含我踩过的坑和最后在用的配置,适合刚接触流媒体服务、想快速私有化部署一套直播服务的开发者参考。

1. 先弄明白SRS和Docker这个组合为什么靠谱

1.1 流媒体服务本身的复杂度比想象中大

很多人以为流媒体服务器就是"把一份视频流转发给很多人看",上手才知道完全不是这么回事。RTMP负责推流接入,HLS要切片生成m3u8索引,HTTP-FLV要解决低延迟分发,WebRTC要处理ICE信令和UDP传输,每个协议都有自己的时序和格式要求。更别提推流鉴权、录制、时移、转码这些附加能力,用纯手工方式从Nginx加模块开始搭,光是编译依赖就能耗掉一整天。

我在做这个内部直播项目之前,也考虑过直接用云厂商的直播服务,但需求里有一条"录制文件要留在我们自己的服务器上,方便后续做剪辑复用",这就意味着要么买云的存储和转码服务,要么自建一套。云方案链路长、账单细,对一个小项目来说反而累赘。SRS正好卡在这个位置:单进程部署,配置文件写明白就能跑,协议覆盖广,官方文档里每个功能都有对应的配置片段,不需要到处找资料拼凑。

1.2 SRS到底解决了什么问题

SRS的全称是Simple Realtime Server,定位是高性能、高实时的流媒体服务器。它最早只做RTMP,后来逐步把HTTP-FLV、HLS、WebRTC、SRT这些主流协议都收了进来。最直观的价值是:你只需要维护一个服务进程、一份配置文件,就能同时提供推流接入、拉流分发、录制存储等多组能力。

我按自己实际使用中的理解整理了一个协议对照表,方便你根据场景选:

协议 典型用途 播放端支持 延迟量级
RTMP 推流上传、老牌播放器拉流 VLC、FFmpeg、Flash遗留设备 1~3秒
HTTP-FLV 浏览器/移动端低延迟直播 flv.js、西瓜播放器等 1~3秒
HLS 点播、大规模直播分发 几乎所有浏览器、iOS原生 5~20秒
WebRTC 音视频通话、互动直播 浏览器原生 0.2~0.5秒

这个表是我长期实践下来的经验值,不是官方承诺值。实际延迟还受推流端编码参数、网络带宽和播放端缓冲设置影响,但量级不会差太多。做内部直播、在线教学这类场景,HTTP-FLV和WebRTC基本够用;做活动录播回放,HLS最省心。

1.3 用Docker包一层有什么收益,又有什么代价

SRS本身支持直接从源码编译,官方也提供了编译脚本,但对不熟悉C++构建链的人来说,编译时间、依赖冲突、系统库版本问题都很劝退。用Docker之后,这些问题基本被隔离在镜像层了。我这边从拉镜像到服务起来,前后不到五分钟,这在裸机上几乎不可能。

容器化的另一个好处是可重复性。同一份srs.conf配合同一个镜像tag,在开发机、测试机、生产机上的行为是一致的,不会出现"我本地能跑,服务器上起不来"的经典事故。遇到需要升级的情况,换一个镜像tag重启容器即可,回滚也方便。

当然代价也有。如果你要编译SRS的自定义模块或修改底层依赖,容器内的环境会更受限,需要自己额外处理构建流程。但对绝大多数使用场景来说,用官方镜像跑标准功能已经足够。我的建议是:先容器化跑通业务,再根据实际需要决定是否深入定制。

2. 从零部署:三条命令跑通SRS

2.1 第一条命令直接验证服务可用

SRS官方维护了Docker镜像,最简单的方式是直接运行:

docker run --rm -it -p 1935:1935 -p 1985:1985 -p 8080:8080 -p 8000:8000/udp \
  -v $PWD/srs.conf:/usr/local/srs/conf/srs.conf \
  ossrs/srs:5

如果只是验证服务能不能起来,不需要挂载配置,直接跑:

docker run --rm -it -p 1935:1935 -p 1985:1985 -p 8080:8080 -p 8000:8000/udp ossrs/srs:5

几个端口别搞混:1935是RTMP推拉流端口,1985是HTTP API端口,8080是HTTP服务端口,用来访问SRS自带的网页和拉取HTTP-FLV/HLS流,8000是WebRTC需要的UDP端口。启动成功后,打开 http://localhost:8080/players/ ,就能看到SRS自带的播放器演示页。

默认配置下可以直接测试RTMP和HTTP-FLV,但HLS在默认配置里是关闭的,想开得用自定义配置。这也是我建议你第一时间把配置文件变成自己可控文件的原因,默认配置只是让你确认服务能用,真正干活还得按业务来调。

2.2 把配置从容器里拿出来的正确姿势

刚开始用SRS容器时,我习惯直接进容器改配置,后来发现这是给自己挖坑。容器一删,改的东西全没了,而且每次都要重新进容器操作,效率很低。正确做法是把宿主机上的配置文件挂载进容器,这样改配置、重启容器就能生效,还能做版本管理。

先看一下容器里的默认配置长什么样:

docker run --rm --entrypoint cat ossrs/srs:5 /usr/local/srs/conf/srs.conf

拿到默认内容后,在宿主机上创建自己的 srs.conf ,然后用 -v $PWD/srs.conf:/usr/local/srs/conf/srs.conf 挂载进去。这样容器里用的是你宿主机上的配置,改完配置只需要:

docker restart srs

挂载时注意路径要写绝对路径,相对路径在某些环境下会产生诡异问题。如果你还想把录制的文件也保留在宿主机上,可以再加一个目录挂载:

-v $PWD/srs-data:/usr/local/srs/objs/nginx/html

这样HLS切片和DVR录制文件都会落在宿主机当前目录的 srs-data 下,方便直接查看和备份。

2.3 版本选型:SRS 5还是4

SRS 5是当前的主线版本,也是官方文档主推的版本。相比4.x,它在WebRTC、GB28181、SRT等协议支持上更完整,配置语法也更统一。如果项目是今年新起的,直接用5就好。

选版本时有一个经验:tag不要用 latest ,生产环境一定要锁版本。 ossrs/srs:5 虽然会跟随5.x的小版本更新,但至少比latest可控。我自己的习惯是进一步锁定到具体小版本,比如 ossrs/srs:5.0 ,确保镜像内容可预测,避免某天重拉镜像后行为变化导致线上问题。

还有一点,SRS 4不止有代码,官方也维护了一个比较稳定的镜像tag ossrs/srs:4 。如果你的项目里有老模块、老回调逻辑是照着4.x写的,先用4也可以,但新项目建议直接5,少走迁移的弯路。

3. 推流和拉流都跑通了,平台才算真的能用

3.1 FFmpeg推流:先确认你的媒体流格式

容器起来、端口开放,接下来就是推流。FFmpeg是测试阶段最顺手的推流工具,命令结构很简单:

ffmpeg -re -i test.mp4 -c copy -f flv rtmp://127.0.0.1:1935/live/livestream

这里有一个坑要提前讲: -c copy 的含义是流复制,不重新编码,速度很快,但要求源视频的编码格式是SRS能直接分发的H.264+AAC。如果源文件是MPEG-4编码或者音频是MP3, -c copy 出来的流在大部分播放器里会黑屏或没声音,这时候需要转码:

ffmpeg -re -i test.mp4 -c:v libx264 -preset veryfast -tune zerolatency \
  -c:a aac -f flv rtmp://127.0.0.1:1935/live/livestream

-re 参数的意思是按原始帧率读取文件,模拟实时推流,不加它FFmpeg会以最快速度推完整个文件,服务端收到的就是"秒送全片",根本达不到直播效果。

推流时URL里的 /live/livestream 要拆开理解: live 是SRS里的应用名(默认vhost下叫live), livestream 是流的标识,可以随便取,但拉流时要用同一个名字。我习惯用日期加场景命名,比如 live/activity_20250110 ,方便后期排查。

3.2 拉流:VLC、ffplay和浏览器三种方式选哪个

推流推上去了,拉流的姿势主要有三种,我分别说下实际体验。

VLC是最省事的播放器,打开网络串流输入 rtmp://127.0.0.1:1935/live/livestream ,如果路径没错基本秒开。VLC也能播 http://127.0.0.1:8080/live/livestream.flv 和 http://127.0.0.1:8080/live/livestream.m3u8 ,适合拿来快速验证协议是否可用。

ffplay适合在命令行环境快速测流,命令:

ffplay -fflags nobuffer -i rtmp://127.0.0.1:1935/live/livestream

-fflags nobuffer 可以降低播放缓冲,延迟更小,但画面可能会更卡。这个就是测试用,不用纠结参数,能看到画面就行。

浏览器场景则要看协议选播放插件:HTTP-FLV用flv.js封装,HLS用hls.js,WebRTC直接原生播放。SRS自带的播放页 http://localhost:8080/players/ 里已经把几种播放器都集成好了,填上流地址就能测,省去了自己写页面的麻烦。我实际做项目时,会把SRS播放页当第一排查工具:流推不上去或者拉不下来,先在这个页面里挨个协议试一遍,哪个能出画面就说明对应协议链路是通的。

三种方式选哪个取决于用户端环境,我自己的选择原则是:内部工具类页面优先HTTP-FLV,延迟低且浏览器兼容性好;需要原生iOS Safari播放就上HLS;互动场景必须WebRTC。

3.3 把HLS和DVR录制一起开了

HLS在SRS里配置很简单,在vhost里加一段:

vhost __defaultVhost__ {
    hls {
        enabled on;
        hls_path ./objs/nginx/html;
        hls_fragment 5;
        hls_window 30;
    }
}

hls_fragment 是切片时长,我习惯设5秒,兼顾延迟和切片数量; hls_window 是保留窗口,30秒意味着播放器最多往回看30秒的直播内容。开完之后,拉流地址变成 http://127.0.0.1:8080/live/livestream.m3u8 ,打开后会发现目录下自动生成了多个 .ts 切片文件。

DVR录制用于把推流内容直接落盘为FLV文件,配置方式:

dvr {
    enabled on;
    dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].flv;
    plan session;
}

plan session 表示每次推流会生成一个独立文件,文件以推流开始时间为命名,这样物料的归档非常清晰。录制文件直接在 srs-data 目录下,配合上一步的目录挂载,宿主机上随时能拿到成片,不需要进容器翻文件。

开过录制之后要注意磁盘占用,一场两小时的720p直播,FLV文件大概在1.5GB到2GB,如果活动密度高,建议给录制目录单独做磁盘配额或定时清理策略。

4. WebRTC低延迟场景:默认能出流,但正式用必须调

4.1 SRS的WebRTC链路到底是怎样转的

WebRTC和RTMP/HLS走的是完全不同的传输链路。前者基于UDP,使用SRTP加密媒体流,由ICE协议负责打通两端网络,而SRS在这条链路上的角色不是简单的媒体转发,它要同时处理信令层面的SDP协商、ICE candidate的交换,以及媒体层面的SRTP包转发。

SRS内置了WebRTC网关,默认监听 8000/udp 。浏览器推流时,会先通过HTTP接口做SDP Offer/Answer交换,协商完成后媒体包直接走8000端口这个UDP通道。所以你在Docker部署时如果漏了 -p 8000:8000/udp 这个映射,WebRTC推拉流大概率失败,但RTMP和HTTP-FLV却一切正常,很有迷惑性。

4.2 局域网部署先改candidate

SRS默认配置里 rtc_server 的candidate是127.0.0.1,也就是说它告诉浏览器"你往这个地址发媒体包"。如果你在容器所在的宿主机上测试,这个默认值没问题;但局域网内其他机器要播放时,浏览器拿到的candidate指向回环地址,根本路由不到你的SRS服务器,表现就是WebRTC一直连接失败。

解决办法是把candidate改成SRS服务器的实际IP:

rtc_server {
    enabled     on;
    listen      8000;
    candidate   192.168.1.100;
}

这里的IP要填客户端能够访问到的地址。如果SRS部署在内网服务器,就填服务器的内网IP;如果部署在云主机且客户端从公网访问,就要填公网IP或绑定到弹性IP上。改完重启容器,再打开播放页测试,能出画面就说明candidate对了。

我还遇到过一种情况:服务器有多网卡,candidate填了一个内网IP,但客户端和服务器在另一个网段,导致媒体包发不回来。排查时可以在客户端那边抓包看有没有收到来自SRS的UDP包,没收到基本就是candidate指向的地址不可达。

4.3 外层加HTTPS和端口放行

WebRTC用于浏览器推流时,尤其涉及摄像头和麦克风,浏览器要求页面必须处于安全上下文,也就是HTTPS或localhost。内网测试环境用http还能将就,一旦部署到公网或者跨部门访问,就必须给SRS的播放页面套上HTTPS。

实操中有两种做法:一种是在SRS前面加Nginx反向代理,由Nginx终结SSL证书,再把对应请求转发给SRS的8080端口;另一种是直接在SRS的http_server层做SSL配置,但证书管理不如反代灵活。我推荐用反代,证书续期、域名变更、限流配置都集中在Nginx,SRS容器保持干净。

还有安全组和防火墙这一层,不只是TCP的443、1935、8080要放行,UDP的8000必须单独加规则。很多云安全组默认只放行TCP,UDP是被忽略的,结果WebRTC推流一直卡在连接阶段,折腾半天发现是安全组规则漏了。

5. 部署中曝光率最高的坑,附排查链路

5.1 镜像拉不动?先配加速器再谈别的

Docker官方镜像仓库在国内的访问速度一直不稳定,刚接触Docker的人很容易卡在拉镜像这一步,甚至误以为是Docker安装有问题。排查顺序其实是先配置镜像加速器,再重试拉取。

以Linux环境为例,编辑 /etc/docker/daemon.json :

{
  "registry-mirrors": [
    "https://xxx.mirror.aliyuncs.com"
  ]
}

xxx 那一段需要登录阿里云容器镜像服务控制台,在"镜像加速器"页面获取你账号专属的地址。配置完执行 sudo systemctl restart docker ,再拉一次镜像速度就正常了。Windows上的Docker Desktop在Settings的Docker Engine选项里改同样的JSON配置即可。

有一点要说明,镜像加速器只管Docker Hub镜像的拉取加速,和网络代理是两码事,不要混为一谈。如果配置了加速器后拉镜像仍然超时,检查一下 daemon.json 的JSON格式是否合法,以及Docker服务是否真的重启成功了。

5.2 推流失败?按这个顺序排

推流失败是流媒体部署里最常见的故障,我总结了一套固定排查顺序,遇到问题就按这个走。

第一,确认容器状态和端口映射。执行 docker ps 看SRS容器是否在运行, docker port srs 看端口映射是否符合预期。经常有朋友反映"我容器起来了但推流失败",结果一看8000端口没映射,WebRTC当然不工作。

第二,看SRS自身日志。容器里SRS默认会把日志打到控制台,执行 docker logs -f srs 就能看到实时输出。推流失败时,日志里一般会有 parse uri failed 、 no vhost 、 auth failed 这些关键词,根据报错去查配置,比瞎猜快得多。

第三,验证端口连通性。在另一台机器上执行 telnet 127.0.0.1 1935 ,确认RTMP端口是否真的能从外部访问。如果telnet不通,优先检查宿主机防火墙和安全组,而不是怀疑SRS本身。

第四,核对推流地址。SRS默认vhost是 __defaultVhost__ ,应用层路径用 live ,如果你推流时写的是 rtmp://127.0.0.1:1935/myapp/mystream ,而SRS里没有名为 myapp 的vhost或应用配置,服务端会直接拒绝。排查时可以把推流地址改回 rtmp://127.0.0.1:1935/live/test 试试,如果通了,就是路径或配置的问题。

5.3 Windows的Docker Desktop虚拟化报错

Windows上跑Docker Desktop,最常见的启动报错是这个: Docker Desktop failed to start because virtualisation support wasn't detected 。这句话翻译过来是"没检测到虚拟化支持",实际原因通常是BIOS里虚拟化没开,或者Windows功能组件不完整。

排查步骤按顺序来:先打开任务管理器,在"性能"标签里看CPU的虚拟化状态是否是"已启用"。如果显示未启用,需要重启进BIOS,找到Intel VT-x或AMD-V选项打开。

如果虚拟化已启用,那就是Windows功能层的问题。在"启用或关闭Windows功能"里勾选"适用于Linux的Windows子系统"和"虚拟机平台",然后重启系统。重启后再启动Docker Desktop,基本就能正常起来。

还有一种情况是旧电脑CPU不支持所需的虚拟化指令,或者系统版本过旧无法装WSL2,这时无论怎么设置都很难让新版Docker Desktop工作。我的建议是别花太多时间纠结,直接换一台新机器,或者改用纯Linux服务器部署,这条路更稳。

5.4 端口冲突和UDP端口丢失

端口冲突在部署时很常见,8080端口经常被其他Web服务占用。SRS的HTTP服务端口其实是可以改的,在配置里调整 http_server 的 listen 字段就行,同时记得改Docker的端口映射关系。比如改成9080:

http_server {
    enabled on;
    listen 9080;
    dir ./objs/nginx/html;
}

Docker映射改成 -p 9080:9080 ,访问地址就变成 http://127.0.0.1:9080/players/ 。

UDP端口丢失这个问题更容易被忽略。部分云平台的控制台默认只让配置TCP端口转发,UDP端口需要单独添加。你可以在容器内用一个简单方式验证UDP端口是否真的可用:执行 docker exec srs netstat -ulnp | grep 8000 ,看看SRS进程有没有监听8000。如果监听正常,但外部访问不通,那基本可以断定是安全组或防火墙没放行UDP。

6. 可以直接抄走的生产化配置

6.1 一份能落地的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       192.168.1.100;
}

vhost __defaultVhost__ {
    hls {
        enabled         on;
        hls_path        ./objs/nginx/html;
        hls_fragment    5;
        hls_window      30;
    }
    dvr {
        enabled         on;
        dvr_path        ./objs/nginx/html/[app]/[stream].[timestamp].flv;
        plan            session;
    }
}

这份配置做了几件事:打开HTTP API方便做状态监控;打开HLS实现浏览器兼容播放;打开DVR把每次推流自动录制为FLV文件;WebRTC的candidate设置为实际服务器IP。字段含义基本都能见名知意,改IP和端口后就可以直接用。

6.2 用docker-compose固化部署

单机用 docker run 够用,但如果你的服务器需要经常重建、或者想统一管理多个容器,用docker-compose更合适。我一般放一个 docker-compose.yml :

services:
  srs:
    image: ossrs/srs:5
    container_name: srs-live
    restart: always
    ports:
      - "1935:1935"
      - "1985:1985"
      - "8080:8080"
      - "8000:8000/udp"
    volumes:
      - ./srs.conf:/usr/local/srs/conf/srs.conf
      - ./srs-data:/usr/local/srs/objs/nginx/html
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"

restart: always 保证了机器重启后服务自动拉起来, logging 配置避免容器日志无限增长撑爆磁盘。到这一步,整个部署就变成了两条命令的事:

docker compose up -d
docker compose logs -f

6.3 后续进阶:鉴权回调与状态监控

如果你不满足于"能推能拉",想加一层鉴权,SRS的HTTP回调是标准做法。在vhost里配置 http_hooks ,推流开始和结束时SRS会向你的业务服务器发一个HTTP请求,你可以在回调里校验推流密钥、记录推流时长:

http_hooks {
    enabled     on;
    on_publish  http://127.0.0.1:8080/api/auth/publish;
    on_unpublish http://127.0.0.1:8080/api/auth/unpublish;
}

业务服务器收到请求后,返回HTTP 200就放行,返回非200就拒绝推流。这个方案实现简单,不需要改SRS源码,也能覆盖大多数内部的权限需求。

状态监控则可以定期调SRS的HTTP API,比如 curl http://127.0.0.1:1985/api/v1/summaries 能拿到当前在线流数、带宽等信息,配合Prometheus等监控工具定期采集,就能在推流异常时第一时间告警,而不是等用户反馈才发现服务器挂了。

在我实际运行这套服务的几个月里,最深的体会是:SRS的默认配置只是一个起点,真正让它适合业务的是配置文件里那些按需打开的功能模块。Docker把部署门槛降到了很低,但流媒体链路的调试能力还是要靠日志、端口排查和协议理解一点点积累。希望这份记录能帮你少走一些我走过的弯路。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐