Bug 994881 - Release audio offload player resources after pause timeout. r=roc
authorVasanthakumar Pandurangan <vasanth@codeaurora.org>
Mon, 21 Apr 2014 16:47:04 +0530
changeset 199128 b7457024c4486680bd98c9028d21f4461430aee7
parent 199127 b5e25b3ba00b1a4bb9ffdcef911f240dbb005dde
child 199129 55c4e39a1e387b45056f9215d5ddf6e847c43d3b
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs994881
milestone31.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 994881 - Release audio offload player resources after pause timeout. r=roc
content/media/omx/AudioOffloadPlayer.cpp
content/media/omx/AudioOffloadPlayer.h
content/media/omx/AudioOffloadPlayerBase.h
content/media/omx/MediaOmxDecoder.cpp
--- a/content/media/omx/AudioOffloadPlayer.cpp
+++ b/content/media/omx/AudioOffloadPlayer.cpp
@@ -42,16 +42,20 @@ namespace mozilla {
 #ifdef PR_LOGGING
 PRLogModuleInfo* gAudioOffloadPlayerLog;
 #define AUDIO_OFFLOAD_LOG(type, msg) \
   PR_LOG(gAudioOffloadPlayerLog, type, msg)
 #else
 #define AUDIO_OFFLOAD_LOG(type, msg)
 #endif
 
+// maximum time in paused state when offloading audio decompression.
+// When elapsed, the AudioSink is destroyed to allow the audio DSP to power down.
+static const uint64_t OFFLOAD_PAUSE_MAX_MSECS = 60000ll;
+
 AudioOffloadPlayer::AudioOffloadPlayer(MediaOmxDecoder* aObserver) :
   mObserver(aObserver),
   mInputBuffer(nullptr),
   mSampleRate(0),
   mSeeking(false),
   mSeekDuringPause(false),
   mReachedEOS(false),
   mSeekTimeUs(0),
@@ -73,19 +77,17 @@ AudioOffloadPlayer::AudioOffloadPlayer(M
   mSessionId = AudioSystem::newAudioSessionId();
   AudioSystem::acquireAudioSessionId(mSessionId);
   mAudioSink = new AudioOutput(mSessionId,
       IPCThreadState::self()->getCallingUid());
 }
 
 AudioOffloadPlayer::~AudioOffloadPlayer()
 {
-  if (mStarted) {
-    Reset();
-  }
+  Reset();
   AudioSystem::releaseAudioSessionId(mSessionId);
 }
 
 void AudioOffloadPlayer::SetSource(const sp<MediaSource> &aSource)
 {
   MOZ_ASSERT(NS_IsMainThread());
   CHECK(!mSource.get());
 
@@ -104,22 +106,16 @@ status_t AudioOffloadPlayer::Start(bool 
   if (!aSourceAlreadyStarted) {
     err = mSource->start();
 
     if (err != OK) {
       return err;
     }
   }
 
-  MediaSource::ReadOptions options;
-  if (mSeeking) {
-    options.setSeekTo(mSeekTimeUs);
-    mSeeking = false;
-  }
-
   sp<MetaData> format = mSource->getFormat();
   const char* mime;
   int avgBitRate = -1;
   int32_t channelMask;
   int32_t numChannels;
   int64_t durationUs = -1;
   audio_format_t audioFormat = AUDIO_FORMAT_PCM_16_BIT;
   uint32_t flags = AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD;
@@ -172,31 +168,34 @@ status_t AudioOffloadPlayer::Start(bool 
     SendMetaDataToHal(mAudioSink, format);
   }
   mStarted = true;
   mPlaying = false;
 
   return err;
 }
 
-void AudioOffloadPlayer::ChangeState(MediaDecoder::PlayState aState)
+status_t AudioOffloadPlayer::ChangeState(MediaDecoder::PlayState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mPlayState = aState;
 
   switch (mPlayState) {
-    case MediaDecoder::PLAY_STATE_PLAYING:
-      Play();
+    case MediaDecoder::PLAY_STATE_PLAYING: {
+      status_t err = Play();
+      if (err != OK) {
+        return err;
+      }
       StartTimeUpdate();
-      break;
+    } break;
 
     case MediaDecoder::PLAY_STATE_SEEKING: {
       int64_t seekTimeUs
           = mObserver->GetSeekTime();
-      SeekTo(seekTimeUs);
+      SeekTo(seekTimeUs, true);
       mObserver->ResetSeekTime();
     } break;
 
     case MediaDecoder::PLAY_STATE_PAUSED:
     case MediaDecoder::PLAY_STATE_SHUTDOWN:
       // Just pause here during play state shutdown as well to stop playing
       // offload track immediately. Resources will be freed by MediaOmxDecoder
       Pause();
@@ -204,51 +203,91 @@ void AudioOffloadPlayer::ChangeState(Med
 
     case MediaDecoder::PLAY_STATE_ENDED:
       Pause(true);
       break;
 
     default:
       break;
   }
+  return OK;
+}
+
+static void ResetCallback(nsITimer* aTimer, void* aClosure)
+{
+  AudioOffloadPlayer* player = static_cast<AudioOffloadPlayer*>(aClosure);
+  if (player) {
+    player->Reset();
+  }
 }
 
 void AudioOffloadPlayer::Pause(bool aPlayPendingSamples)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  CHECK(mStarted);
-  CHECK(mAudioSink.get());
 
-  if (aPlayPendingSamples) {
-    mAudioSink->Stop();
-  } else {
-    mAudioSink->Pause();
+  if (mStarted) {
+    CHECK(mAudioSink.get());
+    if (aPlayPendingSamples) {
+      mAudioSink->Stop();
+    } else {
+      mAudioSink->Pause();
+    }
+    mPlaying = false;
   }
-  mPlaying = false;
+
+  if (mResetTimer) {
+    return;
+  }
+  mResetTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mResetTimer->InitWithFuncCallback(ResetCallback,
+                                    this,
+                                    OFFLOAD_PAUSE_MAX_MSECS,
+                                    nsITimer::TYPE_ONE_SHOT);
 }
 
 status_t AudioOffloadPlayer::Play()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  CHECK(mStarted);
-  CHECK(mAudioSink.get());
+
+  if (mResetTimer) {
+    mResetTimer->Cancel();
+    mResetTimer = nullptr;
+  }
 
   status_t err = OK;
-  err = mAudioSink->Start();
 
-  if (err == OK) {
-    mPlaying = true;
+  if (!mStarted) {
+    // Last pause timed out and offloaded audio sink was reset. Start it again
+    err = Start(false);
+    if (err != OK) {
+      return err;
+    }
+    // Seek to last play position only when there was no seek during last pause
+    if (!mSeeking) {
+      SeekTo(mPositionTimeMediaUs);
+    }
+  }
+
+  if (!mPlaying) {
+    CHECK(mAudioSink.get());
+    err = mAudioSink->Start();
+    if (err == OK) {
+      mPlaying = true;
+    }
   }
 
   return err;
 }
 
 void AudioOffloadPlayer::Reset()
 {
-  CHECK(mStarted);
+  if (!mStarted) {
+    return;
+  }
+
   CHECK(mAudioSink.get());
 
   AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("reset: mPlaying=%d mReachedEOS=%d",
       mPlaying, mReachedEOS));
 
   mAudioSink->Stop();
   // If we're closing and have reached EOS, we don't want to flush
   // the track because if it is offloaded there could be a small
@@ -268,62 +307,68 @@ void AudioOffloadPlayer::Reset()
   // source is able to stop().
 
   if (mInputBuffer) {
     AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Releasing input buffer"));
 
     mInputBuffer->release();
     mInputBuffer = nullptr;
   }
+  mSource->stop();
 
   IPCThreadState::self()->flushCommands();
   StopTimeUpdate();
 
-  mSeeking = false;
-  mSeekTimeUs = 0;
   mReachedEOS = false;
   mStarted = false;
   mPlaying = false;
   mStartPosUs = 0;
 }
 
-status_t AudioOffloadPlayer::SeekTo(int64_t aTimeUs)
+status_t AudioOffloadPlayer::SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents)
 {
   MOZ_ASSERT(NS_IsMainThread());
   CHECK(mAudioSink.get());
 
   android::Mutex::Autolock autoLock(mLock);
 
   AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("SeekTo ( %lld )", aTimeUs));
 
   mSeeking = true;
   mReachedEOS = false;
   mPositionTimeMediaUs = -1;
   mSeekTimeUs = aTimeUs;
   mStartPosUs = aTimeUs;
+  mDispatchSeekEvents = aDispatchSeekEvents;
 
-  nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
-      &MediaDecoder::SeekingStarted);
-  NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL);
+  if (mDispatchSeekEvents) {
+    nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
+        &MediaDecoder::SeekingStarted);
+    NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL);
+  }
 
   if (mPlaying) {
     mAudioSink->Pause();
-  }
+    mAudioSink->Flush();
+    mAudioSink->Start();
 
