use gamut_bitstream::{BitWriter, write_leb128};
use gamut_core::{Error, Result};
const OBU_SEQUENCE_HEADER: u8 = 1;
pub(crate) const OBU_FRAME: u8 = 6;
pub(crate) const TILE_SIZE_BYTES: usize = 4;
#[derive(Debug, Clone, Copy)]
pub struct Av1StillConfig {
pub seq_profile: u8,
pub seq_level_idx_0: u8,
pub seq_tier_0: u8,
pub high_bitdepth: bool,
pub twelve_bit: bool,
pub monochrome: bool,
pub chroma_subsampling_x: u8,
pub chroma_subsampling_y: u8,
pub chroma_sample_position: u8,
pub color_primaries: u16,
pub transfer_characteristics: u16,
pub matrix_coefficients: u16,
pub full_range: bool,
}
const LEVELS: [(u8, u32, u32, u64); 7] = [
(0, 2048, 1152, 147_456), (1, 2816, 1584, 278_784), (4, 4352, 2448, 665_856), (5, 5504, 3096, 1_065_024), (8, 6144, 3456, 2_359_296), (12, 8192, 4352, 8_912_896), (16, 16384, 8704, 35_651_584), ];
pub fn pick_level(width: u32, height: u32) -> Result<u8> {
let pixels = u64::from(width) * u64::from(height);
for &(idx, max_h, max_v, max_pic) in &LEVELS {
if width <= max_h && height <= max_v && pixels <= max_pic {
return Ok(idx);
}
}
Err(Error::Unsupported(
"image dimensions exceed AV1 level 6.0 limits",
))
}
fn dimension_bits(value: u32) -> u32 {
(32 - value.saturating_sub(1).leading_zeros()).max(1)
}
fn tile_log2(blk_size: u32, target: u32) -> u32 {
let mut k = 0;
while (blk_size << k) < target {
k += 1;
}
k
}
pub(crate) fn write_obu(out: &mut Vec<u8>, obu_type: u8, payload: &[u8]) {
out.push((obu_type << 3) | 0b10);
write_leb128(out, payload.len() as u64);
out.extend_from_slice(payload);
}
pub(crate) fn sequence_header_payload(
cfg: &Av1StillConfig,
width: u32,
height: u32,
lossy: bool,
superres: bool,
) -> Vec<u8> {
let mut w = BitWriter::new();
w.put_bits(u32::from(cfg.seq_profile), 3); w.put_bit(1); w.put_bit(1); w.put_bits(u32::from(cfg.seq_level_idx_0), 5);
let wbits = dimension_bits(width);
let hbits = dimension_bits(height);
w.put_bits(wbits - 1, 4); w.put_bits(hbits - 1, 4); w.put_bits(width - 1, wbits); w.put_bits(height - 1, hbits); w.put_bit(0); w.put_bit(u8::from(lossy)); w.put_bit(0); w.put_bit(u8::from(superres)); w.put_bit(u8::from(lossy)); w.put_bit(u8::from(lossy));
w.put_bit(0); w.put_bit(1); w.put_bits(u32::from(cfg.color_primaries), 8);
w.put_bits(u32::from(cfg.transfer_characteristics), 8);
w.put_bits(u32::from(cfg.matrix_coefficients), 8);
w.put_bit(0);
w.put_bit(0);
w.put_bit(1);
w.byte_align();
w.into_bytes()
}
pub(crate) fn frame_header_payload(
width: u32,
height: u32,
mi_cols: u32,
mi_rows: u32,
base_q_idx: u8,
superres_coded_denom: Option<u8>,
) -> Vec<u8> {
let lossless = base_q_idx == 0;
let mut w = BitWriter::new();
w.put_bit(1); w.put_bit(u8::from(!lossless)); if !lossless {
w.put_bit(0); }
if let Some(cd) = superres_coded_denom {
w.put_bit(1); w.put_bits(u32::from(cd), 3); }
w.put_bit(0); if !lossless && superres_coded_denom.is_none() {
w.put_bit(0); }
let _ = (width, height);
let sb_cols = (mi_cols + 15) >> 4;
let sb_rows = (mi_rows + 15) >> 4;
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 tile_cols_log2 = u32::from(sb_cols >= 2);
w.put_bit(1); let mut t = 0;
while t < max_log2_tile_cols {
let inc = t < tile_cols_log2;
w.put_bit(u8::from(inc));
if inc {
t += 1;
} else {
break;
}
}
if max_log2_tile_rows > 0 {
w.put_bit(0); }
if tile_cols_log2 > 0 {
w.put_bits(0, tile_cols_log2);
w.put_bits(TILE_SIZE_BYTES as u32 - 1, 2);
}
w.put_bits(u32::from(base_q_idx), 8); w.put_bit(0); w.put_bit(0); w.put_bit(0); w.put_bit(0);
if lossless {
w.put_bit(0); } else {
w.put_bit(1); for seg in 0..8usize {
for feat in 0..8usize {
if let (0, Some(delta)) = (feat, crate::tile::SEG_ALT_Q[seg]) {
w.put_bit(1); w.put_bits((delta & 0x1FF) as u32, 9);
continue;
}
w.put_bit(0); }
}
}
if !lossless {
w.put_bit(1); w.put_bits(0, 2); w.put_bit(1); w.put_bits(0, 2); w.put_bit(0); let lf = u32::from(crate::filter::deblock_level(base_q_idx));
w.put_bits(lf, 6); w.put_bits(lf, 6); if lf != 0 {
w.put_bits(lf, 6); w.put_bits(lf, 6); }
w.put_bits(0, 3); w.put_bit(0); let (y_pri, y_sec, uv_pri, uv_sec) = crate::filter::cdef_strengths(base_q_idx);
let sec_code = |s: i32| -> u32 { if s == 4 { 3 } else { s as u32 } };
w.put_bits(0, 2); w.put_bits(0, 2); w.put_bits(y_pri as u32, 4); w.put_bits(sec_code(y_sec), 2); w.put_bits(uv_pri as u32, 4); w.put_bits(sec_code(uv_sec), 2); w.put_bits(2, 2); w.put_bits(0, 2); w.put_bits(0, 2); w.put_bit(1); w.put_bit(1); w.put_bit(1); }
w.put_bit(1);
w.byte_align(); if tile_cols_log2 > 0 {
w.put_bit(0); w.byte_align();
}
w.into_bytes()
}
pub(crate) fn assemble_temporal_unit(seq_payload: &[u8], frame_payload: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
write_obu(&mut out, OBU_SEQUENCE_HEADER, seq_payload);
write_obu(&mut out, OBU_FRAME, frame_payload);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn level_selection() {
assert_eq!(pick_level(16, 16).unwrap(), 0);
assert_eq!(pick_level(1920, 1080).unwrap(), 8);
assert!(pick_level(100_000, 100_000).is_err());
}
#[test]
fn dimension_bits_min_one() {
assert_eq!(dimension_bits(1), 1); assert_eq!(dimension_bits(16), 4); assert_eq!(dimension_bits(17), 5); assert_eq!(dimension_bits(256), 8); assert_eq!(dimension_bits(257), 9); }
#[test]
fn obu_framing_has_size_field() {
let mut out = Vec::new();
write_obu(&mut out, OBU_SEQUENCE_HEADER, &[0xaa, 0xbb]);
assert_eq!(out, vec![0x0a, 0x02, 0xaa, 0xbb]);
}
}