第2章:SDP协商

6 阅读11分钟

第 2 章:SDP——媒体协商的艺术

本章解决的痛点:编码器选 G.711 还是 OPUS?为什么通话中点击"保持"对方听不到音乐?双方都说支持视频,为什么通话只有音频?理解 SDP 的 offer/answer 模型,是掌控媒体质量与兼容性的关键。


2.1 SDP 协议基础

2.1.1 SDP 是什么?

SDP(Session Description Protocol,会话描述协议) 定义于 RFC 4566,它本身不负责传输,只是用一种标准格式描述多媒体会话的参数。SIP 消息的消息体(Body)通常就是 SDP。

类比:

  • SIP = 打电话的动作(拨号、接听、挂断)
  • SDP = 双方确认通话细节(用什么语言、什么音量、是否开视频)

2.1.2 SDP 文本格式解析

SDP 是纯文本格式,由一系列 type=value 的行组成,type 必须是小写字母:

v=0                          ← 协议版本,目前固定为 0
o=alice 2890844526 2890844526 IN IP4 pc33.example.com  ← 会话发起者
s=-                          ← 会话名称(- 表示无)
i=A conversation about SDP   ← 会话信息(可选)
u=http://example.com/sdp     ← URI(可选)
e=alice@example.com          ← 邮箱(可选)
p=+1 123 456 7890           ← 电话(可选)
c=IN IP4 pc33.example.com    ← 连接信息(全局或按媒体)
b=AS:64                      ← 带宽信息(可选)
t=0 0                        ← 活动时间(0 0 表示永久)
r=7d 1h 0 25h               ← 重复时间(可选)
m=audio 49170 RTP/AVP 0 8    ← 媒体描述:音频,端口,协议,格式列表
a=rtpmap:0 PCMU/8000        ← 属性:格式 0 对应 PCMU,8kHz
a=rtpmap:8 PCMA/8000        ← 属性:格式 8 对应 PCMA,8kHz
a=sendrecv                   ← 属性:双向收发
m=video 51372 RTP/AVP 31    ← 第二个媒体:视频
a=rtpmap:31 H261/90000      ← 属性:格式 31 对应 H.261

2.1.3 必需字段详解

v=(Version) 协议版本,目前唯一有效值是 0

o=(Origin)

o=<username> <session-id> <session-version> <nettype> <addrtype> <unicast-address>
  • username: 发起者用户名,- 表示无
  • session-id: 唯一会话标识,通常是 NTP 时间戳
  • session-version: 会话版本,每次修改 SDP 时递增
  • nettype: 网络类型,IN = Internet
  • addrtype: 地址类型,IP4IP6
  • unicast-address: 发起者的 IP 地址或主机名

s=(Session Name) 会话名称,每个 SDP 必须有一个且仅有一个。如果不需要,用 -

t=(Timing)

t=<start-time> <stop-time>

NTP 时间戳,0 0 表示会话是永久的(常见于电话呼叫)。

m=(Media)

m=<media> <port> <proto> <fmt> ...
  • media: audio, video, text, application, message
  • port: 传输端口(RTP 端口)
  • proto: 传输协议,RTP/AVP(UDP)、RTP/SAVP(SRTP)、udpTCP
  • fmt: 媒体格式列表,数字对应 RTP payload type

一个 SDP 可以有多个 m= 行,表示多路媒体(如音频 + 视频)。

2.1.4 属性行(a=)——SDP 的扩展机制

a= 是 SDP 中最灵活的部分,常见属性:

属性含义示例
rtpmapRTP 负载类型映射a=rtpmap:0 PCMU/8000
fmtp格式特定参数a=fmtp:97 profile-level-id=42e01f
sendrecv双向收发(默认)a=sendrecv
sendonly仅发送a=sendonly(hold 状态)
recvonly仅接收a=recvonly
inactive不发送也不接收a=inactive
ptime打包时长(毫秒)a=ptime:20
maxptime最大打包时长a=maxptime:60
cryptoSRTP 加密密钥a=crypto:1 AES_CM_128_HMAC_SHA1_80 ...
candidateICE 候选地址a=candidate:1 1 UDP 2130706431 192.168.1.1 ...
rtcpRTCP 端口(非 RTP+1 时)a=rtcp:53020

2.2 offer/answer 模型——谁来决定用什么编解码?

2.2.1 RFC 3264 协商流程

SDP 本身不定义如何协商,RFC 3264 定义了 offer/answer 模型:

  1. offer:主动方发送 SDP,列出自己支持的所有选项(编解码、端口、IP 等)
  2. answer:被动方回复 SDP,从 offer 中选择自己支持的选项
  3. 协商结果:双方都认可的参数组合

关键规则:

  • answer 必须包含与 offer 数量相同的 m= 行(按顺序对应)
  • answer 的 m= 格式列表必须是 offer 的子集(只能删减,不能添加)
  • answer 的 rtpmap 必须映射 offer 中声明的 payload type
  • 第一个共同支持的格式将被使用(顺序很重要!)

2.2.2 协商实例:Alice 呼叫 Bob

Alice 的 offer(INVITE):

