7、SDP协议:SDP是什么、SDP结构解析、Offer/Answer模型、本地与远程SDP设置
聊到WebRTC,就绕不开SDP。很多初学者一看到SDP那密密麻麻的文本就头大,觉得像天书。其实说白了,SDP就是一份「媒体会话的说明书」。它不负责传输数据,只负责告诉对方:我能支持什么格式、我的网络地址在哪、我打算怎么传。
我刚开始接触WebRTC时,也踩过SDP的坑。有一次调试视频通话,画面死活出不来,折腾了半天才发现是SDP里音频和视频的m-line顺序写反了。嗯,从那以后,我对SDP就格外上心。
7.1 SDP是什么
SDP全称Session Description Protocol,会话描述协议。它不是传输协议,而是一种文本格式的描述语言。你可以把它想象成一份「简历」——你的浏览器把自己的媒体能力、网络信息写成一份SDP,发给对方。对方看完后,再回一份自己的SDP。
WebRTC中,SDP主要描述三件事:
- 媒体信息:音频还是视频?用什么编码?
- 网络信息:IP地址、端口号、传输协议
- 安全信息:加密方式、指纹等
我个人习惯把SDP比作「握手前的自我介绍」。双方还没开始传数据,先互相亮个底牌。
核心要点:SDP不是协议本身,而是描述协议。它告诉对方「我在这里,用这个格式,你能接吗?」
7.2 SDP结构解析
SDP的结构其实很规整。它由多行文本组成,每行以<类型>=<值>的形式出现。我刚开始看的时候觉得乱,后来发现只要抓住几个关键字段,就能快速读懂。
下面是一个典型的WebRTC SDP示例:
v=0
o=- 1234567890 2 IN IP4 192.168.1.100
s=-
t=0 0
m=audio 49170 RTP/AVP 111 103 104
a=rtpmap:111 opus/48000/2
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
m=video 49172 RTP/AVP 96 97 98
a=rtpmap:96 H264/90000
a=rtpmap:97 VP8/90000
a=rtpmap:98 VP9/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42e01f
我们来拆解一下关键字段:
| 字段 | 含义 | 说明 |
|---|---|---|
v= |
版本号 | 目前基本都是0 |
o= |
会话发起者 | 包含用户名、会话ID、版本、网络类型、地址 |
s= |
会话名称 | WebRTC中通常用-占位 |
t= |
会话时间 | WebRTC中通常用0 0表示不限制 |
m= |
媒体描述 | 媒体类型、端口、传输协议、编码格式列表 |
a= |
属性行 | 扩展描述,如编码参数、方向、SSRC等 |
这里我要特别说一下m=行。它是最核心的字段,格式是:
m=<媒体类型> <端口> <传输协议> <编码格式列表>
比如m=audio 49170 RTP/AVP 111 103 104,意思是:音频媒体,端口49170,使用RTP/AVP协议,支持opus、ISAC 16k、ISAC 32k三种编码。
小技巧:看SDP时,先找m=行,再看对应的a=rtpmap行。这样就能快速知道对方支持哪些编码。
7.3 Offer/Answer模型
Offer/Answer模型是WebRTC建立连接的核心机制。说白了就是:一方先发Offer,另一方回Answer。
流程是这样的:
- 发起方创建Offer:把自己的媒体能力、网络信息打包成SDP
- 发起方设置本地SDP:调用
setLocalDescription() - 发起方发送Offer:通过信令通道传给对方
- 接收方收到Offer:调用
setRemoteDescription()设置远端SDP - 接收方创建Answer:根据Offer内容,生成自己的SDP
- 接收方设置本地SDP:调用
setLocalDescription() - 接收方发送Answer:通过信令通道传回
- 发起方收到Answer:调用
setRemoteDescription()设置远端SDP
我曾经在项目中遇到过一个问题:Offer和Answer的编码顺序不一致,导致某些浏览器无法协商成功。后来发现,Offer里把opus放在第一位,Answer里却把PCMU放在第一位,两边协商时匹配不上。嗯,这里要注意,Answer应该尽量保持与Offer相同的编码优先级。
避坑指南:Offer和Answer必须成对出现。不能只发Offer不回Answer,也不能重复发送。我曾经见过有人不小心发了两次Offer,结果PeerConnection直接报错。
7.4 本地与远程SDP设置
在WebRTC中,每个PeerConnection都有两个SDP:本地SDP和远程SDP。本地SDP描述的是「我」的能力,远程SDP描述的是「对方」的能力。
设置SDP的核心API就两个:
// 设置本地SDP
await pc.setLocalDescription(offer);
// 设置远程SDP
await pc.setRemoteDescription(answer);
这里有个关键点:设置顺序不能乱。创建Offer后,必须先设置本地SDP,再发送。收到Answer后,必须先设置远程SDP,再开始传输。
我个人的习惯是,在setLocalDescription和setRemoteDescription前后都加日志,方便排查问题:
console.log('设置本地SDP前:', pc.localDescription?.sdp?.slice(0, 100));
await pc.setLocalDescription(offer);
console.log('设置本地SDP后:', pc.localDescription?.sdp?.slice(0, 100));
你想想看,如果SDP设置错了,ICE协商、媒体传输全都会出问题。所以这一步一定要谨慎。
核心原则:本地SDP描述「我」,远程SDP描述「对方」。Offer是发起方先设本地,Answer是接收方先设远程。
7.5 SDP协商流程图
为了让你更直观地理解整个流程,我画了一张图:
这张图把整个流程串起来了。你仔细看,发起方和接收方各做三件事,中间通过信令通道交换SDP。顺序不能乱,少了任何一步,连接都建立不起来。
7.6 实际开发中的注意事项
最后,我分享几个实际开发中容易踩的坑:
- SDP格式校验:有些信令服务器会修改SDP内容,比如添加或删除某些属性行。我曾经遇到过信令服务器把
a=ice-ufrag给删了,结果ICE协商一直失败。建议在setRemoteDescription之前,先打印SDP内容检查一下。 - 多流场景:如果同时传输音频、视频、屏幕共享,SDP里会有多个
m=行。每个媒体流都有自己的SSRC和编码参数。我建议用pc.getTransceivers()来管理,而不是手动解析SDP。 - 重协商:有时候需要在通话中动态添加或移除媒体流,这时候需要重新创建Offer/Answer。注意,重协商时不能重置PeerConnection,只能调用
createOffer重新生成SDP。
调试技巧:Chrome开发者工具中,在Application标签页下可以查看PeerConnection的SDP信息。或者用pc.localDescription.sdp和pc.remoteDescription.sdp直接打印。
好了,关于SDP协议就聊这么多。记住,SDP是WebRTC的「说明书」,读懂它,你就能掌控整个媒体协商过程。