diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/webaudio/test/test_pannerNodeTail.html | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/dom/media/webaudio/test/test_pannerNodeTail.html b/dom/media/webaudio/test/test_pannerNodeTail.html new file mode 100644 index 0000000000..1f6483b581 --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNodeTail.html @@ -0,0 +1,232 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test tail time lifetime of PannerNode</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// This tests that a PannerNode does not release its reference before +// it finishes emitting sound. +// +// The PannerNode tail time is short, so, when a PannerNode is destroyed on +// the main thread, it is unlikely to notify the graph thread before the tail +// time expires. However, by adding DelayNodes downstream from the +// PannerNodes, the graph thread can have enough time to notice that a +// DelayNode has been destroyed. +// +// In the current implementation, DelayNodes will take a tail-time reference +// immediately when they receive the first block of sound from an upstream +// node, so this test connects the downstream DelayNodes while the upstream +// nodes are finishing, and then runs GC (on the main thread) before the +// DelayNodes receive any input (on the graph thread). +// +// Web Audio doesn't provide a means to precisely time connect()s but we can +// test that the output of delay nodes matches the output from a reference +// PannerNode that we know will not be GCed. +// +// Another set of delay nodes is added upstream to ensure that the source node +// has removed its self-reference after dispatching its "ended" event. + +SimpleTest.waitForExplicitFinish(); + +const blockSize = 128; +// bufferSize should be long enough that to allow an audioprocess event to be +// sent to the main thread and a connect message to return to the graph +// thread. +const bufferSize = 4096; +const pannerCount = bufferSize / blockSize; +// sourceDelayBufferCount should be long enough to allow the source node +// onended to finish and remove the source self-reference. +const sourceDelayBufferCount = 3; +var gotEnded = false; +// ccDelayLength should be long enough to allow CC to run +var ccDelayBufferCount = 20; +const ccDelayLength = ccDelayBufferCount * bufferSize; + +var ctx; +var testPanners = []; +var referencePanner; +var referenceProcessCount = 0; +var referenceOutput = [new Float32Array(bufferSize), + new Float32Array(bufferSize)]; +var testProcessor; +var testProcessCount = 0; + +function isChannelSilent(channel) { + for (var i = 0; i < channel.length; ++i) { + if (channel[i] != 0.0) { + return false; + } + } + return true; +} + +function onReferenceOutput(e) { + switch(referenceProcessCount) { + + case sourceDelayBufferCount - 1: + // The panners are about to finish. + if (!gotEnded) { + todo(false, "Source hasn't ended. Increase sourceDelayBufferCount?"); + } + + // Connect each PannerNode output to a downstream DelayNode, + // and connect ScriptProcessors to compare test and reference panners. + var delayDuration = ccDelayLength / ctx.sampleRate; + for (var i = 0; i < pannerCount; ++i) { + var delay = ctx.createDelay(delayDuration); + delay.delayTime.value = delayDuration; + delay.connect(testProcessor); + testPanners[i].connect(delay); + } + testProcessor = null; + testPanners = null; + + // The panning effect is linear so only one reference panner is required. + // This also checks that the individual panners don't chop their output + // too soon. + referencePanner.connect(e.target); + + // Assuming the above operations have already scheduled an event to run in + // stable state and ask the graph thread to make connections, schedule a + // subsequent event to run cycle collection, which should not collect + // panners that are still producing sound. + SimpleTest.executeSoon(function() { + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + }); + + break; + + case sourceDelayBufferCount: + // Record this buffer during which PannerNode outputs were connected. + for (var i = 0; i < 2; ++i) { + e.inputBuffer.copyFromChannel(referenceOutput[i], i); + } + e.target.onaudioprocess = null; + e.target.disconnect(); + + // If the buffer is silent, there is probably not much point just + // increasing the buffer size, because, with the buffer size already + // significantly larger than panner tail time, it demonstrates that the + // lag between threads is much greater than the tail time. + if (isChannelSilent(referenceOutput[0])) { + todo(false, "Connections not detected."); + } + } + + referenceProcessCount++; +} + +function onTestOutput(e) { + if (testProcessCount < sourceDelayBufferCount + ccDelayBufferCount) { + testProcessCount++; + return; + } + + for (var i = 0; i < 2; ++i) { + compareChannels(e.inputBuffer.getChannelData(i), referenceOutput[i]); + } + e.target.onaudioprocess = null; + e.target.disconnect(); + SimpleTest.finish(); +} + +function startTest() { + // 0.002 is MaxDelayTimeSeconds in HRTFpanner.cpp + // and 512 is fftSize() at 48 kHz. + const expectedPannerTailTime = 0.002 * ctx.sampleRate + 512; + + // Create some PannerNodes downstream from DelayNodes with delays long + // enough for their source to finish, dispatch its "ended" event + // and release its playing reference. The DelayNodes should expire their + // tail-time references before the PannerNodes and so only the PannerNode + // lifetimes depends on their tail-time references. Many DelayNodes are + // created and timed to finish at different times so that one PannerNode + // will be finishing the block processed immediately after the connect is + // received. + var source = ctx.createBufferSource(); + // Just short of blockSize here to avoid rounding into the next block + var buffer = ctx.createBuffer(1, blockSize - 1, ctx.sampleRate); + for (var i = 0; i < buffer.length; ++i) { + buffer.getChannelData(0)[i] = Math.cos(Math.PI * i / buffer.length); + } + source.buffer = buffer; + source.start(0); + source.onended = function(e) { + gotEnded = true; + }; + + // Time the first test panner to finish just before downstream DelayNodes + // are about the be connected. Note that DelayNode lifetime depends on + // maxDelayTime so set that equal to the delay. + var delayDuration = + (sourceDelayBufferCount * bufferSize + - expectedPannerTailTime - 2 * blockSize) / ctx.sampleRate; + + for (var i = 0; i < pannerCount; ++i) { + var delay = ctx.createDelay(delayDuration); + delay.delayTime.value = delayDuration; + source.connect(delay); + delay.connect(referencePanner) + + var panner = ctx.createPanner(); + panner.panningModel = "HRTF"; + delay.connect(panner); + testPanners[i] = panner; + + delayDuration += blockSize / ctx.sampleRate; + } + + // Create a ScriptProcessor now to use as a timer to trigger connection of + // downstream nodes. It will also be used to record reference output. + var referenceProcessor = ctx.createScriptProcessor(bufferSize, 2, 0); + referenceProcessor.onaudioprocess = onReferenceOutput; + // Start audioprocess events before source delays are connected. + referenceProcessor.connect(ctx.destination); + + // The test ScriptProcessor will record output of testPanners. + // Create it now so that it is synchronized with the referenceProcessor. + testProcessor = ctx.createScriptProcessor(bufferSize, 2, 0); + testProcessor.onaudioprocess = onTestOutput; + // Start audioprocess events before source delays are connected. + testProcessor.connect(ctx.destination); +} + +function prepareTest() { + ctx = new AudioContext(); + // Place the listener to the side of the origin, where the panners are + // positioned, to maximize delay in one ear. + ctx.listener.setPosition(1,0,0); + + // A PannerNode will produce no output until it has loaded its HRIR + // database. Wait for this to load before starting the test. + var processor = ctx.createScriptProcessor(bufferSize, 2, 0); + referencePanner = ctx.createPanner(); + referencePanner.panningModel = "HRTF"; + referencePanner.connect(processor); + var oscillator = ctx.createOscillator(); + oscillator.connect(referencePanner); + oscillator.start(0); + + processor.onaudioprocess = function(e) { + if (isChannelSilent(e.inputBuffer.getChannelData(0))) + return; + + oscillator.stop(0); + oscillator.disconnect(); + referencePanner.disconnect(); + e.target.onaudioprocess = null; + SimpleTest.executeSoon(startTest); + }; +} +prepareTest(); +</script> +</pre> +</body> +</html> |