从第一次在服务器上折腾流媒体服务到现在,也有不少年头了。以前给人做直播或低延迟播放方案,第一反应基本都是 Nginx 加 RTMP 模块,但这几年越来越觉得不够用——RTMP 的输出协议太单一,浏览器端播放要转 HLS 或 HTTP-FLV,WebRTC 低延迟场景更是基本靠自己拼。后来换到 SRS 配合 Docker 部署实时音视频流媒体平台,整个链路清爽了很多,从装环境到推流、播放、做鉴权和录制,一条龙都能搞定。这篇文章就把我自己部署 SRS 的完整过程和踩坑经验整理出来,给正在琢磨自建流媒体服务的朋友做个参考。

1. 先搞清楚 SRS 到底解决什么问题,你才不至于白折腾

1.1 为什么是 SRS 而不是 Nginx-RTMP 或 ZLMediaKit

SRS 全称是 Simple Realtime Server,一个开源的流媒体服务器,主打高性能、低延迟和丰富的协议支持。很多人第一次接触流媒体服务,第一反应是"我用 Nginx 加个 RTMP 模块不就行了",确实,在只做简单 RTMP 转发、并发不高、不需要复杂业务逻辑的场景下,Nginx-RTMP 够用。但你一旦要同时出 HTTP-FLV、HLS、WebRTC 多种播放协议,或者要接推流鉴权、流状态回调、录制、统计这些能力,Nginx-RTMP 就要自己拼一堆东西。

另外一个常被拿来对比的是 ZLMediaKit,它在 RTSP 场景和流媒体网关能力上很强,C++ 写的,性能也够硬。但它的定位更偏向底层组件,很多能力需要你自己封装一层业务,文档和社区资料相对分散,对于中小团队或个人开发来说,上手成本会高一些。SRS 则把 HTTP API、HTTP 回调、WebRTC 推拉流这些常见需求直接内建在服务里,配合文档和活跃的社区,属于"开箱即用"的类型,这也是我最后选定它的核心原因。

SRS 支持的协议完整度是我最看重的:推流端可以用 RTMP 或 WebRTC,播放端可以出 RTMP、HTTP-FLV、HLS、WebRTC,还能做 SRT 推拉流,覆盖了从传统直播到低延迟连麦的几乎全部场景。协议转换在 SRS 里是原生能力,比如你用 OBS 推一个 RTMP 流进来,客户端可以同时用 HTTP-FLV、HLS、WebRTC 去拉流,不需要额外搭转码服务。

1.2 SRS 6 镜像能给你带来的开箱能力

我部署用的是 SRS 6 的官方镜像。SRS 6 相比早期版本把内核做了重构,WebRTC 的支持更成熟,对容器化部署也更友好。镜像里面自带了默认配置,启动之后你至少能拿到这些能力:

  • RTMP 推流和拉流,监听端口 1935
  • HTTP-FLV 播放,走 HTTP 服务端口 8080
  • HLS 切片和播放,输出目录默认在容器内的网页根目录
  • WebRTC 播放和推流,走 UDP 端口 8000
  • HTTP API 管理接口,监听端口 1985,用来查询在线流、踢流、获取服务器信息等

也就是说,跟着这篇文章跑一条 docker run 命令,你的流媒体服务就已经具备了一个小型直播平台的基础能力。后面要做的,就是按业务需要去改配置、加鉴权、做录制、调时延这些细活。

2. 部署前绕不开的 Docker 环境坑:装不上、拉不下、起不来

2.1 Linux 服务器装 Docker 的常用方式

SRS 本身可以裸装,但用 Docker 部署能省掉很多依赖和编译的麻烦,所以我默认推荐容器方式。服务器是 Linux 的话,装 Docker 有两条路。

一条是用系统自带的包管理器,比如 Ubuntu 上直接 apt install docker.io ,CentOS 上 yum install docker 。这种方式简单,但版本一般比较旧,有些新镜像特性用不了。另一条是装 Docker 官方源的 docker-ce 版本,虽然步骤多一点,但版本新,长期维护也更靠谱。我一般优先用官方安装脚本,一条命令装完:

curl -fsSL https://get.docker.com | bash
systemctl enable --now docker

装完之后先验证一下 docker run hello-world 能不能跑通。这一步别跳过,我见过不少人是直接去拉 SRS 镜像,结果发现 Docker daemon 本身就没起来,白白排查半天。

2.2 镜像拉取慢的根源与加速配置

