arcly-stream 0.1.6

An open-extensible live-media streaming kernel: lock-free zero-copy frame fan-out, instant-start GOP cache, a pluggable multi-protocol ingestion layer (RTMP, RTSP, SRT, WHIP/WHEP shipped), and a feature-gated pure-Rust media plane (MPEG-TS/HLS/fMP4) — runtime, config, and metrics free.
Documentation
//! AAC helpers: ADTS frame-header parsing.

/// MPEG-4 sampling-frequency table indexed by `sampling_frequency_index`.
pub const SAMPLE_RATES: [u32; 13] = [
    96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
];

/// A decoded ADTS frame header (7-byte fixed + variable header).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AdtsHeader {
    /// AAC audio object type (`profile + 1`; 2 = AAC-LC).
    pub object_type: u8,
    /// Sampling rate in Hz (resolved from the frequency index).
    pub sample_rate: u32,
    /// Channel configuration (1 = mono, 2 = stereo, …).
    pub channels: u8,
    /// Total frame length including the header, in bytes.
    pub frame_length: usize,
    /// Header length in bytes (7 without CRC, 9 with).
    pub header_len: usize,
}

/// Parse the ADTS header at the start of `data`. Returns `None` if the syncword
/// is absent or the buffer is too short.
pub fn parse_adts(data: &[u8]) -> Option<AdtsHeader> {
    if data.len() < 7 {
        return None;
    }
    // Syncword: 12 bits of 1s (0xFFF).
    if data[0] != 0xFF || (data[1] & 0xF0) != 0xF0 {
        return None;
    }
    let protection_absent = data[1] & 0x01;
    let profile = (data[2] >> 6) & 0x03;
    let freq_index = ((data[2] >> 2) & 0x0F) as usize;
    let channel_config = ((data[2] & 0x01) << 2) | ((data[3] >> 6) & 0x03);
    let frame_length =
        (((data[3] & 0x03) as usize) << 11) | ((data[4] as usize) << 3) | ((data[5] as usize) >> 5);

    let sample_rate = *SAMPLE_RATES.get(freq_index)?;
    let header_len = if protection_absent == 1 { 7 } else { 9 };
    if frame_length < header_len {
        return None;
    }

    Some(AdtsHeader {
        object_type: profile + 1,
        sample_rate,
        channels: channel_config,
        frame_length,
        header_len,
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_aac_lc_stereo_44100() {
        // AAC-LC (profile 1), freq index 4 (44100), 2 channels, frame_length 13.
        // byte2: profile(01) freq(0100) private(0) chan_hi(0) = 0b01_0100_0_0 = 0x50
        // byte3: chan_lo(10) orig/home/etc(0000) len_hi(00) = 0b10_0000_00 = 0x80
        // frame_length 13 = 0b0_0000_0000_1101 → len_hi(2)=00, byte4=0b0000_0001(=1)<<? ...
        // 13 = (0<<11)|(1<<3)|(13>>5=0)? recompute: 13 = byte3[1:0]<<11 | byte4<<3 | byte5>>5
        //   need 13 → byte4 = 1 (1<<3 = 8), byte5>>5 = 5 → byte5 = 0b101_00000 = 0xA0
        let hdr = [0xFF, 0xF1, 0x50, 0x80, 0x01, 0xA0, 0x00];
        let h = parse_adts(&hdr).expect("parse");
        assert_eq!(h.object_type, 2); // AAC-LC
        assert_eq!(h.sample_rate, 44100);
        assert_eq!(h.channels, 2);
        assert_eq!(h.frame_length, 13);
        assert_eq!(h.header_len, 7); // protection_absent = 1
    }

    #[test]
    fn rejects_without_syncword() {
        assert!(parse_adts(&[0x00; 8]).is_none());
        assert!(parse_adts(&[0xFF, 0x00]).is_none());
    }
}