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