Commit 9d4a34ca authored by Raymond Toy's avatar Raymond Toy Committed by Commit Bot

Set array length to 0 for disconnected worklet input

The spec says that if an input to an AudioWorkletNode is not
connected, the input should be represented as an input with a single
channel with a Float32Array of length 0.

Make it so by passing in nullptr for that input to the
AudioWorkletGlobalScope::Process so it can fill the arrays
appropriately.

Bug: 817145
Change-Id: I82407ed0a9fe84c5012333af8af27f4dd08d29b8
Reviewed-on: https://chromium-review.googlesource.com/953970Reviewed-by: default avatarHongchan Choi <hongchan@chromium.org>
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543506}
parent 38c6a5e5
<!DOCTYPE html>
<html>
<head>
<title>
Test AudioWorkletNode's Disconnected Input Array Length
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// Arbitrary numbers used to align the test with render quantum boundary.
// The sample rate is a power of two to eliminate roundoff in computing
// the suspend time needed for the test.
let sampleRate = 16384;
let renderLength = 8 * RENDER_QUANTUM_FRAMES;
let context;
let filePath = 'processors/input-length-processor.js';
let testChannelValues = [1, 2, 3];
// Creates a 3-channel buffer and play with BufferSourceNode. The source
// goes through a bypass AudioWorkletNode (gain value of 1).
audit.define(
{
label: 'test',
description:
'Input array length should be zero for disconnected input'
},
(task, should) => {
context = new OfflineAudioContext({
numberOfChannels: 1,
length: renderLength,
sampleRate: sampleRate
});
context.audioWorklet.addModule(filePath).then(() => {
let sourceNode = new ConstantSourceNode(context);
let workletNode =
new AudioWorkletNode(context, 'input-length-processor');
workletNode.connect(context.destination);
// Connect the source now.
let connectFrame = RENDER_QUANTUM_FRAMES;
context.suspend(connectFrame / sampleRate)
.then(() => {
sourceNode.connect(workletNode);
})
.then(() => context.resume());
;
// Then disconnect the source after a few renders
let disconnectFrame = 3 * RENDER_QUANTUM_FRAMES;
context.suspend(disconnectFrame / sampleRate)
.then(() => {
sourceNode.disconnect(workletNode);
})
.then(() => context.resume());
sourceNode.start();
context.startRendering()
.then(resultBuffer => {
let data = resultBuffer.getChannelData(0);
should(
data.slice(0, connectFrame),
'Before connecting the source: Input array length')
.beConstantValueOf(0);
// Find where the output is no longer 0.
let nonZeroIndex = data.findIndex(x => x > 0);
should(nonZeroIndex, 'First non-zero output')
.beEqualTo(connectFrame);
should(
data.slice(
nonZeroIndex,
nonZeroIndex + (disconnectFrame - connectFrame)),
'While source is connected: Input array length')
.beConstantValueOf(RENDER_QUANTUM_FRAMES);
should(
data.slice(disconnectFrame),
'After disconnecting the source: Input array length')
.beConstantValueOf(0);
})
.then(() => task.done());
});
});
audit.run();
</script>
</body>
</html>
/**
* @class InputLengthProcessor
* @extends AudioWorkletProcessor
*
* This processor class just sets the output to the length of the
* input array for verifying that the input length changes when the
* input is disconnected.
*/
class InputLengthProcessor extends AudioWorkletProcessor {
constructor() {
super();
}
process(inputs, outputs, parameters) {
let input = inputs[0];
let output = outputs[0];
// Set output channel to the length of the input channel array.
output[0].fill(input[0].length);
return true;
}
}
registerProcessor('input-length-processor', InputLengthProcessor);
......@@ -215,21 +215,26 @@ bool AudioWorkletGlobalScope::Process(
// 1st arg of JS callback: inputs
v8::Local<v8::Array> inputs = v8::Array::New(isolate, input_buses->size());
uint32_t input_bus_index = 0;
for (const auto& input_bus : *input_buses) {
v8::Local<v8::Array> channels =
v8::Array::New(isolate, input_bus->NumberOfChannels());
for (const auto input_bus : *input_buses) {
// If |input_bus| is null, then the input is not connected, and
// the array for that input should have one channel and a length
// of 0.
unsigned number_of_channels = input_bus ? input_bus->NumberOfChannels() : 1;
size_t bus_length = input_bus ? input_bus->length() : 0;
v8::Local<v8::Array> channels = v8::Array::New(isolate, number_of_channels);
bool success;
if (!inputs
->CreateDataProperty(current_context, input_bus_index++, channels)
.To(&success)) {
return false;
}
for (uint32_t channel_index = 0;
channel_index < input_bus->NumberOfChannels(); ++channel_index) {
for (uint32_t channel_index = 0; channel_index < number_of_channels;
++channel_index) {
v8::Local<v8::ArrayBuffer> array_buffer =
v8::ArrayBuffer::New(isolate, input_bus->length() * sizeof(float));
v8::ArrayBuffer::New(isolate, bus_length * sizeof(float));
v8::Local<v8::Float32Array> float32_array =
v8::Float32Array::New(array_buffer, 0, input_bus->length());
v8::Float32Array::New(array_buffer, 0, bus_length);
if (!channels
->CreateDataProperty(current_context, channel_index,
float32_array)
......@@ -237,8 +242,10 @@ bool AudioWorkletGlobalScope::Process(
return false;
}
const v8::ArrayBuffer::Contents& contents = array_buffer->GetContents();
memcpy(contents.Data(), input_bus->Channel(channel_index)->Data(),
input_bus->length() * sizeof(float));
if (input_bus) {
memcpy(contents.Data(), input_bus->Channel(channel_index)->Data(),
bus_length * sizeof(float));
}
}
}
......
......@@ -81,12 +81,16 @@ void AudioWorkletHandler::Process(size_t frames_to_process) {
// Render and update the node state when the processor is ready with no error.
if (processor_ && !processor_->hasErrorOccured()) {
Vector<AudioBus*> inputBuses;
Vector<AudioBus*> outputBuses;
for (unsigned i = 0; i < NumberOfInputs(); ++i)
inputBuses.push_back(Input(i).Bus());
Vector<AudioBus*> input_buses;
Vector<AudioBus*> output_buses;
for (unsigned i = 0; i < NumberOfInputs(); ++i) {
// If the input is not connected, inform the processor of that
// fact by setting the bus to null.
AudioBus* bus = Input(i).IsConnected() ? Input(i).Bus() : nullptr;
input_buses.push_back(bus);
}
for (unsigned i = 0; i < NumberOfOutputs(); ++i)
outputBuses.push_back(Output(i).Bus());
output_buses.push_back(Output(i).Bus());
for (const auto& param_name : param_value_map_.Keys()) {
const auto param_handler = param_handler_map_.at(param_name);
......@@ -103,7 +107,7 @@ void AudioWorkletHandler::Process(size_t frames_to_process) {
// Run the render code and check the state of processor. Finish the
// processor if needed.
if (!processor_->Process(&inputBuses, &outputBuses, &param_value_map_) ||
if (!processor_->Process(&input_buses, &output_buses, &param_value_map_) ||
processor_->hasErrorOccured()) {
FinishProcessorOnRenderThread();
}
......
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