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]) -> legacy::Result<legacy::Header> {
if data.len() < 7 {
return Err(legacy::Error::Ac3HeaderTooShort);
}
if !(data[0] == 0x0B && data[1] == 0x77) {
return Err(legacy::Error::Ac3MissingSyncWord);
}
let fscod = data[4] >> 6;
let frmsizecod = (data[4] & 0x3F) as usize;
if frmsizecod > 37 {
return Err(legacy::Error::Ac3InvalidFrameSizeCode);
}
let bitrate_kbps = BITRATE[frmsizecod >> 1] as usize;
let bsid = data[5] >> 3;
if bsid > 8 {
return Err(legacy::Error::Ac3UnsupportedBsid(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),
_ => return Err(legacy::Error::Ac3ReservedSampleRate),
};
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());
}
}