use crate::bit_reader::BitReader;
use crate::error::CodecError;
pub const AAC_SAMPLE_FREQUENCIES: [u32; 13] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AudioSpecificConfig {
pub object_type: u8,
pub sample_rate: u32,
pub channel_config: u8,
pub sbr_present: bool,
pub ps_present: bool,
}
impl AudioSpecificConfig {
pub fn codec_string(&self) -> String {
format!("mp4a.40.{}", self.object_type)
}
}
pub fn parse_asc(bytes: &[u8]) -> Result<AudioSpecificConfig, CodecError> {
if bytes.is_empty() {
return Err(CodecError::EndOfStream {
needed: 1,
remaining: 0,
});
}
let mut r = BitReader::new(bytes);
let object_type = read_object_type(&mut r)?;
let sample_rate = read_sample_rate(&mut r)?;
let channel_config = r.read_bits(4)? as u8;
let (sbr_present, ps_present, base_object_type) = match object_type {
5 | 29 => {
let ext_sfi = r.read_bits(4)? as u8;
if ext_sfi == 15 {
let _ = r.read_bits(24)?;
}
let downstream = read_object_type(&mut r)?;
let ps = object_type == 29;
(true, ps, downstream)
}
_ => (false, false, object_type),
};
Ok(AudioSpecificConfig {
object_type: if sbr_present { object_type } else { base_object_type },
sample_rate,
channel_config,
sbr_present,
ps_present,
})
}
fn read_object_type(r: &mut BitReader<'_>) -> Result<u8, CodecError> {
let base = r.read_bits(5)? as u8;
if base == 31 {
let ext = r.read_bits(6)? as u8;
Ok(32 + ext)
} else {
Ok(base)
}
}
fn read_sample_rate(r: &mut BitReader<'_>) -> Result<u32, CodecError> {
let sfi = r.read_bits(4)? as u8;
if sfi == 15 {
let freq = r.read_bits(24)?;
const MIN_PLAUSIBLE_HZ: u32 = 7350;
if freq < MIN_PLAUSIBLE_HZ {
return Err(CodecError::MalformedAsc("explicit sample rate below 7350 Hz"));
}
Ok(freq)
} else {
AAC_SAMPLE_FREQUENCIES
.get(sfi as usize)
.copied()
.ok_or(CodecError::MalformedAsc("sampling_frequency_index out of range"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_aac_lc_stereo_48khz() {
let asc = parse_asc(&[0x11, 0x90]).unwrap();
assert_eq!(asc.object_type, 2);
assert_eq!(asc.sample_rate, 48000);
assert_eq!(asc.channel_config, 2);
assert!(!asc.sbr_present);
assert!(!asc.ps_present);
assert_eq!(asc.codec_string(), "mp4a.40.2");
}
#[test]
fn parse_legacy_lvqr_aac_lc_stereo_44k() {
let asc = parse_asc(&[0x12, 0x10]).unwrap();
assert_eq!(asc.object_type, 2);
assert_eq!(asc.sample_rate, 44100);
assert_eq!(asc.channel_config, 2);
}
#[test]
fn parse_he_aac_signals_sbr() {
let asc = parse_asc(&[0x29, 0x88, 0xC8]).unwrap();
assert!(asc.sbr_present);
assert!(!asc.ps_present);
assert_eq!(asc.object_type, 5);
}
#[test]
fn parse_rejects_empty_bytes() {
assert!(matches!(parse_asc(&[]), Err(CodecError::EndOfStream { .. })));
}
#[test]
fn parse_escape_object_type() {
let asc = parse_asc(&[0xF9, 0x46, 0x40]).unwrap();
assert_eq!(asc.object_type, 42);
assert_eq!(asc.sample_rate, 48000);
assert_eq!(asc.channel_config, 2);
}
#[test]
fn parse_explicit_frequency() {
let asc = parse_asc(&[0x17, 0x80, 0xBB, 0x80, 0x10]).unwrap();
assert_eq!(asc.object_type, 2);
assert_eq!(asc.sample_rate, 96000);
assert_eq!(asc.channel_config, 2);
}
#[test]
fn parse_rejects_explicit_sample_rate_below_7350_hz() {
let explicit_seed: &[u8] = &[87, 128, 0, 0, 128];
match parse_asc(explicit_seed) {
Err(CodecError::MalformedAsc(msg)) => {
assert!(msg.contains("7350"), "expected the 7350 Hz floor error, got: {msg}");
}
other => panic!("expected MalformedAsc error, got {other:?}"),
}
}
}