跳到主要内容

SDP结构详解

SDP(Session Description Protocol,RFC 8866)是 WebRTC 协商的载体。本文只讲 WebRTC 实际用到的字段,不复述 RFC 全部内容

重要原则:永远不要做 SDP 字符串拼接 / 替换(俗称 "SDP munging")。优先用 RTCRtpTransceiversetCodecPreferencessetParametersaddTransceiver({direction}) 等标准 API。SDP munging 是 WebRTC 圈最常见的"自爆雷区"。

1. SDP 整体结构

会话级字段(v= o= s= t= a=group ...)
└── 媒体级字段 m=audio + 一堆 a=
└── 媒体级字段 m=video + 一堆 a=
└── 媒体级字段 m=application(DataChannel)+ 一堆 a=

会话级字段对所有 m-line 生效;媒体级字段只对当前 m-line 生效。

2. 会话级字段

v=0 # 版本,固定 0
o=- 4611733054762309635 2 IN IP4 0.0.0.0 # session id、版本号,浏览器随机生成
s=- # session name,WebRTC 不用
t=0 0 # 永久会话
a=group:BUNDLE 0 1 2 # 把多条 m-line 复用同一个端口
a=msid-semantic: WMS * # MediaStream 语义标记
a=extmap-allow-mixed # 允许扩展头混合
a=ice-options:trickle # 启用 Trickle ICE

a=group:BUNDLE 是性能关键字段。bundlePolicy: 'max-bundle' 会把所有音视频流复用到一个 UDP 端口,减少 NAT 穿透成本。

3. m-line:每路媒体一段

m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125
│ │ │ │
│ │ │ └─ 此 m-line 支持的 payload type 列表(按优先级)
│ │ └─────────────────── 传输协议:UDP+TLS+RTP+SAVPF=DTLS-SRTP 必备
│ └────────────────────── 端口(恒为 9,真实端口由 ICE 决定)
└────────────────────────────── 媒体类型:audio / video / application

payload type 顺序就是发送端的偏好顺序。可以通过 RTCRtpTransceiver.setCodecPreferences() 修改而不需要碰 SDP

4. 关键 a-line(按主题分组)

4.1 标识与方向

字段示例含义
a=mid:0a=mid:0媒体 ID,BUNDLE 内唯一
a=msid:...a=msid:stream1 track1MediaStream/Track ID
a=sendrecv双向
a=sendonly只发
a=recvonly只收
a=inactive暂停

方向由 RTCRtpTransceiver.direction 控制。

4.2 编解码

a=rtpmap:111 opus/48000/2 # PT=111 → opus,48kHz,2 声道
a=fmtp:111 minptime=10;useinbandfec=1 # 编码参数
a=rtcp-fb:111 transport-cc # 启用 TWCC
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 goog-remb

rtcp-fb 字段决定了能用哪些反馈机制(NACK/PLI/REMB/TWCC)。Chrome 默认开启全部,Safari 部分缺失。

4.3 安全相关

a=fingerprint:sha-256 4A:AD:B9:B1:8C:... # 本端 DTLS 证书指纹
a=setup:actpass # DTLS 角色(actpass/active/passive)
a=ice-ufrag:abcd # ICE 用户名片段
a=ice-pwd:1234567890abcdefghij # ICE 密码(验证 STUN binding)

指纹必须由对端校验——这是 WebRTC 端到端身份保证的核心。

4.4 BUNDLE 与 RTCP 复用

a=group:BUNDLE 0 1 2 # 复用端口
a=rtcp-mux # RTP 与 RTCP 同端口
a=rtcp-rsize # 简化 RTCP 包

不开 rtcp-mux 每路媒体要两个端口(RTP+RTCP),NAT 穿透成本翻倍。生产必开。

4.5 扩展头(RTP Header Extensions)

a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid

TWCC 必须有 extmap:3 这个扩展,否则 SFU 收不到全链路拥塞反馈,码率自适应失效。

4.6 Simulcast

a=rid:q send
a=rid:h send
a=rid:f send
a=simulcast:send q;h;f

q/h/f 是低/中/高分辨率层,对应 RTCRtpSender.setParameters({encodings: [...]}) 里的三个 encoding。详见 04-媒体引擎与QoS

4.7 SSRC

a=ssrc:1234567890 cname:abc
a=ssrc:1234567890 msid:stream1 track1
a=ssrc-group:FID 1234567890 9876543210 # RTX 重传分组

SSRC 是 RTP 流的唯一标识,SFU 路由全靠它。FID 把主流和 RTX 重传流绑成一对。

5. 完整示例(精简版)

v=0
o=- 1 2 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE 0 1
a=extmap-allow-mixed
a=ice-options:trickle
a=msid-semantic: WMS *

m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:abcd
a=ice-pwd:1234567890abcdefghij
a=fingerprint:sha-256 4A:AD:B9:B1:8C:DE:11:22:33:44:55:66:77:88:99:00:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99
a=setup:actpass
a=mid:0
a=sendrecv
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
a=rtcp-fb:111 transport-cc

m=video 9 UDP/TLS/RTP/SAVPF 96 97
c=IN IP4 0.0.0.0
a=ice-ufrag:abcd
a=ice-pwd:1234567890abcdefghij
a=fingerprint:sha-256 4A:AD:B9:B1:...
a=setup:actpass
a=mid:1
a=sendrecv
a=rtcp-mux
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96

6. 阅读 SDP 的工程化方法

  1. 不要看完。先 grep m= 看几条 m-line。
  2. a=group:BUNDLE 是否覆盖全部 m-line。
  3. a=fingerprint 是否两端一致(信令是否被篡改)。
  4. a=rtcp-fb:* transport-cc 是否存在。
  5. a=extmap:* transport-wide-cc-extensions 是否存在。
  6. a=ssrc-group:FID 是否成对出现(RTX 是否开启)。

7. 反模式

反模式后果替代
用正则替换 SDP 里的 codec 顺序浏览器升级后失效setCodecPreferences()
删除 m-line 想关闭某路媒体setRemoteDescription 直接报错transceiver.direction = 'inactive'
b=AS: 限制码率不同浏览器解析不同,效果不一致sender.setParameters({encodings:[{maxBitrate}]})
手写 SDP 实现 simulcast容易出错浏览器原生支持 addTransceiver({sendEncodings})

8. 权威资料