feat(interop): add NDI 6 native bindings, handle types, and version constants
Some checks failed
CI / build-and-test (push) Has been cancelled
Some checks failed
CI / build-and-test (push) Has been cancelled
This commit is contained in:
parent
60b12eb637
commit
6f09ca35ef
3 changed files with 235 additions and 0 deletions
154
src/TeamsISO.Engine.NdiInterop/NdiNative.cs
Normal file
154
src/TeamsISO.Engine.NdiInterop/NdiNative.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace TeamsISO.Engine.NdiInterop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// P/Invoke declarations for the NewTek/Vizrt NDI SDK 6 native library.
|
||||||
|
/// On Windows the import target is <c>Processing.NDI.Lib.x64.dll</c>; the loader resolves it
|
||||||
|
/// from the NDI Runtime installation path or from the application directory.
|
||||||
|
/// </summary>
|
||||||
|
internal static class NdiNative
|
||||||
|
{
|
||||||
|
private const string LibName = "Processing.NDI.Lib.x64";
|
||||||
|
|
||||||
|
// ---- Lifecycle ----
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_initialize", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
[return: MarshalAs(UnmanagedType.U1)]
|
||||||
|
public static extern bool Initialize();
|
||||||
|
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_destroy", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void Destroy();
|
||||||
|
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_version", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern IntPtr Version();
|
||||||
|
|
||||||
|
// ---- Find ----
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_find_create_v2", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern IntPtr FindCreateV2(IntPtr p_create_settings);
|
||||||
|
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_find_destroy", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void FindDestroy(IntPtr p_instance);
|
||||||
|
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_find_get_current_sources", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern IntPtr FindGetCurrentSources(IntPtr p_instance, out uint p_no_sources);
|
||||||
|
|
||||||
|
// ---- Receive ----
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_recv_create_v3", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern IntPtr RecvCreateV3(ref RecvCreateV3 p_create_settings);
|
||||||
|
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_recv_destroy", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void RecvDestroy(IntPtr p_instance);
|
||||||
|
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_recv_capture_v3", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern FrameType RecvCaptureV3(
|
||||||
|
IntPtr p_instance,
|
||||||
|
out VideoFrameV2 p_video_data,
|
||||||
|
IntPtr p_audio_data,
|
||||||
|
IntPtr p_metadata,
|
||||||
|
uint timeout_in_ms);
|
||||||
|
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_recv_free_video_v2", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void RecvFreeVideoV2(IntPtr p_instance, ref VideoFrameV2 p_video_data);
|
||||||
|
|
||||||
|
// ---- Send ----
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_send_create", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern IntPtr SendCreate(ref SendCreate p_create_settings);
|
||||||
|
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_send_destroy", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void SendDestroy(IntPtr p_instance);
|
||||||
|
|
||||||
|
[DllImport(LibName, EntryPoint = "NDIlib_send_send_video_v2", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void SendSendVideoV2(IntPtr p_instance, ref VideoFrameV2 p_video_data);
|
||||||
|
|
||||||
|
// ---- Enums ----
|
||||||
|
|
||||||
|
public enum FrameType
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Video = 1,
|
||||||
|
Audio = 2,
|
||||||
|
Metadata = 3,
|
||||||
|
Error = 4,
|
||||||
|
StatusChange = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RecvBandwidth
|
||||||
|
{
|
||||||
|
MetadataOnly = -10,
|
||||||
|
AudioOnly = 10,
|
||||||
|
Lowest = 0,
|
||||||
|
Highest = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RecvColorFormat
|
||||||
|
{
|
||||||
|
BgrxBgra = 0,
|
||||||
|
UyvyBgra = 1,
|
||||||
|
Rgbx_Rgba = 2,
|
||||||
|
UyvyRgba = 3,
|
||||||
|
Fastest = 100,
|
||||||
|
Best = 101
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FrameFormatType
|
||||||
|
{
|
||||||
|
Progressive = 1,
|
||||||
|
Interleaved = 0,
|
||||||
|
FieldZero = 2,
|
||||||
|
FieldOne = 3,
|
||||||
|
Max = 0x7FFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FourCC
|
||||||
|
{
|
||||||
|
public const uint UYVY = 0x59565955; // 'U','Y','V','Y'
|
||||||
|
public const uint BGRA = 0x41524742; // 'B','G','R','A'
|
||||||
|
public const uint BGRX = 0x58524742; // 'B','G','R','X'
|
||||||
|
public const uint RGBA = 0x41424752; // 'R','G','B','A'
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Structs ----
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct Source
|
||||||
|
{
|
||||||
|
public IntPtr p_ndi_name; // const char*
|
||||||
|
public IntPtr p_url_address; // const char* (union, treat as URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct RecvCreateV3
|
||||||
|
{
|
||||||
|
public Source source_to_connect_to;
|
||||||
|
public RecvColorFormat color_format;
|
||||||
|
public RecvBandwidth bandwidth;
|
||||||
|
[MarshalAs(UnmanagedType.U1)] public bool allow_video_fields;
|
||||||
|
public IntPtr p_ndi_recv_name; // const char*
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct SendCreate
|
||||||
|
{
|
||||||
|
public IntPtr p_ndi_name; // const char*
|
||||||
|
public IntPtr p_groups; // const char*
|
||||||
|
[MarshalAs(UnmanagedType.U1)] public bool clock_video;
|
||||||
|
[MarshalAs(UnmanagedType.U1)] public bool clock_audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct VideoFrameV2
|
||||||
|
{
|
||||||
|
public int xres;
|
||||||
|
public int yres;
|
||||||
|
public uint FourCC;
|
||||||
|
public int frame_rate_N;
|
||||||
|
public int frame_rate_D;
|
||||||
|
public float picture_aspect_ratio;
|
||||||
|
public FrameFormatType frame_format_type;
|
||||||
|
public long timecode;
|
||||||
|
public IntPtr p_data;
|
||||||
|
public int line_stride_in_bytes; // (union with data_size_in_bytes)
|
||||||
|
public IntPtr p_metadata;
|
||||||
|
public long timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/TeamsISO.Engine.NdiInterop/NdiPInvokeHandles.cs
Normal file
61
src/TeamsISO.Engine.NdiInterop/NdiPInvokeHandles.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
using TeamsISO.Engine.Interop;
|
||||||
|
|
||||||
|
namespace TeamsISO.Engine.NdiInterop;
|
||||||
|
|
||||||
|
internal sealed class NdiPInvokeFindHandle : NdiFindHandle
|
||||||
|
{
|
||||||
|
public IntPtr Native { get; private set; }
|
||||||
|
|
||||||
|
public NdiPInvokeFindHandle(IntPtr native) => Native = native;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (Native != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
NdiNative.FindDestroy(Native);
|
||||||
|
Native = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class NdiPInvokeReceiverHandle : NdiReceiverHandle
|
||||||
|
{
|
||||||
|
public IntPtr Native { get; private set; }
|
||||||
|
public string SourceName { get; }
|
||||||
|
|
||||||
|
public NdiPInvokeReceiverHandle(IntPtr native, string sourceName)
|
||||||
|
{
|
||||||
|
Native = native;
|
||||||
|
SourceName = sourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (Native != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
NdiNative.RecvDestroy(Native);
|
||||||
|
Native = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class NdiPInvokeSenderHandle : NdiSenderHandle
|
||||||
|
{
|
||||||
|
public IntPtr Native { get; private set; }
|
||||||
|
public string OutputName { get; }
|
||||||
|
|
||||||
|
public NdiPInvokeSenderHandle(IntPtr native, string outputName)
|
||||||
|
{
|
||||||
|
Native = native;
|
||||||
|
OutputName = outputName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (Native != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
NdiNative.SendDestroy(Native);
|
||||||
|
Native = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/TeamsISO.Engine.NdiInterop/NdiVersion.cs
Normal file
20
src/TeamsISO.Engine.NdiInterop/NdiVersion.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
namespace TeamsISO.Engine.NdiInterop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constants describing the NDI SDK version this build was compiled against.
|
||||||
|
/// The runtime version reported by <see cref="NdiNative.Version"/> is compared against
|
||||||
|
/// <see cref="ExpectedRuntimeVersionPrefix"/> by the engine's runtime probe to detect
|
||||||
|
/// installations that pre-date or post-date the SDK headers (per spec §6).
|
||||||
|
/// </summary>
|
||||||
|
public static class NdiVersion
|
||||||
|
{
|
||||||
|
/// <summary>The SDK family this build targets (NDI 6).</summary>
|
||||||
|
public const string SdkFamily = "NDI 6";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prefix of the runtime version string we expect (NDI runtime reports e.g.
|
||||||
|
/// "NDI SDK for Windows v6.0.1.0"). Any major change of the leading "v6" is treated
|
||||||
|
/// as a mismatch.
|
||||||
|
/// </summary>
|
||||||
|
public const string ExpectedRuntimeVersionPrefix = "NDI SDK for Windows v6";
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue