4. SDP协议解析:SDP结构剖析、Offer/Answer模型、媒体描述行详解
SDP,全称 Session Description Protocol。很多人以为它是个传输协议,其实不是。它就是个文本格式的描述文件,用来告诉对方:「我想跟你通信,我支持这些能力,你看着办。」
我刚开始接触WebRTC时,总觉得SDP又长又乱,一堆a=行、m=行看得头疼。后来做多了才发现,SDP的结构其实非常清晰。你只要抓住三个核心:会话层、媒体层、属性层,基本就能看懂任何SDP。
核心要点:SDP不是传输协议,是描述协议。它描述的是「我想怎么连」,而不是「我怎么连」。
4.1 SDP整体结构
一个标准的SDP,说白了就是一堆键值对。每行一个字段,字段名是单个字母,后面跟等号和值。我习惯把它分成三个层次:
- 会话层(Session Level):描述整个会话的全局信息,比如会话名称、发起者、时间等
- 媒体层(Media Level):描述每个媒体流的具体信息,比如音频、视频、数据通道
- 属性层(Attribute Level):附加的扩展属性,以a=开头,可以出现在会话层或媒体层
来看一个典型的WebRTC SDP片段:
v=0
o=- 1234567890 1234567890 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE 0 1
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104
c=IN IP4 0.0.0.0
a=mid:0
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98
c=IN IP4 0.0.0.0
a=mid:1
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=42e01f;packetization-mode=1
嗯,这里要注意:v=是协议版本,固定为0。o=是会话发起者信息,包含用户名、会话ID、版本号、网络类型和地址。s=是会话名称,WebRTC里通常用-占位。t=是会话有效时间,WebRTC里也是固定值。
4.2 Offer/Answer模型
Offer/Answer模型是WebRTC协商的核心机制。说白了就是:一方先出招(Offer),另一方接招(Answer)。
我遇到过不少新手问:「为什么不能两边同时发Offer?」理论上可以,但实际中会造成状态混乱。WebRTC规范明确规定了谁先谁后:
- Offer方:创建PeerConnection后,调用createOffer()生成SDP Offer
- Answer方:收到Offer后,调用createAnswer()生成SDP Answer
- 设置本地/远端描述:双方各自setLocalDescription和setRemoteDescription
这里有个关键点:Offer和Answer的SDP结构不完全相同。Offer可以包含多个候选地址和多种编码格式,Answer则要从中选择自己支持的。我曾经调试过一个Bug,Answer方把Offer里所有的H264 profile都返回了,结果导致协商失败——Answer只能选一个,不能全要。
个人经验:调试SDP协商问题时,我习惯先对比Offer和Answer的m=行。如果m=行数量对不上,或者rtpmap不匹配,基本就是协商失败的原因。
4.3 媒体描述行详解
媒体描述行,也就是m=行,是SDP里最重要的部分。它的格式是:
m=<媒体类型> <端口> <传输协议> <格式列表>
举个例子:
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104
拆开来看:
| 字段 | 值 | 说明 |
|---|---|---|
| 媒体类型 | audio | 可以是audio、video、application(数据通道) |
| 端口 | 9 | WebRTC里通常用9占位,实际端口在ICE候选里指定 |
| 传输协议 | UDP/TLS/RTP/SAVPF | WebRTC固定使用这个协议栈 |
| 格式列表 | 111 103 104 | RTP Payload Type编号,对应后面的rtpmap |
你想想看,为什么端口用9?因为WebRTC的传输层是ICE在管,真正的端口在ICE候选里。m=行里的端口就是个占位符,告诉对方「我这里有音频流」。
4.4 属性行详解
属性行以a=开头,是SDP里最灵活的部分。WebRTC大量使用属性行来扩展功能。常见的属性行包括:
a=rtpmap:111 opus/48000/2:定义RTP Payload Type对应的编码格式a=fmtp:111 minptime=10;useinbandfec=1:编码参数,比如Opus的最小打包时长a=mid:0:媒体流标识,用于BUNDLE分组a=sendrecv:媒体方向,还有sendonly、recvonly、inactivea=ice-ufrag和a=ice-pwd:ICE认证信息a=fingerprint:DTLS指纹,用于加密认证
我记得有一次排查一个音频卡顿问题,发现是a=fmtp里的minptime设置成了20ms,而对方只支持10ms。这种参数不匹配,不会导致协商失败,但会造成实际通信质量下降。所以,属性行的参数一定要仔细核对。
避坑指南:我曾经遇到过一个问题:Offer里声明了a=rtcp-mux,但Answer没有回应这个属性。结果RTCP和RTP走了不同端口,导致统计信息收不到。记住,a=rtcp-mux需要双方都支持才能生效。
4.5 SDP协商流程全景图
下面这张图展示了Offer/Answer模型的完整流程,以及SDP各层之间的关系:
4.6 实际调试技巧
做WebRTC开发,SDP调试是家常便饭。我分享几个实用技巧:
- 抓包看SDP:用Wireshark过滤
sip或http,找到SDP内容直接复制出来分析 - 对比工具:我习惯用Beyond Compare对比Offer和Answer的SDP,一眼就能看出差异
- 重点关注:m=行数量、rtpmap列表、ice-ufrag/ice-pwd、fingerprint,这四个地方出问题最多
小技巧:Chrome浏览器里打开chrome://webrtc-internals,可以看到实时的SDP Offer/Answer以及ICE候选信息。我排查问题时第一件事就是开这个页面。
好了,SDP协议的核心内容就这些。记住三个层次、一个模型、一行媒体描述,你就能看懂任何WebRTC的SDP了。实际项目中遇到SDP问题,先看m=行,再看a=行,最后看会话层,基本都能定位到原因。