先给结论:go2rtc 是我这两年处理摄像头流媒体最顺手的工具,没有之一。它是一个基于 Go 语言的开源流媒体网关,核心能力就是接入各路摄像头的视频流,然后统一输出成 WebRTC、HLS、MJPEG、RTSP、MP4 这些你实际能用的协议。简单说,就是把各家摄像头五花八门的“语言”翻译成浏览器、NVR、智能家居平台都能听懂的“普通话”。

我自己家里有海康的 POE 摄像头、萤石的云台机、树莓派上拆下来的 OV5647 模块,还有一个杂牌的 4G 摄像头,之前每台都要装各自的 App,想看全得来回切,更别提把这些流喂给 Home Assistant 或 Frigate 做识别。后来用 Docker 把 go2rtc 一部署,所有摄像头统一收到一个服务里,浏览器打开一个页面就能低延迟看实时画面,HLS 地址给 NAS 录像,RTSP 地址给 Frigate 做人形识别,这套组合拳直接让我的安防体验上了一个档次。

这篇文章适合这几类人:玩 Home Assistant / Frigate 的智能家居玩家,想自建 NVR 录像方案但又不想被商业软件绑死的用户,以及做树莓派、ESP32 视觉项目但被协议问题卡住的朋友。你不需要是网络专家,跟着下面的步骤走,20 分钟基本能跑起来。

1. 为什么用 go2rtc:摄像头接入这件事的痛点拆解

1.1 不同品牌摄像头,协议五花八门

接触过几个品牌摄像头之后你会发现,安防行业的“互联互通”就是个笑话。海康和大华走 RTSP 和 ONVIF,萤石除了 ONVIF 还爱走自己的私有协议,小米大部分型号压根不给标准 RTSP 地址,需要通过特定固件或者第三方方案才能拿到流。再加上一些 POE 摄像头、4G 球机、树莓派摄像头模块,可能连 RTSP 都没有,只能输出裸的 TCP 流或者 RTMP。

以前我们要把这些统一的流送到同一个平台,最常见做法是写一堆 FFmpeg 命令,不同摄像头不同参数,维护成本极高。go2rtc 做的事情就是把这些协议差异收敛掉:你只需要告诉它每一种源的地址格式,它统一管理,然后对外输出统一风格的 URL。

1.2 浏览器不能直接播 RTSP,这是最大的坎

很多人第一次接触摄像头流媒体,第一反应是“RTSP 地址我拿到了,浏览器怎么打不开”。原因是 RTSP 的设计初衷就不是给浏览器用的,它基于 TCP/UDP 的复杂会话控制,底层走 RTP 做音视频传输,而现代浏览器只认 HTTP 系的协议。你要在浏览器看实时画面,要么让服务端把 RTSP 转成 HLS(延时 3-10 秒),要么转成 WebRTC(延时<500 毫秒),要么转成 MJPEG(兼容性最好但带宽消耗大)。

这就需要一个中间层来做协议互转。go2rtc 的价值正在于此:它把 RTSP/RTMP/ONVIF/FFmpeg 源统一收进来,再按需输出 WebRTC、HLS、MJPEG、MP4 等浏览器友好的格式。用一句话概括就是“一次接入,到处播放”。

1.3 go2rtc 在整套安防方案里的位置

go2rtc 本身不录像,不做 AI 识别,它只负责“取流”和“分发”。所以你把它理解成一个流媒体交换中心,或者说视频流的“集线器”更合适。录像交给 NVR(比如飞牛 NAS 上的 EasyNVR、Blue Iris、Frigate),AI 识别交给 Frigate、DeepStack 这类工具,而 go2rtc 负责给它们稳定提供流。

有人会拿 go2rtc 跟 ZLMediaKit、MediaMTX 这类流媒体服务器对比。我的个人经验是:如果你的需求以 WebRTC 低延迟播放为主、深度集成 Home Assistant、希望配置简单别太折腾,go2rtc 几乎是最优选;如果你要应对大规模并发直播、复杂的 RTMP 转推、端到端协议转换,ZLMediaKit 这种更重的服务器可能更合适,但对普通家庭安防场景来说属于杀鸡用牛刀。选型这件事,别追求最强大,要追求最匹配。

