tor-browser

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

AudioData.cpp (28774B)


      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 "mozilla/dom/AudioData.h"
      8 
      9 #include <utility>
     10 
     11 #include "AudioSampleFormat.h"
     12 #include "WebCodecsUtils.h"
     13 #include "js/StructuredClone.h"
     14 #include "mozilla/Assertions.h"
     15 #include "mozilla/CheckedInt.h"
     16 #include "mozilla/Logging.h"
     17 #include "mozilla/Maybe.h"
     18 #include "mozilla/Result.h"
     19 #include "mozilla/dom/AudioDataBinding.h"
     20 #include "mozilla/dom/BufferSourceBinding.h"
     21 #include "mozilla/dom/Promise.h"
     22 #include "mozilla/dom/StructuredCloneTags.h"
     23 #include "nsFmtString.h"
     24 #include "nsStringFwd.h"
     25 #include "nsTHashSet.h"
     26 
     27 extern mozilla::LazyLogModule gWebCodecsLog;
     28 
     29 namespace mozilla::dom {
     30 
     31 #define LOGD(fmt, ...) \
     32  MOZ_LOG_FMT(gWebCodecsLog, LogLevel::Debug, fmt, ##__VA_ARGS__)
     33 
     34 #define LOGE(fmt, ...) \
     35  MOZ_LOG_FMT(gWebCodecsLog, LogLevel::Error, fmt, ##__VA_ARGS__)
     36 
     37 [[nodiscard]] Result<Ok, nsCString> LogAndReturnErr(const char* aLiteral) {
     38  MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("%s", aLiteral));
     39  return Err(nsCString(aLiteral));
     40 }
     41 
     42 template <typename... Args>
     43 [[nodiscard]] Result<Ok, nsCString> LogAndReturnErr(
     44    fmt::format_string<Args...> aFmt, Args&&... aArgs) {
     45  nsAutoCStringN<100> str;
     46  str.AppendVfmt(aFmt, fmt::make_format_args(aArgs...));
     47  MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("%s", str.get()));
     48  return Err(str);
     49 }
     50 
     51 // Only needed for refcounted objects.
     52 //
     53 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AudioData)
     54 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioData)
     55  tmp->CloseIfNeeded();
     56  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
     57  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
     58 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioData)
     60  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
     61 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     62 
     63 NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioData)
     64 // AudioData should be released as soon as its refcount drops to zero,
     65 // without waiting for async deletion by the cycle collector, since it may hold
     66 // a large-size PCM buffer.
     67 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(AudioData, CloseIfNeeded())
     68 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioData)
     69  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     70  NS_INTERFACE_MAP_ENTRY(nsISupports)
     71 NS_INTERFACE_MAP_END
     72 
     73 /*
     74 * W3C Webcodecs AudioData implementation
     75 */
     76 
     77 AudioData::AudioData(nsIGlobalObject* aParent,
     78                     const AudioDataSerializedData& aData)
     79    : mParent(aParent),
     80      mTimestamp(aData.mTimestamp),
     81      mNumberOfChannels(aData.mNumberOfChannels),
     82      mNumberOfFrames(aData.mNumberOfFrames),
     83      mSampleRate(aData.mSampleRate),
     84      mAudioSampleFormat(aData.mAudioSampleFormat),
     85      // The resource is not copied, but referenced
     86      mResource(aData.mResource) {
     87  MOZ_ASSERT(mParent);
     88  MOZ_ASSERT(mResource,
     89             "Resource should always be present then receiving a transfer.");
     90 }
     91 
     92 AudioData::AudioData(const AudioData& aOther)
     93    : mParent(aOther.mParent),
     94      mTimestamp(aOther.mTimestamp),
     95      mNumberOfChannels(aOther.mNumberOfChannels),
     96      mNumberOfFrames(aOther.mNumberOfFrames),
     97      mSampleRate(aOther.mSampleRate),
     98      mAudioSampleFormat(aOther.mAudioSampleFormat),
     99      // The resource is not copied, but referenced
    100      mResource(aOther.mResource) {
    101  MOZ_ASSERT(mParent);
    102 }
    103 
    104 Result<already_AddRefed<AudioDataResource>, nsresult>
    105 AudioDataResource::Construct(const OwningAllowSharedBufferSource& aInit) {
    106  FallibleTArray<uint8_t> copied;
    107  uint8_t* rv = ProcessTypedArraysFixed(
    108      aInit, [&](const Span<uint8_t>& aData) -> uint8_t* {
    109        return copied.AppendElements(aData.Elements(), aData.Length(),
    110                                     fallible);
    111      });
    112  if (!rv) {
    113    LOGE("AudioDataResource::Ctor: OOM");
    114    return Err(NS_ERROR_OUT_OF_MEMORY);
    115  }
    116  return MakeAndAddRef<AudioDataResource>(std::move(copied));
    117 }
    118 
    119 AudioData::AudioData(
    120    nsIGlobalObject* aParent,
    121    already_AddRefed<mozilla::dom::AudioDataResource> aResource,
    122    const AudioDataInit& aInit)
    123    : mParent(aParent),
    124      mTimestamp(aInit.mTimestamp),
    125      mNumberOfChannels(aInit.mNumberOfChannels),
    126      mNumberOfFrames(aInit.mNumberOfFrames),
    127      mSampleRate(aInit.mSampleRate),
    128      mAudioSampleFormat(Some(aInit.mFormat)),
    129      mResource(std::move(aResource)) {
    130  MOZ_ASSERT(mParent);
    131 }
    132 
    133 AudioData::AudioData(
    134    nsIGlobalObject* aParent,
    135    already_AddRefed<mozilla::dom::AudioDataResource> aResource,
    136    int64_t aTimestamp, uint32_t aNumberOfChannels, uint32_t aNumberOfFrames,
    137    float aSampleRate, AudioSampleFormat aAudioSampleFormat)
    138    : mParent(aParent),
    139      mTimestamp(aTimestamp),
    140      mNumberOfChannels(aNumberOfChannels),
    141      mNumberOfFrames(aNumberOfFrames),
    142      mSampleRate(aSampleRate),
    143      mAudioSampleFormat(Some(aAudioSampleFormat)),
    144      mResource(aResource) {
    145  MOZ_ASSERT(mParent);
    146 }
    147 
    148 nsIGlobalObject* AudioData::GetParentObject() const {
    149  AssertIsOnOwningThread();
    150 
    151  return mParent.get();
    152 }
    153 
    154 JSObject* AudioData::WrapObject(JSContext* aCx,
    155                                JS::Handle<JSObject*> aGivenProto) {
    156  AssertIsOnOwningThread();
    157 
    158  return AudioData_Binding::Wrap(aCx, this, aGivenProto);
    159 }
    160 
    161 Result<Ok, nsCString> IsValidAudioDataInit(const AudioDataInit& aInit) {
    162  // The sample rate is an uint32_t within Gecko
    163  uint32_t integerSampleRate = SaturatingCast<uint32_t>(aInit.mSampleRate);
    164  if (integerSampleRate == 0) {
    165    return LogAndReturnErr("sampleRate must be positive");
    166  }
    167  if (aInit.mNumberOfFrames == 0) {
    168    return LogAndReturnErr("mNumberOfFrames must be positive");
    169  }
    170  if (aInit.mNumberOfChannels == 0) {
    171    return LogAndReturnErr("mNumberOfChannels must be positive");
    172  }
    173 
    174  CheckedInt<uint64_t> bytesNeeded = aInit.mNumberOfFrames;
    175  bytesNeeded *= aInit.mNumberOfChannels;
    176  bytesNeeded *= BytesPerSamples(aInit.mFormat);
    177 
    178  if (!bytesNeeded.isValid()) {
    179    return LogAndReturnErr(
    180        FMT_STRING("Overflow when computing the number of bytes needed to hold "
    181                   "audio samples ({}*{}*{})"),
    182        aInit.mNumberOfFrames, aInit.mNumberOfChannels,
    183        BytesPerSamples(aInit.mFormat));
    184  }
    185 
    186  uint64_t arraySizeBytes = ProcessTypedArraysFixed(
    187      aInit.mData, [&](const Span<uint8_t>& aData) -> uint64_t {
    188        return aData.LengthBytes();
    189      });
    190  if (arraySizeBytes < bytesNeeded.value()) {
    191    return LogAndReturnErr(
    192        FMT_STRING("Array of size {} not big enough, should be at least {}"),
    193        arraySizeBytes, bytesNeeded.value());
    194  }
    195  return Ok();
    196 }
    197 
    198 /* static */
    199 already_AddRefed<AudioData> AudioData::Constructor(const GlobalObject& aGlobal,
    200                                                   const AudioDataInit& aInit,
    201                                                   ErrorResult& aRv) {
    202  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    203  LOGD("[{}] AudioData(fmt: {}, rate: {}, ch: {}, ts: {})",
    204       fmt::ptr(global.get()), GetEnumString(aInit.mFormat).get(),
    205       aInit.mSampleRate, aInit.mNumberOfChannels, aInit.mTimestamp);
    206  if (!global) {
    207    LOGE("Global unavailable");
    208    aRv.Throw(NS_ERROR_FAILURE);
    209    return nullptr;
    210  }
    211  nsString errorMessage;
    212  auto rv = IsValidAudioDataInit(aInit);
    213  if (rv.isErr()) {
    214    LOGD("AudioData::Constructor failure (IsValidAudioDataInit)");
    215    aRv.ThrowTypeError(rv.inspectErr());
    216    return nullptr;
    217  }
    218 
    219  nsTHashSet<const JSObject*> transferSet;
    220  for (const auto& buffer : aInit.mTransfer) {
    221    if (transferSet.Contains(buffer.Obj())) {
    222      // 9.2.2.2. If init.transfer contains more than one reference to
    223      // the same ArrayBuffer, then throw a DataCloneError DOMException.
    224      LOGE("AudioData Constructor -- duplicate transferred ArrayBuffer");
    225      aRv.ThrowDataCloneError(
    226          "Transfer contains duplicate ArrayBuffer objects");
    227      return nullptr;
    228    }
    229    transferSet.Insert(buffer.Obj());
    230  }
    231 
    232  for (const auto& buffer : aInit.mTransfer) {
    233    if (JS::IsDetachedArrayBufferObject(buffer.Obj())) {
    234      // 9.2.2.3.1. If [[Detached]] internal slot is true, then
    235      // throw a DataCloneError DOMException.
    236      LOGE("AudioData Constructor -- detached transferred ArrayBuffer");
    237      aRv.ThrowDataCloneError("Transfer contains detached ArrayBuffer objects");
    238      return nullptr;
    239    }
    240  }
    241 
    242  // 9.2.2.4.7. If init.transfer contains an ArrayBuffer referenced by init.data
    243  // the User Agent MAY choose to:
    244  // 9.2.2.4.7.1. Let resource be a new media resource referencing sample data
    245  // in data.
    246  size_t transferLen = 0;
    247  size_t transferOffset = 0;
    248  Maybe<JS::Rooted<JSObject*>> transferView;
    249  Maybe<JS::Rooted<JSObject*>> transferBuffer;
    250  const auto& data = aInit.mData;
    251  if (data.IsArrayBuffer()) {
    252    transferBuffer.emplace(aGlobal.Context(), data.GetAsArrayBuffer().Obj());
    253    if (transferSet.Contains(*transferBuffer)) {
    254      transferLen = JS::GetArrayBufferByteLength(*transferBuffer);
    255    }
    256  } else if (data.IsArrayBufferView()) {
    257    // store rooted ArrayBufferView object in outer variable to prolong
    258    // its lifetime (for JS::Rooted's tracking).
    259    transferView.emplace(aGlobal.Context(), data.GetAsArrayBufferView().Obj());
    260    bool isShared;
    261    transferBuffer.emplace(aGlobal.Context(),
    262                           JS_GetArrayBufferViewBuffer(
    263                               aGlobal.Context(), *transferView, &isShared));
    264    if (transferSet.Contains(*transferBuffer)) {
    265      transferOffset = JS_GetArrayBufferViewByteOffset(*transferView);
    266      transferLen = JS_GetArrayBufferViewByteLength(*transferView);
    267    }
    268  }
    269  UniquePtr<uint8_t[], JS::FreePolicy> transferData;
    270  if (transferLen) {
    271    void* bufferContents =
    272        JS::StealArrayBufferContents(aGlobal.Context(), *transferBuffer);
    273    transferData = UniquePtr<uint8_t[], JS::FreePolicy>(
    274        static_cast<uint8_t*>(bufferContents));
    275  }
    276 
    277  auto resource =
    278      transferData ? MakeAndAddRef<AudioDataResource>(
    279                         std::move(transferData), transferOffset, transferLen)
    280                   : AudioDataResource::Construct(data);
    281  if (resource.isErr()) {
    282    LOGD("AudioData::Constructor failure (OOM)");
    283    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    284    return nullptr;
    285  }
    286 
    287  // 9.2.2.3. For each transferable in init.transfer:
    288  // 9.2.2.3.1. Perform DetachArrayBuffer on transferable
    289  for (const auto& buffer : aInit.mTransfer) {
    290    JS::Rooted<JSObject*> obj(aGlobal.Context(), buffer.Obj());
    291    JS::DetachArrayBuffer(aGlobal.Context(), obj);
    292  }
    293  return MakeAndAddRef<AudioData>(global, resource.unwrap(), aInit);
    294 }
    295 
    296 // https://w3c.github.io/webcodecs/#dom-audiodata-format
    297 Nullable<mozilla::dom::AudioSampleFormat> AudioData::GetFormat() const {
    298  AssertIsOnOwningThread();
    299  return MaybeToNullable(mAudioSampleFormat);
    300 }
    301 
    302 // https://w3c.github.io/webcodecs/#dom-audiodata-samplerate
    303 float AudioData::SampleRate() const {
    304  AssertIsOnOwningThread();
    305  return mSampleRate;
    306 }
    307 
    308 // https://w3c.github.io/webcodecs/#dom-audiodata-numberofframes
    309 uint32_t AudioData::NumberOfFrames() const {
    310  AssertIsOnOwningThread();
    311  return mNumberOfFrames;
    312 }
    313 
    314 // https://w3c.github.io/webcodecs/#dom-audiodata-numberofchannels
    315 uint32_t AudioData::NumberOfChannels() const {
    316  AssertIsOnOwningThread();
    317  return mNumberOfChannels;
    318 }
    319 
    320 // https://w3c.github.io/webcodecs/#dom-audiodata-duration
    321 uint64_t AudioData::Duration() const {
    322  AssertIsOnOwningThread();
    323  if (!mNumberOfFrames) {
    324    return 0;
    325  }
    326  // The spec isn't clear in which direction to convert to integer.
    327  // https://github.com/w3c/webcodecs/issues/726
    328  return static_cast<uint64_t>(
    329      static_cast<float>(USECS_PER_S * mNumberOfFrames) / mSampleRate);
    330 }
    331 
    332 // https://w3c.github.io/webcodecs/#dom-audiodata-timestamp
    333 int64_t AudioData::Timestamp() const {
    334  AssertIsOnOwningThread();
    335  return mTimestamp;
    336 }
    337 
    338 struct CopyToSpec {
    339  CopyToSpec(uint32_t aFrameCount, uint32_t aFrameOffset, uint32_t mPlaneIndex,
    340             AudioSampleFormat aFormat)
    341      : mFrameCount(aFrameCount),
    342        mFrameOffset(aFrameOffset),
    343        mPlaneIndex(mPlaneIndex),
    344        mFormat(aFormat) {}
    345 
    346  const uint32_t mFrameCount;
    347  const uint32_t mFrameOffset;
    348  const uint32_t mPlaneIndex;
    349  const AudioSampleFormat mFormat;
    350 };
    351 
    352 bool IsInterleaved(const AudioSampleFormat& aFormat) {
    353  switch (aFormat) {
    354    case AudioSampleFormat::U8:
    355    case AudioSampleFormat::S16:
    356    case AudioSampleFormat::S32:
    357    case AudioSampleFormat::F32:
    358      return true;
    359    case AudioSampleFormat::U8_planar:
    360    case AudioSampleFormat::S16_planar:
    361    case AudioSampleFormat::S32_planar:
    362    case AudioSampleFormat::F32_planar:
    363      return false;
    364  };
    365  MOZ_ASSERT_UNREACHABLE("Invalid enum value");
    366  return false;
    367 }
    368 
    369 size_t AudioData::ComputeCopyElementCount(
    370    const AudioDataCopyToOptions& aOptions, ErrorResult& aRv) {
    371  // https://w3c.github.io/webcodecs/#compute-copy-element-count
    372  // 1, 2
    373  auto destFormat = mAudioSampleFormat;
    374  if (aOptions.mFormat.WasPassed()) {
    375    destFormat = OptionalToMaybe(aOptions.mFormat);
    376  }
    377  // 3, 4
    378  MOZ_ASSERT(destFormat.isSome());
    379  if (IsInterleaved(destFormat.value())) {
    380    if (aOptions.mPlaneIndex > 0) {
    381      auto msg = "Interleaved format, but plane index > 0"_ns;
    382      LOGD("{}", msg.get());
    383      aRv.ThrowRangeError(msg);
    384      return 0;
    385    }
    386  } else {
    387    if (aOptions.mPlaneIndex >= mNumberOfChannels) {
    388      auto msg = nsFmtCString(FMT_STRING("Plane index {} greater or equal "
    389                                         "than the number of channels {}"),
    390                              aOptions.mPlaneIndex, mNumberOfChannels);
    391      LOGD("{}", msg.get());
    392      aRv.ThrowRangeError(msg);
    393      return 0;
    394    }
    395  }
    396  // 5 -- conversion between all formats supported
    397  // 6 -- all planes have the same number of frames, always
    398  uint64_t frameCount = mNumberOfFrames;
    399  // 7
    400  if (aOptions.mFrameOffset >= frameCount) {
    401    auto msg = nsFmtCString(
    402        FMT_STRING("Frame offset of {} greater or equal than frame count {}"),
    403        aOptions.mFrameOffset, frameCount);
    404    LOGD("{}", msg.get());
    405    aRv.ThrowRangeError(msg);
    406    return 0;
    407  }
    408  // 8, 9
    409  uint64_t copyFrameCount = frameCount - aOptions.mFrameOffset;
    410  if (aOptions.mFrameCount.WasPassed()) {
    411    if (aOptions.mFrameCount.Value() > copyFrameCount) {
    412      auto msg = nsFmtCString(FMT_STRING("Passed copy frame count of {} "
    413                                         "greater than available source frames "
    414                                         "for copy of {}"),
    415                              aOptions.mFrameCount.Value(), copyFrameCount);
    416      LOGD("{}", msg.get());
    417      aRv.ThrowRangeError(msg);
    418      return 0;
    419    }
    420    copyFrameCount = aOptions.mFrameCount.Value();
    421  }
    422  // 10, 11
    423  uint64_t elementCount = copyFrameCount;
    424  if (IsInterleaved(destFormat.value())) {
    425    elementCount *= mNumberOfChannels;
    426  }
    427  return elementCount;
    428 }
    429 
    430 // https://w3c.github.io/webcodecs/#dom-audiodata-allocationsize
    431 // This method returns an int, that can be zero in case of success or error.
    432 // Caller should check aRv to determine success or error.
    433 uint32_t AudioData::AllocationSize(const AudioDataCopyToOptions& aOptions,
    434                                   ErrorResult& aRv) {
    435  AssertIsOnOwningThread();
    436  if (!mResource) {
    437    auto msg = "allocationSize called on detached AudioData"_ns;
    438    LOGD("{}", msg.get());
    439    aRv.ThrowInvalidStateError(msg);
    440    return 0;
    441  }
    442  size_t copyElementCount = ComputeCopyElementCount(aOptions, aRv);
    443  if (aRv.Failed()) {
    444    LOGD("AudioData::AllocationSize failure");
    445    // ComputeCopyElementCount has set the exception type.
    446    return 0;
    447  }
    448  Maybe<mozilla::dom::AudioSampleFormat> destFormat = mAudioSampleFormat;
    449  if (aOptions.mFormat.WasPassed()) {
    450    destFormat = OptionalToMaybe(aOptions.mFormat);
    451  }
    452  if (destFormat.isNothing()) {
    453    auto msg = "AudioData has an unknown format"_ns;
    454    LOGD("{}", msg.get());
    455    // See https://github.com/w3c/webcodecs/issues/727 -- it isn't clear yet
    456    // what to do here
    457    aRv.ThrowRangeError(msg);
    458    return 0;
    459  }
    460  CheckedInt<size_t> bytesPerSample = BytesPerSamples(destFormat.ref());
    461 
    462  auto res = bytesPerSample * copyElementCount;
    463  if (res.isValid()) {
    464    return res.value();
    465  }
    466  aRv.ThrowRangeError("Allocation size too large");
    467  return 0;
    468 }
    469 
    470 template <typename S, typename D>
    471 void CopySamples(Span<S> aSource, Span<D> aDest, uint32_t aSourceChannelCount,
    472                 const AudioSampleFormat aSourceFormat,
    473                 const CopyToSpec& aCopyToSpec) {
    474  if (IsInterleaved(aSourceFormat) && IsInterleaved(aCopyToSpec.mFormat)) {
    475    MOZ_ASSERT(aCopyToSpec.mPlaneIndex == 0);
    476    MOZ_ASSERT(aDest.Length() >= aCopyToSpec.mFrameCount);
    477    MOZ_ASSERT(aSource.Length() - aCopyToSpec.mFrameOffset >=
    478               aCopyToSpec.mFrameCount);
    479    // This turns into a regular memcpy if the types are in fact equal
    480    ConvertAudioSamples(aSource.data() + aCopyToSpec.mFrameOffset, aDest.data(),
    481                        aCopyToSpec.mFrameCount * aSourceChannelCount);
    482    return;
    483  }
    484  if (IsInterleaved(aSourceFormat) && !IsInterleaved(aCopyToSpec.mFormat)) {
    485    DebugOnly<size_t> sourceFrameCount = aSource.Length() / aSourceChannelCount;
    486    MOZ_ASSERT(aDest.Length() >= aCopyToSpec.mFrameCount);
    487    MOZ_ASSERT(aSource.Length() - aCopyToSpec.mFrameOffset >=
    488               aCopyToSpec.mFrameCount);
    489    // Interleaved to planar -- only copy samples of the correct channel to the
    490    // destination
    491    size_t readIndex = aCopyToSpec.mFrameOffset * aSourceChannelCount +
    492                       aCopyToSpec.mPlaneIndex;
    493    for (size_t i = 0; i < aCopyToSpec.mFrameCount; i++) {
    494      aDest[i] = ConvertAudioSample<D>(aSource[readIndex]);
    495      readIndex += aSourceChannelCount;
    496    }
    497    return;
    498  }
    499 
    500  if (!IsInterleaved(aSourceFormat) && IsInterleaved(aCopyToSpec.mFormat)) {
    501    // Planar to interleaved -- copy of all channels of the source into the
    502    // destination buffer.
    503    MOZ_ASSERT(aCopyToSpec.mPlaneIndex == 0);
    504    MOZ_ASSERT(aDest.Length() >= aCopyToSpec.mFrameCount * aSourceChannelCount);
    505    MOZ_ASSERT(aSource.Length() -
    506                   aCopyToSpec.mFrameOffset * aSourceChannelCount >=
    507               aCopyToSpec.mFrameCount * aSourceChannelCount);
    508    size_t writeIndex = 0;
    509    // Scan the source linearly and put each sample at the right position in the
    510    // destination interleaved buffer.
    511    size_t readIndex = 0;
    512    for (size_t channel = 0; channel < aSourceChannelCount; channel++) {
    513      writeIndex = channel;
    514      for (size_t i = 0; i < aCopyToSpec.mFrameCount; i++) {
    515        aDest[writeIndex] = ConvertAudioSample<D>(aSource[readIndex]);
    516        readIndex++;
    517        writeIndex += aSourceChannelCount;
    518      }
    519    }
    520    return;
    521  }
    522  if (!IsInterleaved(aSourceFormat) && !IsInterleaved(aCopyToSpec.mFormat)) {
    523    // Planar to Planar / convert + copy from the right index in the source.
    524    size_t framePerPlane = aSource.Length() / aSourceChannelCount;
    525    size_t offset = aCopyToSpec.mPlaneIndex * framePerPlane;
    526    MOZ_ASSERT(aDest.Length() >= aCopyToSpec.mFrameCount,
    527               "Destination buffer too small");
    528    MOZ_ASSERT(aSource.Length() >= offset + aCopyToSpec.mFrameCount,
    529               "Source buffer too small");
    530    for (uint32_t i = 0; i < aCopyToSpec.mFrameCount; i++) {
    531      aDest[i] =
    532          ConvertAudioSample<D>(aSource[offset + aCopyToSpec.mFrameOffset + i]);
    533    }
    534  }
    535 }
    536 
    537 nsCString AudioData::ToString() const {
    538  if (!mResource) {
    539    return nsCString("AudioData[detached]");
    540  }
    541  return nsFmtCString(FMT_STRING("AudioData[{} bytes {} {}Hz {} x {}ch]"),
    542                      mResource->Data().LengthBytes(),
    543                      GetEnumString(mAudioSampleFormat.value()).get(),
    544                      mSampleRate, mNumberOfFrames, mNumberOfChannels);
    545 }
    546 
    547 nsCString CopyToToString(size_t aDestBufSize,
    548                         const AudioDataCopyToOptions& aOptions) {
    549  return nsFmtCString(
    550      FMT_STRING(
    551          "AudioDataCopyToOptions[data: {} bytes, {}, frame count: {}, frame "
    552          "offset: {}, plane: {}]"),
    553      aDestBufSize,
    554      aOptions.mFormat.WasPassed()
    555          ? GetEnumString(aOptions.mFormat.Value()).get()
    556          : "null",
    557      aOptions.mFrameCount.WasPassed() ? aOptions.mFrameCount.Value() : 0,
    558      aOptions.mFrameOffset, aOptions.mPlaneIndex);
    559 }
    560 
    561 using DataSpanType =
    562    Variant<Span<uint8_t>, Span<int16_t>, Span<int32_t>, Span<float>>;
    563 
    564 DataSpanType GetDataSpan(Span<uint8_t> aSpan, const AudioSampleFormat aFormat) {
    565  const size_t Length = aSpan.Length() / BytesPerSamples(aFormat);
    566  // TODO: Check size so Span can be reasonably constructed?
    567  switch (aFormat) {
    568    case AudioSampleFormat::U8:
    569    case AudioSampleFormat::U8_planar:
    570      return AsVariant(aSpan);
    571    case AudioSampleFormat::S16:
    572    case AudioSampleFormat::S16_planar:
    573      return AsVariant(Span(reinterpret_cast<int16_t*>(aSpan.data()), Length));
    574    case AudioSampleFormat::S32:
    575    case AudioSampleFormat::S32_planar:
    576      return AsVariant(Span(reinterpret_cast<int32_t*>(aSpan.data()), Length));
    577    case AudioSampleFormat::F32:
    578    case AudioSampleFormat::F32_planar:
    579      return AsVariant(Span(reinterpret_cast<float*>(aSpan.data()), Length));
    580  }
    581  MOZ_ASSERT_UNREACHABLE("Invalid enum value");
    582  return AsVariant(aSpan);
    583 }
    584 
    585 void CopySamples(DataSpanType& aSource, DataSpanType& aDest,
    586                 uint32_t aSourceChannelCount,
    587                 const AudioSampleFormat aSourceFormat,
    588                 const CopyToSpec& aCopyToSpec) {
    589  aSource.match([&](auto& src) {
    590    aDest.match([&](auto& dst) {
    591      CopySamples(src, dst, aSourceChannelCount, aSourceFormat, aCopyToSpec);
    592    });
    593  });
    594 }
    595 
    596 void DoCopy(Span<uint8_t> aSource, Span<uint8_t> aDest,
    597            const uint32_t aSourceChannelCount,
    598            const AudioSampleFormat aSourceFormat,
    599            const CopyToSpec& aCopyToSpec) {
    600  DataSpanType source = GetDataSpan(aSource, aSourceFormat);
    601  DataSpanType dest = GetDataSpan(aDest, aCopyToSpec.mFormat);
    602  CopySamples(source, dest, aSourceChannelCount, aSourceFormat, aCopyToSpec);
    603 }
    604 
    605 // https://w3c.github.io/webcodecs/#dom-audiodata-copyto
    606 void AudioData::CopyTo(const AllowSharedBufferSource& aDestination,
    607                       const AudioDataCopyToOptions& aOptions,
    608                       ErrorResult& aRv) {
    609  AssertIsOnOwningThread();
    610 
    611  size_t destLength = ProcessTypedArraysFixed(
    612      aDestination, [&](const Span<uint8_t>& aData) -> size_t {
    613        return aData.LengthBytes();
    614      });
    615 
    616  LOGD("AudioData::CopyTo {} -> {}", ToString().get(),
    617       CopyToToString(destLength, aOptions).get(), 4);
    618 
    619  if (!mResource) {
    620    auto msg = "copyTo called on closed AudioData"_ns;
    621    LOGD("{}", msg.get());
    622    aRv.ThrowInvalidStateError(msg);
    623    return;
    624  }
    625 
    626  uint64_t copyElementCount = ComputeCopyElementCount(aOptions, aRv);
    627  if (aRv.Failed()) {
    628    LOGD("AudioData::CopyTo failed in ComputeCopyElementCount");
    629    return;
    630  }
    631  auto destFormat = mAudioSampleFormat;
    632  if (aOptions.mFormat.WasPassed()) {
    633    destFormat = OptionalToMaybe(aOptions.mFormat);
    634  }
    635 
    636  uint32_t bytesPerSample = BytesPerSamples(destFormat.value());
    637  CheckedInt<uint32_t> copyLength = bytesPerSample;
    638  copyLength *= copyElementCount;
    639  if (copyLength.value() > destLength) {
    640    auto msg = nsFmtCString(FMT_STRING("destination buffer of length {} too "
    641                                       "small for copying {} elements"),
    642                            destLength, bytesPerSample * copyElementCount);
    643    LOGD("{}", msg.get());
    644    aRv.ThrowRangeError(msg);
    645    return;
    646  }
    647 
    648  uint32_t framesToCopy = mNumberOfFrames - aOptions.mFrameOffset;
    649  if (aOptions.mFrameCount.WasPassed()) {
    650    framesToCopy = aOptions.mFrameCount.Value();
    651  }
    652 
    653  CopyToSpec copyToSpec(framesToCopy, aOptions.mFrameOffset,
    654                        aOptions.mPlaneIndex, destFormat.value());
    655 
    656  // Now a couple layers of macros to type the pointers and perform the actual
    657  // copy.
    658  ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) {
    659    DoCopy(mResource->Data(), aData, mNumberOfChannels,
    660           mAudioSampleFormat.value(), copyToSpec);
    661  });
    662 }
    663 
    664 // https://w3c.github.io/webcodecs/#dom-audiodata-clone
    665 already_AddRefed<AudioData> AudioData::Clone(ErrorResult& aRv) {
    666  AssertIsOnOwningThread();
    667 
    668  if (!mResource) {
    669    auto msg = "No media resource in the AudioData now"_ns;
    670    LOGD("{}", msg.get());
    671    aRv.ThrowInvalidStateError(msg);
    672    return nullptr;
    673  }
    674 
    675  return MakeAndAddRef<AudioData>(*this);
    676 }
    677 
    678 // https://w3c.github.io/webcodecs/#close-audiodata
    679 void AudioData::Close() {
    680  AssertIsOnOwningThread();
    681 
    682  mResource = nullptr;
    683  mSampleRate = 0;
    684  mNumberOfFrames = 0;
    685  mNumberOfChannels = 0;
    686  mAudioSampleFormat = Nothing();
    687 }
    688 
    689 bool AudioData::IsClosed() const { return !mResource; }
    690 
    691 // https://w3c.github.io/webcodecs/#ref-for-deserialization-steps%E2%91%A1
    692 /* static */
    693 JSObject* AudioData::ReadStructuredClone(JSContext* aCx,
    694                                         nsIGlobalObject* aGlobal,
    695                                         JSStructuredCloneReader* aReader,
    696                                         const AudioDataSerializedData& aData) {
    697  JS::Rooted<JS::Value> value(aCx, JS::NullValue());
    698  // To avoid a rooting hazard error from returning a raw JSObject* before
    699  // running the RefPtr destructor, RefPtr needs to be destructed before
    700  // returning the raw JSObject*, which is why the RefPtr<AudioData> is created
    701  // in the scope below. Otherwise, the static analysis infers the RefPtr cannot
    702  // be safely destructed while the unrooted return JSObject* is on the stack.
    703  {
    704    RefPtr<AudioData> frame = MakeAndAddRef<AudioData>(aGlobal, aData);
    705    if (!GetOrCreateDOMReflector(aCx, frame, &value) || !value.isObject()) {
    706      LOGE("GetOrCreateDOMReflect failure");
    707      return nullptr;
    708    }
    709  }
    710  return value.toObjectOrNull();
    711 }
    712 
    713 // https://w3c.github.io/webcodecs/#ref-for-audiodata%E2%91%A2%E2%91%A2
    714 bool AudioData::WriteStructuredClone(JSStructuredCloneWriter* aWriter,
    715                                     StructuredCloneHolder* aHolder) const {
    716  AssertIsOnOwningThread();
    717 
    718  // AudioData closed
    719  if (!mResource) {
    720    LOGD("AudioData was already close in WriteStructuredClone");
    721    return false;
    722  }
    723  const uint32_t index = aHolder->AudioData().Length();
    724  // https://github.com/w3c/webcodecs/issues/717
    725  // For now, serialization is only allowed in the same address space, it's OK
    726  // to send a refptr here instead of copying the backing buffer.
    727  aHolder->AudioData().AppendElement(AudioDataSerializedData(*this));
    728 
    729  return !NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_AUDIODATA, index));
    730 }
    731 
    732 // https://w3c.github.io/webcodecs/#ref-for-transfer-steps
    733 UniquePtr<AudioData::TransferredData> AudioData::Transfer() {
    734  AssertIsOnOwningThread();
    735 
    736  if (!mResource) {
    737    // Closed
    738    LOGD("AudioData was already close in Transfer");
    739    return nullptr;
    740  }
    741 
    742  // This adds a ref to the resource
    743  auto serialized = MakeUnique<AudioDataSerializedData>(*this);
    744  // This removes the ref to the resource, effectively transfering the backing
    745  // storage.
    746  Close();
    747  return serialized;
    748 }
    749 
    750 // https://w3c.github.io/webcodecs/#ref-for-transfer-receiving-steps
    751 /* static */
    752 already_AddRefed<AudioData> AudioData::FromTransferred(nsIGlobalObject* aGlobal,
    753                                                       TransferredData* aData) {
    754  MOZ_ASSERT(aData);
    755 
    756  return MakeAndAddRef<AudioData>(aGlobal, *aData);
    757 }
    758 
    759 void AudioData::CloseIfNeeded() {
    760  if (mResource) {
    761    mResource = nullptr;
    762  }
    763 }
    764 
    765 RefPtr<mozilla::AudioData> AudioData::ToAudioData() const {
    766  // Always convert to f32 interleaved for now, as this Gecko's prefered
    767  // internal audio representation for encoding and decoding.
    768  // mResource can be bigger than needed.
    769  Span<uint8_t> data = mResource->Data();
    770  CheckedUint64 sampleCount = mNumberOfFrames;
    771  sampleCount *= mNumberOfChannels;
    772  if (!sampleCount.isValid()) {
    773    LOGE("Overflow AudioData::ToAudioData when computing the number of frames");
    774    return nullptr;
    775  }
    776  AlignedAudioBuffer buf(sampleCount.value());
    777  if (!buf.Length()) {
    778    LOGE("OOM when allocating storage for AudioData conversion");
    779    return nullptr;
    780  }
    781  Span<uint8_t> storage(reinterpret_cast<uint8_t*>(buf.Data()), buf.Size());
    782 
    783  CopyToSpec spec(mNumberOfFrames, 0, 0, AudioSampleFormat::F32);
    784 
    785  DoCopy(data, storage, mNumberOfChannels, mAudioSampleFormat.value(), spec);
    786 
    787  return MakeRefPtr<mozilla::AudioData>(
    788      0, media::TimeUnit::FromMicroseconds(mTimestamp), std::move(buf),
    789      mNumberOfChannels, SaturatingCast<uint32_t>(mSampleRate));
    790 }
    791 
    792 #undef LOGD
    793 #undef LOGE
    794 
    795 }  // namespace mozilla::dom