-  mAudioSink->Flush();
-
-  if (mPlaying) {
-    mAudioSink->Start();
   } else {
     mSeekDuringPause = true;
-    AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Fake seek complete during pause"));
+
+    if (mStarted) {
+      mAudioSink->Flush();
+    }
 
-    nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
-        &MediaDecoder::SeekingStopped);
-    NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL);
+    if (mDispatchSeekEvents) {
+      mDispatchSeekEvents = false;
+      AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Fake seek complete during pause"));
+      nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
+          &MediaDecoder::SeekingStopped);
+      NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL);
+    }
   }
 
   return OK;
 }
 
 double AudioOffloadPlayer::GetMediaTimeSecs()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -334,16 +379,19 @@ double AudioOffloadPlayer::GetMediaTimeS
 int64_t AudioOffloadPlayer::GetMediaTimeUs()
 {
   android::Mutex::Autolock autoLock(mLock);
 
   int64_t playPosition = 0;
   if (mSeeking) {
     return mSeekTimeUs;
   }
+  if (!mStarted) {
+    return mPositionTimeMediaUs;
+  }
 
   playPosition = GetOutputPlayPositionUs_l();
   if (!mReachedEOS) {
     mPositionTimeMediaUs = playPosition;
   }
 
   return mPositionTimeMediaUs;
 }
