Skip to content

Commit

Permalink
Make ChannelMerger active processing test less flaky
Browse files Browse the repository at this point in the history
Replace the offline audio context with a realtime context and an AudioWorklet.
Previously, when the source stopped, the channel count change would sometimes
get delayed until rendering is done, which is too late for the test.  I believe
this is because the main thread was previously too busy to process the messages
to disable outputs and change the channel count.

Using a realtime context makes the main thread less busy because the messages
aren't posted as fast as possible, allowing time for them to be handled. I think
this is still flaky, but much less so than before.

Bug: 974258
Test: the-channelmergernode-interface/active-processing.https.html
Change-Id: Id584ebfa5f54ad6870819f5242aafed5f4fc80d1
  • Loading branch information
Raymond Toy authored and chromium-wpt-export-bot committed Jun 19, 2019
1 parent 3671eb9 commit 8880ab2
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 87 deletions.
@@ -0,0 +1,57 @@
/**
* @class ActiveProcessingTester
* @extends AudioWorkletProcessor
*
* This processor class sends a message to its AudioWorkletNodew whenever the
* number of channels on the input changes. The message includes the actual
* number of channels, the context time at which this occurred, and whether
* we're done processing or not.
*/
class ActiveProcessingTester extends AudioWorkletProcessor {
constructor(options) {
super(options);
this._lastChannelCount = 0;
this.port.onmessage = this.handleMessage_.bind(this);

// See if user specified a value for test duration.
if (options.hasOwnProperty('processorOptions') &&
options.processorOptions.hasOwnProperty('testDuration')) {
this._testDuration = options.processorOptions.testDuration;
} else {
this._testDuration = 5;
}

// Time at which we'll signal we're done, based on the requested
// |testDuration|
this._endTime = currentTime + this._testDuration;
}

handleMessage_(event) {
// We don't expect any messages from the worklet node.
}

process(inputs, outputs) {
let input = inputs[0];
let output = outputs[0];
let inputChannelCount = input.length;
let finished = currentTime > this._endTime;

if (finished || (inputChannelCount != this._lastChannelCount)) {
this.port.postMessage({
channelCount: inputChannelCount,
finished: finished,
time: currentTime
});
this._lastChannelCount = inputChannelCount;
}

// Just copy the input to the output for no particular reason.
for (let channel = 0; channel < input.length; ++channel) {
output[channel].set(input[channel]);
}

return true;
}
}

registerProcessor('active-processing-tester', ActiveProcessingTester);
Expand Up @@ -6,103 +6,101 @@
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>

<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// AudioProcessor that sends a message to its AudioWorkletNode whenver the
// number of channels on its input changes.
let filePath =
'../the-audioworklet-interface/processors/active-processing.js';


// AudioWorkletNode that verifies the number of channels matches
// expectations.
class ActiveProcessingTesterNode extends AudioWorkletNode {
constructor(context, options) {
super(context, 'active-processing-tester', options);
this.port.onmessage = this.handleMessage_.bind(this);
this.expectedValues_ = options.expectedValues;

// The test harness function to use for testing.
this.testHarness_ = options.testHarness;

// Index into |expectedValue_| for comparing the actual and expected
// number of channels.
this.index_ = 0;
}

handleMessage_(event) {
let count = event.data.channelCount;
let finished = event.data.finished;

// If we're finished, end testing and close context if we're not yet
// closed.
if (finished) {
if (context.state != 'closed') {
context.close();
}

// Verify that we got the expected number of changes.
this.testHarness_.step(() => {
assert_equals(
this.index_, this.expectedValues_.length,
'Number of distinct values');
});
this.testHarness_.done();
return;
}

if (this.index_ >= this.expectedValues_.length) {
this.testHarness_.unreached_func('Failed');
} else {
// Verify that the number of channels now matches the expected
// number of channels.
this.testHarness_.step(() => {
assert_equals(
count, this.expectedValues_[this.index_],
`Test ${this.index_}: Number of convolver output channels:`);
});
}

// The sample rate MUST be a power of two to eliminate round-off when
// computing render boundaries but is otherwise arbitrary. And we only new
// a few blocks for rendering to see if things are working.
let sampleRate = 8192;
let renderLength = 10 * RENDER_QUANTUM_FRAMES;
++this.index_;
}
}

// Number of inputs for the ChannelMergerNode. Pretty arbitrary, but
// should not be 1.
let numberOfInputs = 7;
let context;

// How many frames the source should run. Arbitrary but should be more
// than a render quantum.
let sourceDurationFrames = 131;
async function testActiveProcessing() {
context = new AudioContext();

// Frame at which to connect the source to the merger
let connectFrame = 2 * RENDER_QUANTUM_FRAMES;
await context.audioWorklet.addModule(filePath);

// AudioProcessor that counts the number of channels on its single input.
let filePath =
'../the-audioworklet-interface/processors/input-count-processor.js';

audit.define(
{
label: 'Test',
description: 'Active processing for ChannelMergerNode'
},
async (task, should) => {
const context = new OfflineAudioContext({
numberOfChannels: numberOfInputs,
length: renderLength,
sampleRate: sampleRate
});
const src = new OscillatorNode(context);

// Number of inputs for the ChannelMergerNode. Pretty arbitrary, but
// should not be 1.
const numberOfInputs = 7;
const node =
new ChannelMergerNode(context, {numberOfInputs: numberOfInputs});

const testerNode = new ActiveProcessingTesterNode(context, {
expectedValues: [numberOfInputs, 1],
// Use as short a duration as possible to keep the test from taking
// too much time.
processorOptions: {testDuration: 1},
testHarness: async_test(
'ChannelMerger channel count changes to one when not actively processing'),
});

src.connect(node).connect(testerNode).connect(context.destination);
src.start();
// Stop the source after a short time so we can test the channel merger
// change to not actively processing.
src.stop(context.currentTime + .1);
}

// Don't mix the inputs to the destination!
context.destination.channelInterpretation = 'discrete';

await context.audioWorklet.addModule(filePath);

let src = new ConstantSourceNode(context);
let merger = new ChannelMergerNode(
context, {numberOfInputs: numberOfInputs});
let counter = new AudioWorkletNode(context, 'counter');

// Just to print a message that we created the graph with a
// convolver in it.
should(
() => {
merger.connect(counter).connect(context.destination);
},
`Construction of graph with ChannelMergerNode with ${
merger.numberOfInputs} inputs`)
.notThrow()

// Connect the source now and start it and let it run for
// |sourceDurationFrames| frames.
context.suspend(connectFrame / context.sampleRate)
.then(() => {
src.connect(merger, 0, 0);
src.start();
src.stop(
context.currentTime +
sourceDurationFrames / context.sampleRate);
})
.then(() => context.resume());

const renderedBuffer = await context.startRendering();
// The expected output is something like:
//
// 1, 1, 1,..., 7, 7, 7.,,,, 1, 1, 1
//
// When the merger has no inputs, it's not actively processing
// so it must output mono silence. After connecting a source,
// the number of channels of the output should be the same as
// the number of inputs to the merger. Finally, when the
// source stops, the merger is not actively processing anymore
// and should output mono silence again. For this test, we
// don't care too much how many different values there are.
// There just has to be at least one of each value, in the
// order given.
const output = renderedBuffer.getChannelData(0);

should(output, 'Number of output channels').containValues([
1, numberOfInputs, 1
]);

task.done();
});

audit.run();
testActiveProcessing();
</script>
</body>
</html>

0 comments on commit 8880ab2

Please sign in to comment.