跳到主要内容

WebRTC 全景实战 (4):SDP 解剖与媒体协商

Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡
Rainy
10 MIN READ... VIEWS

"Offer/Answer 里那一大段文本,就是整个 WebRTC 会话的「合同」。"

Ch3 信令 负责传递 SDP,但 SDP 本身是什么?Ch2createOffer() 产出的字符串,遵循 RFC 8866 SDP 格式。它描述了:用什么编解码器、用什么 ICE 凭证、用什么 DTLS 指纹、媒体方向是什么

本章逐行解剖 WebRTC 中的 SDP,理解 Unified PlanBUNDLErtcp-mux,以及 RTCRtpTransceiver 与 m-line 的对应关系。


本篇术语表

术语英文解释
SDPSession Description Protocol文本格式的会话描述协议,不是传输协议,只描述「会话参数」
Offer发起方生成的 SDP,声明「我能提供什么」
Answer应答方生成的 SDP,声明「我接受什么」
m-lineMedia Description Linem= 开头的行,描述一条媒体流(audio/video/application)
midMedia Identificationm-line 的唯一标识符,Unified Plan 下每个 track 一个 mid
Payload Type (PT)RTP 包头中的 7bit 字段,映射到具体编解码器
rtpmapSDP 属性,定义 PT 与编解码器的映射关系
fmtpFormat Parameters编解码器的额外参数,如 H.264 的 profile-level-id
rtcp-fbRTCP Feedback声明支持的 RTCP 反馈机制(NACK/PLI/TWCC 等)
BUNDLE将多条 m-line 的多路 RTP 复用到同一个 UDP 五元组
rtcp-muxRTCP MultiplexingRTP 与 RTCP 共用同一端口,而非各用独立端口
Unified Plan现行 SDP 语义:每个 m-line 对应一个 Transceiver
Plan B已废弃:多个 track 共用一个 m-line

一、SDP 在 WebRTC 中的角色

SDP 是 声明式 的:它不建立连接,只描述能力。真正的连通由 ICE 完成,加密由 DTLS 完成,媒体由 RTP 承载。


二、完整 SDP 示例与逐行解读

以下是一段真实的 WebRTC Offer SDP(简化版):

v=0
o=- 4611731400430051336 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=extmap-allow-mixed
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:FPlu
a=ice-pwd:2/1muCWoOi3J5Wfu+86J7GqJ
a=ice-options:trickle
a=fingerprint:sha-256 4A:AD:BA:62:79:...
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=sendrecv
a=msid:stream-id track-audio-id
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:63 red/48000/2
a=rtcp-fb:111 transport-cc
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 103 104 107 108
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:FPlu
a=ice-pwd:2/1muCWoOi3J5Wfu+86J7GqJ
a=fingerprint:sha-256 4A:AD:BA:62:79:...
a=setup:actpass
a=mid:1
a=sendrecv
a=msid:stream-id track-video-id
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96

2.1 会话级字段

字段名含义
v=0VersionSDP 版本,固定为 0
o=- 4611731400430051336 2 IN IP4 127.0.0.1Origin会话创建者 ID、版本号、地址
s=-Session Name会话名,WebRTC 中通常为 -
t=0 0Timing会话时间,0 0 表示永久会话
a=group:BUNDLE 0 1BUNDLE Groupmid 01 共用同一传输通道
a=extmap-allow-mixed允许不同 m-line 使用不同 RTP 扩展

2.2 m-line 结构

m=<media> <port> <proto> <fmt列表>
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 ...
部分示例含义
mediaaudio / video媒体类型
port9占位端口(ICE 实际决定端口,9 为 discard port)
protoUDP/TLS/RTP/SAVPF协议栈:UDP + DTLS + SRTP + AVPF(反馈 profile)
fmt111 63 9 ...支持的 Payload Type 列表,按优先级排序

2.3 ICE 与 DTLS 属性

属性示例作用
a=ice-ufragFPluICE 用户名片段,连通性检查凭证
a=ice-pwd2/1muCWo...ICE 密码
a=ice-options:trickle支持 Trickle ICE
a=fingerprint:sha-256 ...DTLS 证书 SHA-256 指纹,Ch7
a=setup:actpassDTLS 角色:可主动可被动

2.4 媒体方向与 Track 标识

