1use std::str::FromStr;
8use std::time::Duration;
9
10use bytes::Bytes;
11use unsafe_libopus::{
12 OPUS_APPLICATION_AUDIO, OPUS_GET_BITRATE_REQUEST, OPUS_GET_LOOKAHEAD_REQUEST, OPUS_OK, OPUS_RESET_STATE,
13 OPUS_SET_BITRATE_REQUEST, OPUS_SET_DTX_REQUEST, OPUS_SET_INBAND_FEC_REQUEST, OpusEncoder, opus_encode_float,
14 opus_encoder_create, opus_encoder_ctl_impl, opus_encoder_destroy, varargs,
15};
16
17use crate::opus;
18use crate::pcm;
19use crate::{Error, Format};
20
21const MAX_PACKET_BYTES: usize = 4_000;
23
24#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum Codec {
29 #[default]
31 Opus,
32 Pcm,
34}
35
36impl Codec {
37 pub fn as_str(self) -> &'static str {
40 match self {
41 Self::Opus => "opus",
42 Self::Pcm => "pcm",
43 }
44 }
45}
46
47impl std::fmt::Display for Codec {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.write_str(self.as_str())
50 }
51}
52
53impl FromStr for Codec {
54 type Err = Error;
55
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 match s {
58 "opus" => Ok(Self::Opus),
59 "pcm" => Ok(Self::Pcm),
60 other => Err(Error::Unsupported(format!("unknown codec: {other}"))),
61 }
62 }
63}
64
65#[derive(Clone, Debug)]
72pub struct Input {
73 pub format: Format,
75 pub sample_rate: u32,
77 pub channels: u32,
79}
80
81impl Default for Input {
82 fn default() -> Self {
83 Self {
84 format: Format::F32,
85 sample_rate: 48_000,
86 channels: 2,
87 }
88 }
89}
90
91#[derive(Clone, Debug)]
100#[non_exhaustive]
101pub struct Config {
102 pub input: Input,
104 pub codec: Codec,
106 pub sample_rate: Option<u32>,
109 pub channels: Option<u32>,
112 pub bitrate: Option<u32>,
115 pub fec: bool,
117 pub dtx: bool,
119 pub frame_duration: Duration,
122}
123
124impl Config {
125 pub fn new(input: Input) -> Self {
127 Self {
128 input,
129 codec: Codec::default(),
130 sample_rate: None,
131 channels: None,
132 bitrate: None,
133 fec: false,
134 dtx: false,
135 frame_duration: Duration::from_millis(20),
136 }
137 }
138}
139
140pub struct Encoder {
147 backend: Backend,
148 config: Config,
149 codec_rate: u32,
152 codec_channels: u32,
154 bitrate: u64,
156 pre_skip: u16,
158 lookahead: usize,
160 frame_size: usize,
161 started: bool,
163}
164
165enum Backend {
166 Opus(Opus),
167 Pcm,
168}
169
170struct Opus {
171 inner: *mut OpusEncoder,
172 scratch: Vec<u8>,
173}
174
175unsafe impl Send for Opus {}
179
180pub struct Finish {
182 packets: Vec<Bytes>,
183 discard_padding: usize,
184}
185
186impl Finish {
187 pub fn packets(&self) -> &[Bytes] {
189 &self.packets
190 }
191
192 pub fn discard_padding(&self) -> usize {
194 self.discard_padding
195 }
196
197 pub fn into_packets(self) -> Vec<Bytes> {
199 self.packets
200 }
201}
202
203impl Encoder {
204 pub fn new(config: &Config) -> Result<Self, Error> {
206 match config.codec {
207 Codec::Opus => Self::new_opus(config.clone()),
208 Codec::Pcm => Self::new_pcm(config.clone()),
209 }
210 }
211
212 fn new_opus(config: Config) -> Result<Self, Error> {
213 let codec_rate = config
214 .sample_rate
215 .unwrap_or_else(|| opus::pick_rate(config.input.sample_rate));
216 opus::validate_rate(codec_rate)?;
217
218 let codec_channels = config.channels.unwrap_or(config.input.channels);
219 if codec_channels != config.input.channels {
220 return Err(Error::Unsupported(format!(
221 "channel remapping not implemented (input {}ch, output {codec_channels}ch)",
222 config.input.channels
223 )));
224 }
225 let channels = opus::validate_channels(codec_channels)?;
226
227 let frame_size = opus::frame_size(codec_rate, config.frame_duration)?;
228
229 let mut err = 0i32;
230 let inner = unsafe { opus_encoder_create(codec_rate as i32, channels, OPUS_APPLICATION_AUDIO, &mut err) };
232 if err != OPUS_OK || inner.is_null() {
233 return Err(opus::error(err, "opus_encoder_create"));
234 }
235
236 let configured = Self::configure_opus(inner, &config, codec_rate, codec_channels);
237 let (bitrate, lookahead, pre_skip) = match configured {
238 Ok(configured) => configured,
239 Err(err) => {
240 unsafe { opus_encoder_destroy(inner) };
242 return Err(err);
243 }
244 };
245
246 Ok(Self {
247 backend: Backend::Opus(Opus {
248 inner,
249 scratch: vec![0u8; MAX_PACKET_BYTES],
250 }),
251 config,
252 codec_rate,
253 codec_channels,
254 bitrate,
255 pre_skip,
256 lookahead,
257 frame_size,
258 started: false,
259 })
260 }
261
262 fn new_pcm(config: Config) -> Result<Self, Error> {
263 if config.bitrate.is_some() {
264 return Err(Error::Unsupported(
265 "pcm bitrate is fixed; leave Config::bitrate unset".into(),
266 ));
267 }
268
269 let codec_rate = config.sample_rate.unwrap_or(config.input.sample_rate);
270 if codec_rate == 0 {
271 return Err(Error::Unsupported("pcm sample rate must be greater than zero".into()));
272 }
273
274 let codec_channels = config.channels.unwrap_or(config.input.channels);
275 if codec_channels == 0 {
276 return Err(Error::Unsupported("pcm channel count must be greater than zero".into()));
277 }
278 if codec_channels != config.input.channels {
279 return Err(Error::Unsupported(format!(
280 "channel remapping not implemented (input {}ch, output {codec_channels}ch)",
281 config.input.channels
282 )));
283 }
284
285 let frame_size = pcm::frame_size(codec_rate, config.frame_duration)?;
286 pcm::frame_bytes(frame_size, codec_channels)?;
287 let bitrate = pcm::bitrate(codec_rate, codec_channels)?;
288 Ok(Self {
289 backend: Backend::Pcm,
290 config,
291 codec_rate,
292 codec_channels,
293 bitrate,
294 pre_skip: 0,
295 lookahead: 0,
296 frame_size,
297 started: false,
298 })
299 }
300
301 fn configure_opus(
302 inner: *mut OpusEncoder,
303 config: &Config,
304 codec_rate: u32,
305 codec_channels: u32,
306 ) -> Result<(u64, usize, u16), Error> {
307 if let Some(bitrate) = config.bitrate {
308 Self::set_opus_bitrate(inner, codec_channels, bitrate as u64)?;
309 }
310 Self::set_opus_ctl(
311 inner,
312 OPUS_SET_INBAND_FEC_REQUEST,
313 i32::from(config.fec),
314 "OPUS_SET_INBAND_FEC",
315 )?;
316 Self::set_opus_ctl(inner, OPUS_SET_DTX_REQUEST, i32::from(config.dtx), "OPUS_SET_DTX")?;
317
318 let bitrate = Self::get_opus_ctl(inner, OPUS_GET_BITRATE_REQUEST, "OPUS_GET_BITRATE")?;
319 let bitrate = u64::try_from(bitrate)
320 .map_err(|_| Error::Unsupported(format!("Opus reported negative bitrate {bitrate}")))?;
321 let lookahead = Self::get_opus_ctl(inner, OPUS_GET_LOOKAHEAD_REQUEST, "OPUS_GET_LOOKAHEAD")?;
322 let lookahead = u64::try_from(lookahead)
323 .map_err(|_| Error::Unsupported(format!("Opus reported negative lookahead {lookahead}")))?;
324 let pre_skip = u16::try_from((lookahead * 48_000) / codec_rate as u64)
325 .map_err(|_| Error::Unsupported(format!("Opus lookahead {lookahead} does not fit in OpusHead")))?;
326 let lookahead = usize::try_from(lookahead)
327 .map_err(|_| Error::Unsupported(format!("Opus lookahead {lookahead} does not fit in memory")))?;
328
329 Ok((bitrate, lookahead, pre_skip))
330 }
331
332 fn set_opus_bitrate(inner: *mut OpusEncoder, channels: u32, bitrate: u64) -> Result<(), Error> {
333 let max = 300_000 * channels as u64;
334 if !(500..=max).contains(&bitrate) {
335 return Err(Error::Unsupported(format!(
336 "Opus bitrate must be between 500 and {max} bits per second for {channels} channel(s), got {bitrate}"
337 )));
338 }
339 Self::set_opus_ctl(inner, OPUS_SET_BITRATE_REQUEST, bitrate as i32, "OPUS_SET_BITRATE")
340 }
341
342 fn set_opus_ctl(inner: *mut OpusEncoder, request: i32, value: i32, name: &'static str) -> Result<(), Error> {
343 let rc = unsafe { opus_encoder_ctl_impl(inner, request, varargs![value]) };
345 if rc != OPUS_OK {
346 return Err(opus::error(rc, name));
347 }
348 Ok(())
349 }
350
351 fn get_opus_ctl(inner: *mut OpusEncoder, request: i32, name: &'static str) -> Result<i32, Error> {
352 let mut value = 0;
353 let rc = unsafe { opus_encoder_ctl_impl(inner, request, varargs![&mut value]) };
356 if rc != OPUS_OK {
357 return Err(opus::error(rc, name));
358 }
359 Ok(value)
360 }
361
362 pub fn config(&self) -> &Config {
364 &self.config
365 }
366
367 pub fn codec(&self) -> Codec {
370 self.config.codec
371 }
372
373 pub fn codec_rate(&self) -> u32 {
376 self.codec_rate
377 }
378
379 pub fn codec_channels(&self) -> u32 {
382 self.codec_channels
383 }
384
385 pub fn frame_size(&self) -> usize {
388 self.frame_size
389 }
390
391 pub fn bitrate(&self) -> u64 {
393 self.bitrate
394 }
395
396 pub fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
398 let Backend::Opus(opus) = &mut self.backend else {
399 return Err(Error::Unsupported("pcm bitrate is fixed".into()));
400 };
401 if bitrate != self.bitrate {
402 Self::set_opus_bitrate(opus.inner, self.codec_channels, bitrate)?;
403 self.bitrate = bitrate;
404 self.config.bitrate = Some(bitrate as u32);
405 }
406 Ok(())
407 }
408
409 pub(super) fn reset(&mut self) {
411 if let Backend::Opus(opus) = &mut self.backend {
412 let rc = unsafe { opus_encoder_ctl_impl(opus.inner, OPUS_RESET_STATE, varargs![]) };
414 debug_assert_eq!(rc, OPUS_OK, "OPUS_RESET_STATE failed with {rc}");
415 }
416 self.started = false;
417 }
418
419 pub(super) fn started(&self) -> bool {
421 self.started
422 }
423
424 pub fn encode(&mut self, pcm: &[f32]) -> Result<Bytes, Error> {
430 let expected = self.frame_size * self.codec_channels as usize;
431 if pcm.len() != expected {
432 return Err(Error::Misaligned {
433 got: std::mem::size_of_val(pcm),
434 expected: expected * std::mem::size_of::<f32>(),
435 });
436 }
437 let packet = match &mut self.backend {
438 Backend::Opus(opus) => {
439 let n = unsafe {
442 opus_encode_float(
443 opus.inner,
444 pcm.as_ptr(),
445 self.frame_size as i32,
446 opus.scratch.as_mut_ptr(),
447 opus.scratch.len() as i32,
448 )
449 };
450 if n < 0 {
451 return Err(crate::opus::error(n, "opus_encode_float"));
452 }
453 Bytes::copy_from_slice(&opus.scratch[..n as usize])
454 }
455 Backend::Pcm => {
456 let mut payload = Vec::with_capacity(std::mem::size_of_val(pcm));
457 for sample in pcm {
458 payload.extend_from_slice(&sample.to_le_bytes());
459 }
460 payload.into()
461 }
462 };
463 self.started = true;
464 Ok(packet)
465 }
466
467 pub fn finish(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 drain = self.lookahead.saturating_sub(padding);
511 let silence = vec![0.0; frame_samples];
512 for _ in 0..drain.div_ceil(self.frame_size) {
513 packets.push(self.encode(&silence)?);
514 }
515
516 let discard_padding = packets
517 .len()
518 .saturating_mul(self.frame_size)
519 .saturating_sub(self.lookahead)
520 .saturating_sub(source_frames);
521
522 Ok(Finish {
523 packets,
524 discard_padding,
525 })
526 }
527
528 pub fn catalog(&self) -> hang::catalog::AudioConfig {
530 match self.config.codec {
531 Codec::Opus => {
532 let head = moq_mux::codec::opus::Config::new(self.codec_rate, self.codec_channels)
535 .with_pre_skip(self.pre_skip)
536 .encode()
537 .expect("opus encoder channels validated to mono/stereo");
538
539 let mut config = hang::catalog::AudioConfig::new(
540 hang::catalog::AudioCodec::Opus,
541 self.codec_rate,
542 self.codec_channels,
543 );
544 config.bitrate = self.config.bitrate.map(u64::from);
545 config.description = Some(head);
546 config.container = hang::catalog::Container::Legacy;
547 config
548 }
549 Codec::Pcm => {
550 let mut config = hang::catalog::AudioConfig::new(
551 hang::catalog::AudioCodec::Pcm,
552 self.codec_rate,
553 self.codec_channels,
554 );
555 config.bitrate = Some(
556 pcm::bitrate(self.codec_rate, self.codec_channels)
557 .expect("pcm encoder bitrate validated at construction"),
558 );
559 config.container = hang::catalog::Container::Legacy;
560 config
561 }
562 }
563 }
564}
565
566impl Drop for Opus {
567 fn drop(&mut self) {
568 unsafe { opus_encoder_destroy(self.inner) };
570 }
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576 use crate::decode::Decoder;
577
578 fn sine(freq: f32, sample_rate: u32, channels: u32, frames: usize) -> Vec<f32> {
579 let mut out = Vec::with_capacity(frames * channels as usize);
580 for i in 0..frames {
581 let t = i as f32 / sample_rate as f32;
582 let v = (2.0 * std::f32::consts::PI * freq * t).sin() * 0.5;
583 for _ in 0..channels {
584 out.push(v);
585 }
586 }
587 out
588 }
589
590 fn stereo_48k() -> Input {
591 Input {
592 format: Format::F32,
593 sample_rate: 48_000,
594 channels: 2,
595 }
596 }
597
598 fn opus_inner(encoder: &Encoder) -> *mut OpusEncoder {
599 let Backend::Opus(opus) = &encoder.backend else {
600 panic!("expected Opus encoder");
601 };
602 opus.inner
603 }
604
605 #[test]
606 fn opus_encode_then_decode_keeps_signal_close() {
607 let mut enc = Encoder::new(&Config {
608 bitrate: Some(96_000),
609 ..Config::new(stereo_48k())
610 })
611 .unwrap();
612
613 let cfg = enc.catalog();
614 let mut dec = Decoder::new(&cfg).unwrap();
615
616 let frame = sine(440.0, 48_000, 2, enc.frame_size());
617 for _ in 0..5 {
618 let pkt = enc.encode(&frame).unwrap();
619 let _ = dec.decode(&pkt).unwrap();
620 }
621
622 let pkt = enc.encode(&frame).unwrap();
623 let decoded = dec.decode(&pkt).unwrap();
624 assert_eq!(decoded.len(), frame.len());
625
626 let energy_in: f32 = frame.iter().map(|s| s * s).sum();
627 let energy_out: f32 = decoded.iter().map(|s| s * s).sum();
628 let ratio = energy_out / energy_in;
629 assert!(
630 (0.5..2.0).contains(&ratio),
631 "output energy ratio {ratio:.3} should be close to 1"
632 );
633 }
634
635 #[test]
636 fn opus_rejects_unsupported_frame_duration() {
637 let err = Encoder::new(&Config {
638 frame_duration: Duration::from_millis(15),
639 ..Config::new(Input::default())
640 });
641 assert!(matches!(err, Err(Error::Unsupported(_))));
642 }
643
644 #[test]
645 fn opus_rejects_misaligned_input() {
646 let mut enc = Encoder::new(&Config::new(Input::default())).unwrap();
647 assert!(matches!(enc.encode(&[0.0f32; 100]), Err(Error::Misaligned { .. })));
648 }
649
650 #[test]
651 fn opus_catalog_includes_opushead() {
652 let enc = Encoder::new(&Config {
653 bitrate: Some(64_000),
654 ..Config::new(stereo_48k())
655 })
656 .unwrap();
657 let cfg = enc.catalog();
658 assert_eq!(cfg.sample_rate, 48_000);
659 assert_eq!(cfg.channel_count, 2);
660 assert_eq!(cfg.bitrate, Some(64_000));
661 let desc = cfg.description.expect("OpusHead should be present");
662 assert_eq!(desc.len(), 19);
663 let head = moq_mux::codec::opus::Config::parse(&mut desc.as_ref()).unwrap();
664 assert_eq!(head.pre_skip, enc.pre_skip);
665 assert_eq!(head.pre_skip, 312);
666 }
667
668 #[test]
669 fn opus_decoder_trims_encoder_lookahead_once() {
670 let mut enc = Encoder::new(&Config::new(stereo_48k())).unwrap();
671 let mut dec = Decoder::new(&enc.catalog()).unwrap();
672 let frame = vec![0.0; enc.frame_size() * enc.codec_channels() as usize];
673
674 let first = dec.decode(&enc.encode(&frame).unwrap()).unwrap();
675 assert_eq!(
676 first.len(),
677 (enc.frame_size() - enc.pre_skip as usize) * enc.codec_channels() as usize
678 );
679
680 let second = dec.decode(&enc.encode(&frame).unwrap()).unwrap();
681 assert_eq!(second.len(), frame.len());
682 }
683
684 #[test]
685 fn opus_finish_accounts_for_partial_frame_padding() {
686 let enc = Encoder::new(&Config::new(Input {
687 channels: 1,
688 ..Input::default()
689 }))
690 .unwrap();
691
692 let packets = enc.finish(&vec![0.0; 600]).unwrap();
695 assert_eq!(packets.packets().len(), 1);
696 assert_eq!(packets.discard_padding(), 48);
697 }
698
699 #[test]
700 fn opus_finish_drains_lookahead_across_multiple_short_packets() {
701 let mut enc = Encoder::new(&Config {
702 frame_duration: Duration::from_micros(2_500),
703 ..Config::new(Input {
704 channels: 1,
705 ..Input::default()
706 })
707 })
708 .unwrap();
709 let frame = vec![0.0; enc.frame_size()];
710 enc.encode(&frame).unwrap();
711
712 let packets = enc.finish(&[]).unwrap();
714 assert_eq!(packets.packets().len(), 3);
715 assert_eq!(packets.discard_padding(), 48);
716 }
717
718 #[test]
719 fn reset_drops_pending_opus_lookahead() {
720 let mut enc = Encoder::new(&Config::new(Input {
721 channels: 1,
722 ..Input::default()
723 }))
724 .unwrap();
725 let mut old = vec![0.0; enc.frame_size()];
726 old[enc.frame_size() - 1] = 1.0;
727 enc.encode(&old).unwrap();
728
729 enc.reset();
730 let next = vec![0.0; enc.frame_size()];
731 let actual = enc.encode(&next).unwrap();
732 let mut decoder = Decoder::new(&enc.catalog()).unwrap();
733 let decoded = decoder.decode(&actual).unwrap();
734 let peak = decoded.iter().fold(0.0f32, |peak, sample| peak.max(sample.abs()));
735 assert!(peak < 0.001, "pre-reset impulse leaked into the next epoch: {peak}");
736
737 enc.reset();
738 let finish = enc.finish(&[]).unwrap();
739 assert!(finish.packets().is_empty());
740 assert_eq!(finish.discard_padding(), 0);
741 }
742
743 #[test]
744 fn opus_runtime_bitrate_updates_encoder_state() {
745 let mut enc = Encoder::new(&Config {
746 bitrate: Some(64_000),
747 ..Config::new(stereo_48k())
748 })
749 .unwrap();
750
751 enc.set_bitrate(32_000).unwrap();
752 assert_eq!(enc.bitrate(), 32_000);
753 assert_eq!(enc.config().bitrate, Some(32_000));
754 assert_eq!(
755 Encoder::get_opus_ctl(
756 opus_inner(&enc),
757 unsafe_libopus::OPUS_GET_BITRATE_REQUEST,
758 "OPUS_GET_BITRATE"
759 )
760 .unwrap(),
761 32_000
762 );
763 }
764
765 #[test]
766 fn opus_runtime_bitrate_rejects_values_libopus_would_clamp() {
767 let mut enc = Encoder::new(&Config::new(stereo_48k())).unwrap();
768 let original = enc.bitrate();
769 assert!(enc.set_bitrate(1).is_err());
770 assert!(enc.set_bitrate(600_001).is_err());
771 assert_eq!(enc.bitrate(), original);
772 }
773
774 #[test]
775 fn opus_applies_fec_and_dtx_controls() {
776 let enc = Encoder::new(&Config {
777 fec: true,
778 dtx: true,
779 ..Config::new(stereo_48k())
780 })
781 .unwrap();
782
783 assert_eq!(
784 Encoder::get_opus_ctl(
785 opus_inner(&enc),
786 unsafe_libopus::OPUS_GET_INBAND_FEC_REQUEST,
787 "OPUS_GET_INBAND_FEC"
788 )
789 .unwrap(),
790 1
791 );
792 assert_eq!(
793 Encoder::get_opus_ctl(opus_inner(&enc), unsafe_libopus::OPUS_GET_DTX_REQUEST, "OPUS_GET_DTX").unwrap(),
794 1
795 );
796 }
797
798 #[test]
799 fn codec_roundtrips_as_str() {
800 assert_eq!(Codec::Opus.as_str(), "opus");
801 assert_eq!(Codec::Opus.to_string(), "opus");
802 assert_eq!("opus".parse::<Codec>().unwrap(), Codec::Opus);
803 assert_eq!(Codec::Pcm.as_str(), "pcm");
804 assert_eq!(Codec::Pcm.to_string(), "pcm");
805 assert_eq!("pcm".parse::<Codec>().unwrap(), Codec::Pcm);
806 assert!("aac".parse::<Codec>().is_err());
807 }
808
809 #[test]
810 fn config_sample_rate_overrides_the_codec_rate() {
811 let enc = Encoder::new(&Config {
812 sample_rate: Some(24_000),
813 ..Config::new(Input {
814 sample_rate: 48_000,
815 channels: 1,
816 ..Input::default()
817 })
818 })
819 .unwrap();
820 assert_eq!(enc.codec_rate(), 24_000);
821 assert_eq!(enc.catalog().sample_rate, 24_000);
822 assert_eq!(enc.pre_skip, 312);
823 }
824
825 #[test]
826 fn pcm_roundtrip_is_lossless() {
827 let mut enc = Encoder::new(&Config {
828 codec: Codec::Pcm,
829 ..Config::new(stereo_48k())
830 })
831 .unwrap();
832 let mut dec = Decoder::new(&enc.catalog()).unwrap();
833 let input = sine(440.0, enc.codec_rate(), enc.codec_channels(), enc.frame_size());
834
835 let packet = enc.encode(&input).unwrap();
836 let output = dec.decode(&packet).unwrap();
837
838 assert_eq!(output, input);
839 }
840
841 #[test]
842 fn pcm_catalog_declares_fixed_bitrate() {
843 let enc = Encoder::new(&Config {
844 codec: Codec::Pcm,
845 ..Config::new(stereo_48k())
846 })
847 .unwrap();
848 let catalog = enc.catalog();
849
850 assert_eq!(catalog.codec, hang::catalog::AudioCodec::Pcm);
851 assert_eq!(catalog.bitrate, Some(48_000 * 2 * 32));
852 assert_eq!(catalog.description, None);
853 }
854
855 #[test]
856 fn pcm_rejects_runtime_bitrate_change() {
857 let mut enc = Encoder::new(&Config {
858 codec: Codec::Pcm,
859 ..Config::new(stereo_48k())
860 })
861 .unwrap();
862 let bitrate = enc.bitrate();
863
864 assert!(matches!(enc.set_bitrate(bitrate), Err(Error::Unsupported(_))));
865 assert_eq!(enc.bitrate(), bitrate);
866 }
867
868 #[test]
869 fn pcm_rejects_fractional_sample_frame_duration() {
870 let err = Encoder::new(&Config {
871 codec: Codec::Pcm,
872 frame_duration: Duration::from_micros(2_500),
873 ..Config::new(Input {
874 sample_rate: 44_100,
875 ..Input::default()
876 })
877 });
878 assert!(matches!(err, Err(Error::Unsupported(_))));
879 }
880
881 #[test]
882 fn pcm_rejects_bitrate_overflow() {
883 let err = Encoder::new(&Config {
884 codec: Codec::Pcm,
885 frame_duration: Duration::from_secs(1),
886 ..Config::new(Input {
887 sample_rate: u32::MAX,
888 channels: u32::MAX,
889 ..Input::default()
890 })
891 });
892 assert!(matches!(err, Err(Error::Unsupported(_))));
893 }
894}