家里摄像头一多,最先崩溃的往往不是硬盘,而是你的客户端列表。海康要用它自家的工具,大华要装一套,萤石又得开一个 App,桌面上想用浏览器看监控,还得忍受各种插件提示。我今天写的这套东西,就是把这一堆乱七八糟的入口收拢到同一个地方:go2rtc。它是一个用 Go 写的轻量级流媒体服务,专门解决摄像头多协议接入和低延迟播放的问题,RTSP、RTMP、ONVIF、MJPEG 这些输入统统能接,对外输出 WebRTC、MSE、HLS、MP4 等格式。配合 Docker 部署,几百兆内存就能把家里、店里、仓库里的摄像头统一管起来,浏览器直接看实时画面,延迟能压到 0.2 到 0.5 秒。这篇文章适合所有被摄像头生态折腾过的人,不管是家用看护、门店安防,还是搞 Home Assistant、Frigate 这类智能家居集成,按着步骤走都能跑起来。

1. 先把 go2rtc 是什么说清楚

1.1 摄像头圈子的协议乱象

做过摄像头接入的人都知道,这个行业最不缺的就是“标准”。RTSP 名义上是标准协议,但各家实现的细节千差万别:海康的 RTSP 路径长这样,大华的却带 query 参数,TP-LINK 又是另一套。更别提还有 HLS、RTMP、MJPEG、私有云协议这些东西。生活里打个比方,这就像每家充电口都不一样,你得备一抽屉的转接头。

这种割裂直接带来两个问题。第一,你买的摄像头越多,要装的 App 和客户端就越多,今天这个弹升级,明天那个要激活,烦不胜烦。第二,你想把这些视频流统一喂给其他系统,比如 Home Assistant、Frigate,或者一个自建的监控平台,光是搞明白每个摄像头的拉流地址就要折腾半天。我见过不少人买回来的摄像头,因为 App 难用,最后吃灰的都有。

go2rtc 就是冲着这个痛点来的。它不生产视频,也不做录像(这点后面细说),它做的就是“接进来,转出去”:把不同品牌、不同协议的摄像头统一接进来,再用统一的地址输出给浏览器、App、NVR 或者其他平台。

1.2 go2rtc 凭什么解决这个问题

go2rtc 的作者是智能家居圈挺有名的开发者,这项目一开始就是给 Home Assistant 社区用的,后来因为太好用,被 Frigate 等好几个项目直接内置了。它本身是一个用 Go 写的单一可执行文件,资源占用非常低,跑在树莓派、NAS、老电脑上都毫无压力。

它有几个关键特性,我实测下来觉得是真的值:

第一, 低延迟 。它默认走 WebRTC 和 MSE,画面从摄像头到你浏览器基本是 0.2 到 0.5 秒的延迟。用过普通 HLS 流的朋友都知道,那种 3 到 10 秒的延迟在监控场景下有多不能忍,人都走过去了画面才出来,那还看什么。

第二, 不转码,只搬运 。go2rtc 默认做的是流媒体的 remux,也就是把协议格式转换一下,不重新编码视频。这意味着它对 CPU 的压力极小,不会为了转码去吃满你的处理器,也不会拖垮跑在同一个 NAS 上的其他服务。

第三, 配置极简 。核心配置就一个 YAML 文件,一个流一行地址,改完保存自动热加载,不需要每次改个密码就重启容器。这对经常要加摄像头、删摄像头的人太友好了。

第四, 生态成熟 。Home Assistant 官方插件仓库里有它,Frigate 的实时画面也靠它,网上的教程和踩坑记录非常丰富,遇到问题基本都能搜到解决方案。

2. Docker 部署前的准备与配置模板

2.1 部署环境怎么选

go2rtc 本身是个非常轻的东西,官方在 Docker Hub 上的镜像叫 alexxit/go2rtc ,支持 amd64、arm64、armv7 这些主流架构。所以部署环境其实很随意,我见过有人在群晖、飞牛这类 NAS 上跑,有人在树莓派上跑,也有人直接扔在一台装了 Ubuntu 的老笔记本上。选环境就两个原则:一是要能装 Docker,二是要跟你的摄像头网络能通。