属性含义
a=mid:0此 m-line 的 ID,BUNDLE 组内引用
a=sendrecv双向收发(还有 sendonly/recvonly/inactive
a=msid:stream-id track-idMediaStream ID + Track ID,关联 addTrack()
a=rtcp-muxRTP 与 RTCP 同端口
a=rtcp-rsize使用 Reduced-Size RTCP

2.5 编解码器映射

a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
  • PT 111 → Opus,48kHz,2 声道
  • fmtp → 最小时长 10ms,启用带内 FEC
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 transport-cc
  • PT 96 → VP8,90kHz 时钟
  • 支持 NACK 重传、PLI 关键帧请求、TWCC 拥塞控制
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
  • PT 97 → RTX(重传)流,关联主 PT 96

三、BUNDLE 与 rtcp-mux 深度解析

3.1 为什么需要 BUNDLE?

没有 BUNDLE 时,audio 和 video 各占用独立 UDP 端口 → 更多 ICE Candidate → 更慢连通、更多 NAT 打洞失败。

a=group:BUNDLE 0 1 表示 mid 01 的 RTP/RTCP 都走第一个 m-line 协商出的传输通道

3.2 rtcp-mux

传统 RTP:RTP 端口 N,RTCP 端口 N+1。WebRTC 强制 rtcp-mux——RTP 和 RTCP 在同一端口,通过包类型区分。这减少了一半的 Candidate 数量。


四、Unified Plan vs Plan B

特性Plan BUnified Plan
m-line 与 track多 track 共一 m-line一 track 一 m-line
APIaddStream()addTransceiver() / addTrack()
Simulcast支持差原生 rid 支持
浏览器已移除唯一支持

Fippo — Exploring RTCRtpTransceiver 详细记录了 Unified Plan 成为唯一标准的设计决策过程。


五、RTCRtpTransceiver 详解

// 添加只发送视频的 Transceiver
const transceiver = pc.addTransceiver("video", {
direction: "sendonly",
sendEncodings: [
{ rid: "h", maxBitrate: 1_500_000, scaleResolutionDownBy: 1 },
{ rid: "m", maxBitrate: 500_000, scaleResolutionDownBy: 2 },
{ rid: "l", maxBitrate: 150_000, scaleResolutionDownBy: 4 },
],
});

// direction 可选值
// "sendrecv" | "sendonly" | "recvonly" | "inactive"
direction含义SDP 中
sendrecv双向a=sendrecv
sendonly只发送a=sendonly
recvonly只接收a=recvonly
inactive暂停a=inactive

六、编解码器协商过程

Answer 方不能添加 Offer 中未出现的 Codec。协商结果 = 双方 fmt 列表的交集,按 Offer 中的顺序取最优。

// 查看浏览器支持的所有 Codec
const audioCodecs = RTCRtpSender.getCapabilities("audio").codecs;
const videoCodecs = RTCRtpSender.getCapabilities("video").codecs;
console.log(videoCodecs.map((c) => `${c.mimeType} pt=${c.preferredPayloadType}`));

七、手工调试 SDP

7.1 打印与分析

const offer = await pc.createOffer();
console.log(offer.sdp);

// 按 m-line 分段
const sections = offer.sdp.split(/^m=/m);
sections.forEach((s) => console.log("--- m=" + s.slice(0, 20) + "..."));

7.2 限制带宽(调试用)

// 不推荐生产使用,优先用 setParameters
offer.sdp = offer.sdp.replace(
/(a=mid:1\r\n)/,
"$1b=AS:500\r\n" // 视频 mid=1 限制 500 kbps
);
await pc.setLocalDescription(offer);

7.3 强制 H.264(调试用)

const transceiver = pc.addTransceiver("video", { direction: "sendrecv" });
const caps = RTCRtpSender.getCapabilities("video");
const h264 = caps.codecs.filter((c) => c.mimeType === "video/H264");
transceiver.setCodecPreferences(h264);

八、常见问题

问题原因解决
有 Offer 无 Answer对端 Codec 无交集检查 fmt 列表
单向视频direction 设为 sendonly改 sendrecv
Simulcast 不生效SDP 无 rid用 Unified Plan + addTransceiver
BUNDLE 失败一端不支持 BUNDLE检查 a=group:BUNDLE
fingerprint 不匹配SDP 被中间人修改信令必须 WSS

九、实战 Lab

  1. createOffer() 后完整打印 SDP,标出每个 a= 行的含义
  2. 对比 Offer 与 Answer 的 fmt 列表差异
  3. 添加第二个 video Transceiver(屏幕共享),观察新 m-line
  4. setCodecPreferences 强制 Opus + VP8

下一篇(Ch5)ICE、STUN、TURN——SDP 中 ice-ufrag 如何驱动 NAT 穿透。


系列导航

章节主题状态
0架构全景与协议栈地图✅ 已发布
1浏览器媒体 API 与设备管理✅ 已发布
2第一个 P2P 视频通话✅ 已发布
3信令服务器设计与会话状态机✅ 已发布
4SDP 解剖与媒体协商✅ 已发布
5ICE、STUN、TURN 与 NAT 穿透✅ 已发布
6Data Channel 与 SCTP over DTLS✅ 已发布
7DTLS 握手与 SRTP 加密体系✅ 已发布
8RTP/RTCP 媒体传输与 QoS✅ 已发布
9音视频编解码与 Simulcast 入门✅ 已发布
10带宽估计与拥塞控制 GCC✅ 已发布
11Simulcast、SVC 与选择性订阅✅ 已发布
12SFU/MCU/Mesh 架构与 Pion 实战✅ 已发布
13调试工具链与可观测性✅ 已发布
14TURN 集群部署与多区域扩展✅ 已发布
15Capstone 生产级视频会议系统✅ 已发布

References

Logo
RainLib

探索技术、设计与分布式系统的边界。构建面向未来的开发者工具。

留言与建议

© 2026 RainLib. 为未来构建。(Built for the Future)
版权所有。
系统正常