diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/media/platforms/wmf | |
parent | Initial commit. (diff) | |
download | firefox-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.cpp | 1245 | ||||
-rw-r--r-- | dom/media/platforms/wmf/DXVA2Manager.h | 79 | ||||
-rw-r--r-- | dom/media/platforms/wmf/MFTDecoder.cpp | 344 | ||||
-rw-r--r-- | dom/media/platforms/wmf/MFTDecoder.h | 114 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMF.h | 88 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFAudioMFTManager.cpp | 316 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFAudioMFTManager.h | 58 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFDecoderModule.cpp | 342 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFDecoderModule.h | 57 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFMediaDataDecoder.cpp | 257 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFMediaDataDecoder.h | 161 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFUtils.cpp | 323 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFUtils.h | 63 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFVideoMFTManager.cpp | 843 | ||||
-rw-r--r-- | dom/media/platforms/wmf/WMFVideoMFTManager.h | 120 | ||||
-rw-r--r-- | dom/media/platforms/wmf/moz.build | 37 |
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, + ¶ms, 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, ©Rect, mSyncSurface, ©Rect, + 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, ¤tSubtype); + 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, ¤tLength); + 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, ¤tLength); + 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(×tampHns); + 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") |