容器玩得多的朋友基本都遇到过拉镜像卡到怀疑人生的时刻。SRS 官方镜像在 Docker Hub 上,国内网络环境下直连拉取确实慢,尤其某些时间段几乎不动。解决方案不是找什么偏门工具,而是配置镜像加速器。

Linux 下修改 /etc/docker/daemon.json ,把加速器地址填进去:

{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com"
  ]
}

需要注意的是,公开加速器的可用性一直在变,不同时期、不同网络环境下表现不一样。最稳妥的办法是去你用的云服务商控制台找它提供的专属加速器地址,每个账号会有一个独立的 HTTPS 地址,稳定性和速度都远好于公共的。无论填哪种,改完必须重启 Docker daemon 才生效:

systemctl daemon-reload
systemctl restart docker

如果你用的是阿里云容器镜像服务里的 SRS 镜像,比如 registry.cn-hangzhou.aliyuncs.com/ossrs/srs:6 ,拉取速度本身就会快很多。这也是我后面示例里直接用它代替 ossrs/srs:6 的原因。

2.3 Docker Desktop 起不来的常见原因

不少人是拿 Windows 或 macOS 做测试环境,Docker Desktop 装好后一直报 virtualization support not detected 之类的错误。这个提示说白了就是虚拟化没有对 Docker 开放。

Windows 上需要做三件事:一是进 BIOS 把 Intel VT-x 或 AMD-V 打开,二是到"启用或关闭 Windows 功能"里勾选"虚拟机平台"和"适用于 Linux 的 Windows 子系统",三是把 WSL2 内核更新到最新版,控制台执行 wsl --update 。macOS 那边主要是确认当前系统版本在 Docker Desktop 的支持列表里,以及内存分配不要太低。

这套检查做完,Docker Desktop 通常就能正常启动。如果你只是想在服务器上跑 SRS,我建议直接跳过 Docker Desktop,用 Linux 虚拟机或云服务器,省掉不少桌面环境特有的幺蛾子。

3. 一条 docker run 跑起 SRS,先把直播链路打通

3.1 理解 SRS 容器暴露的四个关键端口

SRS 镜像跑起来之前,先看一眼它默认要用的端口,搞清楚每个端口是干嘛的,后面排查问题会轻松很多。

端口 协议 用途
1935 TCP RTMP 推流和拉流入口
1985 TCP HTTP API,管理接口
8080 TCP HTTP 服务,HTTP-FLV/HLS 播放
8000 UDP WebRTC 媒体传输通道

很多人在云服务器上部署,只放了 1935 和 8080,结果 WebRTC 拉流一直黑屏,排查半天发现是 UDP 8000 没放行。记住这四条端口的用途,部署时会少走很多弯路。

跑 SRS 6 容器的命令如下:

docker run --restart=always -d --name srs \
  -p 1935:1935 \
  -p 1985:1985 \
  -p 8080:8080 \
  -p 8000:8000/udp \
  -e CANDIDATE=your_server_ip \
  registry.cn-hangzhou.aliyuncs.com/ossrs/srs:6

CANDIDATE 是给 WebRTC 用的,需要填客户端能访问到的服务器公网 IP。如果你不玩 WebRTC,只做 RTMP 和 HLS,这个环境变量可以暂时不设。启动后看日志确认运行正常:

docker logs srs --tail 30

日志里能看到 SRS 启动的版本号、监听端口和各模块初始化信息,没有明显报错就说明容器起来了。

3.2 用 OBS/ffmpeg 推流到 SRS

服务起来了,先不去管那些花哨配置,直接把一条视频流推进来验证链路。最容易上手的工具是 OBS,推流设置里填:

  • 服务器地址: rtmp://your_server_ip/live
  • 串流密钥: livestream

注意 SRS 的 URL 规则是 rtmp://ip/app/stream , live 是应用名, livestream 是流名。OBS 里服务器填到 live 这一级,流名单独填,两者拼在一起才是完整地址。

如果手上没有摄像头和采集卡,用 ffmpeg 循环推送一个视频文件更方便:

ffmpeg -re -i /path/to/video.mp4 -c copy -f flv rtmp://your_server_ip/live/livestream

推流开始后,SRS 的日志里会打印收到流的记录,同时可以通过 API 确认流状态:

curl http://your_server_ip:1985/api/v1/streams

返回的 JSON 里能看到流的 app 、 name 、 url 等信息,说明服务端已经正常接收并维护这条流了。

