content/media/webaudio/test/test_mediaDecoding.html

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 <!DOCTYPE HTML>
michael@0 2 <html>
michael@0 3 <head>
michael@0 4 <title>Test the decodeAudioData API and Resampling</title>
michael@0 5 <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
michael@0 6 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
michael@0 7 </head>
michael@0 8 <body>
michael@0 9 <pre id="test">
michael@0 10 <script src="webaudio.js" type="text/javascript"></script>
michael@0 11 <script type="text/javascript">
michael@0 12
michael@0 13 // These routines have been copied verbatim from WebKit, and are used in order
michael@0 14 // to convert a memory buffer into a wave buffer.
michael@0 15 function writeString(s, a, offset) {
michael@0 16 for (var i = 0; i < s.length; ++i) {
michael@0 17 a[offset + i] = s.charCodeAt(i);
michael@0 18 }
michael@0 19 }
michael@0 20
michael@0 21 function writeInt16(n, a, offset) {
michael@0 22 n = Math.floor(n);
michael@0 23
michael@0 24 var b1 = n & 255;
michael@0 25 var b2 = (n >> 8) & 255;
michael@0 26
michael@0 27 a[offset + 0] = b1;
michael@0 28 a[offset + 1] = b2;
michael@0 29 }
michael@0 30
michael@0 31 function writeInt32(n, a, offset) {
michael@0 32 n = Math.floor(n);
michael@0 33 var b1 = n & 255;
michael@0 34 var b2 = (n >> 8) & 255;
michael@0 35 var b3 = (n >> 16) & 255;
michael@0 36 var b4 = (n >> 24) & 255;
michael@0 37
michael@0 38 a[offset + 0] = b1;
michael@0 39 a[offset + 1] = b2;
michael@0 40 a[offset + 2] = b3;
michael@0 41 a[offset + 3] = b4;
michael@0 42 }
michael@0 43
michael@0 44 function writeAudioBuffer(audioBuffer, a, offset) {
michael@0 45 var n = audioBuffer.length;
michael@0 46 var channels = audioBuffer.numberOfChannels;
michael@0 47
michael@0 48 for (var i = 0; i < n; ++i) {
michael@0 49 for (var k = 0; k < channels; ++k) {
michael@0 50 var buffer = audioBuffer.getChannelData(k);
michael@0 51 var sample = buffer[i] * 32768.0;
michael@0 52
michael@0 53 // Clip samples to the limitations of 16-bit.
michael@0 54 // If we don't do this then we'll get nasty wrap-around distortion.
michael@0 55 if (sample < -32768)
michael@0 56 sample = -32768;
michael@0 57 if (sample > 32767)
michael@0 58 sample = 32767;
michael@0 59
michael@0 60 writeInt16(sample, a, offset);
michael@0 61 offset += 2;
michael@0 62 }
michael@0 63 }
michael@0 64 }
michael@0 65
michael@0 66 function createWaveFileData(audioBuffer) {
michael@0 67 var frameLength = audioBuffer.length;
michael@0 68 var numberOfChannels = audioBuffer.numberOfChannels;
michael@0 69 var sampleRate = audioBuffer.sampleRate;
michael@0 70 var bitsPerSample = 16;
michael@0 71 var byteRate = sampleRate * numberOfChannels * bitsPerSample/8;
michael@0 72 var blockAlign = numberOfChannels * bitsPerSample/8;
michael@0 73 var wavDataByteLength = frameLength * numberOfChannels * 2; // 16-bit audio
michael@0 74 var headerByteLength = 44;
michael@0 75 var totalLength = headerByteLength + wavDataByteLength;
michael@0 76
michael@0 77 var waveFileData = new Uint8Array(totalLength);
michael@0 78
michael@0 79 var subChunk1Size = 16; // for linear PCM
michael@0 80 var subChunk2Size = wavDataByteLength;
michael@0 81 var chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
michael@0 82
michael@0 83 writeString("RIFF", waveFileData, 0);
michael@0 84 writeInt32(chunkSize, waveFileData, 4);
michael@0 85 writeString("WAVE", waveFileData, 8);
michael@0 86 writeString("fmt ", waveFileData, 12);
michael@0 87
michael@0 88 writeInt32(subChunk1Size, waveFileData, 16); // SubChunk1Size (4)
michael@0 89 writeInt16(1, waveFileData, 20); // AudioFormat (2)
michael@0 90 writeInt16(numberOfChannels, waveFileData, 22); // NumChannels (2)
michael@0 91 writeInt32(sampleRate, waveFileData, 24); // SampleRate (4)
michael@0 92 writeInt32(byteRate, waveFileData, 28); // ByteRate (4)
michael@0 93 writeInt16(blockAlign, waveFileData, 32); // BlockAlign (2)
michael@0 94 writeInt32(bitsPerSample, waveFileData, 34); // BitsPerSample (4)
michael@0 95
michael@0 96 writeString("data", waveFileData, 36);
michael@0 97 writeInt32(subChunk2Size, waveFileData, 40); // SubChunk2Size (4)
michael@0 98
michael@0 99 // Write actual audio data starting at offset 44.
michael@0 100 writeAudioBuffer(audioBuffer, waveFileData, 44);
michael@0 101
michael@0 102 return waveFileData;
michael@0 103 }
michael@0 104
michael@0 105 </script>
michael@0 106 <script class="testbody" type="text/javascript">
michael@0 107
michael@0 108 SimpleTest.waitForExplicitFinish();
michael@0 109
michael@0 110 // fuzzTolerance and fuzzToleranceMobile are used to determine fuzziness
michael@0 111 // thresholds. They're needed to make sure that we can deal with neglibible
michael@0 112 // differences in the binary buffer caused as a result of resampling the
michael@0 113 // audio. fuzzToleranceMobile is typically larger on mobile platforms since
michael@0 114 // we do fixed-point resampling as opposed to floating-point resampling on
michael@0 115 // those platforms.
michael@0 116 var files = [
michael@0 117 // An ogg file, 44.1khz, mono
michael@0 118 {
michael@0 119 url: "ting-44.1k-1ch.ogg",
michael@0 120 valid: true,
michael@0 121 expectedUrl: "ting-44.1k-1ch.wav",
michael@0 122 numberOfChannels: 1,
michael@0 123 frames: 30592,
michael@0 124 sampleRate: 44100,
michael@0 125 duration: 0.693,
michael@0 126 fuzzTolerance: 5,
michael@0 127 fuzzToleranceMobile: 1284
michael@0 128 },
michael@0 129 // An ogg file, 44.1khz, stereo
michael@0 130 {
michael@0 131 url: "ting-44.1k-2ch.ogg",
michael@0 132 valid: true,
michael@0 133 expectedUrl: "ting-44.1k-2ch.wav",
michael@0 134 numberOfChannels: 2,
michael@0 135 frames: 30592,
michael@0 136 sampleRate: 44100,
michael@0 137 duration: 0.693,
michael@0 138 fuzzTolerance: 6,
michael@0 139 fuzzToleranceMobile: 2544
michael@0 140 },
michael@0 141 // An ogg file, 48khz, mono
michael@0 142 {
michael@0 143 url: "ting-48k-1ch.ogg",
michael@0 144 valid: true,
michael@0 145 expectedUrl: "ting-48k-1ch.wav",
michael@0 146 numberOfChannels: 1,
michael@0 147 frames: 33297,
michael@0 148 sampleRate: 48000,
michael@0 149 duration: 0.693,
michael@0 150 fuzzTolerance: 5,
michael@0 151 fuzzToleranceMobile: 1388
michael@0 152 },
michael@0 153 // An ogg file, 48khz, stereo
michael@0 154 {
michael@0 155 url: "ting-48k-2ch.ogg",
michael@0 156 valid: true,
michael@0 157 expectedUrl: "ting-48k-2ch.wav",
michael@0 158 numberOfChannels: 2,
michael@0 159 frames: 33297,
michael@0 160 sampleRate: 48000,
michael@0 161 duration: 0.693,
michael@0 162 fuzzTolerance: 14,
michael@0 163 fuzzToleranceMobile: 2752
michael@0 164 },
michael@0 165 // Make sure decoding a wave file results in the same buffer (for both the
michael@0 166 // resampling and non-resampling cases)
michael@0 167 {
michael@0 168 url: "ting-44.1k-1ch.wav",
michael@0 169 valid: true,
michael@0 170 expectedUrl: "ting-44.1k-1ch.wav",
michael@0 171 numberOfChannels: 1,
michael@0 172 frames: 30592,
michael@0 173 sampleRate: 44100,
michael@0 174 duration: 0.693,
michael@0 175 fuzzTolerance: 0,
michael@0 176 fuzzToleranceMobile: 0
michael@0 177 },
michael@0 178 {
michael@0 179 url: "ting-48k-1ch.wav",
michael@0 180 valid: true,
michael@0 181 expectedUrl: "ting-48k-1ch.wav",
michael@0 182 numberOfChannels: 1,
michael@0 183 frames: 33297,
michael@0 184 sampleRate: 48000,
michael@0 185 duration: 0.693,
michael@0 186 fuzzTolerance: 0,
michael@0 187 fuzzToleranceMobile: 0
michael@0 188 },
michael@0 189 // // A wave file
michael@0 190 // //{ url: "24bit-44khz.wav", valid: true, expectedUrl: "24bit-44khz-expected.wav" },
michael@0 191 // A non-audio file
michael@0 192 { url: "invalid.txt", valid: false, sampleRate: 44100 },
michael@0 193 // A webm file with no audio
michael@0 194 { url: "noaudio.webm", valid: false, sampleRate: 48000 },
michael@0 195 // A video ogg file with audio
michael@0 196 {
michael@0 197 url: "audio.ogv",
michael@0 198 valid: true,
michael@0 199 expectedUrl: "audio-expected.wav",
michael@0 200 numberOfChannels: 2,
michael@0 201 sampleRate: 44100,
michael@0 202 frames: 47680,
michael@0 203 duration: 1.0807,
michael@0 204 fuzzTolerance: 106,
michael@0 205 fuzzToleranceMobile: 3482
michael@0 206 }
michael@0 207 ];
michael@0 208
michael@0 209 // Returns true if the memory buffers are less different that |fuzz| bytes
michael@0 210 function fuzzyMemcmp(buf1, buf2, fuzz) {
michael@0 211 var result = true;
michael@0 212 var difference = 0;
michael@0 213 is(buf1.length, buf2.length, "same length");
michael@0 214 for (var i = 0; i < buf1.length; ++i) {
michael@0 215 if (Math.abs(buf1[i] - buf2[i])) {
michael@0 216 ++difference;
michael@0 217 }
michael@0 218 }
michael@0 219 if (difference > fuzz) {
michael@0 220 ok(false, "Expected at most " + fuzz + " bytes difference, found " + difference + " bytes");
michael@0 221 }
michael@0 222 return difference <= fuzz;
michael@0 223 }
michael@0 224
michael@0 225 function getFuzzTolerance(test) {
michael@0 226 var kIsMobile =
michael@0 227 navigator.userAgent.indexOf("Mobile") != -1 || // b2g
michael@0 228 navigator.userAgent.indexOf("Android") != -1; // android
michael@0 229 return kIsMobile ? test.fuzzToleranceMobile : test.fuzzTolerance;
michael@0 230 }
michael@0 231
michael@0 232 function bufferIsSilent(buffer) {
michael@0 233 for (var i = 0; i < buffer.length; ++i) {
michael@0 234 if (buffer.getChannelData(0)[i] != 0) {
michael@0 235 return false;
michael@0 236 }
michael@0 237 }
michael@0 238 return true;
michael@0 239 }
michael@0 240
michael@0 241 function checkAudioBuffer(buffer, test) {
michael@0 242 if (buffer.numberOfChannels != test.numberOfChannels) {
michael@0 243 is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels");
michael@0 244 return;
michael@0 245 }
michael@0 246 ok(Math.abs(buffer.duration - test.duration) < 1e-3, "Correct duration");
michael@0 247 if (Math.abs(buffer.duration - test.duration) >= 1e-3) {
michael@0 248 ok(false, "got: " + buffer.duration + ", expected: " + test.duration);
michael@0 249 }
michael@0 250 is(buffer.sampleRate, test.sampleRate, "Correct sample rate");
michael@0 251 is(buffer.length, test.frames, "Correct length");
michael@0 252
michael@0 253 var wave = createWaveFileData(buffer);
michael@0 254 ok(fuzzyMemcmp(wave, test.expectedWaveData, getFuzzTolerance(test)), "Received expected decoded data");
michael@0 255 }
michael@0 256
michael@0 257 function checkResampledBuffer(buffer, test, callback) {
michael@0 258 if (buffer.numberOfChannels != test.numberOfChannels) {
michael@0 259 is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels");
michael@0 260 return;
michael@0 261 }
michael@0 262 ok(Math.abs(buffer.duration - test.duration) < 1e-3, "Correct duration");
michael@0 263 if (Math.abs(buffer.duration - test.duration) >= 1e-3) {
michael@0 264 ok(false, "got: " + buffer.duration + ", expected: " + test.duration);
michael@0 265 }
michael@0 266 // Take into account the resampling when checking the size
michael@0 267 var expectedLength = test.frames * buffer.sampleRate / test.sampleRate;
michael@0 268 ok(Math.abs(buffer.length - expectedLength) < 1.0, "Correct length", "got " + buffer.length + ", expected about " + expectedLength);
michael@0 269
michael@0 270 // Playback the buffer in the original context, to resample back to the
michael@0 271 // original rate and compare with the decoded buffer without resampling.
michael@0 272 cx = test.nativeContext;
michael@0 273 var expected = cx.createBufferSource();
michael@0 274 expected.buffer = test.expectedBuffer;
michael@0 275 expected.start();
michael@0 276 var inverse = cx.createGain();
michael@0 277 inverse.gain.value = -1;
michael@0 278 expected.connect(inverse);
michael@0 279 inverse.connect(cx.destination);
michael@0 280 var resampled = cx.createBufferSource();
michael@0 281 resampled.buffer = buffer;
michael@0 282 resampled.start();
michael@0 283 // This stop should do nothing, but it tests for bug 937475
michael@0 284 resampled.stop(test.frames / cx.sampleRate);
michael@0 285 resampled.connect(cx.destination);
michael@0 286 cx.oncomplete = function(e) {
michael@0 287 ok(!bufferIsSilent(e.renderedBuffer), "Expect buffer not silent");
michael@0 288 // Resampling will lose the highest frequency components, so we should
michael@0 289 // pass the difference through a low pass filter. However, either the
michael@0 290 // input files don't have significant high frequency components or the
michael@0 291 // tolerance in compareBuffers() is too high to detect them.
michael@0 292 compareBuffers(e.renderedBuffer,
michael@0 293 cx.createBuffer(test.numberOfChannels,
michael@0 294 test.frames, test.sampleRate));
michael@0 295 callback();
michael@0 296 }
michael@0 297 cx.startRendering();
michael@0 298 }
michael@0 299
michael@0 300 function runResampling(test, response, callback) {
michael@0 301 var sampleRate = test.sampleRate == 44100 ? 48000 : 44100;
michael@0 302 var cx = new OfflineAudioContext(1, 1, sampleRate);
michael@0 303 cx.decodeAudioData(response, function onSuccess(asyncResult) {
michael@0 304 is(asyncResult.sampleRate, sampleRate, "Correct sample rate");
michael@0 305
michael@0 306 checkResampledBuffer(asyncResult, test, callback);
michael@0 307 }, function onFailure() {
michael@0 308 ok(false, "Expected successful decode with resample");
michael@0 309 callback();
michael@0 310 });
michael@0 311 }
michael@0 312
michael@0 313 function runTest(test, response, callback) {
michael@0 314 // We need to copy the array here, because decodeAudioData is going to neuter
michael@0 315 // the array.
michael@0 316 var compressedAudio = response.slice(0);
michael@0 317 var expectCallback = false;
michael@0 318 var cx = new OfflineAudioContext(test.numberOfChannels || 1,
michael@0 319 test.frames || 1, test.sampleRate);
michael@0 320 cx.decodeAudioData(response, function onSuccess(asyncResult) {
michael@0 321 ok(expectCallback, "Success callback should fire asynchronously");
michael@0 322 ok(test.valid, "Did expect success for test " + test.url);
michael@0 323
michael@0 324 checkAudioBuffer(asyncResult, test);
michael@0 325
michael@0 326 test.expectedBuffer = asyncResult;
michael@0 327 test.nativeContext = cx;
michael@0 328 runResampling(test, compressedAudio, callback);
michael@0 329 }, function onFailure() {
michael@0 330 ok(expectCallback, "Failure callback should fire asynchronously");
michael@0 331 ok(!test.valid, "Did expect failure for test " + test.url);
michael@0 332 callback();
michael@0 333 });
michael@0 334 expectCallback = true;
michael@0 335 }
michael@0 336
michael@0 337 function loadTest(test, callback) {
michael@0 338 var xhr = new XMLHttpRequest();
michael@0 339 xhr.open("GET", test.url, true);
michael@0 340 xhr.responseType = "arraybuffer";
michael@0 341 xhr.onload = function() {
michael@0 342 var getExpected = new XMLHttpRequest();
michael@0 343 getExpected.open("GET", test.expectedUrl, true);
michael@0 344 getExpected.responseType = "arraybuffer";
michael@0 345 getExpected.onload = function() {
michael@0 346 test.expectedWaveData = new Uint8Array(getExpected.response);
michael@0 347 runTest(test, xhr.response, callback);
michael@0 348 };
michael@0 349 getExpected.send();
michael@0 350 };
michael@0 351 xhr.send();
michael@0 352 }
michael@0 353
michael@0 354 function loadNextTest() {
michael@0 355 if (files.length) {
michael@0 356 loadTest(files.shift(), loadNextTest);
michael@0 357 } else {
michael@0 358 SimpleTest.finish();
michael@0 359 }
michael@0 360 }
michael@0 361
michael@0 362 // Run some simple tests first
michael@0 363 function callbackShouldNeverRun() {
michael@0 364 ok(false, "callback should not fire");
michael@0 365 }
michael@0 366 (function() {
michael@0 367 var cx = new AudioContext();
michael@0 368 expectTypeError(function() {
michael@0 369 cx.decodeAudioData(null, callbackShouldNeverRun, callbackShouldNeverRun);
michael@0 370 });
michael@0 371 expectTypeError(function() {
michael@0 372 cx.decodeAudioData(undefined, callbackShouldNeverRun, callbackShouldNeverRun);
michael@0 373 });
michael@0 374 expectTypeError(function() {
michael@0 375 cx.decodeAudioData(123, callbackShouldNeverRun, callbackShouldNeverRun);
michael@0 376 });
michael@0 377 expectTypeError(function() {
michael@0 378 cx.decodeAudioData("buffer", callbackShouldNeverRun, callbackShouldNeverRun);
michael@0 379 });
michael@0 380 expectTypeError(function() {
michael@0 381 cx.decodeAudioData(new Uint8Array(100), callbackShouldNeverRun, callbackShouldNeverRun);
michael@0 382 });
michael@0 383 })();
michael@0 384
michael@0 385 // Now, let's get real!
michael@0 386 loadNextTest();
michael@0 387
michael@0 388 </script>
michael@0 389 </pre>
michael@0 390 </body>
michael@0 391 </html>

mercurial