WebRTC 核心概念详解

本文系统梳理 WebRTC 的五大核心概念:信令、SDP、ICE/STUN/TURN、DTLS+SRTP、GCC 拥塞控制,并深入讲解 NAT 穿透原理和 GCC 算法细节。


缩写全称速查

缩写 全称 中文
SDP Session Description Protocol 会话描述协议
ICE Interactive Connectivity Establishment 交互式连接建立
STUN Session Traversal Utilities for NAT NAT 会话穿越工具
TURN Traversal Using Relays around NAT 通过中继穿越 NAT
DTLS Datagram Transport Layer Security 数据报传输层安全协议
SRTP Secure Real-time Transport Protocol 安全实时传输协议
RTP Real-time Transport Protocol 实时传输协议
RTCP RTP Control Protocol RTP 控制协议
GCC Google Congestion Control 谷歌拥塞控制算法
NAT Network Address Translation 网络地址转换
TLS Transport Layer Security 传输层安全协议
UDP User Datagram Protocol 用户数据报协议
TCP Transmission Control Protocol 传输控制协议
NACK Negative Acknowledgement 否定确认(丢包重传请求)
FEC Forward Error Correction 前向纠错
REMB Receiver Estimated Maximum Bitrate 接收端估计最大码率
BWE Bandwidth Estimation 带宽估计

一、信令(Signaling)

一句话:WebRTC 两端想通话,但双方都不知道对方在哪、能用什么格式——信令就是用来交换这些「元信息」的通道。

比喻:你要和朋友打电话,但没有存他号码。你们先互发微信消息「我的号码是 xxx,你的是多少?」——这个过程就是信令。

关键点

  • WebRTC 不规定信令用什么协议,随便你用 WebSocket、HTTP、甚至短信
  • 信令只传两类内容:SDP(我能做什么)和 ICE Candidate(我在哪)
  • 信令服务器本身不碰媒体流,只是个「中间人」转发消息
1
2
3
4
5
A ──── Offer SDP ────→ 信令服务器 ──→ B
A ←─── Answer SDP ─── 信令服务器 ←── B
A ──── ICE Candidate → 信令服务器 ──→ B
A ←─── ICE Candidate ─ 信令服务器 ←── B
// 信令结束后,A 和 B 直连,信令服务器退出历史舞台

信令与 SDP 的关系

一句话:SDP 是信令传递的内容,信令是传递 SDP 的通道。

  • 信令 = 快递公司(负责把东西送到)
  • SDP = 快递里装的其中一封信(具体内容)

信令负责传递两类东西:

1
2
① SDP(Offer / Answer)—— 「我能做什么」(编解码器、分辨率等能力)
② ICE Candidate —— 「我在哪」(网络地址)

SDP 是其中一类,不是全部。两类都传完,连接才能建立。

完整时序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
A                   信令服务器               B

createOffer()
生成 SDP Offer

├── 通过信令发送 SDP Offer ──────────────→ │
│ setRemoteDescription(offer)
│ createAnswer()
│ 生成 SDP Answer
│ ←── 通过信令发送 SDP Answer ─────────── │
setRemoteDescription(answer)

├── 通过信令发送 ICE Candidate ──────────→ │
│ ←── 通过信令发送 ICE Candidate ───────── │

└── 信令使命完成,A 和 B 开始直连

二、SDP(Session Description Protocol)

一句话:我把自己的「能力清单」写给你看——我支持哪些编解码器、用什么分辨率、监听哪个端口。

比喻:相亲前双方交换简历,写清楚「我身高 180、会做饭、有房」,对方看完说「OK 我接受,我的情况是 xxx」。

真实的 SDP 长这样(截取片段):

1
2
3
4
5
6
7
8
9
10
11
12
v=0
o=- 1234567890 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104
a=rtpmap:111 opus/48000/2 ← 支持 Opus 编码,48kHz,双声道
a=rtpmap:103 ISAC/16000
m=video 9 UDP/TLS/RTP/SAVPF 96 97
a=rtpmap:96 VP8/90000 ← 支持 VP8 视频编码
a=rtpmap:97 H264/90000 ← 也支持 H264
a=fmtp:97 profile-level-id=42e01f ← H264 具体 Profile

Offer / Answer 模型

1
2
3
A 发 Offer:「我支持 VP8 和 H264,你选一个」
B 回 Answer:「好,我们用 H264」
// 之后双方都只用 H264 通信

三、ICE / STUN / TURN

核心问题:互联网上大多数设备在 NAT 后面(路由器后),没有公网 IP,两端怎么直连?

3.1 STUN 和 TURN 的本质区别

  • STUN:照镜子,告诉你自己的公网地址是什么,不转发流量
  • TURN:快递柜,P2P 打不通时帮你中转所有流量
STUN TURN
作用 告诉你「你家门牌号是 xxx」 帮你把信收下,再转交给朋友
流量过服务器吗 不过,只是查询 所有流量都过
费用 极低(只有查询请求) 高(要承载所有音视频流量)
使用时机 每次连接都用 仅 P2P 打不通时兜底

3.2 STUN——「你的公网地址是什么」

1
2
3
你(192.168.1.100:12345)
──→ STUN Server(公网)
← 「你的公网地址是 1.2.3.4:5678」

3.3 ICE——「尝试所有路径,找最优的一条」

类型 是什么 优先级
host 本机局域网 IP 最高(同局域网直连)
srflx 通过 STUN 拿到的公网 IP 中(NAT 穿透)
relay TURN 服务器中继地址 最低(兜底)
1
2
3
① 先试 host candidate(局域网直连)→ 最快
② 打不通 → 试 srflx candidate(P2P 穿透)
③ 还打不通 → 用 relay candidate(TURN 中继)→ 兜底,必通

