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]) -> legacy::Result<legacy::Header> {
if data.len() < 6 {
return Err(legacy::Error::Eac3HeaderTooShort);
}
if !(data[0] == 0x0B && data[1] == 0x77) {
return Err(legacy::Error::Eac3MissingSyncWord);
}
let bsid = data[5] >> 3;
if !(11..=16).contains(&bsid) {
return Err(legacy::Error::Eac3NotEac3Bsid(bsid));
}
let strmtyp = data[2] >> 6;
if strmtyp == 3 {
return Err(legacy::Error::Eac3ReservedStreamType);
}
if strmtyp == 1 {
return Err(legacy::Error::Eac3DependentSubstream);
}
let substreamid = (data[2] >> 3) & 0x07;
if substreamid != 0 {
return Err(legacy::Error::Eac3AdditionalSubstream(substreamid));
}
let frmsiz = (((data[2] & 0x07) as usize) << 8) | data[3] as usize;
let len = (frmsiz + 1) * 2;
if len < 6 {
return Err(legacy::Error::Eac3FrameShorterThanHeader(len));
}
let fscod = data[4] >> 6;
let (sample_rate, blocks) = if fscod == 0b11 {
let fscod2 = (data[4] >> 4) & 0x03;
if fscod2 == 3 {
return Err(legacy::Error::Eac3ReservedSampleRate);
}
(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}");
}
}