Files
sousa-gecko/testing/web-platform/tests/webcodecs/audio-encoder-codec-specific.https.any.js
T
Syed AbuTalib c89853f570 Bug 1989450 [wpt PR 54937] - Separate Encoder Decoder Flush() promises to improve reliability, a=testonly
Automatic update from web-platform-tests
Separate Encoder Decoder Flush() promises to improve reliability

This is to make sure we do not flush the decoder until the encoder is
completely flushed. This will improve the reliability of the test.

Bug: 40243924
Change-Id: I96f89a8d54e00a8c46eeb04a5f9b6ad1c44b7c74
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6966926
Reviewed-by: Thomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Syed AbuTalib <lowkey@google.com>
Cr-Commit-Position: refs/heads/main@{#1517521}

--

wpt-commits: 0d0199a406fae5eebd0dc7129867c0006db97eb7
wpt-pr: 54937
2025-09-24 11:05:10 +00:00

355 lines
10 KiB
JavaScript

// META: global=window
// META: script=/webcodecs/utils.js
function make_silent_audio_data(timestamp, channels, sampleRate, frames) {
let data = new Float32Array(frames*channels);
return new AudioData({
timestamp: timestamp,
data: data,
numberOfChannels: channels,
numberOfFrames: frames,
sampleRate: sampleRate,
format: "f32-planar",
});
}
// The Opus DTX flag (discontinuous transmission) reduces the encoding bitrate
// for silence. This test ensures the DTX flag is working properly by encoding
// almost 10s of silence and comparing the bitrate with and without the flag.
promise_test(async t => {
let sample_rate = 48000;
let total_duration_s = 10;
let data_count = 100;
let normal_outputs = [];
let dtx_outputs = [];
let normal_encoder = new AudioEncoder({
error: e => {
assert_unreached('error: ' + e);
},
output: chunk => {
normal_outputs.push(chunk);
}
});
let dtx_encoder = new AudioEncoder({
error: e => {
assert_unreached('error: ' + e);
},
output: chunk => {
dtx_outputs.push(chunk);
}
});
let config = {
codec: 'opus',
sampleRate: sample_rate,
numberOfChannels: 2,
bitrate: 256000, // 256kbit
};
let normal_config = {...config, opus: {usedtx: false}};
let dtx_config = {...config, opus: {usedtx: true}};
let normal_config_support = await AudioEncoder.isConfigSupported(normal_config);
assert_implements_optional(normal_config_support.supported, "Opus not supported");
let dtx_config_support = await AudioEncoder.isConfigSupported(dtx_config);
assert_implements_optional(dtx_config_support.supported, "Opus DTX not supported");
// Configure one encoder with and one without the DTX flag
normal_encoder.configure(normal_config);
dtx_encoder.configure(dtx_config);
let timestamp_us = 0;
let data_duration_s = total_duration_s / data_count;
let data_length = data_duration_s * config.sampleRate;
for (let i = 0; i < data_count; i++) {
let data;
if (i == 0 || i == (data_count - 1)) {
// Send real data for the first and last 100ms.
data = make_audio_data(
timestamp_us, config.numberOfChannels, config.sampleRate,
data_length);
} else {
// Send silence for the rest of the 10s.
data = make_silent_audio_data(
timestamp_us, config.numberOfChannels, config.sampleRate,
data_length);
}
normal_encoder.encode(data);
dtx_encoder.encode(data);
data.close();
timestamp_us += data_duration_s * 1_000_000;
}
await Promise.all([normal_encoder.flush(), dtx_encoder.flush()])
normal_encoder.close();
dtx_encoder.close();
// We expect a significant reduction in the number of packets, over ~10s of silence.
assert_less_than(dtx_outputs.length, (normal_outputs.length / 2));
}, 'Test the Opus DTX flag works.');
// The Opus bitrateMode enum chooses whether we use a constant or variable bitrate.
// This test ensures that VBR/CBR is respected properly by encoding almost 10s of
// silence and comparing the size of the encoded variable or constant bitrates.
promise_test(async t => {
let sample_rate = 48000;
let total_duration_s = 10;
let data_count = 100;
let vbr_outputs = [];
let cbr_outputs = [];
let cbr_encoder = new AudioEncoder({
error: e => {
assert_unreached('error: ' + e);
},
output: chunk => {
cbr_outputs.push(chunk);
}
});
let vbr_encoder = new AudioEncoder({
error: e => {
assert_unreached('error: ' + e);
},
output: chunk => {
vbr_outputs.push(chunk);
}
});
let config = {
codec: 'opus',
sampleRate: sample_rate,
numberOfChannels: 2,
bitrate: 256000, // 256kbit
};
let cbr_config = { ...config, bitrateMode: "constant" };
let vbr_config = { ...config, bitrateMode: "variable" };
let cbr_config_support = await AudioEncoder.isConfigSupported(cbr_config);
assert_implements_optional(cbr_config_support.supported, "Opus CBR not supported");
let vbr_config_support = await AudioEncoder.isConfigSupported(vbr_config);
assert_implements_optional(vbr_config_support.supported, "Opus VBR not supported");
// Configure one encoder with VBR and one CBR.
cbr_encoder.configure(cbr_config);
vbr_encoder.configure(vbr_config);
let timestamp_us = 0;
let data_duration_s = total_duration_s / data_count;
let data_length = data_duration_s * config.sampleRate;
for (let i = 0; i < data_count; i++) {
let data;
if (i == 0 || i == (data_count - 1)) {
// Send real data for the first and last 100ms.
data = make_audio_data(
timestamp_us, config.numberOfChannels, config.sampleRate,
data_length);
} else {
// Send silence for the rest of the 10s.
data = make_silent_audio_data(
timestamp_us, config.numberOfChannels, config.sampleRate,
data_length);
}
vbr_encoder.encode(data);
cbr_encoder.encode(data);
data.close();
timestamp_us += data_duration_s * 1_000_000;
}
await Promise.all([cbr_encoder.flush(), vbr_encoder.flush()])
cbr_encoder.close();
vbr_encoder.close();
let vbr_total_bytes = 0;
vbr_outputs.forEach(chunk => vbr_total_bytes += chunk.byteLength)
let cbr_total_bytes = 0;
cbr_outputs.forEach(chunk => cbr_total_bytes += chunk.byteLength)
// We expect a significant reduction in the size of the packets, over ~10s of silence.
assert_less_than(vbr_total_bytes, (cbr_total_bytes / 2));
}, 'Test the Opus bitrateMode flag works.');
// Opus can handle configs with a frameDuration that is any multiple of 2.5ms,
// up to 120ms. This test ensures that we can handle multiples and encoders
// return encoded packets with the correct frameDuration.
promise_test(async t => {
let sample_rate = 48000;
let total_duration_s = 10;
let data_count = 100;
let valid_frame_durations = [7500, 30000, 120000];
for(const duration of valid_frame_durations) {
let outputs = [];
// We use an AudioDecoder to verify that the EncodedAudioChunks from
// encoder.encode() can be decoded.
let decoder = new AudioDecoder({
error: e => {
assert_unreached(`Duration ${duration} received this error: ` + e);
},
output: chunk => {
chunk.close();
}
});
decoder.configure({
codec: "opus",
numberOfChannels: 2,
sampleRate: sample_rate
});
let encoder = new AudioEncoder({
error: e => {
assert_unreached(`Duration ${duration} received this error: ` + e);
},
output: chunk => {
outputs.push(chunk);
decoder.decode(chunk);
}
});
let config = {
codec: 'opus',
sampleRate: sample_rate,
numberOfChannels: 2,
bitrate: 256000, // 256kbit
bitrateMode: "constant",
opus: {
frameDuration: duration,
},
};
encoder.configure(config);
let timestamp_us = 0;
let data_duration_s = total_duration_s / data_count;
let data_length = data_duration_s * config.sampleRate;
for (let i = 0; i < data_count; i++) {
let data = make_audio_data(
timestamp_us, config.numberOfChannels, config.sampleRate,
data_length);
encoder.encode(data);
data.close();
timestamp_us += data_duration_s * 1_000_000;
}
await encoder.flush()
await decoder.flush();
encoder.close();
decoder.close();
assert_true(outputs.every((chunk) => chunk.duration === duration));
}
}, 'Test Opus valid frame durations.');
// The AAC bitrateMode enum chooses whether we use a constant or variable bitrate.
// This test exercises the VBR/CBR paths. Some platforms don't support VBR for AAC,
// and still emit a constant bitrate.
promise_test(async t => {
let sample_rate = 48000;
let total_duration_s = 10;
let data_count = 100;
let vbr_outputs = [];
let cbr_outputs = [];
let cbr_encoder = new AudioEncoder({
error: e => {
assert_unreached('error: ' + e);
},
output: chunk => {
cbr_outputs.push(chunk);
}
});
let vbr_encoder = new AudioEncoder({
error: e => {
assert_unreached('error: ' + e);
},
output: chunk => {
vbr_outputs.push(chunk);
}
});
let config = {
codec: 'mp4a.40.2',
sampleRate: sample_rate,
numberOfChannels: 2,
bitrate: 192000, // 256kbit
};
let cbr_config = { ...config, bitrateMode: "constant" };
let vbr_config = { ...config, bitrateMode: "variable" };
let cbr_config_support = await AudioEncoder.isConfigSupported(cbr_config);
assert_implements_optional(cbr_config_support.supported, "AAC CBR not supported");
let vbr_config_support = await AudioEncoder.isConfigSupported(vbr_config);
assert_implements_optional(vbr_config_support.supported, "AAC VBR not supported");
// Configure one encoder with VBR and one CBR.
cbr_encoder.configure(cbr_config);
vbr_encoder.configure(vbr_config);
let timestamp_us = 0;
let data_duration_s = total_duration_s / data_count;
let data_length = data_duration_s * config.sampleRate;
for (let i = 0; i < data_count; i++) {
let data;
if (i == 0 || i == (data_count - 1)) {
// Send real data for the first and last 100ms.
data = make_audio_data(
timestamp_us, config.numberOfChannels, config.sampleRate,
data_length);
} else {
// Send silence for the rest of the 10s.
data = make_silent_audio_data(
timestamp_us, config.numberOfChannels, config.sampleRate,
data_length);
}
vbr_encoder.encode(data);
cbr_encoder.encode(data);
data.close();
timestamp_us += data_duration_s * 1_000_000;
}
await Promise.all([cbr_encoder.flush(), vbr_encoder.flush()])
cbr_encoder.close();
vbr_encoder.close();
let vbr_total_bytes = 0;
vbr_outputs.forEach(chunk => vbr_total_bytes += chunk.byteLength)
let cbr_total_bytes = 0;
cbr_outputs.forEach(chunk => cbr_total_bytes += chunk.byteLength)
// We'd like to confirm that the encoded size using VBR is less than CBR, but
// platforms without VBR support will silently revert to CBR (which is
// technically a subset of VBR).
assert_less_than_equal(vbr_total_bytes, cbr_total_bytes);
}, 'Test the AAC bitrateMode flag works.');