3.3 三种方式验证播放:VLC/网页播放器/API

推流成功之后,验证播放端能不能拉流同样重要。最简单的验证方式是打开 VLC,输入下面的地址之一:

  • RTMP 播放: rtmp://your_server_ip/live/livestream
  • HTTP-FLV 播放: http://your_server_ip:8080/live/livestream.flv

想在浏览器里直接测,可以用 SRS 自带的网页播放器。浏览器访问 http://your_server_ip:8080/players/ ,里面有很多示例播放页面,选择对应的播放器类型,填上流地址就能看到画面。SRS 的默认配置里 HLS 是默认开启的,所以 http://your_server_ip:8080/live/livestream.m3u8 这个地址也能直接用。

我平时验证一条流是否健康,习惯按这个顺序来:先 curl API 看流是否存在,再用 ffmpeg 拉一下 FLV 看能不能出数据,最后才开 VLC 或浏览器看画面。这样能快速定位是推流问题、服务问题还是播放器兼容问题。

4. 从"能跑"到"够用":用自定义配置替换默认行为

4.1 镜像默认配置与自己的配置怎么共存

跑通默认命令之后,你会发现默认配置虽然能用,但离"自己的流媒体平台"还差一层。比如你想调大并发连接数、想改 HLS 切片时长、想开启录制、想加上推流鉴权,这些都是默认配置覆盖不到的。SRS 官方镜像把配置目录放在容器内的 /usr/local/srs/conf/srs.conf ,启动时直接读取这个文件。

自定义配置有两种姿势。一种是启动容器后进容器改,但容器一旦重建就全丢了,不适合长期维护。另一种是把宿主机上的配置文件挂载进去:

docker run --restart=always -d --name srs \
  -p 1935:1935 \
  -p 1985:1985 \
  -p 8080:8080 \
  -p 8000:8000/udp \
  -e CANDIDATE=your_server_ip \
  -v /data/srs/conf/srs.conf:/usr/local/srs/conf/srs.conf \
  -v /data/srs/html:/usr/local/srs/objs/nginx/html \
  registry.cn-hangzhou.aliyuncs.com/ossrs/srs:6

这里第二行挂载是关键:SRS 的 HLS 切片文件默认会写到容器内的 ./objs/nginx/html 目录,同时 HTTP 服务的根目录也是同一个地方。把宿主机 /data/srs/html 挂载过去,HLS 文件就能持久化保存,不至于容器删掉就丢了录制和切片。

先看下容器里现在实际生效的配置,方便对照着改:

docker exec srs cat /usr/local/srs/conf/srs.conf

这是最稳妥的习惯,别凭记忆瞎写配置,先看清默认值是什么,再叠加自己的改动。

4.2 一个够用的 srs.conf:开启 HLS、DVR 录制、HTTP-FLV

以下是我在多个项目里反复用的一套配置,接口能力覆盖了推流、播放、录制、API 管理这几个高频需求:

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;
}

vhost __defaultVhost__ {
    http_remux {
        enabled     on;
        mount       [vhost]/[app]/[stream].flv;
    }
    hls {
        enabled     on;
        hls_path    ./objs/nginx/html;
        hls_fragment    2;
        hls_window      10;
    }
    dvr {
        enabled     on;
        dvr_path    ./objs/nginx/html/record/[app]/[stream]/[2006]-[01]-[02]_[15].[04].[05].flv;
        dvr_plan    session;
    }
}

这里有几个细节要特别说明。

daemon off 和 srs_log_tank console 是容器环境下的两条关键配置。 daemon off 让 SRS 以前台进程运行,容器才不会启动后立刻退出; srs_log_tank console 把日志输出到标准输出,这样你才能用 docker logs srs 看日志。很多从网上抄的配置没有这两行,容器反复重启,问题就在这。

http_remux 负责把 RTMP 流转成 HTTP-FLV,挂载路径模板是 [vhost]/[app]/[stream].flv 。默认虚拟主机场景下,对应的播放地址就是 http://ip:8080/live/livestream.flv 。

HLS 这里我设 hls_fragment 2 、 hls_window 10 ,意思是每个切片 2 秒,播放列表保留 10 秒内的切片。这样画面延迟能控制在 5 到 10 秒量级,对于大多数直播场景已经够用。如果你的场景对延迟不敏感、只是做录播回放,可以把切片拉长到 5 到 10 秒,减少碎文件数量。

