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