上个月帮朋友整理家里和门店的监控,一共 8 台摄像头,品牌横跨海康、大华和两台叫不上名字的小厂机。真正麻烦的从来不是画面质量,而是接入方式五花八门:有的给 RTSP 地址,有的只提 ONVIF,有的配套 App 导出来的是一个 HLS 播放链接。一开始我想靠 NVR 统一管理,结果老设备协议对不上,新设备又封闭得一塌糊涂,最后只能自己动手搭流媒体服务。最终定下来的是 go2rtc + Docker 这套组合——一个轻量级多协议流媒体网关,专治摄像头"各说各话"的问题。这篇文章完整记录了我的部署过程、配置思路和踩过的坑,适合手里有不同品牌摄像头、想统一接入并让浏览器直接看流的人。

如果你只是想临时开一个流给 VLC 看,那 FFmpeg 一条命令就能解决。但如果想把家里的摄像头统一管理,让手机、电脑、甚至 Home Assistant / Frigate 都能同时访问,那 go2rtc 这种带状态、带 Web 管理界面、带多协议出口的工具才是正解。

1. 摄像头接入为什么这么乱:先看协议割裂的本质

1.1 摄像头不是"给个 IP 就能拉流"

很多新手拿到摄像头后的第一反应是:设备在局域网里,IP 也知道了,直接拉流不就行了?真不是。摄像头确实普遍支持 RTSP,但这个"支持"只是说它开放了一个 RTSP 端口,具体路径、认证方式、码流编号,每家都不一样。

以最常见的两家为例:

  • 海康威视大部分型号的主码流地址形如 rtsp://admin:密码@IP:554/Streaming/Channels/101 ,子码流则是把结尾的 101 换成 102 。其中 1 是通道号, 01 是码流类型。
  • 大华不少型号的地址是 rtsp://用户名:密码@IP:554/cam/realmonitor?channel=1&subtype=0 , subtype=0 是主码流, subtype=1 是子码流。

这两条路径没有任何统一规律。更别提有些云台相机、门铃、低功耗电池摄像头,根本不给你 RTSP,只允许通过厂商 SDK 或者 App 拉流,有的甚至只提供一个 HLS 的 .m3u8 播放链接。

所以"接入摄像头"这件事,本质上不是在和设备通信,而是在跟各种私有协议打交道。

1.2 go2rtc 做的是协议翻译,而不是录像

go2rtc 这个名字容易让人误以为它只是"把 RTSP 转成 WebRTC"。实际上它做的事情更像一个协议翻译器:上游支持 RTSP、RTMP、HLS、MJPEG、HTTP-FLV、ONVIF 等输入,下游又能以 WebRTC、HLS、MSE、RTSP、RTMP、FLV 等方式输出。也就是说,无论你的摄像头源是哪种格式,你都能用一套统一的地址来访问。

而且它非常轻。go2rtc 是 Go 写的单二进制程序,Docker 镜像很小,内存占用通常只有几十 MB,树莓派、NAS、软路由上都能跑。它不是一个录像系统,而是流媒体接入和分发层。传统的"NVR 录完再看"模式太笨重,go2rtc 的思路是"先把流接进来,再按需分给浏览器、录像机、AI 分析程序"。它可以只维持一路 RTSP 连接,同时复制给多个消费者,避免摄像头被多路拉流拖死。

1.3 和传统方案放一起看差距在哪

方案 优点 缺点
传统 NVR 集中管理、本地录像、报警事件清晰 对协议封闭,新品牌/杂牌摄像头接入困难,扩展性差
FFmpeg 临时转发 灵活,能转码,能自定义几乎一切 无状态、无管理界面,多路时长按脚本维护不现实
go2rtc 多协议适配、自带 Web UI、浏览器原生播放、低延迟 不负责录像,需要搭配 NVR/Frigate 之类使用

我最后选择 go2rtc,就是因为它能把"接入"和"使用"解耦。摄像头只需要告诉我它支持什么协议,GO2RTC 负责把它变成一个统一的内网流媒体服务,剩下的事交给浏览器、Frigate 或者任何支持标准协议的软件。

2. 部署前的关键决策:镜像、目录、网络模式

2.1 镜像选型:认准官方来源

