javascript
[RFC8829] JavaScript Session Establishment Protocol (JSEP)
JavaScript Session Establishment Protocol (JSEP)
原文 https://www.rfc-editor.org/rfc/rfc8829.html
摘要
本文檔描述了 JavaScript 應用程序通過 W3C RTCPeerConnection API 指定的接口來控制多媒體會話信令的機制并討論與現有信令協議的關系。
1. 介紹
本文檔介紹了通過 WebRTC 中 RTCPeerConnection 接口控制多媒體會話建立、管理和關閉的方法。
1.1. JSEP 總體設計
WebRTC 呼叫建立的設計重點關注于媒體層面,而信令層面的行為則盡可能的留給應用程序。其根本原因是不同的應用程序在信令層會使用不同的協議,例如現存的 SIP 呼叫協議或者為特定應用程序定制化的協議(可能是一個新的用例)。在這種實現中,需要交換的關鍵信息是媒體描述,它指定了傳輸參數和媒體配置信息。
考慮到這些因素,本文檔描述了 JavaScript 會話建立協議(JSEP),它允許通過 JavaScript 完全控制信令狀態機。如上所述,JSEP 假設存在一個模型,在這個模型中,JavaScript 應用程序在包含 WebRTC API 的運行時中執行 “JSEP 實現”。JSEP 的實現幾乎完全脫離了核心信令流,它由 JavaScript 使用兩個接口來處理:
在本文檔中描述了JSEP的使用默認為發生在兩個 JSEP 端點之間。但請注意,在許多情況下它實際上是在 JSEP 端點和某些類型的服務器(如網關或多點控制單元(MCU))之間。這種區別對于JSEP 端點是透明的;它只是遵循通過 API 給出的指令。
JSEP 對會話描述的處理簡單而直接。每當需要交換 offer/answer 時,發起端通過調用 createOffer API 來創建 offer,然后應用程序使用這個 offer 通過 setLocalDescription API 來設置其本地配置。 offer 最終通過其首選的信令機制(如 websocket)發送到遠端;在收到 offer 后,遠端使用 setRemoteDescription API 來設置這個 offer。
為了完成 offer/answer 交換,遠端使用 createAnswer API 生成適應的應答,使用 setLocalDescription API 來應用它,并通過信令通道將應答發送回發起方,當發起方獲得這個應答時,它使用 setRemoteDescription API 應用它,初始化就完成了。這個過程可以重復進行額外的 offer/answer 交換。
對于 ICE,JSEP 將 ICE 狀態機從整個信令狀態機中解耦出來。ICE 狀態機必須保留在 JSEP 實現中,因為只是實現具有候選對象和其他傳輸信息的必要信息。通過這種分離增加了協議的靈活性,可以將會話描述從傳輸中解耦。例如,在傳統的 SIP 中,每個 offer 和 answer 都是自包含的,包括會話描述和傳輸參數。然而,[RFC8840] 允許 SIP 與 Trickle ICE 一起使用,其中會話描述可以立即發送,傳輸參數可以在可用時發送。單獨發送傳輸參數可以更快地啟動 ICE 和 DTLS,因為 ICE 檢查可以在任一傳輸信息可用時立即啟動而不是等待所有的傳輸參數。JSEP 對 ICE 和信令狀態機的解耦使其能夠適應以上兩種類型。
盡管 JSEP 抽象了信令,但它要求應用程序知道信令的處理過程。雖然應用程序不需要理解會話描述的內容,但應用程序必須在正確的時機掉用正確的 API,將會話描述和 ICE 信息轉換為其信令協議所定義的消息,并對它從另一端接收到的消息執行解析。
簡化應用程序的一種方法是提供一個 JavaScript 庫,向開發人員隱藏這種復雜性;該庫將實現信令協議及其狀態機并且完成代碼序列化,從而為應用程序開發人員提供更高級別的面相調用的接口。例如,庫可以在 JSEP API 之上提供 SIP 和 XMPP 信令協議的支持。因此,JSEP 為有經驗的開發人員提供了更大的控制,而不會給新手帶來任何額外的復雜性。
1.2. 其他方法考量
一種替代 JSEP 的方法是包含一個輕量級的信令協議。API 將產生并使用來自該協議的消息,而不是向 API 提供會話描述。這樣雖然提供了一個更高級的API,但在 JSEP 實現中增加了對信令的更多控制,迫使它必須理解和處理各種異常狀態(參見 [RFC3264],第4節)。
第二種考慮但沒有選擇的方法是將媒體控制對象的管理與會話描述分離,取而代之的是提供可以直接控制每個組件的 API。這一提議被否決了,理由是要求應用程序開發人員暴露這種級別的復雜性對他們沒有好處:
JSEP 的一種變體是保留基本的面向會話描述的 API,但將生成 offer 和 answer 的機制移出 JSEP 實現。該方法將開放 getCapabilities API,而不提供 createOffer/createAnswer 方法,getCapabilities API 將向應用程序提供生成自己的會話描述所需的信息。這增加了應用程序的工作量;它需要知道如何從功能集生成會話描述,特別是如何從任意 offer 和支持的功能集生成正確的 answer。雖然這可以通過使用類似于上面提到的庫來解決,但它基本上迫使我們使用該庫,即使是一個簡單的例子。提供 createOffer/createAnswer 可以避免這個問題。
1.3. 關于 “bundle-only” 和 “m=” 的矛盾
自從 WebRTC 規范文檔被批準以來,IETF 已經意識到指定 JSEP 的文檔和指定 BUNDLE 的文檔(該 RFC 和 [RFC8843])之間的不一致。這些文檔不是為了達成一項決議而進一步推遲公布,而是按照最初批準的方式予以公布。IETF 打算重新啟動這些工作,一旦有了解決方案,這些文檔的修訂版將會發布。
具體的問題涉及到 m-section 被指定為 “bundle-only”,將在本 RFC 4.1.1 節討論。目前,JSEP 和 BUNDLE 之間存在分歧,這些規范和現有的瀏覽器實現之間也存在分歧:
- JSEP 規定,m-section 應該使用端口 0,并在初始 offer 中添加 “a=bundle-only” 屬性,而不是在 answer 或后續 offer 中;
- BUNDLE 規定,m-section 應該像前面所描述的那樣標記,但是是在所有的 offer 和 answer 中。
- 當前大多數瀏覽器不標記任何端口為 0 的 m-section,而是為所有捆綁的 m-section 使用相同的端口;其他則遵循 JSEP 定義。
2. 術語
本文檔中的關鍵詞“必須”、“不得”、“必需”、“應”、“不應”、“建議”、“不建議”、“可”和“可選”在所有大寫字母出現時(如圖所示)應按照 BCP 14[RFC2119] [RFC8174] 所述進行解釋。
3. 語義和語法
3.1. 信令模型
JSEP 沒有指定特定的信令模型或狀態機,除了一般需要以 [RFC3264] (offer/answer)描述的方式交換會話描述,以便會話雙方知道如何進行會話。JSEP 提供了創建 offer 和 answer 以及將它們應用到會話的機制。然而,JSEP 實現完全與實際機制解耦,這些 offer 和 answer 通過這種機制傳遞到遠程端,包括尋址、重傳、分叉和沖突處理。這些問題完全取決于應用程序;應用程序可以完全控制哪些 offer 和 answer 將提交給實現,以及何時提交。
+-----------+ +-----------+| Web App |<--- App-Specific Signaling -->| Web App |+-----------+ +-----------+^ ^| SDP | SDPV V+-----------+ +-----------+| JSEP |<----------- Media ------------>| JSEP || Impl. | | Impl. |+-----------+ +-----------+圖1:JSEP 信令模型
3.2. 會話描述和狀態機
為了建立媒體交互,JSEP 實現需要特定的參數來指示要向遠端發送什么,以及如何處理接收到的媒體。這些參數是由 offer 和 answer 中會話描述的交換決定的,并且這個過程的某些細節必須在 JSEP API 中處理。
會話描述是否適用于本地或遠端將影響該描述的含義。例如,發送給遠端的編解碼列表表明了本地方愿意接收的內容,當與遠端支持的編解碼集相交叉時,該列表指定了遠程方應該發送的內容。然而,并不是所有的參數都遵循這一規則;有些參數是聲明性的,遠端必須接受或完全拒絕它們。這種參數的一個例子是 TLS 指紋 [RFC8122],在 DTLS [RFC6347] 的上下文中使用;這些指紋是根據提供的本地證書計算的,不受協商的影響。
此外,不同的 RFC 對 offer 和 answer 的格式提出了不同的條件。例如,offer 可以提出任意數量的 m-section(即,媒體描述如 [RFC4566],5.14 節所述),但 answer 必須包含與要約完全相同的數字。
最后,雖然確切的媒體參數只有在一個 offer 和一個 answer 交換之后才知道,但 offer 方可能會在收到 answer 之前收到 ICE 檢查和可能的媒體(例如,在一個連接建立后的重新 offer)。在這種情況下,為了正確處理傳入的媒體,offer 方的媒體處理程序必須在 answer 到達之前了解 offer 的細節。
因此,為了正確處理會話描述,JSEP 實現需要:
JSEP 通過添加 setLocalDescription 和 setRemoteDescription 方法來解決這個問題,并讓會話描述對象包含一個類型字段,用來指示所提供的會話描述的類型。這滿足了上面列出的要求,發起者首先調用 setLocalDescription(sdp [offer]),然后再調用 setRemoteDescription(sdp [answer])。對于應答者,首先調用 setRemoteDescription(sdp [offer]),然后再調用 setLocalDescription(sdp [answer])。
在交換 offer/answer 的過程中,未完成的 offer 在發起方和應答方看來是 “未決的”,因為它可能被接受或拒絕。如果這是一個重新 offer,每一方還將有“當前”的本地和遠程描述,這反映了最后的 offer/answer 交換的結果。章節 4.1.14、4.1.16、4.1.13 和4.1.15 提供了關于待定和當前描述的更多細節。
JSEP 還允許應用程序將 answer 視為臨時 answer。臨時 answer 為應答方提供了一種將初始會話參數反饋給發起者的方法,以允許會話開始,同時允許稍后指定最終 answer。最終 answer 的概念對于 offer/answer 模式非常重要;當接收到這樣一個 answer 時,調用者分配的任何額外資源都可以被釋放,并且確切的會話配置已經知道了。這些“資源”可以包括額外的 ICE 組件、候選 TURN 或視頻解碼集。另一方面,臨時的 answer 沒有這樣的重新分配;因此,多個不同的臨時 answer,有自己的編解碼選擇,傳輸參數等,可以在呼叫設置期間接收和應用。請注意,最終 answer 本身可能與任何收到的 answer 不同。
在 [RFC3264] 中,信令級別的約束是一個給定的會話只能有一個 offer,但是在 JSEP 級別,一個新的 offer 可以在任何點生成。例如,當使用 SIP 信令時,如果發送了一個 offer,然后使用 SIP CANCEL 取消,則可以生成另一個 offer,即使沒有收到第一個 offer 的答復。為了支持這一點,當 JavaScript 應用程序需要一個 offer 作為信令時,JSEP 媒體層可以通過 createOffer 方法提供一個 offer。應答者可以返回零個或多個臨時 answer,然后通過發送一個最終 answer 來結束 offer/answer 交換。這個的狀態機如下:
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)圖2:JSEP 狀態機
除了這些狀態轉換,處理 臨時 answer 和最終 answer 沒有區別。
3.3. 會話描述格式
JSEP 的會話描述使用會話描述協議(session Description Protocol, SDP)語法進行內部表示。雖然這種格式對于 JavaScript 操作不是最佳的,但它被廣泛接受并經常更新新特性;任何會話描述的替代表示都必須與 SDP 的變化保持同步,至少在這種新的表示取代 SDP 流行之前是如此。
然而,為了提供未來的靈活性,SDP 語法被封裝在 SessionDescription 對象中,該對象可以從 SDP 構造并序列化到 SDP。如果未來的規范對會話描述采用 JSON 格式達成一致,我們可以很容易地讓這個對象生成并使用 JSON。
如下所述,大多數應用程序應該能夠將這些各種 API 調用產生和使用的 sessiondescription 視為不透明的 blob;也就是說,應用程序不需要讀取或更改它們。
3.4. 會話描述控制
為了讓應用程序控制各種公共會話參數,JSEP 提供了控制面,告訴 JSEP 實現如何生成會話描述。在大多數情況下,這避免了 JavaScript 修改會話描述的需要。
對這些對象的更改將導致對后續 createOffer/createAnswer 調用生成的會話描述的更改。
3.4.1. RtpTransceivers
RtpTransceivers 允許應用程序控制與一個 m-section 相關聯的 RTP media。每個 RtpTransceiver 有一個 RtpSender 和 一個RtpReceiver,應用程序可以使用它們來控制 RTP media 的發送和接收。應用程序也可以直接修改 RtpTransceiver,例如停止操作。
RtpTransceivers 通常與 m-section 是 1:1 的映射,盡管可能會有比 m-section 更多的 RtpTransceivers ,例如:當RtpTransceivers 被創建但還沒有關聯到 m-section,或者如果 RtpTransceivers 已經停止并從 m-section 中分離。如果 RtpTransceiver 的 mid (media identification) 屬性是非空的,則 RtpTransceiver 被認為與 m-section 相關聯;否則它被認為是游離的。關聯的 m-section 是在創建一個 offer 或應用一個遠程 offer 時使用收發器和 m-section 索引之間的映射確定的。
一個 RtpTransceiver 從來不會與一個以上的 m-section 相關聯,并且一旦會話描述被應用,一個 m-section 總是與一個 RtpTransceiver 相關聯。然而,在某些情況下,當一個 m-section 被拒絕時,正如下面 5.2.2 節中討論的那樣,m-section 將被“回收”,RtpTransceiver 將與一個新的 MID 值相關聯。
RtpTransceivers 可以由應用程序顯式創建,也可以通過調用 setRemoteDescription 來隱式創建,并提供一個新的 m-section。
3.4.2. RtpSenders
RtpSenders 允許應用程序控制 RTP 媒體的發送方式。RtpSender 在概念上負責由 m-section 描述輸出的 RTP 流。這包括編碼附加的 MediaStreamTrack,發送 RTP 媒體包,以及關聯的 RTCP。
3.4.3. RtpReceivers
RtpReceivers 允許應用程序檢查如何接收 RTP 媒體。RtpReceiver 在概念上負責傳入的 RTP 流,由 m-section 描述。這包括處理接收到的 RTP 媒體包,解碼傳入的流以產生遠程 MediaStreamTrack,并為傳入的 RTP 流生成和處理 RTCP。
3.5. ICE
3.5.1. ICE Gathering 概述
JSEP 根據應用程序的需要收集 ICE candidates。ICE candidates 的收集被稱為收集階段,這是由添加一個新的或回收的 m-section 到本地會話描述或描述中新的 ICE 憑證觸發的,表明 ICE 重啟。新 ICE 憑據的使用可以由應用程序顯式觸發,也可以由 JSEP 實現隱式觸發,以響應 ICE 配置中的更改。
當 ICE 配置發生變化,需要一個新的收集階段時,就會設置一個 “needs-ice-restart” 標識。設置了這個標識后,調用 createOffer API 將生成新的 ICE 憑證。這個標識通過調用setLocalDescription API 來清除,該 API 帶有來自 offer 或 answer 的新 ICE 憑證,即本地或遠程發起的ICE重啟。
當一個新的收集階段開始時,ICE 代理將通過狀態更改事件通知應用程序正在進行收集。然后,當每個新的 ICE candidates 可用時,ICE 代理將通過一個 oniccandidate 事件通知應用程序;這些候選對象也將自動添加到當前或者掛起的本地會話描述中。最后,當收集完所有的 ICE candidates 后,將發送一個最后的 oniccandidate 事件,以表明收集過程已經完成。
注意,收集階段只收集 new/recycled/restaring 狀態的 m-section 所需的候選;其他 m-section 繼續使用它們現有的候選項。同樣,如果一個 m-section 被綁定(通過一個成功的bundle 協商或者被標記為 bundle-only),那么當且僅當它的 MID 項是一個 bundle 標簽時,候選的 m-section 將被收集并交換,如 [RFC8843] 所述。
3.5.2. ICE Candidate Trickling
Candidate Trickling 是一種技術方案,通過它呼叫者可以在發出初始 offer 后,逐步地向被呼叫者提供 candidate;“Trickle ICE” 的語義在 [RFC8838] 中定義。這個過程允許被調用方開始對調用開始操作,并立即建立 ICE (可能還有 DTLS )連接,而不必等待調用方收集所有可能的候選連接。在啟動調用之前沒有執行收集的情況下,這將導致更快的媒體設置。
JSEP 通過提供前文提到的 API 來支持可選的候選傳入,這些 API 對 ICE 候選收集過程提供控制和反饋。支持 Trickle ICE 的應用程序可以立即發送初始 offer,并在收到新 candidate 的通知時發送單個 candidate;不支持此功能的應用程序可以簡單地等待收集完成的指示,然后創建和發送攜帶所有 candidate 的 offer。
在收到少量的 candidate 后,接收人將把他們提供給 ICE 代理。這將觸發 ICE 代理開始使用新的遠程候選連接檢查。
3.5.2.1. ICE Candidate 格式
在 JSEP中,ICE 候選對象被一個 iccandidate 對象抽象,與會話描述一樣,內部表示使用 SDP 語法。
候選的詳細信息在 iccandidate 字段中指定,使用與 [RFC8839] 5.1 節中定義的 “candidate-attribute” 字段相同的 SDP 語法。注意,該字段不包含 “a=” 前綴,如下例所示:
candidate:1 1 UDP 1694498815 192.0.2.33 10000 typ hosticcandidate 對象包含一個字段,用來指明它與哪個 ICE 用戶名片段(ufrag)相關聯,如 [RFC8839] 5.4 節中定義的那樣。該值用于確定該 iccandidate 屬于哪個會話描述(以及哪個收集階段),這有助于解決 ICE 重啟時的歧義。如果這個字段在接收到的 iccandidate 中不存在(可能是在與非 JSEP 端點通信時),則假定為最近接收到的會話描述。
可以通過一個 m-section 索引或者 MID 這兩種方式中的一種確認 iceCandidate 對象與 m-section 是相關聯的,m-section 索引是一個從零開始的索引,索引 N 指的第 N + 1 m-section 的會話描述引用的這個 IceCandidate。 MID 是一個 “media stream identification” 的值,在[RFC5888] 第4章中定義,它提供了一種更健壯的方法來標識會話描述中的 m-section,使用關聯的 RtpTransceiver 對象的MID(它可能是由應答者在與不支持 MID 屬性的非 JSEP 端點交互時本地生成的,如下面 5.10 節所討論的)。如果在接收到的 iccandidate 中有 MID 字段,它必須被用于識別;否則,將使用 m-section 索引。如上所述,實現時必須考慮到接收對象缺少某些字段的情況。
3.5.3. ICE Candidate 策略
通常,在收集 ICE Candidate 對象時,JSEP 實現將收集初始候選對象的所有可能形式 —— host/server-reflexive/relay。但在某些情況下,出于隱私或相關考慮,應用程序可能希望對收集過程擁有更具體的控制。例如,可能希望只使用中繼候選以盡可能少地泄漏位置信息(需要注意,這種選擇會帶來相應的操作成本)。為了實現這一點,JSEP 允許應用程序限制在會話中使用哪些 ICE Candidate。請注意,這個過濾是在任何限制的基礎上應用的,如 [RFC8828] 中所討論的,該實現可以強制選擇哪些 IP 地址用于應用程序。
在某些情況下,應用程序可能希望更改會話處于活動狀態時使用的候選類型。一個主要的例子是,接受會話者最初可能希望只使用中繼候選者,以避免將位置信息泄露給任意的呼叫者,一旦用戶表示他們想接這個呼叫,就會更改為使用所有的 Candidate(為了降低操作成本)。對于這個場景,JSEP 實現必須允許在會話中期更改候選策略,這取決于前面提到的與本地策略的交互。
為了管理 ICE Candidate 策略,JSEP 實現將在每個收集階段開始時確定當前設置。然后,在收集階段,實現不可以將當前策略不允許的候選對象公開給應用程序,使用它們作為連接檢查的源,或者通過其他字段間接公開它們,例如其他 ICE Candidate 對象的 raddr/rport 屬性。之后,如果應用程序指定了不同的策略,應用程序可以通過 ICE 重啟來啟動一個新的收集階段并應用它。
3.5.4. ICE Candidate Pool
JSEP 應用程序通常通過提供給 setLocalDescription 的信息通知 JSEP 實現開始收集 ICE,因為本地描述指示了需要的 ICE 組件的數量,以及必須為哪些候選組件收集。但是,為了加速應用程序提前知道要使用的 ICE 組件數量的情況,它可能會要求實現收集潛在的 ICE 候選組件池,以確保快速設置媒體。
當 setLocalDescription 最終被調用并且 JSEP 實現準備收集所需的 ICE Candidate 時,它應該首先檢查池中是否有可用的候選。如果候選池中有 Candidate,他們應該通過ICE Candidate 活動立即提交申請。如果池耗盡,要么是因為使用的 ICE 組件數量超過預期,要么是因為池沒有足夠的時間收集候選項,那么剩余的候選項將像往常一樣收集。這只發生在第一次 offer/answer 交換,之后候選池被清空并且不再使用。
這個概念有用的一個例子是,一個應用程序希望在將來的某個時間點收到一個傳入的呼叫,并希望將建立連接所需的時間最小化,以避免剪切初始媒體。通過將候選對象預先聚集到池中,它可以在收到呼叫后幾乎立即交換并開始從這些候選對象發送連接檢查。不過請注意,通過保留這些預先收集的候選對象(只要需要它們,它們就會一直保持活躍),應用程序將消耗它正在使用的 STUN/TURN 服務器上的資源。
3.5.5. ICE 版本
雖然該規范在形式上依賴于 [RFC8445],但在其發布時,大多數 WebRTC 實現都支持 [RFC5245] 中描述的 ICE 版本。在 [RFC8445] 中定義的 “ice2” 屬性可以用來檢測遠程終端使用的版本,并提供從舊規范到新規范的平穩過渡。實現必須能夠接受沒有 “ice2” 屬性的遠程描述。
3.6. 視頻大小協商
視頻大小協商是接收端可以使用 “a=imageattr” SDP屬性 [RFC6236] 來指示它能夠接收的視頻幀大小的過程。接收端可以對其視頻解碼能處理的內容有硬性限制,或者它可能有一些策略設置的最大值。通過在 “a=imageattr” 屬性中指定這些限制,JSEP 端點可以嘗試確保遠程發送方以可接受的分辨率傳輸視頻。然而,當與不支持此屬性的非 JSEP 端點通信時,可能會超過任何信令限制,而 JSEP 實現必須合理地處理此限制,例如,丟棄視頻。
需要注意,某些編解碼器支持傳輸寬高比不是1.0(即非正方形像素)的樣本。JSEP 實現將不發送非正方形像素,但應該接收和渲染這樣的視頻與正確的寬高比。然而,樣本寬高比對尺寸協商沒有影響;無論是否正方形,所有尺寸都以像素為單位測量。
3.6.1. 創建 imageattr 屬性
接收器將首先結合任何已知的本地限制(例如,硬件解碼器能力或本地策略),以確定它可以接收的絕對最小和最大尺寸。如果沒有已知的局部限制,“a=imageattr” 屬性應該被省略。如果這些局部限制排除了接收任何視頻,例如,不允許分辨率的簡并情況下,“a=imageattr” 屬性必須被省略,如果合適的話,m-section 必須被標記為 sendonly/inactive。
否則,一個 “a=imageattr” 屬性被創建為一個 “recv” 方向,并且由前面提到的交集形成的分辨率空間被用來指定它的最小和最大的 “x=” 和 “y=” 值。
這里的規則表示一組單獨的首選項,因此,“a=imageattr” 中 “q=” 值并不重要。應該設置為 “1.0”。
“a=imageattr” 字段是特定于負載類型的。當所有支持的視頻編解碼器具有相同的功能時,建議使用通配符有效負載類型(*)的單一屬性。然而,當受支持的視頻編解碼器有不同的限制時,必須為每種負載類型插入特定的 “a=imageattr” 屬性。
例如,考慮一個具有多格式視頻解碼器的系統,它能夠解碼從 48x48 到 720p 的任何分辨率。在這種情況下,實現將生成這個屬性:
a=imageattr:* recv [x=[48:1280],y=[48:720],q=1.0]這個聲明表明接收器能夠解碼從 48x48 到 1280x720 像素分辨率的任何圖像。
3.6.2. 解析 imageattr 屬性
[RFC6236] 將 “a=imageattr” 定義為一個建議的字段。這意味著它并不絕對限制發送方可以使用的視頻格式,而是給出了首選值的指示。
這個規范規定了更具體的行為。MediaStreamTrack 的視頻分辨率被附加到一個 RtpSender,追蹤編碼的視頻在同一或低分辨率(s)(“編碼器分辨率”),應用和遠程描述引用發送方和包含有效的 “= imageattr recv” 屬性,它必須遵循以下規則,以確保發送方不會傳輸超出屬性中指定的大小標準的分辨率。只要屬性在遠程描述中仍然存在,就必須遵循這些規則,包括在 track 改變其分辨率或被不同 track 替換的情況下。
根據 RtpSender 是如何配置的,它可能會產生一個特定分辨率的單一編碼,或者,如果同時發送(3.7節)多個已經協商的編碼,每個都有自己的特定分辨率。此外,根據配置的不同,每種編碼都可以在需要時靈活地減少分辨率,或者鎖定到特定的輸出分辨率。
對于由 RtpSender 產生的每個編碼,遠程描述中相應的 m-section 中的 “a=imageattr recv” 屬性集將被處理,以確定應該傳輸什么。僅考慮引用為編碼選擇的媒體格式的屬性;每個這樣的屬性都是單獨計算的,從 “q=” 值最高的屬性開始。如果多個屬性具有相同的 “q=” 值,則按照它們在包含 “m=” 部分中出現的順序計算它們。請注意,雖然 JSEP 端點每一種媒體格式最多包含一個 "a=imageattr recv"屬性,但 JSEP 端點可以從非 JSEP 端點接收包含多個此類屬性的 m-section 的會話描述。
對于每個 “a=imageattr recv” 屬性,應用以下規則。如果此處理成功,則相應地傳輸編碼,并且不再考慮該編碼的其他屬性。否則,將按照前面提到的順序計算下一個屬性。如果所提供的所有屬性都不能被成功處理,那么就不能傳輸編碼,并且應該向應用程序拋出一個錯誤。
- 將該屬性的限制與編碼器分辨率進行比較。只考慮以下提到的具體限制;任何其他值,比如圖片的寬高比,都必須被忽略。當考慮生成旋轉視頻的 MediaStreamTrack 時,必須使用未旋轉的分辨率進行檢查。無論接收機是否支持執行接收側旋轉(例如,通過協調視頻定向(CVO) [TS26.114]),這都是必需的,因為這大大簡化了匹配邏輯。
- 如果屬性包含一個“sar=”(樣本寬高比)值設置為“1.0”以外的值,表明接收者想要接收非正方形像素,這不能滿足,屬性絕對不能被使用。
- 如果編碼器的分辨率超過屬性允許的最大大小,并且允許編碼器調整其分辨率,編碼器應該應用降尺度以滿足限制。一定不要改變圖片的寬高比的編碼,忽略任何微小的差異,由于舍入。例如,如果編碼器的分辨率是1280x720,而屬性的最大指定值是640x480,那么預期的輸出分辨率將是640x360。如果不能應用降尺度,則絕對不能使用該屬性。
- 如果編碼器分辨率小于該屬性允許的最小大小,則一定不要使用該屬性;編碼器一定不能應用升級。JSEP實現應該通過允許接收任意小的分辨率(可能通過回退到軟件解碼器)來避免這種情況。
- 如果編碼器分辨率在最大和最小尺寸范圍內,則不需要任何操作。
3.7. Simulcast
JSEP 支持 MediaStreamTrack 的 Simulcast 傳輸,其中媒體源的多個編碼可以在一個 m-section 的上下文中傳輸。當前的 JSEP API 設計用于允許應用程序發送 Simulcast 媒體,但只接收單一編碼。這允許在多用戶場景中,每個發送客戶端向服務器發送多個編碼,然后服務器為每個接收客戶端選擇要轉發的適當編碼。
應用程序通過在 RtpSender上配置多個編碼來請求支持 Simulcast。在生成 offer 或 answer 時,這些編碼通過相應的 m-section 上的 SDP 標記表示,如下所述。理解并愿意接收 Simulcast 的接收器也將包括 SDP 標記來表示他們的支持,而 JSEP 端點將使用這些標記來確定是否允許給定的 RtpSender 進行 Simulcast。如果沒有協商同步傳輸支持,RtpSender 將只使用第一個配置的編碼。
請注意,Simulcast 的確切參數取決于發送程序。雖然前面提到的 SDP 標記是為了確保遠端可以接收和分解多個 Simulcast 的編碼,但在 JSEP 中,用于每個編碼的具體分辨率和比特率僅由發送端決定。
JSEP 目前還沒有提供一種機制來配置 Simulcast 的接收。這意味著,如果遠端提供了 simulcast,則 JSEP端點生成的 answer 將不指示是否支持接收simulcast,因此遠端將只發送每個 m-section 的單個編碼。
此外,JSEP 沒有提供處理來自 JSEP 端點 simulcast offer 請求的機制。這意味著,在 JSEP 端點收到初始 offer 的情況下,設置 simulcast 需要帶外信令或 SDP 檢查。然而,如果 JSEP 端點在其初始 offer 中設置了 simulcast,則任何已建立的 simulcast 流將在收到傳入的重新 offer 后繼續工作。該規范的未來版本可能會添加額外的 API 來處理傳入的初始 offer 場景。
當使用 JSEP 從 RtpSender 發送多個編碼時,使用來自 [RFC8853] 和 [RFC8851] 的技術。具體來說,當一個 RtpSender 被配置了多個編碼時,RtpSender 的 m-section 將包含一個 “a=simulcast” 屬性,正如在 [RFC8853] 5.1 節中定義的那樣,帶有一個 “send” simulcast 描述并列出了每個期望的編碼,而沒有 “recv” simulcast 描述。m-section 還將包含每個編碼的 “a=rid” 屬性,如 [RFC8851] 4 節中指定的;使用限制標識符(rid,也稱為 rid-ids 或 RtpStreamIds )可以消除各個編碼的歧義,即使它們都屬于同一個 m-section。
3.8. 與 forking 的相互作用
一些呼叫信令系統允許各種類型的 forking ,一個 SDP offer 可以提供給多個設備。例如,SIP [RFC3261] 同時定義了 “并行搜索” 和 “順序搜索”。盡管這些主要是信令層問題,不在 JSEP 的范圍內,但它們確實對媒體相關的配置有一些影響。當 forking 發生在信令層時,負責信令的 JavaScript 應用程序需要決定在什么時間點應該發送或接收什么媒體,以及應該與哪個遠程端點通信;JSEP 用于確保媒體引擎能夠按照應用程序的要求生成 RTP stream 并執行操作。應用程序可以讓媒體引擎做的基本操作如下:
- 開始與給定的遠程對等端交換媒體,但保留 offer 中的所有資源。
- 開始與給定的遠程對等點交換媒體,并釋放 offer 中未被使用的任何資源。
3.8.1. 順序 Forking
順序 forking 涉及一個調用被分派到多個遠程被調用方,其中每個被調用方可以接受調用,但每次只有一個活動會話存在;不執行媒體合流操作。
JSEP 可以很好地處理順序 forking ,允許應用程序輕松地控制選擇所需遠程端點的策略。當來自其中一個調用者的 answer 到達時,應用程序可以選擇將其應用為 provisional answer 或者 final answer。
在 “first-one-wins” 的情況下,第一個 answer 將被應用為 final answer,并且應用程序將拒絕任何隨后的 answer。在 SIP 中可以理解為 “ACK + BYE”。
在 “last-one-wins” 的情況下,所有的 answer 將作為 provisional answer,任何先前的呼叫階段將被終止。在某些時候,應用程序將結束建立過程,可能會使用一個計時器;此時,應用程序可以重新應用掛起的遠程描述作為 final answer。
3.8.2. 并行 Forking
并行 forking 包括一個調用被分派到多個遠程被調用方,其中每個被調用方可以接受該調用從而可以建立多個同時活動的信令會話。如果多個被調用方同時發送媒體,處理的可能性在[RFC3960] 3.1節中描述。如今的大多數 SIP 設備一次只支持與單個設備交換媒體,而不是像早期那樣嘗試混合多個媒體音頻源,因為這可能會導致混亂的情況。例如,考慮將歐洲的回鈴音和北美的回鈴音混合在一起——產生的聲音將不像這兩種音調,會讓用戶感到困惑。如果信令應用程序希望一次只與一個遠程端點交換媒體,那么從媒體引擎的角度來看,這與順序 forking 的情況完全相同。
在并行 forking 的情況下,JavaScript 應用程序希望同時與多個對等體交換媒體,流程稍微復雜一些,但JavaScript應用程序可以遵循 [RFC3960] 描述的策略,使用 UPDATE。UPDATE 方法允許信令為它希望與之交換媒體的每個對等體建立單獨的媒體流。在 JSEP 中,UPDATE 使用的這個 offer 將通過簡單地創建一個新的 PeerConnection (參見 4.1 節)來形成,并確保相同的本地媒體流已經被添加到這個新的 PeerConnection 中。然后新的 PeerConnection 對象將產生一個 SDP offer,該 offer 可以被信令用來執行 [RFC3960] 中討論的 UPDATE 策略。
由于是共享媒體流,應用程序將以 N 個并行 PeerConnection 會話結束,每個會話都有一個本地和遠程描述以及它們自己的本地和遠程地址。來自這些會話的媒體流可以使用setDirection 來管理(參見 4.2.3 節),或者應用程序可以選擇從所有會話混合播放媒體。當然,如果應用程序只想保留單個會話,它可以簡單地終止不再需要的會話。
4. 接口
本節詳細介紹了實現 JSEP 功能必須具備的基本操作。W3C API 中公開的實際 API 可能有一些不同的語法,但應該很容易映射到這些概念。
4.1. PeerConnection
4.1.1. 構造函數
PeerConnection 構造函數允許應用程序為媒體會話指定全局參數,例如在收集候選對象時使用的 STUN/TURN 服務器和憑證,以及初始 ICE 候選策略和池大小,以及使用的 bundle 策略。
如果指定了 ICE 候選策略,它的功能如 3.5.3 節所述,導致 JSEP 實現只向應用程序顯示被允許的候選策略(包括任何實現內部過濾),并且只使用這些候選策略進行連接檢查。可用的策略集合如下:
- all:
所有被執行政策允許的候選將被收集和使用。 - relay:
除中繼候選外,所有其他候選將被過濾掉。這將隱藏遠程 peer 可能從接收到的候選對象確定的位置信息。根據應用程序部署和選擇中繼服務器的方式,這可能會將位置混淆到城市級甚至全球級。
默認的 ICE 候選策略必須設置為 “all”,因為這通常是理想的策略,而且通常也會顯著減少應用程序對 TURN 服務器資源的使用。
如果指定了 ICE 候選池的大小,則表示預收集候選的 ICE 組件的數量。因為預收集的結果是在很長一段時間內使用 STUN/TURN 服務器資源,這必須只發生在應用程序請求時,因此默認的候選池大小必須為零。
應用程序可以指定使用 bundle 的首選策略,bundle 的多路復用機制定義在 [RFC8843] 中。無論策略如何,應用程序將總是嘗試協商捆綁到一個單一的傳輸,并將提供一個單一 bundle 組的所有 m-section;這個單一傳輸的使用取決于接受 bundle 的應答器。但是,通過從下面的列表中指定策略,應用程序可以精確地控制它將媒體流捆綁在一起的積極程度,這將影響它與 “non-bundle-aware” 端點的互操作方式。當與 “non-bundle-aware” 終端協商時,只有未標記為 “bundle-only” 的流將被建立。
可用的策略集合如下:
- balanced:
每個類型(音頻、視頻或應用程序)的第一個 m-section 將包含傳輸參數,這將允許應答者拆分該部分。每個類型的第二個和后續的 m-section 將被標記為 “bundle-only”。結果是,如果有N種不同的媒體類型,那么將為 N 個媒體流收集候選媒體。這一政策平衡了實現多元化的期望和確保基本音頻和視頻仍然可以在遺留方案中協商的需要。當作為應答者時,如果 offer 中沒有bundle 組,實現將拒絕所有類型的 m-section 。 - max-compat:
所有 m-section 將包含傳輸參數但不標記為 “bundle-only”。這個策略將允許所有的流都被 “non-bundle-aware” 的端點接收,但是需要為每個媒體流收集單獨的候選。 - max-bundle:
只有第一個 m-section 將包含傳輸參數,除第一個流之外的所有流將被標記為 “bundle-only”。該策略旨在最小化候選集合和最大化復用,以降低與歷史端點的兼容性為代價。當作為應答者時,實現將拒絕除第一個 m-section 區段之外的任何 m-section,除非它們與 m-section 在同一個 bundle 組中。
由于提供了性能和與遺留端點兼容性之間的最佳權衡,默認 bundle 策略必須設置為 “balanced”。
應用程序可以指定使用 RTP/RTCP 傳輸復用 [RFC5761] 的首選策略,使用以下策略之一:
- negotiate:
JSEP實現將收集 RTP 和 RTCP 候選,但也將提供 “a=rtcp-mux”,從而允許兼容多路或非多路復用終端。 - require:
JSEP 實現將只收集 RTP 候選,并將在它生成的 offer 中任何新的 m-section 中插入 “a=rtcp-mux-only” 。這樣一來,候選收集者需要收集的候選數量就減少了一半。使用不包含"a=rtcp-mux" 屬性的 m-section 描述將導致返回錯誤。
默認的復用策略必須設置為 “require”。實現可以選擇拒絕應用程序設置多路復用策略為 “negotiate” 的嘗試。
4.1.2. addTrack
addTrack 方法為 PeerConnection 添加一個 MediaStreamTrack,使用 MediaStream 參數將該 track 與同一 MediaStream 中的其他 track 關聯起來,這樣當創建一個 offer 或 answer 時,它們可以被添加到相同的 “LS”(Lip Synchronization)組。將 track 添加到相同的 “LS” 組表明,這些 track 的播放應該同步以進行正確的lip sync,如[RFC5888],第 7 節所述。addTrack 試圖最小化收發器的數量,如下所示:如果 PeerConnection 處于 “have-remote-offer” 狀態,該 track 將被附加到第一個兼容的 transceiver 上,該 transceiver 是由最近的 setRemoteDescription 調用創建的,并且沒有本地 track。否則,將創建一個新的 transceiver,如 4.1.4 節所述。
4.1.3. removeTrack
removeTrack 方法從 PeerConnection 中刪除 MediaStreamTrack,使用 RtpSender 參數來指示哪個 sender 的 track 應該被刪除。清除 sender 的 track 后,sender 停止發送。調用 createOffer 后,如果是 transceiver,方向由 sendrecv 變為 recvonly,或者由 sendonly 變為 inactive 。
4.1.4. addTransceiver
addTransceiver 方法增加一個新的 RtpTransceiver 到 PeerConnection。如果提供了 MediaStreamTrack 參數,則 transceiver 將被配置為該媒體類型,并且 track 將被附加到 transceiver 上。否則,應用程序必須顯式地指定類型;這種模式對于創建 recvonly transceiver 非常有用,對于創建可以在以后附加 track 的 transceiver 也非常有用。
應用程序可以在創建的時候指定 transceiver 方向屬性、關聯的 MediaStreams (允許 “LS” 組分配)以及一組媒體編碼(用于在 3.7 節中描述的 simulcast)。
4.1.5. onaddtrack 事件
當 setRemoteDescription 調用后生成了一個新的 remote track 時,onaddtrack 事件被通知給應用程序。新 track 在事件中被作為 MediaStreamTrack 對象,與該 track 所在的 MediaStream 一起包含在事件參數中。
4.1.6. createDataChannel
createDatachannel 方法創建一個新的數據通道并將其附加到 PeerConnection。如果當前 PeerConnection 沒有數據通道,則需要一次新的 offer/answer 交換。在一個給定的PeerConnection 上的所有數據通道共享相同的 SCTP/DTLS 關聯(“SCTP” 代表 “Stream Control Transmission Protocol”),因此相同的 m-section,后續的數據通道的創建不會對 JSEP 狀態產生任何影響。
createdatchannel 方法也包含了 PeerConnection 使用的一些參數(例如 maxPacketLifetime),但不會在 SDP 中反映,也不會影響 JSEP 狀態。
4.1.7. ondatachannel 事件
當遠端協商了一個新的數據通道時,ondatchannel 事件被通知給應用程序,這可以在底層 SCTP/DTLS 關聯建立之后的任何時間發生。在事件參數中包含新的數據通道對象。
4.1.8. createOffer
createOffer 方法生成一個 描述其支持的會話配置的 SDP [RFC3264],包括添加到當前 PeerConnection 媒體的描述 、codec、RTP/RTCP、由 ICE 代理收集的 ICE candidate。可以提供一個選項參數來對生成的 offer 提供額外的控制。這個選項參數允許應用程序觸發 ICE 重啟,以重新建立連接。
在最初的 offer 中,生成的 SDP 將包含會話所需的所有功能(默認支持但不希望使用的功能可以省略);對于 SDP 的每一行,SDP 的生成規則都將遵循已定義的 SDP 規則。初始 offer 生成的具體處理詳見下文 5.2.1 節。
如果在會話建立后調用 createOffer,createOffer 將根據會話的任何更改生成一個修改當前會話的 offer,例如,添加或停止 RtpTransceivers,或請求 ICE 重啟。對于每個現有的流,每個 SDP line 的生成規則必須遵循已有的 RFC 定義。對于每個新媒體流,SDP 的生成必須遵循生成初始 offer 的過程。如果沒有更改,或SDP line 不受請求更改的影響,offer 將只包含由最后的 offer/answer 交換協商的參數。后續 offer 生成的具體處理詳見下文 5.2.2 節。
由 createOffer 生成的會話描述必須立即被 setLocalDescription 使用;如果一個系統有有限的資源(例如,解碼器的數量有限),createOffer 應該返回一個反映系統當前狀態的 offer,這樣 setLocalDescription 在嘗試獲取這些資源時就會成功。
調用這個方法可以做一些事情,比如生成新的 ICE 憑證,但它不會改變 PeerConnection 狀態,觸發候選收集,或導致媒體流開始或停止。具體地說,在調用 setLocalDescription 之前,offer 不會被應用,也不會成為臨時的本地描述。
4.1.9. createAnswer
createAnswer 方法生成一個 SDP,應答在最近的 setRemoteDescription 調用中每個 [RFC3264] 所支持的會話配置兼容的參數,setRemoteDescription 必須在調用 createAnswer 之前調用。像 createOffer 一樣,返回的blob 包含了對添加到 PeerConnection 的媒體的描述,為該會話協商的codec/RTP/RTCP 選項,以及 ICE 代理收集的任何候選項,并且可以提供一個選項參數來提供對生成 answer 的額外控制。
作為一個 answer,生成的SDP將包含一個特定的配置,指定媒體傳輸應該如何建立;對于每一條SDP line,SDP 的生成必須遵循已有的規范。answer 生成的具體處理將在下面的 5.3 節中詳細介紹。
由 createAnswer 生成的會話描述必須立即被對端 setLocalDescription 使用;像 createOffer 一樣,返回的描述應該反映系統的當前狀態。
調用這個方法可以做一些事情,比如生成新的 ICE 憑據,但它不會改變 PeerConnection 狀態,不會觸發候選收集,也不會導致媒體狀態改變。具體來說,answer 不會被應用,也不會成為當前端點的本地描述,直到 setLocalDescription被調用。
4.1.10. SessionDescriptionType
會話描述對象(RTCSessionDescription)的類型可以是 “offer”、“pranswer”、“answer” 或 “rollback”。這些類型提供了關于應該如何解析描述參數以及應該如何更改媒體狀態的信息。
“offer” 表示一個描述必須被解析為一個 offer;描述可能包括許多可能的媒體配置。當 PeerConnection 處于 “stable” 狀態時,作為 offer 的描述可以被應用,或者作為先前提供但未回復的 offer 的更新被應用。
“pranswer” 表示描述必須被解析為一個 answer,而不是最終的 answer,因此不可以釋放已分配資源。如果 answer 沒有指定方向為 inactive ,則可能導致媒體傳輸開始。作為 “pranswer” 的描述可以作為對 offer 的回應,也可以作為對先前發送的 “pranswer” 的更新。
“answer” 表示一個描述必須被解析為一個 answer,offer/answer 交換將被認為是完整的,并且任何不再需要的資源(解碼器,候選)應該被釋放。作為 “answer” 的描述可以作為對 “answer” 的回應,也可以作為對先前發送的 “pranswer” 的更新。
provisional answer 和 final answer 之間的唯一區別是,final answer 的結果是釋放因 offer 而分配的任何未使用的資源。因此,應用程序可以自行決定 answer 是臨時的還是最終的,并可以根據需要更改會話描述的類型。例如,在串行 forking 場景中,應用程序可能會收到多個 final answer,每個遠程端點都有一個。應用程序可以選擇接受最初的 answer 作為臨時 answer,只有當它收到一個滿足其標準的 answer 時(例如,一個活躍的用戶而不是語音郵件),才應用這個 answer 作為最終 answer。
“rollback” 是一種特殊的會話描述類型,它指示狀態機必須回滾到以前的 “stable” 狀態,如 4.1.10.2 節所述。內容必須為空。
4.1.10.1. Provisional Answers 使用方式
大多數應用程序不需要使用 “pranswer” 類型創建 answers。雖然 這樣可以對 offer 立即響應,為了盡快建立會話運輸,防止媒體裁剪發生,JSEP 優先處理應用程序創建和發送一個 “sendonly” 最終 answer (MediaStreamTrack 為空) 后立即收到 answer,這將阻止媒體由呼叫者發送,并允許媒體在被呼叫者回答后立即發送。稍后,當被調用方實際接受該調用時,應用程序可以插入真正的 MediaStreamTrack 并創建一個新的 “sendrecv” offer 來更新之前的 offer/answer對,并啟動雙向媒體流。雖然這也可以通過 “sendonly” pranswer 后接 “sendrecv” answer 來完成,但最初的 pranswer 保留 offer/answer 交換開放,這意味著呼叫者不能在這段時間內發送更新的 offer。
例如,考慮一個典型的 JSEP 應用程序,它希望盡可能快地設置音頻和視頻。當接收到一個 offer 包含了音頻和視頻 MediaStreamTracks,它將發送一個直接的 answer 并將這些 track 設置為 sendonly (這意味著接受者將不會發送任何媒體,因為尚未添加自己的 MediaStreamTracks,發起者也不會發送任何媒體)。然后,它將等待用戶接受呼叫并獲取所需的本地 track。在用戶接受后,應用程序將載入它獲得的 track,這時 ICE 握手和 DTLS 握手可能已經完成,可以立即開始傳輸。應用程序還將向遠端發送一個新的 offer,指示呼叫接受,并將音頻和視頻設置為 “sendrecv”。7.3 節給出了一個詳細的用例流程。
當然,一些應用程序可能無法執行這種雙重 offer/answer 交換,特別是那些使用歷史信令協議的應用程序。在這些情況下,pranswer 仍然可以為應用程序提供一種機制來進行傳輸預連接。
4.1.10.2. Rollback
在某些情況下,可能需要 “rollback” 對 setLocalDescription 或 setRemoteDescription 調用后進行更改。考慮這樣一種情況,一個呼叫正在進行,一方希望更改一些會話參數,該端生成一個更新的 offer,然后調用 setLocalDescription。但是,遠端(在 setRemoteDescription 之前或之后)決定它不想接受新參數,并向提供程序發送拒絕消息。現在,提供者(可能還有回答者)需要返回到 “stable” 狀態和先前的 offer/answer 描述。為了支持這一點,我們引入了 “rollback” 的概念,它丟棄對會話的任何建議更改,將狀態機返回到 “stable” 狀態。回滾是通過向 setLocalDescription 或 setRemoteDescription 提供帶有空內容的類型為 “rollback” 的會話描述來執行的。
4.1.11. setLocalDescription
setLocalDescription 方法指示 PeerConnection 應用提供的會話描述作為它的本地配置。type 字段指示該描述應被處理為 offer 、provisional answer、final answer 還是 rollback;offer 和 answer 是不同的檢查,與已存在的每個SDP line 規則兼容。
這個 API 改變了本地媒體的狀態;除此之外它還創建了接收和解碼媒體的本地資源。為了使應用程序可以支持改變媒體格式的場景,PeerConnection 必須能夠同時支持使用當前和等待本地描述(例如,支持所有已存在的編解碼器格式)。這種雙重處理開始于 PeerConnection 進入 “have-local-offer” 狀態時,并一直持續到 setRemoteDescription 傳入一個 final answer 或者 rollback。
這個API間接地控制候選的收集過程。當提供了本地描述,并且當前使用的傳輸數量與本地描述所需的傳輸數量不匹配時,PeerConnection 將根據需要創建傳輸,并開始為每個傳輸收集候選,如果可用,使用候選池中的候選。
如果(1)setRemoteDescription 被提前調用并帶有一個 offer,(2)setLocalDescription被調用帶有一個 answer(臨時或最終),(3)媒體方向是兼容的,(4)媒體可用來發送,這將導致媒體傳輸的開始。
4.1.12. setRemoteDescription
setRemoteDescription 方法指示PeerConnection 應用提供的會話描述作為所需的遠程配置。與 setLocalDescription 一樣,描述的類型字段指示應該如何處理它。
這個 API 改變了本地媒體的狀態;除此之外,它還設置了用于發送和編碼媒體的本地資源。
如果(1)setLocalDescription 被提前調用并帶有一個 offer,(2)setRemoteDescription 被調用并帶有一個 answer (臨時或最終),(3)媒體方向是兼容的,(4)媒體可用來發送,這將導致媒體傳輸的開始。
4.1.13. currentLocalDescription
currentLocalDescription 方法返回當前協商的本地描述,即來自最后一次成功的 offer/answer 交換的本地描述,不包括本地描述被設置之后由 ICE 代理生成的任何本地候選。
如果 offer/answer 交換尚未完成,將返回一個空對象。
4.1.14. pendingLocalDescription
pendingLocalDescription 方法返回當前正在協商的本地描述的副本,即一個沒有任何相應遠程 answer 的本地 offer集,除了自本地描述被設置以來由 ICE 代理生成的任何本地候選對象之外。
如果 PeerConnection 的狀態是 “stable” 或 “have-remote-offer”,則返回空對象。
4.1.15. currentRemoteDescription
currentRemoteDescription 方法返回當前協商的遠程描述的副本,即來自最后一次成功 offer/answer 交換的遠程描述,以及自遠程描述設置后通過 processIceMessage 提供的任何遠程候選描述。
如果 offer/answer 交換尚未完成,將返回一個空對象。
4.1.16. pendingRemoteDescription
pendingRemoteDescription 方法返回當前正在協商的遠程描述的副本,即一個沒有任何相應本地 answer 的遠程 offer 集合,除了自遠程描述被設置以來通過 processIceMessage 提供的任何遠程候選對象之外。
如果 PeerConnection 的狀態是 “stable” 或 “have-local-offer”,則返回空對象。
4.1.17. canTrickleIceCandidates
cantrickleiceccandidates 屬性表示遠端是否支持接收 Trickle ICE 項。有三個可能的值:
- null:
沒有從另一方收到 SDP,所以不知道它是否能處理 Trickle ICE。這是調用 setRemoteDescription 之前的初始值。 - true:
SDP 已經收到另一方的指示,表明它可以支持 Trickle ICE。 - false:
SDP 已經從另一方收到指示,它不能支持 Trickle ICE。
如 3.5.2 節所述,JSEP 實現總是為應用程序單獨提供候選,這與 Trickle ICE 所需要的是一致的。然而,應用程序可以使用 canTrickleIceCandidates 屬性來確定他們的對等端是否能夠真正地進行 Trickle ICE,也就是說,發送最初的 offer 或 answer 是否安全,然后候選被收集。因為 “true” 是唯一明確表示遠程 Trickle ICE 支持的值,所以一個比較 canTrickleIceCandidates 和 “true” 的應用程序在初始 offer 時默認會嘗試 Half Trickle ICE,在與 Trickle ICE 兼容的代理進行后續交互時默認會嘗試 Full Trickle ICE。
4.1.18. setConfiguration
setConfiguration 方法允許 PeerConnection 的全局配置(最初由構造函數參數設置)在會話期間更改。調用這個方法的效果取決于它被調用的時間,它們會因特定參數的改變而不同:
- 對 STUN/TURN 服務器的任何更改都會影響到下一個收集階段。如果一個 ICE 收集階段已經開始或完成,將設置 3.5.1 節中提到的 “needs-ice-restart” 標識。這將導致下一次調用 createOffer 來生成新的 ICE 憑證,目的是強制 ICE 重啟并開始一個新的收集階段,在這個階段中將使用新的服務器。如果 ICE 候選池的大小為非零且尚未應用本地描述,則將丟棄任何現有的候選池,并從新服務器收集新的候選池。
- 對 ICE 候選策略的任何更改都會影響下一個收集階段。如果一個 ICE 收集階段已經開始或完成,“needs-ice-restart” 標識將被設置。無論哪種方式,對策略的更改都不會對候選池產生影響,因為在出現收集階段之前,應用程序無法使用池中的候選池,因此仍然可以對任何池中的候選池執行任何必要的過濾。
- 應用本地描述后,不能改變 ICE 候選池的大小。如果本地描述還沒有被應用,任何對 ICE 候選池大小的更改都會立即生效;如果增加,則預先收集更多的候選;如果減少,則丟棄現在多余的候選項。
- 在 PeerConnection 建立后,bundle 和 rtcp 復用策略不能被修改。
調用此方法可能導致對ICE代理的狀態進行更改。
4.1.19. addIceCandidate
addiccandidate 方法通過一個 iccandidate 對象來更新 ICE agent(章節3.5.2.1)。如果該 ICE candidate 字段為非空,則該 ICE candidate 將被視為一個新的遠程 ICE candidate,根據為 Trickle ICE 定義的規則,將其添加到 current/pending 狀態的遠程描述中。否則,根據 [RFC8838] 第 14 節的定義,iccandidate 被視為候選結束指示。
在任何一種情況下,提供的 iccandidate 中的 m-section 索引, MID,和 ufrag 字段被用來確定 iccandidate 屬于哪個 m-section 和 ICE candidate,如上文 3.5.2.1 節所述。在候選結束指示的情況下,m-section 索引和 MID 字段的空值被解釋為表示該指示適用于指定 ICE 候選生成中的所有 m-section 。但是,如果對于新的遠程候選對象,這兩個字段都為空,則必須將其視為無效條件,如下所述。
如果任何 iccandidate 字段包含無效值或在處理 iccandidate 對象時發生錯誤,必須忽略提供的 iccandidate 并返回一個錯誤。
否則,將向 ICE 代理提供新的遠程候選項或候選結束項指示。對于一個新的遠程候選對象,連接檢查將被發送到新的候選對象,假設已經調用了 setLocalDescription 來初始化 ICE 收集過程。
4.1.20. onicecandidate 事件
oniccandidate 事件在兩種情況下被發送給應用程序:
在第一種情況下,新發現的候選對象反映在iccandidate對象中,并且它的所有字段必須是非空的。根據為Trickle ICE定義的規則,這個候選也將被添加到當前和/或待處理的本地描述中。
在第二種情況下,事件的 iccandidate 對象必須將其候選字段設置為 null,以表明當前收集階段已經完成,也就是說,在這個階段將不再有其他的候選事件。然而,iccandidate 的 ufrag 字段必須被指定,以表示哪個 ICE 候選的生成正在結束。ICE candidate 的 m-section 和 MID 字段可以指定,表示該事件適用于一個特定的 m-section,或者設置為 null,表示該事件適用于所有在收集階段的 m-section。這個事件可以被應用程序用來生成一個候選結束的指示,定義在 [RFC8838] 第13節中。
4.2. RtpTransceiver
4.2.1. stop
stop 方法停止 RtpTransceiver。這將導致以后調用 createOffer 為關聯的 m-section 生成一個零端口。詳情見下文。
4.2.2. stopped
stopped 屬性指示收發器是否已經停止,可以通過調用停止,也可以通過應用拒絕關聯的 m-section 的 answer。在這兩種情況下,它都被設置為 “true”,否則將被設置為 “false”。
一個停止的 RtpTransceiver 不發送任何 RTP 或 RTCP,也不處理任何流入的 RTP 或 RTCP,并且無法重啟。
4.2.3. setDirection
setDirection 方法設置 RtpTransceiver 的方向,它會在以后調用 createOffer 和 createAnswer 時影響關聯的 m-section 方向屬性。方向允許的值為 “recvonly”,“sendrecv”,“sendonly” 和 “inactive”,鏡像了[RFC4566] 章節 6 中定義的同名方向屬性。
當創建 offer 時,RtpTransceiver 方向直接反映在輸出,即使是重新 offer。當創建 answer 時,RtpTransceiver 方向為與 offer 方向的交集,如下面的 5.3 節所述。
注意,當 setDirection 設置收發器的 direction 屬性時(4.2.4 節),這個屬性不會立即影響收發器的 RtpSender 是否會發送或者 RtpReceiver 是否會接收。有效的方向是由 currentDirection 屬性表示的,它只在應用一個 answer 時更新。
4.2.4. direction
direction 屬性表示傳遞給 setDirection 的最后一個值。如果 setDirection 從未被調用,它將被設置為 RtpTransceiver 初始化時的方向。
4.2.5. currentDirection
currentDirection 屬性表示 RtpTransceiver 關聯的 m-section 最后協商的方向。更具體地說,它指示了最后一個應用 answer (包括臨時 answer)中相關的 m-section 的方向屬性 [RFC3264],如果它是遠程 answer,則 “send” 和"recv" 方向顛倒。例如,如果遠程應答中關聯的 m-section 的 direction 屬性是 “recvonly”,則 currentDirection 被設置為 “sendonly”。
如果一個引用這個 RtpTransceiver 的 answer 還沒有被應用,或者 RtpTransceiver 已經停止,currentDirection 將被設置為 “null”。
4.2.6. setCodecPreferences
setCodecPreferences 方法設置 RtpTransceiver 的編解碼器首選項,這反過來影響在以后調用 createOffer 和 createAnswer 時關聯的 m-section 出現和編解碼器的順序。請注意,setCodecPreferences 并不直接影響實現決定發送哪個編解碼器。它只影響實現表明它更喜歡通過提供或回答接收哪種編解碼器。即使一個編解碼器被 setCodecPreferences 排除,它仍然可以被用來發送,直到下一個 offer/answer 交換丟棄它。
RtpTransceiver 的編解碼器首選項可能導致后續 createOffer 和 createAnswer 調用排除掉相關的編解碼器,在這種情況下,關聯的 m-section 中相應的媒體格式將被排除。編解碼器首選項不能添加不存在的媒體格式。
RtpTransceiver 的編解碼器首選項還可以確定 createOffer 和 createAnswer 后續調用中的編解碼器順序,在這種情況下,關聯的 m-section 中的媒體格式的順序將遵循指定的首選項。
5. SDP 交互過程
本節介紹創建和解析 SDP 對象時需要遵循的具體步驟。
5.1. 需求概述
JSEP 實現必須遵守下面列出的規范,這些規范管理 offer 和 answer 的創建和處理。
5.1.1. Usage Requirements
所有由 JSEP 實現處理的會話描述,無論是本地的還是遠程的,都必須表示對以下規范的支持。如果其中任何一項不存在,則必須將此遺漏視為錯誤。
- 必須使用 [RFC8445] 中規定的 ICE。注意,遠程端點可以使用精簡實現;實現必須正確處理使用 ICE-lite 的遠程端點。遠程端點也可以使用舊版本的ICE;實現必須正確處理 [RFC5245] 中規定的使用ICE的遠程端點。
- 必須使用 DTLS [RFC6347] 或 DTLS-SRTP [RFC5763],根據媒體類型的不同,如 [RFC8827] 所述。
SRTP 密鑰的 SDP 安全描述機制 [RFC4568] 不能被使用,如在 [RFC8827] 中討論的那樣。
5.1.2. Profile Names and Interoperability
對于媒體 m-section,JSEP 實現必須支持在 [RFC5764] 中指定的 “UDP/TLS/RTP/SAVPF” profile,以及在 [RFC7850] 中指定的 “TCP/DTLS/RTP/SAVPF” profile,并且必須為他們在 offer 中產生的每一個媒體 m-section 指定這些profile 之一。對于數據 m-section,實現必須支持 “UDP/DTLS/SCTP” profile 以及 “TCP/DTLS/SCTP” profile,并且必須為他們在 offer 中產生的每一個數據 m-section 指明這些 profile 中的一個。確切的 profile 是由與當前默認或選擇的候選 ICE 相關的協議決定的,如 [RFC8839] 4.2.1.2 節所述。
不幸的是,為了兼容性一些端點會生成不同規則的概要文件字符串,即使它們支持這些概要文件中的一個。例如,端點可能生成 “RTP/AVP”,但提供 “a=fingerprint” 和 “a=rtcp-fb” 屬性,表示它愿意支持 “UDP/TLS/RTP/SAVPF” 或 “TCP/TLS/RTP/SAVPF”。為了簡化與這些端點的兼容性,JSEP 實現在處理接收到的 offer 中的媒體 m-section 時必須遵循以下規則:
- 以下 offer 中出現的 profile 需要被接受:
- “RTP/AVP” (defined in [RFC4566], Section 8.2.2)
- “RTP/AVPF” (defined in [RFC4585], Section 9)
- “RTP/SAVP” (defined in [RFC3711], Section 12)
- “RTP/SAVPF” (defined in [RFC5124], Section 6)
- “TCP/DTLS/RTP/SAVP” (defined in [RFC7850], Section 3.4)
- “TCP/DTLS/RTP/SAVPF” (defined in [RFC7850], Section 3.5)
- “UDP/TLS/RTP/SAVP” (defined in [RFC5764], Section 9)
- “UDP/TLS/RTP/SAVPF” (defined in [RFC5764], Section 9)
- profile 所匹配 m-section 的 answer 必須與 offer 匹配;
- 由于 DTLS-SRTP 是必選項, 所以 SAVP or AVP 并不是必須的; 是否支持 DTLS-SRTP 也可以通過 “a=fingerprint” 屬性判斷。需要注意,缺少 “a=fingerprint” 屬性將會引起協商失敗。
- AVPF 或 AVP的使用只是控制用于 RTCP feedback 的間隔規則。如果提供了 AVPF 或 “a=rtcp-fb” 屬性,假設存在 AVPF,即默認值為 “trr-int=0”。否則,假設 AVPF 是在AVP 兼容模式下使用的,并使用 “trr-int=4000” 的值。
- 在數據 m-section, 實現中必須支持 “UDP/DTLS/SCTP”, “TCP/DTLS/SCTP” 和 “DTLS/SCTP” (為了向后兼容) profile.
5.2. Constructing an Offer
當 createOffer 被調用時,必須創建一個新的 SDP 描述,包含在 [RFC8834] 中指定的功能。這個過程的具體細節如下所述。
5.2.1. Initial Offers
當 createOffer 第一次被調用時,其結果被稱為初始 offer。
生成初始 offer 的第一步是生成會話級屬性,如[RFC4566] 5 節所述。具體地:
- 第一個 SDP line 必須是 “v=0”,定義在[RFC4566],章節5.1。
- 第二個 SDP line 必須是一個 “o=” 的行,定義在[RFC4566],章節5.2。<username> 字段的值應該是 “-”。<session-id> 必須由64位有符號整數表示,且取值必須小于2^63-1。建議通過生成一個 64 位的隨機數來構造 <session-id>,其中最高位設置為 0,其余 63 位采用加密隨機方式。<nettype> <addrtype> <unicast-address> 組合的值應該設置為一個無意義的地址,例如 “IN IP4 0.0.0.0”,以防止本端 IP地址泄露到該字段中,這個問題在 [RFC8828] 中進行了討論。正如在 [RFC4566] 中提到的,整個 “o=” 行需要是唯一的,通過為 <session-id> 選擇一個隨機數就足以實現這一點。
- 第三個 SDP 行必須是 “s=” 的行,在 [RFC4566] 5.3 節中定義;為了匹配 “o=” 行,應該使用 “-” 作為會話名稱,例如,“s=-”。請注意,這與 [RFC4566] 中的建議不同,后者建議使用單個空格,但由于 “o=” 和"s=" 在 JSEP 中都是無意義的,因此擁有相同的無意義值似乎更清晰。
- 會話信息(“i=”),URI(“u=”),電子郵件地址(“e=”),電話號碼(“p=”),重復時間(“r=”),和時區(“z=”)行在這個上下文中是沒有用的,不應該被包括。
- 加密密鑰(“k=”)行不能提供足夠的安全性,絕對不能包含。
- 必須添加 “t=” 行,如 [RFC4566] 5.9 節所述; <start-time> 和 <stop-time> 都應該設置為 0,例如"t=0 0"。
- 必須添加 “a=ice-options” 行以及 “trickle” 和 “ice2” 選項,具體請參見 [RFC8840] 4.1.1 節和 [RFC8445] 10 節。
- 如果使用 WebRTC 標識,必須添加 “a=identity” 行,如 [RFC8827] 5 節所述。
下一步是生成 m-section,如 5.14 節中 [RFC4566] 所述。一個 m-section 是一個已被添加到 PeerConnection 中的 RtpTransceiver,排除任何停止的 RtpTransceiver;這是按照 RtpTransceivers 被添加到 PeerConnection 的順序完成的。如果沒有這樣的 RtpTransceiver,則沒有 m-section 生成;后面可以添加更多內容,如[RFC3264] 5節所述。
對于為 RtpTransceiver 生成的每個 m-section,在 RtpTransceiver 和生成的 m-section 索引之間建立一個映射。
每個 m-section,如果沒有標記為 “bundle-only”,必須包含唯一的 ICE 證書集和唯一的 ICE 候選集。標記了 bundle-only 的 m-section 不能包含任何 ICE 證書,也不能收集任何候選。
對于 DTLS,所有的 m-section 必須使用 PeerConnection 指定的任何和所有證書;因此,它們必須都具有相同的指紋值或值 [RFC8122],或者這些值必須是會話級屬性。
每個 m-section 必須按照 [RFC4566] 5.14 節中指定的方式生成。對于 m-section 本身,必須遵守以下規則:
- 如果 m-section 被標記為 bundle-only,那么 <port> 值必須設置為 0。否則,值將被設置為 m-section 的默認的 ICE 候選端口,但是考慮到沒有可用的候選端口,必須使用默認的端口值 9 (Discard),如 RFC8840 4.1.1 節所示。
- 為了正確地指示使用 DTLS, profile 必須設置為 “UDP/TLS/RTP/SAVPF”,如 [RFC5764] 8節所規定的。
- 如果已為相關的 RtpTransceiver 設置了編解碼器首選項,則必須按相應的順序生成媒體格式,并且必須排除編解碼器首選項中不存在的任何編解碼器。
- 除上述限制外,媒體格式必須包括 [RFC7874] 第3節和 [RFC7742] 第5節中規定的強制性音頻/視頻編解碼器。
m-section 后面必須緊跟著 “c=” 行,如[RFC4566] 5.7節所述。同樣,由于沒有候選項可用,“c=” 行必須包含默認值 “IN IP4 0.0.0.0”,如 [RFC8840] 4.1.1 節所定義。
[RFC8859] 將 SDP 屬性分類。為了 bundling 時不必要的重復,類別相同或傳輸屬性不需要在 bundling 的 “m-section” 中重復,[RFC8843]7.1.3 節中有說明。這包括 m-section,bundling 已經協商好,仍然是需要的,以及“m=”部分標記為 bundle-only。
以下屬性,不是相同或運輸的類別,必須包含在每個 m-section:
- “a=mid” 行,如 [RFC5888] 4 節中所述。所有的 MID 值必須以一種不泄漏用戶信息的方式生成,例如,隨機或使用每個 PeerConnection 計數器,并且應該是3字節或更少,以允許它們有效地適合在[RFC8843]章節15.2中定義的RTP頭擴展。注意,這并沒有設置RtpTransceiver mid屬性,因為這只在應用描述時才會發生。此時,生成的MID值可以被認為是“建議的”MID。
- 方向屬性,與關聯的 RtpTransceiver 的方向屬性相同。
- 對于媒體 m-section,“a=rtpmap” 和 “a=fmtp” 行描述的每一種媒體格式,如 [RFC4566] 6 節和 [RFC3264] 5.1 節所述。
- 對于每個需要使用 RTP 重傳的主編解碼器,對應的 “a=rtpmap” 指示主編解碼器的時鐘速率的 “rtx”,對應的 “a=fmtp” 指示主編解碼器的有效負載類型,如 [RFC4588] 8.1 節所述。
- 對于每個支持的前向糾錯 (FEC) 機制,“a=rtpmap” 和 “a=fmtp” 行,如 [RFC4566] 6 節所述。必須支持的 FEC 機制在 [RFC8854] 7 節中指定,每種媒體類型的具體用法在第 4 節和第 5 節中概述。
- 如果這個 m-section 用于每個包的媒體持續時間可配置的媒體,例如,音頻,一個 “a=maxptime” 表示可以封裝在每個包中的最大媒體量,以毫秒為單位,如 [RFC4566] 6節中所規定的。該值被設置為 m-section 中包含的所有編解碼器的最大持續時間值中的最小值。
- 如果這個“m=”部分用于視頻媒體,并且已知可以解碼的圖像的大小有限制,增加 “a=imageattr” 行,如 3.6 節所述。
- 對于每個支持的 RTP 報頭擴展,一個 “a=extmap” 行,如[RFC5285] 5 節中指定的。應該/必須支持的報頭擴展列表在 [RFC8834] 5.2 節中指定。任何需要加密的頭部擴展必須在 [RFC6904] 4 節中指定。
- 對于每個支持的 RTCP 反饋機制,一個 "a=rtcp-fb”行,如[RFC4585] 4.2 節所述。應該/必須支持的 RTCP 反饋機制列表在 [RFC8834] 5.1 節中指定。
- 如果 RtpTransceiver 是 sendrecv 或 sendonly 方向:
- 對于通過 addTrack 或 addTransceiver 創建的每個 MediaStream,一個 “a=msid” 行,如 [RFC8830] 2 節中指定的,但省略了 “appdata” 字段。
- 如果 RtpTransceiver 是 sendrecv 或 sendonly 方向,并且應用程序已經為一個編碼指定了 rid-id,或者在 RtpSenders 的參數中指定了多個編碼,則為每個指定的編碼指定一個 “a=rid” 行。“a=rid” 行在 [RFC8851] 中指定,它的方向必須是 “send”。如果應用程序已經選擇了 rid-id,它必須被使用;否則 rid-id 必須由實現生成。rid-ids 生成不能泄露用戶信息,例如隨機或使用 每個 PeerConnection 計數器( [RFC8852] 3.3節),并且應該 3 個字節或更少,讓他們有效地融入RTP報頭中定義擴展 [RFC8852] 3.3節。如果沒有指定編碼,或者只指定了一個編碼但沒有指定 rid-id,則不會生成 “a=rid” 行。
- 如果 RtpTransceiver 是 sendrecv 或 sendonly 方向,并且產生了一個以上的 “a=rid” 行,一個 “a=simulcast” 行,方向 “send”,定義在 [RFC8853] 5.1 節。關聯的 rid-id 集合必須包含 m-section 的 "a=rid"行中使用的所有 rid-id。
- 如果這個 PeerConnection的 bundle policy 被設置為 “max-bundle” 并且這不是第一個 m-section ,或者這個 bundle policy 被設置為 “balanced” 并且這不是這個媒體類型的第一個 m-section,需要 “a=bundle-only” 行。
以下屬性,它們屬于 IDENTICAL 或 TRANSPORT 類別,但必須只出現在 m-section 中,這些部分要么有唯一的地址,要么與 bundle 標簽相關聯。(在最初的 offer 中,這意味著那些 m-section 不包含 “a=bundle-only” 屬性。)
- “a=ice-ufrag” 和 “a=ice-pwd” 行,參見[RFC8839]章節5.4。
- 對于每個所需的摘要算法,每個端點的證書都有一個或多個 “a=fingerprint” 行,如 [RFC8122] 5節所述。
- “a=setup” 行,如[RFC4145] 4 節中規定的,并在[RFC5763] 5 節中說明了用于 DTLS-SRTP 場景的用法。offer 中的角色值必須為 “actpass”。
- “a=tls-id” 行,如[RFC8842] 5.2 節所述。
- 一個 “a=rtcp” 行,如[RFC3605] 2.1 節,包含默認值 “9 in IP4 0.0.0.0”,因為還沒有收集候選。
- “a=rtcp-mux” 行,如 [RFC5761] 5.1.3 節所述。
- 如果 RTP/RTCP 復用策略為 “require”,則在 [RFC8858] 4節中指定 “a=rtcp-mux-only” 行。
- 一個 “a=rtcp-rsize” 行,在[RFC5506] 5 節中指定。
最后,如果創建了數據通道,則必須為數據生成 m-section。<media> 必須設置為 “application”,必須設置為 “UDP/DTLS/SCTP” [RFC8841]。<fmt> 的值必須設置為 “webrtc-datchannel”,如 [RFC8841] 4.2.2 節所述。
在數據 m-section,一個 “a=mid” 行必須被生成,并包括在上面描述的,連同一個 “a=sctp-port” 行引用 SCTP 端口號,如 [RFC8841] 5.1 節中定義;并且如果合適的話,一個 “a=max-message-size” 行,在[RFC8841] 6.1 節定義。
如上所述,只有當數據 m-section 部分有一個唯一的地址或與 bundle 標簽相關聯(例如,如果它是唯一的 m-section)時,下列 IDENTICAL 或 TRANSPORT 屬性才會被包含:
- “a=ice-ufrag”
- “a=ice-pwd”
- “a=fingerprint”
- “a=setup”
- “a=tls-id”
一旦所有的 m-section 被生成,一個會話級的 “a=group” 屬性必須被添加,在 [RFC5888] 中規定。這個屬性必須具有 “BUNDLE” 的語義,并且必須包含每個 m-section 的 MID。這樣做的結果是,JSEP 實現將所有 m-section 作為一個 bundle group 提供。然而,m-section 是否為 bundle-only,取決于 bundle 策略。
下一步是生成會話級別的 LS 同步組,定義在[RFC5888] 7 節。對于每個被多個 RtpTransceiver 引用的 MediaStream (通過將這些 MediaStream 作為參數傳遞給 addTrack 和 addTransceiver 方法),必須添加一組類型為 “LS” 的 group,該 group 包含每個 RtpTransceiver 的 MID 值。
SDP 允許應該在媒體級的屬性既放在會話級又放在媒體級,即使它們是相同的。這有助于開發和調試,使它更容易理解各個媒體部分,特別是當一組最初相同的屬性中的一個后來被更改時。然而,實現可以選擇在會話級別聚合屬性,而 JSEP 實現必須準備在任意位置識別屬性。
可以包含除上述規定之外的其他屬性,但以下屬性除外,它們與 [RFC8834] 的要求不兼容且絕對不能包含:
- “a=crypto”
- “a=key-mgmt”
- “a=ice-lite”
注意,當使用 bundle 時,任何附加的屬性必須遵循 [RFC8859] 中關于這些屬性如何與 bundle 交互的建議。
請注意,這些要求在某些情況下比 SDP 的要求更為嚴格。實現必須準備接受兼容的 SDP,即使它不符合本規范中生成 SDP 的要求。
5.2.2. Subsequent Offers
當多次調用 createOffer,或者在本地描述已經設置之后調用,處理過程與初始 offer 略有不同。
如果之前的 offer 沒有使用 setLocalDescription 應用,這意味著 PeerConnection 仍然處于 “stable” 狀態,必須遵循生成初始 offer 的步驟,受以下限制:
- “o=” 行中的字段必須保持不變,除了 <session-version> 字段,如果 createOffer 的輸出可能與之前 createOffer 的輸出不同,則 <session-version> 字段必須在每次調用 createOffer 時遞增 1;實現可以選擇在每次調用時遞增 <session-version>。生成的 <session-version> 的值與當前本地描述的無關;特別注意以下情況,如果當前版本為 N ,一個 offer 被創建并應用于版本 N+1,然后該 offer 被回滾,以便當前版本再次為 N,但是下一個生成的 offer 將為版本 N+2。
請注意,如果應用程序通過讀取 currentLocalDescription 而不是 createOffer 來創建一個 offer,返回的 SDP 可能與最初調用 setLocalDescription 時不同,因為添加了收集的 ICE 候選項,但 <session-version> 將不會改變。沒有已知的情況會導致問題,但如果這是一個問題,解決方案是簡單地使用 createOffer 來確保唯一的 <session-version>。
如果前面使用 setLocalDescription 提供應用,但相應的來自遠端的 answer 尚未應用,這意味著 PeerConnection 仍在 “have-local-offer” 狀態,offer 的生成將遵循以下規則:
- “s=” 和 “t=” 行必須保持不變。
- 如果有任何 RtpTransceiver 被添加,并且在當前本地描述或遠程描述中存在一個 “m=” 的 0 端口區域,m-section 必須被回收,為添加的 RtpTransceiver 生成一個 m-section ,就像 m-section 被添加到會話描述中(包括一個新的 MID 值),并將其放在與 m-section 相同的索引和 0 端口。
- 如果 RtpTransceiver 被停止并且沒有與 m-section 相關聯,則絕對不能為它生成 m-section 。這阻止了 RtpTransceiver 的 m-section 被回收,并在之前的 offer/answer 交換中用于新的 RtpTransceiver,如上所述。
- 如果 RtpTransceiver 已經停止并與一個 m-section 相關聯,并且 m-section 沒有被回收,如上所述,一個 m-section 必須為它生成與端口設置為 0 和刪除所有 “a=msid” 行。
- 對于沒有停止的 RtpTransceiver,“a=msid” 行必須保持不變,如果它們存在于當前的描述中,無論 RtpTransceiver 的方向或 track 的變化。如果在當前描述中沒有 “a=msid” 行,“a=msid” 行(s)必須根據與初始 offer 相同的規則生成。
- 每一行 “m=” 和 “c=” 必須填寫端口,相關的 RTP profile,和 m-section 的默認候選地址,如[RFC8839],4.2.1.2 節和 5.1.2 節所述。如果還沒有收集 RTP 候選項,則必須仍然使用默認值,如上所述。
- 每一行 “a=mid” 必須保持一致。
- “a=ice-ufrag” 和 “a=ice-pwd” 必須保持不變,除非 ICE 配置發生了改變(例如,改變了支持的 STUN/TURN 服務器或 ICE 候選策略)或 icerstart 選項(5.2.3.1 節)被指定。如果 m-section 被捆綁到另一個 m-section 中,它仍然不能包含任何 ICE 憑證。
- 如果 m-section 沒有被捆綁到另一個 m-section 中,它的 “a=rtcp” 屬性行必須被填充默認 rtcp 候選的端口和地址,如 RFC5761 5.1.3 節所示。如果還沒有收集 RTCP 候選項,則必須使用默認值,如 5.2.1 節所述。
- 如果 m-section 沒有捆綁到另一個 m-section,對于在最近的收集階段(參見章節3.5.1)收集的每個候選,必須添加一個 “a=candidate” 行,如 [RFC8839] 5.1 節所定義。如果該 m-section 的候選收集已經完成,則必須添加一個 “a=end-of-candidates” 屬性,如 [RFC8840] 8.2 節所述。如果 m-section 被捆綁到另一個 m-section 中,那么 “a=candidate” 和 “a=end-of-candidates” 都必須被省略。
- 對于仍然存在的 RtpTransceiver,“a=rid” 行必須保持不變。
- 對于仍然存在的 RtpTransceiver,任何 “a=simulcast” 行必須保持不變。
如果之前的 offer 是使用 setLocalDescription 應用的,而來自遠端相應的 answer 已經使用 setRemoteDescription 應用,這意味著 PeerConnection 是在 “have-remote-pranswer” 狀態或 “stable” 狀態,根據協商的會話描述,按照上面提到的 “have-local-offer” 狀態的步驟生成 offer。
此外,對于新 offer 中每一個現有的、不可回收的、不可拒絕的 m-section,根據當前本地或遠程描述中相應的 m-section 的內容,視情況作出以下調整:
- m-section 和相應的 “a=rtpmap” 和 “a=fmtp” 行必須只包含沒有被相關 RtpTransceiver 的編解碼器首選項排除的媒體格式,而且必須包括所有當前可用的格式。先前提供但不再提供的媒體格式(例如,共享硬件編解碼器)可能被排除在外。
- 除非已為相關聯的 RtpTransceiver 設置了編解碼器首選項,否則 m-section 上的媒體格式必須按照與最近的 answer 相同的順序生成。任何媒體格式,沒有出現在最近的 answer 必須添加在所有現有的格式之后。
- RTP 頭部擴展必須只包含那些在最近的 answer 中出現的。
- RTCP 反饋機制必須只包括最近的 answer 中出現的那些,除了引用新添加的媒體格式的特定于格式的機制。
- 如果最近的 answer 包含了 “a=rtcp-mux” 行,那么 “a=rtcp” 行絕對不能被添加。
- “a=rtcp-mux” 必須與最近的 answer 一致。
- 不能添加 “a=rtcp-mux-only” 行。
- “a=rtcp-rsize” 行不能添加,除非出現在最近的 anwer 中。
- 不能添加 “a=bundle-only” 行,定義在 [RFC8843] 6 節。相反,JSEP 實現必須簡單地省略綁定了 m-section 的 IDENTICAL 和 TRANSPORT 類別中的參數,如 [RFC8843] 7.1.3 節所述。
- 注意,如果媒體 m-section 被捆綁到數據 m-section 中,那么某些 TRANSPORT 和 IDENTICAL 的屬性可能會出現在數據 m-section 中,即使它們只適合于媒體 m-section (例如,“a=rtcp-mux”)。這在初始 offer 中不可能發生,因為在初始 offer 中,JSEP 實現總是在數據 m-section 之前列出媒體 m-section,而且至少有一個媒體 m-section 將沒有 “a=bundle-only” 屬性。因此,在最初的 offer 中,任何 “a=bundle-only” 的 m-section 將被捆綁到前面的非 bundle-only 媒體 m-section 中。
“a=group:BUNDLE” 屬性必須包含在最近的 answer 中 BUNDLE 組中指定的 MID 標識符,減去任何被標記為被拒絕的 m-section,加上任何新添加或重新啟用的 m-section。換句話說,bundle 屬性必須包含所有之前綁定的 m-section,只要它們還是活動狀態,以及任何新的 m-section。
“a=group:LS” 屬性生成與首次 offer 相同的方式,有額外的規定,任何 LS 同步組出現在最近的 answer 必須繼續存在并必須包含任何以前現有 MID,只要確定 m-section 仍然存在并且沒有被拒絕, group 仍然包含至少兩個 MID 標識符。這確保了任何同步的 “recvonly” m-section 在新的 offer 中繼續同步。
5.2.3. Options Handling
createOffer 方法接受一個 RTCOfferOptions 對象作為參數。如果存在以下選項,則在生成 SDP 描述時執行特殊處理。
5.2.3.1. IceRestart
如果 IceRestart 選項被指定為 true,則 offer 必須通過生成新的 ICE ufrag 和 pwd 屬性來指示 ICE 重啟,如 [RFC8839] 4.4.3.1.1 節所述。如果在初始 offer 中指定了這個選項,則沒有效果(因為已經生成了一個新的 ICE ufrag 和 pwd )。類似地,如果 ICE 配置改變了,這個選項也沒有作用,因為新的 ufrag 和 pwd 屬性將自動生成。在應用程序檢測到故障的情況下,此選項主要用于重新建立連接。
5.2.3.2. VoiceActivityDetection
靜音抑制,也稱為不連續傳輸(“DTX”),可以在沒有檢測到語音活動時切換到一種特殊的編碼,從而減少音頻使用的帶寬,但代價是會引起一些失真。
如果指定 “VoiceActivityDetection” 選項為 true,必須注明提供支持沉默抑制的音頻接收包括舒適噪聲(“CN”)為每個提供音頻編解碼器編解碼器,按照[RFC3389] 5.1節中定義,除非編解碼器有自己的內部靜音抑制支持。對于具有自身內部靜音抑制支持的編解碼器,必須為該編解碼器指定適當的 fmtp 參數,以表示需要對接收的音頻進行靜音抑制。例如,當使用 Opus 編解碼器 [RFC6716] 時,在 [RFC7587] 中指定的 “usedtx=1” 參數將被用于 offer。
如果 “VoiceActivityDetection” 選項被指定為 false,則 JSEP 實現不能使用 “CN” 編解碼器。對于具有自身內部靜音抑制支持的編解碼器,必須指定該編解碼器的適當的 fmtp 參數,以表示不希望對接收的音頻進行靜音抑制。例如,當使用 Opus 編解碼器時,“usedtx=0” 參數將在 offer 中指定。此外,在 “VoiceActivityDetection” 選項被指定為 false 的場景下,實現絕對不能對它產生的媒體使用靜音抑制,無論 “CN” 編解碼器或相關的 fmtp 參數是否出現在對等體的描述中。這些規則的影響 JSEP 中的靜音抑制依賴于雙方的相互協議,這確保了一致的處理,無論使用哪種編解碼器。
在[RFC6464] 4 節中描述的客戶端到混音器音頻電平報頭擴展的信令中,“VoiceActivityDetection” 選項對 “vad” 值的設置沒有任何影響。
5.3. Generating an Answer
當 createAnswer 被調用時,必須創建一個新的 SDP 描述,該描述必須與提供的遠程描述以及在 [RFC8834] 中指定的要求兼容。這個過程的具體細節如下所述。
5.3.1. Initial Answers
當提供遠程描述后第一次調用 createAnswer 時,結果被稱為初始 answer。如果沒有安裝遠程描述,則無法生成 answer,并且必須返回一個錯誤。
請注意,遠程描述 SDP 可能不是由 JSEP 端點創建的,可能不符合 5.2 節中列出的所有要求。在許多情況下,這不是問題。然而,如果任何強制性的 SDP 屬性缺失,或者上面列出的強制性使用的功能不存在,這必須被視為一個錯誤,并且必須導致受影響的 m-section 被標記為拒絕。
生成初始 answer 的第一步是生成會話級屬性。這里的過程是相同的,表明在上面 5.2.1 節中,除了 “a=ice-options” 和 “trickle” 選項,在 [RFC8840] 4.1.3 節中被定義, 和 “ice2” 選項在 [RFC8445] 10 節中被定義,包括這樣一個選項如果出現在 offer 中。
下一步是生成會話級的 LS group,如 [RFC5888] 7 節中定義的那樣。對于 offer 中出現的每一組類型為 “LS” 的 group,選擇由指定組中的 MID 值引用的本地 RtpTransceiver,并確定其中哪個引用了一個公共的本地 MediaStream(在 addTrack/addTransceiver 調用中指定用于創建它們)或沒有 MediaStream 引用,因為它們不是由 addTrack/addTransceiver 創建的。如果存在至少兩個這樣的 RtpTransceiver,必須添加一組類型為 “LS” 的 RtpTransceiver 的 MID 值。否則,必須忽略提供的 LS group,answer 中不生成相應的組。
作為一個簡單的例子,考慮一下下面提供的同一個 MediaStream 中包含的單個音頻和單個視頻 track。為了清晰起見,與本例無關的 SDP line 已被刪除。正如 5.2 節中所解釋的,添加了一組類型為 “LS” 的引用每個 track 的 RtpTransceiver。
a=group:LS a1 v1m=audio 10000 UDP/TLS/RTP/SAVPF 0a=mid:a1a=msid:ms1m=video 10001 UDP/TLS/RTP/SAVPF 96a=mid:v1a=msid:ms1如果 answer 使用一個單一的 MediaStream,當它添加 track,它的兩個 RtpTransceiver 將引用這個流,所以隨后的 answer 將包含一個 “LS” 組相同的 answer,如下圖所示:
a=group:LS a1 v1m=audio 20000 UDP/TLS/RTP/SAVPF 0a=mid:a1a=msid:ms2m=video 20001 UDP/TLS/RTP/SAVPF 96a=mid:v1a=msid:ms2然而,如果 answer 將其 track 分組到單獨的 MediaStreams 中,它的 RtpTransceiver 將引用不同的流,因此隨后的回答將不包含 “LS” group。
m=audio 20000 UDP/TLS/RTP/SAVPF 0a=mid:a1a=msid:ms2am=video 20001 UDP/TLS/RTP/SAVPF 96a=mid:v1a=msid:ms2b最后,如果 answer 沒有添加任何 track,它的 RtpTransceiver 將不會引用任何 MediaStream,為了維護 offer 的首選項,隨后的 answe 將包含相同的 “LS” group。
a=group:LS a1 v1m=audio 20000 UDP/TLS/RTP/SAVPF 0a=mid:a1a=recvonlym=video 20001 UDP/TLS/RTP/SAVPF 96a=mid:v1a=recvonly7.2 節中的例子展示了一個更復雜的 “LS” group 生成示例。
下一步是為遠程 offer 中出現的每個 m-section 生成一個 m-section,如 [RFC3264] 6 節中所述。出于討論的目的,offer 中的任何會話級屬性如果也是有效的媒體級屬性則被認為出現在每個 m-section 中。每個提供的 m-section 將有一個相關的 RtpTransceiver,如 5.10 節所述。如果 RtpTransceivers 比 m-section 多,不匹配的 RtpTransceiver 將需要在后續的 offer 關聯。
對于每個 offer 的 m-section,如果下列條件符合,對應的 answer 中 m-section 必須通過設置 <port> 為 0 標記為拒絕(在 [RFC3264] 6節中定義),進一步處理可以跳過 m-section:
- 關聯的 RtpTransceiver 已經停止。
- 沒有提供既支持又被編解碼器首選項(如果適用的話)允許的媒體格式。
- bundle 策略是 “max-bundle”,不是第一個 m-section,也不是和第一個 m-section 在同一個 bundle group 中。
- bundle 策略是 “balanced”,不是該媒體類型的第一個 m-section,也不是和第一個 m-section 在同一個 bundle group 中。
- 這個 m-section 在一個 bundle group 中,并且 offer 中標記的 m-section 由于上述原因之一被拒絕。這要求 bundle group 中所有 m-section 被拒絕,如 [RFC8843] 7.3.3 節所述。
否則,answer 中的每個 m-section 必須按照 [RFC3264] 6.1 節中的規定生成。對于 m-section 本身,必須遵守以下規則:
- <port> 值通常會被設置為 m-section 的默認的 ICE 候選端口,但是考慮到還沒有可用的候選端口,必須使用默認的值 9 (Discard),如 [RFC8840] 4.1.1 節所示。
- <proto> 字段必須被設置為與在 offer 中對應的 m-section 完全匹配。
- 如果已為相關的 RtpTransceiver 設置了編解碼器首選項,則媒體格式必須按相應的順序生成,而不管提供了什么,并且必須排除編解碼器首選項中沒有出現的任何編解碼器。
- 否則,m-section 上的媒體格式必須按照當前遠程描述中提供的格式的順序生成,不包括當前不支持的格式。當前遠程描述中不存在的任何當前可用的媒體格式必須添加到所有現有格式之后。
- 在任何一種情況下,answer 中的媒體格式必須包括 offer 中包含的至少一種格式,但可以包括在本地支持但 offer 中不包含的格式,如 [RFC3264] 6.1 節所述。如果不存在通用的格式, m-section 將被拒絕。
m-section 后面必須緊跟著 “c=” 行,如 [RFC4566] 5.7 節所述。同樣,由于沒有候選項可用,“c=” 行必須包含默認值 “IN IP4 0.0.0.0”,如 [RFC8840] 4.1.3 節所定義。
如果 offer 支持 bundle,所有 bundle 的 m-section 必須使用相同的 ICE 證書和候選;所有未 bundle 的 m-section 必須使用唯一的 ICE 證書和候選。每個 m-section 必須包含以下屬性(不同于 IDENTICAL 或 TRANSPORT):
- 當且僅當 offer 中出現 “a=mid” 行,如[RFC5888] 9.1 節所述。MID 值必須與 offer 中指定的值匹配。
- 方向屬性,通過應用 [RFC3264] 6.1 節中提供的方向規則來確定,然后與相關的 RtpTransceiver 的方向相交。例如,在 m-section 被提供為 “sendonly” 和本地 RtpTransceiver 被設置為 “sendrecv” 的情況下,answer 的結果是 “recvonly” 方向。
- 對于 m-section,"a=rtpmap"和 “a=fmtp” 行上的每一種媒體格式,如 [RFC4566] 6 節和 [RFC3264] 6.1 節所述。
- 如果 “rtx” 存在于 offer,則需要相應的 “a=rtpmap” 指示 “rtx” 主要編解碼器的時鐘頻率和一個 “a=fmtp” 引用聲明引用的的負載類型, [RFC4588] 8.1 節定義。
- 對于每個受支持的 FEC 機制,“a=rtpmap” 和 “a=fmtp” 行,如 [RFC4566] 6 節所述。必須支持的 FEC 機制在 [RFC8854] 7 節中指定,每種媒體類型的具體用法在第 4 節和第 5 節中概述。
- 如果這個 m-section 是針對每個包的媒體持續時間可配置的媒體,例如,音頻,需要增加 “a=maxptime” 行,5.2 節所述。
- 如果這個 m-section 用于視頻媒體,并且已知可以解碼的圖像的大小有限制,需要增加 “a=imageattr” 行,如 3.6 節所述。
- 對于 offer 中每個支持的 RTP 頭部擴展,需要增加 "a=extmap“ 行,如 [RFC5285] 5 節中所規定的。支持的報頭擴展列表在 [RFC8834] 5.2 節中指定。任何需要加密的報頭擴展在 [RFC6904] 4 節中指定。
- 對于每個支持的 RTCP 反饋機制,在 offer 中,“a=rtcp-fb” 行,如[RFC4585] 4.2 節所述。支持的 RTCP 反饋機制列表在 [RFC8834] 5.1 節中指定。
- 如果 RtpTransceiver 有一個 sendrecv 或 sendonly 方向:
- 對于通過 addTrack 或 addTransceiver 創建的每個 MediaStream,一個 “a=msid” 行,如在 [RFC8830] 2 節中指定的,但省略了 “appdata” 字段。
如果一個 m-section 沒有捆綁到另一個 m-section 必須包含以下屬性( IDENTICAL 或 TRANSPORT 類別):
- “a=ice-ufrag” 和 “a=ice-pwd” 行,參見[RFC8839] 5.4 節。
- 對于每個所需的摘要算法,每個端點的證書都有一個或多個 “a=fingerprint” 行,如 [RFC8122] 5 節所述。
- “a=setup” 行,如 [RFC4145] 4 節中規定的,并在 [RFC5763] 5 節中澄清了用于 DTLS-SRTP 場景的用法。answer 中的角色值必須是 “active” 或 “passive”。當 offer 包含 “actpass” 值時,就像 JSEP 端點的情況一樣,ansswer 應該使用 “active” 角色。來自非 JSEP 端點的 offer 可以發送 “a=setup” 的其他值,在這種情況下,answer 必須使用與 offer 中的值一致的值。
- “a=tls-id” 行,在[RFC8842] 5.3 節中指定。
- 如果在 offer 中出現 “a=rtcp-mux” 行,如 [RFC5761] 5.1.3 節所述。否則,“a=rtcp” 行,如[RFC3605] 2.1 節中指定的,包含默認值 “9 in IP4 0.0.0.0” (因為還沒有收集候選項)。
- 如果在 offer 中出現 “a=rtcp-rsize” 行,如[RFC5506]章節5所述。
如果提供了一個數據通道,則必須為數據 m-section。<media> 字段必須設置為 “application”,<fmt> 字段必須設置為與 offer 中的字段完全匹配。
在數據 m-section,一個 “a=mid” 行必須被生成,并包括在上面描述的,連同一個 “a=sctp-port” 行引用 SCTP 端口號,在 [RFC8841] 5.1 節中定義;并且可以增加 “a=max-message-size” 行,在 [RFC8841] 6.1 節中定義。
如上所述,只有當數據 m-section 沒有捆綁到另一個 m-section 時,才會包含 IDENTICAL 或 TRANSPORT 類別的以下屬性:
- “a=ice-ufrag”
- “a=ice-pwd”
- “a=fingerprint”
- “a=setup”
- “a=tls-id”
注意,如果媒體 m-section 被捆綁到數據 m-section 中,那么某些 TRANSPORT 和 IDENTICAL 屬性也可能出現在數據 m-section 中,即使它們只適合于媒體 m-section (例如,“a=rtcp-mux”)。
如果提供了具有 “BUNDLE” 語義的 “a=group” 屬性,則必須按照 [RFC5888] 的規定添加相應的會話級 “a=group” 屬性。這些屬性必須具有 “BUNDLE” 語義,并且必須包含所有來自所提供的 BUNDLE 組中沒有被拒絕的中間標識符。請注意,不管 offer 中是否存在 “a=bundle-only”,answer 中所有的 m-section 都不能有 “a=bundle-only” 行。
如果在會話級別顯式地定義為有效,那么所有 m-section 之間的公共屬性可以移動到會話級別。
在創建 offer 時被禁止的屬性也在創建 answer 時被禁止。
5.3.2. Subsequent Answers
當 createAnswer 在第二次(或更晚)時被調用,或者在已經安裝了本地描述之后被調用時,處理過程與初始 answer 略有不同。
如果之前的 answer 沒有使用 setLocalDescription 應用,這意味著 PeerConnection 仍然處于 “have-remote-offer” 狀態,則必須遵循生成初始 answer 的步驟,受以下限制:
- “o=” 行的字段必須保持不變,除了 <session-version> 字段,如果會話描述以任何方式改變以前生成的 answer,該字段必須遞增。
如果之前有任何會話描述被提供給 setLocalDescription,那么 answer 將會按照上面的 “have-remote-offer” 狀態的步驟生成,還有這些例外情況:
- “s=” 和 “t=” 行必須保持不變。
- 每一行 “m=” 和 “c=” 必須填寫 m-section 的默認候選端口和地址,如[RFC8839] 4.2.1.2 節所述。注意,在某些情況下,m-section 協議可能與默認候選協議不匹配,因為 m-section 協議的值必須匹配 offer 中提供的內容,如上所述。
- 每一行 “a=ice-ufrag” 和 “a=iec-pwd” 必須保持不變,除非 m-section 正在重啟,在這種情況下,新的 ICE 證書必須創建,如 [RFC8839] 4.4.1.1.1 節所述。如果 m-section 被捆綁到另一個 m-section中,它仍然不能包含任何 ICE 憑證。
- 每個 “a=tls-id” 行必須保持不變,除非 offer 的 “a=tls-id” 行改變了,在這種情況下,必須創建一個新的 tls-id 值,如 [RFC8842] 5.2 節所述。
- 每個 “a=setup” 行必須使用與現有的 DTLS 關聯一致的 “active” 或 “passive” 角色。
- 必須使用 RTCP 多路復用,當且僅當 m-section 之前使用了 RTCP 多路復用時,插入 “a=rtcp-mux” 行。
- 如果 m-section 沒有捆綁到另一個 m-section 并且 RTCP 復用未啟用,“a=rtcp-mux” 屬性行必須填寫默認 RTCP 候選端口和地址。如果還沒有收集 RTCP 候選項,則必須使用默認值,如上面 5.3.1 節所述。
- 如果 m-section 沒有捆綁到另一個 m-section,對于在最近(參見章節3.5.1)收集的每個候選,必須添加一個 “a=candidate” 行,如 [RFC8839] 5.1 節中定義。如果該 section 的候選收集已經完成,則必須添加一個 “a=end-of-candidates” 屬性,如 [RFC8840] 8.2 節所述。如果 m-section 被捆綁到另一個 m-section 中,那么 “a=candidate” 和 “a=end-of-candidates” 都必須被省略。
- 對于沒有停止的 RtpTransceiver,“a=msid” 行必須保持不變,無論 RtpTransceiver 的方向或 track 發生了什么變化。如果當前描述中沒有 “a=msid” 行,“a=msid” 行必須按照與初始 answer 相同的規則生成。
5.3.3. Options Handling
createAnswer 方法以一個 RTCAnswerOptions 對象作為參數。RTCAnswerOptions 的參數集不同于 RTCOfferOptions 中支持的參數集;IceRestart 選項是非必要的,因為在所有的 m-section 當提供者選擇執行ICE 重啟時,ICE 憑證將自動被更改。
RTCAnswerOptions支持以下選項。
5.3.3.1. VoiceActivityDetection
answer 是靜音抑制的處理如 5.2.3.2 節所述,但有一個例外:如果支持靜音抑制不是在 offer 中聲明,VoiceActivityDetection 參數沒有影響,answer 必須生成 VoiceActivityDetection 被設置為 “false”。這是在每個編解碼器的基礎上完成的(例如,如果提供以某種方式提供對 CN 的支持,但為 Opus 設置 “usedtx=0”,設置 VoiceActivityDetection 為 “true” 將導致一個 answer 與 CN 編解碼器和 “usedtx=0”)。這條規則的影響是應答者不會試圖對任何不提供靜音抑制的端點使用靜音抑制,使得靜音抑制保證雙向支持,即使是非 JSEP 端點。
5.4. Modifying an Offer or Answer
從 createOffer 或 createAnswer 返回的 SDP 在傳遞給 setLocalDescription 之前不能被更改。如果需要對 SDP 進行精確控制,必須使用前面提到的 createOffer/createAnswer 選項或 RtpTransceiver API。
在調用 setLocalDescription 提供一個 offer 或 answer 后,應用程序可以在發送到遠端之前修改 SDP 以減少它的能力,只要它遵循上面定義一個有效的 JSEP offer 或 answer 的規則。類似地,一個應用程序收到了來自對等端的 offer 或 answer,可能會在調用 setRemoteDescription 之前修改收到的 SDP,受同樣的約束。
與往常一樣,應用程序完全負責它發送給另一方的內容,所有傳入的 SDP 將由 JSEP 實現在其能力的范圍內處理。假設所有的 SDP 都格式正確是錯誤的,但是可以假設,該規范的任何實現都能夠處理來自該規范的任何其他實現的未修改的 SDP,作為遠端 offer 或 answer。
5.5. Processing a Local Description
當 SessionDescription 被提供給 setLocalDescription 時,必須執行以下步驟:
- 如果描述類型為 “rollback”,則按照 5.7 節定義的處理,跳過本節其余部分描述的處理。
- 否則,SessionDescription 的類型將根據 PeerConnection 的當前狀態進行檢查:
- 當類型為 offer 時,PeerConnection的狀態必須是 “stable” 或 “have-local-offer”。
- 當類型為 pranswer 或 answer 時,PeerConnection 狀態必須為 “have-remote-offer” 或 “have-local-pranswer”。
- 如果該類型對當前狀態不正確,處理必須停止并返回一個錯誤。
- 然后檢查 SessionDescription 以確保其內容與上次 createOffer/createAnswer 調用中生成的內容相同并且沒有被更改,5.4 節所討論的那樣;否則,處理必須停止并返回一個錯誤。
- 接下來,SessionDescription 被解析成一個數據結構,如下面 5.8 節所述。
- 最后,解析的 SessionDescription 被應用,如下 5.9 節所述。
5.6. Processing a Remote Description
當 SessionDescription 被提供給 setRemoteDescription 時,必須執行以下步驟:
- 如果描述類型為 “rollback”,則按照 5.7 節定義的處理,跳過本節其余部分描述的處理。
- 否則,SessionDescription 的類型將根據 PeerConnection 的當前狀態進行檢查:
- 如果是 offer, PeerConnection 的狀態必須是 “stable” 或者 “have-remote-offer”。
- 當類型為 pranswer 或 answer 時,PeerConnection 狀態必須為 “have-local-offer” 或 “have-remote-pranswer”。
- 如果該類型對當前狀態不正確,處理必須停止并返回一個錯誤。
- 接下來,SessionDescription 被解析成一個數據結構,如下面 5.8 節所述。如果解析由于任何原因失敗,處理必須停止并返回一個錯誤。
- 最后,解析的 SessionDescription 被應用,如下面的 5.10 節所述。
5.7. Processing a Rollback
當 PeerConnection 處于除 “stable” 之外的任何狀態時,可以執行 “rollback”。這意味著 offer 和 provisional answer 都可以回滾。回滾只能用于取消建議的更改;不支持從 “stable” 狀態回滾到以前的 “stable” 狀態。如果在 “stable” 狀態下嘗試回滾,處理必須停止并返回一個錯誤。請注意,這意味著一旦 answer 執行了 setLocalDescription 就不能回滾。
無論調用 setLocalDescription 還是 setRemoteDescription, rollback 的效果都必須相同。
為了處理回滾,JSEP 實現放棄當前的 offer/answer 事務,將信令狀態設置為 “stable”,并將掛起的本地或遠程描述設置為 “null”(參見 4.1.14 和 4.1.16 節)。由廢棄的局部描述分配的任何資源或候選都將被丟棄;接收到的任何媒體都按照前面的本地和遠程描述進行處理。
通過回滾會話描述的應用,回滾將任何與 m-section 相關聯的 RtpTransceiver 分離(參見章節5.10和5.9)。這意味著以前關聯的一些 RtpTransceiver 將不再與任何 m-secion 相關聯;在這種情況下,RtpTransceiver mid 屬性的值必須設置為 “null”,并且必須丟棄該 transceiver 和它的 m-section 索引之間的映射關系。如果 RtpTransceiver 是通過應用一個遠程提供創建的,被回滾后必須停止并從PeerConnection 移除。但是,如果通過 addTrack 方法將一個 track 附加到 RtpTransceiver 上,那么這個 RtpTransceiver 就不能被移除。這使得應用程序可以調用 addTrack,然后調用setRemoteDescription 提供一個 offer,然后回滾那個 offer,再次調用 createOffer,并在生成的 offer 中為添加的 track 對應的 m-section。
5.8. Parsing a Session Description
會話描述對象中包含的 SDP 由一系列的文本行組成,每一行包含一個 key-value 表達式,如 [RFC4566] 5 節所述。逐行讀取 SDP,并將其轉換為包含反序列化信息的數據結構。但是 SDP 允許許多類型的行,并不是所有的行都與 JSEP 應用程序相關。對于每一行,實現首先要根據其定義的 ABNF 語法確保正確,檢查它是否符合 [RFC4566] 和 [RFC3264] 中使用的語義,然后解析、存儲或丟棄所提供的值,如下所述。
如果任何一行的格式不對,或者不能按照描述解析,解析器必須錯誤地停止并拒絕會話描述,即使該值將被丟棄。這確保了實現不會意外地誤解存在二義性的 SDP。
5.8.1. Session-Level Parsing
首先,檢查和解析會話級別的行。這些行必須以特定的順序和特定的語法出現,如 [RFC4566] 5 節中定義的那樣。請注意,雖然特定的行類型(例如,“v=”,“c=”)必須以定義的順序出現,但相同類型的行(通常是 “a=” )可以以任何順序出現。
下面的非屬性行在 JSEP 上下文中沒有意義,并且可能在檢查后被丟棄。
- “c=” 行必須檢查語法,但它的值只用于 ICE 不匹配檢測,如 [RFC8445] 5.4 節中定義的那樣。注意,JSEP實現永遠不會遇到這種情況,因為 WebRTC 需要 ICE。
- “i=”,“u=”,“e=”,“p=”,“t=”,“r=”,“z=” 和 “k=” 行必須檢查語法,但它們的值不被使用。
其余的非屬性行按如下方式處理:
- “v=” 行必須是 0 的版本,如 [RFC4566] 5.1 節所述。
- “o=” 行必須按照 [RFC4566] 5.2 節中的規定進行解析。
- “b=” 行如果存在,必須按照 [RFC4566] 5.8 節的規定解析,并存儲 bwtype 和帶寬值。
最后處理屬性行。特定處理必須應用于以下會話級屬性(“a=”)行:
- 任何 “a=group” 行被解析為在 [RFC5888] 5 節中指定的,并且組的語義和 mid 被存儲。
- 如果存在,一個單獨的 “a=ice-lite” 行被解析為 [RFC8839] 5.3 節中指定的,并且一個值表示存在 ice-lite 被存儲。
- 如果存在,一個單獨的 “a=ice-ufrag” 行被解析為 [RFC8839] 5.4 節中指定的,并且 ufrag 值被存儲。
- 如果存在,一個單獨的 “a=ice-pwd” 行被解析為[RFC8839] 5.4 節中指定的,并且密碼值被存儲。
- 如果存在,“a=ice-options” 一行將被解析為[RFC8839] 5.6節中指定的,并且指定的選項集將被存儲。
- 任何 “a=fingerprint” 行按照[RFC8122] 5 節中的規定進行解析,并存儲指紋和算法值集。
- 如果存在,“a=setup” 一行將按照[RFC4145] 4 節中的規定進行解析,并存儲 setup 值。
- 如果存在,“a=tls-id” 一行將按照[RFC8842] 5 節中的規定進行解析,并存儲屬性值。
- 任何 “a=identity” 行都會被解析并存儲標識值以供后續驗證,如 [RFC8827] 5 節所述。
- 任何 “a=extmap” 行都按照 [RFC5285] 5 節中指定的方式進行解析,并存儲它們的值。
其他與 JSEP 無關的屬性也可能出現,實現應該處理它們所識別的任何屬性。根據 [RFC4566] 5.13 節的要求,必須忽略未知的屬性行。
一旦所有會話級別的行都被解析,處理就會繼續進行 m-section 中的行。
5.8.2. Media Section Parsing
和會話級別的行一樣,媒體部分的行必須按照特定的順序和語法出現,這些語法在 [RFC4566] 5 節中定義。
“m=” 行本身必須按照 [RFC4566] 5.14 節中描述的那樣被解析,并且存儲 <media>,<port>,<proto> 和 <fmt> 值。
在 “m=” 行之后,必須對以下非屬性行應用特定處理:
- 與會話級別的 “c=” 行一樣,“c=” 行必須根據 [RFC4566] 5.7 節解析,但它的值沒有被使用。
- “b=” 行如果存在,必須按照 [RFC4566] 5.8 節的規定解析,并存儲 bwtype 和帶寬值。
以下屬性行也必須應用特定處理:
- 如果存在一個單獨的 “a=ice-ufrag” 行被解析為 [RFC8839] 5.4 節中指定的,并且 ufrag 值被存儲。
- 如果存在一個單獨的 “a=ice-pwd” 行被解析為[RFC8839] 5.4 節中指定的,并且密碼值被存儲。
- 如果存在,“a=ice-options” 行將被解析為 [RFC8839] 5.6 節中指定的,并且指定的選項集將被存儲。
- 任何 “a=candidate” 屬性必須按照 [RFC8839] 5.1 節中的規定進行解析,并存儲它們的值。
- 任何 “a=remote-candidate” 的屬性必須按照 [RFC8839] 5.2 節中的規定進行解析,但是它們的值會被忽略。
- 如果存在,單個 “a=end-of-candidates” 屬性必須按照 [RFC8840] 8.1 節中指定的方式進行解析,并標記和存儲它的存在或不存在。
- 任何 “a=fingerprint” 行按照 [RFC8122] 5 節中的規定進行解析,并存儲指紋和算法值集。
如果 “m=” <proto> 值表示使用 RTP,如上文 5.1.2 節所述,必須處理以下屬性行:
- “m=” <fmt> 值必須按照 [RFC4566] 5.14 節中指定的方式解析,并存儲各個值。
- 任何 “a=rtpmap” 或 “a=fmtp” 行必須按照 [RFC4566] 6 節中的規定進行解析,并存儲它們的值。
- 如果存在 “a=ptime” 行必須按照 [RFC4566] 6 節的描述進行解析,并存儲其值。
- 如果存在 “a=maxptime” 行必須按照 [RFC4566] 6 節中的描述進行解析,并存儲其值。
- 如果存在一個方向屬性行(例如,“a=sendrecv”)必須按照 [RFC4566] 6 節中的描述進行解析,并存儲其值。
- 任何 “a=ssrc” 屬性必須按照 [RFC5576] 4.1 節中的規定進行解析,并存儲它們的值。
- 任何 “a=extmap” 屬性必須按照 [RFC5285] 5 節中指定的解析,并存儲它們的值。
- 任何 “a=rtcp-fb” 屬性必須按照 [RFC4585] 4.2 節中的規定進行解析,并存儲它們的值。
- 如果存在一個單獨的 “a=rtcp-mux” 屬性必須按照 [RFC5761] 5.1.3 節中的規定進行解析,并標記和存儲它的存在或不存在。
- 如果存在一個 “a=rtcp-mux-only” 屬性必須按照 [RFC8858] 3 節中指定的方式進行解析,并標記和存儲它的存在或不存在。
- 如果存在一個單獨的 “a=rtcp-rsize” 屬性必須按照 [RFC5506] 5 節中的規定進行解析,并標記和存儲它的存在或不存在。
- 如果存在,“a=rtcp” 屬性必須按照 2.1 節 [RFC3605] 中的規定進行解析,但是它的值被忽略,因為在使用 ICE 時,這些信息是多余的。
- 如果存在,“a=msid” 屬性必須按照 [RFC8830] 3.2 節中的規定進行解析,并存儲它們的值,忽略任何 “appdata” 字段。如果不存在 “a=msid” 屬性,則為會話的 “default” MediaStream 生成一個隨機的msid-id 值,如果該值還沒有出現,則存儲該值。
- 任何 “a=imageattr” 屬性必須按照 [RFC6236] 3 節中的規定進行解析,并存儲它們的值。
- 任何 “a=rid” 行必須按照 [RFC8851] 10 節中的規定解析,并存儲它們的值。
- 如果存在一個 “a=simulcast” 行必須按照 [RFC8853] 中的規定進行解析,并存儲其值。
否則,如果"m=" <proto> 值表示使用 SCTP,則必須處理以下屬性行:
- “m=” <fmt> 的值必須按照 [RFC8841] 4.3 節中的規定進行解析,并存儲應用協議值。
- 一個 “a=sctp-port” 屬性必須存在,它必須按照 [RFC8841] 5.2 節中的規定進行解析,并存儲該值。
- 如果存在一個單獨的 “a=max-message-size” 屬性必須按照 [RFC8841] 6 節中的規定進行解析,并存儲該值。否則,使用指定的默認值。
其他與 JSEP 無關的屬性也可能出現,實現應該處理它們所識別的任何屬性。根據 [RFC4566] 5.13 節的要求,必須忽略未知的屬性行。
5.8.3. Semantics Verification
假設解析成功完成,然后對解析的描述進行評估,以確保內部一致性以及對強制特性的適當支持。具體來說,執行以下檢查:
- 對于每個 m-section,必須提供 5.1.1 節中列舉的每個強制使用特性的有效值。這些值可以在媒體級出現,也可以從會話級繼承。
- ICE ufrag 和 pwd 的值必須符合[RFC8839] 5.4 節中規定的大小限制。
- tls-id 值必須根據 [RFC8842] 5節設置。如果這是一個更新 offer 或一個更新 offer 的響應,tls-id 值與當前使用的不一樣,DTLS 連接不被繼續,遠程描述必須是 ICE 重啟的一部分,以及新的 ufrag 和 pwd 值。
- DTLS setup 值必須根據 [RFC5763] 5 節中指定的規則設置,并且必須與當前 DTLS 連接所選擇的角色保持一致,如果存在并且正在繼續。
- DTLS 指紋值,其中至少有一個指紋必須存在。
- 所有在 “a=simulcast” 行中引用的 rid-id 必須作為 “a=rid” 行存在。
- 每個 m-section 也會被檢查,以確保不使用禁止的特性。
- 如果 RTP/RTCP 復用策略為 “require”,每個 m-section 必須包含一個 “a=rtcp-mux” 屬性。如果一個 m-section 包含一個 “a=rtcp-mux-only” 屬性,該 section 也必須包含一個 “a=rtcp-mux” 屬性。
- 如果 m-section 出現在前面的 answer 中,RTP/RTCP 復用的狀態必須匹配之前協商的。
如果該會話描述類型為 “pranswer” 或 “answer”,則應用以下附加檢查:
- 會話描述必須遵循 [RFC3264] 6 節中定義的規則,包括要求 m-section 的數量必須與相關 offer 中 m-section 的數量完全匹配。
- 對于每個 m-section,媒體類型和協議值必須與相關 offer 中相應的 m-section 中的媒體類型和協議值完全匹配。
如果上述任何一個檢查失敗,處理必須停止并返回一個錯誤。
5.9. Applying a Local Description
以下步驟在媒體引擎級別執行以應用本地描述。如果返回錯誤,則必須將會話恢復到執行這些步驟之前的狀態。
首先,處理 m-section。對于每個 m-section,必須執行以下步驟;如果任何參數超出范圍或不能應用,處理必須停止并返回一個錯誤。
- 如果這個 m-section 是新的,開始收集候選,定義在[RFC8445] 5.1.1 節,除非它是明確被捆綁 ((1) 是一個 offer 和 m-section 被標記 bundle-only 或 (2) 是一個 answer , m-section 捆綁到另一個 m-section)。
- 或者,如果ICE ufrag 和 pwd 的值已經改變,觸發 ICE 代理 ICE 重啟,如 [RFC8445] 9 節所述,并開始收集 m-section 的新連接候選。如果這個描述是一個 answer,也開始檢查那個媒體部分。
- 如果 m-section <proto> 部分值表示使用 RTP:
- 如果沒有 RtpTransceiver 與這個 m-section 相關聯,找到一個并根據以下步驟將其與這個 m-section 相關聯。請注意,這種情況只會發生在 offer。
- 找到對應于這個 m-section 的 RtpTransceiver,使用在創建 offer 時建立的 RtpTransceiver 和 m-section 索引之間的映射。
- 設置 RtpTransceiver 的 mid 屬性值為 m-section 的 mid。
- 如果 RTCP mux 被指定,準備從 RTP ICE 組件中去除 RTP 和 RTCP,如 [RFC5761] 5.1.3 節所述。
- 對于每個指定的 RTP 報頭擴展,建立擴展 ID 和 URI 之間的映射,如 [RFC5285] 6 節所述。
- 如果支持 MID 報頭擴展,準備基于 MID 報頭擴展來 demux RTP 流,如[RFC8843],15 節所述。
- 對于每個指定的媒體格式,建立有效負載類型和實際媒體格式之間的映射,如 [RFC3264] 6.1 節所述。另外,根據 m-section 所支持的媒體格式,準備好 demux RTP 流,如 [RFC8843] 9.2 節所述。
- 對于每個指定的 “rtx” 媒體格式,建立 rtx 有效載荷類型和相關的主有效載荷類型之間的映射,如 [RFC4588] 8.6 和 8.7 節所述。
- 如果 direction 屬性類型為 “sendrecv” 或 “recvonly”,則啟用媒體接收和解碼。
- 如果沒有 RtpTransceiver 與這個 m-section 相關聯,找到一個并根據以下步驟將其與這個 m-section 相關聯。請注意,這種情況只會發生在 offer。
最后,如果此描述的類型是 “pranswer” 或 “answer”,則遵循下面 5.11 節中定義的處理。
5.10. Applying a Remote Description
執行以下步驟應用遠程描述。如果返回錯誤,則必須將會話恢復到執行這些步驟之前的狀態。
如果 answer 包含任何 “a=ice-options” 屬性,其中 “trickle” 被列為一個屬性,更新 PeerConnection canTrickleIceCandidates 屬性為 “true”。否則,將此屬性設置為 “false”。
必須對會話級別的屬性執行以下步驟;如果任何參數超出范圍或不能應用,處理必須停止并返回一個錯誤。
- 對于任何指定的 “CT” 帶寬值,將該值設置為所有 m-section 的最大總比特率的限制,如 [RFC4566] 5.8 節所述。在這個總體限制內,實現可以動態地決定如何在 m-section 之間最佳地分配可用帶寬,單個 m-section 指定的任何特定限制。
- 對于任何指定的 “RR” 或 “RS” 帶寬值,按照 [RFC3556] 2 節中的規定處理。
- 任何 “AS” 帶寬值([RFC4566] 5.8 節)必須被忽略,因為這個構造在會話級別上的意義沒有被很好地定義。
對于每個 m-section,必須執行以下步驟;如果任何參數超出范圍或不能應用,處理必須停止并返回一個錯誤。
-
如果 ICE ufrag 或 pwd 從之前的遠程描述中更改:
- 如果描述類型為 “offer”,實現必須注意,需要重新啟動 ICE,如[RFC8839] 4.4.1.1.1 節所述。
- 如果描述類型為 “answer” 或 “pranswer”,則檢查當前本地描述是否為 ICE 重啟,如果不是,則生成一個錯誤。如果 PeerConnection 的狀態是 “have-remote-pranswer”,并且 ICE 的 ufrag 或 pwd 由之前的臨時 answer 更改,那么就通知 ICE 代理放棄之前的任何 ICE 檢查表狀態的 m-section。最后,向ICE代理發出信號,開始檢查。
-
如果當前本地描述是 ICE 重啟,但ICE ufrag 和 pwd 都沒有從之前的遠程描述更改(根據 [RFC8445] 9 節的規定),則會產生錯誤。
-
配置與此 m-section 相關的 ICE 組件,以使用提供的 ICE 遠程 ufrag 和 pwd 進行連接檢查。
-
如 [RFC8445] 6.1.2 節所述,將任何提供的 ICE 候選項與任何收集到的本地候選項配對,并使用適當的憑證開始連接檢查。
-
如果存在 “a=end-of-candidates” 屬性,則按照 [RFC8838] 14 節中的描述處理結束候選的指示。
-
如果"m=" <proto> 部分值表示使用 RTP:
- 如果 m-section 被回收(5.2.2 節),通過設置 RtpTransceiver 的 mid 屬性為 “null” 來解除當前關聯的 RtpTransceiver,并且丟棄該 RtpTransceiver 和它的 m-section 索引之間的映射。
- 如果 m-section 沒有與任何 RtpTransceiver 關聯(可能是因為它在上一步被解除關聯),要么找到一個 RtpTransceiver,要么根據以下步驟創建一個 RtpTransceiver:
- 如果 m-section 為 sendrecv 或 recvonly,RtpTransceiver 與 m-section 相同類型 && 通過 addTrack 添加到 PeerConnection && 不與任何 m-section 關聯 && 沒有停止,符合以上條件的第一個(根據 5.2.1 節) RtpTransceiver。
- 如果上一步沒有發現 RtpTransceiver,需要創建一個 recvonly 方向的 RtpTransceiver。
- 通過將 RtpTransceiver 的 mid 屬性的值設置為 m-section 的 mid,將發現或創建的 RtpTransceiver 與 m-section 關聯起來,并在 RtpTransceiver和 m-section 索引間建立一個映射。如果 m-section 不包含 MID(即,遠程端點不支持 MID 擴展),為RtpTransceiver MID 屬性生成一個值,遵循 5.2.1 節中提到的 “a=mid” 的指導。
-
對于每個本地實現也支持的指定媒體格式,建立指定負載類型和媒體格式之間的映射,如 [RFC3264] 6.1 節所述。具體地說,這意味著在發送每個指定的媒體格式時,實現需要記錄在傳出 RTP 包中使用的有效負載類型,以及在它們的順序中表示的每種格式的相對首選項。如果任何指定的媒體格式不被本地實現支持,則必須忽略它。
-
對于每一種指定的 “rtx” 媒體格式,建立 rtx 有效載荷類型與其關聯的主有效載荷類型之間的映射,如 [RFC4588] 4 節所述。如果任何引用的主有效負載類型不存在,則必須返回錯誤。請注意,RTX 有效載荷類型可能引用本地媒體實現不支持的主有效載荷類型,在這種情況下,RTX 有效載荷類型也必須被忽略。
-
對于本地實現支持的每個指定的 fmtp 參數,在相關的媒體格式上啟用它們。
-
對于在 m-section 中發出的每個 SSRC,準備使用該 SSRC 來 demux RTP 流,用于這個 m-section,如 [RFC8843] 9.2 節所述。
-
對于每個指定的 RTP 頭部擴展,也被本地實現支持,建立擴展 ID 和 URI 之間的映射,如 [RFC5285] 5 節所述。具體地說,這意味著實現在發送每個指定的報頭擴展名時記錄在發送 RTP 包中使用的擴展名 ID。如果任何指定的 RTP 報頭擴展不被本地實現支持,它必須被忽略。
-
對于本地實現支持的每個指定的 RTCP 反饋機制,在相關的媒體格式上啟用它們。
-
對于任何指定的 “TIAS”(Transport Independent Application Specific Maximum)帶寬值,將此值設置為發送媒體時使用的最大 RTP 比特率的約束,如 [RFC3890] 中所規定的。如果沒有 “TIAS” 值,但指定了一個 “AS” 值,則使用以下公式生成一個 “TIAS” 值:
TIAS = AS * 1000 * 0.95 - (50 * 40 * 8)1000 將單位從 kbps 更改為 bps(根據 TIAS 的要求),0.95 是將 5% 的帶寬分配給 RTCP。然后減去報頭開銷的估計值,其中 50 基于每秒 50 個包,40 基于典型的報頭大小(以字節為單位),8 將字節轉換為比特。請注意,“TIAS” 比 “AS” 更可取,因為它提供了更精確的帶寬控制。
-
對于任何 “RR” 或 “RS” 帶寬值,按照 [RFC3556] 2 節中的規定處理。
-
任何指定的 “CT” 帶寬值都必須被忽略,因為這個構造在媒體層面上的意義沒有被很好地定義。
-
如果 m-section 的類型是"audio":
- 對于每個指定的 “CN” 媒體格式,為所有支持的具有相同時鐘速率的媒體格式配置靜音抑制,如 [RFC3389] 5 節所述,除了具有自己內部靜音抑制機制的格式。這類格式(如 Opus )的靜音抑制是通過 fmtp 參數來控制的,如 5.2.3.2 節所述。
- 對于每個指定的 “telephone-event” 媒體格式,在相同的時鐘速率下為所有支持的媒體格式啟用雙音多頻(DTMF)傳輸,如 [RFC4733] 2.5.1.2 節所述。如果有任何受支持的媒體格式沒有相應的電話事件格式,請禁用這些格式的 DTMF 傳輸。
- 對于任何指定的 “ptime” 值,將可用的媒體格式配置為在發送時使用指定的包大小。如果媒體格式不支持指定的大小,則使用下一個最接近的值。
最后,如果此描述的類型是 “pranswer” 或 “answer”,則遵循下面第 5.11 節中定義的處理。
5.11. Applying an Answer
除了上面提到的處理本地或遠程描述的步驟外,在處理類型為 “pranswer” 或 “answer” 的描述時,還將執行以下步驟。
對于每個 m-section,必須執行以下步驟:
- 如果 m-section 已經拒絕了(例如 <port> 值設置為 0 的 answer),停止任何接收或傳輸媒體的這一節中,而且除非 non-rejected 的 m-section 支持 bundle,否則丟棄任何 ICE 組件相關聯,如 [RFC8839] 4.4.3.1 節所述。
- 如果遠程 DTLS 指紋被更改或 “a=tls-id” 屬性的值被更改,需要斷開 DTLS 連接。這包括 PeerConnection 狀態為 “have-remote-pranswer” 的情況。如果一個 DTLS 連接需要被斷開,但是 answer 沒有指示 ICE 重啟,或者在 “have-remote-pranswer” 的情況下,新的 ICE 憑證,必須生成一個錯誤。如果在沒有改變 tls-id 值或指紋的情況下執行 ICE 重啟,那么相同的 DTLS 連接將在新的 ICE 通道上繼續。注意,盡管 JSEP 要求當且僅當提供方改變 tls-id 值時,非 JSEP 應答者被允許改變 tls-id 值,只要 offer 包含一個 ICE 重啟。因此,在接收 answer 之前處理 DTLS 數據的 JSEP 實現必須準備好接收來自以前的 DTLS 連接的 ClientHello 或數據。
- 如果不存在有效的 DTLS 連接,則在任何底層 ICE 組件處于活動狀態時,準備使用指定的角色和指紋啟動 DTLS 連接。
- 如果"m=" <proto> 值表示使用RTP:
- 如果 m-section 引用了 RTCP 反饋機制,而在 offer 中相應的 m-section 中沒有出現,這表明協商存在問題,一定會導致錯誤。但是,answer 中允許使用新的媒體格式和新的 RTP 報頭擴展值,如 [RFC3264] 7 節和 [RFC5285] 6 節所述。
- 如果 m-section 啟用了 RTCP mux,則丟棄 RTCP ICE 組件(如果存在),并開始或繼續在 RTP ICE 組件上 muxing RTCP,如 [RFC5761] 5.1.3 節所述。否則,準備通過 RTCP ICE 組件發送 RTCP;如果沒有 RTCP ICE 組件存在,因為之前啟用了 RTCP mux 這一定會導致錯誤。
- 如果 m-section 啟用了 Reduced-Size RTCP,配置這個 m-section 的 RTCP 傳輸使用 Reduced-Size RTCP,如 [RFC5506] 所述。
- 如果方向屬性的 answer 表明 JSEP 實現應該發送媒體(“sendonly” 為本地的 answer,“recvonly” 為遠端 answer,或 “sendrecv” 類型的 answer),選擇媒體格式發送最首選的媒體格式從遠程描述也是本地支持,如 [RFC3264] 6.1 節和 7 節所述,并在底層傳輸層建立后開始使用該格式傳輸 RTP 媒體。如果這個輸出 RTP 流還沒有選擇 SSRC,請選擇一個唯一的隨機流。如果媒體已經在傳輸,則應該使用相同的 SSRC,除非新的編解碼器的時鐘速率不同,在這種情況下必須選擇一個新的 SSRC,如 [RFC7160] 4.1 節所述。
- 遠程描述中的有效載荷類型映射用于確定輸出 RTP 流的有效載荷類型,包括上面選擇的發送媒體格式的有效載荷類型。任何協商的 RTP 報頭擴展都應該包含在輸出 RTP 流中,使用來自遠程描述的擴展映射。如果已經協商好了 MID 報頭擴展,將其包含在輸出 RTP 流中,如[RFC8843] 15 節所示。如果 RtpStreamId 或 RepairedRtpStreamId 報頭擴展已經協商,并且 rid-id 已經建立,在輸出 RTP 流中包括這些報頭擴展,如 [RFC8851] 4 節所示。
- 如果 m-section 的類型是音頻并且靜音抑制,遠端和本地都支持的情況下,用靜音抑制外向的媒體,按照5.2.3.2 節。如果不滿足這些條件,則不能使用靜音抑制對外發媒體。
- 如果協商成功,發送相應數量的源 RTP 流,如 [RFC8853] 5.3.3 節所述。
- 如果上面選擇的發送媒體格式有相應的 “rtx” 媒體格式,或者已經協商了 FEC 機制,則為每個源 RTP 流建立一個具有唯一隨機 SSRC 的冗余 RTP 流,并根據需要啟動或繼續發送 rtx/FEC 包。
- 如果上面選擇的發送媒體格式具有相同的時鐘速率對應的 RED 媒體格式,為了彈性目的,允許使用指定格式的冗余編碼,如 [RFC8854] 3.2 節所述。請注意,與 RTX 或 FEC 媒體格式不同,RED 格式是在源 RTP 流上傳輸的,而不是在冗余 RTP 流上。
- 為所有使用指定媒體格式的源 RTP 流啟用媒體部分中引用的 RTCP 反饋機制。具體來說,開始或繼續發送請求的反饋類型,并對收到的反饋做出反應,如 [RFC4585] 4.2 節所述。當發送 RTCP 反饋時,請遵循 [RFC8108] 5.4.1 節中的規則和建議來選擇使用哪個 SSRC。
- 如果 answer 中的 direction 屬性指示 JSEP 實現不應該發送媒體(本地 answer 為 “recvonly”,遠程 answer 為 “sendonly”,或任何一種類型的答案為 “inactive”),停止發送所有 RTP 媒體,但繼續發送 RTCP,如 [RFC3264] 5.1 節所述。
- 如果"m=" 部分 <proto> 值表示使用 SCTP:
- 如果存在 SCTP 關聯,且遠端 SCTP 端口已經改變,則丟棄現有的 SCTP 關聯。這包括 PeerConnection 狀態為 “have-remote-pranswer” 的情況。
- 如果沒有有效的 SCTP 關聯存在,準備在關聯的 ICE 組件和 DTLS 連接上發起一個 SCTP 關聯,使用來自本地描述的本地 SCTP 端口值和來自遠端描述的遠端 SCTP 端口值,如 [RFC8841] 10.2節所述。
如果 answer 包含了有效的 bundle 組,丟棄所有將被捆綁到每個 bundle 的主 ICE 組件上的 m-section 的ICE組件,并開始相應的混合這些 m-section,如 [RFC8843] 7.4 節所述。
如果描述類型為 “answer”,并且在 ICE 候選池中仍有剩余的候選項,則丟棄它們。
6. 處理 RTP/RTCP
捆綁操作時,將傳入的 RTP/RTCP 與適當的 m-section 關聯(在 [RFC8843] 9.2 節中定義)。當不捆綁時,從接收 RTP/RTCP 的 ICE 組件中清除相應的 m-section。
一旦正確的 m-section 被確認,RTP/RTCP 被交付給與 m-section 相關的 RtpTransceiver(s), RTP/RTCP 的進一步處理在 RtpTransceiver 級別完成。這包括使用 RID 機制 [RFC8851] 及其相關的 RtpStreamId 和 RepairedRtpStreamId 標識符來區分多個編碼流,并確定哪個源 RTP 流可以被指定的冗余的 RTP 流修復。
7. 示例
注意,這個示例部分顯示了幾個 SDP 片段。為了適應 RFC 的行長限制,一些 SDP 行被分成了多行,其中前置空格表示一行是前一行的延續。此外,添加了一些空白行以提高可讀性,但在 SDP 中無效。
更多關于 WebRTC 呼叫流的 SDP 例子,包括 IPv6 地址的例子,可以在 [SDP4WebRTC] 中找到。
7.1. Simple Example
本節展示了一個非常簡單的示例,該示例在兩個 JSEP 端點之間建立最小的音頻/視頻會話,不使用 Trickle ICE。下一節中的示例提供了一個更詳細的示例,說明在 JSEP 會話中可能發生的情況。
下面的代碼流顯示了 Alice 的端點將會話初始化發送到 Bob 的端點。從 Alice 瀏覽器中的 JavaScript 應用程序到 Bob 瀏覽器中的 JavaScript 的消息(分別被縮寫為 “AliceJS” 和 “BobJS”),被假設為通過 web 服務器的某些信令協議傳輸。Alice 側和 Bob 側的 JavaScript 在發送 offer 或 answer 之前等待所有的連接候選,所以 offer 和 answer 是完整的,不使用 Trickle ICE。Alice 和 Bob 瀏覽器中的用戶代理(JSEP 實現),分別縮寫為 “AliceUA” 和 “BobUA”,都使用默認的 bundle 策略 “balanced” 和默認的 RTCP mux 策略 “require”。
// set up local media state AliceJS->AliceUA: create new PeerConnection AliceJS->AliceUA: addTrack with two tracks: audio and video AliceJS->AliceUA: createOffer to get offer AliceJS->AliceUA: setLocalDescription with offer AliceUA->AliceJS: multiple onicecandidate events with candidates// wait for ICE gathering to complete AliceUA->AliceJS: onicecandidate event with null candidate AliceJS->AliceUA: get |offer-A1| from pendingLocalDescription// |offer-A1| is sent over signaling protocol to Bob AliceJS->WebServer: signaling with |offer-A1| WebServer->BobJS: signaling with |offer-A1|// |offer-A1| arrives at Bob BobJS->BobUA: create a PeerConnection BobJS->BobUA: setRemoteDescription with |offer-A1| BobUA->BobJS: ontrack events for audio and video tracks// Bob accepts call BobJS->BobUA: addTrack with local tracks BobJS->BobUA: createAnswer BobJS->BobUA: setLocalDescription with answer BobUA->BobJS: multiple onicecandidate events with candidates// wait for ICE gathering to complete BobUA->BobJS: onicecandidate event with null candidate BobJS->BobUA: get |answer-A1| from currentLocalDescription// |answer-A1| is sent over signaling protocol // to Alice BobJS->WebServer: signaling with |answer-A1| WebServer->AliceJS: signaling with |answer-A1|// |answer-A1| arrives at Alice AliceJS->AliceUA: setRemoteDescription with |answer-A1| AliceUA->AliceJS: ontrack events for audio and video tracks// media flows BobUA->AliceUA: media sent from Bob to Alice AliceUA->BobUA: media sent from Alice to Bob|offer-A1| SDP 如下:
v=0 o=- 4962303333179871722 1 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 v1 a=group:LS a1 v1m=audio 10100 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 203.0.113.100 a=mid:a1 a=sendrecv a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:47017fee-b6c1-4162-929c-a25110252400 a=ice-ufrag:ETEn a=ice-pwd:OtSK0WpNtpUjkY4+86js7ZQl a=fingerprint:sha-25619:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2 a=setup:actpass a=tls-id:91bbf309c0990a6bec11e38ba2933cee a=rtcp:10101 IN IP4 203.0.113.100 a=rtcp-mux a=rtcp-rsize a=candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host a=candidate:1 2 udp 2113929470 203.0.113.100 10101 typ host a=end-of-candidatesm=video 10102 UDP/TLS/RTP/SAVPF 100 101 102 103 c=IN IP4 203.0.113.100 a=mid:v1 a=sendrecv a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=msid:47017fee-b6c1-4162-929c-a25110252400 a=ice-ufrag:BGKk a=ice-pwd:mqyWsAjvtKwTGnvhPztQ9mIf a=fingerprint:sha-25619:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2 a=setup:actpass a=tls-id:91bbf309c0990a6bec11e38ba2933cee a=rtcp:10103 IN IP4 203.0.113.100 a=rtcp-mux a=rtcp-rsize a=candidate:1 1 udp 2113929471 203.0.113.100 10102 typ host a=candidate:1 2 udp 2113929470 203.0.113.100 10103 typ host a=end-of-candidates|answer-A1| SDP 如下:
v=0 o=- 6729291447651054566 1 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 v1 a=group:LS a1 v1m=audio 10200 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 203.0.113.200 a=mid:a1 a=sendrecv a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:61317484-2ed4-49d7-9eb7-1414322a7aae a=ice-ufrag:6sFv a=ice-pwd:cOTZKZNVlO9RSGsEGM63JXT2 a=fingerprint:sha-2566B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08 a=setup:active a=tls-id:eec3392ab83e11ceb6a0990c903fbb19 a=rtcp-mux a=rtcp-rsize a=candidate:1 1 udp 2113929471 203.0.113.200 10200 typ host a=end-of-candidatesm=video 10200 UDP/TLS/RTP/SAVPF 100 101 102 103 c=IN IP4 203.0.113.200 a=mid:v1 a=sendrecv a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=msid:61317484-2ed4-49d7-9eb7-1414322a7aae7.2. Detailed Example
本節展示了兩個 JSEP 端點之間更復雜的會話示例。Trickle ICE 是在 Full Trickle 模式下使用的,捆綁策略是 “max-bundle”,RTCP mux 策略是 “require”,并且只有一個 TURN 服務。最初,Alice 和 Bob 都建立了一個音頻通道和一個數據通道。稍后 Bob 添加了兩個視頻流(一個用于他的視頻提要,一個用于屏幕共享),兩者都支持 FEC 并將視頻提要配置為 simulcast。Alice 接受這些視頻流,但不添加她自己的視頻流,因此它們被作為 recvonly 處理。Alice 還指定了最大視頻解碼器分辨率。
// set up local media state AliceJS->AliceUA: create new PeerConnection AliceJS->AliceUA: addTrack with an audio track AliceJS->AliceUA: createDataChannel to get data channel AliceJS->AliceUA: createOffer to get |offer-B1| AliceJS->AliceUA: setLocalDescription with |offer-B1|// |offer-B1| is sent over signaling protocol to Bob AliceJS->WebServer: signaling with |offer-B1| WebServer->BobJS: signaling with |offer-B1|// |offer-B1| arrives at Bob BobJS->BobUA: create a PeerConnection BobJS->BobUA: setRemoteDescription with |offer-B1| BobUA->BobJS: ontrack event with audio track from Alice// candidates are sent to Bob AliceUA->AliceJS: onicecandidate (host) |offer-B1-candidate-1| AliceJS->WebServer: signaling with |offer-B1-candidate-1| AliceUA->AliceJS: onicecandidate (srflx) |offer-B1-candidate-2| AliceJS->WebServer: signaling with |offer-B1-candidate-2| AliceUA->AliceJS: onicecandidate (relay) |offer-B1-candidate-3| AliceJS->WebServer: signaling with |offer-B1-candidate-3|WebServer->BobJS: signaling with |offer-B1-candidate-1| BobJS->BobUA: addIceCandidate with |offer-B1-candidate-1| WebServer->BobJS: signaling with |offer-B1-candidate-2| BobJS->BobUA: addIceCandidate with |offer-B1-candidate-2| WebServer->BobJS: signaling with |offer-B1-candidate-3| BobJS->BobUA: addIceCandidate with |offer-B1-candidate-3|// Bob accepts call BobJS->BobUA: addTrack with local audio BobJS->BobUA: createDataChannel to get data channel BobJS->BobUA: createAnswer to get |answer-B1| BobJS->BobUA: setLocalDescription with |answer-B1|// |answer-B1| is sent to Alice BobJS->WebServer: signaling with |answer-B1| WebServer->AliceJS: signaling with |answer-B1| AliceJS->AliceUA: setRemoteDescription with |answer-B1| AliceUA->AliceJS: ontrack event with audio track from Bob// candidates are sent to Alice BobUA->BobJS: onicecandidate (host) |answer-B1-candidate-1| BobJS->WebServer: signaling with |answer-B1-candidate-1| BobUA->BobJS: onicecandidate (srflx) |answer-B1-candidate-2| BobJS->WebServer: signaling with |answer-B1-candidate-2| BobUA->BobJS: onicecandidate (relay) |answer-B1-candidate-3| BobJS->WebServer: signaling with |answer-B1-candidate-3|WebServer->AliceJS: signaling with |answer-B1-candidate-1| AliceJS->AliceUA: addIceCandidate with |answer-B1-candidate-1| WebServer->AliceJS: signaling with |answer-B1-candidate-2| AliceJS->AliceUA: addIceCandidate with |answer-B1-candidate-2| WebServer->AliceJS: signaling with |answer-B1-candidate-3| AliceJS->AliceUA: addIceCandidate with |answer-B1-candidate-3|// data channel opens BobUA->BobJS: ondatachannel event AliceUA->AliceJS: ondatachannel event BobUA->BobJS: onopen AliceUA->AliceJS: onopen// media is flowing between endpoints BobUA->AliceUA: audio+data sent from Bob to Alice AliceUA->BobUA: audio+data sent from Alice to Bob// some time later, Bob adds two video streams // note: no candidates exchanged, because of bundle BobJS->BobUA: addTrack with first video stream BobJS->BobUA: addTrack with second video stream BobJS->BobUA: createOffer to get |offer-B2| BobJS->BobUA: setLocalDescription with |offer-B2|// |offer-B2| is sent to Alice BobJS->WebServer: signaling with |offer-B2| WebServer->AliceJS: signaling with |offer-B2| AliceJS->AliceUA: setRemoteDescription with |offer-B2| AliceUA->AliceJS: ontrack event with first video track AliceUA->AliceJS: ontrack event with second video track AliceJS->AliceUA: createAnswer to get |answer-B2| AliceJS->AliceUA: setLocalDescription with |answer-B2|// |answer-B2| is sent over signaling protocol // to Bob AliceJS->WebServer: signaling with |answer-B2| WebServer->BobJS: signaling with |answer-B2| BobJS->BobUA: setRemoteDescription with |answer-B2|// media is flowing between endpoints BobUA->AliceUA: audio+video+data sent from Bob to Alice AliceUA->BobUA: audio+video+data sent from Alice to Bob|offer-B1| SDP 如下:
v=0 o=- 4962303333179871723 1 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 d1m=audio 9 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 0.0.0.0 a=mid:a1 a=sendrecv a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:57017fee-b6c1-4162-929c-a25110252400 a=ice-ufrag:ATEn a=ice-pwd:AtSK0WpNtpUjkY4+86js7ZQl a=fingerprint:sha-25629:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2 a=setup:actpass a=tls-id:17f0f4ba8a5f1213faca591b58ba52a7 a=rtcp-mux a=rtcp-mux-only a=rtcp-rsizem=application 0 UDP/DTLS/SCTP webrtc-datachannel c=IN IP4 0.0.0.0 a=mid:d1 a=sctp-port:5000 a=max-message-size:65536 a=bundle-only|offer-B1-candidate-1| 如下:
ufrag ATEn index 0 mid a1 attr candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host|offer-B1-candidate-2| 如下:
ufrag ATEn index 0 mid a1 attr candidate:1 1 udp 1845494015 198.51.100.100 11100 typ srflxraddr 203.0.113.100 rport 10100|offer-B1-candidate-3| 如下:
ufrag ATEn index 0 mid a1 attr candidate:1 1 udp 255 192.0.2.100 12100 typ relayraddr 198.51.100.100 rport 11100|answer-B1| SDP 如下
v=0 o=- 7729291447651054566 1 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 d1m=audio 9 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 0.0.0.0 a=mid:a1 a=sendrecv a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:71317484-2ed4-49d7-9eb7-1414322a7aae a=ice-ufrag:7sFv a=ice-pwd:dOTZKZNVlO9RSGsEGM63JXT2 a=fingerprint:sha-2567B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08 a=setup:active a=tls-id:7a25ab85b195acaf3121f5a8ab4f0f71 a=rtcp-mux a=rtcp-mux-only a=rtcp-rsizem=application 9 UDP/DTLS/SCTP webrtc-datachannel c=IN IP4 0.0.0.0 a=mid:d1 a=sctp-port:5000 a=max-message-size:65536|answer-B1-candidate-1| 如下:
ufrag 7sFv index 0 mid a1 attr candidate:1 1 udp 2113929471 203.0.113.200 10200 typ host|answer-B1-candidate-2| 如下:
ufrag 7sFv index 0 mid a1 attr candidate:1 1 udp 1845494015 198.51.100.200 11200 typ srflxraddr 203.0.113.200 rport 10200|answer-B1-candidate-3| 如下:
ufrag 7sFv index 0 mid a1 attr candidate:1 1 udp 255 192.0.2.200 12200 typ relayraddr 198.51.100.200 rport 11200|offer-B2| 的 SDP 如下所示。除了新的 m-section 的視頻,兩者都提供 FEC 并且其中一個為 simulcast,注意在 “o=” 行版本號的增量;更改 “c=” 行,表示選中的本地候選;以及將集合的候選包含為 a=candidate 行。
v=0 o=- 7729291447651054566 2 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 d1 v1 v2 a=group:LS a1 v1m=audio 12200 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 192.0.2.200 a=mid:a1 a=sendrecv a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:71317484-2ed4-49d7-9eb7-1414322a7aae a=ice-ufrag:7sFv a=ice-pwd:dOTZKZNVlO9RSGsEGM63JXT2 a=fingerprint:sha-2567B:8B:F0:65:5F:78:E2:51:3B:AC:6F:F3:3F:46:1B:35:DC:B8:5F:64:1A:24:C2:43:F0:A1:58:D0:A1:2C:19:08 a=setup:actpass a=tls-id:7a25ab85b195acaf3121f5a8ab4f0f71 a=rtcp-mux a=rtcp-mux-only a=rtcp-rsize a=candidate:1 1 udp 2113929471 203.0.113.200 10200 typ host a=candidate:1 1 udp 1845494015 198.51.100.200 11200 typ srflxraddr 203.0.113.200 rport 10200 a=candidate:1 1 udp 255 192.0.2.200 12200 typ relayraddr 198.51.100.200 rport 11200 a=end-of-candidatesm=application 12200 UDP/DTLS/SCTP webrtc-datachannel c=IN IP4 192.0.2.200 a=mid:d1 a=sctp-port:5000 a=max-message-size:65536m=video 12200 UDP/TLS/RTP/SAVPF 100 101 102 103 104 c=IN IP4 192.0.2.200 a=mid:v1 a=sendrecv a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=rtpmap:104 flexfec/90000 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=msid:71317484-2ed4-49d7-9eb7-1414322a7aae a=rid:1 send a=rid:2 send a=rid:3 send a=simulcast:send 1;2;3m=video 12200 UDP/TLS/RTP/SAVPF 100 101 102 103 104 c=IN IP4 192.0.2.200 a=mid:v2 a=sendrecv a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=rtpmap:104 flexfec/90000 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=msid:81317484-2ed4-49d7-9eb7-1414322a7aae|answer-B2|的 SDP 如下所示。接受視頻 m-section 使用 a=recvonly 來表示單向視頻,使用 a=imageattr 來限制接收的分辨率,注意使用 setup:passive 來維護現有的 DTLS 角色。
v=0 o=- 4962303333179871723 2 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 d1 v1 v2 a=group:LS a1 v1m=audio 12100 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 192.0.2.100 a=mid:a1 a=sendrecv a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:57017fee-b6c1-4162-929c-a25110252400 a=ice-ufrag:ATEn a=ice-pwd:AtSK0WpNtpUjkY4+86js7ZQl a=fingerprint:sha-25629:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2 a=setup:passive a=tls-id:17f0f4ba8a5f1213faca591b58ba52a7 a=rtcp-mux a=rtcp-mux-only a=rtcp-rsize a=candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host a=candidate:1 1 udp 1845494015 198.51.100.100 11100 typ srflxraddr 203.0.113.100 rport 10100 a=candidate:1 1 udp 255 192.0.2.100 12100 typ relayraddr 198.51.100.100 rport 11100 a=end-of-candidatesm=application 12100 UDP/DTLS/SCTP webrtc-datachannel c=IN IP4 192.0.2.100 a=mid:d1 a=sctp-port:5000 a=max-message-size:65536m=video 12100 UDP/TLS/RTP/SAVPF 100 101 102 103 c=IN IP4 192.0.2.100 a=mid:v1 a=recvonly a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=imageattr:100 recv [x=[48:1920],y=[48:1080],q=1.0] a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack plim=video 12100 UDP/TLS/RTP/SAVPF 100 101 102 103 c=IN IP4 192.0.2.100 a=mid:v2 a=recvonly a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=imageattr:100 recv [x=[48:1920],y=[48:1080],q=1.0] a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli7.3. Early Transport Warmup Example
這個例子演示了 4.1.10.1 節中描述的傳輸預熱技術。在這里,Alice 的端點向 Bob 的端點發送一個 offer 以啟動一個音頻/視頻通話。Bob 立即響應一個 answer,該 answer 接受音頻/視頻 m-section,但將它們標記為 send-only (從 Bob 的角度來看),這意味著 Alice 還不能發送媒體。這允許 JSEP 實現立即開始協商 ICE 和 DTLS。Bob 的端點然后提示他接聽電話,當他接聽時他的端點發送第二個 offer,這使音頻和視頻 m-section 被激活從而實現雙向媒體傳輸。這種流程的優點是,一旦收到第一個 answer,實現就可以繼續進行 ICE 和 DTLS 協商,并建立會話傳輸。如果傳輸設置在第二個 offer 被發送之前完成,那么媒體可以被被呼叫者在應答電話時立即傳輸,最大限度地減少撥號后的延遲。第二個 offer/answer 交換還可以更改首選的編解碼器或其他會話參數。
這個例子也使用了在 3.5.3 節中描述的 relay ICE 候選策略來最小化所需的 ICE 收集和檢查。
// set up local media state AliceJS->AliceUA: create new PeerConnection with "relay" ICE policy AliceJS->AliceUA: addTrack with two tracks: audio and video AliceJS->AliceUA: createOffer to get |offer-C1| AliceJS->AliceUA: setLocalDescription with |offer-C1|// |offer-C1| is sent over signaling protocol to Bob AliceJS->WebServer: signaling with |offer-C1| WebServer->BobJS: signaling with |offer-C1|// |offer-C1| arrives at Bob BobJS->BobUA: create new PeerConnection with "relay" ICE policy BobJS->BobUA: setRemoteDescription with |offer-C1| BobUA->BobJS: ontrack events for audio and video// a relay candidate is sent to Bob AliceUA->AliceJS: onicecandidate (relay) |offer-C1-candidate-1| AliceJS->WebServer: signaling with |offer-C1-candidate-1|WebServer->BobJS: signaling with |offer-C1-candidate-1| BobJS->BobUA: addIceCandidate with |offer-C1-candidate-1|// Bob prepares an early answer to warm up the // transport BobJS->BobUA: addTransceiver with null audio and video tracks BobJS->BobUA: transceiver.setDirection(sendonly) for both BobJS->BobUA: createAnswer BobJS->BobUA: setLocalDescription with answer// |answer-C1| is sent over signaling protocol // to Alice BobJS->WebServer: signaling with |answer-C1| WebServer->AliceJS: signaling with |answer-C1|// |answer-C1| (sendonly) arrives at Alice AliceJS->AliceUA: setRemoteDescription with |answer-C1| AliceUA->AliceJS: ontrack events for audio and video// a relay candidate is sent to Alice BobUA->BobJS: onicecandidate (relay) |answer-B1-candidate-1| BobJS->WebServer: signaling with |answer-B1-candidate-1|WebServer->AliceJS: signaling with |answer-B1-candidate-1| AliceJS->AliceUA: addIceCandidate with |answer-B1-candidate-1|// ICE and DTLS establish while call is ringing// Bob accepts call, starts media, and sends // new offer BobJS->BobUA: transceiver.setTrack with audio and video tracks BobUA->AliceUA: media sent from Bob to Alice BobJS->BobUA: transceiver.setDirection(sendrecv) for bothtransceivers BobJS->BobUA: createOffer BobJS->BobUA: setLocalDescription with offer// |offer-C2| is sent over signaling protocol // to Alice BobJS->WebServer: signaling with |offer-C2| WebServer->AliceJS: signaling with |offer-C2|// |offer-C2| (sendrecv) arrives at Alice AliceJS->AliceUA: setRemoteDescription with |offer-C2| AliceJS->AliceUA: createAnswer AliceJS->AliceUA: setLocalDescription with |answer-C2| AliceUA->BobUA: media sent from Alice to Bob// |answer-C2| is sent over signaling protocol // to Bob AliceJS->WebServer: signaling with |answer-C2| WebServer->BobJS: signaling with |answer-C2| BobJS->BobUA: setRemoteDescription with |answer-C2||offer-C1| SDP 如下:
v=0 o=- 1070771854436052752 1 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 v1 a=group:LS a1 v1m=audio 9 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 0.0.0.0 a=mid:a1 a=sendrecv a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:bbce3ba6-abfc-ac63-d00a-e15b286f8fce a=ice-ufrag:4ZcD a=ice-pwd:ZaaG6OG7tCn4J/lehAGz+HHD a=fingerprint:sha-256C4:68:F8:77:6A:44:F1:98:6D:7C:9F:47:EB:E3:34:A4:0A:AA:2D:49:08:28:70:2E:1F:AE:18:7D:4E:3E:66:BF a=setup:actpass a=tls-id:9e5b948ade9c3d41de6617b68f769e55 a=rtcp-mux a=rtcp-mux-only a=rtcp-rsizem=video 0 UDP/TLS/RTP/SAVPF 100 101 102 103 c=IN IP4 0.0.0.0 a=mid:v1 a=sendrecv a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=msid:bbce3ba6-abfc-ac63-d00a-e15b286f8fce a=bundle-only|offer-C1-candidate-1| 如下:
ufrag 4ZcD index 0 mid a1 attr candidate:1 1 udp 255 192.0.2.100 12100 typ relayraddr 0.0.0.0 rport 0|answer-C1| SDP 如下:
v=0 o=- 6386516489780559513 1 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 v1 a=group:LS a1 v1m=audio 9 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 0.0.0.0 a=mid:a1 a=sendonly a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:751f239e-4ae0-c549-aa3d-890de772998b a=ice-ufrag:TpaA a=ice-pwd:t2Ouhc67y8JcCaYZxUUTgKw/ a=fingerprint:sha-256A2:F3:A5:6D:4C:8C:1E:B2:62:10:4A:F6:70:61:C4:FC:3C:E0:01:D6:F3:24:80:74:DA:7C:3E:50:18:7B:CE:4D a=setup:active a=tls-id:55e967f86b7166ed14d3c9eda849b5e9 a=rtcp-mux a=rtcp-mux-only a=rtcp-rsizem=video 9 UDP/TLS/RTP/SAVPF 100 101 102 103 c=IN IP4 0.0.0.0 a=mid:v1 a=sendonly a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=msid:751f239e-4ae0-c549-aa3d-890de772998b|answer-C1-candidate-1| 如下:
ufrag TpaA index 0 mid a1 attr candidate:1 1 udp 255 192.0.2.200 12200 typ relayraddr 0.0.0.0 rport 0|offer-C2| SDP 如下:
v=0 o=- 6386516489780559513 2 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 v1 a=group:LS a1 v1m=audio 12200 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 192.0.2.200 a=mid:a1 a=sendrecv a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:751f239e-4ae0-c549-aa3d-890de772998b a=ice-ufrag:TpaA a=ice-pwd:t2Ouhc67y8JcCaYZxUUTgKw/ a=fingerprint:sha-256A2:F3:A5:6D:4C:8C:1E:B2:62:10:4A:F6:70:61:C4:FC:3C:E0:01:D6:F3:24:80:74:DA:7C:3E:50:18:7B:CE:4D a=setup:actpass a=tls-id:55e967f86b7166ed14d3c9eda849b5e9 a=rtcp-mux a=rtcp-mux-only a=rtcp-rsize a=candidate:1 1 udp 255 192.0.2.200 12200 typ relayraddr 0.0.0.0 rport 0 a=end-of-candidatesm=video 12200 UDP/TLS/RTP/SAVPF 100 101 102 103 c=IN IP4 192.0.2.200 a=mid:v1 a=sendrecv a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=msid:751f239e-4ae0-c549-aa3d-890de772998b|answer-C2| SDP 如下:
v=0 o=- 1070771854436052752 2 IN IP4 0.0.0.0 s=- t=0 0 a=ice-options:trickle ice2 a=group:BUNDLE a1 v1 a=group:LS a1 v1m=audio 12100 UDP/TLS/RTP/SAVPF 96 0 8 97 98 c=IN IP4 192.0.2.100 a=mid:a1 a=sendrecv a=rtpmap:96 opus/48000/2 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 telephone-event/8000 a=rtpmap:98 telephone-event/48000 a=fmtp:97 0-15 a=fmtp:98 0-15 a=maxptime:120 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=msid:bbce3ba6-abfc-ac63-d00a-e15b286f8fce a=ice-ufrag:4ZcD a=ice-pwd:ZaaG6OG7tCn4J/lehAGz+HHD a=fingerprint:sha-256C4:68:F8:77:6A:44:F1:98:6D:7C:9F:47:EB:E3:34:A4:0A:AA:2D:49:08:28:70:2E:1F:AE:18:7D:4E:3E:66:BF a=setup:passive a=tls-id:9e5b948ade9c3d41de6617b68f769e55 a=rtcp-mux a=rtcp-mux-only a=rtcp-rsize a=candidate:1 1 udp 255 192.0.2.100 12100 typ relayraddr 0.0.0.0 rport 0 a=end-of-candidatesm=video 12100 UDP/TLS/RTP/SAVPF 100 101 102 103 c=IN IP4 192.0.2.100 a=mid:v1 a=sendrecv a=rtpmap:100 VP8/90000 a=rtpmap:101 H264/90000 a=fmtp:101 packetization-mode=1;profile-level-id=42e01f a=rtpmap:102 rtx/90000 a=fmtp:102 apt=100 a=rtpmap:103 rtx/90000 a=fmtp:103 apt=101 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=msid:bbce3ba6-abfc-ac63-d00a-e15b286f8fce8. Security Considerations
IETF 已經發布了單獨的文檔 [RFC8827] [RFC8826],將 WebRTC 的安全架構作為一個整體來描述。本節的其余部分將介紹本文檔的安全考慮事項。
雖然 JSEP 接口在形式上是一個 API,但最好將其視為一個 Internet 協議,從 JSEP 實現的角度來看,應用程序 JavaScript 是不可信任的。因此,采用 [RFC3552] 的威脅模型。特別是 JavaScript 可以以任何順序和任何輸入(包括惡意輸入)調用 API。當我們考慮傳遞給 setLocalDescription 的 SDP 時,這尤其重要。雖然正確的 API 使用要求應用程序通過從 createOffer 或 createAnswer 派生的 SDP,但不能保證應用程序這樣做。JSEP 實現必須為 JavaScript 傳入虛假數據做好準備。
相反,應用程序開發者需要知道 JavaScript 不能完全控制端點行為。特別值得一提的一種情況是,將 ICE 候選從 SDP 中提取出來或寫入 Trickle 候選并不具有預期的行為:實現仍然會對這些候選執行檢查,即使他們沒有被發送到另一邊。因此無法通過刪除 server-reflexive 候選來阻止遠端對等體探測自己的公共 IP 地址。希望隱藏其公網 IP 地址的應用程序必須將 ICE 代理配置為只使用 relay 候選地址。
9. IANA Considerations
This document has no IANA actions.
總結
以上是生活随笔為你收集整理的[RFC8829] JavaScript Session Establishment Protocol (JSEP)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: markdown中编写数学公式的常用 l
- 下一篇: SheetJS 读取excel文件转出j