1use std::collections::VecDeque;
4
5use bytes::Bytes;
6
7use super::decoder::{Config, Decoder};
8use crate::resample::{Resampler, remix, validate_channels};
9use crate::{Activity, Error, Frame};
10
11pub struct Consumer {
19 decoder: Decoder,
20 track: moq_mux::container::Consumer<moq_mux::catalog::hang::Container>,
21 resampler: Option<Resampler>,
22 config: Config,
23 resolved_sample_rate: u32,
24 resolved_channels: u32,
25 tail: Option<moq_net::Timestamp>,
28 spans: VecDeque<ActivitySpan>,
30 trailing: Activity,
33 epoch: Option<moq_net::Timestamp>,
35 frames_decoded: usize,
37 end: Option<moq_net::Timestamp>,
39 terminal_start: Option<moq_net::Timestamp>,
41 discontinuity: u64,
43}
44
45struct ActivitySpan {
46 end: moq_net::Timestamp,
47 activity: Activity,
48}
49
50impl Consumer {
51 pub async fn new(
54 broadcast: &moq_net::broadcast::Consumer,
55 catalog: &hang::catalog::AudioConfig,
56 name: impl Into<String>,
57 config: Config,
58 ) -> Result<Self, Error> {
59 let decoder = Decoder::new(catalog)?;
60 let sample_rate = config.sample_rate.unwrap_or_else(|| decoder.sample_rate());
61 let channels = config.channels.unwrap_or_else(|| decoder.channel_count());
62 validate_channels(channels)?;
63
64 let resampler = if sample_rate == decoder.sample_rate() {
65 None
66 } else {
67 let chunk_frames = (decoder.sample_rate() as usize * 20) / 1000;
68 Some(Resampler::new(
69 decoder.sample_rate(),
70 sample_rate,
71 decoder.channel_count(),
72 chunk_frames,
73 )?)
74 };
75
76 let name = name.into();
77 let track = broadcast
78 .track(&name)?
79 .subscribe(moq_net::track::Subscription::default().with_priority(hang::catalog::PRIORITY.audio))
80 .await?;
81 let container = moq_mux::catalog::hang::Container::try_from(&catalog.container)?;
85 let mut track = moq_mux::container::Consumer::new(track, container);
86 if let Some(latency) = config.latency_max {
87 track = track.with_latency(latency);
88 }
89
90 Ok(Self {
91 decoder,
92 track,
93 resampler,
94 config,
95 resolved_sample_rate: sample_rate,
96 resolved_channels: channels,
97 tail: None,
98 spans: VecDeque::new(),
99 trailing: Activity::Active,
100 epoch: None,
101 frames_decoded: 0,
102 end: None,
103 terminal_start: None,
104 discontinuity: 0,
105 })
106 }
107
108 pub fn config(&self) -> &Config {
110 &self.config
111 }
112
113 pub fn sample_rate(&self) -> u32 {
116 self.resolved_sample_rate
117 }
118
119 pub fn channels(&self) -> u32 {
122 self.resolved_channels
123 }
124
125 pub async fn read(&mut self) -> Result<Option<Frame>, Error> {
132 loop {
133 let mux_frame = self.track.read().await?;
134 self.apply_discontinuity()?;
135 let Some(mux_frame) = mux_frame else {
136 return self.flush();
137 };
138
139 if let Some(end) = self.track.end()
140 && self.end != Some(end)
141 {
142 self.end = Some(end);
143 self.frames_decoded = 0;
144 self.terminal_start = None;
145 }
146
147 let rate = self.decoder.sample_rate();
148 let epoch = *self.epoch.get_or_insert(mux_frame.timestamp);
149 let decoded = self.decoder.decode(&mux_frame.payload)?;
150 let activity = decoded.activity;
151 let mut decoded = decoded.samples;
152 if let Some(end) = self.end {
153 let terminal_start = *self
154 .terminal_start
155 .get_or_insert(rewind(mux_frame.timestamp, self.decoder.delay(), rate)?.max(epoch));
156 let total = frames_between(terminal_start, end, rate)?;
157 let remaining = total.saturating_sub(self.frames_decoded);
158 decoded.truncate(remaining.saturating_mul(self.decoder.channel_count() as usize));
159 }
160
161 let frames = decoded.len() / self.decoder.channel_count().max(1) as usize;
162 let decoded_at = if let Some(terminal_start) = self.terminal_start {
163 advance(terminal_start, self.frames_decoded, rate)?
164 } else {
165 mux_frame.timestamp
166 };
167 if self.end.is_some() {
168 self.frames_decoded += frames;
169 }
170 if decoded.is_empty() {
171 continue;
172 }
173
174 let (pcm, timestamp) = match self.resampler.as_mut() {
175 Some(r) => {
180 let pending = r.pending_frames();
181 let skipped = r.skipped();
182 let pcm = r.process(&decoded)?;
183 (pcm, self.starts_at(decoded_at, pending, skipped, rate)?)
184 }
185 None => (decoded, decoded_at),
186 };
187
188 let decoded_end = advance(decoded_at, frames, rate)?;
189 self.tail = Some(decoded_end);
190
191 let resampled = self.resampler.is_some();
196 if resampled {
197 self.spans.push_back(ActivitySpan {
198 end: decoded_end,
199 activity,
200 });
201 }
202
203 if pcm.is_empty() {
207 continue;
208 }
209
210 let activity = if resampled {
211 self.activity_at(timestamp)
212 } else {
213 activity
214 };
215 return Ok(Some(self.frame(pcm, timestamp, activity)?));
216 }
217 }
218
219 fn apply_discontinuity(&mut self) -> Result<(), Error> {
221 let discontinuity = self.track.discontinuity();
222 if discontinuity == self.discontinuity {
223 return Ok(());
224 }
225
226 self.discontinuity = discontinuity;
227 self.decoder.reset()?;
228 if let Some(resampler) = self.resampler.as_mut() {
229 resampler.reset();
230 }
231 self.tail = None;
232 self.spans.clear();
233 self.trailing = Activity::Active;
234 self.epoch = None;
235 self.frames_decoded = 0;
236 self.end = None;
237 self.terminal_start = None;
238 Ok(())
239 }
240
241 fn flush(&mut self) -> Result<Option<Frame>, Error> {
248 let (Some(resampler), Some(tail)) = (self.resampler.take(), self.tail) else {
249 return Ok(None);
250 };
251
252 let pending = resampler.pending_frames();
253 let skipped = resampler.skipped();
254 let pcm = resampler.flush()?;
255 if pcm.is_empty() {
256 return Ok(None);
257 }
258
259 let timestamp = self.starts_at(tail, pending, skipped, self.decoder.sample_rate())?;
260 let activity = self.activity_at(timestamp);
261 Ok(Some(self.frame(pcm, timestamp, activity)?))
262 }
263
264 fn activity_at(&mut self, timestamp: moq_net::Timestamp) -> Activity {
266 while let Some(span) = self.spans.front().filter(|span| span.end <= timestamp) {
267 self.trailing = span.activity;
268 self.spans.pop_front();
269 }
270
271 self.spans.front().map_or(self.trailing, |span| span.activity)
272 }
273
274 fn starts_at(
283 &self,
284 timestamp: moq_net::Timestamp,
285 pending: usize,
286 skipped: usize,
287 rate: u32,
288 ) -> Result<moq_net::Timestamp, Error> {
289 let timestamp = rewind(timestamp, pending, rate)?;
290 rewind(timestamp, skipped, self.resolved_sample_rate)
291 }
292
293 fn frame(&self, pcm: Vec<f32>, timestamp: moq_net::Timestamp, activity: Activity) -> Result<Frame, Error> {
295 let pcm = if self.decoder.channel_count() == self.resolved_channels {
296 pcm
297 } else {
298 remix(&pcm, self.decoder.channel_count(), self.resolved_channels)?
299 };
300
301 let bytes = self.config.format.from_interleaved_f32(&pcm, self.resolved_channels)?;
302 Ok(Frame {
303 timestamp,
304 data: Bytes::from(bytes),
305 activity,
306 })
307 }
308}
309
310fn advance(timestamp: moq_net::Timestamp, frames: usize, sample_rate: u32) -> Result<moq_net::Timestamp, Error> {
312 if frames == 0 {
313 return Ok(timestamp);
314 }
315
316 let offset = moq_net::Timestamp::from_scale(frames as u64, sample_rate as u64)?.convert(timestamp.scale())?;
317 Ok(timestamp.checked_add(offset)?)
318}
319
320fn frames_between(start: moq_net::Timestamp, end: moq_net::Timestamp, sample_rate: u32) -> Result<usize, Error> {
322 let duration = end.checked_sub(start)?;
323 let frames = (std::time::Duration::from(duration).as_nanos() * sample_rate as u128 + 500_000_000) / 1_000_000_000;
324 usize::try_from(frames).map_err(|_| Error::Unsupported("audio duration does not fit in memory".into()))
325}
326
327fn rewind(timestamp: moq_net::Timestamp, frames: usize, sample_rate: u32) -> Result<moq_net::Timestamp, Error> {
332 if frames == 0 {
333 return Ok(timestamp);
334 }
335
336 let offset = moq_net::Timestamp::from_scale(frames as u64, sample_rate as u64)?.convert(timestamp.scale())?;
337 Ok(timestamp
338 .checked_sub(offset)
339 .unwrap_or(moq_net::Timestamp::new(0, timestamp.scale())?))
340}
341
342#[cfg(test)]
343mod tests {
344 use moq_net::Timestamp;
345
346 use super::*;
347 use crate::Format;
348 use crate::encode::{Encoder, Input, Options, Producer};
349
350 #[tokio::test]
351 async fn remixes_mono_stream_to_stereo_output() {
352 let mut broadcast = moq_net::broadcast::Info::new().produce();
353 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
354 let subscriber = broadcast.consume();
355 let input = Input {
356 format: Format::F32,
357 sample_rate: 48_000,
358 channels: 1,
359 };
360 let options = Options {
361 track: Some("audio".to_string()),
362 ..Options::default()
363 };
364 let mut producer = Producer::new(&mut broadcast, catalog, input.clone(), &options).unwrap();
365 let catalog = Encoder::new(&crate::encode::Config::new(input)).unwrap().catalog();
366 let mut consumer = Consumer::new(
367 &subscriber,
368 &catalog,
369 "audio",
370 Config {
371 channels: Some(2),
372 ..Config::new()
373 },
374 )
375 .await
376 .unwrap();
377
378 let samples = vec![0.1f32; 960];
379 let mut data = Vec::with_capacity(samples.len() * size_of::<f32>());
380 for sample in samples {
381 data.extend_from_slice(&sample.to_le_bytes());
382 }
383 producer.write(&Frame::new(data.into(), Timestamp::ZERO)).unwrap();
384
385 let frame = consumer.read().await.unwrap().expect("decoded frame");
386 let samples = Format::F32.as_interleaved_f32(&frame.data, 2).unwrap();
387 assert_eq!(samples.len(), (960 - 312) * 2);
388 for pair in samples.chunks_exact(2) {
389 assert_eq!(pair[0], pair[1]);
390 }
391 }
392
393 #[tokio::test]
400 async fn resampled_timestamps_follow_the_samples() {
401 let mut broadcast = moq_net::broadcast::Info::new().produce();
402 let track = broadcast.create_track("audio", hang::container::track_info()).unwrap();
403 let subscriber = broadcast.consume();
404
405 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 44_100, 1);
406 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
407
408 let mut consumer = Consumer::new(
409 &subscriber,
410 &catalog,
411 "audio",
412 Config {
413 sample_rate: Some(48_000),
414 ..Config::new()
415 },
416 )
417 .await
418 .unwrap();
419
420 const FRAMES: u64 = 1024;
422 let payload: Bytes = vec![0u8; FRAMES as usize * size_of::<f32>()].into();
423 for packet in 0..2 {
424 producer
425 .write(moq_mux::container::Frame {
426 timestamp: moq_net::Timestamp::from_scale(packet * FRAMES, 44_100).unwrap(),
427 duration: None,
428 payload: payload.clone(),
429 keyframe: true,
430 })
431 .unwrap();
432 }
433
434 let first = consumer.read().await.unwrap().expect("decoded frame");
435 assert_eq!(first.timestamp.as_micros(), 0);
436
437 let second = consumer.read().await.unwrap().expect("decoded frame");
443 let first_frames = (first.data.len() / size_of::<f32>()) as u128;
444 let ends_at = first_frames * 1_000_000 / 48_000;
445 let gap = second.timestamp.as_micros().abs_diff(ends_at);
446 assert!(gap < 100, "expected the frames to meet, got a {gap} us gap");
447 }
448
449 #[tokio::test]
453 async fn resampled_tail_survives_the_end_of_the_track() {
454 let mut broadcast = moq_net::broadcast::Info::new().produce();
455 let track = broadcast.create_track("audio", hang::container::track_info()).unwrap();
456 let subscriber = broadcast.consume();
457
458 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 44_100, 1);
459 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
460
461 let mut consumer = Consumer::new(
462 &subscriber,
463 &catalog,
464 "audio",
465 Config {
466 sample_rate: Some(48_000),
467 ..Config::new()
468 },
469 )
470 .await
471 .unwrap();
472
473 const FRAMES: usize = 1024;
475 let payload: Bytes = vec![0u8; FRAMES * size_of::<f32>()].into();
476 producer
477 .write(moq_mux::container::Frame {
478 timestamp: moq_net::Timestamp::ZERO,
479 duration: None,
480 payload,
481 keyframe: true,
482 })
483 .unwrap();
484 producer.finish().unwrap();
485
486 let first = consumer.read().await.unwrap().expect("decoded frame");
487 let first_frames = first.data.len() / size_of::<f32>();
488
489 let tail = consumer.read().await.unwrap().expect("flushed tail");
490 let tail_frames = tail.data.len() / size_of::<f32>();
491
492 assert!((215..=230).contains(&tail_frames), "unexpected tail: {tail_frames}");
496 let ends_at = (first_frames as u128) * 1_000_000 / 48_000;
499 let gap = tail.timestamp.as_micros().abs_diff(ends_at);
500 assert!(gap < 100, "expected the tail to meet the body, got a {gap} us gap");
501
502 let total = first_frames + tail_frames;
506 assert!((1105..=1120).contains(&total), "unexpected total: {total}");
507 assert!(consumer.read().await.unwrap().is_none());
508 }
509
510 #[tokio::test]
511 async fn resampling_keeps_the_activity_boundary_on_its_source() {
512 let mut encoder = Encoder::new(&crate::encode::Config {
513 dtx: true,
514 bitrate: Some(24_000),
515 frame_duration: std::time::Duration::from_millis(10),
516 ..crate::encode::Config::new(Input {
517 channels: 1,
518 ..Input::default()
519 })
520 })
521 .unwrap();
522 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 1);
523
524 let mut broadcast = moq_net::broadcast::Info::new().produce();
525 let track = broadcast.create_track("audio", hang::container::track_info()).unwrap();
526 let subscriber = broadcast.consume();
527 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
528 let mut consumer = Consumer::new(
529 &subscriber,
530 &catalog,
531 "audio",
532 Config {
533 sample_rate: Some(44_100),
534 ..Config::new()
535 },
536 )
537 .await
538 .unwrap();
539
540 let active = vec![0.5; encoder.frame_size()];
541 let silence = vec![0.0; encoder.frame_size()];
542 let mut first_dtx = None;
543 for index in 0..40u64 {
544 let packet = encoder.encode(if index == 0 { &active } else { &silence }).unwrap();
545 let timestamp = Timestamp::from_scale(index * encoder.frame_size() as u64, 48_000).unwrap();
546 if first_dtx.is_none() && packet.activity.is_dtx() {
547 first_dtx = Some(timestamp);
548 }
549 producer
550 .write(moq_mux::container::Frame {
551 timestamp,
552 payload: packet.payload,
553 keyframe: true,
554 duration: None,
555 })
556 .unwrap();
557 producer.cut(None).unwrap();
558 }
559 producer.finish().unwrap();
560
561 let expected = first_dtx.expect("silence should enter Opus DTX");
562 let mut actual = None;
563 while let Some(frame) = consumer.read().await.unwrap() {
564 assert!(!frame.data.is_empty(), "read returned a frame with no samples");
569 if frame.activity.is_dtx() {
570 actual = Some(frame.timestamp);
571 break;
572 }
573 }
574 let actual = actual.expect("consumer should report Opus DTX");
575
576 let delay = actual.as_micros() as i128 - expected.as_micros() as i128;
583 let chunk_us = 20_000i128;
584 assert!(
585 (0..chunk_us).contains(&delay),
586 "DTX label landed {delay} us from its source, outside [0, {chunk_us})"
587 );
588 }
589
590 #[tokio::test]
591 async fn reads_the_container_the_catalog_declares() {
592 let mut broadcast = moq_net::broadcast::Info::new().produce();
593 let track = broadcast.create_track("audio", hang::container::track_info()).unwrap();
594 let subscriber = broadcast.consume();
595
596 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 1);
597 catalog.container = hang::catalog::Container::Loc;
598
599 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Loc);
600 let mut consumer = Consumer::new(
601 &subscriber,
602 &catalog,
603 "audio",
604 Config {
605 format: Format::F32,
606 ..Config::new()
607 },
608 )
609 .await
610 .unwrap();
611
612 let samples = [0.25f32, -0.5, 0.75, -1.0];
613 let payload: Vec<u8> = samples.iter().flat_map(|sample| sample.to_le_bytes()).collect();
614 producer
615 .write(moq_mux::container::Frame {
616 timestamp: Timestamp::ZERO,
617 duration: None,
618 payload: payload.into(),
619 keyframe: true,
620 })
621 .unwrap();
622
623 let frame = consumer.read().await.unwrap().expect("decoded frame");
624 assert_eq!(
625 Format::F32.as_interleaved_f32(&frame.data, 1).unwrap().as_ref(),
626 samples
627 );
628 }
629
630 #[tokio::test]
635 async fn decodes_a_cmaf_framed_track() {
636 let input = Input {
637 format: Format::F32,
638 sample_rate: 48_000,
639 channels: 2,
640 };
641
642 let mut encoder = Encoder::new(&crate::encode::Config::new(input.clone())).unwrap();
644 let mut catalog = encoder.catalog();
645 let pcm = vec![0.0f32; encoder.frame_size() * encoder.codec_channels() as usize];
646 let packet = encoder.encode(&pcm).unwrap();
647
648 let muxer = moq_mux::container::fmp4::Muxer::audio(&catalog).unwrap();
650 let init = muxer.init().unwrap().expect("an out-of-band codec has an init segment");
651 catalog.container = hang::catalog::Container::Cmaf { init };
652
653 let mut broadcast = moq_net::broadcast::Info::new().produce();
654 let subscriber = broadcast.consume();
655 let track = broadcast.create_track("audio", hang::container::track_info()).unwrap();
656 let container = moq_mux::catalog::hang::Container::try_from(&catalog.container).unwrap();
657 let mut producer = moq_mux::container::Producer::new(track, container);
658
659 let mut consumer = Consumer::new(&subscriber, &catalog, "audio", Config::new())
660 .await
661 .unwrap();
662
663 producer
664 .write(moq_mux::container::Frame {
665 timestamp: Timestamp::ZERO,
666 payload: packet.payload,
667 keyframe: true,
668 duration: None,
669 })
670 .unwrap();
671 producer.cut(None).unwrap();
672
673 let frame = consumer.read().await.unwrap().expect("decoded frame");
677 assert_eq!(frame.timestamp.as_micros(), 0);
680 let samples = Format::F32.as_interleaved_f32(&frame.data, 2).unwrap();
681 assert_eq!(samples.len(), (960 - 312) * 2);
682 }
683}