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
62pub fn is_mp3(data: &[u8]) -> bool {
64 find_frame(data).is_some()
65}
66
67pub 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 [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; assert!(matches!(
271 parse_frame_header(&header),
272 Err(Mp3HeaderError::BadSampleRate)
273 ));
274 }
275}