go2rtc 的 Docker 镜像在 Docker Hub 上的名字是 alexxit/go2rtc 。这个项目在 GitHub 上是开源的,作者的账号就是 alexxIT,所以认准这个来源就好,不要随便拉第三方重新打包的镜像,尤其是那些加了"增强版""汉化版"标签的,容易夹带私货。

镜像 Tag 方面,第一轮测试可以用 latest ,但落到正式环境我建议固定到一个具体版本号,比如 alexxit/go2rtc:1.9.7 。原因很简单:这类工具迭代很快,配置文件字段、API 路径都可能变,固定版本能避免"昨天还能跑,今天拉新镜像后配置失效"的情况。

2.2 目录规划:配置文件一定要挂载出来

go2rtc 容器启动时会去读取配置文件,默认路径一般在 /config/go2rtc.yaml 。所以部署前先把宿主机目录建好,把配置挂进去,而不是每次进容器里改文件。

mkdir -p /opt/go2rtc/config
touch /opt/go2rtc/config/go2rtc.yaml

在 Windows / macOS 上用 Docker Desktop 的话,要注意 bind mount 的路径共享设置。Windows 下我习惯把配置放到 D:\docker\go2rtc\config ,然后在 Docker Desktop 的 Settings -> Resources -> File Sharing 里把 D 盘加进去,否则挂载卷会不生效。

2.3 网络模式与端口划分

go2rtc 默认监听 1984 端口,这个端口同时承载 Web 管理界面、API 和大部分流的入口。如果要用 WebRTC 在浏览器里低延迟看流,还需要 UDP 端口 8555 作为媒体传输通道。

我给出的端口规划如下:

端口 协议 作用
1984 TCP Web 管理界面、API、RTSP/HLS/MSE 等流的入口
8555 UDP WebRTC 媒体数据
8555 TCP 部分网络环境下 WebRTC 的兜底通道

网络模式这里有个选择。如果 go2rtc 跑在 Linux 服务器上,且只服务内网,我会推荐直接用 network_mode: host ,让容器共享宿主机网络栈,WebRTC 的 UDP 端口不会因为 Docker 的 NAT 规则出问题。缺点是不能再用 Docker 的端口映射和网络隔离,端口占用要自己管理。

如果跑在群晖、威联通这类 NAS 上,或者你想保持桥接网络的结构,那记得同时映射 TCP 和 UDP 的 8555 端口,只映射 TCP 会导致 WebRTC 画面经常卡住。

3. Docker 部署实录:从空目录到第一路流跑起来

3.1 先用 docker run 快速验证

第一次跑的时候,我不建议上来就写 compose 文件,先用一条 docker run 验证环境是否正常、镜像是否能拉取、配置文件路径是否正确。

docker run -d \
  --name go2rtc \
  --restart unless-stopped \
  -p 1984:1984 \
  -p 8555:8555/udp \
  -p 8555:8555/tcp \
  -v /opt/go2rtc/config:/config \
  -e TZ=Asia/Shanghai \
  alexxit/go2rtc:1.9.7

启动后访问 http://服务器IP:1984 ,能看到 go2rtc 的 Web 界面就说明容器基本没问题。注意第一次启动时配置文件是空的,界面上自然也没有任何流,这不代表部署失败。

3.2 确认没问题后再固化 docker compose

docker run 命令太长,而且参数不容易维护。确认基本可用之后,我立刻换成了 docker compose。下面是我当时的 docker-compose.yml :

services:
  go2rtc:
    image: alexxit/go2rtc:1.9.7
    container_name: go2rtc
    restart: unless-stopped
    environment:
      - TZ=Asia/Shanghai
    ports:
      - "1984:1984/tcp"
      - "8555:8555/udp"
      - "8555:8555/tcp"
    volumes:
      - /opt/go2rtc/config:/config

启动命令就是:

docker compose up -d
docker compose logs -f go2rtc

日志里如果没有任何报错,说明服务正常。之后每次修改配置文件,我都是直接 docker compose restart go2rtc ,简单粗暴但可靠。

3.3 写入第一路摄像头流

在 /opt/go2rtc/config/go2rtc.yaml 里写入最简单的配置:

streams:
  living_room: "rtsp://admin:your_password@192.168.1.64:554/Streaming/Channels/101"

然后重启容器。再打开 Web 界面,点一下 living_room 这个流,如果能看到画面,说明第一个摄像头已经接入成功。

3.4 用 API 验证状态

Web 界面能看之外,go2rtc 还提供了一个很实用的 JSON API。在服务器上执行:

