1#![allow(dead_code)] use std::collections::VecDeque;
29
30pub mod bitio;
31pub mod decode;
32pub mod encode;
33pub mod error;
34pub mod frame;
35pub mod header;
36pub mod tables;
37
38#[cfg(feature = "lab")]
41pub mod lab;
42
43#[cfg(feature = "prometheus-telemetry")]
49pub mod prometheus_telemetry {
50 pub use crate::encode::psychoacoustic::prometheus::*;
51}
52
53pub use decode::Mp3Decode;
54pub use encode::Mp3Encode;
55pub use error::{Error, Result};
56use header::FrameHeader;
57
58#[derive(Debug, Clone)]
62pub struct DecodedAudio {
63 pub sample_rate: u32,
64 pub channels: u16,
65 pub samples: Vec<f32>,
66}
67
68#[derive(Default)]
72pub struct Mp3Decoder {
73 state: Mp3Decode,
74 buf: Vec<u8>,
76 queue: VecDeque<DecodedAudio>,
77 eof: bool,
78}
79
80impl Mp3Decoder {
81 pub fn new() -> Mp3Decoder {
82 Mp3Decoder::default()
83 }
84
85 pub fn push(&mut self, bytes: &[u8]) {
88 self.buf.extend_from_slice(bytes);
89 self.parse_frames();
90 }
91
92 pub fn next_frame(&mut self) -> Result<DecodedAudio> {
95 if let Some(frame) = self.queue.pop_front() {
96 return Ok(frame);
97 }
98 if self.eof {
99 Err(Error::Eof)
100 } else {
101 Err(Error::Again)
102 }
103 }
104
105 pub fn flush(&mut self) {
108 self.eof = true;
109 }
110
111 fn parse_frames(&mut self) {
115 let mut pos = 0;
116 while pos + 4 <= self.buf.len() {
117 if self.buf[pos] != 0xFF || self.buf[pos + 1] & 0xE0 != 0xE0 {
119 pos += 1;
120 continue;
121 }
122 let hb = [
123 self.buf[pos],
124 self.buf[pos + 1],
125 self.buf[pos + 2],
126 self.buf[pos + 3],
127 ];
128 let header = match FrameHeader::parse(hb) {
129 Ok(h) => h,
130 Err(_) => {
131 pos += 1;
132 continue;
133 }
134 };
135 let frame_size = header.frame_size();
136 if frame_size < 4 {
137 pos += 1;
138 continue;
139 }
140 if pos + frame_size > self.buf.len() {
141 break; }
143
144 let crc = if header.crc_protected { 2 } else { 0 };
145 let si_start = pos + 4 + crc;
146 let si_len = header.side_info_len();
147 let main_start = si_start + si_len;
148 if main_start > pos + frame_size {
149 pos += 1;
150 continue;
151 }
152 let side_info = self.buf[si_start..main_start].to_vec();
153 let main_data = self.buf[main_start..pos + frame_size].to_vec();
154
155 if let Ok(pcm) = self.state.decode_frame(&header, &side_info, &main_data) {
156 let channels = header.channel_mode.channels().max(1);
157 self.queue.push_back(DecodedAudio {
158 sample_rate: header.sample_rate,
159 channels: channels as u16,
160 samples: pcm,
161 });
162 }
163 pos += frame_size;
164 }
165 self.buf.drain(0..pos);
166 }
167}
168
169#[derive(Debug, Clone, Default)]
171pub struct Mp3EncoderConfig {
172 pub bitrate_kbps: u32,
176 pub vbr_quality: Option<f32>,
179}
180
181pub fn vbr_quality_index(q: f32) -> f32 {
184 10f32.powf((q.clamp(0.0, 9.0) * 2.0) / 10.0)
185}
186
187#[derive(Default)]
199pub struct Mp3Encoder {
200 state: Mp3Encode,
201 header: Option<FrameHeader>,
202 pcm: [Vec<f32>; 2],
204 queue: VecDeque<Vec<u8>>,
205 total_frames: u32,
207 total_bytes: usize,
208 cbr_kbps: u32,
210 quality: Option<f32>,
212 reservoir: bool,
216 resv_gain: f32,
217 resv_lookahead: bool,
222 resv_frames_pcm: Vec<(FrameHeader, Vec<Vec<f32>>)>,
223 eof: bool,
224}
225
226pub fn snap_bitrate(version: header::MpegVersion, kbps: u32) -> u32 {
229 let v1 = [
230 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
231 ];
232 let v2 = [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160];
233 let valid: &[u32] = if version == header::MpegVersion::V1 {
234 &v1
235 } else {
236 &v2
237 };
238 *valid
239 .iter()
240 .min_by_key(|&&b| b.abs_diff(kbps))
241 .unwrap_or(&128)
242}
243
244pub fn encoder_header(sample_rate: u32, channels: u16, cbr_kbps: u32) -> Result<FrameHeader> {
247 let version = match sample_rate {
248 32000 | 44100 | 48000 => header::MpegVersion::V1,
249 16000 | 22050 | 24000 => header::MpegVersion::V2,
250 8000 | 11025 | 12000 => header::MpegVersion::V2_5,
251 _ => {
252 return Err(Error::unsupported(
253 "mp3 encode: unsupported sample rate (need 8–48 kHz MPEG-1/2/2.5)",
254 ))
255 }
256 };
257 let channel_mode = if channels >= 2 {
258 frame::ChannelMode::Stereo
259 } else {
260 frame::ChannelMode::Mono
261 };
262 let default_kbps = if version == header::MpegVersion::V1 {
264 128
265 } else {
266 64
267 };
268 let bitrate_kbps = snap_bitrate(
269 version,
270 if cbr_kbps == 0 {
271 default_kbps
272 } else {
273 cbr_kbps
274 },
275 );
276 Ok(FrameHeader {
277 version,
278 crc_protected: false,
279 bitrate_kbps,
280 sample_rate,
281 padding: false,
282 channel_mode,
283 copyright: false,
284 original: true,
285 emphasis: 0,
286 })
287}
288
289impl Mp3Encoder {
290 pub fn new(config: Mp3EncoderConfig) -> Mp3Encoder {
291 Mp3Encoder {
292 cbr_kbps: config.bitrate_kbps,
293 quality: config.vbr_quality,
294 ..Mp3Encoder::default()
295 }
296 }
297
298 pub fn push_pcm_f32(
304 &mut self,
305 interleaved: &[f32],
306 channels: u16,
307 sample_rate: u32,
308 ) -> Result<()> {
309 if self.header.is_none() {
310 self.header = Some(encoder_header(sample_rate, channels, self.cbr_kbps)?);
311 let is_v1 = self.header.as_ref().unwrap().version == crate::header::MpegVersion::V1;
325 self.reservoir = self.quality.is_none()
326 && is_v1
327 && match std::env::var("MP3_RESERVOIR") {
328 Ok(v) => v != "0", Err(_) => self.cbr_kbps <= 256, };
331 self.resv_gain = std::env::var("MP3_RESV_GAIN")
334 .ok()
335 .and_then(|s| s.parse().ok())
336 .unwrap_or(0.2);
337 self.resv_lookahead = std::env::var("MP3_RESV_LOOKAHEAD").is_ok_and(|v| v != "0");
343 }
344 let nch = self.header.as_ref().unwrap().channel_mode.channels();
345 let in_ch = (channels as usize).max(1);
346 let samples = interleaved.len() / in_ch;
349 for s in 0..samples {
350 for c in 0..nch {
351 let ic = c.min(in_ch - 1);
352 self.pcm[c].push(interleaved[s * in_ch + ic]);
353 }
354 }
355 self.drain_frames();
356 Ok(())
357 }
358
359 pub fn push_pcm_s16(
363 &mut self,
364 interleaved: &[i16],
365 channels: u16,
366 sample_rate: u32,
367 ) -> Result<()> {
368 let f32s: Vec<f32> = interleaved.iter().map(|&s| s as f32 / 32768.0).collect();
369 self.push_pcm_f32(&f32s, channels, sample_rate)
370 }
371
372 pub fn next_packet(&mut self) -> Result<Vec<u8>> {
376 if let Some(p) = self.queue.pop_front() {
377 return Ok(p);
378 }
379 if self.eof {
380 Err(Error::Eof)
381 } else {
382 Err(Error::Again)
383 }
384 }
385
386 pub fn finish(&mut self) {
390 if let Some(header) = self.header.clone() {
392 let nch = header.channel_mode.channels();
393 let spf = header.version.samples_per_frame();
394 if (0..nch).any(|c| !self.pcm[c].is_empty()) {
395 for c in 0..nch {
396 let padded = self.pcm[c].len().div_ceil(spf) * spf;
397 self.pcm[c].resize(padded, 0.0);
398 }
399 self.drain_frames();
400 }
401 if self.reservoir {
404 let fsize = header.frame_size();
405 let stream = if self.resv_lookahead {
406 let frames = std::mem::take(&mut self.resv_frames_pcm);
407 self.state
408 .encode_reservoir_lookahead(&frames, self.resv_gain)
409 } else {
410 self.state.finish_reservoir()
411 };
412 for chunk in stream.chunks(fsize) {
413 self.queue.push_back(chunk.to_vec());
414 }
415 }
416 if self.total_frames > 0 {
420 let fsize = header.frame_size() as u32;
421 let info = encode::bitstream::info_frame(
422 &header,
423 self.total_frames + 1,
424 self.total_bytes as u32 + fsize,
425 self.quality.is_some(),
426 );
427 self.queue.push_front(info);
428 }
429 }
430 self.eof = true;
431 }
432
433 fn drain_frames(&mut self) {
435 let Some(header) = self.header.clone() else {
436 return;
437 };
438 let nch = header.channel_mode.channels();
439 let spf = header.version.samples_per_frame();
440 let mut off = 0usize;
447 while self.pcm[0].len() - off >= spf && (nch == 1 || self.pcm[1].len() - off >= spf) {
448 let block: Vec<Vec<f32>> = (0..nch)
449 .map(|c| self.pcm[c][off..off + spf].to_vec())
450 .collect();
451 if self.reservoir && self.resv_lookahead {
452 self.resv_frames_pcm.push((header.clone(), block));
454 self.total_frames += 1;
455 self.total_bytes += header.frame_size();
456 } else if self.reservoir {
457 self.state
459 .encode_frame_reservoir(&header, &block, self.resv_gain);
460 self.total_frames += 1;
461 self.total_bytes += header.frame_size();
462 } else if let Ok(bytes) = self.state.encode_frame(&header, &block, self.quality) {
463 self.total_frames += 1;
464 self.total_bytes += bytes.len();
465 self.queue.push_back(bytes);
466 }
467 off += spf;
468 }
469 if off > 0 {
472 for c in 0..nch {
473 self.pcm[c].drain(0..off);
474 }
475 }
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 fn encode_mono(input: &[f32], sample_rate: u32) -> Vec<u8> {
484 let mut enc = Mp3Encoder::default();
485 enc.push_pcm_f32(input, 1, sample_rate).unwrap();
486 enc.finish();
487 let mut mp3 = Vec::new();
488 while let Ok(p) = enc.next_packet() {
489 mp3.extend_from_slice(&p);
490 }
491 mp3
492 }
493
494 fn encode_mono_s16(input: &[i16], sample_rate: u32) -> Vec<u8> {
496 let mut enc = Mp3Encoder::default();
497 enc.push_pcm_s16(input, 1, sample_rate).unwrap();
498 enc.finish();
499 let mut mp3 = Vec::new();
500 while let Ok(p) = enc.next_packet() {
501 mp3.extend_from_slice(&p);
502 }
503 mp3
504 }
505
506 #[test]
510 fn s16_push_encodes_to_audible_output() {
511 let sr = 44100u32;
512 let n = sr as usize; let s16: Vec<i16> = (0..n)
514 .map(|i| {
515 let t = i as f32 / sr as f32;
516 (0.5 * (2.0 * std::f32::consts::PI * 440.0 * t).sin() * 32767.0) as i16
517 })
518 .collect();
519 let out = decode_mono(encode_mono_s16(&s16, sr));
520 let rms = (out.iter().map(|x| x * x).sum::<f32>() / out.len().max(1) as f32).sqrt();
521 assert!(
522 rms > 0.1,
523 "S16 input produced near-silent output (rms={rms})"
524 );
525 }
526
527 #[test]
531 #[ignore]
532 fn profile_decode() {
533 let sr = 44100u32;
534 let n = 10 * sr as usize;
535 let mut s = 0x9E37_79B9u32;
536 let mut rng = || {
537 s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
538 (s >> 8) as f32 / (1u32 << 24) as f32 * 2.0 - 1.0
539 };
540 let input: Vec<f32> = (0..n)
542 .map(|i| {
543 let t = i as f32 / sr as f32;
544 let mut v = 0f32;
545 for k in 1..=24 {
546 v += (1.0 / k as f32)
547 * (2.0 * std::f32::consts::PI * 130.0 * k as f32 * t).sin();
548 }
549 (0.2 * v + 0.03 * rng()).clamp(-1.0, 1.0)
550 })
551 .collect();
552 let mp3 = encode_mono(&input, sr);
553 eprintln!("[profile] decoding {} KB of mp3 (~10 s)", mp3.len() / 1024);
554 let out = decode_mono(mp3);
555 eprintln!("[profile] {} PCM samples out", out.len());
556 crate::decode::prof::dump();
557 }
558
559 fn decode_mono(mp3: Vec<u8>) -> Vec<f32> {
560 let mut dec = Mp3Decoder::default();
561 dec.push(&mp3);
562 dec.flush();
563 let mut out = Vec::new();
564 while let Ok(af) = dec.next_frame() {
565 out.extend_from_slice(&af.samples);
566 }
567 out
568 }
569
570 fn best_snr(reference: &[f32], out: &[f32]) -> f64 {
573 let (mut best, skip) = (f64::NEG_INFINITY, 2304usize);
574 for delay in 0..3000 {
575 let mut sig = 0f64;
576 let mut err = 0f64;
577 let mut n = 0;
578 let mut i = skip;
579 while i + delay < out.len() && i < reference.len() {
580 let r = reference[i] as f64;
581 sig += r * r;
582 err += (r - out[i + delay] as f64).powi(2);
583 n += 1;
584 i += 1;
585 }
586 if n > 5000 && err > 0.0 {
587 best = best.max(10.0 * (sig / err).log10());
588 }
589 }
590 best
591 }
592
593 #[test]
595 fn encode_decode_stereo() {
596 let sr = 44100u32;
597 let frames = 16;
598 let n = frames * 1152;
599 let pi2 = 2.0 * std::f32::consts::PI;
600 let mut interleaved = Vec::with_capacity(n * 2);
602 for i in 0..n {
603 let t = i as f32 / sr as f32;
604 interleaved.push(0.35 * (pi2 * 700.0 * t).sin()); interleaved.push(0.30 * (pi2 * 3000.0 * t).sin()); }
607
608 let mut enc = Mp3Encoder::default();
609 enc.push_pcm_f32(&interleaved, 2, sr).unwrap();
610 enc.finish();
611 let mut mp3 = Vec::new();
612 while let Ok(p) = enc.next_packet() {
613 mp3.extend_from_slice(&p);
614 }
615 assert!(!mp3.is_empty());
616 if let Ok(path) = std::env::var("MP3_ENC_OUT") {
617 std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
618 }
619
620 let mut dec = Mp3Decoder::default();
622 dec.push(&mp3);
623 dec.flush();
624 let (mut left, mut right) = (Vec::new(), Vec::new());
625 while let Ok(af) = dec.next_frame() {
626 assert_eq!(af.channels, 2, "decoded stream must be stereo");
627 for fr in af.samples.chunks_exact(2) {
628 left.push(fr[0]);
629 right.push(fr[1]);
630 }
631 }
632
633 let ref_l: Vec<f32> = (0..n)
634 .map(|i| 0.35 * (pi2 * 700.0 * i as f32 / sr as f32).sin())
635 .collect();
636 let ref_r: Vec<f32> = (0..n)
637 .map(|i| 0.30 * (pi2 * 3000.0 * i as f32 / sr as f32).sin())
638 .collect();
639 let snr_l = best_snr(&ref_l, &left);
640 let snr_r = best_snr(&ref_r, &right);
641 eprintln!("[R1] stereo SNR L {snr_l:.1} dB R {snr_r:.1} dB");
642 assert!(
643 snr_l > 20.0 && snr_r > 20.0,
644 "stereo channels too noisy: L {snr_l} R {snr_r}"
645 );
646 }
647
648 #[test]
653 fn encode_decode_mpeg2() {
654 let dump_dir = std::env::var("MP3_ENC_DIR").ok();
655 for &sr in &[22050u32, 24000, 16000, 12000, 11025, 8000] {
656 let n = 24 * 576; let input: Vec<f32> = (0..n)
658 .map(|i| 0.4 * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
659 .collect();
660
661 let mp3 = encode_mono(&input, sr);
662 assert!(!mp3.is_empty());
663 if let Some(dir) = &dump_dir {
664 std::fs::write(format!("{dir}/v2_{sr}.mp3"), &mp3).expect("dump");
665 }
666 let h = header::FrameHeader::parse([mp3[0], mp3[1], mp3[2], mp3[3]]).unwrap();
667 assert_ne!(
668 h.version,
669 header::MpegVersion::V1,
670 "{sr}: must be MPEG-2/2.5"
671 );
672 assert_eq!(h.sample_rate, sr);
673
674 let out = decode_mono(mp3);
675 assert!(out.len() > n / 2);
676 let snr = best_snr(&input, &out);
677 eprintln!("[R5] MPEG-2 {sr} Hz round-trip SNR {snr:.1} dB");
678 assert!(
679 snr > 30.0,
680 "{sr}: MPEG-2 round-trip SNR too low: {snr:.1} dB"
681 );
682 }
683 }
684
685 #[test]
689 fn block_switching_on_transients() {
690 let sr = 44100u32;
691 let frames = 20;
692 let n = frames * 1152;
693 let mut s = 0xBEEF_1234u32;
694 let input: Vec<f32> = (0..n)
696 .map(|i| {
697 if i % 1500 < 80 {
698 s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
699 0.7 * ((s >> 8) as f32 / (1u32 << 24) as f32 - 0.5)
700 } else {
701 0.0
702 }
703 })
704 .collect();
705
706 let mp3 = encode_mono(&input, sr);
707 assert!(!mp3.is_empty());
708 if let Ok(path) = std::env::var("MP3_ENC_OUT") {
709 std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
710 }
711
712 let si_len = header::FrameHeader::parse([mp3[0], mp3[1], mp3[2], mp3[3]])
717 .map(|h| h.side_info_len())
718 .unwrap_or(17);
719 let mut switched = 0;
720 let mut pos = 0;
721 while pos + 4 <= mp3.len() {
722 if mp3[pos] == 0xFF && mp3[pos + 1] & 0xE0 == 0xE0 {
723 if let Ok(h) =
724 header::FrameHeader::parse([mp3[pos], mp3[pos + 1], mp3[pos + 2], mp3[pos + 3]])
725 {
726 let si = &mp3[pos + 4..pos + 4 + si_len];
727 if let Ok(parsed) = decode::sideinfo::parse(&h, si) {
728 if (0..h.version.granules())
729 .any(|gr| parsed.granules[gr][0].window_switching)
730 {
731 switched += 1;
732 }
733 }
734 pos += h.frame_size();
735 continue;
736 }
737 }
738 pos += 1;
739 }
740 eprintln!("[Q5] window-switched frames: {switched}/{frames}");
741 assert!(
742 switched >= 3,
743 "transients must trigger block switching, got {switched}"
744 );
745
746 let mut dec = Mp3Decoder::default();
748 dec.push(&mp3);
749 dec.flush();
750 let mut decoded = 0;
751 while let Ok(_) = dec.next_frame() {
752 decoded += 1;
753 }
754 assert!(decoded >= frames, "all frames must decode, got {decoded}");
755 }
756
757 #[test]
761 fn conformance_corpus_round_trips() {
762 let sr = 44100u32;
763 let frames = 14;
764 let n = frames * 1152;
765 let pi2 = std::f32::consts::TAU;
766 let tone = |f: f32| -> Vec<f32> {
767 (0..n)
768 .map(|i| 0.4 * (pi2 * f * i as f32 / sr as f32).sin())
769 .collect()
770 };
771 let noise = |seed: u32, amp: f32| -> Vec<f32> {
772 let mut s = seed;
773 (0..n)
774 .map(|_| {
775 s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
776 amp * ((s >> 8) as f32 / (1u32 << 24) as f32 - 0.5)
777 })
778 .collect()
779 };
780 let sweep: Vec<f32> = {
781 let mut ph = 0f32;
782 (0..n)
783 .map(|i| {
784 let f = 80.0 + (16000.0 - 80.0) * (i as f32 / n as f32);
785 ph += pi2 * f / sr as f32;
786 0.4 * ph.sin()
787 })
788 .collect()
789 };
790 let corpus: Vec<(&str, Vec<f32>, f64)> = vec![
793 ("tone-250", tone(250.0), 40.0),
794 ("tone-1k", tone(1000.0), 50.0),
795 ("tone-4k", tone(4000.0), 45.0),
796 ("tone-12k", tone(12000.0), 30.0),
797 ("sweep", sweep, 15.0),
798 ("noise", noise(0xC0FFEE, 0.4), 5.0),
799 ("quiet-noise", noise(0x1234, 0.02), 3.0),
800 (
801 "two-tone",
802 {
803 let a = tone(600.0);
804 let b = tone(5200.0);
805 a.iter().zip(&b).map(|(x, y)| 0.5 * (x + y)).collect()
806 },
807 25.0,
808 ),
809 ];
810
811 let dump_dir = std::env::var("MP3_ENC_DIR").ok();
812 for (name, sig, floor) in &corpus {
813 let mp3 = encode_mono(sig, sr);
814 assert!(!mp3.is_empty(), "{name}: no output");
815 if let Some(dir) = &dump_dir {
816 std::fs::write(format!("{dir}/{name}.mp3"), &mp3).expect("dump");
817 }
818 let out = decode_mono(mp3);
819 assert!(out.len() > n / 2, "{name}: too few samples");
820 let snr = best_snr(sig, &out);
821 eprintln!("[R4] {name:<12} SNR {snr:.1} dB (floor {floor})");
822 assert!(snr >= *floor, "{name}: SNR {snr:.1} below floor {floor}");
823 }
824 }
825
826 #[test]
829 fn vbr_varies_bitrate_and_round_trips() {
830 let sr = 44100u32;
831 let pi2 = 2.0 * std::f32::consts::PI;
832 let mut input = Vec::new();
834 let mut s = 0x1234_5678u32;
835 for f in 0..20 {
836 for i in 0..1152 {
837 let t = (f * 1152 + i) as f32 / sr as f32;
838 if f < 10 {
839 input.push(0.05 * (pi2 * 500.0 * t).sin()); } else {
841 s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
842 input.push(0.6 * ((s >> 8) as f32 / (1u32 << 24) as f32 - 0.5));
843 }
845 }
846 }
847
848 let mut enc = Mp3Encoder::new(Mp3EncoderConfig {
850 bitrate_kbps: 0,
851 vbr_quality: Some(vbr_quality_index(3.0)),
852 });
853 enc.push_pcm_f32(&input, 1, sr).unwrap();
854 enc.finish();
855 let mut mp3 = Vec::new();
856 while let Ok(p) = enc.next_packet() {
857 mp3.extend_from_slice(&p);
858 }
859 if let Ok(path) = std::env::var("MP3_ENC_OUT") {
860 std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
861 }
862
863 let mut bitrates = std::collections::BTreeSet::new();
865 let mut pos = 0;
866 while pos + 4 <= mp3.len() {
867 if mp3[pos] == 0xFF && mp3[pos + 1] & 0xE0 == 0xE0 {
868 if let Ok(h) =
869 header::FrameHeader::parse([mp3[pos], mp3[pos + 1], mp3[pos + 2], mp3[pos + 3]])
870 {
871 bitrates.insert(h.bitrate_kbps);
872 pos += h.frame_size();
873 continue;
874 }
875 }
876 pos += 1;
877 }
878 eprintln!("[R2] VBR bitrates used: {bitrates:?}");
879 assert!(
880 bitrates.len() >= 2,
881 "VBR must vary the bitrate, got {bitrates:?}"
882 );
883
884 let mut dec = Mp3Decoder::default();
886 dec.push(&mp3);
887 dec.flush();
888 let mut frames = 0;
889 while let Ok(_) = dec.next_frame() {
890 frames += 1;
891 }
892 assert!(frames >= 20, "expected all frames to decode, got {frames}");
893 }
894
895 #[test]
898 fn encode_decode_joint_stereo() {
899 let sr = 44100u32;
900 let n = 16 * 1152;
901 let pi2 = 2.0 * std::f32::consts::PI;
902 let mut interleaved = Vec::with_capacity(n * 2);
904 for i in 0..n {
905 let t = i as f32 / sr as f32;
906 let base = 0.4 * (pi2 * 900.0 * t).sin();
907 interleaved.push(base); interleaved.push(base * 0.95 + 0.02 * (pi2 * 1500.0 * t).sin()); }
910
911 let mut enc = Mp3Encoder::default();
912 enc.push_pcm_f32(&interleaved, 2, sr).unwrap();
913 enc.finish();
914 let mut mp3 = Vec::new();
915 while let Ok(p) = enc.next_packet() {
916 mp3.extend_from_slice(&p);
917 }
918 if let Ok(path) = std::env::var("MP3_ENC_OUT") {
919 std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
920 }
921
922 let joint = mp3
924 .windows(2)
925 .step_by(1)
926 .any(|w| w[0] == 0xFF && (w[1] & 0xE0) == 0xE0 && (w[1] & 0x06) == 0x02);
927 assert!(joint, "no joint-stereo frame emitted");
928
929 let mut dec = Mp3Decoder::default();
930 dec.push(&mp3);
931 dec.flush();
932 let (mut left, mut right) = (Vec::new(), Vec::new());
933 while let Ok(af) = dec.next_frame() {
934 for fr in af.samples.chunks_exact(2) {
935 left.push(fr[0]);
936 right.push(fr[1]);
937 }
938 }
939 let ref_l: Vec<f32> = (0..n)
940 .map(|i| 0.4 * (pi2 * 900.0 * i as f32 / sr as f32).sin())
941 .collect();
942 let ref_r: Vec<f32> = (0..n)
943 .map(|i| {
944 let t = i as f32 / sr as f32;
945 0.4 * (pi2 * 900.0 * t).sin() * 0.95 + 0.02 * (pi2 * 1500.0 * t).sin()
946 })
947 .collect();
948 let snr_l = best_snr(&ref_l, &left);
949 let snr_r = best_snr(&ref_r, &right);
950 eprintln!("[R1+] joint-stereo SNR L {snr_l:.1} dB R {snr_r:.1} dB");
951 assert!(
952 snr_l > 20.0 && snr_r > 20.0,
953 "joint stereo too noisy: L {snr_l} R {snr_r}"
954 );
955 }
956
957 #[test]
962 fn encode_decode_pipeline_multitone() {
963 let sr = 44100u32;
964 let n = 16 * 1152;
965 let input: Vec<f32> = (0..n)
966 .map(|i| {
967 let t = i as f32 / sr as f32;
968 let pi2 = 2.0 * std::f32::consts::PI;
969 0.3 * (pi2 * 600.0 * t).sin()
970 + 0.2 * (pi2 * 2300.0 * t).sin()
971 + 0.12 * (pi2 * 9000.0 * t).sin()
972 })
973 .collect();
974
975 let mp3 = encode_mono(&input, sr);
976 assert!(!mp3.is_empty(), "encoder produced no data");
977 assert_eq!(mp3[0], 0xFF);
978 assert_eq!(mp3[1] & 0xE0, 0xE0);
979 if let Ok(path) = std::env::var("MP3_ENC_OUT") {
980 std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
981 }
982
983 let out = decode_mono(mp3);
984 assert!(out.len() > n / 2, "decoder produced too few samples");
985
986 let snr = best_snr(&input, &out);
987 eprintln!("[C4] encode→decode multitone SNR {snr:.1} dB");
988 assert!(snr > 20.0, "round-trip SNR too low: {snr:.1} dB");
989 }
990
991 #[test]
996 fn decode_real_mp3_structure() {
997 let Ok(path) = std::env::var("MP3_REF") else {
998 return; };
1000 let data = std::fs::read(&path).expect("read MP3_REF");
1001 let mut dec = Mp3Decoder::default();
1002 dec.push(&data);
1003 dec.flush();
1004
1005 let mut frames = 0usize;
1006 let mut samples = 0usize;
1007 let mut pcm: Vec<u8> = Vec::new();
1008 while let Ok(af) = dec.next_frame() {
1009 assert_eq!(af.sample_rate, 44100);
1010 assert_eq!(af.channels, 1);
1011 for s in &af.samples {
1012 pcm.extend_from_slice(&s.to_le_bytes());
1013 }
1014 frames += 1;
1015 samples += af.samples.len() / af.channels as usize;
1016 }
1017 eprintln!("[MP3] decoded frames={frames} samples={samples}");
1018 assert!(frames > 0, "must decode at least one frame from real data");
1019 if let Ok(out) = std::env::var("MP3_OUT") {
1020 std::fs::write(out, &pcm).expect("write MP3_OUT");
1021 }
1022 }
1023
1024 #[test]
1027 fn encode_wav_env() {
1028 let Ok(inp) = std::env::var("WAV_IN") else {
1029 return;
1030 };
1031 let d = std::fs::read(&inp).expect("read WAV_IN");
1032 let fmt = d.windows(4).position(|w| w == b"fmt ").unwrap() + 8;
1034 let audio_fmt = u16::from_le_bytes([d[fmt], d[fmt + 1]]);
1035 let rate = u32::from_le_bytes([d[fmt + 4], d[fmt + 5], d[fmt + 6], d[fmt + 7]]);
1036 let bits = u16::from_le_bytes([d[fmt + 14], d[fmt + 15]]);
1037 let data = d.windows(4).position(|w| w == b"data").unwrap() + 8;
1038 let pcm: Vec<f32> = if audio_fmt == 3 || bits == 32 {
1039 d[data..]
1040 .chunks_exact(4)
1041 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
1042 .collect()
1043 } else {
1044 d[data..]
1045 .chunks_exact(2)
1046 .map(|c| i16::from_le_bytes([c[0], c[1]]) as f32 / 32768.0)
1047 .collect()
1048 };
1049 let nch = u16::from_le_bytes([d[fmt + 2], d[fmt + 3]]).max(1);
1051 let mut enc = Mp3Encoder::new(Mp3EncoderConfig {
1052 bitrate_kbps: std::env::var("BR")
1053 .ok()
1054 .and_then(|s| s.parse().ok())
1055 .unwrap_or(128),
1056 vbr_quality: None,
1057 });
1058 enc.push_pcm_f32(&pcm, nch, rate).unwrap();
1059 enc.finish();
1060 let mut mp3 = Vec::new();
1061 while let Ok(p) = enc.next_packet() {
1062 mp3.extend_from_slice(&p);
1063 }
1064 std::fs::write(std::env::var("ENC_OUT").unwrap(), mp3).unwrap();
1065 }
1066
1067 #[test]
1070 fn encode_at_rate_to_file() {
1071 let Some(rate) = std::env::var("ENC_RATE")
1072 .ok()
1073 .and_then(|s| s.parse::<u32>().ok())
1074 else {
1075 return;
1076 };
1077 let n = rate as usize * 2;
1078 let mut pcm = vec![0f32; n];
1079 let mut s = 0x1234_5u32;
1080 for (i, v) in pcm.iter_mut().enumerate() {
1081 s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1082 let noise = (s >> 8) as f32 / (1u32 << 24) as f32 - 0.5;
1083 let t = i as f32 / rate as f32;
1084 *v = 0.3 * (2.0 * std::f32::consts::PI * 440.0 * t).sin() + 0.05 * noise;
1085 }
1086 let mp3 = encode_mono(&pcm, rate);
1087 std::fs::write(std::env::var("ENC_OUT").unwrap(), mp3).unwrap();
1088 }
1089
1090 #[test]
1093 fn decode_any_mp3_to_pcm() {
1094 let Ok(path) = std::env::var("MP3_REF2") else {
1095 return;
1096 };
1097 let data = std::fs::read(&path).expect("read MP3_REF2");
1098 let mut dec = Mp3Decoder::default();
1099 dec.push(&data);
1100 dec.flush();
1101 let mut pcm: Vec<u8> = Vec::new();
1102 while let Ok(af) = dec.next_frame() {
1103 for s in &af.samples {
1104 pcm.extend_from_slice(&s.to_le_bytes());
1105 }
1106 }
1107 assert!(!pcm.is_empty());
1108 if let Ok(out) = std::env::var("MP3_OUT2") {
1109 std::fs::write(out, &pcm).expect("write MP3_OUT2");
1110 }
1111 }
1112
1113 #[test]
1114 fn header_parse_roundtrip() {
1115 let bytes = [0xFF, 0xFB, 0x90, 0x00];
1117 let h = header::FrameHeader::parse(bytes).unwrap();
1118 assert_eq!(h.version, header::MpegVersion::V1);
1119 assert_eq!(h.bitrate_kbps, 128);
1120 assert_eq!(h.sample_rate, 44100);
1121 assert_eq!(h.channel_mode, frame::ChannelMode::Stereo);
1122 assert!(!h.crc_protected && !h.padding);
1123 assert_eq!(h.frame_size(), 417);
1124 assert_eq!(h.to_bytes(), bytes, "header must round-trip bit-exactly");
1125 }
1126
1127 #[test]
1128 fn header_rejects_non_layer3_and_bad_sync() {
1129 assert!(header::FrameHeader::parse([0xFF, 0xFD, 0x90, 0x00]).is_err());
1131 assert!(header::FrameHeader::parse([0x00, 0x00, 0x00, 0x00]).is_err());
1133 }
1134
1135 fn hdr(version: header::MpegVersion, mode: frame::ChannelMode) -> header::FrameHeader {
1136 header::FrameHeader {
1137 version,
1138 crc_protected: false,
1139 bitrate_kbps: 128,
1140 sample_rate: 44100,
1141 padding: false,
1142 channel_mode: mode,
1143 copyright: false,
1144 original: true,
1145 emphasis: 0,
1146 }
1147 }
1148
1149 #[test]
1150 fn sideinfo_bit_accounting_all_layouts() {
1151 use frame::ChannelMode::{Mono, Stereo};
1152 use header::MpegVersion::{V1, V2};
1153 for (v, m, len) in [
1155 (V1, Stereo, 32),
1156 (V1, Mono, 17),
1157 (V2, Stereo, 17),
1158 (V2, Mono, 9),
1159 ] {
1160 let h = hdr(v, m);
1161 assert_eq!(h.side_info_len(), len);
1162 let si = decode::sideinfo::parse(&h, &vec![0u8; len]).unwrap();
1165 assert_eq!(si.main_data_begin, 0);
1166 }
1167 }
1168
1169 #[test]
1170 fn frame_size_matches_spec_example() {
1171 let h = header::FrameHeader {
1173 version: header::MpegVersion::V1,
1174 crc_protected: false,
1175 bitrate_kbps: 128,
1176 sample_rate: 44100,
1177 padding: false,
1178 channel_mode: frame::ChannelMode::Stereo,
1179 copyright: false,
1180 original: true,
1181 emphasis: 0,
1182 };
1183 assert_eq!(h.frame_size(), 417);
1184 assert_eq!(h.side_info_len(), 32);
1185 }
1186}