// META: global=window,dedicatedworker // META: script=/webcodecs/video-encoder-utils.js // META: variant=?av1 // META: variant=?vp9_p0 // META: variant=?vp9_p2 function get_config() { const config = { '?av1': {codec: 'av01.0.04M.08'}, '?vp8': {codec: 'vp8'}, '?vp9_p0': {codec: 'vp09.00.10.08'}, '?vp9_p2': {codec: 'vp09.02.10.10'}, '?h264': {codec: 'avc1.42001E', avc: {format: 'annexb'}} }[location.search]; config.width = 320; config.height = 200; config.bitrate = 1000000; config.bitrateMode = 'quantizer'; config.framerate = 30; return config; } function get_qp_range() { switch (location.search) { case '?av1': return {min: 1, max: 63}; case '?vp9_p0': return {min: 1, max: 63}; case '?vp9_p2': return {min: 1, max: 63}; case '?h264': return {min: 1, max: 51}; } return null; } function set_qp(options, value) { switch (location.search) { case '?av1': options.av1 = {quantizer: value}; return; case '?vp9_p0': options.vp9 = {quantizer: value}; return; case '?vp9_p2': options.vp9 = {quantizer: value}; return; case '?h264': options.avc = {quantizer: value}; return; } } async function per_frame_qp_test(t, encoder_config, qp_range, validate_result) { const w = encoder_config.width; const h = encoder_config.height; await checkEncoderSupport(t, encoder_config); const frames_to_encode = 12; let frames_decoded = 0; let frames_encoded = 0; let chunks = []; let corrupted_frames = []; const encoder_init = { output(chunk, metadata) { frames_encoded++; chunks.push(chunk); }, error(e) { assert_unreached(e.message); } }; let encoder = new VideoEncoder(encoder_init); encoder.configure(encoder_config); let qp = qp_range.min; for (let i = 0; i < frames_to_encode; i++) { let frame = createDottedFrame(w, h, i); let encode_options = {keyFrame: false}; set_qp(encode_options, qp); encoder.encode(frame, encode_options); frame.close(); qp += 3; if (qp > qp_range.max) { qp = qp_range.min } } await encoder.flush(); let decoder = new VideoDecoder({ output(frame) { frames_decoded++; // Check that we have intended number of dots and no more. // Completely black frame shouldn't pass the test. if (validate_result && !validateBlackDots(frame, frame.timestamp) || validateBlackDots(frame, frame.timestamp + 1)) { corrupted_frames.push(frame.timestamp) } frame.close(); }, error(e) { assert_unreached(e.message); } }); let decoder_config = { codec: encoder_config.codec, codedWidth: w, codedHeight: h, }; decoder.configure(decoder_config); for (let chunk of chunks) { decoder.decode(chunk); } await decoder.flush(); encoder.close(); decoder.close(); assert_equals(frames_encoded, frames_to_encode); assert_equals(chunks.length, frames_to_encode); assert_equals(frames_decoded, frames_to_encode); assert_equals( corrupted_frames.length, 0, `corrupted_frames: ${corrupted_frames}`); } promise_test(async t => { let config = get_config(); let range = get_qp_range(); return per_frame_qp_test(t, config, range, false); }, 'Frame QP encoding, full range'); promise_test(async t => { let config = get_config(); return per_frame_qp_test(t, config, {min: 1, max: 20}, true); }, 'Frame QP encoding, good range with validation');