summaryrefslogtreecommitdiffstats
path: root/dom/media/platforms/wmf
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/media/platforms/wmf
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/platforms/wmf')
-rw-r--r--dom/media/platforms/wmf/DXVA2Manager.cpp1245
-rw-r--r--dom/media/platforms/wmf/DXVA2Manager.h79
-rw-r--r--dom/media/platforms/wmf/MFTDecoder.cpp344
-rw-r--r--dom/media/platforms/wmf/MFTDecoder.h114
-rw-r--r--dom/media/platforms/wmf/WMF.h88
-rw-r--r--dom/media/platforms/wmf/WMFAudioMFTManager.cpp316
-rw-r--r--dom/media/platforms/wmf/WMFAudioMFTManager.h58
-rw-r--r--dom/media/platforms/wmf/WMFDecoderModule.cpp342
-rw-r--r--dom/media/platforms/wmf/WMFDecoderModule.h57
-rw-r--r--dom/media/platforms/wmf/WMFMediaDataDecoder.cpp257
-rw-r--r--dom/media/platforms/wmf/WMFMediaDataDecoder.h161
-rw-r--r--dom/media/platforms/wmf/WMFUtils.cpp323
-rw-r--r--dom/media/platforms/wmf/WMFUtils.h63
-rw-r--r--dom/media/platforms/wmf/WMFVideoMFTManager.cpp843
-rw-r--r--dom/media/platforms/wmf/WMFVideoMFTManager.h120
-rw-r--r--dom/media/platforms/wmf/moz.build37
16 files changed, 4447 insertions, 0 deletions
diff --git a/dom/media/platforms/wmf/DXVA2Manager.cpp b/dom/media/platforms/wmf/DXVA2Manager.cpp
new file mode 100644
index 0000000000..db68b377c5
--- /dev/null
+++ b/dom/media/platforms/wmf/DXVA2Manager.cpp
@@ -0,0 +1,1245 @@
+/* -*- 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 "DXVA2Manager.h"
+#include <d3d11.h>
+#include "D3D9SurfaceImage.h"
+#include "DriverCrashGuard.h"
+#include "GfxDriverInfo.h"
+#include "ImageContainer.h"
+#include "MFTDecoder.h"
+#include "MediaTelemetryConstants.h"
+#include "VideoUtils.h"
+#include "WMFUtils.h"
+#include "gfxCrashReporterUtils.h"
+#include "gfxWindowsPlatform.h"
+#include "mfapi.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/layers/D3D11ShareHandleImage.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/TextureD3D11.h"
+#include "mozilla/layers/TextureForwarder.h"
+#include "mozilla/mscom/EnsureMTA.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+
+const CLSID CLSID_VideoProcessorMFT = {
+ 0x88753b26,
+ 0x5b24,
+ 0x49bd,
+ {0xb2, 0xe7, 0xc, 0x44, 0x5c, 0x78, 0xc9, 0x82}};
+
+const GUID MF_XVP_PLAYBACK_MODE = {
+ 0x3c5d293f,
+ 0xad67,
+ 0x4e29,
+ {0xaf, 0x12, 0xcf, 0x3e, 0x23, 0x8a, 0xcc, 0xe9}};
+
+DEFINE_GUID(MF_LOW_LATENCY, 0x9c27891a, 0xed7a, 0x40e1, 0x88, 0xe8, 0xb2, 0x27,
+ 0x27, 0xa0, 0x24, 0xee);
+
+// R600, R700, Evergreen and Cayman AMD cards. These support DXVA via UVD3 or
+// earlier, and don't handle 1080p60 well.
+static const DWORD sAMDPreUVD4[] = {
+ // clang-format off
+ 0x9400, 0x9401, 0x9402, 0x9403, 0x9405, 0x940a, 0x940b, 0x940f, 0x94c0, 0x94c1, 0x94c3, 0x94c4, 0x94c5,
+ 0x94c6, 0x94c7, 0x94c8, 0x94c9, 0x94cb, 0x94cc, 0x94cd, 0x9580, 0x9581, 0x9583, 0x9586, 0x9587, 0x9588,
+ 0x9589, 0x958a, 0x958b, 0x958c, 0x958d, 0x958e, 0x958f, 0x9500, 0x9501, 0x9504, 0x9505, 0x9506, 0x9507,
+ 0x9508, 0x9509, 0x950f, 0x9511, 0x9515, 0x9517, 0x9519, 0x95c0, 0x95c2, 0x95c4, 0x95c5, 0x95c6, 0x95c7,
+ 0x95c9, 0x95cc, 0x95cd, 0x95ce, 0x95cf, 0x9590, 0x9591, 0x9593, 0x9595, 0x9596, 0x9597, 0x9598, 0x9599,
+ 0x959b, 0x9610, 0x9611, 0x9612, 0x9613, 0x9614, 0x9615, 0x9616, 0x9710, 0x9711, 0x9712, 0x9713, 0x9714,
+ 0x9715, 0x9440, 0x9441, 0x9442, 0x9443, 0x9444, 0x9446, 0x944a, 0x944b, 0x944c, 0x944e, 0x9450, 0x9452,
+ 0x9456, 0x945a, 0x945b, 0x945e, 0x9460, 0x9462, 0x946a, 0x946b, 0x947a, 0x947b, 0x9480, 0x9487, 0x9488,
+ 0x9489, 0x948a, 0x948f, 0x9490, 0x9491, 0x9495, 0x9498, 0x949c, 0x949e, 0x949f, 0x9540, 0x9541, 0x9542,
+ 0x954e, 0x954f, 0x9552, 0x9553, 0x9555, 0x9557, 0x955f, 0x94a0, 0x94a1, 0x94a3, 0x94b1, 0x94b3, 0x94b4,
+ 0x94b5, 0x94b9, 0x68e0, 0x68e1, 0x68e4, 0x68e5, 0x68e8, 0x68e9, 0x68f1, 0x68f2, 0x68f8, 0x68f9, 0x68fa,
+ 0x68fe, 0x68c0, 0x68c1, 0x68c7, 0x68c8, 0x68c9, 0x68d8, 0x68d9, 0x68da, 0x68de, 0x68a0, 0x68a1, 0x68a8,
+ 0x68a9, 0x68b0, 0x68b8, 0x68b9, 0x68ba, 0x68be, 0x68bf, 0x6880, 0x6888, 0x6889, 0x688a, 0x688c, 0x688d,
+ 0x6898, 0x6899, 0x689b, 0x689e, 0x689c, 0x689d, 0x9802, 0x9803, 0x9804, 0x9805, 0x9806, 0x9807, 0x9808,
+ 0x9809, 0x980a, 0x9640, 0x9641, 0x9647, 0x9648, 0x964a, 0x964b, 0x964c, 0x964e, 0x964f, 0x9642, 0x9643,
+ 0x9644, 0x9645, 0x9649, 0x6720, 0x6721, 0x6722, 0x6723, 0x6724, 0x6725, 0x6726, 0x6727, 0x6728, 0x6729,
+ 0x6738, 0x6739, 0x673e, 0x6740, 0x6741, 0x6742, 0x6743, 0x6744, 0x6745, 0x6746, 0x6747, 0x6748, 0x6749,
+ 0x674a, 0x6750, 0x6751, 0x6758, 0x6759, 0x675b, 0x675d, 0x675f, 0x6840, 0x6841, 0x6842, 0x6843, 0x6849,
+ 0x6850, 0x6858, 0x6859, 0x6760, 0x6761, 0x6762, 0x6763, 0x6764, 0x6765, 0x6766, 0x6767, 0x6768, 0x6770,
+ 0x6771, 0x6772, 0x6778, 0x6779, 0x677b, 0x6700, 0x6701, 0x6702, 0x6703, 0x6704, 0x6705, 0x6706, 0x6707,
+ 0x6708, 0x6709, 0x6718, 0x6719, 0x671c, 0x671d, 0x671f, 0x9900, 0x9901, 0x9903, 0x9904, 0x9905, 0x9906,
+ 0x9907, 0x9908, 0x9909, 0x990a, 0x990b, 0x990c, 0x990d, 0x990e, 0x990f, 0x9910, 0x9913, 0x9917, 0x9918,
+ 0x9919, 0x9990, 0x9991, 0x9992, 0x9993, 0x9994, 0x9995, 0x9996, 0x9997, 0x9998, 0x9999, 0x999a, 0x999b,
+ 0x999c, 0x999d, 0x99a0, 0x99a2, 0x99a4
+ // clang-format on
+};
+
+// List of NVidia Telsa GPU known to have broken NV12 rendering.
+static const DWORD sNVIDIABrokenNV12[] = {
+ // clang-format off
+ 0x0191, 0x0193, 0x0194, 0x0197, 0x019d, 0x019e, // G80
+ 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409, // G84
+ 0x040a, 0x040b, 0x040c, 0x040d, 0x040e, 0x040f,
+ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, // G86
+ 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f,
+ 0x0410, 0x0600, 0x0601, 0x0602, 0x0603, 0x0604, 0x0605, 0x0606, 0x0607, 0x0608, // G92
+ 0x0609, 0x060a, 0x060b, 0x060c, 0x060f, 0x0610, 0x0611, 0x0612, 0x0613, 0x0614,
+ 0x0615, 0x0617, 0x0618, 0x0619, 0x061a, 0x061b, 0x061c, 0x061d, 0x061e, 0x061f, // G94
+ 0x0621, 0x0622, 0x0623, 0x0625, 0x0626, 0x0627, 0x0628, 0x062a, 0x062b, 0x062c,
+ 0x062d, 0x062e, 0x0631, 0x0635, 0x0637, 0x0638, 0x063a,
+ 0x0640, 0x0641, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064a, // G96
+ 0x064b, 0x064c, 0x0651, 0x0652, 0x0653, 0x0654, 0x0655, 0x0656, 0x0658, 0x0659,
+ 0x065a, 0x065b, 0x065c, 0x065f,
+ 0x06e0, 0x06e1, 0x06e2, 0x06e3, 0x06e4, 0x06e6, 0x06e7, 0x06e8, 0x06e9, 0x06ea, // G98
+ 0x06eb, 0x06ec, 0x06ef, 0x06f1, 0x06f8, 0x06f9, 0x06fa, 0x06fb, 0x06fd, 0x06ff,
+ 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e6, 0x05e7, 0x05e9, 0x05ea, 0x05eb, 0x05ed, // G200
+ 0x05ee, 0x05ef,
+ 0x0840, 0x0844, 0x0845, 0x0846, 0x0847, 0x0848, 0x0849, 0x084a, 0x084b, 0x084c, // MCP77
+ 0x084d, 0x084f,
+ 0x0860, 0x0861, 0x0862, 0x0863, 0x0864, 0x0865, 0x0866, 0x0867, 0x0868, 0x0869, // MCP79
+ 0x086a, 0x086c, 0x086d, 0x086e, 0x086f, 0x0870, 0x0871, 0x0872, 0x0873, 0x0874,
+ 0x0876, 0x087a, 0x087d, 0x087e, 0x087f,
+ 0x0ca0, 0x0ca2, 0x0ca3, 0x0ca2, 0x0ca4, 0x0ca5, 0x0ca7, 0x0ca9, 0x0cac, 0x0caf, // GT215
+ 0x0cb0, 0x0cb1, 0x0cbc,
+ 0x0a20, 0x0a22, 0x0a23, 0x0a26, 0x0a27, 0x0a28, 0x0a29, 0x0a2a, 0x0a2b, 0x0a2c, // GT216
+ 0x0a2d, 0x0a32, 0x0a34, 0x0a35, 0x0a38, 0x0a3c,
+ 0x0a60, 0x0a62, 0x0a63, 0x0a64, 0x0a65, 0x0a66, 0x0a67, 0x0a68, 0x0a69, 0x0a6a, // GT218
+ 0x0a6c, 0x0a6e, 0x0a6f, 0x0a70, 0x0a71, 0x0a72, 0x0a73, 0x0a74, 0x0a75, 0x0a76,
+ 0x0a78, 0x0a7a, 0x0a7c, 0x10c0, 0x10c3, 0x10c5, 0x10d8
+ // clang-format on
+};
+
+// The size we use for our synchronization surface.
+// 16x16 is the size recommended by Microsoft (in the D3D9ExDXGISharedSurf
+// sample) that works best to avoid driver bugs.
+static const uint32_t kSyncSurfaceSize = 16;
+
+namespace mozilla {
+
+using layers::D3D11RecycleAllocator;
+using layers::D3D11ShareHandleImage;
+using layers::D3D9RecycleAllocator;
+using layers::D3D9SurfaceImage;
+using layers::Image;
+using layers::ImageContainer;
+using namespace layers;
+using namespace gfx;
+
+class D3D9DXVA2Manager : public DXVA2Manager {
+ public:
+ D3D9DXVA2Manager();
+ virtual ~D3D9DXVA2Manager();
+
+ HRESULT Init(layers::KnowsCompositor* aKnowsCompositor,
+ nsACString& aFailureReason);
+
+ IUnknown* GetDXVADeviceManager() override;
+
+ // Copies a region (aRegion) of the video frame stored in aVideoSample
+ // into an image which is returned by aOutImage.
+ HRESULT CopyToImage(IMFSample* aVideoSample, const gfx::IntRect& aRegion,
+ Image** aOutImage) override;
+
+ bool SupportsConfig(IMFMediaType* aType, float aFramerate) override;
+
+ private:
+ bool CanCreateDecoder(const DXVA2_VideoDesc& aDesc,
+ const float aFramerate) const;
+
+ already_AddRefed<IDirectXVideoDecoder> CreateDecoder(
+ const DXVA2_VideoDesc& aDesc) const;
+
+ RefPtr<IDirect3D9Ex> mD3D9;
+ RefPtr<IDirect3DDevice9Ex> mDevice;
+ RefPtr<IDirect3DDeviceManager9> mDeviceManager;
+ RefPtr<D3D9RecycleAllocator> mTextureClientAllocator;
+ RefPtr<IDirectXVideoDecoderService> mDecoderService;
+ RefPtr<IDirect3DSurface9> mSyncSurface;
+ RefPtr<IDirectXVideoDecoder> mDecoder;
+ GUID mDecoderGUID;
+ UINT32 mResetToken = 0;
+};
+
+void GetDXVA2ExtendedFormatFromMFMediaType(IMFMediaType* pType,
+ DXVA2_ExtendedFormat* pFormat) {
+ // Get the interlace mode.
+ MFVideoInterlaceMode interlace = MFVideoInterlaceMode(MFGetAttributeUINT32(
+ pType, MF_MT_INTERLACE_MODE, MFVideoInterlace_Unknown));
+
+ if (interlace == MFVideoInterlace_MixedInterlaceOrProgressive) {
+ pFormat->SampleFormat = DXVA2_SampleFieldInterleavedEvenFirst;
+ } else {
+ pFormat->SampleFormat = UINT(interlace);
+ }
+
+ pFormat->VideoChromaSubsampling = MFGetAttributeUINT32(
+ pType, MF_MT_VIDEO_CHROMA_SITING, MFVideoChromaSubsampling_Unknown);
+ pFormat->NominalRange = MFGetAttributeUINT32(pType, MF_MT_VIDEO_NOMINAL_RANGE,
+ MFNominalRange_Unknown);
+ pFormat->VideoTransferMatrix = MFGetAttributeUINT32(
+ pType, MF_MT_YUV_MATRIX, MFVideoTransferMatrix_Unknown);
+ pFormat->VideoLighting = MFGetAttributeUINT32(pType, MF_MT_VIDEO_LIGHTING,
+ MFVideoLighting_Unknown);
+ pFormat->VideoPrimaries = MFGetAttributeUINT32(pType, MF_MT_VIDEO_PRIMARIES,
+ MFVideoPrimaries_Unknown);
+ pFormat->VideoTransferFunction = MFGetAttributeUINT32(
+ pType, MF_MT_TRANSFER_FUNCTION, MFVideoTransFunc_Unknown);
+}
+
+HRESULT ConvertMFTypeToDXVAType(IMFMediaType* pType, DXVA2_VideoDesc* pDesc) {
+ ZeroMemory(pDesc, sizeof(*pDesc));
+
+ // The D3D format is the first DWORD of the subtype GUID.
+ GUID subtype = GUID_NULL;
+ HRESULT hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ pDesc->Format = (D3DFORMAT)subtype.Data1;
+
+ UINT32 width = 0;
+ UINT32 height = 0;
+ hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ NS_ENSURE_TRUE(width <= MAX_VIDEO_WIDTH, E_FAIL);
+ NS_ENSURE_TRUE(height <= MAX_VIDEO_HEIGHT, E_FAIL);
+ pDesc->SampleWidth = width;
+ pDesc->SampleHeight = height;
+
+ UINT32 fpsNumerator = 0;
+ UINT32 fpsDenominator = 0;
+ hr = MFGetAttributeRatio(pType, MF_MT_FRAME_RATE, &fpsNumerator,
+ &fpsDenominator);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ pDesc->InputSampleFreq.Numerator = fpsNumerator;
+ pDesc->InputSampleFreq.Denominator = fpsDenominator;
+
+ GetDXVA2ExtendedFormatFromMFMediaType(pType, &pDesc->SampleFormat);
+ pDesc->OutputFrameFreq = pDesc->InputSampleFreq;
+ if ((pDesc->SampleFormat.SampleFormat ==
+ DXVA2_SampleFieldInterleavedEvenFirst) ||
+ (pDesc->SampleFormat.SampleFormat ==
+ DXVA2_SampleFieldInterleavedOddFirst)) {
+ pDesc->OutputFrameFreq.Numerator *= 2;
+ }
+
+ return S_OK;
+}
+
+static const GUID DXVA2_ModeH264_E = {
+ 0x1b81be68,
+ 0xa0c7,
+ 0x11d3,
+ {0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5}};
+
+static const GUID DXVA2_Intel_ModeH264_E = {
+ 0x604F8E68,
+ 0x4951,
+ 0x4c54,
+ {0x88, 0xFE, 0xAB, 0xD2, 0x5C, 0x15, 0xB3, 0xD6}};
+
+// This tests if a DXVA video decoder can be created for the given media
+// type/resolution. It uses the same decoder device (DXVA2_ModeH264_E -
+// DXVA2_ModeH264_VLD_NoFGT) as the H264 decoder MFT provided by windows
+// (CLSID_CMSH264DecoderMFT) uses, so we can use it to determine if the MFT will
+// use software fallback or not.
+bool D3D9DXVA2Manager::SupportsConfig(IMFMediaType* aType, float aFramerate) {
+ DXVA2_VideoDesc desc;
+ HRESULT hr = ConvertMFTypeToDXVAType(aType, &desc);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+ return CanCreateDecoder(desc, aFramerate);
+}
+
+D3D9DXVA2Manager::D3D9DXVA2Manager() { MOZ_COUNT_CTOR(D3D9DXVA2Manager); }
+
+D3D9DXVA2Manager::~D3D9DXVA2Manager() { MOZ_COUNT_DTOR(D3D9DXVA2Manager); }
+
+IUnknown* D3D9DXVA2Manager::GetDXVADeviceManager() {
+ MutexAutoLock lock(mLock);
+ return mDeviceManager;
+}
+
+HRESULT
+D3D9DXVA2Manager::Init(layers::KnowsCompositor* aKnowsCompositor,
+ nsACString& aFailureReason) {
+ ScopedGfxFeatureReporter reporter("DXVA2D3D9");
+
+ // Create D3D9Ex.
+ HMODULE d3d9lib = LoadLibraryW(L"d3d9.dll");
+ NS_ENSURE_TRUE(d3d9lib, E_FAIL);
+ decltype(Direct3DCreate9Ex)* d3d9Create =
+ (decltype(Direct3DCreate9Ex)*)GetProcAddress(d3d9lib,
+ "Direct3DCreate9Ex");
+ if (!d3d9Create) {
+ NS_WARNING("Couldn't find Direct3DCreate9Ex symbol in d3d9.dll");
+ aFailureReason.AssignLiteral(
+ "Couldn't find Direct3DCreate9Ex symbol in d3d9.dll");
+ return E_FAIL;
+ }
+ RefPtr<IDirect3D9Ex> d3d9Ex;
+ HRESULT hr = d3d9Create(D3D_SDK_VERSION, getter_AddRefs(d3d9Ex));
+ if (!d3d9Ex) {
+ NS_WARNING("Direct3DCreate9 failed");
+ aFailureReason.AssignLiteral("Direct3DCreate9 failed");
+ return E_FAIL;
+ }
+
+ // Ensure we can do the YCbCr->RGB conversion in StretchRect.
+ // Fail if we can't.
+ hr = d3d9Ex->CheckDeviceFormatConversion(
+ D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
+ (D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2'), D3DFMT_X8R8G8B8);
+ if (!SUCCEEDED(hr)) {
+ aFailureReason =
+ nsPrintfCString("CheckDeviceFormatConversion failed with error %X", hr);
+ return hr;
+ }
+
+ // Create D3D9DeviceEx. We pass null HWNDs here even though the documentation
+ // suggests that one of them should not be. At this point in time Chromium
+ // does the same thing for video acceleration.
+ D3DPRESENT_PARAMETERS params = {0};
+ params.BackBufferWidth = 1;
+ params.BackBufferHeight = 1;
+ params.BackBufferFormat = D3DFMT_A8R8G8B8;
+ params.BackBufferCount = 1;
+ params.SwapEffect = D3DSWAPEFFECT_DISCARD;
+ params.hDeviceWindow = nullptr;
+ params.Windowed = TRUE;
+ params.Flags = D3DPRESENTFLAG_VIDEO;
+
+ RefPtr<IDirect3DDevice9Ex> device;
+ hr = d3d9Ex->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, nullptr,
+ D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED |
+ D3DCREATE_MIXED_VERTEXPROCESSING,
+ &params, nullptr, getter_AddRefs(device));
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString("CreateDeviceEx failed with error %X", hr);
+ return hr;
+ }
+
+ // Ensure we can create queries to synchronize operations between devices.
+ // Without this, when we make a copy of the frame in order to share it with
+ // another device, we can't be sure that the copy has finished before the
+ // other device starts using it.
+ RefPtr<IDirect3DQuery9> query;
+
+ hr = device->CreateQuery(D3DQUERYTYPE_EVENT, getter_AddRefs(query));
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString("CreateQuery failed with error %X", hr);
+ return hr;
+ }
+
+ // Create and initialize IDirect3DDeviceManager9.
+ UINT resetToken = 0;
+ RefPtr<IDirect3DDeviceManager9> deviceManager;
+
+ hr = wmf::DXVA2CreateDirect3DDeviceManager9(&resetToken,
+ getter_AddRefs(deviceManager));
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString(
+ "DXVA2CreateDirect3DDeviceManager9 failed with error %X", hr);
+ return hr;
+ }
+ hr = deviceManager->ResetDevice(device, resetToken);
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString(
+ "IDirect3DDeviceManager9::ResetDevice failed with error %X", hr);
+ return hr;
+ }
+
+ HANDLE deviceHandle;
+ RefPtr<IDirectXVideoDecoderService> decoderService;
+ hr = deviceManager->OpenDeviceHandle(&deviceHandle);
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString(
+ "IDirect3DDeviceManager9::OpenDeviceHandle failed with error %X", hr);
+ return hr;
+ }
+
+ hr = deviceManager->GetVideoService(
+ deviceHandle, IID_PPV_ARGS(decoderService.StartAssignment()));
+ deviceManager->CloseDeviceHandle(deviceHandle);
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString(
+ "IDirectXVideoDecoderServer::GetVideoService failed with error %X", hr);
+ return hr;
+ }
+
+ UINT deviceCount;
+ GUID* decoderDevices = nullptr;
+ hr = decoderService->GetDecoderDeviceGuids(&deviceCount, &decoderDevices);
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString(
+ "IDirectXVideoDecoderServer::GetDecoderDeviceGuids failed with error "
+ "%X",
+ hr);
+ return hr;
+ }
+
+ bool found = false;
+ for (UINT i = 0; i < deviceCount; i++) {
+ if (decoderDevices[i] == DXVA2_ModeH264_E ||
+ decoderDevices[i] == DXVA2_Intel_ModeH264_E) {
+ mDecoderGUID = decoderDevices[i];
+ found = true;
+ break;
+ }
+ }
+ CoTaskMemFree(decoderDevices);
+
+ if (!found) {
+ aFailureReason.AssignLiteral("Failed to find an appropriate decoder GUID");
+ return E_FAIL;
+ }
+
+ D3DADAPTER_IDENTIFIER9 adapter;
+ hr = d3d9Ex->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &adapter);
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString(
+ "IDirect3D9Ex::GetAdapterIdentifier failed with error %X", hr);
+ return hr;
+ }
+
+ if ((adapter.VendorId == 0x1022 || adapter.VendorId == 0x1002) &&
+ !StaticPrefs::media_wmf_skip_blacklist()) {
+ for (const auto& model : sAMDPreUVD4) {
+ if (adapter.DeviceId == model) {
+ mIsAMDPreUVD4 = true;
+ break;
+ }
+ }
+ }
+
+ RefPtr<IDirect3DSurface9> syncSurf;
+ hr = device->CreateRenderTarget(kSyncSurfaceSize, kSyncSurfaceSize,
+ D3DFMT_X8R8G8B8, D3DMULTISAMPLE_NONE, 0, TRUE,
+ getter_AddRefs(syncSurf), NULL);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ mDecoderService = decoderService;
+
+ mResetToken = resetToken;
+ mD3D9 = d3d9Ex;
+ mDevice = device;
+ mDeviceManager = deviceManager;
+ mSyncSurface = syncSurf;
+
+ if (layers::ImageBridgeChild::GetSingleton()) {
+ // There's no proper KnowsCompositor for ImageBridge currently (and it
+ // implements the interface), so just use that if it's available.
+ mTextureClientAllocator = new D3D9RecycleAllocator(
+ layers::ImageBridgeChild::GetSingleton().get(), mDevice);
+ } else {
+ mTextureClientAllocator =
+ new D3D9RecycleAllocator(aKnowsCompositor, mDevice);
+ }
+ mTextureClientAllocator->SetMaxPoolSize(5);
+
+ Telemetry::Accumulate(Telemetry::MEDIA_DECODER_BACKEND_USED,
+ uint32_t(media::MediaDecoderBackend::WMFDXVA2D3D9));
+
+ reporter.SetSuccessful();
+
+ return S_OK;
+}
+
+HRESULT
+D3D9DXVA2Manager::CopyToImage(IMFSample* aSample, const gfx::IntRect& aRegion,
+ Image** aOutImage) {
+ RefPtr<IMFMediaBuffer> buffer;
+ HRESULT hr = aSample->GetBufferByIndex(0, getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IDirect3DSurface9> surface;
+ hr = wmf::MFGetService(buffer, MR_BUFFER_SERVICE, IID_IDirect3DSurface9,
+ getter_AddRefs(surface));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<D3D9SurfaceImage> image = new D3D9SurfaceImage();
+ hr = image->AllocateAndCopy(mTextureClientAllocator, surface, aRegion);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IDirect3DSurface9> sourceSurf = image->GetD3D9Surface();
+
+ // Copy a small rect into our sync surface, and then map it
+ // to block until decoding/color conversion completes.
+ RECT copyRect = {0, 0, kSyncSurfaceSize, kSyncSurfaceSize};
+ hr = mDevice->StretchRect(sourceSurf, &copyRect, mSyncSurface, &copyRect,
+ D3DTEXF_NONE);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ D3DLOCKED_RECT lockedRect;
+ hr = mSyncSurface->LockRect(&lockedRect, NULL, D3DLOCK_READONLY);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mSyncSurface->UnlockRect();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ image.forget(aOutImage);
+ return S_OK;
+}
+
+// Count of the number of DXVAManager's we've created. This is also the
+// number of videos we're decoding with DXVA. Use on main thread only.
+static Atomic<uint32_t> sDXVAVideosCount(0);
+
+/* static */
+DXVA2Manager* DXVA2Manager::CreateD3D9DXVA(
+ layers::KnowsCompositor* aKnowsCompositor, nsACString& aFailureReason) {
+ HRESULT hr;
+
+ // DXVA processing takes up a lot of GPU resources, so limit the number of
+ // videos we use DXVA with at any one time.
+ uint32_t dxvaLimit = StaticPrefs::media_wmf_dxva_max_videos();
+
+ if (sDXVAVideosCount == dxvaLimit) {
+ aFailureReason.AssignLiteral("Too many DXVA videos playing");
+ return nullptr;
+ }
+
+ UniquePtr<D3D9DXVA2Manager> d3d9Manager(new D3D9DXVA2Manager());
+ hr = d3d9Manager->Init(aKnowsCompositor, aFailureReason);
+ if (SUCCEEDED(hr)) {
+ return d3d9Manager.release();
+ }
+
+ // No hardware accelerated video decoding. :(
+ return nullptr;
+}
+
+bool D3D9DXVA2Manager::CanCreateDecoder(const DXVA2_VideoDesc& aDesc,
+ const float aFramerate) const {
+ if (IsUnsupportedResolution(aDesc.SampleWidth, aDesc.SampleHeight,
+ aFramerate)) {
+ return false;
+ }
+ RefPtr<IDirectXVideoDecoder> decoder = CreateDecoder(aDesc);
+ return decoder.get() != nullptr;
+}
+
+already_AddRefed<IDirectXVideoDecoder> D3D9DXVA2Manager::CreateDecoder(
+ const DXVA2_VideoDesc& aDesc) const {
+ UINT configCount;
+ DXVA2_ConfigPictureDecode* configs = nullptr;
+ HRESULT hr = mDecoderService->GetDecoderConfigurations(
+ mDecoderGUID, &aDesc, nullptr, &configCount, &configs);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ RefPtr<IDirect3DSurface9> surface;
+ hr = mDecoderService->CreateSurface(
+ aDesc.SampleWidth, aDesc.SampleHeight, 0,
+ (D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2'), D3DPOOL_DEFAULT, 0,
+ DXVA2_VideoDecoderRenderTarget, surface.StartAssignment(), NULL);
+ if (!SUCCEEDED(hr)) {
+ CoTaskMemFree(configs);
+ return nullptr;
+ }
+
+ for (UINT i = 0; i < configCount; i++) {
+ RefPtr<IDirectXVideoDecoder> decoder;
+ IDirect3DSurface9* surfaces = surface;
+ hr = mDecoderService->CreateVideoDecoder(mDecoderGUID, &aDesc, &configs[i],
+ &surfaces, 1,
+ decoder.StartAssignment());
+ CoTaskMemFree(configs);
+ return decoder.forget();
+ }
+
+ CoTaskMemFree(configs);
+ return nullptr;
+}
+
+class D3D11DXVA2Manager : public DXVA2Manager {
+ public:
+ virtual ~D3D11DXVA2Manager();
+
+ HRESULT Init(layers::KnowsCompositor* aKnowsCompositor,
+ nsACString& aFailureReason, ID3D11Device* aDevice);
+ HRESULT InitInternal(layers::KnowsCompositor* aKnowsCompositor,
+ nsACString& aFailureReason, ID3D11Device* aDevice);
+
+ IUnknown* GetDXVADeviceManager() override;
+
+ // Copies a region (aRegion) of the video frame stored in aVideoSample
+ // into an image which is returned by aOutImage.
+ HRESULT CopyToImage(IMFSample* aVideoSample, const gfx::IntRect& aRegion,
+ Image** aOutImage) override;
+
+ HRESULT CopyToBGRATexture(ID3D11Texture2D* aInTexture,
+ ID3D11Texture2D** aOutTexture) override;
+
+ HRESULT ConfigureForSize(IMFMediaType* aInputType,
+ gfx::YUVColorSpace aColorSpace,
+ gfx::ColorRange aColorRange, uint32_t aWidth,
+ uint32_t aHeight) override;
+
+ bool IsD3D11() override { return true; }
+
+ bool SupportsConfig(IMFMediaType* aType, float aFramerate) override;
+
+ private:
+ HRESULT CreateFormatConverter();
+
+ HRESULT CreateOutputSample(RefPtr<IMFSample>& aSample,
+ ID3D11Texture2D* aTexture);
+
+ bool CanCreateDecoder(const D3D11_VIDEO_DECODER_DESC& aDesc,
+ const float aFramerate) const;
+
+ already_AddRefed<ID3D11VideoDecoder> CreateDecoder(
+ const D3D11_VIDEO_DECODER_DESC& aDesc) const;
+
+ RefPtr<ID3D11Device> mDevice;
+ RefPtr<ID3D11DeviceContext> mContext;
+ RefPtr<IMFDXGIDeviceManager> mDXGIDeviceManager;
+ RefPtr<MFTDecoder> mTransform;
+ RefPtr<D3D11RecycleAllocator> mTextureClientAllocator;
+ RefPtr<ID3D11VideoDecoder> mDecoder;
+ RefPtr<layers::SyncObjectClient> mSyncObject;
+ GUID mDecoderGUID;
+ uint32_t mWidth = 0;
+ uint32_t mHeight = 0;
+ UINT mDeviceManagerToken = 0;
+ RefPtr<IMFMediaType> mInputType;
+ GUID mInputSubType;
+ gfx::YUVColorSpace mYUVColorSpace = gfx::YUVColorSpace::UNKNOWN;
+ gfx::ColorRange mColorRange = gfx::ColorRange::LIMITED;
+};
+
+bool D3D11DXVA2Manager::SupportsConfig(IMFMediaType* aType, float aFramerate) {
+ D3D11_VIDEO_DECODER_DESC desc;
+ desc.Guid = mDecoderGUID;
+ desc.OutputFormat = DXGI_FORMAT_NV12;
+ HRESULT hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &desc.SampleWidth,
+ &desc.SampleHeight);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+ NS_ENSURE_TRUE(desc.SampleWidth <= MAX_VIDEO_WIDTH, false);
+ NS_ENSURE_TRUE(desc.SampleHeight <= MAX_VIDEO_HEIGHT, false);
+
+ return CanCreateDecoder(desc, aFramerate);
+}
+
+D3D11DXVA2Manager::~D3D11DXVA2Manager() {}
+
+IUnknown* D3D11DXVA2Manager::GetDXVADeviceManager() {
+ MutexAutoLock lock(mLock);
+ return mDXGIDeviceManager;
+}
+HRESULT
+D3D11DXVA2Manager::Init(layers::KnowsCompositor* aKnowsCompositor,
+ nsACString& aFailureReason, ID3D11Device* aDevice) {
+ if (aDevice) {
+ return InitInternal(aKnowsCompositor, aFailureReason, aDevice);
+ }
+
+ HRESULT hr;
+ ScopedGfxFeatureReporter reporter("DXVA2D3D11");
+
+ hr = InitInternal(aKnowsCompositor, aFailureReason, aDevice);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (layers::ImageBridgeChild::GetSingleton() || !aKnowsCompositor) {
+ // There's no proper KnowsCompositor for ImageBridge currently (and it
+ // implements the interface), so just use that if it's available.
+ mTextureClientAllocator = new D3D11RecycleAllocator(
+ layers::ImageBridgeChild::GetSingleton().get(), mDevice,
+ gfx::SurfaceFormat::NV12);
+
+ if (ImageBridgeChild::GetSingleton() &&
+ StaticPrefs::media_wmf_use_sync_texture_AtStartup() &&
+ mDevice != DeviceManagerDx::Get()->GetCompositorDevice()) {
+ // We use a syncobject to avoid the cost of the mutex lock when
+ // compositing, and because it allows color conversion ocurring directly
+ // from this texture DXVA does not seem to accept IDXGIKeyedMutex textures
+ // as input.
+ mSyncObject = layers::SyncObjectClient::CreateSyncObjectClient(
+ layers::ImageBridgeChild::GetSingleton()
+ ->GetTextureFactoryIdentifier()
+ .mSyncHandle,
+ mDevice);
+ }
+ } else {
+ mTextureClientAllocator = new D3D11RecycleAllocator(
+ aKnowsCompositor, mDevice, gfx::SurfaceFormat::NV12);
+ if (StaticPrefs::media_wmf_use_sync_texture_AtStartup()) {
+ // We use a syncobject to avoid the cost of the mutex lock when
+ // compositing, and because it allows color conversion ocurring directly
+ // from this texture DXVA does not seem to accept IDXGIKeyedMutex textures
+ // as input.
+ mSyncObject = layers::SyncObjectClient::CreateSyncObjectClient(
+ aKnowsCompositor->GetTextureFactoryIdentifier().mSyncHandle, mDevice);
+ }
+ }
+ mTextureClientAllocator->SetMaxPoolSize(5);
+
+ Telemetry::Accumulate(Telemetry::MEDIA_DECODER_BACKEND_USED,
+ uint32_t(media::MediaDecoderBackend::WMFDXVA2D3D11));
+
+ reporter.SetSuccessful();
+
+ return S_OK;
+}
+
+HRESULT
+D3D11DXVA2Manager::InitInternal(layers::KnowsCompositor* aKnowsCompositor,
+ nsACString& aFailureReason,
+ ID3D11Device* aDevice) {
+ HRESULT hr;
+
+ mDevice = aDevice;
+
+ if (!mDevice) {
+ mDevice = gfx::DeviceManagerDx::Get()->CreateDecoderDevice();
+ if (!mDevice) {
+ aFailureReason.AssignLiteral("Failed to create D3D11 device for decoder");
+ return E_FAIL;
+ }
+ }
+
+ RefPtr<ID3D10Multithread> mt;
+ hr = mDevice->QueryInterface((ID3D10Multithread**)getter_AddRefs(mt));
+ NS_ENSURE_TRUE(SUCCEEDED(hr) && mt, hr);
+ mt->SetMultithreadProtected(TRUE);
+
+ mDevice->GetImmediateContext(getter_AddRefs(mContext));
+
+ hr = wmf::MFCreateDXGIDeviceManager(&mDeviceManagerToken,
+ getter_AddRefs(mDXGIDeviceManager));
+ if (!SUCCEEDED(hr)) {
+ aFailureReason =
+ nsPrintfCString("MFCreateDXGIDeviceManager failed with code %X", hr);
+ return hr;
+ }
+
+ hr = mDXGIDeviceManager->ResetDevice(mDevice, mDeviceManagerToken);
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString(
+ "IMFDXGIDeviceManager::ResetDevice failed with code %X", hr);
+ return hr;
+ }
+
+ // The IMFTransform interface used by MFTDecoder is documented to require to
+ // run on an MTA thread.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ee892371(v=vs.85).aspx#components
+ // The main thread (where this function is called) is STA, not MTA.
+ RefPtr<MFTDecoder> mft;
+ mozilla::mscom::EnsureMTA([&]() -> void {
+ mft = new MFTDecoder();
+ hr = mft->Create(CLSID_VideoProcessorMFT);
+
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString(
+ "MFTDecoder::Create(CLSID_VideoProcessorMFT) failed with code %X",
+ hr);
+ return;
+ }
+
+ hr = mft->SendMFTMessage(MFT_MESSAGE_SET_D3D_MANAGER,
+ ULONG_PTR(mDXGIDeviceManager.get()));
+ if (!SUCCEEDED(hr)) {
+ aFailureReason = nsPrintfCString(
+ "MFTDecoder::SendMFTMessage(MFT_MESSAGE_"
+ "SET_D3D_MANAGER) failed with code %X",
+ hr);
+ return;
+ }
+ });
+
+ if (!SUCCEEDED(hr)) {
+ return hr;
+ }
+ mTransform = mft;
+
+ RefPtr<ID3D11VideoDevice> videoDevice;
+ hr = mDevice->QueryInterface(
+ static_cast<ID3D11VideoDevice**>(getter_AddRefs(videoDevice)));
+ if (!SUCCEEDED(hr)) {
+ aFailureReason =
+ nsPrintfCString("QI to ID3D11VideoDevice failed with code %X", hr);
+ return hr;
+ }
+
+ bool found = false;
+ UINT profileCount = videoDevice->GetVideoDecoderProfileCount();
+ for (UINT i = 0; i < profileCount; i++) {
+ GUID id;
+ hr = videoDevice->GetVideoDecoderProfile(i, &id);
+ if (SUCCEEDED(hr) &&
+ (id == DXVA2_ModeH264_E || id == DXVA2_Intel_ModeH264_E)) {
+ mDecoderGUID = id;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ aFailureReason.AssignLiteral("Failed to find an appropriate decoder GUID");
+ return E_FAIL;
+ }
+
+ BOOL nv12Support = false;
+ hr = videoDevice->CheckVideoDecoderFormat(&mDecoderGUID, DXGI_FORMAT_NV12,
+ &nv12Support);
+ if (!SUCCEEDED(hr)) {
+ aFailureReason =
+ nsPrintfCString("CheckVideoDecoderFormat failed with code %X", hr);
+ return hr;
+ }
+ if (!nv12Support) {
+ aFailureReason.AssignLiteral("Decoder doesn't support NV12 surfaces");
+ return E_FAIL;
+ }
+
+ RefPtr<IDXGIDevice> dxgiDevice;
+ hr = mDevice->QueryInterface(
+ static_cast<IDXGIDevice**>(getter_AddRefs(dxgiDevice)));
+ if (!SUCCEEDED(hr)) {
+ aFailureReason =
+ nsPrintfCString("QI to IDXGIDevice failed with code %X", hr);
+ return hr;
+ }
+
+ RefPtr<IDXGIAdapter> adapter;
+ hr = dxgiDevice->GetAdapter(adapter.StartAssignment());
+ if (!SUCCEEDED(hr)) {
+ aFailureReason =
+ nsPrintfCString("IDXGIDevice::GetAdapter failed with code %X", hr);
+ return hr;
+ }
+
+ DXGI_ADAPTER_DESC adapterDesc;
+ hr = adapter->GetDesc(&adapterDesc);
+ if (!SUCCEEDED(hr)) {
+ aFailureReason =
+ nsPrintfCString("IDXGIAdapter::GetDesc failed with code %X", hr);
+ return hr;
+ }
+
+ if ((adapterDesc.VendorId == 0x1022 || adapterDesc.VendorId == 0x1002) &&
+ !StaticPrefs::media_wmf_skip_blacklist()) {
+ for (const auto& model : sAMDPreUVD4) {
+ if (adapterDesc.DeviceId == model) {
+ mIsAMDPreUVD4 = true;
+ break;
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT
+D3D11DXVA2Manager::CreateOutputSample(RefPtr<IMFSample>& aSample,
+ ID3D11Texture2D* aTexture) {
+ RefPtr<IMFSample> sample;
+ HRESULT hr = wmf::MFCreateSample(getter_AddRefs(sample));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFMediaBuffer> buffer;
+ hr = wmf::MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), aTexture, 0,
+ FALSE, getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ aSample = sample;
+ return S_OK;
+}
+
+HRESULT
+D3D11DXVA2Manager::CopyToImage(IMFSample* aVideoSample,
+ const gfx::IntRect& aRegion, Image** aOutImage) {
+ NS_ENSURE_TRUE(aVideoSample, E_POINTER);
+ NS_ENSURE_TRUE(aOutImage, E_POINTER);
+ MOZ_ASSERT(mTextureClientAllocator);
+
+ RefPtr<D3D11ShareHandleImage> image = new D3D11ShareHandleImage(
+ gfx::IntSize(mWidth, mHeight), aRegion, mYUVColorSpace, mColorRange);
+
+ // Retrieve the DXGI_FORMAT for the current video sample.
+ RefPtr<IMFMediaBuffer> buffer;
+ HRESULT hr = aVideoSample->GetBufferByIndex(0, getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFDXGIBuffer> dxgiBuf;
+ hr = buffer->QueryInterface((IMFDXGIBuffer**)getter_AddRefs(dxgiBuf));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<ID3D11Texture2D> tex;
+ hr = dxgiBuf->GetResource(__uuidof(ID3D11Texture2D), getter_AddRefs(tex));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ D3D11_TEXTURE2D_DESC inDesc;
+ tex->GetDesc(&inDesc);
+
+ bool ok = image->AllocateTexture(mTextureClientAllocator, mDevice);
+ NS_ENSURE_TRUE(ok, E_FAIL);
+
+ RefPtr<TextureClient> client =
+ image->GetTextureClient(ImageBridgeChild::GetSingleton().get());
+ NS_ENSURE_TRUE(client, E_FAIL);
+
+ RefPtr<ID3D11Texture2D> texture = image->GetTexture();
+ D3D11_TEXTURE2D_DESC outDesc;
+ texture->GetDesc(&outDesc);
+
+ RefPtr<IDXGIKeyedMutex> mutex;
+ texture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
+
+ {
+ AutoTextureLock(mutex, hr, 2000);
+ if (mutex && (FAILED(hr) || hr == WAIT_TIMEOUT || hr == WAIT_ABANDONED)) {
+ return hr;
+ }
+
+ if (!mutex && mDevice != DeviceManagerDx::Get()->GetCompositorDevice()) {
+ NS_ENSURE_TRUE(mSyncObject, E_FAIL);
+ }
+
+ // The D3D11TextureClientAllocator may return a different texture format
+ // than preferred. In which case the destination texture will be BGRA32.
+ if (outDesc.Format == inDesc.Format) {
+ // Our video frame is stored in a non-sharable ID3D11Texture2D. We need
+ // to create a copy of that frame as a sharable resource, save its share
+ // handle, and put that handle into the rendering pipeline.
+ UINT width = std::min(inDesc.Width, outDesc.Width);
+ UINT height = std::min(inDesc.Height, outDesc.Height);
+ D3D11_BOX srcBox = {0, 0, 0, width, height, 1};
+
+ UINT index;
+ dxgiBuf->GetSubresourceIndex(&index);
+ mContext->CopySubresourceRegion(texture, 0, 0, 0, 0, tex, index, &srcBox);
+ } else {
+ // Use MFT to do color conversion.
+ hr = E_FAIL;
+ mozilla::mscom::EnsureMTA(
+ [&]() -> void { hr = mTransform->Input(aVideoSample); });
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFSample> sample;
+ hr = CreateOutputSample(sample, texture);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = E_FAIL;
+ mozilla::mscom::EnsureMTA(
+ [&]() -> void { hr = mTransform->Output(&sample); });
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ }
+ }
+
+ if (!mutex && mDevice != DeviceManagerDx::Get()->GetCompositorDevice() &&
+ mSyncObject) {
+ static StaticMutex sMutex;
+ // Ensure that we only ever attempt to synchronise via the sync object
+ // serially as when using the same D3D11 device for multiple video decoders
+ // it can lead to deadlocks.
+ StaticMutexAutoLock lock(sMutex);
+ // It appears some race-condition may allow us to arrive here even when
+ // mSyncObject is null. It's better to avoid that crash.
+ client->SyncWithObject(mSyncObject);
+ if (!mSyncObject->Synchronize(true)) {
+ return DXGI_ERROR_DEVICE_RESET;
+ }
+ }
+
+ image.forget(aOutImage);
+
+ return S_OK;
+}
+
+HRESULT
+D3D11DXVA2Manager::CopyToBGRATexture(ID3D11Texture2D* aInTexture,
+ ID3D11Texture2D** aOutTexture) {
+ NS_ENSURE_TRUE(aInTexture, E_POINTER);
+ NS_ENSURE_TRUE(aOutTexture, E_POINTER);
+
+ HRESULT hr;
+ RefPtr<ID3D11Texture2D> texture, inTexture;
+
+ inTexture = aInTexture;
+
+ CD3D11_TEXTURE2D_DESC desc;
+ aInTexture->GetDesc(&desc);
+
+ if (!mInputType || desc.Width != mWidth || desc.Height != mHeight) {
+ RefPtr<IMFMediaType> inputType;
+ hr = wmf::MFCreateMediaType(getter_AddRefs(inputType));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = inputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ const GUID subType = [&]() {
+ switch (desc.Format) {
+ case DXGI_FORMAT_NV12:
+ return MFVideoFormat_NV12;
+ case DXGI_FORMAT_P010:
+ return MFVideoFormat_P010;
+ case DXGI_FORMAT_P016:
+ return MFVideoFormat_P016;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected texture type");
+ return MFVideoFormat_NV12;
+ }
+ }();
+
+ hr = inputType->SetGUID(MF_MT_SUBTYPE, subType);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = inputType->SetUINT32(MF_MT_INTERLACE_MODE,
+ MFVideoInterlace_Progressive);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = inputType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = ConfigureForSize(inputType, mYUVColorSpace, mColorRange, desc.Width,
+ desc.Height);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ }
+
+ RefPtr<IDXGIKeyedMutex> mutex;
+ inTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
+ // The rest of this function will not work if inTexture implements
+ // IDXGIKeyedMutex! In that case case we would have to copy to a
+ // non-mutex using texture.
+
+ if (mutex) {
+ RefPtr<ID3D11Texture2D> newTexture;
+
+ desc.MiscFlags = 0;
+ hr = mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(newTexture));
+ NS_ENSURE_TRUE(SUCCEEDED(hr) && newTexture, E_FAIL);
+
+ hr = mutex->AcquireSync(0, 2000);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ mContext->CopyResource(newTexture, inTexture);
+
+ mutex->ReleaseSync(0);
+ inTexture = newTexture;
+ }
+
+ desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+
+ hr = mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture));
+ NS_ENSURE_TRUE(SUCCEEDED(hr) && texture, E_FAIL);
+
+ RefPtr<IMFSample> inputSample;
+ wmf::MFCreateSample(getter_AddRefs(inputSample));
+
+ // If these aren't set the decoder fails.
+ inputSample->SetSampleTime(10);
+ inputSample->SetSampleDuration(10000);
+
+ RefPtr<IMFMediaBuffer> inputBuffer;
+ hr = wmf::MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), inTexture, 0,
+ FALSE, getter_AddRefs(inputBuffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ inputSample->AddBuffer(inputBuffer);
+
+ hr = E_FAIL;
+ mozilla::mscom::EnsureMTA(
+ [&]() -> void { hr = mTransform->Input(inputSample); });
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFSample> outputSample;
+ hr = CreateOutputSample(outputSample, texture);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = E_FAIL;
+ mozilla::mscom::EnsureMTA(
+ [&]() -> void { hr = mTransform->Output(&outputSample); });
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ texture.forget(aOutTexture);
+
+ return S_OK;
+}
+
+HRESULT
+D3D11DXVA2Manager::ConfigureForSize(IMFMediaType* aInputType,
+ gfx::YUVColorSpace aColorSpace,
+ gfx::ColorRange aColorRange,
+ uint32_t aWidth, uint32_t aHeight) {
+ GUID subType = {0};
+ HRESULT hr = aInputType->GetGUID(MF_MT_SUBTYPE, &subType);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (subType == mInputSubType && aWidth == mWidth && aHeight == mHeight &&
+ mYUVColorSpace == aColorSpace && mColorRange == aColorRange) {
+ // If the media type hasn't changed, don't reconfigure.
+ return S_OK;
+ }
+
+ // Create a copy of our input type.
+ RefPtr<IMFMediaType> inputType;
+ hr = wmf::MFCreateMediaType(getter_AddRefs(inputType));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ hr = aInputType->CopyAllItems(inputType);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = MFSetAttributeSize(inputType, MF_MT_FRAME_SIZE, aWidth, aHeight);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFAttributes> attr;
+ mozilla::mscom::EnsureMTA(
+ [&]() -> void { attr = mTransform->GetAttributes(); });
+ NS_ENSURE_TRUE(attr != nullptr, E_FAIL);
+
+ hr = attr->SetUINT32(MF_XVP_PLAYBACK_MODE, TRUE);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = attr->SetUINT32(MF_LOW_LATENCY, FALSE);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFMediaType> outputType;
+ hr = wmf::MFCreateMediaType(getter_AddRefs(outputType));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = outputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = outputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_ARGB32);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = E_FAIL;
+ mozilla::mscom::EnsureMTA([&]() -> void {
+ hr = mTransform->SetMediaTypes(
+ inputType, outputType, [aWidth, aHeight](IMFMediaType* aOutput) {
+ HRESULT hr = aOutput->SetUINT32(MF_MT_INTERLACE_MODE,
+ MFVideoInterlace_Progressive);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ hr = aOutput->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ hr = MFSetAttributeSize(aOutput, MF_MT_FRAME_SIZE, aWidth, aHeight);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+ });
+ });
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mInputType = inputType;
+ mInputSubType = subType;
+ mYUVColorSpace = aColorSpace;
+ mColorRange = aColorRange;
+ if (mTextureClientAllocator) {
+ gfx::SurfaceFormat format = [&]() {
+ if (subType == MFVideoFormat_NV12) {
+ return gfx::SurfaceFormat::NV12;
+ } else if (subType == MFVideoFormat_P010) {
+ return gfx::SurfaceFormat::P010;
+ } else if (subType == MFVideoFormat_P016) {
+ return gfx::SurfaceFormat::P016;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected texture type");
+ return gfx::SurfaceFormat::NV12;
+ }
+ }();
+ mTextureClientAllocator->SetPreferredSurfaceFormat(format);
+ }
+ return S_OK;
+}
+
+bool D3D11DXVA2Manager::CanCreateDecoder(const D3D11_VIDEO_DECODER_DESC& aDesc,
+ const float aFramerate) const {
+ if (IsUnsupportedResolution(aDesc.SampleWidth, aDesc.SampleHeight,
+ aFramerate)) {
+ return false;
+ }
+ RefPtr<ID3D11VideoDecoder> decoder = CreateDecoder(aDesc);
+ return decoder.get() != nullptr;
+}
+
+already_AddRefed<ID3D11VideoDecoder> D3D11DXVA2Manager::CreateDecoder(
+ const D3D11_VIDEO_DECODER_DESC& aDesc) const {
+ RefPtr<ID3D11VideoDevice> videoDevice;
+ HRESULT hr = mDevice->QueryInterface(
+ static_cast<ID3D11VideoDevice**>(getter_AddRefs(videoDevice)));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ UINT configCount = 0;
+ hr = videoDevice->GetVideoDecoderConfigCount(&aDesc, &configCount);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ for (UINT i = 0; i < configCount; i++) {
+ D3D11_VIDEO_DECODER_CONFIG config;
+ hr = videoDevice->GetVideoDecoderConfig(&aDesc, i, &config);
+ if (SUCCEEDED(hr)) {
+ RefPtr<ID3D11VideoDecoder> decoder;
+ hr = videoDevice->CreateVideoDecoder(&aDesc, &config,
+ decoder.StartAssignment());
+ return decoder.forget();
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+DXVA2Manager* DXVA2Manager::CreateD3D11DXVA(
+ layers::KnowsCompositor* aKnowsCompositor, nsACString& aFailureReason,
+ ID3D11Device* aDevice) {
+ // DXVA processing takes up a lot of GPU resources, so limit the number of
+ // videos we use DXVA with at any one time.
+ uint32_t dxvaLimit = StaticPrefs::media_wmf_dxva_max_videos();
+
+ if (sDXVAVideosCount == dxvaLimit) {
+ aFailureReason.AssignLiteral("Too many DXVA videos playing");
+ return nullptr;
+ }
+
+ UniquePtr<D3D11DXVA2Manager> manager(new D3D11DXVA2Manager());
+ HRESULT hr = manager->Init(aKnowsCompositor, aFailureReason, aDevice);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ return manager.release();
+}
+
+DXVA2Manager::DXVA2Manager() : mLock("DXVA2Manager") { ++sDXVAVideosCount; }
+
+DXVA2Manager::~DXVA2Manager() { --sDXVAVideosCount; }
+
+bool DXVA2Manager::IsUnsupportedResolution(const uint32_t& aWidth,
+ const uint32_t& aHeight,
+ const float& aFramerate) const {
+ // AMD cards with UVD3 or earlier perform poorly trying to decode 1080p60 in
+ // hardware, so use software instead. Pick 45 as an arbitrary upper bound for
+ // the framerate we can handle.
+ return !StaticPrefs::media_wmf_amd_highres_enabled() && mIsAMDPreUVD4 &&
+ (aWidth >= 1920 || aHeight >= 1088) && aFramerate > 45;
+}
+
+/* static */
+bool DXVA2Manager::IsNV12Supported(uint32_t aVendorID, uint32_t aDeviceID,
+ const nsAString& aDriverVersionString) {
+ if (aVendorID == 0x1022 || aVendorID == 0x1002) {
+ // AMD
+ // Block old cards regardless of driver version.
+ for (const auto& model : sAMDPreUVD4) {
+ if (aDeviceID == model) {
+ return false;
+ }
+ }
+ // AMD driver earlier than 21.19.411.0 have bugs in their handling of NV12
+ // surfaces.
+ uint64_t driverVersion;
+ if (!widget::ParseDriverVersion(aDriverVersionString, &driverVersion) ||
+ driverVersion < widget::V(21, 19, 411, 0)) {
+ return false;
+ }
+ } else if (aVendorID == 0x10DE) {
+ // NVidia
+ for (const auto& model : sNVIDIABrokenNV12) {
+ if (aDeviceID == model) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/wmf/DXVA2Manager.h b/dom/media/platforms/wmf/DXVA2Manager.h
new file mode 100644
index 0000000000..d3049c0e87
--- /dev/null
+++ b/dom/media/platforms/wmf/DXVA2Manager.h
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+#if !defined(DXVA2Manager_h_)
+# define DXVA2Manager_h_
+
+# include "MediaInfo.h"
+# include "WMF.h"
+# include "mozilla/Mutex.h"
+# include "mozilla/gfx/Rect.h"
+# include "d3d11.h"
+
+namespace mozilla {
+
+namespace layers {
+class Image;
+class ImageContainer;
+class KnowsCompositor;
+} // namespace layers
+
+class DXVA2Manager {
+ public:
+ // Creates and initializes a DXVA2Manager. We can use DXVA2 via either
+ // D3D9Ex or D3D11.
+ static DXVA2Manager* CreateD3D9DXVA(layers::KnowsCompositor* aKnowsCompositor,
+ nsACString& aFailureReason);
+ static DXVA2Manager* CreateD3D11DXVA(
+ layers::KnowsCompositor* aKnowsCompositor, nsACString& aFailureReason,
+ ID3D11Device* aDevice = nullptr);
+
+ // Returns a pointer to the D3D device manager responsible for managing the
+ // device we're using for hardware accelerated video decoding. If we're using
+ // D3D9Ex, this is an IDirect3DDeviceManager9. For D3D11 this is an
+ // IMFDXGIDeviceManager. It is safe to call this on any thread.
+ virtual IUnknown* GetDXVADeviceManager() = 0;
+
+ // Creates an Image for the video frame stored in aVideoSample.
+ virtual HRESULT CopyToImage(IMFSample* aVideoSample,
+ const gfx::IntRect& aRegion,
+ layers::Image** aOutImage) = 0;
+
+ virtual HRESULT CopyToBGRATexture(ID3D11Texture2D* aInTexture,
+ ID3D11Texture2D** aOutTexture) {
+ // Not implemented!
+ MOZ_CRASH("CopyToBGRATexture not implemented on this manager.");
+ return E_FAIL;
+ }
+
+ virtual HRESULT ConfigureForSize(IMFMediaType* aInputType,
+ gfx::YUVColorSpace aColorSpace,
+ gfx::ColorRange aColorRange, uint32_t aWidth,
+ uint32_t aHeight) {
+ return S_OK;
+ }
+
+ virtual bool IsD3D11() { return false; }
+
+ virtual ~DXVA2Manager();
+
+ virtual bool SupportsConfig(IMFMediaType* aType, float aFramerate) = 0;
+
+ static bool IsNV12Supported(uint32_t aVendorID, uint32_t aDeviceID,
+ const nsAString& aDriverVersionString);
+
+ protected:
+ Mutex mLock;
+ DXVA2Manager();
+
+ bool IsUnsupportedResolution(const uint32_t& aWidth, const uint32_t& aHeight,
+ const float& aFramerate) const;
+
+ bool mIsAMDPreUVD4 = false;
+};
+
+} // namespace mozilla
+
+#endif // DXVA2Manager_h_
diff --git a/dom/media/platforms/wmf/MFTDecoder.cpp b/dom/media/platforms/wmf/MFTDecoder.cpp
new file mode 100644
index 0000000000..8c6e7ddb79
--- /dev/null
+++ b/dom/media/platforms/wmf/MFTDecoder.cpp
@@ -0,0 +1,344 @@
+/* -*- 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 "MFTDecoder.h"
+#include "WMFUtils.h"
+#include "mozilla/Logging.h"
+#include "nsThreadUtils.h"
+#include "mozilla/mscom/Utils.h"
+
+#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+
+MFTDecoder::MFTDecoder() {
+ memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
+ memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
+}
+
+MFTDecoder::~MFTDecoder() {}
+
+HRESULT
+MFTDecoder::Create(const GUID& aMFTClsID) {
+ // Note: IMFTransform is documented to only be safe on MTA threads.
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ // Create the IMFTransform to do the decoding.
+ HRESULT hr;
+ hr = CoCreateInstance(
+ aMFTClsID, nullptr, CLSCTX_INPROC_SERVER,
+ 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,
+ std::function<HRESULT(IMFMediaType*)>&& aCallback) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+
+ // Set the input type to the one the caller gave us...
+ HRESULT hr = mDecoder->SetInputType(0, aInputType, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ GUID currentSubtype = {0};
+ hr = aOutputType->GetGUID(MF_MT_SUBTYPE, &currentSubtype);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = SetDecoderOutputType(currentSubtype, aOutputType, std::move(aCallback));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+already_AddRefed<IMFAttributes> MFTDecoder::GetAttributes() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ RefPtr<IMFAttributes> attr;
+ HRESULT hr = mDecoder->GetAttributes(getter_AddRefs(attr));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+ return attr.forget();
+}
+
+HRESULT
+MFTDecoder::FindDecoderOutputType() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(mOutputType, "SetDecoderTypes must have been called once");
+
+ return FindDecoderOutputTypeWithSubtype(mOutputSubType);
+}
+
+HRESULT
+MFTDecoder::FindDecoderOutputTypeWithSubtype(const GUID& aSubType) {
+ return SetDecoderOutputType(aSubType, nullptr,
+ [](IMFMediaType*) { return S_OK; });
+}
+
+HRESULT
+MFTDecoder::SetDecoderOutputType(
+ const GUID& aSubType, IMFMediaType* aTypeToUse,
+ std::function<HRESULT(IMFMediaType*)>&& aCallback) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ if (!aTypeToUse) {
+ aTypeToUse = mOutputType;
+ }
+
+ // Iterate the enumerate the output types, until we find one compatible
+ // with what we need.
+ RefPtr<IMFMediaType> outputType;
+ UINT32 typeIndex = 0;
+ while (SUCCEEDED(mDecoder->GetOutputAvailableType(
+ 0, typeIndex++, getter_AddRefs(outputType)))) {
+ GUID outSubtype = {0};
+ HRESULT hr = outputType->GetGUID(MF_MT_SUBTYPE, &outSubtype);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (aSubType == outSubtype) {
+ hr = aCallback(outputType);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->SetOutputType(0, outputType, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ mMFTProvidesOutputSamples = IsFlagSet(mOutputStreamInfo.dwFlags,
+ MFT_OUTPUT_STREAM_PROVIDES_SAMPLES);
+
+ mOutputType = outputType;
+ mOutputSubType = outSubtype;
+
+ return S_OK;
+ }
+ outputType = nullptr;
+ }
+ return E_FAIL;
+}
+
+HRESULT
+MFTDecoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, ULONG_PTR aData) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+ HRESULT hr = mDecoder->ProcessMessage(aMsg, aData);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::CreateInputSample(const uint8_t* aData, uint32_t aDataSize,
+ int64_t aTimestamp, int64_t aDuration,
+ RefPtr<IMFSample>* aOutSample) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ HRESULT hr;
+ RefPtr<IMFSample> sample;
+ hr = wmf::MFCreateSample(getter_AddRefs(sample));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFMediaBuffer> buffer;
+ int32_t bufferSize =
+ std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize);
+ UINT32 alignment =
+ (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0;
+ hr = wmf::MFCreateAlignedMemoryBuffer(bufferSize, alignment,
+ getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ DWORD maxLength = 0;
+ DWORD currentLength = 0;
+ BYTE* dst = nullptr;
+ hr = buffer->Lock(&dst, &maxLength, &currentLength);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ // Copy data into sample's buffer.
+ memcpy(dst, aData, aDataSize);
+
+ hr = buffer->Unlock();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = buffer->SetCurrentLength(aDataSize);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (aDuration == 0) {
+ // If the sample duration is 0, the decoder will try and estimate the
+ // duration. In practice this can lead to some wildly incorrect durations,
+ // as in bug 1560440. The Microsoft docs seem conflicting here with
+ // `IMFSample::SetSampleDuration` stating 'The duration can also be zero.
+ // This might be valid for some types of data.' However,
+ // `IMFSample::GetSampleDuration method` states 'If the retrieved duration
+ // is zero, or if the method returns MF_E_NO_SAMPLE_DURATION, the duration
+ // is unknown. In that case, it might be possible to calculate the duration
+ // from the media type--for example, by using the video frame rate or the
+ // audio sampling rate.' The latter of those seems to be how the decoder
+ // handles 0 duration, hence why it estimates.
+ //
+ // Since our demuxing pipeline can create 0 duration samples, and since the
+ // decoder will override them to something positive anyway, setting them to
+ // have a trivial duration seems like the lesser of evils.
+ aDuration = 1;
+ }
+ hr = sample->SetSampleDuration(UsecsToHNs(aDuration));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ *aOutSample = sample.forget();
+
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::CreateOutputSample(RefPtr<IMFSample>* aOutSample) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ HRESULT hr;
+ RefPtr<IMFSample> sample;
+ hr = wmf::MFCreateSample(getter_AddRefs(sample));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFMediaBuffer> buffer;
+ int32_t bufferSize = mOutputStreamInfo.cbSize;
+ UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1)
+ ? mOutputStreamInfo.cbAlignment - 1
+ : 0;
+ hr = wmf::MFCreateAlignedMemoryBuffer(bufferSize, alignment,
+ getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ *aOutSample = sample.forget();
+
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::Output(RefPtr<IMFSample>* aOutput) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ HRESULT hr;
+
+ MFT_OUTPUT_DATA_BUFFER output = {0};
+
+ bool providedSample = false;
+ RefPtr<IMFSample> sample;
+ if (*aOutput) {
+ output.pSample = *aOutput;
+ providedSample = true;
+ } else if (!mMFTProvidesOutputSamples) {
+ hr = CreateOutputSample(&sample);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ output.pSample = sample;
+ }
+
+ DWORD status = 0;
+ hr = mDecoder->ProcessOutput(0, 1, &output, &status);
+ if (output.pEvents) {
+ // We must release this, as per the IMFTransform::ProcessOutput()
+ // MSDN documentation.
+ output.pEvents->Release();
+ output.pEvents = nullptr;
+ }
+
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ return MF_E_TRANSFORM_STREAM_CHANGE;
+ }
+
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ // Not enough input to produce output. This is an expected failure,
+ // so don't warn on encountering it.
+ return hr;
+ }
+ // Treat other errors as unexpected, and warn.
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (!output.pSample) {
+ return S_OK;
+ }
+
+ if (mDiscontinuity) {
+ output.pSample->SetUINT32(MFSampleExtension_Discontinuity, TRUE);
+ mDiscontinuity = false;
+ }
+
+ *aOutput = output.pSample; // AddRefs
+ if (mMFTProvidesOutputSamples && !providedSample) {
+ // If the MFT is providing samples, we must release the sample here.
+ // Typically only the H.264 MFT provides samples when using DXVA,
+ // and it always re-uses the same sample, so if we don't release it
+ // MFT::ProcessOutput() deadlocks waiting for the sample to be released.
+ output.pSample->Release();
+ output.pSample = nullptr;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::Input(const uint8_t* aData, uint32_t aDataSize, int64_t aTimestamp,
+ int64_t aDuration) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ RefPtr<IMFSample> input;
+ HRESULT hr =
+ CreateInputSample(aData, aDataSize, aTimestamp, aDuration, &input);
+ NS_ENSURE_TRUE(SUCCEEDED(hr) && input != nullptr, hr);
+
+ return Input(input);
+}
+
+HRESULT
+MFTDecoder::Input(IMFSample* aSample) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ HRESULT hr = mDecoder->ProcessInput(0, aSample, 0);
+ if (hr == MF_E_NOTACCEPTING) {
+ // MFT *already* has enough data to produce a sample. Retrieve it.
+ return MF_E_NOTACCEPTING;
+ }
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::Flush() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ mDiscontinuity = true;
+
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::GetOutputMediaType(RefPtr<IMFMediaType>& aMediaType) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder, E_POINTER);
+ return mDecoder->GetOutputCurrentType(0, getter_AddRefs(aMediaType));
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/wmf/MFTDecoder.h b/dom/media/platforms/wmf/MFTDecoder.h
new file mode 100644
index 0000000000..1cf431e85c
--- /dev/null
+++ b/dom/media/platforms/wmf/MFTDecoder.h
@@ -0,0 +1,114 @@
+/* -*- 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/. */
+
+#if !defined(MFTDecoder_h_)
+# define MFTDecoder_h_
+
+# include "WMF.h"
+# include "mozilla/ReentrantMonitor.h"
+# include "mozilla/RefPtr.h"
+# include "nsIThread.h"
+
+namespace mozilla {
+
+class MFTDecoder final {
+ ~MFTDecoder();
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MFTDecoder)
+
+ 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);
+
+ // 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.
+ HRESULT SetMediaTypes(
+ IMFMediaType* aInputType, IMFMediaType* aOutputType,
+ std::function<HRESULT(IMFMediaType*)>&& aCallback =
+ [](IMFMediaType* aOutput) { return S_OK; });
+
+ // Returns the MFT's IMFAttributes object.
+ already_AddRefed<IMFAttributes> GetAttributes();
+
+ // Retrieves the media type being output. This may not be valid until
+ // the first sample is decoded.
+ HRESULT GetOutputMediaType(RefPtr<IMFMediaType>& aMediaType);
+ const GUID& GetOutputMediaSubType() const { return mOutputSubType; }
+
+ // Submits data into the MFT for processing.
+ //
+ // Returns:
+ // - MF_E_NOTACCEPTING if the decoder can't accept input. The data
+ // must be resubmitted after Output() stops producing output.
+ HRESULT Input(const uint8_t* aData, uint32_t aDataSize,
+ int64_t aTimestampUsecs, int64_t aDurationUsecs);
+ HRESULT Input(IMFSample* aSample);
+
+ HRESULT CreateInputSample(const uint8_t* aData, uint32_t aDataSize,
+ int64_t aTimestampUsecs, int64_t aDurationUsecs,
+ RefPtr<IMFSample>* aOutSample);
+
+ // Retrieves output from the MFT. Call this once Input() returns
+ // MF_E_NOTACCEPTING. Some MFTs with hardware acceleration (the H.264
+ // decoder MFT in particular) can't handle it if clients hold onto
+ // references to the output IMFSample, so don't do that.
+ //
+ // Returns:
+ // - MF_E_TRANSFORM_STREAM_CHANGE if the underlying stream output
+ // type changed. Retrieve the output media type and reconfig client,
+ // else you may misinterpret the MFT's output.
+ // - MF_E_TRANSFORM_NEED_MORE_INPUT if no output can be produced
+ // due to lack of input.
+ // - S_OK if an output frame is produced.
+ HRESULT Output(RefPtr<IMFSample>* aOutput);
+
+ // Sends a flush message to the MFT. This causes it to discard all
+ // input data. Use before seeking.
+ HRESULT Flush();
+
+ // Sends a message to the MFT.
+ HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, ULONG_PTR aData);
+
+ HRESULT FindDecoderOutputTypeWithSubtype(const GUID& aSubType);
+ HRESULT FindDecoderOutputType();
+
+ private:
+ // Will search a suitable MediaType using aTypeToUse if set, if not will
+ // use the current mOutputType.
+ HRESULT SetDecoderOutputType(
+ const GUID& aSubType, IMFMediaType* aTypeToUse,
+ std::function<HRESULT(IMFMediaType*)>&& aCallback);
+ HRESULT CreateOutputSample(RefPtr<IMFSample>* aOutSample);
+
+ MFT_INPUT_STREAM_INFO mInputStreamInfo;
+ MFT_OUTPUT_STREAM_INFO mOutputStreamInfo;
+
+ RefPtr<IMFTransform> mDecoder;
+
+ RefPtr<IMFMediaType> mOutputType;
+ GUID mOutputSubType;
+
+ // True if the IMFTransform allocates the samples that it returns.
+ bool mMFTProvidesOutputSamples = false;
+
+ // True if we need to mark the next sample as a discontinuity.
+ bool mDiscontinuity = true;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/platforms/wmf/WMF.h b/dom/media/platforms/wmf/WMF.h
new file mode 100644
index 0000000000..36db75fac5
--- /dev/null
+++ b/dom/media/platforms/wmf/WMF.h
@@ -0,0 +1,88 @@
+/* -*- 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/. */
+
+#ifndef WMF_H_
+#define WMF_H_
+
+#include <windows.h>
+#include <mfapi.h>
+#include <mfidl.h>
+#include <mfreadwrite.h>
+#include <mfobjects.h>
+#include <ks.h>
+#include <stdio.h>
+#include <mferror.h>
+#include <propvarutil.h>
+#include <wmcodecdsp.h>
+#include <d3d9.h>
+#include <dxva2api.h>
+#include <wmcodecdsp.h>
+#include <codecapi.h>
+
+// The Windows headers helpfully declare min and max macros, which don't
+// compile in the presence of std::min and std::max and unified builds.
+// So undef them here.
+#ifdef min
+# undef min
+#endif
+#ifdef max
+# undef max
+#endif
+
+// Some SDK versions don't define the AAC decoder CLSID.
+#ifndef CLSID_CMSAACDecMFT
+extern "C" const CLSID CLSID_CMSAACDecMFT;
+# define WMF_MUST_DEFINE_AAC_MFT_CLSID
+#endif
+
+namespace mozilla {
+namespace wmf {
+
+// If successful, loads all required WMF DLLs and calls the WMF MFStartup()
+// function. This delegates the WMF MFStartup() call to the MTA thread if
+// the current thread is not MTA. This is to ensure we always interact with
+// WMF from threads with the same COM compartment model.
+HRESULT MFStartup();
+
+// Calls the WMF MFShutdown() function. Call this once for every time
+// wmf::MFStartup() succeeds. Note: does not unload the WMF DLLs loaded by
+// MFStartup(); leaves them in memory to save I/O at next MFStartup() call.
+// This delegates the WMF MFShutdown() call to the MTA thread if the current
+// thread is not MTA. This is to ensure we always interact with
+// WMF from threads with the same COM compartment model.
+HRESULT MFShutdown();
+
+// All functions below are wrappers around the corresponding WMF function,
+// and automatically locate and call the corresponding function in the WMF DLLs.
+
+HRESULT MFCreateMediaType(IMFMediaType** aOutMFType);
+
+HRESULT MFGetStrideForBitmapInfoHeader(DWORD aFormat, DWORD aWidth,
+ LONG* aOutStride);
+
+HRESULT MFGetService(IUnknown* punkObject, REFGUID guidService, REFIID riid,
+ LPVOID* ppvObject);
+
+HRESULT DXVA2CreateDirect3DDeviceManager9(
+ UINT* pResetToken, IDirect3DDeviceManager9** ppDXVAManager);
+
+HRESULT MFCreateDXGIDeviceManager(UINT* pResetToken,
+ IMFDXGIDeviceManager** ppDXVAManager);
+
+HRESULT MFCreateSample(IMFSample** ppIMFSample);
+
+HRESULT MFCreateAlignedMemoryBuffer(DWORD cbMaxLength, DWORD fAlignmentFlags,
+ IMFMediaBuffer** ppBuffer);
+
+HRESULT MFCreateDXGISurfaceBuffer(REFIID riid, IUnknown* punkSurface,
+ UINT uSubresourceIndex,
+ BOOL fButtomUpWhenLinear,
+ IMFMediaBuffer** ppBuffer);
+
+} // end namespace wmf
+} // end namespace mozilla
+
+#endif
diff --git a/dom/media/platforms/wmf/WMFAudioMFTManager.cpp b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp
new file mode 100644
index 0000000000..cc480d4f5f
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp
@@ -0,0 +1,316 @@
+/* -*- 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 "WMFAudioMFTManager.h"
+#include "MediaInfo.h"
+#include "TimeUnits.h"
+#include "VideoUtils.h"
+#include "WMFUtils.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Telemetry.h"
+#include "nsTArray.h"
+
+#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+
+using media::TimeUnit;
+
+static void AACAudioSpecificConfigToUserData(uint8_t aAACProfileLevelIndication,
+ const uint8_t* aAudioSpecConfig,
+ uint32_t aConfigLength,
+ nsTArray<BYTE>& aOutUserData) {
+ MOZ_ASSERT(aOutUserData.IsEmpty());
+
+ // The MF_MT_USER_DATA for AAC is defined here:
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/dd742784%28v=vs.85%29.aspx
+ //
+ // For MFAudioFormat_AAC, MF_MT_USER_DATA contains the portion of
+ // the HEAACWAVEINFO structure that appears after the WAVEFORMATEX
+ // structure (that is, after the wfx member). This is followed by
+ // the AudioSpecificConfig() data, as defined by ISO/IEC 14496-3.
+ // [...]
+ // The length of the AudioSpecificConfig() data is 2 bytes for AAC-LC
+ // or HE-AAC with implicit signaling of SBR/PS. It is more than 2 bytes
+ // for HE-AAC with explicit signaling of SBR/PS.
+ //
+ // The value of audioObjectType as defined in AudioSpecificConfig()
+ // must be 2, indicating AAC-LC. The value of extensionAudioObjectType
+ // must be 5 for SBR or 29 for PS.
+ //
+ // HEAACWAVEINFO structure:
+ // typedef struct heaacwaveinfo_tag {
+ // WAVEFORMATEX wfx;
+ // WORD wPayloadType;
+ // WORD wAudioProfileLevelIndication;
+ // WORD wStructType;
+ // WORD wReserved1;
+ // DWORD dwReserved2;
+ // }
+ const UINT32 heeInfoLen = 4 * sizeof(WORD) + sizeof(DWORD);
+
+ // The HEAACWAVEINFO must have payload and profile set,
+ // the rest can be all 0x00.
+ BYTE heeInfo[heeInfoLen] = {0};
+ WORD* w = (WORD*)heeInfo;
+ w[0] = 0x0; // Payload type raw AAC packet
+ w[1] = aAACProfileLevelIndication;
+
+ aOutUserData.AppendElements(heeInfo, heeInfoLen);
+
+ if (aAACProfileLevelIndication == 2 && aConfigLength > 2) {
+ // The AudioSpecificConfig is TTTTTFFF|FCCCCGGG
+ // (T=ObjectType, F=Frequency, C=Channel, G=GASpecificConfig)
+ // If frequency = 0xf, then the frequency is explicitly defined on 24 bits.
+ int8_t frequency =
+ (aAudioSpecConfig[0] & 0x7) << 1 | (aAudioSpecConfig[1] & 0x80) >> 7;
+ int8_t channels = (aAudioSpecConfig[1] & 0x78) >> 3;
+ int8_t gasc = aAudioSpecConfig[1] & 0x7;
+ if (frequency != 0xf && channels && !gasc) {
+ // We enter this condition if the AudioSpecificConfig should theorically
+ // be 2 bytes long but it's not.
+ // The WMF AAC decoder will error if unknown extensions are found,
+ // so remove them.
+ aConfigLength = 2;
+ }
+ }
+ aOutUserData.AppendElements(aAudioSpecConfig, aConfigLength);
+}
+
+WMFAudioMFTManager::WMFAudioMFTManager(const AudioInfo& aConfig)
+ : mAudioChannels(aConfig.mChannels),
+ mChannelsMap(AudioConfig::ChannelLayout::UNKNOWN_MAP),
+ mAudioRate(aConfig.mRate) {
+ MOZ_COUNT_CTOR(WMFAudioMFTManager);
+
+ if (aConfig.mMimeType.EqualsLiteral("audio/mpeg")) {
+ mStreamType = MP3;
+ } else if (aConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
+ mStreamType = AAC;
+ AACAudioSpecificConfigToUserData(
+ aConfig.mExtendedProfile, aConfig.mCodecSpecificConfig->Elements(),
+ aConfig.mCodecSpecificConfig->Length(), mUserData);
+ } else {
+ mStreamType = Unknown;
+ }
+}
+
+WMFAudioMFTManager::~WMFAudioMFTManager() {
+ MOZ_COUNT_DTOR(WMFAudioMFTManager);
+}
+
+const GUID& WMFAudioMFTManager::GetMFTGUID() {
+ MOZ_ASSERT(mStreamType != Unknown);
+ switch (mStreamType) {
+ case AAC:
+ return CLSID_CMSAACDecMFT;
+ case MP3:
+ return CLSID_CMP3DecMediaObject;
+ default:
+ return GUID_NULL;
+ };
+}
+
+const GUID& WMFAudioMFTManager::GetMediaSubtypeGUID() {
+ MOZ_ASSERT(mStreamType != Unknown);
+ switch (mStreamType) {
+ case AAC:
+ return MFAudioFormat_AAC;
+ case MP3:
+ return MFAudioFormat_MP3;
+ default:
+ return GUID_NULL;
+ };
+}
+
+bool WMFAudioMFTManager::Init() {
+ NS_ENSURE_TRUE(mStreamType != Unknown, false);
+
+ RefPtr<MFTDecoder> decoder(new MFTDecoder());
+
+ HRESULT hr = decoder->Create(GetMFTGUID());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ // Setup input/output media types
+ RefPtr<IMFMediaType> inputType;
+
+ hr = wmf::MFCreateMediaType(getter_AddRefs(inputType));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = inputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = inputType->SetGUID(MF_MT_SUBTYPE, GetMediaSubtypeGUID());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = inputType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, mAudioRate);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = inputType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, mAudioChannels);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ if (mStreamType == AAC) {
+ hr = inputType->SetUINT32(MF_MT_AAC_PAYLOAD_TYPE, 0x0); // Raw AAC packet
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = inputType->SetBlob(MF_MT_USER_DATA, mUserData.Elements(),
+ mUserData.Length());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+ }
+
+ RefPtr<IMFMediaType> outputType;
+ hr = wmf::MFCreateMediaType(getter_AddRefs(outputType));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = outputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = outputType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = outputType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 32);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = decoder->SetMediaTypes(inputType, outputType);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ mDecoder = decoder;
+
+ return true;
+}
+
+HRESULT
+WMFAudioMFTManager::Input(MediaRawData* aSample) {
+ return mDecoder->Input(aSample->Data(), uint32_t(aSample->Size()),
+ aSample->mTime.ToMicroseconds(),
+ aSample->mDuration.ToMicroseconds());
+}
+
+HRESULT
+WMFAudioMFTManager::UpdateOutputType() {
+ HRESULT hr;
+
+ RefPtr<IMFMediaType> type;
+ hr = mDecoder->GetOutputMediaType(type);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = type->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &mAudioRate);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = type->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &mAudioChannels);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ uint32_t channelsMap;
+ hr = type->GetUINT32(MF_MT_AUDIO_CHANNEL_MASK, &channelsMap);
+ if (SUCCEEDED(hr)) {
+ mChannelsMap = channelsMap;
+ } else {
+ LOG("Unable to retrieve channel layout. Ignoring");
+ mChannelsMap = AudioConfig::ChannelLayout::UNKNOWN_MAP;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+WMFAudioMFTManager::Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutData) {
+ aOutData = nullptr;
+ RefPtr<IMFSample> sample;
+ HRESULT hr;
+ int typeChangeCount = 0;
+ while (true) {
+ hr = mDecoder->Output(&sample);
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ return hr;
+ }
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ hr = mDecoder->FindDecoderOutputType();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ hr = UpdateOutputType();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ // Catch infinite loops, but some decoders perform at least 2 stream
+ // changes on consecutive calls, so be permissive.
+ // 100 is arbitrarily > 2.
+ NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE);
+ ++typeChangeCount;
+ continue;
+ }
+ break;
+ }
+
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (!sample) {
+ LOG("Audio MFTDecoder returned success but null output.");
+ return E_FAIL;
+ }
+
+ UINT32 discontinuity = false;
+ sample->GetUINT32(MFSampleExtension_Discontinuity, &discontinuity);
+ if (mFirstFrame || discontinuity) {
+ // Update the output type, in case this segment has a different
+ // rate. This also triggers on the first sample, which can have a
+ // different rate than is advertised in the container, and sometimes we
+ // don't get a MF_E_TRANSFORM_STREAM_CHANGE when the rate changes.
+ hr = UpdateOutputType();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ mFirstFrame = false;
+ }
+
+ TimeUnit pts = GetSampleTime(sample);
+ NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
+
+ RefPtr<IMFMediaBuffer> buffer;
+ hr = sample->ConvertToContiguousBuffer(getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ BYTE* data = nullptr; // Note: *data will be owned by the IMFMediaBuffer, we
+ // don't need to free it.
+ DWORD maxLength = 0, currentLength = 0;
+ hr = buffer->Lock(&data, &maxLength, &currentLength);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ // Output is made of floats.
+ int32_t numSamples = currentLength / sizeof(float);
+ int32_t numFrames = numSamples / mAudioChannels;
+ MOZ_ASSERT(numFrames >= 0);
+ MOZ_ASSERT(numSamples >= 0);
+ if (numFrames == 0) {
+ // All data from this chunk stripped, loop back and try to output the next
+ // frame, if possible.
+ return S_OK;
+ }
+
+ AlignedAudioBuffer audioData(numSamples);
+ if (!audioData) {
+ return E_OUTOFMEMORY;
+ }
+
+ PodCopy(audioData.Data(), reinterpret_cast<float*>(data), numSamples);
+
+ buffer->Unlock();
+
+ TimeUnit duration = FramesToTimeUnit(numFrames, mAudioRate);
+ NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
+
+ aOutData = new AudioData(aStreamOffset, pts, std::move(audioData),
+ mAudioChannels, mAudioRate, mChannelsMap);
+ MOZ_DIAGNOSTIC_ASSERT(duration == aOutData->mDuration, "must be equal");
+
+#ifdef LOG_SAMPLE_DECODE
+ LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u",
+ pts.ToMicroseconds(), duration.ToMicroseconds(), currentLength);
+#endif
+
+ return S_OK;
+}
+
+void WMFAudioMFTManager::Shutdown() { mDecoder = nullptr; }
+
+} // namespace mozilla
+
+#undef LOG
diff --git a/dom/media/platforms/wmf/WMFAudioMFTManager.h b/dom/media/platforms/wmf/WMFAudioMFTManager.h
new file mode 100644
index 0000000000..4271d5ef8d
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFAudioMFTManager.h
@@ -0,0 +1,58 @@
+/* -*- 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/. */
+
+#if !defined(WMFAudioOutputSource_h_)
+# define WMFAudioOutputSource_h_
+
+# include "MFTDecoder.h"
+# include "WMF.h"
+# include "WMFMediaDataDecoder.h"
+# include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+class WMFAudioMFTManager : public MFTManager {
+ public:
+ explicit WMFAudioMFTManager(const AudioInfo& aConfig);
+ ~WMFAudioMFTManager();
+
+ bool Init();
+
+ HRESULT Input(MediaRawData* aSample) override;
+
+ // Note WMF's AAC decoder sometimes output negatively timestamped samples,
+ // presumably they're the preroll samples, and we strip them. We may return
+ // a null aOutput in this case.
+ HRESULT Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutput) override;
+
+ void Shutdown() override;
+
+ TrackInfo::TrackType GetType() override { return TrackInfo::kAudioTrack; }
+
+ nsCString GetDescriptionName() const override {
+ return "wmf audio decoder"_ns;
+ }
+
+ private:
+ HRESULT UpdateOutputType();
+
+ uint32_t mAudioChannels;
+ AudioConfig::ChannelLayout::ChannelMap mChannelsMap;
+ uint32_t mAudioRate;
+ nsTArray<BYTE> mUserData;
+
+ enum StreamType { Unknown, AAC, MP3 };
+ StreamType mStreamType;
+
+ const GUID& GetMFTGUID();
+ const GUID& GetMediaSubtypeGUID();
+
+ bool mFirstFrame = true;
+};
+
+} // namespace mozilla
+
+#endif // WMFAudioOutputSource_h_
diff --git a/dom/media/platforms/wmf/WMFDecoderModule.cpp b/dom/media/platforms/wmf/WMFDecoderModule.cpp
new file mode 100644
index 0000000000..64e14675f9
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -0,0 +1,342 @@
+/* -*- 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 "WMFDecoderModule.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "DriverCrashGuard.h"
+#include "GfxDriverInfo.h"
+#include "MFTDecoder.h"
+#include "MP4Decoder.h"
+#include "MediaInfo.h"
+#include "PDMFactory.h"
+#include "VPXDecoder.h"
+#include "WMF.h"
+#include "WMFAudioMFTManager.h"
+#include "WMFMediaDataDecoder.h"
+#include "WMFVideoMFTManager.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/mscom/EnsureMTA.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIXULRuntime.h"
+#include "nsIXULRuntime.h" // for BrowserTabsRemoteAutostart
+#include "nsServiceManagerUtils.h"
+#include "nsWindowsHelpers.h"
+#include "prsystem.h"
+
+#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+extern const GUID CLSID_WebmMfVpxDec;
+
+namespace mozilla {
+
+// Helper function to add a profile marker and log at the same time.
+static void MOZ_FORMAT_PRINTF(2, 3)
+ WmfDecoderModuleMarkerAndLog(const ProfilerString8View& aMarkerTag,
+ const char* aFormat, ...) {
+ va_list ap;
+ va_start(ap, aFormat);
+ const nsVprintfCString markerString(aFormat, ap);
+ va_end(ap);
+ PROFILER_MARKER_TEXT(aMarkerTag, MEDIA_PLAYBACK, {}, markerString);
+ LOG("%s", markerString.get());
+}
+
+static Atomic<bool> sDXVAEnabled(false);
+static Atomic<bool> sUsableVPXMFT(false);
+
+/* static */
+already_AddRefed<PlatformDecoderModule> WMFDecoderModule::Create() {
+ return MakeAndAddRef<WMFDecoderModule>();
+}
+
+WMFDecoderModule::~WMFDecoderModule() {
+ if (mWMFInitialized) {
+ DebugOnly<HRESULT> hr = wmf::MFShutdown();
+ NS_ASSERTION(SUCCEEDED(hr), "MFShutdown failed");
+ }
+}
+
+static bool IsRemoteAcceleratedCompositor(
+ layers::KnowsCompositor* aKnowsCompositor) {
+ if (!aKnowsCompositor) {
+ return false;
+ }
+
+ TextureFactoryIdentifier ident =
+ aKnowsCompositor->GetTextureFactoryIdentifier();
+ return ident.mParentBackend != LayersBackend::LAYERS_BASIC &&
+ !aKnowsCompositor->UsingSoftwareWebRender() &&
+ ident.mParentProcessType == GeckoProcessType_GPU;
+}
+
+static bool CanCreateMFTDecoder(const GUID& aGuid) {
+ // The IMFTransform interface used by MFTDecoder is documented to require to
+ // run on an MTA thread.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ee892371(v=vs.85).aspx#components
+ // Note: our normal SharedThreadPool task queues are initialized to MTA, but
+ // the main thread (which calls in here from our CanPlayType implementation)
+ // is not.
+ bool canCreateDecoder = false;
+ mozilla::mscom::EnsureMTA([&]() -> void {
+ if (FAILED(wmf::MFStartup())) {
+ return;
+ }
+ RefPtr<MFTDecoder> decoder(new MFTDecoder());
+ canCreateDecoder = SUCCEEDED(decoder->Create(aGuid));
+ wmf::MFShutdown();
+ });
+ return canCreateDecoder;
+}
+
+/* static */
+void WMFDecoderModule::Init() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ if (XRE_IsContentProcess()) {
+ // If we're in the content process and the UseGPUDecoder pref is set, it
+ // means that we've given up on the GPU process (it's been crashing) so we
+ // should disable DXVA
+ sDXVAEnabled = !StaticPrefs::media_gpu_process_decoder();
+ } else if (XRE_IsGPUProcess()) {
+ // Always allow DXVA in the GPU process.
+ sDXVAEnabled = true;
+ } else if (XRE_IsRDDProcess()) {
+ // Only allows DXVA if we have an image device. We may have explicitly
+ // disabled its creation following an earlier RDD process crash.
+ sDXVAEnabled = !!DeviceManagerDx::Get()->GetImageDevice();
+ } else {
+ // Only allow DXVA in the UI process if we aren't in e10s Firefox
+ sDXVAEnabled = !mozilla::BrowserTabsRemoteAutostart();
+ }
+
+ // We have heavy logging below to help diagnose issue around hardware decoding
+ // failures. Due to these failures often relating to driver level problems
+ // they're hard to nail down, so we want lots of info. We may be able to relax
+ // this in future if we're not seeing such problems (see bug 1673007 for
+ // references to the bugs motivating this).
+ sDXVAEnabled = sDXVAEnabled && gfx::gfxVars::CanUseHardwareVideoDecoding();
+ bool testForVPx = gfx::gfxVars::CanUseHardwareVideoDecoding();
+ if (testForVPx && StaticPrefs::media_wmf_vp9_enabled_AtStartup()) {
+ gfx::WMFVPXVideoCrashGuard guard;
+ if (!guard.Crashed()) {
+ WmfDecoderModuleMarkerAndLog("WMFInit VPx Pending",
+ "Attempting to create MFT decoder for VPx");
+
+ sUsableVPXMFT = CanCreateMFTDecoder(CLSID_WebmMfVpxDec);
+
+ WmfDecoderModuleMarkerAndLog("WMFInit VPx Initialized",
+ "CanCreateMFTDecoder returned %s for VPx",
+ sUsableVPXMFT ? "true" : "false");
+ } else {
+ WmfDecoderModuleMarkerAndLog(
+ "WMFInit VPx Failure",
+ "Will not use MFT VPx due to crash guard reporting a crash");
+ }
+ }
+
+ WmfDecoderModuleMarkerAndLog(
+ "WMFInit Result",
+ "WMFDecoderModule::Init finishing with sDXVAEnabled=%s testForVPx=%s "
+ "sUsableVPXMFT=%s",
+ sDXVAEnabled ? "true" : "false", testForVPx ? "true" : "false",
+ sUsableVPXMFT ? "true" : "false");
+}
+
+/* static */
+int WMFDecoderModule::GetNumDecoderThreads() {
+ int32_t numCores = PR_GetNumberOfProcessors();
+
+ // If we have more than 4 cores, let the decoder decide how many threads.
+ // On an 8 core machine, WMF chooses 4 decoder threads.
+ static const int WMF_DECODER_DEFAULT = -1;
+ if (numCores > 4) {
+ return WMF_DECODER_DEFAULT;
+ }
+ return std::max(numCores - 1, 1);
+}
+
+nsresult WMFDecoderModule::Startup() {
+ mWMFInitialized = SUCCEEDED(wmf::MFStartup());
+ return mWMFInitialized ? NS_OK : NS_ERROR_FAILURE;
+}
+
+already_AddRefed<MediaDataDecoder> WMFDecoderModule::CreateVideoDecoder(
+ const CreateDecoderParams& aParams) {
+ // In GPU process, only support decoding if an accelerated compositor is
+ // known.
+ if (XRE_IsGPUProcess() &&
+ !IsRemoteAcceleratedCompositor(aParams.mKnowsCompositor)) {
+ return nullptr;
+ }
+
+ UniquePtr<WMFVideoMFTManager> manager(new WMFVideoMFTManager(
+ aParams.VideoConfig(), aParams.mKnowsCompositor, aParams.mImageContainer,
+ aParams.mRate.mValue, aParams.mOptions, sDXVAEnabled));
+
+ MediaResult result = manager->Init();
+ if (NS_FAILED(result)) {
+ if (aParams.mError) {
+ *aParams.mError = result;
+ }
+ WmfDecoderModuleMarkerAndLog(
+ "WMFVDecoderCreation Failure",
+ "WMFDecoderModule::CreateVideoDecoder failed for manager with "
+ "description %s with result: %s",
+ manager->GetDescriptionName().get(), result.Description().get());
+ return nullptr;
+ }
+
+ WmfDecoderModuleMarkerAndLog(
+ "WMFVDecoderCreation Success",
+ "WMFDecoderModule::CreateVideoDecoder success for manager with "
+ "description %s",
+ manager->GetDescriptionName().get());
+
+ RefPtr<MediaDataDecoder> decoder = new WMFMediaDataDecoder(manager.release());
+ return decoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder> WMFDecoderModule::CreateAudioDecoder(
+ const CreateDecoderParams& aParams) {
+ if (XRE_IsGPUProcess()) {
+ // Only allow video in the GPU process.
+ return nullptr;
+ }
+
+ UniquePtr<WMFAudioMFTManager> manager(
+ new WMFAudioMFTManager(aParams.AudioConfig()));
+
+ if (!manager->Init()) {
+ WmfDecoderModuleMarkerAndLog(
+ "WMFADecoderCreation Failure",
+ "WMFDecoderModule::CreateAudioDecoder failed for manager with "
+ "description %s",
+ manager->GetDescriptionName().get());
+ return nullptr;
+ }
+
+ WmfDecoderModuleMarkerAndLog(
+ "WMFADecoderCreation Success",
+ "WMFDecoderModule::CreateAudioDecoder success for manager with "
+ "description %s",
+ manager->GetDescriptionName().get());
+
+ RefPtr<MediaDataDecoder> decoder = new WMFMediaDataDecoder(manager.release());
+ return decoder.forget();
+}
+
+template <const GUID& aGuid>
+static bool CanCreateWMFDecoder() {
+ static StaticMutex sMutex;
+ StaticMutexAutoLock lock(sMutex);
+ static Maybe<bool> result;
+ if (result.isNothing()) {
+ result.emplace(CanCreateMFTDecoder(aGuid));
+ }
+ return result.value();
+}
+
+/* static */
+bool WMFDecoderModule::HasH264() {
+ return CanCreateWMFDecoder<CLSID_CMSH264DecoderMFT>();
+}
+
+/* static */
+bool WMFDecoderModule::HasVP8() {
+ return sUsableVPXMFT && CanCreateWMFDecoder<CLSID_WebmMfVpxDec>();
+}
+
+/* static */
+bool WMFDecoderModule::HasVP9() {
+ return sUsableVPXMFT && CanCreateWMFDecoder<CLSID_WebmMfVpxDec>();
+}
+
+/* static */
+bool WMFDecoderModule::HasAAC() {
+ return CanCreateWMFDecoder<CLSID_CMSAACDecMFT>();
+}
+
+/* static */
+bool WMFDecoderModule::HasMP3() {
+ return CanCreateWMFDecoder<CLSID_CMP3DecMediaObject>();
+}
+
+bool WMFDecoderModule::SupportsMimeType(
+ const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const {
+ UniquePtr<TrackInfo> trackInfo = CreateTrackInfoWithMIMEType(aMimeType);
+ if (!trackInfo) {
+ return false;
+ }
+ return Supports(SupportDecoderParams(*trackInfo), aDiagnostics);
+}
+
+bool WMFDecoderModule::Supports(const SupportDecoderParams& aParams,
+ DecoderDoctorDiagnostics* aDiagnostics) const {
+ // In GPU process, only support decoding if video. This only gives a hint of
+ // what the GPU decoder *may* support. The actual check will occur in
+ // CreateVideoDecoder.
+ const auto& trackInfo = aParams.mConfig;
+ if (XRE_IsGPUProcess() && !trackInfo.GetAsVideoInfo()) {
+ return false;
+ }
+
+ const auto* videoInfo = trackInfo.GetAsVideoInfo();
+ // Temporary - forces use of VPXDecoder when alpha is present.
+ // Bug 1263836 will handle alpha scenario once implemented. It will shift
+ // the check for alpha to PDMFactory but not itself remove the need for a
+ // check.
+ if (videoInfo && (!SupportsColorDepth(videoInfo->mColorDepth, aDiagnostics) ||
+ videoInfo->HasAlpha())) {
+ return false;
+ }
+
+ if ((trackInfo.mMimeType.EqualsLiteral("audio/mp4a-latm") ||
+ trackInfo.mMimeType.EqualsLiteral("audio/mp4")) &&
+ WMFDecoderModule::HasAAC()) {
+ const auto audioInfo = trackInfo.GetAsAudioInfo();
+ if (audioInfo && audioInfo->mRate > 0) {
+ // Supported sampling rates per:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd742784(v=vs.85).aspx
+ const std::vector<uint32_t> frequencies = {
+ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
+ };
+ return std::find(frequencies.begin(), frequencies.end(),
+ audioInfo->mRate) != frequencies.end();
+ }
+ return true;
+ }
+ if (MP4Decoder::IsH264(trackInfo.mMimeType) && WMFDecoderModule::HasH264()) {
+ return true;
+ }
+ if (trackInfo.mMimeType.EqualsLiteral("audio/mpeg") &&
+ !StaticPrefs::media_ffvpx_mp3_enabled() && WMFDecoderModule::HasMP3()) {
+ return true;
+ }
+ static const uint32_t VP8_USABLE_BUILD = 16287;
+ if (VPXDecoder::IsVP8(trackInfo.mMimeType) &&
+ IsWindowsBuildOrLater(VP8_USABLE_BUILD) && WMFDecoderModule::HasVP8()) {
+ return true;
+ }
+ if (VPXDecoder::IsVP9(trackInfo.mMimeType) && WMFDecoderModule::HasVP9()) {
+ return true;
+ }
+
+ // Some unsupported codec.
+ return false;
+}
+
+} // namespace mozilla
+
+#undef WFM_DECODER_MODULE_STATUS_MARKER
+#undef LOG
diff --git a/dom/media/platforms/wmf/WMFDecoderModule.h b/dom/media/platforms/wmf/WMFDecoderModule.h
new file mode 100644
index 0000000000..7c2c90d6e0
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFDecoderModule.h
@@ -0,0 +1,57 @@
+/* -*- 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/. */
+
+#if !defined(WMFPlatformDecoderModule_h_)
+# define WMFPlatformDecoderModule_h_
+
+# include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class WMFDecoderModule : public PlatformDecoderModule {
+ public:
+ static already_AddRefed<PlatformDecoderModule> Create();
+
+ // Initializes the module, loads required dynamic libraries, etc.
+ nsresult Startup() override;
+
+ already_AddRefed<MediaDataDecoder> CreateVideoDecoder(
+ const CreateDecoderParams& aParams) override;
+
+ already_AddRefed<MediaDataDecoder> CreateAudioDecoder(
+ const CreateDecoderParams& aParams) override;
+
+ bool SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+ bool Supports(const SupportDecoderParams& aParams,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+ // Called on main thread.
+ static void Init();
+
+ // Called from any thread, must call init first
+ static int GetNumDecoderThreads();
+
+ // Accessors that report whether we have the required MFTs available
+ // on the system to play various codecs. Windows Vista doesn't have the
+ // H.264/AAC decoders if the "Platform Update Supplement for Windows Vista"
+ // is not installed, and Window N and KN variants also require a "Media
+ // Feature Pack" to be installed. Windows XP doesn't have WMF.
+ static bool HasH264();
+ static bool HasVP8();
+ static bool HasVP9();
+ static bool HasAAC();
+ static bool HasMP3();
+
+ private:
+ virtual ~WMFDecoderModule();
+
+ bool mWMFInitialized = false;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
new file mode 100644
index 0000000000..f616d16968
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
@@ -0,0 +1,257 @@
+/* -*- 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 "WMFMediaDataDecoder.h"
+
+#include "VideoUtils.h"
+#include "WMFUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Telemetry.h"
+#include "nsTArray.h"
+
+#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+
+WMFMediaDataDecoder::WMFMediaDataDecoder(MFTManager* aMFTManager)
+ : mTaskQueue(
+ new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
+ "WMFMediaDataDecoder")),
+ mMFTManager(aMFTManager) {}
+
+WMFMediaDataDecoder::~WMFMediaDataDecoder() {}
+
+RefPtr<MediaDataDecoder::InitPromise> WMFMediaDataDecoder::Init() {
+ MOZ_ASSERT(!mIsShutDown);
+ return InitPromise::CreateAndResolve(mMFTManager->GetType(), __func__);
+}
+
+// A single telemetry sample is reported for each MediaDataDecoder object
+// that has detected error or produced output successfully.
+static void SendTelemetry(unsigned long hr) {
+ // Collapse the error codes into a range of 0-0xff that can be viewed in
+ // telemetry histograms. For most MF_E_* errors, unique samples are used,
+ // retaining the least significant 7 or 8 bits. Other error codes are
+ // bucketed.
+ uint32_t sample;
+ if (SUCCEEDED(hr)) {
+ sample = 0;
+ } else if (hr < 0xc00d36b0) {
+ sample = 1; // low bucket
+ } else if (hr < 0xc00d3700) {
+ sample = hr & 0xffU; // MF_E_*
+ } else if (hr <= 0xc00d3705) {
+ sample = 0x80 + (hr & 0xfU); // more MF_E_*
+ } else if (hr < 0xc00d6d60) {
+ sample = 2; // mid bucket
+ } else if (hr <= 0xc00d6d78) {
+ sample = hr & 0xffU; // MF_E_TRANSFORM_*
+ } else {
+ sample = 3; // high bucket
+ }
+}
+
+RefPtr<ShutdownPromise> WMFMediaDataDecoder::Shutdown() {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
+ mIsShutDown = true;
+
+ return InvokeAsync(mTaskQueue, __func__, [self = RefPtr{this}, this] {
+ if (mMFTManager) {
+ mMFTManager->Shutdown();
+ mMFTManager = nullptr;
+ if (!mRecordedError && mHasSuccessfulOutput) {
+ SendTelemetry(S_OK);
+ }
+ }
+ return mTaskQueue->BeginShutdown();
+ });
+}
+
+// Inserts data into the decoder's pipeline.
+RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::Decode(
+ MediaRawData* aSample) {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
+
+ return InvokeAsync<MediaRawData*>(
+ mTaskQueue, this, __func__, &WMFMediaDataDecoder::ProcessDecode, aSample);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessError(
+ HRESULT aError, const char* aReason) {
+ if (!mRecordedError) {
+ SendTelemetry(aError);
+ mRecordedError = true;
+ }
+
+ nsPrintfCString markerString(
+ "WMFMediaDataDecoder::ProcessError for decoder with description %s with "
+ "reason: %s",
+ GetDescriptionName().get(), aReason);
+ LOG(markerString.get());
+ PROFILER_MARKER_TEXT("WMFDecoder Error", MEDIA_PLAYBACK, {}, markerString);
+
+ // TODO: For the error DXGI_ERROR_DEVICE_RESET, we could return
+ // NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER to get the latest device. Maybe retry
+ // up to 3 times.
+ return DecodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("%s:%x", aReason, aError)),
+ __func__);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessDecode(
+ MediaRawData* aSample) {
+ DecodedData results;
+ HRESULT hr = mMFTManager->Input(aSample);
+ if (hr == MF_E_NOTACCEPTING) {
+ hr = ProcessOutput(results);
+ if (FAILED(hr) && hr != MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ return ProcessError(hr, "MFTManager::Output(1)");
+ }
+ hr = mMFTManager->Input(aSample);
+ }
+
+ if (FAILED(hr)) {
+ NS_WARNING("MFTManager rejected sample");
+ return ProcessError(hr, "MFTManager::Input");
+ }
+
+ if (!mLastTime || aSample->mTime > *mLastTime) {
+ mLastTime = Some(aSample->mTime);
+ mLastDuration = aSample->mDuration;
+ }
+
+ mSamplesCount++;
+ mDrainStatus = DrainStatus::DRAINABLE;
+ mLastStreamOffset = aSample->mOffset;
+
+ hr = ProcessOutput(results);
+ if (SUCCEEDED(hr) || hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ return DecodePromise::CreateAndResolve(std::move(results), __func__);
+ }
+ return ProcessError(hr, "MFTManager::Output(2)");
+}
+
+HRESULT
+WMFMediaDataDecoder::ProcessOutput(DecodedData& aResults) {
+ RefPtr<MediaData> output;
+ HRESULT hr = S_OK;
+ while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output))) {
+ MOZ_ASSERT(output.get(), "Upon success, we must receive an output");
+ mHasSuccessfulOutput = true;
+ aResults.AppendElement(std::move(output));
+ if (mDrainStatus == DrainStatus::DRAINING) {
+ break;
+ }
+ }
+ return hr;
+}
+
+RefPtr<MediaDataDecoder::FlushPromise> WMFMediaDataDecoder::ProcessFlush() {
+ if (mMFTManager) {
+ mMFTManager->Flush();
+ }
+ mDrainStatus = DrainStatus::DRAINED;
+ mSamplesCount = 0;
+ mLastTime.reset();
+ return FlushPromise::CreateAndResolve(true, __func__);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise> WMFMediaDataDecoder::Flush() {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
+
+ return InvokeAsync(mTaskQueue, this, __func__,
+ &WMFMediaDataDecoder::ProcessFlush);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessDrain() {
+ if (!mMFTManager || mDrainStatus == DrainStatus::DRAINED) {
+ return DecodePromise::CreateAndResolve(DecodedData(), __func__);
+ }
+
+ if (mDrainStatus != DrainStatus::DRAINING) {
+ // Order the decoder to drain...
+ mMFTManager->Drain();
+ mDrainStatus = DrainStatus::DRAINING;
+ }
+
+ // Then extract all available output.
+ DecodedData results;
+ HRESULT hr = ProcessOutput(results);
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ mDrainStatus = DrainStatus::DRAINED;
+ }
+ if (SUCCEEDED(hr) || hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ if (results.Length() > 0 &&
+ results.LastElement()->mType == MediaData::Type::VIDEO_DATA) {
+ const RefPtr<MediaData>& data = results.LastElement();
+ if (mSamplesCount == 1 && data->mTime == media::TimeUnit::Zero()) {
+ // WMF is unable to calculate a duration if only a single sample
+ // was parsed. Additionally, the pts always comes out at 0 under those
+ // circumstances.
+ // Seeing that we've only fed the decoder a single frame, the pts
+ // and duration are known, it's of the last sample.
+ data->mTime = *mLastTime;
+ }
+ if (data->mTime == *mLastTime) {
+ // The WMF Video decoder is sometimes unable to provide a valid duration
+ // on the last sample even if it has been first set through
+ // SetSampleTime (appears to always happen on Windows 7). So we force
+ // set the duration of the last sample as it was input.
+ data->mDuration = mLastDuration;
+ }
+ } else if (results.Length() == 1 &&
+ results.LastElement()->mType == MediaData::Type::AUDIO_DATA) {
+ // When we drain the audio decoder and one frame was queued (such as with
+ // AAC) the MFT will re-calculate the starting time rather than use the
+ // value set on the IMF Sample.
+ // This is normally an okay thing to do; however when dealing with poorly
+ // muxed content that has incorrect start time, it could lead to broken
+ // A/V sync. So we ensure that we use the compressed sample's time
+ // instead. Additionally, this is what all other audio decoders are doing
+ // anyway.
+ MOZ_ASSERT(mLastTime,
+ "We must have attempted to decode at least one frame to get "
+ "one decoded output");
+ results.LastElement()->As<AudioData>()->SetOriginalStartTime(*mLastTime);
+ }
+ return DecodePromise::CreateAndResolve(std::move(results), __func__);
+ }
+ return ProcessError(hr, "MFTManager::Output");
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::Drain() {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
+
+ return InvokeAsync(mTaskQueue, this, __func__,
+ &WMFMediaDataDecoder::ProcessDrain);
+}
+
+bool WMFMediaDataDecoder::IsHardwareAccelerated(
+ nsACString& aFailureReason) const {
+ MOZ_ASSERT(!mIsShutDown);
+
+ return mMFTManager && mMFTManager->IsHardwareAccelerated(aFailureReason);
+}
+
+void WMFMediaDataDecoder::SetSeekThreshold(const media::TimeUnit& aTime) {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
+
+ RefPtr<WMFMediaDataDecoder> self = this;
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "WMFMediaDataDecoder::SetSeekThreshold", [self, aTime]() {
+ media::TimeUnit threshold = aTime;
+ self->mMFTManager->SetSeekThreshold(threshold);
+ });
+ nsresult rv = mTaskQueue->Dispatch(runnable.forget());
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+ Unused << rv;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/wmf/WMFMediaDataDecoder.h b/dom/media/platforms/wmf/WMFMediaDataDecoder.h
new file mode 100644
index 0000000000..686079226b
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.h
@@ -0,0 +1,161 @@
+/* -*- 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/. */
+
+#if !defined(WMFMediaDataDecoder_h_)
+# define WMFMediaDataDecoder_h_
+
+# include "MFTDecoder.h"
+# include "PlatformDecoderModule.h"
+# include "WMF.h"
+# include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+// Encapsulates the initialization of the MFTDecoder appropriate for decoding
+// a given stream, and the process of converting the IMFSample produced
+// by the MFT into a MediaData object.
+class MFTManager {
+ public:
+ virtual ~MFTManager() {}
+
+ // Submit a compressed sample for decoding.
+ // This should forward to the MFTDecoder after performing
+ // any required sample formatting.
+ virtual HRESULT Input(MediaRawData* aSample) = 0;
+
+ // Produces decoded output, if possible. Blocks until output can be produced,
+ // or until no more is able to be produced.
+ // Returns S_OK on success, or MF_E_TRANSFORM_NEED_MORE_INPUT if there's not
+ // enough data to produce more output. If this returns a failure code other
+ // than MF_E_TRANSFORM_NEED_MORE_INPUT, an error will be reported to the
+ // MP4Reader.
+ virtual HRESULT Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutput) = 0;
+
+ void Flush() {
+ mDecoder->Flush();
+ mSeekTargetThreshold.reset();
+ }
+
+ void Drain() {
+ if (FAILED(mDecoder->SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0))) {
+ NS_WARNING("Failed to send DRAIN command to MFT");
+ }
+ }
+
+ // Destroys all resources.
+ virtual void Shutdown() = 0;
+
+ virtual bool IsHardwareAccelerated(nsACString& aFailureReason) const {
+ return false;
+ }
+
+ virtual TrackInfo::TrackType GetType() = 0;
+
+ virtual nsCString GetDescriptionName() const = 0;
+
+ virtual void SetSeekThreshold(const media::TimeUnit& aTime) {
+ if (aTime.IsValid()) {
+ mSeekTargetThreshold = Some(aTime);
+ } else {
+ mSeekTargetThreshold.reset();
+ }
+ }
+
+ virtual MediaDataDecoder::ConversionRequired NeedsConversion() const {
+ return MediaDataDecoder::ConversionRequired::kNeedNone;
+ }
+
+ protected:
+ // IMFTransform wrapper that performs the decoding.
+ RefPtr<MFTDecoder> mDecoder;
+
+ Maybe<media::TimeUnit> mSeekTargetThreshold;
+};
+
+DDLoggedTypeDeclNameAndBase(WMFMediaDataDecoder, MediaDataDecoder);
+
+// Decodes audio and video using Windows Media Foundation. Samples are decoded
+// using the MFTDecoder created by the MFTManager. This class implements
+// the higher-level logic that drives mapping the MFT to the async
+// MediaDataDecoder interface. The specifics of decoding the exact stream
+// type are handled by MFTManager and the MFTDecoder it creates.
+class WMFMediaDataDecoder
+ : public MediaDataDecoder,
+ public DecoderDoctorLifeLogger<WMFMediaDataDecoder> {
+ public:
+ explicit WMFMediaDataDecoder(MFTManager* aOutputSource);
+ ~WMFMediaDataDecoder();
+
+ RefPtr<MediaDataDecoder::InitPromise> Init() override;
+
+ RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
+
+ RefPtr<DecodePromise> Drain() override;
+
+ RefPtr<FlushPromise> Flush() override;
+
+ RefPtr<ShutdownPromise> Shutdown() override;
+
+ bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
+
+ nsCString GetDescriptionName() const override {
+ return mMFTManager ? mMFTManager->GetDescriptionName() : ""_ns;
+ }
+
+ ConversionRequired NeedsConversion() const override {
+ MOZ_ASSERT(mMFTManager);
+ return mMFTManager->NeedsConversion();
+ }
+
+ virtual void SetSeekThreshold(const media::TimeUnit& aTime) override;
+
+ private:
+ RefPtr<DecodePromise> ProcessError(HRESULT aError, const char* aReason);
+
+ // Called on the task queue. Inserts the sample into the decoder, and
+ // extracts output if available.
+ RefPtr<DecodePromise> ProcessDecode(MediaRawData* aSample);
+
+ // Called on the task queue. Extracts output if available, and delivers
+ // it to the reader. Called after ProcessDecode() and ProcessDrain().
+ HRESULT ProcessOutput(DecodedData& aResults);
+
+ // Called on the task queue. Orders the MFT to flush. There is no output to
+ // extract.
+ RefPtr<FlushPromise> ProcessFlush();
+
+ // Called on the task queue. Orders the MFT to drain, and then extracts
+ // all available output.
+ RefPtr<DecodePromise> ProcessDrain();
+
+ const RefPtr<TaskQueue> mTaskQueue;
+
+ UniquePtr<MFTManager> mMFTManager;
+
+ // The last offset into the media resource that was passed into Input().
+ // This is used to approximate the decoder's position in the media resource.
+ int64_t mLastStreamOffset;
+ Maybe<media::TimeUnit> mLastTime;
+ media::TimeUnit mLastDuration;
+ int64_t mSamplesCount = 0;
+
+ bool mIsShutDown = false;
+
+ enum class DrainStatus {
+ DRAINED,
+ DRAINABLE,
+ DRAINING,
+ };
+ DrainStatus mDrainStatus = DrainStatus::DRAINED;
+
+ // For telemetry
+ bool mHasSuccessfulOutput = false;
+ bool mRecordedError = false;
+};
+
+} // namespace mozilla
+
+#endif // WMFMediaDataDecoder_h_
diff --git a/dom/media/platforms/wmf/WMFUtils.cpp b/dom/media/platforms/wmf/WMFUtils.cpp
new file mode 100644
index 0000000000..b56ff47c90
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFUtils.cpp
@@ -0,0 +1,323 @@
+/* -*- 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 "WMFUtils.h"
+#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>
+#include "mozilla/mscom/EnsureMTA.h"
+#include "mozilla/WindowsVersion.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
+
+namespace mozilla {
+
+using media::TimeUnit;
+
+HRESULT
+HNsToFrames(int64_t aHNs, uint32_t aRate, int64_t* aOutFrames) {
+ MOZ_ASSERT(aOutFrames);
+ const int64_t HNS_PER_S = USECS_PER_S * 10;
+ CheckedInt<int64_t> i = aHNs;
+ i *= aRate;
+ i /= HNS_PER_S;
+ NS_ENSURE_TRUE(i.isValid(), E_FAIL);
+ *aOutFrames = i.value();
+ return S_OK;
+}
+
+HRESULT
+GetDefaultStride(IMFMediaType* aType, uint32_t aWidth, uint32_t* aOutStride) {
+ // Try to get the default stride from the media type.
+ HRESULT hr = aType->GetUINT32(MF_MT_DEFAULT_STRIDE, aOutStride);
+ if (SUCCEEDED(hr)) {
+ return S_OK;
+ }
+
+ // Stride attribute not set, calculate it.
+ GUID subtype = GUID_NULL;
+
+ hr = aType->GetGUID(MF_MT_SUBTYPE, &subtype);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = wmf::MFGetStrideForBitmapInfoHeader(subtype.Data1, aWidth,
+ (LONG*)(aOutStride));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ return hr;
+}
+
+gfx::YUVColorSpace GetYUVColorSpace(IMFMediaType* aType) {
+ UINT32 yuvColorMatrix;
+ HRESULT hr = aType->GetUINT32(MF_MT_YUV_MATRIX, &yuvColorMatrix);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), gfx::YUVColorSpace::UNKNOWN);
+
+ switch (yuvColorMatrix) {
+ case MFVideoTransferMatrix_BT2020_10:
+ case MFVideoTransferMatrix_BT2020_12:
+ return gfx::YUVColorSpace::BT2020;
+ case MFVideoTransferMatrix_BT709:
+ return gfx::YUVColorSpace::BT709;
+ case MFVideoTransferMatrix_BT601:
+ return gfx::YUVColorSpace::BT601;
+ default:
+ return gfx::YUVColorSpace::UNKNOWN;
+ }
+}
+
+int32_t MFOffsetToInt32(const MFOffset& aOffset) {
+ return int32_t(aOffset.value + (aOffset.fract / 65536.0f));
+}
+
+TimeUnit GetSampleDuration(IMFSample* aSample) {
+ NS_ENSURE_TRUE(aSample, TimeUnit::Invalid());
+ int64_t duration = 0;
+ aSample->GetSampleDuration(&duration);
+ return TimeUnit::FromMicroseconds(HNsToUsecs(duration));
+}
+
+TimeUnit GetSampleTime(IMFSample* aSample) {
+ NS_ENSURE_TRUE(aSample, TimeUnit::Invalid());
+ LONGLONG timestampHns = 0;
+ HRESULT hr = aSample->GetSampleTime(&timestampHns);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), TimeUnit::Invalid());
+ return TimeUnit::FromMicroseconds(HNsToUsecs(timestampHns));
+}
+
+// Gets the sub-region of the video frame that should be displayed.
+// See:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, gfx::IntRect& aOutPictureRegion) {
+ // Determine if "pan and scan" is enabled for this media. If it is, we
+ // only display a region of the video frame, not the entire frame.
+ BOOL panScan =
+ MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE);
+
+ // If pan and scan mode is enabled. Try to get the display region.
+ HRESULT hr = E_FAIL;
+ MFVideoArea videoArea;
+ memset(&videoArea, 0, sizeof(MFVideoArea));
+ if (panScan) {
+ hr = aMediaType->GetBlob(MF_MT_PAN_SCAN_APERTURE, (UINT8*)&videoArea,
+ sizeof(MFVideoArea), nullptr);
+ }
+
+ // If we're not in pan-and-scan mode, or the pan-and-scan region is not set,
+ // check for a minimimum display aperture.
+ if (!panScan || hr == MF_E_ATTRIBUTENOTFOUND) {
+ hr = aMediaType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, (UINT8*)&videoArea,
+ sizeof(MFVideoArea), nullptr);
+ }
+
+ if (hr == MF_E_ATTRIBUTENOTFOUND) {
+ // Minimum display aperture is not set, for "backward compatibility with
+ // some components", check for a geometric aperture.
+ hr = aMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE, (UINT8*)&videoArea,
+ sizeof(MFVideoArea), nullptr);
+ }
+
+ if (SUCCEEDED(hr)) {
+ // The media specified a picture region, return it.
+ aOutPictureRegion = gfx::IntRect(MFOffsetToInt32(videoArea.OffsetX),
+ MFOffsetToInt32(videoArea.OffsetY),
+ videoArea.Area.cx, videoArea.Area.cy);
+ return S_OK;
+ }
+
+ // No picture region defined, fall back to using the entire video area.
+ UINT32 width = 0, height = 0;
+ hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
+ 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 = gfx::IntRect(0, 0, width, height);
+ return S_OK;
+}
+
+nsString GetProgramW6432Path() {
+ char* programPath = PR_GetEnvSecure("ProgramW6432");
+ if (!programPath) {
+ programPath = PR_GetEnvSecure("ProgramFiles");
+ }
+
+ if (!programPath) {
+ return u"C:\\Program Files"_ns;
+ }
+ return NS_ConvertUTF8toUTF16(programPath);
+}
+
+namespace wmf {
+
+static const wchar_t* sDLLs[] = {
+ L"mfplat.dll",
+ L"mf.dll",
+ L"dxva2.dll",
+ L"evr.dll",
+};
+
+HRESULT
+LoadDLLs() {
+ static bool sDLLsLoaded = false;
+ static bool sFailedToLoadDlls = false;
+
+ if (sDLLsLoaded) {
+ return S_OK;
+ }
+ if (sFailedToLoadDlls) {
+ return E_FAIL;
+ }
+
+ // Try to load all the required DLLs. If we fail to load any dll,
+ // unload the dlls we succeeded in loading.
+ nsTArray<const wchar_t*> loadedDlls;
+ for (const wchar_t* dll : sDLLs) {
+ if (!LoadLibrarySystem32(dll)) {
+ NS_WARNING("Failed to load WMF DLLs");
+ for (const wchar_t* loadedDll : loadedDlls) {
+ FreeLibrary(GetModuleHandleW(loadedDll));
+ }
+ sFailedToLoadDlls = true;
+ return E_FAIL;
+ }
+ loadedDlls.AppendElement(dll);
+ }
+ sDLLsLoaded = true;
+
+ return S_OK;
+}
+
+#define ENSURE_FUNCTION_PTR_HELPER(FunctionType, FunctionName, DLL) \
+ static FunctionType FunctionName##Ptr = nullptr; \
+ if (!FunctionName##Ptr) { \
+ FunctionName##Ptr = (FunctionType)GetProcAddress( \
+ GetModuleHandleW(L## #DLL), #FunctionName); \
+ if (!FunctionName##Ptr) { \
+ NS_WARNING("Failed to get GetProcAddress of " #FunctionName \
+ " from " #DLL); \
+ return E_FAIL; \
+ } \
+ }
+
+#define ENSURE_FUNCTION_PTR(FunctionName, DLL) \
+ ENSURE_FUNCTION_PTR_HELPER(decltype(::FunctionName)*, FunctionName, DLL)
+
+#define ENSURE_FUNCTION_PTR_(FunctionName, DLL) \
+ ENSURE_FUNCTION_PTR_HELPER(FunctionName##Ptr_t, FunctionName, DLL)
+
+#define DECL_FUNCTION_PTR(FunctionName, ...) \
+ typedef HRESULT(STDMETHODCALLTYPE* FunctionName##Ptr_t)(__VA_ARGS__)
+
+HRESULT
+MFStartup() {
+ if (IsWin7AndPre2000Compatible()) {
+ /*
+ * Specific exclude the usage of WMF on Win 7 with compatibility mode
+ * prior to Win 2000 as we may crash while trying to startup WMF.
+ * Using GetVersionEx API which takes compatibility mode into account.
+ * See Bug 1279171.
+ */
+ return E_FAIL;
+ }
+
+ HRESULT hr = LoadDLLs();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ const int MF_WIN7_VERSION = (0x0002 << 16 | MF_API_VERSION);
+
+ // decltype is unusable for functions having default parameters
+ DECL_FUNCTION_PTR(MFStartup, ULONG, DWORD);
+ ENSURE_FUNCTION_PTR_(MFStartup, Mfplat.dll)
+
+ hr = E_FAIL;
+ mozilla::mscom::EnsureMTA(
+ [&]() -> void { hr = MFStartupPtr(MF_WIN7_VERSION, MFSTARTUP_FULL); });
+ return hr;
+}
+
+HRESULT
+MFShutdown() {
+ ENSURE_FUNCTION_PTR(MFShutdown, Mfplat.dll)
+ HRESULT hr = E_FAIL;
+ mozilla::mscom::EnsureMTA([&]() -> void { hr = (MFShutdownPtr)(); });
+ return hr;
+}
+
+HRESULT
+MFCreateMediaType(IMFMediaType** aOutMFType) {
+ ENSURE_FUNCTION_PTR(MFCreateMediaType, Mfplat.dll)
+ return (MFCreateMediaTypePtr)(aOutMFType);
+}
+
+HRESULT
+MFGetStrideForBitmapInfoHeader(DWORD aFormat, DWORD aWidth, LONG* aOutStride) {
+ ENSURE_FUNCTION_PTR(MFGetStrideForBitmapInfoHeader, evr.dll)
+ return (MFGetStrideForBitmapInfoHeaderPtr)(aFormat, aWidth, aOutStride);
+}
+
+HRESULT MFGetService(IUnknown* punkObject, REFGUID guidService, REFIID riid,
+ LPVOID* ppvObject) {
+ ENSURE_FUNCTION_PTR(MFGetService, mf.dll)
+ return (MFGetServicePtr)(punkObject, guidService, riid, ppvObject);
+}
+
+HRESULT
+DXVA2CreateDirect3DDeviceManager9(UINT* pResetToken,
+ IDirect3DDeviceManager9** ppDXVAManager) {
+ ENSURE_FUNCTION_PTR(DXVA2CreateDirect3DDeviceManager9, dxva2.dll)
+ return (DXVA2CreateDirect3DDeviceManager9Ptr)(pResetToken, ppDXVAManager);
+}
+
+HRESULT
+MFCreateSample(IMFSample** ppIMFSample) {
+ ENSURE_FUNCTION_PTR(MFCreateSample, mfplat.dll)
+ return (MFCreateSamplePtr)(ppIMFSample);
+}
+
+HRESULT
+MFCreateAlignedMemoryBuffer(DWORD cbMaxLength, DWORD fAlignmentFlags,
+ IMFMediaBuffer** ppBuffer) {
+ ENSURE_FUNCTION_PTR(MFCreateAlignedMemoryBuffer, mfplat.dll)
+ return (MFCreateAlignedMemoryBufferPtr)(cbMaxLength, fAlignmentFlags,
+ ppBuffer);
+}
+
+HRESULT
+MFCreateDXGIDeviceManager(UINT* pResetToken,
+ IMFDXGIDeviceManager** ppDXVAManager) {
+ ENSURE_FUNCTION_PTR(MFCreateDXGIDeviceManager, mfplat.dll)
+ return (MFCreateDXGIDeviceManagerPtr)(pResetToken, ppDXVAManager);
+}
+
+HRESULT
+MFCreateDXGISurfaceBuffer(REFIID riid, IUnknown* punkSurface,
+ UINT uSubresourceIndex, BOOL fButtomUpWhenLinear,
+ IMFMediaBuffer** ppBuffer) {
+ ENSURE_FUNCTION_PTR(MFCreateDXGISurfaceBuffer, mfplat.dll)
+ return (MFCreateDXGISurfaceBufferPtr)(riid, punkSurface, uSubresourceIndex,
+ fButtomUpWhenLinear, ppBuffer);
+}
+
+} // end namespace wmf
+} // end namespace mozilla
diff --git a/dom/media/platforms/wmf/WMFUtils.h b/dom/media/platforms/wmf/WMFUtils.h
new file mode 100644
index 0000000000..45faa2a557
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFUtils.h
@@ -0,0 +1,63 @@
+/* -*- 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/. */
+
+#ifndef WMFUtils_h
+#define WMFUtils_h
+
+#include "ImageTypes.h"
+#include "TimeUnits.h"
+#include "VideoUtils.h"
+#include "WMF.h"
+#include "mozilla/gfx/Rect.h"
+#include "nsString.h"
+
+// Various utilities shared by WMF backend files.
+
+namespace mozilla {
+
+// Converts from microseconds to hundreds of nanoseconds.
+// We use microseconds for our timestamps, whereas WMF uses
+// hundreds of nanoseconds.
+inline int64_t UsecsToHNs(int64_t aUsecs) { return aUsecs * 10; }
+
+// Converts from hundreds of nanoseconds to microseconds.
+// We use microseconds for our timestamps, whereas WMF uses
+// hundreds of nanoseconds.
+inline int64_t HNsToUsecs(int64_t hNanoSecs) { return hNanoSecs / 10; }
+
+HRESULT HNsToFrames(int64_t aHNs, uint32_t aRate, int64_t* aOutFrames);
+
+HRESULT
+GetDefaultStride(IMFMediaType* aType, uint32_t aWidth, uint32_t* aOutStride);
+
+gfx::YUVColorSpace GetYUVColorSpace(IMFMediaType* aType);
+
+int32_t MFOffsetToInt32(const MFOffset& aOffset);
+
+// Gets the sub-region of the video frame that should be displayed.
+// See:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, gfx::IntRect& aOutPictureRegion);
+
+// Returns the duration of a IMFSample in TimeUnit.
+// Returns media::TimeUnit::Invalid() on failure.
+media::TimeUnit GetSampleDuration(IMFSample* aSample);
+
+// Returns the presentation time of a IMFSample in TimeUnit.
+// Returns media::TimeUnit::Invalid() on failure.
+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
diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
new file mode 100644
index 0000000000..295e20ab7e
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -0,0 +1,843 @@
+/* -*- 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 "WMFVideoMFTManager.h"
+
+#include <psapi.h>
+#include <winsdkver.h>
+#include <algorithm>
+#include "DXVA2Manager.h"
+#include "GMPUtils.h" // For SplitAt. TODO: Move SplitAt to a central place.
+#include "IMFYCbCrImage.h"
+#include "ImageContainer.h"
+#include "Layers.h"
+#include "MP4Decoder.h"
+#include "MediaInfo.h"
+#include "MediaTelemetryConstants.h"
+#include "VPXDecoder.h"
+#include "VideoUtils.h"
+#include "WMFDecoderModule.h"
+#include "WMFUtils.h"
+#include "gfx2DGlue.h"
+#include "gfxWindowsPlatform.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+#include "nsWindowsHelpers.h"
+
+#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;
+
+#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}};
+
+const GUID MFVideoFormat_VP90 = {
+ 0x30395056,
+ 0x0000,
+ 0x0010,
+ {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
+#endif
+
+// Note: CLSID_WebmMfVpxDec needs to be extern for the CanCreateWMFDecoder
+// template in WMFDecoderModule.cpp to work.
+extern const GUID CLSID_WebmMfVpxDec = {
+ 0xe3aaf548,
+ 0xc9a4,
+ 0x4c6e,
+ {0x23, 0x4d, 0x5a, 0xda, 0x37, 0x4b, 0x00, 0x00}};
+
+namespace mozilla {
+
+static bool IsWin7H264Decoder4KCapable() {
+ WCHAR systemPath[MAX_PATH + 1];
+ if (!ConstructSystem32Path(L"msmpeg2vdec.dll", systemPath, MAX_PATH + 1)) {
+ // Cannot build path -> Assume it's the old DLL or it's missing.
+ return false;
+ }
+
+ DWORD zero;
+ DWORD infoSize = GetFileVersionInfoSizeW(systemPath, &zero);
+ if (infoSize == 0) {
+ // Can't get file info -> Assume it's the old DLL or it's missing.
+ return false;
+ }
+ auto infoData = MakeUnique<unsigned char[]>(infoSize);
+ VS_FIXEDFILEINFO* vInfo;
+ UINT vInfoLen;
+ if (GetFileVersionInfoW(systemPath, 0, infoSize, infoData.get()) &&
+ VerQueryValueW(infoData.get(), L"\\", (LPVOID*)&vInfo, &vInfoLen)) {
+ uint64_t version = uint64_t(vInfo->dwFileVersionMS) << 32 |
+ uint64_t(vInfo->dwFileVersionLS);
+ // 12.0.9200.16426 & later allow for >1920x1088 resolutions.
+ const uint64_t minimum =
+ (uint64_t(12) << 48) | (uint64_t(9200) << 16) | uint64_t(16426);
+ return version >= minimum;
+ }
+ // Can't get file version -> Assume it's the old DLL.
+ return false;
+}
+
+LayersBackend GetCompositorBackendType(
+ layers::KnowsCompositor* aKnowsCompositor) {
+ if (aKnowsCompositor) {
+ return aKnowsCompositor->GetCompositorBackendType();
+ }
+ return LayersBackend::LAYERS_NONE;
+}
+
+WMFVideoMFTManager::WMFVideoMFTManager(
+ const VideoInfo& aConfig, layers::KnowsCompositor* aKnowsCompositor,
+ layers::ImageContainer* aImageContainer, float aFramerate,
+ const CreateDecoderParams::OptionSet& aOptions, bool aDXVAEnabled)
+ : mVideoInfo(aConfig),
+ mImageSize(aConfig.mImage),
+ mDecodedImageSize(aConfig.mImage),
+ mVideoStride(0),
+ mColorSpace(aConfig.mColorSpace != gfx::YUVColorSpace::UNKNOWN
+ ? Some(aConfig.mColorSpace)
+ : Nothing()),
+ mColorRange(aConfig.mColorRange),
+ mImageContainer(aImageContainer),
+ mKnowsCompositor(aKnowsCompositor),
+ mDXVAEnabled(aDXVAEnabled &&
+ !aOptions.contains(
+ CreateDecoderParams::Option::HardwareDecoderNotAllowed)),
+ mFramerate(aFramerate),
+ mLowLatency(aOptions.contains(CreateDecoderParams::Option::LowLatency))
+// 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;
+ } else if (VPXDecoder::IsVP8(aConfig.mMimeType)) {
+ mStreamType = VP8;
+ } else if (VPXDecoder::IsVP9(aConfig.mMimeType)) {
+ mStreamType = VP9;
+ } else {
+ mStreamType = Unknown;
+ }
+
+ // The V and U planes are stored 16-row-aligned, so we need to add padding
+ // to the row heights to ensure the Y'CbCr planes are referenced properly.
+ // This value is only used with software decoder.
+ if (mDecodedImageSize.height % 16 != 0) {
+ mDecodedImageSize.height += 16 - (mDecodedImageSize.height % 16);
+ }
+}
+
+WMFVideoMFTManager::~WMFVideoMFTManager() {
+ MOZ_COUNT_DTOR(WMFVideoMFTManager);
+}
+
+const GUID& WMFVideoMFTManager::GetMFTGUID() {
+ MOZ_ASSERT(mStreamType != Unknown);
+ switch (mStreamType) {
+ case H264:
+ return CLSID_CMSH264DecoderMFT;
+ case VP8:
+ return CLSID_WebmMfVpxDec;
+ case VP9:
+ return CLSID_WebmMfVpxDec;
+ default:
+ return GUID_NULL;
+ };
+}
+
+const GUID& WMFVideoMFTManager::GetMediaSubtypeGUID() {
+ MOZ_ASSERT(mStreamType != Unknown);
+ switch (mStreamType) {
+ case H264:
+ return MFVideoFormat_H264;
+ case VP8:
+ return MFVideoFormat_VP80;
+ case VP9:
+ return MFVideoFormat_VP90;
+ default:
+ return GUID_NULL;
+ };
+}
+
+bool WMFVideoMFTManager::InitializeDXVA() {
+ // If we use DXVA but aren't running with a D3D layer manager then the
+ // readback of decoded video frames from GPU to CPU memory grinds painting
+ // to a halt, and makes playback performance *worse*.
+ if (!mDXVAEnabled) {
+ mDXVAFailureReason.AssignLiteral(
+ "Hardware video decoding disabled or blacklisted");
+ return false;
+ }
+ MOZ_ASSERT(!mDXVA2Manager);
+ if (!mKnowsCompositor || !mKnowsCompositor->SupportsD3D11()) {
+ mDXVAFailureReason.AssignLiteral("Unsupported layers backend");
+ return false;
+ }
+
+ if (!XRE_IsRDDProcess() && !XRE_IsGPUProcess()) {
+ mDXVAFailureReason.AssignLiteral(
+ "DXVA only supported in RDD or GPU process");
+ return false;
+ }
+
+ nsACString* failureReason = &mDXVAFailureReason;
+ nsCString secondFailureReason;
+ if (StaticPrefs::media_wmf_dxva_d3d11_enabled() && IsWin8OrLater()) {
+ mDXVA2Manager.reset(
+ DXVA2Manager::CreateD3D11DXVA(mKnowsCompositor, *failureReason));
+ if (mDXVA2Manager) {
+ return true;
+ }
+ // Try again with d3d9, but record the failure reason
+ // into a new var to avoid overwriting the d3d11 failure.
+ failureReason = &secondFailureReason;
+ mDXVAFailureReason.AppendLiteral("; ");
+ }
+
+ mDXVA2Manager.reset(
+ DXVA2Manager::CreateD3D9DXVA(mKnowsCompositor, *failureReason));
+ // Make sure we include the messages from both attempts (if applicable).
+ mDXVAFailureReason.Append(secondFailureReason);
+
+ return mDXVA2Manager != nullptr;
+}
+
+MediaResult WMFVideoMFTManager::ValidateVideoInfo() {
+ if (mStreamType != H264 ||
+ StaticPrefs::media_wmf_allow_unsupported_resolutions()) {
+ return NS_OK;
+ }
+
+ // The WMF H.264 decoder is documented to have a minimum resolution 48x48
+ // pixels for resolution, but we won't enable hw decoding for the resolution <
+ // 132 pixels. It's assumed the software decoder doesn't have this limitation,
+ // but it still might have maximum resolution limitation.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd797815(v=vs.85).aspx
+ const bool Is4KCapable = IsWin8OrLater() || IsWin7H264Decoder4KCapable();
+ static const int32_t MAX_H264_PIXEL_COUNT =
+ Is4KCapable ? 4096 * 2304 : 1920 * 1088;
+ const CheckedInt32 pixelCount =
+ CheckedInt32(mVideoInfo.mImage.width) * mVideoInfo.mImage.height;
+
+ if (!pixelCount.isValid() || pixelCount.value() > MAX_H264_PIXEL_COUNT) {
+ mIsValid = false;
+ return MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Can't decode H.264 stream because its "
+ "resolution is out of the maximum limitation"));
+ }
+
+ return NS_OK;
+}
+
+MediaResult WMFVideoMFTManager::Init() {
+ MediaResult result = ValidateVideoInfo();
+ if (NS_FAILED(result)) {
+ return result;
+ }
+
+ result = InitInternal();
+ if (NS_SUCCEEDED(result) && mDXVA2Manager) {
+ // If we had some failures but eventually made it work,
+ // make sure we preserve the messages.
+ if (mDXVA2Manager->IsD3D11()) {
+ mDXVAFailureReason.AppendLiteral("Using D3D11 API");
+ } else {
+ mDXVAFailureReason.AppendLiteral("Using D3D9 API");
+ }
+ }
+
+ return result;
+}
+
+MediaResult WMFVideoMFTManager::InitInternal() {
+ // The H264 SanityTest uses a 132x132 videos to determine if DXVA can be used.
+ // so we want to use the software decoder for videos with lower resolutions.
+ static const int MIN_H264_HW_WIDTH = 132;
+ static const int MIN_H264_HW_HEIGHT = 132;
+
+ mUseHwAccel = false; // default value; changed if D3D setup succeeds.
+ bool useDxva = (mStreamType != H264 ||
+ (mVideoInfo.ImageRect().width > MIN_H264_HW_WIDTH &&
+ mVideoInfo.ImageRect().height > MIN_H264_HW_HEIGHT)) &&
+ InitializeDXVA();
+
+ RefPtr<MFTDecoder> decoder = new MFTDecoder();
+ HRESULT hr = decoder->Create(GetMFTGUID());
+ NS_ENSURE_TRUE(SUCCEEDED(hr),
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Can't create the MFT decoder.")));
+
+ RefPtr<IMFAttributes> attr(decoder->GetAttributes());
+ UINT32 aware = 0;
+ if (attr) {
+ attr->GetUINT32(MF_SA_D3D_AWARE, &aware);
+ attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads,
+ WMFDecoderModule::GetNumDecoderThreads());
+ bool lowLatency =
+ (StaticPrefs::media_wmf_low_latency_enabled() || IsWin10OrLater()) &&
+ !StaticPrefs::media_wmf_low_latency_force_disabled();
+ if (mLowLatency || lowLatency) {
+ hr = attr->SetUINT32(CODECAPI_AVLowLatencyMode, TRUE);
+ if (SUCCEEDED(hr)) {
+ LOG("Enabling Low Latency Mode");
+ } else {
+ LOG("Couldn't enable Low Latency Mode");
+ }
+ }
+ }
+
+ if (useDxva) {
+ if (aware) {
+ // TODO: Test if I need this anywhere... Maybe on Vista?
+ // hr = attr->SetUINT32(CODECAPI_AVDecVideoAcceleration_H264, TRUE);
+ // NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ MOZ_ASSERT(mDXVA2Manager);
+ ULONG_PTR manager = ULONG_PTR(mDXVA2Manager->GetDXVADeviceManager());
+ hr = decoder->SendMFTMessage(MFT_MESSAGE_SET_D3D_MANAGER, manager);
+ if (SUCCEEDED(hr)) {
+ mUseHwAccel = true;
+ } else {
+ mDXVAFailureReason = nsPrintfCString(
+ "MFT_MESSAGE_SET_D3D_MANAGER failed with code %X", hr);
+ }
+ } else {
+ mDXVAFailureReason.AssignLiteral(
+ "Decoder returned false for MF_SA_D3D_AWARE");
+ }
+ }
+
+ if (!mUseHwAccel) {
+ if (mDXVA2Manager) {
+ // Either mDXVAEnabled was set to false prior the second call to
+ // InitInternal() due to CanUseDXVA() returning false, or
+ // MFT_MESSAGE_SET_D3D_MANAGER failed
+ mDXVA2Manager.reset();
+ }
+ if (mStreamType == VP9 || mStreamType == VP8) {
+ return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Use VP8/9 MFT only if HW acceleration "
+ "is available."));
+ }
+ Telemetry::Accumulate(Telemetry::MEDIA_DECODER_BACKEND_USED,
+ uint32_t(media::MediaDecoderBackend::WMFSoftware));
+ }
+
+ mDecoder = decoder;
+ hr = SetDecoderMediaTypes();
+ NS_ENSURE_TRUE(
+ SUCCEEDED(hr),
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Fail to set the decoder media types.")));
+
+ RefPtr<IMFMediaType> outputType;
+ hr = mDecoder->GetOutputMediaType(outputType);
+ NS_ENSURE_TRUE(
+ SUCCEEDED(hr),
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Fail to get the output media type.")));
+
+ if (mUseHwAccel && !CanUseDXVA(outputType, mFramerate)) {
+ mDXVAEnabled = false;
+ // DXVA initialization with current decoder actually failed,
+ // re-do initialization.
+ return InitInternal();
+ }
+
+ LOG("Video Decoder initialized, Using DXVA: %s",
+ (mUseHwAccel ? "Yes" : "No"));
+
+ if (mUseHwAccel) {
+ hr = mDXVA2Manager->ConfigureForSize(
+ outputType,
+ mColorSpace.refOr(
+ DefaultColorSpace({mImageSize.width, mImageSize.height})),
+ mColorRange, mVideoInfo.ImageRect().width,
+ mVideoInfo.ImageRect().height);
+ NS_ENSURE_TRUE(SUCCEEDED(hr),
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Fail to configure image size for "
+ "DXVA2Manager.")));
+ } else {
+ GetDefaultStride(outputType, mVideoInfo.ImageRect().width, &mVideoStride);
+ }
+ LOG("WMFVideoMFTManager frame geometry stride=%u picture=(%d, %d, %d, %d) "
+ "display=(%d,%d)",
+ mVideoStride, mVideoInfo.ImageRect().x, mVideoInfo.ImageRect().y,
+ mVideoInfo.ImageRect().width, mVideoInfo.ImageRect().height,
+ mVideoInfo.mDisplay.width, mVideoInfo.mDisplay.height);
+
+ if (!mUseHwAccel) {
+ RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetImageDevice();
+ if (device) {
+ mIMFUsable = true;
+ }
+ }
+ return MediaResult(NS_OK);
+}
+
+HRESULT
+WMFVideoMFTManager::SetDecoderMediaTypes() {
+ // Setup the input/output media types.
+ RefPtr<IMFMediaType> inputType;
+ HRESULT hr = wmf::MFCreateMediaType(getter_AddRefs(inputType));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = inputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = inputType->SetGUID(MF_MT_SUBTYPE, GetMediaSubtypeGUID());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = inputType->SetUINT32(MF_MT_INTERLACE_MODE,
+ MFVideoInterlace_MixedInterlaceOrProgressive);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = inputType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = MFSetAttributeSize(inputType, MF_MT_FRAME_SIZE,
+ mVideoInfo.ImageRect().width,
+ mVideoInfo.ImageRect().height);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFMediaType> outputType;
+ hr = wmf::MFCreateMediaType(getter_AddRefs(outputType));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = outputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = MFSetAttributeSize(outputType, MF_MT_FRAME_SIZE,
+ mVideoInfo.ImageRect().width,
+ mVideoInfo.ImageRect().height);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ GUID outputSubType = mUseHwAccel ? MFVideoFormat_NV12 : MFVideoFormat_YV12;
+ hr = outputType->SetGUID(MF_MT_SUBTYPE, outputSubType);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ return mDecoder->SetMediaTypes(inputType, outputType);
+}
+
+HRESULT
+WMFVideoMFTManager::Input(MediaRawData* aSample) {
+ if (!mIsValid) {
+ return E_FAIL;
+ }
+
+ if (!mDecoder) {
+ // This can happen during shutdown.
+ return E_FAIL;
+ }
+
+ if (mStreamType == VP9 && aSample->mKeyframe) {
+ // Check the VP9 profile. the VP9 MFT can only handle correctly profile 0
+ // and 2 (yuv420 8/10/12 bits)
+ int profile =
+ VPXDecoder::GetVP9Profile(Span(aSample->Data(), aSample->Size()));
+ if (profile != 0 && profile != 2) {
+ return E_FAIL;
+ }
+ }
+
+ RefPtr<IMFSample> inputSample;
+ HRESULT hr = mDecoder->CreateInputSample(
+ aSample->Data(), uint32_t(aSample->Size()),
+ aSample->mTime.ToMicroseconds(), aSample->mDuration.ToMicroseconds(),
+ &inputSample);
+ NS_ENSURE_TRUE(SUCCEEDED(hr) && inputSample != nullptr, hr);
+
+ if (!mColorSpace && aSample->mTrackInfo) {
+ // The colorspace definition is found in the H264 SPS NAL, available out of
+ // band, while for VP9 it's only available within the VP9 bytestream.
+ // The info would have been updated by the MediaChangeMonitor.
+ mColorSpace = Some(aSample->mTrackInfo->GetAsVideoInfo()->mColorSpace);
+ mColorRange = aSample->mTrackInfo->GetAsVideoInfo()->mColorRange;
+ }
+ mLastDuration = aSample->mDuration;
+
+ // Forward sample data to the decoder.
+ return mDecoder->Input(inputSample);
+}
+
+// The MFTransform we use for decoding h264 video will silently fall
+// back to software decoding (even if we've negotiated DXVA) if the GPU
+// doesn't support decoding the given resolution. It will then upload
+// the software decoded frames into d3d textures to preserve behaviour.
+//
+// Unfortunately this seems to cause corruption (see bug 1193547) and is
+// slow because the upload is done into a non-shareable texture and requires
+// us to copy it.
+//
+// This code tests if the given resolution can be supported directly on the GPU,
+// and makes sure we only ask the MFT for DXVA if it can be supported properly.
+//
+// Ideally we'd know the framerate during initialization and would also ensure
+// that new decoders are created if the resolution changes. Then we could move
+// this check into Init and consolidate the main thread blocking code.
+bool WMFVideoMFTManager::CanUseDXVA(IMFMediaType* aType, float aFramerate) {
+ MOZ_ASSERT(mDXVA2Manager);
+ // SupportsConfig only checks for valid h264 decoders currently.
+ if (mStreamType != H264) {
+ return true;
+ }
+
+ return mDXVA2Manager->SupportsConfig(aType, aFramerate);
+}
+
+HRESULT
+WMFVideoMFTManager::CreateBasicVideoFrame(IMFSample* aSample,
+ int64_t aStreamOffset,
+ VideoData** aOutVideoData) {
+ NS_ENSURE_TRUE(aSample, E_POINTER);
+ NS_ENSURE_TRUE(aOutVideoData, E_POINTER);
+
+ *aOutVideoData = nullptr;
+
+ HRESULT hr;
+ RefPtr<IMFMediaBuffer> buffer;
+
+ // Must convert to contiguous buffer to use IMD2DBuffer interface.
+ hr = aSample->ConvertToContiguousBuffer(getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ // Try and use the IMF2DBuffer interface if available, otherwise fallback
+ // to the IMFMediaBuffer interface. Apparently IMF2DBuffer is more efficient,
+ // but only some systems (Windows 8?) support it.
+ BYTE* data = nullptr;
+ LONG stride = 0;
+ RefPtr<IMF2DBuffer> twoDBuffer;
+ hr = buffer->QueryInterface(
+ static_cast<IMF2DBuffer**>(getter_AddRefs(twoDBuffer)));
+ if (SUCCEEDED(hr)) {
+ hr = twoDBuffer->Lock2D(&data, &stride);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ } else {
+ hr = buffer->Lock(&data, nullptr, nullptr);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ stride = mVideoStride;
+ }
+
+ const GUID& subType = mDecoder->GetOutputMediaSubType();
+ MOZ_DIAGNOSTIC_ASSERT(subType == MFVideoFormat_YV12 ||
+ subType == MFVideoFormat_P010 ||
+ subType == MFVideoFormat_P016);
+ const gfx::ColorDepth colorDepth = subType == MFVideoFormat_YV12
+ ? gfx::ColorDepth::COLOR_8
+ : gfx::ColorDepth::COLOR_16;
+
+ // YV12, planar format (3 planes): [YYYY....][VVVV....][UUUU....]
+ // i.e., Y, then V, then U.
+ // P010, P016 planar format (2 planes) [YYYY....][UVUV...]
+ // See
+ // https://docs.microsoft.com/en-us/windows/desktop/medfound/10-bit-and-16-bit-yuv-video-formats
+ VideoData::YCbCrBuffer b;
+
+ uint32_t videoWidth = mImageSize.width;
+ uint32_t videoHeight = mImageSize.height;
+
+ // Y (Y') plane
+ b.mPlanes[0].mData = data;
+ b.mPlanes[0].mStride = stride;
+ b.mPlanes[0].mHeight = videoHeight;
+ b.mPlanes[0].mWidth = videoWidth;
+ b.mPlanes[0].mSkip = 0;
+
+ MOZ_DIAGNOSTIC_ASSERT(mDecodedImageSize.height % 16 == 0,
+ "decoded height must be 16 bytes aligned");
+ uint32_t y_size = stride * mDecodedImageSize.height;
+ uint32_t v_size = stride * mDecodedImageSize.height / 4;
+ uint32_t halfStride = (stride + 1) / 2;
+ uint32_t halfHeight = (videoHeight + 1) / 2;
+ uint32_t halfWidth = (videoWidth + 1) / 2;
+
+ if (subType == MFVideoFormat_YV12) {
+ // U plane (Cb)
+ b.mPlanes[1].mData = data + y_size + v_size;
+ b.mPlanes[1].mStride = halfStride;
+ b.mPlanes[1].mHeight = halfHeight;
+ b.mPlanes[1].mWidth = halfWidth;
+ b.mPlanes[1].mSkip = 0;
+
+ // V plane (Cr)
+ b.mPlanes[2].mData = data + y_size;
+ b.mPlanes[2].mStride = halfStride;
+ b.mPlanes[2].mHeight = halfHeight;
+ b.mPlanes[2].mWidth = halfWidth;
+ b.mPlanes[2].mSkip = 0;
+ } else {
+ // U plane (Cb)
+ b.mPlanes[1].mData = data + y_size;
+ b.mPlanes[1].mStride = stride;
+ b.mPlanes[1].mHeight = halfHeight;
+ b.mPlanes[1].mWidth = halfWidth;
+ b.mPlanes[1].mSkip = 1;
+
+ // V plane (Cr)
+ b.mPlanes[2].mData = data + y_size + sizeof(short);
+ b.mPlanes[2].mStride = stride;
+ b.mPlanes[2].mHeight = halfHeight;
+ b.mPlanes[2].mWidth = halfWidth;
+ b.mPlanes[2].mSkip = 1;
+ }
+
+ // YuvColorSpace
+ b.mYUVColorSpace =
+ mColorSpace.refOr(DefaultColorSpace({videoWidth, videoHeight}));
+ b.mColorDepth = colorDepth;
+ b.mColorRange = mColorRange;
+
+ TimeUnit pts = GetSampleTime(aSample);
+ NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
+ TimeUnit duration = GetSampleDuration(aSample);
+ NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
+ gfx::IntRect pictureRegion =
+ mVideoInfo.ScaledImageRect(videoWidth, videoHeight);
+
+ if (colorDepth != gfx::ColorDepth::COLOR_8 || !mKnowsCompositor ||
+ !mKnowsCompositor->SupportsD3D11() || !mIMFUsable) {
+ RefPtr<VideoData> v = VideoData::CreateAndCopyData(
+ mVideoInfo, mImageContainer, aStreamOffset, pts, duration, b, false,
+ TimeUnit::FromMicroseconds(-1), pictureRegion, mKnowsCompositor);
+ if (twoDBuffer) {
+ twoDBuffer->Unlock2D();
+ } else {
+ buffer->Unlock();
+ }
+ v.forget(aOutVideoData);
+ return S_OK;
+ }
+
+ RefPtr<layers::PlanarYCbCrImage> image =
+ new IMFYCbCrImage(buffer, twoDBuffer, mKnowsCompositor, mImageContainer);
+
+ VideoData::SetVideoDataToImage(image, mVideoInfo, b, pictureRegion, false);
+
+ RefPtr<VideoData> v = VideoData::CreateFromImage(
+ mVideoInfo.mDisplay, aStreamOffset, pts, duration, image.forget(), false,
+ TimeUnit::FromMicroseconds(-1));
+
+ v.forget(aOutVideoData);
+ return S_OK;
+}
+
+HRESULT
+WMFVideoMFTManager::CreateD3DVideoFrame(IMFSample* aSample,
+ int64_t aStreamOffset,
+ VideoData** aOutVideoData) {
+ NS_ENSURE_TRUE(aSample, E_POINTER);
+ NS_ENSURE_TRUE(aOutVideoData, E_POINTER);
+ NS_ENSURE_TRUE(mDXVA2Manager, E_ABORT);
+ NS_ENSURE_TRUE(mUseHwAccel, E_ABORT);
+
+ *aOutVideoData = nullptr;
+ HRESULT hr;
+
+ gfx::IntRect pictureRegion =
+ mVideoInfo.ScaledImageRect(mImageSize.width, mImageSize.height);
+ RefPtr<Image> image;
+ hr =
+ mDXVA2Manager->CopyToImage(aSample, pictureRegion, getter_AddRefs(image));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ NS_ENSURE_TRUE(image, E_FAIL);
+
+ TimeUnit pts = GetSampleTime(aSample);
+ NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
+ TimeUnit duration = GetSampleDuration(aSample);
+ NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
+ RefPtr<VideoData> v = VideoData::CreateFromImage(
+ mVideoInfo.mDisplay, aStreamOffset, pts, duration, image.forget(), false,
+ TimeUnit::FromMicroseconds(-1));
+
+ NS_ENSURE_TRUE(v, E_FAIL);
+ v.forget(aOutVideoData);
+
+ return S_OK;
+}
+
+// Blocks until decoded sample is produced by the decoder.
+HRESULT
+WMFVideoMFTManager::Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutData) {
+ RefPtr<IMFSample> sample;
+ HRESULT hr;
+ aOutData = nullptr;
+ int typeChangeCount = 0;
+
+ // Loop until we decode a sample, or an unexpected error that we can't
+ // handle occurs.
+ while (true) {
+ hr = mDecoder->Output(&sample);
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ return MF_E_TRANSFORM_NEED_MORE_INPUT;
+ }
+
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ MOZ_ASSERT(!sample);
+ // Video stream output type change, probably geometric aperture change or
+ // pixel type.
+ // We must reconfigure the decoder output type.
+
+ // Attempt to find an appropriate OutputType, trying in order:
+ // if HW accelerated: NV12, P010, P016
+ // if SW: YV12, P010, P016
+ if (FAILED(
+ (hr = (mDecoder->FindDecoderOutputTypeWithSubtype(
+ mUseHwAccel ? MFVideoFormat_NV12 : MFVideoFormat_YV12)))) &&
+ FAILED((hr = mDecoder->FindDecoderOutputTypeWithSubtype(
+ MFVideoFormat_P010))) &&
+ FAILED((hr = mDecoder->FindDecoderOutputTypeWithSubtype(
+ MFVideoFormat_P016)))) {
+ LOG("No suitable output format found");
+ return hr;
+ }
+
+ RefPtr<IMFMediaType> outputType;
+ hr = mDecoder->GetOutputMediaType(outputType);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (mUseHwAccel) {
+ hr = mDXVA2Manager->ConfigureForSize(
+ outputType,
+ mColorSpace.refOr(
+ DefaultColorSpace({mImageSize.width, mImageSize.height})),
+ mColorRange, mVideoInfo.ImageRect().width,
+ mVideoInfo.ImageRect().height);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ } else {
+ // The stride may have changed, recheck for it.
+ hr = GetDefaultStride(outputType, mVideoInfo.ImageRect().width,
+ &mVideoStride);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ UINT32 width = 0, height = 0;
+ hr = MFGetAttributeSize(outputType, MF_MT_FRAME_SIZE, &width, &height);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ NS_ENSURE_TRUE(width <= MAX_VIDEO_WIDTH, E_FAIL);
+ NS_ENSURE_TRUE(height <= MAX_VIDEO_HEIGHT, E_FAIL);
+ mDecodedImageSize = gfx::IntSize(width, height);
+ }
+ // Catch infinite loops, but some decoders perform at least 2 stream
+ // changes on consecutive calls, so be permissive.
+ // 100 is arbitrarily > 2.
+ NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE);
+ // Loop back and try decoding again...
+ ++typeChangeCount;
+ continue;
+ }
+
+ if (SUCCEEDED(hr)) {
+ if (!sample) {
+ LOG("Video MFTDecoder returned success but no output!");
+ // On some machines/input the MFT returns success but doesn't output
+ // a video frame. If we detect this, try again, but only up to a
+ // point; after 250 failures, give up. Note we count all failures
+ // over the life of the decoder, as we may end up exiting with a
+ // NEED_MORE_INPUT and coming back to hit the same error. So just
+ // counting with a local variable (like typeChangeCount does) may
+ // not work in this situation.
+ ++mNullOutputCount;
+ if (mNullOutputCount > 250) {
+ LOG("Excessive Video MFTDecoder returning success but no output; "
+ "giving up");
+ mGotExcessiveNullOutput = true;
+ return E_FAIL;
+ }
+ continue;
+ }
+ TimeUnit pts = GetSampleTime(sample);
+ TimeUnit duration = GetSampleDuration(sample);
+ if (!pts.IsValid() || !duration.IsValid()) {
+ return E_FAIL;
+ }
+ if (mSeekTargetThreshold.isSome()) {
+ if ((pts + duration) < mSeekTargetThreshold.ref()) {
+ LOG("Dropping video frame which pts is smaller than seek target.");
+ // It is necessary to clear the pointer to release the previous output
+ // buffer.
+ sample = nullptr;
+ continue;
+ }
+ mSeekTargetThreshold.reset();
+ }
+ break;
+ }
+ // Else unexpected error so bail.
+ NS_WARNING("WMFVideoMFTManager::Output() unexpected error");
+ return hr;
+ }
+
+ RefPtr<VideoData> frame;
+ if (mUseHwAccel) {
+ hr = CreateD3DVideoFrame(sample, aStreamOffset, getter_AddRefs(frame));
+ } else {
+ hr = CreateBasicVideoFrame(sample, aStreamOffset, getter_AddRefs(frame));
+ }
+ // Frame should be non null only when we succeeded.
+ MOZ_ASSERT((frame != nullptr) == SUCCEEDED(hr));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ NS_ENSURE_TRUE(frame, E_FAIL);
+
+ aOutData = frame;
+ // The VP9 decoder doesn't provide a valid duration. As VP9 doesn't have a
+ // concept of pts vs dts and have no latency. We can as such use the last
+ // known input duration.
+ if (mStreamType == VP9 && aOutData->mDuration == TimeUnit::Zero()) {
+ aOutData->mDuration = mLastDuration;
+ }
+
+ if (mNullOutputCount) {
+ mGotValidOutputAfterNullOutput = true;
+ }
+
+ return S_OK;
+}
+
+void WMFVideoMFTManager::Shutdown() {
+ mDecoder = nullptr;
+ mDXVA2Manager.reset();
+}
+
+bool WMFVideoMFTManager::IsHardwareAccelerated(
+ nsACString& aFailureReason) const {
+ aFailureReason = mDXVAFailureReason;
+ return mDecoder && mUseHwAccel;
+}
+
+nsCString WMFVideoMFTManager::GetDescriptionName() const {
+ nsCString failureReason;
+ bool hw = IsHardwareAccelerated(failureReason);
+ return nsPrintfCString("wmf %s codec %s video decoder - %s",
+ StreamTypeString(), hw ? "hardware" : "software",
+ hw ? StaticPrefs::media_wmf_use_nv12_format() &&
+ gfx::DeviceManagerDx::Get()->CanUseNV12()
+ ? "nv12"
+ : "rgba32"
+ : "yuv420");
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.h b/dom/media/platforms/wmf/WMFVideoMFTManager.h
new file mode 100644
index 0000000000..1a7007ed38
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.h
@@ -0,0 +1,120 @@
+/* -*- 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/. */
+
+#if !defined(WMFVideoMFTManager_h_)
+# define WMFVideoMFTManager_h_
+
+# include "MFTDecoder.h"
+# include "MediaResult.h"
+# include "WMF.h"
+# include "WMFMediaDataDecoder.h"
+# include "mozilla/Atomics.h"
+# include "mozilla/RefPtr.h"
+# include "mozilla/gfx/Rect.h"
+
+namespace mozilla {
+
+class DXVA2Manager;
+
+class WMFVideoMFTManager : public MFTManager {
+ public:
+ WMFVideoMFTManager(const VideoInfo& aConfig,
+ layers::KnowsCompositor* aKnowsCompositor,
+ layers::ImageContainer* aImageContainer, float aFramerate,
+ const CreateDecoderParams::OptionSet& aOptions,
+ bool aDXVAEnabled);
+ ~WMFVideoMFTManager();
+
+ MediaResult Init();
+
+ HRESULT Input(MediaRawData* aSample) override;
+
+ HRESULT Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutput) override;
+
+ void Shutdown() override;
+
+ bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
+
+ TrackInfo::TrackType GetType() override { return TrackInfo::kVideoTrack; }
+
+ nsCString GetDescriptionName() const override;
+
+ MediaDataDecoder::ConversionRequired NeedsConversion() const override {
+ return mStreamType == H264
+ ? MediaDataDecoder::ConversionRequired::kNeedAnnexB
+ : MediaDataDecoder::ConversionRequired::kNeedNone;
+ }
+
+ private:
+ MediaResult ValidateVideoInfo();
+
+ bool InitializeDXVA();
+
+ MediaResult InitInternal();
+
+ HRESULT CreateBasicVideoFrame(IMFSample* aSample, int64_t aStreamOffset,
+ VideoData** aOutVideoData);
+
+ HRESULT CreateD3DVideoFrame(IMFSample* aSample, int64_t aStreamOffset,
+ VideoData** aOutVideoData);
+
+ HRESULT SetDecoderMediaTypes();
+
+ bool CanUseDXVA(IMFMediaType* aType, float aFramerate);
+
+ // Video frame geometry.
+ const VideoInfo mVideoInfo;
+ const gfx::IntSize mImageSize;
+ gfx::IntSize mDecodedImageSize;
+ uint32_t mVideoStride;
+ Maybe<gfx::YUVColorSpace> mColorSpace;
+ gfx::ColorRange mColorRange;
+
+ RefPtr<layers::ImageContainer> mImageContainer;
+ RefPtr<layers::KnowsCompositor> mKnowsCompositor;
+ UniquePtr<DXVA2Manager> mDXVA2Manager;
+
+ media::TimeUnit mLastDuration;
+
+ bool mDXVAEnabled;
+ bool mUseHwAccel;
+
+ nsCString mDXVAFailureReason;
+
+ enum StreamType { Unknown, H264, VP8, VP9 };
+
+ StreamType mStreamType;
+
+ // Get a string representation of the stream type. Useful for logging.
+ inline const char* StreamTypeString() const {
+ switch (mStreamType) {
+ case StreamType::H264:
+ return "H264";
+ case StreamType::VP8:
+ return "VP8";
+ case StreamType::VP9:
+ return "VP9";
+ default:
+ MOZ_ASSERT(mStreamType == StreamType::Unknown);
+ return "Unknown";
+ }
+ }
+
+ const GUID& GetMFTGUID();
+ const GUID& GetMediaSubtypeGUID();
+
+ uint32_t mNullOutputCount = 0;
+ bool mGotValidOutputAfterNullOutput = false;
+ bool mGotExcessiveNullOutput = false;
+ bool mIsValid = true;
+ bool mIMFUsable = false;
+ const float mFramerate;
+ const bool mLowLatency;
+};
+
+} // namespace mozilla
+
+#endif // WMFVideoMFTManager_h_
diff --git a/dom/media/platforms/wmf/moz.build b/dom/media/platforms/wmf/moz.build
new file mode 100644
index 0000000000..bd9c0e03be
--- /dev/null
+++ b/dom/media/platforms/wmf/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS += [
+ "DXVA2Manager.h",
+ "MFTDecoder.h",
+ "WMF.h",
+ "WMFAudioMFTManager.h",
+ "WMFDecoderModule.h",
+ "WMFMediaDataDecoder.h",
+ "WMFUtils.h",
+ "WMFVideoMFTManager.h",
+]
+UNIFIED_SOURCES += [
+ "DXVA2Manager.cpp",
+ "MFTDecoder.cpp",
+ "WMFAudioMFTManager.cpp",
+ "WMFDecoderModule.cpp",
+ "WMFMediaDataDecoder.cpp",
+ "WMFVideoMFTManager.cpp",
+]
+
+SOURCES += [
+ "WMFUtils.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")