Bug 1187763. Part 3 - refactor AudioSink::AudioLoop into a series of events. r=kinetik.
authorJW Wang <jwwang@mozilla.com>
Tue, 28 Jul 2015 11:52:05 +0800
changeset 254873 82c871e225abdfc1adf6e0b590048e886cf51d37
parent 254872 2360777e82ad624bd749c10af0e1b80af1ea014f
child 254874 c3212a7e91ce09a37a9df949aa5426360a159437
push id62879
push userjwwang@mozilla.com
push dateTue, 28 Jul 2015 04:04:28 +0000
treeherdermozilla-inbound@82c871e225ab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs1187763
milestone42.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 1187763. Part 3 - refactor AudioSink::AudioLoop into a series of events. r=kinetik.
dom/media/AudioSink.cpp
dom/media/AudioSink.h
--- a/dom/media/AudioSink.cpp
+++ b/dom/media/AudioSink.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioSink.h"
 #include "AudioStream.h"
 #include "MediaQueue.h"
 #include "VideoUtils.h"
 
 #include "mozilla/CheckedInt.h"
+#include "mozilla/DebugOnly.h"
 
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define SINK_LOG(msg, ...) \
   MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("AudioSink=%p " msg, this, ##__VA_ARGS__))
 #define SINK_LOG_V(msg, ...) \
   MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, ("AudioSink=%p " msg, this, ##__VA_ARGS__))
@@ -23,52 +24,90 @@ extern PRLogModuleInfo* gMediaDecoderLog
 static const int64_t AUDIO_FUZZ_FRAMES = 1;
 
 AudioSink::AudioSink(MediaQueue<AudioData>& aAudioQueue,
                      int64_t aStartTime,
                      const AudioInfo& aInfo,
                      dom::AudioChannel aChannel)
   : mAudioQueue(aAudioQueue)
   , mMonitor("AudioSink::mMonitor")
+  , mState(AUDIOSINK_STATE_INIT)
+  , mAudioLoopScheduled(false)
   , mStartTime(aStartTime)
   , mWritten(0)
   , mLastGoodPosition(0)
   , mInfo(aInfo)
   , mChannel(aChannel)
   , mVolume(1.0)
   , mPlaybackRate(1.0)
   , mPreservesPitch(false)
   , mStopAudioThread(false)
   , mSetVolume(false)
   , mSetPlaybackRate(false)
   , mSetPreservesPitch(false)
   , mPlaying(true)
 {
 }
 
+void
+AudioSink::SetState(State aState)
+{
+  AssertOnAudioThread();
+  mPendingState = Some(aState);
+}
+
+void
+AudioSink::DispatchTask(already_AddRefed<nsIRunnable>&& event)
+{
+  DebugOnly<nsresult> rv = mThread->Dispatch(Move(event), NS_DISPATCH_NORMAL);
+  // There isn't much we can do if Dispatch() fails.
+  // Just assert it to keep things simple.
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+void
+AudioSink::ScheduleNextLoop()
+{
+  AssertOnAudioThread();
+  if (mAudioLoopScheduled) {
+    return;
+  }
+  mAudioLoopScheduled = true;
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &AudioSink::AudioLoop);
+  DispatchTask(r.forget());
+}
+
+void
+AudioSink::ScheduleNextLoopCrossThread()
+{
+  AssertNotOnAudioThread();
+  nsRefPtr<AudioSink> self = this;
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () {
+    // Do nothing if there is already a pending task waiting for its turn.
+    if (!self->mAudioLoopScheduled) {
+      self->AudioLoop();
+    }
+  });
+  DispatchTask(r.forget());
+}
+
 nsRefPtr<GenericPromise>
 AudioSink::Init()
 {
   nsRefPtr<GenericPromise> p = mEndPromise.Ensure(__func__);
   nsresult rv = NS_NewNamedThread("Media Audio",
                                   getter_AddRefs(mThread),
                                   nullptr,
                                   MEDIA_THREAD_STACK_SIZE);
   if (NS_FAILED(rv)) {
     mEndPromise.Reject(rv, __func__);
     return p;
   }
 
-  nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &AudioSink::AudioLoop);
-  rv =  mThread->Dispatch(event, NS_DISPATCH_NORMAL);
-  if (NS_FAILED(rv)) {
-    mEndPromise.Reject(rv, __func__);
-    return p;
-  }
-
+  ScheduleNextLoopCrossThread();
   return p;
 }
 
 int64_t
 AudioSink::GetPosition()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
 
