JSEP 协议解读

826 阅读6分钟

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,分别为:

  • addTrack
  • setRemoteDescription
  • addTransceiver

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 为可重用状态。