#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ac3SyncInfo {
pub fscod: u8,
pub bit_rate_code: u8,
pub bsid: u8,
pub bsmod: u8,
pub acmod: u8,
pub lfeon: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Eac3SyncInfo {
pub strmtyp: u8,
pub substreamid: u8,
pub frmsiz: u16,
pub fscod: u8,
pub fscod2: u8,
pub numblkscod: u8,
pub acmod: u8,
pub lfeon: bool,
pub bsid: u8,
pub dialnorm: u8,
pub bsmod: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncInfo {
Ac3(Ac3SyncInfo),
Eac3(Eac3SyncInfo),
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum SyncError {
#[error("AC-3/E-AC-3 sync: input shorter than minimum syncframe")]
Truncated,
#[error("AC-3/E-AC-3 sync: missing 0x0B77 syncword at offset 0")]
MissingSyncword,
#[error("AC-3/E-AC-3 sync: reserved fscod=3 outside E-AC-3 reduced-rate path")]
ReservedFscod,
#[error("AC-3/E-AC-3 sync: bsid {0} outside the AC-3 (≤10) / E-AC-3 (16) ranges supported")]
UnsupportedBsid(u8),
}
pub fn parse_sync_info(bytes: &[u8]) -> Result<SyncInfo, SyncError> {
if bytes.len() < 6 {
return Err(SyncError::Truncated);
}
if bytes[0] != 0x0B || bytes[1] != 0x77 {
return Err(SyncError::MissingSyncword);
}
let bsid = bytes[5] >> 3;
if bsid <= 10 {
Ok(SyncInfo::Ac3(parse_ac3(bytes)?))
} else if bsid == 16 {
Ok(SyncInfo::Eac3(parse_eac3(bytes)?))
} else {
Err(SyncError::UnsupportedBsid(bsid))
}
}
fn parse_ac3(bytes: &[u8]) -> Result<Ac3SyncInfo, SyncError> {
if bytes.len() < 7 {
return Err(SyncError::Truncated);
}
let mut br = BitReader::new(bytes);
br.skip(16); br.skip(16); let fscod = br.read(2) as u8;
if fscod == 3 {
return Err(SyncError::ReservedFscod);
}
let frmsizecod = br.read(6) as u8;
let bit_rate_code = frmsizecod >> 1; let bsid = br.read(5) as u8;
let bsmod = br.read(3) as u8;
let acmod = br.read(3) as u8;
if (acmod & 0x01) != 0 && acmod != 0x01 {
br.skip(2); }
if (acmod & 0x04) != 0 {
br.skip(2); }
if acmod == 0x02 {
br.skip(2); }
let lfeon = br.read(1) == 1;
Ok(Ac3SyncInfo {
fscod,
bit_rate_code,
bsid,
bsmod,
acmod,
lfeon,
})
}
fn parse_eac3(bytes: &[u8]) -> Result<Eac3SyncInfo, SyncError> {
if bytes.len() < 8 {
return Err(SyncError::Truncated);
}
let mut br = BitReader::new(bytes);
br.skip(16); let strmtyp = br.read(2) as u8;
let substreamid = br.read(3) as u8;
let frmsiz = br.read(11) as u16;
let fscod = br.read(2) as u8;
let (fscod2, numblkscod) = if fscod == 3 {
(br.read(2) as u8, 3u8)
} else {
(0u8, br.read(2) as u8)
};
let acmod = br.read(3) as u8;
let lfeon = br.read(1) == 1;
let bsid = br.read(5) as u8;
if bsid != 16 {
return Err(SyncError::UnsupportedBsid(bsid));
}
let dialnorm = br.read(5) as u8;
let compre = br.read(1) == 1;
let bsmod = 0u8;
if compre {
br.skip(8);
let _ = bsmod;
}
Ok(Eac3SyncInfo {
strmtyp,
substreamid,
frmsiz,
fscod,
fscod2,
numblkscod,
acmod,
lfeon,
bsid,
dialnorm,
bsmod,
})
}
pub fn channel_count(acmod: u8, lfeon: bool) -> u16 {
let base = match acmod {
0 => 2, 1 => 1, 2 => 2, 3 => 3, 4 => 3, 5 => 4, 6 => 4, 7 => 5, _ => 0,
};
base + if lfeon { 1 } else { 0 }
}
pub fn ac3_bit_rate_kbps(bit_rate_code: u8) -> u32 {
match bit_rate_code {
0 => 32,
1 => 40,
2 => 48,
3 => 56,
4 => 64,
5 => 80,
6 => 96,
7 => 112,
8 => 128,
9 => 160,
10 => 192,
11 => 224,
12 => 256,
13 => 320,
14 => 384,
15 => 448,
16 => 512,
17 => 576,
18 => 640,
_ => 0,
}
}
pub fn ac3_sample_rate_hz(fscod: u8) -> u32 {
match fscod {
0 => 48_000,
1 => 44_100,
2 => 32_000,
_ => 0,
}
}
pub fn eac3_sample_rate_hz(fscod: u8, fscod2: u8) -> u32 {
if fscod < 3 {
return ac3_sample_rate_hz(fscod);
}
match fscod2 {
0 => 24_000,
1 => 22_050,
2 => 16_000,
_ => 0,
}
}
pub fn eac3_samples_per_frame(numblkscod: u8) -> u32 {
let blocks = match numblkscod & 0x03 {
0 => 1u32,
1 => 2u32,
2 => 3u32,
_ => 6u32,
};
blocks * 256
}
struct BitReader<'a> {
data: &'a [u8],
bit_pos: usize,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, bit_pos: 0 }
}
fn skip(&mut self, n: usize) {
self.bit_pos += n;
}
fn read(&mut self, n: usize) -> u32 {
debug_assert!(n <= 24, "BitReader::read: cap is 24 bits per call");
let mut value: u32 = 0;
for _ in 0..n {
let byte_idx = self.bit_pos / 8;
let bit_idx = 7 - (self.bit_pos % 8);
let bit = if byte_idx < self.data.len() {
(self.data[byte_idx] >> bit_idx) & 0x01
} else {
0
};
value = (value << 1) | bit as u32;
self.bit_pos += 1;
}
value
}
}
#[cfg(test)]
mod tests {
use super::*;
fn synth_ac3_header(
fscod: u8,
bit_rate_code: u8,
bsid: u8,
bsmod: u8,
acmod: u8,
lfeon: bool,
) -> Vec<u8> {
let mut bw = BitWriter::new();
bw.put(16, 0x0B77); bw.put(16, 0); bw.put(2, fscod as u32);
bw.put(6, (bit_rate_code as u32) << 1);
bw.put(5, bsid as u32);
bw.put(3, bsmod as u32);
bw.put(3, acmod as u32);
if (acmod & 0x01) != 0 && acmod != 0x01 {
bw.put(2, 0); }
if (acmod & 0x04) != 0 {
bw.put(2, 0); }
if acmod == 0x02 {
bw.put(2, 0); }
bw.put(1, if lfeon { 1 } else { 0 });
while bw.bytes.len() < 12 {
bw.put(8, 0);
}
bw.flush()
}
fn synth_eac3_header(
strmtyp: u8,
substreamid: u8,
frmsiz: u16,
fscod: u8,
numblkscod: u8,
acmod: u8,
lfeon: bool,
) -> Vec<u8> {
let mut bw = BitWriter::new();
bw.put(16, 0x0B77);
bw.put(2, strmtyp as u32);
bw.put(3, substreamid as u32);
bw.put(11, frmsiz as u32);
bw.put(2, fscod as u32);
bw.put(2, numblkscod as u32);
bw.put(3, acmod as u32);
bw.put(1, if lfeon { 1 } else { 0 });
bw.put(5, 16); bw.put(5, 0); bw.put(1, 0); while bw.bytes.len() < 16 {
bw.put(8, 0);
}
bw.flush()
}
struct BitWriter {
bytes: Vec<u8>,
bit_pos: usize,
}
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
bit_pos: 0,
}
}
fn put(&mut self, n: usize, v: u32) {
for i in (0..n).rev() {
let bit = ((v >> i) & 0x01) as u8;
if self.bit_pos % 8 == 0 {
self.bytes.push(0);
}
let byte_idx = self.bit_pos / 8;
let bit_idx = 7 - (self.bit_pos % 8);
self.bytes[byte_idx] |= bit << bit_idx;
self.bit_pos += 1;
}
}
fn flush(self) -> Vec<u8> {
self.bytes
}
}
#[test]
fn parse_ac3_5_1_384k_48k() {
let bytes = synth_ac3_header(0, 14, 8, 0, 7, true);
let info = parse_sync_info(&bytes).expect("must parse");
match info {
SyncInfo::Ac3(ac3) => {
assert_eq!(ac3.fscod, 0, "fscod=0 → 48 kHz");
assert_eq!(ac3.bit_rate_code, 14, "Table F.6 idx 14 = 384 kbps");
assert_eq!(ac3.bsid, 8, "AC-3 bsid = 8");
assert_eq!(ac3.bsmod, 0);
assert_eq!(ac3.acmod, 7, "acmod=7 → 3/2 (5.1 with LFE)");
assert!(ac3.lfeon);
assert_eq!(channel_count(ac3.acmod, ac3.lfeon), 6);
assert_eq!(ac3_bit_rate_kbps(ac3.bit_rate_code), 384);
assert_eq!(ac3_sample_rate_hz(ac3.fscod), 48_000);
}
_ => panic!("expected AC-3"),
}
}
#[test]
fn parse_ac3_stereo_192k() {
let bytes = synth_ac3_header(0, 10, 8, 0, 2, false);
let info = parse_sync_info(&bytes).expect("parse");
match info {
SyncInfo::Ac3(ac3) => {
assert_eq!(ac3.acmod, 2);
assert!(!ac3.lfeon);
assert_eq!(channel_count(ac3.acmod, ac3.lfeon), 2);
assert_eq!(ac3_bit_rate_kbps(ac3.bit_rate_code), 192);
}
_ => panic!("expected AC-3"),
}
}
#[test]
fn parse_ac3_mono_64k() {
let bytes = synth_ac3_header(0, 4, 8, 0, 1, false);
match parse_sync_info(&bytes).expect("parse") {
SyncInfo::Ac3(ac3) => {
assert_eq!(ac3.acmod, 1);
assert_eq!(channel_count(ac3.acmod, ac3.lfeon), 1);
assert_eq!(ac3_bit_rate_kbps(ac3.bit_rate_code), 64);
}
_ => panic!("AC-3 expected"),
}
}
#[test]
fn parse_eac3_5_1_independent() {
let bytes = synth_eac3_header(0, 0, 0x05F, 0, 3, 7, true);
match parse_sync_info(&bytes).expect("parse") {
SyncInfo::Eac3(e) => {
assert_eq!(e.strmtyp, 0);
assert_eq!(e.substreamid, 0);
assert_eq!(e.frmsiz, 0x05F);
assert_eq!(e.fscod, 0);
assert_eq!(e.numblkscod, 3);
assert_eq!(e.acmod, 7);
assert!(e.lfeon);
assert_eq!(e.bsid, 16);
assert_eq!(channel_count(e.acmod, e.lfeon), 6);
assert_eq!(eac3_samples_per_frame(e.numblkscod), 1536);
assert_eq!(eac3_sample_rate_hz(e.fscod, e.fscod2), 48_000);
}
_ => panic!("expected E-AC-3"),
}
}
#[test]
fn parse_rejects_bad_syncword() {
let mut bytes = synth_ac3_header(0, 14, 8, 0, 7, true);
bytes[0] = 0xAA;
assert_eq!(parse_sync_info(&bytes), Err(SyncError::MissingSyncword));
}
#[test]
fn parse_rejects_truncated() {
let bytes = vec![0x0B, 0x77];
assert_eq!(parse_sync_info(&bytes), Err(SyncError::Truncated));
}
#[test]
fn parse_rejects_unknown_bsid() {
let bytes = synth_ac3_header(0, 14, 12, 0, 7, true);
assert_eq!(parse_sync_info(&bytes), Err(SyncError::UnsupportedBsid(12)));
}
#[test]
fn channel_count_table() {
assert_eq!(channel_count(0, false), 2); assert_eq!(channel_count(1, false), 1); assert_eq!(channel_count(2, false), 2); assert_eq!(channel_count(3, false), 3); assert_eq!(channel_count(4, false), 3); assert_eq!(channel_count(5, false), 4); assert_eq!(channel_count(6, false), 4); assert_eq!(channel_count(7, false), 5); assert_eq!(channel_count(7, true), 6); assert_eq!(channel_count(2, true), 3); }
#[test]
fn bit_rate_table_spans_zero_to_640() {
assert_eq!(ac3_bit_rate_kbps(0), 32);
assert_eq!(ac3_bit_rate_kbps(8), 128);
assert_eq!(ac3_bit_rate_kbps(14), 384);
assert_eq!(ac3_bit_rate_kbps(18), 640);
assert_eq!(ac3_bit_rate_kbps(19), 0); }
}