2、WebRTC核心架构:整体架构、API与信令媒体分离设计

WebRTC 的架构,说白了就是一套「浏览器原生音视频引擎」。它把采集、编码、传输、渲染这些脏活累活全包了,你只需要调 API 就行。我刚开始接触时也觉得它像个黑盒,但拆开看其实就几大块。

2.1 整体架构:三层模型

WebRTC 官方架构图分三层,我习惯这么记:

  • 应用层:你写的 JS 代码,调 getUserMedia、RTCPeerConnection 这些 API。
  • 会话管理层:信令状态机、ICE 代理、SDP 协商。这部分浏览器帮你做了,但信令消息得你自己传。
  • 传输与媒体层:DTLS、SRTP、NACK、FEC、JitterBuffer。真正的音视频数据在这跑。

嗯,这里要注意:应用层和传输层是隔离的。你调 addTrack 时,根本不用管底层是 UDP 还是 TCP,WebRTC 自己会选。

核心观点:WebRTC 不是单体的 SDK,而是一组规范 + 浏览器实现。Native 端和 Web 端底层逻辑一样,只是 API 风格不同。

WebRTC 三层架构图 WebRTC 三层架构 应用层 (Application) getUserMedia / RTCPeerConnection / RTCDataChannel Web API (JS) ↔ Native API (C++/Obj-C/Java) 会话管理层 (Session) 信令状态机 / ICE 代理 / SDP 协商 / STUN/TURN PeerConnection 内部状态管理 传输与媒体层 (Transport & Media) DTLS / SRTP / NACK / FEC / JitterBuffer / NetEQ 音视频编解码器 (VP8/VP9/H264/Opus) 应用层只管调 API,传输层自动处理丢包、抖动、加密

2.2 Native 与 Web 端 API 差异

Web 端你写 JS,Native 端你用 C++ 或各平台封装。我做过一个项目,Web 端调 navigator.mediaDevices.getUserMedia 三行代码搞定,Android 端却要写一堆 Camera2 回调。但底层 PeerConnection 的逻辑完全一样。

能力 Web API (JS) Native API (C++/Obj-C/Java)
媒体采集 getUserMedia VideoCapturer / AudioDeviceModule
连接建立 RTCPeerConnection PeerConnectionFactory + PeerConnection
数据通道 RTCDataChannel DataChannelInterface
统计信息 getStats() PeerConnection::GetStats()
自定义编解码 不支持(浏览器限制) 支持(可注入自定义 encoder/decoder)

我的经验:如果你做 RTSP 推流,建议用 Native API。Web 端对 H264 的支持有限,而且无法直接读取 RTSP 流。Native 端你可以自己写一个 VideoCapturer,把 RTSP 解码后的帧喂进去。

2.3 信令与媒体通道分离设计

这是 WebRTC 最容易被误解的地方。很多人以为 WebRTC 自带信令,其实它只定义了 信令状态机,信令消息怎么传——你自己定。

为什么会这样?因为信令没有「标准协议」。你可以用 WebSocket、HTTP、MQTT,甚至拿鸽子送信都行。WebRTC 只管两件事:

  1. 交换 SDP:描述媒体能力(编解码、分辨率、IP 端口)。
  2. 交换 ICE Candidate:告诉对方「我能从哪些地址连到你」。

媒体通道则是另一条路。信令走你的服务器,媒体走 P2P 或 TURN 中继。我见过有人把信令和媒体混在一起,结果 NAT 穿透时乱成一团。

避坑指南:我曾经在一个项目中把信令消息也塞进 RTCDataChannel,想着「省一个连接」。结果信令丢包导致连接状态不同步,排查了两天。记住:信令必须可靠,媒体可以容忍丢包。信令走 TCP/WebSocket,媒体走 UDP/DTLS-SRTP。

信令与媒体通道分离 信令通道 vs 媒体通道 发送端 (RTSP 源 / 摄像头) 接收端 (浏览器 / 播放器) 信令服务器 WebSocket / HTTP SDP / ICE SDP / ICE 媒体通道 (SRTP / DTLS) — P2P 或 TURN 中继 关键设计原则 1. 信令通道:可靠传输(TCP/WebSocket),用于交换 SDP 和 ICE Candidate 2. 媒体通道:实时传输(UDP/DTLS-SRTP),容忍丢包,低延迟优先 3. 两者完全独立,互不影响。信令断了不会丢媒体,媒体卡了不会阻塞信令

2.4 信令状态机:从 Offer 到 Connected

WebRTC 内部维护一个状态机。你调 createOffer 时,状态从 stable 变成 have-local-offer。我刚开始调试时老看 signalingState,后来发现只要按规范走,基本不会出问题。

标准流程是这样的:

  1. 发起方:createOffer → setLocalDescription → 发送 SDP 给信令服务器。
  2. 接收方:收到 SDP → setRemoteDescription → createAnswer → setLocalDescription → 发回 Answer。
  3. ICE 收集:双方各自收集 Candidate,通过信令交换。
  4. 连接建立:ICE 连通性检查通过,媒体开始传输。

注意:信令状态和 ICE 状态是两回事。信令状态表示 SDP 交换进度,ICE 状态表示网络连通性。我见过有人把 iceConnectionState === 'connected' 当成信令完成,结果 SDP 还没交换完就发数据,当然不行。

2.5 媒体通道:P2P 与 TURN 中继

媒体通道默认走 P2P。但现实是大部分设备在 NAT 后面,P2P 打不通。这时候就需要 TURN 服务器做中继。

我做过一个跨运营商的项目,P2P 成功率不到 30%。加了 TURN 后提到 95% 以上。但 TURN 贵啊,带宽成本翻倍。所以我的策略是:先试 P2P,不行再降级到 TURN。

通道类型 延迟 带宽成本 适用场景
P2P (直连) 低(10-50ms) 无服务器成本 内网、公网 IP、NAT 类型友好
TURN (中继) 中(50-150ms) 服务器带宽成本高 对称 NAT、防火墙限制、跨运营商
混合 (ICE 策略) 动态切换 可控 大部分生产环境

我的建议:做 RTSP 推流时,如果接收端是浏览器,大概率需要 TURN。因为浏览器在用户内网,RTSP 源在服务器端,两边 NAT 类型往往不匹配。提前部署好 TURN 服务器,别等上线了再补。

2.6 小结

WebRTC 核心架构其实不复杂:三层分离,信令和媒体各走各的路。Native 端更灵活,Web 端更便捷。你只要记住信令是「你的活」,媒体是「WebRTC 的活」,就不会搞混。