| OLD | NEW |
| 1 /** | 1 /** |
| 2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 * Use of this source code is governed by a BSD-style license that can be | 3 * Use of this source code is governed by a BSD-style license that can be |
| 4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
| 5 */ | 5 */ |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * The gStartedAt when the capturing begins. Used for timeout adjustments. |
| 9 * @private |
| 10 */ |
| 11 var gStartedAt = 0; |
| 12 |
| 13 /** |
| 8 * The duration of the all frame capture in milliseconds. | 14 * The duration of the all frame capture in milliseconds. |
| 9 * @private | 15 * @private |
| 10 */ | 16 */ |
| 11 var gCaptureDuration = 0; | 17 var gCaptureDuration = 0; |
| 12 | 18 |
| 13 /** | 19 /** |
| 14 * The recorded video encoded in Base64. | 20 * The time interval at which the video is sampled. |
| 15 * @private | 21 * @private |
| 16 */ | 22 */ |
| 17 var gVideoBase64 = ''; | 23 var gFrameCaptureInterval = 0; |
| 18 | 24 |
| 19 /** | 25 /** |
| 20 * Chunks of the video recorded by MediaRecorded as they become available. | 26 * The global array of frames. Frames are pushed, i.e. this should be treated as |
| 27 * a queue and we should read from the start. |
| 21 * @private | 28 * @private |
| 22 */ | 29 */ |
| 23 var gChunks = []; | 30 var gFrames = []; |
| 31 |
| 32 /** |
| 33 * We need to skip the first two frames due to timing issues. |
| 34 * @private |
| 35 */ |
| 36 var gHasThrownAwayFirstTwoFrames = false; |
| 24 | 37 |
| 25 /** | 38 /** |
| 26 * A string to be returned to the test about the current status of capture. | 39 * A string to be returned to the test about the current status of capture. |
| 27 */ | 40 */ |
| 28 var gCapturingStatus = 'capturing-not-started'; | 41 var gCapturingStatus = 'capturing-not-started'; |
| 29 | 42 |
| 30 /** | 43 /** |
| 31 * Starts the frame capturing. | 44 * Starts the frame capturing. |
| 32 * | 45 * |
| 33 * @param {!Object} The video tag from which the height and width parameters are | 46 * @param {!Object} The video tag from which the height and width parameters are |
| 34 to be extracted. | 47 to be extracted. |
| 48 * @param {Number} The frame rate at which we would like to capture frames. |
| 35 * @param {Number} The duration of the frame capture in seconds. | 49 * @param {Number} The duration of the frame capture in seconds. |
| 36 */ | 50 */ |
| 37 function startFrameCapture(videoTag, duration) { | 51 function startFrameCapture(videoTag, frameRate, duration) { |
| 38 debug('inputElement stream: ' + getStreamFromElement_(videoTag)); | 52 gFrameCaptureInterval = 1000 / frameRate; |
| 39 var mediaRecorder = new MediaRecorder(getStreamFromElement_(videoTag)); | 53 gCaptureDuration = 1000 * duration; |
| 40 mediaRecorder.ondataavailable = function(recording) { | 54 inputElement = document.getElementById("local-view"); |
| 41 gChunks.push(recording.data); | 55 var width = inputElement.videoWidth; |
| 56 var height = inputElement.videoHeight; |
| 57 |
| 58 // The WebRTC code is free to start in VGA, so make sure that the output video |
| 59 // tag scales up to whatever the input size is (otherwise the video quality |
| 60 // comparison will go poorly. |
| 61 videoTag.width = width; |
| 62 videoTag.height = height; |
| 63 |
| 64 if (width == 0 || height == 0) { |
| 65 // Video must be playing at this point since this function is invoked from |
| 66 // onplay on the <video> tag. See http://crbug.com/625943. |
| 67 gCapturingStatus = 'failed-video-was-0x0-after-onplay' |
| 68 return; |
| 42 } | 69 } |
| 43 mediaRecorder.onstop = function() { | 70 |
| 44 var videoBlob = new Blob(gChunks, {type: "video/webm"}); | 71 console.log('Received width is: ' + width + ', received height is: ' + height |
| 45 gChunks = []; | 72 + ', capture interval is: ' + gFrameCaptureInterval + |
| 46 var reader = new FileReader(); | 73 ', duration is: ' + gCaptureDuration); |
| 47 reader.onloadend = function() { | |
| 48 gVideoBase64 = reader.result.substr(reader.result.indexOf(',') + 1); | |
| 49 gCapturingStatus = 'done-capturing'; | |
| 50 debug('done-capturing'); | |
| 51 } | |
| 52 reader.readAsDataURL(videoBlob); | |
| 53 } | |
| 54 mediaRecorder.start(); | |
| 55 gCaptureDuration = 1000 * duration; | |
| 56 setTimeout(function() { mediaRecorder.stop(); }, gCaptureDuration); | |
| 57 gCapturingStatus = 'still-capturing'; | 74 gCapturingStatus = 'still-capturing'; |
| 75 |
| 76 var remoteCanvas = document.createElement('canvas'); |
| 77 remoteCanvas.width = width; |
| 78 remoteCanvas.height = height; |
| 79 document.body.appendChild(remoteCanvas); |
| 80 |
| 81 gStartedAt = new Date().getTime(); |
| 82 gFrames = []; |
| 83 setTimeout(function() { shoot_(videoTag, remoteCanvas, width, height); }, |
| 84 gFrameCaptureInterval); |
| 58 } | 85 } |
| 59 | 86 |
| 60 /** | 87 /** |
| 61 * Returns the video recorded by RecordMedia encoded in Base64. | |
| 62 */ | |
| 63 function getRecordedVideoAsBase64() { | |
| 64 silentReturnToTest(gVideoBase64); | |
| 65 } | |
| 66 | |
| 67 /** | |
| 68 * Queries if we're done with the frame capturing yet. | 88 * Queries if we're done with the frame capturing yet. |
| 69 */ | 89 */ |
| 70 function doneFrameCapturing() { | 90 function doneFrameCapturing() { |
| 71 returnToTest(gCapturingStatus); | 91 returnToTest(gCapturingStatus); |
| 72 } | 92 } |
| 73 | 93 |
| 74 /** | 94 /** |
| 75 * Returns the stream from the input element to be attached to MediaRecorder. | 95 * Retrieves the number of captured frames. |
| 96 */ |
| 97 function getTotalNumberCapturedFrames() { |
| 98 returnToTest(gFrames.length.toString()); |
| 99 } |
| 100 |
| 101 /** |
| 102 * Retrieves one captured frame in ARGB format as a base64-encoded string. |
| 103 * |
| 104 * Also updates the page's progress bar. |
| 105 * |
| 106 * @param frameIndex A frame index in the range 0 to total-1 where total is |
| 107 * given by getTotalNumberCapturedFrames. |
| 108 */ |
| 109 function getOneCapturedFrame(frameIndex) { |
| 110 var codedFrame = convertArrayBufferToBase64String_(gFrames[frameIndex]); |
| 111 updateProgressBar_(frameIndex); |
| 112 silentReturnToTest(codedFrame); |
| 113 } |
| 114 |
| 115 /** |
| 116 * @private |
| 117 * |
| 118 * @param {ArrayBuffer} buffer An array buffer to convert to a base 64 string. |
| 119 * @return {String} A base 64 string. |
| 120 */ |
| 121 function convertArrayBufferToBase64String_(buffer) { |
| 122 var binary = ''; |
| 123 var bytes = new Uint8Array(buffer); |
| 124 for (var i = 0; i < bytes.byteLength; i++) { |
| 125 binary += String.fromCharCode(bytes[i]); |
| 126 } |
| 127 return window.btoa(binary); |
| 128 } |
| 129 |
| 130 /** |
| 131 * The function which is called at the end of every gFrameCaptureInterval. Gets |
| 132 * the current frame from the video and extracts the data from it. Then it saves |
| 133 * it in the frames array and adjusts the capture interval (timers in JavaScript |
| 134 * aren't precise). |
| 135 * |
| 136 * @private |
| 137 * |
| 138 * @param {!Object} The video whose frames are to be captured. |
| 139 * @param {Canvas} The canvas on which the image will be captured. |
| 140 * @param {Number} The width of the video/canvas area to be captured. |
| 141 * @param {Number} The height of the video area to be captured. |
| 142 */ |
| 143 function shoot_(video, canvas, width, height) { |
| 144 // The first two captured frames have big difference between the ideal time |
| 145 // interval between two frames and the real one. As a consequence this affects |
| 146 // enormously the interval adjustment for subsequent frames. That's why we |
| 147 // have to reset the time after the first two frames and get rid of these two |
| 148 // frames. |
| 149 if (gFrames.length == 1 && !gHasThrownAwayFirstTwoFrames) { |
| 150 gStartedAt = new Date().getTime(); |
| 151 gHasThrownAwayFirstTwoFrames = true; |
| 152 gFrames = []; |
| 153 } |
| 154 |
| 155 // We capture the whole video frame. |
| 156 var img = captureFrame_(video, canvas.getContext('2d'), width, height); |
| 157 gFrames.push(img.data.buffer); |
| 158 |
| 159 // Adjust the timer and try to account for timer incorrectness. |
| 160 var currentTime = new Date().getTime(); |
| 161 var idealTime = gFrames.length * gFrameCaptureInterval; |
| 162 var realTimeElapsed = currentTime - gStartedAt; |
| 163 var diff = realTimeElapsed - idealTime; |
| 164 |
| 165 if (realTimeElapsed < gCaptureDuration) { |
| 166 // If duration isn't over shoot_ again. |
| 167 setTimeout(function() { shoot_(video, canvas, width, height); }, |
| 168 gFrameCaptureInterval - diff); |
| 169 } else { |
| 170 // Done capturing! |
| 171 gCapturingStatus = 'done-capturing'; |
| 172 prepareProgressBar_(); |
| 173 } |
| 174 } |
| 175 |
| 176 /** |
| 76 * @private | 177 * @private |
| 77 */ | 178 */ |
| 78 function getStreamFromElement_(element) { | 179 function captureFrame_(video, context, width, height) { |
| 79 if (typeof element.srcObject !== 'undefined') { | 180 context.drawImage(video, 0, 0, width, height); |
| 80 return element.srcObject; | 181 return context.getImageData(0, 0, width, height); |
| 81 } else if (typeof element.mozSrcObject !== 'undefined') { | |
| 82 return element.mozSrcObject; | |
| 83 } else if (typeof element.src !== 'undefined') { | |
| 84 return element.src; | |
| 85 } else { | |
| 86 console.log('Error attaching stream to element.'); | |
| 87 } | |
| 88 } | 182 } |
| 183 |
| 184 /** |
| 185 * @private |
| 186 */ |
| 187 function prepareProgressBar_() { |
| 188 document.body.innerHTML = |
| 189 '<html><body>' + |
| 190 '<p id="progressBar" style="position: absolute; top: 50%; left: 40%;">' + |
| 191 'Preparing to send frames.</p>' + |
| 192 '</body></html>'; |
| 193 } |
| 194 |
| 195 /** |
| 196 * @private |
| 197 */ |
| 198 function updateProgressBar_(currentFrame) { |
| 199 progressBar.innerHTML = |
| 200 'Transferring captured frames: ' + '(' + currentFrame + '/' + |
| 201 gFrames.length + ')'; |
| 202 } |
| OLD | NEW |