Source code
Revision control
Copy as Markdown
Other Tools
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:
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);
});
});
}