Bug 1374068: P1. Drain the decoder when content is changing. r=gerald
authorJean-Yves Avenard <jyavenard@mozilla.com>
Sun, 18 Jun 2017 17:29:23 +0200
changeset 596554 7f312f55453ffd8406f61341e4ec6dd0e49dea74
parent 596553 9b82992bcacb64e533fb2a626a885cc0ec25f77e
child 596555 fd18e49efaaafb4eb3594faf956e0e963b9eba7a
push id64675
push usermak77@bonardo.net
push dateMon, 19 Jun 2017 12:08:05 +0000
reviewersgerald
bugs1374068
milestone56.0a1
Bug 1374068: P1. Drain the decoder when content is changing. r=gerald MozReview-Commit-ID: EUiBIaHzBCO
dom/media/platforms/wrappers/H264Converter.cpp
dom/media/platforms/wrappers/H264Converter.h
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -131,54 +131,60 @@ H264Converter::Decode(MediaRawData* aSam
 }
 
 RefPtr<MediaDataDecoder::FlushPromise>
 H264Converter::Flush()
 {
   mDecodePromiseRequest.DisconnectIfExists();
   mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   mNeedKeyframe = true;
+  mPendingFrames.Clear();
 
   MOZ_RELEASE_ASSERT(mFlushPromise.IsEmpty(), "Previous flush didn't complete");
 
   /*
-      When we detect a change of content in the H264 stream, we first flush the
-    current decoder (1), shut it down (2) create a new decoder and initialize
-    it (3).
-    It is possible possible for H264Converter::Flush to be called during any of
-    those time.
+    When we detect a change of content in the H264 stream, we first drain the
+    current decoder (1), flush (2), shut it down (3) create a new decoder and
+    initialize it (4). It is possible for H264Converter::Flush to be called
+    during any of those times.
     If during (1):
-      - mFlushRequest will not be empty.
+      - mDrainRequest will not be empty.
       - The old decoder can still be used, with the current extradata as stored
         in mCurrentConfig.mExtraData.
 
     If during (2):
+      - mFlushRequest will not be empty.
+      - The old decoder can still be used, with the current extradata as stored
+        in mCurrentConfig.mExtraData.
+
+    If during (3):
       - mShutdownRequest won't be empty.
       - mDecoder is empty.
       - The old decoder is no longer referenced by the H264Converter.
 
-    If during (3):
+    If during (4):
       - mInitPromiseRequest won't be empty.
       - mDecoder is set but not usable yet.
   */
 
-  if (mFlushRequest.Exists() || mShutdownRequest.Exists() ||
-      mInitPromiseRequest.Exists()) {
+  if (mDrainRequest.Exists() || mFlushRequest.Exists() ||
+      mShutdownRequest.Exists() || mInitPromiseRequest.Exists()) {
     // We let the current decoder complete and will resume after.
     return mFlushPromise.Ensure(__func__);
   }
   if (mDecoder) {
     return mDecoder->Flush();
   }
   return FlushPromise::CreateAndResolve(true, __func__);
 }
 
 RefPtr<MediaDataDecoder::DecodePromise>
 H264Converter::Drain()
 {
+  MOZ_RELEASE_ASSERT(!mDrainRequest.Exists());
   mNeedKeyframe = true;
   if (mDecoder) {
     return mDecoder->Drain();
   }
   return DecodePromise::CreateAndResolve(DecodedData(), __func__);
 }
 
 RefPtr<ShutdownPromise>
@@ -348,17 +354,18 @@ H264Converter::CanRecycleDecoder() const
   return MediaPrefs::MediaDecoderCheckRecycling()
          && mDecoder->SupportDecoderRecycling();
 }
 
 void
 H264Converter::DecodeFirstSample(MediaRawData* aSample)
 {
   if (mNeedKeyframe && !aSample->mKeyframe) {
-    mDecodePromise.Resolve(DecodedData(), __func__);
+    mDecodePromise.Resolve(mPendingFrames, __func__);
+    mPendingFrames.Clear();
     return;
   }
 
   if (!*mNeedAVCC
       && !mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample, mNeedKeyframe)) {
     mDecodePromise.Reject(
       MediaResult(NS_ERROR_OUT_OF_MEMORY,
                   RESULT_DETAIL("ConvertSampleToAnnexB")),
@@ -368,17 +375,19 @@ H264Converter::DecodeFirstSample(MediaRa
 
   mNeedKeyframe = false;
 
   RefPtr<H264Converter> self = this;
   mDecoder->Decode(aSample)
     ->Then(AbstractThread::GetCurrent()->AsTaskQueue(), __func__,
            [self, this](const MediaDataDecoder::DecodedData& aResults) {
              mDecodePromiseRequest.Complete();
-             mDecodePromise.Resolve(aResults, __func__);
+             mPendingFrames.AppendElements(aResults);
+             mDecodePromise.Resolve(mPendingFrames, __func__);
+             mPendingFrames.Clear();
            },
            [self, this](const MediaResult& aError) {
              mDecodePromiseRequest.Complete();
              mDecodePromise.Reject(aError, __func__);
            })
     ->Track(mDecodePromiseRequest);
 }
 
@@ -406,30 +415,72 @@ H264Converter::CheckForSPSChange(MediaRa
     }
     extra_data = mOriginalExtraData = aSample->mExtraData;
   }
   if (mp4_demuxer::AnnexB::CompareExtraData(extra_data,
                                             mCurrentConfig.mExtraData)) {
     return NS_OK;
   }
 
-  RefPtr<MediaRawData> sample = aSample;
   MOZ_ASSERT(mCanRecycleDecoder.isSome());
   if (*mCanRecycleDecoder) {
     // Do not recreate the decoder, reuse it.
     UpdateConfigFromExtraData(extra_data);
-    if (!sample->mTrackInfo) {
-      sample->mTrackInfo = new TrackInfoSharedPtr(mCurrentConfig, 0);
+    if (!aSample->mTrackInfo) {
+      aSample->mTrackInfo = new TrackInfoSharedPtr(mCurrentConfig, 0);
     }
     mNeedKeyframe = true;
     return NS_OK;
   }
 
-  // The SPS has changed, signal to flush the current decoder and create a
-  // new one.
+  // The SPS has changed, signal to drain the current decoder and once done
+  // create a new one.
+  DrainThenFlushDecoder(aSample);
+  return NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER;
+}
+
+void
+H264Converter::DrainThenFlushDecoder(MediaRawData* aPendingSample)
+{
+  RefPtr<MediaRawData> sample = aPendingSample;
+  RefPtr<H264Converter> self = this;
+  mDecoder->Drain()
+    ->Then(AbstractThread::GetCurrent()->AsTaskQueue(),
+           __func__,
+           [self, sample, this](const MediaDataDecoder::DecodedData& aResults) {
+             mDrainRequest.Complete();
+             if (!mFlushPromise.IsEmpty()) {
+               // A Flush is pending, abort the current operation.
+               mFlushPromise.Resolve(true, __func__);
+               return;
+             }
+             if (aResults.Length() > 0) {
+               mPendingFrames.AppendElements(aResults);
+               DrainThenFlushDecoder(sample);
+               return;
+             }
+             // We've completed the draining operation, we can now flush the
+             // decoder.
+             FlushThenShutdownDecoder(sample);
+           },
+           [self, this](const MediaResult& aError) {
+             mDrainRequest.Complete();
+             if (!mFlushPromise.IsEmpty()) {
+               // A Flush is pending, abort the current operation.
+               mFlushPromise.Reject(aError, __func__);
+               return;
+             }
+             mDecodePromise.Reject(aError, __func__);
+           })
+    ->Track(mDrainRequest);
+}
+
+void H264Converter::FlushThenShutdownDecoder(MediaRawData* aPendingSample)
+{
+  RefPtr<MediaRawData> sample = aPendingSample;
   RefPtr<H264Converter> self = this;
   mDecoder->Flush()
     ->Then(AbstractThread::GetCurrent()->AsTaskQueue(),
            __func__,
            [self, sample, this]() {
              mFlushRequest.Complete();
 
              if (!mFlushPromise.IsEmpty()) {
@@ -461,25 +512,24 @@ H264Converter::CheckForSPSChange(MediaRa
                         mDecodePromise.Reject(rv, __func__);
                         return;
                       },
                       [] { MOZ_CRASH("Can't reach here'"); })
                ->Track(mShutdownRequest);
            },
            [self, this](const MediaResult& aError) {
              mFlushRequest.Complete();
-               if (!mFlushPromise.IsEmpty()) {
-                 // A Flush is pending, abort the current operation.
-                 mFlushPromise.Reject(aError, __func__);
-                 return;
-               }
-               mDecodePromise.Reject(aError, __func__);
+             if (!mFlushPromise.IsEmpty()) {
+               // A Flush is pending, abort the current operation.
+               mFlushPromise.Reject(aError, __func__);
+               return;
+             }
+             mDecodePromise.Reject(aError, __func__);
            })
     ->Track(mFlushRequest);
-  return NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER;
 }
 
 void
 H264Converter::UpdateConfigFromExtraData(MediaByteBuffer* aExtraData)
 {
   mp4_demuxer::SPSData spsdata;
   if (mp4_demuxer::H264::DecodeSPSFromExtraData(aExtraData, spsdata)
       && spsdata.pic_width > 0
--- a/dom/media/platforms/wrappers/H264Converter.h
+++ b/dom/media/platforms/wrappers/H264Converter.h
@@ -72,31 +72,35 @@ private:
   void UpdateConfigFromExtraData(MediaByteBuffer* aExtraData);
 
   void OnDecoderInitDone(const TrackType aTrackType);
   void OnDecoderInitFailed(const MediaResult& aError);
 
   bool CanRecycleDecoder() const;
 
   void DecodeFirstSample(MediaRawData* aSample);
+  void DrainThenFlushDecoder(MediaRawData* aPendingSample);
+  void FlushThenShutdownDecoder(MediaRawData* aPendingSample);
 
   RefPtr<PlatformDecoderModule> mPDM;
   const VideoInfo mOriginalConfig;
   VideoInfo mCurrentConfig;
   // Current out of band extra data (as found in metadata's VideoInfo).
   RefPtr<MediaByteBuffer> mOriginalExtraData;
   RefPtr<layers::KnowsCompositor> mKnowsCompositor;
   RefPtr<layers::ImageContainer> mImageContainer;
   const RefPtr<TaskQueue> mTaskQueue;
   RefPtr<MediaRawData> mPendingSample;
   RefPtr<MediaDataDecoder> mDecoder;
   MozPromiseRequestHolder<InitPromise> mInitPromiseRequest;
   MozPromiseRequestHolder<DecodePromise> mDecodePromiseRequest;
   MozPromiseHolder<DecodePromise> mDecodePromise;
   MozPromiseRequestHolder<FlushPromise> mFlushRequest;
+  MediaDataDecoder::DecodedData mPendingFrames;
+  MozPromiseRequestHolder<DecodePromise> mDrainRequest;
   MozPromiseRequestHolder<ShutdownPromise> mShutdownRequest;
   RefPtr<ShutdownPromise> mShutdownPromise;
   MozPromiseHolder<FlushPromise> mFlushPromise;
 
   RefPtr<GMPCrashHelper> mGMPCrashHelper;
   Maybe<bool> mNeedAVCC;
   nsresult mLastError;
   bool mNeedKeyframe = true;