use anyhow::Context;
const SAMPLE_RATES: [u32; 13] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
];
pub(super) const MIN_HEADER_LEN: usize = 7;
#[derive(Clone, Copy, Debug)]
pub(super) struct Header {
pub object_type: u8,
pub sample_rate: u32,
pub channel_config: u8,
pub frame_len: usize,
pub header_len: usize,
}
impl Header {
pub fn parse(data: &[u8]) -> anyhow::Result<Self> {
anyhow::ensure!(data.len() >= MIN_HEADER_LEN, "ADTS header truncated");
anyhow::ensure!(data[0] == 0xFF && (data[1] & 0xF0) == 0xF0, "missing ADTS syncword");
let protection_absent = data[1] & 0x01;
let profile = (data[2] >> 6) & 0x03;
let freq_index = (data[2] >> 2) & 0x0F;
let channel_config = ((data[2] & 0x01) << 2) | ((data[3] >> 6) & 0x03);
let frame_len =
((data[3] as usize & 0x03) << 11) | ((data[4] as usize) << 3) | ((data[5] as usize >> 5) & 0x07);
let sample_rate = SAMPLE_RATES
.get(freq_index as usize)
.copied()
.context("reserved ADTS sample rate index")?;
let header_len = if protection_absent == 1 { 7 } else { 9 };
anyhow::ensure!(frame_len >= header_len, "ADTS frame length smaller than its header");
Ok(Self {
object_type: profile + 1,
sample_rate,
channel_config,
frame_len,
header_len,
})
}
}
pub(super) fn write_header(
object_type: u8,
sample_rate: u32,
channel_config: u8,
raw_len: usize,
) -> anyhow::Result<[u8; 7]> {
anyhow::ensure!(
(1..=4).contains(&object_type),
"audioObjectType {object_type} not representable in ADTS"
);
let profile = object_type - 1;
let freq_index = freq_index_from_rate(sample_rate)?;
anyhow::ensure!(
channel_config < 8,
"channelConfiguration {channel_config} not representable in ADTS"
);
let frame_len = raw_len + 7;
anyhow::ensure!(frame_len < (1 << 13), "AAC frame too large for ADTS framing");
let mut h = [0u8; 7];
h[0] = 0xFF;
h[1] = 0xF1;
h[2] = (profile << 6) | ((freq_index & 0x0F) << 2) | ((channel_config >> 2) & 0x01);
h[3] = ((channel_config & 0x03) << 6) | ((frame_len >> 11) as u8 & 0x03);
h[4] = ((frame_len >> 3) & 0xFF) as u8;
h[5] = (((frame_len & 0x07) << 5) as u8) | 0x1F;
h[6] = 0xFC;
Ok(h)
}
fn freq_index_from_rate(sample_rate: u32) -> anyhow::Result<u8> {
SAMPLE_RATES
.iter()
.position(|&r| r == sample_rate)
.map(|i| i as u8)
.with_context(|| format!("sample rate {sample_rate} not representable in ADTS"))
}
pub(super) fn channel_config_from_count(channel_count: u32) -> anyhow::Result<u8> {
match channel_count {
1..=6 => Ok(channel_count as u8),
8 => Ok(7),
_ => anyhow::bail!("{channel_count} channels have no ADTS channelConfiguration"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_roundtrip() {
let header = write_header(2, 48_000, 2, 100).unwrap();
let parsed = Header::parse(&header).unwrap();
assert_eq!(parsed.object_type, 2);
assert_eq!(parsed.sample_rate, 48_000);
assert_eq!(parsed.channel_config, 2);
assert_eq!(parsed.header_len, 7);
assert_eq!(parsed.frame_len, 107, "frame_len includes the 7-byte header");
}
#[test]
fn parse_rejects_bad_syncword() {
let mut header = write_header(2, 44_100, 2, 10).unwrap();
header[0] = 0x00;
assert!(Header::parse(&header).is_err());
}
#[test]
fn write_refuses_object_types_outside_the_profile_field() {
for object_type in [0, 5, 29] {
assert!(write_header(object_type, 48_000, 2, 10).is_err());
}
for object_type in 1..=4 {
let header = write_header(object_type, 48_000, 2, 10).unwrap();
assert_eq!(Header::parse(&header).unwrap().object_type, object_type);
}
}
#[test]
fn channel_counts_without_a_configuration_are_refused() {
assert_eq!(channel_config_from_count(8).unwrap(), 7);
for count in [0, 7, 9, 24] {
assert!(channel_config_from_count(count).is_err());
}
}
#[test]
fn frame_len_for_5_1() {
let header = write_header(2, 44_100, 6, 512).unwrap();
let parsed = Header::parse(&header).unwrap();
assert_eq!(parsed.channel_config, 6);
assert_eq!(parsed.sample_rate, 44_100);
assert_eq!(parsed.frame_len, 519);
}
}