Bug 1085356: Fix Mac audio output changes on older/different macs r=padenot a=lmandel
authorRandell Jesup <rjesup@jesup.org>
Wed, 29 Oct 2014 10:47:28 -0400
changeset 225912 ddc951a77894
parent 225911 80b1fc2042df
child 225913 bf50cf09506c
push id4064
push userrjesup@wgate.com
push date2014-11-03 01:27 +0000
treeherdermozilla-beta@ddc951a77894 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot, lmandel
bugs1085356
milestone34.0
Bug 1085356: Fix Mac audio output changes on older/different macs r=padenot a=lmandel Remove any buffered audio from before an output switch is signaled, as well as handling gaps in callbacks after a switch.
content/media/GraphDriver.cpp
content/media/MediaSegment.h
content/media/MediaStreamGraph.cpp
content/media/MediaStreamGraphImpl.h
content/media/StreamBuffer.h
content/media/TrackUnionStream.h
--- a/content/media/GraphDriver.cpp
+++ b/content/media/GraphDriver.cpp
@@ -294,16 +294,18 @@ ThreadedDriver::RunThread()
     mNextStateComputedTime =
       mGraphImpl->RoundUpToNextAudioBlock(
         nextCurrentTime + mGraphImpl->MillisecondsToMediaTime(AUDIO_TARGET_MS));
     STREAM_LOG(PR_LOG_DEBUG,
                ("interval[%ld; %ld] state[%ld; %ld]",
                (long)mIterationStart, (long)mIterationEnd,
                (long)mStateComputedTime, (long)mNextStateComputedTime));
 
