1use rubato::audioadapter_buffers::direct::SequentialSliceOfVecs;
9use rubato::{
10 Async, FixedAsync, Resampler as RubatoTrait, SincInterpolationParameters, SincInterpolationType, WindowFunction,
11};
12
13use crate::Error;
14
15pub struct Resampler {
17 resampler: Async<f32>,
18 chunk_frames: usize,
19 input_rate: u32,
22 ratio: f64,
24 delay: usize,
26 started: bool,
29 skip: usize,
32 channels: usize,
33 input_planar: Vec<Vec<f32>>,
34 output_planar: Vec<Vec<f32>>,
35 output_frames_max: usize,
36 pending: Vec<f32>,
37 held: Option<moq_net::Timestamp>,
41}
42
43impl Resampler {
44 pub fn new(input_rate: u32, output_rate: u32, channels: u32, chunk_frames: usize) -> Result<Self, Error> {
51 if chunk_frames == 0 {
52 return Err(Error::Unsupported("chunk_frames must be > 0".into()));
53 }
54
55 let params = SincInterpolationParameters {
56 sinc_len: 128,
57 f_cutoff: Some(0.95),
58 interpolation: SincInterpolationType::Linear,
59 oversampling_factor: 128,
60 window: WindowFunction::BlackmanHarris2,
61 };
62 let ratio = output_rate as f64 / input_rate as f64;
63 let resampler =
64 Async::<f32>::new_sinc(ratio, 1.0, ¶ms, chunk_frames, channels as usize, FixedAsync::Input)?;
65
66 let delay = resampler.output_delay();
67 let input_planar = (0..channels as usize).map(|_| vec![0.0f32; chunk_frames]).collect();
68 let output_frames_max = resampler.output_frames_max();
69 let output_planar = vec![vec![0.0f32; output_frames_max]; channels as usize];
70
71 Ok(Self {
72 resampler,
73 chunk_frames,
74 input_rate,
75 ratio,
76 delay,
77 started: false,
78 skip: delay,
79 channels: channels as usize,
80 input_planar,
81 output_planar,
82 output_frames_max,
83 pending: Vec::new(),
84 held: None,
85 })
86 }
87
88 pub fn skipped(&self) -> usize {
94 self.delay - self.skip
95 }
96
97 pub fn pending_frames(&self) -> usize {
99 self.pending.len() / self.channels
100 }
101
102 pub(crate) fn held_at(&self) -> Option<moq_net::Timestamp> {
115 self.held
116 }
117
118 pub fn reset(&mut self) {
125 self.resampler.reset();
126 self.pending.clear();
127 self.skip = self.delay;
128 self.started = false;
129 self.held = None;
130 }
131
132 pub fn flush(mut self) -> Result<Vec<f32>, Error> {
144 self.drain()
145 }
146
147 pub fn drain(&mut self) -> Result<Vec<f32>, Error> {
155 let out = self.drained()?;
156 self.reset();
157 Ok(out)
158 }
159
160 fn drained(&mut self) -> Result<Vec<f32>, Error> {
161 if !self.started {
165 return Ok(Vec::new());
166 }
167
168 let pending = self.pending_frames();
169
170 let repaid = self.delay - self.skip;
181 let wanted = ((pending as f64 * self.ratio).round() as usize + repaid) * self.channels;
182
183 let mut out = Vec::new();
184 while out.len() < wanted {
185 let skip_before = self.skip;
190 self.pending.resize(self.chunk_frames * self.channels, 0.0);
191 let produced = self.convert()?;
192 if produced.is_empty() && self.skip == skip_before {
193 break;
194 }
195 out.extend_from_slice(&produced);
196 }
197
198 out.truncate(wanted);
199 Ok(out)
200 }
201
202 pub fn process(&mut self, samples: &[f32], at: moq_net::Timestamp) -> Result<Vec<f32>, Error> {
210 if !samples.len().is_multiple_of(self.channels) {
211 return Err(Error::Misaligned {
212 got: samples.len(),
213 expected: samples.len().next_multiple_of(self.channels),
214 });
215 }
216
217 if self.pending.is_empty() {
219 self.held = Some(at);
220 }
221
222 self.started |= !samples.is_empty();
223 self.pending.extend_from_slice(samples);
224 let buffered = self.pending.len();
225 let out = self.convert()?;
226
227 if self.pending.len() < buffered {
231 let consumed = (samples.len() - self.pending.len()) / self.channels;
232 let elapsed =
233 moq_net::Timestamp::from_scale(consumed as u64, self.input_rate as u64)?.convert(at.scale())?;
234 self.held = Some(at.checked_add(elapsed)?);
235 }
236
237 Ok(out)
238 }
239
240 fn convert(&mut self) -> Result<Vec<f32>, Error> {
242 let chunk_samples = self.chunk_frames * self.channels;
243 let mut out = Vec::new();
244 while self.pending.len() >= chunk_samples {
245 for (frame_idx, frame) in self.pending[..chunk_samples].chunks_exact(self.channels).enumerate() {
246 for (ch, &sample) in frame.iter().enumerate() {
247 self.input_planar[ch][frame_idx] = sample;
248 }
249 }
250
251 let input = SequentialSliceOfVecs::new(&self.input_planar, self.channels, self.chunk_frames)
252 .expect("resampler input buffer dimensions");
253 let mut output =
254 SequentialSliceOfVecs::new_mut(&mut self.output_planar, self.channels, self.output_frames_max)
255 .expect("resampler output buffer dimensions");
256 let (_, produced) = self.resampler.process_into_buffer(&input, &mut output, None)?;
257
258 let prev_len = out.len();
259 out.resize(prev_len + produced * self.channels, 0.0);
260 for frame_idx in 0..produced {
261 for ch in 0..self.channels {
262 out[prev_len + frame_idx * self.channels + ch] = self.output_planar[ch][frame_idx];
263 }
264 }
265
266 self.pending.drain(..chunk_samples);
267 }
268
269 if self.skip > 0 {
273 let drop = self.skip.min(out.len() / self.channels) * self.channels;
274 out.drain(..drop);
275 self.skip -= drop / self.channels;
276 }
277
278 Ok(out)
279 }
280}
281
282pub(crate) fn validate_channels(count: u32) -> Result<(), Error> {
285 match count {
286 1 | 2 => Ok(()),
287 other => Err(Error::Unsupported(format!(
288 "channel remix only supports mono and stereo (got {other})"
289 ))),
290 }
291}
292
293pub(crate) fn remix(samples: &[f32], input_channels: u32, output_channels: u32) -> Result<Vec<f32>, Error> {
295 match (input_channels, output_channels) {
296 (1, 1) | (2, 2) => Ok(samples.to_vec()),
297 (1, 2) => {
298 let mut output = Vec::with_capacity(samples.len() * 2);
299 for &sample in samples {
300 output.extend_from_slice(&[sample, sample]);
301 }
302 Ok(output)
303 }
304 (2, 1) => Ok(samples.chunks_exact(2).map(|pair| (pair[0] + pair[1]) * 0.5).collect()),
305 _ => Err(Error::Unsupported(format!(
306 "channel remix only supports mono and stereo (got {input_channels} to {output_channels})"
307 ))),
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 fn at(frames: u64, rate: u64) -> moq_net::Timestamp {
317 moq_net::Timestamp::from_scale(frames, rate).unwrap()
318 }
319
320 #[test]
321 fn rejects_zero_chunk_frames() {
322 let r = Resampler::new(48_000, 48_000, 2, 0);
323 assert!(matches!(r, Err(Error::Unsupported(_))));
324 }
325
326 #[test]
327 fn upsample_44100_to_48000_preserves_energy_roughly() {
328 let mut r = Resampler::new(44_100, 48_000, 1, 1024).unwrap();
329 let input: Vec<f32> = (0..44_100)
330 .map(|i| (2.0 * std::f32::consts::PI * 440.0 * i as f32 / 44_100.0).sin() * 0.5)
331 .collect();
332 let mut out = r.process(&input, at(0, 44_100)).unwrap();
333 out.extend(r.process(&vec![0.0; 1024], at(44_100, 44_100)).unwrap());
334 assert!(
335 (47_000..50_000).contains(&out.len()),
336 "expected ~48k samples, got {}",
337 out.len()
338 );
339 }
340
341 #[test]
346 fn flush_drains_the_delayed_tail() {
347 let mut r = Resampler::new(44_100, 48_000, 1, 882).unwrap();
348
349 let mut input = vec![0.0f32; 1024];
354 input[1000] = 1.0;
355
356 let body = r.process(&input, at(0, 44_100)).unwrap();
357 let tail = r.flush().unwrap();
358
359 let peak = |samples: &[f32]| samples.iter().fold(0.0f32, |max, s| max.max(s.abs()));
360 assert!(peak(&body) < 0.01, "the sample emerged early: peak {}", peak(&body));
361 assert!(peak(&tail) > 0.5, "the tail lost the sample: peak {}", peak(&tail));
362 }
363
364 #[test]
369 fn flush_drains_on_an_exact_chunk_boundary() {
370 let mut r = Resampler::new(44_100, 48_000, 1, 882).unwrap();
371
372 let mut input = vec![0.0f32; 1764];
374 input[1750] = 1.0;
375
376 let body = r.process(&input, at(0, 44_100)).unwrap();
377 assert_eq!(r.pending_frames(), 0, "the input should divide evenly");
378
379 let tail = r.flush().unwrap();
380
381 let peak = |samples: &[f32]| samples.iter().fold(0.0f32, |max, s| max.max(s.abs()));
384 assert!(peak(&body) < 0.01, "the sample emerged early: peak {}", peak(&body));
385 assert!(peak(&tail) > 0.5, "the tail lost the sample: peak {}", peak(&tail));
386 }
387
388 #[test]
393 fn flush_sizes_a_stream_shorter_than_a_chunk() {
394 let mut r = Resampler::new(44_100, 48_000, 1, 882).unwrap();
395
396 let body = r.process(&[0.25f32; 441], at(0, 44_100)).unwrap();
397 let tail = r.flush().unwrap();
398
399 let total = body.len() + tail.len();
401 assert!((475..=485).contains(&total), "unexpected total: {total}");
402 }
403
404 #[test]
409 fn flush_survives_a_chunk_smaller_than_the_delay() {
410 let mut r = Resampler::new(44_100, 48_000, 1, 32).unwrap();
411
412 let body = r.process(&[0.5f32; 20], at(0, 44_100)).unwrap();
413 let tail = r.flush().unwrap();
414
415 let total = body.len() + tail.len();
416 assert!((18..=26).contains(&total), "unexpected total: {total}");
417 assert!(
418 tail.iter().any(|s| s.abs() > 0.25),
419 "the stream came back silent: peak {}",
420 tail.iter().fold(0.0f32, |m, s| m.max(s.abs()))
421 );
422 }
423
424 #[test]
428 fn drain_ends_the_stream_and_starts_a_new_one() {
429 let mut r = Resampler::new(44_100, 48_000, 1, 882).unwrap();
430
431 let mut input = vec![0.0f32; 1024];
432 input[1000] = 1.0;
433 let body = r.process(&input, at(0, 44_100)).unwrap();
434 let tail = r.drain().unwrap();
435
436 let peak = |samples: &[f32]| samples.iter().fold(0.0f32, |max, s| max.max(s.abs()));
437 assert!(peak(&tail) > 0.5, "the tail lost the sample: peak {}", peak(&tail));
438 assert!(
439 (1105..=1120).contains(&(body.len() + tail.len())),
440 "unexpected total: {}",
441 body.len() + tail.len()
442 );
443
444 assert_eq!(r.pending_frames(), 0);
446 assert_eq!(r.held_at(), None, "the drain should forget where the old stream was");
447 let after = r.process(&vec![0.0f32; 1024], at(2048, 44_100)).unwrap();
448 assert!(peak(&after) < 0.01, "audio crossed the gap: peak {}", peak(&after));
449 }
450
451 #[test]
456 fn held_frames_keep_the_stamp_they_arrived_under() {
457 let mut r = Resampler::new(44_100, 48_000, 1, 882).unwrap();
458
459 assert!(r.process(&[0.25f32; 441], at(0, 44_100)).unwrap().is_empty());
461 assert_eq!(r.held_at(), Some(at(0, 44_100)));
462
463 assert!(!r.process(&[0.25f32; 441], at(44_100, 44_100)).unwrap().is_empty());
465 assert_eq!(
466 r.held_at(),
467 Some(at(44_541, 44_100)),
468 "the tail starts at the end of the last packet consumed"
469 );
470 }
471
472 #[test]
476 fn an_emptied_buffer_re_anchors_on_the_next_input() {
477 let mut r = Resampler::new(44_100, 48_000, 1, 882).unwrap();
478
479 r.process(&[0.25f32; 882], at(0, 44_100)).unwrap();
480 assert_eq!(r.pending_frames(), 0);
481
482 r.process(&[0.25f32; 441], at(44_100, 44_100)).unwrap();
483 assert_eq!(r.held_at(), Some(at(44_100, 44_100)));
484 }
485
486 #[test]
487 fn leftover_frames_keep_the_new_packet_timestamp() {
488 let mut r = Resampler::new(44_100, 48_000, 1, 882).unwrap();
489 r.process(&[0.25; 441], at(0, 44_100)).unwrap();
490 r.process(&[0.25; 882], at(44_100, 44_100)).unwrap();
491 assert_eq!(r.pending_frames(), 441);
492 assert_eq!(r.held_at(), Some(at(44_541, 44_100)));
493 }
494
495 #[test]
496 fn held_timestamp_preserves_fractional_chunk_progress() {
497 let mut r = Resampler::new(11_025, 48_000, 1, 220).unwrap();
498 r.process(&vec![0.25; 11_025], at(0, 1000)).unwrap();
499 assert_eq!(r.pending_frames(), 25);
500 assert_eq!(r.held_at(), Some(at(997, 1000)));
501 }
502
503 #[test]
504 fn remix_mono_to_stereo_duplicates_samples() {
505 assert_eq!(remix(&[1.0, 2.0], 1, 2).unwrap(), [1.0, 1.0, 2.0, 2.0]);
506 }
507
508 #[test]
509 fn remix_stereo_to_mono_averages_channels() {
510 assert_eq!(remix(&[1.0, 3.0, 2.0, 4.0], 2, 1).unwrap(), [2.0, 3.0]);
511 }
512}