3.4 TURN——「P2P 打不通时的中继」

走 TURN 就是走公网服务器代理所有媒体流

1
2
P2P 直连:手机A ──────────────────────→ 手机B
走 TURN: 手机A ──→ TURN服务器(公网)──→ 手机B

影响:

  • 延迟增加 20-100ms
  • 带宽成本高(服务器承载完整媒体流)
  • 内容安全:SRTP 加密,TURN 只转发密文

生产环境策略: 能直连绝不走 TURN;TURN 使用率超 30% 说明打洞成功率偏低需排查。

代码里只需配置地址,其余全自动:

1
2
3
4
5
6
7
8
9
val iceServers = listOf(
PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer(),
PeerConnection.IceServer.builder("turn:your-turn-server.com:3478")
.setUsername("user")
.setPassword("password")
.createIceServer()
)
val config = PeerConnection.RTCConfiguration(iceServers)
val peerConnection = factory.createPeerConnection(config, observer)

四、NAT 深挖

4.1 NAT 是什么

IPv4 地址只有 43 亿个,NAT 让一个公网 IP 后面可以藏几百台设备:

1
2
3
你的手机(192.168.1.100:12345)
↓ 路由器做 NAT
公网(1.2.3.4:54321)

4.2 NAT 四种类型

① 完全锥型(Full Cone):任何人都能主动发包进来,穿透最容易 ★☆☆☆

② 地址限制锥型(Address Restricted):只有 A 发过包的目标 IP 才能回包 ★★☆☆

③ 端口限制锥型(Port Restricted):只有 A 发过包的目标 IP+Port 才能回包 ★★★☆

④ 对称型(Symmetric):每次发往不同目标,分配不同公网端口,最难穿透 ★★★★

4.3 P2P 打洞原理

两端同时向对方发包,让路由器 NAT 表留下记录:

1
2
3
② A 向 B 的公网地址发包 → NAT-A 留记录(允许 B 回包)→ NAT-B 无记录,丢弃
③ B 同时向 A 的公网地址发包 → NAT-B 留记录 → NAT-A 已有记录,放行!
④ 双方互通 ✅

关键:两端必须同时发包,信令服务器负责协调。

4.4 对称型 NAT 打不通的原因

1
2
3
A 通过 STUN 拿到端口 5678(只对 STUN 服务器有效)
A 向 B 发包 → NAT-A 分配新端口 5679(换了!)
B 拿着 5678 去敲门 → 找不到 → 永远打不通

现实中 P2P 打洞成功率约 70-80%,剩下 20-30% 靠 TURN 兜底。


五、DTLS + SRTP

WebRTC 所有媒体流强制加密,不能关。

协议 作用
密钥交换 DTLS 基于 UDP 的 TLS,握手交换加密密钥
媒体加密 SRTP 用 DTLS 协商的密钥加密每一帧音视频
1
2
3
1. ICE 建立 UDP 连接
2. DTLS 握手(交换证书 + 密钥)
3. 之后所有 RTP 包加密传输 → SRTP

用 DTLS 不用 TLS 的原因:RTP 跑在 UDP 上,TLS 依赖 TCP,DTLS 是能在 UDP 上工作的版本。


六、GCC 拥塞控制深挖

两个并行估计器,取最小值:

1
2
3
              ┌── 基于延迟的估计器(Trendline Filter)──┐
输入:网络包 → → min() → 目标码率
└── 基于丢包的估计器(Loss-based)─────────┘

6.1 延迟梯度估计器(Trendline Filter)

延迟梯度 δ = 到达间隔 - 发送间隔

1
2
δ > 0 → 队列积压(拥塞)
δ < 0 → 队列消散(网络变好)

用线性回归拟合趋势,过滤抖动噪声,状态机判断:

1
NORMAL → OVERUSE(持续上升)→ UNDERUSE(下降)→ NORMAL

AIMD 码率调整:

1
2
UNDERUSE → 每秒 +8%(慢慢探测上限)
OVERUSE → ×0.85,立刻降 15%(快速响应)

6.2 丢包率估计器(Loss-based)

1
2
3
丢包率 < 2%   → 码率 ×1.08
丢包率 2~10% → 保持
丢包率 > 10% → 码率 × (1 - 0.5×丢包率)

6.3 为什么两个都需要

延迟梯度 丢包率
响应速度 快(队列刚积压就感知) 慢(等包真的丢了)
准确性 受抖动影响,有误判 准确

取最小值 = 保守策略,哪个说降就降。

6.4 完整闭环

1
2
3
发送端 ── RTP包(带时间戳)──────────────→ 接收端
←─ RTCP Transport-CC Feedback ─── (每包反馈到达时间)
发送端本地运行 Trendline Filter → 计算目标码率 → 通知编码器

现代 WebRTC 用 Transport-CC(发送端自己算延迟梯度),比老的 REMB 更精准。


七、串起来看整个流程

1
2
3
4
5
6
7
8
1. A 和 B 各自通过 STUN 拿到自己的公网地址
2. A 创建 Offer SDP 发给信令服务器 → 转给 B
3. B 创建 Answer SDP 发回给 A
4. 双方交换 ICE Candidate,开始打洞
5. ICE 找到最优路径(优先直连,打不通走 TURN)
6. DTLS 握手,协商加密密钥
7. 开始传 SRTP 加密的音视频流
8. GCC 持续监测网络,动态调整发送码率

最后更新:2026-06