Skip to main content

ndi_sdk/
lib.rs

1mod error;
2mod find;
3mod routing;
4mod source;
5pub mod sys;
6
7pub use error::{NDIError, NDIResult};
8pub use find::*;
9pub use routing::*;
10pub use source::*;
11
12use libloading::{Library, Symbol};
13use std::path::PathBuf;
14use std::sync::LazyLock;
15use std::{env, ffi, fs};
16
17pub(crate) static HANDLE: LazyLock<NDILib> =
18    LazyLock::new(|| initialize().expect("Expected to lazily initialize NDI library handle"));
19
20pub(crate) struct NDILib {
21    pub(crate) lib: *const sys::NDIlib_v5,
22}
23unsafe impl Send for NDILib {}
24unsafe impl Sync for NDILib {}
25
26/// Initialize the NDI Library
27fn initialize() -> NDIResult<NDILib> {
28    let ndi_lib = load_lib()?;
29    let load_fn: Symbol<unsafe extern "C" fn() -> *const sys::NDIlib_v5> =
30        unsafe { ndi_lib.get(b"NDIlib_v5_load\0")? };
31
32    let v5 = unsafe { load_fn() };
33    if v5.is_null() {
34        return Err(NDIError::LoadV5Failed);
35    }
36
37    let Some(init) = (unsafe { (*v5).initialize }) else {
38        return Err(NDIError::MissingSymbolV5("initialize"));
39    };
40
41    if unsafe { init() } {
42        Ok(NDILib { lib: v5 })
43    } else {
44        Err(NDIError::InitializeFailed)
45    }
46}
47
48/// Load the NDI dynamic library.
49fn load_lib() -> NDIResult<Library> {
50    let lib_name = if cfg!(target_os = "macos") {
51        "libndi.dylib"
52    } else if cfg!(unix) {
53        "libndi.so.6"
54    } else if cfg!(all(windows, target_pointer_width = "64")) {
55        "Processing.NDI.Lib.x64.dll"
56    } else if cfg!(all(windows, target_pointer_width = "32")) {
57        "Processing.NDI.Lib.x86.dll"
58    } else {
59        panic!("NDI SDK is only supported on Linux, macOS and Windows x86/x64")
60    };
61
62    let mut search_paths = Vec::new();
63    if let Ok(rtd) = env::var("NDI_RUNTIME_DIR_V6") {
64        let p = PathBuf::from(rtd);
65        if cfg!(windows) {
66            search_paths.push(p.join("lib"));
67        } else {
68            search_paths.push(p);
69        }
70    };
71
72    #[cfg(unix)]
73    search_paths.push(PathBuf::from("/usr/local/lib"));
74    #[cfg(all(unix, not(target_os = "macos")))]
75    search_paths.push(PathBuf::from("/usr/lib"));
76
77    for path in search_paths {
78        let file = path.join(lib_name);
79        if let Ok(true) = fs::exists(&file) {
80            return Ok(unsafe { Library::new(file)? });
81        }
82    }
83
84    // Only the operating system is left to guess.
85    Ok(unsafe { Library::new(lib_name)? })
86}
87
88pub fn version() -> NDIResult<String> {
89    let Some(version_fn) = (unsafe { (*HANDLE.lib).version }) else {
90        return Err(NDIError::MissingSymbolV5("version"));
91    };
92
93    let version_ptr = unsafe { version_fn() };
94    if version_ptr.is_null() {
95        return Err(NDIError::UnexpectedNullPointer("version"));
96    }
97    let c_str = unsafe { ffi::CStr::from_ptr(version_ptr) };
98    Ok(c_str.to_str().map(|s| s.to_owned())?)
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_version_alloc() -> NDIResult<()> {
107        unsafe {
108            let vp_1 = (*HANDLE.lib).version.unwrap()();
109            let vp_2 = (*HANDLE.lib).version.unwrap()();
110            // If those pointers change, it would probably allocate, hence need freeing.
111            assert_eq!(vp_1, vp_2);
112        }
113        println!("Version: {}", version()?);
114        Ok(())
115    }
116}