玩摄像头或者智能家居的朋友,大概率都遇到过这种场景:手里一台支持 RTSP 的摄像头,想在厂商 APP、Home Assistant、网页预览之间来回切换,结果要么被厂商协议锁死,要么浏览器不支持 RTSP 播放,折腾半天只能在手机上装专用客户端。其实解决思路很简单:在中间加一个“会翻译的”流媒体网关,把所有摄像头的流统一收进来,再按需输出成浏览器能直接看的格式。这个网关,我用的是 go2rtc,跑在 Docker 里,干净、好升级、迁移也方便。

go2rtc 是一个用 Go 语言写的轻量流媒体中转服务,名字里的“rtc”主要指 WebRTC,但它实际支持的协议远不止这些。它能接收 RTSP、RTMP、HLS、HTTP-FLV、MJPEG 等常见协议输入,也能把它们互相转换输出,最常用的场景就是把摄像头的 RTSP 流转成 WebRTC,让浏览器直接低延时播放。配合 Docker 部署,不用在宿主机装一堆依赖,一条命令就能跑起来,特别适合家庭实验室、小型监控项目、Home Assistant 玩家,以及需要在多个系统之间打通视频流的开发者。

下面我会从选型思路、Docker 环境准备、配置写法、部署步骤、问题排查到长期运行经验,完整过一遍我自己的落地过程。

1. 为什么要用 go2rtc 做摄像头接入

1.1 传统取流方式到底卡在哪

摄像头本身通常只提供 RTSP 或 RTMP 流,浏览器默认不支持这些协议。以前想做网页预览,主流方案是跑一个 Nginx,然后把 RTSP 拉过来转成 HLS,或者用 FFmpeg 推 RTMP 再让 Flash 播放。麻烦点很多:

  • 浏览器兼容问题:RTSP 需要插件或特定客户端,HLS 有 5 到 10 秒延迟,不适合需要实时操作的场景。
  • 协议转换链路太长:RTSP 到 HLS 再到播放器,中间涉及 FFmpeg 拉流、切片、Nginx 分发,配置复杂,出问题不知道查哪一环。
  • 厂商锁定:很多摄像头的私有协议只能在自家 APP 里看,想把视频流接到自己的平台很麻烦。
  • 多路摄像头接入时,手工维护一条条拉流命令,痛苦。

go2rtc 解决的核心问题,就是把这些协议打通。它默认不做转码,只做“转封装”,也就是把视频流从一个容器格式搬到另一个容器格式,所以 CPU 占用很低。摄像头原本是什么编码格式,到浏览器端还是什么格式,画面质量不会因为中转而明显下降。

1.2 go2rtc 的核心能力与协议矩阵

go2rtc 最吸引我的地方,是它对“输入协议”和“输出协议”的处理都很灵活。输入侧,它原生支持 RTSP、RTMP、HLS、HTTP-FLV、MJPEG 等;输出侧,支持 WebRTC、HLS、MJPEG、RTSP、RTMP 等。也就是说,一个源可以同时让不同端去拉,互不影响。

它还有一个很实用的机制:当源是 RTSP 时,go2rtc 会优先尝试用原生 RTSP 客户端去拉流,拉不到或格式不兼容时,它会自动调用内置的 FFmpeg 去拉流。这个“自动回退”机制让它在面对杂牌摄像头时格外省心,不需要你手动去纠结到底用哪种方式取流。

WebRTC 是它最值钱的能力。WebRTC 延迟可以做到几百毫秒,浏览器原生支持,不需要装插件。你可以直接在网页里打开摄像头画面,拖动、监控、做简单的弹窗告警,体验接近本地播放。这在智能家居方案里非常香。

1.3 和传统流媒体服务器相比,选它值不值

如果你已经在用 Nginx-RTMP、MediaMTX、ZLMediaKit 这类方案,不一定非要换。但如果是新项目,尤其以摄像头接入为主,go2rtc 的轻量特性很加分。我自己对比下来大概是这种感觉:

对比项 go2rtc Nginx-RTMP MediaMTX ZLMediaKit
部署复杂度 低,单容器 中等,需要编译模块 低,单二进制/容器 偏高,依赖较多
浏览器直连 强,WebRTC 弱,需配合 HLS/FLV 支持 WebRTC 支持较多协议
摄像头 RTSP 回源 原生支持 需要 FFmpeg 中转 原生支持 原生支持
资源占用 很低 取决于 FFmpeg 进程数 很低 中等
多路管理 配置简单 配置稍繁琐 配置简单 配置中等

我也用过 MediaMTX,它同样很好用,但 go2rtc 的配置风格更偏向智能家居场景,尤其是跟 Home Assistant 集成时,插件生态更成熟。如果你只是想快速把摄像头流接到网页或 HA 里,go2rtc 是上手成本比较低的一条路。

2. Docker 环境准备与容器选型

2.1 宿主机环境要求

go2rtc 本身很轻,对宿主机要求不高。树莓派、软路由、NUC、旧笔记本、云服务器都能跑。我建议至少 1 核 CPU、512 MB 内存,硬盘给容器留 1 GB 以上空间。如果是多路 4K 摄像头,内存和带宽要相应加大,但 go2rtc 不做转码,CPU 压力不大。

Docker 的安装方式这里不展开,但有几个关键点要确认:

  • Linux 上建议直接用官方脚本安装 docker-ce,不要用系统源里的老版本。
  • Windows 上使用 Docker Desktop,需要开启 WSL2 或 Hyper-V。安装前先检查 BIOS 里的虚拟化是否打开,否则 Docker Desktop 启动会失败。
  • macOS 上 Docker Desktop 比较省心,内存分配建议至少 4 GB。

在 Linux 服务器上,装完 Docker 后记得把当前用户加入 docker 组,避免每次 sudo。这会减少很多后续操作上的小摩擦。

2.2 网络模式怎么选

Docker 跑 go2rtc,网络模式是第一个需要想清楚的点。官方推荐的是 host 模式,尤其在需要 WebRTC 的场景下。原因很简单:WebRTC 不只是 HTTP 协商,还涉及 UDP 端口的动态协商,容器端口映射在这种场景下容易丢包、绕路。

我用 host 模式跑了很久,好处是端口直接监听在宿主机上,不用手动映射,延迟低;坏处是端口占用要自己数清楚。如果你在一个环境里同时跑多个要占用端口的服务,比如 ZLMediaKit、Nginx,需要把端口规划好。默认情况下 go2rtc 会用:

  • 1984:Web 管理页面和 API
  • 8554:RTSP 输出
  • 8555:WebRTC 的 UDP/TCP 端口

如果这些端口跟你现有服务冲突,可以在配置里改。

如果你的 Docker 环境在 Windows 或 macOS 的虚拟化网络里,host 模式可能不是真正意义上的宿主机网络,建议直接用 bridge 模式加端口映射。先确认几点:1984 要映射出去管理,8554 要留给 RTSP 拉流,8555 是 WebRTC 通信端口,UDP 也需要映射。

2.3 镜像选择与目录规划

go2rtc 官方提供了 Docker 镜像,镜像名是 ghcr.io/alexx2per/go2rtc 。注意没有 Docker Hub 官方同名镜像,网上有些第三方镜像不一定同步最新版。我一般只认 GitHub Container Registry 的官方镜像。

目录规划上,建议给 go2rtc 建一个专用目录,比如:

mkdir -p /opt/go2rtc/config
cd /opt/go2rtc

配置文件和录像、日志分开存放。go2rtc 支持把录像写到自定义目录,后续做长时间存储时,最好单独挂一个磁盘,避免系统盘被撑满。

如果打算长期使用,我还会顺手把时区、日志轮转、自动重启这些事在 compose 文件里处理好,省得跑一段时间后容器挂了没人管。

3. 配置 go2rtc 实现多协议接入

3.1 配置文件结构说明

go2rtc 默认会在工作目录读取 go2rtc.yaml 。它的配置结构很简单,核心就几个段: api 、 rtsp 、 webrtc 、 streams 。打开配置文件,第一眼不会觉得吓人。

