use super::legacy;
pub(crate) static DESCRIPTOR: legacy::Descriptor = legacy::Descriptor {
track_suffix: ".ac3",
codec: hang::catalog::AudioCodec::Ac3,
min_header_len: 7,
parse: parse_header,
};
const BITRATE: [u32; 19] = [
32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640,
];
const CHANNELS: [u32; 8] = [2, 1, 2, 3, 3, 4, 4, 5];
const SAMPLES_PER_FRAME: u64 = 1536;
pub(crate) fn parse_header(data: &[u8]) -> anyhow::Result<legacy::Header> {
anyhow::ensure!(data.len() >= 7, "AC-3 header needs 7 bytes");
anyhow::ensure!(data[0] == 0x0B && data[1] == 0x77, "missing AC-3 sync word");
let fscod = data[4] >> 6;
let frmsizecod = (data[4] & 0x3F) as usize;
anyhow::ensure!(frmsizecod <= 37, "invalid AC-3 frame size code");
let bitrate_kbps = BITRATE[frmsizecod >> 1] as usize;
let bsid = data[5] >> 3;
anyhow::ensure!(bsid <= 8, "unsupported AC-3 bsid {bsid}");
let (sample_rate, len) = match fscod {
0b00 => (48000, 4 * bitrate_kbps),
0b01 => (44100, 2 * (320 * bitrate_kbps / 147 + (frmsizecod & 1))),
0b10 => (32000, 6 * bitrate_kbps),
_ => anyhow::bail!("reserved AC-3 sample-rate code"),
};
let acmod = data[6] >> 5;
let mut bit = 3;
if acmod & 0x01 != 0 && acmod != 0x01 {
bit += 2; }
if acmod & 0x04 != 0 {
bit += 2; }
if acmod == 0x02 {
bit += 2; }
let lfeon = (data[6] >> (7 - bit)) & 0x01;
let channel_count = CHANNELS[acmod as usize] + lfeon as u32;
Ok(legacy::Header {
len,
sample_rate,
channel_count,
samples: SAMPLES_PER_FRAME,
})
}
#[cfg(test)]
mod test {
use super::parse_header;
#[test]
fn parses_5_1_sync_frame() {
let h = parse_header(&[0x0B, 0x77, 0x00, 0x00, 0x1C, 0x40, 0xE1]).unwrap();
assert_eq!(
(h.len, h.sample_rate, h.channel_count, h.samples),
(1536, 48_000, 6, 1536)
);
}
#[test]
fn rejects_eac3_bsid() {
assert!(parse_header(&[0x0B, 0x77, 0x00, 0x00, 0x1C, 0x80, 0xE1]).is_err());
}
}