前几年折腾过一套摄像头监控方案,最头疼的事就是家里摄像头品牌太杂——大门口的用RTSP,阳台的只支持厂商私有协议,再加上一些旧设备只出RTMP流,光是把这些流统一拉到一个界面里就要装好几个App。后来我尝试用 Docker 部署 go2rtc 做了个多协议流媒体平台,把RTSP、RTMP、HLS、WebRTC这些格式全部揉到同一个服务里,局域网内通过网页、VLC、Home Assistant 都能直接看,而且整个服务占用的内存极低。这篇文章就完整记录一下我实际部署 go2rtc 的过程,从Docker启动、摄像头流接入、协议转换到常见问题的排查,适合正在折腾摄像头接入、想统一管理多品牌流媒体、或者想给Home Assistant补一个低延迟视频源的开发者参考。

1. 内容整体设计与思路拆解

1.1 为什么选 go2rtc,而不是一上来就用 FFmpeg 或商用 NVR

很多人在摄像头流统一接入时,第一反应是“用 FFmpeg 拉流再转推”,或者干脆上一台成品NVR录像机。这两种方案都能解决问题,但各有各的别扭。

先说说 FFmpeg 思路。FFmpeg 确实是万能的,但它的定位是“转码工具”,不是“流媒体服务”。你需要自己写脚本、管理进程、处理断流重连、维护多个协议监听端口,稍微复杂点儿的场景,比如不同摄像头走不同编码格式、客户端有的要RTSP有的要HLS,脚本逻辑就会膨胀得很难维护。而且 FFmpeg 一次转码就是一路进程,CPU占用高,摄像头多了主机扛不住。

商用 NVR 的问题更明显:协议封闭。很多 NVR 只认自家摄像头,海康的接大华、大华接 TP-LINK,虽然部分支持ONVIF,但跨品牌接入时经常出现子码流不支持、云台控制失效、报警推送对不上等问题,折腾一圈比部署开源方案还费劲。

go2rtc 这个项目解决的是“流媒体汇聚与分发”这个中间层问题。它是用 Go 写的,运行起来非常轻量,默认只需要一个二进制文件,内存占用通常几十MB级别。它把不同来源的摄像头流拉进来之后,会用统一的方式对外输出,前端可以直接通过 WebRTC 低延迟观看,也可以通过 HLS 给普通播放器使用,还能继续输出 RTSP 给 NVR 录像,或者把流接入 Home Assistant 作为摄像头实体。最实用的是,go2rtc 对不支持的协议可以调用 FFmpeg 做补充,相当于“服务框架 + 转码工具”组合拳,而不需要你自己从零拼装。

1.2 典型的摄像头接入架构是什么样

我实际部署下来的架构大概是这样的:

  • 摄像头端:可能走 ONVIF/RTSP,也可能走 RTMP、HTTP-FLV,甚至某些设备只能输出 HLS。
  • go2rtc 端:在 Docker 容器里运行,主动去拉取这些流,并按名称注册成一个个“流”。它内部会把协议差异消化掉,统一以多种协议对外暴露。
  • 客户端端:浏览器打开 go2rtc 的 Web 界面,用 WebRTC 或 MSE 直接预览;VLC 用 RTSP 地址拉流;Home Assistant 通过 Stream 组件读取;NVR 或录像程序也可以用 RTSP 接入录制。

这个“多入多出”的架构是我最喜欢的部分。比如一个摄像头本身只支持 RTSP,但我希望手机端能低延迟观看,同时网页端又需要 HLS 兼容,NVR 还要继续录像。没有 go2rtc 时,你只能让摄像头同时推多路流,或者自己在中间做转分发。有了 go2rtc 之后,所有客户端都从 go2rtc 这一个入口拿流,摄像头到 go2rtc 只需要一条拉流连接,压力小了很多。

1.3 go2rtc 支持哪些主流协议

