1. 为什么先把 go2rtc 拉进 Docker:多协议拆解

1.1 RTSP、RTMP、HLS、WebRTC、MJPEG 到底各管什么

如果你和我一样同时接触过安防摄像头、直播推流、Web 播放器这三类东西,会很快意识到一个尴尬:摄像头最擅长的是 RTSP,但浏览器不直接支持 RTSP;直播平台习惯收 RTMP 推流,但监控头又不会自己推 RTMP;手机浏览器里 HLS 能放,延迟却又高得离谱;至于 MJPEG,虽然老设备喜欢用,但码率爆炸、画质一般。于是你不得不在各个协议之间来回搭桥。

go2rtc 解决的就是这件事,它把这些协议的“翻译”工作集中到一个轻量进程里。它本身不是转码工具,而是一个多协议流媒体网关——往里看,可以接 RTSP 摄像头、RTMP 直播流、HTTP-FLV、HLS、MJPEG、WebRTC 源;往外看,又能以任意一种你想要的协议把同一路视频分发出去。这一点非常关键,去看 go2rtc 的文档时会发现它的核心概念只有一个:stream。一个 stream 就是一路视频源,你给它取个名字,比如 front_door ,之后所有客户端都通过这个名字来拿流,至于拿的时候想要 RTSP 还是 HLS 还是 WebRTC,由 go2rtc 处理。

我在部署之前最担心的是“协议转换会不会要 CPU 重编码”,实际跑起来才发现大部分情况它只做 remux(转封装),也就是把 H.264/H.265 的裸流从 RTSP 容器里拆出来,再装进 WebRTC 或 HLS 的容器里,视频编码不做二次压缩。摄像头是什么编码,输出就是什么编码,所以 CPU 占用低得不像一个实时转码服务。只有当浏览器不支持原始编码(比如某些安卓 WebView 不认 H.265)时,才需要走 ffmpeg 转码,这个你可以选择在 go2rtc 里让它自动调用,也可以自己单独开一个 ffmpeg 进程处理,灵活度很高。

1.2 和 ffmpeg / mediamtx / 成品NVR 有什么本质区别

网上讲流媒体服务器,经常会被几个名字绕晕:ffmpeg、mediamtx(原 rtsp-simple-server)、gstreamer、ZoneMinder、Frigate、go2rtc。我刚开始也以为它们都是同类,其实分工差别很大。

ffmpeg 是“瑞士军刀”,几乎什么都能做,但它是命令行工具,本身不常驻监听端口,也缺少给客户端动态分配流的能力。你用它做流媒体服务,需要自己写一大堆进程管理、流状态判断、端口分配逻辑,复杂度一下子上去了。

mediamtx 更接近“纯 RTSP/RTMP 服务器”,它能收流、能分发,但有协议覆盖没有 go2rtc 广,尤其在 WebRTC 和 HLS 上,需要配合其他组件才能做到低延迟浏览器播放。

NVR(比如成品录像机、Synology Surveillance Station)解决的是录像、回放、告警、用户权限管理,它更重,也更偏存储,而不是灵活输出。你要把摄像头流接给 Home Assistant 或自研 Web 页面,NVR 往往给不出一个干净的 rtsp:// 地址让你随便消费。

go2rtc 的定位正好卡在中间:不抢 NVR 的录像饭碗,不做重转码,但它把“摄取多路源”和“多协议分发”这件事做到极简。举个例子,我有一只摄像头同时要被 Home Assistant 抓实时流、被一个老旧的 NVR 拉存储流、被家里人用手机浏览器看,放在以前得分别配置三套路径,在 go2rtc 里只需要配置一个流名称,三种消费端自己去选协议。这个“一份源、多协议分发”的模型,是我最终留下来的主要原因。

1.3 go2rtc 不适合干的事

