| function make_audio_data(timestamp, channels, sampleRate, frames) { |
| let data = new Float32Array(frames*channels); |
| |
| // This generates samples in a planar format. |
| for (var channel = 0; channel < channels; channel++) { |
| let hz = 100 + channel * 50; // sound frequency |
| let base_index = channel * frames; |
| for (var i = 0; i < frames; i++) { |
| let t = (i / sampleRate) * hz * (Math.PI * 2); |
| data[base_index + i] = Math.sin(t); |
| } |
| } |
| |
| return new AudioData({ |
| timestamp: timestamp, |
| data: data, |
| numberOfChannels: channels, |
| numberOfFrames: frames, |
| sampleRate: sampleRate, |
| format: "f32-planar", |
| }); |
| } |
| |
| function makeOffscreenCanvas(width, height, options) { |
| let canvas = new OffscreenCanvas(width, height); |
| let ctx = canvas.getContext('2d', options); |
| ctx.fillStyle = 'rgba(50, 100, 150, 255)'; |
| ctx.fillRect(0, 0, width, height); |
| return canvas; |
| } |
| |
| function makeImageBitmap(width, height) { |
| return makeOffscreenCanvas(width, height).transferToImageBitmap(); |
| } |
| |
| // Gives a chance to pending output and error callbacks to complete before |
| // resolving. |
| function endAfterEventLoopTurn() { |
| return new Promise(resolve => step_timeout(resolve, 0)); |
| } |
| |
| // Returns a codec initialization with callbacks that expected to not be called. |
| function getDefaultCodecInit(test) { |
| return { |
| output: test.unreached_func("unexpected output"), |
| error: test.unreached_func("unexpected error"), |
| } |
| } |
| |
| // Checks that codec can be configured, reset, reconfigured, and that incomplete |
| // or invalid configs throw errors immediately. |
| function testConfigurations(codec, validConfig, unsupportedCodecsList) { |
| assert_equals(codec.state, "unconfigured"); |
| |
| const requiredConfigPairs = validConfig; |
| let incrementalConfig = {}; |
| |
| for (let key in requiredConfigPairs) { |
| // Configure should fail while required keys are missing. |
| assert_throws_js(TypeError, () => { codec.configure(incrementalConfig); }); |
| incrementalConfig[key] = requiredConfigPairs[key]; |
| assert_equals(codec.state, "unconfigured"); |
| } |
| |
| // Configure should pass once incrementalConfig meets all requirements. |
| codec.configure(incrementalConfig); |
| assert_equals(codec.state, "configured"); |
| |
| // We should be able to reconfigure the codec. |
| codec.configure(incrementalConfig); |
| assert_equals(codec.state, "configured"); |
| |
| let config = incrementalConfig; |
| |
| unsupportedCodecsList.forEach(unsupportedCodec => { |
| // Invalid codecs should fail. |
| config.codec = unsupportedCodec; |
| assert_throws_dom('NotSupportedError', () => { |
| codec.configure(config); |
| }, unsupportedCodec); |
| }); |
| |
| // The failed configures should not affect the current config. |
| assert_equals(codec.state, "configured"); |
| |
| // Test we can configure after a reset. |
| codec.reset() |
| assert_equals(codec.state, "unconfigured"); |
| |
| codec.configure(validConfig); |
| assert_equals(codec.state, "configured"); |
| } |
| |
| // Performs an encode or decode with the provided input, depending on whether |
| // the passed codec is an encoder or a decoder. |
| function encodeOrDecodeShouldThrow(codec, input) { |
| // We are testing encode/decode on codecs in invalid states. |
| assert_not_equals(codec.state, "configured"); |
| |
| if (codec.decode) { |
| assert_throws_dom("InvalidStateError", |
| () => codec.decode(input), |
| "decode"); |
| } else if (codec.encode) { |
| // Encoders consume frames, so clone it to be safe. |
| assert_throws_dom("InvalidStateError", |
| () => codec.encode(input.clone()), |
| "encode"); |
| |
| } else { |
| assert_unreached("Codec should have encode or decode function"); |
| } |
| } |
| |
| // Makes sure that we cannot close, configure, reset, flush, decode or encode a |
| // closed codec. |
| function testClosedCodec(test, codec, validconfig, codecInput) { |
| assert_equals(codec.state, "unconfigured"); |
| |
| codec.close(); |
| assert_equals(codec.state, "closed"); |
| |
| assert_throws_dom("InvalidStateError", |
| () => codec.configure(validconfig), |
| "configure"); |
| assert_throws_dom("InvalidStateError", |
| () => codec.reset(), |
| "reset"); |
| assert_throws_dom("InvalidStateError", |
| () => codec.close(), |
| "close"); |
| |
| encodeOrDecodeShouldThrow(codec, codecInput); |
| |
| return promise_rejects_dom(test, 'InvalidStateError', codec.flush(), 'flush'); |
| } |
| |
| // Makes sure we cannot flush, encode or decode with an unconfigured coded, and |
| // that reset is a valid no-op. |
| function testUnconfiguredCodec(test, codec, codecInput) { |
| assert_equals(codec.state, "unconfigured"); |
| |
| // Configure() and Close() are valid operations that would transition us into |
| // a different state. |
| |
| // Resetting an unconfigured encoder is a no-op. |
| codec.reset(); |
| assert_equals(codec.state, "unconfigured"); |
| |
| encodeOrDecodeShouldThrow(codec, codecInput); |
| |
| return promise_rejects_dom(test, 'InvalidStateError', codec.flush(), 'flush'); |
| } |
| |
| // Reference values generated by: |
| // https://fiddle.skia.org/c/f100d4d5f085a9e09896aabcbc463868 |
| |
| const kSRGBPixel = [50, 100, 150, 255]; |
| const kP3Pixel = [62, 99, 146, 255]; |
| |
| function testCanvas(ctx, width, height, expected_pixel, imageSetting, assert_compares) { |
| // The dup getImageData is to workaournd crbug.com/1100233 |
| let imageData = ctx.getImageData(0, 0, width, height, imageSetting); |
| let colorData = ctx.getImageData(0, 0, width, height, imageSetting).data; |
| const kMaxPixelToCheck = 128 * 96; |
| let step = width * height / kMaxPixelToCheck; |
| step = Math.round(step); |
| step = (step < 1) ? 1 : step; |
| for (let i = 0; i < 4 * width * height; i += (4 * step)) { |
| assert_compares(colorData[i], expected_pixel[0]); |
| assert_compares(colorData[i + 1], expected_pixel[1]); |
| assert_compares(colorData[i + 2], expected_pixel[2]); |
| assert_compares(colorData[i + 3], expected_pixel[3]); |
| } |
| } |
| |
| function makeDetachedArrayBuffer() { |
| const buffer = new ArrayBuffer(10); |
| const view = new Uint8Array(buffer); |
| new MessageChannel().port1.postMessage(buffer, [buffer]); |
| return view; |
| } |
| |
| function isFrameClosed(frame) { |
| return frame.format == null && frame.codedWidth == 0 && |
| frame.codedHeight == 0 && frame.displayWidth == 0 && |
| frame.displayHeight == 0 && frame.codedRect == null && |
| frame.visibleRect == null; |
| } |
| |
| function testImageBitmapToAndFromVideoFrame( |
| width, height, expectedPixel, canvasOptions, imageBitmapOptions, |
| imageSetting) { |
| let canvas = new OffscreenCanvas(width, height); |
| let ctx = canvas.getContext('2d', canvasOptions); |
| ctx.fillStyle = 'rgb(50, 100, 150)'; |
| ctx.fillRect(0, 0, width, height); |
| testCanvas(ctx, width, height, expectedPixel, imageSetting, assert_equals); |
| |
| return createImageBitmap(canvas, imageBitmapOptions) |
| .then((fromImageBitmap) => { |
| let videoFrame = new VideoFrame(fromImageBitmap, {timestamp: 0}); |
| return createImageBitmap(videoFrame, imageBitmapOptions); |
| }) |
| .then((toImageBitmap) => { |
| let myCanvas = new OffscreenCanvas(width, height); |
| let myCtx = myCanvas.getContext('2d', canvasOptions); |
| myCtx.drawImage(toImageBitmap, 0, 0); |
| let tolerance = 2; |
| testCanvas( |
| myCtx, width, height, expectedPixel, imageSetting, |
| (actual, expected) => { |
| assert_approx_equals(actual, expected, tolerance); |
| }); |
| }); |
| } |