#[derive(Default)]
pub struct BitWriter {
out: Vec<u8>,
bits: u32,
}
impl BitWriter {
pub fn f(&mut self, n: u32, value: u32) {
for i in (0..n).rev() {
let bit = (value >> i) & 1;
if self.bits % 8 == 0 {
self.out.push(0);
}
if bit == 1 {
if let Some(last) = self.out.last_mut() {
*last |= 0x80 >> (self.bits % 8);
}
}
self.bits += 1;
}
}
pub fn flag(&mut self, value: bool) {
self.f(1, u32::from(value));
}
pub fn trailing_bits(&mut self) {
self.f(1, 1);
while self.bits % 8 != 0 {
self.f(1, 0);
}
}
pub fn byte_alignment(&mut self) {
while self.bits % 8 != 0 {
self.f(1, 0);
}
}
#[must_use]
pub fn finish(self) -> Vec<u8> {
self.out
}
}
pub fn leb128(mut value: usize, out: &mut Vec<u8>) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
if value == 0 {
out.push(byte);
return;
}
out.push(byte | 0x80);
}
}
pub fn obu(kind: u8, payload: &[u8]) -> Vec<u8> {
let mut out = vec![(kind << 3) | 0b10];
leb128(payload.len(), &mut out);
out.extend_from_slice(payload);
out
}
pub const OBU_SEQUENCE_HEADER: u8 = 1;
pub const OBU_FRAME: u8 = 6;
#[derive(Debug, Clone, Copy)]
pub struct SequenceParams {
pub width: u32,
pub height: u32,
pub mono_chrome: bool,
pub cicp: (u8, u8, u8),
pub full_range: bool,
}
impl SequenceParams {
#[must_use]
pub fn level(&self) -> u8 {
const LEVELS: [(u8, u64, u32, u32); 5] = [
(0, 147_456, 2048, 1152),
(4, 665_856, 4096, 2176),
(8, 2_359_296, 4096, 2176),
(12, 8_912_896, 8192, 4352),
(16, 35_651_584, 16384, 8704),
];
let area = u64::from(self.width) * u64::from(self.height);
LEVELS
.iter()
.find(|&&(_, size, w, h)| area <= size && self.width <= w && self.height <= h)
.map_or(31, |&(idx, ..)| idx)
}
}
fn bits_for(value: u32) -> u32 {
32 - value.max(1).leading_zeros()
}
#[must_use]
pub fn sequence_header(p: &SequenceParams) -> Vec<u8> {
let mut w = BitWriter::default();
w.f(3, 0); w.flag(true); w.flag(true); w.f(5, u32::from(p.level()));
let (wb, hb) = (bits_for(p.width - 1), bits_for(p.height - 1));
w.f(4, wb - 1);
w.f(4, hb - 1);
w.f(wb, p.width - 1);
w.f(hb, p.height - 1);
w.flag(false); w.flag(false); w.flag(true); w.flag(false); w.flag(true); w.flag(false); w.flag(false); w.flag(p.mono_chrome);
w.flag(true); w.f(8, u32::from(p.cicp.0));
w.f(8, u32::from(p.cicp.1));
w.f(8, u32::from(p.cicp.2));
w.flag(p.full_range);
if !p.mono_chrome {
w.f(2, 0); w.flag(false); }
w.flag(false); w.trailing_bits();
w.finish()
}
#[derive(Debug, Clone, Copy)]
pub struct CdefStrength {
pub y_pri: u8,
pub y_sec: u8,
pub uv_pri: u8,
pub uv_sec: u8,
}
#[derive(Debug, Clone)]
pub struct FrameParams {
pub base_q_idx: u8,
pub loop_filter: [u8; 4],
pub sharpness: u8,
pub cdef: (u8, Vec<CdefStrength>),
pub tx_mode_select: bool,
pub tile_log2: (u32, u32),
}
#[must_use]
pub fn tile_limits(width: u32, height: u32) -> (u32, u32, u32, u32, u32, u32) {
let tile_log2 = |blk: u32, target: u32| {
let mut k = 0;
while (blk << k) < target {
k += 1;
}
k
};
let mi_cols = 2 * width.div_ceil(8);
let mi_rows = 2 * height.div_ceil(8);
let sb_cols = mi_cols.div_ceil(16);
let sb_rows = mi_rows.div_ceil(16);
let max_tile_width_sb = 4096 >> 6;
let max_tile_area_sb = (4096 * 2304) >> 12;
let min_log2_tile_cols = tile_log2(max_tile_width_sb, sb_cols);
let max_log2_tile_cols = tile_log2(1, sb_cols.min(64));
let max_log2_tile_rows = tile_log2(1, sb_rows.min(64));
let min_log2_tiles = min_log2_tile_cols.max(tile_log2(max_tile_area_sb, sb_rows * sb_cols));
(
min_log2_tile_cols,
max_log2_tile_cols,
min_log2_tiles,
max_log2_tile_rows,
sb_cols,
sb_rows,
)
}
#[must_use]
pub fn frame_header(seq: &SequenceParams, p: &FrameParams) -> Vec<u8> {
let mut w = BitWriter::default();
w.flag(false); w.flag(false); w.flag(false); let (min_cols, max_cols, min_tiles, max_rows, _, _) = tile_limits(seq.width, seq.height);
w.flag(true); let cols = p.tile_log2.0.clamp(min_cols, max_cols);
for _ in min_cols..cols {
w.flag(true);
}
if cols < max_cols {
w.flag(false);
}
let min_rows = min_tiles.saturating_sub(cols);
let rows = p.tile_log2.1.clamp(min_rows, max_rows.max(min_rows));
for _ in min_rows..rows {
w.flag(true);
}
if rows < max_rows {
w.flag(false);
}
if cols + rows > 0 {
w.f(cols + rows, 0); w.f(2, 3); }
w.f(8, u32::from(p.base_q_idx));
w.flag(false); if !seq.mono_chrome {
w.flag(false); w.flag(false); }
w.flag(false); w.flag(false); w.flag(false); w.f(6, u32::from(p.loop_filter[0]));
w.f(6, u32::from(p.loop_filter[1]));
if !seq.mono_chrome && (p.loop_filter[0] != 0 || p.loop_filter[1] != 0) {
w.f(6, u32::from(p.loop_filter[2]));
w.f(6, u32::from(p.loop_filter[3]));
}
w.f(3, u32::from(p.sharpness));
w.flag(false); let (damping, strengths) = &p.cdef;
w.f(2, u32::from(*damping));
let bits = strengths.len().max(1).trailing_zeros();
w.f(2, bits);
for s in strengths.iter().take(1 << bits) {
w.f(4, u32::from(s.y_pri));
w.f(2, u32::from(s.y_sec));
if !seq.mono_chrome {
w.f(4, u32::from(s.uv_pri));
w.f(2, u32::from(s.uv_sec));
}
}
w.flag(p.tx_mode_select);
w.flag(false);
w.byte_alignment();
w.finish()
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
reason = "tests operate on known-good values"
)]
mod tests {
use super::super::super::bits::BitReader;
use super::super::super::frame::FrameHeader;
use super::super::super::seq::SequenceHeader;
use super::*;
fn seq(width: u32, height: u32, mono: bool) -> SequenceParams {
SequenceParams {
width,
height,
mono_chrome: mono,
cicp: (1, 13, 6),
full_range: true,
}
}
fn frame() -> FrameParams {
FrameParams {
base_q_idx: 120,
loop_filter: [10, 12, 5, 6],
sharpness: 2,
cdef: (
2,
vec![
CdefStrength {
y_pri: 3,
y_sec: 1,
uv_pri: 2,
uv_sec: 0,
},
CdefStrength {
y_pri: 7,
y_sec: 2,
uv_pri: 4,
uv_sec: 1,
},
],
),
tx_mode_select: true,
tile_log2: (0, 0),
}
}
#[test]
fn headers_parse_back_to_what_was_written() {
for &(w, h, mono) in &[
(1, 1, false),
(97, 61, false),
(640, 480, true),
(5000, 3000, false),
] {
let sp = seq(w, h, mono);
let bytes = sequence_header(&sp);
let parsed = SequenceHeader::parse(&mut BitReader::new(&bytes)).unwrap();
assert_eq!((parsed.max_frame_width, parsed.max_frame_height), (w, h));
assert_eq!(parsed.color.mono_chrome, mono);
assert_eq!(parsed.color.matrix_coefficients, 6);
assert!(
parsed.color.color_range && parsed.enable_cdef && !parsed.use_128x128_superblock
);
let fp = frame();
let bytes = frame_header(&sp, &fp);
let mut r = BitReader::new(&bytes);
let fh = FrameHeader::parse(&mut r, &parsed, 0, 0).unwrap();
r.byte_alignment().unwrap();
assert_eq!(r.byte_position(), bytes.len(), "{w}x{h}: header length");
assert_eq!(fh.quantization.base_q_idx, 120);
assert_eq!(fh.loop_filter.sharpness, 2);
assert_eq!(fh.loop_filter.level[0], 10);
if !mono {
assert_eq!(fh.loop_filter.level, [10, 12, 5, 6]);
}
assert_eq!(fh.cdef.bits, 1);
assert_eq!(fh.cdef.y_pri_strength, vec![3, 7]);
assert_eq!((fh.frame_width, fh.frame_height), (w, h));
assert_eq!(fh.tile_info.cols, if w > 4096 { 2 } else { 1 }, "{w}x{h}");
}
}
#[test]
fn levels_follow_the_picture_size() {
assert_eq!(seq(640, 480, false).level(), 4);
assert_eq!(seq(1920, 1080, false).level(), 8);
assert_eq!(seq(4000, 2000, false).level(), 12);
assert_eq!(seq(8000, 4000, false).level(), 16);
assert_eq!(seq(20000, 100, false).level(), 31);
}
}