Backed out 11 changesets (bug 1297265) for gtest timeouts during MediaDataDecoder.H264
authorPhil Ringnalda <philringnalda@gmail.com>
Wed, 31 Aug 2016 19:49:28 -0700
changeset 353515 6658a683e292b3ef5fcc94ae7f185c78c3078299
parent 353514 d316aa33106d4b9ad5fce1e89d8818efad79e141
child 353516 54efec27fb406597ecf6b82b5c6a53c8f24eed57
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1297265
milestone51.0a1
backs out04d9b7160ac9a3751124c2c98d1ae8eadc65f0ac
c7bffd1ba7fdfedc72d6cf5d68f90d61fe50b271
ad1915ff688b2989655473d7b8fb0f0f3538d848
67a97304d44db79b61197045d3f95b0f9cc4709d
e6b9e618445742890aeff875283bf58cfb1af726
30636ac0790f16001e67bf1c9f157d1f6616739f
cc70d3a139bc7eb8474f01723ead8b9728264c81
345a577c92a19faee26823300af1aaad993f6b63
ec1f3ac8751ce1cf9b189cbad92be6fae69322e1
da9117375d2e78449950fabc1aa7b9271ac15e73
dd4c7aafcc3d727bdcdbbc2a584ad73d7e31054f
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
Backed out 11 changesets (bug 1297265) for gtest timeouts during MediaDataDecoder.H264 Backed out changeset 04d9b7160ac9 (bug 1297265) Backed out changeset c7bffd1ba7fd (bug 1297265) Backed out changeset ad1915ff688b (bug 1297265) Backed out changeset 67a97304d44d (bug 1297265) Backed out changeset e6b9e6184457 (bug 1297265) Backed out changeset 30636ac0790f (bug 1297265) Backed out changeset cc70d3a139bc (bug 1297265) Backed out changeset 345a577c92a1 (bug 1297265) Backed out changeset ec1f3ac8751c (bug 1297265) Backed out changeset da9117375d2e (bug 1297265) Backed out changeset dd4c7aafcc3d (bug 1297265)
dom/media/MediaFormatReader.cpp
dom/media/MediaFormatReader.h
dom/media/platforms/PlatformDecoderModule.h
dom/media/platforms/agnostic/BlankDecoderModule.cpp
dom/media/platforms/agnostic/OpusDecoder.cpp
dom/media/platforms/agnostic/TheoraDecoder.cpp
dom/media/platforms/agnostic/VPXDecoder.cpp
dom/media/platforms/agnostic/VorbisDecoder.cpp
dom/media/platforms/apple/AppleATDecoder.cpp
dom/media/platforms/apple/AppleVTDecoder.cpp
dom/media/platforms/apple/AppleVTDecoder.h
dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -57,19 +57,19 @@ TrackTypeToStr(TrackInfo::TrackType aTra
   }
 }
 
 MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
                                      MediaDataDemuxer* aDemuxer,
                                      VideoFrameContainer* aVideoFrameContainer,
                                      layers::LayersBackend aLayersBackend)
   : MediaDecoderReader(aDecoder)
-  , mAudio(this, MediaData::AUDIO_DATA,
+  , mAudio(this, MediaData::AUDIO_DATA, Preferences::GetUint("media.audio-decode-ahead", 2),
            Preferences::GetUint("media.audio-max-decode-error", 3))
-  , mVideo(this, MediaData::VIDEO_DATA,
+  , mVideo(this, MediaData::VIDEO_DATA, Preferences::GetUint("media.video-decode-ahead", 2),
            Preferences::GetUint("media.video-max-decode-error", 2))
   , mDemuxer(aDemuxer)
   , mDemuxerInitDone(false)
   , mLastReportedNumDecodedFrames(0)
   , mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe)
   , mLayersBackendType(aLayersBackend)
   , mInitDone(false)
   , mIsEncrypted(false)
@@ -557,17 +557,17 @@ MediaFormatReader::RequestVideoData(bool
   if (!mVideo.HasInternalSeekPending() &&
       ShouldSkip(aSkipToNextKeyframe, timeThreshold)) {
     RefPtr<MediaDataPromise> p = mVideo.EnsurePromise(__func__);
     SkipVideoDemuxToNextKeyFrame(timeThreshold);
     return p;
   }
 
   RefPtr<MediaDataPromise> p = mVideo.EnsurePromise(__func__);
-  ScheduleUpdate(TrackInfo::kVideoTrack);
+  NotifyDecodingRequested(TrackInfo::kVideoTrack);
 
   return p;
 }
 
 void
 MediaFormatReader::OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure)
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -601,16 +601,17 @@ MediaFormatReader::OnDemuxFailed(TrackTy
       MOZ_ASSERT(false);
       break;
   }
 }
 
 void
 MediaFormatReader::DoDemuxVideo()
 {
+  // TODO Use DecodeAhead value rather than 1.
   mVideo.mDemuxRequest.Begin(mVideo.mTrackDemuxer->GetSamples(1)
                       ->Then(OwnerThread(), __func__, this,
                              &MediaFormatReader::OnVideoDemuxCompleted,
                              &MediaFormatReader::OnVideoDemuxFailed));
 }
 
 void
 MediaFormatReader::OnVideoDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
@@ -651,24 +652,25 @@ MediaFormatReader::RequestAudioData()
   }
 
   if (mShutdown) {
     NS_WARNING("RequestAudioData on shutdown MediaFormatReader!");
     return MediaDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   RefPtr<MediaDataPromise> p = mAudio.EnsurePromise(__func__);
-  ScheduleUpdate(TrackInfo::kAudioTrack);
+  NotifyDecodingRequested(TrackInfo::kAudioTrack);
 
   return p;
 }
 
 void
 MediaFormatReader::DoDemuxAudio()
 {
+  // TODO Use DecodeAhead value rather than 1.
   mAudio.mDemuxRequest.Begin(mAudio.mTrackDemuxer->GetSamples(1)
                       ->Then(OwnerThread(), __func__, this,
                              &MediaFormatReader::OnAudioDemuxCompleted,
                              &MediaFormatReader::OnAudioDemuxFailed));
 }
 
 void
 MediaFormatReader::OnAudioDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
@@ -690,27 +692,26 @@ MediaFormatReader::NotifyNewOutput(Track
   auto& decoder = GetDecoderData(aTrack);
   if (!decoder.mOutputRequested) {
     LOG("MediaFormatReader produced output while flushing, discarding.");
     return;
   }
   decoder.mOutput.AppendElement(aSample);
   decoder.mNumSamplesOutput++;
   decoder.mNumOfConsecutiveError = 0;
-  decoder.mDecodePending = false;
   ScheduleUpdate(aTrack);
 }
 
 void
 MediaFormatReader::NotifyInputExhausted(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Decoder has requested more %s data", TrackTypeToStr(aTrack));
   auto& decoder = GetDecoderData(aTrack);
-  decoder.mDecodePending = false;
+  decoder.mInputExhausted = true;
   ScheduleUpdate(aTrack);
 }
 
 void
 MediaFormatReader::NotifyDrainComplete(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
@@ -749,31 +750,43 @@ void
 MediaFormatReader::NotifyEndOfStream(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   decoder.mDemuxEOS = true;
   ScheduleUpdate(aTrack);
 }
 
+void
+MediaFormatReader::NotifyDecodingRequested(TrackType aTrack)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  auto& decoder = GetDecoderData(aTrack);
+  decoder.mDecodingRequested = true;
+  ScheduleUpdate(aTrack);
+}
+
 bool
 MediaFormatReader::NeedInput(DecoderData& aDecoder)
 {
-  // To account for H.264 streams which may require a longer
-  // run of input than we input, decoders fire an "input exhausted" callback.
-  // The decoder will not be fed a new raw sample until either Output callback
-  // has been called, or InputExhausted was called.
+  // We try to keep a few more compressed samples input than decoded samples
+  // have been output, provided the state machine has requested we send it a
+  // decoded sample. To account for H.264 streams which may require a longer
+  // run of input than we input, decoders fire an "input exhausted" callback,
+  // which overrides our "few more samples" threshold.
   return
-    (aDecoder.HasPromise() || aDecoder.mTimeThreshold.isSome()) &&
     !aDecoder.HasPendingDrain() &&
     !aDecoder.HasFatalError() &&
+    aDecoder.mDecodingRequested &&
     !aDecoder.mDemuxRequest.Exists() &&
-    !aDecoder.mOutput.Length() &&
     !aDecoder.HasInternalSeekPending() &&
-    !aDecoder.mDecodePending;
+    aDecoder.mOutput.Length() <= aDecoder.mDecodeAhead &&
+    (aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() ||
+     aDecoder.mTimeThreshold.isSome() ||
+     aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput <= aDecoder.mDecodeAhead);
 }
 
 void
 MediaFormatReader::ScheduleUpdate(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mShutdown) {
     return;
@@ -914,17 +927,16 @@ MediaFormatReader::DecodeDemuxedSamples(
                                         MediaRawData* aSample)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   if (NS_FAILED(decoder.mDecoder->Input(aSample))) {
       LOG("Unable to pass frame to decoder");
       return false;
   }
-  decoder.mDecodePending = true;
   return true;
 }
 
 void
 MediaFormatReader::HandleDemuxedSamples(TrackType aTrack,
                                         AbstractMediaDecoder::AutoNotifyDecoded& aA)
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -990,17 +1002,17 @@ MediaFormatReader::HandleDemuxedSamples(
       decoder.mLastStreamSourceID = info->GetID();
       decoder.mNextStreamSourceID.reset();
       // Reset will clear our array of queued samples. So make a copy now.
       nsTArray<RefPtr<MediaRawData>> samples{decoder.mQueuedSamples};
       Reset(aTrack);
       decoder.ShutdownDecoder();
       if (sample->mKeyframe) {
         decoder.mQueuedSamples.AppendElements(Move(samples));
-        ScheduleUpdate(aTrack);
+        NotifyDecodingRequested(aTrack);
       } else {
         TimeInterval time =
           TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
                        TimeUnit::FromMicroseconds(sample->GetEndTime()));
         InternalSeekTarget seekTarget =
           decoder.mTimeThreshold.refOr(InternalSeekTarget(time, false));
         LOG("Stream change occurred on a non-keyframe. Seeking to:%lld",
             sample->mTime);
@@ -1028,16 +1040,19 @@ MediaFormatReader::HandleDemuxedSamples(
     decoder.mQueuedSamples.RemoveElementAt(0);
     if (mDemuxOnly) {
       // If demuxed-only case, ReturnOutput will resolve with one demuxed data.
       // Then we should stop doing the iteration.
       return;
     }
     samplesPending = true;
   }
+
+  // We have serviced the decoder's request for more data.
+  decoder.mInputExhausted = false;
 }
 
 void
 MediaFormatReader::InternalSeek(TrackType aTrack, const InternalSeekTarget& aTarget)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("%s internal seek to %f",
       TrackTypeToStr(aTrack), aTarget.Time().ToSeconds());
