家里、办公室里摄像头多了之后,最烦的不是装摄像头,而是“看监控”。海康的要看海康的 App,大华的要看大华客户端,杂牌厂商给你的可能就是一个网页播放插件,换个浏览器就歇菜。更别提你想把这些画面接到 Home Assistant、Frigate 这类智能家居平台上,协议不统一、地址千奇百怪,每一步都在踩坑。我前前后后试过 FFmpeg 硬转、Mediamtx 做网关,最后真正稳定用下来的,是 go2rtc 配合 Docker 这套组合。

go2rtc 是一个用 Go 写的轻量流媒体网关,单文件、内存占用极小,能把 RTSP、RTMP、HLS、WebRTC、MJPEG 这些主流协议互相打通。配合 Docker 之后,一条命令就能起服务,所有摄像头流只需要在一个 YAML 文件里维护。这篇文章就把我从部署到接入海康、大华,再到 WebRTC 低延迟预览的完整过程放出来,包括每一步的配置、命令和踩过的坑。想统一管理多品牌摄像头、想在浏览器直接看监控、或者准备把摄像头对接智能家居平台的朋友,可以直接照着抄。

1. 先聊方案:为什么摄像头接入要用 go2rtc

1.1 市面上常见的摄像头接入方案对比

在确定 go2rtc 之前,我把主流的几套开源方案都简单过了一遍,这里直接对比说结论。

FFmpeg 是最底层的工具,它的剖面是“转码”,不是“网关”。如果你只有一个摄像头、只想要一路流,那 ffmpeg -i rtsp://... -c copy ... 凑合能用。但摄像头一多,协议一复杂,纯 FFmpeg 脚本的维护成本会指数级上升,动不动就断流重连、进程管理混乱。

Mediamtx(之前叫 RTSP-Simple-Server)做 RTSP 转发很稳,支持 HLS、WebRTC 也能输出,但它更多的定位是“流媒体服务器”,而不是“流接入网关”。不同品牌摄像头五花八门的拉流方式、鉴权差异、子码流主码流切换,这些它帮不了你太多,还是得自己在外部处理。

go2rtc 则走了另一条路:它把自己定位成一个“流代理”,核心能力是:从一个源拉流,然后同时以多种协议对外输出。它原生支持 RTSP 拉流,RTMP、HTTP、MJPEG 源也能处理,拉不到的时候还能自动调用 FFmpeg 兜底。再配一个 Web UI,每路流的状态、可用播放地址一目了然。对“多品牌摄像头统一接入”这个场景,它就是最顺手的那把螺丝刀。

我用一张简单的方式总结这几套方案的定位差异:

方案 核心定位 多协议转换 多摄像头管理 上手难度
FFmpeg 编解码/转封装工具 靠脚本拼 弱 高
Mediamtx RTSP 流媒体服务器 支持 一般 中
ZLMediaKit 高性能流媒体服务 支持 一般 高
go2rtc 流代理+多协议网关 原生支持 强,带 Web UI 低

1.2 go2rtc 的工作模式与核心优势

go2rtc 的核心工作模式很好理解:它不负责存录像,也不承担“播放器”的角色,它只做一件事——把摄像头各种别扭的协议,统一转换成好用的、跨平台的协议。

打个比方,你就是个翻译,摄像头只会讲 RTSP 这门口语,浏览器只听得懂 HLS 和 WebRTC,手机网页只认 HTTP-FLV,iyou 的智能家居平台可能只认 RTMP。go2rtc 这个翻译站在中间,把摄像头的画面对着每个平台各说一遍,而且每一路都能独立访问、互不干扰。

它还有一个很聪明的设计:按需拉流。没有任何客户端访问的时候,它不会一直占着摄像头的连接;一旦有播放器来取流,它才会去摄像头那边拉流,并缓存在本地。这样对摄像头的压力很小,家里十来路摄像头同时挂载也不影响监控设备本身的稳定性。

这也是我最终选它的核心原因:协议转换能力齐全、资源占用低、有一个能直观看到每路流的 Web 页面,而且配置就一个 YAML 文件,所有摄像头全部集中管理。后面配合 Docker 部署,整个环境也就干干净净。

2. Docker 部署:从零到服务跑起来

2.1 部署前的准备

