JSEP 概述
JSEP 中文翻译 github.com/aggresss/sp…
WebRTC 作为 W3C 的标准,在 媒体获取和传输时主要参考:
在接口定义层面,WebRTC 的主要依据为 JSEP (JavaScript Session Establishment Protocol),所以 JSEP 对于理解 WebRTC 接口定义具有很高的参考意义。
0x01 信令模型
1.1 状态机
JSEP 对会话描述的处理简单而直接。每当需要交换 offer/answer 时,发起端通过调用 createOffer API 来创建 offer,然后应用程序使用这个 offer 通过 setLocalDescription API 来设置其本地配置。 offer 最终通过其首选的信令机制(如 websocket)发送到远端;在收到 offer 后,远端使用 setRemoteDescription API 来设置这个 offer。 为了完成 offer/answer 交换,远端使用 createAnswer API 生成适应的 answer,使用 setLocalDescription API 来应用它,并通过信令通道将 answer 发送回发起方,当发起方获得这个 answer 时,它使用 setRemoteDescription API 应用它,初始化就完成了。这个过程可以重复进行额外的 offer/answer 交换。
对应 RTCPeerConnection.signalingState,每个端点的状态包括:
- stable
- have-local-offer
- have-remote-offer
- have-local-pranswer
- have-remote-pranswer
状态机如下:
setRemote(OFFER) setLocal(PRANSWER)
/-----\ /-----\
| | | |
v | v |
+---------------+ | +---------------+ |
| |----/ | |----/
| have- | setLocal(PRANSWER) | have- |
| remote-offer |------------------- >| local-pranswer|
| | | |
| | | |
+---------------+ +---------------+
^ | |
| | setLocal(ANSWER) |
setRemote(OFFER) | |
| V setLocal(ANSWER) |
+---------------+ |
| | |
| |<---------------------------+
| stable |
| |<---------------------------+
| | |
+---------------+ setRemote(ANSWER) |
^ | |
| | setLocal(OFFER) |
setRemote(ANSWER) | |
| V |
+---------------+ +---------------+
| | | |
| have- | setRemote(PRANSWER) |have- |
| local-offer |------------------- >|remote-pranswer|
| | | |
| |----\ | |----\
+---------------+ | +---------------+ |
^ | ^ |
| | | |
-----/ -----/
setLocal(OFFER) setRemote(PRANSWER)
1.2 PeerConnection
PeerConnection 与 SDP 的 Session Level 相对应,PeerConnection 构造函数允许应用程序为媒体会话指定全局参数,例如在收集候选对象时使用的 STUN/TURN 服务器和凭证,以及初始 ICE 候选策略和池大小,以及使用的 bundle 策略,常用的属性包括:
1.3 RtpTransceiver
RtpTransceiver 与 SDP 中的 m-section 存在一一映射关系,每个 RtpTransceiver 有一个 RtpSender 和一个RtpReceiver,Offer/Answer 机制使用 SDP 作为会话描述方式的精华就是 SDP 的一个 m-section 可以描述双向媒体流,而 RtpTransceiver 就作为 m-section 在 JSEP 接口层面的映射。 RtpTransceiver 存在两个状态,关联状态 (associated) 和游离状态 (disassociated),分别对应当前 RtpTransceiver 是否与 SDP 中一个 m-section 关联,判断的依据是它的 mid 属性是否为 null。由此可以理解在 Unified Plan SDP 中,为什么 a=mid 是每个 m-section 的必须存在属性行。
有三种方式生成 RtpTransceiver,分别为:
addTracksetRemoteDescriptionaddTransceiver
PeerConnection 中 addTrack 时如果没有指定附加的 RtpTransceiver 则会自动创建;setRemoteDescription 操作时如果存在新增 remote track,则会自动创建 RtpTransceiver 。这两种方法都是隐式方式创建,JSEP 中显示创建 RtpTransceiver 的方式是 addTransceiver,手动创建 RtpTransceiver 的使用场景:
- 生成 offer 时,需要一个 direction 为
recvonly的 m-section; - 先生成 transceiver ,再将 track 附加到该 transceiver 的
RtpSender;
可以看出,手动控制 RtpTransceiver 在常规场景下并不是必须的,也可以理解 RtpTransceiver 操作是比 track 更接近底层的操作。
与 addTrack/removeTrack 接口不同,JSEP 并没有定义 removeTransceiver ,而只是在 RtpTransceiver 对象中增加了 stop 方法,这是因为 RtpTransceiver 是 SDP 中 m-section 的映射导致的,因为在 SDP 中,m-section 的索引顺序是固定的,类似于数组模式,所以 m-section 只能 append 或者 replace,而不可以 remove。
0x02 SDP
出于与历史系统兼容性的考虑,JSEP 仍然使用 SDP 作为会话描述语言。JSEP 的不同实现 (不同版本或不同类型的浏览器) 对于 JSEP 所定义接口的支持程度不同,所以有些场景需要手动修改 SDP 来满足一些特定的需求,当然修改的前提是要兼容 JSEP 对 SDP 的要求。
2.1 会话级别
会话级别需要满足以下条件:
-
① 必须以 "v="/"o="/"s="/"t=" 顺序开始,其中
-
- 必须 "v=0"
- 必须 "o= "
-
-
必须为 "-"
-
每个 PeerConnection 生命周期内唯一,如果构造 Answer 需要与对应的 Offer 保持一致,如果构造 初始化 Offer 需要生成一个 2^(63)-1 的随机数;
-
同一个 PeerConnection 内,每次构造需要 加 1
-
必须为 "IN IP4 0.0.0.0"
-
-
-
必须 "s=-"
-
必须 "t=0 0"
-
-
② 必须在 Session Level 包含 "a=msid-semantic: WMS",并且将当前会话包含的 stream 作为 tag,也可以通过 "*" 来适配所有出现的 stream;
-
③ 如果构造的 SDP 存在 m-section,并且 m-section 满足 direction 不为 inactive 并且 m-line 不为 0,则必须在 Session Level 增加 "a=group:BUNDLE",并且将满足上述条件 m-section 的 mid 属性值作为 tag,附加到 "a=group:BUNDLE" 后面。如果不存在满足条件的 m-section,不可以增加 "a=group:BUNDLE" 属性。
-
④ 如果允许 RTP Header Extension 中 1-byte 和 2-byte 类型同时存在,需要在 Session Level 包含 "a=extmap-allow-mixed"
示例:
v=0
o=- 5439927428082778422 3 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE 1 2
a=extmap-allow-mixed
a=msid-semantic: WMS *
2.2 媒体级别
媒体级别需要满足以下条件:
-
① m-section 中必须包含 "a=mid " 属性,其中:
-
-
mid 为长度不大于 8 的字符串,并且在会话周期内保持唯一;
-
answer 中的 mid 需要与对应 offer 保持一致;
-
-
② m-section 如果包含发送 track 则必须包含 "a=msid " 属性,其中:
-
-
如果 track 没有附加到具体的 MediaStream 中,需要使用 "-" 进行占位 ;
-
-
③ m-section 中必须包含 "a=" 属性,可用的 包括:
-
-
sendrecv
-
sendonly
-
recvonly
-
inactive
-
-
④ m-section 中必须包含 "a=rtcp-mux" 属性
-
⑤ 每个 m-section 必须包含连接信息,包括:
-
-
"a=ice-ufrag"
-
"a=ice-pwd"
-
"a=fingerprint"
-
"a=setup"
-
-
⑥ 每个 m-section 必须包含当前 m-section 支持的 RTP Header Extension,包括:
-
-
"a=extmap"
-
-
⑦ 每个 m-section 必须包含当前 m-section 支持的 Codec ,并且 Codec 出现的顺序代表从高到低的优先级,包括:
-
-
"a=rtpmap"
-
"a=fmtp"
-
"a=rtcp-fb"
-
-
⑧ m-section 如果包含发送 track 则需要包含 "a=ssrc" 属性,包括:
-
-
"a=ssrc cname:"
-
"a=msid "
-
"a=mslabel "
-
"a=label "
-
2.3 关于 m-section 重用
由于 SDP 中 m-section 是索引关联的,即 m-section 不可以删除但可以重用,JSEP 实现会在生成 offer 时检索可重用的 m-section 进行重用,可重用的 m-section 只要满足类型兼容 (audio/video/application) 即可重用。通常以下情况可以使 m-section 变为可重用状态:
- RtpTransceiver Close 当调用
RtpTransceiver.close()并经过一次协商后,RtpTransceiver将变为游离状态,原关联的 m-section 将变为可重用状态。 - Remote Answer Reject SDP 规定 answer 中 m-line 为 0 表示拒绝对应的 m-section ,所以当 Remote Answer 中对应的 m-section 中 m-line 等于 0 时,则认为该 m-section 为可重用状态。