用go2rtc统一接入多品牌摄像头:RTSP、ONVIF与WebRTC低延迟播放实践
我家里现在有三个品牌的摄像头,海康、大华、还有个杂牌,它们的取流方式没有一个是一样的。海康给了RTSP地址,大华直接连ONVIF能发现,杂牌那台只放了RTMP推流地址。之前我把这些流分别交给VLC播放器、NVR和FFmpeg轮询,维护起来非常割裂。后来我在 Docker 里部署了 go2rtc,只用一个 YAML 文件就把这几路流的接入统一了,再通过浏览器 WebRTC 秒开预览,延迟基本体感不到。这篇文章把这套部署方式完整记录下来,包含端口规划、摄像头型号参数、断流恢复这些实战细节,适合正好想把手里杂牌摄像头统一管理的朋友。
1. go2rtc 解决的痛点:摄像头协议割裂与高延迟播放
1.1 摄像头接入的三个层面对不齐
先说场景。很多时候我们发现问题不是某一路摄像头接不上,而是所有摄像头在一起时非常混乱。协议层面,海康喜欢用
rtsp://ip:554/Streaming/Channels/101
,大华是
rtsp://ip:554/cam/realmonitor?channel=1&subtype=0
,TP-LINK 又是
rtsp://ip:554/stream1
,彼此之间没有统一规律。再遇到只支持 RTMP 推流的设备,情况就更麻烦,很多老平台的 RTMP 地址没法直接在浏览器里播放。播放层面,浏览器原生不支持 RTSP,如果非要看画面,要么装 VLC 这类客户端,要么临时起 FFmpeg 把 RTSP 转成 HLS 挂到网页上。转换的进程一多,端口管理和进程守护就成了新负担,摄像头一重启,那一堆 FFmpeg 命令基本都要手动再搞一轮。转发层面也存在问题,多个人同时点开同一个摄像头地址,摄像头本身要同时编码输出多路流,很多设备的硬件编码能力根本承受不住,画面卡顿、设备过热都很常见。
我当时的诉求很明确:把各协议统一到一个入口,浏览器直接打开就能看,延迟尽量低,多人观看时不要给摄像头增加负担。go2rtc 正好把这几个问题都覆盖了。
1.2 go2rtc 的定位:流媒体网关,而不是转码器
go2rtc 是 Go 语言写的单体服务,官方 Docker 镜像很小,部署就是拉一个容器再挂一个配置文件的事。它的核心能力是协议转换和流分发,支持 RTSP、RTMP、HLS、LL-HLS、MSE、WebRTC、ONVIF、SIP 这些常见协议。你可以把不同来源的流都汇聚到它这里,再由它统一对外输出;它也内置了 Web UI 和一组 HTTP API,方便管理和调试。项目最初在 Home Assistant 社区里很流行,后来很多玩摄像头、做多路流预览的人都开始单独部署它,正是因为协议覆盖面广且切换链路很干净。
有一点需要提前建立认知:go2rtc 本身不是完整转码服务。它默认做的事情是“直接转发原始流”,也就是摄像头输出的 H.264/H.265 画面是什么编码,它转到浏览器里还是什么编码,只是换了个传输方式。这样 CPU 占用极低,也基本不引入画质损失。真正的转码它也能做,通过内置 FFmpeg 模块以
ffmpeg:
前缀的方式启动,但那是兜底方案,不是默认选项。
1.3 和常见方案放在一起比
很多人可能会问:既然有 FFmpeg、nginx-rtmp、甚至厂商自己的 SDK,为什么还要单独上 go2rtc?我做过一个简单对比:
| 方案 | 协议接入 | 浏览器低延迟播放 | 多客户端共享上游 | 配置维护成本 |
|---|---|---|---|---|
| go2rtc | 多协议统一 | WebRTC,亚秒级 | 天然支持 | 一个 YAML |
| FFmpeg 转 HLS | 需自己写命令 | 高,约 3-10 秒 | 需配合 Nginx | 进程多、易乱 |
| nginx-rtmp | 主要收 RTMP | 需再转 HLS | 支持一般 | 配置分散 |
| 厂商 SDK 自研 | 单一厂商 | 看实现 | 看实现 | 开发量最大 |
我实际用下来,go2rtc 最省心的地方就是“只维护一个配置文件”和“WebRTC 播放不需要额外插件”。其它方案也不是不能跑,但要从零把多协议接入、低延迟播放、多客户端分发全部串起来,工作量会涨好几倍。
1.4 哪些场景不适合 go2rtc
说实话,go2rtc 也不是万能。如果你的目标是把各路流统一转码成固定的 HLS 切片给几千人看直播,那它不太合适,更合理的做法是 FFmpeg 负责转码,CDN 负责分发,go2rtc 只需要作为推流入口之一。如果你手头全是完全封闭的私有协议设备,比如某些老平台只允许用自家 App 连接,不支持 RTSP/RTMP/ONVIF 任何一项,那 go2rtc 也帮不上忙,只能先把设备接到能转出标准流的边端设备上,再接进来。理解了它的边界,后面调参就不会走偏。
2. 部署前先定几件事:镜像、端口、配置挂载和网络模式
2.1 镜像选择与版本习惯
go2rtc 的 Docker 镜像名是
alexxit/go2rtc
,直接拉下来就能用。它更新节奏比较快,我建议日常使用固定一个大版本 tag,不要长期追 latest,否则某天升级可能遇到配置格式不兼容或者启动参数变化。我自己会在项目 Releases 页面看当前推荐 tag,然后写进 compose 文件里。配置备份也很轻松,整个服务就一个 YAML 文件,升级前复制一份出来就够了。
镜像本身很小,基础环境干净,容器里只跑它一个进程,不会像某些全家桶镜像那样塞一堆系统工具进来。这在磁盘空间紧张的小主机上尤其舒服。
2.2 端口职责先说清楚:1984、8554、8555
go2rtc 默认使用三个端口,很多人第一次部署就是被这三个端口搞糊涂的。
| 端口 | 协议 | 作用 | 是否必开 |
|---|---|---|---|
| 1984 | TCP | Web UI、HTTP API、WebRTC 信令 | 必开 |
| 8555 | UDP | WebRTC 的媒体数据传输 | 浏览器播放时必开 |
| 8554 | TCP | go2rtc 对外提供 RTSP 转发服务 | 按需开 |
1984 是核心入口,浏览器打开 Web UI、调用 API 都走这里。8555 是 WebRTC 传输视频流的 UDP 端口,如果你希望用浏览器低延迟预览,这个端口必须映射到宿主机,而且最好保持端口号不变。8554 则是当你想把 go2rtc 里已经统一的流再以 RTSP 方式提供给外部 NVR、录像机或其它播放器时才会用到。我一开始只开了 1984,浏览器界面能打开但画面一直转圈,后来检查了一番才发现是 8555 的 UDP 端口没有映射出来。
2.3 配置文件的挂载方式
go2rtc 的服务配置都写在
go2rtc.yaml
里,Docker 部署时直接把目录挂到容器内的
/config
就行。我习惯在主目录下建一个
go2rtc
文件夹,里面只放这个 YAML 文件,方便备份。
容器启动后,它默认会读取
/config/go2rtc.yaml
。如果你用不同文件名,可以通过启动参数指定路径。只要挂载目录不出错,后续修改配置可以用 Web UI 在线编辑,保存后热重载,不需要重启容器。
2.4 网络模式:Linux 上用 host,Windows/macOS 上要有心理准备
这是 go2rtc 部署里最大的一个坑,也是很多人 WebRTC 连不上的根本原因。在 Linux 主机、NAS 或树莓派上,我强烈建议使用
network_mode: host
。host 模式下容器直接共享宿主机网络,1984、8555、8554 这些端口天然对应宿主机的真实 IP 和端口。WebRTC 在做协商时会把宿主机的 IP 信息带进信令,浏览器拿到这个地址就能直接建立 UDP 通道,链路最顺畅。
但在 Docker Desktop 这类运行在虚拟机里的环境上,host 网络模型的表现不一样,尤其 UDP 端口的透传和地址映射偶尔会有问题。症状很典型:Web UI 里能看到摄像头列表,也能看到流已经开始拉取,但播放画面一直停在 connecting。如果你必须用 Docker Desktop,可以先尝试标准的 bridge 模式加端口映射:
ports:
- "1984:1984"
- "8555:8555/udp"
- "8554:8554"
如果仍然连不上,别浪费时间折腾了,直接把 go2rtc 挪到局域网内的 Linux 小主机或者 NAS 上跑。长期做流媒体服务,一个稳定的 Linux 运行环境比什么都重要。
3. 用 docker compose 起服务,接入第一路摄像头
3.1 一份可以直接用的 compose 文件
我自己的部署环境是局域网里一台 J4125 小主机,Debian 系统,Docker 跑的 compose。文件长这样:
services:
go2rtc:
image: alexxit/go2rtc:latest
container_name: go2rtc
restart: unless-stopped
network_mode: host
volumes:
- /opt/go2rtc:/config
如果网络环境不支持 host 模式,就把
network_mode: host
注释掉,改成我上面说的 ports 映射。
/opt/go2rtc
是我自己定义的宿主机目录,你可以改成任意路径。
restart: unless-stopped
保证小主机重启后 go2rtc 能自动起来,这点对无人值守运行很重要。
启动之前先把配置目录建好,并放一个最小的 go2rtc.yaml 进去,否则容器起来可能因为找不到配置直接退出,或者启动一个空服务。
3.2 最小配置文件:只加一个 RTSP 摄像头
先写一个最简单但能立刻工作的配置:
log:
level: info
streams:
down_hall: rtsp://admin:YourPassword@192.168.1.10:554/Streaming/Channels/101
这里的
down_hall
是我给这路流取的名称,后面所有播放地址里都会用到它。
rtsp://
后面是摄像头的实际地址和账号密码。在写进配置前,我建议先用 VLC 试播一下这个地址,确认摄像头本身能出画面,再交给 go2rtc。这样可以缩小排障范围,避免把摄像头侧的问题误判成 go2rtc 的问题。
3.3 启动、验证和第一个流的确认方式
执行:
docker compose up -d
docker compose logs -f
看到日志里没有报错、摄像头状态变成 online 之后,打开浏览器访问:
http://小主机IP:1984
Web UI 里能看到 down_hall 这路流,点播放如果秒出画面,第一步就算跑通了。如果还是想用命令行确认,可以执行:
curl http://127.0.0.1:1984/api/streams
返回的 JSON 里会列出当前所有流的状态。online 表示正常,offline 表示连接失败,这个接口在我们后面排查问题时非常有用。
3.4 后续加流:在线配置与热重载
go2rtc 的 Web UI 里有一个 Config 页面,可以直接编辑 go2rtc.yaml 并保存。保存后服务会自动应用新配置,不需要重启容器。这个功能非常实用,我后来加摄像头都是直接在网页上补一个 streams 条目,几十秒就生效。需要注意,在线编辑失败时页面上会给出错误提示,此时不要强行刷新丢弃错误信息,先复制内容留底,再修正。
4. 多品牌摄像头接入:RTSP 参数、ONVIF 发现与 FFmpeg 兜底
4.1 常见品牌的 RTSP 地址规律
不同品牌的默认取流地址各不相同,我把遇到过的规格整理了一下:
| 品牌/设备 | 常见 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/camera1
|
部分需要
/media/video1
|
| 通用 ONVIF |
rtsp://user:pass@ip:554/onvif1
| 很多第三方设备都支持这个路径 |
这些只是最常见的路径,同品牌不同固件也可能不一样。我一般打开摄像头后台的管理页面,在“音视频”或“媒体设置”里都能看到 RTSP 取流地址示例,照抄最稳。
4.2 不想猜地址,就先用 ONVIF 发现
如果摄像头支持 ONVIF,go2rtc 的 Web UI 里可以直接做设备发现。它会扫描局域网内的 ONVIF 设备,弹出来之后你填上摄像头用户名密码,系统会自动尝试探测 RTSP 地址并生成对应的 streams 配置。对于大华这类 ONVIF 支持比较完整的设备,这一步基本不需要手写地址。前提是摄像头后台里没有把 ONVIF 功能关闭,很多设备默认是开启的,但也有一些安全模板会顺手把它关掉。
ONVIF 发现机制本质上是通过 WS-Discovery 广播去找支持 ONVIF 的设备,再通过设备服务接口拿媒体流地址。它帮你省去的是“查文档、找路径”的时间,但认证信息仍然以摄像头本身的账号密码为准。
4.3 接不动的协议,用 ffmpeg 前缀兜底
遇到只开放 RTMP 或者其它不好直接接入的流,可以在 streams 配置里加
ffmpeg:
前缀,让 go2rtc 调用内置的 FFmpeg 把流抓进来。比如:
streams:
old_cam: ffmpeg:rtmp://192.168.1.30/live/main
这样 go2rtc 会启动一个 FFmpeg 进程去连接 RTMP 源,再把数据转交给 go2rtc 做 WebRTC 或 HLS 输出。这个模式也可以用来做转码,比如把摄像头输出为 H.265 的画面转成 H.264 给浏览器用,代价是 CPU 占用会明显上升。所以我把它定位成兜底方案,能用原生 RTSP 直连就尽量直连。
4.4 编码类型选择:H.264 还是 H.265,直接决定浏览器能不能播
很多新型摄像头默认输出 H.265 编码,因为同等画质下码率更低。但浏览器对 H.265 的 WebRTC 播放支持很有限,Chrome 经常直接黑屏,Safari 则相对好一些。如果只想图省事,优先到摄像头后台把视频编码改成 H.264 使用。你会发现接入 go2rtc 之后浏览器秒开,CPU 也轻松。如果确实要保留 H.265 以节省带宽,那就要做好部分浏览器播放不了的心理准备,或者在 go2rtc 里对那路流单独特转码。
5. WebRTC 低延迟链路与几种播放地址的实测
5.1 为什么最终选 WebRTC,而不是 HLS
最开始我也试过 HLS,兼容性确实很好,但延迟太高。我实测局域网环境下 HLS 从画面事件发生到浏览器显示,大约滞后 3 到 5 秒,开门、找东西这种场景根本没法用。WebRTC 是浏览器原生支持的实时传输方案,走 UDP,建立连接后画面延迟能压到 0.2 到 0.5 秒,我自己的体感是“基本实时”。
5.2 几种常用的播放地址写法
go2rtc 对外输出几种方式,在浏览器和外部播放器里的玩法不同:
| 方式 | 地址示例 | 适用场景 |
|---|---|---|
| Web UI 播放 |
http://host:1984/?src=down_hall
| 日常预览、调试 |
| HLS |
http://host:1984/api/stream.m3u8?src=down_hall
| 兼容性要求高的播放器 |
| MSE |
http://host:1984/api/stream.mse?src=down_hall
| 自定义页面播放 |
| MP4 |
http://host:1984/api/stream.mp4?src=down_hall
| 片段播放、录制 |
| RTSP 转发 |
rtsp://host:8554/down_hall
| 外部 NVR、VLC |
这些地址里的
down_hall
就是配置文件里定义的流名,换成你自己的即可。
5.3 多客户端同时预览,摄像头侧只有一路流
这是 go2rtc 分发机制里最有价值的一点。多个用户同时通过 WebRTC 看同一路摄像头时,go2rtc 只在源头维持一条到摄像头的 RTSP 连接,其余观看者从 go2rtc 这里拿转发的数据。摄像头不会因为观看人数变多而被迫输出多路编码流。相比之下,直接把摄像头 RTSP 地址给多个人播放的方式,每多一个人,摄像头就要多承担一路编解码压力,设备很快就会卡顿。做了统一接入之后,这个问题基本消失。
5.4 实测下来的延迟数据与设备负载
我在局域网里做过一次粗略测试:摄像头画面中秒表跳动,浏览器 WebRTC 窗口显示的画面比真实秒表慢约 0.3 秒。换到 HLS 后,差距拉到 4 秒以上。设备负载方面,在 J4125 小主机上接 8 路 1080P 摄像头,全部通过 go2rtc 以 WebRTC 分发给两三个客户端同时观看,整体 CPU 占用在 15% 左右。如果开了转码,这个数字会大幅上升,所以日常预览我都是让摄像头直接输出 H.264,go2rtc 只做转发。
6. 排错实录:密码转义、断流重连、H.265 黑屏与公网安全
6.1 密码里的特殊字符,坑了我半小时
有一次我添加一路大华摄像头,密码里带了
@
符号,写进 YAML 之后日志一直报 401 Unauthorized,但设备本身是好的。原因很简单:RTSP URL 里的用户名密码部分遇到
@
会被解析器拆错。解决办法是做 URL 编码,
@
写成
%40
,
#
写成
%23
,
:
写成
%3A
。我后来都会提前用一个小命令把密码处理一下:
python3 -c "from urllib.parse import quote; print(quote('Admin@123'))"
输出
Admin%40123
,再把这段粘到 RTSP 地址里。如果日志里出现 “error connection refused” 或者 “host not found”,先别怀疑 go2rtc 的问题,优先检查摄像头地址里有没有需要转义的字符。
6.2 摄像头断电再恢复,go2rtc 能自己拉回来
我做过一次模拟故障:把某台摄像头网线拔掉几分钟,再插回去。go2rtc 的日志里能够看到连接断开、然后按退避策略反复重连,设备恢复后日志显示拉流成功,Web UI 里的画面自动恢复,不需要重启容器。这个特性在实际运行中很重要,因为家用摄像头偶尔会因为 WiFi 波动或电源接触不良掉线,没人愿意每次都手动去重启服务。只要摄像头本身恢复网络,go2rtc 会自动把流接回来。
6.3 H.265 黑屏:不是 go2rtc 的问题,是浏览器不认
有一路新摄像头默认开了 H.265,接入 go2rtc 后画面一直黑屏,但流状态显示正常。当时我以为是 WebRTC 端口的问题,排查了半天,最后发现是浏览器不支持 H.265 编码。Safari 能播,Chrome 不行。解决方案是到摄像头后台把编码改成 H.264,或者对那一路流单独加转码。以后再遇到自家摄像头黑屏,我会第一反应看一下编码格式,而不是先怀疑服务配置。
6.4 别把 1984 端口直接暴露到公网
go2rtc 默认没有鉴权,任何人只要能访问到 1984 端口,就能看到所有摄像头画面,这是一个非常严肃的安全隐患。如果你只是在内网用,可以接受;如果要在公网访问,一定要做两层防护。第一层,不要让 go2rtc 直接监听公网端口,Docker 端口映射写
127.0.0.1:1984:1984
,让服务只对本机开放;第二层,在前面架一个带 TLS 和认证的反向代理,比如 Nginx Basic Auth、Authelia 这类工具。我自己的习惯是内网直接访问,远程场景统一走带身份认证的反向代理入口。摄像头本身也建议尽快改掉默认密码,并把不必要的 ONVIF、RTMP 服务关掉,减少暴露面。
7. 嵌入更大系统:给 NVR 供流、接入 Home Assistant 与性能维护
7.1 把 go2rtc 里的流再转给 NVR 和录像机
go2rtc 不只是给浏览器看,也可以作为 NVR 的上游。在你的录像机或者 NVR 里添加“自定义流媒体”通道时,地址填
rtsp://go2rtc所在主机IP:8554/down_hall
,NVR 就可以从 go2rtc 拉流进行录制和显示。这样做的收益是录像机和摄像头之间不再需要逐个建立直连,所有取流逻辑统一集中在 go2rtc 上,摄像头的并发压力也会小很多。需要注意的是,NVR 对 RTSP 路径比较敏感,如果填完发现不通,先在 VLC 里用同一个地址验证,排除路径拼写问题。
7.2 与 Home Assistant 联动
go2rtc 在 Home Assistant 社区很流行,很多人直接把它作为摄像头接入层。它提供自定义集成能力,也可以在 Home Assistant 的 WebRTC Camera 卡片里直接输入 go2rtc 的流地址,实现低延迟页面展示。我并没有把每路流都做进自动化,只是把 go2rtc 暴露的流地址交给 HA 的仪表盘,使用时直接拖卡片,配置过程很轻。如果你已经有一套 HA 在跑,这一步属于低成本高收益的整合。
7.3 性能优化的大方向:能不转码就不转码
性能方面最核心的一条经验:保持默认的中转模式,不轻易开转码。H.264 就能看的情况下,绝对不为显示效果强行转 H.265。大路数场景最好把主码流和子码流分开,预览用子码流,录像用主码流,这样一个页面同时显示八路画面时负载会低很多。当摄像头数量超过十路,我建议用目录划分或者命名前缀管理 streams,比如
hall_main
、
hall_sub
,避免配置越来越难维护。
7.4 升级、备份与长期维护
go2rtc 升级很快,但升级本身压力不大,因为配置只有一个 YAML 文件。我每次升级前都会先
docker compose pull
,把旧配置备份到一个
backup
目录,再执行
docker compose up -d
。启动后看一遍日志和
/api/streams
,确认所有流的状态正常。如果某个版本引入的行为变化比较大,回滚也很容易,直接把镜像 tag 改回上一个版本重新 up 就行。长期运行下来,这个服务基本不需要额外维护,我唯一要做的只是偶尔清理一下日志,以及定期检查摄像头固件和 go2rtc 版本。
对我来说,go2rtc 现在就像是一个流媒体的统一入口,不管后面接几只摄像头,浏览器打开就是画面。如果你也手里一堆不同协议的设备,按这套部署流程走一遍,十分钟左右应该就能把第一路流跑起来。
更多推荐

所有评论(0)