Bug 1336431: P5. Re-add ConfigurationChanged API. r?JamesCheng draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Mon, 13 Feb 2017 11:15:54 +0100
changeset 483576 f1fbdc6929133049348faa1bc9dcd08d9850019e
parent 483575 485ccfe9c084b63c2d496fabbec8de8000967c4c
child 483577 02bfd674c98b2e63f6c23f3a1714852c9d84f16b
push id45346
push userbmo:jyavenard@mozilla.com
push dateTue, 14 Feb 2017 15:11:15 +0000
reviewersJamesCheng
bugs1336431
milestone54.0a1
Bug 1336431: P5. Re-add ConfigurationChanged API. r?JamesCheng We need to inform the MediaDataDecoder that a new sample configuration has been detected. To do so, and to ensure that samples are easily matched to a given configuration, we first drain the decoder prior feeding a new frame. MozReview-Commit-ID: Hye251CF21i
dom/media/MediaFormatReader.cpp
dom/media/platforms/PlatformDecoderModule.h
dom/media/platforms/wrappers/H264Converter.cpp
dom/media/platforms/wrappers/H264Converter.h
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -285,16 +285,20 @@ public:
   void SetSeekThreshold(const media::TimeUnit& aTime) override
   {
     mDecoder->SetSeekThreshold(aTime);
   }
   bool SupportDecoderRecycling() const override
   {
     return mDecoder->SupportDecoderRecycling();
   }
