| Crates.io | grafton-ndi |
| lib.rs | grafton-ndi |
| version | 0.9.0 |
| created_at | 2024-07-18 19:59:04.211066+00 |
| updated_at | 2025-10-20 05:40:48.161592+00 |
| description | High-performance Rust bindings for the NDI® 6 SDK - real-time IP video streaming |
| homepage | https://www.grafton.ai |
| repository | https://github.com/GrantSparks/grafton-ndi |
| max_upload_size | |
| id | 1307965 |
| size | 502,324 |
High-performance, idiomatic Rust bindings for the NDI® 6 SDK, enabling real-time, low-latency IP video streaming. Built for production use with zero-copy performance and comprehensive async support.
SourceCache eliminates ~150 lines of boilerplate#[non_exhaustive] enumsuse grafton_ndi::{NDI, FinderOptions, Finder};
use std::time::Duration;
fn main() -> Result<(), grafton_ndi::Error> {
// Initialize NDI
let ndi = NDI::new()?;
// Find sources on the network
let finder_options = FinderOptions::builder().show_local_sources(true).build();
let finder = Finder::new(&ndi, &finder_options)?;
// Discover sources
let sources = finder.find_sources(Duration::from_secs(5))?;
for source in sources {
println!("Found source: {}", source);
}
Ok(())
}
Add to your Cargo.toml:
[dependencies]
grafton-ndi = "0.9"
# For NDI Advanced SDK features (optional)
# grafton-ndi = { version = "0.9", features = ["advanced_sdk"] }
# For image encoding support (PNG/JPEG)
# grafton-ndi = { version = "0.9", features = ["image-encoding"] }
# For async runtime integration
# grafton-ndi = { version = "0.9", features = ["tokio"] }
# grafton-ndi = { version = "0.9", features = ["async-std"] }
NDI SDK: Download and install the NDI SDK for your platform.
C:\Program Files\NDI\NDI 6 SDK by default/usr/share/NDI SDK for Linux or set NDI_SDK_DIR/Library/NDI SDK for Apple by defaultRust: Requires Rust 1.75 or later
Build Dependencies:
Runtime: NDI runtime libraries must be available:
%NDI_SDK_DIR%\Bin\x64 is in your PATHFor complete API documentation and detailed examples:
NDI - Runtime ManagementThe main entry point that manages NDI library initialization and lifecycle.
let ndi = NDI::new()?; // Reference-counted, thread-safe
Finder - Source DiscoveryDiscovers NDI sources on the network.
let finder_options = FinderOptions::builder()
.show_local_sources(true)
.groups("Public,Private")
.build();
let finder = Finder::new(&ndi, &finder_options)?;
Receiver - Video/Audio ReceptionReceives video, audio, and metadata from NDI sources.
use std::time::Duration;
// Assuming source is from finder.find_sources() or finder.sources()
let options = ReceiverOptions::builder(source)
.color(ReceiverColorFormat::RGBX_RGBA)
.bandwidth(ReceiverBandwidth::Highest)
.build(); // Infallible
let receiver = grafton_ndi::Receiver::new(&ndi, &options)?;
// Capture a video frame (blocks until success or timeout)
let frame = receiver.capture_video(Duration::from_secs(5))?;
// Or use zero-copy for maximum performance
let frame_ref = receiver.capture_video_ref(Duration::from_secs(5))?;
let data = frame_ref.data(); // Direct reference, no copy!
Sender - Video/Audio TransmissionSends video, audio, and metadata as an NDI source.
let options = SenderOptions::builder("Source Name")
.clock_video(true)
.build(); // Infallible
let mut sender = grafton_ndi::Sender::new(&ndi, &options)?; // Must be mut for async send
// Synchronous send
sender.send_video(&video_frame);
// Or async zero-copy send
let token = sender.send_video_async(&borrowed_frame);
Owned Frames:
VideoFrame - Owned video frame data with resolution, pixel format, and timingAudioFrame - Owned 32-bit float audio samples with channel configurationMetadataFrame - Owned XML metadata for tally, PTZ, and custom dataBorrowed Frames (Zero-Copy):
VideoFrameRef<'rx> - Zero-copy video frame reference (eliminates ~475 MB/s memcpy @ 1080p60)AudioFrameRef<'rx> - Zero-copy audio frame referenceMetadataFrameRef<'rx> - Zero-copy metadata frame referenceBorrowedVideoFrame<'buf> - Zero-copy send frame (for async transmission)All primary types (Finder, Receiver, Sender) are Send + Sync as the underlying NDI SDK is thread-safe. You can safely share instances across threads, though performance is best when keeping instances thread-local.
ReceiverBandwidth::Lowest for preview qualityuse grafton_ndi::{NDI, ReceiverOptions, Receiver};
use std::time::Duration;
// Assuming you already have a source from discovery
let options = ReceiverOptions::builder(source).build();
let receiver = Receiver::new(&ndi, &options)?;
// Check connection status
if receiver.is_connected() {
// Get performance statistics
let stats = receiver.connection_stats();
println!("Connections: {}", stats.connections);
println!("Video frames received: {}", stats.video_frames_received);
println!("Video frames dropped: {}", stats.video_frames_dropped);
// Monitor receiver performance using built-in helper
let drop_rate = stats.video_drop_percentage();
if drop_rate > 1.0 {
eprintln!("High drop rate: {:.1}%", drop_rate);
}
}
// Poll for status changes (tally, connections, etc.)
if let Some(status) = receiver.poll_status_change(Duration::from_millis(100))? {
if let Some(connections) = status.connections {
println!("Connection count changed: {}", connections);
}
if let Some(tally) = status.tally {
println!("Tally: program={}, preview={}", tally.on_program, tally.on_preview);
}
}
See the examples/ directory for complete applications:
NDIlib_Find.rs - Discover NDI sources on the networkstatus_monitor.rs - Monitor receiver status and performanceNDIlib_Recv_Audio.rs - Receive and process audio streamsNDIlib_Recv_Audio_16bpp.rs - Receive 16-bit audio samplesNDIlib_Recv_PNG.rs - Receive video and save as PNG imagesNDIlib_Recv_PTZ.rs - Control PTZ camerasconcurrent_capture.rs - Capture from multiple sources simultaneouslyNDIlib_Send_Audio.rs - Send audio streamsNDIlib_Send_Video.rs - Send video streamsasync_send.rs - Async video sending with completion callbackszero_copy_send.rs - Zero-copy video transmissionRun examples with:
cargo run --example NDIlib_Find
| Platform | Status | Notes |
|---|---|---|
| Windows | ✅ Fully supported | Tested on Windows 10/11 |
| Linux | ✅ Fully supported | Tested on Ubuntu 20.04+ |
| macOS | ⚠️ Experimental | Limited testing |
Contributions are welcome! Please see our Contributing Guidelines.
Licensed under the Apache License, Version 2.0. See LICENSE for details.
This is an unofficial community project and is not affiliated with NewTek or Vizrt.
NDI® is a registered trademark of Vizrt NDI AB.
Version 0.9.0 is a major milestone toward 1.0, with comprehensive API stabilization and significant improvements:
std::time::Duration instead of u32 millisecondsReceiver and Sender have symmetric, infallible builderscapture_* and capture_*_timeout)FourCCVideoType → PixelFormat, FrameFormatType → ScanType, AudioType → AudioFormat#[non_exhaustive] for future SDK versionsget_ prefixes per Rust API guidelinesVideoFrameRef, AudioFrameRef, MetadataFrameRef typesReceiver lifetime, preventing use-after-free at compile-timesend_video_asyncLineStrideOrSize enumSourceCache eliminates ~150 lines of boilerplateimage-encoding feature)AudioLayout supportThis release contains extensive breaking changes necessary for API stabilization. See CHANGELOG.md for the comprehensive migration guide with before/after examples.
Quick migration:
// 0.8.1
finder.wait_for_sources(5000);
let sources = finder.get_sources(0)?;
let frame = receiver.capture_video_blocking(5000)?;
// 0.9.0
use std::time::Duration;
finder.wait_for_sources(Duration::from_secs(5))?;
let sources = finder.sources(Duration::ZERO)?;
let frame = receiver.capture_video(Duration::from_secs(5))?;
See CHANGELOG.md for complete details and migration guide.