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