WebRTC Native 原始碼導讀(十四):API 概覽
第一次看到 WebRTC 這個詞是 15 年在一期 Android Weekly 中 ,但當時完全看不懂它在講什麼,也就沒有深究。兩年後,我開始搞起 WebRTC,並整理出了一套開箱即用的 WebRTC 開發環境 ,距今又過了一年多。
按常理,這篇文章要介紹的內容本應最先呈現,但我搞 WebRTC 的路線略微反常,由於沒有實際使用的需求,所以我先是研究了不少模組的實現原理。但隨著接觸到更多內容,我越來越意識到,如果沒有一個清晰的全域性觀,那效率將會越來越低,因此也就有了這篇文章。
鑑於目前我對 WebRTC 的瞭解仍然有限,所以本文很可能仍有遺漏,或錯誤,但我會持續更新本文 。
SDP 基本結構
首先我們搞清楚 SDP 的基本結構。
總體來說,WebRTC 的 SDP 分為幾個部分:
- session metadata: v=, o=, s=, t=
- network description: c=, a=candidate
- stream description: m=, a=rtpmap, a=fmtp, a=sendrecv …
- security description: a=crypto, a=ice-frag, a=ice-pwd, a=fingerprint
- QoS, grouping description: a=rtcp-fb, a=group, a=rtcpmux
m= 開頭的一段叫做 m section,這一行叫 m line,裡面有很多 a line 來描述這種 media 的各種屬性。我們稱一種媒體資料為一種 media,每種 media 在 SDP 裡都有 m section。
WebRTC 的 SDP 有三種類型:
- offer: 發起方提供的自己對本次通話的描述;
- answer: 其他方收到 offer 後,給出的迴應;
- pranswer: provisional answer,非最終 answer,之後可能被 pranswer 或 answer 更新;
Plan B v.s. Unified Plan
說到 SDP,就不得不提它的兩種 Plan,它們是表達傳輸多路媒體流時的兩種 SDP 格式。多路媒體流的例子有:錄屏 + 相機,或多個相機(視角)。
Plan B 是 SDP 裡同類型的媒體流只有一個 m line,同類型的多個媒體流之間通過 msid 區分,而 Unified Plan 則是每個媒體流都有一個 m line,因此如果有兩路視訊,那就會有兩個 video m line。
WebRTC 標準採納的是 Unified Plan,WebRTC 程式碼也已支援,所以我們就只關注 Unified Plan 的 API。
參考:sary.com/plan-b/" rel="nofollow,noindex" target="_blank">Plan B ,Unified Plan ,Unified Plan vs Plan B 。
Plan B 在 WebRTC 原始碼裡對應的是 PC 的 Stream/Sender/Receiver API,Unified Plan 對應的是 Track/Transceiver API。
Capturer/Source/Track/Sink/Transceiver
接著我們梳理一下媒體資料交換過程中的幾個關鍵概念:
- Capturer: 負責資料採集,只有視訊才有這一層抽象,它有多種實現,相機採集(Android 還有 Camera1/Camera2 兩套)、錄屏採集、視訊檔案採集等;
- Source: 資料來源;
- Track: 媒體資料交換的載體,傳送端把本地的 Track 傳送給遠端的接收端,每個 Track 都有自己的 track id,多個關聯的 Track 有一個相同的 stream id;
- Sink: Track 資料的消費者,只有視訊才有這一層封裝,傳送端視訊的本地預覽、接收端收到遠端視訊後的渲染,都由 Sink 負責;
- Transceiver: 負責收發媒體資料;
以視訊為例,資料由傳送端的 Capturer 採集,交給 Source,再交給本地的 Track,然後兵分兩路,一路由本地 Sink 進行預覽,一路由 Transceiver 傳送給接收端;接收端 Track 則把資料交給 Sink 渲染。
Capturer 的建立和銷燬完全由 APP 層負責,只需要把它和 Source 關聯起來即可;建立 Source 需要呼叫 PC Factory 介面,建立 Track 也是,並且需要提供 Source 引數;Sink 的建立和銷燬也由 APP 層負責,只需要把它們新增到 Track 裡即可;建立 Transceiver 則需要呼叫 PC 介面。
好了,接下來我們就看看 PC Factory 和 PC 的介面。
PeerConnectionFactory 介面
CreatePeerConnectionFactory
預設的編譯選項裡,rtc_use_builtin_sw_codecs = false
,因此USE_BUILTIN_SW_CODECS
未被定義,CreatePeerConnectionFactory
只有一個過載版本:接收三個 thread、adm、audio/video encoder/decoder factory、AudioMixer 和 AudioProcessing。
CreatePeerConnection
建立 PC 物件,接收 RTCConfiguration 和 PeerConnectionDependencies,前者用來容納各種配置,後者則用來容納各種可定製的介面實現,例如 PortAllocator, AsyncResolverFactory, RTCCertificateGeneratorInterface, SSLCertificateVerifier。
目前 Android/iOS 對 dependencies 的支援還未跟上,雖然這種高階用法的使用者也不怕在 native 層自己做封裝,但就又得重新造一遍 WebRTC Java/ObjC 程式碼裡的輪子了。
CreateAudio/VideoSource/Track
這就是前面我們提到的建立 Audio/Video Source/Track 的介面了。
PeerConnection 介面
準備工作相關:
- AddTrack: 新增要傳送的 track;
- AddTransceiver: 新增 transceiver;
- CreateDataChannel: 新增 DataChannel;
- RemoveTrack: 移除 track;
- GetTransceivers: 獲取所有的 transceiver;
建立 P2P 連線相關:
- CreateOffer: 建立 offer;
- CreateAnswer: 建立 answer;
- SetLocalDescription: 設定本地 SDP;
- SetRemoteDescription: 設定遠端 SDP;
- AddIceCandidate: 新增 ICE candidate;
- RemoveIceCandidates: 移除 ICE candidate;
注意:CreateOffer/CreateAnswer 時傳入的 RTCOfferAnswerOptions 裡,有offer_to_receive_X
欄位,它們是為了相容 Plan B 語義的,一旦設定,即便沒有 AddTrack,SDP 裡也會包含 audio/video 的 m line。使用 Unified Plan 時,不應設定這兩個欄位,而應提前呼叫 AddTrack/AddTransceiver/CreateDataChannel,來表明自己是否需要 audio/video/data。
其他介面:
- GetStats: 獲取統計資料;
- SetBitrate: 設定這個 PC 總的傳送位元速率,包括初始位元速率、最小位元速率、最大位元速率;
- SetBitrateAllocationStrategy: 設定自定義位元速率分配策略,可以通過這個介面實現針對每個 track 的位元速率分配策略;
注意:SetBitrateAllocationStrategy
在 Android 和 iOS 平臺都沒有暴露出來,Android 暴露了 SetBitrate 介面,iOS 則沒有,不過可以通過RTCRtpSender setParameters
限制編碼器的輸出位元速率。
回撥介面 PeerConnectionObserver:
-
OnSignalingChange: 產生/設定 SDP 後,會觸發 signaling state 變化,常見的變化是
stable -> have-local-offer -> stable
或stable -> have-remote-offer -> stable
,具體可以檢視SPEC 4.3 State Definitions ; - OnRenegotiationNeeded: 需要重新協商(重新建立 P2P 連線)時回撥,例如 ICE restart 時會回撥;
- OnIceGatheringChange: ICE candidate 收集狀態變化後回撥;
- OnIceConnectionChange: ICE 連線狀態變化後回撥;
- OnIceCandidate: 收集到本地 ICE candidate 後回撥;
- OnIceCandidatesRemoved: 本地 ICE candidate 被移除後回撥;
- OnTrack: 呼叫 SetRemoteDescription 後,如果 SDP 表明將會建立接收用的 transceiver,就會回撥這個介面;
- OnRemoveTrack: 當確定一個 track 不再接收媒體資料後,會回撥這個介面,track 不會移除,但 transceiver 的 recv 方向將會被去掉;
接下來我們重點看一下 transceiver。
RtpTransceiver
SDP 的 m section 裡有一行a=mid:
,定義了這種 media 的 id,叫 mid,例如下面這對 offer 和 answer:
# offer ... a=group:BUNDLE 0 1 2 ... m=video 9 UDP/TLS/RTP/SAVPF 100 96 97 98 99 101 127 124 125 ... a=mid:0 ... m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126 ... a=mid:1 ... m=application 9 DTLS/SCTP 5000 ... a=mid:2 ... # answer ... a=group:BUNDLE 0 1 2 ... m=video 9 UDP/TLS/RTP/SAVPF 100 96 97 98 99 101 127 124 ... a=mid:0 ... m=audio 9 UDP/TLS/RTP/SAVPF 103 104 9 102 0 8 106 105 13 110 112 113 126 ... a=mid:1 ... m=application 9 DTLS/SCTP 5000 ... a=mid:2 ...
其中有三種 media: video, audio, application,mid 依次為 0, 1, 2。application 是 DataChannel 的 media type。
我們注意到,offer 和 answer 裡同一種 media 的 mid 是相同的,也就是說,對某一端來說,他收發的同一種媒體資料,mid 是相同的。
在 WebRTC 標準裡,transceiver 表示的就是收發相同 mid 的 sender 和 receiver 的一個組合體,其中會有 media type, mid, direction, sender, receiver 等欄位。其中 direction 有幾種取值:kSendRecv, kSendOnly, kRecvOnly, kInactive。
AddTrack 時我們 add 的是本地的 track,即要傳送的資料流,首次 AddTrack 時,會建立 transceiver,預設其 direction 是 kSendRecv。儘管在 CreateOffer 時我們可以通過設定RTCOfferAnswerOptions
的offer_to_receive_X
欄位來控制是否 receive,但這兩個欄位是 legacy 欄位,我們應該儘量避免。那如何控制 transceiver 的方向呢?我們可以使用 AddTransceiver 介面。
如果想要建立 kSendOnly 的 transceiver,可以傳入 track,並在RtpTransceiverInit
中設定 direction 為 kSendOnly;或者只傳入 media type 和 init 結構體,稍後再 AddTrack。如果想要建立 kRecvOnly 的 transceiver,可以只傳入 media type 和 init 結構體,並且不 AddTrack。
transceiver 何時與 SDP 裡的 m section 關聯呢?offer 端在建立 offer 時,會根據已有的 transceiver 建立 m section,並記下每個 transceiver 在 SDP 裡對應的 m section 的 index 值,以便在 SetLocalDescription 時,可以為 transceiver 設定正確的 mid ;answer 端在 SetRemoteDescription(offer 端發來的 offer)時,如果 offer 裡的 m section 有 recv 方向,那就按 media type 來查詢已有的 transceiver,如果能找到就可以將其關聯起來,否則就建立一個 kRecvOnly 的 transceiver (因為 offer 只有可能是 kSendOnly 了,不發也不收的 media,不會出現在 SDP 裡,那對此 offer 的迴應也就只能是 kRecvOnly 了)。
總結一下,無論是 offer 端還是 answer 端,需要傳送的 media,才提前新增好有 send 方向的 transceiver,僅接收的 media,無需提前新增 transceiver(提前添加了也不會被使用)。
附錄一:SDP 部分細節
-
m line 裡會指明傳輸協議,例如 UDP/TLS/RTP/SAVPF,最後的 SAVPF 還有其他幾種值:AVP, SAVP, AVPF, SAVPF
- AVP 意為 AV profile
- S 意為 secure
- F 意為 feedback
- rtpmap 是描述 codec 的,但有特殊的 rtx codec,其實不是 codec,例如 rtx;
-
fmtp 補充描述 codec 的引數,format parameters
- max-fr: maximum framerate
- profile-level-id: H.264 的 profile level id
-
rtx 描述重傳策略,由 rtpmap 指明,它的引數由 fmtp 描述
- apt: associated payload type,指明所描述的 stream;
- rtx-time: rtp 包在緩衝區保留時間;
-
rtcp-fb: RTCP 反饋機制
- offer 裡面列出一些反饋機制,answer 裡應移除不理解、不支援的機制,但不能修改;
- ack rpsi/app
- nack pli/sli/rpsi/app
- rpsi: reference picture selection indication
- app: app 層反饋機制
- pli: picture loss indication,表明收流端丟失了一幅影象的一些資料,傳送端可能會發送一個 I 幀(類似於 FIR),但要考慮擁塞控制
- sli: slice loss indication
- ccm fir: codec control message, full intra refresh
- fec 類似於 rtx,也由 rtpmap 指明,它的引數由 fmtp 指明;