diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/media/ipc | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/ipc')
52 files changed, 11084 insertions, 0 deletions
diff --git a/dom/media/ipc/MFCDMChild.cpp b/dom/media/ipc/MFCDMChild.cpp new file mode 100644 index 0000000000..aedae5bc36 --- /dev/null +++ b/dom/media/ipc/MFCDMChild.cpp @@ -0,0 +1,449 @@ +/* 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 "MFCDMChild.h" + +#include "mozilla/EMEUtils.h" +#include "mozilla/KeySystemConfig.h" +#include "mozilla/RefPtr.h" +#include "mozilla/WMFCDMProxyCallback.h" +#include "nsString.h" +#include "RemoteDecoderManagerChild.h" + +namespace mozilla { + +#define LOG(msg, ...) \ + EME_LOG("MFCDMChild[%p]@%s: " msg, this, __func__, ##__VA_ARGS__) +#define SLOG(msg, ...) EME_LOG("MFCDMChild@%s: " msg, __func__, ##__VA_ARGS__) + +#define HANDLE_PENDING_PROMISE(method, callsite, promise, promiseId) \ + do { \ + promise->Then( \ + self->mManagerThread, callsite, \ + [self, promiseId, callsite]( \ + PMFCDMChild::method##Promise::ResolveOrRejectValue&& result) { \ + auto iter = self->mPendingGenericPromises.find(promiseId); \ + if (iter == self->mPendingGenericPromises.end()) { \ + return; \ + } \ + auto& promiseHolder = iter->second; \ + if (result.IsResolve()) { \ + if (NS_SUCCEEDED(result.ResolveValue())) { \ + promiseHolder.ResolveIfExists(true, callsite); \ + } else { \ + promiseHolder.RejectIfExists(result.ResolveValue(), callsite); \ + } \ + } else { \ + /* IPC die */ \ + promiseHolder.RejectIfExists(NS_ERROR_FAILURE, callsite); \ + } \ + self->mPendingGenericPromises.erase(iter); \ + }); \ + } while (0) + +#define INVOKE_ASYNC(method, promiseId, param1) \ + do { \ + auto callsite = __func__; \ + using ParamType = std::remove_reference<decltype(param1)>::type; \ + mManagerThread->Dispatch(NS_NewRunnableFunction( \ + callsite, [self = RefPtr{this}, callsite, promiseId, \ + param_1 = std::forward<ParamType>(param1)] { \ + auto p = self->Send##method(param_1); \ + HANDLE_PENDING_PROMISE(method, callsite, p, promiseId); \ + })); \ + } while (0) + +#define INVOKE_ASYNC2(method, promiseId, param1, param2) \ + do { \ + auto callsite = __func__; \ + using ParamType1 = std::remove_reference<decltype(param1)>::type; \ + using ParamType2 = std::remove_reference<decltype(param2)>::type; \ + mManagerThread->Dispatch(NS_NewRunnableFunction( \ + callsite, [self = RefPtr{this}, callsite, promiseId, \ + param_1 = std::forward<ParamType1>(param1), \ + param_2 = std::forward<ParamType2>(param2)] { \ + auto p = self->Send##method(param_1, param_2); \ + HANDLE_PENDING_PROMISE(method, callsite, p, promiseId); \ + })); \ + } while (0) + +MFCDMChild::MFCDMChild(const nsAString& aKeySystem) + : mKeySystem(aKeySystem), + mManagerThread(RemoteDecoderManagerChild::GetManagerThread()), + mState(NS_ERROR_NOT_INITIALIZED), + mShutdown(false) { + mRemotePromise = EnsureRemote(); +} + +MFCDMChild::~MFCDMChild() {} + +RefPtr<MFCDMChild::RemotePromise> MFCDMChild::EnsureRemote() { + if (!mManagerThread) { + LOG("no manager thread"); + mState = NS_ERROR_NOT_AVAILABLE; + return RemotePromise::CreateAndReject(mState, __func__); + } + + RefPtr<MFCDMChild> self = this; + RemoteDecoderManagerChild::LaunchUtilityProcessIfNeeded( + RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) + ->Then( + mManagerThread, __func__, + [self, this](bool) { + mRemoteRequest.Complete(); + RefPtr<RemoteDecoderManagerChild> manager = + RemoteDecoderManagerChild::GetSingleton( + RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM); + if (!manager || !manager->CanSend()) { + LOG("manager not exists or can't send"); + mState = NS_ERROR_NOT_AVAILABLE; + mRemotePromiseHolder.RejectIfExists(mState, __func__); + return; + } + + mIPDLSelfRef = this; + MOZ_ALWAYS_TRUE(manager->SendPMFCDMConstructor(this, mKeySystem)); + mState = NS_OK; + mRemotePromiseHolder.ResolveIfExists(true, __func__); + }, + [self, this](nsresult rv) { + mRemoteRequest.Complete(); + LOG("fail to launch MFCDM process"); + mState = rv; + mRemotePromiseHolder.RejectIfExists(rv, __func__); + }) + ->Track(mRemoteRequest); + return mRemotePromiseHolder.Ensure(__func__); +} + +void MFCDMChild::Shutdown() { + MOZ_ASSERT(!mShutdown); + + mShutdown = true; + mProxyCallback = nullptr; + + if (mState == NS_OK) { + mManagerThread->Dispatch( + NS_NewRunnableFunction(__func__, [self = RefPtr{this}, this]() { + mRemoteRequest.DisconnectIfExists(); + mInitRequest.DisconnectIfExists(); + + for (auto& promise : mPendingSessionPromises) { + promise.second.RejectIfExists(NS_ERROR_ABORT, __func__); + } + mPendingSessionPromises.clear(); + + for (auto& promise : mPendingGenericPromises) { + promise.second.RejectIfExists(NS_ERROR_ABORT, __func__); + } + mPendingGenericPromises.clear(); + + mRemotePromiseHolder.RejectIfExists(NS_ERROR_ABORT, __func__); + mCapabilitiesPromiseHolder.RejectIfExists(NS_ERROR_ABORT, __func__); + + Send__delete__(this); + })); + } +} + +RefPtr<MFCDMChild::CapabilitiesPromise> MFCDMChild::GetCapabilities( + bool aIsHWSecured) { + MOZ_ASSERT(mManagerThread); + + if (mShutdown) { + return CapabilitiesPromise::CreateAndReject(NS_ERROR_ABORT, __func__); + } + + if (mState != NS_OK && mState != NS_ERROR_NOT_INITIALIZED) { + LOG("error=%x", uint32_t(nsresult(mState))); + return CapabilitiesPromise::CreateAndReject(mState, __func__); + } + + auto doSend = [self = RefPtr{this}, aIsHWSecured, this]() { + SendGetCapabilities(aIsHWSecured) + ->Then( + mManagerThread, __func__, + [self, this](MFCDMCapabilitiesResult&& aResult) { + if (aResult.type() == MFCDMCapabilitiesResult::Tnsresult) { + mCapabilitiesPromiseHolder.RejectIfExists( + aResult.get_nsresult(), __func__); + return; + } + mCapabilitiesPromiseHolder.ResolveIfExists( + std::move(aResult.get_MFCDMCapabilitiesIPDL()), __func__); + }, + [self, this](const mozilla::ipc::ResponseRejectReason& aReason) { + mCapabilitiesPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, + __func__); + }); + }; + + return InvokeAsync(doSend, __func__, mCapabilitiesPromiseHolder); +} + +// Neither error nor shutdown. +void MFCDMChild::AssertSendable() { + MOZ_ASSERT((mState == NS_ERROR_NOT_INITIALIZED || mState == NS_OK) && + mShutdown == false); +} + +template <typename PromiseType> +already_AddRefed<PromiseType> MFCDMChild::InvokeAsync( + std::function<void()>&& aCall, const char* aCallerName, + MozPromiseHolder<PromiseType>& aPromise) { + AssertSendable(); + + if (mState == NS_OK) { + // mRemotePromise is resolved, send on manager thread. + mManagerThread->Dispatch( + NS_NewRunnableFunction(aCallerName, std::move(aCall))); + } else if (mState == NS_ERROR_NOT_INITIALIZED) { + // mRemotePromise is pending, chain to it. + mRemotePromise->Then( + mManagerThread, __func__, std::move(aCall), + [self = RefPtr{this}, this, &aPromise, aCallerName](nsresult rv) { + LOG("error=%x", uint32_t(rv)); + mState = rv; + aPromise.RejectIfExists(rv, aCallerName); + }); + } + + return aPromise.Ensure(aCallerName); +} + +RefPtr<MFCDMChild::InitPromise> MFCDMChild::Init( + const nsAString& aOrigin, const CopyableTArray<nsString>& aInitDataTypes, + const KeySystemConfig::Requirement aPersistentState, + const KeySystemConfig::Requirement aDistinctiveID, + const CopyableTArray<MFCDMMediaCapability>& aAudioCapabilities, + const CopyableTArray<MFCDMMediaCapability>& aVideoCapabilities, + WMFCDMProxyCallback* aProxyCallback) { + MOZ_ASSERT(mManagerThread); + + if (mShutdown) { + return InitPromise::CreateAndReject(NS_ERROR_ABORT, __func__); + } + + if (mState != NS_OK && mState != NS_ERROR_NOT_INITIALIZED) { + LOG("error=%x", uint32_t(nsresult(mState))); + return InitPromise::CreateAndReject(mState, __func__); + } + + mProxyCallback = aProxyCallback; + MFCDMInitParamsIPDL params{nsString(aOrigin), aInitDataTypes, + aDistinctiveID, aPersistentState, + aAudioCapabilities, aVideoCapabilities}; + auto doSend = [self = RefPtr{this}, this, params]() { + SendInit(params) + ->Then( + mManagerThread, __func__, + [self, this](MFCDMInitResult&& aResult) { + mInitRequest.Complete(); + if (aResult.type() == MFCDMInitResult::Tnsresult) { + nsresult rv = aResult.get_nsresult(); + mInitPromiseHolder.RejectIfExists(rv, __func__); + return; + } + mId = aResult.get_MFCDMInitIPDL().id(); + mInitPromiseHolder.ResolveIfExists(aResult.get_MFCDMInitIPDL(), + __func__); + }, + [self, this](const mozilla::ipc::ResponseRejectReason& aReason) { + mInitRequest.Complete(); + mInitPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__); + }) + ->Track(mInitRequest); + }; + + return InvokeAsync(std::move(doSend), __func__, mInitPromiseHolder); +} + +RefPtr<MFCDMChild::SessionPromise> MFCDMChild::CreateSessionAndGenerateRequest( + uint32_t aPromiseId, KeySystemConfig::SessionType aSessionType, + const nsAString& aInitDataType, const nsTArray<uint8_t>& aInitData) { + MOZ_ASSERT(mManagerThread); + MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it"); + + if (mShutdown) { + return MFCDMChild::SessionPromise::CreateAndReject(NS_ERROR_ABORT, + __func__); + } + + MOZ_ASSERT(mPendingSessionPromises.find(aPromiseId) == + mPendingSessionPromises.end()); + mPendingSessionPromises.emplace(aPromiseId, + MozPromiseHolder<SessionPromise>{}); + mManagerThread->Dispatch(NS_NewRunnableFunction( + __func__, [self = RefPtr{this}, this, + params = + MFCDMCreateSessionParamsIPDL{ + aSessionType, nsString{aInitDataType}, aInitData}, + aPromiseId] { + SendCreateSessionAndGenerateRequest(params)->Then( + mManagerThread, __func__, + [self, aPromiseId, this](const MFCDMSessionResult& result) { + auto iter = mPendingSessionPromises.find(aPromiseId); + if (iter == mPendingSessionPromises.end()) { + return; + } + auto& promiseHolder = iter->second; + if (result.type() == MFCDMSessionResult::Tnsresult) { + promiseHolder.RejectIfExists(result.get_nsresult(), __func__); + } else { + LOG("session ID=[%zu]%s", result.get_nsString().Length(), + NS_ConvertUTF16toUTF8(result.get_nsString()).get()); + promiseHolder.ResolveIfExists(result.get_nsString(), __func__); + } + mPendingSessionPromises.erase(iter); + }, + [self, aPromiseId, + this](const mozilla::ipc::ResponseRejectReason& aReason) { + auto iter = mPendingSessionPromises.find(aPromiseId); + if (iter == mPendingSessionPromises.end()) { + return; + } + auto& promiseHolder = iter->second; + promiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__); + mPendingSessionPromises.erase(iter); + }); + })); + return mPendingSessionPromises[aPromiseId].Ensure(__func__); +} + +RefPtr<GenericPromise> MFCDMChild::LoadSession( + uint32_t aPromiseId, const KeySystemConfig::SessionType aSessionType, + const nsAString& aSessionId) { + MOZ_ASSERT(mManagerThread); + MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it"); + + if (mShutdown) { + return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__); + } + + MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) == + mPendingGenericPromises.end()); + mPendingGenericPromises.emplace(aPromiseId, + MozPromiseHolder<GenericPromise>{}); + INVOKE_ASYNC2(LoadSession, aPromiseId, aSessionType, nsString{aSessionId}); + return mPendingGenericPromises[aPromiseId].Ensure(__func__); +} + +RefPtr<GenericPromise> MFCDMChild::UpdateSession(uint32_t aPromiseId, + const nsAString& aSessionId, + nsTArray<uint8_t>& aResponse) { + MOZ_ASSERT(mManagerThread); + MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it"); + + if (mShutdown) { + return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__); + } + + MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) == + mPendingGenericPromises.end()); + mPendingGenericPromises.emplace(aPromiseId, + MozPromiseHolder<GenericPromise>{}); + INVOKE_ASYNC2(UpdateSession, aPromiseId, nsString{aSessionId}, + std::move(aResponse)); + return mPendingGenericPromises[aPromiseId].Ensure(__func__); +} + +RefPtr<GenericPromise> MFCDMChild::CloseSession(uint32_t aPromiseId, + const nsAString& aSessionId) { + MOZ_ASSERT(mManagerThread); + MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it"); + + if (mShutdown) { + return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__); + } + + MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) == + mPendingGenericPromises.end()); + mPendingGenericPromises.emplace(aPromiseId, + MozPromiseHolder<GenericPromise>{}); + INVOKE_ASYNC(CloseSession, aPromiseId, nsString{aSessionId}); + return mPendingGenericPromises[aPromiseId].Ensure(__func__); +} + +RefPtr<GenericPromise> MFCDMChild::RemoveSession(uint32_t aPromiseId, + const nsAString& aSessionId) { + MOZ_ASSERT(mManagerThread); + MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it"); + + if (mShutdown) { + return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__); + } + + MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) == + mPendingGenericPromises.end()); + mPendingGenericPromises.emplace(aPromiseId, + MozPromiseHolder<GenericPromise>{}); + INVOKE_ASYNC(RemoveSession, aPromiseId, nsString{aSessionId}); + return mPendingGenericPromises[aPromiseId].Ensure(__func__); +} + +RefPtr<GenericPromise> MFCDMChild::SetServerCertificate( + uint32_t aPromiseId, nsTArray<uint8_t>& aCert) { + MOZ_ASSERT(mManagerThread); + MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it"); + + if (mShutdown) { + return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__); + } + + MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) == + mPendingGenericPromises.end()); + mPendingGenericPromises.emplace(aPromiseId, + MozPromiseHolder<GenericPromise>{}); + INVOKE_ASYNC(SetServerCertificate, aPromiseId, std::move(aCert)); + return mPendingGenericPromises[aPromiseId].Ensure(__func__); +} + +RefPtr<GenericPromise> MFCDMChild::GetStatusForPolicy( + uint32_t aPromiseId, const dom::HDCPVersion& aMinHdcpVersion) { + MOZ_ASSERT(mManagerThread); + MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it"); + + if (mShutdown) { + return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__); + } + + MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) == + mPendingGenericPromises.end()); + mPendingGenericPromises.emplace(aPromiseId, + MozPromiseHolder<GenericPromise>{}); + INVOKE_ASYNC(GetStatusForPolicy, aPromiseId, aMinHdcpVersion); + return mPendingGenericPromises[aPromiseId].Ensure(__func__); +} + +mozilla::ipc::IPCResult MFCDMChild::RecvOnSessionKeyMessage( + const MFCDMKeyMessage& aMessage) { + LOG("RecvOnSessionKeyMessage, sessionId=%s", + NS_ConvertUTF16toUTF8(aMessage.sessionId()).get()); + MOZ_ASSERT(mProxyCallback); + mProxyCallback->OnSessionMessage(aMessage); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMChild::RecvOnSessionKeyStatusesChanged( + const MFCDMKeyStatusChange& aKeyStatuses) { + LOG("RecvOnSessionKeyStatusesChanged, sessionId=%s", + NS_ConvertUTF16toUTF8(aKeyStatuses.sessionId()).get()); + MOZ_ASSERT(mProxyCallback); + mProxyCallback->OnSessionKeyStatusesChange(aKeyStatuses); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMChild::RecvOnSessionKeyExpiration( + const MFCDMKeyExpiration& aExpiration) { + LOG("RecvOnSessionKeyExpiration, sessionId=%s", + NS_ConvertUTF16toUTF8(aExpiration.sessionId()).get()); + MOZ_ASSERT(mProxyCallback); + mProxyCallback->OnSessionKeyExpiration(aExpiration); + return IPC_OK(); +} + +#undef SLOG +#undef LOG + +} // namespace mozilla diff --git a/dom/media/ipc/MFCDMChild.h b/dom/media/ipc/MFCDMChild.h new file mode 100644 index 0000000000..e62f2b7184 --- /dev/null +++ b/dom/media/ipc/MFCDMChild.h @@ -0,0 +1,154 @@ +/* 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 DOM_MEDIA_IPC_MFCDMCHILD_H_ +#define DOM_MEDIA_IPC_MFCDMCHILD_H_ + +#include <unordered_map> +#include "mozilla/Atomics.h" +#include "mozilla/MozPromise.h" +#include "mozilla/PMFCDMChild.h" + +namespace mozilla { + +class WMFCDMProxyCallback; + +/** + * MFCDMChild is a content process proxy to MFCDMParent and the actual CDM + * running in utility process. + */ +class MFCDMChild final : public PMFCDMChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MFCDMChild); + + explicit MFCDMChild(const nsAString& aKeySystem); + + using CapabilitiesPromise = MozPromise<MFCDMCapabilitiesIPDL, nsresult, true>; + RefPtr<CapabilitiesPromise> GetCapabilities(bool aIsHWSecured); + + template <typename PromiseType> + already_AddRefed<PromiseType> InvokeAsync( + std::function<void()>&& aCall, const char* aCallerName, + MozPromiseHolder<PromiseType>& aPromise); + + using InitPromise = MozPromise<MFCDMInitIPDL, nsresult, true>; + RefPtr<InitPromise> Init( + const nsAString& aOrigin, const CopyableTArray<nsString>& aInitDataTypes, + const KeySystemConfig::Requirement aPersistentState, + const KeySystemConfig::Requirement aDistinctiveID, + const CopyableTArray<MFCDMMediaCapability>& aAudioCapabilities, + const CopyableTArray<MFCDMMediaCapability>& aVideoCapabilities, + WMFCDMProxyCallback* aProxyCallback); + + using SessionPromise = MozPromise<nsString, nsresult, true>; + RefPtr<SessionPromise> CreateSessionAndGenerateRequest( + uint32_t aPromiseId, const KeySystemConfig::SessionType aSessionType, + const nsAString& aInitDataType, const nsTArray<uint8_t>& aInitData); + + RefPtr<GenericPromise> LoadSession( + uint32_t aPromiseId, const KeySystemConfig::SessionType aSessionType, + const nsAString& aSessionId); + + RefPtr<GenericPromise> UpdateSession(uint32_t aPromiseId, + const nsAString& aSessionId, + nsTArray<uint8_t>& aResponse); + + RefPtr<GenericPromise> CloseSession(uint32_t aPromiseId, + const nsAString& aSessionId); + + RefPtr<GenericPromise> RemoveSession(uint32_t aPromiseId, + const nsAString& aSessionId); + + RefPtr<GenericPromise> SetServerCertificate(uint32_t aPromiseId, + nsTArray<uint8_t>& aCert); + + RefPtr<GenericPromise> GetStatusForPolicy( + uint32_t aPromiseId, const dom::HDCPVersion& aMinHdcpVersion); + + mozilla::ipc::IPCResult RecvOnSessionKeyMessage( + const MFCDMKeyMessage& aMessage); + mozilla::ipc::IPCResult RecvOnSessionKeyStatusesChanged( + const MFCDMKeyStatusChange& aKeyStatuses); + mozilla::ipc::IPCResult RecvOnSessionKeyExpiration( + const MFCDMKeyExpiration& aExpiration); + + uint64_t Id() const { return mId; } + const nsString& KeySystem() const { return mKeySystem; } + + void IPDLActorDestroyed() { + AssertOnManagerThread(); + mIPDLSelfRef = nullptr; + if (!mShutdown) { + // Remote crashed! + mState = NS_ERROR_NOT_AVAILABLE; + } + } + void Shutdown(); + + nsISerialEventTarget* ManagerThread() { return mManagerThread; } + void AssertOnManagerThread() const { + MOZ_ASSERT(mManagerThread->IsOnCurrentThread()); + } + + private: + ~MFCDMChild(); + + using RemotePromise = GenericNonExclusivePromise; + RefPtr<RemotePromise> EnsureRemote(); + + void AssertSendable(); + + const nsString mKeySystem; + + const RefPtr<nsISerialEventTarget> mManagerThread; + RefPtr<MFCDMChild> mIPDLSelfRef; + + RefPtr<RemotePromise> mRemotePromise; + MozPromiseHolder<RemotePromise> mRemotePromiseHolder; + MozPromiseRequestHolder<RemotePromise> mRemoteRequest; + // Before EnsureRemote(): NS_ERROR_NOT_INITIALIZED; After EnsureRemote(): + // NS_OK or some error code. + Atomic<nsresult> mState; + + MozPromiseHolder<CapabilitiesPromise> mCapabilitiesPromiseHolder; + + Atomic<bool> mShutdown; + + // This represents an unique Id to indentify the CDM in the remote process. + // 0(zero) means the CDM is not yet initialized. + // Modified on the manager thread, and read on other threads. + Atomic<uint64_t> mId; + MozPromiseHolder<InitPromise> mInitPromiseHolder; + using InitIPDLPromise = MozPromise<mozilla::MFCDMInitResult, + mozilla::ipc::ResponseRejectReason, true>; + MozPromiseRequestHolder<InitIPDLPromise> mInitRequest; + + MozPromiseHolder<SessionPromise> mCreateSessionPromiseHolder; + MozPromiseRequestHolder<CreateSessionAndGenerateRequestPromise> + mCreateSessionRequest; + + MozPromiseHolder<GenericPromise> mLoadSessionPromiseHolder; + MozPromiseRequestHolder<LoadSessionPromise> mLoadSessionRequest; + + MozPromiseHolder<GenericPromise> mUpdateSessionPromiseHolder; + MozPromiseRequestHolder<UpdateSessionPromise> mUpdateSessionRequest; + + MozPromiseHolder<GenericPromise> mCloseSessionPromiseHolder; + MozPromiseRequestHolder<CloseSessionPromise> mCloseSessionRequest; + + MozPromiseHolder<GenericPromise> mRemoveSessionPromiseHolder; + MozPromiseRequestHolder<RemoveSessionPromise> mRemoveSessionRequest; + + std::unordered_map<uint32_t, MozPromiseHolder<SessionPromise>> + mPendingSessionPromises; + + std::unordered_map<uint32_t, MozPromiseHolder<GenericPromise>> + mPendingGenericPromises; + + RefPtr<WMFCDMProxyCallback> mProxyCallback; +}; + +} // namespace mozilla + +#endif // DOM_MEDIA_IPC_MFCDMCHILD_H_ diff --git a/dom/media/ipc/MFCDMParent.cpp b/dom/media/ipc/MFCDMParent.cpp new file mode 100644 index 0000000000..2e91048b88 --- /dev/null +++ b/dom/media/ipc/MFCDMParent.cpp @@ -0,0 +1,1272 @@ +/* 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 "MFCDMParent.h" + +#include <mfmediaengine.h> +#include <wtypes.h> +#define INITGUID // Enable DEFINE_PROPERTYKEY() +#include <propkeydef.h> // For DEFINE_PROPERTYKEY() definition +#include <propvarutil.h> // For InitPropVariantFrom*() + +#include "mozilla/dom/MediaKeysBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/KeySystemNames.h" +#include "mozilla/ipc/UtilityAudioDecoderChild.h" +#include "mozilla/ipc/UtilityProcessManager.h" +#include "mozilla/ipc/UtilityProcessParent.h" +#include "mozilla/EMEUtils.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/KeySystemConfig.h" +#include "mozilla/WindowsVersion.h" +#include "MFCDMProxy.h" +#include "MFMediaEngineUtils.h" +#include "nsTHashMap.h" +#include "RemoteDecodeUtils.h" // For GetCurrentSandboxingKind() +#include "SpecialSystemDirectory.h" // For temp dir +#include "WMFUtils.h" + +#ifdef MOZ_WMF_CDM_LPAC_SANDBOX +# include "sandboxBroker.h" +#endif + +using Microsoft::WRL::ComPtr; +using Microsoft::WRL::MakeAndInitialize; + +namespace mozilla { + +// See +// https://source.chromium.org/chromium/chromium/src/+/main:media/cdm/win/media_foundation_cdm_util.cc;l=26-40;drc=503535015a7b373cc6185c69c991e01fda5da571 +#ifndef EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID +DEFINE_PROPERTYKEY(EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID, 0x1218a3e2, 0xcfb0, + 0x4c98, 0x90, 0xe5, 0x5f, 0x58, 0x18, 0xd4, 0xb6, 0x7e, + PID_FIRST_USABLE); +#endif + +#define MFCDM_PARENT_LOG(msg, ...) \ + EME_LOG("MFCDMParent[%p, Id=%" PRIu64 "]@%s: " msg, this, this->mId, \ + __func__, ##__VA_ARGS__) +#define MFCDM_PARENT_SLOG(msg, ...) \ + EME_LOG("MFCDMParent@%s: " msg, __func__, ##__VA_ARGS__) + +#define MFCDM_RETURN_IF_FAILED(x) \ + do { \ + HRESULT rv = x; \ + if (MOZ_UNLIKELY(FAILED(rv))) { \ + MFCDM_PARENT_SLOG("(" #x ") failed, rv=%lx", rv); \ + return rv; \ + } \ + } while (false) + +#define MFCDM_RETURN_BOOL_IF_FAILED(x) \ + do { \ + HRESULT rv = x; \ + if (MOZ_UNLIKELY(FAILED(rv))) { \ + MFCDM_PARENT_SLOG("(" #x ") failed, rv=%lx", rv); \ + return false; \ + } \ + } while (false) + +#define MFCDM_REJECT_IF(pred, rv) \ + do { \ + if (MOZ_UNLIKELY(pred)) { \ + MFCDM_PARENT_LOG("reject for [" #pred "], rv=%x", uint32_t(rv)); \ + aResolver(rv); \ + return IPC_OK(); \ + } \ + } while (false) + +#define MFCDM_REJECT_IF_FAILED(op, rv) \ + do { \ + HRESULT hr = op; \ + if (MOZ_UNLIKELY(FAILED(hr))) { \ + MFCDM_PARENT_LOG("(" #op ") failed(hr=%lx), rv=%x", hr, uint32_t(rv)); \ + aResolver(rv); \ + return IPC_OK(); \ + } \ + } while (false) + +StaticMutex sFactoryMutex; +static nsTHashMap<nsStringHashKey, ComPtr<IMFContentDecryptionModuleFactory>> + sFactoryMap; +static CopyableTArray<MFCDMCapabilitiesIPDL> sCapabilities; + +// RAIIized PROPVARIANT. See +// third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.h +class AutoPropVar { + public: + AutoPropVar() { PropVariantInit(&mVar); } + + ~AutoPropVar() { Reset(); } + + AutoPropVar(const AutoPropVar&) = delete; + AutoPropVar& operator=(const AutoPropVar&) = delete; + bool operator==(const AutoPropVar&) const = delete; + bool operator!=(const AutoPropVar&) const = delete; + + // Returns a pointer to the underlying PROPVARIANT for use as an out param + // in a function call. + PROPVARIANT* Receive() { + MOZ_ASSERT(mVar.vt == VT_EMPTY); + return &mVar; + } + + // Clears the instance to prepare it for re-use (e.g., via Receive). + void Reset() { + if (mVar.vt != VT_EMPTY) { + HRESULT hr = PropVariantClear(&mVar); + MOZ_ASSERT(SUCCEEDED(hr)); + Unused << hr; + } + } + + const PROPVARIANT& get() const { return mVar; } + const PROPVARIANT* ptr() const { return &mVar; } + + private: + PROPVARIANT mVar; +}; + +static MF_MEDIAKEYS_REQUIREMENT ToMFRequirement( + const KeySystemConfig::Requirement aRequirement) { + switch (aRequirement) { + case KeySystemConfig::Requirement::NotAllowed: + return MF_MEDIAKEYS_REQUIREMENT_NOT_ALLOWED; + case KeySystemConfig::Requirement::Optional: + return MF_MEDIAKEYS_REQUIREMENT_OPTIONAL; + case KeySystemConfig::Requirement::Required: + return MF_MEDIAKEYS_REQUIREMENT_REQUIRED; + } +}; + +static inline LPCWSTR InitDataTypeToString(const nsAString& aInitDataType) { + // The strings are defined in https://www.w3.org/TR/eme-initdata-registry/ + if (aInitDataType.EqualsLiteral("webm")) { + return L"webm"; + } else if (aInitDataType.EqualsLiteral("cenc")) { + return L"cenc"; + } else if (aInitDataType.EqualsLiteral("keyids")) { + return L"keyids"; + } else { + return L"unknown"; + } +} + +// The HDCP value follows the feature value in +// https://docs.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilities.istypesupported?view=winrt-19041 +// - 1 (on without HDCP 2.2 Type 1 restriction) +// - 2 (on with HDCP 2.2 Type 1 restriction) +static nsString GetHdcpPolicy(const dom::HDCPVersion& aMinHdcpVersion) { + if (aMinHdcpVersion == dom::HDCPVersion::_2_2 || + aMinHdcpVersion == dom::HDCPVersion::_2_3) { + return nsString(u"hdcp=2"); + } + return nsString(u"hdcp=1"); +} + +static void BuildCapabilitiesArray( + const nsTArray<MFCDMMediaCapability>& aCapabilities, + AutoPropVar& capabilitiesPropOut) { + PROPVARIANT* capabilitiesArray = (PROPVARIANT*)CoTaskMemAlloc( + sizeof(PROPVARIANT) * aCapabilities.Length()); + for (size_t idx = 0; idx < aCapabilities.Length(); idx++) { + ComPtr<IPropertyStore> capabilitiesProperty; + RETURN_VOID_IF_FAILED( + PSCreateMemoryPropertyStore(IID_PPV_ARGS(&capabilitiesProperty))); + + AutoPropVar contentType; + auto* var = contentType.Receive(); + var->vt = VT_BSTR; + var->bstrVal = SysAllocString(aCapabilities[idx].contentType().get()); + RETURN_VOID_IF_FAILED( + capabilitiesProperty->SetValue(MF_EME_CONTENTTYPE, contentType.get())); + + AutoPropVar robustness; + var = robustness.Receive(); + var->vt = VT_BSTR; + var->bstrVal = SysAllocString(aCapabilities[idx].robustness().get()); + RETURN_VOID_IF_FAILED( + capabilitiesProperty->SetValue(MF_EME_ROBUSTNESS, robustness.get())); + + capabilitiesArray[idx].vt = VT_UNKNOWN; + capabilitiesArray[idx].punkVal = capabilitiesProperty.Detach(); + } + auto* var = capabilitiesPropOut.Receive(); + var->vt = VT_VARIANT | VT_VECTOR; + var->capropvar.cElems = aCapabilities.Length(); + var->capropvar.pElems = capabilitiesArray; +} + +static HRESULT BuildCDMAccessConfig(const MFCDMInitParamsIPDL& aParams, + ComPtr<IPropertyStore>& aConfig) { + ComPtr<IPropertyStore> mksc; // EME MediaKeySystemConfiguration + MFCDM_RETURN_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&mksc))); + + // Init type. If we don't set `MF_EME_INITDATATYPES` then we won't be able + // to create CDM module on Windows 10, which is not documented officially. + BSTR* initDataTypeArray = + (BSTR*)CoTaskMemAlloc(sizeof(BSTR) * aParams.initDataTypes().Length()); + for (size_t i = 0; i < aParams.initDataTypes().Length(); i++) { + initDataTypeArray[i] = + SysAllocString(InitDataTypeToString(aParams.initDataTypes()[i])); + } + AutoPropVar initDataTypes; + PROPVARIANT* var = initDataTypes.Receive(); + var->vt = VT_VECTOR | VT_BSTR; + var->cabstr.cElems = static_cast<ULONG>(aParams.initDataTypes().Length()); + var->cabstr.pElems = initDataTypeArray; + MFCDM_RETURN_IF_FAILED( + mksc->SetValue(MF_EME_INITDATATYPES, initDataTypes.get())); + + // Audio capabilities + AutoPropVar audioCapabilities; + BuildCapabilitiesArray(aParams.audioCapabilities(), audioCapabilities); + MFCDM_RETURN_IF_FAILED( + mksc->SetValue(MF_EME_AUDIOCAPABILITIES, audioCapabilities.get())); + + // Video capabilities + AutoPropVar videoCapabilities; + BuildCapabilitiesArray(aParams.videoCapabilities(), videoCapabilities); + MFCDM_RETURN_IF_FAILED( + mksc->SetValue(MF_EME_VIDEOCAPABILITIES, videoCapabilities.get())); + + // Persist state + AutoPropVar persistState; + InitPropVariantFromUInt32(ToMFRequirement(aParams.persistentState()), + persistState.Receive()); + MFCDM_RETURN_IF_FAILED( + mksc->SetValue(MF_EME_PERSISTEDSTATE, persistState.get())); + + // Distintive Id + AutoPropVar distinctiveID; + InitPropVariantFromUInt32(ToMFRequirement(aParams.distinctiveID()), + distinctiveID.Receive()); + MFCDM_RETURN_IF_FAILED( + mksc->SetValue(MF_EME_DISTINCTIVEID, distinctiveID.get())); + + aConfig.Swap(mksc); + return S_OK; +} + +static HRESULT BuildCDMProperties(const nsString& aOrigin, + ComPtr<IPropertyStore>& aProps) { + MOZ_ASSERT(!aOrigin.IsEmpty()); + + ComPtr<IPropertyStore> props; + MFCDM_RETURN_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&props))); + + AutoPropVar origin; + MFCDM_RETURN_IF_FAILED( + InitPropVariantFromString(aOrigin.get(), origin.Receive())); + MFCDM_RETURN_IF_FAILED( + props->SetValue(EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID, origin.get())); + + // TODO: support client token? + + // TODO: CDM store path per profile? + nsCOMPtr<nsIFile> dir; + if (NS_FAILED(GetSpecialSystemDirectory(OS_TemporaryDirectory, + getter_AddRefs(dir)))) { + return E_ACCESSDENIED; + } + if (NS_FAILED(dir->AppendNative(nsDependentCString("mfcdm")))) { + return E_ACCESSDENIED; + } + nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_FAILED(rv)) { + return E_ACCESSDENIED; + } + nsAutoString cdmStorePath; + if (NS_FAILED(dir->GetPath(cdmStorePath))) { + return E_ACCESSDENIED; + } + + AutoPropVar path; + MFCDM_RETURN_IF_FAILED( + InitPropVariantFromString(cdmStorePath.get(), path.Receive())); + MFCDM_RETURN_IF_FAILED( + props->SetValue(MF_CONTENTDECRYPTIONMODULE_STOREPATH, path.get())); + + aProps.Swap(props); + return S_OK; +} + +static HRESULT CreateContentDecryptionModule( + ComPtr<IMFContentDecryptionModuleFactory> aFactory, + const nsString& aKeySystem, const MFCDMInitParamsIPDL& aParams, + ComPtr<IMFContentDecryptionModule>& aCDMOut) { + // Get access object to CDM. + ComPtr<IPropertyStore> accessConfig; + RETURN_IF_FAILED(BuildCDMAccessConfig(aParams, accessConfig)); + + AutoTArray<IPropertyStore*, 1> configs = {accessConfig.Get()}; + ComPtr<IMFContentDecryptionModuleAccess> cdmAccess; + RETURN_IF_FAILED(aFactory->CreateContentDecryptionModuleAccess( + aKeySystem.get(), configs.Elements(), configs.Length(), &cdmAccess)); + + // Get CDM. + ComPtr<IPropertyStore> cdmProps; + RETURN_IF_FAILED(BuildCDMProperties(aParams.origin(), cdmProps)); + ComPtr<IMFContentDecryptionModule> cdm; + RETURN_IF_FAILED( + cdmAccess->CreateContentDecryptionModule(cdmProps.Get(), &cdm)); + aCDMOut.Swap(cdm); + return S_OK; +} + +// Wrapper function for IMFContentDecryptionModuleFactory::IsTypeSupported. +static bool IsTypeSupported( + const ComPtr<IMFContentDecryptionModuleFactory>& aFactory, + const nsString& aKeySystem, const nsString* aContentType = nullptr) { + nsString keySystem; + // Widevine's factory only takes original key system string. + if (IsWidevineExperimentKeySystemAndSupported(aKeySystem)) { + keySystem.AppendLiteral(u"com.widevine.alpha"); + } + // kPlayReadyHardwareClearLeadKeySystemName is our custom key system name, + // we should use kPlayReadyKeySystemHardware which is the real key system + // name. + else if (aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) { + keySystem.AppendLiteral(kPlayReadyKeySystemHardware); + } else { + keySystem = aKeySystem; + } + return aFactory->IsTypeSupported( + keySystem.get(), aContentType ? aContentType->get() : nullptr); +} + +static nsString MapKeySystem(const nsString& aKeySystem) { + // When website requests HW secure robustness for video by original Widevine + // key system name, it would be mapped to this key system which is for HWDRM. + if (IsWidevineKeySystem(aKeySystem)) { + return nsString(u"com.widevine.alpha.experiment"); + } + // kPlayReadyHardwareClearLeadKeySystemName is our custom key system name, + // we should use kPlayReadyKeySystemHardware which is the real key system + // name. + if (aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) { + return NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware); + } + return aKeySystem; +} + +/* static */ +void MFCDMParent::SetWidevineL1Path(const char* aPath) { + nsAutoCString path(aPath); + path.AppendLiteral("\\Google.Widevine.CDM.dll"); + sWidevineL1Path = CreateBSTRFromConstChar(path.get()); + MFCDM_PARENT_SLOG("Set Widevine L1 dll path=%ls\n", sWidevineL1Path); +} + +void MFCDMParent::Register() { + MOZ_ASSERT(!sRegisteredCDMs.Contains(this->mId)); + sRegisteredCDMs.InsertOrUpdate(this->mId, this); + MFCDM_PARENT_LOG("Registered!"); +} + +void MFCDMParent::Unregister() { + MOZ_ASSERT(sRegisteredCDMs.Contains(this->mId)); + sRegisteredCDMs.Remove(this->mId); + MFCDM_PARENT_LOG("Unregistered!"); +} + +MFCDMParent::MFCDMParent(const nsAString& aKeySystem, + RemoteDecoderManagerParent* aManager, + nsISerialEventTarget* aManagerThread) + : mKeySystem(aKeySystem), + mManager(aManager), + mManagerThread(aManagerThread), + mId(sNextId++), + mKeyMessageEvents(aManagerThread), + mKeyChangeEvents(aManagerThread), + mExpirationEvents(aManagerThread) { + MOZ_ASSERT(IsPlayReadyKeySystemAndSupported(aKeySystem) || + IsWidevineExperimentKeySystemAndSupported(aKeySystem) || + IsWidevineKeySystem(mKeySystem) || + IsWMFClearKeySystemAndSupported(aKeySystem)); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aManagerThread); + MOZ_ASSERT(XRE_IsUtilityProcess()); + MOZ_ASSERT(GetCurrentSandboxingKind() == + ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM); + MFCDM_PARENT_LOG("MFCDMParent created"); + mIPDLSelfRef = this; + Register(); + + mKeyMessageListener = mKeyMessageEvents.Connect( + mManagerThread, this, &MFCDMParent::SendOnSessionKeyMessage); + mKeyChangeListener = mKeyChangeEvents.Connect( + mManagerThread, this, &MFCDMParent::SendOnSessionKeyStatusesChanged); + mExpirationListener = mExpirationEvents.Connect( + mManagerThread, this, &MFCDMParent::SendOnSessionKeyExpiration); + + RETURN_VOID_IF_FAILED(GetOrCreateFactory(mKeySystem, mFactory)); +} + +void MFCDMParent::ShutdownCDM() { + AssertOnManagerThread(); + if (!mCDM) { + return; + } + auto rv = mCDM->SetPMPHostApp(nullptr); + if (FAILED(rv)) { + MFCDM_PARENT_LOG("Failed to clear PMP Host App, rv=%lx", rv); + } + SHUTDOWN_IF_POSSIBLE(mCDM); + mCDM = nullptr; + MFCDM_PARENT_LOG("Shutdown CDM completed"); +} + +void MFCDMParent::Destroy() { + AssertOnManagerThread(); + mKeyMessageEvents.DisconnectAll(); + mKeyChangeEvents.DisconnectAll(); + mExpirationEvents.DisconnectAll(); + mKeyMessageListener.DisconnectIfExists(); + mKeyChangeListener.DisconnectIfExists(); + mExpirationListener.DisconnectIfExists(); + if (mPMPHostWrapper) { + mPMPHostWrapper->Shutdown(); + mPMPHostWrapper = nullptr; + } + ShutdownCDM(); + mFactory = nullptr; + for (auto& iter : mSessions) { + iter.second->Close(); + } + mSessions.clear(); + mIPDLSelfRef = nullptr; +} + +MFCDMParent::~MFCDMParent() { + MFCDM_PARENT_LOG("MFCDMParent detroyed"); + Unregister(); +} + +/* static */ +LPCWSTR MFCDMParent::GetCDMLibraryName(const nsString& aKeySystem) { + if (IsWMFClearKeySystemAndSupported(aKeySystem) || + StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms()) { + return L"wmfclearkey.dll"; + } + // PlayReady is a built-in CDM on Windows, no need to load external library. + if (IsPlayReadyKeySystemAndSupported(aKeySystem)) { + return L""; + } + if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) || + IsWidevineKeySystem(aKeySystem)) { + return sWidevineL1Path ? sWidevineL1Path : L"L1-not-found"; + } + return L"Unknown"; +} + +/* static */ +void MFCDMParent::Shutdown() { + sFactoryMap.Clear(); + sCapabilities.Clear(); +} + +/* static */ +HRESULT MFCDMParent::GetOrCreateFactory( + const nsString& aKeySystem, + ComPtr<IMFContentDecryptionModuleFactory>& aFactoryOut) { + StaticMutexAutoLock lock(sFactoryMutex); + auto rv = sFactoryMap.MaybeGet(aKeySystem); + if (!rv) { + MFCDM_PARENT_SLOG("No factory %s, creating...", + NS_ConvertUTF16toUTF8(aKeySystem).get()); + ComPtr<IMFContentDecryptionModuleFactory> factory; + MFCDM_RETURN_IF_FAILED(LoadFactory(aKeySystem, factory)); + sFactoryMap.InsertOrUpdate(aKeySystem, factory); + aFactoryOut.Swap(factory); + } else { + aFactoryOut = *rv; + } + return S_OK; +} + +/* static */ +HRESULT MFCDMParent::LoadFactory( + const nsString& aKeySystem, + ComPtr<IMFContentDecryptionModuleFactory>& aFactoryOut) { + LPCWSTR libraryName = GetCDMLibraryName(aKeySystem); + const bool loadFromPlatform = wcslen(libraryName) == 0; + MFCDM_PARENT_SLOG("Load factory for %s (libraryName=%ls)", + NS_ConvertUTF16toUTF8(aKeySystem).get(), libraryName); + + MFCDM_PARENT_SLOG("Create factory for %s", + NS_ConvertUTF16toUTF8(aKeySystem).get()); + ComPtr<IMFContentDecryptionModuleFactory> cdmFactory; + if (loadFromPlatform) { + ComPtr<IMFMediaEngineClassFactory4> clsFactory; + MFCDM_RETURN_IF_FAILED(CoCreateInstance(CLSID_MFMediaEngineClassFactory, + nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&clsFactory))); + MFCDM_RETURN_IF_FAILED(clsFactory->CreateContentDecryptionModuleFactory( + MapKeySystem(aKeySystem).get(), IID_PPV_ARGS(&cdmFactory))); + aFactoryOut.Swap(cdmFactory); + MFCDM_PARENT_SLOG("Created factory for %s from platform!", + NS_ConvertUTF16toUTF8(aKeySystem).get()); + return S_OK; + } + + HMODULE handle = LoadLibraryW(libraryName); + if (!handle) { + MFCDM_PARENT_SLOG("Failed to load library %ls! (error=%lx)", libraryName, + GetLastError()); + return E_FAIL; + } + MFCDM_PARENT_SLOG("Loaded external library '%ls'", libraryName); + + using DllGetActivationFactoryFunc = + HRESULT(WINAPI*)(_In_ HSTRING, _COM_Outptr_ IActivationFactory**); + DllGetActivationFactoryFunc pDllGetActivationFactory = + (DllGetActivationFactoryFunc)GetProcAddress(handle, + "DllGetActivationFactory"); + if (!pDllGetActivationFactory) { + MFCDM_PARENT_SLOG("Failed to get activation function!"); + return E_FAIL; + } + + // The follow classID format is what Widevine's DLL expects + // "<key_system>.ContentDecryptionModuleFactory". In addition, when querying + // factory, need to use original Widevine key system name. + nsString stringId; + if (StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms() || + IsWMFClearKeySystemAndSupported(aKeySystem)) { + stringId.AppendLiteral("org.w3.clearkey"); + } else if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) || + IsWidevineKeySystem(aKeySystem)) { + // Widevine's DLL expects "<key_system>.ContentDecryptionModuleFactory" for + // the class Id. + stringId.AppendLiteral("com.widevine.alpha.ContentDecryptionModuleFactory"); + } + MFCDM_PARENT_SLOG("Query factory by classId '%s'", + NS_ConvertUTF16toUTF8(stringId).get()); + ScopedHString classId(stringId); + ComPtr<IActivationFactory> pFactory = NULL; + MFCDM_RETURN_IF_FAILED( + pDllGetActivationFactory(classId.Get(), pFactory.GetAddressOf())); + + ComPtr<IInspectable> pInspectable; + MFCDM_RETURN_IF_FAILED(pFactory->ActivateInstance(&pInspectable)); + MFCDM_RETURN_IF_FAILED(pInspectable.As(&cdmFactory)); + aFactoryOut.Swap(cdmFactory); + MFCDM_PARENT_SLOG("Created factory for %s from external library!", + NS_ConvertUTF16toUTF8(aKeySystem).get()); + return S_OK; +} + +static nsString GetRobustnessStringForKeySystem(const nsString& aKeySystem, + const bool aIsHWSecure, + const bool aIsVideo = true) { + if (IsPlayReadyKeySystemAndSupported(aKeySystem)) { + // Audio doesn't support SL3000. + return aIsHWSecure && aIsVideo ? nsString(u"3000") : nsString(u"2000"); + } + if (IsWidevineExperimentKeySystemAndSupported(aKeySystem)) { + return aIsHWSecure ? nsString(u"HW_SECURE_ALL") + : nsString(u"SW_SECURE_DECODE"); + } + return nsString(u""); +} + +// Use IMFContentDecryptionModuleFactory::IsTypeSupported() to get DRM +// capabilities. The query string is based on following, they are pretty much +// equivalent. +// https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilities.istypesupported?view=winrt-22621 +// https://learn.microsoft.com/en-us/windows/win32/api/mfmediaengine/nf-mfmediaengine-imfextendeddrmtypesupport-istypesupportedex +static bool FactorySupports(ComPtr<IMFContentDecryptionModuleFactory>& aFactory, + const nsString& aKeySystem, + const nsCString& aVideoCodec, + const nsCString& aAudioCodec = nsCString(""), + const nsString& aAdditionalFeatures = nsString(u""), + bool aIsHWSecure = false) { + // Create query string, MP4 is the only container supported. + nsString contentType(u"video/mp4;codecs=\""); + MOZ_ASSERT(!aVideoCodec.IsEmpty()); + contentType.AppendASCII(aVideoCodec); + if (!aAudioCodec.IsEmpty()) { + contentType.AppendLiteral(u","); + contentType.AppendASCII(aAudioCodec); + } + // These features are required to call IsTypeSupported(). We only care about + // codec and encryption scheme so hardcode the rest. + contentType.AppendLiteral( + u"\";features=\"decode-bpp=8," + "decode-res-x=1920,decode-res-y=1080," + "decode-bitrate=10000000,decode-fps=30,"); + if (!aAdditionalFeatures.IsEmpty()) { + contentType.Append(aAdditionalFeatures); + } + // `encryption-robustness` is for Widevine only. + if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) || + IsWidevineKeySystem(aKeySystem)) { + if (aIsHWSecure) { + contentType.AppendLiteral(u"encryption-robustness=HW_SECURE_ALL"); + } else { + contentType.AppendLiteral(u"encryption-robustness=SW_SECURE_DECODE"); + } + } + contentType.AppendLiteral(u"\""); + // End of the query string + + // PlayReady doesn't implement IsTypeSupported properly, so it requires us to + // use another way to check the capabilities. + if (IsPlayReadyKeySystemAndSupported(aKeySystem) && + StaticPrefs::media_eme_playready_istypesupportedex()) { + ComPtr<IMFMediaEngineClassFactory> spFactory; + ComPtr<IMFExtendedDRMTypeSupport> spDrmTypeSupport; + MFCDM_RETURN_BOOL_IF_FAILED( + CoCreateInstance(CLSID_MFMediaEngineClassFactory, NULL, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spFactory))); + MFCDM_RETURN_BOOL_IF_FAILED(spFactory.As(&spDrmTypeSupport)); + BSTR keySystem = aIsHWSecure + ? CreateBSTRFromConstChar(kPlayReadyKeySystemHardware) + : CreateBSTRFromConstChar(kPlayReadyKeySystemName); + MF_MEDIA_ENGINE_CANPLAY canPlay; + spDrmTypeSupport->IsTypeSupportedEx(SysAllocString(contentType.get()), + keySystem, &canPlay); + const bool support = + canPlay != + MF_MEDIA_ENGINE_CANPLAY::MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED; + MFCDM_PARENT_SLOG("IsTypeSupportedEx=%d (key-system=%ls, content-type=%s)", + support, keySystem, + NS_ConvertUTF16toUTF8(contentType).get()); + return support; + } + + // Checking capabilies from CDM's IsTypeSupported. Widevine implements this + // method well. + bool support = IsTypeSupported(aFactory, aKeySystem, &contentType); + MFCDM_PARENT_SLOG("IsTypeSupport=%d (key-system=%s, content-type=%s)", + support, NS_ConvertUTF16toUTF8(aKeySystem).get(), + NS_ConvertUTF16toUTF8(contentType).get()); + return support; +} + +static nsresult IsHDCPVersionSupported( + ComPtr<IMFContentDecryptionModuleFactory>& aFactory, + const nsString& aKeySystem, const dom::HDCPVersion& aMinHdcpVersion) { + nsresult rv = NS_OK; + // Codec doesn't matter when querying the HDCP policy, so use H264. + if (!FactorySupports(aFactory, aKeySystem, nsCString("avc1"), + KeySystemConfig::EMECodecString(""), + GetHdcpPolicy(aMinHdcpVersion))) { + rv = NS_ERROR_DOM_MEDIA_CDM_HDCP_NOT_SUPPORT; + } + return rv; +} + +static bool IsKeySystemHWSecure( + const nsAString& aKeySystem, + const nsTArray<MFCDMMediaCapability>& aCapabilities) { + if (IsPlayReadyKeySystemAndSupported(aKeySystem)) { + if (aKeySystem.EqualsLiteral(kPlayReadyKeySystemHardware) || + aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) { + return true; + } + for (const auto& capabilities : aCapabilities) { + if (capabilities.robustness().EqualsLiteral("3000")) { + return true; + } + } + } + if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) || + IsWidevineKeySystem(aKeySystem)) { + // We only support Widevine HWDRM. + return true; + } + return false; +} + +/* static */ +RefPtr<MFCDMParent::CapabilitiesPromise> +MFCDMParent::GetAllKeySystemsCapabilities() { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsISerialEventTarget> backgroundTaskQueue; + if (NS_FAILED(NS_CreateBackgroundTaskQueue( + __func__, getter_AddRefs(backgroundTaskQueue)))) { + MFCDM_PARENT_SLOG( + "Failed to create task queue for all key systems capabilities!"); + return CapabilitiesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__); + } + + RefPtr<CapabilitiesPromise::Private> p = + new CapabilitiesPromise::Private(__func__); + Unused << backgroundTaskQueue->Dispatch(NS_NewRunnableFunction(__func__, [p] { + MFCDM_PARENT_SLOG("GetAllKeySystemsCapabilities"); + if (sCapabilities.IsEmpty()) { + enum SecureLevel : bool { + Software = false, + Hardware = true, + }; + const nsTArray<std::pair<nsString, SecureLevel>> kKeySystems{ + std::pair<nsString, SecureLevel>( + NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName), + SecureLevel::Software), + std::pair<nsString, SecureLevel>( + NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware), + SecureLevel::Hardware), + std::pair<nsString, SecureLevel>( + NS_ConvertUTF8toUTF16(kPlayReadyHardwareClearLeadKeySystemName), + SecureLevel::Hardware), + std::pair<nsString, SecureLevel>( + NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName), + SecureLevel::Hardware), + std::pair<nsString, SecureLevel>( + NS_ConvertUTF8toUTF16(kWidevineExperiment2KeySystemName), + SecureLevel::Hardware), + }; + for (const auto& keySystem : kKeySystems) { + // Only check the capabilites if the relative prefs for the key system + // are ON. + if (IsPlayReadyKeySystemAndSupported(keySystem.first) || + IsWidevineExperimentKeySystemAndSupported(keySystem.first)) { + MFCDMCapabilitiesIPDL* c = sCapabilities.AppendElement(); + GetCapabilities(keySystem.first, keySystem.second, nullptr, *c); + } + } + } + p->Resolve(sCapabilities, __func__); + })); + return p; +} + +/* static */ +void MFCDMParent::GetCapabilities(const nsString& aKeySystem, + const bool aIsHWSecure, + IMFContentDecryptionModuleFactory* aFactory, + MFCDMCapabilitiesIPDL& aCapabilitiesOut) { + aCapabilitiesOut.keySystem() = aKeySystem; + // WMF CDMs usually require these. See + // https://source.chromium.org/chromium/chromium/src/+/main:media/cdm/win/media_foundation_cdm_factory.cc;l=69-73;drc=b3ca5c09fa0aa07b7f9921501f75e43d80f3ba48 + aCapabilitiesOut.persistentState() = KeySystemConfig::Requirement::Required; + aCapabilitiesOut.distinctiveID() = KeySystemConfig::Requirement::Required; + + // Return empty capabilites for SWDRM on Windows 10 because it has the process + // leaking problem. + if (!IsWin11OrLater() && !aIsHWSecure) { + return; + } + + ComPtr<IMFContentDecryptionModuleFactory> factory = aFactory; + if (!factory) { + RETURN_VOID_IF_FAILED(GetOrCreateFactory(aKeySystem, factory)); + } + + // Widevine requires codec type to be four CC, PlayReady is fine with both. + static auto convertCodecToFourCC = + [](const KeySystemConfig::EMECodecString& aCodec) { + if (aCodec.Equals(KeySystemConfig::EME_CODEC_H264)) { + return "avc1"_ns; + } + if (aCodec.Equals(KeySystemConfig::EME_CODEC_VP8)) { + return "vp80"_ns; + } + if (aCodec.Equals(KeySystemConfig::EME_CODEC_VP9)) { + return "vp09"_ns; + } + if (aCodec.Equals(KeySystemConfig::EME_CODEC_HEVC)) { + return "hev1"_ns; + } + // TODO : support AV1? + if (aCodec.Equals(KeySystemConfig::EME_CODEC_AAC)) { + return "mp4a"_ns; + } + if (aCodec.Equals(KeySystemConfig::EME_CODEC_OPUS)) { + return "Opus"_ns; + } + if (aCodec.Equals(KeySystemConfig::EME_CODEC_VORBIS)) { + return "vrbs"_ns; + } + if (aCodec.Equals(KeySystemConfig::EME_CODEC_FLAC)) { + return "fLaC"_ns; + } + MOZ_ASSERT_UNREACHABLE("Unsupported codec"); + return "none"_ns; + }; + + // TODO : add AV1 + static nsTArray<KeySystemConfig::EMECodecString> kVideoCodecs({ + KeySystemConfig::EME_CODEC_H264, + KeySystemConfig::EME_CODEC_VP8, + KeySystemConfig::EME_CODEC_VP9, + KeySystemConfig::EME_CODEC_HEVC, + }); + + // Remember supported video codecs. + // It will be used when collecting audio codec and encryption scheme + // support. + nsTArray<KeySystemConfig::EMECodecString> supportedVideoCodecs; + for (const auto& codec : kVideoCodecs) { + if (codec == KeySystemConfig::EME_CODEC_HEVC && + !StaticPrefs::media_wmf_hevc_enabled()) { + continue; + } + if (FactorySupports(factory, aKeySystem, convertCodecToFourCC(codec), + KeySystemConfig::EMECodecString(""), nsString(u""), + aIsHWSecure)) { + MFCDMMediaCapability* c = + aCapabilitiesOut.videoCapabilities().AppendElement(); + c->contentType() = NS_ConvertUTF8toUTF16(codec); + c->robustness() = + GetRobustnessStringForKeySystem(aKeySystem, aIsHWSecure); + MFCDM_PARENT_SLOG("%s: +video:%s", __func__, codec.get()); + supportedVideoCodecs.AppendElement(codec); + } + } + if (supportedVideoCodecs.IsEmpty()) { + // Return a capabilities with no codec supported. + return; + } + + static nsTArray<KeySystemConfig::EMECodecString> kAudioCodecs({ + KeySystemConfig::EME_CODEC_AAC, + KeySystemConfig::EME_CODEC_FLAC, + KeySystemConfig::EME_CODEC_OPUS, + KeySystemConfig::EME_CODEC_VORBIS, + }); + for (const auto& codec : kAudioCodecs) { + if (FactorySupports( + factory, aKeySystem, convertCodecToFourCC(supportedVideoCodecs[0]), + convertCodecToFourCC(codec), nsString(u""), aIsHWSecure)) { + MFCDMMediaCapability* c = + aCapabilitiesOut.audioCapabilities().AppendElement(); + c->contentType() = NS_ConvertUTF8toUTF16(codec); + c->robustness() = GetRobustnessStringForKeySystem(aKeySystem, aIsHWSecure, + false /* isVideo */); + MFCDM_PARENT_SLOG("%s: +audio:%s", __func__, codec.get()); + } + } + + // Collect schemes supported by all video codecs. + static nsTArray<std::pair<CryptoScheme, nsDependentString>> kSchemes = { + std::pair<CryptoScheme, nsDependentString>( + CryptoScheme::Cenc, u"encryption-type=cenc,encryption-iv-size=8,"), + std::pair<CryptoScheme, nsDependentString>( + CryptoScheme::Cbcs, u"encryption-type=cbcs,encryption-iv-size=16,")}; + for (auto& scheme : kSchemes) { + bool ok = true; + for (auto& codec : supportedVideoCodecs) { + ok &= FactorySupports( + factory, aKeySystem, convertCodecToFourCC(codec), nsCString(""), + scheme.second /* additional feature */, aIsHWSecure); + if (!ok) { + break; + } + } + if (ok) { + aCapabilitiesOut.encryptionSchemes().AppendElement(scheme.first); + MFCDM_PARENT_SLOG("%s: +scheme:%s", __func__, + scheme.first == CryptoScheme::Cenc ? "cenc" : "cbcs"); + } + } + + static auto RequireClearLead = [](const nsString& aKeySystem) { + if (aKeySystem.EqualsLiteral(kWidevineExperiment2KeySystemName) || + aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) { + return true; + } + return false; + }; + + // For key system requires clearlead, every codec needs to have clear support. + // If not, then we will remove the codec from supported codec. + if (RequireClearLead(aKeySystem)) { + for (const auto& scheme : aCapabilitiesOut.encryptionSchemes()) { + nsTArray<KeySystemConfig::EMECodecString> noClearLeadCodecs; + for (const auto& codec : supportedVideoCodecs) { + nsAutoString additionalFeature(u"encryption-type="); + // If we don't specify 'encryption-iv-size', it would use 8 bytes IV as + // default [1]. If it's not supported, then we will try 16 bytes later. + // Since PlayReady 4.0 [2], 8 and 16 bytes IV are both supported. But + // We're not sure if Widevine supports both or not. + // [1] + // https://learn.microsoft.com/en-us/windows/win32/api/mfmediaengine/nf-mfmediaengine-imfextendeddrmtypesupport-istypesupportedex + // [2] + // https://learn.microsoft.com/en-us/playready/packaging/content-encryption-modes#initialization-vectors-ivs + if (scheme == CryptoScheme::Cenc) { + additionalFeature.AppendLiteral(u"cenc-clearlead,"); + } else { + additionalFeature.AppendLiteral(u"cbcs-clearlead,"); + } + bool rv = + FactorySupports(factory, aKeySystem, convertCodecToFourCC(codec), + nsCString(""), additionalFeature, aIsHWSecure); + MFCDM_PARENT_SLOG("clearlead %s IV 8 bytes %s %s", + CryptoSchemeToString(scheme), codec.get(), + rv ? "supported" : "not supported"); + if (rv) { + continue; + } + // Try 16 bytes IV. + additionalFeature.AppendLiteral(u"encryption-iv-size=16,"); + rv = FactorySupports(factory, aKeySystem, convertCodecToFourCC(codec), + nsCString(""), additionalFeature, aIsHWSecure); + MFCDM_PARENT_SLOG("clearlead %s IV 16 bytes %s %s", + CryptoSchemeToString(scheme), codec.get(), + rv ? "supported" : "not supported"); + // Failed on both, so remove the codec from supported codec. + if (!rv) { + noClearLeadCodecs.AppendElement(codec); + } + } + for (const auto& codec : noClearLeadCodecs) { + MFCDM_PARENT_SLOG("%s: -video:%s", __func__, codec.get()); + aCapabilitiesOut.videoCapabilities().RemoveElementsBy( + [&codec](const MFCDMMediaCapability& aCapbilities) { + return aCapbilities.contentType() == NS_ConvertUTF8toUTF16(codec); + }); + supportedVideoCodecs.RemoveElement(codec); + } + } + } + + if (IsHDCPVersionSupported(factory, aKeySystem, dom::HDCPVersion::_2_2) == + NS_OK) { + aCapabilitiesOut.isHDCP22Compatible() = true; + } + + // TODO: don't hardcode + aCapabilitiesOut.initDataTypes().AppendElement(u"keyids"); + aCapabilitiesOut.initDataTypes().AppendElement(u"cenc"); + aCapabilitiesOut.sessionTypes().AppendElement( + KeySystemConfig::SessionType::Temporary); + aCapabilitiesOut.sessionTypes().AppendElement( + KeySystemConfig::SessionType::PersistentLicense); +} + +mozilla::ipc::IPCResult MFCDMParent::RecvGetCapabilities( + const bool aIsHWSecure, GetCapabilitiesResolver&& aResolver) { + MFCDM_REJECT_IF(!mFactory, NS_ERROR_DOM_NOT_SUPPORTED_ERR); + MFCDMCapabilitiesIPDL capabilities; + GetCapabilities(mKeySystem, aIsHWSecure, mFactory.Get(), capabilities); + aResolver(std::move(capabilities)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMParent::RecvInit( + const MFCDMInitParamsIPDL& aParams, InitResolver&& aResolver) { + static auto RequirementToStr = [](KeySystemConfig::Requirement aRequirement) { + switch (aRequirement) { + case KeySystemConfig::Requirement::Required: + return "Required"; + case KeySystemConfig::Requirement::Optional: + return "Optional"; + default: + return "NotAllowed"; + } + }; + + MFCDM_PARENT_LOG( + "Creating a CDM (key-system=%s, origin=%s, distinctiveID=%s, " + "persistentState=%s, " + "hwSecure=%d)", + NS_ConvertUTF16toUTF8(mKeySystem).get(), + NS_ConvertUTF16toUTF8(aParams.origin()).get(), + RequirementToStr(aParams.distinctiveID()), + RequirementToStr(aParams.persistentState()), + IsKeySystemHWSecure(mKeySystem, aParams.videoCapabilities())); + MOZ_ASSERT(IsTypeSupported(mFactory, mKeySystem)); + + MFCDM_REJECT_IF_FAILED(CreateContentDecryptionModule( + mFactory, MapKeySystem(mKeySystem), aParams, mCDM), + NS_ERROR_FAILURE); + MOZ_ASSERT(mCDM); + MFCDM_PARENT_LOG("Created a CDM!"); + + // This is only required by PlayReady. + if (IsPlayReadyKeySystemAndSupported(mKeySystem)) { + ComPtr<IMFPMPHost> pmpHost; + ComPtr<IMFGetService> cdmService; + MFCDM_REJECT_IF_FAILED(mCDM.As(&cdmService), NS_ERROR_FAILURE); + MFCDM_REJECT_IF_FAILED( + cdmService->GetService(MF_CONTENTDECRYPTIONMODULE_SERVICE, + IID_PPV_ARGS(&pmpHost)), + NS_ERROR_FAILURE); + MFCDM_REJECT_IF_FAILED(SUCCEEDED(MakeAndInitialize<MFPMPHostWrapper>( + &mPMPHostWrapper, pmpHost)), + NS_ERROR_FAILURE); + MFCDM_REJECT_IF_FAILED(mCDM->SetPMPHostApp(mPMPHostWrapper.Get()), + NS_ERROR_FAILURE); + MFCDM_PARENT_LOG("Set PMPHostWrapper on CDM!"); + } + + aResolver(MFCDMInitIPDL{mId}); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMParent::RecvCreateSessionAndGenerateRequest( + const MFCDMCreateSessionParamsIPDL& aParams, + CreateSessionAndGenerateRequestResolver&& aResolver) { + MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call"); + + static auto SessionTypeToStr = [](KeySystemConfig::SessionType aSessionType) { + switch (aSessionType) { + case KeySystemConfig::SessionType::Temporary: + return "temporary"; + case KeySystemConfig::SessionType::PersistentLicense: + return "persistent-license"; + default: { + MOZ_ASSERT_UNREACHABLE("Unsupported license type!"); + return "invalid"; + } + } + }; + MFCDM_PARENT_LOG("Creating session for type '%s'", + SessionTypeToStr(aParams.sessionType())); + UniquePtr<MFCDMSession> session{ + MFCDMSession::Create(aParams.sessionType(), mCDM.Get(), mManagerThread)}; + if (!session) { + MFCDM_PARENT_LOG("Failed to create CDM session"); + aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); + return IPC_OK(); + } + + MFCDM_REJECT_IF_FAILED(session->GenerateRequest(aParams.initDataType(), + aParams.initData().Elements(), + aParams.initData().Length()), + NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR); + ConnectSessionEvents(session.get()); + + // TODO : now we assume all session ID is available after session is + // created, but this is not always true. Need to remove this assertion and + // handle cases where session Id is not available yet. + const auto& sessionId = session->SessionID(); + MOZ_ASSERT(sessionId); + mSessions.emplace(*sessionId, std::move(session)); + MFCDM_PARENT_LOG("Created a CDM session!"); + aResolver(*sessionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMParent::RecvLoadSession( + const KeySystemConfig::SessionType& aSessionType, + const nsString& aSessionId, LoadSessionResolver&& aResolver) { + MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call"); + + nsresult rv = NS_OK; + auto* session = GetSession(aSessionId); + if (!session) { + aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); + return IPC_OK(); + } + MFCDM_REJECT_IF_FAILED(session->Load(aSessionId), + NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR); + aResolver(rv); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMParent::RecvUpdateSession( + const nsString& aSessionId, const CopyableTArray<uint8_t>& aResponse, + UpdateSessionResolver&& aResolver) { + MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call"); + nsresult rv = NS_OK; + auto* session = GetSession(aSessionId); + if (!session) { + aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); + return IPC_OK(); + } + MFCDM_REJECT_IF_FAILED(session->Update(aResponse), + NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR); + aResolver(rv); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMParent::RecvCloseSession( + const nsString& aSessionId, UpdateSessionResolver&& aResolver) { + MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call"); + nsresult rv = NS_OK; + auto* session = GetSession(aSessionId); + if (!session) { + aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); + return IPC_OK(); + } + MFCDM_REJECT_IF_FAILED(session->Close(), + NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR); + aResolver(rv); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMParent::RecvRemoveSession( + const nsString& aSessionId, UpdateSessionResolver&& aResolver) { + MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call"); + nsresult rv = NS_OK; + auto* session = GetSession(aSessionId); + if (!session) { + aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); + return IPC_OK(); + } + MFCDM_REJECT_IF_FAILED(session->Remove(), + NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR); + aResolver(rv); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMParent::RecvSetServerCertificate( + const CopyableTArray<uint8_t>& aCertificate, + UpdateSessionResolver&& aResolver) { + MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call"); + nsresult rv = NS_OK; + MFCDM_PARENT_LOG("Set server certificate"); + MFCDM_REJECT_IF_FAILED(mCDM->SetServerCertificate( + static_cast<const BYTE*>(aCertificate.Elements()), + aCertificate.Length()), + NS_ERROR_DOM_MEDIA_CDM_ERR); + aResolver(rv); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFCDMParent::RecvGetStatusForPolicy( + const dom::HDCPVersion& aMinHdcpVersion, + GetStatusForPolicyResolver&& aResolver) { + MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call"); + aResolver(IsHDCPVersionSupported(mFactory, mKeySystem, aMinHdcpVersion)); + return IPC_OK(); +} + +void MFCDMParent::ConnectSessionEvents(MFCDMSession* aSession) { + // TODO : clear session's event source when the session gets removed. + mKeyMessageEvents.Forward(aSession->KeyMessageEvent()); + mKeyChangeEvents.Forward(aSession->KeyChangeEvent()); + mExpirationEvents.Forward(aSession->ExpirationEvent()); +} + +MFCDMSession* MFCDMParent::GetSession(const nsString& aSessionId) { + AssertOnManagerThread(); + auto iter = mSessions.find(aSessionId); + if (iter == mSessions.end()) { + return nullptr; + } + return iter->second.get(); +} + +already_AddRefed<MFCDMProxy> MFCDMParent::GetMFCDMProxy() { + if (!mCDM) { + return nullptr; + } + RefPtr<MFCDMProxy> proxy = new MFCDMProxy(mCDM.Get(), mId); + return proxy.forget(); +} + +/* static */ +void MFCDMService::GetAllKeySystemsCapabilities(dom::Promise* aPromise) { + MOZ_ASSERT(XRE_IsParentProcess()); + const static auto kSandboxKind = ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM; + LaunchMFCDMProcessIfNeeded(kSandboxKind) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [promise = RefPtr(aPromise)]() { + RefPtr<ipc::UtilityAudioDecoderChild> uadc = + ipc::UtilityAudioDecoderChild::GetSingleton(kSandboxKind); + if (NS_WARN_IF(!uadc)) { + promise->MaybeReject(NS_ERROR_FAILURE); + return; + } + uadc->GetKeySystemCapabilities(promise); + }, + [promise = RefPtr(aPromise)](nsresult aError) { + promise->MaybeReject(NS_ERROR_FAILURE); + }); +} + +/* static */ +RefPtr<GenericNonExclusivePromise> MFCDMService::LaunchMFCDMProcessIfNeeded( + ipc::SandboxingKind aSandbox) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(aSandbox == ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM); + RefPtr<ipc::UtilityProcessManager> utilityProc = + ipc::UtilityProcessManager::GetSingleton(); + if (NS_WARN_IF(!utilityProc)) { + NS_WARNING("Failed to get UtilityProcessManager"); + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + + // Check if the MFCDM process exists or not. If not, launch it. + if (utilityProc->Process(aSandbox)) { + return GenericNonExclusivePromise::CreateAndResolve(true, __func__); + } + + RefPtr<ipc::UtilityAudioDecoderChild> uadc = + ipc::UtilityAudioDecoderChild::GetSingleton(aSandbox); + if (NS_WARN_IF(!uadc)) { + NS_WARNING("Failed to get UtilityAudioDecoderChild"); + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + return utilityProc->StartUtility(uadc, aSandbox) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [uadc, utilityProc, aSandbox]() { + RefPtr<ipc::UtilityProcessParent> parent = + utilityProc->GetProcessParent(aSandbox); + if (!parent) { + NS_WARNING("UtilityAudioDecoderParent lost in the middle"); + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + } + + if (!uadc->CanSend()) { + NS_WARNING("UtilityAudioDecoderChild lost in the middle"); + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + } + return GenericNonExclusivePromise::CreateAndResolve(true, __func__); + }, + [](nsresult aError) { + NS_WARNING("Failed to start the MFCDM process!"); + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + }); +} + +/* static */ +void MFCDMService::UpdateWidevineL1Path(nsIFile* aFile) { + RefPtr<ipc::UtilityProcessManager> utilityProc = + ipc::UtilityProcessManager::GetSingleton(); + if (NS_WARN_IF(!utilityProc)) { + NS_WARNING("Failed to get UtilityProcessManager"); + return; + } + + // If the MFCDM process hasn't been created yet, then we will set the path + // when creating the process later. + const auto sandboxKind = ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM; + if (!utilityProc->Process(sandboxKind)) { + return; + } + + // The MFCDM process has been started, we need to update its L1 path and set + // the permission for LPAC. + nsString widevineL1Path; + MOZ_ASSERT(aFile); + if (NS_WARN_IF(NS_FAILED(aFile->GetTarget(widevineL1Path)))) { + NS_WARNING("MFCDMService::UpdateWidevineL1Path, Failed to get L1 path!"); + return; + } + + RefPtr<ipc::UtilityAudioDecoderChild> uadc = + ipc::UtilityAudioDecoderChild::GetSingleton(sandboxKind); + if (NS_WARN_IF(!uadc)) { + NS_WARNING("Failed to get UtilityAudioDecoderChild"); + return; + } + Unused << uadc->SendUpdateWidevineL1Path(widevineL1Path); +#ifdef MOZ_WMF_CDM_LPAC_SANDBOX + SandboxBroker::EnsureLpacPermsissionsOnDir(widevineL1Path); +#endif +} + +#undef MFCDM_REJECT_IF_FAILED +#undef MFCDM_REJECT_IF +#undef MFCDM_RETURN_IF_FAILED +#undef MFCDM_RETURN_BOOL_IF_FAILED +#undef MFCDM_PARENT_SLOG +#undef MFCDM_PARENT_LOG + +} // namespace mozilla diff --git a/dom/media/ipc/MFCDMParent.h b/dom/media/ipc/MFCDMParent.h new file mode 100644 index 0000000000..b4ef1b831b --- /dev/null +++ b/dom/media/ipc/MFCDMParent.h @@ -0,0 +1,168 @@ +/* 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 DOM_MEDIA_IPC_MFCDMPARENT_H_ +#define DOM_MEDIA_IPC_MFCDMPARENT_H_ + +#include <wrl.h> + +#include "mozilla/Assertions.h" +#include "mozilla/PMFCDMParent.h" +#include "MFCDMExtra.h" +#include "MFCDMSession.h" +#include "MFPMPHostWrapper.h" +#include "RemoteDecoderManagerParent.h" + +namespace mozilla { + +class MFCDMProxy; + +/** + * MFCDMParent is a wrapper class for the Media Foundation CDM in the utility + * process. + * It's responsible to create and manage a CDM and its sessions, and acts as a + * proxy to the Media Foundation interfaces + * (https://learn.microsoft.com/en-us/windows/win32/api/mfcontentdecryptionmodule/) + * by accepting calls from and calling back to MFCDMChild in the content + * process. + */ +class MFCDMParent final : public PMFCDMParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MFCDMParent); + + MFCDMParent(const nsAString& aKeySystem, RemoteDecoderManagerParent* aManager, + nsISerialEventTarget* aManagerThread); + + static void SetWidevineL1Path(const char* aPath); + + // Perform clean-up when shutting down the MFCDM process. + static void Shutdown(); + + // Return capabilities from all key systems which the media foundation CDM + // supports. + using CapabilitiesPromise = + MozPromise<CopyableTArray<MFCDMCapabilitiesIPDL>, nsresult, true>; + static RefPtr<CapabilitiesPromise> GetAllKeySystemsCapabilities(); + + static MFCDMParent* GetCDMById(uint64_t aId) { + MOZ_ASSERT(sRegisteredCDMs.Contains(aId)); + return sRegisteredCDMs.Get(aId); + } + uint64_t Id() const { return mId; } + + mozilla::ipc::IPCResult RecvGetCapabilities( + const bool aIsHWSecured, GetCapabilitiesResolver&& aResolver); + + mozilla::ipc::IPCResult RecvInit(const MFCDMInitParamsIPDL& aParams, + InitResolver&& aResolver); + + mozilla::ipc::IPCResult RecvCreateSessionAndGenerateRequest( + const MFCDMCreateSessionParamsIPDL& aParams, + CreateSessionAndGenerateRequestResolver&& aResolver); + + mozilla::ipc::IPCResult RecvLoadSession( + const KeySystemConfig::SessionType& aSessionType, + const nsString& aSessionId, LoadSessionResolver&& aResolver); + + mozilla::ipc::IPCResult RecvUpdateSession( + const nsString& aSessionId, const CopyableTArray<uint8_t>& aResponse, + UpdateSessionResolver&& aResolver); + + mozilla::ipc::IPCResult RecvCloseSession(const nsString& aSessionId, + UpdateSessionResolver&& aResolver); + + mozilla::ipc::IPCResult RecvRemoveSession(const nsString& aSessionId, + UpdateSessionResolver&& aResolver); + + mozilla::ipc::IPCResult RecvSetServerCertificate( + const CopyableTArray<uint8_t>& aCertificate, + UpdateSessionResolver&& aResolver); + + mozilla::ipc::IPCResult RecvGetStatusForPolicy( + const dom::HDCPVersion& aMinHdcpVersion, + GetStatusForPolicyResolver&& aResolver); + + nsISerialEventTarget* ManagerThread() { return mManagerThread; } + void AssertOnManagerThread() const { + MOZ_ASSERT(mManagerThread->IsOnCurrentThread()); + } + + already_AddRefed<MFCDMProxy> GetMFCDMProxy(); + + void ShutdownCDM(); + + void Destroy(); + + private: + ~MFCDMParent(); + + static LPCWSTR GetCDMLibraryName(const nsString& aKeySystem); + + static HRESULT GetOrCreateFactory( + const nsString& aKeySystem, + Microsoft::WRL::ComPtr<IMFContentDecryptionModuleFactory>& aFactoryOut); + + static HRESULT LoadFactory( + const nsString& aKeySystem, + Microsoft::WRL::ComPtr<IMFContentDecryptionModuleFactory>& aFactoryOut); + + static void GetCapabilities(const nsString& aKeySystem, + const bool aIsHWSecure, + IMFContentDecryptionModuleFactory* aFactory, + MFCDMCapabilitiesIPDL& aCapabilitiesOut); + + void Register(); + void Unregister(); + + void ConnectSessionEvents(MFCDMSession* aSession); + + MFCDMSession* GetSession(const nsString& aSessionId); + + nsString mKeySystem; + + const RefPtr<RemoteDecoderManagerParent> mManager; + const RefPtr<nsISerialEventTarget> mManagerThread; + + static inline nsTHashMap<nsUint64HashKey, MFCDMParent*> sRegisteredCDMs; + + static inline uint64_t sNextId = 1; + const uint64_t mId; + + static inline BSTR sWidevineL1Path; + + RefPtr<MFCDMParent> mIPDLSelfRef; + Microsoft::WRL::ComPtr<IMFContentDecryptionModuleFactory> mFactory; + Microsoft::WRL::ComPtr<IMFContentDecryptionModule> mCDM; + Microsoft::WRL::ComPtr<MFPMPHostWrapper> mPMPHostWrapper; + + std::map<nsString, UniquePtr<MFCDMSession>> mSessions; + + MediaEventForwarder<MFCDMKeyMessage> mKeyMessageEvents; + MediaEventForwarder<MFCDMKeyStatusChange> mKeyChangeEvents; + MediaEventForwarder<MFCDMKeyExpiration> mExpirationEvents; + + MediaEventListener mKeyMessageListener; + MediaEventListener mKeyChangeListener; + MediaEventListener mExpirationListener; +}; + +// A helper class only used in the chrome process to handle CDM related tasks. +class MFCDMService { + public: + // This is used to display CDM capabilites in `about:support`. + static void GetAllKeySystemsCapabilities(dom::Promise* aPromise); + + // If Widevine L1 is downloaded after the MFCDM process is created, then we + // use this method to update the L1 path and setup L1 permission for the MFCDM + // process. + static void UpdateWidevineL1Path(nsIFile* aFile); + + private: + static RefPtr<GenericNonExclusivePromise> LaunchMFCDMProcessIfNeeded( + ipc::SandboxingKind aSandbox); +}; + +} // namespace mozilla + +#endif // DOM_MEDIA_IPC_MFCDMPARENT_H_ diff --git a/dom/media/ipc/MFCDMSerializers.h b/dom/media/ipc/MFCDMSerializers.h new file mode 100644 index 0000000000..587f30ae09 --- /dev/null +++ b/dom/media/ipc/MFCDMSerializers.h @@ -0,0 +1,58 @@ +/* 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 DOM_MEDIA_IPC_MFCDMSERIALIZERS_H_ +#define DOM_MEDIA_IPC_MFCDMSERIALIZERS_H_ + +#include "ipc/EnumSerializer.h" +#include "MediaData.h" +#include "mozilla/KeySystemConfig.h" +#include "mozilla/dom/MediaKeyMessageEventBinding.h" +#include "mozilla/dom/MediaKeyStatusMapBinding.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::KeySystemConfig::Requirement> + : public ContiguousEnumSerializerInclusive< + mozilla::KeySystemConfig::Requirement, + mozilla::KeySystemConfig::Requirement::Required, + mozilla::KeySystemConfig::Requirement::NotAllowed> {}; + +template <> +struct ParamTraits<mozilla::KeySystemConfig::SessionType> + : public ContiguousEnumSerializerInclusive< + mozilla::KeySystemConfig::SessionType, + mozilla::KeySystemConfig::SessionType::Temporary, + mozilla::KeySystemConfig::SessionType::PersistentLicense> {}; + +template <> +struct ParamTraits<mozilla::CryptoScheme> + : public ContiguousEnumSerializerInclusive<mozilla::CryptoScheme, + mozilla::CryptoScheme::None, + mozilla::CryptoScheme::Cbcs> {}; + +template <> +struct ParamTraits<mozilla::dom::MediaKeyMessageType> + : public ContiguousEnumSerializer< + mozilla::dom::MediaKeyMessageType, + mozilla::dom::MediaKeyMessageType::License_request, + mozilla::dom::MediaKeyMessageType::EndGuard_> {}; + +template <> +struct ParamTraits<mozilla::dom::MediaKeyStatus> + : public ContiguousEnumSerializer<mozilla::dom::MediaKeyStatus, + mozilla::dom::MediaKeyStatus::Usable, + mozilla::dom::MediaKeyStatus::EndGuard_> { +}; + +template <> +struct ParamTraits<mozilla::dom::HDCPVersion> + : public ContiguousEnumSerializer<mozilla::dom::HDCPVersion, + mozilla::dom::HDCPVersion::_1_0, + mozilla::dom::HDCPVersion::EndGuard_> {}; + +} // namespace IPC + +#endif // DOM_MEDIA_IPC_MFCDMSERIALIZERS_H_ diff --git a/dom/media/ipc/MFMediaEngineChild.cpp b/dom/media/ipc/MFMediaEngineChild.cpp new file mode 100644 index 0000000000..02013056d5 --- /dev/null +++ b/dom/media/ipc/MFMediaEngineChild.cpp @@ -0,0 +1,402 @@ +/* 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 "MFMediaEngineChild.h" + +#include "MFMediaEngineUtils.h" +#include "RemoteDecoderManagerChild.h" + +#ifdef MOZ_WMF_CDM +# include "WMFCDMProxy.h" +#endif + +namespace mozilla { + +#define CLOG(msg, ...) \ + MOZ_LOG(gMFMediaEngineLog, LogLevel::Debug, \ + ("MFMediaEngineChild=%p, Id=%" PRId64 ", " msg, this, this->Id(), \ + ##__VA_ARGS__)) + +#define WLOG(msg, ...) \ + MOZ_LOG(gMFMediaEngineLog, LogLevel::Debug, \ + ("MFMediaEngineWrapper=%p, Id=%" PRId64 ", " msg, this, this->Id(), \ + ##__VA_ARGS__)) + +#define WLOGV(msg, ...) \ + MOZ_LOG(gMFMediaEngineLog, LogLevel::Verbose, \ + ("MFMediaEngineWrapper=%p, Id=%" PRId64 ", " msg, this, this->Id(), \ + ##__VA_ARGS__)) + +using media::TimeUnit; + +MFMediaEngineChild::MFMediaEngineChild(MFMediaEngineWrapper* aOwner, + FrameStatistics* aFrameStats) + : mOwner(aOwner), + mManagerThread(RemoteDecoderManagerChild::GetManagerThread()), + mMediaEngineId(0 /* invalid id, will be initialized later */), + mFrameStats(WrapNotNull(aFrameStats)) { + if (mFrameStats->GetPresentedFrames() > 0) { + mAccumulatedPresentedFramesFromPrevEngine = + Some(mFrameStats->GetPresentedFrames()); + } + if (mFrameStats->GetDroppedSinkFrames() > 0) { + mAccumulatedDroppedFramesFromPrevEngine = + Some(mFrameStats->GetDroppedSinkFrames()); + } +} + +RefPtr<GenericNonExclusivePromise> MFMediaEngineChild::Init( + bool aShouldPreload) { + if (!mManagerThread) { + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + + CLOG("Init"); + MOZ_ASSERT(mMediaEngineId == 0); + RefPtr<MFMediaEngineChild> self = this; + RemoteDecoderManagerChild::LaunchUtilityProcessIfNeeded( + RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) + ->Then( + mManagerThread, __func__, + [self, this, aShouldPreload](bool) { + RefPtr<RemoteDecoderManagerChild> manager = + RemoteDecoderManagerChild::GetSingleton( + RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM); + if (!manager || !manager->CanSend()) { + CLOG("Manager not exists or can't send"); + mInitPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__); + return; + } + + mIPDLSelfRef = this; + Unused << manager->SendPMFMediaEngineConstructor(this); + MediaEngineInfoIPDL info(aShouldPreload); + SendInitMediaEngine(info) + ->Then( + mManagerThread, __func__, + [self, this](uint64_t aId) { + mInitEngineRequest.Complete(); + // Id 0 is used to indicate error. + if (aId == 0) { + CLOG("Failed to initialize MFMediaEngineChild"); + mInitPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, + __func__); + return; + } + mMediaEngineId = aId; + CLOG("Initialized MFMediaEngineChild"); + mInitPromiseHolder.ResolveIfExists(true, __func__); + }, + [self, + this](const mozilla::ipc::ResponseRejectReason& aReason) { + mInitEngineRequest.Complete(); + CLOG( + "Failed to initialize MFMediaEngineChild due to " + "IPC failure"); + mInitPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, + __func__); + }) + ->Track(mInitEngineRequest); + }, + [self, this](nsresult aResult) { + CLOG("SendInitMediaEngine Failed"); + self->mInitPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__); + }); + return mInitPromiseHolder.Ensure(__func__); +} + +mozilla::ipc::IPCResult MFMediaEngineChild::RecvRequestSample(TrackType aType, + bool aIsEnough) { + AssertOnManagerThread(); + if (!mOwner) { + return IPC_OK(); + } + if (aType == TrackType::kVideoTrack) { + mOwner->NotifyEvent(aIsEnough ? ExternalEngineEvent::VideoEnough + : ExternalEngineEvent::RequestForVideo); + } else if (aType == TrackType::kAudioTrack) { + mOwner->NotifyEvent(aIsEnough ? ExternalEngineEvent::AudioEnough + : ExternalEngineEvent::RequestForAudio); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineChild::RecvUpdateCurrentTime( + double aCurrentTimeInSecond) { + AssertOnManagerThread(); + if (mOwner) { + mOwner->UpdateCurrentTime(aCurrentTimeInSecond); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineChild::RecvNotifyEvent( + MFMediaEngineEvent aEvent) { + AssertOnManagerThread(); + switch (aEvent) { + case MF_MEDIA_ENGINE_EVENT_FIRSTFRAMEREADY: + mOwner->NotifyEvent(ExternalEngineEvent::LoadedFirstFrame); + break; + case MF_MEDIA_ENGINE_EVENT_LOADEDDATA: + mOwner->NotifyEvent(ExternalEngineEvent::LoadedData); + break; + case MF_MEDIA_ENGINE_EVENT_WAITING: + mOwner->NotifyEvent(ExternalEngineEvent::Waiting); + break; + case MF_MEDIA_ENGINE_EVENT_SEEKED: + mOwner->NotifyEvent(ExternalEngineEvent::Seeked); + break; + case MF_MEDIA_ENGINE_EVENT_BUFFERINGSTARTED: + mOwner->NotifyEvent(ExternalEngineEvent::BufferingStarted); + break; + case MF_MEDIA_ENGINE_EVENT_BUFFERINGENDED: + mOwner->NotifyEvent(ExternalEngineEvent::BufferingEnded); + break; + case MF_MEDIA_ENGINE_EVENT_ENDED: + mOwner->NotifyEvent(ExternalEngineEvent::Ended); + break; + case MF_MEDIA_ENGINE_EVENT_PLAYING: + mOwner->NotifyEvent(ExternalEngineEvent::Playing); + break; + default: + NS_WARNING( + nsPrintfCString("Unhandled event=%s", MediaEngineEventToStr(aEvent)) + .get()); + break; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineChild::RecvNotifyError( + const MediaResult& aError) { + AssertOnManagerThread(); + mOwner->NotifyError(aError); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineChild::RecvUpdateStatisticData( + const StatisticData& aData) { + AssertOnManagerThread(); + const uint64_t currentRenderedFrames = mFrameStats->GetPresentedFrames(); + const uint64_t newRenderedFrames = GetUpdatedRenderedFrames(aData); + // Media engine won't tell us that which stage those dropped frames happened, + // so we treat all of them as the frames dropped in the a/v sync stage (sink). + const uint64_t currentDroppedSinkFrames = mFrameStats->GetDroppedSinkFrames(); + const uint64_t newDroppedSinkFrames = GetUpdatedDroppedFrames(aData); + mFrameStats->Accumulate({0, 0, newRenderedFrames - currentRenderedFrames, 0, + newDroppedSinkFrames - currentDroppedSinkFrames, 0}); + CLOG("Update statictis data (rendered %" PRIu64 " -> %" PRIu64 + ", dropped %" PRIu64 " -> %" PRIu64 ")", + currentRenderedFrames, mFrameStats->GetPresentedFrames(), + currentDroppedSinkFrames, mFrameStats->GetDroppedSinkFrames()); + MOZ_ASSERT(mFrameStats->GetPresentedFrames() >= currentRenderedFrames); + MOZ_ASSERT(mFrameStats->GetDroppedSinkFrames() >= currentDroppedSinkFrames); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineChild::RecvNotifyResizing( + uint32_t aWidth, uint32_t aHeight) { + mOwner->NotifyResizing(aWidth, aHeight); + return IPC_OK(); +} + +uint64_t MFMediaEngineChild::GetUpdatedRenderedFrames( + const StatisticData& aData) { + return mAccumulatedPresentedFramesFromPrevEngine + ? (aData.renderedFrames() + + *mAccumulatedPresentedFramesFromPrevEngine) + : aData.renderedFrames(); +} + +uint64_t MFMediaEngineChild::GetUpdatedDroppedFrames( + const StatisticData& aData) { + return mAccumulatedDroppedFramesFromPrevEngine + ? (aData.droppedFrames() + + *mAccumulatedDroppedFramesFromPrevEngine) + : aData.droppedFrames(); +} + +void MFMediaEngineChild::OwnerDestroyed() { + Unused << ManagerThread()->Dispatch(NS_NewRunnableFunction( + "MFMediaEngineChild::OwnerDestroy", [self = RefPtr{this}, this] { + self->mOwner = nullptr; + // Ask to destroy IPDL. + if (CanSend()) { + MFMediaEngineChild::Send__delete__(this); + } + })); +} + +void MFMediaEngineChild::IPDLActorDestroyed() { + AssertOnManagerThread(); + if (!mShutdown) { + CLOG("Destroyed actor without shutdown, remote process has crashed!"); + mOwner->NotifyError(NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR); + } + mIPDLSelfRef = nullptr; +} + +void MFMediaEngineChild::Shutdown() { + AssertOnManagerThread(); + if (mShutdown) { + return; + } + SendShutdown(); + mInitPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__); + mInitEngineRequest.DisconnectIfExists(); + mShutdown = true; +} + +MFMediaEngineWrapper::MFMediaEngineWrapper(ExternalEngineStateMachine* aOwner, + FrameStatistics* aFrameStats) + : ExternalPlaybackEngine(aOwner), + mEngine(new MFMediaEngineChild(this, aFrameStats)), + mCurrentTimeInSecond(0.0) {} + +RefPtr<GenericNonExclusivePromise> MFMediaEngineWrapper::Init( + bool aShouldPreload) { + WLOG("Init"); + return mEngine->Init(aShouldPreload); +} + +MFMediaEngineWrapper::~MFMediaEngineWrapper() { mEngine->OwnerDestroyed(); } + +void MFMediaEngineWrapper::Play() { + WLOG("Play"); + MOZ_ASSERT(IsInited()); + Unused << ManagerThread()->Dispatch( + NS_NewRunnableFunction("MFMediaEngineWrapper::Play", + [engine = mEngine] { engine->SendPlay(); })); +} + +void MFMediaEngineWrapper::Pause() { + WLOG("Pause"); + MOZ_ASSERT(IsInited()); + Unused << ManagerThread()->Dispatch( + NS_NewRunnableFunction("MFMediaEngineWrapper::Pause", + [engine = mEngine] { engine->SendPause(); })); +} + +void MFMediaEngineWrapper::Seek(const TimeUnit& aTargetTime) { + auto currentTimeInSecond = aTargetTime.ToSeconds(); + mCurrentTimeInSecond = currentTimeInSecond; + WLOG("Seek to %f", currentTimeInSecond); + MOZ_ASSERT(IsInited()); + Unused << ManagerThread()->Dispatch(NS_NewRunnableFunction( + "MFMediaEngineWrapper::Seek", [engine = mEngine, currentTimeInSecond] { + engine->SendSeek(currentTimeInSecond); + })); +} + +void MFMediaEngineWrapper::Shutdown() { + WLOG("Shutdown"); + Unused << ManagerThread()->Dispatch( + NS_NewRunnableFunction("MFMediaEngineWrapper::Shutdown", + [engine = mEngine] { engine->Shutdown(); })); +} + +void MFMediaEngineWrapper::SetPlaybackRate(double aPlaybackRate) { + WLOG("Set playback rate %f", aPlaybackRate); + MOZ_ASSERT(IsInited()); + Unused << ManagerThread()->Dispatch( + NS_NewRunnableFunction("MFMediaEngineWrapper::SetPlaybackRate", + [engine = mEngine, aPlaybackRate] { + engine->SendSetPlaybackRate(aPlaybackRate); + })); +} + +void MFMediaEngineWrapper::SetVolume(double aVolume) { + WLOG("Set volume %f", aVolume); + MOZ_ASSERT(IsInited()); + Unused << ManagerThread()->Dispatch(NS_NewRunnableFunction( + "MFMediaEngineWrapper::SetVolume", + [engine = mEngine, aVolume] { engine->SendSetVolume(aVolume); })); +} + +void MFMediaEngineWrapper::SetLooping(bool aLooping) { + WLOG("Set looping %d", aLooping); + MOZ_ASSERT(IsInited()); + Unused << ManagerThread()->Dispatch(NS_NewRunnableFunction( + "MFMediaEngineWrapper::SetLooping", + [engine = mEngine, aLooping] { engine->SendSetLooping(aLooping); })); +} + +void MFMediaEngineWrapper::SetPreservesPitch(bool aPreservesPitch) { + // Media Engine doesn't support this. +} + +void MFMediaEngineWrapper::NotifyEndOfStream(TrackInfo::TrackType aType) { + WLOG("NotifyEndOfStream, type=%s", TrackTypeToStr(aType)); + MOZ_ASSERT(IsInited()); + Unused << ManagerThread()->Dispatch(NS_NewRunnableFunction( + "MFMediaEngineWrapper::NotifyEndOfStream", + [engine = mEngine, aType] { engine->SendNotifyEndOfStream(aType); })); +} + +void MFMediaEngineWrapper::SetMediaInfo(const MediaInfo& aInfo) { + WLOG("SetMediaInfo, hasAudio=%d, hasVideo=%d, encrypted=%d", aInfo.HasAudio(), + aInfo.HasVideo(), aInfo.IsEncrypted()); + MOZ_ASSERT(IsInited()); + Unused << ManagerThread()->Dispatch(NS_NewRunnableFunction( + "MFMediaEngineWrapper::SetMediaInfo", [engine = mEngine, aInfo] { + MediaInfoIPDL info(aInfo.HasAudio() ? Some(aInfo.mAudio) : Nothing(), + aInfo.HasVideo() ? Some(aInfo.mVideo) : Nothing()); + engine->SendNotifyMediaInfo(info); + })); +} + +bool MFMediaEngineWrapper::SetCDMProxy(CDMProxy* aProxy) { +#ifdef MOZ_WMF_CDM + WMFCDMProxy* proxy = aProxy->AsWMFCDMProxy(); + if (!proxy) { + WLOG("Only WFMCDM Proxy is supported for the media engine!"); + return false; + } + + const uint64_t proxyId = proxy->GetCDMProxyId(); + WLOG("SetCDMProxy, CDM-Id=%" PRIu64, proxyId); + MOZ_ASSERT(IsInited()); + Unused << ManagerThread()->Dispatch(NS_NewRunnableFunction( + "MFMediaEngineWrapper::SetCDMProxy", + [engine = mEngine, proxyId] { engine->SendSetCDMProxyId(proxyId); })); + return true; +#else + return false; +#endif +} + +TimeUnit MFMediaEngineWrapper::GetCurrentPosition() { + return TimeUnit::FromSeconds(mCurrentTimeInSecond); +} + +void MFMediaEngineWrapper::UpdateCurrentTime(double aCurrentTimeInSecond) { + AssertOnManagerThread(); + WLOGV("Update current time %f", aCurrentTimeInSecond); + mCurrentTimeInSecond = aCurrentTimeInSecond; + NotifyEvent(ExternalEngineEvent::Timeupdate); +} + +void MFMediaEngineWrapper::NotifyEvent(ExternalEngineEvent aEvent) { + AssertOnManagerThread(); + WLOGV("Received event %s", ExternalEngineEventToStr(aEvent)); + mOwner->NotifyEvent(aEvent); +} + +void MFMediaEngineWrapper::NotifyError(const MediaResult& aError) { + AssertOnManagerThread(); + WLOG("Received error: %s", aError.Description().get()); + mOwner->NotifyError(aError); +} + +void MFMediaEngineWrapper::NotifyResizing(uint32_t aWidth, uint32_t aHeight) { + WLOG("Video resizing, new size [%u,%u]", aWidth, aHeight); + mOwner->NotifyResizing(aWidth, aHeight); +} + +#undef CLOG +#undef WLOG +#undef WLOGV + +} // namespace mozilla diff --git a/dom/media/ipc/MFMediaEngineChild.h b/dom/media/ipc/MFMediaEngineChild.h new file mode 100644 index 0000000000..92de3b9483 --- /dev/null +++ b/dom/media/ipc/MFMediaEngineChild.h @@ -0,0 +1,138 @@ +/* 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 DOM_MEDIA_IPC_MFMEDIAENGINECHILD_H_ +#define DOM_MEDIA_IPC_MFMEDIAENGINECHILD_H_ + +#include "ExternalEngineStateMachine.h" +#include "MFMediaEngineUtils.h" +#include "TimeUnits.h" +#include "mozilla/Atomics.h" +#include "mozilla/PMFMediaEngineChild.h" +#include "mozilla/NotNull.h" + +namespace mozilla { + +class MFMediaEngineWrapper; + +/** + * MFMediaEngineChild is a wrapper class for a MediaEngine in the content + * process. It communicates with MFMediaEngineParent in the remote process by + * using IPDL interfaces to send commands to the MediaEngine. + * https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/nn-mfmediaengine-imfmediaengine + */ +class MFMediaEngineChild final : public PMFMediaEngineChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MFMediaEngineChild); + + MFMediaEngineChild(MFMediaEngineWrapper* aOwner, + FrameStatistics* aFrameStats); + + void OwnerDestroyed(); + void IPDLActorDestroyed(); + + RefPtr<GenericNonExclusivePromise> Init(bool aShouldPreload); + void Shutdown(); + + // Methods for PMFMediaEngineChild + mozilla::ipc::IPCResult RecvRequestSample(TrackInfo::TrackType aType, + bool aIsEnough); + mozilla::ipc::IPCResult RecvUpdateCurrentTime(double aCurrentTimeInSecond); + mozilla::ipc::IPCResult RecvNotifyEvent(MFMediaEngineEvent aEvent); + mozilla::ipc::IPCResult RecvNotifyError(const MediaResult& aError); + mozilla::ipc::IPCResult RecvUpdateStatisticData(const StatisticData& aData); + mozilla::ipc::IPCResult RecvNotifyResizing(uint32_t aWidth, uint32_t aHeight); + + nsISerialEventTarget* ManagerThread() { return mManagerThread; } + void AssertOnManagerThread() const { + MOZ_ASSERT(mManagerThread->IsOnCurrentThread()); + } + + uint64_t Id() const { return mMediaEngineId; } + + private: + ~MFMediaEngineChild() = default; + + uint64_t GetUpdatedRenderedFrames(const StatisticData& aData); + uint64_t GetUpdatedDroppedFrames(const StatisticData& aData); + + // Only modified on the manager thread. + MFMediaEngineWrapper* MOZ_NON_OWNING_REF mOwner; + + const nsCOMPtr<nsISerialEventTarget> mManagerThread; + + // This represents an unique Id to indentify the media engine in the remote + // process. Zero is used for the status before initializaing Id from the + // remote process. + // Modified on the manager thread, and read on other threads. + Atomic<uint64_t> mMediaEngineId; + + RefPtr<MFMediaEngineChild> mIPDLSelfRef; + + MozPromiseHolder<GenericNonExclusivePromise> mInitPromiseHolder; + MozPromiseRequestHolder<InitMediaEnginePromise> mInitEngineRequest; + + // This is guaranteed always being alive in our lifetime. + NotNull<FrameStatistics*> const MOZ_NON_OWNING_REF mFrameStats; + + bool mShutdown = false; + + // Whenever the remote media engine process crashes, we will create a new + // engine child to rebuild the connection. These engine child shares the same + // frame stats data so we need to keep accumulate same data from previous + // engine. + Maybe<uint64_t> mAccumulatedPresentedFramesFromPrevEngine; + Maybe<uint64_t> mAccumulatedDroppedFramesFromPrevEngine; +}; + +/** + * MFMediaEngineWrapper acts as an external playback engine, which is + * implemented by using the Media Foundation Media Engine. It holds hold an + * actor used to communicate with the engine in the remote process. Its methods + * are all thread-safe. + */ +class MFMediaEngineWrapper final : public ExternalPlaybackEngine { + public: + MFMediaEngineWrapper(ExternalEngineStateMachine* aOwner, + FrameStatistics* aFrameStats); + ~MFMediaEngineWrapper(); + + // Methods for ExternalPlaybackEngine + RefPtr<GenericNonExclusivePromise> Init(bool aShouldPreload) override; + void Play() override; + void Pause() override; + void Seek(const media::TimeUnit& aTargetTime) override; + void Shutdown() override; + void SetPlaybackRate(double aPlaybackRate) override; + void SetVolume(double aVolume) override; + void SetLooping(bool aLooping) override; + void SetPreservesPitch(bool aPreservesPitch) override; + media::TimeUnit GetCurrentPosition() override; + void NotifyEndOfStream(TrackInfo::TrackType aType) override; + uint64_t Id() const override { return mEngine->Id(); } + void SetMediaInfo(const MediaInfo& aInfo) override; + bool SetCDMProxy(CDMProxy* aProxy) override; + void NotifyResizing(uint32_t aWidth, uint32_t aHeight) override; + + nsISerialEventTarget* ManagerThread() { return mEngine->ManagerThread(); } + void AssertOnManagerThread() const { mEngine->AssertOnManagerThread(); } + + private: + friend class MFMediaEngineChild; + + bool IsInited() const { return mEngine->Id() != 0; } + void UpdateCurrentTime(double aCurrentTimeInSecond); + void NotifyEvent(ExternalEngineEvent aEvent); + void NotifyError(const MediaResult& aError); + + const RefPtr<MFMediaEngineChild> mEngine; + + // The current time which we receive from the MediaEngine or set by the state + // machine when seeking. + std::atomic<double> mCurrentTimeInSecond; +}; + +} // namespace mozilla + +#endif // DOM_MEDIA_IPC_MFMEDIAENGINECHILD_H_ diff --git a/dom/media/ipc/MFMediaEngineParent.cpp b/dom/media/ipc/MFMediaEngineParent.cpp new file mode 100644 index 0000000000..0238956781 --- /dev/null +++ b/dom/media/ipc/MFMediaEngineParent.cpp @@ -0,0 +1,711 @@ +/* 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 "MFMediaEngineParent.h" + +#include <audiosessiontypes.h> +#include <intsafe.h> +#include <mfapi.h> + +#ifdef MOZ_WMF_CDM +# include "MFCDMParent.h" +# include "MFContentProtectionManager.h" +#endif + +#include "MFMediaEngineExtension.h" +#include "MFMediaEngineVideoStream.h" +#include "MFMediaEngineUtils.h" +#include "MFMediaEngineStream.h" +#include "MFMediaSource.h" +#include "RemoteDecoderManagerParent.h" +#include "WMF.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/RemoteDecodeUtils.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/gfx/DeviceManagerDx.h" + +namespace mozilla { + +#define LOG(msg, ...) \ + MOZ_LOG(gMFMediaEngineLog, LogLevel::Debug, \ + ("MFMediaEngineParent=%p, Id=%" PRId64 ", " msg, this, this->Id(), \ + ##__VA_ARGS__)) + +using MediaEngineMap = nsTHashMap<nsUint64HashKey, MFMediaEngineParent*>; +static StaticAutoPtr<MediaEngineMap> sMediaEngines; + +using Microsoft::WRL::ComPtr; +using Microsoft::WRL::MakeAndInitialize; + +StaticMutex sMediaEnginesLock; + +static void RegisterMediaEngine(MFMediaEngineParent* aMediaEngine) { + MOZ_ASSERT(aMediaEngine); + StaticMutexAutoLock lock(sMediaEnginesLock); + if (!sMediaEngines) { + sMediaEngines = new MediaEngineMap(); + } + sMediaEngines->InsertOrUpdate(aMediaEngine->Id(), aMediaEngine); +} + +static void UnregisterMedieEngine(MFMediaEngineParent* aMediaEngine) { + StaticMutexAutoLock lock(sMediaEnginesLock); + if (sMediaEngines) { + sMediaEngines->Remove(aMediaEngine->Id()); + } +} + +/* static */ +MFMediaEngineParent* MFMediaEngineParent::GetMediaEngineById(uint64_t aId) { + StaticMutexAutoLock lock(sMediaEnginesLock); + return sMediaEngines->Get(aId); +} + +MFMediaEngineParent::MFMediaEngineParent(RemoteDecoderManagerParent* aManager, + nsISerialEventTarget* aManagerThread) + : mMediaEngineId(++sMediaEngineIdx), + mManager(aManager), + mManagerThread(aManagerThread) { + MOZ_ASSERT(aManager); + MOZ_ASSERT(aManagerThread); + MOZ_ASSERT(mMediaEngineId != 0); + MOZ_ASSERT(XRE_IsUtilityProcess()); + MOZ_ASSERT(GetCurrentSandboxingKind() == + ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM); + LOG("Created MFMediaEngineParent"); + RegisterMediaEngine(this); + mIPDLSelfRef = this; + CreateMediaEngine(); +} + +MFMediaEngineParent::~MFMediaEngineParent() { + LOG("Destoryed MFMediaEngineParent"); + DestroyEngineIfExists(); + UnregisterMedieEngine(this); +} + +void MFMediaEngineParent::DestroyEngineIfExists( + const Maybe<MediaResult>& aError) { + LOG("DestroyEngineIfExists, hasError=%d", aError.isSome()); + ENGINE_MARKER("MFMediaEngineParent::DestroyEngineIfExists"); + mMediaEngineNotify = nullptr; + mMediaEngineExtension = nullptr; + if (mMediaSource) { + mMediaSource->ShutdownTaskQueue(); + mMediaSource = nullptr; + } +#ifdef MOZ_WMF_CDM + if (mContentProtectionManager) { + mContentProtectionManager->Shutdown(); + mContentProtectionManager = nullptr; + } +#endif + if (mMediaEngine) { + LOG_IF_FAILED(mMediaEngine->Shutdown()); + mMediaEngine = nullptr; + } + mMediaEngineEventListener.DisconnectIfExists(); + mRequestSampleListener.DisconnectIfExists(); + if (mDXGIDeviceManager) { + mDXGIDeviceManager = nullptr; + wmf::MFUnlockDXGIDeviceManager(); + } + if (aError) { + Unused << SendNotifyError(*aError); + } +} + +void MFMediaEngineParent::CreateMediaEngine() { + LOG("CreateMediaEngine"); + auto errorExit = MakeScopeExit([&] { + MediaResult error(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to create engine"); + DestroyEngineIfExists(Some(error)); + }); + + if (!wmf::MediaFoundationInitializer::HasInitialized()) { + NS_WARNING("Failed to initialize media foundation"); + return; + } + + InitializeDXGIDeviceManager(); + + // Create an attribute and set mandatory information that are required for + // a media engine creation. + ComPtr<IMFAttributes> creationAttributes; + RETURN_VOID_IF_FAILED(wmf::MFCreateAttributes(&creationAttributes, 6)); + RETURN_VOID_IF_FAILED( + MakeAndInitialize<MFMediaEngineNotify>(&mMediaEngineNotify)); + mMediaEngineEventListener = mMediaEngineNotify->MediaEngineEvent().Connect( + mManagerThread, this, &MFMediaEngineParent::HandleMediaEngineEvent); + RETURN_VOID_IF_FAILED(creationAttributes->SetUnknown( + MF_MEDIA_ENGINE_CALLBACK, mMediaEngineNotify.Get())); + RETURN_VOID_IF_FAILED(creationAttributes->SetUINT32( + MF_MEDIA_ENGINE_AUDIO_CATEGORY, AudioCategory_Media)); + RETURN_VOID_IF_FAILED( + MakeAndInitialize<MFMediaEngineExtension>(&mMediaEngineExtension)); + RETURN_VOID_IF_FAILED(creationAttributes->SetUnknown( + MF_MEDIA_ENGINE_EXTENSION, mMediaEngineExtension.Get())); + RETURN_VOID_IF_FAILED( + creationAttributes->SetUINT32(MF_MEDIA_ENGINE_CONTENT_PROTECTION_FLAGS, + MF_MEDIA_ENGINE_ENABLE_PROTECTED_CONTENT)); + if (mDXGIDeviceManager) { + RETURN_VOID_IF_FAILED(creationAttributes->SetUnknown( + MF_MEDIA_ENGINE_DXGI_MANAGER, mDXGIDeviceManager.Get())); + } + + ComPtr<IMFMediaEngineClassFactory> factory; + RETURN_VOID_IF_FAILED(CoCreateInstance(CLSID_MFMediaEngineClassFactory, + nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&factory))); + const bool isLowLatency = StaticPrefs::media_wmf_low_latency_enabled(); + static const DWORD MF_MEDIA_ENGINE_DEFAULT = 0; + RETURN_VOID_IF_FAILED(factory->CreateInstance( + isLowLatency ? MF_MEDIA_ENGINE_REAL_TIME_MODE : MF_MEDIA_ENGINE_DEFAULT, + creationAttributes.Get(), &mMediaEngine)); + + LOG("Created media engine successfully"); + mIsCreatedMediaEngine = true; + ENGINE_MARKER("MFMediaEngineParent::CreatedMediaEngine"); + errorExit.release(); +} + +void MFMediaEngineParent::InitializeDXGIDeviceManager() { + auto* deviceManager = gfx::DeviceManagerDx::Get(); + if (!deviceManager) { + return; + } + RefPtr<ID3D11Device> d3d11Device = deviceManager->CreateMediaEngineDevice(); + if (!d3d11Device) { + return; + } + + auto errorExit = MakeScopeExit([&] { + mDXGIDeviceManager = nullptr; + wmf::MFUnlockDXGIDeviceManager(); + }); + UINT deviceResetToken; + RETURN_VOID_IF_FAILED( + wmf::MFLockDXGIDeviceManager(&deviceResetToken, &mDXGIDeviceManager)); + RETURN_VOID_IF_FAILED( + mDXGIDeviceManager->ResetDevice(d3d11Device.get(), deviceResetToken)); + LOG("Initialized DXGI manager"); + errorExit.release(); +} + +#ifndef ENSURE_EVENT_DISPATCH_DURING_PLAYING +# define ENSURE_EVENT_DISPATCH_DURING_PLAYING(event) \ + do { \ + if (mMediaEngine->IsPaused()) { \ + LOG("Ignore incorrect '%s' during pausing!", event); \ + return; \ + } \ + } while (false) +#endif + +void MFMediaEngineParent::HandleMediaEngineEvent( + MFMediaEngineEventWrapper aEvent) { + AssertOnManagerThread(); + LOG("Received media engine event %s", MediaEngineEventToStr(aEvent.mEvent)); + ENGINE_MARKER_TEXT( + "MFMediaEngineParent::HandleMediaEngineEvent", + nsPrintfCString("%s", MediaEngineEventToStr(aEvent.mEvent))); + switch (aEvent.mEvent) { + case MF_MEDIA_ENGINE_EVENT_ERROR: { + MOZ_ASSERT(aEvent.mParam1 && aEvent.mParam2); + auto error = static_cast<MF_MEDIA_ENGINE_ERR>(*aEvent.mParam1); + auto result = static_cast<HRESULT>(*aEvent.mParam2); + NotifyError(error, result); + break; + } + case MF_MEDIA_ENGINE_EVENT_FORMATCHANGE: { + if (mMediaEngine->HasVideo()) { + NotifyVideoResizing(); + } + break; + } + case MF_MEDIA_ENGINE_EVENT_FIRSTFRAMEREADY: { + if (mMediaEngine->HasVideo()) { + EnsureDcompSurfaceHandle(); + } + [[fallthrough]]; + } + case MF_MEDIA_ENGINE_EVENT_LOADEDDATA: + case MF_MEDIA_ENGINE_EVENT_WAITING: + case MF_MEDIA_ENGINE_EVENT_SEEKED: + case MF_MEDIA_ENGINE_EVENT_BUFFERINGSTARTED: + case MF_MEDIA_ENGINE_EVENT_BUFFERINGENDED: + Unused << SendNotifyEvent(aEvent.mEvent); + break; + case MF_MEDIA_ENGINE_EVENT_PLAYING: + ENSURE_EVENT_DISPATCH_DURING_PLAYING( + MediaEngineEventToStr(aEvent.mEvent)); + Unused << SendNotifyEvent(aEvent.mEvent); + break; + case MF_MEDIA_ENGINE_EVENT_ENDED: { + ENSURE_EVENT_DISPATCH_DURING_PLAYING( + MediaEngineEventToStr(aEvent.mEvent)); + Unused << SendNotifyEvent(aEvent.mEvent); + UpdateStatisticsData(); + break; + } + case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE: { + auto currentTimeInSeconds = mMediaEngine->GetCurrentTime(); + Unused << SendUpdateCurrentTime(currentTimeInSeconds); + UpdateStatisticsData(); + break; + } + default: + LOG("Unhandled event=%s", MediaEngineEventToStr(aEvent.mEvent)); + break; + } +} + +void MFMediaEngineParent::NotifyError(MF_MEDIA_ENGINE_ERR aError, + HRESULT aResult) { + // TODO : handle HRESULT 0x8004CD12, DRM_E_TEE_INVALID_HWDRM_STATE, which can + // happen during OS sleep/resume, or moving video to different graphics + // adapters. + if (aError == MF_MEDIA_ENGINE_ERR_NOERROR) { + return; + } + LOG("Notify error '%s', hr=%lx", MFMediaEngineErrorToStr(aError), aResult); + ENGINE_MARKER_TEXT( + "MFMediaEngineParent::NotifyError", + nsPrintfCString("%s, hr=%lx", MFMediaEngineErrorToStr(aError), aResult)); + switch (aError) { + case MF_MEDIA_ENGINE_ERR_ABORTED: + case MF_MEDIA_ENGINE_ERR_NETWORK: + // We ignore these two because we fetch data by ourselves. + return; + case MF_MEDIA_ENGINE_ERR_DECODE: { + MediaResult error(NS_ERROR_DOM_MEDIA_DECODE_ERR, "Decoder error"); + Unused << SendNotifyError(error); + return; + } + case MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED: { + MediaResult error(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, + "Source not supported"); + Unused << SendNotifyError(error); + return; + } + case MF_MEDIA_ENGINE_ERR_ENCRYPTED: { + MediaResult error(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Encrypted error"); + Unused << SendNotifyError(error); + return; + } + default: + MOZ_ASSERT_UNREACHABLE("Unsupported error"); + return; + } +} + +MFMediaEngineStreamWrapper* MFMediaEngineParent::GetMediaEngineStream( + TrackType aType, const CreateDecoderParams& aParam) { + // Has been shutdowned. + if (!mMediaSource) { + return nullptr; + } + LOG("Create a media engine decoder for %s", TrackTypeToStr(aType)); + if (aType == TrackType::kAudioTrack) { + auto* stream = mMediaSource->GetAudioStream(); + return new MFMediaEngineStreamWrapper(stream, stream->GetTaskQueue(), + aParam); + } + MOZ_ASSERT(aType == TrackType::kVideoTrack); + auto* stream = mMediaSource->GetVideoStream(); + stream->AsVideoStream()->SetKnowsCompositor(aParam.mKnowsCompositor); + stream->AsVideoStream()->SetConfig(aParam.mConfig); + return new MFMediaEngineStreamWrapper(stream, stream->GetTaskQueue(), aParam); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvInitMediaEngine( + const MediaEngineInfoIPDL& aInfo, InitMediaEngineResolver&& aResolver) { + AssertOnManagerThread(); + if (!mIsCreatedMediaEngine) { + aResolver(0); + return IPC_OK(); + } + // Metadata preload is controlled by content process side before creating + // media engine. + if (aInfo.preload()) { + // TODO : really need this? + Unused << mMediaEngine->SetPreload(MF_MEDIA_ENGINE_PRELOAD_AUTOMATIC); + } + aResolver(mMediaEngineId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvNotifyMediaInfo( + const MediaInfoIPDL& aInfo) { + AssertOnManagerThread(); + MOZ_ASSERT(mIsCreatedMediaEngine, "Hasn't created media engine?"); + MOZ_ASSERT(!mMediaSource); + + LOG("RecvNotifyMediaInfo"); + + auto errorExit = MakeScopeExit([&] { + MediaResult error(NS_ERROR_DOM_MEDIA_FATAL_ERR, + "Failed to create media source"); + DestroyEngineIfExists(Some(error)); + }); + + // Create media source and set it to the media engine. + NS_ENSURE_TRUE( + SUCCEEDED(MakeAndInitialize<MFMediaSource>( + &mMediaSource, aInfo.audioInfo(), aInfo.videoInfo(), mManagerThread)), + IPC_OK()); + + const bool isEncryted = mMediaSource->IsEncrypted(); + ENGINE_MARKER("MFMediaEngineParent,CreatedMediaSource"); + nsPrintfCString message( + "Created the media source, audio=%s, video=%s, encrypted-audio=%s, " + "encrypted-video=%s, isEncrypted=%d", + aInfo.audioInfo() ? aInfo.audioInfo()->mMimeType.BeginReading() : "none", + aInfo.videoInfo() ? aInfo.videoInfo()->mMimeType.BeginReading() : "none", + aInfo.audioInfo() && aInfo.audioInfo()->mCrypto.IsEncrypted() ? "yes" + : "no", + aInfo.videoInfo() && aInfo.videoInfo()->mCrypto.IsEncrypted() ? "yes" + : "no", + isEncryted); + LOG("%s", message.get()); + + if (aInfo.videoInfo()) { + ComPtr<IMFMediaEngineEx> mediaEngineEx; + RETURN_PARAM_IF_FAILED(mMediaEngine.As(&mediaEngineEx), IPC_OK()); + RETURN_PARAM_IF_FAILED(mediaEngineEx->EnableWindowlessSwapchainMode(true), + IPC_OK()); + LOG("Enabled dcomp swap chain mode"); + ENGINE_MARKER("MFMediaEngineParent,EnabledSwapChain"); + } + + mRequestSampleListener = mMediaSource->RequestSampleEvent().Connect( + mManagerThread, this, &MFMediaEngineParent::HandleRequestSample); + errorExit.release(); + +#ifdef MOZ_WMF_CDM + if (isEncryted && !mContentProtectionManager) { + // We will set the source later when the CDM proxy is ready. + return IPC_OK(); + } + + if (isEncryted && mContentProtectionManager) { + auto* proxy = mContentProtectionManager->GetCDMProxy(); + MOZ_ASSERT(proxy); + mMediaSource->SetCDMProxy(proxy); + } +#endif + + SetMediaSourceOnEngine(); + return IPC_OK(); +} + +void MFMediaEngineParent::SetMediaSourceOnEngine() { + AssertOnManagerThread(); + MOZ_ASSERT(mMediaSource); + + auto errorExit = MakeScopeExit([&] { + MediaResult error(NS_ERROR_DOM_MEDIA_FATAL_ERR, + "Failed to set media source"); + DestroyEngineIfExists(Some(error)); + }); + + mMediaEngineExtension->SetMediaSource(mMediaSource.Get()); + + // We use the source scheme in order to let the media engine to load our + // custom source. + RETURN_VOID_IF_FAILED( + mMediaEngine->SetSource(SysAllocString(L"MFRendererSrc"))); + + LOG("Finished setup our custom media source to the media engine"); + ENGINE_MARKER("MFMediaEngineParent,FinishedSetupMediaSource"); + errorExit.release(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvPlay() { + AssertOnManagerThread(); + if (!mMediaEngine) { + LOG("Engine has been shutdowned!"); + return IPC_OK(); + } + LOG("Play, expected playback rate %f, default playback rate=%f", + mPlaybackRate, mMediaEngine->GetDefaultPlaybackRate()); + ENGINE_MARKER("MFMediaEngineParent,Play"); + NS_ENSURE_TRUE(SUCCEEDED(mMediaEngine->Play()), IPC_OK()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvPause() { + AssertOnManagerThread(); + if (!mMediaEngine) { + LOG("Engine has been shutdowned!"); + return IPC_OK(); + } + LOG("Pause"); + ENGINE_MARKER("MFMediaEngineParent,Pause"); + NS_ENSURE_TRUE(SUCCEEDED(mMediaEngine->Pause()), IPC_OK()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvSeek( + double aTargetTimeInSecond) { + AssertOnManagerThread(); + if (!mMediaEngine) { + return IPC_OK(); + } + + // If the target time is already equal to the current time, the media engine + // doesn't perform seek internally so we won't be able to receive `seeked` + // event. Therefore, we simply return `seeked` here because we're already in + // the target time. + const auto currentTimeInSeconds = mMediaEngine->GetCurrentTime(); + if (currentTimeInSeconds == aTargetTimeInSecond) { + Unused << SendNotifyEvent(MF_MEDIA_ENGINE_EVENT_SEEKED); + return IPC_OK(); + } + + LOG("Seek to %f", aTargetTimeInSecond); + ENGINE_MARKER_TEXT("MFMediaEngineParent,Seek", + nsPrintfCString("%f", aTargetTimeInSecond)); + NS_ENSURE_TRUE(SUCCEEDED(mMediaEngine->SetCurrentTime(aTargetTimeInSecond)), + IPC_OK()); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvSetCDMProxyId( + uint64_t aProxyId) { + if (!mMediaEngine) { + return IPC_OK(); + } +#ifdef MOZ_WMF_CDM + LOG("SetCDMProxy, Id=%" PRIu64, aProxyId); + MFCDMParent* cdmParent = MFCDMParent::GetCDMById(aProxyId); + MOZ_DIAGNOSTIC_ASSERT(cdmParent); + RETURN_PARAM_IF_FAILED( + MakeAndInitialize<MFContentProtectionManager>(&mContentProtectionManager), + IPC_OK()); + + ComPtr<IMFMediaEngineProtectedContent> protectedMediaEngine; + RETURN_PARAM_IF_FAILED(mMediaEngine.As(&protectedMediaEngine), IPC_OK()); + RETURN_PARAM_IF_FAILED(protectedMediaEngine->SetContentProtectionManager( + mContentProtectionManager.Get()), + IPC_OK()); + + RefPtr<MFCDMProxy> proxy = cdmParent->GetMFCDMProxy(); + if (!proxy) { + LOG("Failed to get MFCDMProxy!"); + return IPC_OK(); + } + + RETURN_PARAM_IF_FAILED(mContentProtectionManager->SetCDMProxy(proxy), + IPC_OK()); + // TODO : is it possible to set CDM proxy before creating media source? If so, + // handle that as well. + if (mMediaSource) { + mMediaSource->SetCDMProxy(proxy); + SetMediaSourceOnEngine(); + } + LOG("Set CDM Proxy successfully on the media engine!"); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvSetVolume(double aVolume) { + AssertOnManagerThread(); + if (mMediaEngine) { + LOG("SetVolume=%f", aVolume); + ENGINE_MARKER_TEXT("MFMediaEngineParent,SetVolume", + nsPrintfCString("%f", aVolume)); + NS_ENSURE_TRUE(SUCCEEDED(mMediaEngine->SetVolume(aVolume)), IPC_OK()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvSetPlaybackRate( + double aPlaybackRate) { + AssertOnManagerThread(); + if (aPlaybackRate <= 0) { + LOG("Not support zero or negative playback rate"); + return IPC_OK(); + } + LOG("SetPlaybackRate=%f", aPlaybackRate); + ENGINE_MARKER_TEXT("MFMediaEngineParent,SetPlaybackRate", + nsPrintfCString("%f", aPlaybackRate)); + mPlaybackRate = aPlaybackRate; + NS_ENSURE_TRUE(SUCCEEDED(mMediaEngine->SetPlaybackRate(mPlaybackRate)), + IPC_OK()); + // The media Engine uses the default playback rate to determine the playback + // rate when calling `play()`. So if we don't change default playback rate + // together, the playback rate would fallback to 1 after pausing or + // seeking, which would be different from our expected playback rate. + NS_ENSURE_TRUE(SUCCEEDED(mMediaEngine->SetDefaultPlaybackRate(mPlaybackRate)), + IPC_OK()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvSetLooping(bool aLooping) { + AssertOnManagerThread(); + // We handle looping by seeking back to the head by ourselves, so we don't + // rely on the media engine for looping. + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvNotifyEndOfStream( + TrackInfo::TrackType aType) { + AssertOnManagerThread(); + MOZ_ASSERT(mMediaSource); + LOG("NotifyEndOfStream, type=%s", TrackTypeToStr(aType)); + mMediaSource->NotifyEndOfStream(aType); + return IPC_OK(); +} + +mozilla::ipc::IPCResult MFMediaEngineParent::RecvShutdown() { + AssertOnManagerThread(); + LOG("Shutdown"); + ENGINE_MARKER("MFMediaEngineParent,Shutdown"); + DestroyEngineIfExists(); + return IPC_OK(); +} + +void MFMediaEngineParent::Destroy() { + AssertOnManagerThread(); + mIPDLSelfRef = nullptr; +} + +void MFMediaEngineParent::HandleRequestSample(const SampleRequest& aRequest) { + AssertOnManagerThread(); + MOZ_ASSERT(aRequest.mType == TrackInfo::TrackType::kAudioTrack || + aRequest.mType == TrackInfo::TrackType::kVideoTrack); + Unused << SendRequestSample(aRequest.mType, aRequest.mIsEnough); +} + +void MFMediaEngineParent::AssertOnManagerThread() const { + MOZ_ASSERT(mManagerThread->IsOnCurrentThread()); +} + +Maybe<gfx::IntSize> MFMediaEngineParent::DetectVideoSizeChange() { + AssertOnManagerThread(); + MOZ_ASSERT(mMediaEngine); + MOZ_ASSERT(mMediaEngine->HasVideo()); + + DWORD width, height; + RETURN_PARAM_IF_FAILED(mMediaEngine->GetNativeVideoSize(&width, &height), + Nothing()); + if (width != mDisplayWidth || height != mDisplayHeight) { + ENGINE_MARKER_TEXT("MFMediaEngineParent,VideoSizeChange", + nsPrintfCString("%lux%lu", width, height)); + LOG("Updated video size [%lux%lu] -> [%lux%lu] ", mDisplayWidth, + mDisplayHeight, width, height); + mDisplayWidth = width; + mDisplayHeight = height; + return Some(gfx::IntSize{width, height}); + } + return Nothing(); +} + +void MFMediaEngineParent::EnsureDcompSurfaceHandle() { + AssertOnManagerThread(); + MOZ_ASSERT(mMediaEngine); + MOZ_ASSERT(mMediaEngine->HasVideo()); + + ComPtr<IMFMediaEngineEx> mediaEngineEx; + RETURN_VOID_IF_FAILED(mMediaEngine.As(&mediaEngineEx)); + + // Ensure that the width and height is already up-to-date. + gfx::IntSize size{mDisplayWidth, mDisplayHeight}; + if (auto newSize = DetectVideoSizeChange()) { + size = *newSize; + } + + // Update stream size before asking for a handle. If we don't update the + // size, media engine will create the dcomp surface in a wrong size. + RECT rect = {0, 0, (LONG)size.width, (LONG)size.height}; + RETURN_VOID_IF_FAILED(mediaEngineEx->UpdateVideoStream( + nullptr /* pSrc */, &rect, nullptr /* pBorderClr */)); + + HANDLE surfaceHandle = INVALID_HANDLE_VALUE; + RETURN_VOID_IF_FAILED(mediaEngineEx->GetVideoSwapchainHandle(&surfaceHandle)); + if (surfaceHandle && surfaceHandle != INVALID_HANDLE_VALUE) { + LOG("EnsureDcompSurfaceHandle, handle=%p, size=[%dx%d]", surfaceHandle, + size.width, size.height); + mMediaSource->SetDCompSurfaceHandle(surfaceHandle, size); + } else { + NS_WARNING("SurfaceHandle is not ready yet"); + } +} + +void MFMediaEngineParent::NotifyVideoResizing() { + AssertOnManagerThread(); + if (auto newSize = DetectVideoSizeChange()) { + Unused << SendNotifyResizing(newSize->width, newSize->height); + } +} + +void MFMediaEngineParent::UpdateStatisticsData() { + AssertOnManagerThread(); + + // Statistic data is only for video. + if (!mMediaEngine->HasVideo()) { + return; + } + + ComPtr<IMFMediaEngineEx> mediaEngineEx; + RETURN_VOID_IF_FAILED(mMediaEngine.As(&mediaEngineEx)); + + struct scopePropVariant : public PROPVARIANT { + scopePropVariant() { PropVariantInit(this); } + ~scopePropVariant() { PropVariantClear(this); } + scopePropVariant(scopePropVariant const&) = delete; + scopePropVariant& operator=(scopePropVariant const&) = delete; + }; + + // https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/ne-mfmediaengine-mf_media_engine_statistic + scopePropVariant renderedFramesProp, droppedFramesProp; + RETURN_VOID_IF_FAILED(mediaEngineEx->GetStatistics( + MF_MEDIA_ENGINE_STATISTIC_FRAMES_RENDERED, &renderedFramesProp)); + RETURN_VOID_IF_FAILED(mediaEngineEx->GetStatistics( + MF_MEDIA_ENGINE_STATISTIC_FRAMES_DROPPED, &droppedFramesProp)); + + const unsigned long renderedFrames = renderedFramesProp.ulVal; + const unsigned long droppedFrames = droppedFramesProp.ulVal; + + // As the amount of rendered frame MUST increase monotonically. If the new + // statistic data show the decreasing, which means the media engine has reset + // the statistic data and started a new one. (That will happens after calling + // flush internally) + if (renderedFrames < mCurrentPlaybackStatisticData.renderedFrames()) { + mPrevPlaybackStatisticData = + StatisticData{mPrevPlaybackStatisticData.renderedFrames() + + mCurrentPlaybackStatisticData.renderedFrames(), + mPrevPlaybackStatisticData.droppedFrames() + + mCurrentPlaybackStatisticData.droppedFrames()}; + mCurrentPlaybackStatisticData = StatisticData{}; + } + + if (mCurrentPlaybackStatisticData.renderedFrames() != renderedFrames || + mCurrentPlaybackStatisticData.droppedFrames() != droppedFrames) { + mCurrentPlaybackStatisticData = + StatisticData{renderedFrames, droppedFrames}; + const uint64_t totalRenderedFrames = + mPrevPlaybackStatisticData.renderedFrames() + + mCurrentPlaybackStatisticData.renderedFrames(); + const uint64_t totalDroppedFrames = + mPrevPlaybackStatisticData.droppedFrames() + + mCurrentPlaybackStatisticData.droppedFrames(); + LOG("Update statistic data, rendered=%" PRIu64 ", dropped=%" PRIu64, + totalRenderedFrames, totalDroppedFrames); + Unused << SendUpdateStatisticData( + StatisticData{totalRenderedFrames, totalDroppedFrames}); + } +} + +#undef LOG +#undef RETURN_IF_FAILED +#undef ENSURE_EVENT_DISPATCH_DURING_PLAYING + +} // namespace mozilla diff --git a/dom/media/ipc/MFMediaEngineParent.h b/dom/media/ipc/MFMediaEngineParent.h new file mode 100644 index 0000000000..f606d3c44d --- /dev/null +++ b/dom/media/ipc/MFMediaEngineParent.h @@ -0,0 +1,139 @@ +/* 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 DOM_MEDIA_IPC_MFMEDIAENGINEPARENT_H_ +#define DOM_MEDIA_IPC_MFMEDIAENGINEPARENT_H_ + +#include <Mfidl.h> +#include <winnt.h> +#include <wrl.h> + +#include "MediaInfo.h" +#include "MFMediaEngineExtra.h" +#include "MFMediaEngineNotify.h" +#include "MFMediaEngineUtils.h" +#include "MFMediaSource.h" +#include "PlatformDecoderModule.h" +#include "mozilla/PMFMediaEngineParent.h" + +namespace mozilla { + +class MFCDMProxy; +class MFContentProtectionManager; +class MFMediaEngineExtension; +class MFMediaEngineStreamWrapper; +class MFMediaSource; +class RemoteDecoderManagerParent; + +/** + * MFMediaEngineParent is a wrapper class for a MediaEngine in the MF-CDM + * process. It's responsible to create the media engine and its related classes, + * such as a custom media source, media engine extension, media engine + * notify...e.t.c It communicates with MFMediaEngineChild in the content process + * to receive commands and direct them to the media engine. + * https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/nn-mfmediaengine-imfmediaengine + */ +class MFMediaEngineParent final : public PMFMediaEngineParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MFMediaEngineParent); + MFMediaEngineParent(RemoteDecoderManagerParent* aManager, + nsISerialEventTarget* aManagerThread); + + using TrackType = TrackInfo::TrackType; + + static MFMediaEngineParent* GetMediaEngineById(uint64_t aId); + + MFMediaEngineStreamWrapper* GetMediaEngineStream( + TrackType aType, const CreateDecoderParams& aParam); + + uint64_t Id() const { return mMediaEngineId; } + + // Methods for PMFMediaEngineParent + mozilla::ipc::IPCResult RecvInitMediaEngine( + const MediaEngineInfoIPDL& aInfo, InitMediaEngineResolver&& aResolver); + mozilla::ipc::IPCResult RecvNotifyMediaInfo(const MediaInfoIPDL& aInfo); + mozilla::ipc::IPCResult RecvPlay(); + mozilla::ipc::IPCResult RecvPause(); + mozilla::ipc::IPCResult RecvSeek(double aTargetTimeInSecond); + mozilla::ipc::IPCResult RecvSetCDMProxyId(uint64_t aProxyId); + mozilla::ipc::IPCResult RecvSetVolume(double aVolume); + mozilla::ipc::IPCResult RecvSetPlaybackRate(double aPlaybackRate); + mozilla::ipc::IPCResult RecvSetLooping(bool aLooping); + mozilla::ipc::IPCResult RecvNotifyEndOfStream(TrackInfo::TrackType aType); + mozilla::ipc::IPCResult RecvShutdown(); + + void Destroy(); + + private: + ~MFMediaEngineParent(); + + void CreateMediaEngine(); + + void InitializeDXGIDeviceManager(); + + void AssertOnManagerThread() const; + + void HandleMediaEngineEvent(MFMediaEngineEventWrapper aEvent); + void HandleRequestSample(const SampleRequest& aRequest); + + void NotifyError(MF_MEDIA_ENGINE_ERR aError, HRESULT aResult = 0); + + void DestroyEngineIfExists(const Maybe<MediaResult>& aError = Nothing()); + + void EnsureDcompSurfaceHandle(); + + void UpdateStatisticsData(); + + void SetMediaSourceOnEngine(); + + Maybe<gfx::IntSize> DetectVideoSizeChange(); + void NotifyVideoResizing(); + + // This generates unique id for each MFMediaEngineParent instance, and it + // would be increased monotonically. + static inline uint64_t sMediaEngineIdx = 0; + + const uint64_t mMediaEngineId; + + // The life cycle of this class is determined by the actor in the content + // process, we would hold a reference until the content actor asks us to + // destroy. + RefPtr<MFMediaEngineParent> mIPDLSelfRef; + + const RefPtr<RemoteDecoderManagerParent> mManager; + const RefPtr<nsISerialEventTarget> mManagerThread; + + // Required classes for working with the media engine. + Microsoft::WRL::ComPtr<IMFMediaEngine> mMediaEngine; + Microsoft::WRL::ComPtr<MFMediaEngineNotify> mMediaEngineNotify; + Microsoft::WRL::ComPtr<MFMediaEngineExtension> mMediaEngineExtension; + Microsoft::WRL::ComPtr<MFMediaSource> mMediaSource; +#ifdef MOZ_WMF_CDM + Microsoft::WRL::ComPtr<MFContentProtectionManager> mContentProtectionManager; +#endif + + MediaEventListener mMediaEngineEventListener; + MediaEventListener mRequestSampleListener; + bool mIsCreatedMediaEngine = false; + + Microsoft::WRL::ComPtr<IMFDXGIDeviceManager> mDXGIDeviceManager; + + // These will be always zero for audio playback. + DWORD mDisplayWidth = 0; + DWORD mDisplayHeight = 0; + + float mPlaybackRate = 1.0; + + // When flush happens inside the media engine, it will reset the statistic + // data. Therefore, whenever the statistic data gets reset, we will use + // `mCurrentPlaybackStatisticData` to track new data and store previous data + // to `mPrevPlaybackStatisticData`. The sum of these two data is the total + // statistic data for playback. + StatisticData mCurrentPlaybackStatisticData; + StatisticData mPrevPlaybackStatisticData; +}; + +} // namespace mozilla + +#endif // DOM_MEDIA_IPC_MFMEDIAENGINEPARENT_H_ diff --git a/dom/media/ipc/MFMediaEngineUtils.cpp b/dom/media/ipc/MFMediaEngineUtils.cpp new file mode 100644 index 0000000000..df1197ce8b --- /dev/null +++ b/dom/media/ipc/MFMediaEngineUtils.cpp @@ -0,0 +1,199 @@ +/* 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 "MFMediaEngineUtils.h" + +#include "mozilla/UniquePtr.h" +#include "WMFUtils.h" + +namespace mozilla { + +#define ENUM_TO_STR(enumVal) \ + case enumVal: \ + return #enumVal + +#define ENUM_TO_STR2(guid, enumVal) \ + if (guid == enumVal) { \ + return #enumVal; \ + } + +const char* MediaEventTypeToStr(MediaEventType aType) { + switch (aType) { + ENUM_TO_STR(MESourceUnknown); + ENUM_TO_STR(MESourceStarted); + ENUM_TO_STR(MEStreamStarted); + ENUM_TO_STR(MESourceSeeked); + ENUM_TO_STR(MEStreamSeeked); + ENUM_TO_STR(MENewStream); + ENUM_TO_STR(MEUpdatedStream); + ENUM_TO_STR(MESourceStopped); + ENUM_TO_STR(MEStreamStopped); + ENUM_TO_STR(MESourcePaused); + ENUM_TO_STR(MEStreamPaused); + ENUM_TO_STR(MEEndOfPresentation); + ENUM_TO_STR(MEEndOfStream); + ENUM_TO_STR(MEMediaSample); + ENUM_TO_STR(MEStreamTick); + ENUM_TO_STR(MEStreamThinMode); + ENUM_TO_STR(MEStreamFormatChanged); + ENUM_TO_STR(MESourceRateChanged); + ENUM_TO_STR(MEEndOfPresentationSegment); + ENUM_TO_STR(MESourceCharacteristicsChanged); + ENUM_TO_STR(MESourceRateChangeRequested); + ENUM_TO_STR(MESourceMetadataChanged); + ENUM_TO_STR(MESequencerSourceTopologyUpdated); + default: + return "Unknown MediaEventType"; + } +} + +const char* MediaEngineEventToStr(MF_MEDIA_ENGINE_EVENT aEvent) { + switch (aEvent) { + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_LOADSTART); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_PROGRESS); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_SUSPEND); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_ABORT); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_ERROR); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_EMPTIED); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_STALLED); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_PLAY); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_PAUSE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_LOADEDMETADATA); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_LOADEDDATA); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_WAITING); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_PLAYING); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_CANPLAY); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_CANPLAYTHROUGH); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_SEEKING); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_SEEKED); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_TIMEUPDATE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_ENDED); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_RATECHANGE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_DURATIONCHANGE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_VOLUMECHANGE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_FORMATCHANGE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_PURGEQUEUEDEVENTS); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_TIMELINE_MARKER); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_BALANCECHANGE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_DOWNLOADCOMPLETE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_BUFFERINGSTARTED); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_BUFFERINGENDED); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_FRAMESTEPCOMPLETED); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_NOTIFYSTABLESTATE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_FIRSTFRAMEREADY); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_TRACKSCHANGE); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_OPMINFO); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_RESOURCELOST); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_DELAYLOADEVENT_CHANGED); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_STREAMRENDERINGERROR); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_SUPPORTEDRATES_CHANGED); + ENUM_TO_STR(MF_MEDIA_ENGINE_EVENT_AUDIOENDPOINTCHANGE); + default: + return "Unknown MF_MEDIA_ENGINE_EVENT"; + } +} + +const char* MFMediaEngineErrorToStr(MFMediaEngineError aError) { + switch (aError) { + ENUM_TO_STR(MF_MEDIA_ENGINE_ERR_NOERROR); + ENUM_TO_STR(MF_MEDIA_ENGINE_ERR_ABORTED); + ENUM_TO_STR(MF_MEDIA_ENGINE_ERR_NETWORK); + ENUM_TO_STR(MF_MEDIA_ENGINE_ERR_DECODE); + ENUM_TO_STR(MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED); + ENUM_TO_STR(MF_MEDIA_ENGINE_ERR_ENCRYPTED); + default: + return "Unknown MFMediaEngineError"; + } +} + +const char* GUIDToStr(GUID aGUID) { + ENUM_TO_STR2(aGUID, MFAudioFormat_MP3) + ENUM_TO_STR2(aGUID, MFAudioFormat_AAC) + ENUM_TO_STR2(aGUID, MFAudioFormat_Vorbis) + ENUM_TO_STR2(aGUID, MFAudioFormat_Opus) + ENUM_TO_STR2(aGUID, MFVideoFormat_H264) + ENUM_TO_STR2(aGUID, MFVideoFormat_VP80) + ENUM_TO_STR2(aGUID, MFVideoFormat_VP90) + ENUM_TO_STR2(aGUID, MFVideoFormat_AV1) + ENUM_TO_STR2(aGUID, MFVideoFormat_HEVC) + ENUM_TO_STR2(aGUID, MFMediaType_Audio) + return "Unknown GUID"; +} + +const char* MFVideoRotationFormatToStr(MFVideoRotationFormat aFormat) { + switch (aFormat) { + ENUM_TO_STR(MFVideoRotationFormat_0); + ENUM_TO_STR(MFVideoRotationFormat_90); + ENUM_TO_STR(MFVideoRotationFormat_180); + ENUM_TO_STR(MFVideoRotationFormat_270); + default: + return "Unknown MFVideoRotationFormat"; + } +} + +const char* MFVideoTransferFunctionToStr(MFVideoTransferFunction aFunc) { + switch (aFunc) { + ENUM_TO_STR(MFVideoTransFunc_Unknown); + ENUM_TO_STR(MFVideoTransFunc_709); + ENUM_TO_STR(MFVideoTransFunc_2020); + ENUM_TO_STR(MFVideoTransFunc_sRGB); + default: + return "Unsupported MFVideoTransferFunction"; + } +} + +const char* MFVideoPrimariesToStr(MFVideoPrimaries aPrimaries) { + switch (aPrimaries) { + ENUM_TO_STR(MFVideoPrimaries_Unknown); + ENUM_TO_STR(MFVideoPrimaries_BT709); + ENUM_TO_STR(MFVideoPrimaries_BT2020); + default: + return "Unsupported MFVideoPrimaries"; + } +} + +void ByteArrayFromGUID(REFGUID aGuidIn, nsTArray<uint8_t>& aByteArrayOut) { + aByteArrayOut.SetLength(sizeof(GUID)); + // GUID is little endian. The byte array in network order is big endian. + GUID* reversedGuid = reinterpret_cast<GUID*>(aByteArrayOut.Elements()); + *reversedGuid = aGuidIn; + reversedGuid->Data1 = _byteswap_ulong(aGuidIn.Data1); + reversedGuid->Data2 = _byteswap_ushort(aGuidIn.Data2); + reversedGuid->Data3 = _byteswap_ushort(aGuidIn.Data3); + // Data4 is already a byte array so no need to byte swap. +} + +void GUIDFromByteArray(const nsTArray<uint8_t>& aByteArrayIn, GUID& aGuidOut) { + MOZ_ASSERT(aByteArrayIn.Length() == sizeof(GUID)); + GUID* reversedGuid = + reinterpret_cast<GUID*>(const_cast<uint8_t*>(aByteArrayIn.Elements())); + aGuidOut = *reversedGuid; + aGuidOut.Data1 = _byteswap_ulong(reversedGuid->Data1); + aGuidOut.Data2 = _byteswap_ushort(reversedGuid->Data2); + aGuidOut.Data3 = _byteswap_ushort(reversedGuid->Data3); + // Data4 is already a byte array so no need to byte swap. +} + +BSTR CreateBSTRFromConstChar(const char* aNarrowStr) { + int wideStrSize = MultiByteToWideChar(CP_UTF8, 0, aNarrowStr, -1, nullptr, 0); + if (wideStrSize == 0) { + NS_WARNING("Failed to get wide str size!"); + return nullptr; + } + + UniquePtr<wchar_t[]> wideStrBuffer(new wchar_t[wideStrSize]); + if (MultiByteToWideChar(CP_UTF8, 0, aNarrowStr, -1, wideStrBuffer.get(), + wideStrSize) == 0) { + NS_WARNING("Failed to covert to wide str!"); + return nullptr; + } + + BSTR bstr = SysAllocString(wideStrBuffer.get()); + return bstr; +} + +#undef ENUM_TO_STR +#undef ENUM_TO_STR2 + +} // namespace mozilla diff --git a/dom/media/ipc/MFMediaEngineUtils.h b/dom/media/ipc/MFMediaEngineUtils.h new file mode 100644 index 0000000000..67f18b6fca --- /dev/null +++ b/dom/media/ipc/MFMediaEngineUtils.h @@ -0,0 +1,195 @@ +/* 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 DOM_MEDIA_IPC_MFMEDIAENGINEUTILS_H_ +#define DOM_MEDIA_IPC_MFMEDIAENGINEUTILS_H_ + +#include "MFMediaEngineExtra.h" +#include "ipc/EnumSerializer.h" +#include "mozilla/Logging.h" +#include "mozilla/ProfilerMarkerTypes.h" +#include "nsPrintfCString.h" + +namespace mozilla { + +inline LazyLogModule gMFMediaEngineLog{"MFMediaEngine"}; + +// https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/ne-mfmediaengine-mf_media_engine_event +using MFMediaEngineEvent = MF_MEDIA_ENGINE_EVENT; + +// https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/ne-mfmediaengine-mf_media_engine_err +using MFMediaEngineError = MF_MEDIA_ENGINE_ERR; + +#define LOG_AND_WARNING(msg, ...) \ + do { \ + NS_WARNING(nsPrintfCString(msg, rv).get()); \ + MOZ_LOG(gMFMediaEngineLog, LogLevel::Debug, \ + ("%s:%d, " msg, __FILE__, __LINE__, ##__VA_ARGS__)); \ + } while (false) + +#ifndef LOG_IF_FAILED +# define LOG_IF_FAILED(x) \ + do { \ + HRESULT rv = x; \ + if (MOZ_UNLIKELY(FAILED(rv))) { \ + LOG_AND_WARNING("(" #x ") failed, rv=%lx", rv); \ + } \ + } while (false) +#endif + +#ifndef RETURN_IF_FAILED +# define RETURN_IF_FAILED(x) \ + do { \ + HRESULT rv = x; \ + if (MOZ_UNLIKELY(FAILED(rv))) { \ + LOG_AND_WARNING("(" #x ") failed, rv=%lx", rv); \ + return rv; \ + } \ + } while (false) +#endif + +#ifndef RETURN_VOID_IF_FAILED +# define RETURN_VOID_IF_FAILED(x) \ + do { \ + HRESULT rv = x; \ + if (MOZ_UNLIKELY(FAILED(rv))) { \ + LOG_AND_WARNING("(" #x ") failed, rv=%lx", rv); \ + return; \ + } \ + } while (false) +#endif + +#ifndef RETURN_PARAM_IF_FAILED +# define RETURN_PARAM_IF_FAILED(x, defaultOut) \ + do { \ + HRESULT rv = x; \ + if (MOZ_UNLIKELY(FAILED(rv))) { \ + LOG_AND_WARNING("(" #x ") failed, rv=%lx", rv); \ + return defaultOut; \ + } \ + } while (false) +#endif + +#ifndef SHUTDOWN_IF_POSSIBLE +# define SHUTDOWN_IF_POSSIBLE(class) \ + do { \ + IMFShutdown* pShutdown = nullptr; \ + HRESULT rv = class->QueryInterface(IID_PPV_ARGS(&pShutdown)); \ + if (SUCCEEDED(rv)) { \ + rv = pShutdown->Shutdown(); \ + if (FAILED(rv)) { \ + LOG_AND_WARNING(#class " failed to shutdown, rv=%lx", rv); \ + } else { \ + MOZ_LOG(gMFMediaEngineLog, LogLevel::Verbose, \ + ((#class " shutdowned successfully"))); \ + } \ + pShutdown->Release(); \ + } else { \ + LOG_AND_WARNING(#class " doesn't support IMFShutdown?, rv=%lx", rv); \ + } \ + } while (false) +#endif + +#define ENGINE_MARKER(markerName) \ + PROFILER_MARKER(markerName, MEDIA_PLAYBACK, {}, MediaEngineMarker, Id()) + +#define ENGINE_MARKER_TEXT(markerName, text) \ + PROFILER_MARKER(markerName, MEDIA_PLAYBACK, {}, MediaEngineTextMarker, Id(), \ + text) + +const char* MediaEventTypeToStr(MediaEventType aType); +const char* MediaEngineEventToStr(MF_MEDIA_ENGINE_EVENT aEvent); +const char* MFMediaEngineErrorToStr(MFMediaEngineError aError); +const char* GUIDToStr(GUID aGUID); +const char* MFVideoRotationFormatToStr(MFVideoRotationFormat aFormat); +const char* MFVideoTransferFunctionToStr(MFVideoTransferFunction aFunc); +const char* MFVideoPrimariesToStr(MFVideoPrimaries aPrimaries); +void ByteArrayFromGUID(REFGUID aGuidIn, nsTArray<uint8_t>& aByteArrayOut); +void GUIDFromByteArray(const nsTArray<uint8_t>& aByteArrayIn, GUID& aGuidOut); +BSTR CreateBSTRFromConstChar(const char* aNarrowStr); + +// See cdm::SubsampleEntry +struct MediaFoundationSubsampleEntry { + uint32_t mClearBytes; + uint32_t mCipherBytes; +}; + +template <typename T> +class ScopedCoMem { + public: + ScopedCoMem() : mPtr(nullptr) {} + + ~ScopedCoMem() { Reset(nullptr); } + + ScopedCoMem(const ScopedCoMem&) = delete; + ScopedCoMem& operator=(const ScopedCoMem&) = delete; + + T** operator&() { // NOLINT + MOZ_ASSERT(mPtr == nullptr); // To catch memory leaks. + return &mPtr; + } + + operator T*() { return mPtr; } + + T* operator->() { + MOZ_ASSERT(mPtr != nullptr); + return mPtr; + } + + const T* operator->() const { + MOZ_ASSERT(mPtr != nullptr); + return mPtr; + } + + explicit operator bool() const { return mPtr; } + + friend bool operator==(const ScopedCoMem& lhs, std::nullptr_t) { + return lhs.Get() == nullptr; + } + + friend bool operator==(std::nullptr_t, const ScopedCoMem& rhs) { + return rhs.Get() == nullptr; + } + + friend bool operator!=(const ScopedCoMem& lhs, std::nullptr_t) { + return lhs.Get() != nullptr; + } + + friend bool operator!=(std::nullptr_t, const ScopedCoMem& rhs) { + return rhs.Get() != nullptr; + } + + void Reset(T* ptr) { + if (mPtr) CoTaskMemFree(mPtr); + mPtr = ptr; + } + + T* Get() const { return mPtr; } + + private: + T* mPtr; +}; + +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::MFMediaEngineError> + : public ContiguousEnumSerializerInclusive< + mozilla::MFMediaEngineError, + mozilla::MFMediaEngineError::MF_MEDIA_ENGINE_ERR_ABORTED, + mozilla::MFMediaEngineError::MF_MEDIA_ENGINE_ERR_ENCRYPTED> {}; + +template <> +struct ParamTraits<mozilla::MFMediaEngineEvent> + : public ContiguousEnumSerializerInclusive< + mozilla::MFMediaEngineEvent, + mozilla::MFMediaEngineEvent::MF_MEDIA_ENGINE_EVENT_LOADSTART, + mozilla::MFMediaEngineEvent:: + MF_MEDIA_ENGINE_EVENT_AUDIOENDPOINTCHANGE> {}; + +} // namespace IPC + +#endif // DOM_MEDIA_IPC_MFMEDIAENGINECHILD_H_ diff --git a/dom/media/ipc/MediaIPCUtils.h b/dom/media/ipc/MediaIPCUtils.h new file mode 100644 index 0000000000..fecf41c325 --- /dev/null +++ b/dom/media/ipc/MediaIPCUtils.h @@ -0,0 +1,376 @@ +/* -*- 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 <type_traits> + +#include "DecoderDoctorDiagnostics.h" +#include "PerformanceRecorder.h" +#include "PlatformDecoderModule.h" +#include "ipc/EnumSerializer.h" +#include "mozilla/EnumSet.h" +#include "mozilla/GfxMessageUtils.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/dom/MFCDMSerializers.h" + +namespace IPC { +template <> +struct ParamTraits<mozilla::VideoInfo> { + typedef mozilla::VideoInfo paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + // TrackInfo + WriteParam(aWriter, aParam.mMimeType); + + // VideoInfo + WriteParam(aWriter, aParam.mDisplay); + WriteParam(aWriter, aParam.mStereoMode); + WriteParam(aWriter, aParam.mImage); + WriteParam(aWriter, aParam.mImageRect); + WriteParam(aWriter, *aParam.mCodecSpecificConfig); + WriteParam(aWriter, *aParam.mExtraData); + WriteParam(aWriter, aParam.mRotation); + WriteParam(aWriter, aParam.mColorDepth); + WriteParam(aWriter, aParam.mColorSpace); + WriteParam(aWriter, aParam.mColorPrimaries); + WriteParam(aWriter, aParam.mTransferFunction); + WriteParam(aWriter, aParam.mColorRange); + WriteParam(aWriter, aParam.HasAlpha()); + WriteParam(aWriter, aParam.mCrypto); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + mozilla::gfx::IntRect imageRect; + bool alphaPresent; + if (ReadParam(aReader, &aResult->mMimeType) && + ReadParam(aReader, &aResult->mDisplay) && + ReadParam(aReader, &aResult->mStereoMode) && + ReadParam(aReader, &aResult->mImage) && + ReadParam(aReader, &aResult->mImageRect) && + ReadParam(aReader, aResult->mCodecSpecificConfig.get()) && + ReadParam(aReader, aResult->mExtraData.get()) && + ReadParam(aReader, &aResult->mRotation) && + ReadParam(aReader, &aResult->mColorDepth) && + ReadParam(aReader, &aResult->mColorSpace) && + ReadParam(aReader, &aResult->mColorPrimaries) && + ReadParam(aReader, &aResult->mTransferFunction) && + ReadParam(aReader, &aResult->mColorRange) && + ReadParam(aReader, &alphaPresent) && + ReadParam(aReader, &aResult->mCrypto)) { + 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::VideoRotation> + : public ContiguousEnumSerializerInclusive< + mozilla::VideoRotation, mozilla::VideoRotation::kDegree_0, + mozilla::VideoRotation::kDegree_270> {}; + +template <> +struct ParamTraits<mozilla::MediaByteBuffer> + : public ParamTraits<nsTArray<uint8_t>> { + typedef mozilla::MediaByteBuffer paramType; +}; + +// Traits for AudioCodecSpecificVariant types. + +template <> +struct ParamTraits<mozilla::NoCodecSpecificData> + : public EmptyStructSerializer<mozilla::NoCodecSpecificData> {}; + +template <> +struct ParamTraits<mozilla::AudioCodecSpecificBinaryBlob> { + using paramType = mozilla::AudioCodecSpecificBinaryBlob; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, *aParam.mBinaryBlob); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, aResult->mBinaryBlob.get()); + } +}; + +template <> +struct ParamTraits<mozilla::AacCodecSpecificData> { + using paramType = mozilla::AacCodecSpecificData; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, *aParam.mEsDescriptorBinaryBlob); + WriteParam(aWriter, *aParam.mDecoderConfigDescriptorBinaryBlob); + WriteParam(aWriter, aParam.mEncoderDelayFrames); + WriteParam(aWriter, aParam.mMediaFrameCount); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, aResult->mEsDescriptorBinaryBlob.get()) && + ReadParam(aReader, + aResult->mDecoderConfigDescriptorBinaryBlob.get()) && + ReadParam(aReader, &aResult->mEncoderDelayFrames) && + ReadParam(aReader, &aResult->mMediaFrameCount); + } +}; + +template <> +struct ParamTraits<mozilla::FlacCodecSpecificData> { + using paramType = mozilla::FlacCodecSpecificData; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, *aParam.mStreamInfoBinaryBlob); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, aResult->mStreamInfoBinaryBlob.get()); + } +}; + +template <> +struct ParamTraits<mozilla::Mp3CodecSpecificData> + : public PlainOldDataSerializer<mozilla::Mp3CodecSpecificData> {}; + +template <> +struct ParamTraits<mozilla::OpusCodecSpecificData> { + using paramType = mozilla::OpusCodecSpecificData; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mContainerCodecDelayFrames); + WriteParam(aWriter, *aParam.mHeadersBinaryBlob); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mContainerCodecDelayFrames) && + ReadParam(aReader, aResult->mHeadersBinaryBlob.get()); + } +}; + +template <> +struct ParamTraits<mozilla::VorbisCodecSpecificData> { + using paramType = mozilla::VorbisCodecSpecificData; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, *aParam.mHeadersBinaryBlob); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, aResult->mHeadersBinaryBlob.get()); + } +}; + +template <> +struct ParamTraits<mozilla::WaveCodecSpecificData> + : public EmptyStructSerializer<mozilla::WaveCodecSpecificData> {}; + +// End traits for AudioCodecSpecificVariant types. + +template <> +struct ParamTraits<mozilla::AudioInfo> { + typedef mozilla::AudioInfo paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + // TrackInfo + WriteParam(aWriter, aParam.mMimeType); + + // AudioInfo + WriteParam(aWriter, aParam.mRate); + WriteParam(aWriter, aParam.mChannels); + WriteParam(aWriter, aParam.mChannelMap); + WriteParam(aWriter, aParam.mBitDepth); + WriteParam(aWriter, aParam.mProfile); + WriteParam(aWriter, aParam.mExtendedProfile); + WriteParam(aWriter, aParam.mCodecSpecificConfig); + WriteParam(aWriter, aParam.mCrypto); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (ReadParam(aReader, &aResult->mMimeType) && + ReadParam(aReader, &aResult->mRate) && + ReadParam(aReader, &aResult->mChannels) && + ReadParam(aReader, &aResult->mChannelMap) && + ReadParam(aReader, &aResult->mBitDepth) && + ReadParam(aReader, &aResult->mProfile) && + ReadParam(aReader, &aResult->mExtendedProfile) && + ReadParam(aReader, &aResult->mCodecSpecificConfig) && + ReadParam(aReader, &aResult->mCrypto)) { + 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> { + using paramType = mozilla::media::TimeUnit; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.IsValid()); + WriteParam(aWriter, aParam.IsValid() ? aParam.mTicks.value() : 0); + WriteParam(aWriter, + aParam.IsValid() ? aParam.mBase : 1); // base can't be 0 + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + bool valid; + int64_t ticks; + int64_t base; + if (ReadParam(aReader, &valid) && ReadParam(aReader, &ticks) && + ReadParam(aReader, &base)) { + if (valid) { + *aResult = mozilla::media::TimeUnit(ticks, base); + } else { + *aResult = mozilla::media::TimeUnit::Invalid(); + } + return true; + } + return false; + }; +}; + +template <> +struct ParamTraits<mozilla::media::TimeInterval> { + typedef mozilla::media::TimeInterval paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mStart); + WriteParam(aWriter, aParam.mEnd); + WriteParam(aWriter, aParam.mFuzz); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (ReadParam(aReader, &aResult->mStart) && + ReadParam(aReader, &aResult->mEnd) && + ReadParam(aReader, &aResult->mFuzz)) { + return true; + } + return false; + } +}; + +template <> +struct ParamTraits<mozilla::MediaResult> { + typedef mozilla::MediaResult paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.Code()); + WriteParam(aWriter, aParam.Message()); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + nsresult result; + nsCString message; + if (ReadParam(aReader, &result) && ReadParam(aReader, &message)) { + *aResult = paramType(result, std::move(message)); + return true; + } + return false; + }; +}; + +template <> +struct ParamTraits<mozilla::DecoderDoctorDiagnostics> { + typedef mozilla::DecoderDoctorDiagnostics paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mDiagnosticsType); + WriteParam(aWriter, aParam.mFormat); + WriteParam(aWriter, aParam.mFlags); + WriteParam(aWriter, aParam.mEvent); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (ReadParam(aReader, &aResult->mDiagnosticsType) && + ReadParam(aReader, &aResult->mFormat) && + ReadParam(aReader, &aResult->mFlags) && + ReadParam(aReader, &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(MessageWriter* aWriter, const paramType& aParam) { + int domain = aParam.mDomain; + WriteParam(aWriter, domain); + WriteParam(aWriter, aParam.mResult); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + int domain = 0; + if (ReadParam(aReader, &domain) && ReadParam(aReader, &aResult->mResult)) { + aResult->mDomain = paramType::Domain(domain); + return true; + } + return false; + }; +}; + +template <> +struct ParamTraits<mozilla::TrackingId::Source> + : public ContiguousEnumSerializer< + mozilla::TrackingId::Source, + mozilla::TrackingId::Source::Unimplemented, + mozilla::TrackingId::Source::LAST> {}; + +template <> +struct ParamTraits<mozilla::TrackingId> { + typedef mozilla::TrackingId paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mSource); + WriteParam(aWriter, aParam.mProcId); + WriteParam(aWriter, aParam.mUniqueInProcId); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mSource) && + ReadParam(aReader, &aResult->mProcId) && + ReadParam(aReader, &aResult->mUniqueInProcId); + } +}; + +template <> +struct ParamTraits<mozilla::CryptoTrack> { + typedef mozilla::CryptoTrack paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mCryptoScheme); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mCryptoScheme); + } +}; + +} // namespace IPC + +#endif // mozilla_dom_media_MediaIPCUtils_h diff --git a/dom/media/ipc/PMFCDM.ipdl b/dom/media/ipc/PMFCDM.ipdl new file mode 100644 index 0000000000..e86b94c217 --- /dev/null +++ b/dom/media/ipc/PMFCDM.ipdl @@ -0,0 +1,121 @@ +/* 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 "MFCDMSerializers.h"; + +include protocol PRemoteDecoderManager; + +using mozilla::KeySystemConfig::Requirement from "mozilla/KeySystemConfig.h"; +using mozilla::KeySystemConfig::SessionType from "mozilla/KeySystemConfig.h"; +using mozilla::CryptoScheme from "MediaData.h"; +using mozilla::dom::MediaKeyMessageType from "mozilla/dom/MediaKeyMessageEventBinding.h"; +using mozilla::dom::MediaKeyStatus from "mozilla/dom/MediaKeyStatusMapBinding.h"; +using mozilla::dom::HDCPVersion from "mozilla/dom/MediaKeysBinding.h"; + +namespace mozilla { + +// For EME spec 'message' event +// https://w3c.github.io/encrypted-media/#queue-message +struct MFCDMKeyMessage { + nsString sessionId; + MediaKeyMessageType type; + uint8_t[] message; +}; + +// For EME spec 'keystatuseschange' event +// https://w3c.github.io/encrypted-media/#dom-evt-keystatuseschange +struct MFCDMKeyInformation { + uint8_t[] keyId; + MediaKeyStatus status; +}; + +struct MFCDMKeyStatusChange { + nsString sessionId; + MFCDMKeyInformation[] keyInfo; +}; + +// For EME spec Update Expiration algorithm +// https://w3c.github.io/encrypted-media/#update-expiration +struct MFCDMKeyExpiration { + nsString sessionId; + double expiredTimeMilliSecondsSinceEpoch; +}; + +// For GetCapabilities() +struct MFCDMMediaCapability { + nsString contentType; + nsString robustness; +}; + +struct MFCDMCapabilitiesIPDL { + nsString keySystem; + nsString[] initDataTypes; + MFCDMMediaCapability[] audioCapabilities; + MFCDMMediaCapability[] videoCapabilities; + SessionType[] sessionTypes; + CryptoScheme[] encryptionSchemes; + Requirement distinctiveID; + Requirement persistentState; + bool isHDCP22Compatible; +}; + +union MFCDMCapabilitiesResult { + nsresult; + MFCDMCapabilitiesIPDL; +}; + +// For Init() +struct MFCDMInitParamsIPDL { + nsString origin; + nsString[] initDataTypes; + Requirement distinctiveID; + Requirement persistentState; + MFCDMMediaCapability[] audioCapabilities; + MFCDMMediaCapability[] videoCapabilities; +}; + +struct MFCDMInitIPDL { + uint64_t id; +}; + +union MFCDMInitResult { + nsresult; + MFCDMInitIPDL; +}; + +struct MFCDMCreateSessionParamsIPDL { + SessionType sessionType; + nsString initDataType; + uint8_t[] initData; +}; + +union MFCDMSessionResult { + nsString; + nsresult; +}; + +[ManualDealloc] +async protocol PMFCDM +{ + manager PRemoteDecoderManager; +parent: + async GetCapabilities(bool isHwSecured) returns (MFCDMCapabilitiesResult result); + async Init(MFCDMInitParamsIPDL params) returns (MFCDMInitResult result); + async CreateSessionAndGenerateRequest(MFCDMCreateSessionParamsIPDL type) + returns (MFCDMSessionResult result); + async LoadSession(SessionType sessionType, nsString sessionId) returns (nsresult result); + async UpdateSession(nsString sessionId, uint8_t[] response) returns (nsresult result); + async CloseSession(nsString sessionId) returns (nsresult result); + async RemoveSession(nsString sessionId) returns (nsresult result); + async SetServerCertificate(uint8_t[] certificate) returns (nsresult result); + async GetStatusForPolicy(HDCPVersion minHdcpVersion) returns (nsresult result); + async __delete__(); + +child: + async OnSessionKeyMessage(MFCDMKeyMessage message); + async OnSessionKeyStatusesChanged(MFCDMKeyStatusChange keystatuses); + async OnSessionKeyExpiration(MFCDMKeyExpiration expiration); +}; + +} // namespace mozilla diff --git a/dom/media/ipc/PMFMediaEngine.ipdl b/dom/media/ipc/PMFMediaEngine.ipdl new file mode 100644 index 0000000000..8edc44bb81 --- /dev/null +++ b/dom/media/ipc/PMFMediaEngine.ipdl @@ -0,0 +1,63 @@ +/* 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::AudioInfo from "MediaInfo.h"; +using mozilla::VideoInfo from "MediaInfo.h"; +using mozilla::MediaResult from "MediaResult.h"; +using mozilla::TrackInfo::TrackType from "MediaInfo.h"; +using mozilla::MFMediaEngineError from "MFMediaEngineUtils.h"; +using mozilla::MFMediaEngineEvent from "MFMediaEngineUtils.h"; + +namespace mozilla { + +struct MediaEngineInfoIPDL +{ + bool preload; +}; + +struct MediaInfoIPDL +{ + AudioInfo? audioInfo; + VideoInfo? videoInfo; +}; + +struct StatisticData +{ + uint64_t renderedFrames; + uint64_t droppedFrames; +}; + +[ManualDealloc] +async protocol PMFMediaEngine +{ + manager PRemoteDecoderManager; +parent: + // Return 0 if media engine can't be created. + async InitMediaEngine(MediaEngineInfoIPDL info) returns (uint64_t id); + async NotifyMediaInfo(MediaInfoIPDL info); + async Play(); + async Pause(); + async Seek(double targetTimeInSecond); + async SetCDMProxyId(uint64_t type); + async SetVolume(double volume); + async SetPlaybackRate(double playbackRate); + async SetLooping(bool looping); + async NotifyEndOfStream(TrackType type); + async Shutdown(); + async __delete__(); + +child: + async NotifyEvent(MFMediaEngineEvent event); + async NotifyError(MediaResult error); + async UpdateCurrentTime(double currentTimeInSecond); + async RequestSample(TrackType type, bool isEnough); + async UpdateStatisticData(StatisticData data); + async NotifyResizing(uint32_t width, uint32_t height); +}; + +} // namespace mozilla diff --git a/dom/media/ipc/PMediaDecoderParams.ipdlh b/dom/media/ipc/PMediaDecoderParams.ipdlh new file mode 100644 index 0000000000..449744b5ac --- /dev/null +++ b/dom/media/ipc/PMediaDecoderParams.ipdlh @@ -0,0 +1,30 @@ +/* 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"; +using mozilla::CryptoScheme from "MediaData.h"; + +namespace mozilla { + +// used for both SendInput/RecvInput and ProcessDecodedData/RecvOutput +struct MediaDataIPDL +{ + int64_t offset; + TimeUnit time; + TimeUnit timecode; + TimeUnit duration; + bool keyframe; +}; + +struct CryptoInfo { + CryptoScheme mEncryptionScheme; + uint8_t[] mIV; + uint8_t[] mKeyId; + uint32_t[] mClearBytes; + uint32_t[] mCipherBytes; +}; + +} // namespace mozilla diff --git a/dom/media/ipc/PRDD.ipdl b/dom/media/ipc/PRDD.ipdl new file mode 100644 index 0000000000..715ed7f502 --- /dev/null +++ b/dom/media/ipc/PRDD.ipdl @@ -0,0 +1,128 @@ +/* -*- 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; + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +include protocol PSandboxTesting; +#endif + +include "mozilla/ipc/ByteBufUtils.h"; + +using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h"; +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; +using mozilla::media::MediaCodecsSupported from "MediaCodecsSupport.h"; + +// Telemetry +using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h"; + +#if defined(XP_WIN) +[MoveOnly] using mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h"; +[MoveOnly] using mozilla::ModulePaths from "mozilla/UntrustedModulesData.h"; +[MoveOnly] using mozilla::ModulesMapResult from "mozilla/UntrustedModulesData.h"; +#endif // defined(XP_WIN) + +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. +[NeedsOtherPid, ParentProc=RDD, ChildProc=Parent] +protocol PRDD +{ +parent: + + async Init(GfxVarUpdate[] vars, FileDescriptor? sandboxBroker, + bool canRecordReleaseTelemetry, + bool aIsReadyForBackgroundProcessing); + + async InitProfiler(Endpoint<PProfilerChild> endpoint); + + async NewContentRemoteDecoderManager( + Endpoint<PRemoteDecoderManagerParent> endpoint, ContentParentId childId); + + 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); + +#if defined(XP_WIN) + async GetUntrustedModulesData() returns (UntrustedModulesData? data); + + /** + * This method is used to notifty a child process to start + * processing module loading events in UntrustedModulesProcessor. + * This should be called when the parent process has gone idle. + */ + async UnblockUntrustedModulesThread(); +#endif // defined(XP_WIN) + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint); +#endif + + // Tells the RDD process to flush any pending telemetry. + // Used in tests and ping assembly. Buffer contains bincoded Rust structs. + // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html + async FlushFOGData() returns (ByteBuf buf); + + // Test-only method. + // Asks the RDD process to trigger test-only instrumentation. + // The unused returned value is to have a promise we can await. + async TestTriggerMetrics() returns (bool unused); + + async TestTelemetryProbes(); + +child: + + async InitCrashReporter(NativeThreadId threadId); + + async AddMemoryReport(MemoryReport aReport); + +#if defined(XP_WIN) + async GetModulesTrust(ModulePaths aModPaths, bool aRunAtNormalPriority) + returns (ModulesMapResult? modMapResult); +#endif // defined(XP_WIN) + + // Update the cached list of codec supported following a check in the + // RDD parent. + async UpdateMediaCodecsSupported(MediaCodecsSupported aSupported); + + // Messages for sending telemetry to parent process. + async AccumulateChildHistograms(HistogramAccumulation[] accumulations); + async AccumulateChildKeyedHistograms(KeyedHistogramAccumulation[] accumulations); + async UpdateChildScalars(ScalarAction[] actions); + async UpdateChildKeyedScalars(KeyedScalarAction[] actions); + async RecordChildEvents(ChildEventData[] events); + async RecordDiscardedData(DiscardedData data); + + // Sent from time-to-time to limit the amount of telemetry vulnerable to loss + // Buffer contains bincoded Rust structs. + // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html + async FOGData(ByteBuf buf); +}; + +} // namespace mozilla diff --git a/dom/media/ipc/PRemoteDecoder.ipdl b/dom/media/ipc/PRemoteDecoder.ipdl new file mode 100644 index 0000000000..a0af29a9d6 --- /dev/null +++ b/dom/media/ipc/PRemoteDecoder.ipdl @@ -0,0 +1,80 @@ +/* -*- 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"; +[RefCounted] using class mozilla::ArrayOfRemoteMediaRawData from "mozilla/RemoteMediaData.h"; +[RefCounted] using class mozilla::ArrayOfRemoteAudioData from "mozilla/RemoteMediaData.h"; +[RefCounted] using class mozilla::ArrayOfRemoteVideoData from "mozilla/RemoteMediaData.h"; +include PMediaDecoderParams; +include LayersSurfaces; + +namespace mozilla { + +union DecodedOutputIPDL +{ + nullable ArrayOfRemoteAudioData; + nullable ArrayOfRemoteVideoData; +}; + +struct InitCompletionIPDL +{ + TrackType type; + nsCString decoderDescription; + nsCString decoderProcessName; + nsCString decoderCodecName; + 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. +[ManualDealloc] +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(nullable 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..8997face2c --- /dev/null +++ b/dom/media/ipc/PRemoteDecoderManager.ipdl @@ -0,0 +1,70 @@ +/* -*- 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/. */ + +#ifdef MOZ_WMF_MEDIA_ENGINE + include protocol PMFMediaEngine; +#endif +#ifdef MOZ_WMF_CDM + include protocol PMFCDM; +#endif + +include protocol PTexture; +include protocol PRemoteDecoder; +include LayersSurfaces; +include PMediaDecoderParams; +include "mozilla/dom/MediaIPCUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; + +using mozilla::VideoInfo from "MediaInfo.h"; +using mozilla::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"; +using mozilla::TrackingId from "PerformanceRecorder.h"; + +namespace mozilla { + +struct VideoDecoderInfoIPDL +{ + VideoInfo videoInfo; + float framerate; +}; + +union RemoteDecoderInfoIPDL +{ + AudioInfo; + VideoDecoderInfoIPDL; +}; + +[NeedsOtherPid, ParentProc=any, ChildProc=anydom] +sync protocol PRemoteDecoderManager +{ + manages PRemoteDecoder; +#ifdef MOZ_WMF_MEDIA_ENGINE + manages PMFMediaEngine; +#endif +#ifdef MOZ_WMF_CDM + manages PMFCDM; +#endif + +parent: +#ifdef MOZ_WMF_MEDIA_ENGINE + async PMFMediaEngine(); +#endif +#ifdef MOZ_WMF_CDM + async PMFCDM(nsString keySystem); +#endif + async PRemoteDecoder(RemoteDecoderInfoIPDL info, + OptionSet options, + TextureFactoryIdentifier? identifier, + uint64_t? mediaEngineId, + TrackingId? trackingId); + + 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..fb2e14bb4f --- /dev/null +++ b/dom/media/ipc/RDDChild.cpp @@ -0,0 +1,225 @@ +/* -*- 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/FOGIPC.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" +#include "mozilla/TelemetryIPC.h" + +#if defined(XP_WIN) +# include "mozilla/WinDllServices.h" +#endif + +#include "ProfilerParent.h" +#include "RDDProcessHost.h" + +namespace mozilla { + +using namespace layers; +using namespace gfx; + +RDDChild::RDDChild(RDDProcessHost* aHost) : mHost(aHost) {} + +RDDChild::~RDDChild() = default; + +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(); + + bool isReadyForBackgroundProcessing = false; +#if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + isReadyForBackgroundProcessing = dllSvc->IsReadyForBackgroundProcessing(); +#endif + + SendInit(updates, brokerFd, Telemetry::CanRecordReleaseData(), + isReadyForBackgroundProcessing); + + Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid())); + + 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(); +} + +#if defined(XP_WIN) +mozilla::ipc::IPCResult RDDChild::RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver) { + 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(); +} +#endif // defined(XP_WIN) + +mozilla::ipc::IPCResult RDDChild::RecvUpdateMediaCodecsSupported( + const media::MediaCodecsSupported& aSupported) { + dom::ContentParent::BroadcastMediaCodecsSupportedUpdate( + RemoteDecodeIn::RddProcess, aSupported); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDChild::RecvAccumulateChildHistograms( + nsTArray<HistogramAccumulation>&& aAccumulations) { + TelemetryIPC::AccumulateChildHistograms(Telemetry::ProcessID::Rdd, + aAccumulations); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDChild::RecvAccumulateChildKeyedHistograms( + nsTArray<KeyedHistogramAccumulation>&& aAccumulations) { + TelemetryIPC::AccumulateChildKeyedHistograms(Telemetry::ProcessID::Rdd, + aAccumulations); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDChild::RecvUpdateChildScalars( + nsTArray<ScalarAction>&& aScalarActions) { + TelemetryIPC::UpdateChildScalars(Telemetry::ProcessID::Rdd, aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDChild::RecvUpdateChildKeyedScalars( + nsTArray<KeyedScalarAction>&& aScalarActions) { + TelemetryIPC::UpdateChildKeyedScalars(Telemetry::ProcessID::Rdd, + aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDChild::RecvRecordChildEvents( + nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents) { + TelemetryIPC::RecordChildEvents(Telemetry::ProcessID::Rdd, aEvents); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDChild::RecvRecordDiscardedData( + const mozilla::Telemetry::DiscardedData& aDiscardedData) { + TelemetryIPC::RecordDiscardedData(Telemetry::ProcessID::Rdd, aDiscardedData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDChild::RecvFOGData(ByteBuf&& aBuf) { + glean::FOGData(std::move(aBuf)); + 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(RefPtr<RDDChild>&& aChild) + : Runnable("gfx::DeferredDeleteRDDChild"), mChild(std::move(aChild)) {} + + NS_IMETHODIMP Run() override { return NS_OK; } + + private: + RefPtr<RDDChild> mChild; +}; + +/* static */ +void RDDChild::Destroy(RefPtr<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..1e0e1b439d --- /dev/null +++ b/dom/media/ipc/RDDChild.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_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: + NS_INLINE_DECL_REFCOUNTING(RDDChild, final) + + explicit RDDChild(RDDProcessHost* aHost); + + bool Init(); + + void OnCompositorUnexpectedShutdown() override; + void OnVarChanged(const GfxVarUpdate& aVar) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport); +#if defined(XP_WIN) + mozilla::ipc::IPCResult RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver); +#endif // defined(XP_WIN) + mozilla::ipc::IPCResult RecvUpdateMediaCodecsSupported( + const media::MediaCodecsSupported& aSupported); + mozilla::ipc::IPCResult RecvFOGData(ByteBuf&& aBuf); + + mozilla::ipc::IPCResult RecvAccumulateChildHistograms( + nsTArray<HistogramAccumulation>&& aAccumulations); + mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistograms( + nsTArray<KeyedHistogramAccumulation>&& aAccumulations); + mozilla::ipc::IPCResult RecvUpdateChildScalars( + nsTArray<ScalarAction>&& aScalarActions); + mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars( + nsTArray<KeyedScalarAction>&& aScalarActions); + mozilla::ipc::IPCResult RecvRecordChildEvents( + nsTArray<ChildEventData>&& events); + mozilla::ipc::IPCResult RecvRecordDiscardedData( + const DiscardedData& aDiscardedData); + + bool SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile); + + static void Destroy(RefPtr<RDDChild>&& aChild); + + private: + ~RDDChild(); + + 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..8892e8fbbe --- /dev/null +++ b/dom/media/ipc/RDDParent.cpp @@ -0,0 +1,341 @@ +/* -*- 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 "gfxConfig.h" +#include "mozilla/Assertions.h" +#include "mozilla/FOGIPC.h" +#include "mozilla/Preferences.h" +#include "mozilla/RemoteDecoderManagerParent.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/ProcessChild.h" + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +#include "ChildProfilerController.h" + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "RDDProcessHost.h" +# include "mozilla/Sandbox.h" +# include "nsMacUtilsImpl.h" +#endif + +#include "mozilla/ipc/ProcessUtils.h" +#include "nsDebugImpl.h" +#include "nsIObserverService.h" +#include "nsIXULRuntime.h" +#include "nsThreadManager.h" + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +# include "mozilla/SandboxTestingChild.h" +#endif + +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(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const char* aParentBuildID) { + // 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(!aEndpoint.Bind(this))) { + 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(); + auto rv = wmf::MediaFoundationInitializer::HasInitialized(); + if (!rv) { + NS_WARNING("Failed to init Media Foundation in the RDD process"); + } +#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, + const bool& aIsReadyForBackgroundProcessing) { + 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(aIsReadyForBackgroundProcessing); + } +#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) { + mProfilerController = ChildProfilerController::Create(std::move(aEndpoint)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDParent::RecvNewContentRemoteDecoderManager( + Endpoint<PRemoteDecoderManagerParent>&& aEndpoint, + const ContentParentId& aParentId) { + if (!RemoteDecoderManagerParent::CreateForContent(std::move(aEndpoint), + aParentId)) { + 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::DIRECT2D, + }, + 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(); +} + +#if defined(XP_WIN) +mozilla::ipc::IPCResult RDDParent::RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver) { + 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(); +} + +mozilla::ipc::IPCResult RDDParent::RecvUnblockUntrustedModulesThread() { + if (nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService()) { + obs->NotifyObservers(nullptr, "unblock-untrusted-modules-thread", nullptr); + } + return IPC_OK(); +} +#endif // defined(XP_WIN) + +mozilla::ipc::IPCResult RDDParent::RecvPreferenceUpdate(const Pref& aPref) { + Preferences::SetPreference(aPref); + return IPC_OK(); +} + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +mozilla::ipc::IPCResult RDDParent::RecvInitSandboxTesting( + Endpoint<PSandboxTestingChild>&& aEndpoint) { + if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) { + return IPC_FAIL( + this, "InitSandboxTesting failed to initialise the child process."); + } + return IPC_OK(); +} +#endif + +mozilla::ipc::IPCResult RDDParent::RecvFlushFOGData( + FlushFOGDataResolver&& aResolver) { + glean::FlushFOGData(std::move(aResolver)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDParent::RecvTestTriggerMetrics( + TestTriggerMetricsResolver&& aResolve) { + mozilla::glean::test_only_ipc::a_counter.Add(nsIXULRuntime::PROCESS_TYPE_RDD); + aResolve(true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RDDParent::RecvTestTelemetryProbes() { + const uint32_t kExpectedUintValue = 42; + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_RDD_ONLY_UINT, + kExpectedUintValue); + return IPC_OK(); +} + +void RDDParent::ActorDestroy(ActorDestroyReason aWhy) { + if (AbnormalShutdown == aWhy) { + NS_WARNING("Shutting down RDD process early due to a crash!"); + Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, "rdd"_ns, 1); + ProcessChild::QuickExit(); + } + + // Send the last bits of Glean data over to the main process. + glean::FlushFOGData( + [](ByteBuf&& aBuf) { glean::SendFOGData(std::move(aBuf)); }); + +#ifndef NS_FREE_PERMANENT_DATA + // 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__, [&]() { + +#if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->DisableFull(); +#endif // defined(XP_WIN) + + if (mProfilerController) { + mProfilerController->Shutdown(); + mProfilerController = nullptr; + } + + 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..ebee4a7f41 --- /dev/null +++ b/dom/media/ipc/RDDParent.h @@ -0,0 +1,85 @@ +/* -*- 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/ipc/AsyncBlockers.h" + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +# include "mozilla/PSandboxTestingChild.h" +#endif + +namespace mozilla { + +class TimeStamp; +class ChildProfilerController; + +class RDDParent final : public PRDDParent { + public: + NS_INLINE_DECL_REFCOUNTING(RDDParent, final) + + RDDParent(); + + static RDDParent* GetSingleton(); + + ipc::AsyncBlockers& AsyncShutdownService() { return mShutdownBlockers; } + + bool Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const char* aParentBuildID); + + mozilla::ipc::IPCResult RecvInit(nsTArray<GfxVarUpdate>&& vars, + const Maybe<ipc::FileDescriptor>& aBrokerFd, + const bool& aCanRecordReleaseTelemetry, + const bool& aIsReadyForBackgroundProcessing); + mozilla::ipc::IPCResult RecvInitProfiler( + Endpoint<PProfilerChild>&& aEndpoint); + + mozilla::ipc::IPCResult RecvNewContentRemoteDecoderManager( + Endpoint<PRemoteDecoderManagerParent>&& aEndpoint, + const ContentParentId& aParentId); + 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); +#if defined(XP_WIN) + mozilla::ipc::IPCResult RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver); + mozilla::ipc::IPCResult RecvUnblockUntrustedModulesThread(); +#endif // defined(XP_WIN) + mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& pref); + mozilla::ipc::IPCResult RecvUpdateVar(const GfxVarUpdate& pref); + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + mozilla::ipc::IPCResult RecvInitSandboxTesting( + Endpoint<PSandboxTestingChild>&& aEndpoint); +#endif + void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult RecvFlushFOGData(FlushFOGDataResolver&& aResolver); + + mozilla::ipc::IPCResult RecvTestTriggerMetrics( + TestTriggerMetricsResolver&& aResolve); + + mozilla::ipc::IPCResult RecvTestTelemetryProbes(); + + private: + ~RDDParent(); + + const TimeStamp mLaunchTime; + RefPtr<ChildProfilerController> mProfilerController; + ipc::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..0c0d35e7b5 --- /dev/null +++ b/dom/media/ipc/RDDProcessHost.cpp @@ -0,0 +1,299 @@ +/* -*- 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 "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/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(GeckoProcessType_RDD, + /* remoteType */ ""_ns)) { + 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(base::ProcessId 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); + } + })); +} + +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 = MakeRefPtr<RDDChild>(this); + DebugOnly<bool> rv = TakeInitialEndpoint().Bind(mRDDChild.get()); + 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)) { + 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..8376224613 --- /dev/null +++ b/dom/media/ipc/RDDProcessHost.h @@ -0,0 +1,160 @@ +/* -*- 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::ipc { +class SharedPreferenceSerializer; +} // namespace mozilla::ipc +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(base::ProcessId peer_pid) 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; + + RefPtr<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..9edd1219ee --- /dev/null +++ b/dom/media/ipc/RDDProcessImpl.cpp @@ -0,0 +1,52 @@ +/* -*- 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" +#include "mozilla/GeckoArgs.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "mozilla/sandboxTarget.h" +#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +# include "prlink.h" +#endif + +namespace mozilla { + +using namespace ipc; + +RDDProcessImpl::~RDDProcessImpl() = default; + +bool RDDProcessImpl::Init(int aArgc, char* aArgv[]) { +#if defined(MOZ_SANDBOX) && defined(XP_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(); +#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX) + PR_LoadLibrary("libmozavcodec.so"); + PR_LoadLibrary("libmozavutil.so"); + PR_LoadLibrary("libavcodec.so"); + StartOpenBSDSandbox(GeckoProcessType_RDD); +#endif + Maybe<const char*> parentBuildID = + geckoargs::sParentBuildID.Get(aArgc, aArgv); + if (parentBuildID.isNothing()) { + return false; + } + + if (!ProcessChild::InitPrefs(aArgc, aArgv)) { + return false; + } + + return mRDD->Init(TakeInitialEndpoint(), *parentBuildID); +} + +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..a7d119a2c6 --- /dev/null +++ b/dom/media/ipc/RDDProcessImpl.h @@ -0,0 +1,39 @@ +/* -*- 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: + using ipc::ProcessChild::ProcessChild; + ~RDDProcessImpl(); + + bool Init(int aArgc, char* aArgv[]) override; + void CleanUp() override; + + private: + RefPtr<RDDParent> mRDD = new RDDParent; + +#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..e7da3c3569 --- /dev/null +++ b/dom/media/ipc/RDDProcessManager.cpp @@ -0,0 +1,413 @@ +/* -*- 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/ipc/ProcessChild.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; + +static bool sRDDProcessShutdown = false; + +bool RDDProcessManager::IsShutdown() const { + MOZ_ASSERT(NS_IsMainThread()); + return sRDDProcessShutdown || !sRDDSingleton; +} + +RDDProcessManager* RDDProcessManager::Get() { return sRDDSingleton; } + +void RDDProcessManager::Initialize() { + MOZ_ASSERT(XRE_IsParentProcess()); + sRDDSingleton = new RDDProcessManager(); +} + +void RDDProcessManager::Shutdown() { sRDDSingleton = nullptr; } + +void RDDProcessManager::RDDProcessShutdown() { + MOZ_ASSERT(NS_IsMainThread()); + sRDDProcessShutdown = true; + if (sRDDSingleton) { + sRDDSingleton->DestroyProcess(); + } +} + +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() { + MOZ_ASSERT(NS_IsMainThread()); + nsContentUtils::UnregisterShutdownObserver(mObserver); + Preferences::RemoveObserver(mObserver, ""); +} + +void RDDProcessManager::OnPreferenceChange(const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + if (!mProcess) { + // Process hasn't been launched yet + return; + } + + // We know prefs are ASCII here. + NS_LossyConvertUTF16toASCII strData(aData); + + mozilla::dom::Pref pref(strData, /* isLocked */ false, + /* isSanitized */ false, Nothing(), Nothing()); + + Preferences::GetPreference(&pref, GeckoProcessType_RDD, + /* remoteType */ ""_ns); + if (!!mRDDChild) { + MOZ_ASSERT(mQueuedPrefs.IsEmpty()); + mRDDChild->SendPreferenceUpdate(pref); + } else if (IsRDDProcessLaunching()) { + mQueuedPrefs.AppendElement(pref); + } +} + +RefPtr<GenericNonExclusivePromise> RDDProcessManager::LaunchRDDProcess() { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsShutdown()) { + 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; + ipc::ProcessChild::AddPlatformBuildID(extraArgs); + + // 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 (IsShutdown()) { + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_NOT_AVAILABLE, __func__); + } + + if (IsRDDProcessDestroyed()) { + 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, dom::ContentParentId aParentId) + -> RefPtr<EnsureRDDPromise> { + return InvokeAsync( + GetMainThreadSerialEventTarget(), __func__, + [aOtherProcess, aParentId, this]() -> RefPtr<EnsureRDDPromise> { + return LaunchRDDProcess()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aOtherProcess, aParentId, this]() { + if (IsShutdown()) { + return EnsureRDDPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + ipc::Endpoint<PRemoteDecoderManagerChild> endpoint; + if (!CreateContentBridge(aOtherProcess, aParentId, &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; +} + +bool RDDProcessManager::IsRDDProcessDestroyed() const { + MOZ_ASSERT(NS_IsMainThread()); + return !mRDDChild && !mProcess; +} + +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::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, dom::ContentParentId aParentId, + ipc::Endpoint<PRemoteDecoderManagerChild>* aOutRemoteDecoderManager) { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsRDDProcessDestroyed()) { + MOZ_LOG(sPDMLog, LogLevel::Debug, + ("RDD shutdown before creating content bridge")); + return false; + } + + 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), + aParentId); + + *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() : base::kInvalidProcessId; + + // 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 != base::kInvalidProcessId + ? 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 != base::kInvalidProcessId) { + gpuManager->InitVideoBridge(std::move(parentPipe), + VideoBridgeSource::RddProcess); + } else { + VideoBridgeParent::Open(std::move(parentPipe), + VideoBridgeSource::RddProcess); + } + + return true; +} + +base::ProcessId RDDProcessManager::RDDProcessPid() { + MOZ_ASSERT(NS_IsMainThread()); + base::ProcessId rddPid = + mRDDChild ? mRDDChild->OtherPid() : base::kInvalidProcessId; + 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(); +} + +RefPtr<PRDDChild::TestTriggerMetricsPromise> +RDDProcessManager::TestTriggerMetrics() { + if (!NS_WARN_IF(!mRDDChild)) { + return mRDDChild->SendTestTriggerMetrics(); + } + + return PRDDChild::TestTriggerMetricsPromise::CreateAndReject( + ipc::ResponseRejectReason::SendError, __func__); +} + +} // namespace mozilla diff --git a/dom/media/ipc/RDDProcessManager.h b/dom/media/ipc/RDDProcessManager.h new file mode 100644 index 0000000000..730ab4fa1f --- /dev/null +++ b/dom/media/ipc/RDDProcessManager.h @@ -0,0 +1,128 @@ +/* -*- 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/dom/ipc/IdType.h" +#include "mozilla/ipc/TaskFactory.h" +#include "mozilla/PRDDChild.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 RDDProcessShutdown(); + 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, dom::ContentParentId aParentId); + + 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; } + + /* + * ** Test-only Method ** + * + * Trigger RDD-process test metric instrumentation. + */ + RefPtr<PRDDChild::TestTriggerMetricsPromise> TestTriggerMetrics(); + + private: + bool IsRDDProcessLaunching(); + bool IsRDDProcessDestroyed() const; + bool CreateVideoBridge(); + + // Called from our xpcom-shutdown observer. + void OnXPCOMShutdown(); + void OnPreferenceChange(const char16_t* aData); + + RDDProcessManager(); + + // Shutdown the RDD process. + void DestroyProcess(); + + bool IsShutdown() const; + + 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, dom::ContentParentId aParentId, + 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..4622410fad --- /dev/null +++ b/dom/media/ipc/RemoteAudioDecoder.cpp @@ -0,0 +1,121 @@ +/* -*- 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" +#include "mozilla/StaticPrefs_media.h" + +namespace mozilla { + +RemoteAudioDecoderChild::RemoteAudioDecoderChild(RemoteDecodeIn aLocation) + : RemoteDecoderChild(aLocation) {} + +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, + const Maybe<uint64_t>& aMediaEngineId) { + 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; + MOZ_ALWAYS_TRUE(manager->SendPRemoteDecoderConstructor( + this, aAudioInfo, aOptions, Nothing(), aMediaEngineId, Nothing())); + return NS_OK; +} + +RemoteAudioDecoderParent::RemoteAudioDecoderParent( + RemoteDecoderManagerParent* aParent, const AudioInfo& aAudioInfo, + const CreateDecoderParams::OptionSet& aOptions, + nsISerialEventTarget* aManagerThread, TaskQueue* aDecodeTaskQueue, + Maybe<uint64_t> aMediaEngineId) + : RemoteDecoderParent(aParent, aOptions, aManagerThread, aDecodeTaskQueue, + aMediaEngineId, Nothing()), + mAudioInfo(aAudioInfo) {} + +IPCResult RemoteAudioDecoderParent::RecvConstruct( + ConstructResolver&& aResolver) { + auto params = CreateDecoderParams{mAudioInfo, mOptions, + CreateDecoderParams::NoWrapper(true), + mMediaEngineId, mTrackingId}; + + 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..55c4e001d1 --- /dev/null +++ b/dom/media/ipc/RemoteAudioDecoder.h @@ -0,0 +1,53 @@ +/* -*- 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(RemoteDecodeIn aLocation); + + MOZ_IS_CLASS_INIT + MediaResult InitIPDL(const AudioInfo& aAudioInfo, + const CreateDecoderParams::OptionSet& aOptions, + const Maybe<uint64_t>& aMediaEngineId); + + 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, + Maybe<uint64_t> aMediaEngineId); + + 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/RemoteDecodeUtils.cpp b/dom/media/ipc/RemoteDecodeUtils.cpp new file mode 100644 index 0000000000..15036a0370 --- /dev/null +++ b/dom/media/ipc/RemoteDecodeUtils.cpp @@ -0,0 +1,103 @@ +/* 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 "RemoteDecodeUtils.h" +#include "mozilla/ipc/UtilityProcessChild.h" + +namespace mozilla { + +using SandboxingKind = ipc::SandboxingKind; + +SandboxingKind GetCurrentSandboxingKind() { + MOZ_ASSERT(XRE_IsUtilityProcess()); + return ipc::UtilityProcessChild::GetSingleton()->mSandbox; +} + +SandboxingKind GetSandboxingKindFromLocation(RemoteDecodeIn aLocation) { + switch (aLocation) { + case RemoteDecodeIn::UtilityProcess_Generic: + return SandboxingKind::GENERIC_UTILITY; +#ifdef MOZ_APPLEMEDIA + case RemoteDecodeIn::UtilityProcess_AppleMedia: + return SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA; + break; +#endif +#ifdef XP_WIN + case RemoteDecodeIn::UtilityProcess_WMF: + return SandboxingKind::UTILITY_AUDIO_DECODING_WMF; +#endif +#ifdef MOZ_WMF_MEDIA_ENGINE + case RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM: + return SandboxingKind::MF_MEDIA_ENGINE_CDM; +#endif + default: + MOZ_ASSERT_UNREACHABLE("Unsupported RemoteDecodeIn"); + return SandboxingKind::COUNT; + } +} + +RemoteDecodeIn GetRemoteDecodeInFromKind(SandboxingKind aKind) { + switch (aKind) { + case SandboxingKind::GENERIC_UTILITY: + return RemoteDecodeIn::UtilityProcess_Generic; +#ifdef MOZ_APPLEMEDIA + case SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA: + return RemoteDecodeIn::UtilityProcess_AppleMedia; +#endif +#ifdef XP_WIN + case SandboxingKind::UTILITY_AUDIO_DECODING_WMF: + return RemoteDecodeIn::UtilityProcess_WMF; +#endif +#ifdef MOZ_WMF_MEDIA_ENGINE + case SandboxingKind::MF_MEDIA_ENGINE_CDM: + return RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM; +#endif + default: + MOZ_ASSERT_UNREACHABLE("Unsupported SandboxingKind"); + return RemoteDecodeIn::Unspecified; + } +} + +RemoteDecodeIn GetRemoteDecodeInFromVideoBridgeSource( + layers::VideoBridgeSource aSource) { + switch (aSource) { + case layers::VideoBridgeSource::RddProcess: + return RemoteDecodeIn::RddProcess; + case layers::VideoBridgeSource::GpuProcess: + return RemoteDecodeIn::GpuProcess; + case layers::VideoBridgeSource::MFMediaEngineCDMProcess: + return RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported VideoBridgeSource"); + return RemoteDecodeIn::Unspecified; + } +} + +const char* RemoteDecodeInToStr(RemoteDecodeIn aLocation) { + switch (aLocation) { + case RemoteDecodeIn::RddProcess: + return "RDD"; + case RemoteDecodeIn::GpuProcess: + return "GPU"; + case RemoteDecodeIn::UtilityProcess_Generic: + return "Utility Generic"; +#ifdef MOZ_APPLEMEDIA + case RemoteDecodeIn::UtilityProcess_AppleMedia: + return "Utility AppleMedia"; +#endif +#ifdef XP_WIN + case RemoteDecodeIn::UtilityProcess_WMF: + return "Utility WMF"; +#endif +#ifdef MOZ_WMF_MEDIA_ENGINE + case RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM: + return "Utility MF Media Engine CDM"; +#endif + default: + MOZ_ASSERT_UNREACHABLE("Unsupported RemoteDecodeIn"); + return "Unknown"; + } +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteDecodeUtils.h b/dom/media/ipc/RemoteDecodeUtils.h new file mode 100644 index 0000000000..212250c089 --- /dev/null +++ b/dom/media/ipc/RemoteDecodeUtils.h @@ -0,0 +1,31 @@ +/* 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 DOM_MEDIA_IPC_REMOTEDECODEUTILS_H_ +#define DOM_MEDIA_IPC_REMOTEDECODEUTILS_H_ + +#include "mozilla/Logging.h" +#include "mozilla/RemoteDecoderManagerChild.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" + +namespace mozilla { + +inline LazyLogModule gRemoteDecodeLog{"RemoteDecode"}; + +// Return the sandboxing kind of the current utility process. Should only be +// called on the utility process. +ipc::SandboxingKind GetCurrentSandboxingKind(); + +ipc::SandboxingKind GetSandboxingKindFromLocation(RemoteDecodeIn aLocation); + +RemoteDecodeIn GetRemoteDecodeInFromKind(ipc::SandboxingKind aKind); + +RemoteDecodeIn GetRemoteDecodeInFromVideoBridgeSource( + layers::VideoBridgeSource aSource); + +const char* RemoteDecodeInToStr(RemoteDecodeIn aLocation); + +} // namespace mozilla + +#endif // DOM_MEDIA_IPC_REMOTEDECODEUTILS_H_ diff --git a/dom/media/ipc/RemoteDecoderChild.cpp b/dom/media/ipc/RemoteDecoderChild.cpp new file mode 100644 index 0000000000..ff36f0ff1f --- /dev/null +++ b/dom/media/ipc/RemoteDecoderChild.cpp @@ -0,0 +1,315 @@ +/* -*- 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" + +#include "mozilla/RemoteDecodeUtils.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_REMOTE_DECODER_CRASHED_RDD_OR_GPU_ERR, + __func__); + callback(error); + })); + return; + } + + nsresult err = NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_UTILITY_ERR; + if (mLocation == RemoteDecodeIn::GpuProcess || + mLocation == RemoteDecodeIn::RddProcess) { + err = NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_RDD_OR_GPU_ERR; + } else if (mLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) { + err = NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR; + } + // 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 by + aCallback(MediaResult(err, __func__)); +} + +// ActorDestroy is called if the channel goes down while waiting for a response. +void RemoteDecoderChild::ActorDestroy(ActorDestroyReason aWhy) { + mRemoteDecoderCrashed = (aWhy == AbnormalShutdown); + mDecodedData.Clear(); + CleanupShmemRecycleAllocator(); + RecordShutdownTelemetry(mRemoteDecoderCrashed); +} + +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(); + + mRemoteDecoderCrashed = false; + + 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(); + mDescription.Append(" ("); + mDescription.Append(RemoteDecodeInToStr(GetManager()->Location())); + mDescription.Append(" remote)"); + + mProcessName = initResponse.decoderProcessName(); + mCodecName = initResponse.decoderCodecName(); + + 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(); + + if (mRemoteDecoderCrashed) { + nsresult err = NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_UTILITY_ERR; + if (mLocation == RemoteDecodeIn::GpuProcess || + mLocation == RemoteDecodeIn::RddProcess) { + err = NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_RDD_OR_GPU_ERR; + } else if (mLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) { + err = NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR; + } + return MediaDataDecoder::DecodePromise::CreateAndReject(err, __func__); + } + + 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; +} + +nsCString RemoteDecoderChild::GetProcessName() const { + AssertOnManagerThread(); + return mProcessName; +} + +nsCString RemoteDecoderChild::GetCodecName() const { + AssertOnManagerThread(); + return mCodecName; +} + +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..b1c283cbe8 --- /dev/null +++ b/dom/media/ipc/RemoteDecoderChild.h @@ -0,0 +1,90 @@ +/* -*- 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/RemoteDecoderManagerChild.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; + nsCString GetProcessName() const; + nsCString GetCodecName() 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; + nsCString mProcessName; + nsCString mCodecName; + bool mIsHardwareAccelerated = false; + bool mRemoteDecoderCrashed = 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..48da254f39 --- /dev/null +++ b/dom/media/ipc/RemoteDecoderManagerChild.cpp @@ -0,0 +1,909 @@ +/* -*- 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 "ErrorList.h" +#include "MP4Decoder.h" +#include "PDMFactory.h" +#include "PlatformDecoderModule.h" +#include "RemoteAudioDecoder.h" +#include "RemoteMediaDataDecoder.h" +#include "RemoteVideoDecoder.h" +#include "VideoUtils.h" +#include "mozilla/DataMutex.h" +#include "mozilla/RemoteDecodeUtils.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 "mozilla/ipc/UtilityAudioDecoderChild.h" +#include "mozilla/MozPromise.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/StaticPtr.h" +#include "nsContentUtils.h" +#include "nsIObserver.h" +#include "nsPrintfCString.h" + +#ifdef MOZ_WMF_MEDIA_ENGINE +# include "MFMediaEngineChild.h" +#endif + +#ifdef MOZ_WMF_CDM +# include "MFCDMChild.h" +#endif + +namespace mozilla { + +#define LOG(msg, ...) \ + MOZ_LOG(gRemoteDecodeLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +using namespace layers; +using namespace gfx; + +// Used so that we only ever attempt to check if the RDD/GPU/Utility processes +// should be launched serially. Protects sLaunchPromise +StaticMutex sLaunchMutex; +static EnumeratedArray<RemoteDecodeIn, RemoteDecodeIn::SENTINEL, + StaticRefPtr<GenericNonExclusivePromise>> + sLaunchPromises MOZ_GUARDED_BY(sLaunchMutex); + +// 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 EnumeratedArray<RemoteDecodeIn, RemoteDecodeIn::SENTINEL, + StaticRefPtr<RemoteDecoderManagerChild>> + sRemoteDecoderManagerChildForProcesses; + +static StaticAutoPtr<nsTArray<RefPtr<Runnable>>> sRecreateTasks; + +// Used for protecting codec support information collected from different remote +// processes. +StaticMutex sProcessSupportedMutex; +static EnumeratedArray<RemoteDecodeIn, RemoteDecodeIn::SENTINEL, + Maybe<media::MediaCodecsSupported>> + sProcessSupported MOZ_GUARDED_BY(sProcessSupportedMutex); + +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()); + LOG("RemoteDecoderManagerChild Init"); + + auto remoteDecoderManagerThread = sRemoteDecoderManagerChildThread.Lock(); + if (!*remoteDecoderManagerThread) { + LOG("RemoteDecoderManagerChild's thread is created"); + // 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_WARNING_ASSERTION(bgActor, + "Failed to start Background channel"); + Unused << bgActor; + })); + + NS_ENSURE_SUCCESS_VOID(rv); + *remoteDecoderManagerThread = childThread; + sRecreateTasks = new 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", + &OpenRemoteDecoderManagerChildForProcess, + std::move(aVideoManager), RemoteDecodeIn::GpuProcess))); +} + +/* static */ +void RemoteDecoderManagerChild::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + LOG("RemoteDecoderManagerChild Shutdown"); + + if (sObserver) { + nsContentUtils::UnregisterShutdownObserver(sObserver); + sObserver = nullptr; + } + + nsCOMPtr<nsIThread> childThread; + { + auto remoteDecoderManagerThread = sRemoteDecoderManagerChildThread.Lock(); + childThread = remoteDecoderManagerThread->forget(); + LOG("RemoteDecoderManagerChild's thread is released"); + } + if (childThread) { + MOZ_ALWAYS_SUCCEEDS(childThread->Dispatch(NS_NewRunnableFunction( + "dom::RemoteDecoderManagerChild::Shutdown", []() { + for (auto& p : sRemoteDecoderManagerChildForProcesses) { + if (p && p->CanSend()) { + p->Close(); + } + p = nullptr; + } + { + StaticMutexAutoLock lock(sLaunchMutex); + for (auto& p : sLaunchPromises) { + p = 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: + case RemoteDecodeIn::RddProcess: + case RemoteDecodeIn::UtilityProcess_Generic: + case RemoteDecodeIn::UtilityProcess_AppleMedia: + case RemoteDecodeIn::UtilityProcess_WMF: + case RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM: + return sRemoteDecoderManagerChildForProcesses[aLocation]; + default: + MOZ_CRASH("Unexpected RemoteDecode variant"); + return nullptr; + } +} + +/* static */ +nsISerialEventTarget* RemoteDecoderManagerChild::GetManagerThread() { + auto remoteDecoderManagerThread = sRemoteDecoderManagerChildThread.Lock(); + return *remoteDecoderManagerThread; +} + +/* static */ +bool RemoteDecoderManagerChild::Supports( + RemoteDecodeIn aLocation, const SupportDecoderParams& aParams, + DecoderDoctorDiagnostics* aDiagnostics) { + Maybe<media::MediaCodecsSupported> supported; + switch (aLocation) { + case RemoteDecodeIn::GpuProcess: + case RemoteDecodeIn::RddProcess: + case RemoteDecodeIn::UtilityProcess_AppleMedia: + case RemoteDecodeIn::UtilityProcess_Generic: + case RemoteDecodeIn::UtilityProcess_WMF: + case RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM: { + StaticMutexAutoLock lock(sProcessSupportedMutex); + supported = sProcessSupported[aLocation]; + break; + } + default: + return false; + } + if (!supported) { + // We haven't received the correct information yet from either the GPU or + // the RDD process nor the Utility process. + if (aLocation == RemoteDecodeIn::UtilityProcess_Generic || + aLocation == RemoteDecodeIn::UtilityProcess_AppleMedia || + aLocation == RemoteDecodeIn::UtilityProcess_WMF || + aLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) { + LaunchUtilityProcessIfNeeded(aLocation); + } + if (aLocation == RemoteDecodeIn::RddProcess) { + // Ensure the RDD process got started. + // TODO: This can be removed once bug 1684991 is fixed. + LaunchRDDProcessIfNeeded(); + } + + // Assume the format is supported to prevent false negative, if the remote + // process supports that specific track type. + const bool isVideo = aParams.mConfig.IsVideo(); + const bool isAudio = aParams.mConfig.IsAudio(); + const auto trackSupport = GetTrackSupport(aLocation); + if (isVideo) { + // Special condition for HEVC, which can only be supported in specific + // process. As HEVC support is still a experimental feature, we don't want + // to report support for it arbitrarily. + if (MP4Decoder::IsHEVC(aParams.mConfig.mMimeType)) { + return aLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM || + aLocation == RemoteDecodeIn::GpuProcess; + } + return trackSupport.contains(TrackSupport::Video); + } + if (isAudio) { + return trackSupport.contains(TrackSupport::Audio); + } + MOZ_ASSERT_UNREACHABLE("Not audio and video?!"); + return false; + } + + // 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, + aLocation) + .isEmpty(); +} + +/* static */ +RefPtr<PlatformDecoderModule::CreateDecoderPromise> +RemoteDecoderManagerChild::CreateAudioDecoder( + 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 (!GetTrackSupport(aLocation).contains(TrackSupport::Audio)) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_CANCELED, + nsPrintfCString("%s doesn't support audio decoding", + RemoteDecodeInToStr(aLocation)) + .get()), + __func__); + } + + RefPtr<GenericNonExclusivePromise> launchPromise; + if (StaticPrefs::media_utility_process_enabled() && + (aLocation == RemoteDecodeIn::UtilityProcess_Generic || + aLocation == RemoteDecodeIn::UtilityProcess_AppleMedia || + aLocation == RemoteDecodeIn::UtilityProcess_WMF)) { + launchPromise = LaunchUtilityProcessIfNeeded(aLocation); + } else if (aLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) { + launchPromise = LaunchUtilityProcessIfNeeded(aLocation); + } else { + if (StaticPrefs::media_allow_audio_non_utility()) { + launchPromise = LaunchRDDProcessIfNeeded(); + } else { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + MediaResult( + NS_ERROR_DOM_MEDIA_DENIED_IN_NON_UTILITY, + nsPrintfCString("%s is not allowed to perform audio decoding", + RemoteDecodeInToStr(aLocation)) + .get()), + __func__); + } + } + LOG("Create audio decoder in %s", RemoteDecodeInToStr(aLocation)); + + return launchPromise->Then( + managerThread, __func__, + [params = CreateDecoderParamsForAsync(aParams), aLocation](bool) { + auto child = MakeRefPtr<RemoteAudioDecoderChild>(aLocation); + MediaResult result = child->InitIPDL( + params.AudioConfig(), params.mOptions, params.mMediaEngineId); + if (NS_FAILED(result)) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + result, __func__); + } + return Construct(std::move(child), aLocation); + }, + [aLocation](nsresult aResult) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + MediaResult(aResult, + aLocation == RemoteDecodeIn::GpuProcess + ? "Couldn't start GPU process" + : (aLocation == RemoteDecodeIn::RddProcess + ? "Couldn't start RDD process" + : "Couldn't start Utility 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__); + } + + if (!GetTrackSupport(aLocation).contains(TrackSupport::Video)) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_CANCELED, + nsPrintfCString("%s doesn't support video decoding", + RemoteDecodeInToStr(aLocation)) + .get()), + __func__); + } + + MOZ_ASSERT(aLocation != RemoteDecodeIn::Unspecified); + + RefPtr<GenericNonExclusivePromise> p; + if (aLocation == RemoteDecodeIn::GpuProcess) { + p = GenericNonExclusivePromise::CreateAndResolve(true, __func__); + } else if (aLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) { + p = LaunchUtilityProcessIfNeeded(aLocation); + } else { + p = LaunchRDDProcessIfNeeded(); + } + LOG("Create video decoder in %s", RemoteDecodeInToStr(aLocation)); + + 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(), + params.mMediaEngineId, params.mTrackingId); + if (NS_FAILED(result)) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + result, __func__); + } + return Construct(std::move(child), aLocation); + }, + [](nsresult aResult) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + MediaResult(aResult, "Couldn't start RDD process"), __func__); + }); +} + +/* static */ +RefPtr<PlatformDecoderModule::CreateDecoderPromise> +RemoteDecoderManagerChild::Construct(RefPtr<RemoteDecoderChild>&& aChild, + RemoteDecodeIn aLocation) { + 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__); + }, + [aLocation](const mozilla::ipc::ResponseRejectReason& aReason) { + // The parent has died. + nsresult err = + NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_UTILITY_ERR; + if (aLocation == RemoteDecodeIn::GpuProcess || + aLocation == RemoteDecodeIn::RddProcess) { + err = NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_RDD_OR_GPU_ERR; + } else if (aLocation == + RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) { + err = NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR; + } + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + err, __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); + auto& rddLaunchPromise = sLaunchPromises[RemoteDecodeIn::RddProcess]; + if (rddLaunchPromise) { + return rddLaunchPromise; + } + + // 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 = std::get<0>(aResult.ResolveValue()); + if (NS_FAILED(rv)) { + return GenericNonExclusivePromise::CreateAndReject(rv, + __func__); + } + OpenRemoteDecoderManagerChildForProcess( + std::get<1>(std::move(aResult.ResolveValue())), + RemoteDecodeIn::RddProcess); + return GenericNonExclusivePromise::CreateAndResolve(true, + __func__); + }); + }); + + // This should not be dispatched to a threadpool thread, so use managerThread + p = p->Then( + managerThread, __func__, + [](const GenericNonExclusivePromise::ResolveOrRejectValue& aResult) { + StaticMutexAutoLock lock(sLaunchMutex); + sLaunchPromises[RemoteDecodeIn::RddProcess] = nullptr; + return GenericNonExclusivePromise::CreateAndResolveOrReject(aResult, + __func__); + }); + + rddLaunchPromise = p; + return rddLaunchPromise; +} + +/* static */ +RefPtr<GenericNonExclusivePromise> +RemoteDecoderManagerChild::LaunchUtilityProcessIfNeeded( + RemoteDecodeIn aLocation) { + 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__); + } + + MOZ_ASSERT(aLocation == RemoteDecodeIn::UtilityProcess_Generic || + aLocation == RemoteDecodeIn::UtilityProcess_AppleMedia || + aLocation == RemoteDecodeIn::UtilityProcess_WMF || + aLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM); + StaticMutexAutoLock lock(sLaunchMutex); + auto& utilityLaunchPromise = sLaunchPromises[aLocation]; + + if (utilityLaunchPromise) { + return utilityLaunchPromise; + } + + // We have a couple possible states here. We are in a content process + // and: + // 1) the Utility process has never been launched. Utility should be launched + // and the IPC connections setup. + // 2) the Utility 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 Utility 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 + // LaunchUtilityProcess which will launch Utility if necessary, and setup the + // IPC connections between *this* content process and the Utility process. + + RefPtr<GenericNonExclusivePromise> p = InvokeAsync( + managerThread, __func__, + [aLocation]() -> RefPtr<GenericNonExclusivePromise> { + auto* rps = GetSingleton(aLocation); + 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->SendEnsureUtilityProcessAndCreateBridge(aLocation) + ->Then(managerThread, __func__, + [aLocation](ipc::PBackgroundChild:: + EnsureUtilityProcessAndCreateBridgePromise:: + ResolveOrRejectValue&& aResult) + -> RefPtr<GenericNonExclusivePromise> { + 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 = std::get<0>(aResult.ResolveValue()); + if (NS_FAILED(rv)) { + return GenericNonExclusivePromise::CreateAndReject( + rv, __func__); + } + OpenRemoteDecoderManagerChildForProcess( + std::get<1>(std::move(aResult.ResolveValue())), + aLocation); + return GenericNonExclusivePromise::CreateAndResolve( + true, __func__); + }); + }); + + // Let's make sure this promise is also run on the managerThread to avoid + // situations where it would be run on a threadpool thread. + // During bug 1794988 this was happening when enabling Utility for audio on + // Android when running the sequence of tests + // dom/media/test/test_access_control.html + // dom/media/test/test_arraybuffer.html + // + // We would have a launched utility process but the promises would not have + // been cleared, so any subsequent tentative to perform audio decoding would + // think the process is not yet ran and it would try to wait on the pending + // promises. + p = p->Then( + managerThread, __func__, + [aLocation]( + const GenericNonExclusivePromise::ResolveOrRejectValue& aResult) { + StaticMutexAutoLock lock(sLaunchMutex); + sLaunchPromises[aLocation] = nullptr; + return GenericNonExclusivePromise::CreateAndResolveOrReject(aResult, + __func__); + }); + utilityLaunchPromise = p; + return utilityLaunchPromise; +} + +/* static */ +TrackSupportSet RemoteDecoderManagerChild::GetTrackSupport( + RemoteDecodeIn aLocation) { + switch (aLocation) { + case RemoteDecodeIn::GpuProcess: { + return TrackSupportSet{TrackSupport::Video}; + } + case RemoteDecodeIn::RddProcess: { + TrackSupportSet s{TrackSupport::Video}; + // Only use RDD for audio decoding if we don't have the utility process. + if (!StaticPrefs::media_utility_process_enabled()) { + s += TrackSupport::Audio; + } + return s; + } + case RemoteDecodeIn::UtilityProcess_Generic: + case RemoteDecodeIn::UtilityProcess_AppleMedia: + case RemoteDecodeIn::UtilityProcess_WMF: + return StaticPrefs::media_utility_process_enabled() + ? TrackSupportSet{TrackSupport::Audio} + : TrackSupportSet{TrackSupport::None}; + case RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM: { + TrackSupportSet s{TrackSupport::None}; +#ifdef MOZ_WMF_MEDIA_ENGINE + // When we enable the media engine, it would need both tracks to + // synchronize the a/v playback. + if (StaticPrefs::media_wmf_media_engine_enabled()) { + s += TrackSupportSet{TrackSupport::Audio, TrackSupport::Video}; + } +#endif + return s; + } + default: + MOZ_ASSERT_UNREACHABLE("Undefined location!"); + } + return TrackSupportSet{TrackSupport::None}; +} + +PRemoteDecoderChild* RemoteDecoderManagerChild::AllocPRemoteDecoderChild( + const RemoteDecoderInfoIPDL& /* not used */, + const CreateDecoderParams::OptionSet& aOptions, + const Maybe<layers::TextureFactoryIdentifier>& aIdentifier, + const Maybe<uint64_t>& aMediaEngineId, + const Maybe<TrackingId>& aTrackingId) { + // 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; +} + +PMFMediaEngineChild* RemoteDecoderManagerChild::AllocPMFMediaEngineChild() { + MOZ_ASSERT_UNREACHABLE( + "RemoteDecoderManagerChild cannot create MFMediaEngineChild classes"); + return nullptr; +} + +bool RemoteDecoderManagerChild::DeallocPMFMediaEngineChild( + PMFMediaEngineChild* actor) { +#ifdef MOZ_WMF_MEDIA_ENGINE + MFMediaEngineChild* child = static_cast<MFMediaEngineChild*>(actor); + child->IPDLActorDestroyed(); +#endif + return true; +} + +PMFCDMChild* RemoteDecoderManagerChild::AllocPMFCDMChild(const nsAString&) { + MOZ_ASSERT_UNREACHABLE( + "RemoteDecoderManagerChild cannot create PMFContentDecryptionModuleChild " + "classes"); + return nullptr; +} + +bool RemoteDecoderManagerChild::DeallocPMFCDMChild(PMFCDMChild* actor) { +#ifdef MOZ_WMF_CDM + static_cast<MFCDMChild*>(actor)->IPDLActorDestroyed(); +#endif + return true; +} + +RemoteDecoderManagerChild::RemoteDecoderManagerChild(RemoteDecodeIn aLocation) + : mLocation(aLocation) { + MOZ_ASSERT(mLocation == RemoteDecodeIn::GpuProcess || + mLocation == RemoteDecodeIn::RddProcess || + mLocation == RemoteDecodeIn::UtilityProcess_Generic || + mLocation == RemoteDecodeIn::UtilityProcess_AppleMedia || + mLocation == RemoteDecodeIn::UtilityProcess_WMF || + mLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM); +} + +/* static */ +void RemoteDecoderManagerChild::OpenRemoteDecoderManagerChildForProcess( + Endpoint<PRemoteDecoderManagerChild>&& aEndpoint, + RemoteDecodeIn aLocation) { + nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread(); + if (!managerThread) { + // We've been shutdown, bail. + return; + } + MOZ_ASSERT(managerThread->IsOnCurrentThread()); + + // For GPU process, 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. + auto runRecreateTasksIfNeeded = MakeScopeExit([aLocation]() { + if (aLocation == RemoteDecodeIn::GpuProcess) { + for (Runnable* task : *sRecreateTasks) { + task->Run(); + } + sRecreateTasks->Clear(); + } + }); + + // Only create RemoteDecoderManagerChild, bind new endpoint and init + // ipdl if: + // 1) haven't init'd sRemoteDecoderManagerChildForProcesses[aLocation] + // or + // 2) if ActorDestroy was called meaning the other end of the ipc channel was + // torn down + // But for GPU process, we always recreate a new manager child. + MOZ_ASSERT(aLocation != RemoteDecodeIn::SENTINEL); + auto& remoteDecoderManagerChild = + sRemoteDecoderManagerChildForProcesses[aLocation]; + if (aLocation != RemoteDecodeIn::GpuProcess && remoteDecoderManagerChild && + remoteDecoderManagerChild->CanSend()) { + return; + } + remoteDecoderManagerChild = nullptr; + if (aEndpoint.IsValid()) { + RefPtr<RemoteDecoderManagerChild> manager = + new RemoteDecoderManagerChild(aLocation); + if (aEndpoint.Bind(manager)) { + remoteDecoderManagerChild = manager; + } + } +} + +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_DispatchAndSpinEventLoopUntilComplete here since that will + // 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) { + dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid()); +} + +void RemoteDecoderManagerChild::SetSupported( + RemoteDecodeIn aLocation, const media::MediaCodecsSupported& aSupported) { + switch (aLocation) { + case RemoteDecodeIn::GpuProcess: + case RemoteDecodeIn::RddProcess: + case RemoteDecodeIn::UtilityProcess_AppleMedia: + case RemoteDecodeIn::UtilityProcess_Generic: + case RemoteDecodeIn::UtilityProcess_WMF: + case RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM: { + StaticMutexAutoLock lock(sProcessSupportedMutex); + sProcessSupported[aLocation] = Some(aSupported); + break; + } + default: + MOZ_CRASH("Not to be used for any other process"); + } +} + +#undef LOG + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteDecoderManagerChild.h b/dom/media/ipc/RemoteDecoderManagerChild.h new file mode 100644 index 0000000000..effeb92d93 --- /dev/null +++ b/dom/media/ipc/RemoteDecoderManagerChild.h @@ -0,0 +1,153 @@ +/* -*- 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 "PDMFactory.h" +#include "ipc/EnumSerializer.h" +#include "mozilla/EnumTypeTraits.h" +#include "mozilla/PRemoteDecoderManagerChild.h" +#include "mozilla/layers/VideoBridgeUtils.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" + +namespace mozilla { + +class PMFCDMChild; +class PMFMediaEngineChild; +class RemoteDecoderChild; + +enum class RemoteDecodeIn { + Unspecified, + RddProcess, + GpuProcess, + UtilityProcess_Generic, + UtilityProcess_AppleMedia, + UtilityProcess_WMF, + UtilityProcess_MFMediaEngineCDM, + SENTINEL, +}; + +enum class TrackSupport { + None, + Audio, + Video, +}; +using TrackSupportSet = EnumSet<TrackSupport, uint8_t>; + +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 media::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, RemoteDecodeIn aLocation); + static RefPtr<PlatformDecoderModule::CreateDecoderPromise> CreateVideoDecoder( + const CreateDecoderParams& aParams, RemoteDecodeIn aLocation); + + // Can be called from any thread. + static nsISerialEventTarget* GetManagerThread(); + + // Return the track support information based on the location of the remote + // process. Thread-safe. + static TrackSupportSet GetTrackSupport(RemoteDecodeIn aLocation); + + // 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::Shmem* aShmem) override { + return PRemoteDecoderManagerChild::AllocShmem(aSize, aShmem); + } + bool AllocUnsafeShmem(size_t aSize, mozilla::ipc::Shmem* aShmem) override { + return PRemoteDecoderManagerChild::AllocUnsafeShmem(aSize, 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; } + + // A thread-safe method to launch the utility process if it hasn't launched + // yet. + static RefPtr<GenericNonExclusivePromise> LaunchUtilityProcessIfNeeded( + RemoteDecodeIn aLocation); + + protected: + void HandleFatalError(const char* aMsg) override; + + PRemoteDecoderChild* AllocPRemoteDecoderChild( + const RemoteDecoderInfoIPDL& aRemoteDecoderInfo, + const CreateDecoderParams::OptionSet& aOptions, + const Maybe<layers::TextureFactoryIdentifier>& aIdentifier, + const Maybe<uint64_t>& aMediaEngineId, + const Maybe<TrackingId>& aTrackingId); + bool DeallocPRemoteDecoderChild(PRemoteDecoderChild* actor); + + PMFMediaEngineChild* AllocPMFMediaEngineChild(); + bool DeallocPMFMediaEngineChild(PMFMediaEngineChild* actor); + + PMFCDMChild* AllocPMFCDMChild(const nsAString& aKeySystem); + bool DeallocPMFCDMChild(PMFCDMChild* actor); + + private: + explicit RemoteDecoderManagerChild(RemoteDecodeIn aLocation); + ~RemoteDecoderManagerChild() = default; + static RefPtr<PlatformDecoderModule::CreateDecoderPromise> Construct( + RefPtr<RemoteDecoderChild>&& aChild, RemoteDecodeIn aLocation); + + static void OpenRemoteDecoderManagerChildForProcess( + Endpoint<PRemoteDecoderManagerChild>&& aEndpoint, + RemoteDecodeIn aLocation); + + // A thread-safe method to launch the RDD process if it hasn't launched yet. + static RefPtr<GenericNonExclusivePromise> LaunchRDDProcessIfNeeded(); + + // 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..ffb3b75dbe --- /dev/null +++ b/dom/media/ipc/RemoteDecoderManagerParent.cpp @@ -0,0 +1,378 @@ +/* -*- 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/RemoteDecodeUtils.h" +#include "mozilla/ipc/UtilityProcessChild.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" +#include "mozilla/layers/VideoBridgeParent.h" +#include "nsIObserverService.h" + +#ifdef MOZ_WMF_MEDIA_ENGINE +# include "MFMediaEngineParent.h" +#endif + +#ifdef MOZ_WMF_CDM +# include "MFCDMParent.h" +#endif + +namespace mozilla { + +#define LOG(msg, ...) \ + MOZ_LOG(gRemoteDecodeLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +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 = TaskQueue::Create( + 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, + dom::ContentParentId aChildId) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_RDD || + XRE_GetProcessType() == GeckoProcessType_Utility || + XRE_GetProcessType() == GeckoProcessType_GPU); + MOZ_ASSERT(NS_IsMainThread()); + + if (!StartupThreads()) { + return false; + } + + RefPtr<RemoteDecoderManagerParent> parent = new RemoteDecoderManagerParent( + sRemoteDecoderManagerParentThread, aChildId); + + 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) { + LOG("Create video bridge"); + // We never want to decode in the GPU process, but output + // frames to the parent process. + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_RDD || + XRE_GetProcessType() == GeckoProcessType_Utility); +#ifdef MOZ_WMF_MEDIA_ENGINE + MOZ_ASSERT_IF( + XRE_GetProcessType() == GeckoProcessType_Utility, + GetCurrentSandboxingKind() == SandboxingKind::MF_MEDIA_ENGINE_CDM); +#endif + 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, dom::ContentParentId aContentId) + : mThread(aThread), mContentId(aContentId) { + MOZ_COUNT_CTOR(RemoteDecoderManagerParent); + auto& registrar = + XRE_IsGPUProcess() ? GPUParent::GetSingleton()->AsyncShutdownService() + : XRE_IsUtilityProcess() + ? UtilityProcessChild::GetSingleton()->AsyncShutdownService() + : RDDParent::GetSingleton()->AsyncShutdownService(); + registrar.Register(this); +} + +RemoteDecoderManagerParent::~RemoteDecoderManagerParent() { + MOZ_COUNT_DTOR(RemoteDecoderManagerParent); + auto& registrar = + XRE_IsGPUProcess() ? GPUParent::GetSingleton()->AsyncShutdownService() + : XRE_IsUtilityProcess() + ? UtilityProcessChild::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, + const Maybe<uint64_t>& aMediaEngineId, + const Maybe<TrackingId>& aTrackingId) { + RefPtr<TaskQueue> decodeTaskQueue = + TaskQueue::Create(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, + aMediaEngineId, aTrackingId); + } + + if (aRemoteDecoderInfo.type() == RemoteDecoderInfoIPDL::TAudioInfo) { + return new RemoteAudioDecoderParent( + this, aRemoteDecoderInfo.get_AudioInfo(), aOptions, + sRemoteDecoderManagerParentThread, decodeTaskQueue, aMediaEngineId); + } + + MOZ_CRASH("unrecognized type of RemoteDecoderInfoIPDL union"); + return nullptr; +} + +bool RemoteDecoderManagerParent::DeallocPRemoteDecoderParent( + PRemoteDecoderParent* actor) { + RemoteDecoderParent* parent = static_cast<RemoteDecoderParent*>(actor); + parent->Destroy(); + return true; +} + +PMFMediaEngineParent* RemoteDecoderManagerParent::AllocPMFMediaEngineParent() { +#ifdef MOZ_WMF_MEDIA_ENGINE + return new MFMediaEngineParent(this, sRemoteDecoderManagerParentThread); +#else + return nullptr; +#endif +} + +bool RemoteDecoderManagerParent::DeallocPMFMediaEngineParent( + PMFMediaEngineParent* actor) { +#ifdef MOZ_WMF_MEDIA_ENGINE + MFMediaEngineParent* parent = static_cast<MFMediaEngineParent*>(actor); + parent->Destroy(); +#endif + return true; +} + +PMFCDMParent* RemoteDecoderManagerParent::AllocPMFCDMParent( + const nsAString& aKeySystem) { +#ifdef MOZ_WMF_CDM + return new MFCDMParent(aKeySystem, this, sRemoteDecoderManagerParentThread); +#else + return nullptr; +#endif +} + +bool RemoteDecoderManagerParent::DeallocPMFCDMParent(PMFCDMParent* actor) { +#ifdef MOZ_WMF_CDM + static_cast<MFCDMParent*>(actor)->Destroy(); +#endif + 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"); + } +} + +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(); + } + + // Let's try reading directly into the shmem first to avoid extra copies. + SurfaceDescriptorBuffer sdb; + nsresult rv = image->BuildSurfaceDescriptorBuffer( + sdb, Image::BuildSdbFlags::RgbOnly, [&](uint32_t aBufferSize) { + Shmem buffer; + if (!AllocShmem(aBufferSize, &buffer)) { + return MemoryOrShmem(); + } + return MemoryOrShmem(std::move(buffer)); + }); + + if (NS_SUCCEEDED(rv)) { + *aResult = std::move(sdb); + return IPC_OK(); + } + + if (sdb.data().type() == MemoryOrShmem::TShmem) { + DeallocShmem(sdb.data().get_Shmem()); + } + + if (rv != NS_ERROR_NOT_IMPLEMENTED) { + *aResult = null_t(); + return IPC_OK(); + } + + // Fallback to reading to a SourceSurface and copying that into a shmem. + 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, &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), + 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); + } +} + +#undef LOG + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteDecoderManagerParent.h b/dom/media/ipc/RemoteDecoderManagerParent.h new file mode 100644 index 0000000000..772eee323e --- /dev/null +++ b/dom/media/ipc/RemoteDecoderManagerParent.h @@ -0,0 +1,101 @@ +/* -*- 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/dom/ipc/IdType.h" +#include "mozilla/layers/VideoBridgeChild.h" + +namespace mozilla { + +class PDMFactory; +class PMFCDMParent; +class PMFMediaEngineParent; + +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, + dom::ContentParentId aContentId); + + 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(); + + const dom::ContentParentId& GetContentId() const { return mContentId; } + + protected: + PRemoteDecoderParent* AllocPRemoteDecoderParent( + const RemoteDecoderInfoIPDL& aRemoteDecoderInfo, + const CreateDecoderParams::OptionSet& aOptions, + const Maybe<layers::TextureFactoryIdentifier>& aIdentifier, + const Maybe<uint64_t>& aMediaEngineId, + const Maybe<TrackingId>& aTrackingId); + bool DeallocPRemoteDecoderParent(PRemoteDecoderParent* actor); + + PMFMediaEngineParent* AllocPMFMediaEngineParent(); + bool DeallocPMFMediaEngineParent(PMFMediaEngineParent* actor); + + PMFCDMParent* AllocPMFCDMParent(const nsAString& aKeySystem); + bool DeallocPMFCDMParent(PMFCDMParent* actor); + + mozilla::ipc::IPCResult RecvReadback(const SurfaceDescriptorGPUVideo& aSD, + SurfaceDescriptor* aResult); + mozilla::ipc::IPCResult RecvDeallocateSurfaceDescriptorGPUVideo( + const SurfaceDescriptorGPUVideo& aSD); + + void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override; + + private: + RemoteDecoderManagerParent(nsISerialEventTarget* aThread, + dom::ContentParentId aContentId); + ~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; + dom::ContentParentId mContentId; +}; + +} // 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..4d61f67a0e --- /dev/null +++ b/dom/media/ipc/RemoteDecoderModule.cpp @@ -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/. */ +#include "RemoteDecoderModule.h" + +#ifdef MOZ_AV1 +# include "AOMDecoder.h" +#endif +#include "RemoteAudioDecoder.h" +#include "RemoteDecoderManagerChild.h" +#include "RemoteMediaDataDecoder.h" +#include "RemoteVideoDecoder.h" +#include "VideoUtils.h" +#include "gfxConfig.h" +#include "mozilla/RemoteDecodeUtils.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) {} + +media::DecodeSupportSet RemoteDecoderModule::SupportsMimeType( + const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const { + MOZ_CRASH("Deprecated: Use RemoteDecoderModule::Supports"); +} // namespace mozilla + +media::DecodeSupportSet RemoteDecoderModule::Supports( + const SupportDecoderParams& aParams, + DecoderDoctorDiagnostics* aDiagnostics) const { + bool supports = + RemoteDecoderManagerChild::Supports(mLocation, aParams, aDiagnostics); + // This should only be supported by mf media engine cdm process. + if (aParams.mMediaEngineId && + mLocation != RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) { + supports = false; + } + MOZ_LOG(sPDMLog, LogLevel::Debug, + ("Sandbox %s decoder %s requested type %s", + RemoteDecodeInToStr(mLocation), supports ? "supports" : "rejects", + aParams.MimeType().get())); + if (supports) { + // TODO: Note that we do not yet distinguish between SW/HW decode support. + // Will be done in bug 1754239. + return media::DecodeSupport::SoftwareDecode; + } + return media::DecodeSupportSet{}; +} + +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 (aParams.mConfig.mMimeType.Equals("audio/opus") && + IsDefaultPlaybackDeviceMono()) { + CreateDecoderParams params = aParams; + params.mOptions += CreateDecoderParams::Option::DefaultPlaybackDeviceMono; + return RemoteDecoderManagerChild::CreateAudioDecoder(params, mLocation); + } + return RemoteDecoderManagerChild::CreateAudioDecoder(aParams, mLocation); + } + 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..a11f73227e --- /dev/null +++ b/dom/media/ipc/RemoteDecoderModule.h @@ -0,0 +1,52 @@ +/* -*- 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); + + media::DecodeSupportSet SupportsMimeType( + const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override; + + media::DecodeSupportSet 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..2ca9eaa2d7 --- /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, + const Maybe<uint64_t>& aMediaEngineId, Maybe<TrackingId> aTrackingId) + : ShmemRecycleAllocator(this), + mParent(aParent), + mOptions(aOptions), + mDecodeTaskQueue(aDecodeTaskQueue), + mTrackingId(aTrackingId), + mMediaEngineId(aMediaEngineId), + 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(), + self->mDecoder->GetProcessName(), self->mDecoder->GetCodecName(), + 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()); + // 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..002a93fa09 --- /dev/null +++ b/dom/media/ipc/RemoteDecoderParent.h @@ -0,0 +1,74 @@ +/* -*- 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, + const Maybe<uint64_t>& aMediaEngineId, + Maybe<TrackingId> aTrackingId); + + 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; + const Maybe<TrackingId> mTrackingId; + + // Only be used on Windows when the media engine playback is enabled. + const Maybe<uint64_t> mMediaEngineId; + + 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..282f0d2c92 --- /dev/null +++ b/dom/media/ipc/RemoteImageHolder.cpp @@ -0,0 +1,192 @@ +/* -*- 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/RemoteDecodeUtils.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 gfx::ColorDepth& aColorDepth, const layers::SurfaceDescriptor& aSD, + gfx::YUVColorSpace aYUVColorSpace, gfx::ColorSpace2 aColorPrimaries, + gfx::TransferFunction aTransferFunction, gfx::ColorRange aColorRange) + : mSource(aSource), + mSize(aSize), + mColorDepth(aColorDepth), + mSD(Some(aSD)), + mManager(aManager), + mYUVColorSpace(aYUVColorSpace), + mColorPrimaries(aColorPrimaries), + mTransferFunction(aTransferFunction), + mColorRange(aColorRange) {} + +RemoteImageHolder::RemoteImageHolder(RemoteImageHolder&& aOther) + : mSource(aOther.mSource), + mSize(aOther.mSize), + mColorDepth(aOther.mColorDepth), + mSD(std::move(aOther.mSD)), + mManager(aOther.mManager), + mYUVColorSpace(aOther.mYUVColorSpace), + mColorPrimaries(aOther.mColorPrimaries), + mTransferFunction(aOther.mTransferFunction), + mColorRange(aOther.mColorRange) { + 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.mYStride = descriptor.yStride(); + pData.mCbCrStride = descriptor.cbCrStride(); + // default mYSkip, mCbSkip, mCrSkip because not held in YCbCrDescriptor + pData.mYSkip = pData.mCbSkip = pData.mCrSkip = 0; + pData.mPictureRect = descriptor.display(); + pData.mStereoMode = descriptor.stereoMode(); + pData.mColorDepth = descriptor.colorDepth(); + pData.mYUVColorSpace = descriptor.yUVColorSpace(); + pData.mColorRange = descriptor.colorRange(); + pData.mChromaSubsampling = descriptor.chromaSubsampling(); + 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 = NS_SUCCEEDED(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, mColorDepth, + mYUVColorSpace, mColorPrimaries, + mTransferFunction, mColorRange); + } + 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::MessageWriter* aWriter, ipc::IProtocol* aActor, + RemoteImageHolder&& aParam) { + WriteIPDLParam(aWriter, aActor, aParam.mSource); + WriteIPDLParam(aWriter, aActor, aParam.mSize); + WriteIPDLParam(aWriter, aActor, aParam.mColorDepth); + WriteIPDLParam(aWriter, aActor, aParam.mSD); + WriteIPDLParam(aWriter, aActor, aParam.mYUVColorSpace); + WriteIPDLParam(aWriter, aActor, aParam.mColorPrimaries); + WriteIPDLParam(aWriter, aActor, aParam.mTransferFunction); + WriteIPDLParam(aWriter, aActor, aParam.mColorRange); + // Empty this holder. + aParam.mSD = Nothing(); + aParam.mManager = nullptr; +} + +/* static */ bool ipc::IPDLParamTraits<RemoteImageHolder>::Read( + IPC::MessageReader* aReader, ipc::IProtocol* aActor, + RemoteImageHolder* aResult) { + if (!ReadIPDLParam(aReader, aActor, &aResult->mSource) || + !ReadIPDLParam(aReader, aActor, &aResult->mSize) || + !ReadIPDLParam(aReader, aActor, &aResult->mColorDepth) || + !ReadIPDLParam(aReader, aActor, &aResult->mSD) || + !ReadIPDLParam(aReader, aActor, &aResult->mYUVColorSpace) || + !ReadIPDLParam(aReader, aActor, &aResult->mColorPrimaries) || + !ReadIPDLParam(aReader, aActor, &aResult->mTransferFunction) || + !ReadIPDLParam(aReader, aActor, &aResult->mColorRange)) { + return false; + } + + if (!aResult->IsEmpty()) { + aResult->mManager = RemoteDecoderManagerChild::GetSingleton( + GetRemoteDecodeInFromVideoBridgeSource(aResult->mSource)); + } + return true; +} + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteImageHolder.h b/dom/media/ipc/RemoteImageHolder.h new file mode 100644 index 0000000000..981e24d150 --- /dev/null +++ b/dom/media/ipc/RemoteImageHolder.h @@ -0,0 +1,71 @@ +/* -*- 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 gfx::ColorDepth& aColorDepth, const layers::SurfaceDescriptor& aSD, + gfx::YUVColorSpace aYUVColorSpace, gfx::ColorSpace2 aColorPrimaries, + gfx::TransferFunction aTransferFunction, gfx::ColorRange aColorRange); + 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; + gfx::ColorDepth mColorDepth = gfx::ColorDepth::COLOR_8; + Maybe<layers::SurfaceDescriptor> mSD; + RefPtr<layers::IGPUVideoSurfaceManager> mManager; + gfx::YUVColorSpace mYUVColorSpace = {}; + gfx::ColorSpace2 mColorPrimaries = {}; + gfx::TransferFunction mTransferFunction = {}; + gfx::ColorRange mColorRange = {}; +}; + + template <> + struct ipc::IPDLParamTraits<RemoteImageHolder> { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + RemoteImageHolder&& aParam); + static bool Read(IPC::MessageReader* aReader, 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..6334a3bd6d --- /dev/null +++ b/dom/media/ipc/RemoteMediaData.cpp @@ -0,0 +1,370 @@ +/* -*- 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 "PerformanceRecorder.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 || !std::get<1>(entry)) { + // It's an empty one. + return nullptr; + } + size_t entrySize = std::get<1>(entry); + if (!Check(std::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>() + std::get<0>(entry), + entrySize); + return buffer.forget(); +} + +/*static */ void ipc::IPDLParamTraits<RemoteArrayOfByteBuffer>::Write( + IPC::MessageWriter* aWriter, ipc::IProtocol* aActor, + const RemoteArrayOfByteBuffer& aVar) { + WriteIPDLParam(aWriter, 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(aWriter, aActor, Some(ipc::Shmem(*aVar.mBuffers))); + } else { + WriteIPDLParam(aWriter, aActor, Maybe<ipc::Shmem>()); + } + WriteIPDLParam(aWriter, aActor, aVar.mOffsets); +} + +/* static */ bool ipc::IPDLParamTraits<RemoteArrayOfByteBuffer>::Read( + IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor, + RemoteArrayOfByteBuffer* aVar) { + return ReadIPDLParam(aReader, aActor, &aVar->mIsValid) && + ReadIPDLParam(aReader, aActor, &aVar->mBuffers) && + ReadIPDLParam(aReader, 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()); + int32_t height = 0; + for (auto&& entry : aData) { + dataBuffers.AppendElement(std::move(entry->mBuffer)); + alphaBuffers.AppendElement(std::move(entry->mAlphaBuffer)); + extraDataBuffers.AppendElement(std::move(entry->mExtraData)); + if (auto&& info = entry->mTrackInfo; info && info->GetAsVideoInfo()) { + height = info->GetAsVideoInfo()->mImage.height; + } + mSamples.AppendElement(RemoteMediaRawData{ + MediaDataIPDL(entry->mOffset, entry->mTime, entry->mTimecode, + entry->mDuration, entry->mKeyframe), + entry->mEOS, height, entry->mOriginalPresentationWindow, + entry->mCrypto.IsEncrypted() && entry->mShouldCopyCryptoToRemoteRawData + ? Some(CryptoInfo{ + entry->mCrypto.mCryptoScheme, + entry->mCrypto.mIV, + entry->mCrypto.mKeyId, + entry->mCrypto.mPlainSizes, + entry->mCrypto.mEncryptedSizes, + }) + : Nothing()}); + } + PerformanceRecorder<PlaybackStage> perfRecorder(MediaStage::CopyDemuxedData, + height); + mBuffers = RemoteArrayOfByteBuffer(dataBuffers, aAllocator); + if (!mBuffers.IsValid()) { + return false; + } + mAlphaBuffers = RemoteArrayOfByteBuffer(alphaBuffers, aAllocator); + if (!mAlphaBuffers.IsValid()) { + return false; + } + mExtraDatas = RemoteArrayOfByteBuffer(extraDataBuffers, aAllocator); + if (!mExtraDatas.IsValid()) { + return false; + } + perfRecorder.Record(); + return true; +} + +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]; + PerformanceRecorder<PlaybackStage> perfRecorder(MediaStage::CopyDemuxedData, + sample.mHeight); + 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->mExtraData = mExtraDatas.MediaByteBufferAt(aIndex); + if (sample.mCryptoConfig) { + CryptoSample& cypto = rawData->GetWritableCrypto(); + cypto.mCryptoScheme = sample.mCryptoConfig->mEncryptionScheme(); + cypto.mIV = std::move(sample.mCryptoConfig->mIV()); + cypto.mIVSize = cypto.mIV.Length(); + cypto.mKeyId = std::move(sample.mCryptoConfig->mKeyId()); + cypto.mPlainSizes = std::move(sample.mCryptoConfig->mClearBytes()); + cypto.mEncryptedSizes = std::move(sample.mCryptoConfig->mCipherBytes()); + } + perfRecorder.Record(); + return rawData.forget(); +} + +/*static */ void ipc::IPDLParamTraits<ArrayOfRemoteMediaRawData*>::Write( + IPC::MessageWriter* aWriter, ipc::IProtocol* aActor, + ArrayOfRemoteMediaRawData* aVar) { + WriteIPDLParam(aWriter, aActor, std::move(aVar->mSamples)); + WriteIPDLParam(aWriter, aActor, std::move(aVar->mBuffers)); + WriteIPDLParam(aWriter, aActor, std::move(aVar->mAlphaBuffers)); + WriteIPDLParam(aWriter, aActor, std::move(aVar->mExtraDatas)); +} + +/* static */ bool ipc::IPDLParamTraits<ArrayOfRemoteMediaRawData*>::Read( + IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor, + RefPtr<ArrayOfRemoteMediaRawData>* aVar) { + auto array = MakeRefPtr<ArrayOfRemoteMediaRawData>(); + if (!ReadIPDLParam(aReader, aActor, &array->mSamples) || + !ReadIPDLParam(aReader, aActor, &array->mBuffers) || + !ReadIPDLParam(aReader, aActor, &array->mAlphaBuffers) || + !ReadIPDLParam(aReader, aActor, &array->mExtraDatas)) { + return false; + } + *aVar = std::move(array); + return true; +} + +/* static */ void +ipc::IPDLParamTraits<ArrayOfRemoteMediaRawData::RemoteMediaRawData>::Write( + IPC::MessageWriter* aWriter, ipc::IProtocol* aActor, + const paramType& aVar) { + WriteIPDLParam(aWriter, aActor, aVar.mBase); + WriteIPDLParam(aWriter, aActor, aVar.mEOS); + WriteIPDLParam(aWriter, aActor, aVar.mHeight); + WriteIPDLParam(aWriter, aActor, aVar.mOriginalPresentationWindow); + WriteIPDLParam(aWriter, aActor, aVar.mCryptoConfig); +} + +/* static */ bool +ipc::IPDLParamTraits<ArrayOfRemoteMediaRawData::RemoteMediaRawData>::Read( + IPC::MessageReader* aReader, ipc::IProtocol* aActor, paramType* aVar) { + MediaDataIPDL mBase; + return ReadIPDLParam(aReader, aActor, &aVar->mBase) && + ReadIPDLParam(aReader, aActor, &aVar->mEOS) && + ReadIPDLParam(aReader, aActor, &aVar->mHeight) && + ReadIPDLParam(aReader, aActor, &aVar->mOriginalPresentationWindow) && + ReadIPDLParam(aReader, aActor, &aVar->mCryptoConfig); +}; + +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::MessageWriter* aWriter, ipc::IProtocol* aActor, + ArrayOfRemoteAudioData* aVar) { + WriteIPDLParam(aWriter, aActor, std::move(aVar->mSamples)); + WriteIPDLParam(aWriter, aActor, std::move(aVar->mBuffers)); +} + +/* static */ bool ipc::IPDLParamTraits<ArrayOfRemoteAudioData*>::Read( + IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor, + RefPtr<ArrayOfRemoteAudioData>* aVar) { + auto array = MakeRefPtr<ArrayOfRemoteAudioData>(); + if (!ReadIPDLParam(aReader, aActor, &array->mSamples) || + !ReadIPDLParam(aReader, aActor, &array->mBuffers)) { + return false; + } + *aVar = std::move(array); + return true; +} + +/* static */ void +ipc::IPDLParamTraits<ArrayOfRemoteAudioData::RemoteAudioData>::Write( + IPC::MessageWriter* aWriter, ipc::IProtocol* aActor, + const paramType& aVar) { + WriteIPDLParam(aWriter, aActor, aVar.mBase); + WriteIPDLParam(aWriter, aActor, aVar.mChannels); + WriteIPDLParam(aWriter, aActor, aVar.mRate); + WriteIPDLParam(aWriter, aActor, aVar.mChannelMap); + WriteIPDLParam(aWriter, aActor, aVar.mOriginalTime); + WriteIPDLParam(aWriter, aActor, aVar.mTrimWindow); + WriteIPDLParam(aWriter, aActor, aVar.mFrames); + WriteIPDLParam(aWriter, aActor, aVar.mDataOffset); +} + +/* static */ bool +ipc::IPDLParamTraits<ArrayOfRemoteAudioData::RemoteAudioData>::Read( + IPC::MessageReader* aReader, ipc::IProtocol* aActor, paramType* aVar) { + MediaDataIPDL mBase; + if (!ReadIPDLParam(aReader, aActor, &aVar->mBase) || + !ReadIPDLParam(aReader, aActor, &aVar->mChannels) || + !ReadIPDLParam(aReader, aActor, &aVar->mRate) || + !ReadIPDLParam(aReader, aActor, &aVar->mChannelMap) || + !ReadIPDLParam(aReader, aActor, &aVar->mOriginalTime) || + !ReadIPDLParam(aReader, aActor, &aVar->mTrimWindow) || + !ReadIPDLParam(aReader, aActor, &aVar->mFrames) || + !ReadIPDLParam(aReader, 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..1797a81ea0 --- /dev/null +++ b/dom/media/ipc/RemoteMediaData.h @@ -0,0 +1,394 @@ +/* -*- 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 = std::get<1>(entry); + if (!mBuffers || !entrySize) { + // It's an empty one. + return AlignedBuffer<Type>(); + } + if (!Check(std::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() + std::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 std::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 std::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; + // This will be zero for audio. + int32_t mHeight; + Maybe<media::TimeInterval> mOriginalPresentationWindow; + Maybe<CryptoInfo> mCryptoConfig; + }; + + 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::MessageWriter* aWriter, ipc::IProtocol* aActor, + paramType&& aVar) { + WriteIPDLParam(aWriter, aActor, std::move(aVar.mBase)); + WriteIPDLParam(aWriter, aActor, std::move(aVar.mDisplay)); + WriteIPDLParam(aWriter, aActor, std::move(aVar.mImage)); + aWriter->WriteBytes(&aVar.mFrameID, 4); + } + + static bool Read(IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor, + paramType* aVar) { + if (!ReadIPDLParam(aReader, aActor, &aVar->mBase) || + !ReadIPDLParam(aReader, aActor, &aVar->mDisplay) || + !ReadIPDLParam(aReader, aActor, &aVar->mImage) || + !aReader->ReadBytesInto(&aVar->mFrameID, 4)) { + return false; + } + return true; + } +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteVideoData*> { + typedef ArrayOfRemoteVideoData paramType; + static void Write(IPC::MessageWriter* aWriter, + mozilla::ipc::IProtocol* aActor, paramType* aVar) { + WriteIPDLParam(aWriter, aActor, std::move(aVar->mArray)); + } + + static bool Read(IPC::MessageReader* aReader, ipc::IProtocol* aActor, + RefPtr<paramType>* aVar) { + nsTArray<RemoteVideoData> array; + if (!ReadIPDLParam(aReader, 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::MessageWriter* aWriter, ipc::IProtocol* aActor, + const paramType& aVar); + + static bool Read(IPC::MessageReader* aReader, ipc::IProtocol* aActor, + paramType* aVar); +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteMediaRawData::RemoteMediaRawData> { + typedef ArrayOfRemoteMediaRawData::RemoteMediaRawData paramType; + static void Write(IPC::MessageWriter* aWriter, ipc::IProtocol* aActor, + const paramType& aVar); + + static bool Read(IPC::MessageReader* aReader, ipc::IProtocol* aActor, + paramType* aVar); +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteMediaRawData*> { + typedef ArrayOfRemoteMediaRawData paramType; + static void Write(IPC::MessageWriter* aWriter, ipc::IProtocol* aActor, + paramType* aVar); + + static bool Read(IPC::MessageReader* aReader, ipc::IProtocol* aActor, + RefPtr<paramType>* aVar); +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteAudioData::RemoteAudioData> { + typedef ArrayOfRemoteAudioData::RemoteAudioData paramType; + static void Write(IPC::MessageWriter* aWriter, ipc::IProtocol* aActor, + const paramType& aVar); + + static bool Read(IPC::MessageReader* aReader, ipc::IProtocol* aActor, + paramType* aVar); +}; + +template <> +struct IPDLParamTraits<ArrayOfRemoteAudioData*> { + typedef ArrayOfRemoteAudioData paramType; + static void Write(IPC::MessageWriter* aWriter, ipc::IProtocol* aActor, + paramType* aVar); + + static bool Read(IPC::MessageReader* aReader, 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..6db3c0d940 --- /dev/null +++ b/dom/media/ipc/RemoteMediaDataDecoder.cpp @@ -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/. */ +#include "RemoteMediaDataDecoder.h" + +#include "RemoteDecoderChild.h" +#include "RemoteDecoderManagerChild.h" + +namespace mozilla { + +#ifdef LOG +# undef LOG +#endif // LOG +#define LOG(arg, ...) \ + DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, "::%s: " arg, __func__, \ + ##__VA_ARGS__) + +RemoteMediaDataDecoder::RemoteMediaDataDecoder(RemoteDecoderChild* aChild) + : mChild(aChild) { + LOG("%p is created", this); +} + +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(); + }); + })); + } + LOG("%p is released", this); +} + +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(); + mProcessName = mChild->GetProcessName(); + mCodecName = mChild->GetCodecName(); + mIsHardwareAccelerated = + mChild->IsHardwareAccelerated(mHardwareAcceleratedReason); + mConversion = mChild->NeedsConversion(); + LOG("%p RemoteDecoderChild has been initialized - description: %s, " + "process: %s, codec: %s", + this, mDescription.get(), mProcessName.get(), mCodecName.get()); + 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; +} + +nsCString RemoteMediaDataDecoder::GetProcessName() const { + return mProcessName; +} + +nsCString RemoteMediaDataDecoder::GetCodecName() const { return mCodecName; } + +#undef LOG + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteMediaDataDecoder.h b/dom/media/ipc/RemoteMediaDataDecoder.h new file mode 100644 index 0000000000..4acc5801f7 --- /dev/null +++ b/dom/media/ipc/RemoteMediaDataDecoder.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_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 final + : public MediaDataDecoder, + public DecoderDoctorLifeLogger<RemoteMediaDataDecoder> { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteMediaDataDecoder, final); + + 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; + nsCString GetProcessName() const override; + nsCString GetCodecName() 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; + nsCString mProcessName = "unknown"_ns; + nsCString mCodecName = "unknown"_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..8e4be480f0 --- /dev/null +++ b/dom/media/ipc/RemoteVideoDecoder.cpp @@ -0,0 +1,306 @@ +/* -*- 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, + const Maybe<uint64_t>& aMediaEngineId, + const Maybe<TrackingId>& aTrackingId) { + 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); + MOZ_ALWAYS_TRUE(manager->SendPRemoteDecoderConstructor( + this, decoderInfo, aOptions, aIdentifier, aMediaEngineId, aTrackingId)); + + 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, + const Maybe<uint64_t>& aMediaEngineId, Maybe<TrackingId> aTrackingId) + : RemoteDecoderParent(aParent, aOptions, aManagerThread, aDecodeTaskQueue, + aMediaEngineId, std::move(aTrackingId)), + 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), + mMediaEngineId, mTrackingId, + }; + + 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; + + YUVColorSpace YUVColorSpace = gfx::YUVColorSpace::Default; + ColorSpace2 colorPrimaries = gfx::ColorSpace2::UNKNOWN; + TransferFunction transferFunction = gfx::TransferFunction::BT709; + ColorRange colorRange = gfx::ColorRange::LIMITED; + + if (mKnowsCompositor) { + texture = video->mImage->GetTextureClient(mKnowsCompositor); + + if (!texture) { + texture = ImageClient::CreateTextureClientForImage(video->mImage, + mKnowsCompositor); + } + + if (texture) { + if (!texture->IsAddedToCompositableClient()) { + texture->InitIPDLActor(mKnowsCompositor, mParent->GetContentId()); + 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"); + } + YUVColorSpace = image->GetData()->mYUVColorSpace; + colorPrimaries = image->GetData()->mColorPrimaries; + transferFunction = image->GetData()->mTransferFunction; + colorRange = image->GetData()->mColorRange; + + SurfaceDescriptorBuffer sdBuffer; + nsresult rv = image->BuildSurfaceDescriptorBuffer( + sdBuffer, Image::BuildSdbFlags::Default, [&](uint32_t aBufferSize) { + ShmemBuffer buffer = AllocateBuffer(aBufferSize); + if (buffer.Valid()) { + return MemoryOrShmem(std::move(buffer.Get())); + } + return MemoryOrShmem(); + }); + NS_ENSURE_SUCCESS(rv, rv); + + 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 + : (XRE_IsRDDProcess() + ? VideoBridgeSource::RddProcess + : VideoBridgeSource::MFMediaEngineCDMProcess), + size, video->mImage->GetColorDepth(), sd, YUVColorSpace, + colorPrimaries, transferFunction, colorRange), + 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..160d5cc4ba --- /dev/null +++ b/dom/media/ipc/RemoteVideoDecoder.h @@ -0,0 +1,80 @@ +/* -*- 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::layers { +class BufferRecycleBin; +} // namespace mozilla::layers + +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, + const Maybe<uint64_t>& aMediaEngineId, + const Maybe<TrackingId>& aTrackingId); + + 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, + const Maybe<uint64_t>& aMediaEngineId, Maybe<TrackingId> aTrackingId); + + 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..a9440780ce --- /dev/null +++ b/dom/media/ipc/moz.build @@ -0,0 +1,107 @@ +# -*- 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", + "PRemoteDecoder.ipdl", +] + +PREPROCESSED_IPDL_SOURCES += [ + "PRDD.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", + "RemoteDecodeUtils.h", + "RemoteImageHolder.h", + "RemoteMediaData.h", + "RemoteMediaDataDecoder.h", + "ShmemRecycleAllocator.h", +] + +EXPORTS.mozilla.dom += [ + "MediaIPCUtils.h", + "MFCDMSerializers.h", +] + +SOURCES += [ + "RDDChild.cpp", + "RDDParent.cpp", + "RDDProcessHost.cpp", + "RDDProcessImpl.cpp", + "RDDProcessManager.cpp", + "RemoteAudioDecoder.cpp", + "RemoteDecoderChild.cpp", + "RemoteDecoderManagerChild.cpp", + "RemoteDecoderManagerParent.cpp", + "RemoteDecoderModule.cpp", + "RemoteDecoderParent.cpp", + "RemoteDecodeUtils.cpp", + "RemoteImageHolder.cpp", + "RemoteMediaData.cpp", + "RemoteMediaDataDecoder.cpp", + "RemoteVideoDecoder.cpp", +] + +if CONFIG["MOZ_WMF_MEDIA_ENGINE"]: + IPDL_SOURCES += [ + "PMFMediaEngine.ipdl", + ] + SOURCES += [ + "MFMediaEngineChild.cpp", + "MFMediaEngineParent.cpp", + "MFMediaEngineUtils.cpp", + ] + EXPORTS.mozilla += [ + "MFMediaEngineChild.h", + "MFMediaEngineParent.h", + "MFMediaEngineUtils.h", + ] + LOCAL_INCLUDES += [ + "../platforms/wmf", + ] + +if CONFIG["MOZ_WMF_CDM"]: + IPDL_SOURCES += [ + "PMFCDM.ipdl", + ] + EXPORTS.mozilla += [ + "MFCDMChild.h", + "MFCDMParent.h", + ] + UNIFIED_SOURCES += [ + "MFCDMChild.cpp", + ] + SOURCES += [ + "MFCDMParent.cpp", + ] + LOCAL_INCLUDES += [ + "../eme/mediafoundation", + ] + +# 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" |