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=:协议版本,一般都是0
  • o=:会话发起者信息
  • m=:媒体描述,比如音频、视频
  • a=:属性行,具体描述编码参数

我在项目中遇到过最坑的一次,就是对方发来的SDP里m=video 0,端口是0。这意味着对方禁用了视频。当时我以为是bug,查了半天才发现是对方故意设置的。

6.2 创建Offer/Answer

SDP交换的核心就是Offer/Answer模型。这个模型是RFC 3264定义的,WebRTC直接沿用了这套机制。

流程很简单:

  1. 发起方(Caller)创建一个Offer SDP
  2. 接收方(Callee)收到Offer后,创建一个Answer SDP
  3. 双方各自设置本地和远端描述

代码实现是这样的:

// 发起方:创建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协商流程,我用一张图来总结:

SDP协商流程图 发起方 (Caller) 接收方 (Callee) 1. createOffer() Offer SDP 2. setRemoteDescription() 3. createAnswer() 4. setLocalDescription() Answer SDP 5. setRemoteDescription() ✅ 连接建立成功

整个流程其实就五步:

  1. 发起方创建Offer:调用createOffer()生成SDP
  2. 接收方设置远端描述:把收到的Offer设置进去
  3. 接收方创建Answer:调用createAnswer()生成应答SDP
  4. 接收方设置本地描述:把Answer设置到本地
  5. 发起方设置远端描述:把收到的Answer设置进去

五步走完,SDP协商就完成了。接下来就是ICE候选交换和媒体传输。

关键点:SDP协商是双向的。Offer和Answer必须一一对应。如果一方修改了SDP(比如中途切换编码),需要重新走一遍完整的Offer/Answer流程。

我记得有一次做多端通话,一个房间里有三个人。结果其中一个人中途退出了,另外两个人的SDP没有重新协商,导致音频一直卡顿。后来我加了一个监听onnegotiationneeded事件,只要有人加入或退出就重新协商,问题就解决了。

嗯,SDP交换这块内容其实不难,但细节很多。你只要记住:先创建,再设置本地,再发送,再设置远端,这个顺序别搞反就行。

避坑指南:我曾经在setLocalDescription之后直接发送SDP,但忘了等ICE候选收集完成。结果对方收到的SDP里没有ICE候选信息,连接一直卡在checking状态。后来我改用pc.onicecandidate事件,等候选收集完再统一发送,问题解决。

最后说一句:SDP本质上就是个文本,你可以打印出来看看。我调试的时候经常把SDP打印到控制台,看看媒体行对不对、编码顺序对不对。这比瞎猜快多了。


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