@@ -422,18 +470,16 @@ size_t AudioOffloadPlayer::AudioSinkCall
 size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize)
 {
   CHECK(mAudioSink.get());
 
   if (mReachedEOS) {
     return 0;
   }
 
-  bool postSeekComplete = false;
-
   size_t sizeDone = 0;
   size_t sizeRemaining = aSize;
   while (sizeRemaining > 0) {
     MediaSource::ReadOptions options;
     bool refreshSeekTime = false;
 
     {
       android::Mutex::Autolock autoLock(mLock);
@@ -441,19 +487,17 @@ size_t AudioOffloadPlayer::FillBuffer(vo
       if (mSeeking) {
         options.setSeekTo(mSeekTimeUs);
         refreshSeekTime = true;
 
         if (mInputBuffer) {
           mInputBuffer->release();
           mInputBuffer = nullptr;
         }
-
         mSeeking = false;
-        postSeekComplete = true;
       }
     }
 
     if (!mInputBuffer) {
 
       status_t err;
       err = mSource->read(&mInputBuffer, &options);
 
@@ -486,41 +530,41 @@ size_t AudioOffloadPlayer::FillBuffer(vo
         break;
       }
 
       if(mInputBuffer->range_length() != 0) {
         CHECK(mInputBuffer->meta_data()->findInt64(
             kKeyTime, &mPositionTimeMediaUs));
       }
 
-      // need to adjust the mStartPosUs for offload decoding since parser
-      // might not be able to get the exact seek time requested.
       if (refreshSeekTime) {
-        if (postSeekComplete) {
 
-          if (!mSeekDuringPause) {
-            AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("FillBuffer posting SEEK_COMPLETE"));
-            nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
-                &MediaDecoder::SeekingStopped);
-            NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL);
-          } else {
-            // Callback is already called for seek during pause. Just reset the
-            // flag
-            AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Not posting seek complete as its"
-                " already faked"));
-            mSeekDuringPause = false;
-          }
+        if (mDispatchSeekEvents && !mSeekDuringPause) {
+          mDispatchSeekEvents = false;
+          AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("FillBuffer posting SEEK_COMPLETE"));
+          nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
+              &MediaDecoder::SeekingStopped);
+          NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL);
 
