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。

流程是这样的:

  1. 发起方创建Offer:把自己的媒体能力、网络信息打包成SDP
  2. 发起方设置本地SDP:调用setLocalDescription()
  3. 发起方发送Offer:通过信令通道传给对方
  4. 接收方收到Offer:调用setRemoteDescription()设置远端SDP
  5. 接收方创建Answer:根据Offer内容,生成自己的SDP
  6. 接收方设置本地SDP:调用setLocalDescription()
  7. 接收方发送Answer:通过信令通道传回
  8. 发起方收到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,再开始传输。

我个人的习惯是,在setLocalDescriptionsetRemoteDescription前后都加日志,方便排查问题:

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 Offer/Answer 协商流程 发起方 (Offerer) 接收方 (Answerer) ① 创建 Offer ② setLocalDescription(offer) 发送 Offer (信令) ③ setRemoteDescription(offer) ④ 创建 Answer ⑤ setLocalDescription(answer) 发送 Answer (信令) ⑥ setRemoteDescription(answer) ✅ 协商完成,开始传输

这张图把整个流程串起来了。你仔细看,发起方和接收方各做三件事,中间通过信令通道交换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.sdppc.remoteDescription.sdp直接打印。

好了,关于SDP协议就聊这么多。记住,SDP是WebRTC的「说明书」,读懂它,你就能掌控整个媒体协商过程。

公众号:蓝海资料掘金营,微信deep3321