go2rtc 官方协议支持列表很广,实际部署中我用到的核心协议有这几个:

  • RTSP (输入/输出):最常见,大部分安防摄像头都支持。
  • RTMP (输入/输出):用于老的推流设备或直播源。
  • HLS (输入/输出):适合网页播放、低兼容场景。
  • WebRTC (输出):浏览器低延迟播放的首选。
  • HTTP-FLV (输入/输出):嵌入式设备或某些直播平台会用到。
  • MJPEG (输入):老式网络摄像头常输出 MJPEG 流。
  • MP4 (输入/输出):适合回放或录像片段生成。

当然 go2rtc 底层也会调用 FFmpeg 处理一些不常见格式,但上面这些是其原生能力。选它做核心服务,基本覆盖了家用和中小型项目的摄像头接入需求。

2. Docker 部署 go2rtc 实操

2.1 环境准备与镜像选择

部署 go2rtc 之前,先确认一下主机环境。我用的是局域网内一台 Debian 12 主机,Docker 版本是 24.x,其实只要是能跑 Docker 的机器都可以,包括 Windows 的 Docker Desktop、群晖 NAS 的 Container Manager、甚至是树莓派这种 ARM 设备。

先拉取镜像,官方镜像是 alexxit/go2rtc :

docker pull alexxit/go2rtc:latest

如果拉取速度特别慢,可以在 Docker 的 daemon.json 里配一个国内镜像加速器,我这边实测换成加速器之后速度从几分钟降到十几秒。另外要注意,有些机器如果 Docker Desktop 启动时报“virtualization support not detected”,先去 BIOS 里打开虚拟化(VT-x/AMD-V),Windows 还需要启用 Hyper-V 或 WSL2 后端,这个问题在 Windows 宿主机上尤其常见。

go2rtc 运行时主要需要三个端口:

  • 1984:Web 管理界面和 HTTP/HLS 流量端口,也是默认的主端口。
  • 8554:对外提供 RTSP 服务的端口。
  • 8555:WebRTC 的 UDP/TCP 端口,浏览器低延迟播放时依赖它。

2.2 docker run 快速部署

如果想最快速度跑起来,直接用一条 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 \
  -e TZ=Asia/Shanghai \
  alexxit/go2rtc:latest

注意我把 8555 的 TCP 和 UDP 都映射出来了。很多网上的教程只映射 UDP,跨网络或浏览器行为异常时才意识到还差 TCP。容器内部的配置目录是 /config ,我把它挂载到宿主机的 /opt/go2rtc/config 目录,这样可以持久化保存配置,后续修改流信息不需要重建容器。

启动后先看下日志,确认服务是否正常:

docker logs -f go2rtc

如果看到类似 listening on 0.0.0.0:1984 的输出,说明服务已经起来了。这时候打开浏览器访问 http://宿主机IP:1984 ,应该能看到 go2rtc 的 Web 界面,虽然还没有任何摄像头流,但界面的 Streams 和 Logs 页面已经可以正常访问。

2.3 docker-compose 正式部署

docker run 适合临时试玩,但正式使用我更推荐 docker compose。用 compose 的好处是配置可版本化管理,后续换机器迁移非常方便。

我目前用的 docker-compose.yml 是这样:

version: "3.8"

services:
  go2rtc:
    image: alexxit/go2rtc:latest
    container_name: go2rtc
    restart: unless-stopped
    ports:
      - "1984:1984"
      - "8554:8554"
      - "8555:8555/tcp"
      - "8555:8555/udp"
    volumes:
      - ./config:/config
    environment:
      - TZ=Asia/Shanghai
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

启动命令:

docker compose up -d

这里有几个细节值得解释。 restart: unless-stopped 保证宿主机重启后容器能自动拉起,监控服务不能依赖手动启动; ./config:/config 是把当前目录下的 config 目录映射进容器,所以我在项目目录下提前建了 config 文件夹;日志大小限制是我踩过坑之后加的,默认 docker json-file 日志不限制大小,go2rtc 调 FFmpeg 时日志量很大,不及时清理会把磁盘写满。

2.4 容器启动后的初始化检查

容器正常运行后,不要急着配摄像头。我习惯先做两类检查。

第一是端口连通性检查。在另一台机器上执行:

curl http://宿主机IP:1984/api/streams

如果返回 JSON 数组,哪怕是空的,也说明 Web API 正常。再用 VLC 打开 rtsp://宿主机IP:8554/ ,正常情况下会提示需要提供流名称,而不是直接连接拒绝。

第二是查看日志里的错误。如果之前端口被占用,docker run 阶段就会报错退出。如果容器起来但在外面访问不了 1984,大概率是宿主机防火墙没放行端口,Debian/Ubuntu 用 ufw allow 1984/tcp ,CentOS 用 firewall-cmd --add-port=1984/tcp --permanent && firewall-cmd --reload 。

3. 配置摄像头流与多协议接入

3.1 go2rtc 配置文件格式

go2rtc 的配置文件默认叫 go2rtc.yaml ,放在 /config 目录下。如果挂载目录里没有这个文件,第一次启动后 go2rtc 会自动生成一个占位配置,然后 Web 界面上也可以添加流,但最终配置会写回 yaml 文件。

我先给一个只有两个摄像头的配置示例:

streams:
  front_door:
    - rtsp://admin:password@192.168.1.100:554/stream1
  backyard:
    - rtsp://admin:password@192.168.1.101:554/stream2

每个摄像头在 streams 下用自定义名称作为 key,值是一个列表。列表里可以放多个地址,go2rtc 会按顺序尝试连接,第一个地址失败会自动切换到下一个,这个特性在做主备源时特别有用。比如一个主摄像头同时推 H.265 和 H.264 两路流,我可以把 H.264 作为 fallback:

streams:
  livingroom:
    - rtsp://admin:password@192.168.1.110:554/h265
    - rtsp://admin:password@192.168.1.110:554/h264

要注意的是,虽然 go2rtc 支持 name 里带中文或空格,但为了后续在 URL 里引用方便,建议使用纯英文、下划线和小写字母。我一开始给摄像头命名成“Camera 01”,结果在 VLC 里拼接 RTSP 地址时总是出现编码问题,改成 camera_01 后一劳永逸。

配置文件的修改不是立即生效的,有两种方式让它生效:一是直接删除容器重建, docker compose restart 只能重启容器,但 go2rtc 会在启动时重新加载配置;二是用 Web 界面上的 Streams 页面“添加流”,界面自动保存并热加载。我建议直接用 Web 界面添加,然后去配置文件里检查最终生成的参数,学习效果更快。

3.2 拉流参数优化:主码流、子码流与 TCP 模式

摄像头通常有两路流:主码流分辨率和码率高,适合录像和全屏预览;子码流分辨率低,适合密集网格预览或低带宽场景。go2rtc 里添加流时,URL 本身就是摄像头提供的,所以我通常会单独建两个流名称,比如 front_door_main 和 front_door_sub 。

很多品牌的 RTSP 地址路径不太一样,常见的几种格式:

  • 海康/萤石: rtsp://user:pass@ip:554/Streaming/Channels/101 (101 表示主码流,102 表示子码流)
  • 大华: rtsp://user:pass@ip:554/cam/realmonitor?channel=1&subtype=0 (subtype=0 主码流,1 子码流)
  • TP-LINK: rtsp://user:pass@ip:554/stream1 (主码流), /stream2 (子码流)
  • 宇视: rtsp://user:pass@ip:554/media/video1 (主码流), video2 (子码流)

如果摄像头默认走 UDP 拉流不稳定(经常卡顿、花屏),go2rtc 也支持通过参数的强制连接模式。在 go2rtc 的 RTSP 源里,可以用 #tcp 强制使用 TCP:

streams:
  front_door:
    - "rtsp://admin:password@192.168.1.100:554/stream1#tcp"