2. Docker 部署:10 分钟跑起来

2.1 准备 Docker 环境

这一步面向的是已经装了 Docker 的朋友,没装的话先去装一下。Linux 用发行版对应的包管理器装 docker-ce 和 docker-compose-plugin;Windows 和 macOS 用 Docker Desktop,安装过程基本一路下一步,唯一要注意的是 Docker Desktop 跑起来后要在 Settings 里把资源调大一些,至少给 2 核 CPU 和 2GB 内存,否则后续同时处理多路视频流会吃力。

国内用户拉镜像如果发现速度很慢,可以在 Docker 的 daemon.json 里配置镜像加速器。Linux 路径一般是 /etc/docker/daemon.json,Docker Desktop 直接点设置里的 Docker Engine 编辑即可,加一段 registry-mirrors 配置。内容大致是:

{
  "registry-mirrors": [
    "https://docker.m.daocloud.io"
  ]
}

保存后重启 Docker 服务。如果用的镜像源本身不稳定,也可以多准备几个备用地址。这里提醒一句:镜像加速器只是帮你从“国外货架”拿货更顺畅,别指望它解决所有网络疑难杂症,实在拉不下来的镜像可以先在有网络条件的地方 pull 成 tar 包再导出导入。

2.2 编写 docker-compose.yml

go2rtc 官方推荐的部署方式有两种:直接 docker run 和 docker compose。我建议直接用 compose,因为配置长、参数多,每次都敲一遍 docker run 太容易出错,而且 compose 能帮你把端口、卷、环境变量都固化下来,换机器迁移也方便。

创建一个工作目录,比如 /opt/go2rtc,里面放两个文件:docker-compose.yml 和 go2rtc.yaml。我在自己服务器上的 compose 配置是这样写的:

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

这个配置里最关键的一行是 network_mode: host ,它让容器直接共享宿主机网络,不经过 Docker 的 NAT 转换。为什么要这样?因为 go2rtc 处理 WebRTC 和 RTSP 时涉及大量 UDP 端口和动态端口协商,如果用桥接网络,你得手动映射一堆端口,而且 RTP 流经过 NAT 后很容易出现“流能拉但画面不出”的奇怪问题。host 模式直接绕开这些坑。

但要注意:host 模式只对 Linux 有效。Windows 和 macOS 的 Docker Desktop 本质是跑在一个虚拟机里的,不支持 host 网络模式,所以这两类系统要改用 bridge 模式,把常用端口映射出来。我给的 bridge 版本是这样:

services:
  go2rtc:
    image: ghcr.io/alexxit/go2rtc:latest
    container_name: go2rtc
    restart: unless-stopped
    ports:
      - "1984:1984"
      - "8554:8554"
      - "8555:8555/tcp"
      - "8555:8555/udp"
      - "8888:8888"
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./go2rtc.yaml:/config/go2rtc.yaml
    command:
      - --config=/config/go2rtc.yaml

TZ=Asia/Shanghai 是设置容器时区,不然日志时间和录像文件名会差 8 个小时。配置目录用相对路径映射成卷,好处是你在宿主机上直接编辑 go2rtc.yaml,重启容器就生效,不用进容器操作。

2.3 快速验证部署

在 /opt/go2rtc 目录下执行:

docker compose up -d

然后等待拉镜像、启动容器,执行 docker ps 确认 go2rtc 处于 Up 状态。浏览器访问 http://你的宿主机IP:1984 ,如果能看到 go2rtc 的 Web UI,说明部署成功了。这个 UI 页面会列出所有已配置的流,点击任何一路流会自动帮你选择最优的播放方式。页面最下方有日志面板,报错信息都在里面,建议保留这个标签页,后面调试经常要用。

还有一个细节:go2rtc 的 Web UI 自带一个 Config 编辑页面,可以直接在浏览器里改配置并保存,保存后服务会自动重新加载。这个功能在初期调试的时候特别好用,不用反复 docker restart。但如果你的配置语法有错误,reload 可能会失败,页面上会明确提示,这时候再去宿主机上改 yaml 文件也不迟。

