forked from TheStudio/VPDevtemplate
Added ndi receiver and sdi outputs
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
Copyright (C) 2024 Vizrt NDI AB. All rights reserved.
|
||||
|
||||
This file and its use within a Product is bound by the terms of NDI SDK license that was provided
|
||||
as part of the NDI SDK. For more information, please review the license and the NDI SDK documentation.
|
||||
*/
|
||||
|
||||
#include <Services/NDIConnectionService.h>
|
||||
|
||||
#include <UObject/UObjectGlobals.h>
|
||||
#include <UObject/Package.h>
|
||||
#include <Misc/CoreDelegates.h>
|
||||
#include <NDIIOPluginSettings.h>
|
||||
#include <Objects/Media/NDIMediaSender.h>
|
||||
#include <Framework/Application/SlateApplication.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
#include <Engine/Engine.h>
|
||||
#include <TextureResource.h>
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include <Editor.h>
|
||||
|
||||
#endif
|
||||
|
||||
/** Define Global Accessors */
|
||||
|
||||
FNDIConnectionServiceSendVideoEvent FNDIConnectionService::EventOnSendVideoFrame;
|
||||
TMap<USoundSubmix*, FNDIConnectionServiceSendAudioEvent> FNDIConnectionService::SubmixSendAudioFrameEvents;
|
||||
|
||||
|
||||
FCriticalSection FNDIConnectionService::AudioSyncContext;
|
||||
FCriticalSection FNDIConnectionService::RenderSyncContext;
|
||||
|
||||
/** ************************ **/
|
||||
|
||||
/**
|
||||
Constructs a new instance of this object
|
||||
*/
|
||||
FNDIConnectionService::FNDIConnectionService() {}
|
||||
|
||||
// Begin the service
|
||||
bool FNDIConnectionService::Start()
|
||||
{
|
||||
if (!bIsInitialized)
|
||||
{
|
||||
bIsInitialized = true;
|
||||
|
||||
// Define some basic properties
|
||||
FNDIBroadcastConfiguration Configuration;
|
||||
FString BroadcastName = TEXT("Unreal Engine");
|
||||
EObjectFlags Flags = RF_Public | RF_Standalone | RF_Transient | RF_MarkAsNative;
|
||||
|
||||
bool bBeginBroadcastOnPlay = false;
|
||||
|
||||
// Load the plugin settings for broadcasting the active viewport
|
||||
if (auto* CoreSettings = NewObject<UNDIIOPluginSettings>())
|
||||
{
|
||||
// Define the configuration properties
|
||||
Configuration.FrameRate = CoreSettings->BroadcastRate;
|
||||
Configuration.FrameSize = FIntPoint(FMath::Clamp(CoreSettings->PreferredFrameSize.X, 240, 3840),
|
||||
FMath::Clamp(CoreSettings->PreferredFrameSize.Y, 240, 3840));
|
||||
|
||||
// Set the broadcast name
|
||||
BroadcastName = CoreSettings->ApplicationStreamName;
|
||||
|
||||
bBeginBroadcastOnPlay = CoreSettings->bBeginBroadcastOnPlay;
|
||||
|
||||
// clean-up the settings object
|
||||
CoreSettings->ConditionalBeginDestroy();
|
||||
CoreSettings = nullptr;
|
||||
}
|
||||
|
||||
/** Construct the Active Viewport video texture */
|
||||
this->VideoTexture = NewObject<UTextureRenderTarget2D>(
|
||||
GetTransientPackage(), UTextureRenderTarget2D::StaticClass(), TEXT("NDIViewportVideoTexture"), Flags);
|
||||
|
||||
/** Construct the active viewport sender */
|
||||
this->ActiveViewportSender = NewObject<UNDIMediaSender>(GetTransientPackage(), UNDIMediaSender::StaticClass(),
|
||||
TEXT("NDIViewportSender"), Flags);
|
||||
|
||||
VideoTexture->UpdateResource();
|
||||
|
||||
// Update the active viewport sender, with the properties defined in the settings configuration
|
||||
this->ActiveViewportSender->ChangeSourceName(BroadcastName);
|
||||
this->ActiveViewportSender->ChangeVideoTexture(VideoTexture);
|
||||
this->ActiveViewportSender->ChangeBroadcastConfiguration(Configuration);
|
||||
|
||||
// Hook into the core for the end of frame handlers
|
||||
FCoreDelegates::OnEndFrameRT.AddRaw(this, &FNDIConnectionService::OnEndRenderFrame);
|
||||
|
||||
if (!GIsEditor)
|
||||
{
|
||||
FCoreDelegates::OnPostEngineInit.AddRaw(this, &FNDIConnectionService::OnPostEngineInit);
|
||||
FCoreDelegates::OnEnginePreExit.AddRaw(this, &FNDIConnectionService::OnEnginePreExit);
|
||||
if (bBeginBroadcastOnPlay)
|
||||
BeginBroadcastingActiveViewport();
|
||||
}
|
||||
#if WITH_EDITOR
|
||||
else
|
||||
{
|
||||
FEditorDelegates::PostPIEStarted.AddLambda([this](const bool Success) {
|
||||
if (auto* CoreSettings = NewObject<UNDIIOPluginSettings>())
|
||||
{
|
||||
if (CoreSettings->bBeginBroadcastOnPlay == true)
|
||||
BeginBroadcastingActiveViewport();
|
||||
else
|
||||
BeginAudioCapture();
|
||||
|
||||
// clean-up the settings object
|
||||
CoreSettings->ConditionalBeginDestroy();
|
||||
CoreSettings = nullptr;
|
||||
}
|
||||
bIsInPIEMode = true;
|
||||
});
|
||||
FEditorDelegates::PrePIEEnded.AddLambda([this](const bool Success) { StopBroadcastingActiveViewport(); });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stop the service
|
||||
void FNDIConnectionService::Shutdown()
|
||||
{
|
||||
// Wait for the sync context locks
|
||||
FScopeLock AudioLock(&AudioSyncContext);
|
||||
FScopeLock RenderLock(&RenderSyncContext);
|
||||
|
||||
// reset the initialization properties
|
||||
bIsInitialized = false;
|
||||
|
||||
StopAudioCapture();
|
||||
|
||||
// unbind our handlers for our frame events
|
||||
FCoreDelegates::OnEndFrame.RemoveAll(this);
|
||||
FCoreDelegates::OnEndFrameRT.RemoveAll(this);
|
||||
|
||||
// Cleanup the broadcasting of the active viewport
|
||||
StopBroadcastingActiveViewport();
|
||||
}
|
||||
|
||||
|
||||
// Handler for when the render thread frame has ended
|
||||
void FNDIConnectionService::OnEndRenderFrame()
|
||||
{
|
||||
FScopeLock Lock(&RenderSyncContext);
|
||||
|
||||
if (bIsInitialized)
|
||||
{
|
||||
int64 ticks = FDateTime::Now().GetTimeOfDay().GetTicks();
|
||||
|
||||
if (FNDIConnectionService::EventOnSendVideoFrame.IsBound())
|
||||
{
|
||||
FNDIConnectionService::EventOnSendVideoFrame.Broadcast(ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FNDIConnectionService::BeginAudioCapture()
|
||||
{
|
||||
if (bIsInitialized)
|
||||
{
|
||||
if (!bIsAudioInitialized)
|
||||
{
|
||||
if (GEngine)
|
||||
{
|
||||
FAudioDeviceHandle AudioDevice = GEngine->GetActiveAudioDevice();
|
||||
if (AudioDevice.IsValid())
|
||||
{
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 4)) // 5.4 or later
|
||||
for (auto& SendAudioEvent : SubmixSendAudioFrameEvents)
|
||||
{
|
||||
if (SendAudioEvent.Key == nullptr)
|
||||
AudioDevice->RegisterSubmixBufferListener(AsShared(), AudioDevice->GetMainSubmixObject());
|
||||
else
|
||||
AudioDevice->RegisterSubmixBufferListener(AsShared(), *SendAudioEvent.Key);
|
||||
}
|
||||
#else
|
||||
AudioDevice->RegisterSubmixBufferListener(this);
|
||||
#endif
|
||||
bIsAudioInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FNDIConnectionService::StopAudioCapture()
|
||||
{
|
||||
if (bIsAudioInitialized)
|
||||
{
|
||||
if (GEngine)
|
||||
{
|
||||
FAudioDeviceHandle AudioDevice = GEngine->GetActiveAudioDevice();
|
||||
if (AudioDevice)
|
||||
{
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 4)) // 5.4 or later
|
||||
for (auto& SendAudioEvent : SubmixSendAudioFrameEvents)
|
||||
{
|
||||
if (SendAudioEvent.Key == nullptr)
|
||||
AudioDevice->UnregisterSubmixBufferListener(AsShared(), AudioDevice->GetMainSubmixObject());
|
||||
else
|
||||
AudioDevice->UnregisterSubmixBufferListener(AsShared(), *SendAudioEvent.Key);
|
||||
}
|
||||
#else
|
||||
AudioDevice->UnregisterSubmixBufferListener(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
bIsAudioInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FNDIConnectionService::OnPostEngineInit()
|
||||
{
|
||||
BeginAudioCapture();
|
||||
}
|
||||
|
||||
void FNDIConnectionService::OnEnginePreExit()
|
||||
{
|
||||
StopAudioCapture();
|
||||
}
|
||||
|
||||
bool FNDIConnectionService::BeginBroadcastingActiveViewport()
|
||||
{
|
||||
if (!bIsBroadcastingActiveViewport && IsValid(ActiveViewportSender))
|
||||
{
|
||||
// Load the plugin settings for broadcasting the active viewport
|
||||
if (auto* CoreSettings = NewObject<UNDIIOPluginSettings>())
|
||||
{
|
||||
// Define some basic properties
|
||||
FNDIBroadcastConfiguration Configuration;
|
||||
FString BroadcastName = TEXT("Unreal Engine");
|
||||
|
||||
// Define the configuration properties
|
||||
Configuration.FrameRate = CoreSettings->BroadcastRate;
|
||||
Configuration.FrameSize = FIntPoint(FMath::Clamp(CoreSettings->PreferredFrameSize.X, 240, 3840),
|
||||
FMath::Clamp(CoreSettings->PreferredFrameSize.Y, 240, 3840));
|
||||
|
||||
// Set the broadcast name
|
||||
BroadcastName = CoreSettings->ApplicationStreamName;
|
||||
|
||||
// clean-up the settings object
|
||||
CoreSettings->ConditionalBeginDestroy();
|
||||
CoreSettings = nullptr;
|
||||
|
||||
// Update the active viewport sender, with the properties defined in the settings configuration
|
||||
this->ActiveViewportSender->ChangeSourceName(BroadcastName);
|
||||
this->ActiveViewportSender->ChangeBroadcastConfiguration(Configuration);
|
||||
}
|
||||
|
||||
// we don't want to perform the linear conversion for the active viewport,
|
||||
// since it's already had the conversion completed by the engine before passing to the sender
|
||||
ActiveViewportSender->PerformLinearTosRGBConversion(false);
|
||||
|
||||
// Do not enable PTZ capabilities for active viewport sender
|
||||
ActiveViewportSender->EnablePTZ(false);
|
||||
|
||||
// Initialize the sender, this will automatically start rendering output via NDI
|
||||
ActiveViewportSender->Initialize(nullptr);
|
||||
|
||||
// We've initialized the active viewport
|
||||
bIsBroadcastingActiveViewport = true;
|
||||
|
||||
// However we need to update the 'Video Texture' to the active viewport back buffer...
|
||||
FSlateApplication::Get().GetRenderer()->OnPreResizeWindowBackBuffer().AddRaw(
|
||||
this, &FNDIConnectionService::OnActiveViewportBackbufferPreResize);
|
||||
FSlateApplication::Get().GetRenderer()->OnBackBufferReadyToPresent().AddRaw(
|
||||
this, &FNDIConnectionService::OnActiveViewportBackbufferReadyToPresent);
|
||||
|
||||
BeginAudioCapture();
|
||||
}
|
||||
|
||||
// always return true
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handler for when the active viewport back buffer has been resized
|
||||
void FNDIConnectionService::OnActiveViewportBackbufferPreResize(void* Backbuffer)
|
||||
{
|
||||
check(IsInGameThread());
|
||||
|
||||
// Ensure we have a valid video texture
|
||||
FTextureResource* TextureResource = GetVideoTextureResource();
|
||||
if (TextureResource != nullptr)
|
||||
{
|
||||
FRenderCommandFence Fence;
|
||||
|
||||
TextureResource->TextureRHI.SafeRelease();
|
||||
this->ActiveViewportSender->ChangeVideoTexture(VideoTexture);
|
||||
|
||||
ENQUEUE_RENDER_COMMAND(FlushRHIThreadToUpdateTextureRenderTargetReference)(
|
||||
[this](FRHICommandListImmediate& RHICmdList)
|
||||
{
|
||||
RHIUpdateTextureReference(VideoTexture->TextureReference.TextureReferenceRHI, nullptr);
|
||||
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
|
||||
});
|
||||
|
||||
// Wait for render thread to finish, so that renderthread texture references are updated
|
||||
Fence.BeginFence();
|
||||
Fence.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for when the back buffer is read to present to the end user
|
||||
void FNDIConnectionService::OnActiveViewportBackbufferReadyToPresent(SWindow& Window,
|
||||
const FTextureRHIRef& Backbuffer)
|
||||
{
|
||||
if (Window.GetType() == EWindowType::GameWindow || (Window.IsRegularWindow() && IsRunningInPIE()))
|
||||
{
|
||||
FTextureResource* TextureResource = GetVideoTextureResource();
|
||||
if (TextureResource != nullptr)
|
||||
{
|
||||
// Lets improve the performance a bit
|
||||
if (TextureResource->TextureRHI != Backbuffer)
|
||||
{
|
||||
TextureResource->TextureRHI = (FTextureRHIRef&)Backbuffer;
|
||||
this->ActiveViewportSender->ChangeVideoTexture(VideoTexture);
|
||||
RHIUpdateTextureReference(VideoTexture->TextureReference.TextureReferenceRHI, Backbuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FNDIConnectionService::StopBroadcastingActiveViewport()
|
||||
{
|
||||
// Wait for the sync context locks
|
||||
FScopeLock RenderLock(&RenderSyncContext);
|
||||
|
||||
// reset the initialization properties
|
||||
bIsInPIEMode = false;
|
||||
|
||||
StopAudioCapture();
|
||||
|
||||
// Ensure that if the active viewport sender is active, that we shut it down
|
||||
if (IsValid(this->ActiveViewportSender))
|
||||
{
|
||||
FSlateApplication::Get().GetRenderer()->OnPreResizeWindowBackBuffer().RemoveAll(this);
|
||||
FSlateApplication::Get().GetRenderer()->OnBackBufferReadyToPresent().RemoveAll(this);
|
||||
|
||||
// shutdown the active viewport sender (just in case it was activated)
|
||||
this->ActiveViewportSender->Shutdown();
|
||||
|
||||
// reset the broadcasting flag, so that we can restart the broadcast later
|
||||
this->bIsBroadcastingActiveViewport = false;
|
||||
|
||||
FTextureResource* TextureResource = GetVideoTextureResource();
|
||||
if (TextureResource != nullptr)
|
||||
{
|
||||
TextureResource->TextureRHI.SafeRelease();
|
||||
this->ActiveViewportSender->ChangeVideoTexture(VideoTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FTextureResource* FNDIConnectionService::GetVideoTextureResource() const
|
||||
{
|
||||
if(IsValid(this->VideoTexture))
|
||||
return this->VideoTexture->GetResource();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void FNDIConnectionService::OnNewSubmixBuffer(const USoundSubmix* OwningSubmix, float* AudioData, int32 NumSamples, int32 NumChannels, const int32 SampleRate, double AudioClock)
|
||||
{
|
||||
if (NumSamples > 0)
|
||||
{
|
||||
FScopeLock Lock(&AudioSyncContext);
|
||||
|
||||
if (bIsAudioInitialized)
|
||||
{
|
||||
int64 ticks = FDateTime::Now().GetTimeOfDay().GetTicks();
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 4)) // 5.4 or later
|
||||
FAudioDeviceHandle AudioDevice = GEngine->GetActiveAudioDevice();
|
||||
if (&AudioDevice->GetMainSubmixObject() == OwningSubmix)
|
||||
OwningSubmix = nullptr;
|
||||
#else
|
||||
OwningSubmix = nullptr;
|
||||
#endif
|
||||
|
||||
FNDIConnectionServiceSendAudioEvent* SendAudioEvent = SubmixSendAudioFrameEvents.Find(OwningSubmix);
|
||||
if (SendAudioEvent)
|
||||
{
|
||||
if (SendAudioEvent->IsBound())
|
||||
{
|
||||
SendAudioEvent->Broadcast(ticks, AudioData, NumSamples, NumChannels, SampleRate, AudioClock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 4)) // 5.4 or later
|
||||
const FString& FNDIConnectionService::GetListenerName() const
|
||||
{
|
||||
static const FString ListenerName(TEXT("NDIConnectionServiceListener"));
|
||||
return ListenerName;
|
||||
}
|
||||
#endif
|
||||
232
Plugins/NDIIO/Source/Core/Classes/Services/NDIFinderService.cpp
Normal file
232
Plugins/NDIIO/Source/Core/Classes/Services/NDIFinderService.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
Copyright (C) 2024 Vizrt NDI AB. All rights reserved.
|
||||
|
||||
This file and its use within a Product is bound by the terms of NDI SDK license that was provided
|
||||
as part of the NDI SDK. For more information, please review the license and the NDI SDK documentation.
|
||||
*/
|
||||
|
||||
#include <Services/NDIFinderService.h>
|
||||
#include <Async/Async.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
#include <NDIIOPluginAPI.h>
|
||||
|
||||
/** Define Global Accessors */
|
||||
|
||||
static NDIlib_find_instance_t NDI_FIND_INSTANCE = nullptr;
|
||||
static FCriticalSection NDI_FIND_SYNC_CONTEXT;
|
||||
|
||||
FNDIFinderService::FNDISourceCollectionChangedEvent FNDIFinderService::EventOnNDISourceCollectionChanged;
|
||||
|
||||
TArray<FNDIConnectionInformation> FNDIFinderService::NetworkSourceCollection = TArray<FNDIConnectionInformation>();
|
||||
|
||||
/** ************************ **/
|
||||
|
||||
FNDIFinderService::FNDIFinderService()
|
||||
{
|
||||
if (NDI_FIND_INSTANCE == nullptr)
|
||||
{
|
||||
FScopeLock Lock(&NDI_FIND_SYNC_CONTEXT);
|
||||
|
||||
NDI_FIND_INSTANCE = NDIlib_find_create_v2(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Begin the service
|
||||
bool FNDIFinderService::Start()
|
||||
{
|
||||
if (!bIsThreadRunning && p_RunnableThread == nullptr)
|
||||
{
|
||||
if (NDI_FIND_INSTANCE != nullptr)
|
||||
{
|
||||
this->bIsThreadRunning = true;
|
||||
p_RunnableThread = FRunnableThread::Create(this, TEXT("FNDIFinderService_Tick"), 0, TPri_BelowNormal);
|
||||
|
||||
return bIsThreadRunning = p_RunnableThread != nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** FRunnable Interface implementation for 'Init' */
|
||||
bool FNDIFinderService::Init()
|
||||
{
|
||||
return NDI_FIND_INSTANCE != nullptr;
|
||||
}
|
||||
|
||||
/** FRunnable Interface implementation for 'Stop' */
|
||||
uint32 FNDIFinderService::Run()
|
||||
{
|
||||
static const uint32 find_wait_time = 500;
|
||||
|
||||
if (NDI_FIND_INSTANCE == nullptr)
|
||||
return 0;
|
||||
|
||||
// Only update when we are suppose to run
|
||||
while (bIsThreadRunning)
|
||||
{
|
||||
// Wait up to 'find_wait_time' (in milliseconds) to determine whether new sources have been added
|
||||
if (!NDIlib_find_wait_for_sources(NDI_FIND_INSTANCE, find_wait_time))
|
||||
{
|
||||
// alright the source collection has stopped updating, did we change the network source collection?
|
||||
if (UpdateNetworkSourceCollection())
|
||||
{
|
||||
// Broadcast the even on the game thread for thread safety purposes
|
||||
AsyncTask(ENamedThreads::GameThread, []() {
|
||||
if (FNDIFinderService::EventOnNDISourceCollectionChanged.IsBound())
|
||||
FNDIFinderService::EventOnNDISourceCollectionChanged.Broadcast();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return success
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** FRunnable Interface implementation for 'Run' */
|
||||
void FNDIFinderService::Shutdown()
|
||||
{
|
||||
if (p_RunnableThread != nullptr)
|
||||
{
|
||||
this->bIsThreadRunning = false;
|
||||
|
||||
p_RunnableThread->WaitForCompletion();
|
||||
p_RunnableThread = nullptr;
|
||||
}
|
||||
|
||||
// Ensure we unload the finder instance
|
||||
if (NDI_FIND_INSTANCE != nullptr)
|
||||
NDIlib_find_destroy(NDI_FIND_INSTANCE);
|
||||
}
|
||||
|
||||
// Stop the service
|
||||
void FNDIFinderService::Stop()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool FNDIFinderService::UpdateNetworkSourceCollection()
|
||||
{
|
||||
uint32 no_sources = 0;
|
||||
bool bHasCollectionChanged = false;
|
||||
|
||||
if (NDI_FIND_INSTANCE != nullptr)
|
||||
{
|
||||
const NDIlib_source_t* p_sources = NDIlib_find_get_current_sources(NDI_FIND_INSTANCE, &no_sources);
|
||||
|
||||
// Change Scope
|
||||
{
|
||||
FScopeLock lock(&NDI_FIND_SYNC_CONTEXT);
|
||||
|
||||
bHasCollectionChanged = FNDIFinderService::NetworkSourceCollection.Num() != no_sources;
|
||||
|
||||
if (no_sources > 0 && p_sources != nullptr)
|
||||
{
|
||||
uint32 CurrentSourceCount = NetworkSourceCollection.Num();
|
||||
|
||||
for (uint32 iter = 0; iter < no_sources; iter++)
|
||||
{
|
||||
if (iter >= CurrentSourceCount)
|
||||
{
|
||||
NetworkSourceCollection.Add(FNDIConnectionInformation());
|
||||
}
|
||||
|
||||
const NDIlib_source_t* SourceInformation = &p_sources[iter];
|
||||
FNDIConnectionInformation* CollectionSource = &NetworkSourceCollection[iter];
|
||||
|
||||
bHasCollectionChanged |= SourceInformation->p_url_address != CollectionSource->Url;
|
||||
CollectionSource->Url = SourceInformation->p_url_address;
|
||||
CollectionSource->SourceName = SourceInformation->p_ndi_name;
|
||||
FString SourceName = SourceInformation->p_ndi_name;
|
||||
SourceName.Split(TEXT(" "), &CollectionSource->MachineName, &CollectionSource->StreamName);
|
||||
|
||||
// Now that the MachineName and StreamName have been split, cleanup the stream name
|
||||
CollectionSource->StreamName.RemoveFromStart("(");
|
||||
CollectionSource->StreamName.RemoveFromEnd(")");
|
||||
}
|
||||
|
||||
if (CurrentSourceCount > no_sources)
|
||||
{
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 5)) // 5.5 or later
|
||||
NetworkSourceCollection.RemoveAt(no_sources, CurrentSourceCount - no_sources, EAllowShrinking::Yes);
|
||||
#else
|
||||
NetworkSourceCollection.RemoveAt(no_sources, CurrentSourceCount - no_sources, true);
|
||||
#endif
|
||||
bHasCollectionChanged = true;
|
||||
}
|
||||
}
|
||||
else if (NetworkSourceCollection.Num() > 0)
|
||||
{
|
||||
NetworkSourceCollection.Empty();
|
||||
bHasCollectionChanged = true;
|
||||
}
|
||||
|
||||
bHasCollectionChanged |= NetworkSourceCollection.Num() != no_sources;
|
||||
}
|
||||
}
|
||||
|
||||
return bHasCollectionChanged;
|
||||
}
|
||||
|
||||
/** Call to update an existing collection of network sources to match the current collection */
|
||||
bool FNDIFinderService::UpdateSourceCollection(TArray<FNDIConnectionInformation>& InSourceCollection)
|
||||
{
|
||||
bool bHasCollectionChanged = false;
|
||||
|
||||
{
|
||||
FScopeLock Lock(&NDI_FIND_SYNC_CONTEXT);
|
||||
|
||||
const uint32& no_sources = NetworkSourceCollection.Num();
|
||||
bHasCollectionChanged = InSourceCollection.Num() != no_sources;
|
||||
|
||||
if (no_sources > 0)
|
||||
{
|
||||
uint32 CurrentSourceCount = InSourceCollection.Num();
|
||||
|
||||
for (uint32 iter = 0; iter < no_sources; iter++)
|
||||
{
|
||||
if (iter >= CurrentSourceCount)
|
||||
{
|
||||
InSourceCollection.Add(FNDIConnectionInformation());
|
||||
CurrentSourceCount = InSourceCollection.Num();
|
||||
}
|
||||
|
||||
FNDIConnectionInformation* CollectionSource = &InSourceCollection[iter];
|
||||
const FNDIConnectionInformation* SourceInformation = &NetworkSourceCollection[iter];
|
||||
|
||||
bHasCollectionChanged |= SourceInformation->Url != CollectionSource->Url;
|
||||
|
||||
CollectionSource->Url = SourceInformation->Url;
|
||||
CollectionSource->SourceName = SourceInformation->SourceName;
|
||||
CollectionSource->MachineName = SourceInformation->MachineName;
|
||||
CollectionSource->StreamName = SourceInformation->StreamName;
|
||||
}
|
||||
|
||||
if (CurrentSourceCount > no_sources)
|
||||
{
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 5)) // 5.5 or later
|
||||
InSourceCollection.RemoveAt(no_sources, CurrentSourceCount - no_sources, EAllowShrinking::Yes);
|
||||
#else
|
||||
InSourceCollection.RemoveAt(no_sources, CurrentSourceCount - no_sources, true);
|
||||
#endif
|
||||
bHasCollectionChanged = true;
|
||||
}
|
||||
}
|
||||
else if (InSourceCollection.Num() > 0)
|
||||
{
|
||||
InSourceCollection.Empty();
|
||||
bHasCollectionChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
return bHasCollectionChanged;
|
||||
}
|
||||
|
||||
/** Get the available sources on the network */
|
||||
const TArray<FNDIConnectionInformation> FNDIFinderService::GetNetworkSourceCollection()
|
||||
{
|
||||
FScopeLock Lock(&NDI_FIND_SYNC_CONTEXT);
|
||||
|
||||
return FNDIFinderService::NetworkSourceCollection;
|
||||
}
|
||||
Reference in New Issue
Block a user