#![allow(dead_code)]
use crate::pps::{Pps, SliceGroupMap};
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum FmoError {
#[error("slice_group_change_cycle not supplied but slice_group_map_type is {0}")]
MissingChangeCycle(u32),
#[error("PicSizeInMapUnits is 0 (degenerate stream)")]
EmptyPicture,
#[error("explicit slice_group_id length {got} != PicSizeInMapUnits {expected}")]
ExplicitMapSizeMismatch { got: usize, expected: u32 },
#[error("foreground rectangle corner {0} out of range for PicSizeInMapUnits {1}")]
ForegroundOutOfRange(u32, u32),
#[error("interleaved run_length_minus1 array is empty")]
EmptyRunLengths,
}
pub fn map_unit_to_slice_group_map(
pps: &Pps,
pic_size_in_map_units: u32,
pic_width_in_mbs: u32,
slice_group_change_cycle: u32,
_mbaff_frame_flag: bool,
) -> Result<Vec<u32>, FmoError> {
if pic_size_in_map_units == 0 {
return Err(FmoError::EmptyPicture);
}
let n = pic_size_in_map_units as usize;
let num_slice_groups_minus1 = pps.num_slice_groups_minus1;
if num_slice_groups_minus1 == 0 {
return Ok(vec![0u32; n]);
}
let num_slice_groups = num_slice_groups_minus1 + 1;
let Some(map) = pps.slice_group_map.as_ref() else {
return Ok(vec![0u32; n]);
};
let mut out = vec![0u32; n];
match map {
SliceGroupMap::Interleaved { run_length_minus1 } => {
if run_length_minus1.is_empty() {
return Err(FmoError::EmptyRunLengths);
}
let mut i: usize = 0;
loop {
let mut i_group: u32 = 0;
while i_group <= num_slice_groups_minus1 && i < n {
let rl = run_length_minus1[i_group as usize] as usize;
let mut j: usize = 0;
while j <= rl && i + j < n {
out[i + j] = i_group;
j += 1;
}
i += rl + 1;
i_group += 1;
}
if i >= n {
break;
}
}
}
SliceGroupMap::Dispersed => {
#[allow(clippy::needless_range_loop)] for i in 0..n {
let i_u = i as u32;
let x = i_u % pic_width_in_mbs;
let y = i_u / pic_width_in_mbs;
out[i] = (x + (y * num_slice_groups) / 2) % num_slice_groups;
}
}
SliceGroupMap::Foreground {
top_left,
bottom_right,
} => {
for slot in out.iter_mut().take(n) {
*slot = num_slice_groups_minus1;
}
for i_group in (0..num_slice_groups_minus1).rev() {
let idx = i_group as usize;
let tl = top_left[idx];
let br = bottom_right[idx];
if tl >= pic_size_in_map_units || br >= pic_size_in_map_units {
return Err(FmoError::ForegroundOutOfRange(
tl.max(br),
pic_size_in_map_units,
));
}
let y_top_left = tl / pic_width_in_mbs;
let x_top_left = tl % pic_width_in_mbs;
let y_bottom_right = br / pic_width_in_mbs;
let x_bottom_right = br % pic_width_in_mbs;
let mut y = y_top_left;
while y <= y_bottom_right {
let mut x = x_top_left;
while x <= x_bottom_right {
let idx = (y * pic_width_in_mbs + x) as usize;
if idx < n {
out[idx] = i_group;
}
x += 1;
}
y += 1;
}
}
}
SliceGroupMap::Changing {
slice_group_map_type,
change_direction_flag,
change_rate_minus1,
} => {
let slice_group_change_rate = change_rate_minus1 + 1;
let mapunits_in_slice_group0 = (slice_group_change_rate as u64)
.saturating_mul(slice_group_change_cycle as u64)
.min(pic_size_in_map_units as u64)
as u32;
match *slice_group_map_type {
3 => {
let dir = *change_direction_flag as i32; for slot in out.iter_mut().take(n) {
*slot = 1;
}
let pic_width = pic_width_in_mbs as i32;
let pic_height = (pic_size_in_map_units / pic_width_in_mbs) as i32;
let mut x: i32 = (pic_width - dir) / 2;
let mut y: i32 = (pic_height - dir) / 2;
let mut left_bound: i32 = x;
let mut top_bound: i32 = y;
let mut right_bound: i32 = x;
let mut bottom_bound: i32 = y;
let mut x_dir: i32 = dir - 1;
let mut y_dir: i32 = dir;
let mapunits_in_g0 = mapunits_in_slice_group0 as usize;
let mut k: usize = 0;
while k < mapunits_in_g0 {
let lin = (y * pic_width + x) as usize;
let map_unit_vacant = if lin < n { out[lin] == 1 } else { false };
if map_unit_vacant && lin < n {
out[lin] = 0;
}
if x_dir == -1 && x == left_bound {
left_bound = (left_bound - 1).max(0);
x = left_bound;
x_dir = 0;
y_dir = 2 * dir - 1;
} else if x_dir == 1 && x == right_bound {
right_bound = (right_bound + 1).min(pic_width - 1);
x = right_bound;
x_dir = 0;
y_dir = 1 - 2 * dir;
} else if y_dir == -1 && y == top_bound {
top_bound = (top_bound - 1).max(0);
y = top_bound;
x_dir = 1 - 2 * dir;
y_dir = 0;
} else if y_dir == 1 && y == bottom_bound {
bottom_bound = (bottom_bound + 1).min(pic_height - 1);
y = bottom_bound;
x_dir = 2 * dir - 1;
y_dir = 0;
} else {
x += x_dir;
y += y_dir;
}
k += 1;
}
}
4 => {
let size_of_upper_left = if *change_direction_flag {
pic_size_in_map_units - mapunits_in_slice_group0
} else {
mapunits_in_slice_group0
};
let dir = *change_direction_flag as u32;
for (i, slot) in out.iter_mut().enumerate().take(n) {
*slot = if (i as u32) < size_of_upper_left {
dir
} else {
1 - dir
};
}
}
5 => {
let size_of_upper_left = if *change_direction_flag {
pic_size_in_map_units - mapunits_in_slice_group0
} else {
mapunits_in_slice_group0
};
let dir = *change_direction_flag as u32;
let pic_height = pic_size_in_map_units / pic_width_in_mbs;
let mut k: u32 = 0;
for j in 0..pic_width_in_mbs {
for i in 0..pic_height {
let lin = (i * pic_width_in_mbs + j) as usize;
if lin < n {
out[lin] = if k < size_of_upper_left { dir } else { 1 - dir };
}
k += 1;
}
}
}
other => {
return Err(FmoError::MissingChangeCycle(other));
}
}
}
SliceGroupMap::Explicit {
pic_size_in_map_units_minus1,
slice_group_id,
} => {
let expected = *pic_size_in_map_units_minus1 + 1;
if slice_group_id.len() != expected as usize {
return Err(FmoError::ExplicitMapSizeMismatch {
got: slice_group_id.len(),
expected,
});
}
let take = n.min(slice_group_id.len());
out[..take].copy_from_slice(&slice_group_id[..take]);
}
}
Ok(out)
}
pub fn mb_to_slice_group_map(
map_unit_map: &[u32],
frame_mbs_only_flag: bool,
mbaff_frame_flag: bool,
field_pic_flag: bool,
pic_width_in_mbs: u32,
) -> Vec<u32> {
if frame_mbs_only_flag || field_pic_flag {
return map_unit_map.to_vec();
}
let n_map_units = map_unit_map.len();
let pic_size_in_mbs = n_map_units * 2;
if mbaff_frame_flag {
let mut out = vec![0u32; pic_size_in_mbs];
for (i, slot) in out.iter_mut().enumerate() {
let mu = i / 2;
if mu < n_map_units {
*slot = map_unit_map[mu];
}
}
out
} else {
let w = pic_width_in_mbs as usize;
let two_w = 2 * w;
let mut out = vec![0u32; pic_size_in_mbs];
for (i, slot) in out.iter_mut().enumerate() {
let mu = (i / two_w) * w + (i % w);
if mu < n_map_units {
*slot = map_unit_map[mu];
}
}
out
}
}
pub fn next_mb_address(
current_mb_addr: u32,
mb_to_slice_group_map: &[u32],
slice_group: u32,
pic_size_in_mbs: u32,
) -> u32 {
let mut i = current_mb_addr.saturating_add(1);
let len = mb_to_slice_group_map.len() as u32;
let end = pic_size_in_mbs.min(len);
while i < end && mb_to_slice_group_map[i as usize] != slice_group {
i += 1;
}
i
}
pub fn slice_mb_to_slice_group_map(
sps: &crate::sps::Sps,
pps: &Pps,
slice_header: &crate::slice_header::SliceHeader,
) -> Option<Vec<u32>> {
if pps.num_slice_groups_minus1 == 0 {
return None;
}
let pic_width_in_mbs = sps.pic_width_in_mbs();
let pic_size_in_map_units = pic_width_in_mbs * sps.pic_height_in_map_units();
let mbaff_frame_flag = sps.mb_adaptive_frame_field_flag && !slice_header.field_pic_flag;
let units = map_unit_to_slice_group_map(
pps,
pic_size_in_map_units,
pic_width_in_mbs,
slice_header.slice_group_change_cycle,
mbaff_frame_flag,
)
.ok()?;
Some(mb_to_slice_group_map(
&units,
sps.frame_mbs_only_flag,
mbaff_frame_flag,
slice_header.field_pic_flag,
pic_width_in_mbs,
))
}
#[inline]
pub fn advance_mb_addr(curr: u32, map: Option<&[u32]>) -> u32 {
match map {
None => curr.saturating_add(1),
Some(m) => {
let group = m.get(curr as usize).copied().unwrap_or(0);
next_mb_address(curr, m, group, m.len() as u32)
}
}
}
pub fn mb_pair_flags(curr_mb_addr: u32, mb_field_decoding_flag: bool) -> (bool, bool) {
let mb_is_top = curr_mb_addr % 2 == 0;
let curr_frame = !mb_field_decoding_flag;
(curr_frame, mb_is_top)
}
pub fn mbaff_mb_to_sample_xy(
curr_mb_addr: u32,
pic_width_in_mbs: u32,
mb_field_decoding_flag: bool,
) -> (u32, u32) {
if pic_width_in_mbs == 0 {
return (0, 0);
}
let pair_idx = curr_mb_addr / 2;
let x_o = (pair_idx % pic_width_in_mbs) * 16;
let y_o = (pair_idx / pic_width_in_mbs) * 32;
let bot = curr_mb_addr % 2; if mb_field_decoding_flag {
(x_o, y_o + bot)
} else {
(x_o, y_o + bot * 16)
}
}
#[inline]
pub fn mbaff_y_step(mb_field_decoding_flag: bool) -> u32 {
if mb_field_decoding_flag {
2
} else {
1
}
}
pub fn mbaff_pair_neighbour_addrs(curr_mb_addr: u32, pic_width_in_mbs: u32) -> [Option<u32>; 4] {
if pic_width_in_mbs == 0 {
return [None; 4];
}
let pair_idx = curr_mb_addr / 2;
let pair_col = pair_idx % pic_width_in_mbs;
let pair_row = pair_idx / pic_width_in_mbs;
let a = if pair_col > 0 {
Some(2 * (pair_idx - 1))
} else {
None
};
let b = if pair_row > 0 {
Some(2 * (pair_idx - pic_width_in_mbs))
} else {
None
};
let c = if pair_row > 0 && pair_col + 1 < pic_width_in_mbs {
Some(2 * (pair_idx - pic_width_in_mbs + 1))
} else {
None
};
let d = if pair_row > 0 && pair_col > 0 {
Some(2 * (pair_idx - pic_width_in_mbs - 1))
} else {
None
};
[a, b, c, d]
}
#[allow(clippy::too_many_arguments)]
pub fn mbaff_neigh_location(
xn: i32,
yn: i32,
max_w: i32,
max_h: i32,
curr_mb_frame_flag: bool,
mb_is_top: bool,
curr_mb_addr: u32,
pair: [Option<u32>; 4],
frame_flag_of: impl Fn(u32) -> bool,
) -> Option<(u32, i32, i32)> {
let [mb_a, mb_b, mb_c, mb_d] = pair;
if yn > max_h - 1 {
return None;
}
enum Cell {
TopLeft,
TopCentre,
TopRight,
MidLeft,
MidCentre,
MidRight,
}
let cell = if yn < 0 {
if xn < 0 {
Cell::TopLeft
} else if xn > max_w - 1 {
Cell::TopRight
} else {
Cell::TopCentre
}
} else if xn < 0 {
Cell::MidLeft
} else if xn > max_w - 1 {
Cell::MidRight
} else {
Cell::MidCentre
};
let frame = curr_mb_frame_flag;
let top = mb_is_top;
let (mb_addr_n, y_m): (Option<u32>, i32) = match cell {
Cell::MidCentre => {
(Some(curr_mb_addr), yn)
}
Cell::MidRight => (None, 0),
Cell::TopLeft => {
if frame {
if top {
match mb_d {
Some(d) => (Some(d + 1), yn),
None => (None, 0),
}
} else {
match mb_a {
Some(a) => {
if frame_flag_of(a) {
(Some(a), yn)
} else {
(Some(a + 1), (yn + max_h) >> 1)
}
}
None => (None, 0),
}
}
} else {
if top {
match mb_d {
Some(d) => {
if frame_flag_of(d) {
(Some(d + 1), 2 * yn)
} else {
(Some(d), yn)
}
}
None => (None, 0),
}
} else {
match mb_d {
Some(d) => (Some(d + 1), yn),
None => (None, 0),
}
}
}
}
Cell::MidLeft => {
match mb_a {
None => (None, 0),
Some(a) => {
let a_frame = frame_flag_of(a);
if frame {
if top {
if a_frame {
(Some(a), yn)
} else if yn % 2 == 0 {
(Some(a), yn >> 1)
} else {
(Some(a + 1), yn >> 1)
}
} else {
if a_frame {
(Some(a + 1), yn)
} else if yn % 2 == 0 {
(Some(a), (yn + max_h) >> 1)
} else {
(Some(a + 1), (yn + max_h) >> 1)
}
}
} else {
if top {
if a_frame {
if yn < max_h / 2 {
(Some(a), yn << 1)
} else {
(Some(a + 1), (yn << 1) - max_h)
}
} else {
(Some(a), yn)
}
} else {
if a_frame {
if yn < max_h / 2 {
(Some(a), (yn << 1) + 1)
} else {
(Some(a + 1), (yn << 1) + 1 - max_h)
}
} else {
(Some(a + 1), yn)
}
}
}
}
}
}
Cell::TopCentre => {
if frame {
if top {
match mb_b {
Some(b) => (Some(b + 1), yn),
None => (None, 0),
}
} else {
(Some(curr_mb_addr - 1), yn)
}
} else if top {
match mb_b {
Some(b) => {
if frame_flag_of(b) {
(Some(b + 1), 2 * yn)
} else {
(Some(b), yn)
}
}
None => (None, 0),
}
} else {
match mb_b {
Some(b) => (Some(b + 1), yn),
None => (None, 0),
}
}
}
Cell::TopRight => {
if frame {
if top {
match mb_c {
Some(c) => (Some(c + 1), yn),
None => (None, 0),
}
} else {
(None, 0)
}
} else if top {
match mb_c {
Some(c) => {
if frame_flag_of(c) {
(Some(c + 1), 2 * yn)
} else {
(Some(c), yn)
}
}
None => (None, 0),
}
} else {
match mb_c {
Some(c) => (Some(c + 1), yn),
None => (None, 0),
}
}
}
};
let mb_addr_n = mb_addr_n?;
if mb_addr_n > curr_mb_addr {
return None;
}
let xw = (xn + max_w).rem_euclid(max_w);
let yw = (y_m + max_h).rem_euclid(max_h);
Some((mb_addr_n, xw, yw))
}
pub fn mbaff_neighbour_addrs(
curr_mb_addr: u32,
pic_width_in_mbs: u32,
mb_field_decoding_flag_grid: &[bool],
) -> [Option<u32>; 4] {
let pair_neighbours = mbaff_pair_neighbour_addrs(curr_mb_addr, pic_width_in_mbs);
let mb_is_top = curr_mb_addr % 2 == 0;
let curr_field = mb_field_decoding_flag_grid
.get(curr_mb_addr as usize)
.copied()
.unwrap_or(false);
let in_bounds = |addr: u32| -> Option<u32> {
if (addr as usize) < mb_field_decoding_flag_grid.len() {
Some(addr)
} else {
None
}
};
let pair_a = pair_neighbours[0];
let pair_b = pair_neighbours[1];
let pair_c = pair_neighbours[2];
let pair_d = pair_neighbours[3];
let a = pair_a.and_then(|base| {
let neigh_field = mb_field_decoding_flag_grid
.get(base as usize)
.copied()
.unwrap_or(false);
if neigh_field == curr_field {
let off = if mb_is_top { 0 } else { 1 };
in_bounds(base + off)
} else {
in_bounds(base)
}
});
let b = if mb_is_top {
pair_b.and_then(|base| in_bounds(base + 1))
} else {
if curr_mb_addr > 0 {
in_bounds(curr_mb_addr - 1)
} else {
None
}
};
let c = if mb_is_top {
pair_c.and_then(|base| in_bounds(base + 1))
} else {
None
};
let d = if mb_is_top {
pair_d.and_then(|base| in_bounds(base + 1))
} else {
pair_a
};
[a, b, c, d]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pps::{Pps, SliceGroupMap};
fn make_pps(num_slice_groups_minus1: u32, map: Option<SliceGroupMap>) -> Pps {
Pps {
pic_parameter_set_id: 0,
seq_parameter_set_id: 0,
entropy_coding_mode_flag: false,
bottom_field_pic_order_in_frame_present_flag: false,
num_slice_groups_minus1,
slice_group_map: map,
num_ref_idx_l0_default_active_minus1: 0,
num_ref_idx_l1_default_active_minus1: 0,
weighted_pred_flag: false,
weighted_bipred_idc: 0,
pic_init_qp_minus26: 0,
pic_init_qs_minus26: 0,
chroma_qp_index_offset: 0,
deblocking_filter_control_present_flag: false,
constrained_intra_pred_flag: false,
redundant_pic_cnt_present_flag: false,
extension: None,
}
}
#[test]
fn no_fmo_all_zeros() {
let pps = make_pps(0, None);
let map = map_unit_to_slice_group_map(&pps, 16, 4, 0, false).unwrap();
assert_eq!(map, vec![0u32; 16]);
}
#[test]
fn type0_interleaved_two_groups() {
let pps = make_pps(
1,
Some(SliceGroupMap::Interleaved {
run_length_minus1: vec![2, 4],
}),
);
let map = map_unit_to_slice_group_map(&pps, 16, 4, 0, false).unwrap();
assert_eq!(map, vec![0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1]);
}
#[test]
fn type0_interleaved_runs_of_one_each() {
let pps = make_pps(
1,
Some(SliceGroupMap::Interleaved {
run_length_minus1: vec![0, 0],
}),
);
let map = map_unit_to_slice_group_map(&pps, 8, 4, 0, false).unwrap();
assert_eq!(map, vec![0, 1, 0, 1, 0, 1, 0, 1]);
}
#[test]
fn type1_dispersed_two_groups_4x4() {
let pps = make_pps(1, Some(SliceGroupMap::Dispersed));
let map = map_unit_to_slice_group_map(&pps, 16, 4, 0, false).unwrap();
assert_eq!(map, vec![0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0]);
}
#[test]
fn type1_dispersed_three_groups_3x3() {
let pps = make_pps(2, Some(SliceGroupMap::Dispersed));
let map = map_unit_to_slice_group_map(&pps, 9, 3, 0, false).unwrap();
assert_eq!(map, vec![0, 1, 2, 1, 2, 0, 0, 1, 2]);
}
#[test]
fn type2_foreground_one_rectangle() {
let pps = make_pps(
1,
Some(SliceGroupMap::Foreground {
top_left: vec![5],
bottom_right: vec![10],
}),
);
let map = map_unit_to_slice_group_map(&pps, 16, 4, 0, false).unwrap();
let mut expected = vec![1u32; 16];
expected[5] = 0;
expected[6] = 0;
expected[9] = 0;
expected[10] = 0;
assert_eq!(map, expected);
}
#[test]
fn type2_foreground_two_rectangles_priority() {
let pps = make_pps(
2,
Some(SliceGroupMap::Foreground {
top_left: vec![0, 4],
bottom_right: vec![7, 11],
}),
);
let map = map_unit_to_slice_group_map(&pps, 16, 4, 0, false).unwrap();
let expected = vec![0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
assert_eq!(map, expected);
}
#[test]
fn type3_box_out_clockwise_small() {
let pps = make_pps(
1,
Some(SliceGroupMap::Changing {
slice_group_map_type: 3,
change_direction_flag: false,
change_rate_minus1: 0, }),
);
let map = map_unit_to_slice_group_map(&pps, 16, 4, 3, false).unwrap();
let mut expected = vec![1u32; 16];
expected[10] = 0;
expected[9] = 0;
expected[5] = 0;
assert_eq!(map, expected);
}
#[test]
fn type3_box_out_size_zero() {
let pps = make_pps(
1,
Some(SliceGroupMap::Changing {
slice_group_map_type: 3,
change_direction_flag: false,
change_rate_minus1: 0,
}),
);
let map = map_unit_to_slice_group_map(&pps, 16, 4, 0, false).unwrap();
assert_eq!(map, vec![1u32; 16]);
}
#[test]
fn type4_raster_direction_0() {
let pps = make_pps(
1,
Some(SliceGroupMap::Changing {
slice_group_map_type: 4,
change_direction_flag: false,
change_rate_minus1: 1, }),
);
let map = map_unit_to_slice_group_map(&pps, 16, 4, 3, false).unwrap();
let expected = vec![0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
assert_eq!(map, expected);
}
#[test]
fn type4_raster_direction_1() {
let pps = make_pps(
1,
Some(SliceGroupMap::Changing {
slice_group_map_type: 4,
change_direction_flag: true,
change_rate_minus1: 1,
}),
);
let map = map_unit_to_slice_group_map(&pps, 16, 4, 3, false).unwrap();
let expected = vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0];
assert_eq!(map, expected);
}
#[test]
fn type5_wipe_direction_0() {
let pps = make_pps(
1,
Some(SliceGroupMap::Changing {
slice_group_map_type: 5,
change_direction_flag: false,
change_rate_minus1: 0,
}),
);
let map = map_unit_to_slice_group_map(&pps, 16, 4, 3, false).unwrap();
let mut expected = vec![1u32; 16];
expected[0] = 0;
expected[4] = 0;
expected[8] = 0;
assert_eq!(map, expected);
}
#[test]
fn type6_explicit_passthrough() {
let pps = make_pps(
1,
Some(SliceGroupMap::Explicit {
pic_size_in_map_units_minus1: 7,
slice_group_id: vec![0, 0, 1, 1, 0, 0, 1, 1],
}),
);
let map = map_unit_to_slice_group_map(&pps, 8, 4, 0, false).unwrap();
assert_eq!(map, vec![0, 0, 1, 1, 0, 0, 1, 1]);
}
#[test]
fn mb_map_passthrough_frame_only() {
let mu_map = vec![0, 1, 0, 1, 2, 2];
let mb_map = mb_to_slice_group_map(&mu_map, true, false, false, 3);
assert_eq!(mb_map, mu_map);
}
#[test]
fn mb_map_passthrough_field_pic() {
let mu_map = vec![3, 3, 2, 1];
let mb_map = mb_to_slice_group_map(&mu_map, false, false, true, 2);
assert_eq!(mb_map, mu_map);
}
#[test]
fn mb_map_mbaff_i_over_2() {
let mu_map = vec![0, 1, 2, 3];
let mb_map = mb_to_slice_group_map(&mu_map, false, true, false, 2);
assert_eq!(mb_map, vec![0, 0, 1, 1, 2, 2, 3, 3]);
}
#[test]
fn mb_map_non_mbaff_interlaced_frame() {
let mu_map = vec![10, 20, 30, 40];
let mb_map = mb_to_slice_group_map(&mu_map, false, false, false, 2);
assert_eq!(mb_map, vec![10, 20, 10, 20, 30, 40, 30, 40]);
}
#[test]
fn next_mb_skips_other_slice_group() {
let mb_map = vec![0u32, 1, 1, 0, 1, 0, 0, 1];
let next = next_mb_address(0, &mb_map, 0, 8);
assert_eq!(next, 3);
}
#[test]
fn next_mb_same_slice_group_adjacent() {
let mb_map = vec![0u32, 0, 1, 0, 1, 0, 0, 1];
assert_eq!(next_mb_address(0, &mb_map, 0, 8), 1);
assert_eq!(next_mb_address(2, &mb_map, 1, 8), 4);
}
#[test]
fn next_mb_returns_pic_size_when_done() {
let mb_map = vec![0u32, 0, 0, 1];
let next = next_mb_address(2, &mb_map, 0, 4);
assert_eq!(next, 4); }
#[test]
fn next_mb_current_at_end() {
let mb_map = vec![0u32, 0, 0];
let next = next_mb_address(2, &mb_map, 0, 3);
assert_eq!(next, 3);
}
#[test]
fn empty_picture_rejected() {
let pps = make_pps(0, None);
let err = map_unit_to_slice_group_map(&pps, 0, 4, 0, false).unwrap_err();
assert_eq!(err, FmoError::EmptyPicture);
}
#[test]
fn explicit_length_mismatch_detected() {
let pps = make_pps(
1,
Some(SliceGroupMap::Explicit {
pic_size_in_map_units_minus1: 3,
slice_group_id: vec![0, 0], }),
);
let err = map_unit_to_slice_group_map(&pps, 4, 2, 0, false).unwrap_err();
assert!(matches!(err, FmoError::ExplicitMapSizeMismatch { .. }));
}
#[test]
fn type0_bounded_by_pic_size() {
let pps = make_pps(
1,
Some(SliceGroupMap::Interleaved {
run_length_minus1: vec![99, 99],
}),
);
let map = map_unit_to_slice_group_map(&pps, 3, 3, 0, false).unwrap();
assert_eq!(map, vec![0, 0, 0]);
}
#[test]
fn mb_pair_flags_top_frame() {
let (frame, top) = mb_pair_flags(0, false);
assert!(frame, "mb_field_decoding_flag=0 → currMbFrameFlag=1");
assert!(top, "CurrMbAddr % 2 == 0 → mbIsTopMbFlag=1");
}
#[test]
fn mb_pair_flags_bottom_field() {
let (frame, top) = mb_pair_flags(1, true);
assert!(!frame, "mb_field_decoding_flag=1 → currMbFrameFlag=0");
assert!(!top, "CurrMbAddr % 2 == 1 → mbIsTopMbFlag=0");
}
#[test]
fn mb_pair_flags_pair_three_bottom_frame() {
let (frame, top) = mb_pair_flags(7, false);
assert!(frame);
assert!(!top);
}
#[test]
fn mbaff_xy_frame_top_of_first_pair() {
let (x, y) = mbaff_mb_to_sample_xy(0, 4, false);
assert_eq!((x, y), (0, 0));
}
#[test]
fn mbaff_xy_frame_bottom_of_first_pair() {
let (x, y) = mbaff_mb_to_sample_xy(1, 4, false);
assert_eq!((x, y), (0, 16));
}
#[test]
fn mbaff_xy_field_top_of_first_pair() {
let (x, y) = mbaff_mb_to_sample_xy(0, 4, true);
assert_eq!((x, y), (0, 0));
}
#[test]
fn mbaff_xy_field_bottom_of_first_pair() {
let (x, y) = mbaff_mb_to_sample_xy(1, 4, true);
assert_eq!((x, y), (0, 1));
}
#[test]
fn mbaff_xy_second_pair_top_frame() {
let (x, y) = mbaff_mb_to_sample_xy(2, 4, false);
assert_eq!((x, y), (16, 0));
}
#[test]
fn mbaff_xy_second_row_pair_top_frame() {
let (x, y) = mbaff_mb_to_sample_xy(8, 4, false);
assert_eq!((x, y), (0, 32));
}
#[test]
fn mbaff_xy_y_step_helper() {
assert_eq!(mbaff_y_step(false), 1, "frame MB → stride 1");
assert_eq!(mbaff_y_step(true), 2, "field MB → stride 2");
}
#[test]
fn mbaff_xy_zero_width_is_safe() {
let (x, y) = mbaff_mb_to_sample_xy(5, 0, false);
assert_eq!((x, y), (0, 0));
}
#[test]
fn pair_neighbours_top_left_corner_all_none() {
let ns = mbaff_pair_neighbour_addrs(0, 4);
assert_eq!(ns, [None, None, None, None]);
}
#[test]
fn pair_neighbours_interior_has_all_four() {
let ns = mbaff_pair_neighbour_addrs(12, 4);
assert_eq!(ns, [Some(10), Some(4), Some(6), Some(2)]);
}
#[test]
fn pair_neighbours_same_for_top_and_bottom_of_pair() {
let top = mbaff_pair_neighbour_addrs(12, 4);
let bot = mbaff_pair_neighbour_addrs(13, 4);
assert_eq!(top, bot);
}
#[test]
fn pair_neighbours_right_edge_no_c() {
let ns = mbaff_pair_neighbour_addrs(14, 4);
assert_eq!(ns, [Some(12), Some(6), None, Some(4)]);
}
#[test]
fn pair_neighbours_left_edge_no_a_d() {
let ns = mbaff_pair_neighbour_addrs(8, 4);
assert_eq!(ns, [None, Some(0), Some(2), None]);
}
#[test]
fn neighbour_addrs_uniform_field_pair_at_interior() {
let flags = vec![false; 16];
let n = mbaff_neighbour_addrs(12, 4, &flags);
assert_eq!(n, [Some(10), Some(5), Some(7), Some(3)]);
}
#[test]
fn neighbour_addrs_top_left_corner_all_none() {
let flags = vec![false; 16];
let n = mbaff_neighbour_addrs(0, 4, &flags);
assert_eq!(n, [None, None, None, None]);
}
#[test]
fn neighbour_addrs_bottom_within_same_pair() {
let flags = vec![false; 16];
let n = mbaff_neighbour_addrs(13, 4, &flags);
assert_eq!(n, [Some(11), Some(12), None, Some(10)]);
}
#[test]
fn neighbour_addrs_left_edge_has_only_above() {
let flags = vec![false; 16];
let n = mbaff_neighbour_addrs(8, 4, &flags);
assert_eq!(n, [None, Some(1), Some(3), None]);
}
fn loc_all_frame(xn: i32, yn: i32, curr_mb_addr: u32) -> Option<(u32, i32, i32)> {
let pair = mbaff_pair_neighbour_addrs(curr_mb_addr, 4);
let mb_is_top = curr_mb_addr % 2 == 0;
mbaff_neigh_location(xn, yn, 16, 16, true, mb_is_top, curr_mb_addr, pair, |_| {
true
})
}
#[test]
fn loc_frame_top_left_neighbour_is_left_pair_top() {
assert_eq!(loc_all_frame(-1, 0, 10), Some((8, 15, 0)));
}
#[test]
fn loc_frame_top_above_neighbour_is_above_pair_bottom() {
assert_eq!(loc_all_frame(0, -1, 10), Some((3, 0, 15)));
}
#[test]
fn loc_frame_bottom_left_neighbour_is_left_pair_bottom() {
assert_eq!(loc_all_frame(-1, 0, 11), Some((9, 15, 0)));
}
#[test]
fn loc_frame_bottom_above_neighbour_is_own_pair_top() {
assert_eq!(loc_all_frame(0, -1, 11), Some((10, 0, 15)));
}
#[test]
fn loc_frame_inside_is_curr() {
assert_eq!(loc_all_frame(5, 7, 10), Some((10, 5, 7)));
}
#[test]
fn loc_frame_left_edge_unavailable() {
assert_eq!(loc_all_frame(-1, 0, 8), None);
}
#[test]
fn loc_top_row_above_unavailable() {
assert_eq!(loc_all_frame(0, -1, 0), None);
}
#[test]
fn loc_below_mb_always_unavailable() {
assert_eq!(loc_all_frame(0, 16, 10), None);
}
#[test]
fn loc_field_current_top_left_frame_neighbour_splits_rows() {
let pair = mbaff_pair_neighbour_addrs(10, 4);
let r = mbaff_neigh_location(-1, 3, 16, 16, false, true, 10, pair, |_| true);
assert_eq!(r, Some((8, 15, 6)));
let r2 = mbaff_neigh_location(-1, 10, 16, 16, false, true, 10, pair, |_| true);
assert_eq!(r2, Some((9, 15, 4)));
}
#[test]
fn loc_field_current_top_left_field_neighbour_same_row() {
let pair = mbaff_pair_neighbour_addrs(10, 4);
let r = mbaff_neigh_location(-1, 5, 16, 16, false, true, 10, pair, |_| false);
assert_eq!(r, Some((8, 15, 5)));
}
}