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 把多个流塞进同一个 m= 行,Unified Plan 每个流独占一个 m= 行。

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。这在简单场景下没问题,但一旦涉及 SimulcastSVC,问题就来了——你没法清晰区分哪组 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。别给自己挖坑。如果你还在维护老项目,尽早规划迁移。

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 行为一致
注意: 如果你用旧版 Chrome(72 以下),默认还是 Plan B。我曾经在集成一个第三方 SDK 时,对方用的 Chrome 70,结果两边 SDP 格式不匹配,协商直接失败。排查了整整一天。

如何检测当前用的是哪种方案?

很简单,看 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 行的情况,后面的流全丢了。
总结一下: Unified Plan 是标准,Plan B 是历史遗留。新项目别犹豫,直接用 Unified Plan。老项目尽早规划迁移,但要做好兼容测试。

核心逻辑示意图

下面这张图展示了 Plan B 和 Unified Plan 在 SDP 结构上的核心差异:

Plan B vs Unified Plan SDP 结构对比 Plan B(已废弃) m=video 9 UDP/TLS/RTP/SAVPF 100 101 a=ssrc:12345 cname:user1 ← 主摄像头 a=ssrc:67890 cname:user1 ← 屏幕共享 ⚠ 多个 SSRC 挤在一个 m= 行 ⚠ 无法清晰区分流归属 ⚠ Simulcast/SVC 支持差 VS Unified Plan(标准) m=video 9 UDP/TLS/RTP/SAVPF 100 101 a=msid:stream1 track1 ← 主摄像头 m=video 9 UDP/TLS/RTP/SAVPF 100 101 a=msid:stream2 track2 ← 屏幕共享 ✅ 每个流独立 m= 行 ✅ 清晰区分流归属

从图上可以直观看到:左边 Plan B 把所有流塞进一个 m= 行,右边 Unified Plan 每个流都有自己的独立空间。这就是两者最本质的区别。


好了,关于 Plan B 和 Unified Plan 的核心区别就讲到这里。记住一句话:Unified Plan 是现在和未来,Plan B 是过去。做新项目别犹豫,直接上 Unified Plan。