我也要泼一点冷水,它不是万能的。第一,它默认不带录像回放 UI,你可以拿它配合 ffmpeg 落地录像文件,但没有漂亮的时间轴回放界面;第二,它没有用户权限体系,登录、多用户、摄像头权限隔离这些都得靠外部的反向代理或认证网关来做;第三,如果需要广电级大规模分发、多节点调度,它同样不是这个方向。

所以比较恰当的使用姿势是:go2rtc 做流媒体接入与协议汇聚的那一层,录像、告警、权限交给其他更专业的系统。把这个边界想清楚,后面才不会出现“装了 go2rtc 之后发现少了一堆功能”的落差感。

2. 部署前的规划:网络模式、端口和摄像头选型一起说

2.1 摄像头侧最常见的前置条件

我踩过最大的坑其实发生在部署 go2rtc 之前:有一台家用摄像头压根不提供 RTSP 地址,只给手机 App 看,问客服也只丢过来一个“云平台专用”的说法。这种设备无论用什么流媒体服务器都接不进去,所以选摄像头时第一条原则就是确认支持 RTSP/ONVIF 标准协议。海康、大华、TP-Link、宇视这些安防品牌的主流型号基本都支持,但一些纯互联网品牌的低成本云台摄像头很可能不支持主动拉流。

另一件容易被忽略的事是摄像头是否需要先“激活”。新买的海康/大华设备,第一次通电后必须用官方工具或浏览器激活并设置密码,否则 RTSP 地址就算写对了,也会返回 401 或连接被拒绝。很多刚接触的人卡在“扫到设备但拉不到流”,其实不是 go2rtc 的问题,而是摄像头还没有完成初始化。先把摄像头通过官方 App 或浏览器后台激活,再进到“网络-高级设置”里打开 RTSP 服务,接下来才有得谈。

2.2 端口规划与 network_mode: host 的原因

go2rtc 默认监听这几个端口:

端口 协议 用途
1984/TCP HTTP Web 管理界面、API、HLS、MJPEG 输出
8554/TCP RTSP RTSP 拉流端口,客户端用 rtsp://IP:8554/流名称 访问
8555/UDP WebRTC WebRTC 信令与媒体传输的默认入口

我在 Docker 里给 go2rtc 用的网络模式是 network_mode: host ,而不是默认的 bridge。原因很直接:WebRTC 要同时走 UDP 和 TCP,端口协商比较活跃,如果用桥接网络再手动映射端口,遇到 ICE 候选地址取不到正确 IP 的情况会非常痛苦。host 模式下容器直接复用宿主机网络栈,go2rtc 能拿到真正的局域网 IP,WebRTC 通话建立的稳定性高很多。代价就是端口隔离没了,宿主机上其他服务要避开这三个端口,尤其是 8555 这种 UDP 端口冲突时不太容易察觉。

如果是在 Windows 上用 Docker Desktop,host 网络的支持有限,那就只能老老实实用端口映射,把 1984/TCP 、 8554/TCP 、 8555/UDP 映射出去,同时确认 Windows 防火墙放行对应端口。局域网内自用没问题,但不要指望 WebRTC 在所有复杂网络环境里都一次成功。

2.3 资源你想多了:其实省得很

我用一台只有 2 核 CPU、2GB 内存的老迷你主机跑了 8 路 1080p 摄像头,全部走 H.264 直通、不做转码的情况下,内存占用长期在 200MB 上下,CPU 占用在 5% 到 15% 之间波动,主要消耗在 WebRTC 打包和 HTTP 分发。真正吃 CPU 的是 ffmpeg 转码,尤其是 H.265 转 H.264,那个开销能瞬间吃满好几个核。所以如果预算有限,路线很明确:优先保证摄像头输出 H.264,go2rtc 只做 remux,资源问题基本不存在。

3. Docker Compose 部署 go2rtc 的完整过程

3.1 目录结构与 compose 文件

我的宿主机上专门建了一个目录 /opt/go2rtc ,所有文件都放在里面,方便后续备份配置和查看日志。目录结构很简单:

mkdir -p /opt/go2rtc
cd /opt/go2rtc
touch go2rtc.yaml

