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/ipc | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
37 files changed, 5892 insertions, 0 deletions
diff --git a/dom/media/ipc/MediaIPCUtils.h b/dom/media/ipc/MediaIPCUtils.h new file mode 100644 index 0000000000..b940a3af8b --- /dev/null +++ b/dom/media/ipc/MediaIPCUtils.h @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_dom_media_MediaIPCUtils_h +#define mozilla_dom_media_MediaIPCUtils_h + +#include "DecoderDoctorDiagnostics.h" +#include "PlatformDecoderModule.h" +#include "ipc/EnumSerializer.h" +#include "mozilla/EnumSet.h" +#include "mozilla/GfxMessageUtils.h" +#include "mozilla/gfx/Rect.h" + +namespace IPC { +template <> +struct ParamTraits<mozilla::VideoInfo> { + typedef mozilla::VideoInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + // TrackInfo + WriteParam(aMsg, aParam.mMimeType); + + // VideoInfo + WriteParam(aMsg, aParam.mDisplay); + WriteParam(aMsg, aParam.mStereoMode); + WriteParam(aMsg, aParam.mImage); + WriteParam(aMsg, aParam.mImageRect); + WriteParam(aMsg, *aParam.mCodecSpecificConfig); + WriteParam(aMsg, *aParam.mExtraData); + WriteParam(aMsg, aParam.mRotation); + WriteParam(aMsg, aParam.mColorDepth); + WriteParam(aMsg, aParam.mColorSpace); + WriteParam(aMsg, aParam.mColorRange); + WriteParam(aMsg, aParam.HasAlpha()); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + mozilla::gfx::IntRect imageRect; + bool alphaPresent; + if (ReadParam(aMsg, aIter, &aResult->mMimeType) && + ReadParam(aMsg, aIter, &aResult->mDisplay) && + ReadParam(aMsg, aIter, &aResult->mStereoMode) && + ReadParam(aMsg, aIter, &aResult->mImage) && + ReadParam(aMsg, aIter, &aResult->mImageRect) && + ReadParam(aMsg, aIter, aResult->mCodecSpecificConfig.get()) && + ReadParam(aMsg, aIter, aResult->mExtraData.get()) && + ReadParam(aMsg, aIter, &aResult->mRotation) && + ReadParam(aMsg, aIter, &aResult->mColorDepth) && + ReadParam(aMsg, aIter, &aResult->mColorSpace) && + ReadParam(aMsg, aIter, &aResult->mColorRange) && + ReadParam(aMsg, aIter, &alphaPresent)) { + aResult->SetAlpha(alphaPresent); + return true; + } + return false; + } +}; + +template <> +struct ParamTraits<mozilla::TrackInfo::TrackType> + : public ContiguousEnumSerializerInclusive< + mozilla::TrackInfo::TrackType, + mozilla::TrackInfo::TrackType::kUndefinedTrack, + mozilla::TrackInfo::TrackType::kTextTrack> {}; + +template <> +struct ParamTraits<mozilla::VideoInfo::Rotation> + : public ContiguousEnumSerializerInclusive< + mozilla::VideoInfo::Rotation, mozilla::VideoInfo::Rotation::kDegree_0, + mozilla::VideoInfo::Rotation::kDegree_270> {}; + +template <> +struct ParamTraits<mozilla::MediaByteBuffer> + : public ParamTraits<nsTArray<uint8_t>> { + typedef mozilla::MediaByteBuffer paramType; +}; + +template <> +struct ParamTraits<mozilla::AudioInfo> { + typedef mozilla::AudioInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + // TrackInfo + WriteParam(aMsg, aParam.mMimeType); + + // AudioInfo + WriteParam(aMsg, aParam.mRate); + WriteParam(aMsg, aParam.mChannels); + WriteParam(aMsg, aParam.mChannelMap); + WriteParam(aMsg, aParam.mBitDepth); + WriteParam(aMsg, aParam.mProfile); + WriteParam(aMsg, aParam.mExtendedProfile); + WriteParam(aMsg, *aParam.mCodecSpecificConfig); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (ReadParam(aMsg, aIter, &aResult->mMimeType) && + ReadParam(aMsg, aIter, &aResult->mRate) && + ReadParam(aMsg, aIter, &aResult->mChannels) && + ReadParam(aMsg, aIter, &aResult->mChannelMap) && + ReadParam(aMsg, aIter, &aResult->mBitDepth) && + ReadParam(aMsg, aIter, &aResult->mProfile) && + ReadParam(aMsg, aIter, &aResult->mExtendedProfile) && + ReadParam(aMsg, aIter, aResult->mCodecSpecificConfig.get())) { + return true; + } + return false; + } +}; + +template <> +struct ParamTraits<mozilla::MediaDataDecoder::ConversionRequired> + : public ContiguousEnumSerializerInclusive< + mozilla::MediaDataDecoder::ConversionRequired, + mozilla::MediaDataDecoder::ConversionRequired(0), + mozilla::MediaDataDecoder::ConversionRequired( + mozilla::MediaDataDecoder::ConversionRequired::kNeedAnnexB)> {}; + +template <> +struct ParamTraits<mozilla::media::TimeUnit> { + typedef mozilla::media::TimeUnit paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.IsValid()); + WriteParam(aMsg, aParam.IsValid() ? aParam.ToMicroseconds() : 0); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + bool valid; + int64_t value; + if (ReadParam(aMsg, aIter, &valid) && ReadParam(aMsg, aIter, &value)) { + if (!valid) { + *aResult = mozilla::media::TimeUnit::Invalid(); + } else { + *aResult = mozilla::media::TimeUnit::FromMicroseconds(value); + } + return true; + } + return false; + }; +}; + +template <> +struct ParamTraits<mozilla::media::TimeInterval> { + typedef mozilla::media::TimeInterval paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mStart); + WriteParam(aMsg, aParam.mEnd); + WriteParam(aMsg, aParam.mFuzz); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (ReadParam(aMsg, aIter, &aResult->mStart) && + ReadParam(aMsg, aIter, &aResult->mEnd) && + ReadParam(aMsg, aIter, &aResult->mFuzz)) { + return true; + } + return false; + } +}; + +template <> +struct ParamTraits<mozilla::MediaResult> { + typedef mozilla::MediaResult paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.Code()); + WriteParam(aMsg, aParam.Message()); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + nsresult result; + nsCString message; + if (ReadParam(aMsg, aIter, &result) && ReadParam(aMsg, aIter, &message)) { + *aResult = paramType(result, std::move(message)); + return true; + } + return false; + }; +}; + +template <> +struct ParamTraits<mozilla::DecoderDoctorDiagnostics> { + typedef mozilla::DecoderDoctorDiagnostics paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mDiagnosticsType); + WriteParam(aMsg, aParam.mFormat); + WriteParam(aMsg, aParam.mFlags); + WriteParam(aMsg, aParam.mEvent); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (ReadParam(aMsg, aIter, &aResult->mDiagnosticsType) && + ReadParam(aMsg, aIter, &aResult->mFormat) && + ReadParam(aMsg, aIter, &aResult->mFlags) && + ReadParam(aMsg, aIter, &aResult->mEvent)) { + return true; + } + return false; + }; +}; + +template <> +struct ParamTraits<mozilla::DecoderDoctorDiagnostics::DiagnosticsType> + : public ContiguousEnumSerializerInclusive< + mozilla::DecoderDoctorDiagnostics::DiagnosticsType, + mozilla::DecoderDoctorDiagnostics::DiagnosticsType::eUnsaved, + mozilla::DecoderDoctorDiagnostics::DiagnosticsType::eDecodeWarning> { +}; + +template <> +struct ParamTraits<mozilla::DecoderDoctorEvent> { + typedef mozilla::DecoderDoctorEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + int domain = aParam.mDomain; + WriteParam(aMsg, domain); + WriteParam(aMsg, aParam.mResult); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + int domain = 0; + if (ReadParam(aMsg, aIter, &domain) && + ReadParam(aMsg, aIter, &aResult->mResult)) { + aResult->mDomain = paramType::Domain(domain); + return true; + } + return false; + }; +}; + +} // namespace IPC + +#endif // mozilla_dom_media_MediaIPCUtils_h diff --git a/dom/media/ipc/PMediaDecoderParams.ipdlh b/dom/media/ipc/PMediaDecoderParams.ipdlh new file mode 100644 index 0000000000..636b91bafa --- /dev/null +++ b/dom/media/ipc/PMediaDecoderParams.ipdlh @@ -0,0 +1,21 @@ +/* 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 "mozilla/dom/MediaIPCUtils.h"; + +using mozilla::media::TimeUnit from "TimeUnits.h"; + +namespace mozilla { + +// used for both SendInput/RecvInput and ProcessDecodedData/RecvOutput +struct MediaDataIPDL +{ + int64_t offset; + TimeUnit time; + TimeUnit timecode; + TimeUnit duration; + bool keyframe; +}; + +} // namespace mozilla diff --git a/dom/media/ipc/PRDD.ipdl b/dom/media/ipc/PRDD.ipdl new file mode 100644 index 0000000000..e2898f4cab --- /dev/null +++ b/dom/media/ipc/PRDD.ipdl @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 GraphicsMessages; +include MemoryReportTypes; +include PrefsTypes; + +include protocol PProfiler; +include protocol PRemoteDecoderManager; +include protocol PVideoBridge; + +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; +using moveonly mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h"; +using moveonly mozilla::ModulePaths from "mozilla/UntrustedModulesData.h"; +using moveonly mozilla::ModulesMapResult from "mozilla/UntrustedModulesData.h"; +using mozilla::PDMFactory::MediaCodecsSupported from "PDMFactory.h"; + +namespace mozilla { + +// This protocol allows the UI process to talk to the RDD +// (RemoteDataDecoder) process. There is one instance of this protocol, +// with the RDDParent living on the main thread of the RDD process and +// the RDDChild living on the main thread of the UI process. +protocol PRDD +{ +parent: + + async Init(GfxVarUpdate[] vars, FileDescriptor? sandboxBroker, + bool canRecordReleaseTelemetry); + + async InitProfiler(Endpoint<PProfilerChild> endpoint); + + async NewContentRemoteDecoderManager( + Endpoint<PRemoteDecoderManagerParent> endpoint); + + async RequestMemoryReport(uint32_t generation, + bool anonymize, + bool minimizeMemoryUsage, + FileDescriptor? DMDFile) + returns (uint32_t aGeneration); + + async PreferenceUpdate(Pref pref); + + async UpdateVar(GfxVarUpdate var); + + async InitVideoBridge(Endpoint<PVideoBridgeChild> endpoint, + bool createHardwareDevice, + ContentDeviceData contentDeviceData); + + async GetUntrustedModulesData() returns (UntrustedModulesData? data); + +child: + + async InitCrashReporter(NativeThreadId threadId); + + async AddMemoryReport(MemoryReport aReport); + + async GetModulesTrust(ModulePaths aModPaths, bool aRunAtNormalPriority) + returns (ModulesMapResult? modMapResult); + + // Update the cached list of codec supported following a check in the + // RDD parent. + async UpdateMediaCodecsSupported(MediaCodecsSupported aSupported); +}; + +} // namespace mozilla diff --git a/dom/media/ipc/PRemoteDecoder.ipdl b/dom/media/ipc/PRemoteDecoder.ipdl new file mode 100644 index 0000000000..f71b08cda4 --- /dev/null +++ b/dom/media/ipc/PRemoteDecoder.ipdl @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/dom/MediaIPCUtils.h"; + +include protocol PRemoteDecoderManager; + +using mozilla::MediaDataDecoder::ConversionRequired from "PlatformDecoderModule.h"; +using mozilla::TrackInfo::TrackType from "MediaInfo.h"; +using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; +using mozilla::MediaResult from "MediaResult.h"; +using refcounted class mozilla::ArrayOfRemoteMediaRawData from "mozilla/RemoteMediaData.h"; +using refcounted class mozilla::ArrayOfRemoteAudioData from "mozilla/RemoteMediaData.h"; +using refcounted class mozilla::ArrayOfRemoteVideoData from "mozilla/RemoteMediaData.h"; +include PMediaDecoderParams; +include LayersSurfaces; + +namespace mozilla { + +union DecodedOutputIPDL +{ + ArrayOfRemoteAudioData; + ArrayOfRemoteVideoData; +}; + +struct InitCompletionIPDL +{ + TrackType type; + nsCString decoderDescription; + bool hardware; + nsCString hardwareReason; + ConversionRequired conversion; +}; + +union InitResultIPDL +{ + MediaResult; + InitCompletionIPDL; +}; + +union DecodeResultIPDL +{ + MediaResult; + DecodedOutputIPDL; +}; + +// This protocol provides a way to use MediaDataDecoder across processes. +// The parent side currently is only implemented to work with +// RemoteDecoderModule or WindowsMediaFoundation. +// The child side runs in the content process, and the parent side runs +// in the RDD process or the GPU process. We run a separate IPDL thread +// for both sides. +async protocol PRemoteDecoder +{ + manager PRemoteDecoderManager; +parent: + async Construct() returns (MediaResult result); + + async Init() returns (InitResultIPDL result); + + // Each output may include a SurfaceDescriptorGPUVideo that represents the decoded + // frame. This SurfaceDescriptor can be used on the Layers IPDL protocol, but + // must be released explicitly using DeallocateSurfaceDescriptorGPUVideo + // on the manager protocol. + async Decode(ArrayOfRemoteMediaRawData data) returns (DecodeResultIPDL result); + async Flush() returns (MediaResult error); + async Drain() returns (DecodeResultIPDL result); + async Shutdown() returns (bool unused); + // To clear the threshold, call with INT64_MIN. + async SetSeekThreshold(TimeUnit time); + + async __delete__(); +}; + +} // namespace mozilla diff --git a/dom/media/ipc/PRemoteDecoderManager.ipdl b/dom/media/ipc/PRemoteDecoderManager.ipdl new file mode 100644 index 0000000000..1e8f4eafe9 --- /dev/null +++ b/dom/media/ipc/PRemoteDecoderManager.ipdl @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 protocol PTexture; +include protocol PRemoteDecoder; +include LayersSurfaces; +include "mozilla/dom/MediaIPCUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; + +using VideoInfo from "MediaInfo.h"; +using AudioInfo from "MediaInfo.h"; +using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; +using mozilla::CreateDecoderParams::OptionSet from "PlatformDecoderModule.h"; +using mozilla::DecoderDoctorDiagnostics from "DecoderDoctorDiagnostics.h"; + +namespace mozilla { + +struct VideoDecoderInfoIPDL +{ + VideoInfo videoInfo; + float framerate; +}; + +union RemoteDecoderInfoIPDL +{ + AudioInfo; + VideoDecoderInfoIPDL; +}; + +sync protocol PRemoteDecoderManager +{ + manages PRemoteDecoder; + +parent: + async PRemoteDecoder(RemoteDecoderInfoIPDL info, + OptionSet options, + TextureFactoryIdentifier? identifier); + + sync Readback(SurfaceDescriptorGPUVideo sd) returns (SurfaceDescriptor aResult); + + async DeallocateSurfaceDescriptorGPUVideo(SurfaceDescriptorGPUVideo sd); +}; + +} // namespace mozilla diff --git a/dom/media/ipc/RDDChild.cpp b/dom/media/ipc/RDDChild.cpp new file mode 100644 index 0000000000..5d51a49b30 --- /dev/null +++ b/dom/media/ipc/RDDChild.cpp @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RDDChild.h" + +#include "mozilla/RDDProcessManager.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/CrashReporterHost.h" +#include "mozilla/ipc/Endpoint.h" + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxBroker.h" +# include "mozilla/SandboxBrokerPolicyFactory.h" +#endif + +#include "mozilla/Telemetry.h" + +#if defined(XP_WIN) +# include "mozilla/WinDllServices.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "ProfilerParent.h" +#endif +#include "RDDProcessHost.h" + +namespace mozilla { + +using namespace layers; +using namespace gfx; + +RDDChild::RDDChild(RDDProcessHost* aHost) : mHost(aHost) { + MOZ_COUNT_CTOR(RDDChild); +} + +RDDChild::~RDDChild() { MOZ_COUNT_DTOR(RDDChild); } + +bool RDDChild::Init() { + Maybe<FileDescriptor> brokerFd; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + auto policy = SandboxBrokerPolicyFactory::GetRDDPolicy(OtherPid()); + if (policy != nullptr) { + brokerFd = Some(FileDescriptor()); + mSandboxBroker = + SandboxBroker::Create(std::move(policy), OtherPid(), brokerFd.ref()); + // This is unlikely to fail and probably indicates OS resource + // exhaustion, but we can at least try to recover. + if (NS_WARN_IF(mSandboxBroker == nullptr)) { + return false; + } + MOZ_ASSERT(brokerFd.ref().IsValid()); + } +#endif // XP_LINUX && MOZ_SANDBOX + + nsTArray<GfxVarUpdate> updates = gfxVars::FetchNonDefaultVars(); + + SendInit(updates, brokerFd, Telemetry::CanRecordReleaseData()); + +#ifdef MOZ_GECKO_PROFILER + Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid())); +#endif + + gfxVars::AddReceiver(this); + auto* gpm = gfx::GPUProcessManager::Get(); + if (gpm) { + gpm->AddListener(this); + } + + return true; +} + +bool RDDChild::SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<FileDescriptor>& aDMDFile) { + mMemoryReportRequest = MakeUnique<MemoryReportRequestHost>(aGeneration); + + PRDDChild::SendRequestMemoryReport( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, + [&](const uint32_t& aGeneration2) { + if (RDDProcessManager* rddpm = RDDProcessManager::Get()) { + if (RDDChild* child = rddpm->GetRDDChild()) { + if (child->mMemoryReportRequest) { + child->mMemoryReportRequest->Finish(aGeneration2); + child->mMemoryReportRequest = nullptr; + } + } + } + }, + [&](mozilla::ipc::ResponseRejectReason) { + if (RDDProcessManager* rddpm = RDDProcessManager::Get()) { + if (RDDChild* child = rddpm->GetRDDChild()) { + child->mMemoryReportRequest = nullptr; + } + } + }); + + return true; +} + +void RDDChild::OnCompositorUnexpectedShutdown() { + auto* rddm = RDDProcessManager::Get(); + if (rddm) { + rddm->CreateVideoBridge(); + } +} + +void RDDChild::OnVarChanged(const GfxVarUpdate& aVar) { SendUpdateVar(aVar); } + +mozilla::ipc::IPCResult RDDChild::RecvAddMemoryReport( + const MemoryReport& aReport) { + if (mMemoryReportRequest) { + mMemoryReportRequest->RecvReport(aReport); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDChild::RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver) { +#if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->GetModulesTrust(std::move(aModPaths), aRunAtNormalPriority) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](ModulesMapResult&& aResult) { + aResolver(Some(ModulesMapResult(std::move(aResult)))); + }, + [aResolver](nsresult aRv) { aResolver(Nothing()); }); + return IPC_OK(); +#else + return IPC_FAIL(this, "Unsupported on this platform"); +#endif // defined(XP_WIN) +} + +mozilla::ipc::IPCResult RDDChild::RecvUpdateMediaCodecsSupported( + const PDMFactory::MediaCodecsSupported& aSupported) { + dom::ContentParent::BroadcastMediaCodecsSupportedUpdate( + RemoteDecodeIn::RddProcess, aSupported); + return IPC_OK(); +} + +void RDDChild::ActorDestroy(ActorDestroyReason aWhy) { + if (aWhy == AbnormalShutdown) { + GenerateCrashReport(OtherPid()); + } + + auto* gpm = gfx::GPUProcessManager::Get(); + if (gpm) { + // Note: the manager could have shutdown already. + gpm->RemoveListener(this); + } + + gfxVars::RemoveReceiver(this); + mHost->OnChannelClosed(); +} + +class DeferredDeleteRDDChild : public Runnable { + public: + explicit DeferredDeleteRDDChild(UniquePtr<RDDChild>&& aChild) + : Runnable("gfx::DeferredDeleteRDDChild"), mChild(std::move(aChild)) {} + + NS_IMETHODIMP Run() override { return NS_OK; } + + private: + UniquePtr<RDDChild> mChild; +}; + +/* static */ +void RDDChild::Destroy(UniquePtr<RDDChild>&& aChild) { + NS_DispatchToMainThread(new DeferredDeleteRDDChild(std::move(aChild))); +} + +} // namespace mozilla diff --git a/dom/media/ipc/RDDChild.h b/dom/media/ipc/RDDChild.h new file mode 100644 index 0000000000..af90afe11e --- /dev/null +++ b/dom/media/ipc/RDDChild.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 _include_dom_media_ipc_RDDChild_h_ +#define _include_dom_media_ipc_RDDChild_h_ +#include "mozilla/PRDDChild.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/GPUProcessListener.h" +#include "mozilla/gfx/gfxVarReceiver.h" +#include "mozilla/ipc/CrashReporterHelper.h" + +namespace mozilla { + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +class SandboxBroker; +#endif + +namespace dom { +class MemoryReportRequestHost; +} // namespace dom + +class RDDProcessHost; + +class RDDChild final : public PRDDChild, + public ipc::CrashReporterHelper<GeckoProcessType_RDD>, + public gfx::gfxVarReceiver, + public gfx::GPUProcessListener { + typedef mozilla::dom::MemoryReportRequestHost MemoryReportRequestHost; + + public: + explicit RDDChild(RDDProcessHost* aHost); + ~RDDChild(); + + bool Init(); + + void OnCompositorUnexpectedShutdown() override; + void OnVarChanged(const GfxVarUpdate& aVar) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport); + mozilla::ipc::IPCResult RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver); + mozilla::ipc::IPCResult RecvUpdateMediaCodecsSupported( + const PDMFactory::MediaCodecsSupported& aSupported); + + bool SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile); + + static void Destroy(UniquePtr<RDDChild>&& aChild); + + private: + RDDProcessHost* mHost; + UniquePtr<MemoryReportRequestHost> mMemoryReportRequest; +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + UniquePtr<SandboxBroker> mSandboxBroker; +#endif +}; + +} // namespace mozilla + +#endif // _include_dom_media_ipc_RDDChild_h_ diff --git a/dom/media/ipc/RDDParent.cpp b/dom/media/ipc/RDDParent.cpp new file mode 100644 index 0000000000..9bbd5ae18c --- /dev/null +++ b/dom/media/ipc/RDDParent.cpp @@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RDDParent.h" + +#if defined(XP_WIN) +# include <dwrite.h> +# include <process.h> + +# include "WMF.h" +# include "WMFDecoderModule.h" +# include "mozilla/WinDllServices.h" +# include "mozilla/gfx/DeviceManagerDx.h" +#else +# include <unistd.h> +#endif + +#include "PDMFactory.h" +#include "chrome/common/ipc_channel.h" +#include "gfxConfig.h" +#include "mozilla/Assertions.h" +#include "mozilla/HangDetails.h" +#include "mozilla/Preferences.h" +#include "mozilla/RemoteDecoderManagerChild.h" +#include "mozilla/RemoteDecoderManagerParent.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/ProcessChild.h" + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "ChildProfilerController.h" +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "RDDProcessHost.h" +# include "mozilla/Sandbox.h" +# include "nsMacUtilsImpl.h" +#endif + +#include "ProcessUtils.h" +#include "nsDebugImpl.h" +#include "nsThreadManager.h" + +namespace mozilla { + +using namespace ipc; +using namespace gfx; + +static RDDParent* sRDDParent; + +RDDParent::RDDParent() : mLaunchTime(TimeStamp::Now()) { sRDDParent = this; } + +RDDParent::~RDDParent() { sRDDParent = nullptr; } + +/* static */ +RDDParent* RDDParent::GetSingleton() { + MOZ_DIAGNOSTIC_ASSERT(sRDDParent); + return sRDDParent; +} + +bool RDDParent::Init(base::ProcessId aParentPid, const char* aParentBuildID, + MessageLoop* aIOLoop, UniquePtr<IPC::Channel> aChannel) { + // Initialize the thread manager before starting IPC. Otherwise, messages + // may be posted to the main thread and we won't be able to process them. + if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) { + return false; + } + + // Now it's safe to start IPC. + if (NS_WARN_IF(!Open(std::move(aChannel), aParentPid, aIOLoop))) { + return false; + } + + nsDebugImpl::SetMultiprocessMode("RDD"); + + // This must be checked before any IPDL message, which may hit sentinel + // errors due to parent and content processes having different + // versions. + MessageChannel* channel = GetIPCChannel(); + if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID)) { + // We need to quit this process if the buildID doesn't match the parent's. + // This can occur when an update occurred in the background. + ProcessChild::QuickExit(); + } + + // Init crash reporter support. + CrashReporterClient::InitSingleton(this); + + if (NS_FAILED(NS_InitMinimalXPCOM())) { + return false; + } + + gfxConfig::Init(); + gfxVars::Initialize(); +#ifdef XP_WIN + DeviceManagerDx::Init(); + wmf::MFStartup(); +#endif + + mozilla::ipc::SetThisProcessName("RDD Process"); + + return true; +} + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +extern "C" { +void CGSShutdownServerConnections(); +}; +#endif + +mozilla::ipc::IPCResult RDDParent::RecvInit( + nsTArray<GfxVarUpdate>&& vars, const Maybe<FileDescriptor>& aBrokerFd, + const bool& aCanRecordReleaseTelemetry) { + for (const auto& var : vars) { + gfxVars::ApplyUpdate(var); + } + + auto supported = PDMFactory::Supported(); + Unused << SendUpdateMediaCodecsSupported(supported); + +#if defined(MOZ_SANDBOX) +# if defined(XP_MACOSX) + // Close all current connections to the WindowServer. This ensures that the + // Activity Monitor will not label the content process as "Not responding" + // because it's not running a native event loop. See bug 1384336. + CGSShutdownServerConnections(); + +# elif defined(XP_LINUX) + int fd = -1; + if (aBrokerFd.isSome()) { + fd = aBrokerFd.value().ClonePlatformHandle().release(); + } + SetRemoteDataDecoderSandbox(fd); +# endif // XP_MACOSX/XP_LINUX +#endif // MOZ_SANDBOX + +#if defined(XP_WIN) + if (aCanRecordReleaseTelemetry) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->StartUntrustedModulesProcessor(); + } +#endif // defined(XP_WIN) + return IPC_OK(); +} + +IPCResult RDDParent::RecvUpdateVar(const GfxVarUpdate& aUpdate) { +#if defined(XP_WIN) + auto scopeExit = MakeScopeExit( + [couldUseHWDecoder = gfx::gfxVars::CanUseHardwareVideoDecoding()] { + if (couldUseHWDecoder != gfx::gfxVars::CanUseHardwareVideoDecoding()) { + // The capabilities of the system may have changed, force a refresh by + // re-initializing the WMF PDM. + WMFDecoderModule::Init(); + Unused << RDDParent::GetSingleton()->SendUpdateMediaCodecsSupported( + PDMFactory::Supported(true /* force refresh */)); + } + }); +#endif + gfxVars::ApplyUpdate(aUpdate); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDParent::RecvInitProfiler( + Endpoint<PProfilerChild>&& aEndpoint) { +#ifdef MOZ_GECKO_PROFILER + mProfilerController = ChildProfilerController::Create(std::move(aEndpoint)); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDParent::RecvNewContentRemoteDecoderManager( + Endpoint<PRemoteDecoderManagerParent>&& aEndpoint) { + if (!RemoteDecoderManagerParent::CreateForContent(std::move(aEndpoint))) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDParent::RecvInitVideoBridge( + Endpoint<PVideoBridgeChild>&& aEndpoint, const bool& aCreateHardwareDevice, + const ContentDeviceData& aContentDeviceData) { + if (!RemoteDecoderManagerParent::CreateVideoBridgeToOtherProcess( + std::move(aEndpoint))) { + return IPC_FAIL_NO_REASON(this); + } + + gfxConfig::Inherit( + { + Feature::HW_COMPOSITING, + Feature::D3D11_COMPOSITING, + Feature::OPENGL_COMPOSITING, + Feature::ADVANCED_LAYERS, + Feature::DIRECT2D, + Feature::WEBGPU, + }, + aContentDeviceData.prefs()); +#ifdef XP_WIN + if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + auto* devmgr = DeviceManagerDx::Get(); + if (devmgr) { + devmgr->ImportDeviceInfo(aContentDeviceData.d3d11()); + if (aCreateHardwareDevice) { + devmgr->CreateContentDevices(); + } + } + } +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDParent::RecvRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, const Maybe<FileDescriptor>& aDMDFile, + const RequestMemoryReportResolver& aResolver) { + nsPrintfCString processName("RDD (pid %u)", (unsigned)getpid()); + + mozilla::dom::MemoryReportRequestClient::Start( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, processName, + [&](const MemoryReport& aReport) { + Unused << GetSingleton()->SendAddMemoryReport(aReport); + }, + aResolver); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDParent::RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver) { +#if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->GetUntrustedModulesData()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](Maybe<UntrustedModulesData>&& aData) { + aResolver(std::move(aData)); + }, + [aResolver](nsresult aReason) { aResolver(Nothing()); }); + return IPC_OK(); +#else + return IPC_FAIL(this, "Unsupported on this platform"); +#endif // defined(XP_WIN) +} + +mozilla::ipc::IPCResult RDDParent::RecvPreferenceUpdate(const Pref& aPref) { + Preferences::SetPreference(aPref); + return IPC_OK(); +} + +void RDDParent::ActorDestroy(ActorDestroyReason aWhy) { + if (AbnormalShutdown == aWhy) { + NS_WARNING("Shutting down RDD process early due to a crash!"); + ProcessChild::QuickExit(); + } + +#ifndef NS_FREE_PERMANENT_DATA +# ifdef XP_WIN + wmf::MFShutdown(); +# endif + // No point in going through XPCOM shutdown because we don't keep persistent + // state. + ProcessChild::QuickExit(); +#endif + + // Wait until all RemoteDecoderManagerParent have closed. + mShutdownBlockers.WaitUntilClear(10 * 1000 /* 10s timeout*/) + ->Then(GetCurrentSerialEventTarget(), __func__, [this]() { + +#ifdef XP_WIN + wmf::MFShutdown(); +#endif + +#if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->DisableFull(); +#endif // defined(XP_WIN) + +#ifdef MOZ_GECKO_PROFILER + if (mProfilerController) { + mProfilerController->Shutdown(); + mProfilerController = nullptr; + } +#endif + + RemoteDecoderManagerParent::ShutdownVideoBridge(); + +#ifdef XP_WIN + DeviceManagerDx::Shutdown(); +#endif + gfxVars::Shutdown(); + gfxConfig::Shutdown(); + CrashReporterClient::DestroySingleton(); + XRE_ShutdownChildProcess(); + }); +} + +} // namespace mozilla diff --git a/dom/media/ipc/RDDParent.h b/dom/media/ipc/RDDParent.h new file mode 100644 index 0000000000..8f0e5ea737 --- /dev/null +++ b/dom/media/ipc/RDDParent.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 _include_dom_media_ipc_RDDParent_h__ +#define _include_dom_media_ipc_RDDParent_h__ +#include "mozilla/PRDDParent.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/media/MediaUtils.h" + +namespace mozilla { + +class TimeStamp; +class ChildProfilerController; + +class RDDParent final : public PRDDParent { + public: + RDDParent(); + ~RDDParent(); + + static RDDParent* GetSingleton(); + + AsyncBlockers& AsyncShutdownService() { return mShutdownBlockers; } + + bool Init(base::ProcessId aParentPid, const char* aParentBuildID, + MessageLoop* aIOLoop, UniquePtr<IPC::Channel> aChannel); + + mozilla::ipc::IPCResult RecvInit(nsTArray<GfxVarUpdate>&& vars, + const Maybe<ipc::FileDescriptor>& aBrokerFd, + const bool& aCanRecordReleaseTelemetry); + mozilla::ipc::IPCResult RecvInitProfiler( + Endpoint<PProfilerChild>&& aEndpoint); + + mozilla::ipc::IPCResult RecvNewContentRemoteDecoderManager( + Endpoint<PRemoteDecoderManagerParent>&& aEndpoint); + mozilla::ipc::IPCResult RecvInitVideoBridge( + Endpoint<PVideoBridgeChild>&& aEndpoint, + const bool& aCreateHardwareDevice, + const ContentDeviceData& aContentDeviceData); + mozilla::ipc::IPCResult RecvRequestMemoryReport( + const uint32_t& generation, const bool& anonymize, + const bool& minimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& DMDFile, + const RequestMemoryReportResolver& aResolver); + mozilla::ipc::IPCResult RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver); + mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& pref); + mozilla::ipc::IPCResult RecvUpdateVar(const GfxVarUpdate& pref); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + const TimeStamp mLaunchTime; +#ifdef MOZ_GECKO_PROFILER + RefPtr<ChildProfilerController> mProfilerController; +#endif + AsyncBlockers mShutdownBlockers; +}; + +} // namespace mozilla + +#endif // _include_dom_media_ipc_RDDParent_h__ diff --git a/dom/media/ipc/RDDProcessHost.cpp b/dom/media/ipc/RDDProcessHost.cpp new file mode 100644 index 0000000000..c17c0d292f --- /dev/null +++ b/dom/media/ipc/RDDProcessHost.cpp @@ -0,0 +1,311 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RDDProcessHost.h" + +#include "ProcessUtils.h" +#include "RDDChild.h" +#include "chrome/common/process_watcher.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_media.h" + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +namespace mozilla { + +using namespace ipc; + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +bool RDDProcessHost::sLaunchWithMacSandbox = false; +#endif + +RDDProcessHost::RDDProcessHost(Listener* aListener) + : GeckoChildProcessHost(GeckoProcessType_RDD), + mListener(aListener), + mLiveToken(new media::Refcountable<bool>(true)) { + MOZ_COUNT_CTOR(RDDProcessHost); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (!sLaunchWithMacSandbox) { + sLaunchWithMacSandbox = (PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX") == nullptr); + } + mDisableOSActivityMode = sLaunchWithMacSandbox; +#endif +} + +RDDProcessHost::~RDDProcessHost() { MOZ_COUNT_DTOR(RDDProcessHost); } + +bool RDDProcessHost::Launch(StringVector aExtraOpts) { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched); + MOZ_ASSERT(!mRDDChild); + + mPrefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>(); + if (!mPrefSerializer->SerializeToSharedMemory()) { + return false; + } + mPrefSerializer->AddSharedPrefCmdLineArgs(*this, aExtraOpts); + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + mSandboxLevel = Preferences::GetInt("security.sandbox.rdd.level"); +#endif + + mLaunchPhase = LaunchPhase::Waiting; + mLaunchTime = TimeStamp::Now(); + + int32_t timeoutMs = StaticPrefs::media_rdd_process_startup_timeout_ms(); + + // If one of the following environment variables are set we can + // effectively ignore the timeout - as we can guarantee the RDD + // process will be terminated + if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS") || + PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { + timeoutMs = 0; + } + if (timeoutMs) { + // We queue a delayed task. If that task runs before the + // WhenProcessHandleReady promise gets resolved, we will abort the launch. + GetMainThreadSerialEventTarget()->DelayedDispatch( + NS_NewRunnableFunction( + "RDDProcessHost::Launchtimeout", + [this, liveToken = mLiveToken]() { + if (!*liveToken || mTimerChecked) { + // We have been deleted or the runnable has already started, we + // can abort. + return; + } + InitAfterConnect(false); + MOZ_ASSERT(mTimerChecked, + "InitAfterConnect must have acted on the promise"); + }), + timeoutMs); + } + + if (!GeckoChildProcessHost::AsyncLaunch(aExtraOpts)) { + mLaunchPhase = LaunchPhase::Complete; + mPrefSerializer = nullptr; + return false; + } + return true; +} + +RefPtr<GenericNonExclusivePromise> RDDProcessHost::LaunchPromise() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mLaunchPromise) { + return mLaunchPromise; + } + mLaunchPromise = MakeRefPtr<GenericNonExclusivePromise::Private>(__func__); + WhenProcessHandleReady()->Then( + GetCurrentSerialEventTarget(), __func__, + [this, liveToken = mLiveToken]( + const ipc::ProcessHandlePromise::ResolveOrRejectValue& aResult) { + if (!*liveToken) { + // The RDDProcessHost got deleted. Abort. The promise would have + // already been rejected. + return; + } + if (mTimerChecked) { + // We hit the timeout earlier, abort. + return; + } + mTimerChecked = true; + if (aResult.IsReject()) { + RejectPromise(); + } + // If aResult.IsResolve() then we have succeeded in launching the + // RDD process. The promise will be resolved once the channel has + // connected (or failed to) later. + }); + return mLaunchPromise; +} + +void RDDProcessHost::OnChannelConnected(int32_t peer_pid) { + MOZ_ASSERT(!NS_IsMainThread()); + + GeckoChildProcessHost::OnChannelConnected(peer_pid); + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "RDDProcessHost::OnChannelConnected", [this, liveToken = mLiveToken]() { + if (*liveToken && mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(true); + } + })); +} + +void RDDProcessHost::OnChannelError() { + MOZ_ASSERT(!NS_IsMainThread()); + + GeckoChildProcessHost::OnChannelError(); + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "RDDProcessHost::OnChannelError", [this, liveToken = mLiveToken]() { + if (*liveToken && mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(false); + } + })); +} + +static uint64_t sRDDProcessTokenCounter = 0; + +void RDDProcessHost::InitAfterConnect(bool aSucceeded) { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting); + MOZ_ASSERT(!mRDDChild); + + mLaunchPhase = LaunchPhase::Complete; + + if (!aSucceeded) { + RejectPromise(); + return; + } + mProcessToken = ++sRDDProcessTokenCounter; + mRDDChild = MakeUnique<RDDChild>(this); + DebugOnly<bool> rv = + mRDDChild->Open(TakeChannel(), base::GetProcId(GetChildProcessHandle())); + MOZ_ASSERT(rv); + + // Only clear mPrefSerializer in the success case to avoid a + // possible race in the case case of a timeout on Windows launch. + // See Bug 1555076 comment 7: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1555076#c7 + mPrefSerializer = nullptr; + + if (!mRDDChild->Init()) { + // Can't just kill here because it will create a timing race that + // will crash the tab. We don't really want to crash the tab just + // because RDD linux sandbox failed to initialize. In this case, + // we'll close the child channel which will cause the RDD process + // to shutdown nicely avoiding the tab crash (which manifests as + // Bug 1535335). + mRDDChild->Close(); + RejectPromise(); + } else { + ResolvePromise(); + } +} + +void RDDProcessHost::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mShutdownRequested); + + RejectPromise(); + + if (mRDDChild) { + // OnChannelClosed uses this to check if the shutdown was expected or + // unexpected. + mShutdownRequested = true; + + // The channel might already be closed if we got here unexpectedly. + if (!mChannelClosed) { + mRDDChild->Close(); + } + +#ifndef NS_FREE_PERMANENT_DATA + // No need to communicate shutdown, the RDD process doesn't need to + // communicate anything back. + KillHard("NormalShutdown"); +#endif + + // If we're shutting down unexpectedly, we're in the middle of handling an + // ActorDestroy for PRDDChild, which is still on the stack. We'll return + // back to OnChannelClosed. + // + // Otherwise, we'll wait for OnChannelClose to be called whenever PRDDChild + // acknowledges shutdown. + return; + } + + DestroyProcess(); +} + +void RDDProcessHost::OnChannelClosed() { + MOZ_ASSERT(NS_IsMainThread()); + + mChannelClosed = true; + RejectPromise(); + + if (!mShutdownRequested && mListener) { + // This is an unclean shutdown. Notify our listener that we're going away. + mListener->OnProcessUnexpectedShutdown(this); + } else { + DestroyProcess(); + } + + // Release the actor. + RDDChild::Destroy(std::move(mRDDChild)); +} + +void RDDProcessHost::KillHard(const char* aReason) { + MOZ_ASSERT(NS_IsMainThread()); + + ProcessHandle handle = GetChildProcessHandle(); + if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER, false)) { + NS_WARNING("failed to kill subprocess!"); + } + + SetAlreadyDead(); +} + +uint64_t RDDProcessHost::GetProcessToken() const { + MOZ_ASSERT(NS_IsMainThread()); + return mProcessToken; +} + +void RDDProcessHost::DestroyProcess() { + MOZ_ASSERT(NS_IsMainThread()); + RejectPromise(); + + // Any pending tasks will be cancelled from now on. + *mLiveToken = false; + + NS_DispatchToMainThread( + NS_NewRunnableFunction("DestroyProcessRunnable", [this] { Destroy(); })); +} + +void RDDProcessHost::ResolvePromise() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mLaunchPromiseSettled) { + mLaunchPromise->Resolve(true, __func__); + mLaunchPromiseSettled = true; + } + // We have already acted on the promise; the timeout runnable no longer needs + // to interrupt anything. + mTimerChecked = true; +} + +void RDDProcessHost::RejectPromise() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mLaunchPromiseSettled) { + mLaunchPromise->Reject(NS_ERROR_FAILURE, __func__); + mLaunchPromiseSettled = true; + } + // We have already acted on the promise; the timeout runnable no longer needs + // to interrupt anything. + mTimerChecked = true; +} + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +bool RDDProcessHost::FillMacSandboxInfo(MacSandboxInfo& aInfo) { + GeckoChildProcessHost::FillMacSandboxInfo(aInfo); + if (!aInfo.shouldLog && PR_GetEnv("MOZ_SANDBOX_RDD_LOGGING")) { + aInfo.shouldLog = true; + } + return true; +} + +/* static */ +MacSandboxType RDDProcessHost::GetMacSandboxType() { + return MacSandboxType_RDD; +} +#endif + +} // namespace mozilla diff --git a/dom/media/ipc/RDDProcessHost.h b/dom/media/ipc/RDDProcessHost.h new file mode 100644 index 0000000000..199c414024 --- /dev/null +++ b/dom/media/ipc/RDDProcessHost.h @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 _include_dom_media_ipc_RDDProcessHost_h_ +#define _include_dom_media_ipc_RDDProcessHost_h_ +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/media/MediaUtils.h" + +namespace mozilla { +namespace ipc { +class SharedPreferenceSerializer; +} +} // namespace mozilla +class nsITimer; + +namespace mozilla { + +class RDDChild; + +// RDDProcessHost is the "parent process" container for a subprocess handle and +// IPC connection. It owns the parent process IPDL actor, which in this case, +// is a RDDChild. +// +// RDDProcessHosts are allocated and managed by RDDProcessManager. For all +// intents and purposes it is a singleton, though more than one may be allocated +// at a time due to its shutdown being asynchronous. +class RDDProcessHost final : public mozilla::ipc::GeckoChildProcessHost { + friend class RDDChild; + + public: + class Listener { + public: + // The RDDProcessHost has unexpectedly shutdown or had its connection + // severed. This is not called if an error occurs after calling + // Shutdown(). + virtual void OnProcessUnexpectedShutdown(RDDProcessHost* aHost) {} + }; + + explicit RDDProcessHost(Listener* listener); + + // Launch the subprocess asynchronously. On failure, false is returned. + // Otherwise, true is returned. If succeeded, a follow-up call should be made + // to LaunchPromise() which will return a promise that will be resolved once + // the RDD process has launched and a channel has been established. + // + // @param aExtraOpts (StringVector) + // Extra options to pass to the subprocess. + bool Launch(StringVector aExtraOpts); + + // Return a promise that will be resolved once the process has completed its + // launch. The promise will be immediately resolved if the launch has already + // succeeded. + RefPtr<GenericNonExclusivePromise> LaunchPromise(); + + // Inform the process that it should clean up its resources and shut + // down. This initiates an asynchronous shutdown sequence. After this + // method returns, it is safe for the caller to forget its pointer to + // the RDDProcessHost. + // + // After this returns, the attached Listener is no longer used. + void Shutdown(); + + // Return the actor for the top-level actor of the process. If the process + // has not connected yet, this returns null. + RDDChild* GetActor() const { + MOZ_ASSERT(NS_IsMainThread()); + return mRDDChild.get(); + } + + // Return a unique id for this process, guaranteed not to be shared with any + // past or future instance of RDDProcessHost. + uint64_t GetProcessToken() const; + + bool IsConnected() const { + MOZ_ASSERT(NS_IsMainThread()); + return !!mRDDChild; + } + + // Return the time stamp for when we tried to launch the RDD process. + // This is currently used for Telemetry so that we can determine how + // long RDD processes take to spin up. Note this doesn't denote a + // successful launch, just when we attempted launch. + TimeStamp GetLaunchTime() const { return mLaunchTime; } + + // Called on the IO thread. + void OnChannelConnected(int32_t peer_pid) override; + void OnChannelError() override; + + void SetListener(Listener* aListener); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // Return the sandbox type to be used with this process type. + static MacSandboxType GetMacSandboxType(); +#endif + + private: + ~RDDProcessHost(); + + // Called on the main thread with true after a connection has been established + // or false if it failed (including if it failed before the timeout kicked in) + void InitAfterConnect(bool aSucceeded); + + // Called on the main thread when the mRDDChild actor is shutting down. + void OnChannelClosed(); + + // Kill the remote process, triggering IPC shutdown. + void KillHard(const char* aReason); + + void DestroyProcess(); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + static bool sLaunchWithMacSandbox; + + // Sandbox the RDD process at launch for all instances + bool IsMacSandboxLaunchEnabled() override { return sLaunchWithMacSandbox; } + + // Override so we can turn on RDD process-specific sandbox logging + bool FillMacSandboxInfo(MacSandboxInfo& aInfo) override; +#endif + + DISALLOW_COPY_AND_ASSIGN(RDDProcessHost); + + Listener* const mListener; + + // All members below are only ever accessed on the main thread. + enum class LaunchPhase { Unlaunched, Waiting, Complete }; + LaunchPhase mLaunchPhase = LaunchPhase::Unlaunched; + + UniquePtr<RDDChild> mRDDChild; + uint64_t mProcessToken = 0; + + UniquePtr<ipc::SharedPreferenceSerializer> mPrefSerializer; + + bool mShutdownRequested = false; + bool mChannelClosed = false; + + TimeStamp mLaunchTime; + void RejectPromise(); + void ResolvePromise(); + + // Set to true on construction and to false just prior deletion. + // The RDDProcessHost isn't refcounted; so we can capture this by value in + // lambdas along with a strong reference to mLiveToken and check if that value + // is true before accessing "this". + // While a reference to mLiveToken can be taken on any thread; its value can + // only be read on the main thread. + const RefPtr<media::Refcountable<bool>> mLiveToken; + RefPtr<GenericNonExclusivePromise::Private> mLaunchPromise; + bool mLaunchPromiseSettled = false; + // Will be set to true if we've exceeded the allowed startup time or if the + // RDD process as successfully started. This is used to determine if the + // timeout runnable needs to execute code or not. + bool mTimerChecked = false; +}; + +} // namespace mozilla + +#endif // _include_dom_media_ipc_RDDProcessHost_h_ diff --git a/dom/media/ipc/RDDProcessImpl.cpp b/dom/media/ipc/RDDProcessImpl.cpp new file mode 100644 index 0000000000..facf0b148a --- /dev/null +++ b/dom/media/ipc/RDDProcessImpl.cpp @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RDDProcessImpl.h" + +#include "mozilla/ipc/IOThreadChild.h" + +#if defined(OS_WIN) && defined(MOZ_SANDBOX) +# include "mozilla/sandboxTarget.h" +#endif + +#include "ProcessUtils.h" + +namespace mozilla { + +using namespace ipc; + +RDDProcessImpl::RDDProcessImpl(ProcessId aParentPid) + : ProcessChild(aParentPid) {} + +RDDProcessImpl::~RDDProcessImpl() = default; + +bool RDDProcessImpl::Init(int aArgc, char* aArgv[]) { +#if defined(MOZ_SANDBOX) && defined(OS_WIN) + // Preload AV dlls so we can enable Binary Signature Policy + // to restrict further dll loads. + LoadLibraryW(L"mozavcodec.dll"); + LoadLibraryW(L"mozavutil.dll"); + mozilla::SandboxTarget::Instance()->StartSandbox(); +#endif + char* parentBuildID = nullptr; + char* prefsHandle = nullptr; + char* prefMapHandle = nullptr; + char* prefsLen = nullptr; + char* prefMapSize = nullptr; + for (int i = 1; i < aArgc; i++) { + if (!aArgv[i]) { + continue; + } + if (strcmp(aArgv[i], "-parentBuildID") == 0) { + parentBuildID = aArgv[i + 1]; + +#ifdef XP_WIN + } else if (strcmp(aArgv[i], "-prefsHandle") == 0) { + if (++i == aArgc) { + return false; + } + prefsHandle = aArgv[i]; + } else if (strcmp(aArgv[i], "-prefMapHandle") == 0) { + if (++i == aArgc) { + return false; + } + prefMapHandle = aArgv[i]; +#endif + } else if (strcmp(aArgv[i], "-prefsLen") == 0) { + if (++i == aArgc) { + return false; + } + prefsLen = aArgv[i]; + } else if (strcmp(aArgv[i], "-prefMapSize") == 0) { + if (++i == aArgc) { + return false; + } + prefMapSize = aArgv[i]; + } + } + + SharedPreferenceDeserializer deserializer; + if (!deserializer.DeserializeFromSharedMemory(prefsHandle, prefMapHandle, + prefsLen, prefMapSize)) { + return false; + } + + return mRDD.Init(ParentPid(), parentBuildID, IOThreadChild::message_loop(), + IOThreadChild::TakeChannel()); +} + +void RDDProcessImpl::CleanUp() { NS_ShutdownXPCOM(nullptr); } + +} // namespace mozilla diff --git a/dom/media/ipc/RDDProcessImpl.h b/dom/media/ipc/RDDProcessImpl.h new file mode 100644 index 0000000000..7406b32293 --- /dev/null +++ b/dom/media/ipc/RDDProcessImpl.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 _include_dom_media_ipc_RDDProcessImpl_h__ +#define _include_dom_media_ipc_RDDProcessImpl_h__ +#include "mozilla/ipc/ProcessChild.h" + +#if defined(XP_WIN) +# include "mozilla/mscom/ProcessRuntime.h" +#endif + +#include "RDDParent.h" + +namespace mozilla { + +// This class owns the subprocess instance of a PRDD - which in this case, +// is a RDDParent. It is instantiated as a singleton in XRE_InitChildProcess. +class RDDProcessImpl final : public ipc::ProcessChild { + public: + explicit RDDProcessImpl(ProcessId aParentPid); + ~RDDProcessImpl(); + + bool Init(int aArgc, char* aArgv[]) override; + void CleanUp() override; + + private: + DISALLOW_COPY_AND_ASSIGN(RDDProcessImpl); + + RDDParent mRDD; + +#if defined(XP_WIN) + // This object initializes and configures COM. + mozilla::mscom::ProcessRuntime mCOMRuntime; +#endif +}; + +} // namespace mozilla + +#endif // _include_dom_media_ipc_RDDProcessImpl_h__ diff --git a/dom/media/ipc/RDDProcessManager.cpp b/dom/media/ipc/RDDProcessManager.cpp new file mode 100644 index 0000000000..b9aa7c06ad --- /dev/null +++ b/dom/media/ipc/RDDProcessManager.cpp @@ -0,0 +1,373 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RDDProcessManager.h" + +#include "RDDChild.h" +#include "RDDProcessHost.h" +#include "mozilla/MemoryReportingProcess.h" +#include "mozilla/Preferences.h" +#include "mozilla/RemoteDecoderManagerChild.h" +#include "mozilla/RemoteDecoderManagerParent.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/SyncRunnable.h" // for LaunchRDDProcess +#include "mozilla/dom/ContentParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/VideoBridgeParent.h" +#include "nsAppRunner.h" +#include "nsContentUtils.h" + +namespace mozilla { + +using namespace gfx; +using namespace layers; + +static StaticAutoPtr<RDDProcessManager> sRDDSingleton; + +RDDProcessManager* RDDProcessManager::Get() { return sRDDSingleton; } + +void RDDProcessManager::Initialize() { + MOZ_ASSERT(XRE_IsParentProcess()); + sRDDSingleton = new RDDProcessManager(); +} + +void RDDProcessManager::Shutdown() { sRDDSingleton = nullptr; } + +RDDProcessManager::RDDProcessManager() + : mObserver(new Observer(this)), mTaskFactory(this) { + MOZ_COUNT_CTOR(RDDProcessManager); + // Start listening for pref changes so we can + // forward them to the process once it is running. + nsContentUtils::RegisterShutdownObserver(mObserver); + Preferences::AddStrongObserver(mObserver, ""); +} + +RDDProcessManager::~RDDProcessManager() { + MOZ_COUNT_DTOR(RDDProcessManager); + MOZ_ASSERT(NS_IsMainThread()); + + // The RDD process should have already been shut down. + MOZ_ASSERT(!mProcess && !mRDDChild); +} + +NS_IMPL_ISUPPORTS(RDDProcessManager::Observer, nsIObserver); + +RDDProcessManager::Observer::Observer(RDDProcessManager* aManager) + : mManager(aManager) {} + +NS_IMETHODIMP +RDDProcessManager::Observer::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + mManager->OnXPCOMShutdown(); + } else if (!strcmp(aTopic, "nsPref:changed")) { + mManager->OnPreferenceChange(aData); + } + return NS_OK; +} + +void RDDProcessManager::OnXPCOMShutdown() { + nsContentUtils::UnregisterShutdownObserver(mObserver); + Preferences::RemoveObserver(mObserver, ""); + CleanShutdown(); +} + +void RDDProcessManager::OnPreferenceChange(const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + if (!mProcess) { + // Process hasn't been launched yet + return; + } + // A pref changed. If it is useful to do so, inform child processes. + if (!dom::ContentParent::ShouldSyncPreference(aData)) { + return; + } + + // We know prefs are ASCII here. + NS_LossyConvertUTF16toASCII strData(aData); + + mozilla::dom::Pref pref(strData, /* isLocked */ false, Nothing(), Nothing()); + Preferences::GetPreference(&pref); + if (!!mRDDChild) { + MOZ_ASSERT(mQueuedPrefs.IsEmpty()); + mRDDChild->SendPreferenceUpdate(pref); + } else if (IsRDDProcessLaunching()) { + mQueuedPrefs.AppendElement(pref); + } +} + +RefPtr<GenericNonExclusivePromise> RDDProcessManager::LaunchRDDProcess() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!Get()) { + // Shutdown? + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + + if (mNumProcessAttempts && !StaticPrefs::media_rdd_retryonfailure_enabled()) { + // We failed to start the RDD process earlier, abort now. + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + + if (mLaunchRDDPromise && mProcess) { + return mLaunchRDDPromise; + } + + std::vector<std::string> extraArgs; + nsCString parentBuildID(mozilla::PlatformBuildID()); + extraArgs.push_back("-parentBuildID"); + extraArgs.push_back(parentBuildID.get()); + + // The subprocess is launched asynchronously, so we + // wait for the promise to be resolved to acquire the IPDL actor. + mProcess = new RDDProcessHost(this); + if (!mProcess->Launch(extraArgs)) { + mNumProcessAttempts++; + DestroyProcess(); + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + + mLaunchRDDPromise = mProcess->LaunchPromise()->Then( + GetMainThreadSerialEventTarget(), __func__, + [this](bool) { + if (!Get()) { + // Shutdown? + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_NOT_AVAILABLE, __func__); + } + + mRDDChild = mProcess->GetActor(); + mProcessToken = mProcess->GetProcessToken(); + + // Flush any pref updates that happened during + // launch and weren't included in the blobs set + // up in LaunchRDDProcess. + for (const mozilla::dom::Pref& pref : mQueuedPrefs) { + Unused << NS_WARN_IF(!mRDDChild->SendPreferenceUpdate(pref)); + } + mQueuedPrefs.Clear(); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::RDDProcessStatus, "Running"_ns); + + if (!CreateVideoBridge()) { + mNumProcessAttempts++; + DestroyProcess(); + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_NOT_AVAILABLE, __func__); + } + return GenericNonExclusivePromise::CreateAndResolve(true, __func__); + }, + [this](nsresult aError) { + if (Get()) { + mNumProcessAttempts++; + DestroyProcess(); + } + return GenericNonExclusivePromise::CreateAndReject(aError, __func__); + }); + return mLaunchRDDPromise; +} + +auto RDDProcessManager::EnsureRDDProcessAndCreateBridge( + base::ProcessId aOtherProcess) -> RefPtr<EnsureRDDPromise> { + return InvokeAsync( + GetMainThreadSerialEventTarget(), __func__, + [aOtherProcess, this]() -> RefPtr<EnsureRDDPromise> { + return LaunchRDDProcess()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aOtherProcess, this]() { + if (!Get()) { + // Shutdown? + return EnsureRDDPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + ipc::Endpoint<PRemoteDecoderManagerChild> endpoint; + if (!CreateContentBridge(aOtherProcess, &endpoint)) { + return EnsureRDDPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + mNumProcessAttempts = 0; + return EnsureRDDPromise::CreateAndResolve(std::move(endpoint), + __func__); + }, + [](nsresult aError) { + return EnsureRDDPromise::CreateAndReject(aError, __func__); + }); + }); +} + +bool RDDProcessManager::IsRDDProcessLaunching() { + MOZ_ASSERT(NS_IsMainThread()); + return !!mProcess && !mRDDChild; +} + +void RDDProcessManager::OnProcessUnexpectedShutdown(RDDProcessHost* aHost) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mProcess && mProcess == aHost); + + mNumUnexpectedCrashes++; + + DestroyProcess(); +} + +void RDDProcessManager::NotifyRemoteActorDestroyed( + const uint64_t& aProcessToken) { + if (!NS_IsMainThread()) { + RefPtr<Runnable> task = mTaskFactory.NewRunnableMethod( + &RDDProcessManager::NotifyRemoteActorDestroyed, aProcessToken); + NS_DispatchToMainThread(task.forget()); + return; + } + + if (mProcessToken != aProcessToken) { + // This token is for an older process; we can safely ignore it. + return; + } + + // One of the bridged top-level actors for the RDD process has been + // prematurely terminated, and we're receiving a notification. This + // can happen if the ActorDestroy for a bridged protocol fires + // before the ActorDestroy for PRDDChild. + OnProcessUnexpectedShutdown(mProcess); +} + +void RDDProcessManager::CleanShutdown() { DestroyProcess(); } + +void RDDProcessManager::DestroyProcess() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mProcess) { + return; + } + + mProcess->Shutdown(); + mProcessToken = 0; + mProcess = nullptr; + mRDDChild = nullptr; + mQueuedPrefs.Clear(); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::RDDProcessStatus, "Destroyed"_ns); +} + +bool RDDProcessManager::CreateContentBridge( + base::ProcessId aOtherProcess, + ipc::Endpoint<PRemoteDecoderManagerChild>* aOutRemoteDecoderManager) { + MOZ_ASSERT(NS_IsMainThread()); + + ipc::Endpoint<PRemoteDecoderManagerParent> parentPipe; + ipc::Endpoint<PRemoteDecoderManagerChild> childPipe; + + nsresult rv = PRemoteDecoderManager::CreateEndpoints( + mRDDChild->OtherPid(), aOtherProcess, &parentPipe, &childPipe); + if (NS_FAILED(rv)) { + MOZ_LOG(sPDMLog, LogLevel::Debug, + ("Could not create content remote decoder: %d", int(rv))); + return false; + } + + mRDDChild->SendNewContentRemoteDecoderManager(std::move(parentPipe)); + + *aOutRemoteDecoderManager = std::move(childPipe); + return true; +} + +bool RDDProcessManager::CreateVideoBridge() { + MOZ_ASSERT(NS_IsMainThread()); + ipc::Endpoint<PVideoBridgeParent> parentPipe; + ipc::Endpoint<PVideoBridgeChild> childPipe; + + GPUProcessManager* gpuManager = GPUProcessManager::Get(); + base::ProcessId gpuProcessPid = gpuManager ? gpuManager->GPUProcessPid() : -1; + + // Build content device data first; this ensure that the GPU process is fully + // ready. + ContentDeviceData contentDeviceData; + gfxPlatform::GetPlatform()->BuildContentDeviceData(&contentDeviceData); + + // The child end is the producer of video frames; the parent end is the + // consumer. + base::ProcessId childPid = RDDProcessPid(); + base::ProcessId parentPid = + gpuProcessPid != -1 ? gpuProcessPid : base::GetCurrentProcId(); + + nsresult rv = PVideoBridge::CreateEndpoints(parentPid, childPid, &parentPipe, + &childPipe); + if (NS_FAILED(rv)) { + MOZ_LOG(sPDMLog, LogLevel::Debug, + ("Could not create video bridge: %d", int(rv))); + return false; + } + + mRDDChild->SendInitVideoBridge(std::move(childPipe), + mNumUnexpectedCrashes == 0, contentDeviceData); + if (gpuProcessPid != -1) { + gpuManager->InitVideoBridge(std::move(parentPipe)); + } else { + VideoBridgeParent::Open(std::move(parentPipe), + VideoBridgeSource::RddProcess); + } + + return true; +} + +base::ProcessId RDDProcessManager::RDDProcessPid() { + MOZ_ASSERT(NS_IsMainThread()); + base::ProcessId rddPid = mRDDChild ? mRDDChild->OtherPid() : -1; + return rddPid; +} + +class RDDMemoryReporter : public MemoryReportingProcess { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RDDMemoryReporter, override) + + bool IsAlive() const override { return !!GetChild(); } + + bool SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile) override { + RDDChild* child = GetChild(); + if (!child) { + return false; + } + + return child->SendRequestMemoryReport(aGeneration, aAnonymize, + aMinimizeMemoryUsage, aDMDFile); + } + + int32_t Pid() const override { + if (RDDChild* child = GetChild()) { + return (int32_t)child->OtherPid(); + } + return 0; + } + + private: + RDDChild* GetChild() const { + if (RDDProcessManager* rddpm = RDDProcessManager::Get()) { + if (RDDChild* child = rddpm->GetRDDChild()) { + return child; + } + } + return nullptr; + } + + protected: + ~RDDMemoryReporter() = default; +}; + +RefPtr<MemoryReportingProcess> RDDProcessManager::GetProcessMemoryReporter() { + if (!mProcess || !mProcess->IsConnected()) { + return nullptr; + } + return new RDDMemoryReporter(); +} + +} // namespace mozilla diff --git a/dom/media/ipc/RDDProcessManager.h b/dom/media/ipc/RDDProcessManager.h new file mode 100644 index 0000000000..0204eba373 --- /dev/null +++ b/dom/media/ipc/RDDProcessManager.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 _include_dom_media_ipc_RDDProcessManager_h_ +#define _include_dom_media_ipc_RDDProcessManager_h_ +#include "mozilla/MozPromise.h" +#include "mozilla/PRemoteDecoderManagerChild.h" +#include "mozilla/RDDProcessHost.h" +#include "mozilla/ipc/TaskFactory.h" +#include "nsIObserver.h" + +namespace mozilla { + +class MemoryReportingProcess; +class RDDChild; + +// The RDDProcessManager is a singleton responsible for creating RDD-bound +// objects that may live in another process. Currently, it provides access +// to the RDD process via ContentParent. +class RDDProcessManager final : public RDDProcessHost::Listener { + friend class RDDChild; + + public: + static void Initialize(); + static void Shutdown(); + static RDDProcessManager* Get(); + + ~RDDProcessManager(); + + using EnsureRDDPromise = + MozPromise<ipc::Endpoint<PRemoteDecoderManagerChild>, nsresult, true>; + // Launch a new RDD process asynchronously + RefPtr<GenericNonExclusivePromise> LaunchRDDProcess(); + // If not using a RDD process, launch a new RDD process asynchronously and + // create a RemoteDecoderManager bridge + RefPtr<EnsureRDDPromise> EnsureRDDProcessAndCreateBridge( + base::ProcessId aOtherProcess); + + void OnProcessUnexpectedShutdown(RDDProcessHost* aHost) override; + + // Notify the RDDProcessManager that a top-level PRDD protocol has been + // terminated. This may be called from any thread. + void NotifyRemoteActorDestroyed(const uint64_t& aProcessToken); + + // Returns -1 if there is no RDD process, or the platform pid for it. + base::ProcessId RDDProcessPid(); + + // If a RDD process is present, create a MemoryReportingProcess object. + // Otherwise, return null. + RefPtr<MemoryReportingProcess> GetProcessMemoryReporter(); + + // Returns access to the PRDD protocol if a RDD process is present. + RDDChild* GetRDDChild() { return mRDDChild; } + + // Returns whether or not a RDD process was ever launched. + bool AttemptedRDDProcess() const { return mNumProcessAttempts > 0; } + + // Returns the RDD Process + RDDProcessHost* Process() { return mProcess; } + + private: + bool IsRDDProcessLaunching(); + bool CreateVideoBridge(); + + // Called from our xpcom-shutdown observer. + void OnXPCOMShutdown(); + void OnPreferenceChange(const char16_t* aData); + + RDDProcessManager(); + + // Shutdown the RDD process. + void CleanShutdown(); + void DestroyProcess(); + + DISALLOW_COPY_AND_ASSIGN(RDDProcessManager); + + class Observer final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + explicit Observer(RDDProcessManager* aManager); + + protected: + ~Observer() = default; + + RDDProcessManager* mManager; + }; + friend class Observer; + + bool CreateContentBridge( + base::ProcessId aOtherProcess, + ipc::Endpoint<PRemoteDecoderManagerChild>* aOutRemoteDecoderManager); + + const RefPtr<Observer> mObserver; + ipc::TaskFactory<RDDProcessManager> mTaskFactory; + uint32_t mNumProcessAttempts = 0; + uint32_t mNumUnexpectedCrashes = 0; + + // Fields that are associated with the current RDD process. + RDDProcessHost* mProcess = nullptr; + uint64_t mProcessToken = 0; + RDDChild* mRDDChild = nullptr; + // Collects any pref changes that occur during process launch (after + // the initial map is passed in command-line arguments) to be sent + // when the process can receive IPC messages. + nsTArray<dom::Pref> mQueuedPrefs; + // Promise will be resolved when the RDD process has been fully started and + // VideoBridge configured. Only accessed on the main thread. + RefPtr<GenericNonExclusivePromise> mLaunchRDDPromise; +}; + +} // namespace mozilla + +#endif // _include_dom_media_ipc_RDDProcessManager_h_ diff --git a/dom/media/ipc/RemoteAudioDecoder.cpp b/dom/media/ipc/RemoteAudioDecoder.cpp new file mode 100644 index 0000000000..48250dc0c4 --- /dev/null +++ b/dom/media/ipc/RemoteAudioDecoder.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteAudioDecoder.h" + +#include "MediaDataDecoderProxy.h" +#include "PDMFactory.h" +#include "RemoteDecoderManagerChild.h" +#include "RemoteDecoderManagerParent.h" +#include "mozilla/PodOperations.h" + +namespace mozilla { + +RemoteAudioDecoderChild::RemoteAudioDecoderChild() + : RemoteDecoderChild(RemoteDecodeIn::RddProcess) {} + +MediaResult RemoteAudioDecoderChild::ProcessOutput( + DecodedOutputIPDL&& aDecodedData) { + AssertOnManagerThread(); + + MOZ_ASSERT(aDecodedData.type() == DecodedOutputIPDL::TArrayOfRemoteAudioData); + RefPtr<ArrayOfRemoteAudioData> arrayData = + aDecodedData.get_ArrayOfRemoteAudioData(); + + for (size_t i = 0; i < arrayData->Count(); i++) { + RefPtr<AudioData> data = arrayData->ElementAt(i); + if (!data) { + // OOM + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); + } + mDecodedData.AppendElement(data); + } + return NS_OK; +} + +MediaResult RemoteAudioDecoderChild::InitIPDL( + const AudioInfo& aAudioInfo, + const CreateDecoderParams::OptionSet& aOptions) { + RefPtr<RemoteDecoderManagerChild> manager = + RemoteDecoderManagerChild::GetSingleton(mLocation); + + // The manager isn't available because RemoteDecoderManagerChild has been + // initialized with null end points and we don't want to decode video on RDD + // process anymore. Return false here so that we can fallback to other PDMs. + if (!manager) { + return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("RemoteDecoderManager is not available.")); + } + + if (!manager->CanSend()) { + return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("RemoteDecoderManager unable to send.")); + } + + mIPDLSelfRef = this; + Unused << manager->SendPRemoteDecoderConstructor(this, aAudioInfo, aOptions, + Nothing()); + return NS_OK; +} + +RemoteAudioDecoderParent::RemoteAudioDecoderParent( + RemoteDecoderManagerParent* aParent, const AudioInfo& aAudioInfo, + const CreateDecoderParams::OptionSet& aOptions, + nsISerialEventTarget* aManagerThread, TaskQueue* aDecodeTaskQueue) + : RemoteDecoderParent(aParent, aOptions, aManagerThread, aDecodeTaskQueue), + mAudioInfo(aAudioInfo) {} + +IPCResult RemoteAudioDecoderParent::RecvConstruct( + ConstructResolver&& aResolver) { + auto params = CreateDecoderParams{mAudioInfo, mOptions, + CreateDecoderParams::NoWrapper(true)}; + + mParent->EnsurePDMFactory().CreateDecoder(params)->Then( + GetCurrentSerialEventTarget(), __func__, + [resolver = std::move(aResolver), self = RefPtr{this}]( + PlatformDecoderModule::CreateDecoderPromise::ResolveOrRejectValue&& + aValue) { + if (aValue.IsReject()) { + resolver(aValue.RejectValue()); + return; + } + MOZ_ASSERT(aValue.ResolveValue()); + self->mDecoder = + new MediaDataDecoderProxy(aValue.ResolveValue().forget(), + do_AddRef(self->mDecodeTaskQueue.get())); + resolver(NS_OK); + }); + + return IPC_OK(); +} + +MediaResult RemoteAudioDecoderParent::ProcessDecodedData( + MediaDataDecoder::DecodedData&& aData, DecodedOutputIPDL& aDecodedData) { + MOZ_ASSERT(OnManagerThread()); + + // Converted array to array of RefPtr<AudioData> + nsTArray<RefPtr<AudioData>> data(aData.Length()); + for (auto&& element : aData) { + MOZ_ASSERT(element->mType == MediaData::Type::AUDIO_DATA, + "Can only decode audio using RemoteAudioDecoderParent!"); + AudioData* audio = static_cast<AudioData*>(element.get()); + data.AppendElement(audio); + } + auto array = MakeRefPtr<ArrayOfRemoteAudioData>(); + if (!array->Fill(std::move(data), + [&](size_t aSize) { return AllocateBuffer(aSize); })) { + return MediaResult( + NS_ERROR_OUT_OF_MEMORY, + "Failed in RemoteAudioDecoderParent::ProcessDecodedData"); + } + aDecodedData = std::move(array); + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteAudioDecoder.h b/dom/media/ipc/RemoteAudioDecoder.h new file mode 100644 index 0000000000..c09834eac0 --- /dev/null +++ b/dom/media/ipc/RemoteAudioDecoder.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 include_dom_media_ipc_RemoteAudioDecoderChild_h +#define include_dom_media_ipc_RemoteAudioDecoderChild_h +#include "RemoteDecoderChild.h" +#include "RemoteDecoderParent.h" + +namespace mozilla { + +using mozilla::ipc::IPCResult; + +class RemoteAudioDecoderChild final : public RemoteDecoderChild { + public: + explicit RemoteAudioDecoderChild(); + + MOZ_IS_CLASS_INIT + MediaResult InitIPDL(const AudioInfo& aAudioInfo, + const CreateDecoderParams::OptionSet& aOptions); + + MediaResult ProcessOutput(DecodedOutputIPDL&& aDecodedData) override; +}; + +class RemoteAudioDecoderParent final : public RemoteDecoderParent { + public: + RemoteAudioDecoderParent(RemoteDecoderManagerParent* aParent, + const AudioInfo& aAudioInfo, + const CreateDecoderParams::OptionSet& aOptions, + nsISerialEventTarget* aManagerThread, + TaskQueue* aDecodeTaskQueue); + + protected: + IPCResult RecvConstruct(ConstructResolver&& aResolver) override; + MediaResult ProcessDecodedData(MediaDataDecoder::DecodedData&& aData, + DecodedOutputIPDL& aDecodedData) override; + + private: + // Can only be accessed from the manager thread + // Note: we can't keep a reference to the original AudioInfo here + // because unlike in typical MediaDataDecoder situations, we're being + // passed a deserialized AudioInfo from RecvPRemoteDecoderConstructor + // which is destroyed when RecvPRemoteDecoderConstructor returns. + const AudioInfo mAudioInfo; + const CreateDecoderParams::OptionSet mOptions; +}; + +} // namespace mozilla + +#endif // include_dom_media_ipc_RemoteAudioDecoderChild_h diff --git a/dom/media/ipc/RemoteDecoderChild.cpp b/dom/media/ipc/RemoteDecoderChild.cpp new file mode 100644 index 0000000000..1cc957e982 --- /dev/null +++ b/dom/media/ipc/RemoteDecoderChild.cpp @@ -0,0 +1,275 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteDecoderChild.h" + +#include "RemoteDecoderManagerChild.h" + +namespace mozilla { + +RemoteDecoderChild::RemoteDecoderChild(RemoteDecodeIn aLocation) + : ShmemRecycleAllocator(this), + mLocation(aLocation), + mThread(GetCurrentSerialEventTarget()) { + MOZ_DIAGNOSTIC_ASSERT( + RemoteDecoderManagerChild::GetManagerThread() && + RemoteDecoderManagerChild::GetManagerThread()->IsOnCurrentThread(), + "Must be created on the manager thread"); +} + +RemoteDecoderChild::~RemoteDecoderChild() = default; + +void RemoteDecoderChild::HandleRejectionError( + const ipc::ResponseRejectReason& aReason, + std::function<void(const MediaResult&)>&& aCallback) { + // If the channel goes down and CanSend() returns false, the IPDL promise will + // be rejected with SendError rather than ActorDestroyed. Both means the same + // thing and we can consider that the parent has crashed. The child can no + // longer be used. + + // The GPU/RDD process crashed. + if (mLocation == RemoteDecodeIn::GpuProcess) { + // The GPU process will get automatically restarted by the parent process. + // Once it has been restarted the ContentChild will receive the message and + // will call GetManager()->InitForGPUProcess. + // We defer reporting an error until we've recreated the RemoteDecoder + // manager so that it'll be safe for MediaFormatReader to recreate decoders + RefPtr<RemoteDecoderChild> self = this; + GetManager()->RunWhenGPUProcessRecreated(NS_NewRunnableFunction( + "RemoteDecoderChild::HandleRejectionError", + [self, callback = std::move(aCallback)]() { + MediaResult error(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER, __func__); + callback(error); + })); + return; + } + // The RDD process is restarted on demand and asynchronously, we can + // immediately inform the caller that a new decoder is needed. The RDD will + // then be restarted during the new decoder creation. + aCallback(MediaResult(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER, __func__)); +} + +// ActorDestroy is called if the channel goes down while waiting for a response. +void RemoteDecoderChild::ActorDestroy(ActorDestroyReason aWhy) { + mDecodedData.Clear(); + CleanupShmemRecycleAllocator(); + RecordShutdownTelemetry(aWhy == AbnormalShutdown); +} + +void RemoteDecoderChild::DestroyIPDL() { + AssertOnManagerThread(); + MOZ_DIAGNOSTIC_ASSERT(mInitPromise.IsEmpty() && mDecodePromise.IsEmpty() && + mDrainPromise.IsEmpty() && + mFlushPromise.IsEmpty() && + mShutdownPromise.IsEmpty(), + "All promises should have been rejected"); + if (CanSend()) { + PRemoteDecoderChild::Send__delete__(this); + } +} + +void RemoteDecoderChild::IPDLActorDestroyed() { mIPDLSelfRef = nullptr; } + +// MediaDataDecoder methods + +RefPtr<MediaDataDecoder::InitPromise> RemoteDecoderChild::Init() { + AssertOnManagerThread(); + + RefPtr<RemoteDecoderChild> self = this; + SendInit() + ->Then( + mThread, __func__, + [self, this](InitResultIPDL&& aResponse) { + mInitPromiseRequest.Complete(); + if (aResponse.type() == InitResultIPDL::TMediaResult) { + mInitPromise.Reject(aResponse.get_MediaResult(), __func__); + return; + } + const auto& initResponse = aResponse.get_InitCompletionIPDL(); + mDescription = + initResponse.decoderDescription() + + (GetManager()->Location() == RemoteDecodeIn::RddProcess + ? " (RDD remote)"_ns + : " (GPU remote)"_ns); + mIsHardwareAccelerated = initResponse.hardware(); + mHardwareAcceleratedReason = initResponse.hardwareReason(); + mConversion = initResponse.conversion(); + // Either the promise has not yet been resolved or the handler has + // been disconnected and we can't get here. + mInitPromise.Resolve(initResponse.type(), __func__); + }, + [self](const mozilla::ipc::ResponseRejectReason& aReason) { + self->mInitPromiseRequest.Complete(); + self->HandleRejectionError( + aReason, [self](const MediaResult& aError) { + self->mInitPromise.RejectIfExists(aError, __func__); + }); + }) + ->Track(mInitPromiseRequest); + + return mInitPromise.Ensure(__func__); +} + +RefPtr<MediaDataDecoder::DecodePromise> RemoteDecoderChild::Decode( + const nsTArray<RefPtr<MediaRawData>>& aSamples) { + AssertOnManagerThread(); + + auto samples = MakeRefPtr<ArrayOfRemoteMediaRawData>(); + if (!samples->Fill(aSamples, + [&](size_t aSize) { return AllocateBuffer(aSize); })) { + return MediaDataDecoder::DecodePromise::CreateAndReject( + NS_ERROR_OUT_OF_MEMORY, __func__); + } + SendDecode(samples)->Then( + mThread, __func__, + [self = RefPtr{this}, this]( + PRemoteDecoderChild::DecodePromise::ResolveOrRejectValue&& aValue) { + // We no longer need the samples as the data has been + // processed by the parent. + // If the parent died, the error being fatal will cause the + // decoder to be torn down and all shmem in the pool will be + // deallocated. + ReleaseAllBuffers(); + + if (aValue.IsReject()) { + HandleRejectionError( + aValue.RejectValue(), [self](const MediaResult& aError) { + self->mDecodePromise.RejectIfExists(aError, __func__); + }); + return; + } + MOZ_DIAGNOSTIC_ASSERT(CanSend(), + "The parent unexpectedly died, promise should " + "have been rejected first"); + if (mDecodePromise.IsEmpty()) { + // We got flushed. + return; + } + auto response = std::move(aValue.ResolveValue()); + if (response.type() == DecodeResultIPDL::TMediaResult && + NS_FAILED(response.get_MediaResult())) { + mDecodePromise.Reject(response.get_MediaResult(), __func__); + return; + } + if (response.type() == DecodeResultIPDL::TDecodedOutputIPDL) { + ProcessOutput(std::move(response.get_DecodedOutputIPDL())); + } + mDecodePromise.Resolve(std::move(mDecodedData), __func__); + mDecodedData = MediaDataDecoder::DecodedData(); + }); + + return mDecodePromise.Ensure(__func__); +} + +RefPtr<MediaDataDecoder::FlushPromise> RemoteDecoderChild::Flush() { + AssertOnManagerThread(); + mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + + RefPtr<RemoteDecoderChild> self = this; + SendFlush()->Then( + mThread, __func__, + [self](const MediaResult& aResult) { + if (NS_SUCCEEDED(aResult)) { + self->mFlushPromise.ResolveIfExists(true, __func__); + } else { + self->mFlushPromise.RejectIfExists(aResult, __func__); + } + }, + [self](const mozilla::ipc::ResponseRejectReason& aReason) { + self->HandleRejectionError(aReason, [self](const MediaResult& aError) { + self->mFlushPromise.RejectIfExists(aError, __func__); + }); + }); + return mFlushPromise.Ensure(__func__); +} + +RefPtr<MediaDataDecoder::DecodePromise> RemoteDecoderChild::Drain() { + AssertOnManagerThread(); + + RefPtr<RemoteDecoderChild> self = this; + SendDrain()->Then( + mThread, __func__, + [self, this](DecodeResultIPDL&& aResponse) { + if (mDrainPromise.IsEmpty()) { + // We got flushed. + return; + } + if (aResponse.type() == DecodeResultIPDL::TMediaResult && + NS_FAILED(aResponse.get_MediaResult())) { + mDrainPromise.Reject(aResponse.get_MediaResult(), __func__); + return; + } + MOZ_DIAGNOSTIC_ASSERT(CanSend(), + "The parent unexpectedly died, promise should " + "have been rejected first"); + if (aResponse.type() == DecodeResultIPDL::TDecodedOutputIPDL) { + ProcessOutput(std::move(aResponse.get_DecodedOutputIPDL())); + } + mDrainPromise.Resolve(std::move(mDecodedData), __func__); + mDecodedData = MediaDataDecoder::DecodedData(); + }, + [self](const mozilla::ipc::ResponseRejectReason& aReason) { + self->HandleRejectionError(aReason, [self](const MediaResult& aError) { + self->mDrainPromise.RejectIfExists(aError, __func__); + }); + }); + return mDrainPromise.Ensure(__func__); +} + +RefPtr<mozilla::ShutdownPromise> RemoteDecoderChild::Shutdown() { + AssertOnManagerThread(); + // Shutdown() can be called while an InitPromise is pending. + mInitPromiseRequest.DisconnectIfExists(); + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + mFlushPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + + RefPtr<RemoteDecoderChild> self = this; + SendShutdown()->Then( + mThread, __func__, + [self]( + PRemoteDecoderChild::ShutdownPromise::ResolveOrRejectValue&& aValue) { + self->mShutdownPromise.Resolve(aValue.IsResolve(), __func__); + }); + return mShutdownPromise.Ensure(__func__); +} + +bool RemoteDecoderChild::IsHardwareAccelerated( + nsACString& aFailureReason) const { + AssertOnManagerThread(); + aFailureReason = mHardwareAcceleratedReason; + return mIsHardwareAccelerated; +} + +nsCString RemoteDecoderChild::GetDescriptionName() const { + AssertOnManagerThread(); + return mDescription; +} + +void RemoteDecoderChild::SetSeekThreshold(const media::TimeUnit& aTime) { + AssertOnManagerThread(); + Unused << SendSetSeekThreshold(aTime); +} + +MediaDataDecoder::ConversionRequired RemoteDecoderChild::NeedsConversion() + const { + AssertOnManagerThread(); + return mConversion; +} + +void RemoteDecoderChild::AssertOnManagerThread() const { + MOZ_ASSERT(mThread->IsOnCurrentThread()); +} + +RemoteDecoderManagerChild* RemoteDecoderChild::GetManager() { + if (!CanSend()) { + return nullptr; + } + return static_cast<RemoteDecoderManagerChild*>(Manager()); +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteDecoderChild.h b/dom/media/ipc/RemoteDecoderChild.h new file mode 100644 index 0000000000..7d9602497a --- /dev/null +++ b/dom/media/ipc/RemoteDecoderChild.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 include_dom_media_ipc_RemoteDecoderChild_h +#define include_dom_media_ipc_RemoteDecoderChild_h + +#include <functional> + +#include "mozilla/PRemoteDecoderChild.h" +#include "mozilla/ShmemRecycleAllocator.h" + +namespace mozilla { + +class RemoteDecoderManagerChild; +using mozilla::MediaDataDecoder; +using mozilla::ipc::IPCResult; + +class RemoteDecoderChild : public ShmemRecycleAllocator<RemoteDecoderChild>, + public PRemoteDecoderChild { + friend class PRemoteDecoderChild; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderChild); + + explicit RemoteDecoderChild(RemoteDecodeIn aLocation); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + // This interface closely mirrors the MediaDataDecoder plus a bit + // (DestroyIPDL) to allow proxying to a remote decoder in RemoteDecoderModule. + RefPtr<MediaDataDecoder::InitPromise> Init(); + RefPtr<MediaDataDecoder::DecodePromise> Decode( + const nsTArray<RefPtr<MediaRawData>>& aSamples); + RefPtr<MediaDataDecoder::DecodePromise> Drain(); + RefPtr<MediaDataDecoder::FlushPromise> Flush(); + RefPtr<mozilla::ShutdownPromise> Shutdown(); + bool IsHardwareAccelerated(nsACString& aFailureReason) const; + nsCString GetDescriptionName() const; + void SetSeekThreshold(const media::TimeUnit& aTime); + MediaDataDecoder::ConversionRequired NeedsConversion() const; + void DestroyIPDL(); + + // Called from IPDL when our actor has been destroyed + void IPDLActorDestroyed(); + + RemoteDecoderManagerChild* GetManager(); + + protected: + virtual ~RemoteDecoderChild(); + void AssertOnManagerThread() const; + + virtual MediaResult ProcessOutput(DecodedOutputIPDL&& aDecodedData) = 0; + virtual void RecordShutdownTelemetry(bool aForAbnormalShutdown) {} + + RefPtr<RemoteDecoderChild> mIPDLSelfRef; + MediaDataDecoder::DecodedData mDecodedData; + const RemoteDecodeIn mLocation; + + private: + const nsCOMPtr<nsISerialEventTarget> mThread; + + MozPromiseHolder<MediaDataDecoder::InitPromise> mInitPromise; + MozPromiseRequestHolder<PRemoteDecoderChild::InitPromise> mInitPromiseRequest; + MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise; + MozPromiseHolder<MediaDataDecoder::DecodePromise> mDrainPromise; + MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushPromise; + MozPromiseHolder<mozilla::ShutdownPromise> mShutdownPromise; + + void HandleRejectionError( + const ipc::ResponseRejectReason& aReason, + std::function<void(const MediaResult&)>&& aCallback); + + nsCString mHardwareAcceleratedReason; + nsCString mDescription; + bool mIsHardwareAccelerated = false; + MediaDataDecoder::ConversionRequired mConversion = + MediaDataDecoder::ConversionRequired::kNeedNone; +}; + +} // namespace mozilla + +#endif // include_dom_media_ipc_RemoteDecoderChild_h diff --git a/dom/media/ipc/RemoteDecoderManagerChild.cpp b/dom/media/ipc/RemoteDecoderManagerChild.cpp new file mode 100644 index 0000000000..50e80e7a33 --- /dev/null +++ b/dom/media/ipc/RemoteDecoderManagerChild.cpp @@ -0,0 +1,619 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteDecoderManagerChild.h" + +#include "PDMFactory.h" +#include "RemoteAudioDecoder.h" +#include "RemoteMediaDataDecoder.h" +#include "RemoteVideoDecoder.h" +#include "VideoUtils.h" +#include "mozilla/DataMutex.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/dom/ContentChild.h" // for launching RDD w/ ContentChild +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "nsContentUtils.h" +#include "nsIObserver.h" + +namespace mozilla { + +using namespace layers; +using namespace gfx; + +// Used so that we only ever attempt to check if the RDD process should be +// launched serially. Protects sLaunchPromise +StaticMutex sLaunchMutex; +static StaticRefPtr<GenericNonExclusivePromise> sLaunchRDDPromise; + +// Only modified on the main-thread, read on any thread. While it could be read +// on the main thread directly, for clarity we force access via the DataMutex +// wrapper. +static StaticDataMutex<StaticRefPtr<nsIThread>> + sRemoteDecoderManagerChildThread("sRemoteDecoderManagerChildThread"); + +// Only accessed from sRemoteDecoderManagerChildThread +static StaticRefPtr<RemoteDecoderManagerChild> + sRemoteDecoderManagerChildForRDDProcess; + +static StaticRefPtr<RemoteDecoderManagerChild> + sRemoteDecoderManagerChildForGPUProcess; +static UniquePtr<nsTArray<RefPtr<Runnable>>> sRecreateTasks; + +static StaticDataMutex<Maybe<PDMFactory::MediaCodecsSupported>> sGPUSupported( + "RDMC::sGPUSupported"); +static StaticDataMutex<Maybe<PDMFactory::MediaCodecsSupported>> sRDDSupported( + "RDMC::sRDDSupported"); + +class ShutdownObserver final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + protected: + ~ShutdownObserver() = default; +}; +NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver); + +NS_IMETHODIMP +ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)); + RemoteDecoderManagerChild::Shutdown(); + return NS_OK; +} + +StaticRefPtr<ShutdownObserver> sObserver; + +/* static */ +void RemoteDecoderManagerChild::Init() { + MOZ_ASSERT(NS_IsMainThread()); + + auto remoteDecoderManagerThread = sRemoteDecoderManagerChildThread.Lock(); + if (!*remoteDecoderManagerThread) { + // We can't use a MediaThreadType::SUPERVISOR as the RemoteDecoderModule + // runs on it and dispatch synchronous tasks to the manager thread, should + // more than 4 concurrent videos being instantiated at the same time, we + // could end up in a deadlock. + RefPtr<nsIThread> childThread; + nsresult rv = NS_NewNamedThread( + "RemVidChild", getter_AddRefs(childThread), + NS_NewRunnableFunction( + "RemoteDecoderManagerChild::InitPBackground", []() { + ipc::PBackgroundChild* bgActor = + ipc::BackgroundChild::GetOrCreateForCurrentThread(); + NS_ASSERTION(bgActor, "Failed to start Background channel"); + Unused << bgActor; + })); + + NS_ENSURE_SUCCESS_VOID(rv); + *remoteDecoderManagerThread = childThread; + sRecreateTasks = MakeUnique<nsTArray<RefPtr<Runnable>>>(); + sObserver = new ShutdownObserver(); + nsContentUtils::RegisterShutdownObserver(sObserver); + } +} + +/* static */ +void RemoteDecoderManagerChild::InitForGPUProcess( + Endpoint<PRemoteDecoderManagerChild>&& aVideoManager) { + MOZ_ASSERT(NS_IsMainThread()); + + Init(); + + auto remoteDecoderManagerThread = sRemoteDecoderManagerChildThread.Lock(); + MOZ_ALWAYS_SUCCEEDS((*remoteDecoderManagerThread) + ->Dispatch(NewRunnableFunction( + "InitForContentRunnable", &OpenForGPUProcess, + std::move(aVideoManager)))); +} + +/* static */ +void RemoteDecoderManagerChild::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sObserver) { + nsContentUtils::UnregisterShutdownObserver(sObserver); + sObserver = nullptr; + } + + nsCOMPtr<nsIThread> childThread; + { + auto remoteDecoderManagerThread = sRemoteDecoderManagerChildThread.Lock(); + childThread = remoteDecoderManagerThread->forget(); + } + if (childThread) { + MOZ_ALWAYS_SUCCEEDS(childThread->Dispatch(NS_NewRunnableFunction( + "dom::RemoteDecoderManagerChild::Shutdown", []() { + if (sRemoteDecoderManagerChildForRDDProcess && + sRemoteDecoderManagerChildForRDDProcess->CanSend()) { + sRemoteDecoderManagerChildForRDDProcess->Close(); + } + sRemoteDecoderManagerChildForRDDProcess = nullptr; + if (sRemoteDecoderManagerChildForGPUProcess && + sRemoteDecoderManagerChildForGPUProcess->CanSend()) { + sRemoteDecoderManagerChildForGPUProcess->Close(); + } + sRemoteDecoderManagerChildForGPUProcess = nullptr; + ipc::BackgroundChild::CloseForCurrentThread(); + }))); + childThread->Shutdown(); + sRecreateTasks = nullptr; + } +} + +void RemoteDecoderManagerChild::RunWhenGPUProcessRecreated( + already_AddRefed<Runnable> aTask) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + // We've been shutdown, bail. + return; + } + MOZ_ASSERT(managerThread->IsOnCurrentThread()); + + // If we've already been recreated, then run the task immediately. + auto* manager = GetSingleton(RemoteDecodeIn::GpuProcess); + if (manager && manager != this && manager->CanSend()) { + RefPtr<Runnable> task = aTask; + task->Run(); + } else { + sRecreateTasks->AppendElement(aTask); + } +} + +/* static */ +RemoteDecoderManagerChild* RemoteDecoderManagerChild::GetSingleton( + RemoteDecodeIn aLocation) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + // We've been shutdown, bail. + return nullptr; + } + MOZ_ASSERT(managerThread->IsOnCurrentThread()); + switch (aLocation) { + case RemoteDecodeIn::GpuProcess: + return sRemoteDecoderManagerChildForGPUProcess; + case RemoteDecodeIn::RddProcess: + return sRemoteDecoderManagerChildForRDDProcess; + default: + MOZ_CRASH("Unexpected RemoteDecode variant"); + } +} + +/* static */ +nsISerialEventTarget* RemoteDecoderManagerChild::GetManagerThread() { + auto remoteDecoderManagerThread = sRemoteDecoderManagerChildThread.Lock(); + return *remoteDecoderManagerThread; +} + +/* static */ +bool RemoteDecoderManagerChild::Supports( + RemoteDecodeIn aLocation, const SupportDecoderParams& aParams, + DecoderDoctorDiagnostics* aDiagnostics) { + Maybe<PDMFactory::MediaCodecsSupported> supported; + switch (aLocation) { + case RemoteDecodeIn::RddProcess: { + auto supportedRDD = sRDDSupported.Lock(); + supported = *supportedRDD; + break; + } + case RemoteDecodeIn::GpuProcess: { + auto supportedGPU = sGPUSupported.Lock(); + supported = *supportedGPU; + break; + } + default: + return false; + } + if (!supported) { + // We haven't received the correct information yet from either the GPU or + // the RDD process; assume it is supported to prevent false negative. + if (aLocation == RemoteDecodeIn::RddProcess) { + // Ensure the RDD process got started. + // TODO: This can be removed once bug 1684991 is fixed. + LaunchRDDProcessIfNeeded(); + } + return true; + } + + // We can ignore the SupportDecoderParams argument for now as creation of the + // decoder will actually fail later and fallback PDMs will be tested on later. + return PDMFactory::SupportsMimeType(aParams.MimeType(), *supported); +} + +/* static */ +RefPtr<PlatformDecoderModule::CreateDecoderPromise> +RemoteDecoderManagerChild::CreateAudioDecoder( + const CreateDecoderParams& aParams) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + // We got shutdown. + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_CANCELED, __func__); + } + return LaunchRDDProcessIfNeeded()->Then( + managerThread, __func__, + [params = CreateDecoderParamsForAsync(aParams)](bool) { + auto child = MakeRefPtr<RemoteAudioDecoderChild>(); + MediaResult result = + child->InitIPDL(params.AudioConfig(), params.mOptions); + if (NS_FAILED(result)) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + result, __func__); + } + return Construct(std::move(child)); + }, + [](nsresult aResult) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + MediaResult(aResult, "Couldn't start RDD process"), __func__); + }); +} + +/* static */ +RefPtr<PlatformDecoderModule::CreateDecoderPromise> +RemoteDecoderManagerChild::CreateVideoDecoder( + const CreateDecoderParams& aParams, RemoteDecodeIn aLocation) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + // We got shutdown. + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_CANCELED, __func__); + } + + if (!aParams.mKnowsCompositor && aLocation == RemoteDecodeIn::GpuProcess) { + // We don't have an image bridge; don't attempt to decode in the GPU + // process. + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, __func__); + } + + MOZ_ASSERT(aLocation != RemoteDecodeIn::Unspecified); + + RefPtr<GenericNonExclusivePromise> p = + aLocation == RemoteDecodeIn::GpuProcess + ? GenericNonExclusivePromise::CreateAndResolve(true, __func__) + : LaunchRDDProcessIfNeeded(); + + return p->Then( + managerThread, __func__, + [aLocation, params = CreateDecoderParamsForAsync(aParams)](bool) { + auto child = MakeRefPtr<RemoteVideoDecoderChild>(aLocation); + MediaResult result = child->InitIPDL( + params.VideoConfig(), params.mRate.mValue, params.mOptions, + params.mKnowsCompositor + ? Some(params.mKnowsCompositor->GetTextureFactoryIdentifier()) + : Nothing()); + if (NS_FAILED(result)) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + result, __func__); + } + return Construct(std::move(child)); + }, + [](nsresult aResult) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + MediaResult(aResult, "Couldn't start RDD process"), __func__); + }); +} + +/* static */ +RefPtr<PlatformDecoderModule::CreateDecoderPromise> +RemoteDecoderManagerChild::Construct(RefPtr<RemoteDecoderChild>&& aChild) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + // We got shutdown. + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_CANCELED, __func__); + } + MOZ_ASSERT(managerThread->IsOnCurrentThread()); + + RefPtr<PlatformDecoderModule::CreateDecoderPromise> p = + aChild->SendConstruct()->Then( + managerThread, __func__, + [child = std::move(aChild)](MediaResult aResult) { + if (NS_FAILED(aResult)) { + // We will never get to use this remote decoder, tear it down. + child->DestroyIPDL(); + return PlatformDecoderModule::CreateDecoderPromise:: + CreateAndReject(aResult, __func__); + } + return PlatformDecoderModule::CreateDecoderPromise:: + CreateAndResolve(MakeRefPtr<RemoteMediaDataDecoder>(child), + __func__); + }, + [](const mozilla::ipc::ResponseRejectReason& aReason) { + // The parent has died. + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER, __func__); + }); + return p; +} + +/* static */ +RefPtr<GenericNonExclusivePromise> +RemoteDecoderManagerChild::LaunchRDDProcessIfNeeded() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess(), + "Only supported from a content process."); + + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + // We got shutdown. + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + + StaticMutexAutoLock lock(sLaunchMutex); + + if (sLaunchRDDPromise) { + return sLaunchRDDPromise; + } + + // We have a couple possible states here. We are in a content process + // and: + // 1) the RDD process has never been launched. RDD should be launched + // and the IPC connections setup. + // 2) the RDD process has been launched, but this particular content + // process has not setup (or has lost) its IPC connection. + // In the code below, we assume we need to launch the RDD process and + // setup the IPC connections. However, if the manager thread for + // RemoteDecoderManagerChild is available we do a quick check to see + // if we can send (meaning the IPC channel is open). If we can send, + // then no work is necessary. If we can't send, then we call + // LaunchRDDProcess which will launch RDD if necessary, and setup the + // IPC connections between *this* content process and the RDD process. + + RefPtr<GenericNonExclusivePromise> p = InvokeAsync( + managerThread, __func__, []() -> RefPtr<GenericNonExclusivePromise> { + auto* rps = GetSingleton(RemoteDecodeIn::RddProcess); + if (rps && rps->CanSend()) { + return GenericNonExclusivePromise::CreateAndResolve(true, __func__); + } + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + ipc::PBackgroundChild* bgActor = + ipc::BackgroundChild::GetForCurrentThread(); + if (!managerThread || NS_WARN_IF(!bgActor)) { + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + + return bgActor->SendEnsureRDDProcessAndCreateBridge()->Then( + managerThread, __func__, + [](ipc::PBackgroundChild::EnsureRDDProcessAndCreateBridgePromise:: + ResolveOrRejectValue&& aResult) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread || aResult.IsReject()) { + // The parent process died or we got shutdown + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + } + nsresult rv = Get<0>(aResult.ResolveValue()); + if (NS_FAILED(rv)) { + return GenericNonExclusivePromise::CreateAndReject(rv, + __func__); + } + OpenForRDDProcess(Get<1>(std::move(aResult.ResolveValue()))); + return GenericNonExclusivePromise::CreateAndResolve(true, + __func__); + }); + }); + + p = p->Then( + GetCurrentSerialEventTarget(), __func__, + [](const GenericNonExclusivePromise::ResolveOrRejectValue& aResult) { + StaticMutexAutoLock lock(sLaunchMutex); + sLaunchRDDPromise = nullptr; + return GenericNonExclusivePromise::CreateAndResolveOrReject(aResult, + __func__); + }); + sLaunchRDDPromise = p; + return sLaunchRDDPromise; +} + +PRemoteDecoderChild* RemoteDecoderManagerChild::AllocPRemoteDecoderChild( + const RemoteDecoderInfoIPDL& /* not used */, + const CreateDecoderParams::OptionSet& aOptions, + const Maybe<layers::TextureFactoryIdentifier>& aIdentifier) { + // RemoteDecoderModule is responsible for creating RemoteDecoderChild + // classes. + MOZ_ASSERT(false, + "RemoteDecoderManagerChild cannot create " + "RemoteDecoderChild classes"); + return nullptr; +} + +bool RemoteDecoderManagerChild::DeallocPRemoteDecoderChild( + PRemoteDecoderChild* actor) { + RemoteDecoderChild* child = static_cast<RemoteDecoderChild*>(actor); + child->IPDLActorDestroyed(); + return true; +} + +RemoteDecoderManagerChild::RemoteDecoderManagerChild(RemoteDecodeIn aLocation) + : mLocation(aLocation) {} + +void RemoteDecoderManagerChild::OpenForRDDProcess( + Endpoint<PRemoteDecoderManagerChild>&& aEndpoint) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + // We've been shutdown, bail. + return; + } + MOZ_ASSERT(managerThread->IsOnCurrentThread()); + + // Only create RemoteDecoderManagerChild, bind new endpoint and init + // ipdl if: + // 1) haven't init'd sRemoteDecoderManagerChild + // or + // 2) if ActorDestroy was called meaning the other end of the ipc channel was + // torn down + if (sRemoteDecoderManagerChildForRDDProcess && + sRemoteDecoderManagerChildForRDDProcess->CanSend()) { + return; + } + sRemoteDecoderManagerChildForRDDProcess = nullptr; + if (aEndpoint.IsValid()) { + RefPtr<RemoteDecoderManagerChild> manager = + new RemoteDecoderManagerChild(RemoteDecodeIn::RddProcess); + if (aEndpoint.Bind(manager)) { + sRemoteDecoderManagerChildForRDDProcess = manager; + manager->InitIPDL(); + } + } +} + +void RemoteDecoderManagerChild::OpenForGPUProcess( + Endpoint<PRemoteDecoderManagerChild>&& aEndpoint) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + // We've been shutdown, bail. + return; + } + MOZ_ASSERT(managerThread->IsOnCurrentThread()); + // Make sure we always dispatch everything in sRecreateTasks, even if we + // fail since this is as close to being recreated as we will ever be. + sRemoteDecoderManagerChildForGPUProcess = nullptr; + if (aEndpoint.IsValid()) { + RefPtr<RemoteDecoderManagerChild> manager = + new RemoteDecoderManagerChild(RemoteDecodeIn::GpuProcess); + if (aEndpoint.Bind(manager)) { + sRemoteDecoderManagerChildForGPUProcess = manager; + manager->InitIPDL(); + } + } + for (Runnable* task : *sRecreateTasks) { + task->Run(); + } + sRecreateTasks->Clear(); +} + +void RemoteDecoderManagerChild::InitIPDL() { mIPDLSelfRef = this; } + +void RemoteDecoderManagerChild::ActorDealloc() { mIPDLSelfRef = nullptr; } + +VideoBridgeSource RemoteDecoderManagerChild::GetSource() const { + switch (mLocation) { + case RemoteDecodeIn::RddProcess: + return VideoBridgeSource::RddProcess; + case RemoteDecodeIn::GpuProcess: + return VideoBridgeSource::GpuProcess; + default: + MOZ_CRASH("Unexpected RemoteDecode variant"); + } +} + +bool RemoteDecoderManagerChild::DeallocShmem(mozilla::ipc::Shmem& aShmem) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + return false; + } + if (!managerThread->IsOnCurrentThread()) { + MOZ_ALWAYS_SUCCEEDS(managerThread->Dispatch(NS_NewRunnableFunction( + "RemoteDecoderManagerChild::DeallocShmem", + [self = RefPtr{this}, shmem = aShmem]() mutable { + if (self->CanSend()) { + self->PRemoteDecoderManagerChild::DeallocShmem(shmem); + } + }))); + return true; + } + return PRemoteDecoderManagerChild::DeallocShmem(aShmem); +} + +struct SurfaceDescriptorUserData { + SurfaceDescriptorUserData(RemoteDecoderManagerChild* aAllocator, + SurfaceDescriptor& aSD) + : mAllocator(aAllocator), mSD(aSD) {} + ~SurfaceDescriptorUserData() { DestroySurfaceDescriptor(mAllocator, &mSD); } + + RefPtr<RemoteDecoderManagerChild> mAllocator; + SurfaceDescriptor mSD; +}; + +void DeleteSurfaceDescriptorUserData(void* aClosure) { + SurfaceDescriptorUserData* sd = + reinterpret_cast<SurfaceDescriptorUserData*>(aClosure); + delete sd; +} + +already_AddRefed<SourceSurface> RemoteDecoderManagerChild::Readback( + const SurfaceDescriptorGPUVideo& aSD) { + // We can't use NS_DISPATCH_SYNC here since that can spin the event + // loop while it waits. This function can be called from JS and we + // don't want that to happen. + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + return nullptr; + } + + SurfaceDescriptor sd; + RefPtr<Runnable> task = + NS_NewRunnableFunction("RemoteDecoderManagerChild::Readback", [&]() { + if (CanSend()) { + SendReadback(aSD, &sd); + } + }); + SyncRunnable::DispatchToThread(managerThread, task); + + if (!IsSurfaceDescriptorValid(sd)) { + return nullptr; + } + + RefPtr<DataSourceSurface> source = GetSurfaceForDescriptor(sd); + if (!source) { + DestroySurfaceDescriptor(this, &sd); + NS_WARNING("Failed to map SurfaceDescriptor in Readback"); + return nullptr; + } + + static UserDataKey sSurfaceDescriptor; + source->AddUserData(&sSurfaceDescriptor, + new SurfaceDescriptorUserData(this, sd), + DeleteSurfaceDescriptorUserData); + + return source.forget(); +} + +void RemoteDecoderManagerChild::DeallocateSurfaceDescriptor( + const SurfaceDescriptorGPUVideo& aSD) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + return; + } + MOZ_ALWAYS_SUCCEEDS(managerThread->Dispatch(NS_NewRunnableFunction( + "RemoteDecoderManagerChild::DeallocateSurfaceDescriptor", + [ref = RefPtr{this}, sd = aSD]() { + if (ref->CanSend()) { + ref->SendDeallocateSurfaceDescriptorGPUVideo(sd); + } + }))); +} + +void RemoteDecoderManagerChild::HandleFatalError(const char* aMsg) const { + dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid()); +} + +void RemoteDecoderManagerChild::SetSupported( + RemoteDecodeIn aLocation, + const PDMFactory::MediaCodecsSupported& aSupported) { + switch (aLocation) { + case RemoteDecodeIn::GpuProcess: { + auto supported = sGPUSupported.Lock(); + *supported = Some(aSupported); + break; + } + case RemoteDecodeIn::RddProcess: { + auto supported = sRDDSupported.Lock(); + *supported = Some(aSupported); + break; + } + default: + MOZ_CRASH("Not to be used for any other process"); + } +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteDecoderManagerChild.h b/dom/media/ipc/RemoteDecoderManagerChild.h new file mode 100644 index 0000000000..ea65ebc86f --- /dev/null +++ b/dom/media/ipc/RemoteDecoderManagerChild.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 include_dom_media_ipc_RemoteDecoderManagerChild_h +#define include_dom_media_ipc_RemoteDecoderManagerChild_h + +#include "GPUVideoImage.h" +#include "ipc/EnumSerializer.h" +#include "mozilla/EnumTypeTraits.h" +#include "mozilla/PRemoteDecoderManagerChild.h" +#include "mozilla/layers/VideoBridgeUtils.h" + +namespace mozilla { + +class RemoteDecoderChild; + +enum class RemoteDecodeIn { + Unspecified, + RddProcess, + GpuProcess, + + SENTINEL, +}; + +class RemoteDecoderManagerChild final + : public PRemoteDecoderManagerChild, + public mozilla::ipc::IShmemAllocator, + public mozilla::layers::IGPUVideoSurfaceManager { + friend class PRemoteDecoderManagerChild; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderManagerChild, override) + + // Can only be called from the manager thread + static RemoteDecoderManagerChild* GetSingleton(RemoteDecodeIn aLocation); + + static void Init(); + static void SetSupported(RemoteDecodeIn aLocation, + const PDMFactory::MediaCodecsSupported& aSupported); + + // Can be called from any thread. + static bool Supports(RemoteDecodeIn aLocation, + const SupportDecoderParams& aParams, + DecoderDoctorDiagnostics* aDiagnostics); + static RefPtr<PlatformDecoderModule::CreateDecoderPromise> CreateAudioDecoder( + const CreateDecoderParams& aParams); + static RefPtr<PlatformDecoderModule::CreateDecoderPromise> CreateVideoDecoder( + const CreateDecoderParams& aParams, RemoteDecodeIn aLocation); + + // Can be called from any thread. + static nsISerialEventTarget* GetManagerThread(); + + // Can be called from any thread, dispatches the request to the IPDL thread + // internally and will be ignored if the IPDL actor has been destroyed. + already_AddRefed<gfx::SourceSurface> Readback( + const SurfaceDescriptorGPUVideo& aSD) override; + void DeallocateSurfaceDescriptor( + const SurfaceDescriptorGPUVideo& aSD) override; + + bool AllocShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + mozilla::ipc::Shmem* aShmem) override { + return PRemoteDecoderManagerChild::AllocShmem(aSize, aShmType, aShmem); + } + bool AllocUnsafeShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + mozilla::ipc::Shmem* aShmem) override { + return PRemoteDecoderManagerChild::AllocUnsafeShmem(aSize, aShmType, + aShmem); + } + + // Can be called from any thread, dispatches the request to the IPDL thread + // internally and will be ignored if the IPDL actor has been destroyed. + bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override; + + // Main thread only + static void InitForGPUProcess( + Endpoint<PRemoteDecoderManagerChild>&& aVideoManager); + static void Shutdown(); + + // Run aTask (on the manager thread) when we next attempt to create a new + // manager (even if creation fails). Intended to be called from ActorDestroy + // when we get notified that the old manager is being destroyed. Can only be + // called from the manager thread. + void RunWhenGPUProcessRecreated(already_AddRefed<Runnable> aTask); + + RemoteDecodeIn Location() const { return mLocation; } + layers::VideoBridgeSource GetSource() const; + + protected: + void InitIPDL(); + + void ActorDealloc() override; + + void HandleFatalError(const char* aMsg) const override; + + PRemoteDecoderChild* AllocPRemoteDecoderChild( + const RemoteDecoderInfoIPDL& aRemoteDecoderInfo, + const CreateDecoderParams::OptionSet& aOptions, + const Maybe<layers::TextureFactoryIdentifier>& aIdentifier); + bool DeallocPRemoteDecoderChild(PRemoteDecoderChild* actor); + + private: + explicit RemoteDecoderManagerChild(RemoteDecodeIn aLocation); + ~RemoteDecoderManagerChild() = default; + static RefPtr<PlatformDecoderModule::CreateDecoderPromise> Construct( + RefPtr<RemoteDecoderChild>&& aChild); + + static void OpenForRDDProcess( + Endpoint<PRemoteDecoderManagerChild>&& aEndpoint); + static void OpenForGPUProcess( + Endpoint<PRemoteDecoderManagerChild>&& aEndpoint); + static RefPtr<GenericNonExclusivePromise> LaunchRDDProcessIfNeeded(); + + RefPtr<RemoteDecoderManagerChild> mIPDLSelfRef; + // The location for decoding, Rdd or Gpu process. + const RemoteDecodeIn mLocation; +}; + +} // namespace mozilla + +namespace IPC { +template <> +struct ParamTraits<mozilla::RemoteDecodeIn> + : public ContiguousEnumSerializer<mozilla::RemoteDecodeIn, + mozilla::RemoteDecodeIn::Unspecified, + mozilla::RemoteDecodeIn::SENTINEL> {}; +} // namespace IPC + +#endif // include_dom_media_ipc_RemoteDecoderManagerChild_h diff --git a/dom/media/ipc/RemoteDecoderManagerParent.cpp b/dom/media/ipc/RemoteDecoderManagerParent.cpp new file mode 100644 index 0000000000..8dc23bd34a --- /dev/null +++ b/dom/media/ipc/RemoteDecoderManagerParent.cpp @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteDecoderManagerParent.h" + +#if XP_WIN +# include <objbase.h> +#endif + +#include "ImageContainer.h" +#include "PDMFactory.h" +#include "RemoteAudioDecoder.h" +#include "RemoteVideoDecoder.h" +#include "VideoUtils.h" // for MediaThreadType +#include "mozilla/RDDParent.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/VideoBridgeChild.h" + +namespace mozilla { + +#ifdef XP_WIN +extern const nsCString GetFoundD3D11BlacklistedDLL(); +extern const nsCString GetFoundD3D9BlacklistedDLL(); +#endif // XP_WIN + +using namespace ipc; +using namespace layers; +using namespace gfx; + +StaticRefPtr<TaskQueue> sRemoteDecoderManagerParentThread; + +void RemoteDecoderManagerParent::StoreImage( + const SurfaceDescriptorGPUVideo& aSD, Image* aImage, + TextureClient* aTexture) { + MOZ_ASSERT(OnManagerThread()); + mImageMap[static_cast<SurfaceDescriptorRemoteDecoder>(aSD).handle()] = aImage; + mTextureMap[static_cast<SurfaceDescriptorRemoteDecoder>(aSD).handle()] = + aTexture; +} + +class RemoteDecoderManagerThreadShutdownObserver : public nsIObserver { + virtual ~RemoteDecoderManagerThreadShutdownObserver() = default; + + public: + RemoteDecoderManagerThreadShutdownObserver() = default; + + NS_DECL_ISUPPORTS + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); + + RemoteDecoderManagerParent::ShutdownVideoBridge(); + RemoteDecoderManagerParent::ShutdownThreads(); + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(RemoteDecoderManagerThreadShutdownObserver, nsIObserver); + +bool RemoteDecoderManagerParent::StartupThreads() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sRemoteDecoderManagerParentThread) { + return true; + } + + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (!observerService) { + return false; + } + + sRemoteDecoderManagerParentThread = new TaskQueue( + GetMediaThreadPool(MediaThreadType::SUPERVISOR), "RemVidParent"); + if (XRE_IsGPUProcess()) { + MOZ_ALWAYS_SUCCEEDS( + sRemoteDecoderManagerParentThread->Dispatch(NS_NewRunnableFunction( + "RemoteDecoderManagerParent::StartupThreads", + []() { layers::VideoBridgeChild::StartupForGPUProcess(); }))); + } + + auto* obs = new RemoteDecoderManagerThreadShutdownObserver(); + observerService->AddObserver(obs, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + return true; +} + +void RemoteDecoderManagerParent::ShutdownThreads() { + sRemoteDecoderManagerParentThread->BeginShutdown(); + sRemoteDecoderManagerParentThread->AwaitShutdownAndIdle(); + sRemoteDecoderManagerParentThread = nullptr; +} + +/* static */ +void RemoteDecoderManagerParent::ShutdownVideoBridge() { + if (sRemoteDecoderManagerParentThread) { + RefPtr<Runnable> task = NS_NewRunnableFunction( + "RemoteDecoderManagerParent::ShutdownVideoBridge", + []() { VideoBridgeChild::Shutdown(); }); + SyncRunnable::DispatchToThread(sRemoteDecoderManagerParentThread, task); + } +} + +bool RemoteDecoderManagerParent::OnManagerThread() { + return sRemoteDecoderManagerParentThread->IsOnCurrentThread(); +} + +PDMFactory& RemoteDecoderManagerParent::EnsurePDMFactory() { + MOZ_ASSERT(OnManagerThread()); + if (!mPDMFactory) { + mPDMFactory = MakeRefPtr<PDMFactory>(); + } + return *mPDMFactory; +} + +bool RemoteDecoderManagerParent::CreateForContent( + Endpoint<PRemoteDecoderManagerParent>&& aEndpoint) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_RDD || + XRE_GetProcessType() == GeckoProcessType_GPU); + MOZ_ASSERT(NS_IsMainThread()); + + if (!StartupThreads()) { + return false; + } + + RefPtr<RemoteDecoderManagerParent> parent = + new RemoteDecoderManagerParent(sRemoteDecoderManagerParentThread); + + RefPtr<Runnable> task = + NewRunnableMethod<Endpoint<PRemoteDecoderManagerParent>&&>( + "dom::RemoteDecoderManagerParent::Open", parent, + &RemoteDecoderManagerParent::Open, std::move(aEndpoint)); + MOZ_ALWAYS_SUCCEEDS( + sRemoteDecoderManagerParentThread->Dispatch(task.forget())); + return true; +} + +bool RemoteDecoderManagerParent::CreateVideoBridgeToOtherProcess( + Endpoint<PVideoBridgeChild>&& aEndpoint) { + // We never want to decode in the GPU process, but output + // frames to the parent process. + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_RDD); + MOZ_ASSERT(NS_IsMainThread()); + + if (!StartupThreads()) { + return false; + } + + RefPtr<Runnable> task = + NewRunnableFunction("gfx::VideoBridgeChild::Open", + &VideoBridgeChild::Open, std::move(aEndpoint)); + MOZ_ALWAYS_SUCCEEDS( + sRemoteDecoderManagerParentThread->Dispatch(task.forget())); + return true; +} + +RemoteDecoderManagerParent::RemoteDecoderManagerParent( + nsISerialEventTarget* aThread) + : mThread(aThread) { + MOZ_COUNT_CTOR(RemoteDecoderManagerParent); + auto& registrar = XRE_IsGPUProcess() + ? GPUParent::GetSingleton()->AsyncShutdownService() + : RDDParent::GetSingleton()->AsyncShutdownService(); + registrar.Register(this); +} + +RemoteDecoderManagerParent::~RemoteDecoderManagerParent() { + MOZ_COUNT_DTOR(RemoteDecoderManagerParent); + auto& registrar = XRE_IsGPUProcess() + ? GPUParent::GetSingleton()->AsyncShutdownService() + : RDDParent::GetSingleton()->AsyncShutdownService(); + registrar.Deregister(this); +} + +void RemoteDecoderManagerParent::ActorDestroy( + mozilla::ipc::IProtocol::ActorDestroyReason) { + mThread = nullptr; +} + +PRemoteDecoderParent* RemoteDecoderManagerParent::AllocPRemoteDecoderParent( + const RemoteDecoderInfoIPDL& aRemoteDecoderInfo, + const CreateDecoderParams::OptionSet& aOptions, + const Maybe<layers::TextureFactoryIdentifier>& aIdentifier) { + RefPtr<TaskQueue> decodeTaskQueue = + new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), + "RemoteVideoDecoderParent::mDecodeTaskQueue"); + + if (aRemoteDecoderInfo.type() == + RemoteDecoderInfoIPDL::TVideoDecoderInfoIPDL) { + const VideoDecoderInfoIPDL& decoderInfo = + aRemoteDecoderInfo.get_VideoDecoderInfoIPDL(); + return new RemoteVideoDecoderParent( + this, decoderInfo.videoInfo(), decoderInfo.framerate(), aOptions, + aIdentifier, sRemoteDecoderManagerParentThread, decodeTaskQueue); + } + + if (aRemoteDecoderInfo.type() == RemoteDecoderInfoIPDL::TAudioInfo) { + return new RemoteAudioDecoderParent( + this, aRemoteDecoderInfo.get_AudioInfo(), aOptions, + sRemoteDecoderManagerParentThread, decodeTaskQueue); + } + + MOZ_CRASH("unrecognized type of RemoteDecoderInfoIPDL union"); + return nullptr; +} + +bool RemoteDecoderManagerParent::DeallocPRemoteDecoderParent( + PRemoteDecoderParent* actor) { + RemoteDecoderParent* parent = static_cast<RemoteDecoderParent*>(actor); + parent->Destroy(); + return true; +} + +void RemoteDecoderManagerParent::Open( + Endpoint<PRemoteDecoderManagerParent>&& aEndpoint) { + if (!aEndpoint.Bind(this)) { + // We can't recover from this. + MOZ_CRASH("Failed to bind RemoteDecoderManagerParent to endpoint"); + } + AddRef(); +} + +void RemoteDecoderManagerParent::ActorDealloc() { Release(); } + +mozilla::ipc::IPCResult RemoteDecoderManagerParent::RecvReadback( + const SurfaceDescriptorGPUVideo& aSD, SurfaceDescriptor* aResult) { + const SurfaceDescriptorRemoteDecoder& sd = aSD; + RefPtr<Image> image = mImageMap[sd.handle()]; + if (!image) { + *aResult = null_t(); + return IPC_OK(); + } + + RefPtr<SourceSurface> source = image->GetAsSourceSurface(); + if (!source) { + *aResult = null_t(); + return IPC_OK(); + } + + SurfaceFormat format = source->GetFormat(); + IntSize size = source->GetSize(); + size_t length = ImageDataSerializer::ComputeRGBBufferSize(size, format); + + Shmem buffer; + if (!length || + !AllocShmem(length, Shmem::SharedMemory::TYPE_BASIC, &buffer)) { + *aResult = null_t(); + return IPC_OK(); + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + gfx::BackendType::CAIRO, buffer.get<uint8_t>(), size, + ImageDataSerializer::ComputeRGBStride(format, size.width), format); + if (!dt) { + DeallocShmem(buffer); + *aResult = null_t(); + return IPC_OK(); + } + + dt->CopySurface(source, IntRect(0, 0, size.width, size.height), IntPoint()); + dt->Flush(); + + *aResult = SurfaceDescriptorBuffer(RGBDescriptor(size, format, true), + MemoryOrShmem(std::move(buffer))); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +RemoteDecoderManagerParent::RecvDeallocateSurfaceDescriptorGPUVideo( + const SurfaceDescriptorGPUVideo& aSD) { + MOZ_ASSERT(OnManagerThread()); + const SurfaceDescriptorRemoteDecoder& sd = aSD; + mImageMap.erase(sd.handle()); + mTextureMap.erase(sd.handle()); + return IPC_OK(); +} + +void RemoteDecoderManagerParent::DeallocateSurfaceDescriptor( + const SurfaceDescriptorGPUVideo& aSD) { + if (!OnManagerThread()) { + MOZ_ALWAYS_SUCCEEDS( + sRemoteDecoderManagerParentThread->Dispatch(NS_NewRunnableFunction( + "RemoteDecoderManagerParent::DeallocateSurfaceDescriptor", + [ref = RefPtr{this}, sd = aSD]() { + ref->RecvDeallocateSurfaceDescriptorGPUVideo(sd); + }))); + } else { + RecvDeallocateSurfaceDescriptorGPUVideo(aSD); + } +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteDecoderManagerParent.h b/dom/media/ipc/RemoteDecoderManagerParent.h new file mode 100644 index 0000000000..7cd2084824 --- /dev/null +++ b/dom/media/ipc/RemoteDecoderManagerParent.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 include_dom_media_ipc_RemoteDecoderManagerParent_h +#define include_dom_media_ipc_RemoteDecoderManagerParent_h + +#include "GPUVideoImage.h" +#include "mozilla/PRemoteDecoderManagerParent.h" +#include "mozilla/layers/VideoBridgeChild.h" + +namespace mozilla { + +class PDMFactory; + +class RemoteDecoderManagerParent final + : public PRemoteDecoderManagerParent, + public layers::IGPUVideoSurfaceManager { + friend class PRemoteDecoderManagerParent; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderManagerParent, override) + + static bool CreateForContent( + Endpoint<PRemoteDecoderManagerParent>&& aEndpoint); + + static bool CreateVideoBridgeToOtherProcess( + Endpoint<layers::PVideoBridgeChild>&& aEndpoint); + + // Must be called on manager thread. + // Store the image so that it can be used out of process. Will be released + // when DeallocateSurfaceDescriptor is called. + void StoreImage(const SurfaceDescriptorGPUVideo& aSD, layers::Image* aImage, + layers::TextureClient* aTexture); + + // IGPUVideoSurfaceManager methods + already_AddRefed<gfx::SourceSurface> Readback( + const SurfaceDescriptorGPUVideo& aSD) override { + MOZ_ASSERT_UNREACHABLE("Not usable from the parent"); + return nullptr; + } + void DeallocateSurfaceDescriptor( + const SurfaceDescriptorGPUVideo& aSD) override; + + static bool StartupThreads(); + static void ShutdownThreads(); + + static void ShutdownVideoBridge(); + + bool OnManagerThread(); + + // Can be called from manager thread only + PDMFactory& EnsurePDMFactory(); + + protected: + PRemoteDecoderParent* AllocPRemoteDecoderParent( + const RemoteDecoderInfoIPDL& aRemoteDecoderInfo, + const CreateDecoderParams::OptionSet& aOptions, + const Maybe<layers::TextureFactoryIdentifier>& aIdentifier); + bool DeallocPRemoteDecoderParent(PRemoteDecoderParent* actor); + + mozilla::ipc::IPCResult RecvReadback(const SurfaceDescriptorGPUVideo& aSD, + SurfaceDescriptor* aResult); + mozilla::ipc::IPCResult RecvDeallocateSurfaceDescriptorGPUVideo( + const SurfaceDescriptorGPUVideo& aSD); + + void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override; + void ActorDealloc() override; + + private: + explicit RemoteDecoderManagerParent(nsISerialEventTarget* aThread); + ~RemoteDecoderManagerParent(); + + void Open(Endpoint<PRemoteDecoderManagerParent>&& aEndpoint); + + std::map<uint64_t, RefPtr<layers::Image>> mImageMap; + std::map<uint64_t, RefPtr<layers::TextureClient>> mTextureMap; + + nsCOMPtr<nsISerialEventTarget> mThread; + RefPtr<PDMFactory> mPDMFactory; +}; + +} // namespace mozilla + +#endif // include_dom_media_ipc_RemoteDecoderManagerParent_h diff --git a/dom/media/ipc/RemoteDecoderModule.cpp b/dom/media/ipc/RemoteDecoderModule.cpp new file mode 100644 index 0000000000..310803c6ce --- /dev/null +++ b/dom/media/ipc/RemoteDecoderModule.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteDecoderModule.h" + +#ifdef MOZ_AV1 +# include "AOMDecoder.h" +#endif +#include "OpusDecoder.h" +#include "RemoteAudioDecoder.h" +#include "RemoteDecoderManagerChild.h" +#include "RemoteMediaDataDecoder.h" +#include "RemoteVideoDecoder.h" +#include "VideoUtils.h" +#include "VorbisDecoder.h" +#include "WAVDecoder.h" +#include "gfxConfig.h" + +namespace mozilla { + +using namespace ipc; +using namespace layers; + +already_AddRefed<PlatformDecoderModule> RemoteDecoderModule::Create( + RemoteDecodeIn aLocation) { + MOZ_ASSERT(!XRE_IsGPUProcess() && !XRE_IsRDDProcess(), + "Should not be created in GPU or RDD process."); + if (!XRE_IsContentProcess()) { + // For now, the RemoteDecoderModule is only available in the content + // process. + return nullptr; + } + return MakeAndAddRef<RemoteDecoderModule>(aLocation); +} + +RemoteDecoderModule::RemoteDecoderModule(RemoteDecodeIn aLocation) + : mLocation(aLocation) {} + +bool RemoteDecoderModule::SupportsMimeType( + const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const { + MOZ_CRASH("Deprecated: Use RemoteDecoderModule::Supports"); +} // namespace mozilla + +bool RemoteDecoderModule::Supports( + const SupportDecoderParams& aParams, + DecoderDoctorDiagnostics* aDiagnostics) const { + bool supports = + RemoteDecoderManagerChild::Supports(mLocation, aParams, aDiagnostics); + MOZ_LOG(sPDMLog, LogLevel::Debug, + ("Sandbox %s decoder %s requested type", + mLocation == RemoteDecodeIn::GpuProcess ? "GPU" : "RDD", + supports ? "supports" : "rejects")); + return supports; +} + +RefPtr<RemoteDecoderModule::CreateDecoderPromise> +RemoteDecoderModule::AsyncCreateDecoder(const CreateDecoderParams& aParams) { + if (aParams.mConfig.IsAudio()) { + // OpusDataDecoder will check this option to provide the same info + // that IsDefaultPlaybackDeviceMono provides. We want to avoid calls + // to IsDefaultPlaybackDeviceMono on RDD because initializing audio + // backends on RDD will be blocked by the sandbox. + if (OpusDataDecoder::IsOpus(aParams.mConfig.mMimeType) && + IsDefaultPlaybackDeviceMono()) { + CreateDecoderParams params = aParams; + params.mOptions += CreateDecoderParams::Option::DefaultPlaybackDeviceMono; + return RemoteDecoderManagerChild::CreateAudioDecoder(params); + } + return RemoteDecoderManagerChild::CreateAudioDecoder(aParams); + } + return RemoteDecoderManagerChild::CreateVideoDecoder(aParams, mLocation); +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteDecoderModule.h b/dom/media/ipc/RemoteDecoderModule.h new file mode 100644 index 0000000000..2b84f7e8c9 --- /dev/null +++ b/dom/media/ipc/RemoteDecoderModule.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 include_dom_media_ipc_RemoteDecoderModule_h +#define include_dom_media_ipc_RemoteDecoderModule_h +#include "PlatformDecoderModule.h" + +namespace mozilla { + +enum class RemoteDecodeIn; + +// A decoder module that proxies decoding to either GPU or RDD process. +class RemoteDecoderModule : public PlatformDecoderModule { + template <typename T, typename... Args> + friend already_AddRefed<T> MakeAndAddRef(Args&&...); + + public: + static already_AddRefed<PlatformDecoderModule> Create( + RemoteDecodeIn aLocation); + + bool SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override; + + bool Supports(const SupportDecoderParams& aParams, + DecoderDoctorDiagnostics* aDiagnostics) const override; + + RefPtr<CreateDecoderPromise> AsyncCreateDecoder( + const CreateDecoderParams& aParams) override; + + already_AddRefed<MediaDataDecoder> CreateVideoDecoder( + const CreateDecoderParams& aParams) override { + MOZ_CRASH("Not available"); + } + + already_AddRefed<MediaDataDecoder> CreateAudioDecoder( + const CreateDecoderParams& aParams) override { + MOZ_CRASH("Not available"); + } + + private: + explicit RemoteDecoderModule(RemoteDecodeIn aLocation); + + const RemoteDecodeIn mLocation; +}; + +} // namespace mozilla + +#endif // include_dom_media_ipc_RemoteDecoderModule_h diff --git a/dom/media/ipc/RemoteDecoderParent.cpp b/dom/media/ipc/RemoteDecoderParent.cpp new file mode 100644 index 0000000000..a429201157 --- /dev/null +++ b/dom/media/ipc/RemoteDecoderParent.cpp @@ -0,0 +1,228 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteDecoderParent.h" + +#include "RemoteDecoderManagerParent.h" +#include "mozilla/Unused.h" + +namespace mozilla { + +RemoteDecoderParent::RemoteDecoderParent( + RemoteDecoderManagerParent* aParent, + const CreateDecoderParams::OptionSet& aOptions, + nsISerialEventTarget* aManagerThread, TaskQueue* aDecodeTaskQueue) + : ShmemRecycleAllocator(this), + mParent(aParent), + mOptions(aOptions), + mDecodeTaskQueue(aDecodeTaskQueue), + mManagerThread(aManagerThread) { + MOZ_COUNT_CTOR(RemoteDecoderParent); + MOZ_ASSERT(OnManagerThread()); + // We hold a reference to ourselves to keep us alive until IPDL + // explictly destroys us. There may still be refs held by + // tasks, but no new ones should be added after we're + // destroyed. + mIPDLSelfRef = this; +} + +RemoteDecoderParent::~RemoteDecoderParent() { + MOZ_COUNT_DTOR(RemoteDecoderParent); +} + +void RemoteDecoderParent::Destroy() { + MOZ_ASSERT(OnManagerThread()); + mIPDLSelfRef = nullptr; +} + +mozilla::ipc::IPCResult RemoteDecoderParent::RecvInit( + InitResolver&& aResolver) { + MOZ_ASSERT(OnManagerThread()); + RefPtr<RemoteDecoderParent> self = this; + mDecoder->Init()->Then( + mManagerThread, __func__, + [self, resolver = std::move(aResolver)]( + MediaDataDecoder::InitPromise::ResolveOrRejectValue&& aValue) { + if (!self->CanRecv()) { + // The promise to the child would have already been rejected. + return; + } + if (aValue.IsReject()) { + resolver(aValue.RejectValue()); + return; + } + auto track = aValue.ResolveValue(); + MOZ_ASSERT(track == TrackInfo::kAudioTrack || + track == TrackInfo::kVideoTrack); + if (self->mDecoder) { + nsCString hardwareReason; + bool hardwareAccelerated = + self->mDecoder->IsHardwareAccelerated(hardwareReason); + resolver(InitCompletionIPDL{ + track, self->mDecoder->GetDescriptionName(), hardwareAccelerated, + hardwareReason, self->mDecoder->NeedsConversion()}); + } + }); + return IPC_OK(); +} + +void RemoteDecoderParent::DecodeNextSample( + const RefPtr<ArrayOfRemoteMediaRawData>& aData, size_t aIndex, + MediaDataDecoder::DecodedData&& aOutput, DecodeResolver&& aResolver) { + MOZ_ASSERT(OnManagerThread()); + + if (!CanRecv()) { + // Avoid unnecessarily creating shmem objects later. + return; + } + + if (!mDecoder) { + // We got shutdown or the child got destroyed. + aResolver(MediaResult(NS_ERROR_ABORT, __func__)); + return; + } + + if (aData->Count() == aIndex) { + DecodedOutputIPDL result; + MediaResult rv = ProcessDecodedData(std::move(aOutput), result); + if (NS_FAILED(rv)) { + aResolver(std::move(rv)); // Out of Memory. + } else { + aResolver(std::move(result)); + } + return; + } + + RefPtr<MediaRawData> rawData = aData->ElementAt(aIndex); + if (!rawData) { + // OOM + aResolver(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + return; + } + + mDecoder->Decode(rawData)->Then( + mManagerThread, __func__, + [self = RefPtr{this}, this, aData, aIndex, output = std::move(aOutput), + resolver = std::move(aResolver)]( + MediaDataDecoder::DecodePromise::ResolveOrRejectValue&& + aValue) mutable { + if (aValue.IsReject()) { + resolver(aValue.RejectValue()); + return; + } + + output.AppendElements(std::move(aValue.ResolveValue())); + + // Call again in case we have more data to decode. + DecodeNextSample(aData, aIndex + 1, std::move(output), + std::move(resolver)); + }); +} + +mozilla::ipc::IPCResult RemoteDecoderParent::RecvDecode( + ArrayOfRemoteMediaRawData* aData, DecodeResolver&& aResolver) { + MOZ_ASSERT(OnManagerThread()); + // XXX: This copies the data into a buffer owned by the MediaRawData. Ideally + // we'd just take ownership of the shmem. + // Use the passed bufferSize in MediaRawDataIPDL since we can get a Shmem + // buffer from ShmemPool larger than the requested size. + + // If we are here, we know all previously returned DecodedOutputIPDL got + // used by the child. We can mark all previously sent ShmemBuffer as + // available again. + ReleaseAllBuffers(); + MediaDataDecoder::DecodedData output; + DecodeNextSample(aData, 0, std::move(output), std::move(aResolver)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteDecoderParent::RecvFlush( + FlushResolver&& aResolver) { + MOZ_ASSERT(OnManagerThread()); + RefPtr<RemoteDecoderParent> self = this; + mDecoder->Flush()->Then( + mManagerThread, __func__, + [self, resolver = std::move(aResolver)]( + MediaDataDecoder::FlushPromise::ResolveOrRejectValue&& aValue) { + self->ReleaseAllBuffers(); + if (aValue.IsReject()) { + resolver(aValue.RejectValue()); + } else { + resolver(MediaResult(NS_OK)); + } + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteDecoderParent::RecvDrain( + DrainResolver&& aResolver) { + MOZ_ASSERT(OnManagerThread()); + RefPtr<RemoteDecoderParent> self = this; + mDecoder->Drain()->Then( + mManagerThread, __func__, + [self, this, resolver = std::move(aResolver)]( + MediaDataDecoder::DecodePromise::ResolveOrRejectValue&& aValue) { + ReleaseAllBuffers(); + if (!self->CanRecv()) { + // Avoid unnecessarily creating shmem objects later. + return; + } + if (aValue.IsReject()) { + resolver(aValue.RejectValue()); + return; + } + DecodedOutputIPDL output; + MediaResult rv = + ProcessDecodedData(std::move(aValue.ResolveValue()), output); + if (NS_FAILED(rv)) { + resolver(rv); + } else { + resolver(std::move(output)); + } + }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteDecoderParent::RecvShutdown( + ShutdownResolver&& aResolver) { + MOZ_ASSERT(OnManagerThread()); + if (mDecoder) { + RefPtr<RemoteDecoderParent> self = this; + mDecoder->Shutdown()->Then( + mManagerThread, __func__, + [self, resolver = std::move(aResolver)]( + const ShutdownPromise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(aValue.IsResolve()); + self->ReleaseAllBuffers(); + resolver(true); + }); + } + mDecoder = nullptr; + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteDecoderParent::RecvSetSeekThreshold( + const TimeUnit& aTime) { + MOZ_ASSERT(OnManagerThread()); + mDecoder->SetSeekThreshold(aTime); + return IPC_OK(); +} + +void RemoteDecoderParent::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_ASSERT(OnManagerThread()); + if (mDecoder) { + mDecoder->Shutdown(); + mDecoder = nullptr; + } + CleanupShmemRecycleAllocator(); +} + +bool RemoteDecoderParent::OnManagerThread() { + return mParent->OnManagerThread(); +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteDecoderParent.h b/dom/media/ipc/RemoteDecoderParent.h new file mode 100644 index 0000000000..bce56ee77e --- /dev/null +++ b/dom/media/ipc/RemoteDecoderParent.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 include_dom_media_ipc_RemoteDecoderParent_h +#define include_dom_media_ipc_RemoteDecoderParent_h + +#include "mozilla/PRemoteDecoderParent.h" +#include "mozilla/ShmemRecycleAllocator.h" + +namespace mozilla { + +class RemoteDecoderManagerParent; +using mozilla::ipc::IPCResult; + +class RemoteDecoderParent : public ShmemRecycleAllocator<RemoteDecoderParent>, + public PRemoteDecoderParent { + friend class PRemoteDecoderParent; + + public: + // We refcount this class since the task queue can have runnables + // that reference us. + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderParent) + + RemoteDecoderParent(RemoteDecoderManagerParent* aParent, + const CreateDecoderParams::OptionSet& aOptions, + nsISerialEventTarget* aManagerThread, + TaskQueue* aDecodeTaskQueue); + + void Destroy(); + + // PRemoteDecoderParent + virtual IPCResult RecvConstruct(ConstructResolver&& aResolver) = 0; + IPCResult RecvInit(InitResolver&& aResolver); + IPCResult RecvDecode(ArrayOfRemoteMediaRawData* aData, + DecodeResolver&& aResolver); + IPCResult RecvFlush(FlushResolver&& aResolver); + IPCResult RecvDrain(DrainResolver&& aResolver); + IPCResult RecvShutdown(ShutdownResolver&& aResolver); + IPCResult RecvSetSeekThreshold(const media::TimeUnit& aTime); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + protected: + virtual ~RemoteDecoderParent(); + + bool OnManagerThread(); + + virtual MediaResult ProcessDecodedData(MediaDataDecoder::DecodedData&& aData, + DecodedOutputIPDL& aDecodedData) = 0; + + const RefPtr<RemoteDecoderManagerParent> mParent; + const CreateDecoderParams::OptionSet mOptions; + const RefPtr<TaskQueue> mDecodeTaskQueue; + RefPtr<MediaDataDecoder> mDecoder; + + private: + void DecodeNextSample(const RefPtr<ArrayOfRemoteMediaRawData>& aData, + size_t aIndex, MediaDataDecoder::DecodedData&& aOutput, + DecodeResolver&& aResolver); + RefPtr<RemoteDecoderParent> mIPDLSelfRef; + const RefPtr<nsISerialEventTarget> mManagerThread; +}; + +} // namespace mozilla + +#endif // include_dom_media_ipc_RemoteDecoderParent_h diff --git a/dom/media/ipc/RemoteImageHolder.cpp b/dom/media/ipc/RemoteImageHolder.cpp new file mode 100644 index 0000000000..31b28ad0ce --- /dev/null +++ b/dom/media/ipc/RemoteImageHolder.cpp @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteImageHolder.h" + +#include "GPUVideoImage.h" +#include "mozilla/PRemoteDecoderChild.h" +#include "mozilla/RemoteDecoderManagerChild.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/VideoBridgeUtils.h" + +namespace mozilla { + +using namespace gfx; +using namespace layers; + +RemoteImageHolder::RemoteImageHolder() = default; +RemoteImageHolder::RemoteImageHolder(layers::IGPUVideoSurfaceManager* aManager, + layers::VideoBridgeSource aSource, + const gfx::IntSize& aSize, + const layers::SurfaceDescriptor& aSD) + : mSource(aSource), mSize(aSize), mSD(Some(aSD)), mManager(aManager) {} +RemoteImageHolder::RemoteImageHolder(RemoteImageHolder&& aOther) + : mSource(aOther.mSource), + mSize(aOther.mSize), + mSD(std::move(aOther.mSD)), + mManager(aOther.mManager) { + aOther.mSD = Nothing(); +} + +already_AddRefed<Image> RemoteImageHolder::DeserializeImage( + layers::BufferRecycleBin* aBufferRecycleBin) { + MOZ_ASSERT(mSD && mSD->type() == SurfaceDescriptor::TSurfaceDescriptorBuffer); + const SurfaceDescriptorBuffer& sdBuffer = mSD->get_SurfaceDescriptorBuffer(); + MOZ_ASSERT(sdBuffer.desc().type() == BufferDescriptor::TYCbCrDescriptor); + if (sdBuffer.desc().type() != BufferDescriptor::TYCbCrDescriptor || + !aBufferRecycleBin) { + return nullptr; + } + const YCbCrDescriptor& descriptor = sdBuffer.desc().get_YCbCrDescriptor(); + + uint8_t* buffer = nullptr; + const MemoryOrShmem& memOrShmem = sdBuffer.data(); + switch (memOrShmem.type()) { + case MemoryOrShmem::Tuintptr_t: + buffer = reinterpret_cast<uint8_t*>(memOrShmem.get_uintptr_t()); + break; + case MemoryOrShmem::TShmem: + buffer = memOrShmem.get_Shmem().get<uint8_t>(); + break; + default: + MOZ_ASSERT(false, "Unknown MemoryOrShmem type"); + } + if (!buffer) { + return nullptr; + } + + PlanarYCbCrData pData; + pData.mYSize = descriptor.ySize(); + pData.mYStride = descriptor.yStride(); + pData.mCbCrSize = descriptor.cbCrSize(); + pData.mCbCrStride = descriptor.cbCrStride(); + // default mYSkip, mCbSkip, mCrSkip because not held in YCbCrDescriptor + pData.mYSkip = pData.mCbSkip = pData.mCrSkip = 0; + gfx::IntRect display = descriptor.display(); + pData.mPicX = display.X(); + pData.mPicY = display.Y(); + pData.mPicSize = display.Size(); + pData.mStereoMode = descriptor.stereoMode(); + pData.mColorDepth = descriptor.colorDepth(); + pData.mYUVColorSpace = descriptor.yUVColorSpace(); + pData.mYChannel = ImageDataSerializer::GetYChannel(buffer, descriptor); + pData.mCbChannel = ImageDataSerializer::GetCbChannel(buffer, descriptor); + pData.mCrChannel = ImageDataSerializer::GetCrChannel(buffer, descriptor); + + // images coming from AOMDecoder are RecyclingPlanarYCbCrImages. + RefPtr<RecyclingPlanarYCbCrImage> image = + new RecyclingPlanarYCbCrImage(aBufferRecycleBin); + bool setData = image->CopyData(pData); + MOZ_ASSERT(setData); + + switch (memOrShmem.type()) { + case MemoryOrShmem::Tuintptr_t: + delete[] reinterpret_cast<uint8_t*>(memOrShmem.get_uintptr_t()); + break; + case MemoryOrShmem::TShmem: + // Memory buffer will be recycled by the parent automatically. + break; + default: + MOZ_ASSERT(false, "Unknown MemoryOrShmem type"); + } + + if (!setData) { + return nullptr; + } + + return image.forget(); +} + +already_AddRefed<layers::Image> RemoteImageHolder::TransferToImage( + layers::BufferRecycleBin* aBufferRecycleBin) { + if (IsEmpty()) { + return nullptr; + } + RefPtr<Image> image; + if (mSD->type() == SurfaceDescriptor::TSurfaceDescriptorBuffer) { + image = DeserializeImage(aBufferRecycleBin); + } else { + // The Image here creates a TextureData object that takes ownership + // of the SurfaceDescriptor, and is responsible for making sure that + // it gets deallocated. + SurfaceDescriptorRemoteDecoder remoteSD = + static_cast<const SurfaceDescriptorGPUVideo&>(*mSD); + remoteSD.source() = Some(mSource); + image = new GPUVideoImage(mManager, remoteSD, mSize); + } + mSD = Nothing(); + mManager = nullptr; + + return image.forget(); +} + +RemoteImageHolder::~RemoteImageHolder() { + // GPU Images are held by the RemoteDecoderManagerParent, we didn't get to use + // this image holder (the decoder could have been flushed). We don't need to + // worry about Shmem based image as the Shmem will be automatically re-used + // once the decoder is used again. + if (!IsEmpty() && mManager && + mSD->type() != SurfaceDescriptor::TSurfaceDescriptorBuffer) { + SurfaceDescriptorRemoteDecoder remoteSD = + static_cast<const SurfaceDescriptorGPUVideo&>(*mSD); + mManager->DeallocateSurfaceDescriptor(remoteSD); + } +} + +/* static */ void ipc::IPDLParamTraits<RemoteImageHolder>::Write( + IPC::Message* aMsg, ipc::IProtocol* aActor, RemoteImageHolder&& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.mSource); + WriteIPDLParam(aMsg, aActor, aParam.mSize); + WriteIPDLParam(aMsg, aActor, aParam.mSD); + // Empty this holder. + aParam.mSD = Nothing(); + aParam.mManager = nullptr; +} + +/* static */ bool ipc::IPDLParamTraits<RemoteImageHolder>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, ipc::IProtocol* aActor, + RemoteImageHolder* aResult) { + if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSource) || + !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSize) || + !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSD)) { + return false; + } + if (!aResult->IsEmpty()) { + aResult->mManager = RemoteDecoderManagerChild::GetSingleton( + aResult->mSource == VideoBridgeSource::GpuProcess + ? RemoteDecodeIn::GpuProcess + : RemoteDecodeIn::RddProcess); + } + return true; +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteImageHolder.h b/dom/media/ipc/RemoteImageHolder.h new file mode 100644 index 0000000000..c677b388ad --- /dev/null +++ b/dom/media/ipc/RemoteImageHolder.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_dom_media_RemoteImageHolder_h +#define mozilla_dom_media_RemoteImageHolder_h + +#include "MediaData.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/layers/LayersSurfaces.h" +#include "mozilla/layers/VideoBridgeUtils.h" + +namespace mozilla { +namespace layers { +class BufferRecycleBin; +class IGPUVideoSurfaceManager; +class SurfaceDescriptor; +} // namespace layers +class RemoteImageHolder final { + friend struct ipc::IPDLParamTraits<RemoteImageHolder>; + + public: + RemoteImageHolder(); + RemoteImageHolder(layers::IGPUVideoSurfaceManager* aManager, + layers::VideoBridgeSource aSource, + const gfx::IntSize& aSize, + const layers::SurfaceDescriptor& aSD); + RemoteImageHolder(RemoteImageHolder&& aOther); + // Ensure we never copy this object. + RemoteImageHolder(const RemoteImageHolder& aOther) = delete; + RemoteImageHolder& operator=(const RemoteImageHolder& aOther) = delete; + ~RemoteImageHolder(); + + bool IsEmpty() const { return mSD.isNothing(); } + // Move content of RemoteImageHolder into a usable Image. Ownership is + // transfered to that Image. + already_AddRefed<layers::Image> TransferToImage( + layers::BufferRecycleBin* aBufferRecycleBin = nullptr); + + private: + already_AddRefed<layers::Image> DeserializeImage( + layers::BufferRecycleBin* aBufferRecycleBin); + // We need a default for the default constructor, never used in practice. + layers::VideoBridgeSource mSource = layers::VideoBridgeSource::GpuProcess; + gfx::IntSize mSize; + Maybe<layers::SurfaceDescriptor> mSD; + RefPtr<layers::IGPUVideoSurfaceManager> mManager; +}; + +template <> +struct ipc::IPDLParamTraits<RemoteImageHolder> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + RemoteImageHolder&& aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, RemoteImageHolder* aResult); +}; + +} // namespace mozilla + +#endif // mozilla_dom_media_RemoteImageHolder_h diff --git a/dom/media/ipc/RemoteMediaData.cpp b/dom/media/ipc/RemoteMediaData.cpp new file mode 100644 index 0000000000..1411cae2a2 --- /dev/null +++ b/dom/media/ipc/RemoteMediaData.cpp @@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteMediaData.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/dom/MediaIPCUtils.h" +#include "mozilla/ipc/Shmem.h" + +namespace mozilla { + +bool RemoteArrayOfByteBuffer::AllocateShmem( + size_t aSize, std::function<ShmemBuffer(size_t)>& aAllocator) { + ShmemBuffer buffer = aAllocator(aSize); + if (!buffer.Valid()) { + return false; + } + mBuffers.emplace(std::move(buffer.Get())); + return true; +} + +uint8_t* RemoteArrayOfByteBuffer::BuffersStartAddress() const { + MOZ_ASSERT(mBuffers); + return mBuffers->get<uint8_t>(); +} + +bool RemoteArrayOfByteBuffer::Check(size_t aOffset, size_t aSizeInBytes) const { + return mBuffers && mBuffers->IsReadable() && + detail::IsAddValid(aOffset, aSizeInBytes) && + aOffset + aSizeInBytes <= mBuffers->Size<uint8_t>(); +} + +void RemoteArrayOfByteBuffer::Write(size_t aOffset, const void* aSourceAddr, + size_t aSizeInBytes) { + if (!aSizeInBytes) { + return; + } + MOZ_DIAGNOSTIC_ASSERT(Check(aOffset, aSizeInBytes), + "Allocated Shmem is too small"); + memcpy(BuffersStartAddress() + aOffset, aSourceAddr, aSizeInBytes); +} + +RemoteArrayOfByteBuffer::RemoteArrayOfByteBuffer() = default; + +RemoteArrayOfByteBuffer::RemoteArrayOfByteBuffer( + const nsTArray<RefPtr<MediaByteBuffer>>& aArray, + std::function<ShmemBuffer(size_t)>& aAllocator) { + // Determine the total size we will need for this object. + size_t totalSize = 0; + for (const auto& buffer : aArray) { + if (buffer) { + totalSize += buffer->Length(); + } + } + if (totalSize) { + if (!AllocateShmem(totalSize, aAllocator)) { + return; + } + } + size_t offset = 0; + for (const auto& buffer : aArray) { + size_t sizeBuffer = buffer ? buffer->Length() : 0; + if (totalSize && sizeBuffer) { + Write(offset, buffer->Elements(), sizeBuffer); + } + mOffsets.AppendElement(OffsetEntry{offset, sizeBuffer}); + offset += sizeBuffer; + } + mIsValid = true; +} + +RemoteArrayOfByteBuffer& RemoteArrayOfByteBuffer::operator=( + RemoteArrayOfByteBuffer&& aOther) noexcept { + mIsValid = aOther.mIsValid; + mBuffers = std::move(aOther.mBuffers); + mOffsets = std::move(aOther.mOffsets); + aOther.mIsValid = false; + return *this; +} + +RemoteArrayOfByteBuffer::~RemoteArrayOfByteBuffer() = default; + +already_AddRefed<MediaByteBuffer> RemoteArrayOfByteBuffer::MediaByteBufferAt( + size_t aIndex) const { + MOZ_ASSERT(aIndex < Count()); + const OffsetEntry& entry = mOffsets[aIndex]; + if (!mBuffers || !Get<1>(entry)) { + // It's an empty one. + return nullptr; + } + size_t entrySize = Get<1>(entry); + if (!Check(Get<0>(entry), entrySize)) { + // This Shmem is corrupted and can't contain the data we are about to + // retrieve. We return an empty array instead of asserting to allow for + // recovery. + return nullptr; + } + RefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(entrySize); + buffer->SetLength(entrySize); + memcpy(buffer->Elements(), mBuffers->get<uint8_t>() + Get<0>(entry), + entrySize); + return buffer.forget(); +} + +/*static */ void ipc::IPDLParamTraits<RemoteArrayOfByteBuffer>::Write( + IPC::Message* aMsg, ipc::IProtocol* aActor, + const RemoteArrayOfByteBuffer& aVar) { + WriteIPDLParam(aMsg, aActor, aVar.mIsValid); + // We need the following gymnastic as the Shmem transfered over IPC will be + // revoked. We must create a temporary one instead so that it can be recycled + // later back into the original ShmemPool. + if (aVar.mBuffers) { + WriteIPDLParam(aMsg, aActor, Some(ipc::Shmem(*aVar.mBuffers))); + } else { + WriteIPDLParam(aMsg, aActor, Maybe<ipc::Shmem>()); + } + WriteIPDLParam(aMsg, aActor, aVar.mOffsets); +} + +/* static */ bool ipc::IPDLParamTraits<RemoteArrayOfByteBuffer>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor, RemoteArrayOfByteBuffer* aVar) { + return ReadIPDLParam(aMsg, aIter, aActor, &aVar->mIsValid) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mBuffers) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mOffsets); +} + +bool ArrayOfRemoteMediaRawData::Fill( + const nsTArray<RefPtr<MediaRawData>>& aData, + std::function<ShmemBuffer(size_t)>&& aAllocator) { + nsTArray<AlignedByteBuffer> dataBuffers(aData.Length()); + nsTArray<AlignedByteBuffer> alphaBuffers(aData.Length()); + nsTArray<RefPtr<MediaByteBuffer>> extraDataBuffers(aData.Length()); + for (auto&& entry : aData) { + dataBuffers.AppendElement(std::move(entry->mBuffer)); + alphaBuffers.AppendElement(std::move(entry->mAlphaBuffer)); + extraDataBuffers.AppendElement(std::move(entry->mExtraData)); + mSamples.AppendElement(RemoteMediaRawData{ + MediaDataIPDL(entry->mOffset, entry->mTime, entry->mTimecode, + entry->mDuration, entry->mKeyframe), + entry->mEOS, entry->mDiscardPadding, + entry->mOriginalPresentationWindow}); + } + mBuffers = RemoteArrayOfByteBuffer(dataBuffers, aAllocator); + if (!mBuffers.IsValid()) { + return false; + } + mAlphaBuffers = RemoteArrayOfByteBuffer(alphaBuffers, aAllocator); + if (!mAlphaBuffers.IsValid()) { + return false; + } + mExtraDatas = RemoteArrayOfByteBuffer(extraDataBuffers, aAllocator); + return mExtraDatas.IsValid(); +} + +already_AddRefed<MediaRawData> ArrayOfRemoteMediaRawData::ElementAt( + size_t aIndex) const { + if (!IsValid()) { + return nullptr; + } + MOZ_ASSERT(aIndex < Count()); + MOZ_DIAGNOSTIC_ASSERT(mBuffers.Count() == Count() && + mAlphaBuffers.Count() == Count() && + mExtraDatas.Count() == Count(), + "Something ain't right here"); + const auto& sample = mSamples[aIndex]; + AlignedByteBuffer data = mBuffers.AlignedBufferAt<uint8_t>(aIndex); + if (mBuffers.SizeAt(aIndex) && !data) { + // OOM + return nullptr; + } + AlignedByteBuffer alphaData = mAlphaBuffers.AlignedBufferAt<uint8_t>(aIndex); + if (mAlphaBuffers.SizeAt(aIndex) && !alphaData) { + // OOM + return nullptr; + } + RefPtr<MediaRawData> rawData; + if (mAlphaBuffers.SizeAt(aIndex)) { + rawData = new MediaRawData(std::move(data), std::move(alphaData)); + } else { + rawData = new MediaRawData(std::move(data)); + } + rawData->mOffset = sample.mBase.offset(); + rawData->mTime = sample.mBase.time(); + rawData->mTimecode = sample.mBase.timecode(); + rawData->mDuration = sample.mBase.duration(); + rawData->mKeyframe = sample.mBase.keyframe(); + rawData->mEOS = sample.mEOS; + rawData->mDiscardPadding = sample.mDiscardPadding; + rawData->mExtraData = mExtraDatas.MediaByteBufferAt(aIndex); + return rawData.forget(); +} + +/*static */ void ipc::IPDLParamTraits<ArrayOfRemoteMediaRawData*>::Write( + IPC::Message* aMsg, ipc::IProtocol* aActor, + ArrayOfRemoteMediaRawData* aVar) { + WriteIPDLParam(aMsg, aActor, std::move(aVar->mSamples)); + WriteIPDLParam(aMsg, aActor, std::move(aVar->mBuffers)); + WriteIPDLParam(aMsg, aActor, std::move(aVar->mAlphaBuffers)); + WriteIPDLParam(aMsg, aActor, std::move(aVar->mExtraDatas)); +} + +/* static */ bool ipc::IPDLParamTraits<ArrayOfRemoteMediaRawData*>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor, RefPtr<ArrayOfRemoteMediaRawData>* aVar) { + auto array = MakeRefPtr<ArrayOfRemoteMediaRawData>(); + if (!ReadIPDLParam(aMsg, aIter, aActor, &array->mSamples) || + !ReadIPDLParam(aMsg, aIter, aActor, &array->mBuffers) || + !ReadIPDLParam(aMsg, aIter, aActor, &array->mAlphaBuffers) || + !ReadIPDLParam(aMsg, aIter, aActor, &array->mExtraDatas)) { + return false; + } + *aVar = std::move(array); + return true; +} + +/* static */ void +ipc::IPDLParamTraits<ArrayOfRemoteMediaRawData::RemoteMediaRawData>::Write( + IPC::Message* aMsg, ipc::IProtocol* aActor, const paramType& aVar) { + WriteIPDLParam(aMsg, aActor, aVar.mBase); + WriteIPDLParam(aMsg, aActor, aVar.mEOS); + WriteIPDLParam(aMsg, aActor, aVar.mDiscardPadding); + WriteIPDLParam(aMsg, aActor, aVar.mOriginalPresentationWindow); +} + +/* static */ bool +ipc::IPDLParamTraits<ArrayOfRemoteMediaRawData::RemoteMediaRawData>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, ipc::IProtocol* aActor, + paramType* aVar) { + MediaDataIPDL mBase; + return ReadIPDLParam(aMsg, aIter, aActor, &aVar->mBase) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mEOS) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mDiscardPadding) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mOriginalPresentationWindow); +}; + +bool ArrayOfRemoteAudioData::Fill( + const nsTArray<RefPtr<AudioData>>& aData, + std::function<ShmemBuffer(size_t)>&& aAllocator) { + nsTArray<AlignedAudioBuffer> dataBuffers(aData.Length()); + for (auto&& entry : aData) { + dataBuffers.AppendElement(std::move(entry->mAudioData)); + mSamples.AppendElement(RemoteAudioData{ + MediaDataIPDL(entry->mOffset, entry->mTime, entry->mTimecode, + entry->mDuration, entry->mKeyframe), + entry->mChannels, entry->mRate, uint32_t(entry->mChannelMap), + entry->mOriginalTime, entry->mTrimWindow, entry->mFrames, + entry->mDataOffset}); + } + mBuffers = RemoteArrayOfByteBuffer(dataBuffers, aAllocator); + if (!mBuffers.IsValid()) { + return false; + } + return true; +} + +already_AddRefed<AudioData> ArrayOfRemoteAudioData::ElementAt( + size_t aIndex) const { + if (!IsValid()) { + return nullptr; + } + MOZ_ASSERT(aIndex < Count()); + MOZ_DIAGNOSTIC_ASSERT(mBuffers.Count() == Count(), + "Something ain't right here"); + const auto& sample = mSamples[aIndex]; + AlignedAudioBuffer data = mBuffers.AlignedBufferAt<AudioDataValue>(aIndex); + if (mBuffers.SizeAt(aIndex) && !data) { + // OOM + return nullptr; + } + auto audioData = MakeRefPtr<AudioData>( + sample.mBase.offset(), sample.mBase.time(), std::move(data), + sample.mChannels, sample.mRate, sample.mChannelMap); + // An AudioData's duration is set at construction time based on the size of + // the provided buffer. However, if a trim window is set, this value will be + // incorrect. We have to re-set it to what it actually was. + audioData->mDuration = sample.mBase.duration(); + audioData->mOriginalTime = sample.mOriginalTime; + audioData->mTrimWindow = sample.mTrimWindow; + audioData->mFrames = sample.mFrames; + audioData->mDataOffset = sample.mDataOffset; + return audioData.forget(); +} + +/*static */ void ipc::IPDLParamTraits<ArrayOfRemoteAudioData*>::Write( + IPC::Message* aMsg, ipc::IProtocol* aActor, ArrayOfRemoteAudioData* aVar) { + WriteIPDLParam(aMsg, aActor, std::move(aVar->mSamples)); + WriteIPDLParam(aMsg, aActor, std::move(aVar->mBuffers)); +} + +/* static */ bool ipc::IPDLParamTraits<ArrayOfRemoteAudioData*>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor, RefPtr<ArrayOfRemoteAudioData>* aVar) { + auto array = MakeRefPtr<ArrayOfRemoteAudioData>(); + if (!ReadIPDLParam(aMsg, aIter, aActor, &array->mSamples) || + !ReadIPDLParam(aMsg, aIter, aActor, &array->mBuffers)) { + return false; + } + *aVar = std::move(array); + return true; +} + +/* static */ void +ipc::IPDLParamTraits<ArrayOfRemoteAudioData::RemoteAudioData>::Write( + IPC::Message* aMsg, ipc::IProtocol* aActor, const paramType& aVar) { + WriteIPDLParam(aMsg, aActor, aVar.mBase); + WriteIPDLParam(aMsg, aActor, aVar.mChannels); + WriteIPDLParam(aMsg, aActor, aVar.mRate); + WriteIPDLParam(aMsg, aActor, aVar.mChannelMap); + WriteIPDLParam(aMsg, aActor, aVar.mOriginalTime); + WriteIPDLParam(aMsg, aActor, aVar.mTrimWindow); + WriteIPDLParam(aMsg, aActor, aVar.mFrames); + WriteIPDLParam(aMsg, aActor, aVar.mDataOffset); +} + +/* static */ bool +ipc::IPDLParamTraits<ArrayOfRemoteAudioData::RemoteAudioData>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, ipc::IProtocol* aActor, + paramType* aVar) { + MediaDataIPDL mBase; + if (!ReadIPDLParam(aMsg, aIter, aActor, &aVar->mBase) || + !ReadIPDLParam(aMsg, aIter, aActor, &aVar->mChannels) || + !ReadIPDLParam(aMsg, aIter, aActor, &aVar->mRate) || + !ReadIPDLParam(aMsg, aIter, aActor, &aVar->mChannelMap) || + !ReadIPDLParam(aMsg, aIter, aActor, &aVar->mOriginalTime) || + !ReadIPDLParam(aMsg, aIter, aActor, &aVar->mTrimWindow) || + !ReadIPDLParam(aMsg, aIter, aActor, &aVar->mFrames) || + !ReadIPDLParam(aMsg, aIter, aActor, &aVar->mDataOffset)) { + return false; + } + return true; +}; + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteMediaData.h b/dom/media/ipc/RemoteMediaData.h new file mode 100644 index 0000000000..89a9a25d32 --- /dev/null +++ b/dom/media/ipc/RemoteMediaData.h @@ -0,0 +1,392 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_dom_media_ipc_RemoteMediaData_h +#define mozilla_dom_media_ipc_RemoteMediaData_h + +#include <functional> + +#include "MediaData.h" +#include "PlatformDecoderModule.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/GfxMessageUtils.h" +#include "mozilla/PMediaDecoderParams.h" +#include "mozilla/RemoteImageHolder.h" +#include "mozilla/ShmemPool.h" +#include "mozilla/gfx/Rect.h" + +namespace mozilla { + +class ShmemPool; + +namespace ipc { +class IProtocol; +class Shmem; +} // namespace ipc + +//----------------------------------------------------------------------------- +// Declaration of the IPDL type |struct RemoteVideoData| +// +// We can't use the generated binding in order to use move semantics properly +// (see bug 1664362) +class RemoteVideoData final { + private: + typedef mozilla::gfx::IntSize IntSize; + + public: + RemoteVideoData() = default; + + RemoteVideoData(const MediaDataIPDL& aBase, const IntSize& aDisplay, + RemoteImageHolder&& aImage, int32_t aFrameID) + : mBase(aBase), + mDisplay(aDisplay), + mImage(std::move(aImage)), + mFrameID(aFrameID) {} + + // This is equivalent to the old RemoteVideoDataIPDL object and is similar to + // the RemoteAudioDataIPDL object. To ensure style consistency we use the IPDL + // naming convention here. + MediaDataIPDL& base() { return mBase; } + const MediaDataIPDL& base() const { return mBase; } + + IntSize& display() { return mDisplay; } + const IntSize& display() const { return mDisplay; } + + RemoteImageHolder& image() { return mImage; } + const RemoteImageHolder& image() const { return mImage; } + + int32_t& frameID() { return mFrameID; } + const int32_t& frameID() const { return mFrameID; } + + private: + friend struct ipc::IPDLParamTraits<RemoteVideoData>; + MediaDataIPDL mBase; + IntSize mDisplay; + RemoteImageHolder mImage; + int32_t mFrameID; +}; + +// Until bug 1572054 is resolved, we can't move our objects when using IPDL's +// union or array. They are always copied. So we make the class refcounted to +// and always pass it by pointed to bypass the problem for now. +class ArrayOfRemoteVideoData final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ArrayOfRemoteVideoData) + public: + ArrayOfRemoteVideoData() = default; + ArrayOfRemoteVideoData(ArrayOfRemoteVideoData&& aOther) + : mArray(std::move(aOther.mArray)) {} + explicit ArrayOfRemoteVideoData(nsTArray<RemoteVideoData>&& aOther) + : mArray(std::move(aOther)) {} + ArrayOfRemoteVideoData(const ArrayOfRemoteVideoData& aOther) { + MOZ_CRASH("Should never be used but declared by generated IPDL binding"); + } + ArrayOfRemoteVideoData& operator=(ArrayOfRemoteVideoData&& aOther) noexcept { + if (this != &aOther) { + mArray = std::move(aOther.mArray); + } + return *this; + } + ArrayOfRemoteVideoData& operator=(nsTArray<RemoteVideoData>&& aOther) { + mArray = std::move(aOther); + return *this; + } + + void AppendElements(nsTArray<RemoteVideoData>&& aOther) { + mArray.AppendElements(std::move(aOther)); + } + void Append(RemoteVideoData&& aVideo) { + mArray.AppendElement(std::move(aVideo)); + } + const nsTArray<RemoteVideoData>& Array() const { return mArray; } + nsTArray<RemoteVideoData>& Array() { return mArray; } + + private: + ~ArrayOfRemoteVideoData() = default; + friend struct ipc::IPDLParamTraits<mozilla::ArrayOfRemoteVideoData*>; + nsTArray<RemoteVideoData> mArray; +}; + +/* The class will pack either an array of AlignedBuffer or MediaByteBuffer + * into a single Shmem objects. */ +class RemoteArrayOfByteBuffer { + public: + RemoteArrayOfByteBuffer(); + template <typename Type> + RemoteArrayOfByteBuffer(const nsTArray<AlignedBuffer<Type>>& aArray, + std::function<ShmemBuffer(size_t)>& aAllocator) { + // Determine the total size we will need for this object. + size_t totalSize = 0; + for (auto& buffer : aArray) { + totalSize += buffer.Size(); + } + if (totalSize) { + if (!AllocateShmem(totalSize, aAllocator)) { + return; + } + } + size_t offset = 0; + for (auto& buffer : aArray) { + if (totalSize && buffer && buffer.Size()) { + Write(offset, buffer.Data(), buffer.Size()); + } + mOffsets.AppendElement(OffsetEntry{offset, buffer.Size()}); + offset += buffer.Size(); + } + mIsValid = true; + } + + RemoteArrayOfByteBuffer(const nsTArray<RefPtr<MediaByteBuffer>>& aArray, + std::function<ShmemBuffer(size_t)>& aAllocator); + RemoteArrayOfByteBuffer& operator=(RemoteArrayOfByteBuffer&& aOther) noexcept; + + // Return the packed aIndexth buffer as an AlignedByteBuffer. + // The operation is fallible should an out of memory be encountered. The + // result should be tested accordingly. + template <typename Type> + AlignedBuffer<Type> AlignedBufferAt(size_t aIndex) const { + MOZ_ASSERT(aIndex < Count()); + const OffsetEntry& entry = mOffsets[aIndex]; + size_t entrySize = Get<1>(entry); + if (!mBuffers || !entrySize) { + // It's an empty one. + return AlignedBuffer<Type>(); + } + if (!Check(Get<0>(entry), entrySize)) { + // This Shmem is corrupted and can't contain the data we are about to + // retrieve. We return an empty array instead of asserting to allow for + // recovery. + return AlignedBuffer<Type>(); + } + if (0 != entrySize % sizeof(Type)) { + // There's an error, that entry can't represent this data. + return AlignedBuffer<Type>(); + } + return AlignedBuffer<Type>( + reinterpret_cast<Type*>(BuffersStartAddress() + Get<0>(entry)), + entrySize / sizeof(Type)); + } + + // Return the packed aIndexth buffer as aMediaByteBuffer. + // Will return nullptr if the packed buffer was originally empty. + already_AddRefed<MediaByteBuffer> MediaByteBufferAt(size_t aIndex) const; + // Return the size of the aIndexth buffer. + size_t SizeAt(size_t aIndex) const { return Get<1>(mOffsets[aIndex]); } + // Return false if an out of memory error was encountered during construction. + bool IsValid() const { return mIsValid; }; + // Return the number of buffers packed into this entity. + size_t Count() const { return mOffsets.Length(); } + virtual ~RemoteArrayOfByteBuffer(); + + private: + friend struct ipc::IPDLParamTraits<RemoteArrayOfByteBuffer>; + // Allocate shmem, false if an error occurred. + bool AllocateShmem(size_t aSize, + std::function<ShmemBuffer(size_t)>& aAllocator); + // The starting address of the Shmem + uint8_t* BuffersStartAddress() const; + // Check that the allocated Shmem can contain such range. + bool Check(size_t aOffset, size_t aSizeInBytes) const; + void Write(size_t aOffset, const void* aSourceAddr, size_t aSizeInBytes); + // Set to false is the buffer isn't initialized yet or a memory error occurred + // during construction. + bool mIsValid = false; + // The packed data. The Maybe will be empty if all buffers packed were + // orignally empty. + Maybe<ipc::Shmem> mBuffers; + // The offset to the start of the individual buffer and its size (all in + // bytes) + typedef Tuple<size_t, size_t> OffsetEntry; + nsTArray<OffsetEntry> mOffsets; +}; + +/* The class will pack an array of MediaRawData using at most three Shmem + * objects. Under the most common scenaria, only two Shmems will be used as + * there are few videos with an alpha channel in the wild. + * We unfortunately can't populate the array at construction nor present an + * interface similar to an actual nsTArray or the ArrayOfRemoteVideoData above + * as currently IPC serialization is always non-fallible. So we must create the + * object first, fill it to determine if we ran out of memory and then send the + * object over IPC. + */ +class ArrayOfRemoteMediaRawData { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ArrayOfRemoteMediaRawData) + public: + // Fill the content, return false if an OOM occurred. + bool Fill(const nsTArray<RefPtr<MediaRawData>>& aData, + std::function<ShmemBuffer(size_t)>&& aAllocator); + + // Return the aIndexth MediaRawData or nullptr if a memory error occurred. + already_AddRefed<MediaRawData> ElementAt(size_t aIndex) const; + + // Return the number of MediaRawData stored in this container. + size_t Count() const { return mSamples.Length(); } + bool IsEmpty() const { return Count() == 0; } + bool IsValid() const { + return mBuffers.IsValid() && mAlphaBuffers.IsValid() && + mExtraDatas.IsValid(); + } + + struct RemoteMediaRawData { + MediaDataIPDL mBase; + bool mEOS; + uint32_t mDiscardPadding; + Maybe<media::TimeInterval> mOriginalPresentationWindow; + }; + + private: + friend struct ipc::IPDLParamTraits<ArrayOfRemoteMediaRawData*>; + virtual ~ArrayOfRemoteMediaRawData() = default; + + nsTArray<RemoteMediaRawData> mSamples; + RemoteArrayOfByteBuffer mBuffers; + RemoteArrayOfByteBuffer mAlphaBuffers; + RemoteArrayOfByteBuffer mExtraDatas; +}; + +/* The class will pack an array of MediaAudioData using at most a single Shmem + * objects. + * We unfortunately can't populate the array at construction nor present an + * interface similar to an actual nsTArray or the ArrayOfRemoteVideoData above + * as currently IPC serialization is always non-fallible. So we must create the + * object first, fill it to determine if we ran out of memory and then send the + * object over IPC. + */ +class ArrayOfRemoteAudioData final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ArrayOfRemoteAudioData) + public: + // Fill the content, return false if an OOM occurred. + bool Fill(const nsTArray<RefPtr<AudioData>>& aData, + std::function<ShmemBuffer(size_t)>&& aAllocator); + + // Return the aIndexth MediaRawData or nullptr if a memory error occurred. + already_AddRefed<AudioData> ElementAt(size_t aIndex) const; + + // Return the number of MediaRawData stored in this container. + size_t Count() const { return mSamples.Length(); } + bool IsEmpty() const { return Count() == 0; } + bool IsValid() const { return mBuffers.IsValid(); } + + struct RemoteAudioData { + friend struct ipc::IPDLParamTraits<RemoteVideoData>; + MediaDataIPDL mBase; + uint32_t mChannels; + uint32_t mRate; + uint32_t mChannelMap; + media::TimeUnit mOriginalTime; + Maybe<media::TimeInterval> mTrimWindow; + uint32_t mFrames; + size_t mDataOffset; + }; + + private: + friend struct ipc::IPDLParamTraits<ArrayOfRemoteAudioData*>; + ~ArrayOfRemoteAudioData() = default; + + nsTArray<RemoteAudioData> mSamples; + RemoteArrayOfByteBuffer mBuffers; +}; + +namespace ipc { + +template <> +struct IPDLParamTraits<RemoteVideoData> { + typedef RemoteVideoData paramType; + static void Write(IPC::Message* aMsg, ipc::IProtocol* aActor, + paramType&& aVar) { + WriteIPDLParam(aMsg, aActor, std::move(aVar.mBase)); + WriteIPDLParam(aMsg, aActor, std::move(aVar.mDisplay)); + WriteIPDLParam(aMsg, aActor, std::move(aVar.mImage)); + aMsg->WriteBytes(&aVar.mFrameID, 4); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor, paramType* aVar) { + if (!ReadIPDLParam(aMsg, aIter, aActor, &aVar->mBase) || + !ReadIPDLParam(aMsg, aIter, aActor, &aVar->mDisplay) || + !ReadIPDLParam(aMsg, aIter, aActor, &aVar->mImage) || + !aMsg->ReadBytesInto(aIter, &aVar->mFrameID, 4)) { + return false; + } + return true; + } +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteVideoData*> { + typedef ArrayOfRemoteVideoData paramType; + static void Write(IPC::Message* aMsg, mozilla::ipc::IProtocol* aActor, + paramType* aVar) { + WriteIPDLParam(aMsg, aActor, std::move(aVar->mArray)); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + ipc::IProtocol* aActor, RefPtr<paramType>* aVar) { + nsTArray<RemoteVideoData> array; + if (!ReadIPDLParam(aMsg, aIter, aActor, &array)) { + return false; + } + auto results = MakeRefPtr<ArrayOfRemoteVideoData>(std::move(array)); + *aVar = std::move(results); + return true; + } +}; + +template <> +struct IPDLParamTraits<RemoteArrayOfByteBuffer> { + typedef RemoteArrayOfByteBuffer paramType; + // We do not want to move the RemoteArrayOfByteBuffer as we want to recycle + // the shmem it contains for another time. + static void Write(IPC::Message* aMsg, ipc::IProtocol* aActor, + const paramType& aVar); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + ipc::IProtocol* aActor, paramType* aVar); +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteMediaRawData::RemoteMediaRawData> { + typedef ArrayOfRemoteMediaRawData::RemoteMediaRawData paramType; + static void Write(IPC::Message* aMsg, ipc::IProtocol* aActor, + const paramType& aVar); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + ipc::IProtocol* aActor, paramType* aVar); +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteMediaRawData*> { + typedef ArrayOfRemoteMediaRawData paramType; + static void Write(IPC::Message* aMsg, ipc::IProtocol* aActor, + paramType* aVar); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + ipc::IProtocol* aActor, RefPtr<paramType>* aVar); +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteAudioData::RemoteAudioData> { + typedef ArrayOfRemoteAudioData::RemoteAudioData paramType; + static void Write(IPC::Message* aMsg, ipc::IProtocol* aActor, + const paramType& aVar); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + ipc::IProtocol* aActor, paramType* aVar); +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteAudioData*> { + typedef ArrayOfRemoteAudioData paramType; + static void Write(IPC::Message* aMsg, ipc::IProtocol* aActor, + paramType* aVar); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + ipc::IProtocol* aActor, RefPtr<paramType>* aVar); +}; +} // namespace ipc + +} // namespace mozilla + +#endif // mozilla_dom_media_ipc_RemoteMediaData_h diff --git a/dom/media/ipc/RemoteMediaDataDecoder.cpp b/dom/media/ipc/RemoteMediaDataDecoder.cpp new file mode 100644 index 0000000000..740f38721c --- /dev/null +++ b/dom/media/ipc/RemoteMediaDataDecoder.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteMediaDataDecoder.h" + +#include "RemoteDecoderChild.h" +#include "RemoteDecoderManagerChild.h" + +namespace mozilla { + +RemoteMediaDataDecoder::RemoteMediaDataDecoder(RemoteDecoderChild* aChild) + : mChild(aChild) {} + +RemoteMediaDataDecoder::~RemoteMediaDataDecoder() { + if (mChild) { + // Shutdown didn't get called. This can happen if the creation of the + // decoder got interrupted while pending. + nsCOMPtr<nsISerialEventTarget> thread = + RemoteDecoderManagerChild::GetManagerThread(); + MOZ_ASSERT(thread); + thread->Dispatch(NS_NewRunnableFunction( + "RemoteMediaDataDecoderShutdown", [child = std::move(mChild), thread] { + child->Shutdown()->Then( + thread, __func__, + [child](const ShutdownPromise::ResolveOrRejectValue& aValue) { + child->DestroyIPDL(); + }); + })); + } +} + +RefPtr<MediaDataDecoder::InitPromise> RemoteMediaDataDecoder::Init() { + RefPtr<RemoteMediaDataDecoder> self = this; + return InvokeAsync(RemoteDecoderManagerChild::GetManagerThread(), __func__, + [self]() { return self->mChild->Init(); }) + ->Then( + RemoteDecoderManagerChild::GetManagerThread(), __func__, + [self, this](TrackType aTrack) { + // If shutdown has started in the meantime shutdown promise may + // be resloved before this task. In this case mChild will be null + // and the init promise has to be canceled. + if (!mChild) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, + __func__); + } + mDescription = mChild->GetDescriptionName(); + mIsHardwareAccelerated = + mChild->IsHardwareAccelerated(mHardwareAcceleratedReason); + mConversion = mChild->NeedsConversion(); + return InitPromise::CreateAndResolve(aTrack, __func__); + }, + [self](const MediaResult& aError) { + return InitPromise::CreateAndReject(aError, __func__); + }); +} + +RefPtr<MediaDataDecoder::DecodePromise> RemoteMediaDataDecoder::Decode( + MediaRawData* aSample) { + RefPtr<RemoteMediaDataDecoder> self = this; + RefPtr<MediaRawData> sample = aSample; + return InvokeAsync( + RemoteDecoderManagerChild::GetManagerThread(), __func__, + [self, sample]() { + return self->mChild->Decode(nsTArray<RefPtr<MediaRawData>>{sample}); + }); +} + +RefPtr<MediaDataDecoder::DecodePromise> RemoteMediaDataDecoder::DecodeBatch( + nsTArray<RefPtr<MediaRawData>>&& aSamples) { + RefPtr<RemoteMediaDataDecoder> self = this; + return InvokeAsync(RemoteDecoderManagerChild::GetManagerThread(), __func__, + [self, samples = std::move(aSamples)]() { + return self->mChild->Decode(samples); + }); +} + +RefPtr<MediaDataDecoder::FlushPromise> RemoteMediaDataDecoder::Flush() { + RefPtr<RemoteMediaDataDecoder> self = this; + return InvokeAsync(RemoteDecoderManagerChild::GetManagerThread(), __func__, + [self]() { return self->mChild->Flush(); }); +} + +RefPtr<MediaDataDecoder::DecodePromise> RemoteMediaDataDecoder::Drain() { + RefPtr<RemoteMediaDataDecoder> self = this; + return InvokeAsync(RemoteDecoderManagerChild::GetManagerThread(), __func__, + [self]() { return self->mChild->Drain(); }); +} + +RefPtr<ShutdownPromise> RemoteMediaDataDecoder::Shutdown() { + RefPtr<RemoteMediaDataDecoder> self = this; + return InvokeAsync( + RemoteDecoderManagerChild::GetManagerThread(), __func__, [self]() { + RefPtr<ShutdownPromise> p = self->mChild->Shutdown(); + + // We're about to be destroyed and drop our ref to + // *DecoderChild. Make sure we put a ref into the + // task queue for the *DecoderChild thread to keep + // it alive until we send the delete message. + p->Then(RemoteDecoderManagerChild::GetManagerThread(), __func__, + [child = std::move(self->mChild)]( + const ShutdownPromise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(aValue.IsResolve()); + child->DestroyIPDL(); + return ShutdownPromise::CreateAndResolveOrReject(aValue, + __func__); + }); + return p; + }); +} + +bool RemoteMediaDataDecoder::IsHardwareAccelerated( + nsACString& aFailureReason) const { + aFailureReason = mHardwareAcceleratedReason; + return mIsHardwareAccelerated; +} + +void RemoteMediaDataDecoder::SetSeekThreshold(const media::TimeUnit& aTime) { + RefPtr<RemoteMediaDataDecoder> self = this; + media::TimeUnit time = aTime; + RemoteDecoderManagerChild::GetManagerThread()->Dispatch( + NS_NewRunnableFunction("dom::RemoteMediaDataDecoder::SetSeekThreshold", + [=]() { + MOZ_ASSERT(self->mChild); + self->mChild->SetSeekThreshold(time); + }), + NS_DISPATCH_NORMAL); +} + +MediaDataDecoder::ConversionRequired RemoteMediaDataDecoder::NeedsConversion() + const { + return mConversion; +} + +nsCString RemoteMediaDataDecoder::GetDescriptionName() const { + return mDescription; +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteMediaDataDecoder.h b/dom/media/ipc/RemoteMediaDataDecoder.h new file mode 100644 index 0000000000..6e5ff8c406 --- /dev/null +++ b/dom/media/ipc/RemoteMediaDataDecoder.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 include_dom_media_ipc_RemoteMediaDataDecoder_h +#define include_dom_media_ipc_RemoteMediaDataDecoder_h +#include "PlatformDecoderModule.h" + +#include "MediaData.h" + +namespace mozilla { + +class RemoteDecoderChild; +class RemoteDecoderManagerChild; +class RemoteMediaDataDecoder; + +DDLoggedTypeCustomNameAndBase(RemoteMediaDataDecoder, RemoteMediaDataDecoder, + MediaDataDecoder); + +// A MediaDataDecoder implementation that proxies through IPDL +// to a 'real' decoder in the GPU or RDD process. +// All requests get forwarded to a *DecoderChild instance that +// operates solely on the provided manager and abstract manager threads. +class RemoteMediaDataDecoder + : public MediaDataDecoder, + public DecoderDoctorLifeLogger<RemoteMediaDataDecoder> { + public: + explicit RemoteMediaDataDecoder(RemoteDecoderChild* aChild); + + // MediaDataDecoder + RefPtr<InitPromise> Init() override; + RefPtr<DecodePromise> Decode(MediaRawData* aSample) override; + bool CanDecodeBatch() const override { return true; } + RefPtr<DecodePromise> DecodeBatch( + nsTArray<RefPtr<MediaRawData>>&& aSamples) override; + RefPtr<DecodePromise> Drain() override; + RefPtr<FlushPromise> Flush() override; + RefPtr<ShutdownPromise> Shutdown() override; + bool IsHardwareAccelerated(nsACString& aFailureReason) const override; + void SetSeekThreshold(const media::TimeUnit& aTime) override; + nsCString GetDescriptionName() const override; + ConversionRequired NeedsConversion() const override; + + private: + ~RemoteMediaDataDecoder(); + + // Only ever written to from the reader task queue (during the constructor and + // destructor when we can guarantee no other threads are accessing it). Only + // read from the manager thread. + RefPtr<RemoteDecoderChild> mChild; + // Only ever written/modified during decoder initialisation. + // As such can be accessed from any threads after that. + nsCString mDescription = "RemoteMediaDataDecoder"_ns; + bool mIsHardwareAccelerated = false; + nsCString mHardwareAcceleratedReason; + ConversionRequired mConversion = ConversionRequired::kNeedNone; +}; + +} // namespace mozilla + +#endif // include_dom_media_ipc_RemoteMediaDataDecoder_h diff --git a/dom/media/ipc/RemoteVideoDecoder.cpp b/dom/media/ipc/RemoteVideoDecoder.cpp new file mode 100644 index 0000000000..ad65cd8d25 --- /dev/null +++ b/dom/media/ipc/RemoteVideoDecoder.cpp @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteVideoDecoder.h" + +#include "mozilla/layers/ImageDataSerializer.h" + +#ifdef MOZ_AV1 +# include "AOMDecoder.h" +# include "DAV1DDecoder.h" +#endif +#ifdef XP_WIN +# include "WMFDecoderModule.h" +#endif +#include "GPUVideoImage.h" +#include "ImageContainer.h" // for PlanarYCbCrData and BufferRecycleBin +#include "MediaDataDecoderProxy.h" +#include "MediaInfo.h" +#include "PDMFactory.h" +#include "RemoteDecoderManagerParent.h" +#include "RemoteImageHolder.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/Telemetry.h" +#include "mozilla/layers/ImageClient.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/VideoBridgeChild.h" + +namespace mozilla { + +using namespace layers; // for PlanarYCbCrData and BufferRecycleBin +using namespace ipc; +using namespace gfx; + +layers::TextureForwarder* KnowsCompositorVideo::GetTextureForwarder() { + auto* vbc = VideoBridgeChild::GetSingleton(); + return (vbc && vbc->CanSend()) ? vbc : nullptr; +} +layers::LayersIPCActor* KnowsCompositorVideo::GetLayersIPCActor() { + return GetTextureForwarder(); +} + +/* static */ already_AddRefed<KnowsCompositorVideo> +KnowsCompositorVideo::TryCreateForIdentifier( + const layers::TextureFactoryIdentifier& aIdentifier) { + VideoBridgeChild* child = VideoBridgeChild::GetSingleton(); + if (!child) { + return nullptr; + } + + RefPtr<KnowsCompositorVideo> knowsCompositor = new KnowsCompositorVideo(); + knowsCompositor->IdentifyTextureHost(aIdentifier); + return knowsCompositor.forget(); +} + +RemoteVideoDecoderChild::RemoteVideoDecoderChild(RemoteDecodeIn aLocation) + : RemoteDecoderChild(aLocation), mBufferRecycleBin(new BufferRecycleBin) {} + +MediaResult RemoteVideoDecoderChild::ProcessOutput( + DecodedOutputIPDL&& aDecodedData) { + AssertOnManagerThread(); + MOZ_ASSERT(aDecodedData.type() == DecodedOutputIPDL::TArrayOfRemoteVideoData); + + nsTArray<RemoteVideoData>& arrayData = + aDecodedData.get_ArrayOfRemoteVideoData()->Array(); + + for (auto&& data : arrayData) { + if (data.image().IsEmpty()) { + // This is a NullData object. + mDecodedData.AppendElement(MakeRefPtr<NullData>( + data.base().offset(), data.base().time(), data.base().duration())); + continue; + } + RefPtr<Image> image = data.image().TransferToImage(mBufferRecycleBin); + + RefPtr<VideoData> video = VideoData::CreateFromImage( + data.display(), data.base().offset(), data.base().time(), + data.base().duration(), image, data.base().keyframe(), + data.base().timecode()); + + if (!video) { + // OOM + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); + } + mDecodedData.AppendElement(std::move(video)); + } + return NS_OK; +} + +MediaResult RemoteVideoDecoderChild::InitIPDL( + const VideoInfo& aVideoInfo, float aFramerate, + const CreateDecoderParams::OptionSet& aOptions, + Maybe<layers::TextureFactoryIdentifier> aIdentifier) { + MOZ_ASSERT_IF(mLocation == RemoteDecodeIn::GpuProcess, aIdentifier); + + RefPtr<RemoteDecoderManagerChild> manager = + RemoteDecoderManagerChild::GetSingleton(mLocation); + + // The manager isn't available because RemoteDecoderManagerChild has been + // initialized with null end points and we don't want to decode video on RDD + // process anymore. Return false here so that we can fallback to other PDMs. + if (!manager) { + return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("RemoteDecoderManager is not available.")); + } + + if (!manager->CanSend()) { + if (mLocation == RemoteDecodeIn::GpuProcess) { + // The manager doesn't support sending messages because we've just crashed + // and are working on reinitialization. Don't initialize mIPDLSelfRef and + // leave us in an error state. We'll then immediately reject the promise + // when Init() is called and the caller can try again. Hopefully by then + // the new manager is ready, or we've notified the caller of it being no + // longer available. If not, then the cycle repeats until we're ready. + return NS_OK; + } + + return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("RemoteDecoderManager unable to send.")); + } + + mIPDLSelfRef = this; + VideoDecoderInfoIPDL decoderInfo(aVideoInfo, aFramerate); + Unused << manager->SendPRemoteDecoderConstructor(this, decoderInfo, aOptions, + aIdentifier); + + return NS_OK; +} + +RemoteVideoDecoderParent::RemoteVideoDecoderParent( + RemoteDecoderManagerParent* aParent, const VideoInfo& aVideoInfo, + float aFramerate, const CreateDecoderParams::OptionSet& aOptions, + const Maybe<layers::TextureFactoryIdentifier>& aIdentifier, + nsISerialEventTarget* aManagerThread, TaskQueue* aDecodeTaskQueue) + : RemoteDecoderParent(aParent, aOptions, aManagerThread, aDecodeTaskQueue), + mVideoInfo(aVideoInfo), + mFramerate(aFramerate) { + if (aIdentifier) { + // Check to see if we have a direct PVideoBridge connection to the + // destination process specified in aIdentifier, and create a + // KnowsCompositor representing that connection if so. If this fails, then + // we fall back to returning the decoded frames directly via Output(). + mKnowsCompositor = + KnowsCompositorVideo::TryCreateForIdentifier(*aIdentifier); + } +} + +IPCResult RemoteVideoDecoderParent::RecvConstruct( + ConstructResolver&& aResolver) { + auto imageContainer = MakeRefPtr<layers::ImageContainer>(); + if (mKnowsCompositor && XRE_IsRDDProcess()) { + // Ensure to allocate recycle allocator + imageContainer->EnsureRecycleAllocatorForRDD(mKnowsCompositor); + } + auto params = CreateDecoderParams{ + mVideoInfo, mKnowsCompositor, + imageContainer, CreateDecoderParams::VideoFrameRate(mFramerate), + mOptions, CreateDecoderParams::NoWrapper(true), + }; + + mParent->EnsurePDMFactory().CreateDecoder(params)->Then( + GetCurrentSerialEventTarget(), __func__, + [resolver = std::move(aResolver), self = RefPtr{this}]( + PlatformDecoderModule::CreateDecoderPromise::ResolveOrRejectValue&& + aValue) { + if (aValue.IsReject()) { + resolver(aValue.RejectValue()); + return; + } + MOZ_ASSERT(aValue.ResolveValue()); + self->mDecoder = + new MediaDataDecoderProxy(aValue.ResolveValue().forget(), + do_AddRef(self->mDecodeTaskQueue.get())); + resolver(NS_OK); + }); + return IPC_OK(); +} + +MediaResult RemoteVideoDecoderParent::ProcessDecodedData( + MediaDataDecoder::DecodedData&& aData, DecodedOutputIPDL& aDecodedData) { + MOZ_ASSERT(OnManagerThread()); + + // If the video decoder bridge has shut down, stop. + if (mKnowsCompositor && !mKnowsCompositor->GetTextureForwarder()) { + aDecodedData = MakeRefPtr<ArrayOfRemoteVideoData>(); + return NS_OK; + } + + nsTArray<RemoteVideoData> array; + + for (const auto& data : aData) { + MOZ_ASSERT(data->mType == MediaData::Type::VIDEO_DATA || + data->mType == MediaData::Type::NULL_DATA, + "Can only decode videos using RemoteDecoderParent!"); + if (data->mType == MediaData::Type::NULL_DATA) { + RemoteVideoData output( + MediaDataIPDL(data->mOffset, data->mTime, data->mTimecode, + data->mDuration, data->mKeyframe), + IntSize(), RemoteImageHolder(), -1); + + array.AppendElement(std::move(output)); + continue; + } + VideoData* video = static_cast<VideoData*>(data.get()); + + MOZ_ASSERT(video->mImage, + "Decoded video must output a layer::Image to " + "be used with RemoteDecoderParent"); + + RefPtr<TextureClient> texture; + SurfaceDescriptor sd; + IntSize size; + bool needStorage = false; + + if (mKnowsCompositor) { + texture = video->mImage->GetTextureClient(mKnowsCompositor); + + if (!texture) { + texture = ImageClient::CreateTextureClientForImage(video->mImage, + mKnowsCompositor); + } + + if (texture) { + if (!texture->IsAddedToCompositableClient()) { + texture->InitIPDLActor(mKnowsCompositor); + texture->SetAddedToCompositableClient(); + } + needStorage = true; + SurfaceDescriptorRemoteDecoder remoteSD; + texture->GetSurfaceDescriptorRemoteDecoder(&remoteSD); + sd = remoteSD; + size = texture->GetSize(); + } + } + + // If failed to create a GPU accelerated surface descriptor, fall back to + // copying frames via shmem. + if (!IsSurfaceDescriptorValid(sd)) { + needStorage = false; + PlanarYCbCrImage* image = video->mImage->AsPlanarYCbCrImage(); + if (!image) { + return MediaResult(NS_ERROR_UNEXPECTED, + "Expected Planar YCbCr image in " + "RemoteVideoDecoderParent::ProcessDecodedData"); + } + + SurfaceDescriptorBuffer sdBuffer; + ShmemBuffer buffer = AllocateBuffer(image->GetDataSize()); + if (!buffer.Valid()) { + return MediaResult(NS_ERROR_OUT_OF_MEMORY, + "AllocShmem failed in " + "RemoteVideoDecoderParent::ProcessDecodedData"); + } + + sdBuffer.data() = std::move(buffer.Get()); + image->BuildSurfaceDescriptorBuffer(sdBuffer); + + sd = sdBuffer; + size = image->GetSize(); + } + + if (needStorage) { + MOZ_ASSERT(sd.type() != SurfaceDescriptor::TSurfaceDescriptorBuffer); + mParent->StoreImage(static_cast<const SurfaceDescriptorGPUVideo&>(sd), + video->mImage, texture); + } + + RemoteVideoData output( + MediaDataIPDL(data->mOffset, data->mTime, data->mTimecode, + data->mDuration, data->mKeyframe), + video->mDisplay, + RemoteImageHolder(mParent, + XRE_IsGPUProcess() ? VideoBridgeSource::GpuProcess + : VideoBridgeSource::RddProcess, + size, sd), + video->mFrameID); + + array.AppendElement(std::move(output)); + } + + aDecodedData = MakeRefPtr<ArrayOfRemoteVideoData>(std::move(array)); + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteVideoDecoder.h b/dom/media/ipc/RemoteVideoDecoder.h new file mode 100644 index 0000000000..52bcdca9be --- /dev/null +++ b/dom/media/ipc/RemoteVideoDecoder.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 include_dom_media_ipc_RemoteVideoDecoderChild_h +#define include_dom_media_ipc_RemoteVideoDecoderChild_h +#include "RemoteDecoderChild.h" +#include "RemoteDecoderManagerChild.h" +#include "RemoteDecoderParent.h" + +namespace mozilla { +namespace layers { +class BufferRecycleBin; +} // namespace layers +} // namespace mozilla + +namespace mozilla { + +class KnowsCompositorVideo : public layers::KnowsCompositor { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(KnowsCompositorVideo, override) + + layers::TextureForwarder* GetTextureForwarder() override; + layers::LayersIPCActor* GetLayersIPCActor() override; + + static already_AddRefed<KnowsCompositorVideo> TryCreateForIdentifier( + const layers::TextureFactoryIdentifier& aIdentifier); + + private: + KnowsCompositorVideo() = default; + virtual ~KnowsCompositorVideo() = default; +}; + +using mozilla::ipc::IPCResult; + +class RemoteVideoDecoderChild : public RemoteDecoderChild { + public: + explicit RemoteVideoDecoderChild(RemoteDecodeIn aLocation); + + MOZ_IS_CLASS_INIT MediaResult + InitIPDL(const VideoInfo& aVideoInfo, float aFramerate, + const CreateDecoderParams::OptionSet& aOptions, + mozilla::Maybe<layers::TextureFactoryIdentifier> aIdentifier); + + MediaResult ProcessOutput(DecodedOutputIPDL&& aDecodedData) override; + + private: + RefPtr<mozilla::layers::BufferRecycleBin> mBufferRecycleBin; +}; + +class RemoteVideoDecoderParent final : public RemoteDecoderParent { + public: + RemoteVideoDecoderParent( + RemoteDecoderManagerParent* aParent, const VideoInfo& aVideoInfo, + float aFramerate, const CreateDecoderParams::OptionSet& aOptions, + const Maybe<layers::TextureFactoryIdentifier>& aIdentifier, + nsISerialEventTarget* aManagerThread, TaskQueue* aDecodeTaskQueue); + + protected: + IPCResult RecvConstruct(ConstructResolver&& aResolver) override; + + MediaResult ProcessDecodedData(MediaDataDecoder::DecodedData&& aData, + DecodedOutputIPDL& aDecodedData) override; + + private: + // Can only be accessed from the manager thread + // Note: we can't keep a reference to the original VideoInfo here + // because unlike in typical MediaDataDecoder situations, we're being + // passed a deserialized VideoInfo from RecvPRemoteDecoderConstructor + // which is destroyed when RecvPRemoteDecoderConstructor returns. + const VideoInfo mVideoInfo; + const float mFramerate; + RefPtr<KnowsCompositorVideo> mKnowsCompositor; +}; + +} // namespace mozilla + +#endif // include_dom_media_ipc_RemoteVideoDecoderChild_h diff --git a/dom/media/ipc/ShmemRecycleAllocator.h b/dom/media/ipc/ShmemRecycleAllocator.h new file mode 100644 index 0000000000..7207e27014 --- /dev/null +++ b/dom/media/ipc/ShmemRecycleAllocator.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 include_dom_media_ipc_ShmemRecycleAllocator_h +#define include_dom_media_ipc_ShmemRecycleAllocator_h + +#include "mozilla/ShmemPool.h" + +namespace mozilla { + +template <class T> +class ShmemRecycleAllocator { + public: + explicit ShmemRecycleAllocator(T* aActor) + : mActor(aActor), mPool(1, ShmemPool::PoolType::DynamicPool) {} + ShmemBuffer AllocateBuffer(size_t aSize, + ShmemPool::AllocationPolicy aPolicy = + ShmemPool::AllocationPolicy::Unsafe) { + ShmemBuffer buffer = mPool.Get(mActor, aSize, aPolicy); + if (!buffer.Valid()) { + return buffer; + } + MOZ_DIAGNOSTIC_ASSERT(aSize <= buffer.Get().Size<uint8_t>()); + mUsedShmems.AppendElement(buffer.Get()); + mNeedCleanup = true; + return buffer; + } + + void ReleaseBuffer(ShmemBuffer&& aBuffer) { mPool.Put(std::move(aBuffer)); } + + void ReleaseAllBuffers() { + for (auto&& mem : mUsedShmems) { + ReleaseBuffer(ShmemBuffer(mem.Get())); + } + mUsedShmems.Clear(); + } + + void CleanupShmemRecycleAllocator() { + ReleaseAllBuffers(); + mPool.Cleanup(mActor); + mNeedCleanup = false; + } + + ~ShmemRecycleAllocator() { + MOZ_DIAGNOSTIC_ASSERT(mUsedShmems.IsEmpty() && !mNeedCleanup, + "Shmems not all deallocated"); + } + + private: + T* const mActor; + ShmemPool mPool; + AutoTArray<ShmemBuffer, 4> mUsedShmems; + bool mNeedCleanup = false; +}; + +} // namespace mozilla + +#endif // include_dom_media_ipc_ShmemRecycleAllocator_h diff --git a/dom/media/ipc/moz.build b/dom/media/ipc/moz.build new file mode 100644 index 0000000000..031cebc41d --- /dev/null +++ b/dom/media/ipc/moz.build @@ -0,0 +1,65 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. + + +IPDL_SOURCES += [ + "PMediaDecoderParams.ipdlh", + "PRDD.ipdl", + "PRemoteDecoder.ipdl", + "PRemoteDecoderManager.ipdl", +] + +EXPORTS.mozilla += [ + "RDDChild.h", + "RDDParent.h", + "RDDProcessHost.h", + "RDDProcessImpl.h", + "RDDProcessManager.h", + "RemoteDecoderChild.h", + "RemoteDecoderManagerChild.h", + "RemoteDecoderManagerParent.h", + "RemoteDecoderModule.h", + "RemoteDecoderParent.h", + "RemoteImageHolder.h", + "RemoteMediaData.h", + "RemoteMediaDataDecoder.h", + "ShmemRecycleAllocator.h", +] + +EXPORTS.mozilla.dom += [ + "MediaIPCUtils.h", +] + +SOURCES += [ + "RDDChild.cpp", + "RDDParent.cpp", + "RDDProcessHost.cpp", + "RDDProcessImpl.cpp", + "RDDProcessManager.cpp", + "RemoteAudioDecoder.cpp", + "RemoteDecoderChild.cpp", + "RemoteDecoderManagerChild.cpp", + "RemoteDecoderManagerParent.cpp", + "RemoteDecoderModule.cpp", + "RemoteDecoderParent.cpp", + "RemoteImageHolder.cpp", + "RemoteMediaData.cpp", + "RemoteMediaDataDecoder.cpp", + "RemoteVideoDecoder.cpp", +] + +# so we can include nsMacUtilsImpl.h in RDDParent.cpp for sandboxing +LOCAL_INCLUDES += [ + "/xpcom/base", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") + + +FINAL_LIBRARY = "xul" |