Commit 710f745e authored by Raymond Toy's avatar Raymond Toy Committed by Commit Bot

Handle transferred channels in ConvolverNode

If any channel of an AudioBuffer is transferred (detached) when given
to a ConvolverNode, we treat it as if no buffer were given[1] and reset
the internal reverb to null so that the output is all zeroes.

[1] The spec on acquiring the contents of an AudioBuffer says if any
buffer is acquired, treat it as if all are.

Bug: 1134930
Change-Id: I413b9acf38bdf2efe2a466968ae9074df6956fd5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2450350
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Reviewed-by: default avatarHongchan Choi <hongchan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#814670}
parent 42ac9e51
...@@ -152,6 +152,31 @@ void ConvolverHandler::SetBuffer(AudioBuffer* buffer, ...@@ -152,6 +152,31 @@ void ConvolverHandler::SetBuffer(AudioBuffer* buffer,
// reference to it is kept for later use in that class. // reference to it is kept for later use in that class.
scoped_refptr<AudioBus> buffer_bus = scoped_refptr<AudioBus> buffer_bus =
AudioBus::Create(number_of_channels, buffer_length, false); AudioBus::Create(number_of_channels, buffer_length, false);
// Check to see if any of the channels have been transferred. Note that an
// AudioBuffer cannot be created with a length of 0, so if any channel has a
// length of 0, it was transferred.
bool any_buffer_detached = false;
for (unsigned i = 0; i < number_of_channels; ++i) {
for (unsigned i = 0; i < number_of_channels; ++i) {
if (buffer->getChannelData(i)->lengthAsSizeT() == 0) {
any_buffer_detached = true;
break;
}
}
}
if (any_buffer_detached) {
// If any channel is detached, we're supposed to treat it as if all were.
// This means the buffer effectively has length 0, which is the same as if
// no buffer were given.
BaseAudioContext::GraphAutoLocker context_locker(Context());
MutexLocker locker(process_lock_);
reverb_.reset();
shared_buffer_ = nullptr;
return;
}
for (unsigned i = 0; i < number_of_channels; ++i) { for (unsigned i = 0; i < number_of_channels; ++i) {
buffer_bus->SetChannelMemory(i, buffer->getChannelData(i).View()->Data(), buffer_bus->SetChannelMemory(i, buffer->getChannelData(i).View()->Data(),
buffer_length); buffer_length);
......
<!doctype html>
<html>
<head>
<title>
Test Convolver Output with Transferred Buffer
</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>
// Arbitrary sample rate.
const sampleRate = 16000;
// Number of frames to render. Just need to have at least 2 render
// quanta.
const lengthInFrames = 10 * RENDER_QUANTUM_FRAMES;
let audit = Audit.createTaskRunner();
// Buffer to use for the impulse response of a ConvolverNode.
let impulseBuffer;
// This sets up a worker to receive one channel of an AudioBuffer.
function setUpWorkerForTest() {
impulseBuffer = new AudioBuffer({
numberOfChannels: 2,
length: 2 * RENDER_QUANTUM_FRAMES,
sampleRate: sampleRate
});
// Just fill the buffer with a constant value; the contents shouldn't
// matter for this test since we're transferring one of the channels.
impulseBuffer.getChannelData(0).fill(1);
impulseBuffer.getChannelData(1).fill(2);
// We're going to transfer channel 0 to the worker, making it
// unavailable for the convolver
let data = impulseBuffer.getChannelData(0).buffer;
let string = [
'onmessage = function(e) {', ' postMessage(\'done\');', '};'
].join('\n');
let blobURL = URL.createObjectURL(new Blob([string]));
let worker = new Worker(blobURL);
worker.onmessage = workerReply;
worker.postMessage(data, [data]);
}
function workerReply() {
// Worker has received the message. Run the test.
audit.run();
}
audit.define(
{
label: 'Test Convolver with transferred buffer',
description: 'Output should be all zeroes'
},
async (task, should) => {
// Two channels so we can capture the output of the convolver with a
// stereo convolver.
let context = new OfflineAudioContext({
numberOfChannels: 2,
length: lengthInFrames,
sampleRate: sampleRate
});
// Use a simple constant source so we easily check that the
// convolver output is correct.
let source = new ConstantSourceNode(context);
// Create the convolver with the desired impulse response and
// disable normalization so we can easily check the output.
let conv = new ConvolverNode(
context, {disableNormalization: true, buffer: impulseBuffer});
source.connect(conv).connect(context.destination);
source.start();
let renderedBuffer = await context.startRendering();
// Get the actual data
let c0 = renderedBuffer.getChannelData(0);
let c1 = renderedBuffer.getChannelData(1);
// Since one channel was transferred, we must behave as if all were
// transferred. Hence, the output should be all zeroes for both
// channels.
should(c0, `Convolver channel 0 output[0:${c0.length - 1}]`)
.beConstantValueOf(0);
should(c1, `Convolver channel 1 output[0:${c1.length - 1}]`)
.beConstantValueOf(0);
task.done();
});
setUpWorkerForTest();
</script>
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment