First commit
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
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 <Components/NDIBroadcastComponent.h>
|
||||
|
||||
UNDIBroadcastComponent::UNDIBroadcastComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{}
|
||||
|
||||
/**
|
||||
Initialize this component with the media source required for sending NDI audio, video, and metadata.
|
||||
Returns false, if the MediaSource is already been set. This is usually the case when this component is
|
||||
initialized in Blueprints.
|
||||
*/
|
||||
bool UNDIBroadcastComponent::Initialize(UNDIMediaSender* InMediaSource)
|
||||
{
|
||||
// is the media source already set?
|
||||
if (this->NDIMediaSource == nullptr && InMediaSource != nullptr)
|
||||
{
|
||||
// we passed validation, so set the media source
|
||||
this->NDIMediaSource = InMediaSource;
|
||||
}
|
||||
|
||||
// did we pass validation
|
||||
return InMediaSource != nullptr && InMediaSource == NDIMediaSource;
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to start broadcasting audio, video, and metadata via the 'NDIMediaSource' associated with this object
|
||||
|
||||
@param ErrorMessage The error message received when the media source is unable to start broadcasting
|
||||
@result Indicates whether this object successfully started broadcasting
|
||||
*/
|
||||
bool UNDIBroadcastComponent::StartBroadcasting(FString& ErrorMessage)
|
||||
{
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->Initialize(nullptr);
|
||||
|
||||
// the underlying functionality is always return 'true'
|
||||
return true;
|
||||
}
|
||||
|
||||
// We have no media source to broadcast
|
||||
ErrorMessage = TEXT("No Media Source present to broadcast");
|
||||
|
||||
// looks like we don't have a media source to broadcast
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
Changes the name of the sender object as seen on the network for remote connections
|
||||
|
||||
@param InSourceName The new name of the source to be identified as on the network
|
||||
*/
|
||||
void UNDIBroadcastComponent::ChangeSourceName(const FString& InSourceName)
|
||||
{
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->ChangeSourceName(InSourceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to change the Broadcast information associated with this media object
|
||||
|
||||
@param InConfiguration The new configuration to broadcast
|
||||
*/
|
||||
void UNDIBroadcastComponent::ChangeBroadcastConfiguration(const FNDIBroadcastConfiguration& InConfiguration)
|
||||
{
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->ChangeBroadcastConfiguration(InConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to change the RenderTarget used in sending video frames over NDI
|
||||
|
||||
@param BroadcastTexture The texture to use as video, while broadcasting over NDI
|
||||
*/
|
||||
void UNDIBroadcastComponent::ChangeBroadcastTexture(UTextureRenderTarget2D* BroadcastTexture)
|
||||
{
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->ChangeVideoTexture(BroadcastTexture);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Determines the current tally information. If you specify a timeout then it will wait until it has
|
||||
changed, otherwise it will simply poll it and return the current tally immediately
|
||||
|
||||
@param IsOnPreview - A state indicating whether this source in on preview of a receiver
|
||||
@param IsOnProgram - A state indicating whether this source is on program of a receiver
|
||||
*/
|
||||
void UNDIBroadcastComponent::GetTallyInformation(bool& IsOnPreview, bool& IsOnProgram)
|
||||
{
|
||||
// Initialize the properties
|
||||
IsOnPreview = false;
|
||||
IsOnProgram = false;
|
||||
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->GetTallyInformation(IsOnPreview, IsOnProgram, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the current number of receivers connected to this source. This can be used to avoid rendering
|
||||
when nothing is connected to the video source. which can significantly improve the efficiency if
|
||||
you want to make a lot of sources available on the network
|
||||
|
||||
@param Result The total number of connected receivers attached to the broadcast of this object
|
||||
*/
|
||||
void UNDIBroadcastComponent::GetNumberOfConnections(int32& Result)
|
||||
{
|
||||
// Initialize the property
|
||||
Result = 0;
|
||||
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->GetNumberOfConnections(Result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to immediately stop sending frames over NDI to any connected receivers
|
||||
*/
|
||||
void UNDIBroadcastComponent::StopBroadcasting()
|
||||
{
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->Shutdown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
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 <Components/NDIFinderComponent.h>
|
||||
#include <Services/NDIFinderService.h>
|
||||
|
||||
UNDIFinderComponent::UNDIFinderComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {}
|
||||
|
||||
void UNDIFinderComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Provide some sense of thread-safety
|
||||
FScopeLock Lock(&CollectionSyncContext);
|
||||
|
||||
// Update the NetworkSourceCollection with some sources which that the service has already found
|
||||
FNDIFinderService::UpdateSourceCollection(NetworkSourceCollection);
|
||||
|
||||
// Ensure that we are subscribed to the collection changed notification so we can handle it locally
|
||||
FNDIFinderService::EventOnNDISourceCollectionChanged.AddUObject(
|
||||
this, &UNDIFinderComponent::OnNetworkSourceCollectionChangedEvent);
|
||||
}
|
||||
|
||||
void UNDIFinderComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
|
||||
// Provide some sense of thread-safety
|
||||
FScopeLock Lock(&CollectionSyncContext);
|
||||
|
||||
// Empty the source collection
|
||||
this->NetworkSourceCollection.Empty(0);
|
||||
|
||||
// Ensure that we are no longer subscribed to collection change notifications
|
||||
FNDIFinderService::EventOnNDISourceCollectionChanged.RemoveAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
An Event handler for when the NDI Finder Service notifies listeners that changes have been
|
||||
detected in the network source collection
|
||||
*/
|
||||
void UNDIFinderComponent::OnNetworkSourceCollectionChangedEvent()
|
||||
{
|
||||
// Since we don't poll the NDIFinderService for network sources, we subscribe to the change notification.
|
||||
// Now we need to update the Network Source Collection, but we need to do it in a thread-safe way.
|
||||
|
||||
FScopeLock Lock(&CollectionSyncContext);
|
||||
|
||||
// Check to determine if something actually changed within the collection. We don't want to trigger
|
||||
// notifications unnecessarily.
|
||||
if (FNDIFinderService::UpdateSourceCollection(NetworkSourceCollection))
|
||||
{
|
||||
// Trigger the blueprint handling of the situation.
|
||||
this->OnNetworkSourcesChangedEvent();
|
||||
|
||||
// If any listeners have subscribed broadcast any collection changes
|
||||
if (this->OnNetworkSourcesChanged.IsBound())
|
||||
this->OnNetworkSourcesChanged.Broadcast(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to find a network source by the supplied name.
|
||||
|
||||
@param ConnectionInformation An existing source information structure which contains the source name
|
||||
@param InSourceName A string value representing the name of the source to find
|
||||
@result A value indicating whether a source with the supplied name was found
|
||||
*/
|
||||
const bool UNDIFinderComponent::FindNetworkSourceByName(FNDIConnectionInformation& ConnectionInformation,
|
||||
FString InSourceName)
|
||||
{
|
||||
// Lock the Collection so that we are working with a solid collection of items
|
||||
FScopeLock Lock(&CollectionSyncContext);
|
||||
|
||||
// Ensure we Reset the SourceInformation
|
||||
ConnectionInformation.Reset();
|
||||
|
||||
for (const auto& connectionInfo : NetworkSourceCollection)
|
||||
{
|
||||
if (InSourceName.Equals(connectionInfo.SourceName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
ConnectionInformation = connectionInfo;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the current collection of sources found on the network
|
||||
*/
|
||||
const TArray<FNDIConnectionInformation> UNDIFinderComponent::GetNetworkSources()
|
||||
{
|
||||
// Lock the current source collection
|
||||
FScopeLock Lock(&CollectionSyncContext);
|
||||
|
||||
// return the source collection
|
||||
return this->NetworkSourceCollection;
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
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 <Components/NDIPTZControllerComponent.h>
|
||||
#include <GameFramework/Actor.h>
|
||||
|
||||
#include <Structures/NDIXml.h>
|
||||
|
||||
|
||||
/**
|
||||
Parsers for PTZ metadata
|
||||
*/
|
||||
|
||||
class NDIXmlElementParser_ntk_ptz_pan_tilt_speed : public NDIXmlElementParser
|
||||
{
|
||||
public:
|
||||
NDIXmlElementParser_ntk_ptz_pan_tilt_speed(UPTZController* PTZControllerIn)
|
||||
: PTZController(PTZControllerIn)
|
||||
{}
|
||||
|
||||
virtual bool ProcessOpen(const TCHAR* ElementName, const TCHAR* ElementData)
|
||||
{
|
||||
PanSpeed = 0.0;
|
||||
TiltSpeed = 0.0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override
|
||||
{
|
||||
if(FCString::Strcmp(TEXT("pan_speed"), AttributeName) == 0)
|
||||
{
|
||||
PanSpeed = FCString::Atod(AttributeValue);
|
||||
}
|
||||
else if(FCString::Strcmp(TEXT("tilt_speed"), AttributeName) == 0)
|
||||
{
|
||||
TiltSpeed = FCString::Atod(AttributeValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessClose(const TCHAR* ElementName) override
|
||||
{
|
||||
PTZController->SetPTZPanTiltSpeed(PanSpeed, TiltSpeed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
UPTZController* PTZController;
|
||||
|
||||
double PanSpeed { 0.0 };
|
||||
double TiltSpeed { 0.0 };
|
||||
};
|
||||
|
||||
class NDIXmlElementParser_ntk_ptz_zoom_speed : public NDIXmlElementParser
|
||||
{
|
||||
public:
|
||||
NDIXmlElementParser_ntk_ptz_zoom_speed(UPTZController* PTZControllerIn)
|
||||
: PTZController(PTZControllerIn)
|
||||
{}
|
||||
|
||||
virtual bool ProcessOpen(const TCHAR* ElementName, const TCHAR* ElementData)
|
||||
{
|
||||
ZoomSpeed = 0.0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override
|
||||
{
|
||||
if(FCString::Strcmp(TEXT("zoom_speed"), AttributeName) == 0)
|
||||
{
|
||||
ZoomSpeed = FCString::Atod(AttributeValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessClose(const TCHAR* ElementName) override
|
||||
{
|
||||
PTZController->SetPTZZoomSpeed(ZoomSpeed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
UPTZController* PTZController;
|
||||
|
||||
double ZoomSpeed { 0.0 };
|
||||
};
|
||||
|
||||
class NDIXmlElementParser_ntk_ptz_focus : public NDIXmlElementParser
|
||||
{
|
||||
public:
|
||||
NDIXmlElementParser_ntk_ptz_focus(UPTZController* PTZControllerIn)
|
||||
: PTZController(PTZControllerIn)
|
||||
{}
|
||||
|
||||
virtual bool ProcessOpen(const TCHAR* ElementName, const TCHAR* ElementData)
|
||||
{
|
||||
AutoMode = true;
|
||||
Distance = 0.5;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override
|
||||
{
|
||||
if(FCString::Strcmp(TEXT("mode"), AttributeName) == 0)
|
||||
{
|
||||
if(FCString::Strcmp(TEXT("manual"), AttributeValue) == 0)
|
||||
AutoMode = false;
|
||||
}
|
||||
else if(FCString::Strcmp(TEXT("distance"), AttributeName) == 0)
|
||||
{
|
||||
Distance = FCString::Atod(AttributeValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessClose(const TCHAR* ElementName) override
|
||||
{
|
||||
PTZController->SetPTZFocus(AutoMode, Distance);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
UPTZController* PTZController;
|
||||
|
||||
bool AutoMode { true };
|
||||
double Distance { 0.5 };
|
||||
};
|
||||
|
||||
class NDIXmlElementParser_ntk_ptz_store_preset : public NDIXmlElementParser
|
||||
{
|
||||
public:
|
||||
NDIXmlElementParser_ntk_ptz_store_preset(UPTZController* PTZControllerIn)
|
||||
: PTZController(PTZControllerIn)
|
||||
{}
|
||||
|
||||
virtual bool ProcessOpen(const TCHAR* ElementName, const TCHAR* ElementData)
|
||||
{
|
||||
StoreIndex = -1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override
|
||||
{
|
||||
if(FCString::Strcmp(TEXT("index"), AttributeName) == 0)
|
||||
{
|
||||
StoreIndex = FCString::Atoi(AttributeValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessClose(const TCHAR* ElementName) override
|
||||
{
|
||||
if(StoreIndex >= 0)
|
||||
{
|
||||
PTZController->StorePTZState(StoreIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
UPTZController* PTZController;
|
||||
|
||||
int StoreIndex { -1 };
|
||||
};
|
||||
|
||||
class NDIXmlElementParser_ntk_ptz_recall_preset : public NDIXmlElementParser
|
||||
{
|
||||
public:
|
||||
NDIXmlElementParser_ntk_ptz_recall_preset(UPTZController* PTZControllerIn)
|
||||
: PTZController(PTZControllerIn)
|
||||
{}
|
||||
|
||||
virtual bool ProcessOpen(const TCHAR* ElementName, const TCHAR* ElementData)
|
||||
{
|
||||
RecallIndex = -1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override
|
||||
{
|
||||
if(FCString::Strcmp(TEXT("index"), AttributeName) == 0)
|
||||
{
|
||||
RecallIndex = FCString::Atoi(AttributeValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessClose(const TCHAR* ElementName) override
|
||||
{
|
||||
if(RecallIndex >= 0)
|
||||
{
|
||||
PTZController->RecallPTZState(RecallIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
UPTZController* PTZController;
|
||||
|
||||
int RecallIndex { -1 };
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
PTZ controller component
|
||||
*/
|
||||
UPTZController::UPTZController()
|
||||
{
|
||||
this->bWantsInitializeComponent = true;
|
||||
|
||||
this->PrimaryComponentTick.bAllowTickOnDedicatedServer = false;
|
||||
this->PrimaryComponentTick.bCanEverTick = true;
|
||||
this->PrimaryComponentTick.bHighPriority = true;
|
||||
this->PrimaryComponentTick.bRunOnAnyThread = false;
|
||||
this->PrimaryComponentTick.bStartWithTickEnabled = true;
|
||||
this->PrimaryComponentTick.bTickEvenWhenPaused = true;
|
||||
|
||||
this->NDIMetadataParser = MakeShareable(new NDIXmlParser());
|
||||
this->NDIMetadataParser->AddElementParser("ntk_ptz_pan_tilt_speed", MakeShareable(new NDIXmlElementParser_ntk_ptz_pan_tilt_speed(this)));
|
||||
this->NDIMetadataParser->AddElementParser("ntk_ptz_zoom_speed", MakeShareable(new NDIXmlElementParser_ntk_ptz_zoom_speed(this)));
|
||||
this->NDIMetadataParser->AddElementParser("ntk_ptz_focus", MakeShareable(new NDIXmlElementParser_ntk_ptz_focus(this)));
|
||||
this->NDIMetadataParser->AddElementParser("ntk_ptz_store_preset", MakeShareable(new NDIXmlElementParser_ntk_ptz_store_preset(this)));
|
||||
this->NDIMetadataParser->AddElementParser("ntk_ptz_recall_preset", MakeShareable(new NDIXmlElementParser_ntk_ptz_recall_preset(this)));
|
||||
}
|
||||
|
||||
UPTZController::~UPTZController()
|
||||
{}
|
||||
|
||||
void UPTZController::InitializeComponent()
|
||||
{
|
||||
Super::InitializeComponent();
|
||||
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// Ensure the PTZ controller is subscribed to the sender receiving metadata
|
||||
this->NDIMediaSource->OnSenderMetaDataReceived.RemoveAll(this);
|
||||
this->NDIMediaSource->OnSenderMetaDataReceived.AddDynamic(this, &UPTZController::ReceiveMetaDataFromSender);
|
||||
}
|
||||
}
|
||||
|
||||
bool UPTZController::Initialize(UNDIMediaSender* InMediaSource)
|
||||
{
|
||||
// is the media source already set?
|
||||
if (this->NDIMediaSource == nullptr && InMediaSource != nullptr)
|
||||
{
|
||||
// we passed validation, so set the media source
|
||||
this->NDIMediaSource = InMediaSource;
|
||||
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// Ensure the PTZ controller is subscribed to the sender receiving metadata
|
||||
this->NDIMediaSource->OnSenderMetaDataReceived.RemoveAll(this);
|
||||
this->NDIMediaSource->OnSenderMetaDataReceived.AddDynamic(this, &UPTZController::ReceiveMetaDataFromSender);
|
||||
}
|
||||
}
|
||||
|
||||
// did we pass validation
|
||||
return InMediaSource != nullptr && InMediaSource == NDIMediaSource;
|
||||
}
|
||||
|
||||
void UPTZController::SetPTZPanTiltSpeed(float PanSpeed, float TiltSpeed)
|
||||
{
|
||||
PTZPanSpeed = PanSpeed;
|
||||
PTZTiltSpeed = TiltSpeed;
|
||||
|
||||
OnPTZPanTiltSpeed.Broadcast(PanSpeed, TiltSpeed);
|
||||
}
|
||||
|
||||
void UPTZController::SetPTZZoomSpeed(float ZoomSpeed)
|
||||
{
|
||||
PTZZoomSpeed = ZoomSpeed;
|
||||
|
||||
OnPTZZoomSpeed.Broadcast(ZoomSpeed);
|
||||
}
|
||||
|
||||
void UPTZController::SetPTZFocus(bool AutoMode, float Distance)
|
||||
{
|
||||
FPTZState PTZState = GetPTZStateFromUE();
|
||||
PTZState.FocusDistance = Distance;
|
||||
PTZState.bAutoFocus = AutoMode;
|
||||
SetPTZStateToUE(PTZState);
|
||||
|
||||
OnPTZFocus.Broadcast(AutoMode, Distance);
|
||||
}
|
||||
|
||||
void UPTZController::StorePTZState(int Index)
|
||||
{
|
||||
if((Index >= 0) && (Index < 256))
|
||||
{
|
||||
FPTZState PTZState = GetPTZStateFromUE();
|
||||
|
||||
if(Index >= PTZStoredStates.Num())
|
||||
PTZStoredStates.SetNum(Index+1);
|
||||
PTZStoredStates[Index] = PTZState;
|
||||
|
||||
OnPTZStore.Broadcast(Index);
|
||||
}
|
||||
}
|
||||
|
||||
void UPTZController::RecallPTZState(int Index)
|
||||
{
|
||||
if((Index >= 0) && (Index < PTZStoredStates.Num()))
|
||||
{
|
||||
if(PTZRecallEasing > 0)
|
||||
{
|
||||
PTZStateInterp.PTZTargetState = PTZStoredStates[Index];
|
||||
PTZStateInterp.EasingDuration = PTZRecallEasing;
|
||||
PTZStateInterp.EasingRemaining = PTZStateInterp.EasingDuration;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPTZStateToUE(PTZStoredStates[Index]);
|
||||
}
|
||||
}
|
||||
|
||||
OnPTZRecall.Broadcast(Index);
|
||||
}
|
||||
|
||||
FPTZState UPTZController::GetPTZStateFromUE() const
|
||||
{
|
||||
AActor* OwnerActor = GetOwner();
|
||||
|
||||
IPTZControllableInterface* ControllableObject = Cast<IPTZControllableInterface>(OwnerActor);
|
||||
if (ControllableObject != nullptr)
|
||||
{
|
||||
return ControllableObject->GetPTZStateFromUE();
|
||||
}
|
||||
else
|
||||
{
|
||||
FPTZState PTZState;
|
||||
|
||||
FTransform Transform = OwnerActor->GetActorTransform();
|
||||
FVector Euler = Transform.GetRotation().Euler();
|
||||
PTZState.Pan = FMath::DegreesToRadians(Euler[2]);
|
||||
PTZState.Tilt = FMath::DegreesToRadians(Euler[1]);
|
||||
Transform.SetRotation(FQuat::MakeFromEuler(FVector(Euler[0], 0.f, 0.f)));
|
||||
PTZState.CameraTransform = Transform;
|
||||
|
||||
return PTZState;
|
||||
}
|
||||
}
|
||||
|
||||
void UPTZController::SetPTZStateToUE(const FPTZState& PTZState)
|
||||
{
|
||||
if (EnablePTZ == true)
|
||||
{
|
||||
AActor* OwnerActor = GetOwner();
|
||||
|
||||
IPTZControllableInterface* ControllableObject = Cast<IPTZControllableInterface>(OwnerActor);
|
||||
if (ControllableObject != nullptr)
|
||||
{
|
||||
ControllableObject->SetPTZStateToUE(PTZState);
|
||||
}
|
||||
else
|
||||
{
|
||||
FTransform Transform = PTZState.CameraTransform;
|
||||
FVector Euler = Transform.GetRotation().Euler();
|
||||
float Pitch = FMath::RadiansToDegrees(PTZState.Tilt);
|
||||
float Yaw = FMath::RadiansToDegrees(PTZState.Pan);
|
||||
Transform.SetRotation(FQuat::MakeFromEuler(FVector(Euler[0], Pitch, Yaw)));
|
||||
OwnerActor->SetActorTransform(Transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UPTZController::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
bool bUpdatePTZ = false;
|
||||
|
||||
if(PTZStateInterp.EasingRemaining > 0)
|
||||
bUpdatePTZ = true;
|
||||
|
||||
if((PTZPanSpeed != 0) || (PTZTiltSpeed != 0) || (PTZZoomSpeed != 0))
|
||||
bUpdatePTZ = true;
|
||||
|
||||
if(bUpdatePTZ)
|
||||
{
|
||||
FPTZState PTZState = GetPTZStateFromUE();
|
||||
|
||||
if(PTZStateInterp.EasingRemaining > 0)
|
||||
{
|
||||
float EasingDelta = FMath::Min(PTZStateInterp.EasingRemaining, DeltaTime);
|
||||
|
||||
/** Interpolate from 0 to 1 using polynomial:
|
||||
I(F) = a*F^3 + b*F^2 + c*F + d
|
||||
with constraints:
|
||||
Start and end points: I(0) = 0, I(1) = 1
|
||||
Smooth stop at end: I'(1) = 0 (velocity)
|
||||
I''(1) = 0 (acceleration)
|
||||
Solve to get:
|
||||
a = 1, b = -3, c = 3, d = 0
|
||||
I(F) = F^3 - 3*F^2 + 3*F
|
||||
*/
|
||||
float EasingFrac = (PTZStateInterp.EasingRemaining > 0) ? (EasingDelta / PTZStateInterp.EasingRemaining) : 1;
|
||||
float EasingInterp = EasingFrac*EasingFrac*EasingFrac - 3*EasingFrac*EasingFrac + 3*EasingFrac;
|
||||
|
||||
PTZState.Pan = PTZState.Pan * (1 - EasingInterp) + PTZStateInterp.PTZTargetState.Pan * EasingInterp;
|
||||
PTZState.Tilt = PTZState.Tilt * (1 - EasingInterp) + PTZStateInterp.PTZTargetState.Tilt * EasingInterp;
|
||||
PTZState.FieldOfView = PTZState.FieldOfView * (1 - EasingInterp) + PTZStateInterp.PTZTargetState.FieldOfView * EasingInterp;
|
||||
PTZState.FocusDistance = PTZState.FocusDistance * (1 - EasingInterp) + PTZStateInterp.PTZTargetState.FocusDistance * EasingInterp;
|
||||
PTZState.CameraTransform.BlendWith(PTZStateInterp.PTZTargetState.CameraTransform, EasingInterp);
|
||||
|
||||
PTZStateInterp.EasingRemaining -= EasingDelta;
|
||||
}
|
||||
|
||||
PTZState.FieldOfView -= FMath::RadiansToDegrees(PTZZoomSpeed) * DeltaTime;
|
||||
if(PTZWithFoVLimit)
|
||||
{
|
||||
PTZState.FieldOfView = FMath::Clamp(PTZState.FieldOfView, PTZFoVMinLimit, PTZFoVMaxLimit);
|
||||
}
|
||||
PTZState.FieldOfView = FMath::Clamp(PTZState.FieldOfView, 5.f, 170.f);
|
||||
|
||||
float MovementScale = PTZState.FieldOfView / 90.f;
|
||||
|
||||
PTZState.Pan += PTZPanSpeed * DeltaTime * MovementScale * (bPTZPanInvert ? -1 : 1);
|
||||
PTZState.Pan = FMath::Fmod(PTZState.Pan, 2*PI);
|
||||
if(PTZWithPanLimit)
|
||||
{
|
||||
PTZState.Pan = FMath::Clamp(PTZState.Pan, FMath::DegreesToRadians(PTZPanMinLimit), FMath::DegreesToRadians(PTZPanMaxLimit));
|
||||
}
|
||||
|
||||
PTZState.Tilt += PTZTiltSpeed * DeltaTime * MovementScale * (bPTZTiltInvert ? -1 : 1);
|
||||
PTZState.Tilt = FMath::Fmod(PTZState.Tilt, 2*PI);
|
||||
if(PTZWithTiltLimit)
|
||||
{
|
||||
PTZState.Tilt = FMath::Clamp(PTZState.Tilt, FMath::DegreesToRadians(PTZTiltMinLimit), FMath::DegreesToRadians(PTZTiltMaxLimit));
|
||||
}
|
||||
|
||||
SetPTZStateToUE(PTZState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UPTZController::ReceiveMetaDataFromSender(UNDIMediaSender* Sender, FString Data)
|
||||
{
|
||||
FText OutErrorMessage;
|
||||
int32 OutErrorLineNumber;
|
||||
|
||||
FFastXml::ParseXmlFile(this->NDIMetadataParser.Get(),
|
||||
nullptr, // XmlFilePath
|
||||
Data.GetCharArray().GetData(), // XmlFileContents
|
||||
nullptr, // FeedbackContext
|
||||
false, // bShowSlowTaskDialog
|
||||
false, // bShowCancelButton
|
||||
OutErrorMessage, // OutErrorMessage
|
||||
OutErrorLineNumber // OutErrorLineNumber
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
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 <Components/NDIReceiverComponent.h>
|
||||
|
||||
UNDIReceiverComponent::UNDIReceiverComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {}
|
||||
|
||||
/**
|
||||
Initialize this component with the media source required for receiving NDI audio, video, and metadata.
|
||||
Returns false, if the MediaSource is already been set. This is usually the case when this component is
|
||||
initialized in Blueprints.
|
||||
*/
|
||||
bool UNDIReceiverComponent::Initialize(UNDIMediaReceiver* InMediaSource)
|
||||
{
|
||||
if (this->NDIMediaSource == nullptr && InMediaSource != nullptr)
|
||||
{
|
||||
this->NDIMediaSource = InMediaSource;
|
||||
}
|
||||
|
||||
return InMediaSource != nullptr && InMediaSource == NDIMediaSource;
|
||||
}
|
||||
|
||||
/**
|
||||
Begin receiving NDI audio, video, and metadata frames
|
||||
*/
|
||||
bool UNDIReceiverComponent::StartReceiver(const FNDIConnectionInformation& InConnectionInformation)
|
||||
{
|
||||
if (IsValid(this->NDIMediaSource))
|
||||
{
|
||||
// Call to the Media Source's function to initialize (hopefully with valid connection information)
|
||||
if (NDIMediaSource->Initialize(InConnectionInformation, UNDIMediaReceiver::EUsage::Standalone))
|
||||
{
|
||||
// FNDIConnectionService::RegisterReceiver(this->NDIMediaSource);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
Attempt to change the connection for which to get audio, video, and metadata frame from
|
||||
*/
|
||||
void UNDIReceiverComponent::ChangeConnection(const FNDIConnectionInformation& InConnectionInformation)
|
||||
{
|
||||
// Ensure a valid source to change the connection on
|
||||
if (IsValid(this->NDIMediaSource))
|
||||
{
|
||||
// Call the underlying function
|
||||
NDIMediaSource->ChangeConnection(InConnectionInformation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
This will add a metadata frame and return immediately, having scheduled the frame asynchronously
|
||||
*/
|
||||
void UNDIReceiverComponent::SendMetadataFrame(const FString& metadata)
|
||||
{
|
||||
// Ensure a valid source to send metadata from
|
||||
if (IsValid(this->NDIMediaSource))
|
||||
{
|
||||
// Call the underlying function
|
||||
NDIMediaSource->SendMetadataFrame(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
This will setup the up-stream tally notifications. If no streams are connected, it will automatically send
|
||||
the tally state upon connection
|
||||
*/
|
||||
void UNDIReceiverComponent::SendTallyInformation(const bool& IsOnPreview, const bool& IsOnProgram)
|
||||
{
|
||||
if (IsValid(this->NDIMediaSource))
|
||||
{
|
||||
NDIMediaSource->SendTallyInformation(IsOnPreview, IsOnProgram);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to stop receiving audio, video, and metadata frame from the connected source
|
||||
*/
|
||||
void UNDIReceiverComponent::ShutdownReceiver()
|
||||
{
|
||||
if (IsValid(this->NDIMediaSource))
|
||||
{
|
||||
NDIMediaSource->Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the current framerate of the connected source
|
||||
*/
|
||||
FFrameRate UNDIReceiverComponent::GetCurrentFrameRate() const
|
||||
{
|
||||
return IsValid(NDIMediaSource) ? NDIMediaSource->GetCurrentFrameRate() : FFrameRate(60, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the current timecode of the connected source
|
||||
*/
|
||||
FTimecode UNDIReceiverComponent::GetCurrentTimecode() const
|
||||
{
|
||||
return IsValid(NDIMediaSource)
|
||||
? NDIMediaSource->GetCurrentTimecode()
|
||||
: FTimecode::FromTimespan(FTimespan::FromMilliseconds(0.0), FFrameRate(60, 1), false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the current connection information of the connected source
|
||||
*/
|
||||
FNDIConnectionInformation UNDIReceiverComponent::GetCurrentConnectionInformation() const
|
||||
{
|
||||
return IsValid(NDIMediaSource) ? NDIMediaSource->GetCurrentConnectionInformation() : FNDIConnectionInformation();
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the current performance data of the receiver while connected to the source
|
||||
*/
|
||||
FNDIReceiverPerformanceData UNDIReceiverComponent::GetPerformanceData() const
|
||||
{
|
||||
return IsValid(NDIMediaSource) ? NDIMediaSource->GetPerformanceData() : FNDIReceiverPerformanceData();
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
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 <Components/NDITriCasterExtComponent.h>
|
||||
|
||||
#include <Structures/NDIXml.h>
|
||||
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
|
||||
#include <EngineUtils.h>
|
||||
|
||||
|
||||
/**
|
||||
Parsers for TriCasterExt metadata
|
||||
*/
|
||||
|
||||
class NDIXmlElementParser_tricaster_ext : public NDIXmlElementParser
|
||||
{
|
||||
public:
|
||||
NDIXmlElementParser_tricaster_ext(UTriCasterExtComponent* TriCasterExtComponentIn)
|
||||
: TriCasterExtComponent(TriCasterExtComponentIn)
|
||||
{}
|
||||
|
||||
virtual bool ProcessOpen(const TCHAR* ElementName, const TCHAR* ElementData)
|
||||
{
|
||||
TCData.Value = FString();
|
||||
TCData.KeyValues.Empty();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override
|
||||
{
|
||||
if(FCString::Strcmp(TEXT("name"), AttributeName) == 0)
|
||||
{}
|
||||
else if(FCString::Strcmp(TEXT("value"), AttributeName) == 0)
|
||||
{
|
||||
TCData.Value = FString(AttributeValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
TCData.KeyValues.Add(FName(AttributeName), FString(AttributeValue));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessClose(const TCHAR* ElementName) override
|
||||
{
|
||||
if(TCData.Value == "ndiio")
|
||||
{
|
||||
FString* ActorNamePtr = TCData.KeyValues.Find("actor");
|
||||
FString* PropertyNamePtr = TCData.KeyValues.Find("property");
|
||||
FString* PropertyValueStrPtr = TCData.KeyValues.Find("propertyvalue");
|
||||
FString* ComponentNamePtr = TCData.KeyValues.Find("component");
|
||||
FString* EasingDurationPtr = TCData.KeyValues.Find("easing");
|
||||
|
||||
if((ActorNamePtr != nullptr) && (PropertyNamePtr != nullptr) && (PropertyValueStrPtr != nullptr))
|
||||
{
|
||||
FString PropertyBaseName, PropertyElementName;
|
||||
if(!PropertyNamePtr->Split(TEXT(":"), &PropertyBaseName, &PropertyElementName))
|
||||
PropertyBaseName = *PropertyNamePtr;
|
||||
|
||||
FTimespan EasingDuration = 0;
|
||||
if(EasingDurationPtr != nullptr)
|
||||
{
|
||||
double Seconds = FCString::Atod(**EasingDurationPtr);
|
||||
EasingDuration = FTimespan::FromSeconds(Seconds);
|
||||
}
|
||||
|
||||
for(TActorIterator<AActor> ActorItr(TriCasterExtComponent->GetWorld()); ActorItr; ++ActorItr)
|
||||
{
|
||||
AActor* Actor = *ActorItr;
|
||||
if(Actor->GetName() == *ActorNamePtr)
|
||||
{
|
||||
UObject* FoundObject = nullptr;
|
||||
FProperty* FoundProperty = nullptr;
|
||||
|
||||
if(ComponentNamePtr != nullptr)
|
||||
{
|
||||
TInlineComponentArray<UActorComponent*> PrimComponents;
|
||||
Actor->GetComponents(PrimComponents, true);
|
||||
for(auto& CompIt : PrimComponents)
|
||||
{
|
||||
if(CompIt->GetName() == *ComponentNamePtr)
|
||||
{
|
||||
FProperty* Property = CompIt->GetClass()->FindPropertyByName(*PropertyBaseName);
|
||||
if(Property)
|
||||
{
|
||||
FoundObject = CompIt;
|
||||
FoundProperty = Property;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FProperty* ActorProperty = Actor->GetClass()->FindPropertyByName(*PropertyBaseName);
|
||||
if(ActorProperty)
|
||||
{
|
||||
FoundObject = Actor;
|
||||
FoundProperty = ActorProperty;
|
||||
}
|
||||
else
|
||||
{
|
||||
TInlineComponentArray<UActorComponent*> PrimComponents;
|
||||
Actor->GetComponents(PrimComponents, true);
|
||||
|
||||
for(auto& CompIt : PrimComponents)
|
||||
{
|
||||
FProperty* CompProperty = CompIt->GetClass()->FindPropertyByName(*PropertyBaseName);
|
||||
if(CompProperty)
|
||||
{
|
||||
FoundObject = CompIt;
|
||||
FoundProperty = CompProperty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(FoundObject && FoundProperty)
|
||||
{
|
||||
TriCasterExtComponent->TriCasterExt(Actor, FoundObject, FoundProperty, PropertyElementName, *PropertyValueStrPtr, EasingDuration);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TriCasterExtComponent->TriCasterExtCustom(TCData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
UTriCasterExtComponent* TriCasterExtComponent;
|
||||
|
||||
FTriCasterExt TCData;
|
||||
};
|
||||
// <tricaster_ext name="net1" value="ndiio" actor="LightSource" property="Intensity" propertyvalue="1.234" />
|
||||
// <tricaster_ext name="net1" value="ndiio" actor="LightSource" component="LightComponent0" property="Intensity" propertyvalue="1.234" />
|
||||
// <tricaster_ext name="net1" value="ndiio" actor="LightSource" property="RelativeLocation" propertyvalue="(X=1,Y=2,Z=3)" />
|
||||
// <tricaster_ext name="net1" value="ndiio" actor="LightSource" property="RelativeLocation" propertyvalue="(X=1)" />
|
||||
// <tricaster_ext name="net1" value="ndiio" actor="LightSource" property="RelativeLocation:Y" propertyvalue="2" easing="5.3"/>
|
||||
|
||||
|
||||
|
||||
UTriCasterExtComponent::UTriCasterExtComponent()
|
||||
{
|
||||
this->bWantsInitializeComponent = true;
|
||||
|
||||
this->PrimaryComponentTick.bAllowTickOnDedicatedServer = false;
|
||||
this->PrimaryComponentTick.bCanEverTick = true;
|
||||
this->PrimaryComponentTick.bHighPriority = true;
|
||||
this->PrimaryComponentTick.bRunOnAnyThread = false;
|
||||
this->PrimaryComponentTick.bStartWithTickEnabled = true;
|
||||
this->PrimaryComponentTick.bTickEvenWhenPaused = true;
|
||||
|
||||
this->NDIMetadataParser = MakeShareable(new NDIXmlParser());
|
||||
NDIMetadataParser->AddElementParser("tricaster_ext", MakeShareable(new NDIXmlElementParser_tricaster_ext(this)));
|
||||
}
|
||||
|
||||
UTriCasterExtComponent::~UTriCasterExtComponent()
|
||||
{}
|
||||
|
||||
void UTriCasterExtComponent::InitializeComponent()
|
||||
{
|
||||
Super::InitializeComponent();
|
||||
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// Ensure the TriCasterExt component is subscribed to the sender receiving metadata
|
||||
this->NDIMediaSource->OnSenderMetaDataReceived.RemoveAll(this);
|
||||
this->NDIMediaSource->OnSenderMetaDataReceived.AddDynamic(this, &UTriCasterExtComponent::ReceiveMetaDataFromSender);
|
||||
}
|
||||
}
|
||||
|
||||
bool UTriCasterExtComponent::Initialize(UNDIMediaSender* InMediaSource)
|
||||
{
|
||||
// is the media source already set?
|
||||
if (this->NDIMediaSource == nullptr && InMediaSource != nullptr)
|
||||
{
|
||||
// we passed validation, so set the media source
|
||||
this->NDIMediaSource = InMediaSource;
|
||||
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// Ensure the TriCasterExt component is subscribed to the sender receiving metadata
|
||||
this->NDIMediaSource->OnSenderMetaDataReceived.RemoveAll(this);
|
||||
this->NDIMediaSource->OnSenderMetaDataReceived.AddDynamic(this, &UTriCasterExtComponent::ReceiveMetaDataFromSender);
|
||||
}
|
||||
}
|
||||
|
||||
// did we pass validation
|
||||
return InMediaSource != nullptr && InMediaSource == NDIMediaSource;
|
||||
}
|
||||
|
||||
void UTriCasterExtComponent::TriCasterExt(AActor* Actor, UObject* Object, FProperty* Property, FString PropertyElementName, FString PropertyValueStr, FTimespan EasingDuration)
|
||||
{
|
||||
if(Actor && Object && Property)
|
||||
{
|
||||
FTriCasterExtInterp Interp;
|
||||
Interp.Actor = Actor;
|
||||
Interp.Object = Object;
|
||||
Interp.Property = Property;
|
||||
Interp.PropertyElementName = PropertyElementName;
|
||||
Interp.PropertyValueStr = PropertyValueStr;
|
||||
Interp.EasingDuration = EasingDuration.GetTotalSeconds();
|
||||
Interp.EasingRemaining = Interp.EasingDuration;
|
||||
|
||||
TriCasterExtInterp.Add(Interp);
|
||||
}
|
||||
|
||||
OnTriCasterExt.Broadcast(Actor, Object, PropertyElementName, PropertyValueStr, EasingDuration);
|
||||
}
|
||||
|
||||
void UTriCasterExtComponent::TriCasterExtCustom(const FTriCasterExt& TCData)
|
||||
{
|
||||
OnTriCasterExtCustom.Broadcast(TCData);
|
||||
}
|
||||
|
||||
|
||||
void UTriCasterExtComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
for(int32 i = 0; i < TriCasterExtInterp.Num(); ++i)
|
||||
{
|
||||
FTriCasterExtInterp& Interp = TriCasterExtInterp[i];
|
||||
|
||||
float EasingDelta = FMath::Min(Interp.EasingRemaining, DeltaTime);
|
||||
|
||||
void* Data = Interp.Property->ContainerPtrToValuePtr<void>(Interp.Object);
|
||||
if(Data)
|
||||
{
|
||||
bool Done = false;
|
||||
|
||||
#if WITH_EDITOR
|
||||
Interp.Object->PreEditChange(Interp.Property);
|
||||
Interp.Actor->PreEditChange(Interp.Property);
|
||||
#endif
|
||||
|
||||
if(FNumericProperty* NumericProperty = CastField<FNumericProperty>(Interp.Property))
|
||||
{
|
||||
double PropertyValue = NumericProperty->GetFloatingPointPropertyValue(Data);
|
||||
double TargetValue = FCString::Atod(*Interp.PropertyValueStr);
|
||||
|
||||
double EasingFrac = (Interp.EasingRemaining > 0) ? (EasingDelta / Interp.EasingRemaining) : 1;
|
||||
double EasingInterp = 3*EasingFrac - 3*EasingFrac*EasingFrac + EasingFrac*EasingFrac*EasingFrac;
|
||||
|
||||
double NewValue = PropertyValue * (1 - EasingInterp) + TargetValue * EasingInterp;
|
||||
NumericProperty->SetFloatingPointPropertyValue(Data, NewValue);
|
||||
Done = true;
|
||||
}
|
||||
else if(FStructProperty* StructProperty = CastField<FStructProperty>(Interp.Property))
|
||||
{
|
||||
FProperty* FieldProperty = FindFProperty<FProperty>(StructProperty->Struct, *(Interp.PropertyElementName));
|
||||
if(FNumericProperty* StructNumericProperty = CastField<FNumericProperty>(FieldProperty))
|
||||
{
|
||||
void* FieldData = FieldProperty->ContainerPtrToValuePtr<void>(Data);
|
||||
double PropertyValue = StructNumericProperty->GetFloatingPointPropertyValue(FieldData);
|
||||
double TargetValue = FCString::Atod(*Interp.PropertyValueStr);
|
||||
|
||||
double EasingFrac = (Interp.EasingRemaining > 0) ? (EasingDelta / Interp.EasingRemaining) : 1;
|
||||
double EasingInterp = 3*EasingFrac - 3*EasingFrac*EasingFrac + EasingFrac*EasingFrac*EasingFrac;
|
||||
|
||||
double NewValue = PropertyValue * (1 - EasingInterp) + TargetValue * EasingInterp;
|
||||
StructNumericProperty->SetFloatingPointPropertyValue(FieldData, NewValue);
|
||||
Done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!Done)
|
||||
{
|
||||
FString ImportText;
|
||||
if(!Interp.PropertyElementName.IsEmpty())
|
||||
ImportText = "(" + Interp.PropertyElementName + "=" + Interp.PropertyValueStr + ")";
|
||||
else
|
||||
ImportText = Interp.PropertyValueStr;
|
||||
Interp.Property->ImportText_Direct(*ImportText, Data, Interp.Object, 0);
|
||||
}
|
||||
|
||||
UActorComponent* ActorComponent = Cast<UActorComponent>(Interp.Object);
|
||||
if(ActorComponent)
|
||||
{
|
||||
if((Interp.Property->GetFName() == TEXT("RelativeLocation")) ||
|
||||
(Interp.Property->GetFName() == TEXT("RelativeRotation")) ||
|
||||
(Interp.Property->GetFName() == TEXT("RelativeScale3D")))
|
||||
{
|
||||
ActorComponent->UpdateComponentToWorld();
|
||||
}
|
||||
}
|
||||
#if (ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION < 3) // Before 5.3
|
||||
if(Interp.Property->HasAnyPropertyFlags(CPF_Interp))
|
||||
Interp.Object->PostInterpChange(Interp.Property);
|
||||
#endif
|
||||
|
||||
#if WITH_EDITOR
|
||||
TArray<const UObject*> ModifiedObjects;
|
||||
ModifiedObjects.Add(Interp.Actor);
|
||||
FPropertyChangedEvent PropertyChangedEvent(Interp.Property, EPropertyChangeType::ValueSet, MakeArrayView(ModifiedObjects));
|
||||
FEditPropertyChain PropertyChain;
|
||||
PropertyChain.AddHead(Interp.Property);
|
||||
FPropertyChangedChainEvent PropertyChangedChainEvent(PropertyChain, PropertyChangedEvent);
|
||||
|
||||
Interp.Object->PostEditChangeChainProperty(PropertyChangedChainEvent);
|
||||
Interp.Actor->PostEditChangeChainProperty(PropertyChangedChainEvent);
|
||||
#endif
|
||||
}
|
||||
|
||||
Interp.EasingRemaining -= EasingDelta;
|
||||
if(Interp.EasingRemaining == 0)
|
||||
TriCasterExtInterp.RemoveAtSwap(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UTriCasterExtComponent::ReceiveMetaDataFromSender(UNDIMediaSender* Sender, FString Data)
|
||||
{
|
||||
FText OutErrorMessage;
|
||||
int32 OutErrorLineNumber;
|
||||
|
||||
FFastXml::ParseXmlFile(this->NDIMetadataParser.Get(),
|
||||
nullptr, // XmlFilePath
|
||||
Data.GetCharArray().GetData(), // XmlFileContents
|
||||
nullptr, // FeedbackContext
|
||||
false, // bShowSlowTaskDialog
|
||||
false, // bShowCancelButton
|
||||
OutErrorMessage, // OutErrorMessage
|
||||
OutErrorLineNumber // OutErrorLineNumber
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
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 <Components/NDIViewportCaptureComponent.h>
|
||||
#include <Rendering/RenderingCommon.h>
|
||||
#include <SceneView.h>
|
||||
#include <SceneViewExtension.h>
|
||||
#include <CanvasTypes.h>
|
||||
#include <EngineModule.h>
|
||||
#include <LegacyScreenPercentageDriver.h>
|
||||
#include <RenderResource.h>
|
||||
#include <UnrealClient.h>
|
||||
#include <Engine/Engine.h>
|
||||
#include <EngineUtils.h>
|
||||
#include <Misc/CoreDelegates.h>
|
||||
#include <Engine/TextureRenderTarget2D.h>
|
||||
#include <UObject/Package.h>
|
||||
|
||||
|
||||
UNDIViewportCaptureComponent::UNDIViewportCaptureComponent(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
this->bWantsInitializeComponent = true;
|
||||
this->CaptureSource = ESceneCaptureSource::SCS_FinalToneCurveHDR;
|
||||
this->PostProcessSettings.bOverride_DepthOfFieldFocalDistance = true;
|
||||
this->PostProcessSettings.DepthOfFieldFocalDistance = 10000.f;
|
||||
}
|
||||
|
||||
UNDIViewportCaptureComponent::~UNDIViewportCaptureComponent()
|
||||
{}
|
||||
|
||||
void UNDIViewportCaptureComponent::InitializeComponent()
|
||||
{
|
||||
Super::InitializeComponent();
|
||||
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// define default capture values
|
||||
const auto& capture_size = !bOverrideBroadcastSettings ? NDIMediaSource->GetFrameSize() : CaptureSize;
|
||||
const auto& capture_rate = !bOverrideBroadcastSettings ? NDIMediaSource->GetFrameRate() : CaptureRate;
|
||||
|
||||
// change the capture sizes as necessary
|
||||
ChangeCaptureSettings(capture_size, capture_rate);
|
||||
|
||||
// ensure we are subscribed to the broadcast configuration changed event
|
||||
this->NDIMediaSource->OnBroadcastConfigurationChanged.RemoveAll(this);
|
||||
this->NDIMediaSource->OnBroadcastConfigurationChanged.AddDynamic(
|
||||
this, &UNDIViewportCaptureComponent::OnBroadcastConfigurationChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void UNDIViewportCaptureComponent::UninitializeComponent()
|
||||
{
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
if (IsValid(TextureTarget))
|
||||
{
|
||||
NDIMediaSource->ChangeVideoTexture(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Super::UninitializeComponent();
|
||||
}
|
||||
|
||||
bool UNDIViewportCaptureComponent::Initialize(UNDIMediaSender* InMediaSource)
|
||||
{
|
||||
// is the media source already set?
|
||||
if (this->NDIMediaSource == nullptr && InMediaSource != nullptr)
|
||||
{
|
||||
// we passed validation, so set the media source
|
||||
this->NDIMediaSource = InMediaSource;
|
||||
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// define default capture values
|
||||
const auto& capture_size = !bOverrideBroadcastSettings ? NDIMediaSource->GetFrameSize() : CaptureSize;
|
||||
const auto& capture_rate = !bOverrideBroadcastSettings ? NDIMediaSource->GetFrameRate() : CaptureRate;
|
||||
|
||||
// change the capture sizes as necessary
|
||||
ChangeCaptureSettings(capture_size, capture_rate);
|
||||
|
||||
// ensure we are subscribed to the broadcast configuration changed event
|
||||
this->NDIMediaSource->OnBroadcastConfigurationChanged.RemoveAll(this);
|
||||
this->NDIMediaSource->OnBroadcastConfigurationChanged.AddDynamic(
|
||||
this, &UNDIViewportCaptureComponent::OnBroadcastConfigurationChanged);
|
||||
}
|
||||
}
|
||||
|
||||
// did we pass validation
|
||||
return InMediaSource != nullptr && InMediaSource == NDIMediaSource;
|
||||
}
|
||||
|
||||
/**
|
||||
Changes the name of the sender object as seen on the network for remote connections
|
||||
|
||||
@param InSourceName The new name of the source to be identified as on the network
|
||||
*/
|
||||
void UNDIViewportCaptureComponent::ChangeSourceName(const FString& InSourceName)
|
||||
{
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->ChangeSourceName(InSourceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to change the Broadcast information associated with this media object
|
||||
|
||||
@param InConfiguration The new configuration to broadcast
|
||||
*/
|
||||
void UNDIViewportCaptureComponent::ChangeBroadcastConfiguration(const FNDIBroadcastConfiguration& InConfiguration)
|
||||
{
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->ChangeBroadcastConfiguration(InConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to change the RenderTarget used in sending video frames over NDI
|
||||
|
||||
@param BroadcastTexture The texture to use as video, while broadcasting over NDI
|
||||
*/
|
||||
void UNDIViewportCaptureComponent::ChangeBroadcastTexture(UTextureRenderTarget2D* BroadcastTexture)
|
||||
{
|
||||
// ensure we have some thread-safety
|
||||
FScopeLock Lock(&UpdateRenderContext);
|
||||
|
||||
this->TextureTarget = BroadcastTexture;
|
||||
}
|
||||
|
||||
/**
|
||||
Change the capture settings of the viewport capture
|
||||
|
||||
@param InCaptureSize The Capture size of the frame to capture of the viewport
|
||||
@param InCaptureRate A framerate at which to capture frames of the viewport
|
||||
*/
|
||||
void UNDIViewportCaptureComponent::ChangeCaptureSettings(FIntPoint InCaptureSize, FFrameRate InCaptureRate)
|
||||
{
|
||||
// clamp our viewport capture size
|
||||
int32 capture_width = FMath::Max(InCaptureSize.X, 64);
|
||||
int32 capture_height = FMath::Max(InCaptureSize.Y, 64);
|
||||
|
||||
// set the capture size
|
||||
this->CaptureSize = FIntPoint(capture_width, capture_height);
|
||||
|
||||
// set the capture rate
|
||||
this->CaptureRate = InCaptureRate;
|
||||
|
||||
// clamp the maximum capture rate to something reasonable
|
||||
float capture_rate_max = 1 / 1000.0f;
|
||||
float capture_rate = CaptureRate.Denominator / (float)CaptureRate.Numerator;
|
||||
|
||||
// set the primary tick interval to the sensible capture rate
|
||||
this->PrimaryComponentTick.TickInterval = capture_rate >= capture_rate_max ? capture_rate : -1.0f;
|
||||
|
||||
// ensure we have some thread-safety
|
||||
FScopeLock Lock(&UpdateRenderContext);
|
||||
|
||||
if (!IsValid(this->TextureTarget))
|
||||
{
|
||||
this->TextureTarget = NewObject<UTextureRenderTarget2D>(
|
||||
GetTransientPackage(), UTextureRenderTarget2D::StaticClass(), NAME_None, RF_Transient | RF_MarkAsNative);
|
||||
this->TextureTarget->UpdateResource();
|
||||
}
|
||||
this->TextureTarget->ResizeTarget(this->CaptureSize.X, this->CaptureSize.Y);
|
||||
}
|
||||
|
||||
/**
|
||||
Determines the current tally information. If you specify a timeout then it will wait until it has
|
||||
changed, otherwise it will simply poll it and return the current tally immediately
|
||||
|
||||
@param IsOnPreview - A state indicating whether this source in on preview of a receiver
|
||||
@param IsOnProgram - A state indicating whether this source is on program of a receiver
|
||||
*/
|
||||
void UNDIViewportCaptureComponent::GetTallyInformation(bool& IsOnPreview, bool& IsOnProgram)
|
||||
{
|
||||
// Initialize the properties
|
||||
IsOnPreview = false;
|
||||
IsOnProgram = false;
|
||||
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->GetTallyInformation(IsOnPreview, IsOnProgram, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the current number of receivers connected to this source. This can be used to avoid rendering
|
||||
when nothing is connected to the video source. which can significantly improve the efficiency if
|
||||
you want to make a lot of sources available on the network
|
||||
|
||||
@param Result The total number of connected receivers attached to the broadcast of this object
|
||||
*/
|
||||
void UNDIViewportCaptureComponent::GetNumberOfConnections(int32& Result)
|
||||
{
|
||||
// Initialize the property
|
||||
Result = 0;
|
||||
|
||||
// validate the Media Source object
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
// call the media source implementation of the function
|
||||
NDIMediaSource->GetNumberOfConnections(Result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 6)) // 5.6 or later
|
||||
void UNDIViewportCaptureComponent::UpdateSceneCaptureContents(FSceneInterface* Scene, ISceneRenderBuilder& SceneRenderBuilder)
|
||||
#else
|
||||
void UNDIViewportCaptureComponent::UpdateSceneCaptureContents(FSceneInterface* Scene)
|
||||
#endif
|
||||
{
|
||||
// ensure we have some thread-safety
|
||||
FScopeLock Lock(&UpdateRenderContext);
|
||||
|
||||
if (TextureTarget == nullptr)
|
||||
return;
|
||||
|
||||
if (IsValid(NDIMediaSource))
|
||||
{
|
||||
NDIMediaSource->ChangeVideoTexture(TextureTarget);
|
||||
|
||||
// Some capture sources treat alpha as opacity, some sources use transparency.
|
||||
// Alpha in NDI is opacity. Reverse the alpha mapping to always get opacity.
|
||||
bool flip_alpha = (CaptureSource == SCS_SceneColorHDR) || (CaptureSource == SCS_SceneColorHDRNoAlpha) ||
|
||||
(CaptureSource == SCS_SceneDepth) || (CaptureSource == SCS_Normal) ||
|
||||
(CaptureSource == SCS_BaseColor);
|
||||
if (flip_alpha == false)
|
||||
NDIMediaSource->ChangeAlphaRemap(AlphaMin, AlphaMax);
|
||||
else
|
||||
NDIMediaSource->ChangeAlphaRemap(AlphaMax, AlphaMin);
|
||||
|
||||
// Do the actual capturing
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 6)) // 5.6 or later
|
||||
Super::UpdateSceneCaptureContents(Scene, SceneRenderBuilder);
|
||||
#else
|
||||
Super::UpdateSceneCaptureContents(Scene);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void UNDIViewportCaptureComponent::OnBroadcastConfigurationChanged(UNDIMediaSender* Sender)
|
||||
{
|
||||
// If we are not overriding the broadcast settings and the sender is valid
|
||||
if (!bOverrideBroadcastSettings && IsValid(Sender))
|
||||
{
|
||||
// change the capture sizes as necessary
|
||||
ChangeCaptureSettings(Sender->GetFrameSize(), Sender->GetFrameRate());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user