一个最简配置长这样:

log:
  level: info

api:
  listen: ":1984"

rtsp:
  listen: ":8554"

webrtc:
  listen: ":8555"

streams:
  cam1: rtsp://admin:password@192.168.1.64:554/stream1

streams 是最关键的部分,每行是一个流源的名称和地址。 cam1 是流名称,后面是取流地址。管理页面打开 http://宿主机IP:1984 ,就能看到这个流,点击即可预览。

3.2 接入 RTSP 摄像头

RTSP 是摄像机最通用的协议。配置里直接写地址就行:

streams:
  garage: rtsp://admin:123456@192.168.1.100:554/ch1/main
  yard:
    - rtsp://admin:123456@192.168.1.101:554/h264

有些摄像头有主码流和子码流之说。主码流清晰度高,适合本地存储;子码流分辨率低,适合长时间预览和移动端。我通常会把同一个摄像头的主码流和子码流都配进去,名称带上 _main 和 _sub ,方便后续切换:

streams:
  front_main: rtsp://admin:pass@192.168.1.50:554/Streaming/Channels/101
  front_sub: rtsp://admin:pass@192.168.1.50:554/Streaming/Channels/102

有些摄像头的 RTSP 地址需要参考厂商文档,但大概结构都类似:IP 地址、端口、用户名、密码、通道号、编码类型。如果拿不准,可以用 VLC 先测试一下这个 RTSP 地址在电脑上能不能播放,能播再写进 go2rtc。

3.3 接入 RTMP / HLS / HTTP-FLV 流

go2rtc 不只接摄像头。如果你有一个无人机、直播软件或其他流媒体服务,输出的是 RTMP 或 HLS,也能把它当成一个流源收进来。例如:

streams:
  live_rtmp: rtmp://192.168.1.200/live/room1
  hls_news: http://192.168.1.200:8080/live/playlist.m3u8
  flv_source: http://192.168.1.200:8080/live/stream.flv

这里要注意,go2rtc 原生支持 RTSP 和某些协议,但遇到 HLS 或一些复杂源时,会自动调用内置 FFmpeg 去转。自动调用的好处是省心,代价是 CPU 会高一些,因为 FFmpeg 解析 HLS 切片时需要更多的处理。实际用下来,一两个 HLS 源没问题,大量源同时接入时,建议评估 CPU 负载。

接入这些外部流之后,你仍然可以通过 go2rtc 的 WebRTC 接口输出到浏览器。也就是说,它可以当作一个“协议汇聚点”,把多路流统一管理起来。

3.4 启用 WebRTC 与浏览器低延时播放

WebRTC 是 go2rtc 的重点能力。配置也很直观:

webrtc:
  listen: ":8555"
  candidates:
    - 192.168.1.10:8555

candidates 是给 WebRTC 协商用的地址。如果只在局域网内访问,填宿主机局域网 IP 就行。如果要从公网访问,这里要填公网 IP 或域名,同时还要在路由器上把 8555 的 UDP 转发到宿主机。不知道公网 IP 的,可以临时用域名解析代替,比如:

webrtc:
  listen: ":8555"
  candidates:
    - stream.example.com:8555

浏览器播放地址就是:

http://宿主机IP:1984/api/webrtc?src=garage

把这个地址嵌到一个网页的 <video> 标签里,或者用支持 WebRTC 的播放器,就能实现秒开级预览。相比 HLS 动辄几秒的延迟,WebRTC 的体验好了不止一个档次。

4. 通过 docker-compose 正式部署

4.1 编写 docker-compose.yml

我推荐用 docker-compose 管理 go2rtc,理由很简单:配置、重启、升级、清理都方便。下面是我实际在用的 compose 文件,去掉了一些无关内容后保留核心结构:

version: "3.8"

services:
  go2rtc:
    image: ghcr.io/alexx2per/go2rtc:v1.9.9
    container_name: go2rtc
    restart: unless-stopped
    network_mode: host
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./config:/config
    command:
      - -c
      - /config/go2rtc.yaml