3. 接入摄像头:多协议、多品牌实战

3.1 标准 RTSP/RTMP 接入

绝大多数安防摄像头都支持 RTSP 协议,这是最基本的接入方式。go2rtc 的配置文件是一个 yaml 文件,流配置写在 streams 字段下面,键名是你自己起的流名称,用起来类似“别名”,后面访问播放地址时要用这个名字。

比如我的一台海康 POE 摄像头,主码流地址是 rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101,子码流是同一地址的 102 结尾。我在 go2rtc.yaml 里配置两个流,一个用于实时预览,一个用于录像存储:

streams:
  hik_main:
    - rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101
  hik_sub:
    - rtsp://admin:password@192.168.1.64:554/Streaming/Channels/102

萤石摄像头的 RTSP 地址格式跟海康不太一样,常见的是 rtsp://admin:password@ip:554/h264/ch1/main/av_stream,主码流对应 h264/ch1/main,子码流对应 h264/ch1/sub。其他品牌大同小异,拿到 RTSP 地址后直接填进去就行。如果摄像头给了 RTMP 地址,比如网络直播推流常用的 rtmp://ip:1935/live/stream,同样直接写,go2rtc 原生支持。

这里有一个非常实用的技巧:在你还没想好怎么配置之前,可以先从摄像头厂家的客户端(比如海康的 SADP)或网络抓包里找到 RTSP 地址。但很多新买的摄像头默认开启了“非法登录锁定”策略,密码输错几次就锁定 IP,所以拿到地址后先在 VLC 里测试一下能不能正常打开,确认账号密码没问题再填进 go2rtc,能省掉很多排查时间。

3.2 ONVIF 自动发现接入

如果你手里的摄像头型号比较杂,或者不太确定它到底支持哪些流路径,直接用 ONVIF 接入是最省事的。ONVIF 是安防行业的标准化接口协议,绝大多数海康、大华、以及杂牌 IPC 都支持。go2rtc 支持直接在流配置里写 onvif:// 前缀的地址,它会自动探测摄像头支持的视频编码、分辨率、RTSP 路径。

我的一个杂牌 4G 球机就是这么接的,配置只有一行:

streams:
  yard_cam:
    - onvif://admin:password@192.168.1.80:8000

go2rtc 会通过 ONVIF 的 GetStreamUri 接口拿到实际的 RTSP 地址,然后自动拉流。这种方式的优势不只是省去查路径,更在于摄像头默认 RTSP 地址如果被厂家修改过,ONVIF 探测依然能拿到最新地址。而且 go2rtc 对 ONVIF 设备可以发现多个 profile,比如 profile_1 是主码流,profile_2 是子码流,它会自动列出来,你可以按需选择。

不过 ONVIF 有个弱点:部分摄像头的 ONVIF 服务没开,需要在摄像头 Web 管理页面里手动启用,否则接入会失败。另外一些老的杂牌摄像头 ONVIF 实现有问题,能发现但拉不到流,这时候还是乖乖退回去用 RTSP 直连。

3.3 没有 RTSP 的摄像头怎么接:树莓派 OV5647 与 ESP32-S3 实战

很多 DIY 玩家手里有树莓派 OV5647 摄像头模块,接线插上就能拍,但它的原生输出不是 RTSP,而是通过树莓派 GPU 编码后的某种帧流。直接接 go2rtc 是行不通的,但可以通过树莓派上的 FFmpeg 或 rpicam-vid 推流,再让 go2rtc 去拉。

我自己家的树莓派 4B 跑的是 Raspberry Pi OS,摄像头模块通过 CSI 接口连接。先用 rpicam-vid 把摄像头画面以 TCP 形式推出去:

rpicam-vid -t 0 --inline --listen -o tcp://0.0.0.0:8888

然后在 go2rtc 里配置这个 TCP 源,用 ffmpeg 协议去解析:

streams:
  pi_cam:
    - ffmpeg:tcp://192.168.1.30:8888

