TL;NR 更详细的 WebRTC SDP 解析请参考 https://tools.ietf.org/html/draft-ietf-rtcweb-sdp-14
SDP (Session Description Protocol) 格式是一种很有历史的格式,在 20 世纪的会议系统中通常都是使用 SDP 格式的文本来交互多媒体通信双方的连接属性信息和媒体属性信息,在今天 JSON 这种对象化和可拓展的格式面前确实显得不够通用,尤其是在进行 RPC 通信时通常要将 SDP 信息解析成模块化的格式,ORTC 就是基于这个出发点创建的,但是 SDP 在传统流媒体通信设备上的通用度还是比较高,熟练的理解并分析 SDP 信息对于系统功能开发和调试都是大有裨益的。本文主要通过参考 RFC8866 文档和 ORTC 思维模式来阐述一套系统化的分析 WebRTC 中 SDP 信息的方法。
WebRTC 中 Peer Connection 建立时的交互方式基于 Offer/Answer 机制,这种机制具有较高的可拓展性和兼容性。RFC3264 中描述了 SDP 在 Offer/Answer 机制中的使用方法,RFC4317 提供了一些使用案例,WebRTC 使用的 JSEP (JavaScript Session Establishment Protocol) 协议建立会话就是在以上机制的基础上进行了拓展。
SDP 会话描述由一个会话级别的部分后跟零个或多个媒体级别的部分组成。会话级部分从 “v=” 行开始,持续到第一个媒体级部分。每个媒体级部分以 “m=” 行开始,并继续到下一个媒体级部分或整个会话描述的末尾。通常,会话级值是所有媒体的默认值,除非被等效的媒体级值覆盖。
一个 SDP 会话描述由若干行文本组成形式:
<type>=<value>
其中 <type>
必须是一个区分大小写的字符,<value>
是结构化文本,其格式取决于<type>
。通常,<value>
是由单个空格字符或自由格式字符串分隔的多个字段,并且是区分大小写的,除非特定字段另有定义。"=" 符号的两边不能有空格。
对于比较复杂的行还会在空格等级的基础上继续拓展使用分号 ;
作为各个参数之间的分隔符,例如:
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
SDP 的语法规则遵循 扩展巴克斯范式 (Augmented Backus-Naur Form) RFC5234
按照分级的思路可以记忆为行等冒空分
SDP 的结构分为两个层级 Session Level
和 Media Level
,在 RFC8859 中有详细描述。
- Session Level
- Media Level
WebRTC 的 SDP 内容要求相对宽松一些,只要满足 v o s t m c b a
行即可,即 Version/Origin/Session/Timing/Media/Connection/Bandwidth/Attributes
,示例如下:
v=0
o=mozilla...THIS_IS_SDPARTA-82.0.3 485627351772987711 0 IN IP4 0.0.0.0
s=-
t=0 0
m=video 9 UDP/TLS/RTP/SAVPF 120 124 121 125 126 127 97 98
c=IN IP4 0.0.0.0
b=AS:1000
a=sendrecv
// o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
// m=<media> <port> <proto> <fmt> ...
RTP/AVP
:应用场景为视频/音频的 RTP 协议。参考 RFC3551
RTP/SAVP
:应用场景为视频/音频的 SRTP 协议。参考 RFC3711
RTP/AVPF
: 应用场景为视频/音频的 RTP 协议,支持 RTCP-based Feedback。参考 RFC4585
RTP/SAVPF
: 应用场景为视频/音频的 SRTP 协议,支持 RTCP-based Feedback。参考 RFC5124
b=AS:1000
的格式为b=<modifier>:<bandwidth-value>
AS (Application Specific Maximum);带宽数值单位为 kbps 。参考 RFC3556
随着 WebRTC 的发展 SDP 衍生出两种风格: Plan B
和 Unified Plan
。这两种风格的主要区别在于一个 media 单元中是否携带多个 track 信息。早期的 WebRTC 中一次会话描述通常只包含一个 Audio 和 一个 Video,基于这种需求衍生出来的 SDP 比较简单,即一个 Session 和两个 m-line 。但随着业务复杂度的发展可能一次会话会包含多个 Audio 和 Video,早期的解决方案是在 m-line 中增加媒体信息,也就是 Plan B
的风格,但这样使 SDP 的解析增加了难度,尤其是当多个 Audio 或 Video 在 同一个 m-line 中存在属性冲突的场景下 Plan B
风格的缺点也就彰显了出来,于是又衍生出了 Unified Plan
风格的 SDP 会话描述,将每个 media 记录到独立的 m-line 当中。
上文描述了随着 WebRTC 的发展同一个会话中出现了更多的 media,下面我们要说的是随着 Simulcast 的引入,WebRTC 中每个 media 的内容也进行了拓展。传统的基于 RTP 的会议系统中一个 media 通常只包含一对 RTP stream (即媒体 RTP stream 和 重传 RTP Stream)但是当引入 Simulcast 概念后,一个 media 中便包含了多对 RTP stream,这种拓展也同时要求我们在解析 SDP 信息时要更新观念,即一个 media 记录中会包含多个 RTP stream。
WebRTC 中 answer SDP 中 m-line 不能随意增加和删除,顺序不能随意变更,需要和 Offer SDP 中保持一致。
WebRTC 的 SDP 信息中的 a-line 承载了大多数的信息,主要包括 媒体信息 和 连接信息 :
可以方便记忆为
CHECIID
下文将基于上面的分类方式详细的描述 WebRTC 中 SDP 常见 a-line 的属性信息。
a-line 有两种组成形式:
a=<flag>
只用来标记开关类型的属性,例如 “a=recvonly”.a=<attribute>:<field1> <field2>
用来标记具有值的属性,例如 “a=orient:landscape”Attribute | Illustrate |
---|---|
a=rtpmap | a=rtpmap:108 H264/90000 |
a=fmtp | a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f |
a=rtcp-fb | a=rtcp-fb:108 goog-remb |
a=rtpmap
的 value 对应 RTP 头部的 Payload Type,长度 7 位,也就是取值范围 0-127,96-127 为自定义,通过 rtpmap 字段进行定义并通过跟随其后的 fmtp 字段来定义属性信息。举例:a=rtpmap:108 H264/90000
a=fmtp
为对应 codec 的参数信息 (Format Parameters),常见的几种 codec 的 fmtp 举例:
a=fmtp:111 minptime=10;stereo=0;useinbandfec=1
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=fmtp:120 apt=102
a=rtcp-fb
用于描述一个 Codec 支持的 RTCP Feedback 的类型,常见的有:
a=rtcp-fb:120 nack
支持 nack 重传,nack (Negative-Acknowledgment) 。a=rtcp-fb:120 nack pli
支持 nack 关键帧重传,PLI (Picture Loss Indication) 。a=rtcp-fb:120 ccm fir
支持编码层关键帧请求,CCM (Codec Control Message),FIR (Full Intra Request ),通常与 nack pli
有同样的效果,但是 nack pli
是用于重传时的关键帧请求。a=rtcp-fb:120 goog-remb
支持 REMB (Receiver Estimated Maximum Bitrate) 。a=rtcp-fb:120 transport-cc
支持 TCC (Transport Congest Control) 。Attribute | Illustrate |
---|---|
a=extmap | a=extmap:14 urn:ietf:params:rtp-hdrext:toffset |
a=extmap
描述了拓展头部 ID 与实际传输的 RTP 头部拓展内容的映射关系。Attribute | Illustrate |
---|---|
a=ssrc | a=ssrc:2430709021 cname:nYAodpnTebS8lziR |
a=ssrc-group | a=ssrc-group:FID 2430709021 3715850271 |
a=ssrc
用于描述 RTP packet 中 SSRC (Synchronization sources) 字段对应的媒体信息,既用于描述当前 media 中存在该 SSRC ,又用于描述该 SSRC 的属性信息,早期的 Chrome 产生的 SDP 中每个 SSRC 通常有 4 行,例如:
a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7
a=ssrc:3245185839 msid:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9 0cf7e597-36a2-4480-9796-69bf0955eef5
a=ssrc:3245185839 mslabel:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9
a=ssrc:3245185839 label:0cf7e597-36a2-4480-9796-69bf0955eef5
这么繁杂的信息是有历史原因的,cname 是必须的,msid/mslabel/label 这三个属性都是 WebRTC 自创的,或者说 Google 自创的,可以参考 https://tools.ietf.org/html/draft-ietf-mmusic-msid-17,理解它们三者的关系需要先了解三个概念:RTP stream
/ MediaStreamTrack
/ MediaStream
:
- 一个
a=ssrc
代表一个RTP stream
;- 一个
MediaStreamTrack
通常包含一个或多个RTP stream
,例如一个视频MediaStreamTrack
中通常包含两个RTP stream
,一个用于常规传输,一个用于 nack 重传;- 一个
MediaStream
通常包含一个或多个MediaStreamTrack
,例如 simulcast 场景下,一个MediaStream
通常会包含三个不同编码质量的MediaStreamTrack
;
上面的示例中: label
对应 MediaStreamTrack ID
,mslabel
对应 MediaStream ID
,msid
将 MediaStream ID
和 MediaStreamTrack ID
组合在一起。
这种标记方式并不被 Firefox 认可,在 Firefox 生成的 SDP 中一个 a=ssrc
通常只有一行,例如:
a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7
用于描述该 RTP stream 对应的 RTCP 协议中的 CNAME。
RTCP 为每个 RTP 用户提供了一个全局唯一的规范名称标识符 CNAME (Canonical Name) ,接收者使用它来追踪一个 RTP stream。当发现冲突或程序重新启动时,RTP 中的 SSRC (同步源标识符) 可能会发生改变,接收者可利用 CNAME 来索引 RTP stream。同时,接收者也需要利用 CNAME 在相关 RTP 连接中多个 RTP stream 之间建立联系。当 RTP 需要进行音视频同步的时候,接受者就需要使用 CNAME 来使得同一发送者的音视频数据相关联,然后根据 RTCP 包中的 NTP (Network Time Protocol) 来实现音频和视频的同步。
a=ssrc-group
定义参考 RFC5576 ,用于描述多个 ssrc 之间的关联,常见的有两种:
a=ssrc-group:FID 2430709021 3715850271
FID (Flow Identification) 最初用在 FEC 的关联中,WebRTC 中通常用于关联一组常规 RTP stream 和 重传 RTP stream 。a=ssrc-group:SIM 360918977 360918978 360918980
在 Chrome 独有的 SDP munging
风格的 simulcast 中使用,将三组编码质量由低到高的 MediaStreamTrack 关联在一起。Attribute | Illustrate |
---|---|
a=rtcp | a=rtcp:51472 IN IP4 127.0.0.1 |
a=rtcp-mux | a=rtcp-mux |
a=rtcp-rsize | a=rtcp-rsize |
a=rtcp
用于描述 RTCP 的通信地址,在 WebRTC 已经不常用到,因为 WebRTC 通常会使用 rtcp-mux
方式,也就是 RTP 和 RTCP 使用同一个连接地址。同时因为 ICE 在 WebRTC 中是强制性的,所以 a=rtcp
和 c-line 一般都不会被使用。a=rtcp-mux
在 RFC5761 中定义,用于标示当前会话将 RTP 和 RTCP 绑定在同一连接地址和端口中。a=rtcp-rsize
在 RFC5506 中定义,用于标示当前会话支持 reduced-size RTCP packets 。Attribute | Illustrate |
---|---|
a=candidate | a=candidate:0 1 UDP 2122252543 192.168.0.111 56774 typ host |
a=end-of-candidates | a=end-of-candidates |
a=candidate
用于描述 ICE 交互时的一个连接候选者的参数,需要注意 UDP 类型和 TCP 类型的 candidate 定义上是有区别的。
UDP
<foundation> <component-id> <transport> <priority> <conn-addr> <conn-port> <cand-type> <candidate-types>
a=candidate:1 1 UDP 2130706431 fe80::6676:baff:fe9c:ee4a 8998 typ host
TCP
<foundation> <component-id> <transport> <priority> <conn-addr> <conn-port> <cand-type> <candidate-types> <tcp-type-ext> <tcp-type>
a=candidate:2 1 TCP 2124414975 192.0.2.1 3478 typ host tcptype passive
component-id 1:RTP 2:RTCP
ICE Candidate 的 4 种类型 https://tools.ietf.org/html/draft-ietf-mmusic-ice-sip-sdp-39
- host 该 candidate 是一个真实的主机,参数中的地址和端口对应一个真实的主机地址
- srflx (server reflexive) 该 candidate 是通过 Cone NAT 反射的类型,参数中的地址和端口是端发送 Binding 请求到 STUN/TURN server 经过 NAT 时,NAT 上分配的地址和端口
- prflx (peer reflexive) 该 candidate 是通过 Symmetric NAT 反射的类型,参数中的地址和端口是端发送 Binding 请求到 STUN/TURN server 经过 NAT 时,NAT 上分配的地址和端口
- relay 该 candidate 是通过 TURN 服务中继的类型,参数中的地址和端口是 TURN 服务用于在两个对等点之间转发数据的地址和端口
priority 表示优先级,数值越大优先级越高
a=end-of-candidates
在 Trickle ICE
模式时,用于显示声明 candidate 信息的结束,在 https://tools.ietf.org/html/draft-ietf-mmusic-trickle-ice-sip-18 中定义。
Attribute | Illustrate |
---|---|
a=ice-options | a=ice-options:trickle |
a=ice-lite | a=ice-lite |
a=ice-ufrag | a=ice-ufrag:3e1029a1 |
a=ice-pwd | a=ice-pwd:143a54b37278247c66e93088f767c62d |
a=ice-options
用于描述 ICE 连接的属性信息,ice-options 的定义有很多种,WebRTC 中常见的有:
a=ice-options:trickle
client 一边收集 candidate 一边发送给对端并开始连通性检查,可以缩短 ICE 建立连接的时间。a=ice-options:renomination
允许 ICE controlling 一方动态重新提名新的 candidate ,默认情况 Offer 一方为controlling 角色,answer 一方为 controlled 角色;同时 Lite 一方只能为 controlled 角色。a=ice-lite
用于描述使用 ICE lite 实现。
ICE 的实现分为
Full ICE
和Lite ICE
:
Full ICE
参与连接的两端都要进行连通性检查。
Lite ICE
在 FULL ICE 和 Lite ICE 连接时,只需要 Full ICE 一方进行连通性检查, Lite ICE 一方只需回应 Response 消息。这种模式对于部署在公网的设备比较常用。
a=ice-ufrag
(ICE Username Fragment),描述当前 ICE 连接临时凭证的用户名部分。a=ice-pwd
(ICE Password),描述当前 ICE 连接临时凭证的密码部分。Attribute | Illustrate |
---|---|
a=setup | a=setup:actpass |
a=fingerprint | a=fingerprint:sha-256 9D:58:00:00:00:00:FF:FF:00:00:00:FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00:00:00:00:00:00 |
a=setup
合法值包括 actpass
/active
/passive
。在 WebRTC 中 DTLS 主要是为了交换 SRTP 的密钥,定义参考 RFC 5763 。一次 DTLS 通信的角色通常需要协商指定,通常发起 Offer 一方都会设置为 actpass
,即由对方来定,这时 Answer 回复 active
或者 passive
即完成了角色的协商,当然如果 Offer 一方指定了 active
或者 passive
,Answer 一方就只能选择剩下的那个角色了。a=fingerprint
DTLS 通信开始前上方都需要校验证书是否被篡改,检验的依据就是协商阶段的证书指纹信息。常见的指纹校验算法有:sha-1
/sha-224
/sha-256
/sha-384
/sha-512
Attribute | Illustrate |
---|---|
a=sendrecv/sendonly/recvonly/inactive | a=sendrecv |
a=group | a=group:BUNDLE 0 1 |
a=mid | a=mid:0 |
a=rid | a=rid:1 send pt=98;max-width=1280;max-height=720 |
a=simulcast | a=simulcast:send hi;mid;lo |
a=bundle-only | a=bundle-only |
a=msid-semantic | a=msid-semantic:WMS * |
a=msid | a=msid:ARDAMS ARDAMSa0 |
a=sendrecv/sendonly/recvonly/inactive
用于描述当前 m-line 媒体的流动方向。a=group
属于会话级别的参数,用于描述将当前会话中的多个媒体绑定在一个连接中,以 mid 作为描述对象。参考 https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54a=mid
用于标识 Media ID , 定义参考 RFC5888a=rid
用于标识每个 RTP Stream ,定义参考 https://tools.ietf.org/html/draft-ietf-mmusic-rid-15a=simulcast
这是 Firefox 风格的 simulcast 喜欢的开启 simulcast 的模式,需要 RTP Header Extension 同时开启 rid 支持,具体参考 https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-14a=bundle-only
用于 Answer 时 显式声明为 BUNDLE 传输,定义参考 https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54a=msid-semantic
显示声明当前会话的 msid,在多个 Media Stream 的场景中通常被置为 *
,WMS (WebRTC Media Stream) ,定义参考 https://datatracker.ietf.org/doc/html/rfc8830a=msid
用于标识当前 m-line
作用域所属的 MediaStrteam ID,定义参考 [https://datatracker.ietf.org/doc/html/rfc8830]