/* 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 "MFProtectedPathReadinessMonitor.h" #include "MFMediaEngineUtils.h" #include "mozilla/EMEUtils.h" #include "mozilla/EnumeratedRange.h" #include "nsString.h" #include "nsThreadUtils.h" namespace mozilla { #define LOG(msg, ...) \ EME_LOG("MFProtectedReadiness[{}] " msg, fmt::ptr(this), ##__VA_ARGS__) namespace { bool IsRecoverableActivationError(HRESULT aError) { // These two errors are reported when Media Foundation fails to build the // protected topology because the protected pipeline was not fully in place // yet (for example output protection had not settled), not because the system // lacks a capability. They typically clear once the pipeline is re-driven // after readiness settles, so they are worth a bounded retry; capability // failures such as MF_E_HARDWARE_DRM_UNSUPPORTED are terminal and are not // treated as recoverable here. return aError == MF_E_TOPO_UNSUPPORTED || aError == MF_E_INCOMPATIBLE_SAMPLE_PROTECTION; } using Condition = MFProtectedPathReadinessMonitor::Condition; // The conditions that must be ready before the protected topology can build, // shared by the activation gate (IsReadyToActivate) and the settled check // (AllActivationConditionsSettledLocked). The per-stream decryptor is // deliberately excluded: it is established during sample handling after the // topology builds, not before activation. constexpr Condition kGatingConditions[] = {Condition::HwDrmAdapter, Condition::ContentProtectionManager, Condition::Hdcp}; } // namespace MFProtectedPathReadinessMonitor::MFProtectedPathReadinessMonitor() : mLock("MFProtectedPathReadinessMonitor"), mRecoveriesUsed(0) { for (auto& state : mState) { state = State::Pending; } for (auto& error : mLastError) { error = S_OK; } } void MFProtectedPathReadinessMonitor::MarkReady(Condition aWhich) { MutexAutoLock lock(mLock); mState[aWhich] = State::Ready; LOG("MarkReady({})", EnumValueToString(aWhich)); MaybeRunSettledCallbackLocked(); } void MFProtectedPathReadinessMonitor::MarkAllReady() { MutexAutoLock lock(mLock); for (Condition condition : MakeInclusiveEnumeratedRange(sHighestCondition)) { mState[condition] = State::Ready; } LOG("MarkAllReady (protected frame produced)"); MaybeRunSettledCallbackLocked(); } void MFProtectedPathReadinessMonitor::MarkFailed(Condition aWhich, HRESULT aError) { MutexAutoLock lock(mLock); mState[aWhich] = State::Failed; mLastError[aWhich] = aError; LOG("MarkFailed({}, {:#x})", EnumValueToString(aWhich), static_cast(aError)); MaybeRunSettledCallbackLocked(); } void MFProtectedPathReadinessMonitor::ResetEngineConditions() { MutexAutoLock lock(mLock); // Only the engine-side conditions are cleared: a recreated engine // re-establishes the adapter binding and the content protection manager, // while the CDM-side conditions (HDCP, decryptor) persist with the surviving // CDM actor and so must not be reset here. mState[Condition::HwDrmAdapter] = State::Pending; mState[Condition::ContentProtectionManager] = State::Pending; LOG("ResetEngineConditions"); } bool MFProtectedPathReadinessMonitor::IsReady(Condition aWhich) const { MutexAutoLock lock(mLock); return mState[aWhich] == State::Ready; } bool MFProtectedPathReadinessMonitor::IsReadyToActivate() const { MutexAutoLock lock(mLock); for (Condition condition : kGatingConditions) { if (mState[condition] != State::Ready) { return false; } } return true; } MFProtectedPathReadinessMonitor::Reaction MFProtectedPathReadinessMonitor::OnActivationError(HRESULT aError, uint32_t aMaxRecoveries) { if (!IsRecoverableActivationError(aError)) { LOG("OnActivationError({:#x}): not recoverable, terminal", static_cast(aError)); return Reaction::Terminal; } MutexAutoLock lock(mLock); if (mRecoveriesUsed >= aMaxRecoveries) { LOG("OnActivationError({:#x}): budget exhausted ({}/{}), terminal", static_cast(aError), mRecoveriesUsed, aMaxRecoveries); return Reaction::Terminal; } ++mRecoveriesUsed; LOG("OnActivationError({:#x}): recover ({}/{})", static_cast(aError), mRecoveriesUsed, aMaxRecoveries); return Reaction::Recover; } void MFProtectedPathReadinessMonitor::ResetRecoveryBudget() { MutexAutoLock lock(mLock); mRecoveriesUsed = 0; LOG("ResetRecoveryBudget"); } uint32_t MFProtectedPathReadinessMonitor::RecoveriesUsed() const { MutexAutoLock lock(mLock); return mRecoveriesUsed; } bool MFProtectedPathReadinessMonitor::AllActivationConditionsSettled() const { MutexAutoLock lock(mLock); return AllActivationConditionsSettledLocked(); } bool MFProtectedPathReadinessMonitor::AllActivationConditionsSettledLocked() const { for (Condition condition : kGatingConditions) { if (mState[condition] == State::Pending) { return false; } } return true; } void MFProtectedPathReadinessMonitor::RunWhenActivationSettled( std::function aCallback, nsISerialEventTarget* aTarget) { MutexAutoLock lock(mLock); mPendingSettledCallback = std::move(aCallback); mPendingSettledTarget = aTarget; LOG("RunWhenActivationSettled registered"); MaybeRunSettledCallbackLocked(); } void MFProtectedPathReadinessMonitor::MaybeRunSettledCallbackLocked() { if (!mPendingSettledCallback || !AllActivationConditionsSettledLocked()) { return; } std::function callback = std::move(mPendingSettledCallback); RefPtr target = std::move(mPendingSettledTarget); mPendingSettledCallback = nullptr; mPendingSettledTarget = nullptr; LOG("activation gate released"); // Dispatch can fail when the target thread is already shutting down (e.g. // during teardown), which is not a programming error, so warn rather than // assert. [[maybe_unused]] nsresult rv = target->Dispatch(NS_NewRunnableFunction( "MFProtectedPathReadinessMonitor::SettledCallback", std::move(callback))); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch readiness settled callback"); } nsCString MFProtectedPathReadinessMonitor::DescribeReadiness() const { MutexAutoLock lock(mLock); nsAutoCString out; for (Condition condition : MakeInclusiveEnumeratedRange(sHighestCondition)) { if (!out.IsEmpty()) { out.Append(' '); } out.AppendFmt("{}={}", EnumValueToString(condition), EnumValueToString(mState[condition])); if (mState[condition] == State::Failed) { out.AppendFmt("({:#x})", static_cast(mLastError[condition])); } } return out; } #undef LOG } // namespace mozilla