1use std::time::Duration;
4
5use bytes::Bytes;
6
7use moq_mux::catalog::hang::CatalogExt;
8use moq_mux::container::Frame as MuxFrame;
9use moq_net::Timestamp;
10
11use super::encoder::{Codec, Config, Encoder, Input};
12use crate::resample::Resampler;
13use crate::{Error, Frame};
14
15#[derive(Clone, Debug)]
24#[non_exhaustive]
25pub struct Options {
26 pub track: Option<String>,
30 pub codec: Codec,
32 pub sample_rate: Option<u32>,
35 pub channels: Option<u32>,
38 pub bitrate: Option<u32>,
41 pub fec: bool,
43 pub dtx: bool,
45 pub frame_duration: Duration,
48}
49
50impl Default for Options {
51 fn default() -> Self {
52 Self {
53 track: None,
54 codec: Codec::default(),
55 sample_rate: None,
56 channels: None,
57 bitrate: None,
58 fec: false,
59 dtx: false,
60 frame_duration: Duration::from_millis(20),
61 }
62 }
63}
64
65impl Options {
66 fn config(&self, input: Input) -> Config {
68 Config {
69 input,
70 codec: self.codec,
71 sample_rate: self.sample_rate,
72 channels: self.channels,
73 bitrate: self.bitrate,
74 fec: self.fec,
75 dtx: self.dtx,
76 frame_duration: self.frame_duration,
77 }
78 }
79}
80
81pub struct Producer<E: CatalogExt = ()> {
91 encoder: Encoder,
92 resampler: Option<Resampler>,
93 track: moq_mux::container::Producer<moq_mux::container::legacy::Wire>,
94 rendition: Rendition<E>,
96 pending: Vec<f32>,
97 frames_produced: u64,
99 epoch_us: Option<u64>,
103 pending_discontinuity: bool,
105 decoder_boundary: bool,
107}
108
109struct Terminal {
110 packets: Vec<Bytes>,
111 end: Timestamp,
112 start: Timestamp,
113 frame_size: usize,
114 codec_rate: u32,
115}
116
117impl<E: CatalogExt> Producer<E> {
118 pub fn new(
121 broadcast: &mut moq_net::broadcast::Producer,
122 catalog: moq_mux::catalog::Producer<E>,
123 input: Input,
124 options: &Options,
125 ) -> Result<Self, Error> {
126 let encoder = Encoder::new(&options.config(input))?;
127 let input = &encoder.config().input;
128
129 let resampler = if input.sample_rate == encoder.codec_rate() {
130 None
131 } else {
132 let chunk_frames =
135 ((input.sample_rate as u128 * encoder.config().frame_duration.as_micros()) / 1_000_000) as usize;
136 Some(Resampler::new(
137 input.sample_rate,
138 encoder.codec_rate(),
139 input.channels,
140 chunk_frames,
141 )?)
142 };
143
144 let track = match &options.track {
145 Some(name) => broadcast.create_track(name.clone(), catalog.track_info())?,
149 None => broadcast.unique_track(&format!(".{}", options.codec), catalog.track_info())?,
152 };
153 let name = track.name().to_string();
154 let track = catalog.media_producer(track, moq_mux::container::legacy::Wire)?;
155
156 let mut catalog_mut = catalog.clone();
157 let mut config = encoder.catalog();
158 config.timeline = Some(catalog.timeline(&name)?.section());
159 catalog_mut.lock().audio.insert(&name, config)?;
160
161 Ok(Self {
162 encoder,
163 resampler,
164 track,
165 rendition: Rendition { catalog, name },
166 pending: Vec::new(),
167 frames_produced: 0,
168 epoch_us: None,
169 pending_discontinuity: false,
170 decoder_boundary: true,
171 })
172 }
173
174 pub fn track_name(&self) -> &str {
176 &self.rendition.name
177 }
178
179 pub fn track(&self) -> &moq_net::track::Producer {
182 self.track.track()
183 }
184
185 pub fn bitrate(&self) -> u64 {
187 self.encoder.bitrate()
188 }
189
190 pub fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
192 self.encoder.set_bitrate(bitrate)
193 }
194
195 pub fn reset_epoch(&mut self) {
203 if self.encoder.started() && !self.decoder_boundary {
204 self.pending_discontinuity = true;
205 }
206 self.reset_state();
207 }
208
209 fn reset_state(&mut self) {
210 self.epoch_us = None;
211 self.frames_produced = 0;
212 self.pending.clear();
213 self.encoder.reset();
214 if let Some(resampler) = self.resampler.as_mut() {
219 resampler.reset();
220 }
221 }
222
223 pub fn write(&mut self, frame: &Frame) -> Result<(), Error> {
235 if self.pending_discontinuity {
236 self.track.discontinuity()?;
237 self.pending_discontinuity = false;
238 self.decoder_boundary = true;
239 }
240
241 let timestamp_us = u64::try_from(frame.timestamp.as_micros())
242 .map_err(|_| Error::Unsupported(format!("frame timestamp {:?} out of range", frame.timestamp)))?;
243 let epoch_us = *self.epoch_us.get_or_insert(timestamp_us);
244
245 let input = &self.encoder.config().input;
246 let (format, channels) = (input.format, input.channels);
247 let pcm = format.as_interleaved_f32(frame.data.as_ref(), channels)?;
248 let pcm: Vec<f32> = match self.resampler.as_mut() {
249 Some(r) => r.process(&pcm)?,
250 None => pcm.into_owned(),
251 };
252
253 self.pending.extend(pcm);
254
255 self.publish_full_frames(epoch_us)
256 }
257
258 fn publish_full_frames(&mut self, epoch_us: u64) -> Result<(), Error> {
261 let frame_samples = self.encoder.frame_size() * self.encoder.codec_channels() as usize;
262 while self.pending.len() >= frame_samples {
263 let chunk: Vec<f32> = self.pending.drain(..frame_samples).collect();
264 let packet = self.encoder.encode(&chunk)?;
265
266 let timestamp = Self::timestamp(epoch_us, self.frames_produced, self.encoder.codec_rate())?;
267 self.frames_produced += self.encoder.frame_size() as u64;
268 Self::publish(&mut self.track, packet, timestamp)?;
269 self.decoder_boundary = false;
270 }
271
272 Ok(())
273 }
274
275 fn timestamp(epoch_us: u64, frames_produced: u64, codec_rate: u32) -> Result<Timestamp, Error> {
277 let offset_us = (frames_produced * 1_000_000) / codec_rate as u64;
278 Ok(Timestamp::from_micros(epoch_us + offset_us)?)
279 }
280
281 fn publish(
282 track: &mut moq_mux::container::Producer<moq_mux::container::legacy::Wire>,
283 payload: Bytes,
284 timestamp: Timestamp,
285 ) -> Result<(), Error> {
286 let mux_frame = MuxFrame {
290 timestamp,
291 payload,
292 keyframe: true,
293 duration: None,
294 };
295 track.write(mux_frame)?;
296 track.cut(None)?;
299 Ok(())
300 }
301
302 fn publish_terminal(
304 track: &mut moq_mux::container::Producer<moq_mux::container::legacy::Wire>,
305 terminal: Terminal,
306 ) -> Result<(), Error> {
307 track.write(MuxFrame {
308 timestamp: terminal.end,
309 payload: Bytes::new(),
310 keyframe: true,
311 duration: None,
312 })?;
313
314 for (index, packet) in terminal.packets.into_iter().enumerate() {
315 let offset = Timestamp::from_scale((index * terminal.frame_size) as u64, terminal.codec_rate as u64)?
316 .convert(terminal.start.scale())?;
317 track.write(MuxFrame {
318 timestamp: terminal.start.checked_add(offset)?,
319 payload: packet,
320 keyframe: false,
321 duration: None,
322 })?;
323 }
324
325 track.cut(Some(terminal.end))?;
326 Ok(())
327 }
328
329 pub fn discontinuity(&mut self) -> Result<(), Error> {
336 self.track.discontinuity()?;
337 self.pending_discontinuity = false;
338 self.decoder_boundary = true;
339 self.reset_state();
340 Ok(())
341 }
342
343 pub fn finish(mut self) -> Result<(), Error> {
346 if let Some(resampler) = self.resampler.take() {
350 self.pending.extend(resampler.flush()?);
351 }
352
353 let epoch_us = self.epoch_us.unwrap_or(0);
356 self.publish_full_frames(epoch_us)?;
357
358 let frame_size = self.encoder.frame_size();
359 let codec_rate = self.encoder.codec_rate();
360 let channels = self.encoder.codec_channels() as usize;
361 let source_frames = self.pending.len() / channels;
362 let start = Self::timestamp(epoch_us, self.frames_produced, codec_rate)?;
363 let end = Self::timestamp(epoch_us, self.frames_produced + source_frames as u64, codec_rate)?;
364 let finish = self.encoder.finish(&self.pending)?;
365 let discard_padding = finish.discard_padding();
366 let packets = finish.into_packets();
367
368 if discard_padding > 0 {
369 Self::publish_terminal(
370 &mut self.track,
371 Terminal {
372 packets,
373 end,
374 start,
375 frame_size,
376 codec_rate,
377 },
378 )?;
379 } else {
380 for packet in packets {
381 let timestamp = Self::timestamp(epoch_us, self.frames_produced, codec_rate)?;
382 Self::publish(&mut self.track, packet, timestamp)?;
383 self.frames_produced += frame_size as u64;
384 }
385 }
386
387 self.track.finish()?;
388 Ok(())
389 }
390
391 pub fn abort(self, err: moq_net::Error) {
394 self.track.abort(err);
395 }
396}
397
398struct Rendition<E: CatalogExt> {
403 catalog: moq_mux::catalog::Producer<E>,
404 name: String,
405}
406
407impl<E: CatalogExt> Drop for Rendition<E> {
408 fn drop(&mut self) {
409 self.catalog.lock().audio.remove(&self.name);
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use crate::Format;
417 use crate::decode::{Config as DecodeConfig, Consumer as AudioConsumer};
418
419 #[tokio::test]
421 async fn finish_publishes_the_opus_lookahead_tail() {
422 for frames in [960, 860] {
423 let input = Input {
424 format: Format::F32,
425 sample_rate: 48_000,
426 channels: 1,
427 };
428 let options = Options {
429 track: Some("audio".to_string()),
430 bitrate: Some(128_000),
431 ..Options::default()
432 };
433 let decoder_config = Encoder::new(&options.config(input.clone())).unwrap().catalog();
434
435 let mut broadcast = moq_net::broadcast::Info::new().produce();
436 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
437 let consumer = broadcast.consume();
438 let mut producer = Producer::new(&mut broadcast, catalog, input, &options).unwrap();
439 let mut audio = AudioConsumer::new(&consumer, &decoder_config, "audio", DecodeConfig::new())
440 .await
441 .unwrap();
442
443 let mut pcm = vec![0.0f32; frames];
444 let impulse = pcm.len() - 100;
445 pcm[impulse] = 1.0;
446 let data: Vec<u8> = pcm.iter().flat_map(|sample| sample.to_le_bytes()).collect();
447 producer
448 .write(&Frame {
449 timestamp: Timestamp::ZERO,
450 data: Bytes::from(data),
451 })
452 .unwrap();
453 producer.finish().unwrap();
454
455 let mut decoded = Vec::new();
456 while let Some(frame) = audio.read().await.unwrap() {
457 let pcm = Format::F32.as_interleaved_f32(&frame.data, 1).unwrap();
458 decoded.extend_from_slice(&pcm);
459 }
460 assert_eq!(decoded.len(), frames, "terminal padding extended the source");
461 let peak = decoded.iter().fold(0.0f32, |peak, sample| peak.max(sample.abs()));
462 assert!(peak > 0.1, "the {frames}-frame Opus tail lost the impulse: peak {peak}");
463 }
464 }
465
466 #[tokio::test]
470 async fn finish_publishes_the_resampled_tail() {
471 let input = Input {
472 format: Format::F32,
473 sample_rate: 44_100,
474 channels: 1,
475 };
476
477 let mut broadcast = moq_net::broadcast::Info::new().produce();
478 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
479 let consumer = broadcast.consume();
480 let options = Options {
481 track: Some("audio".to_string()),
482 ..Options::default()
483 };
484 let mut producer = Producer::new(&mut broadcast, catalog, input.clone(), &options).unwrap();
485
486 let mut track = moq_mux::container::Consumer::new(
488 consumer
489 .track("audio")
490 .unwrap()
491 .subscribe(moq_net::track::Subscription::default())
492 .await
493 .unwrap(),
494 moq_mux::catalog::hang::Container::Legacy,
495 );
496
497 let data: Vec<u8> = vec![0.25f32; 8_838].iter().flat_map(|s| s.to_le_bytes()).collect();
501 producer
502 .write(&Frame {
503 timestamp: moq_net::Timestamp::ZERO,
504 data: data.into(),
505 })
506 .unwrap();
507 producer.finish().unwrap();
508
509 let mut packets = 0;
510 while track.read().await.unwrap().is_some() {
511 packets += 1;
512 }
513 assert_eq!(packets, 11);
514 }
515
516 #[tokio::test]
520 async fn reset_epoch_drops_the_resampler_buffer_too() {
521 let input = Input {
522 format: Format::F32,
523 sample_rate: 44_100,
524 channels: 1,
525 };
526
527 let mut broadcast = moq_net::broadcast::Info::new().produce();
528 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
529 let consumer = broadcast.consume();
530 let options = Options {
531 track: Some("audio".to_string()),
532 ..Options::default()
533 };
534 let mut producer = Producer::new(&mut broadcast, catalog, input.clone(), &options).unwrap();
535
536 let mut track = moq_mux::container::Consumer::new(
537 consumer
538 .track("audio")
539 .unwrap()
540 .subscribe(moq_net::track::Subscription::default())
541 .await
542 .unwrap(),
543 moq_mux::catalog::hang::Container::Legacy,
544 );
545
546 let data: Vec<u8> = vec![0.25f32; 441].iter().flat_map(|s| s.to_le_bytes()).collect();
548 producer
549 .write(&Frame {
550 timestamp: moq_net::Timestamp::ZERO,
551 data: data.into(),
552 })
553 .unwrap();
554
555 producer.reset_epoch();
556 producer.finish().unwrap();
557
558 assert!(track.read().await.unwrap().is_none());
560 }
561
562 #[tokio::test]
564 async fn reset_epoch_drops_the_encoder_lookahead() {
565 let mut broadcast = moq_net::broadcast::Info::new().produce();
566 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
567 let consumer = broadcast.consume();
568 let options = Options {
569 track: Some("audio".to_string()),
570 ..Options::default()
571 };
572 let mut producer = Producer::new(
573 &mut broadcast,
574 catalog,
575 Input {
576 channels: 1,
577 ..Input::default()
578 },
579 &options,
580 )
581 .unwrap();
582 let mut track = moq_mux::container::Consumer::new(
583 consumer
584 .track("audio")
585 .unwrap()
586 .subscribe(moq_net::track::Subscription::default())
587 .await
588 .unwrap(),
589 moq_mux::catalog::hang::Container::Legacy,
590 );
591
592 producer.write(&full_frame(1_000_000)).unwrap();
593 producer.reset_epoch();
594 producer.finish().unwrap();
595
596 assert!(track.read().await.unwrap().is_some());
597 assert!(track.read().await.unwrap().is_none());
598 }
599
600 #[tokio::test]
602 async fn reset_epoch_restarts_the_decoder() {
603 let input = Input {
604 format: Format::F32,
605 sample_rate: 48_000,
606 channels: 1,
607 };
608 let options = Options {
609 track: Some("audio".to_string()),
610 ..Options::default()
611 };
612 let decoder_config = Encoder::new(&options.config(input.clone())).unwrap().catalog();
613
614 let mut broadcast = moq_net::broadcast::Info::new().produce();
615 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
616 let subscriber = broadcast.consume();
617 let mut producer = Producer::new(&mut broadcast, catalog, input, &options).unwrap();
618 let mut audio = AudioConsumer::new(
619 &subscriber,
620 &decoder_config,
621 "audio",
622 DecodeConfig {
623 latency_max: Some(Duration::from_millis(500)),
624 ..DecodeConfig::new()
625 },
626 )
627 .await
628 .unwrap();
629
630 producer.write(&full_frame(0)).unwrap();
631 let first = audio.read().await.unwrap().expect("first epoch packet");
632 assert_eq!(first.data.len() / size_of::<f32>(), 960 - 312);
633
634 producer.reset_epoch();
635 producer.write(&full_frame(1_000_000)).unwrap();
636 producer.finish().unwrap();
637
638 let mut resumed_frames = 0;
639 while let Some(frame) = audio.read().await.unwrap() {
640 assert!(frame.timestamp.as_micros() >= 1_000_000);
641 resumed_frames += frame.data.len() / size_of::<f32>();
642 }
643 assert_eq!(resumed_frames, 960, "the resumed epoch must trim its own pre-skip once");
644 }
645
646 fn full_frame(timestamp_us: u64) -> Frame {
649 let mut data = Vec::with_capacity(960 * 4);
650 for _ in 0..960 {
651 data.extend_from_slice(&0.1f32.to_le_bytes());
652 }
653 Frame {
654 timestamp: Timestamp::from_micros(timestamp_us).unwrap(),
655 data: data.into(),
656 }
657 }
658
659 async fn published_pts(frames: &[Frame], reset_before: Option<usize>) -> Vec<u128> {
663 let mut broadcast = moq_net::broadcast::Info::new().produce();
664 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
665 let consumer = broadcast.consume();
666
667 let input = Input {
670 format: Format::F32,
671 sample_rate: 48_000,
672 channels: 1,
673 };
674 let options = Options {
675 track: Some("audio".to_string()),
676 ..Options::default()
677 };
678 let mut producer = Producer::new(&mut broadcast, catalog, input, &options).unwrap();
679
680 let track = consumer.track("audio").unwrap().subscribe(None).await.unwrap();
681 let mut reader = moq_mux::container::Consumer::new(track, moq_mux::container::legacy::Wire);
682
683 let mut pts = Vec::new();
684 for (i, frame) in frames.iter().enumerate() {
685 if reset_before == Some(i) {
686 producer.reset_epoch();
687 }
688 producer.write(frame).unwrap();
689 let read = reader.read().await.unwrap().expect("a packet per full frame");
690 pts.push(read.timestamp.as_micros());
691 }
692 pts
693 }
694
695 #[tokio::test]
696 async fn epoch_anchors_to_first_frame_timestamp() {
697 let pts = published_pts(&[full_frame(1_000_000)], None).await;
700 assert_eq!(pts, vec![1_000_000]);
701 }
702
703 #[tokio::test]
704 async fn pts_advances_by_frame_duration_ignoring_later_timestamps() {
705 let pts = published_pts(&[full_frame(1_000), full_frame(999_999)], None).await;
708 assert_eq!(pts, vec![1_000, 1_000 + 20_000]);
709 }
710
711 #[tokio::test]
712 async fn reset_epoch_reanchors_so_the_gap_lands_in_pts() {
713 let pts = published_pts(&[full_frame(0), full_frame(5_000_000)], Some(1)).await;
716 assert_eq!(pts, vec![0, 5_000_000]);
717 }
718
719 #[tokio::test]
723 async fn default_options_derive_the_track_name() {
724 let mut broadcast = moq_net::broadcast::Info::new().produce();
725 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
726
727 let first = Producer::new(&mut broadcast, catalog.clone(), Input::default(), &Options::default()).unwrap();
728 assert_eq!(first.track_name(), "0.opus");
729
730 let second = Producer::new(&mut broadcast, catalog, Input::default(), &Options::default()).unwrap();
731 assert_eq!(second.track_name(), "1.opus");
732 }
733}