WebRTC getStats 詳解:從標準、調用到實現

AmbGreenwel 8年前發布 | 36K 次閱讀 電話/通訊/IM聊天 webRTC

前言

getStats是WebRTC一個非常重要的API,用來向開發者和用戶導出WebRTC運行時狀態信息,包括網絡數據接收和發送狀態、P2P客戶端媒體數據采集和渲染狀態等[1]。這些信息對于監控WebRTC運行狀態、排除程序錯誤等非常重要。

本文首先描述W3C定義的getStats標準,然后展示如何在JS層調用getStats,最后深入分析WebRTC源代碼中getStats的實現。全文從標準到實現,全方位透徹展示getStats的細節。

一 getStats標準

getStats的標準由W3C定義,其接口很簡單,但是卻返回豐富的WebRTC運行時信息。其返回信息的主要內容如下[2]:

  1. 發送端采集統計:對應于媒體數據的產生,包括幀率,幀大小,媒體數據源的時鐘頻率,編解碼器名稱,等等。
  2. 發送端RTP統計:對應于媒體數據的發送,包括發送數據包數,發送字節數,往返時間RTT,等等。
  3. 接收端RTP統計:對應于媒體數據的接收,包括接收數據包數,接收字節數,丟棄數據包數,丟失數據包數,網絡抖動jitter,等等。
  4. 接收端渲染統計:對應于媒體數據的渲染,包括丟棄幀數,丟失幀數,渲染幀數,渲染延遲,等等。

另外還有一些雜項統計,如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

 

 本文由用戶 AmbGreenwel 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!