+    mGraphImpl->mFlushSourcesNow = mGraphImpl->mFlushSourcesOnNextIteration;
+    mGraphImpl->mFlushSourcesOnNextIteration = false;
     stillProcessing = mGraphImpl->OneIteration(prevCurrentTime,
                                                nextCurrentTime,
                                                StateComputedTime(),
                                                mNextStateComputedTime);
 
     if (mNextDriver && stillProcessing) {
       STREAM_LOG(PR_LOG_DEBUG, ("Switching to AudioCallbackDriver"));
       mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd,
@@ -790,17 +792,19 @@ bool
 AudioCallbackDriver::OSXDeviceSwitchingWorkaround()
 {
   MonitorAutoLock mon(GraphImpl()->GetMonitor());
   if (mSelfReference) {
     // Apparently, depending on the osx version, on device switch, the
     // callback is called "some" number of times, and then stops being called,
     // and then gets called again. 10 is to be safe, it's a low-enough number
     // of milliseconds anyways (< 100ms)
+    //STREAM_LOG(PR_LOG_DEBUG, ("Callbacks during switch: %d", mCallbackReceivedWhileSwitching+1));
     if (mCallbackReceivedWhileSwitching++ >= 10) {
+      STREAM_LOG(PR_LOG_DEBUG, ("Got %d callbacks, switching back to CallbackDriver", mCallbackReceivedWhileSwitching));
       // If we have a self reference, we have fallen back temporarily on a
       // system clock driver, but we just got called back, that means the osx
       // audio backend has switched to the new device.
       // Ask the graph to switch back to the previous AudioCallbackDriver
       // (`this`), and when the graph has effectively switched, we can drop
       // the self reference and unref the SystemClockDriver we fallen back on.
       if (GraphImpl()->CurrentDriver() == this) {
         mSelfReference.Drop(this);
@@ -1026,18 +1030,20 @@ AudioCallbackDriver::DeviceChangedCallba
   // SourceMediaStream.
   if (!GraphImpl()->Running()) {
     return;
   }
 
   if (mSelfReference) {
     return;
   }
+  STREAM_LOG(PR_LOG_ERROR, ("Switching to SystemClockDriver during output switch"));
   mSelfReference.Take(this);
   mCallbackReceivedWhileSwitching = 0;
+  mGraphImpl->mFlushSourcesOnNextIteration = true;
   mNextDriver = new SystemClockDriver(GraphImpl());
   mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd,
                             mStateComputedTime, mNextStateComputedTime);
   mGraphImpl->SetCurrentDriver(mNextDriver);
   mNextDriver->Start();
 #endif
 }
 
--- a/content/media/MediaSegment.h
+++ b/content/media/MediaSegment.h
@@ -84,16 +84,20 @@ public:
    */
   virtual void AppendSlice(const MediaSegment& aSource,
                            TrackTicks aStart, TrackTicks aEnd) = 0;
   /**
    * Replace all contents up to aDuration with null data.
    */
   virtual void ForgetUpTo(TrackTicks aDuration) = 0;
   /**
+   * Forget all data buffered after a given point
+   */
+  virtual void FlushAfter(TrackTicks aNewEnd) = 0;
+  /**
    * Insert aDuration of null data at the start of the segment.
    */
   virtual void InsertNullDataAtStart(TrackTicks aDuration) = 0;
   /**
    * Insert aDuration of null data at the end of the segment.
    */
   virtual void AppendNullData(TrackTicks aDuration) = 0;
   /**
@@ -171,16 +175,39 @@ public:
         mDuration += extraToForget;
       }
       return;
     }
     RemoveLeading(aDuration, 0);
     mChunks.InsertElementAt(0)->SetNull(aDuration);
     mDuration += aDuration;
   }
+  virtual void FlushAfter(TrackTicks aNewEnd)
+  {
+    if (mChunks.IsEmpty()) {
+      return;
+    }
+
+    if (mChunks[0].IsNull()) {
+      TrackTicks extraToKeep = aNewEnd - mChunks[0].GetDuration();
+      if (extraToKeep < 0) {
+        // reduce the size of the Null, get rid of everthing else
+        mChunks[0].SetNull(aNewEnd);
+        extraToKeep = 0;
+      }
+      RemoveTrailing(extraToKeep, 1);
+    } else {
+      if (aNewEnd > mDuration) {
+        NS_ASSERTION(aNewEnd <= mDuration, "can't add data in FlushAfter");
+        return;
+      }
+      RemoveTrailing(aNewEnd, 0);
+    }
+    mDuration = aNewEnd;
+  }
   virtual void InsertNullDataAtStart(TrackTicks aDuration)
   {
     if (aDuration <= 0) {
       return;
     }
     if (!mChunks.IsEmpty() && mChunks[0].IsNull()) {
       mChunks[0].mDuration += aDuration;
     } else {
@@ -345,16 +372,38 @@ protected:
       }
       t -= c->GetDuration();
       chunksToRemove = i + 1 - aStartIndex;
     }
     mChunks.RemoveElementsAt(aStartIndex, chunksToRemove);
     mDuration -= aDuration - t;
   }
 
+  void RemoveTrailing(TrackTicks aKeep, uint32_t aStartIndex)
+  {
+    NS_ASSERTION(aKeep >= 0, "Can't keep negative duration");
+    TrackTicks t = aKeep;
+    uint32_t i;
+    for (i = aStartIndex; i < mChunks.Length(); ++i) {
+      Chunk* c = &mChunks[i];
+      if (c->GetDuration() > t) {
+        c->SliceTo(0, t);
+        break;
+      }
+      t -= c->GetDuration();
+      if (t == 0) {
+        break;
+      }
+    }
+    if (i+1 < mChunks.Length()) {
+      mChunks.RemoveElementsAt(i+1, mChunks.Length() - (i+1));
+    }
+    // Caller must adjust mDuration
+  }
+
   nsTArray<Chunk> mChunks;
 #ifdef MOZILLA_INTERNAL_API
   mozilla::TimeStamp mTimeStamp;
 #endif
 };
 
 }
 
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -1432,16 +1432,17 @@ MediaStreamGraphImpl::OneIteration(Graph
       // stream is responsible for calling Destroy on them.
       return false;
     }
 
     CurrentDriver()->WaitForNextIteration();
 
     SwapMessageQueues();
   }
+  mFlushSourcesNow = false;
 
   return true;
 }
 
 void
 MediaStreamGraphImpl::ApplyStreamUpdate(StreamUpdate* aUpdate)
 {
   mMonitor.AssertCurrentThreadOwns();
@@ -2715,16 +2716,18 @@ MediaStreamGraphImpl::MediaStreamGraphIm
   , mNeedAnotherIteration(false)
   , mGraphDriverAsleep(false)
   , mMonitor("MediaStreamGraphImpl")
   , mLifecycleState(LIFECYCLE_THREAD_NOT_STARTED)
   , mEndTime(GRAPH_TIME_MAX)
   , mSampleRate(aSampleRate)
   , mForceShutDown(false)
   , mPostedRunInStableStateEvent(false)
+  , mFlushSourcesNow(false)
+  , mFlushSourcesOnNextIteration(false)
   , mDetectedNotRunning(false)
   , mPostedRunInStableState(false)
   , mRealtime(aRealtime)
   , mNonRealtimeProcessing(false)
   , mStreamOrderDirty(false)
   , mLatencyLog(AsyncLatencyLogger::Get())
 #ifdef MOZ_WEBRTC
   , mFarendObserverRef(nullptr)
--- a/content/media/MediaStreamGraphImpl.h
+++ b/content/media/MediaStreamGraphImpl.h
@@ -605,16 +605,23 @@ public:
    */
   bool mForceShutDown;
   /**
    * True when we have posted an event to the main thread to run
    * RunInStableState() and the event hasn't run yet.
    */
   bool mPostedRunInStableStateEvent;
 
+  /**
+   * Used to flush any accumulated data when the output streams
+   * may have stalled (on Mac after an output device change)
+   */
+  bool mFlushSourcesNow;
+  bool mFlushSourcesOnNextIteration;
+
   // Main thread only
 
   /**
    * Messages posted by the current event loop task. These are forwarded to
    * the media graph thread during RunInStableState. We can't forward them
    * immediately because we want all messages between stable states to be
    * processed as an atomic batch.
    */
--- a/content/media/StreamBuffer.h
+++ b/content/media/StreamBuffer.h
@@ -149,16 +149,22 @@ public:
     MediaSegment* RemoveSegment()
     {
       return mSegment.forget();
     }
     void ForgetUpTo(TrackTicks aTime)
     {
       mSegment->ForgetUpTo(aTime);
     }
+    void FlushAfter(TrackTicks aNewEnd)
+    {
+      // Forget everything after a given endpoint
+      // a specified amount
+      mSegment->FlushAfter(aNewEnd);
+    }
 
     size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
     {
       size_t amount = aMallocSizeOf(this);
       if (mSegment) {
         amount += mSegment->SizeOfIncludingThis(aMallocSizeOf);
       }
       return amount;
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -250,18 +250,19 @@ protected:
       StreamTime inputEnd = source->GraphTimeToStreamTime(interval.mEnd);
       TrackTicks inputTrackEndPoint = aInputTrack->GetEnd();
 
       if (aInputTrack->IsEnded() &&
           aInputTrack->GetEndTimeRoundDown() <= inputEnd) {
         *aOutputTrackFinished = true;
       }
 
-      if (interval.mStart >= interval.mEnd)
+      if (interval.mStart >= interval.mEnd) {
         break;
+      }
       next = interval.mEnd;
 
       // Ticks >= startTicks and < endTicks are in the interval
       StreamTime outputEnd = GraphTimeToStreamTime(interval.mEnd);
       TrackTicks startTicks = outputTrack->GetEnd();
       StreamTime outputStart = GraphTimeToStreamTime(interval.mStart);
       MOZ_ASSERT(startTicks == TimeToTicksRoundUp(rate, outputStart), "Samples missing");
       TrackTicks endTicks = TimeToTicksRoundUp(rate, outputEnd);
@@ -292,16 +293,24 @@ protected:
           // Start of a new series of intervals where neither stream is blocked.
           map->mEndOfConsumedInputTicks = TimeToTicksRoundDown(rate, inputStart) - 1;
         }
         TrackTicks inputStartTicks = map->mEndOfConsumedInputTicks;
         TrackTicks inputEndTicks = inputStartTicks + ticks;
         map->mEndOfConsumedInputTicks = inputEndTicks;
         map->mEndOfLastInputIntervalInInputStream = inputEnd;
         map->mEndOfLastInputIntervalInOutputStream = outputEnd;
+
+        if (GraphImpl()->mFlushSourcesNow) {
+          TrackTicks flushto = inputEndTicks;
+          STREAM_LOG(PR_LOG_DEBUG, ("TrackUnionStream %p flushing after %lld of %lld ticks of input data from track %d for track %d",
+              this, flushto, aInputTrack->GetSegment()->GetDuration(), aInputTrack->GetID(), outputTrack->GetID()));
+          aInputTrack->FlushAfter(flushto);
+          MOZ_ASSERT(inputTrackEndPoint >= aInputTrack->GetEnd());
+        }
         // Now we prove that the above properties hold:
         // Property #1: trivial by construction.
         // Property #3: trivial by construction. Between every two
         // intervals where both streams are not blocked, the above if condition
         // is false and mEndOfConsumedInputTicks advances exactly to match
         // the ticks that were consumed.
         // Property #2:
         // Let originalOutputStart be the value of outputStart and originalInputStart