use super::legacy;
pub(crate) static DESCRIPTOR: legacy::Descriptor = legacy::Descriptor {
track_suffix: ".mp2",
codec: hang::catalog::AudioCodec::Mp2,
min_header_len: 4,
parse: parse_header,
};
#[derive(Clone, Copy)]
enum Version {
Mpeg1,
Mpeg2,
}
const BITRATE_MPEG1_L2: [u32; 16] = [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0];
const BITRATE_MPEG2_L2: [u32; 16] = [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0];
const SAMPLE_RATE: [[u32; 3]; 2] = [[44100, 48000, 32000], [22050, 24000, 16000]];
const SAMPLES_PER_FRAME: u64 = 1152;
pub(crate) fn parse_header(data: &[u8]) -> anyhow::Result<legacy::Header> {
anyhow::ensure!(data.len() >= 4, "MP2 header needs 4 bytes");
anyhow::ensure!(data[0] == 0xFF && (data[1] & 0xE0) == 0xE0, "missing MP2 frame sync");
let (version, sr_row) = match (data[1] >> 3) & 0x03 {
0b11 => (Version::Mpeg1, 0),
0b10 => (Version::Mpeg2, 1),
_ => anyhow::bail!("reserved or MPEG-2.5 audio version"),
};
anyhow::ensure!((data[1] >> 1) & 0x03 == 0b10, "not MPEG Layer II");
let bitrate_index = (data[2] >> 4) & 0x0F;
let sr_index = (data[2] >> 2) & 0x03;
let padding = ((data[2] >> 1) & 0x01) as usize;
anyhow::ensure!(sr_index != 3, "reserved MP2 sample-rate index");
let sample_rate = SAMPLE_RATE[sr_row][sr_index as usize];
let bitrate_kbps = match version {
Version::Mpeg1 => BITRATE_MPEG1_L2[bitrate_index as usize],
Version::Mpeg2 => BITRATE_MPEG2_L2[bitrate_index as usize],
};
anyhow::ensure!(bitrate_kbps != 0, "free-format or invalid MP2 bitrate");
let len = (144 * bitrate_kbps * 1000 / sample_rate) as usize + padding;
let channel_count = if (data[3] >> 6) & 0x03 == 0b11 { 1 } else { 2 };
Ok(legacy::Header {
len,
sample_rate,
channel_count,
samples: SAMPLES_PER_FRAME,
})
}
#[cfg(test)]
mod test {
use super::parse_header;
#[test]
fn parses_mpeg1_and_mpeg2() {
let h = parse_header(&[0xFF, 0xFD, 0x14, 0x00]).unwrap();
assert_eq!(
(h.len, h.sample_rate, h.channel_count, h.samples),
(96, 48_000, 2, 1152)
);
let h = parse_header(&[0xFF, 0xF5, 0x18, 0xC0]).unwrap();
assert_eq!(
(h.len, h.sample_rate, h.channel_count, h.samples),
(72, 16_000, 1, 1152)
);
}
#[test]
fn rejects_mpeg2_5_and_reserved_versions() {
assert!(parse_header(&[0xFF, 0xE5, 0x14, 0x00]).is_err());
assert!(parse_header(&[0xFF, 0xED, 0x14, 0x00]).is_err());
}
}