use crate::error::{MjpegError as Error, Result};
#[derive(Clone, Copy, Debug, Default)]
pub struct HuffCode {
pub code: u16,
pub len: u8,
}
#[derive(Clone, Debug)]
pub struct HuffTable {
pub min_code: [i32; 17],
pub val_offset: [i32; 17],
pub values: Vec<u8>,
pub max_code: [i32; 17],
pub encode: Vec<HuffCode>,
}
impl HuffTable {
pub fn build(bits: &[u8; 16], values: &[u8]) -> Result<Self> {
let total: usize = bits.iter().map(|&b| b as usize).sum();
if total != values.len() {
return Err(Error::invalid("DHT: symbol count mismatch"));
}
if total > 256 {
return Err(Error::invalid("DHT: > 256 symbols"));
}
let mut min_code = [0i32; 17];
let mut max_code = [-1i32; 17];
let mut val_offset = [0i32; 17];
let mut code: u32 = 0;
let mut sym_idx: usize = 0;
for l in 0..16 {
let n = bits[l] as usize;
if n == 0 {
min_code[l] = i32::MAX;
max_code[l] = -1;
val_offset[l] = -1;
code <<= 1;
continue;
}
min_code[l] = code as i32;
val_offset[l] = sym_idx as i32 - code as i32;
for _ in 0..n {
code += 1;
}
max_code[l] = (code - 1) as i32;
sym_idx += n;
code <<= 1;
}
let mut encode = vec![HuffCode::default(); 256];
let mut sidx = 0usize;
let mut c: u32 = 0;
for l in 0..16 {
let n = bits[l] as usize;
for _ in 0..n {
let sym = values[sidx] as usize;
encode[sym] = HuffCode {
code: c as u16,
len: (l as u8) + 1,
};
sidx += 1;
c += 1;
}
c <<= 1;
}
Ok(Self {
min_code,
val_offset,
values: values.to_vec(),
max_code,
encode,
})
}
}
pub struct DefaultHuffman {
pub luma_dc: HuffTable,
pub luma_ac: HuffTable,
pub chroma_dc: HuffTable,
pub chroma_ac: HuffTable,
}
pub const STD_DC_LUMA_BITS: [u8; 16] = [0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0];
pub const STD_DC_LUMA_VALS: [u8; 12] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
];
pub const STD_DC_CHROMA_BITS: [u8; 16] = [0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0];
pub const STD_DC_CHROMA_VALS: [u8; 12] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
];
pub const STD_AC_LUMA_BITS: [u8; 16] = [0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7D];
pub const STD_AC_LUMA_VALS: [u8; 162] = [
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
0xF9, 0xFA,
];
pub const STD_AC_CHROMA_BITS: [u8; 16] = [0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77];
pub const STD_AC_CHROMA_VALS: [u8; 162] = [
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0,
0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26,
0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5,
0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3,
0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA,
0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
0xF9, 0xFA,
];
impl DefaultHuffman {
pub fn build() -> Result<Self> {
Ok(Self {
luma_dc: HuffTable::build(&STD_DC_LUMA_BITS, &STD_DC_LUMA_VALS)?,
luma_ac: HuffTable::build(&STD_AC_LUMA_BITS, &STD_AC_LUMA_VALS)?,
chroma_dc: HuffTable::build(&STD_DC_CHROMA_BITS, &STD_DC_CHROMA_VALS)?,
chroma_ac: HuffTable::build(&STD_AC_CHROMA_BITS, &STD_AC_CHROMA_VALS)?,
})
}
}
pub fn parse_dht(
payload: &[u8],
dc_tables: &mut [Option<HuffTable>; 4],
ac_tables: &mut [Option<HuffTable>; 4],
) -> Result<()> {
let mut i = 0;
while i < payload.len() {
let tc_th = payload[i];
let class = tc_th >> 4;
let id = (tc_th & 0x0F) as usize;
if id >= 4 {
return Err(Error::invalid("DHT: table id > 3"));
}
if class > 1 {
return Err(Error::invalid("DHT: class > 1"));
}
i += 1;
if i + 16 > payload.len() {
return Err(Error::invalid("DHT: truncated bits"));
}
let mut bits = [0u8; 16];
bits.copy_from_slice(&payload[i..i + 16]);
i += 16;
let total: usize = bits.iter().map(|&b| b as usize).sum();
if i + total > payload.len() {
return Err(Error::invalid("DHT: truncated values"));
}
let values = payload[i..i + total].to_vec();
i += total;
let table = HuffTable::build(&bits, &values)?;
if class == 0 {
dc_tables[id] = Some(table);
} else {
ac_tables[id] = Some(table);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_tables_build() {
DefaultHuffman::build().unwrap();
}
#[test]
fn encode_roundtrips_all_symbols() {
let t = HuffTable::build(&STD_DC_LUMA_BITS, &STD_DC_LUMA_VALS).unwrap();
for &sym in &STD_DC_LUMA_VALS {
let c = t.encode[sym as usize];
assert!(c.len > 0);
assert!(c.len <= 16);
}
}
}