Skip to main content

mpeg_audio/
mux.rs

1//! MPEG audio (Layer III) frame writer — one 4-byte header per already-encoded frame body.
2
3#![forbid(unsafe_code)]
4
5use crate::error::Error;
6use crate::types::{FrameHeader, MpegVersion, bitrate_table, sample_rate_table};
7
8const HEADER_LEN: usize = 4;
9const LAYER_III_BITS: u8 = 0b01;
10
11const fn version_bits(version: MpegVersion) -> u8 {
12    match version {
13        MpegVersion::Mpeg25 => 0b00,
14        MpegVersion::Mpeg2 => 0b10,
15        MpegVersion::Mpeg1 => 0b11,
16    }
17}
18
19/// Writes MPEG-1/2/2.5 Layer III frame headers for a fixed [`FrameHeader`].
20///
21/// This crate frames already-encoded MPEG audio data — it does not encode PCM
22/// into Layer III bitstreams (that is a codec's job, out of scope for a
23/// container/framing crate). [`Muxer::write_frame`] validates that `frame_body`'s
24/// length matches what the header's bitrate/sample-rate/padding combination
25/// requires, so a caller cannot silently write a frame that desyncs a real decoder.
26#[derive(Debug, Clone, Copy)]
27pub struct Muxer {
28    header: FrameHeader,
29    bitrate_index: u8,
30    sample_rate_index: u8,
31}
32
33impl Muxer {
34    /// Validate `header` (bitrate/sample rate must be standard Layer III values
35    /// for `header.version`) and start a mux session.
36    #[allow(
37        clippy::cast_possible_truncation,
38        reason = "bitrate/sample-rate tables have 14/3 entries; the index always fits u8"
39    )]
40    pub fn new(header: FrameHeader) -> Result<Self, Error> {
41        let bitrate_index = bitrate_table(header.version)
42            .iter()
43            .position(|&kbps| kbps == header.bitrate_kbps)
44            .map_or_else(
45                || Err(Error::UnsupportedBitrate(header.bitrate_kbps)),
46                |i| Ok(i as u8 + 1), // table index 0 => header field value 1 (0 = "free format", unsupported)
47            )?;
48        let sample_rate_index = sample_rate_table(header.version)
49            .iter()
50            .position(|&rate| rate == header.sample_rate)
51            .map_or_else(
52                || Err(Error::UnsupportedSampleRate(header.sample_rate)),
53                |i| Ok(i as u8),
54            )?;
55        Ok(Self {
56            header,
57            bitrate_index,
58            sample_rate_index,
59        })
60    }
61
62    /// Append one Layer III frame (4-byte header + `frame_body`) to `out`.
63    ///
64    /// `frame_body` must be exactly `header.frame_len(padding) - 4` bytes — the
65    /// already-encoded Layer III payload for this bitrate/sample-rate/padding
66    /// combination.
67    pub fn write_frame(
68        &self,
69        frame_body: &[u8],
70        padding: bool,
71        out: &mut Vec<u8>,
72    ) -> Result<(), Error> {
73        let expected = self.header.frame_len(padding) - HEADER_LEN;
74        if frame_body.len() != expected {
75            return Err(Error::FrameBodyLengthMismatch {
76                expected,
77                actual: frame_body.len(),
78            });
79        }
80
81        out.push(0xFF);
82        out.push(0xE0 | (version_bits(self.header.version) << 3) | (LAYER_III_BITS << 1) | 1);
83        out.push(
84            (self.bitrate_index << 4) | (self.sample_rate_index << 2) | (u8::from(padding) << 1),
85        );
86        out.push((self.header.channel_mode.bits() << 6) | 0b0000_0100); // original=1, rest 0
87        out.extend_from_slice(frame_body);
88        Ok(())
89    }
90}
91
92#[cfg(test)]
93#[path = "mux_tests.rs"]
94mod tests;