前言
getStats是WebRTC一個(gè)非常重要的API守伸,用來向開發(fā)者和用戶導(dǎo)出WebRTC運(yùn)行時(shí)狀態(tài)信息绎秒,包括網(wǎng)絡(luò)數(shù)據(jù)接收和發(fā)送狀態(tài)、P2P客戶端媒體數(shù)據(jù)采集和渲染狀態(tài)等[1]尼摹。這些信息對(duì)于監(jiān)控WebRTC運(yùn)行狀態(tài)见芹、排除程序錯(cuò)誤等非常重要剂娄。<br />
本文首先描述W3C定義的getStats標(biāo)準(zhǔn),然后展示如何在JS層調(diào)用getStats玄呛,最后深入分析WebRTC源代碼中g(shù)etStats的實(shí)現(xiàn)阅懦。全文從標(biāo)準(zhǔn)到實(shí)現(xiàn),全方位透徹展示getStats的細(xì)節(jié)徘铝。<br />
一 getStats標(biāo)準(zhǔn)
getStats的標(biāo)準(zhǔn)由W3C定義耳胎,其接口很簡(jiǎn)單,但是卻返回豐富的WebRTC運(yùn)行時(shí)信息惕它。其返回信息的主要內(nèi)容如下[2]:<br />
- 發(fā)送端采集統(tǒng)計(jì):對(duì)應(yīng)于媒體數(shù)據(jù)的產(chǎn)生怕午,包括幀率,幀大小淹魄,媒體數(shù)據(jù)源的時(shí)鐘頻率郁惜,編解碼器名稱,等等揭北。<br />
- 發(fā)送端RTP統(tǒng)計(jì):對(duì)應(yīng)于媒體數(shù)據(jù)的發(fā)送扳炬,包括發(fā)送數(shù)據(jù)包數(shù),發(fā)送字節(jié)數(shù)搔体,往返時(shí)間RTT,等等半醉。<br />
- 接收端RTP統(tǒng)計(jì):對(duì)應(yīng)于媒體數(shù)據(jù)的接收疚俱,包括接收數(shù)據(jù)包數(shù),接收字節(jié)數(shù)缩多,丟棄數(shù)據(jù)包數(shù)呆奕,丟失數(shù)據(jù)包數(shù),網(wǎng)絡(luò)抖動(dòng)jitter衬吆,等等梁钾。<br />
- 接收端渲染統(tǒng)計(jì):對(duì)應(yīng)于媒體數(shù)據(jù)的渲染,包括丟棄幀數(shù)逊抡,丟失幀數(shù)姆泻,渲染幀數(shù),渲染延遲冒嫡,等等拇勃。<br />
另外還有一些雜項(xiàng)統(tǒng)計(jì),如DataChannel度量孝凌,網(wǎng)絡(luò)接口度量方咆,證書統(tǒng)計(jì)等等。在眾多信息中蟀架,有一些反映WebRTC運(yùn)行狀態(tài)的核心度量瓣赂,包括往返時(shí)間RTT榆骚,丟包率和接收端延遲等,分別表述如下:<br />
- 往返時(shí)間RTT:表示數(shù)據(jù)在網(wǎng)絡(luò)上傳輸所用的時(shí)間煌集,一般通過RTCP 的SR/RR數(shù)據(jù)包中的相關(guān)域進(jìn)行計(jì)算寨躁。該度量直接反映網(wǎng)絡(luò)狀況的好壞。<br />
- 丟包率影響接收端音視頻質(zhì)量牙勘,在嚴(yán)重的情況下可能導(dǎo)致聲音跳變或者視頻馬賽克职恳,從側(cè)面反映網(wǎng)絡(luò)狀況的好壞。<br />
- 音視頻數(shù)據(jù)到達(dá)接收端之后方面,要經(jīng)歷收包放钦、解碼、渲染等過程恭金,該過程會(huì)帶來延遲操禀。接收端延遲是數(shù)據(jù)從采集到渲染單向延遲的重要組成部分。<br />
通過以上分析可知横腿,getStats的返回信息包含WebRTC數(shù)據(jù)管線的各個(gè)階段的統(tǒng)計(jì)信息颓屑,從數(shù)據(jù)采集、編碼到發(fā)送耿焊,再到數(shù)據(jù)接收揪惦、解碼和渲染。這為監(jiān)控WebRTC應(yīng)用的運(yùn)行狀態(tài)提供第一手?jǐn)?shù)據(jù)罗侯。<br />
二 使用JS調(diào)用getStats
getStats的JS API很簡(jiǎn)單器腋, W3C規(guī)定getStats的JS API函數(shù)PTCPeerConnection.getStats需要三個(gè)參數(shù):一個(gè)可為空的MediaStreamTrack對(duì)象,一個(gè)調(diào)用成功時(shí)的回調(diào)函數(shù)和一個(gè)調(diào)用失敗時(shí)的回調(diào)函數(shù)钩杰。成功回調(diào)函數(shù)的參數(shù)為getStats得到的RTCStatsReport纫塌,主要工作就發(fā)生在解析RTCStatsReport上,拿到我們感興趣的參數(shù)讲弄,進(jìn)而分析應(yīng)用的運(yùn)行狀態(tài)措左。<br />
下面我們選取W3C標(biāo)準(zhǔn)上給出的例子作簡(jiǎn)單講解[1]糖儡。假設(shè)當(dāng)前會(huì)話的通話質(zhì)量很差吼砂,我們想知道是不是由于丟包率過大引起的薄扁。因此饰迹,我們可以通過getStats返回結(jié)果的outbound-rtp中的丟包數(shù)和收包數(shù)計(jì)算丟包率埠褪,然后進(jìn)行判斷搏存。具體代碼實(shí)現(xiàn)如下:<br />
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);
}
通過上述例子横朋,我們可以體會(huì)到從JS層調(diào)用getStats分析應(yīng)用運(yùn)行狀態(tài)的基本流程藐俺。值得注意的是赏壹,Chrome和Firefox兩款瀏覽器在調(diào)用方面有稍微差別鱼炒,具體請(qǐng)參考文檔[3]。<br />
三 getStats在WebRTC內(nèi)部的實(shí)現(xiàn)
JS層的getStats調(diào)用如何傳遞到到WebRTC內(nèi)部的實(shí)現(xiàn)函數(shù)蝌借,涉及到瀏覽器的內(nèi)部工作原理昔瞧,具體到Chrome瀏覽器來講指蚁,是由WebKit,V8自晰,Content凝化,libjingle等模塊一起協(xié)同工作實(shí)現(xiàn)。本節(jié)我們不討論這里面的細(xì)節(jié)酬荞,我們只關(guān)注getStats在WebRTC內(nèi)部的實(shí)現(xiàn)搓劫。<br />
WebRTC模塊對(duì)外提供兩個(gè)重要對(duì)象:PeerConnectionFactory和PeerConnection,前者負(fù)責(zé)一系列重要對(duì)象的創(chuàng)建混巧,如MediaStream枪向,MediaSource,MediaTrack等等咧党,后者則負(fù)責(zé)P2P連接的建立和維護(hù)秘蛔,包括CreateOffer/Answer,AddStream等操作傍衡。監(jiān)控P2P連接運(yùn)行狀態(tài)GetStats函數(shù)深员,自然在PeerConnection對(duì)象中實(shí)現(xiàn),而該對(duì)象把任務(wù)委托給成員變量StatsCollector對(duì)象的UpdateStats函數(shù)來實(shí)現(xiàn):<br />
void StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) {
RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); // 由signal線程調(diào)用蛙埂;
double time_now = GetTimeNow();
const double kMinGatherStatsPeriod = 50;
if (stats_gathering_started_ != 0 &&
stats_gathering_started_ + kMinGatherStatsPeriod > time_now) {
return; // 調(diào)用間隔不低于50ms倦畅;
}
stats_gathering_started_ = time_now;
if (pc_->session()) {
ExtractSessionInfo(); // 收集傳輸信息;
ExtractVoiceInfo(); // 收集VoiceChannel信息箱残;
ExtractVideoInfo(level); // 收集VideoChannel信息滔迈;
ExtractSenderInfo(); // 收集PeerConnection的sender信息;
ExtractDataInfo(); // 收集DataChannel信息被辑;
UpdateTrackReports(); // 更新Track報(bào)告;
}
}
由該函數(shù)我們可以看到敬惦,信息的收集是分模塊進(jìn)行的盼理,其中最重要的是四個(gè)模塊的信息:Transport,VoiceChannel俄删,VideoChannel宏怔,DataChannel。顧名思義畴椰,Transport是和網(wǎng)絡(luò)相關(guān)的統(tǒng)計(jì)信息臊诊,而其余三個(gè)是和各自MediaChannel相關(guān)的統(tǒng)計(jì)信息。<br />
Extract系列函數(shù)從相應(yīng)模塊收集到信息后斜脂,執(zhí)行后處理操作抓艳,把不同類型的信息重新組織為類型相同的StatsReport對(duì)象,存儲(chǔ)到StatsCollector的列表中帚戳。StatsReport對(duì)象結(jié)構(gòu)基本定義如下:<br />
struct StatsReport {
const Id id_; // 包括類型玷或,唯一標(biāo)示符等信息儡首;
double timestamp_; // 本次信息收集的開始時(shí)間;
Values values_; // 信息集合偏友,可存儲(chǔ)int, int64, string, bool, double等類型
};
下面以ExtractVideoInfo為例分析信息收集過程:<br />
void StatsCollector::ExtractVideoInfo(PeerConnectionInterface::StatsOutputLevel level) {
cricket::VideoMediaInfo video_info;
// 從video channel收集信息蔬胯,包括發(fā)送端,接收端和帶寬估計(jì)信息位他;
if (!pc_->session()->video_channel()->GetStats(&video_info)) {
return;
}
// 收集到的信息歸一化為StatsReport對(duì)象氛濒;
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收集到的數(shù)據(jù)來自三個(gè)模塊:VideoSendStream,VideoReceiveStream 和Call鹅髓,這三個(gè)模塊分別從自己的信息統(tǒng)計(jì)對(duì)象中獲得統(tǒng)計(jì)數(shù)據(jù)舞竿,最后匯總為VideoMediaInfo對(duì)象,由ExtractStatsFromXX系列函數(shù)歸一化為StatsReport對(duì)象迈勋。<br />
以上分析的即為getStats函數(shù)的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)炬灭。需要注意的是,getStats只負(fù)責(zé)拉取統(tǒng)計(jì)數(shù)據(jù)靡菇,而統(tǒng)計(jì)數(shù)據(jù)本身則由WebRTC內(nèi)部各個(gè)模塊周期性更新重归,這個(gè)過程是異步的。例如厦凤,傳輸層的RTT是由網(wǎng)絡(luò)線程收到數(shù)據(jù)包后實(shí)時(shí)更新鼻吮,而帶寬估計(jì)信息則是在受到RTCP報(bào)文后解析計(jì)算得到。下面以VideoReceiveStream統(tǒng)計(jì)信息的更新過程為例较鼓,深入分析這部分是如何協(xié)同工作的:<br />
在Video接收端椎木,network/decoder/render三個(gè)線程在各自工作完成后,都會(huì)更新相應(yīng)的統(tǒng)計(jì)數(shù)據(jù)到timing對(duì)象中博烂。而module process線程則周期性更新Stats proxy對(duì)象香椎,該對(duì)象從timing對(duì)象中拉取數(shù)據(jù),保存在自己的stats成員變量中禽篱。最后畜伐,getstats線程調(diào)用流程到達(dá)stats proxy對(duì)象,獲取stats數(shù)據(jù)而返躺率。工作線程玛界、更新線程和拉取線程共同協(xié)同工作完成統(tǒng)計(jì)數(shù)據(jù)的產(chǎn)生、更新和拉取悼吱。<br />
四 總結(jié)
本文從標(biāo)準(zhǔn)慎框、使用和實(shí)現(xiàn)三個(gè)方面全方位分析了WebRTC的getStats API,這對(duì)WebRTC應(yīng)用的運(yùn)行時(shí)監(jiān)控和狀態(tài)分析排錯(cuò)具有重要意義后添,我們從另一角度對(duì)WebRTC有了更深入的理解笨枯。<br />
<br />
參考文獻(xiàn)
[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