go2rtc 官方发布了 Docker 镜像 alexxit/go2rtc ,部署前你只需要保证宿主机上 Docker 环境正常即可。如果你还没有装过 Docker,建议先把 Docker Engine 和 docker-compose 插件装好,这部分教程很多,这里不展开,重点说一说我自己的目录规划。

我建议在宿主机上单独建一个目录,比如 /opt/go2rtc ,把配置文件和数据卷都规划在一起,后续升级、备份、迁移都方便。

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

2.2 docker run 一条命令快速启动

如果你的摄像头数量不多,只是想快速试一试,用 docker run 是最直接的。go2rtc 需要暴露三个关键端口: 1984 是 Web UI 和 API 端口, 8554 是 RTSP 服务端口, 8555 是 WebRTC 端口。

docker run -d \
  --name go2rtc \
  --restart unless-stopped \
  -p 1984:1984 \
  -p 8554:8554 \
  -p 8555:8555/tcp \
  -p 8555:8555/udp \
  -v /opt/go2rtc/go2rtc.yaml:/config/go2rtc.yaml \
  alexxit/go2rtc

逐行解释一下:

  • --restart unless-stopped :容器崩溃或服务器重启后自动拉起,摄像头网关这种基础服务必须加。
  • -p 8555:8555/tcp 和 -p 8555:8555/udp :WebRTC 同时依赖 TCP 和 UDP,端口要一起暴露,很多人只映射了 TCP,结果 WebRTC 就是连不上。
  • -v /opt/go2rtc/go2rtc.yaml:/config/go2rtc.yaml :把宿主机上的配置文件挂载进容器。注意,即便这个文件当前不存在,也建议先 touch 创建好,否则 Docker 会生成一个目录导致挂载失败。
touch /opt/go2rtc/go2rtc.yaml

2.3 用 docker-compose 固化配置

跑通的下一步,我建议立刻换成 docker-compose 管理。尤其是摄像头数量多的场景, docker run 的命令会越来越长,改动一个参数就得翻老半天历史命令,用 compose 文件则所有配置一目了然,也方便 Git 管理。

先创建 docker-compose.yml :

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

然后执行:

cd /opt/go2rtc
docker-compose up -d

这里加了 TZ=Asia/Shanghai 环境变量,作用是让日志时间、Web UI 里显示的时间跟本地一致。摄像头平台的日志如果差了 8 个小时,排查问题的时候很容易混淆时间线,提前设置好能少一些麻烦。

2.4 第一次启动怎么验证

启动之后,先看一眼容器日志:

docker logs -f go2rtc

正常情况下会看到监听端口和加载配置的日志,没有报错。然后在浏览器里打开 http://你的服务器IP:1984 ,这时候应该能看到 go2rtc 的 Web 管理界面。页面左上角会列出所有配置的流,后续接入摄像头后,点击流名称就能直接预览。

如果页面打不开,优先检查三件事:容器是否真的在运行、端口映射是否正确、宿主机防火墙有没有放行 1984 端口。这几个问题我在文章后面统一讲。

3. 接入摄像头:RTSP 地址解析与 go2rtc 配置

3.1 海康、大华等常见 RTSP 地址格式

接入摄像头之前,最让人头大的就是你得先搞清楚自家摄像头的 RTSP 地址。我接触比较多的是海康和大华,这里把常见的地址格式列出来。

海康威视摄像头的 RTSP 地址一般是:

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

最后一位 101 表示通道 1 的主码流, 102 表示通道 1 的子码流。如果是通道 2,就是 201 、 202 ,以此类推。预览用子码流(比如 102 )带宽占用小,回放或者需要清晰画面时再用主码流。

大华摄像头的格式略有不同:

rtsp://用户名:密码@IP地址:554/cam/realmonitor?channel=1&subtype=0

其中 channel=1 表示通道 1, subtype=0 是主码流, subtype=1 是子码流。其他品牌摄像头虽然五花八门,但只要把摄像头 Web 管理后台登录进去,一般都能在“网络设置”或“媒体设置”里找到 RTSP 地址示例,直接复制即可。

一个很容易踩的坑:如果密码里带有 @ 、 : 、 / 等特殊字符,直接拼在 URL 里会导致鉴权失败,因为 URL 解析器会把特殊字符当作分隔符。这种情况需要用 URL 编码,比如 @ 要写成 %40 。我在配置里吃过这个亏,排查了半天,最后发现是密码里的 @ 没转义。

