Bug 1618225 - Added DURATION to AudioBufferSourceNode. r=karlt
authorCorentin Arnould <koalab1999@gmail.com>
Tue, 27 Oct 2020 22:25:21 +0000
changeset 554897 bfda8cd8ce2f909377293938375f107007324ff9
parent 554896 2f48d38b9bc922b6c61d7de5049a4eac78f24ac5
child 554898 3aa921cab4179f31edd28f63f04325f55fcf6a8b
push id129637
push userpadenot@mozilla.com
push dateWed, 28 Oct 2020 13:41:43 +0000
treeherderautoland@0c7b5d5a2307 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs1618225
milestone84.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1618225 - Added DURATION to AudioBufferSourceNode. r=karlt Handle duration, and it works on looping sources. Differential Revision: https://phabricator.services.mozilla.com/D84402
dom/media/webaudio/AudioBufferSourceNode.cpp
dom/media/webaudio/AudioBufferSourceNode.h
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -30,30 +30,30 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END_INHERITING(AudioScheduledSourceNode)
 
 NS_IMPL_ADDREF_INHERITED(AudioBufferSourceNode, AudioScheduledSourceNode)
 NS_IMPL_RELEASE_INHERITED(AudioBufferSourceNode, AudioScheduledSourceNode)
 
 /**
  * Media-thread playback engine for AudioBufferSourceNode.
  * Nothing is played until a non-null buffer has been set (via
- * AudioNodeTrack::SetBuffer) and a non-zero mBufferEnd has been set (via
- * AudioNodeTrack::SetInt32Parameter).
+ * AudioNodeTrack::SetBuffer) and a non-zero mBufferSampleRate has been set
+ * (via AudioNodeTrack::SetInt32Parameter)
  */
 class AudioBufferSourceNodeEngine final : public AudioNodeEngine {
  public:
   AudioBufferSourceNodeEngine(AudioNode* aNode,
                               AudioDestinationNode* aDestination)
       : AudioNodeEngine(aNode),
         mStart(0.0),
         mBeginProcessing(0),
         mStop(TRACK_TIME_MAX),
         mResampler(nullptr),
         mRemainingResamplerTail(0),
-        mBufferEnd(0),
+        mRemainingFrames(TRACK_TICKS_MAX),
         mLoopStart(0),
         mLoopEnd(0),
         mBufferPosition(0),
         mBufferSampleRate(0),
         // mResamplerOutRate is initialized in UpdateResampler().
         mChannels(0),
         mDestination(aDestination->Track()),
         mPlaybackRateTimeline(1.0f),
@@ -96,16 +96,20 @@ class AudioBufferSourceNodeEngine final 
   void SetDoubleParameter(uint32_t aIndex, double aParam) override {
     switch (aIndex) {
       case AudioBufferSourceNode::START:
         MOZ_ASSERT(!mStart, "Another START?");
         mStart = aParam * mDestination->mSampleRate;
         // Round to nearest
         mBeginProcessing = mStart + 0.5;
         break;
+      case AudioBufferSourceNode::DURATION:
+        MOZ_ASSERT(aParam >= 0);
+        mRemainingFrames = llround(aParam * mBufferSampleRate);
+        break;
       default:
         NS_ERROR("Bad AudioBufferSourceNodeEngine double parameter.");
     };
   }
   void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override {
     switch (aIndex) {
       case AudioBufferSourceNode::SAMPLE_RATE:
         MOZ_ASSERT(aParam > 0);
@@ -113,20 +117,16 @@ class AudioBufferSourceNodeEngine final 
         mSource->SetActive();
         break;
       case AudioBufferSourceNode::BUFFERSTART:
         MOZ_ASSERT(aParam >= 0);
         if (mBufferPosition == 0) {
           mBufferPosition = aParam;
         }
         break;
-      case AudioBufferSourceNode::BUFFEREND:
-        MOZ_ASSERT(aParam >= 0);
-        mBufferEnd = aParam;
-        break;
       case AudioBufferSourceNode::LOOP:
         mLoop = !!aParam;
         break;
       case AudioBufferSourceNode::LOOPSTART:
         MOZ_ASSERT(aParam >= 0);
         mLoopStart = aParam;
         break;
       case AudioBufferSourceNode::LOOPEND:
@@ -291,20 +291,23 @@ class AudioBufferSourceNodeEngine final 
           MOZ_ASSERT(mBuffer.mBufferFormat == AUDIO_FORMAT_S16);
           const int16_t* inputData =
               mBuffer.ChannelData<int16_t>()[i] + mBufferPosition;
           WebAudioUtils::SpeexResamplerProcess(
               resampler, i, inputData, &inSamples, outputData, &outSamples);
         }
         if (++i == aChannels) {
           mBufferPosition += inSamples;
-          MOZ_ASSERT(mBufferPosition <= mBufferEnd || mLoop);
+          mRemainingFrames -= inSamples;
+          MOZ_ASSERT(mBufferPosition <= mBuffer.GetDuration());
+          MOZ_ASSERT(mRemainingFrames >= 0);
           *aOffsetWithinBlock += outSamples;
           *aCurrentPosition += outSamples;
-          if (inSamples == availableInInputBuffer && !mLoop) {
+          if ((!mLoop && inSamples == availableInInputBuffer) ||
+              mRemainingFrames == 0) {
             // We'll feed in enough zeros to empty out the resampler's memory.
             // This handles the output latency as well as capturing the low
             // pass effects of the resample filter.
             mRemainingResamplerTail =
                 2 * speex_resampler_get_input_latency(resampler) - 1;
           }
           return;
         }
@@ -428,16 +431,17 @@ class AudioBufferSourceNodeEngine final 
         MOZ_ASSERT(mBuffer.mBufferFormat == AUDIO_FORMAT_S16);
         CopyFromInputBuffer<int16_t>(aOutput, aChannels, *aOffsetWithinBlock,
                                      numFrames);
       }
     }
     *aOffsetWithinBlock += numFrames;
     *aCurrentPosition += numFrames;
     mBufferPosition += numFrames;
+    mRemainingFrames -= numFrames;
   }
 
   int32_t ComputeFinalOutSampleRate(float aPlaybackRate, float aDetune) {
     float computedPlaybackRate = aPlaybackRate * exp2(aDetune / 1200.f);
     // Make sure the playback rate is something our resampler can work with.
     int32_t rate = WebAudioUtils::TruncateFloatToInt<int32_t>(
         mSource->mSampleRate / computedPlaybackRate);
     return rate ? rate : mBufferSampleRate;
@@ -477,51 +481,54 @@ class AudioBufferSourceNodeEngine final 
     }
 
     TrackTime streamPosition = mDestination->GraphTimeToTrackTime(aFrom);
     uint32_t channels = mBuffer.ChannelCount();
 
     UpdateSampleRateIfNeeded(channels, streamPosition);
 
     uint32_t written = 0;
