pub const SAMPLE_RATES: [u32; 13] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AdtsHeader {
pub object_type: u8,
pub sample_rate: u32,
pub channels: u8,
pub frame_length: usize,
pub header_len: usize,
}
pub fn parse_adts(data: &[u8]) -> Option<AdtsHeader> {
if data.len() < 7 {
return None;
}
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() {
let hdr = [0xFF, 0xF1, 0x50, 0x80, 0x01, 0xA0, 0x00];
let h = parse_adts(&hdr).expect("parse");
assert_eq!(h.object_type, 2); assert_eq!(h.sample_rate, 44100);
assert_eq!(h.channels, 2);
assert_eq!(h.frame_length, 13);
assert_eq!(h.header_len, 7); }
#[test]
fn rejects_without_syncword() {
assert!(parse_adts(&[0x00; 8]).is_none());
assert!(parse_adts(&[0xFF, 0x00]).is_none());
}
}