use super::legacy;
pub(crate) static DESCRIPTOR: legacy::Descriptor = legacy::Descriptor {
track_suffix: ".eac3",
codec: hang::catalog::AudioCodec::Ec3,
min_header_len: 6,
parse: parse_header,
};
const SAMPLE_RATE: [u32; 3] = [48000, 44100, 32000];
const SAMPLE_RATE_REDUCED: [u32; 3] = [24000, 22050, 16000];
const BLOCKS: [u64; 4] = [1, 2, 3, 6];
const CHANNELS: [u32; 8] = [2, 1, 2, 3, 3, 4, 4, 5];
pub(crate) fn parse_header(data: &[u8]) -> anyhow::Result<legacy::Header> {
anyhow::ensure!(data.len() >= 6, "E-AC-3 header needs 6 bytes");
anyhow::ensure!(data[0] == 0x0B && data[1] == 0x77, "missing E-AC-3 sync word");
let bsid = data[5] >> 3;
anyhow::ensure!((11..=16).contains(&bsid), "not an E-AC-3 bitstream (bsid {bsid})");
let strmtyp = data[2] >> 6;
anyhow::ensure!(strmtyp != 3, "reserved E-AC-3 stream type");
anyhow::ensure!(
strmtyp != 1,
"E-AC-3 dependent substream (7.1+ layout) is not supported; only a single independent substream"
);
let substreamid = (data[2] >> 3) & 0x07;
anyhow::ensure!(
substreamid == 0,
"E-AC-3 additional substream {substreamid} is not supported; only a single independent substream"
);
let frmsiz = (((data[2] & 0x07) as usize) << 8) | data[3] as usize;
let len = (frmsiz + 1) * 2;
anyhow::ensure!(len >= 6, "E-AC-3 frame length {len} shorter than its header");
let fscod = data[4] >> 6;
let (sample_rate, blocks) = if fscod == 0b11 {
let fscod2 = (data[4] >> 4) & 0x03;
anyhow::ensure!(fscod2 != 3, "reserved E-AC-3 sample-rate code");
(SAMPLE_RATE_REDUCED[fscod2 as usize], 6)
} else {
(SAMPLE_RATE[fscod as usize], BLOCKS[((data[4] >> 4) & 0x03) as usize])
};
let acmod = (data[4] >> 1) & 0x07;
let lfeon = data[4] & 0x01;
Ok(legacy::Header {
len,
sample_rate,
channel_count: CHANNELS[acmod as usize] + lfeon as u32,
samples: 256 * blocks,
})
}
#[cfg(test)]
mod test {
use super::parse_header;
#[test]
fn parses_independent_substream() {
let h = parse_header(&[0x0B, 0x77, 0x00, 0xFF, 0x3F, 0x80]).unwrap();
assert_eq!(
(h.len, h.sample_rate, h.channel_count, h.samples),
(512, 48_000, 6, 1536)
);
let h = parse_header(&[0x0B, 0x77, 0x00, 0xFF, 0xCF, 0x80]).unwrap();
assert_eq!(
(h.len, h.sample_rate, h.channel_count, h.samples),
(512, 24_000, 6, 1536)
);
}
#[test]
fn rejects_reserved_codes() {
let err = parse_header(&[0x0B, 0x77, 0xC0, 0xFF, 0x3F, 0x80]).unwrap_err();
assert!(err.to_string().contains("reserved E-AC-3 stream type"), "{err}");
let err = parse_header(&[0x0B, 0x77, 0x00, 0xFF, 0xFF, 0x80]).unwrap_err();
assert!(err.to_string().contains("reserved E-AC-3 sample-rate code"), "{err}");
let err = parse_header(&[0x0B, 0x77, 0x00, 0x01, 0x3F, 0x80]).unwrap_err();
assert!(err.to_string().contains("shorter than its header"), "{err}");
}
#[test]
fn rejects_out_of_scope_substreams() {
let err = parse_header(&[0x0B, 0x77, 0x40, 0xFF, 0x3F, 0x80]).unwrap_err();
assert!(err.to_string().contains("dependent substream"), "{err}");
let err = parse_header(&[0x0B, 0x77, 0x08, 0xFF, 0x3F, 0x80]).unwrap_err();
assert!(err.to_string().contains("additional substream"), "{err}");
let err = parse_header(&[0x0B, 0x77, 0x00, 0xFF, 0x3F, 0x40]).unwrap_err();
assert!(err.to_string().contains("bsid"), "{err}");
}
}