280 lines
8.1 KiB
JavaScript
280 lines
8.1 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.');
|
|
|
|
|
|
// 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.');
|