diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/ipc/MFMediaEngineParent.cpp | 715 |
1 files changed, 715 insertions, 0 deletions
diff --git a/dom/media/ipc/MFMediaEngineParent.cpp b/dom/media/ipc/MFMediaEngineParent.cpp new file mode 100644 index 0000000000..b3d2fbc1c2 --- /dev/null +++ b/dom/media/ipc/MFMediaEngineParent.cpp @@ -0,0 +1,715 @@ +/* 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; + } + if (mMediaEngine) { + mMediaEngine->Shutdown(); + mMediaEngine = nullptr; + } + mMediaEngineEventListener.DisconnectIfExists(); + mRequestSampleListener.DisconnectIfExists(); + if (mDXGIDeviceManager) { + mDXGIDeviceManager = nullptr; + wmf::MFUnlockDXGIDeviceManager(); + } + if (mVirtualVideoWindow) { + DestroyWindow(mVirtualVideoWindow); + } + 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(); + InitializeVirtualVideoWindow(); + + // 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())); + } + if (mVirtualVideoWindow) { + RETURN_VOID_IF_FAILED(creationAttributes->SetUINT64( + MF_MEDIA_ENGINE_OPM_HWND, + reinterpret_cast<uint64_t>(mVirtualVideoWindow))); + } + + 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() && + !StaticPrefs::media_wmf_low_latency_force_disabled(); + 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(); +} + +void MFMediaEngineParent::InitializeVirtualVideoWindow() { + static ATOM sVideoWindowClass = 0; + if (!sVideoWindowClass) { + WNDCLASS wnd{}; + wnd.lpszClassName = L"MFMediaEngine"; + wnd.hInstance = nullptr; + wnd.lpfnWndProc = DefWindowProc; + sVideoWindowClass = RegisterClass(&wnd); + } + if (!sVideoWindowClass) { + HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); + LOG("Failed to register video window class: %lX", hr); + return; + } + mVirtualVideoWindow = + CreateWindowEx(WS_EX_NOPARENTNOTIFY | WS_EX_LAYERED | WS_EX_TRANSPARENT | + WS_EX_NOREDIRECTIONBITMAP, + reinterpret_cast<wchar_t*>(sVideoWindowClass), L"", + WS_POPUP | WS_DISABLED | WS_CLIPSIBLINGS, 0, 0, 1, 1, + nullptr, nullptr, nullptr, nullptr); + if (!mVirtualVideoWindow) { + HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); + LOG("Failed to create virtual window: %lX", hr); + return; + } + LOG("Initialized virtual window"); +} + +#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(); + 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: + 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()); + + 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()); +} + +void MFMediaEngineParent::EnsureDcompSurfaceHandle() { + AssertOnManagerThread(); + MOZ_ASSERT(mMediaEngine); + MOZ_ASSERT(mMediaEngine->HasVideo()); + + ComPtr<IMFMediaEngineEx> mediaEngineEx; + RETURN_VOID_IF_FAILED(mMediaEngine.As(&mediaEngineEx)); + DWORD width, height; + RETURN_VOID_IF_FAILED(mMediaEngine->GetNativeVideoSize(&width, &height)); + if (width != mDisplayWidth || height != mDisplayHeight) { + // 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. If + // the size isn't changed, then we don't need to recreate the surface. + mDisplayWidth = width; + mDisplayHeight = height; + RECT rect = {0, 0, (LONG)mDisplayWidth, (LONG)mDisplayHeight}; + RETURN_VOID_IF_FAILED(mediaEngineEx->UpdateVideoStream( + nullptr /* pSrc */, &rect, nullptr /* pBorderClr */)); + LOG("Updated video size for engine=[%lux%lu]", mDisplayWidth, + mDisplayHeight); + ENGINE_MARKER_TEXT( + "MFMediaEngineParent,UpdateVideoSize", + nsPrintfCString("%lux%lu", mDisplayWidth, mDisplayHeight)); + } + + if (!mIsEnableDcompMode) { + RETURN_VOID_IF_FAILED(mediaEngineEx->EnableWindowlessSwapchainMode(true)); + LOG("Enabled dcomp swap chain mode"); + mIsEnableDcompMode = true; + ENGINE_MARKER("MFMediaEngineParent,EnabledSwapChain"); + } + + HANDLE surfaceHandle = INVALID_HANDLE_VALUE; + RETURN_VOID_IF_FAILED(mediaEngineEx->GetVideoSwapchainHandle(&surfaceHandle)); + if (surfaceHandle && surfaceHandle != INVALID_HANDLE_VALUE) { + LOG("EnsureDcompSurfaceHandle, handle=%p, size=[%lux%lu]", surfaceHandle, + width, height); + mMediaSource->SetDCompSurfaceHandle(surfaceHandle, + gfx::IntSize{width, height}); + } else { + NS_WARNING("SurfaceHandle is not ready yet"); + } +} + +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 |