这里要提醒一句,摄像头和 go2rtc 最好在同一个局域网里,或者至少有稳定的路由可达。跨网段、跨公网拉流不是不行,但延迟和稳定性都会差很多,尤其是 Wi-Fi 摄像头,跨网以后画面卡顿会非常明显。如果你家里网络环境比较复杂,有多 VLAN 或者多网段,记得先确认 docker 容器所在的那个网段能访问摄像头的 IP。

内存方面,go2rtc 本身体积小,跑几十路流也就占两三百兆内存,真正吃带宽的是视频码流本身。摄像头主码流动辄 4Mbps 起步,如果你只是想预览、报警,强烈建议用子码流接入,带宽和存储压力都会小很多。这个具体到配置阶段我再展开。

2.2 先生成一份最小可用的配置文件

go2rtc 启动后会读取 /config/go2rtc.yaml 这个文件,所以我们需要在挂载目录里先建一个。第一次部署别贪多,先把最核心的三段配置写上,跑通了再加摄像头。

log:
  level: info

api:
  listen: ":1984"

rtsp:
  listen: ":8554"

webrtc:
  listen: ":8555/tcp"
  ice_servers:
    - urls:
        - stun:stun.l.google.com:19302

streams:
  hik_living:
    - rtsp://admin:密码@192.168.1.64:554/Streaming/Channels/101

逐段解释一下。 api 这一段是 go2rtc 的 Web 管理界面和 API 服务,默认监听 1984 端口,你部署完直接用浏览器打开 http://设备IP:1984 就能看到控制台。 rtsp 这一段是它对外提供的 RTSP 服务,也就是说所有接进来的流,go2rtc 都会在 8554 端口上重新暴露成一个标准的 RTSP 地址,方便给 NVR、ffmpeg 这些工具消费。

webrtc 这一段比较关键。WebRTC 是 go2rtc 低延迟播放的核心,它需要占用一个 UDP/TCP 端口来做媒体传输,这里填 8555。 ice_servers 里配的是 STUN 服务器,作用是在跨网段、有 NAT 的情况下帮浏览器找到 go2rtc 的可用地址。局域网里用其实多数时候用不上,但留着没坏处。

最后是 streams ,这里定义的就是你要接的摄像头。每个摄像头起一个名字,名字下面写它的拉流地址。go2rtc 会以一个 daemon 进程去后台拉流,多个客户端同时看同一路时,也只会维持一条上游连接,这对摄像头的并发压力非常友好。

2.3 用 docker-compose 一次到位

配置文件准备好之后,部署就很简单了。我更推荐用 docker-compose,因为配置清晰、后续好维护,尤其是以后想加其他容器一起编排的时候。先建一个项目目录:

mkdir -p /opt/go2rtc/config

把上面那份 YAML 保存成 /opt/go2rtc/config/go2rtc.yaml ,然后写一个 docker-compose.yml :

services:
  go2rtc:
    image: alexxit/go2rtc
    container_name: go2rtc
    restart: unless-stopped
    ports:
      - "1984:1984"
      - "8554:8554"
      - "8555:8555/tcp"
      - "8555:8555/udp"
    volumes:
      - ./config:/config

目录里执行 docker compose up -d ,等一两分钟,浏览器打开 http://你的IP:1984 ,能看到控制台界面就算成了。

如果你不习惯 compose,用 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 \
  alexxit/go2rtc

这里有个取舍要说明一下,就是 network_mode 。上面的配置用的是 bridge 模式加端口映射,好处是对 Docker Desktop(Windows/macOS)友好,坏处是如果是跨网段或者涉及组播、ONVIF 自动发现,可能会不通。如果你是在 Linux 的 NAS 或者树莓派上部署,摄像头也在同一个网段,我更建议直接用 network_mode: host ,让容器直接共享宿主机的网络栈,省掉端口映射那一层,也少很多网络上的怪问题。

注意:如果你把配置放在宿主机上,发现修改后长时间没生效,先看看 go2rtc 的日志。大部分情况下它支持热加载,但某些字段改了之后需要重启容器才可靠。另外如果发现拉取 alexxit/go2rtc 镜像很慢,多半是网络链路问题,可以换个时间重试,或者给 Docker daemon 配置 registry mirror。