@@ -1051,17 +1066,17 @@ MediaFormatReader::InternalSeek(TrackTyp
              ->Then(OwnerThread(), __func__,
                     [self, aTrack] (media::TimeUnit aTime) {
                       auto& decoder = self->GetDecoderData(aTrack);
                       decoder.mSeekRequest.Complete();
                       MOZ_ASSERT(decoder.mTimeThreshold,
                                  "Seek promise must be disconnected when timethreshold is reset");
                       decoder.mTimeThreshold.ref().mHasSeeked = true;
                       self->SetVideoDecodeThreshold();
-                      self->ScheduleUpdate(aTrack);
+                      self->NotifyDecodingRequested(aTrack);
                     },
                     [self, aTrack] (DemuxerFailureReason aResult) {
                       auto& decoder = self->GetDecoderData(aTrack);
                       decoder.mSeekRequest.Complete();
                       switch (aResult) {
                         case DemuxerFailureReason::WAITING_FOR_DATA:
                           self->NotifyWaitingForData(aTrack);
                           break;
@@ -1255,17 +1270,16 @@ MediaFormatReader::Update(TrackType aTra
 
   if (decoder.mNeedDraining) {
     DrainDecoder(aTrack);
     return;
   }
 
   if (decoder.mError &&
       decoder.mError.ref() == MediaDataDecoderError::DECODE_ERROR) {
-    decoder.mDecodePending = false;
     decoder.mError.reset();
     if (++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) {
       NotifyError(aTrack);
       return;
     }
     LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
                                      decoder.mNumOfConsecutiveError);
     media::TimeUnit nextKeyframe;
@@ -1273,21 +1287,21 @@ MediaFormatReader::Update(TrackType aTra
         NS_SUCCEEDED(decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
       SkipVideoDemuxToNextKeyFrame(decoder.mLastSampleTime.refOr(TimeInterval()).Length());
       return;
     }
   }
 
   bool needInput = NeedInput(decoder);
 
-  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d promise:%d sid:%u",
-       TrackTypeToStr(aTrack), needInput, needOutput, decoder.mDecodePending,
+  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d ahead:%d sid:%u",
+       TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
        decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
        uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
-       decoder.mWaitingForData, decoder.HasPromise(), decoder.mLastStreamSourceID);
+       decoder.mWaitingForData, !decoder.HasPromise(), decoder.mLastStreamSourceID);
 
   if (decoder.mWaitingForData &&
       (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting)) {
     // Nothing more we can do at present.
     LOGV("Still waiting for data.");
     return;
   }
 
@@ -1558,17 +1572,17 @@ void
 MediaFormatReader::OnVideoSkipCompleted(uint32_t aSkipped)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping succeeded, skipped %u frames", aSkipped);
   mSkipRequest.Complete();
 
   VideoSkipReset(aSkipped);
 
-  ScheduleUpdate(TrackInfo::kVideoTrack);
+  NotifyDecodingRequested(TrackInfo::kVideoTrack);
 }
 
 void
 MediaFormatReader::OnVideoSkipFailed(MediaTrackDemuxer::SkipFailureHolder aFailure)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping failed, skipped %u frames", aFailure.mSkipped);
   mSkipRequest.Complete();
@@ -1576,17 +1590,17 @@ MediaFormatReader::OnVideoSkipFailed(Med
   switch (aFailure.mFailure) {
     case DemuxerFailureReason::END_OF_STREAM: MOZ_FALLTHROUGH;
     case DemuxerFailureReason::WAITING_FOR_DATA:
       // Some frames may have been output by the decoder since we initiated the
       // videoskip process and we know they would be late.
       DropDecodedSamples(TrackInfo::kVideoTrack);
       // We can't complete the skip operation, will just service a video frame
       // normally.
-      ScheduleUpdate(TrackInfo::kVideoTrack);
+      NotifyDecodingRequested(TrackInfo::kVideoTrack);
       break;
     case DemuxerFailureReason::CANCELED: MOZ_FALLTHROUGH;
     case DemuxerFailureReason::SHUTDOWN:
       if (mVideo.HasPromise()) {
         mVideo.RejectPromise(CANCELED, __func__);
       }
       break;
     default:
@@ -1994,21 +2008,22 @@ MediaFormatReader::GetMozDebugReaderData
     MonitorAutoLock mon(mVideo.mMonitor);
     videoName = mVideo.mDescription;
   }
 
   result += nsPrintfCString("audio decoder: %s\n", audioName);
   result += nsPrintfCString("audio frames decoded: %lld\n",
                             mAudio.mNumSamplesOutputTotal);
   if (HasAudio()) {
-    result += nsPrintfCString("audio state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
+    result += nsPrintfCString("audio state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d decoder:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
                               NeedInput(mAudio), mAudio.HasPromise(),
-                              mAudio.mDecodePending,
+                              mAudio.mInputExhausted,
                               mAudio.mDemuxRequest.Exists(),
                               int(mAudio.mQueuedSamples.Length()),
+                              mAudio.mDecodingRequested,
                               mAudio.mTimeThreshold
                               ? mAudio.mTimeThreshold.ref().Time().ToSeconds()
                               : -1.0,
                               mAudio.mTimeThreshold
                               ? mAudio.mTimeThreshold.ref().mHasSeeked
                               : -1,
                               mAudio.mNumSamplesInput, mAudio.mNumSamplesOutput,
                               unsigned(size_t(mAudio.mSizeOfQueue)),
@@ -2017,21 +2032,22 @@ MediaFormatReader::GetMozDebugReaderData
   }
   result += nsPrintfCString("video decoder: %s\n", videoName);
   result += nsPrintfCString("hardware video decoding: %s\n",
                             VideoIsHardwareAccelerated() ? "enabled" : "disabled");
   result += nsPrintfCString("video frames decoded: %lld (skipped:%lld)\n",
                             mVideo.mNumSamplesOutputTotal,
                             mVideo.mNumSamplesSkippedTotal);
   if (HasVideo()) {
-    result += nsPrintfCString("video state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
+    result += nsPrintfCString("video state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d decoder:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
                               NeedInput(mVideo), mVideo.HasPromise(),
-                              mVideo.mDecodePending,
+                              mVideo.mInputExhausted,
                               mVideo.mDemuxRequest.Exists(),
                               int(mVideo.mQueuedSamples.Length()),
+                              mVideo.mDecodingRequested,
                               mVideo.mTimeThreshold
                               ? mVideo.mTimeThreshold.ref().Time().ToSeconds()
                               : -1.0,
                               mVideo.mTimeThreshold
                               ? mVideo.mTimeThreshold.ref().mHasSeeked
                               : -1,
                               mVideo.mNumSamplesInput, mVideo.mNumSamplesOutput,
                               unsigned(size_t(mVideo.mSizeOfQueue)),
@@ -2059,14 +2075,14 @@ MediaFormatReader::SetBlankDecode(TrackT
 
   if (decoder.mIsBlankDecode == aIsBlankDecode) {
     return;
   }
 
   decoder.mIsBlankDecode = aIsBlankDecode;
   decoder.Flush();
   decoder.ShutdownDecoder();
-  ScheduleUpdate(TrackInfo::kVideoTrack);
+  NotifyDecodingRequested(TrackInfo::kVideoTrack); // Calls ScheduleUpdate().
 
   return;
 }
 
 } // namespace mozilla
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -164,16 +164,17 @@ private:
   // Drain the current decoder.
   void DrainDecoder(TrackType aTrack);
   void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
   void NotifyInputExhausted(TrackType aTrack);
   void NotifyDrainComplete(TrackType aTrack);
   void NotifyError(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
   void NotifyWaitingForData(TrackType aTrack);
   void NotifyEndOfStream(TrackType aTrack);
+  void NotifyDecodingRequested(TrackType aTrack);
 
   void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   // DecoderCallback proxies the MediaDataDecoderCallback calls to these
   // functions.
@@ -225,28 +226,31 @@ private:
   private:
     MediaFormatReader* mReader;
     TrackType mType;
   };
 
   struct DecoderData {
     DecoderData(MediaFormatReader* aOwner,
                 MediaData::Type aType,
+                uint32_t aDecodeAhead,
                 uint32_t aNumOfMaxError)
       : mOwner(aOwner)
       , mType(aType)
       , mMonitor("DecoderData")
       , mDescription("shutdown")
+      , mDecodeAhead(aDecodeAhead)
       , mUpdateScheduled(false)
       , mDemuxEOS(false)
       , mWaitingForData(false)
       , mReceivedNewData(false)
       , mDecoderInitialized(false)
+      , mDecodingRequested(false)
       , mOutputRequested(false)
-      , mDecodePending(false)
+      , mInputExhausted(false)
       , mNeedDraining(false)
       , mDraining(false)
       , mDrainComplete(false)
       , mNumOfConsecutiveError(0)
       , mMaxConsecutiveError(aNumOfMaxError)
       , mNumSamplesInput(0)
       , mNumSamplesOutput(0)
       , mNumSamplesOutputTotal(0)
@@ -279,16 +283,17 @@ private:
       if (mDecoder) {
         mDecoder->Shutdown();
       }
       mDescription = "shutdown";
       mDecoder = nullptr;
     }
 
     // Only accessed from reader's task queue.
+    uint32_t mDecodeAhead;
     bool mUpdateScheduled;
     bool mDemuxEOS;
     bool mWaitingForData;
     bool mReceivedNewData;
 
     // Pending seek.
     MozPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mSeekRequest;
 
@@ -302,24 +307,21 @@ private:
       return !mWaitingPromise.IsEmpty();
     }
 
     // MediaDataDecoder handler's variables.
     // Decoder initialization promise holder.
     MozPromiseRequestHolder<MediaDataDecoder::InitPromise> mInitPromise;
     // False when decoder is created. True when decoder Init() promise is resolved.
     bool mDecoderInitialized;
+    // Set when decoding can proceed. It is reset when a decoding promise is
+    // rejected or prior a seek operation.
+    bool mDecodingRequested;
     bool mOutputRequested;
-    // Set to true once the MediaDataDecoder has been fed a compressed sample.
-    // No more sample will be passed to the decoder while true.
-    // mDecodePending is reset when:
-    // 1- The decoder returns a sample
-    // 2- The decoder calls InputExhausted
-    // 3- The decoder is Flushed or Reset.
-    bool mDecodePending;
+    bool mInputExhausted;
     bool mNeedDraining;
     bool mDraining;
     bool mDrainComplete;
 
     bool HasPendingDrain() const
     {
       return mDraining || mDrainComplete;
     }
@@ -369,18 +371,19 @@ private:
     // Flush the decoder if present and reset decoding related data.
     // Decoding will be suspended until mInputRequested is set again.
     // Following a flush, the decoder is ready to accept any new data.
     void Flush()
     {
       if (mDecoder) {
         mDecoder->Flush();
       }
+      mDecodingRequested = false;
       mOutputRequested = false;
-      mDecodePending = false;
+      mInputExhausted = false;
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
       mSizeOfQueue = 0;
       mDraining = false;
       mDrainComplete = false;
     }
 
@@ -389,19 +392,20 @@ private:
     // Decoding will be suspended until mInputRequested is set again.
     // The track demuxer is *not* reset.
     void ResetState()
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       mDemuxEOS = false;
       mWaitingForData = false;
       mQueuedSamples.Clear();
+      mDecodingRequested = false;
       mOutputRequested = false;
+      mInputExhausted = false;
       mNeedDraining = false;
-      mDecodePending = false;
       mDraining = false;
       mDrainComplete = false;
       mTimeThreshold.reset();
       mLastSampleTime.reset();
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
       mSizeOfQueue = 0;
@@ -432,18 +436,19 @@ private:
     bool mIsBlankDecode;
 
   };
 
   class DecoderDataWithPromise : public DecoderData {
   public:
     DecoderDataWithPromise(MediaFormatReader* aOwner,
                            MediaData::Type aType,
+                           uint32_t aDecodeAhead,
                            uint32_t aNumOfMaxError)
-      : DecoderData(aOwner, aType, aNumOfMaxError)
+      : DecoderData(aOwner, aType, aDecodeAhead, aNumOfMaxError)
       , mHasPromise(false)
 
     {}
 
     bool HasPromise() const override
     {
       return mHasPromise;
     }
@@ -462,16 +467,17 @@ private:
       mHasPromise = false;
     }
 
     void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
                        const char* aMethodName) override
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       mPromise.Reject(aReason, aMethodName);
+      mDecodingRequested = false;
       mHasPromise = false;
     }
 
   private:
     MozPromiseHolder<MediaDataPromise> mPromise;
     Atomic<bool> mHasPromise;
   };
 
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -170,18 +170,16 @@ public:
   virtual void Output(MediaData* aData) = 0;
 
   // Denotes an error in the decoding process. The reader will stop calling
   // the decoder.
   virtual void Error(MediaDataDecoderError aError) = 0;
 
   // Denotes that the last input sample has been inserted into the decoder,
   // and no more output can be produced unless more input is sent.
-  // InputExhausted should only be called if no decoded samples were returned
-  // from the last input.
   virtual void InputExhausted() = 0;
 
   virtual void DrainComplete() = 0;
 
   virtual void ReleaseMediaResources() {}
 
   virtual bool OnReaderTaskQueue() = 0;
 
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -88,23 +88,24 @@ private:
     if (!aData) {
       mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
       return;
     }
 
     // Frames come out in DTS order but we need to output them in PTS order.
     mReorderQueue.Push(aData);
 
+    while (mReorderQueue.Length() > mMaxRefFrames) {
+      mCallback->Output(mReorderQueue.Pop().get());
+    }
+
     if (mReorderQueue.Length() <= mMaxRefFrames) {
       mCallback->InputExhausted();
-    } else {
-      while (mReorderQueue.Length() > mMaxRefFrames) {
-        mCallback->Output(mReorderQueue.Pop().get());
-      }
     }
+
   }
 
 private:
   nsAutoPtr<BlankMediaDataCreator> mCreator;
   MediaDataDecoderCallback* mCallback;
   const uint32_t mMaxRefFrames;
   ReorderQueue mReorderQueue;
   TrackInfo::TrackType mType;
--- a/dom/media/platforms/agnostic/OpusDecoder.cpp
+++ b/dom/media/platforms/agnostic/OpusDecoder.cpp
@@ -160,16 +160,20 @@ OpusDataDecoder::ProcessDecode(MediaRawD
       mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
       return;
     case DecodeError::DECODE_ERROR:
       mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
       break;
     case DecodeError::DECODE_SUCCESS:
       break;
   }
+
+  if (mTaskQueue->IsEmpty()) {
+    mCallback->InputExhausted();
+  }
 }
 
 OpusDataDecoder::DecodeError
 OpusDataDecoder::DoDecode(MediaRawData* aSample)
 {
   int64_t aDiscardPadding = 0;
   if (aSample->mExtraData) {
     aDiscardPadding = BigEndian::readInt64(aSample->mExtraData->Elements());
--- a/dom/media/platforms/agnostic/TheoraDecoder.cpp
+++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp
@@ -197,16 +197,18 @@ void
 TheoraDecoder::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   if (DoDecode(aSample) == -1) {
     mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+  } else if (mTaskQueue->IsEmpty()) {
+    mCallback->InputExhausted();
   }
 }
 
 nsresult
 TheoraDecoder::Input(MediaRawData* aSample)
 {
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
   mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -116,17 +116,16 @@ VPXDecoder::DoDecode(MediaRawData* aSamp
 
   if (vpx_codec_err_t r = vpx_codec_decode(&mVPX, aSample->Data(), aSample->Size(), nullptr, 0)) {
     LOG("VPX Decode error: %s", vpx_codec_err_to_string(r));
     return -1;
   }
 
   vpx_codec_iter_t  iter = nullptr;
   vpx_image_t      *img;
-  bool didOutput = false;
 
   while ((img = vpx_codec_get_frame(&mVPX, &iter))) {
     NS_ASSERTION(img->fmt == VPX_IMG_FMT_I420 ||
                  img->fmt == VPX_IMG_FMT_I444,
                  "WebM image format not I420 or I444");
 
     // Chroma shifts are rounded down as per the decoding examples in the SDK
     VideoData::YCbCrBuffer b;
@@ -175,33 +174,31 @@ VPXDecoder::DoDecode(MediaRawData* aSamp
 
     if (!v) {
       LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld",
           img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
           mInfo.mImage.width, mInfo.mImage.height);
       return -1;
     }
     mCallback->Output(v);
-    didOutput = true;
-  }
-  if (!didOutput) {
-    mCallback->InputExhausted();
   }
   return 0;
 }
 
 void
 VPXDecoder::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   if (DoDecode(aSample) == -1) {
     mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+  } else if (mTaskQueue->IsEmpty()) {
+    mCallback->InputExhausted();
   }
 }
 
 nsresult
 VPXDecoder::Input(MediaRawData* aSample)
 {
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
   mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
--- a/dom/media/platforms/agnostic/VorbisDecoder.cpp
+++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp
@@ -138,16 +138,18 @@ void
 VorbisDataDecoder::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   if (DoDecode(aSample) == -1) {
     mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+  } else if (mTaskQueue->IsEmpty()) {
+    mCallback->InputExhausted();
   }
 }
 
 int
 VorbisDataDecoder::DoDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
 
--- a/dom/media/platforms/apple/AppleATDecoder.cpp
+++ b/dom/media/platforms/apple/AppleATDecoder.cpp
@@ -209,17 +209,19 @@ AppleATDecoder::SubmitSample(MediaRawDat
     for (size_t i = 0; i < mQueuedSamples.Length(); i++) {
       if (NS_FAILED(DecodeSample(mQueuedSamples[i]))) {
         mQueuedSamples.Clear();
         mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
         return;
       }
     }
     mQueuedSamples.Clear();
-  } else {
+  }
+
+  if (mTaskQueue->IsEmpty()) {
     mCallback->InputExhausted();
   }
 }
 
 nsresult
 AppleATDecoder::DecodeSample(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
@@ -275,19 +277,16 @@ AppleATDecoder::DecodeSample(MediaRawDat
     }
 
     if (rv == kNoMoreDataErr) {
       break;
     }
   } while (true);
 
   if (outputData.IsEmpty()) {
-    // We aren't going to output anything, inform the reader that we need more
-    // data.
-    mCallback->InputExhausted();
     return NS_OK;
   }
 
   size_t numFrames = outputData.Length() / channels;
   int rate = mOutputFormat.mSampleRate;
   media::TimeUnit duration = FramesToTimeUnit(numFrames, rate);
   if (!duration.IsValid()) {
     NS_WARNING("Invalid count of accumulated audio samples");
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -29,19 +29,21 @@ AppleVTDecoder::AppleVTDecoder(const Vid
                                MediaDataDecoderCallback* aCallback,
                                layers::ImageContainer* aImageContainer)
   : mExtraData(aConfig.mExtraData)
   , mCallback(aCallback)
   , mPictureWidth(aConfig.mImage.width)
   , mPictureHeight(aConfig.mImage.height)
   , mDisplayWidth(aConfig.mDisplay.width)
   , mDisplayHeight(aConfig.mDisplay.height)
+  , mQueuedSamples(0)
   , mTaskQueue(aTaskQueue)
   , mMaxRefFrames(mp4_demuxer::H264::ComputeMaxRefFrames(aConfig.mExtraData))
   , mImageContainer(aImageContainer)
+  , mInputIncoming(0)
   , mIsShutDown(false)
 #ifdef MOZ_WIDGET_UIKIT
   , mUseSoftwareImages(true)
 #else
   , mUseSoftwareImages(false)
 #endif
   , mIsFlushing(false)
   , mMonitor("AppleVideoDecoder")
@@ -81,30 +83,34 @@ AppleVTDecoder::Input(MediaRawData* aSam
 
   LOG("mp4 input sample %p pts %lld duration %lld us%s %d bytes",
       aSample,
       aSample->mTime,
       aSample->mDuration,
       aSample->mKeyframe ? " keyframe" : "",
       aSample->Size());
 
+  mInputIncoming++;
+
   mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
     this, &AppleVTDecoder::ProcessDecode, aSample));
   return NS_OK;
 }
 
 nsresult
 AppleVTDecoder::Flush()
 {
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
   mIsFlushing = true;
   nsCOMPtr<nsIRunnable> runnable =
     NewRunnableMethod(this, &AppleVTDecoder::ProcessFlush);
   SyncRunnable::DispatchToThread(mTaskQueue, runnable);
   mIsFlushing = false;
+  // All ProcessDecode() tasks should be done.
+  MOZ_ASSERT(mInputIncoming == 0);
 
   mSeekTargetThreshold.reset();
 
   return NS_OK;
 }
 
 nsresult
 AppleVTDecoder::Drain()