v=0
o=alice 2890844526 0 IN IP4 alice.example.com
s=-
c=IN IP4 alice.example.com
t=0 0
m=audio 49170 RTP/AVP 0 8 96
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:96 opus/48000/2
a=ptime:20

Alice 支持:PCMU (G.711 μ-law)、PCMA (G.711 A-law)、OPUS。她希望用 OPUS(放在最后,但实际优先看对方支持什么)。

Bob 的 answer(200 OK):

v=0
o=bob 2890844730 0 IN IP4 bob.example.com
s=-
c=IN IP4 bob.example.com
t=0 0
m=audio 3456 RTP/AVP 0 96
a=rtpmap:0 PCMU/8000
a=rtpmap:96 opus/48000/2
a=ptime:20

Bob 支持 PCMU 和 OPUS,不支持 PCMA。最终双方使用 PCMU(因为 0 在 96 之前,且双方支持)。

如果 Bob 只想用 OPUS:

m=audio 3456 RTP/AVP 96 0

把 96 放在第一位,双方就会协商使用 OPUS。

2.2.3 FreeSWITCH 的协商策略

FreeSWITCH 通过 vars.xml 或 SIP Profile 配置支持的编解码器顺序:

<!-- conf/vars.xml -->
<X-PRE-PROCESS cmd="set" data="global_codec_prefs=OPUS,PCMU,PCMA,G729"/>
<X-PRE-PROCESS cmd="set" data="outbound_codec_prefs=OPUS,PCMU,PCMA"/>

协商逻辑

  1. FS 作为 UAC(发起呼叫):按 outbound_codec_prefs 顺序发送 offer
  2. FS 作为 UAS(接收呼叫):按 global_codec_prefs 顺序选择 answer
  3. 如果双方没有共同编解码器 → 488 Not Acceptable Here

2.2.4 协商失败案例分析

案例 1:无共同编解码器

Alice offer: m=audio 10000 RTP/AVP 96
            a=rtpmap:96 opus/48000/2

Bob answer: m=audio 20000 RTP/AVP 0
            a=rtpmap:0 PCMU/8000

Bob 的 answer 中 m= 的格式列表是 0,但 offer 中没有 0,只有 96。这是非法 answer,Alice 应回复 488 或忽略。

案例 2:payload type 冲突

Alice: a=rtpmap:96 opus/48000/2
Bob:   a=rtpmap:96 G729/8000

双方都用了 96,但映射到不同编解码器!这会导致音频完全错乱。

解决方案:FS 会自动进行 payload type 重写(rewrite) ,在 SDP 中统一映射。


2.3 媒体方向属性——hold/resume 的 SDP 实现

2.3.1 四种方向属性

属性含义应用场景
sendrecv双向收发正常通话(默认值)
sendonly仅发送,不接收保持通话但只听,或单向广播
recvonly仅接收,不发送保持通话但只发静音,或录音端
inactive既不发送也不接收完全暂停,节省带宽

2.3.2 保持(Hold)的 SDP 流程

当你点击"保持"按钮:

步骤 1:Re-INVITE with sendonly

INVITE sip:bob@example.com SIP/2.0
...
m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendonly  ← 告诉对方:我还在发,但不想收你的了
a=inactive  ← 或完全不收发

步骤 2:对方回复 200 OK with recvonly

SIP/2.0 200 OK
...
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=recvonly  ← 好的,我只收不发

步骤 3:保持音乐(MOH) FS 检测到 sendonly 后,会向被保持方播放保持音乐(Music on Hold)。音乐通过 RTP 发送到对方的 recvonly 端口。

2.3.3 为什么点了"保持"对方却听不到音乐?

常见故障排查

  1. SDP 方向错误:检查 Re-INVITE 是否真的包含 a=sendonly,而不是 a=inactive

    • sendonly = 我方发音乐,对方收
    • inactive = 完全静音
  2. RTP 流向错误:音乐应该发到对方的 RTP 端口,检查 SDP 中的 c=m=

  3. MOH 配置问题:FreeSWITCH 的保持音乐配置

    <!-- conf/autoload_configs/switch.conf.xml -->
    <param name="hold-music" value="local_stream://moh"/>
    

    检查 local_stream 是否正常加载:

    fs_cli> show local_stream
    
  4. 防火墙阻断:MOH 的 RTP 流可能走不同端口,检查防火墙规则

2.3.4 恢复通话(Resume)

再次 Re-INVITE,将方向改回 sendrecv

INVITE sip:bob@example.com SIP/2.0
...
a=sendrecv  ← 恢复正常双向通话

2.4 带宽管理与 QoS

2.4.1 b= 行:带宽声明

SDP 支持两种带宽类型:

b=CT:1000    ← Conference Total,整个会议的总带宽
b=AS:64      ← Application Specific,单个媒体的带宽
b=TIAS:64000 ← Transport Independent Application Specific(精确到 bps)

编解码器带宽参考

