summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webcodecs/audioDecoder-codec-specific.https.any.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webcodecs/audioDecoder-codec-specific.https.any.js')
-rw-r--r--testing/web-platform/tests/webcodecs/audioDecoder-codec-specific.https.any.js371
1 files changed, 371 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webcodecs/audioDecoder-codec-specific.https.any.js b/testing/web-platform/tests/webcodecs/audioDecoder-codec-specific.https.any.js
new file mode 100644
index 0000000000..92513be087
--- /dev/null
+++ b/testing/web-platform/tests/webcodecs/audioDecoder-codec-specific.https.any.js
@@ -0,0 +1,371 @@
+// META: global=window,dedicatedworker
+// META: script=/webcodecs/utils.js
+// META: variant=?adts_aac
+// META: variant=?mp4_aac
+// META: variant=?mp3
+// META: variant=?opus
+// META: variant=?pcm_alaw
+// META: variant=?pcm_mulaw
+
+const ADTS_AAC_DATA = {
+ src: 'sfx.adts',
+ config: {
+ codec: 'mp4a.40.2',
+ sampleRate: 48000,
+ numberOfChannels: 1,
+ },
+ chunks: [
+ {offset: 0, size: 248}, {offset: 248, size: 280}, {offset: 528, size: 258},
+ {offset: 786, size: 125}, {offset: 911, size: 230},
+ {offset: 1141, size: 148}, {offset: 1289, size: 224},
+ {offset: 1513, size: 166}, {offset: 1679, size: 216},
+ {offset: 1895, size: 183}
+ ],
+ duration: 24000
+};
+
+const MP3_DATA = {
+ src: 'sfx.mp3',
+ config: {
+ codec: 'mp3',
+ sampleRate: 48000,
+ numberOfChannels: 1,
+ },
+ chunks: [
+ {offset: 333, size: 288}, {offset: 621, size: 288},
+ {offset: 909, size: 288}, {offset: 1197, size: 288},
+ {offset: 1485, size: 288}, {offset: 1773, size: 288},
+ {offset: 2061, size: 288}, {offset: 2349, size: 288},
+ {offset: 2637, size: 288}, {offset: 2925, size: 288}
+ ],
+ duration: 24000
+};
+
+const MP4_AAC_DATA = {
+ src: 'sfx-aac.mp4',
+ config: {
+ codec: 'mp4a.40.2',
+ sampleRate: 48000,
+ numberOfChannels: 1,
+ description: {offset: 2552, size: 5},
+ },
+ chunks: [
+ {offset: 44, size: 241},
+ {offset: 285, size: 273},
+ {offset: 558, size: 251},
+ {offset: 809, size: 118},
+ {offset: 927, size: 223},
+ {offset: 1150, size: 141},
+ {offset: 1291, size: 217},
+ {offset: 1508, size: 159},
+ {offset: 1667, size: 209},
+ {offset: 1876, size: 176},
+ ],
+ duration: 21333
+};
+
+const OPUS_DATA = {
+ src: 'sfx-opus.ogg',
+ config: {
+ codec: 'opus',
+ sampleRate: 48000,
+ numberOfChannels: 1,
+ description: {offset: 28, size: 19},
+ },
+ chunks: [
+ {offset: 185, size: 450}, {offset: 635, size: 268},
+ {offset: 903, size: 285}, {offset: 1188, size: 296},
+ {offset: 1484, size: 287}, {offset: 1771, size: 308},
+ {offset: 2079, size: 289}, {offset: 2368, size: 286},
+ {offset: 2654, size: 296}, {offset: 2950, size: 294}
+ ],
+ duration: 20000
+};
+
+const PCM_ALAW_DATA = {
+ src: 'sfx-alaw.wav',
+ config: {
+ codec: 'alaw',
+ sampleRate: 48000,
+ numberOfChannels: 1,
+ },
+ // Any arbitrary grouping should work.
+ chunks: [
+ {offset: 0, size: 2048}, {offset: 2048, size: 2048},
+ {offset: 4096, size: 2048}, {offset: 6144, size: 2048},
+ {offset: 8192, size: 2048}, {offset: 10240, size: 92}
+ ],
+ duration: 35555
+};
+
+const PCM_MULAW_DATA = {
+ src: 'sfx-mulaw.wav',
+ config: {
+ codec: 'ulaw',
+ sampleRate: 48000,
+ numberOfChannels: 1,
+ },
+
+ // Any arbitrary grouping should work.
+ chunks: [
+ {offset: 0, size: 2048}, {offset: 2048, size: 2048},
+ {offset: 4096, size: 2048}, {offset: 6144, size: 2048},
+ {offset: 8192, size: 2048}, {offset: 10240, size: 92}
+ ],
+ duration: 35555
+};
+
+// Allows mutating `callbacks` after constructing the AudioDecoder, wraps calls
+// in t.step().
+function createAudioDecoder(t, callbacks) {
+ return new AudioDecoder({
+ output(frame) {
+ if (callbacks && callbacks.output) {
+ t.step(() => callbacks.output(frame));
+ } else {
+ t.unreached_func('unexpected output()');
+ }
+ },
+ error(e) {
+ if (callbacks && callbacks.error) {
+ t.step(() => callbacks.error(e));
+ } else {
+ t.unreached_func('unexpected error()');
+ }
+ }
+ });
+}
+
+// Create a view of an ArrayBuffer.
+function view(buffer, {offset, size}) {
+ return new Uint8Array(buffer, offset, size);
+}
+
+let CONFIG = null;
+let CHUNK_DATA = null;
+let CHUNKS = null;
+promise_setup(async () => {
+ const data = {
+ '?adts_aac': ADTS_AAC_DATA,
+ '?mp3': MP3_DATA,
+ '?mp4_aac': MP4_AAC_DATA,
+ '?opus': OPUS_DATA,
+ '?pcm_alaw': PCM_ALAW_DATA,
+ '?pcm_mulaw': PCM_MULAW_DATA,
+ }[location.search];
+
+ // Don't run any tests if the codec is not supported.
+ assert_equals("function", typeof AudioDecoder.isConfigSupported);
+ let supported = false;
+ try {
+ const support = await AudioDecoder.isConfigSupported({
+ codec: data.config.codec,
+ sampleRate: data.config.sampleRate,
+ numberOfChannels: data.config.numberOfChannels
+ });
+ supported = support.supported;
+ } catch (e) {
+ }
+ assert_implements_optional(supported, data.config.codec + ' unsupported');
+
+ // Fetch the media data and prepare buffers.
+ const response = await fetch(data.src);
+ const buf = await response.arrayBuffer();
+
+ CONFIG = {...data.config};
+ if (data.config.description) {
+ CONFIG.description = view(buf, data.config.description);
+ }
+
+ CHUNK_DATA = data.chunks.map((chunk, i) => view(buf, chunk));
+
+ CHUNKS = CHUNK_DATA.map((encodedData, i) => new EncodedAudioChunk({
+ type: 'key',
+ timestamp: i * data.duration,
+ duration: data.duration,
+ data: encodedData
+ }));
+});
+
+promise_test(t => {
+ return AudioDecoder.isConfigSupported(CONFIG);
+}, 'Test isConfigSupported()');
+
+promise_test(t => {
+ // Define a valid config that includes a hypothetical 'futureConfigFeature',
+ // which is not yet recognized by the User Agent.
+ const validConfig = {
+ ...CONFIG,
+ futureConfigFeature: 'foo',
+ };
+
+ // The UA will evaluate validConfig as being "valid", ignoring the
+ // `futureConfigFeature` it doesn't recognize.
+ return AudioDecoder.isConfigSupported(validConfig).then((decoderSupport) => {
+ // AudioDecoderSupport must contain the following properites.
+ assert_true(decoderSupport.hasOwnProperty('supported'));
+ assert_true(decoderSupport.hasOwnProperty('config'));
+
+ // AudioDecoderSupport.config must not contain unrecognized properties.
+ assert_false(decoderSupport.config.hasOwnProperty('futureConfigFeature'));
+
+ // AudioDecoderSupport.config must contiain the recognized properties.
+ assert_equals(decoderSupport.config.codec, validConfig.codec);
+ assert_equals(decoderSupport.config.sampleRate, validConfig.sampleRate);
+ assert_equals(
+ decoderSupport.config.numberOfChannels, validConfig.numberOfChannels);
+
+ if (validConfig.description) {
+ // The description must be copied.
+ assert_false(
+ decoderSupport.config.description === validConfig.description,
+ 'description is unique');
+ assert_array_equals(
+ new Uint8Array(decoderSupport.config.description, 0),
+ new Uint8Array(validConfig.description, 0), 'description');
+ } else {
+ assert_false(
+ decoderSupport.config.hasOwnProperty('description'), 'description');
+ }
+ });
+}, 'Test that AudioDecoder.isConfigSupported() returns a parsed configuration');
+
+promise_test(async t => {
+ const decoder = createAudioDecoder(t);
+ decoder.configure(CONFIG);
+ assert_equals(decoder.state, 'configured', 'state');
+}, 'Test configure()');
+
+promise_test(t => {
+ const decoder = createAudioDecoder(t);
+ return testClosedCodec(t, decoder, CONFIG, CHUNKS[0]);
+}, 'Verify closed AudioDecoder operations');
+
+promise_test(async t => {
+ const callbacks = {};
+ const decoder = createAudioDecoder(t, callbacks);
+
+ let outputs = 0;
+ callbacks.output = frame => {
+ outputs++;
+ frame.close();
+ };
+
+ decoder.configure(CONFIG);
+ CHUNKS.forEach(chunk => {
+ decoder.decode(chunk);
+ });
+
+ await decoder.flush();
+ assert_equals(outputs, CHUNKS.length, 'outputs');
+}, 'Test decoding');
+
+promise_test(async t => {
+ const callbacks = {};
+ const decoder = createAudioDecoder(t, callbacks);
+
+ let outputs = 0;
+ callbacks.output = frame => {
+ outputs++;
+ frame.close();
+ };
+
+ decoder.configure(CONFIG);
+ decoder.decode(new EncodedAudioChunk(
+ {type: 'key', timestamp: -42, data: CHUNK_DATA[0]}));
+
+ await decoder.flush();
+ assert_equals(outputs, 1, 'outputs');
+}, 'Test decoding a with negative timestamp');
+
+promise_test(async t => {
+ const callbacks = {};
+ const decoder = createAudioDecoder(t, callbacks);
+
+ let outputs = 0;
+ callbacks.output = frame => {
+ outputs++;
+ frame.close();
+ };
+
+ decoder.configure(CONFIG);
+ decoder.decode(CHUNKS[0]);
+
+ await decoder.flush();
+ assert_equals(outputs, 1, 'outputs');
+
+ decoder.decode(CHUNKS[0]);
+ await decoder.flush();
+ assert_equals(outputs, 2, 'outputs');
+}, 'Test decoding after flush');
+
+promise_test(async t => {
+ const callbacks = {};
+ const decoder = createAudioDecoder(t, callbacks);
+
+ decoder.configure(CONFIG);
+ decoder.decode(CHUNKS[0]);
+ decoder.decode(CHUNKS[1]);
+ const flushDone = decoder.flush();
+
+ // Wait for the first output, then reset.
+ let outputs = 0;
+ await new Promise(resolve => {
+ callbacks.output = frame => {
+ outputs++;
+ assert_equals(outputs, 1, 'outputs');
+ decoder.reset();
+ frame.close();
+ resolve();
+ };
+ });
+
+ // Flush should have been synchronously rejected.
+ await promise_rejects_dom(t, 'AbortError', flushDone);
+
+ assert_equals(outputs, 1, 'outputs');
+}, 'Test reset during flush');
+
+promise_test(async t => {
+ const callbacks = {};
+ const decoder = createAudioDecoder(t, callbacks);
+
+ // No decodes yet.
+ assert_equals(decoder.decodeQueueSize, 0);
+
+ decoder.configure(CONFIG);
+
+ // Still no decodes.
+ assert_equals(decoder.decodeQueueSize, 0);
+
+ let lastDequeueSize = Infinity;
+ decoder.ondequeue = () => {
+ assert_greater_than(lastDequeueSize, 0, "Dequeue event after queue empty");
+ assert_greater_than(lastDequeueSize, decoder.decodeQueueSize,
+ "Dequeue event without decreased queue size");
+ lastDequeueSize = decoder.decodeQueueSize;
+ };
+
+ for (let chunk of CHUNKS)
+ decoder.decode(chunk);
+
+ assert_greater_than_equal(decoder.decodeQueueSize, 0);
+ assert_less_than_equal(decoder.decodeQueueSize, CHUNKS.length);
+
+ await decoder.flush();
+ // We can guarantee that all decodes are processed after a flush.
+ assert_equals(decoder.decodeQueueSize, 0);
+ // Last dequeue event should fire when the queue is empty.
+ assert_equals(lastDequeueSize, 0);
+
+ // Reset this to Infinity to track the decline of queue size for this next
+ // batch of decodes.
+ lastDequeueSize = Infinity;
+
+ for (let chunk of CHUNKS)
+ decoder.decode(chunk);
+
+ assert_greater_than_equal(decoder.decodeQueueSize, 0);
+ decoder.reset();
+ assert_equals(decoder.decodeQueueSize, 0);
+}, 'AudioDecoder decodeQueueSize test');