#[derive(Clone, Copy, Debug)]
pub struct DcVlcEntry {
pub code: u16,
pub len: u8,
}
#[derive(Clone, Copy, Debug)]
pub struct AcVlcEntry {
pub run: u8,
pub level: u16,
pub last: bool,
pub code: u32,
pub len: u8,
}
pub const DC_TABLE_8BIT: [DcVlcEntry; 12] = [
DcVlcEntry {
code: 0b100 << 13,
len: 3,
}, DcVlcEntry {
code: 0b000 << 13,
len: 3,
}, DcVlcEntry {
code: 0b001 << 13,
len: 3,
}, DcVlcEntry {
code: 0b101 << 13,
len: 3,
}, DcVlcEntry {
code: 0b110 << 13,
len: 3,
}, DcVlcEntry {
code: 0b1110 << 12,
len: 4,
}, DcVlcEntry {
code: 0b11110 << 11,
len: 5,
}, DcVlcEntry {
code: 0b111110 << 10,
len: 6,
}, DcVlcEntry {
code: 0b1111110 << 9,
len: 7,
}, DcVlcEntry {
code: 0b11111110 << 8,
len: 8,
}, DcVlcEntry {
code: 0b111111110 << 7,
len: 9,
}, DcVlcEntry {
code: 0b1111111110 << 6,
len: 10,
}, ];
pub const DC_TABLE_10BIT: [DcVlcEntry; 12] = DC_TABLE_8BIT;
pub struct VlcTable {
pub table: Vec<(i16, u8)>,
pub index_bits: u8,
}
impl VlcTable {
pub fn build(entries: &[(u32, u8, i16)]) -> Self {
let max_len = entries.iter().map(|&(_, l, _)| l).max().unwrap_or(1);
let index_bits = max_len.max(1);
let table_size = 1usize << index_bits;
let mut table = vec![(0i16, 0u8); table_size];
for &(code, len, value) in entries {
if len == 0 || len > index_bits {
continue;
}
let shift = index_bits - len;
let base = (code as usize) << shift;
let span = 1usize << shift;
for offset in 0..span {
let idx = base | offset;
if idx < table_size {
table[idx] = (value, len);
}
}
}
Self { table, index_bits }
}
pub fn lookup(&self, bits: u32) -> Option<(i16, u8)> {
let idx = (bits >> (32 - self.index_bits as u32)) as usize;
if idx >= self.table.len() {
return None;
}
let (value, len) = self.table[idx];
if len == 0 {
None
} else {
Some((value, len))
}
}
}
pub fn build_dc_table_8bit() -> VlcTable {
let entries: Vec<(u32, u8, i16)> = DC_TABLE_8BIT
.iter()
.enumerate()
.map(|(size, e)| {
let code = (e.code as u32) >> (16 - e.len as u32);
(code, e.len, size as i16)
})
.collect();
VlcTable::build(&entries)
}
pub fn build_dc_table_10bit() -> VlcTable {
build_dc_table_8bit()
}
#[allow(clippy::type_complexity)]
pub const MPEG2_AC_TABLE: &[(u8, u16, bool, u32, u8)] = &[
(0, 0, true, 0x80000000, 2),
(0, 1, false, 0xC0000000, 2), (1, 1, false, 0x48000000, 5), (0, 2, false, 0x44000000, 5), (2, 1, false, 0x28000000, 5), (0, 3, false, 0x26000000, 6), (4, 1, false, 0x24000000, 6), (3, 1, false, 0x22000000, 6), (7, 1, false, 0x21000000, 7),
(6, 1, false, 0x20800000, 7),
(1, 2, false, 0x20400000, 7),
(5, 1, false, 0x20200000, 7),
(2, 2, false, 0x20100000, 8),
(9, 1, false, 0x20080000, 8),
(0, 4, false, 0x20040000, 8),
(8, 1, false, 0x20020000, 8),
(13, 1, false, 0x20010000, 9),
(0, 6, false, 0x20008000, 9),
(0, 5, false, 0x20004000, 9),
(3, 2, false, 0x20002000, 9),
(10, 1, false, 0x20001000, 9),
(11, 1, false, 0x20000800, 10),
(12, 1, false, 0x20000400, 10),
(1, 3, false, 0x20000200, 10),
(0, 7, false, 0x20000100, 10),
(4, 2, false, 0x20000080, 11),
(0, 8, false, 0x20000040, 11),
(14, 1, false, 0x20000020, 11),
(0, 12, false, 0x20000010, 12),
(5, 2, false, 0x20000008, 12),
(0, 11, false, 0x20000004, 12),
(0, 10, false, 0x20000002, 12),
(0, 9, false, 0x20000001, 12),
(0, 1, true, 0x78000000, 4), (1, 1, true, 0x74000000, 5),
(0, 2, true, 0x70000000, 5),
(0, 3, true, 0x5C000000, 6),
(4, 1, true, 0x58000000, 6),
(3, 1, true, 0x54000000, 6),
(2, 1, true, 0x50000000, 6),
(7, 1, true, 0x4C000000, 7),
(6, 1, true, 0x4A000000, 7),
(1, 2, true, 0x48800000, 8),
(5, 1, true, 0x48400000, 8),
(2, 2, true, 0x48200000, 8),
(9, 1, true, 0x48100000, 8),
(0, 4, true, 0x48080000, 8),
(8, 1, true, 0x48040000, 8),
(13, 1, true, 0x48020000, 9),
(0, 6, true, 0x48010000, 9),
(0, 5, true, 0x48008000, 9),
(3, 2, true, 0x48004000, 9),
(10, 1, true, 0x48002000, 9),
(11, 1, true, 0x48001000, 10),
(12, 1, true, 0x48000800, 10),
(1, 3, true, 0x48000400, 10),
(0, 7, true, 0x48000200, 10),
];
pub fn build_ac_table() -> VlcTable {
let entries: Vec<(u32, u8, i16)> = MPEG2_AC_TABLE
.iter()
.map(|&(run, level, last, code_msb, len)| {
let code = code_msb >> (32 - len as u32);
let packed =
((last as i16) << 14) | ((run as i16 & 0x3F) << 8) | (level.min(255) as i16 & 0xFF);
(code, len, packed)
})
.collect();
VlcTable::build(&entries)
}
#[must_use]
pub fn unpack_ac_value(packed: i16) -> (u8, u16, bool) {
let last = (packed >> 14) & 1 != 0;
let run = ((packed >> 8) & 0x3F) as u8;
let level = (packed & 0xFF) as u16;
(run, level, last)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dc_table_8bit_has_12_entries() {
assert_eq!(DC_TABLE_8BIT.len(), 12);
}
#[test]
fn dc_table_all_lens_nonzero() {
for (i, e) in DC_TABLE_8BIT.iter().enumerate() {
assert!(e.len > 0, "DC_TABLE_8BIT[{i}] has zero length");
assert!(e.len <= 16, "DC_TABLE_8BIT[{i}] len={} > 16", e.len);
}
}
#[test]
fn build_dc_table_and_lookup_all_sizes() {
let table = build_dc_table_8bit();
for (size, entry) in DC_TABLE_8BIT.iter().enumerate() {
let code_msb: u32 = (entry.code as u32) << 16;
let result = table.lookup(code_msb);
assert!(
result.is_some(),
"DC size {size}: lookup failed for code {:016b} len {}",
entry.code,
entry.len
);
let (val, consumed) = result.unwrap();
assert_eq!(val as usize, size, "DC size {size}: got value {val}");
assert_eq!(
consumed, entry.len,
"DC size {size}: expected len {} got {consumed}",
entry.len
);
}
}
#[test]
fn vlc_table_build_empty_is_safe() {
let table = VlcTable::build(&[]);
assert!(table.lookup(0).is_none());
}
#[test]
fn vlc_table_single_entry() {
let entries = vec![(0b10u32, 2u8, 42i16)];
let table = VlcTable::build(&entries);
let result = table.lookup(0b10 << 30);
assert_eq!(result, Some((42, 2)));
}
#[test]
fn unpack_ac_value_roundtrip() {
let (run, level, last) = (5u8, 3u16, true);
let packed = ((last as i16) << 14) | ((run as i16) << 8) | (level as i16);
let (r2, l2, la2) = unpack_ac_value(packed);
assert_eq!(r2, run);
assert_eq!(l2, level);
assert_eq!(la2, last);
}
}