Skip to content

Commit 9b7bb77

Browse files
committed
bug 1558123 create JSObjects for inputs/outputs AudioWorkletProcessor.process() parameters r=padenot,bzbarsky
Objects are retained for re-use so as to reduce garbage generation and other performance benefits. This is currently unspecified. https://github.com/WebAudio/web-audio-api/issues/1934 WebAudio/web-audio-api#1933 WebAudio/web-audio-api#1935 tracks specification of zero-channels of input when inputs are not actively processing. Differential Revision: https://phabricator.services.mozilla.com/D34836 UltraBlame original commit: 3cbbbf45fe77986a8fdeaa8d67fa95aded0427ab
1 parent cdbe526 commit 9b7bb77

File tree

1 file changed

+157
-4
lines changed

1 file changed

+157
-4
lines changed

dom/media/webaudio/AudioWorkletNode.cpp

Lines changed: 157 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,32 @@ class WorkletNodeEngine final : public AudioNodeEngine {
4343

4444
void NotifyForcedShutdown() override { ReleaseJSResources(); }
4545

46+
47+
48+
49+
50+
51+
52+
struct Channels {
53+
Vector<JS::PersistentRooted<JSObject*>, GUESS_AUDIO_CHANNELS>
54+
mFloat32Arrays;
55+
JS::PersistentRooted<JSObject*> mJSArray;
56+
57+
operator JS::Handle<JSObject*>() const { return mJSArray; }
58+
};
59+
struct Ports {
60+
Vector<Channels, 1> mPorts;
61+
JS::PersistentRooted<JSObject*> mJSArray;
62+
};
63+
4664
private:
4765
void SendProcessorError();
4866

4967
void ReleaseJSResources() {
68+
mInputs.mPorts.clearAndFree();
69+
mOutputs.mPorts.clearAndFree();
70+
mInputs.mJSArray.reset();
71+
mOutputs.mJSArray.reset();
5072
mGlobal = nullptr;
5173
mProcessor.reset();
5274
}
@@ -60,6 +82,16 @@ class WorkletNodeEngine final : public AudioNodeEngine {
6082

6183

6284

85+
86+
87+
88+
89+
90+
91+
92+
Ports mInputs;
93+
Ports mOutputs;
94+
6395
RefPtr<AudioWorkletGlobalScope> mGlobal;
6496
JS::PersistentRooted<JSObject*> mProcessor;
6597
};
@@ -82,25 +114,135 @@ void WorkletNodeEngine::SendProcessorError() {
82114
void WorkletNodeEngine::ConstructProcessor(
83115
AudioWorkletImpl* aWorkletImpl, const nsAString& aName,
84116
NotNull<StructuredCloneHolder*> aOptionsSerialization) {
117+
MOZ_ASSERT(mInputs.mPorts.empty() && mOutputs.mPorts.empty());
85118
RefPtr<AudioWorkletGlobalScope> global = aWorkletImpl->GetGlobalScope();
86119
MOZ_ASSERT(global);
87120
JS::RootingContext* cx = RootingCx();
88121
mProcessor.init(cx);
89-
if (!global->ConstructProcessor(aName, aOptionsSerialization, &mProcessor)) {
122+
if (!global->ConstructProcessor(aName, aOptionsSerialization, &mProcessor) ||
123+
124+
125+
NS_WARN_IF(!mInputs.mPorts.growBy(InputCount())) ||
126+
NS_WARN_IF(!mOutputs.mPorts.growBy(OutputCount()))) {
90127
SendProcessorError();
91128
return;
92129
}
93130
mGlobal = std::move(global);
131+
mInputs.mJSArray.init(cx);
132+
mOutputs.mJSArray.init(cx);
133+
for (auto& port : mInputs.mPorts) {
134+
port.mJSArray.init(cx);
135+
}
136+
for (auto& port : mOutputs.mPorts) {
137+
port.mJSArray.init(cx);
138+
}
139+
}
140+
141+
142+
143+
template <typename T>
144+
static bool SetArrayElements(JSContext* aCx, const T& aElements,
145+
JS::Handle<JSObject*> aArray) {
146+
for (size_t i = 0; i < aElements.length(); ++i) {
147+
if (!JS_DefineElement(aCx, aArray, i, aElements[i], JSPROP_ENUMERATE)) {
148+
return false;
149+
}
150+
}
151+
152+
return true;
153+
}
154+
155+
template <typename T>
156+
static bool PrepareArray(JSContext* aCx, const T& aElements,
157+
JS::MutableHandle<JSObject*> aArray) {
158+
size_t length = aElements.length();
159+
if (aArray) {
160+
161+
uint32_t oldLength;
162+
if (JS_GetArrayLength(aCx, aArray, &oldLength) &&
163+
(oldLength == length || JS_SetArrayLength(aCx, aArray, length)) &&
164+
SetArrayElements(aCx, aElements, aArray)) {
165+
return true;
166+
}
167+
168+
JS_ClearPendingException(aCx);
169+
}
170+
JSObject* array = JS_NewArrayObject(aCx, length);
171+
if (NS_WARN_IF(!array)) {
172+
return false;
173+
}
174+
aArray.set(array);
175+
return SetArrayElements(aCx, aElements, aArray);
176+
}
177+
178+
enum class ArrayElementInit { None, Zero };
179+
180+
181+
182+
183+
184+
185+
static bool PrepareBufferArrays(JSContext* aCx, Span<const AudioBlock> aBlocks,
186+
WorkletNodeEngine::Ports* aPorts,
187+
ArrayElementInit aInit) {
188+
for (size_t i = 0; i < aBlocks.Length(); ++i) {
189+
size_t channelCount = aBlocks[i].ChannelCount();
190+
WorkletNodeEngine::Channels& portRef = aPorts->mPorts[i];
191+
192+
auto& float32ArraysRef = portRef.mFloat32Arrays;
193+
for (auto& channelRef : float32ArraysRef) {
194+
uint32_t length = JS_GetTypedArrayLength(channelRef);
195+
if (length != WEBAUDIO_BLOCK_SIZE) {
196+
197+
JSObject* array = JS_NewFloat32Array(aCx, WEBAUDIO_BLOCK_SIZE);
198+
if (NS_WARN_IF(!array)) {
199+
return false;
200+
}
201+
channelRef = array;
202+
} else if (aInit == ArrayElementInit::Zero) {
203+
204+
JS::AutoCheckCannotGC nogc;
205+
bool isShared;
206+
float* elementData =
207+
JS_GetFloat32ArrayData(channelRef, &isShared, nogc);
208+
MOZ_ASSERT(!isShared);
209+
std::fill_n(elementData, WEBAUDIO_BLOCK_SIZE, 0.0f);
210+
}
211+
}
212+
213+
if (NS_WARN_IF(!float32ArraysRef.reserve(channelCount))) {
214+
return false;
215+
}
216+
while (float32ArraysRef.length() < channelCount) {
217+
JSObject* array = JS_NewFloat32Array(aCx, WEBAUDIO_BLOCK_SIZE);
218+
if (NS_WARN_IF(!array)) {
219+
return false;
220+
}
221+
float32ArraysRef.infallibleEmplaceBack(aCx, array);
222+
}
223+
224+
float32ArraysRef.shrinkTo(channelCount);
225+
226+
if (NS_WARN_IF(!PrepareArray(aCx, float32ArraysRef, &portRef.mJSArray))) {
227+
return false;
228+
}
229+
}
230+
231+
return !(NS_WARN_IF(!PrepareArray(aCx, aPorts->mPorts, &aPorts->mJSArray)));
232+
}
233+
234+
static void ProduceSilence(Span<AudioBlock> aOutput) {
235+
for (AudioBlock& output : aOutput) {
236+
output.SetNull(WEBAUDIO_BLOCK_SIZE);
237+
}
94238
}
95239

96240
void WorkletNodeEngine::ProcessBlocksOnPorts(AudioNodeStream* aStream,
97241
Span<const AudioBlock> aInput,
98242
Span<AudioBlock> aOutput,
99243
bool* aFinished) {
100244
if (!mProcessor) {
101-
for (AudioBlock& output : aOutput) {
102-
output.SetNull(WEBAUDIO_BLOCK_SIZE);
103-
}
245+
ProduceSilence(aOutput);
104246
return;
105247
}
106248

@@ -116,6 +258,17 @@ void WorkletNodeEngine::ProcessBlocksOnPorts(AudioNodeStream* aStream,
116258
output.AllocateChannels(1);
117259
}
118260
}
261+
262+
AutoEntryScript aes(mGlobal, "Worklet Process");
263+
JSContext* cx = aes.cx();
264+
265+
if (!PrepareBufferArrays(cx, aInput, &mInputs, ArrayElementInit::None) ||
266+
!PrepareBufferArrays(cx, aOutput, &mOutputs, ArrayElementInit::Zero)) {
267+
268+
SendProcessorError();
269+
ProduceSilence(aOutput);
270+
return;
271+
}
119272
}
120273

121274
AudioWorkletNode::AudioWorkletNode(AudioContext* aAudioContext,

0 commit comments

Comments
 (0)