go2rtc WebRTC 接入与部署完全指南:多协议客户端、服务端候选配置与 WHIP/WHEP 推拉流
go2rtc WebRTC 接入与部署完全指南:多协议客户端、服务端候选配置与 WHIP/WHEP 推拉流
导读
WebRTC 是 go2rtc 中"延迟最低"的拉流与推流通道:浏览器直接与 go2rtc 建立点对点(Peer-to-Peer)连接,媒体数据不经过任何中间代理。本文以 internal/webrtc/README.md 为核心,系统讲解 go2rtc 作为 WebRTC Client 可接入的七种信令协议(go2rtc 原生、WHEP、Kinesis、OpenIPC、SwitchBot、Wyze、Creality)及其 URL 写法;作为 WebRTC Server 时的监听端口、公网候选地址(candidates)、STUN/TURN 与过滤器(filters)完整配置;以及通过浏览器或 OBS(WHIP)向 go2rtc 推流的方法。读完本文,你将能独立把各类 WebRTC 摄像头接入 go2rtc,并完成从局域网到公网(静态 IP / 动态 IP / TURN)的全套连通性配置。
一、WebRTC Client:一种源类型,多种连接协议
在 go2rtc 中,webrtc 是一类流源(source)类型。在 go2rtc.yaml 的 streams 中,把 URL 前缀写为 webrtc: 即可声明一个 WebRTC 源,例如:
streams:
camera1: webrtc:ws://192.168.1.123:1984/api/ws?src=camera1
URL 以 # 分隔出查询参数区(#format=...、#client_id=...、#ice_servers=<a href="https://link.gitcode.com/i/1d5484ee8a0dd02713e2df1bbe67ff24" target="_blank">...] 等)。从源码结构看,分发入口位于 [internal/webrtc/client.go 的 streamsHandler:它先按 # 切分出参数,再根据 URL scheme 与 format 参数选择具体客户端实现——ws/wss 走 go2rtc 原生 / Kinesis / OpenIPC / SwitchBot,http/https 走 WHEP / Wyze / Creality / Milestone。因此,"webrtc: 源"实际是一个按协议自动分发的多格式入口。
版本说明:go2rtc 原生、WHEP、Wyze、Kinesis、OpenIPC、SwitchBot、Creality 分别于 v1.3.0、v1.6.1、v1.7.0、v1.9.10 等版本加入。
1. Creality(3D 打印机摄像头)
Creality 3D 打印机摄像头使用自定义 HTTP 信令。需要指定 #format=creality:
streams:
creality_k2p: webrtc:http://192.168.1.123:8000/call/webrtc_local#format=creality
从实现看,client_creality.go 会把 SDP Offer 封装为 JSON 对象后 Base64 编码,以 Content-Type: plain/text POST 到打印机,再从响应中 Base64 解码出 Answer;随后 fixCrealitySDP 会修正 Creality 返回的 SDP——跳过第一个编解码器及含 x-google 的 fmtp/rtpmap 属性,避免 pion 库解析失败(详见源码注释与 相关 issue 对应的处理逻辑)。
2. go2rtc 原生格式(推荐,支持双向音频)
这是仅 go2rtc 之间互通的格式,通过 WebSocket 信令连接:
streams:
webrtc-go2rtc: webrtc:ws://192.168.1.123:1984/api/ws?src=camera1
与 WHEP 不同,它支持异步 WebRTC 连接(无需等待全部 ICE candidate 即可交换 SDP,候选后续通过 webrtc/candidate 消息补发)和双向音频(客户端 Offer 中同时携带 audio sendonly 媒体,用于回传麦克风)。对应服务端处理是 internal/webrtc/webrtc.go 中注册的 asyncHandler,消息类型为 webrtc/offer、webrtc/answer、webrtc/candidate(支持 V1 原始 SDP 与 V2 JSON 对象两种交换方式)。
3. Amazon Kinesis Video Streams
Kinesis 源使用 wss:// 信令地址,所有凭据以查询参数携带,其中 client_id 与 ice_servers(JSON 格式的 RTCIceServer 列表)必须显式给出:
streams:
webrtc-kinesis: webrtc:wss://...amazonaws.com/?...#format=kinesis#client_id=...#ice_servers=[{...},{...}]
实现细节(kinesis.go):go2rtc 通过 WebSocket 与 AWS 信令服务交换 SDP_OFFER / SDP_ANSWER / ICE_CANDIDATE 三类消息(recipientClientId 即 client_id),ice_servers 参数经 UnmarshalICEServers 解析进 pion 配置。对 Kinesis 源,还可以配合 internal/echo/README.md 用 bash、python 等任意脚本语言动态获取连接参数。
4. OpenIPC(开源固件摄像头)
OpenIPC 是开源 IP 摄像头固件,其 WebRTC 信令走 WebSocket(/webrtc_ws 端点),需要 #format=openipc 并通常要指定 ice_servers:
streams:
webrtc-openipc: webrtc:ws://192.168.1.123/webrtc_ws#format=openipc#ice_servers=[{"urls":"stun:stun.kinesisvideo.eu-north-1.amazonaws.com:443"}]
从 openipc.go 看,握手顺序是:连接信令 WS → 接收 webrtc_answer(远程 Offer)→ 本地生成 Answer 并以 {"req":"answer","data":...} 回发 → 继续通过 webrtc_candidate 交换候选。
5. SwitchBot(基于 Kinesis 的摄像头)
SwitchBot 的 Pan/Tilt Cam Plus 2K、Pan/Tilt Cam Plus 3K 与 Smart Video Doorbell 基于 Kinesis Video Streams,因此复用 Kinesis 客户端(#format=switchbot),并额外支持 resolution 与 play_type 参数:
streams:
webrtc-switchbot: webrtc:wss://...amazonaws.com/?...#format=switchbot#resolution=hd#play_type=0#client_id=...#ice_servers=[{...},{...}]
参数取值(见 switchbot.go):resolution 支持 hd(0)、sd(1)、auto(2);play_type 为整型,缺省为 0。需要注意的是,SwitchBot 的 Outdoor Spotlight Cam 1080P/2K、Pan/Tilt Cam、Pan/Tilt Cam 2K、Indoor Cam 基于 Tuya 平台,不支持此 WebRTC 接入方式。
6. WHEP(WebRTC-HTTP Egress Protocol)
WHEP 是面向 WebRTC 观看端(viewer)的 HTTP 拉流标准,其后续演进方向是 WebRTC/WISH 标准;目前部分第三方软件已支持 WHEP,go2rtc 亦可作为 WHEP 客户端:
streams:
webrtc-whep: webrtc:http://192.168.1.123:1984/api/webrtc?src=camera1
实现上(client.go 的 whepClient):创建 PeerConnection 后,把完整 SDP Offer(CreateCompleteOffer)以 Content-Type: application/sdp POST 到 api/webrtc?src=...,读取响应体作为 Answer。
7. Wyze(经 docker-wyze-bridge)
通过 docker-wyze-bridge 以 WebRTC 协议连接 Wyze 摄像头的旧方式:
streams:
webrtc-wyze: webrtc:http://192.168.1.123:5000/signaling/camera1?kvs#format=wyze
从 kinesis.go 的 wyzeClient 看:先 GET 桥接服务的 /signaling/...?kvs 端点,解析返回的 ClientId、servers(ice_servers)与 signalingUrl,再以 Kinesis 协议完成后续握手。需要原生 P2P 直连(不经 docker-wyze-bridge)可改用 Source: Wyze。
8. Milestone(源码支持的补充格式)
除 README 列出的七种外,源码还实现了 #format=milestone(见 milestone.go),用于连接 Milestone 监控平台的 WebRTC 会话。URL 形如:
streams:
webrtc-milestone: webrtc:https://milestone-host/api#format=milestone#username=User#password=TestPassword#cameraId=...
其流程为:先向 /IDP/connect/token 换取 access_token,再 POST /REST/v1/WebRTC/Session 创建会话并获取远程 Offer,本地生成 Answer 后 PATCH 回写会话。可选参数还包括 streamId、playbackTime、skipGaps、speed。
9. 客户端握手状态机
无论哪种协议,底层都遵循标准的 SDP/ICE 状态流转。go2rtc 的 pkg/webrtc/README.md 用 pkg/webrtc 库的视角总结了典型时序:
pc.CreateOffer()生成 OfferSetLocalDescription(offer),进入have-local-offer- ICE Gathering 完成(
gathering → complete) SetRemoteDescription(answer),信令状态回到stable- ICE 连接状态
checking → connected
在实际接入排障时,可对照该状态机定位问题出现在信令交换阶段还是候选连通阶段。
二、WebRTC Server:先理解"点对点"的本质
go2rtc 同时也是一个 WebRTC 服务端,为浏览器 / Home Assistant / Frigate 等提供低延迟拉流。在配置之前,需要建立以下基本认知(来自官方文档):
- WebRTC 几乎总是浏览器与 go2rtc 之间的直接点对点连接;
- 使用 Home Assistant、Frigate、Nginx、Nabu Casa、Cloudflare 等软件时,它们只参与连接建立,不参与媒体数据转发;
- WebRTC 媒体无法在 HTTP 连接内部传输;
- 通常 WebRTC 在客户端和服务端使用随机 UDP 端口建立连接;
- 跨局域网时,通常借助公网 STUN 服务器完成连接建立,STUN 同样只参与建连、不参与数据传输;
- WebRTC 会自动发现本机与公网的所有地址并尝试连通。
如果通过 STUN 建立外网连接,还需知晓其边界:
- 它使用 UDP 打洞(hole punching) 技术,即使未向公网开放服务器端口,也可能穿透 NAT;
- 对约 20% 的用户该技术会失效,原因是 对称型 NAT(Symmetric NAT);
- UDP 不适合在公网传输 2K/4K 高码率视频,丢包率较高——4K 场景请优先考虑 HLS 等其他传输方式。
三、服务端配置建议:让外部能连进来
默认情况下 go2rtc 在 TCP 与 UDP 的 8555 端口 同时监听 WebRTC 连接(源码默认值见 internal/webrtc/webrtc.go 的 cfg.Mod.Listen = ":8555")。该端口可直接用于外网访问,也可在 YAML 中修改:
webrtc:
listen: ":8555" # 本机监听地址与端口(TCP/UDP)
静态公网 IP
- 在路由器上转发 8555 端口(外部端口可用 8555 或其他任意端口);
- 在 YAML 中写入你的公网 IP 与公网端口:
webrtc:
candidates:
- 216.58.210.174:8555 # 拥有静态公网 IP 时使用
动态公网 IP
- 在路由器上转发 8555 端口;
- 在 YAML 中写入
stun关键字与外部端口——go2rtc 会自动通过 STUN 服务器探测公网地址:
webrtc:
candidates:
- stun:8555 # 拥有动态公网 IP 时使用
实现原理(internal/webrtc/candidates.go 与 pkg/webrtc/helpers.go):stun: 前缀的候选地址会触发 go2rtc 向配置的 STUN 服务器发送 Binding Request 获取公网 IP,结果缓存 5 分钟(GetCachedPublicIP)。实际使用的 STUN 服务器来自 ice_servers 配置中的 stun: 条目。
进阶方案 1:自建 TCP 隧道
如果拥有个人 VPS,可以建立 TCP 隧道,按"静态公网 IP"的方式配置,只需把 YAML 中的地址换成 VPS 的 IP。
进阶方案 2:使用 TURN 服务器
如果拥有个人 VPS,可部署 TURN 服务器(例如 coturn)作为媒体中继,彻底解决对称 NAT 与 UDP 打洞失败问题:
webrtc:
ice_servers:
- urls: [stun:stun.l.google.com:19302]
- urls: [turn:123.123.123.123:3478]
username: your_user
credential: your_pass
四、完整配置与源码级解析
以下为完整演示配置(官方强调:此示例不是用于直接复制粘贴的,请按需裁剪):
webrtc:
# 固定本地 WebRTC 媒体的 TCP 或 UDP 端口
listen: ":8555" # 本机服务器地址
# 手动追加 host 候选地址
# 顺序很重要,排在前面的优先级更高
candidates:
- 216.58.210.174:8555 # 静态公网 IP
- stun:8555 # 动态公网 IP
- home.duckdns.org:8555 # 域名
# 追加自定义 STUN / TURN 服务器
# 使用 ice_servers: [] 可移除默认项并留空
ice_servers:
- urls: [ stun:stun1.l.google.com:19302 ]
- urls: [ turn:123.123.123.123:3478 ]
username: your_user
credential: your_pass
# 自动发现逻辑的可选过滤列表
# 部分设置仅在未指定固定 UDP 端口时才有意义
filters:
# 发送给对端的自动发现候选列表
# 包含来自 listen 选项的候选
# 使用 candidates: [] 移除全部自动发现候选
candidates: [ 192.168.1.123 ]
# 启用 localhost 候选
loopback: true
# 用于连接的网络类型
# 包含来自 listen 选项的候选
networks: [ udp4, udp6, tcp4, tcp6 ]
# 用于连接的网卡列表
# 包含来自 listen 选项(空主机)的网卡
interfaces: [ eno1 ]
# 用于连接的主机 IP 列表
# 包含来自 listen 选项(空主机)的 IP
ips: [ 192.168.1.123 ]
# 随机 UDP 端口范围 [min, max]
# 与 listen 选项无关
udp_ports: [ 50000, 50100 ]
各配置项与源码的对应关系如下:
listen 的三种形态
listen 的值形如 address/network,源码中通过 strings.Cut(cfg.Mod.Listen, "/") 拆分为监听地址与网络类型:
| 写法 | 效果 |
|---|---|
":8555"(默认) |
固定 TCP 端口 + 每个直连会话固定 UDP 端口 |
":8555/tcp" |
固定 TCP 端口 + 随机 UDP 端口 |
"" |
关闭 TCP 监听,仅使用随机 UDP 端口 |
对应到 pkg/webrtc/api.go 的 NewServerAPI:当 network 为空或为 tcp 时启用 SetICETCPMux,为空或为 udp 时启用 SetICEUDPMux。注意 UDP mux 不会监听未指定地址(0.0.0.0),而是遍历所有被过滤器放行的网卡 IP 分别绑定端口(newUDPMux 的逻辑)。
candidates 与优先级
手动添加的候选会被转换为标准的 ICE host candidate 字符串(CandidateICE,TCP 类型为 tcptype passive,见 pkg/webrtc/helpers.go)。优先级规律(candidates.go 的 AddCandidate):
- 序号从 1 开始累加,手动候选的优先级低于内建(自动发现)候选;
- 列表越靠前的候选优先级越高;
- UDP 候选基础优先级高于 TCP passive 候选(
PriorityHostUDP > PriorityHostTCPPassive)。
ice_servers 的默认值
未配置时,go2rtc 默认使用 stun:stun.cloudflare.com:3478 与 stun:stun.l.google.com:19302(见 internal/webrtc/webrtc.go)。设置 ice_servers: [] 可清空默认项。
filters 逐项说明
Filters 结构体定义在 pkg/webrtc/api.go(webrtc.Filters):
candidates:host 候选白名单,仅当候选 IP 出现在列表中才会发送给对端;loopback:是否包含回环(localhost)候选,开启后还会在 SettingEngine 中调用SetIncludeLoopbackCandidate(true);networks:允许的网络类型,支持udp4/udp6/tcp4/tcp6,会被映射为 pion 的NetworkType与 ICE mux 的监听网络;interfaces:网卡白名单,作用于 pion 的SetInterfaceFilter;ips:IP 白名单,作用于 pion 的SetIPFilter;udp_ports:随机 UDP 端口范围(恰好两个元素时生效),对应SetEphemeralUDPPortRange。
五、过滤器(filters)实战
重要前提:默认情况下,go2rtc 会无条件排除所有 Docker 风格的候选地址(172.16.0.0/12 网段),且该行为无法关闭(见 candidates.go 的 FilterCandidate 与 pkg/webrtc/api.go 中默认 ipFilter 对 Docker 网段的排除逻辑)。
过滤器用于排除不必要的候选。多余的候选不会让连接更差或更好,但错误的过滤配置可能破坏一切连接——如果看不懂这项配置,建议直接跳过不设置。
场景 A:go2rtc 安装在宿主机
若宿主机存在多余网卡,可用 interfaces 或 ips 只保留相关地址;若服务器支持 IPv6 但家庭网络不支持,可排除 IPv6 候选:
webrtc:
listen: ":8555/tcp" # 固定 TCP 端口 + 随机 UDP 端口
filters:
ips: [ 192.168.1.2 ] # 服务器 IP 地址
networks: [ udp4, tcp4 ] # 跳过 IPv6(如不支持)
场景 B:go2rtc 运行在封闭 Docker 容器内(如 Frigate)
不要过滤 Docker 网卡,否则 go2rtc 将无法连接到任何地方;但可以过滤 Docker 候选,因为外部无法连接它们。同时通过 candidates 手动添加宿主机端口映射后的地址:
webrtc:
listen: ":8555" # 固定 TCP 与 UDP 端口
candidates: [ 192.168.1.2:8555 ] # 手动 host 候选(利用 Docker 端口转发)
六、Streaming Ingest:把设备变成推流端
除拉流外,go2rtc 还支持反向使用——把浏览器或专业推流软件变成"IP 摄像头"。
Ingest:浏览器(支持视频 + 双向音频)
任意 PC 或手机的浏览器都可以变成支持视频与双向音频的 IP 摄像头,甚至可以直接广播电脑屏幕:
- 在
go2rtc.yaml中创建空流(无 source 的 stream); - 打开 go2rtc WebUI;
- 进入该流的 links 页面;
- 选择
camera+microphone(摄像头+麦克风)或display+speaker(屏幕+扬声器)选项; - 打开 webrtc 本地页面(对应前端页面 www/webrtc.html;注意你的 go2rtc 必须运行在 HTTPS 下),或通过 WebTorrent 技术分享链接(默认走 HTTPS)。
Ingest:WHIP(WebRTC-HTTP Ingest Protocol)
可使用 OBS Studio 或其他支持 WHIP 协议的推流软件(WHIP 标准尚未正式获批,需使用支持该协议的开发版 OBS):
- 在 OBS 中设置:
设置 > 推流 > 服务: WHIP,服务器地址填:
http://192.168.1.123:1984/api/webrtc?dst=camera1
注意与拉流 URL 的差异:拉流用 src=,推流用 dst=(目标流名)。
服务端 API 汇总(源码视角)
WebRTC 服务端在 internal/webrtc/server.go 的 syncHandler 中实现,POST /api/webrtc 依据 Content-Type 分发三种 API:
| Content-Type | 行为 |
|---|---|
application/json |
接收 {"type":"offer","sdp":"v=0..."},返回 {"type":"answer","sdp":"v=0..."} |
application/sdp |
WHEP 拉流;返回 201 Created 的 SDP Answer |
其他(如 application/x-www-form-urlencoded) |
原始/base64 编码的 SDP 交换(兼容旧 API) |
?src=流名:拉流方向(WHEP,将流推给对端);?dst=流名:推流方向(WHIP,接收对端媒体);DELETE /api/webrtc?id=会话ID:关闭指定 WHIP 会话(WHIP 响应头Location: webrtc?id=...中携带会话 ID);OPTIONS:返回 204,用于跨域预检。
此外,WebSocket 异步端点(/api/ws 上的 webrtc、webrtc/offer、webrtc/candidate 消息)由 internal/webrtc/webrtc.go 的 asyncHandler 处理,支持浏览器 WebUI、Home Assistant 等实时信令场景;连接建立后,媒体方向由 SDP 判定(IsConsumer/IsProducer,见 webrtc.go):请求发送视频则为消费端,请求接收视频则为推流端。
七、相关标准与延伸阅读
- WHIP(WebRTC-HTTP Ingest Protocol)草案:
draft-ietf-wish-whip-01; - WHEP(WebRTC-HTTP Egress Protocol)草案:
draft-murillo-whep-01(其后续演进方向为 WISH 标准); - 参考实现:
Glimesh/broadcast-box(WebRTC 广播参考实现)、OBS Studio 的 WHIP 支持相关 PR; - WebRTC 编码实战参考:
webrtc-c0d3l4b代码实验室; - WebTorrent 相关 FAQ(用于浏览器推流分享链路的实现参考)。
如需深入了解,可直接阅读本仓库的 internal/webrtc/README.md、internal/webrtc 目录下的各协议实现,以及 pkg/webrtc 的底层封装(编解码注册、ICE 候选构造、STUN 探测等)。