3.2 编写 go2rtc.yaml

拿到 RTSP 地址之后,就可以写 go2rtc 的配置文件了。先看一个实际的例子:

streams:
  客厅摄像头:
    - rtsp://admin:password@192.168.1.100:554/Streaming/Channels/101
  院子摄像头:
    - rtsp://admin:password@192.168.1.101:554/cam/realmonitor?channel=1&subtype=0
  测试摄像头:
    - ffmpeg:rtsp://admin:password@192.168.1.102:554/Streaming/Channels/102

每个流名称下面的列表表示“源地址”,也就是 go2rtc 可以去哪里拉流。默认情况下,go2rtc 会优先用内置的原生 RTSP 客户端去拉流,如果拉流失败或者源是 RTMP、HTTP 这种它原生不支持的类型,它会自动调用容器里的 FFmpeg 去处理。

你也可以像我示例里那样,在源地址前手动加 ffmpeg: 前缀,强制让某一路流走 FFmpeg 处理。需要强制走 FFmpeg 的典型场景有两个:一是摄像头 RTSP 地址非常不标准、原生客户端拉不出来;二是需要对画面做缩放、水印等处理。但要注意,走 FFmpeg 的流 CPU 占用会明显升高,能走原生 RTSP 就尽量走原生。

写完之后重启容器让配置生效:

docker exec go2rtc kill -1 1

或者直接重启容器:

docker restart go2rtc

然后在 Web UI 里刷新,就能看到刚才配置的流名称了,点击即可预览画面。如果显示失败,旁边会给出具体的错误日志,这个日志对后面排查问题非常有用。

3.3 多协议播放地址说明

配置好之后,go2rtc 会自动为每一路流提供多种协议的访问地址。以下地址中的 cam1 换成你的流名称即可。

浏览器直接播放,推荐用 HLS:

http://你的服务器IP:1984/api/hls/cam1.m3u8

需要兼容老旧播放器或低版本浏览器,可以用 HTTP-FLV:

http://你的服务器IP:1984/api/flv/cam1.flv

只需要在网页上显示一帧一帧的画面,像 web 后台的“实时预览缩略图”这种场景,用 MJPEG 最省事:

http://你的服务器IP:1984/api/mjpeg/cam1.mjpeg

其他的流媒体服务、NVR 软件、VLC 播放器,可以直接用 go2rtc 转出来的 RTSP 地址:

rtsp://你的服务器IP:8554/cam1

实际使用中我的习惯是:网页预览全部走 WebRTC 或 HLS,NVR 和录像服务走 RTSP,对接展示大屏这种不太需要交互的页面走 MJPEG。go2rtc 能在同一路源上同时输出这几种协议,互不干扰,这是它比很多方案方便的地方。

4. WebRTC 低延迟与进阶玩法

4.1 WebRTC 延迟为什么能做到亚秒

如果你之前用过 HLS 看摄像头,应该有体会:画面延迟普遍在 3 到 10 秒,转个身要等好几秒才在屏幕上看到,这种感觉很憋屈。HLS 的切片机制决定了它天生就有延迟,因为它要求播放器先下载完一个切片再播放。

WebRTC 走的是 UDP 直连加 P2P 协商,端到端延迟能做到几百毫秒,观感上基本是“所见即所得”。而且 WebRTC 是浏览器原生支持的,不需要装任何插件,Chrome、Edge、Firefox 都能直接播放。go2rtc 内部对 WebRTC 做了比较成熟的信令和媒体协商处理,我实测在局域网环境下,go2rtc 的 WebRTC 预览延迟大概在 0.3 到 0.8 秒之间,这个延迟水平已经接近专业监控客户端的效果了。

4.2 配置 WebRTC 拉流

go2rtc 默认在 8555 端口监听 WebRTC 连接。不过要让 WebRTC 在大多数场景下稳定工作,需要在配置文件里指定本机地址,也就是告诉客户端“你要把媒体数据发到哪个地址”。

webrtc:
  listen: ":8555"
  candidates:
    - 你的服务器IP:8555

candidates 是 ICE 协商时使用的候选地址。如果你只是局域网内使用,填服务器的内网 IP 就够了。如果客户端和服务器不在同一个网络,那你需要填服务器在公网上的地址,并且保证 8555 的 TCP 和 UDP 都能从外部访问到。这个就是 WebRTC 能不能连上的关键,后面排查章节我会详细讲。

