WebRTC 源码精度:CreateOffer之后应该应该调用那个API
问题一:
CreateOffer 生成之后 应该是先调用SetLocalDescription 还是先通过信令通道把Offer送给远端?
正确顺序:先 SetLocalDescription,再通过信令发送 Offer。
CreateOffer() ↓SetLocalDescription(offer) ← 第一步 ↓通过信令发送 SDP Offer 给对端 ← 第二步
原因很简单:只有 SetLocalDescription 之后,MaybeStartGathering 才会启动 ICE 候选收集,OnIceCandidate 回调才会开始触发。如果先发 Offer 再 SetLocalDescription,可能会造成时序混乱,下面就详细展开。
先发 Offer 再 SetLocalDescription 会怎样?
正常时序(正确做法)
发起方 接收方 │ ├─ CreateOffer() ├─ SetLocalDescription(offer) ← ICE 收集启动 ├─ 发送 SDP Offer ───────────────→ SetRemoteDescription(offer) ├─ OnIceCandidate() 触发 CreateAnswer() ├─ 发送 Candidate ───────────────→ SetLocalDescription(answer) │ 发送 Answer + Candidates | AddIceCandidate() ├─ SetRemoteDescription(answer) ├─ AddIceCandidate() │ └─ ICE 建连 ✅
异常时序(先发 Offer 再 SetLocalDescription)
发起方 接收方 │ ├─ CreateOffer() ├─ 发送 SDP Offer ───────────────→ SetRemoteDescription(offer) │ CreateAnswer() │ SetLocalDescription(answer) ← 对端 ICE 收集启动 │ 发送 Answer │ OnIceCandidate() 触发 │ 发送 Candidate ────────────→ 发起方收到候选 │ ├─ SetLocalDescription(offer) ← 这时才启动 ICE 收集 │ (此时对端的 Answer 和 Candidate 可能已经到了)
具体会出现什么问题?
一:发起方收到 Answer 时本地还没有 SetLocalDescription
对端很快完成协商,发回 Answer, 发起方此时调用 SetRemoteDescription(answer) 会直接失败:
SignalingState = Stable(还没有 SetLocalDescription(offer))SetRemoteDescription(answer) 要求状态必须是 kHaveLocalOffer→ 报错:INVALID_STATE
二:ICE 收集延迟,建连变慢
正常情况下,发送 Offer 和 ICE 收集是并行的:
正确做法: SetLocalDescription ──→ ICE 收集开始 同时发送 Offer ──────→ 对端处理 Offer 两件事并行,节省时间 ✅错误做法: 发送 Offer ──────────→ 对端处理 Offer(对端 ICE 已在收集) 等对端回来 Answer... 才 SetLocalDescription → ICE 收集才开始 两件事串行,白白浪费时间 ❌
一张图说清楚
正确做法(并行): t=0 SetLocalDescription → ICE 收集启动 t=0 发 Offer t=10 收到 Answer → SetRemoteDescription t=15 ICE 连通 ✅(ICE 收集和信令并行,总耗时短)错误做法(串行): t=0 发 Offer t=10 收到 Answer → SetRemoteDescription 失败(状态不对) 或者: t=10 才 SetLocalDescription → ICE 收集才启动 t=20 ICE 连通 ❌(多等了 10ms,实际场景可能更长)
一句话总结
先发 Offer 再 SetLocalDescription 轻则建连变慢(ICE 收集被推迟), 重则信令状态错乱(SetRemoteDescription 因状态不对直接报错), 是一个典型的时序 bug,在网络快的环境下偶发,网络快时必现。
问题二:
如果信令乱序 对端还是先收到 ICE Candidate 再收到 Offer 会怎样?
第一步:先收到 Candidate,调用 SdpOfferAnswerHandler::AddIceCandidateInternal
if (!remote_description()) { RTC_LOG(LS_ERROR) << "AddIceCandidate: ICE candidates can't be added ""without any remote session description.";return kAddIceCandidateFailNoRemoteDescription; }
kAddIceCandidateFailNoRemoteDescription 不在成功列表里, 所以这次调用返回 false,候选直接丢弃,不会暂存。
bool SdpOfferAnswerHandler::AddIceCandidate( const IceCandidateInterface* ice_candidate) { const AddIceCandidateResult result = AddIceCandidateInternal(ice_candidate); NoteAddIceCandidateResult(result); // If the return value is kAddIceCandidateFailNotReady, the candidate has been // added, although not 'ready', but that's a success. return result == kAddIceCandidateSuccess || result == kAddIceCandidateFailNotReady;}
第二步:之后收到 Offer,调用 SetRemoteDescription
Offer SDP 本身可能内嵌了候选(非 Trickle ICE 场景), ApplyRemoteDescription 末尾会通过 UseCandidatesInSessionDescription 应用这些候选。 但之前单独通过 AddIceCandidate 发来的候选,已经丢了,不会自动补回来。
正确理解:Trickle ICE 下的正确做法 Trickle ICE 场景下,候选和 Offer 分开发送, 应用层自己需要保证顺序,或者缓存候选等 Offer 到来后再调用 AddIceCandidate:
接收方正确处理逻辑(应用层负责):收到 Candidate → 检查:是否已有 remote description? → 没有 → 应用层自己缓存候选 → 等收到 Offer,SetRemoteDescription 成功后 → 再把缓存的候选逐一调用 AddIceCandidate ✅
完整总结
|
|
|
|
|---|---|---|
|
|
AddIceCandidate
kAddIceCandidateFailNoRemoteDescription |
|
SetRemoteDescription 成功 |
UseCandidatesInSessionDescription) |
|
|
|
|
AddIceCandidate ✅ |
|
|
SetRemoteDescription
|
|
夜雨聆风
