Docker部署go2rtc:统一RTSP/ONVIF/WebRTC摄像头流媒体网关实战
如果你手里已经攒了大大小小好几台摄像头,海康、大华、萤石、小米,还有一个树莓派上的 CSI 摄像头,你很快就会意识到一个问题:它们各家有各家的 App、各家的私有协议,根本没法拉到同一个界面里统一看。折腾过一段时间后,我现在的选择是 Docker 部署一个叫 go2rtc 的轻量多协议流媒体网关,把 RTSP、ONVIF、MJPEG、HLS 这些五花八门的摄像头源全部收编,再统一输出成浏览器里可以秒开的 WebRTC / HLS 流。go2rtc 这个项目是我目前用过的最省心的摄像头接入层,尤其适合玩智能家居、树莓派和自建安防监控的人。这篇文章就把我从零开始部署、接入、排坑的完整过程写下来,给准备上手的人一条能直接照抄的路线。
1. 为什么选择 go2rtc:先弄懂它到底解决了什么
1.1 多协议聚合,黑灯瞎火的设备也有出路
摄像头这个圈子最烦人的地方就是协议太散。十几年前的老摄像头只给一个 MJPEG 网页预览地址,主流 IP 摄像头走 RTSP,很多互联网摄像头又只提供 HLS 或自家私有协议,再加上音视频封装格式 H.264、H.265、AAC、G.711 混着来,想全部统一处理,靠人肉逐个写命令非常崩溃。
go2rtc 的核心思路就是"我不挑食"。它同时支持 rtsp、rtmp、hls、mjpeg、onvif 这些输入形式,也支持 ffmpeg 作为万能兜底。你只要把各个摄像头的数据源丢给它,它就能把这些不同协议的输入统统转成统一的内部流,然后再按需输出成 WebRTC、HLS、RTSP、RTMP、MJPEG 甚至截图。换句话说,你给它一个 RTSP 的海康摄像头,它同时可以吐出一个 HLS 地址给你手机浏览器看,吐出一个 RTSP 地址给录像机用,再吐出一个 MJPEG 地址给老旧的网页模板。
这种多对多的协议映射,省掉了非常多的中间环节。以前我需要跑三四个服务,一个负责拉流,一个负责转 HLS,一个负责网页播放;现在 go2rtc 一个进程全干了。尤其是设备品牌杂、协议乱的家庭环境,一套 go2rtc 足够把"接入层"全部统一。
1.2 为什么 WebRTC 低延迟是关键
监控场景最怕延迟。普通 HLS 播放从摄像头画面到手机屏幕,延迟经常在 3 到 10 秒之间,看门口有人按门铃,等你打开 App 画面都凉了。go2rtc 内置了一个基于 WebRTC 的低延迟播放链路,配合它自带的 WASM 播放器页面,实测在局域网环境下,从摄像头到浏览器画面的延迟经常可以压到 500 毫秒以内,体感就是"几乎实时"。
WebRTC 不需要你额外搭一套复杂的流媒体服务器,它走的是 P2P 加信令的思路。go2rtc 负责信令协商和媒体数据的转发,浏览器端通过原生 WebRTC 直接接收视频帧,省去了 HLS 分段切片的等待,也不像 RTSP 那样受浏览器原生不支持的限制。这一点对于"想用浏览器直接看监控"的场景来说是决定性的,也是我选它而不是自己堆 ffmpeg 的原因。
当然 WebRTC 也有它的脾气:如果客户端和服务端跨了复杂 NAT,可能需要 TURN 服务器中转。这个我在后面公网访问的章节里会细化。局域网内绝大多数情况是零配置直接可用的。
1.3 和 Mediamtx、自建 ffmpeg 方案比,优势在哪
可能有人会问,还有 Mediamtx、SRS、Nginx-RTMP 这些方案,为什么偏偏推荐 go2rtc?我用这几个项目都做过测试,简单说说体感差异。
Mediamtx(原 RTSP-Simple-Server)也是一款很优秀的轻量流媒体服务器,支持 RTSP、RTMP、HLS、WebRTC 等协议转换,但我个人觉得它的 Web 管理和多路摄像头接入体验不如 go2rtc 方便。Mediamtx 更多是解决"推流进来再转发出去"的问题,而 go2rtc 默认就是一个偏"摄像头接入"的网关,内置了 ONVIF 发现和 Web 界面,第一次上手时更舒服。
自建 ffmpeg 方案也常见,比如写一堆 shell 脚本循环跑 ffmpeg,把每路 RTSP 转成 HLS。这个方法的问题在于:进程管理麻烦,每路摄像头都要维护一个进程;转出来的 HLS 延迟高;临时加一路流要重新起进程、改配置。go2rtc 则是一份 YAML 配置管所有流,内部并发模型由 Go 运行时托管,进程稳定性和资源占用都更可控。
我个人的选择逻辑是:如果你只想转发单路流、追求极致简单,用 Mediamtx 没问题;如果摄像头数量多、协议杂、还要浏览器低延迟观看,go2rtc 会更合适。我自己部署了四台摄像头加一个树莓派摄像头源,go2rtc 单容器跑了大半年,稳得很。
2. Docker 部署前的规划和准备
2.1 盘点摄像头类型和取流地址
动手部署前,先把你的摄像头清单理一遍。绝大多数支持 RTSP 的 IP 摄像头,取流地址无非下面几种模板:
海康威视主码流:
rtsp://账号:密码@IP:554/Streaming/Channels/101
,子码流把最后的 101 改成 102。大华摄像头主码流:
rtsp://账号:密码@IP:554/cam/realmonitor?channel=1&subtype=0
,子码流把 subtype 改成 1。一些杂牌摄像头默认是单码流,路径可能直接在
/live
或
/h264
,这个需要看说明书或者用扫描工具确认。
我在接入之前,习惯先在电脑上用 VLC 打开一遍 RTSP 地址,确认账号密码、端口、路径都没问题。这一步非常重要,因为很多排查到最后发现根本不是 go2rtc 的问题,而是摄像头地址写错了,或者密码里有
@
、
#
这种特殊字符没有做 URL 编码。
另外要考虑硬盘录像机(NVR)的情况。如果摄像头接在录像机后面,直接从 NVR 取流通常比逐个穿到摄像头更省心,尤其摄像头 IP 和你的 Docker 主机不在同一个 VLAN 时。取流地址就是录像机的 RTSP 端口,路径一般带通道号,具体去录像机后台查。
2.2 端口与网络拓扑
go2rtc 默认的 Web / API 端口是 1984,这个端口同时提供网页界面和 JSON API。WebRTC 播放的场景下,还需要一个用于媒体数据收发的端口,默认是 8555,协议上 UDP 和 TCP 都要放开。如果你打算对外输出 RTSP 或 RTMP,还要额外留出 8554 和 1935 这两个转发口。
Docker 部署时,网络模式我强烈建议优先考虑 host 模式。host 模式下容器直接使用宿主机网络,没有端口映射这层概念,和局域网摄像头之间天然同网段,不需要担心容器 NAT 导致访问不到摄像头的问题。缺点也很明显,端口直接占用宿主机的端口,管理上要小心冲突。
如果你用的是 Docker Desktop 或者习惯了 bridge 网络,也可以,但一定要把 1984、8555 映射出来,而且容器里访问摄像头要填宿主机局域网 IP,不能填 localhost,否则容器内看到的是容器自己的回环地址,根本连不上摄像头。所以最简单粗暴的部署方案就是启动容器时加
network_mode: host
。
2.3 Docker 环境和镜像加速
部署 go2rtc 之前,先把 Docker 环境准备好。Linux 服务器和树莓派上通常直接安装 docker-ce 或使用发行版自带的 docker 包;Windows 和 macOS 用 Docker Desktop 最方便。Docker 安装好之后,建议顺手配置一个国内能稳定访问的镜像加速器,否则拉取
alexta69/go2rtc
这个镜像时会比较痛苦。
镜像加速器的配置只需要编辑
/etc/docker/daemon.json
加入
registry-mirrors
列表,然后重启 docker 服务。这个是环境准备阶段最不起眼但最影响体验的一件事,尤其你在国内网络环境下拉镜像,没加速器的时候等进度条能等出心理阴影。
3. 亲手部署 go2rtc 容器
3.1 用 docker-compose 一键启动
我习惯用 docker-compose 管理这类常驻服务,配置清晰、可以跟着项目走。在宿主机上新建一个目录,例如
/opt/go2rtc
,里面放一个
docker-compose.yml
:
services:
go2rtc:
image: alexta69/go2rtc:latest
container_name: go2rtc
network_mode: host
restart: unless-stopped
volumes:
- /opt/go2rtc/go2rtc.yaml:/config/go2rtc.yaml
command: -config /config/go2rtc.yaml
如果你不想用 host 模式,也可以用 bridge 加端口映射:
services:
go2rtc:
image: alexta69/go2rtc:latest
container_name: go2rtc
restart: unless-stopped
ports:
- "1984:1984"
- "8555:8555"
- "8554:8554"
- "1935:1935"
volumes:
- /opt/go2rtc/go2rtc.yaml:/config/go2rtc.yaml
command: -config /config/go2rtc.yaml
注意这里的端口映射不是越多越好。如果你用不到 RTMP 输出,就不必暴露 1935;用不到 RTSP 输出,8554 也可以不映射。原则是最小暴露,减少安全风险。
3.2 初始化 go2rtc.yaml 配置
go2rtc 的所有流配置都放在一个 YAML 文件里。常见的初始配置大概长这样:
streams:
living_room: rtsp://admin:yourpassword@192.168.1.64:554/Streaming/Channels/101
yard_sub: rtsp://admin:yourpassword@192.168.1.65:554/cam/realmonitor?channel=1&subtype=1
raspi_cam: ffmpeg:rtsp://192.168.1.88:8554/raspi#video=copy#audio=copy
每个键名是流的 ID,后面跟的是取流地址。如果某个源需要先用 ffmpeg 做处理,可以用
ffmpeg:
前缀,后面用
#video=copy
这种参数表示尽量无损复制,避免 CPU 被无谓的转码吃掉。
还有几个全局选项值得加上:
log:
level: info
webrtc:
listen: ":8555"
listen
配置里写了
:8555
表示监听所有网卡的 8555 端口。WebRTC 播放时,浏览器会拿到这个端口作为 ICE 候选,所以请确保这个端口在防火墙上能正常进出数据包。
3.3 启动、验证与日志排查
配置文件准备好之后,在目录里执行:
docker compose up -d
如果用的是 docker run,等价命令是:
docker run -d --name go2rtc --network host \
-v /opt/go2rtc/go2rtc.yaml:/config/go2rtc.yaml \
alexta69/go2rtc -config /config/go2rtc.yaml
启动后我习惯先看日志:
docker logs -f go2rtc
正常的日志里会打印配置加载的路径,以及当前启用的模块信息。接着打开浏览器访问
http://宿主机IP:1984
,正常情况下可以看到 go2rtc 的 Web 界面,界面上会列出你配置的 streams。如果某个流显示红色错误状态,优先点开流旁边的图标去看具体报错,最常见的错误无非是连接超时、认证失败、协议不支持三种。
我更推荐用 API 快速验证:
curl http://localhost:1984/api/streams
返回的 JSON 里会包含每路流的状态和错误信息,比肉眼观察 Web 界面更直接。
4. 配置多协议流:摄像头接入实战
4.1 从 RTSP 源接入
接入 RTSP 摄像头是最常规的操作。以海康摄像头为例,在 go2rtc.yaml 里加一行:
streams:
hik_main: rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101
这里有个容易被忽略的坑:如果密码里包含特殊字符,一定要做 URL 编码。比如密码是
abc@123
,
@
必须写成
%40
,否则 go2rtc 会把后面那部分当成主机地址的一部分,直接导致连接失败。一个保险的验证办法是先写进 ffprobe 或者 VLC 里测一遍,能放出来再填进 YAML。
接入之后建议立刻用 WebRTC 测试一下延迟和画面是否正常。如果摄像头支持 H.265 编码,或者分辨率太高导致浏览器解码卡顿,可以再用
ffmpeg:
前缀强制转一次编码:
streams:
hik_h265: ffmpeg:rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101#video=copy
video=copy
代表视频编码不改,只是封装转换。如果播放端设备不支持 H.265,就需要去掉
copy
,改成具体的编码参数,例如
#video=h264
,让 ffmpeg 负责实时转码。这个过程很吃 CPU,所以一般不建议多路同时硬转,机器配置不够就优先使用摄像头的子码流。
4.2 走 ONVIF 自动发现
有些摄像头你不一定记得准确取流地址,尤其大华或一些杂牌设备,菜单里绕来绕去就是找不到 RTSP 路径。这时 go2rtc 的 ONVIF 支持就派上用场了。ONVIF 是安防设备通用的网络接口标准,go2rtc 内置了相关模块,可以直接在配置里写:
streams:
auto_cam: onvif://admin:password@192.168.1.66
go2rtc 会尝试通过 ONVIF 协议去探测这台设备支持的服务和 Media Profile,然后自动建流。不过我在实际使用中,ONVIF 的自动发现成功率取决于设备对标准的兼容程度,有些老设备只能探测到基本信息,最后还是得手动补充 RTSP 路径。所以我的建议是:ONVIF 用来做辅助发现和验证,真正稳定接入还是优先手动 URL。
另外,如果你有一堆摄像头都不知道 IP,可以用 go2rtc 的 API 在局域网做扫描,这类功能能帮你快速找到在线设备,省去挨个猜 IP 的麻烦。
4.3 树莓派 CSI 摄像头和 USB 摄像头的接入
树莓派上的 ov5647 摄像头模块是一个典型场景。很多人以为 go2rtc 可以直接读取
/dev/video0
,其实不然。go2rtc 本身不是采集器,它更擅长接管已经产生的流媒体。所以我的做法是,先在树莓派上用 ffmpeg 把 CSI 摄像头的数据推成一路 RTSP 流,再让 go2rtc 去接入。
简单方案是用 libcamera 启动一个 RTSP 推流,或者用 ffmpeg 读取设备:
ffmpeg -f v4l2 -input_format h264 -i /dev/video0 \
-c copy -f rtsp rtsp://localhost:8554/raspi
如果你的树莓派摄像头输出的是 YUV 原始图像,ffmpeg 命令要加编码参数,比如
-c:v h264
,让 CPU 编码成 H.264。然后在 go2rtc.yaml 里加一行:
streams:
raspi: rtsp://127.0.0.1:8554/raspi
USB 摄像头也类似,先用 ffmpeg 读取 V4L2 设备转成 RTSP,或者如果你用的是支持直接输出 RTSP 的固件,就省掉中间层。总之原则是"设备采集"和"协议转换"分离,go2rtc 专注做流媒体网关,采集工作交给 ffmpeg 或摄像头固件完成。
4.4 局域网与公网访问
配置好摄像头之后,局域网内访问 web 界面直接点选即可。如果需要从外部网络访问,就复杂一些,但可以先把架构理清:
局域网内访问且系统简单时,直接在浏览器打开
http://宿主机IP:1984
,然后选择对应流,go2rtc 底层通过 WebRTC 的局域网候选路径直连,延迟极低。
公网访问时,问题主要集中在 NAT 和防火墙。WebRTC 的媒体数据默认走 8555 端口,所以如果你只是简单地把 1984 和 8555 端口映射到公网,有些网络环境下依然会黑屏。更稳的方案是部署一个 TURN 服务器,在 go2rtc 配置里加上 TURN 地址,让双方协商失败时能走 TURN 中继。这个配置稍微麻烦,但一套下来之后,无论手机在办公室还是出差路上,都能直接拉流预览。
安全上我强烈建议:不要裸奔暴露 1984 端口到公网。即使要开,也要套一层带认证的反向代理,或者至少给 go2rtc 加访问控制。摄像头画面是非常敏感的隐私数据,这个端口一旦暴露,等于把你家门口的实时画面送给全网扫描器,代价远比配置麻烦得多。
5. 浏览器预览、转码与对外集成
5.1 WebRTC 低延迟预览
go2rtc 的 Web 界面比大多数同类项目做得顺手。启动后打开
http://IP:1984
,你配置的所有流会直接列在主面板。点击任意一路流,页面会弹出播放窗口,默认走 WebRTC 协议。如果当前设备网络和宿主机之间 ICE 协商没问题,画面几乎秒开。
这里顺便解释一下为什么 WebRTC 体验这么好:它在浏览器里走的是现代化媒体管道,不用像 HLS 那样分片请求再拼接播放。摄像头源进来后,go2rtc 直接把 RTP 包封装成 WebRTC 需要的格式,浏览器原生解码,所以端到端延迟能压到极低。
如果 WebRTC 在某些网络环境下打不通,go2rtc 也提供备用输出地址。比如 HLS 播放地址是
http://IP:1984/api/hls?src=流ID
,这个地址可以直接填进任何支持 HLS 的播放器,甚至一些智能电视 App 也能用。MJPEG 地址是
http://IP:1984/api/mjpeg?src=流ID
,适合嵌入老式网页或低性能设备。
5.2 HLS / RTSP / RTMP 输出
go2rtc 不只是给浏览器用,它还能把流转发给其他系统。比如你要把某个摄像头流推到 OBS 做直播机位,可以用 RTMP 输出,前提是配置里启用了 RTMP 监听端口:
rtmp:
listen: ":1935"
启动之后,RTMP 地址就是
rtmp://宿主机IP:1935/流ID
。RTSP 输出类似,默认监听 8554 端口,地址是
rtsp://宿主机IP:8554/流ID
。这个能力非常实用,比如我的录像机只接受 RTSP,但某些摄像头是纯 HLS 源,这时候让 go2rtc 中转一路 RTSP 给录像机,比单独再起一个 ffmpeg 进程稳定得多。
需要注意,输出转发的过程中如果源编码是 H.265,而下游录像机或播放器只支持 H.264,就必须在 go2rtc 里加转码配置。go2rtc 转 H.264 的 ffmpeg 模式已经封装得比较方便,开转码后 CPU 占用会明显上升,所以真正落地时建议优先拉子码流,避免所有路数同时做高分辨率转码。
5.3 对接 Home Assistant 等平台
如果你和我一样还玩智能家居,go2rtc 和 Home Assistant 的组合绝对值得尝试。Home Assistant 自带的摄像头集成能力有限,但是配合 go2rtc 之后,你可以把各路摄像头先统一接入 go2rtc,然后在 Home Assistant 里配置 RTSP 或 HLS 地址,最终在仪表盘上看到实时画面。
我在 Home Assistant 里用的方法是添加一个"Stream"摄像头实体,直接填
rtsp://go2rtc宿主IP:8554/流ID
。这样既不需要在 Home Assistant 里单独维护摄像头密码和取流地址,还能把编码或转码策略统一在 go2rtc 这一层控制。后续新增摄像头时,只需要改 go2rtc.yaml 并重启容器,Home Assistant 侧基本不用动。
如果你已经在 NAS 上跑着飞牛、EasyNVR 这类平台,也可以让 go2rtc 平行部署,把它的 WebRTC 低延迟能力作为现有系统的一个补充入口,而不是替代关系。流媒体平台之间本来就是可以互相拉流、互相转发的,多一个 go2rtc 等于多了一层灵活调度能力。
6. 常见问题与避坑总结
6.1 拉流失败原因排查
我在整个调试过程中遇到过最多的就是拉流失败。遇到红叉不要慌,先检查三个点:
第一,摄像头地址能不能在外部播放器里正常打开。用 VLC 或 ffprobe 验证:
ffprobe -rtsp_transport tcp -i "rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101"
能正常解析出视频流信息,说明地址没问题,问题在 go2rtc 这一侧。如果 ffprobe 直接超时,先去检查摄像头 IP、端口、账号密码。
第二,特殊字符是否正确编码。尤其是密码里的
@
、
#
、
:
、
/
这些字符,很多新手在这里翻车。简单做法是找一个在线 URL 编码工具,把整个密码部分编码后再拼进 RTSP URL。
第三,Docker 容器和摄像头之间是否真的通。bridge 模式下容器需要能访问到摄像头 IP,最简单测试是进容器里 ping 摄像头,或者直接用容器内的 busybox 工具测试端口。如果从容器内出不去,那就是网络模式选错了,换成 host 模式通常能解决。
6.2 WebRTC 播放黑屏或卡顿
如果你在 Web 界面看到流状态正常,但 WebRTC 播放黑屏,大概率是 ICE 协商失败或者防火墙挡了 8555 端口。这个问题的典型场景就是局域网内正常、手机用 4G/5G 预览不正常。
解决路径是:确认宿主机防火墙放行了 8555 的 UDP 和 TCP;如果跨公网,优先部署 TURN 服务并在 go2rtc 配置里声明 TURN;实在不想搭 TURN,就先退回 HLS 地址看,延迟高一点但至少能看到画面。
另外还有一股容易被忽略的因素:摄像头源本身是 H.265 且客户端设备浏览器不支持硬解。go2rtc 的 WebRTC 播放是把流直接转给浏览器的,如果浏览器解不动 H.265,就会出现"有流但黑屏"。这种场景要么换用支持 H.265 的浏览器方案,要么让 go2rtc 转码成 H.264。
6.3 容器启动异常和资源占用
Docker 部署 go2rtc 通常很稳定,但有几个坑我踩过。一个是端口被占,尤其 host 模式下 1984 和系统内其他服务冲突。启动后立刻用
docker logs go2rtc
看有没有 "address already in use",有的话就换个端口。
另一个是长时间运行后占用内存偏高。go2rtc 底层是 Go 写的,正常情况内存控制很优秀,但如果你给很多路流都开了 ffmpeg 转码,内存和 CPU 都会上升不少。我的建议是在 compose 文件里加个资源限制:
deploy:
resources:
limits:
memory: 512M
这样即便某一路流源异常导致内存飙升,也不会拖垮整个宿主机。
6.4 安全加固不能省
最后专门说安全。摄像头流是非常敏感的数据,我见过不少人图方便,把 go2rtc 的 1984 端口直接映射到公网,结果被扫描器扫到,画面被拖走。这个风险完全可以通过以下方式避免:
go2rtc 配置里不要长期写明文密码,可以使用环境变量替换,或者至少用权限严格的配置文件。如果服务器在公网,务必把 1984 端口限制为本机或内网访问;需要远程访问时,用带鉴权的反向代理包一层,2FA 与否看你的接受程度。摄像头端的默认密码更要第一时间改掉,否则即使 go2rtc 被保护住,摄像头本身的 RTSP 地址仍然可能被直接访问。
我最近的部署形态是:go2rtc 容器只监听内网,通过内网 DNS 和反向代理提供带 Basic Auth 的访问入口,公网入口不开 1984。这套结构跑了快一年,没有出过任何被扫描的事件。
最后再分享一个心得:摄像头行业协议太散,别试图找到一个通吃所有设备的神器。go2rtc 擅长的是做"协议统一和低延迟预览"这一层,录像存储、AI 识别这些事我建议交给 Frigate、NVR 或各家专业平台去干。我自己的项目里,go2rtc 负责把各路摄像头和树莓派摄像头源全部收拢,然后以 WebRTC 和 HLS 两种协议对外供应,画面稳定、延迟低、配置改动也只需要重启一个容器,折腾完这个基础架构之后,后面加摄像头就真的只是改几行 YAML 的事了。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)