Bug 973408 - Split DecodeThreadRun() into separate decode tasks. r=kinetik
authorChris Pearce <cpearce@mozilla.com>
Tue, 11 Mar 2014 11:44:09 +0800
changeset 190103 5e78ec92e1c1c458ca6c011536a9bf9720a0e634
parent 190102 d9dfc05b1be905789819b9ef1302a391a9d504f2
child 190104 dcaba0ea43525b6e617060c0e0f7ae1f92eb553d
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs973408
milestone30.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 973408 - Split DecodeThreadRun() into separate decode tasks. r=kinetik
content/media/MediaDecoderStateMachine.cpp
content/media/MediaDecoderStateMachine.h
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -265,67 +265,16 @@ int64_t MediaDecoderStateMachine::GetDec
   AssertCurrentThreadInMonitor();
   int64_t audioDecoded = mReader->AudioQueue().Duration();
   if (mAudioEndTime != -1) {
     audioDecoded += mAudioEndTime - GetMediaTime();
   }
   return audioDecoded;
 }
 
-void MediaDecoderStateMachine::DecodeThreadRun()
-{
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-
-  if (mReader) {
-    mReader->OnDecodeThreadStart();
-  }
-
-  if (mState == DECODER_STATE_DECODING_METADATA &&
-      NS_FAILED(DecodeMetadata())) {
-    NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
-                  "Should be in shutdown state if metadata loading fails.");
-    DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
-  }
-
-  while (mState != DECODER_STATE_SHUTDOWN &&
-         mState != DECODER_STATE_COMPLETED &&
-         mState != DECODER_STATE_DORMANT &&
-         !mStopDecodeThread)
-  {
-    if (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) {
-      DecodeLoop();
-    } else if (mState == DECODER_STATE_SEEKING) {
-      DecodeSeek();
-    } else if (mState == DECODER_STATE_DECODING_METADATA) {
-      if (NS_FAILED(DecodeMetadata())) {
-        NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
-                      "Should be in shutdown state if metadata loading fails.");
-        DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
-      }
-    } else if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) {
-      mDecoder->GetReentrantMonitor().Wait();
-
-      if (!mReader->IsWaitingMediaResources()) {
-        // change state to DECODER_STATE_WAIT_FOR_RESOURCES
-        StartDecodeMetadata();
-      }
-    } else if (mState == DECODER_STATE_DORMANT) {
-      mDecoder->GetReentrantMonitor().Wait();
-    }
-  }
-
-  if (mReader) {
-    mReader->OnDecodeThreadFinish();
-  }
-
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get()));
-  mDispatchedEventToDecode = false;
-  mon.NotifyAll();
-}
-
 void MediaDecoderStateMachine::SendStreamAudio(AudioData* aAudio,
                                                DecodedStreamData* aStream,
                                                AudioSegment* aOutput)
 {
   NS_ASSERTION(OnDecodeThread() ||
                OnStateMachineThread(), "Should be on decode thread or state machine thread");
   AssertCurrentThreadInMonitor();
 
@@ -591,23 +540,28 @@ bool MediaDecoderStateMachine::HaveEnoug
         GetStateMachineThread(), GetWakeDecoderRunnable());
   }
 
   return true;
 }
 
 void MediaDecoderStateMachine::DecodeLoop()
 {
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Start DecodeLoop()", mDecoder.get()));
-
-  AssertCurrentThreadInMonitor();
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
 
-  mIsAudioPrerolling = true;
-  mIsVideoPrerolling = true;
+  if ((mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING) ||
+      mStopDecodeThread) {
+    return;
+  }
+
+  // At least one of our streams should still need decoding.
+  MOZ_ASSERT(mIsVideoDecoding || mIsAudioDecoding);
+
+  DECODER_LOG(PR_LOG_DEBUG, ("%p Start DecodeLoop()", mDecoder.get()));
 
   // Main decode loop.
   while ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) &&
          !mStopDecodeThread &&
          (mIsVideoDecoding || mIsAudioDecoding))
   {
     // 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
@@ -741,16 +695,18 @@ void MediaDecoderStateMachine::DecodeLoo
       mState != DECODER_STATE_SHUTDOWN &&
       mState != DECODER_STATE_DORMANT &&
       mState != DECODER_STATE_SEEKING)
   {
     mState = DECODER_STATE_COMPLETED;
     ScheduleStateMachine();
   }
 
+  mDispatchedEventToDecode = false;
+  mon.NotifyAll();
   DECODER_LOG(PR_LOG_DEBUG, ("%p Exiting DecodeLoop", mDecoder.get()));
 }
 
 bool MediaDecoderStateMachine::IsPlaying()
 {
   AssertCurrentThreadInMonitor();
 
   return !mPlayStartTime.IsNull();
@@ -1340,47 +1296,44 @@ void MediaDecoderStateMachine::Shutdown(
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void MediaDecoderStateMachine::StartDecoding()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  if (mState != DECODER_STATE_DECODING) {
-    mDecodeStartTime = TimeStamp::Now();
+  if (mState == DECODER_STATE_DECODING) {
+    return;
   }
   mState = DECODER_STATE_DECODING;
 
+  mDecodeStartTime = TimeStamp::Now();
+
   // Reset our "stream finished decoding" flags, so we try to decode all
   // streams that we have when we start decoding.
   mIsVideoDecoding = HasVideo();
   mIsAudioDecoding = HasAudio();
 
+  // Reset other state to pristine values before starting decode.
   mSkipToNextKeyFrame = false;
+  mIsAudioPrerolling = true;
+  mIsVideoPrerolling = true;
 
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::StartWaitForResources()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   mState = DECODER_STATE_WAIT_FOR_RESOURCES;
 }
 
-void MediaDecoderStateMachine::StartDecodeMetadata()
-{
-  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
-               "Should be on state machine or decode thread.");
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  mState = DECODER_STATE_DECODING_METADATA;
-}
-
 void MediaDecoderStateMachine::Play()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   // When asked to play, switch to decoding state only if
   // we are currently buffering. In other cases, we'll start playing anyway
   // when the state machine notices the decoder's state change to PLAYING.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mState == DECODER_STATE_BUFFERING) {
@@ -1497,28 +1450,63 @@ void MediaDecoderStateMachine::StopAudio
     mAudioThread = nullptr;
     // Now that the audio thread is dead, try sending data to our MediaStream(s).
     // That may have been waiting for the audio thread to stop.
     SendStreamData();
   }
 }
 
 nsresult
-MediaDecoderStateMachine::ScheduleDecodeThread()
+MediaDecoderStateMachine::EnqueueDecodeMetadataTask()
+{
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+  AssertCurrentThreadInMonitor();
+
+  if (mState != DECODER_STATE_DECODING_METADATA) {
+    return NS_OK;
+  }
+  nsresult rv = mDecodeTaskQueue->Dispatch(
+    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeMetadata));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+MediaDecoderStateMachine::EnqueueDecodeSeekTask()
+{
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+  AssertCurrentThreadInMonitor();
+
+  if (mState != DECODER_STATE_SEEKING) {
+    return NS_OK;
+  }
+  nsresult rv = mDecodeTaskQueue->Dispatch(
+    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeSeek));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+
+nsresult
+MediaDecoderStateMachine::EnqueueDecodeTask()
 {
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
   AssertCurrentThreadInMonitor();
 
   mStopDecodeThread = false;
   if (mState >= DECODER_STATE_COMPLETED) {
     return NS_OK;
   }
   if (!mDispatchedEventToDecode) {
     nsresult rv = mDecodeTaskQueue->Dispatch(
-      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeThreadRun));
+      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeLoop));
     NS_ENSURE_SUCCESS(rv, rv);
     mDispatchedEventToDecode = true;
   }
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::StartAudioThread()
@@ -1609,53 +1597,71 @@ bool MediaDecoderStateMachine::HasLowUnd
 void MediaDecoderStateMachine::SetFrameBufferLength(uint32_t aLength)
 {
   NS_ASSERTION(aLength >= 512 && aLength <= 16384,
                "The length must be between 512 and 16384");
   AssertCurrentThreadInMonitor();
   mEventManager.SetSignalBufferLength(aLength);
 }
 
