737 lines
26 KiB
C++
737 lines
26 KiB
C++
/* 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;
|
|
}
|
|
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));
|
|
if (!mDXGIDeviceManager) {
|
|
return;
|
|
}
|
|
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) {
|
|
if (aError == MF_MEDIA_ENGINE_ERR_NOERROR) {
|
|
return;
|
|
}
|
|
#ifdef MOZ_WMF_CDM
|
|
// A special error requires to reset the hareware context, not a real error.
|
|
if (aResult == DRM_E_TEE_INVALID_HWDRM_STATE) {
|
|
LOG("Notify error 'DRM_E_TEE_INVALID_HWDRM_STATE', hr=%lx", aResult);
|
|
ENGINE_MARKER(
|
|
"MFMediaEngineParent,Received 'DRM_E_TEE_INVALID_HWDRM_STATE'");
|
|
auto* proxy = mContentProtectionManager
|
|
? mContentProtectionManager->GetCDMProxy()
|
|
: nullptr;
|
|
if (proxy) {
|
|
proxy->OnHardwareContextReset();
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
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,
|
|
nsPrintfCString("Decoder error (hr=%lx)", aResult),
|
|
Some(static_cast<int32_t>(aResult)));
|
|
Unused << SendNotifyError(error);
|
|
return;
|
|
}
|
|
case MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED: {
|
|
MediaResult error(
|
|
NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR,
|
|
nsPrintfCString("Source not supported (hr=%lx)", aResult),
|
|
Some(static_cast<int32_t>(aResult)));
|
|
Unused << SendNotifyError(error);
|
|
return;
|
|
}
|
|
case MF_MEDIA_ENGINE_ERR_ENCRYPTED: {
|
|
MediaResult error(NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
|
nsPrintfCString("Encrypted error (hr=%lx)", aResult),
|
|
Some(static_cast<int32_t>(aResult)));
|
|
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);
|
|
}
|
|
RETURN_PARAM_IF_FAILED(
|
|
SetMediaInfo(aInfo.mediaInfo(), aInfo.encryptedCustomIdent()), IPC_OK());
|
|
aResolver(mMediaEngineId);
|
|
return IPC_OK();
|
|
}
|
|
|
|
HRESULT MFMediaEngineParent::SetMediaInfo(const MediaInfoIPDL& aInfo,
|
|
bool aIsEncrytpedCustomInit) {
|
|
AssertOnManagerThread();
|
|
MOZ_ASSERT(mIsCreatedMediaEngine, "Hasn't created media engine?");
|
|
MOZ_ASSERT(!mMediaSource);
|
|
|
|
LOG("SetMediaInfo");
|
|
|
|
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, aIsEncrytpedCustomInit)),
|
|
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, aIsEncrytpedCustomInit=%d, 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",
|
|
aIsEncrytpedCustomInit, isEncryted);
|
|
LOG("%s", message.get());
|
|
|
|
if (aInfo.videoInfo()) {
|
|
ComPtr<IMFMediaEngineEx> mediaEngineEx;
|
|
RETURN_IF_FAILED(mMediaEngine.As(&mediaEngineEx));
|
|
RETURN_IF_FAILED(mediaEngineEx->EnableWindowlessSwapchainMode(true));
|
|
LOG("Enabled dcomp swap chain mode");
|
|
ENGINE_MARKER("MFMediaEngineParent,EnabledSwapChain");
|
|
if (isEncryted) {
|
|
// Microsoft recommends to disable low latency with DRM.
|
|
RETURN_IF_FAILED(mediaEngineEx->SetRealTimeMode(false));
|
|
LOG("Turned off the real time mode for encrypted playback");
|
|
}
|
|
}
|
|
|
|
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 S_OK;
|
|
}
|
|
|
|
if (isEncryted && mContentProtectionManager) {
|
|
auto* proxy = mContentProtectionManager->GetCDMProxy();
|
|
MOZ_ASSERT(proxy);
|
|
mMediaSource->SetCDMProxy(proxy);
|
|
}
|
|
#endif
|
|
|
|
SetMediaSourceOnEngine();
|
|
return S_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
|