Backed out changeset 528a97f6bc3b a=backout
authorChris Pearce <chris@pearce.org.nz>
Mon, 06 Sep 2010 16:52:22 +1200
changeset 52062 0d386e58fbdfdd99c930c1711e1309f6246309b8
parent 52050 527a97f6bc3b15279125f9b90cd47404241f292a
child 52063 5cdd188795553e38987b8cac6e8d248c342f3bed
push idunknown
push userunknown
push dateunknown
reviewersbackout
milestone2.0b6pre
Backed out changeset 528a97f6bc3b a=backout
content/media/nsBuiltinDecoderReader.h
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/nsBuiltinDecoderStateMachine.h
content/media/nsMediaDecoder.cpp
--- a/content/media/nsBuiltinDecoderReader.h
+++ b/content/media/nsBuiltinDecoderReader.h
@@ -325,25 +325,16 @@ template <class T> class MediaQueue : pr
     mEndOfStream = PR_FALSE;
   }
 
   PRBool AtEndOfStream() {
     MonitorAutoEnter mon(mMonitor);
     return GetSize() == 0 && mEndOfStream;    
   }
 
-  // Returns PR_TRUE if the media queue has had its last sample added to it.
-  // This happens when the media stream has been completely decoded. Note this
-  // does not mean that the corresponding stream has finished playback.
-  PRBool IsFinished() {
-    MonitorAutoEnter mon(mMonitor);
-    return mEndOfStream;    
-  }
-
-  // Informs the media queue that it won't be receiving any more samples.
   void Finish() {
     MonitorAutoEnter mon(mMonitor);
     mEndOfStream = PR_TRUE;    
   }
 
   // Returns the approximate number of milliseconds of samples in the queue.
   PRInt64 Duration() {
     MonitorAutoEnter mon(mMonitor);
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -94,21 +94,16 @@ const PRUint32 SILENCE_BYTES_CHUNK = 32 
 // which is at or after the current playback position.
 //
 // Also if the decode catches up with the end of the downloaded data,
 // we'll only go into BUFFERING state if we've got audio and have queued
 // less than LOW_AUDIO_MS of audio, or if we've got video and have queued
 // less than LOW_VIDEO_FRAMES frames.
 static const PRUint32 LOW_VIDEO_FRAMES = 1;
 
-// If we've got more than AMPLE_VIDEO_FRAMES decoded video frames waiting in
-// the video queue, we will not decode any more video frames until some have
-// been consumed by the play state machine thread.
-static const PRUint32 AMPLE_VIDEO_FRAMES = 10;
-
 // Arbitrary "frame duration" when playing only audio.
 static const int AUDIO_DURATION_MS = 40;
 
 class nsAudioMetadataEventRunner : public nsRunnable
 {
 private:
   nsCOMPtr<nsBuiltinDecoder> mDecoder;
 public:
@@ -162,30 +157,29 @@ nsBuiltinDecoderStateMachine::nsBuiltinD
 
 nsBuiltinDecoderStateMachine::~nsBuiltinDecoderStateMachine()
 {
   MOZ_COUNT_DTOR(nsBuiltinDecoderStateMachine);
 }
 
 PRBool nsBuiltinDecoderStateMachine::HasFutureAudio() const {
   mDecoder->GetMonitor().AssertCurrentThreadIn();
-  NS_ASSERTION(HasAudio(), "Should only call HasFutureAudio() when we have audio");
-  // We've got audio ready to play if:
-  // 1. We've not completed playback of audio, and
-  // 2. we either have more than the threshold of decoded audio available, or
-  //    we've completely decoded all audio (but not finished playing it yet
-  //    as per 1).
-  return !mAudioCompleted &&
-         (AudioDecodedMs() > LOW_AUDIO_MS || mReader->mAudioQueue.IsFinished());
+  PRBool aboveLowAudioThreshold = PR_FALSE;
+  if (mAudioEndTime != -1) {
+    aboveLowAudioThreshold = mAudioEndTime - GetMediaTime() > LOW_AUDIO_MS;
+  }
+  return HasAudio() &&
+    !mAudioCompleted &&
+    (mReader->mAudioQueue.GetSize() > 0 || aboveLowAudioThreshold);
 }
 
 PRBool nsBuiltinDecoderStateMachine::HaveNextFrameData() const {
-  mDecoder->GetMonitor().AssertCurrentThreadIn();
-  return (!HasAudio() || HasFutureAudio()) &&
-         (!HasVideo() || mReader->mVideoQueue.GetSize() > 0);
+    return ((!HasAudio() || mReader->mAudioQueue.AtEndOfStream()) && 
+             mReader->mVideoQueue.GetSize() > 0) ||
+            HasFutureAudio();
 }
 
 void nsBuiltinDecoderStateMachine::DecodeLoop()
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   PRBool videoPlaying = PR_FALSE;
   PRBool audioPlaying = PR_FALSE;
   {
@@ -202,50 +196,60 @@ void nsBuiltinDecoderStateMachine::Decod
   // If the video decode is falling behind the audio, we'll start dropping the
   // inter-frames up until the next keyframe which is at or before the current
   // playback position. skipToNextKeyframe is PR_TRUE if we're currently
   // skipping up to the next keyframe.
   PRBool skipToNextKeyframe = PR_FALSE;
 
   // Once we've decoded more than videoPumpThreshold video frames, we'll
   // no longer be considered to be "pumping video".
-  const unsigned videoPumpThreshold = AMPLE_VIDEO_FRAMES / 2;
+  const unsigned videoPumpThreshold = 5;
+
+  // If we've got more than videoWaitThreshold decoded video frames waiting in
+  // the video queue, we will not decode any more video frames until they've
+  // been consumed by the play state machine thread.
+  const unsigned videoWaitThreshold = 10;
 
   // After the audio decode fills with more than audioPumpThresholdMs ms
   // of decoded audio, we'll start to check whether the audio or video decode
   // is falling behind.
   const unsigned audioPumpThresholdMs = LOW_AUDIO_MS * 2;
 
   // Main decode loop.
   while (videoPlaying || audioPlaying) {
     PRBool audioWait = !audioPlaying;
     PRBool videoWait = !videoPlaying;
     {
       // Wait for more data to download if we've exhausted all our
       // buffered data.
       MonitorAutoEnter mon(mDecoder->GetMonitor());
+      while (!mStopDecodeThreads &&
+             mBufferExhausted &&
+             mState != DECODER_STATE_SHUTDOWN)
+      {
+        mon.Wait();
+      }
       if (mState == DECODER_STATE_SHUTDOWN || mStopDecodeThreads)
         break;
     }
 
     PRUint32 videoQueueSize = mReader->mVideoQueue.GetSize();
     // Don't decode any more frames if we've filled our buffers.
     // Limits memory consumption.
-    if (videoQueueSize > AMPLE_VIDEO_FRAMES) {
+    if (videoQueueSize > videoWaitThreshold) {
       videoWait = PR_TRUE;
     }
 
     // We don't want to consider skipping to the next keyframe if we've
     // only just started up the decode loop, so wait until we've decoded
     // some frames before allowing the keyframe skip.
     if (videoPump && videoQueueSize >= videoPumpThreshold) {
       videoPump = PR_FALSE;
     }
-    if (audioPlaying &&
-        !videoPump &&
+    if (!videoPump &&
         videoPlaying &&
         videoQueueSize < LOW_VIDEO_FRAMES)
     {
       skipToNextKeyframe = PR_TRUE;
     }
 
     // Determine how much audio data is decoded ahead of the current playback
     // position.
@@ -274,31 +278,35 @@ void nsBuiltinDecoderStateMachine::Decod
     if (!audioPump && audioPlaying && audioDecoded < LOW_AUDIO_MS) {
       skipToNextKeyframe = PR_TRUE;
     }
 
     if (videoPlaying && !videoWait) {
       videoPlaying = mReader->DecodeVideoFrame(skipToNextKeyframe, currentTime);
       {
         MonitorAutoEnter mon(mDecoder->GetMonitor());
-        mBufferExhausted = mDecoder->mDecoderPosition > initialDownloadPosition;
+        if (mDecoder->mDecoderPosition > initialDownloadPosition) {
+          mBufferExhausted = PR_TRUE;
+        }
       }
     }
     {
       MonitorAutoEnter mon(mDecoder->GetMonitor());
       initialDownloadPosition =
         mDecoder->GetCurrentStream()->GetCachedDataEnd(mDecoder->mDecoderPosition);
       mDecoder->GetMonitor().NotifyAll();
     }
 
     if (audioPlaying && !audioWait) {
       audioPlaying = mReader->DecodeAudioData();
       {
         MonitorAutoEnter mon(mDecoder->GetMonitor());
-        mBufferExhausted = mDecoder->mDecoderPosition > initialDownloadPosition;
+        if (mDecoder->mDecoderPosition > initialDownloadPosition) {
+          mBufferExhausted = PR_TRUE;
+        }
       }
     }
 
     {
       MonitorAutoEnter mon(mDecoder->GetMonitor());
 
       if (!IsPlaying() &&
           (!audioWait || !videoWait) &&
@@ -312,25 +320,21 @@ void nsBuiltinDecoderStateMachine::Decod
         // not playing; if we're playing the playback code will post an update
         // whenever it advances a frame.
         UpdateReadyState();
       }
 
       if (mState == DECODER_STATE_SHUTDOWN || mStopDecodeThreads) {
         break;
       }
-
       if ((!HasAudio() || (audioWait && audioPlaying)) &&
           (!HasVideo() || (videoWait && videoPlaying)))
       {
         // All active bitstreams' decode is well ahead of the playback
-        // position, we may as well wait for the playback to catch up.
-        // Set mBufferExhausted to PR_FALSE, as we'll receive more data
-        // while we wait.
-        mBufferExhausted = PR_FALSE;
+        // position, we may as well wait have for the playback to catch up.
         mon.Wait();
       }
     }
   }
 
   {
     MonitorAutoEnter mon(mDecoder->GetMonitor());
     if (!mStopDecodeThreads &&
@@ -839,49 +843,16 @@ nsBuiltinDecoderStateMachine::StartDecod
     }
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(this, &nsBuiltinDecoderStateMachine::AudioLoop);
     mAudioThread->Dispatch(event, NS_DISPATCH_NORMAL);
   }
   return NS_OK;
 }
 
-PRInt64 nsBuiltinDecoderStateMachine::AudioDecodedMs() const
-{
-  NS_ASSERTION(HasAudio(),
-               "Should only call AudioDecodedMs() when we have audio");
-  // The amount of audio we have decoded is the amount of audio data we've
-  // already decoded and pushed to the hardware, plus the amount of audio
-  // data waiting to be pushed to the hardware.
-  PRInt64 pushed = (mAudioEndTime != -1) ? (mAudioEndTime - GetMediaTime()) : 0;
-  return pushed + mReader->mAudioQueue.Duration();
-}
-
-PRBool nsBuiltinDecoderStateMachine::HasLowDecodedData() const
-{
-  // We consider ourselves low on decoded data if we're low on audio, or
-  // if we're only playing video and we're low on video frames.
-  return (HasAudio() && AudioDecodedMs() < LOW_AUDIO_MS)
-         ||
-         (!HasAudio() &&
-          HasVideo() &&
-          (PRUint32)mReader->mVideoQueue.GetSize() < LOW_VIDEO_FRAMES);
-}
-
-PRBool nsBuiltinDecoderStateMachine::HasAmpleDecodedData() const
-{
-  return (!HasAudio() ||
-          AudioDecodedMs() >= AMPLE_AUDIO_MS ||
-          mReader->mAudioQueue.IsFinished())
-         &&
-         (!HasVideo() ||
-          (PRUint32)mReader->mVideoQueue.GetSize() > AMPLE_VIDEO_FRAMES ||
-          mReader->mVideoQueue.AtEndOfStream());
-}
-
 nsresult nsBuiltinDecoderStateMachine::Run()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   nsMediaStream* stream = mDecoder->GetCurrentStream();
   NS_ENSURE_TRUE(stream, NS_ERROR_NULL_POINTER);
 
   while (PR_TRUE) {
@@ -962,26 +933,35 @@ nsresult nsBuiltinDecoderStateMachine::R
         }
 
         AdvanceFrame();
 
         if (mState != DECODER_STATE_DECODING)
           continue;
 
         if (mBufferExhausted &&
-            HasLowDecodedData() &&
             mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING &&
-            !stream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
-            !stream->IsSuspended())
+            !mDecoder->GetCurrentStream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
+            !mDecoder->GetCurrentStream()->IsSuspendedByCache() &&
+            ((HasAudio() && mReader->mAudioQueue.Duration() < LOW_AUDIO_MS) ||
+             (HasVideo() && (PRUint32)mReader->mVideoQueue.GetSize() < LOW_VIDEO_FRAMES)))
         {
-          // We're low on decoded data, and/or our decode has caught up with
-          // the download. Let's buffer to make sure we can play a decent
-          // amount of video in the future.
+          // There is at most one frame in the queue and there's
+          // more data to load. Let's buffer to make sure we can play a
+          // decent amount of video in the future.
           StartBuffering();
+        } else {
+          if (mBufferExhausted) {
+            // This will wake up the decode thread and force it to try to
+            // decode video and audio. This guarantees we make progress.
+            mBufferExhausted = PR_FALSE;
+            mDecoder->GetMonitor().NotifyAll();
+          }
         }
+
       }
       break;
 
     case DECODER_STATE_SEEKING:
       {
         // During the seek, don't have a lock on the decoder state,
         // otherwise long seek operations can block the main thread.
         // The events dispatched to the main thread are SYNC calls.
@@ -1074,51 +1054,50 @@ nsresult nsBuiltinDecoderStateMachine::R
           MonitorAutoExit exitMon(mDecoder->GetMonitor());
           NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
         }
       }
       break;
 
     case DECODER_STATE_BUFFERING:
       {
-        // We will remain in the buffering state if we've not decoded enough
-        // data to begin playback, or if we've not downloaded a reasonable
-        // amount of data inside our buffering time.
-        TimeDuration elapsed = TimeStamp::Now() - mBufferingStart;
-        if ((!mDecoder->CanPlayThrough() || !HasAmpleDecodedData()) &&
-             elapsed < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
-             stream->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
-             !stream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
-             !stream->IsSuspended())
-        {
+        TimeStamp now = TimeStamp::Now();
+        nsMediaStream* stream = mDecoder->GetCurrentStream();
+        if (!mDecoder->CanPlayThrough() &&
+            now - mBufferingStart < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
+            stream->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
+            !stream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
+            !stream->IsSuspendedByCache()) {
           LOG(PR_LOG_DEBUG,
-              ("In buffering: buffering data until %u bytes available or %f seconds",
-               PRUint32(mBufferingEndOffset - stream->GetCachedDataEnd(mDecoder->mDecoderPosition)),
-               BUFFERING_WAIT - elapsed.ToSeconds()));
+              ("In buffering: buffering data until %d bytes available or %f seconds",
+               PRUint32(mBufferingEndOffset - mDecoder->GetCurrentStream()->GetCachedDataEnd(mDecoder->mDecoderPosition)),
+               BUFFERING_WAIT - (now - mBufferingStart).ToSeconds()));
           Wait(1000);
           if (mState == DECODER_STATE_SHUTDOWN)
             continue;
         } else {
           LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
           LOG(PR_LOG_DEBUG, ("%p Buffered for %lf seconds",
                              mDecoder,
                              (TimeStamp::Now() - mBufferingStart).ToSeconds()));
           mState = DECODER_STATE_DECODING;
         }
 
         if (mState != DECODER_STATE_BUFFERING) {
+          mBufferExhausted = PR_FALSE;
           // Notify to allow blocked decoder thread to continue
           mDecoder->GetMonitor().NotifyAll();
           UpdateReadyState();
           if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
             if (!IsPlaying()) {
               StartPlayback();
             }
           }
         }
+
         break;
       }
 
     case DECODER_STATE_COMPLETED:
       {
         if (NS_FAILED(StartDecodeThreads())) {
           continue;
         }
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -237,27 +237,16 @@ public:
 
   nsresult GetBuffered(nsTimeRanges* aBuffered) {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     return mReader->GetBuffered(aBuffered, mStartTime);
   }
 
 protected:
 
-  // Returns the number of unplayed ms of audio we've got decoded and/or
-  // pushed to the hardware waiting to play. This is how much audio we can
-  // play without having to run the audio decoder.
-  PRInt64 AudioDecodedMs() const;
-
-  // Returns PR_TRUE if we're running low on decoded data.
-  PRBool HasLowDecodedData() const;
-
-  // Returns PR_TRUE if we've got plenty of decoded data.
-  PRBool HasAmpleDecodedData() const;
-
   // Returns PR_TRUE when there's decoded audio waiting to play.
   // The decoder monitor must be held.
   PRBool HasFutureAudio() const;
 
   // Waits on the decoder Monitor for aMs. If the decoder monitor is awoken
   // by a Notify() call, we'll continue waiting, unless we've moved into
   // shutdown state. This enables us to ensure that we wait for a specified
   // time, and that the myriad of Notify()s we do an the decoder monitor
--- a/content/media/nsMediaDecoder.cpp
+++ b/content/media/nsMediaDecoder.cpp
@@ -63,24 +63,16 @@
 #endif
 
 // Number of milliseconds between progress events as defined by spec
 #define PROGRESS_MS 350
 
 // Number of milliseconds of no data before a stall event is fired as defined by spec
 #define STALL_MS 3000
 
-// Number of estimated seconds worth of data we need to have buffered 
-// ahead of the current playback position before we allow the media decoder
-// to report that it can play through the entire media without the decode
-// catching up with the download. Having this margin make the
-// nsMediaDecoder::CanPlayThrough() calculation more stable in the case of
-// fluctuating bitrates.
-#define CAN_PLAY_THROUGH_MARGIN 20
-
 nsMediaDecoder::nsMediaDecoder() :
   mElement(0),
   mRGBWidth(-1),
   mRGBHeight(-1),
   mProgressTime(),
   mDataTime(),
   mVideoUpdateLock(nsnull),
   mPixelAspectRatio(1.0),
@@ -292,26 +284,10 @@ PRBool nsMediaDecoder::CanPlayThrough()
   if (!stats.mDownloadRateReliable || !stats.mPlaybackRateReliable) {
     return PR_FALSE;
   }
   PRInt64 bytesToDownload = stats.mTotalBytes - stats.mDownloadPosition;
   PRInt64 bytesToPlayback = stats.mTotalBytes - stats.mPlaybackPosition;
   double timeToDownload =
     (bytesToDownload + gDownloadSizeSafetyMargin)/stats.mDownloadRate;
   double timeToPlay = bytesToPlayback/stats.mPlaybackRate;
-
-  if (timeToDownload > timeToPlay) {
-    // Estimated time to download is greater than the estimated time to play.
-    // We probably can't play through without having to stop to buffer.
-    return PR_FALSE;
-  }
-
-  // Estimated time to download is less than the estimated time to play.
-  // We can probably play through without having to buffer, but ensure that
-  // we've got a reasonable amount of data buffered after the current
-  // playback position, so that if the bitrate of the media fluctuates, or if
-  // our download rate or decode rate estimation is otherwise inaccurate,
-  // we don't suddenly discover that we need to buffer. This is particularly
-  // required near the start of the media, when not much data is downloaded.
-  PRInt64 readAheadMargin = stats.mPlaybackRate * CAN_PLAY_THROUGH_MARGIN;
-  return stats.mTotalBytes == stats.mDownloadPosition ||
-         stats.mDownloadPosition > stats.mPlaybackPosition + readAheadMargin;
+  return timeToDownload <= timeToPlay;
 }