@@ -131,21 +137,28 @@ AppleVTDecoder::Shutdown()
   return NS_OK;
 }
 
 nsresult
 AppleVTDecoder::ProcessDecode(MediaRawData* aSample)
 {
   AssertOnTaskQueueThread();
 
+  mInputIncoming--;
+
   if (mIsFlushing) {
     return NS_OK;
   }
 
   auto rv = DoDecode(aSample);
+  // Ask for more data.
+  if (NS_SUCCEEDED(rv) && !mInputIncoming && mQueuedSamples <= mMaxRefFrames) {
+    LOG("%s task queue empty; requesting more data", GetDescriptionName());
+    mCallback->InputExhausted();
+  }
 
   return rv;
 }
 
 void
 AppleVTDecoder::ProcessShutdown()
 {
   if (mSession) {
@@ -195,25 +208,27 @@ AppleVTDecoder::CreateAppleFrameRef(cons
 
 void
 AppleVTDecoder::DrainReorderedFrames()
 {
   MonitorAutoLock mon(mMonitor);
   while (!mReorderQueue.IsEmpty()) {
     mCallback->Output(mReorderQueue.Pop().get());
   }
+  mQueuedSamples = 0;
 }
 
 void
 AppleVTDecoder::ClearReorderedFrames()
 {
   MonitorAutoLock mon(mMonitor);
   while (!mReorderQueue.IsEmpty()) {
     mReorderQueue.Pop();
   }
+  mQueuedSamples = 0;
 }
 
 void
 AppleVTDecoder::SetSeekThreshold(const media::TimeUnit& aTime)
 {
   LOG("SetSeekThreshold %lld", aTime.ToMicroseconds());
   mSeekTargetThreshold = Some(aTime);
 }
@@ -268,20 +283,26 @@ AppleVTDecoder::OutputFrame(CVPixelBuffe
   LOG("mp4 output frame %lld dts %lld pts %lld duration %lld us%s",
       aFrameRef.byte_offset,
       aFrameRef.decode_timestamp.ToMicroseconds(),
       aFrameRef.composition_timestamp.ToMicroseconds(),
       aFrameRef.duration.ToMicroseconds(),
       aFrameRef.is_sync_point ? " keyframe" : ""
   );
 
+  if (mQueuedSamples > mMaxRefFrames) {
+    // We had stopped requesting more input because we had received too much at
+    // the time. We can ask for more once again.
+    mCallback->InputExhausted();
+  }
+  MOZ_ASSERT(mQueuedSamples);
+  mQueuedSamples--;
+
   if (!aImage) {
-    // Image was dropped by decoder or none return yet.
-    // We need more input to continue.
-    mCallback->InputExhausted();
+    // Image was dropped by decoder.
     return NS_OK;
   }
 
   bool useNullSample = false;
   if (mSeekTargetThreshold.isSome()) {
     if ((aFrameRef.composition_timestamp + aFrameRef.duration) < mSeekTargetThreshold.ref()) {
       useNullSample = true;
     } else {
@@ -384,20 +405,18 @@ AppleVTDecoder::OutputFrame(CVPixelBuffe
     mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
     return NS_ERROR_FAILURE;
   }
 
   // Frames come out in DTS order but we need to output them
   // in composition order.
   MonitorAutoLock mon(mMonitor);
   mReorderQueue.Push(data);
-  if (mReorderQueue.Length() > mMaxRefFrames) {
+  while (mReorderQueue.Length() > mMaxRefFrames) {
     mCallback->Output(mReorderQueue.Pop().get());
-  } else {
-    mCallback->InputExhausted();
   }
   LOG("%llu decoded frames queued",
       static_cast<unsigned long long>(mReorderQueue.Length()));
 
   return NS_OK;
 }
 
 nsresult
@@ -456,16 +475,18 @@ AppleVTDecoder::DoDecode(MediaRawData* a
   }
   CMSampleTimingInfo timestamp = TimingInfoFromSample(aSample);
   rv = CMSampleBufferCreate(kCFAllocatorDefault, block, true, 0, 0, mFormat, 1, 1, &timestamp, 0, NULL, sample.receive());
   if (rv != noErr) {
     NS_ERROR("Couldn't create CMSampleBuffer");
     return NS_ERROR_FAILURE;
   }
 
+  mQueuedSamples++;
+
   VTDecodeFrameFlags decodeFlags =
     kVTDecodeFrame_EnableAsynchronousDecompression;
   rv = VTDecompressionSessionDecodeFrame(mSession,
                                          sample,
                                          decodeFlags,
                                          CreateAppleFrameRef(aSample),
                                          &infoFlags);
   if (rv != noErr && !(infoFlags & kVTDecodeInfo_FrameDropped)) {
--- a/dom/media/platforms/apple/AppleVTDecoder.h
+++ b/dom/media/platforms/apple/AppleVTDecoder.h
@@ -85,27 +85,35 @@ private:
 
   const RefPtr<MediaByteBuffer> mExtraData;
   MediaDataDecoderCallback* mCallback;
   const uint32_t mPictureWidth;
   const uint32_t mPictureHeight;
   const uint32_t mDisplayWidth;
   const uint32_t mDisplayHeight;
 
+  // Number of times a sample was queued via Input(). Will be decreased upon
+  // the decoder's callback being invoked.
+  // This is used to calculate how many frames has been buffered by the decoder.
+  Atomic<uint32_t> mQueuedSamples;
+
   // Method to set up the decompression session.
   nsresult InitializeSession();
   nsresult WaitForAsynchronousFrames();
   CFDictionaryRef CreateDecoderSpecification();
   CFDictionaryRef CreateDecoderExtensions();
   // Method to pass a frame to VideoToolbox for decoding.
   nsresult DoDecode(MediaRawData* aSample);
 
   const RefPtr<TaskQueue> mTaskQueue;
   const uint32_t mMaxRefFrames;
   const RefPtr<layers::ImageContainer> mImageContainer;
+  // Increased when Input is called, and decreased when ProcessFrame runs.
+  // Reaching 0 indicates that there's no pending Input.
+  Atomic<uint32_t> mInputIncoming;
   Atomic<bool> mIsShutDown;
   const bool mUseSoftwareImages;
 
   // Set on reader/decode thread calling Flush() to indicate that output is
   // not required and so input samples on mTaskQueue need not be processed.
   // Cleared on mTaskQueue in ProcessDrain().
   Atomic<bool> mIsFlushing;
   // Protects mReorderQueue.
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
@@ -128,17 +128,16 @@ FFmpegAudioDecoder<LIBAV_VER>::DoDecode(
 
   if (!PrepareFrame()) {
     NS_WARNING("FFmpeg audio decoder failed to allocate frame.");
     return DecodeResult::FATAL_ERROR;
   }
 
   int64_t samplePosition = aSample->mOffset;
   media::TimeUnit pts = media::TimeUnit::FromMicroseconds(aSample->mTime);
-  bool didOutput = false;
 
   while (packet.size > 0) {
     int decoded;
     int bytesConsumed =
       mLib->avcodec_decode_audio4(mCodecContext, mFrame, &decoded, &packet);
 
     if (bytesConsumed < 0) {
       NS_WARNING("FFmpeg audio decoder error.");
@@ -177,29 +176,28 @@ FFmpegAudioDecoder<LIBAV_VER>::DoDecode(
       RefPtr<AudioData> data = new AudioData(samplePosition,
                                              pts.ToMicroseconds(),
                                              duration.ToMicroseconds(),
                                              mFrame->nb_samples,
                                              Move(audio),
                                              numChannels,
                                              samplingRate);
       mCallback->Output(data);
-      didOutput = true;
       pts += duration;
       if (!pts.IsValid()) {
         NS_WARNING("Invalid count of accumulated audio samples");
         return DecodeResult::DECODE_ERROR;
       }
     }
     packet.data += bytesConsumed;
     packet.size -= bytesConsumed;
     samplePosition += bytesConsumed;
   }
 
-  return didOutput ? DecodeResult::DECODE_FRAME : DecodeResult::DECODE_NO_FRAME;
+  return DecodeResult::DECODE_FRAME;
 }
 
 void
 FFmpegAudioDecoder<LIBAV_VER>::ProcessDrain()
 {
   ProcessFlush();
   mCallback->DrainComplete();
 }
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -112,21 +112,20 @@ FFmpegDataDecoder<LIBAV_VER>::ProcessDec
   }
   switch (DoDecode(aSample)) {
     case DecodeResult::DECODE_ERROR:
       mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
       break;
     case DecodeResult::FATAL_ERROR:
       mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
       break;
-    case DecodeResult::DECODE_NO_FRAME:
-      mCallback->InputExhausted();
-      break;
     default:
-      break;
+      if (mTaskQueue->IsEmpty()) {
+        mCallback->InputExhausted();
+      }
   }
 }
 
 nsresult
 FFmpegDataDecoder<LIBAV_VER>::Input(MediaRawData* aSample)
 {
   mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
     this, &FFmpegDataDecoder::ProcessDecode, aSample));
--- a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
@@ -138,25 +138,23 @@ WMFMediaDataDecoder::ProcessDecode(Media
   ProcessOutput();
 }
 
 void
 WMFMediaDataDecoder::ProcessOutput()
 {
   RefPtr<MediaData> output;
   HRESULT hr = S_OK;
-  bool didOutput = false;
   while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output)) &&
          output) {
     mHasSuccessfulOutput = true;
     mCallback->Output(output);
-    didOutput = true;
   }
   if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
-    if (!didOutput) {
+    if (mTaskQueue->IsEmpty()) {
       mCallback->InputExhausted();
     }
   } else if (FAILED(hr)) {
     NS_WARNING("WMFMediaDataDecoder failed to output data");
     mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     if (!mRecordedError) {
       SendTelemetry(hr);
       mRecordedError = true;