配置完后,在 Web UI 里点击流的播放按钮,选择 WebRTC 播放方式,或者直接在支持 WebRTC 的播放器里接入:

http://你的服务器IP:1984/api/webrtc?src=cam1

4.3 硬件加速转码

如果你的服务器有 Intel 核显或者 AMD 显卡,可以让 go2rtc 在需要转码的时候调用硬件加速,大幅降低 CPU 负担。这个不是必选项,但如果你接了十路以上的摄像头,或者有多路走 ffmpeg: 的流,建议还是配置上。

首先确认宿主机上有显卡设备,然后启动容器时把设备挂进去:

services:
  go2rtc:
    image: alexxit/go2rtc:latest
    container_name: go2rtc
    restart: unless-stopped
    devices:
      - /dev/dri/renderD128:/dev/dri/renderD128
    ports:
      - "1984:1984"
      - "8554:8554"
      - "8555:8555/tcp"
      - "8555:8555/udp"
    volumes:
      - /opt/go2rtc/go2rtc.yaml:/config/go2rtc.yaml

然后在 go2rtc.yaml 里启用硬件解码:

ffmpeg:
  hwaccel: vaapi
  hwaccel_device: /dev/dri/renderD128

配置好之后,走 FFmpeg 转码的流就会用 VAAPI 加速解码。注意, renderD128 这个设备节点的编号可能会因机器而异,建议先 ls /dev/dri 确认一下再挂载。

4.4 对接 Home Assistant 与 Frigate

很多朋友搭摄像头流媒体平台,终极目标是接入 Home Assistant 或者 Frigate 做智能监控。go2rtc 跟这两个项目的配合非常成熟。

Frigate 现在原生支持 go2rtc 作为流代理,在 Frigate 的配置里加一段:

go2rtc:
  host: 你的服务器IP
  port: 1984

然后在摄像头配置里直接引用 go2rtc 的流:

cameras:
  front_door:
    ffmpeg:
      inputs:
        - path: rtsp://你的服务器IP:8554/front_door
          roles:
            - detect

这样做的好处是,Frigate 只需要对接 go2rtc 统一转换后的 RTSP 流,不用直接连各种品牌摄像头,稳定性会好很多。

Home Assistant 的集成方式类似,在配置里加上 go2rtc 地址,然后就可以把 go2rtc 里面的摄像头直接当作“摄像头实体”用,配合卡片在仪表盘里实时预览。这块玩起来花样挺多,篇幅原因这里不展开,先知道“能对接”就行。

5. 踩坑实录:常见问题与排查思路

5.1 拉流失败排查

我在接入摄像头的过程中,遇到最多的问题就是“流状态显示失败”。go2rtc 的 Web UI 要么给出错误信息,要么流状态一直转圈。这种情况我一般按照下面的顺序排查。

先确认摄像头地址能不能单独播放。把 RTSP 地址复制到 VLC 里试播一下,如果 VLC 能放,说明地址和账号没问题,问题在 go2rtc 这边;如果 VLC 也放不了,那就是地址格式或账号密码的问题,不用再往下看。

再看 go2rtc 的日志。日志里如果出现 connection refused 、 unauthorized 、 timeout ,分别对应的原因大概是:IP 和端口不对、用户名密码错误、摄像头连接数已达上限或者网络不通。摄像头尤其是海康的,默认并发连接数有限,被预览、录像软件占用满后,新连接就会被拒掉,这时候去摄像头后台把最大连接数调大即可。

如果日志显示 failed to parse 之类的信息,通常是 RTSP 地址里的特殊字符没有转义,重点检查密码里是否有 @ 、 # 、 : 等字符。

5.2 WebRTC 黑屏/连不上的排查

WebRTC 配置出来之后,浏览器打开黑屏或者一直连接中,是另一大高频问题。我经历过几次,总结下来主要是三个原因。

第一个是端口没完全映射。WebRTC 核心是 UDP 媒体传输,如果只映射了 TCP 的 8555 ,UDP 数据过不来,画面肯定出不来。Docker 映射的时候要同时加上 8555/udp 。

第二个是 candidates 配置不对。go2rtc 在信令协商时会把自己认为可用的候选地址发给浏览器,如果这个地址填的是 localhost 或者内网 IP,而浏览器在另一个网段,媒体数据就发不过去。解决方法就是在 webrtc 配置里显式指定服务器在客户端可达的地址。