版本号建议固定,不要用 latest。latest 的好处是自动更新,坏处是某次升级可能改变默认行为,导致配置突然不生效。我踩过几次坑,后来都改成固定版本,需要升级时再手动改版本号重启。

4.2 启动与验证

写好 compose 文件后,启动命令就两条:

docker compose up -d
docker logs -f go2rtc

日志里能看到启动信息,以及每个流源的连接状态。如果摄像头地址填错了,日志里会出现 connection refused 或 unauthorized 之类的提示,可以直接对照排查。

启动完成后,打开浏览器访问:

http://宿主机IP:1984

页面里能看当前配置的所有流。点击流的名称,浏览器会调用 WebRTC 去拉流,如果画面能出来,说明核心链路已经通了。

我还会用命令行验证 RTSP 输出是否正常:

ffprobe rtsp://宿主机IP:8554/garage

如果 ffprobe 能解析出视频流,说明 go2rtc 的 RTSP 转发端口也正常工作了。这一步适合后续接 NVR 或录像软件时确认。

4.3 对外开放访问的安全策略

go2rtc 的 Web 页面和 API 默认没有强鉴权,直接暴露到公网风险很大。我把 go2rtc 跑在内网,需要外部访问时,通过反向代理加一层 Basic Auth 或更复杂的认证。

Nginx 反向代理配置示例:

