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