Skip to main content

rusty_mp3/
header.rs

1//! MP3 frame header — the 32-bit sync header that prefixes every frame.
2//!
3//! ```text
4//!  syncword(11) | version(2) | layer(2) | crc(1)
5//!  bitrate_idx(4) | samplerate_idx(2) | padding(1) | private(1)
6//!  channel_mode(2) | mode_ext(2) | copyright(1) | original(1) | emphasis(2)
7//! ```
8//!
9//! From these fields we derive the frame size in bytes and the per-frame sample
10//! count, which the demux/packetizer needs to walk the stream.
11
12use crate::{Error, Result};
13
14use crate::frame::ChannelMode;
15use crate::tables;
16
17/// MPEG audio version (the sample-rate base differs per version).
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum MpegVersion {
20    /// MPEG-1 — 2 granules/frame, 1152 samples/frame.
21    V1,
22    /// MPEG-2 (LSF) — 1 granule/frame, 576 samples/frame.
23    V2,
24    /// MPEG-2.5 (unofficial low-sample-rate extension).
25    V2_5,
26}
27
28impl MpegVersion {
29    /// Granules per frame: 2 for MPEG-1, 1 for MPEG-2/2.5.
30    pub fn granules(self) -> usize {
31        match self {
32            MpegVersion::V1 => 2,
33            _ => 1,
34        }
35    }
36    /// Decoded PCM samples per channel per frame.
37    pub fn samples_per_frame(self) -> usize {
38        self.granules() * crate::frame::GRANULE_LINES
39    }
40}
41
42/// A parsed Layer III frame header.
43#[derive(Debug, Clone)]
44pub struct FrameHeader {
45    pub version: MpegVersion,
46    pub crc_protected: bool,
47    pub bitrate_kbps: u32,
48    pub sample_rate: u32,
49    pub padding: bool,
50    pub channel_mode: ChannelMode,
51    pub copyright: bool,
52    pub original: bool,
53    pub emphasis: u8,
54}
55
56impl FrameHeader {
57    /// Parse a 4-byte header. Returns `Error::invalid` on a bad sync word or a
58    /// reserved/free field this decoder doesn't accept yet.
59    pub fn parse(bytes: [u8; 4]) -> Result<FrameHeader> {
60        let h = u32::from_be_bytes(bytes);
61
62        // 11-bit frame sync: all ones.
63        if (h >> 21) & 0x7FF != 0x7FF {
64            return Err(Error::invalid("mp3 header: bad frame sync"));
65        }
66        let version = match (h >> 19) & 0x3 {
67            0b00 => MpegVersion::V2_5,
68            0b10 => MpegVersion::V2,
69            0b11 => MpegVersion::V1,
70            _ => return Err(Error::invalid("mp3 header: reserved MPEG version")),
71        };
72        // Layer field: 0b01 == Layer III. This codec only does Layer III.
73        if (h >> 17) & 0x3 != 0b01 {
74            return Err(Error::unsupported(
75                "mp3 header: only Layer III is supported",
76            ));
77        }
78        let crc_protected = (h >> 16) & 1 == 0;
79
80        let bitrate_index = ((h >> 12) & 0xF) as usize;
81        if bitrate_index == 0 {
82            return Err(Error::unsupported("mp3 header: free-format bitrate"));
83        }
84        if bitrate_index == 15 {
85            return Err(Error::invalid("mp3 header: reserved bitrate index"));
86        }
87        let bitrate_kbps = match version {
88            MpegVersion::V1 => tables::BITRATE_V1_L3[bitrate_index],
89            _ => tables::BITRATE_V2_L3[bitrate_index],
90        };
91
92        let samplerate_index = ((h >> 10) & 0x3) as usize;
93        if samplerate_index == 3 {
94            return Err(Error::invalid("mp3 header: reserved sample-rate index"));
95        }
96        // Base rates are MPEG-1; MPEG-2 halves them and MPEG-2.5 quarters them.
97        let base = tables::SAMPLE_RATE[samplerate_index];
98        let sample_rate = match version {
99            MpegVersion::V1 => base,
100            MpegVersion::V2 => base / 2,
101            MpegVersion::V2_5 => base / 4,
102        };
103
104        let padding = (h >> 9) & 1 == 1;
105        let mode_ext = ((h >> 4) & 0x3) as u8;
106        let channel_mode = match (h >> 6) & 0x3 {
107            0b00 => ChannelMode::Stereo,
108            // mode_extension bit 1 (value 2) = MS, bit 0 (value 1) = intensity.
109            0b01 => ChannelMode::JointStereo {
110                ms_stereo: mode_ext & 0x2 != 0,
111                intensity_stereo: mode_ext & 0x1 != 0,
112            },
113            0b10 => ChannelMode::DualMono,
114            _ => ChannelMode::Mono,
115        };
116
117        Ok(FrameHeader {
118            version,
119            crc_protected,
120            bitrate_kbps,
121            sample_rate,
122            padding,
123            channel_mode,
124            copyright: (h >> 3) & 1 == 1,
125            original: (h >> 2) & 1 == 1,
126            emphasis: (h & 0x3) as u8,
127        })
128    }
129
130    /// Serialize back to 4 bytes (encoder side) — the exact inverse of [`parse`](Self::parse).
131    pub fn to_bytes(&self) -> [u8; 4] {
132        let mut h: u32 = 0x7FF << 21; // frame sync
133        let version = match self.version {
134            MpegVersion::V2_5 => 0b00,
135            MpegVersion::V2 => 0b10,
136            MpegVersion::V1 => 0b11,
137        };
138        h |= version << 19;
139        h |= 0b01 << 17; // Layer III
140        h |= (!self.crc_protected as u32) << 16;
141
142        let br_table: &[u32; 16] = match self.version {
143            MpegVersion::V1 => &tables::BITRATE_V1_L3,
144            _ => &tables::BITRATE_V2_L3,
145        };
146        let br_idx = br_table
147            .iter()
148            .position(|&b| b == self.bitrate_kbps)
149            .unwrap_or(0) as u32;
150        h |= br_idx << 12;
151
152        let base = match self.version {
153            MpegVersion::V1 => self.sample_rate,
154            MpegVersion::V2 => self.sample_rate * 2,
155            MpegVersion::V2_5 => self.sample_rate * 4,
156        };
157        let sr_idx = tables::SAMPLE_RATE
158            .iter()
159            .position(|&s| s == base)
160            .unwrap_or(0) as u32;
161        h |= sr_idx << 10;
162        h |= (self.padding as u32) << 9;
163
164        let (chan, ext) = match self.channel_mode {
165            ChannelMode::Stereo => (0b00, 0),
166            ChannelMode::JointStereo {
167                ms_stereo,
168                intensity_stereo,
169            } => (0b01, (ms_stereo as u32) << 1 | intensity_stereo as u32),
170            ChannelMode::DualMono => (0b10, 0),
171            ChannelMode::Mono => (0b11, 0),
172        };
173        h |= chan << 6;
174        h |= ext << 4;
175        h |= (self.copyright as u32) << 3;
176        h |= (self.original as u32) << 2;
177        h |= self.emphasis as u32;
178        h.to_be_bytes()
179    }
180
181    /// Total frame size in bytes, including the header and optional CRC.
182    ///
183    /// `floor(samples_per_frame / 8 * bitrate / sample_rate) + padding`.
184    pub fn frame_size(&self) -> usize {
185        let spf = self.version.samples_per_frame();
186        let bytes = (spf / 8) * (self.bitrate_kbps as usize * 1000) / self.sample_rate as usize;
187        bytes + self.padding as usize
188    }
189
190    /// Bytes of side information following the header (+CRC): depends on version
191    /// and channel count (MPEG-1 stereo: 32, mono: 17; MPEG-2 stereo: 17, mono: 9).
192    pub fn side_info_len(&self) -> usize {
193        let stereo = self.channel_mode.channels() == 2;
194        match (self.version, stereo) {
195            (MpegVersion::V1, true) => 32,
196            (MpegVersion::V1, false) => 17,
197            (_, true) => 17,
198            (_, false) => 9,
199        }
200    }
201}