bug 1391482 use AudioChunk to generalize AudioBuffer shared channel storage r=padenot
authorKarl Tomlinson <karlt+@karlt.net>
Thu, 03 Aug 2017 17:58:23 +1200
changeset 377729 f8fc4ff76ed9d4ba404c138d303a5044fbc8ea9f
parent 377728 a4e0ecc18caec2c6772e227e8fb89f2751523feb
child 377730 043d871b9b8173d4ba8b9412a1e400ae362a31ed
push id94338
push userkwierso@gmail.com
push dateThu, 31 Aug 2017 02:58:58 +0000
treeherdermozilla-inbound@9ca18987dabb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1391482
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1391482 use AudioChunk to generalize AudioBuffer shared channel storage r=padenot Although the AudioChunk buffer is still always a ThreadSharedFloatArrayBufferList, buffers with 16-bit data will be permitted in a future patch. MozReview-Commit-ID: FEGKMiQOCpR
dom/media/SharedBuffer.h
dom/media/webaudio/AudioBuffer.cpp
dom/media/webaudio/AudioBuffer.h
dom/media/webaudio/AudioNodeEngine.h
--- a/dom/media/SharedBuffer.h
+++ b/dom/media/SharedBuffer.h
@@ -9,28 +9,33 @@
 #include "mozilla/CheckedInt.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 
 class AudioBlockBuffer;
+class ThreadSharedFloatArrayBufferList;
 
 /**
  * Base class for objects with a thread-safe refcount and a virtual
  * destructor.
  */
 class ThreadSharedObject {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadSharedObject)
 
   bool IsShared() { return mRefCnt.get() > 1; }
 
   virtual AudioBlockBuffer* AsAudioBlockBuffer() { return nullptr; };
+  virtual ThreadSharedFloatArrayBufferList* AsThreadSharedFloatArrayBufferList()
+  {
+    return nullptr;
+  };
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   {
     return 0;
   }
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   {
--- a/dom/media/webaudio/AudioBuffer.cpp
+++ b/dom/media/webaudio/AudioBuffer.cpp
@@ -154,26 +154,21 @@ AudioBufferMemoryTracker::CollectReports
     "Memory used by AudioBuffer objects (Web Audio).");
 
   return NS_OK;
 }
 
 AudioBuffer::AudioBuffer(nsPIDOMWindowInner* aWindow,
                          uint32_t aNumberOfChannels,
                          uint32_t aLength,
-                         float aSampleRate,
-                         already_AddRefed<ThreadSharedFloatArrayBufferList>
-                           aInitialContents)
+                         float aSampleRate)
   : mOwnerWindow(do_GetWeakReference(aWindow)),
