utils.js (7406B)
1 function make_audio_data(timestamp, channels, sampleRate, frames) { 2 let data = new Float32Array(frames*channels); 3 4 // This generates samples in a planar format. 5 for (var channel = 0; channel < channels; channel++) { 6 let hz = 100 + channel * 50; // sound frequency 7 let base_index = channel * frames; 8 for (var i = 0; i < frames; i++) { 9 let t = (i / sampleRate) * hz * (Math.PI * 2); 10 data[base_index + i] = Math.sin(t); 11 } 12 } 13 14 return new AudioData({ 15 timestamp: timestamp, 16 data: data, 17 numberOfChannels: channels, 18 numberOfFrames: frames, 19 sampleRate: sampleRate, 20 format: "f32-planar", 21 }); 22 } 23 24 function makeOffscreenCanvas(width, height, options) { 25 let canvas = new OffscreenCanvas(width, height); 26 let ctx = canvas.getContext('2d', options); 27 ctx.fillStyle = 'rgba(50, 100, 150, 255)'; 28 ctx.fillRect(0, 0, width, height); 29 return canvas; 30 } 31 32 function makeImageBitmap(width, height) { 33 return makeOffscreenCanvas(width, height).transferToImageBitmap(); 34 } 35 36 // Gives a chance to pending output and error callbacks to complete before 37 // resolving. 38 function endAfterEventLoopTurn() { 39 return new Promise(resolve => step_timeout(resolve, 0)); 40 } 41 42 // Returns a codec initialization with callbacks that expected to not be called. 43 function getDefaultCodecInit(test) { 44 return { 45 output: test.unreached_func("unexpected output"), 46 error: test.unreached_func("unexpected error"), 47 } 48 } 49 50 // Checks that codec can be configured, reset, reconfigured, and that incomplete 51 // or invalid configs throw errors immediately. 52 function testConfigurations(codec, validConfig, unsupportedCodecsList) { 53 assert_equals(codec.state, "unconfigured"); 54 55 const requiredConfigPairs = validConfig; 56 let incrementalConfig = {}; 57 58 for (let key in requiredConfigPairs) { 59 // Configure should fail while required keys are missing. 60 assert_throws_js(TypeError, () => { codec.configure(incrementalConfig); }); 61 incrementalConfig[key] = requiredConfigPairs[key]; 62 assert_equals(codec.state, "unconfigured"); 63 } 64 65 // Configure should pass once incrementalConfig meets all requirements. 66 codec.configure(incrementalConfig); 67 assert_equals(codec.state, "configured"); 68 69 // We should be able to reconfigure the codec. 70 codec.configure(incrementalConfig); 71 assert_equals(codec.state, "configured"); 72 73 let config = incrementalConfig; 74 75 unsupportedCodecsList.forEach(unsupportedCodec => { 76 // Invalid codecs should fail. 77 config.codec = unsupportedCodec; 78 assert_throws_dom('NotSupportedError', () => { 79 codec.configure(config); 80 }, unsupportedCodec); 81 }); 82 83 // The failed configures should not affect the current config. 84 assert_equals(codec.state, "configured"); 85 86 // Test we can configure after a reset. 87 codec.reset() 88 assert_equals(codec.state, "unconfigured"); 89 90 codec.configure(validConfig); 91 assert_equals(codec.state, "configured"); 92 } 93 94 // Performs an encode or decode with the provided input, depending on whether 95 // the passed codec is an encoder or a decoder. 96 function encodeOrDecodeShouldThrow(codec, input) { 97 // We are testing encode/decode on codecs in invalid states. 98 assert_not_equals(codec.state, "configured"); 99 100 if (codec.decode) { 101 assert_throws_dom("InvalidStateError", 102 () => codec.decode(input), 103 "decode"); 104 } else if (codec.encode) { 105 // Encoders consume frames, so clone it to be safe. 106 assert_throws_dom("InvalidStateError", 107 () => codec.encode(input.clone()), 108 "encode"); 109 110 } else { 111 assert_unreached("Codec should have encode or decode function"); 112 } 113 } 114 115 // Makes sure that we cannot close, configure, reset, flush, decode or encode a 116 // closed codec. 117 function testClosedCodec(test, codec, validconfig, codecInput) { 118 assert_equals(codec.state, "unconfigured"); 119 120 codec.close(); 121 assert_equals(codec.state, "closed"); 122 123 assert_throws_dom("InvalidStateError", 124 () => codec.configure(validconfig), 125 "configure"); 126 assert_throws_dom("InvalidStateError", 127 () => codec.reset(), 128 "reset"); 129 assert_throws_dom("InvalidStateError", 130 () => codec.close(), 131 "close"); 132 133 encodeOrDecodeShouldThrow(codec, codecInput); 134 135 return promise_rejects_dom(test, 'InvalidStateError', codec.flush(), 'flush'); 136 } 137 138 // Makes sure we cannot flush, encode or decode with an unconfigured coded, and 139 // that reset is a valid no-op. 140 function testUnconfiguredCodec(test, codec, codecInput) { 141 assert_equals(codec.state, "unconfigured"); 142 143 // Configure() and Close() are valid operations that would transition us into 144 // a different state. 145 146 // Resetting an unconfigured encoder is a no-op. 147 codec.reset(); 148 assert_equals(codec.state, "unconfigured"); 149 150 encodeOrDecodeShouldThrow(codec, codecInput); 151 152 return promise_rejects_dom(test, 'InvalidStateError', codec.flush(), 'flush'); 153 } 154 155 // Reference values generated by: 156 // https://fiddle.skia.org/c/f100d4d5f085a9e09896aabcbc463868 157 158 const kSRGBPixel = [50, 100, 150, 255]; 159 const kP3Pixel = [62, 99, 146, 255]; 160 161 function testCanvas(ctx, width, height, expected_pixel, imageSetting, assert_compares) { 162 // The dup getImageData is to workaournd crbug.com/1100233 163 let imageData = ctx.getImageData(0, 0, width, height, imageSetting); 164 let colorData = ctx.getImageData(0, 0, width, height, imageSetting).data; 165 const kMaxPixelToCheck = 128 * 96; 166 let step = width * height / kMaxPixelToCheck; 167 step = Math.round(step); 168 step = (step < 1) ? 1 : step; 169 for (let i = 0; i < 4 * width * height; i += (4 * step)) { 170 assert_compares(colorData[i], expected_pixel[0]); 171 assert_compares(colorData[i + 1], expected_pixel[1]); 172 assert_compares(colorData[i + 2], expected_pixel[2]); 173 assert_compares(colorData[i + 3], expected_pixel[3]); 174 } 175 } 176 177 function makeDetachedArrayBuffer() { 178 const buffer = new ArrayBuffer(10); 179 const view = new Uint8Array(buffer); 180 new MessageChannel().port1.postMessage(buffer, [buffer]); 181 return view; 182 } 183 184 function isFrameClosed(frame) { 185 return frame.format == null && frame.codedWidth == 0 && 186 frame.codedHeight == 0 && frame.displayWidth == 0 && 187 frame.displayHeight == 0 && frame.codedRect == null && 188 frame.visibleRect == null; 189 } 190 191 function testImageBitmapToAndFromVideoFrame( 192 width, height, expectedPixel, canvasOptions, imageBitmapOptions, 193 imageSetting) { 194 let canvas = new OffscreenCanvas(width, height); 195 let ctx = canvas.getContext('2d', canvasOptions); 196 ctx.fillStyle = 'rgb(50, 100, 150)'; 197 ctx.fillRect(0, 0, width, height); 198 testCanvas(ctx, width, height, expectedPixel, imageSetting, assert_equals); 199 200 return createImageBitmap(canvas, imageBitmapOptions) 201 .then((fromImageBitmap) => { 202 let videoFrame = new VideoFrame(fromImageBitmap, {timestamp: 0}); 203 return createImageBitmap(videoFrame, imageBitmapOptions); 204 }) 205 .then((toImageBitmap) => { 206 let myCanvas = new OffscreenCanvas(width, height); 207 let myCtx = myCanvas.getContext('2d', canvasOptions); 208 myCtx.drawImage(toImageBitmap, 0, 0); 209 let tolerance = 2; 210 testCanvas( 211 myCtx, width, height, expectedPixel, imageSetting, 212 (actual, expected) => { 213 assert_approx_equals(actual, expected, tolerance); 214 }); 215 }); 216 }