go2rtc.yaml 是它的核心配置文件。初次部署时不需要写太多,先留一个最小配置让它跑起来,确认服务正常后再慢慢加摄像头流。最小配置大概长这样:

log:
  level: info
api:
  listen: ":1984"
rtsp:
  listen: ":8554"
webrtc:
  listen: ":8555"

如果你用的是 Docker Compose, docker-compose.yml 可以这样写:

services:
  go2rtc:
    image: alexxit/go2rtc:latest
    container_name: go2rtc
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./go2rtc.yaml:/config/go2rtc.yaml

注意这里的挂载路径是 /config/go2rtc.yaml ,不是随意指定的,go2rtc 镜像默认从 /config 目录读取配置。漏掉这个路径,启动后你会发现自己写的配置完全没生效。另外官方镜像名是 alexxit/go2rtc ,在 Docker Hub 上可以直接拉取;如果网络拉镜像慢,设置一下 Docker 的镜像加速器会省心很多。

3.2 启动与验证

一切都准备好后,执行:

docker compose up -d
docker compose logs -f go2rtc

日志里没有报错后,先验证 HTTP 服务是否正常:

curl http://localhost:1984/api/streams

刚启动时没有配置任何流,这条命令会返回一个空数组或空的 JSON 对象,那就说明服务已经在跑了。接着打开浏览器访问 http://宿主机IP:1984 ,会看到 go2rtc 自带的 Web UI。这个界面虽然朴素,但非常实用,既能实时预览流,也能直接在里面测试添加摄像头。

到这一步已经完成了 80%,剩下的时间基本都花在配摄像头上。如果你是第一次跑,建议先别急着加很多路流,就加一路最熟悉的摄像头,把“从 Web UI 点击预览”这条链路打通,再批量接入剩余设备。

3.3 Windows/macOS 用 Docker Desktop 时的桥接替代

如果你没有 Linux 服务器,只能用 Docker Desktop,host 网络方案在 Windows 上并不好用。这时候改为端口映射模式:

services:
  go2rtc:
    image: alexxit/go2rtc:latest
    container_name: go2rtc
    restart: unless-stopped
    ports:
      - "1984:1984"
      - "8554:8554"
      - "8555:8555/udp"
    volumes:
      - ./go2rtc.yaml:/config/go2rtc.yaml

这种模式下,Web UI 访问 http://localhost:1984 没问题,RTSP 播放一般也能通,WebRTC 预览则可能因为 NAT 和防火墙问题出现“一直连接中”。我的建议是:Windows 上如果只是临时调试,用 bridge 模式足够;要长期当家庭流媒体中心用,还是弄一台 Linux 小主机或者直接用 WSL2 里的 Docker 更省事。

4. 接入摄像头的正确姿势:从已知 URL 到可用流

4.1 主流摄像头 RTSP 地址规则

写 go2rtc 配置之前,最关键的是搞到摄像头的真实 RTSP 地址。这个地址不是随便猜的,不同品牌有固定的 URL 风格。

海康威视系设备常见格式:

rtsp://用户名:密码@摄像头IP:554/Streaming/Channels/101

其中 101 表示第一通道的主码流, 102 是第一通道的子码流。如果摄像头是多通道的,依次是 201 、 202 这样。萤石云设备如果已经开放了 RTSP,也沿用类似的路径。

大华系设备常见格式:

rtsp://用户名:密码@摄像头IP:554/cam/realmonitor?channel=1&subtype=0

这里的 channel=1 是通道号, subtype=0 表示主码流, subtype=1 表示子码流。子码流通常只有 720p 甚至更低,码率也小,适合多人预览和网页低延迟播放;主码流则留给录像或需要在电视上全屏看高清的场景。

