揭开 WebRTC 的“哑巴”翻译官:SDP 协议全解析
如果你曾经在这个领域摸爬滚打过一段时间,我相信你一定经历过这样一个时刻:当你打开浏览器的 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 连接建立,媒体流开始传输 ]
关键步骤解析
-
Offer (提议):A 生成一份 SDP,把自己支持的所有“技能”(编解码、加密方式)和“地址”都写进去,发给 B。
-
Answer (应答):B 收到后,拿着 A 的“技能表”跟自己的对比。
-
A 支持 H.264 和 VP8,B 只支持 H.264 -> 结果:双方决定用 H.264。
-
B 生成一份新的 SDP(Answer),只包含协商一致的参数,发回给 A。
-
-
达成一致: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=,答案往往就藏在其中。
更多推荐
所有评论(0)