Skip to main content

Sender

Struct Sender 

Source
pub struct Sender { /* private fields */ }

Implementations§

Source§

impl Sender

Source

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_video and clock_audio are 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
Source

pub fn send_video(&self, video_frame: &VideoFrame)

Send a video frame synchronously (NDI copies the buffer immediately).

Source

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
Source

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);
Source

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.

Source

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 exceed crate::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");
}
Source

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 exceed crate::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);
Source

pub fn clear_connection_metadata(&self)

Source

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.

Source

pub fn set_failover(&self, source: &Source) -> Result<()>

Sets failover source.

§Errors

Returns an error if source conversion fails.

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}");
Source

pub fn on_async_video_done<F>(&self, handler: F)
where F: Fn(usize) + Send + Sync + 'static,

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.

Source

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);
Source

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 timeout
  • Err(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§

Source§

impl Debug for Sender

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Send for Sender

§Safety

The NDI 6 SDK documentation specifically marks these send functions as thread-safe:

  • NDIlib_send_send_video_v2 and NDIlib_send_send_video_async_v2
  • NDIlib_send_send_audio_v3 (no async variant exists)
  • NDIlib_send_send_metadata (no async variant exists)
  • NDIlib_send_get_tally
  • NDIlib_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.

Source§

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.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.