#![forbid(unsafe_code)]
#![allow(dead_code)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::unused_self)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::trivially_copy_pass_by_ref)]
#![allow(clippy::match_same_arms)]
#![allow(clippy::manual_div_ceil)]
#![allow(clippy::missing_errors_doc)]
use super::frame_header::FrameSize;
use super::sequence::SequenceHeader;
use crate::error::{CodecError, CodecResult};
use oximedia_io::BitReader;
pub const MAX_TILE_COLS: usize = 64;
pub const MAX_TILE_ROWS: usize = 64;
pub const MAX_TILE_COUNT: usize = MAX_TILE_COLS * MAX_TILE_ROWS;
pub const MAX_TILE_AREA_SB: usize = 4096;
pub const MIN_TILE_WIDTH_SB: usize = 1;
pub const MAX_TILE_WIDTH_SB: usize = 64;
pub const MIN_TILE_HEIGHT_SB: usize = 1;
pub const MAX_TILE_HEIGHT_SB: usize = 64;
pub const TILE_SIZE_BYTES_MINUS_1_BITS: u8 = 2;
#[derive(Clone, Debug, Default)]
pub struct TileInfo {
pub tile_cols: u32,
pub tile_rows: u32,
pub tile_col_starts: Vec<u32>,
pub tile_row_starts: Vec<u32>,
pub context_update_tile_id: u32,
pub tile_size_bytes: u8,
pub uniform_tile_spacing: bool,
pub tile_cols_log2: u8,
pub tile_rows_log2: u8,
pub min_tile_cols_log2: u8,
pub max_tile_cols_log2: u8,
pub min_tile_rows_log2: u8,
pub max_tile_rows_log2: u8,
pub sb_cols: u32,
pub sb_rows: u32,
pub sb_size: u32,
}
impl TileInfo {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
pub fn parse(
reader: &mut BitReader<'_>,
_seq: &SequenceHeader,
frame_size: &FrameSize,
) -> CodecResult<Self> {
let mut tile_info = Self::new();
tile_info.sb_size = 64;
tile_info.sb_cols = frame_size.sb_cols(tile_info.sb_size);
tile_info.sb_rows = frame_size.sb_rows(tile_info.sb_size);
let max_tile_width_sb = MAX_TILE_WIDTH_SB.min(tile_info.sb_cols as usize);
let max_tile_area_sb = MAX_TILE_AREA_SB;
tile_info.min_tile_cols_log2 = Self::tile_log2(max_tile_width_sb as u32, tile_info.sb_cols);
tile_info.max_tile_cols_log2 =
Self::tile_log2(1, tile_info.sb_cols.min(MAX_TILE_COLS as u32));
tile_info.max_tile_rows_log2 =
Self::tile_log2(1, tile_info.sb_rows.min(MAX_TILE_ROWS as u32));
let min_log2_tiles = Self::tile_log2(
max_tile_area_sb as u32,
tile_info.sb_cols * tile_info.sb_rows,
);
tile_info.min_tile_rows_log2 = min_log2_tiles.saturating_sub(tile_info.max_tile_cols_log2);
tile_info.uniform_tile_spacing = reader.read_bit().map_err(CodecError::Core)? != 0;
if tile_info.uniform_tile_spacing {
tile_info.tile_cols_log2 = tile_info.min_tile_cols_log2;
while tile_info.tile_cols_log2 < tile_info.max_tile_cols_log2 {
let increment = reader.read_bit().map_err(CodecError::Core)?;
if increment != 0 {
tile_info.tile_cols_log2 += 1;
} else {
break;
}
}
let tile_width_sb = (tile_info.sb_cols + (1 << tile_info.tile_cols_log2) - 1)
>> tile_info.tile_cols_log2;
let mut start_sb = 0u32;
tile_info.tile_col_starts.clear();
while start_sb < tile_info.sb_cols {
tile_info.tile_col_starts.push(start_sb);
start_sb += tile_width_sb;
}
tile_info.tile_col_starts.push(tile_info.sb_cols);
tile_info.tile_cols = (tile_info.tile_col_starts.len() - 1) as u32;
tile_info.tile_rows_log2 = tile_info.min_tile_rows_log2;
while tile_info.tile_rows_log2 < tile_info.max_tile_rows_log2 {
let increment = reader.read_bit().map_err(CodecError::Core)?;
if increment != 0 {
tile_info.tile_rows_log2 += 1;
} else {
break;
}
}
let tile_height_sb = (tile_info.sb_rows + (1 << tile_info.tile_rows_log2) - 1)
>> tile_info.tile_rows_log2;
let mut start_sb = 0u32;
tile_info.tile_row_starts.clear();
while start_sb < tile_info.sb_rows {
tile_info.tile_row_starts.push(start_sb);
start_sb += tile_height_sb;
}
tile_info.tile_row_starts.push(tile_info.sb_rows);
tile_info.tile_rows = (tile_info.tile_row_starts.len() - 1) as u32;
} else {
let mut widest_tile_sb = 0u32;
let mut start_sb = 0u32;
tile_info.tile_col_starts.clear();
while start_sb < tile_info.sb_cols {
tile_info.tile_col_starts.push(start_sb);
let max_width = tile_info.sb_cols - start_sb;
let width_in_sbs_minus_1 = Self::ns(reader, max_width)?;
let size_sb = width_in_sbs_minus_1 + 1;
widest_tile_sb = widest_tile_sb.max(size_sb);
start_sb += size_sb;
}
tile_info.tile_col_starts.push(tile_info.sb_cols);
tile_info.tile_cols = (tile_info.tile_col_starts.len() - 1) as u32;
tile_info.tile_cols_log2 = Self::tile_log2(1, tile_info.tile_cols);
let mut start_sb = 0u32;
tile_info.tile_row_starts.clear();
while start_sb < tile_info.sb_rows {
tile_info.tile_row_starts.push(start_sb);
let max_height = tile_info.sb_rows - start_sb;
let height_in_sbs_minus_1 = Self::ns(reader, max_height)?;
let size_sb = height_in_sbs_minus_1 + 1;
start_sb += size_sb;
}
tile_info.tile_row_starts.push(tile_info.sb_rows);
tile_info.tile_rows = (tile_info.tile_row_starts.len() - 1) as u32;
tile_info.tile_rows_log2 = Self::tile_log2(1, tile_info.tile_rows);
}
if tile_info.tile_cols_log2 > 0 || tile_info.tile_rows_log2 > 0 {
let tile_bits = tile_info.tile_cols_log2 + tile_info.tile_rows_log2;
tile_info.context_update_tile_id =
reader.read_bits(tile_bits).map_err(CodecError::Core)? as u32;
tile_info.tile_size_bytes = reader
.read_bits(TILE_SIZE_BYTES_MINUS_1_BITS)
.map_err(CodecError::Core)? as u8
+ 1;
} else {
tile_info.context_update_tile_id = 0;
tile_info.tile_size_bytes = 1;
}
Ok(tile_info)
}
#[must_use]
fn tile_log2(blk_size: u32, target: u32) -> u8 {
let mut k = 0u8;
while (blk_size << k) < target {
k += 1;
}
k
}
#[allow(clippy::cast_possible_truncation)]
fn ns(reader: &mut BitReader<'_>, n: u32) -> CodecResult<u32> {
if n <= 1 {
return Ok(0);
}
let w = 32 - (n - 1).leading_zeros();
let m = (1u32 << w) - n;
let v = reader.read_bits(w as u8 - 1).map_err(CodecError::Core)? as u32;
if v < m {
Ok(v)
} else {
let extra_bit = u32::from(reader.read_bit().map_err(CodecError::Core)?);
Ok((v << 1) - m + extra_bit)
}
}
#[must_use]
pub const fn tile_count(&self) -> u32 {
self.tile_cols * self.tile_rows
}
#[must_use]
pub fn tile_size_sb(&self, tile_col: u32, tile_row: u32) -> (u32, u32) {
let col_start = self
.tile_col_starts
.get(tile_col as usize)
.copied()
.unwrap_or(0);
let col_end = self
.tile_col_starts
.get(tile_col as usize + 1)
.copied()
.unwrap_or(col_start);
let row_start = self
.tile_row_starts
.get(tile_row as usize)
.copied()
.unwrap_or(0);
let row_end = self
.tile_row_starts
.get(tile_row as usize + 1)
.copied()
.unwrap_or(row_start);
(col_end - col_start, row_end - row_start)
}
#[must_use]
pub fn tile_size_pixels(&self, tile_col: u32, tile_row: u32) -> (u32, u32) {
let (width_sb, height_sb) = self.tile_size_sb(tile_col, tile_row);
(width_sb * self.sb_size, height_sb * self.sb_size)
}
#[must_use]
pub const fn tile_index(&self, tile_col: u32, tile_row: u32) -> u32 {
tile_row * self.tile_cols + tile_col
}
#[must_use]
pub const fn tile_position(&self, tile_idx: u32) -> (u32, u32) {
(tile_idx % self.tile_cols, tile_idx / self.tile_cols)
}
#[must_use]
pub fn tile_col_start_sb(&self, tile_col: u32) -> u32 {
self.tile_col_starts
.get(tile_col as usize)
.copied()
.unwrap_or(0)
}
#[must_use]
pub fn tile_row_start_sb(&self, tile_row: u32) -> u32 {
self.tile_row_starts
.get(tile_row as usize)
.copied()
.unwrap_or(0)
}
#[must_use]
pub fn tile_start_pixels(&self, tile_col: u32, tile_row: u32) -> (u32, u32) {
(
self.tile_col_start_sb(tile_col) * self.sb_size,
self.tile_row_start_sb(tile_row) * self.sb_size,
)
}
#[must_use]
pub const fn is_single_tile(&self) -> bool {
self.tile_cols == 1 && self.tile_rows == 1
}
#[must_use]
pub const fn is_left_edge(&self, tile_col: u32) -> bool {
tile_col == 0
}
#[must_use]
pub fn is_right_edge(&self, tile_col: u32) -> bool {
tile_col == self.tile_cols - 1
}
#[must_use]
pub const fn is_top_edge(&self, tile_row: u32) -> bool {
tile_row == 0
}
#[must_use]
pub fn is_bottom_edge(&self, tile_row: u32) -> bool {
tile_row == self.tile_rows - 1
}
}
pub type TileConfig = TileInfo;
#[derive(Clone, Debug)]
pub struct TileData {
pub tile_col: u32,
pub tile_row: u32,
pub offset: usize,
pub size: usize,
pub tile_idx: u32,
}
impl TileData {
#[must_use]
pub fn new(tile_col: u32, tile_row: u32, offset: usize, size: usize, tile_cols: u32) -> Self {
Self {
tile_col,
tile_row,
offset,
size,
tile_idx: tile_row * tile_cols + tile_col,
}
}
#[must_use]
pub const fn is_valid(&self) -> bool {
self.size > 0
}
}
#[derive(Clone, Debug)]
pub struct TileGroup {
pub tile_start: u32,
pub tile_end: u32,
pub tiles: Vec<TileData>,
pub num_tiles: u32,
}
impl TileGroup {
#[must_use]
pub fn new(tile_start: u32, tile_end: u32) -> Self {
Self {
tile_start,
tile_end,
tiles: Vec::new(),
num_tiles: tile_end - tile_start + 1,
}
}
#[must_use]
pub fn tile_count(&self) -> u32 {
self.tile_end - self.tile_start + 1
}
pub fn add_tile(&mut self, tile: TileData) {
self.tiles.push(tile);
}
#[must_use]
pub fn get_tile(&self, idx: usize) -> Option<&TileData> {
self.tiles.get(idx)
}
#[must_use]
pub const fn contains_tile(&self, tile_idx: u32) -> bool {
tile_idx >= self.tile_start && tile_idx <= self.tile_end
}
#[must_use]
pub const fn is_single_tile(&self) -> bool {
self.tile_start == self.tile_end
}
}
#[derive(Clone, Debug)]
pub struct TileGroupObu {
pub tile_info: TileInfo,
pub groups: Vec<TileGroup>,
}
impl TileGroupObu {
#[must_use]
pub fn new(tile_info: TileInfo) -> Self {
Self {
tile_info,
groups: Vec::new(),
}
}
#[allow(clippy::cast_possible_truncation)]
pub fn parse(&mut self, data: &[u8]) -> CodecResult<()> {
let mut reader = BitReader::new(data);
let num_tiles = self.tile_info.tile_count();
let (tile_start, tile_end) = if num_tiles > 1 {
let tile_bits = self.tile_info.tile_cols_log2 + self.tile_info.tile_rows_log2;
let tile_start = reader.read_bits(tile_bits).map_err(CodecError::Core)? as u32;
let tile_end = reader.read_bits(tile_bits).map_err(CodecError::Core)? as u32;
(tile_start, tile_end)
} else {
(0, 0)
};
let mut group = TileGroup::new(tile_start, tile_end);
reader.byte_align();
let header_bytes = reader.bits_read().div_ceil(8);
let mut offset = header_bytes;
for tile_idx in tile_start..=tile_end {
let tile_size = if tile_idx < tile_end {
let size_bytes = self.tile_info.tile_size_bytes as usize;
let mut size = 0u32;
for i in 0..size_bytes {
if offset + i >= data.len() {
return Err(CodecError::InvalidBitstream(
"Tile data truncated".to_string(),
));
}
size |= u32::from(data[offset + i]) << (8 * i);
}
offset += size_bytes;
(size + 1) as usize
} else {
data.len() - offset
};
let (tile_col, tile_row) = self.tile_info.tile_position(tile_idx);
let tile_data = TileData::new(
tile_col,
tile_row,
offset,
tile_size,
self.tile_info.tile_cols,
);
group.add_tile(tile_data);
offset += tile_size;
}
self.groups.push(group);
Ok(())
}
#[must_use]
pub fn total_tiles(&self) -> usize {
self.groups.iter().map(|g| g.tiles.len()).sum()
}
#[must_use]
pub fn get_tile(&self, tile_idx: u32) -> Option<&TileData> {
for group in &self.groups {
if group.contains_tile(tile_idx) {
let local_idx = (tile_idx - group.tile_start) as usize;
return group.get_tile(local_idx);
}
}
None
}
#[must_use]
pub fn is_complete(&self) -> bool {
let total_expected = self.tile_info.tile_count() as usize;
self.total_tiles() == total_expected
}
}
#[derive(Clone, Debug, Default)]
pub struct TileDecoderState {
pub tile_col: u32,
pub tile_row: u32,
pub sb_col: u32,
pub sb_row: u32,
pub tile_width_sb: u32,
pub tile_height_sb: u32,
pub completed: bool,
}
impl TileDecoderState {
#[must_use]
pub fn new(tile_info: &TileInfo, tile_col: u32, tile_row: u32) -> Self {
let (width, height) = tile_info.tile_size_sb(tile_col, tile_row);
Self {
tile_col,
tile_row,
sb_col: 0,
sb_row: 0,
tile_width_sb: width,
tile_height_sb: height,
completed: false,
}
}
pub fn advance(&mut self) {
self.sb_col += 1;
if self.sb_col >= self.tile_width_sb {
self.sb_col = 0;
self.sb_row += 1;
if self.sb_row >= self.tile_height_sb {
self.completed = true;
}
}
}
#[must_use]
pub const fn is_complete(&self) -> bool {
self.completed
}
#[must_use]
pub const fn position(&self) -> (u32, u32) {
(self.sb_col, self.sb_row)
}
#[must_use]
pub const fn total_superblocks(&self) -> u32 {
self.tile_width_sb * self.tile_height_sb
}
#[must_use]
pub fn decoded_superblocks(&self) -> u32 {
self.sb_row * self.tile_width_sb + self.sb_col
}
}
#[derive(Clone, Debug)]
pub struct TileDecoder {
pub tile_info: TileInfo,
pub tile_states: Vec<TileDecoderState>,
}
impl TileDecoder {
#[must_use]
pub fn new(tile_info: TileInfo) -> Self {
let mut tile_states = Vec::with_capacity(tile_info.tile_count() as usize);
for row in 0..tile_info.tile_rows {
for col in 0..tile_info.tile_cols {
tile_states.push(TileDecoderState::new(&tile_info, col, row));
}
}
Self {
tile_info,
tile_states,
}
}
#[must_use]
pub fn get_state(&self, idx: usize) -> Option<&TileDecoderState> {
self.tile_states.get(idx)
}
pub fn get_state_mut(&mut self, idx: usize) -> Option<&mut TileDecoderState> {
self.tile_states.get_mut(idx)
}
#[must_use]
pub fn all_complete(&self) -> bool {
self.tile_states.iter().all(TileDecoderState::is_complete)
}
#[must_use]
pub fn complete_count(&self) -> usize {
self.tile_states.iter().filter(|s| s.is_complete()).count()
}
pub fn reset(&mut self) {
for state in &mut self.tile_states {
state.sb_col = 0;
state.sb_row = 0;
state.completed = false;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_tile_info() -> TileInfo {
TileInfo {
tile_cols: 2,
tile_rows: 2,
tile_col_starts: vec![0, 15, 30],
tile_row_starts: vec![0, 8, 17],
context_update_tile_id: 0,
tile_size_bytes: 4,
uniform_tile_spacing: true,
tile_cols_log2: 1,
tile_rows_log2: 1,
min_tile_cols_log2: 0,
max_tile_cols_log2: 2,
min_tile_rows_log2: 0,
max_tile_rows_log2: 2,
sb_cols: 30,
sb_rows: 17,
sb_size: 64,
}
}
#[test]
fn test_tile_info_default() {
let tile_info = TileInfo::default();
assert_eq!(tile_info.tile_cols, 0);
assert_eq!(tile_info.tile_rows, 0);
}
#[test]
fn test_tile_count() {
let tile_info = create_test_tile_info();
assert_eq!(tile_info.tile_count(), 4);
}
#[test]
fn test_tile_size_sb() {
let tile_info = create_test_tile_info();
assert_eq!(tile_info.tile_size_sb(0, 0), (15, 8));
assert_eq!(tile_info.tile_size_sb(1, 0), (15, 8));
assert_eq!(tile_info.tile_size_sb(0, 1), (15, 9));
assert_eq!(tile_info.tile_size_sb(1, 1), (15, 9));
}
#[test]
fn test_tile_size_pixels() {
let tile_info = create_test_tile_info();
let (width, height) = tile_info.tile_size_pixels(0, 0);
assert_eq!(width, 15 * 64);
assert_eq!(height, 8 * 64);
}
#[test]
fn test_tile_index() {
let tile_info = create_test_tile_info();
assert_eq!(tile_info.tile_index(0, 0), 0);
assert_eq!(tile_info.tile_index(1, 0), 1);
assert_eq!(tile_info.tile_index(0, 1), 2);
assert_eq!(tile_info.tile_index(1, 1), 3);
}
#[test]
fn test_tile_position() {
let tile_info = create_test_tile_info();
assert_eq!(tile_info.tile_position(0), (0, 0));
assert_eq!(tile_info.tile_position(1), (1, 0));
assert_eq!(tile_info.tile_position(2), (0, 1));
assert_eq!(tile_info.tile_position(3), (1, 1));
}
#[test]
fn test_tile_start_sb() {
let tile_info = create_test_tile_info();
assert_eq!(tile_info.tile_col_start_sb(0), 0);
assert_eq!(tile_info.tile_col_start_sb(1), 15);
assert_eq!(tile_info.tile_row_start_sb(0), 0);
assert_eq!(tile_info.tile_row_start_sb(1), 8);
}
#[test]
fn test_tile_start_pixels() {
let tile_info = create_test_tile_info();
assert_eq!(tile_info.tile_start_pixels(0, 0), (0, 0));
assert_eq!(tile_info.tile_start_pixels(1, 0), (15 * 64, 0));
assert_eq!(tile_info.tile_start_pixels(0, 1), (0, 8 * 64));
}
#[test]
fn test_is_single_tile() {
let mut tile_info = TileInfo::default();
tile_info.tile_cols = 1;
tile_info.tile_rows = 1;
assert!(tile_info.is_single_tile());
tile_info.tile_cols = 2;
assert!(!tile_info.is_single_tile());
}
#[test]
fn test_tile_edges() {
let tile_info = create_test_tile_info();
assert!(tile_info.is_left_edge(0));
assert!(!tile_info.is_left_edge(1));
assert!(!tile_info.is_right_edge(0));
assert!(tile_info.is_right_edge(1));
assert!(tile_info.is_top_edge(0));
assert!(!tile_info.is_top_edge(1));
assert!(!tile_info.is_bottom_edge(0));
assert!(tile_info.is_bottom_edge(1));
}
#[test]
fn test_tile_log2() {
assert_eq!(TileInfo::tile_log2(1, 1), 0);
assert_eq!(TileInfo::tile_log2(1, 2), 1);
assert_eq!(TileInfo::tile_log2(1, 4), 2);
assert_eq!(TileInfo::tile_log2(2, 4), 1);
}
#[test]
fn test_tile_data() {
let tile_data = TileData::new(1, 2, 100, 500, 4);
assert_eq!(tile_data.tile_col, 1);
assert_eq!(tile_data.tile_row, 2);
assert_eq!(tile_data.offset, 100);
assert_eq!(tile_data.size, 500);
assert_eq!(tile_data.tile_idx, 2 * 4 + 1);
assert!(tile_data.is_valid());
let empty_tile = TileData::new(0, 0, 0, 0, 1);
assert!(!empty_tile.is_valid());
}
#[test]
fn test_tile_group() {
let mut group = TileGroup::new(0, 3);
assert_eq!(group.tile_count(), 4);
assert!(!group.is_single_tile());
assert!(group.contains_tile(0));
assert!(group.contains_tile(3));
assert!(!group.contains_tile(4));
group.add_tile(TileData::new(0, 0, 0, 100, 2));
assert_eq!(group.tiles.len(), 1);
assert!(group.get_tile(0).is_some());
}
#[test]
fn test_tile_group_single() {
let group = TileGroup::new(5, 5);
assert_eq!(group.tile_count(), 1);
assert!(group.is_single_tile());
}
#[test]
fn test_tile_group_obu() {
let tile_info = create_test_tile_info();
let obu = TileGroupObu::new(tile_info);
assert_eq!(obu.total_tiles(), 0);
assert!(!obu.is_complete());
}
#[test]
fn test_tile_decoder_state() {
let tile_info = create_test_tile_info();
let mut state = TileDecoderState::new(&tile_info, 0, 0);
assert_eq!(state.tile_col, 0);
assert_eq!(state.tile_row, 0);
assert_eq!(state.position(), (0, 0));
assert!(!state.is_complete());
assert_eq!(state.tile_width_sb, 15);
assert_eq!(state.tile_height_sb, 8);
assert_eq!(state.total_superblocks(), 120);
state.advance();
assert_eq!(state.position(), (1, 0));
assert_eq!(state.decoded_superblocks(), 1);
}
#[test]
fn test_tile_decoder_state_wrap() {
let tile_info = TileInfo {
tile_cols: 1,
tile_rows: 1,
tile_col_starts: vec![0, 2],
tile_row_starts: vec![0, 2],
sb_cols: 2,
sb_rows: 2,
sb_size: 64,
..Default::default()
};
let mut state = TileDecoderState::new(&tile_info, 0, 0);
assert_eq!(state.tile_width_sb, 2);
assert_eq!(state.tile_height_sb, 2);
state.advance();
assert_eq!(state.position(), (1, 0));
state.advance();
assert_eq!(state.position(), (0, 1));
state.advance();
assert_eq!(state.position(), (1, 1));
state.advance();
assert!(state.is_complete());
}
#[test]
fn test_tile_decoder() {
let tile_info = create_test_tile_info();
let decoder = TileDecoder::new(tile_info);
assert_eq!(decoder.tile_states.len(), 4);
assert!(!decoder.all_complete());
assert_eq!(decoder.complete_count(), 0);
assert!(decoder.get_state(0).is_some());
assert!(decoder.get_state(3).is_some());
assert!(decoder.get_state(4).is_none());
}
#[test]
fn test_tile_decoder_reset() {
let tile_info = create_test_tile_info();
let mut decoder = TileDecoder::new(tile_info);
if let Some(state) = decoder.get_state_mut(0) {
state.advance();
state.advance();
}
decoder.reset();
for state in &decoder.tile_states {
assert_eq!(state.position(), (0, 0));
assert!(!state.is_complete());
}
}
#[test]
fn test_constants() {
assert_eq!(MAX_TILE_COLS, 64);
assert_eq!(MAX_TILE_ROWS, 64);
assert_eq!(MAX_TILE_COUNT, 4096);
assert_eq!(MAX_TILE_AREA_SB, 4096);
}
}