Added ndi receiver and sdi outputs
@@ -14,6 +14,7 @@ InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent")
|
||||
[/Script/MediaFrameworkUtilities.MediaProfileSettings]
|
||||
bApplyInCommandlet=False
|
||||
+MediaSourceProxy=/Game/TheStudio/Media/Proxies/ProxyMediaSource_CineCamera.ProxyMediaSource_CineCamera
|
||||
+MediaSourceProxy=/Game/TheStudio/Media/Proxies/ProxyMediaSource_CameraSDI3.ProxyMediaSource_CameraSDI3
|
||||
+MediaOutputProxy=/Game/TheStudio/Media/Proxies/ProxyMediaOutput_Monitor.ProxyMediaOutput_Monitor
|
||||
+MediaOutputProxy=/Game/TheStudio/Media/Proxies/ProxyMediaOutput_Multiview.ProxyMediaOutput_Multiview
|
||||
StartupMediaProfile=/Game/TheStudio/Media/Profiles/MediaProfile_Production.MediaProfile_Production
|
||||
|
||||
BIN
Content/TheStudio/Media/Bundles/MB_CameraSDI2Input.uasset
Normal file
BIN
Content/TheStudio/Media/NDI/BP_NDIReceiver.uasset
Normal file
BIN
Content/TheStudio/Media/NDI/NDIMediaReceiver.uasset
Normal file
BIN
Content/TheStudio/Media/NDI/NDIMediaSender.uasset
Normal file
BIN
Content/TheStudio/Media/NDI/NewNDIMediaTexture2D.uasset
Normal file
BIN
Content/TheStudio/Media/NDI/NewNDIMediaTexture2D_Mat.uasset
Normal file
BIN
Plugins/NDIIO/Content/Editor/Icons/NDIBroadcastActorIcon_x20.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDIBroadcastActorIcon_x64.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDIReceiveActorIcon_x20.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDIReceiveActorIcon_x64.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDIReceiverIcon_x20.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDIReceiverIcon_x64.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDISenderIcon_x20.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDISenderIcon_x64.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDISoundWaveIcon_x20.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDISoundWaveIcon_x64.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDIVideoTextureIcon_x20.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
Plugins/NDIIO/Content/Editor/Icons/NDIVideoTextureIcon_x64.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Plugins/NDIIO/Content/Materials/NDI_UI_SourceMaterial.uasset
Normal file
BIN
Plugins/NDIIO/Content/Materials/NDI_Unlit_SourceMaterial.uasset
Normal file
BIN
Plugins/NDIIO/Documentation/Quick Start.pdf
Normal file
BIN
Plugins/NDIIO/Documentation/Release Notes.pdf
Normal file
58
Plugins/NDIIO/NDIIOPlugin.uplugin
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 11,
|
||||
"VersionName": "3.8",
|
||||
"FriendlyName": "NDI IO Plugin",
|
||||
"Description": "Exposes Core NDI Support for integration into Unreal Engine Applications",
|
||||
"Category": "Virtual Production",
|
||||
"CreatedBy": "Vizrt NDI AB",
|
||||
"CreatedByURL": "https://www.ndi.video",
|
||||
"DocsURL": "",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "",
|
||||
"EngineVersion": "5.5.0",
|
||||
"CanContainContent": true,
|
||||
"Installed": true,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "NDIIO",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Linux",
|
||||
"LinuxArm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "NDIIOEditor",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "PostEngineInit",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Linux",
|
||||
"LinuxArm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "NDIIOShaders",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "PostConfigInit",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Linux",
|
||||
"LinuxArm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "MediaIOFramework",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "MediaFrameworkUtilities",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Plugins/NDIIO/Resources/Icon128.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
237
Plugins/NDIIO/Shaders/Private/NDIIOShaders.usf
Normal file
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
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 "/Engine/Public/Platform.ush"
|
||||
#include "/Engine/Generated/GeneratedUniformBuffers.ush"
|
||||
#include "/Engine/Private/GammaCorrectionCommon.ush"
|
||||
|
||||
|
||||
// Matches FNDIIOShaderPS::EColorCorrection enum
|
||||
#define COLOR_CORRECTION_None 0
|
||||
#define COLOR_CORRECTION_sRGBToLinear 1
|
||||
#define COLOR_CORRECTION_LinearTosRGB 2
|
||||
|
||||
|
||||
void NDIIOMainVS(
|
||||
in float4 InPosition : ATTRIBUTE0,
|
||||
in float2 InUV : ATTRIBUTE1,
|
||||
out float4 OutPosition : SV_POSITION,
|
||||
out float2 OutUV : TEXCOORD0)
|
||||
{
|
||||
OutPosition = InPosition;
|
||||
OutUV = InUV;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Shader from 8 bits RGBA to 8 bits UYVY
|
||||
void NDIIOBGRAtoUYVYPS(
|
||||
float4 InPosition : SV_POSITION,
|
||||
float2 InUV : TEXCOORD0,
|
||||
out float4 OutColor : SV_Target0)
|
||||
{
|
||||
float3x3 RGBToYCbCrMat =
|
||||
{
|
||||
0.18300, 0.61398, 0.06201,
|
||||
-0.10101, -0.33899, 0.43900,
|
||||
0.43902, -0.39900, -0.04001
|
||||
};
|
||||
float3 RGBToYCbCrVec = { 0.06302, 0.50198, 0.50203 };
|
||||
|
||||
float2 UV = NDIIOShaderUB.UVOffset + InUV * NDIIOShaderUB.UVScale;
|
||||
float2 UVdelta = NDIIOShaderUB.UVScale * float2(2.0f/NDIIOShaderUB.OutputWidth, 1.0f/NDIIOShaderUB.OutputHeight);
|
||||
float2 UV0 = UV + float2(-1.0f/4.0f, 0.0f) * UVdelta;
|
||||
float2 UV1 = UV + float2( 1.0f/4.0f, 0.0f) * UVdelta;
|
||||
|
||||
float3 YUV0 = RGBToYCbCrVec;
|
||||
float3 YUV1 = RGBToYCbCrVec;
|
||||
|
||||
if(all(UV0 >= float2(0,0)) && all(UV0 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA0 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV0);
|
||||
float3 RGB0 = (NDIIOShaderUB.ColorCorrection == COLOR_CORRECTION_LinearTosRGB) ? LinearToSrgb(RGBA0.xyz) : RGBA0.xyz;
|
||||
YUV0 = mul(RGBToYCbCrMat, RGB0) + RGBToYCbCrVec;
|
||||
}
|
||||
if(all(UV1 >= float2(0,0)) && all(UV1 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA1 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV1);
|
||||
float3 RGB1 = (NDIIOShaderUB.ColorCorrection == COLOR_CORRECTION_LinearTosRGB) ? LinearToSrgb(RGBA1.xyz) : RGBA1.xyz;
|
||||
YUV1 = mul(RGBToYCbCrMat, RGB1) + RGBToYCbCrVec;
|
||||
}
|
||||
|
||||
OutColor.xz = (YUV0.zy + YUV1.zy) / 2.f;
|
||||
OutColor.y = YUV0.x;
|
||||
OutColor.w = YUV1.x;
|
||||
}
|
||||
|
||||
|
||||
// Shader from 8 bits RGBA to 8 bits Alpha suitable for UYVA; even-numbered lines
|
||||
void NDIIOBGRAtoAlphaEvenPS(
|
||||
float4 InPosition : SV_POSITION,
|
||||
float2 InUV : TEXCOORD0,
|
||||
out float4 OutColor : SV_Target0)
|
||||
{
|
||||
float2 UV = NDIIOShaderUB.UVOffset + InUV * NDIIOShaderUB.UVScale;
|
||||
float2 UVdelta = NDIIOShaderUB.UVScale * float2(4.0f/NDIIOShaderUB.OutputWidth, 2.0f/NDIIOShaderUB.OutputHeight);
|
||||
float2 UV0 = UV + float2(-3.0f/8.0f, -1.0f/4.0f) * UVdelta;
|
||||
float2 UV1 = UV + float2(-1.0f/8.0f, -1.0f/4.0f) * UVdelta;
|
||||
float2 UV2 = UV + float2( 1.0f/8.0f, -1.0f/4.0f) * UVdelta;
|
||||
float2 UV3 = UV + float2( 3.0f/8.0f, -1.0f/4.0f) * UVdelta;
|
||||
|
||||
float A0 = 0.0f;
|
||||
float A1 = 0.0f;
|
||||
float A2 = 0.0f;
|
||||
float A3 = 0.0f;
|
||||
|
||||
if(all(UV0 >= float2(0,0)) && all(UV0 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA0 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV0);
|
||||
A0 = RGBA0.w * NDIIOShaderUB.AlphaScale + NDIIOShaderUB.AlphaOffset;
|
||||
}
|
||||
if(all(UV1 >= float2(0,0)) && all(UV1 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA1 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV1);
|
||||
A1 = RGBA1.w * NDIIOShaderUB.AlphaScale + NDIIOShaderUB.AlphaOffset;
|
||||
}
|
||||
if(all(UV2 >= float2(0,0)) && all(UV2 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA2 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV2);
|
||||
A2 = RGBA2.w * NDIIOShaderUB.AlphaScale + NDIIOShaderUB.AlphaOffset;
|
||||
}
|
||||
if(all(UV3 >= float2(0,0)) && all(UV3 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA3 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV3);
|
||||
A3 = RGBA3.w * NDIIOShaderUB.AlphaScale + NDIIOShaderUB.AlphaOffset;
|
||||
}
|
||||
|
||||
OutColor.xyzw = float4(A2, A1, A0, A3);
|
||||
}
|
||||
|
||||
|
||||
// Shader from 8 bits RGBA to 8 bits Alpha suitable for UYVA; odd-numbered lines
|
||||
void NDIIOBGRAtoAlphaOddPS(
|
||||
float4 InPosition : SV_POSITION,
|
||||
float2 InUV : TEXCOORD0,
|
||||
out float4 OutColor : SV_Target0)
|
||||
{
|
||||
float2 UV = NDIIOShaderUB.UVOffset + InUV * NDIIOShaderUB.UVScale;
|
||||
float2 UVdelta = NDIIOShaderUB.UVScale * float2(4.0f/NDIIOShaderUB.OutputWidth, 2.0f/NDIIOShaderUB.OutputHeight);
|
||||
float2 UV0 = UV + float2(-3.0f/8.0f, 1.0f/4.0f) * UVdelta;
|
||||
float2 UV1 = UV + float2(-1.0f/8.0f, 1.0f/4.0f) * UVdelta;
|
||||
float2 UV2 = UV + float2( 1.0f/8.0f, 1.0f/4.0f) * UVdelta;
|
||||
float2 UV3 = UV + float2( 3.0f/8.0f, 1.0f/4.0f) * UVdelta;
|
||||
|
||||
float A0 = 0.0f;
|
||||
float A1 = 0.0f;
|
||||
float A2 = 0.0f;
|
||||
float A3 = 0.0f;
|
||||
|
||||
if(all(UV0 >= float2(0,0)) && all(UV0 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA0 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV0);
|
||||
A0 = RGBA0.w * NDIIOShaderUB.AlphaScale + NDIIOShaderUB.AlphaOffset;
|
||||
}
|
||||
if(all(UV1 >= float2(0,0)) && all(UV1 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA1 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV1);
|
||||
A1 = RGBA1.w * NDIIOShaderUB.AlphaScale + NDIIOShaderUB.AlphaOffset;
|
||||
}
|
||||
if(all(UV2 >= float2(0,0)) && all(UV2 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA2 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV2);
|
||||
A2 = RGBA2.w * NDIIOShaderUB.AlphaScale + NDIIOShaderUB.AlphaOffset;
|
||||
}
|
||||
if(all(UV3 >= float2(0,0)) && all(UV3 < float2(1,1)))
|
||||
{
|
||||
float4 RGBA3 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, UV3);
|
||||
A3 = RGBA3.w * NDIIOShaderUB.AlphaScale + NDIIOShaderUB.AlphaOffset;
|
||||
}
|
||||
|
||||
OutColor.xyzw = float4(A2, A1, A0, A3);
|
||||
}
|
||||
|
||||
|
||||
// Shader from 8 bits UYVY to 8 bits RGBA (alpha set to 1)
|
||||
void NDIIOUYVYtoBGRAPS(
|
||||
float4 InPosition : SV_POSITION,
|
||||
float2 InUV : TEXCOORD0,
|
||||
out float4 OutColor : SV_Target0)
|
||||
{
|
||||
float3x3 YCbCrToRGBMat =
|
||||
{
|
||||
1.16414, -0.0011, 1.7923,
|
||||
1.16390, -0.2131, -0.5342,
|
||||
1.16660, 2.1131, -0.0001
|
||||
};
|
||||
float3 YCbCrToRGBVec = { -0.9726, 0.3018, -1.1342 };
|
||||
|
||||
if(all(InUV >= float2(0,0)) && all(InUV < float2(1,1)) && all(InUV >= float2(0,0)) && all(InUV < float2(1,1)))
|
||||
{
|
||||
float4 UYVY = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerP, InUV);
|
||||
|
||||
float PosX = 2.0f * InUV.x * NDIIOShaderUB.InputWidth;
|
||||
float4 YUVA;
|
||||
|
||||
float FracX = floor(PosX) % 2.0f;
|
||||
YUVA.x = (1 - FracX) * UYVY.y + FracX * UYVY.w;
|
||||
YUVA.yz = UYVY.zx;
|
||||
YUVA.w = 1;
|
||||
|
||||
OutColor.xyz = mul(YCbCrToRGBMat, YUVA.xyz) + YCbCrToRGBVec;
|
||||
if(NDIIOShaderUB.ColorCorrection == COLOR_CORRECTION_sRGBToLinear)
|
||||
OutColor.xyz = sRGBToLinear(OutColor.xyz);
|
||||
OutColor.w = YUVA.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
OutColor.xyz = YCbCrToRGBVec;
|
||||
if(NDIIOShaderUB.ColorCorrection == COLOR_CORRECTION_sRGBToLinear)
|
||||
OutColor.xyz = sRGBToLinear(OutColor.xyz);
|
||||
OutColor.w = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Shader from 8 bits UYVA to 8 bits RGBA
|
||||
void NDIIOUYVAtoBGRAPS(
|
||||
float4 InPosition : SV_POSITION,
|
||||
float2 InUV : TEXCOORD0,
|
||||
out float4 OutColor : SV_Target0)
|
||||
{
|
||||
float3x3 YCbCrToRGBMat =
|
||||
{
|
||||
1.16414, -0.0011, 1.7923,
|
||||
1.16390, -0.2131, -0.5342,
|
||||
1.16660, 2.1131, -0.0001
|
||||
};
|
||||
float3 YCbCrToRGBVec = { -0.9726, 0.3018, -1.1342 };
|
||||
|
||||
if(all(InUV >= float2(0,0)) && all(InUV < float2(1,1)) && all(InUV >= float2(0,0)) && all(InUV < float2(1,1)))
|
||||
{
|
||||
float4 UYVY = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerP, InUV);
|
||||
float Alpha = NDIIOShaderUB.InputAlphaTarget.Sample(NDIIOShaderUB.SamplerP, InUV).w;
|
||||
|
||||
float PosX = 2.0f * InUV.x * NDIIOShaderUB.InputWidth;
|
||||
float4 YUVA;
|
||||
|
||||
float FracX = floor(PosX) % 2.0f;
|
||||
YUVA.x = (1 - FracX) * UYVY.y + FracX * UYVY.w;
|
||||
YUVA.yz = UYVY.zx;
|
||||
YUVA.w = Alpha;
|
||||
|
||||
OutColor.xyz = mul(YCbCrToRGBMat, YUVA.xyz) + YCbCrToRGBVec;
|
||||
if(NDIIOShaderUB.ColorCorrection == COLOR_CORRECTION_sRGBToLinear)
|
||||
OutColor.xyz = sRGBToLinear(OutColor.xyz);
|
||||
OutColor.w = YUVA.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
OutColor.xyz = YCbCrToRGBVec;
|
||||
if(NDIIOShaderUB.ColorCorrection == COLOR_CORRECTION_sRGBToLinear)
|
||||
OutColor.xyz = sRGBToLinear(OutColor.xyz);
|
||||
OutColor.w = 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
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 <Actors/NDIBroadcastActor.h>
|
||||
|
||||
|
||||
|
||||
ANDIBroadcastActor::ANDIBroadcastActor(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
|
||||
this->ViewportCaptureComponent = ObjectInitializer.CreateDefaultSubobject<UNDIViewportCaptureComponent>(this, TEXT("ViewportCaptureComponent"));
|
||||
this->ViewportCaptureComponent->AttachToComponent(this->RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
|
||||
|
||||
this->PTZController = ObjectInitializer.CreateDefaultSubobject<UPTZController>(this, TEXT("PTZController"));
|
||||
}
|
||||
|
||||
void ANDIBroadcastActor::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// validate the viewport capture component
|
||||
if (IsValid(this->ViewportCaptureComponent))
|
||||
{
|
||||
// Initialize the Capture Component with the media source
|
||||
ViewportCaptureComponent->Initialize(this->NDIMediaSource);
|
||||
}
|
||||
|
||||
if (IsValid(this->PTZController))
|
||||
{
|
||||
// Initialize the PTZ Controller with the media source
|
||||
PTZController->Initialize(this->NDIMediaSource);
|
||||
}
|
||||
|
||||
if (IsValid(this->NDIMediaSource))
|
||||
{
|
||||
this->NDIMediaSource->Initialize(this->SubmixCapture);
|
||||
}
|
||||
}
|
||||
|
||||
FPTZState ANDIBroadcastActor::GetPTZStateFromUE() const
|
||||
{
|
||||
FPTZState PTZState;
|
||||
|
||||
PTZState.CameraTransform = GetActorTransform();
|
||||
FTransform Transform = FTransform::Identity;
|
||||
if (IsValid(this->ViewportCaptureComponent))
|
||||
Transform = this->ViewportCaptureComponent->GetRelativeTransform();
|
||||
FQuat Rotation = Transform.GetRotation();
|
||||
FVector Euler = Rotation.Euler();
|
||||
PTZState.Pan = FMath::DegreesToRadians(Euler[2]);
|
||||
PTZState.Tilt = FMath::DegreesToRadians(Euler[1]);
|
||||
|
||||
if (IsValid(this->ViewportCaptureComponent))
|
||||
{
|
||||
PTZState.FieldOfView = this->ViewportCaptureComponent->FOVAngle;
|
||||
PTZState.FocusDistance = 1.f - 1.f / (this->ViewportCaptureComponent->PostProcessSettings.DepthOfFieldFocalDistance / 100.f + 1.f);
|
||||
PTZState.bAutoFocus = (this->ViewportCaptureComponent->PostProcessSettings.bOverride_DepthOfFieldFocalDistance == true) ? false : true;
|
||||
}
|
||||
|
||||
return PTZState;
|
||||
}
|
||||
|
||||
void ANDIBroadcastActor::SetPTZStateToUE(const FPTZState& PTZState)
|
||||
{
|
||||
SetActorTransform(PTZState.CameraTransform);
|
||||
FVector Euler(0, FMath::RadiansToDegrees(PTZState.Tilt), FMath::RadiansToDegrees(PTZState.Pan));
|
||||
FQuat NewRotation = FQuat::MakeFromEuler(Euler);
|
||||
|
||||
if (IsValid(this->ViewportCaptureComponent))
|
||||
{
|
||||
this->ViewportCaptureComponent->SetRelativeLocationAndRotation(this->ViewportCaptureComponent->GetRelativeLocation(), NewRotation);
|
||||
this->ViewportCaptureComponent->FOVAngle = PTZState.FieldOfView;
|
||||
this->ViewportCaptureComponent->PostProcessSettings.DepthOfFieldFocalDistance = (1.f / FMath::Max(1 - PTZState.FocusDistance, 0.01f) - 1.f) * 100.f;
|
||||
this->ViewportCaptureComponent->PostProcessSettings.DepthOfFieldFocalDistance = FMath::Max(this->ViewportCaptureComponent->PostProcessSettings.DepthOfFieldFocalDistance, SMALL_NUMBER);
|
||||
this->ViewportCaptureComponent->PostProcessSettings.bOverride_DepthOfFieldFocalDistance = (PTZState.bAutoFocus == true) ? false : true;
|
||||
}
|
||||
}
|
||||
369
Plugins/NDIIO/Source/Core/Classes/Actors/NDIReceiveActor.cpp
Normal file
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
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 <Actors/NDIReceiveActor.h>
|
||||
|
||||
#include <UObject/Package.h>
|
||||
#include <AudioDevice.h>
|
||||
#include <ActiveSound.h>
|
||||
#include <Async/Async.h>
|
||||
#include <Engine/StaticMesh.h>
|
||||
#include <Kismet/GameplayStatics.h>
|
||||
#include <Materials/MaterialInstanceDynamic.h>
|
||||
#include <Objects/Media/NDIMediaTexture2D.h>
|
||||
#include <UObject/ConstructorHelpers.h>
|
||||
|
||||
ANDIReceiveActor::ANDIReceiveActor(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
// Get the Engine's 'Plane' static mesh
|
||||
static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshObject(
|
||||
TEXT("StaticMesh'/Engine/BasicShapes/Plane.Plane'"));
|
||||
static ConstructorHelpers::FObjectFinder<UMaterialInterface> MaterialObject(
|
||||
TEXT("Material'/NDIIOPlugin/Materials/NDI_Unlit_SourceMaterial.NDI_Unlit_SourceMaterial'"));
|
||||
|
||||
// Ensure that the object is valid
|
||||
if (MeshObject.Object)
|
||||
{
|
||||
// Create the static mesh component visual
|
||||
this->VideoMeshComponent =
|
||||
ObjectInitializer.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("VideoMeshComponent"), true);
|
||||
|
||||
// setup the attachment and modify the position, rotation, and mesh properties
|
||||
this->VideoMeshComponent->SetupAttachment(RootComponent);
|
||||
this->VideoMeshComponent->SetStaticMesh(MeshObject.Object);
|
||||
this->VideoMeshComponent->SetRelativeRotation(FQuat::MakeFromEuler(FVector(90.0f, 0.0f, 90.0f)));
|
||||
this->VideoMeshComponent->SetRelativeScale3D(FVector(FrameWidth / 100.0f, FrameHeight / 100.0f, 1.0f));
|
||||
|
||||
this->VideoMeshComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||
this->VideoMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
this->VideoMeshComponent->SetCollisionObjectType(ECC_WorldDynamic);
|
||||
|
||||
// This is object is mainly used for simple tests and things that don't require
|
||||
// additional material shading support, store the an unlit source material to display
|
||||
this->VideoMaterial = MaterialObject.Object;
|
||||
|
||||
// If the material is valid
|
||||
if (this->VideoMaterial)
|
||||
{
|
||||
// Set the Mesh Material to the Video Material
|
||||
this->VideoMeshComponent->SetMaterial(0, this->VideoMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
this->AudioComponent = ObjectInitializer.CreateDefaultSubobject<UAudioComponent>(this, TEXT("AudioComponent"), true);
|
||||
this->AudioComponent->SetupAttachment(RootComponent);
|
||||
this->AudioComponent->SetRelativeLocationAndRotation(FVector::ZeroVector, FRotator::ZeroRotator);
|
||||
this->AudioComponent->SetRelativeScale3D(FVector::OneVector);
|
||||
|
||||
this->bAllowTickBeforeBeginPlay = false;
|
||||
this->PrimaryActorTick.bCanEverTick = true;
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::BeginPlay()
|
||||
{
|
||||
// call the base implementation for 'BeginPlay'
|
||||
Super::BeginPlay();
|
||||
|
||||
// We need to validate that we have media source, so we can set the texture in the material instance
|
||||
if (IsValid(this->NDIMediaSource))
|
||||
{
|
||||
this->NDIMediaSource->Initialize(UNDIMediaReceiver::EUsage::Standalone);
|
||||
|
||||
// Validate the Video Material Instance so we can set the texture used in the NDI Media source
|
||||
if (IsValid(this->VideoMaterial))
|
||||
{
|
||||
// create and set the instance material from the MaterialObject
|
||||
VideoMaterialInstance =
|
||||
this->VideoMeshComponent->CreateAndSetMaterialInstanceDynamicFromMaterial(0, this->VideoMaterial);
|
||||
|
||||
// Ensure we have a valid material instance
|
||||
if (IsValid(VideoMaterialInstance))
|
||||
{
|
||||
// Enable/disable the color and/or alpha channels
|
||||
this->VideoMaterialInstance->SetScalarParameterValue("Enable Video Texture", bEnableColor ? 1.0f : 0.0f);
|
||||
this->VideoMaterialInstance->SetScalarParameterValue("Enable Video Alpha", bEnableAlpha ? 1.0f : 0.0f);
|
||||
|
||||
this->NDIMediaSource->UpdateMaterialTexture(VideoMaterialInstance, "Video Texture");
|
||||
}
|
||||
}
|
||||
|
||||
// Define the basic parameters for constructing temporary audio wave object
|
||||
FString AudioSource = FString::Printf(TEXT("AudioSource_%s"), *GetFName().ToString().Right(1));
|
||||
FName AudioWaveName = FName(*AudioSource);
|
||||
EObjectFlags Flags = RF_Public | RF_Standalone | RF_Transient | RF_MarkAsNative;
|
||||
|
||||
// Construct a temporary audio sound wave to be played by this component
|
||||
this->AudioSoundWave = NewObject<UNDIMediaSoundWave>(GetTransientPackage(), UNDIMediaSoundWave::StaticClass(),
|
||||
AudioWaveName, Flags);
|
||||
|
||||
// Ensure the validity of the temporary sound wave object
|
||||
if (IsValid(this->AudioSoundWave))
|
||||
{
|
||||
// Set the number of channels
|
||||
bStoppedForChannelsMode = false;
|
||||
ApplyChannelsMode();
|
||||
|
||||
// Set the sound of the Audio Component and Ensure playback
|
||||
this->AudioComponent->SetSound(this->AudioSoundWave);
|
||||
|
||||
// Ensure we register the audio wave object with the media.
|
||||
this->NDIMediaSource->RegisterAudioWave(AudioSoundWave);
|
||||
}
|
||||
|
||||
if (this->NDIMediaSource->GetCurrentConnectionInformation().IsValid())
|
||||
{
|
||||
if (IsValid(AudioComponent))
|
||||
{
|
||||
// we should play the audio, if we want audio playback
|
||||
if (bEnableAudioPlayback)
|
||||
{
|
||||
this->AudioComponent->Play(0.0f);
|
||||
}
|
||||
|
||||
// otherwise just stop
|
||||
else
|
||||
{
|
||||
this->AudioComponent->Stop();
|
||||
this->bStoppedForChannelsMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a lambda to the OnReceiverConnected Event
|
||||
else
|
||||
this->NDIMediaSource->OnNDIReceiverConnectedEvent.AddWeakLambda(this, [&](UNDIMediaReceiver*) {
|
||||
// Ensure that the audio component is valid
|
||||
if (IsValid(AudioComponent))
|
||||
{
|
||||
// we should play the audio, if we want audio playback
|
||||
if (bEnableAudioPlayback)
|
||||
{
|
||||
this->AudioComponent->Play(0.0f);
|
||||
}
|
||||
|
||||
// otherwise just stop
|
||||
else
|
||||
{
|
||||
this->AudioComponent->Stop();
|
||||
this->bStoppedForChannelsMode = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
|
||||
this->bStoppedForChannelsMode = false;
|
||||
|
||||
// Ensure we have a valid material instance
|
||||
if (EndPlayReason == EEndPlayReason::EndPlayInEditor && IsValid(VideoMaterialInstance))
|
||||
{
|
||||
// Ensure that the video texture is disabled
|
||||
this->VideoMaterialInstance->SetScalarParameterValue("Enable Video Texture", 0.0f);
|
||||
this->VideoMaterialInstance->SetScalarParameterValue("Enable Video Alpha", 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
|
||||
ApplyChannelsMode();
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::ApplyChannelsMode()
|
||||
{
|
||||
if (IsValid(this->AudioComponent) && IsValid(this->NDIMediaSource) && IsValid(this->AudioSoundWave))
|
||||
{
|
||||
int32 NewNumChannels = 0;
|
||||
switch(this->AudioPlaybackChannels)
|
||||
{
|
||||
case ENDIAudioChannels::Mono:
|
||||
NewNumChannels = 1;
|
||||
break;
|
||||
case ENDIAudioChannels::Stereo:
|
||||
NewNumChannels = 2;
|
||||
break;
|
||||
case ENDIAudioChannels::Source:
|
||||
NewNumChannels = this->NDIMediaSource->GetAudioChannels();
|
||||
break;
|
||||
}
|
||||
|
||||
if ((NewNumChannels != 0) && (NewNumChannels != this->AudioSoundWave->NumChannels))
|
||||
{
|
||||
bool isPlaying = this->AudioComponent->IsPlaying();
|
||||
if (isPlaying)
|
||||
{
|
||||
this->AudioComponent->Stop();
|
||||
bStoppedForChannelsMode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->AudioSoundWave->NumChannels = NewNumChannels;
|
||||
|
||||
if (bEnableAudioPlayback && bStoppedForChannelsMode)
|
||||
{
|
||||
this->AudioComponent->Play(0.0f);
|
||||
}
|
||||
bStoppedForChannelsMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to set the desired frame size in cm, represented in the virtual scene
|
||||
*/
|
||||
void ANDIReceiveActor::SetFrameSize(FVector2D InFrameSize)
|
||||
{
|
||||
// clamp the values to the lowest we'll allow
|
||||
const float frame_height = FMath::Max(InFrameSize.Y, 0.00001f);
|
||||
const float frame_width = FMath::Max(InFrameSize.X, 0.00001f);
|
||||
|
||||
FrameWidth = frame_width;
|
||||
FrameHeight = frame_height;
|
||||
|
||||
// validate the static mesh component
|
||||
if (IsValid(this->VideoMeshComponent))
|
||||
{
|
||||
// change the scale of the video
|
||||
this->VideoMeshComponent->SetRelativeScale3D(FVector(FrameWidth / 100.0f, FrameHeight / 100.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::SetFrameHeight(const float& InFrameHeight)
|
||||
{
|
||||
// Clamp the Frame Height to a minimal value
|
||||
FrameHeight = FMath::Max(InFrameHeight, 0.00001f);
|
||||
|
||||
// Call the function to set the frame size with the newly clamped value
|
||||
SetFrameSize(FVector2D(FrameWidth, FrameHeight));
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::SetFrameWidth(const float& InFrameWidth)
|
||||
{
|
||||
// Clamp the Frame Width to a minimal value
|
||||
FrameWidth = FMath::Max(InFrameWidth, 0.00001f);
|
||||
|
||||
// Call the function to set the frame size with the newly clamped value
|
||||
SetFrameSize(FVector2D(FrameWidth, FrameHeight));
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::UpdateAudioPlayback(const bool& Enabled)
|
||||
{
|
||||
// Ensure validity and we are currently playing
|
||||
if (IsValid(this->AudioComponent))
|
||||
{
|
||||
// Stop playback when possible
|
||||
if (Enabled)
|
||||
{
|
||||
// Start the playback
|
||||
this->AudioComponent->Play(0.0f);
|
||||
}
|
||||
|
||||
// otherwise just stop playback (even if it's not playing)
|
||||
else
|
||||
this->AudioComponent->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::UpdateAudioPlaybackChannels(const ENDIAudioChannels& Channels)
|
||||
{}
|
||||
|
||||
|
||||
void ANDIReceiveActor::EnableColor(const bool& Enabled)
|
||||
{
|
||||
bEnableColor = Enabled;
|
||||
if (IsValid(VideoMaterialInstance))
|
||||
{
|
||||
// Enable/disable the color channels
|
||||
this->VideoMaterialInstance->SetScalarParameterValue("Enable Video Texture", bEnableColor ? 1.0f : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::EnableAlpha(const bool& Enabled)
|
||||
{
|
||||
bEnableAlpha = Enabled;
|
||||
if (IsValid(VideoMaterialInstance))
|
||||
{
|
||||
// Enable/disable the alpha channel
|
||||
this->VideoMaterialInstance->SetScalarParameterValue("Enable Video Alpha", bEnableAlpha ? 1.0f : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the current frame size of the 'VideoMeshComponent' for this object
|
||||
*/
|
||||
const FVector2D ANDIReceiveActor::GetFrameSize() const
|
||||
{
|
||||
return FVector2D(FrameWidth, FrameHeight);
|
||||
}
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
|
||||
void ANDIReceiveActor::PreEditChange(FProperty* InProperty)
|
||||
{
|
||||
// call the base class 'PreEditChange'
|
||||
Super::PreEditChange(InProperty);
|
||||
}
|
||||
|
||||
void ANDIReceiveActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||
{
|
||||
// get the name of the property which changed
|
||||
FName PropertyName =
|
||||
(PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
|
||||
|
||||
// compare against the 'FrameHeight' property
|
||||
if (PropertyName == GET_MEMBER_NAME_CHECKED(ANDIReceiveActor, FrameHeight))
|
||||
{
|
||||
// resize the frame
|
||||
SetFrameSize(FVector2D(FrameWidth, FrameHeight));
|
||||
}
|
||||
|
||||
// compare against the 'FrameWidth' property
|
||||
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ANDIReceiveActor, FrameWidth))
|
||||
{
|
||||
// resize the frame
|
||||
SetFrameSize(FVector2D(FrameWidth, FrameHeight));
|
||||
}
|
||||
|
||||
// compare against the 'bEnableAudioPlayback' property
|
||||
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ANDIReceiveActor, bEnableAudioPlayback))
|
||||
{
|
||||
// start or stop the audio playback depending on state
|
||||
UpdateAudioPlayback(bEnableAudioPlayback);
|
||||
}
|
||||
|
||||
// compare against the 'AudioPlaybackChannels' property
|
||||
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ANDIReceiveActor, AudioPlaybackChannels))
|
||||
{
|
||||
// start or stop the audio playback depending on state
|
||||
UpdateAudioPlaybackChannels(AudioPlaybackChannels);
|
||||
}
|
||||
|
||||
// compare against the 'bEnableColor' property
|
||||
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ANDIReceiveActor, bEnableColor))
|
||||
{
|
||||
// enable or disable color channels (if it exists)
|
||||
EnableColor(bEnableColor);
|
||||
}
|
||||
|
||||
// compare against the 'bEnableAlpha' property
|
||||
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ANDIReceiveActor, bEnableAlpha))
|
||||
{
|
||||
// enable or disable alpha channel (if it exists)
|
||||
EnableAlpha(bEnableAlpha);
|
||||
}
|
||||
|
||||
// call the base class 'PostEditChangeProperty'
|
||||
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||
}
|
||||
|
||||
#endif
|
||||
101
Plugins/NDIIO/Source/Core/Classes/Assets/NDITimecodeProvider.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
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 <Assets/NDITimecodeProvider.h>
|
||||
|
||||
|
||||
UNDITimecodeProvider::UNDITimecodeProvider(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{}
|
||||
|
||||
bool UNDITimecodeProvider::FetchTimecode(FQualifiedFrameTime& OutFrameTime)
|
||||
{
|
||||
FScopeLock Lock(&this->StateSyncContext);
|
||||
|
||||
if (!IsValid(this->NDIMediaSource) ||
|
||||
(GetSynchronizationState() != ETimecodeProviderSynchronizationState::Synchronized))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutFrameTime = this->MostRecentFrameTime;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ETimecodeProviderSynchronizationState UNDITimecodeProvider::GetSynchronizationState() const
|
||||
{
|
||||
FScopeLock Lock(&this->StateSyncContext);
|
||||
|
||||
if (!IsValid(this->NDIMediaSource))
|
||||
return ETimecodeProviderSynchronizationState::Closed;
|
||||
|
||||
return this->State;
|
||||
}
|
||||
|
||||
bool UNDITimecodeProvider::Initialize(UEngine* InEngine)
|
||||
{
|
||||
this->State = ETimecodeProviderSynchronizationState::Closed;
|
||||
|
||||
if (!IsValid(this->NDIMediaSource))
|
||||
{
|
||||
this->State = ETimecodeProviderSynchronizationState::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
this->NDIMediaSource->Initialize(UNDIMediaReceiver::EUsage::Standalone);
|
||||
|
||||
this->VideoCaptureEventHandle = this->NDIMediaSource->OnNDIReceiverVideoCaptureEvent.AddLambda([this](UNDIMediaReceiver* Receiver, const NDIlib_video_frame_v2_t& VideoFrame)
|
||||
{
|
||||
const FFrameRate Rate = Receiver->GetCurrentFrameRate();
|
||||
const FTimecode Timecode = Receiver->GetCurrentTimecode();
|
||||
|
||||
FScopeLock Lock(&this->StateSyncContext);
|
||||
this->State = ETimecodeProviderSynchronizationState::Synchronized;
|
||||
this->MostRecentFrameTime = FQualifiedFrameTime(Timecode, Rate);
|
||||
});
|
||||
this->ConnectedEventHandle = this->NDIMediaSource->OnNDIReceiverConnectedEvent.AddLambda([this](UNDIMediaReceiver* Receiver)
|
||||
{
|
||||
FScopeLock Lock(&this->StateSyncContext);
|
||||
this->State = ETimecodeProviderSynchronizationState::Synchronizing;
|
||||
});
|
||||
this->DisconnectedEventHandle = this->NDIMediaSource->OnNDIReceiverDisconnectedEvent.AddLambda([this](UNDIMediaReceiver* Receiver)
|
||||
{
|
||||
FScopeLock Lock(&this->StateSyncContext);
|
||||
this->State = ETimecodeProviderSynchronizationState::Closed;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UNDITimecodeProvider::Shutdown(UEngine* InEngine)
|
||||
{
|
||||
ReleaseResources();
|
||||
}
|
||||
|
||||
|
||||
void UNDITimecodeProvider::BeginDestroy()
|
||||
{
|
||||
ReleaseResources();
|
||||
|
||||
Super::BeginDestroy();
|
||||
}
|
||||
|
||||
void UNDITimecodeProvider::ReleaseResources()
|
||||
{
|
||||
if(IsValid(this->NDIMediaSource))
|
||||
{
|
||||
this->NDIMediaSource->OnNDIReceiverVideoCaptureEvent.Remove(this->VideoCaptureEventHandle);
|
||||
this->NDIMediaSource->OnNDIReceiverConnectedEvent.Remove(this->ConnectedEventHandle);
|
||||
this->NDIMediaSource->OnNDIReceiverDisconnectedEvent.Remove(this->DisconnectedEventHandle);
|
||||
}
|
||||
this->VideoCaptureEventHandle.Reset();
|
||||
this->ConnectedEventHandle.Reset();
|
||||
this->DisconnectedEventHandle.Reset();
|
||||
|
||||
this->State = ETimecodeProviderSynchronizationState::Closed;
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
305
Plugins/NDIIO/Source/Core/Classes/NDIIOPluginModule.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
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 <NDIIOPluginModule.h>
|
||||
|
||||
#include <Modules/ModuleManager.h>
|
||||
#include <IMediaModule.h>
|
||||
#include <NDIIOPluginAPI.h>
|
||||
#include "Player/NDIMediaPlayer.h"
|
||||
#include <Misc/Paths.h>
|
||||
|
||||
#include <GenericPlatform/GenericPlatformMisc.h>
|
||||
|
||||
#include <Services/NDIConnectionService.h>
|
||||
#include <Services/NDIFinderService.h>
|
||||
|
||||
#include <Misc/MessageDialog.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
|
||||
// Meaning the plugin is being compiled with the editor
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "ThumbnailRendering/ThumbnailManager.h"
|
||||
#include "ThumbnailRendering/TextureThumbnailRenderer.h"
|
||||
|
||||
#include <ISettingsModule.h>
|
||||
#include <Editor.h>
|
||||
|
||||
#include <Objects/Media/NDIMediaTexture2D.h>
|
||||
|
||||
#endif
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FNDIIOPluginModule"
|
||||
|
||||
|
||||
void FNDIIOPluginModule::StartupModule()
|
||||
{
|
||||
// Doubly Ensure that this handle is nullptr
|
||||
NDI_LIB_HANDLE = nullptr;
|
||||
|
||||
if (LoadModuleDependencies())
|
||||
{
|
||||
#if UE_EDITOR
|
||||
|
||||
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
|
||||
{
|
||||
SettingsModule->RegisterSettings(
|
||||
"Project", "Plugins", "NDI", LOCTEXT("NDISettingsName", "Vizrt NDI"),
|
||||
LOCTEXT("NDISettingsDescription", "Vizrt NDI(R) Engine Intergration Settings"),
|
||||
GetMutableDefault<UNDIIOPluginSettings>());
|
||||
}
|
||||
|
||||
// Ensure that the thumbnail for the 'NDI Media Texture2D' is being updated, as the texture is being used.
|
||||
UThumbnailManager::Get().RegisterCustomRenderer(UNDIMediaTexture2D::StaticClass(),
|
||||
UTextureThumbnailRenderer::StaticClass());
|
||||
|
||||
#endif
|
||||
|
||||
// Construct our Services
|
||||
this->NDIFinderService = MakeShareable(new FNDIFinderService());
|
||||
this->NDIConnectionService = MakeShareable(new FNDIConnectionService());
|
||||
|
||||
// Start the service
|
||||
if (NDIFinderService.IsValid())
|
||||
NDIFinderService->Start();
|
||||
|
||||
// Start the service
|
||||
if (NDIConnectionService.IsValid())
|
||||
NDIConnectionService->Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
#if PLATFORM_WINDOWS
|
||||
// Write an error message to the log.
|
||||
UE_LOG(LogWindows, Error,
|
||||
TEXT("Unable to load \"Processing.NDI.Lib.x64.dll\" from the NDI 6 Runtime Directory."));
|
||||
|
||||
#if UE_EDITOR
|
||||
|
||||
const FText& WarningMessage =
|
||||
LOCTEXT("NDIRuntimeMissing",
|
||||
"Cannot find \"Processing.NDI.Lib.x64.dll\" from the NDI 6 Runtime Directory. "
|
||||
"Continued usage of the plugin can cause instability within the editor.\r\n\r\n"
|
||||
|
||||
"Please refer to the 'NDI IO Plugin for Unreal Engine Quickstart Guide' "
|
||||
"for additional information related to installation instructions for this plugin.\r\n\r\n");
|
||||
|
||||
// Open a message box, showing that things will not work since the NDI Runtime Directory cannot be found
|
||||
if (FMessageDialog::Open(EAppMsgType::OkCancel, EAppReturnType::Ok, WarningMessage) == EAppReturnType::Ok)
|
||||
{
|
||||
FString URLResult = FString("");
|
||||
FPlatformProcess::LaunchURL(*FString("https://ndi.video/sdk/"), nullptr, &URLResult);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (PLATFORM_LINUX || PLATFORM_LINUXARM64)
|
||||
// Write an error message to the log.
|
||||
UE_LOG(LogLinux, Error,
|
||||
TEXT("Unable to load \"" NDILIB_LIBRARY_NAME "\" from the NDI 6 Runtime."));
|
||||
|
||||
#if UE_EDITOR
|
||||
|
||||
const FText& WarningMessage =
|
||||
LOCTEXT("NDIRuntimeMissing",
|
||||
"Cannot find \"" NDILIB_LIBRARY_NAME "\" from the NDI 6 Runtime. "
|
||||
"Continued usage of the plugin can cause instability within the editor.\r\n\r\n"
|
||||
|
||||
"Please refer to the 'NDI IO Plugin for Unreal Engine Quickstart Guide' "
|
||||
"for additional information related to installation instructions for this plugin.\r\n\r\n");
|
||||
|
||||
// Open a message box, showing that things will not work since the NDI Runtime Directory cannot be found
|
||||
if (FMessageDialog::Open(EAppMsgType::OkCancel, EAppReturnType::Ok, WarningMessage) == EAppReturnType::Ok)
|
||||
{
|
||||
FString URLResult = FString("");
|
||||
FPlatformProcess::LaunchURL(*FString("https://ndi.video/sdk/"), nullptr, &URLResult);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// supported platforms
|
||||
SupportedPlatforms.Add(TEXT("Windows"));
|
||||
SupportedPlatforms.Add(TEXT("Linux"));
|
||||
SupportedPlatforms.Add(TEXT("LinuxAArch64"));
|
||||
|
||||
// supported schemes
|
||||
SupportedUriSchemes.Add(TEXT("ndiio"));
|
||||
|
||||
// register player factory
|
||||
auto MediaModule = FModuleManager::LoadModulePtr<IMediaModule>("Media");
|
||||
|
||||
if (MediaModule != nullptr)
|
||||
{
|
||||
MediaModule->RegisterPlayerFactory(*this);
|
||||
}
|
||||
|
||||
FApp::SetUnfocusedVolumeMultiplier(1.f);
|
||||
}
|
||||
|
||||
void FNDIIOPluginModule::ShutdownModule()
|
||||
{
|
||||
// unregister player factory
|
||||
auto MediaModule = FModuleManager::GetModulePtr<IMediaModule>("Media");
|
||||
|
||||
if (MediaModule != nullptr)
|
||||
{
|
||||
MediaModule->UnregisterPlayerFactory(*this);
|
||||
}
|
||||
|
||||
|
||||
if (NDIFinderService.IsValid())
|
||||
NDIFinderService->Shutdown();
|
||||
|
||||
ShutdownModuleDependencies();
|
||||
}
|
||||
|
||||
bool FNDIIOPluginModule::BeginBroadcastingActiveViewport()
|
||||
{
|
||||
// Ensure we have a valid service
|
||||
if (NDIConnectionService.IsValid())
|
||||
{
|
||||
// perform the requested functionality
|
||||
return NDIConnectionService->BeginBroadcastingActiveViewport();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FNDIIOPluginModule::StopBroadcastingActiveViewport()
|
||||
{
|
||||
// Ensure we have a valid service
|
||||
if (NDIConnectionService.IsValid())
|
||||
{
|
||||
// perform the requested functionality
|
||||
NDIConnectionService->StopBroadcastingActiveViewport();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//~ IMediaPlayerFactory interface
|
||||
bool FNDIIOPluginModule::CanPlayUrl(const FString& Url, const IMediaOptions* /*Options*/, TArray<FText>* /*OutWarnings*/, TArray<FText>* OutErrors) const
|
||||
{
|
||||
FString Scheme;
|
||||
FString Location;
|
||||
|
||||
// check scheme
|
||||
if (!Url.Split(TEXT("://"), &Scheme, &Location, ESearchCase::CaseSensitive))
|
||||
{
|
||||
if (OutErrors != nullptr)
|
||||
{
|
||||
OutErrors->Add(LOCTEXT("NoSchemeFound", "No URI scheme found"));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SupportedUriSchemes.Contains(Scheme))
|
||||
{
|
||||
if (OutErrors != nullptr)
|
||||
{
|
||||
OutErrors->Add(FText::Format(LOCTEXT("SchemeNotSupported", "The URI scheme '{0}' is not supported"), FText::FromString(Scheme)));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TSharedPtr<IMediaPlayer, ESPMode::ThreadSafe> FNDIIOPluginModule::CreatePlayer(IMediaEventSink& EventSink)
|
||||
{
|
||||
return MakeShared<FNDIMediaPlayer, ESPMode::ThreadSafe>(EventSink);
|
||||
}
|
||||
|
||||
FText FNDIIOPluginModule::GetDisplayName() const
|
||||
{
|
||||
return LOCTEXT("MediaPlayerDisplayName", "NDI Interface");
|
||||
}
|
||||
|
||||
FName FNDIIOPluginModule::GetPlayerName() const
|
||||
{
|
||||
static FName PlayerName(TEXT("NDIMedia"));
|
||||
return PlayerName;
|
||||
}
|
||||
|
||||
FGuid FNDIIOPluginModule::GetPlayerPluginGUID() const
|
||||
{
|
||||
static FGuid PlayerPluginGUID(0x71b13c2b, 0x70874965, 0x8a0e23f7, 0x5be6698f);
|
||||
return PlayerPluginGUID;
|
||||
}
|
||||
|
||||
const TArray<FString>& FNDIIOPluginModule::GetSupportedPlatforms() const
|
||||
{
|
||||
return SupportedPlatforms;
|
||||
}
|
||||
|
||||
bool FNDIIOPluginModule::SupportsFeature(EMediaFeature Feature) const
|
||||
{
|
||||
return Feature == EMediaFeature::AudioSamples ||
|
||||
Feature == EMediaFeature::MetadataTracks ||
|
||||
Feature == EMediaFeature::VideoSamples;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool FNDIIOPluginModule::LoadModuleDependencies()
|
||||
{
|
||||
#if PLATFORM_WINDOWS
|
||||
// Get the Binaries File Location
|
||||
const FString env_variable = TEXT(NDILIB_REDIST_FOLDER);
|
||||
const FString binaries_path = FPlatformMisc::GetEnvironmentVariable(*env_variable) + "/Processing.NDI.Lib.x64.dll";
|
||||
|
||||
// We can't validate if it's valid, but we can determine if it's explicitly not.
|
||||
if (binaries_path.Len() > 0)
|
||||
{
|
||||
// Load the DLL
|
||||
this->NDI_LIB_HANDLE = FPlatformProcess::GetDllHandle(*binaries_path);
|
||||
|
||||
// Not required, but "correct" (see the SDK documentation)
|
||||
if (this->NDI_LIB_HANDLE != nullptr && !NDIlib_initialize())
|
||||
{
|
||||
// We were unable to initialize the library, so lets free the handle
|
||||
FPlatformProcess::FreeDllHandle(this->NDI_LIB_HANDLE);
|
||||
this->NDI_LIB_HANDLE = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Did we successfully load the NDI library?
|
||||
return this->NDI_LIB_HANDLE != nullptr;
|
||||
#endif
|
||||
|
||||
#if (PLATFORM_LINUX || PLATFORM_LINUXARM64)
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void FNDIIOPluginModule::ShutdownModuleDependencies()
|
||||
{
|
||||
#if PLATFORM_WINDOWS
|
||||
if (this->NDI_LIB_HANDLE != nullptr)
|
||||
{
|
||||
NDIlib_destroy();
|
||||
FPlatformProcess::FreeDllHandle(this->NDI_LIB_HANDLE);
|
||||
this->NDI_LIB_HANDLE = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (PLATFORM_LINUX || PLATFORM_LINUXARM64)
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FNDIIOPluginModule, NDIIO);
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
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 <Objects/Libraries/NDIIOLibrary.h>
|
||||
#include <Services/NDIFinderService.h>
|
||||
#include <NDIIOPluginModule.h>
|
||||
|
||||
#include <FastXml.h>
|
||||
|
||||
const TArray<FNDIConnectionInformation> UNDIIOLibrary::K2_GetNDISourceCollection()
|
||||
{
|
||||
// Return the FinderServices current network source collection
|
||||
return FNDIFinderService::GetNetworkSourceCollection();
|
||||
}
|
||||
|
||||
const bool UNDIIOLibrary::K2_FindNetworkSourceByName(UObject* WorldContextObject,
|
||||
FNDIConnectionInformation& ConnectionInformation,
|
||||
FString InSourceName)
|
||||
{
|
||||
// Ensure that the passed in information is empty
|
||||
ConnectionInformation.Reset();
|
||||
|
||||
// Get the current network source collection from the finder service.
|
||||
const TArray<FNDIConnectionInformation> NetworkSourceCollection = FNDIFinderService::GetNetworkSourceCollection();
|
||||
|
||||
// Get the current number of network source items in the collection
|
||||
int32 final_count = NetworkSourceCollection.Num();
|
||||
|
||||
// Ensure we have a wide range of items to search through.
|
||||
int32 last_index = final_count;
|
||||
|
||||
// Since the Source collection returned is already sorted alphabetically do a binary search to speed things up.
|
||||
// We are only going to do comparisons that are necessary using O(log(n)) time complexity
|
||||
for (int32 current_index = 0; current_index < last_index; /* current_index changed in loop */)
|
||||
{
|
||||
// Ensure that the index is valid (this will protect against negative values)
|
||||
if (NetworkSourceCollection.IsValidIndex(current_index))
|
||||
{
|
||||
// Get the source reference from the collection
|
||||
FNDIConnectionInformation source_info = NetworkSourceCollection[current_index];
|
||||
|
||||
// do a comparison against the requested SourceName
|
||||
if (int32 comparitor_value = InSourceName.Compare(source_info.SourceName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
// Our search says that our source name is greater than the info we checked
|
||||
if (comparitor_value <= 0)
|
||||
{
|
||||
// set the last index to the current index
|
||||
last_index = current_index;
|
||||
|
||||
// get halfway between the last index and the 0th index
|
||||
current_index = last_index / 2;
|
||||
}
|
||||
|
||||
// Our search says that our source name is less than the info we checked
|
||||
else if (comparitor_value > 0)
|
||||
{
|
||||
// move up half the number of items within the collection
|
||||
current_index = (last_index + current_index + 1) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// We found a comparable source.
|
||||
else
|
||||
{
|
||||
// Set the source information structure
|
||||
ConnectionInformation = source_info;
|
||||
|
||||
// return success
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Something weird happened (maybe the first check was larger than the search term); just return a fail
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UNDIIOLibrary::K2_BeginBroadcastingActiveViewport(UObject* WorldContextObject)
|
||||
{
|
||||
// Get the plugin module for the owner of this object
|
||||
if (FNDIIOPluginModule* PluginModule = FModuleManager::GetModulePtr<FNDIIOPluginModule>("NDIIO"))
|
||||
{
|
||||
// Call the underlying functionality
|
||||
return PluginModule->BeginBroadcastingActiveViewport();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UNDIIOLibrary::K2_StopBroadcastingActiveViewport(UObject* WorldContextObject)
|
||||
{
|
||||
// Get the plugin module for the owner of this object
|
||||
if (FNDIIOPluginModule* PluginModule = FModuleManager::GetModulePtr<FNDIIOPluginModule>("NDIIO"))
|
||||
{
|
||||
// Call the underlying functionality
|
||||
PluginModule->StopBroadcastingActiveViewport();
|
||||
}
|
||||
}
|
||||
|
||||
UNDIMediaReceiver* UNDIIOLibrary::K2_GetNDIMediaReceiver(UNDIMediaReceiver* Receiver)
|
||||
{
|
||||
return Receiver;
|
||||
}
|
||||
|
||||
UNDIMediaSender* UNDIIOLibrary::K2_GetNDIMediaSender(UNDIMediaSender* Sender)
|
||||
{
|
||||
return Sender;
|
||||
}
|
||||
|
||||
|
||||
const TArray<FNDIMetaDataElement> UNDIIOLibrary::K2_ParseNDIMetaData(FString Data)
|
||||
{
|
||||
class Parser : public IFastXmlCallback
|
||||
{
|
||||
public:
|
||||
Parser(TArray<FNDIMetaDataElement>& ElementsIn)
|
||||
: Elements(ElementsIn)
|
||||
{}
|
||||
|
||||
virtual ~Parser()
|
||||
{}
|
||||
|
||||
virtual bool ProcessXmlDeclaration(const TCHAR* ElementData, int32 XmlFileLineNumber) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessElement(const TCHAR* ElementName, const TCHAR* ElementData, int32 XmlFileLineNumber) override
|
||||
{
|
||||
if(CurrentElementStack.Num() > 0)
|
||||
return false;
|
||||
|
||||
FNDIMetaDataElement NewElement;
|
||||
NewElement.ElementName = ElementName;
|
||||
NewElement.Data = ElementData;
|
||||
|
||||
if(CurrentElementStack.Num() == 0)
|
||||
{
|
||||
Elements.Push(NewElement);
|
||||
CurrentElementStack.Push(&Elements.Last());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override
|
||||
{
|
||||
check(CurrentElementStack.Num() > 0);
|
||||
|
||||
FNDIMetaDataElement* CurrentElement = CurrentElementStack.Last();
|
||||
|
||||
CurrentElement->Attributes.Add(AttributeName, AttributeValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessClose(const TCHAR* ElementName) override
|
||||
{
|
||||
check(CurrentElementStack.Num() > 0);
|
||||
|
||||
CurrentElementStack.Pop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessComment(const TCHAR* Comment) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
TArray<FNDIMetaDataElement>& Elements;
|
||||
TArray<FNDIMetaDataElement*> CurrentElementStack;
|
||||
};
|
||||
|
||||
TArray<FNDIMetaDataElement> Elements;
|
||||
Parser Parser(Elements);
|
||||
|
||||
FText OutErrorMessage;
|
||||
int32 OutErrorLineNumber;
|
||||
|
||||
FFastXml::ParseXmlFile(&Parser,
|
||||
nullptr, // XmlFilePath
|
||||
Data.GetCharArray().GetData(), // XmlFileContents
|
||||
nullptr, // FeedbackContext
|
||||
false, // bShowSlowTaskDialog
|
||||
false, // bShowCancelButton
|
||||
OutErrorMessage, // OutErrorMessage
|
||||
OutErrorLineNumber // OutErrorLineNumber
|
||||
);
|
||||
|
||||
return Elements;
|
||||
}
|
||||
1525
Plugins/NDIIO/Source/Core/Classes/Objects/Media/NDIMediaReceiver.cpp
Normal file
1368
Plugins/NDIIO/Source/Core/Classes/Objects/Media/NDIMediaSender.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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 <Objects/Media/NDIMediaSoundWave.h>
|
||||
#include <Objects/Media/NDIMediaReceiver.h>
|
||||
|
||||
|
||||
UNDIMediaSoundWave::UNDIMediaSoundWave(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
// Set the Default Values for this object
|
||||
this->bLooping = false;
|
||||
this->NumChannels = 1;
|
||||
this->SampleRate = 48000;
|
||||
|
||||
this->Duration = INDEFINITELY_LOOPING_DURATION;
|
||||
}
|
||||
|
||||
/**
|
||||
Set the Media Source of this object, so that when this object is called to 'GeneratePCMData' by the engine
|
||||
we can request the media source to provide the pcm data from the current connected source
|
||||
*/
|
||||
void UNDIMediaSoundWave::SetConnectionSource(UNDIMediaReceiver* InMediaSource)
|
||||
{
|
||||
// Ensure there is no thread contention for generating pcm data from the connection source
|
||||
FScopeLock Lock(&SyncContext);
|
||||
|
||||
// Do we have a media source object to work with
|
||||
if (this->MediaSource != nullptr)
|
||||
{
|
||||
// Are we already registered with the incoming media source object
|
||||
if (this->MediaSource != InMediaSource)
|
||||
{
|
||||
// It doesn't look like we are registered with the incoming, make sure
|
||||
// to unregistered with the previous source
|
||||
this->MediaSource->UnregisterAudioWave(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we have a reference to the media source object
|
||||
this->MediaSource = InMediaSource;
|
||||
}
|
||||
|
||||
/**
|
||||
Called by the engine to generate pcm data to be 'heard' by audio listener objects
|
||||
*/
|
||||
int32 UNDIMediaSoundWave::OnGeneratePCMAudio(TArray<uint8>& OutAudio, int32 NumSamples)
|
||||
{
|
||||
// Ensure there is no thread contention for generating pcm data from the connection source
|
||||
FScopeLock Lock(&SyncContext);
|
||||
|
||||
// set the default value, in case we have no connection source
|
||||
int32 samples_generated = 0;
|
||||
|
||||
OutAudio.Reset();
|
||||
OutAudio.AddZeroed(NumSamples * sizeof(int16));
|
||||
|
||||
// check the connection source and continue
|
||||
if (this->MediaSource != nullptr)
|
||||
{
|
||||
samples_generated = MediaSource->GeneratePCMData(this, OutAudio.GetData(), NumSamples);
|
||||
}
|
||||
|
||||
// return to the engine the number of samples actually generated
|
||||
return samples_generated;
|
||||
}
|
||||
|
||||
bool UNDIMediaSoundWave::IsReadyForFinishDestroy()
|
||||
{
|
||||
// Ensure that there is no thread contention for generating data
|
||||
FScopeLock Lock(&SyncContext);
|
||||
|
||||
return USoundWaveProcedural::IsReadyForFinishDestroy();
|
||||
}
|
||||
@@ -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 <Objects/Media/NDIMediaTexture2D.h>
|
||||
#include <Objects/Media/NDIMediaTextureResource.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
|
||||
UNDIMediaTexture2D::UNDIMediaTexture2D(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
this->SetMyResource(nullptr);
|
||||
}
|
||||
|
||||
void UNDIMediaTexture2D::UpdateTextureReference(FRHICommandList& RHICmdList, FTextureRHIRef Reference)
|
||||
{
|
||||
if (GetMyResource() != nullptr)
|
||||
{
|
||||
if (Reference.IsValid() && GetMyResource()->TextureRHI != Reference)
|
||||
{
|
||||
GetMyResource()->TextureRHI = (FTextureRHIRef&)Reference;
|
||||
RHIUpdateTextureReference(TextureReference.TextureReferenceRHI, GetMyResource()->TextureRHI);
|
||||
}
|
||||
else if (!Reference.IsValid())
|
||||
{
|
||||
if (FNDIMediaTextureResource* TextureResource = static_cast<FNDIMediaTextureResource*>(this->GetMyResource()))
|
||||
{
|
||||
ENQUEUE_RENDER_COMMAND(FNDIMediaTexture2DUpdateTextureReference)
|
||||
([this](FRHICommandListImmediate& RHICmdList) {
|
||||
|
||||
static int32 DefaultWidth = 1280;
|
||||
static int32 DefaultHeight = 720;
|
||||
|
||||
// Set the default video texture to reference nothing
|
||||
TRefCountPtr<FRHITexture> RenderableTexture;
|
||||
|
||||
const FRHITextureCreateDesc CreateDesc = FRHITextureCreateDesc::Create2D(TEXT("NDIMediaTexture2DUpdateTextureReference"))
|
||||
.SetExtent(DefaultWidth, DefaultHeight)
|
||||
.SetFormat(EPixelFormat::PF_B8G8R8A8)
|
||||
.SetNumMips(1)
|
||||
.SetFlags(ETextureCreateFlags::Dynamic | ETextureCreateFlags::RenderTargetable)
|
||||
.SetClearValue(FClearValueBinding(FLinearColor(0.0f, 0.0f, 0.0f)));
|
||||
|
||||
RenderableTexture = RHICreateTexture(CreateDesc);
|
||||
|
||||
GetMyResource()->TextureRHI = (FTextureRHIRef&)RenderableTexture;
|
||||
|
||||
RHIUpdateTextureReference(TextureReference.TextureReferenceRHI, GetMyResource()->TextureRHI);
|
||||
});
|
||||
|
||||
// Make sure _RenderThread is executed before continuing
|
||||
FlushRenderingCommands();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FTextureResource* UNDIMediaTexture2D::CreateResource()
|
||||
{
|
||||
if (this->GetMyResource() != nullptr)
|
||||
{
|
||||
delete this->GetMyResource();
|
||||
this->SetMyResource(nullptr);
|
||||
}
|
||||
|
||||
if (FNDIMediaTextureResource* TextureResource = new FNDIMediaTextureResource(this))
|
||||
{
|
||||
this->SetMyResource(TextureResource);
|
||||
|
||||
ENQUEUE_RENDER_COMMAND(FNDIMediaTexture2DUpdateTextureReference)
|
||||
([this](FRHICommandListImmediate& RHICmdList) {
|
||||
|
||||
static int32 DefaultWidth = 1280;
|
||||
static int32 DefaultHeight = 720;
|
||||
|
||||
// Set the default video texture to reference nothing
|
||||
TRefCountPtr<FRHITexture> RenderableTexture;
|
||||
|
||||
const FRHITextureCreateDesc CreateDesc = FRHITextureCreateDesc::Create2D(TEXT("NDIMediaTexture2DCreateResourceTexture"))
|
||||
.SetExtent(DefaultWidth, DefaultHeight)
|
||||
.SetFormat(EPixelFormat::PF_B8G8R8A8)
|
||||
.SetNumMips(1)
|
||||
.SetFlags(ETextureCreateFlags::Dynamic | ETextureCreateFlags::RenderTargetable)
|
||||
.SetClearValue(FClearValueBinding(FLinearColor(0.0f, 0.0f, 0.0f)));
|
||||
|
||||
RenderableTexture = RHICreateTexture(CreateDesc);
|
||||
|
||||
GetMyResource()->TextureRHI = (FTextureRHIRef&)RenderableTexture;
|
||||
|
||||
RHIUpdateTextureReference(TextureReference.TextureReferenceRHI, GetMyResource()->TextureRHI);
|
||||
});
|
||||
}
|
||||
|
||||
return this->GetMyResource();
|
||||
}
|
||||
|
||||
void UNDIMediaTexture2D::GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize)
|
||||
{
|
||||
Super::GetResourceSizeEx(CumulativeResourceSize);
|
||||
|
||||
if (FNDIMediaTextureResource* CurrentResource = static_cast<FNDIMediaTextureResource*>(this->GetMyResource()))
|
||||
{
|
||||
CumulativeResourceSize.AddUnknownMemoryBytes(CurrentResource->GetResourceSize());
|
||||
}
|
||||
}
|
||||
|
||||
float UNDIMediaTexture2D::GetSurfaceHeight() const
|
||||
{
|
||||
return GetMyResource() != nullptr ? GetMyResource()->GetSizeY() : 0.0f;
|
||||
}
|
||||
|
||||
float UNDIMediaTexture2D::GetSurfaceWidth() const
|
||||
{
|
||||
return GetMyResource() != nullptr ? GetMyResource()->GetSizeX() : 0.0f;
|
||||
}
|
||||
|
||||
float UNDIMediaTexture2D::GetSurfaceDepth() const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
uint32 UNDIMediaTexture2D::GetSurfaceArraySize() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMaterialValueType UNDIMediaTexture2D::GetMaterialType() const
|
||||
{
|
||||
return MCT_Texture2D;
|
||||
}
|
||||
|
||||
|
||||
ETextureClass UNDIMediaTexture2D::GetTextureClass() const
|
||||
{
|
||||
return ETextureClass::Other2DNoSource;
|
||||
}
|
||||
|
||||
void UNDIMediaTexture2D::SetMyResource(FTextureResource* ResourceIn)
|
||||
{
|
||||
SetResource(ResourceIn);
|
||||
}
|
||||
|
||||
FTextureResource* UNDIMediaTexture2D::GetMyResource()
|
||||
{
|
||||
return GetResource();
|
||||
}
|
||||
|
||||
const FTextureResource* UNDIMediaTexture2D::GetMyResource() const
|
||||
{
|
||||
return GetResource();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
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 <Objects/Media/NDIMediaTextureResource.h>
|
||||
|
||||
#include <RHI.h>
|
||||
#include <DeviceProfiles/DeviceProfile.h>
|
||||
#include <DeviceProfiles/DeviceProfileManager.h>
|
||||
#include <Objects/Media/NDIMediaTexture2D.h>
|
||||
#include <RenderUtils.h>
|
||||
|
||||
/**
|
||||
Constructs a new instance of this object specifying a media texture owner
|
||||
|
||||
@param Owner The media object used as the owner for this object
|
||||
*/
|
||||
FNDIMediaTextureResource::FNDIMediaTextureResource(UNDIMediaTexture2D* Owner)
|
||||
{
|
||||
this->MediaTexture = Owner;
|
||||
}
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
void FNDIMediaTextureResource::InitRHI(FRHICommandListBase& RHICmdList)
|
||||
#else
|
||||
void FNDIMediaTextureResource::InitDynamicRHI()
|
||||
#endif
|
||||
{
|
||||
if (this->MediaTexture != nullptr)
|
||||
{
|
||||
FSamplerStateInitializerRHI SamplerStateInitializer(
|
||||
(ESamplerFilter)UDeviceProfileManager::Get().GetActiveProfile()->GetTextureLODSettings()->GetSamplerFilter(
|
||||
MediaTexture),
|
||||
AM_Border, AM_Border, AM_Wrap);
|
||||
|
||||
SamplerStateRHI = RHICreateSamplerState(SamplerStateInitializer);
|
||||
}
|
||||
}
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
void FNDIMediaTextureResource::ReleaseRHI()
|
||||
#else
|
||||
void FNDIMediaTextureResource::ReleaseDynamicRHI()
|
||||
#endif
|
||||
{
|
||||
// Release the TextureRHI bound by this object
|
||||
this->TextureRHI.SafeRelease();
|
||||
|
||||
// Ensure that we have a owning media texture
|
||||
if (this->MediaTexture != nullptr)
|
||||
{
|
||||
// Remove the texture reference associated with the owner texture object
|
||||
RHIUpdateTextureReference(MediaTexture->TextureReference.TextureReferenceRHI, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
SIZE_T FNDIMediaTextureResource::GetResourceSize()
|
||||
{
|
||||
return CalcTextureSize(GetSizeX(), GetSizeY(), EPixelFormat::PF_A8R8G8B8, 1);
|
||||
}
|
||||
|
||||
uint32 FNDIMediaTextureResource::GetSizeX() const
|
||||
{
|
||||
return this->TextureRHI.IsValid() ? TextureRHI->GetSizeXYZ().X : 0;
|
||||
}
|
||||
|
||||
uint32 FNDIMediaTextureResource::GetSizeY() const
|
||||
{
|
||||
return this->TextureRHI.IsValid() ? TextureRHI->GetSizeXYZ().Y : 0;
|
||||
}
|
||||
492
Plugins/NDIIO/Source/Core/Classes/Player/NDIMediaPlayer.cpp
Normal file
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
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 "NDIMediaPlayer.h"
|
||||
|
||||
#include <MediaIOCoreSamples.h>
|
||||
#include <MediaIOCoreTextureSampleBase.h>
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
#include <MediaIOCoreTextureSampleConverter.h>
|
||||
#endif
|
||||
#include <MediaIOCoreAudioSampleBase.h>
|
||||
#include <IMediaEventSink.h>
|
||||
#include <IMediaTextureSampleConverter.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FNDIMediaPlayer"
|
||||
|
||||
|
||||
|
||||
// An NDI-derived media texture sample, representing a frame of video
|
||||
class NDIMediaTextureSample : public FMediaIOCoreTextureSampleBase, public IMediaTextureSampleConverter
|
||||
{
|
||||
using Super = FMediaIOCoreTextureSampleBase;
|
||||
|
||||
public:
|
||||
|
||||
NDIMediaTextureSample() = default;
|
||||
virtual ~NDIMediaTextureSample() = default;
|
||||
|
||||
bool Initialize(const NDIlib_video_frame_v2_t& InVideoFrame, FTimespan InTime, UNDIMediaReceiver* InReceiver)
|
||||
{
|
||||
FreeSample();
|
||||
|
||||
VideoFrame = InVideoFrame;
|
||||
Receiver = InReceiver;
|
||||
|
||||
if (InVideoFrame.FourCC == NDIlib_FourCC_video_type_UYVY)
|
||||
SetBuffer(InVideoFrame.p_data, InVideoFrame.line_stride_in_bytes * InVideoFrame.yres);
|
||||
else if (InVideoFrame.FourCC == NDIlib_FourCC_video_type_UYVA)
|
||||
SetBuffer(InVideoFrame.p_data, InVideoFrame.line_stride_in_bytes * InVideoFrame.yres +
|
||||
InVideoFrame.xres*InVideoFrame.yres);
|
||||
else
|
||||
return false;
|
||||
|
||||
VideoFrame.p_data = Buffer.GetData();
|
||||
|
||||
SetProperties(InVideoFrame.line_stride_in_bytes, InVideoFrame.xres, InVideoFrame.yres, EMediaTextureSampleFormat::CharUYVY,
|
||||
InTime, FFrameRate(InVideoFrame.frame_rate_N, InVideoFrame.frame_rate_D), FTimecode(),
|
||||
true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const FMatrix& GetYUVToRGBMatrix() const override
|
||||
{
|
||||
return MediaShaders::YuvToRgbRec709Scaled;
|
||||
}
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
virtual void CopyConfiguration(const TSharedPtr<FMediaIOCoreTextureSampleBase>& SourceSample) override
|
||||
{
|
||||
Super::CopyConfiguration(SourceSample);
|
||||
|
||||
if (SourceSample.IsValid())
|
||||
{
|
||||
TSharedPtr<NDIMediaTextureSample> NDISamplePtr = StaticCastSharedPtr<NDIMediaTextureSample>(SourceSample);
|
||||
VideoFrame = NDISamplePtr->VideoFrame;
|
||||
Receiver = NDISamplePtr->Receiver;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual uint32 GetConverterInfoFlags() const override
|
||||
{
|
||||
return ConverterInfoFlags_WillCreateOutputTexture;
|
||||
}
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 5)) // 5.5 or later
|
||||
virtual bool Convert(FRHICommandListImmediate& RHICmdList, FTextureRHIRef& InDstTexture, const FConversionHints& Hints) override
|
||||
{
|
||||
if (!Receiver)
|
||||
return false;
|
||||
|
||||
FTextureRHIRef DstTexture(Receiver->DisplayFrame(VideoFrame));
|
||||
InDstTexture = DstTexture;
|
||||
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
virtual bool Convert(FTexture2DRHIRef & InDstTexture, const FConversionHints & Hints) override
|
||||
{
|
||||
if (!Receiver)
|
||||
return false;
|
||||
|
||||
FTexture2DRHIRef DstTexture(Receiver->DisplayFrame(VideoFrame));
|
||||
InDstTexture = DstTexture;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
NDIlib_video_frame_v2_t VideoFrame;
|
||||
UNDIMediaReceiver* Receiver { nullptr };
|
||||
//FMediaTimeStamp Time;
|
||||
//std::vector<uint8_t> Data;
|
||||
};
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
class NDIMediaTextureSampleConverter : public FMediaIOCoreTextureSampleConverter
|
||||
{
|
||||
using Super = FMediaIOCoreTextureSampleConverter;
|
||||
|
||||
public:
|
||||
|
||||
NDIMediaTextureSampleConverter() = default;
|
||||
virtual ~NDIMediaTextureSampleConverter() = default;
|
||||
|
||||
virtual void Setup(const TSharedPtr<FMediaIOCoreTextureSampleBase>& InSample) override
|
||||
{
|
||||
FMediaIOCoreTextureSampleConverter::Setup(InSample);
|
||||
JITRProxySample = InSample;
|
||||
}
|
||||
|
||||
virtual uint32 GetConverterInfoFlags() const override
|
||||
{
|
||||
return ConverterInfoFlags_WillCreateOutputTexture;
|
||||
}
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 5)) // 5.5 or later
|
||||
virtual bool Convert(FRHICommandListImmediate& RHICmdList, FTextureRHIRef& InDstTexture, const FConversionHints& Hints) override
|
||||
{
|
||||
if (FMediaIOCoreTextureSampleConverter::Convert(RHICmdList, InDstTexture, Hints))
|
||||
{
|
||||
TSharedPtr<FMediaIOCoreTextureSampleBase> SamplePtr = JITRProxySample.Pin();
|
||||
if (SamplePtr.IsValid())
|
||||
{
|
||||
TSharedPtr<NDIMediaTextureSample> NDISamplePtr = StaticCastSharedPtr<NDIMediaTextureSample>(SamplePtr);
|
||||
return NDISamplePtr->Convert(RHICmdList, InDstTexture, Hints);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
virtual bool Convert(FTexture2DRHIRef& InDstTexture, const FConversionHints& Hints) override
|
||||
{
|
||||
if (FMediaIOCoreTextureSampleConverter::Convert(InDstTexture, Hints))
|
||||
{
|
||||
TSharedPtr<FMediaIOCoreTextureSampleBase> SamplePtr = JITRProxySample.Pin();
|
||||
if (SamplePtr.IsValid())
|
||||
{
|
||||
TSharedPtr<NDIMediaTextureSample> NDISamplePtr = StaticCastSharedPtr<NDIMediaTextureSample>(SamplePtr);
|
||||
return NDISamplePtr->Convert(InDstTexture, Hints);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
TWeakPtr<FMediaIOCoreTextureSampleBase> JITRProxySample;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
class NDIMediaTextureSamplePool : public TMediaObjectPool<NDIMediaTextureSample>
|
||||
{};
|
||||
|
||||
|
||||
// An NDI-derived media audio sample, representing a frame of audio
|
||||
class NDIMediaAudioSample : public FMediaIOCoreAudioSampleBase
|
||||
{
|
||||
using Super = FMediaIOCoreAudioSampleBase;
|
||||
|
||||
public:
|
||||
};
|
||||
|
||||
class NDIMediaAudioSamplePool : public TMediaObjectPool<NDIMediaAudioSample>
|
||||
{};
|
||||
|
||||
|
||||
|
||||
FNDIMediaPlayer::FNDIMediaPlayer(IMediaEventSink& InEventSink)
|
||||
: Super(InEventSink)
|
||||
, NDIPlayerState(EMediaState::Closed)
|
||||
, EventSink(InEventSink)
|
||||
, TextureSamplePool(new NDIMediaTextureSamplePool)
|
||||
, AudioSamplePool(new NDIMediaAudioSamplePool)
|
||||
{}
|
||||
|
||||
|
||||
FNDIMediaPlayer::~FNDIMediaPlayer()
|
||||
{
|
||||
Close();
|
||||
|
||||
delete TextureSamplePool;
|
||||
delete AudioSamplePool;
|
||||
}
|
||||
|
||||
|
||||
FGuid FNDIMediaPlayer::GetPlayerPluginGUID() const
|
||||
{
|
||||
static FGuid PlayerPluginGUID(0x71b13c2b, 0x70874965, 0x8a0e23f7, 0x5be6698f);
|
||||
return PlayerPluginGUID;
|
||||
}
|
||||
|
||||
|
||||
bool FNDIMediaPlayer::Open(const FString& Url, const IMediaOptions* Options)
|
||||
{
|
||||
if (!Super::Open(Url, Options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MaxNumVideoFrameBuffer = Options->GetMediaOption(NDIMediaOption::MaxVideoFrameBuffer, (int64)8);
|
||||
MaxNumAudioFrameBuffer = Options->GetMediaOption(NDIMediaOption::MaxAudioFrameBuffer, (int64)8);
|
||||
MaxNumMetadataFrameBuffer = Options->GetMediaOption(NDIMediaOption::MaxAncillaryFrameBuffer, (int64)8);
|
||||
|
||||
// Setup our different supported channels based on source settings
|
||||
SetupSampleChannels();
|
||||
|
||||
// If the player is opened with an NDIMediaReceiver, use that. Otherwise create an internal one.
|
||||
bool bIsNDIMediaReceiver = Options->HasMediaOption(NDIMediaOption::IsNDIMediaReceiver);
|
||||
if (bIsNDIMediaReceiver)
|
||||
{
|
||||
Receiver = static_cast<UNDIMediaReceiver*>(const_cast<IMediaOptions*>(Options));
|
||||
bInternalReceiver = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Receiver = NewObject<UNDIMediaReceiver>();
|
||||
bInternalReceiver = true;
|
||||
}
|
||||
|
||||
// Hook into the video and audio captures
|
||||
Receiver->OnNDIReceiverVideoCaptureEvent.Remove(VideoCaptureEventHandle);
|
||||
VideoCaptureEventHandle = Receiver->OnNDIReceiverVideoCaptureEvent.AddLambda([this](UNDIMediaReceiver* receiver, const NDIlib_video_frame_v2_t& video_frame)
|
||||
{
|
||||
this->DisplayFrame(video_frame);
|
||||
});
|
||||
Receiver->OnNDIReceiverAudioCaptureEvent.Remove(AudioCaptureEventHandle);
|
||||
AudioCaptureEventHandle = Receiver->OnNDIReceiverAudioCaptureEvent.AddLambda([this](UNDIMediaReceiver* receiver, const NDIlib_audio_frame_v2_t& audio_frame)
|
||||
{
|
||||
this->PlayAudio(audio_frame);
|
||||
});
|
||||
|
||||
// Control the player's state based on the receiver connecting and disconnecting
|
||||
Receiver->OnNDIReceiverConnectedEvent.Remove(ConnectedEventHandle);
|
||||
ConnectedEventHandle = Receiver->OnNDIReceiverConnectedEvent.AddLambda([this](UNDIMediaReceiver* receiver)
|
||||
{
|
||||
this->NDIPlayerState = EMediaState::Playing;
|
||||
});
|
||||
Receiver->OnNDIReceiverDisconnectedEvent.Remove(DisconnectedEventHandle);
|
||||
DisconnectedEventHandle = Receiver->OnNDIReceiverDisconnectedEvent.AddLambda([this](UNDIMediaReceiver* receiver)
|
||||
{
|
||||
this->NDIPlayerState = EMediaState::Closed;
|
||||
});
|
||||
|
||||
|
||||
// Get ready to connect
|
||||
CurrentState = EMediaState::Preparing;
|
||||
NDIPlayerState = EMediaState::Preparing;
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::MediaConnecting);
|
||||
|
||||
// Start up the receiver under the player's control.
|
||||
// Use the provided URL as the source if given, otherwise use the connection info set for the receiver
|
||||
FString Scheme;
|
||||
FString Location;
|
||||
if (Url.Split(TEXT("://"), &Scheme, &Location, ESearchCase::CaseSensitive))
|
||||
{
|
||||
FNDIConnectionInformation ConnectionInformation = Receiver->ConnectionSetting;
|
||||
ConnectionInformation.SourceName = Location;
|
||||
Receiver->Initialize(ConnectionInformation, UNDIMediaReceiver::EUsage::Controlled);
|
||||
}
|
||||
else
|
||||
{
|
||||
Receiver->Initialize(UNDIMediaReceiver::EUsage::Controlled);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void FNDIMediaPlayer::Close()
|
||||
{
|
||||
NDIPlayerState = EMediaState::Closed;
|
||||
|
||||
if (Receiver != nullptr)
|
||||
{
|
||||
// Disconnect from receiver events
|
||||
Receiver->OnNDIReceiverVideoCaptureEvent.Remove(VideoCaptureEventHandle);
|
||||
VideoCaptureEventHandle.Reset();
|
||||
Receiver->OnNDIReceiverAudioCaptureEvent.Remove(AudioCaptureEventHandle);
|
||||
AudioCaptureEventHandle.Reset();
|
||||
Receiver->OnNDIReceiverConnectedEvent.Remove(ConnectedEventHandle);
|
||||
ConnectedEventHandle.Reset();
|
||||
Receiver->OnNDIReceiverDisconnectedEvent.Remove(DisconnectedEventHandle);
|
||||
DisconnectedEventHandle.Reset();
|
||||
|
||||
// Shut down the receiver
|
||||
Receiver->Shutdown();
|
||||
|
||||
// If the player created the receiver, destroy the receiver
|
||||
if (bInternalReceiver)
|
||||
Receiver->ConditionalBeginDestroy();
|
||||
|
||||
Receiver = nullptr;
|
||||
bInternalReceiver = false;
|
||||
}
|
||||
|
||||
TextureSamplePool->Reset();
|
||||
AudioSamplePool->Reset();
|
||||
|
||||
Super::Close();
|
||||
}
|
||||
|
||||
|
||||
void FNDIMediaPlayer::TickInput(FTimespan DeltaTime, FTimespan Timecode)
|
||||
{
|
||||
// Update player state
|
||||
EMediaState NewState = NDIPlayerState;
|
||||
|
||||
if (NewState != CurrentState)
|
||||
{
|
||||
CurrentState = NewState;
|
||||
if (CurrentState == EMediaState::Playing)
|
||||
{
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::TracksChanged);
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::MediaOpened);
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackResumed);
|
||||
}
|
||||
else if (NewState == EMediaState::Error)
|
||||
{
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::MediaOpenFailed);
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentState != EMediaState::Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TickTimeManagement();
|
||||
}
|
||||
|
||||
|
||||
void FNDIMediaPlayer::TickFetch(FTimespan DeltaTime, FTimespan Timecode)
|
||||
{
|
||||
Super::TickFetch(DeltaTime, Timecode);
|
||||
|
||||
if ((CurrentState == EMediaState::Preparing) || (CurrentState == EMediaState::Playing))
|
||||
{
|
||||
if (Receiver != nullptr)
|
||||
{
|
||||
// Ask receiver to capture a new frame of video and audio.
|
||||
// Will call DisplayFrame() and PlayAudio() through capture event.
|
||||
Receiver->CaptureConnectedAudio();
|
||||
Receiver->CaptureConnectedVideo();
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentState == EMediaState::Playing)
|
||||
{
|
||||
ProcessFrame();
|
||||
VerifyFrameDropCount();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FNDIMediaPlayer::ProcessFrame()
|
||||
{
|
||||
if (CurrentState == EMediaState::Playing)
|
||||
{
|
||||
// No need to lock here. That info is only used for debug information.
|
||||
//AudioTrackFormat.NumChannels = 0;//NDIThreadAudioChannels;
|
||||
//AudioTrackFormat.SampleRate = 0;//NDIThreadAudioSampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FNDIMediaPlayer::DisplayFrame(const NDIlib_video_frame_v2_t& video_frame)
|
||||
{
|
||||
auto TextureSample = TextureSamplePool->AcquireShared();
|
||||
|
||||
if (TextureSample->Initialize(video_frame, FTimespan::FromSeconds(GetPlatformSeconds()), Receiver))
|
||||
{
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
AddVideoSample(TextureSample);
|
||||
#else
|
||||
Samples->AddVideo(TextureSample);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FNDIMediaPlayer::PlayAudio(const NDIlib_audio_frame_v2_t& audio_frame)
|
||||
{
|
||||
auto AudioSample = AudioSamplePool->AcquireShared();
|
||||
|
||||
// UE wants 32bit signed interleaved audio data, so need to convert the NDI audio.
|
||||
// Fortunately the NDI library has a utility function to do that.
|
||||
|
||||
// Get a buffer to convert to
|
||||
const int32 available_samples = audio_frame.no_samples * audio_frame.no_channels;
|
||||
void* SampleBuffer = AudioSample->RequestBuffer(available_samples);
|
||||
|
||||
if (SampleBuffer != nullptr)
|
||||
{
|
||||
// Format to convert to
|
||||
NDIlib_audio_frame_interleaved_32s_t audio_frame_32s(
|
||||
audio_frame.sample_rate,
|
||||
audio_frame.no_channels,
|
||||
audio_frame.no_samples,
|
||||
audio_frame.timecode,
|
||||
20,
|
||||
static_cast<int32_t*>(SampleBuffer));
|
||||
|
||||
// Convert received NDI audio
|
||||
NDIlib_util_audio_to_interleaved_32s_v2(&audio_frame, &audio_frame_32s);
|
||||
|
||||
// Supply converted audio data
|
||||
if (AudioSample->SetProperties(available_samples
|
||||
, audio_frame_32s.no_channels
|
||||
, audio_frame_32s.sample_rate
|
||||
, FTimespan::FromSeconds(GetPlatformSeconds())
|
||||
, TOptional<FTimecode>()))
|
||||
{
|
||||
Samples->AddAudio(AudioSample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FNDIMediaPlayer::VerifyFrameDropCount()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool FNDIMediaPlayer::IsHardwareReady() const
|
||||
{
|
||||
return NDIPlayerState == EMediaState::Playing ? true : false;
|
||||
}
|
||||
|
||||
|
||||
void FNDIMediaPlayer::SetupSampleChannels()
|
||||
{
|
||||
FMediaIOSamplingSettings VideoSettings = BaseSettings;
|
||||
VideoSettings.BufferSize = MaxNumVideoFrameBuffer;
|
||||
Samples->InitializeVideoBuffer(VideoSettings);
|
||||
|
||||
FMediaIOSamplingSettings AudioSettings = BaseSettings;
|
||||
AudioSettings.BufferSize = MaxNumAudioFrameBuffer;
|
||||
Samples->InitializeAudioBuffer(AudioSettings);
|
||||
|
||||
FMediaIOSamplingSettings MetadataSettings = BaseSettings;
|
||||
MetadataSettings.BufferSize = MaxNumMetadataFrameBuffer;
|
||||
Samples->InitializeMetadataBuffer(MetadataSettings);
|
||||
}
|
||||
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
TSharedPtr<FMediaIOCoreTextureSampleBase> FNDIMediaPlayer::AcquireTextureSample_AnyThread() const
|
||||
{
|
||||
return TextureSamplePool->AcquireShared();
|
||||
}
|
||||
|
||||
TSharedPtr<FMediaIOCoreTextureSampleConverter> FNDIMediaPlayer::CreateTextureSampleConverter() const
|
||||
{
|
||||
return MakeShared<NDIMediaTextureSampleConverter>();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//~ ITimedDataInput interface
|
||||
#if WITH_EDITOR
|
||||
const FSlateBrush* FNDIMediaPlayer::GetDisplayIcon() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
75
Plugins/NDIIO/Source/Core/Classes/Player/NDIMediaPlayer.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <NDIIOPluginAPI.h>
|
||||
#include <Objects/Media/NDIMediaReceiver.h>
|
||||
#include <MediaIOCorePlayerBase.h>
|
||||
|
||||
|
||||
class FNDIMediaPlayer : public FMediaIOCorePlayerBase
|
||||
{
|
||||
using Super = FMediaIOCorePlayerBase;
|
||||
|
||||
public:
|
||||
FNDIMediaPlayer(IMediaEventSink& InEventSink);
|
||||
|
||||
virtual ~FNDIMediaPlayer();
|
||||
|
||||
|
||||
//~ IMediaPlayer interface
|
||||
virtual FGuid GetPlayerPluginGUID() const override;
|
||||
virtual bool Open(const FString& Url, const IMediaOptions* Options) override;
|
||||
virtual void Close() override;
|
||||
|
||||
virtual void TickInput(FTimespan DeltaTime, FTimespan Timecode) override;
|
||||
virtual void TickFetch(FTimespan DeltaTime, FTimespan Timecode) override;
|
||||
|
||||
protected:
|
||||
virtual bool IsHardwareReady() const override;
|
||||
virtual void SetupSampleChannels() override;
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
virtual TSharedPtr<FMediaIOCoreTextureSampleBase> AcquireTextureSample_AnyThread() const override;
|
||||
virtual TSharedPtr<FMediaIOCoreTextureSampleConverter> CreateTextureSampleConverter() const override;
|
||||
#endif
|
||||
|
||||
void DisplayFrame(const NDIlib_video_frame_v2_t& video_frame);
|
||||
void PlayAudio(const NDIlib_audio_frame_v2_t& audio_frame);
|
||||
|
||||
void ProcessFrame();
|
||||
void VerifyFrameDropCount();
|
||||
|
||||
public:
|
||||
//~ ITimedDataInput interface
|
||||
#if WITH_EDITOR
|
||||
virtual const FSlateBrush* GetDisplayIcon() const override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
/** Max sample count our different buffer can hold. Taken from MediaSource */
|
||||
int32 MaxNumAudioFrameBuffer = 0;
|
||||
int32 MaxNumMetadataFrameBuffer = 0;
|
||||
int32 MaxNumVideoFrameBuffer = 0;
|
||||
|
||||
/** Current state of the media player. */
|
||||
EMediaState NDIPlayerState = EMediaState::Closed;
|
||||
|
||||
/** The media event handler. */
|
||||
IMediaEventSink& EventSink;
|
||||
|
||||
UNDIMediaReceiver* Receiver = nullptr;
|
||||
bool bInternalReceiver = true;
|
||||
|
||||
FDelegateHandle VideoCaptureEventHandle;
|
||||
FDelegateHandle AudioCaptureEventHandle;
|
||||
FDelegateHandle ConnectedEventHandle;
|
||||
FDelegateHandle DisconnectedEventHandle;
|
||||
|
||||
class NDIMediaTextureSamplePool* TextureSamplePool;
|
||||
class NDIMediaAudioSamplePool* AudioSamplePool;
|
||||
};
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 <Structures/NDIBroadcastConfiguration.h>
|
||||
|
||||
/** Copies an existing instance to this object */
|
||||
FNDIBroadcastConfiguration::FNDIBroadcastConfiguration(const FNDIBroadcastConfiguration& other)
|
||||
{
|
||||
// perform a deep copy of the 'other' structure and store the values in this object
|
||||
this->FrameRate = other.FrameRate;
|
||||
this->FrameSize = other.FrameSize;
|
||||
}
|
||||
|
||||
/** Copies existing instance properties to this object */
|
||||
FNDIBroadcastConfiguration& FNDIBroadcastConfiguration::operator=(const FNDIBroadcastConfiguration& other)
|
||||
{
|
||||
// perform a deep copy of the 'other' structure
|
||||
this->FrameRate = other.FrameRate;
|
||||
this->FrameSize = other.FrameSize;
|
||||
|
||||
// return the result of the copy
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Compares this object to 'other' and returns a determination of whether they are equal */
|
||||
bool FNDIBroadcastConfiguration::operator==(const FNDIBroadcastConfiguration& other) const
|
||||
{
|
||||
// return the value of a deep compare against the 'other' structure
|
||||
return this->FrameRate == other.FrameRate && this->FrameSize == other.FrameSize;
|
||||
}
|
||||
|
||||
/** Attempts to serialize this object using an Archive object */
|
||||
FArchive& FNDIBroadcastConfiguration::Serialize(FArchive& Ar)
|
||||
{
|
||||
// we want to make sure that we are able to serialize this object, over many different version of this structure
|
||||
int32 current_version = 0;
|
||||
|
||||
// serialize this structure
|
||||
return Ar << current_version << this->FrameRate.Numerator << this->FrameRate.Denominator << this->FrameSize;
|
||||
}
|
||||
|
||||
/** Compares this object to 'other" and returns a determination of whether they are NOT equal */
|
||||
bool FNDIBroadcastConfiguration::operator!=(const FNDIBroadcastConfiguration& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
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 <Structures/NDIConnectionInformation.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
/** Copies an existing instance to this object */
|
||||
FNDIConnectionInformation::FNDIConnectionInformation(const FNDIConnectionInformation& other)
|
||||
{
|
||||
// perform a deep copy of the 'other' structure and store the values in this object
|
||||
this->Bandwidth = other.Bandwidth;
|
||||
this->MachineName = other.MachineName;
|
||||
this->SourceName = other.SourceName;
|
||||
this->StreamName = other.StreamName;
|
||||
this->Url = other.Url;
|
||||
this->bMuteAudio = other.bMuteAudio;
|
||||
this->bMuteVideo = other.bMuteVideo;
|
||||
}
|
||||
|
||||
/** Copies existing instance properties to this object */
|
||||
FNDIConnectionInformation& FNDIConnectionInformation::operator=(const FNDIConnectionInformation& other)
|
||||
{
|
||||
// perform a deep copy of the 'other' structure
|
||||
this->Bandwidth = other.Bandwidth;
|
||||
this->MachineName = other.MachineName;
|
||||
this->SourceName = other.SourceName;
|
||||
this->StreamName = other.StreamName;
|
||||
this->Url = other.Url;
|
||||
this->bMuteAudio = other.bMuteAudio;
|
||||
this->bMuteVideo = other.bMuteVideo;
|
||||
|
||||
// return the result of the copy
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Compares this object to 'other' and returns a determination of whether they are equal */
|
||||
bool FNDIConnectionInformation::operator==(const FNDIConnectionInformation& other) const
|
||||
{
|
||||
// return the value of a deep compare against the 'other' structure
|
||||
return this->Bandwidth == other.Bandwidth &&
|
||||
this->MachineName == other.MachineName && this->SourceName == other.SourceName &&
|
||||
this->StreamName == other.StreamName && this->Url == other.Url &&
|
||||
this->bMuteAudio == other.bMuteAudio && this->bMuteVideo == other.bMuteVideo;
|
||||
}
|
||||
|
||||
FNDIConnectionInformation::operator NDIlib_recv_bandwidth_e() const
|
||||
{
|
||||
return this->Bandwidth == ENDISourceBandwidth::MetadataOnly ? NDIlib_recv_bandwidth_metadata_only
|
||||
: this->Bandwidth == ENDISourceBandwidth::AudioOnly ? NDIlib_recv_bandwidth_audio_only
|
||||
: this->Bandwidth == ENDISourceBandwidth::Lowest ? NDIlib_recv_bandwidth_lowest
|
||||
: NDIlib_recv_bandwidth_highest;
|
||||
}
|
||||
|
||||
/** Resets the current parameters to the default property values */
|
||||
void FNDIConnectionInformation::Reset()
|
||||
{
|
||||
// Ensure we reset all the properties of this object to nominal default properties
|
||||
this->Bandwidth = ENDISourceBandwidth::Highest;
|
||||
this->MachineName = FString("");
|
||||
this->SourceName = FString("");
|
||||
this->StreamName = FString("");
|
||||
this->Url = FString("");
|
||||
this->bMuteAudio = false;
|
||||
this->bMuteVideo = false;
|
||||
}
|
||||
|
||||
/** Attempts to serialize this object using an Archive object */
|
||||
FArchive& FNDIConnectionInformation::Serialize(FArchive& Ar)
|
||||
{
|
||||
// we want to make sure that we are able to serialize this object, over many different version of this structure
|
||||
int32 current_version = 0;
|
||||
|
||||
// serialize this structure
|
||||
return Ar << current_version << this->Bandwidth
|
||||
<< this->MachineName << this->SourceName << this->StreamName << this->Url
|
||||
<< this->bMuteAudio << this->bMuteVideo;
|
||||
}
|
||||
|
||||
/** Determines whether this object is valid connection information */
|
||||
bool FNDIConnectionInformation::IsValid() const
|
||||
{
|
||||
// Need at least a source name and/or machine+stream name and/or a URL
|
||||
return (!this->SourceName.IsEmpty()) ||
|
||||
((!this->MachineName.IsEmpty()) && (!this->StreamName.IsEmpty())) ||
|
||||
(!this->Url.IsEmpty());
|
||||
}
|
||||
|
||||
FString FNDIConnectionInformation::GetNDIName() const
|
||||
{
|
||||
std::string source_name;
|
||||
|
||||
if(!this->SourceName.IsEmpty())
|
||||
return this->SourceName;
|
||||
|
||||
if ((!this->MachineName.IsEmpty()) && (!this->StreamName.IsEmpty()))
|
||||
return this->MachineName + " (" + this->StreamName + ")";
|
||||
|
||||
return FString();
|
||||
}
|
||||
|
||||
|
||||
/** Compares this object to 'other" and returns a determination of whether they are NOT equal */
|
||||
bool FNDIConnectionInformation::operator!=(const FNDIConnectionInformation& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
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 <Structures/NDIReceiverPerformanceData.h>
|
||||
|
||||
/** Copies an existing instance to this object */
|
||||
FNDIReceiverPerformanceData::FNDIReceiverPerformanceData(const FNDIReceiverPerformanceData& other)
|
||||
{
|
||||
// perform a deep copy of the 'other' structure and store the values in this object
|
||||
this->AudioFrames = other.AudioFrames;
|
||||
this->DroppedAudioFrames = other.DroppedAudioFrames;
|
||||
this->DroppedMetadataFrames = other.DroppedMetadataFrames;
|
||||
this->DroppedVideoFrames = other.DroppedVideoFrames;
|
||||
this->MetadataFrames = other.MetadataFrames;
|
||||
this->VideoFrames = other.VideoFrames;
|
||||
}
|
||||
|
||||
/** Copies existing instance properties to this object */
|
||||
FNDIReceiverPerformanceData& FNDIReceiverPerformanceData::operator=(const FNDIReceiverPerformanceData& other)
|
||||
{
|
||||
// perform a deep copy of the 'other' structure
|
||||
this->AudioFrames = other.AudioFrames;
|
||||
this->DroppedAudioFrames = other.DroppedAudioFrames;
|
||||
this->DroppedMetadataFrames = other.DroppedMetadataFrames;
|
||||
this->DroppedVideoFrames = other.DroppedVideoFrames;
|
||||
this->MetadataFrames = other.MetadataFrames;
|
||||
this->VideoFrames = other.VideoFrames;
|
||||
|
||||
// return the result of the copy
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Compares this object to 'other' and returns a determination of whether they are equal */
|
||||
bool FNDIReceiverPerformanceData::operator==(const FNDIReceiverPerformanceData& other) const
|
||||
{
|
||||
// return the value of a deep compare against the 'other' structure
|
||||
return this->AudioFrames == other.AudioFrames && this->DroppedAudioFrames == other.DroppedAudioFrames &&
|
||||
this->DroppedMetadataFrames == other.DroppedMetadataFrames &&
|
||||
this->DroppedVideoFrames == other.DroppedVideoFrames && this->MetadataFrames == other.MetadataFrames &&
|
||||
this->VideoFrames == other.VideoFrames;
|
||||
}
|
||||
|
||||
/** Resets the current parameters to the default property values */
|
||||
void FNDIReceiverPerformanceData::Reset()
|
||||
{
|
||||
// Ensure we reset all the properties of this object to nominal default properties
|
||||
this->AudioFrames = 0;
|
||||
this->DroppedAudioFrames = 0;
|
||||
this->DroppedMetadataFrames = 0;
|
||||
this->DroppedVideoFrames = 0;
|
||||
this->MetadataFrames = 0;
|
||||
this->VideoFrames = 0;
|
||||
}
|
||||
|
||||
/** Attempts to serialize this object using an Archive object */
|
||||
FArchive& FNDIReceiverPerformanceData::Serialize(FArchive& Ar)
|
||||
{
|
||||
// we want to make sure that we are able to serialize this object, over many different version of this structure
|
||||
int32 current_version = 0;
|
||||
|
||||
// serialize this structure
|
||||
return Ar << current_version << this->AudioFrames << this->DroppedAudioFrames << this->DroppedMetadataFrames
|
||||
<< this->DroppedVideoFrames << this->MetadataFrames << this->VideoFrames;
|
||||
}
|
||||
|
||||
/** Compares this object to 'other" and returns a determination of whether they are NOT equal */
|
||||
bool FNDIReceiverPerformanceData::operator!=(const FNDIReceiverPerformanceData& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
106
Plugins/NDIIO/Source/Core/NDIIO.Build.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class NDIIO : ModuleRules
|
||||
{
|
||||
public NDIIO(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
#if UE_5_2_OR_LATER
|
||||
IWYUSupport = IWYUSupport.Full;
|
||||
#else
|
||||
bEnforceIWYU = true;
|
||||
#endif
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
#region Public Includes
|
||||
|
||||
// Include the Public include paths
|
||||
if (Directory.Exists(Path.Combine(ModuleDirectory, "Public")))
|
||||
{
|
||||
PublicIncludePaths.AddRange(new string[] {
|
||||
// ... add public include paths required here ...
|
||||
Path.Combine(ModuleDirectory, "Public" ),
|
||||
});
|
||||
}
|
||||
|
||||
// Define the public dependencies
|
||||
PublicDependencyModuleNames.AddRange(new string[] {
|
||||
"Engine",
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Projects",
|
||||
"NDIIOShaders"
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Includes
|
||||
|
||||
if (Directory.Exists(Path.Combine(ModuleDirectory, "Private")))
|
||||
{
|
||||
PrivateIncludePaths.AddRange(new string[] {
|
||||
// ... add other private include paths required here ...
|
||||
Path.Combine(ModuleDirectory, "Private" )
|
||||
});
|
||||
}
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
"Renderer",
|
||||
"RenderCore",
|
||||
"RHI",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"UMG",
|
||||
"ImageWrapper",
|
||||
"AudioMixer",
|
||||
"AudioExtensions",
|
||||
|
||||
"InputCore",
|
||||
|
||||
"Media",
|
||||
"MediaAssets",
|
||||
"MediaIOCore",
|
||||
"MediaUtils",
|
||||
"TimeManagement",
|
||||
|
||||
"CinematicCamera",
|
||||
|
||||
"XmlParser"
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region Editor Includes
|
||||
|
||||
if (Target.bBuildEditor == true)
|
||||
{
|
||||
PrivateIncludePathModuleNames.AddRange(new string[] {
|
||||
"AssetTools",
|
||||
"TargetPlatform",
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
"UnrealEd",
|
||||
"AssetTools",
|
||||
"MaterialUtilities"
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ThirdParty Includes
|
||||
|
||||
PublicDependencyModuleNames.Add("NDI");
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
59
Plugins/NDIIO/Source/Core/Public/Actors/NDIBroadcastActor.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <GameFramework/Actor.h>
|
||||
#include <Components/NDIViewportCaptureComponent.h>
|
||||
#include <Components/NDIPTZControllerComponent.h>
|
||||
#include <Sound/SoundSubmix.h>
|
||||
|
||||
#include "NDIBroadcastActor.generated.h"
|
||||
|
||||
/**
|
||||
A quick and easy way to capture the from the perspective of a camera that starts broadcasting the viewport
|
||||
immediate upon 'BeginPlay'
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, Category = "NDI IO", META = (DisplayName = "NDI Broadcast Actor"))
|
||||
class NDIIO_API ANDIBroadcastActor : public AActor, public IPTZControllableInterface
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
private:
|
||||
/**
|
||||
The NDI Media Sender representing the configuration of the network source to send audio, video, and metadata
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditInstanceOnly, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
|
||||
UNDIMediaSender* NDIMediaSource = nullptr;
|
||||
|
||||
/**
|
||||
A component used to capture an additional viewport for broadcasting over NDI
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "NDI IO",
|
||||
META = (DisplayName = "Viewport Capture Component", AllowPrivateAccess = true))
|
||||
UNDIViewportCaptureComponent* ViewportCaptureComponent = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditInstanceOnly, Category = "NDI IO",
|
||||
META = (DisplayName = "Audio Submix Capture", AllowPrivateAccess = true))
|
||||
USoundSubmix* SubmixCapture = nullptr;
|
||||
|
||||
/**
|
||||
Component used for PTZ control
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "NDI IO",
|
||||
META = (DisplayName = "PTZ Controller", AllowPrivateAccess = true))
|
||||
UPTZController* PTZController = nullptr;
|
||||
|
||||
public:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
// IPTZControllableInterface
|
||||
virtual FPTZState GetPTZStateFromUE() const override;
|
||||
virtual void SetPTZStateToUE(const FPTZState& PTZState) override;
|
||||
};
|
||||
129
Plugins/NDIIO/Source/Core/Public/Actors/NDIReceiveActor.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <GameFramework/Actor.h>
|
||||
#include <Components/AudioComponent.h>
|
||||
#include <Components/StaticMeshComponent.h>
|
||||
#include <Components/NDIReceiverComponent.h>
|
||||
#include <Enumerations/NDIAudioChannels.h>
|
||||
|
||||
#include "NDIReceiveActor.generated.h"
|
||||
|
||||
UCLASS(HideCategories = (Activation, Rendering, AssetUserData, Material, Attachment, Actor, Input, Cooking, LOD,
|
||||
StaticMesh, Materials),
|
||||
Category = "NDI IO", META = (DisplayName = "NDI Receive Actor"))
|
||||
class NDIIO_API ANDIReceiveActor : public AActor
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
private:
|
||||
/** The desired width of the frame in cm, represented in the virtual scene */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, BlueprintSetter = "SetFrameWidth", Category = "NDI IO",
|
||||
META = (DisplayName = "Frame Width", AllowPrivateAccess = true))
|
||||
float FrameWidth = 177.778;
|
||||
|
||||
/** The desired height of the frame in cm, represented in the virtual scene */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, BlueprintSetter = "SetFrameHeight", Category = "NDI IO",
|
||||
META = (DisplayName = "Frame Height", AllowPrivateAccess = true))
|
||||
float FrameHeight = 100.0f;
|
||||
|
||||
/**
|
||||
Indicates that this object should play the audio.
|
||||
|
||||
*Note Audio played by this object will be played as a UI sound, and won't normalize the audio
|
||||
if the same 'MediaSource' object is being used as the audio source on multiple receivers.
|
||||
*/
|
||||
UPROPERTY(EditInstanceOnly, BlueprintSetter = "UpdateAudioPlayback", Category = "NDI IO",
|
||||
META = (DisplayName = "Enable Audio Playback?", AllowPrivateAccess = true))
|
||||
bool bEnableAudioPlayback = false;
|
||||
|
||||
UPROPERTY(EditInstanceOnly, BlueprintSetter = "UpdateAudioPlaybackChannels", Category = "NDI IO",
|
||||
META = (DisplayName = "Audio Playback Channels", AllowPrivateAccess = true))
|
||||
ENDIAudioChannels AudioPlaybackChannels = ENDIAudioChannels::Mono;
|
||||
|
||||
/** Enable/disable the use of the color channels (if there are any) */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, BlueprintSetter = "EnableColor", Category = "NDI IO",
|
||||
META = (DisplayName = "Enable Color?", AllowPrivateAccess = true))
|
||||
bool bEnableColor = true;
|
||||
|
||||
/** Enable/disable the use of the alpha channel (if there is one) */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, BlueprintSetter = "EnableAlpha", Category = "NDI IO",
|
||||
META = (DisplayName = "Enable Alpha?", AllowPrivateAccess = true))
|
||||
bool bEnableAlpha = true;
|
||||
|
||||
/** The Receiver object used to get Audio, Video, and Metadata from on the network */
|
||||
UPROPERTY(BlueprintReadWrite, EditInstanceOnly, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
|
||||
UNDIMediaReceiver* NDIMediaSource = nullptr;
|
||||
|
||||
/** The component used to display the video received from the Media Sender object */
|
||||
UPROPERTY(Transient, META = (DisplayName = "Video Mesh Component"))
|
||||
UStaticMeshComponent* VideoMeshComponent = nullptr;
|
||||
|
||||
/** The component used to play the audio from the NDI Media source */
|
||||
UPROPERTY(BlueprintReadWrite, EditInstanceOnly, Category= "NDI IO", META = (DisplayName = "Audio Component", AllowPrivateAccess = true))
|
||||
UAudioComponent* AudioComponent = nullptr;
|
||||
|
||||
/** The audio sound wave which receives the audio from the NDI Media source */
|
||||
UPROPERTY(Transient, META = (DisplayName = "Soundwave"))
|
||||
UNDIMediaSoundWave* AudioSoundWave = nullptr;
|
||||
|
||||
private:
|
||||
/** The material we are trying to apply to the video mesh */
|
||||
class UMaterialInterface* VideoMaterial = nullptr;
|
||||
|
||||
/** The dynamic material to apply to the plane object of this actor */
|
||||
UPROPERTY()
|
||||
class UMaterialInstanceDynamic* VideoMaterialInstance = nullptr;
|
||||
|
||||
public:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
|
||||
/**
|
||||
Attempts to set the desired frame size in cm, represented in the virtual scene
|
||||
*/
|
||||
void SetFrameSize(FVector2D InFrameSize);
|
||||
|
||||
/**
|
||||
Returns the current frame size of the 'VideoMeshComponent' for this object
|
||||
*/
|
||||
const FVector2D GetFrameSize() const;
|
||||
|
||||
private:
|
||||
UFUNCTION(BlueprintSetter)
|
||||
void SetFrameHeight(const float& InFrameHeight);
|
||||
|
||||
UFUNCTION(BlueprintSetter)
|
||||
void SetFrameWidth(const float& InFrameWidth);
|
||||
|
||||
UFUNCTION(BlueprintSetter)
|
||||
void UpdateAudioPlayback(const bool& Enabled);
|
||||
|
||||
UFUNCTION(BlueprintSetter)
|
||||
void UpdateAudioPlaybackChannels(const ENDIAudioChannels& Channels);
|
||||
|
||||
UFUNCTION(BlueprintSetter)
|
||||
void EnableColor(const bool& Enabled);
|
||||
|
||||
UFUNCTION(BlueprintSetter)
|
||||
void EnableAlpha(const bool& Enabled);
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
|
||||
virtual void PreEditChange(FProperty* InProperty) override;
|
||||
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
|
||||
#endif
|
||||
|
||||
void ApplyChannelsMode();
|
||||
bool bStoppedForChannelsMode = false;
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <GenlockedTimecodeProvider.h>
|
||||
|
||||
#include <Objects/Media/NDIMediaReceiver.h>
|
||||
|
||||
#include "NDITimecodeProvider.generated.h"
|
||||
|
||||
|
||||
/**
|
||||
Timecode provider from an NDI source
|
||||
*/
|
||||
UCLASS(Blueprintable, editinlinenew, meta=(DisplayName="NDI Timecode Provider"))
|
||||
class NDIIO_API UNDITimecodeProvider : public UGenlockedTimecodeProvider
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
private:
|
||||
/** The Receiver object used to get timecodes from */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
|
||||
UNDIMediaReceiver* NDIMediaSource = nullptr;
|
||||
|
||||
public:
|
||||
//~ UTimecodeProvider interface
|
||||
virtual bool FetchTimecode(FQualifiedFrameTime& OutFrameTime) override;
|
||||
virtual ETimecodeProviderSynchronizationState GetSynchronizationState() const override;
|
||||
virtual bool Initialize(class UEngine* InEngine) override;
|
||||
virtual void Shutdown(class UEngine* InEngine) override;
|
||||
|
||||
//~ UObject interface
|
||||
virtual void BeginDestroy() override;
|
||||
|
||||
private:
|
||||
void ReleaseResources();
|
||||
|
||||
private:
|
||||
FDelegateHandle VideoCaptureEventHandle;
|
||||
FDelegateHandle ConnectedEventHandle;
|
||||
FDelegateHandle DisconnectedEventHandle;
|
||||
|
||||
mutable FCriticalSection StateSyncContext;
|
||||
ETimecodeProviderSynchronizationState State = ETimecodeProviderSynchronizationState::Closed;
|
||||
FQualifiedFrameTime MostRecentFrameTime;
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include <Components/ActorComponent.h>
|
||||
#include <Objects/Media/NDIMediaSender.h>
|
||||
#include <Structures/NDIBroadcastConfiguration.h>
|
||||
|
||||
#include "NDIBroadcastComponent.generated.h"
|
||||
|
||||
/**
|
||||
Provides a wrapper to allow you to modify an NDI Media Sender object from blueprints and perform broadcasting
|
||||
functionality
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Broadcast Component", BlueprintSpawnableComponent))
|
||||
class NDIIO_API UNDIBroadcastComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
private:
|
||||
/** The NDI Media Sender representing the configuration of the network source to send audio, video, and metadata */
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Properties",
|
||||
META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
|
||||
UNDIMediaSender* NDIMediaSource = nullptr;
|
||||
|
||||
public:
|
||||
/**
|
||||
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 Initialize(UNDIMediaSender* InMediaSource = nullptr);
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Start Broadcasting"))
|
||||
bool StartBroadcasting(FString& ErrorMessage);
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Source Name"))
|
||||
void ChangeSourceName(const FString& InSourceName);
|
||||
|
||||
/**
|
||||
Attempts to change the Broadcast information associated with this media object
|
||||
|
||||
@param InConfiguration The new configuration to broadcast
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Broadcast Configuration"))
|
||||
void ChangeBroadcastConfiguration(const FNDIBroadcastConfiguration& 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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Broadcast Texture"))
|
||||
void ChangeBroadcastTexture(UTextureRenderTarget2D* BroadcastTexture = nullptr);
|
||||
|
||||
/**
|
||||
Determines the current tally information.
|
||||
|
||||
@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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Tally Information"))
|
||||
void GetTallyInformation(bool& IsOnPreview, bool& IsOnProgram);
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Number of Connections"))
|
||||
void GetNumberOfConnections(int32& Result);
|
||||
|
||||
/**
|
||||
Attempts to immediately stop sending frames over NDI to any connected receivers
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Stop Broadcasting"))
|
||||
void StopBroadcasting();
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include <Components/ActorComponent.h>
|
||||
#include <Structures/NDIConnectionInformation.h>
|
||||
|
||||
#include "NDIFinderComponent.generated.h"
|
||||
|
||||
/** Delegates **/
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIFinderServiceCollectionChangedDelegate, UNDIFinderComponent*,
|
||||
InComponent);
|
||||
|
||||
/** ******************* **/
|
||||
|
||||
/**
|
||||
A component used for essential functionality when dealing with the finder service. Allowing you to
|
||||
get a collection of sources found on the network.
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Finder Component", BlueprintSpawnableComponent))
|
||||
class NDIIO_API UNDIFinderComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
/** A collection of the current sources and their information, found on the network */
|
||||
UPROPERTY()
|
||||
TArray<FNDIConnectionInformation> NetworkSourceCollection;
|
||||
|
||||
/** A delegate which is broadcast when any change to the network source collection has been detected */
|
||||
UPROPERTY(BlueprintAssignable, META = (DisplayName = "On Network Sources Changed", AllowPrivateAccess = true))
|
||||
FNDIFinderServiceCollectionChangedDelegate OnNetworkSourcesChanged;
|
||||
|
||||
public:
|
||||
/**
|
||||
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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Find Network Source by Name"))
|
||||
const bool FindNetworkSourceByName(FNDIConnectionInformation& ConnectionInformation,
|
||||
FString InSourceName = FString(""));
|
||||
|
||||
/**
|
||||
Returns the current collection of sources found on the network
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "NDI IO", META = (DisplayName = "Get Network Sources"))
|
||||
const TArray<FNDIConnectionInformation> GetNetworkSources();
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
/** An override function for when the network source collection has been changed */
|
||||
UFUNCTION(BlueprintImplementableEvent, META = (DisplayName = "On Network Sources Changed Event"))
|
||||
void OnNetworkSourcesChangedEvent();
|
||||
|
||||
private:
|
||||
/**
|
||||
An Event handler for when the NDI Finder Service notifies listeners that changes have been
|
||||
detected in the network source collection
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnNetworkSourceCollectionChangedEvent() final;
|
||||
|
||||
private:
|
||||
FCriticalSection CollectionSyncContext;
|
||||
};
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <UObject/Interface.h>
|
||||
#include <Components/ActorComponent.h>
|
||||
#include <Objects/Media/NDIMediaSender.h>
|
||||
|
||||
#include "NDIPTZControllerComponent.generated.h"
|
||||
|
||||
|
||||
USTRUCT(BlueprintType, Blueprintable, Category = "NDI IO", META = (DisplayName = "NDI PTZ State"))
|
||||
struct NDIIO_API FPTZState
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PTZ")
|
||||
float Pan;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PTZ")
|
||||
float Tilt;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PTZ")
|
||||
float FieldOfView;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PTZ")
|
||||
float FocusDistance;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PTZ")
|
||||
bool bAutoFocus;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PTZ")
|
||||
FTransform CameraTransform;
|
||||
|
||||
FPTZState()
|
||||
: Pan(0.f)
|
||||
, Tilt(0.f)
|
||||
, FieldOfView(90.f)
|
||||
, FocusDistance(0.5f)
|
||||
, bAutoFocus(false)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FNDIEventDelegate_OnPTZPanTiltSpeed, float, PanSpeed, float, TiltSpeed);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIEventDelegate_OnPTZZoomSpeed, float, ZoomSpeed);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FNDIEventDelegate_OnPTZFocus, bool, AutoMode, float, Distance);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIEventDelegate_OnPTZStore, int, Index);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIEventDelegate_OnPTZRecall, int, Index);
|
||||
|
||||
|
||||
|
||||
UINTERFACE(BlueprintType, Blueprintable, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI PTZ Controllable", BlueprintSpawnableComponent))
|
||||
class NDIIO_API UPTZControllableInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
class IPTZControllableInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FPTZState GetPTZStateFromUE() const = 0;
|
||||
virtual void SetPTZStateToUE(const FPTZState& PTZState) = 0;
|
||||
};
|
||||
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI PTZ Controller", BlueprintSpawnableComponent))
|
||||
class UPTZController : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Enable PTZ", AllowPrivateAccess = true), Category="PTZ")
|
||||
bool EnablePTZ = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Pan Limit", AllowPrivateAccess = true), Category="PTZ")
|
||||
bool PTZWithPanLimit = false;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Pan Min Limit", UIMin="-180", UIMax="180", AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZPanMinLimit = -180.f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Pan Max Limit", UIMin="-180", UIMax="180", AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZPanMaxLimit = 180.f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Invert Pan", AllowPrivateAccess = true), Category="PTZ")
|
||||
bool bPTZPanInvert = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Tilt Limit", AllowPrivateAccess = true), Category="PTZ")
|
||||
bool PTZWithTiltLimit = true;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Tilt Min Limit", UIMin="-180", UIMax="180", AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZTiltMinLimit = -90.f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Tilt Max Limit", UIMin="-180", UIMax="180", AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZTiltMaxLimit = 90.f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Invert Tilt", AllowPrivateAccess = true), Category="PTZ")
|
||||
bool bPTZTiltInvert = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Field of View Limit", AllowPrivateAccess = true), Category="PTZ")
|
||||
bool PTZWithFoVLimit = false;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Field of View Min Limit", UIMin="5", UIMax="170", AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZFoVMinLimit = 5.f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Field of View Max Limit", UIMin="5", UIMax="170", AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZFoVMaxLimit = 170.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Preset Recall Easing", UIMin="0", UIMax="60", AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZRecallEasing = 2.f;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, meta=(AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZPanSpeed = 0.f;
|
||||
UPROPERTY(BlueprintReadWrite, meta=(AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZTiltSpeed = 0.f;
|
||||
UPROPERTY(BlueprintReadWrite, meta=(AllowPrivateAccess = true), Category="PTZ")
|
||||
float PTZZoomSpeed = 0.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="PTZ Presets", AllowPrivateAccess = true), Category="PTZ")
|
||||
TArray<FPTZState> PTZStoredStates;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditInstanceOnly, Category = "NDI IO", META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
|
||||
UNDIMediaSender* NDIMediaSource = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On PTZ Pan Tilt Speed", AllowPrivateAccess = true))
|
||||
FNDIEventDelegate_OnPTZPanTiltSpeed OnPTZPanTiltSpeed;
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On PTZ Zoom Speed", AllowPrivateAccess = true))
|
||||
FNDIEventDelegate_OnPTZZoomSpeed OnPTZZoomSpeed;
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On PTZ Focus", AllowPrivateAccess = true))
|
||||
FNDIEventDelegate_OnPTZFocus OnPTZFocus;
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On PTZ Store", AllowPrivateAccess = true))
|
||||
FNDIEventDelegate_OnPTZStore OnPTZStore;
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On PTZ Recall", AllowPrivateAccess = true))
|
||||
FNDIEventDelegate_OnPTZRecall OnPTZRecall;
|
||||
|
||||
public:
|
||||
/** Call with the PTZ metadata received from an NDI media sender */
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Receive Metadata From Sender"))
|
||||
void ReceiveMetaDataFromSender(UNDIMediaSender* Sender, FString Data);
|
||||
|
||||
public:
|
||||
UPTZController();
|
||||
virtual ~UPTZController();
|
||||
|
||||
/**
|
||||
Initialize this component with the required media source to receive metadata from.
|
||||
Returns false, if the MediaSource is already been set. This is usually the case when this component is
|
||||
initialized in Blueprints.
|
||||
*/
|
||||
bool Initialize(UNDIMediaSender* InMediaSource = nullptr);
|
||||
|
||||
void SetPTZPanTiltSpeed(float PanSpeed, float TiltSpeed);
|
||||
void SetPTZZoomSpeed(float ZoomSpeed);
|
||||
void SetPTZFocus(bool AutoMode, float Distance);
|
||||
void StorePTZState(int Index);
|
||||
void RecallPTZState(int Index);
|
||||
|
||||
FPTZState GetPTZStateFromUE() const;
|
||||
void SetPTZStateToUE(const FPTZState& PTZState);
|
||||
|
||||
protected:
|
||||
virtual void InitializeComponent() override;
|
||||
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
protected:
|
||||
TSharedPtr<class NDIXmlParser> NDIMetadataParser;
|
||||
|
||||
struct FPTZStateInterp
|
||||
{
|
||||
FPTZState PTZTargetState;
|
||||
float EasingDuration { 0 };
|
||||
float EasingRemaining { 0 };
|
||||
};
|
||||
FPTZStateInterp PTZStateInterp;
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include <Components/ActorComponent.h>
|
||||
#include <Structures/NDIConnectionInformation.h>
|
||||
#include <Objects/Media/NDIMediaReceiver.h>
|
||||
|
||||
#include "NDIReceiverComponent.generated.h"
|
||||
|
||||
/**
|
||||
A component used to receive audio, video, and metadata over NDI
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Receiver Component", BlueprintSpawnableComponent))
|
||||
class NDIIO_API UNDIReceiverComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
private:
|
||||
/** The NDI Media Receiver representing the configuration of the network source to receive audio, video, and
|
||||
* metadata from */
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Properties",
|
||||
META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
|
||||
UNDIMediaReceiver* NDIMediaSource = nullptr;
|
||||
|
||||
public:
|
||||
/**
|
||||
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 Initialize(UNDIMediaReceiver* InMediaSource = nullptr);
|
||||
|
||||
/**
|
||||
Begin receiving NDI audio, video, and metadata frames
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Start Receiver"))
|
||||
bool StartReceiver(const FNDIConnectionInformation& InConnectionInformation);
|
||||
|
||||
/**
|
||||
Attempt to change the connection for which to get audio, video, and metadata frame from
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Connection"))
|
||||
void ChangeConnection(const FNDIConnectionInformation& InConnectionInformation);
|
||||
|
||||
/**
|
||||
This will add a metadata frame and return immediately, having scheduled the frame asynchronously
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Send Metadata Frame"))
|
||||
void SendMetadataFrame(const FString& metadata);
|
||||
|
||||
/**
|
||||
This will setup the up-stream tally notifications. If no streams are connected, it will automatically send
|
||||
the tally state upon connection
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Send Tally Information"))
|
||||
void SendTallyInformation(const bool& IsOnPreview, const bool& IsOnProgram);
|
||||
|
||||
/**
|
||||
Attempts to stop receiving audio, video, and metadata frame from the connected source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Shutdown Receiver"))
|
||||
void ShutdownReceiver();
|
||||
|
||||
public:
|
||||
/**
|
||||
Returns the current framerate of the connected source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Current Frame Rate"))
|
||||
FFrameRate GetCurrentFrameRate() const;
|
||||
|
||||
/**
|
||||
Returns the current timecode of the connected source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Current Timecode"))
|
||||
FTimecode GetCurrentTimecode() const;
|
||||
|
||||
/**
|
||||
Returns the current connection information of the connected source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Current Connection Information"))
|
||||
FNDIConnectionInformation GetCurrentConnectionInformation() const;
|
||||
|
||||
/**
|
||||
Returns the current performance data of the receiver while connected to the source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Performance Data"))
|
||||
FNDIReceiverPerformanceData GetPerformanceData() const;
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <UObject/UnrealType.h>
|
||||
#include <Objects/Media/NDIMediaSender.h>
|
||||
#include <Components/ActorComponent.h>
|
||||
|
||||
#include "NDITriCasterExtComponent.generated.h"
|
||||
|
||||
|
||||
USTRUCT(BlueprintType, Blueprintable, Category = "NDI IO", META = (DisplayName = "NDI TricasterExt"))
|
||||
struct NDIIO_API FTriCasterExt
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="TricasterExt")
|
||||
FString Value;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="TricasterExt")
|
||||
TMap<FName,FString> KeyValues;
|
||||
};
|
||||
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FNDIEventDelegate_OnTriCasterExt, AActor*, Actor, UObject*, Object, FString, PropertyElementName, FString, PropertyValueStr, FTimespan, EasingDuration);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIEventDelegate_OnTriCasterExtCustom, const FTriCasterExt&, TCData);
|
||||
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI TricasterExt Component", BlueprintSpawnableComponent))
|
||||
class UTriCasterExtComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Enable TricasterExt", AllowPrivateAccess = true), Category="TricasterExt")
|
||||
bool EnableTriCasterExt = true;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditInstanceOnly, Category = "NDI IO", META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
|
||||
UNDIMediaSender* NDIMediaSource = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, BlueprintCallable, Category="NDI Events", META = (DisplayName = "On TricasterExt", AllowPrivateAccess = true))
|
||||
FNDIEventDelegate_OnTriCasterExt OnTriCasterExt;
|
||||
UPROPERTY(BlueprintAssignable, BlueprintCallable, Category="NDI Events", META = (DisplayName = "On TricasterExt Custom", AllowPrivateAccess = true))
|
||||
FNDIEventDelegate_OnTriCasterExtCustom OnTriCasterExtCustom;
|
||||
|
||||
public:
|
||||
/** Call with the TriCasterExt metadata received from an NDI media sender */
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Receive Metadata From Sender"))
|
||||
void ReceiveMetaDataFromSender(UNDIMediaSender* Sender, FString Data);
|
||||
|
||||
public:
|
||||
UTriCasterExtComponent();
|
||||
virtual ~UTriCasterExtComponent();
|
||||
|
||||
/**
|
||||
Initialize this component with the required media source to receive metadata from.
|
||||
Returns false, if the MediaSource is already been set. This is usually the case when this component is
|
||||
initialized in Blueprints.
|
||||
*/
|
||||
bool Initialize(UNDIMediaSender* InMediaSource = nullptr);
|
||||
|
||||
void TriCasterExt(AActor* Actor, UObject* Object, FProperty* Property, FString PropertyElementName, FString PropertyValueStr, FTimespan EasingDuration);
|
||||
void TriCasterExtCustom(const FTriCasterExt& TCData);
|
||||
|
||||
protected:
|
||||
virtual void InitializeComponent() override;
|
||||
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
protected:
|
||||
TSharedPtr<class NDIXmlParser> NDIMetadataParser;
|
||||
|
||||
struct FTriCasterExtInterp
|
||||
{
|
||||
AActor* Actor;
|
||||
UObject* Object;
|
||||
FProperty* Property;
|
||||
FString PropertyElementName;
|
||||
FString PropertyValueStr;
|
||||
float EasingDuration;
|
||||
|
||||
float EasingRemaining;
|
||||
};
|
||||
TArray<FTriCasterExtInterp> TriCasterExtInterp;
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include <CineCameraComponent.h>
|
||||
#include <Engine/TextureRenderTarget2D.h>
|
||||
#include <Components/SceneCaptureComponent2D.h>
|
||||
#include <Objects/Media/NDIMediaSender.h>
|
||||
#include <Misc/FrameRate.h>
|
||||
#include <Framework/Application/SlateApplication.h>
|
||||
#include <SceneManagement.h>
|
||||
#include <Slate/SceneViewport.h>
|
||||
#include <Widgets/SViewport.h>
|
||||
#include <Widgets/SWindow.h>
|
||||
|
||||
#include "NDIViewportCaptureComponent.generated.h"
|
||||
|
||||
|
||||
/**
|
||||
A component used to capture an additional viewport for broadcasting over NDI
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Viewport Capture Component", BlueprintSpawnableComponent))
|
||||
class NDIIO_API UNDIViewportCaptureComponent : public USceneCaptureComponent2D
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
private:
|
||||
/**
|
||||
If true, will allow you to override the capture settings by ignoring the default Broadcast Settings
|
||||
in the NDI Media Sender, Potentially Requiring a texture rescale of the capture frame when broadcasting
|
||||
over NDI.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Capture Settings", META = (AllowPrivateAccess = true))
|
||||
bool bOverrideBroadcastSettings = false;
|
||||
|
||||
/**
|
||||
Describes the Height and Width of the viewport frame to capture.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Capture Settings",
|
||||
META = (DisplayName = "Capture Size", AllowPrivateAccess = true,
|
||||
EditCondition = "bOverrideBroadcastSettings"))
|
||||
FIntPoint CaptureSize = FIntPoint(1280, 720);
|
||||
|
||||
/**
|
||||
Represents the desired number of frames (per second) to capture the viewport
|
||||
*/
|
||||
UPROPERTY(BlueprintReadwrite, EditAnywhere, Category = "Capture Settings",
|
||||
META = (DisplayName = "Capture Rate", AllowPrivateAccess = true,
|
||||
EditCondition = "bOverrideBroadcastSettings"))
|
||||
FFrameRate CaptureRate = FFrameRate(60, 1);
|
||||
|
||||
/**
|
||||
The NDI Media Sender representing the configuration of the network source to send audio, video, and metadata
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Properties",
|
||||
META = (DisplayName = "NDI Media Source", AllowPrivateAccess = true))
|
||||
UNDIMediaSender* NDIMediaSource = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Capture Settings",
|
||||
META = (DisplayName = "Alpha Remap Min", AllowPrivateAccess = true))
|
||||
float AlphaMin = 0.f;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Capture Settings",
|
||||
META = (DisplayName = "Alpha Remap Max", AllowPrivateAccess = true))
|
||||
float AlphaMax = 1.f;
|
||||
|
||||
public:
|
||||
/**
|
||||
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 Initialize(UNDIMediaSender* InMediaSource = nullptr);
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Source Name"))
|
||||
void ChangeSourceName(const FString& InSourceName);
|
||||
|
||||
/**
|
||||
Attempts to change the Broadcast information associated with this media object
|
||||
|
||||
@param InConfiguration The new configuration to broadcast
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Broadcast Configuration"))
|
||||
void ChangeBroadcastConfiguration(const FNDIBroadcastConfiguration& 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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Broadcast Texture"))
|
||||
void ChangeBroadcastTexture(UTextureRenderTarget2D* BroadcastTexture = nullptr);
|
||||
|
||||
/**
|
||||
Change the capture settings of the viewport capture and overrides the NDI Media Sender settings
|
||||
|
||||
@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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Capture Settings"))
|
||||
void ChangeCaptureSettings(FIntPoint InCaptureSize, FFrameRate InCaptureRate);
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Tally Information"))
|
||||
void GetTallyInformation(bool& IsOnPreview, bool& IsOnProgram);
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Number of Connections"))
|
||||
void GetNumberOfConnections(int32& Result);
|
||||
|
||||
protected:
|
||||
virtual ~UNDIViewportCaptureComponent();
|
||||
|
||||
virtual void InitializeComponent() override;
|
||||
virtual void UninitializeComponent() override;
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 6)) // 5.6 or later
|
||||
virtual void UpdateSceneCaptureContents(FSceneInterface* Scene, ISceneRenderBuilder& SceneRenderBuilder) override;
|
||||
#else
|
||||
virtual void UpdateSceneCaptureContents(FSceneInterface* Scene) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void OnBroadcastConfigurationChanged(UNDIMediaSender* Sender);
|
||||
|
||||
private:
|
||||
FCriticalSection UpdateRenderContext;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include "NDIAudioChannels.generated.h"
|
||||
|
||||
/**
|
||||
Receiver Bandwidth modes
|
||||
*/
|
||||
UENUM(BlueprintType, META = (DisplayName = "NDI Audio Channels"))
|
||||
enum class ENDIAudioChannels : uint8
|
||||
{
|
||||
/** Mono. */
|
||||
Mono = 0x00 UMETA(DisplayName = "Mono"),
|
||||
|
||||
/** Stereo. */
|
||||
Stereo = 0x01 UMETA(DisplayName = "Stereo"),
|
||||
|
||||
/** Whatever the number of channels in the source is. */
|
||||
Source = 0x02 UMETA(DisplayName = "Source"),
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include "NDISourceBandwidth.generated.h"
|
||||
|
||||
/**
|
||||
Receiver Bandwidth modes
|
||||
*/
|
||||
UENUM(BlueprintType, META = (DisplayName = "NDI Source Bandwidth"))
|
||||
enum class ENDISourceBandwidth : uint8
|
||||
{
|
||||
/** Receive metadata. */
|
||||
MetadataOnly = 0x00 UMETA(DisplayName = "Metadata Only"),
|
||||
|
||||
/** Receive metadata, audio */
|
||||
AudioOnly = 0x01 UMETA(DisplayName = "Audio Only"),
|
||||
|
||||
/** Receive metadata, audio, video at a lower bandwidth and resolution. */
|
||||
Lowest = 0x02 UMETA(DisplayName = "Lowest"),
|
||||
|
||||
// Receive metadata, audio, video at full resolution.
|
||||
Highest = 0x03 UMETA(DisplayName = "Highest")
|
||||
};
|
||||
34
Plugins/NDIIO/Source/Core/Public/NDIIOPluginAPI.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
#include <Windows/AllowWindowsPlatformTypes.h>
|
||||
#endif
|
||||
|
||||
#ifndef NDI_SDK_ENABLED
|
||||
#error NDI(R) 6.x Runtime must be installed for the NDI(R) IO plugin to run properly.
|
||||
#endif
|
||||
|
||||
#ifdef NDI_SDK_ENABLED
|
||||
#include <Processing.NDI.Lib.h>
|
||||
#include <Processing.NDI.Lib.cplusplus.h>
|
||||
#endif
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
#include <Windows/HideWindowsPlatformTypes.h>
|
||||
#endif
|
||||
|
||||
#define NDIIO_MODULE_NAME FName(TEXT("NDIIO"))
|
||||
61
Plugins/NDIIO/Source/Core/Public/NDIIOPluginModule.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
#include <Engine/World.h>
|
||||
#include <Interfaces/IPluginManager.h>
|
||||
#include <Modules/ModuleManager.h>
|
||||
#include <IMediaPlayerFactory.h>
|
||||
|
||||
#include <NDIIOPluginSettings.h>
|
||||
|
||||
class NDIIO_API FNDIIOPluginModule
|
||||
: public IModuleInterface
|
||||
, public IMediaPlayerFactory
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
/** IMediaPlayerFactory implementation */
|
||||
virtual bool CanPlayUrl(const FString& Url, const IMediaOptions* /*Options*/, TArray<FText>* /*OutWarnings*/, TArray<FText>* OutErrors) const override;
|
||||
virtual TSharedPtr<IMediaPlayer, ESPMode::ThreadSafe> CreatePlayer(IMediaEventSink& EventSink) override;
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual FName GetPlayerName() const override;
|
||||
virtual FGuid GetPlayerPluginGUID() const override;
|
||||
virtual const TArray<FString>& GetSupportedPlatforms() const override;
|
||||
virtual bool SupportsFeature(EMediaFeature Feature) const override;
|
||||
|
||||
|
||||
bool BeginBroadcastingActiveViewport();
|
||||
void StopBroadcastingActiveViewport();
|
||||
|
||||
private:
|
||||
bool LoadModuleDependencies();
|
||||
void ShutdownModuleDependencies();
|
||||
|
||||
private:
|
||||
TSharedPtr<class FNDIFinderService> NDIFinderService = nullptr;
|
||||
TSharedPtr<class FNDIConnectionService> NDIConnectionService = nullptr;
|
||||
|
||||
void* NDI_LIB_HANDLE = nullptr;
|
||||
|
||||
/** List of platforms that the media player support. */
|
||||
TArray<FString> SupportedPlatforms;
|
||||
|
||||
/** List of supported URI schemes. */
|
||||
TArray<FString> SupportedUriSchemes;
|
||||
};
|
||||
52
Plugins/NDIIO/Source/Core/Public/NDIIOPluginSettings.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include <Misc/FrameRate.h>
|
||||
#include <UObject/Object.h>
|
||||
|
||||
#include "NDIIOPluginSettings.generated.h"
|
||||
|
||||
/**
|
||||
Settings for the Broadcasting of the Active Viewport configurable in the running editor of the application
|
||||
*/
|
||||
UCLASS(Config = Engine, DefaultConfig)
|
||||
class NDIIO_API UNDIIOPluginSettings : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(VisibleAnywhere, Category = "NDI IO", META = (DisplayName = "Description", MultiLine = true))
|
||||
FString Decription = TEXT(
|
||||
"These values define the 'Active Viewport' broadcast settings and does not define default values for outputs."
|
||||
"\r\n"
|
||||
"\r\nApplication Stream Name - The default name to use when broadcasting the Currently Active Viewport over "
|
||||
"NDI."
|
||||
"\r\nBroadcast Rate - Indicates the preferred frame rate to broadcast the Currently Active Viewport over NDI."
|
||||
"\r\nPreferred FrameSize - Indicates the preferred frame size to broadcast the Currently Active Viewport over "
|
||||
"NDI."
|
||||
"\r\nBegin Broadcast On Play - Starts the broadcast of the Currently Active Viewport immediately on Play."
|
||||
);
|
||||
|
||||
/** The default name to use when broadcasting the Currently Active Viewport over NDI. */
|
||||
UPROPERTY(Config, EditAnywhere, Category = "NDI IO")
|
||||
FString ApplicationStreamName = FString("Unreal Engine");
|
||||
|
||||
/** Indicates the preferred frame rate to broadcast the Currently Active Viewport over NDI. */
|
||||
UPROPERTY(Config, EditAnywhere, Category = "NDI IO", META = (DisplayName = "Broadcast Rate"))
|
||||
FFrameRate BroadcastRate = FFrameRate(60, 1);
|
||||
|
||||
/** Indicates the preferred frame size to broadcast the Currently Active Viewport over NDI. */
|
||||
UPROPERTY(Config, EditAnywhere, Category = "NDI IO", META = (DisplayName = "Preferred Broadcast Framesize"))
|
||||
FIntPoint PreferredFrameSize = FIntPoint(1920, 1080);
|
||||
|
||||
UPROPERTY(Config, EditAnywhere, Category = "NDI IO", META = (DisplayName = "Begin Broadcast On Play"))
|
||||
bool bBeginBroadcastOnPlay = false;
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Kismet/BlueprintFunctionLibrary.h>
|
||||
#include <Structures/NDIBroadcastConfiguration.h>
|
||||
|
||||
#include "NDIBroadcastConfigurationLibrary.generated.h"
|
||||
|
||||
UCLASS(NotBlueprintable, BlueprintType, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Broadcast Configuration Library"))
|
||||
class NDIIO_API UNDIBroadcastConfigurationLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
private:
|
||||
/**
|
||||
Returns a value indicating whether the two structures are comparably equal
|
||||
|
||||
@param A The structure used as the source comparator
|
||||
@param B The structure used as the target comparator
|
||||
@return The resulting value of the comparator operator
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "NDI IO",
|
||||
META = (DisplayName = "Equals (NDI Broadcast Configuration)",
|
||||
CompactNodeTitle = "==", Keywords = "= == Equals", AllowPrivateAccess = true))
|
||||
static bool K2_Compare_NDIBroadcastConfiguration(FNDIBroadcastConfiguration A, FNDIBroadcastConfiguration B)
|
||||
{
|
||||
return A == B;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a value indicating whether the two structures are NOT comparably equal
|
||||
|
||||
@param A The structure used as the source comparator
|
||||
@param B The structure used as the target comparator
|
||||
@return The resulting value of the comparator operator
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "NDI IO",
|
||||
META = (DisplayName = "Not Equals (NDI Broadcast Configuration)",
|
||||
CompactNodeTitle = "!=", Keywords = "! != Not Equals", AllowPrivateAccess = true))
|
||||
static bool K2_Compare_Not_NDIBroadcastConfiguration(FNDIBroadcastConfiguration A, FNDIBroadcastConfiguration B)
|
||||
{
|
||||
return A != B;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Kismet/BlueprintFunctionLibrary.h>
|
||||
#include <Structures/NDIConnectionInformation.h>
|
||||
|
||||
#include "NDIConnectionInformationLibrary.generated.h"
|
||||
|
||||
UCLASS(NotBlueprintable, BlueprintType, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Connection Information Library"))
|
||||
class NDIIO_API UNDIConnectionInformationLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
private:
|
||||
/**
|
||||
Returns a value indicating whether the two structures are comparably equal
|
||||
|
||||
@param A The structure used as the source comparator
|
||||
@param B The structure used as the target comparator
|
||||
@return The resulting value of the comparator operator
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "NDI IO",
|
||||
META = (DisplayName = "Equals (NDI Connection Information)",
|
||||
CompactNodeTitle = "==", Keywords = "= == Equals", AllowPrivateAccess = true))
|
||||
static bool K2_Compare_NDIConnectionInformation(FNDIConnectionInformation A, FNDIConnectionInformation B)
|
||||
{
|
||||
return A == B;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a value indicating whether the two structures are NOT comparably equal
|
||||
|
||||
@param A The structure used as the source comparator
|
||||
@param B The structure used as the target comparator
|
||||
@return The resulting value of the comparator operator
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "NDI IO",
|
||||
META = (DisplayName = "Not Equals (NDI Connection Information)",
|
||||
CompactNodeTitle = "!=", Keywords = "! != Not Equals", AllowPrivateAccess = true))
|
||||
static bool K2_Compare_Not_NDIConnectionInformation(FNDIConnectionInformation A, FNDIConnectionInformation B)
|
||||
{
|
||||
return A != B;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a value indicating whether the property values of the supplied structure is valid
|
||||
|
||||
@param ConnectionInformation The structure to validate
|
||||
@return An indication of the supplied structures validity
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Is Valid?", AllowPrivateAccess = true))
|
||||
static bool K2_NDIConnectionInformation_IsValid(FNDIConnectionInformation& ConnectionInformation)
|
||||
{
|
||||
return ConnectionInformation.IsValid();
|
||||
}
|
||||
|
||||
/**
|
||||
Resets the structure's properties to their default values
|
||||
|
||||
@param ConnectionInformation The structure to reset to the default value
|
||||
@return The reference to the passed in structure after the 'reset' has been completed
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO",
|
||||
META = (DisplayName = "Reset Connection Information", AllowPrivateAccess = true))
|
||||
static UPARAM(ref) FNDIConnectionInformation& K2_NDIConnectionInformation_Reset(
|
||||
UPARAM(ref) FNDIConnectionInformation& ConnectionInformation)
|
||||
{
|
||||
// call the underlying function to reset the properties of the object
|
||||
ConnectionInformation.Reset();
|
||||
|
||||
// return the ConnectionInformation object reference
|
||||
return ConnectionInformation;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Kismet/BlueprintFunctionLibrary.h>
|
||||
|
||||
#include <Structures/NDIConnectionInformation.h>
|
||||
#include <Objects/Media/NDIMediaReceiver.h>
|
||||
#include <Objects/Media/NDIMediaSender.h>
|
||||
|
||||
#include "NDIIOLibrary.generated.h"
|
||||
|
||||
|
||||
/**
|
||||
An metadata element as returned by K2_ParseNDIMetaData()
|
||||
Blueprints do not support recursive datastructures, so parsing metadata
|
||||
with this will result in only the top-level elements being returned.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FNDIMetaDataElement
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Metadata")
|
||||
FString ElementName;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Metadata")
|
||||
TMap<FString,FString> Attributes;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Metadata")
|
||||
FString Data;
|
||||
};
|
||||
|
||||
|
||||
UCLASS(META = (DisplayName = "NDI IO Library"))
|
||||
class NDIIO_API UNDIIOLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
private:
|
||||
/**
|
||||
Retrieves a collection of NDI sources appearing on the network
|
||||
|
||||
@return A collection of NDI Sources appearing on the network
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "NDI IO",
|
||||
META = (DisplayName = "Get NDI Source Collection", AllowPrivateAccess = true))
|
||||
static const TArray<FNDIConnectionInformation> K2_GetNDISourceCollection();
|
||||
|
||||
/**
|
||||
Attempts to search the NDI Source Collection for the source name, returning a result indicating
|
||||
success with the ConnectionInformation parameter filled with the found connection
|
||||
|
||||
@param ConnectionInformation The connection information for a successful find with the supplied InSourceName
|
||||
@param InSourceName The name of the source to find within the collection of NDI sources
|
||||
|
||||
@return The result of whether the search was successful
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO",
|
||||
META = (DisplayName = "Find Network Source by Name", DefaultToSelf = "WorldContextObject",
|
||||
HidePin = "WorldContextObject", AllowPrivateAccess = true))
|
||||
static const bool K2_FindNetworkSourceByName(UObject* WorldContextObject,
|
||||
FNDIConnectionInformation& ConnectionInformation,
|
||||
FString InSourceName = FString(""));
|
||||
|
||||
private:
|
||||
/**
|
||||
Attempts to start broadcasting the active viewport. The output of the active viewport is the current camera
|
||||
that is actively being viewed (through), and does not have to be an NDI Broadcast Viewport Component.
|
||||
|
||||
@return The result of whether broadcasting the active viewport was started
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO",
|
||||
META = (DisplayName = "Begin Broadcasting Active Viewport", DefaultToSelf = "WorldContextObject",
|
||||
HidePin = "WorldContextObject", AllowPrivateAccess = true))
|
||||
static bool K2_BeginBroadcastingActiveViewport(UObject* WorldContextObject);
|
||||
|
||||
/**
|
||||
Will stop broadcasting the active viewport, which was started by a previous call to 'Begin Broadcasting Active
|
||||
Viewport'
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO",
|
||||
META = (DisplayName = "Stop Broadcasting Active Viewport", DefaultToSelf = "WorldContextObject",
|
||||
HidePin = "WorldContextObject", AllowPrivateAccess = true))
|
||||
static void K2_StopBroadcastingActiveViewport(UObject* WorldContextObject);
|
||||
|
||||
private:
|
||||
/**
|
||||
Returns an NDI Media Receiver object
|
||||
|
||||
@param Receiver The Receiver object to return
|
||||
@return The selected Receiver object
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO",
|
||||
META = (DisplayName = "Get NDI Media Receiver", AllowPrivateAccess = true))
|
||||
static UPARAM(ref) UNDIMediaReceiver* K2_GetNDIMediaReceiver(UNDIMediaReceiver* Receiver = nullptr);
|
||||
|
||||
/**
|
||||
Returns an NDI Media Sender object
|
||||
|
||||
@param Sender The Sender object to return
|
||||
@return The selected Sender object
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO",
|
||||
META = (DisplayName = "Get NDI Media Sender", AllowPrivateAccess = true))
|
||||
static UPARAM(ref) UNDIMediaSender* K2_GetNDIMediaSender(UNDIMediaSender* Sender = nullptr);
|
||||
|
||||
private:
|
||||
/**
|
||||
Parses a string as metadata
|
||||
Blueprints do not support recursive datastructures, so parsing metadata
|
||||
with this will result in only the top-level elements being returned.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO",
|
||||
META = (DisplayName = "Parse NDI MetaData", AllowPrivateAccess = true))
|
||||
static const TArray<FNDIMetaDataElement> K2_ParseNDIMetaData(FString Data);
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Kismet/BlueprintFunctionLibrary.h>
|
||||
#include <Structures/NDIReceiverPerformanceData.h>
|
||||
|
||||
#include "NDIReceiverPerformanceDataLibrary.generated.h"
|
||||
|
||||
UCLASS(NotBlueprintable, BlueprintType, Category = "NDI IO",
|
||||
META = (DisplayName = "NDI Receiver Performance Data Library"))
|
||||
class NDIIO_API UNDIReceiverPerformanceDataLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
private:
|
||||
/**
|
||||
Returns a value indicating whether the two structures are comparably equal
|
||||
|
||||
@param A The structure used as the source comparator
|
||||
@param B The structure used as the target comparator
|
||||
@return The resulting value of the comparator operator
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "NDI IO",
|
||||
META = (DisplayName = "Equals (NDI Receiver Performance Data)",
|
||||
CompactNodeTitle = "==", Keywords = "= == Equals", AllowPrivateAccess = true))
|
||||
static bool K2_Compare_NDIReceiverPerformanceData(FNDIReceiverPerformanceData A, FNDIReceiverPerformanceData B)
|
||||
{
|
||||
return A == B;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a value indicating whether the two structures are NOT comparably equal
|
||||
|
||||
@param A The structure used as the source comparator
|
||||
@param B The structure used as the target comparator
|
||||
@return The resulting value of the comparator operator
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "NDI IO",
|
||||
META = (DisplayName = "Not Equals (NDI Receiver Performance Data)",
|
||||
CompactNodeTitle = "!=", Keywords = "! != Not Equals", AllowPrivateAccess = true))
|
||||
static bool K2_Compare_Not_NDIReceiverPerformanceData(FNDIReceiverPerformanceData A, FNDIReceiverPerformanceData B)
|
||||
{
|
||||
return A != B;
|
||||
}
|
||||
|
||||
/**
|
||||
Resets the structure's properties to their default values
|
||||
|
||||
@param PerformanceData The structure to reset to the default value
|
||||
@return The reference to the passed in structure after the 'reset' has been completed
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO",
|
||||
META = (DisplayName = "Reset Receiver Performance Data", AllowPrivateAccess = true))
|
||||
static UPARAM(ref) FNDIReceiverPerformanceData& K2_NDIReceiverPerformanceData_Reset(
|
||||
UPARAM(ref) FNDIReceiverPerformanceData& PerformanceData)
|
||||
{
|
||||
// call the underlying function to reset the properties of the object
|
||||
PerformanceData.Reset();
|
||||
|
||||
// return the Performance Data object reference
|
||||
return PerformanceData;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <NDIIOPluginAPI.h>
|
||||
|
||||
#include <UObject/Object.h>
|
||||
#include <Misc/Timecode.h>
|
||||
#include <Misc/FrameRate.h>
|
||||
#include <TimeSynchronizableMediaSource.h>
|
||||
#include <RendererInterface.h>
|
||||
|
||||
#include <Objects/Media/NDIMediaSoundWave.h>
|
||||
#include <Objects/Media/NDIMediaTexture2D.h>
|
||||
#include <Structures/NDIConnectionInformation.h>
|
||||
#include <Structures/NDIReceiverPerformanceData.h>
|
||||
|
||||
#include "NDIMediaReceiver.generated.h"
|
||||
|
||||
|
||||
namespace NDIMediaOption
|
||||
{
|
||||
static const FName IsNDIMediaReceiver("IsNDIMediaReceiver");
|
||||
static const FName MaxVideoFrameBuffer("MaxVideoFrameBuffer");
|
||||
static const FName MaxAudioFrameBuffer("MaxAudioFrameBuffer");
|
||||
static const FName MaxAncillaryFrameBuffer("MaxAncillaryFrameBuffer");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Delegates to notify that the NDIMediaReceiver has received a video, audio, or metadata frame
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIMediaReceiverVideoReceived, UNDIMediaReceiver*, Receiver);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIMediaReceiverAudioReceived, UNDIMediaReceiver*, Receiver);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FNDIMediaReceiverMetaDataReceived, UNDIMediaReceiver*, Receiver, FString, Data, bool, bAttachedToVideoFrame);
|
||||
|
||||
|
||||
/**
|
||||
A Media object representing the NDI Receiver for being able to receive Audio, Video, and Metadata over NDI
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, HideCategories = ("Platforms"), Category = "NDI IO",
|
||||
HideCategories = ("Information"), AutoCollapseCategories = ("Content"),
|
||||
META = (DisplayName = "NDI Media Receiver"))
|
||||
class NDIIO_API UNDIMediaReceiver : public UTimeSynchronizableMediaSource
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
Information describing detailed information about the sender this receiver is to connect to
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings",
|
||||
META = (DisplayName = "Connection", AllowPrivateAccess = true))
|
||||
FNDIConnectionInformation ConnectionSetting;
|
||||
|
||||
private:
|
||||
/**
|
||||
The current frame count, seconds, minutes, and hours in time-code notation
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Timecode", AllowPrivateAccess = true))
|
||||
FTimecode Timecode;
|
||||
|
||||
/**
|
||||
The desired number of frames (per second) for video to be displayed
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Frame Rate", AllowPrivateAccess = true))
|
||||
FFrameRate FrameRate;
|
||||
|
||||
/**
|
||||
The width and height of the last received video frame
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Resolution", AllowPrivateAccess = true))
|
||||
FIntPoint Resolution;
|
||||
|
||||
/**
|
||||
Indicates whether the timecode should be synced to the Source Timecode value
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings",
|
||||
META = (DisplayName = "Sync Timecode to Source", AllowPrivateAccess = true))
|
||||
bool bSyncTimecodeToSource = true;
|
||||
|
||||
/**
|
||||
Should perform the sRGB to Linear color space conversion
|
||||
*/
|
||||
UPROPERTY(BlueprintReadonly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Perform sRGB to Linear?", AllowPrivateAccess = true))
|
||||
bool bPerformsRGBtoLinear = true;
|
||||
|
||||
/**
|
||||
Information describing detailed information about the sender this receiver is currently connected to
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Connection Information", AllowPrivateAccess = true))
|
||||
FNDIConnectionInformation ConnectionInformation;
|
||||
|
||||
/**
|
||||
Information describing detailed information about the receiver performance when connected to an NDI sender
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Performance Data", AllowPrivateAccess = true))
|
||||
FNDIReceiverPerformanceData PerformanceData;
|
||||
|
||||
/**
|
||||
Provides an NDI Video Texture object to render videos frames from the source onto (optional)
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, BlueprintSetter = "ChangeVideoTexture", Category = "Content",
|
||||
AdvancedDisplay, META = (DisplayName = "Video Texture (optional)", AllowPrivateAccess = true))
|
||||
UNDIMediaTexture2D* VideoTexture = nullptr;
|
||||
|
||||
public:
|
||||
DECLARE_EVENT_OneParam(FNDIMediaReceiverConnectionEvent, FOnReceiverConnectionEvent,
|
||||
UNDIMediaReceiver*) FOnReceiverConnectionEvent OnNDIReceiverConnectedEvent;
|
||||
DECLARE_EVENT_OneParam(FNDIMediaReceiverDisconnectionEvent, FOnReceiverDisconnectionEvent,
|
||||
UNDIMediaReceiver*) FOnReceiverDisconnectionEvent OnNDIReceiverDisconnectedEvent;
|
||||
|
||||
DECLARE_EVENT_TwoParams(FNDIMediaReceiverVideoCaptureEvent, FOnReceiverVideoCaptureEvent,
|
||||
UNDIMediaReceiver*, const NDIlib_video_frame_v2_t&) FOnReceiverVideoCaptureEvent OnNDIReceiverVideoCaptureEvent;
|
||||
DECLARE_EVENT_TwoParams(FNDIMediaReceiverAudioCaptureEvent, FOnReceiverAudioCaptureEvent,
|
||||
UNDIMediaReceiver*, const NDIlib_audio_frame_v2_t&) FOnReceiverAudioCaptureEvent OnNDIReceiverAudioCaptureEvent;
|
||||
DECLARE_EVENT_TwoParams(FNDIMediaReceiverMetadataCaptureEvent, FOnReceiverMetadataCaptureEvent,
|
||||
UNDIMediaReceiver*, const NDIlib_metadata_frame_t&) FOnReceiverMetadataCaptureEvent OnNDIReceiverMetadataCaptureEvent;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On Video Received by Receiver", AllowPrivateAccess = true))
|
||||
FNDIMediaReceiverVideoReceived OnReceiverVideoReceived;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On Audio Received by Receiver", AllowPrivateAccess = true))
|
||||
FNDIMediaReceiverAudioReceived OnReceiverAudioReceived;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On MetaData Received by Receiver", AllowPrivateAccess = true))
|
||||
FNDIMediaReceiverMetaDataReceived OnReceiverMetaDataReceived;
|
||||
|
||||
public:
|
||||
|
||||
UNDIMediaReceiver();
|
||||
|
||||
/**
|
||||
Called before destroying the object. This is called immediately upon deciding to destroy the object,
|
||||
to allow the object to begin an asynchronous cleanup process.
|
||||
*/
|
||||
void BeginDestroy() override;
|
||||
|
||||
/**
|
||||
Attempts to perform initialization logic for creating a receiver through the NDI sdk api
|
||||
*/
|
||||
enum class EUsage
|
||||
{
|
||||
Standalone, // The receiver automatically captures its own video frame every engine render frame
|
||||
Controlled // The user of the receiver manually triggers capturing a frame through CaptureConnectedVideo/Audio()
|
||||
};
|
||||
bool Initialize(const FNDIConnectionInformation& InConnectionInformation, EUsage InUsage);
|
||||
bool Initialize(EUsage Inusage);
|
||||
|
||||
/**
|
||||
Attempt to (re-)start the connection
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Start Connection"))
|
||||
void StartConnection();
|
||||
|
||||
/**
|
||||
Stop the connection
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Stop Connection"))
|
||||
void StopConnection();
|
||||
|
||||
/**
|
||||
Attempts to change the connection to another NDI sender source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Connection"))
|
||||
void ChangeConnection(const FNDIConnectionInformation& InConnectionInformation);
|
||||
|
||||
/**
|
||||
Attempts to change the Video Texture object used as the video frame capture object
|
||||
*/
|
||||
UFUNCTION(BlueprintSetter)
|
||||
void ChangeVideoTexture(UNDIMediaTexture2D* InVideoTexture = nullptr);
|
||||
|
||||
/**
|
||||
Attempts to generate the pcm data required by the 'AudioWave' object
|
||||
*/
|
||||
int32 GeneratePCMData(UNDIMediaSoundWave* AudioWave, uint8* PCMData, const int32 SamplesNeeded);
|
||||
int32 GetAudioChannels();
|
||||
|
||||
/**
|
||||
Attempts to register a sound wave object with this object
|
||||
*/
|
||||
void RegisterAudioWave(UNDIMediaSoundWave* InAudioWave = nullptr);
|
||||
|
||||
/**
|
||||
This will send a metadata frame to the sender
|
||||
The data is expected to be valid XML
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Send Metadata To Sender"))
|
||||
void SendMetadataFrame(const FString& Data);
|
||||
/**
|
||||
This will send a metadata frame to the sender
|
||||
The data will be formatted as: <Element>ElementData</Element>
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Send Metadata To Sender (Element + Data)"))
|
||||
void SendMetadataFrameAttr(const FString& Element, const FString& ElementData);
|
||||
/**
|
||||
This will send a metadata frame to the sender
|
||||
The data will be formatted as: <Element Key0="Value0" Key1="Value1" Keyn="Valuen"/>
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Send Metadata To Sender (Element + Attributes)"))
|
||||
void SendMetadataFrameAttrs(const FString& Element, const TMap<FString,FString>& Attributes);
|
||||
|
||||
/**
|
||||
This will set the up-stream tally notifications. If no streams are connected, it will automatically
|
||||
send the tally state upon connection
|
||||
*/
|
||||
void SendTallyInformation(const bool& IsOnPreview, const bool& IsOnProgram);
|
||||
|
||||
/**
|
||||
Attempts to immediately stop receiving frames from the connected NDI sender
|
||||
*/
|
||||
void Shutdown();
|
||||
|
||||
/**
|
||||
Remove the AudioWave object from this object (if it was previously registered)
|
||||
|
||||
@param InAudioWave An NDIMediaSoundWave object registered with this object
|
||||
*/
|
||||
void UnregisterAudioWave(UNDIMediaSoundWave* InAudioWave = nullptr);
|
||||
|
||||
/**
|
||||
Updates the DynamicMaterial with the VideoTexture of this object
|
||||
*/
|
||||
void UpdateMaterialTexture(class UMaterialInstanceDynamic* MaterialInstance, FString ParameterName);
|
||||
|
||||
/**
|
||||
Attempts to capture a frame from the connected source. If a new frame is captured, broadcast it to
|
||||
interested receivers through the capture event. Returns true if new data was captured.
|
||||
*/
|
||||
bool CaptureConnectedVideo();
|
||||
bool CaptureConnectedAudio();
|
||||
bool CaptureConnectedMetadata();
|
||||
|
||||
/**
|
||||
Attempts to immediately update the 'VideoTexture' object with the captured video frame
|
||||
*/
|
||||
FTextureRHIRef DisplayFrame(const NDIlib_video_frame_v2_t& video_frame);
|
||||
|
||||
private:
|
||||
void SetIsCurrentlyConnected(bool bConnected);
|
||||
|
||||
/**
|
||||
Attempts to gather the performance metrics of the connection to the remote source
|
||||
*/
|
||||
void GatherPerformanceMetrics();
|
||||
|
||||
public:
|
||||
/**
|
||||
Set whether or not a RGB to Linear conversion is made
|
||||
*/
|
||||
void PerformsRGBToLinearConversion(bool Value);
|
||||
|
||||
/**
|
||||
Returns the current framerate of the connected source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Current Frame Rate"))
|
||||
const FFrameRate& GetCurrentFrameRate() const;
|
||||
|
||||
/**
|
||||
Returns the current resolution of the connected source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Current Resolution"))
|
||||
const FIntPoint& GetCurrentResolution() const;
|
||||
|
||||
/**
|
||||
Returns the current timecode of the connected source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Current Timecode"))
|
||||
const FTimecode& GetCurrentTimecode() const;
|
||||
|
||||
/**
|
||||
Returns the current connection information of the connected source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Current Connection Information"))
|
||||
const FNDIConnectionInformation& GetCurrentConnectionInformation() const;
|
||||
|
||||
/**
|
||||
Returns the current performance data of the receiver while connected to the source
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Get Performance Data"))
|
||||
const FNDIReceiverPerformanceData& GetPerformanceData() const;
|
||||
|
||||
/** Returns a value indicating whether this object is currently connected to the sender source */
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Is Currently Connected"))
|
||||
const bool GetIsCurrentlyConnected() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
Perform the color conversion (if any) and bit copy from the gpu
|
||||
*/
|
||||
FTextureRHIRef DrawProgressiveVideoFrame(FRHICommandListImmediate& RHICmdList, const NDIlib_video_frame_v2_t& Result);
|
||||
FTextureRHIRef DrawProgressiveVideoFrameAlpha(FRHICommandListImmediate& RHICmdList, const NDIlib_video_frame_v2_t& Result);
|
||||
FTextureRHIRef DrawInterlacedVideoFrame(FRHICommandListImmediate& RHICmdList, const NDIlib_video_frame_v2_t& Result);
|
||||
FTextureRHIRef DrawInterlacedVideoFrameAlpha(FRHICommandListImmediate& RHICmdList, const NDIlib_video_frame_v2_t& Result);
|
||||
|
||||
virtual bool Validate() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual FString GetUrl() const override;
|
||||
|
||||
FTextureResource* GetVideoTextureResource() const;
|
||||
FTextureResource* GetInternalVideoTextureResource() const;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
#endif
|
||||
|
||||
public:
|
||||
virtual bool GetMediaOption(const FName& Key, bool DefaultValue) const override;
|
||||
virtual int64 GetMediaOption(const FName& Key, int64 DefaultValue) const override;
|
||||
virtual FString GetMediaOption(const FName& Key, const FString& DefaultValue) const override;
|
||||
virtual bool HasMediaOption(const FName& Key) const override;
|
||||
|
||||
private:
|
||||
int64_t LastFrameTimestamp = 0;
|
||||
NDIlib_frame_format_type_e LastFrameFormatType = NDIlib_frame_format_type_max;
|
||||
|
||||
bool bIsCurrentlyConnected = false;
|
||||
|
||||
NDIlib_recv_instance_t p_receive_instance = nullptr;
|
||||
NDIlib_framesync_instance_t p_framesync_instance = nullptr;
|
||||
|
||||
FCriticalSection RenderSyncContext;
|
||||
FCriticalSection AudioSyncContext;
|
||||
FCriticalSection MetadataSyncContext;
|
||||
FCriticalSection ConnectionSyncContext;
|
||||
|
||||
TArray<UNDIMediaSoundWave*> AudioSourceCollection;
|
||||
|
||||
UNDIMediaTexture2D* InternalVideoTexture = nullptr;
|
||||
|
||||
FTextureRHIRef SourceTexture;
|
||||
FTextureRHIRef SourceAlphaTexture;
|
||||
FPooledRenderTargetDesc RenderTargetDescriptor;
|
||||
TRefCountPtr<IPooledRenderTarget> RenderTarget;
|
||||
enum class EDrawMode
|
||||
{
|
||||
Invalid,
|
||||
Progressive,
|
||||
ProgressiveAlpha,
|
||||
Interlaced,
|
||||
InterlacedAlpha
|
||||
};
|
||||
EDrawMode DrawMode = EDrawMode::Invalid;
|
||||
|
||||
FDelegateHandle FrameEndRTHandle;
|
||||
FDelegateHandle VideoCaptureEventHandle;
|
||||
};
|
||||
362
Plugins/NDIIO/Source/Core/Public/Objects/Media/NDIMediaSender.h
Normal file
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <NDIIOPluginAPI.h>
|
||||
|
||||
#include <RendererInterface.h>
|
||||
#include <UObject/Object.h>
|
||||
#include <Misc/FrameRate.h>
|
||||
#include <Engine/TextureRenderTarget2D.h>
|
||||
#include <Sound/SoundSubmix.h>
|
||||
#include <Structures/NDIBroadcastConfiguration.h>
|
||||
#include <Objects/Media/NDIMediaTexture2D.h>
|
||||
#include <BaseMediaSource.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "NDIMediaSender.generated.h"
|
||||
|
||||
/**
|
||||
A delegate used for notifications on property changes on the NDIMediaSender object
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIMediaSenderPropertyChanged, UNDIMediaSender*, Sender);
|
||||
|
||||
/**
|
||||
A delegate used for notifications on the NDIMediaSender object receiving metadata
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FNDIMediaSenderMetaDataReceived, UNDIMediaSender*, Sender, FString, Data);
|
||||
|
||||
/**
|
||||
Delegates to notify just before and after the NDIMediaSender sends a video, audio, or metadata frame
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIMediaSenderVideoPreSend, UNDIMediaSender*, Sender);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIMediaSenderVideoSent, UNDIMediaSender*, Sender);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIMediaSenderAudioPreSend, UNDIMediaSender*, Sender);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIMediaSenderAudioSent, UNDIMediaSender*, Sender);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIMediaSenderMetaDataPreSend, UNDIMediaSender*, Sender);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNDIMediaSenderMetaDataSent, UNDIMediaSender*, Sender);
|
||||
|
||||
/**
|
||||
Defines a media object representing an NDI(R) Sender object. This object is used with the
|
||||
NDI Broadcast Component to send Audio / Video / Metadata to a 'receiving' NDI object.
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, HideCategories = ("Platforms"), Category = "NDI IO",
|
||||
HideCategories = ("Information"), AutoCollapseCategories = ("Content"),
|
||||
META = (DisplayName = "NDI Sender Object"))
|
||||
class NDIIO_API UNDIMediaSender : public UBaseMediaSource
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
private:
|
||||
/** Describes a user-friendly name of the output stream to differentiate from other output streams on the current
|
||||
* machine */
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Broadcast Settings",
|
||||
META = (DisplayName = "Source Name", AllowPrivateAccess = true))
|
||||
FString SourceName = TEXT("Unreal Engine Output");
|
||||
|
||||
/** Describes the output frame size while sending video frame over NDI */
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Broadcast Settings",
|
||||
META = (DisplayName = "Frame Size", AllowPrivateAccess = true))
|
||||
FIntPoint FrameSize = FIntPoint(1920, 1080);
|
||||
|
||||
/** Represents the desired number of frames (per second) for video to be sent over NDI */
|
||||
UPROPERTY(BlueprintReadwrite, EditDefaultsOnly, Category = "Broadcast Settings",
|
||||
META = (DisplayName = "Frame Rate", AllowPrivateAccess = true))
|
||||
FFrameRate FrameRate = FFrameRate(60, 1);
|
||||
|
||||
/** Sets whether or not to output an alpha channel */
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Broadcast Settings",
|
||||
META = (DisplayName="Output Alpha", AllowPrivateAccess = true))
|
||||
bool OutputAlpha = false;
|
||||
|
||||
UPROPERTY(BlueprintReadonly, VisibleAnywhere, Category = "Broadcast Settings",
|
||||
META = (DisplayName = "Alpha Remap Min", AllowPrivateAccess = true))
|
||||
float AlphaMin = 0.f;
|
||||
|
||||
UPROPERTY(BlueprintReadonly, VisibleAnywhere, Category = "Broadcast Settings",
|
||||
META = (DisplayName = "Alpha Remap Max", AllowPrivateAccess = true))
|
||||
float AlphaMax = 1.f;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Broadcast Settings",
|
||||
META = (DisplayName="Enable Audio", AllowPrivateAccess = true))
|
||||
bool bEnableAudio = true;
|
||||
|
||||
/** Sets whether or not to present PTZ capabilities */
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Broadcast Settings",
|
||||
META = (DisplayName="Enable PTZ", AllowPrivateAccess = true))
|
||||
bool bEnablePTZ = true;
|
||||
|
||||
/** Indicates the texture to send over NDI (optional) */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Content",
|
||||
AdvancedDisplay, META = (DisplayName = "Render Target (optional)", AllowPrivateAccess = true))
|
||||
UTextureRenderTarget2D* RenderTarget = nullptr;
|
||||
|
||||
/**
|
||||
Should perform the Linear to sRGB color space conversion
|
||||
*/
|
||||
UPROPERTY(BlueprintReadonly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Perform Linear to sRGB?", AllowPrivateAccess = true))
|
||||
bool bPerformLinearTosRGB = true;
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
FNDIMediaSenderPropertyChanged OnBroadcastConfigurationChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On MetaData Received by Sender", AllowPrivateAccess = true))
|
||||
FNDIMediaSenderMetaDataReceived OnSenderMetaDataReceived;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On Before Video Being Sent by Sender", AllowPrivateAccess = true))
|
||||
FNDIMediaSenderVideoPreSend OnSenderVideoPreSend;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On Video Sent by Sender", AllowPrivateAccess = true))
|
||||
FNDIMediaSenderVideoSent OnSenderVideoSent;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On Before Audio Being Sent by Sender", AllowPrivateAccess = true))
|
||||
FNDIMediaSenderAudioPreSend OnSenderAudioPreSend;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On Audio Sent by Sender", AllowPrivateAccess = true))
|
||||
FNDIMediaSenderAudioSent OnSenderAudioSent;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On Before MetaData Being Sent by Sender", AllowPrivateAccess = true))
|
||||
FNDIMediaSenderMetaDataPreSend OnSenderMetaDataPreSend;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="NDI Events", META = (DisplayName = "On MetaData Sent by Sender", AllowPrivateAccess = true))
|
||||
FNDIMediaSenderMetaDataSent OnSenderMetaDataSent;
|
||||
|
||||
public:
|
||||
/**
|
||||
Attempts to perform initialization logic for creating a sender through the NDI(R) sdk api
|
||||
*/
|
||||
void Initialize(USoundSubmix* SubmixCapture);
|
||||
|
||||
/**
|
||||
Changes the name of the sender object as seen on the network for remote connections
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Source Name"))
|
||||
void ChangeSourceName(const FString& InSourceName);
|
||||
|
||||
/**
|
||||
Attempts to change the Broadcast information associated with this media object
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Change Broadcast Configuration"))
|
||||
void ChangeBroadcastConfiguration(const FNDIBroadcastConfiguration& InConfiguration);
|
||||
|
||||
/**
|
||||
This will send a metadata frame to all receivers
|
||||
The data is expected to be valid XML
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Send Metadata To Receivers"))
|
||||
void SendMetadataFrame(const FString& Data, bool AttachToVideoFrame = true);
|
||||
/**
|
||||
This will send a metadata frame to all receivers
|
||||
The data will be formatted as: <Element>ElementData</Element>
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Send Metadata To Receivers (Element + Data)"))
|
||||
void SendMetadataFrameAttr(const FString& Element, const FString& ElementData, bool AttachToVideoFrame = true);
|
||||
/**
|
||||
This will send a metadata frame to all receivers
|
||||
The data will be formatted as: <Element Key0="Value0" Key1="Value1" Keyn="Valuen"/>
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "NDI IO", META = (DisplayName = "Send Metadata To Receivers (Element + Attributes)"))
|
||||
void SendMetadataFrameAttrs(const FString& Element, const TMap<FString,FString>& Attributes, bool AttachToVideoFrame = true);
|
||||
|
||||
/**
|
||||
Attempts to change the RenderTarget used in sending video frames over NDI
|
||||
*/
|
||||
void ChangeVideoTexture(UTextureRenderTarget2D* VideoTexture = nullptr);
|
||||
|
||||
/**
|
||||
Change the alpha remapping settings
|
||||
*/
|
||||
void ChangeAlphaRemap(float AlphaMinIn, float AlphaMaxIn);
|
||||
|
||||
/**
|
||||
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
|
||||
@param TimeOut - Indicates the amount of time to wait (in milliseconds) until a change has occurred
|
||||
*/
|
||||
void GetTallyInformation(bool& IsOnPreview, bool& IsOnProgram, uint32 Timeout = 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
|
||||
*/
|
||||
void GetNumberOfConnections(int32& Result);
|
||||
|
||||
/**
|
||||
Attempts to immediately stop sending frames over NDI to any connected receivers
|
||||
*/
|
||||
void Shutdown();
|
||||
|
||||
/**
|
||||
Called before destroying the object. This is called immediately upon deciding to destroy the object,
|
||||
to allow the object to begin an asynchronous cleanup process.
|
||||
*/
|
||||
virtual void BeginDestroy() override;
|
||||
|
||||
/**
|
||||
Set whether or not a RGB to Linear conversion is made
|
||||
*/
|
||||
void PerformLinearTosRGBConversion(bool Value);
|
||||
|
||||
/**
|
||||
Set whether or not to enable PTZ support
|
||||
*/
|
||||
void EnablePTZ(bool Value);
|
||||
|
||||
/**
|
||||
Returns the Render Target used for sending a frame over NDI
|
||||
*/
|
||||
UTextureRenderTarget2D* GetRenderTarget();
|
||||
|
||||
const FIntPoint& GetFrameSize()
|
||||
{
|
||||
return this->FrameSize;
|
||||
}
|
||||
|
||||
const FFrameRate& GetFrameRate()
|
||||
{
|
||||
return this->FrameRate;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool CreateSender();
|
||||
|
||||
/**
|
||||
Attempts to get a metadata frame from the sender.
|
||||
If there is one, the data is broadcast through OnSenderMetaDataReceived.
|
||||
Returns true if metadata was received, false otherwise.
|
||||
*/
|
||||
bool GetMetadataFrame();
|
||||
|
||||
/**
|
||||
This will attempt to generate an audio frame, add the frame to the stack and return immediately,
|
||||
having scheduled the frame asynchronously.
|
||||
*/
|
||||
void TrySendAudioFrame(int64 time_code, float* AudioData, int32 NumSamples, int32 NumChannels, const int32 SampleRate, double AudioClock);
|
||||
|
||||
/**
|
||||
This will attempt to generate a video frame, add the frame to the stack and return immediately,
|
||||
having scheduled the frame asynchronously.
|
||||
*/
|
||||
void TrySendVideoFrame(int64 time_code = 0);
|
||||
|
||||
/**
|
||||
Perform the color conversion (if any) and bit copy from the gpu
|
||||
*/
|
||||
bool DrawRenderTarget(FRHICommandListImmediate& RHICmdList);
|
||||
|
||||
/**
|
||||
Change the render target configuration based on the passed in parameters
|
||||
|
||||
@param InFrameSize The frame size to resize the render target to
|
||||
@param InFrameRate The frame rate at which we should be sending frames via NDI
|
||||
*/
|
||||
void ChangeRenderTargetConfiguration(FIntPoint InFrameSize, FFrameRate InFrameRate);
|
||||
|
||||
virtual bool Validate() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual FString GetUrl() const override
|
||||
{
|
||||
return FString();
|
||||
}
|
||||
|
||||
FTextureResource* GetRenderTargetResource() const;
|
||||
|
||||
void PrepareDefaultTexture();
|
||||
|
||||
private:
|
||||
std::atomic<bool> bIsChangingBroadcastSize { false };
|
||||
|
||||
FTimecode LastRenderTime;
|
||||
|
||||
FTextureRHIRef DefaultVideoTextureRHI;
|
||||
|
||||
TArray<float> SendAudioData;
|
||||
|
||||
NDIlib_video_frame_v2_t NDI_video_frame;
|
||||
NDIlib_send_instance_t p_send_instance = nullptr;
|
||||
|
||||
FCriticalSection AudioSyncContext;
|
||||
FCriticalSection RenderSyncContext;
|
||||
|
||||
/**
|
||||
A texture with CPU readback
|
||||
*/
|
||||
class MappedTexture
|
||||
{
|
||||
private:
|
||||
FTextureRHIRef Texture = nullptr;
|
||||
void* pData = nullptr;
|
||||
std::string MetaData;
|
||||
FIntPoint FrameSize;
|
||||
|
||||
public:
|
||||
~MappedTexture();
|
||||
|
||||
void Create(FIntPoint FrameSize);
|
||||
void Destroy();
|
||||
|
||||
FIntPoint GetSizeXY() const;
|
||||
|
||||
void Resolve(FRHICommandListImmediate& RHICmdList, FRHITexture* SourceTextureRHI, const FResolveRect& Rect = FResolveRect(), const FResolveRect& DestRect = FResolveRect());
|
||||
|
||||
void Map(FRHICommandListImmediate& RHICmdList, int32& OutWidth, int32& OutHeight, int32& OutLineStride);
|
||||
void* MappedData() const;
|
||||
void Unmap(FRHICommandListImmediate& RHICmdList);
|
||||
|
||||
void AddMetaData(const FString& Data);
|
||||
const std::string& GetMetaData() const;
|
||||
|
||||
private:
|
||||
void PrepareTexture();
|
||||
};
|
||||
|
||||
/**
|
||||
Class for managing the sending of mapped texture data to an NDI video stream.
|
||||
Sending is done asynchronously, so mapping and unmapping of texture data must
|
||||
be managed so that CPU accessible texture content remains valid until the
|
||||
sending of the frame is guaranteed to have been completed. This is achieved
|
||||
by double-buffering readback textures.
|
||||
*/
|
||||
class MappedTextureASyncSender
|
||||
{
|
||||
private:
|
||||
MappedTexture MappedTextures[2];
|
||||
int32 CurrentIndex = 0;
|
||||
|
||||
public:
|
||||
void Create(FIntPoint FrameSize);
|
||||
void Destroy();
|
||||
|
||||
FIntPoint GetSizeXY() const;
|
||||
|
||||
void Resolve(FRHICommandListImmediate& RHICmdList, FRHITexture* SourceTextureRHI, const FResolveRect& Rect = FResolveRect(), const FResolveRect& DestRect = FResolveRect());
|
||||
|
||||
void Map(FRHICommandListImmediate& RHICmdList, int32& OutWidth, int32& OutHeight, int32& OutLineStride);
|
||||
void Send(FRHICommandListImmediate& RHICmdList, NDIlib_send_instance_t p_send_instance, NDIlib_video_frame_v2_t& p_video_data);
|
||||
void Flush(FRHICommandListImmediate& RHICmdList, NDIlib_send_instance_t p_send_instance);
|
||||
|
||||
void AddMetaData(const FString& Data);
|
||||
};
|
||||
|
||||
MappedTextureASyncSender ReadbackTextures;
|
||||
bool ReadbackTexturesHaveAlpha = false;
|
||||
FPooledRenderTargetDesc RenderTargetDescriptor;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Sound/SoundWaveProcedural.h>
|
||||
|
||||
#include "NDIMediaSoundWave.generated.h"
|
||||
|
||||
/**
|
||||
Defines a SoundWave object used by an NDI Media Receiver object for capturing audio from
|
||||
a network source
|
||||
*/
|
||||
UCLASS(NotBlueprintable, Category = "NDI IO", META = (DisplayName = "NDI Media Sound Wave"))
|
||||
class NDIIO_API UNDIMediaSoundWave : public USoundWaveProcedural
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
Set the Media Source of this object, so that when this object is called to 'GeneratePCMData' by the engine
|
||||
we can request the media source to provide the pcm data from the current connected source
|
||||
*/
|
||||
void SetConnectionSource(class UNDIMediaReceiver* InMediaSource = nullptr);
|
||||
|
||||
protected:
|
||||
/**
|
||||
Called by the engine to generate pcm data to be 'heard' by audio listener objects
|
||||
*/
|
||||
virtual int32 OnGeneratePCMAudio(TArray<uint8>& OutAudio, int32 NumSamples) override final;
|
||||
|
||||
virtual bool IsReadyForFinishDestroy() override final;
|
||||
|
||||
private:
|
||||
FCriticalSection SyncContext;
|
||||
class UNDIMediaReceiver* MediaSource = nullptr;
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Engine/Texture.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
#include <RHI.h>
|
||||
#include <RHICommandList.h>
|
||||
|
||||
#include "NDIMediaTexture2D.generated.h"
|
||||
|
||||
/**
|
||||
A Texture Object used by an NDI Media Receiver object for capturing video from
|
||||
a network source
|
||||
*/
|
||||
UCLASS(NotBlueprintType, NotBlueprintable, HideDropdown,
|
||||
HideCategories = (ImportSettings, Compression, Texture, Adjustments, Compositing, LevelOfDetail, Object),
|
||||
META = (DisplayName = "NDI Media Texture 2D"))
|
||||
class NDIIO_API UNDIMediaTexture2D : public UTexture
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
virtual float GetSurfaceHeight() const override;
|
||||
virtual float GetSurfaceWidth() const override;
|
||||
|
||||
virtual float GetSurfaceDepth() const;
|
||||
virtual uint32 GetSurfaceArraySize() const;
|
||||
|
||||
virtual ETextureClass GetTextureClass() const;
|
||||
|
||||
virtual void GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) override;
|
||||
virtual EMaterialValueType GetMaterialType() const override;
|
||||
|
||||
virtual void UpdateTextureReference(FRHICommandList& RHICmdList, FTextureRHIRef Reference) final;
|
||||
|
||||
private:
|
||||
virtual class FTextureResource* CreateResource() override;
|
||||
|
||||
void SetMyResource(FTextureResource* ResourceIn);
|
||||
FTextureResource* GetMyResource();
|
||||
const FTextureResource* GetMyResource() const;
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <ExternalTexture.h>
|
||||
#include <TextureResource.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
|
||||
/**
|
||||
A Texture Resource object used by the NDIMediaTexture2D object for capturing video
|
||||
from a network source
|
||||
*/
|
||||
class NDIIO_API FNDIMediaTextureResource : public FTextureResource
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Constructs a new instance of this object specifying a media texture owner
|
||||
|
||||
@param Owner The media object used as the owner for this object
|
||||
*/
|
||||
FNDIMediaTextureResource(class UNDIMediaTexture2D* Owner = nullptr);
|
||||
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
/** FTextureResource Interface Implementation for 'InitDynamicRHI' */
|
||||
virtual void InitRHI(FRHICommandListBase& RHICmdList) override;
|
||||
|
||||
/** FTextureResource Interface Implementation for 'ReleaseDynamicRHI' */
|
||||
virtual void ReleaseRHI() override;
|
||||
#else
|
||||
/** FTextureResource Interface Implementation for 'InitDynamicRHI' */
|
||||
virtual void InitDynamicRHI() override;
|
||||
|
||||
/** FTextureResource Interface Implementation for 'ReleaseDynamicRHI' */
|
||||
virtual void ReleaseDynamicRHI() override;
|
||||
#endif
|
||||
|
||||
/** FTextureResource Interface Implementation for 'GetResourceSize' */
|
||||
SIZE_T GetResourceSize();
|
||||
|
||||
/** FTextureResource Interface Implementation for 'GetSizeX' */
|
||||
virtual uint32 GetSizeX() const override;
|
||||
|
||||
/** FTextureResource Interface Implementation for 'GetSizeY' */
|
||||
virtual uint32 GetSizeY() const override;
|
||||
|
||||
private:
|
||||
class UNDIMediaTexture2D* MediaTexture = nullptr;
|
||||
};
|
||||
109
Plugins/NDIIO/Source/Core/Public/Services/NDIConnectionService.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Engine/TextureRenderTarget2D.h>
|
||||
#include <AudioDevice.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 3)) // 5.3 or later
|
||||
#include <ISubmixBufferListener.h>
|
||||
#endif
|
||||
#include <Widgets/SWindow.h>
|
||||
|
||||
DECLARE_EVENT_OneParam(FNDICoreDelegates, FNDIConnectionServiceSendVideoEvent, int64)
|
||||
DECLARE_EVENT_SixParams(FNDICoreDelegates, FNDIConnectionServiceSendAudioEvent, int64, float*, int32, int32, const int32, double)
|
||||
|
||||
/**
|
||||
A service which runs and triggers updates for interested parties to be notified of
|
||||
Audio and Video Frame events
|
||||
*/
|
||||
class NDIIO_API FNDIConnectionService final : public ISubmixBufferListener
|
||||
{
|
||||
public:
|
||||
static FNDIConnectionServiceSendVideoEvent EventOnSendVideoFrame;
|
||||
private:
|
||||
static TMap<USoundSubmix*, FNDIConnectionServiceSendAudioEvent> SubmixSendAudioFrameEvents;
|
||||
|
||||
public:
|
||||
/**
|
||||
Constructs a new instance of this object
|
||||
*/
|
||||
FNDIConnectionService();
|
||||
|
||||
// Begin the service
|
||||
bool Start();
|
||||
|
||||
// Stop the service
|
||||
void Shutdown();
|
||||
|
||||
bool BeginBroadcastingActiveViewport();
|
||||
void StopBroadcastingActiveViewport();
|
||||
|
||||
bool IsRunningInPIE() const
|
||||
{
|
||||
return bIsInPIEMode;
|
||||
}
|
||||
|
||||
template <typename UserClass>
|
||||
static void AddAudioSender(UserClass* InUserObject, USoundSubmix* Submix, typename TMemFunPtrType<false, UserClass, void (int64 /*time_code*/, float* /*AudioData*/, int32 /*NumSamples*/, int32 /*NumChannels*/, const int32 /*SampleRate*/, double /*AudioClock*/)>::Type InFunc)
|
||||
{
|
||||
FScopeLock Lock(&AudioSyncContext);
|
||||
|
||||
FNDIConnectionServiceSendAudioEvent& SendAudioEvent = SubmixSendAudioFrameEvents.FindOrAdd(Submix);
|
||||
SendAudioEvent.AddUObject(InUserObject, InFunc);
|
||||
}
|
||||
|
||||
template <typename UserClass>
|
||||
static void RemoveAudioSender(UserClass* InUserObject)
|
||||
{
|
||||
FScopeLock Lock(&AudioSyncContext);
|
||||
|
||||
for (auto it = SubmixSendAudioFrameEvents.CreateIterator(); it; ++it)
|
||||
{
|
||||
it->Value.RemoveAll(InUserObject);
|
||||
if (it->Value.IsBound() == false)
|
||||
it.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Handler for when the render thread frame has ended
|
||||
void OnEndRenderFrame();
|
||||
|
||||
void BeginAudioCapture();
|
||||
void StopAudioCapture();
|
||||
|
||||
void OnPostEngineInit();
|
||||
void OnEnginePreExit();
|
||||
|
||||
// Handler for when the active viewport back buffer is about to be resized
|
||||
void OnActiveViewportBackbufferPreResize(void* Backbuffer);
|
||||
|
||||
// Handler for when the back buffer is read to present to the end user
|
||||
void OnActiveViewportBackbufferReadyToPresent(SWindow& Window, const FTextureRHIRef& Backbuffer);
|
||||
|
||||
FTextureResource* GetVideoTextureResource() const;
|
||||
|
||||
virtual void OnNewSubmixBuffer(const USoundSubmix* OwningSubmix, float* AudioData, int32 NumSamples, int32 NumChannels, const int32 SampleRate, double AudioClock) override final;
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 4)) // 5.4 or later
|
||||
virtual const FString& GetListenerName() const override final;
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool bIsInitialized = false;
|
||||
bool bIsAudioInitialized = false;
|
||||
bool bIsBroadcastingActiveViewport = false;
|
||||
bool bIsInPIEMode = false;
|
||||
|
||||
static FCriticalSection AudioSyncContext;
|
||||
static FCriticalSection RenderSyncContext;
|
||||
|
||||
UTextureRenderTarget2D* VideoTexture = nullptr;
|
||||
class UNDIMediaSender* ActiveViewportSender = nullptr;
|
||||
};
|
||||
62
Plugins/NDIIO/Source/Core/Public/Services/NDIFinderService.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include <HAL/Runnable.h>
|
||||
#include <HAL/ThreadSafeBool.h>
|
||||
#include <Structures/NDIConnectionInformation.h>
|
||||
|
||||
/**
|
||||
A Runnable object used for Finding NDI network Sources, and updating interested parties
|
||||
*/
|
||||
class NDIIO_API FNDIFinderService : public FRunnable
|
||||
{
|
||||
public:
|
||||
FNDIFinderService();
|
||||
|
||||
// Begin the service
|
||||
virtual bool Start();
|
||||
|
||||
// Stop the service
|
||||
virtual void Shutdown();
|
||||
|
||||
public:
|
||||
/** Get the available sources on the network */
|
||||
static const TArray<FNDIConnectionInformation> GetNetworkSourceCollection();
|
||||
|
||||
/** Call to update an existing collection of network sources to match the current collection */
|
||||
static bool UpdateSourceCollection(TArray<FNDIConnectionInformation>& InSourceCollection);
|
||||
|
||||
/** Event which is triggered when the collection of network sources has changed */
|
||||
DECLARE_EVENT(FNDICoreDelegates, FNDISourceCollectionChangedEvent)
|
||||
static FNDISourceCollectionChangedEvent EventOnNDISourceCollectionChanged;
|
||||
|
||||
protected:
|
||||
/** FRunnable Interface implementation for 'Init' */
|
||||
virtual bool Init() override;
|
||||
|
||||
/** FRunnable Interface implementation for 'Stop' */
|
||||
virtual void Stop() override;
|
||||
|
||||
/** FRunnable Interface implementation for 'Run' */
|
||||
virtual uint32 Run() override;
|
||||
|
||||
private:
|
||||
bool UpdateNetworkSourceCollection();
|
||||
|
||||
private:
|
||||
bool bShouldWaitOneFrame = true;
|
||||
bool bIsNetworkSourceCollectionDirty = false;
|
||||
|
||||
FThreadSafeBool bIsThreadRunning;
|
||||
FRunnableThread* p_RunnableThread = nullptr;
|
||||
|
||||
static TArray<FNDIConnectionInformation> NetworkSourceCollection;
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Misc/FrameRate.h>
|
||||
|
||||
#include "NDIBroadcastConfiguration.generated.h"
|
||||
|
||||
/**
|
||||
Describes essential properties used for modifying the broadcast configuration of an Sender object
|
||||
*/
|
||||
USTRUCT(BlueprintType, Blueprintable, Category = "NDI IO", META = (DisplayName = "NDI Broadcast Configuration"))
|
||||
struct NDIIO_API FNDIBroadcastConfiguration
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
public:
|
||||
/** Describes the output frame size while sending video frame over NDI */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Broadcast Settings", META = (DisplayName = "Frame Size"))
|
||||
FIntPoint FrameSize = FIntPoint(1920, 1080);
|
||||
|
||||
/** Represents the desired number of frames (per second) for video to be sent over NDI */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Broadcast Settings", META = (DisplayName = "Frame Rate"))
|
||||
FFrameRate FrameRate = FFrameRate(60, 1);
|
||||
|
||||
public:
|
||||
/** Constructs a new instance of this object */
|
||||
FNDIBroadcastConfiguration() = default;
|
||||
|
||||
/** Copies an existing instance to this object */
|
||||
FNDIBroadcastConfiguration(const FNDIBroadcastConfiguration& other);
|
||||
|
||||
/** Copies existing instance properties to this object */
|
||||
FNDIBroadcastConfiguration& operator=(const FNDIBroadcastConfiguration& other);
|
||||
|
||||
/** Destructs this object */
|
||||
virtual ~FNDIBroadcastConfiguration() = default;
|
||||
|
||||
/** Compares this object to 'other' and returns a determination of whether they are equal */
|
||||
bool operator==(const FNDIBroadcastConfiguration& other) const;
|
||||
|
||||
/** Compares this object to 'other" and returns a determination of whether they are NOT equal */
|
||||
bool operator!=(const FNDIBroadcastConfiguration& other) const;
|
||||
|
||||
protected:
|
||||
/** Attempts to serialize this object using an Archive object */
|
||||
virtual FArchive& Serialize(FArchive& Ar);
|
||||
|
||||
private:
|
||||
/** Operator override for serializing this object to an Archive object */
|
||||
friend class FArchive& operator<<(FArchive& Ar, FNDIBroadcastConfiguration& Input)
|
||||
{
|
||||
return Input.Serialize(Ar);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <NDIIOPluginAPI.h>
|
||||
#include <Enumerations/NDISourceBandwidth.h>
|
||||
#include <Serialization/Archive.h>
|
||||
|
||||
#include "NDIConnectionInformation.generated.h"
|
||||
|
||||
/**
|
||||
Describes essential properties used for connection objects over NDI
|
||||
*/
|
||||
USTRUCT(BlueprintType, Blueprintable, Category = "NDI IO", META = (DisplayName = "NDI Connection Information"))
|
||||
struct NDIIO_API FNDIConnectionInformation
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
public:
|
||||
/** A user-friendly name of the source */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Properties", META = (DisplayName = "Source Name"))
|
||||
FString SourceName = FString("");
|
||||
|
||||
/** The machine name of the source */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Properties", META = (DisplayName = "Machine Name"))
|
||||
FString MachineName = FString("");
|
||||
|
||||
/** The stream name of the source */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Properties", META = (DisplayName = "Stream Name"))
|
||||
FString StreamName = FString("");
|
||||
|
||||
/** A location on the network for which this source exists */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Properties", META = (DisplayName = "Url"))
|
||||
FString Url = FString("");
|
||||
|
||||
/** Indicates the current bandwidth mode used for this connection */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Properties", META = (DisplayName = "Bandwidth"))
|
||||
ENDISourceBandwidth Bandwidth = ENDISourceBandwidth::Highest;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Properties", META = (DisplayName = "Mute Audio"))
|
||||
bool bMuteAudio = false;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Properties", META = (DisplayName = "Mute Video"))
|
||||
bool bMuteVideo = false;
|
||||
|
||||
public:
|
||||
/** Constructs a new instance of this object */
|
||||
FNDIConnectionInformation() = default;
|
||||
|
||||
/** Copies an existing instance to this object */
|
||||
FNDIConnectionInformation(const FNDIConnectionInformation& other);
|
||||
|
||||
/** Copies existing instance properties to this object */
|
||||
FNDIConnectionInformation& operator=(const FNDIConnectionInformation& other);
|
||||
|
||||
/** Destructs this object */
|
||||
virtual ~FNDIConnectionInformation() = default;
|
||||
|
||||
/** Implicit conversion to a base NDI bandwidth value */
|
||||
operator NDIlib_recv_bandwidth_e() const;
|
||||
|
||||
/** Compares this object to 'other' and returns a determination of whether they are equal */
|
||||
bool operator==(const FNDIConnectionInformation& other) const;
|
||||
|
||||
/** Compares this object to 'other" and returns a determination of whether they are NOT equal */
|
||||
bool operator!=(const FNDIConnectionInformation& other) const;
|
||||
|
||||
public:
|
||||
/** Resets the current parameters to the default property values */
|
||||
void Reset();
|
||||
|
||||
/** Determines whether this object is valid connection information */
|
||||
bool IsValid() const;
|
||||
|
||||
FString GetNDIName() const;
|
||||
|
||||
protected:
|
||||
/** Attempts to serialize this object using an Archive object */
|
||||
virtual FArchive& Serialize(FArchive& Ar);
|
||||
|
||||
private:
|
||||
/** Operator override for serializing this object to an Archive object */
|
||||
friend class FArchive& operator<<(FArchive& Ar, FNDIConnectionInformation& Input)
|
||||
{
|
||||
return Input.Serialize(Ar);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <NDIIOPluginAPI.h>
|
||||
#include <Serialization/Archive.h>
|
||||
|
||||
#include "NDIReceiverPerformanceData.generated.h"
|
||||
|
||||
/**
|
||||
A structure holding data allowing you to determine the current performance levels of the receiver with the
|
||||
ability to detect whether frames has been dropped
|
||||
*/
|
||||
USTRUCT(BlueprintType, Blueprintable, Category = "NDI IO", META = (DisplayName = "NDI Receiver Performance Data"))
|
||||
struct NDIIO_API FNDIReceiverPerformanceData
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
The number of audio frames received from the NDI sender
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information", META = (DisplayName = "Audio Frames"))
|
||||
int64 AudioFrames = 0;
|
||||
|
||||
/**
|
||||
The number of video frames dropped in transit from an NDI sender
|
||||
*/
|
||||
UPROPERTY(BlueprintReadonly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Dropped Video Frames"))
|
||||
int64 DroppedVideoFrames = 0;
|
||||
|
||||
/**
|
||||
The number of audio frames dropped in transit from the NDI sender
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Dropped Audio Frames"))
|
||||
int64 DroppedAudioFrames = 0;
|
||||
|
||||
/**
|
||||
The number of metadata frames dropped in transit from the NDI sender
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information",
|
||||
META = (DisplayName = "Dropped Metadata Frames"))
|
||||
int64 DroppedMetadataFrames = 0;
|
||||
|
||||
/**
|
||||
The number of metadata frames received from the NDI sender
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information", META = (DisplayName = "Metadata Frames"))
|
||||
int64 MetadataFrames = 0;
|
||||
|
||||
/**
|
||||
The number of video frames received from the NDI sender
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Information", META = (DisplayName = "Video Frames"))
|
||||
int64 VideoFrames = 0;
|
||||
|
||||
public:
|
||||
/** Constructs a new instance of this object */
|
||||
FNDIReceiverPerformanceData() = default;
|
||||
|
||||
/** Copies an existing instance to this object */
|
||||
FNDIReceiverPerformanceData(const FNDIReceiverPerformanceData& other);
|
||||
|
||||
/** Copies existing instance properties to this object */
|
||||
FNDIReceiverPerformanceData& operator=(const FNDIReceiverPerformanceData& other);
|
||||
|
||||
/** Destructs this object */
|
||||
virtual ~FNDIReceiverPerformanceData() = default;
|
||||
|
||||
/** Compares this object to 'other' and returns a determination of whether they are equal */
|
||||
bool operator==(const FNDIReceiverPerformanceData& other) const;
|
||||
|
||||
/** Compares this object to 'other" and returns a determination of whether they are NOT equal */
|
||||
bool operator!=(const FNDIReceiverPerformanceData& other) const;
|
||||
|
||||
public:
|
||||
/** Resets the current parameters to the default property values */
|
||||
void Reset();
|
||||
|
||||
protected:
|
||||
/** Attempts to serialize this object using an Archive object */
|
||||
virtual FArchive& Serialize(FArchive& Ar);
|
||||
|
||||
private:
|
||||
/** Operator override for serializing this object to an Archive object */
|
||||
friend class FArchive& operator<<(FArchive& Ar, FNDIReceiverPerformanceData& Input)
|
||||
{
|
||||
return Input.Serialize(Ar);
|
||||
}
|
||||
};
|
||||
131
Plugins/NDIIO/Source/Core/Public/Structures/NDIXml.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
#include <FastXml.h>
|
||||
|
||||
class NDIXmlElementParser
|
||||
{
|
||||
public:
|
||||
virtual ~NDIXmlElementParser()
|
||||
{}
|
||||
|
||||
// Start parsing this element
|
||||
virtual bool ProcessOpen(const TCHAR* ElementName, const TCHAR* ElementData)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse an attribute of this element
|
||||
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Start parsing a sub-element
|
||||
virtual TSharedRef<NDIXmlElementParser>* ProcessElement(const TCHAR* ElementName, const TCHAR* ElementData)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Finish parsing this element
|
||||
virtual bool ProcessClose(const TCHAR* ElementName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class NDIXmlElementParser_null : public NDIXmlElementParser
|
||||
{
|
||||
public:
|
||||
};
|
||||
|
||||
|
||||
class NDIXmlParser : public IFastXmlCallback
|
||||
{
|
||||
public:
|
||||
virtual ~NDIXmlParser()
|
||||
{}
|
||||
|
||||
|
||||
void AddElementParser(FName ElementName, TSharedRef<NDIXmlElementParser> ElementParser)
|
||||
{
|
||||
ElementParsers.Add(ElementName, ElementParser);
|
||||
}
|
||||
|
||||
virtual bool ProcessXmlDeclaration(const TCHAR* ElementData, int32 XmlFileLineNumber) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ProcessElement(const TCHAR* ElementName, const TCHAR* ElementData, int32 XmlFileLineNumber) override
|
||||
{
|
||||
if(ElementParserStack.Num() == 0)
|
||||
{
|
||||
TSharedRef<NDIXmlElementParser>* ParserPtr = ElementParsers.Find(ElementName);
|
||||
if(ParserPtr == nullptr)
|
||||
ParserPtr = &NullParser;
|
||||
|
||||
ElementParserStack.Push(*ParserPtr);
|
||||
return (*ParserPtr)->ProcessOpen(ElementName, ElementData);
|
||||
}
|
||||
else
|
||||
{
|
||||
TSharedRef<NDIXmlElementParser>* ParserPtr = ElementParserStack.Last()->ProcessElement(ElementName, ElementData);
|
||||
if(ParserPtr == nullptr)
|
||||
ParserPtr = &NullParser;
|
||||
|
||||
ElementParserStack.Push(*ParserPtr);
|
||||
return (*ParserPtr)->ProcessOpen(ElementName, ElementData);
|
||||
}
|
||||
|
||||
//return false;
|
||||
}
|
||||
|
||||
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override
|
||||
{
|
||||
if(ElementParserStack.Num() == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ElementParserStack.Last()->ProcessAttribute(AttributeName, AttributeValue);
|
||||
}
|
||||
|
||||
//return false;
|
||||
}
|
||||
|
||||
virtual bool ProcessClose(const TCHAR* ElementName) override
|
||||
{
|
||||
if(ElementParserStack.Num() == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto Parser = ElementParserStack.Pop();
|
||||
return Parser->ProcessClose(ElementName);
|
||||
}
|
||||
|
||||
//return false;
|
||||
}
|
||||
|
||||
virtual bool ProcessComment(const TCHAR* Comment) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
TMap<FName, TSharedRef<NDIXmlElementParser> > ElementParsers;
|
||||
TArray<TSharedRef<NDIXmlElementParser> > ElementParserStack;
|
||||
|
||||
TSharedRef<NDIXmlElementParser> NullParser { MakeShareable(new NDIXmlElementParser_null()) };
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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 <Factories/NDIMediaReceiverFactory.h>
|
||||
|
||||
#include <AssetTypeCategories.h>
|
||||
#include <Objects/Media/NDIMediaReceiver.h>
|
||||
|
||||
#define LOCTEXT_NAMESPACE "NDIIOEditorMediaReceiverFactory"
|
||||
|
||||
UNDIMediaReceiverFactory::UNDIMediaReceiverFactory(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer) {
|
||||
|
||||
this->bCreateNew = true;
|
||||
this->bEditAfterNew = true;
|
||||
|
||||
this->SupportedClass = UNDIMediaReceiver::StaticClass();
|
||||
}
|
||||
|
||||
FText UNDIMediaReceiverFactory::GetDisplayName() const { return LOCTEXT("NDIMediaReceiverFactoryDisplayName", "NDI Media Receiver"); }
|
||||
|
||||
uint32 UNDIMediaReceiverFactory::GetMenuCategories() const
|
||||
{
|
||||
return EAssetTypeCategories::Media;
|
||||
}
|
||||
|
||||
UObject* UNDIMediaReceiverFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UNDIMediaReceiver>(InParent, InClass, InName, Flags | RF_Transactional);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 <Factories/NDIMediaSenderFactory.h>
|
||||
|
||||
#include <AssetTypeCategories.h>
|
||||
#include <Objects/Media/NDIMediaSender.h>
|
||||
|
||||
#define LOCTEXT_NAMESPACE "NDIIOEditorMediaSenderFactory"
|
||||
|
||||
UNDIMediaSenderFactory::UNDIMediaSenderFactory(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer) {
|
||||
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
|
||||
this->SupportedClass = UNDIMediaSender::StaticClass();
|
||||
}
|
||||
|
||||
FText UNDIMediaSenderFactory::GetDisplayName() const { return LOCTEXT("NDIMediaSenderFactoryDisplayName", "NDI Media Sender"); }
|
||||
|
||||
uint32 UNDIMediaSenderFactory::GetMenuCategories() const { return EAssetTypeCategories::Media; }
|
||||
|
||||
UObject* UNDIMediaSenderFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UNDIMediaSender>(InParent, InClass, InName, Flags | RF_Transactional);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 <Factories/NDIMediaSoundWaveFactory.h>
|
||||
|
||||
#include <AssetTypeCategories.h>
|
||||
#include <Objects/Media/NDIMediaSoundWave.h>
|
||||
|
||||
#define LOCTEXT_NAMESPACE "NDIIOEditorMediaSoundWaveFactory"
|
||||
|
||||
UNDIMediaSoundWaveFactory::UNDIMediaSoundWaveFactory(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer) {
|
||||
|
||||
this->bCreateNew = true;
|
||||
this->bEditAfterNew = true;
|
||||
|
||||
this->SupportedClass = UNDIMediaSoundWave::StaticClass();
|
||||
}
|
||||
|
||||
FText UNDIMediaSoundWaveFactory::GetDisplayName() const { return LOCTEXT("NDIMediaSoundWaveFactoryDisplayName", "NDI Media Sound Wave"); }
|
||||
|
||||
uint32 UNDIMediaSoundWaveFactory::GetMenuCategories() const { return EAssetTypeCategories::Sounds; }
|
||||
|
||||
UObject* UNDIMediaSoundWaveFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UNDIMediaSoundWave>(InParent, InName, Flags | RF_Transactional);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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 <Factories/NDIMediaTexture2DFactory.h>
|
||||
|
||||
#include <AssetTypeCategories.h>
|
||||
#include <Objects/Media/NDIMediaTexture2D.h>
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
|
||||
#define LOCTEXT_NAMESPACE "NDIIOEditorMediaSoundWaveFactory"
|
||||
|
||||
UNDIMediaTexture2DFactory::UNDIMediaTexture2DFactory(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer) {
|
||||
|
||||
this->bCreateNew = true;
|
||||
this->bEditAfterNew = true;
|
||||
|
||||
this->SupportedClass = UNDIMediaTexture2D::StaticClass();
|
||||
}
|
||||
|
||||
FText UNDIMediaTexture2DFactory::GetDisplayName() const { return LOCTEXT("NDIMediaTexture2DFactoryDisplayName", "NDI Media Texture2D"); }
|
||||
|
||||
uint32 UNDIMediaTexture2DFactory::GetMenuCategories() const { return EAssetTypeCategories::Textures; }
|
||||
|
||||
UObject* UNDIMediaTexture2DFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
if (UNDIMediaTexture2D* Resource = NewObject<UNDIMediaTexture2D>(InParent, InName, Flags | RF_Transactional))
|
||||
{
|
||||
Resource->UpdateResource();
|
||||
return Resource;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
136
Plugins/NDIIO/Source/Editor/Classes/NDIIOEditorModule.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
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 <NDIIOEditorModule.h>
|
||||
|
||||
#include <Editor.h>
|
||||
#include <PropertyEditorModule.h>
|
||||
#include <IPlacementModeModule.h>
|
||||
#include <Interfaces/IPluginManager.h>
|
||||
#include <Styling/SlateStyleRegistry.h>
|
||||
|
||||
#include <Actors/NDIReceiveActor.h>
|
||||
#include <Actors/NDIBroadcastActor.h>
|
||||
|
||||
#include <Framework/Application/SlateApplication.h>
|
||||
|
||||
#include <Misc/EngineVersionComparison.h>
|
||||
|
||||
#include "Widgets/NDIWidgets.h"
|
||||
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FNDIEditorModule"
|
||||
#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush(StyleInstance->RootToContentDir(RelativePath, TEXT(".png")), __VA_ARGS__)
|
||||
|
||||
#define PLACEMENT_CATEGORY TEXT("NDI(R)")
|
||||
#define PLACEMENT_LOCTEXT NSLOCTEXT("Vizrt", "NDI", "NDI(R)")
|
||||
#define PLACEMENT_TEXT TEXT("PMNDI")
|
||||
|
||||
void FNDIIOEditorModule::StartupModule()
|
||||
{
|
||||
const FName& CategoryName = PLACEMENT_CATEGORY;
|
||||
IPlacementModeModule& PlacementModeModule = IPlacementModeModule::Get();
|
||||
|
||||
const FVector2D Icon20x20(20.0f, 20.0f);
|
||||
const FVector2D Icon64x64(64.0f, 64.0f);
|
||||
|
||||
this->StyleInstance = MakeUnique<FSlateStyleSet>("NDIEditorStyle");
|
||||
|
||||
if (IPlugin* NDIIOPlugin = IPluginManager::Get().FindPlugin("NDIIOPlugin").Get())
|
||||
{
|
||||
StyleInstance->SetContentRoot(FPaths::Combine(NDIIOPlugin->GetContentDir(), TEXT("Editor/Icons")));
|
||||
|
||||
StyleInstance->Set("ClassThumbnail.NDIBroadcastActor", new IMAGE_BRUSH("NDIBroadcastActorIcon_x64", Icon64x64));
|
||||
StyleInstance->Set("ClassIcon.NDIBroadcastActor", new IMAGE_BRUSH("NDIBroadcastActorIcon_x20", Icon20x20));
|
||||
|
||||
StyleInstance->Set("ClassThumbnail.NDIReceiveActor", new IMAGE_BRUSH("NDIReceiveActorIcon_x64", Icon64x64));
|
||||
StyleInstance->Set("ClassIcon.NDIReceiveActor", new IMAGE_BRUSH("NDIReceiveActorIcon_x20", Icon20x20));
|
||||
|
||||
StyleInstance->Set("ClassThumbnail.NDIMediaReceiver", new IMAGE_BRUSH("NDIReceiverIcon_x64", Icon64x64));
|
||||
StyleInstance->Set("ClassIcon.NDIMediaReceiver", new IMAGE_BRUSH("NDIReceiverIcon_x20", Icon20x20));
|
||||
|
||||
StyleInstance->Set("ClassThumbnail.NDIMediaSender", new IMAGE_BRUSH("NDISenderIcon_x64", Icon64x64));
|
||||
StyleInstance->Set("ClassIcon.NDIMediaSender", new IMAGE_BRUSH("NDISenderIcon_x20", Icon20x20));
|
||||
|
||||
StyleInstance->Set("ClassThumbnail.NDIMediaSoundWave", new IMAGE_BRUSH("NDISoundWaveIcon_x64", Icon64x64));
|
||||
StyleInstance->Set("ClassIcon.NDIMediaSoundWave", new IMAGE_BRUSH("NDISoundWaveIcon_x20", Icon20x20));
|
||||
|
||||
StyleInstance->Set("ClassThumbnail.NDIMediaTexture2D", new IMAGE_BRUSH("NDIVideoTextureIcon_x64", Icon64x64));
|
||||
StyleInstance->Set("ClassIcon.NDIMediaTexture2D", new IMAGE_BRUSH("NDIVideoTextureIcon_x20", Icon20x20));
|
||||
|
||||
FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance.Get());
|
||||
|
||||
PlacementModeModule.RegisterPlacementCategory(
|
||||
FPlacementCategoryInfo(
|
||||
PLACEMENT_LOCTEXT,
|
||||
CategoryName,
|
||||
PLACEMENT_TEXT,
|
||||
41, // FBuiltInPlacementCategories::Volumes() == 40
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Get the Registered Placement Category
|
||||
if (const FPlacementCategoryInfo* PlacementCategoryInformation = PlacementModeModule.GetRegisteredPlacementCategory(CategoryName))
|
||||
{
|
||||
// Register the NDI Broadcast Actor a placeable item within the editor
|
||||
PlacementModeModule.RegisterPlaceableItem(PlacementCategoryInformation->UniqueHandle, MakeShareable(
|
||||
new FPlaceableItem(
|
||||
*UActorFactory::StaticClass(),
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 6)) // 5.6 or later
|
||||
FAssetData(GetDefault<ANDIBroadcastActor>()),
|
||||
#else
|
||||
FAssetData(ANDIBroadcastActor::StaticClass()->ClassDefaultObject),
|
||||
#endif
|
||||
FName("ClassThumbnail.NDIBroadcastActor"),
|
||||
NAME_None,
|
||||
TOptional<FLinearColor>(),
|
||||
10,
|
||||
NSLOCTEXT("Vizrt", "NDIBroadcastActor", "NDI Broadcast Actor")
|
||||
))
|
||||
);
|
||||
|
||||
// Register the NDI Receive Actor a placeable item within the editor
|
||||
PlacementModeModule.RegisterPlaceableItem(PlacementCategoryInformation->UniqueHandle, MakeShareable(
|
||||
new FPlaceableItem(
|
||||
*UActorFactory::StaticClass(),
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 6)) // 5.6 or later
|
||||
FAssetData(GetDefault<ANDIReceiveActor>()),
|
||||
#else
|
||||
FAssetData(ANDIReceiveActor::StaticClass()->ClassDefaultObject),
|
||||
#endif
|
||||
FName("ClassThumbnail.NDIReceiveActor"),
|
||||
NAME_None,
|
||||
TOptional<FLinearColor>(),
|
||||
20,
|
||||
NSLOCTEXT("Vizrt", "NDIReceiveActor", "NDI Receive Actor")
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
PropertyModule.RegisterCustomPropertyTypeLayout(FNDIConnectionInformation::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FNDIConnectionInformationCustomization::MakeInstance));
|
||||
}
|
||||
|
||||
void FNDIIOEditorModule::ShutdownModule()
|
||||
{
|
||||
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
PropertyModule.UnregisterCustomPropertyTypeLayout(FNDIConnectionInformation::StaticStruct()->GetFName());
|
||||
|
||||
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance.Get());
|
||||
StyleInstance.Reset();
|
||||
|
||||
IPlacementModeModule& PlacementModeModule = IPlacementModeModule::Get();
|
||||
PlacementModeModule.UnregisterPlacementCategory(PLACEMENT_CATEGORY);
|
||||
}
|
||||
|
||||
#undef PLACEMENT_CATEGORY
|
||||
#undef PLACEMENT_LOCTEXT
|
||||
#undef PLACEMENT_TEXT
|
||||
#undef IMAGE_BRUSH
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
380
Plugins/NDIIO/Source/Editor/Classes/Widgets/NDIWidgets.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
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 <Widgets/NDIWidgets.h>
|
||||
#include <Services/NDIFinderService.h>
|
||||
|
||||
#include <DetailLayoutBuilder.h>
|
||||
#include <DetailWidgetRow.h>
|
||||
#include <Editor.h>
|
||||
#include <IDetailChildrenBuilder.h>
|
||||
#include <IPropertyUtilities.h>
|
||||
#include <PropertyHandle.h>
|
||||
#include <Framework/MultiBox/MultiBoxBuilder.h>
|
||||
#include <Widgets/Input/SComboButton.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#define LOCTEXT_NAMESPACE "UNDIWidgets"
|
||||
|
||||
|
||||
/**
|
||||
Organizes NDI sources into a tree
|
||||
*/
|
||||
struct FNDISourceTreeItem
|
||||
{
|
||||
TArray<TSharedRef<FNDISourceTreeItem> > Children;
|
||||
FNDIConnectionInformation NDISource;
|
||||
FText DisplayText;
|
||||
bool IsExpanded { false };
|
||||
bool IsSelected { false };
|
||||
|
||||
FNDISourceTreeItem()
|
||||
{}
|
||||
|
||||
FNDISourceTreeItem(const FText& DisplayTextIn)
|
||||
: DisplayText(DisplayTextIn)
|
||||
{}
|
||||
|
||||
FNDISourceTreeItem(const FNDIConnectionInformation& Source)
|
||||
: NDISource(Source)
|
||||
{}
|
||||
|
||||
FNDISourceTreeItem(TSharedRef<FNDISourceTreeItem>&& Child)
|
||||
{
|
||||
Children.Add(Child);
|
||||
}
|
||||
|
||||
static const TSharedRef<FNDISourceTreeItem>* FindMachineNode(const FNDISourceTreeItem& RootNode, const FNDIConnectionInformation& SourceItem)
|
||||
{
|
||||
const TSharedRef<FNDISourceTreeItem>* MachineNode = nullptr;
|
||||
|
||||
if(!SourceItem.MachineName.IsEmpty())
|
||||
{
|
||||
const FString& SearchName = SourceItem.MachineName;
|
||||
MachineNode = RootNode.Children.FindByPredicate([&SearchName](const TSharedRef<FNDISourceTreeItem>& Child)
|
||||
{
|
||||
if(Child->Children.Num() > 0)
|
||||
return Child->Children[0]->NDISource.MachineName == SearchName;
|
||||
else
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else if(!SourceItem.Url.IsEmpty())
|
||||
{
|
||||
const FString& SearchName = SourceItem.Url;
|
||||
MachineNode = RootNode.Children.FindByPredicate([&SearchName](const TSharedRef<FNDISourceTreeItem>& Child)
|
||||
{
|
||||
if(Child->Children.Num() > 0)
|
||||
return Child->Children[0]->NDISource.Url == SearchName;
|
||||
else
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return MachineNode;
|
||||
}
|
||||
|
||||
static const TSharedRef<FNDISourceTreeItem>* FindStreamNodeInMachineNode(const TSharedRef<FNDISourceTreeItem>& MachineNode, const FNDIConnectionInformation& SourceItem)
|
||||
{
|
||||
const TSharedRef<FNDISourceTreeItem>* StreamNode = nullptr;
|
||||
|
||||
if(!SourceItem.StreamName.IsEmpty())
|
||||
{
|
||||
const FString& SearchName = SourceItem.StreamName;
|
||||
StreamNode = MachineNode->Children.FindByPredicate([&SearchName](const TSharedRef<FNDISourceTreeItem>& Child)
|
||||
{
|
||||
return Child->NDISource.StreamName == SearchName;
|
||||
});
|
||||
}
|
||||
else if(!SourceItem.Url.IsEmpty())
|
||||
{
|
||||
const FString& SearchName = SourceItem.Url;
|
||||
StreamNode = MachineNode->Children.FindByPredicate([&SearchName](const TSharedRef<FNDISourceTreeItem>& Child)
|
||||
{
|
||||
return Child->NDISource.Url == SearchName;
|
||||
});
|
||||
}
|
||||
|
||||
return StreamNode;
|
||||
}
|
||||
|
||||
void SetFromSources(const TArray<FNDIConnectionInformation>& SourceItems, const FText& SearchingTxt, bool StartExpanded)
|
||||
{
|
||||
FNDISourceTreeItem RootNode;
|
||||
|
||||
//
|
||||
// Build new tree
|
||||
//
|
||||
|
||||
for(int32 i = 0; i < SourceItems.Num(); ++i)
|
||||
{
|
||||
const TSharedRef<FNDISourceTreeItem>* MachineNode = FindMachineNode(RootNode, SourceItems[i]);
|
||||
|
||||
if(MachineNode != nullptr)
|
||||
{
|
||||
FNDISourceTreeItem* NewNode = new FNDISourceTreeItem(SourceItems[i]);
|
||||
(*MachineNode)->Children.Add(MakeShareable(NewNode));
|
||||
}
|
||||
else
|
||||
{
|
||||
FNDISourceTreeItem* NewNode = new FNDISourceTreeItem(SourceItems[i]);
|
||||
FNDISourceTreeItem* NewMachineNode = new FNDISourceTreeItem(MakeShareable(NewNode));
|
||||
RootNode.Children.Add(MakeShareable(NewMachineNode));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Preserve expansion and selection state by matching with old tree
|
||||
//
|
||||
|
||||
for(int32 i = 0; i < RootNode.Children.Num(); ++i)
|
||||
{
|
||||
const TSharedRef<FNDISourceTreeItem>* OldMachineNode = FindMachineNode(*this, RootNode.Children[i]->Children[0]->NDISource);
|
||||
if(OldMachineNode != nullptr)
|
||||
{
|
||||
RootNode.Children[i]->IsExpanded = (*OldMachineNode)->IsExpanded;
|
||||
|
||||
for(int32 j = 0; j < RootNode.Children[i]->Children.Num(); ++j)
|
||||
{
|
||||
const TSharedRef<FNDISourceTreeItem>* OldStreamNode = FindStreamNodeInMachineNode(*OldMachineNode, RootNode.Children[i]->Children[j]->NDISource);
|
||||
if(OldStreamNode != nullptr)
|
||||
{
|
||||
RootNode.Children[i]->Children[j]->IsSelected = (*OldStreamNode)->IsSelected;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RootNode.Children[i]->IsExpanded = StartExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
if(RootNode.Children.Num() == 0)
|
||||
{
|
||||
RootNode.Children.Add(MakeShareable(new FNDISourceTreeItem(SearchingTxt)));
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Set to new tree
|
||||
//
|
||||
|
||||
*this = RootNode;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
A menu widget containing NDI sources
|
||||
*/
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnSourceClicked, FNDIConnectionInformation);
|
||||
|
||||
class SNDISourcesMenu : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SNDISourcesMenu)
|
||||
: _OnSourceClicked()
|
||||
{}
|
||||
|
||||
SLATE_EVENT(FOnSourceClicked, OnSourceClicked)
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
SNDISourcesMenu()
|
||||
{}
|
||||
|
||||
virtual ~SNDISourcesMenu()
|
||||
{
|
||||
FNDIFinderService::EventOnNDISourceCollectionChanged.Remove(SourceCollectionChangedEventHandle);
|
||||
SourceCollectionChangedEventHandle.Reset();
|
||||
}
|
||||
|
||||
void Construct(const FArguments& InArgs)
|
||||
{
|
||||
OnSourceClicked = InArgs._OnSourceClicked;
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SComboButton)
|
||||
.ButtonContent()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Font(IDetailLayoutBuilder::GetDetailFont())
|
||||
.ToolTipText(LOCTEXT("NDI Sources Tip", "Currently Available NDI Sources"))
|
||||
.Text(LOCTEXT("NDI Sources", "NDI Sources"))
|
||||
]
|
||||
.OnGetMenuContent_Lambda([this]() -> TSharedRef<SWidget>
|
||||
{
|
||||
FMenuBuilder MenuBuilder(true, nullptr);
|
||||
|
||||
for (const auto& Sources : SourceTreeItems.Children)
|
||||
ConstructSourceMenu(MenuBuilder, Sources.Get());
|
||||
|
||||
return MenuBuilder.MakeWidget();
|
||||
})
|
||||
];
|
||||
|
||||
UpdateSources = true;
|
||||
|
||||
FNDIFinderService::EventOnNDISourceCollectionChanged.Remove(SourceCollectionChangedEventHandle);
|
||||
SourceCollectionChangedEventHandle.Reset();
|
||||
SourceCollectionChangedEventHandle = FNDIFinderService::EventOnNDISourceCollectionChanged.AddLambda([this]()
|
||||
{
|
||||
UpdateSources = true;
|
||||
});
|
||||
}
|
||||
|
||||
virtual void Tick(const FGeometry& AllottedGeometry, const double CurrentTime, const float DeltaTime) override
|
||||
{
|
||||
bool IsDifferent = false;
|
||||
|
||||
if (UpdateSources.exchange(false))
|
||||
{
|
||||
IsDifferent = FNDIFinderService::UpdateSourceCollection(SourceItems);
|
||||
}
|
||||
|
||||
if (SourceItems.Num() == 0)
|
||||
{
|
||||
FText NewSearchingTxt;
|
||||
|
||||
double WholeTime = 0.0;
|
||||
double FracTime = FMath::Modf(CurrentTime, &WholeTime);
|
||||
if(FracTime < 1/4.0)
|
||||
NewSearchingTxt = FText(LOCTEXT("NDI Sources Searching0", "Searching"));
|
||||
else if(FracTime < 2/4.0)
|
||||
NewSearchingTxt = FText(LOCTEXT("NDI Sources Searching1", "Searching."));
|
||||
else if(FracTime < 3/4.0)
|
||||
NewSearchingTxt = FText(LOCTEXT("NDI Sources Searching2", "Searching.."));
|
||||
else
|
||||
NewSearchingTxt = FText(LOCTEXT("NDI Sources Searching3", "Searching..."));
|
||||
|
||||
if(!NewSearchingTxt.EqualTo(SearchingTxt))
|
||||
{
|
||||
SearchingTxt = NewSearchingTxt;
|
||||
IsDifferent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsDifferent)
|
||||
{
|
||||
SourceTreeItems.SetFromSources(SourceItems, SearchingTxt, false);
|
||||
Invalidate(EInvalidateWidgetReason::PaintAndVolatility | EInvalidateWidgetReason::ChildOrder);
|
||||
}
|
||||
|
||||
SCompoundWidget::Tick(AllottedGeometry, CurrentTime, DeltaTime);
|
||||
}
|
||||
|
||||
protected:
|
||||
void ConstructSourceMenu(FMenuBuilder& MenuBuilder, const FNDISourceTreeItem& SourceTreeItem)
|
||||
{
|
||||
if (SourceTreeItem.NDISource.IsValid())
|
||||
{
|
||||
MenuBuilder.AddMenuEntry(
|
||||
FText::FromString(SourceTreeItem.NDISource.StreamName),
|
||||
FText::GetEmpty(),
|
||||
FSlateIcon(),
|
||||
FUIAction(FExecuteAction::CreateLambda([this,&SourceTreeItem]()
|
||||
{
|
||||
this->OnSourceClicked.ExecuteIfBound(SourceTreeItem.NDISource);
|
||||
})),
|
||||
NAME_None,
|
||||
EUserInterfaceActionType::Button
|
||||
);
|
||||
}
|
||||
else if (SourceTreeItem.Children.Num() > 0)
|
||||
{
|
||||
MenuBuilder.AddSubMenu(
|
||||
FText::FromString(SourceTreeItem.Children[0]->NDISource.MachineName),
|
||||
FText::GetEmpty(),
|
||||
FNewMenuDelegate::CreateLambda([this,&SourceTreeItem](FMenuBuilder& MenuBuilder)
|
||||
{
|
||||
for(const auto& ChildSource : SourceTreeItem.Children)
|
||||
ConstructSourceMenu(MenuBuilder, ChildSource.Get());
|
||||
})
|
||||
);
|
||||
}
|
||||
else if (!SourceTreeItem.DisplayText.IsEmpty())
|
||||
{
|
||||
MenuBuilder.AddMenuEntry(
|
||||
SourceTreeItem.DisplayText,
|
||||
FText::GetEmpty(),
|
||||
FSlateIcon(),
|
||||
FUIAction(FExecuteAction::CreateLambda([this]
|
||||
{
|
||||
})),
|
||||
NAME_None,
|
||||
EUserInterfaceActionType::Button
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
TArray<FNDIConnectionInformation> SourceItems;
|
||||
FText SearchingTxt;
|
||||
FNDISourceTreeItem SourceTreeItems;
|
||||
|
||||
FDelegateHandle SourceCollectionChangedEventHandle;
|
||||
std::atomic_bool UpdateSources { false };
|
||||
|
||||
FOnSourceClicked OnSourceClicked;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Customization of NDIConnectionInformation property
|
||||
by including a menu to select from currently available NDI sources
|
||||
*/
|
||||
|
||||
TSharedRef<IPropertyTypeCustomization> FNDIConnectionInformationCustomization::MakeInstance()
|
||||
{
|
||||
return MakeShareable(new FNDIConnectionInformationCustomization);
|
||||
}
|
||||
|
||||
void FNDIConnectionInformationCustomization::CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils)
|
||||
{
|
||||
HeaderRow.NameContent()
|
||||
[
|
||||
PropertyHandle->CreatePropertyNameWidget()
|
||||
]
|
||||
.ValueContent()
|
||||
[
|
||||
SNew(SNDISourcesMenu)
|
||||
.OnSourceClicked_Lambda([this,PropertyHandle](FNDIConnectionInformation Source)
|
||||
{
|
||||
TArray<void*> RawData;
|
||||
PropertyHandle->AccessRawData(RawData);
|
||||
FNDIConnectionInformation* ConnectionInformation = reinterpret_cast<FNDIConnectionInformation*>(RawData[0]);
|
||||
if (ConnectionInformation != nullptr)
|
||||
{
|
||||
ConnectionInformation->Url = "";
|
||||
PropertyHandle->GetChildHandle("SourceName")->SetValue(Source.SourceName);
|
||||
}
|
||||
})
|
||||
].IsEnabled(true);
|
||||
}
|
||||
|
||||
void FNDIConnectionInformationCustomization::CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils)
|
||||
{
|
||||
TSharedPtr<IPropertyUtilities> PropertyUtils = CustomizationUtils.GetPropertyUtilities();
|
||||
uint32 NumberOfChild;
|
||||
if (PropertyHandle->GetNumChildren(NumberOfChild) == FPropertyAccess::Success)
|
||||
{
|
||||
for (uint32 Index = 0; Index < NumberOfChild; ++Index)
|
||||
{
|
||||
TSharedRef<IPropertyHandle> ChildPropertyHandle = PropertyHandle->GetChildHandle(Index).ToSharedRef();
|
||||
ChildBuilder.AddProperty(ChildPropertyHandle)
|
||||
.ShowPropertyButtons(true)
|
||||
.IsEnabled(MakeAttributeLambda([=] { return !PropertyHandle->IsEditConst() && PropertyUtils->IsPropertyEditingEnabled(); }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
92
Plugins/NDIIO/Source/Editor/NDIIOEditor.Build.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class NDIIOEditor : ModuleRules
|
||||
{
|
||||
public NDIIOEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
#if UE_5_2_OR_LATER
|
||||
IWYUSupport = IWYUSupport.Full;
|
||||
#else
|
||||
bEnforceIWYU = true;
|
||||
#endif
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
#region Public Includes
|
||||
|
||||
if (Directory.Exists(Path.Combine(ModuleDirectory, "Public")))
|
||||
{
|
||||
PublicIncludePaths.AddRange(new string[] {
|
||||
// ... add public include paths required here ...
|
||||
Path.Combine(ModuleDirectory, "Public" ),
|
||||
});
|
||||
}
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[] {
|
||||
"Engine",
|
||||
"Core",
|
||||
"CoreUObject"
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
if (Target.bBuildEditor == true)
|
||||
{
|
||||
#region Private Includes
|
||||
|
||||
if (Directory.Exists(Path.Combine(ModuleDirectory, "Private")))
|
||||
{
|
||||
PrivateIncludePaths.AddRange(new string[] {
|
||||
// ... add other private include paths required here ...
|
||||
Path.Combine(ModuleDirectory, "Private" ),
|
||||
Path.Combine(ModuleDirectory, "../Core/Private"),
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
PrivateIncludePathModuleNames.AddRange(new string[] {
|
||||
"AssetTools",
|
||||
"TargetPlatform",
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
"Projects",
|
||||
"UnrealEd",
|
||||
"AssetTools",
|
||||
"MaterialUtilities",
|
||||
"Renderer",
|
||||
"RenderCore",
|
||||
"PlacementMode",
|
||||
"CinematicCamera",
|
||||
|
||||
"RHI",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"UMG",
|
||||
"ImageWrapper",
|
||||
|
||||
"Media",
|
||||
"MediaAssets",
|
||||
"MediaUtils",
|
||||
|
||||
"AssetTools",
|
||||
"TargetPlatform",
|
||||
"PropertyEditor",
|
||||
"DetailCustomizations",
|
||||
"EditorStyle",
|
||||
|
||||
"NDIIO"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Factories/Factory.h>
|
||||
#include <UObject/Object.h>
|
||||
|
||||
#include "NDIMediaReceiverFactory.generated.h"
|
||||
|
||||
/**
|
||||
Factory Class used to create assets via content browser for NDI Receiver objects
|
||||
*/
|
||||
UCLASS()
|
||||
class NDIIOEDITOR_API UNDIMediaReceiverFactory : public UFactory
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
|
||||
virtual bool ShouldShowInNewMenu() const override { return true; }
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Factories/Factory.h>
|
||||
#include <UObject/Object.h>
|
||||
|
||||
#include "NDIMediaSenderFactory.generated.h"
|
||||
|
||||
/**
|
||||
Factory Class used to create assets via content browser for NDI Sender objects
|
||||
*/
|
||||
UCLASS()
|
||||
class NDIIOEDITOR_API UNDIMediaSenderFactory : public UFactory
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
};
|
||||