我遇到的情况是,同一局域网内大部分摄像头 UDP 也能很稳定,但是只要跨交换机或者 WiFi 链路,UDP 丢包就很明显,强制 TCP 之后画面稳定了不少。代价是 TCP 会占用略高一点的带宽,但对局域网来说基本可以忽略。

3.3 转码与硬件加速:让 H.265 摄像头也能兼容播放

很多新摄像头的默认编码是 H.265/HEVC,码率低、清晰度高,但浏览器和部分播放器不支持直接播放。HLS 和 WebRTC 的输出如果遇到 H.265,轻则白屏,重则整个流都拉不起来。go2rtc 的做法是先尝试内部转码,同时支持调用外置 FFmpeg 做转换。

在 go2rtc 配置中,可以通过 ffmpeg 前缀来让某一路流经过 FFmpeg 处理:

streams:
  front_door_pc:
    - "ffmpeg:rtsp://admin:password@192.168.1.100:554/stream1#video=h264#video_param=preset,ultrafast#video_param=tune,zerolatency"

这里 #video=h264 表示把视频转成 H.264, #video_param 是传给 FFmpeg 的编码参数, preset=ultrafast 和 tune=zerolatency 都是为了降低转码延迟。实际测试中,对于 1080P 摄像头,纯 CPU 转码大约会吃掉一个核心的 60% 左右,所以不建议同时在很多路流上做转码。

如果主机有支持 Quick Sync 的 Intel 核显,可以在 docker compose 里把 /dev/dri 设备映射进容器,然后在 go2rtc 配置中启用 VAAPI 硬件转码:

ffmpeg:
  bin: ffmpeg
  vaapi: /dev/dri/renderD128

硬件转码的 CPU 占用可以下降一大截,但同时也会增加配置复杂度。如果没有硬解需求,建议先把摄像头编码改成 H.264,这是最简单省事的方案。

3.4 多协议出口配置详解

go2rtc 服务跑起来之后,每一路 stream 都会自动拥有多个协议的访问地址。以流名 front_door 为例,常用的访问 URL 是:

协议 地址示例 适用场景
WebRTC 浏览器打开 http://IP:1984/api/webrtc?src=front_door 低延迟网页播放
HLS http://IP:1984/api/stream.m3u8?src=front_door 通用网页播放器
RTSP rtsp://IP:8554/front_door VLC、NVR、Home Assistant
HTTP-FLV http://IP:1984/api/stream.flv?src=front_door 低延迟 Flash/JS 播放
MSE http://IP:1984/api/stream.mse?src=front_door 浏览器 MSE 播放

如果你希望某些流对外不可见,可以在 stream 后面加 #hidden ,避免在 Web 界面上暴露。比如只用于内网录像的流就可以隐藏起来。

如果你需要让外网也能通过 WebRTC 低延迟播放,需要给 go2rtc 配置 STUN/TURN。在局域网内默认就能播放,但跨网段时涉及 NAT 穿透,我通常会在 go2rtc.yaml 里加一段:

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

如果完全无法建立 WebRTC 连接,又不方便搭 TURN 服务,建议退回 HLS 或 RTSP 方案,效果虽然延迟高一些,但胜在稳定。

4. 核心功能与场景应用

4.1 Web 界面实时预览与流管理

go2rtc 自带一个非常简洁的 Web 管理界面,不需要再装任何前端组件。浏览器打开 http://IP:1984 后,Streams 页面会列出所有已经配置的流。

点击任意一个流名,界面会直接弹出播放器,默认走 WebRTC,延迟基本在 0.5 秒以内,非常跟手。如果你只想快速确认画面是否正常,这个功能比打开 VLC 方便太多。

Web 界面上还有一个“添加流”的入口,可以直接粘贴 URL,填写流名称后保存。它最终会把配置写回 yaml 文件,并自动 reload。我后来深入了解发现,它就是调用了 go2rtc 暴露的几个 API,比如 GET /api/streams 获取流列表, POST /api/streams 添加流,所以如果你有前端开发需求,可以直接复用这套 API。

4.2 与 Home Assistant 集成

