use oxideav_core::bits::BitReader;
use oxideav_core::{Error, Result};
#[derive(Clone, Copy, Debug)]
pub struct VlcEntry<T: Copy> {
pub code: u32,
pub bits: u8,
pub value: T,
}
impl<T: Copy> VlcEntry<T> {
pub const fn new(bits: u8, code: u32, value: T) -> Self {
Self { code, bits, value }
}
}
pub fn decode_vlc<T: Copy>(br: &mut BitReader<'_>, table: &[VlcEntry<T>]) -> Result<T> {
let max_bits = table.iter().map(|e| e.bits).max().unwrap_or(0) as u32;
if max_bits == 0 {
return Err(Error::invalid("h261 vlc: empty table"));
}
let remaining = br.bits_remaining() as u32;
let peek_bits = max_bits.min(remaining);
if peek_bits == 0 {
return Err(Error::invalid("h261 vlc: no bits available"));
}
let peeked = br.peek_u32(peek_bits)?;
let peeked_full = peeked << (max_bits - peek_bits);
for e in table {
if (e.bits as u32) > peek_bits {
continue;
}
let shift = max_bits - e.bits as u32;
let prefix = peeked_full >> shift;
if prefix == e.code {
br.consume(e.bits as u32)?;
return Ok(e.value);
}
}
Err(Error::invalid("h261 vlc: no matching codeword"))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MbaSym {
Diff(u8),
Stuffing,
}
#[rustfmt::skip]
pub const MBA_TABLE: &[VlcEntry<MbaSym>] = &[
VlcEntry::new(1, 0b1, MbaSym::Diff(1)),
VlcEntry::new(3, 0b011, MbaSym::Diff(2)),
VlcEntry::new(3, 0b010, MbaSym::Diff(3)),
VlcEntry::new(4, 0b0011, MbaSym::Diff(4)),
VlcEntry::new(4, 0b0010, MbaSym::Diff(5)),
VlcEntry::new(5, 0b0001_1, MbaSym::Diff(6)),
VlcEntry::new(5, 0b0001_0, MbaSym::Diff(7)),
VlcEntry::new(7, 0b0000_111, MbaSym::Diff(8)),
VlcEntry::new(7, 0b0000_110, MbaSym::Diff(9)),
VlcEntry::new(8, 0b0000_1011, MbaSym::Diff(10)),
VlcEntry::new(8, 0b0000_1010, MbaSym::Diff(11)),
VlcEntry::new(8, 0b0000_1001, MbaSym::Diff(12)),
VlcEntry::new(8, 0b0000_1000, MbaSym::Diff(13)),
VlcEntry::new(8, 0b0000_0111, MbaSym::Diff(14)),
VlcEntry::new(8, 0b0000_0110, MbaSym::Diff(15)),
VlcEntry::new(10, 0b0000_0101_11, MbaSym::Diff(16)),
VlcEntry::new(10, 0b0000_0101_10, MbaSym::Diff(17)),
VlcEntry::new(10, 0b0000_0101_01, MbaSym::Diff(18)),
VlcEntry::new(10, 0b0000_0101_00, MbaSym::Diff(19)),
VlcEntry::new(10, 0b0000_0100_11, MbaSym::Diff(20)),
VlcEntry::new(10, 0b0000_0100_10, MbaSym::Diff(21)),
VlcEntry::new(11, 0b0000_0100_011, MbaSym::Diff(22)),
VlcEntry::new(11, 0b0000_0100_010, MbaSym::Diff(23)),
VlcEntry::new(11, 0b0000_0100_001, MbaSym::Diff(24)),
VlcEntry::new(11, 0b0000_0100_000, MbaSym::Diff(25)),
VlcEntry::new(11, 0b0000_0011_111, MbaSym::Diff(26)),
VlcEntry::new(11, 0b0000_0011_110, MbaSym::Diff(27)),
VlcEntry::new(11, 0b0000_0011_101, MbaSym::Diff(28)),
VlcEntry::new(11, 0b0000_0011_100, MbaSym::Diff(29)),
VlcEntry::new(11, 0b0000_0011_011, MbaSym::Diff(30)),
VlcEntry::new(11, 0b0000_0011_010, MbaSym::Diff(31)),
VlcEntry::new(11, 0b0000_0011_001, MbaSym::Diff(32)),
VlcEntry::new(11, 0b0000_0011_000, MbaSym::Diff(33)),
VlcEntry::new(11, 0b0000_0001_111, MbaSym::Stuffing),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MtypeInfo {
pub prediction: Prediction,
pub mquant: bool,
pub mvd: bool,
pub cbp: bool,
pub tcoeff: bool,
pub filter: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Prediction {
Intra,
Inter,
InterMc,
InterMcFil,
}
macro_rules! mt {
($pred:ident, $mq:literal, $mvd:literal, $cbp:literal, $tc:literal, $fil:literal) => {
MtypeInfo {
prediction: Prediction::$pred,
mquant: $mq,
mvd: $mvd,
cbp: $cbp,
tcoeff: $tc,
filter: $fil,
}
};
}
#[rustfmt::skip]
pub const MTYPE_TABLE: &[VlcEntry<MtypeInfo>] = &[
VlcEntry::new(4, 0b0001, mt!(Intra, false, false, false, true, false)),
VlcEntry::new(7, 0b0000_001, mt!(Intra, true, false, false, true, false)),
VlcEntry::new(1, 0b1, mt!(Inter, false, false, true, true, false)),
VlcEntry::new(5, 0b0000_1, mt!(Inter, true, false, true, true, false)),
VlcEntry::new(9, 0b0000_0000_1, mt!(InterMc, false, true, false, false, false)),
VlcEntry::new(8, 0b0000_0001, mt!(InterMc, false, true, true, true, false)),
VlcEntry::new(10, 0b0000_0000_01,mt!(InterMc, true, true, true, true, false)),
VlcEntry::new(3, 0b001, mt!(InterMcFil, false, true, false, false, true)),
VlcEntry::new(2, 0b01, mt!(InterMcFil, false, true, true, true, true)),
VlcEntry::new(6, 0b0000_01, mt!(InterMcFil, true, true, true, true, true)),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MvdSym {
pub a: i8,
pub b: i8,
}
#[rustfmt::skip]
pub const MVD_TABLE: &[VlcEntry<MvdSym>] = &[
VlcEntry::new(11, 0b0000_0011_001, MvdSym { a: -16, b: 16 }),
VlcEntry::new(11, 0b0000_0011_011, MvdSym { a: -15, b: 17 }),
VlcEntry::new(11, 0b0000_0011_101, MvdSym { a: -14, b: 18 }),
VlcEntry::new(11, 0b0000_0011_111, MvdSym { a: -13, b: 19 }),
VlcEntry::new(11, 0b0000_0100_001, MvdSym { a: -12, b: 20 }),
VlcEntry::new(11, 0b0000_0100_011, MvdSym { a: -11, b: 21 }),
VlcEntry::new(10, 0b0000_0100_11, MvdSym { a: -10, b: 22 }),
VlcEntry::new(10, 0b0000_0101_01, MvdSym { a: -9, b: 23 }),
VlcEntry::new(10, 0b0000_0101_11, MvdSym { a: -8, b: 24 }),
VlcEntry::new(8, 0b0000_0111, MvdSym { a: -7, b: 25 }),
VlcEntry::new(8, 0b0000_1001, MvdSym { a: -6, b: 26 }),
VlcEntry::new(8, 0b0000_1011, MvdSym { a: -5, b: 27 }),
VlcEntry::new(7, 0b0000_111, MvdSym { a: -4, b: 28 }),
VlcEntry::new(5, 0b0001_1, MvdSym { a: -3, b: 29 }),
VlcEntry::new(4, 0b0011, MvdSym { a: -2, b: 30 }),
VlcEntry::new(3, 0b011, MvdSym { a: -1, b: 31 }),
VlcEntry::new(1, 0b1, MvdSym { a: 0, b: 0 }),
VlcEntry::new(3, 0b010, MvdSym { a: 1, b: -31 }),
VlcEntry::new(4, 0b0010, MvdSym { a: 2, b: -30 }),
VlcEntry::new(5, 0b0001_0, MvdSym { a: 3, b: -29 }),
VlcEntry::new(7, 0b0000_110, MvdSym { a: 4, b: -28 }),
VlcEntry::new(8, 0b0000_1010, MvdSym { a: 5, b: -27 }),
VlcEntry::new(8, 0b0000_1000, MvdSym { a: 6, b: -26 }),
VlcEntry::new(8, 0b0000_0110, MvdSym { a: 7, b: -25 }),
VlcEntry::new(10, 0b0000_0101_10, MvdSym { a: 8, b: -24 }),
VlcEntry::new(10, 0b0000_0101_00, MvdSym { a: 9, b: -23 }),
VlcEntry::new(10, 0b0000_0100_10, MvdSym { a: 10, b: -22 }),
VlcEntry::new(11, 0b0000_0100_010, MvdSym { a: 11, b: -21 }),
VlcEntry::new(11, 0b0000_0100_000, MvdSym { a: 12, b: -20 }),
VlcEntry::new(11, 0b0000_0011_110, MvdSym { a: 13, b: -19 }),
VlcEntry::new(11, 0b0000_0011_100, MvdSym { a: 14, b: -18 }),
VlcEntry::new(11, 0b0000_0011_010, MvdSym { a: 15, b: -17 }),
];
#[rustfmt::skip]
pub const CBP_TABLE: &[VlcEntry<u8>] = &[
VlcEntry::new(3, 0b111, 60),
VlcEntry::new(4, 0b1101, 4),
VlcEntry::new(4, 0b1100, 8),
VlcEntry::new(4, 0b1011, 16),
VlcEntry::new(4, 0b1010, 32),
VlcEntry::new(5, 0b1001_1, 12),
VlcEntry::new(5, 0b1001_0, 48),
VlcEntry::new(5, 0b1000_1, 20),
VlcEntry::new(5, 0b1000_0, 40),
VlcEntry::new(5, 0b0111_1, 28),
VlcEntry::new(5, 0b0111_0, 44),
VlcEntry::new(5, 0b0110_1, 52),
VlcEntry::new(5, 0b0110_0, 56),
VlcEntry::new(5, 0b0101_1, 1),
VlcEntry::new(5, 0b0101_0, 61),
VlcEntry::new(5, 0b0100_1, 2),
VlcEntry::new(5, 0b0100_0, 62),
VlcEntry::new(6, 0b0011_11, 24),
VlcEntry::new(6, 0b0011_10, 36),
VlcEntry::new(6, 0b0011_01, 3),
VlcEntry::new(6, 0b0011_00, 63),
VlcEntry::new(7, 0b0010_111, 5),
VlcEntry::new(7, 0b0010_110, 9),
VlcEntry::new(7, 0b0010_101, 17),
VlcEntry::new(7, 0b0010_100, 33),
VlcEntry::new(7, 0b0010_011, 6),
VlcEntry::new(7, 0b0010_010, 10),
VlcEntry::new(7, 0b0010_001, 18),
VlcEntry::new(7, 0b0010_000, 34),
VlcEntry::new(8, 0b0001_1111, 7),
VlcEntry::new(8, 0b0001_1110, 11),
VlcEntry::new(8, 0b0001_1101, 19),
VlcEntry::new(8, 0b0001_1100, 35),
VlcEntry::new(8, 0b0001_1011, 13),
VlcEntry::new(8, 0b0001_1010, 49),
VlcEntry::new(8, 0b0001_1001, 21),
VlcEntry::new(8, 0b0001_1000, 41),
VlcEntry::new(8, 0b0001_0111, 14),
VlcEntry::new(8, 0b0001_0110, 50),
VlcEntry::new(8, 0b0001_0101, 22),
VlcEntry::new(8, 0b0001_0100, 42),
VlcEntry::new(8, 0b0001_0011, 15),
VlcEntry::new(8, 0b0001_0010, 51),
VlcEntry::new(8, 0b0001_0001, 23),
VlcEntry::new(8, 0b0001_0000, 43),
VlcEntry::new(8, 0b0000_1111, 25),
VlcEntry::new(8, 0b0000_1110, 37),
VlcEntry::new(8, 0b0000_1101, 26),
VlcEntry::new(8, 0b0000_1100, 38),
VlcEntry::new(8, 0b0000_1011, 29),
VlcEntry::new(8, 0b0000_1010, 45),
VlcEntry::new(8, 0b0000_1001, 53),
VlcEntry::new(8, 0b0000_1000, 57),
VlcEntry::new(8, 0b0000_0111, 30),
VlcEntry::new(8, 0b0000_0110, 46),
VlcEntry::new(8, 0b0000_0101, 54),
VlcEntry::new(8, 0b0000_0100, 58),
VlcEntry::new(9, 0b0000_0011_1, 31),
VlcEntry::new(9, 0b0000_0011_0, 47),
VlcEntry::new(9, 0b0000_0010_1, 55),
VlcEntry::new(9, 0b0000_0010_0, 59),
VlcEntry::new(9, 0b0000_0001_1, 27),
VlcEntry::new(9, 0b0000_0001_0, 39),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TcoeffSym {
Eob,
RunLevel { run: u8, level_abs: u8 },
Escape,
}
#[rustfmt::skip]
const TCOEFF_ENTRIES: &[VlcEntry<(u8, u8)>] = &[
VlcEntry::new(4, 0b0100, (0, 2)),
VlcEntry::new(4, 0b0101, (2, 1)),
VlcEntry::new(3, 0b011, (1, 1)),
VlcEntry::new(5, 0b0010_1, (0, 3)),
VlcEntry::new(5, 0b0011_1, (3, 1)),
VlcEntry::new(5, 0b0011_0, (4, 1)),
VlcEntry::new(6, 0b0001_11, (5, 1)),
VlcEntry::new(6, 0b0001_01, (6, 1)),
VlcEntry::new(6, 0b0001_00, (7, 1)),
VlcEntry::new(6, 0b0001_10, (1, 2)),
VlcEntry::new(7, 0b0000_110, (0, 4)),
VlcEntry::new(7, 0b0000_111, (8, 1)),
VlcEntry::new(7, 0b0000_101, (9, 1)),
VlcEntry::new(7, 0b0000_100, (2, 2)),
VlcEntry::new(8, 0b0010_0110, (0, 5)),
VlcEntry::new(8, 0b0010_0001, (0, 6)),
VlcEntry::new(8, 0b0010_0101, (1, 3)),
VlcEntry::new(8, 0b0010_0100, (3, 2)),
VlcEntry::new(8, 0b0010_0111, (10, 1)),
VlcEntry::new(8, 0b0010_0011, (11, 1)),
VlcEntry::new(8, 0b0010_0010, (12, 1)),
VlcEntry::new(8, 0b0010_0000, (13, 1)),
VlcEntry::new(10, 0b0000_0010_10, (0, 7)),
VlcEntry::new(10, 0b0000_0011_11, (4, 2)),
VlcEntry::new(10, 0b0000_0010_11, (2, 3)),
VlcEntry::new(10, 0b0000_0010_01, (5, 2)),
VlcEntry::new(10, 0b0000_0011_00, (1, 4)),
VlcEntry::new(10, 0b0000_0011_10, (14, 1)),
VlcEntry::new(10, 0b0000_0011_01, (15, 1)),
VlcEntry::new(10, 0b0000_0010_00, (16, 1)),
VlcEntry::new(12, 0b0000_0001_1101, (0, 8)),
VlcEntry::new(12, 0b0000_0001_1000, (0, 9)),
VlcEntry::new(12, 0b0000_0001_0011, (0, 10)),
VlcEntry::new(12, 0b0000_0001_0000, (0, 11)),
VlcEntry::new(12, 0b0000_0001_1011, (1, 5)),
VlcEntry::new(12, 0b0000_0001_0100, (2, 4)),
VlcEntry::new(12, 0b0000_0001_1100, (3, 3)),
VlcEntry::new(12, 0b0000_0001_0010, (4, 3)),
VlcEntry::new(12, 0b0000_0001_1110, (6, 2)),
VlcEntry::new(12, 0b0000_0001_0101, (7, 2)),
VlcEntry::new(12, 0b0000_0001_0001, (8, 2)),
VlcEntry::new(12, 0b0000_0001_1111, (17, 1)),
VlcEntry::new(12, 0b0000_0001_1010, (18, 1)),
VlcEntry::new(12, 0b0000_0001_1001, (19, 1)),
VlcEntry::new(12, 0b0000_0001_0111, (20, 1)),
VlcEntry::new(12, 0b0000_0001_0110, (21, 1)),
VlcEntry::new(13, 0b0_0000_0000_1101_0, (0, 12)),
VlcEntry::new(13, 0b0_0000_0000_1100_1, (0, 13)),
VlcEntry::new(13, 0b0_0000_0000_1100_0, (0, 14)),
VlcEntry::new(13, 0b0_0000_0000_1011_1, (0, 15)),
VlcEntry::new(13, 0b0_0000_0000_1011_0, (1, 6)),
VlcEntry::new(13, 0b0_0000_0000_1010_1, (1, 7)),
VlcEntry::new(13, 0b0_0000_0000_1010_0, (2, 5)),
VlcEntry::new(13, 0b0_0000_0000_1001_1, (3, 4)),
VlcEntry::new(13, 0b0_0000_0000_1001_0, (5, 3)),
VlcEntry::new(13, 0b0_0000_0000_1000_1, (9, 2)),
VlcEntry::new(13, 0b0_0000_0000_1000_0, (10, 2)),
VlcEntry::new(13, 0b0_0000_0000_1111_1, (22, 1)),
VlcEntry::new(13, 0b0_0000_0000_1111_0, (23, 1)),
VlcEntry::new(13, 0b0_0000_0000_1110_1, (24, 1)),
VlcEntry::new(13, 0b0_0000_0000_1110_0, (25, 1)),
VlcEntry::new(13, 0b0_0000_0000_1101_1, (26, 1)),
];
pub fn decode_tcoeff(br: &mut BitReader<'_>, is_first: bool) -> Result<TcoeffSym> {
let avail = br.bits_remaining().min(20) as u32;
if avail == 0 {
return Err(Error::invalid("h261 tcoeff: no bits"));
}
let peek = br.peek_u32(avail)?;
let b0 = (peek >> (avail - 1)) & 1;
if b0 == 1 {
if is_first {
br.consume(1)?;
return Ok(TcoeffSym::RunLevel {
run: 0,
level_abs: 1,
});
}
if avail < 2 {
return Err(Error::invalid("h261 tcoeff: truncated `1?` prefix"));
}
let two = (peek >> (avail - 2)) & 0b11;
if two == 0b10 {
br.consume(2)?;
return Ok(TcoeffSym::Eob);
} else {
br.consume(2)?;
return Ok(TcoeffSym::RunLevel {
run: 0,
level_abs: 1,
});
}
}
if avail >= 6 {
let six = (peek >> (avail - 6)) & 0x3F;
if six == 0b0000_01 {
br.consume(6)?;
return Ok(TcoeffSym::Escape);
}
}
let sym = decode_vlc(br, TCOEFF_ENTRIES)?;
Ok(TcoeffSym::RunLevel {
run: sym.0,
level_abs: sym.1,
})
}
#[rustfmt::skip]
pub const ZIGZAG: [usize; 64] = [
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34,
27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36,
29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63,
];
pub fn encode_mba_diff(diff: u8) -> (u8, u32) {
debug_assert!((1..=33).contains(&diff), "MBA diff out of range: {diff}");
for e in MBA_TABLE {
if let MbaSym::Diff(d) = e.value {
if d == diff {
return (e.bits, e.code);
}
}
}
unreachable!("MBA_TABLE missing entry for diff={diff}");
}
pub const MBA_STUFFING: (u8, u32) = (11, 0b0000_0001_111);
pub fn encode_cbp(cbp: u8) -> (u8, u32) {
debug_assert!((1..=63).contains(&cbp), "CBP out of range: {cbp}");
for e in CBP_TABLE {
if e.value == cbp {
return (e.bits, e.code);
}
}
unreachable!("CBP_TABLE missing entry for cbp={cbp}");
}
pub const MTYPE_INTRA: (u8, u32) = (4, 0b0001);
pub const MTYPE_INTRA_MQUANT: (u8, u32) = (7, 0b0000_001);
pub const MTYPE_INTER: (u8, u32) = (1, 0b1);
pub const MTYPE_INTER_MQUANT: (u8, u32) = (5, 0b0000_1);
pub const MTYPE_INTER_MC_CBP: (u8, u32) = (8, 0b0000_0001);
pub const MTYPE_INTER_MC_CBP_MQUANT: (u8, u32) = (10, 0b0000_0000_01);
pub const MTYPE_INTER_MC_ONLY: (u8, u32) = (9, 0b0000_0000_1);
pub const MTYPE_INTER_MC_FIL_ONLY: (u8, u32) = (3, 0b001);
pub const MTYPE_INTER_MC_FIL_CBP: (u8, u32) = (2, 0b01);
pub const MTYPE_INTER_MC_FIL_CBP_MQUANT: (u8, u32) = (6, 0b0000_01);
pub fn encode_mvd(d: i32) -> (u8, u32) {
debug_assert!(
(-30..=30).contains(&d),
"MVD differential out of range: {d}"
);
for e in MVD_TABLE {
if e.value.a as i32 == d || e.value.b as i32 == d {
return (e.bits, e.code);
}
}
unreachable!("MVD_TABLE missing entry for d={d}");
}
#[rustfmt::skip]
pub const TCOEFF_ENCODE: &[(u8, u32, u8, u8)] = &[
( 4, 0b0100, 0, 2),
( 5, 0b0010_1, 0, 3),
( 7, 0b0000_110, 0, 4),
( 8, 0b0010_0110, 0, 5),
( 8, 0b0010_0001, 0, 6),
(10, 0b0000_0010_10, 0, 7),
(12, 0b0000_0001_1101, 0, 8),
(12, 0b0000_0001_1000, 0, 9),
(12, 0b0000_0001_0011, 0, 10),
(12, 0b0000_0001_0000, 0, 11),
(13, 0b0_0000_0000_1101_0, 0, 12),
(13, 0b0_0000_0000_1100_1, 0, 13),
(13, 0b0_0000_0000_1100_0, 0, 14),
(13, 0b0_0000_0000_1011_1, 0, 15),
( 3, 0b011, 1, 1),
( 6, 0b0001_10, 1, 2),
( 8, 0b0010_0101, 1, 3),
(10, 0b0000_0011_00, 1, 4),
(12, 0b0000_0001_1011, 1, 5),
(13, 0b0_0000_0000_1011_0, 1, 6),
(13, 0b0_0000_0000_1010_1, 1, 7),
( 4, 0b0101, 2, 1),
( 7, 0b0000_100, 2, 2),
(10, 0b0000_0010_11, 2, 3),
(12, 0b0000_0001_0100, 2, 4),
(13, 0b0_0000_0000_1010_0, 2, 5),
( 5, 0b0011_1, 3, 1),
( 8, 0b0010_0100, 3, 2),
(12, 0b0000_0001_1100, 3, 3),
(13, 0b0_0000_0000_1001_1, 3, 4),
( 5, 0b0011_0, 4, 1),
(10, 0b0000_0011_11, 4, 2),
(12, 0b0000_0001_0010, 4, 3),
( 6, 0b0001_11, 5, 1),
(10, 0b0000_0010_01, 5, 2),
(13, 0b0_0000_0000_1001_0, 5, 3),
( 6, 0b0001_01, 6, 1),
(12, 0b0000_0001_1110, 6, 2),
( 6, 0b0001_00, 7, 1),
(12, 0b0000_0001_0101, 7, 2),
( 7, 0b0000_111, 8, 1),
(12, 0b0000_0001_0001, 8, 2),
( 7, 0b0000_101, 9, 1),
(13, 0b0_0000_0000_1000_1, 9, 2),
( 8, 0b0010_0111, 10, 1),
(13, 0b0_0000_0000_1000_0, 10, 2),
( 8, 0b0010_0011, 11, 1),
( 8, 0b0010_0010, 12, 1),
( 8, 0b0010_0000, 13, 1),
(10, 0b0000_0011_10, 14, 1),
(10, 0b0000_0011_01, 15, 1),
(10, 0b0000_0010_00, 16, 1),
(12, 0b0000_0001_1111, 17, 1),
(12, 0b0000_0001_1010, 18, 1),
(12, 0b0000_0001_1001, 19, 1),
(12, 0b0000_0001_0111, 20, 1),
(12, 0b0000_0001_0110, 21, 1),
(13, 0b0_0000_0000_1111_1, 22, 1),
(13, 0b0_0000_0000_1111_0, 23, 1),
(13, 0b0_0000_0000_1110_1, 24, 1),
(13, 0b0_0000_0000_1110_0, 25, 1),
(13, 0b0_0000_0000_1101_1, 26, 1),
];
pub fn lookup_tcoeff(run: u8, abs_level: u8) -> Option<(u8, u32)> {
for &(bits, code, r, l) in TCOEFF_ENCODE {
if r == run && l == abs_level {
return Some((bits, code));
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mba_diff_1_is_one_bit() {
let data = [0b1000_0000u8];
let mut br = BitReader::new(&data);
assert_eq!(decode_vlc(&mut br, MBA_TABLE).unwrap(), MbaSym::Diff(1));
}
#[test]
fn mba_diff_2_is_011() {
let data = [0b0110_0000u8];
let mut br = BitReader::new(&data);
assert_eq!(decode_vlc(&mut br, MBA_TABLE).unwrap(), MbaSym::Diff(2));
}
#[test]
fn mtype_intra_1bit_vs_inter() {
let data = [0b0001_0000u8];
let mut br = BitReader::new(&data);
let v = decode_vlc(&mut br, MTYPE_TABLE).unwrap();
assert_eq!(v.prediction, Prediction::Intra);
assert!(!v.mquant);
let data = [0b1000_0000u8];
let mut br = BitReader::new(&data);
let v = decode_vlc(&mut br, MTYPE_TABLE).unwrap();
assert_eq!(v.prediction, Prediction::Inter);
assert!(v.cbp);
}
#[test]
fn cbp_basic() {
let data = [0b1110_0000u8];
let mut br = BitReader::new(&data);
assert_eq!(decode_vlc(&mut br, CBP_TABLE).unwrap(), 60);
}
#[test]
fn mvd_zero() {
let data = [0b1000_0000u8];
let mut br = BitReader::new(&data);
let v = decode_vlc(&mut br, MVD_TABLE).unwrap();
assert_eq!(v.a, 0);
}
#[test]
fn tcoeff_first_one() {
let data = [0b1_000_0000u8];
let mut br = BitReader::new(&data);
assert_eq!(
decode_tcoeff(&mut br, true).unwrap(),
TcoeffSym::RunLevel {
run: 0,
level_abs: 1
}
);
}
#[test]
fn tcoeff_eob() {
let data = [0b1000_0000u8];
let mut br = BitReader::new(&data);
assert_eq!(decode_tcoeff(&mut br, false).unwrap(), TcoeffSym::Eob);
}
#[test]
fn tcoeff_subsequent_one() {
let data = [0b1100_0000u8];
let mut br = BitReader::new(&data);
assert_eq!(
decode_tcoeff(&mut br, false).unwrap(),
TcoeffSym::RunLevel {
run: 0,
level_abs: 1
}
);
}
#[test]
fn tcoeff_0_2() {
let data = [0b0100_0000u8];
let mut br = BitReader::new(&data);
assert_eq!(
decode_tcoeff(&mut br, false).unwrap(),
TcoeffSym::RunLevel {
run: 0,
level_abs: 2
}
);
}
#[test]
fn tcoeff_escape() {
let data = [0b0000_0100u8];
let mut br = BitReader::new(&data);
assert_eq!(decode_tcoeff(&mut br, false).unwrap(), TcoeffSym::Escape);
}
#[test]
fn tcoeff_truncated_one_bit_does_not_panic() {
let data = [0b0000_0001u8];
let mut br = BitReader::new(&data);
for _ in 0..7 {
br.consume(1).unwrap();
}
let r = decode_tcoeff(&mut br, false);
assert!(
r.is_err(),
"truncated `1?` prefix must return Err, not panic"
);
}
#[test]
fn zigzag_starts_natural() {
assert_eq!(ZIGZAG[0], 0);
assert_eq!(ZIGZAG[1], 1);
assert_eq!(ZIGZAG[2], 8);
}
}