// META: global=window,dedicatedworker // META: script=/webcodecs/utils.js // META: script=/webcodecs/video-encoder-utils.js // META: variant=?av1 // META: variant=?vp8 // META: variant=?vp9 // META: variant=?vp9_p1 // META: variant=?vp9_p2 // META: variant=?vp9_p3 // META: variant=?h264 // Tests encoding high-bit-depth (16-bit) VideoFrame inputs. // - Standard 8-bit codec profiles (AV1 Main 8-bit, VP8, VP9 Profile 0, VP9 Profile 1, H.264 Baseline) // perform 16-to-8 bit conversion prior to encoding. // - High-bit-depth codec profiles (VP9 Profile 2, VP9 Profile 3) perform 16-bit high-bit-depth encoding. const ENCODER_CONFIG = { '?av1': { codec: 'av01.0.04M.08', isHbd: false }, '?vp8': { codec: 'vp8', isHbd: false }, '?vp9': { codec: 'vp09.00.10.08', isHbd: false }, '?vp9_p1': { codec: 'vp09.01.10.08.03', isHbd: false }, '?vp9_p2': { codec: 'vp09.02.10.10', isHbd: true }, '?vp9_p3': { codec: 'vp09.03.10.10.03', isHbd: true }, '?h264': { codec: 'avc1.42001E', avc: { format: 'avc' }, isHbd: false }, }[location.search] || {}; ENCODER_CONFIG.width = 64; ENCODER_CONFIG.height = 48; ENCODER_CONFIG.bitrate = 1000000; ENCODER_CONFIG.framerate = 30; function createHbdFrame(format, width, height, ts) { let sub_w = width; let sub_h = height; if (format.includes('I420')) { sub_w = width / 2; sub_h = height / 2; } else if (format.includes('I422')) { sub_w = width / 2; } let luma_size = width * height; let chroma_size = sub_w * sub_h; let has_alpha = format.includes('I420A') || format.includes('I422A') || format.includes('I444A'); let is_12bit = format.includes('P12'); let shift = is_12bit ? 4 : 2; let total_samples = luma_size + 2 * chroma_size + (has_alpha ? luma_size : 0); let data = new Uint16Array(total_samples); let uOffset = luma_size; let vOffset = uOffset + chroma_size; let aOffset = vOffset + chroma_size; // Y=96, U=155, V=104 data.fill(96 << shift, 0, uOffset); data.fill(155 << shift, uOffset, vOffset); data.fill(104 << shift, vOffset, has_alpha ? aOffset : total_samples); if (has_alpha) { let max_alpha = is_12bit ? 4095 : 1023; data.fill(max_alpha, aOffset); } return new VideoFrame(data, { format: format, timestamp: ts, codedWidth: width, codedHeight: height, }); } const HBD_FORMATS = [ 'I420P10', 'I422P10', 'I444P10', 'I420P12', 'I422P12', 'I444P12', 'I420AP10', 'I422AP10', 'I444AP10', ]; HBD_FORMATS.forEach(format => { promise_test(async t => { let output_chunks = []; let decoder_config = null; let encoder_init = { output: (chunk, metadata) => { output_chunks.push(chunk); if (metadata && metadata.decoderConfig) { decoder_config = metadata.decoderConfig; } }, error: (e) => { assert_unreached(e.message); } }; await checkEncoderSupport(t, ENCODER_CONFIG); let encoder = new VideoEncoder(encoder_init); encoder.configure(ENCODER_CONFIG); let frame = createHbdFrame(format, ENCODER_CONFIG.width, ENCODER_CONFIG.height, 0); encoder.encode(frame); frame.close(); await encoder.flush(); encoder.close(); assert_equals(output_chunks.length, 1, 'output chunk count'); assert_greater_than(output_chunks[0].byteLength, 0, 'output chunk byte length'); assert_not_equals(decoder_config, null, 'decoderConfig'); let decoder_support = await VideoDecoder.isConfigSupported(decoder_config); assert_implements_optional( decoder_support.supported, 'Unsupported decoder config: ' + JSON.stringify(decoder_config)); let decoded_frame = null; let decoder_init = { output: (f) => { decoded_frame = f; }, error: (e) => { assert_unreached(e.message); } }; let decoder = new VideoDecoder(decoder_init); decoder.configure(decoder_config); decoder.decode(output_chunks[0]); await decoder.flush(); decoder.close(); assert_not_equals(decoded_frame, null, 'decoded frame'); // Using copyTo({ format: 'RGBA' }) guarantees unified cross-browser readback // regardless of whether the decoder outputs software YUV or hardware GPU frames. let size = decoded_frame.allocationSize({ format: 'RGBA' }); assert_greater_than(size, 0, 'allocationSize RGBA'); let buffer = new ArrayBuffer(size); await decoded_frame.copyTo(buffer, { format: 'RGBA' }); decoded_frame.close(); let view = new Uint8Array(buffer); let tolerance = 25; assert_approx_equals(view[0], kSRGBPixel[0], tolerance, 'R pixel value'); assert_approx_equals(view[1], kSRGBPixel[1], tolerance, 'G pixel value'); assert_approx_equals(view[2], kSRGBPixel[2], tolerance, 'B pixel value'); assert_approx_equals(view[3], 255, tolerance, 'A pixel value'); }, ENCODER_CONFIG.isHbd ? `Test 16-bit high-bit-depth encoding of ${format}` : `Test 16-to-8 bit conversion encoding of ${format}`); });