还有一部分摄像头支持 ONVIF 标准,但 ONVIF 自己并不定义绝对的 RTSP 路径,它只告诉你的客户端“RTSP URL 在哪个地址”。用 ONVIF Device Tool 或 VLC 的“打开网络串流”功能,输入设备的 IP 和账号密码,有时候能直接探测到。但要注意,扫到 URL 不代表能一直稳定用,很多固件会把 RTSP URL 动态调整,所以我还是建议进入摄像头后台的“媒体设置”页面里查看官方给出的地址示例,那个最靠谱。

4.2 把多路摄像头写进 streams 配置

有了地址后,在 go2rtc.yaml 里的 streams 段中逐条写入即可:

streams:
  gate:
    - rtsp://admin:密码@192.168.1.64:554/Streaming/Channels/102
  yard:
    - rtsp://admin:密码@192.168.1.65:554/cam/realmonitor?channel=1&subtype=1
  workshop:
    - rtsp://admin:密码@192.168.1.66:554/Streaming/Channels/102

这里我给 gate 、 yard 、 workshop 分别起了容易记的名字,后面所有客户端都按名字取流,不用再记一大串带账号密码的 RTSP URL。注意我特意用了子码流(海康 102 ,大华 subtype=1 )来做日常预览,因为这几路只是需要“能看”,子码流已经足够,还能大幅节省带宽和并发压力。

改完配置后重启容器让配置生效:

docker compose restart go2rtc

然后回到 Web UI,你应该能看到这三条流出现在列表里,点击任意一条就能实时播放。如果播放失败,先别急着改配置,用后面的 ffprobe 方法验证一下原始地址究竟通不通。

4.3 临时摸流:Web UI 和 ffprobe 配合

go2rtc 的 Web UI 上有一个输入框,可以直接临时添加流,不需要编辑配置。这个功能对调试特别友好,你可以先在 UI 上写一条 test: rtsp://... ,点击连接看看能不能出画面。但临时加的流不会保存到配置文件里,重启后会消失,确认没问题后还是要写进 go2rtc.yaml 。

如果想从命令行侧确认摄像头地址是否正确,用 ffprobe 是最快的:

ffprobe -rtsp_transport tcp "rtsp://admin:密码@192.168.1.64:554/Streaming/Channels/102"

如果能够打印出视频流信息,说明地址和账号密码都没问题。如果卡住不动或报错,大概率是地址格式错了、账号密码不对、摄像头没有启用 RTSP,或者摄像头和 go2rtc 之间跨了网段导致 RTSP 握手失败。我遇到过一台摄像头在 VLAN 的另一个网段里,VLC 能看,go2rtc 却连不上,最后发现是交换机上限制了该网段的 RTP 媒体端口,放行后立刻就好了。像这种跨网段问题,最好先临时把 go2rtc 网段和摄像头网段打通,或者通过交换机出口放行 UDP/TCP 端口,不要指望纯软件层能解决。

5. 多协议分发实战:一部流给所有端

5.1 浏览器低延迟预览靠 WebRTC

go2rtc 最惊艳我的部分是它内置的 WebRTC 能力。以前要在 Web 页面上看 RTSP 监控,常见方案是后端用 ffmpeg 转 HLS,延迟随随便便两三秒,而且每增加一个观看者就要多开一路转码进程。go2rtc 的浏览器预览走的是 WebRTC,协商好后可以直接点对点传媒体流,延迟通常在几百毫秒左右,而且不需要额外装插件。

在 Web UI 里点击某个流名称,go2rtc 会尝试用 WebRTC 建立连接。如果几秒内出画面,说明端口和网络都正常。如果一直转圈,优先排查 8555/UDP 端口是否被防火墙拦截、宿主机的网络是否为 NAT 多层环境。WebRTC 对 NAT 穿透的要求比较苛刻,在公司网络或手机热点环境下,偶尔会因为 UDP 被限制而失败,这时可以退回 HLS 模式看,只是延迟会变高。

5.2 统一输出给 ffplay、VLC、HLS、MJPEG

摄像头接入 go2rtc 后,一个流名称就可以对应多种播放方式。比如我在客厅的电视盒子上用 VLC 看门口摄像头,VLC 里填的就是:

rtsp://go2rtc主机IP:8554/gate

