Skip to main content

FrameSync

Struct FrameSync 

Source
pub struct FrameSync { /* private fields */ }
Expand description

Frame synchronizer for clock-corrected capture.

Converts push-based NDI streams into pull-based capture with automatic time-base correction and dynamic audio resampling.

§Ownership

FrameSync takes ownership of the Receiver (similar to how BufWriter wraps a Write). Use receiver() to access the underlying receiver for tally, PTZ, or status operations. Use into_receiver() to recover the receiver when done.

§Thread Safety

FrameSync is Send + Sync like Receiver, as the underlying NDI SDK frame-sync functions are thread-safe. Frames returned by capture methods borrow from the FrameSync and are neither Send nor Sync. Convert them to owned frames to transfer ownership to another thread. Immutable video plane/row views can instead be moved or shared with scoped workers while the frame guard stays on its capture thread until those workers finish.

Raw video capture through the underlying receiver is disabled while this FrameSync exists. Self::into_receiver destroys the synchronizer and restores raw video capture before returning the receiver.

§Example

let framesync = FrameSync::new(receiver)?;

// Sample available data without waiting for a new network frame
if let Some(video) = framesync.capture_video(ScanType::Progressive)? {
    println!("Video: {}x{}", video.width(), video.height());
}

// Audio capture fixes the output dimensions before entering the SDK.
let audio = framesync.capture_audio(FrameSyncAudioRequest::capture_with(
    Some(48_000), 2, 1_024,
)?)?;
println!("Audio: {} samples", audio.num_samples());

Implementations§

Source§

impl FrameSync

Source

pub fn new(receiver: Receiver) -> Result<Self>

Create a frame synchronizer from a receiver.

Takes ownership of the receiver. Use receiver() to access the underlying receiver for tally, PTZ, or status operations. Use into_receiver() to recover the receiver.

§Errors

Returns an error if the NDI SDK fails to create the frame-sync instance.

§Example
let receiver = Receiver::new(&ndi, &options)?;
let framesync = FrameSync::new(receiver)?;
Source

pub fn receiver(&self) -> &Receiver<FrameSyncAttached>

Access the underlying receiver.

Use this to access receiver functionality like tally, PTZ, or status queries while the FrameSync is active. Tally control publishes state upstream; it does not query incoming tally. This receiver mode has no raw-video method; use FrameSync capture methods or recover the receiver with Self::into_receiver first.

Source

pub fn into_receiver(self) -> Receiver

Consume the FrameSync and recover the underlying Receiver.

This destroys the frame synchronizer and returns the receiver for continued use with raw capture or for creating a new FrameSync.

Source

pub fn capture_video( &self, field_type: ScanType, ) -> Result<Option<FrameSyncVideoRef<'_>>>

Capture video with time-base correction.

This does not wait for a new network frame. It returns the best frame for the current output timing, handling clock drift and jitter automatically. The same frame may be returned multiple times when capture rate exceeds source frame rate. Wrapper synchronization and SDK processing still take time.

§Arguments
  • field_type - The desired field format. Use ScanType::Progressive for most applications. For interlaced output, use the appropriate field type to maintain correct field ordering.
§Returns
  • Ok(Some(FrameSyncVideoRef)) - A validated zero-copy reference to the captured frame
  • Ok(None) - The SDK returned the documented all-zero “no video yet” state
  • Err(_) - The SDK returned an unsupported format or malformed frame descriptor, including invalid dimensions, stride, buffers, or metadata
§Example
let framesync = FrameSync::new(receiver)?;

// Capture loop - call at your output frame rate
loop {
    if let Some(frame) = framesync.capture_video(ScanType::Progressive)? {
        // Process/display frame
        println!("{}x{} @ {}/{} fps",
            frame.width(), frame.height(),
            frame.frame_rate_n(), frame.frame_rate_d());
    } else {
        // No video received yet - display placeholder
        println!("Waiting for video...");
    }
}
Source

pub fn capture_video_owned( &self, field_type: ScanType, ) -> Result<Option<VideoFrame>>

Capture video and convert to an owned frame.

This is a convenience method that captures video and immediately converts it to an owned VideoFrame that can be sent across threads.

§Arguments
  • field_type - The desired field format.
§Returns
  • Ok(Some(VideoFrame)) - An owned copy of the captured frame
  • Ok(None) - No video has been received yet
§Errors

Propagates format and frame-validation errors from Self::capture_video or conversion to an owned frame.

Source

pub fn capture_audio( &self, request: FrameSyncAudioRequest, ) -> Result<FrameSyncAudioRef<'_>>

Capture audio with dynamic resampling.

This does not wait for new network audio. Capture requests ask the NDI SDK to resample incoming audio to match the requested sample rate, channel count, and sample count. Query requests return the current input format without requesting samples. Wrapper synchronization and resampling still take time.

Call this at the rate you need audio - the SDK will adapt the incoming signal to match your output timing using dynamic audio sampling.

§Arguments
  • request - Explicit capture or query operation. For capture requests, None sample rate means “use the current source rate”; channels are explicit.
§Querying Input Format

Use FrameSyncAudioRequest::query_input to query the current input format without capturing samples. The returned frame contains the input’s sample rate and channel count, or a validated empty no-source state when no audio format has been received yet.

§Errors

Returns frame-validation errors if the SDK returns malformed audio dimensions, sample layout, buffers, or metadata. Request dimensions were already validated at construction.

§Example
let framesync = FrameSync::new(receiver)?;

// Audio callback - called by sound card at its rate
let request = FrameSyncAudioRequest::capture_with(Some(48_000), 2, 1_024)?;

loop {
    // Request 1024 stereo samples at 48kHz
    let audio = framesync.capture_audio(request)?;

    // Process each channel without reading inter-channel padding.
    for samples in audio.channels() {
        println!("Got {} samples in a channel", samples.len());
    }

    // Check for a validated query/no-source empty state
    if audio.is_empty() {
        println!("No audio source yet");
    }
}
Source

pub fn capture_audio_owned( &self, request: FrameSyncAudioRequest, ) -> Result<Option<AudioFrame>>

Capture audio and convert to an owned frame.

This is a convenience method that captures audio and immediately converts it to an owned AudioFrame that can be sent across threads. Query or no-source states that contain no sample buffer return Ok(None).

§Arguments
  • request - Explicit capture or query operation.
§Errors

Propagates returned-frame validation and owned-copy errors from Self::capture_audio and AudioRef::to_owned.

Source

pub fn audio_queue_depth(&self) -> i32

Query the current audio queue depth.

Returns the approximate number of audio samples currently buffered. This can be useful when using an inaccurate timer for audio playback.

Note: This value may change immediately after being read as new samples are continuously received. For most applications, simply call capture_audio at your desired rate and let the SDK handle timing.

§Example
let framesync = FrameSync::new(receiver)?;

// Check available samples before capture
let available = framesync.audio_queue_depth();
println!("Audio samples available: {}", available);

Trait Implementations§

Source§

impl Debug for FrameSync

Source§

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

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

impl Send for FrameSync

§Safety

The NDI SDK documentation states that framesync operations are thread-safe. The FrameSync owns its receiver, keeping the SDK runtime alive. Borrowed frame guards cannot be moved between threads and keep this owner borrowed until their buffers are freed; destruction requires exclusive ownership.

Source§

impl Sync for FrameSync

§Safety

The NDI SDK guarantees that framesync capture functions are internally synchronized and can be called concurrently from multiple threads. Receiver attachment disables raw video capture until the synchronizer is destroyed, including when the receiver is accessed through FrameSync::receiver.

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.