717 lines
22 KiB
C++
717 lines
22 KiB
C++
// ------------------------------------------------
|
|
// Copyright Joe Marshall 2024- All Rights Reserved
|
|
// ------------------------------------------------
|
|
//
|
|
// The main IMediaPlayer implementation. This is a
|
|
// shim which provides Unreal interfaces, and then
|
|
// defers everything vulkan related to the main
|
|
// AndroidVulkanVideoImpl class.
|
|
// ------------------------------------------------
|
|
|
|
#include "AndroidVulkanMediaPlayer.h"
|
|
#include "AndroidVulkanTextureSample.h"
|
|
|
|
#include "IVulkanImpl.h"
|
|
|
|
#include "VideoMediaSampleHolder.h"
|
|
|
|
#include "UnrealArchiveFileSource.h"
|
|
#include "UnrealAudioOut.h"
|
|
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformFilemanager.h"
|
|
#include "IPlatformFilePak.h"
|
|
#include "Misc/ConfigCacheIni.h" // for GConfig access
|
|
|
|
#include "UnrealLogging.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "FAndroidVulkanVideoModule"
|
|
|
|
#include <dlfcn.h>
|
|
|
|
static void *implDLL = NULL;
|
|
|
|
// logging object passed to impl
|
|
static UnrealLogger logger;
|
|
|
|
typedef IVulkanImpl *(*CreateImplType)();
|
|
typedef void (*DestroyImplType)(IVulkanImpl *);
|
|
static CreateImplType createImpl = NULL;
|
|
static DestroyImplType destroyImpl = NULL;
|
|
|
|
FAndroidVulkanMediaPlayer::FAndroidVulkanMediaPlayer(IMediaEventSink &InEventSink)
|
|
: SampleQueue(MakeShared<FVideoMediaSampleHolder, ESPMode::ThreadSafe>()),
|
|
EventSink(InEventSink)
|
|
{
|
|
if (implDLL == NULL)
|
|
{
|
|
implDLL = dlopen("libVkLayer_OverrideLib.so", RTLD_NOW | RTLD_LOCAL);
|
|
createImpl = (CreateImplType)(dlsym(implDLL, "createImpl"));
|
|
destroyImpl = (DestroyImplType)(dlsym(implDLL, "destroyImpl"));
|
|
}
|
|
impl = createImpl();
|
|
impl->setDataCallback(this);
|
|
impl->setLogger(&logger);
|
|
impl->setLooping(Looping);
|
|
// get log format bitmask from defaultEngine.ini
|
|
int32 LogVisibility;
|
|
if (GConfig->GetInt(TEXT("DirectVideo"), TEXT("LogBitmask"), LogVisibility, GEngineIni))
|
|
{
|
|
logger.SetLogVisibilityBitmask((int64)LogVisibility);
|
|
}
|
|
else
|
|
{
|
|
logger.SetLogVisibilityBitmask(ILogger::LogTypes::ALL_LOGS_BITMASK);
|
|
}
|
|
// get output format from defaultEngine.ini
|
|
FString OutFormat;
|
|
EMediaTextureSampleFormat SampleFormat = EMediaTextureSampleFormat::CharBGR10A2;
|
|
if (GConfig->GetString(TEXT("DirectVideo"), TEXT("OutputFormat"), OutFormat, GEngineIni))
|
|
{
|
|
if (OutFormat.Equals(TEXT("CharBGR10A2"), ESearchCase::IgnoreCase))
|
|
{
|
|
SampleFormat = EMediaTextureSampleFormat::CharBGR10A2;
|
|
}
|
|
else if (OutFormat.Equals(TEXT("CharBGRA"), ESearchCase::IgnoreCase))
|
|
{
|
|
SampleFormat = EMediaTextureSampleFormat::CharBGRA;
|
|
}
|
|
|
|
else if (OutFormat.Equals(TEXT("CharRGBA"), ESearchCase::IgnoreCase))
|
|
{
|
|
SampleFormat = EMediaTextureSampleFormat::CharRGBA;
|
|
}
|
|
else if (OutFormat.Equals(TEXT("RGBA16"), ESearchCase::IgnoreCase))
|
|
{
|
|
SampleFormat = EMediaTextureSampleFormat::RGBA16;
|
|
}
|
|
|
|
else if (OutFormat.Equals(TEXT("FloatRGB"), ESearchCase::IgnoreCase))
|
|
{
|
|
SampleFormat = EMediaTextureSampleFormat::FloatRGB;
|
|
}
|
|
else if (OutFormat.Equals(TEXT("FloatRGBA"), ESearchCase::IgnoreCase))
|
|
{
|
|
SampleFormat = EMediaTextureSampleFormat::FloatRGBA;
|
|
}
|
|
else
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, Error, "Bad sample format in defaultEngine.ini");
|
|
}
|
|
}
|
|
AndroidVulkanTextureSample::SetVideoFormat(SampleFormat);
|
|
|
|
AudioOut = new UnrealAudioOut(NULL);
|
|
impl->setAudioOut(AudioOut);
|
|
PlayState = EMediaState::Closed;
|
|
CurInfo.Empty();
|
|
DelegateEnterBackground = FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddRaw(
|
|
this, &FAndroidVulkanMediaPlayer::OnEnterBackground);
|
|
DelegateEnterForeground = FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddRaw(
|
|
this, &FAndroidVulkanMediaPlayer::OnEnterForeground);
|
|
SeekIndex = 0;
|
|
SentBlankFrame = false;
|
|
Seeking = false;
|
|
}
|
|
|
|
FAndroidVulkanMediaPlayer::~FAndroidVulkanMediaPlayer()
|
|
{
|
|
Close();
|
|
SentBlankFrame = true;
|
|
if (DelegateEnterBackground.IsValid())
|
|
{
|
|
FCoreDelegates::ApplicationWillEnterBackgroundDelegate.Remove(DelegateEnterBackground);
|
|
}
|
|
if (DelegateEnterForeground.IsValid())
|
|
{
|
|
FCoreDelegates::ApplicationHasEnteredForegroundDelegate.Remove(DelegateEnterForeground);
|
|
}
|
|
destroyImpl(impl);
|
|
impl = NULL;
|
|
delete AudioOut;
|
|
AudioOut = NULL;
|
|
}
|
|
|
|
// IMediaPlayer
|
|
// --------------------------------
|
|
void FAndroidVulkanMediaPlayer::Close()
|
|
{
|
|
SentBlankFrame = false;
|
|
|
|
if (PlayState != EMediaState::Closed)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Media player close called");
|
|
SampleQueue->FlushSamples();
|
|
VideoSamplePool.ReleaseEverything();
|
|
SeekIndex = 0;
|
|
Seeking = false;
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::TracksChanged);
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::MediaClosed);
|
|
PlayState = EMediaState::Closed;
|
|
// close the impl last because it will
|
|
// kill all the texture images that we release in the pool above
|
|
impl->close();
|
|
}
|
|
}
|
|
FString FAndroidVulkanMediaPlayer::GetInfo() const
|
|
{
|
|
return CurInfo;
|
|
}
|
|
FGuid FAndroidVulkanMediaPlayer::GetPlayerPluginGUID() const
|
|
{
|
|
static FGuid OurGUID(0x9bf2d7c6, 0xb2b84d26, 0xb6ae5a3a, 0xc9883569);
|
|
return OurGUID;
|
|
}
|
|
FString FAndroidVulkanMediaPlayer::GetStats() const
|
|
{
|
|
return TEXT("Not implemented");
|
|
}
|
|
FString FAndroidVulkanMediaPlayer::GetUrl() const
|
|
{
|
|
return VideoURL;
|
|
}
|
|
bool FAndroidVulkanMediaPlayer::Open(const FString &Url, const IMediaOptions *Options)
|
|
{
|
|
Close();
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Try open Url {0}", Url);
|
|
SentBlankFrame = false;
|
|
bool started = false;
|
|
FString fullPath = Url;
|
|
if (fullPath.StartsWith("file://"))
|
|
{
|
|
fullPath = fullPath.RightChop(7);
|
|
}
|
|
if (fullPath.Contains("://"))
|
|
{
|
|
// a (non-file url)
|
|
started = impl->startVideoURL(TCHAR_TO_UTF8(*fullPath), false);
|
|
}
|
|
else
|
|
{
|
|
// a file path - check if it is local or not
|
|
if (fullPath.StartsWith("./"))
|
|
{
|
|
fullPath = FPaths::ProjectContentDir() + fullPath.RightChop(2);
|
|
}
|
|
FPaths::NormalizeFilename(fullPath);
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Start video {0}", fullPath);
|
|
IAndroidPlatformFile &PlatformFile = IAndroidPlatformFile::GetPlatformPhysical();
|
|
if (PlatformFile.FileExists(*fullPath))
|
|
{
|
|
int64 FileOffset = PlatformFile.FileStartOffset(*fullPath);
|
|
int64 FileSize = PlatformFile.FileSize(*fullPath);
|
|
FString FileRootPath = PlatformFile.FileRootPath(*fullPath);
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "File exists: {0} {1} {2} {3}", FileRootPath,
|
|
FileOffset, FileSize, fullPath);
|
|
started =
|
|
impl->startVideoFile(TCHAR_TO_UTF8(*FileRootPath), FileOffset, FileSize, false);
|
|
}
|
|
else
|
|
{
|
|
// check if the file exists in an archive / encrypted etc.
|
|
FPakPlatformFile *PakPlatformFile =
|
|
(FPakPlatformFile *)(FPlatformFileManager::Get().FindPlatformFile(
|
|
FPakPlatformFile::GetTypeName()));
|
|
|
|
TRefCountPtr<FPakFile> PakFile;
|
|
FPakEntry FileEntry;
|
|
if (PakPlatformFile != nullptr &&
|
|
PakPlatformFile->FindFileInPakFiles(*fullPath, &PakFile, &FileEntry))
|
|
{
|
|
// we have the file in a pak file
|
|
// use a custom media data source to read it
|
|
// n.b. if pak file is uncompressed and the file isn't encrypted we could skip this
|
|
// step but this should work whatever
|
|
TSharedRef<FArchive, ESPMode::ThreadSafe> Archive =
|
|
MakeShareable(IFileManager::Get().CreateFileReader(*fullPath));
|
|
UnrealArchiveFileSource *fs = new UnrealArchiveFileSource(Archive);
|
|
started = impl->startVideoCustomSource(fs, false);
|
|
}
|
|
}
|
|
}
|
|
if (started)
|
|
{
|
|
PlayState = EMediaState::Stopped;
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::TracksChanged);
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::MediaOpened);
|
|
UE_LOGFMT(LogDirectVideo, Verbose, "Opening {0}", *Url);
|
|
SeekIndex = 0;
|
|
Seeking = false;
|
|
}
|
|
else
|
|
{
|
|
// couldn't open
|
|
UE_LOGFMT(LogDirectVideo, Error, "Can't find video file {0}", *Url);
|
|
}
|
|
return started;
|
|
}
|
|
bool FAndroidVulkanMediaPlayer::Open(const TSharedRef<FArchive, ESPMode::ThreadSafe> &Archive,
|
|
const FString &OriginalUrl, const IMediaOptions *Options)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, Error, "Opening archive {0} not supported", *OriginalUrl);
|
|
return false;
|
|
}
|
|
void FAndroidVulkanMediaPlayer::SetGuid(const FGuid &Guid)
|
|
{
|
|
PlayerGUID = Guid;
|
|
}
|
|
|
|
void FAndroidVulkanMediaPlayer::TickFetch(FTimespan DeltaTime, FTimespan Timecode)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "TickFetch");
|
|
}
|
|
|
|
void FAndroidVulkanMediaPlayer::TickInput(FTimespan DeltaTime, FTimespan Timecode)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "TickInput start");
|
|
|
|
/* int64_t presTimeNs = impl->getTimeNS();
|
|
SampleQueue->SetLastTimeStamp(
|
|
FMediaTimeStamp(DeltaTime,
|
|
#if UE_VERSION_OLDER_THAN(5, 3, 0)
|
|
static_cast<int64>(SeekIndex) << 32));
|
|
#else
|
|
FMediaTimeStamp::MakeSequenceIndex(SeekIndex, 0)));
|
|
#endif
|
|
*/
|
|
HasVideoThisFrame = false;
|
|
VideoSamplePool.Tick();
|
|
if (!SentBlankFrame && impl->numVideoTracks() == 0)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "blank frame");
|
|
SentBlankFrame = true;
|
|
auto textureSample = VideoSamplePool.AcquireShared();
|
|
textureSample->InitNoVideo();
|
|
SampleQueue->AddVideo(textureSample);
|
|
}
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "TickInput dt:{0} tc {1} num {2}", DeltaTime.GetTicks(),
|
|
Timecode.GetTicks(), SampleQueue->NumVideoSamples());
|
|
}
|
|
|
|
bool FAndroidVulkanMediaPlayer::GetPlayerFeatureFlag(EFeatureFlag flag) const
|
|
{
|
|
|
|
switch (flag)
|
|
{
|
|
case EFeatureFlag::PlayerUsesInternalFlushOnSeek:
|
|
return true;
|
|
case EFeatureFlag::AlwaysPullNewestVideoFrame:
|
|
return true;
|
|
case EFeatureFlag::UsePlaybackTimingV2:
|
|
return true;
|
|
case EFeatureFlag::IsTrackSwitchSeamless:
|
|
return true;
|
|
case EFeatureFlag::UseRealtimeWithVideoOnly:
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Feature flag query");
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// IMediaControl
|
|
// --------------------------------
|
|
|
|
bool FAndroidVulkanMediaPlayer::CanControl(EMediaControl Control) const
|
|
{
|
|
switch (Control)
|
|
{
|
|
case EMediaControl::Pause:
|
|
return PlayState == EMediaState::Playing;
|
|
case EMediaControl::Resume:
|
|
return PlayState == EMediaState::Stopped;
|
|
case EMediaControl::Seek:
|
|
return PlayState != EMediaState::Closed;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FTimespan FAndroidVulkanMediaPlayer::GetDuration() const
|
|
{
|
|
int64_t timeNS = impl->getDurationNS();
|
|
if (timeNS >= 0 && PlayState != EMediaState::Closed)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get Duration {0}", int64(timeNS));
|
|
return FTimespan(timeNS / 100LL);
|
|
}
|
|
else
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get Duration empty");
|
|
return FTimespan::MaxValue();
|
|
}
|
|
}
|
|
|
|
float FAndroidVulkanMediaPlayer::GetRate() const
|
|
{
|
|
if (PlayState == EMediaState::Playing)
|
|
{
|
|
return impl->getRate();
|
|
}
|
|
else
|
|
{
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
EMediaState FAndroidVulkanMediaPlayer::GetState() const
|
|
{
|
|
return PlayState;
|
|
}
|
|
|
|
EMediaStatus FAndroidVulkanMediaPlayer::GetStatus() const
|
|
{
|
|
// not supported yet
|
|
return EMediaStatus::None;
|
|
}
|
|
|
|
TRangeSet<float> FAndroidVulkanMediaPlayer::GetSupportedRates(EMediaRateThinning Thinning) const
|
|
{
|
|
TRangeSet<float> Retval;
|
|
if (impl->numAudioTracks() > 0)
|
|
{
|
|
Retval.Add(TRange<float>(0.0f));
|
|
Retval.Add(TRange<float>(1.0f));
|
|
}
|
|
else
|
|
{
|
|
Retval.Add(TRange<float>(TRange<float>::BoundsType::Inclusive(0.0f),
|
|
TRange<float>::BoundsType::Inclusive(10.0f)));
|
|
}
|
|
return Retval;
|
|
}
|
|
|
|
FTimespan FAndroidVulkanMediaPlayer::GetTime() const
|
|
{
|
|
int64_t timeNS = impl->getTimeNS();
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get Time {0}", int64(timeNS));
|
|
if (timeNS >= 0)
|
|
{
|
|
return FTimespan(timeNS / 100LL);
|
|
}
|
|
return FTimespan::Zero();
|
|
}
|
|
|
|
bool FAndroidVulkanMediaPlayer::IsLooping() const
|
|
{
|
|
return Looping;
|
|
}
|
|
|
|
bool FAndroidVulkanMediaPlayer::Seek(const FTimespan &Time)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, Verbose, "Seek index:{0}", SeekIndex);
|
|
Seeking = true;
|
|
int64_t ticks = Time.GetTicks();
|
|
int64_t nanoseconds = ticks * 100LL;
|
|
impl->seek(nanoseconds);
|
|
return true;
|
|
}
|
|
|
|
bool FAndroidVulkanMediaPlayer::SetLooping(bool Loop)
|
|
{
|
|
Looping = Loop;
|
|
impl->setLooping(Looping);
|
|
return true;
|
|
}
|
|
bool FAndroidVulkanMediaPlayer::SetRate(float Rate)
|
|
{
|
|
bool retval = false;
|
|
int iInitialState = (int)PlayState;
|
|
switch (PlayState)
|
|
{
|
|
case EMediaState::Playing:
|
|
if (Rate == 0.0)
|
|
{
|
|
impl->setPlaying(false);
|
|
PlayState = EMediaState::Stopped;
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended);
|
|
retval = true;
|
|
}
|
|
else
|
|
{
|
|
return impl->setRate(Rate);
|
|
retval = true;
|
|
}
|
|
break;
|
|
case EMediaState::Stopped:
|
|
if (Rate == 0.0)
|
|
{
|
|
retval = true;
|
|
}
|
|
else if (Rate > 0.0)
|
|
{
|
|
impl->setPlaying(true);
|
|
PlayState = EMediaState::Playing;
|
|
retval = impl->setRate(Rate);
|
|
if (retval)
|
|
{
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackResumed);
|
|
}
|
|
}
|
|
break;
|
|
};
|
|
int iFinalState = (int)PlayState;
|
|
UE_LOGFMT(LogDirectVideo, Verbose, "Set rate {0} {1} state:{2}->{3}", Rate, retval,
|
|
iInitialState, iFinalState);
|
|
// return false;
|
|
return retval;
|
|
}
|
|
|
|
bool FAndroidVulkanMediaPlayer::SetNativeVolume(float Volume)
|
|
{
|
|
return AudioOut->setVolume(Volume);
|
|
}
|
|
|
|
// IMediaTracks
|
|
// --------------------------------
|
|
bool FAndroidVulkanMediaPlayer::GetAudioTrackFormat(int32 TrackIndex, int32 FormatIndex,
|
|
FMediaAudioTrackFormat &OutFormat) const
|
|
{
|
|
if (FormatIndex != 0 || PlayState == EMediaState::Closed)
|
|
{
|
|
return false;
|
|
}
|
|
int32 bitsPerSample;
|
|
int32 channels;
|
|
int32 rate;
|
|
if (!impl->getAudioTrackFormat(TrackIndex, &bitsPerSample, &channels, &rate))
|
|
{
|
|
return false;
|
|
}
|
|
OutFormat.BitsPerSample = bitsPerSample;
|
|
OutFormat.NumChannels = channels;
|
|
OutFormat.SampleRate = rate;
|
|
OutFormat.TypeName = TEXT("Native");
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get audio trackformat {0} {1} {2} {3}", TrackIndex,
|
|
bitsPerSample, channels, rate);
|
|
return true;
|
|
}
|
|
int32 FAndroidVulkanMediaPlayer::GetNumTracks(EMediaTrackType TrackType) const
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get num tracks type: {0}", int(TrackType));
|
|
// TODO: support audio / video only
|
|
switch (TrackType)
|
|
{
|
|
case EMediaTrackType::Audio:
|
|
return impl->numAudioTracks();
|
|
case EMediaTrackType::Video:
|
|
return impl->numVideoTracks();
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int32 FAndroidVulkanMediaPlayer::GetNumTrackFormats(EMediaTrackType TrackType,
|
|
int32 TrackIndex) const
|
|
{
|
|
if (TrackIndex >= GetNumTracks(TrackType))
|
|
{
|
|
return 0;
|
|
}
|
|
switch (TrackType)
|
|
{
|
|
case EMediaTrackType::Audio:
|
|
return 1;
|
|
case EMediaTrackType::Video:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int32 FAndroidVulkanMediaPlayer::GetSelectedTrack(EMediaTrackType TrackType) const
|
|
{
|
|
if (TrackType == EMediaTrackType::Audio)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Returning no selected audio track");
|
|
return INDEX_NONE;
|
|
}
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Returning selected track type {0}", int(TrackType));
|
|
return 0;
|
|
}
|
|
|
|
FText FAndroidVulkanMediaPlayer::GetTrackDisplayName(EMediaTrackType TrackType,
|
|
int32 TrackIndex) const
|
|
{
|
|
// TODO: pass through display names
|
|
|
|
return FText::Format(LOCTEXT("TrackName", "Track {0} type {1}"), (int)TrackType, TrackIndex);
|
|
}
|
|
|
|
int32 FAndroidVulkanMediaPlayer::GetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex) const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
FString FAndroidVulkanMediaPlayer::GetTrackLanguage(EMediaTrackType TrackType,
|
|
int32 TrackIndex) const
|
|
{
|
|
// TODO - pass language through
|
|
return TEXT("");
|
|
}
|
|
|
|
FString FAndroidVulkanMediaPlayer::GetTrackName(EMediaTrackType TrackType, int32 TrackIndex) const
|
|
{
|
|
if (TrackIndex == 0 && PlayState != EMediaState::Closed)
|
|
{
|
|
return TEXT("TRACK");
|
|
}
|
|
else
|
|
{
|
|
return TEXT("");
|
|
}
|
|
}
|
|
|
|
bool FAndroidVulkanMediaPlayer::GetVideoTrackFormat(int32 TrackIndex, int32 FormatIndex,
|
|
FMediaVideoTrackFormat &OutFormat) const
|
|
{
|
|
if (FormatIndex != 0 || PlayState == EMediaState::Closed)
|
|
{
|
|
return false;
|
|
}
|
|
int w = 0, h = 0;
|
|
float frameRate = 0;
|
|
if (!impl->getVideoTrackFormat(TrackIndex, &w, &h, &frameRate))
|
|
{
|
|
return false;
|
|
}
|
|
OutFormat.Dim.X = w;
|
|
OutFormat.Dim.Y = h;
|
|
OutFormat.FrameRate = frameRate;
|
|
OutFormat.FrameRates = TRange<float>(frameRate);
|
|
OutFormat.TypeName = TEXT("Vulkan Video Frame");
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Got out video format");
|
|
return true;
|
|
}
|
|
|
|
bool FAndroidVulkanMediaPlayer::SelectTrack(EMediaTrackType TrackType, int32 TrackIndex)
|
|
{
|
|
if (TrackType == EMediaTrackType::Audio)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Not selecting audio track");
|
|
return false;
|
|
}
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Selecting track type {0} = {0}", int(TrackType),
|
|
TrackIndex);
|
|
return true;
|
|
// return TrackIndex == 0 && PlayState != EMediaState::Closed;
|
|
}
|
|
|
|
bool FAndroidVulkanMediaPlayer::SetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex,
|
|
int32 FormatIndex)
|
|
{
|
|
// todo: support multiple formats / track
|
|
return TrackIndex == 0 && FormatIndex == 0 && PlayState != EMediaState::Closed;
|
|
}
|
|
|
|
IMediaSamples &FAndroidVulkanMediaPlayer::GetSamples()
|
|
{
|
|
return *SampleQueue.Get();
|
|
}
|
|
|
|
void FAndroidVulkanMediaPlayer::onVideoFrame(void *frameHwBuffer, int w, int h, int64_t presTimeNs)
|
|
{
|
|
if (PlayState == EMediaState::Closed)
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Releasing frame as we are closed");
|
|
impl->releaseFrame(frameHwBuffer);
|
|
return;
|
|
}
|
|
if (HasVideoThisFrame)
|
|
{
|
|
impl->releaseFrame(frameHwBuffer);
|
|
return;
|
|
}
|
|
HasVideoThisFrame = true;
|
|
auto textureSample = VideoSamplePool.AcquireShared();
|
|
textureSample->Init(impl, frameHwBuffer, w, h,
|
|
FMediaTimeStamp(FTimespan(presTimeNs / 100LL),
|
|
|
|
FMediaTimeStamp::MakeSequenceIndex(SeekIndex, 0)));
|
|
|
|
SampleQueue->AddVideo(textureSample);
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Add video sample");
|
|
// UE_LOGFMT(LogDirectVideo, VeryVerbose, "Video samples {0}",
|
|
// SampleQueue->NumVideoSamples());
|
|
// NB: this needs to happen after the sample is in the queue, so that
|
|
// the queue has something in when media player facade samples it for playback
|
|
// time
|
|
if (Seeking)
|
|
{
|
|
SeekIndex += 1;
|
|
UE_LOGFMT(LogDirectVideo, Verbose, "Seek completed {0}", SeekIndex);
|
|
Seeking = false;
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::SeekCompleted);
|
|
}
|
|
}
|
|
|
|
void FAndroidVulkanMediaPlayer::OnEnterBackground()
|
|
{
|
|
// going into backgroud - if playing, pause implementation player
|
|
if (PlayState == EMediaState::Playing)
|
|
{
|
|
impl->setPlaying(false);
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended);
|
|
UE_LOGFMT(LogDirectVideo, Verbose, "Enter suspend");
|
|
}
|
|
}
|
|
void FAndroidVulkanMediaPlayer::OnEnterForeground()
|
|
{
|
|
// back into foreground - if playing, start
|
|
if (PlayState == EMediaState::Playing)
|
|
{
|
|
impl->setPlaying(true);
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackResumed);
|
|
UE_LOGFMT(LogDirectVideo, Verbose, "Enter foreground");
|
|
}
|
|
}
|
|
|
|
void FAndroidVulkanMediaPlayer::ProcessVideoSamples()
|
|
{
|
|
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Process video samples");
|
|
}
|
|
|
|
void *FAndroidVulkanMediaPlayer::getVkDeviceProcAddr(const char *name)
|
|
{
|
|
void *result = static_cast<void *>(
|
|
(static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI))->RHIGetVkDeviceProcAddr(name));
|
|
|
|
return result;
|
|
}
|
|
|
|
VkDevice FAndroidVulkanMediaPlayer::getVkDevice()
|
|
{
|
|
IVulkanDynamicRHI *rhi = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
|
|
if (rhi == NULL)
|
|
{
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
return rhi->RHIGetVkDevice();
|
|
}
|
|
|
|
const VkAllocationCallbacks *FAndroidVulkanMediaPlayer::getVkAllocationCallbacks()
|
|
{
|
|
IVulkanDynamicRHI *rhi = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
|
|
if (rhi == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
return rhi->RHIGetVkAllocationCallbacks();
|
|
}
|
|
|
|
VkPhysicalDevice FAndroidVulkanMediaPlayer::getNativePhysicalDevice()
|
|
{
|
|
return static_cast<VkPhysicalDevice>(GDynamicRHI->RHIGetNativePhysicalDevice());
|
|
}
|
|
|
|
void FAndroidVulkanMediaPlayer::onPlaybackEnd(bool looping)
|
|
{
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackEndReached);
|
|
if (!looping)
|
|
{
|
|
PlayState = EMediaState::Stopped;
|
|
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended);
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|