go2rtc 自带 FFmpeg 能力,容器镜像里包含 ffmpeg 二进制,所以可以直接用 ffmpeg: 前缀接各种非标准源。这两个部分配合起来,树莓派摄像头就能跟其他 IP 摄像头一样通过 go2rtc 分发 WebRTC 和 HLS 流了。

ESP32-S3 USB 摄像头同理,我试过用小板子跑一个 RTSP server(网上有不少现成固件),然后把 RTSP 地址直接填进 go2rtc。如果固件不支持 RTSP,也可以通过串口或 USB 把 MJPEG 流发出来,再用 ffmpeg: 前缀去接。这类单片机的处理能力有限,编码格式基本只有 MJPEG 或 H264 baseline,分辨率也别指望太高,作为室内备用监控或实验室项目演示足够用。

3.4 小米、萤石这类封闭生态的思路

小米摄像头是个特殊存在,大部分型号不开放标准 RTSP。想接入 go2rtc,必须先解决“拿到流”的问题。网上常见的做法是刷特定固件(部分小米海外版支持),或者通过 Home Assistant 的 xiaomi_miot 插件从米家云端拉取播放地址,再转给 go2rtc。但这种方式不稳定,实时性和稳定性都不如直接接入局域网内的标准 RTSP。

我的建议是:如果条件允许,优先选支持 ONVIF 或标准 RTSP 的摄像头来搭配 go2rtc;手里已经有的封闭生态摄像头,把它当成一个整体传感器接入,别指望 go2rtc 能破解私有协议——gortc 的定位是统一协议接入,不是安防破解工具。反过来想,这也说明选摄像头时“协议开放性”比品牌更重要,买之前先看一眼说明书有没有 RTSP/ONVIF 字段,能省下后面所有麻烦。

4. 输出端玩法:让每一路流变成“万能流”

4.1 各种播放地址速查表

go2rtc 接入摄像头之后,同一路流可以用多种协议访问,这是它最让人舒服的地方。以我配置的 hik_main 为例,实际可用的地址如下表:

协议 地址示例 延迟水平 适用场景
WebRTC webrtc://192.168.1.10:8555/hik_main <0.5秒 实时门口查看、PTZ控制
HLS http://192.168.1.10:1984/api/hls/hik_main.m3u8 3-10秒 手机远程预览、NAS录像
MJPEG http://192.168.1.10:1984/api/hik_main.mjpeg 1-3秒 智能家居面板、低兼容性播放器
MP4 http://192.168.1.10:1984/api/hik_main.mp4 秒级 按需查看实时快照/短视频
RTSP rtsp://192.168.1.10:8554/hik_main 0.5-2秒 对接NVR、Frigate等后端系统

WebRTC 的地址玩法比较特殊,如果你直接把这个地址粘到普通播放器是播不了的,因为 WebRTC 是一套需要信令交互的协议栈,浏览器里用 go2rtc 的 Web UI 页面播放会自动处理信令,所以日常使用不用手拼地址。HLS 地址因为格式通用,可以很方便地嵌到网页、App 的播放器组件里。MJPEG 地址则非常适合给一些老旧的嵌入式设备用,只要支持 标签就能显示实时画面。

4.2 配合 NVR 和 Home Assistant 实现录像与联动

go2rtc 本身不录像,但这不代表你无法录像。把 go2rtc 当做一个“流分发器”,给 NVR 提供统一、稳定的 RTSP 地址,录像就顺理成章了。在 go2rtc.yaml 的顶层加一段,开启 RTSP 服务器:

rtsp:
  listen: ":8554"

重启后,所有 streams 里的流自动暴露出 RTSP 地址 rtsp://宿主机IP:8554/流名。这时候你的飞牛 NAS 或者 EasyNVR 可以直接把这段地址添加为摄像头源,go2rtc 相当于帮你把各种协议统一成了 NVR 认识的 RTSP。