3. 摄像头接入:从 RTSP 到品牌私有协议

3.1 手把手配置一个 RTSP 摄像头

绝大多数专业安防摄像头都支持 RTSP 拉流,只是地址格式不一样。这是接入 go2rtc 最主流的方式。你只需要知道三个信息:摄像头的 IP、用户名密码、RTSP 路径。

拿海康举例,海康摄像头的 RTSP 地址一般长这样:

rtsp://用户名:密码@IP地址:554/Streaming/Channels/101

路径最后的三位数有讲究: 101 代表第一路通道的主码流, 102 代表同一通道的子码流。如果你有多个通道,那第三位数字还会变成 201 、 202 。新固件海康还有一种简写形式, /h264/ch1/main/av_stream ,代表通道 1 主码流。

那么问题来了:怎么找到摄像头的 IP?最笨也最稳的办法是登录路由器的 DHCP 客户端列表,找到你摄像头的 MAC 和 IP。也可以用厂家提供的设备搜索工具,比如海康的 SADP、大华的 ConfigTool。这些工具还能顺便看到摄像头是否处于未激活状态,这个在下一小节说。

拿到 IP 之后,我强烈建议先用电脑上的 VLC 或者 ffprobe 验证一下地址对不对,再把地址填进 go2rtc。因为排错最怕的是配置填错了,结果锅让 go2rtc 背。用 ffprobe 验证的命令长这样:

ffprobe -rtsp_transport tcp -v error -show_entries stream=codec_name,width,height \
  -of default=noprint_wrappers=1 "rtsp://admin:密码@192.168.1.64:554/Streaming/Channels/101"

能正常输出视频流信息,说明地址、账号、路径全都没问题,再把这个地址原样复制到 go2rtc 的 YAML 里。

3.2 海康、大华等品牌摄像头的接入细节

不同品牌的 RTSP 路径差异很大,我把常见的几个整理成了表,方便你对照自己的摄像头查:

品牌 主码流地址 子码流地址
海康(Hikvision) rtsp://user:pass@ip:554/Streaming/Channels/101 /Streaming/Channels/102
大华(Dahua) rtsp://user:pass@ip:554/cam/realmonitor?channel=1&subtype=0 ...&subtype=1
TP-LINK rtsp://user:pass@ip:554/stream1 /stream2
宇视(Uniview) rtsp://user:pass@ip:554/MediaInput/ch1/main /ch1/sub
Reolink rtsp://user:pass@ip:554/h264Preview_01_main /h264Preview_01_sub

用这些地址的时候有几点要非常注意。

第一, 大华的新摄像头默认是未激活状态 。刚买回来的大华摄像头,插上网线后默认 IP 是 192.168.1.108,但你不激活账号,RTSP 是绝对拉不动的。需要在电脑上装官方 ConfigTool,把摄像头激活并设置密码,之后才能用 admin 和这个密码去拉流。绕过了这一步,后面所有配置都是白搭,这是我在帮朋友配大华时踩过最深的坑。

第二, 海康的老摄像头登录页面在 Windows 10/11 上经常打不开 ,因为官网还在用 IE 插件。但这不是大事,RTSP 服务是独立于 Web 管理界面的,只要你记得摄像头 IP 和密码,浏览器打不开管理页不影响 go2rtc 拉流。很多老监控点位的海康摄像头,其实都是这么苟活的:Web 管不了,但流一直能出来。

第三, 密码里有特殊字符一定要做 URL 编码 。比如密码是 Abc@123 ,里面的 @ 必须写成 %40 ,否则 RTSP 地址会解析错误,因为 @ 在 URL 里是用户名和地址之间的分隔符。同理, # 、 ? 、 % 这些也建议编码,别省这一步,不然排查起来怀疑人生。

另外萤石(EZVIZ)这类云摄像头,虽然主打云平台,但部分型号可以在 App 里开启本地 RTSP 功能,开启之后会显示一个局域网地址,也能用 go2rtc 接进来。这种接法有个好处,本地录像和预览不再受云服务影响,设备断网了只要局域网还在,流就能看。如果你手里正好有一批萤石摄像头想接到飞牛NAS或者自建平台,这个方法很值得试。