如果是在公司电脑上临时网页查看,直接访问:

http://go2rtc主机IP:1984/gate.m3u8

go2rtc 会根据路径后缀自动把流转成对应协议。 gate.m3u8 就是 HLS 播放列表, gate.mjpeg 就输出 MJPEG 流,一些老的嵌入式设备或低版本浏览器可以通过 MJPEG 方式拿到画面。命令行下想快速验证流是否正常,可以:

ffplay -rtsp_transport tcp rtsp://127.0.0.1:8554/gate

这种“同一个流名称,后缀决定协议”的设计,几乎不需要学习成本,对集成开发特别友好。我在给自己的小面板写监控页面时,只需要把 http://go2rtc主机IP:1984/gate.mjpeg 塞进 <img> 标签,页面立刻就有画面了。

5.3 一份输入多份输出:推到 RTMP/其他系统

go2rtc 的 streams 配置支持把同一个源同时输出到多个目标。写法是把源地址和输出地址写成一个数组:

streams:
  gate:
    - rtsp://admin:密码@192.168.1.64:554/Streaming/Channels/102
    - rtmp://127.0.0.1:1935/live/gate

这样 go2rtc 会先拉取摄像头 RTSP,再以 RTMP 客户端身份推给本地另一个流媒体服务或直播平台。以前要做这种“一边本地看、一边推直播平台”的操作,得自己写一个 ffmpeg 循环脚本,还要处理断流重连;go2rtc 这里把输入和输出当成同一个流,断流后会自动重连,省了很多事。

如果你有多个下游系统,继续往数组里加即可。不过要提醒一点:每个输出目标都会增加一路会话,虽然 go2rtc 不转码,但网络带宽和连接数还是会线性增加。我的经验是,下游在局域网内就优先用 RTSP 或 WebRTC,跨公网就用 RTMP 或 HLS,不要所有目标全都用同一种协议。

5.4 想要录像文件?go2rtc 不做 NVR 但能配合

go2rtc 自身不带录像管理界面,但它输出的 RTSP 流非常适合被外部工具消费。我自己用了一个很轻量的方案:写一个定时 ffmpeg 命令,从 go2rtc 拉一路 RTSP,按小时切片保存成 MP4。

ffmpeg -rtsp_transport tcp -i rtsp://127.0.0.1:8554/gate -c copy \
  -f segment -segment_time 3600 \
  "/video/gate_%Y%m%d_%H%M%S.mp4"

这段命令的关键是 -c copy ,不转码,只做封装切片,CPU 占用几乎为零。只要磁盘够大,就能按小时生成一段录像。配合 cron 定期清理过期文件,一个轻量录像系统就搭起来了。回放体验虽然不如专业 NVR,但对家庭用、只求留痕来说是性价比极高的方案。

如果家里有 Frigate 这类 AI 检测系统,也可以让它直接连 go2rtc 的 rtsp://127.0.0.1:8554/流名称 ,这样前端预览统一走 go2rtc 的 WebRTC,后端检测分析走 Frigate,两不耽误。

6. 跑了一段时间后踩到的坑,能少走就少走

6.1 花屏/延迟高:先怀疑传输而不是转码

我刚开始接入某台 TP-Link 摄像头时,Web UI 预览花屏严重,画面还经常卡住。第一反应是 go2rtc 转码不行,后来用 ffprobe 直接连原始 RTSP 地址也同样花屏,才意识到是摄像头和主机之间的网络传输问题。这种花屏大概率是 RTSP 默认使用 UDP 传输,跨交换机或 WiFi 环境下丢包导致的。

解决方法有几个方向:一是到摄像头后台把 RTSP 传输方式改成 TCP,很多摄像头默认是 UDP;二是降低码率,主码流如果设成 8Mbps 以上,普通 WiFi 的稳定性很难扛住;三是尽量用有线连接摄像头和 go2rtc 主机。go2rtc 这边的配置也可以加参数强制走 TCP,但不同品牌对参数的支持不太一样,最稳妥还是在摄像头后台直接设置。

