content/media/webaudio/test/test_pannerNodeTail.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 tail time lifetime of PannerNode</title>
michael@0 5 <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
michael@0 6 <script type="text/javascript" src="webaudio.js"></script>
michael@0 7 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
michael@0 8 </head>
michael@0 9 <body>
michael@0 10 <pre id="test">
michael@0 11 <script class="testbody" type="text/javascript">
michael@0 12
michael@0 13 // This tests that a PannerNode does not release its reference before
michael@0 14 // it finishes emitting sound.
michael@0 15 //
michael@0 16 // The PannerNode tail time is short, so, when a PannerNode is destroyed on
michael@0 17 // the main thread, it is unlikely to notify the graph thread before the tail
michael@0 18 // time expires. However, by adding DelayNodes downstream from the
michael@0 19 // PannerNodes, the graph thread can have enough time to notice that a
michael@0 20 // DelayNode has been destroyed.
michael@0 21 //
michael@0 22 // In the current implementation, DelayNodes will take a tail-time reference
michael@0 23 // immediately when they receive the first block of sound from an upstream
michael@0 24 // node, so this test connects the downstream DelayNodes while the upstream
michael@0 25 // nodes are finishing, and then runs GC (on the main thread) before the
michael@0 26 // DelayNodes receive any input (on the graph thread).
michael@0 27 //
michael@0 28 // Web Audio doesn't provide a means to precisely time connect()s but we can
michael@0 29 // test that the output of delay nodes matches the output from a reference
michael@0 30 // PannerNode that we know will not be GCed.
michael@0 31 //
michael@0 32 // Another set of delay nodes is added upstream to ensure that the source node
michael@0 33 // has removed its self-reference after dispatching its "ended" event.
michael@0 34
michael@0 35 SimpleTest.waitForExplicitFinish();
michael@0 36
michael@0 37 const blockSize = 128;
michael@0 38 // bufferSize should be long enough that to allow an audioprocess event to be
michael@0 39 // sent to the main thread and a connect message to return to the graph
michael@0 40 // thread.
michael@0 41 const bufferSize = 4096;
michael@0 42 const pannerCount = bufferSize / blockSize;
michael@0 43 // sourceDelayBufferCount should be long enough to allow the source node
michael@0 44 // onended to finish and remove the source self-reference.
michael@0 45 const sourceDelayBufferCount = 3;
michael@0 46 var gotEnded = false;
michael@0 47 // ccDelayLength should be long enough to allow CC to run
michael@0 48 var ccDelayBufferCount = 20;
michael@0 49 const ccDelayLength = ccDelayBufferCount * bufferSize;
michael@0 50
michael@0 51 var ctx;
michael@0 52 var testPanners = [];
michael@0 53 var referencePanner;
michael@0 54 var referenceProcessCount = 0;
michael@0 55 var referenceOutput = [new Float32Array(bufferSize),
michael@0 56 new Float32Array(bufferSize)];
michael@0 57 var testProcessor;
michael@0 58 var testProcessCount = 0;
michael@0 59
michael@0 60 function isChannelSilent(channel) {
michael@0 61 for (var i = 0; i < channel.length; ++i) {
michael@0 62 if (channel[i] != 0.0) {
michael@0 63 return false;
michael@0 64 }
michael@0 65 }
michael@0 66 return true;
michael@0 67 }
michael@0 68
michael@0 69 function onReferenceOutput(e) {
michael@0 70 switch(referenceProcessCount) {
michael@0 71
michael@0 72 case sourceDelayBufferCount - 1:
michael@0 73 // The panners are about to finish.
michael@0 74 if (!gotEnded) {
michael@0 75 todo(false, "Source hasn't ended. Increase sourceDelayBufferCount?");
michael@0 76 }
michael@0 77
michael@0 78 // Connect each PannerNode output to a downstream DelayNode,
michael@0 79 // and connect ScriptProcessors to compare test and reference panners.
michael@0 80 var delayDuration = ccDelayLength / ctx.sampleRate;
michael@0 81 for (var i = 0; i < pannerCount; ++i) {
michael@0 82 var delay = ctx.createDelay(delayDuration);
michael@0 83 delay.delayTime.value = delayDuration;
michael@0 84 delay.connect(testProcessor);
michael@0 85 testPanners[i].connect(delay);
michael@0 86 }
michael@0 87 testProcessor = null;
michael@0 88 testPanners = null;
michael@0 89
michael@0 90 // The panning effect is linear so only one reference panner is required.
michael@0 91 // This also checks that the individual panners don't chop their output
michael@0 92 // too soon.
michael@0 93 referencePanner.connect(e.target);
michael@0 94
michael@0 95 // Assuming the above operations have already scheduled an event to run in
michael@0 96 // stable state and ask the graph thread to make connections, schedule a
michael@0 97 // subsequent event to run cycle collection, which should not collect
michael@0 98 // panners that are still producing sound.
michael@0 99 SimpleTest.executeSoon(function() {
michael@0 100 SpecialPowers.forceGC();
michael@0 101 SpecialPowers.forceCC();
michael@0 102 });
michael@0 103
michael@0 104 break;
michael@0 105
michael@0 106 case sourceDelayBufferCount:
michael@0 107 // Record this buffer during which PannerNode outputs were connected.
michael@0 108 for (var i = 0; i < 2; ++i) {
michael@0 109 e.inputBuffer.copyFromChannel(referenceOutput[i], i);
michael@0 110 }
michael@0 111 e.target.onaudioprocess = null;
michael@0 112 e.target.disconnect();
michael@0 113
michael@0 114 // If the buffer is silent, there is probably not much point just
michael@0 115 // increasing the buffer size, because, with the buffer size already
michael@0 116 // significantly larger than panner tail time, it demonstrates that the
michael@0 117 // lag between threads is much greater than the tail time.
michael@0 118 if (isChannelSilent(referenceOutput[0])) {
michael@0 119 todo(false, "Connections not detected.");
michael@0 120 }
michael@0 121 }
michael@0 122
michael@0 123 referenceProcessCount++;
michael@0 124 }
michael@0 125
michael@0 126 function onTestOutput(e) {
michael@0 127 if (testProcessCount < sourceDelayBufferCount + ccDelayBufferCount) {
michael@0 128 testProcessCount++;
michael@0 129 return;
michael@0 130 }
michael@0 131
michael@0 132 for (var i = 0; i < 2; ++i) {
michael@0 133 compareChannels(e.inputBuffer.getChannelData(i), referenceOutput[i]);
michael@0 134 }
michael@0 135 e.target.onaudioprocess = null;
michael@0 136 e.target.disconnect();
michael@0 137 SimpleTest.finish();
michael@0 138 }
michael@0 139
michael@0 140 function startTest() {
michael@0 141 // 0.002 is MaxDelayTimeSeconds in HRTFpanner.cpp
michael@0 142 // and 512 is fftSize() at 48 kHz.
michael@0 143 const expectedPannerTailTime = 0.002 * ctx.sampleRate + 512;
michael@0 144
michael@0 145 // Create some PannerNodes downstream from DelayNodes with delays long
michael@0 146 // enough for their source to finish, dispatch its "ended" event
michael@0 147 // and release its playing reference. The DelayNodes should expire their
michael@0 148 // tail-time references before the PannerNodes and so only the PannerNode
michael@0 149 // lifetimes depends on their tail-time references. Many DelayNodes are
michael@0 150 // created and timed to finish at different times so that one PannerNode
michael@0 151 // will be finishing the block processed immediately after the connect is
michael@0 152 // received.
michael@0 153 var source = ctx.createBufferSource();
michael@0 154 // Just short of blockSize here to avoid rounding into the next block
michael@0 155 var buffer = ctx.createBuffer(1, blockSize - 1, ctx.sampleRate);
michael@0 156 for (var i = 0; i < buffer.length; ++i) {
michael@0 157 buffer.getChannelData(0)[i] = Math.cos(Math.PI * i / buffer.length);
michael@0 158 }
michael@0 159 source.buffer = buffer;
michael@0 160 source.start(0);
michael@0 161 source.onended = function(e) {
michael@0 162 gotEnded = true;
michael@0 163 };
michael@0 164
michael@0 165 // Time the first test panner to finish just before downstream DelayNodes
michael@0 166 // are about the be connected. Note that DelayNode lifetime depends on
michael@0 167 // maxDelayTime so set that equal to the delay.
michael@0 168 var delayDuration =
michael@0 169 (sourceDelayBufferCount * bufferSize
michael@0 170 - expectedPannerTailTime - 2 * blockSize) / ctx.sampleRate;
michael@0 171
michael@0 172 for (var i = 0; i < pannerCount; ++i) {
michael@0 173 var delay = ctx.createDelay(delayDuration);
michael@0 174 delay.delayTime.value = delayDuration;
michael@0 175 source.connect(delay);
michael@0 176 delay.connect(referencePanner)
michael@0 177
michael@0 178 var panner = ctx.createPanner();
michael@0 179 delay.connect(panner);
michael@0 180 testPanners[i] = panner;
michael@0 181
michael@0 182 delayDuration += blockSize / ctx.sampleRate;
michael@0 183 }
michael@0 184
michael@0 185 // Create a ScriptProcessor now to use as a timer to trigger connection of
michael@0 186 // downstream nodes. It will also be used to record reference output.
michael@0 187 var referenceProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
michael@0 188 referenceProcessor.onaudioprocess = onReferenceOutput;
michael@0 189 // Start audioprocess events before source delays are connected.
michael@0 190 referenceProcessor.connect(ctx.destination);
michael@0 191
michael@0 192 // The test ScriptProcessor will record output of testPanners.
michael@0 193 // Create it now so that it is synchronized with the referenceProcessor.
michael@0 194 testProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
michael@0 195 testProcessor.onaudioprocess = onTestOutput;
michael@0 196 // Start audioprocess events before source delays are connected.
michael@0 197 testProcessor.connect(ctx.destination);
michael@0 198 }
michael@0 199
michael@0 200 function prepareTest() {
michael@0 201 ctx = new AudioContext();
michael@0 202 // Place the listener to the side of the origin, where the panners are
michael@0 203 // positioned, to maximize delay in one ear.
michael@0 204 ctx.listener.setPosition(1,0,0);
michael@0 205
michael@0 206 // A PannerNode will produce no output until it has loaded its HRIR
michael@0 207 // database. Wait for this to load before starting the test.
michael@0 208 var processor = ctx.createScriptProcessor(bufferSize, 2, 0);
michael@0 209 referencePanner = ctx.createPanner();
michael@0 210 referencePanner.connect(processor);
michael@0 211 var oscillator = ctx.createOscillator();
michael@0 212 oscillator.connect(referencePanner);
michael@0 213 oscillator.start(0);
michael@0 214
michael@0 215 processor.onaudioprocess = function(e) {
michael@0 216 if (isChannelSilent(e.inputBuffer.getChannelData(0)))
michael@0 217 return;
michael@0 218
michael@0 219 oscillator.stop(0);
michael@0 220 oscillator.disconnect();
michael@0 221 referencePanner.disconnect();
michael@0 222 e.target.onaudioprocess = null;
michael@0 223 SimpleTest.executeSoon(startTest);
michael@0 224 };
michael@0 225 }
michael@0 226 prepareTest();
michael@0 227 </script>
michael@0 228 </pre>
michael@0 229 </body>
michael@0 230 </html>

mercurial