Skip to content

Commit d378462

Browse files
hochCommit Bot
authored and
Commit Bot
committed
Avoid memory allocation in AudioWorkletProcessor.process()
Based on the spec [1], the current implementation of AudioWorkletProcessor needs to create a new data container (i.e. WebIDL sequence<>) for input, output, and param arrays. With the new spec change [2], this CL changes the overall design of the audio processing callback: 1. Moves the processing call from AudioWorkletGlobalScope to AudioWorkletProcessor object. 2. AudioWorkletProcessor now keeps the data container within the object and allocate memory when it is needed. The preliminary benchmark shows the sizable improvement in the audio stream quality. The glitch score (= buffer underrun/total callback) is improved by ~9x in the low-tier machine. [3] This is an API change [4], but the real world impact would be negligible because there's no functionality change. [1]: https://webaudio.github.io/web-audio-api/#dom-audioworkletprocessor-process-inputs-outputs-parameters-inputs [2]: WebAudio/web-audio-api#1933 (comment) [3]: https://bugs.chromium.org/p/chromium/issues/detail?id=1086665#c2 [4]: https://chromestatus.com/feature/5647541725036544 Bug: 1071085, 1086665 Change-Id: I3e664754973d4d86649d38c1807c6b9d7830fb96 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2218702 Reviewed-by: Raymond Toy <[email protected]> Reviewed-by: Yuki Shiino <[email protected]> Commit-Queue: Hongchan Choi <[email protected]> Cr-Commit-Position: refs/heads/master@{#779052}
1 parent 6e4709a commit d378462

File tree

8 files changed

+646
-202
lines changed

8 files changed

+646
-202
lines changed

third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope.cc

Lines changed: 0 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,13 @@
1212
#include "third_party/blink/renderer/bindings/core/v8/idl_types.h"
1313
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
1414
#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
15-
#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
16-
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
1715
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
18-
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_param_descriptor.h"
1916
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_worklet_processor.h"
2017
#include "third_party/blink/renderer/bindings/modules/v8/v8_blink_audio_worklet_process_callback.h"
2118
#include "third_party/blink/renderer/bindings/modules/v8/v8_blink_audio_worklet_processor_constructor.h"
22-
#include "third_party/blink/renderer/core/messaging/message_port.h"
23-
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
24-
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
2519
#include "third_party/blink/renderer/core/workers/worker_thread.h"
26-
#include "third_party/blink/renderer/modules/webaudio/audio_buffer.h"
27-
#include "third_party/blink/renderer/modules/webaudio/audio_worklet_processor.h"
2820
#include "third_party/blink/renderer/modules/webaudio/audio_worklet_processor_definition.h"
29-
#include "third_party/blink/renderer/modules/webaudio/audio_worklet_processor_error_state.h"
3021
#include "third_party/blink/renderer/modules/webaudio/cross_thread_audio_worklet_processor_info.h"
31-
#include "third_party/blink/renderer/platform/audio/audio_bus.h"
32-
#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
3322
#include "third_party/blink/renderer/platform/bindings/callback_method_retriever.h"
3423