如果你在用 Home Assistant,把 go2rtc 挂到里面当摄像头源是一个刚需。官方推荐的方式是在 HA 里直接安装 go2rtc 插件,但如果你已经用 Docker 单独部署了 go2rtc,也可以只在 HA 里配置 generic camera。

在 configuration.yaml 中增加:

camera:
  - platform: generic
    name: Front Door
    stream_source: rtsp://192.168.1.200:8554/front_door

保存并重启 HA 后,前端 Lovelace 添加到实体,通过 stream 组件就能看到实时画面。但在实际使用中,HA 默认的摄像头组件拉 RTSP 是用 FFmpeg,延迟明显偏高,画面经常延迟 2-3 秒。如果你要求低延迟,可以配合 HA 的 WebRTC Camera 卡片,直接用 go2rtc 的 WebRTC 输出地址:

camera:
  - platform: generic
    name: Front Door Low Latency
    stream_source: http://192.168.1.200:1984/api/webrtc?src=front_door

不过要注意,HA 的 generic camera 对 stream_source 的支持程度依赖系统安装的 stream 组件,建议先确认 HA 侧能正常播放 RTSP 源,再切入 WebRTC。

4.3 对接外部播放器与其他监控系统

go2rtc 的流地址可以非常方便地接进各种现有系统。

VLC 是最通用的测试工具。在 VLC 里打开网络串流,输入 rtsp://IP:8554/front_door ,如果能出画面说明 RTSP 出口正常。如果你有支持 RTSP 的 NVR,也可以把这路流作为“IP 摄像头”添加到 NVR 里,等于把 go2rtc 作为一个虚拟摄像头网关。这里需要注意,NVR 连接数有限,go2rtc 已经会缓存来自摄像头的流,多个客户端共用一路拉流,所以不至于把摄像头本身的连接数耗尽。

网页端如果想嵌入已有的监控大屏,可以用 HLS 地址。我在一个内部看板项目里直接用了 <video> 标签播放 HLS,通过 hls.js 库就能实现,代码大概就几行:

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="video" controls autoplay muted></video>
<script>
  const video = document.getElementById('video');
  const hls = new Hls();
  hls.loadSource('http://IP:1984/api/stream.m3u8?src=front_door');
  hls.attachMedia(video);
</script>

这种方式的延迟通常在 2-5 秒,但胜在兼容所有现代浏览器,不需要额外插件。

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

5.1 容器启动失败或 Web 界面打不开

先确认容器状态:

docker ps -a | grep go2rtc

如果容器已经退出,看日志:

docker logs --tail 50 go2rtc

最常见的启动失败原因是端口被占用。1984 或 8554 如果被其他服务占用,go2rtc 会报 address already in use 。先检查端口占用:

ss -lntp | grep -E '1984|8554|8555'

如果端口被占用,可以修改 compose 里的对外映射,比如改成 8084:1984 ,但注意 go2rtc 内部默认端口不变,只改宿主机映射那一侧即可。

另一个容易忽略的问题是防火墙。很多 Linux 发行版默认开 ufw 或 firewalld,即使容器端口映射正确,外部流量也进不来。Debian/Ubuntu 放行示例:

sudo ufw allow 1984/tcp
sudo ufw allow 8554/tcp
sudo ufw allow 8555/tcp
sudo ufw allow 8555/udp

5.2 摄像头画面一直黑屏或提示拉流失败

摄像头配置好后,Web 界面如果一直转圈或提示 error,首先去 Logs 页面看日志。重点看两行:一是是否成功连接摄像头,二是编码是否被正确识别。

如果日志里出现 unauthorized ,说明 RTSP 地址里的用户名或密码不对,需要确认摄像头管理后台里的认证信息。有些摄像头默认关闭 RTSP 服务,需要在摄像头的网络设置里打开 RTSP 开关。

