/* -*- 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 "PDMFactory.h" #ifdef MOZ_AV1 # include "AOMDecoder.h" #endif #include "AgnosticDecoderModule.h" #include "AudioTrimmer.h" #include "BlankDecoderModule.h" #include "DecoderDoctorDiagnostics.h" #include "EMEDecoderModule.h" #include "GMPDecoderModule.h" #include "H264.h" #include "MP4Decoder.h" #include "MediaChangeMonitor.h" #include "MediaInfo.h" #include "OpusDecoder.h" #include "TheoraDecoder.h" #include "VPXDecoder.h" #include "VideoUtils.h" #include "VorbisDecoder.h" #include "WAVDecoder.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/RemoteDecoderManagerChild.h" #include "mozilla/RemoteDecoderModule.h" #include "mozilla/SharedThreadPool.h" #include "mozilla/StaticPrefs_media.h" #include "mozilla/StaticPtr.h" #include "mozilla/SyncRunnable.h" #include "mozilla/TaskQueue.h" #include "mozilla/gfx/gfxVars.h" #include "nsIXULRuntime.h" // for BrowserTabsRemoteAutostart #include "nsPrintfCString.h" #ifdef XP_WIN # include "WMFDecoderModule.h" # include "mozilla/WindowsVersion.h" #endif #ifdef MOZ_FFVPX # include "FFVPXRuntimeLinker.h" #endif #ifdef MOZ_FFMPEG # include "FFmpegRuntimeLinker.h" #endif #ifdef MOZ_APPLEMEDIA # include "AppleDecoderModule.h" #endif #ifdef MOZ_WIDGET_ANDROID # include "AndroidDecoderModule.h" #endif #ifdef MOZ_OMX # include "OmxDecoderModule.h" #endif #include namespace mozilla { extern already_AddRefed CreateNullDecoderModule(); class PDMFactoryImpl final { public: PDMFactoryImpl() { if (XRE_IsGPUProcess()) { InitGpuPDMs(); } else if (XRE_IsRDDProcess()) { InitRddPDMs(); } else if (XRE_IsContentProcess()) { InitContentPDMs(); } else { MOZ_DIAGNOSTIC_ASSERT( XRE_IsParentProcess(), "PDMFactory is only usable in the Parent/GPU/RDD/Content process"); InitDefaultPDMs(); } } private: void InitGpuPDMs() { #ifdef XP_WIN if (!IsWin7AndPre2000Compatible()) { WMFDecoderModule::Init(); } #endif } void InitRddPDMs() { #ifdef XP_WIN if (!IsWin7AndPre2000Compatible()) { WMFDecoderModule::Init(); } #endif #ifdef MOZ_APPLEMEDIA AppleDecoderModule::Init(); #endif #ifdef MOZ_FFVPX FFVPXRuntimeLinker::Init(); #endif #ifdef MOZ_FFMPEG if (StaticPrefs::media_rdd_ffmpeg_enabled()) { FFmpegRuntimeLinker::Init(); } #endif } void InitContentPDMs() { #ifdef XP_WIN if (!IsWin7AndPre2000Compatible()) { WMFDecoderModule::Init(); } #endif #ifdef MOZ_APPLEMEDIA AppleDecoderModule::Init(); #endif #ifdef MOZ_OMX OmxDecoderModule::Init(); #endif #ifdef MOZ_FFVPX FFVPXRuntimeLinker::Init(); #endif #ifdef MOZ_FFMPEG FFmpegRuntimeLinker::Init(); #endif RemoteDecoderManagerChild::Init(); } void InitDefaultPDMs() { #ifdef XP_WIN if (!IsWin7AndPre2000Compatible()) { WMFDecoderModule::Init(); } #endif #ifdef MOZ_APPLEMEDIA AppleDecoderModule::Init(); #endif #ifdef MOZ_OMX OmxDecoderModule::Init(); #endif #ifdef MOZ_FFVPX FFVPXRuntimeLinker::Init(); #endif #ifdef MOZ_FFMPEG FFmpegRuntimeLinker::Init(); #endif } }; StaticAutoPtr PDMFactory::sInstance; StaticMutex PDMFactory::sMonitor; class SupportChecker { public: enum class Reason : uint8_t { kSupported, kVideoFormatNotSupported, kAudioFormatNotSupported, kUnknown, }; struct CheckResult { explicit CheckResult(Reason aReason, MediaResult aResult = MediaResult(NS_OK)) : mReason(aReason), mMediaResult(std::move(aResult)) {} CheckResult(const CheckResult& aOther) = default; CheckResult(CheckResult&& aOther) = default; CheckResult& operator=(const CheckResult& aOther) = default; CheckResult& operator=(CheckResult&& aOther) = default; Reason mReason; MediaResult mMediaResult; }; template void AddToCheckList(Func&& aChecker) { mCheckerList.AppendElement(std::forward(aChecker)); } void AddMediaFormatChecker(const TrackInfo& aTrackConfig) { if (aTrackConfig.IsVideo()) { auto mimeType = aTrackConfig.GetAsVideoInfo()->mMimeType; RefPtr extraData = aTrackConfig.GetAsVideoInfo()->mExtraData; AddToCheckList([mimeType, extraData]() { if (MP4Decoder::IsH264(mimeType)) { SPSData spsdata; // WMF H.264 Video Decoder and Apple ATDecoder // do not support YUV444 format. // For consistency, all decoders should be checked. if (H264::DecodeSPSFromExtraData(extraData, spsdata) && (spsdata.profile_idc == 244 /* Hi444PP */ || spsdata.chroma_format_idc == PDMFactory::kYUV444)) { return CheckResult( SupportChecker::Reason::kVideoFormatNotSupported, MediaResult( NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("Decoder may not have the capability " "to handle the requested video format " "with YUV444 chroma subsampling."))); } } return CheckResult(SupportChecker::Reason::kSupported); }); } } SupportChecker::CheckResult Check() { for (auto& checker : mCheckerList) { auto result = checker(); if (result.mReason != SupportChecker::Reason::kSupported) { return result; } } return CheckResult(SupportChecker::Reason::kSupported); } void Clear() { mCheckerList.Clear(); } private: nsTArray> mCheckerList; }; // SupportChecker PDMFactory::PDMFactory() { EnsureInit(); CreatePDMs(); CreateNullPDM(); } PDMFactory::~PDMFactory() = default; /* static */ void PDMFactory::EnsureInit() { { StaticMutexAutoLock mon(sMonitor); if (sInstance) { // Quick exit if we already have an instance. return; } } auto initalization = []() { MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); StaticMutexAutoLock mon(sMonitor); if (!sInstance) { // Ensure that all system variables are initialized. gfx::gfxVars::Initialize(); // Prime the preferences system from the main thread. Unused << BrowserTabsRemoteAutostart(); // On the main thread and holding the lock -> Create instance. sInstance = new PDMFactoryImpl(); ClearOnShutdown(&sInstance); } }; if (NS_IsMainThread()) { initalization(); return; } // Not on the main thread -> Sync-dispatch creation to main thread. nsCOMPtr mainTarget = GetMainThreadEventTarget(); nsCOMPtr runnable = NS_NewRunnableFunction( "PDMFactory::EnsureInit", std::move(initalization)); SyncRunnable::DispatchToThread(mainTarget, runnable); } RefPtr PDMFactory::CreateDecoder( const CreateDecoderParams& aParams) { if (aParams.mUseNullDecoder.mUse) { MOZ_ASSERT(mNullPDM); return CreateDecoderWithPDM(mNullPDM, aParams); } bool isEncrypted = mEMEPDM && aParams.mConfig.mCrypto.IsEncrypted(); if (isEncrypted) { return CreateDecoderWithPDM(mEMEPDM, aParams); } return CheckAndMaybeCreateDecoder(CreateDecoderParamsForAsync(aParams), 0); } RefPtr PDMFactory::CheckAndMaybeCreateDecoder(CreateDecoderParamsForAsync&& aParams, uint32_t aIndex) { uint32_t i = aIndex; auto params = SupportDecoderParams(aParams); for (; i < mCurrentPDMs.Length(); i++) { if (!mCurrentPDMs[i]->Supports(params, nullptr /* diagnostic */)) { continue; } RefPtr p = CreateDecoderWithPDM(mCurrentPDMs[i], aParams) ->Then( GetCurrentSerialEventTarget(), __func__, [](RefPtr&& aDecoder) { return PlatformDecoderModule::CreateDecoderPromise:: CreateAndResolve(std::move(aDecoder), __func__); }, [self = RefPtr{this}, i, params = std::move(aParams)]( const MediaResult& aError) mutable { // Try the next PDM. return self->CheckAndMaybeCreateDecoder(std::move(params), i + 1); }); return p; } return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, nsPrintfCString("Error no decoder found for %s", aParams.mConfig->mMimeType.get()) .get()), __func__); } RefPtr PDMFactory::CreateDecoderWithPDM(PlatformDecoderModule* aPDM, const CreateDecoderParams& aParams) { MOZ_ASSERT(aPDM); MediaResult result = NS_OK; SupportChecker supportChecker; const TrackInfo& config = aParams.mConfig; supportChecker.AddMediaFormatChecker(config); auto checkResult = supportChecker.Check(); if (checkResult.mReason != SupportChecker::Reason::kSupported) { if (checkResult.mReason == SupportChecker::Reason::kVideoFormatNotSupported) { result = checkResult.mMediaResult; } else if (checkResult.mReason == SupportChecker::Reason::kAudioFormatNotSupported) { result = checkResult.mMediaResult; } return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( result, __func__); } if (config.IsAudio()) { RefPtr p; p = aPDM->AsyncCreateDecoder(aParams)->Then( GetCurrentSerialEventTarget(), __func__, [params = CreateDecoderParamsForAsync(aParams)]( RefPtr&& aDecoder) { RefPtr decoder = std::move(aDecoder); if (!params.mNoWrapper.mDontUseWrapper) { decoder = new AudioTrimmer(decoder.forget(), CreateDecoderParams(params)); } return PlatformDecoderModule::CreateDecoderPromise::CreateAndResolve( decoder, __func__); }, [](const MediaResult& aError) { return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( aError, __func__); }); return p; } if (!config.IsVideo()) { return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( MediaResult( NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL( "Decoder configuration error, expected audio or video.")), __func__); } if ((MP4Decoder::IsH264(config.mMimeType) || VPXDecoder::IsVPX(config.mMimeType)) && !aParams.mUseNullDecoder.mUse && !aParams.mNoWrapper.mDontUseWrapper) { return MediaChangeMonitor::Create(aPDM, aParams); } return aPDM->AsyncCreateDecoder(aParams); } bool PDMFactory::SupportsMimeType( const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const { UniquePtr trackInfo = CreateTrackInfoWithMIMEType(aMimeType); if (!trackInfo) { return false; } return Supports(SupportDecoderParams(*trackInfo), aDiagnostics); } bool PDMFactory::Supports(const SupportDecoderParams& aParams, DecoderDoctorDiagnostics* aDiagnostics) const { if (mEMEPDM) { return mEMEPDM->Supports(aParams, aDiagnostics); } RefPtr current = GetDecoderModule(aParams, aDiagnostics); return !!current; } void PDMFactory::CreatePDMs() { if (StaticPrefs::media_use_blank_decoder()) { CreateAndStartupPDM(); // The Blank PDM SupportsMimeType reports true for all codecs; the creation // of its decoder is infallible. As such it will be used for all media, we // can stop creating more PDM from this point. return; } if (XRE_IsGPUProcess()) { CreateGpuPDMs(); } else if (XRE_IsRDDProcess()) { CreateRddPDMs(); } else if (XRE_IsContentProcess()) { CreateContentPDMs(); } else { MOZ_DIAGNOSTIC_ASSERT( XRE_IsParentProcess(), "PDMFactory is only usable in the Parent/GPU/RDD/Content process"); CreateDefaultPDMs(); } } void PDMFactory::CreateGpuPDMs() { #ifdef XP_WIN if (StaticPrefs::media_wmf_enabled() && !IsWin7AndPre2000Compatible()) { CreateAndStartupPDM(); } #endif } void PDMFactory::CreateRddPDMs() { #ifdef XP_WIN if (StaticPrefs::media_wmf_enabled() && StaticPrefs::media_rdd_wmf_enabled()) { CreateAndStartupPDM(); } #endif #ifdef MOZ_APPLEMEDIA if (StaticPrefs::media_rdd_applemedia_enabled()) { CreateAndStartupPDM(); } #endif #ifdef MOZ_FFVPX if (StaticPrefs::media_ffvpx_enabled() && StaticPrefs::media_rdd_ffvpx_enabled()) { CreateAndStartupPDM(); } #endif #ifdef MOZ_FFMPEG if (StaticPrefs::media_ffmpeg_enabled() && StaticPrefs::media_rdd_ffmpeg_enabled() && !CreateAndStartupPDM()) { mFailureFlags += DecoderDoctorDiagnostics::Flags::FFmpegFailedToLoad; } else { mFailureFlags -= DecoderDoctorDiagnostics::Flags::FFmpegFailedToLoad; } #endif CreateAndStartupPDM(); } void PDMFactory::CreateContentPDMs() { if (StaticPrefs::media_gpu_process_decoder()) { CreateAndStartupPDM(RemoteDecodeIn::GpuProcess); } if (StaticPrefs::media_rdd_process_enabled()) { CreateAndStartupPDM(RemoteDecodeIn::RddProcess); } #ifdef XP_WIN if (StaticPrefs::media_wmf_enabled() && !IsWin7AndPre2000Compatible()) { RefPtr m = MakeAndAddRef(); if (!StartupPDM(m.forget())) { mFailureFlags += DecoderDoctorDiagnostics::Flags::WMFFailedToLoad; } } else if (StaticPrefs::media_decoder_doctor_wmf_disabled_is_failure()) { mFailureFlags += DecoderDoctorDiagnostics::Flags::WMFFailedToLoad; } #endif #ifdef MOZ_APPLEMEDIA CreateAndStartupPDM(); #endif #ifdef MOZ_OMX if (StaticPrefs::media_omx_enabled()) { CreateAndStartupPDM(); } #endif #ifdef MOZ_FFVPX if (StaticPrefs::media_ffvpx_enabled()) { CreateAndStartupPDM(); } #endif #ifdef MOZ_FFMPEG if (StaticPrefs::media_ffmpeg_enabled() && !CreateAndStartupPDM()) { mFailureFlags += DecoderDoctorDiagnostics::Flags::FFmpegFailedToLoad; } #endif #ifdef MOZ_WIDGET_ANDROID if (StaticPrefs::media_android_media_codec_enabled()) { StartupPDM(AndroidDecoderModule::Create(), StaticPrefs::media_android_media_codec_preferred()); } #endif CreateAndStartupPDM(); if (StaticPrefs::media_gmp_decoder_enabled() && !CreateAndStartupPDM()) { mFailureFlags += DecoderDoctorDiagnostics::Flags::GMPPDMFailedToStartup; } } void PDMFactory::CreateDefaultPDMs() { #ifdef XP_WIN if (StaticPrefs::media_wmf_enabled() && !IsWin7AndPre2000Compatible()) { RefPtr m = MakeAndAddRef(); if (!StartupPDM(m.forget())) { mFailureFlags += DecoderDoctorDiagnostics::Flags::WMFFailedToLoad; } } else if (StaticPrefs::media_decoder_doctor_wmf_disabled_is_failure()) { mFailureFlags += DecoderDoctorDiagnostics::Flags::WMFFailedToLoad; } #endif #ifdef MOZ_APPLEMEDIA CreateAndStartupPDM(); #endif #ifdef MOZ_OMX if (StaticPrefs::media_omx_enabled()) { CreateAndStartupPDM(); } #endif #ifdef MOZ_FFVPX if (StaticPrefs::media_ffvpx_enabled()) { CreateAndStartupPDM(); } #endif #ifdef MOZ_FFMPEG if (StaticPrefs::media_ffmpeg_enabled() && !CreateAndStartupPDM()) { mFailureFlags += DecoderDoctorDiagnostics::Flags::FFmpegFailedToLoad; } #endif #ifdef MOZ_WIDGET_ANDROID if (StaticPrefs::media_android_media_codec_enabled()) { StartupPDM(AndroidDecoderModule::Create(), StaticPrefs::media_android_media_codec_preferred()); } #endif CreateAndStartupPDM(); if (StaticPrefs::media_gmp_decoder_enabled() && !CreateAndStartupPDM()) { mFailureFlags += DecoderDoctorDiagnostics::Flags::GMPPDMFailedToStartup; } } void PDMFactory::CreateNullPDM() { mNullPDM = CreateNullDecoderModule(); MOZ_ASSERT(mNullPDM && NS_SUCCEEDED(mNullPDM->Startup())); } bool PDMFactory::StartupPDM(already_AddRefed aPDM, bool aInsertAtBeginning) { RefPtr pdm = aPDM; if (pdm && NS_SUCCEEDED(pdm->Startup())) { if (aInsertAtBeginning) { mCurrentPDMs.InsertElementAt(0, pdm); } else { mCurrentPDMs.AppendElement(pdm); } return true; } return false; } already_AddRefed PDMFactory::GetDecoderModule( const SupportDecoderParams& aParams, DecoderDoctorDiagnostics* aDiagnostics) const { if (aDiagnostics) { // If libraries failed to load, the following loop over mCurrentPDMs // will not even try to use them. So we record failures now. aDiagnostics->SetFailureFlags(mFailureFlags); } RefPtr pdm; for (auto& current : mCurrentPDMs) { if (current->Supports(aParams, aDiagnostics)) { pdm = current; break; } } return pdm.forget(); } void PDMFactory::SetCDMProxy(CDMProxy* aProxy) { MOZ_ASSERT(aProxy); #ifdef MOZ_WIDGET_ANDROID if (IsWidevineKeySystem(aProxy->KeySystem())) { mEMEPDM = AndroidDecoderModule::Create(aProxy); return; } #endif auto m = MakeRefPtr(); mEMEPDM = MakeRefPtr(aProxy, m); } /* static */ PDMFactory::MediaCodecsSupported PDMFactory::Supported(bool aForceRefresh) { MOZ_ASSERT(NS_IsMainThread()); static auto calculate = []() { auto pdm = MakeRefPtr(); MediaCodecsSupported supported; // H264 and AAC depends on external framework that must be dynamically // loaded. // We currently only ship a single PDM per platform able to decode AAC or // H264. As such we can assert that being able to create a H264 or AAC // decoder indicates that with WMF on Windows or FFmpeg on Unixes is // available. // This logic will have to be revisited if a PDM supporting either codec // will be added in addition to the WMF and FFmpeg PDM (such as OpenH264) if (pdm->SupportsMimeType("video/avc"_ns, nullptr)) { supported += MediaCodecs::H264; } if (pdm->SupportsMimeType("video/vp9"_ns, nullptr)) { supported += MediaCodecs::VP9; } if (pdm->SupportsMimeType("video/vp8"_ns, nullptr)) { supported += MediaCodecs::VP8; } if (pdm->SupportsMimeType("video/av1"_ns, nullptr)) { supported += MediaCodecs::AV1; } if (pdm->SupportsMimeType("video/theora"_ns, nullptr)) { supported += MediaCodecs::Theora; } if (pdm->SupportsMimeType("audio/mp4a-latm"_ns, nullptr)) { supported += MediaCodecs::AAC; } // MP3 can be either decoded by ffvpx or WMF/FFmpeg if (pdm->SupportsMimeType("audio/mpeg"_ns, nullptr)) { supported += MediaCodecs::MP3; } if (pdm->SupportsMimeType("audio/opus"_ns, nullptr)) { supported += MediaCodecs::Opus; } if (pdm->SupportsMimeType("audio/vorbis"_ns, nullptr)) { supported += MediaCodecs::Vorbis; } if (pdm->SupportsMimeType("audio/flac"_ns, nullptr)) { supported += MediaCodecs::Flac; } if (pdm->SupportsMimeType("audio/x-wav"_ns, nullptr)) { supported += MediaCodecs::Wave; } return supported; }; static MediaCodecsSupported supported = calculate(); if (aForceRefresh) { supported = calculate(); } return supported; } /* static */ bool PDMFactory::SupportsMimeType(const nsACString& aMimeType, const MediaCodecsSupported& aSupported) { if (MP4Decoder::IsH264(aMimeType)) { return aSupported.contains(MediaCodecs::H264); } if (VPXDecoder::IsVP9(aMimeType)) { return aSupported.contains(MediaCodecs::VP9); } if (VPXDecoder::IsVP8(aMimeType)) { return aSupported.contains(MediaCodecs::VP8); } #ifdef MOZ_AV1 if (AOMDecoder::IsAV1(aMimeType)) { return aSupported.contains(MediaCodecs::AV1); } #endif if (TheoraDecoder::IsTheora(aMimeType)) { return aSupported.contains(MediaCodecs::Theora); } if (MP4Decoder::IsAAC(aMimeType)) { return aSupported.contains(MediaCodecs::AAC); } if (aMimeType.EqualsLiteral("audio/mpeg")) { return aSupported.contains(MediaCodecs::MP3); } if (OpusDataDecoder::IsOpus(aMimeType)) { return aSupported.contains(MediaCodecs::Opus); } if (VorbisDataDecoder::IsVorbis(aMimeType)) { return aSupported.contains(MediaCodecs::Vorbis); } if (aMimeType.EqualsLiteral("audio/flac")) { return aSupported.contains(MediaCodecs::Flac); } if (WaveDataDecoder::IsWave(aMimeType)) { return aSupported.contains(MediaCodecs::Wave); } return false; } } // namespace mozilla