dvr 模块做录制,我用的是 session 模式,推流开始录、推流结束停止,一个流对应一个 FLV 文件。文件名模板里的 [2006]-[01]-[02] 是年-月-日的通配符, [15].[04].[05] 是时.分.秒,这样录制文件按日期和时间排序很清晰。

4.3 推流鉴权回调:on_publish 怎么接

一个流媒体平台只要对外开放,几乎必然要加推流鉴权,不能让任何人拿个 OBS 就随意往你服务器推流。SRS 的做法是 HTTP 回调:收到推流请求时,向你的业务服务发一个 HTTP POST 请求,业务服务返回 0 表示放行,返回非 0 表示拒绝。

在 vhost 里加上 http_hooks 配置:

vhost __defaultVhost__ {
    http_hooks {
        enabled       on;
        on_publish    http://your_api_server/api/auth/publish;
        on_play       http://your_api_server/api/auth/play;
        on_unpublish  http://your_api_server/api/auth/unpublish;
    }
}

SRS 回调请求里会带上推流的关键信息,包括 app 、 stream 、 param 、 ip 、 tcUrl 等。你可以在 param 里携带密钥参数,推流地址写成 rtmp://ip/live/livestream?secret=xxx ,回调服务拿到 param 后校验密钥是否合法,合法就返回 0 。一个极简的 Flask 回调服务长这样:

from flask import Flask, request

app = Flask(__name__)

@app.post("/api/auth/publish")
def publish():
    data = request.get_json()
    if "secret=my_token" in data.get("param", ""):
        return "0"
    return "1"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=9000)

on_play 同理,控制谁可以拉流。这种回调机制的好处是鉴权逻辑完全由你掌控,想接数据库、Redis、内部账号系统都可以。

5. 时延优化与生产落地:这些参数值得手动调

5.1 时延从哪来:GOP cache、HLS fragment、协议选择

流媒体项目的时延是一个绕不开的话题。先说结论:RTMP 推流加 HTTP-FLV 播放,端到端延迟一般在 1 到 3 秒;HLS 播放因为切片和播放器加载机制,延迟基本在 5 到 10 秒;WebRTC 可以把延迟压到 500 毫秒以内。选哪种播放协议,本质是延迟、兼容性和服务器压力的取舍。

播放协议 端到端延迟 兼容性 适用场景
RTMP 1~3 秒 客户端需支持 RTMP,网页端基本不行 传统直播、对播放器有控制权的场景
HTTP-FLV 1~3 秒 现代浏览器需配 flv.js 或原生支持 低延迟直播、网页播放
HLS 5~10 秒 几乎所有设备原生支持 大规模分发、兼容性优先
WebRTC <500 毫秒 浏览器原生支持,需 HTTPS 安全上下文 连麦、实时互动、监控

SRS 默认开启了 GOP cache,也就是缓存最近一个关键帧到当前帧的数据,新播放器一进来立刻能出画面,首屏时间很短。副作用是延迟会高一些。如果你追求极致的 HTTP-FLV 低延迟,可以在 vhost 里关掉:

vhost __defaultVhost__ {
    gop_cache off;
}

关掉之后,播放器必须等到下一个关键帧才能起播,首屏会慢一点,但图像延迟会更低。我自己的经验是,一般直播场景不用关,GOP cache 带来的首屏体验提升比那几百毫秒延迟更值钱;但做连麦或远程控制这类强互动场景,关掉会更好。

5.2 生产环境下的端口、资源与日志设计

从一个测试容器变成生产服务,要做的第一件事是加 --restart=always ,或者用 Docker Compose 管理。我习惯用 Compose,把端口、环境变量、挂载目录、重启策略都写在文件里,后续维护可追溯。一个生产可用的 docker-compose.yml 长这样:

version: "3.8"

services:
  srs:
    image: registry.cn-hangzhou.aliyuncs.com/ossrs/srs:6
    container_name: srs
    restart: always
    environment:
      CANDIDATE: "your_public_ip"
      TZ: "Asia/Shanghai"
    ports:
      - "1935:1935"
      - "1985:1985"
      - "8080:8080"
      - "8000:8000/udp"
    volumes:
      - ./conf/srs.conf:/usr/local/srs/conf/srs.conf
      - ./html:/usr/local/srs/objs/nginx/html

TZ=Asia/Shanghai 是我一定会加的环境变量。SRS 录制文件和时间戳默认用 UTC 时间,不加的话录出来的文件名会比北京时间晚 8 小时,排查问题的时候非常容易犯迷糊。

