Skip to main content

grafton_ndi/
lib.rs

1//! High-performance Rust bindings for the NDIĀ® 6 SDK (Network Device Interface).
2//!
3//! This crate provides safe, idiomatic Rust bindings for the NDI SDK, enabling
4//! real-time, low-latency video/audio streaming over IP networks. NDI is widely
5//! used in broadcast, live production, and video conferencing applications.
6//!
7//! # Quick Start
8//!
9//! ```no_run
10//! use grafton_ndi::{NDI, FinderOptions, Finder};
11//! use std::time::Duration;
12//!
13//! # fn main() -> Result<(), grafton_ndi::Error> {
14//! // Initialize the NDI runtime
15//! let ndi = NDI::new()?;
16//!
17//! // Find sources on the network
18//! let options = FinderOptions::builder().show_local_sources(true).build();
19//! let finder = Finder::new(&ndi, &options)?;
20//!
21//! // Discover sources
22//! let sources = finder.find_sources(Duration::from_secs(5))?;
23//!
24//! for source in sources {
25//!     println!("Found: {}", source);
26//! }
27//! # Ok(())
28//! # }
29//! ```
30//!
31//! # Core Concepts
32//!
33//! ## Runtime Management
34//!
35//! The [`NDI`] struct manages the NDI runtime lifecycle. It must be created before
36//! any other NDI operations and should be kept alive for the duration of your
37//! application's NDI usage.
38//!
39//! ## Source Discovery
40//!
41//! Use [`Finder`] to discover NDI sources on the network. Sources can be filtered
42//! by groups and additional IP addresses can be specified for discovery.
43//!
44//! ## Receiving
45//!
46//! The [`Receiver`] type handles receiving video, audio, and metadata from NDI
47//! sources. It supports various color formats and bandwidth modes.
48//!
49//! ## Sending
50//!
51//! Use [`Sender`] to transmit video, audio, and metadata as an NDI source.
52//! Senders can be configured with clock settings and group assignments.
53//!
54//! # Thread Safety
55//!
56//! All primary types ([`Finder`], [`Receiver`], [`Sender`]) implement `Send + Sync`
57//! as the underlying NDI SDK is thread-safe. However, for optimal performance,
58//! minimize cross-thread operations and maintain thread affinity where possible.
59//!
60//! ## Zero-Copy Async Sending
61//!
62//! The library provides zero-copy async video sending using `NDIlib_send_send_video_async_v2`.
63//! The completion callback notifies when the buffer can be reused:
64//!
65//! ```no_run
66//! # use grafton_ndi::{NDI, SenderOptions, BorrowedVideoFrame, PixelFormat};
67//! # fn main() -> Result<(), grafton_ndi::Error> {
68//! # let ndi = NDI::new()?;
69//! # let mut sender = grafton_ndi::Sender::new(&ndi, &SenderOptions::builder("Test").build())?;
70//! // Register callback to know when buffer is released
71//! sender.on_async_video_done(|len| println!("Buffer released: {} bytes", len));
72//!
73//! let buffer = vec![0u8; 1920 * 1080 * 4];
74//! let frame = BorrowedVideoFrame::try_from_uncompressed(&buffer, 1920, 1080, PixelFormat::BGRA, 30, 1)?;
75//! let token = sender.send_video_async(&frame);
76//! // Buffer is now owned by NDI - cannot be modified until callback fires
77//! // The AsyncVideoToken must be kept alive to track the operation
78//! # Ok(())
79//! # }
80//! ```
81//!
82//! Note: Only video supports async sending in the NDI SDK. Audio and metadata are always synchronous.
83//!
84//! # Performance
85//!
86//! - **Zero-copy**: Frame data directly references NDI's buffers when possible
87//! - **Lock-free async**: Atomic operations for minimal overhead in hot paths
88//! - **Bandwidth control**: Multiple quality levels for different use cases
89//! - **Hardware acceleration**: Automatically uses GPU acceleration when available
90//!
91//! # Platform Support
92//!
93//! Windows, Linux, and macOS are all exercised in CI (build, test, lint, and
94//! semver checks) against the NDI SDK.
95//!
96//! - **Windows**: Links the NDI SDK import library at build time; requires the
97//!   NDI runtime DLLs at runtime.
98//! - **Linux**: Supports both standard and Advanced SDK install directories;
99//!   the runtime libraries must be discoverable by the dynamic linker.
100//! - **macOS**: Supports current NDI SDK package layouts used by the CI setup
101//!   action and common local install paths.
102
103#![allow(non_upper_case_globals)]
104#![allow(non_camel_case_types)]
105#![allow(clippy::wildcard_imports)]
106#![allow(clippy::must_use_candidate)]
107#![allow(clippy::missing_errors_doc)]
108
109mod capture;
110mod error;
111mod ndi_lib;
112mod recv_guard;
113
114pub mod framesync;
115
116#[cfg(feature = "advanced_sdk")]
117pub mod waitable_completion;
118
119pub mod finder;
120pub mod frames;
121pub mod receiver;
122pub mod runtime;
123pub mod sender;
124
125#[cfg(any(feature = "tokio", feature = "async-std"))]
126mod async_runtime;
127
128#[cfg(feature = "tokio")]
129pub use async_runtime::tokio;
130
131#[cfg(feature = "async-std")]
132pub use async_runtime::async_std;
133
134pub use {
135    capture::{
136        AudioKind, CaptureKind, FrameFree, FrameSyncAudioFree, FrameSyncVideoFree, MetadataKind,
137        VideoKind,
138    },
139    error::*,
140    finder::{Finder, FinderOptions, FinderOptionsBuilder, Source, SourceAddress, SourceCache},
141    frames::{
142        AudioFormat, AudioFrame, AudioFrameBuilder, AudioFrameRef, AudioLayout, AudioRef,
143        FormatCategory, LineStrideOrSize, MetadataFrame, MetadataFrameRef, PixelFormat,
144        PixelFormatInfo, ScanType, VideoFrame, VideoFrameBuilder, VideoFrameRef, VideoRef,
145    },
146    framesync::{FrameSync, FrameSyncAudioRef, FrameSyncAudioRequest, FrameSyncVideoRef},
147    receiver::{
148        Capture, ConnectionStats, Receiver, ReceiverBandwidth, ReceiverColorFormat,
149        ReceiverOptions, ReceiverOptionsBuilder, ReceiverStatus, Tally,
150    },
151    runtime::NDI,
152    sender::{AsyncVideoToken, BorrowedVideoFrame, Sender, SenderOptions, SenderOptionsBuilder},
153};
154
155#[cfg(feature = "image-encoding")]
156pub use frames::ImageFormat;
157
158/// Alias for Result with our Error type
159pub type Result<T> = std::result::Result<T, crate::error::Error>;
160
161/// Maximum timeout duration supported by the NDI SDK (~49.7 days).
162///
163/// The NDI SDK uses `u32` milliseconds for timeout values, which limits the maximum
164/// timeout to approximately 49.7 days. Attempting to use a larger `Duration` will
165/// result in an error.
166pub const MAX_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(u32::MAX as u64);
167
168/// Converts a `Duration` to milliseconds for FFI, checking for overflow.
169///
170/// # Errors
171///
172/// Returns [`Error::InvalidConfiguration`] if the duration exceeds [`MAX_TIMEOUT`].
173///
174/// # Examples
175///
176/// ```ignore
177/// use std::time::Duration;
178/// use grafton_ndi::to_ms_checked;
179///
180/// // Valid timeout
181/// let ms = to_ms_checked(Duration::from_secs(5))?;
182/// assert_eq!(ms, 5000);
183///
184/// // Overflow error
185/// let result = to_ms_checked(Duration::from_secs(u64::MAX));
186/// assert!(result.is_err());
187/// ```
188pub(crate) fn to_ms_checked(d: std::time::Duration) -> Result<u32> {
189    if d > MAX_TIMEOUT {
190        Err(Error::InvalidConfiguration(format!(
191            "timeout {:?} exceeds MAX_TIMEOUT (~49.7 days)",
192            d
193        )))
194    } else {
195        Ok(d.as_millis() as u32)
196    }
197}
198
199#[cfg(test)]
200#[path = "tests.rs"]
201mod tests;