go2rtc WebRTC 接入与部署完全指南:多协议客户端、服务端候选配置与 WHIP/WHEP 推拉流

原创2026-09-13 18:27:291,898 阅读
文章标签:音视频视频后端智能硬件

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 库的视角总结了典型时序:

  1. pc.CreateOffer() 生成 Offer
  2. SetLocalDescription(offer),进入 have-local-offer
  3. ICE Gathering 完成(gathering → complete)
  4. SetRemoteDescription(answer),信令状态回到 stable
  5. 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

  1. 在路由器上转发 8555 端口(外部端口可用 8555 或其他任意端口);
  2. 在 YAML 中写入你的公网 IP 与公网端口:
webrtc:
  candidates:
    - 216.58.210.174:8555  # 拥有静态公网 IP 时使用

动态公网 IP

  1. 在路由器上转发 8555 端口;
  2. 在 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 摄像头,甚至可以直接广播电脑屏幕:

  1. 在 go2rtc.yaml 中创建空流(无 source 的 stream);
  2. 打开 go2rtc WebUI;
  3. 进入该流的 links 页面;
  4. 选择 camera+microphone(摄像头+麦克风)或 display+speaker(屏幕+扬声器)选项;
  5. 打开 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 探测等)。

登录后查看全文
go2rtc