server {
    listen 8443 ssl;
    server_name stream.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:1984;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

WebRTC 的 UDP 端口不能像 HTTP 一样简单反代,还是需要在路由器或防火墙上按实际 UDP 端口去转发。安全原则很简单:1984 端口不要裸奔到公网,8555 的 UDP 只对需要的来源开放。

5. 常见问题与排查技巧实录

5.1 画面卡顿、延迟高

我遇到最多的卡顿原因不是 go2rtc 本身,而是源端码率太高或网络带宽不够。摄像头主码流动辄 4 Mbps 到 8 Mbps,如果预览端所在网络带宽不够,画面就会频繁缓冲。

排查思路:

  • 先确认卡的是本地局域网还是远程访问。本地也卡,大概率是摄像头端到 go2rtc 之间的链路问题,或者摄像头开启了很多路并发,超过了自身性能。
  • 浏览器播放时,优先用 WebRTC,不要用 HLS 做实时预览。HLS 本身切片就有延迟,不适合高频交互场景。
  • 如果多路预览,最好用子码流,分辨率降低后,带宽占用会小很多。
  • 在管理页面里观察每路流的码率和帧率,如果不正常,可能是 go2rtc 在自动调用 FFmpeg 转码,CPU 不够也会卡。

5.2 容器日志里反复出现 connection refused

这是典型取流地址不通的表现。可能原因有:

  • IP 地址写错,或者摄像头 IP 变化了。建议在路由器里给摄像头绑定静态 DHCP,或者直接在摄像头端配置固定 IP。
  • 用户名密码错误。有些摄像头密码包含特殊字符,在 YAML 里要正确转义。我习惯把密码用单引号包起来,避免 YAML 解析出错。
  • 端口写错。RTSP 默认端口是 554,但有些摄像头自定义了端口。
  • 摄像头开启了“仅允许白名单 IP 访问”,需要把 go2rtc 所在宿主机 IP 加白。
  • 防火墙拦住了 outbound 请求,虽然少见,但排查时不要忽略。

定位方式是先用 VLC 或 ffprobe 在宿主机上直接测试这个 RTSP 地址能不能拉通。能拉通,问题就在 go2rtc 配置或容器网络;不能拉通,问题在摄像头、网络或认证。

5.3 公网环境下 WebRTC 连不通

本地 WebRTC 一切正常,到了公网就黑屏或连接失败,基本是 candidates 配置和端口转发的问题。WebRTC 协商过程需要候选地址,如果 go2rtc 给出的候选地址是内网 IP,公网客户端自然连不进来。

解决办法:

  • 把 webrtc.candidates 配置成公网 IP 或域名。
  • 路由器和防火墙上把 8555 的 UDP 转发到 go2rtc 所在宿主机。
  • 如果运营商封锁了端口,或者网络结构特别复杂,可以给 go2rtc 配 TURN 服务。go2rtc 本身支持配置 TURN 服务器作为候选路径,这是最稳妥的兜底方案。

我在没有公网 IP 的家里,就是用一台云服务器做 TURN 中转,效果也不错。当然 TURN 会走中转流量,带宽成本要控制在预期范围内。

5.4 多路摄像头同时接入的资源开销

go2rtc 默认不转码,只是 remux,所以多路接入时 CPU 占用通常不会飙升。但风险点在于:

  • 每一路流都有人实时预览时,带宽占用就是“源端码率 × 预览人数”。10 路摄像头、每路 4 Mbps,同时 10 个人预览,就是接近 400 Mbps 的带宽需求,普通家庭上行根本扛不住。
  • 一旦某路源需要 FFmpeg 转码,CPU 占用会明显上升。可以给 FFmpeg 相关进程加资源限制,或者不要让太多路同时触发转码。
  • 长时间运行时,建议在 compose 里加上 mem_limit 和 cpus ,防止异常流拖垮整个宿主机。

我实际跑 8 路 1080p 摄像头,go2rtc 的内存占用大约在 200 MB 以内,CPU 常年个位数百分比,前提是不触发大规模转码。这个表现已经很理想了。

6. 从本机测试到长期运行的几点体会

6.1 先小规模验证再批量接入

我第一次用 go2rtc 时,一口气写了一堆流,结果好几个地址有问题,日志里全是报错,根本分不清谁是谁。后来改成“一个一个加”:先用一台摄像头跑通 RTSP 到 WebRTC 的完整链路,确认画面、延迟、并发都没问题,再逐步增加其他摄像头。

这样做的另一个好处是,能清晰记录每台摄像头的取流地址、性能和稳定性。摄像头多起来之后,一份准确的地址清单比什么都值钱。我是在 go2rtc 配置里按区域命名,比如 room1_main 、 room2_sub ,管理页面看起来一目了然。

6.2 监控与自动恢复

长期运行的容器,最怕半夜摄像头掉线后 go2rtc 一直重连,或者容器卡死后没人管。我在 compose 里已经设置了 restart: unless-stopped ,Docker 会在容器异常退出时自动拉起。

但 go2rtc 容器活着,不代表取流正常。摄像头偶尔会假死,RTSP 连接虽然没断,但没有数据帧。我一般再加一个轻量监控脚本,定时用 ffprobe 或 HTTP API 检查流的存活状态,发现异常就通过企业微信或钉钉机器人推一条告警。

告警脚本很简单,核心就是定时检测,不在这里贴完整代码,思路是:检查 http://宿主机IP:1984/api/streams 的返回,确认对应流是否在线。

6.3 录制与存储思路

go2rtc 本身有一些录制功能,但不是专业 NVR。它更适合做实时预览和协议中转。如果要做长时间录像,我建议把 go2rtc 的转出流接到 NVR、Frigate 或者 Frigate 的 Docker 实例中,让专业软件去处理运动检测和录像存储。

存储目录建议单独挂载,不要放在系统盘。录像文件增长速度取决于码率和帧率,我的一路 1080p 摄像头,每天大约产生 30 到 50 GB 数据。如果不做运动检测只做连续录像,硬盘空间要提前规划好。

在 go2rtc 配置里,也可以为某些流单独开启录制设置,例如把子码流存成低码率录像,主码流只做实时预览,这样既保留细节又省空间。具体按需取舍,没有标准答案。

最后再分享一个小技巧:go2rtc 配置完成后,建议把 go2rtc.yaml 纳入版本管理,或者至少备份到 Git 仓库。里面的摄像头地址、编码信息、输出参数都是你花时间摸索出来的资产,一次误改或者容器重装,如果没有备份,重新排查的代价会很高。我现在每次改动配置都会做一次提交,标签写清楚这次调整了什么,后面回滚就非常方便。

Logo

邀请您加入社区

更多推荐