curl http://127.0.0.1:1984/api/streams

返回内容里能看到各个流的状态、已连接客户端数量、延迟指标。这排错时特别好用,比反复刷新界面靠谱得多。我第一次接入时画面黑屏,就是通过这个接口发现流状态是 failed ,才定位到是摄像头密码里的特殊字符没转义。

4. streams 配置详解:不同摄像头怎么填

4.1 三类常见摄像头的 URL 写法

go2rtc 的配置文件核心就一个段落: streams 。每个流的名字对应一个 URL,格式是 名字: 地址 。

我把我实际遇到过的三类设备整理如下:

设备类型 配置示例
海康威视 hik_main: "rtsp://admin:pass@192.168.1.64:554/Streaming/Channels/101"
大华 dahua_1: "rtsp://admin:pass@192.168.1.66:554/cam/realmonitor?channel=1&subtype=0"
通用 RTSP 设备 generic: "rtsp://user:pass@192.168.1.67:554/onvif1"
RTMP 推流源 gamecam: "rtmp://192.168.1.80/live/cam_a"
云平台 HLS 链接 cloud_cam: "https://example.com/live/abc/index.m3u8"

注意大华 URL 里有 ? 和 & 这种特殊字符,YAML 里一定要用双引号把整个地址包起来,否则解析时会报错,或者在容器日志里看到"cannot parse stream"一类的问题。

4.2 认证信息里的特殊字符是头号坑

摄像头密码千奇百怪,很多还强制要求大小写加特殊符号。一旦密码里出现 @ 、 : 、 / 、 ? 这些字符,直接写在 RTSP 地址里,URL 解析就会出问题。因为 rtsp:// 后面第一个冒号是分隔用户名和密码的,第一个 @ 是分隔认证信息和 IP 的,它们都已经被占用了。

解决方法是做 URL 编码。比如密码是 Abc@123 ,那么写到配置里就要变成 Abc%40123 , @ 的 URL 编码就是 %40 。

streams:
  gate: "rtsp://admin:Abc%40123@192.168.1.64:554/Streaming/Channels/101"

类似地, : 编码成 %3A , / 编码成 %2F , ? 编码成 %3F 。踩过一次坑后我总结出一个习惯:所有密码统一先做 URL 编码再填进配置,避免排查半天。

4.3 多路摄像头和主/子码流设计

一个摄像头通常有主码流和子码流。主码流分辨率高、码流大,适合本地录像;子码流分辨率低、带宽占用小,适合手机远程预览。在 go2rtc 里可以分别配成两个流:

streams:
  hik_main: "rtsp://admin:pass@192.168.1.64:554/Streaming/Channels/101"
  hik_sub: "rtsp://admin:pass@192.168.1.64:554/Streaming/Channels/102"

这样做的好处是:浏览器端默认看子码流,流畅且不占带宽;录像或 AI 分析系统需要高清画面时再去拉主码流。分工明确之后,一个摄像头只需要让 go2rtc 维持两条 RTSP 连接,就能满足所有下游消费者,而不是每个客户端各拉一次,把摄像头连接数直接打满。

5. 浏览器播放与对外分发:延迟、端口和自然网络问题

5.1 三种播放协议怎么选

go2rtc 最有吸引力的地方,就是可以在浏览器里直接看流,不需要装 VLC、不需要插件。它支持 WebRTC、MSE、HLS 三种主要播放方式,延迟差别很大。

播放方式 延迟 适用场景
WebRTC 0.3~1 秒 实时预览、语音对讲这类低延迟场景
MSE 1~3 秒 浏览器原生支持,兼容性较好
HLS 5~10 秒 跨网络、弱网环境播放,兼容性最好

如果你是在局域网内看监控,直接选 WebRTC 就对了,画面基本实时。如果摄像头源是 H.265 编码,而浏览器不支持硬解,那 WebRTC 可能黑屏,这时候可以先切到 HLS 试试,或者让摄像头输出 H.264 的子码流。

5.2 UDP 8555 不通是画面卡顿的元凶

我遇到过好几次"点了播放,画面卡在加载一秒后就不动"的情况。排查到最后,十有八九是 WebRTC 的 UDP 端口没有打通。

WebRTC 的协商是走 1984 端口的,但真正的视频数据走的是 UDP 8555。很多路由器或 NAS 防火墙默认只放行了 TCP,没有放行 UDP,于是浏览器能拿到流信息,却收不到媒体数据。所以在部署时, 8555/udp 的映射和防火墙放行一定要确认。

