
家里装了五六路摄像头海康、大华、小米、萤石各占一半这大概是很多人开始折腾 go2rtc 的第一推动力。我当时就是被折腾烦了海康要看海康的客户端大华网页版每次都要装插件小米就只能打开 App 等它云端转圈。各路摄像头的视频流本质都是 H.264偏偏各家协议、端口、地址格式完全不同最后被迫在手机里装四五个 App 来回切换。go2rtc 就是为了解决这个割裂局面的。它是一个用 Go 写成的轻量级流媒体服务器核心能力是把各种来源的摄像头视频流统一接入再以你想要的协议重新发出去浏览器里用 WebRTC 低延迟看老系统里接 RTSP/RTMP网页嵌入用 HLS 或 MJPEGAPI 侧则是一堆现成的 HTTP 接口。最方便的一点是它官方提供了 Docker 镜像部署起来比编译源码省心太多。这篇文章就按我实际项目里一步步做的顺序来写先讲明白为什么需要它再讲 Docker 怎么部署然后把海康、大华、小米、树莓派 CSI、USB 摄像头这些不同来源的接入配置逐个过一遍最后聊聊低延迟播放和录像落盘的方案。适合有摄像头想统一接入的人、NAS 玩家以及做智能车/机器人需要多路视频调试的开发者参考。1. 先把场景说清楚为什么需要一个多协议流媒体层1.1 摄像头生态的割裂远比想象中严重海康有海康的 SDK 和 Hik-Connect大华有乐橙和 DMSS萤石要装萤石云小米绑死米家生态。这些平台互不通信接口规范也不同。最讽刺的是绝大多数网络摄像头底层都支持 RTSP 协议你只要知道用户名密码用 VLC 就能直接打开视频流。但厂商偏不在自己的客户端里给你一个标准入口而是想方设法把你拉进云平台。再看设备形态就更乱了有 PoE 供电的枪机有 Wi-Fi 家用云台有树莓派上的 CSI 摄像头模块比如 ov5647有机器人上插的 USB 摄像头甚至还有 ESP32-S3 通过 USB 转出来的 UVC 视频节点。这些设备有的走 RTSP有的走 ONVIF有的根本没有网络协议栈只是 Linux 下的一个/dev/video0。在这样的环境里做统一视频平台最核心的问题不是要不要上云而是怎么先把这些协议全都消化掉。go2rtc 的角色就是这样一个多协议消化层。1.2 go2rtc 的定位所有视频流的中转站go2rtc 的名字很直白go 语言的 RTSP/RTC 服务器。它支持的输入源包括RTSP 流海康、大华、萤石、TP-LINK 等绝大多数 IPC/NVR 的取流地址RTMP 流推流设备、OBS 等ONVIF Profile S/T 摄像头可自动发现局域网内的 ONVIF 设备本地视频文件测试时特别方便Linux V4L2 设备USB 摄像头、树莓派摄像头等HTTPS/UDP 直播流、HLS 地址甚至是某些云平台的拉流 URL支持的输出协议更丰富WebRTC、RTSP、RTMP、HLS、MP4 片段、MJPEG、FLV、fMP4。也就是说一个好比的摄像头取到流之后你想接着把这个流以什么形式给别人全由 go2rtc 决定。它做的是接入转发而不是录制重压缩。所以它的资源占用非常低在树莓派或 NAS 的 Docker 里跑起来很轻松单进程内存占用基本保持在几十 MB 级别。1.3 和 NVR、录像软件的边界go2rtc 不是用来取代录像机的需要说明一个很关键的边界go2rtc 不负责录像持久化也不负责运动检测、人脸识别那些智能分析。它是流媒体代理层负责把摄像头流接进来、转出去让下游的消费方能够方便地使用这些视频流。所以常见架构是摄像头 RTSP 流 ↓ go2rtc 统一接入并转换协议 ↓ 浏览器 WebRTC 实时预览 ↓ Frigate / 群晖 Surveillance / FFmpeg 录像我自己项目里就是 go2rtc 做实时预览分发Frigate 消费 go2rtc 转出来的 RTSP 流做检测和录像存储。大家各管一段职责清晰。不要把录像和存储这两件事压在 go2rtc 上它真的不擅长也没必要。2. Docker 部署从一条命令到 docker compose2.1 最小启动命令和一个能打开的后台go2rtc 的官方镜像写得比较省心数据目录就一个配置文件夹。我第一回测试用的是一条最简单的命令docker run -d \ --name go2rtc \ --restartalways \ -p 1984:1984 \ -p 8554:8554 \ -p 1935:1935 \ -v /opt/go2rtc:/config \ alexxit/go2rtc:latest启动后浏览器直接打开http://你的IP:1984看到的就是 go2rtc 的 Web 管理界面。界面上能做的事不少在顶部添加需要接入的流地址查看每个流的参数与状态点击预览实测时 WebRTC 延迟基本在 200~500ms 内查看可用的 HTTP API 路由端口这块说明一下1984 是 Web 界面和 HTTP API 的默认端口8554 是 go2rtc 对外提供的 RTSP 服务端口1935 是 RTMP 的默认端口。go2rtc 还会用到 WebRTC 的动态 UDP 端口如果跑在桥接模式 Docker 里需要按实际需要把 UDP 端口范围也映射出来更省心的办法是用 host 网络后面单独说。2.2 镜像拉取慢先解决别等到部署时才卡住Docker 镜像下载慢是很多人的第一道坎尤其在国内网络环境下拉 go2rtc 这种镜像有时候要等几分钟甚至超时。标准的处理办法是配置 registry mirror也就是镜像加速源。改/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.mirrors.ustc.edu.cn ] }改完重启 Dockersystemctl restart docker然后再执行 docker pull 或直接 run速度会明显提升。这个配置只影响 Docker Hub 镜像拉取不影响容器运行随手开好能省不少事。2.3 用 docker compose 固化你的部署命令行跑起来只是起步真正要长期稳定运行我强烈建议用 docker compose 把配置固化。我的做法是单独建一个目录比如~/docker/go2rtc里面放一个docker-compose.ymlservices: go2rtc: image: alexxit/go2rtc:latest container_name: go2rtc restart: always ports: - 1984:1984 - 8554:8554 - 1935:1935 volumes: - ./go2rtc.yaml:/config/go2rtc.yaml - ./recordings:/recordings environment: - TZAsia/Shanghai目录里的go2rtc.yaml就是全局配置。go2rtc 会自动读取这个文件里的流定义里面写完摄像头配置以后即使容器重启所有接入配置也不会丢。另外加了个recordings目录挂载进去虽然 go2rtc 本身不录像但后面要用 ffmpeg 或者其他容器落盘时这个目录可以直接复用。启动关闭的常用操作docker compose up -d docker compose logs -f go2rtc docker compose restart go2rtc docker compose down2.4 host 网络什么时候更好桥接模式是最通用的部署方式但如果你打算依赖 go2rtc 的 ONVIF 自动发现摄像头或者摄像头在另一个网段需要靠组播广播发现建议直接在 Linux 上用 host 网络docker run -d \ --name go2rtc \ --restartalways \ --network host \ -v /opt/go2rtc:/config \ alexxit/go2rtc:latesthost 模式下容器直接共享宿主机网络栈没有端口映射WebRTC 的 UDP 端口也能直接被外部访问到某些跨网段组播和 mDNS 发现的场景会顺畅很多。但注意host 网络只在 Linux 下有效Windows 和 macOS 的 Docker Desktop 不支持。如果你的 go2rtc 计划跑在群晖 NAS 或树莓派上那基本都是 Linux可以放心用 host。我个人的选型经验是单纯接几路 RTSP 给浏览器看桥接模式加端口映射就够一旦涉及 ONVIF 自动发现、多网段发现、WebRTC 打通优先 host。3. 摄像头连接配置RTSP、ONVIF 和那些老设备的信令3.1 海康、大华、常规 RTSP 地址怎么写接入网络摄像头最常用的是 RTSP。不同厂商的 RTSP 地址格式差别很大这里给出最常见的三个例子。海康威视包括萤石部分型号一般是这样rtsp://admin:password192.168.1.64:554/Streaming/Channels/101末尾的 101 代表通道 1 主码流102 代表通道 1 子码流。如果摄像头有多个通道依次是 201、202、301、302 等。大华的一般格式是这样rtsp://admin:password192.168.1.65:554/cam/realmonitor?channel1subtype0channel 是通道号subtype 为 0 是主码流为 1 是子码流。TP-LINK 的则类似rtsp://admin:password192.168.1.66:554/stream1在 go2rtc 的配置里这些地址直接填进streams段落即可streams: haike_main: - rtsp://admin:password192.168.1.64:554/Streaming/Channels/101 haike_sub: - rtsp://admin:password192.168.1.64:554/Streaming/Channels/102 dahua_main: - rtsp://admin:password192.168.1.65:554/cam/realmonitor?channel1subtype0go2rtc 的 streams 支持写一个名字对应一个或一组地址多个地址时会自动在主源断掉后切换到备用源。如果你的摄像头有主码流和子码流建议分开定义为两个流方便在不同带宽场景下调用。3.2 摄像头没有激活或忘记密码时先做什么大华摄像头第一次使用必须激活你不激活它连 RTSP 取流都不开放。所谓激活就是通过大华的官网工具或 Web 界面给摄像头设置一个管理员密码。新买的大华设备插上网线进它的默认 IP浏览器会先跳到激活页面这时候千万别直接关掉。海康的老设备也是类似默认 IP 通常是 192.168.1.64需要用 SADP 这个工具扫描局域网内的设备然后改 IP、激活、设置密码。我之前帮朋友装一台旧海康怎么都拉不到流最后发现是设备停在出厂状态压根没激活。密码搞忘了也别慌海康大华都支持通过硬件恢复按钮重置但重置会清掉所有配置摄像头 IP 会回落到默认值。这一步完成后再去 go2rtc 里填 RTSP 地址才有意义。如果你连密码都还没设置好看到摄像头画面是灰的不要调 go2rtc先回到设备供应商的工具里把摄 像头基础状态弄干净。3.3 跨网段取流让 go2rtc 驻留在有路由的网段很多人遇到海康摄像头跨地址进行硬盘录像机的问题本质是网络可达性问题。举个例子录像机在 192.168.50.x 网段摄像头在 192.168.1.x 网段两个网段之间如果没配置静态路由或者 VLAN 互通那无论你怎么填 RTSP 地址流都过不来。go2rtc 本身的处理能力再强也只能拉它自己能访问到的地址。所以跨网段场景下第一个思路是让 go2rtc 这台机器能路由到摄像头网段。可以在宿主机上加多个网卡一个连摄像头网段一个连办公网段或者在上游路由器做静态路由。然后 go2rtc 配两条路由指向不同网段它就能把多个网段的摄像头流都拉回来。第二个思路是用 Docker macvlan 网络让 go2rtc 容器直接绑定到摄像头所在的物理网段分配一个同网段 IP。这种做法在网络隔离比较严格的园区里很实用因为容器不再是跨越三层去取流而是在二层直接面对摄像头。配置 macvlan 需要确认宿主机网卡支持并且 macvlan 网络不能让宿主机直接与容器互通所以通常只作为接入专项使用。还有第三个思路比较取巧既然 go2rtc 支持多个实例那就在每个摄像头网段各部署一个 go2rtc然后让中心节点去拉各网段 go2rtc 转出来的 RTSP 流。相当于用多个轻量代理把网络打通这个方案我在多站点项目里用过部署成本低排查也直观。3.4 本地视频源树莓派 CSI、V4L2 USB 摄像头如果你的摄像头不是网络摄像头而是树莓派上的 CSI 接口摄像头或者是智能小车上的 USB 摄像头go2rtc 也能处理。在 Linux 系统里这些设备最终都会映射成一个视频设备节点也就是/dev/video0。go2rtc 支持 V4L2 输入源配置示例streams: usb_camera: - v4l2:/dev/video0 rpi_csi: - libcamera:0v4l2适用于 USB 摄像头包括常见的 ESP32-S3 USB 摄像头模块和很多支持 UVC 协议的摄像头libcamera主要面向树莓派 CSI 接口的摄像头比如 ov5647 模块。要注意的是在 Docker 里要访问这些设备必须把宿主机设备映射进容器。用 docker run 时加上--device /dev/video0:/dev/video0用 docker compose 时写devices: - /dev/video0:/dev/video0少了这一步容器里根本看不到物理设备go2rtc 会一直报打开设备失败。树莓派的 CSI 摄像头如果你用的是较新的系统还可能需要配合 libcamera-apps 先确认设备能正常出图再接进 go2rtc。4. 浏览器低延迟播放WebRTC、HLS、MJPEG 到底选哪个4.1 为什么浏览器不能直接播 RTSP视频流进了 go2rtc 之后摆在面前的下一个问题是怎么让用户在浏览器里直接看而不装任何插件。RTSP 不是浏览器原生支持的协议哪怕是 Chrome 也不会内置 RTSP 播放能力。过去大华、海康的网页端都要求装 ActiveX 插件换到非 IE 浏览器就抓瞎。HLS 和 WebRTC 没有这个问题。go2rtc 支持把同一个源转换成多种输出格式你按使用场景挑选就行。不需要挨个配置go2rtc 会自动根据请求的 API 路径做转换。最简单的是直接在 Web 界面里点播放go2rtc 会优先尝试使用 WebRTC 播放如果网络环境不允许会回退到其他方式。你不需要手动选择协议这一点对新手很友好。4.2 go2rtc 的输出通道对于想在别的页面或系统里嵌入播放的场景go2rtc 提供了一批 HTTP API 地址。假设你已经配置了一个名为cam1的流HLS 播放地址http://192.168.1.100:1984/api/hls/cam1.m3u8MJPEG 播放地址http://192.168.1.100:1984/api/mjpeg?srccam1MP4/fMP4 播放地址http://192.168.1.100:1984/api/mp4?srccam1RTSP 输出地址给其他录像机或软件消费rtsp://192.168.1.100:8554/cam1RTMP 输出地址rtmp://192.168.1.100:1935/cam1HLS 的兼容性最好几乎所有浏览器、手机端播放器都能直接播放代价是延迟一般在 2~5 秒如果只是监控回看或近距离查看完全够用。MJPEG 是逐帧 JPEG 图片流兼容性极强很多老旧的监控面板协议都认它但带宽占用大、画面更新率有限适合做缩略图或者嵌入式老系统对接。4.3 WebRTC 的低延迟表现和实际优化如果你需要低延迟播放比如门铃、看护、远程操纵机器人这种场景建议用 WebRTC。WebRTC 基于 UDP 传输配合 SRTP 加密在局域网内延迟通常能做到 200ms 到 500ms这个体感基本就是实时了。go2rtc 的 Web UI 默认就会用 WebRTC 来播放如果你要在自己的页面里嵌入可以调用 go2rtc 的 WebRTC API。我这里先给一个思路前端页面通过 HTTP API 发起 WebRTC 请求拿到 SDP 后交给 go2rtc 建立连接。如果 WebRTC 总是连接不上先排查三件事查看浏览器控制台有没有 ICE 失败记录go2rtc 所在机器的 UDP 端口是否被防火墙拦截容器是否做了正确的 UDP 端口映射跨公网使用时WebRTC 需要 STUN 服务器配合网络地址转换。go2rtc 的配置文件里可以指定 ICE 服务器webrtc: ice_servers: - stun:stun.l.google.com:19302STUN 只是让双方找到可用的公网路径并不像有些人想的那样是媒体数据经过的中转服务器所以延时开销很小。5. 一个真实的接入案例小米、海康、大华共用一个页面5.1 配置清单和逐条解释我实际项目里有一个 all-cams 配置把小米、海康、大华三家的摄像头都收了进来。先说小米的问题小米家用摄像头特别是国内版对标准 RTSP 支持很差有些型号只有通过米家 App 或者云存储才能看好在部分海外版和部分新固件开放了 ONVIF 或 RTSP。我的这台小米智能摄像头恰好支持 RTSP固件设置里开启局域网访问后就能拿到取流地址。另外小米摄像头的 ONVIF 能力是可以通过搜索引擎查到社区反馈的配置前先在局域网扫一下是否有开放 8000 端口有就说明 ONVIF 服务起来了。如果完全找不到那只能走米家 Appgo2rtc 也救不了它。完整示例配置如下streams: xiaomi: - rtsp://xiaomi_user:pass192.168.1.70:554/stream0 haike_nvr: - rtsp://admin:pass192.168.1.64:554/Streaming/Channels/101 - rtsp://admin:pass192.168.1.64:554/Streaming/Channels/102 dahua_ptz: - rtsp://admin:pass192.168.1.65:554/cam/realmonitor?channel1subtype0我在这里把海康 NVR 的 101 和 102 同时写在了haike_nvr下面这样 go2rtc 会在主码流出问题时自动尝试子码流。虽然不是严格的双源备份但至少提高了稳定性。实际测试中如果 NVR 主码流带宽过高我也经常会主动去请求 102 子码流作为低延迟预览源。5.2 多路画面批量预览配置好之后不需要再做任何前端开发直接在浏览器里打开 go2rtc 的 Web UI左侧能看到已配置的所有流点哪一个都会直接播放。如果要在同一个页面里看多路画面技术上有两种方案。第一种是直接打开多个标签页每个标签页指向不同的流地址。这个操作最简单缺点是浏览器多进程内存占用高。第二种是把 go2rtc 的播放页面通过 iframe 嵌入你自己的监控面板。比如iframe srchttp://192.168.1.100:1984/stream.html?srcxiaomi width640 height360/iframe iframe srchttp://192.168.1.100:1984/stream.html?srchaike_nvr width640 height360/iframe组装一个 2x2 的网格页面就能同时看到多路画面。Home Assistant 里也可以直接通过 iframe 卡片把这个页面嵌进去这样手机端、PC 端都能在一个面板里管理全部摄像头了。5.3 录像和存储go2rtc 负责转发Frigate 负责记录很多 NAS 用户希望把摄像头录像存到大容量存储里。go2rtc 本身不处理录像但它和其他录像系统配合得很好。我目前使用的方案是 go2rtc FrigateFrigate 从 go2rtc 的 RTSP 输出口拉流录制存储目录挂载到 NAS。docker-compose 里大致这样services: go2rtc: image: alexxit/go2rtc:latest restart: always ports: - 1984:1984 - 8554:8554 - 1935:1935 volumes: - ./go2rtc.yaml:/config/go2rtc.yaml frigate: image: ghcr.io/blakeblackshear/frigate:stable restart: always volumes: - ./frigate.yml:/config/config.yml - /mnt/nas/recordings:/media/frigate devices: - /dev/video0:/dev/video0 ports: - 5000:5000Frigate 的配置里摄像头源直接指向 go2rtc 转出来的 RTSP 地址比如rtsp://go2rtc:8554/haike_nvr。这样 go2rtc 负责把多个摄像头协议统一转成 RTSPFrigate 只消费这一种协议稳定性会好很多。有的同学直接用 FFmpeg 循环录像也是一种方案ffmpeg -i rtsp://192.168.1.100:8554/haike_nvr \ -c copy -f segment -segment_time 3600 -reset_timestamps 1 \ /recordings/haike_%Y%m%d_%H%M.mp4segment_time 控制单个录像文件的时长。之前有朋友问老摄像头存储大文件大的话怎么弄其实就是把切片时长从 3600 秒调小比如 600 秒或 300 秒这样每个文件体积更小好管理也方便按时间段回看。6. 部署后你必须知道的调优和安全注意点6.1 主码流/子码流切换与带宽计算很多人上来就全部用主码流接入结果 8 路 4K 摄像头全部拉主码流交换机先扛不住了。先算一笔账单路 4K 主码流大约 8~12Mbps8 路就是 64~96Mbps接近千兆网卡的极限再加上 NVR 录像、NAS 备份链路带宽瞬间被打爆。建议分场景用流实时预览和低延迟播放优先使用子码流一般 720P/1080P 在 1~3Mbps多路画面很流畅重要区域录像用主码流录制保证细节公网或弱网下的远程查看只开子码流同一路摄像头同时有人查看和录像时用 go2rtc 把子码流用于实时预览主码流交给录像服务消费go2rtc 有一个很有用的点你可以在 Web UI 或 API 里针对不同消费方选择不同的源。一个摄像头地址可以是子码流录制侧可以单独引用主码流互不干扰。6.2 常见故障卡顿、延迟、摄像头掉线重连接入后最常见的坑有三个。第一是密码或地址里的特殊字符导致拉流失败。比如密码里带、#、/这类特殊字符URL 解析会错位。解决方式是先用工具把特殊字符做 URL 编码再填入。比如密码原本是abc123要写成abc%40123。第二是摄像头反复掉线。这可能是摄像头端 RTSP 连接数限制导致的很多低端摄像头只允许同时建立 1~2 路 RTSP 会话。解决办法是不要用多个服务同时去拉同一台摄像头的 RTSP统一通过 go2rtc 拉流其他消费方都从 go2rtc 的转出地址取流这样摄像头侧只有 go2rtc 这一个会话。第三是 NTP 时间不同步。有些摄像头时钟不准导致 ONVIF 事件时间、录像时间轴全部乱掉。接入前先确认摄像头开了 NTP 校准并且 Docker 容器里也设置了正确的时区。前面 compose 里的TZAsia/Shanghai就是干这个的。6.3 不要在公网裸奔go2rtc 的认证和反代go2rtc 默认没有较强的认证机制Web UI 和 API 一旦暴露到公网等于把摄像头画面直接送给路人。这是绝对不能忽略的事情。正确的远程访问姿势是在 go2rtc 前面加一层 Nginx/Caddy 反向代理并开启 Basic Auth 或更严格的身份认证。一个很基本的 Nginx 反代配置片段大概是location / { proxy_pass http://127.0.0.1:1984; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; auth_basic Restricted; auth_basic_user_file /etc/nginx/.htpasswd; }用htpasswd生成账号密码文件然后只把反代端口暴露给需要访问的人。如果只是局域网内使用尽量把 go2rtc 的 1984 端口绑定在内网 IP 或仅限内网访问不建议直接写0.0.0.0:1984映射到公网。另外如果摄像头本身有 ONVIF 的 Web 服务端口不要把它和 go2rtc 一起暴露出去。ONVIF 设备默认端口8000、80、554在公网裸奔非常危险特别是有些摄像头还开着弱口令。重要的事再说一遍go2rtc 只在内网取流暴露出去的只有经过反代保护的画面页面。6.4 Docker 更新、备份和迁移go2rtc 的更新频率不算高但隔几个月升级一次也很正常。升级前先备份配置文件cp /opt/go2rtc/go2rtc.yaml /opt/go2rtc/go2rtc.yaml.bak docker compose pull docker compose up -d如果新版本出了问题恢复也很简单直接把配置文件和镜像回退到旧版本即可。配置文件是 go2rtc 唯一的持久化状态流定义、日志级别、API 设置全在里面日常备份这一个文件就够。迁移到新机器时同样只要把go2rtc.yaml和 compose 文件复制过去再重新docker compose up -d就能无缝恢复。我在树莓派和 NAS 之间迁移过一次整个过程没超过五分钟。回到最开始那个问题为什么要折腾 go2rtc说白了我只是想在一个网页里把所有摄像头看完不用为每一路摄像头的牌子单独记一个 App 地址。做完这个项目之后我对这套架构最满意的一点是它把摄像头接入这件事从各家厂商的封闭生态里抽离了出来全部归口到一个标准协议层。以后再买摄像头不用再关心它到底哪家的只要能出 RTSP 或者能被 ONVIF 发现就能直接并进这个平台。如果让我重新部署一遍我一定先把网段规划和摄像头激活状态检查做完再去写 go2rtc 的配置这会少走很多弯路。