3524
namespace blink {
@@ -217,180 +206,6 @@ AudioWorkletProcessor* AudioWorkletGlobalScope::CreateProcessor(
217206
return processor;
218207
}
219208

220-
bool AudioWorkletGlobalScope::Process(
221-
AudioWorkletProcessor* processor,
222-
Vector<scoped_refptr<AudioBus>>* input_buses,
223-
Vector<AudioBus*>* output_buses,
224-
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map) {
225-
CHECK_GE(input_buses->size(), 0u);
226-
CHECK_GE(output_buses->size(), 0u);
227-
228-
ScriptState* script_state = ScriptController()->GetScriptState();
229-
ScriptState::Scope scope(script_state);
230-
231-
v8::Isolate* isolate = script_state->GetIsolate();
232-
v8::Local<v8::Context> current_context = script_state->GetContext();
233-
AudioWorkletProcessorDefinition* definition =
234-
FindDefinition(processor->Name());
235-
DCHECK(definition);
236-
237-
v8::TryCatch try_catch(isolate);
238-
try_catch.SetVerbose(true);
239-
240-
// Prepare arguments of JS callback (inputs, outputs and param_values) with
241-
// directly using V8 API because the overhead of
242-
// ToV8(HeapVector<HeapVector<DOMFloat32Array>>) is not negligible and there
243-
// is no need to externalize the array buffers.
244-
245-
// 1st arg of JS callback: inputs
246-
v8::Local<v8::Array> inputs = v8::Array::New(isolate, input_buses->size());
247-
uint32_t input_bus_index = 0;
248-
for (auto input_bus : *input_buses) {
249-
// If |input_bus| is null, it means the input port is not connected, and
250-
// the corresponding array for that input port should have zero channel.
251-
unsigned number_of_channels = input_bus ? input_bus->NumberOfChannels() : 0;
252-
size_t bus_length = input_bus ? input_bus->length() : 0;
253-
254-
v8::Local<v8::Array> channels = v8::Array::New(isolate, number_of_channels);
255-
bool success;
256-
if (!inputs
257-
->CreateDataProperty(current_context, input_bus_index++, channels)
258-
.To(&success)) {
259-
return false;
260-
}
261-
for (uint32_t channel_index = 0; channel_index < number_of_channels;
262-
++channel_index) {
263-
v8::Local<v8::ArrayBuffer> array_buffer =
264-
v8::ArrayBuffer::New(isolate, bus_length * sizeof(float));
265-
v8::Local<v8::Float32Array> float32_array =
266-
v8::Float32Array::New(array_buffer, 0, bus_length);
267-
if (!channels
268-
->CreateDataProperty(current_context, channel_index,
269-
float32_array)
270-
.To(&success)) {
271-
return false;
272-
}
273-
const v8::ArrayBuffer::Contents& contents = array_buffer->GetContents();
274-
if (input_bus) {
275-
memcpy(contents.Data(), input_bus->Channel(channel_index)->Data(),
276-
bus_length * sizeof(float));
277-
}
278-
}
279-
}
280-
281-
// 2nd arg of JS callback: outputs
282-
v8::Local<v8::Array> outputs = v8::Array::New(isolate, output_buses->size());
283-
uint32_t output_bus_counter = 0;
284-
285-
// |output_array_buffers| stores underlying array buffers so that we can copy
286-
// them back to |output_buses|.
287-
Vector<Vector<v8::Local<v8::ArrayBuffer>>> output_array_buffers;
288-
output_array_buffers.ReserveInitialCapacity(output_buses->size());
289-
290-
for (auto* const output_bus : *output_buses) {
291-
output_array_buffers.UncheckedAppend(Vector<v8::Local<v8::ArrayBuffer>>());
292-
output_array_buffers.back().ReserveInitialCapacity(
293-
output_bus->NumberOfChannels());
294-
v8::Local<v8::Array> channels =
295-
v8::Array::New(isolate, output_bus->NumberOfChannels());
296-
bool success;
297-
if (!outputs
298-
->CreateDataProperty(current_context, output_bus_counter++,
299-
channels)
300-
.To(&success)) {
301-
return false;
302-
}
303-
for (uint32_t channel_index = 0;
304-
channel_index < output_bus->NumberOfChannels(); ++channel_index) {
305-
v8::Local<v8::ArrayBuffer> array_buffer =
306-
v8::ArrayBuffer::New(isolate, output_bus->length() * sizeof(float));
307-
v8::Local<v8::Float32Array> float32_array =
308-
v8::Float32Array::New(array_buffer, 0, output_bus->length());
309-
if (!channels
310-
->CreateDataProperty(current_context, channel_index,
311-
float32_array)
312-
.To(&success)) {
313-
return false;
314-
}
315-
output_array_buffers.back().UncheckedAppend(array_buffer);
316-
}
317-
}
318-
319-
// 3rd arg of JS callback: param_values
320-
v8::Local<v8::Object> param_values = v8::Object::New(isolate);
321-
for (const auto& param : *param_value_map) {
322-
const String& param_name = param.key;
323-
const AudioFloatArray* param_array = param.value.get();
324-
325-
// If the AudioParam is constant, then the param array should have length 1.
326-
// Manually check to see if the parameter is truly constant.
327-
unsigned array_size = 1;
328-
329-
for (unsigned k = 1; k < param_array->size(); ++k) {
330-
if (param_array->Data()[k] != param_array->Data()[0]) {
331-
array_size = param_array->size();
332-
break;
333-
}
334-
}
335-
336-
v8::Local<v8::ArrayBuffer> array_buffer =
337-
v8::ArrayBuffer::New(isolate, array_size * sizeof(float));
338-
v8::Local<v8::Float32Array> float32_array =
339-
v8::Float32Array::New(array_buffer, 0, array_size);
340-
bool success;
341-
if (!param_values
342-
->CreateDataProperty(current_context,
343-
V8String(isolate, param_name.IsolatedCopy()),
344-
float32_array)
345-
.To(&success)) {
346-
return false;
347-
}
348-
const v8::ArrayBuffer::Contents& contents = array_buffer->GetContents();
349-
memcpy(contents.Data(), param_array->Data(), array_size * sizeof(float));
350-
}
351-
352-
// Perform JS function process() in AudioWorkletProcessor instance. The actual
353-
// V8 operation happens here to make the AudioWorkletProcessor class a thin
354-
// wrapper of v8::Object instance.
355-
ScriptValue result;
356-
if (!definition->ProcessFunction()
357-
->Invoke(processor, ScriptValue(isolate, inputs),
358-
ScriptValue(isolate, outputs),
359-
ScriptValue(isolate, param_values))
360-
.To(&result)) {
361-
// process() method call failed for some reason or an exception was thrown
362-
// by the user supplied code. Disable the processor to exclude it from the
363-
// subsequent rendering task.
364-
processor->SetErrorState(AudioWorkletProcessorErrorState::kProcessError);
365-
return false;
366-
}
367-
368-
// Copy |sequence<sequence<Float32Array>>| back to the original
369-
// |Vector<AudioBus*>|. While iterating, we also check if the size of backing
370-
// array buffer is changed. When the size does not match, silence the buffer.
371-
for (uint32_t output_bus_index = 0; output_bus_index < output_buses->size();
372-
++output_bus_index) {
373-
AudioBus* output_bus = (*output_buses)[output_bus_index];
374-
for (uint32_t channel_index = 0;
375-
channel_index < output_bus->NumberOfChannels(); ++channel_index) {
376-
const v8::ArrayBuffer::Contents& contents =
377-
output_array_buffers[output_bus_index][channel_index]->GetContents();
378-
const size_t size = output_bus->length() * sizeof(float);
379-
if (contents.ByteLength() == size) {
380-
memcpy(output_bus->Channel(channel_index)->MutableData(),
381-
contents.Data(), size);
382-
} else {
383-
memset(output_bus->Channel(channel_index)->MutableData(), 0, size);
384-
}
385-
}
386-
}
387-
388-
// Return the value from the user-supplied |process()| function. It is
389-
// used to maintain the lifetime of the node and the processor.
390-
DCHECK(!try_catch.HasCaught());
391-
return result.V8Value()->IsTrue();
392-
}
393-
394209
AudioWorkletProcessorDefinition* AudioWorkletGlobalScope::FindDefinition(
395210
const String& name) {
396211
return processor_definition_map_.at(name);

third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
namespace blink {
1717

18-
class AudioBus;
1918
class AudioWorkletProcessor;
2019
class AudioWorkletProcessorDefinition;
2120
class CrossThreadAudioWorkletProcessorInfo;
@@ -77,14 +76,6 @@ class MODULES_EXPORT AudioWorkletGlobalScope final : public WorkletGlobalScope {
7776
MessagePortChannel,
7877
scoped_refptr<SerializedScriptValue> node_options);
7978

80-
// Invokes the JS audio processing function from an instance of
81-
// AudioWorkletProcessor, along with given AudioBuffer from the audio graph.
82-
bool Process(
83-
AudioWorkletProcessor*,
84-
Vector<scoped_refptr<AudioBus>>* input_buses,
85-
Vector<AudioBus*>* output_buses,
86-
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map);
87-
8879
AudioWorkletProcessorDefinition* FindDefinition(const String& name);
8980

9081
unsigned NumberOfRegisteredDefinitions();

third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ class AudioWorkletGlobalScopeTest : public PageTestBase {
296296
EXPECT_TRUE(processor);
297297

298298
Vector<scoped_refptr<AudioBus>> input_buses;
299-
Vector<AudioBus*> output_buses;
299+
Vector<scoped_refptr<AudioBus>> output_buses;
300300
HashMap<String, std::unique_ptr<AudioFloatArray>> param_data_map;
301301
scoped_refptr<AudioBus> input_bus =
302302
AudioBus::Create(1, kRenderQuantumFrames);

third_party/blink/renderer/modules/webaudio/audio_worklet_node.cc

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ void AudioWorkletHandler::Process(uint32_t frames_to_process) {
9595
// the rendering in the AudioWorkletGlobalScope.
9696
if (processor_ && !processor_->hasErrorOccured()) {
9797
Vector<scoped_refptr<AudioBus>> input_buses;
98-
Vector<AudioBus*> output_buses;
98+
Vector<scoped_refptr<AudioBus>> output_buses;
9999
for (unsigned i = 0; i < NumberOfInputs(); ++i) {
100100
// If the input is not connected, inform the processor of that
101101
// fact by setting the bus to null.
@@ -104,8 +104,7 @@ void AudioWorkletHandler::Process(uint32_t frames_to_process) {
104104
input_buses.push_back(bus);
105105
}
106106
for (unsigned i = 0; i < NumberOfOutputs(); ++i)
107-
output_buses.push_back(Output(i).Bus());
108-
107+
output_buses.push_back(WrapRefCounted(Output(i).Bus()));
109108
for (const auto& param_name : param_value_map_.Keys()) {
110109
auto* const param_handler = param_handler_map_.at(param_name);
111110
AudioFloatArray* param_values = param_value_map_.at(param_name);

0 commit comments

Comments
 (0)