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::encoded::Encoded;
12use super::encoder::{Codec, Config, Encoder, Input};
13use crate::resample::Resampler;
14use crate::{Activity, Error, Frame};
15
16#[derive(Clone, Debug)]
25#[non_exhaustive]
26pub struct Options {
27 pub track: Option<String>,
31 pub codec: Codec,
33 pub sample_rate: Option<u32>,
36 pub channels: Option<u32>,
39 pub bitrate: Option<u32>,
42 pub fec: bool,
44 pub dtx: bool,
46 pub frame_duration: Duration,
49}
50
51impl Default for Options {
52 fn default() -> Self {
53 Self {
54 track: None,
55 codec: Codec::default(),
56 sample_rate: None,
57 channels: None,
58 bitrate: None,
59 fec: false,
60 dtx: false,
61 frame_duration: Duration::from_millis(20),
62 }
63 }
64}
65
66impl Options {
67 fn config(&self, input: Input) -> Config {
69 Config {
70 input,
71 codec: self.codec,
72 sample_rate: self.sample_rate,
73 channels: self.channels,
74 bitrate: self.bitrate,
75 fec: self.fec,
76 dtx: self.dtx,
77 frame_duration: self.frame_duration,
78 }
79 }
80}
81
82pub struct Producer<E: CatalogExt = ()> {
92 encoder: Encoder,
93 resampler: Option<Resampler>,
94 track: moq_mux::container::Producer<moq_mux::container::legacy::Wire>,
95 rendition: Rendition<E>,
97 pending: Vec<f32>,
98 frames_produced: u64,
100 epoch_us: Option<u64>,
104 pending_discontinuity: bool,
106 decoder_boundary: bool,
108 activity: Activity,
110}
111
112struct Terminal {
113 packets: Vec<Encoded>,
114 end: Timestamp,
115 start: Timestamp,
116 frame_size: usize,
117 codec_rate: u32,
118}
119
120pub(crate) struct Reserved<E: CatalogExt = ()> {
128 catalog: moq_mux::catalog::Producer<E>,
129 track: moq_mux::container::Producer<moq_mux::container::legacy::Wire>,
130 name: String,
131}
132
133impl<E: CatalogExt> Reserved<E> {
134 pub(crate) fn new(
135 broadcast: &mut moq_net::broadcast::Producer,
136 catalog: moq_mux::catalog::Producer<E>,
137 options: &Options,
138 ) -> Result<Self, Error> {
139 let track = match &options.track {
140 Some(name) => broadcast.create_track(name.clone(), catalog.track_info())?,
144 None => broadcast.unique_track(&format!(".{}", options.codec), catalog.track_info())?,
147 };
148 let name = track.name().to_string();
149 let track = catalog.media_producer(track, moq_mux::container::legacy::Wire)?;
150
151 Ok(Self { catalog, track, name })
152 }
153
154 pub(crate) fn register(&mut self, input: Input, options: &Options) -> Result<Registered, Error> {
160 let encoder = Encoder::new(&options.config(input))?;
161 let input = &encoder.config().input;
162
163 let resampler = if input.sample_rate == encoder.codec_rate() {
164 None
165 } else {
166 let chunk_frames =
169 ((input.sample_rate as u128 * encoder.config().frame_duration.as_micros()) / 1_000_000) as usize;
170 Some(Resampler::new(
171 input.sample_rate,
172 encoder.codec_rate(),
173 input.channels,
174 chunk_frames,
175 )?)
176 };
177
178 let mut config = encoder.catalog();
179 config.timeline = Some(self.catalog.timeline(&self.name)?.section());
180 self.catalog.lock().audio.insert(&self.name, config)?;
181
182 Ok(Registered { encoder, resampler })
183 }
184
185 pub(crate) fn encode(self, registered: Registered) -> Producer<E> {
187 Producer {
188 encoder: registered.encoder,
189 resampler: registered.resampler,
190 track: self.track,
191 rendition: Rendition {
192 catalog: self.catalog,
193 name: self.name,
194 },
195 pending: Vec::new(),
196 frames_produced: 0,
197 epoch_us: None,
198 pending_discontinuity: false,
199 decoder_boundary: true,
200 activity: Activity::Active,
201 }
202 }
203}
204
205pub(crate) struct Registered {
210 encoder: Encoder,
211 resampler: Option<Resampler>,
212}
213
214#[cfg(feature = "capture")]
216impl<E: CatalogExt> Reserved<E> {
217 pub(crate) fn name(&self) -> &str {
219 &self.name
220 }
221
222 pub(crate) fn track(&self) -> &moq_net::track::Producer {
224 self.track.track()
225 }
226
227 pub(crate) fn finish(mut self) -> Result<(), Error> {
229 self.track.finish()?;
230 Ok(())
231 }
232
233 pub(crate) fn abort(self, err: moq_net::Error) {
235 self.track.abort(err);
236 }
237}
238
239impl<E: CatalogExt> Producer<E> {
240 pub fn new(
243 broadcast: &mut moq_net::broadcast::Producer,
244 catalog: moq_mux::catalog::Producer<E>,
245 input: Input,
246 options: &Options,
247 ) -> Result<Self, Error> {
248 let mut reserved = Reserved::new(broadcast, catalog, options)?;
249 let registered = reserved.register(input, options)?;
250 Ok(reserved.encode(registered))
251 }
252
253 pub fn track_name(&self) -> &str {
255 &self.rendition.name
256 }
257
258 pub fn track(&self) -> &moq_net::track::Producer {
261 self.track.track()
262 }
263
264 pub fn activity(&self) -> Activity {
273 self.activity
274 }
275
276 pub fn bitrate(&self) -> u64 {
278 self.encoder.bitrate()
279 }
280
281 pub fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
283 self.encoder.set_bitrate(bitrate)
284 }
285
286 pub fn reset_epoch(&mut self) {
294 if self.encoder.started() && !self.decoder_boundary {
295 self.pending_discontinuity = true;
296 }
297 self.reset_state();
298 }
299
300 fn reset_state(&mut self) {
301 self.epoch_us = None;
302 self.activity = Activity::Active;
303 self.frames_produced = 0;
304 self.pending.clear();
305 self.encoder.reset();
306 if let Some(resampler) = self.resampler.as_mut() {
311 resampler.reset();
312 }
313 }
314
315 pub fn write(&mut self, frame: &Frame) -> Result<(), Error> {
330 if self.pending_discontinuity {
331 self.track.discontinuity()?;
332 self.pending_discontinuity = false;
333 self.decoder_boundary = true;
334 }
335
336 let timestamp_us = u64::try_from(frame.timestamp.as_micros())
337 .map_err(|_| Error::Unsupported(format!("frame timestamp {:?} out of range", frame.timestamp)))?;
338 let epoch_us = *self.epoch_us.get_or_insert(timestamp_us);
339
340 let input = &self.encoder.config().input;
341 let (format, channels) = (input.format, input.channels);
342 let pcm = format.as_interleaved_f32(frame.data.as_ref(), channels)?;
343 let pcm: Vec<f32> = match self.resampler.as_mut() {
344 Some(r) => r.process(&pcm)?,
345 None => pcm.into_owned(),
346 };
347
348 self.pending.extend(pcm);
349
350 self.publish_full_frames(epoch_us)
351 }
352
353 fn publish_full_frames(&mut self, epoch_us: u64) -> Result<(), Error> {
356 let frame_samples = self.encoder.frame_size() * self.encoder.codec_channels() as usize;
357 while self.pending.len() >= frame_samples {
358 let chunk: Vec<f32> = self.pending.drain(..frame_samples).collect();
359 let packet = self.encoder.encode(&chunk)?;
360
361 let timestamp = Self::timestamp(epoch_us, self.frames_produced, self.encoder.codec_rate())?;
362 self.frames_produced += self.encoder.frame_size() as u64;
363 self.activity = packet.activity;
364 Self::publish(&mut self.track, packet, timestamp)?;
365 self.decoder_boundary = false;
366 }
367
368 Ok(())
369 }
370
371 fn timestamp(epoch_us: u64, frames_produced: u64, codec_rate: u32) -> Result<Timestamp, Error> {
373 let offset_us = (frames_produced * 1_000_000) / codec_rate as u64;
374 Ok(Timestamp::from_micros(epoch_us + offset_us)?)
375 }
376
377 fn publish(
378 track: &mut moq_mux::container::Producer<moq_mux::container::legacy::Wire>,
379 encoded: Encoded,
380 timestamp: Timestamp,
381 ) -> Result<(), Error> {
382 let mux_frame = MuxFrame {
386 timestamp,
387 payload: encoded.payload,
388 keyframe: true,
389 duration: None,
390 };
391 track.write(mux_frame)?;
392 track.cut(None)?;
395 Ok(())
396 }
397
398 fn publish_terminal(
400 track: &mut moq_mux::container::Producer<moq_mux::container::legacy::Wire>,
401 terminal: Terminal,
402 ) -> Result<(), Error> {
403 track.write(MuxFrame {
404 timestamp: terminal.end,
405 payload: Bytes::new(),
406 keyframe: true,
407 duration: None,
408 })?;
409
410 for (index, packet) in terminal.packets.into_iter().enumerate() {
411 let offset = Timestamp::from_scale((index * terminal.frame_size) as u64, terminal.codec_rate as u64)?
412 .convert(terminal.start.scale())?;
413 track.write(MuxFrame {
414 timestamp: terminal.start.checked_add(offset)?,
415 payload: packet.payload,
416 keyframe: false,
417 duration: None,
418 })?;
419 }
420
421 track.cut(Some(terminal.end))?;
422 Ok(())
423 }
424
425 pub fn discontinuity(&mut self) -> Result<(), Error> {
432 self.track.discontinuity()?;
433 self.pending_discontinuity = false;
434 self.decoder_boundary = true;
435 self.reset_state();
436 Ok(())
437 }
438
439 pub fn finish(mut self) -> Result<(), Error> {
442 if let Some(resampler) = self.resampler.take() {
446 self.pending.extend(resampler.flush()?);
447 }
448
449 let epoch_us = self.epoch_us.unwrap_or(0);
452 self.publish_full_frames(epoch_us)?;
453
454 let frame_size = self.encoder.frame_size();
455 let codec_rate = self.encoder.codec_rate();
456 let channels = self.encoder.codec_channels() as usize;
457 let source_frames = self.pending.len() / channels;
458 let start = Self::timestamp(epoch_us, self.frames_produced, codec_rate)?;
459 let end = Self::timestamp(epoch_us, self.frames_produced + source_frames as u64, codec_rate)?;
460 let finish = self.encoder.finish(&self.pending)?;
461 let discard_padding = finish.discard_padding();
462 let packets = finish.into_packets();
463
464 if discard_padding > 0 {
465 Self::publish_terminal(
466 &mut self.track,
467 Terminal {
468 packets,
469 end,
470 start,
471 frame_size,
472 codec_rate,
473 },
474 )?;
475 } else {
476 for packet in packets {
477 let timestamp = Self::timestamp(epoch_us, self.frames_produced, codec_rate)?;
478 self.activity = packet.activity;
479 Self::publish(&mut self.track, packet, timestamp)?;
480 self.frames_produced += frame_size as u64;
481 }
482 }
483
484 self.track.finish()?;
485 Ok(())
486 }
487
488 pub fn abort(self, err: moq_net::Error) {
491 self.track.abort(err);
492 }
493}
494
495struct Rendition<E: CatalogExt> {
500 catalog: moq_mux::catalog::Producer<E>,
501 name: String,
502}
503
504impl<E: CatalogExt> Drop for Rendition<E> {
505 fn drop(&mut self) {
506 self.catalog.lock().audio.remove(&self.name);
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513 use crate::decode::{Config as DecodeConfig, Consumer as AudioConsumer};
514 use crate::{Activity, Format};
515
516 #[tokio::test]
518 async fn finish_publishes_the_opus_lookahead_tail() {
519 for frames in [960, 860] {
520 let input = Input {
521 format: Format::F32,
522 sample_rate: 48_000,
523 channels: 1,
524 };
525 let options = Options {
526 track: Some("audio".to_string()),
527 bitrate: Some(128_000),
528 ..Options::default()
529 };
530 let decoder_config = Encoder::new(&options.config(input.clone())).unwrap().catalog();
531
532 let mut broadcast = moq_net::broadcast::Info::new().produce();
533 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
534 let consumer = broadcast.consume();
535 let mut producer = Producer::new(&mut broadcast, catalog, input, &options).unwrap();
536 let mut audio = AudioConsumer::new(&consumer, &decoder_config, "audio", DecodeConfig::new())
537 .await
538 .unwrap();
539
540 let mut pcm = vec![0.0f32; frames];
541 let impulse = pcm.len() - 100;
542 pcm[impulse] = 1.0;
543 let data: Vec<u8> = pcm.iter().flat_map(|sample| sample.to_le_bytes()).collect();
544 producer.write(&Frame::new(Bytes::from(data), Timestamp::ZERO)).unwrap();
545 producer.finish().unwrap();
546
547 let mut decoded = Vec::new();
548 while let Some(frame) = audio.read().await.unwrap() {
549 let pcm = Format::F32.as_interleaved_f32(&frame.data, 1).unwrap();
550 decoded.extend_from_slice(&pcm);
551 }
552 assert_eq!(decoded.len(), frames, "terminal padding extended the source");
553 let peak = decoded.iter().fold(0.0f32, |peak, sample| peak.max(sample.abs()));
554 assert!(peak > 0.1, "the {frames}-frame Opus tail lost the impulse: peak {peak}");
555 }
556 }
557
558 #[tokio::test]
562 async fn finish_publishes_the_resampled_tail() {
563 let input = Input {
564 format: Format::F32,
565 sample_rate: 44_100,
566 channels: 1,
567 };
568
569 let mut broadcast = moq_net::broadcast::Info::new().produce();
570 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
571 let consumer = broadcast.consume();
572 let options = Options {
573 track: Some("audio".to_string()),
574 ..Options::default()
575 };
576 let mut producer = Producer::new(&mut broadcast, catalog, input.clone(), &options).unwrap();
577
578 let mut track = moq_mux::container::Consumer::new(
580 consumer
581 .track("audio")
582 .unwrap()
583 .subscribe(moq_net::track::Subscription::default())
584 .await
585 .unwrap(),
586 moq_mux::catalog::hang::Container::Legacy,
587 );
588
589 let data: Vec<u8> = vec![0.25f32; 8_838].iter().flat_map(|s| s.to_le_bytes()).collect();
593 producer
594 .write(&Frame::new(data.into(), moq_net::Timestamp::ZERO))
595 .unwrap();
596 producer.finish().unwrap();
597
598 let mut packets = 0;
599 while track.read().await.unwrap().is_some() {
600 packets += 1;
601 }
602 assert_eq!(packets, 11);
603 }
604
605 #[tokio::test]
609 async fn reset_epoch_drops_the_resampler_buffer_too() {
610 let input = Input {
611 format: Format::F32,
612 sample_rate: 44_100,
613 channels: 1,
614 };
615
616 let mut broadcast = moq_net::broadcast::Info::new().produce();
617 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
618 let consumer = broadcast.consume();
619 let options = Options {
620 track: Some("audio".to_string()),
621 ..Options::default()
622 };
623 let mut producer = Producer::new(&mut broadcast, catalog, input.clone(), &options).unwrap();
624
625 let mut track = moq_mux::container::Consumer::new(
626 consumer
627 .track("audio")
628 .unwrap()
629 .subscribe(moq_net::track::Subscription::default())
630 .await
631 .unwrap(),
632 moq_mux::catalog::hang::Container::Legacy,
633 );
634
635 let data: Vec<u8> = vec![0.25f32; 441].iter().flat_map(|s| s.to_le_bytes()).collect();
637 producer
638 .write(&Frame::new(data.into(), moq_net::Timestamp::ZERO))
639 .unwrap();
640
641 producer.reset_epoch();
642 producer.finish().unwrap();
643
644 assert!(track.read().await.unwrap().is_none());
646 }
647
648 #[tokio::test]
650 async fn reset_epoch_drops_the_encoder_lookahead() {
651 let mut broadcast = moq_net::broadcast::Info::new().produce();
652 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
653 let consumer = broadcast.consume();
654 let options = Options {
655 track: Some("audio".to_string()),
656 ..Options::default()
657 };
658 let mut producer = Producer::new(
659 &mut broadcast,
660 catalog,
661 Input {
662 channels: 1,
663 ..Input::default()
664 },
665 &options,
666 )
667 .unwrap();
668 let mut track = moq_mux::container::Consumer::new(
669 consumer
670 .track("audio")
671 .unwrap()
672 .subscribe(moq_net::track::Subscription::default())
673 .await
674 .unwrap(),
675 moq_mux::catalog::hang::Container::Legacy,
676 );
677
678 producer.write(&full_frame(1_000_000)).unwrap();
679 producer.reset_epoch();
680 producer.finish().unwrap();
681
682 assert!(track.read().await.unwrap().is_some());
683 assert!(track.read().await.unwrap().is_none());
684 }
685
686 #[tokio::test]
688 async fn reset_epoch_restarts_the_decoder() {
689 let input = Input {
690 format: Format::F32,
691 sample_rate: 48_000,
692 channels: 1,
693 };
694 let options = Options {
695 track: Some("audio".to_string()),
696 ..Options::default()
697 };
698 let decoder_config = Encoder::new(&options.config(input.clone())).unwrap().catalog();
699
700 let mut broadcast = moq_net::broadcast::Info::new().produce();
701 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
702 let subscriber = broadcast.consume();
703 let mut producer = Producer::new(&mut broadcast, catalog, input, &options).unwrap();
704 let mut audio = AudioConsumer::new(
705 &subscriber,
706 &decoder_config,
707 "audio",
708 DecodeConfig {
709 latency_max: Some(Duration::from_millis(500)),
710 ..DecodeConfig::new()
711 },
712 )
713 .await
714 .unwrap();
715
716 producer.write(&full_frame(0)).unwrap();
717 let first = audio.read().await.unwrap().expect("first epoch packet");
718 assert_eq!(first.data.len() / size_of::<f32>(), 960 - 312);
719
720 producer.reset_epoch();
721 producer.write(&full_frame(1_000_000)).unwrap();
722 producer.finish().unwrap();
723
724 let mut resumed_frames = 0;
725 while let Some(frame) = audio.read().await.unwrap() {
726 assert!(frame.timestamp.as_micros() >= 1_000_000);
727 resumed_frames += frame.data.len() / size_of::<f32>();
728 }
729 assert_eq!(resumed_frames, 960, "the resumed epoch must trim its own pre-skip once");
730 }
731
732 #[tokio::test]
733 async fn producer_and_consumer_keep_activity_on_the_audio_stream() {
734 let input = Input {
735 format: Format::F32,
736 sample_rate: 48_000,
737 channels: 1,
738 };
739 let options = Options {
740 track: Some("audio".to_string()),
741 bitrate: Some(24_000),
742 dtx: true,
743 ..Options::default()
744 };
745 let decoder_config = Encoder::new(&options.config(input.clone())).unwrap().catalog();
746
747 let mut broadcast = moq_net::broadcast::Info::new().produce();
748 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
749 let subscriber = broadcast.consume();
750 let mut producer = Producer::new(&mut broadcast, catalog, input, &options).unwrap();
751 let mut consumer = AudioConsumer::new(&subscriber, &decoder_config, "audio", DecodeConfig::new())
752 .await
753 .unwrap();
754
755 let silence = vec![0.0; 960];
756 let mut entered_dtx = false;
757 for index in 0..100 {
758 producer.write(&pcm_frame(&silence, index * 20_000)).unwrap();
759 let consumed = consumer.read().await.unwrap().expect("one decoded frame");
760 assert_eq!(producer.activity(), consumed.activity);
761 if consumed.activity.is_dtx() {
762 entered_dtx = true;
763 break;
764 }
765 }
766 assert!(entered_dtx, "silence should enter Opus DTX");
767
768 let active: Vec<f32> = (0..960)
769 .map(|sample| {
770 let phase = sample as f32 * 440.0 * 2.0 * std::f32::consts::PI / 48_000.0;
771 phase.sin() * 0.5
772 })
773 .collect();
774 producer.write(&pcm_frame(&active, 2_000_000)).unwrap();
775 let consumed = consumer.read().await.unwrap().expect("one decoded frame");
776 assert_eq!(producer.activity(), Activity::Active);
777 assert_eq!(consumed.activity, Activity::Active);
778 }
779
780 fn full_frame(timestamp_us: u64) -> Frame {
783 pcm_frame(&vec![0.1; 960], timestamp_us)
784 }
785
786 fn pcm_frame(samples: &[f32], timestamp_us: u64) -> Frame {
787 let data: Vec<u8> = samples.iter().flat_map(|sample| sample.to_le_bytes()).collect();
788 Frame::new(Bytes::from(data), Timestamp::from_micros(timestamp_us).unwrap())
789 }
790
791 async fn published_pts(frames: &[Frame], reset_before: Option<usize>) -> Vec<u128> {
795 let mut broadcast = moq_net::broadcast::Info::new().produce();
796 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
797 let consumer = broadcast.consume();
798
799 let input = Input {
802 format: Format::F32,
803 sample_rate: 48_000,
804 channels: 1,
805 };
806 let options = Options {
807 track: Some("audio".to_string()),
808 ..Options::default()
809 };
810 let mut producer = Producer::new(&mut broadcast, catalog, input, &options).unwrap();
811
812 let track = consumer.track("audio").unwrap().subscribe(None).await.unwrap();
813 let mut reader = moq_mux::container::Consumer::new(track, moq_mux::container::legacy::Wire);
814
815 let mut pts = Vec::new();
816 for (i, frame) in frames.iter().enumerate() {
817 if reset_before == Some(i) {
818 producer.reset_epoch();
819 }
820 producer.write(frame).unwrap();
821 let read = reader.read().await.unwrap().expect("a packet per full frame");
822 pts.push(read.timestamp.as_micros());
823 }
824 pts
825 }
826
827 #[tokio::test]
828 async fn epoch_anchors_to_first_frame_timestamp() {
829 let pts = published_pts(&[full_frame(1_000_000)], None).await;
832 assert_eq!(pts, vec![1_000_000]);
833 }
834
835 #[tokio::test]
836 async fn pts_advances_by_frame_duration_ignoring_later_timestamps() {
837 let pts = published_pts(&[full_frame(1_000), full_frame(999_999)], None).await;
840 assert_eq!(pts, vec![1_000, 1_000 + 20_000]);
841 }
842
843 #[tokio::test]
844 async fn reset_epoch_reanchors_so_the_gap_lands_in_pts() {
845 let pts = published_pts(&[full_frame(0), full_frame(5_000_000)], Some(1)).await;
848 assert_eq!(pts, vec![0, 5_000_000]);
849 }
850
851 #[tokio::test]
855 async fn default_options_derive_the_track_name() {
856 let mut broadcast = moq_net::broadcast::Info::new().produce();
857 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
858
859 let first = Producer::new(&mut broadcast, catalog.clone(), Input::default(), &Options::default()).unwrap();
860 assert_eq!(first.track_name(), "0.opus");
861
862 let second = Producer::new(&mut broadcast, catalog, Input::default(), &Options::default()).unwrap();
863 assert_eq!(second.track_name(), "1.opus");
864 }
865}