1use bytes::Bytes;
4
5use super::decoder::{Config, Decoder};
6use crate::resample::{Resampler, remix, validate_channels};
7use crate::{Error, Frame};
8
9pub struct Consumer {
16 decoder: Decoder,
17 track: moq_mux::container::Consumer<moq_mux::catalog::hang::Container>,
18 resampler: Option<Resampler>,
19 config: Config,
20 resolved_sample_rate: u32,
21 resolved_channels: u32,
22 tail: Option<moq_net::Timestamp>,
25 epoch: Option<moq_net::Timestamp>,
27 frames_decoded: usize,
29 end: Option<moq_net::Timestamp>,
31 terminal_start: Option<moq_net::Timestamp>,
33 discontinuity: u64,
35}
36
37impl Consumer {
38 pub async fn new(
41 broadcast: &moq_net::broadcast::Consumer,
42 catalog: &hang::catalog::AudioConfig,
43 name: impl Into<String>,
44 config: Config,
45 ) -> Result<Self, Error> {
46 let decoder = Decoder::new(catalog)?;
47 let sample_rate = config.sample_rate.unwrap_or_else(|| decoder.sample_rate());
48 let channels = config.channels.unwrap_or_else(|| decoder.channel_count());
49 validate_channels(channels)?;
50
51 let resampler = if sample_rate == decoder.sample_rate() {
52 None
53 } else {
54 let chunk_frames = (decoder.sample_rate() as usize * 20) / 1000;
55 Some(Resampler::new(
56 decoder.sample_rate(),
57 sample_rate,
58 decoder.channel_count(),
59 chunk_frames,
60 )?)
61 };
62
63 let name = name.into();
64 let track = broadcast
65 .track(&name)?
66 .subscribe(moq_net::track::Subscription::default().with_priority(hang::catalog::PRIORITY.audio))
67 .await?;
68 let container = moq_mux::catalog::hang::Container::try_from(&catalog.container)?;
72 let mut track = moq_mux::container::Consumer::new(track, container);
73 if let Some(latency) = config.latency_max {
74 track = track.with_latency(latency);
75 }
76
77 Ok(Self {
78 decoder,
79 track,
80 resampler,
81 config,
82 resolved_sample_rate: sample_rate,
83 resolved_channels: channels,
84 tail: None,
85 epoch: None,
86 frames_decoded: 0,
87 end: None,
88 terminal_start: None,
89 discontinuity: 0,
90 })
91 }
92
93 pub fn config(&self) -> &Config {
95 &self.config
96 }
97
98 pub fn sample_rate(&self) -> u32 {
101 self.resolved_sample_rate
102 }
103
104 pub fn channels(&self) -> u32 {
107 self.resolved_channels
108 }
109
110 pub async fn read(&mut self) -> Result<Option<Frame>, Error> {
112 loop {
113 let mux_frame = self.track.read().await?;
114 self.apply_discontinuity()?;
115 let Some(mux_frame) = mux_frame else {
116 return self.flush();
117 };
118
119 if let Some(end) = self.track.end()
120 && self.end != Some(end)
121 {
122 self.end = Some(end);
123 self.frames_decoded = 0;
124 self.terminal_start = None;
125 }
126
127 let rate = self.decoder.sample_rate();
128 let epoch = *self.epoch.get_or_insert(mux_frame.timestamp);
129 let mut decoded = self.decoder.decode(&mux_frame.payload)?;
130 if let Some(end) = self.end {
131 let terminal_start = *self
132 .terminal_start
133 .get_or_insert(rewind(mux_frame.timestamp, self.decoder.delay(), rate)?.max(epoch));
134 let total = frames_between(terminal_start, end, rate)?;
135 let remaining = total.saturating_sub(self.frames_decoded);
136 decoded.truncate(remaining.saturating_mul(self.decoder.channel_count() as usize));
137 }
138
139 let frames = decoded.len() / self.decoder.channel_count().max(1) as usize;
140 let decoded_at = if let Some(terminal_start) = self.terminal_start {
141 advance(terminal_start, self.frames_decoded, rate)?
142 } else {
143 mux_frame.timestamp
144 };
145 if self.end.is_some() {
146 self.frames_decoded += frames;
147 }
148 if decoded.is_empty() {
149 continue;
150 }
151
152 let (pcm, timestamp) = match self.resampler.as_mut() {
153 Some(r) => {
158 let pending = r.pending_frames();
159 let skipped = r.skipped();
160 let pcm = r.process(&decoded)?;
161 (pcm, self.starts_at(decoded_at, pending, skipped, rate)?)
162 }
163 None => (decoded, decoded_at),
164 };
165
166 self.tail = Some(advance(decoded_at, frames, rate)?);
167
168 return Ok(Some(self.frame(pcm, timestamp)?));
169 }
170 }
171
172 fn apply_discontinuity(&mut self) -> Result<(), Error> {
174 let discontinuity = self.track.discontinuity();
175 if discontinuity == self.discontinuity {
176 return Ok(());
177 }
178
179 self.discontinuity = discontinuity;
180 self.decoder.reset()?;
181 if let Some(resampler) = self.resampler.as_mut() {
182 resampler.reset();
183 }
184 self.tail = None;
185 self.epoch = None;
186 self.frames_decoded = 0;
187 self.end = None;
188 self.terminal_start = None;
189 Ok(())
190 }
191
192 fn flush(&mut self) -> Result<Option<Frame>, Error> {
199 let (Some(resampler), Some(tail)) = (self.resampler.take(), self.tail) else {
200 return Ok(None);
201 };
202
203 let pending = resampler.pending_frames();
204 let skipped = resampler.skipped();
205 let pcm = resampler.flush()?;
206 if pcm.is_empty() {
207 return Ok(None);
208 }
209
210 let timestamp = self.starts_at(tail, pending, skipped, self.decoder.sample_rate())?;
211 Ok(Some(self.frame(pcm, timestamp)?))
212 }
213
214 fn starts_at(
223 &self,
224 timestamp: moq_net::Timestamp,
225 pending: usize,
226 skipped: usize,
227 rate: u32,
228 ) -> Result<moq_net::Timestamp, Error> {
229 let timestamp = rewind(timestamp, pending, rate)?;
230 rewind(timestamp, skipped, self.resolved_sample_rate)
231 }
232
233 fn frame(&self, pcm: Vec<f32>, timestamp: moq_net::Timestamp) -> Result<Frame, Error> {
235 let pcm = if self.decoder.channel_count() == self.resolved_channels {
236 pcm
237 } else {
238 remix(&pcm, self.decoder.channel_count(), self.resolved_channels)?
239 };
240
241 let bytes = self.config.format.from_interleaved_f32(&pcm, self.resolved_channels)?;
242 Ok(Frame {
243 timestamp,
244 data: Bytes::from(bytes),
245 })
246 }
247}
248
249fn advance(timestamp: moq_net::Timestamp, frames: usize, sample_rate: u32) -> Result<moq_net::Timestamp, Error> {
251 if frames == 0 {
252 return Ok(timestamp);
253 }
254
255 let offset = moq_net::Timestamp::from_scale(frames as u64, sample_rate as u64)?.convert(timestamp.scale())?;
256 Ok(timestamp.checked_add(offset)?)
257}
258
259fn frames_between(start: moq_net::Timestamp, end: moq_net::Timestamp, sample_rate: u32) -> Result<usize, Error> {
261 let duration = end.checked_sub(start)?;
262 let frames = (std::time::Duration::from(duration).as_nanos() * sample_rate as u128 + 500_000_000) / 1_000_000_000;
263 usize::try_from(frames).map_err(|_| Error::Unsupported("audio duration does not fit in memory".into()))
264}
265
266fn rewind(timestamp: moq_net::Timestamp, frames: usize, sample_rate: u32) -> Result<moq_net::Timestamp, Error> {
271 if frames == 0 {
272 return Ok(timestamp);
273 }
274
275 let offset = moq_net::Timestamp::from_scale(frames as u64, sample_rate as u64)?.convert(timestamp.scale())?;
276 Ok(timestamp
277 .checked_sub(offset)
278 .unwrap_or(moq_net::Timestamp::new(0, timestamp.scale())?))
279}
280
281#[cfg(test)]
282mod tests {
283 use moq_net::Timestamp;
284
285 use super::*;
286 use crate::Format;
287 use crate::encode::{Encoder, Input, Options, Producer};
288
289 #[tokio::test]
290 async fn remixes_mono_stream_to_stereo_output() {
291 let mut broadcast = moq_net::broadcast::Info::new().produce();
292 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
293 let subscriber = broadcast.consume();
294 let input = Input {
295 format: Format::F32,
296 sample_rate: 48_000,
297 channels: 1,
298 };
299 let options = Options {
300 track: Some("audio".to_string()),
301 ..Options::default()
302 };
303 let mut producer = Producer::new(&mut broadcast, catalog, input.clone(), &options).unwrap();
304 let catalog = Encoder::new(&crate::encode::Config::new(input)).unwrap().catalog();
305 let mut consumer = Consumer::new(
306 &subscriber,
307 &catalog,
308 "audio",
309 Config {
310 channels: Some(2),
311 ..Config::new()
312 },
313 )
314 .await
315 .unwrap();
316
317 let samples = vec![0.1f32; 960];
318 let mut data = Vec::with_capacity(samples.len() * size_of::<f32>());
319 for sample in samples {
320 data.extend_from_slice(&sample.to_le_bytes());
321 }
322 producer
323 .write(&Frame {
324 timestamp: Timestamp::ZERO,
325 data: data.into(),
326 })
327 .unwrap();
328
329 let frame = consumer.read().await.unwrap().expect("decoded frame");
330 let samples = Format::F32.as_interleaved_f32(&frame.data, 2).unwrap();
331 assert_eq!(samples.len(), (960 - 312) * 2);
332 for pair in samples.chunks_exact(2) {
333 assert_eq!(pair[0], pair[1]);
334 }
335 }
336
337 #[tokio::test]
344 async fn resampled_timestamps_follow_the_samples() {
345 let mut broadcast = moq_net::broadcast::Info::new().produce();
346 let track = broadcast.create_track("audio", hang::container::track_info()).unwrap();
347 let subscriber = broadcast.consume();
348
349 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 44_100, 1);
350 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
351
352 let mut consumer = Consumer::new(
353 &subscriber,
354 &catalog,
355 "audio",
356 Config {
357 sample_rate: Some(48_000),
358 ..Config::new()
359 },
360 )
361 .await
362 .unwrap();
363
364 const FRAMES: u64 = 1024;
366 let payload: Bytes = vec![0u8; FRAMES as usize * size_of::<f32>()].into();
367 for packet in 0..2 {
368 producer
369 .write(moq_mux::container::Frame {
370 timestamp: moq_net::Timestamp::from_scale(packet * FRAMES, 44_100).unwrap(),
371 duration: None,
372 payload: payload.clone(),
373 keyframe: true,
374 })
375 .unwrap();
376 }
377
378 let first = consumer.read().await.unwrap().expect("decoded frame");
379 assert_eq!(first.timestamp.as_micros(), 0);
380
381 let second = consumer.read().await.unwrap().expect("decoded frame");
387 let first_frames = (first.data.len() / size_of::<f32>()) as u128;
388 let ends_at = first_frames * 1_000_000 / 48_000;
389 let gap = second.timestamp.as_micros().abs_diff(ends_at);
390 assert!(gap < 100, "expected the frames to meet, got a {gap} us gap");
391 }
392
393 #[tokio::test]
397 async fn resampled_tail_survives_the_end_of_the_track() {
398 let mut broadcast = moq_net::broadcast::Info::new().produce();
399 let track = broadcast.create_track("audio", hang::container::track_info()).unwrap();
400 let subscriber = broadcast.consume();
401
402 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 44_100, 1);
403 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
404
405 let mut consumer = Consumer::new(
406 &subscriber,
407 &catalog,
408 "audio",
409 Config {
410 sample_rate: Some(48_000),
411 ..Config::new()
412 },
413 )
414 .await
415 .unwrap();
416
417 const FRAMES: usize = 1024;
419 let payload: Bytes = vec![0u8; FRAMES * size_of::<f32>()].into();
420 producer
421 .write(moq_mux::container::Frame {
422 timestamp: moq_net::Timestamp::ZERO,
423 duration: None,
424 payload,
425 keyframe: true,
426 })
427 .unwrap();
428 producer.finish().unwrap();
429
430 let first = consumer.read().await.unwrap().expect("decoded frame");
431 let first_frames = first.data.len() / size_of::<f32>();
432
433 let tail = consumer.read().await.unwrap().expect("flushed tail");
434 let tail_frames = tail.data.len() / size_of::<f32>();
435
436 assert!((215..=230).contains(&tail_frames), "unexpected tail: {tail_frames}");
440 let ends_at = (first_frames as u128) * 1_000_000 / 48_000;
443 let gap = tail.timestamp.as_micros().abs_diff(ends_at);
444 assert!(gap < 100, "expected the tail to meet the body, got a {gap} us gap");
445
446 let total = first_frames + tail_frames;
450 assert!((1105..=1120).contains(&total), "unexpected total: {total}");
451 assert!(consumer.read().await.unwrap().is_none());
452 }
453
454 #[tokio::test]
455 async fn reads_the_container_the_catalog_declares() {
456 let mut broadcast = moq_net::broadcast::Info::new().produce();
457 let track = broadcast.create_track("audio", hang::container::track_info()).unwrap();
458 let subscriber = broadcast.consume();
459
460 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 1);
461 catalog.container = hang::catalog::Container::Loc;
462
463 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Loc);
464 let mut consumer = Consumer::new(
465 &subscriber,
466 &catalog,
467 "audio",
468 Config {
469 format: Format::F32,
470 ..Config::new()
471 },
472 )
473 .await
474 .unwrap();
475
476 let samples = [0.25f32, -0.5, 0.75, -1.0];
477 let payload: Vec<u8> = samples.iter().flat_map(|sample| sample.to_le_bytes()).collect();
478 producer
479 .write(moq_mux::container::Frame {
480 timestamp: Timestamp::ZERO,
481 duration: None,
482 payload: payload.into(),
483 keyframe: true,
484 })
485 .unwrap();
486
487 let frame = consumer.read().await.unwrap().expect("decoded frame");
488 assert_eq!(
489 Format::F32.as_interleaved_f32(&frame.data, 1).unwrap().as_ref(),
490 samples
491 );
492 }
493
494 #[tokio::test]
499 async fn decodes_a_cmaf_framed_track() {
500 let input = Input {
501 format: Format::F32,
502 sample_rate: 48_000,
503 channels: 2,
504 };
505
506 let mut encoder = Encoder::new(&crate::encode::Config::new(input.clone())).unwrap();
508 let mut catalog = encoder.catalog();
509 let pcm = vec![0.0f32; encoder.frame_size() * encoder.codec_channels() as usize];
510 let packet = encoder.encode(&pcm).unwrap();
511
512 let muxer = moq_mux::container::fmp4::Muxer::audio(&catalog).unwrap();
514 let init = muxer.init().unwrap().expect("an out-of-band codec has an init segment");
515 catalog.container = hang::catalog::Container::Cmaf { init };
516
517 let mut broadcast = moq_net::broadcast::Info::new().produce();
518 let subscriber = broadcast.consume();
519 let track = broadcast.create_track("audio", hang::container::track_info()).unwrap();
520 let container = moq_mux::catalog::hang::Container::try_from(&catalog.container).unwrap();
521 let mut producer = moq_mux::container::Producer::new(track, container);
522
523 let mut consumer = Consumer::new(&subscriber, &catalog, "audio", Config::new())
524 .await
525 .unwrap();
526
527 producer
528 .write(moq_mux::container::Frame {
529 timestamp: Timestamp::ZERO,
530 payload: packet,
531 keyframe: true,
532 duration: None,
533 })
534 .unwrap();
535 producer.cut(None).unwrap();
536
537 let frame = consumer.read().await.unwrap().expect("decoded frame");
541 assert_eq!(frame.timestamp.as_micros(), 0);
544 let samples = Format::F32.as_interleaved_f32(&frame.data, 2).unwrap();
545 assert_eq!(samples.len(), (960 - 312) * 2);
546 }
547}