Bug 1657521 - P5. Add VP9 HW decoder support on macOS 11 (Big Sur). r=jolin
authorJean-Yves Avenard <jyavenard@mozilla.com>
Thu, 13 Aug 2020 02:16:19 +0000
changeset 544499 791287b954142d17ae82c82ce9a4cf992d36eeea
parent 544498 df297d346db2729d7c9b3a951321e6f49b59586e
child 544500 d6c1978bcabea9bbadd17764a249a504577e3ca9
push id124072
push userjyavenard@mozilla.com
push dateThu, 13 Aug 2020 04:30:00 +0000
treeherderautoland@d6c1978bcabe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjolin
bugs1657521
milestone81.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 1657521 - P5. Add VP9 HW decoder support on macOS 11 (Big Sur). r=jolin To create a VP9 decoder, the VideoToolbox requires a vppC atom similar to how the H264 one requires an avcC one. That information is typically not available in the webm container and is found in the VP9 bytestream with each keyframe. In order to minimise the extent of the changes, we move the task of retrieving the vpcC content in the MediaChangeMonitor as it already performs a similar task in order to detect if the format has changed. The VPXChangeMonitor will now only instantiate a VP9 decoder once a keyframe is seen. Differential Revision: https://phabricator.services.mozilla.com/D86544
dom/media/platforms/apple/AppleDecoderModule.cpp
dom/media/platforms/apple/AppleDecoderModule.h
dom/media/platforms/apple/AppleVTDecoder.cpp
dom/media/platforms/apple/AppleVTDecoder.h
dom/media/platforms/moz.build
dom/media/platforms/wrappers/MediaChangeMonitor.cpp
dom/media/platforms/wrappers/MediaChangeMonitor.h
--- a/dom/media/platforms/apple/AppleDecoderModule.cpp
+++ b/dom/media/platforms/apple/AppleDecoderModule.cpp
@@ -1,64 +1,166 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "AppleDecoderModule.h"
+
+#include <dlfcn.h>
+
 #include "AppleATDecoder.h"
-#include "AppleDecoderModule.h"
 #include "AppleVTDecoder.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Logging.h"
 #include "mozilla/gfx/gfxVars.h"
 
