pub struct RealtimeResampler<T = f64>{ /* 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.
- Call
write_sample(...)with each incoming interleaved sample andread_sample(...)at your output cadence. - For multichannel streams, samples must be written interleaved.
- Call
new_span(input_sample_rate, channels)when the input sample rate or channel count changes. - Call
finalize()at end-of-stream, then keep callingread_sample(...)until it returnsNone.
§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>
impl<T> RealtimeResampler<T>
Sourcepub fn new(config: Config) -> Result<Self, Error>
pub fn new(config: Config) -> Result<Self, Error>
Constructs a sample-streaming resampler from config.
Sourcepub fn num_samples_ready(&self) -> usize
pub fn num_samples_ready(&self) -> usize
Returns the number of buffered output samples ready to read.
Sourcepub fn is_primed(&mut self) -> bool
pub fn is_primed(&mut self) -> bool
Returns true when the output buffer has enough samples for realtime read_sample() pulls.
Sourcepub fn estimate_priming_samples(&self) -> usize
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.
Sourcepub fn estimate_priming_duration(&self) -> Duration
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.
Sourcepub fn reset(&mut self)
pub fn reset(&mut self)
Resets internal streaming state so the next input is treated as a new, independent stream.
Note: this allocates
Sourcepub fn new_span(
&mut self,
input_sample_rate: usize,
channels: usize,
) -> Result<(), Error>
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.
Sourcepub fn samples_left_in_span(&self) -> SamplesLeftInSpan
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.
Sourcepub fn output_channels(&self) -> usize
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.
Sourcepub fn input_channels(&self) -> usize
pub fn input_channels(&self) -> usize
Returns the input channel count for samples currently being written.
Sourcepub fn input_sample_rate(&self) -> usize
pub fn input_sample_rate(&self) -> usize
Returns the input sample rate for samples currently being written.
Sourcepub fn output_sample_rate(&self) -> usize
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.
Sourcepub fn is_done(&self) -> bool
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.
Sourcepub fn is_finalized(&self) -> bool
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.
Sourcepub fn write_sample(&mut self, sample: T) -> Result<(), Error>
pub fn write_sample(&mut self, sample: T) -> Result<(), Error>
Accepts a single sample.
Sourcepub fn write_samples(&mut self, input: &[T]) -> Result<(), Error>
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.
Sourcepub fn read_sample(&mut self) -> Option<T>
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
Sourcepub fn read_samples(&mut self, output: &mut [T]) -> Option<usize>
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 tooutput. 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.Noneif the resampler is done and no more output will be produced.
Sourcepub fn finalize(&mut self) -> Result<(), Error>
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.
Sourcepub fn sample_is_underrun(sample: T) -> bool
pub fn sample_is_underrun(sample: T) -> bool
Returns true when the sample is negative-zero silence emitted during initial delay.