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.
--- 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