3.3 多路摄像头的批量接入与多源冗余

摄像头多了以后,配置文件会变得又长又无聊。这时候可以用 YAML 的锚点语法来收敛重复内容,尤其是账号密码一样的摄像头,效果很明显:

cameras_auth: &cam_auth
  username: admin
  password: Passw0rd123

streams:
  hik_gate:
    - rtsp://admin:Passw0rd123@192.168.1.64:554/Streaming/Channels/101
  hik_yard:
    - rtsp://admin:Passw0rd123@192.168.1.65:554/Streaming/Channels/101
  dahua_door:
    - rtsp://admin:Passw0rd123@192.168.1.66:554/cam/realmonitor?channel=1&subtype=0

cameras_auth 这种写法在 YAML 里叫锚点,实际展开后就是把 username 和 password 复制到引用的位置。go2rtc 的配置里虽然支持这种语法,但 RTSP URL 是整串写的,锚点用起来反而绕,我实际项目中更倾向于直接复制粘贴地址,然后靠命名规范来管理。比如统一用 品牌_位置_用途 的格式: hik_gate_main 、 dahua_door_sub ,一眼就能看懂。

还有一个小众但好用的功能: 多源冗余 。go2rtc 允许一个流名下面写多个地址,它会在第一个源拉不通时自动切换到下一个。比如你有个摄像头同时支持 RTSP 和 RTMP,或者你做了双链路备份,就可以这样写:

streams:
  gate_cam:
    - rtsp://admin:pass@192.168.1.64:554/Streaming/Channels/101
    - rtmp://192.168.1.64:1935/live/gate

这在网络不稳定的环境里非常有价值。我有个朋友在仓库部署过一套,主链路是 Wi-Fi,时不时掉包,加了 RTMP 备源之后,断流时间从十几秒缩短到基本无感。

4. 多协议输出:让每个终端都能看

4.1 浏览器直接看:MSE 与 WebRTC

go2rtc 部署好之后,你打开它的 Web 控制台(1984 端口),就能看到所有配置的流。点击任意一个流,浏览器会直接开始播放。这里底层用的是 MSE(Media Source Extensions)和 WebRTC,不需要装任何插件。

MSE 是浏览器原生的能力,Chrome、Edge、Safari 都支持,播放体验稳定,延迟大概在 0.5 到 1 秒左右。WebRTC 的延迟更低,能压到 0.2 到 0.5 秒,而且 go2rtc 还做了回音检测,如果你的流同时被好几个浏览器看,它会自动协调,避免带宽爆炸。实际测试里,WebRTC 的体验是最接近“直连摄像头”的,人从画面里走过去,几乎是同步的。

这里要给初次用的人提醒一下: 访问控制台的时候,要用 http 协议,并且保证浏览器能直连到 go2rtc 的 IP 。如果你反代了域名、加了 HTTPS,也能用,但 WebRTC 对端口和 UDP 的要求比较严格,反代配置不对经常会导致画面加载不出来。我的建议是,先在内网用 IP:1984 直接访问,跑通以后再谈反代。

延迟方面,不同的输出方式差距很大,我整理了一张表:

输出方式 延迟 适合场景
WebRTC 0.2 - 0.5 秒 实时监控、门铃、对讲
MSE 0.5 - 1 秒 浏览器多路预览
MP4(fmp4) 1 - 2 秒 移动端、兼容性优先
HLS 3 - 10 秒 外网分享、跨平台稳定播放

4.2 对接 Home Assistant / Frigate / 其他平台

go2rtc 真正的价值,在于它是整个智能家居监控体系的“中间层”。接 Home Assistant 是最常见的玩法。Home Assistant 官方仓库里就有 go2rtc 的插件,装完之后在 HA 里就能直接看到流,并可以把任意一路摄像头转成摄像头实体,用于自动化、报警联动。如果你是手动配置,也可以在 YAML 里指向 go2rtc 的流地址,写法很简单:

camera:
  - platform: generic
    name: 客厅监控
    stream_source: http://192.168.1.10:1984/api/stream.mse?src=hik_living

