mirror of
https://github.com/servo/servo.git
synced 2025-06-25 09:34:32 +01:00
235 lines
7.6 KiB
JavaScript
235 lines
7.6 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
}
|