Bug 994881 - Release audio offload player resources after pause timeout r=roc
authorVasanthakumar Pandurangan <vasanth@codeaurora.org>
Mon, 21 Apr 2014 16:43:13 +0530
changeset 192048 ec64a6046a31a2493d5a42ef681dc8f4a7ec0550
parent 192047 88d2a912a227eaa50122903c4a1496fee864ac67
child 192049 4308d4e9ff77200e349192a78c1163f2a76a08b0
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)
reviewersroc
bugs994881
milestone30.0a2
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
           = static_cast<int64_t>(mObserver->GetSeekTime()) * USECS_PER_S;
-      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);
   }
 }