pub struct Sender { /* private fields */ }Implementations§
Source§impl Sender
impl Sender
Sourcepub fn new(ndi: &NDI, create_settings: &SenderOptions) -> Result<Self>
pub fn new(ndi: &NDI, create_settings: &SenderOptions) -> Result<Self>
Creates a new NDI send instance.
§Errors
Returns an error if:
- The sender name is empty or contains only whitespace
- Both
clock_videoandclock_audioare false (at least one must be true) - The sender name contains a null byte
- The groups string contains a null byte
- NDI fails to create the send instance
Sourcepub fn send_video(&self, video_frame: &VideoFrame)
pub fn send_video(&self, video_frame: &VideoFrame)
Send a video frame synchronously (NDI copies the buffer immediately).
Sourcepub fn send_video_async<'b>(
&'b mut self,
video_frame: &BorrowedVideoFrame<'b>,
) -> AsyncVideoToken<'b, 'b>
pub fn send_video_async<'b>( &'b mut self, video_frame: &BorrowedVideoFrame<'b>, ) -> AsyncVideoToken<'b, 'b>
Send a video frame asynchronously with zero-copy.
Uses NDIlib_send_send_video_async_v2 for zero-copy transmission.
IMPORTANT: This method requires a mutable borrow of the sender, which enforces single-flight semantics at compile time. Only one async send can be in-flight at a time.
Returns an AsyncVideoToken that holds borrows of both the sender and the
frame buffer. The token must be kept alive until the frame has been transmitted.
When the token is dropped, a flush is automatically performed to ensure the
NDI SDK releases the buffer.
§Type Safety
The returned token holds:
- A borrow of the sender (preventing multiple concurrent async sends)
- A borrow of the frame buffer (preventing the buffer from being dropped)
This ensures memory safety at compile time without runtime overhead.
§Example
let ndi = NDI::new()?;
let send_options = SenderOptions::builder("MyCam")
.clock_video(true)
.clock_audio(true)
.build();
let mut sender = grafton_ndi::Sender::new(&ndi, &send_options)?;
// Register callback to know when buffer is released
sender.on_async_video_done(|len| println!("Buffer released: {len} bytes"));
// Use borrowed buffer directly (zero-copy, no allocation)
let mut buffer = vec![0u8; 1920 * 1080 * 4];
let borrowed_frame = BorrowedVideoFrame::try_from_uncompressed(&buffer, 1920, 1080, PixelFormat::BGRA, 30, 1)?;
let token = sender.send_video_async(&borrowed_frame);
// Buffer is owned by SDK until token is dropped
drop(token); // This triggers automatic flush
// Now safe to reuse buffer
Sourcepub fn send_audio(&self, audio_frame: &AudioFrame)
pub fn send_audio(&self, audio_frame: &AudioFrame)
Sends an audio frame synchronously.
This function copies the audio data immediately and returns, making the buffer
available for reuse. The underlying NDI SDK function NDIlib_send_send_audio_v3
performs a synchronous copy of the data.
See the NDI SDK documentation section on NDIlib_send_send_audio_v3 for more details.
§Example
let mut audio_buffer = vec![0.0f32; 48000 * 2]; // 1 second of stereo audio
// Fill buffer with audio data...
let frame = AudioFrame::builder()
.sample_rate(48000)
.channels(2)
.samples(48000)
.data(audio_buffer.clone())
.build()?;
sender.send_audio(&frame);
// Buffer can be reused immediately
audio_buffer.fill(0.5);
let frame2 = AudioFrame::builder()
.sample_rate(48000)
.channels(2)
.samples(48000)
.data(audio_buffer)
.build()?;
sender.send_audio(&frame2);Sourcepub fn send_metadata(&self, metadata_frame: &MetadataFrame) -> Result<()>
pub fn send_metadata(&self, metadata_frame: &MetadataFrame) -> Result<()>
Sends a metadata frame.
§Errors
Returns an error if the metadata contains an interior NUL byte, exceeds the metadata size limit, or cannot fit the SDK length field.
Sourcepub fn tally(&self, timeout: Duration) -> Result<Option<Tally>>
pub fn tally(&self, timeout: Duration) -> Result<Option<Tally>>
Get the current tally state for this sender.
§Arguments
timeout- Maximum time to wait for tally information. Must not exceedcrate::MAX_TIMEOUT(~49.7 days).
§Returns
Ok(Some(tally)) if tally was successfully retrieved, Ok(None) on timeout.
§Errors
Returns Error::InvalidConfiguration if timeout exceeds crate::MAX_TIMEOUT.
§Examples
let ndi = NDI::new()?;
let options = SenderOptions::builder("Test Sender").build();
let sender = grafton_ndi::Sender::new(&ndi, &options)?;
// Try to get tally with 1 second timeout
if let Some(tally) = sender.tally(Duration::from_secs(1))? {
println!("On program: {}, On preview: {}", tally.on_program, tally.on_preview);
} else {
println!("Tally request timed out");
}Sourcepub fn connection_count(&self, timeout: Duration) -> Result<u32>
pub fn connection_count(&self, timeout: Duration) -> Result<u32>
Get the number of active connections to this sender.
§Arguments
timeout- Maximum time to wait for connection count. Must not exceedcrate::MAX_TIMEOUT(~49.7 days).
§Returns
Number of active connections as a u32.
§Errors
Returns Error::Timeout if the SDK returns a negative value (indicating timeout or error).
Returns Error::InvalidConfiguration if timeout exceeds crate::MAX_TIMEOUT.
§Examples
let ndi = NDI::new()?;
let options = SenderOptions::builder("Test Sender").build();
let sender = grafton_ndi::Sender::new(&ndi, &options)?;
// Get connection count with 1 second timeout
let count = sender.connection_count(Duration::from_secs(1))?;
println!("Active connections: {}", count);pub fn clear_connection_metadata(&self)
Sourcepub fn add_connection_metadata(
&self,
metadata_frame: &MetadataFrame,
) -> Result<()>
pub fn add_connection_metadata( &self, metadata_frame: &MetadataFrame, ) -> Result<()>
Adds connection metadata.
§Errors
Returns an error if the metadata contains an interior NUL byte, exceeds the metadata size limit, or cannot fit the SDK length field.
Sourcepub fn set_failover(&self, source: &Source) -> Result<()>
pub fn set_failover(&self, source: &Source) -> Result<()>
Sourcepub fn source(&self) -> Result<Source>
pub fn source(&self) -> Result<Source>
Get the source information for this sender.
§Errors
Returns Error::NullPointer if the NDI SDK returns a null pointer or
if the source data contains null pointers.
§Examples
let ndi = NDI::new()?;
let options = SenderOptions::builder("Test Sender").build();
let sender = grafton_ndi::Sender::new(&ndi, &options)?;
let source = sender.source()?;
println!("Sender source: {source}");Sourcepub fn on_async_video_done<F>(&self, handler: F)
pub fn on_async_video_done<F>(&self, handler: F)
Register a handler that will be called once the SDK has released
the last buffer passed to send_video_async.
The callback receives the buffer length.
Note: Due to the use of OnceLock, this callback can only be set once.
Subsequent calls to this method will be silently ignored.
Sourcepub fn flush_async_blocking(&self)
pub fn flush_async_blocking(&self)
Flush pending async video operations synchronously.
Sends a true NULL video frame pointer to the SDK, blocking until all pending async video operations are complete.
AsyncVideoToken::drop and AsyncVideoToken::wait already perform
this drain for ordinary safe send_video_async calls. Prefer waiting the
token when you hold one; use this method when you need an explicit
sender-level drain.
§Buffer Lifetime
After this function returns, all previously sent async video buffers can be safely reused or freed.
§Example
let ndi = NDI::new()?;
let options = SenderOptions::builder("Test").build();
let mut sender = grafton_ndi::Sender::new(&ndi, &options)?;
let mut buffer = vec![0u8; 1920 * 1080 * 4];
let frame = BorrowedVideoFrame::try_from_uncompressed(&buffer, 1920, 1080, PixelFormat::BGRA, 30, 1)?;
let token = sender.send_video_async(&frame);
// Prefer waiting the token for ordinary borrowed async sends.
token.wait()?;
// Buffer can now be safely reused
buffer.fill(0);Sourcepub fn flush_async(&self, timeout: Duration) -> Result<()>
pub fn flush_async(&self, timeout: Duration) -> Result<()>
Wait for pending async operations with timeout.
With advanced_sdk, this waits up to the specified timeout for the
in-flight frame’s completion callback. Without advanced_sdk, this
calls flush_async_blocking to drain pending operations.
§Returns
Ok(())if the operation completed within the timeoutErr(Error::Timeout)if the timeout elapsed (advanced_sdk only)
§Example
let ndi = NDI::new()?;
let options = SenderOptions::builder("Test").build();
let sender = grafton_ndi::Sender::new(&ndi, &options)?;
// ... send some async frames ...
// Wait with timeout for completion
sender.flush_async(Duration::from_secs(1))?;Trait Implementations§
impl Send for Sender
§Safety
The NDI 6 SDK documentation specifically marks these send functions as thread-safe:
NDIlib_send_send_video_v2andNDIlib_send_send_video_async_v2NDIlib_send_send_audio_v3(no async variant exists)NDIlib_send_send_metadata(no async variant exists)NDIlib_send_get_tallyNDIlib_send_get_no_connections
The Advanced SDK provides NDIlib_send_set_video_async_completion for registering
buffer-release callbacks (not available in the standard SDK).
Inner holds an opaque NDI pointer and owned CStrings that are automatically
freed in Drop, making it safe to move between threads.
Functions like NDIlib_send_create and NDIlib_send_destroy should be called
from a single thread.
impl Sync for Sender
§Safety
The NDI 6 SDK guarantees that multiple threads can safely call send methods concurrently. The SDK uses internal synchronization for:
- Video sending (both sync and async)
- Audio sending (sync only)
- Metadata sending
- Status queries (tally, connections)
Note: Creation and destruction (NDIlib_send_create/NDIlib_send_destroy)
are handled in our Rust wrapper to ensure single-threaded access.