如果出现 unsupported codec 或画面能出来但花屏,大概率是编码问题。先把摄像头主码流的编码手动改成 H.264,关闭 H.265/H.265+。改完还不行,看看是不是音频编码导致 go2rtc 解析出错,比如某些摄像头带 G.711 音频会正常,但带 AAC 低延迟流可能异常。你可以先临时注释掉音频相关的配置,或者用 ffmpeg 前缀强制 #audio=copy 试试。

如果摄像头在公网或跨网段,且 UDP 丢包严重,就把拉流地址改成 #tcp 强制走 TCP。我测试过一个在 WiFi 链路下的摄像头,UDP 模式画面每隔几秒就卡顿一次,改成 TCP 后稳定了一个多小时。

5.3 WebRTC 无法播放或卡在黑屏

WebRTC 是局域网低延迟播放的“大杀器”,但也最容易出问题。

首先确认浏览器是否允许自动播放音频。Chrome 中如果标签页长时间没有用户操作,WebRTC 的音频可能会被静音,视频画面一般正常,但有时会误以为黑屏。可以在页面里加一个自动播放属性或者手动点击一下播放按钮。

其次是端口可达性。WebRTC 使用 8555 的 UDP 端口,如果宿主机防火墙只放行了 TCP,UDP 被挡会导致连接一直协商不成功。检查 UDP 放行,或者用 tcpdump 抓包确认 8555 UDP 是否有进出流量。

如果 go2rtc 部署在 NAT 之后,而客户端又在不同局域网,单靠默认配置很难穿透。此时需要配置 STUN/TURN。内网环境我一般直接不折腾,退到 HLS 或 RTSP 播放,毕竟低延迟不是所有场景都必需。

5.4 多路流同时拉取时性能如何优化

go2rtc 本身非常省资源,纯转发不转码的话,一个 CPU 核心跑几十路流没问题。真正吃资源的是 FFmpeg 转码和 WebRTC 的并发编码。

如果你的方案里既有主码流又有子码流,建议对外只暴露子码流给网页预览,主码流只给 NVR 录像使用。这样可以避免同一条摄像头流被多次拉取和转码。内存方面,go2rtc 每路流通常只占 30MB 左右,但如果你用 ffmpeg 转码,每路额外增加 100-200MB 内存,所以摄像头数量多的时候,优先考虑硬解或直接让摄像头输出 H.264。

我在实际项目里最多接入过 16 路 1080P 摄像头,不带转码时 CPU 占用不到 5%,内存不到 500MB,非常轻松。但如果全部开 WebRTC 预览同时还要转 HLS,CPU 占用会明显上去,所以我的方案是:WebRTC 只用于紧急预览,日常看画面走 HLS 子码流,录像走 RTSP 主码流。

5.5 实用小技巧:用 go2rtc 做录像与回放

go2rtc 本身不负责录像,但你可以配合 FFmpeg 的命令把流存成切片。我在计划任务里写了一段简单的循环脚本,每 10 分钟从 go2rtc 拉一段 RTSP 流存成 mp4:

ffmpeg -i rtsp://localhost:8554/front_door -c copy -f segment -segment_time 600 -strftime 1 "/mnt/recordings/front_door_%Y%m%d_%H%M%S.mp4"

这样做的最大好处是,摄像头到 go2rtc 只有一条连接,无论多少个客户端或录像任务,都不会给摄像头增加连接压力。录制文件的回放也很简单,用 VLC 直接打开 mp4 即可,或者再配合一个简单的文件列表页面就能做轻量级回放平台。

最后说几句

试过不少流媒体方案之后,我的体会是:go2rtc 最适合的场景不是大规模商业监控,而是“把散落的、多协议的摄像头流统一起来,快速接入自己的系统”。Docker 化部署让它在迁移、备份、升级时都非常省心,配置文件跟着目录走,一条 compose 文件就能在另一台机器恢复整个服务。如果你也正在为多品牌摄像头接入发愁,不妨先用一个 docker run 命令把 go2rtc 跑起来,再在 Web 界面上添加你的第一路 RTSP 流,后面自然会越来越顺手。

Logo

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

更多推荐