webgl-webcodecs-video-frame.html (6114B)
1 <!-- 2 Copyright (c) 2021 The Khronos Group Inc. 3 Use of this source code is governed by an MIT-style license that can be 4 found in the LICENSE.txt file. 5 --> 6 7 <!DOCTYPE html> 8 <html> 9 10 <head> 11 <meta charset="UTF-8"> 12 <link rel="stylesheet" href="../../resources/js-test-style.css" /> 13 <script src="../../js/js-test-pre.js"></script> 14 <script src="../../js/webgl-test-utils.js"></script> 15 <script src="../../js/tests/out-of-bounds-test.js"></script> 16 <script src="../../../../extensions/proposals/WEBGL_webcodecs_video_frame/webgl_webcodecs_video_frame.js"></script> 17 <style> 18 canvas { 19 padding: 10px; 20 background: gold; 21 } 22 23 button { 24 background-color: #555555; 25 border: none; 26 color: white; 27 padding: 15px 32px; 28 width: 150px; 29 text-align: center; 30 display: block; 31 font-size: 16px; 32 } 33 </style> 34 </head> 35 36 <body> 37 <canvas id="src" width="640" height="480"></canvas> 38 <canvas id="dst" width="640" height="480"></canvas> 39 <p id="info"></p> 40 <div id="description"></div> 41 <div id="console"></div> 42 <script> 43 "use strict"; 44 description("Test of importing Videoframe from Webcodecs to Webgl"); 45 46 const kIsRunningTest = true; 47 const kMaxFrame = 10; 48 const kTestPixel = [255, 128, 0, 255]; 49 // Sum of pixel difference of R/G/B channel. Use to decide whether a 50 // pixel is matched with another. 51 const codec_string = "vp09.00.51.08.00"; 52 53 let wtu = WebGLTestUtils; 54 let cnv = document.getElementById("src"); 55 let src_width = cnv.width; 56 let src_height = cnv.height; 57 let src_color = "rgba(" + kTestPixel[0].toString() + "," + kTestPixel[1].toString() + "," 58 + kTestPixel[2].toString() + "," + kTestPixel[3].toString() + ")"; 59 let frame_counter = 0; 60 let pixelCompareTolerance = 5; 61 62 function getQueryVariable(variable) { 63 var query = window.location.search.substring(1); 64 var vars = query.split("&"); 65 for (var i = 0; i < vars.length; i++) { 66 var pair = vars[i].split("="); 67 if (pair[0] == variable) { return pair[1]; } 68 } 69 return false; 70 } 71 72 let th = parseInt(getQueryVariable('threshold')); 73 if (!isNaN(th)) 74 pixelCompareTolerance = th; 75 76 async function startDrawing() { 77 let cnv = document.getElementById("src"); 78 var ctx = cnv.getContext('2d', { alpha: false }); 79 80 ctx.fillStyle = src_color; 81 let drawOneFrame = function (time) { 82 ctx.fillStyle = src_color; 83 ctx.fillRect(0, 0, src_width, src_height); 84 window.requestAnimationFrame(drawOneFrame); 85 } 86 window.requestAnimationFrame(drawOneFrame); 87 } 88 89 function captureAndEncode(processChunk) { 90 let cnv = document.getElementById("src"); 91 let fps = 60; 92 let pending_outputs = 0; 93 let stream = cnv.captureStream(fps); 94 let processor = new MediaStreamTrackProcessor(stream.getVideoTracks()[0]); 95 96 const init = { 97 output: (chunk) => { 98 testPassed("Encode frame successfully."); 99 pending_outputs--; 100 processChunk(chunk); 101 }, 102 error: (e) => { 103 testFailed("Failed to encode frame."); 104 finishTest(); 105 vtr.stop(); 106 } 107 }; 108 109 const config = { 110 codec: codec_string, 111 width: cnv.width, 112 height: cnv.height, 113 bitrate: 10e6, 114 framerate: fps, 115 }; 116 117 let encoder = new VideoEncoder(init); 118 encoder.configure(config); 119 120 const frame_reader = processor.readable.getReader(); 121 frame_reader.read().then(function processFrame({done, value}) { 122 if (done) 123 return; 124 125 if (pending_outputs > 30) { 126 console.log("drop this frame"); 127 // Too many frames in flight, encoder is overwhelmed 128 // let's drop this frame. 129 value.close(); 130 frame_reader.read().then(processFrame); 131 return; 132 } 133 134 if(frame_counter == kMaxFrame) { 135 frame_reader.releaseLock(); 136 processor.readable.cancel(); 137 value.close(); 138 return; 139 } 140 141 frame_counter++; 142 pending_outputs++; 143 const insert_keyframe = (frame_counter % 150) == 0; 144 encoder.encode(value, { keyFrame: insert_keyframe }); 145 146 frame_reader.read().then(processFrame); 147 }); 148 } 149 150 function startDecodingAndRendering(cnv, handleFrame) { 151 const init = { 152 output: handleFrame, 153 error: (e) => { 154 testFailed("Failed to decode frame."); 155 finishTest(); 156 } 157 }; 158 159 const config = { 160 codec: codec_string, 161 codedWidth: cnv.width, 162 codedHeight: cnv.height, 163 acceleration: "deny", 164 165 }; 166 167 let decoder = new VideoDecoder(init); 168 decoder.configure(config); 169 return decoder; 170 } 171 172 function isFramePixelMatched(gl, th_per_pixel = pixelCompareTolerance) { 173 WebGLTestUtils.checkCanvasRect(gl, 0, 0, src_width, src_width, kTestPixel, "should be orange", pixelCompareTolerance) 174 } 175 176 function main() { 177 if (!("VideoEncoder" in window)) { 178 testPassed("WebCodecs API is not supported."); 179 finishTest(); 180 return; 181 } 182 let cnv = document.getElementById("dst"); 183 184 let webgl_webcodecs_test_context = { 185 maxFrameTested: kMaxFrame, 186 displayed_frame: 0, 187 isFramePixelMatched: isFramePixelMatched, 188 testFailed: testFailed, 189 testPassed: testPassed, 190 finishTest: finishTest 191 }; 192 setTestMode(webgl_webcodecs_test_context); 193 let handleFrame = requestWebGLVideoFrameHandler(cnv); 194 if (handleFrame === null) { 195 finishTest(); 196 return; 197 } 198 199 startDrawing(); 200 let decoder = startDecodingAndRendering(cnv, handleFrame); 201 captureAndEncode((chunk) => { 202 decoder.decode(chunk); 203 }); 204 } 205 206 document.body.onload = main; 207 </script> 208 209 </body> 210 211 </html>