feat(interop): add NDI 6 native bindings, handle types, and version constants
Some checks failed
CI / build-and-test (push) Has been cancelled

This commit is contained in:
Zac Gaetano 2026-05-07 15:34:54 +00:00
parent 60b12eb637
commit 6f09ca35ef
3 changed files with 235 additions and 0 deletions

View 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;
}
}

View 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;
}
}
}

View 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";
}