/* -*- 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 #include #include #include "WMFVideoMFTManager.h" #include "MediaDecoderReader.h" #include "gfxPrefs.h" #include "WMFUtils.h" #include "ImageContainer.h" #include "VideoUtils.h" #include "DXVA2Manager.h" #include "nsThreadUtils.h" #include "Layers.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/layers/LayersTypes.h" #include "MediaInfo.h" #include "mozilla/Logging.h" #include "nsWindowsHelpers.h" #include "gfx2DGlue.h" #include "gfxWindowsPlatform.h" #include "IMFYCbCrImage.h" #include "mozilla/WindowsVersion.h" #include "mozilla/Telemetry.h" #include "nsPrintfCString.h" #include "MediaTelemetryConstants.h" #include "GMPUtils.h" // For SplitAt. TODO: Move SplitAt to a central place. #include "MP4Decoder.h" #include "VPXDecoder.h" #include "mozilla/SyncRunnable.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; #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 const CLSID CLSID_WebmMfVpxDec = { 0xe3aaf548, 0xc9a4, 0x4c6e, { 0x23, 0x4d, 0x5a, 0xda, 0x37, 0x4b, 0x00, 0x00 } }; namespace mozilla { 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, bool aDXVAEnabled) : mVideoInfo(aConfig) , mVideoStride(0) , mImageSize(aConfig.mImage) , mImageContainer(aImageContainer) , mDXVAEnabled(aDXVAEnabled) , mKnowsCompositor(aKnowsCompositor) , mNullOutputCount(0) , mGotValidOutputAfterNullOutput(false) , mGotExcessiveNullOutput(false) , mIsValid(true) // 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; } } WMFVideoMFTManager::~WMFVideoMFTManager() { MOZ_COUNT_DTOR(WMFVideoMFTManager); // Ensure DXVA/D3D9 related objects are released on the main thread. if (mDXVA2Manager) { DeleteOnMainThread(mDXVA2Manager); } // Record whether the video decoder successfully decoded, or output null // samples but did/didn't recover. uint32_t telemetry = (mNullOutputCount == 0) ? 0 : (mGotValidOutputAfterNullOutput && mGotExcessiveNullOutput) ? 1 : mGotExcessiveNullOutput ? 2 : mGotValidOutputAfterNullOutput ? 3 : 4; nsCOMPtr task = NS_NewRunnableFunction([=]() -> void { LOG(nsPrintfCString("Reporting telemetry VIDEO_MFT_OUTPUT_NULL_SAMPLES=%d", telemetry).get()); Telemetry::Accumulate(Telemetry::ID::VIDEO_MFT_OUTPUT_NULL_SAMPLES, telemetry); }); AbstractThread::MainThread()->Dispatch(task.forget()); } 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; }; } struct D3DDLLBlacklistingCache { // Blacklist pref value last seen. nsCString mBlacklistPref; // Non-empty if a blacklisted DLL was found. nsCString mBlacklistedDLL; }; StaticAutoPtr sD3D11BlacklistingCache; StaticAutoPtr sD3D9BlacklistingCache; // If a blacklisted DLL is found, return its information, otherwise "". static const nsCString& FindDXVABlacklistedDLL(StaticAutoPtr& aDLLBlacklistingCache, const nsCString& aBlacklist, const char* aDLLBlacklistPrefName) { NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); if (!aDLLBlacklistingCache) { // First time here, create persistent data that will be reused in all // D3D11-blacklisting checks. aDLLBlacklistingCache = new D3DDLLBlacklistingCache(); ClearOnShutdown(&aDLLBlacklistingCache); } if (aBlacklist.IsEmpty()) { // Empty blacklist -> No blacklisting. aDLLBlacklistingCache->mBlacklistPref.SetLength(0); aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0); return aDLLBlacklistingCache->mBlacklistedDLL; } // Detect changes in pref. if (aDLLBlacklistingCache->mBlacklistPref.Equals(aBlacklist)) { // Same blacklist -> Return same result (i.e., don't check DLLs again). return aDLLBlacklistingCache->mBlacklistedDLL; } // Adopt new pref now, so we don't work on it again. aDLLBlacklistingCache->mBlacklistPref = aBlacklist; HANDLE hProcess = GetCurrentProcess(); mozilla::UniquePtr hMods; unsigned int modulesNum = 0; if (hProcess != NULL) { DWORD modulesSize; EnumProcessModules(hProcess, nullptr, 0, &modulesSize); modulesNum = modulesSize / sizeof(HMODULE); hMods = mozilla::MakeUnique(modulesNum); EnumProcessModules(hProcess, hMods.get(), modulesNum * sizeof(HMODULE), &modulesSize); } // media.wmf.disable-d3d*-for-dlls format: (whitespace is trimmed) // "dll1.dll: 1.2.3.4[, more versions...][; more dlls...]" nsTArray dlls; SplitAt(";", aBlacklist, dlls); for (const auto& dll : dlls) { nsTArray nameAndVersions; SplitAt(":", dll, nameAndVersions); if (nameAndVersions.Length() != 2) { NS_WARNING(nsPrintfCString("Skipping incorrect '%s' dll:versions format", aDLLBlacklistPrefName).get()); continue; } nameAndVersions[0].CompressWhitespace(); NS_ConvertUTF8toUTF16 name(nameAndVersions[0]); for (unsigned int i = 0; i <= modulesNum; i++) { WCHAR dllPath[MAX_PATH + 1]; if (i < modulesNum) { if (!GetModuleFileNameEx(hProcess, hMods[i], dllPath, sizeof(dllPath) / sizeof(WCHAR))) { continue; } nsCOMPtr file; if (NS_WARN_IF(NS_FAILED(NS_NewLocalFile(nsDependentString(dllPath), false, getter_AddRefs(file))))) { continue; } nsAutoString leafName; if (NS_WARN_IF(NS_FAILED(file->GetLeafName(leafName)))) { continue; } if (_wcsicmp(leafName.get(), name.get())) { continue; } } else { if (!ConstructSystem32Path(name.get(), dllPath, MAX_PATH + 1)) { // Cannot build path -> Assume it's not the blacklisted DLL. continue; } } DWORD zero; DWORD infoSize = GetFileVersionInfoSizeW(dllPath, &zero); if (infoSize == 0) { // Can't get file info -> Assume we don't have the blacklisted DLL. continue; } // vInfo is a pointer into infoData, that's why we keep it outside of the loop. auto infoData = MakeUnique(infoSize); VS_FIXEDFILEINFO *vInfo; UINT vInfoLen; if (!GetFileVersionInfoW(dllPath, 0, infoSize, infoData.get()) || !VerQueryValueW(infoData.get(), L"\\", (LPVOID*)&vInfo, &vInfoLen) || !vInfo) { // Can't find version -> Assume it's not blacklisted. continue; } nsTArray versions; SplitAt(",", nameAndVersions[1], versions); for (const auto& version : versions) { nsTArray numberStrings; SplitAt(".", version, numberStrings); if (numberStrings.Length() != 4) { NS_WARNING(nsPrintfCString("Skipping incorrect '%s' a.b.c.d version format", aDLLBlacklistPrefName).get()); continue; } DWORD numbers[4]; nsresult errorCode = NS_OK; for (int i = 0; i < 4; ++i) { numberStrings[i].CompressWhitespace(); numbers[i] = DWORD(numberStrings[i].ToInteger(&errorCode)); if (NS_FAILED(errorCode)) { break; } if (numbers[i] > UINT16_MAX) { errorCode = NS_ERROR_FAILURE; break; } } if (NS_FAILED(errorCode)) { NS_WARNING(nsPrintfCString("Skipping incorrect '%s' a.b.c.d version format", aDLLBlacklistPrefName).get()); continue; } if (vInfo->dwFileVersionMS == ((numbers[0] << 16) | numbers[1]) && vInfo->dwFileVersionLS == ((numbers[2] << 16) | numbers[3])) { // Blacklisted! Record bad DLL. aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0); aDLLBlacklistingCache->mBlacklistedDLL.AppendPrintf( "%s (%lu.%lu.%lu.%lu)", nameAndVersions[0].get(), numbers[0], numbers[1], numbers[2], numbers[3]); return aDLLBlacklistingCache->mBlacklistedDLL; } } } } // No blacklisted DLL. aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0); return aDLLBlacklistingCache->mBlacklistedDLL; } static const nsCString& FindD3D11BlacklistedDLL() { return FindDXVABlacklistedDLL(sD3D11BlacklistingCache, gfx::gfxVars::PDMWMFDisableD3D11Dlls(), "media.wmf.disable-d3d11-for-dlls"); } static const nsCString& FindD3D9BlacklistedDLL() { return FindDXVABlacklistedDLL(sD3D9BlacklistingCache, gfx::gfxVars::PDMWMFDisableD3D9Dlls(), "media.wmf.disable-d3d9-for-dlls"); } class CreateDXVAManagerEvent : public Runnable { public: CreateDXVAManagerEvent(LayersBackend aBackend, layers::KnowsCompositor* aKnowsCompositor, nsCString& aFailureReason) : mBackend(aBackend) , mKnowsCompositor(aKnowsCompositor) , mFailureReason(aFailureReason) {} NS_IMETHOD Run() override { NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); nsACString* failureReason = &mFailureReason; nsCString secondFailureReason; if (mBackend == LayersBackend::LAYERS_D3D11 && gfxPrefs::PDMWMFAllowD3D11() && IsWin8OrLater()) { const nsCString& blacklistedDLL = FindD3D11BlacklistedDLL(); if (!blacklistedDLL.IsEmpty()) { failureReason->AppendPrintf("D3D11 blacklisted with DLL %s", blacklistedDLL.get()); } else { mDXVA2Manager = DXVA2Manager::CreateD3D11DXVA(mKnowsCompositor, *failureReason); if (mDXVA2Manager) { return NS_OK; } } // Try again with d3d9, but record the failure reason // into a new var to avoid overwriting the d3d11 failure. failureReason = &secondFailureReason; mFailureReason.Append(NS_LITERAL_CSTRING("; ")); } const nsCString& blacklistedDLL = FindD3D9BlacklistedDLL(); if (!blacklistedDLL.IsEmpty()) { mFailureReason.AppendPrintf("D3D9 blacklisted with DLL %s", blacklistedDLL.get()); } else { mDXVA2Manager = DXVA2Manager::CreateD3D9DXVA(mKnowsCompositor, *failureReason); // Make sure we include the messages from both attempts (if applicable). mFailureReason.Append(secondFailureReason); } return NS_OK; } nsAutoPtr mDXVA2Manager; layers::LayersBackend mBackend; KnowsCompositor* mKnowsCompositor; nsACString& mFailureReason; }; bool WMFVideoMFTManager::InitializeDXVA(bool aForceD3D9) { // 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); LayersBackend backend = GetCompositorBackendType(mKnowsCompositor); if (backend != LayersBackend::LAYERS_D3D9 && backend != LayersBackend::LAYERS_D3D11) { mDXVAFailureReason.AssignLiteral("Unsupported layers backend"); return false; } // The DXVA manager must be created on the main thread. RefPtr event = new CreateDXVAManagerEvent(aForceD3D9 ? LayersBackend::LAYERS_D3D9 : backend, mKnowsCompositor, mDXVAFailureReason); if (NS_IsMainThread()) { event->Run(); } else { // This logic needs to run on the main thread nsCOMPtr mainThread = do_GetMainThread(); mozilla::SyncRunnable::DispatchToThread(mainThread, event); } mDXVA2Manager = event->mDXVA2Manager; return mDXVA2Manager != nullptr; } bool WMFVideoMFTManager::ValidateVideoInfo() { // The WMF H.264 decoder is documented to have a minimum resolution // 48x48 pixels. We've observed the decoder working for output smaller than // that, but on some output it hangs in IMFTransform::ProcessOutput(), so // we just reject streams which are less than the documented minimum. // https://msdn.microsoft.com/en-us/library/windows/desktop/dd797815(v=vs.85).aspx static const int32_t MIN_H264_FRAME_DIMENSION = 48; if (mStreamType == H264 && (mVideoInfo.mImage.width < MIN_H264_FRAME_DIMENSION || mVideoInfo.mImage.height < MIN_H264_FRAME_DIMENSION)) { LogToBrowserConsole(NS_LITERAL_STRING( "Can't decode H.264 stream with width or height less than 48 pixels.")); mIsValid = false; } return mIsValid; } bool WMFVideoMFTManager::Init() { if (!ValidateVideoInfo()) { return false; } bool success = InitInternal(/* aForceD3D9 = */ false); if (success && mDXVA2Manager) { // If we had some failures but eventually made it work, // make sure we preserve the messages. if (mDXVA2Manager->IsD3D11()) { mDXVAFailureReason.Append(NS_LITERAL_CSTRING("Using D3D11 API")); } else { mDXVAFailureReason.Append(NS_LITERAL_CSTRING("Using D3D9 API")); } } return success; } bool WMFVideoMFTManager::InitInternal(bool aForceD3D9) { mUseHwAccel = false; // default value; changed if D3D setup succeeds. bool useDxva = InitializeDXVA(aForceD3D9); RefPtr decoder(new MFTDecoder()); HRESULT hr = decoder->Create(GetMFTGUID()); NS_ENSURE_TRUE(SUCCEEDED(hr), false); RefPtr attr(decoder->GetAttributes()); UINT32 aware = 0; if (attr) { attr->GetUINT32(MF_SA_D3D_AWARE, &aware); attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads, WMFDecoderModule::GetNumDecoderThreads()); if (gfxPrefs::PDMWMFLowLatencyEnabled()) { 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 { DeleteOnMainThread(mDXVA2Manager); 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) { // Use VP8/9 MFT only if HW acceleration is available if (mStreamType == VP9 || mStreamType == VP8) { return false; } Telemetry::Accumulate(Telemetry::MEDIA_DECODER_BACKEND_USED, uint32_t(media::MediaDecoderBackend::WMFSoftware)); } mDecoder = decoder; hr = SetDecoderMediaTypes(); NS_ENSURE_TRUE(SUCCEEDED(hr), false); LOG("Video Decoder initialized, Using DXVA: %s", (mUseHwAccel ? "Yes" : "No")); return true; } HRESULT WMFVideoMFTManager::SetDecoderMediaTypes() { // Setup the input/output media types. RefPtr 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); // MSFT MFT needs this frame size set for VP9? if (mStreamType == VP9 || mStreamType == VP8) { 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 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); 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; } HRESULT hr = mDecoder->CreateInputSample(aSample->Data(), uint32_t(aSample->Size()), aSample->mTime, &mLastInput); NS_ENSURE_TRUE(SUCCEEDED(hr) && mLastInput != nullptr, hr); mLastDuration = aSample->mDuration; mLastTime = aSample->mTime; mSamplesCount++; // Forward sample data to the decoder. return mDecoder->Input(mLastInput); } class SupportsConfigEvent : public Runnable { public: SupportsConfigEvent(DXVA2Manager* aDXVA2Manager, IMFMediaType* aMediaType, float aFramerate) : mDXVA2Manager(aDXVA2Manager) , mMediaType(aMediaType) , mFramerate(aFramerate) , mSupportsConfig(false) {} NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); mSupportsConfig = mDXVA2Manager->SupportsConfig(mMediaType, mFramerate); return NS_OK; } DXVA2Manager* mDXVA2Manager; IMFMediaType* mMediaType; float mFramerate; bool mSupportsConfig; }; // 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) { MOZ_ASSERT(mDXVA2Manager); // SupportsConfig only checks for valid h264 decoders currently. if (mStreamType != H264) { return true; } // Assume the current samples duration is representative for the // entire video. float framerate = 1000000.0 / mLastDuration; // The supports config check must be done on the main thread since we have // a crash guard protecting it. RefPtr event = new SupportsConfigEvent(mDXVA2Manager, aType, framerate); if (NS_IsMainThread()) { event->Run(); } else { // This logic needs to run on the main thread nsCOMPtr mainThread = do_GetMainThread(); mozilla::SyncRunnable::DispatchToThread(mainThread, event); } return event->mSupportsConfig; } HRESULT WMFVideoMFTManager::ConfigureVideoFrameGeometry() { RefPtr mediaType; HRESULT hr = mDecoder->GetOutputMediaType(mediaType); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); // If we enabled/disabled DXVA in response to a resolution // change then we need to renegotiate our media types, // and resubmit our previous frame (since the MFT appears // to lose it otherwise). if (mUseHwAccel && !CanUseDXVA(mediaType)) { mDXVAEnabled = false; if (!Init()) { return E_FAIL; } mDecoder->Input(mLastInput); return S_OK; } // Verify that the video subtype is what we expect it to be. // When using hardware acceleration/DXVA2 the video format should // be NV12, which is DXVA2's preferred format. For software decoding // we use YV12, as that's easier for us to stick into our rendering // pipeline than NV12. NV12 has interleaved UV samples, whereas YV12 // is a planar format. GUID videoFormat; hr = mediaType->GetGUID(MF_MT_SUBTYPE, &videoFormat); NS_ENSURE_TRUE(videoFormat == MFVideoFormat_NV12 || !mUseHwAccel, E_FAIL); NS_ENSURE_TRUE(videoFormat == MFVideoFormat_YV12 || mUseHwAccel, E_FAIL); nsIntRect pictureRegion; hr = GetPictureRegion(mediaType, pictureRegion); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); UINT32 width = pictureRegion.width; UINT32 height = pictureRegion.height; mImageSize = nsIntSize(width, height); // Calculate and validate the picture region and frame dimensions after // scaling by the pixel aspect ratio. pictureRegion = mVideoInfo.ScaledImageRect(width, height); if (!IsValidVideoRegion(mImageSize, pictureRegion, mVideoInfo.mDisplay)) { // Video track's frame sizes will overflow. Ignore the video track. return E_FAIL; } if (mDXVA2Manager) { hr = mDXVA2Manager->ConfigureForSize(width, height); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); } // Success! Save state. GetDefaultStride(mediaType, width, &mVideoStride); LOG("WMFVideoMFTManager frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, %d, %d) display=(%d,%d)", width, height, mVideoStride, pictureRegion.x, pictureRegion.y, pictureRegion.width, pictureRegion.height, mVideoInfo.mDisplay.width, mVideoInfo.mDisplay.height); return S_OK; } 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 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 twoDBuffer; hr = buffer->QueryInterface(static_cast(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; } // YV12, planar format: [YYYY....][VVVV....][UUUU....] // i.e., Y, then V, then U. 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].mOffset = 0; b.mPlanes[0].mSkip = 0; // 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. uint32_t padding = 0; if (videoHeight % 16 != 0) { padding = 16 - (videoHeight % 16); } uint32_t y_size = stride * (videoHeight + padding); uint32_t v_size = stride * (videoHeight + padding) / 4; uint32_t halfStride = (stride + 1) / 2; uint32_t halfHeight = (videoHeight + 1) / 2; uint32_t halfWidth = (videoWidth + 1) / 2; // 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].mOffset = 0; 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].mOffset = 0; b.mPlanes[2].mSkip = 0; media::TimeUnit pts = GetSampleTime(aSample); NS_ENSURE_TRUE(pts.IsValid(), E_FAIL); media::TimeUnit duration = GetSampleDuration(aSample); NS_ENSURE_TRUE(duration.IsValid(), E_FAIL); nsIntRect pictureRegion = mVideoInfo.ScaledImageRect(videoWidth, videoHeight); LayersBackend backend = GetCompositorBackendType(mKnowsCompositor); if (backend != LayersBackend::LAYERS_D3D9 && backend != LayersBackend::LAYERS_D3D11) { RefPtr v = VideoData::CreateAndCopyData(mVideoInfo, mImageContainer, aStreamOffset, pts.ToMicroseconds(), duration.ToMicroseconds(), b, false, -1, pictureRegion); if (twoDBuffer) { twoDBuffer->Unlock2D(); } else { buffer->Unlock(); } v.forget(aOutVideoData); return S_OK; } RefPtr image = new IMFYCbCrImage(buffer, twoDBuffer); VideoData::SetVideoDataToImage(image, mVideoInfo, b, pictureRegion, false); RefPtr v = VideoData::CreateFromImage(mVideoInfo, aStreamOffset, pts.ToMicroseconds(), duration.ToMicroseconds(), image.forget(), false, -1, pictureRegion); 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; nsIntRect pictureRegion = mVideoInfo.ScaledImageRect(mImageSize.width, mImageSize.height); RefPtr image; hr = mDXVA2Manager->CopyToImage(aSample, pictureRegion, getter_AddRefs(image)); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); NS_ENSURE_TRUE(image, E_FAIL); media::TimeUnit pts = GetSampleTime(aSample); NS_ENSURE_TRUE(pts.IsValid(), E_FAIL); media::TimeUnit duration = GetSampleDuration(aSample); NS_ENSURE_TRUE(duration.IsValid(), E_FAIL); RefPtr v = VideoData::CreateFromImage(mVideoInfo, aStreamOffset, pts.ToMicroseconds(), duration.ToMicroseconds(), image.forget(), false, -1, pictureRegion); NS_ENSURE_TRUE(v, E_FAIL); v.forget(aOutVideoData); return S_OK; } // Blocks until decoded sample is produced by the deoder. HRESULT WMFVideoMFTManager::Output(int64_t aStreamOffset, RefPtr& aOutData) { RefPtr sample; HRESULT hr; aOutData = nullptr; int typeChangeCount = 0; bool wasDraining = mDraining; int64_t sampleCount = mSamplesCount; if (wasDraining) { mSamplesCount = 0; mDraining = false; } media::TimeUnit pts; media::TimeUnit duration; // 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) { // Video stream output type change. Probably a geometric apperature // change. Reconfigure the video geometry, so that we output the // correct size frames. MOZ_ASSERT(!sample); hr = ConfigureVideoFrameGeometry(); 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); // 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; } pts = GetSampleTime(sample); duration = GetSampleDuration(sample); if (!pts.IsValid() || !duration.IsValid()) { return E_FAIL; } if (wasDraining && sampleCount == 1 && pts == media::TimeUnit()) { // 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. pts = media::TimeUnit::FromMicroseconds(mLastTime); duration = media::TimeUnit::FromMicroseconds(mLastDuration); } 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, assert, and bail. NS_WARNING("WMFVideoMFTManager::Output() unexpected error"); return hr; } RefPtr 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; // Set the potentially corrected pts and duration. aOutData->mTime = pts.ToMicroseconds(); aOutData->mDuration = duration.ToMicroseconds(); if (mNullOutputCount) { mGotValidOutputAfterNullOutput = true; } return S_OK; } void WMFVideoMFTManager::Shutdown() { mDecoder = nullptr; DeleteOnMainThread(mDXVA2Manager); } bool WMFVideoMFTManager::IsHardwareAccelerated(nsACString& aFailureReason) const { aFailureReason = mDXVAFailureReason; return mDecoder && mUseHwAccel; } } // namespace mozilla