7、SDP协议详解:SDP结构、Offer/Answer模型、媒体描述与属性

聊到WebRTC,SDP是绕不开的核心。说白了,SDP就是通信双方用来“握手”的协议。它不负责传输媒体数据,而是告诉对方:“我能发什么格式的视频、用什么编码、IP和端口是多少”。

我刚开始接触WebRTC时,觉得SDP就是一堆乱七八糟的文本行。后来踩了不少坑才明白,每一行都有它的意义。今天我就带你彻底搞懂SDP的结构、Offer/Answer模型,以及媒体描述和属性。

7.1 SDP的基本结构

SDP全称Session Description Protocol,会话描述协议。它用纯文本描述多媒体会话。一个典型的SDP长这样:

v=0
o=- 1234567890 1234567890 IN IP4 192.168.1.100
s=-
t=0 0
m=audio 5004 RTP/AVP 96 97
a=rtpmap:96 OPUS/48000/2
a=rtpmap:97 PCMU/8000
c=IN IP4 192.168.1.100
m=video 5006 RTP/AVP 98 99
a=rtpmap:98 H264/90000
a=rtpmap:99 VP8/90000
c=IN IP4 192.168.1.100

每一行以类型=值的形式出现。类型是单个字母,代表不同含义。我习惯把SDP分成三部分:会话级描述、时间描述、媒体级描述。

核心要点:SDP的每一行都是key=value结构。key是单字母,value是具体内容。顺序很重要,不能乱。

7.2 会话级描述

会话级描述放在最前面,作用于整个会话。常见的字段有:

字段 含义 示例
v= 协议版本 v=0
o= 会话发起者信息 o=- 1234567890 1234567890 IN IP4 192.168.1.100
s= 会话名称 s=-
t= 会话时间 t=0 0

o=行包含用户名、会话ID、版本号、网络类型、地址类型和地址。我曾在项目中遇到过一个问题:两个Peer的o=行版本号相同,导致ICE重启失败。后来我养成了每次生成新Offer时递增版本号的习惯。

个人经验:o=行里的版本号很重要。每次修改SDP都要递增它,否则对方可能忽略你的更新。

7.3 媒体级描述

媒体级描述以m=开头,描述一个媒体流。格式是:

m=媒体类型 端口 传输协议 格式列表

举个例子:

m=audio 5004 RTP/AVP 96 97

这表示:音频媒体,端口5004,使用RTP/AVP协议,支持格式96和97。格式96和97对应的是a=rtpmap行定义的编码。

常见的媒体类型有:

  • audio:音频流
  • video:视频流
  • application:数据通道

传输协议通常是RTP/AVPRTP/SAVPF。后者用于SRTP加密,WebRTC中常用。

注意:端口号在WebRTC中通常设为9,表示端口由ICE协商决定。不要写死端口,否则ICE可能无法绑定。

7.4 媒体属性

媒体属性以a=开头,是对媒体描述的补充。WebRTC中大量使用属性行。常见的属性有:

属性 含义 示例
a=rtpmap 定义编码格式 a=rtpmap:96 OPUS/48000/2
a=fmtp 编码参数 a=fmtp:96 minptime=10; useinbandfec=1
a=sendrecv 双向传输 a=sendrecv
a=recvonly 只接收 a=recvonly
a=candidate ICE候选地址 a=candidate:1 1 UDP 2122252543 192.168.1.100 5004 typ host

a=rtpmap的格式是:a=rtpmap:负载类型 编码名称/采样率/声道数。比如a=rtpmap:96 OPUS/48000/2表示负载类型96对应Opus编码,48kHz采样率,双声道。

我曾经调试过一个音频卡顿的问题,查了半天发现是a=fmtpuseinbandfec=0,关闭了带内FEC。改成1之后,丢包情况下的音质明显改善。

避坑指南:我曾经在a=fmtp里写错了参数格式,导致对方解析失败。记住,多个参数用分号分隔,不要用逗号。

7.5 Offer/Answer模型

Offer/Answer模型是SDP交换的核心机制。简单说就是:一方发起Offer,另一方回复Answer。双方通过这个流程协商出一套双方都支持的媒体配置。

流程是这样的:

  1. 发起方生成Offer SDP,描述自己支持的媒体能力
  2. 接收方收到Offer后,生成Answer SDP,选择双方都支持的配置
  3. 双方根据协商结果建立媒体通道

为什么需要这个模型?因为双方的能力可能不同。比如一方支持H264和VP8,另一方只支持VP8。通过Offer/Answer,最终选择VP8。

我画了一张图来展示这个流程:

发起方 接收方 1. 生成Offer SDP (包含支持的编码、端口等) 2. 回复Answer SDP (选择双方都支持的配置) 3. 协商完成 双方使用协商后的编码、端口、ICE候选建立连接

Offer和Answer的生成在WebRTC中由createOffer()createAnswer()完成。你不需要手动拼SDP字符串,但理解它的结构对调试问题很有帮助。

个人建议:调试SDP问题时,把Offer和Answer打印出来对比。看m=行和a=rtpmap行是否匹配。很多连接失败都是因为编码格式没对上。

7.6 媒体方向属性

媒体方向属性控制流的发送和接收方向。WebRTC中常用的有:

  • a=sendrecv:既能发送也能接收(默认)
  • a=sendonly:只发送不接收
  • a=recvonly:只接收不发送
  • a=inactive:既不发送也不接收

举个例子,一个只推流不拉流的客户端,它的音频和视频方向应该是sendonly。而一个只观看的客户端,方向是recvonly

我记得有一次做直播场景,推流端设了sendonly,但拉流端忘了设recvonly,结果拉流端也尝试推流,导致带宽浪费。方向属性一定要根据实际场景设置。

7.7 ICE候选与SDP

ICE候选地址也写在SDP里,以a=candidate行出现。格式是:

a=candidate:组件ID 候选类型 优先级 传输协议 IP 端口 typ 候选类型

比如:

a=candidate:1 1 UDP 2122252543 192.168.1.100 5004 typ host

这表示一个主机候选,IP是192.168.1.100,端口5004。ICE会收集多个候选,包括host(本地)、srflx(反射)、relay(中继)。

我曾经遇到过一个问题:候选地址太多,SDP体积过大,导致信令传输变慢。后来我限制了候选数量,只保留优先级最高的几个,问题就解决了。

注意:ICE候选行在SDP中可能出现很多条。不要手动修改它们,ICE层会自动管理。你只需要确保ICE配置正确即可。

7.8 实际调试技巧

最后分享几个我在项目中积累的SDP调试技巧:

  • 打印SDP:在onicecandidateonnegotiationneeded回调中打印SDP,方便对比
  • 检查编码:确保Offer和Answer的a=rtpmap行有交集
  • 检查端口:WebRTC中端口通常为9,不要写死
  • 检查方向:确认sendrecv/sendonly/recvonly设置正确

嗯,SDP的内容就讲到这里。理解SDP结构、Offer/Answer模型和媒体属性,是调试WebRTC连接问题的基本功。下次遇到连接失败,先看SDP,八成能找到线索。

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