summaryrefslogtreecommitdiffstats
path: root/dom/media/webaudio/test/test_mediaDecoding.html
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/webaudio/test/test_mediaDecoding.html388
1 files changed, 388 insertions, 0 deletions
diff --git a/dom/media/webaudio/test/test_mediaDecoding.html b/dom/media/webaudio/test/test_mediaDecoding.html
new file mode 100644
index 0000000000..d796ef6add
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaDecoding.html
@@ -0,0 +1,388 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the decodeAudioData API and Resampling</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script type="text/javascript">
+
+// These routines have been copied verbatim from WebKit, and are used in order
+// to convert a memory buffer into a wave buffer.
+function writeString(s, a, offset) {
+ for (var i = 0; i < s.length; ++i) {
+ a[offset + i] = s.charCodeAt(i);
+ }
+}
+
+function writeInt16(n, a, offset) {
+ n = Math.floor(n);
+
+ var b1 = n & 255;
+ var b2 = (n >> 8) & 255;
+
+ a[offset + 0] = b1;
+ a[offset + 1] = b2;
+}
+
+function writeInt32(n, a, offset) {
+ n = Math.floor(n);
+ var b1 = n & 255;
+ var b2 = (n >> 8) & 255;
+ var b3 = (n >> 16) & 255;
+ var b4 = (n >> 24) & 255;
+
+ a[offset + 0] = b1;
+ a[offset + 1] = b2;
+ a[offset + 2] = b3;
+ a[offset + 3] = b4;
+}
+
+function writeAudioBuffer(audioBuffer, a, offset) {
+ var n = audioBuffer.length;
+ var channels = audioBuffer.numberOfChannels;
+
+ for (var i = 0; i < n; ++i) {
+ for (var k = 0; k < channels; ++k) {
+ var buffer = audioBuffer.getChannelData(k);
+ var sample = buffer[i] * 32768.0;
+
+ // Clip samples to the limitations of 16-bit.
+ // If we don't do this then we'll get nasty wrap-around distortion.
+ if (sample < -32768)
+ sample = -32768;
+ if (sample > 32767)
+ sample = 32767;
+
+ writeInt16(sample, a, offset);
+ offset += 2;
+ }
+ }
+}
+
+function createWaveFileData(audioBuffer) {
+ var frameLength = audioBuffer.length;
+ var numberOfChannels = audioBuffer.numberOfChannels;
+ var sampleRate = audioBuffer.sampleRate;
+ var bitsPerSample = 16;
+ var byteRate = sampleRate * numberOfChannels * bitsPerSample/8;
+ var blockAlign = numberOfChannels * bitsPerSample/8;
+ var wavDataByteLength = frameLength * numberOfChannels * 2; // 16-bit audio
+ var headerByteLength = 44;
+ var totalLength = headerByteLength + wavDataByteLength;
+
+ var waveFileData = new Uint8Array(totalLength);
+
+ var subChunk1Size = 16; // for linear PCM
+ var subChunk2Size = wavDataByteLength;
+ var chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
+
+ writeString("RIFF", waveFileData, 0);
+ writeInt32(chunkSize, waveFileData, 4);
+ writeString("WAVE", waveFileData, 8);
+ writeString("fmt ", waveFileData, 12);
+
+ writeInt32(subChunk1Size, waveFileData, 16); // SubChunk1Size (4)
+ writeInt16(1, waveFileData, 20); // AudioFormat (2)
+ writeInt16(numberOfChannels, waveFileData, 22); // NumChannels (2)
+ writeInt32(sampleRate, waveFileData, 24); // SampleRate (4)
+ writeInt32(byteRate, waveFileData, 28); // ByteRate (4)
+ writeInt16(blockAlign, waveFileData, 32); // BlockAlign (2)
+ writeInt32(bitsPerSample, waveFileData, 34); // BitsPerSample (4)
+
+ writeString("data", waveFileData, 36);
+ writeInt32(subChunk2Size, waveFileData, 40); // SubChunk2Size (4)
+
+ // Write actual audio data starting at offset 44.
+ writeAudioBuffer(audioBuffer, waveFileData, 44);
+
+ return waveFileData;
+}
+
+</script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// fuzzTolerance and fuzzToleranceMobile are used to determine fuzziness
+// thresholds. They're needed to make sure that we can deal with neglibible
+// differences in the binary buffer caused as a result of resampling the
+// audio. fuzzToleranceMobile is typically larger on mobile platforms since
+// we do fixed-point resampling as opposed to floating-point resampling on
+// those platforms.
+var files = [
+ // An ogg file, 44.1khz, mono
+ {
+ url: "ting-44.1k-1ch.ogg",
+ valid: true,
+ expectedUrl: "ting-44.1k-1ch.wav",
+ numberOfChannels: 1,
+ frames: 30592,
+ sampleRate: 44100,
+ duration: 0.693,
+ fuzzTolerance: 5,
+ fuzzToleranceMobile: 1284
+ },
+ // An ogg file, 44.1khz, stereo
+ {
+ url: "ting-44.1k-2ch.ogg",
+ valid: true,
+ expectedUrl: "ting-44.1k-2ch.wav",
+ numberOfChannels: 2,
+ frames: 30592,
+ sampleRate: 44100,
+ duration: 0.693,
+ fuzzTolerance: 6,
+ fuzzToleranceMobile: 2544
+ },
+ // An ogg file, 48khz, mono
+ {
+ url: "ting-48k-1ch.ogg",
+ valid: true,
+ expectedUrl: "ting-48k-1ch.wav",
+ numberOfChannels: 1,
+ frames: 33297,
+ sampleRate: 48000,
+ duration: 0.693,
+ fuzzTolerance: 5,
+ fuzzToleranceMobile: 1388
+ },
+ // An ogg file, 48khz, stereo
+ {
+ url: "ting-48k-2ch.ogg",
+ valid: true,
+ expectedUrl: "ting-48k-2ch.wav",
+ numberOfChannels: 2,
+ frames: 33297,
+ sampleRate: 48000,
+ duration: 0.693,
+ fuzzTolerance: 14,
+ fuzzToleranceMobile: 2752
+ },
+ // Make sure decoding a wave file results in the same buffer (for both the
+ // resampling and non-resampling cases)
+ {
+ url: "ting-44.1k-1ch.wav",
+ valid: true,
+ expectedUrl: "ting-44.1k-1ch.wav",
+ numberOfChannels: 1,
+ frames: 30592,
+ sampleRate: 44100,
+ duration: 0.693,
+ fuzzTolerance: 0,
+ fuzzToleranceMobile: 0
+ },
+ {
+ url: "ting-48k-1ch.wav",
+ valid: true,
+ expectedUrl: "ting-48k-1ch.wav",
+ numberOfChannels: 1,
+ frames: 33297,
+ sampleRate: 48000,
+ duration: 0.693,
+ fuzzTolerance: 0,
+ fuzzToleranceMobile: 0
+ },
+ // // A wave file
+ // //{ url: "24bit-44khz.wav", valid: true, expectedUrl: "24bit-44khz-expected.wav" },
+ // A non-audio file
+ { url: "invalid.txt", valid: false, sampleRate: 44100 },
+ // A webm file with no audio
+ { url: "noaudio.webm", valid: false, sampleRate: 48000 },
+ // A video ogg file with audio
+ {
+ url: "audio.ogv",
+ valid: true,
+ expectedUrl: "audio-expected.wav",
+ numberOfChannels: 2,
+ sampleRate: 44100,
+ frames: 47680,
+ duration: 1.0807,
+ fuzzTolerance: 106,
+ fuzzToleranceMobile: 3482
+ },
+ {
+ url: "nil-packet.ogg",
+ expectedUrl: null,
+ valid: true,
+ numberOfChannels: 2,
+ sampleRate: 48000,
+ frames: 18600,
+ duration: 0.3874,
+ }
+];
+
+// Returns true if the memory buffers are less different that |fuzz| bytes
+function fuzzyMemcmp(buf1, buf2, fuzz) {
+ var result = true;
+ var difference = 0;
+ is(buf1.length, buf2.length, "same length");
+ for (var i = 0; i < buf1.length; ++i) {
+ if (Math.abs(buf1[i] - buf2[i])) {
+ ++difference;
+ }
+ }
+ if (difference > fuzz) {
+ ok(false, "Expected at most " + fuzz + " bytes difference, found " + difference + " bytes");
+ }
+ return difference <= fuzz;
+}
+
+function getFuzzTolerance(test) {
+ var kIsMobile =
+ navigator.userAgent.includes("Mobile") || // b2g
+ navigator.userAgent.includes("Android"); // android
+ return kIsMobile ? test.fuzzToleranceMobile : test.fuzzTolerance;
+}
+
+function bufferIsSilent(buffer) {
+ for (var i = 0; i < buffer.length; ++i) {
+ if (buffer.getChannelData(0)[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function checkAudioBuffer(buffer, test) {
+ if (buffer.numberOfChannels != test.numberOfChannels) {
+ is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels");
+ return;
+ }
+ ok(Math.abs(buffer.duration - test.duration) < 1e-3, "Correct duration");
+ if (Math.abs(buffer.duration - test.duration) >= 1e-3) {
+ ok(false, "got: " + buffer.duration + ", expected: " + test.duration);
+ }
+ is(buffer.sampleRate, test.sampleRate, "Correct sample rate");
+ is(buffer.length, test.frames, "Correct length");
+
+ var wave = createWaveFileData(buffer);
+ if (test.expectedWaveData) {
+ ok(fuzzyMemcmp(wave, test.expectedWaveData, getFuzzTolerance(test)), "Received expected decoded data");
+ }
+}
+
+function checkResampledBuffer(buffer, test, callback) {
+ if (buffer.numberOfChannels != test.numberOfChannels) {
+ is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels");
+ return;
+ }
+ ok(Math.abs(buffer.duration - test.duration) < 1e-3, "Correct duration");
+ if (Math.abs(buffer.duration - test.duration) >= 1e-3) {
+ ok(false, "got: " + buffer.duration + ", expected: " + test.duration);
+ }
+ // Take into account the resampling when checking the size
+ var expectedLength = test.frames * buffer.sampleRate / test.sampleRate;
+ SimpleTest.ok(
+ Math.abs(buffer.length - expectedLength) < 1.0,
+ "Correct length - got " + buffer.length +
+ ", expected about " + expectedLength
+ );
+
+ // Playback the buffer in the original context, to resample back to the
+ // original rate and compare with the decoded buffer without resampling.
+ cx = test.nativeContext;
+ var expected = cx.createBufferSource();
+ expected.buffer = test.expectedBuffer;
+ expected.start();
+ var inverse = cx.createGain();
+ inverse.gain.value = -1;
+ expected.connect(inverse);
+ inverse.connect(cx.destination);
+ var resampled = cx.createBufferSource();
+ resampled.buffer = buffer;
+ resampled.start();
+ // This stop should do nothing, but it tests for bug 937475
+ resampled.stop(test.frames / cx.sampleRate);
+ resampled.connect(cx.destination);
+ cx.oncomplete = function(e) {
+ ok(!bufferIsSilent(e.renderedBuffer), "Expect buffer not silent");
+ // Resampling will lose the highest frequency components, so we should
+ // pass the difference through a low pass filter. However, either the
+ // input files don't have significant high frequency components or the
+ // tolerance in compareBuffers() is too high to detect them.
+ compareBuffers(e.renderedBuffer,
+ cx.createBuffer(test.numberOfChannels,
+ test.frames, test.sampleRate));
+ callback();
+ }
+ cx.startRendering();
+}
+
+function runResampling(test, response, callback) {
+ var sampleRate = test.sampleRate == 44100 ? 48000 : 44100;
+ var cx = new OfflineAudioContext(1, 1, sampleRate);
+ cx.decodeAudioData(response, function onSuccess(asyncResult) {
+ is(asyncResult.sampleRate, sampleRate, "Correct sample rate");
+
+ checkResampledBuffer(asyncResult, test, callback);
+ }, function onFailure() {
+ ok(false, "Expected successful decode with resample");
+ callback();
+ });
+}
+
+function runTest(test, response, callback) {
+ // We need to copy the array here, because decodeAudioData will detach the
+ // array's buffer.
+ var compressedAudio = response.slice(0);
+ var expectCallback = false;
+ var cx = new OfflineAudioContext(test.numberOfChannels || 1,
+ test.frames || 1, test.sampleRate);
+ cx.decodeAudioData(response, function onSuccess(asyncResult) {
+ ok(expectCallback, "Success callback should fire asynchronously");
+ ok(test.valid, "Did expect success for test " + test.url);
+
+ checkAudioBuffer(asyncResult, test);
+
+ test.expectedBuffer = asyncResult;
+ test.nativeContext = cx;
+ runResampling(test, compressedAudio, callback);
+ }, function onFailure(e) {
+ ok(e instanceof DOMException, "We want to see an exception here");
+ is(e.name, "EncodingError", "Exception name matches");
+ ok(expectCallback, "Failure callback should fire asynchronously");
+ ok(!test.valid, "Did expect failure for test " + test.url);
+ callback();
+ });
+ expectCallback = true;
+}
+
+function loadTest(test, callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", test.url, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onload = function() {
+ if (!test.expectedUrl) {
+ runTest(test, xhr.response, callback);
+ return;
+ }
+ var getExpected = new XMLHttpRequest();
+ getExpected.open("GET", test.expectedUrl, true);
+ getExpected.responseType = "arraybuffer";
+ getExpected.onload = function() {
+ test.expectedWaveData = new Uint8Array(getExpected.response);
+ runTest(test, xhr.response, callback);
+ };
+ getExpected.send();
+ };
+ xhr.send();
+}
+
+function loadNextTest() {
+ if (files.length) {
+ loadTest(files.shift(), loadNextTest);
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+loadNextTest();
+
+</script>
+</pre>
+</body>
+</html>