tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AudioBuffer.cpp (17784B)


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