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规范明确规定了谁先谁后:

  1. Offer方:创建PeerConnection后,调用createOffer()生成SDP Offer
  2. Answer方:收到Offer后,调用createAnswer()生成SDP Answer
  3. 设置本地/远端描述:双方各自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、inactive
  • a=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各层之间的关系:

SDP Offer/Answer 协商流程 SDP Offer/Answer 协商流程 Offer方 createOffer() → 生成SDP Offer Answer方 收到Offer → createAnswer() SDP Offer SDP 结构 会话层:v=, o=, s=, t=, a=group:BUNDLE 媒体层 - 音频:m=audio ... 媒体层 - 视频:m=video ... 属性层:a=rtpmap, a=fmtp, a=ice-ufrag, a=fingerprint SDP Answer 协商完成 双方setLocal/RemoteDescription

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=行,最后看会话层,基本都能定位到原因。