3. WebRTC 核心架构解析:PeerConnection、SDP 协商流程、ICE/STUN/TURN 协议栈、数据通道与媒体通道的区别

这一章,我们直接切入 WebRTC 的“心脏”。

很多做嵌入式移植的朋友,一开始就被 PeerConnection、SDP、ICE 这些术语搞懵了。说实话,我当年第一次看 WebRTC 源码时,也觉得这东西像个黑盒子。但后来在 ARM Cortex-A 平台上调通第一路视频通话后,我才真正理解——WebRTC 的核心架构,其实就三件事:建立连接、协商参数、传输数据。

3.1 PeerConnection:通话的“总指挥”

PeerConnection 是整个 WebRTC 的顶层对象。你可以把它理解成一个“通话管家”。

它负责管理:

  • 本地媒体流的采集与发送
  • 远端媒体流的接收与渲染
  • 网络连接的建立与维护
  • 编解码器的协商与切换

在嵌入式系统里,我习惯把 PeerConnection 看作一个状态机。它的生命周期大致是:

  1. 创建:new RTCPeerConnection(configuration)
  2. 添加流:addTrack() / addTransceiver()
  3. 创建 Offer:createOffer()
  4. 设置本地描述:setLocalDescription()
  5. 接收远端描述:setRemoteDescription()
  6. ICE 连接建立:iceConnectionState 变为 connected
  7. 通话中:媒体流双向传输
  8. 关闭:close()

关键点:在嵌入式平台上,PeerConnection 的配置参数需要特别关注。比如 iceServers 列表、iceCandidatePoolSize、bundlePolicy 等,这些直接影响连接建立的速度和稳定性。

3.2 SDP 协商流程:双方“握手”的协议

SDP(Session Description Protocol)不是传输协议,它只是一个描述格式。说白了,就是通话双方互相告诉对方:“我能支持什么编码?我的 IP 和端口是多少?”

完整的 SDP 协商流程是这样的:

  1. A 创建 Offer:A 调用 createOffer(),生成一个包含 A 端媒体能力、网络信息的 SDP
  2. A 设置本地描述:A 调用 setLocalDescription(offer)
  3. A 发送 Offer 给 B:通过信令通道(WebSocket、MQTT 等)
  4. B 设置远端描述:B 调用 setRemoteDescription(offer)
  5. B 创建 Answer:B 调用 createAnswer(),生成应答 SDP
  6. B 设置本地描述:B 调用 setLocalDescription(answer)
  7. B 发送 Answer 给 A:通过信令通道
  8. A 设置远端描述:A 调用 setRemoteDescription(answer)

嗯,这里要注意:SDP 协商是“先 Offer 后 Answer”的严格顺序。我在调试一个嵌入式摄像头项目时,曾经因为信令通道延迟导致 Offer 和 Answer 乱序,结果 PeerConnection 一直卡在 “have-remote-offer” 状态。后来加了序列号校验才解决。

嵌入式优化建议:在资源受限的嵌入式设备上,SDP 可以预先“裁剪”。比如去掉不支持的编解码器、减少 ICE candidate 数量,能显著降低协商耗时。

3.3 ICE / STUN / TURN 协议栈:穿透 NAT 的“三剑客”

这是 WebRTC 里最绕的部分,也是嵌入式移植的难点。

先看一张我画的协议栈关系图:

ICE/STUN/TURN 协议栈关系图 ICE/STUN/TURN 协议栈关系 WebRTC 应用层 ICE 框架(连接管理) STUN 协议 获取公网 IP + 端口 TURN 协议 中继转发(兜底方案) UDP 传输层(低延迟首选) 说明: ICE 优先尝试 STUN 直连;直连失败后,自动降级为 TURN 中继。 嵌入式设备通常部署在 NAT 后,TURN 服务器是必备组件。

简单解释一下:

  • STUN:帮你找到自己的公网 IP 和端口。我遇到过 STUN 请求被防火墙拦截的情况,后来改用 TCP 封装的 STUN 才解决。
  • TURN:当 STUN 直连失败(比如对称 NAT),TURN 服务器作为中继转发数据。代价是延迟增加、带宽消耗翻倍。
  • ICE:一个“连接管理器”。它收集所有可能的 candidate(本地、STUN、TURN),然后按优先级排序,逐个尝试连接。

嵌入式避坑指南:我曾经在 ESP32 上跑 WebRTC,发现 ICE 连接建立耗时超过 10 秒。原因是默认的 ICE candidate 收集超时太长。建议将 iceCandidatePoolSize 设为 0,并手动控制 candidate 的收集时机。

3.4 数据通道 vs 媒体通道:两条“车道”

很多初学者搞不清这两者的区别。我打个比方:

  • 媒体通道:走的是“高速公路”,专门跑音视频数据。使用 SRTP/SCTP 协议,有 QoS 保障。
  • 数据通道:走的是“普通公路”,传输任意二进制数据。使用 SCTP over DTLS,可靠或不可靠模式可选。

它们的核心差异如下:

对比维度 媒体通道 数据通道
传输内容 音频/视频流 任意二进制数据
底层协议 SRTP / SCTP SCTP over DTLS
可靠性 部分可靠(丢包可容忍) 可靠或不可靠(可配置)
延迟要求 极低(< 100ms) 低(可配置)
典型用途 实时音视频通话 文件传输、游戏指令、传感器数据
嵌入式适配难度 高(需要硬件编解码) 中(纯软件处理)

在嵌入式场景下,我建议优先使用数据通道做控制信令的传输。比如摄像头云台控制、传感器状态上报等,用数据通道比单独建一个 WebSocket 连接更省资源。

实战技巧:数据通道支持“部分可靠”模式(partial reliability)。在嵌入式设备上,如果传输的是非关键数据(如日志),可以设置 maxPacketLifetime 为 100ms,这样超过 100ms 还没发出去的数据就直接丢弃,避免阻塞通道。

3.5 本章小结

这一章我们拆解了 WebRTC 的四大核心模块:

  • PeerConnection:通话的“总指挥”,管理连接生命周期
  • SDP 协商:双方“握手”,确定媒体参数和网络信息
  • ICE/STUN/TURN:穿透 NAT 的“三剑客”,确保连接可达
  • 数据通道 vs 媒体通道:两条“车道”,各司其职