-          NotifyPositionChanged();
-          postSeekComplete = false;
+        } else if (mSeekDuringPause) {
+          // Callback is already called for seek during pause. Just reset the
+          // flag
+          AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Not posting seek complete as its"
+              " already faked"));
+          mSeekDuringPause = false;
         }
 
+        NotifyPositionChanged();
+
+        // need to adjust the mStartPosUs for offload decoding since parser
+        // might not be able to get the exact seek time requested.
         mStartPosUs = mPositionTimeMediaUs;
         AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Adjust seek time to: %.2f",
             mStartPosUs / 1E6));
+
         // clear seek time with mLock locked and once we have valid
         // mPositionTimeMediaUs
         // before clearing mSeekTimeUs check if a new seek request has been
         // received while we were reading from the source with mLock released.
         if (!mSeeking) {
           mSeekTimeUs = 0;
         }
       }
--- a/content/media/omx/AudioOffloadPlayer.h
+++ b/content/media/omx/AudioOffloadPlayer.h
@@ -81,27 +81,30 @@ public:
   // create an offloaded audio track
   status_t Start(bool aSourceAlreadyStarted = false);
 
   double GetMediaTimeSecs();
 
   // To update progress bar when the element is visible
   void SetElementVisibility(bool aIsVisible);
 
-  void ChangeState(MediaDecoder::PlayState aState);
+  status_t ChangeState(MediaDecoder::PlayState aState);
 
   void SetVolume(double aVolume);
 
   // Update ready state based on current play state. Not checking data
   // availability since offloading is currently done only when whole compressed
   // data is available
   MediaDecoderOwner::NextFrameStatus GetNextFrameStatus();
 
   void TimeUpdate();
 
+  // Close the audio sink, stop time updates, frees the input buffers
+  void Reset();
+
 private:
   // Set when audio source is started and audioSink is initialized
   // Used only in main thread
   bool mStarted;
 
   // Set when audio sink is started. i.e. playback started
   // Used only in main thread
   bool mPlaying;
@@ -117,16 +120,22 @@ private:
   // mLock
   bool mReachedEOS;
 
   // Set when there is a seek request during pause.
   // Used in main thread and offload callback thread, protected by Mutex
   // mLock
   bool mSeekDuringPause;
 
+  // Seek can be triggered internally or by MediaDecoder. This bool is to
+  // to track seek triggered by MediaDecoder so that we can send back
+  // SeekingStarted and SeekingStopped events.
+  // Used in main thread and offload callback thread, protected by Mutex mLock
+  bool mDispatchSeekEvents;
+
   // Set when the HTML Audio Element is visible to the user.
   // Used only in main thread
   bool mIsElementVisible;
 
   // Session id given by Android::AudioSystem and used while creating audio sink
   // Used only in main thread
   int mSessionId;
 
