/* -*- 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 #include #include "DriverCrashGuard.h" #include "GfxDriverInfo.h" #include "MFTDecoder.h" #include "MP4Decoder.h" #include "MediaInfo.h" #include "PDMFactory.h" #include "VPXDecoder.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/SyncRunnable.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" #ifdef MOZ_AV1 # include "AOMDecoder.h" #endif #define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) 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 const GUID CLSID_CMSAACDecMFT = { 0x32D186A7, 0x218F, 0x4C75, {0x88, 0x76, 0xDD, 0x77, 0x27, 0x3A, 0x89, 0x99}}; static Atomic sDXVAEnabled(false); /* static */ already_AddRefed WMFDecoderModule::Create() { RefPtr wmf = new WMFDecoderModule(); return wmf.forget(); } static bool IsRemoteAcceleratedCompositor( layers::KnowsCompositor* aKnowsCompositor) { if (!aKnowsCompositor) { return false; } if (aKnowsCompositor->UsingSoftwareWebRenderD3D11()) { return true; } layers::TextureFactoryIdentifier ident = aKnowsCompositor->GetTextureFactoryIdentifier(); return !aKnowsCompositor->UsingSoftwareWebRender() && ident.mParentProcessType == GeckoProcessType_GPU; } static Atomic sSupportedTypesInitialized(false); static EnumSet sSupportedTypes; /* 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()) { // Hardware accelerated decoding is explicitly only done in the GPU process // to avoid copying textures whenever possible. Previously, detecting // whether the video bridge was set up could be done with the following: // sDXVAEnabled = !!DeviceManagerDx::Get()->GetImageDevice(); // The video bridge was previously broken due to initialization order // issues. For more information see Bug 1763880. sDXVAEnabled = false; } 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). bool hwVideo = gfx::gfxVars::GetCanUseHardwareVideoDecodingOrDefault(); WmfDecoderModuleMarkerAndLog( "WMFInit DXVA Status", "sDXVAEnabled: %s, CanUseHardwareVideoDecoding: %s", sDXVAEnabled ? "true" : "false", hwVideo ? "true" : "false"); sDXVAEnabled = sDXVAEnabled && hwVideo; mozilla::mscom::EnsureMTA([&]() { // Store the supported MFT decoders. sSupportedTypes.clear(); // i = 1 to skip Unknown. for (uint32_t i = 1; i < static_cast(WMFStreamType::SENTINEL); i++) { WMFStreamType type = static_cast(i); RefPtr decoder = new MFTDecoder(); HRESULT hr = CreateMFTDecoder(type, decoder); if (SUCCEEDED(hr)) { sSupportedTypes += type; WmfDecoderModuleMarkerAndLog("WMFInit Decoder Supported", "%s is enabled", StreamTypeToString(type)); } else if (hr != E_FAIL) { // E_FAIL should be logged by CreateMFTDecoder. Skipping those codes // will help to keep the logs readable. WmfDecoderModuleMarkerAndLog("WMFInit Decoder Failed", "%s failed with code 0x%lx", StreamTypeToString(type), hr); } } }); sSupportedTypesInitialized = true; WmfDecoderModuleMarkerAndLog("WMFInit Result", "WMFDecoderModule::Init finishing"); } /* 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); } /* static */ HRESULT WMFDecoderModule::CreateMFTDecoder(const WMFStreamType& aType, RefPtr& aDecoder) { // Do not expose any video decoder on utility process which is only for audio // decoding. if (XRE_IsUtilityProcess()) { switch (aType) { case WMFStreamType::H264: case WMFStreamType::VP8: case WMFStreamType::VP9: case WMFStreamType::AV1: return E_FAIL; default: break; } } switch (aType) { case WMFStreamType::H264: return aDecoder->Create(CLSID_CMSH264DecoderMFT); case WMFStreamType::VP8: static const uint32_t VP8_USABLE_BUILD = 16287; if (!IsWindowsBuildOrLater(VP8_USABLE_BUILD)) { WmfDecoderModuleMarkerAndLog("CreateMFTDecoder, VP8 Failure", "VP8 MFT requires Windows build %" PRId32 " or later", VP8_USABLE_BUILD); return E_FAIL; } if (!gfx::gfxVars::UseVP8HwDecode()) { WmfDecoderModuleMarkerAndLog("CreateMFTDecoder, VP8 Failure", "Gfx VP8 blocklist"); return E_FAIL; } [[fallthrough]]; case WMFStreamType::VP9: if (!sDXVAEnabled) { WmfDecoderModuleMarkerAndLog("CreateMFTDecoder, VPx Disabled", "%s MFT requires DXVA", StreamTypeToString(aType)); return E_FAIL; } { gfx::WMFVPXVideoCrashGuard guard; if (guard.Crashed()) { WmfDecoderModuleMarkerAndLog( "CreateMFTDecoder, VPx Failure", "Will not use VPx MFT due to crash guard reporting a crash"); return E_FAIL; } return aDecoder->Create(CLSID_CMSVPXDecMFT); } #ifdef MOZ_AV1 case WMFStreamType::AV1: // If this process cannot use DXVA, the AV1 decoder will not be used. // Also, upon startup, init will be called both before and after // layers acceleration is setup. This prevents creating the AV1 decoder // twice. if (!sDXVAEnabled) { WmfDecoderModuleMarkerAndLog("CreateMFTDecoder AV1 Disabled", "AV1 MFT requires DXVA"); return E_FAIL; } // TODO: MFTEnumEx is slower than creating by CLSID, it may be worth // investigating other ways to instantiate the AV1 decoder. return aDecoder->Create(MFT_CATEGORY_VIDEO_DECODER, MFVideoFormat_AV1, MFVideoFormat_NV12); #endif case WMFStreamType::MP3: return aDecoder->Create(CLSID_CMP3DecMediaObject); case WMFStreamType::AAC: return aDecoder->Create(CLSID_CMSAACDecMFT); default: return E_FAIL; } } /* static */ bool WMFDecoderModule::CanCreateMFTDecoder(const WMFStreamType& aType) { MOZ_ASSERT(WMFStreamType::Unknown < aType && aType < WMFStreamType::SENTINEL); if (!sSupportedTypesInitialized) { if (NS_IsMainThread()) { Init(); } else { nsCOMPtr runnable = NS_NewRunnableFunction("WMFDecoderModule::Init", [&]() { Init(); }); SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable); } } // Check prefs here rather than CreateMFTDecoder so that prefs aren't baked // into sSupportedTypes switch (aType) { case WMFStreamType::VP8: case WMFStreamType::VP9: if (!StaticPrefs::media_wmf_vp9_enabled()) { return false; } break; #ifdef MOZ_AV1 case WMFStreamType::AV1: if (!StaticPrefs::media_av1_enabled() || !StaticPrefs::media_wmf_av1_enabled()) { return false; } break; #endif case WMFStreamType::MP3: // Prefer ffvpx mp3 decoder over WMF. if (StaticPrefs::media_ffvpx_mp3_enabled()) { return false; } break; default: break; } // Do not expose any video decoder on utility process which is only for audio // decoding. if (XRE_IsUtilityProcess()) { switch (aType) { case WMFStreamType::H264: case WMFStreamType::VP8: case WMFStreamType::VP9: case WMFStreamType::AV1: return false; default: break; } } return sSupportedTypes.contains(aType); } bool WMFDecoderModule::SupportsColorDepth( gfx::ColorDepth aColorDepth, DecoderDoctorDiagnostics* aDiagnostics) const { // Color depth support can be determined by creating DX decoders. return true; } media::DecodeSupportSet WMFDecoderModule::Supports( const SupportDecoderParams& aParams, DecoderDoctorDiagnostics* aDiagnostics) const { // This should only be supported by MFMediaEngineDecoderModule. if (aParams.mMediaEngineId) { return media::DecodeSupport::Unsupported; } // 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 media::DecodeSupport::Unsupported; } 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 media::DecodeSupport::Unsupported; } WMFStreamType type = GetStreamTypeFromMimeType(aParams.MimeType()); if (type == WMFStreamType::Unknown) { return media::DecodeSupport::Unsupported; } if (CanCreateMFTDecoder(type)) { if (StreamTypeIsVideo(type)) { return sDXVAEnabled ? media::DecodeSupport::HardwareDecode : media::DecodeSupport::SoftwareDecode; } else { // Audio only supports software decode return media::DecodeSupport::SoftwareDecode; } } return media::DecodeSupport::Unsupported; } nsresult WMFDecoderModule::Startup() { return wmf::MediaFoundationInitializer::HasInitialized() ? NS_OK : NS_ERROR_FAILURE; } already_AddRefed 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 manager(new WMFVideoMFTManager( aParams.VideoConfig(), aParams.mKnowsCompositor, aParams.mImageContainer, aParams.mRate.mValue, aParams.mOptions, sDXVAEnabled, aParams.mTrackingId)); 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; } nsAutoCString hwFailure; if (!manager->IsHardwareAccelerated(hwFailure)) { // The decoder description includes whether it is using software or // hardware, but no information about how the hardware acceleration failed. WmfDecoderModuleMarkerAndLog( "WMFVDecoderCreation Success", "WMFDecoderModule::CreateVideoDecoder success for manager with " "description %s - DXVA failure: %s", manager->GetDescriptionName().get(), hwFailure.get()); } else { WmfDecoderModuleMarkerAndLog( "WMFVDecoderCreation Success", "WMFDecoderModule::CreateVideoDecoder success for manager with " "description %s", manager->GetDescriptionName().get()); } RefPtr decoder = new WMFMediaDataDecoder(manager.release()); return decoder.forget(); } already_AddRefed WMFDecoderModule::CreateAudioDecoder( const CreateDecoderParams& aParams) { if (XRE_IsGPUProcess()) { // Only allow video in the GPU process. return nullptr; } UniquePtr 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 decoder = new WMFMediaDataDecoder(manager.release()); return decoder.forget(); } media::DecodeSupportSet WMFDecoderModule::SupportsMimeType( const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const { UniquePtr trackInfo = CreateTrackInfoWithMIMEType(aMimeType); if (!trackInfo) { return media::DecodeSupport::Unsupported; } auto supports = Supports(SupportDecoderParams(*trackInfo), aDiagnostics); MOZ_LOG( sPDMLog, LogLevel::Debug, ("WMF decoder %s requested type '%s'", supports != media::DecodeSupport::Unsupported ? "supports" : "rejects", aMimeType.BeginReading())); return supports; } } // namespace mozilla #undef WFM_DECODER_MODULE_STATUS_MARKER #undef LOG