| Crates.io | grafton-ndi |
| lib.rs | grafton-ndi |
| version | 0.8.1 |
| created_at | 2024-07-18 19:59:04.211066+00 |
| updated_at | 2025-06-02 12:35:54.253258+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 | 250,871 |
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.
use grafton_ndi::{NDI, FinderOptions, Finder};
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)?;
// Wait for sources
finder.wait_for_sources(5000);
let sources = finder.get_sources(5000)?;
for source in sources {
println!("Found source: {}", source);
}
Ok(())
}
Add to your Cargo.toml:
[dependencies]
grafton-ndi = "0.8"
# For NDI Advanced SDK features (optional)
# grafton-ndi = { version = "0.8", features = ["advanced_sdk"] }
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 PATHuse grafton_ndi::{NDI, FinderOptions, Finder};
let ndi = NDI::new()?;
// Configure the finder
let finder_options = FinderOptions::builder()
.show_local_sources(false)
.groups("Public")
.extra_ips("192.168.1.100")
.build();
let finder = Finder::new(&ndi, &finder_options)?;
// Discover sources
if finder.wait_for_sources(5000) {
let sources = finder.get_sources(0)?;
for source in &sources {
println!("Found: {} at {}", source.name, source.address);
}
}
use grafton_ndi::{NDI, ReceiverOptions, Receiver, ReceiverColorFormat, ReceiverBandwidth, FrameType, Finder};
let ndi = NDI::new()?;
// First, find a source
let finder = Finder::new(&ndi, &Default::default())?;
finder.wait_for_sources(5000);
let sources = finder.get_sources(0)?;
let source = sources.first().ok_or("No sources found")?;
// Create receiver
let receiver = ReceiverOptions::builder(source.clone())
.color(ReceiverColorFormat::RGBX_RGBA)
.bandwidth(ReceiverBandwidth::Highest)
.name("My Receiver")
.build(&ndi)?;
// Capture frames
match receiver.capture(5000)? {
FrameType::Video(video) => {
println!("Video: {}x{} @ {}/{} fps",
video.width, video.height,
video.frame_rate_n, video.frame_rate_d
);
// Process video data...
}
FrameType::Audio(audio) => {
println!("Audio: {} channels @ {} Hz",
audio.num_channels, audio.sample_rate
);
// Access audio samples as f32
let samples: &[f32] = audio.data();
println!("First sample: {:.3}", samples[0]);
}
_ => {}
}
use grafton_ndi::{NDI, Sender, SenderOptions, VideoFrame, FourCCVideoType};
let ndi = NDI::new()?;
// Configure sender
let options = SenderOptions::builder("My NDI Source")
.groups("Public")
.clock_video(true)
.clock_audio(false)
.build()?;
let sender = Sender::new(&ndi, &options)?;
// Create frame using builder
let frame = VideoFrame::builder()
.resolution(1920, 1080)
.fourcc(FourCCVideoType::BGRA)
.frame_rate(60, 1)
.aspect_ratio(16.0 / 9.0)
.build()?;
// Frame is created with zero-initialized data
// You can access the data to fill it:
// let data = frame.data_mut();
// ... fill data with your video content ...
sender.send_video(&frame);
use grafton_ndi::{NDI, Sender, SenderOptions, BorrowedVideoFrame, FourCCVideoType};
use std::sync::Arc;
let ndi = NDI::new()?;
let sender = Sender::new(&ndi, &SenderOptions::builder("Async Source").build()?)?;
// Register completion callback
let completed = Arc::new(std::sync::atomic::AtomicU32::new(0));
let completed_clone = completed.clone();
sender.on_async_video_done(move |frame_id| {
completed_clone.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
println!("Frame {} can be reused", frame_id);
});
// Send frame asynchronously
let buffer = vec![0u8; 1920 * 1080 * 4];
let frame = BorrowedVideoFrame::from_buffer(&buffer, 1920, 1080, FourCCVideoType::BGRA, 30, 1);
// Token automatically manages frame lifetime
let token = sender.send_video_async(&frame);
// Buffer can be safely reused when token is dropped or completion callback fires
drop(token);
// Flush all pending frames with timeout
sender.flush_async(std::time::Duration::from_secs(5))?;
use grafton_ndi::{NDI, ReceiverOptions, ReceiverBandwidth};
// Assuming you already have a source from discovery
let receiver = ReceiverOptions::builder(source)
.bandwidth(ReceiverBandwidth::AudioOnly)
.build(&ndi)?;
// Capture audio frame
if let Some(audio) = receiver.capture_audio(5000)? {
// Audio samples are 32-bit floats
let samples: &[f32] = audio.data();
// Calculate RMS level
let rms = (samples.iter()
.map(|&x| x * x)
.sum::<f32>() / samples.len() as f32)
.sqrt();
// Access individual channels (stereo example)
if let Some(left) = audio.channel_data(0) {
println!("Left channel: {} samples", left.len());
}
if let Some(right) = audio.channel_data(1) {
println!("Right channel: {} samples", right.len());
}
}
use grafton_ndi::{NDI, ReceiverOptions};
// Assuming you already have a source from discovery
let receiver = ReceiverOptions::builder(source).build(&ndi)?;
// Check PTZ support
if receiver.ptz_is_supported()? {
// Control camera
receiver.ptz_zoom(0.5)?; // Zoom to 50%
receiver.ptz_pan_tilt(0.0, 0.25)?; // Pan center, tilt up 25%
receiver.ptz_auto_focus()?; // Enable auto-focus
}
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.
// Assuming source is from Finder::get_sources()
let receiver = ReceiverOptions::builder(source)
.color(ReceiverColorFormat::UYVY_BGRA)
.bandwidth(ReceiverBandwidth::Highest)
.build(&ndi)?;
Sender - Video/Audio TransmissionSends video, audio, and metadata as an NDI source.
let sender = Sender::new(&ndi, &SenderOptions::builder("Source Name")
.clock_video(true)
.build()?)?);
VideoFrame - Video frame data with resolution, format, and timingAudioFrame - 32-bit float audio samples with channel configurationMetadataFrame - XML metadata for tally, PTZ, and custom dataAll 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, RecvStatus};
// Assuming you already have a source from discovery
let receiver = ReceiverOptions::builder(source).build(&ndi)?;
// Get current connection status
let status: RecvStatus = receiver.get_status();
println!("Connected: {}", status.is_connected);
println!("Video frames: {}", status.video_frames);
println!("Audio frames: {}", status.audio_frames);
// Monitor receiver performance
if status.total_frames > 0 {
let drop_rate = status.dropped_frames as f32 / status.total_frames as f32;
if drop_rate > 0.01 {
eprintln!("High drop rate: {:.1}%", drop_rate * 100.0);
}
}
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.
For upgrading from previous versions: