access_unit/
mp3.rs

1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum MpegVersion {
3    V1,
4    V2,
5    V25,
6}
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum MpegLayer {
10    LayerI,
11    LayerII,
12    LayerIII,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ChannelMode {
17    Stereo,
18    JointStereo,
19    DualChannel,
20    Mono,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Mp3FrameHeader {
25    pub version: MpegVersion,
26    pub layer: MpegLayer,
27    pub bitrate_kbps: u16,
28    pub sample_rate: u32,
29    pub padding: bool,
30    pub channel_mode: ChannelMode,
31    pub frame_length: usize,
32    pub samples_per_frame: u16,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum Mp3HeaderError {
37    TooShort,
38    InvalidSync,
39    ReservedVersion,
40    ReservedLayer,
41    BadBitrate,
42    BadSampleRate,
43    ReservedEmphasis,
44}
45
46const BITRATE_V1_L1: [u16; 14] = [
47    32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448,
48];
49const BITRATE_V1_L2: [u16; 14] = [
50    32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384,
51];
52const BITRATE_V1_L3: [u16; 14] = [
53    32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
54];
55const BITRATE_V2_L1: [u16; 14] = [
56    32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,
57];
58const BITRATE_V2_L2_L3: [u16; 14] = [
59    8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
60];
61
62/// Returns true if a valid MP3 frame header is found anywhere in the slice.
63pub fn is_mp3(data: &[u8]) -> bool {
64    find_frame(data).is_some()
65}
66
67/// Scans for the first valid MP3 frame header and returns its offset and parsed header.
68pub fn find_frame(data: &[u8]) -> Option<(usize, Mp3FrameHeader)> {
69    if data.len() < 4 {
70        return None;
71    }
72
73    for offset in 0..=data.len() - 4 {
74        if let Ok(header) = parse_frame_header(&data[offset..]) {
75            if header.frame_length >= 16 {
76                return Some((offset, header));
77            }
78        }
79    }
80
81    None
82}
83
84pub fn parse_frame_header(input: &[u8]) -> Result<Mp3FrameHeader, Mp3HeaderError> {
85    if input.len() < 4 {
86        return Err(Mp3HeaderError::TooShort);
87    }
88
89    let b0 = input[0];
90    let b1 = input[1];
91    let b2 = input[2];
92    let b3 = input[3];
93
94    if b0 != 0xFF || (b1 & 0xE0) != 0xE0 {
95        return Err(Mp3HeaderError::InvalidSync);
96    }
97
98    let version = match (b1 >> 3) & 0x03 {
99        0b00 => MpegVersion::V25,
100        0b10 => MpegVersion::V2,
101        0b11 => MpegVersion::V1,
102        _ => return Err(Mp3HeaderError::ReservedVersion),
103    };
104
105    let layer = match (b1 >> 1) & 0x03 {
106        0b01 => MpegLayer::LayerIII,
107        0b10 => MpegLayer::LayerII,
108        0b11 => MpegLayer::LayerI,
109        _ => return Err(Mp3HeaderError::ReservedLayer),
110    };
111
112    let bitrate_index = (b2 >> 4) & 0x0F;
113    let bitrate_kbps =
114        bitrate_kbps(version, layer, bitrate_index).ok_or(Mp3HeaderError::BadBitrate)?;
115
116    let sample_rate_index = (b2 >> 2) & 0x03;
117    let sample_rate =
118        sample_rate(version, sample_rate_index).ok_or(Mp3HeaderError::BadSampleRate)?;
119
120    let padding = ((b2 >> 1) & 0x01) == 1;
121
122    let channel_mode = match (b3 >> 6) & 0x03 {
123        0b00 => ChannelMode::Stereo,
124        0b01 => ChannelMode::JointStereo,
125        0b10 => ChannelMode::DualChannel,
126        _ => ChannelMode::Mono,
127    };
128
129    let emphasis = b3 & 0x03;
130    if emphasis == 0b10 {
131        return Err(Mp3HeaderError::ReservedEmphasis);
132    }
133
134    let samples_per_frame = samples_per_frame(version, layer);
135    let frame_length =
136        frame_length_bytes(samples_per_frame, bitrate_kbps, sample_rate, layer, padding);
137
138    Ok(Mp3FrameHeader {
139        version,
140        layer,
141        bitrate_kbps,
142        sample_rate,
143        padding,
144        channel_mode,
145        frame_length,
146        samples_per_frame,
147    })
148}
149
150fn bitrate_kbps(version: MpegVersion, layer: MpegLayer, index: u8) -> Option<u16> {
151    if index == 0 || index == 0x0F {
152        return None;
153    }
154
155    let table = match (version, layer) {
156        (MpegVersion::V1, MpegLayer::LayerI) => &BITRATE_V1_L1,
157        (MpegVersion::V1, MpegLayer::LayerII) => &BITRATE_V1_L2,
158        (MpegVersion::V1, MpegLayer::LayerIII) => &BITRATE_V1_L3,
159        (_, MpegLayer::LayerI) => &BITRATE_V2_L1,
160        _ => &BITRATE_V2_L2_L3,
161    };
162
163    Some(table[index as usize - 1])
164}
165
166fn sample_rate(version: MpegVersion, index: u8) -> Option<u32> {
167    let value = match version {
168        MpegVersion::V1 => match index {
169            0 => 44_100,
170            1 => 48_000,
171            2 => 32_000,
172            _ => 0,
173        },
174        MpegVersion::V2 => match index {
175            0 => 22_050,
176            1 => 24_000,
177            2 => 16_000,
178            _ => 0,
179        },
180        MpegVersion::V25 => match index {
181            0 => 11_025,
182            1 => 12_000,
183            2 => 8_000,
184            _ => 0,
185        },
186    };
187
188    if value == 0 { None } else { Some(value) }
189}
190
191fn samples_per_frame(version: MpegVersion, layer: MpegLayer) -> u16 {
192    match (layer, version) {
193        (MpegLayer::LayerI, _) => 384,
194        (MpegLayer::LayerII, _) => 1152,
195        (MpegLayer::LayerIII, MpegVersion::V1) => 1152,
196        (MpegLayer::LayerIII, _) => 576,
197    }
198}
199
200fn frame_length_bytes(
201    samples_per_frame: u16,
202    bitrate_kbps: u16,
203    sample_rate: u32,
204    layer: MpegLayer,
205    padding: bool,
206) -> usize {
207    let samples = samples_per_frame as u64;
208    let bitrate = bitrate_kbps as u64 * 1000;
209    let mut length = (samples * bitrate) / (sample_rate as u64 * 8);
210
211    let padding_bytes = if layer == MpegLayer::LayerI && padding {
212        4
213    } else if padding {
214        1
215    } else {
216        0
217    };
218
219    length += padding_bytes;
220    length as usize
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    fn frame_header_bytes() -> [u8; 4] {
228        // MPEG1 Layer III, 128 kbps, 44.1 kHz, stereo, no padding.
229        [0xFF, 0xFB, 0x90, 0x00]
230    }
231
232    fn frame_bytes() -> Vec<u8> {
233        let header = frame_header_bytes();
234        let mut frame = header.to_vec();
235        frame.resize(417, 0u8);
236        frame
237    }
238
239    #[test]
240    fn parses_mp3_header() {
241        let header = parse_frame_header(&frame_header_bytes()).expect("header should parse");
242        assert_eq!(header.version, MpegVersion::V1);
243        assert_eq!(header.layer, MpegLayer::LayerIII);
244        assert_eq!(header.bitrate_kbps, 128);
245        assert_eq!(header.sample_rate, 44_100);
246        assert_eq!(header.samples_per_frame, 1152);
247        assert_eq!(header.frame_length, 417);
248        assert_eq!(header.channel_mode, ChannelMode::Stereo);
249        assert!(!header.padding);
250    }
251
252    #[test]
253    fn detects_frame_in_stream() {
254        let frame = frame_bytes();
255        let mut stream = vec![0u8; 5];
256        stream.extend_from_slice(&frame);
257        stream.extend_from_slice(&frame);
258
259        assert!(is_mp3(&stream));
260
261        let (offset, header) = find_frame(&stream).expect("frame expected");
262        assert_eq!(offset, 5);
263        assert_eq!(header.frame_length, frame.len());
264    }
265
266    #[test]
267    fn rejects_reserved_sample_rate() {
268        let mut header = frame_header_bytes();
269        header[2] |= 0x0C; // Set sample rate index to reserved value.
270        assert!(matches!(
271            parse_frame_header(&header),
272            Err(Mp3HeaderError::BadSampleRate)
273        ));
274    }
275}