Skip to main content

adts_core/
mux.rs

1//! ADTS frame writer — one 7-byte header (no CRC) per raw AAC payload.
2
3#![forbid(unsafe_code)]
4
5use crate::error::Error;
6use crate::types::{AdtsConfig, sampling_frequency_index};
7
8const HEADER_LEN: usize = 7;
9const MAX_FRAME_LEN: usize = 0x1FFF; // 13-bit aac_frame_length field
10const BUFFER_FULLNESS_UNKNOWN: u16 = 0x7FF; // VBR / not indicated
11
12/// Writes ADTS frames for a fixed `AdtsConfig`.
13///
14/// Unlike `iso-bmff`'s box-based mux, ADTS has no container-level header at all —
15/// each call appends one self-contained frame directly to `out`, so there is no
16/// `finish()` step.
17#[derive(Debug, Clone, Copy)]
18pub struct Muxer {
19    config: AdtsConfig,
20    sfi: u8,
21}
22
23impl Muxer {
24    /// Validate `config` (sample rate must be a standard ADTS rate) and start a mux session.
25    pub fn new(config: AdtsConfig) -> Result<Self, Error> {
26        let sfi = sampling_frequency_index(config.sample_rate)
27            .ok_or(Error::UnsupportedSampleRate(config.sample_rate))?;
28        Ok(Self { config, sfi })
29    }
30
31    /// Append one ADTS frame (7-byte header + `raw_aac`) to `out`.
32    #[allow(
33        clippy::cast_possible_truncation,
34        reason = "every cast operand is bit-masked to fit u8 immediately before the cast"
35    )]
36    pub fn write_frame(&self, raw_aac: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
37        let frame_len = HEADER_LEN + raw_aac.len();
38        if frame_len > MAX_FRAME_LEN {
39            return Err(Error::FrameTooLarge(frame_len));
40        }
41        let profile = self.config.profile.bits();
42        let channels = self.config.channels & 0x07;
43        let fullness = usize::from(BUFFER_FULLNESS_UNKNOWN);
44
45        out.push(0xFF);
46        out.push(0xF1); // MPEG-4 (ID=0), layer=00, protection_absent=1 (no CRC)
47        out.push((profile << 6) | (self.sfi << 2) | (channels >> 2));
48        out.push(((channels & 0x03) << 6) | ((frame_len >> 11) & 0x03) as u8);
49        out.push(((frame_len >> 3) & 0xFF) as u8);
50        out.push((((frame_len & 0x07) as u8) << 5) | ((fullness >> 6) & 0x1F) as u8);
51        out.push(((fullness & 0x3F) as u8) << 2); // low 6 bits of fullness + 2-bit block count (0 = 1 block)
52        out.extend_from_slice(raw_aac);
53        Ok(())
54    }
55}
56
57#[cfg(test)]
58#[path = "mux_tests.rs"]
59mod tests;