Bug 1085356: Fix Mac audio output changes on older/different macs r=padenot
authorRandell Jesup <rjesup@jesup.org>
Wed, 29 Oct 2014 10:47:28 -0400
changeset 237354 8ccb2bf14892d13c278580667b2a1670e5ec2b88
parent 237353 e82273c162bf9e7fc764d426a217994c31b702b9
child 237355 5e5b3c89df165dae1ee53e682641ccd2599e1200
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1085356
milestone36.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 1085356: Fix Mac audio output changes on older/different macs r=padenot Remove any buffered audio from before an output switch is signaled, as well as handling gaps in callbacks after a switch.
dom/media/GraphDriver.cpp
dom/media/MediaSegment.h
dom/media/MediaStreamGraph.cpp
dom/media/MediaStreamGraphImpl.h
dom/media/StreamBuffer.h
dom/media/TrackUnionStream.cpp
--- a/dom/media/GraphDriver.cpp
+++ b/dom/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,
@@ -791,17 +793,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);
@@ -1027,18 +1031,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/dom/media/MediaSegment.h
+++ b/dom/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/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -1434,16 +1434,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();
@@ -2717,16 +2718,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/dom/media/MediaStreamGraphImpl.h
+++ b/dom/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/dom/media/StreamBuffer.h
+++ b/dom/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/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -258,18 +258,19 @@ TrackUnionStream::TrackUnionStream(DOMMe
       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);
@@ -300,16 +301,24 @@ TrackUnionStream::TrackUnionStream(DOMMe
           // 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