还有一个小技巧:同一路摄像头,日常预览用子码流,需要截图或录像时才切主码流,这样花屏概率会小很多。我用海康的 102 子码流和主码流做过对比,子码流在弱网下的表现明显稳定,画面清晰度也足够手机观看。

6.2 并发连接多了之后 UDP 端口不够用

go2rtc 的 WebRTC 默认监听 8555/udp,但实际的媒体流离不开 RTP 端口分配。当多个客户端同时通过 WebRTC 看不同摄像头,或者一个页面开了多个画面时,偶尔会出现“连接建立成功但画面一直黑”的情况,看日志会发现是 UDP 端口资源不足。这个问题在 host 网络模式下相对少见,但如果你用的是 bridge 映射模式,Docker 的端口隔离会加剧这个现象。

我的处理方式是:如果 WebRTC 并发要求高,优先回到 host 网络模式;在宿主机防火墙里放行 8555/udp,以及可能需要的高位 UDP 端口段。因为 go2rtc 在不同版本里对 WebRTC 端口的使用策略有调整,最直接的办法是启动后查看日志里有没有 “Failed to listen” 之类的报错,根据日志提示去放行对应端口,比照着旧文档猜省力得多。

6.3 私有协议摄像头“扫到地址也拉不动”的真实原因

有些摄像头虽然可以被 ONVIF 工具扫到,甚至显示出一个 RTSP 地址,但填到 go2rtc 里就是连不上。我遇到过两种典型情况:一种是通过 ONVIF 扫出来的地址里带的是 ONVIF 服务端口,而非 RTSP 媒体端口,需要手动改成 554 ;另一种是摄像头固件限制只有特定来源 IP 可以发起 RTSP 拉流,比如只在“可信 IP”里加白名单,go2rtc 所在主机的 IP 不在名单里就会被静默拒绝。这类问题排查起来很费时间,我的建议是直接进摄像头后台找“RTSP 认证”或“访问策略”相关选项,把 go2rtc 主机 IP 加进允许列表。

另外,如果你手头有“只支持主动推流的云摄像头”,也就是设备只会定时往厂商云平台推流,不支持外部主动拉 RTSP,那么任何流媒体服务器都拉不到它。这种情况只能放弃接入,或者换支持 RTSP 推送的设备。不要被“扫到端口”这个表象迷惑,连接不上就是连接不上,别再耗时间。

6.4 默认零认证:千万别把 1984 直接暴露到公网

go2rtc 默认没有任何认证机制,这是它保持轻量的一部分原因,但也意味着只要你的 1984 端口暴露在公网,任何能访问到的人都可以看到全部摄像头的画面,甚至可以往里面添加自己的流的地址,完全失控。这个安全隐患比“录像被偷看”更严重,因为攻击者可以利用它做跳板。

我的安全原则很明确:

  1. go2rtc 的端口只允许局域网或受信任的内网网段访问,公网防火墙直接丢弃外部到 1984 的请求;
  2. 如果确实需要远程观看,前面要套一个带身份认证的反向代理,用 Basic Auth 或更复杂的登录认证都行,不要在 go2rtc 本身上面裸奔;
  3. 不要为了方便直接做端口转发,修改 go2rtc 默认端口也改变不了什么,攻破扫描器对常见端口的探测易如反掌,真正要解决的问题是“谁能访问它”,而不是“它在哪个端口”。

最后分享一个我的使用习惯:我会把所有摄像头的子码流都以 流名称_sub 的形式接入,浏览器和 Home Assistant 预览统一用子码流,只有需要回看细节时才切到主码流。这样既保证了多端并发的流畅性,又不会因为带宽压力影响整个网络。go2rtc 这套东西不复杂,但它把“多协议流媒体平台”的门槛降低了非常多,如果你手上正好有一堆规格参差不齐的摄像头,很值得花一个晚上部署起来。

Logo

邀请您加入社区

更多推荐