@@ -159,31 +168,38 @@ private:
   // offload callback thread
   Mutex mLock;
 
   // Compressed audio source.
   // Used in main thread first and later in offload callback thread
   android::sp<MediaSource> mSource;
 
   // Audio sink wrapper to access offloaded audio tracks
-  // Used in main thread and offload callback thread, access is protected by
+  // Used in main thread and offload callback thread
   // Race conditions are protected in underlying Android::AudioTrack class
   android::sp<AudioSink> mAudioSink;
 
   // Buffer used to get date from audio source. Used in offload callback thread
   MediaBuffer* mInputBuffer;
 
   // MediaOmxDecoder object used mainly to notify the audio sink status
   MediaOmxDecoder* mObserver;
 
   TimeStamp mLastFireUpdateTime;
+
   // Timer to trigger position changed events
   nsCOMPtr<nsITimer> mTimeUpdateTimer;
 
+  // Timer to reset AudioSink when audio is paused for OFFLOAD_PAUSE_MAX_USECS.
+  // It is triggered in Pause() and canceled when there is a Play() within
+  // OFFLOAD_PAUSE_MAX_USECS. Used only from main thread so no lock is needed.
+  nsCOMPtr<nsITimer> mResetTimer;
+
   int64_t GetMediaTimeUs();
+
   // Provide the playback position in microseconds from total number of
   // frames played by audio track
   int64_t GetOutputPlayPositionUs_l() const;
 
   // Fill the buffer given by audio sink with data from compressed audio
   // source. Also handles the seek by seeking audio source and stop the sink in
   // case of error
   size_t FillBuffer(void *aData, size_t aSize);
@@ -193,26 +209,24 @@ private:
                                   void *aData,
                                   size_t aSize,
                                   void *aMe,
                                   AudioSink::cb_event_t aEvent);
 
   bool IsSeeking();
 
   // Set mSeekTime to the given position and restart the sink. Actual seek
-  // happens in FillBuffer(). To MediaDecoder, send SeekingStarted event always
-  // and SeekingStopped event when the play state is paused.
+  // happens in FillBuffer(). If aDispatchSeekEvents is true, send
+  // SeekingStarted event always and SeekingStopped event when the play state is
+  // paused to MediaDecoder.
   // When decoding and playing happens separately, if there is a seek during
   // pause, we can decode and keep data ready.
   // In case of offload player, no way to seek during pause. So just fake that
   // seek is done.
-  status_t SeekTo(int64_t aTimeUs);
-
-  // Close the audio sink, stop time updates, frees the input buffers
-  void Reset();
+  status_t SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents = false);
 
   // Start/Resume the audio sink so that callback will start being called to get
   // compressed data
   status_t Play();
 
   // Stop the audio sink if we need to play till we drain the current buffer.
   // or Pause the sink in case we should stop playing immediately
   void Pause(bool aPlayPendingSamples = false);
--- a/content/media/omx/AudioOffloadPlayerBase.h
+++ b/content/media/omx/AudioOffloadPlayerBase.h
@@ -44,17 +44,20 @@ public:
 
   // Start the source if it's not already started and open the AudioSink to
   // create an offloaded audio track
   virtual status_t Start(bool aSourceAlreadyStarted = false)
   {
     return android::NO_INIT;
   }
 
-  virtual void ChangeState(MediaDecoder::PlayState aState) {}
+  virtual status_t ChangeState(MediaDecoder::PlayState aState)
+  {
+    return android::NO_INIT;
+  }
 
   virtual void SetVolume(double aVolume) {}
 
   virtual double GetMediaTimeSecs() { return 0; }
 
   // To update progress bar when the element is visible
   virtual void SetElementVisibility(bool aIsVisible) {}
 
