11、SDP协议:SDP是什么、SDP结构解析(媒体描述、编解码信息)、本地SDP与远程SDP、SDP协商过程。
聊到WebRTC,绕不开的一个东西就是SDP。我记得刚接触WebRTC那会儿,看到控制台打印出来的一大串文本,第一反应是——这什么玩意儿?密密麻麻的,像乱码又不像乱码。后来硬着头皮啃了一遍RFC,才明白这东西其实就是个“谈判桌”。
说白了,SDP(Session Description Protocol)不是用来传输媒体数据的,它是用来描述“这场会议该怎么开”的。你用什么编解码器?你的IP和端口是多少?你要发几路流?这些信息都写在SDP里。没有它,两个浏览器之间根本没法建立音视频通话。
SDP长什么样?
我们先看一个真实的SDP片段。这是我从一次实际通话中截取出来的,去掉了敏感信息:
v=0
o=- 1234567890 2 IN IP4 192.168.1.100
s=-
t=0 0
m=audio 49170 RTP/AVP 111 103 104
c=IN IP4 192.168.1.100
a=rtpmap:111 opus/48000/2
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=sendrecv
m=video 49172 RTP/AVP 96 97 98
c=IN IP4 192.168.1.100
a=rtpmap:96 H264/90000
a=rtpmap:97 VP8/90000
a=rtpmap:98 VP9/90000
a=sendrecv
嗯,看着还是有点乱对吧?别急,我们拆开来看。
SDP的结构:一行一个属性
SDP的格式非常简洁,每一行都是一个键值对,用等号连接。我习惯把它分成三块来看:
- 会话层(Session Level):描述整个通话的全局信息,比如版本号、会话ID、时间戳。
- 媒体层(Media Level):描述每一路媒体流,比如音频、视频各自用什么端口、什么编解码器。
- 属性行(Attribute Lines):以
a=开头,补充说明媒体流的行为,比如是发送还是接收。
我们逐行看一下上面那个例子:
| 行 | 含义 |
|---|---|
v=0 |
SDP版本号,目前就是0,没变过。 |
o=- 1234567890 2 IN IP4 192.168.1.100 |
会话发起者信息。用户名、会话ID、版本号、网络类型、地址。 |
s=- |
会话名称,没填就用短横线占位。 |
t=0 0 |
会话有效时间,0 0表示不限制。 |
m=audio 49170 RTP/AVP 111 103 104 |
媒体行。音频流,端口49170,使用RTP协议,支持三种编解码器(编号111、103、104)。 |
c=IN IP4 192.168.1.100 |
连接信息。IP地址和网络类型。 |
a=rtpmap:111 opus/48000/2 |
属性行。把编号111映射为Opus编解码器,采样率48000,双声道。 |
a=sendrecv |
表示这条媒体流既能发送也能接收。 |
m= 行,它告诉你总共有几路流。然后看 a=rtpmap,就知道对方支持哪些编解码器。其他行大多是辅助信息。
本地SDP vs 远程SDP
这个其实很好理解。你想想看,在WebRTC里,每个端都要生成一份SDP来描述自己。你生成的叫“本地SDP”,对方生成的叫“远程SDP”。
举个例子:
- 你的浏览器调用
createOffer(),得到的就是你的本地SDP。 - 你通过信令服务器把它发给对方,对方收到后,调用
setRemoteDescription(),这份SDP就成了对方的远程SDP。 - 反过来,对方生成Answer,你收到后设置成你的远程SDP。
我在项目中遇到过一个问题:有个同事把本地SDP和远程SDP搞反了,结果音频一直没声音。排查了半天,发现他把自己的SDP设成了远程的,等于自己跟自己握手——当然没声音了。
SDP协商过程:Offer/Answer模型
WebRTC的SDP协商遵循一个非常经典的模型——Offer/Answer。说白了就是:
- 发起方生成一个Offer(包含自己的媒体能力)。
- 接收方收到Offer后,生成一个Answer(包含自己的媒体能力,同时回应Offer中的提议)。
- 双方各自设置对方的SDP,完成协商。
这个过程看起来简单,但里面有个关键点:编解码器匹配。
Offer里会列出所有支持的编解码器,按优先级排序。Answer里会从这些编解码器里选一个它自己也支持的。如果双方没有交集,那这路流就建不起来。
我曾经调试过一个VP9的问题。Offer里写了VP8、VP9、H264,但Answer只回了VP8。我一开始以为是对方不支持VP9,后来发现是Answer的SDP里把VP9的payload type写错了。嗯,这种低级错误排查起来最费时间。
用SVG看SDP协商流程
下面这张图是我自己画的,把Offer/Answer的流程走了一遍。你可以对照着看:
实际开发中要注意什么?
我做了几年WebRTC开发,踩过不少SDP的坑。这里列几个最常见的:
- ICE候选信息没带全:SDP里除了媒体描述,还会携带ICE候选信息。如果候选信息不全,P2P连接可能建不起来。我遇到过只发了host候选,没发srflx候选的情况,结果内网能通,外网就不行。
- 编解码器顺序很重要:Offer里排第一的编解码器,是发起方最想要的。Answer通常会选第一个它支持的。所以如果你想让对方用H264,就把H264排在前面。
- 不要手动修改SDP:除非你非常清楚自己在做什么。我见过有人为了“优化”SDP,把一些属性行删了,结果通话直接挂掉。SDP的每一行都有它的作用。
chrome://webrtc-internals 页面里,可以实时看到SDP的Offer和Answer。我每次排查问题,第一件事就是打开这个页面,把SDP复制出来对比一下。
总结一下
SDP在WebRTC里扮演的角色,就是一份“会议议程”。它告诉对方:我在这里,我用这些编解码器,我能收也能发。双方通过Offer/Answer模型交换SDP,最终达成一致,媒体流才能建立起来。
理解SDP,是理解WebRTC信令流程的关键一步。你不需要背下每一行的含义,但至少要能看懂SDP里哪些是媒体描述、哪些是编解码信息、哪些是网络信息。这样出了问题,你才知道从哪里下手。