+  void ConfigurationChanged(const TrackInfo& aConfig) override
+  {
+    mDecoder->ConfigurationChanged(aConfig);
+  }
   RefPtr<ShutdownPromise> Shutdown() override
   {
     RefPtr<MediaDataDecoder> decoder = mDecoder.forget();
     RefPtr<Token> token = mToken.forget();
     return decoder->Shutdown()->Then(
       AbstractThread::GetCurrent(), __func__,
       [token]() {},
       [token]() { MOZ_RELEASE_ASSERT(false, "Can't reach here"); });
@@ -1756,49 +1760,50 @@ MediaFormatReader::HandleDemuxedSamples(
   LOGV("Giving %s input to decoder", TrackTypeToStr(aTrack));
 
   // Decode all our demuxed frames.
   while (decoder.mQueuedSamples.Length()) {
     RefPtr<MediaRawData> sample = decoder.mQueuedSamples[0];
     RefPtr<TrackInfoSharedPtr> info = sample->mTrackInfo;
 
     if (info && decoder.mLastStreamSourceID != info->GetID()) {
-      bool supportRecycling = MediaPrefs::MediaDecoderCheckRecycling()
-                              && decoder.mDecoder->SupportDecoderRecycling();
       if (decoder.mNextStreamSourceID.isNothing()
           || decoder.mNextStreamSourceID.ref() != info->GetID()) {
-        if (!supportRecycling) {
-          LOG("%s stream id has changed from:%d to:%d, draining decoder.",
-            TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
-            info->GetID());
-          decoder.RequestDrain();
-          decoder.mNextStreamSourceID = Some(info->GetID());
-          ScheduleUpdate(aTrack);
-          return;
-        }
+        LOG("%s stream id has changed from:%d to:%d, draining decoder.",
+          TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
+          info->GetID());
+        decoder.RequestDrain();
+        decoder.mNextStreamSourceID = Some(info->GetID());
+        ScheduleUpdate(aTrack);
+        return;
       }
 
       LOG("%s stream id has changed from:%d to:%d.",
           TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
           info->GetID());
       decoder.mLastStreamSourceID = info->GetID();
       decoder.mNextStreamSourceID.reset();
-      decoder.mInfo = info;
-
-      if (!supportRecycling) {
+
+      if (!MediaPrefs::MediaDecoderCheckRecycling()
+          || !decoder.mDecoder->SupportDecoderRecycling()) {
         LOG("Decoder does not support recycling, recreate decoder.");
         // If flushing is required, it will clear our array of queued samples.
         // So make a copy now.
         nsTArray<RefPtr<MediaRawData>> samples{ Move(decoder.mQueuedSamples) };
         ShutdownDecoder(aTrack);
         if (sample->mKeyframe) {
           decoder.mQueuedSamples.AppendElements(Move(samples));
         }
+      } else if (decoder.mInfo && *decoder.mInfo != *info) {
+        const TrackInfo* trackInfo = *info;
+        decoder.mDecoder->ConfigurationChanged(*trackInfo);
       }
 
+      decoder.mInfo = info;
+
       if (sample->mKeyframe) {
         ScheduleUpdate(aTrack);
       } else {
         TimeInterval time =
           TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
                        TimeUnit::FromMicroseconds(sample->GetEndTime()));
         InternalSeekTarget seekTarget =
           decoder.mTimeThreshold.refOr(InternalSeekTarget(time, false));
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -278,17 +278,16 @@ public:
   // Called from the state machine task queue or main thread. Decoder needs to
   // decide whether or not hardware acceleration is supported after creating.
   // It doesn't need to call Init() before calling this function.
   virtual bool IsHardwareAccelerated(nsACString& aFailureReason) const
   {
     return false;
   }
 
-
   // Return the name of the MediaDataDecoder, only used for decoding.
   // Only return a static const string, as the information may be accessed
   // in a non thread-safe fashion.
   virtual const char* GetDescriptionName() const = 0;
 
   // Set a hint of seek target time to decoder. Decoder will drop any decoded
   // data which pts is smaller than this value. This threshold needs to be clear
   // after reset decoder.
@@ -297,13 +296,28 @@ public:
   // Note: it should be called before Input() or after Flush().
   virtual void SetSeekThreshold(const media::TimeUnit& aTime) {}
 
   // When playing adaptive playback, recreating an Android video decoder will
   // cause the transition not smooth during resolution change.
   // Reuse the decoder if the decoder support recycling.
   // Currently, only Android video decoder will return true.
   virtual bool SupportDecoderRecycling() const { return false; }
+
+  // ConfigurationChanged will be called to inform the video or audio decoder
+  // that the format of the next input sample is about to change.
+  // If video decoder, aConfig will be a VideoInfo object.
+  // If audio decoder, aConfig will be a AudioInfo object.
+  // It is not safe to store a reference to this object and the decoder must
+  // make a copy.
+  // Care should be taken as ConfigurationChanged is called on the reader's
+  // taskqueue.
+  virtual void ConfigurationChanged(const TrackInfo& aConfig)
+  {
+    MOZ_ASSERT(SupportDecoderRecycling(),
+               "Can only work with a decoder supporting recycling.");
+  }
+
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -14,16 +14,17 @@
 #include "mp4_demuxer/H264.h"
 
 namespace mozilla
 {
 
 H264Converter::H264Converter(PlatformDecoderModule* aPDM,
                              const CreateDecoderParams& aParams)
   : mPDM(aPDM)
+  , mOriginalConfig(aParams.VideoConfig())
   , mCurrentConfig(aParams.VideoConfig())
   , mKnowsCompositor(aParams.mKnowsCompositor)
   , mImageContainer(aParams.mImageContainer)
   , mTaskQueue(aParams.mTaskQueue)
   , mDecoder(nullptr)
   , mGMPCrashHelper(aParams.mCrashHelper)
   , mNeedAVCC(aPDM->DecoderNeedsConversion(aParams.mConfig)
       == PlatformDecoderModule::ConversionRequired::kNeedAVCC)
@@ -194,31 +195,32 @@ H264Converter::CreateDecoder(DecoderDoct
     }
   } else {
     // SPS was invalid.
     mLastError = NS_ERROR_FAILURE;
     return NS_ERROR_FAILURE;
   }
 
   mDecoder = mPDM->CreateVideoDecoder({
-    mCurrentConfig,
+    mUseOriginalConfig ? mOriginalConfig : mCurrentConfig,
     mTaskQueue,
     aDiagnostics,
     mImageContainer,
     mKnowsCompositor,
     mGMPCrashHelper,
     mType,
     mOnWaitingForKeyEvent
   });
 
   if (!mDecoder) {
     mLastError = NS_ERROR_FAILURE;
     return NS_ERROR_FAILURE;
   }
 
+  mUseOriginalConfig = false;
   mNeedKeyframe = true;
 
   return NS_OK;
 }
 
 nsresult
 H264Converter::CreateDecoderAndInit(MediaRawData* aSample)
 {
@@ -245,74 +247,106 @@ H264Converter::CreateDecoderAndInit(Medi
   return rv;
 }
 
 void
 H264Converter::OnDecoderInitDone(const TrackType aTrackType)
 {
   mInitPromiseRequest.Complete();
   RefPtr<MediaRawData> sample = mPendingSample.forget();
-  if (mNeedKeyframe && !sample->mKeyframe) {
+  DecodeFirstSample(sample);
+}
+
+void
+H264Converter::OnDecoderInitFailed(const MediaResult& aError)
+{
+  mInitPromiseRequest.Complete();
+  mDecodePromise.Reject(
+    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                RESULT_DETAIL("Unable to initialize H264 decoder")),
+    __func__);
+}
+
+void
+H264Converter::DecodeFirstSample(MediaRawData* aSample)
+{
+  if (mNeedKeyframe && !aSample->mKeyframe) {
     mDecodePromise.Resolve(DecodedData(), __func__);
     return;
   }
   mNeedKeyframe = false;
   if (!mNeedAVCC
-      && !mp4_demuxer::AnnexB::ConvertSampleToAnnexB(sample, mNeedKeyframe)) {
+      && !mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample, mNeedKeyframe)) {
     mDecodePromise.Reject(
       MediaResult(NS_ERROR_OUT_OF_MEMORY,
                   RESULT_DETAIL("ConvertSampleToAnnexB")),
       __func__);
     return;
   }
+  if (CanRecycleDecoder()) {
+    mDecoder->ConfigurationChanged(mCurrentConfig);
+  }
   RefPtr<H264Converter> self = this;
-  mDecoder->Decode(sample)
+  mDecoder->Decode(aSample)
     ->Then(AbstractThread::GetCurrent()->AsTaskQueue(), __func__,
            [self, this](const MediaDataDecoder::DecodedData& aResults) {
              mDecodePromiseRequest.Complete();
              mDecodePromise.Resolve(aResults, __func__);
            },
            [self, this](const MediaResult& aError) {
              mDecodePromiseRequest.Complete();
              mDecodePromise.Reject(aError, __func__);
            })
     ->Track(mDecodePromiseRequest);
 }
 
-void
-H264Converter::OnDecoderInitFailed(const MediaResult& aError)
-{
-  mInitPromiseRequest.Complete();
-  mDecodePromise.Reject(
-    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
-                RESULT_DETAIL("Unable to initialize H264 decoder")),
-    __func__);
-}
-
 nsresult
 H264Converter::CheckForSPSChange(MediaRawData* aSample)
 {
   RefPtr<MediaByteBuffer> extra_data =
     mp4_demuxer::AnnexB::ExtractExtraData(aSample);
   if (!mp4_demuxer::AnnexB::HasSPS(extra_data)
       || mp4_demuxer::AnnexB::CompareExtraData(extra_data,
                                                mCurrentConfig.mExtraData)) {
         return NS_OK;
       }
 
-  if (MediaPrefs::MediaDecoderCheckRecycling()
-      && mDecoder->SupportDecoderRecycling()) {
+  mPendingSample = aSample;
+
+  if (CanRecycleDecoder()) {
     // Do not recreate the decoder, reuse it.
     UpdateConfigFromExtraData(extra_data);
+    // Ideally we would want to drain the decoder instead of flushing it.
+    // However the draining operation requires calling Drain and looping several
+    // times which isn't possible from within the H264Converter. So instead we
+    // flush the decoder. In practice, this is a no-op as SPS change will only
+    // be used with MSE. And with MSE, the MediaFormatReader would have drained
+    // the decoder already.
+    RefPtr<H264Converter> self = this;
+    mDecoder->Flush()
+      ->Then(AbstractThread::GetCurrent()->AsTaskQueue(),
+             __func__,
+             [self, this]() {
+               mFlushRequest.Complete();
+               DecodeFirstSample(mPendingSample);
+               mPendingSample = nullptr;
+             },
+             [self, this](const MediaResult& aError) {
+               mFlushRequest.Complete();
+               mDecodePromise.Reject(aError, __func__);
+             })
+      ->Track(mFlushRequest);
     mNeedKeyframe = true;
-    return NS_OK;
+    // This is not really initializing the decoder, but it will do as it
+    // indicates an operation is pending.
+    return NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER;
   }
+
   // The SPS has changed, signal to flush the current decoder and create a
   // new one.
-  mPendingSample = aSample;
   RefPtr<H264Converter> self = this;
   mDecoder->Flush()
     ->Then(AbstractThread::GetCurrent()->AsTaskQueue(),
            __func__,
            [self, this]() {
              mFlushRequest.Complete();
              mShutdownPromise = Shutdown();
              mShutdownPromise
--- a/dom/media/platforms/wrappers/H264Converter.h
+++ b/dom/media/platforms/wrappers/H264Converter.h
@@ -42,32 +42,47 @@ public:
   void SetSeekThreshold(const media::TimeUnit& aTime) override;
   bool SupportDecoderRecycling() const override
   {
     if (mDecoder) {
       return mDecoder->SupportDecoderRecycling();
     }
     return false;
   }
-
+  void ConfigurationChanged(const TrackInfo& aConfig) override
+  {
+    if (mDecoder && mDecoder->SupportDecoderRecycling()) {
+      mDecoder->ConfigurationChanged(aConfig);
+    }
+  }
   nsresult GetLastError() const { return mLastError; }
 
 private:
   // Will create the required MediaDataDecoder if need AVCC and we have a SPS NAL.
   // Returns NS_ERROR_FAILURE if error is permanent and can't be recovered and
   // will set mError accordingly.
   nsresult CreateDecoder(DecoderDoctorDiagnostics* aDiagnostics);
   nsresult CreateDecoderAndInit(MediaRawData* aSample);
   nsresult CheckForSPSChange(MediaRawData* aSample);
   void UpdateConfigFromExtraData(MediaByteBuffer* aExtraData);
 
   void OnDecoderInitDone(const TrackType aTrackType);
   void OnDecoderInitFailed(const MediaResult& aError);
 
+  bool CanRecycleDecoder() const
+  {
+    MOZ_ASSERT(mDecoder);
+    return MediaPrefs::MediaDecoderCheckRecycling()
+           && mDecoder->SupportDecoderRecycling();
+  }
+
+  void DecodeFirstSample(MediaRawData* aSample);
+
   RefPtr<PlatformDecoderModule> mPDM;
+  const VideoInfo mOriginalConfig;
   VideoInfo mCurrentConfig;
   RefPtr<layers::KnowsCompositor> mKnowsCompositor;
   RefPtr<layers::ImageContainer> mImageContainer;
   const RefPtr<TaskQueue> mTaskQueue;
   RefPtr<MediaRawData> mPendingSample;
   RefPtr<MediaDataDecoder> mDecoder;
   MozPromiseRequestHolder<InitPromise> mInitPromiseRequest;
   MozPromiseRequestHolder<DecodePromise> mDecodePromiseRequest;
@@ -75,15 +90,17 @@ private:
   MozPromiseRequestHolder<FlushPromise> mFlushRequest;
   MozPromiseRequestHolder<ShutdownPromise> mShutdownRequest;
   RefPtr<ShutdownPromise> mShutdownPromise;
 
   RefPtr<GMPCrashHelper> mGMPCrashHelper;
   bool mNeedAVCC;
   nsresult mLastError;
   bool mNeedKeyframe = true;
+  // Set to true once a decoder has been created.
+  bool mUseOriginalConfig = true;
   const TrackInfo::TrackType mType;
   MediaEventProducer<TrackInfo::TrackType>* const mOnWaitingForKeyEvent;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_H264Converter_h