#![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 Drop for Mp3Decoder {
fn drop(&mut self) {
if std::env::var_os("MP3_CENSUS").is_some() {
eprintln!("rusty_mp3 {}", decode::prof::kernel_census());
}
}
}
impl Mp3Decoder {
#[must_use]
pub fn new() -> Self {
Self::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 Ok(header) = FrameHeader::parse(hb) else {
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];
let main_data = &self.buf[main_start..pos + frame_size];
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);
}
}
#[must_use]
pub fn decode_pipelined(bytes: &[u8]) -> Vec<DecodedAudio> {
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
{
let mut dec = Mp3Decoder::new();
dec.push(bytes);
dec.flush();
let mut out = Vec::new();
while let Ok(f) = dec.next_frame() {
out.push(f);
}
return out;
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
{
use std::sync::mpsc::sync_channel;
let (tx, rx) = sync_channel::<(FrameHeader, Vec<decode::GranuleWork>)>(32);
let mut out: Vec<DecodedAudio> = Vec::new();
std::thread::scope(|scope| {
scope.spawn(move || {
let mut state = Mp3Decode::new();
let mut pos = 0usize;
while pos + 4 <= bytes.len() {
if bytes[pos] != 0xFF || bytes[pos + 1] & 0xE0 != 0xE0 {
pos += 1;
continue;
}
let hb = [bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]];
let Ok(header) = FrameHeader::parse(hb) else {
pos += 1;
continue;
};
let frame_size = header.frame_size();
if frame_size < 4 || pos + frame_size > bytes.len() {
if frame_size < 4 {
pos += 1;
continue;
}
break; }
let crc = if header.crc_protected { 2 } else { 0 };
let si_start = pos + 4 + crc;
let main_start = si_start + header.side_info_len();
if main_start > pos + frame_size {
pos += 1;
continue;
}
let side = &bytes[si_start..main_start];
let main = &bytes[main_start..pos + frame_size];
if let Ok(work) = state.decode_frame_entropy(&header, side, main) {
if tx.send((header, work)).is_err() {
break; }
}
pos += frame_size;
}
});
let mut tstate = decode::TransformState::new();
for (header, work) in rx {
let channels = header.channel_mode.channels().max(1);
let mut pcm =
Vec::with_capacity(work.len() * crate::frame::GRANULE_LINES * channels);
for mut g in work {
tstate.granule_to_pcm(&g.side, &mut g.spectrum, channels, &mut pcm);
}
out.push(DecodedAudio {
sample_rate: header.sample_rate,
channels: channels as u16,
samples: pcm,
});
}
});
out
}
}
pub const MAX_INPUT_AMPLITUDE: f32 = 8.0;
#[inline]
fn sanitize_sample(v: f32) -> f32 {
if v.is_nan() {
0.0
} else {
v.clamp(-MAX_INPUT_AMPLITUDE, MAX_INPUT_AMPLITUDE)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Mp3EncoderConfig {
pub bitrate_kbps: u32,
pub vbr_quality: Option<f32>,
}
#[must_use]
pub fn vbr_quality_index(q: f32) -> f32 {
const KBPS: [f32; 10] = [
245.0, 225.0, 190.0, 175.0, 165.0, 130.0, 115.0, 100.0, 85.0, 65.0,
];
let q = q.clamp(0.0, 9.0);
let lo = q.floor() as usize;
let hi = (lo + 1).min(9);
let t = q - lo as f32;
KBPS[lo] * (1.0 - t) + KBPS[hi] * t
}
#[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,
}
#[must_use]
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 {
#[must_use]
pub fn new(config: Mp3EncoderConfig) -> Self {
Self {
cbr_kbps: config.bitrate_kbps,
quality: config.vbr_quality,
..Self::default()
}
}
pub fn push_pcm_f32(
&mut self,
interleaved: &[f32],
channels: u16,
sample_rate: u32,
) -> Result<()> {
let in_ch = (channels as usize).max(1);
self.push_with(interleaved.len() / in_ch, channels, sample_rate, |i| {
interleaved[i]
})
}
pub fn push_pcm_s16(
&mut self,
interleaved: &[i16],
channels: u16,
sample_rate: u32,
) -> Result<()> {
let in_ch = (channels as usize).max(1);
self.push_with(interleaved.len() / in_ch, channels, sample_rate, |i| {
f32::from(interleaved[i]) / 32768.0
})
}
pub fn push_pcm_s16le(&mut self, bytes: &[u8], channels: u16, sample_rate: u32) -> Result<()> {
let in_ch = (channels as usize).max(1);
self.push_with(bytes.len() / 2 / in_ch, channels, sample_rate, |i| {
f32::from(i16::from_le_bytes([bytes[2 * i], bytes[2 * i + 1]])) / 32768.0
})
}
pub fn push_pcm_f32le(&mut self, bytes: &[u8], channels: u16, sample_rate: u32) -> Result<()> {
let in_ch = (channels as usize).max(1);
self.push_with(bytes.len() / 4 / in_ch, channels, sample_rate, |i| {
let b = &bytes[4 * i..4 * i + 4];
f32::from_le_bytes([b[0], b[1], b[2], b[3]])
})
}
fn push_with(
&mut self,
samples: usize,
channels: u16,
sample_rate: u32,
get: impl Fn(usize) -> f32,
) -> 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 header = self.header.as_ref().unwrap();
let nch = header.channel_mode.channels();
let spf = header.version.samples_per_frame();
let in_ch = (channels as usize).max(1);
let mut s = 0;
while s < samples {
if self.pcm[0].len() >= spf {
self.drain_frames();
}
let n = (spf - self.pcm[0].len()).min(samples - s);
for c in 0..nch {
let ic = c.min(in_ch - 1);
let dst = &mut self.pcm[c];
dst.reserve(spf.saturating_sub(dst.len()));
dst.extend((s..s + n).map(|k| sanitize_sample(get(k * in_ch + ic))));
}
s += n;
}
if self.pcm[0].len() >= spf {
self.drain_frames();
}
Ok(())
}
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 {
if self.resv_lookahead {
let fsize = header.frame_size();
let frames = std::mem::take(&mut self.resv_frames_pcm);
let stream = self
.state
.encode_reservoir_lookahead(&frames, self.resv_gain);
for chunk in stream.chunks(fsize) {
self.queue.push_back(chunk.to_vec());
}
} else {
self.queue.extend(self.state.finish_reservoir_frames());
}
}
if self.total_frames > 0 {
let fsize = u32::try_from(header.frame_size()).unwrap_or(u32::MAX);
let info = encode::bitstream::info_frame(
&header,
self.total_frames.saturating_add(1),
u32::try_from(self.total_bytes)
.unwrap_or(u32::MAX)
.saturating_add(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: [&[f32]; 2] = [
&self.pcm[0][off..off + spf],
&self.pcm[nch - 1][off..off + spf],
];
let block = &block[..nch];
if self.reservoir && self.resv_lookahead {
let owned = block.iter().map(|c| c.to_vec()).collect();
self.resv_frames_pcm.push((header.clone(), owned));
self.total_frames = self.total_frames.saturating_add(1);
self.total_bytes += header.frame_size();
} else if self.reservoir {
self.state
.encode_frame_reservoir(&header, block, self.resv_gain);
self.total_frames = self.total_frames.saturating_add(1);
self.total_bytes += header.frame_size();
} else if let Ok(bytes) = self.state.encode_frame(&header, block, self.quality) {
self.total_frames = self.total_frames.saturating_add(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]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
fn hostile_pcm_never_panics() {
let mut seed: u64 = 0x2545_F491_4F6C_DD1D;
let mut rnd = move || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
for kind in 0..4 {
for &sr in &[32_000u32, 44_100, 48_000] {
for &kbps in &[32u32, 128, 192] {
let pcm: Vec<f32> = (0..2 * 4 * 1152)
.map(|_| match kind {
0 => f32::from_bits(rnd() as u32),
1 => [f32::NAN, f32::INFINITY, f32::NEG_INFINITY][(rnd() % 3) as usize],
2 => (rnd() as i32) as f32,
_ => 65_536.0 * if rnd() & 1 == 0 { 1.0 } else { -1.0 },
})
.collect();
let mut enc = Mp3Encoder::new(Mp3EncoderConfig {
bitrate_kbps: kbps,
vbr_quality: None,
});
enc.push_pcm_f32(&pcm, 2, sr).unwrap();
enc.finish();
let mut bytes = 0;
while let Ok(p) = enc.next_packet() {
bytes += p.len();
}
assert!(bytes > 0, "kind {kind} @ {sr}/{kbps}k produced nothing");
}
}
}
for v in [0.0f32, -0.0, 1.0, -1.0, 0.5, 7.999, -8.0, f32::MIN_POSITIVE] {
assert_eq!(
sanitize_sample(v).to_bits(),
v.to_bits(),
"{v} must pass through"
);
}
assert_eq!(sanitize_sample(f32::NAN), 0.0);
assert_eq!(sanitize_sample(f32::INFINITY), MAX_INPUT_AMPLITUDE);
assert_eq!(sanitize_sample(-1e9), -MAX_INPUT_AMPLITUDE);
}
#[test]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
fn push_entry_points_and_chunkings_are_byte_identical() {
fn drain(mut enc: Mp3Encoder) -> Vec<u8> {
enc.finish();
let mut mp3 = Vec::new();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
mp3
}
for &(sr, kbps) in &[(44_100u32, 192u32), (22_050, 64)] {
let n = 7 * 1152 + 333; let s16: Vec<i16> = (0..2 * n)
.map(|i| {
let t = (i / 2) as f32 / sr as f32;
let f = if i % 2 == 0 { 440.0 } else { 660.0 };
((2.0 * std::f32::consts::PI * f * t).sin() * 12000.0) as i16
})
.collect();
let f32s: Vec<f32> = s16.iter().map(|&s| f32::from(s) / 32768.0).collect();
let s16le: Vec<u8> = s16.iter().flat_map(|s| s.to_le_bytes()).collect();
let f32le: Vec<u8> = f32s.iter().flat_map(|s| s.to_le_bytes()).collect();
let cfg = || Mp3EncoderConfig {
bitrate_kbps: kbps,
vbr_quality: None,
};
let mut enc = Mp3Encoder::new(cfg());
enc.push_pcm_s16(&s16, 2, sr).unwrap();
let reference = drain(enc);
assert!(reference.len() > 1000);
let mut enc = Mp3Encoder::new(cfg());
enc.push_pcm_s16le(&s16le, 2, sr).unwrap();
assert_eq!(drain(enc), reference, "s16le whole @ {sr}");
let mut enc = Mp3Encoder::new(cfg());
enc.push_pcm_f32(&f32s, 2, sr).unwrap();
assert_eq!(drain(enc), reference, "f32 whole @ {sr}");
let mut enc = Mp3Encoder::new(cfg());
enc.push_pcm_f32le(&f32le, 2, sr).unwrap();
assert_eq!(drain(enc), reference, "f32le whole @ {sr}");
for chunk in [1usize, 37, 1151, 1153, 2304, 5000] {
let mut enc = Mp3Encoder::new(cfg());
for c in s16.chunks(2 * chunk) {
enc.push_pcm_s16(c, 2, sr).unwrap();
}
assert_eq!(drain(enc), reference, "s16 chunks of {chunk} @ {sr}");
let mut enc = Mp3Encoder::new(cfg());
for c in f32le.chunks(8 * chunk) {
enc.push_pcm_f32le(c, 2, sr).unwrap();
}
assert_eq!(drain(enc), reference, "f32le chunks of {chunk} @ {sr}");
}
}
}
#[test]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
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 = "manual profiling run (prints the decode stage profile)"]
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: &[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 = f64::from(reference[i]);
sig += r * r;
err += (r - f64::from(out[i + delay])).powi(2);
n += 1;
i += 1;
}
if n > 5000 && err > 0.0 {
best = best.max(10.0 * (sig / err).log10());
}
}
best
}
#[test]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
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]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
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]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
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_or(17, |h| h.side_info_len());
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 dec.next_frame().is_ok() {
decoded += 1;
}
assert!(decoded >= frames, "all frames must decode, got {decoded}");
}
#[test]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
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]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
fn pipelined_decode_matches_serial_exactly() {
let sr = 44_100u32;
let n = 90 * 1152;
let mut lcg: u32 = 0xA5A5_1234;
let pcm: Vec<f32> = (0..n * 2)
.map(|i| {
lcg = lcg.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
let t = (i / 2) as f32 / sr as f32;
let tone = (2.0 * std::f32::consts::PI * 220.0 * t).sin()
+ 0.5 * (2.0 * std::f32::consts::PI * 1310.0 * t).sin();
(0.3 * tone + 0.03 * ((lcg >> 9) as f32 / (1 << 23) as f32 - 0.5)).clamp(-1.0, 1.0)
})
.collect();
let mut enc = Mp3Encoder::new(Mp3EncoderConfig {
bitrate_kbps: 192,
vbr_quality: None,
});
enc.push_pcm_f32(&pcm, 2, sr).unwrap();
enc.finish();
let mut mp3 = Vec::new();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
assert!(mp3.len() > 10_000, "test stream too short to be meaningful");
let mut dec = Mp3Decoder::new();
dec.push(&mp3);
dec.flush();
let mut serial: Vec<f32> = Vec::new();
while let Ok(f) = dec.next_frame() {
serial.extend_from_slice(&f.samples);
}
let piped: Vec<f32> = decode_pipelined(&mp3)
.into_iter()
.flat_map(|f| f.samples)
.collect();
assert_eq!(
serial.len(),
piped.len(),
"pipelined produced a different sample count"
);
assert!(!serial.is_empty(), "decoded nothing");
for (i, (a, b)) in serial.iter().zip(piped.iter()).enumerate() {
assert_eq!(a.to_bits(), b.to_bits(), "pipelined diverged at sample {i}");
}
}
#[test]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
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]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
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 dec.next_frame().is_ok() {
frames += 1;
}
assert!(frames >= 20, "expected all frames to decode, got {frames}");
}
#[test]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
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]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
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| f32::from(i16::from_le_bytes([c[0], c[1]])) / 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 = 0x0001_2345u32;
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);
}
}