зеркало из https://github.com/mozilla/gecko-dev.git
233 строки
8.2 KiB
HTML
233 строки
8.2 KiB
HTML
<!DOCTYPE HTML>
|
|
<html>
|
|
<head>
|
|
<title>Test tail time lifetime of PannerNode</title>
|
|
<script type="text/javascript" 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.type = "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.type = "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>
|