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

rtp_stats.js 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. /* global ssrc2jid */
  2. /**
  3. * Function object which once created can be used to calculate moving average of
  4. * given period. Example for SMA3:</br>
  5. * var sma3 = new SimpleMovingAverager(3);
  6. * while(true) // some update loop
  7. * {
  8. * var currentSma3Value = sma3(nextInputValue);
  9. * }
  10. *
  11. * @param period moving average period that will be used by created instance.
  12. * @returns {Function} SMA calculator function of given <tt>period</tt>.
  13. * @constructor
  14. */
  15. function SimpleMovingAverager(period)
  16. {
  17. var nums = [];
  18. return function (num)
  19. {
  20. nums.push(num);
  21. if (nums.length > period)
  22. nums.splice(0, 1);
  23. var sum = 0;
  24. for (var i in nums)
  25. sum += nums[i];
  26. var n = period;
  27. if (nums.length < period)
  28. n = nums.length;
  29. return (sum / n);
  30. };
  31. }
  32. /**
  33. * Peer statistics data holder.
  34. * @constructor
  35. */
  36. function PeerStats()
  37. {
  38. this.ssrc2Loss = {};
  39. this.ssrc2AudioLevel = {};
  40. }
  41. /**
  42. * Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
  43. * represented by this instance.
  44. * @param ssrc audio or video RTP stream SSRC.
  45. * @param lossRate new packet loss rate value to be set.
  46. */
  47. PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
  48. {
  49. this.ssrc2Loss[ssrc] = lossRate;
  50. };
  51. /**
  52. * Sets new audio level(input or output) for given <tt>ssrc</tt> that identifies
  53. * the stream which belongs to the peer represented by this instance.
  54. * @param ssrc RTP stream SSRC for which current audio level value will be
  55. * updated.
  56. * @param audioLevel the new audio level value to be set. Value is truncated to
  57. * fit the range from 0 to 1.
  58. */
  59. PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
  60. {
  61. // Range limit 0 - 1
  62. this.ssrc2AudioLevel[ssrc] = Math.min(Math.max(audioLevel, 0), 1);
  63. };
  64. /**
  65. * Calculates average packet loss for all streams that belong to the peer
  66. * represented by this instance.
  67. * @returns {number} average packet loss for all streams that belong to the peer
  68. * represented by this instance.
  69. */
  70. PeerStats.prototype.getAvgLoss = function ()
  71. {
  72. var self = this;
  73. var avg = 0;
  74. var count = Object.keys(this.ssrc2Loss).length;
  75. Object.keys(this.ssrc2Loss).forEach(
  76. function (ssrc)
  77. {
  78. avg += self.ssrc2Loss[ssrc];
  79. }
  80. );
  81. return count > 0 ? avg / count : 0;
  82. };
  83. /**
  84. * <tt>StatsCollector</tt> registers for stats updates of given
  85. * <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
  86. * stats are extracted and put in {@link PeerStats} objects. Once the processing
  87. * is done <tt>updateCallback</tt> is called with <tt>this</tt> instance as
  88. * an event source.
  89. *
  90. * @param peerconnection webRTC peer connection object.
  91. * @param interval stats refresh interval given in ms.
  92. * @param {function(StatsCollector)} updateCallback the callback called on stats
  93. * update.
  94. * @constructor
  95. */
  96. function StatsCollector(peerconnection, interval, updateCallback)
  97. {
  98. this.peerconnection = peerconnection;
  99. this.baselineReport = null;
  100. this.currentReport = null;
  101. this.intervalId = null;
  102. // Updates stats interval
  103. this.intervalMilis = interval;
  104. // Use SMA 3 to average packet loss changes over time
  105. this.sma3 = new SimpleMovingAverager(3);
  106. // Map of jids to PeerStats
  107. this.jid2stats = {};
  108. this.updateCallback = updateCallback;
  109. }
  110. /**
  111. * Stops stats updates.
  112. */
  113. StatsCollector.prototype.stop = function ()
  114. {
  115. if (this.intervalId)
  116. {
  117. clearInterval(this.intervalId);
  118. this.intervalId = null;
  119. }
  120. };
  121. /**
  122. * Callback passed to <tt>getStats</tt> method.
  123. * @param error an error that occurred on <tt>getStats</tt> call.
  124. */
  125. StatsCollector.prototype.errorCallback = function (error)
  126. {
  127. console.error("Get stats error", error);
  128. this.stop();
  129. };
  130. /**
  131. * Starts stats updates.
  132. */
  133. StatsCollector.prototype.start = function ()
  134. {
  135. var self = this;
  136. this.intervalId = setInterval(
  137. function ()
  138. {
  139. // Interval updates
  140. self.peerconnection.getStats(
  141. function (report)
  142. {
  143. var results = report.result();
  144. //console.error("Got interval report", results);
  145. self.currentReport = results;
  146. self.processReport();
  147. self.baselineReport = self.currentReport;
  148. },
  149. self.errorCallback
  150. );
  151. },
  152. self.intervalMilis
  153. );
  154. };
  155. /**
  156. * Stats processing logic.
  157. */
  158. StatsCollector.prototype.processReport = function ()
  159. {
  160. if (!this.baselineReport)
  161. {
  162. return;
  163. }
  164. for (var idx in this.currentReport)
  165. {
  166. var now = this.currentReport[idx];
  167. if (now.type != 'ssrc')
  168. {
  169. continue;
  170. }
  171. var before = this.baselineReport[idx];
  172. if (!before)
  173. {
  174. console.warn(now.stat('ssrc') + ' not enough data');
  175. continue;
  176. }
  177. var ssrc = now.stat('ssrc');
  178. var jid = ssrc2jid[ssrc];
  179. if (!jid)
  180. {
  181. console.warn("No jid for ssrc: " + ssrc);
  182. continue;
  183. }
  184. var jidStats = this.jid2stats[jid];
  185. if (!jidStats)
  186. {
  187. jidStats = new PeerStats();
  188. this.jid2stats[jid] = jidStats;
  189. }
  190. // Audio level
  191. var audioLevel = now.stat('audioInputLevel');
  192. if (!audioLevel)
  193. audioLevel = now.stat('audioOutputLevel');
  194. if (audioLevel)
  195. {
  196. // TODO: can't find specs about what this value really is,
  197. // but it seems to vary between 0 and around 32k.
  198. audioLevel = audioLevel / 32767;
  199. jidStats.setSsrcAudioLevel(ssrc, audioLevel);
  200. if(jid != connection.emuc.myroomjid)
  201. this.updateCallback(jid, audioLevel);
  202. }
  203. var key = 'packetsReceived';
  204. if (!now.stat(key))
  205. {
  206. key = 'packetsSent';
  207. if (!now.stat(key))
  208. {
  209. console.error("No packetsReceived nor packetSent stat found");
  210. this.stop();
  211. return;
  212. }
  213. }
  214. var packetsNow = now.stat(key);
  215. var packetsBefore = before.stat(key);
  216. var packetRate = packetsNow - packetsBefore;
  217. var currentLoss = now.stat('packetsLost');
  218. var previousLoss = before.stat('packetsLost');
  219. var lossRate = currentLoss - previousLoss;
  220. var packetsTotal = (packetRate + lossRate);
  221. var lossPercent;
  222. if (packetsTotal > 0)
  223. lossPercent = lossRate / packetsTotal;
  224. else
  225. lossPercent = 0;
  226. //console.info(jid + " ssrc: " + ssrc + " " + key + ": " + packetsNow);
  227. jidStats.setSsrcLoss(ssrc, lossPercent);
  228. }
  229. var self = this;
  230. // Jid stats
  231. var allPeersAvg = 0;
  232. var jids = Object.keys(this.jid2stats);
  233. jids.forEach(
  234. function (jid)
  235. {
  236. var peerAvg = self.jid2stats[jid].getAvgLoss(
  237. function (avg)
  238. {
  239. //console.info(jid + " stats: " + (avg * 100) + " %");
  240. allPeersAvg += avg;
  241. }
  242. );
  243. }
  244. );
  245. if (jids.length > 1)
  246. {
  247. // Our streams loss is reported as 0 always, so -1 to length
  248. allPeersAvg = allPeersAvg / (jids.length - 1);
  249. /**
  250. * Calculates number of connection quality bars from 4(hi) to 0(lo).
  251. */
  252. var outputAvg = self.sma3(allPeersAvg);
  253. // Linear from 4(0%) to 0(25%).
  254. var quality = Math.round(4 - outputAvg * 16);
  255. quality = Math.max(quality, 0); // lower limit 0
  256. quality = Math.min(quality, 4); // upper limit 4
  257. // TODO: quality can be used to indicate connection quality using 4 step
  258. // bar indicator
  259. //console.info("Loss SMA3: " + outputAvg + " Q: " + quality);
  260. }
  261. };