tor-browser

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

AudioBufferSourceNode.cpp (31402B)


      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 "AudioBufferSourceNode.h"
      8 
      9 #include <algorithm>
     10 #include <limits>
     11 
     12 #include "AlignmentUtils.h"
     13 #include "AudioDestinationNode.h"
     14 #include "AudioNodeEngine.h"
     15 #include "AudioNodeTrack.h"
     16 #include "AudioParamTimeline.h"
     17 #include "Tracing.h"
     18 #include "mozilla/dom/AudioBufferSourceNodeBinding.h"
     19 #include "mozilla/dom/AudioParam.h"
     20 #include "nsContentUtils.h"
     21 #include "nsDebug.h"
     22 #include "nsMathUtils.h"
     23 
     24 namespace mozilla::dom {
     25 
     26 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioBufferSourceNode,
     27                                   AudioScheduledSourceNode, mBuffer,
     28                                   mPlaybackRate, mDetune)
     29 
     30 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioBufferSourceNode)
     31 NS_INTERFACE_MAP_END_INHERITING(AudioScheduledSourceNode)
     32 
     33 NS_IMPL_ADDREF_INHERITED(AudioBufferSourceNode, AudioScheduledSourceNode)
     34 NS_IMPL_RELEASE_INHERITED(AudioBufferSourceNode, AudioScheduledSourceNode)
     35 
     36 /**
     37 * Media-thread playback engine for AudioBufferSourceNode.
     38 * Nothing is played until a non-null buffer has been set (via
     39 * AudioNodeTrack::SetBuffer) and a non-zero mBufferSampleRate has been set
     40 * (via AudioNodeTrack::SetInt32Parameter)
     41 */
     42 class AudioBufferSourceNodeEngine final : public AudioNodeEngine {
     43 public:
     44  AudioBufferSourceNodeEngine(AudioNode* aNode,
     45                              AudioDestinationNode* aDestination)
     46      : AudioNodeEngine(aNode),
     47        mStart(0.0),
     48        mBeginProcessing(0),
     49        mStop(TRACK_TIME_MAX),
     50        mResampler(nullptr),
     51        mRemainingResamplerTail(0),
     52        mRemainingFrames(TRACK_TICKS_MAX),
     53        mLoopStart(0),
     54        mLoopEnd(0),
     55        mBufferPosition(0),
     56        mBufferSampleRate(0),
     57        // mResamplerOutRate is initialized in UpdateResampler().
     58        mChannels(0),
     59        mDestination(aDestination->Track()),
     60        mPlaybackRateTimeline(1.0f),
     61        mDetuneTimeline(0.0f),
     62        mLoop(false) {}
     63 
     64  ~AudioBufferSourceNodeEngine() {
     65    if (mResampler) {
     66      speex_resampler_destroy(mResampler);
     67    }
     68  }
     69 
     70  void SetSourceTrack(AudioNodeTrack* aSource) { mSource = aSource; }
     71 
     72  void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
     73    MOZ_ASSERT(mDestination);
     74    aEvent.ConvertToTicks(mDestination);
     75    mRecomputeOutRate = true;
     76 
     77    switch (aIndex) {
     78      case AudioBufferSourceNode::PLAYBACKRATE:
     79        mPlaybackRateTimeline.InsertEvent<int64_t>(aEvent);
     80        break;
     81      case AudioBufferSourceNode::DETUNE:
     82        mDetuneTimeline.InsertEvent<int64_t>(aEvent);
     83        break;
     84      default:
     85        NS_ERROR("Bad AudioBufferSourceNodeEngine TimelineParameter");
     86    }
     87  }
     88  void SetTrackTimeParameter(uint32_t aIndex, TrackTime aParam) override {
     89    switch (aIndex) {
     90      case AudioBufferSourceNode::STOP:
     91        mStop = aParam;
     92        break;
     93      default:
     94        NS_ERROR("Bad AudioBufferSourceNodeEngine TrackTimeParameter");
     95    }
     96  }
     97  void SetDoubleParameter(uint32_t aIndex, double aParam) override {
     98    switch (aIndex) {
     99      case AudioBufferSourceNode::START:
    100        MOZ_ASSERT(!mStart, "Another START?");
    101        mStart = aParam * mDestination->mSampleRate;
    102        // Round to nearest
    103        mBeginProcessing = llround(mStart);
    104        break;
    105      case AudioBufferSourceNode::DURATION:
    106        MOZ_ASSERT(aParam >= 0);
    107        mRemainingFrames = llround(aParam * mBufferSampleRate);
    108        break;
    109      default:
    110        NS_ERROR("Bad AudioBufferSourceNodeEngine double parameter.");
    111    };
    112  }
    113  void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override {
    114    switch (aIndex) {
    115      case AudioBufferSourceNode::SAMPLE_RATE:
    116        MOZ_ASSERT(aParam > 0);
    117        MOZ_ASSERT(mRecomputeOutRate);
    118        mBufferSampleRate = aParam;
    119        mSource->SetActive();
    120        break;
    121      case AudioBufferSourceNode::BUFFERSTART:
    122        MOZ_ASSERT(aParam >= 0);
    123        if (mBufferPosition == 0) {
    124          mBufferPosition = aParam;
    125        }
    126        break;
    127      case AudioBufferSourceNode::LOOP:
    128        mLoop = !!aParam;
    129        break;
    130      case AudioBufferSourceNode::LOOPSTART:
    131        MOZ_ASSERT(aParam >= 0);
    132        mLoopStart = aParam;
    133        break;
    134      case AudioBufferSourceNode::LOOPEND:
    135        MOZ_ASSERT(aParam >= 0);
    136        mLoopEnd = aParam;
    137        break;
    138      default:
    139        NS_ERROR("Bad AudioBufferSourceNodeEngine Int32Parameter");
    140    }
    141  }
    142  void SetBuffer(AudioChunk&& aBuffer) override { mBuffer = aBuffer; }
    143 
    144  bool BegunResampling() { return mBeginProcessing == -TRACK_TIME_MAX; }
    145 
    146  void UpdateResampler(int32_t aOutRate, uint32_t aChannels) {
    147    if (mResampler &&
    148        (aChannels != mChannels ||
    149         // If the resampler has begun, then it will have moved
    150         // mBufferPosition to after the samples it has read, but it hasn't
    151         // output its buffered samples.  Keep using the resampler, even if
    152         // the rates now match, so that this latent segment is output.
    153         (aOutRate == mBufferSampleRate && !BegunResampling()))) {
    154      speex_resampler_destroy(mResampler);
    155      mResampler = nullptr;
    156      mRemainingResamplerTail = 0;
    157      mBeginProcessing = llround(mStart);
    158    }
    159 
    160    if (aChannels == 0 || (aOutRate == mBufferSampleRate && !mResampler)) {
    161      mResamplerOutRate = aOutRate;
    162      return;
    163    }
    164 
    165    if (!mResampler) {
    166      mChannels = aChannels;
    167      mResampler = speex_resampler_init(mChannels, mBufferSampleRate, aOutRate,
    168                                        SPEEX_RESAMPLER_QUALITY_MIN, nullptr);
    169    } else {
    170      if (mResamplerOutRate == aOutRate) {
    171        return;
    172      }
    173      if (speex_resampler_set_rate(mResampler, mBufferSampleRate, aOutRate) !=
    174          RESAMPLER_ERR_SUCCESS) {
    175        NS_ASSERTION(false, "speex_resampler_set_rate failed");
    176        return;
    177      }
    178    }
    179 
    180    mResamplerOutRate = aOutRate;
    181 
    182    if (!BegunResampling()) {
    183      // Low pass filter effects from the resampler mean that samples before
    184      // the start time are influenced by resampling the buffer.  The input
    185      // latency indicates half the filter width.
    186      int64_t inputLatency = speex_resampler_get_input_latency(mResampler);
    187      uint32_t ratioNum, ratioDen;
    188      speex_resampler_get_ratio(mResampler, &ratioNum, &ratioDen);
    189      // The output subsample resolution supported in aligning the resampler
    190      // is ratioNum.  First round the start time to the nearest subsample.
    191      int64_t subsample = llround(mStart * ratioNum);
    192      // Now include the leading effects of the filter, and round *up* to the
    193      // next whole tick, because there is no effect on samples outside the
    194      // filter width.
    195      mBeginProcessing =
    196          (subsample - inputLatency * ratioDen + ratioNum - 1) / ratioNum;
    197    }
    198  }
    199 
    200  // Borrow a full buffer of size WEBAUDIO_BLOCK_SIZE from the source buffer
    201  // at offset aSourceOffset.  This avoids copying memory.
    202  void BorrowFromInputBuffer(AudioBlock* aOutput, uint32_t aChannels) {
    203    aOutput->SetBuffer(mBuffer.mBuffer);
    204    aOutput->mChannelData.SetLength(aChannels);
    205    for (uint32_t i = 0; i < aChannels; ++i) {
    206      aOutput->mChannelData[i] =
    207          mBuffer.ChannelData<float>()[i] + mBufferPosition;
    208    }
    209    aOutput->mVolume = mBuffer.mVolume;
    210    aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32;
    211  }
    212 
    213  // Copy aNumberOfFrames frames from the source buffer at offset aSourceOffset
    214  // and put it at offset aBufferOffset in the destination buffer.
    215  template <typename T>
    216  void CopyFromInputBuffer(AudioBlock* aOutput, uint32_t aChannels,
    217                           uintptr_t aOffsetWithinBlock,
    218                           uint32_t aNumberOfFrames) {
    219    MOZ_ASSERT(mBuffer.mVolume == 1.0f);
    220    for (uint32_t i = 0; i < aChannels; ++i) {
    221      float* baseChannelData = aOutput->ChannelFloatsForWrite(i);
    222      ConvertAudioSamples(mBuffer.ChannelData<T>()[i] + mBufferPosition,
    223                          baseChannelData + aOffsetWithinBlock,
    224                          aNumberOfFrames);
    225    }
    226  }
    227 
    228  // Resamples input data to an output buffer, according to |mBufferSampleRate|
    229  // and the playbackRate/detune. The number of frames consumed/produced depends
    230  // on the amount of space remaining in both the input and output buffer, and
    231  // the playback rate (that is, the ratio between the output samplerate and the
    232  // input samplerate).
    233  void CopyFromInputBufferWithResampling(AudioBlock* aOutput,
    234                                         uint32_t aChannels,
    235                                         uint32_t* aOffsetWithinBlock,
    236                                         uint32_t aAvailableInOutput,
    237                                         TrackTime* aCurrentPosition,
    238                                         uint32_t aBufferMax) {
    239    if (*aOffsetWithinBlock == 0) {
    240      aOutput->AllocateChannels(aChannels);
    241    }
    242    SpeexResamplerState* resampler = mResampler;
    243    MOZ_ASSERT(aChannels > 0);
    244 
    245    if (mBufferPosition < aBufferMax) {
    246      uint32_t availableInInputBuffer = aBufferMax - mBufferPosition;
    247      uint32_t ratioNum, ratioDen;
    248      speex_resampler_get_ratio(resampler, &ratioNum, &ratioDen);
    249      // Limit the number of input samples copied and possibly
    250      // format-converted for resampling by estimating how many will be used.
    251      // This may be a little small if still filling the resampler with
    252      // initial data, but we'll get called again and it will work out.
    253      uint32_t inputLimit = aAvailableInOutput * ratioNum / ratioDen + 10;
    254      if (!BegunResampling()) {
    255        // First time the resampler is used.
    256        uint32_t inputLatency = speex_resampler_get_input_latency(resampler);
    257        inputLimit += inputLatency;
    258        // If starting after mStart, then play from the beginning of the
    259        // buffer, but correct for input latency.  If starting before mStart,
    260        // then align the resampler so that the time corresponding to the
    261        // first input sample is mStart.
    262        int64_t skipFracNum = static_cast<int64_t>(inputLatency) * ratioDen;
    263        double leadTicks = mStart - *aCurrentPosition;
    264        if (leadTicks > 0.0) {
    265          // Round to nearest output subsample supported by the resampler at
    266          // these rates.
    267          int64_t leadSubsamples = llround(leadTicks * ratioNum);
    268          MOZ_ASSERT(leadSubsamples <= skipFracNum,
    269                     "mBeginProcessing is wrong?");
    270          skipFracNum -= leadSubsamples;
    271        }
    272        speex_resampler_set_skip_frac_num(
    273            resampler, std::min<int64_t>(skipFracNum, UINT32_MAX));
    274 
    275        mBeginProcessing = -TRACK_TIME_MAX;
    276      }
    277      inputLimit = std::min(inputLimit, availableInInputBuffer);
    278 
    279      MOZ_ASSERT(mBuffer.mVolume == 1.0f);
    280      for (uint32_t i = 0; true;) {
    281        uint32_t inSamples = inputLimit;
    282 
    283        uint32_t outSamples = aAvailableInOutput;
    284        float* outputData =
    285            aOutput->ChannelFloatsForWrite(i) + *aOffsetWithinBlock;
    286 
    287        if (mBuffer.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
    288          const float* inputData =
    289              mBuffer.ChannelData<float>()[i] + mBufferPosition;
    290          WebAudioUtils::SpeexResamplerProcess(
    291              resampler, i, inputData, &inSamples, outputData, &outSamples);
    292        } else {
    293          MOZ_ASSERT(mBuffer.mBufferFormat == AUDIO_FORMAT_S16);
    294          const int16_t* inputData =
    295              mBuffer.ChannelData<int16_t>()[i] + mBufferPosition;
    296          WebAudioUtils::SpeexResamplerProcess(
    297              resampler, i, inputData, &inSamples, outputData, &outSamples);
    298        }
    299        if (++i == aChannels) {
    300          mBufferPosition += inSamples;
    301          mRemainingFrames -= inSamples;
    302          MOZ_ASSERT(mBufferPosition <= mBuffer.GetDuration());
    303          MOZ_ASSERT(mRemainingFrames >= 0);
    304          *aOffsetWithinBlock += outSamples;
    305          *aCurrentPosition += outSamples;
    306          if ((!mLoop && inSamples == availableInInputBuffer) ||
    307              mRemainingFrames == 0) {
    308            // We'll feed in enough zeros to empty out the resampler's memory.
    309            // This handles the output latency as well as capturing the low
    310            // pass effects of the resample filter.
    311            mRemainingResamplerTail =
    312                2 * speex_resampler_get_input_latency(resampler) - 1;
    313          }
    314          return;
    315        }
    316      }
    317    } else {
    318      for (uint32_t i = 0; true;) {
    319        uint32_t inSamples = mRemainingResamplerTail;
    320        uint32_t outSamples = aAvailableInOutput;
    321        float* outputData =
    322            aOutput->ChannelFloatsForWrite(i) + *aOffsetWithinBlock;
    323 
    324        // AudioDataValue* for aIn selects the function that does not try to
    325        // copy and format-convert input data.
    326        WebAudioUtils::SpeexResamplerProcess(
    327            resampler, i, static_cast<AudioDataValue*>(nullptr), &inSamples,
    328            outputData, &outSamples);
    329        if (++i == aChannels) {
    330          MOZ_ASSERT(inSamples <= mRemainingResamplerTail);
    331          mRemainingResamplerTail -= inSamples;
    332          *aOffsetWithinBlock += outSamples;
    333          *aCurrentPosition += outSamples;
    334          break;
    335        }
    336      }
    337    }
    338  }
    339 
    340  /**
    341   * Fill aOutput with as many zero frames as we can, and advance
    342   * aOffsetWithinBlock and aCurrentPosition based on how many frames we write.
    343   * This will never advance aOffsetWithinBlock past WEBAUDIO_BLOCK_SIZE or
    344   * aCurrentPosition past aMaxPos.  This function knows when it needs to
    345   * allocate the output buffer, and also optimizes the case where it can avoid
    346   * memory allocations.
    347   */
    348  void FillWithZeroes(AudioBlock* aOutput, uint32_t aChannels,
    349                      uint32_t* aOffsetWithinBlock, TrackTime* aCurrentPosition,
    350                      TrackTime aMaxPos) {
    351    MOZ_ASSERT(*aCurrentPosition < aMaxPos);
    352    uint32_t numFrames = std::min<TrackTime>(
    353        WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock, aMaxPos - *aCurrentPosition);
    354    if (numFrames == WEBAUDIO_BLOCK_SIZE || !aChannels) {
    355      aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
    356    } else {
    357      if (*aOffsetWithinBlock == 0) {
    358        aOutput->AllocateChannels(aChannels);
    359      }
    360      WriteZeroesToAudioBlock(aOutput, *aOffsetWithinBlock, numFrames);
    361    }
    362    *aOffsetWithinBlock += numFrames;
    363    *aCurrentPosition += numFrames;
    364  }
    365 
    366  /**
    367   * Copy as many frames as possible from the source buffer to aOutput, and
    368   * advance aOffsetWithinBlock and aCurrentPosition based on how many frames
    369   * we write.  This will never advance aOffsetWithinBlock past
    370   * WEBAUDIO_BLOCK_SIZE, or aCurrentPosition past mStop.  It takes data from
    371   * the buffer at aBufferOffset, and never takes more data than aBufferMax.
    372   * This function knows when it needs to allocate the output buffer, and also
    373   * optimizes the case where it can avoid memory allocations.
    374   */
    375  void CopyFromBuffer(AudioBlock* aOutput, uint32_t aChannels,
    376                      uint32_t* aOffsetWithinBlock, TrackTime* aCurrentPosition,
    377                      uint32_t aBufferMax) {
    378    MOZ_ASSERT(*aCurrentPosition < mStop);
    379    uint32_t availableInOutput = std::min<TrackTime>(
    380        WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock, mStop - *aCurrentPosition);
    381    if (mResampler) {
    382      CopyFromInputBufferWithResampling(aOutput, aChannels, aOffsetWithinBlock,
    383                                        availableInOutput, aCurrentPosition,
    384                                        aBufferMax);
    385      return;
    386    }
    387 
    388    if (aChannels == 0) {
    389      aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
    390      // There is no attempt here to limit advance so that mBufferPosition is
    391      // limited to aBufferMax.  The only observable affect of skipping the
    392      // check would be in the precise timing of the ended event if the loop
    393      // attribute is reset after playback has looped.
    394      *aOffsetWithinBlock += availableInOutput;
    395      *aCurrentPosition += availableInOutput;
    396      // Rounding at the start and end of the period means that fractional
    397      // increments essentially accumulate if outRate remains constant.  If
    398      // outRate is varying, then accumulation happens on average but not
    399      // precisely.
    400      TrackTicks start =
    401          *aCurrentPosition * mBufferSampleRate / mResamplerOutRate;
    402      TrackTicks end = (*aCurrentPosition + availableInOutput) *
    403                       mBufferSampleRate / mResamplerOutRate;
    404      mBufferPosition += end - start;
    405      return;
    406    }
    407 
    408    uint32_t numFrames =
    409        std::min(aBufferMax - mBufferPosition, availableInOutput);
    410 
    411    bool shouldBorrow = false;
    412    if (numFrames == WEBAUDIO_BLOCK_SIZE &&
    413        mBuffer.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
    414      shouldBorrow = true;
    415      for (uint32_t i = 0; i < aChannels; ++i) {
    416        if (!IS_ALIGNED16(mBuffer.ChannelData<float>()[i] + mBufferPosition)) {
    417          shouldBorrow = false;
    418          break;
    419        }
    420      }
    421    }
    422    MOZ_ASSERT(mBufferPosition < aBufferMax);
    423    if (shouldBorrow) {
    424      BorrowFromInputBuffer(aOutput, aChannels);
    425    } else {
    426      if (*aOffsetWithinBlock == 0) {
    427        aOutput->AllocateChannels(aChannels);
    428      }
    429      if (mBuffer.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
    430        CopyFromInputBuffer<float>(aOutput, aChannels, *aOffsetWithinBlock,
    431                                   numFrames);
    432      } else {
    433        MOZ_ASSERT(mBuffer.mBufferFormat == AUDIO_FORMAT_S16);
    434        CopyFromInputBuffer<int16_t>(aOutput, aChannels, *aOffsetWithinBlock,
    435                                     numFrames);
    436      }
    437    }
    438    *aOffsetWithinBlock += numFrames;
    439    *aCurrentPosition += numFrames;
    440    mBufferPosition += numFrames;
    441    mRemainingFrames -= numFrames;
    442  }
    443 
    444  int32_t ComputeFinalOutSampleRate(float aPlaybackRate, float aDetune) {
    445    float computedPlaybackRate = aPlaybackRate * fdlibm_exp2f(aDetune / 1200.f);
    446    if (std::isnan(computedPlaybackRate)) {
    447      computedPlaybackRate = 1.0f;
    448    }
    449    // Make sure the playback rate is something our resampler can work with
    450    int32_t rate = WebAudioUtils::TruncateFloatToInt<int32_t>(
    451        mSource->mSampleRate / computedPlaybackRate);
    452    return rate > 0 ? rate : mBufferSampleRate;
    453  }
    454 
    455  void UpdateSampleRateIfNeeded(uint32_t aChannels, TrackTime aTrackPosition) {
    456    bool simplePlaybackRate = mPlaybackRateTimeline.HasSimpleValue();
    457    bool simpleDetune = mDetuneTimeline.HasSimpleValue();
    458 
    459    if (simplePlaybackRate && simpleDetune && !mRecomputeOutRate) {
    460      return;  // skipping the slow exp2f() for the detune
    461    }
    462    mRecomputeOutRate = false;
    463 
    464    float playbackRate;
    465    float detune;
    466    if (simplePlaybackRate) {
    467      playbackRate = mPlaybackRateTimeline.GetValue();
    468    } else {
    469      playbackRate =
    470          mPlaybackRateTimeline.GetComplexValueAtTime(aTrackPosition);
    471    }
    472    if (simpleDetune) {
    473      detune = mDetuneTimeline.GetValue();
    474    } else {
    475      detune = mDetuneTimeline.GetComplexValueAtTime(aTrackPosition);
    476    }
    477 
    478    int32_t outRate = ComputeFinalOutSampleRate(playbackRate, detune);
    479    UpdateResampler(outRate, aChannels);
    480  }
    481 
    482  void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
    483                    const AudioBlock& aInput, AudioBlock* aOutput,
    484                    bool* aFinished) override {
    485    TRACE("AudioBufferSourceNodeEngine::ProcessBlock");
    486    if (mBufferSampleRate == 0) {
    487      // start() has not yet been called or no buffer has yet been set
    488      aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
    489      return;
    490    }
    491 
    492    TrackTime streamPosition = mDestination->GraphTimeToTrackTime(aFrom);
    493    uint32_t channels = mBuffer.ChannelCount();
    494 
    495    UpdateSampleRateIfNeeded(channels, streamPosition);
    496 
    497    uint32_t written = 0;
    498    while (true) {
    499      if ((mStop != TRACK_TIME_MAX && streamPosition >= mStop) ||
    500          (!mRemainingResamplerTail &&
    501           ((mBufferPosition >= mBuffer.GetDuration() && !mLoop) ||
    502            mRemainingFrames <= 0))) {
    503        if (written != WEBAUDIO_BLOCK_SIZE) {
    504          FillWithZeroes(aOutput, channels, &written, &streamPosition,
    505                         TRACK_TIME_MAX);
    506        }
    507        *aFinished = true;
    508        break;
    509      }
    510      if (written == WEBAUDIO_BLOCK_SIZE) {
    511        break;
    512      }
    513      if (streamPosition < mBeginProcessing) {
    514        FillWithZeroes(aOutput, channels, &written, &streamPosition,
    515                       mBeginProcessing);
    516        continue;
    517      }
    518 
    519      TrackTicks bufferLeft;
    520      if (mLoop) {
    521        // mLoopEnd can become less than mBufferPosition when a LOOPEND engine
    522        // parameter is received after "loopend" is changed on the node or a
    523        // new buffer with lower samplerate is set.
    524        if (mBufferPosition >= mLoopEnd) {
    525          mBufferPosition = mLoopStart;
    526        }
    527        bufferLeft =
    528            std::min<TrackTicks>(mRemainingFrames, mLoopEnd - mBufferPosition);
    529      } else {
    530        bufferLeft =
    531            std::min(mRemainingFrames, mBuffer.GetDuration() - mBufferPosition);
    532      }
    533 
    534      CopyFromBuffer(aOutput, channels, &written, &streamPosition,
    535                     bufferLeft + mBufferPosition);
    536    }
    537  }
    538 
    539  bool IsActive() const override {
    540    // Whether buffer has been set and start() has been called.
    541    return mBufferSampleRate != 0;
    542  }
    543 
    544  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
    545    // Not owned:
    546    // - mBuffer - shared w/ AudioNode
    547    // - mPlaybackRateTimeline - shared w/ AudioNode
    548    // - mDetuneTimeline - shared w/ AudioNode
    549 
    550    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
    551 
    552    // NB: We need to modify speex if we want the full memory picture, internal
    553    //     fields that need measuring noted below.
    554    // - mResampler->mem
    555    // - mResampler->sinc_table
    556    // - mResampler->last_sample
    557    // - mResampler->magic_samples
    558    // - mResampler->samp_frac_num
    559    amount += aMallocSizeOf(mResampler);
    560 
    561    return amount;
    562  }
    563 
    564  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
    565    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
    566  }
    567 
    568  double mStart;  // including the fractional position between ticks
    569  // Low pass filter effects from the resampler mean that samples before the
    570  // start time are influenced by resampling the buffer.  mBeginProcessing
    571  // includes the extent of this filter.  The special value of -TRACK_TIME_MAX
    572  // indicates that the resampler has begun processing.
    573  TrackTime mBeginProcessing;
    574  TrackTime mStop;
    575  AudioChunk mBuffer;
    576  SpeexResamplerState* mResampler;
    577  // mRemainingResamplerTail, like mBufferPosition
    578  // is measured in input buffer samples.
    579  uint32_t mRemainingResamplerTail;
    580  TrackTicks mRemainingFrames;
    581  uint32_t mLoopStart;
    582  uint32_t mLoopEnd;
    583  uint32_t mBufferPosition;
    584  int32_t mBufferSampleRate;
    585  int32_t mResamplerOutRate;
    586  uint32_t mChannels;
    587  RefPtr<AudioNodeTrack> mDestination;
    588 
    589  // mSource deletes the engine in its destructor.
    590  AudioNodeTrack* MOZ_NON_OWNING_REF mSource;
    591  AudioParamTimeline mPlaybackRateTimeline;
    592  AudioParamTimeline mDetuneTimeline;
    593  bool mLoop;
    594  bool mRecomputeOutRate = true;
    595 };
    596 
    597 AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* aContext)
    598    : AudioScheduledSourceNode(aContext, 2, ChannelCountMode::Max,
    599                               ChannelInterpretation::Speakers),
    600      mLoopStart(0.0),
    601      mLoopEnd(0.0),
    602      // mOffset and mDuration are initialized in Start().
    603      mLoop(false),
    604      mStartCalled(false),
    605      mBufferSet(false) {
    606  mPlaybackRate = CreateAudioParam(PLAYBACKRATE, u"playbackRate"_ns, 1.0f);
    607  mDetune = CreateAudioParam(DETUNE, u"detune"_ns, 0.0f);
    608  AudioBufferSourceNodeEngine* engine =
    609      new AudioBufferSourceNodeEngine(this, aContext->Destination());
    610  mTrack = AudioNodeTrack::Create(aContext, engine,
    611                                  AudioNodeTrack::NEED_MAIN_THREAD_ENDED,
    612                                  aContext->Graph());
    613  engine->SetSourceTrack(mTrack);
    614  mTrack->AddMainThreadListener(this);
    615 }
    616 
    617 /* static */
    618 already_AddRefed<AudioBufferSourceNode> AudioBufferSourceNode::Create(
    619    JSContext* aCx, AudioContext& aAudioContext,
    620    const AudioBufferSourceOptions& aOptions) {
    621  RefPtr<AudioBufferSourceNode> audioNode =
    622      new AudioBufferSourceNode(&aAudioContext);
    623 
    624  if (aOptions.mBuffer.WasPassed()) {
    625    ErrorResult ignored;
    626    MOZ_ASSERT(aCx);
    627    audioNode->SetBuffer(aCx, aOptions.mBuffer.Value(), ignored);
    628  }
    629 
    630  audioNode->Detune()->SetInitialValue(aOptions.mDetune);
    631  audioNode->SetLoop(aOptions.mLoop);
    632  audioNode->SetLoopEnd(aOptions.mLoopEnd);
    633  audioNode->SetLoopStart(aOptions.mLoopStart);
    634  audioNode->PlaybackRate()->SetInitialValue(aOptions.mPlaybackRate);
    635 
    636  return audioNode.forget();
    637 }
    638 void AudioBufferSourceNode::DestroyMediaTrack() {
    639  bool hadTrack = mTrack;
    640  if (hadTrack) {
    641    mTrack->RemoveMainThreadListener(this);
    642  }
    643  AudioNode::DestroyMediaTrack();
    644 }
    645 
    646 size_t AudioBufferSourceNode::SizeOfExcludingThis(
    647    MallocSizeOf aMallocSizeOf) const {
    648  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
    649 
    650  /* mBuffer can be shared and is accounted for separately. */
    651 
    652  amount += mPlaybackRate->SizeOfIncludingThis(aMallocSizeOf);
    653  amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
    654  return amount;
    655 }
    656 
    657 size_t AudioBufferSourceNode::SizeOfIncludingThis(
    658    MallocSizeOf aMallocSizeOf) const {
    659  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
    660 }
    661 
    662 JSObject* AudioBufferSourceNode::WrapObject(JSContext* aCx,
    663                                            JS::Handle<JSObject*> aGivenProto) {
    664  return AudioBufferSourceNode_Binding::Wrap(aCx, this, aGivenProto);
    665 }
    666 
    667 void AudioBufferSourceNode::Start(double aWhen, double aOffset,
    668                                  const Optional<double>& aDuration,
    669                                  ErrorResult& aRv) {
    670  if (!WebAudioUtils::IsTimeValid(aWhen)) {
    671    aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("start time");
    672    return;
    673  }
    674  if (aOffset < 0) {
    675    aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("offset");
    676    return;
    677  }
    678  if (aDuration.WasPassed() && !WebAudioUtils::IsTimeValid(aDuration.Value())) {
    679    aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("duration");
    680    return;
    681  }
    682 
    683  if (mStartCalled) {
    684    aRv.ThrowInvalidStateError(
    685        "Start has already been called on this AudioBufferSourceNode.");
    686    return;
    687  }
    688  mStartCalled = true;
    689 
    690  AudioNodeTrack* ns = mTrack;
    691  if (!ns) {
    692    // Nothing to play, or we're already dead for some reason
    693    return;
    694  }
    695 
    696  // Remember our arguments so that we can use them when we get a new buffer.
    697  mOffset = aOffset;
    698  mDuration = aDuration.WasPassed() ? aDuration.Value()
    699                                    : std::numeric_limits<double>::min();
    700 
    701  WEB_AUDIO_API_LOG("{:f}: {} {} Start({:f}, {}, {})", Context()->CurrentTime(),
    702                    NodeType(), Id(), aWhen, aOffset, mDuration);
    703 
    704  // We can't send these parameters without a buffer because we don't know the
    705  // buffer's sample rate or length.
    706  if (mBuffer) {
    707    SendOffsetAndDurationParametersToTrack(ns);
    708  }
    709 
    710  // Don't set parameter unnecessarily
    711  if (aWhen > 0.0) {
    712    ns->SetDoubleParameter(START, aWhen);
    713  }
    714 
    715  Context()->StartBlockedAudioContextIfAllowed();
    716 }
    717 
    718 void AudioBufferSourceNode::Start(double aWhen, ErrorResult& aRv) {
    719  Start(aWhen, 0 /* offset */, Optional<double>(), aRv);
    720 }
    721 
    722 void AudioBufferSourceNode::SendBufferParameterToTrack(JSContext* aCx) {
    723  AudioNodeTrack* ns = mTrack;
    724  if (!ns) {
    725    return;
    726  }
    727 
    728  if (mBuffer) {
    729    AudioChunk data = mBuffer->GetThreadSharedChannelsForRate(aCx);
    730    ns->SetBuffer(std::move(data));
    731 
    732    if (mStartCalled) {
    733      SendOffsetAndDurationParametersToTrack(ns);
    734    }
    735  } else {
    736    ns->SetBuffer(AudioChunk());
    737 
    738    MarkInactive();
    739  }
    740 }
    741 
    742 void AudioBufferSourceNode::SendOffsetAndDurationParametersToTrack(
    743    AudioNodeTrack* aTrack) {
    744  NS_ASSERTION(
    745      mBuffer && mStartCalled,
    746      "Only call this when we have a buffer and start() has been called");
    747 
    748  float rate = mBuffer->SampleRate();
    749  aTrack->SetInt32Parameter(SAMPLE_RATE, rate);
    750 
    751  int32_t offsetSamples = std::max(0, NS_lround(mOffset * rate));
    752 
    753  // Don't set parameter unnecessarily
    754  if (offsetSamples > 0) {
    755    aTrack->SetInt32Parameter(BUFFERSTART, offsetSamples);
    756  }
    757 
    758  if (mDuration != std::numeric_limits<double>::min()) {
    759    MOZ_ASSERT(mDuration >= 0.0);  // provided by Start()
    760    MOZ_ASSERT(rate >= 0.0f);      // provided by AudioBuffer::Create()
    761    aTrack->SetDoubleParameter(DURATION, mDuration);
    762  }
    763  MarkActive();
    764 }
    765 
    766 void AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv) {
    767  if (!WebAudioUtils::IsTimeValid(aWhen)) {
    768    aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("stop time");
    769    return;
    770  }
    771 
    772  if (!mStartCalled) {
    773    aRv.ThrowInvalidStateError(
    774        "Start has not been called on this AudioBufferSourceNode.");
    775    return;
    776  }
    777 
    778  WEB_AUDIO_API_LOG("{:f}: {} {} Stop({:f})", Context()->CurrentTime(),
    779                    NodeType(), Id(), aWhen);
    780 
    781  AudioNodeTrack* ns = mTrack;
    782  if (!ns || !Context()) {
    783    // We've already stopped and had our track shut down
    784    return;
    785  }
    786 
    787  ns->SetTrackTimeParameter(STOP, Context(), std::max(0.0, aWhen));
    788 }
    789 
    790 void AudioBufferSourceNode::NotifyMainThreadTrackEnded() {
    791  MOZ_ASSERT(mTrack->IsEnded());
    792 
    793  class EndedEventDispatcher final : public Runnable {
    794   public:
    795    explicit EndedEventDispatcher(AudioBufferSourceNode* aNode)
    796        : mozilla::Runnable("EndedEventDispatcher"), mNode(aNode) {}
    797    NS_IMETHOD Run() override {
    798      // If it's not safe to run scripts right now, schedule this to run later
    799      if (!nsContentUtils::IsSafeToRunScript()) {
    800        nsContentUtils::AddScriptRunner(this);
    801        return NS_OK;
    802      }
    803 
    804      mNode->DispatchTrustedEvent(u"ended"_ns);
    805      // Release track resources.
    806      mNode->DestroyMediaTrack();
    807      return NS_OK;
    808    }
    809 
    810   private:
    811    RefPtr<AudioBufferSourceNode> mNode;
    812  };
    813 
    814  Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
    815 
    816  // Drop the playing reference
    817  // Warning: The below line might delete this.
    818  MarkInactive();
    819 }
    820 
    821 void AudioBufferSourceNode::SendLoopParametersToTrack() {
    822  if (!mTrack) {
    823    return;
    824  }
    825  // Don't compute and set the loop parameters unnecessarily
    826  if (mLoop && mBuffer) {
    827    float rate = mBuffer->SampleRate();
    828    double length = (double(mBuffer->Length()) / mBuffer->SampleRate());
    829    double actualLoopStart, actualLoopEnd;
    830    if (mLoopStart >= 0.0 && mLoopEnd > 0.0 && mLoopStart < mLoopEnd) {
    831      MOZ_ASSERT(mLoopStart != 0.0 || mLoopEnd != 0.0);
    832      actualLoopStart = (mLoopStart > length) ? 0.0 : mLoopStart;
    833      actualLoopEnd = std::min(mLoopEnd, length);
    834    } else {
    835      actualLoopStart = 0.0;
    836      actualLoopEnd = length;
    837    }
    838    int32_t loopStartTicks = NS_lround(actualLoopStart * rate);
    839    int32_t loopEndTicks = NS_lround(actualLoopEnd * rate);
    840    if (loopStartTicks < loopEndTicks) {
    841      SendInt32ParameterToTrack(LOOPSTART, loopStartTicks);
    842      SendInt32ParameterToTrack(LOOPEND, loopEndTicks);
    843      SendInt32ParameterToTrack(LOOP, 1);
    844    } else {
    845      // Be explicit about looping not happening if the offsets make
    846      // looping impossible.
    847      SendInt32ParameterToTrack(LOOP, 0);
    848    }
    849  } else {
    850    SendInt32ParameterToTrack(LOOP, 0);
    851  }
    852 }
    853 
    854 }  // namespace mozilla::dom