Docker部署go2rtc:统一接入RTSP、RTMP、HLS与WebRTC的轻量流媒体平台
前几年折腾过一套摄像头监控方案,最头疼的事就是家里摄像头品牌太杂——大门口的用RTSP,阳台的只支持厂商私有协议,再加上一些旧设备只出RTMP流,光是把这些流统一拉到一个界面里就要装好几个App。后来我尝试用 Docker 部署 go2rtc 做了个多协议流媒体平台,把RTSP、RTMP、HLS、WebRTC这些格式全部揉到同一个服务里,局域网内通过网页、VLC、Home Assistant 都能直接看,而且整个服务占用的内存极低。这篇文章就完整记录一下我实际部署 go2rtc 的过程,从Docker启动、摄像头流接入、协议转换到常见问题的排查,适合正在折腾摄像头接入、想统一管理多品牌流媒体、或者想给Home Assistant补一个低延迟视频源的开发者参考。
1. 内容整体设计与思路拆解
1.1 为什么选 go2rtc,而不是一上来就用 FFmpeg 或商用 NVR
很多人在摄像头流统一接入时,第一反应是“用 FFmpeg 拉流再转推”,或者干脆上一台成品NVR录像机。这两种方案都能解决问题,但各有各的别扭。
先说说 FFmpeg 思路。FFmpeg 确实是万能的,但它的定位是“转码工具”,不是“流媒体服务”。你需要自己写脚本、管理进程、处理断流重连、维护多个协议监听端口,稍微复杂点儿的场景,比如不同摄像头走不同编码格式、客户端有的要RTSP有的要HLS,脚本逻辑就会膨胀得很难维护。而且 FFmpeg 一次转码就是一路进程,CPU占用高,摄像头多了主机扛不住。
商用 NVR 的问题更明显:协议封闭。很多 NVR 只认自家摄像头,海康的接大华、大华接 TP-LINK,虽然部分支持ONVIF,但跨品牌接入时经常出现子码流不支持、云台控制失效、报警推送对不上等问题,折腾一圈比部署开源方案还费劲。
go2rtc 这个项目解决的是“流媒体汇聚与分发”这个中间层问题。它是用 Go 写的,运行起来非常轻量,默认只需要一个二进制文件,内存占用通常几十MB级别。它把不同来源的摄像头流拉进来之后,会用统一的方式对外输出,前端可以直接通过 WebRTC 低延迟观看,也可以通过 HLS 给普通播放器使用,还能继续输出 RTSP 给 NVR 录像,或者把流接入 Home Assistant 作为摄像头实体。最实用的是,go2rtc 对不支持的协议可以调用 FFmpeg 做补充,相当于“服务框架 + 转码工具”组合拳,而不需要你自己从零拼装。
1.2 典型的摄像头接入架构是什么样
我实际部署下来的架构大概是这样的:
- 摄像头端:可能走 ONVIF/RTSP,也可能走 RTMP、HTTP-FLV,甚至某些设备只能输出 HLS。
- go2rtc 端:在 Docker 容器里运行,主动去拉取这些流,并按名称注册成一个个“流”。它内部会把协议差异消化掉,统一以多种协议对外暴露。
- 客户端端:浏览器打开 go2rtc 的 Web 界面,用 WebRTC 或 MSE 直接预览;VLC 用 RTSP 地址拉流;Home Assistant 通过 Stream 组件读取;NVR 或录像程序也可以用 RTSP 接入录制。
这个“多入多出”的架构是我最喜欢的部分。比如一个摄像头本身只支持 RTSP,但我希望手机端能低延迟观看,同时网页端又需要 HLS 兼容,NVR 还要继续录像。没有 go2rtc 时,你只能让摄像头同时推多路流,或者自己在中间做转分发。有了 go2rtc 之后,所有客户端都从 go2rtc 这一个入口拿流,摄像头到 go2rtc 只需要一条拉流连接,压力小了很多。
1.3 go2rtc 支持哪些主流协议
go2rtc 官方协议支持列表很广,实际部署中我用到的核心协议有这几个:
- RTSP (输入/输出):最常见,大部分安防摄像头都支持。
- RTMP (输入/输出):用于老的推流设备或直播源。
- HLS (输入/输出):适合网页播放、低兼容场景。
- WebRTC (输出):浏览器低延迟播放的首选。
- HTTP-FLV (输入/输出):嵌入式设备或某些直播平台会用到。
- MJPEG (输入):老式网络摄像头常输出 MJPEG 流。
- MP4 (输入/输出):适合回放或录像片段生成。
当然 go2rtc 底层也会调用 FFmpeg 处理一些不常见格式,但上面这些是其原生能力。选它做核心服务,基本覆盖了家用和中小型项目的摄像头接入需求。
2. Docker 部署 go2rtc 实操
2.1 环境准备与镜像选择
部署 go2rtc 之前,先确认一下主机环境。我用的是局域网内一台 Debian 12 主机,Docker 版本是 24.x,其实只要是能跑 Docker 的机器都可以,包括 Windows 的 Docker Desktop、群晖 NAS 的 Container Manager、甚至是树莓派这种 ARM 设备。
先拉取镜像,官方镜像是
alexxit/go2rtc
:
docker pull alexxit/go2rtc:latest
如果拉取速度特别慢,可以在 Docker 的 daemon.json 里配一个国内镜像加速器,我这边实测换成加速器之后速度从几分钟降到十几秒。另外要注意,有些机器如果 Docker Desktop 启动时报“virtualization support not detected”,先去 BIOS 里打开虚拟化(VT-x/AMD-V),Windows 还需要启用 Hyper-V 或 WSL2 后端,这个问题在 Windows 宿主机上尤其常见。
go2rtc 运行时主要需要三个端口:
- 1984:Web 管理界面和 HTTP/HLS 流量端口,也是默认的主端口。
- 8554:对外提供 RTSP 服务的端口。
- 8555:WebRTC 的 UDP/TCP 端口,浏览器低延迟播放时依赖它。
2.2 docker run 快速部署
如果想最快速度跑起来,直接用一条 docker run 命令即可:
docker run -d --name go2rtc --restart=always \
-p 1984:1984 \
-p 8554:8554 \
-p 8555:8555/tcp \
-p 8555:8555/udp \
-v /opt/go2rtc/config:/config \
-e TZ=Asia/Shanghai \
alexxit/go2rtc:latest
注意我把 8555 的 TCP 和 UDP 都映射出来了。很多网上的教程只映射 UDP,跨网络或浏览器行为异常时才意识到还差 TCP。容器内部的配置目录是
/config
,我把它挂载到宿主机的
/opt/go2rtc/config
目录,这样可以持久化保存配置,后续修改流信息不需要重建容器。
启动后先看下日志,确认服务是否正常:
docker logs -f go2rtc
如果看到类似
listening on 0.0.0.0:1984
的输出,说明服务已经起来了。这时候打开浏览器访问
http://宿主机IP:1984
,应该能看到 go2rtc 的 Web 界面,虽然还没有任何摄像头流,但界面的 Streams 和 Logs 页面已经可以正常访问。
2.3 docker-compose 正式部署
docker run 适合临时试玩,但正式使用我更推荐 docker compose。用 compose 的好处是配置可版本化管理,后续换机器迁移非常方便。
我目前用的 docker-compose.yml 是这样:
version: "3.8"
services:
go2rtc:
image: alexxit/go2rtc:latest
container_name: go2rtc
restart: unless-stopped
ports:
- "1984:1984"
- "8554:8554"
- "8555:8555/tcp"
- "8555:8555/udp"
volumes:
- ./config:/config
environment:
- TZ=Asia/Shanghai
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
启动命令:
docker compose up -d
这里有几个细节值得解释。
restart: unless-stopped
保证宿主机重启后容器能自动拉起,监控服务不能依赖手动启动;
./config:/config
是把当前目录下的 config 目录映射进容器,所以我在项目目录下提前建了 config 文件夹;日志大小限制是我踩过坑之后加的,默认 docker json-file 日志不限制大小,go2rtc 调 FFmpeg 时日志量很大,不及时清理会把磁盘写满。
2.4 容器启动后的初始化检查
容器正常运行后,不要急着配摄像头。我习惯先做两类检查。
第一是端口连通性检查。在另一台机器上执行:
curl http://宿主机IP:1984/api/streams
如果返回 JSON 数组,哪怕是空的,也说明 Web API 正常。再用 VLC 打开
rtsp://宿主机IP:8554/
,正常情况下会提示需要提供流名称,而不是直接连接拒绝。
第二是查看日志里的错误。如果之前端口被占用,docker run 阶段就会报错退出。如果容器起来但在外面访问不了 1984,大概率是宿主机防火墙没放行端口,Debian/Ubuntu 用
ufw allow 1984/tcp
,CentOS 用
firewall-cmd --add-port=1984/tcp --permanent && firewall-cmd --reload
。
3. 配置摄像头流与多协议接入
3.1 go2rtc 配置文件格式
go2rtc 的配置文件默认叫
go2rtc.yaml
,放在
/config
目录下。如果挂载目录里没有这个文件,第一次启动后 go2rtc 会自动生成一个占位配置,然后 Web 界面上也可以添加流,但最终配置会写回 yaml 文件。
我先给一个只有两个摄像头的配置示例:
streams:
front_door:
- rtsp://admin:password@192.168.1.100:554/stream1
backyard:
- rtsp://admin:password@192.168.1.101:554/stream2
每个摄像头在
streams
下用自定义名称作为 key,值是一个列表。列表里可以放多个地址,go2rtc 会按顺序尝试连接,第一个地址失败会自动切换到下一个,这个特性在做主备源时特别有用。比如一个主摄像头同时推 H.265 和 H.264 两路流,我可以把 H.264 作为 fallback:
streams:
livingroom:
- rtsp://admin:password@192.168.1.110:554/h265
- rtsp://admin:password@192.168.1.110:554/h264
要注意的是,虽然 go2rtc 支持 name 里带中文或空格,但为了后续在 URL 里引用方便,建议使用纯英文、下划线和小写字母。我一开始给摄像头命名成“Camera 01”,结果在 VLC 里拼接 RTSP 地址时总是出现编码问题,改成
camera_01
后一劳永逸。
配置文件的修改不是立即生效的,有两种方式让它生效:一是直接删除容器重建,
docker compose restart
只能重启容器,但 go2rtc 会在启动时重新加载配置;二是用 Web 界面上的 Streams 页面“添加流”,界面自动保存并热加载。我建议直接用 Web 界面添加,然后去配置文件里检查最终生成的参数,学习效果更快。
3.2 拉流参数优化:主码流、子码流与 TCP 模式
摄像头通常有两路流:主码流分辨率和码率高,适合录像和全屏预览;子码流分辨率低,适合密集网格预览或低带宽场景。go2rtc 里添加流时,URL 本身就是摄像头提供的,所以我通常会单独建两个流名称,比如
front_door_main
和
front_door_sub
。
很多品牌的 RTSP 地址路径不太一样,常见的几种格式:
-
海康/萤石:
rtsp://user:pass@ip:554/Streaming/Channels/101(101 表示主码流,102 表示子码流) -
大华:
rtsp://user:pass@ip:554/cam/realmonitor?channel=1&subtype=0(subtype=0 主码流,1 子码流) -
TP-LINK:
rtsp://user:pass@ip:554/stream1(主码流),/stream2(子码流) -
宇视:
rtsp://user:pass@ip:554/media/video1(主码流),video2(子码流)
如果摄像头默认走 UDP 拉流不稳定(经常卡顿、花屏),go2rtc 也支持通过参数的强制连接模式。在 go2rtc 的 RTSP 源里,可以用
#tcp
强制使用 TCP:
streams:
front_door:
- "rtsp://admin:password@192.168.1.100:554/stream1#tcp"
我遇到的情况是,同一局域网内大部分摄像头 UDP 也能很稳定,但是只要跨交换机或者 WiFi 链路,UDP 丢包就很明显,强制 TCP 之后画面稳定了不少。代价是 TCP 会占用略高一点的带宽,但对局域网来说基本可以忽略。
3.3 转码与硬件加速:让 H.265 摄像头也能兼容播放
很多新摄像头的默认编码是 H.265/HEVC,码率低、清晰度高,但浏览器和部分播放器不支持直接播放。HLS 和 WebRTC 的输出如果遇到 H.265,轻则白屏,重则整个流都拉不起来。go2rtc 的做法是先尝试内部转码,同时支持调用外置 FFmpeg 做转换。
在 go2rtc 配置中,可以通过
ffmpeg
前缀来让某一路流经过 FFmpeg 处理:
streams:
front_door_pc:
- "ffmpeg:rtsp://admin:password@192.168.1.100:554/stream1#video=h264#video_param=preset,ultrafast#video_param=tune,zerolatency"
这里
#video=h264
表示把视频转成 H.264,
#video_param
是传给 FFmpeg 的编码参数,
preset=ultrafast
和
tune=zerolatency
都是为了降低转码延迟。实际测试中,对于 1080P 摄像头,纯 CPU 转码大约会吃掉一个核心的 60% 左右,所以不建议同时在很多路流上做转码。
如果主机有支持 Quick Sync 的 Intel 核显,可以在 docker compose 里把
/dev/dri
设备映射进容器,然后在 go2rtc 配置中启用 VAAPI 硬件转码:
ffmpeg:
bin: ffmpeg
vaapi: /dev/dri/renderD128
硬件转码的 CPU 占用可以下降一大截,但同时也会增加配置复杂度。如果没有硬解需求,建议先把摄像头编码改成 H.264,这是最简单省事的方案。
3.4 多协议出口配置详解
go2rtc 服务跑起来之后,每一路 stream 都会自动拥有多个协议的访问地址。以流名
front_door
为例,常用的访问 URL 是:
| 协议 | 地址示例 | 适用场景 |
|---|---|---|
| WebRTC |
浏览器打开
http://IP:1984/api/webrtc?src=front_door
| 低延迟网页播放 |
| HLS |
http://IP:1984/api/stream.m3u8?src=front_door
| 通用网页播放器 |
| RTSP |
rtsp://IP:8554/front_door
| VLC、NVR、Home Assistant |
| HTTP-FLV |
http://IP:1984/api/stream.flv?src=front_door
| 低延迟 Flash/JS 播放 |
| MSE |
http://IP:1984/api/stream.mse?src=front_door
| 浏览器 MSE 播放 |
如果你希望某些流对外不可见,可以在 stream 后面加
#hidden
,避免在 Web 界面上暴露。比如只用于内网录像的流就可以隐藏起来。
如果你需要让外网也能通过 WebRTC 低延迟播放,需要给 go2rtc 配置 STUN/TURN。在局域网内默认就能播放,但跨网段时涉及 NAT 穿透,我通常会在 go2rtc.yaml 里加一段:
webrtc:
listen: ":8555"
candidates:
- stun:8555
如果完全无法建立 WebRTC 连接,又不方便搭 TURN 服务,建议退回 HLS 或 RTSP 方案,效果虽然延迟高一些,但胜在稳定。
4. 核心功能与场景应用
4.1 Web 界面实时预览与流管理
go2rtc 自带一个非常简洁的 Web 管理界面,不需要再装任何前端组件。浏览器打开
http://IP:1984
后,Streams 页面会列出所有已经配置的流。
点击任意一个流名,界面会直接弹出播放器,默认走 WebRTC,延迟基本在 0.5 秒以内,非常跟手。如果你只想快速确认画面是否正常,这个功能比打开 VLC 方便太多。
Web 界面上还有一个“添加流”的入口,可以直接粘贴 URL,填写流名称后保存。它最终会把配置写回 yaml 文件,并自动 reload。我后来深入了解发现,它就是调用了 go2rtc 暴露的几个 API,比如
GET /api/streams
获取流列表,
POST /api/streams
添加流,所以如果你有前端开发需求,可以直接复用这套 API。
4.2 与 Home Assistant 集成
如果你在用 Home Assistant,把 go2rtc 挂到里面当摄像头源是一个刚需。官方推荐的方式是在 HA 里直接安装 go2rtc 插件,但如果你已经用 Docker 单独部署了 go2rtc,也可以只在 HA 里配置 generic camera。
在
configuration.yaml
中增加:
camera:
- platform: generic
name: Front Door
stream_source: rtsp://192.168.1.200:8554/front_door
保存并重启 HA 后,前端 Lovelace 添加到实体,通过 stream 组件就能看到实时画面。但在实际使用中,HA 默认的摄像头组件拉 RTSP 是用 FFmpeg,延迟明显偏高,画面经常延迟 2-3 秒。如果你要求低延迟,可以配合 HA 的 WebRTC Camera 卡片,直接用 go2rtc 的 WebRTC 输出地址:
camera:
- platform: generic
name: Front Door Low Latency
stream_source: http://192.168.1.200:1984/api/webrtc?src=front_door
不过要注意,HA 的 generic camera 对
stream_source
的支持程度依赖系统安装的
stream
组件,建议先确认 HA 侧能正常播放 RTSP 源,再切入 WebRTC。
4.3 对接外部播放器与其他监控系统
go2rtc 的流地址可以非常方便地接进各种现有系统。
VLC 是最通用的测试工具。在 VLC 里打开网络串流,输入
rtsp://IP:8554/front_door
,如果能出画面说明 RTSP 出口正常。如果你有支持 RTSP 的 NVR,也可以把这路流作为“IP 摄像头”添加到 NVR 里,等于把 go2rtc 作为一个虚拟摄像头网关。这里需要注意,NVR 连接数有限,go2rtc 已经会缓存来自摄像头的流,多个客户端共用一路拉流,所以不至于把摄像头本身的连接数耗尽。
网页端如果想嵌入已有的监控大屏,可以用 HLS 地址。我在一个内部看板项目里直接用了
<video>
标签播放 HLS,通过 hls.js 库就能实现,代码大概就几行:
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="video" controls autoplay muted></video>
<script>
const video = document.getElementById('video');
const hls = new Hls();
hls.loadSource('http://IP:1984/api/stream.m3u8?src=front_door');
hls.attachMedia(video);
</script>
这种方式的延迟通常在 2-5 秒,但胜在兼容所有现代浏览器,不需要额外插件。
5. 常见问题与排查技巧实录
5.1 容器启动失败或 Web 界面打不开
先确认容器状态:
docker ps -a | grep go2rtc
如果容器已经退出,看日志:
docker logs --tail 50 go2rtc
最常见的启动失败原因是端口被占用。1984 或 8554 如果被其他服务占用,go2rtc 会报
address already in use
。先检查端口占用:
ss -lntp | grep -E '1984|8554|8555'
如果端口被占用,可以修改 compose 里的对外映射,比如改成
8084:1984
,但注意 go2rtc 内部默认端口不变,只改宿主机映射那一侧即可。
另一个容易忽略的问题是防火墙。很多 Linux 发行版默认开 ufw 或 firewalld,即使容器端口映射正确,外部流量也进不来。Debian/Ubuntu 放行示例:
sudo ufw allow 1984/tcp
sudo ufw allow 8554/tcp
sudo ufw allow 8555/tcp
sudo ufw allow 8555/udp
5.2 摄像头画面一直黑屏或提示拉流失败
摄像头配置好后,Web 界面如果一直转圈或提示 error,首先去 Logs 页面看日志。重点看两行:一是是否成功连接摄像头,二是编码是否被正确识别。
如果日志里出现
unauthorized
,说明 RTSP 地址里的用户名或密码不对,需要确认摄像头管理后台里的认证信息。有些摄像头默认关闭 RTSP 服务,需要在摄像头的网络设置里打开 RTSP 开关。
如果出现
unsupported codec
或画面能出来但花屏,大概率是编码问题。先把摄像头主码流的编码手动改成 H.264,关闭 H.265/H.265+。改完还不行,看看是不是音频编码导致 go2rtc 解析出错,比如某些摄像头带 G.711 音频会正常,但带 AAC 低延迟流可能异常。你可以先临时注释掉音频相关的配置,或者用 ffmpeg 前缀强制
#audio=copy
试试。
如果摄像头在公网或跨网段,且 UDP 丢包严重,就把拉流地址改成
#tcp
强制走 TCP。我测试过一个在 WiFi 链路下的摄像头,UDP 模式画面每隔几秒就卡顿一次,改成 TCP 后稳定了一个多小时。
5.3 WebRTC 无法播放或卡在黑屏
WebRTC 是局域网低延迟播放的“大杀器”,但也最容易出问题。
首先确认浏览器是否允许自动播放音频。Chrome 中如果标签页长时间没有用户操作,WebRTC 的音频可能会被静音,视频画面一般正常,但有时会误以为黑屏。可以在页面里加一个自动播放属性或者手动点击一下播放按钮。
其次是端口可达性。WebRTC 使用 8555 的 UDP 端口,如果宿主机防火墙只放行了 TCP,UDP 被挡会导致连接一直协商不成功。检查 UDP 放行,或者用 tcpdump 抓包确认 8555 UDP 是否有进出流量。
如果 go2rtc 部署在 NAT 之后,而客户端又在不同局域网,单靠默认配置很难穿透。此时需要配置 STUN/TURN。内网环境我一般直接不折腾,退到 HLS 或 RTSP 播放,毕竟低延迟不是所有场景都必需。
5.4 多路流同时拉取时性能如何优化
go2rtc 本身非常省资源,纯转发不转码的话,一个 CPU 核心跑几十路流没问题。真正吃资源的是 FFmpeg 转码和 WebRTC 的并发编码。
如果你的方案里既有主码流又有子码流,建议对外只暴露子码流给网页预览,主码流只给 NVR 录像使用。这样可以避免同一条摄像头流被多次拉取和转码。内存方面,go2rtc 每路流通常只占 30MB 左右,但如果你用 ffmpeg 转码,每路额外增加 100-200MB 内存,所以摄像头数量多的时候,优先考虑硬解或直接让摄像头输出 H.264。
我在实际项目里最多接入过 16 路 1080P 摄像头,不带转码时 CPU 占用不到 5%,内存不到 500MB,非常轻松。但如果全部开 WebRTC 预览同时还要转 HLS,CPU 占用会明显上去,所以我的方案是:WebRTC 只用于紧急预览,日常看画面走 HLS 子码流,录像走 RTSP 主码流。
5.5 实用小技巧:用 go2rtc 做录像与回放
go2rtc 本身不负责录像,但你可以配合 FFmpeg 的命令把流存成切片。我在计划任务里写了一段简单的循环脚本,每 10 分钟从 go2rtc 拉一段 RTSP 流存成 mp4:
ffmpeg -i rtsp://localhost:8554/front_door -c copy -f segment -segment_time 600 -strftime 1 "/mnt/recordings/front_door_%Y%m%d_%H%M%S.mp4"
这样做的最大好处是,摄像头到 go2rtc 只有一条连接,无论多少个客户端或录像任务,都不会给摄像头增加连接压力。录制文件的回放也很简单,用 VLC 直接打开 mp4 即可,或者再配合一个简单的文件列表页面就能做轻量级回放平台。
最后说几句
试过不少流媒体方案之后,我的体会是:go2rtc 最适合的场景不是大规模商业监控,而是“把散落的、多协议的摄像头流统一起来,快速接入自己的系统”。Docker 化部署让它在迁移、备份、升级时都非常省心,配置文件跟着目录走,一条 compose 文件就能在另一台机器恢复整个服务。如果你也正在为多品牌摄像头接入发愁,不妨先用一个 docker run 命令把 go2rtc 跑起来,再在 Web 界面上添加你的第一路 RTSP 流,后面自然会越来越顺手。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)