use crate::{AudioError, AudioResult};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MpegVersion {
Mpeg1,
Mpeg2,
Mpeg25,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Layer {
I,
II,
III,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChannelMode {
Stereo,
JointStereo(JointStereoMode),
DualChannel,
Mono,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct JointStereoMode {
pub intensity: bool,
pub ms_stereo: bool,
pub bound: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Emphasis {
None,
Ms5015,
CcitJ17,
}
#[derive(Clone, Debug)]
pub struct FrameHeader {
pub version: MpegVersion,
pub layer: Layer,
pub protection: bool,
pub bitrate: u32,
pub sample_rate: u32,
pub padding: bool,
pub private: bool,
pub mode: ChannelMode,
pub copyright: bool,
pub original: bool,
pub emphasis: Emphasis,
pub frame_size: usize,
pub samples: usize,
}
impl FrameHeader {
pub fn parse(data: &[u8]) -> AudioResult<Self> {
if data.len() < 4 {
return Err(AudioError::InvalidData("Need 4 bytes for header".into()));
}
if data[0] != 0xFF || (data[1] & 0xE0) != 0xE0 {
return Err(AudioError::InvalidData("Invalid sync word".into()));
}
let version = match (data[1] >> 3) & 0x03 {
0 => MpegVersion::Mpeg25,
2 => MpegVersion::Mpeg2,
3 => MpegVersion::Mpeg1,
_ => return Err(AudioError::InvalidData("Reserved MPEG version".into())),
};
let layer = match (data[1] >> 1) & 0x03 {
1 => Layer::III,
2 => Layer::II,
3 => Layer::I,
_ => return Err(AudioError::InvalidData("Reserved layer".into())),
};
let protection = (data[1] & 0x01) == 0;
let bitrate_index = (data[2] >> 4) & 0x0F;
let bitrate = Self::get_bitrate(version, layer, bitrate_index)?;
let samplerate_index = (data[2] >> 2) & 0x03;
let sample_rate = Self::get_sample_rate(version, samplerate_index)?;
let padding = (data[2] & 0x02) != 0;
let private = (data[2] & 0x01) != 0;
let mode_value = (data[3] >> 6) & 0x03;
let mode_ext = (data[3] >> 4) & 0x03;
let mode = match mode_value {
0 => ChannelMode::Stereo,
1 => {
let (intensity, ms_stereo, bound) = match layer {
Layer::III => {
let intensity = (mode_ext & 0x01) != 0;
let ms_stereo = (mode_ext & 0x02) != 0;
(intensity, ms_stereo, 0)
}
_ => {
let bound = (mode_ext + 1) * 4;
(false, false, bound)
}
};
ChannelMode::JointStereo(JointStereoMode {
intensity,
ms_stereo,
bound,
})
}
2 => ChannelMode::DualChannel,
3 => ChannelMode::Mono,
_ => unreachable!(),
};
let copyright = (data[3] & 0x08) != 0;
let original = (data[3] & 0x04) != 0;
let emphasis = match data[3] & 0x03 {
0 => Emphasis::None,
1 => Emphasis::Ms5015,
3 => Emphasis::CcitJ17,
_ => return Err(AudioError::InvalidData("Reserved emphasis".into())),
};
let frame_size = Self::calculate_frame_size(version, layer, bitrate, sample_rate, padding)?;
let samples = Self::get_samples_per_frame(version, layer);
Ok(Self {
version,
layer,
protection,
bitrate,
sample_rate,
padding,
private,
mode,
copyright,
original,
emphasis,
frame_size,
samples,
})
}
fn get_bitrate(version: MpegVersion, layer: Layer, index: u8) -> AudioResult<u32> {
if index == 0x00 || index == 0x0F {
return Err(AudioError::InvalidData("Invalid bitrate index".into()));
}
let bitrate = match (version, layer) {
(MpegVersion::Mpeg1, Layer::I) => [
0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448,
][index as usize],
(MpegVersion::Mpeg1, Layer::II) => [
0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384,
][index as usize],
(MpegVersion::Mpeg1, Layer::III) => [
0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
][index as usize],
(MpegVersion::Mpeg2 | MpegVersion::Mpeg25, Layer::I) => [
0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,
][index as usize],
(MpegVersion::Mpeg2 | MpegVersion::Mpeg25, Layer::II | Layer::III) => {
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160][index as usize]
}
};
Ok(bitrate * 1000)
}
fn get_sample_rate(version: MpegVersion, index: u8) -> AudioResult<u32> {
if index == 0x03 {
return Err(AudioError::InvalidData("Reserved sample rate".into()));
}
let sample_rate = match version {
MpegVersion::Mpeg1 => [44100, 48000, 32000][index as usize],
MpegVersion::Mpeg2 => [22050, 24000, 16000][index as usize],
MpegVersion::Mpeg25 => [11025, 12000, 8000][index as usize],
};
Ok(sample_rate)
}
fn calculate_frame_size(
_version: MpegVersion,
layer: Layer,
bitrate: u32,
sample_rate: u32,
padding: bool,
) -> AudioResult<usize> {
if sample_rate == 0 {
return Err(AudioError::InvalidData("Zero sample rate".into()));
}
let padding_size = if padding {
match layer {
Layer::I => 4,
Layer::II | Layer::III => 1,
}
} else {
0
};
let frame_size = match layer {
Layer::I => {
((12 * bitrate / sample_rate) + padding_size) as usize
}
Layer::II | Layer::III => {
((144 * bitrate / sample_rate) + padding_size) as usize
}
};
Ok(frame_size)
}
const fn get_samples_per_frame(version: MpegVersion, layer: Layer) -> usize {
match layer {
Layer::I => 384,
Layer::II => 1152,
Layer::III => match version {
MpegVersion::Mpeg1 => 1152,
MpegVersion::Mpeg2 | MpegVersion::Mpeg25 => 576,
},
}
}
#[must_use]
pub const fn channels(&self) -> usize {
match self.mode {
ChannelMode::Mono => 1,
_ => 2,
}
}
#[must_use]
pub const fn is_mono(&self) -> bool {
matches!(self.mode, ChannelMode::Mono)
}
#[must_use]
pub const fn is_joint_stereo(&self) -> bool {
matches!(self.mode, ChannelMode::JointStereo(_))
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn duration(&self) -> f64 {
self.samples as f64 / f64::from(self.sample_rate)
}
}
#[must_use]
pub fn find_sync(data: &[u8]) -> Option<usize> {
for i in 0..data.len().saturating_sub(1) {
if data[i] == 0xFF && (data[i + 1] & 0xE0) == 0xE0 {
return Some(i);
}
}
None
}
#[must_use]
pub fn is_compatible(h1: &FrameHeader, h2: &FrameHeader) -> bool {
h1.version == h2.version
&& h1.layer == h2.layer
&& h1.sample_rate == h2.sample_rate
&& h1.mode == h2.mode
}