17、Plan B 与 Unified Plan:两种 SDP 协商方案的区别、Unified Plan 的 m= 行结构、浏览器兼容性
聊到 WebRTC 的媒体协商,有个绕不开的话题——Plan B 和 Unified Plan。
说实话,我刚入行那会儿也被这两个东西搞晕过。明明都是 SDP,为什么还要分两种?后来在项目里踩了坑,才真正搞明白它们的区别。
今天我就把这块内容掰开揉碎,跟你讲清楚。
为什么会有两种方案?
WebRTC 刚出来的时候,Chrome 和 Firefox 对多轨流的处理方式不一样。Chrome 搞了一套自己的方案,叫 Plan B。Firefox 觉得这不符合标准,就推了 Unified Plan。
后来标准组织也站队了——Unified Plan 成了 RFC 8843 的正式标准。Plan B 呢?嗯,它已经被标记为废弃了。
但现实是,很多老项目还在用 Plan B。你想想看,一个跑了五六年的视频会议系统,说改就改?没那么简单。
Plan B 的问题在哪?
我举个例子你就明白了。假设你要发送两路视频(比如主摄像头和屏幕共享),Plan B 的 SDP 长这样:
m=video 9 UDP/TLS/RTP/SAVPF 100 101
a=ssrc:12345 cname:user1
a=ssrc:12346 cname:user1
a=ssrc-group:SIM 12345 12346
看到了吗?一个 m=video 行,里面塞了两个 SSRC。这在简单场景下没问题,但一旦涉及 Simulcast 或 SVC,问题就来了——你没法清晰区分哪组 SSRC 属于哪路流。
我在项目中遇到过一个问题:用 Plan B 做屏幕共享时,接收端经常把主视频和共享流的包搞混。排查了半天,最后发现是 SSRC 分组逻辑有歧义。
Unified Plan 怎么解决?
Unified Plan 的思路很直接——一路流,一个 m= 行。还是上面那个例子,Unified Plan 的 SDP 是这样的:
m=video 9 UDP/TLS/RTP/SAVPF 100 101
a=msid:stream1 track1
a=ssrc:12345 cname:user1
m=video 9 UDP/TLS/RTP/SAVPF 100 101
a=msid:stream2 track2
a=ssrc:67890 cname:user1
每个 m= 行都带自己的 msid 和 SSRC。接收端一看就知道:哦,这是 stream1 的 track1,那是 stream2 的 track2。清清楚楚,不会串。
Unified Plan 的 m= 行结构详解
咱们来拆一个实际的 Unified Plan m= 行,看看里面都有什么:
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100
c=IN IP4 0.0.0.0
a=mid:0
a=sendrecv
a=msid:stream1 track1
a=ssrc:12345 cname:user1
a=ssrc:12345 msid:stream1 track1
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42e01f
a=rtcp-fb:96 nack
a=rtcp-fb:96 goog-remb
关键字段我解释一下:
- m=video:媒体类型,可以是 audio 或 video
- mid:0:这个 m= 行的唯一标识,用于 bundle 分组
- msid:关联的 MediaStream 和 MediaStreamTrack
- ssrc:同步源标识符,每个 track 一个
- rtpmap:编码格式映射
- fmtp:编码参数,比如 H264 的打包模式
注意看,每个 m= 行都独立指定了编码参数。这意味着你可以让第一路视频用 H264,第二路用 VP8——这在 Plan B 里很难做到。
浏览器兼容性现状
这是大家最关心的问题。我直接给你一张表:
| 浏览器 | Plan B | Unified Plan | 备注 |
|---|---|---|---|
| Chrome 72+ | 废弃 | 默认 | 可通过 flag 回退 |
| Firefox 59+ | 不支持 | 默认 | 一直只用 Unified Plan |
| Safari 12.1+ | 不支持 | 默认 | 跟随标准 |
| Edge (Chromium) | 废弃 | 默认 | 和 Chrome 行为一致 |
如何检测当前用的是哪种方案?
很简单,看 SDP 里 m= 行的数量。如果音频和视频各只有一个 m= 行,大概率是 Plan B。如果有多条 m=video 行,那就是 Unified Plan。
代码里也可以判断:
// 检测是否为 Unified Plan
function isUnifiedPlan(sdp) {
const videoLines = sdp.match(/^m=video/gm);
return videoLines && videoLines.length > 1;
}
嗯,这个方法比较粗暴,但够用。
迁移到 Unified Plan 的避坑指南
我曾经帮一个客户做迁移,踩了几个坑,分享给你:
- 坑一: 旧版浏览器用户。如果你的用户群体里还有 Chrome 70 以下的,得做兼容处理。我当时的方案是检测浏览器版本,动态切换 SDP 生成方式。
- 坑二: Simulcast 配置。Unified Plan 下 Simulcast 的 SDP 写法跟 Plan B 完全不同。别直接复制旧代码,会出问题。
- 坑三: 服务器端转发的兼容性。如果你用 MCU 或 SFU,确保它们支持 Unified Plan。我遇到过 SFU 只解析第一个 m=video 行的情况,后面的流全丢了。
核心逻辑示意图
下面这张图展示了 Plan B 和 Unified Plan 在 SDP 结构上的核心差异:
从图上可以直观看到:左边 Plan B 把所有流塞进一个 m= 行,右边 Unified Plan 每个流都有自己的独立空间。这就是两者最本质的区别。
好了,关于 Plan B 和 Unified Plan 的核心区别就讲到这里。记住一句话:Unified Plan 是现在和未来,Plan B 是过去。做新项目别犹豫,直接上 Unified Plan。