tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_seamless_looping.html (6374B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4  <title>Test for seamless loop of HTMLAudioElements</title>
      5  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      6  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
      7  <script type="text/javascript" src="manifest.js"></script>
      8 </head>
      9 <body>
     10 <canvas id="canvas" width="300" height="300"></canvas>
     11 <script type="application/javascript">
     12 /**
     13 * This test is used to ensure every time we loop audio, the audio can loop
     14 * seamlessly which means there won't have any silenece or noise between the
     15 * end and the start.
     16 */
     17 
     18 SimpleTest.waitForExplicitFinish();
     19 
     20 // Set DEBUG to true to add a canvas with a little drawing of what is going
     21 // on, and actually outputs the audio to the speakers.
     22 var DEBUG = true;
     23 var LOOPING_COUNT = 0;
     24 var MAX_LOOPING_COUNT = 10;
     25 // Test files are at 44100Hz, files are one second long, and contain therefore
     26 // 100 periods
     27 var TONE_FREQUENCY = 441;
     28 
     29 (async function testSeamlesslooping() {
     30  let wavFileURL = {
     31    name: URL.createObjectURL(new Blob([createSrcBuffer()], { type: 'audio/wav'
     32                })),
     33    type: "audio/wav"
     34  };
     35 
     36  let testURLs = gSeamlessLoopingTests.splice(0)
     37  testURLs.push(wavFileURL);
     38  for (let testFile of testURLs) {
     39    LOOPING_COUNT = 0;
     40    info(`- create looping audio element ${testFile.name}`);
     41    let audio = createAudioElement(testFile.name);
     42 
     43    info(`- start audio and analyze audio wave data to ensure looping audio without any silence or noise -`);
     44    await playAudioAndStartAnalyzingWaveData(audio);
     45 
     46    info(`- test seamless looping multiples times -`);
     47    for (LOOPING_COUNT = 0; LOOPING_COUNT < MAX_LOOPING_COUNT; LOOPING_COUNT++) {
     48      await once(audio, "seeked");
     49      info(`- the round ${LOOPING_COUNT} of the seamless looping succeeds -`);
     50    }
     51    window.audio.remove();
     52    window.ac.close();
     53  }
     54 
     55  info(`- end of seamless looping test -`);
     56  SimpleTest.finish();
     57 })();
     58 
     59 /**
     60 * Test utility functions
     61 */
     62 function createSrcBuffer() {
     63  // Generate the sine in floats, then convert, for simplicity.
     64  let channels = 1;
     65  let sampleRate = 44100;
     66  let buffer = new Float32Array(sampleRate * channels);
     67  let phase = 0;
     68  const TAU = 2 * Math.PI;
     69  for (let i = 0; i < buffer.length; i++) {
     70    // Adjust the gain a little so we're sure it's not going to clip. This is
     71    // important because we're converting to 16bit integer right after, and
     72    // clipping will clearly introduce a discontinuity that will be
     73    // mischaracterized as a looping click.
     74    buffer[i] = Math.sin(phase) * 0.99;
     75    phase += TAU * TONE_FREQUENCY / 44100;
     76    if (phase > 2 * TAU) {
     77      phase -= TAU;
     78    }
     79  }
     80 
     81  // Make a RIFF header, it's 23 bytes
     82  let buf = new Int16Array(buffer.length + 23);
     83  buf[0] = 0x4952;
     84  buf[1] = 0x4646;
     85  buf[2] = (2 * buffer.length + 15) & 0x0000ffff;
     86  buf[3] = ((2 * buffer.length + 15) & 0xffff0000) >> 16;
     87  buf[4] = 0x4157;
     88  buf[5] = 0x4556;
     89  buf[6] = 0x6d66;
     90  buf[7] = 0x2074;
     91  buf[8] = 0x0012;
     92  buf[9] = 0x0000;
     93  buf[10] = 0x0001;
     94  buf[11] = 1;
     95  buf[12] = 44100 & 0x0000ffff;
     96  buf[13] = (44100 & 0xffff0000) >> 16;
     97  buf[14] = (2 * channels * sampleRate) & 0x0000ffff;
     98  buf[15] = ((2 * channels * sampleRate) & 0xffff0000) >> 16;
     99  buf[16] = 0x0004;
    100  buf[17] = 0x0010;
    101  buf[18] = 0x0000;
    102  buf[19] = 0x6164;
    103  buf[20] = 0x6174;
    104  buf[21] = (2 * buffer.length) & 0x0000ffff;
    105  buf[22] = ((2 * buffer.length) & 0xffff0000) >> 16;
    106 
    107  // convert to int16 and copy.
    108  for (let i = 0; i < buffer.length; i++) {
    109    buf[i + 23] = Math.round(buffer[i] * (1 << 15));
    110  }
    111  return buf;
    112 }
    113 
    114 function createAudioElement(url) {
    115  /* global audio */
    116  window.audio = document.createElement("audio");
    117  audio.src = url;
    118  audio.controls = true;
    119  audio.loop = true;
    120  document.body.appendChild(audio);
    121  return audio;
    122 }
    123 
    124 async function playAudioAndStartAnalyzingWaveData(audio) {
    125  createAudioWaveAnalyser(audio);
    126  ok(await once(audio, "canplay").then(() => true, () => false),
    127     `audio can start playing.`)
    128  ok(await audio.play().then(() => true, () => false),
    129     `audio started playing successfully.`);
    130 }
    131 
    132 function createAudioWaveAnalyser(source) {
    133  /* global ac, analyser */
    134  window.ac = new AudioContext();
    135  window.analyser = ac.createAnalyser();
    136  analyser.frequencyBuf = new Float32Array(analyser.frequencyBinCount);
    137  analyser.smoothingTimeConstant = 0;
    138  analyser.fftSize = 2048; // 1024 bins
    139 
    140  let sourceNode = ac.createMediaElementSource(source);
    141  sourceNode.connect(analyser);
    142 
    143  if (DEBUG) {
    144    analyser.connect(ac.destination);
    145    analyser.timeDomainBuf = new Float32Array(analyser.frequencyBinCount);
    146    let cvs = document.querySelector("canvas");
    147    analyser.c = cvs.getContext("2d");
    148    analyser.w = cvs.width;
    149    analyser.h = cvs.height;
    150  }
    151 
    152  analyser.notifyAnalysis = () => {
    153    if (LOOPING_COUNT >= MAX_LOOPING_COUNT) {
    154      return;
    155    }
    156    let {frequencyBuf} = analyser;
    157    analyser.getFloatFrequencyData(frequencyBuf);
    158    // Let things stabilize at the beginning. See bug 1441509.
    159    if (LOOPING_COUNT > 1) {
    160      analyser.doAnalysis(frequencyBuf, ac.sampleRate);
    161    }
    162 
    163    if (DEBUG) {
    164      let {c, w, h, timeDomainBuf} = analyser;
    165      c.clearRect(0, 0, w, h);
    166      analyser.getFloatTimeDomainData(timeDomainBuf);
    167      for (let i = 0; i < frequencyBuf.length; i++) {
    168        c.fillRect(i, h, 1, -frequencyBuf[i] + analyser.minDecibels);
    169      }
    170 
    171      for (let i = 0; i < timeDomainBuf.length; i++) {
    172        c.fillRect(i, h / 2, 1, -timeDomainBuf[i] * h / 2);
    173      }
    174    }
    175 
    176    requestAnimationFrame(analyser.notifyAnalysis);
    177  }
    178 
    179  analyser.doAnalysis = (buf, ctxSampleRate) => {
    180    // The size of an FFT is twice the number of bins in its output.
    181    let fftSize = 2 * buf.length;
    182    // first find a peak where we expect one.
    183    let binIndexTone = 1 + Math.round(TONE_FREQUENCY * fftSize / ctxSampleRate);
    184    ok(buf[binIndexTone] > -35,
    185       `Could not find a peak: ${buf[binIndexTone]} db at ${TONE_FREQUENCY}Hz
    186       (${source.src})`);
    187 
    188    // check that the energy some octaves higher is very low.
    189    let binIndexOutsidePeak = 1 + Math.round(TONE_FREQUENCY * 4 * buf.length / ctxSampleRate);
    190    ok(buf[binIndexOutsidePeak] < -84,
    191       `Found unexpected high frequency content: ${buf[binIndexOutsidePeak]}db
    192       at ${TONE_FREQUENCY * 4}Hz (${source.src})`);
    193  }
    194 
    195  analyser.notifyAnalysis();
    196 }
    197 </script>
    198 </body>
    199 </html>