如果你曾经在这个领域摸爬滚打过一段时间,我相信你一定经历过这样一个时刻:当你打开浏览器的 chrome://webrtc-internals,或者查看移动端日志时,会被那一长串不知所云的文本吓一跳——

v=0
o=- 4862282362080646624 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 9 0 8 106 105 13 110 112 113 126
...

这就是 SDP (Session Description Protocol)。

很多新手(包括当年的我)往往觉得它枯燥乏味,甚至认为“反正 WebRTC API 会自动生成它,我不用管”。但事实是,SDP 是 WebRTC 协商的核心。看不懂 SDP,你就在处理连接失败、编解码不兼容、音视频流错乱等问题时是个“瞎子”。

今天,我就结合个人的实战经验,带大家拆解一下这个看似高冷的协议。


一、 SDP 到底是什么?

首先要澄清一个误区:SDP 不传输媒体数据,它甚至不是一个传输协议。

你可以把它想象成一张名片或者一份简历。

当两个客户端(Peer)想要建立视频通话时,它们必须先互相“摸底”:

  • “你支持什么视频格式?是 H.264 还是 VP8?”

  • “你的网络地址是啥?端口多少?”

  • “你是要加密传输吗?”

SDP 就是承载这些信息的载体。在 WebRTC 的世界里,SDP 负责描述多媒体会话的属性。


二、 解剖一只“麻雀”:SDP 结构详解

SDP 的格式非常古老(源于 SIP 时代),遵循 <type>=<value> 的文本格式。虽然看着乱,但逻辑很清晰。我们可以把它分为两大部分:会话级描述(Session Level) 和 媒体级描述(Media Level)。

1. 核心字段速查

咱们不搞教科书式的背诵,我只列出你在 debug 时最需要关注的几个字段:

  • v= (Version): 协议版本,目前永远是 0。

  • o= (Origin): 发起者的身份信息。

    • 老鸟经验:注意这一行里的版本号(通常是第二个数字)。如果在一次通话中 SDP 发生了更新(Renegotiation),这个版本号必须增加,否则对方可能会忽略新的 SDP。

  • c= (Connection): 连接信息。

    • WebRTC 特例:在传统的 SIP/SDP 中,这里填 IP 地址。但在 WebRTC 中,由于我们要穿越 NAT,真实的连接地址通常藏在后面的 ICE Candidate 里,所以这里经常看到 0.0.0.0 或者内网 IP,不要被它迷惑了。

  • t= (Time): 会话活动时间。WebRTC 这种实时通信通常填 0 0,表示一直活动。

2. 重头戏:媒体行 (m=) 与 属性行 (a=)

这才是 SDP 的灵魂所在。

m= (Media Line)

格式:m=<媒体类型> <端口> <传输协议> <格式列表>
例如:m=video 9 UDP/TLS/RTP/SAVPF 96 98 100

这句话翻译过来就是:“我要传视频,端口写着 9(其实 WebRTC 里端口通常由 ICE 决定,这里经常乱填),协议是 UDP/TLS/RTP/SAVPF(这串加密协议全家桶),我支持的编码格式 ID 是 96、98、100。”

a= (Attribute Line)

这是对 m= 的详细补充。刚才说的 96、98 到底是啥?全靠 a= 来解释。

  • a=rtpmap:96 VP8/90000 -> 解释:ID 96 代表 VP8 编码,时钟频率 90k。

  • a=fmtp:96 ... -> 解释:VP8 的具体参数配置。

  • a=sendrecv / a=recvonly -> 解释:你是想双向通话,还是只看不说?

  • a=candidate:... -> 重点! 这就是 ICE 候选者,包含了打洞需要的 IP 和端口信息(如果是 Trickle ICE 模式,这里可能不包含 candidate,而是单独发送)。


三、 经典的 Offer/Answer 机制

SDP 本身是静态的文本,要让它动起来,需要配合 Offer/Answer 模型。这就像是一场商务谈判。

流程图解(ASCII 版)

假设 A(发起端) 想给 B(接收端) 打电话:

      User A (Offerer)                        User B (Answerer)
           |                                         |
           |   1. createOffer()                      |
           |   2. setLocalDescription(offer)         |
           |                                         |
           |   3. Signal: 发送 Offer (SDP A) --------->
           |                                         |
           |                                         | 4. setRemoteDescription(offer)
           |                                         | 5. createAnswer()
           |                                         | 6. setLocalDescription(answer)
           |                                         |
           <--------- Signal: 发送 Answer (SDP B)    |
           |                                         |
           |   7. setRemoteDescription(answer)       |
           |                                         |
     [ P2P 连接建立,媒体流开始传输 ]

关键步骤解析

  1. Offer (提议):A 生成一份 SDP,把自己支持的所有“技能”(编解码、加密方式)和“地址”都写进去,发给 B。

  2. Answer (应答):B 收到后,拿着 A 的“技能表”跟自己的对比。

    • A 支持 H.264 和 VP8,B 只支持 H.264 -> 结果:双方决定用 H.264。

    • B 生成一份新的 SDP(Answer),只包含协商一致的参数,发回给 A。

  3. 达成一致:A 收到 Answer,握手成功。


四、 实战中的“坑”与“建议”

作为一名在这里踩过无数坑的工程师,我有几点掏心窝子的建议:

1. 著名的 "SDP Munging"(手动篡改 SDP)

以前,因为浏览器 API 不完善,我们经常需要用正则表达式去暴力修改 SDP 文本(比如强制修改比特率、强制通过 m= 行的顺序来调整 Codec 优先级)。
现在建议:尽量使用标准的 WebRTC API(如 RTCRtpTransceiver 或 setParameters)来调整参数。直接改 SDP 文本不仅脆弱,而且容易随着浏览器更新而失效。

2. Unified Plan vs Plan B

这是 WebRTC 历史遗留问题。早期的 Chrome 使用 Plan B 格式来处理多流(Multi-stream),而标准是 Unified Plan。
现状:现在所有主流浏览器都已经默认 Unified Plan。如果你还在维护几年前的老代码,一定要检查一下 SDP 格式是否兼容。Unified Plan 的特点是每一个媒体流(Track)都有一个独立的 m= 部分。

3. 排查问题的思路

当你发现视频黑屏或者连接失败时,先抓取 SDP:

  • 检查 a=candidate 是否存在?(没有 candidate 说明打洞没开始或彻底失败)

  • 检查 a=fingerprint?(DTLS 握手失败常源于此)

  • 检查 Codec 匹配?(搜索 rtpmap,看双方是否有共同的编码 ID)


总结

SDP 就像是 WebRTC 的说明书。虽然它看起来只是一堆杂乱的字符,但它诚实地记录了连接双方的每一次协商细节。

掌握 SDP 的阅读能力,你就拥有了透视 WebRTC 运行状态的“X 光眼”。下次再遇到连接问题,不妨深呼吸,耐心地读一读这几行 m= 和 a=,答案往往就藏在其中。

Logo

邀请您加入社区

更多推荐