diff options
Diffstat (limited to 'dom/media/test/test_seamless_looping.html')
-rw-r--r-- | dom/media/test/test_seamless_looping.html | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/dom/media/test/test_seamless_looping.html b/dom/media/test/test_seamless_looping.html new file mode 100644 index 0000000000..94d020c834 --- /dev/null +++ b/dom/media/test/test_seamless_looping.html @@ -0,0 +1,199 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test for seamless loop of HTMLAudioElements</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<canvas id="canvas" width="300" height="300"></canvas> +<script type="application/javascript"> +/** + * This test is used to ensure every time we loop audio, the audio can loop + * seamlessly which means there won't have any silenece or noise between the + * end and the start. + */ + +SimpleTest.waitForExplicitFinish(); + +// Set DEBUG to true to add a canvas with a little drawing of what is going +// on, and actually outputs the audio to the speakers. +var DEBUG = true; +var LOOPING_COUNT = 0; +var MAX_LOOPING_COUNT = 10; +// Test files are at 44100Hz, files are one second long, and contain therefore +// 100 periods +var TONE_FREQUENCY = 441; + +(async function testSeamlesslooping() { + let wavFileURL = { + name: URL.createObjectURL(new Blob([createSrcBuffer()], { type: 'audio/wav' + })), + type: "audio/wav" + }; + + let testURLs = gSeamlessLoopingTests.splice(0) + testURLs.push(wavFileURL); + for (let testFile of testURLs) { + LOOPING_COUNT = 0; + info(`- create looping audio element ${testFile.name}`); + let audio = createAudioElement(testFile.name); + + info(`- start audio and analyze audio wave data to ensure looping audio without any silence or noise -`); + await playAudioAndStartAnalyzingWaveData(audio); + + info(`- test seamless looping multiples times -`); + for (LOOPING_COUNT = 0; LOOPING_COUNT < MAX_LOOPING_COUNT; LOOPING_COUNT++) { + await once(audio, "seeked"); + info(`- the round ${LOOPING_COUNT} of the seamless looping succeeds -`); + } + window.audio.remove(); + window.ac.close(); + } + + info(`- end of seamless looping test -`); + SimpleTest.finish(); +})(); + +/** + * Test utility functions + */ +function createSrcBuffer() { + // Generate the sine in floats, then convert, for simplicity. + let channels = 1; + let sampleRate = 44100; + let buffer = new Float32Array(sampleRate * channels); + let phase = 0; + const TAU = 2 * Math.PI; + for (let i = 0; i < buffer.length; i++) { + // Adjust the gain a little so we're sure it's not going to clip. This is + // important because we're converting to 16bit integer right after, and + // clipping will clearly introduce a discontinuity that will be + // mischaracterized as a looping click. + buffer[i] = Math.sin(phase) * 0.99; + phase += TAU * TONE_FREQUENCY / 44100; + if (phase > 2 * TAU) { + phase -= TAU; + } + } + + // Make a RIFF header, it's 23 bytes + let buf = new Int16Array(buffer.length + 23); + buf[0] = 0x4952; + buf[1] = 0x4646; + buf[2] = (2 * buffer.length + 15) & 0x0000ffff; + buf[3] = ((2 * buffer.length + 15) & 0xffff0000) >> 16; + buf[4] = 0x4157; + buf[5] = 0x4556; + buf[6] = 0x6d66; + buf[7] = 0x2074; + buf[8] = 0x0012; + buf[9] = 0x0000; + buf[10] = 0x0001; + buf[11] = 1; + buf[12] = 44100 & 0x0000ffff; + buf[13] = (44100 & 0xffff0000) >> 16; + buf[14] = (2 * channels * sampleRate) & 0x0000ffff; + buf[15] = ((2 * channels * sampleRate) & 0xffff0000) >> 16; + buf[16] = 0x0004; + buf[17] = 0x0010; + buf[18] = 0x0000; + buf[19] = 0x6164; + buf[20] = 0x6174; + buf[21] = (2 * buffer.length) & 0x0000ffff; + buf[22] = ((2 * buffer.length) & 0xffff0000) >> 16; + + // convert to int16 and copy. + for (let i = 0; i < buffer.length; i++) { + buf[i + 23] = Math.round(buffer[i] * (1 << 15)); + } + return buf; +} + +function createAudioElement(url) { + /* global audio */ + window.audio = document.createElement("audio"); + audio.src = url; + audio.controls = true; + audio.loop = true; + document.body.appendChild(audio); + return audio; +} + +async function playAudioAndStartAnalyzingWaveData(audio) { + createAudioWaveAnalyser(audio); + ok(await once(audio, "canplay").then(() => true, () => false), + `audio can start playing.`) + ok(await audio.play().then(() => true, () => false), + `audio started playing successfully.`); +} + +function createAudioWaveAnalyser(source) { + /* global ac, analyser */ + window.ac = new AudioContext(); + window.analyser = ac.createAnalyser(); + analyser.frequencyBuf = new Float32Array(analyser.frequencyBinCount); + analyser.smoothingTimeConstant = 0; + analyser.fftSize = 2048; // 1024 bins + + let sourceNode = ac.createMediaElementSource(source); + sourceNode.connect(analyser); + + if (DEBUG) { + analyser.connect(ac.destination); + analyser.timeDomainBuf = new Float32Array(analyser.frequencyBinCount); + let cvs = document.querySelector("canvas"); + analyser.c = cvs.getContext("2d"); + analyser.w = cvs.width; + analyser.h = cvs.height; + } + + analyser.notifyAnalysis = () => { + if (LOOPING_COUNT >= MAX_LOOPING_COUNT) { + return; + } + let {frequencyBuf} = analyser; + analyser.getFloatFrequencyData(frequencyBuf); + // Let things stabilize at the beginning. See bug 1441509. + if (LOOPING_COUNT > 1) { + analyser.doAnalysis(frequencyBuf, ac.sampleRate); + } + + if (DEBUG) { + let {c, w, h, timeDomainBuf} = analyser; + c.clearRect(0, 0, w, h); + analyser.getFloatTimeDomainData(timeDomainBuf); + for (let i = 0; i < frequencyBuf.length; i++) { + c.fillRect(i, h, 1, -frequencyBuf[i] + analyser.minDecibels); + } + + for (let i = 0; i < timeDomainBuf.length; i++) { + c.fillRect(i, h / 2, 1, -timeDomainBuf[i] * h / 2); + } + } + + requestAnimationFrame(analyser.notifyAnalysis); + } + + analyser.doAnalysis = (buf, ctxSampleRate) => { + // The size of an FFT is twice the number of bins in its output. + let fftSize = 2 * buf.length; + // first find a peak where we expect one. + let binIndexTone = 1 + Math.round(TONE_FREQUENCY * fftSize / ctxSampleRate); + ok(buf[binIndexTone] > -35, + `Could not find a peak: ${buf[binIndexTone]} db at ${TONE_FREQUENCY}Hz + (${source.src})`); + + // check that the energy some octaves higher is very low. + let binIndexOutsidePeak = 1 + Math.round(TONE_FREQUENCY * 4 * buf.length / ctxSampleRate); + ok(buf[binIndexOutsidePeak] < -84, + `Found unexpected high frequency content: ${buf[binIndexOutsidePeak]}db + at ${TONE_FREQUENCY * 4}Hz (${source.src})`); + } + + analyser.notifyAnalysis(); +} +</script> +</body> +</html> |