第三个是浏览器自动播放策略。有些浏览器限制了页面加载后自动播放音视频,需要用户先点击页面上的播放按钮。这个不算 bug,但经常被误认为是 WebRTC 的问题。go2rtc 的 Web UI 默认是点击播放,所以一般只有接第三方面板时才会遇到,了解即可。

5.3 Docker 层面的坑

容器部署本身也有一些容易忽略的细节。比如宿主机防火墙。很多 Linux 发行版默认防火墙是开启的,只开了 443 和 80,导致 go2rtc 的 1984 、 8554 、 8555 端口外部根本访问不到。处理方式很简单,放行这三个端口:

firewall-cmd --permanent --add-port=1984/tcp
firewall-cmd --permanent --add-port=8554/tcp
firewall-cmd --permanent --add-port=8555/tcp
firewall-cmd --permanent --add-port=8555/udp
firewall-cmd --reload

不同发行版的防火墙命令不一样,核心思路就是保证这三个端口在上层防火墙和云服务商安全组里都是放行状态。

再一个就是容器时区的问题。前面提到加 TZ=Asia/Shanghai 环境变量,如果你发现 go2rtc 日志时间和本地时间差 8 小时,那就是时区没有设置。这个不直接影响播放,但排查录像或日志时间线的时候会非常痛苦。

另外,升级 go2rtc 版本前记得备份 go2rtc.yaml 和容器内数据卷里的内容。虽然 go2rtc 配置很简单,但万一遇到升级后配置格式不兼容的问题,至少能快速回滚。

5.4 多路并发与稳定性调优

摄像头数量多起来之后,另一个常见问题是资源占用和稳定性。我接入过十几路摄像头,总结了几条实用经验。

一是子码流优先。预览和检测场景优先使用子码流,主码流只在需要回放或截图清晰画面时使用。子码流分辨率低、带宽小,对服务器压力和摄像头本身压力都小很多。

二是能复制流就不要重复转码。go2rtc 对同一路源支持多协议同时输出,底层是同一份数据,这点非常省资源。反而是强制 ffmpeg: 处理之后,每一路流都可能产生额外的解码编码开销,能不加就不加。

三是关注摄像头连接数。每路流被 go2rtc 拉取后,在摄像头那边就占了一个连接。如果多个服务同时拉同一路流,摄像头连接会被占满。解决方式就是把 go2rtc 当作统一网关,所有下游服务都从 go2rtc 拉流,而不是直接连摄像头。

四是日志的滚动。容器跑久了,日志可能会变得很大,建议在 docker-compose 里加日志滚动限制:

logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

5.5 问题速查表

最后把常见问题整理成一个速查表,方便直接对照。

现象 可能原因 解决办法
Web UI 打不开 防火墙/端口映射未生效 确认 1984 端口映射并放行
流状态失败 RTSP 地址或账号错误 用 VLC 试播确认地址
拉流延迟高 摄像头连接数占满 调大摄像头最大连接数
WebRTC 黑屏 UDP 端口未映射或 candidates 错误 补映射 8555/udp,配置正确 candidates
日志时间差 8 小时 缺少时区设置 添加 TZ=Asia/Shanghai
CPU 居高不下 大量 ffmpeg 转码 启用 VAAPI 或使用子码流
Docker 升级后配置兼容问题 版本差异 升级前备份 go2rtc.yaml

写在后面

这套 go2rtc + Docker 的方案我实际跑了有大半年,期间经历了几次摄像头换机、服务器迁移,修改基本都只动 YAML 文件,容器本身一直很稳定。说起来,最让我满意的不是某个单一功能,而是它把“接入摄像头”这件事的成本压到了极低——不用记一长串 FFmpeg 参数,不用给每台服务器单独编译依赖,Docker 一把梭,配置文件一发,新的摄像头几分钟就能上线。

最后给你一个建议:如果之前一直用摄像头厂商的 App 看监控,第一次用 go2rtc 在浏览器里把画面拉起来的时候,那种“所有品牌终于统一了”的感觉,真的挺值得体验的。后面如果愿意,还可以接上 Frigate 做人物识别、接上 Home Assistant 做自动化场景联动,扩展空间比你想的还要大。

Logo

邀请您加入社区

更多推荐