content/media/webaudio/test/test_pannerNodeTail.html

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

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

mercurial