Bug 943461. Part 5: Don't allow a stream to finish before it has produced output up to mStateComputedTime. r=padenot
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 06 Dec 2013 09:23:57 +1300
changeset 162322 08e5578eded873ca3a4ff49abac1984fa4f377c1
parent 162321 ac6bfc6f050e1645f7bb8a3e1004583d50e99d17
child 162323 7590e388abaffc8b125301b5141a938bb21116b5
push idunknown
push userunknown
push dateunknown
reviewerspadenot
bugs943461
milestone29.0a1
Bug 943461. Part 5: Don't allow a stream to finish before it has produced output up to mStateComputedTime. r=padenot
content/media/AudioNodeExternalInputStream.cpp
content/media/AudioNodeExternalInputStream.h
content/media/AudioNodeStream.cpp
content/media/AudioNodeStream.h
content/media/MediaStreamGraph.cpp
content/media/MediaStreamGraph.h
content/media/TrackUnionStream.h
--- a/content/media/AudioNodeExternalInputStream.cpp
+++ b/content/media/AudioNodeExternalInputStream.cpp
@@ -319,17 +319,18 @@ ConvertSegmentToAudioBlock(AudioSegment*
   while (!ci.IsEnded()) {
     CopyChunkToBlock(*ci, aBlock, duration);
     duration += ci->GetDuration();
     ci.Next();
   }
 }
 
 void
-AudioNodeExternalInputStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
+AudioNodeExternalInputStream::ProduceOutput(GraphTime aFrom, GraphTime aTo,
+                                            uint32_t aFlags)
 {
   // According to spec, number of outputs is always 1.
   mLastChunks.SetLength(1);
 
   // GC stuff can result in our input stream being destroyed before this stream.
   // Handle that.
   if (mInputs.IsEmpty()) {
     mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
--- a/content/media/AudioNodeExternalInputStream.h
+++ b/content/media/AudioNodeExternalInputStream.h
@@ -20,17 +20,17 @@ namespace mozilla {
  * input --- handling any number of audio tracks, resampling them from whatever
  * sample rate they're using, and handling blocking of the input MediaStream.
  */
 class AudioNodeExternalInputStream : public AudioNodeStream {
 public:
   AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate);
   ~AudioNodeExternalInputStream();
 
-  virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo) MOZ_OVERRIDE;
+  virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
 
 private:
   // For storing pointers and data about input tracks, like the last TrackTick which
   // was read, and the associated speex resampler.
   struct TrackMapEntry {
     ~TrackMapEntry();
 
     /**
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -394,26 +394,21 @@ AudioNodeStream::UpMixDownMixChunk(const
         aOutputChannels.Length() - aOutputChannelCount);
     }
   }
 }
 
 // The MediaStreamGraph guarantees that this is actually one block, for
 // AudioNodeStreams.
 void
-AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
+AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags)
 {
-  if (mMarkAsFinishedAfterThisBlock) {
-    // This stream was finished the last time that we looked at it, and all
-    // of the depending streams have finished their output as well, so now
-    // it's time to mark this stream as finished.
-    FinishOutput();
-  }
-
   EnsureTrack(AUDIO_TRACK, mSampleRate);
+  // No more tracks will be coming
+  mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX);
 
   uint16_t outputCount = std::max(uint16_t(1), mEngine->OutputCount());
   mLastChunks.SetLength(outputCount);
 
   if (mMuted) {
     for (uint16_t i = 0; i < outputCount; ++i) {
       mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
     }
@@ -441,17 +436,26 @@ AudioNodeStream::ProduceOutput(GraphTime
   }
 
   if (mDisabledTrackIDs.Contains(static_cast<TrackID>(AUDIO_TRACK))) {
     for (uint32_t i = 0; i < mLastChunks.Length(); ++i) {
       mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
     }
   }
 
-  AdvanceOutputSegment();
+  if (!IsFinishedOnGraphThread()) {
+    // Don't output anything after we've finished!
+    AdvanceOutputSegment();
+    if (mMarkAsFinishedAfterThisBlock && (aFlags & ALLOW_FINISH)) {
+      // This stream was finished the last time that we looked at it, and all
+      // of the depending streams have finished their output as well, so now
+      // it's time to mark this stream as finished.
+      FinishOutput();
+    }
+  }
 }
 
 void
 AudioNodeStream::AdvanceOutputSegment()
 {
   StreamBuffer::Track* track = EnsureTrack(AUDIO_TRACK, mSampleRate);
   AudioSegment* segment = track->Get<AudioSegment>();
 
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -79,25 +79,25 @@ public:
                                   dom::ChannelCountMode aChannelCountMoe,
                                   dom::ChannelInterpretation aChannelInterpretation);
   void SetAudioParamHelperStream()
   {
     MOZ_ASSERT(!mAudioParamStream, "Can only do this once");
     mAudioParamStream = true;
   }
 
-  virtual AudioNodeStream* AsAudioNodeStream() { return this; }
+  virtual AudioNodeStream* AsAudioNodeStream() MOZ_OVERRIDE { return this; }
 
   // Graph thread only
   void SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
                                   double aStreamTime);
   void SetChannelMixingParametersImpl(uint32_t aNumberOfChannels,
                                       dom::ChannelCountMode aChannelCountMoe,
                                       dom::ChannelInterpretation aChannelInterpretation);
-  virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo);
+  virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
   TrackTicks GetCurrentPosition();
   bool IsAudioParamStream() const
   {
     return mAudioParamStream;
   }
   void Mute() {
     mMuted = true;
   }
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -1087,17 +1087,17 @@ MediaStreamGraphImpl::ProduceDataForStre
                                                         GraphTime aTo)
 {
   GraphTime t = aFrom;
   while (t < aTo) {
     GraphTime next = RoundUpToNextAudioBlock(aSampleRate, t);
     for (uint32_t i = aStreamIndex; i < mStreams.Length(); ++i) {
       ProcessedMediaStream* ps = mStreams[i]->AsProcessedStream();
       if (ps) {
-        ps->ProduceOutput(t, next);
+        ps->ProduceOutput(t, next, (next == aTo) ? ProcessedMediaStream::ALLOW_FINISH : 0);
       }
     }
     t = next;
   }
   NS_ASSERTION(t == aTo, "Something went wrong with rounding to block boundaries");
 }
 
 void
@@ -1192,17 +1192,18 @@ MediaStreamGraphImpl::RunThread()
             }
 #endif
             // Since an AudioNodeStream is present, go ahead and
             // produce audio block by block for all the rest of the streams.
             ProduceDataForStreamsBlockByBlock(i, n->SampleRate(), prevComputedTime, mStateComputedTime);
             ticksProcessed += TimeToTicksRoundDown(n->SampleRate(), mStateComputedTime - prevComputedTime);
             doneAllProducing = true;
           } else {
-            ps->ProduceOutput(prevComputedTime, mStateComputedTime);
+            ps->ProduceOutput(prevComputedTime, mStateComputedTime,
+                              ProcessedMediaStream::ALLOW_FINISH);
             NS_ASSERTION(stream->mBuffer.GetEnd() >=
                          GraphTimeToStreamTime(stream, mStateComputedTime),
                        "Stream did not produce enough data");
           }
         }
       }
       NotifyHasCurrentData(stream);
       if (mRealtime) {
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -953,20 +953,29 @@ public:
     return mInputs.Length();
   }
   virtual void DestroyImpl();
   /**
    * This gets called after we've computed the blocking states for all
    * streams (mBlocked is up to date up to mStateComputedTime).
    * Also, we've produced output for all streams up to this one. If this stream
    * is not in a cycle, then all its source streams have produced data.
-   * Generate output up to mStateComputedTime.
+   * Generate output from aFrom to aTo.
    * This is called only on streams that have not finished.
+   * ProduceOutput is allowed to call FinishOnGraphThread only if ALLOW_FINISH
+   * is in aFlags. (This flag will be set when aTo >= mStateComputedTime, i.e.
+   * when we've producing the last block of data we need to produce.) Otherwise
+   * we can get into a situation where we've determined the stream should not
+   * block before mStateComputedTime, but the stream finishes before
+   * mStateComputedTime, violating the invariant that finished streams are blocked.
    */
-  virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo) = 0;
+  enum {
+    ALLOW_FINISH = 0x01
+  };
+  virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) = 0;
   void SetAutofinishImpl(bool aAutofinish) { mAutofinish = aAutofinish; }
 
   /**
    * Forward SetTrackEnabled() to the input MediaStream(s) and translate the ID
    */
   virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {};
 
   bool InCycle() const { return mInCycle; }
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -24,27 +24,27 @@ namespace mozilla {
  */
 class TrackUnionStream : public ProcessedMediaStream {
 public:
   TrackUnionStream(DOMMediaStream* aWrapper) :
     ProcessedMediaStream(aWrapper),
     mFilterCallback(nullptr),
     mMaxTrackID(0) {}
 
-  virtual void RemoveInput(MediaInputPort* aPort)
+  virtual void RemoveInput(MediaInputPort* aPort) MOZ_OVERRIDE
   {
     for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
       if (mTrackMap[i].mInputPort == aPort) {
         EndTrack(i);
         mTrackMap.RemoveElementAt(i);
       }
     }
     ProcessedMediaStream::RemoveInput(aPort);
   }
-  virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo)
+  virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE
   {
     nsAutoTArray<bool,8> mappedTracksFinished;
     nsAutoTArray<bool,8> mappedTracksWithMatchingInputTracks;
     for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
       mappedTracksFinished.AppendElement(true);
       mappedTracksWithMatchingInputTracks.AppendElement(false);
     }
     bool allFinished = true;
@@ -90,17 +90,17 @@ public:
         EndTrack(i);
       } else {
         allFinished = false;
       }
       if (!mappedTracksWithMatchingInputTracks[i]) {
         mTrackMap.RemoveElementAt(i);
       }
     }
-    if (allFinished && mAutofinish) {
+    if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) {
       // All streams have finished and won't add any more tracks, and
       // all our tracks have actually finished and been removed from our map,
       // so we're finished now.
       FinishOnGraphThread();
     }
     mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo));
     if (allHaveCurrentData) {
       // We can make progress if we're not blocked
@@ -112,17 +112,17 @@ public:
   // Returns true to allow the track to act as an input; false to reject it entirely.
   typedef bool (*TrackIDFilterCallback)(StreamBuffer::Track*);
   void SetTrackIDFilter(TrackIDFilterCallback aCallback) {
     mFilterCallback = aCallback;
   }
 
   // Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream,
   // translating the output track ID into the correct ID in the source.
-  virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {
+  virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) MOZ_OVERRIDE {
     for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
       if (mTrackMap[i].mOutputTrackID == aOutputID) {
         mTrackMap[i].mInputPort->GetSource()->
           SetTrackEnabled(mTrackMap[i].mInputTrackID, aEnabled);
       }
     }
   }