b=913854 pass start time as double parameter and subsample align r=padenot
authorKarl Tomlinson <karlt+@karlt.net>
Thu, 27 Feb 2014 11:45:04 +1300
changeset 171177 78fc4cbf92f8b4da144c483ee700c87a19d2f1da
parent 171176 36e1f6623a6d9b633c8ea20c16b0973b8ee8d0ff
child 171178 ffab287900cbfdfc1f0f62cbf5da990abcb023c7
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerspadenot
bugs913854
milestone30.0a1
b=913854 pass start time as double parameter and subsample align r=padenot The subsample alignment of resampled buffers provides seamless playback even when buffer durations are not an integer number of track ticks.
content/media/AudioNodeStream.cpp
content/media/AudioNodeStream.h
content/media/webaudio/AudioBufferSourceNode.cpp
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -510,32 +510,42 @@ AudioNodeStream::FinishOutput()
     AudioSegment emptySegment;
     l->NotifyQueuedTrackChanges(Graph(), AUDIO_TRACK,
                                 mSampleRate,
                                 track->GetSegment()->GetDuration(),
                                 MediaStreamListener::TRACK_EVENT_ENDED, emptySegment);
   }
 }
 
-TrackTicks
-AudioNodeStream::TicksFromDestinationTime(MediaStream* aDestination,
-                                          double aSeconds)
+double
+AudioNodeStream::TimeFromDestinationTime(AudioNodeStream* aDestination,
+                                         double aSeconds)
 {
-  MOZ_ASSERT(aDestination->AsAudioNodeStream() &&
-             aDestination->AsAudioNodeStream()->SampleRate() == SampleRate());
+  MOZ_ASSERT(aDestination->SampleRate() == SampleRate());
 
   double destinationSeconds = std::max(0.0, aSeconds);
   StreamTime streamTime = SecondsToMediaTime(destinationSeconds);
   // MediaTime does not have the resolution of double
   double offset = destinationSeconds - MediaTimeToSeconds(streamTime);
 
   GraphTime graphTime = aDestination->StreamTimeToGraphTime(streamTime);
   StreamTime thisStreamTime = GraphTimeToStreamTimeOptimistic(graphTime);
   double thisSeconds = MediaTimeToSeconds(thisStreamTime) + offset;
   MOZ_ASSERT(thisSeconds >= 0.0);
+  return thisSeconds;
+}
+
+TrackTicks
+AudioNodeStream::TicksFromDestinationTime(MediaStream* aDestination,
+                                          double aSeconds)
+{
+  AudioNodeStream* destination = aDestination->AsAudioNodeStream();
+  MOZ_ASSERT(destination);
+
+  double thisSeconds = TimeFromDestinationTime(destination, aSeconds);
   // Round to nearest
   TrackTicks ticks = thisSeconds * SampleRate() + 0.5;
   return ticks;
 }
 
 double
 AudioNodeStream::DestinationTimeFromTicks(AudioNodeStream* aDestination,
                                           TrackTicks aPosition)
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -124,16 +124,22 @@ public:
     return true;
   }
 
   // Any thread
   AudioNodeEngine* Engine() { return mEngine; }
   TrackRate SampleRate() const { return mSampleRate; }
 
   /**
+   * Convert a time in seconds on the destination stream to seconds
+   * on this stream.
+   */
+  double TimeFromDestinationTime(AudioNodeStream* aDestination,
+                                 double aSeconds);
+  /**
    * Convert a time in seconds on the destination stream to TrackTicks
    * on this stream.
    */
   TrackTicks TicksFromDestinationTime(MediaStream* aDestination,
                                       double aSeconds);
   /**
    * Get the destination stream time in seconds corresponding to a position on
    * this stream.
--- a/content/media/webaudio/AudioBufferSourceNode.cpp
+++ b/content/media/webaudio/AudioBufferSourceNode.cpp
@@ -51,17 +51,17 @@ NS_IMPL_RELEASE_INHERITED(AudioBufferSou
  * AudioNodeStream::SetInt32Parameter).
  */
 class AudioBufferSourceNodeEngine : public AudioNodeEngine
 {
 public:
   explicit AudioBufferSourceNodeEngine(AudioNode* aNode,
                                        AudioDestinationNode* aDestination) :
     AudioNodeEngine(aNode),
-    mStart(0), mBeginProcessing(0),
+    mStart(0.0), mBeginProcessing(0),
     mStop(TRACK_TICKS_MAX),
     mResampler(nullptr), mRemainingResamplerTail(0),
     mBufferEnd(0),
     mLoopStart(0), mLoopEnd(0),
     mBufferSampleRate(0), mBufferPosition(0), mChannels(0),
     mDopplerShift(1.0f),
     mDestination(static_cast<AudioNodeStream*>(aDestination->Stream())),
     mPlaybackRateTimeline(1.0f), mLoop(false)
@@ -90,33 +90,36 @@ public:
       break;
     default:
       NS_ERROR("Bad AudioBufferSourceNodeEngine TimelineParameter");
     }
   }
   virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam)
   {
     switch (aIndex) {
-    case AudioBufferSourceNode::START:
-      MOZ_ASSERT(!mStart, "Another START?");
-      mBeginProcessing = mStart = aParam;
-      break;
     case AudioBufferSourceNode::STOP: mStop = aParam; break;
     default:
       NS_ERROR("Bad AudioBufferSourceNodeEngine StreamTimeParameter");
     }
   }
   virtual void SetDoubleParameter(uint32_t aIndex, double aParam)
   {
     switch (aIndex) {
-      case AudioBufferSourceNode::DOPPLERSHIFT:
-        mDopplerShift = aParam > 0 && aParam == aParam ? aParam : 1.0;
-        break;
-      default:
-        NS_ERROR("Bad AudioBufferSourceNodeEngine double parameter.");
+    case AudioBufferSourceNode::START:
+      MOZ_ASSERT(!mStart, "Another START?");
+      mStart = mSource->TimeFromDestinationTime(mDestination, aParam) *
+        mSource->SampleRate();
+      // Round to nearest
+      mBeginProcessing = mStart + 0.5;
+      break;
+    case AudioBufferSourceNode::DOPPLERSHIFT:
+      mDopplerShift = aParam > 0 && aParam == aParam ? aParam : 1.0;
+      break;
+    default:
+      NS_ERROR("Bad AudioBufferSourceNodeEngine double parameter.");
     };
   }
   virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
   {
     switch (aIndex) {
     case AudioBufferSourceNode::SAMPLE_RATE: mBufferSampleRate = aParam; break;
     case AudioBufferSourceNode::BUFFERSTART:
       if (mBufferPosition == 0) {
@@ -147,17 +150,17 @@ public:
         (aChannels != mChannels ||
          // If the resampler has begun, then it will have moved
          // mBufferPosition to after the samples it has read, but it hasn't
          // output its buffered samples.  Keep using the resampler, even if
          // the rates now match, so that this latent segment is output.
          (aOutRate == mBufferSampleRate && !BegunResampling()))) {
       speex_resampler_destroy(mResampler);
       mResampler = nullptr;
-      mBeginProcessing = mStart;
+      mBeginProcessing = mStart + 0.5;
     }
 
     if (aOutRate == mBufferSampleRate && !mResampler) {
       return;
     }
 
     if (!mResampler) {
       mChannels = aChannels;
@@ -174,18 +177,26 @@ public:
       speex_resampler_set_rate(mResampler, currentInSampleRate, aOutRate);
     }
 
     if (!BegunResampling()) {
       // Low pass filter effects from the resampler mean that samples before
       // the start time are influenced by resampling the buffer.  The input
       // latency indicates half the filter width.
       int64_t inputLatency = speex_resampler_get_input_latency(mResampler);
-      // Intentionally rounding down.  There is no effect beyond the filter.
-      mBeginProcessing = mStart - inputLatency * aOutRate / mBufferSampleRate;
+      uint32_t ratioNum, ratioDen;
+      speex_resampler_get_ratio(mResampler, &ratioNum, &ratioDen);
+      // The output subsample resolution supported in aligning the resampler
+      // is ratioNum.  First round the start time to the nearest subsample.
+      int64_t subsample = mStart * ratioNum + 0.5;
+      // Now include the leading effects of the filter, and round *up* to the
+      // next whole tick, because there is no effect on samples outside the
+      // filter width.
+      mBeginProcessing =
+        (subsample - inputLatency * ratioDen + ratioNum - 1) / ratioNum;
     }
   }
 
   // Borrow a full buffer of size WEBAUDIO_BLOCK_SIZE from the source buffer
   // at offset aSourceOffset.  This avoids copying memory.
   void BorrowFromInputBuffer(AudioChunk* aOutput,
                              uint32_t aChannels)
   {
@@ -243,18 +254,21 @@ public:
         // First time the resampler is used.
         uint32_t inputLatency = speex_resampler_get_input_latency(resampler);
         inputLimit += inputLatency;
         // If starting after mStart, then play from the beginning of the
         // buffer, but correct for input latency.  If starting before mStart,
         // then align the resampler so that the time corresponding to the
         // first input sample is mStart.
         uint32_t skipFracNum = inputLatency * ratioDen;
-        if (*aCurrentPosition < mStart) {
-          skipFracNum -= (mStart - *aCurrentPosition) * ratioNum;
+        double leadTicks = mStart - *aCurrentPosition;
+        if (leadTicks > 0.0) {
+          // Round to nearest output subsample supported by the resampler at
+          // these rates.
+          skipFracNum -= leadTicks * ratioNum + 0.5;
           MOZ_ASSERT(skipFracNum < INT32_MAX, "mBeginProcessing is wrong?");
         }
         speex_resampler_set_skip_frac_num(resampler, skipFracNum);
 
         mBeginProcessing = -TRACK_TICKS_MAX;
       }
       inputLimit = std::min(inputLimit, availableInInputBuffer);
 
@@ -463,17 +477,17 @@ public:
     // 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;
     }
   }
 
-  TrackTicks mStart;
+  double mStart; // including the fractional position between ticks
   // 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_TICKS_MAX
   // indicates that the resampler has begun processing.
   TrackTicks mBeginProcessing;
   TrackTicks mStop;
   nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
   SpeexResamplerState* mResampler;
@@ -555,17 +569,17 @@ AudioBufferSourceNode::Start(double aWhe
   // We can't send these parameters without a buffer because we don't know the
   // buffer's sample rate or length.
   if (mBuffer) {
     SendOffsetAndDurationParametersToStream(ns);
   }
 
   // Don't set parameter unnecessarily
   if (aWhen > 0.0) {
-    ns->SetStreamTimeParameter(START, Context(), aWhen);
+    ns->SetDoubleParameter(START, mContext->DOMTimeToStreamTime(aWhen));
   }
 }
 
 void
 AudioBufferSourceNode::SendBufferParameterToStream(JSContext* aCx)
 {
   AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
   MOZ_ASSERT(ns, "Why don't we have a stream here?");