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.

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

mercurial