blob: 0a8b39c9204e4617f1f5ddf5d89bd0a9f3144d4c [file] [log] [blame]
// META: global=window
// META: script=/webcodecs/utils.js
function make_audio_frame(timestamp, channels, sampleRate, length) {
let buffer = new AudioBuffer({
length: length,
numberOfChannels: channels,
sampleRate: sampleRate
});
for (var channel = 0; channel < buffer.numberOfChannels; channel++) {
// This gives us the actual array that contains the data
var array = buffer.getChannelData(channel);
let hz = 100 + channel * 50; // sound frequency
for (var i = 0; i < array.length; i++) {
let t = (i / sampleRate) * hz * (Math.PI * 2);
array[i] = Math.sin(t);
}
}
return new AudioFrame({
timestamp: timestamp,
buffer: buffer
});
}
// Merge all audio buffers into a new big one with all the data.
function join_buffers(buffers) {
assert_greater_than_equal(buffers.length, 0);
let total_length = 0;
let base_buffer = buffers[0];
for (const buffer of buffers) {
assert_not_equals(buffer, null);
assert_equals(buffer.sampleRate, base_buffer.sampleRate);
assert_equals(buffer.numberOfChannels, base_buffer.numberOfChannels);
total_length += buffer.length;
}
let result = new AudioBuffer({
length: total_length,
numberOfChannels: base_buffer.numberOfChannels,
sampleRate: base_buffer.sampleRate
});
for (let i = 0; i < base_buffer.numberOfChannels; i++) {
let channel = result.getChannelData(i);
let position = 0;
for (const buffer of buffers) {
channel.set(buffer.getChannelData(i), position);
position += buffer.length;
}
assert_equals(position, total_length);
}
return result;
}
function clone_frame(frame) {
return new AudioFrame({
timestamp: frame.timestamp,
buffer: join_buffers([frame.buffer])
});
}
promise_test(async t => {
let sample_rate = 48000;
let total_duration_s = 2;
let frame_count = 20;
let outputs = [];
let init = {
error: e => {
assert_unreached("error: " + e);
},
output: chunk => {
outputs.push(chunk);
}
};
let encoder = new AudioEncoder(init);
assert_equals(encoder.state, "unconfigured");
let config = {
codec: 'opus',
sampleRate: sample_rate,
numberOfChannels: 2,
bitrate: 256000 //256kbit
};
encoder.configure(config);
let timestamp_us = 0;
for (let i = 0; i < frame_count; i++) {
let frame_duration_s = total_duration_s / frame_count;
let length = frame_duration_s * config.sampleRate;
let frame = make_audio_frame(timestamp_us, config.numberOfChannels,
config.sampleRate, length);
encoder.encode(frame);
timestamp_us += frame_duration_s * 1_000_000;
}
await encoder.flush();
encoder.close();
assert_greater_than_equal(outputs.length, frame_count);
assert_equals(outputs[0].timestamp, 0, "first chunk timestamp");
for (chunk of outputs) {
assert_greater_than(chunk.data.byteLength, 0);
assert_greater_than(timestamp_us, chunk.timestamp);
}
}, 'Simple audio encoding');
async function checkEncodingError(config, good_frames, bad_frame) {
let error = null;
let outputs = 0;
let init = {
error: e => {
error = e;
},
output: chunk => {
outputs++;
}
};
let encoder = new AudioEncoder(init);
encoder.configure(config);
for (let frame of good_frames) {
encoder.encode(frame);
}
await encoder.flush();
let txt_config = "sampleRate: " + config.sampleRate
+ " numberOfChannels: " + config.numberOfChannels;
assert_equals(error, null, txt_config);
assert_greater_than(outputs, 0);
encoder.encode(bad_frame);
await encoder.flush().catch(() => {});
assert_not_equals(error, null, txt_config);
}
function channelNumberVariationTests() {
let sample_rate = 48000;
for (let channels = 1; channels < 12; channels++) {
let config = {
codec: 'opus',
sampleRate: sample_rate,
numberOfChannels: channels,
bitrate: 128000
};
let ts = 0;
let length = sample_rate / 10;
let frame1 = make_audio_frame(ts, channels, sample_rate, length);
ts += Math.floor(frame1.buffer.duration / 1000000);
let frame2 = make_audio_frame(ts, channels, sample_rate, length);
ts += Math.floor(frame2.buffer.duration / 1000000);
let bad_frame = make_audio_frame(ts, channels + 1, sample_rate, length);
promise_test(async t =>
checkEncodingError(config, [frame1, frame2], bad_frame),
"Channel number variation: " + channels);
}
}
channelNumberVariationTests();
function sampleRateVariationTests() {
let channels = 1
for (let sample_rate = 3000; sample_rate < 96000; sample_rate += 10000) {
let config = {
codec: 'opus',
sampleRate: sample_rate,
numberOfChannels: channels,
bitrate: 128000
};
let ts = 0;
let length = sample_rate / 10;
let frame1 = make_audio_frame(ts, channels, sample_rate, length);
ts += Math.floor(frame1.buffer.duration / 1000000);
let frame2 = make_audio_frame(ts, channels, sample_rate, length);
ts += Math.floor(frame2.buffer.duration / 1000000);
let bad_frame = make_audio_frame(ts, channels, sample_rate + 333, length);
promise_test(async t =>
checkEncodingError(config, [frame1, frame2], bad_frame),
"Sample rate variation: " + sample_rate);
}
}
sampleRateVariationTests();
promise_test(async t => {
let sample_rate = 48000;
let total_duration_s = 2;
let frame_count = 20;
let input_frames = [];
let output_frames = [];
let decoder_init = {
error: t.unreached_func("Decode error"),
output: frame => {
output_frames.push(frame);
}
};
let decoder = new AudioDecoder(decoder_init);
let encoder_init = {
error: t.unreached_func("Encoder error"),
output: (chunk, config) => {
if (config)
decoder.configure(config);
decoder.decode(chunk);
}
};
let encoder = new AudioEncoder(encoder_init);
let config = {
codec: 'opus',
sampleRate: sample_rate,
numberOfChannels: 2,
bitrate: 256000, //256kbit
};
encoder.configure(config);
let timestamp_us = 0;
const frame_duration_s = total_duration_s / frame_count;
const frame_length = frame_duration_s * config.sampleRate;
for (let i = 0; i < frame_count; i++) {
let frame = make_audio_frame(timestamp_us, config.numberOfChannels,
config.sampleRate, frame_length);
input_frames.push(clone_frame(frame));
encoder.encode(frame);
timestamp_us += frame_duration_s * 1_000_000;
}
await encoder.flush();
encoder.close();
await decoder.flush();
decoder.close();
let total_input = join_buffers(input_frames.map(f => f.buffer));
let total_output = join_buffers(output_frames.map(f => f.buffer));
assert_equals(total_output.numberOfChannels, 2);
assert_equals(total_output.sampleRate, sample_rate);
// Output can be slightly longer that the input due to padding
assert_greater_than_equal(total_output.length, total_input.length);
assert_greater_than_equal(total_output.duration, total_duration_s);
assert_approx_equals(total_output.duration, total_duration_s, 0.1);
// Compare waveform before and after encoding
for (let channel = 0; channel < total_input.numberOfChannels; channel++) {
let input_data = total_input.getChannelData(channel);
let output_data = total_output.getChannelData(channel);
for (let i = 0; i < total_input.length; i++) {
assert_approx_equals(input_data[i], output_data[i], 0.5,
"Difference between input and output is too large."
+ " index: " + i
+ " input: " + input_data[i]
+ " output: " + output_data[i]);
}
}
}, 'Encoding and decoding');
promise_test(async t => {
let output_count = 0;
let encoder_config = {
codec: 'opus',
sampleRate: 24000,
numberOfChannels: 1,
bitrate: 96000
};
let decoder_config = null;
let init = {
error: t.unreached_func("Encoder error"),
output: (chunk, config) => {
// Only the first invocation of the output callback is supposed to have
// a |config| in it.
output_count++;
if (output_count == 1) {
assert_equals(typeof config, "object");
decoder_config = config;
} else {
assert_equals(config, undefined);
}
}
};
let encoder = new AudioEncoder(init);
encoder.configure(encoder_config);
let long_frame = make_audio_frame(0, encoder_config.numberOfChannels,
encoder_config.sampleRate, encoder_config.sampleRate);
encoder.encode(clone_frame(long_frame));
await encoder.flush();
// Long frame produced more than one output, and we've got decoder_config
assert_greater_than(output_count, 1);
assert_not_equals(decoder_config, null);
assert_equals(decoder_config.codec, encoder_config.codec);
assert_equals(decoder_config.sampleRate, encoder_config.sampleRate);
assert_equals(decoder_config.numberOfChannels, encoder_config.numberOfChannels);
// Check that description start with 'Opus'
let extra_data = new Uint8Array(decoder_config.description);
assert_equals(extra_data[0], 0x4f);
assert_equals(extra_data[1], 0x70);
assert_equals(extra_data[2], 0x75);
assert_equals(extra_data[3], 0x73);
decoder_config = null;
output_count = 0;
encoder_config.bitrate = 256000;
encoder.configure(encoder_config);
encoder.encode(clone_frame(long_frame));
await encoder.flush();
// After reconfiguring encoder should produce decoder config again
assert_greater_than(output_count, 1);
assert_not_equals(decoder_config, null);
assert_not_equals(decoder_config.description, null);
encoder.close();
}, "Emit decoder config and extra data.");