Frigate 用户更省事。Frigate 从 0.13 开始内置了 go2rtc 作为实时流代理,你甚至不需要单独装 go2rtc,直接在 Frigate 的配置里填摄像头 RTSP 地址,Frigate 就会自动拉起 go2rtc 来负责实时预览。如果你已经有独立部署的 go2rtc,Frigate 也能直接消费它的流。

另一个典型场景是给传统 NVR 或者录像软件提供服务。go2rtc 在 8554 端口上暴露的是标准 RTSP,你可以在任何支持 RTSP 的录像软件里,填 rtsp://go2rtc地址:8554/流名称 来拉流。这样即使摄像头品牌不同、协议不同,你的录像软件只需要面对 go2rtc 一个出口就行了,彻底解决“这个 NVR 不支持那个品牌摄像头”的兼容性问题。

4.3 输出格式与协议选择建议

选输出方式的时候,没有银弹,全看场景。

如果你只是自己内网预览,无脑选 WebRTC,延迟最低、体验最好。如果你要把流分享给别人,比如同事看店、家人看家,而且对方可能在不同网络环境下用手机打开,那就用 HLS,兼容性最广。如果你要接一个不支持 MSE/WebRTC 的老系统,MP4 或者 RTMP 往往是更稳妥的选择。

RTMP 输出要特别说一下。go2rtc 支持把一路流以 RTMP 方式转发出去,这个在对接第三方直播平台或者自建流媒体服务时很有用。比如你有一家店,想让某个平台做直播监控,就可以让 go2rtc 把摄像头的流重新以 RTMP 推到指定的收流地址。因为 go2rtc 不转码,所以推出去的流画质就是摄像头原始画质,CPU 依旧几乎为零。

5. 实操中的坑与排查思路

5.1 摄像头断流与自动恢复

摄像头断流是不可避免的,不管是设备重启、网络波动还是供电问题。go2rtc 本身有自动重连机制,上游断了它会一直重试,恢复后自动接上。但这个机制有个前提:你的拉流地址要能很快地“失败”。有些摄像头在 Wi-Fi 信号弱的情况下,RTSP 连接会挂在半死状态,既不成功也不失败,go2rtc 就只能干等。这种情况下,拉流地址后面加一个传输方式参数会管用很多:

streams:
  gate_cam:
    - rtsp://admin:pass@192.168.1.64:554/Streaming/Channels/101?rtsp_transport=tcp

rtsp_transport=tcp 的意思是强制走 RTSP over TCP,而不是默认的 UDP。在 Wi-Fi、跨交换机这种丢包率高的环境里,TCP 虽然会多一点延迟,但稳定性会好非常多。我自己的经验是: 凡是走 Wi-Fi 的摄像头,全部强制 TCP,省心 。走网线尤其是 PoE 供电的摄像头,UDP 通常没问题,但改成 TCP 也无伤大雅。

注意:PoE 供电的摄像头如果频繁掉线,八九成是供电不稳。别一上来就怀疑 go2rtc,先看摄像头是不是在周期性重启,检查交换机 PoE 功率余量。软件层面做得再完美,也救不了电压不够的硬件。

5.2 延迟高、花屏、打不开

延迟高的问题,十有八九是用错了输出方式。很多人一开始图省事,把 go2rtc 当成普通流媒体服务器用 HLS,结果发现延迟五六秒,就说 go2rtc 不行。其实它只是把“低延迟”的能力放在 WebRTC/MSE 上,你用 HLS 当然快不了。监控场景里,延迟高一般先看两个地方:浏览器是不是用的 MSE/WebRTC;摄像头那边是不是误接了主码流。主码流分辨率高、带宽大,会显著增加缓冲时间,预览用子码流就够了。

花屏或者画面马赛克,很多时候是 UDP 丢包造成的。这种问题不用犹豫,直接加 rtsp_transport=tcp 。如果加了还花,那就排查从摄像头到 go2rtc 之间的链路,是不是 Wi-Fi 信号差、是不是交换机端口协商到了百兆。有个很容易忽略的点是网线质量,我见过不少工程上用的网线只有四芯通着,百兆都跑不满,高清码流一多就出马赛克。

