6、SDP交换过程:SDP是什么、创建Offer/Answer、设置本地描述、设置远端描述、SDP协商流程
好,咱们今天来聊聊SDP交换。说实话,这是WebRTC里最容易被忽视、但又最容易出坑的环节。很多新手一上来就盯着媒体流怎么传,结果卡在SDP交换上半天调不通。我当年第一次做WebRTC项目时,就因为这个SDP没搞明白,debug了整整两天——后来发现只是顺序错了。
6.1 SDP是什么?
SDP,全称Session Description Protocol,会话描述协议。说白了,它就是一个“自我介绍”。
你想想看,两个人要视频通话,总得先告诉对方:
- 我支持什么编码格式?H264还是VP8?
- 我的网络端口是多少?
- 我的音视频参数是什么?
这些信息,就是通过SDP来传递的。它不是真正的媒体数据,而是描述媒体数据的“元数据”。
核心理解:SDP不是传输协议,它只是一个文本格式的描述文件。WebRTC用它来协商双方的媒体能力。
一个典型的SDP长这样:
v=0
o=- 123456 2 IN IP4 192.168.1.100
s=-
t=0 0
m=audio 49170 RTP/AVP 0 8 97
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:97 opus/48000/2
m=video 49172 RTP/AVP 96 98
a=rtpmap:96 H264/90000
a=rtpmap:98 VP8/90000
嗯,看着有点乱是吧?别急,我们拆开看:
v=:协议版本,一般都是0o=:会话发起者信息m=:媒体描述,比如音频、视频a=:属性行,具体描述编码参数
我在项目中遇到过最坑的一次,就是对方发来的SDP里m=video 0,端口是0。这意味着对方禁用了视频。当时我以为是bug,查了半天才发现是对方故意设置的。
6.2 创建Offer/Answer
SDP交换的核心就是Offer/Answer模型。这个模型是RFC 3264定义的,WebRTC直接沿用了这套机制。
流程很简单:
- 发起方(Caller)创建一个Offer SDP
- 接收方(Callee)收到Offer后,创建一个Answer SDP
- 双方各自设置本地和远端描述
代码实现是这样的:
// 发起方:创建Offer
const pc = new RTCPeerConnection(configuration);
// 先创建Offer
const offer = await pc.createOffer();
// 设置本地描述
await pc.setLocalDescription(offer);
// 通过信令服务器发送给远端
sendToRemote(offer);
// 接收方:收到Offer后创建Answer
const pc = new RTCPeerConnection(configuration);
// 设置远端描述(收到的Offer)
await pc.setRemoteDescription(offer);
// 创建Answer
const answer = await pc.createAnswer();
// 设置本地描述
await pc.setLocalDescription(answer);
// 发送Answer给发起方
sendToRemote(answer);
// 发起方:收到Answer后设置远端描述
await pc.setRemoteDescription(answer);
这里有个细节我特别想强调:createOffer和createAnswer都是异步的。我见过有人忘了await,结果setLocalDescription传了个Promise对象进去,直接报错。
个人习惯:我一般会在createOffer之前先调用一次pc.addTransceiver()来明确指定要传输的媒体类型,这样生成的SDP会更干净,不容易出现多余的媒体行。
6.3 设置本地描述(setLocalDescription)
setLocalDescription,顾名思义,就是把生成的SDP设置到本地PeerConnection上。这一步告诉浏览器:“嘿,这就是我自己的媒体能力描述,你照着这个来准备。”
为什么需要这一步?因为浏览器需要根据SDP里的信息来初始化编码器、分配端口、准备缓冲区。你不设置,浏览器就不知道该怎么干活。
我曾经犯过一个错误:在createOffer之后直接就把SDP字符串通过信令发出去了,忘了调用setLocalDescription。结果远端收到Offer后回复了Answer,我这边setRemoteDescription的时候直接报错——因为本地PeerConnection根本不知道自己在干什么。
注意:setLocalDescription必须在createOffer/createAnswer之后立即调用。顺序不能乱,也不能跳过。否则ICE代理无法正常工作。
6.4 设置远端描述(setRemoteDescription)
setRemoteDescription就是把对方发来的SDP设置到本地。这一步告诉浏览器:“对方的能力是这样的,你按照这个来适配。”
说白了,setRemoteDescription就是让浏览器知道:
- 对方支持什么编码
- 对方的IP和端口是什么
- 对方的ICE候选信息
代码上很简单:
// 收到远端发来的SDP
const remoteSDP = await receiveFromRemote();
await pc.setRemoteDescription(new RTCSessionDescription(remoteSDP));
但这里有个坑:setRemoteDescription的时机。如果你在ICE候选还没收集完就设置了远端描述,可能会导致连接失败。我建议等ICE候选收集完成后再设置,或者至少确保候选信息已经包含在SDP中。
6.5 SDP协商流程
整个SDP协商流程,我用一张图来总结:
整个流程其实就五步:
- 发起方创建Offer:调用createOffer()生成SDP
- 接收方设置远端描述:把收到的Offer设置进去
- 接收方创建Answer:调用createAnswer()生成应答SDP
- 接收方设置本地描述:把Answer设置到本地
- 发起方设置远端描述:把收到的Answer设置进去
五步走完,SDP协商就完成了。接下来就是ICE候选交换和媒体传输。
关键点:SDP协商是双向的。Offer和Answer必须一一对应。如果一方修改了SDP(比如中途切换编码),需要重新走一遍完整的Offer/Answer流程。
我记得有一次做多端通话,一个房间里有三个人。结果其中一个人中途退出了,另外两个人的SDP没有重新协商,导致音频一直卡顿。后来我加了一个监听onnegotiationneeded事件,只要有人加入或退出就重新协商,问题就解决了。
嗯,SDP交换这块内容其实不难,但细节很多。你只要记住:先创建,再设置本地,再发送,再设置远端,这个顺序别搞反就行。
避坑指南:我曾经在setLocalDescription之后直接发送SDP,但忘了等ICE候选收集完成。结果对方收到的SDP里没有ICE候选信息,连接一直卡在checking状态。后来我改用pc.onicecandidate事件,等候选收集完再统一发送,问题解决。
最后说一句:SDP本质上就是个文本,你可以打印出来看看。我调试的时候经常把SDP打印到控制台,看看媒体行对不对、编码顺序对不对。这比瞎猜快多了。
公众号:蓝海资料掘金营,微信deep3321