您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

rtp_sts.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /* global ssrc2jid */
  2. /**
  3. * Calculates packet lost percent using the number of lost packets and the number of all packet.
  4. * @param lostPackets the number of lost packets
  5. * @param totalPackets the number of all packets.
  6. * @returns {number} packet loss percent
  7. */
  8. function calculatePacketLoss(lostPackets, totalPackets) {
  9. return Math.round((lostPackets/totalPackets)*100);
  10. }
  11. /**
  12. * Peer statistics data holder.
  13. * @constructor
  14. */
  15. function PeerStats()
  16. {
  17. this.ssrc2Loss = {};
  18. this.ssrc2AudioLevel = {};
  19. this.ssrc2bitrate = {};
  20. this.ssrc2resolution = {};
  21. }
  22. /**
  23. * The bandwidth
  24. * @type {{}}
  25. */
  26. PeerStats.bandwidth = {};
  27. /**
  28. * The bit rate
  29. * @type {{}}
  30. */
  31. PeerStats.bitrate = {};
  32. /**
  33. * The packet loss rate
  34. * @type {{}}
  35. */
  36. PeerStats.packetLoss = null;
  37. /**
  38. * Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
  39. * represented by this instance.
  40. * @param ssrc audio or video RTP stream SSRC.
  41. * @param lossRate new packet loss rate value to be set.
  42. */
  43. PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
  44. {
  45. this.ssrc2Loss[ssrc] = lossRate;
  46. };
  47. /**
  48. * Sets resolution for given <tt>ssrc</tt> that belong to the peer
  49. * represented by this instance.
  50. * @param ssrc audio or video RTP stream SSRC.
  51. * @param resolution new resolution value to be set.
  52. */
  53. PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
  54. {
  55. if(resolution == null && this.ssrc2resolution[ssrc])
  56. {
  57. delete this.ssrc2resolution[ssrc];
  58. }
  59. else if(resolution != null)
  60. this.ssrc2resolution[ssrc] = resolution;
  61. };
  62. /**
  63. * Sets the bit rate for given <tt>ssrc</tt> that blong to the peer
  64. * represented by this instance.
  65. * @param ssrc audio or video RTP stream SSRC.
  66. * @param bitrate new bitrate value to be set.
  67. */
  68. PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
  69. {
  70. this.ssrc2bitrate[ssrc] = bitrate;
  71. };
  72. /**
  73. * Sets new audio level(input or output) for given <tt>ssrc</tt> that identifies
  74. * the stream which belongs to the peer represented by this instance.
  75. * @param ssrc RTP stream SSRC for which current audio level value will be
  76. * updated.
  77. * @param audioLevel the new audio level value to be set. Value is truncated to
  78. * fit the range from 0 to 1.
  79. */
  80. PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
  81. {
  82. // Range limit 0 - 1
  83. this.ssrc2AudioLevel[ssrc] = Math.min(Math.max(audioLevel, 0), 1);
  84. };
  85. /**
  86. * Array with the transport information.
  87. * @type {Array}
  88. */
  89. PeerStats.transport = [];
  90. /**
  91. * <tt>StatsCollector</tt> registers for stats updates of given
  92. * <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
  93. * stats are extracted and put in {@link PeerStats} objects. Once the processing
  94. * is done <tt>audioLevelsUpdateCallback</tt> is called with <tt>this</tt> instance as
  95. * an event source.
  96. *
  97. * @param peerconnection webRTC peer connection object.
  98. * @param interval stats refresh interval given in ms.
  99. * @param {function(StatsCollector)} audioLevelsUpdateCallback the callback called on stats
  100. * update.
  101. * @constructor
  102. */
  103. function StatsCollector(peerconnection, audioLevelsInterval, audioLevelsUpdateCallback, statsInterval, statsUpdateCallback)
  104. {
  105. this.peerconnection = peerconnection;
  106. this.baselineAudioLevelsReport = null;
  107. this.currentAudioLevelsReport = null;
  108. this.currentStatsReport = null;
  109. this.baselineStatsReport = null;
  110. this.audioLevelsIntervalId = null;
  111. // Updates stats interval
  112. this.audioLevelsIntervalMilis = audioLevelsInterval;
  113. this.statsIntervalId = null;
  114. this.statsIntervalMilis = statsInterval;
  115. // Map of jids to PeerStats
  116. this.jid2stats = {};
  117. this.audioLevelsUpdateCallback = audioLevelsUpdateCallback;
  118. this.statsUpdateCallback = statsUpdateCallback;
  119. }
  120. /**
  121. * Stops stats updates.
  122. */
  123. StatsCollector.prototype.stop = function ()
  124. {
  125. if (this.audioLevelsIntervalId)
  126. {
  127. clearInterval(this.audioLevelsIntervalId);
  128. this.audioLevelsIntervalId = null;
  129. clearInterval(this.statsIntervalId);
  130. this.statsIntervalId = null;
  131. }
  132. };
  133. /**
  134. * Callback passed to <tt>getStats</tt> method.
  135. * @param error an error that occurred on <tt>getStats</tt> call.
  136. */
  137. StatsCollector.prototype.errorCallback = function (error)
  138. {
  139. console.error("Get stats error", error);
  140. this.stop();
  141. };
  142. /**
  143. * Starts stats updates.
  144. */
  145. StatsCollector.prototype.start = function ()
  146. {
  147. var self = this;
  148. this.audioLevelsIntervalId = setInterval(
  149. function ()
  150. {
  151. // Interval updates
  152. self.peerconnection.getStats(
  153. function (report)
  154. {
  155. var results = report.result();
  156. //console.error("Got interval report", results);
  157. self.currentAudioLevelsReport = results;
  158. self.processAudioLevelReport();
  159. self.baselineAudioLevelsReport = self.currentAudioLevelsReport;
  160. },
  161. self.errorCallback
  162. );
  163. },
  164. self.audioLevelsIntervalMilis
  165. );
  166. this.statsIntervalId = setInterval(
  167. function () {
  168. // Interval updates
  169. self.peerconnection.getStats(
  170. function (report)
  171. {
  172. var results = report.result();
  173. //console.error("Got interval report", results);
  174. self.currentStatsReport = results;
  175. self.processStatsReport();
  176. self.baselineStatsReport = self.currentStatsReport;
  177. },
  178. self.errorCallback
  179. );
  180. },
  181. self.statsIntervalMilis
  182. );
  183. };
  184. /**
  185. * Stats processing logic.
  186. */
  187. StatsCollector.prototype.processStatsReport = function () {
  188. if (!this.baselineStatsReport) {
  189. return;
  190. }
  191. for (var idx in this.currentStatsReport) {
  192. var now = this.currentStatsReport[idx];
  193. if (now.stat('googAvailableReceiveBandwidth') || now.stat('googAvailableSendBandwidth')) {
  194. PeerStats.bandwidth = {
  195. "download": Math.round((now.stat('googAvailableReceiveBandwidth')) / 1000),
  196. "upload": Math.round((now.stat('googAvailableSendBandwidth')) / 1000)
  197. };
  198. }
  199. if(now.type == 'googCandidatePair')
  200. {
  201. var ip = now.stat('googRemoteAddress');
  202. var type = now.stat("googTransportType");
  203. var localIP = now.stat("googLocalAddress");
  204. var active = now.stat("googActiveConnection");
  205. if(!ip || !type || !localIP || active != "true")
  206. continue;
  207. var addressSaved = false;
  208. for(var i = 0; i < PeerStats.transport.length; i++)
  209. {
  210. if(PeerStats.transport[i].ip == ip && PeerStats.transport[i].type == type &&
  211. PeerStats.transport[i].localip == localIP)
  212. {
  213. addressSaved = true;
  214. }
  215. }
  216. if(addressSaved)
  217. continue;
  218. PeerStats.transport.push({localip: localIP, ip: ip, type: type});
  219. continue;
  220. }
  221. // console.log("bandwidth: " + now.stat('googAvailableReceiveBandwidth') + " - " + now.stat('googAvailableSendBandwidth'));
  222. if (now.type != 'ssrc') {
  223. continue;
  224. }
  225. var before = this.baselineStatsReport[idx];
  226. if (!before) {
  227. console.warn(now.stat('ssrc') + ' not enough data');
  228. continue;
  229. }
  230. var ssrc = now.stat('ssrc');
  231. var jid = ssrc2jid[ssrc];
  232. if (!jid) {
  233. console.warn("No jid for ssrc: " + ssrc);
  234. continue;
  235. }
  236. var jidStats = this.jid2stats[jid];
  237. if (!jidStats) {
  238. jidStats = new PeerStats();
  239. this.jid2stats[jid] = jidStats;
  240. }
  241. var isDownloadStream = true;
  242. var key = 'packetsReceived';
  243. if (!now.stat(key))
  244. {
  245. isDownloadStream = false;
  246. key = 'packetsSent';
  247. if (!now.stat(key))
  248. {
  249. console.error("No packetsReceived nor packetSent stat found");
  250. this.stop();
  251. return;
  252. }
  253. }
  254. var packetsNow = now.stat(key);
  255. if(!packetsNow || packetsNow < 0)
  256. packetsNow = 0;
  257. var packetsBefore = before.stat(key);
  258. if(!packetsBefore || packetsBefore < 0)
  259. packetsBefore = 0;
  260. var packetRate = packetsNow - packetsBefore;
  261. var currentLoss = now.stat('packetsLost');
  262. if(!currentLoss || currentLoss < 0)
  263. currentLoss = 0;
  264. var previousLoss = before.stat('packetsLost');
  265. if(!previousLoss || previousLoss < 0)
  266. previousLoss = 0;
  267. var lossRate = currentLoss - previousLoss;
  268. if(lossRate < 0)
  269. lossRate = 0;
  270. var packetsTotal = (packetRate + lossRate);
  271. jidStats.setSsrcLoss(ssrc, {"packetsTotal": packetsTotal, "packetsLost": lossRate,
  272. "isDownloadStream": isDownloadStream});
  273. var bytesReceived = 0, bytesSent = 0;
  274. if(now.stat("bytesReceived"))
  275. {
  276. bytesReceived = now.stat("bytesReceived") - before.stat("bytesReceived");
  277. }
  278. if(now.stat("bytesSent"))
  279. {
  280. bytesSent = now.stat("bytesSent") - before.stat("bytesSent");
  281. }
  282. if(bytesReceived < 0)
  283. bytesReceived = 0;
  284. if(bytesSent < 0)
  285. bytesSent = 0;
  286. var time = Math.round((now.timestamp - before.timestamp) / 1000);
  287. jidStats.setSsrcBitrate(ssrc, {
  288. "download": Math.round(((bytesReceived * 8) / time) / 1000),
  289. "upload": Math.round(((bytesSent * 8) / time) / 1000)});
  290. var resolution = {height: null, width: null};
  291. if(now.stat("googFrameHeightReceived") && now.stat("googFrameWidthReceived"))
  292. {
  293. resolution.height = now.stat("googFrameHeightReceived");
  294. resolution.width = now.stat("googFrameWidthReceived");
  295. }
  296. else if(now.stat("googFrameHeightSent") && now.stat("googFrameWidthSent"))
  297. {
  298. resolution.height = now.stat("googFrameHeightSent");
  299. resolution.width = now.stat("googFrameWidthSent");
  300. }
  301. if(resolution.height && resolution.width)
  302. {
  303. jidStats.setSsrcResolution(ssrc, resolution);
  304. }
  305. else
  306. {
  307. jidStats.setSsrcResolution(ssrc, null);
  308. }
  309. }
  310. var self = this;
  311. // Jid stats
  312. var totalPackets = {download: 0, upload: 0};
  313. var lostPackets = {download: 0, upload: 0};
  314. var bitrateDownload = 0;
  315. var bitrateUpload = 0;
  316. var resolution = {};
  317. Object.keys(this.jid2stats).forEach(
  318. function (jid)
  319. {
  320. Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
  321. function (ssrc)
  322. {
  323. var type = "upload";
  324. if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
  325. type = "download";
  326. totalPackets[type] += self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
  327. lostPackets[type] += self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
  328. }
  329. );
  330. Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
  331. function (ssrc) {
  332. bitrateDownload += self.jid2stats[jid].ssrc2bitrate[ssrc].download;
  333. bitrateUpload += self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
  334. }
  335. );
  336. resolution[jid] = self.jid2stats[jid].ssrc2resolution;
  337. }
  338. );
  339. PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
  340. PeerStats.packetLoss = {
  341. total:
  342. calculatePacketLoss(lostPackets.download + lostPackets.upload,
  343. totalPackets.download + totalPackets.upload),
  344. download:
  345. calculatePacketLoss(lostPackets.download, totalPackets.download),
  346. upload:
  347. calculatePacketLoss(lostPackets.upload, totalPackets.upload)
  348. };
  349. this.statsUpdateCallback(
  350. {
  351. "bitrate": PeerStats.bitrate,
  352. "packetLoss": PeerStats.packetLoss,
  353. "bandwidth": PeerStats.bandwidth,
  354. "resolution": resolution,
  355. "transport": PeerStats.transport
  356. });
  357. PeerStats.transport = [];
  358. }
  359. /**
  360. * Stats processing logic.
  361. */
  362. StatsCollector.prototype.processAudioLevelReport = function ()
  363. {
  364. if (!this.baselineAudioLevelsReport)
  365. {
  366. return;
  367. }
  368. for (var idx in this.currentAudioLevelsReport)
  369. {
  370. var now = this.currentAudioLevelsReport[idx];
  371. if (now.type != 'ssrc')
  372. {
  373. continue;
  374. }
  375. var before = this.baselineAudioLevelsReport[idx];
  376. if (!before)
  377. {
  378. console.warn(now.stat('ssrc') + ' not enough data');
  379. continue;
  380. }
  381. var ssrc = now.stat('ssrc');
  382. var jid = ssrc2jid[ssrc];
  383. if (!jid)
  384. {
  385. console.warn("No jid for ssrc: " + ssrc);
  386. continue;
  387. }
  388. var jidStats = this.jid2stats[jid];
  389. if (!jidStats)
  390. {
  391. jidStats = new PeerStats();
  392. this.jid2stats[jid] = jidStats;
  393. }
  394. // Audio level
  395. var audioLevel = now.stat('audioInputLevel');
  396. if (!audioLevel)
  397. audioLevel = now.stat('audioOutputLevel');
  398. if (audioLevel)
  399. {
  400. // TODO: can't find specs about what this value really is,
  401. // but it seems to vary between 0 and around 32k.
  402. audioLevel = audioLevel / 32767;
  403. jidStats.setSsrcAudioLevel(ssrc, audioLevel);
  404. if(jid != connection.emuc.myroomjid)
  405. this.audioLevelsUpdateCallback(jid, audioLevel);
  406. }
  407. }
  408. };