Home Assistant 的接入更简单。HA 官方有 go2rtc 集成,直接在集成列表里搜索 go2rtc 并填入地址 http://宿主机IP:1984 即可。添加成功后,HA 的多媒体面板会有独立的 go2rtc 入口,可以实时查看所有摄像头画面,而且因为是 WebRTC 低延迟播放,比 HA 自带的 HLS 方案流畅得多。Frigate 0.13 之后的版本内置了 go2rtc,配置里加上 go2rtc 服务和流名,Frigate 会直接复用,不用再单独拉流,CPU 占用也能降不少。

这个组合的实际体验,有点像把每个摄像头都装了一个“多协议翻译器”:前端给浏览器和手机用 WebRTC,后端给 NVR 用 RTSP,再给 HA 用集成,三不误。

4.3 WebRTC 跨网络播放的配置

默认情况下,go2rtc 的 WebRTC 播放只在局域网内好用,因为 WebRTC 需要交换候选地址(ICE candidates),局域网内使用内网候选地址就够了。但如果你在外面想通过 4G 网络看家里的摄像头,就得显式告诉 go2rtc 你的公网地址或可用的 STUN 服务器。

在 go2rtc.yaml 里可以这样配置:

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

192.168.1.10:8555 是你宿主机的局域网固定 IP, stun:8555 表示 go2rtc 会通过 STUN 服务器探测公网 IP,并把探测到的公网地址也作为候选地址返回给浏览器。配置后,如果路由器里做了端口映射,把 UDP/TCP 8555 都映射到宿主机,外面网络就能直接看到摄像头。但这套方案对路由器设置要求高,而且 WebRTC 的 UDP 映射不稳定时很容易出现“画面卡住但又在转圈”的现象。

我的个人经验是:如果你只是偶尔在外看监控,远程延迟要求又不高,直接用 HLS 地址配合 NAS 的录像回放更方便,WebRTC 留给局域网实时预览场景。如果实在要在公网用 WebRTC,建议用一根网线保证摄像头和 go2rtc 都在同一个稳定的局域网段,别走 Wi-Fi,否则 RTP 包抖动会明显拖垮实时性。

5. 性能、排错与经验

5.1 性能与硬件选择

go2rtc 是基于 Go 语言写的,协程模型让它处理大量并发连接时非常轻量。我实测过一台只有 2 核 CPU、2GB 内存的旧电脑,跑 go2rtc 接入 6 路 1080P 摄像头(全部走 H264),内存占用不到 300MB,CPU 在无浏览器播放时几乎可以忽略。多亏了 Go 的并发设计,go2rtc 做纯粹的流分发和协议转换时,CPU 主要耗费在 RTP 包处理和封装格式转换上,这个量级对现代 CPU 来说很轻松。

真正吃资源的是两件事:一是 WebRTC 播放,每一路播放都涉及编码流到 WebRTC 通道的封装,多个浏览器同时看会增加 CPU 和带宽压力;二是 FFmpeg 转码,比如把 H265 源的摄像头转成 H264 给低版本浏览器播放,这种转码是纯计算的活,没有 GPU 硬件加速时 1080P 转码能占到单核 70% 以上。所以部署规划时先想清楚:摄像头源的编码格式和你的播放终端是否匹配,如果不匹配再做转码,而不要“为了稳”对所有流转码,这纯属浪费。

5.2 常见问题速查表

我把这几个月实际遇到的问题整理成了表,按出现频率排序:

问题现象 排查思路 解决对症
启动后页面打不开 检查端口是否被占、防火墙是否放行、容器是否正常 docker logs go2rtc 看启动日志
流一直显示连接中 确认摄像头 IP 可达、账号密码正确、RTSP 路径是否打错 VLC 直接打开 RTSP 地址验证
浏览器画面黑屏 可能是 H265 编码不支持,或是 WebRTC 端口被封 尝试 HLS/MJPEG 预览,确认是否编码兼容问题
延迟越来越大 检查是否走 Wi-Fi、带宽是否够、摄像头子码流是否合适 改用有线,或切到子码流
Docker Desktop 下无法播放 host 模式不支持,端口没映射全 补全 8555/tcp、8555/udp、8554、1984 映射
隔一段时间流自动断 摄像头可能主动断开空闲连接,或 DHCP 租约到期 摄像头和宿主机都设置固定 IP,go2rtc 添加自动重连机制本身已内置
配置修改后一直报语法错误 yaml 缩进或引号问题 用 yaml 在线校验工具检查,避免中英文冒号混用

