1use std::str::FromStr;
8use std::time::Duration;
9
10use bytes::Bytes;
11
12use super::Encoded;
13use super::backend::{self, Backend};
14use crate::opus;
15use crate::pcm;
16use crate::{Error, Format, Layout};
17
18const AAC_FRAME_SIZE: usize = 1024;
20
21const AAC_LC: u8 = 2;
23
24const AAC_MAX_SAMPLE_RATE: u32 = 0xFF_FFFF;
27
28#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum Codec {
33 #[default]
35 Opus,
36 Pcm,
38 Aac,
41}
42
43impl Codec {
44 pub fn as_str(self) -> &'static str {
47 match self {
48 Self::Opus => "opus",
49 Self::Pcm => "pcm",
50 Self::Aac => "aac",
51 }
52 }
53}
54
55impl std::fmt::Display for Codec {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.write_str(self.as_str())
58 }
59}
60
61impl FromStr for Codec {
62 type Err = Error;
63
64 fn from_str(s: &str) -> Result<Self, Self::Err> {
65 match s {
66 "opus" => Ok(Self::Opus),
67 "pcm" => Ok(Self::Pcm),
68 "aac" => Ok(Self::Aac),
69 other => Err(Error::Unsupported(format!("unknown codec: {other}"))),
70 }
71 }
72}
73
74#[derive(Clone, Debug, Default, PartialEq, Eq)]
76#[non_exhaustive]
77pub enum Kind {
78 #[default]
80 Auto,
81 Software,
83 Named(String),
87}
88
89#[derive(Clone, Debug)]
91#[non_exhaustive]
92pub struct Input {
93 pub format: Format,
95 pub sample_rate: u32,
97 pub layout: Layout,
99}
100
101impl Input {
102 pub fn new(sample_rate: u32, layout: Layout) -> Self {
104 Self {
105 format: Format::F32,
106 sample_rate,
107 layout,
108 }
109 }
110}
111
112impl Default for Input {
113 fn default() -> Self {
114 Self::new(48_000, Layout::Stereo)
115 }
116}
117
118#[derive(Clone, Debug)]
120#[non_exhaustive]
121pub struct Settings {
122 pub codec: Codec,
124 pub sample_rate: u32,
126 pub layout: Layout,
131 pub bitrate: Option<moq_net::bandwidth::Rate>,
139 pub dtx: bool,
141 pub frame_duration: Duration,
147 pub kind: Kind,
149}
150
151impl Settings {
152 pub fn new(sample_rate: u32, layout: Layout) -> Self {
154 Self {
155 codec: Codec::default(),
156 sample_rate,
157 layout,
158 bitrate: None,
159 dtx: false,
160 frame_duration: Duration::from_millis(20),
161 kind: Kind::Auto,
162 }
163 }
164
165 pub fn from_input(codec: Codec, input: &Input) -> Self {
167 let sample_rate = match codec {
168 Codec::Opus => opus::pick_rate(input.sample_rate),
169 Codec::Pcm | Codec::Aac => input.sample_rate,
170 };
171 let defaults = Self::new(sample_rate, input.layout);
172 let frame_duration = match codec {
173 Codec::Aac => aac_frame_duration(sample_rate),
174 Codec::Opus | Codec::Pcm => defaults.frame_duration,
175 };
176 Self {
177 codec,
178 frame_duration,
179 ..defaults
180 }
181 }
182
183 fn frame_size(&self) -> Result<usize, Error> {
188 self.layout.validate()?;
189 let (rate, channels) = (self.sample_rate, self.layout.channels());
190
191 match self.codec {
192 Codec::Opus => {
193 opus::validate_rate(rate)?;
194 if !matches!(self.layout, Layout::Mono | Layout::Stereo) {
195 return Err(Error::Unsupported("opus requires a named mono or stereo layout".into()));
196 }
197 opus::frame_size(rate, self.frame_duration)
198 }
199 Codec::Pcm => {
200 if self.bitrate.is_some() {
201 return Err(Error::Unsupported(
202 "pcm bitrate is fixed; leave Settings::bitrate unset".into(),
203 ));
204 }
205 if self.dtx {
206 return Err(Error::Unsupported(
207 "pcm does not support discontinuous transmission".into(),
208 ));
209 }
210 if rate == 0 {
211 return Err(Error::Unsupported("pcm sample rate must be greater than zero".into()));
212 }
213 let frame_size = pcm::frame_size(rate, self.frame_duration)?;
214 pcm::frame_bytes(frame_size, channels)?;
215 pcm::bitrate(rate, channels)?;
216 Ok(frame_size)
217 }
218 Codec::Aac => {
219 if self.dtx {
220 return Err(Error::Unsupported(
221 "aac does not support discontinuous transmission".into(),
222 ));
223 }
224 aac_config(self)?;
225 let frames = (self.frame_duration.as_nanos() * u128::from(rate) + 500_000_000) / 1_000_000_000;
226 if frames != AAC_FRAME_SIZE as u128 {
227 return Err(Error::Unsupported(format!(
228 "aac frames are {AAC_FRAME_SIZE} samples, {:?} at {rate} Hz (got {:?})",
229 aac_frame_duration(rate),
230 self.frame_duration
231 )));
232 }
233 Ok(AAC_FRAME_SIZE)
234 }
235 }
236 }
237}
238
239impl Default for Settings {
240 fn default() -> Self {
241 Self::new(48_000, Layout::Stereo)
242 }
243}
244
245fn aac_frame_duration(sample_rate: u32) -> Duration {
247 if sample_rate == 0 {
248 return Duration::ZERO;
249 }
250 let rate = u64::from(sample_rate);
251 Duration::from_nanos((AAC_FRAME_SIZE as u64 * 1_000_000_000 + rate / 2) / rate)
252}
253
254fn aac_config(settings: &Settings) -> Result<moq_mux::codec::aac::Config, Error> {
261 let layout = settings.layout;
262 if !matches!(
263 layout,
264 Layout::Mono
265 | Layout::Stereo
266 | Layout::ThreePointZero
267 | Layout::FourPointZero
268 | Layout::FivePointZero
269 | Layout::FivePointOne
270 | Layout::SevenPointOne
271 ) {
272 return Err(Error::Unsupported(format!(
273 "aac has no channelConfiguration for {layout:?}; use mono, stereo, 3.0, 4.0, 5.0, 5.1, or 7.1"
274 )));
275 }
276
277 let sample_rate = settings.sample_rate;
278 if !(1..=AAC_MAX_SAMPLE_RATE).contains(&sample_rate) {
279 return Err(Error::Unsupported(format!(
280 "aac sample rate must be between 1 and {AAC_MAX_SAMPLE_RATE} Hz (got {sample_rate})"
281 )));
282 }
283
284 Ok(moq_mux::codec::aac::Config {
285 profile: AAC_LC,
286 sample_rate,
287 channel_count: layout.channels(),
288 })
289}
290
291pub struct Encoder {
298 backend: Box<dyn Backend>,
299 settings: Settings,
300 frame_size: usize,
301 description: Option<Bytes>,
304 started: bool,
306}
307
308pub struct Finish {
310 packets: Vec<Encoded>,
311 discard_padding: usize,
312}
313
314impl Finish {
315 pub fn packets(&self) -> &[Encoded] {
317 &self.packets
318 }
319
320 pub fn discard_padding(&self) -> usize {
322 self.discard_padding
323 }
324
325 pub fn into_packets(self) -> Vec<Encoded> {
327 self.packets
328 }
329}
330
331impl Encoder {
332 pub fn new(settings: &Settings) -> Result<Self, Error> {
335 let frame_size = settings.frame_size()?;
336 let backend = backend::open(settings)?;
337
338 let description = match settings.codec {
339 Codec::Opus => {
340 let lookahead = backend.delay() as u64;
342 let pre_skip = u16::try_from((lookahead * 48_000) / u64::from(settings.sample_rate))
343 .map_err(|_| Error::Unsupported(format!("Opus lookahead {lookahead} does not fit in OpusHead")))?;
344 let head = moq_mux::codec::opus::Config::new(settings.sample_rate, settings.layout.channels())
345 .with_pre_skip(pre_skip)
346 .encode()
347 .map_err(moq_mux::Error::from)?;
348 Some(head)
349 }
350 Codec::Aac => Some(aac_config(settings)?.encode()),
351 Codec::Pcm => None,
352 };
353
354 Ok(Self {
355 backend,
356 settings: settings.clone(),
357 frame_size,
358 description,
359 started: false,
360 })
361 }
362
363 pub fn name(&self) -> &str {
365 self.backend.name()
366 }
367
368 pub fn settings(&self) -> &Settings {
370 &self.settings
371 }
372
373 pub fn codec(&self) -> Codec {
376 self.settings.codec
377 }
378
379 pub fn codec_rate(&self) -> u32 {
382 self.settings.sample_rate
383 }
384
385 pub fn codec_channels(&self) -> u32 {
388 self.settings.layout.channels()
389 }
390
391 pub fn frame_size(&self) -> usize {
394 self.frame_size
395 }
396
397 pub fn bitrate(&self) -> moq_net::bandwidth::Rate {
399 moq_net::bandwidth::Rate::from_bps(self.backend.bitrate())
400 }
401
402 pub fn set_bitrate(&mut self, bitrate: moq_net::bandwidth::Rate) -> Result<(), Error> {
411 let previous = self.backend.bitrate();
412 self.backend.set_bitrate(bitrate.as_bps())?;
413 if bitrate.as_bps() != previous {
414 self.settings.bitrate = Some(bitrate);
415 }
416 Ok(())
417 }
418
419 pub(super) fn reset(&mut self) {
421 self.backend.reset();
422 self.started = false;
423 }
424
425 pub(super) fn started(&self) -> bool {
427 self.started
428 }
429
430 pub(super) fn folded_delay(&self) -> usize {
438 match self.settings.codec {
439 Codec::Aac => self.backend.delay(),
440 Codec::Opus | Codec::Pcm => 0,
441 }
442 }
443
444 pub fn encode(&mut self, pcm: &[f32]) -> Result<Encoded, Error> {
450 let expected = self.frame_size * self.codec_channels() as usize;
451 if pcm.len() != expected {
452 return Err(Error::Misaligned {
453 got: std::mem::size_of_val(pcm),
454 expected: expected * std::mem::size_of::<f32>(),
455 });
456 }
457 let encoded = self.backend.encode(pcm)?;
458 self.started = true;
459 Ok(encoded)
460 }
461
462 pub fn finish(mut self, pcm: &[f32]) -> Result<Finish, Error> {
472 self.drain(pcm)
473 }
474
475 pub(super) fn drain(&mut self, pcm: &[f32]) -> Result<Finish, Error> {
477 let channels = self.codec_channels() as usize;
478 let frame_samples = self.frame_size * channels;
479 if pcm.len() > frame_samples || !pcm.len().is_multiple_of(channels) {
480 return Err(Error::Misaligned {
481 got: std::mem::size_of_val(pcm),
482 expected: if pcm.len() > frame_samples {
483 frame_samples * std::mem::size_of::<f32>()
484 } else {
485 pcm.len().next_multiple_of(channels) * std::mem::size_of::<f32>()
486 },
487 });
488 }
489
490 let source_frames = pcm.len() / channels;
491 let mut packets = Vec::new();
492 let padding = if pcm.is_empty() {
493 0
494 } else {
495 let mut frame = Vec::with_capacity(frame_samples);
496 frame.extend_from_slice(pcm);
497 frame.resize(frame_samples, 0.0);
498 let padding = (frame_samples - pcm.len()) / channels;
499 packets.push(self.encode(&frame)?);
500 padding
501 };
502
503 if !self.started {
504 return Ok(Finish {
505 packets,
506 discard_padding: 0,
507 });
508 }
509
510 let lookahead = self.backend.delay();
511 let drain = lookahead.saturating_sub(padding);
512 let silence = vec![0.0; frame_samples];
513 for _ in 0..drain.div_ceil(self.frame_size) {
514 packets.push(self.encode(&silence)?);
515 }
516
517 let discard_padding = packets
518 .len()
519 .saturating_mul(self.frame_size)
520 .saturating_sub(lookahead)
521 .saturating_sub(source_frames);
522
523 Ok(Finish {
524 packets,
525 discard_padding,
526 })
527 }
528
529 pub fn catalog(&self) -> hang::catalog::AudioConfig {
531 let (rate, channels) = (self.codec_rate(), self.codec_channels());
532 let (codec, bitrate): (hang::catalog::AudioCodec, _) = match self.settings.codec {
533 Codec::Opus => (
534 hang::catalog::AudioCodec::Opus,
535 self.settings.bitrate.map(moq_net::bandwidth::Rate::as_bps),
536 ),
537 Codec::Pcm => (
538 hang::catalog::AudioCodec::Pcm,
539 Some(pcm::bitrate(rate, channels).expect("pcm encoder bitrate validated at construction")),
540 ),
541 Codec::Aac => (
542 hang::catalog::AAC { profile: AAC_LC }.into(),
543 self.settings.bitrate.map(moq_net::bandwidth::Rate::as_bps),
544 ),
545 };
546
547 let mut config = hang::catalog::AudioConfig::new(codec, rate, channels);
548 config.bitrate = bitrate;
549 config.description = self.description.clone();
550 config.container = hang::catalog::Container::Legacy;
551 config
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558 use crate::decode::{Config as DecodeConfig, Decoder};
559
560 fn sine(freq: f32, sample_rate: u32, channels: u32, frames: usize) -> Vec<f32> {
561 let mut out = Vec::with_capacity(frames * channels as usize);
562 for i in 0..frames {
563 let t = i as f32 / sample_rate as f32;
564 let v = (2.0 * std::f32::consts::PI * freq * t).sin() * 0.5;
565 for _ in 0..channels {
566 out.push(v);
567 }
568 }
569 out
570 }
571
572 #[test]
574 fn encoder_is_unwind_safe() {
575 fn assert_unwind_safe<T: std::panic::UnwindSafe + std::panic::RefUnwindSafe>() {}
576 assert_unwind_safe::<Encoder>();
577 }
578
579 #[test]
580 fn opus_encode_then_decode_keeps_signal_close() {
581 let mut enc = Encoder::new(&Settings {
582 bitrate: Some(moq_net::bandwidth::Rate::from_bps(96_000)),
583 ..Settings::default()
584 })
585 .unwrap();
586
587 let cfg = enc.catalog();
588 let mut dec = Decoder::new(&cfg, &DecodeConfig::default()).unwrap();
589
590 let frame = sine(440.0, 48_000, 2, enc.frame_size());
591 for _ in 0..5 {
592 let pkt = enc.encode(&frame).unwrap();
593 let _ = dec.decode(&pkt.payload).unwrap();
594 }
595
596 let pkt = enc.encode(&frame).unwrap();
597 let decoded = dec.decode(&pkt.payload).unwrap();
598 assert_eq!(decoded.samples.len(), frame.len());
599
600 let energy_in: f32 = frame.iter().map(|s| s * s).sum();
601 let energy_out: f32 = decoded.samples.iter().map(|s| s * s).sum();
602 let ratio = energy_out / energy_in;
603 assert!(
604 (0.5..2.0).contains(&ratio),
605 "output energy ratio {ratio:.3} should be close to 1"
606 );
607 }
608
609 #[test]
610 fn opus_rejects_unsupported_frame_duration() {
611 let err = Encoder::new(&Settings {
612 frame_duration: Duration::from_millis(15),
613 ..Settings::default()
614 });
615 assert!(matches!(err, Err(Error::Unsupported(_))));
616 }
617
618 #[test]
619 fn opus_rejects_misaligned_input() {
620 let mut enc = Encoder::new(&Settings::default()).unwrap();
621 assert!(matches!(enc.encode(&[0.0f32; 100]), Err(Error::Misaligned { .. })));
622 }
623
624 #[test]
625 fn opus_catalog_includes_opushead() {
626 let enc = Encoder::new(&Settings {
627 bitrate: Some(moq_net::bandwidth::Rate::from_bps(64_000)),
628 ..Settings::default()
629 })
630 .unwrap();
631 let cfg = enc.catalog();
632 assert_eq!(cfg.sample_rate, 48_000);
633 assert_eq!(cfg.channel_count, 2);
634 assert_eq!(cfg.bitrate, Some(64_000));
635 let desc = cfg.description.expect("OpusHead should be present");
636 assert_eq!(desc.len(), 19);
637 let head = moq_mux::codec::opus::Config::parse(&mut desc.as_ref()).unwrap();
638 assert_eq!(head.pre_skip, 312);
639 }
640
641 #[test]
642 fn opus_decoder_trims_encoder_lookahead_once() {
643 let mut enc = Encoder::new(&Settings::default()).unwrap();
644 let mut dec = Decoder::new(&enc.catalog(), &DecodeConfig::default()).unwrap();
645 let frame = vec![0.0; enc.frame_size() * enc.codec_channels() as usize];
646
647 let first = dec.decode(&enc.encode(&frame).unwrap().payload).unwrap();
648 assert_eq!(
649 first.samples.len(),
650 (enc.frame_size() - enc.backend.delay()) * enc.codec_channels() as usize
651 );
652
653 let second = dec.decode(&enc.encode(&frame).unwrap().payload).unwrap();
654 assert_eq!(second.samples.len(), frame.len());
655 }
656
657 #[test]
658 fn opus_finish_accounts_for_partial_frame_padding() {
659 let enc = Encoder::new(&Settings::new(48_000, Layout::Mono)).unwrap();
660
661 let packets = enc.finish(&vec![0.0; 600]).unwrap();
664 assert_eq!(packets.packets().len(), 1);
665 assert_eq!(packets.discard_padding(), 48);
666 }
667
668 #[test]
669 fn opus_finish_drains_lookahead_across_multiple_short_packets() {
670 let mut enc = Encoder::new(&Settings {
671 frame_duration: Duration::from_micros(2_500),
672 ..Settings::new(48_000, Layout::Mono)
673 })
674 .unwrap();
675 let frame = vec![0.0; enc.frame_size()];
676 enc.encode(&frame).unwrap();
677
678 let packets = enc.finish(&[]).unwrap();
680 assert_eq!(packets.packets().len(), 3);
681 assert_eq!(packets.discard_padding(), 48);
682 }
683
684 #[test]
685 fn reset_drops_pending_opus_lookahead() {
686 let mut enc = Encoder::new(&Settings::new(48_000, Layout::Mono)).unwrap();
687 let mut old = vec![0.0; enc.frame_size()];
688 old[enc.frame_size() - 1] = 1.0;
689 enc.encode(&old).unwrap();
690
691 enc.reset();
692 let next = vec![0.0; enc.frame_size()];
693 let actual = enc.encode(&next).unwrap();
694 let mut decoder = Decoder::new(&enc.catalog(), &DecodeConfig::default()).unwrap();
695 let decoded = decoder.decode(&actual.payload).unwrap();
696 let peak = decoded
697 .samples
698 .iter()
699 .fold(0.0f32, |peak, sample| peak.max(sample.abs()));
700 assert!(peak < 0.001, "pre-reset impulse leaked into the next epoch: {peak}");
701
702 enc.reset();
703 let finish = enc.finish(&[]).unwrap();
704 assert!(finish.packets().is_empty());
705 assert_eq!(finish.discard_padding(), 0);
706 }
707
708 #[test]
709 fn opus_runtime_bitrate_updates_encoder_state() {
710 let mut enc = Encoder::new(&Settings {
711 bitrate: Some(moq_net::bandwidth::Rate::from_bps(64_000)),
712 ..Settings::default()
713 })
714 .unwrap();
715
716 enc.set_bitrate(moq_net::bandwidth::Rate::from_bps(32_000)).unwrap();
717 assert_eq!(enc.bitrate(), moq_net::bandwidth::Rate::from_bps(32_000));
718 assert_eq!(enc.settings().bitrate, Some(moq_net::bandwidth::Rate::from_bps(32_000)));
719 }
720
721 #[test]
722 fn opus_runtime_bitrate_rejects_values_libopus_would_clamp() {
723 let mut enc = Encoder::new(&Settings::default()).unwrap();
724 let original = enc.bitrate();
725 assert!(enc.set_bitrate(moq_net::bandwidth::Rate::from_bps(1)).is_err());
726 assert!(enc.set_bitrate(moq_net::bandwidth::Rate::from_bps(600_001)).is_err());
727 assert_eq!(enc.bitrate(), original);
728 }
729
730 #[test]
731 fn codec_roundtrips_as_str() {
732 assert_eq!(Codec::Opus.as_str(), "opus");
733 assert_eq!(Codec::Opus.to_string(), "opus");
734 assert_eq!("opus".parse::<Codec>().unwrap(), Codec::Opus);
735 assert_eq!(Codec::Pcm.as_str(), "pcm");
736 assert_eq!(Codec::Pcm.to_string(), "pcm");
737 assert_eq!("pcm".parse::<Codec>().unwrap(), Codec::Pcm);
738 assert_eq!(Codec::Aac.as_str(), "aac");
739 assert_eq!(Codec::Aac.to_string(), "aac");
740 assert_eq!("aac".parse::<Codec>().unwrap(), Codec::Aac);
741 assert!("mp3".parse::<Codec>().is_err());
742 }
743
744 #[test]
745 fn settings_fix_the_codec_rate() {
746 let enc = Encoder::new(&Settings::new(24_000, Layout::Mono)).unwrap();
747 assert_eq!(enc.codec_rate(), 24_000);
748 let catalog = enc.catalog();
749 assert_eq!(catalog.sample_rate, 24_000);
750 let head = moq_mux::codec::opus::Config::parse(&mut catalog.description.unwrap().as_ref()).unwrap();
751 assert_eq!(head.pre_skip, 312);
752 }
753
754 #[test]
755 fn pcm_roundtrip_is_lossless() {
756 let mut enc = Encoder::new(&Settings {
757 codec: Codec::Pcm,
758 ..Settings::default()
759 })
760 .unwrap();
761 let mut dec = Decoder::new(&enc.catalog(), &DecodeConfig::default()).unwrap();
762 let input = sine(440.0, enc.codec_rate(), enc.codec_channels(), enc.frame_size());
763
764 let packet = enc.encode(&input).unwrap();
765 let output = dec.decode(&packet.payload).unwrap();
766
767 assert_eq!(output.samples, input);
768 }
769
770 #[test]
773 fn pcm_refuses_layouts_its_count_cannot_name() {
774 for layout in [Layout::ThreePointZero, Layout::FourPointZero] {
775 let err = Encoder::new(&Settings {
776 codec: Codec::Pcm,
777 ..Settings::new(48_000, layout)
778 });
779 assert!(matches!(err, Err(Error::Unsupported(_))), "{layout:?}");
780 }
781
782 for layout in [Layout::TwoPointOne, Layout::Quad, Layout::Discrete(3)] {
783 let mut enc = Encoder::new(&Settings {
784 codec: Codec::Pcm,
785 ..Settings::new(48_000, layout)
786 })
787 .unwrap();
788 let mut dec = Decoder::new(&enc.catalog(), &DecodeConfig::default()).unwrap();
789 let input = sine(440.0, enc.codec_rate(), enc.codec_channels(), enc.frame_size());
790 let output = dec.decode(&enc.encode(&input).unwrap().payload).unwrap();
791 assert_eq!(output.samples, input, "{layout:?}");
792 }
793 }
794
795 #[test]
796 fn pcm_catalog_declares_fixed_bitrate() {
797 let enc = Encoder::new(&Settings {
798 codec: Codec::Pcm,
799 ..Settings::default()
800 })
801 .unwrap();
802 let catalog = enc.catalog();
803
804 assert_eq!(catalog.codec, hang::catalog::AudioCodec::Pcm);
805 assert_eq!(catalog.bitrate, Some(48_000 * 2 * 32));
806 assert_eq!(catalog.description, None);
807 }
808
809 #[test]
810 fn pcm_rejects_runtime_bitrate_change() {
811 let mut enc = Encoder::new(&Settings {
812 codec: Codec::Pcm,
813 ..Settings::default()
814 })
815 .unwrap();
816 let bitrate = enc.bitrate();
817
818 assert!(matches!(enc.set_bitrate(bitrate), Err(Error::Unsupported(_))));
819 assert_eq!(enc.bitrate(), bitrate);
820 }
821
822 #[test]
823 fn pcm_rejects_fractional_sample_frame_duration() {
824 let err = Encoder::new(&Settings {
825 codec: Codec::Pcm,
826 frame_duration: Duration::from_micros(2_500),
827 ..Settings::new(44_100, Layout::Stereo)
828 });
829 assert!(matches!(err, Err(Error::Unsupported(_))));
830 }
831
832 #[test]
833 fn pcm_rejects_bitrate_overflow() {
834 let err = Encoder::new(&Settings {
835 codec: Codec::Pcm,
836 frame_duration: Duration::from_secs(1),
837 ..Settings::new(u32::MAX, Layout::Discrete(u32::MAX))
838 });
839 assert!(matches!(err, Err(Error::Unsupported(_))));
840 }
841
842 #[test]
843 fn pcm_rejects_opus_only_settings() {
844 let settings = Settings {
845 codec: Codec::Pcm,
846 dtx: true,
847 ..Settings::default()
848 };
849 assert!(matches!(Encoder::new(&settings), Err(Error::Unsupported(_))));
850 }
851
852 #[test]
855 fn pcm_passes_discrete_multichannel_samples_through() {
856 let settings = Settings {
857 codec: Codec::Pcm,
858 ..Settings::new(48_000, Layout::Discrete(3))
859 };
860 let mut encoder = Encoder::new(&settings).unwrap();
861 let catalog = encoder.catalog();
862 let mut decoder = Decoder::new(&catalog, &DecodeConfig::default()).unwrap();
863 let input = [0.1, 0.2, 0.3].repeat(encoder.frame_size());
864 let output = decoder.decode(&encoder.encode(&input).unwrap().payload).unwrap();
865
866 assert_eq!(decoder.layout(), Layout::TwoPointOne);
867 assert_eq!(output.samples, input);
868 }
869
870 #[test]
871 fn opus_refuses_discrete_layout() {
872 let settings = Settings::new(48_000, Layout::Discrete(2));
873 assert!(matches!(Encoder::new(&settings), Err(Error::Unsupported(_))));
874 }
875
876 fn aac(layout: Layout) -> Settings {
877 Settings::from_input(Codec::Aac, &Input::new(48_000, layout))
878 }
879
880 fn stub(settings: &Settings) -> Result<Encoder, Error> {
882 let _stub = backend::stub::install();
883 Encoder::new(settings)
884 }
885
886 #[test]
888 fn aac_is_refused_without_an_encoder() {
889 let err = Encoder::new(&aac(Layout::Stereo))
890 .err()
891 .expect("no AAC encoder on this host");
892 assert!(matches!(err, Error::Unsupported(_)), "{err}");
893 }
894
895 #[test]
897 fn aac_catalog_carries_the_synthesized_asc() {
898 let enc = stub(&aac(Layout::Stereo)).unwrap();
899 assert_eq!(enc.name(), backend::stub::NAME);
900 assert_eq!(enc.frame_size(), 1024);
901
902 let catalog = enc.catalog();
903 assert_eq!(catalog.codec, hang::catalog::AAC { profile: 2 }.into());
904 assert_eq!(catalog.codec.to_string(), "mp4a.40.2");
905 assert_eq!(catalog.sample_rate, 48_000);
906 assert_eq!(catalog.channel_count, 2);
907 assert_eq!(catalog.container, hang::catalog::Container::Legacy);
908 assert_eq!(catalog.description.as_deref(), Some(&[0x11, 0x90][..]));
910 }
911
912 #[test]
913 fn aac_takes_the_layouts_with_a_channel_configuration() {
914 for (layout, config) in [
915 (Layout::Mono, 1),
916 (Layout::Stereo, 2),
917 (Layout::ThreePointZero, 3),
918 (Layout::FourPointZero, 4),
919 (Layout::FivePointZero, 5),
920 (Layout::FivePointOne, 6),
921 (Layout::SevenPointOne, 7),
922 ] {
923 let catalog = stub(&aac(layout)).unwrap().catalog();
924 let description = catalog.description.unwrap();
925 assert_eq!(description[1] >> 3 & 0xF, config, "{layout:?}");
926 assert_eq!(catalog.channel_count, layout.channels(), "{layout:?}");
927 }
928
929 for layout in [
930 Layout::TwoPointOne,
931 Layout::Quad,
932 Layout::SixPointOne,
933 Layout::Discrete(2),
934 ] {
935 assert!(matches!(stub(&aac(layout)), Err(Error::Unsupported(_))), "{layout:?}");
936 }
937 }
938
939 #[test]
941 fn aac_frame_duration_is_the_codecs() {
942 assert_eq!(aac(Layout::Stereo).frame_duration, Duration::from_nanos(21_333_333));
943 let settings = Settings {
944 frame_duration: Duration::from_micros(21_333),
945 ..aac(Layout::Stereo)
946 };
947 assert_eq!(stub(&settings).unwrap().frame_size(), 1024);
948
949 let settings = Settings {
950 frame_duration: Duration::from_millis(20),
951 ..aac(Layout::Stereo)
952 };
953 let err = stub(&settings).err().expect("20 ms is 960 samples");
954 assert!(err.to_string().contains("1024"), "{err}");
955 }
956
957 #[test]
958 fn aac_refuses_dtx() {
959 let settings = Settings {
960 dtx: true,
961 ..aac(Layout::Stereo)
962 };
963 assert!(matches!(stub(&settings), Err(Error::Unsupported(_))));
964 }
965
966 #[test]
968 fn fixed_rate_backend_keeps_its_opening_rate() {
969 let mut enc = stub(&Settings {
970 bitrate: Some(moq_net::bandwidth::Rate::from_bps(96_000)),
971 ..aac(Layout::Stereo)
972 })
973 .unwrap();
974
975 let err = enc.set_bitrate(moq_net::bandwidth::Rate::from_bps(64_000));
976 assert!(matches!(err, Err(Error::Unsupported(_))));
977 assert_eq!(enc.bitrate(), moq_net::bandwidth::Rate::from_bps(96_000));
978 assert_eq!(enc.settings().bitrate, Some(moq_net::bandwidth::Rate::from_bps(96_000)));
979 assert_eq!(enc.catalog().bitrate, Some(96_000));
980 }
981
982 #[test]
984 fn aac_finish_drains_the_encoder_delay() {
985 let mut enc = stub(&aac(Layout::Mono)).unwrap();
986 assert_eq!(enc.folded_delay(), backend::stub::DELAY);
987 enc.encode(&[0.0; 1024]).unwrap();
988
989 let finish = enc.finish(&[]).unwrap();
991 assert_eq!(finish.packets().len(), 3);
992 assert_eq!(finish.discard_padding(), 3 * 1024 - backend::stub::DELAY);
993 }
994
995 #[test]
997 fn opus_folds_no_delay() {
998 assert_eq!(Encoder::new(&Settings::default()).unwrap().folded_delay(), 0);
999 }
1000}