video-encoder-utils.js (3861B)
1 async function checkEncoderSupport(test, config) { 2 assert_equals("function", typeof VideoEncoder.isConfigSupported); 3 let supported = false; 4 try { 5 const support = await VideoEncoder.isConfigSupported(config); 6 supported = support.supported; 7 } catch (e) {} 8 9 assert_implements_optional(supported, 'Unsupported config: ' + 10 JSON.stringify(config)); 11 } 12 13 function fourColorsFrame(ctx, width, height, text) { 14 const kYellow = "#FFFF00"; 15 const kRed = "#FF0000"; 16 const kBlue = "#0000FF"; 17 const kGreen = "#00FF00"; 18 19 ctx.fillStyle = kYellow; 20 ctx.fillRect(0, 0, width / 2, height / 2); 21 22 ctx.fillStyle = kRed; 23 ctx.fillRect(width / 2, 0, width / 2, height / 2); 24 25 ctx.fillStyle = kBlue; 26 ctx.fillRect(0, height / 2, width / 2, height / 2); 27 28 ctx.fillStyle = kGreen; 29 ctx.fillRect(width / 2, height / 2, width / 2, height / 2); 30 31 ctx.fillStyle = 'white'; 32 ctx.font = (height / 10) + 'px sans-serif'; 33 ctx.fillText(text, width / 2, height / 2); 34 } 35 36 // Paints |count| black dots on the |ctx|, so their presence can be validated 37 // later. This is an analog of the most basic bar code. 38 function putBlackDots(ctx, width, height, count) { 39 ctx.fillStyle = 'black'; 40 const dot_size = 20; 41 const step = dot_size * 2; 42 43 for (let i = 1; i <= count; i++) { 44 let x = i * step; 45 let y = step * (x / width + 1); 46 x %= width; 47 ctx.fillRect(x, y, dot_size, dot_size); 48 } 49 } 50 51 // Validates that frame has |count| black dots in predefined places. 52 function validateBlackDots(frame, count) { 53 const width = frame.displayWidth; 54 const height = frame.displayHeight; 55 let cnv = new OffscreenCanvas(width, height); 56 var ctx = cnv.getContext('2d', {willReadFrequently: true}); 57 ctx.drawImage(frame, 0, 0); 58 const dot_size = 20; 59 const step = dot_size * 2; 60 61 for (let i = 1; i <= count; i++) { 62 let x = i * step + dot_size / 2; 63 let y = step * (x / width + 1) + dot_size / 2; 64 x %= width; 65 66 if (x) 67 x = x -1; 68 if (y) 69 y = y -1; 70 71 let rgba = ctx.getImageData(x, y, 2, 2).data; 72 const tolerance = 60; 73 if ((rgba[0] > tolerance || rgba[1] > tolerance || rgba[2] > tolerance) 74 && (rgba[4] > tolerance || rgba[5] > tolerance || rgba[6] > tolerance) 75 && (rgba[8] > tolerance || rgba[9] > tolerance || rgba[10] > tolerance) 76 && (rgba[12] > tolerance || rgba[13] > tolerance || rgba[14] > tolerance)) { 77 // The dot is too bright to be a black dot. 78 return false; 79 } 80 } 81 return true; 82 } 83 84 function createFrame(width, height, ts = 0, additionalOptions = {}) { 85 let duration = 33333; // 30fps 86 let text = ts.toString(); 87 let cnv = new OffscreenCanvas(width, height); 88 var ctx = cnv.getContext('2d'); 89 fourColorsFrame(ctx, width, height, text); 90 91 // Merge the default options with the provided additionalOptions 92 const videoFrameOptions = { 93 timestamp: ts, 94 duration, 95 ...additionalOptions, // Spread the additional options to merge them 96 }; 97 98 return new VideoFrame(cnv, videoFrameOptions); 99 } 100 101 function createDottedFrame(width, height, dots, ts) { 102 if (ts === undefined) 103 ts = dots; 104 let duration = 33333; // 30fps 105 let text = ts.toString(); 106 let cnv = new OffscreenCanvas(width, height); 107 var ctx = cnv.getContext('2d'); 108 fourColorsFrame(ctx, width, height, text); 109 putBlackDots(ctx, width, height, dots); 110 return new VideoFrame(cnv, { timestamp: ts, duration }); 111 } 112 113 function createVideoEncoder(t, callbacks) { 114 return new VideoEncoder({ 115 output(chunk, metadata) { 116 if (callbacks && callbacks.output) { 117 t.step(() => callbacks.output(chunk, metadata)); 118 } else { 119 t.unreached_func('unexpected output()'); 120 } 121 }, 122 error(e) { 123 if (callbacks && callbacks.error) { 124 t.step(() => callbacks.error(e)); 125 } else { 126 t.unreached_func('unexpected error()'); 127 } 128 } 129 }); 130 }