#![allow(dead_code)]
use std::collections::VecDeque;
pub mod bitio;
pub mod decode;
pub mod encode;
pub mod error;
pub mod frame;
pub mod header;
pub mod tables;
#[cfg(feature = "lab")]
pub mod lab;
#[cfg(feature = "prometheus-telemetry")]
pub mod prometheus_telemetry {
pub use crate::encode::psychoacoustic::prometheus::*;
}
pub use decode::Mp3Decode;
pub use encode::Mp3Encode;
pub use error::{Error, Result};
use header::FrameHeader;
#[derive(Debug, Clone)]
pub struct DecodedAudio {
pub sample_rate: u32,
pub channels: u16,
pub samples: Vec<f32>,
}
#[derive(Default)]
pub struct Mp3Decoder {
state: Mp3Decode,
buf: Vec<u8>,
queue: VecDeque<DecodedAudio>,
eof: bool,
}
impl Mp3Decoder {
pub fn new() -> Mp3Decoder {
Mp3Decoder::default()
}
pub fn push(&mut self, bytes: &[u8]) {
self.buf.extend_from_slice(bytes);
self.parse_frames();
}
pub fn next_frame(&mut self) -> Result<DecodedAudio> {
if let Some(frame) = self.queue.pop_front() {
return Ok(frame);
}
if self.eof {
Err(Error::Eof)
} else {
Err(Error::Again)
}
}
pub fn flush(&mut self) {
self.eof = true;
}
fn parse_frames(&mut self) {
let mut pos = 0;
while pos + 4 <= self.buf.len() {
if self.buf[pos] != 0xFF || self.buf[pos + 1] & 0xE0 != 0xE0 {
pos += 1;
continue;
}
let hb = [
self.buf[pos],
self.buf[pos + 1],
self.buf[pos + 2],
self.buf[pos + 3],
];
let header = match FrameHeader::parse(hb) {
Ok(h) => h,
Err(_) => {
pos += 1;
continue;
}
};
let frame_size = header.frame_size();
if frame_size < 4 {
pos += 1;
continue;
}
if pos + frame_size > self.buf.len() {
break; }
let crc = if header.crc_protected { 2 } else { 0 };
let si_start = pos + 4 + crc;
let si_len = header.side_info_len();
let main_start = si_start + si_len;
if main_start > pos + frame_size {
pos += 1;
continue;
}
let side_info = self.buf[si_start..main_start].to_vec();
let main_data = self.buf[main_start..pos + frame_size].to_vec();
if let Ok(pcm) = self.state.decode_frame(&header, &side_info, &main_data) {
let channels = header.channel_mode.channels().max(1);
self.queue.push_back(DecodedAudio {
sample_rate: header.sample_rate,
channels: channels as u16,
samples: pcm,
});
}
pos += frame_size;
}
self.buf.drain(0..pos);
}
}
#[derive(Debug, Clone, Default)]
pub struct Mp3EncoderConfig {
pub bitrate_kbps: u32,
pub vbr_quality: Option<f32>,
}
pub fn vbr_quality_index(q: f32) -> f32 {
let q = q.clamp(0.0, 9.0);
10f32.powf((-24.0 + 4.0 * q) / 10.0)
}
#[derive(Default)]
pub struct Mp3Encoder {
state: Mp3Encode,
header: Option<FrameHeader>,
pcm: [Vec<f32>; 2],
queue: VecDeque<Vec<u8>>,
total_frames: u32,
total_bytes: usize,
cbr_kbps: u32,
quality: Option<f32>,
reservoir: bool,
resv_gain: f32,
resv_lookahead: bool,
resv_frames_pcm: Vec<(FrameHeader, Vec<Vec<f32>>)>,
eof: bool,
}
pub fn snap_bitrate(version: header::MpegVersion, kbps: u32) -> u32 {
let v1 = [
32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
];
let v2 = [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160];
let valid: &[u32] = if version == header::MpegVersion::V1 {
&v1
} else {
&v2
};
*valid
.iter()
.min_by_key(|&&b| b.abs_diff(kbps))
.unwrap_or(&128)
}
pub fn encoder_header(sample_rate: u32, channels: u16, cbr_kbps: u32) -> Result<FrameHeader> {
let version = match sample_rate {
32000 | 44100 | 48000 => header::MpegVersion::V1,
16000 | 22050 | 24000 => header::MpegVersion::V2,
8000 | 11025 | 12000 => header::MpegVersion::V2_5,
_ => {
return Err(Error::unsupported(
"mp3 encode: unsupported sample rate (need 8–48 kHz MPEG-1/2/2.5)",
))
}
};
let channel_mode = if channels >= 2 {
frame::ChannelMode::Stereo
} else {
frame::ChannelMode::Mono
};
let default_kbps = if version == header::MpegVersion::V1 {
128
} else {
64
};
let bitrate_kbps = snap_bitrate(
version,
if cbr_kbps == 0 {
default_kbps
} else {
cbr_kbps
},
);
Ok(FrameHeader {
version,
crc_protected: false,
bitrate_kbps,
sample_rate,
padding: false,
channel_mode,
copyright: false,
original: true,
emphasis: 0,
})
}
impl Mp3Encoder {
pub fn new(config: Mp3EncoderConfig) -> Mp3Encoder {
Mp3Encoder {
cbr_kbps: config.bitrate_kbps,
quality: config.vbr_quality,
..Mp3Encoder::default()
}
}
pub fn push_pcm_f32(
&mut self,
interleaved: &[f32],
channels: u16,
sample_rate: u32,
) -> Result<()> {
if self.header.is_none() {
self.header = Some(encoder_header(sample_rate, channels, self.cbr_kbps)?);
let is_v1 = self.header.as_ref().unwrap().version == crate::header::MpegVersion::V1;
self.reservoir = self.quality.is_none()
&& is_v1
&& match std::env::var("MP3_RESERVOIR") {
Ok(v) => v != "0", Err(_) => self.cbr_kbps <= 256, };
self.resv_gain = std::env::var("MP3_RESV_GAIN")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0.2);
self.resv_lookahead = std::env::var("MP3_RESV_LOOKAHEAD").is_ok_and(|v| v != "0");
}
let nch = self.header.as_ref().unwrap().channel_mode.channels();
let in_ch = (channels as usize).max(1);
let samples = interleaved.len() / in_ch;
for s in 0..samples {
for c in 0..nch {
let ic = c.min(in_ch - 1);
self.pcm[c].push(interleaved[s * in_ch + ic]);
}
}
self.drain_frames();
Ok(())
}
pub fn push_pcm_s16(
&mut self,
interleaved: &[i16],
channels: u16,
sample_rate: u32,
) -> Result<()> {
let f32s: Vec<f32> = interleaved.iter().map(|&s| s as f32 / 32768.0).collect();
self.push_pcm_f32(&f32s, channels, sample_rate)
}
pub fn next_packet(&mut self) -> Result<Vec<u8>> {
if let Some(p) = self.queue.pop_front() {
return Ok(p);
}
if self.eof {
Err(Error::Eof)
} else {
Err(Error::Again)
}
}
pub fn finish(&mut self) {
if let Some(header) = self.header.clone() {
let nch = header.channel_mode.channels();
let spf = header.version.samples_per_frame();
if (0..nch).any(|c| !self.pcm[c].is_empty()) {
for c in 0..nch {
let padded = self.pcm[c].len().div_ceil(spf) * spf;
self.pcm[c].resize(padded, 0.0);
}
self.drain_frames();
}
if self.reservoir {
let fsize = header.frame_size();
let stream = if self.resv_lookahead {
let frames = std::mem::take(&mut self.resv_frames_pcm);
self.state
.encode_reservoir_lookahead(&frames, self.resv_gain)
} else {
self.state.finish_reservoir()
};
for chunk in stream.chunks(fsize) {
self.queue.push_back(chunk.to_vec());
}
}
if self.total_frames > 0 {
let fsize = header.frame_size() as u32;
let info = encode::bitstream::info_frame(
&header,
self.total_frames + 1,
self.total_bytes as u32 + fsize,
self.quality.is_some(),
);
self.queue.push_front(info);
}
}
self.eof = true;
}
fn drain_frames(&mut self) {
let Some(header) = self.header.clone() else {
return;
};
let nch = header.channel_mode.channels();
let spf = header.version.samples_per_frame();
let mut off = 0usize;
while self.pcm[0].len() - off >= spf && (nch == 1 || self.pcm[1].len() - off >= spf) {
let block: Vec<Vec<f32>> = (0..nch)
.map(|c| self.pcm[c][off..off + spf].to_vec())
.collect();
if self.reservoir && self.resv_lookahead {
self.resv_frames_pcm.push((header.clone(), block));
self.total_frames += 1;
self.total_bytes += header.frame_size();
} else if self.reservoir {
self.state
.encode_frame_reservoir(&header, &block, self.resv_gain);
self.total_frames += 1;
self.total_bytes += header.frame_size();
} else if let Ok(bytes) = self.state.encode_frame(&header, &block, self.quality) {
self.total_frames += 1;
self.total_bytes += bytes.len();
self.queue.push_back(bytes);
}
off += spf;
}
if off > 0 {
for c in 0..nch {
self.pcm[c].drain(0..off);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn encode_mono(input: &[f32], sample_rate: u32) -> Vec<u8> {
let mut enc = Mp3Encoder::default();
enc.push_pcm_f32(input, 1, sample_rate).unwrap();
enc.finish();
let mut mp3 = Vec::new();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
mp3
}
fn encode_mono_s16(input: &[i16], sample_rate: u32) -> Vec<u8> {
let mut enc = Mp3Encoder::default();
enc.push_pcm_s16(input, 1, sample_rate).unwrap();
enc.finish();
let mut mp3 = Vec::new();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
mp3
}
#[test]
fn s16_push_encodes_to_audible_output() {
let sr = 44100u32;
let n = sr as usize; let s16: Vec<i16> = (0..n)
.map(|i| {
let t = i as f32 / sr as f32;
(0.5 * (2.0 * std::f32::consts::PI * 440.0 * t).sin() * 32767.0) as i16
})
.collect();
let out = decode_mono(encode_mono_s16(&s16, sr));
let rms = (out.iter().map(|x| x * x).sum::<f32>() / out.len().max(1) as f32).sqrt();
assert!(
rms > 0.1,
"S16 input produced near-silent output (rms={rms})"
);
}
#[test]
#[ignore]
fn profile_decode() {
let sr = 44100u32;
let n = 10 * sr as usize;
let mut s = 0x9E37_79B9u32;
let mut rng = || {
s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(s >> 8) as f32 / (1u32 << 24) as f32 * 2.0 - 1.0
};
let input: Vec<f32> = (0..n)
.map(|i| {
let t = i as f32 / sr as f32;
let mut v = 0f32;
for k in 1..=24 {
v += (1.0 / k as f32)
* (2.0 * std::f32::consts::PI * 130.0 * k as f32 * t).sin();
}
(0.2 * v + 0.03 * rng()).clamp(-1.0, 1.0)
})
.collect();
let mp3 = encode_mono(&input, sr);
eprintln!("[profile] decoding {} KB of mp3 (~10 s)", mp3.len() / 1024);
let out = decode_mono(mp3);
eprintln!("[profile] {} PCM samples out", out.len());
crate::decode::prof::dump();
}
fn decode_mono(mp3: Vec<u8>) -> Vec<f32> {
let mut dec = Mp3Decoder::default();
dec.push(&mp3);
dec.flush();
let mut out = Vec::new();
while let Ok(af) = dec.next_frame() {
out.extend_from_slice(&af.samples);
}
out
}
fn best_snr(reference: &[f32], out: &[f32]) -> f64 {
let (mut best, skip) = (f64::NEG_INFINITY, 2304usize);
for delay in 0..3000 {
let mut sig = 0f64;
let mut err = 0f64;
let mut n = 0;
let mut i = skip;
while i + delay < out.len() && i < reference.len() {
let r = reference[i] as f64;
sig += r * r;
err += (r - out[i + delay] as f64).powi(2);
n += 1;
i += 1;
}
if n > 5000 && err > 0.0 {
best = best.max(10.0 * (sig / err).log10());
}
}
best
}
#[test]
fn encode_decode_stereo() {
let sr = 44100u32;
let frames = 16;
let n = frames * 1152;
let pi2 = 2.0 * std::f32::consts::PI;
let mut interleaved = Vec::with_capacity(n * 2);
for i in 0..n {
let t = i as f32 / sr as f32;
interleaved.push(0.35 * (pi2 * 700.0 * t).sin()); interleaved.push(0.30 * (pi2 * 3000.0 * t).sin()); }
let mut enc = Mp3Encoder::default();
enc.push_pcm_f32(&interleaved, 2, sr).unwrap();
enc.finish();
let mut mp3 = Vec::new();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
assert!(!mp3.is_empty());
if let Ok(path) = std::env::var("MP3_ENC_OUT") {
std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
}
let mut dec = Mp3Decoder::default();
dec.push(&mp3);
dec.flush();
let (mut left, mut right) = (Vec::new(), Vec::new());
while let Ok(af) = dec.next_frame() {
assert_eq!(af.channels, 2, "decoded stream must be stereo");
for fr in af.samples.chunks_exact(2) {
left.push(fr[0]);
right.push(fr[1]);
}
}
let ref_l: Vec<f32> = (0..n)
.map(|i| 0.35 * (pi2 * 700.0 * i as f32 / sr as f32).sin())
.collect();
let ref_r: Vec<f32> = (0..n)
.map(|i| 0.30 * (pi2 * 3000.0 * i as f32 / sr as f32).sin())
.collect();
let snr_l = best_snr(&ref_l, &left);
let snr_r = best_snr(&ref_r, &right);
eprintln!("[R1] stereo SNR L {snr_l:.1} dB R {snr_r:.1} dB");
assert!(
snr_l > 20.0 && snr_r > 20.0,
"stereo channels too noisy: L {snr_l} R {snr_r}"
);
}
#[test]
fn encode_decode_mpeg2() {
let dump_dir = std::env::var("MP3_ENC_DIR").ok();
for &sr in &[22050u32, 24000, 16000, 12000, 11025, 8000] {
let n = 24 * 576; let input: Vec<f32> = (0..n)
.map(|i| 0.4 * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
.collect();
let mp3 = encode_mono(&input, sr);
assert!(!mp3.is_empty());
if let Some(dir) = &dump_dir {
std::fs::write(format!("{dir}/v2_{sr}.mp3"), &mp3).expect("dump");
}
let h = header::FrameHeader::parse([mp3[0], mp3[1], mp3[2], mp3[3]]).unwrap();
assert_ne!(
h.version,
header::MpegVersion::V1,
"{sr}: must be MPEG-2/2.5"
);
assert_eq!(h.sample_rate, sr);
let out = decode_mono(mp3);
assert!(out.len() > n / 2);
let snr = best_snr(&input, &out);
eprintln!("[R5] MPEG-2 {sr} Hz round-trip SNR {snr:.1} dB");
assert!(
snr > 30.0,
"{sr}: MPEG-2 round-trip SNR too low: {snr:.1} dB"
);
}
}
#[test]
fn block_switching_on_transients() {
let sr = 44100u32;
let frames = 20;
let n = frames * 1152;
let mut s = 0xBEEF_1234u32;
let input: Vec<f32> = (0..n)
.map(|i| {
if i % 1500 < 80 {
s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
0.7 * ((s >> 8) as f32 / (1u32 << 24) as f32 - 0.5)
} else {
0.0
}
})
.collect();
let mp3 = encode_mono(&input, sr);
assert!(!mp3.is_empty());
if let Ok(path) = std::env::var("MP3_ENC_OUT") {
std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
}
let si_len = header::FrameHeader::parse([mp3[0], mp3[1], mp3[2], mp3[3]])
.map(|h| h.side_info_len())
.unwrap_or(17);
let mut switched = 0;
let mut pos = 0;
while pos + 4 <= mp3.len() {
if mp3[pos] == 0xFF && mp3[pos + 1] & 0xE0 == 0xE0 {
if let Ok(h) =
header::FrameHeader::parse([mp3[pos], mp3[pos + 1], mp3[pos + 2], mp3[pos + 3]])
{
let si = &mp3[pos + 4..pos + 4 + si_len];
if let Ok(parsed) = decode::sideinfo::parse(&h, si) {
if (0..h.version.granules())
.any(|gr| parsed.granules[gr][0].window_switching)
{
switched += 1;
}
}
pos += h.frame_size();
continue;
}
}
pos += 1;
}
eprintln!("[Q5] window-switched frames: {switched}/{frames}");
assert!(
switched >= 3,
"transients must trigger block switching, got {switched}"
);
let mut dec = Mp3Decoder::default();
dec.push(&mp3);
dec.flush();
let mut decoded = 0;
while let Ok(_) = dec.next_frame() {
decoded += 1;
}
assert!(decoded >= frames, "all frames must decode, got {decoded}");
}
#[test]
fn conformance_corpus_round_trips() {
let sr = 44100u32;
let frames = 14;
let n = frames * 1152;
let pi2 = std::f32::consts::TAU;
let tone = |f: f32| -> Vec<f32> {
(0..n)
.map(|i| 0.4 * (pi2 * f * i as f32 / sr as f32).sin())
.collect()
};
let noise = |seed: u32, amp: f32| -> Vec<f32> {
let mut s = seed;
(0..n)
.map(|_| {
s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
amp * ((s >> 8) as f32 / (1u32 << 24) as f32 - 0.5)
})
.collect()
};
let sweep: Vec<f32> = {
let mut ph = 0f32;
(0..n)
.map(|i| {
let f = 80.0 + (16000.0 - 80.0) * (i as f32 / n as f32);
ph += pi2 * f / sr as f32;
0.4 * ph.sin()
})
.collect()
};
let corpus: Vec<(&str, Vec<f32>, f64)> = vec![
("tone-250", tone(250.0), 40.0),
("tone-1k", tone(1000.0), 50.0),
("tone-4k", tone(4000.0), 45.0),
("tone-12k", tone(12000.0), 30.0),
("sweep", sweep, 15.0),
("noise", noise(0xC0FFEE, 0.4), 5.0),
("quiet-noise", noise(0x1234, 0.02), 3.0),
(
"two-tone",
{
let a = tone(600.0);
let b = tone(5200.0);
a.iter().zip(&b).map(|(x, y)| 0.5 * (x + y)).collect()
},
25.0,
),
];
let dump_dir = std::env::var("MP3_ENC_DIR").ok();
for (name, sig, floor) in &corpus {
let mp3 = encode_mono(sig, sr);
assert!(!mp3.is_empty(), "{name}: no output");
if let Some(dir) = &dump_dir {
std::fs::write(format!("{dir}/{name}.mp3"), &mp3).expect("dump");
}
let out = decode_mono(mp3);
assert!(out.len() > n / 2, "{name}: too few samples");
let snr = best_snr(sig, &out);
eprintln!("[R4] {name:<12} SNR {snr:.1} dB (floor {floor})");
assert!(snr >= *floor, "{name}: SNR {snr:.1} below floor {floor}");
}
}
#[test]
fn vbr_ladder_is_live_conformant_and_ordered() {
let sr = 44_100u32;
let n = 8 * 1152;
let mut lcg: u32 = 0x5EED_1234;
let pcm: Vec<f32> = (0..n)
.map(|i| {
lcg = lcg.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
let t = i as f32 / sr as f32;
let tone = (0..6)
.map(|k| (1.0 / (k + 1) as f32) * (2.0 * std::f32::consts::PI * 220.0 * (k + 1) as f32 * t).sin())
.sum::<f32>();
(0.25 * tone + 0.02 * ((lcg >> 9) as f32 / (1 << 23) as f32 - 0.5)).clamp(-1.0, 1.0)
})
.collect();
let mut sizes = Vec::new();
for q in [0.0f32, 3.0, 6.0, 9.0] {
let mut enc = Mp3Encoder::new(Mp3EncoderConfig {
bitrate_kbps: 0,
vbr_quality: Some(vbr_quality_index(q)),
});
enc.push_pcm_f32(&pcm, 1, sr).unwrap();
enc.finish();
let mut mp3 = Vec::new();
while let Ok(p) = enc.next_packet() {
assert!(
!p.is_empty(),
"q={q}: empty packet"
);
mp3.extend_from_slice(&p);
}
assert!(mp3.len() > 1000, "q={q}: implausibly small output");
sizes.push(mp3.len());
}
let (best, worst) = (sizes[0], sizes[sizes.len() - 1]);
assert!(
best as f64 / worst as f64 > 1.5,
"-q:a is inert: sizes {sizes:?} span less than 1.5x"
);
for w in sizes.windows(2) {
assert!(
w[1] <= w[0],
"VBR ladder not monotonic: {sizes:?}"
);
}
}
#[test]
fn vbr_varies_bitrate_and_round_trips() {
let sr = 44100u32;
let pi2 = 2.0 * std::f32::consts::PI;
let mut input = Vec::new();
let mut s = 0x1234_5678u32;
for f in 0..20 {
for i in 0..1152 {
let t = (f * 1152 + i) as f32 / sr as f32;
if f < 10 {
input.push(0.05 * (pi2 * 500.0 * t).sin()); } else {
s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
input.push(0.6 * ((s >> 8) as f32 / (1u32 << 24) as f32 - 0.5));
}
}
}
let mut enc = Mp3Encoder::new(Mp3EncoderConfig {
bitrate_kbps: 0,
vbr_quality: Some(vbr_quality_index(3.0)),
});
enc.push_pcm_f32(&input, 1, sr).unwrap();
enc.finish();
let mut mp3 = Vec::new();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
if let Ok(path) = std::env::var("MP3_ENC_OUT") {
std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
}
let mut bitrates = std::collections::BTreeSet::new();
let mut pos = 0;
while pos + 4 <= mp3.len() {
if mp3[pos] == 0xFF && mp3[pos + 1] & 0xE0 == 0xE0 {
if let Ok(h) =
header::FrameHeader::parse([mp3[pos], mp3[pos + 1], mp3[pos + 2], mp3[pos + 3]])
{
bitrates.insert(h.bitrate_kbps);
pos += h.frame_size();
continue;
}
}
pos += 1;
}
eprintln!("[R2] VBR bitrates used: {bitrates:?}");
assert!(
bitrates.len() >= 2,
"VBR must vary the bitrate, got {bitrates:?}"
);
let mut dec = Mp3Decoder::default();
dec.push(&mp3);
dec.flush();
let mut frames = 0;
while let Ok(_) = dec.next_frame() {
frames += 1;
}
assert!(frames >= 20, "expected all frames to decode, got {frames}");
}
#[test]
fn encode_decode_joint_stereo() {
let sr = 44100u32;
let n = 16 * 1152;
let pi2 = 2.0 * std::f32::consts::PI;
let mut interleaved = Vec::with_capacity(n * 2);
for i in 0..n {
let t = i as f32 / sr as f32;
let base = 0.4 * (pi2 * 900.0 * t).sin();
interleaved.push(base); interleaved.push(base * 0.95 + 0.02 * (pi2 * 1500.0 * t).sin()); }
let mut enc = Mp3Encoder::default();
enc.push_pcm_f32(&interleaved, 2, sr).unwrap();
enc.finish();
let mut mp3 = Vec::new();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
if let Ok(path) = std::env::var("MP3_ENC_OUT") {
std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
}
let joint = mp3
.windows(2)
.step_by(1)
.any(|w| w[0] == 0xFF && (w[1] & 0xE0) == 0xE0 && (w[1] & 0x06) == 0x02);
assert!(joint, "no joint-stereo frame emitted");
let mut dec = Mp3Decoder::default();
dec.push(&mp3);
dec.flush();
let (mut left, mut right) = (Vec::new(), Vec::new());
while let Ok(af) = dec.next_frame() {
for fr in af.samples.chunks_exact(2) {
left.push(fr[0]);
right.push(fr[1]);
}
}
let ref_l: Vec<f32> = (0..n)
.map(|i| 0.4 * (pi2 * 900.0 * i as f32 / sr as f32).sin())
.collect();
let ref_r: Vec<f32> = (0..n)
.map(|i| {
let t = i as f32 / sr as f32;
0.4 * (pi2 * 900.0 * t).sin() * 0.95 + 0.02 * (pi2 * 1500.0 * t).sin()
})
.collect();
let snr_l = best_snr(&ref_l, &left);
let snr_r = best_snr(&ref_r, &right);
eprintln!("[R1+] joint-stereo SNR L {snr_l:.1} dB R {snr_r:.1} dB");
assert!(
snr_l > 20.0 && snr_r > 20.0,
"joint stereo too noisy: L {snr_l} R {snr_r}"
);
}
#[test]
fn encode_decode_pipeline_multitone() {
let sr = 44100u32;
let n = 16 * 1152;
let input: Vec<f32> = (0..n)
.map(|i| {
let t = i as f32 / sr as f32;
let pi2 = 2.0 * std::f32::consts::PI;
0.3 * (pi2 * 600.0 * t).sin()
+ 0.2 * (pi2 * 2300.0 * t).sin()
+ 0.12 * (pi2 * 9000.0 * t).sin()
})
.collect();
let mp3 = encode_mono(&input, sr);
assert!(!mp3.is_empty(), "encoder produced no data");
assert_eq!(mp3[0], 0xFF);
assert_eq!(mp3[1] & 0xE0, 0xE0);
if let Ok(path) = std::env::var("MP3_ENC_OUT") {
std::fs::write(path, &mp3).expect("write MP3_ENC_OUT");
}
let out = decode_mono(mp3);
assert!(out.len() > n / 2, "decoder produced too few samples");
let snr = best_snr(&input, &out);
eprintln!("[C4] encode→decode multitone SNR {snr:.1} dB");
assert!(snr > 20.0, "round-trip SNR too low: {snr:.1} dB");
}
#[test]
fn decode_real_mp3_structure() {
let Ok(path) = std::env::var("MP3_REF") else {
return; };
let data = std::fs::read(&path).expect("read MP3_REF");
let mut dec = Mp3Decoder::default();
dec.push(&data);
dec.flush();
let mut frames = 0usize;
let mut samples = 0usize;
let mut pcm: Vec<u8> = Vec::new();
while let Ok(af) = dec.next_frame() {
assert_eq!(af.sample_rate, 44100);
assert_eq!(af.channels, 1);
for s in &af.samples {
pcm.extend_from_slice(&s.to_le_bytes());
}
frames += 1;
samples += af.samples.len() / af.channels as usize;
}
eprintln!("[MP3] decoded frames={frames} samples={samples}");
assert!(frames > 0, "must decode at least one frame from real data");
if let Ok(out) = std::env::var("MP3_OUT") {
std::fs::write(out, &pcm).expect("write MP3_OUT");
}
}
#[test]
fn encode_wav_env() {
let Ok(inp) = std::env::var("WAV_IN") else {
return;
};
let d = std::fs::read(&inp).expect("read WAV_IN");
let fmt = d.windows(4).position(|w| w == b"fmt ").unwrap() + 8;
let audio_fmt = u16::from_le_bytes([d[fmt], d[fmt + 1]]);
let rate = u32::from_le_bytes([d[fmt + 4], d[fmt + 5], d[fmt + 6], d[fmt + 7]]);
let bits = u16::from_le_bytes([d[fmt + 14], d[fmt + 15]]);
let data = d.windows(4).position(|w| w == b"data").unwrap() + 8;
let pcm: Vec<f32> = if audio_fmt == 3 || bits == 32 {
d[data..]
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect()
} else {
d[data..]
.chunks_exact(2)
.map(|c| i16::from_le_bytes([c[0], c[1]]) as f32 / 32768.0)
.collect()
};
let nch = u16::from_le_bytes([d[fmt + 2], d[fmt + 3]]).max(1);
let mut enc = Mp3Encoder::new(Mp3EncoderConfig {
bitrate_kbps: std::env::var("BR")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(128),
vbr_quality: None,
});
enc.push_pcm_f32(&pcm, nch, rate).unwrap();
enc.finish();
let mut mp3 = Vec::new();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
std::fs::write(std::env::var("ENC_OUT").unwrap(), mp3).unwrap();
}
#[test]
fn encode_at_rate_to_file() {
let Some(rate) = std::env::var("ENC_RATE")
.ok()
.and_then(|s| s.parse::<u32>().ok())
else {
return;
};
let n = rate as usize * 2;
let mut pcm = vec![0f32; n];
let mut s = 0x1234_5u32;
for (i, v) in pcm.iter_mut().enumerate() {
s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
let noise = (s >> 8) as f32 / (1u32 << 24) as f32 - 0.5;
let t = i as f32 / rate as f32;
*v = 0.3 * (2.0 * std::f32::consts::PI * 440.0 * t).sin() + 0.05 * noise;
}
let mp3 = encode_mono(&pcm, rate);
std::fs::write(std::env::var("ENC_OUT").unwrap(), mp3).unwrap();
}
#[test]
fn decode_any_mp3_to_pcm() {
let Ok(path) = std::env::var("MP3_REF2") else {
return;
};
let data = std::fs::read(&path).expect("read MP3_REF2");
let mut dec = Mp3Decoder::default();
dec.push(&data);
dec.flush();
let mut pcm: Vec<u8> = Vec::new();
while let Ok(af) = dec.next_frame() {
for s in &af.samples {
pcm.extend_from_slice(&s.to_le_bytes());
}
}
assert!(!pcm.is_empty());
if let Ok(out) = std::env::var("MP3_OUT2") {
std::fs::write(out, &pcm).expect("write MP3_OUT2");
}
}
#[test]
fn header_parse_roundtrip() {
let bytes = [0xFF, 0xFB, 0x90, 0x00];
let h = header::FrameHeader::parse(bytes).unwrap();
assert_eq!(h.version, header::MpegVersion::V1);
assert_eq!(h.bitrate_kbps, 128);
assert_eq!(h.sample_rate, 44100);
assert_eq!(h.channel_mode, frame::ChannelMode::Stereo);
assert!(!h.crc_protected && !h.padding);
assert_eq!(h.frame_size(), 417);
assert_eq!(h.to_bytes(), bytes, "header must round-trip bit-exactly");
}
#[test]
fn header_rejects_non_layer3_and_bad_sync() {
assert!(header::FrameHeader::parse([0xFF, 0xFD, 0x90, 0x00]).is_err());
assert!(header::FrameHeader::parse([0x00, 0x00, 0x00, 0x00]).is_err());
}
fn hdr(version: header::MpegVersion, mode: frame::ChannelMode) -> header::FrameHeader {
header::FrameHeader {
version,
crc_protected: false,
bitrate_kbps: 128,
sample_rate: 44100,
padding: false,
channel_mode: mode,
copyright: false,
original: true,
emphasis: 0,
}
}
#[test]
fn sideinfo_bit_accounting_all_layouts() {
use frame::ChannelMode::{Mono, Stereo};
use header::MpegVersion::{V1, V2};
for (v, m, len) in [
(V1, Stereo, 32),
(V1, Mono, 17),
(V2, Stereo, 17),
(V2, Mono, 9),
] {
let h = hdr(v, m);
assert_eq!(h.side_info_len(), len);
let si = decode::sideinfo::parse(&h, &vec![0u8; len]).unwrap();
assert_eq!(si.main_data_begin, 0);
}
}
#[test]
fn frame_size_matches_spec_example() {
let h = header::FrameHeader {
version: header::MpegVersion::V1,
crc_protected: false,
bitrate_kbps: 128,
sample_rate: 44100,
padding: false,
channel_mode: frame::ChannelMode::Stereo,
copyright: false,
original: true,
emphasis: 0,
};
assert_eq!(h.frame_size(), 417);
assert_eq!(h.side_info_len(), 32);
}
}