# WebRTC CreateOffer 分析
## 1. CreateOffer 的目的
>   WebRTC 主要用于 peer 之间音视频通讯,而通讯前需要协商一些参数,比如编解码器、传输协议等。
> 所以CreareOffer 的目的就在于搜集本地相关参数,用于初始化一次 session.
## 2. CreateOffer 主要收集了哪些信息
>   a. 要传输什么:即媒体相关信息,主要是 audio、video、data,以及相应支持的参数;
><br/>
>   b. 怎么传输:即传输地址、策略、协议等;
## 3. Offer 如何交换
>   WebRTC 中 Offer 及与之对应的 Answer 都通过信令服务器使用 SDP 交换。
<br/>
>   SDP:Session Description Protocol 会话描述协议,广泛用于多媒体会话中会话信息的描述,它定义了会话描述的规范、语义,并不限制传输层协议。
><br/>
>   由于 SDP 协议需要支持各种异构的终端(如PC、电话等)、异构的网络、异构的系统等,所以其使用场景和交互定义相当复杂,建议先了解其基础规范:[RFC4566](https://tools.ietf.org/html/rfc4566)
>
><br/>
>
>  附:感谢 lichao2 提供 WebRTC 中,各场景下 SDP 交互示例:[WebRTC中的SDP](https://www.ietf.org/archive/id/draft-nandakumar-rtcweb-sdp-08.txt)
>
## 4. Offer 长啥样
>   由于 SDP 是一个纯文本协议,且以行为组织单位,所以一个 Offer 其实就是一个多行字符串。
><br/>
>   一个包含 audio、video、data 的 Offer 示例:
```
v=0
o=- 139014573241466234 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1 2
a=msid-semantic: WMS stream_id
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:Ysdw
a=ice-pwd:R2xJhQfg5pS3D+zt9g/IvB1V
a=ice-options:trickle
a=fingerprint:sha-256 81:81:2F:DE:C5:FD:42:FB:DD:A8:3C:05:94:60:56:A2:D6:BC:72:59:39:C9:27:6E:43:B6:12:92:F9:1E:19:1B
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:13 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:14 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:stream_id audio_label
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:102 ILBC/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:2221963028 cname:jmpt/WDLtG9Ys2lu
a=ssrc:2221963028 msid:stream_id audio_label
a=ssrc:2221963028 mslabel:stream_id
a=ssrc:2221963028 label:audio_label
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 124 125
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:Ysdw
a=ice-pwd:R2xJhQfg5pS3D+zt9g/IvB1V
a=ice-options:trickle
a=fingerprint:sha-256 81:81:2F:DE:C5:FD:42:FB:DD:A8:3C:05:94:60:56:A2:D6:BC:72:59:39:C9:27:6E:43:B6:12:92:F9:1E:19:1B
a=setup:actpass
a=mid:1
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:13 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:14 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:stream_id video_label
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
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 red/90000
a=rtpmap:124 rtx/90000
a=fmtp:124 apt=127
a=rtpmap:125 ulpfec/90000
a=ssrc-group:FID 8456514 3575385009
a=ssrc:8456514 cname:jmpt/WDLtG9Ys2lu
a=ssrc:8456514 msid:stream_id video_label
a=ssrc:8456514 mslabel:stream_id
a=ssrc:8456514 label:video_label
a=ssrc:3575385009 cname:jmpt/WDLtG9Ys2lu
a=ssrc:3575385009 msid:stream_id video_label
a=ssrc:3575385009 mslabel:stream_id
a=ssrc:3575385009 label:video_label
m=application 9 DTLS/SCTP 5000
c=IN IP4 0.0.0.0
a=ice-ufrag:Ysdw
a=ice-pwd:R2xJhQfg5pS3D+zt9g/IvB1V
a=ice-options:trickle
a=fingerprint:sha-256 81:81:2F:DE:C5:FD:42:FB:DD:A8:3C:05:94:60:56:A2:D6:BC:72:59:39:C9:27:6E:43:B6:12:92:F9:1E:19:1B
a=setup:actpass
a=mid:2
a=sctpmap:5000 webrtc-datachannel 1024
```
## 5. Offer 中各项是啥意思
>   通过对 SDP 规范([RFC4566](https://tools.ietf.org/html/rfc4566))的了解,我们认识到 Offer/Answer 主要包含各种属性描述信息,整个描述主要由全局属性(sesssion level)和一到多个 media section(m= lines)组成,media section 内也包含对该类 media 的属性描述(a= lines),media section 内的属性(media level)可覆盖 session level 的属性,但仅对本 media section 生效。
><br/>
>  在我们的使用场景中,主要是使用 DataChannel 通讯,不包含 audio、video部分,所以以下以仅包含 data channel 的Offer 为例,说明其中各项的含义。
><br/>
>   仅包含 data 的 Offer 示例:
```
// 协议版本,RFC4566 规定一直为 0
v=0
// 会话发起方(origin)描述
// 语法: o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
// username 如果没有使用 - 代替
// 如果在会话过程中有改变编码之类的操作,需重新生成 SDP 时,sess-id 不变,sess-version 加 1
// WebRTC 中实现为,每次生成 SDP 都将 sess-version 加 1
o=- 370369590685861625 2 IN IP4 127.0.0.1
// 会话名(session name),没有就用 - 代替
s=-
// 会话时间
// 语法: t=<start-time> <stop-time>
// 0 代表无限制
t=0 0
// 组属性
// 语法:a=group:<semantics> <semantics-extension>
// group:BUNDLE 用于将多个 media 捆绑到一个传输通道,后面的 0 为 mid(media id)
a=group:BUNDLE 0
// msid 语义描述
// 语法:a=msid-semantic: <msid>
// 在 WebRTC 中使用 WMS(WebRTC Media Stream) + msid,
// msid 语义主要用于指定由 msid 标识的 media stream 与 RTC SSRC 之间的关联.
// 比如发送方需要发送两个 stream(一个来自麦克风的 audio stream 和一个来自摄像头的 video stream),
// 对这两个 stream,RTP session 中将有两个 SSRC 与之对应,在 SDP 中也会有两个 m=line 与之对应,
// msid 机制提供能力,将属于同一个 MediaStream 对象的这些 stream 设置为 1 组,让接收方认为这是同一个流既有音频又有视频,而不是分开的一路音频流和一路视频流
// 由于我们的使用场景没有 media stream,所以这里没有 msid, 可忽略
a=msid-semantic: WMS
// 以下是 SDP 中 media section
// media 描述
// 语法:m=<media> <port> <proto> <fmt> ...
// RFC4566 定义的 media 有:"audio", "video", "text", "application", and "message"
// 我们的 DataChannel 属于 application
// 紧接着是传输端口、协议、媒体格式,媒体格式的含义由媒体类型决定
// 我们这里的 5000 标识的是 SCTP 使用的默认端口
// 传输端口为什么是 9 ?后续有解析
m=application 9 DTLS/SCTP 5000
// 以下是该 section 内的各属性描述
// 连接属性
// 语法:c=<network-type> <address-type> <connection-address>
// 0.0.0.0 标识地址暂时不可知
c=IN IP4 0.0.0.0
// 属性
// 语法:a=<attribute>:<value>
// ICE 服务器设置的用户名,若没有,可随机生成
a=ice-ufrag:2LMP
// ICE 密码
a=ice-pwd:XgcWowAzvUI2G501/6aTHRXi
// ICE 扩展选项
// trickle 标识使用 "循序渐进"的 ICE,即边收集候选项边做连通性测试
a=ice-options:trickle
// DTLS 相关的指纹信息
a=fingerprint:sha-256 14:D9:36:A2:83:4A:24:02:9B:65:B0:6B:15:BE:A3:29:9E:38:78:EF:43:BE:DA:27:44:A3:3B:53:F7:96:D4:22
// 本端角色
// actpass = active + passive,标识既能发起会话,也能响应会话
a=setup:actpass
// media id,由发起方随机分配,与 group:BUNDLE 中 mid 对应
a=mid:0
// sctp 相关参数列表
// 语法:a=sctpmap:sctpmap-number protocol [streams]
// 1024 代表最大流个数
a=sctpmap:5000 webrtc-datachannel 1024
```
>  注:a. 上述释义为参阅 rfc 或 webRTC 源码后的个人理解,若有不妥之处,欢迎及时指出;
>
>      b. group:BUNDLE 属性可参阅:[SDP BUNDLE协商](https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54 )
>
>      c. msid 属性可参阅:[WebRTC MSID机制](https://tools.ietf.org/html/draft-ietf-mmusic-msid-16#page-10)
>
>      d. Trickle ICE 可参阅:[Trickle ICE](https://tools.ietf.org/html/draft-ietf-ice-trickle-05)
>
>      e. ice 相关参数以及 SRTP 指纹信息等可参阅:[RFC5245](https://tools.ietf.org/html/rfc5245)
>      f. sctp 相关参数描述可参阅:[SCTP in SDP](https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-04)
>
>      g. 后续 ICE 过程中,还会通过 SDP 交换 Candidate 信息,此处略
>
## 6. Offer 的创建过程
>  WebRTC 里面创建 Offer 的过程写的有点绕,所以以下以 Demo 中调用链为线索,记录该过程中的核心类和逻辑,以备后忘:
>
><br/>
>  a. Offer 的创建由 PeerConnection -> CreateOffer 完成, PeerConnection 由 PeerConnectionFactory 创建并初始化,特别的,要在创建 Offer 之前创建 DataChannel;
>
><br/>
><br/>
>  b. DataChannel 的类型由创建 PeerConnection 时的 RTCConfiguration 相关参数指定,Demo 中主要有 RTP、SCTP 两种,RTP 已被明确表示不支持,SCTP 必须同时使用 DTLS,至于怎么去掉这个 DTLS ,还在研究中;
>
><br/>
><br/>
>  c. PeerConnection::CreateOffer 校验相关选项和处理 Plan B 相关参数后 ,开始 GetOptionsForOffer,即根据配置组织成创建 Offer 需要的全部选项信息 struct cricket::MediaSessionOptions;
>
><br/>
><br/>
>  d. MediaSessionOptions 含有生成 Offer 的所有信息,与 Offer 对应,其中 std::vector<MediaDescriptionOptions> 描述了各 media 的信息,与 Offer 中的 m= lines 对应;
>
><br/>
><br/>
>  e. 进入 WebRtcSessionDescriptionFactory::CreateOffer,生成 CreateSessionDescriptionRequest 后开始真正的创建过程,由于 Offer 中需要用到 DTLS 相关的加密属性,所以如果此时证书尚未提供或生成,会去异步的生成证书,然后回调;
>
><br/>
><br/>
>  f. WebRtcSessionDescriptionFactory::InternalCreateOffer -> MediaSessionDescriptionFactory::CreateOffer 开始真正的创建:
><br/>
>    f.1 先获取当前 current_description 的内容、参数等,初始创建时,此为空;
><br/>
>    f.2 GetCodecsForOffer 获取本地支持的编解码参数,并根据选项做些过滤,体现为 Offer 中的 VP8、OPUS 等;
><br/>
>    f.3 GetRtpHdrExtsToOffer 获取 RTP 扩展头属性,体现为 Offer 中的 a=extmap等;
><br/>
>    f.4 合并在 current_description 中已存在的 media description,并通过 MediaSessionDescriptionFactory::AddXXXContentForOffer 添加到最终 Offer 中;
><br/>
>    f.5 我们的场景中只有 data media,所以这里只关心 MediaSessionDescriptionFactory::AddDataContentForOffer;
><br/>
>    f.6 MediaSessionDescriptionFactory::AddDataContentForOffer 主要完成两件事,第一通过 CreateMediaContentOffer 创建 DataContentDescription, DataContentDescription 是与 MediaDescriptionOptions 同级的概念,对应于 Offer 中 m=lines,第二使用 AddTransportOffer 生成与该 mid 相关的传输方面属性描述;
><br/>
>    f.7 CreateMediaContentOffer 使用 AddStreamParams 创建该 media stream 与传输相关的属性,体现在 Offer 中的 a=ssrc:8456514 msid:stream_id video_label 等,主要是指定 RTP 传输音视频数据时的映射关系等,由于我们的 DataChannel 使用 SCTP,所以没有相关内容;
><br/>
>    f.8 CreateMediaContentOffer 使用 AddSimulcastToMediaDescription 指定联播(Simulcast)相关信息,Simulcast 是将一路流编码为不同格式、不同分辨率等场景,该场景下使用 rid 描述同一 stream 的多种不同格式传输,我们不使用 rid,可忽略;
><br/>
>    f.9 MediaSessionDescriptionFactory::AddTransportOffer -> TransportDescriptionFactory::CreateOffer 创建该 mid 的传输相关信息 struct TransportDescription,体现在 Offer 中的 a=ice-ufrag、a=ice-pwd等;
><br/>
><br/>
>  g. MediaSessionDescriptionFactory::AddDataContentForOffer 之后回到 MediaSessionDescriptionFactory::CreateOffer,开始处理绑定组(a=group:BUNDLE)相关信息,绑定完之后,更新本组所有 media 的传输信息和加密信息为组内第一条 media 使用的信息;
><br/>
><br/>
>  h. MediaSessionDescriptionFactory::CreateOffer 根据创建 PeerConnection 时指定的 SDP 语义(Plan B or Unified)来设置 msid 标志(msid_signaling_),由于只影响 RTP,暂时忽略;
><br/>
><br/>
>  i. MediaSessionDescriptionFactory::CreateOffer 完成,回到 MediaSessionDescriptionFactory::CreateOffer, 将 o= 中的 session_ver 自增,并从原 local_description 中复制 Candidate,体现在 Offer 中的 Candidate 信息,由于此时 local_description 为空,且尚未开始 candidate 收集,所以 Offer 中没有 Candidate;
><br/>
><br/>
>  j. 至此,一个完整的 Offer 创建完成,通过 WebRtcSessionDescriptionFactory::OnMessage 回调到上层,如果成功,回调 Conductor::OnSuccess,失败回调 Conductor::OnFailure;
><br/>
><br/>
>  k. Conductor::OnSuccess 将生成的 Offer (class webrtc::JsepSessionDescription) 序列化并组织成自定义的协议格式,通过信令服务发给 remote peer;
><br/>
><br/>
>  l. 创建 Offer 成功后会通过 PeerConnection::SetLocalDescription --> JsepTransportController::MaybeStartGathering 开始搜集 candidate,以及 ICE 打洞,由于 Offer 创建时,尚未有这些信息,所有 Offer 中部分传输相关的属性其实是“占位符”,如 m=application 9、c=IN IP4 0.0.0.0 中的端口信息 9 和 ip 地址 0.0.0.0。
><br/>
><br/>
>  注:remote peer 收到 Offer 后会开始 PeerConnection::CreateAnswer,其流程与 CreateOffer 一致。
## 7. Source、Track、Stream的关系:
>  WebRTC 中对 media 数据的管理有几个概念,分别是 Source、Track、Stream,理解清楚他们的关系有利于理解代码逻辑:
><br/>
>  Source:media 数据的来源,比如麦克风、摄像头;
><br/>
>  Track:Captuer 从 Source 获取数据并编码后形成 Track,由于可以多种编码格式、采样率等,所以一个Source 可能被多个 Track 使用;
><br/>
>  Stream:一个或多个 Track 被逻辑上视为一个 Stream;
><br/>
>  在 WebRTC 中,一个 Stream对应一个 msid,一个 Stream 下的一个 Track 对应 RTP 中的一个 ssrc。
><br/>
><br/>
>  **注意:以上纯属对代码阅读的理解,如有不实之处,恳请立马纠正。**
## 8. 其他
>  1. SCTP 是一个传输层协议,整合了Udp、Tcp的优点,可以看成是 Tcp 的增强版,但由于相对 Tcp 较年轻,并不是所有的系统协议栈都支持(Linux 在 2.6 之后才支持),所以 WebRTC 中 DataChannel 使用的 SCTP 是一个用户态的 SCTP(实现在 third_party 中),且使用 UDP + TLS 传输,SCTP 详细可参考:[RFC4960](https://tools.ietf.org/html/rfc4960);
<br/>
>  2. 一个介绍 RTP 的不错的 PPT: [RTP简介](https://wenku.baidu.com/view/7209e10e844769eae009edd8.html?from=related&hasrec=1);
><br/>
>  3. WebRTC 中常用的 SDP 属性含义:[WebRTC 常用 SDP 字段](https://blog.youkuaiyun.com/chinabinlang/article/details/79151589) 以及 SDP 中常用字段 rfc 索引:[SDP 常用字段及 RFC](https://blog.youkuaiyun.com/myiloveuuu/article/details/78998183)