diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/webaudio/test/test_mediaDecoding.html | 388 |
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> |