Skip to main content

RealtimeResampler

Struct RealtimeResampler 

Source
pub struct RealtimeResampler<T = f64>
where T: Float + FftNum,
{ /* private fields */ }
Expand description

Realtime reasampler for live audio streams. If you’re looking for rodio support, see RodioResampler.

The real-time resampler allow you to plug ardftsrc into your own realtime audio pipeline. It accepts interleaved samples one-at-a-time.

  1. Call write_sample(...) with each incoming interleaved sample and read_sample(...) at your output cadence.
  2. For multichannel streams, samples must be written interleaved.
  3. Call new_span(input_sample_rate, channels) when the input sample rate or channel count changes.
  4. Call finalize() at end-of-stream, then keep calling read_sample(...) until it returns None.

§Startup Delay

RealtimeResampler has some startup delay and will emit negative-zero silence until the resampler is primed and producing samples. You can check RealtimeResampler::is_primed() to see if the resampler is ready to produce real samples. You can also check RealtimeResampler::sample_is_underrun() to see if a produced sample is negative-zero silence emitted during initial delay.

If the upstream source can handle it, on stream startup it is recommended to prime the resampler by pulling samples from the upstream source rapidly, then fast-forwarding until RealtimeResampler::is_primed() returns true. See the RodioResampler::fast_start() source code for an example on how to do this.

§Pacing

If you are wiring RealtimeResampler into your own realtime audio pipeline, you’ll want to keep proper pacing ratios between input and output samples. See the rodio source for an example on how to do this. If you notice crackling with slow playback, or a very slow response to seeking, those are both symptoms of bad pacing.

§Spans

Streaming sources sometimes change format while they are still producing samples. For example, a playlist-like source may play one file at 44.1 kHz stereo and then another at 48 kHz mono. The realtime resampler models those format regions as spans. You can start a new span with new_span(). When a new span starts, writes go to the new span immediately, and reads continue draining the previous span first before switching to the next.

Input spans and output spans are non-synchronous. After calling new_span(), query samples_left_in_span() to see how many samples are left on the output side before the output will switch to a new span.

§Potential allocations on span boundaries

RealtimeResampler uses a span pool to avoid allocations. After a span has played, its allocations are returned to the pool to be re-used. Under ideal conditions (playing a single album back to back with no format changes between spans) the resampler will not allocate during playback. However, the resampler may still perform transient allocations at span boundaries under the following conditions:

  • Rapidly cycling through spans (eg. a user pressing next, next, next, next…)
  • Playing a heterogeneous playlist with many different sample-rates and channel counts.

In both of these allocation conditions, the allocation is transient at span boundary and will eventually “settle down” and avoid allocations as the span pool is populated.

§Buffer Size

It is recommended to use a buffer before sending output samples to your DAC. Use a buffer size that is at least 2048 to 4096 frames. If you experience crackling, try increasing the buffer size. Marginal buffer capacity first shows up as small glitches on seek.

§Example

fn resample_streaming(span_1_input: Vec<f32>, span_2_input: Vec<f32>) -> Result<Vec<f32>, ardftsrc::Error> {
    use ardftsrc::{PRESET_GOOD, RealtimeResampler, SamplesLeftInSpan};

    // Span 1 is 44.1 kHz stereo. Span 2 is 48 kHz mono.
    // Both spans are resampled to the same 48 kHz output rate.
    assert!(span_1_input.len().is_multiple_of(2));

    let config = PRESET_GOOD
        .with_input_rate(44_100)
        .with_output_rate(48_000)
        .with_channels(2);

    let mut resampler = RealtimeResampler::<f64>::new(config)?;
    let mut output = Vec::<f32>::new();

    // This intentionally writes one sample at a time. Larger slices are more efficient,
    // but single-sample writes are valid.
    for sample in span_1_input {
        resampler.write_sample(sample as f64);

        if let Some(sample) = resampler.read_sample() {
            output.push(sample as f32);
        }

        if resampler.samples_left_in_span() == SamplesLeftInSpan::Known(0) {
            // New span detected, maybe switch channel count in output.
        }
    }

    resampler.new_span(48_000, 1);

    for sample in span_2_input {
        resampler.write_sample(sample as f64);

        if let Some(sample) = resampler.read_sample() {
            output.push(sample as f32);
        }

        if resampler.samples_left_in_span() == SamplesLeftInSpan::Known(0) {
            // New span detected, maybe switch channel count in output.
        }
    }

    // Finalization can produce delayed tail output, so keep reading until the stream is drained.
    resampler.finalize();
    while let Some(sample) = resampler.read_sample() {
        output.push(sample as f32);
    }

    Ok(output)
}

Implementations§

Source§

impl<T> RealtimeResampler<T>
where T: Float + FftNum,

Source

pub fn new(config: Config) -> Result<Self, Error>

Constructs a sample-streaming resampler from config.

Source

