1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "AudioBuffer.h"
8 #include "mozilla/dom/AudioBufferBinding.h"
9 #include "jsfriendapi.h"
10 #include "js/ArrayBuffer.h" // JS::StealArrayBufferContents
11 #include "js/experimental/TypedData.h" // JS_NewFloat32Array, JS_GetFloat32ArrayData, JS_GetTypedArrayLength, JS_GetArrayBufferViewBuffer
12 #include "mozilla/ErrorResult.h"
13 #include "AudioSegment.h"
14 #include "AudioChannelFormat.h"
15 #include "mozilla/PodOperations.h"
16 #include "mozilla/CheckedInt.h"
17 #include "mozilla/HoldDropJSObjects.h"
18 #include "mozilla/MemoryReporting.h"
19 #include "AudioNodeEngine.h"
20 #include "nsPrintfCString.h"
21 #include "nsTHashSet.h"
22 #include <numeric>
23
24 namespace mozilla::dom {
25
26 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioBuffer)
27
28 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioBuffer)
29 NS_IMPL_CYCLE_COLLECTION_UNLINK(mJSChannels)
30 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
31 tmp->ClearJSChannels();
32 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
33
34 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioBuffer)
35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
36
37 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AudioBuffer)
38 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
39 for (uint32_t i = 0; i < tmp->mJSChannels.Length(); ++i) {
40 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSChannels[i])
41 }
42 NS_IMPL_CYCLE_COLLECTION_TRACE_END
43
44 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioBuffer, AddRef)
45 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioBuffer, Release)
46
47 /**
48 * AudioBuffers can be shared between AudioContexts, so we need a separate
49 * mechanism to track their memory usage. This thread-safe class keeps track of
50 * all the AudioBuffers, and gets called back by the memory reporting system
51 * when a memory report is needed, reporting how much memory is used by the
52 * buffers backing AudioBuffer objects. */
53 class AudioBufferMemoryTracker : public nsIMemoryReporter {
54 NS_DECL_THREADSAFE_ISUPPORTS
55 NS_DECL_NSIMEMORYREPORTER
56
57 private:
58 AudioBufferMemoryTracker();
59 virtual ~AudioBufferMemoryTracker();
60
61 public:
62 /* Those methods can be called on any thread. */
63 static void RegisterAudioBuffer(const AudioBuffer* aAudioBuffer);
64 static void UnregisterAudioBuffer(const AudioBuffer* aAudioBuffer);
65
66 private:
67 static AudioBufferMemoryTracker* GetInstance();
68 /* Those methods must be called with the lock held. */
69 void RegisterAudioBufferInternal(const AudioBuffer* aAudioBuffer);
70 /* Returns the number of buffers still present in the hash table. */
71 uint32_t UnregisterAudioBufferInternal(const AudioBuffer* aAudioBuffer);
72 void Init();
73
74 /* This protects all members of this class. */
75 static StaticMutex sMutex;
76 static StaticRefPtr<AudioBufferMemoryTracker> sSingleton;
77 nsTHashSet<const AudioBuffer*> mBuffers;
78 };
79
80 StaticRefPtr<AudioBufferMemoryTracker> AudioBufferMemoryTracker::sSingleton;
81 StaticMutex AudioBufferMemoryTracker::sMutex;
82
83 NS_IMPL_ISUPPORTS(AudioBufferMemoryTracker, nsIMemoryReporter);
84
GetInstance()85 AudioBufferMemoryTracker* AudioBufferMemoryTracker::GetInstance() {
86 sMutex.AssertCurrentThreadOwns();
87 if (!sSingleton) {
88 sSingleton = new AudioBufferMemoryTracker();
89 sSingleton->Init();
90 }
91 return sSingleton;
92 }
93
94 AudioBufferMemoryTracker::AudioBufferMemoryTracker() = default;
95
Init()96 void AudioBufferMemoryTracker::Init() { RegisterWeakMemoryReporter(this); }
97
~AudioBufferMemoryTracker()98 AudioBufferMemoryTracker::~AudioBufferMemoryTracker() {
99 UnregisterWeakMemoryReporter(this);
100 }
101
RegisterAudioBuffer(const AudioBuffer * aAudioBuffer)102 void AudioBufferMemoryTracker::RegisterAudioBuffer(
103 const AudioBuffer* aAudioBuffer) {
104 StaticMutexAutoLock lock(sMutex);
105 AudioBufferMemoryTracker* tracker = AudioBufferMemoryTracker::GetInstance();
106 tracker->RegisterAudioBufferInternal(aAudioBuffer);
107 }
108
UnregisterAudioBuffer(const AudioBuffer * aAudioBuffer)109 void AudioBufferMemoryTracker::UnregisterAudioBuffer(
110 const AudioBuffer* aAudioBuffer) {
111 StaticMutexAutoLock lock(sMutex);
112 AudioBufferMemoryTracker* tracker = AudioBufferMemoryTracker::GetInstance();
113 uint32_t count;
114 count = tracker->UnregisterAudioBufferInternal(aAudioBuffer);
115 if (count == 0) {
116 sSingleton = nullptr;
117 }
118 }
119
RegisterAudioBufferInternal(const AudioBuffer * aAudioBuffer)120 void AudioBufferMemoryTracker::RegisterAudioBufferInternal(
121 const AudioBuffer* aAudioBuffer) {
122 sMutex.AssertCurrentThreadOwns();
123 mBuffers.Insert(aAudioBuffer);
124 }
125
UnregisterAudioBufferInternal(const AudioBuffer * aAudioBuffer)126 uint32_t AudioBufferMemoryTracker::UnregisterAudioBufferInternal(
127 const AudioBuffer* aAudioBuffer) {
128 sMutex.AssertCurrentThreadOwns();
129 mBuffers.Remove(aAudioBuffer);
130 return mBuffers.Count();
131 }
132
MOZ_DEFINE_MALLOC_SIZE_OF(AudioBufferMemoryTrackerMallocSizeOf)133 MOZ_DEFINE_MALLOC_SIZE_OF(AudioBufferMemoryTrackerMallocSizeOf)
134
135 NS_IMETHODIMP
136 AudioBufferMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport,
137 nsISupports* aData, bool) {
138 const size_t amount =
139 std::accumulate(mBuffers.cbegin(), mBuffers.cend(), size_t(0),
140 [](size_t val, const AudioBuffer* buffer) {
141 return val + buffer->SizeOfIncludingThis(
142 AudioBufferMemoryTrackerMallocSizeOf);
143 });
144
145 MOZ_COLLECT_REPORT("explicit/webaudio/audiobuffer", KIND_HEAP, UNITS_BYTES,
146 amount, "Memory used by AudioBuffer objects (Web Audio).");
147
148 return NS_OK;
149 }
150
AudioBuffer(nsPIDOMWindowInner * aWindow,uint32_t aNumberOfChannels,uint32_t aLength,float aSampleRate,ErrorResult & aRv)151 AudioBuffer::AudioBuffer(nsPIDOMWindowInner* aWindow,
152 uint32_t aNumberOfChannels, uint32_t aLength,
153 float aSampleRate, ErrorResult& aRv)
154 : mOwnerWindow(do_GetWeakReference(aWindow)), mSampleRate(aSampleRate) {
155 // Note that a buffer with zero channels is permitted here for the sake of
156 // AudioProcessingEvent, where channel counts must match parameters passed
157 // to createScriptProcessor(), one of which may be zero.
158 if (aSampleRate < WebAudioUtils::MinSampleRate ||
159 aSampleRate > WebAudioUtils::MaxSampleRate) {
160 aRv.ThrowNotSupportedError(
161 nsPrintfCString("Sample rate (%g) is out of range", aSampleRate));
162 return;
163 }
164
165 if (aNumberOfChannels > WebAudioUtils::MaxChannelCount) {
166 aRv.ThrowNotSupportedError(nsPrintfCString(
167 "Number of channels (%u) is out of range", aNumberOfChannels));
168 return;
169 }
170
171 if (!aLength || aLength > INT32_MAX) {
172 aRv.ThrowNotSupportedError(
173 nsPrintfCString("Length (%u) is out of range", aLength));
174 return;
175 }
176
177 mSharedChannels.mDuration = aLength;
178 mJSChannels.SetLength(aNumberOfChannels);
179 mozilla::HoldJSObjects(this);
180 AudioBufferMemoryTracker::RegisterAudioBuffer(this);
181 }
182
~AudioBuffer()183 AudioBuffer::~AudioBuffer() {
184 AudioBufferMemoryTracker::UnregisterAudioBuffer(this);
185 ClearJSChannels();
186 mozilla::DropJSObjects(this);
187 }
188
189 /* static */
Constructor(const GlobalObject & aGlobal,const AudioBufferOptions & aOptions,ErrorResult & aRv)190 already_AddRefed<AudioBuffer> AudioBuffer::Constructor(
191 const GlobalObject& aGlobal, const AudioBufferOptions& aOptions,
192 ErrorResult& aRv) {
193 if (!aOptions.mNumberOfChannels) {
194 aRv.ThrowNotSupportedError("Must have nonzero number of channels");
195 return nullptr;
196 }
197
198 nsCOMPtr<nsPIDOMWindowInner> window =
199 do_QueryInterface(aGlobal.GetAsSupports());
200
201 return Create(window, aOptions.mNumberOfChannels, aOptions.mLength,
202 aOptions.mSampleRate, aRv);
203 }
204
ClearJSChannels()205 void AudioBuffer::ClearJSChannels() { mJSChannels.Clear(); }
206
SetSharedChannels(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)207 void AudioBuffer::SetSharedChannels(
208 already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer) {
209 RefPtr<ThreadSharedFloatArrayBufferList> buffer = aBuffer;
210 uint32_t channelCount = buffer->GetChannels();
211 mSharedChannels.mChannelData.SetLength(channelCount);
212 for (uint32_t i = 0; i < channelCount; ++i) {
213 mSharedChannels.mChannelData[i] = buffer->GetData(i);
214 }
215 mSharedChannels.mBuffer = std::move(buffer);
216 mSharedChannels.mBufferFormat = AUDIO_FORMAT_FLOAT32;
217 }
218
219 /* static */
Create(nsPIDOMWindowInner * aWindow,uint32_t aNumberOfChannels,uint32_t aLength,float aSampleRate,already_AddRefed<ThreadSharedFloatArrayBufferList> aInitialContents,ErrorResult & aRv)220 already_AddRefed<AudioBuffer> AudioBuffer::Create(
221 nsPIDOMWindowInner* aWindow, uint32_t aNumberOfChannels, uint32_t aLength,
222 float aSampleRate,
223 already_AddRefed<ThreadSharedFloatArrayBufferList> aInitialContents,
224 ErrorResult& aRv) {
225 RefPtr<ThreadSharedFloatArrayBufferList> initialContents = aInitialContents;
226 RefPtr<AudioBuffer> buffer =
227 new AudioBuffer(aWindow, aNumberOfChannels, aLength, aSampleRate, aRv);
228 if (aRv.Failed()) {
229 return nullptr;
230 }
231
232 if (initialContents) {
233 MOZ_ASSERT(initialContents->GetChannels() == aNumberOfChannels);
234 buffer->SetSharedChannels(initialContents.forget());
235 }
236
237 return buffer.forget();
238 }
239
240 /* static */
Create(nsPIDOMWindowInner * aWindow,float aSampleRate,AudioChunk && aInitialContents)241 already_AddRefed<AudioBuffer> AudioBuffer::Create(
242 nsPIDOMWindowInner* aWindow, float aSampleRate,
243 AudioChunk&& aInitialContents) {
244 AudioChunk initialContents = aInitialContents;
245 ErrorResult rv;
246 RefPtr<AudioBuffer> buffer =
247 new AudioBuffer(aWindow, initialContents.ChannelCount(),
248 initialContents.mDuration, aSampleRate, rv);
249 if (rv.Failed()) {
250 return nullptr;
251 }
252 buffer->mSharedChannels = std::move(aInitialContents);
253
254 return buffer.forget();
255 }
256
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)257 JSObject* AudioBuffer::WrapObject(JSContext* aCx,
258 JS::Handle<JSObject*> aGivenProto) {
259 return AudioBuffer_Binding::Wrap(aCx, this, aGivenProto);
260 }
261
CopyChannelDataToFloat(const AudioChunk & aChunk,uint32_t aChannel,uint32_t aSrcOffset,float * aOutput,uint32_t aLength)262 static void CopyChannelDataToFloat(const AudioChunk& aChunk, uint32_t aChannel,
263 uint32_t aSrcOffset, float* aOutput,
264 uint32_t aLength) {
265 MOZ_ASSERT(aChunk.mVolume == 1.0f);
266 if (aChunk.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
267 mozilla::PodCopy(
268 aOutput, aChunk.ChannelData<float>()[aChannel] + aSrcOffset, aLength);
269 } else {
270 MOZ_ASSERT(aChunk.mBufferFormat == AUDIO_FORMAT_S16);
271 ConvertAudioSamples(aChunk.ChannelData<int16_t>()[aChannel] + aSrcOffset,
272 aOutput, aLength);
273 }
274 }
275
RestoreJSChannelData(JSContext * aJSContext)276 bool AudioBuffer::RestoreJSChannelData(JSContext* aJSContext) {
277 nsPIDOMWindowInner* global = GetParentObject();
278 if (!global || !global->AsGlobal()->HasJSGlobal()) {
279 return false;
280 }
281
282 JSAutoRealm ar(aJSContext, global->AsGlobal()->GetGlobalJSObject());
283
284 for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
285 if (mJSChannels[i]) {
286 // Already have data in JS array.
287 continue;
288 }
289
290 // The following code first zeroes the array and then copies our data
291 // into it. We could avoid this with additional JS APIs to construct
292 // an array (or ArrayBuffer) containing initial data.
293 JS::Rooted<JSObject*> array(aJSContext,
294 JS_NewFloat32Array(aJSContext, Length()));
295 if (!array) {
296 return false;
297 }
298 if (!mSharedChannels.IsNull()) {
299 // "4. Attach ArrayBuffers containing copies of the data to the
300 // AudioBuffer, to be returned by the next call to getChannelData."
301 JS::AutoCheckCannotGC nogc;
302 bool isShared;
303 float* jsData = JS_GetFloat32ArrayData(array, &isShared, nogc);
304 MOZ_ASSERT(!isShared); // Was created as unshared above
305 CopyChannelDataToFloat(mSharedChannels, i, 0, jsData, Length());
306 }
307 mJSChannels[i] = array;
308 }
309
310 mSharedChannels.SetNull(Length());
311
312 return true;
313 }
314
CopyFromChannel(const Float32Array & aDestination,uint32_t aChannelNumber,uint32_t aBufferOffset,ErrorResult & aRv)315 void AudioBuffer::CopyFromChannel(const Float32Array& aDestination,
316 uint32_t aChannelNumber,
317 uint32_t aBufferOffset, ErrorResult& aRv) {
318 if (aChannelNumber >= NumberOfChannels()) {
319 aRv.ThrowIndexSizeError(
320 nsPrintfCString("Channel number (%u) is out of range", aChannelNumber));
321 return;
322 }
323
324 JS::AutoCheckCannotGC nogc;
325 aDestination.ComputeState();
326
327 int64_t length = Length();
328 int64_t offset = aBufferOffset;
329 int64_t destLength = aDestination.Length();
330 uint32_t count = std::max(int64_t(0), std::min(length - offset, destLength));
331
332 JSObject* channelArray = mJSChannels[aChannelNumber];
333 if (channelArray) {
334 if (JS_GetTypedArrayLength(channelArray) != Length()) {
335 // The array's buffer was detached.
336 return;
337 }
338 bool isShared = false;
339 const float* sourceData =
340 JS_GetFloat32ArrayData(channelArray, &isShared, nogc);
341 // The sourceData arrays should all have originated in
342 // RestoreJSChannelData, where they are created unshared.
343 MOZ_ASSERT(!isShared);
344 PodMove(aDestination.Data(), sourceData + aBufferOffset, count);
345 return;
346 }
347
348 if (!mSharedChannels.IsNull()) {
349 CopyChannelDataToFloat(mSharedChannels, aChannelNumber, aBufferOffset,
350 aDestination.Data(), count);
351 return;
352 }
353
354 PodZero(aDestination.Data(), count);
355 }
356
CopyToChannel(JSContext * aJSContext,const Float32Array & aSource,uint32_t aChannelNumber,uint32_t aBufferOffset,ErrorResult & aRv)357 void AudioBuffer::CopyToChannel(JSContext* aJSContext,
358 const Float32Array& aSource,
359 uint32_t aChannelNumber, uint32_t aBufferOffset,
360 ErrorResult& aRv) {
361 if (aChannelNumber >= NumberOfChannels()) {
362 aRv.ThrowIndexSizeError(
363 nsPrintfCString("Channel number (%u) is out of range", aChannelNumber));
364 return;
365 }
366
367 if (!RestoreJSChannelData(aJSContext)) {
368 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
369 return;
370 }
371
372 JS::AutoCheckCannotGC nogc;
373 JSObject* channelArray = mJSChannels[aChannelNumber];
374 if (JS_GetTypedArrayLength(channelArray) != Length()) {
375 // The array's buffer was detached.
376 return;
377 }
378
379 aSource.ComputeState();
380 int64_t length = JS_GetTypedArrayLength(channelArray);
381 int64_t offset = aBufferOffset;
382 int64_t srcLength = aSource.Length();
383 uint32_t count = std::max(int64_t(0), std::min(length - offset, srcLength));
384 bool isShared = false;
385 float* channelData = JS_GetFloat32ArrayData(channelArray, &isShared, nogc);
386 // The channelData arrays should all have originated in
387 // RestoreJSChannelData, where they are created unshared.
388 MOZ_ASSERT(!isShared);
389 PodMove(channelData + aBufferOffset, aSource.Data(), count);
390 }
391
GetChannelData(JSContext * aJSContext,uint32_t aChannel,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)392 void AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel,
393 JS::MutableHandle<JSObject*> aRetval,
394 ErrorResult& aRv) {
395 if (aChannel >= NumberOfChannels()) {
396 aRv.ThrowIndexSizeError(
397 nsPrintfCString("Channel number (%u) is out of range", aChannel));
398 return;
399 }
400
401 if (!RestoreJSChannelData(aJSContext)) {
402 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
403 return;
404 }
405
406 aRetval.set(mJSChannels[aChannel]);
407 }
408
409 already_AddRefed<ThreadSharedFloatArrayBufferList>
StealJSArrayDataIntoSharedChannels(JSContext * aJSContext)410 AudioBuffer::StealJSArrayDataIntoSharedChannels(JSContext* aJSContext) {
411 nsPIDOMWindowInner* global = GetParentObject();
412 if (!global || !global->AsGlobal()->HasJSGlobal()) {
413 return nullptr;
414 }
415
416 JSAutoRealm ar(aJSContext, global->AsGlobal()->GetGlobalJSObject());
417
418 // "1. If any of the AudioBuffer's ArrayBuffer have been detached, abort
419 // these steps, and return a zero-length channel data buffers to the
420 // invoker."
421 for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
422 JSObject* channelArray = mJSChannels[i];
423 if (!channelArray || Length() != JS_GetTypedArrayLength(channelArray)) {
424 // Either empty buffer or one of the arrays' buffers was detached.
425 return nullptr;
426 }
427 }
428
429 // "2. Detach all ArrayBuffers for arrays previously returned by
430 // getChannelData on this AudioBuffer."
431 // "3. Retain the underlying data buffers from those ArrayBuffers and return
432 // references to them to the invoker."
433 RefPtr<ThreadSharedFloatArrayBufferList> result =
434 new ThreadSharedFloatArrayBufferList(mJSChannels.Length());
435 for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
436 JS::Rooted<JSObject*> arrayBufferView(aJSContext, mJSChannels[i]);
437 bool isSharedMemory;
438 JS::Rooted<JSObject*> arrayBuffer(
439 aJSContext, JS_GetArrayBufferViewBuffer(aJSContext, arrayBufferView,
440 &isSharedMemory));
441 // The channel data arrays should all have originated in
442 // RestoreJSChannelData, where they are created unshared.
443 MOZ_ASSERT(!isSharedMemory);
444 auto stolenData = arrayBuffer
445 ? static_cast<float*>(JS::StealArrayBufferContents(
446 aJSContext, arrayBuffer))
447 : nullptr;
448 if (stolenData) {
449 result->SetData(i, stolenData, js_free, stolenData);
450 } else {
451 NS_ASSERTION(i == 0, "some channels lost when contents not acquired");
452 return nullptr;
453 }
454 }
455
456 for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
457 mJSChannels[i] = nullptr;
458 }
459
460 return result.forget();
461 }
462
GetThreadSharedChannelsForRate(JSContext * aJSContext)463 const AudioChunk& AudioBuffer::GetThreadSharedChannelsForRate(
464 JSContext* aJSContext) {
465 if (mSharedChannels.IsNull()) {
466 // mDuration is set in constructor
467 RefPtr<ThreadSharedFloatArrayBufferList> buffer =
468 StealJSArrayDataIntoSharedChannels(aJSContext);
469
470 if (buffer) {
471 SetSharedChannels(buffer.forget());
472 }
473 }
474
475 return mSharedChannels;
476 }
477
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const478 size_t AudioBuffer::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
479 size_t amount = aMallocSizeOf(this);
480 amount += mJSChannels.ShallowSizeOfExcludingThis(aMallocSizeOf);
481 amount += mSharedChannels.SizeOfExcludingThis(aMallocSizeOf, false);
482 return amount;
483 }
484
485 } // namespace mozilla::dom
486