SDP结构详解
SDP(Session Description Protocol,RFC 8866)是 WebRTC 协商的载体。本文只讲 WebRTC 实际用到的字段,不复述 RFC 全部内容。
重要原则:永远不要做 SDP 字符串拼接 / 替换(俗称 "SDP munging")。优先用
RTCRtpTransceiver、setCodecPreferences、setParameters、addTransceiver({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:0 | a=mid:0 | 媒体 ID,BUNDLE 内唯一 |
a=msid:... | a=msid:stream1 track1 | MediaStream/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 的工程化方法
- 不要看完。先 grep
m=看几条 m-line。 - 看
a=group:BUNDLE是否覆盖全部 m-line。 - 看
a=fingerprint是否两端一致(信令是否被篡改)。 - 看
a=rtcp-fb:* transport-cc是否存在。 - 看
a=extmap:* transport-wide-cc-extensions是否存在。 - 看
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. 权威资料
- RFC 8866 SDP: https://www.rfc-editor.org/rfc/rfc8866
- RFC 8829 JSEP: https://www.rfc-editor.org/rfc/rfc8829
- RFC 8843 Bundle: https://www.rfc-editor.org/rfc/rfc8843
- RFC 8851 RID: https://www.rfc-editor.org/rfc/rfc8851
- W3C WebRTC 1.0 §5 RTP Media API: https://www.w3.org/TR/webrtc/#rtp-media-api
- 核对日期:2026-06-22