pub fn num_samples_ready(&self) -> usize

Returns the number of buffered output samples ready to read.

Source

pub fn is_primed(&mut self) -> bool

Returns true when the output buffer has enough samples for realtime read_sample() pulls.

Source

pub fn estimate_priming_samples(&self) -> usize

Estimates the number of input samples required to prime the resampler.

This can be inaccurate if there is a span transition during the priming process.

Source

pub fn estimate_priming_duration(&self) -> Duration

Estimates the duration required to prime the resampler.

This can be inaccurate if there is a span transition during the priming process.

Source

pub fn reset(&mut self)

Resets internal streaming state so the next input is treated as a new, independent stream.

Note: this allocates

Source

pub fn new_span( &mut self, input_sample_rate: usize, channels: usize, ) -> Result<(), Error>

Starts a new input span while preserving output rate and quality settings.

Writes will write to the new span immediately. Reads will drain the previous span before moving to the new span.

If input_sample_rate and channels match the current input-active span, this is a no-op.

Source

pub fn samples_left_in_span(&self) -> SamplesLeftInSpan

Returns samples left before reads cross from the output-active span into the next queued span.

Returns EndOfStream if the stream is done. Returns Unknown if the length is not known and we are not in a span transition. Returns Known(usize) if the length is known and we are in a span transition.

Source

pub fn output_channels(&self) -> usize

Returns the output channel count for the next samples that read_samples() would emit.

When samples_left_in_span() == Some(0), the current output-active span is already drained, so this reports the queued next span’s channel count.

Source

pub fn input_channels(&self) -> usize

Returns the input channel count for samples currently being written.

Source

pub fn input_sample_rate(&self) -> usize

Returns the input sample rate for samples currently being written.

Source

pub fn output_sample_rate(&self) -> usize

Returns the output sample rate for samples currently being read.

When samples_left_in_span() == Some(0), the current output-active span is already drained, so this reports the queued next span’s channel count.

Source

pub fn is_done(&self) -> bool

Returns true when the stream has fully completed.

A stream is considered done when:

  • the output-active span has been finalized,
  • there is no queued next span, and
  • all buffered input/output samples have been drained.
Source

pub fn is_finalized(&self) -> bool

Check if this resampler has been finalized.

A finalized resampler will not accept any more input, but will still continue to produce output until all buffered samples have been drained.

Call is_done() to check if the resampler is fully drained.

Source

pub fn write_sample(&mut self, sample: T) -> Result<(), Error>

Accepts a single sample.

Source

pub fn write_samples(&mut self, input: &[T]) -> Result<(), Error>

Accepts interleaved streaming samples of any length.

Input is internally buffered and converted into fixed-size chunks. This method does not return produced output directly; call read_samples() to drain available samples.

Source

pub fn read_sample(&mut self) -> Option<T>

Reads a single sample from the resampler.

Returns None if the resampler is done and no more output will be produced.

Returns Some(T::neg_zero()) if the resampler is not primed and is producing negative-zero silence during initial priming delay. Query sample_is_underrun() to check if a sample is negative-zero silence emitted during initial delay.s

Source

pub fn read_samples(&mut self, output: &mut [T]) -> Option<usize>

Reads up to output.len() interleaved samples from internally buffered output.

Returns one of:

  • Some(written_samples) if samples were written to output. Read the inner value to see how many samples were written.
  • Some(0) if the resampler is not primed, the output buffer is zero-length, or the resampler is input starved and is underrunning.
  • None if the resampler is done and no more output will be produced.
Source

pub fn finalize(&mut self) -> Result<(), Error>

Marks the input-active span as finalized and flushes delayed output into pending samples.

After this call, no new input should be written for the current stream. Keep calling read_samples() or read_sample() until it returns zero to drain all finalized output.

If your streaming pipeline does not need delayed tail output at end-of-stream, call reset() directly instead of finalize_samples(). This is specifically for abrupt switching cases where you intentionally discard the previous stream tail. For normal endings where tail output is desired, use finalize().

For multi-channel streams, callers must provide a complete interleaved frame via write_samples() before finalizing. If a dangling partial frame remains buffered, this method returns Error::DanglingPartialFrame.

Source

pub fn sample_is_underrun(sample: T) -> bool

Returns true when the sample is negative-zero silence emitted during initial delay.

Auto Trait Implementations§

§

impl<T> Freeze for RealtimeResampler<T>
where T: Freeze,

§

impl<T = f64> !RefUnwindSafe for RealtimeResampler<T>

§

impl<T> Send for RealtimeResampler<T>

§

impl<T> Sync for RealtimeResampler<T>

§

impl<T> Unpin for RealtimeResampler<T>
where T: Unpin,

§

impl<T> UnsafeUnpin for RealtimeResampler<T>
where T: UnsafeUnpin,

§

impl<T = f64> !UnwindSafe for RealtimeResampler<T>

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<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

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> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

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.
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,