-    mSharedChannels(aInitialContents),
-    mLength(aLength),
     mSampleRate(aSampleRate)
 {
-  MOZ_ASSERT(!mSharedChannels ||
-             mSharedChannels->GetChannels() == aNumberOfChannels);
+  mSharedChannels.mDuration = aLength;
   mJSChannels.SetLength(aNumberOfChannels);
   mozilla::HoldJSObjects(this);
   AudioBufferMemoryTracker::RegisterAudioBuffer(this);
 }
 
 AudioBuffer::~AudioBuffer()
 {
   AudioBufferMemoryTracker::UnregisterAudioBuffer(this);
@@ -199,37 +194,58 @@ AudioBuffer::Constructor(const GlobalObj
 }
 
 void
 AudioBuffer::ClearJSChannels()
 {
   mJSChannels.Clear();
 }
 
+void
+AudioBuffer::SetSharedChannels(
+  already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
+{
+  RefPtr<ThreadSharedFloatArrayBufferList> buffer = aBuffer;
+  uint32_t channelCount = buffer->GetChannels();
+  mSharedChannels.mChannelData.SetLength(channelCount);
+  for (uint32_t i = 0; i < channelCount; ++i) {
+    mSharedChannels.mChannelData[i] = buffer->GetData(i);
+  }
+  mSharedChannels.mBuffer = buffer.forget();
+  mSharedChannels.mVolume = 1.0f;
+  mSharedChannels.mBufferFormat = AUDIO_FORMAT_FLOAT32;
+}
+
 /* static */ already_AddRefed<AudioBuffer>
 AudioBuffer::Create(nsPIDOMWindowInner* aWindow, uint32_t aNumberOfChannels,
                     uint32_t aLength, float aSampleRate,
                     already_AddRefed<ThreadSharedFloatArrayBufferList>
                       aInitialContents,
                     ErrorResult& aRv)
 {
+  RefPtr<ThreadSharedFloatArrayBufferList> initialContents = aInitialContents;
+
   // Note that a buffer with zero channels is permitted here for the sake of
   // AudioProcessingEvent, where channel counts must match parameters passed
   // to createScriptProcessor(), one of which may be zero.
   if (aSampleRate < WebAudioUtils::MinSampleRate ||
       aSampleRate > WebAudioUtils::MaxSampleRate ||
       aNumberOfChannels > WebAudioUtils::MaxChannelCount ||
       !aLength || aLength > INT32_MAX) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
   RefPtr<AudioBuffer> buffer =
-    new AudioBuffer(aWindow, aNumberOfChannels, aLength, aSampleRate,
-                    Move(aInitialContents));
+    new AudioBuffer(aWindow, aNumberOfChannels, aLength, aSampleRate);
+
+  if (initialContents) {
+    MOZ_ASSERT(initialContents->GetChannels() == aNumberOfChannels);
+    buffer->SetSharedChannels(initialContents.forget());
+  }
 
   return buffer.forget();
 }
 
 JSObject*
 AudioBuffer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return AudioBufferBinding::Wrap(aCx, this, aGivenProto);
@@ -243,69 +259,73 @@ AudioBuffer::RestoreJSChannelData(JSCont
       // Already have data in JS array.
       continue;
     }
 
     // The following code first zeroes the array and then copies our data
     // into it. We could avoid this with additional JS APIs to construct
     // an array (or ArrayBuffer) containing initial data.
     JS::Rooted<JSObject*> array(aJSContext,
-                                JS_NewFloat32Array(aJSContext, mLength));
+                                JS_NewFloat32Array(aJSContext, Length()));
     if (!array) {
       return false;
     }
-    if (mSharedChannels) {
+    if (!mSharedChannels.IsNull()) {
       // "4. Attach ArrayBuffers containing copies of the data to the
       // AudioBuffer, to be returned by the next call to getChannelData."
-      const float* data = mSharedChannels->GetData(i);
+      MOZ_ASSERT(mSharedChannels.mVolume == 1.0f);
+      const float* data = mSharedChannels.ChannelData<float>()[i];
       JS::AutoCheckCannotGC nogc;
       bool isShared;
-      mozilla::PodCopy(JS_GetFloat32ArrayData(array, &isShared, nogc), data, mLength);
+      mozilla::PodCopy(JS_GetFloat32ArrayData(array, &isShared, nogc),
+                       data, Length());
       MOZ_ASSERT(!isShared); // Was created as unshared above
     }
     mJSChannels[i] = array;
   }
 
-  mSharedChannels = nullptr;
+  mSharedChannels.mBuffer = nullptr;
+  mSharedChannels.mChannelData.Clear();
 
   return true;
 }
 
 void
 AudioBuffer::CopyFromChannel(const Float32Array& aDestination, uint32_t aChannelNumber,
                              uint32_t aStartInChannel, ErrorResult& aRv)
 {
   aDestination.ComputeLengthAndData();
 
   uint32_t length = aDestination.Length();
   CheckedInt<uint32_t> end = aStartInChannel;
   end += length;
   if (aChannelNumber >= NumberOfChannels() ||
-      !end.isValid() || end.value() > mLength) {
+      !end.isValid() || end.value() > Length()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   JS::AutoCheckCannotGC nogc;
   JSObject* channelArray = mJSChannels[aChannelNumber];
   const float* sourceData = nullptr;
   if (channelArray) {
-    if (JS_GetTypedArrayLength(channelArray) != mLength) {
+    if (JS_GetTypedArrayLength(channelArray) != Length()) {
       // The array's buffer was detached.
       aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
       return;
     }
 
     bool isShared = false;
     sourceData = JS_GetFloat32ArrayData(channelArray, &isShared, nogc);
     // The sourceData arrays should all have originated in
     // RestoreJSChannelData, where they are created unshared.
     MOZ_ASSERT(!isShared);
-  } else if (mSharedChannels) {
-    sourceData = mSharedChannels->GetData(aChannelNumber);
+  } else if (!mSharedChannels.IsNull()) {
+    MOZ_ASSERT(mSharedChannels.mVolume == 1.0f);
+    sourceData = mSharedChannels.ChannelData<float>()[aChannelNumber];
   }
 
   if (sourceData) {
     PodMove(aDestination.Data(), sourceData + aStartInChannel, length);
   } else {
     PodZero(aDestination.Data(), length);
   }
 }
@@ -316,29 +336,29 @@ AudioBuffer::CopyToChannel(JSContext* aJ
                            ErrorResult& aRv)
 {
   aSource.ComputeLengthAndData();
 
   uint32_t length = aSource.Length();
   CheckedInt<uint32_t> end = aStartInChannel;
   end += length;
   if (aChannelNumber >= NumberOfChannels() ||
-      !end.isValid() || end.value() > mLength) {
+      !end.isValid() || end.value() > Length()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   if (!RestoreJSChannelData(aJSContext)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   JS::AutoCheckCannotGC nogc;
   JSObject* channelArray = mJSChannels[aChannelNumber];
-  if (JS_GetTypedArrayLength(channelArray) != mLength) {
+  if (JS_GetTypedArrayLength(channelArray) != Length()) {
     // The array's buffer was detached.
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   bool isShared = false;
   float* channelData = JS_GetFloat32ArrayData(channelArray, &isShared, nogc);
   // The channelData arrays should all have originated in
@@ -368,17 +388,17 @@ AudioBuffer::GetChannelData(JSContext* a
 already_AddRefed<ThreadSharedFloatArrayBufferList>
 AudioBuffer::StealJSArrayDataIntoSharedChannels(JSContext* aJSContext)
 {
   // "1. If any of the AudioBuffer's ArrayBuffer have been detached, abort
   // these steps, and return a zero-length channel data buffers to the
   // invoker."
   for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
     JSObject* channelArray = mJSChannels[i];
-    if (!channelArray || mLength != JS_GetTypedArrayLength(channelArray)) {
+    if (!channelArray || Length() != JS_GetTypedArrayLength(channelArray)) {
       // Either empty buffer or one of the arrays' buffers was detached.
       return nullptr;
     }
   }
 
   // "2. Detach all ArrayBuffers for arrays previously returned by
   // getChannelData on this AudioBuffer."
   // "3. Retain the underlying data buffers from those ArrayBuffers and return
@@ -412,28 +432,32 @@ AudioBuffer::StealJSArrayDataIntoSharedC
   }
 
   return result.forget();
 }
 
 ThreadSharedFloatArrayBufferList*
 AudioBuffer::GetThreadSharedChannelsForRate(JSContext* aJSContext)
 {
-  if (!mSharedChannels) {
-    mSharedChannels = StealJSArrayDataIntoSharedChannels(aJSContext);
+  if (mSharedChannels.IsNull()) {
+    // mDuration is set in constructor
+    RefPtr<ThreadSharedFloatArrayBufferList> buffer =
+      StealJSArrayDataIntoSharedChannels(aJSContext);
+
+    if (buffer) {
+      SetSharedChannels(buffer.forget());
+    }
   }
 
-  return mSharedChannels;
+  return mSharedChannels.mBuffer->AsThreadSharedFloatArrayBufferList();
 }
 
 size_t
 AudioBuffer::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = aMallocSizeOf(this);
   amount += mJSChannels.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  if (mSharedChannels) {
-    amount += mSharedChannels->SizeOfIncludingThis(aMallocSizeOf);
-  }
+  amount += mSharedChannels.SizeOfExcludingThis(aMallocSizeOf, false);
   return amount;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/AudioBuffer.h
+++ b/dom/media/webaudio/AudioBuffer.h
@@ -2,16 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef AudioBuffer_h_
 #define AudioBuffer_h_
 
+#include "AudioSegment.h"
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/StaticMutex.h"
 #include "nsTArray.h"
 #include "js/TypeDecls.h"
 #include "mozilla/MemoryReporting.h"
@@ -72,22 +73,22 @@ public:
 
   float SampleRate() const
   {
     return mSampleRate;
   }
 
   uint32_t Length() const
   {
-    return mLength;
+    return mSharedChannels.mDuration;
   }
 
   double Duration() const
   {
-    return mLength / static_cast<double> (mSampleRate);
+    return Length() / static_cast<double> (mSampleRate);
   }
 
   uint32_t NumberOfChannels() const
   {
     return mJSChannels.Length();
   }
 
   /**
@@ -107,36 +108,37 @@ public:
   /**
    * Returns a ThreadSharedFloatArrayBufferList containing the sample data.
    * Can return null if there is no data.
    */
   ThreadSharedFloatArrayBufferList* GetThreadSharedChannelsForRate(JSContext* aContext);
 
 protected:
   AudioBuffer(nsPIDOMWindowInner* aWindow, uint32_t aNumberOfChannels,
-              uint32_t aLength, float aSampleRate,
-              already_AddRefed<ThreadSharedFloatArrayBufferList>
-                aInitialContents);
+              uint32_t aLength, float aSampleRate);
   ~AudioBuffer();
 
+  void
+  SetSharedChannels(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer);
+
   bool RestoreJSChannelData(JSContext* aJSContext);
 
   already_AddRefed<ThreadSharedFloatArrayBufferList>
   StealJSArrayDataIntoSharedChannels(JSContext* aJSContext);
 
   void ClearJSChannels();
 
-  nsWeakPtr mOwnerWindow;
   // Float32Arrays
   AutoTArray<JS::Heap<JSObject*>, 2> mJSChannels;
+  // mSharedChannels aggregates the data from mJSChannels. This is non-null
+  // if and only if the mJSChannels' buffers are detached, but its mDuration
+  // member keeps the buffer length regardless of whether the buffer is
+  // provided by mJSChannels or mSharedChannels.
+  AudioChunk mSharedChannels;
 
-  // mSharedChannels aggregates the data from mJSChannels. This is non-null
-  // if and only if the mJSChannels' buffers are detached.
-  RefPtr<ThreadSharedFloatArrayBufferList> mSharedChannels;
-
-  uint32_t mLength;
+  nsWeakPtr mOwnerWindow;
   float mSampleRate;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/media/webaudio/AudioNodeEngine.h
+++ b/dom/media/webaudio/AudioNodeEngine.h
@@ -43,16 +43,22 @@ public:
   /**
    * Create with buffers suitable for transfer to
    * JS_NewArrayBufferWithContents().  The buffer contents are uninitialized
    * and so should be set using GetDataForWrite().
    */
   static already_AddRefed<ThreadSharedFloatArrayBufferList>
   Create(uint32_t aChannelCount, size_t aLength, const mozilla::fallible_t&);
 
+  ThreadSharedFloatArrayBufferList*
+  AsThreadSharedFloatArrayBufferList() override
+  {
+    return this;
+  };
+
   struct Storage final
   {
     Storage() :
       mDataToFree(nullptr),
       mFree(nullptr),
       mSampleData(nullptr)
     {}
     ~Storage() {