Home | History | Annotate | Download | only in video_WebRtcPeerConnectionWithCamera
      1 <!DOCTYPE html>
      2 <html>
      3 <head><title>Loopback test</title></head>
      4 <body>
      5   <video id="localVideo" width="1280" height="720" autoplay muted></video>
      6   <video id="remoteVideo" width="1280" height="720" autoplay muted></video>
      7 <script src="ssim.js"></script>
      8 <script src="blackframe.js"></script>
      9 <script>
     10 
     11 
     12 var results = {};
     13 var testProgress = 0;
     14 
     15 // Starts the test.
     16 function testCamera(resolution) {
     17   var test = new CameraTest(resolution);
     18   test.run();
     19 }
     20 
     21 // Returns the results to caller.
     22 function getResults() {
     23   return results;
     24 }
     25 
     26 function setResults(stats) {
     27   results = stats;
     28 }
     29 
     30 // Calculates averages of array values.
     31 function average(array) {
     32   var count = array.length;
     33   var total = 0;
     34   for (var i = 0; i < count; i++) {
     35     total += parseInt(array[i]);
     36   }
     37   return Math.floor(total / count);
     38 }
     39 
     40 // Actual test object.
     41 function CameraTest(resolutionArray) {
     42   this.resolution = resolutionArray;
     43   this.localStream = null;
     44   this.remoteStream = null;
     45   this.results = {cameraType: '', cameraErrors: [], peerConnectionStats: [],
     46       frameStats: {numBlackFrames: 0, numFrozenFrames:0, numFrames: 0}};
     47 
     48   this.inFps = [];
     49   this.outFps = [];
     50   // Variables associated with nearly-frozen frames detection.
     51   this.previousFrame = [];
     52   this.identicalFrameSsimThreshold = 0.985;
     53   this.frameComparator = new Ssim();
     54 
     55   this.remoteVideo = document.getElementById("remoteVideo");
     56   this.localVideo = document.getElementById("localVideo");
     57   this.localVideo.width = this.resolution[0].toString();
     58   this.localVideo.height = this.resolution[1].toString();
     59   this.remoteVideo.width = this.resolution[0].toString();
     60   this.remoteVideo.height = this.resolution[1].toString();
     61 }
     62 
     63 function resolutionMatchesIndependentOfRotation(aWidth, aHeight,
     64                                                 bWidth, bHeight) {
     65   return (aWidth === bWidth && aHeight === bHeight) ||
     66          (aWidth === bHeight && aHeight === bWidth);
     67 }
     68 
     69 CameraTest.prototype = {
     70   collectAndAnalyzeStats: function() {
     71     if (!resolutionMatchesIndependentOfRotation(this.localVideo.width,
     72         this.localVideo.height, this.resolution[0], this.resolution[1])) {
     73       this.reportError('resolution', 'Got resolution ' + this.resolution[0] +
     74           + 'x' + this.resolution[1] + ', expected resolution' +
     75           this.localVideo.width + 'x' + this.localVideo.height +
     76           ' or rotated version thereof');
     77     }
     78     this.gatherStats(this.localPeerConnection, 100, 20000,
     79         this.reportTestDone.bind(this));
     80   },
     81 
     82   setup: function() {
     83     this.canvas = document.createElement('canvas');
     84     this.canvas.width = localVideo.width;
     85     this.canvas.height = localVideo.height;
     86     this.context = this.canvas.getContext('2d');
     87     this.remoteVideo.onloadedmetadata = this.collectAndAnalyzeStats.bind(this);
     88     this.localVideo.addEventListener('play',
     89         this.startCheckingVideoFrames.bind(this), false);
     90   },
     91 
     92   startCheckingVideoFrames: function() {
     93     this.videoFrameChecker = setInterval(this.checkVideoFrame.bind(this), 20);
     94   },
     95 
     96   run: function() {
     97     this.setup();
     98     this.triggerGetUserMedia(this.resolution);
     99   },
    100 
    101   triggerGetUserMedia: function(resolution) {
    102     var constraints = {
    103       audio: false,
    104       video: {
    105         mandatory: {
    106           minWidth:  resolution[0],
    107           minHeight: resolution[1],
    108           maxWidth:  resolution[0],
    109           maxHeight: resolution[1]
    110         }
    111       }
    112     };
    113     try {
    114       this.doGetUserMedia(constraints, this.gotLocalStream.bind(this),
    115           this.onGetUserMediaError.bind(this));
    116     } catch (exception) {
    117       console.log('Unexpected exception: ', exception);
    118       this.reportError('gUM', 'doGetUserMedia failed: ' + exception);
    119     }
    120   },
    121 
    122   reportError: function(errorType, message) {
    123     this.results.cameraErrors.push([errorType, message]);
    124     console.log(message);
    125   },
    126 
    127   doGetUserMedia: function(constraints, onSuccess, onFail) {
    128     navigator.getUserMedia = navigator.getUserMedia ||
    129       navigator.webkitGetUserMedia;
    130     navigator.getUserMedia(constraints, onSuccess, onFail);
    131   },
    132 
    133   gotLocalStream: function(stream) {
    134     this.localStream = stream;
    135     var servers = null;
    136 
    137     this.localPeerConnection = new webkitRTCPeerConnection(servers);
    138     this.localPeerConnection.onicecandidate = this.gotLocalIceCandidate.bind(
    139         this);
    140 
    141     this.remotePeerConnection = new webkitRTCPeerConnection(servers);
    142     this.remotePeerConnection.onicecandidate = this.gotRemoteIceCandidate.bind(
    143         this);
    144     this.remotePeerConnection.onaddstream = this.gotRemoteStream.bind(this);
    145 
    146     this.localPeerConnection.addStream(this.localStream);
    147     this.localPeerConnection.createOffer(this.gotLocalDescription.bind(this));
    148     this.localVideo.src = URL.createObjectURL(stream);
    149 
    150     this.results.cameraType = stream.getVideoTracks()[0].label;
    151   },
    152 
    153   onGetUserMediaError: function(stream) {
    154     this.reportError('gUM', 'gUM call failed');
    155   },
    156 
    157   gatherStats: function(peerConnection, interval, durationMs, callback) {
    158     var startTime = new Date();
    159     var pollFunction = setInterval(gatherOneReport.bind(this), interval);
    160     function gatherOneReport() {
    161       var elapsed = new Date() - startTime;
    162       if (elapsed > durationMs) {
    163         console.log('Done gathering stats.');
    164         clearInterval(pollFunction);
    165         callback();
    166         return;
    167       }
    168       peerConnection.getStats(this.gotStats.bind(this));
    169     }
    170   },
    171 
    172   getStatFromReport: function(data, name) {
    173     if (data.type = 'ssrc' && data.stat(name)) {
    174       return data.stat(name);
    175     } else {
    176       return null;
    177     }
    178   },
    179 
    180   gotStats: function(response) {
    181     var reports = response.result();
    182     for (var i = 0; i < reports.length; ++i) {
    183       var report = reports[i];
    184       var incomingFps = this.getStatFromReport(report, 'googFrameRateInput');
    185       if (incomingFps == null) {
    186         // Skip on null.
    187         continue;
    188       }
    189       var outgoingFps = this.getStatFromReport(report, 'googFrameRateSent');
    190       // Save rates for later processing.
    191       this.inFps.push(incomingFps)
    192       this.outFps.push(outgoingFps);
    193     }
    194   },
    195 
    196   reportTestDone: function() {
    197     this.processStats();
    198 
    199     clearInterval(this.videoFrameChecker);
    200 
    201     setResults(this.results);
    202 
    203     testProgress = 1;
    204   },
    205 
    206   processStats: function() {
    207     if (this.inFps != [] && this.outFps != []) {
    208       var minInFps = Math.min.apply(null, this.inFps);
    209       var maxInFps = Math.max.apply(null, this.inFps);
    210       var averageInFps = average(this.inFps);
    211       var minOutFps = Math.min.apply(null, this.outFps);
    212       var maxOutFps = Math.max.apply(null, this.outFps);
    213       var averageOutFps = average(this.outFps);
    214       this.results.peerConnectionStats = [minInFps, maxInFps, averageInFps,
    215           minOutFps, maxOutFps, averageOutFps];
    216     }
    217   },
    218 
    219   checkVideoFrame: function() {
    220     this.context.drawImage(this.localVideo, 0, 0, this.canvas.width,
    221       this.canvas.height);
    222     var imageData = this.context.getImageData(0, 0, this.canvas.width,
    223         this.canvas.height);
    224 
    225       if (isBlackFrame(imageData.data, imageData.data.length)) {
    226         this.results.frameStats.numBlackFrames++;
    227       }
    228 
    229       if (this.frameComparator.calculate(this.previousFrame, imageData.data) >
    230         this.identicalFrameSsimThreshold) {
    231         this.results.frameStats.numFrozenFrames++;
    232       }
    233 
    234       this.previousFrame = imageData.data;
    235       this.results.frameStats.numFrames++;
    236   },
    237 
    238   isBlackFrame: function(data, length) {
    239     var accumulatedLuma = 0;
    240     for (var i = 4; i < length; i += 4) {
    241       // Use Luma as in Rec. 709: Y709 = 0.21R + 0.72G + 0.07B;
    242       accumulatedLuma += (0.21 * data[i] +  0.72 * data[i + 1]
    243           + 0.07 * data[i + 2]);
    244       // Early termination if the average Luma so far is bright enough.
    245       if (accumulatedLuma > (this.nonBlackPixelLumaThreshold * i / 4)) {
    246         return false;
    247       }
    248     }
    249     return true;
    250   },
    251 
    252   gotRemoteStream: function(event) {
    253     this.remoteVideo.src = URL.createObjectURL(event.stream);
    254   },
    255 
    256   gotLocalDescription: function(description) {
    257     this.localPeerConnection.setLocalDescription(description);
    258     this.remotePeerConnection.setRemoteDescription(description);
    259     this.remotePeerConnection.createAnswer(this.gotRemoteDescription.bind(
    260         this));
    261   },
    262 
    263   gotRemoteDescription: function(description) {
    264     this.remotePeerConnection.setLocalDescription(description);
    265     this.localPeerConnection.setRemoteDescription(description);
    266   },
    267 
    268   gotLocalIceCandidate: function(event) {
    269     if (event.candidate)
    270       this.remotePeerConnection.addIceCandidate(
    271         new RTCIceCandidate(event.candidate));
    272   },
    273 
    274   gotRemoteIceCandidate: function(event) {
    275     if (event.candidate)
    276       this.localPeerConnection.addIceCandidate(
    277         new RTCIceCandidate(event.candidate));
    278   },
    279 }
    280 
    281 window.onerror = function (message, filename, lineno, colno, error) {
    282   console.log("Something went wrong, here is the stack trace --> %s",
    283     error.stack);
    284 };
    285 </script>
    286 </body>
    287 </html>
    288