资源限制方面,SRS 是事件驱动的网络服务,正常转发场景下 CPU 占用不高,主要瓶颈在网络带宽和连接数。容器里我习惯加一层显式限制,防止和同机其他服务抢资源:

    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2g

max_connections 这个配置项也别乱调,SRS 默认 1000,如果你只有几兆带宽,调再大也没意义;如果走的是大带宽服务器,再考虑往上加。

5.3 压测参考与性能预期

我见过很多人费劲搭好服务后,第一反应是问"能支持多少人同时看"。这个问题的答案被带宽卡死:假设每个观看用户平均码率 2 Mbps,100 并发在线就是 200 Mbps 带宽,这已经不是服务器配置能解决的问题了。所以压测之前先算带宽账。

SRS 官方提供了压测工具 srs-bench,可以在 GitHub 上找到,用 Go 写的,编译后用生成器模拟多路推拉流。我自己实测的参考数据是:一台 4 核 8G 的云主机,千兆内网带宽,纯 RTMP/HTTP-FLV 转发场景,支撑几千路并发观看没有压力,因为 SRS 在转封装方面开销很小。但如果你的业务涉及转码、音频转码、多码率输出,CPU 消耗会明显上升,那就得按转码路数额外规划资源。

6. 我踩过的坑和一些实话

6.1 常见故障排查链路

把这一年多来在群里、帖子里、自己项目里遇到的典型问题整理出来,按排查顺序列给你。

推流超时连不上,先测端口通不通:

telnet your_server_ip 1935

不通就看云安全组和服务器防火墙,确认 1935 端口放行了 TCP。通的话再检查推流地址的 app 和 stream 名是否和配置匹配。

容器反复重启,先看日志:

docker logs srs --tail 50

绝大多数情况是配置里开了 daemon on ,或者端口被宿主机其他进程占用。SRS 6 的默认镜像已经把容器友好的参数处理好了,问题通常出在自定义配置上。

HTTP-FLV 播放 404,第一反应检查 http_remux 是否开启,然后确认端口 8080 的 HTTP 服务和转发路径模板是否匹配。HLS 播放 404,则是看 hls_path 和 http_server.dir 是否指向了同一个目录,挂载自定义目录时经常有人只挂载了 HLS 输出目录,却忘了 HTTP 服务的根目录也要跟着变。

WebRTC 黑屏,优先查两件事:UDP 8000 端口是否放行, CANDIDATE 是否填了客户端可达的 IP。另外浏览器用 WebRTC 必须处于安全上下文,也就是 HTTPS 页面或 localhost 页面,直接用公网 IP 的 HTTP 页面是无法调起 WebRTC 的。

播放器跨域报错,常见于自己开发的前端页面用 flv.js 拉流。SRS 自身不需要额外配置 CORS,但如果你在前端和后端之间加了网关,那就在网关层统一处理跨域头。

6.2 关于 SRS 5 vs SRS 6 的选择建议

SRS 5 是老一代稳定版本,教程最多,踩坑记录也最全。SRS 6 是重构之后的新版本,WebRTC 能力和整体架构更新,Docker 部署更省事。我自己的建议是:新项目直接用 SRS 6,因为它的 WebRTC 链路明显更顺,官方镜像更新也更勤;如果你维护的是老项目,用的是 SRS 5 的配置模板和 API,那就保持 5 别折腾,等有明确升级需求再迁。

无论用哪个版本,部署路径都遵循同一个节奏:先跑默认配置通链路,再逐步叠加自定义配置和业务逻辑,每一步都用 API 确认状态。一次贪多最容易出的问题就是,服务起不来的同时不知道是哪一行配置写错了。

最后分享一个用 Docker Compose 的细节:每次改动 srs.conf 后,直接 docker compose restart srs 不会重新读取挂载的文件吗?实际上 restart 不会重新创建容器,配置文件是通过挂载卷实时同步进去的,SRS 进程重启时自然会读取到新配置。建议流程是改完配置先执行 docker compose exec srs cat /usr/local/srs/conf/srs.conf 确认文件内容已更新,再 docker compose restart srs ,既安全又不容易出现"改了配置却没生效"的错觉。

流媒体服务这东西,真上手跑一遍推流拉流,比看十篇架构分析都管用。先把这条链路跑通,后面接录制、接鉴权、接 CDN 分发,都是水到渠成的事。

Logo

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

更多推荐