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 配置写错,都会导致连接失败。排查链路我建议按顺序走一遍:

  1. 确认宿主机上 nc -vz IP 1985 端口能通;
  2. 确认go2rtc配置里的 webrtc.listen 是 :1985 ;
  3. 看浏览器控制台里candidate的IP和宿主机实际IP是否一致;
  4. 如果客户端和宿主机跨网段, 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尽量做透传。这个选择决定了你后面是“轻量转发”还是“负重转码”,差距非常大。摄像头接入这种事,前期的规划和细节往往比后期的优化更决定成败。希望这份部署经验能帮你少走点弯路。

Logo

邀请您加入社区

更多推荐