编解码器采样率打包时长码率实际带宽(含 RTP/UDP/IP 头)
G.711 (PCMU/PCMA)8kHz20ms64 kbps~80 kbps
G.7298kHz20ms8 kbps~24 kbps
OPUS48kHz20ms可变 6-510 kbps通常 20-40 kbps
iLBC8kHz30ms13.33 kbps~30 kbps

2.4.2 ptime:打包时长的艺术

ptime 定义每个 RTP 包包含多少毫秒的音频数据:

ptime每包采样数包率延迟适用场景
10ms80100 pkt/s实时交互、低延迟网络
20ms16050 pkt/s默认推荐,平衡延迟与开销
40ms32025 pkt/s高丢包网络,减少包头开销
60ms48016.7 pkt/s很高卫星链路、极端弱网

计算示例

  • G.711,ptime=20ms:每秒 50 个包,每个包 160 字节音频数据 + 12 字节 RTP 头 + 8 字节 UDP 头 + 20 字节 IP 头 = 200 字节/包 → 80 kbps
  • G.711,ptime=40ms:每秒 25 个包,每个包 320 + 40 = 360 字节 → 72 kbps(节省 10% 带宽)

2.4.3 QoS 标记:DSCP/ToS

c= 行后可以添加网络类型,配合 SIP/SDP 指示 QoS:

c=IN IP4 192.168.1.100
b=AS:64
a=dscp:ef  ← Expedited Forwarding,语音优先

FreeSWITCH 配置(sip_profiles/internal.xml):

<param name="rtp-ip-tos" value="184"/>  <!-- DSCP EF = 10111000 = 184 -->
<param name="sip-ip-tos" value="184"/>

2.5 多路媒体与流标识

2.5.1 音频 + 视频的 SDP 结构

m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000
m=video 51372 RTP/AVP 31 32
a=rtpmap:31 H261/90000
a=rtpmap:32 MPV/90000

两个 m= 行表示两路独立的媒体流,分别有独立的端口和编解码器。

2.5.2 流复用:bundle

WebRTC 常用 BUNDLE 扩展,将音频和视频复用到同一端口:

m=audio 9 UDP/TLS/RTP/SAVPF 111
a=mid:0
...
m=video 9 UDP/TLS/RTP/SAVPF 96
a=mid:1
...
a=group:BUNDLE 0 1  ← 将 mid 01 捆绑到同一传输

2.5.3 SSRC 与 CNAME

在 SDP 中声明 RTP 流的同步源标识:

a=ssrc:123456789 cname:user@example.com
a=ssrc:123456789 msid:stream1 track1

2.6 实战:分析一次 SDP 协商故障

场景:FreeSWITCH 与某网关互通,呼叫失败

抓包看到的 offer(FS 发出):

m=audio 26748 RTP/AVP 102 0 8 9 103 101
a=rtpmap:102 opus/48000/2
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:9 G722/8000
a=rtpmap:103 telephone-event/48000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=ptime:20
a=sendrecv

answer(网关回复):

m=audio 10000 RTP/AVP 18 0 8
a=rtpmap:18 G729/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=sendrecv

问题分析

  1. FS 不支持 G.729(没有授权)
  2. 网关首选 G.729(payload 18 放在第一位)
  3. 双方共同支持的是 PCMU (0) 和 PCMA (8)
  4. 但网关的 answer 格式列表是 18 0 8,FS 应该选择 0 或 8

实际结果:FS 回复 488 Not Acceptable Here

根因:网关 SDP 格式有误,或 FS 配置禁用了 G.711

解决:检查 FS 的 global_codec_prefs,确保包含 PCMU/PCMA:

<X-PRE-PROCESS cmd="set" data="global_codec_prefs=OPUS,G722,PCMU,PCMA"/>

本章小结

  1. SDP 描述会话参数,SIP 传输 SDP。看懂 SDP 是排查"没声音、音质差、视频不显示"等问题的基本功。
  2. offer/answer 模型决定编解码选择:格式列表的顺序决定优先级,第一个共同支持的格式将被使用。
  3. 方向属性(sendrecv/sendonly/recvonly/inactive)控制媒体流:hold/resume 的本质是 Re-INVITE + 方向修改。
  4. ptime 影响延迟与带宽:默认 20ms 是最佳平衡点,弱网可增大到 40ms。
  5. 多路媒体用多个 m= 行,WebRTC 的 BUNDLE 扩展可复用传输。

练习题

  1. Alice 支持 OPUS、G.711,Bob 只支持 G.711。如果 Alice 的 offer 中格式列表是 m=audio 10000 RTP/AVP 96 0,而 Bob 的 answer 是 m=audio 20000 RTP/AVP 0 96,最终协商使用什么编解码?为什么?
  2. 通话中保持音乐的 SDP 流程是怎样的?如果 answer 方回复 a=inactive 而不是 a=recvonly,会发生什么?
  3. G.711 的 payload type 0 和 8 分别代表什么?在跨国互通中有什么注意事项?
  4. 解释 a=rtpmap:96 opus/48000/2 中每个字段的含义。为什么是 48000 而不是 8000?

下一章预告:RTP/RTCP——语音数据的搬运工。我们将深入解析 RTP 包头、抖动缓冲、丢包补偿,以及如何用工具分析通话质量。