// Copyright Epic Games, Inc. All Rights Reserved. #include "MetaHumanVideoLiveLinkSubject.h" #include "MetaHumanPipelineMediaPlayerUENode.h" #include "MetaHumanPipelineMediaPlayerWMFNode.h" #include "MetaHumanPipelineMediaPlayerWMFReaderNode.h" #include "Async/Async.h" TAutoConsoleVariable CVarMetaHumanLiveLinkMediaPlayer { TEXT("mh.LiveLink.MediaPlayer"), "WMF", TEXT("Controls which media player is used. Options are \"WMF\", \"WMFReader\" or \"UE\""), ECVF_Default }; FMetaHumanVideoLiveLinkSubject::FMetaHumanVideoLiveLinkSubject(ILiveLinkClient* InLiveLinkClient, const FGuid& InSourceGuid, const FName& InSubjectName, UMetaHumanVideoLiveLinkSubjectSettings* InSettings) : FMetaHumanVideoBaseLiveLinkSubject(InLiveLinkClient, InSourceGuid, InSubjectName, InSettings) { AnalyticsItems.Add(TEXT("DeviceFormat"), InSettings->MediaSourceCreateParams.VideoTrackFormatName); if (InSettings->MediaSourceCreateParams.VideoURL.StartsWith(UE::MetaHuman::Pipeline::FMediaPlayerNode::BundleURL)) { AnalyticsItems.Add(TEXT("DeviceModel"), TEXT("MediaBundle")); MediaPlayer = MakeShared("MediaPlayerUE"); } else { AnalyticsItems.Add(TEXT("DeviceModel"), InSettings->MediaSourceCreateParams.VideoName); const FString MediaPlayerType = CVarMetaHumanLiveLinkMediaPlayer.GetValueOnAnyThread(); if (MediaPlayerType == "WMF") { MediaPlayer = MakeShared("MediaPlayerWMF"); } else if (MediaPlayerType == "WMFReader") { MediaPlayer = MakeShared("MediaPlayerWMFReader"); } else if (MediaPlayerType == "UE") { MediaPlayer = MakeShared("MediaPlayerUE"); } else { UE_LOG(LogMetaHumanLocalLiveLinkSubject, Warning, TEXT("Unknown media player option: %s"), *MediaPlayerType); MediaPlayer = MakeShared("MediaPlayerWMF"); } } UE_LOG(LogMetaHumanLocalLiveLinkSubject, Display, TEXT("Using media player: %s"), *MediaPlayer->Name); MediaPlayer->StartTimeout = InSettings->MediaSourceCreateParams.StartTimeout; MediaPlayer->FormatWaitTime = InSettings->MediaSourceCreateParams.FormatWaitTime; MediaPlayer->SampleTimeout = InSettings->MediaSourceCreateParams.SampleTimeout; MediaPlayer->Play(InSettings->MediaSourceCreateParams.VideoURL, InSettings->MediaSourceCreateParams.VideoTrack, InSettings->MediaSourceCreateParams.VideoTrackFormat); for (UE::MetaHuman::Pipeline::FPin& Pin : MediaPlayer->Pins) { Pin.Address = MediaPlayer->Name + "." + Pin.Name; } } FMetaHumanVideoLiveLinkSubject::~FMetaHumanVideoLiveLinkSubject() { // The media player must be closed from the game thread. In LLH when we delete a subject in the GUI // we are in the LLH ticker thread, not game thread so have to schedule a delayed close. // However, the media player must be closed before it can be re-opened, so for subject reloading // we need the close to happen immediately. Fortunately in UE we are always in the game // thread and for LLH the subject reload call also happens in the game thread, even if other // calls to this destructor are in non-game thread. So close media player immediately if in game thread // (which is all UE cases and LLH subject reload) but delay close if not in game thread (other LLH // cases besides subject reload). if (IsInGameThread()) { if (!MediaPlayer->Close()) { UE_LOG(LogMetaHumanLocalLiveLinkSubject, Warning, TEXT("Failed to close media player")); } } else { AsyncTask(ENamedThreads::GameThread, [MediaPlayer = this->MediaPlayer]() { if (!MediaPlayer->Close()) { UE_LOG(LogMetaHumanLocalLiveLinkSubject, Warning, TEXT("Failed to close media player")); } }); } MediaPlayer.Reset(); } void FMetaHumanVideoLiveLinkSubject::MediaSamplerMain() { TSharedPtr PipelineData; MediaPlayer->bAbort = GetIsRunningPtr(); PipelineData = MakeShared(); if (!MediaPlayer->Start(PipelineData)) { SetError(PipelineData->GetErrorNodeMessage()); return; } int32 Frame = 0; while (IsRunning()) { PipelineData = MakeShared(); PipelineData->SetFrameNumber(Frame++); if (!MediaPlayer->Process(PipelineData)) { SetError(PipelineData->GetErrorNodeMessage()); break; } UE::MetaHuman::Pipeline::FUEImageDataType Image = PipelineData->MoveData(MediaPlayer->Name + ".UE Image Out"); FVideoSample VideoSample; VideoSample.Width = Image.Width; VideoSample.Height = Image.Height; VideoSample.Data = MoveTemp(Image.Data); VideoSample.Time = PipelineData->GetData(MediaPlayer->Name + TEXT(".UE Image Sample Time Out")); VideoSample.TimeSource = static_cast(PipelineData->GetData(MediaPlayer->Name + TEXT(".UE Image Sample Time Source Out"))); VideoSample.NumDropped = PipelineData->GetData(MediaPlayer->Name + ".Dropped Frame Count Out"); AddVideoSample(MoveTemp(VideoSample)); } PipelineData = MakeShared(); if (!MediaPlayer->End(PipelineData)) { SetError(PipelineData->GetErrorNodeMessage()); return; } }