blob: a44037e35abb9c075fb2d7e8dc0849a4b1c2b238 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
async function main(arg) {
const frame_width = 640;
const frame_height = 480;
// Use one encode size for both hardware and software variants. Some
// accelerated encode/decode paths only work above their advertised minimum
// dimensions, while 480x360 is still small enough for software and avoids
// making the test geometry depend on arg.acceleration.
//
// Known HW floors:
// - D3D11 decode: NVIDIA HEVC 144x144 (VP9/AV1 128x128)
// - Android MediaCodec HW preference: 96x96
// - Mac VT decode: 16x16 (x86) / 64x64 (ARM)
// - VAAPI encode / hybrid-decode: 321x241 (QVGA+1)
// - V4L2 mins are driver-reported (default 16x16).
const encoder_width = 480;
const encoder_height = 360;
let errors = 0;
const cnv = document.getElementById('cnv');
cnv.width = encoder_width;
cnv.height = encoder_height;
var ctx = cnv.getContext('2d');
const encoder_config = {
codec: arg.codec,
hardwareAcceleration: arg.acceleration,
width: encoder_width,
height: encoder_height,
bitrate: 5000000,
framerate: 24
};
if (arg.codec.startsWith('avc1')) {
encoder_config.avc = {format: 'annexb'};
} else if (arg.codec.startsWith('hvc1')) {
encoder_config.hevc = {format: 'annexb'};
}
TEST.log('Starting test with arguments: ' + JSON.stringify(arg));
let supported = false;
try {
supported =
(await VideoEncoder.isConfigSupported(encoder_config)).supported;
} catch (e) {
}
if (!supported) {
TEST.skip('Unsupported codec: ' + arg.codec);
return;
}
// Try to match the encoder acceleration preference for symmetry, but fall
// back to 'no-preference' since decode acceleration is not symmetric with
// encode on all platforms for fully optional codecs like H.265 or sometimes
// optional codecs like H.264.
let decoder_acceleration = 'no-preference';
try {
let support = await VideoDecoder.isConfigSupported({
codec: arg.codec,
hardwareAcceleration: arg.acceleration,
});
if (support.supported) {
decoder_acceleration = arg.acceleration;
}
} catch (e) {
}
let source =
await createFrameSource(arg.source_type, frame_width, frame_height);
if (!source) {
TEST.skip('Unsupported source: ' + arg.source_type);
return;
}
let decoder = new VideoDecoder({
output(frame) {
ctx.drawImage(frame, 0, 0);
frame.close();
},
error(e) {
errors++;
TEST.log(e);
}
});
const encoder_init = {
output(chunk, metadata) {
let config = metadata.decoderConfig;
if (config) {
config.hardwareAcceleration = decoder_acceleration;
decoder.configure(config);
}
decoder.decode(chunk);
},
error(e) {
errors++;
TEST.log(e);
}
};
let encoder = new VideoEncoder(encoder_init);
encoder.configure(encoder_config);
let frame = await source.getNextFrame();
let cropped_frame = new VideoFrame(frame, {
timestamp: frame.timestamp,
visibleRect: {
x: (frame.codedWidth - encoder_width) / 2,
y: (frame.codedHeight - encoder_height) / 2,
width: encoder_width,
height: encoder_height
}
});
{
// Exercise copyTo of a cropped frame.
let size = cropped_frame.allocationSize();
let buf = new ArrayBuffer(size);
let layout = await cropped_frame.copyTo(buf);
TEST.assert(layout.length > 0, 'copyTo layout is empty');
}
encoder.encode(cropped_frame, {keyFrame: true});
frame.close();
cropped_frame.close();
await encoder.flush();
await decoder.flush();
encoder.close();
decoder.close();
source.close();
// TODO(crbug.com/349062462): Enable pixel validation once we sort it out
// on all platforms.
/*
if (arg.source_type == 'camera' && !arg.validate_camera_frames) {
TEST.log('Can\'t validate camera frames, skipping');
} else {
// The inner cropped rect maintains the four color quadrant structure,
// so we can still check the center cropped frame.
checkFourColorsFrame(ctx, encoder_width, encoder_height, 15);
}
*/
TEST.assert(
errors == 0, 'Decoding or encoding errors occurred during the test');
TEST.log('Test completed');
}