用go2rtc在Docker中统一多协议摄像头流,实现WebRTC低延迟预览
搞摄像头接入这件事,最烦的不是摄像头本身,而是协议不统一。我手头既有海康的枪机,又有大华的半球,还有一块树莓派上插着OV5647做的自制摄像头,每路设备的取流方式都不一样。以前做本地监控预览,要么在浏览器里装插件,要么自己写一套转流服务,维护起来很头大。后来在 Docker 里部署了 go2rtc,把 RTSP、ONVIF、V4L2 这些不同协议的摄像头统一收敛成 WebRTC、HLS、MJPEG 等标准输出,才算是把这摊子事彻底理顺了。
这篇文章我会从零讲一遍完整思路:为什么选 go2rtc、Docker Compose 怎么编排、摄像头接入时 RTSP 地址怎么写、H.265 编码的流怎么兼容浏览器,以及我实际跑下来遇到的坑和排查方法。内容面向想把本地多路摄像头统一接入、或者想在 Home Assistant 里做低延迟预览的朋友,对 Docker 有最基础的了解就能跟着操作。
1. 为什么用 go2rtc 来统一摄像头流
1.1 摄像头协议碎片化,这是最让人头疼的一关
先还原一下我最初遇到的场景。装了三路摄像头,实际要用的有三个不同来源:
-
海康枪机:标准 RTSP 协议,地址是
rtsp://admin:密码@ip:554/Streaming/Channels/101 -
大华半球:同样是 RTSP,但路径格式完全不同,是
/cam/realmonitor?channel=1&subtype=0 -
树莓派 + OV5647 摄像头:压根没有 RTSP 服务,只能从
/dev/video0读取 V4L2 原始视频
这三路流,如果分别去喂给不同的播放器或者平台,光是地址映射就能把人搞疯。而且大多数浏览器不支持 RTSP,原生 video 标签直接播不了,这就要么装 VLC Web 插件,要么写个后端把 RTSP 转成 HLS,转出来的播放延迟还经常卡在 5 秒以上。
所以问题不是“怎么播放一路流”,而是“怎么把多路不同协议的流,统一成一个稳定的视频平台向外输出”。go2rtc 干的正是这件事:它同时兼任流媒体网关和协议转换器,接管所有摄像头源,再以统一的 HTTP API 和标准协议暴露出来,下游接浏览器、接 Home Assistant、接录像机都行。
1.2 go2rtc 的核心特性,以及我觉得最值钱的设计
先说结论:go2rtc 是 AlexxIT 开源的轻量级流媒体工具,在 Home Assistant 生态里非常常见。它不是监控录像软件,而是“实时取流与转发出”的那一层中间网络。
主要特性可以这样列:
- 多种输入协议 :RTSP、RTMP、HLS、MJPEG、V4L2 设备、USB 摄像头、SIP 等
- 多种输出协议 :WebRTC、HLS、MJPEG、RTSP、RTMP,一个源可以同时出多路
- 自动能力探测 :连接摄像头时先探测支持的编码格式,再决定是否需要调用 ffmpeg 做转码
- WebRTC 低延迟播放 :局域网内实测延迟能压到几百毫秒,这是它最吸引我的点
- Web 管理界面 :默认 1984 端口,所有流的状态、推流地址、log 一眼就能看到
- 资源占用低 :纯 Go 编译产物,单个容器跑在树莓派这种小机器上毫无压力
它最聪明的地方,是默认采用“原生协议转发”,不把所有视频全部重新编码。举个例子:海康摄像头吐出的是 H.264 编码的 RTSP 流,go2rtc 拿到之后直接以 H.264 原始编码通过 WebRTC 发到浏览器,中间不做二次转码。转码是非常消耗 CPU 的操作,能在协议层直接转发掉,就等于把成本和延迟同时压低了。
那什么时候才会转码?当源编码格式和下游播放端不匹配时。最常见的就是 H.265/HEVC:Windows 和 Android 上不少浏览器不支持原生解码,这时 go2rtc 才会按配置调用 ffmpeg,把 H.265 转成 H.264 再输出。这种兜底转码的思路,保证了选择权始终在你手里。
1.3 和 Frigate、ZoneMinder 这类方案对比,我最终怎么选
可能有人会问:Frigate、ZoneMinder 不是也能做摄像头接入吗?它们和 go2rtc 不是同一种东西。Frigate 的核心是 NVR 和 AI 检测,它强调录像、物体识别、事件管理;ZoneMinder 则是一套传统的开源监控平台,功能更重,装在普通设备上性能开销比较大。
go2rtc 走的是另一个定位:不管录像,只负责把流接进来、以多种协议发出去。它更适合做“视频接入层”,而不是完整的 NVR。实际使用中,很多人就是让 go2rtc 负责取流和低延迟 WebRTC 预览,再把它作为视频源接到 Frigate 或者 Home Assistant 的摄像头实体里。
| 方案 | 核心定位 | 资源占用 | 适合场景 |
|---|---|---|---|
| go2rtc | 多协议取流与转发 | 极低 | 多摄像头统一接入、低延迟预览、HA 联动 |
| Frigate | NVR + AI 识别 | 较高 | 需要录像、人体/车辆检测的完整监控 |
| ZoneMinder | 传统监控平台 | 高 | 老牌 NVR 场景,功能全面但较重 |
| 各家云平台 | 摄像头厂商云服务 | 无本机开销 | 只看少量摄像头,愿意走云端 |
我的实际选择是 go2rtc 作为取流层,再配合已有的轻量录像方案。这样既能保持低延迟浏览器预览,又不用为每个摄像头各自维护一套转换逻辑。
2. Docker 部署与 Compose 编排
2.1 部署前的准备工作,先把这几项确认好
在启动容器之前,有几个基础条件需要确认。首先是装有 Docker 的宿主机,Linux、Windows、macOS 都行。如果在 Windows 上用 Docker Desktop,记得先在 BIOS/UEFI 里开启虚拟化支持,否则引擎会启动失败。然后是网络层面,go2rtc 默认使用 1984 端口,需要确保宿主机防火墙和路由器端口没有拦截内网访问。
还需要准备好一个空目录,用来放 go2rtc 的配置文件和日志。我不太建议把配置直接塞在容器内部,因为每次容器重建都会丢失,官方推荐的方式是挂载外部配置文件。另外,如果要用树莓派或者其他 Linux 小主机接 CSI/USB 摄像头,还需要考虑把物理设备透传给容器,这一步放在后面的配置章节细讲。
对于不熟悉 Docker 的同学,这里简单同步一个概念:Docker Compose 和 Docker 命令行的关系,就是把
docker run
那串很长的参数写进一个 YAML 文件里,以后想启动直接用
docker compose up -d
就行,不用再记参数。下面给的部署示例都是 Compose 形式,建议统一用这个方式。
2.2 一份可以直接抄的 docker-compose.yml
我的标准部署文件是这样:
services:
go2rtc:
image: alexxit/go2rtc:latest
container_name: go2rtc
restart: unless-stopped
ports:
- "1984:1984"
volumes:
- ./go2rtc.yaml:/config/go2rtc.yaml
command: -c /config/go2rtc.yaml
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
逐行说明几个关键点:
-
image:我用的是alexxit/go2rtc,注意镜像名以项目发布页为准,新版本可能会有变化。生产环境建议固定到具体版本号,比如alexxit/go2rtc:1.9.x,避免 latest 更新带来意外变化。 -
ports:核心是1984,Web 界面和所有 API 都走这个端口。如果你需要把 go2rtc 当作 RTSP 服务器往外输出流,再额外映射8554(RTSP 默认端口),但这个场景不是每个人都用得上。 -
volumes:把单独的go2rtc.yaml挂载进容器,路径固定为/config/go2rtc.yaml,然后通过command指定加载。很多同学一上来没挂配置也能跑,但等你想改流的时候就明白这个步骤多重要了。 -
logging:这一项是我踩过坑之后才加上的,后面“日志与磁盘”那节会展开说。
2.3 启动服务并验证界面
在
docker-compose.yml
所在目录下执行:
docker compose up -d
容器启动后,等 3 到 5 秒,浏览器访问
http://宿主机IP:1984
。正常情况下你会看到一个简单的 Web 页面,左侧是流列表,右侧是播放器区域。这个页面本身不带摄像头,刚打开可能会显示空白,但只要页面能打开,就说明 go2rtc 进程已经正常起来了。
我习惯用
docker logs go2rtc
检查启动日志。正常会输出监听端口、配置文件加载路径等信息。如果看到配置文件读不到、端口占用一类的报错,排查起来也很直接,先把容器停掉再根据日志修。第一次跑通之后,就可以开始做真正的摄像头接入了。
3. 把摄像头接进来:多协议接入实操
3.1 先确认各路摄像头的 RTSP 取流地址,别瞎填
go2rtc 本身不关心摄像头品牌,它只负责按你给的 URL 去建立连接。所以接入的第一步,是把每台设备的出流地址搞清楚。常见的地址格式如下:
-
海康威视主码流:
rtsp://用户名:密码@IP:554/Streaming/Channels/101 -
海康威视子码流:
rtsp://用户名:密码@IP:554/Streaming/Channels/102 -
大华主码流:
rtsp://用户名:密码@IP:554/cam/realmonitor?channel=1&subtype=0 -
大华子码流:
rtsp://用户名:密码@IP:554/cam/realmonitor?channel=1&subtype=1 -
部分 TP-Link、宇视设备:
rtsp://用户名:密码@IP:554/stream1
这里有两个容易出问题的细节。一个是密码里如果含有
@
、
:
、
#
这类特殊字符,直接拼进 URL 会把解析搞乱,必须做 URL 编码,比如
@
写成
%40
,
#
写成
%23
。另一个是主码流和子码流的区别:主码流分辨率高、码率大,适合本地录像和分析;子码流分辨率低、码率小,适合做页面预览。如果家里带宽一般,接入时优先用子码流试通,通了再切主码流。
稳妥的判断方式是先用 VLC 或者 ffprobe 验证地址能出画面,再接进 go2rtc。当然 go2rtc 的 Web 界面里也有“添加流”的入口,填地址之后能直接预览,这也是一种快速验证方式。
3.2 编写 go2rtc.yaml:streams 配置解析
go2rtc 的配置核心就是
streams
部分,一个顶层键代表一路流的名称,值是一个列表。列表中第一个源会被优先使用,如果连接失败,go2rtc 会按顺序尝试列表里的下一个源。这一点很适合做备用流。
我的
go2rtc.yaml
长这样:
streams:
living_room:
- rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/101
front_gate:
- rtsp://admin:123456@192.168.1.20:554/cam/realmonitor?channel=1&subtype=0
pi_cam:
- v4l2:///dev/video0
backup_cam:
- rtsp://admin:123456@192.168.1.30:554/onvif1
- rtsp://admin:123456@192.168.1.31:554/onvif1
解释几个细节:
-
pi_cam这路没有走 RTSP,而是直接读取树莓派上的/dev/video0设备。要想让容器内能访问到这个设备,docker-compose 里要加设备映射:
devices:
- /dev/video0:/dev/video0
-
backup_cam里我写了两个 RTSP 地址,第一个连不上时 go2rtc 会尝试第二个。这个在摄像头偶尔重启或者 IP 漂移时很有用。 -
每个
streams键名不要带空格和特殊字符,后续你在 API 里会用这个名字作为src参数。
改完配置后重启容器:
docker compose restart go2rtc
然后在 Web 界面里刷新,就能看到这几路流。点击任意一路流,播放器如果出现画面,说明接入成功。
3.3 编码不兼容时的兜底方案:让 go2rtc 调用 ffmpeg
接入成功只是第一步。到我实际接入的时候,问题来了:海康的枪机是 H.265 编码,在大华那路是 H.264,树莓派那路默认可能是 MJPEG。浏览器播放器对 H.264 的兼容性最好,对 H.265 则要看浏览器的具体实现,很多浏览器不支持原生 HEVC 解码,直接预览就是黑屏或绿屏。
这时候需要为 H.265 的源加一层 ffmpeg 转码。go2rtc 里用带
ffmpeg:
前缀的源即可:
streams:
living_room_h265:
- ffmpeg:rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/101#video=h264
这里的
#video=h264
并不是注释,而是告诉 go2rtc:这个源需要通过 ffmpeg 处理,并且输出视频编码最好是 H.264。go2rtc 收到指令后会自动拉起 ffmpeg,把源流解码后重新编码成 H.264。代价是 CPU 占用会明显上升,所以数量多的 H.265 摄像头,建议优先选择浏览器支持 HEVC 的方案,只在确实不支持的客户端上启用转码。
还有一种折中方案:不同摄像头分别配置不同编码,主码流做录像、子码流做浏览器预览。如果你的摄像头支持主码流 H.265 + 子码流 H.264,那么在 go2rtc 里分别建两路流,预览时始终用子码流的 H.264,录像时用主码流的原始流,这样就不需要为预览做转码。
3.4 浏览器低延迟播放的关键:WebRTC 怎么用起来
配置好了源之后,播放方式也值得好好选。go2rtc 的多协议输出是我用它的最大动力,尤其是 WebRTC。
在 Web 界面上,流名称旁边可以直接点预览,后台默认走的就是 WebRTC。如果要在自己的页面里嵌入流,可以直接用 HTTP API:
-
WebRTC 播放地址:
http://IP:1984/api/webrtc?src=流名称 -
HLS 播放地址:
http://IP:1984/api/hls?src=流名称/index.m3u8 -
MJPEG 播放地址:
http://IP:1984/api/mjpeg?src=流名称
为什么我优先推荐 WebRTC?因为它在局域网里能做到真正意义上的低延迟。WebRTC 基于 UDP 传输,走 ICE 做 NAT 会话检测,DTLS-SRTP 做加密,视频帧是实时推到浏览器里的。对比之下,HLS 因为要把视频切成一个个小分片、生成 m3u8 索引,天然会有好几秒延迟。本地多路预览场景里,延迟越低保价值越大,所以我把 WebRTC 作为默认播放通道。
实际接入 Home Assistant 时,也用 go2rtc 的这个能力。HA 的
stream
组件很多时候拿不到低延迟的 WebRTC 源,而 go2rtc 提供了一个集成方式,把 WebRTC 流转给 HA 的摄像头实体,这样 HA 里的预览也能做到接近实时。这部分我放在第 4 节详细说。
3.5 多路并发时的带宽估算,别忽略这一笔账
接入多路摄像头后,还有一个很容易被忽略的问题:带宽。你可能会觉得都是局域网,不会有什么瓶颈,但多路主码流并发起来照样会卡。
一个简单的估算方式:假设一路 H.264 主码流 1080P 的码率是 4 Mbps,接入 8 路就是 32 Mbps,也就是大概 4 MB/s 的持续吞吐。如果你的交换机和网线质量一般,或者 WiFi 传输,这个负载并不低。我的做法是:局域网内的浏览器预览统一走子码流,只有录像和分析才走主码流。在 go2rtc 里建立两组流,一组
xxx_main
一组
xxx_sub
,需要高画质就取 main,需要低延迟预览就取 sub。
另外,如果有公网远程看的需求,建议不要直接暴露 Web 界面和 WebRTC 端口,而是优先用支持 WebRTC 的网关方案,或者在更外层做一层带认证的 HTTPS/WSS 转发。这套网络链路的安全配置要单独考虑,不要贪图方便裸奔在公网。
4. 让 go2rtc 更好用:Home Assistant 联动与性能优化
4.1 把 go2rtc 接到 Home Assistant,预览延迟大幅下降
我自己在家庭场景里最常用的联动就是 Home Assistant。HA 官方有摄像头实体,但通过
generic camera
集成拉 RTSP 流预览时,默认用的是 stream 组件转 HLS,延迟经常在 3 秒以上,而且转码吃 CPU。
go2rtc 提供了一种更好的联动方案:在 HA 的配置里启用 go2rtc 集成,配置它的 API 地址,HA 会自动发现 go2rtc 里已有的流,并建立对应的
camera
实体。预览时 HA 可以直接走 WebRTC 通道,延迟能降到几百毫秒级别,本地摄像头云台控制、人体检测联动起来都顺畅很多。
在 HA 的
configuration.yaml
里可以加入类似下面的配置片段(具体集成方式以 HA 版本为准):
go2rtc:
url: http://127.0.0.1:1984
如果 go2rtc 和 HA 在同一个宿主机上,用
127.0.0.1
就行;如果分开部署,填 go2rtc 宿主机 IP。配置完成后重启 HA,在“设备与服务”里搜索 go2rtc 集成,按提示添加,就能看到 go2rtc 里的流自动映射成摄像头实体。
4.2 用 ffmpeg 参数做精细控制,一个细节能省很多事
go2rtc 本身有着合理的默认配置,但遇到特殊网络环境时,自己调参数很有必要。最常见的是 RTSP 的传输模式:默认可能走 UDP,但摄像头和 go2rtc 之间的网络如果丢包严重,画面会花屏或断流。这时强制走 TCP 反而更稳定。
在 go2rtc 里可以通过追加 URL 参数配置源的行为,比如:
streams:
living_room_tcp:
- rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/101?tcp
URL 最后加
?tcp
,会让 go2rtc 通过 RTSP over TCP 的方式拉流,牺牲一点点传输效率,换来稳定性和兼容性。这个细节在无线摄像头、PoE 供电不稳定或跨交换机的场景里特别实用。
除了 TCP 模式,还有几个参数可以按需调整:超时时间、重连间隔、码率上限等。我自己的经验是,不要一上来就把参数堆满,先用默认配置跑,观察 go2rtc 的日志和 Web 界面的流状态,哪个环节出问题就针对性加参数。
4.3 镜像版本与配置变更管理,长期运行的注意事项
长期跑服务,版本管理是避不开的。我吃过的亏,是之前一直用
latest
标签,有次升级后某个配置语法变了,流全部拉不起来。后来我把版本固定在发布页的具体版本号,更新前先在测试环境跑一遍再切换。
另外,go2rtc 的配置文件的变更不会自动热加载,我一般是这样操作的:修改
go2rtc.yaml
后,先执行
docker compose config
验证一下语法,然后
docker compose restart go2rtc
。如果配置写错,启动会失败,这时候看容器日志就能定位到第几行有问题。
还有一个容易忽视的问题:容器日志持续输出。go2rtc 默认日志等级偏 verbose,如果跑了几个月不清理,docker 的 json log 文件会把磁盘撑满。我的 compose 里已经加了
logging
限制,最大 10MB 一个文件、保留 3 个,这样就不需要定期手动清理了。
5. 常见故障与排查技巧实录
5.1 摄像头拉流超时或频繁断开
这是接摄像头时最常遇到的问题。现象是 Web 界面上流显示存在,但播放画面转圈几秒后黑屏。排查我一般按这个顺序来:
- 先用 VLC 在宿主机上测试同一路 RTSP 地址,排除摄像头本身问题。
-
看 go2rtc 日志里有没有
timeout、connection refused、EOF之类的关键字。有的话,通常是地址、端口或用户名密码写错。 - 检查摄像头侧是否设置了允许的最大连接数。有些摄像头默认只允许一路 RTSP 会话,如果同时被其他平台占用了,go2rtc 自然拉不到。
- 换成 TCP 模式再试,避免 UDP 丢包导致的假超时。
如果确认是摄像头偶发断流,go2rtc 会自动重连,默认重连策略一般够用。遇到频繁断开,我还会检查一下供电稳定性,尤其是 PoE 供电的摄像头,电压不稳时断流往往比协议问题更常见。
5.2 WebRTC 协商成功但画面一直不出现
WebRTC 属于“能协商成功但不代表能出图”的典型。如果页面显示已经 connected,但画面黑屏,我首先会怀疑编码格式:源的编码是不是 H.265,浏览器又不支持,此时按 3.3 节的方法加
#video=h264
转码,问题通常就解决了。
还有一种情况是网络模式的问题。go2rtc 容器用 bridge 桥接模式时,WebRTC 打洞如果失败,媒体数据传不回来,表现就是一直转圈。我的经验是:在局域网内部署,可以直接把网络模式改成
host
,让 go2rtc 直接使用宿主机的网络栈,少一层 NAT 转发,很多奇怪的打洞问题都会消失。代价是 host 模式下端口映射配置会失效,你自己要保证 1984 端口不被占用。
network_mode: host
5.3 接入的摄像头 IP 变了,流就断了
家用摄像头如果没设置静态 IP,重启后 DHCP 可能给它分配一个新地址,go2rtc 里配置的老地址自然连不上。这个问题在长期运行中几乎一定会遇到。
两个解决方向。第一,在路由器或摄像头管理界面里,给每一路摄像头做 DHCP 静态绑定,把 MAC 地址和 IP 绑定死,这是最省心的方案。第二,在 go2rtc 的 stream 配置里通过备用源兜底,一旦第一个地址连不上,自动切换到备用地址。两个方法可以同时用,双保险。
如果摄像头支持 mDNS/Onvif 探测,go2rtc 也有自动发现机制,可以把摄像头从网络里自动探测出来,不依赖固定 IP。但自动发现一般只负责发现,真正稳定的生产部署还是会落到固定 IP + 主备源两个手段上。
5.4 树莓派摄像头设备权限问题
用树莓派接 OV5647 或者 CSI 摄像头时,容器内经常会报
open /dev/video0: Permission denied
或者干脆找不到设备。这里有三个点需要排查:
-
宿主机的
/dev/video0是否真实存在。可以用ls /dev/video*确认。 -
docker-compose 里是否做了
devices映射,没有映射容器里永远看不到。 -
设备权限是否足够。有些系统上摄像头设备属于
video组,容器内进程可能没有权限,最简单的临时方案是加一个privileged: true,但长期跑不建议这么干。更稳妥的做法是把宿主机的/dev/video0权限改为 666,或者通过 udev 规则赋予当前用户权限。
我用树莓派跑 go2rtc 时,配置了
/dev/video0
映射,并把 host 宿主机用户加入
video
组后重启,容器内外就都正常了。如果你的摄像头接在 USB 上,注意 USB 摄像头可能枚举成
/dev/video0
、
/dev/video1
两个节点,一个是视频流、一个是 metadata,映射时以实际能出流的那个为准。
最后再分享一个我实际用下来的体会
折腾了这么多路摄像头之后,我最大的感受是:真正稳定的系统不是靠某一个工具包打天下,而是把每一层的问题都提前拆开。go2rtc 管接入和低延迟转发,固定 IP 管设备寻址,TCP/重连策略管网络稳定性,日志清理管长期运行不爆盘。这套组合下来,家里的多路摄像头基本做到了一年半载不用手动干预。
如果接下来你还想继续扩展,我建议研究一下 go2rtc 的录制功能扩展,用它定期截图或者推流到本地录像服务,再配合 Home Assistant 的自动化做异常告警。这中间有一点经验是:接入服务稳定后先别急着上各种高级功能,把所有流跑上 24 小时看日志和磁盘,把基础盘扎实了,后面的扩展都是水到渠成的事。
更多推荐
所有评论(0)