use crate::encoder::bitwriter::BitWriter;
use crate::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TilePayload {
pub bytes: Vec<u8>,
}
impl TilePayload {
pub fn new(bytes: Vec<u8>) -> Self {
Self { bytes }
}
}
#[derive(Debug, Clone)]
pub struct TileGroupObu {
pub num_tiles: u32,
pub tile_cols_log2: u32,
pub tile_rows_log2: u32,
pub tile_size_bytes: u32,
pub tg_start: u32,
pub tg_end: u32,
pub start_and_end_present: bool,
pub tiles: Vec<TilePayload>,
}
impl TileGroupObu {
pub fn whole_frame(
num_tiles: u32,
tile_cols_log2: u32,
tile_rows_log2: u32,
tile_size_bytes: u32,
tiles: Vec<TilePayload>,
) -> Self {
debug_assert!(num_tiles >= 1, "§5.9.15 NumTiles >= 1");
debug_assert!((1..=4).contains(&tile_size_bytes));
debug_assert!(
tiles.len() == num_tiles as usize,
"whole_frame expects num_tiles payloads ({} != {})",
tiles.len(),
num_tiles
);
Self {
num_tiles,
tile_cols_log2,
tile_rows_log2,
tile_size_bytes,
tg_start: 0,
tg_end: num_tiles - 1,
start_and_end_present: false,
tiles,
}
}
}
#[derive(Debug, Default)]
pub struct TileGroupObuWriter;
impl TileGroupObuWriter {
pub fn write(&self, obu: &TileGroupObu) -> Result<Vec<u8>, Error> {
debug_assert!(obu.tg_end >= obu.tg_start, "§6.10.1 tg_end >= tg_start");
debug_assert!(
obu.tg_end < obu.num_tiles,
"§6.10.1 tg_end < num_tiles ({} >= {})",
obu.tg_end,
obu.num_tiles
);
let expected_len = (obu.tg_end - obu.tg_start + 1) as usize;
debug_assert!(
obu.tiles.len() == expected_len,
"tiles len {} does not match tg range [{}, {}] (expected {})",
obu.tiles.len(),
obu.tg_start,
obu.tg_end,
expected_len
);
debug_assert!(
(1..=4).contains(&obu.tile_size_bytes),
"§6.8.14 TileSizeBytes is 1..=4"
);
let mut bw = BitWriter::new();
if obu.num_tiles > 1 {
bw.write_bits(1, u64::from(obu.start_and_end_present));
if obu.start_and_end_present {
let tile_bits = obu.tile_cols_log2 + obu.tile_rows_log2;
debug_assert!(
tile_bits == 0 || obu.tg_start < (1u32 << tile_bits),
"tg_start {} >= 1 << tileBits {}",
obu.tg_start,
tile_bits
);
debug_assert!(
tile_bits == 0 || obu.tg_end < (1u32 << tile_bits),
"tg_end {} >= 1 << tileBits {}",
obu.tg_end,
tile_bits
);
bw.write_bits(tile_bits, u64::from(obu.tg_start));
bw.write_bits(tile_bits, u64::from(obu.tg_end));
}
}
bw.byte_align();
debug_assert!(bw.is_byte_aligned());
let mut out = bw.finish();
let last_idx = obu.tiles.len() - 1;
for (i, tile) in obu.tiles.iter().enumerate() {
if i != last_idx {
let size = tile.bytes.len();
debug_assert!(size >= 1, "non-last tile cannot be empty (§5.11.1)");
let size_minus_1 = (size - 1) as u64;
let cap_bits: u32 = obu.tile_size_bytes * 8;
debug_assert!(
cap_bits == 64 || size_minus_1 < (1u64 << cap_bits),
"tile_size_minus_1 {} does not fit in {} bytes",
size_minus_1,
obu.tile_size_bytes
);
for b in 0..obu.tile_size_bytes {
out.push(((size_minus_1 >> (b * 8)) & 0xff) as u8);
}
}
out.extend_from_slice(&tile.bytes);
}
Ok(out)
}
}
pub fn write_tile_group_obu(obu: &TileGroupObu) -> Result<Vec<u8>, Error> {
TileGroupObuWriter.write(obu)
}
pub(crate) fn split_whole_frame_body(
body: &[u8],
num_tiles: u32,
tile_cols_log2: u32,
tile_rows_log2: u32,
tile_size_bytes: u32,
groups: u32,
) -> Result<Vec<Vec<u8>>, Error> {
let groups = groups.clamp(1, num_tiles);
if groups <= 1 || num_tiles <= 1 {
return Ok(vec![body.to_vec()]);
}
let parsed = parse_tile_group_obu_body(
body,
num_tiles,
tile_cols_log2,
tile_rows_log2,
tile_size_bytes,
)?;
debug_assert_eq!(parsed.tg_start, 0, "whole-frame body expected");
debug_assert_eq!(parsed.tg_end + 1, num_tiles, "whole-frame body expected");
let base = num_tiles / groups;
let rem = num_tiles % groups;
let mut out: Vec<Vec<u8>> = Vec::with_capacity(groups as usize);
let mut start = 0u32;
for g in 0..groups {
let count = base + u32::from(g < rem);
let end = start + count - 1;
let obu = TileGroupObu {
num_tiles,
tile_cols_log2,
tile_rows_log2,
tile_size_bytes,
tg_start: start,
tg_end: end,
start_and_end_present: true,
tiles: parsed.tiles[start as usize..=end as usize].to_vec(),
};
out.push(write_tile_group_obu(&obu)?);
start = end + 1;
}
debug_assert_eq!(start, num_tiles);
Ok(out)
}
pub(crate) fn single_group_with_spans(
body: &[u8],
num_tiles: u32,
tile_cols_log2: u32,
tile_rows_log2: u32,
tile_size_bytes: u32,
) -> Result<Vec<u8>, Error> {
if num_tiles <= 1 {
return Ok(body.to_vec());
}
let parsed = parse_tile_group_obu_body(
body,
num_tiles,
tile_cols_log2,
tile_rows_log2,
tile_size_bytes,
)?;
debug_assert_eq!(parsed.tg_start, 0, "whole-frame body expected");
debug_assert_eq!(parsed.tg_end + 1, num_tiles, "whole-frame body expected");
write_tile_group_obu(&TileGroupObu {
num_tiles,
tile_cols_log2,
tile_rows_log2,
tile_size_bytes,
tg_start: 0,
tg_end: num_tiles - 1,
start_and_end_present: true,
tiles: parsed.tiles,
})
}
pub fn parse_tile_group_obu_body(
body: &[u8],
num_tiles: u32,
tile_cols_log2: u32,
tile_rows_log2: u32,
tile_size_bytes: u32,
) -> Result<ParsedTileGroup, Error> {
debug_assert!(num_tiles >= 1);
debug_assert!((1..=4).contains(&tile_size_bytes));
let mut br = crate::bitreader::BitReader::new(body);
let (tg_start, tg_end, flag) = if num_tiles > 1 {
let flag = br.f(1)? as u32;
if flag != 0 {
let tile_bits = tile_cols_log2 + tile_rows_log2;
let tg_start = br.f(tile_bits)? as u32;
let tg_end = br.f(tile_bits)? as u32;
if tg_start > tg_end || tg_end >= num_tiles {
return Err(Error::UnexpectedEnd);
}
(tg_start, tg_end, true)
} else {
(0, num_tiles - 1, false)
}
} else {
(0, 0, false)
};
while br.position() & 7 != 0 {
let _ = br.f(1)?;
}
let header_bytes = br.position() / 8;
let mut cursor = header_bytes;
let last_idx = (tg_end - tg_start) as usize;
let mut tiles: Vec<TilePayload> = Vec::with_capacity(last_idx + 1);
let mut remaining = body.len() - header_bytes;
for i in 0..=last_idx {
let tile_size = if i == last_idx {
remaining
} else {
if cursor + tile_size_bytes as usize > body.len() {
return Err(Error::UnexpectedEnd);
}
let mut tile_size_minus_1: u64 = 0;
for b in 0..tile_size_bytes {
tile_size_minus_1 |= u64::from(body[cursor + b as usize]) << (b * 8);
}
cursor += tile_size_bytes as usize;
remaining -= tile_size_bytes as usize;
let s = (tile_size_minus_1 + 1) as usize;
if s > remaining {
return Err(Error::UnexpectedEnd);
}
s
};
if cursor + tile_size > body.len() {
return Err(Error::UnexpectedEnd);
}
tiles.push(TilePayload::new(body[cursor..cursor + tile_size].to_vec()));
cursor += tile_size;
remaining -= tile_size;
}
Ok(ParsedTileGroup {
tg_start,
tg_end,
tile_start_and_end_present_flag: flag,
tiles,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedTileGroup {
pub tg_start: u32,
pub tg_end: u32,
pub tile_start_and_end_present_flag: bool,
pub tiles: Vec<TilePayload>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::obu::{write_obu_with_size, ObuHeader};
use crate::encoder::symbol_writer::SymbolWriter;
use crate::obu::ObuType;
fn tile_payload_with_bools(bools: &[u32]) -> TilePayload {
let mut w = SymbolWriter::new(true);
for &b in bools {
w.write_bool(b).unwrap();
}
TilePayload::new(w.finish())
}
#[test]
fn single_tile_omits_flag_and_size() {
let payload = tile_payload_with_bools(&[1, 0, 1, 1, 0, 0, 1, 0]);
let obu = TileGroupObu::whole_frame(1, 0, 0, 1, vec![payload.clone()]);
let body = write_tile_group_obu(&obu).unwrap();
assert_eq!(body, payload.bytes);
}
#[test]
fn multi_tile_flag_zero_round_trip() {
let a = tile_payload_with_bools(&[1, 1, 0, 0]);
let b = tile_payload_with_bools(&[0, 1, 0, 1, 1, 0]);
let c = tile_payload_with_bools(&[1, 0]);
let obu = TileGroupObu::whole_frame(
3,
1, 1, 1,
vec![a.clone(), b.clone(), c.clone()],
);
let body = write_tile_group_obu(&obu).unwrap();
assert_eq!(body[0], 0x00);
let mut expected = vec![0x00];
expected.push((a.bytes.len() - 1) as u8);
expected.extend_from_slice(&a.bytes);
expected.push((b.bytes.len() - 1) as u8);
expected.extend_from_slice(&b.bytes);
expected.extend_from_slice(&c.bytes);
assert_eq!(body, expected);
let parsed = parse_tile_group_obu_body(&body, 3, 1, 1, 1).unwrap();
assert_eq!(parsed.tg_start, 0);
assert_eq!(parsed.tg_end, 2);
assert!(!parsed.tile_start_and_end_present_flag);
assert_eq!(parsed.tiles.len(), 3);
assert_eq!(parsed.tiles[0], a);
assert_eq!(parsed.tiles[1], b);
assert_eq!(parsed.tiles[2], c);
}
#[test]
fn multi_tile_flag_one_writes_tg_start_and_end() {
let payloads: Vec<TilePayload> = (0..4)
.map(|i| tile_payload_with_bools(&[i & 1, (i >> 1) & 1, 1, 0]))
.collect();
let obu = TileGroupObu {
num_tiles: 4,
tile_cols_log2: 1,
tile_rows_log2: 1,
tile_size_bytes: 1,
tg_start: 1,
tg_end: 3,
start_and_end_present: true,
tiles: payloads[1..].to_vec(),
};
let body = write_tile_group_obu(&obu).unwrap();
let parsed = parse_tile_group_obu_body(&body, 4, 1, 1, 1).unwrap();
assert_eq!(parsed.tg_start, 1);
assert_eq!(parsed.tg_end, 3);
assert!(parsed.tile_start_and_end_present_flag);
assert_eq!(parsed.tiles.len(), 3);
assert_eq!(parsed.tiles[0].bytes, payloads[1].bytes);
assert_eq!(parsed.tiles[1].bytes, payloads[2].bytes);
assert_eq!(parsed.tiles[2].bytes, payloads[3].bytes);
}
#[test]
fn tile_size_bytes_two_round_trip() {
let a = TilePayload::new(vec![0xAA; 300]);
let b = TilePayload::new(vec![0xBB; 200]);
let obu = TileGroupObu::whole_frame(2, 1, 0, 2, vec![a.clone(), b.clone()]);
let body = write_tile_group_obu(&obu).unwrap();
assert_eq!(body[0], 0x00);
assert_eq!(body[1], 0x2B);
assert_eq!(body[2], 0x01);
assert_eq!(&body[3..303], &a.bytes[..]);
assert_eq!(&body[303..], &b.bytes[..]);
let parsed = parse_tile_group_obu_body(&body, 2, 1, 0, 2).unwrap();
assert_eq!(parsed.tiles.len(), 2);
assert_eq!(parsed.tiles[0], a);
assert_eq!(parsed.tiles[1], b);
}
#[test]
fn tile_size_bytes_four_round_trip() {
let a = TilePayload::new(vec![0x11; 5]);
let b = TilePayload::new(vec![0x22; 9]);
let obu = TileGroupObu::whole_frame(2, 1, 0, 4, vec![a.clone(), b.clone()]);
let body = write_tile_group_obu(&obu).unwrap();
assert_eq!(body[0], 0x00);
assert_eq!(&body[1..5], &[0x04, 0x00, 0x00, 0x00]);
assert_eq!(&body[5..10], &a.bytes[..]);
assert_eq!(&body[10..], &b.bytes[..]);
let parsed = parse_tile_group_obu_body(&body, 2, 1, 0, 4).unwrap();
assert_eq!(parsed.tiles[0], a);
assert_eq!(parsed.tiles[1], b);
}
#[test]
fn tile_group_obu_in_obu_wrapper_round_trip() {
let a = tile_payload_with_bools(&[1, 0, 1, 0]);
let b = tile_payload_with_bools(&[1, 1, 1, 1, 0, 0]);
let obu = TileGroupObu::whole_frame(2, 1, 0, 1, vec![a.clone(), b.clone()]);
let body = write_tile_group_obu(&obu).unwrap();
let mut out = Vec::new();
write_obu_with_size(&mut out, &ObuHeader::new(ObuType::TileGroup), &body);
let (desc, _consumed) = crate::obu::parse_obu(&out).unwrap();
assert_eq!(desc.obu_type, ObuType::TileGroup);
assert_eq!(desc.payload_len, body.len());
assert_eq!(desc.payload, &body[..]);
let parsed = parse_tile_group_obu_body(desc.payload, 2, 1, 0, 1).unwrap();
assert_eq!(parsed.tiles[0], a);
assert_eq!(parsed.tiles[1], b);
}
#[test]
fn payload_pad_bytes_counted_in_tile_size() {
let mut w = SymbolWriter::new(true);
for _ in 0..18 {
w.write_bool(1).unwrap();
}
let bytes_a = w.finish();
let mut w2 = SymbolWriter::new(true);
for _ in 0..7 {
w2.write_bool(0).unwrap();
}
let bytes_b = w2.finish();
let len_a = bytes_a.len();
let len_b = bytes_b.len();
let obu = TileGroupObu::whole_frame(
2,
1,
0,
1,
vec![
TilePayload::new(bytes_a.clone()),
TilePayload::new(bytes_b.clone()),
],
);
let body = write_tile_group_obu(&obu).unwrap();
assert_eq!(body.len(), 1 + 1 + len_a + len_b);
let parsed = parse_tile_group_obu_body(&body, 2, 1, 0, 1).unwrap();
assert_eq!(parsed.tiles[0].bytes, bytes_a);
assert_eq!(parsed.tiles[1].bytes, bytes_b);
}
#[test]
fn whole_frame_constructor_defaults() {
let payloads = vec![TilePayload::new(vec![0x10]), TilePayload::new(vec![0x20])];
let obu = TileGroupObu::whole_frame(2, 1, 0, 1, payloads);
assert_eq!(obu.tg_start, 0);
assert_eq!(obu.tg_end, 1);
assert!(!obu.start_and_end_present);
}
}