@@ -94,26 +133,32 @@ AudioSink::HasUnplayedFrames()
 void
 AudioSink::Shutdown()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mStopAudioThread = true;
   if (mAudioStream) {
     mAudioStream->Cancel();
   }
-  GetReentrantMonitor().NotifyAll();
+  ScheduleNextLoopCrossThread();
 
   // Exit the monitor so audio loop can enter the monitor and finish its job.
   ReentrantMonitorAutoExit exit(GetReentrantMonitor());
   mThread->Shutdown();
   mThread = nullptr;
   if (mAudioStream) {
     mAudioStream->Shutdown();
     mAudioStream = nullptr;
   }
+
+  // Should've reached the final state after shutdown.
+  MOZ_ASSERT(mState == AUDIOSINK_STATE_SHUTDOWN ||
+             mState == AUDIOSINK_STATE_ERROR);
+  // Should have no pending state change.
+  MOZ_ASSERT(mPendingState.isNothing());
 }
 
 void
 AudioSink::SetVolume(double aVolume)
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mVolume = aVolume;
   mSetVolume = true;
@@ -136,54 +181,23 @@ AudioSink::SetPreservesPitch(bool aPrese
   mSetPreservesPitch = true;
 }
 
 void
 AudioSink::SetPlaying(bool aPlaying)
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mPlaying = aPlaying;
-  GetReentrantMonitor().NotifyAll();
+  ScheduleNextLoopCrossThread();
 }
 
 void
 AudioSink::NotifyData()
 {
-  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  GetReentrantMonitor().NotifyAll();
-}
-
-void
-AudioSink::AudioLoop()
-{
-  AssertOnAudioThread();
-  SINK_LOG("AudioLoop started");
-
-  nsresult rv = InitializeAudioStream();
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Initializing AudioStream failed.");
-    mEndPromise.Reject(rv, __func__);
-    return;
-  }
-
-  while (1) {
-    {
-      ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-      while (WaitingForAudioToPlay()) {
-        GetReentrantMonitor().Wait();
-      }
-      if (!IsPlaybackContinuing()) {
-        break;
-      }
-    }
-    if (!PlayAudio()) {
-      break;
-    }
-  }
-  FinishAudioLoop();
+  ScheduleNextLoopCrossThread();
 }
 
 nsresult
 AudioSink::InitializeAudioStream()
 {
   // AudioStream initialization can block for extended periods in unusual
   // circumstances, so we take care to drop the decoder monitor while
   // initializing.
@@ -261,16 +275,82 @@ AudioSink::IsPlaybackContinuing()
     return false;
   }
 
   UpdateStreamSettings();
 
   return true;
 }
 
+void
+AudioSink::AudioLoop()
+{
+  AssertOnAudioThread();
+  mAudioLoopScheduled = false;
+
+  switch (mState) {
+    case AUDIOSINK_STATE_INIT: {
+      SINK_LOG("AudioLoop started");
+      nsresult rv = InitializeAudioStream();
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Initializing AudioStream failed.");
+        mEndPromise.Reject(rv, __func__);
+        SetState(AUDIOSINK_STATE_ERROR);
+        break;
+      }
+      SetState(AUDIOSINK_STATE_PLAYING);
+      break;
+    }
+
+    case AUDIOSINK_STATE_PLAYING: {
+      {
+        ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+        if (WaitingForAudioToPlay()) {
+          // NotifyData() will schedule next loop.
+          break;
+        }
+        if (!IsPlaybackContinuing()) {
+          SetState(AUDIOSINK_STATE_COMPLETE);
+          break;
+        }
+      }
+      if (!PlayAudio()) {
+        SetState(AUDIOSINK_STATE_COMPLETE);
+        break;
+      }
+      // Schedule next loop to play next sample.
+      ScheduleNextLoop();
+      break;
+    }
+
+    case AUDIOSINK_STATE_COMPLETE: {
+      FinishAudioLoop();
+      SetState(AUDIOSINK_STATE_SHUTDOWN);
+      break;
+    }
+
+    case AUDIOSINK_STATE_SHUTDOWN:
+      break;
+
+    case AUDIOSINK_STATE_ERROR:
+      break;
+  } // end of switch
+
+  // We want mState to stay stable during AudioLoop to keep things simple.
+  // Therefore, we only do state transition at the end of AudioLoop.
+  if (mPendingState.isSome()) {
+    MOZ_ASSERT(mState != mPendingState.ref());
+    SINK_LOG("change mState, %d -> %d", mState, mPendingState.ref());
+    mState = mPendingState.ref();
+    mPendingState.reset();
+    // Schedule next loop when state changes.
+    ScheduleNextLoop();
+  }
+}
+
 bool
 AudioSink::PlayAudio()
 {
   // See if there's a gap in the audio. If there is, push silence into the
   // audio hardware, so we can play across the gap.
   // Calculate the timestamp of the next chunk of audio in numbers of
   // samples.
   NS_ASSERTION(AudioQueue().GetSize() > 0, "Should have data to play");
@@ -425,9 +505,15 @@ AudioSink::GetEndTime() const
 }
 
 void
 AudioSink::AssertOnAudioThread()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mThread);
 }
 
+void
+AudioSink::AssertNotOnAudioThread()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() != mThread);
+}
+
 } // namespace mozilla
--- a/dom/media/AudioSink.h
+++ b/dom/media/AudioSink.h
@@ -7,16 +7,17 @@
 #define AudioSink_h__
 
 #include "MediaInfo.h"
 #include "nsRefPtr.h"
 #include "nsISupportsImpl.h"
 
 #include "mozilla/dom/AudioChannelBinding.h"
 #include "mozilla/Atomics.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/ReentrantMonitor.h"
 
 namespace mozilla {
 
 class AudioData;
 class AudioStream;
 template <class T> class MediaQueue;
@@ -52,18 +53,31 @@ public:
   void SetPreservesPitch(bool aPreservesPitch);
   void SetPlaying(bool aPlaying);
 
   // Wake up the audio loop if it is waiting for data to play or the audio
   // queue is finished.
   void NotifyData();
 
 private:
+  enum State {
+    AUDIOSINK_STATE_INIT,
+    AUDIOSINK_STATE_PLAYING,
+    AUDIOSINK_STATE_COMPLETE,
+    AUDIOSINK_STATE_SHUTDOWN,
+    AUDIOSINK_STATE_ERROR
+  };
+
   ~AudioSink() {}
 
+  void DispatchTask(already_AddRefed<nsIRunnable>&& event);
+  void SetState(State aState);
+  void ScheduleNextLoop();
+  void ScheduleNextLoopCrossThread();
+
   // 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();
 
   // Allocate and initialize mAudioStream.  Returns NS_OK on success.
   nsresult InitializeAudioStream();
 
@@ -115,20 +129,26 @@ private:
     return mMonitor;
   }
 
   void AssertCurrentThreadInMonitor() const {
     GetReentrantMonitor().AssertCurrentThreadIn();
   }
 
   void AssertOnAudioThread();
+  void AssertNotOnAudioThread();
 
   MediaQueue<AudioData>& mAudioQueue;
   mutable ReentrantMonitor mMonitor;
 
+  // There members are accessed on the audio thread only.
+  State mState;
+  Maybe<State> mPendingState;
+  bool mAudioLoopScheduled;
+
   // Thread for pushing audio onto the audio hardware.
   // The "audio push thread".
   nsCOMPtr<nsIThread> mThread;
 
   // The audio stream resource. Used on the state machine, and audio threads.
   // This is created and destroyed on the audio thread, while holding the
   // decoder monitor, so if this is used off the audio thread, you must
   // first acquire the decoder monitor and check that it is non-null.