bug use AudioChunk to generalize AudioBuffer shared channel storage r?padenot draft
authorKarl Tomlinson <karlt+@karlt.net>
Thu, 03 Aug 2017 17:58:23 +1200
changeset 645165 63c08b4a1c1bb904515c358f256fd54a1d063fda
parent 645164 52b98a4c3b3266905c8cb4aa222769cd3ecce7fd
child 645166 7f70cb68fa9fcb5bfdfba590831daef97a499941
push id73684
push userktomlinson@mozilla.com
push dateFri, 11 Aug 2017 22:29:12 +0000
reviewerspadenot
milestone57.0a1
bug 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.
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
@@ -8,28 +8,33 @@
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/mozalloc.h"
 #include "nsCOMPtr.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() {