-    while (written < WEBAUDIO_BLOCK_SIZE) {
-      if (mStop != TRACK_TIME_MAX && streamPosition >= mStop) {
-        FillWithZeroes(aOutput, channels, &written, &streamPosition,
-                       TRACK_TIME_MAX);
-        continue;
+    while (true) {
+      if ((mStop != TRACK_TIME_MAX && streamPosition >= mStop) ||
+          (!mRemainingResamplerTail &&
+           ((mBufferPosition >= mBuffer.GetDuration() && !mLoop) ||
+            mRemainingFrames <= 0))) {
+        if (written != WEBAUDIO_BLOCK_SIZE) {
+          FillWithZeroes(aOutput, channels, &written, &streamPosition,
+                         TRACK_TIME_MAX);
+        }
+        *aFinished = true;
+        break;
+      }
+      if (written == WEBAUDIO_BLOCK_SIZE) {
+        break;
       }
       if (streamPosition < mBeginProcessing) {
         FillWithZeroes(aOutput, channels, &written, &streamPosition,
                        mBeginProcessing);
         continue;
       }
+
+      TrackTicks bufferLeft;
       if (mLoop) {
         // mLoopEnd can become less than mBufferPosition when a LOOPEND engine
         // parameter is received after "loopend" is changed on the node or a
         // new buffer with lower samplerate is set.
         if (mBufferPosition >= mLoopEnd) {
           mBufferPosition = mLoopStart;
         }
-        CopyFromBuffer(aOutput, channels, &written, &streamPosition, mLoopEnd);
+        bufferLeft =
+            std::min<TrackTicks>(mRemainingFrames, mLoopEnd - mBufferPosition);
       } else {
-        if (mBufferPosition < mBufferEnd || mRemainingResamplerTail) {
-          CopyFromBuffer(aOutput, channels, &written, &streamPosition,
-                         mBufferEnd);
-        } else {
-          FillWithZeroes(aOutput, channels, &written, &streamPosition,
-                         TRACK_TIME_MAX);
-        }
+        bufferLeft =
+            std::min(mRemainingFrames, mBuffer.GetDuration() - mBufferPosition);
       }
-    }
 
-    // We've finished if we've gone past mStop, or if we're past mDuration when
-    // looping is disabled.
-    if (streamPosition >= mStop ||
-        (!mLoop && mBufferPosition >= mBufferEnd && !mRemainingResamplerTail)) {
-      *aFinished = true;
+      CopyFromBuffer(aOutput, channels, &written, &streamPosition,
+                     bufferLeft + mBufferPosition);
     }
   }
 
   bool IsActive() const override {
     // Whether buffer has been set and start() has been called.
     return mBufferSampleRate != 0;
   }
 
@@ -553,20 +560,20 @@ class AudioBufferSourceNodeEngine final 
   // Low pass filter effects from the resampler mean that samples before the
   // start time are influenced by resampling the buffer.  mBeginProcessing
   // includes the extent of this filter.  The special value of -TRACK_TIME_MAX
   // indicates that the resampler has begun processing.
   TrackTime mBeginProcessing;
   TrackTime mStop;
   AudioChunk mBuffer;
   SpeexResamplerState* mResampler;
-  // mRemainingResamplerTail, like mBufferPosition, and
-  // mBufferEnd, is measured in input buffer samples.
+  // mRemainingResamplerTail, like mBufferPosition
+  // is measured in input buffer samples.
   uint32_t mRemainingResamplerTail;
-  uint32_t mBufferEnd;
+  TrackTicks mRemainingFrames;
   uint32_t mLoopStart;
   uint32_t mLoopEnd;
   uint32_t mBufferPosition;
   int32_t mBufferSampleRate;
   int32_t mResamplerOutRate;
   uint32_t mChannels;
   RefPtr<AudioNodeTrack> mDestination;
 
@@ -711,52 +718,43 @@ void AudioBufferSourceNode::SendBufferPa
   if (mBuffer) {
     AudioChunk data = mBuffer->GetThreadSharedChannelsForRate(aCx);
     ns->SetBuffer(std::move(data));
 
     if (mStartCalled) {
       SendOffsetAndDurationParametersToTrack(ns);
     }
   } else {
-    ns->SetInt32Parameter(BUFFEREND, 0);
     ns->SetBuffer(AudioChunk());
 
     MarkInactive();
   }
 }
 
 void AudioBufferSourceNode::SendOffsetAndDurationParametersToTrack(
     AudioNodeTrack* aTrack) {
   NS_ASSERTION(
       mBuffer && mStartCalled,
       "Only call this when we have a buffer and start() has been called");
 
   float rate = mBuffer->SampleRate();
   aTrack->SetInt32Parameter(SAMPLE_RATE, rate);
 
-  int32_t bufferEnd = mBuffer->Length();
   int32_t offsetSamples = std::max(0, NS_lround(mOffset * rate));
 
   // Don't set parameter unnecessarily
   if (offsetSamples > 0) {
     aTrack->SetInt32Parameter(BUFFERSTART, offsetSamples);
   }
 
   if (mDuration != std::numeric_limits<double>::min()) {
     MOZ_ASSERT(mDuration >= 0.0);  // provided by Start()
     MOZ_ASSERT(rate >= 0.0f);      // provided by AudioBuffer::Create()
-    static_assert(std::numeric_limits<double>::digits >=
-                      std::numeric_limits<decltype(bufferEnd)>::digits,
-                  "bufferEnd should be represented exactly by double");
-    // + 0.5 rounds mDuration to nearest sample when assigned to bufferEnd.
-    bufferEnd =
-        std::min<double>(bufferEnd, offsetSamples + mDuration * rate + 0.5);
+    aTrack->SetDoubleParameter(DURATION, mDuration);
   }
-  aTrack->SetInt32Parameter(BUFFEREND, bufferEnd);
-
   MarkActive();
 }
 
 void AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv) {
   if (!WebAudioUtils::IsTimeValid(aWhen)) {
     aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("stop time");
     return;
   }
--- a/dom/media/webaudio/AudioBufferSourceNode.h
+++ b/dom/media/webaudio/AudioBufferSourceNode.h
@@ -87,28 +87,26 @@ class AudioBufferSourceNode final : publ
 
  private:
   explicit AudioBufferSourceNode(AudioContext* aContext);
   ~AudioBufferSourceNode() = default;
 
   friend class AudioBufferSourceNodeEngine;
   // START is sent during Start().
   // STOP is sent during Stop().
-  // BUFFERSTART and BUFFEREND are sent when SetBuffer() and Start() have
+  // BUFFERSTART and DURATION are sent when SetBuffer() and Start() have
   // been called (along with sending the buffer).
   enum EngineParameters {
     SAMPLE_RATE,
     START,
     STOP,
     // BUFFERSTART is the "offset" passed to start(), multiplied by
     // buffer.sampleRate.
     BUFFERSTART,
-    // BUFFEREND is the sum of "offset" and "duration" passed to start(),
-    // multiplied by buffer.sampleRate, or the size of the buffer, if smaller.
-    BUFFEREND,
+    DURATION,
     LOOP,
     LOOPSTART,
     LOOPEND,
     PLAYBACKRATE,
     DETUNE
   };
 
   void SendLoopParametersToTrack();