如果是跨网段访问,比如在公司看家里的摄像头,光在 Docker 里映射端口还不够,你还需要在路由器上做端口转发,并且到 go2rtc 的 WebRTC 配置里手动指定一个外部可达的 candidate 地址。这块配置我没法直接抄给你,因为每个网络环境不一样,但排查思路是固定的:先确认 UDP 8555 通不通,再考虑 NAT 问题。

5.3 顺手把流喂给 Home Assistant 和 Frigate

go2rtc 和 Home Assistant / Frigate 的配合非常紧密。Frigate 新版本内置了对 go2rtc 的支持,配置里可以直接把 go2rtc 作为它的视频源,利用 go2rtc 已经建好的 RTSP 连接,避免 Frigate 每次检测时重新去摄像头拉流。

Home Assistant 里如果安装了 go2rtc 组件,摄像头实体可以直接指向 go2rtc 里的流名,画面加载速度和稳定性比我之前用通用 RTSP 集成强很多。即使你没有这两个系统,把 go2rtc 当成一个中转站,让手机 App、Web 页面、录像软件各自通过标准协议取流,也比让每个系统都直连摄像头更省资源。

6. 我踩过的坑和最后留下的配置

6.1 摄像头凌晨掉线后不再自动恢复

一台老海康摄像头偶尔会在凌晨断流,go2rtc 会显示 failed 状态。一开始我以为重启 go2rtc 就能解决,后来发现真正原因是这台摄像头限制了 RTSP 连接数量,旧的连接被设备端踢掉后,go2rtc 没有及时重连。

解决方法是把不必要的下游客户端断开,尽量让 go2rtc 成为唯一的摄像头拉流方。如果还有问题,就在 go2rtc 的日志里看是不是有 too many sessions 之类的提示,这通常是摄像头并发连接数被占满了,而不是 go2rtc 配置写错。

6.2 H.265 摄像头在浏览器里直接黑屏

有一台新的摄像头默认输出 H.265,局域网里用 VLC 看没问题,但浏览器打开 go2rtc 里的流时全是黑屏。这是因为 Chrome/Firefox 对 H.265 硬解的支持一直很暧昧,不是所有系统都能直接解。

我的处理方式不是转码,而是去摄像头后台把子码流编码改成 H.264。反正手机和浏览器预览用子码流就够了,主码流继续 H.265 给录像系统用。这样既不增加 go2rtc 的负载,又解决了浏览器播放问题。如果摄像头不支持双码流或者不能改编码,那才需要考虑用带 ffmpeg 的镜像做转码,但那会明显增加 CPU 占用,不要把转码当默认方案。

6.3 容器时区错误导致日志时间对不上

排查半夜断流问题时,我发现 go2rtc 日志里的时间戳比北京时间慢了 8 个小时,对问题时特别痛苦。这是因为容器默认 UTC 时区。

在 compose 文件里加一行环境变量就好:

environment:
  - TZ=Asia/Shanghai

加上之后重启容器,日志时间就正常了。这个坑很小,但在凌晨排障时特别影响判断。

6.4 我最后留下的 go2rtc.yaml 参考

一套跑得比较稳的配置大概长这样:

streams:
  living_room: "rtsp://admin:Living%40Room@192.168.1.64:554/Streaming/Channels/101"
  yard_sub: "rtsp://admin:Yard%40123@192.168.1.65:554/Streaming/Channels/102"
  dahua_main: "rtsp://admin:Dahua%40123@192.168.1.66:554/cam/realmonitor?channel=1&subtype=0"

所有密码都提前做了 URL 编码,流名都用了小写英文字母和下划线,没有空格,避免在 API 路径里出现转义问题。配合前面那份 compose 文件, docker compose up -d 之后,所有摄像头就统一接入到一个多协议流媒体平台里了。

这套配置我跑了几个月,日志里基本没有拉流失败记录。唯一要提醒的是,如果以后要升级 go2rtc 版本,先备份 go2rtc.yaml ,再检查新版本的 changelog,看看配置格式有没有变化。摄像头接入这件事本身不难,难的是把各种私有协议统一到一个维护成本可控的体系里,go2rtc 目前是我用过最省心的一个。

Logo

邀请您加入社区

更多推荐