Expand description
Frame synchronization for clock-corrected video/audio capture.
The FrameSync type provides a “pull” interface for receiving NDI streams with
automatic time-base correction. This transforms the NDI SDK’s push-based capture
model into a pull model suitable for:
- Video playback synced to GPU v-sync
- Audio playback synced to sound card clock
- Multi-source mixing with a common output clock
§When to Use FrameSync vs Raw Capture
| Use Case | Raw Receiver | FrameSync |
|---|---|---|
| Recording (preserving timing) | ✓ | |
| Playback to GPU | ✓ | |
| Playback to sound card | ✓ | |
| Multi-source mixing | ✓ | |
| Analysis/processing pipeline | ✓ | |
| Low-latency monitoring | ✓ |
§Why FrameSync Exists
Computer clocks drift. The NDI SDK documentation explains:
Computer clocks rely on crystals which while all rated for the same frequency are still not exact. If your sending computer has an audio clock that it “thinks” is 48000Hz, to the receiver computer that has a different audio clock this might be 48001Hz or 47998Hz.
Without time-base correction, this causes:
- Video jitter: When sender/receiver clocks are nearly aligned, naive frame buffering causes visible jitter as frames occasionally repeat or skip.
- Audio drift: Accumulated clock difference causes audio to fall out of sync or cause glitches as the receiver runs out of or accumulates too many samples.
FrameSync solves these by:
- Using hysteresis-based video timing to determine optimal frame repeat/skip points
- Dynamically resampling audio with high-order filters to track clock differences
§Example
use grafton_ndi::{
NDI, Finder, FinderOptions, ReceiverOptions, Receiver, FrameSync,
FrameSyncAudioRequest, ScanType,
};
use std::{num::NonZeroI32, time::Duration};
fn main() -> Result<(), grafton_ndi::Error> {
let ndi = NDI::new()?;
let finder = Finder::new(&ndi, &FinderOptions::default())?;
finder.wait_for_sources(Duration::from_secs(1))?;
let sources = finder.current_sources()?;
let options = ReceiverOptions::builder(sources[0].clone()).build();
let receiver = Receiver::new(&ndi, &options)?;
// Create frame-sync from receiver
let framesync = FrameSync::new(receiver)?;
// Capture video synced to your output timing
if let Some(frame) = framesync.capture_video(ScanType::Progressive)? {
println!("{}x{} frame", frame.width(), frame.height());
}
// Capture audio at your sound card's rate
let audio = framesync.capture_audio(FrameSyncAudioRequest::Capture {
sample_rate: Some(NonZeroI32::new(48_000).unwrap()),
channels: Some(NonZeroI32::new(2).unwrap()),
samples: NonZeroI32::new(1_024).unwrap(),
})?;
println!("{} audio samples", audio.num_samples());
Ok(())
}Structs§
- Frame
Sync - Frame synchronizer for clock-corrected capture.
Enums§
- Frame
Sync Audio Request - Explicit audio operation for
FrameSync::capture_audio.
Type Aliases§
- Frame
Sync Audio Ref - A zero-copy borrowed audio frame from a
FrameSynccapture. - Frame
Sync Video Ref - A zero-copy borrowed video frame from a
FrameSynccapture.