+extern "C" {
+// Only exists from MacOS 11
+extern void VTRegisterSupplementalVideoDecoderIfAvailable(
+    CMVideoCodecType codecType) __attribute__((weak_import));
+extern Boolean VTIsHardwareDecodeSupported(CMVideoCodecType codecType)
+    __attribute__((weak_import));
+}
+
 namespace mozilla {
 
 bool AppleDecoderModule::sInitialized = false;
 bool AppleDecoderModule::sCanUseHardwareVideoDecoder = true;
+bool AppleDecoderModule::sCanUseVP9Decoder = false;
 
 AppleDecoderModule::AppleDecoderModule() {}
 
 AppleDecoderModule::~AppleDecoderModule() {}
 
 /* static */
 void AppleDecoderModule::Init() {
   if (sInitialized) {
     return;
   }
 
   sCanUseHardwareVideoDecoder = gfx::gfxVars::CanUseHardwareVideoDecoding();
 
   sInitialized = true;
+  if (RegisterSupplementalVP9Decoder()) {
+    sCanUseVP9Decoder = CanCreateVP9Decoder();
+  }
 }
 
 nsresult AppleDecoderModule::Startup() {
   if (!sInitialized) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 already_AddRefed<MediaDataDecoder> AppleDecoderModule::CreateVideoDecoder(
     const CreateDecoderParams& aParams) {
-  RefPtr<MediaDataDecoder> decoder = new AppleVTDecoder(
-      aParams.VideoConfig(), aParams.mTaskQueue, aParams.mImageContainer,
-      aParams.mOptions, aParams.mKnowsCompositor);
+  RefPtr<MediaDataDecoder> decoder;
+  if (IsVideoSupported(aParams.VideoConfig(), aParams.mOptions)) {
+    decoder = new AppleVTDecoder(aParams.VideoConfig(), aParams.mTaskQueue,
+                                 aParams.mImageContainer, aParams.mOptions,
+                                 aParams.mKnowsCompositor);
+  }
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder> AppleDecoderModule::CreateAudioDecoder(
     const CreateDecoderParams& aParams) {
   RefPtr<MediaDataDecoder> decoder =
       new AppleATDecoder(aParams.AudioConfig(), aParams.mTaskQueue);
   return decoder.forget();
 }
 
 bool AppleDecoderModule::SupportsMimeType(
     const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const {
   return aMimeType.EqualsLiteral("audio/mpeg") ||
          aMimeType.EqualsLiteral("audio/mp4a-latm") ||
-         aMimeType.EqualsLiteral("video/mp4") ||
-         aMimeType.EqualsLiteral("video/avc");
+         MP4Decoder::IsH264(aMimeType) || VPXDecoder::IsVP9(aMimeType);
+}
+
+bool AppleDecoderModule::Supports(
+    const TrackInfo& aTrackInfo, DecoderDoctorDiagnostics* aDiagnostics) const {
+  if (aTrackInfo.IsAudio()) {
+    return SupportsMimeType(aTrackInfo.mMimeType, aDiagnostics);
+  }
+  return aTrackInfo.GetAsVideoInfo() &&
+         IsVideoSupported(*aTrackInfo.GetAsVideoInfo());
+}
+
+bool AppleDecoderModule::IsVideoSupported(
+    const VideoInfo& aConfig,
+    const CreateDecoderParams::OptionSet& aOptions) const {
+  if (MP4Decoder::IsH264(aConfig.mMimeType)) {
+    return true;
+  }
+  if (!VPXDecoder::IsVP9(aConfig.mMimeType) || !sCanUseVP9Decoder ||
+      aOptions.contains(
+          CreateDecoderParams::Option::HardwareDecoderNotAllowed)) {
+    return false;
+  }
+  if (aConfig.HasAlpha()) {
+    return false;
+  }
+
+  // HW VP9 decoder only supports 8 or 10 bit color.
+  if (aConfig.mColorDepth != gfx::ColorDepth::COLOR_8 &&
+      aConfig.mColorDepth != gfx::ColorDepth::COLOR_10) {
+    return false;
+  }
+
+  // See if we have a vpcC box, and check further constraints.
+  // HW VP9 Decoder supports Profile 0 & 2 (YUV420)
+  if (aConfig.mExtraData && aConfig.mExtraData->Length() < 5) {
+    return true;  // Assume it's okay.
+  }
+  int profile = aConfig.mExtraData->ElementAt(4);
+
+  if (profile != 0 && profile != 2) {
+    return false;
+  }
+
+  return true;
+}
+
+/* static */
+bool AppleDecoderModule::CanCreateVP9Decoder() {
+  // We must wrap the code within __builtin_available to avoid compilation
+  // warning as VTIsHardwareDecodeSupported is only available from macOS 10.13.
+  if (__builtin_available(macOS 10.13, *)) {
+    // Only use VP9 decoder if it's hardware accelerated.
+    if (!sCanUseHardwareVideoDecoder || !VTIsHardwareDecodeSupported ||
+        !VTIsHardwareDecodeSupported(kCMVideoCodecType_VP9)) {
+      return false;
+    }
+
+    VideoInfo info(1920, 1080);
+    VPXDecoder::GetVPCCBox(info.mExtraData, VPXDecoder::VPXStreamInfo());
+
+    RefPtr<AppleVTDecoder> decoder =
+        new AppleVTDecoder(info, nullptr, nullptr, {}, nullptr);
+    nsAutoCString reason;
+    MediaResult rv = decoder->InitializeSession();
+    bool isHardwareAccelerated = decoder->IsHardwareAccelerated(reason);
+    decoder->Shutdown();
+
+    return NS_SUCCEEDED(rv) && isHardwareAccelerated;
+  }
+
+  return false;
+}
+
+/* static */
+bool AppleDecoderModule::RegisterSupplementalVP9Decoder() {
+  static bool sRegisterIfAvailable = []() {
+    if (VTRegisterSupplementalVideoDecoderIfAvailable) {
+      VTRegisterSupplementalVideoDecoderIfAvailable(kCMVideoCodecType_VP9);
+      return true;
+    }
+    return false;
+  }();
+  return sRegisterIfAvailable;
 }
 
 }  // namespace mozilla
--- a/dom/media/platforms/apple/AppleDecoderModule.h
+++ b/dom/media/platforms/apple/AppleDecoderModule.h
@@ -24,19 +24,32 @@ class AppleDecoderModule : public Platfo
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder> CreateAudioDecoder(
       const CreateDecoderParams& aParams) override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
+  bool Supports(const TrackInfo& aTrackInfo,
+                DecoderDoctorDiagnostics* aDiagnostics) const override;
+
   static void Init();
 
   static bool sCanUseHardwareVideoDecoder;
+  static bool sCanUseVP9Decoder;
+
+  static constexpr int kCMVideoCodecType_VP9{'vp09'};
 
  private:
   static bool sInitialized;
+  bool IsVideoSupported(const VideoInfo& aConfig,
+                        const CreateDecoderParams::OptionSet& aOptions =
+                            CreateDecoderParams::OptionSet()) const;
+  // Enable VP9 HW decoder.
+  static bool RegisterSupplementalVP9Decoder();
+  // Return true if a dummy hardware VP9 decoder could be created.
+  static bool CanCreateVP9Decoder();
 };
 
 }  // namespace mozilla
 
 #endif  // mozilla_AppleDecoderModule_h
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -6,25 +6,26 @@
 
 #include "AppleVTDecoder.h"
 
 #include <CoreVideo/CVPixelBufferIOSurface.h>
 #include <IOSurface/IOSurface.h>
 
 #include "AppleDecoderModule.h"
 #include "AppleUtils.h"
+#include "H264.h"
+#include "MP4Decoder.h"
 #include "MacIOSurfaceImage.h"
 #include "MediaData.h"
-#include "mozilla/ArrayUtils.h"
-#include "H264.h"
-#include "nsThreadUtils.h"
-#include "mozilla/Logging.h"
+#include "VPXDecoder.h"
 #include "VideoUtils.h"
 #include "gfxPlatform.h"
-#include "MacIOSurfaceImage.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Logging.h"
+#include "nsThreadUtils.h"
 
 #define LOG(...) DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, __VA_ARGS__)
 #define LOGEX(_this, ...) \
   DDMOZ_LOGEX(_this, sPDMLog, mozilla::LogLevel::Debug, __VA_ARGS__)
 
 namespace mozilla {
 
 using namespace layers;
@@ -37,20 +38,27 @@ AppleVTDecoder::AppleVTDecoder(const Vid
       mPictureWidth(aConfig.mImage.width),
       mPictureHeight(aConfig.mImage.height),
       mDisplayWidth(aConfig.mDisplay.width),
       mDisplayHeight(aConfig.mDisplay.height),
       mColorSpace(aConfig.mColorSpace == gfx::YUVColorSpace::UNKNOWN
                       ? DefaultColorSpace({mPictureWidth, mPictureHeight})
                       : aConfig.mColorSpace),
       mColorRange(aConfig.mColorRange),
+      mStreamType(MP4Decoder::IsH264(aConfig.mMimeType)
+                      ? StreamType::H264
+                      : VPXDecoder::IsVP9(aConfig.mMimeType)
+                            ? StreamType::VP9
+                            : StreamType::Unknown),
       mTaskQueue(aTaskQueue),
-      mMaxRefFrames(aOptions.contains(CreateDecoderParams::Option::LowLatency)
-                        ? 0
-                        : H264::ComputeMaxRefFrames(aConfig.mExtraData)),
+      mMaxRefFrames(
+          mStreamType != StreamType::H264 ||
+                  aOptions.contains(CreateDecoderParams::Option::LowLatency)
+              ? 0
+              : H264::ComputeMaxRefFrames(aConfig.mExtraData)),
       mImageContainer(aImageContainer),
       mKnowsCompositor(aKnowsCompositor)
 #ifdef MOZ_WIDGET_UIKIT
       ,
       mUseSoftwareImages(true)
 #else
       ,
       mUseSoftwareImages(false)
@@ -59,19 +67,20 @@ AppleVTDecoder::AppleVTDecoder(const Vid
       mIsFlushing(false),
       mMonitor("AppleVTDecoder"),
       mPromise(&mMonitor),  // To ensure our PromiseHolder is only ever accessed
                             // with the monitor held.
       mFormat(nullptr),
       mSession(nullptr),
       mIsHardwareAccelerated(false) {
   MOZ_COUNT_CTOR(AppleVTDecoder);
+  MOZ_ASSERT(mStreamType != StreamType::Unknown);
   // TODO: Verify aConfig.mime_type.
-  LOG("Creating AppleVTDecoder for %dx%d h.264 video", mDisplayWidth,
-      mDisplayHeight);
+  LOG("Creating AppleVTDecoder for %dx%d %s video", mDisplayWidth,
+      mDisplayHeight, mStreamType == StreamType::H264 ? "H.264" : "VP9");
 }
 
 AppleVTDecoder::~AppleVTDecoder() { MOZ_COUNT_DTOR(AppleVTDecoder); }
 
 RefPtr<MediaDataDecoder::InitPromise> AppleVTDecoder::Init() {
   MediaResult rv = InitializeSession();
 
   if (NS_SUCCEEDED(rv)) {
@@ -491,19 +500,22 @@ nsresult AppleVTDecoder::WaitForAsynchro
   return NS_OK;
 }
 
 MediaResult AppleVTDecoder::InitializeSession() {
   OSStatus rv;
 
   AutoCFRelease<CFDictionaryRef> extensions = CreateDecoderExtensions();
 
-  rv = CMVideoFormatDescriptionCreate(kCFAllocatorDefault,
-                                      kCMVideoCodecType_H264, mPictureWidth,
-                                      mPictureHeight, extensions, &mFormat);
+  rv = CMVideoFormatDescriptionCreate(
+      kCFAllocatorDefault,
+      mStreamType == StreamType::H264
+          ? kCMVideoCodecType_H264
+          : CMVideoCodecType(AppleDecoderModule::kCMVideoCodecType_VP9),
+      mPictureWidth, mPictureHeight, extensions, &mFormat);
   if (rv != noErr) {
     return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                        RESULT_DETAIL("Couldn't create format description!"));
   }
 
   // Contruct video decoder selection spec.
   AutoCFRelease<CFDictionaryRef> spec = CreateDecoderSpecification();
 
@@ -534,21 +546,22 @@ MediaResult AppleVTDecoder::InitializeSe
   mIsHardwareAccelerated = rv == noErr && isUsingHW == kCFBooleanTrue;
   LOG("AppleVTDecoder: %s hardware accelerated decoding",
       mIsHardwareAccelerated ? "using" : "not using");
 
   return NS_OK;
 }
 
 CFDictionaryRef AppleVTDecoder::CreateDecoderExtensions() {
-  AutoCFRelease<CFDataRef> avc_data = CFDataCreate(
+  AutoCFRelease<CFDataRef> data = CFDataCreate(
       kCFAllocatorDefault, mExtraData->Elements(), mExtraData->Length());
 
-  const void* atomsKey[] = {CFSTR("avcC")};
-  const void* atomsValue[] = {avc_data};
+  const void* atomsKey[1];
+  atomsKey[0] = mStreamType == StreamType::H264 ? CFSTR("avcC") : CFSTR("vpcC");
+  const void* atomsValue[] = {data};
   static_assert(ArrayLength(atomsKey) == ArrayLength(atomsValue),
                 "Non matching keys/values array size");
 
   AutoCFRelease<CFDictionaryRef> atoms = CFDictionaryCreate(
       kCFAllocatorDefault, atomsKey, atomsValue, ArrayLength(atomsKey),
       &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 
   const void* extensionKeys[] = {
--- a/dom/media/platforms/apple/AppleVTDecoder.h
+++ b/dom/media/platforms/apple/AppleVTDecoder.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_AppleVTDecoder_h
 #define mozilla_AppleVTDecoder_h
 
 #include <CoreFoundation/CFDictionary.h>  // For CFDictionaryRef
 #include <CoreMedia/CoreMedia.h>          // For CMVideoFormatDescriptionRef
 #include <VideoToolbox/VideoToolbox.h>    // For VTDecompressionSessionRef
+#include "AppleDecoderModule.h"
 #include "PlatformDecoderModule.h"
 #include "ReorderQueue.h"
 #include "TimeUnits.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/gfx/Types.h"
 
 namespace mozilla {
 
@@ -65,16 +66,17 @@ class AppleVTDecoder : public MediaDataD
   }
 
   // Access from the taskqueue and the decoder's thread.
   // OutputFrame is thread-safe.
   void OutputFrame(CVPixelBufferRef aImage, AppleFrameRef aFrameRef);
   void OnDecodeError(OSStatus aError);
 
  private:
+  friend class AppleDecoderModule;  // To access InitializeSession.
   virtual ~AppleVTDecoder();
   RefPtr<FlushPromise> ProcessFlush();
   RefPtr<DecodePromise> ProcessDrain();
   void ProcessShutdown();
   void ProcessDecode(MediaRawData* aSample);
   void MaybeResolveBufferedFrames();
 
   void AssertOnTaskQueueThread() {
@@ -93,16 +95,18 @@ class AppleVTDecoder : public MediaDataD
   const gfx::ColorRange mColorRange;
 
   // Method to set up the decompression session.
   MediaResult InitializeSession();
   nsresult WaitForAsynchronousFrames();
   CFDictionaryRef CreateDecoderSpecification();
   CFDictionaryRef CreateDecoderExtensions();
 
+  enum class StreamType { Unknown, H264, VP9 };
+  const StreamType mStreamType;
   const RefPtr<TaskQueue> mTaskQueue;
   const uint32_t mMaxRefFrames;
   const RefPtr<layers::ImageContainer> mImageContainer;
   const RefPtr<layers::KnowsCompositor> mKnowsCompositor;
   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.
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -104,16 +104,23 @@ if CONFIG['MOZ_APPLEMEDIA']:
   ]
   LOCAL_INCLUDES += [
       '/media/libyuv/libyuv/include',
   ]
   OS_LIBS += [
       '-framework AudioToolbox',
       '-framework CoreMedia',
       '-framework VideoToolbox',
+      # For some unknown reason, the documented method of using weak_import
+      # attribute doesn't work with VideoToolbox's functions.
+      # We want to lazily load _VTRegisterSupplementalVideoDecoderIfAvailable
+      # symbol as it's only available in macOS 11 and later.
+      '-Wl,-U,_VTRegisterSupplementalVideoDecoderIfAvailable',
+      # Same for VTIsHardwareDecodeSupported available from macOS 10.13.
+      '-Wl,-U,_VTIsHardwareDecodeSupported',
   ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     EXPORTS += [
         'android/AndroidDecoderModule.h',
         'android/AndroidEncoderModule.h',
--- a/dom/media/platforms/wrappers/MediaChangeMonitor.cpp
+++ b/dom/media/platforms/wrappers/MediaChangeMonitor.cpp
@@ -151,17 +151,22 @@ class VPXChangeMonitor : public MediaCha
  public:
   explicit VPXChangeMonitor(const VideoInfo& aInfo)
       : mCurrentConfig(aInfo),
         mCodec(VPXDecoder::IsVP8(aInfo.mMimeType) ? VPXDecoder::Codec::VP8
                                                   : VPXDecoder::Codec::VP9) {
     mTrackInfo = new TrackInfoSharedPtr(mCurrentConfig, mStreamID++);
   }
 
-  bool CanBeInstantiated() const override { return true; }
+  bool CanBeInstantiated() const override {
+    // We want to see at least one sample before we create a decoder so that we
+    // can create the vpcC content on mCurrentConfig.mExtraData.
+    return mCodec == VPXDecoder::Codec::VP8 || mInfo ||
+           mCurrentConfig.mCrypto.IsEncrypted();
+  }
 
   MediaResult CheckForChange(MediaRawData* aSample) override {
     // Don't look at encrypted content.
     if (aSample->mCrypto.IsEncrypted()) {
       return NS_OK;
     }
     // For both VP8 and VP9, we only look for resolution changes
     // on keyframes. Other resolution changes are invalid.
@@ -189,16 +194,19 @@ class VPXChangeMonitor : public MediaCha
     }
     mInfo = Some(info);
     // For the first frame, we leave the mDisplay/mImage untouched as they
     // contain aspect ratio (AR) information set by the demuxer.
     // The AR data isn't found in the VP8/VP9 bytestream.
     mCurrentConfig.mColorDepth = gfx::ColorDepthForBitDepth(info.mBitDepth);
     mCurrentConfig.mColorSpace = info.ColorSpace();
     mCurrentConfig.mColorRange = info.ColorRange();
+    if (mCodec == VPXDecoder::Codec::VP9) {
+      VPXDecoder::GetVPCCBox(mCurrentConfig.mExtraData, info);
+    }
     mTrackInfo = new TrackInfoSharedPtr(mCurrentConfig, mStreamID++);
 
     return rv;
   }
 
   const TrackInfo& Config() const override { return mCurrentConfig; }
 
   MediaResult PrepareSample(MediaDataDecoder::ConversionRequired aConversion,
@@ -254,16 +262,17 @@ RefPtr<MediaDataDecoder::InitPromise> Me
       mTaskQueue, __func__, [self, this]() -> RefPtr<InitPromise> {
         if (mDecoder) {
           RefPtr<InitPromise> p = mInitPromise.Ensure(__func__);
           mDecoder->Init()
               ->Then(mTaskQueue, __func__,
                      [self, this](InitPromise::ResolveOrRejectValue&& aValue) {
                        mInitPromiseRequest.Complete();
                        if (aValue.IsResolve()) {
+                         mDecoderInitialized = true;
                          mConversionRequired =
                              Some(mDecoder->NeedsConversion());
                          mCanRecycleDecoder = Some(CanRecycleDecoder());
                        }
                        return mInitPromise.ResolveOrRejectIfExists(
                            std::move(aValue), __func__);
                      })
               ->Track(mInitPromiseRequest);
@@ -362,29 +371,29 @@ RefPtr<MediaDataDecoder::FlushPromise> M
     */
 
     if (mDrainRequest.Exists() || mFlushRequest.Exists() ||
         mShutdownRequest.Exists() || mInitPromiseRequest.Exists()) {
       // We let the current decoder complete and will resume after.
       RefPtr<FlushPromise> p = mFlushPromise.Ensure(__func__);
       return p;
     }
-    if (mDecoder) {
+    if (mDecoder && mDecoderInitialized) {
       return mDecoder->Flush();
     }
     return FlushPromise::CreateAndResolve(true, __func__);
   });
 }
 
 RefPtr<MediaDataDecoder::DecodePromise> MediaChangeMonitor::Drain() {
   RefPtr<MediaChangeMonitor> self = this;
   return InvokeAsync(mTaskQueue, __func__, [self, this]() {
     MOZ_RELEASE_ASSERT(!mDrainRequest.Exists());
     mNeedKeyframe = true;
-    if (mDecoder) {
+    if (mDecoder && mDecoderInitialized) {
       return mDecoder->Drain();
     }
     return DecodePromise::CreateAndResolve(DecodedData(), __func__);
   });
 }
 
 RefPtr<ShutdownPromise> MediaChangeMonitor::Shutdown() {
   RefPtr<MediaChangeMonitor> self = this;
@@ -468,16 +477,17 @@ MediaResult MediaChangeMonitor::CreateDe
     } else {
       return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                          RESULT_DETAIL("Unable to create decoder"));
     }
   }
 
   DDLINKCHILD("decoder", mDecoder.get());
 
+  mDecoderInitialized = false;
   mNeedKeyframe = true;
 
   return NS_OK;
 }
 
 MediaResult MediaChangeMonitor::CreateDecoderAndInit(MediaRawData* aSample) {
   AssertOnTaskQueue();
 
@@ -491,16 +501,17 @@ MediaResult MediaChangeMonitor::CreateDe
   if (NS_SUCCEEDED(rv)) {
     RefPtr<MediaChangeMonitor> self = this;
     RefPtr<MediaRawData> sample = aSample;
     mDecoder->Init()
         ->Then(
             mTaskQueue, __func__,
             [self, sample, this](const TrackType aTrackType) {
               mInitPromiseRequest.Complete();
+              mDecoderInitialized = true;
               mConversionRequired = Some(mDecoder->NeedsConversion());
               mCanRecycleDecoder = Some(CanRecycleDecoder());
 
               if (!mFlushPromise.IsEmpty()) {
                 // A Flush is pending, abort the current operation.
                 mFlushPromise.Resolve(true, __func__);
                 return;
               }
@@ -599,16 +610,17 @@ MediaResult MediaChangeMonitor::CheckFor
   // The content 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 MediaChangeMonitor::DrainThenFlushDecoder(MediaRawData* aPendingSample) {
   AssertOnTaskQueue();
+  MOZ_ASSERT(mDecoderInitialized);
 
   RefPtr<MediaRawData> sample = aPendingSample;
   RefPtr<MediaChangeMonitor> self = this;
   mDecoder->Drain()
       ->Then(
           mTaskQueue, __func__,
           [self, sample, this](MediaDataDecoder::DecodedData&& aResults) {
             mDrainRequest.Complete();
@@ -636,16 +648,17 @@ void MediaChangeMonitor::DrainThenFlushD
             mDecodePromise.Reject(aError, __func__);
           })
       ->Track(mDrainRequest);
 }
 
 void MediaChangeMonitor::FlushThenShutdownDecoder(
     MediaRawData* aPendingSample) {
   AssertOnTaskQueue();
+  MOZ_ASSERT(mDecoderInitialized);
 
   RefPtr<MediaRawData> sample = aPendingSample;
   RefPtr<MediaChangeMonitor> self = this;
   mDecoder->Flush()
       ->Then(
           mTaskQueue, __func__,
           [self, sample, this]() {
             mFlushRequest.Complete();
--- a/dom/media/platforms/wrappers/MediaChangeMonitor.h
+++ b/dom/media/platforms/wrappers/MediaChangeMonitor.h
@@ -118,13 +118,14 @@ class MediaChangeMonitor : public MediaD
   const TrackInfo::TrackType mType;
   MediaEventProducer<TrackInfo::TrackType>* const mOnWaitingForKeyEvent;
   const CreateDecoderParams::OptionSet mDecoderOptions;
   const CreateDecoderParams::VideoFrameRate mRate;
   Maybe<bool> mCanRecycleDecoder;
   Maybe<MediaDataDecoder::ConversionRequired> mConversionRequired;
   // Used for debugging purposes only
   Atomic<bool> mInConstructor;
+  bool mDecoderInitialized = false;
 };
 
 }  // namespace mozilla
 
 #endif  // mozilla_H264Converter_h