417 lines
15 KiB
C++
417 lines
15 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 "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(
|
|
const MediaInfo& aInfo, const ExternalPlaybackEngine::InitFlagSet& aFlags) {
|
|
if (!mManagerThread) {
|
|
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
|
|
__func__);
|
|
}
|
|
|
|
CLOG("Init, hasAudio=%d, hasVideo=%d, encrypted=%d", aInfo.HasAudio(),
|
|
aInfo.HasVideo(), aInfo.IsEncrypted());
|
|
|
|
MOZ_ASSERT(mMediaEngineId == 0);
|
|
RefPtr<MFMediaEngineChild> self = this;
|
|
RemoteDecoderManagerChild::LaunchUtilityProcessIfNeeded(
|
|
RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM)
|
|
->Then(
|
|
mManagerThread, __func__,
|
|
[self, this, flag = aFlags, info = aInfo](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);
|
|
|
|
MediaInfoIPDL mediaInfo(
|
|
info.HasAudio() ? Some(info.mAudio) : Nothing(),
|
|
info.HasVideo() ? Some(info.mVideo) : Nothing());
|
|
|
|
MediaEngineInfoIPDL initInfo(
|
|
mediaInfo,
|
|
flag.contains(ExternalPlaybackEngine::InitFlag::ShouldPreload),
|
|
flag.contains(
|
|
ExternalPlaybackEngine::InitFlag::EncryptedCustomIdent));
|
|
SendInitMediaEngine(initInfo)
|
|
->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 || mShutdown) {
|
|
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 (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
if (mOwner) {
|
|
mOwner->UpdateCurrentTime(aCurrentTimeInSecond);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult MFMediaEngineChild::RecvNotifyEvent(
|
|
MFMediaEngineEvent aEvent) {
|
|
AssertOnManagerThread();
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
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();
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
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) {
|
|
AssertOnManagerThread();
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
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(
|
|
const MediaInfo& aInfo, const InitFlagSet& aFlags) {
|
|
WLOG("Init");
|
|
return mEngine->Init(aInfo, aFlags);
|
|
}
|
|
|
|
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); }));
|
|
}
|
|
|
|
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, proxy = RefPtr{aProxy}, 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) {
|
|
AssertOnManagerThread();
|
|
WLOG("Video resizing, new size [%u,%u]", aWidth, aHeight);
|
|
mOwner->NotifyResizing(aWidth, aHeight);
|
|
}
|
|
|
|
#undef CLOG
|
|
#undef WLOG
|
|
#undef WLOGV
|
|
|
|
} // namespace mozilla
|