Bug 1332234 - P1. Add support for AMD's VP9 hardware decoder. r=cpearce draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 04 Aug 2017 18:33:20 +0200
changeset 648178 42df425823e9debac0c292d1c2f357d599abab5d
parent 641268 5c6c7f0f79eadece0f5907cf3244aa1362d58f30
child 726735 7769c4ba9c4f2872a30dc6a2bd00f683c46a326c
push id74655
push userbmo:jyavenard@mozilla.com
push dateThu, 17 Aug 2017 10:13:04 +0000
reviewerscpearce
bugs1332234
milestone56.0a1
Bug 1332234 - P1. Add support for AMD's VP9 hardware decoder. r=cpearce This enables VP9 decoding on Windows with AMD graphic adapters supporting it. The AMD VP9 MFT only works 720 and more pixels high. The system will attempt the following decoding configuration: 1- AMD VP9 MFT 2- Microsoft VP9 MFT (only if DXVA is enabled) 3- FFmpeg ffvp9 software decoder MozReview-Commit-ID: IP2eHZEQ7Tj
dom/media/platforms/wmf/MFTDecoder.cpp
dom/media/platforms/wmf/MFTDecoder.h
dom/media/platforms/wmf/WMFUtils.cpp
dom/media/platforms/wmf/WMFUtils.h
dom/media/platforms/wmf/WMFVideoMFTManager.cpp
dom/media/platforms/wmf/WMFVideoMFTManager.h
--- a/dom/media/platforms/wmf/MFTDecoder.cpp
+++ b/dom/media/platforms/wmf/MFTDecoder.cpp
@@ -26,24 +26,64 @@ MFTDecoder::~MFTDecoder()
 HRESULT
 MFTDecoder::Create(const GUID& aMFTClsID)
 {
   // Create the IMFTransform to do the decoding.
   HRESULT hr;
   hr = CoCreateInstance(aMFTClsID,
                         nullptr,
                         CLSCTX_INPROC_SERVER,
-                        IID_IMFTransform,
-                        reinterpret_cast<void**>(static_cast<IMFTransform**>(
+                        IID_PPV_ARGS(static_cast<IMFTransform**>(
                           getter_AddRefs(mDecoder))));
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   return S_OK;
 }
 
+// Helper function to create a COM object instance from a DLL.
+static HRESULT
+CreateCOMObjectFromDll(HMODULE aDLL,
+                       const CLSID& aCLSId,
+                       const IID& aIID,
+                       void** aObject)
+{
+  if (!aDLL || !aObject) {
+    return E_INVALIDARG;
+  }
+  using GetClassObject =
+    HRESULT(WINAPI*)(const CLSID& clsid, const IID& iid, void** object);
+
+  GetClassObject getClassObject = reinterpret_cast<GetClassObject>(
+    GetProcAddress(aDLL, "DllGetClassObject"));
+  NS_ENSURE_TRUE(getClassObject, E_FAIL);
+
+  RefPtr<IClassFactory> factory;
+  HRESULT hr =
+    getClassObject(aCLSId,
+                   IID_PPV_ARGS(static_cast<IClassFactory**>(
+                     getter_AddRefs(factory))));
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+  hr = factory->CreateInstance(NULL, aIID, aObject);
+  return hr;
+}
+
+HRESULT
+MFTDecoder::Create(HMODULE aDecoderDLL, const GUID& aMFTClsID)
+{
+  // Create the IMFTransform to do the decoding.
+  HRESULT hr =
+    CreateCOMObjectFromDll(aDecoderDLL, aMFTClsID,
+                           IID_PPV_ARGS(static_cast<IMFTransform**>(
+                             getter_AddRefs(mDecoder))));
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
 HRESULT
 MFTDecoder::SetMediaTypes(IMFMediaType* aInputType,
                           IMFMediaType* aOutputType,
                           ConfigureOutputCallback aCallback,
                           void* aData)
 {
   mOutputType = aOutputType;
 
--- a/dom/media/platforms/wmf/MFTDecoder.h
+++ b/dom/media/platforms/wmf/MFTDecoder.h
@@ -24,16 +24,17 @@ public:
   MFTDecoder();
 
   // Creates the MFT. First thing to do as part of setup.
   //
   // Params:
   //  - aMFTClsID the clsid used by CoCreateInstance to instantiate the
   //    decoder MFT.
   HRESULT Create(const GUID& aMFTClsID);
+  HRESULT Create(HMODULE aDecoderDLL, const GUID& aMFTClsID);
 
   // Sets the input and output media types. Call after Init().
   //
   // Params:
   //  - aInputType needs at least major and minor types set.
   //  - aOutputType needs at least major and minor types set.
   //    This is used to select the matching output type out
   //    of all the available output types of the MFT.
--- a/dom/media/platforms/wmf/WMFUtils.cpp
+++ b/dom/media/platforms/wmf/WMFUtils.cpp
@@ -8,16 +8,19 @@
 #include "VideoUtils.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Logging.h"
 #include "mozilla/RefPtr.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "nsWindowsHelpers.h"
+#include "prenv.h"
+#include <Shlobj.h>
+#include <Shlwapi.h>
 #include <initguid.h>
 #include <stdint.h>
 
 #ifdef WMF_MUST_DEFINE_AAC_MFT_CLSID
 // Some SDK versions don't define the AAC decoder CLSID.
 // {32D186A7-218F-4C75-8876-DD77273A8999}
 DEFINE_GUID(CLSID_CMSAACDecMFT, 0x32D186A7, 0x218F, 0x4C75, 0x88, 0x76, 0xDD, 0x77, 0x27, 0x3A, 0x89, 0x99);
 #endif
@@ -140,16 +143,30 @@ GetPictureRegion(IMFMediaType* aMediaTyp
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
   NS_ENSURE_TRUE(width <= MAX_VIDEO_WIDTH, E_FAIL);
   NS_ENSURE_TRUE(height <= MAX_VIDEO_HEIGHT, E_FAIL);
 
   aOutPictureRegion = nsIntRect(0, 0, width, height);
   return S_OK;
 }
 
+nsString
+GetProgramW6432Path()
+{
+  char* programPath = PR_GetEnvSecure("ProgramW6432");
+  if (!programPath) {
+    programPath = PR_GetEnvSecure("ProgramFiles");
+  }
+
+  if (!programPath) {
+    return NS_LITERAL_STRING("C:\\Program Files");
+  }
+  return NS_ConvertUTF8toUTF16(programPath);
+}
+
 namespace wmf {
 
 static const wchar_t* sDLLs[] = {
   L"mfplat.dll",
   L"mf.dll",
   L"dxva2.dll",
   L"evr.dll",
 };
--- a/dom/media/platforms/wmf/WMFUtils.h
+++ b/dom/media/platforms/wmf/WMFUtils.h
@@ -56,11 +56,14 @@ media::TimeUnit GetSampleDuration(IMFSam
 media::TimeUnit GetSampleTime(IMFSample* aSample);
 
 inline bool
 IsFlagSet(DWORD flags, DWORD pattern)
 {
   return (flags & pattern) == pattern;
 }
 
+// Will return %ProgramW6432% value as per:
+// https://msdn.microsoft.com/library/windows/desktop/aa384274.aspx
+nsString GetProgramW6432Path();
 } // namespace mozilla
 
 #endif
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -39,16 +39,38 @@
 #define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
 
 using mozilla::layers::Image;
 using mozilla::layers::IMFYCbCrImage;
 using mozilla::layers::LayerManager;
 using mozilla::layers::LayersBackend;
 using mozilla::media::TimeUnit;
 
+// AMD
+// Path is appended on to the %ProgramW6432% base path.
+const wchar_t kAMDVPXDecoderDLLPath[] =
+  L"\\Common Files\\ATI Technologies\\Multimedia\\";
+
+const wchar_t kAMDVP9DecoderDLLName[] =
+#if defined(ARCH_CPU_X86)
+  L"amf-mft-decvp9-decoder32.dll";
+#elif defined(ARCH_CPU_X86_64)
+  L"amf-mft-decvp9-decoder64.dll";
+#else
+#error Unsupported Windows CPU Architecture
+#endif
+
+const CLSID CLSID_AMDWebmMfVp9Dec =
+{
+  0x2d2d728a,
+  0x67d6,
+  0x48ab,
+  { 0x89, 0xfb, 0xa6, 0xec, 0x65, 0x55, 0x49, 0x70 }
+};
+
 #if WINVER_MAXVER < 0x0A00
 // Windows 10+ SDK has VP80 and VP90 defines
 const GUID MFVideoFormat_VP80 =
 {
   0x30385056,
   0x0000,
   0x0010,
   {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
@@ -111,16 +133,17 @@ WMFVideoMFTManager::WMFVideoMFTManager(
                             layers::ImageContainer* aImageContainer,
                             bool aDXVAEnabled)
   : mVideoInfo(aConfig)
   , mImageSize(aConfig.mImage)
   , mVideoStride(0)
   , mImageContainer(aImageContainer)
   , mDXVAEnabled(aDXVAEnabled)
   , mKnowsCompositor(aKnowsCompositor)
+  , mAMDVP9InUse(false)
   // mVideoStride, mVideoWidth, mVideoHeight, mUseHwAccel are initialized in
   // Init().
 {
   MOZ_COUNT_CTOR(WMFVideoMFTManager);
 
   // Need additional checks/params to check vp8/vp9
   if (MP4Decoder::IsH264(aConfig.mMimeType)) {
     mStreamType = H264;
@@ -488,24 +511,53 @@ WMFVideoMFTManager::ValidateVideoInfo()
     LogToBrowserConsole(NS_LITERAL_STRING(
       "Can't decode H.264 stream with width or height less than 48 pixels."));
     mIsValid = false;
   }
 
   return mIsValid;
 }
 
+already_AddRefed<MFTDecoder>
+WMFVideoMFTManager::LoadAMDVP9Decoder()
+{
+  MOZ_ASSERT(mStreamType == VP9);
+
+  RefPtr<MFTDecoder> decoder = new MFTDecoder();
+  // Check if we can load the AMD VP9 decoder.
+  nsString path = GetProgramW6432Path();
+  path.Append(kAMDVPXDecoderDLLPath);
+  path.Append(kAMDVP9DecoderDLLName);
+  HMODULE decoderDLL = ::LoadLibraryEx(path.get(), NULL,
+                                       LOAD_WITH_ALTERED_SEARCH_PATH);
+  if (!decoderDLL) {
+    return nullptr;
+  }
+  HRESULT hr = decoder->Create(decoderDLL, CLSID_AMDWebmMfVp9Dec);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+  return decoder.forget();
+}
+
 bool
 WMFVideoMFTManager::Init()
 {
   if (!ValidateVideoInfo()) {
     return false;
   }
 
   bool success = InitInternal();
+  if (!success && mAMDVP9InUse) {
+    // Something failed with the AMD VP9 decoder; attempt again defaulting back
+    // to Microsoft MFT.
+    mCheckForAMDDecoder = false;
+    if (mDXVA2Manager) {
+      DeleteOnMainThread(mDXVA2Manager);
+    }
+    success = InitInternal();
+  }
 
   if (success && mDXVA2Manager) {
     // If we had some failures but eventually made it work,
     // make sure we preserve the messages.
     if (mDXVA2Manager->IsD3D11()) {
       mDXVAFailureReason.Append(NS_LITERAL_CSTRING("Using D3D11 API"));
     } else {
       mDXVAFailureReason.Append(NS_LITERAL_CSTRING("Using D3D9 API"));
@@ -516,20 +568,31 @@ WMFVideoMFTManager::Init()
 }
 
 bool
 WMFVideoMFTManager::InitInternal()
 {
   mUseHwAccel = false; // default value; changed if D3D setup succeeds.
   bool useDxva = InitializeDXVA();
 
-  RefPtr<MFTDecoder> decoder(new MFTDecoder());
+  RefPtr<MFTDecoder> decoder;
 
-  HRESULT hr = decoder->Create(GetMFTGUID());
-  NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+  HRESULT hr;
+  if (mStreamType == VP9 && useDxva && mCheckForAMDDecoder) {
+    if ((decoder = LoadAMDVP9Decoder())) {
+      mAMDVP9InUse = true;
+    }
+  }
+  if (!decoder) {
+    mCheckForAMDDecoder = false;
+    mAMDVP9InUse = false;
+    decoder = new MFTDecoder();
+    hr = decoder->Create(GetMFTGUID());
+    NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+  }
 
   RefPtr<IMFAttributes> attr(decoder->GetAttributes());
   UINT32 aware = 0;
   if (attr) {
     attr->GetUINT32(MF_SA_D3D_AWARE, &aware);
     attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads,
       WMFDecoderModule::GetNumDecoderThreads());
     if (gfxPrefs::PDMWMFLowLatencyEnabled()) {
@@ -578,17 +641,18 @@ WMFVideoMFTManager::InitInternal()
   NS_ENSURE_TRUE(SUCCEEDED(hr), false);
 
   RefPtr<IMFMediaType> outputType;
   hr = mDecoder->GetOutputMediaType(outputType);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   if (mUseHwAccel && !CanUseDXVA(outputType)) {
     mDXVAEnabled = false;
-    // DXVA initialization actually failed, re-do initialisation.
+    // DXVA initialization with current decoder actually failed,
+    // re-do initialization.
     return InitInternal();
   }
 
   LOG("Video Decoder initialized, Using DXVA: %s",
       (mUseHwAccel ? "Yes" : "No"));
 
   if (mDXVA2Manager) {
     hr = mDXVA2Manager->ConfigureForSize(mVideoInfo.ImageRect().width,
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.h
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(WMFVideoMFTManager_h_)
 #define WMFVideoMFTManager_h_
 
 #include "MFTDecoder.h"
 #include "WMF.h"
 #include "WMFMediaDataDecoder.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/RefPtr.h"
 #include "nsAutoPtr.h"
 #include "nsRect.h"
 
 namespace mozilla {
 
 class DXVA2Manager;
 
@@ -37,18 +38,21 @@ public:
 
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
 
   TrackInfo::TrackType GetType() override { return TrackInfo::kVideoTrack; }
 
   const char* GetDescriptionName() const override
   {
     nsCString failureReason;
-    return IsHardwareAccelerated(failureReason) ? "wmf hardware video decoder"
-                                                : "wmf software video decoder";
+    return IsHardwareAccelerated(failureReason)
+      ? mAMDVP9InUse
+        ? "AMD VP9 hardware video decoder"
+        : "wmf hardware video decoder"
+      : "wmf software video decoder";
   }
 
   void Flush() override
   {
     MFTManager::Flush();
     mDraining = false;
     mSamplesCount = 0;
   }
@@ -80,16 +84,18 @@ private:
   HRESULT CreateD3DVideoFrame(IMFSample* aSample,
                               int64_t aStreamOffset,
                               VideoData** aOutVideoData);
 
   HRESULT SetDecoderMediaTypes();
 
   bool CanUseDXVA(IMFMediaType* aType);
 
+  already_AddRefed<MFTDecoder> LoadAMDVP9Decoder();
+
   // Video frame geometry.
   const VideoInfo mVideoInfo;
   const nsIntSize mImageSize;
   uint32_t mVideoStride;
 
   RefPtr<layers::ImageContainer> mImageContainer;
   RefPtr<layers::KnowsCompositor> mKnowsCompositor;
   nsAutoPtr<DXVA2Manager> mDXVA2Manager;
@@ -117,13 +123,15 @@ private:
   const GUID& GetMFTGUID();
   const GUID& GetMediaSubtypeGUID();
 
   uint32_t mNullOutputCount = 0;
   bool mGotValidOutputAfterNullOutput = false;
   bool mGotExcessiveNullOutput = false;
   bool mIsValid = true;
   bool mIMFUsable = false;
+  bool mCheckForAMDDecoder = true;
+  Atomic<bool> mAMDVP9InUse;
 };
 
 } // namespace mozilla
 
 #endif // WMFVideoMFTManager_h_