go2rtc实战:统一接入多摄像头,从RTSP到WebRTC的低延迟流媒体方案
1. 为什么摄像头接入到最后都绕不开 go2rtc 这种“胶水层”
我在一个混合环境的项目里被折腾得够呛:前端的监控大屏既要看几台海康威视枪机,又要接大华的球机,旁边还有用树莓派OV5647模块自制的摄像头和一台ESP32-S3接的USB摄像头。设备各自携带不同的协议和厂商SDK,想在同一个网页上统一预览、统一转存,最初我直接用ffmpeg脚本一条条拉流再转发,脚本越写越长,设备一重启就要手动清理僵尸进程。后来把go2rtc用Docker部署成独立的流媒体平台,所有视频源统一收进一个服务,浏览器直接低延迟播放,这才算真正把多摄像头、多协议的问题一次解决掉。
1.1 设备协议各自为政,最终都得靠一个“中转站”
先说痛点。摄像头行业到现在也没有一个谁都认的“普通话”:专业安防设备普遍支持RTSP和ONVIF,直播场景喜欢RTMP,网页播放器又依赖HTTP-FLV或者HLS,而低延迟的远程预览基本绕不开WebRTC。用传统方案在浏览器里看RTSP流,通常得先转成HLS或FLV,ffmpeg命令行堆出来又慢又容易“水土不服”。go2rtc的价值在于它把“协议转换”这个通用能力做成一个开箱即用的服务:你只需要告诉它输入源是什么协议、对外要暴露什么协议,剩下的握手、重连、流格式协商这些脏活累活,全部交给它处理。用我的话来说,go2rtc就是摄像头、播放器、存储系统之间的一层“胶水”。
这层胶水不是可有可无的。我在项目里见过太多人自己用ffmpeg循环脚本去拉流,脚本里又是判断进程状态又是写重试逻辑,最后设备一多根本维护不过来。go2rtc把这些高频重复的事情沉淀成了一个服务,配置是声明式的,状态是自管理的,这才称得上“平台”。
1.2 为什么不直接上 SRS、nginx-rtmp 这类方案
很多朋友会问:SRS、nginx-rtmp也都能做流媒体转发,凭什么选go2rtc?我对比下来的总结很直接:SRS是一个面向直播场景的完整流媒体服务器,配置粒度更重,很多参数对监控场景是多余的;nginx-rtmp模块只擅长简单的RTMP转发,要播放HLS还得再挂一堆插件,对WebRTC支持基本为零。go2rtc最突出的几个点,恰好都是监控场景刚需:
- 单二进制,配置极简,从拉流到出流只需要一份YAML;
- 对H.264流默认不做转码,直接透传,延迟和CPU占用都低;
- 内置WebRTC服务,浏览器可以原生播放,不需要Flash插件,也不需要额外部署WebSocket代理这一层;
- 支持ONVIF自动发现,局域网摄像头可以自动接入;
- 和Frigate、Home Assistant这些智能监控系统的集成很成熟。
如果你只是想把几路摄像头统一起来做低延迟预览,go2rtc的性价比明显更高。SRS我更多是留给真正需要大规模直播分发、多租户、CDN对接的场景去用。工具没有绝对好坏,选型和场景强相关。
1.3 Docker 部署带来的额外收益
go2rtc主程序是Go编译出来的单文件,本身没有运行依赖,但在生产环境里我经常需要挂ffmpeg做转码,在Docker容器里统一放好这些依赖,比在宿主机上维护一套运行环境省事得多。Docker的restart策略也天然适合摄像头这种需要7×24小时跑的服务,崩溃后自动拉起,日志有统一管理,升级就拉一个新镜像换容器,回滚也快。所以下面我会直接以Docker Compose方式给出部署方案,这也是我最推荐大家直接“抄作业”的方式。
2. 部署方案:为什么我最终选择 Docker + host 网络
go2rtc的部署方式有几种,Docker是我的首选,但其中有两个关键决定:用哪个镜像、用哪种网络模式。这俩不先想清楚,后面大概率出问题。我第一次按“传统思维”用bridge网络映射端口部署,结果WebRTC播放始终建立不起来,浏览器控制台一直报ICE连接失败,排查了半天才发现是UDP候选地址和端口映射没对上。这一节就是把我的最终方案和踩坑过程完整讲清楚。
2.1 一份可以直接抄的 docker-compose.yml
这是我项目里实际在用的配置:
version: "3.8"
services:
go2rtc:
image: alexxit/go2rtc:latest
container_name: go2rtc
restart: unless-stopped
network_mode: host
volumes:
- ./config/go2rtc.yaml:/config/go2rtc.yaml
logging:
options:
max-size: "10m"
max-file: "3"
几个关键信息逐个说明。
镜像名是
alexxit/go2rtc
,这是作者发布的官方镜像,amd64和arm64都支持,树莓派上直接跑也没问题。我一开始图省事在Docker Hub搜了一个转发镜像,后来发现版本比官方落后很多,一些新功能和Bug修复都没有,所以强烈建议直接用官方镜像。
挂载路径是容器内的
/config/go2rtc.yaml
。这个路径很多人会挂错,我第一次随手挂到
/app/go2rtc.yaml
,容器启动后服务一直用的是空配置,Web页面上什么都看不到。正确做法是把宿主机目录下的配置文件挂到
/config/go2rtc.yaml
,go2rtc启动时才会读到你写的流配置。
日志限制也建议加上。摄像头流服务跑久了日志会一直涨,不限制的话半年能撑爆一块小硬盘。
max-size: "10m"
和
max-file: "3"
的意思是单文件10MB、最多保留3个,够用了。
2.2 为什么是 host 网络,而不是传统的 ports 映射
我踩过的最大的坑就是WebRTC的候选地址问题。go2rtc的WebRTC模块通过UDP端口和浏览器协商媒体流,如果容器跑在bridge网络里,默认会把容器内部IP作为候选地址告诉浏览器,浏览器根本访问不到这个内网地址,于是ICE协商失败、画面一直转圈。使用host网络后,go2rtc拿到的就是宿主机真实IP,候选地址天然正确,省掉了很多人工指定candidate的麻烦。
如果你的环境用不了host网络,比如Docker Desktop for Windows的默认设置,或者公司网络对端口暴露有严格限制,可以改用端口映射方式,但必须把候选地址显式指到宿主机IP:
ports:
- "1984:1984/tcp"
- "8554:8554/tcp"
- "1985:1985/udp"
配套的go2rtc.yaml里要写:
webrtc:
listen: ":1985"
candidates:
- 192.168.1.100:1985
注意,
candidates
里填的是宿主机对客户端可达的IP和端口。如果客户端和宿主机不在同一网段,这里可能要填公网IP或做端口映射,否则依然会建立失败。这个“候选地址必须让浏览器能直接访问到”的概念,是整个WebRTC播放能否成功的关键,建议配置时多想一层。
2.3 镜像下载慢、版本选择的处理
国内拉Docker镜像慢是老问题,Docker Hub官方源经常超时。我现在给Docker配置国内镜像加速地址,Linux下写在
/etc/docker/daemon.json
的
registry-mirrors
字段,Windows下在Docker Desktop的Settings -> Docker Engine里加同样配置,然后重启Docker服务。加速源可以多配置几个,拉镜像时Docker会按顺序尝试。
镜像标签我建议用
:latest
,因为go2rtc版本迭代比较快,新功能经常以latest发;生产环境如果想求稳,可以在GitHub Releases页面挑一个具体版本号固定下来,比如
alexxit/go2rtc:v1.9.7
,避免某次升级破坏现有配置。两者的取舍是:latest省心,固定版本可预期。
部署完成后怎么确认服务起来了?跑一条命令:
curl http://127.0.0.1:1984/api/streams
正常会返回一个JSON数组或空数组,表示API已经在响应。如果连接被拒绝,先检查容器状态、端口监听和防火墙。
3. 摄像头接入配置:RTSP 地址到 go2rtc 流定义
go2rtc的配置文件是YAML,核心就一个概念:streams。每个流本质是一个视频源,给它起个名字,然后写这个视频源怎么获取。流名称就是后续访问时的对外标识,命名要见名知意,尤其是摄像头数量多的时候。
3.1 流定义语法:单源、多源和通配符
最简单的单源配置:
streams:
cam_front: rtsp://admin:your_password@192.168.1.64:554/Streaming/Channels/101
这里
cam_front
是流名称,访问时用
http://IP:1984/?src=cam_front
就能看到这个摄像头的画面。如果想做高可用或多源冗余,go2rtc支持给同一个流配置多个源,主源断开后自动切换到备用源:
streams:
cam_front:
- rtsp://admin:pass@192.168.1.64:554/Streaming/Channels/101
- rtsp://admin:pass@192.168.1.65:554/Streaming/Channels/101
这个能力在关键区域很实用,一台摄像头断线了,画面会自动切到旁边的备份设备,不需要人工干预。注意,多源并不仅仅是备灾,也可以把“主码流”和“子码流”配给同一个名称,这样在弱网环境下播放端能自己挑合适的源。
3.2 海康、大华等主流设备的 RTSP 地址格式
摄像头厂家RTSP地址格式差异很大,这里直接给一份我自己验证过的地址清单,拿去改IP、账号、密码就能用:
| 设备类型 | 主码流地址 | 子码流地址 |
|---|---|---|
| 海康威视 |
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
|
| 大多数ONVIF设备 |
rtsp://用户:密码@IP:554/onvif1
|
rtsp://用户:密码@IP:554/onvif2
|
海康的地址里,
101
代表第一个通道的主码流,
102
代表第一个通道的子码流。如果你用的是多通道录像机而不是单摄像头,通道号要对应调整,比如第二个摄像头可能是
201
、
202
。大华则用
channel
和
subtype
两个参数,
subtype=0
是主码流,
subtype=1
是子码流。
这里有个很关键的细节:密码里的特殊字符必须做URL编码。比如密码是
Ab@123456
,@要转成
%40
,否则整个URL会被解析成“用户Ab”,后面全是多余的路径,鉴权直接失败。类似的字符还有冒号、斜杠、井号。我习惯在配置生成脚本里统一做一次URL编码,避免手写出错。
3.3 ONVIF 自动发现:把局域网摄像头一键拉进来
如果不想一台一台手写RTSP地址,go2rtc支持通过ONVIF自动发现局域网内的摄像头。我常用的是通配符流加onvif源的方式:
streams:
"#":
- "onvif://admin:pass@192.168.1.100"
这里的
#
是一个模板,go2rtc会去指定IP的设备上枚举视频通道,并把发现的每个通道自动注册成独立流。前提是摄像头要开启ONVIF功能,而且账号有权限访问ONVIF服务。海康、大华的新固件默认ONVIF不一定开启,要去设备Web管理页的“网络”或“集成协议”设置里启用。
自动发现省事,但生产环境下我一般只用于初期的设备盘点。因为自动发现的命名取决于设备内部型号,后续不好维护,落地项目时我还是会把每一路摄像头写成明确的命名配置,这样大屏、存储、告警模块引用起来才清晰。
3.4 USB/V4L2、树莓派 CSI、ESP32-S3 这类非IP摄像头的接入
不是所有摄像头都有网口和RTSP。树莓派OV5647模块、ESP32-S3接的USB摄像头、Jetson上的CSI摄像头,都是走本机视频设备节点,同样可以接到go2rtc里,方式是用ffmpeg作为源:
streams:
usb_cam:
- "ffmpeg:v4l2:///dev/video0#video=copy"
这段配置的意思是让go2rtc调用ffmpeg从
/dev/video0
读取V4L2设备数据,
video=copy
表示尽量不转码、直接拷贝原始编码流。如果设备不支持原始编码直出,就把
copy
换成具体编码,比如
video=h264
,go2rtc会自动完成转码。树莓派CSI摄像头在较新的系统里同样会注册成
/dev/video0
这样的V4L2节点,所以上面的方式通用。ESP32-S3的USB摄像头通常走UVC协议,插上后也会变成一个V4L2设备,同样可以这样接入。
权限问题要留意:容器内读
/dev/video0
需要把设备节点映射进容器,同时运行用户最好是video组。Docker Compose里可以加:
devices:
- /dev/video0:/dev/video0
group_add:
- "44"
44
是Linux下video组的常见GID,如果宿主机的组ID不同,要按实际值调整。遇到“打不开设备”的报错,十有八九是权限问题。
3.5 用 curl 和日志验证接入成功
配置好后重启容器:
docker compose restart go2rtc
然后看日志:
docker logs -f go2rtc
如果流添加成功,日志里会看到对应的连接信息或错误信息。更直接的方式是访问API:
curl http://127.0.0.1:1984/api/streams
返回的JSON里能看到所有流的状态。最后直接在浏览器打开
http://IP:1984/?src=cam_front
,看到画面就说明接入成功。如果黑屏,跳转到后面第6节的排错部分,我列了具体的原因和解决路径。
4. 多协议输出与浏览器播放:实测过的几种播放路径
go2rtc的第二大部分是出流,也就是对外暴露协议。它不只是自己播放,而是能给下游提供多种协议的地址,这样后端录像、大屏、小程序都能按自己习惯的方式拉流。多协议输出能力,才是它配得上“流媒体平台”这个定位的原因。
4.1 go2rtc 对外暴露了哪些协议
| 输出协议 | 典型地址 | 延迟量级 | 主要场景 |
|---|---|---|---|
| WebRTC |
http://IP:1984/api/webrtc?src=cam1
| 300ms~1s | 浏览器低延迟预览 |
| HLS |
http://IP:1984/api/playlist.m3u8?src=cam1
| 2~5s | 移动端、公网播放 |
| HTTP-FLV |
http://IP:1984/api/stream.flv?src=cam1
| 1~3s | 后端拉流、老播放器 |
| RTSP |
rtsp://IP:8554/cam1
| 1~3s | NVR、VLC |
| MJPEG |
http://IP:1984/api/stream.jpeg?src=cam1
| 帧率受限 | 简单抓帧、低帧率预览 |
这些地址我在项目里逐个验证过,可以直接拿去用。需要强调一点,RTSP出流默认端口是
8554
,不是传统的554,老NVR或VLC去拉流时别搞混端口。
4.2 WebRTC 为什么延迟最低且最适合浏览器
WebRTC是浏览器原生的实时音视频协议,go2rtc不需要把视频切成HLS分片再让播放器去轮询,而是建立一条直接的点对点媒体连接。go2rtc对H.264流默认是透传,摄像头出来是什么编码就直接塞给浏览器,所以不需要转码,延迟自然就低。这就是为什么我推荐在新项目里首选WebRTC作为内部预览协议。
go2rtc官方的Web界面里点击播放流,底层就是在用WebRTC。如果你要自己写前端集成,可以用它的API。大致逻辑是:创建
RTCPeerConnection
,从
/api/webrtc?src=流名称
取到SDP answer,设置到本地连接上,然后接收对端的视频轨放到video元素里。官方也有封装好的播放器JS,可以直接引用,省掉自己写协商逻辑的时间。
4.3 HLS 与 HTTP-FLV 的实际使用场景
浏览器直接播放虽然方便,但很多场景还是需要标准协议输出。比如公司已经有了一套监控大屏,后端只认HLS;或者移动端App里用ExoPlayer拉流,HLS是最稳妥的。用go2rtc的HLS输出,地址是
http://IP:1984/api/playlist.m3u8?src=cam_front
,手机浏览器、VLC播放器都能直接拉。HTTP-FLV适合后端服务做拉流处理后转存或录像,地址是
http://IP:1984/api/stream.flv?src=cam_front
。
RTSP则适合让旧版NVR或桌面VLC去拉。我有一台不支持ONVIF的老录像机,就让它直接去go2rtc拉
rtsp://IP:8554/cam_front
,相当于用go2rtc把新摄像头的协议“翻译”成了老录像机能听懂的语言。老设备不用换,新设备也不用改,这个场景在实际项目中特别值钱。
4.4 多路同屏与自动布局
go2rtc的Web界面支持同时预览多路。地址栏直接带多个src参数即可:
http://IP:1984/?src=cam_front&src=cam_factory_01&src=cam_factory_02
页面上会自适应网格布局。这个功能在项目交付时很好用,不用自己开发一个监控墙,直接把go2rtc页面嵌到iframe里,或者给客户演示时打开这个地址就够了。我经常用它来做验收演示,比PPT讲方案直观得多。
5. 进阶玩法:转码、硬件加速与 Frigate/Home Assistant 联动
到这里,基本部署和播放已经没问题了。接下来这部分是我在项目里的进阶用法,主要处理H.265、多路资源占用,还有把go2rtc接到现有智能监控系统里。这些能力直接决定了go2rtc是“能跑”还是“好用”。
5.1 什么情况下必须开转码
go2rtc默认不转码,这对大多数H.264摄像头没问题。但很多新摄像头默认输出H.265(HEVC),浏览器里的WebRTC播放H.265支持很不统一,经常出现黑屏。这种情况下必须让go2rtc转成H.264。配置方式是在流定义里加一个带ffmpeg前缀的源:
streams:
cam_h265:
- "ffmpeg:rtsp://user:pass@192.168.1.101:554/...#video=h264#audio=aac"
这里
#video=h264
表示视频编码强制转成H.264,音频转成AAC。注意,转码不是免费的,CPU占用率会明显上升。如果只是预览,我一般建议先去摄像头后台把编码改成H.264,这样go2rtc还能继续透传;只有确实拿不到H.264源时才走转码。能用透传解决的,绝不上转码,这是监控资源规划的第一原则。
5.2 硬件转码让 CPU 喘口气
如果非要转码,尽量用硬件。Intel核显的Quick Sync、AMD的VCE、树莓派上的硬件编码器都能接。Docker部署时把显卡设备映射进容器:
devices:
- /dev/dri:/dev/dri
然后在转码配置里给ffmpeg参数加上硬件加速指令。go2rtc底层调用ffmpeg,具体的硬件编码参数可以写成
-hwaccel vaapi -hwaccel_device /dev/dri/renderD128
这样的形式。我实测下来,一台i5的迷你主机,四路1080p摄像头如果纯软件转码,CPU能到60%以上;启用Quick Sync后基本能压到10%以内。没有
/dev/dri
的云服务器或虚拟机,就只能靠CPU硬扛了,所以能不开转码就透传,资源占用完全是两个量级。
5.3 和 Frigate、Home Assistant 联动
go2rtc在智能家居和NVR圈子里流行,很大程度是因为Frigate和Home Assistant都内置或优先推荐它。Frigate这个NVR项目本身就集成了go2rtc,你把摄像头直接配置给Frigate,Frigate会用go2rtc去拉流做实时预览和事件录像。如果你像我一样先把go2rtc单独跑起来,再让Frigate连过来,可以在Frigate配置里写上go2rtc地址,这样前端预览走go2rtc的WebRTC,回放走Frigate的存储。
Home Assistant那边,go2rtc可以作为摄像头源接入到Media Source,Home Assistant的大屏卡片就能直接播放go2rtc管理的所有摄像头流。对我这种既做监控又做智能家居联动的场景,这一套组合拳非常实用。这里不做太深的配置展开,但方向给你指出来了,后面需要再针对集成细聊。
5.4 把 go2rtc 当协议转换器解决老录像机兼容问题
顺便提一个实际案例。之前有个客户环境里有几台新买的海康摄像头,但录像机是七八年前的杂牌老NVR,不支持H.265新编码,经常花屏或者直接不出画面。我没有去换它的固件,而是让海康摄像头先进go2rtc,go2rtc转成老录像机能接受的H.264 RTSP流,再让老NVR去拉go2rtc的8554端口。相当于用一台小主机把新设备“翻译”成旧设备能懂的语言。这个用法不用改动原有网络结构,效果立竿见影,客户只追加了一台小主机,没花大钱。
6. 排错与运维实录:我在实际部署中踩过的坑
最后这部分,我把反复踩过、也确实有代表性的坑列出来,按“现象—原因—解决”的方式写,方便你直接对号入座。排错思路比答案本身更重要,很多问题表面上不一样,底层链路是通的。
6.1 摄像头 401 认证失败与 URL 编码问题
现象:日志里不断出现
401 Unauthorized
,浏览器播放黑屏。原因绝大多数不是密码错了,而是URL里的特殊字符没编码。密码包含
@
、
:
、
/
、
#
这些字符时,会被解析成URL的语法结构,服务器收到的根本不是真实密码。解决办法是用URL编码工具转一下。比如
Ab@123:456
要写成
Ab%40123%3A456
。另一个常见原因是大华、海康这类设备修改密码后,RTSP服务更新有延迟,改完密码等一分钟再重启go2rtc重试,很多时候就好了。
6.2 “流已添加但网页黑屏”
现象:
/api/streams
里流状态正常,但Web界面黑屏,控制台报解码错误或codec不支持。原因基本是H.265直出。解决思路:先去摄像头后台把主码流或子码流编码改成H.264;如果设备不支持改,就用第5.1节里的ffmpeg转码方案。另外,个别摄像头开了解码的B帧(B-Frame)或SVC增强层,WebRTC也会花屏或卡顿,可以把主码流换成子码流试试。子码流码率低、关键帧间隔短,稳定性通常更好,这是实战里很有用的一招。
6.3 WebRTC 连接建立失败
现象:点播放一直加载,浏览器控制台报
ICE failed
或者 candidate gathering 失败。这一类问题九成出在候选地址和UDP端口上。防火墙没放行
1985/udp
,bridge网络没正确映射UDP端口,或者
candidates
配置写错,都会导致连接失败。排查链路我建议按顺序走一遍:
-
确认宿主机上
nc -vz IP 1985端口能通; -
确认go2rtc配置里的
webrtc.listen是:1985; - 看浏览器控制台里candidate的IP和宿主机实际IP是否一致;
-
如果客户端和宿主机跨网段,
candidates必须写公网可达的地址。
这套排查链路基本能覆盖绝大多数ICE失败的问题。
6.4 容器日志、重启策略和时间同步
摄像头流因为网络抖动偶尔断开是正常的,go2rtc会自动重连,不用过度干预。但容器本身如果崩溃了,一定要配
restart: unless-stopped
,否则半夜崩了没人拉起,第二天监控直接瘫痪。日志建议限制单文件大小,否则跑几个月日志能撑爆磁盘。
还有个很隐蔽的坑:时间同步。部分摄像头在NTP不同步的情况下,RTSP鉴权会失败,表现是重启go2rtc后能连上,过一会儿又断。遇到这种“时好时坏”的问题,先去摄像头后台核对系统时间,再去宿主机容器里看时间是否一致,时区尽量统一。摄像头设备不像服务器一样有严格的时间运维,NTP漂移是常态,这也是老监控项目里一个常见的隐形故障源。
6.5 批量接入多路摄像头
最后给一个提高效率的小技巧:go2rtc的流配置支持模板,也可以直接用脚本生成。我管理几十台摄像头的做法是维护一份清单,里面记录摄像头IP、账号、密码、通道号,然后写个Python脚本根据模板批量生成go2rtc.yaml里的streams段。生成完再比对一下配置,确认无重复、无异常字符,最后
docker compose restart go2rtc
。这样做比手工在网页上一路一路添加靠谱得多,也不容易漏写子码流。脚本本身不复杂,核心就是把命名规则和RTSP地址拼接规则固定下来,人只需要维护表格数据就够了。
6.6 流健康检查与 API 集成
除了Web界面,go2rtc提供了一套HTTP API,可以用来做流健康检查。我写过一个简单的巡检脚本,定时请求
/api/streams
,解析每个流的状态,如果有流变成错误或长时间无数据,就推送到企业微信或钉钉告警。这个能力在项目交付后期尤其重要,客户不会24小时盯着屏幕,但告警机器人会。
API的具体字段不同版本略有差异,但整体结构稳定。你拿到JSON后先打印出来看一遍,再写解析逻辑。巡检间隔建议不要低于30秒,频繁请求API对go2rtc没压力,但对部分老摄像头可能会有额外负载。
我个人建议,新项目里先别急着配转码,第一件事是去摄像头后台把所有编码统一成H.264,把子码流分辨率确认好,让go2rtc尽量做透传。这个选择决定了你后面是“轻量转发”还是“负重转码”,差距非常大。摄像头接入这种事,前期的规划和细节往往比后期的优化更决定成败。希望这份部署经验能帮你少走点弯路。
更多推荐

所有评论(0)