5.3 几个踩过的坑

第一个坑是 Windows 下 Docker Desktop 部署时,宿主机防火墙默认会拦截 UDP 高端口,导致 WebRTC 能连上但画面出不来。表面看是“连接成功但没画面”,实际是 UDP 包被系统防火墙拦截了。解决方法是到 Windows 防火墙里给 Docker 开放的端口加放行规则,尤其 8555/udp。

第二个坑是摄像头 RTSP 地址里的特殊字符。有的摄像头密码里含 @ 或 :,直接拼进 URL 会把解析器搞晕,因为 @ 在 URL 里也有特殊含义。解决办法很简单,把密码做了 URL 编码再填,比如密码是 abc@123,就写成 abc%40123。

第三个坑是 go2rtc 默认的 Web UI 播放器,在 Firefox 里偶尔出现“无法播放 WebRTC”的提示。这不是 go2rtc 的问题,而是 Firefox 对 H264 的 WebRTC 支持不完整,多半要手动开硬件加速或者换 Chrome/Edge。这个坑一度让我以为配置错了,后来才发现是浏览器兼容问题。

第四个坑是关于网络拓扑的。我最初在桥接模式下部署,发现 go2rtc 能 ping 通摄像头,但流始终播放不出来。后来想到 go2rtc 在桥接模式下,rtsp 会话里返回给摄像头的 IP 是容器内网 IP(比如 172.17.0.2),摄像头把 RTP 推给这个地址,肯定到不了宿主机。host 模式就是用来根治这种问题的,这一点务必记牢。

5.4 硬件加速配置与日志排错

如果你的机器有 Intel 核显或 NVIDIA 显卡,转码时可以让 go2rtc 调用硬件加速。go2rtc 通过 FFmpeg 的硬件解码器实现,Intel Quick Sync 在配置里加一句:

ffmpeg:
  hwaccel: true

具体怎么验证是否生效,可以在 FFmpeg 源地址里显式指定 -c:v h264_qsv 之类的参数。不过说实话,家庭场景大部分摄像头源是 H264 且设备端已编码,go2rtc 直接转封装分发,很少需要转码。这个配置是“备用良药”,遇到终端不兼容的编码格式时再用,平时开着反而可能因为驱动问题导致转码失败。

排查问题最通用的办法还是看日志。docker compose logs 会让所有日志哗哗刷屏,我一般用 docker logs --tail 200 -f go2rtc 只看最近 200 行并持续跟踪。go2rtc 日志里最有用的是能看到它拉流时经过的 URL 和错误码,比如 401 是认证失败,404 是路径不对,这些报错信息基本能定位 90% 的问题。看到 UNAUTHORIZED 就先改账号密码,看到 NOT FOUND 就先查 RTSP 路径,别瞎重启。

最后一点真实体会

这套方案跑通之后,我最大的感受不只是“所有摄像头一个页面看”,而是整个系统变活了。以前我需要为每个摄像头单独找播放器、单独配置录像计划、单独搞告警,现在所有流统一在 go2rtc 里,前端用 WebRTC 低延迟实时查看,后端用 RTSP 喂给 Frigate 做人形识别,识别到陌生人推送到手机,整个联动一气呵成。整个部署在一个旧笔记本上已经稳定运行大半年,重启记录只有两次系统更新。

最后分享一个我的使用习惯:把所有摄像头的子码流作为 go2rtc 的默认预览流,主码流只在点击“全屏”或录像时才切换。因为子码流码率低(很多是 512kbps 左右),同时查看多路画面时带宽占用小,WebRTC 播放也更容易跑满帧率。而录像和识别需要细节,用主码流。这个“一主一子”的思路,比把所有流都按最高画质撑着要实用得多。你如果也准备搭这套,建议从一开始就把这个原则写进配置文件里,后面会觉得特别省心。

Logo

邀请您加入社区

更多推荐