WebRTC getStats 詳解:從標準、調用到實現
前言
getStats是WebRTC一個非常重要的API,用來向開發者和用戶導出WebRTC運行時狀態信息,包括網絡數據接收和發送狀態、P2P客戶端媒體數據采集和渲染狀態等[1]。這些信息對于監控WebRTC運行狀態、排除程序錯誤等非常重要。
本文首先描述W3C定義的getStats標準,然后展示如何在JS層調用getStats,最后深入分析WebRTC源代碼中getStats的實現。全文從標準到實現,全方位透徹展示getStats的細節。
一 getStats標準
getStats的標準由W3C定義,其接口很簡單,但是卻返回豐富的WebRTC運行時信息。其返回信息的主要內容如下[2]:
- 發送端采集統計:對應于媒體數據的產生,包括幀率,幀大小,媒體數據源的時鐘頻率,編解碼器名稱,等等。
- 發送端RTP統計:對應于媒體數據的發送,包括發送數據包數,發送字節數,往返時間RTT,等等。
- 接收端RTP統計:對應于媒體數據的接收,包括接收數據包數,接收字節數,丟棄數據包數,丟失數據包數,網絡抖動jitter,等等。
- 接收端渲染統計:對應于媒體數據的渲染,包括丟棄幀數,丟失幀數,渲染幀數,渲染延遲,等等。
另外還有一些雜項統計,如DataChannel度量,網絡接口度量,證書統計等等。在眾多信息中,有一些反映WebRTC運行狀態的核心度量,包括往返時間RTT,丟包率和接收端延遲等,分別表述如下:
- 往返時間RTT:表示數據在網絡上傳輸所用的時間,一般通過RTCP 的SR/RR數據包中的相關域進行計算。該度量直接反映網絡狀況的好壞。
- 丟包率影響接收端音視頻質量,在嚴重的情況下可能導致聲音跳變或者視頻馬賽克,從側面反映網絡狀況的好壞。
- 音視頻數據到達接收端之后,要經歷收包、解碼、渲染等過程,該過程會帶來延遲。接收端延遲是數據從采集到渲染單向延遲的重要組成部分。
通過以上分析可知,getStats的返回信息包含WebRTC數據管線的各個階段的統計信息,從數據采集、編碼到發送,再到數據接收、解碼和渲染。這為監控WebRTC應用的運行狀態提供第一手數據。
二 使用JS調用getStats
getStats的JS API很簡單, W3C規定getStats的JS API函數PTCPeerConnection.getStats需要三個參數:一個可為空的MediaStreamTrack對象,一個調用成功時的回調函數和一個調用失敗時的回調函數。成功回調函數的參數為getStats得到的RTCStatsReport,主要工作就發生在解析RTCStatsReport上,拿到我們感興趣的參數,進而分析應用的運行狀態。
下面我們選取W3C標準上給出的例子作簡單講解[1]。假設當前會話的通話質量很差,我們想知道是不是由于丟包率過大引起的。因此,我們可以通過getStats返回結果的outbound-rtp中的丟包數和收包數計算丟包率,然后進行判斷。具體代碼實現如下:
var baselineReport, currentReport; var selector = pc.getRemoteStreams()[0].getAudioTracks()[0]; pc.getStats(selector, function (report) { baselineReport = report; }, logError);setTimeout(function () { pc.getStats(selector, function (report) { currentReport = report; processStats(); }, logError); }, aBit);
function processStats() { for (var i in currentReport) { var now = currentReport[i]; if (now.type != "outbund-rtp") continue;
base = baselineReport[now.id]; if (base) { remoteNow = currentReport[now.associateStatsId]; remoteBase = baselineReport[base.associateStatsId]; var packetsSent = now.packetsSent - base.packetsSent; var packetsReceived = remoteNow.packetsReceived – remoteBase.packetsReceived; // if fractionLost is > 0.3, we have probably found the culprit var fractionLost = (packetsSent - packetsReceived) / packetsSent; } }
} function logError(error) { log(error.name + ": " + error.message); }</pre>
通過上述例子,我們可以體會到從JS層調用getStats分析應用運行狀態的基本流程。值得注意的是,Chrome和Firefox兩款瀏覽器在調用方面有稍微差別,具體請參考文檔[3]。
三 getStats在WebRTC內部的實現
JS層的getStats調用如何傳遞到到WebRTC內部的實現函數,涉及到瀏覽器的內部工作原理,具體到Chrome瀏覽器來講,是由WebKit,V8,Content,libjingle等模塊一起協同工作實現。本節我們不討論這里面的細節,我們只關注getStats在WebRTC內部的實現。
WebRTC模塊對外提供兩個重要對象:PeerConnectionFactory和PeerConnection,前者負責一系列重要對象的創建,如MediaStream,MediaSource,MediaTrack等等,后者則負責P2P連接的建立和維護,包括CreateOffer/Answer,AddStream等操作。監控P2P連接運行狀態GetStats函數,自然在PeerConnection對象中實現,而該對象把任務委托給成員變量StatsCollector對象的UpdateStats函數來實現:
void StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) { RTCDCHECK(pc->session()->signaling_thread()->IsCurrent()); // 由signal線程調用; double time_now = GetTimeNow(); const double kMinGatherStatsPeriod = 50; if (stats_gatheringstarted != 0 && stats_gatheringstarted + kMinGatherStatsPeriod > time_now) { return; // 調用間隔不低于50ms; }stats_gatheringstarted = timenow; if (pc->session()) { ExtractSessionInfo(); // 收集傳輸信息; ExtractVoiceInfo(); // 收集VoiceChannel信息; ExtractVideoInfo(level); // 收集VideoChannel信息; ExtractSenderInfo(); // 收集PeerConnection的sender信息; ExtractDataInfo(); // 收集DataChannel信息; UpdateTrackReports(); // 更新Track報告; } }</pre>
由該函數我們可以看到,信息的收集是分模塊進行的,其中最重要的是四個模塊的信息:Transport,VoiceChannel,VideoChannel,DataChannel。顧名思義,Transport是和網絡相關的統計信息,而其余三個是和各自MediaChannel相關的統計信息。
Extract系列函數從相應模塊收集到信息后,執行后處理操作,把不同類型的信息重新組織為類型相同的StatsReport對象,存儲到StatsCollector的列表中。StatsReport對象結構基本定義如下:
struct StatsReport { const Id id_; // 包括類型,唯一標示符等信息; double timestamp_; // 本次信息收集的開始時間; Values values_; // 信息集合,可存儲int, int64, string, bool, double等類型 };下面以ExtractVideoInfo為例分析信息收集過程:
void StatsCollector::ExtractVideoInfo(PeerConnectionInterface::StatsOutputLevel level) { cricket::VideoMediaInfo video_info; // 從video channel收集信息,包括發送端,接收端和帶寬估計信息; if (!pc_->session()->video_channel()->GetStats(&video_info)) { return; } // 收集到的信息歸一化為StatsReport對象; ExtractStatsFromList(video_info.receivers, transport_id, this, StatsReport::kReceive); ExtractStatsFromList(video_info.senders, transport_id, this, StatsReport::kSend); ExtractStats(video_info.bw_estimations[0], stats_gathering_started_, level, report); }從videochannel收集到的數據來自三個模塊:VideoSendStream,VideoReceiveStream 和Call,這三個模塊分別從自己的信息統計對象中獲得統計數據,最后匯總為VideoMediaInfo對象,由ExtractStatsFromXX系列函數歸一化為StatsReport對象。
以上分析的即為getStats函數的內部實現細節。需要注意的是,getStats只負責拉取統計數據,而統計數據本身則由WebRTC內部各個模塊周期性更新,這個過程是異步的。例如,傳輸層的RTT是由網絡線程收到數據包后實時更新,而帶寬估計信息則是在受到RTCP報文后解析計算得到。下面以VideoReceiveStream統計信息的更新過程為例,深入分析這部分是如何協同工作的:
VideoReceiveStream的數據更新和拉取.png
在Video接收端,network/decoder/render三個線程在各自工作完成后,都會更新相應的統計數據到timing對象中。而module process線程則周期性更新Stats proxy對象,該對象從timing對象中拉取數據,保存在自己的stats成員變量中。最后,getstats線程調用流程到達stats proxy對象,獲取stats數據而返。工作線程、更新線程和拉取線程共同協同工作完成統計數據的產生、更新和拉取。
四 總結
本文從標準、使用和實現三個方面全方位分析了WebRTC的getStats API,這對WebRTC應用的運行時監控和狀態分析排錯具有重要意義,我們從另一角度對WebRTC有了更深入的理解。
參考文獻
[1] Identifiers for WebRTC's Statistics API:
https://www.w3.org/TR/webrtc-stats/
[2] Basics of WebRTC getStats() API:
https://www.callstats.io/2015/07/06/basics-webrtc-getstats-api/
[3] RTCPeerConnection.getStats: Chrome VS Firefox:
http://blog.telenor.io/webrtc/2015/06/11/getstats-chrome-vs-firefox.html
來自:http://www.jianshu.com/p/41856118f833