+void
+MediaDecoderStateMachine::CallDecodeMetadata()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+
+  if (mState != DECODER_STATE_DECODING_METADATA) {
+    return;
+  }
+
+  if (NS_FAILED(DecodeMetadata())) {
+    NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
+                  "Should be in shutdown state if metadata loading fails.");
+    DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decoder"));
+
+    // Dispatch the event to call DecodeError synchronously. This ensures
+    // we're in shutdown state by the time we exit the decode thread.
+    // If we just moved to shutdown state here on the decode thread, we may
+    // cause the state machine to shutdown/free memory without closing its
+    // media stream properly, and we'll get callbacks from the media stream
+    // causing a crash.
+
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
+    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+    NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
+  }
+}
+
 nsresult MediaDecoderStateMachine::DecodeMetadata()
 {
+  AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  AssertCurrentThreadInMonitor();
-  NS_ASSERTION(mState == DECODER_STATE_DECODING_METADATA,
-               "Only call when in metadata decoding state");
+  DECODER_LOG(PR_LOG_DEBUG, ("%p Decoding Media Headers", mDecoder.get()));
+  if (mState != DECODER_STATE_DECODING_METADATA) {
+    return NS_ERROR_FAILURE;
+  }
 
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Decoding Media Headers", mDecoder.get()));
   nsresult res;
   MediaInfo info;
   MetadataTags* tags;
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     res = mReader->ReadMetadata(&info, &tags);
   }
