<!DOCTYPE html>
<!--
Check that AudioDecoder can decode output of AudioEncoder
-->
<title>Encode test</title>
<script src="webcodecs_common.js"></script>
<script type="text/javascript">
'use strict';
function make_audio_data(timestamp, channels, sampleRate, frames) {
let data = new Float32Array(frames*channels);
// This generates samples in a planar format.
for (var channel = 0; channel < channels; channel++) {
let hz = 100 + channel * 50; // sound frequency
let base_index = channel * frames;
for (var i = 0; i < frames; i++) {
let t = (i / sampleRate) * hz * (Math.PI * 2);
data[base_index + i] = Math.sin(t);
}
}
return new AudioData({
timestamp: timestamp,
data: data,
numberOfChannels: channels,
numberOfFrames: frames,
sampleRate: sampleRate,
format: "f32-planar",
});
}
async function main(args) {
let errors = 0;
let output_count = 0;
let config = {
codec: args.codec,
sampleRate: args.sample_rate,
numberOfChannels: args.channels,
bitrate: 96000
};
if (args.aac_format) {
config.aac = {
format : args.aac_format
};
}
let decoder_config = null;
let supported = false;
try {
supported = (await AudioEncoder.isConfigSupported(config)).supported;
} catch (e) {}
if (!supported) {
TEST.skip('Unsupported codec: ' + args.codec);
return;
}
let total_duration_s = 1.0;
let data_count = 20; // each chunk is 500 ms
let outputs = [];
let init = {
error: e => {
errors++;
TEST.log(e);
},
output: (chunk, md) => {
if (md.decoderConfig) {
decoder_config = md.decoderConfig;
}
outputs.push(chunk);
}
};
// Make some sin() waves and encode them
let encoder = new AudioEncoder(init);
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;
if (i % 10 == 0) {
// Inserting flushes in the middle, to see how the encoder handles
// more inputs coming after flush()
await encoder.flush();
}
}
await encoder.flush();
encoder.close();
// Maximum amount of padding from supported encoders. Applied twice, since the
// flush above can also introduce padding.
timestamp_us += 2 * (2112 / config.sampleRate) * 1_000_000;
TEST.assert(decoder_config != null, "No decoder config");
if (args.aac_format == "adts") {
TEST.assert(decoder_config.description == null, "ADTS should carry desc");
} else if (args.aac_format == "aac") {
TEST.assert(decoder_config.description != null, "AAC should carry desc");
TEST.assert(decoder_config.description.byteLength > 1,
"AAC desc is too short");
}
TEST.assert(outputs.length > 0, "no outputs");
TEST.assert(outputs[0].timestamp == 0, "first chunk timestamp non zero");
let total_encoded_duration = 0
for (let chunk of outputs) {
TEST.assert(chunk.byteLength > 0, "chunk is empty");
TEST.assert(timestamp_us >= chunk.timestamp,
`chunk timestamp is too small. ${timestamp_us} vs ${chunk.timestamp}`);
TEST.assert(chunk.duration >= 0, "chunk duration is zero");
total_encoded_duration += chunk.duration;
let buf = new ArrayBuffer(chunk.byteLength);
chunk.copyTo(buf);
if (args.aac_format == "adts") {
let adts_header = new DataView(buf).getUint8(0);
TEST.assert(adts_header == 0xff, "Incorrect ADTS header");
}
}
// The total duration might be padded with silence.
TEST.assert(
total_encoded_duration >= total_duration_s * 1_000_000,
`Unexpected encoded duration: ${total_encoded_duration} us` );
// Decode output and check that the output still makes sense
let audio_buffers = [];
let decoder_init = {
error: e => {
errors++;
TEST.log(e);
},
output: (audio_data) => {
let buffer = new AudioBuffer({
length: audio_data.numberOfFrames,
numberOfChannels: audio_data.numberOfChannels,
sampleRate: audio_data.sampleRate
});
for (let i = 0; i < audio_data.numberOfChannels; i++) {
audio_data.copyTo(buffer.getChannelData(i), {
planeIndex : i,
frameOffset : 0,
frameCount : audio_data.numberOfFrames,
format : "f32-planar"
});
}
audio_buffers.push(buffer);
audio_data.close();
}
};
let decoder = new AudioDecoder(decoder_init)
decoder.configure(decoder_config);
for (let chunk of outputs) {
decoder.decode(chunk);
}
await decoder.flush();
let total_decoded_duration_s = 0.0;
for (let buffer of audio_buffers) {
total_decoded_duration_s += buffer.duration;
TEST.assert(buffer.numberOfChannels == config.numberOfChannels,
"unexpected number of channels");
}
TEST.assert(errors == 0, "errors: " + errors);
// The total duration might be padded with silence, but no too much.
TEST.assert(
total_decoded_duration_s >= total_duration_s,
`Decoded duration is too short: ${total_decoded_duration_s} s`);
TEST.assert(
total_decoded_duration_s < 2 * total_duration_s,
`Decoded duration is too long: ${total_decoded_duration_s} s`);
}
</script>