summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webcodecs/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webcodecs/utils.js')
-rw-r--r--testing/web-platform/tests/webcodecs/utils.js235
1 files changed, 235 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webcodecs/utils.js b/testing/web-platform/tests/webcodecs/utils.js
new file mode 100644
index 0000000000..bbb53e70e0
--- /dev/null
+++ b/testing/web-platform/tests/webcodecs/utils.js
@@ -0,0 +1,235 @@
+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, validCondig, invalidCodecs) {
+ assert_equals(codec.state, "unconfigured");
+
+ const requiredConfigPairs = validCondig;
+ 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;
+
+ invalidCodecs.forEach(badCodec => {
+ // Invalid codecs should fail.
+ config.codec = badCodec;
+ assert_throws_js(TypeError, () => { codec.configure(config); }, badCodec);
+ })
+
+ // 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(validCondig);
+ 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];
+const kRec2020Pixel = [87, 106, 151, 255];
+
+const kCanvasOptionsP3Uint8 = {
+ colorSpace: 'display-p3',
+ pixelFormat: 'uint8'
+};
+
+const kImageSettingOptionsP3Uint8 = {
+ colorSpace: 'display-p3',
+ storageFormat: 'uint8'
+};
+
+const kCanvasOptionsRec2020Uint8 = {
+ colorSpace: 'rec2020',
+ pixelFormat: 'uint8'
+};
+
+const kImageSettingOptionsRec2020Uint8 = {
+ colorSpace: 'rec2020',
+ storageFormat: 'uint8'
+};
+
+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);
+ });
+ });
+}