-  if (NS_SUCCEEDED(res) && (mState == DECODER_STATE_DECODING_METADATA) && (mReader->IsWaitingMediaResources())) {
+  if (NS_SUCCEEDED(res) &&
+      mState == DECODER_STATE_DECODING_METADATA &&
+      mReader->IsWaitingMediaResources()) {
     // change state to DECODER_STATE_WAIT_FOR_RESOURCES
     StartWaitForResources();
     return NS_OK;
   }
 
   mInfo = info;
 
   if (NS_FAILED(res) || (!info.HasValidMedia())) {
-    // Dispatch the event to call DecodeError synchronously. This ensures
-    // we're in shutdown state by the time we exit the decode thread.
-    // If we just moved to shutdown state here on the decode thread, we may
-    // cause the state machine to shutdown/free memory without closing its
-    // media stream properly, and we'll get callbacks from the media stream
-    // causing a crash. Note the state machine shutdown joins this decode
-    // thread during shutdown (and other state machines can run on the state
-    // machine thread while the join is waiting), so it's safe to do this
-    // synchronously.
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
     return NS_ERROR_FAILURE;
   }
   mDecoder->StartProgressUpdates();
   mGotDurationFromMetaData = (GetDuration() != -1);
 
   VideoData* videoData = FindStartTime();
   if (videoData) {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@@ -1711,20 +1717,21 @@ nsresult MediaDecoderStateMachine::Decod
     StartPlayback();
   }
 
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::DecodeSeek()
 {
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  AssertCurrentThreadInMonitor();
-  NS_ASSERTION(mState == DECODER_STATE_SEEKING,
-               "Only call when in seeking state");
+  if (mState != DECODER_STATE_SEEKING) {
+    return;
+  }
 
   mDidThrottleAudioDecoding = false;
   mDidThrottleVideoDecoding = false;
 
   // 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.
   // These calls are made outside of the decode monitor lock so
@@ -1953,17 +1960,17 @@ nsresult MediaDecoderStateMachine::RunSt
     }
 
     case DECODER_STATE_WAIT_FOR_RESOURCES: {
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_METADATA: {
       // Ensure we have a decode thread to decode metadata.
-      return ScheduleDecodeThread();
+      return EnqueueDecodeMetadataTask();
     }
 
     case DECODER_STATE_DECODING: {
       if (mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING &&
           IsPlaying())
       {
         // We're playing, but the element/decoder is in paused state. Stop
         // playing! Note we do this before StopDecodeThread() below because
@@ -1984,17 +1991,17 @@ nsresult MediaDecoderStateMachine::RunSt
         // The decode buffers are full, and playback is paused. Shutdown the
         // decode thread.
         StopDecodeThread();
         return NS_OK;
       }
 
       // We're playing and/or our decode buffers aren't full. Ensure we have
       // an active decode thread.
-      if (NS_FAILED(ScheduleDecodeThread())) {
+      if (NS_FAILED(EnqueueDecodeTask())) {
         NS_WARNING("Failed to start media decode thread!");
         return NS_ERROR_FAILURE;
       }
 
       AdvanceFrame();
       NS_ASSERTION(mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING ||
                    IsStateMachineScheduled() ||
                    mPlaybackRate == 0.0, "Must have timer scheduled");
@@ -2049,17 +2056,17 @@ nsresult MediaDecoderStateMachine::RunSt
         StartPlayback();
       }
       NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
       return NS_OK;
     }
 
     case DECODER_STATE_SEEKING: {
       // Ensure we have a decode thread to perform the seek.
-     return ScheduleDecodeThread();
+     return EnqueueDecodeSeekTask();
     }
 
     case DECODER_STATE_COMPLETED: {
       StopDecodeThread();
 
       if (mState != DECODER_STATE_COMPLETED) {
         // While we're waiting for the decode thread to shutdown, we can
         // change state, for example to seeking or shutdown state.
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -494,22 +494,16 @@ private:
   // decode thread it is canceled. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   void StopDecodeThread();
 
   // Stops the audio thread. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   void StopAudioThread();
 
-  // Ensures the decode thread is running if it already exists, or requests
-  // a new decode thread be started if there currently is no decode thread.
-  // The decoder monitor must be held with exactly one lock count. Called on
-  // the state machine thread.
-  nsresult ScheduleDecodeThread();
-
   // Starts the audio thread. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   nsresult StartAudioThread();
 
   // The main loop for the audio thread. Sent to the thread as
   // an nsRunnableMethod. This continually does blocking writes to
   // to audio stream to play audio data.
   void AudioLoop();
@@ -523,17 +517,30 @@ private:
   void StartPlayback();
 
   // Moves the decoder into decoding state. Called on the state machine
   // thread. The decoder monitor must be held.
   void StartDecoding();
 
   void StartWaitForResources();
 
-  void StartDecodeMetadata();
+  // Dispatches a task to the decode task queue to begin decoding metadata.
+  // This is called on the state machine or decode threads.
+  // The decoder monitor must be held.
+  nsresult EnqueueDecodeMetadataTask();
+
+  // Dispatches a task to the decode task queue to run the decode loop.
+  // This is called on the state machine or decode threads.
+  // The decoder monitor must be held.
+  nsresult EnqueueDecodeTask();
+
+  // Dispatches a task to the decode task queue to seek the decoder.
+  // This is called on the state machine or decode threads.
+  // The decoder monitor must be held.
+  nsresult EnqueueDecodeSeekTask();
 
   // Returns the "media time". This is the absolute time which the media
   // playback has reached. i.e. this returns values in the range
   // [mStartTime, mEndTime], and mStartTime will not be 0 if the media does
   // not start at 0. Note this is different to the value returned
   // by GetCurrentTime(), which is in the range [0,duration].
   int64_t GetMediaTime() const {
     AssertCurrentThreadInMonitor();
@@ -556,19 +563,17 @@ private:
   // Seeks to mSeekTarget. Called on the decode thread. The decoder monitor
   // must be held with exactly one lock count.
   void DecodeSeek();
 
   // Decode loop, decodes data until EOF or shutdown.
   // Called on the decode thread.
   void DecodeLoop();
 
-  // Decode thread run function. Determines which of the Decode*() functions
-  // to call.
-  void DecodeThreadRun();
+  void CallDecodeMetadata();
 
   // Copy audio from an AudioData packet to aOutput. This may require
   // inserting silence depending on the timing of the audio packet.
   void SendStreamAudio(AudioData* aAudio, DecodedStreamData* aStream,
                        AudioSegment* aOutput);
 
   // State machine thread run function. Defers to RunStateMachine().
   nsresult CallRunStateMachine();