dragon-iso/src/TeamsISO.Engine.NdiInterop/NdiNative.cs
Zac Gaetano d14a33a0a3 fix(ndi): resolve Processing.NDI.Lib.x64 via NDI_RUNTIME_DIR_V6 env var
The NDI 6 installer sets NDI_RUNTIME_DIR_V6 (and V5/V4 for back-compat) but does not always add the runtime directory to PATH, so a default DllImport(Processing.NDI.Lib.x64) failed with 0x8007007E (DllNotFoundException) on otherwise correctly installed machines, killing both TeamsISO.exe and TeamsISO.Console at preflight.

Add NdiNativeLibraryResolver, registered from NdiNative's static ctor, that resolves the DLL by trying NDI_RUNTIME_DIR_V6 / V5 / V4 in order, NativeLibrary.Load-ing the file from disk before the runtime falls back to its default search algorithm. Static-ctor registration (rather than [ModuleInitializer]) sidesteps CA2255 under TreatWarningsAsErrors and still guarantees the resolver is in place before the first P/Invoke fires.
2026-05-07 15:14:54 -04:00

171 lines
6.1 KiB
C#

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>. Resolution of
/// the DLL is handled by <see cref="NdiNativeLibraryResolver"/>, which loads it from
/// the NDI Runtime installation directory exposed by the
/// <c>NDI_RUNTIME_DIR_V&lt;n&gt;</c> environment variables — the NDI installer sets
/// these but does not always add the runtime directory to <c>PATH</c>, so a default
/// loader-based resolution would fail with <c>0x8007007E</c> on otherwise correctly
/// installed machines.
/// </summary>
internal static class NdiNative
{
private const string LibName = "Processing.NDI.Lib.x64";
/// <summary>
/// Registers <see cref="NdiNativeLibraryResolver"/> with the runtime before any
/// P/Invoke against <see cref="LibName"/> fires. The static constructor is
/// guaranteed to run before the first access to any member of this type, which
/// includes the very first <c>[DllImport]</c> call site, so the resolver is
/// always in place when the loader needs it.
/// </summary>
static NdiNative()
{
NdiNativeLibraryResolver.Register();
}
// ---- 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 RecvCreateV3Settings 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 SendCreateSettings 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 RecvCreateV3Settings
{
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 SendCreateSettings
{
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;
}
}