--- a/content/media/omx/MediaOmxDecoder.cpp
+++ b/content/media/omx/MediaOmxDecoder.cpp
@@ -60,17 +60,16 @@ void MediaOmxDecoder::MetadataLoaded(int
                                      bool aHasAudio,
                                      bool aHasVideo,
                                      MetadataTags* aTags)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MediaDecoder::MetadataLoaded(aChannels, aRate, aHasAudio, aHasVideo, aTags);
 
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-
   if (!mCanOffloadAudio || mFallbackToStateMachine || mOutputStreams.Length() ||
       mInitialPlaybackRate != 1.0) {
     DECODER_LOG(PR_LOG_DEBUG, ("In %s Offload Audio check failed",
         __PRETTY_FUNCTION__));
     return;
   }
 
 #ifdef MOZ_AUDIO_OFFLOAD
@@ -90,28 +89,27 @@ void MediaOmxDecoder::MetadataLoaded(int
       "Switching to normal mode", __PRETTY_FUNCTION__, err));
 }
 
 void MediaOmxDecoder::PauseStateMachine()
 {
   MOZ_ASSERT(NS_IsMainThread());
   GetReentrantMonitor().AssertCurrentThreadIn();
   DECODER_LOG(PR_LOG_DEBUG, ("%s", __PRETTY_FUNCTION__));
-
   if (!mDecoderStateMachine) {
     return;
   }
   StopProgress();
   mDecoderStateMachine->SetDormant(true);
 }
 
 void MediaOmxDecoder::ResumeStateMachine()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  GetReentrantMonitor().AssertCurrentThreadIn();
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   DECODER_LOG(PR_LOG_DEBUG, ("%s current time %f", __PRETTY_FUNCTION__,
       mCurrentTime));
 
   if (!mDecoderStateMachine) {
     return;
   }
 
   mFallbackToStateMachine = true;
@@ -125,69 +123,67 @@ void MediaOmxDecoder::ResumeStateMachine
 
 void MediaOmxDecoder::AudioOffloadTearDown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   PlaybackPositionChanged();
   DECODER_LOG(PR_LOG_DEBUG, ("%s", __PRETTY_FUNCTION__));
   {
     // Audio offload player sent tear down event. Fallback to state machine
-    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     ResumeStateMachine();
   }
 }
 
 void MediaOmxDecoder::AddOutputStream(ProcessedMediaStream* aStream,
                                       bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   PlaybackPositionChanged();
 
   if (mAudioOffloadPlayer) {
     // Offload player cannot handle MediaStream. Fallback
-    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     ResumeStateMachine();
   }
 
   MediaDecoder::AddOutputStream(aStream, aFinishWhenEnded);
 }
 
 void MediaOmxDecoder::SetPlaybackRate(double aPlaybackRate)
 {
   MOZ_ASSERT(NS_IsMainThread());
   PlaybackPositionChanged();
 
   if (mAudioOffloadPlayer &&
       ((aPlaybackRate != 0.0) || (aPlaybackRate != 1.0))) {
     // Offload player cannot handle playback rate other than 1/0. Fallback
-    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     ResumeStateMachine();
   }
 
   MediaDecoder::SetPlaybackRate(aPlaybackRate);
 }
 
 void MediaOmxDecoder::ChangeState(PlayState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
   // Keep MediaDecoder state in sync with MediaElement irrespective of offload
   // playback so it will continue to work in normal mode when offloading fails
   // in between
   MediaDecoder::ChangeState(aState);
 
   if (mAudioOffloadPlayer) {
-    mAudioOffloadPlayer->ChangeState(aState);
+    status_t err = mAudioOffloadPlayer->ChangeState(aState);
+    if (err != OK) {
+      ResumeStateMachine();
+    }
   }
 }
 
 void MediaOmxDecoder::ApplyStateToStateMachine(PlayState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
   // During offload playback, state machine should be in dormant state.
   // ApplyStateToStateMachine() can change state machine state to
   // something else or reset the seek time. So don't call this when audio is
   // offloaded
   if (!mAudioOffloadPlayer) {
     MediaDecoder::ApplyStateToStateMachine(aState);
   }
 }