打不开流的话,先别慌,按这个顺序排查:

  1. 在 go2rtc 控制台看有没有报错日志,上游地址写没写对。
  2. 用 ffprobe 在宿主机上直接拉摄像头的流,确认摄像头本身没问题。
  3. 确认容器端口映射没写错,1984 能打开不代表 8554 的 RTSP 也映射对了。
  4. 如果摄像头在别的容器里共享网络,检查 Docker 网络模式是否冲突。

5.3 调试命令与日志速查

排查问题最常用的命令我给你整理成一个速查表:

目的 命令
查看 go2rtc 日志 docker logs -f go2rtc
查看 API 返回的流状态 curl http://localhost:1984/api
用 ffprobe 测摄像头源 ffprobe -rtsp_transport tcp "rtsp://..."
查看容器端口映射 docker port go2rtc
验证 RTSP 服务端口 nc -zv 127.0.0.1 8554

日志这块我要强调一下,go2rtc 的日志对“流断开”和“连接失败”这两个信息区分得很清楚。如果你看到 connection refused ,多半是 IP、端口或者路径的问题;如果看到 unauthorized 或者 401 ,那就是账号密码不对。有一次我帮人排查,他日志里反复出现 connection reset by peer ,最后发现是摄像头有连接数限制,太多客户端在同时拉流。go2rtc 因为所有客户端共享一条上游连接,正常不会触发这个限制,但如果你绕过 go2rtc 直接用 VLC 反复测试,就很容易把摄像头拉爆。

6. 进阶扩展:一个小型家庭流媒体平台的完整方案

最后分享一个我在家里实际跑过的完整方案。光照和网络条件都算典型,你可以直接参考着搭。

硬件是:一台飞牛NAS(四核 x86,16G 内存)跑 go2rtc 和 Frigate,两路海康 PoE 摄像头接在交换机上,一路大华摄像头走 Wi-Fi,再加上一个树莓派带 CSI 摄像头模块专门对着鱼缸。

go2rtc 负责所有摄像头的实时流转发,配置里全部用子码流接入,地址加 rtsp_transport=tcp ,确保 Wi-Fi 那路大华也能稳定出图。Frigate 消费 go2rtc 的流做运动检测和录像,检测到人推送到 Home Assistant,由 HA 触发手机通知。

如果你也想用 USB 摄像头或者树莓派摄像头模块,go2rtc 也能接。它的 ffmpeg 源可以直接读取 /dev/video0 这类设备文件,然后在容器启动时把摄像头设备挂载进去:

services:
  go2rtc:
    image: alexxit/go2rtc
    devices:
      - /dev/video0:/dev/video0

配置里对应写:

streams:
  nemo_cam:
    - ffmpeg:/dev/video0

这种玩法在智能车视觉、双目摄像头调试这些场景里尤其有用,因为你可以把本地设备统一成一个 RTSP 流,让其他程序通过标准协议去消费,不用再去兼容一堆 SDK。注意不同 go2rtc 版本对 ffmpeg 源的参数略有差异,建议以项目文档为准,第一次跑不通很正常,多看日志多试参数。

录像策略上,我的建议是别让 go2rtc 背这个锅。它是流转发服务,不是录像机。真正需要录像的时候,用 Frigate 按事件录像,或者用下面的命令让系统定时切片:

ffmpeg -rtsp_transport tcp -i "rtsp://127.0.0.1:8554/nemo_cam" \
  -t 3600 -c copy "/mnt/record/nemo_$(date +%Y%m%d_%H%M).mp4"

因为 go2rtc 是转发的,所以 FFmpeg 拉它的流不会给摄像头增加额外负担,同时拉它自己上游的连接也只有一条。

这套方案我前后跑了大半年,最后悔的是没有早点把摄像头统一到 go2rtc 上。原来三天两头要开各种客户端,现在所有画面就在浏览器一个页面里,Home Assistant 的自动化也全部跑通了。如果你也正被各种摄像头客户端搞得焦头烂额,花一个下午照着这篇文章搭一遍,大概率能省下未来很长时间的折腾。

Logo

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

更多推荐