1use oxideav_core::{Error, Result};
44
45use crate::cabac::CabacEngine;
46use crate::deblock::{CuPredMode, CuSideInfo, SideInfoGrid};
47use crate::dequant::scale_and_inverse_transform;
48use crate::intra::IntraMode;
49use crate::picture::{intra_reconstruct_cb, YuvPicture};
50
51#[derive(Clone, Copy, Debug)]
56pub struct SliceWalkInputs {
57 pub pic_width: u32,
59 pub pic_height: u32,
61 pub ctb_log2_size_y: u32,
64 pub min_cb_log2_size_y: u32,
67 pub max_tb_log2_size_y: u32,
70 pub chroma_format_idc: u32,
73 pub cu_qp_delta_enabled: bool,
76 pub sps_ibc_flag: bool,
81 pub log2_max_ibc_cand_size: u32,
86 pub slice_alf_enabled_flag: bool,
90 pub slice_alf_map_flag: bool,
94 pub slice_chroma_alf_enabled_flag: bool,
99 pub slice_alf_chroma_map_flag: bool,
102 pub slice_chroma2_alf_enabled_flag: bool,
106 pub slice_alf_chroma2_map_flag: bool,
109}
110
111impl Default for SliceWalkInputs {
112 fn default() -> Self {
113 Self {
114 pic_width: 0,
115 pic_height: 0,
116 ctb_log2_size_y: 5,
117 min_cb_log2_size_y: 2,
118 max_tb_log2_size_y: 5,
119 chroma_format_idc: 1,
120 cu_qp_delta_enabled: false,
121 sps_ibc_flag: false,
122 log2_max_ibc_cand_size: 0,
123 slice_alf_enabled_flag: false,
124 slice_alf_map_flag: false,
125 slice_chroma_alf_enabled_flag: false,
126 slice_alf_chroma_map_flag: false,
127 slice_chroma2_alf_enabled_flag: false,
128 slice_alf_chroma2_map_flag: false,
129 }
130 }
131}
132
133impl SliceWalkInputs {
134 fn ctb_size(&self) -> u32 {
135 1 << self.ctb_log2_size_y
136 }
137 fn pic_width_in_ctus(&self) -> u32 {
138 (self.pic_width + self.ctb_size() - 1) >> self.ctb_log2_size_y
139 }
140 fn pic_height_in_ctus(&self) -> u32 {
141 (self.pic_height + self.ctb_size() - 1) >> self.ctb_log2_size_y
142 }
143}
144
145#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
153pub struct AlfCtbFlags {
154 pub luma: bool,
156 pub chroma_cb: bool,
158 pub chroma_cr: bool,
160}
161
162#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
167pub struct AlfCtbStats {
168 pub luma_bins: u32,
170 pub chroma_cb_bins: u32,
172 pub chroma_cr_bins: u32,
174 pub luma_on_ctus: u32,
177}
178
179fn decode_coding_tree_unit_alf(
199 eng: &mut CabacEngine,
200 inputs: &SliceWalkInputs,
201 stats: &mut AlfCtbStats,
202) -> Result<AlfCtbFlags> {
203 let mut flags = AlfCtbFlags::default();
204
205 if inputs.slice_alf_enabled_flag && inputs.slice_alf_map_flag {
206 let bin = eng.decode_decision(0, 0)?;
207 stats.luma_bins += 1;
208 flags.luma = bin != 0;
209 } else {
210 flags.luma = inputs.slice_alf_enabled_flag;
212 }
213 if flags.luma {
214 stats.luma_on_ctus += 1;
215 }
216
217 if inputs.slice_chroma_alf_enabled_flag && inputs.slice_alf_chroma_map_flag {
218 let bin = eng.decode_decision(0, 0)?;
219 stats.chroma_cb_bins += 1;
220 flags.chroma_cb = bin != 0;
221 } else {
222 flags.chroma_cb = inputs.slice_chroma_alf_enabled_flag;
223 }
224
225 if inputs.slice_chroma2_alf_enabled_flag && inputs.slice_alf_chroma2_map_flag {
226 let bin = eng.decode_decision(0, 0)?;
227 stats.chroma_cr_bins += 1;
228 flags.chroma_cr = bin != 0;
229 } else {
230 flags.chroma_cr = inputs.slice_chroma2_alf_enabled_flag;
231 }
232
233 Ok(flags)
234}
235
236#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
240pub struct SliceWalkStats {
241 pub ctus: u32,
243 pub split_cu_flag_bins: u32,
245 pub coding_units: u32,
248 pub cbf_luma_bins: u32,
250 pub cbf_chroma_bins: u32,
252 pub cu_qp_delta_abs_bins: u32,
254 pub intra_pred_mode_bins: u32,
256 pub ibc_flag_bins: u32,
259 pub ibc_cus: u32,
263 pub ibc_abs_mvd_bins: u32,
266 pub ibc_mvd_sign_bins: u32,
269 pub coeff_runs: u32,
271 pub alf_ctb: AlfCtbStats,
275 pub end_of_tile_bits: u32,
279 pub tile_byte_alignments: u32,
283 pub hmvp_resets: u32,
292}
293
294#[derive(Clone, Copy, Debug, PartialEq, Eq)]
300enum TreeType {
301 #[allow(dead_code)]
303 SingleTree,
304 DualTreeLuma,
306 DualTreeChroma,
308}
309
310pub fn walk_baseline_idr_slice(rbsp: &[u8], inputs: SliceWalkInputs) -> Result<SliceWalkStats> {
315 if inputs.ctb_log2_size_y < 5 || inputs.ctb_log2_size_y > 7 {
316 return Err(Error::invalid(format!(
317 "evc slice_data: CtbLog2SizeY {} out of Baseline range [5, 7]",
318 inputs.ctb_log2_size_y
319 )));
320 }
321 if inputs.min_cb_log2_size_y < 2 || inputs.min_cb_log2_size_y > inputs.ctb_log2_size_y {
322 return Err(Error::invalid(format!(
323 "evc slice_data: MinCbLog2SizeY {} invalid (CtbLog2SizeY={})",
324 inputs.min_cb_log2_size_y, inputs.ctb_log2_size_y
325 )));
326 }
327 let mut eng = CabacEngine::new(rbsp)?;
328 let mut stats = SliceWalkStats::default();
329 let n_ctus = inputs
330 .pic_width_in_ctus()
331 .checked_mul(inputs.pic_height_in_ctus())
332 .ok_or_else(|| Error::invalid("evc slice_data: ctu count overflow"))?;
333 if n_ctus == 0 {
334 return Err(Error::invalid("evc slice_data: no CTUs in slice"));
335 }
336 if n_ctus > 1_048_576 {
339 return Err(Error::invalid(format!(
340 "evc slice_data: ctu count {n_ctus} > sanity bound"
341 )));
342 }
343 for ctu_idx in 0..n_ctus {
344 walk_single_ctu(&mut eng, &mut stats, &inputs, ctu_idx, 0)?;
352 }
353 let term = eng.decode_terminate()?;
355 if !term {
356 return Err(Error::invalid(
357 "evc slice_data: end_of_tile_one_bit must terminate engine",
358 ));
359 }
360 stats.end_of_tile_bits += 1;
361 Ok(stats)
365}
366
367fn walk_single_ctu(
384 eng: &mut CabacEngine,
385 stats: &mut SliceWalkStats,
386 inputs: &SliceWalkInputs,
387 ctb_addr_in_rs: u32,
388 x_first_ctb: u32,
389) -> Result<()> {
390 let x_ctb = (ctb_addr_in_rs % inputs.pic_width_in_ctus()) << inputs.ctb_log2_size_y;
391 let y_ctb = (ctb_addr_in_rs / inputs.pic_width_in_ctus()) << inputs.ctb_log2_size_y;
392 if x_ctb == x_first_ctb {
397 stats.hmvp_resets += 1;
398 }
399 let _alf = decode_coding_tree_unit_alf(eng, inputs, &mut stats.alf_ctb)?;
404 walk_split_unit(
405 eng,
406 stats,
407 inputs,
408 x_ctb,
409 y_ctb,
410 inputs.ctb_log2_size_y,
411 inputs.ctb_log2_size_y,
412 )?;
413 stats.ctus += 1;
414 Ok(())
415}
416
417pub fn walk_baseline_idr_slice_tiled(
451 rbsp: &[u8],
452 inputs: SliceWalkInputs,
453 order: &SliceTileWalkOrder,
454 subset_ranges: &[core::ops::Range<usize>],
455) -> Result<SliceWalkStats> {
456 if inputs.ctb_log2_size_y < 5 || inputs.ctb_log2_size_y > 7 {
457 return Err(Error::invalid(format!(
458 "evc slice_data: CtbLog2SizeY {} out of Baseline range [5, 7]",
459 inputs.ctb_log2_size_y
460 )));
461 }
462 if inputs.min_cb_log2_size_y < 2 || inputs.min_cb_log2_size_y > inputs.ctb_log2_size_y {
463 return Err(Error::invalid(format!(
464 "evc slice_data: MinCbLog2SizeY {} invalid (CtbLog2SizeY={})",
465 inputs.min_cb_log2_size_y, inputs.ctb_log2_size_y
466 )));
467 }
468 if order.segments.is_empty() {
469 return Err(Error::invalid(
470 "evc slice_data: empty tile walk order (no tiles in slice)",
471 ));
472 }
473 if subset_ranges.len() != order.segments.len() {
474 return Err(Error::invalid(format!(
475 "evc slice_data: {} tile subset ranges for {} walk segments \
476 (§7.4.5 eq. 88/89 must yield one subset per tile)",
477 subset_ranges.len(),
478 order.segments.len()
479 )));
480 }
481 let n_ctus = inputs
482 .pic_width_in_ctus()
483 .checked_mul(inputs.pic_height_in_ctus())
484 .ok_or_else(|| Error::invalid("evc slice_data: ctu count overflow"))?;
485 if n_ctus == 0 {
486 return Err(Error::invalid("evc slice_data: no CTUs in slice"));
487 }
488 let mut stats = SliceWalkStats::default();
489 let num_tiles = order.segments.len();
490 for (i, (seg, range)) in order.segments.iter().zip(subset_ranges.iter()).enumerate() {
491 let subset = rbsp.get(range.clone()).ok_or_else(|| {
494 Error::invalid(format!(
495 "evc slice_data: tile {} subset range {}..{} outside slice data (len {})",
496 seg.tile_idx,
497 range.start,
498 range.end,
499 rbsp.len()
500 ))
501 })?;
502 let mut eng = CabacEngine::new(subset)?;
505 let first_ctb_addr_rs = *seg.ctb_addr_in_rs.first().ok_or_else(|| {
509 Error::invalid(format!(
510 "evc slice_data: tile {} has no CTUs (empty CtbAddrInRs)",
511 seg.tile_idx
512 ))
513 })?;
514 let x_first_ctb =
515 (first_ctb_addr_rs % inputs.pic_width_in_ctus()) << inputs.ctb_log2_size_y;
516 for &rs in &seg.ctb_addr_in_rs {
517 if rs >= n_ctus {
520 return Err(Error::invalid(format!(
521 "evc slice_data: tile {} CtbAddrInRs {rs} >= picture CTU count {n_ctus}",
522 seg.tile_idx
523 )));
524 }
525 walk_single_ctu(&mut eng, &mut stats, &inputs, rs, x_first_ctb)?;
526 }
527 let term = eng.decode_terminate()?;
529 if !term {
530 return Err(Error::invalid(format!(
531 "evc slice_data: end_of_tile_one_bit for tile {} must terminate engine",
532 seg.tile_idx
533 )));
534 }
535 stats.end_of_tile_bits += 1;
536 if i + 1 < num_tiles {
542 debug_assert!(seg.byte_align_after);
543 stats.tile_byte_alignments += 1;
544 } else {
545 debug_assert!(!seg.byte_align_after);
546 }
547 }
548 Ok(stats)
549}
550
551fn walk_split_unit(
555 eng: &mut CabacEngine,
556 stats: &mut SliceWalkStats,
557 inputs: &SliceWalkInputs,
558 x0: u32,
559 y0: u32,
560 log2_cb_width: u32,
561 log2_cb_height: u32,
562) -> Result<()> {
563 let mut split = false;
566 let cb_w = 1u32 << log2_cb_width;
567 let cb_h = 1u32 << log2_cb_height;
568 let cb_within_picture = x0 + cb_w <= inputs.pic_width && y0 + cb_h <= inputs.pic_height;
569 let can_split = log2_cb_width > inputs.min_cb_log2_size_y
570 && log2_cb_height > inputs.min_cb_log2_size_y
571 && cb_within_picture;
572 if can_split && (log2_cb_width > 2 || log2_cb_height > 2) {
573 let bin = eng.decode_decision(0, 0)?;
575 stats.split_cu_flag_bins += 1;
576 split = bin != 0;
577 } else if !cb_within_picture && can_split {
578 split = true;
581 }
582
583 if split {
584 let half_w = log2_cb_width.saturating_sub(1);
585 let half_h = log2_cb_height.saturating_sub(1);
586 let x1 = x0 + (1u32 << half_w);
587 let y1 = y0 + (1u32 << half_h);
588 walk_split_unit(eng, stats, inputs, x0, y0, half_w, half_h)?;
590 if x1 < inputs.pic_width {
591 walk_split_unit(eng, stats, inputs, x1, y0, half_w, half_h)?;
592 }
593 if y1 < inputs.pic_height {
594 walk_split_unit(eng, stats, inputs, x0, y1, half_w, half_h)?;
595 }
596 if x1 < inputs.pic_width && y1 < inputs.pic_height {
597 walk_split_unit(eng, stats, inputs, x1, y1, half_w, half_h)?;
598 }
599 return Ok(());
600 }
601
602 walk_coding_unit(
604 eng,
605 stats,
606 inputs,
607 x0,
608 y0,
609 log2_cb_width,
610 log2_cb_height,
611 TreeType::DualTreeLuma,
612 )?;
613 walk_coding_unit(
614 eng,
615 stats,
616 inputs,
617 x0,
618 y0,
619 log2_cb_width,
620 log2_cb_height,
621 TreeType::DualTreeChroma,
622 )?;
623 Ok(())
624}
625
626#[allow(clippy::too_many_arguments)]
641fn walk_coding_unit(
642 eng: &mut CabacEngine,
643 stats: &mut SliceWalkStats,
644 inputs: &SliceWalkInputs,
645 x0: u32,
646 y0: u32,
647 log2_cb_width: u32,
648 log2_cb_height: u32,
649 tree_type: TreeType,
650) -> Result<()> {
651 stats.coding_units += 1;
652 let is_luma_tree = matches!(tree_type, TreeType::DualTreeLuma | TreeType::SingleTree);
658 let ibc_allowed = is_luma_tree
659 && crate::ibc::is_ibc_allowed_for_size(
660 inputs.sps_ibc_flag,
661 inputs.log2_max_ibc_cand_size,
662 log2_cb_width,
663 log2_cb_height,
664 );
665 let mut is_ibc = false;
666 if ibc_allowed {
667 let ibc_bin = eng.decode_decision(0, 0)?;
672 stats.ibc_flag_bins += 1;
673 is_ibc = ibc_bin != 0;
674 if is_ibc {
675 stats.ibc_cus += 1;
676 for _comp in 0..2 {
681 let abs = eng.decode_egk_bypass(0)?;
682 stats.ibc_abs_mvd_bins += 1;
683 if abs != 0 {
684 let _sign = eng.decode_bypass()?;
685 stats.ibc_mvd_sign_bins += 1;
686 }
687 }
688 }
695 }
696 if !is_ibc && is_luma_tree {
697 let _intra_mode = eng.decode_u_regular(0, |_bin_idx| 0)?;
703 stats.intra_pred_mode_bins += 1;
704 }
705 walk_transform_unit(
711 eng,
712 stats,
713 inputs,
714 x0,
715 y0,
716 log2_cb_width,
717 log2_cb_height,
718 tree_type,
719 )
720}
721
722#[allow(clippy::too_many_arguments)]
724fn walk_transform_unit(
725 eng: &mut CabacEngine,
726 stats: &mut SliceWalkStats,
727 inputs: &SliceWalkInputs,
728 _x0: u32,
729 _y0: u32,
730 log2_cb_width: u32,
731 log2_cb_height: u32,
732 tree_type: TreeType,
733) -> Result<()> {
734 let log2_tb_width = log2_cb_width.min(inputs.max_tb_log2_size_y);
735 let log2_tb_height = log2_cb_height.min(inputs.max_tb_log2_size_y);
736 let chroma_present = inputs.chroma_format_idc != 0;
737 let mut cbf_cb = 0u32;
738 let mut cbf_cr = 0u32;
739 let mut cbf_luma = 0u32;
740 if tree_type != TreeType::DualTreeLuma && chroma_present {
742 cbf_cb = eng.decode_decision(0, 0)? as u32;
743 cbf_cr = eng.decode_decision(0, 0)? as u32;
744 stats.cbf_chroma_bins += 2;
745 }
746 let is_split =
750 log2_cb_width > inputs.max_tb_log2_size_y || log2_cb_height > inputs.max_tb_log2_size_y;
751 let is_intra = true;
752 if (is_split || is_intra || cbf_cb != 0 || cbf_cr != 0) && tree_type != TreeType::DualTreeChroma
753 {
754 cbf_luma = eng.decode_decision(0, 0)? as u32;
755 stats.cbf_luma_bins += 1;
756 }
757 if inputs.cu_qp_delta_enabled && (cbf_luma != 0 || cbf_cb != 0 || cbf_cr != 0) {
761 let qp_delta_abs = eng.decode_u_regular(0, |_| 0)?;
762 stats.cu_qp_delta_abs_bins += 1;
763 if qp_delta_abs > 0 {
764 let _sign = eng.decode_bypass()?;
771 }
772 }
773 if cbf_luma != 0 {
777 walk_residual_coding_rle(eng, stats, log2_tb_width, log2_tb_height)?;
778 }
779 if cbf_cb != 0 {
780 let log2_c_w = log2_tb_width.saturating_sub(1);
783 let log2_c_h = log2_tb_height.saturating_sub(1);
784 walk_residual_coding_rle(eng, stats, log2_c_w, log2_c_h)?;
785 }
786 if cbf_cr != 0 {
787 let log2_c_w = log2_tb_width.saturating_sub(1);
788 let log2_c_h = log2_tb_height.saturating_sub(1);
789 walk_residual_coding_rle(eng, stats, log2_c_w, log2_c_h)?;
790 }
791 Ok(())
792}
793
794fn walk_residual_coding_rle(
804 eng: &mut CabacEngine,
805 stats: &mut SliceWalkStats,
806 log2_tb_width: u32,
807 log2_tb_height: u32,
808) -> Result<()> {
809 let total_coeffs: u32 = 1u32 << (log2_tb_width + log2_tb_height);
810 if total_coeffs == 0 || total_coeffs > (1 << 12) {
811 return Err(Error::invalid(format!(
812 "evc residual_coding_rle: total_coeffs {total_coeffs} out of range"
813 )));
814 }
815 let mut scan_pos: u32 = 0;
816 loop {
817 let zr = eng.decode_u_regular(0, |_| 0)?;
819 scan_pos = scan_pos
820 .checked_add(zr)
821 .ok_or_else(|| Error::invalid("evc residual_coding_rle: scan_pos overflow"))?;
822 if scan_pos >= total_coeffs {
823 return Err(Error::invalid(
824 "evc residual_coding_rle: zero-run pushed past block size",
825 ));
826 }
827 let _level_minus1 = eng.decode_u_regular(0, |_| 0)?;
830 let _sign = eng.decode_bypass()?;
832 stats.coeff_runs += 1;
833 let last_pos_reached = scan_pos == total_coeffs - 1;
835 let coeff_last = if !last_pos_reached {
836 eng.decode_decision(0, 0)?
837 } else {
838 1
839 };
840 scan_pos += 1;
841 if coeff_last != 0 || scan_pos >= total_coeffs {
842 return Ok(());
843 }
844 }
845}
846
847fn zigzag_scan(blk_w: usize, blk_h: usize) -> Vec<usize> {
855 let total = blk_w * blk_h;
856 let mut zz = Vec::with_capacity(total);
857 if total == 0 {
858 return zz;
859 }
860 zz.push(0);
861 let bw = blk_w as i32;
862 let bh = blk_h as i32;
863 for line in 1..(bw + bh - 1) {
864 if line & 1 == 1 {
865 let mut x = line.min(bw - 1);
867 let mut y = (line - (bw - 1)).max(0);
868 while x >= 0 && y < bh {
869 zz.push((y * bw + x) as usize);
870 x -= 1;
871 y += 1;
872 }
873 } else {
874 let mut y = line.min(bh - 1);
876 let mut x = (line - (bh - 1)).max(0);
877 while y >= 0 && x < bw {
878 zz.push((y * bw + x) as usize);
879 x += 1;
880 y -= 1;
881 }
882 }
883 }
884 debug_assert_eq!(zz.len(), total);
885 zz
886}
887
888fn decode_residual_coding_rle(
902 eng: &mut CabacEngine,
903 levels: &mut [i32],
904 coeff_runs_counter: &mut u32,
905 log2_tb_width: u32,
906 log2_tb_height: u32,
907) -> Result<()> {
908 let blk_w = 1usize << log2_tb_width;
909 let blk_h = 1usize << log2_tb_height;
910 let total = blk_w * blk_h;
911 if levels.len() != total {
912 return Err(Error::invalid(format!(
913 "evc residual_coding_rle: levels len {} != {}*{} = {}",
914 levels.len(),
915 blk_w,
916 blk_h,
917 total
918 )));
919 }
920 if total > (1 << 12) {
921 return Err(Error::invalid(format!(
922 "evc residual_coding_rle: block too large ({total} > 4096)"
923 )));
924 }
925 let scan = zigzag_scan(blk_w, blk_h);
926 let mut scan_pos: u32 = 0;
927 loop {
928 let zr = eng.decode_u_regular(0, |_| 0)?;
930 scan_pos = scan_pos
931 .checked_add(zr)
932 .ok_or_else(|| Error::invalid("evc residual_coding_rle: scan_pos overflow"))?;
933 if (scan_pos as usize) >= total {
934 return Err(Error::invalid(
935 "evc residual_coding_rle: zero-run pushed past block size",
936 ));
937 }
938 let lvl_minus1 = eng.decode_u_regular(0, |_| 0)?;
940 let abs_lvl = (lvl_minus1 as i32) + 1;
941 let sign = eng.decode_bypass()?;
943 let level: i32 = if sign != 0 { -abs_lvl } else { abs_lvl };
944 let level = level.clamp(-32768, 32767);
947 let blk_pos = *scan
949 .get(scan_pos as usize)
950 .ok_or_else(|| Error::invalid("evc residual_coding_rle: scan index out of bounds"))?;
951 levels[blk_pos] = level;
952 *coeff_runs_counter += 1;
953 let last_pos_reached = scan_pos as usize == total - 1;
955 let coeff_last = if !last_pos_reached {
956 eng.decode_decision(0, 0)?
957 } else {
958 1
959 };
960 scan_pos += 1;
961 if coeff_last != 0 || (scan_pos as usize) >= total {
962 return Ok(());
963 }
964 }
965}
966
967#[derive(Clone, Debug, PartialEq, Eq)]
993pub struct SliceTileWalkSegment {
994 pub tile_idx: u32,
996 pub first_ctb_addr_ts: u32,
999 pub num_ctus: u32,
1001 pub ctb_addr_in_rs: Vec<u32>,
1005 pub byte_align_after: bool,
1011}
1012
1013#[derive(Clone, Debug, PartialEq, Eq, Default)]
1019pub struct SliceTileWalkOrder {
1020 pub segments: Vec<SliceTileWalkSegment>,
1023}
1024
1025impl SliceTileWalkOrder {
1026 #[must_use]
1029 pub fn total_ctus(&self) -> u32 {
1030 self.segments.iter().map(|s| s.num_ctus).sum()
1031 }
1032
1033 #[must_use]
1036 pub fn ctb_addr_in_rs_flat(&self) -> Vec<u32> {
1037 self.segments
1038 .iter()
1039 .flat_map(|s| s.ctb_addr_in_rs.iter().copied())
1040 .collect()
1041 }
1042}
1043
1044pub fn resolve_slice_tile_walk_order(
1076 slice_tile_idx: &[u32],
1077 first_ctb_addr_ts: &[u32],
1078 num_ctus_in_tile: &[u32],
1079 ctb_addr_ts_to_rs: &[u32],
1080) -> Result<SliceTileWalkOrder> {
1081 let num_tiles_in_slice = slice_tile_idx.len();
1082 let mut segments = Vec::with_capacity(num_tiles_in_slice);
1083 let ts_len = ctb_addr_ts_to_rs.len() as u64;
1084 for (i, &tile_idx) in slice_tile_idx.iter().enumerate() {
1085 let ti = tile_idx as usize;
1086 let first = *first_ctb_addr_ts.get(ti).ok_or_else(|| {
1087 Error::invalid(format!(
1088 "evc slice_data: SliceTileIdx[{i}] = {tile_idx} out of \
1089 FirstCtbAddrTs range (len {})",
1090 first_ctb_addr_ts.len()
1091 ))
1092 })?;
1093 let num_ctus = *num_ctus_in_tile.get(ti).ok_or_else(|| {
1094 Error::invalid(format!(
1095 "evc slice_data: SliceTileIdx[{i}] = {tile_idx} out of \
1096 NumCtusInTile range (len {})",
1097 num_ctus_in_tile.len()
1098 ))
1099 })?;
1100 let end = u64::from(first) + u64::from(num_ctus);
1105 if end > ts_len {
1106 return Err(Error::invalid(format!(
1107 "evc slice_data: tile {tile_idx} CTU range \
1108 [{first}, {end}) overruns CtbAddrTsToRs (len {ts_len})"
1109 )));
1110 }
1111 let mut ctb_addr_in_rs = Vec::with_capacity(num_ctus as usize);
1112 for ts in first..first + num_ctus {
1113 ctb_addr_in_rs.push(ctb_addr_ts_to_rs[ts as usize]);
1114 }
1115 segments.push(SliceTileWalkSegment {
1116 tile_idx,
1117 first_ctb_addr_ts: first,
1118 num_ctus,
1119 ctb_addr_in_rs,
1120 byte_align_after: i + 1 < num_tiles_in_slice,
1121 });
1122 }
1123 Ok(SliceTileWalkOrder { segments })
1124}
1125
1126pub fn derive_x_first_ctb(
1175 ctb_addr_in_rs: u32,
1176 ctb_addr_rs_to_ts: &[u32],
1177 tile_id: &[u32],
1178 tile_index_maps: &crate::pps::TileIndexMaps,
1179 ctb_addr_ts_to_rs: &[u32],
1180 pic_width_in_ctbs_y: u32,
1181 ctb_log2_size_y: u32,
1182) -> Result<u32> {
1183 if pic_width_in_ctbs_y == 0 {
1184 return Err(Error::invalid(
1185 "evc slice_data: PicWidthInCtbsY == 0 has no CTB grid for xFirstCtb",
1186 ));
1187 }
1188 let ctb_addr_ts = *ctb_addr_rs_to_ts
1190 .get(ctb_addr_in_rs as usize)
1191 .ok_or_else(|| {
1192 Error::invalid(format!(
1193 "evc slice_data: CtbAddrInRs {ctb_addr_in_rs} out of \
1194 CtbAddrRsToTs range (len {})",
1195 ctb_addr_rs_to_ts.len()
1196 ))
1197 })?;
1198 let id = *tile_id.get(ctb_addr_ts as usize).ok_or_else(|| {
1200 Error::invalid(format!(
1201 "evc slice_data: ctbAddrTs {ctb_addr_ts} out of TileId range (len {})",
1202 tile_id.len()
1203 ))
1204 })?;
1205 let tile_index = tile_index_maps.tile_idx_for_id(id).ok_or_else(|| {
1207 Error::invalid(format!(
1208 "evc slice_data: TileId {id} names no tile in TileIdToIdx"
1209 ))
1210 })?;
1211 let first_ctb_addr_ts = *tile_index_maps
1213 .first_ctb_addr_ts
1214 .get(tile_index as usize)
1215 .ok_or_else(|| {
1216 Error::invalid(format!(
1217 "evc slice_data: tileIndex {tile_index} out of \
1218 FirstCtbAddrTs range (len {})",
1219 tile_index_maps.first_ctb_addr_ts.len()
1220 ))
1221 })?;
1222 let first_ctb_addr_rs = *ctb_addr_ts_to_rs
1224 .get(first_ctb_addr_ts as usize)
1225 .ok_or_else(|| {
1226 Error::invalid(format!(
1227 "evc slice_data: FirstCtbAddrTs {first_ctb_addr_ts} out of \
1228 CtbAddrTsToRs range (len {})",
1229 ctb_addr_ts_to_rs.len()
1230 ))
1231 })?;
1232 Ok((first_ctb_addr_rs % pic_width_in_ctbs_y) << ctb_log2_size_y)
1234}
1235
1236#[derive(Clone, Copy, Debug)]
1243pub struct SliceDecodeInputs {
1244 pub slice_qp: i32,
1245 pub bit_depth_luma: u32,
1246 pub bit_depth_chroma: u32,
1247 pub enable_deblock: bool,
1250 pub slice_cb_qp_offset: i32,
1254 pub slice_cr_qp_offset: i32,
1256 pub sps_ibc_flag: bool,
1259 pub log2_max_ibc_cand_size: u32,
1262}
1263
1264impl Default for SliceDecodeInputs {
1265 fn default() -> Self {
1266 Self {
1267 slice_qp: 0,
1268 bit_depth_luma: 8,
1269 bit_depth_chroma: 8,
1270 enable_deblock: false,
1271 slice_cb_qp_offset: 0,
1272 slice_cr_qp_offset: 0,
1273 sps_ibc_flag: false,
1274 log2_max_ibc_cand_size: 0,
1275 }
1276 }
1277}
1278
1279#[derive(Clone, Debug, Default, PartialEq, Eq)]
1283pub struct SliceDecodeStats {
1284 pub ctus: u32,
1285 pub split_cu_flag_bins: u32,
1286 pub coding_units: u32,
1287 pub cbf_luma_bins: u32,
1288 pub cbf_chroma_bins: u32,
1289 pub intra_pred_mode_bins: u32,
1290 pub ibc_flag_bins: u32,
1293 pub ibc_cus: u32,
1296 pub ibc_abs_mvd_bins: u32,
1299 pub ibc_mvd_sign_bins: u32,
1302 pub cu_qp_delta_abs_bins: u32,
1307 pub coeff_runs: u32,
1308 pub deblock_edges: u32,
1310 pub alf_ctb: AlfCtbStats,
1314 pub alf_ctb_map: crate::alf::AlfCtbMap,
1320}
1321
1322pub fn decode_baseline_idr_slice(
1337 rbsp: &[u8],
1338 walk: SliceWalkInputs,
1339 decode: SliceDecodeInputs,
1340) -> Result<(YuvPicture, SliceDecodeStats)> {
1341 if walk.ctb_log2_size_y < 5 || walk.ctb_log2_size_y > 7 {
1342 return Err(Error::invalid(format!(
1343 "evc decode: CtbLog2SizeY {} out of Baseline range [5, 7]",
1344 walk.ctb_log2_size_y
1345 )));
1346 }
1347 if walk.min_cb_log2_size_y < 2 || walk.min_cb_log2_size_y > walk.ctb_log2_size_y {
1348 return Err(Error::invalid(format!(
1349 "evc decode: MinCbLog2SizeY {} invalid",
1350 walk.min_cb_log2_size_y
1351 )));
1352 }
1353 if decode.bit_depth_luma != 8 || decode.bit_depth_chroma != 8 {
1354 return Err(Error::unsupported(format!(
1355 "evc decode: round-3 supports 8-bit only (luma={}, chroma={})",
1356 decode.bit_depth_luma, decode.bit_depth_chroma
1357 )));
1358 }
1359 let mut pic = YuvPicture::new(
1360 walk.pic_width,
1361 walk.pic_height,
1362 walk.chroma_format_idc,
1363 decode.bit_depth_luma,
1364 )?;
1365 let mut eng = CabacEngine::new(rbsp)?;
1366 let mut stats = SliceDecodeStats {
1367 alf_ctb_map: crate::alf::AlfCtbMap::new(
1368 walk.pic_width,
1369 walk.pic_height,
1370 walk.ctb_log2_size_y,
1371 ),
1372 ..Default::default()
1373 };
1374 let mut side_info = SideInfoGrid::new(walk.pic_width, walk.pic_height);
1375 let n_ctus = walk
1376 .pic_width_in_ctus()
1377 .checked_mul(walk.pic_height_in_ctus())
1378 .ok_or_else(|| Error::invalid("evc decode: ctu count overflow"))?;
1379 if n_ctus == 0 {
1380 return Err(Error::invalid("evc decode: no CTUs in slice"));
1381 }
1382 if n_ctus > 1_048_576 {
1383 return Err(Error::invalid(format!(
1384 "evc decode: ctu count {n_ctus} > sanity bound"
1385 )));
1386 }
1387 for ctu_idx in 0..n_ctus {
1388 let x_ctb = (ctu_idx % walk.pic_width_in_ctus()) << walk.ctb_log2_size_y;
1389 let y_ctb = (ctu_idx / walk.pic_width_in_ctus()) << walk.ctb_log2_size_y;
1390 let alf = decode_coding_tree_unit_alf(&mut eng, &walk, &mut stats.alf_ctb)?;
1394 stats
1395 .alf_ctb_map
1396 .set(ctu_idx as usize, alf.luma, alf.chroma_cb, alf.chroma_cr);
1397 decode_split_unit(
1398 &mut eng,
1399 &mut pic,
1400 &mut stats,
1401 &mut side_info,
1402 &walk,
1403 &decode,
1404 x_ctb,
1405 y_ctb,
1406 walk.ctb_log2_size_y,
1407 walk.ctb_log2_size_y,
1408 )?;
1409 stats.ctus += 1;
1410 }
1411 let term = eng.decode_terminate()?;
1412 if !term {
1413 return Err(Error::invalid(
1414 "evc decode: end_of_tile_one_bit must terminate engine",
1415 ));
1416 }
1417 if decode.enable_deblock {
1418 let mut edges = crate::deblock::deblock_luma(&mut pic, &side_info, decode.slice_qp)?;
1419 if walk.chroma_format_idc != 0 {
1420 edges += crate::deblock::deblock_chroma(
1421 &mut pic,
1422 &side_info,
1423 decode.slice_qp,
1424 decode.slice_cb_qp_offset,
1425 1,
1426 )?;
1427 edges += crate::deblock::deblock_chroma(
1428 &mut pic,
1429 &side_info,
1430 decode.slice_qp,
1431 decode.slice_cr_qp_offset,
1432 2,
1433 )?;
1434 }
1435 stats.deblock_edges = edges;
1436 }
1437 Ok((pic, stats))
1438}
1439
1440#[allow(clippy::too_many_arguments)]
1441fn decode_split_unit(
1442 eng: &mut CabacEngine,
1443 pic: &mut YuvPicture,
1444 stats: &mut SliceDecodeStats,
1445 side_info: &mut SideInfoGrid,
1446 walk: &SliceWalkInputs,
1447 decode: &SliceDecodeInputs,
1448 x0: u32,
1449 y0: u32,
1450 log2_cb_width: u32,
1451 log2_cb_height: u32,
1452) -> Result<()> {
1453 let cb_w = 1u32 << log2_cb_width;
1454 let cb_h = 1u32 << log2_cb_height;
1455 let cb_within_picture = x0 + cb_w <= walk.pic_width && y0 + cb_h <= walk.pic_height;
1456 let can_recurse =
1457 log2_cb_width > walk.min_cb_log2_size_y && log2_cb_height > walk.min_cb_log2_size_y;
1458 let mut split = false;
1459 if can_recurse && cb_within_picture && (log2_cb_width > 2 || log2_cb_height > 2) {
1460 let bin = eng.decode_decision(0, 0)?;
1461 stats.split_cu_flag_bins += 1;
1462 split = bin != 0;
1463 } else if can_recurse && !cb_within_picture {
1464 split = true;
1466 }
1467 if split {
1468 let half_w = log2_cb_width.saturating_sub(1);
1469 let half_h = log2_cb_height.saturating_sub(1);
1470 let x1 = x0 + (1u32 << half_w);
1471 let y1 = y0 + (1u32 << half_h);
1472 decode_split_unit(
1473 eng, pic, stats, side_info, walk, decode, x0, y0, half_w, half_h,
1474 )?;
1475 if x1 < walk.pic_width {
1476 decode_split_unit(
1477 eng, pic, stats, side_info, walk, decode, x1, y0, half_w, half_h,
1478 )?;
1479 }
1480 if y1 < walk.pic_height {
1481 decode_split_unit(
1482 eng, pic, stats, side_info, walk, decode, x0, y1, half_w, half_h,
1483 )?;
1484 }
1485 if x1 < walk.pic_width && y1 < walk.pic_height {
1486 decode_split_unit(
1487 eng, pic, stats, side_info, walk, decode, x1, y1, half_w, half_h,
1488 )?;
1489 }
1490 return Ok(());
1491 }
1492 decode_coding_unit(
1494 eng,
1495 pic,
1496 stats,
1497 side_info,
1498 walk,
1499 decode,
1500 x0,
1501 y0,
1502 log2_cb_width,
1503 log2_cb_height,
1504 TreeType::DualTreeLuma,
1505 )?;
1506 decode_coding_unit(
1507 eng,
1508 pic,
1509 stats,
1510 side_info,
1511 walk,
1512 decode,
1513 x0,
1514 y0,
1515 log2_cb_width,
1516 log2_cb_height,
1517 TreeType::DualTreeChroma,
1518 )?;
1519 Ok(())
1520}
1521
1522#[allow(clippy::too_many_arguments)]
1523fn decode_coding_unit(
1524 eng: &mut CabacEngine,
1525 pic: &mut YuvPicture,
1526 stats: &mut SliceDecodeStats,
1527 side_info: &mut SideInfoGrid,
1528 walk: &SliceWalkInputs,
1529 decode: &SliceDecodeInputs,
1530 x0: u32,
1531 y0: u32,
1532 log2_cb_width: u32,
1533 log2_cb_height: u32,
1534 tree_type: TreeType,
1535) -> Result<()> {
1536 stats.coding_units += 1;
1537 let is_luma_tree = matches!(tree_type, TreeType::DualTreeLuma | TreeType::SingleTree);
1548 let ibc_allowed = is_luma_tree
1549 && crate::ibc::is_ibc_allowed_for_size(
1550 decode.sps_ibc_flag,
1551 decode.log2_max_ibc_cand_size,
1552 log2_cb_width,
1553 log2_cb_height,
1554 );
1555 if ibc_allowed {
1556 let ibc_bin = eng.decode_decision(0, 0)?;
1557 stats.ibc_flag_bins += 1;
1558 if ibc_bin != 0 {
1559 stats.ibc_cus += 1;
1560 let mvd_x = decode_signed_mvd(
1564 eng,
1565 &mut stats.ibc_abs_mvd_bins,
1566 &mut stats.ibc_mvd_sign_bins,
1567 )?;
1568 let mvd_y = decode_signed_mvd(
1569 eng,
1570 &mut stats.ibc_abs_mvd_bins,
1571 &mut stats.ibc_mvd_sign_bins,
1572 )?;
1573 return decode_ibc_branch(
1574 eng,
1575 pic,
1576 stats,
1577 side_info,
1578 walk,
1579 decode,
1580 x0,
1581 y0,
1582 log2_cb_width,
1583 log2_cb_height,
1584 tree_type,
1585 MotionVector { x: mvd_x, y: mvd_y },
1586 );
1587 }
1588 }
1589 let luma_cu_is_ibc =
1598 matches!(tree_type, TreeType::DualTreeChroma) && luma_cell_is_ibc(side_info, x0, y0);
1599 let intra_idx = if is_luma_tree {
1604 let v = eng.decode_u_regular(0, |_| 0)?;
1605 stats.intra_pred_mode_bins += 1;
1606 v
1607 } else {
1608 0
1609 };
1610 let intra_mode = IntraMode::from_baseline_idx(intra_idx).ok_or_else(|| {
1611 Error::invalid(format!(
1612 "evc decode: intra_pred_mode {intra_idx} out of Baseline range 0..=4"
1613 ))
1614 })?;
1615
1616 decode_transform_unit(
1617 eng,
1618 pic,
1619 stats,
1620 side_info,
1621 walk,
1622 decode,
1623 x0,
1624 y0,
1625 log2_cb_width,
1626 log2_cb_height,
1627 tree_type,
1628 intra_mode,
1629 luma_cu_is_ibc,
1630 )
1631}
1632
1633fn luma_cell_is_ibc(side_info: &SideInfoGrid, x_luma: u32, y_luma: u32) -> bool {
1640 let xc = (x_luma >> 2) as usize;
1641 let yc = (y_luma >> 2) as usize;
1642 if xc >= side_info.w_cells || yc >= side_info.h_cells {
1643 return false;
1644 }
1645 side_info.at(xc, yc).pred_mode == CuPredMode::Ibc
1646}
1647
1648#[allow(clippy::too_many_arguments)]
1667fn decode_ibc_branch(
1668 eng: &mut CabacEngine,
1669 pic: &mut YuvPicture,
1670 stats: &mut SliceDecodeStats,
1671 side_info: &mut SideInfoGrid,
1672 walk: &SliceWalkInputs,
1673 decode: &SliceDecodeInputs,
1674 x0: u32,
1675 y0: u32,
1676 log2_cb_width: u32,
1677 log2_cb_height: u32,
1678 tree_type: TreeType,
1679 mvd: MotionVector,
1680) -> Result<()> {
1681 let log2_tb_width = log2_cb_width.min(walk.max_tb_log2_size_y);
1682 let log2_tb_height = log2_cb_height.min(walk.max_tb_log2_size_y);
1683 let cbf_luma = 1u32;
1695 if log2_tb_width != log2_cb_width || log2_tb_height != log2_cb_height {
1699 return Err(Error::unsupported(
1700 "evc ibc decode: round-90 requires log2_cb == log2_tb (CB ≤ MaxTb)",
1701 ));
1702 }
1703 let mut qp_delta: i32 = 0;
1718 if walk.cu_qp_delta_enabled && cbf_luma != 0 {
1719 let qp_delta_abs = eng.decode_u_regular(0, |_| 0)?;
1720 stats.cu_qp_delta_abs_bins += 1;
1721 if qp_delta_abs > 0 {
1722 let sign = eng.decode_bypass()?;
1723 qp_delta = if sign != 0 {
1724 -(qp_delta_abs as i32)
1725 } else {
1726 qp_delta_abs as i32
1727 };
1728 }
1729 }
1730 let cu_qp = (decode.slice_qp + qp_delta).clamp(0, 51);
1731 let n_tb = (1usize << log2_tb_width) * (1usize << log2_tb_height);
1734 let mut residual_levels_y = vec![0i32; n_tb];
1735 if cbf_luma != 0 {
1736 decode_residual_coding_rle(
1737 eng,
1738 &mut residual_levels_y,
1739 &mut stats.coeff_runs,
1740 log2_tb_width,
1741 log2_tb_height,
1742 )?;
1743 }
1744 apply_ibc_branch_predict_and_reconstruct(
1749 pic,
1750 side_info,
1751 walk,
1752 decode,
1753 x0,
1754 y0,
1755 log2_cb_width,
1756 log2_cb_height,
1757 tree_type,
1758 mvd,
1759 cbf_luma as u8,
1760 &residual_levels_y,
1761 cu_qp,
1762 )
1763}
1764
1765#[allow(clippy::too_many_arguments)]
1775fn apply_ibc_branch_predict_and_reconstruct(
1776 pic: &mut YuvPicture,
1777 side_info: &mut SideInfoGrid,
1778 walk: &SliceWalkInputs,
1779 decode: &SliceDecodeInputs,
1780 x0: u32,
1781 y0: u32,
1782 log2_cb_width: u32,
1783 log2_cb_height: u32,
1784 tree_type: TreeType,
1785 mvd: MotionVector,
1786 cbf_luma: u8,
1787 residual_levels_y: &[i32],
1788 cu_qp: i32,
1789) -> Result<()> {
1790 let chroma_present = walk.chroma_format_idc != 0;
1791 let n_cb_w_l = 1usize << log2_cb_width;
1792 let n_cb_h_l = 1usize << log2_cb_height;
1793 let n_l = n_cb_w_l * n_cb_h_l;
1794 let (n_c_w, n_c_h) = if chroma_present {
1795 match pic.chroma_format_idc {
1796 1 => (n_cb_w_l / 2, n_cb_h_l / 2),
1797 2 => (n_cb_w_l / 2, n_cb_h_l),
1798 3 => (n_cb_w_l, n_cb_h_l),
1799 _ => (0, 0),
1800 }
1801 } else {
1802 (0, 0)
1803 };
1804 let n_c = n_c_w * n_c_h;
1805 let mut pred_y = vec![0i32; n_l];
1806 let mut pred_cb = vec![0i32; n_c];
1807 let mut pred_cr = vec![0i32; n_c];
1808 let (mv_l, _mv_c) = crate::ibc::decode_ibc_cu(
1809 pic,
1810 x0 as i32,
1811 y0 as i32,
1812 n_cb_w_l,
1813 n_cb_h_l,
1814 mvd,
1815 walk.ctb_log2_size_y,
1816 chroma_present,
1817 &mut pred_y,
1818 &mut pred_cb,
1819 &mut pred_cr,
1820 )?;
1821 let mut residual_y = vec![0i32; n_l];
1825 if cbf_luma != 0 {
1826 if residual_levels_y.len() != n_l {
1827 return Err(Error::invalid(format!(
1828 "evc ibc apply: residual_levels_y len {} != {n_l}",
1829 residual_levels_y.len()
1830 )));
1831 }
1832 scale_and_inverse_transform(
1833 residual_levels_y,
1834 &mut residual_y,
1835 n_cb_w_l,
1836 n_cb_h_l,
1837 cu_qp,
1838 decode.bit_depth_luma,
1839 )?;
1840 }
1841 for (p, r) in pred_y.iter_mut().zip(residual_y.iter()) {
1842 *p += *r;
1843 }
1844 pic.store_block(x0, y0, n_cb_w_l, n_cb_h_l, 0, &pred_y);
1845 if chroma_present {
1846 pic.store_block(x0, y0, n_c_w, n_c_h, 1, &pred_cb);
1847 pic.store_block(x0, y0, n_c_w, n_c_h, 2, &pred_cr);
1848 }
1849 if matches!(tree_type, TreeType::DualTreeLuma | TreeType::SingleTree) {
1850 side_info.stamp_block(
1851 x0,
1852 y0,
1853 1u32 << log2_cb_width,
1854 1u32 << log2_cb_height,
1855 CuSideInfo {
1856 pred_mode: CuPredMode::Ibc,
1857 cbf_luma,
1858 mv_l0_x: mv_l.x,
1859 mv_l0_y: mv_l.y,
1860 ..Default::default()
1861 },
1862 );
1863 }
1864 Ok(())
1865}
1866
1867#[allow(clippy::too_many_arguments)]
1868fn decode_transform_unit(
1869 eng: &mut CabacEngine,
1870 pic: &mut YuvPicture,
1871 stats: &mut SliceDecodeStats,
1872 side_info: &mut SideInfoGrid,
1873 walk: &SliceWalkInputs,
1874 decode: &SliceDecodeInputs,
1875 x0: u32,
1876 y0: u32,
1877 log2_cb_width: u32,
1878 log2_cb_height: u32,
1879 tree_type: TreeType,
1880 intra_mode: IntraMode,
1881 luma_cu_is_ibc: bool,
1882) -> Result<()> {
1883 let log2_tb_width = log2_cb_width.min(walk.max_tb_log2_size_y);
1884 let log2_tb_height = log2_cb_height.min(walk.max_tb_log2_size_y);
1885 let chroma_present = walk.chroma_format_idc != 0;
1886 let mut cbf_cb = 0u32;
1887 let mut cbf_cr = 0u32;
1888 let mut cbf_luma = 0u32;
1889 if tree_type != TreeType::DualTreeLuma && chroma_present {
1890 cbf_cb = eng.decode_decision(0, 0)? as u32;
1891 cbf_cr = eng.decode_decision(0, 0)? as u32;
1892 stats.cbf_chroma_bins += 2;
1893 }
1894 let is_split =
1895 log2_cb_width > walk.max_tb_log2_size_y || log2_cb_height > walk.max_tb_log2_size_y;
1896 let is_intra = true;
1897 if (is_split || is_intra || cbf_cb != 0 || cbf_cr != 0) && tree_type != TreeType::DualTreeChroma
1898 {
1899 cbf_luma = eng.decode_decision(0, 0)? as u32;
1900 stats.cbf_luma_bins += 1;
1901 }
1902 let mut qp_delta: i32 = 0;
1903 if walk.cu_qp_delta_enabled && (cbf_luma != 0 || cbf_cb != 0 || cbf_cr != 0) {
1904 let qp_delta_abs = eng.decode_u_regular(0, |_| 0)?;
1905 stats.cu_qp_delta_abs_bins += 1;
1906 if qp_delta_abs > 0 {
1907 let sign = eng.decode_bypass()?;
1908 qp_delta = if sign != 0 {
1909 -(qp_delta_abs as i32)
1910 } else {
1911 qp_delta_abs as i32
1912 };
1913 }
1914 }
1915 let cu_qp = (decode.slice_qp + qp_delta).clamp(0, 51);
1916 if matches!(tree_type, TreeType::DualTreeLuma | TreeType::SingleTree) {
1919 side_info.stamp_block(
1920 x0,
1921 y0,
1922 1u32 << log2_cb_width,
1923 1u32 << log2_cb_height,
1924 CuSideInfo {
1925 pred_mode: CuPredMode::Intra,
1926 cbf_luma: cbf_luma as u8,
1927 ..Default::default()
1928 },
1929 );
1930 }
1931 match tree_type {
1933 TreeType::DualTreeLuma | TreeType::SingleTree => {
1934 let n = (1usize << log2_tb_width) * (1usize << log2_tb_height);
1935 let mut residual = vec![0i32; n];
1936 if cbf_luma != 0 {
1937 let mut levels = vec![0i32; n];
1938 decode_residual_coding_rle(
1939 eng,
1940 &mut levels,
1941 &mut stats.coeff_runs,
1942 log2_tb_width,
1943 log2_tb_height,
1944 )?;
1945 scale_and_inverse_transform(
1946 &levels,
1947 &mut residual,
1948 1usize << log2_tb_width,
1949 1usize << log2_tb_height,
1950 cu_qp,
1951 decode.bit_depth_luma,
1952 )?;
1953 }
1954 intra_reconstruct_cb(
1957 pic,
1958 x0,
1959 y0,
1960 log2_tb_width,
1961 log2_tb_height,
1962 intra_mode,
1963 0,
1964 &residual,
1965 )?;
1966 }
1967 TreeType::DualTreeChroma => {
1968 if chroma_present {
1969 let log2_c_w = log2_tb_width.saturating_sub(1);
1973 let log2_c_h = log2_tb_height.saturating_sub(1);
1974 let n_c = (1usize << log2_c_w) * (1usize << log2_c_h);
1975 let mut res_cb = vec![0i32; n_c];
1976 let mut res_cr = vec![0i32; n_c];
1977 if cbf_cb != 0 {
1978 let mut levels = vec![0i32; n_c];
1979 decode_residual_coding_rle(
1980 eng,
1981 &mut levels,
1982 &mut stats.coeff_runs,
1983 log2_c_w,
1984 log2_c_h,
1985 )?;
1986 scale_and_inverse_transform(
1987 &levels,
1988 &mut res_cb,
1989 1usize << log2_c_w,
1990 1usize << log2_c_h,
1991 cu_qp,
1992 decode.bit_depth_chroma,
1993 )?;
1994 }
1995 if cbf_cr != 0 {
1996 let mut levels = vec![0i32; n_c];
1997 decode_residual_coding_rle(
1998 eng,
1999 &mut levels,
2000 &mut stats.coeff_runs,
2001 log2_c_w,
2002 log2_c_h,
2003 )?;
2004 scale_and_inverse_transform(
2005 &levels,
2006 &mut res_cr,
2007 1usize << log2_c_w,
2008 1usize << log2_c_h,
2009 cu_qp,
2010 decode.bit_depth_chroma,
2011 )?;
2012 }
2013 if luma_cu_is_ibc {
2014 if cbf_cb != 0 {
2022 add_chroma_residual_to_block(
2023 pic,
2024 x0,
2025 y0,
2026 log2_tb_width,
2027 log2_tb_height,
2028 1,
2029 &res_cb,
2030 )?;
2031 }
2032 if cbf_cr != 0 {
2033 add_chroma_residual_to_block(
2034 pic,
2035 x0,
2036 y0,
2037 log2_tb_width,
2038 log2_tb_height,
2039 2,
2040 &res_cr,
2041 )?;
2042 }
2043 } else {
2044 intra_reconstruct_cb(
2045 pic,
2046 x0,
2047 y0,
2048 log2_tb_width,
2049 log2_tb_height,
2050 intra_mode,
2051 1,
2052 &res_cb,
2053 )?;
2054 intra_reconstruct_cb(
2055 pic,
2056 x0,
2057 y0,
2058 log2_tb_width,
2059 log2_tb_height,
2060 intra_mode,
2061 2,
2062 &res_cr,
2063 )?;
2064 }
2065 }
2066 }
2067 }
2068 Ok(())
2069}
2070
2071fn add_chroma_residual_to_block(
2076 pic: &mut YuvPicture,
2077 x_luma: u32,
2078 y_luma: u32,
2079 log2_cb_w_luma: u32,
2080 log2_cb_h_luma: u32,
2081 c_idx: u32,
2082 residual: &[i32],
2083) -> Result<()> {
2084 let (sub_w, sub_h) = match (pic.chroma_format_idc, c_idx) {
2085 (_, 0) => (1u32, 1u32),
2086 (1, _) => (2, 2),
2087 (2, _) => (2, 1),
2088 (3, _) => (1, 1),
2089 (n, _) => {
2090 return Err(Error::invalid(format!(
2091 "evc ibc decode: unsupported chroma_format_idc {n}"
2092 )))
2093 }
2094 };
2095 let x = x_luma / sub_w;
2096 let y = y_luma / sub_h;
2097 let n_cb_w = 1usize << (log2_cb_w_luma - sub_w.trailing_zeros());
2098 let n_cb_h = 1usize << (log2_cb_h_luma - sub_h.trailing_zeros());
2099 if residual.len() != n_cb_w * n_cb_h {
2100 return Err(Error::invalid(format!(
2101 "evc ibc decode: chroma residual len {} != {}*{}={}",
2102 residual.len(),
2103 n_cb_w,
2104 n_cb_h,
2105 n_cb_w * n_cb_h
2106 )));
2107 }
2108 let max_val = (1i32 << pic.bit_depth) - 1;
2109 let stride = pic.c_stride();
2110 let plane = match c_idx {
2111 1 => &mut pic.cb,
2112 2 => &mut pic.cr,
2113 _ => unreachable!(),
2114 };
2115 let (cw, ch) = match pic.chroma_format_idc {
2116 1 => (
2117 pic.width.div_ceil(2) as usize,
2118 pic.height.div_ceil(2) as usize,
2119 ),
2120 2 => (pic.width.div_ceil(2) as usize, pic.height as usize),
2121 3 => (pic.width as usize, pic.height as usize),
2122 _ => (0, 0),
2123 };
2124 for j in 0..n_cb_h {
2125 let yy = y as usize + j;
2126 if yy >= ch {
2127 break;
2128 }
2129 for i in 0..n_cb_w {
2130 let xx = x as usize + i;
2131 if xx >= cw {
2132 break;
2133 }
2134 let cur = plane[yy * stride + xx] as i32;
2135 let v = (cur + residual[j * n_cb_w + i]).clamp(0, max_val) as u8;
2136 plane[yy * stride + xx] = v;
2137 }
2138 }
2139 Ok(())
2140}
2141
2142#[cfg(test)]
2147use crate::inter::build_amvp_list_baseline;
2148use crate::inter::{
2149 average_bipred, derive_chroma_mv, interpolate_chroma_block, interpolate_luma_block,
2150 MotionVector, RefPictureView,
2151};
2152
2153#[derive(Clone, Copy, Debug)]
2162pub struct InterDecodeInputs<'a, 'b> {
2163 pub walk: SliceWalkInputs,
2164 pub decode: SliceDecodeInputs,
2165 pub slice_is_b: bool,
2168 pub num_ref_idx_active_minus1_l0: u32,
2172 pub num_ref_idx_active_minus1_l1: u32,
2174 pub ref_list_l0: &'b [RefPictureView<'a>],
2179 pub ref_list_l1: &'b [RefPictureView<'a>],
2183}
2184
2185impl<'a, 'b> InterDecodeInputs<'a, 'b> {
2186 pub fn ref_l0(&self, ref_idx: u32) -> Option<RefPictureView<'a>> {
2188 self.ref_list_l0.get(ref_idx as usize).copied()
2189 }
2190 pub fn ref_l1(&self, ref_idx: u32) -> Option<RefPictureView<'a>> {
2193 self.ref_list_l1.get(ref_idx as usize).copied()
2194 }
2195}
2196
2197#[derive(Clone, Debug, Default, PartialEq, Eq)]
2199pub struct InterDecodeStats {
2200 pub ctus: u32,
2201 pub split_cu_flag_bins: u32,
2202 pub coding_units: u32,
2203 pub cu_skip_flag_bins: u32,
2204 pub pred_mode_flag_bins: u32,
2205 pub inter_pred_idc_bins: u32,
2206 pub mvp_idx_bins: u32,
2207 pub abs_mvd_egk_bins: u32,
2208 pub mvd_sign_flag_bins: u32,
2209 pub ref_idx_bins: u32,
2210 pub cbf_luma_bins: u32,
2211 pub cbf_chroma_bins: u32,
2212 pub uni_pred_cus: u32,
2214 pub bi_pred_cus: u32,
2216 pub coeff_runs: u32,
2219 pub deblock_edges: u32,
2222 pub hmvp_cand_count_final: u32,
2227 pub ibc_flag_bins: u32,
2231 pub ibc_cus: u32,
2236 pub ibc_abs_mvd_bins: u32,
2239 pub ibc_mvd_sign_bins: u32,
2242 pub cu_qp_delta_abs_bins: u32,
2249 pub alf_ctb: AlfCtbStats,
2253 pub alf_ctb_map: crate::alf::AlfCtbMap,
2257}
2258
2259pub fn decode_baseline_inter_slice(
2274 rbsp: &[u8],
2275 inputs: InterDecodeInputs<'_, '_>,
2276) -> Result<(YuvPicture, InterDecodeStats)> {
2277 let walk = inputs.walk;
2278 let decode = inputs.decode;
2279 if walk.ctb_log2_size_y < 5 || walk.ctb_log2_size_y > 7 {
2280 return Err(Error::invalid(format!(
2281 "evc inter decode: CtbLog2SizeY {} out of Baseline range",
2282 walk.ctb_log2_size_y
2283 )));
2284 }
2285 if decode.bit_depth_luma != 8 || decode.bit_depth_chroma != 8 {
2286 return Err(Error::unsupported(
2287 "evc inter decode: round-4 is 8-bit only",
2288 ));
2289 }
2290 if inputs.ref_list_l0.is_empty() {
2293 return Err(Error::invalid(
2294 "evc inter decode: ref_list_l0 must hold at least one reference",
2295 ));
2296 }
2297 if (inputs.num_ref_idx_active_minus1_l0 as usize) >= inputs.ref_list_l0.len() {
2298 return Err(Error::invalid(format!(
2299 "evc inter decode: num_ref_idx_active_minus1_l0 {} but ref_list_l0 has {} entries",
2300 inputs.num_ref_idx_active_minus1_l0,
2301 inputs.ref_list_l0.len()
2302 )));
2303 }
2304 if inputs.slice_is_b {
2305 if inputs.ref_list_l1.is_empty() {
2306 return Err(Error::invalid(
2307 "evc inter decode: B slice requires at least one L1 reference",
2308 ));
2309 }
2310 if (inputs.num_ref_idx_active_minus1_l1 as usize) >= inputs.ref_list_l1.len() {
2311 return Err(Error::invalid(format!(
2312 "evc inter decode: num_ref_idx_active_minus1_l1 {} but ref_list_l1 has {} entries",
2313 inputs.num_ref_idx_active_minus1_l1,
2314 inputs.ref_list_l1.len()
2315 )));
2316 }
2317 }
2318 let mut pic = YuvPicture::new(
2319 walk.pic_width,
2320 walk.pic_height,
2321 walk.chroma_format_idc,
2322 decode.bit_depth_luma,
2323 )?;
2324 let mut eng = CabacEngine::new(rbsp)?;
2325 let mut stats = InterDecodeStats {
2326 alf_ctb_map: crate::alf::AlfCtbMap::new(
2327 walk.pic_width,
2328 walk.pic_height,
2329 walk.ctb_log2_size_y,
2330 ),
2331 ..Default::default()
2332 };
2333 let mut side_info = SideInfoGrid::new(walk.pic_width, walk.pic_height);
2334 let mut hmvp = crate::hmvp::HmvpCandList::new();
2339 let n_ctus = walk
2340 .pic_width_in_ctus()
2341 .checked_mul(walk.pic_height_in_ctus())
2342 .ok_or_else(|| Error::invalid("evc inter decode: ctu count overflow"))?;
2343 if n_ctus == 0 {
2344 return Err(Error::invalid("evc inter decode: no CTUs"));
2345 }
2346 for ctu_idx in 0..n_ctus {
2347 let x_ctb = (ctu_idx % walk.pic_width_in_ctus()) << walk.ctb_log2_size_y;
2348 let y_ctb = (ctu_idx / walk.pic_width_in_ctus()) << walk.ctb_log2_size_y;
2349 if x_ctb == 0 {
2352 hmvp.reset();
2353 }
2354 let alf = decode_coding_tree_unit_alf(&mut eng, &walk, &mut stats.alf_ctb)?;
2357 stats
2358 .alf_ctb_map
2359 .set(ctu_idx as usize, alf.luma, alf.chroma_cb, alf.chroma_cr);
2360 decode_inter_split_unit(
2361 &mut eng,
2362 &mut pic,
2363 &mut stats,
2364 &mut side_info,
2365 &mut hmvp,
2366 &inputs,
2367 x_ctb,
2368 y_ctb,
2369 walk.ctb_log2_size_y,
2370 walk.ctb_log2_size_y,
2371 )?;
2372 stats.ctus += 1;
2373 }
2374 let term = eng.decode_terminate()?;
2375 if !term {
2376 return Err(Error::invalid(
2377 "evc inter decode: end_of_tile_one_bit must terminate",
2378 ));
2379 }
2380 stats.hmvp_cand_count_final = hmvp.len() as u32;
2381 if decode.enable_deblock {
2382 let mut edges = crate::deblock::deblock_luma(&mut pic, &side_info, decode.slice_qp)?;
2383 if walk.chroma_format_idc != 0 {
2384 edges += crate::deblock::deblock_chroma(
2385 &mut pic,
2386 &side_info,
2387 decode.slice_qp,
2388 decode.slice_cb_qp_offset,
2389 1,
2390 )?;
2391 edges += crate::deblock::deblock_chroma(
2392 &mut pic,
2393 &side_info,
2394 decode.slice_qp,
2395 decode.slice_cr_qp_offset,
2396 2,
2397 )?;
2398 }
2399 stats.deblock_edges = edges;
2400 }
2401 Ok((pic, stats))
2402}
2403
2404#[allow(clippy::too_many_arguments)]
2405fn decode_inter_split_unit(
2406 eng: &mut CabacEngine,
2407 pic: &mut YuvPicture,
2408 stats: &mut InterDecodeStats,
2409 side_info: &mut SideInfoGrid,
2410 hmvp: &mut crate::hmvp::HmvpCandList,
2411 inputs: &InterDecodeInputs<'_, '_>,
2412 x0: u32,
2413 y0: u32,
2414 log2_cb_width: u32,
2415 log2_cb_height: u32,
2416) -> Result<()> {
2417 let walk = inputs.walk;
2418 let cb_w = 1u32 << log2_cb_width;
2419 let cb_h = 1u32 << log2_cb_height;
2420 let cb_within_picture = x0 + cb_w <= walk.pic_width && y0 + cb_h <= walk.pic_height;
2421 let can_recurse =
2422 log2_cb_width > walk.min_cb_log2_size_y && log2_cb_height > walk.min_cb_log2_size_y;
2423 let mut split = false;
2424 if can_recurse && cb_within_picture && (log2_cb_width > 2 || log2_cb_height > 2) {
2425 let bin = eng.decode_decision(0, 0)?;
2426 stats.split_cu_flag_bins += 1;
2427 split = bin != 0;
2428 } else if can_recurse && !cb_within_picture {
2429 split = true;
2430 }
2431 if split {
2432 let half_w = log2_cb_width.saturating_sub(1);
2433 let half_h = log2_cb_height.saturating_sub(1);
2434 let x1 = x0 + (1u32 << half_w);
2435 let y1 = y0 + (1u32 << half_h);
2436 decode_inter_split_unit(
2437 eng, pic, stats, side_info, hmvp, inputs, x0, y0, half_w, half_h,
2438 )?;
2439 if x1 < walk.pic_width {
2440 decode_inter_split_unit(
2441 eng, pic, stats, side_info, hmvp, inputs, x1, y0, half_w, half_h,
2442 )?;
2443 }
2444 if y1 < walk.pic_height {
2445 decode_inter_split_unit(
2446 eng, pic, stats, side_info, hmvp, inputs, x0, y1, half_w, half_h,
2447 )?;
2448 }
2449 if x1 < walk.pic_width && y1 < walk.pic_height {
2450 decode_inter_split_unit(
2451 eng, pic, stats, side_info, hmvp, inputs, x1, y1, half_w, half_h,
2452 )?;
2453 }
2454 return Ok(());
2455 }
2456 decode_inter_coding_unit(
2457 eng,
2458 pic,
2459 stats,
2460 side_info,
2461 hmvp,
2462 inputs,
2463 x0,
2464 y0,
2465 log2_cb_width,
2466 log2_cb_height,
2467 )
2468}
2469
2470#[allow(clippy::too_many_arguments)]
2471fn decode_inter_coding_unit(
2472 eng: &mut CabacEngine,
2473 pic: &mut YuvPicture,
2474 stats: &mut InterDecodeStats,
2475 side_info: &mut SideInfoGrid,
2476 hmvp: &mut crate::hmvp::HmvpCandList,
2477 inputs: &InterDecodeInputs<'_, '_>,
2478 x0: u32,
2479 y0: u32,
2480 log2_cb_width: u32,
2481 log2_cb_height: u32,
2482) -> Result<()> {
2483 stats.coding_units += 1;
2484 let walk = inputs.walk;
2485 let n_cb_w = 1u32 << log2_cb_width;
2486 let n_cb_h = 1u32 << log2_cb_height;
2487 let cu_skip = eng.decode_decision(0, 0)? != 0;
2489 stats.cu_skip_flag_bins += 1;
2490 let pred_l0;
2491 let pred_l1;
2492 if cu_skip {
2493 let mvp_idx_l0 = eng.decode_tr_regular(3, 0, 0, |_| 0)?;
2497 stats.mvp_idx_bins += 1;
2498 let mut mvp_idx_l1 = 0u32;
2499 if inputs.slice_is_b {
2500 mvp_idx_l1 = eng.decode_tr_regular(3, 0, 0, |_| 0)?;
2501 stats.mvp_idx_bins += 1;
2502 }
2503 let mv_l0 = baseline_amvp_select_with_grid_and_hmvp(
2510 mvp_idx_l0,
2511 side_info,
2512 hmvp,
2513 x0 as i32,
2514 y0 as i32,
2515 n_cb_w as i32,
2516 n_cb_h as i32,
2517 0,
2518 0,
2519 );
2520 let mv_l1 = if inputs.slice_is_b {
2521 Some(baseline_amvp_select_with_grid_and_hmvp(
2522 mvp_idx_l1,
2523 side_info,
2524 hmvp,
2525 x0 as i32,
2526 y0 as i32,
2527 n_cb_w as i32,
2528 n_cb_h as i32,
2529 0,
2530 1,
2531 ))
2532 } else {
2533 None
2534 };
2535 pred_l0 = Some((mv_l0, 0u32));
2536 pred_l1 = mv_l1.map(|mv| (mv, 0u32));
2537 } else {
2538 let pred_mode_flag = eng.decode_decision(0, 0)?;
2541 stats.pred_mode_flag_bins += 1;
2542 let ibc_allowed = crate::ibc::is_ibc_allowed_for_size(
2551 inputs.decode.sps_ibc_flag,
2552 inputs.decode.log2_max_ibc_cand_size,
2553 log2_cb_width,
2554 log2_cb_height,
2555 );
2556 if ibc_allowed {
2557 let ibc_bin = eng.decode_decision(0, 0)?;
2558 stats.ibc_flag_bins += 1;
2559 if ibc_bin != 0 {
2560 stats.ibc_cus += 1;
2561 let mvd_x = decode_signed_mvd(
2565 eng,
2566 &mut stats.ibc_abs_mvd_bins,
2567 &mut stats.ibc_mvd_sign_bins,
2568 )?;
2569 let mvd_y = decode_signed_mvd(
2570 eng,
2571 &mut stats.ibc_abs_mvd_bins,
2572 &mut stats.ibc_mvd_sign_bins,
2573 )?;
2574 return decode_inter_ibc_branch(
2575 eng,
2576 pic,
2577 stats,
2578 side_info,
2579 hmvp,
2580 inputs,
2581 x0,
2582 y0,
2583 log2_cb_width,
2584 log2_cb_height,
2585 MotionVector { x: mvd_x, y: mvd_y },
2586 );
2587 }
2588 }
2589 if pred_mode_flag != 0 {
2590 return decode_inter_intra_cu(
2592 eng,
2593 pic,
2594 stats,
2595 side_info,
2596 walk,
2597 inputs.decode,
2598 x0,
2599 y0,
2600 log2_cb_width,
2601 log2_cb_height,
2602 );
2603 }
2604 let mut inter_pred_idc = 0u32; if inputs.slice_is_b {
2607 inter_pred_idc = eng.decode_tr_regular(2, 0, 0, |_| 0)?;
2609 stats.inter_pred_idc_bins += 1;
2610 }
2611 let use_l0 = inter_pred_idc != 1;
2613 let use_l1 = inputs.slice_is_b && inter_pred_idc != 0;
2614 let mut mvl0 = MotionVector::default();
2615 let mut mvl1 = MotionVector::default();
2616 let mut ref_idx_l0 = 0u32;
2617 let mut ref_idx_l1 = 0u32;
2618 if use_l0 {
2619 if inputs.num_ref_idx_active_minus1_l0 > 0 {
2620 ref_idx_l0 =
2621 eng.decode_tr_regular(inputs.num_ref_idx_active_minus1_l0, 0, 0, |_| 0)?;
2622 stats.ref_idx_bins += 1;
2623 }
2624 let mvp_idx = eng.decode_tr_regular(3, 0, 0, |_| 0)?;
2625 stats.mvp_idx_bins += 1;
2626 let mvd_x = decode_signed_mvd(
2627 eng,
2628 &mut stats.abs_mvd_egk_bins,
2629 &mut stats.mvd_sign_flag_bins,
2630 )?;
2631 let mvd_y = decode_signed_mvd(
2632 eng,
2633 &mut stats.abs_mvd_egk_bins,
2634 &mut stats.mvd_sign_flag_bins,
2635 )?;
2636 let mvp = baseline_amvp_select_with_grid_and_hmvp(
2637 mvp_idx,
2638 side_info,
2639 hmvp,
2640 x0 as i32,
2641 y0 as i32,
2642 n_cb_w as i32,
2643 n_cb_h as i32,
2644 ref_idx_l0 as i8,
2645 0,
2646 );
2647 mvl0 = mvp.wrapping_add(&MotionVector::quarter_pel(mvd_x, mvd_y));
2648 }
2649 if use_l1 {
2650 if inputs.num_ref_idx_active_minus1_l1 > 0 {
2651 ref_idx_l1 =
2652 eng.decode_tr_regular(inputs.num_ref_idx_active_minus1_l1, 0, 0, |_| 0)?;
2653 stats.ref_idx_bins += 1;
2654 }
2655 let mvp_idx = eng.decode_tr_regular(3, 0, 0, |_| 0)?;
2656 stats.mvp_idx_bins += 1;
2657 let mvd_x = decode_signed_mvd(
2658 eng,
2659 &mut stats.abs_mvd_egk_bins,
2660 &mut stats.mvd_sign_flag_bins,
2661 )?;
2662 let mvd_y = decode_signed_mvd(
2663 eng,
2664 &mut stats.abs_mvd_egk_bins,
2665 &mut stats.mvd_sign_flag_bins,
2666 )?;
2667 let mvp = baseline_amvp_select_with_grid_and_hmvp(
2668 mvp_idx,
2669 side_info,
2670 hmvp,
2671 x0 as i32,
2672 y0 as i32,
2673 n_cb_w as i32,
2674 n_cb_h as i32,
2675 ref_idx_l1 as i8,
2676 1,
2677 );
2678 mvl1 = mvp.wrapping_add(&MotionVector::quarter_pel(mvd_x, mvd_y));
2679 }
2680 pred_l0 = if use_l0 {
2681 Some((mvl0, ref_idx_l0))
2682 } else {
2683 None
2684 };
2685 pred_l1 = if use_l1 {
2686 Some((mvl1, ref_idx_l1))
2687 } else {
2688 None
2689 };
2690 }
2691 let chroma_present = walk.chroma_format_idc != 0;
2696 let cbf_luma = eng.decode_decision(0, 0)?;
2697 stats.cbf_luma_bins += 1;
2698 let mut cbf_cb = 0u8;
2699 let mut cbf_cr = 0u8;
2700 if chroma_present {
2701 cbf_cb = eng.decode_decision(0, 0)?;
2702 cbf_cr = eng.decode_decision(0, 0)?;
2703 stats.cbf_chroma_bins += 2;
2704 }
2705 let mut qp_delta: i32 = 0;
2716 if walk.cu_qp_delta_enabled && (cbf_luma != 0 || cbf_cb != 0 || cbf_cr != 0) {
2717 let qp_delta_abs = eng.decode_u_regular(0, |_| 0)?;
2718 stats.cu_qp_delta_abs_bins += 1;
2719 if qp_delta_abs > 0 {
2720 let sign = eng.decode_bypass()?;
2721 qp_delta = if sign != 0 {
2722 -(qp_delta_abs as i32)
2723 } else {
2724 qp_delta_abs as i32
2725 };
2726 }
2727 }
2728 let cu_qp = (inputs.decode.slice_qp + qp_delta).clamp(0, 51);
2729 side_info.stamp_block(
2732 x0,
2733 y0,
2734 n_cb_w,
2735 n_cb_h,
2736 CuSideInfo {
2737 pred_mode: CuPredMode::Inter,
2738 cbf_luma,
2739 mv_l0_x: pred_l0.map(|(m, _)| m.x).unwrap_or(0),
2740 mv_l0_y: pred_l0.map(|(m, _)| m.y).unwrap_or(0),
2741 mv_l1_x: pred_l1.map(|(m, _)| m.x).unwrap_or(0),
2742 mv_l1_y: pred_l1.map(|(m, _)| m.y).unwrap_or(0),
2743 ref_idx_l0: pred_l0.map(|(_, r)| r as i8).unwrap_or(-1),
2744 ref_idx_l1: pred_l1.map(|(_, r)| r as i8).unwrap_or(-1),
2745 },
2746 );
2747 let cand = crate::hmvp::HmvpCandidate {
2752 mv_l0: pred_l0.map(|(m, _)| m).unwrap_or_default(),
2753 mv_l1: pred_l1.map(|(m, _)| m).unwrap_or_default(),
2754 ref_idx_l0: pred_l0.map(|(_, r)| r as i8).unwrap_or(-1),
2755 ref_idx_l1: pred_l1.map(|(_, r)| r as i8).unwrap_or(-1),
2756 };
2757 hmvp.update(cand);
2758 let log2_tb_w = log2_cb_width.min(walk.max_tb_log2_size_y);
2760 let log2_tb_h = log2_cb_height.min(walk.max_tb_log2_size_y);
2761 let n_y = (1usize << log2_tb_w) * (1usize << log2_tb_h);
2762 let mut residual_y_vec: Vec<i32> = Vec::new();
2763 if cbf_luma != 0 {
2764 let mut levels = vec![0i32; n_y];
2765 decode_residual_coding_rle(
2766 eng,
2767 &mut levels,
2768 &mut stats.coeff_runs,
2769 log2_tb_w,
2770 log2_tb_h,
2771 )?;
2772 let mut res = vec![0i32; n_y];
2773 scale_and_inverse_transform(
2774 &levels,
2775 &mut res,
2776 1usize << log2_tb_w,
2777 1usize << log2_tb_h,
2778 cu_qp,
2779 inputs.decode.bit_depth_luma,
2780 )?;
2781 residual_y_vec = res;
2782 }
2783 let (log2_c_w, log2_c_h) = if chroma_present {
2784 (log2_tb_w.saturating_sub(1), log2_tb_h.saturating_sub(1))
2785 } else {
2786 (0, 0)
2787 };
2788 let n_c = (1usize << log2_c_w) * (1usize << log2_c_h);
2789 let mut residual_cb_vec: Vec<i32> = Vec::new();
2790 let mut residual_cr_vec: Vec<i32> = Vec::new();
2791 if chroma_present && cbf_cb != 0 {
2792 let mut levels = vec![0i32; n_c];
2793 decode_residual_coding_rle(eng, &mut levels, &mut stats.coeff_runs, log2_c_w, log2_c_h)?;
2794 let mut res = vec![0i32; n_c];
2795 scale_and_inverse_transform(
2796 &levels,
2797 &mut res,
2798 1usize << log2_c_w,
2799 1usize << log2_c_h,
2800 cu_qp,
2801 inputs.decode.bit_depth_chroma,
2802 )?;
2803 residual_cb_vec = res;
2804 }
2805 if chroma_present && cbf_cr != 0 {
2806 let mut levels = vec![0i32; n_c];
2807 decode_residual_coding_rle(eng, &mut levels, &mut stats.coeff_runs, log2_c_w, log2_c_h)?;
2808 let mut res = vec![0i32; n_c];
2809 scale_and_inverse_transform(
2810 &levels,
2811 &mut res,
2812 1usize << log2_c_w,
2813 1usize << log2_c_h,
2814 cu_qp,
2815 inputs.decode.bit_depth_chroma,
2816 )?;
2817 residual_cr_vec = res;
2818 }
2819 let bipred = pred_l0.is_some() && pred_l1.is_some();
2821 if bipred {
2822 stats.bi_pred_cus += 1;
2823 } else {
2824 stats.uni_pred_cus += 1;
2825 }
2826 apply_inter_prediction(
2827 pic,
2828 inputs,
2829 x0,
2830 y0,
2831 n_cb_w as usize,
2832 n_cb_h as usize,
2833 pred_l0,
2834 pred_l1,
2835 &residual_y_vec,
2836 &residual_cb_vec,
2837 &residual_cr_vec,
2838 )
2839}
2840
2841#[cfg(test)]
2856fn baseline_amvp_select_with_hmvp(
2857 mvp_idx: u32,
2858 hmvp: &crate::hmvp::HmvpCandList,
2859 cur_ref_idx_lx: i8,
2860 list_x: u8,
2861) -> MotionVector {
2862 let list = build_amvp_list_baseline(0, 0, 0, 0, |_, _| None, MotionVector::default());
2863 let chosen = list[mvp_idx.min(3) as usize].0;
2864 let unavailable = MotionVector::quarter_pel(1, 1);
2865 if chosen == unavailable && !hmvp.is_empty() {
2866 if let Some((mv, _)) = hmvp.derive_default_mv(cur_ref_idx_lx, list_x) {
2867 return mv;
2868 }
2869 }
2870 chosen
2871}
2872
2873fn spatial_neighbour_mv(
2880 side_info: &SideInfoGrid,
2881 x: i32,
2882 y: i32,
2883 cur_ref_idx_lx: i8,
2884 list_x: u8,
2885) -> Option<MotionVector> {
2886 if x < 0 || y < 0 {
2887 return None;
2888 }
2889 let x_cell = (x as u32) >> 2;
2890 let y_cell = (y as u32) >> 2;
2891 if (x_cell as usize) >= side_info.w_cells || (y_cell as usize) >= side_info.h_cells {
2892 return None;
2893 }
2894 let info = side_info.at(x_cell as usize, y_cell as usize);
2895 if info.pred_mode != CuPredMode::Inter {
2896 return None;
2897 }
2898 let (ref_idx, mv_x, mv_y) = if list_x == 0 {
2899 (info.ref_idx_l0, info.mv_l0_x, info.mv_l0_y)
2900 } else {
2901 (info.ref_idx_l1, info.mv_l1_x, info.mv_l1_y)
2902 };
2903 if ref_idx < 0 || ref_idx != cur_ref_idx_lx {
2904 return None;
2905 }
2906 Some(MotionVector::quarter_pel(mv_x, mv_y))
2907}
2908
2909#[allow(clippy::too_many_arguments)]
2931fn baseline_amvp_select_with_grid_and_hmvp(
2932 mvp_idx: u32,
2933 side_info: &SideInfoGrid,
2934 hmvp: &crate::hmvp::HmvpCandList,
2935 x_cb: i32,
2936 y_cb: i32,
2937 n_cb_w: i32,
2938 n_cb_h: i32,
2939 cur_ref_idx_lx: i8,
2940 list_x: u8,
2941) -> MotionVector {
2942 let unavailable = MotionVector::quarter_pel(1, 1);
2943 let nb_left = spatial_neighbour_mv(
2944 side_info,
2945 x_cb - 1,
2946 y_cb + n_cb_h - 1,
2947 cur_ref_idx_lx,
2948 list_x,
2949 );
2950 let nb_above = spatial_neighbour_mv(
2951 side_info,
2952 x_cb + n_cb_w - 1,
2953 y_cb - 1,
2954 cur_ref_idx_lx,
2955 list_x,
2956 );
2957 let nb_above_right =
2958 spatial_neighbour_mv(side_info, x_cb + n_cb_w, y_cb - 1, cur_ref_idx_lx, list_x);
2959 let list = [
2960 nb_left.unwrap_or(unavailable),
2961 nb_above.unwrap_or(unavailable),
2962 nb_above_right.unwrap_or(unavailable),
2963 MotionVector::default(), ];
2965 let chosen = list[mvp_idx.min(3) as usize];
2966 if chosen == unavailable && !hmvp.is_empty() {
2967 if let Some((mv, _)) = hmvp.derive_default_mv(cur_ref_idx_lx, list_x) {
2968 return mv;
2969 }
2970 }
2971 chosen
2972}
2973
2974fn decode_signed_mvd(
2975 eng: &mut CabacEngine,
2976 abs_count: &mut u32,
2977 sign_count: &mut u32,
2978) -> Result<i32> {
2979 let abs = eng.decode_egk_bypass(0)?;
2980 *abs_count += 1;
2981 if abs == 0 {
2982 return Ok(0);
2983 }
2984 let sign = eng.decode_bypass()?;
2985 *sign_count += 1;
2986 Ok(if sign != 0 { -(abs as i32) } else { abs as i32 })
2987}
2988
2989#[allow(clippy::too_many_arguments)]
2990fn decode_inter_intra_cu(
2991 eng: &mut CabacEngine,
2992 pic: &mut YuvPicture,
2993 stats: &mut InterDecodeStats,
2994 side_info: &mut SideInfoGrid,
2995 walk: SliceWalkInputs,
2996 decode: SliceDecodeInputs,
2997 x0: u32,
2998 y0: u32,
2999 log2_cb_width: u32,
3000 log2_cb_height: u32,
3001) -> Result<()> {
3002 use crate::intra::IntraMode;
3003 use crate::picture::intra_reconstruct_cb;
3004 let intra_idx = eng.decode_u_regular(0, |_| 0)?;
3005 let intra_mode = IntraMode::from_baseline_idx(intra_idx).ok_or_else(|| {
3006 Error::invalid(format!(
3007 "evc inter decode: intra_pred_mode {intra_idx} out of range"
3008 ))
3009 })?;
3010 let log2_tb_w = log2_cb_width.min(walk.max_tb_log2_size_y);
3011 let log2_tb_h = log2_cb_height.min(walk.max_tb_log2_size_y);
3012 let chroma_present = walk.chroma_format_idc != 0;
3013 let cbf_luma = eng.decode_decision(0, 0)?;
3014 stats.cbf_luma_bins += 1;
3015 let mut cbf_cb = 0u8;
3016 let mut cbf_cr = 0u8;
3017 if chroma_present {
3018 cbf_cb = eng.decode_decision(0, 0)?;
3019 cbf_cr = eng.decode_decision(0, 0)?;
3020 stats.cbf_chroma_bins += 2;
3021 }
3022 let cu_qp = decode.slice_qp.clamp(0, 51);
3023 side_info.stamp_block(
3025 x0,
3026 y0,
3027 1u32 << log2_cb_width,
3028 1u32 << log2_cb_height,
3029 CuSideInfo {
3030 pred_mode: CuPredMode::Intra,
3031 cbf_luma,
3032 ..Default::default()
3033 },
3034 );
3035 let n = (1usize << log2_tb_w) * (1usize << log2_tb_h);
3036 let mut residual = vec![0i32; n];
3037 if cbf_luma != 0 {
3038 let mut levels = vec![0i32; n];
3039 decode_residual_coding_rle(
3040 eng,
3041 &mut levels,
3042 &mut stats.coeff_runs,
3043 log2_tb_w,
3044 log2_tb_h,
3045 )?;
3046 scale_and_inverse_transform(
3047 &levels,
3048 &mut residual,
3049 1usize << log2_tb_w,
3050 1usize << log2_tb_h,
3051 cu_qp,
3052 decode.bit_depth_luma,
3053 )?;
3054 }
3055 intra_reconstruct_cb(pic, x0, y0, log2_tb_w, log2_tb_h, intra_mode, 0, &residual)?;
3056 if chroma_present {
3057 let log2_c_w = log2_tb_w.saturating_sub(1);
3058 let log2_c_h = log2_tb_h.saturating_sub(1);
3059 let n_c = (1usize << log2_c_w) * (1usize << log2_c_h);
3060 let mut res_cb = vec![0i32; n_c];
3061 let mut res_cr = vec![0i32; n_c];
3062 if cbf_cb != 0 {
3063 let mut levels = vec![0i32; n_c];
3064 decode_residual_coding_rle(
3065 eng,
3066 &mut levels,
3067 &mut stats.coeff_runs,
3068 log2_c_w,
3069 log2_c_h,
3070 )?;
3071 scale_and_inverse_transform(
3072 &levels,
3073 &mut res_cb,
3074 1usize << log2_c_w,
3075 1usize << log2_c_h,
3076 cu_qp,
3077 decode.bit_depth_chroma,
3078 )?;
3079 }
3080 if cbf_cr != 0 {
3081 let mut levels = vec![0i32; n_c];
3082 decode_residual_coding_rle(
3083 eng,
3084 &mut levels,
3085 &mut stats.coeff_runs,
3086 log2_c_w,
3087 log2_c_h,
3088 )?;
3089 scale_and_inverse_transform(
3090 &levels,
3091 &mut res_cr,
3092 1usize << log2_c_w,
3093 1usize << log2_c_h,
3094 cu_qp,
3095 decode.bit_depth_chroma,
3096 )?;
3097 }
3098 intra_reconstruct_cb(pic, x0, y0, log2_tb_w, log2_tb_h, intra_mode, 1, &res_cb)?;
3099 intra_reconstruct_cb(pic, x0, y0, log2_tb_w, log2_tb_h, intra_mode, 2, &res_cr)?;
3100 }
3101 Ok(())
3102}
3103
3104#[allow(clippy::too_many_arguments)]
3123fn decode_inter_ibc_branch(
3124 eng: &mut CabacEngine,
3125 pic: &mut YuvPicture,
3126 stats: &mut InterDecodeStats,
3127 side_info: &mut SideInfoGrid,
3128 hmvp: &mut crate::hmvp::HmvpCandList,
3129 inputs: &InterDecodeInputs<'_, '_>,
3130 x0: u32,
3131 y0: u32,
3132 log2_cb_width: u32,
3133 log2_cb_height: u32,
3134 mvd: MotionVector,
3135) -> Result<()> {
3136 let walk = inputs.walk;
3137 let decode = inputs.decode;
3138 let log2_tb_width = log2_cb_width.min(walk.max_tb_log2_size_y);
3139 let log2_tb_height = log2_cb_height.min(walk.max_tb_log2_size_y);
3140 if log2_tb_width != log2_cb_width || log2_tb_height != log2_cb_height {
3141 return Err(Error::unsupported(
3142 "evc inter ibc decode: round-95 requires log2_cb == log2_tb (CB ≤ MaxTb)",
3143 ));
3144 }
3145 let chroma_present = walk.chroma_format_idc != 0;
3146 let cbf_luma = eng.decode_decision(0, 0)?;
3155 stats.cbf_luma_bins += 1;
3156 let mut cbf_cb = 0u8;
3157 let mut cbf_cr = 0u8;
3158 if chroma_present {
3159 cbf_cb = eng.decode_decision(0, 0)?;
3160 cbf_cr = eng.decode_decision(0, 0)?;
3161 stats.cbf_chroma_bins += 2;
3162 }
3163 let mut qp_delta: i32 = 0;
3174 if walk.cu_qp_delta_enabled && (cbf_luma != 0 || cbf_cb != 0 || cbf_cr != 0) {
3175 let qp_delta_abs = eng.decode_u_regular(0, |_| 0)?;
3176 stats.cu_qp_delta_abs_bins += 1;
3177 if qp_delta_abs > 0 {
3178 let sign = eng.decode_bypass()?;
3179 qp_delta = if sign != 0 {
3180 -(qp_delta_abs as i32)
3181 } else {
3182 qp_delta_abs as i32
3183 };
3184 }
3185 }
3186 let cu_qp = (decode.slice_qp + qp_delta).clamp(0, 51);
3187 let n_tb_y = (1usize << log2_tb_width) * (1usize << log2_tb_height);
3189 let mut residual_levels_y = vec![0i32; n_tb_y];
3190 if cbf_luma != 0 {
3191 decode_residual_coding_rle(
3192 eng,
3193 &mut residual_levels_y,
3194 &mut stats.coeff_runs,
3195 log2_tb_width,
3196 log2_tb_height,
3197 )?;
3198 }
3199 let (log2_c_w, log2_c_h) = if chroma_present {
3200 (
3201 log2_tb_width.saturating_sub(1),
3202 log2_tb_height.saturating_sub(1),
3203 )
3204 } else {
3205 (0, 0)
3206 };
3207 let n_tb_c = (1usize << log2_c_w) * (1usize << log2_c_h);
3208 let mut residual_levels_cb = vec![0i32; n_tb_c];
3209 let mut residual_levels_cr = vec![0i32; n_tb_c];
3210 if chroma_present && cbf_cb != 0 {
3211 decode_residual_coding_rle(
3212 eng,
3213 &mut residual_levels_cb,
3214 &mut stats.coeff_runs,
3215 log2_c_w,
3216 log2_c_h,
3217 )?;
3218 }
3219 if chroma_present && cbf_cr != 0 {
3220 decode_residual_coding_rle(
3221 eng,
3222 &mut residual_levels_cr,
3223 &mut stats.coeff_runs,
3224 log2_c_w,
3225 log2_c_h,
3226 )?;
3227 }
3228 apply_inter_ibc_branch_predict_and_reconstruct(
3229 pic,
3230 side_info,
3231 hmvp,
3232 &walk,
3233 &decode,
3234 x0,
3235 y0,
3236 log2_cb_width,
3237 log2_cb_height,
3238 mvd,
3239 cbf_luma,
3240 &residual_levels_y,
3241 cbf_cb,
3242 &residual_levels_cb,
3243 cbf_cr,
3244 &residual_levels_cr,
3245 cu_qp,
3246 )
3247}
3248
3249#[allow(clippy::too_many_arguments)]
3267fn apply_inter_ibc_branch_predict_and_reconstruct(
3268 pic: &mut YuvPicture,
3269 side_info: &mut SideInfoGrid,
3270 hmvp: &mut crate::hmvp::HmvpCandList,
3271 walk: &SliceWalkInputs,
3272 decode: &SliceDecodeInputs,
3273 x0: u32,
3274 y0: u32,
3275 log2_cb_width: u32,
3276 log2_cb_height: u32,
3277 mvd: MotionVector,
3278 cbf_luma: u8,
3279 residual_levels_y: &[i32],
3280 cbf_cb: u8,
3281 residual_levels_cb: &[i32],
3282 cbf_cr: u8,
3283 residual_levels_cr: &[i32],
3284 cu_qp: i32,
3285) -> Result<()> {
3286 let chroma_present = walk.chroma_format_idc != 0;
3287 let n_cb_w_l = 1usize << log2_cb_width;
3288 let n_cb_h_l = 1usize << log2_cb_height;
3289 let n_l = n_cb_w_l * n_cb_h_l;
3290 let (n_c_w, n_c_h) = if chroma_present {
3291 match pic.chroma_format_idc {
3292 1 => (n_cb_w_l / 2, n_cb_h_l / 2),
3293 2 => (n_cb_w_l / 2, n_cb_h_l),
3294 3 => (n_cb_w_l, n_cb_h_l),
3295 _ => (0, 0),
3296 }
3297 } else {
3298 (0, 0)
3299 };
3300 let n_c = n_c_w * n_c_h;
3301 let mut pred_y = vec![0i32; n_l];
3302 let mut pred_cb = vec![0i32; n_c];
3303 let mut pred_cr = vec![0i32; n_c];
3304 let (mv_l, _mv_c) = crate::ibc::decode_ibc_cu(
3305 pic,
3306 x0 as i32,
3307 y0 as i32,
3308 n_cb_w_l,
3309 n_cb_h_l,
3310 mvd,
3311 walk.ctb_log2_size_y,
3312 chroma_present,
3313 &mut pred_y,
3314 &mut pred_cb,
3315 &mut pred_cr,
3316 )?;
3317 let mut residual_y = vec![0i32; n_l];
3321 if cbf_luma != 0 {
3322 if residual_levels_y.len() != n_l {
3323 return Err(Error::invalid(format!(
3324 "evc inter ibc apply: residual_levels_y len {} != {n_l}",
3325 residual_levels_y.len()
3326 )));
3327 }
3328 scale_and_inverse_transform(
3329 residual_levels_y,
3330 &mut residual_y,
3331 n_cb_w_l,
3332 n_cb_h_l,
3333 cu_qp,
3334 decode.bit_depth_luma,
3335 )?;
3336 }
3337 for (p, r) in pred_y.iter_mut().zip(residual_y.iter()) {
3338 *p += *r;
3339 }
3340 pic.store_block(x0, y0, n_cb_w_l, n_cb_h_l, 0, &pred_y);
3341 if chroma_present {
3342 let mut residual_cb = vec![0i32; n_c];
3343 let mut residual_cr = vec![0i32; n_c];
3344 if cbf_cb != 0 {
3345 if residual_levels_cb.len() != n_c {
3346 return Err(Error::invalid(format!(
3347 "evc inter ibc apply: residual_levels_cb len {} != {n_c}",
3348 residual_levels_cb.len()
3349 )));
3350 }
3351 scale_and_inverse_transform(
3352 residual_levels_cb,
3353 &mut residual_cb,
3354 n_c_w,
3355 n_c_h,
3356 cu_qp,
3357 decode.bit_depth_chroma,
3358 )?;
3359 }
3360 if cbf_cr != 0 {
3361 if residual_levels_cr.len() != n_c {
3362 return Err(Error::invalid(format!(
3363 "evc inter ibc apply: residual_levels_cr len {} != {n_c}",
3364 residual_levels_cr.len()
3365 )));
3366 }
3367 scale_and_inverse_transform(
3368 residual_levels_cr,
3369 &mut residual_cr,
3370 n_c_w,
3371 n_c_h,
3372 cu_qp,
3373 decode.bit_depth_chroma,
3374 )?;
3375 }
3376 for (p, r) in pred_cb.iter_mut().zip(residual_cb.iter()) {
3377 *p += *r;
3378 }
3379 for (p, r) in pred_cr.iter_mut().zip(residual_cr.iter()) {
3380 *p += *r;
3381 }
3382 let (sub_w, sub_h) = match pic.chroma_format_idc {
3388 1 => (2u32, 2u32),
3389 2 => (2u32, 1u32),
3390 3 => (1u32, 1u32),
3391 _ => (1u32, 1u32),
3392 };
3393 let x_c = x0 / sub_w;
3394 let y_c = y0 / sub_h;
3395 pic.store_block(x_c, y_c, n_c_w, n_c_h, 1, &pred_cb);
3396 pic.store_block(x_c, y_c, n_c_w, n_c_h, 2, &pred_cr);
3397 }
3398 side_info.stamp_block(
3403 x0,
3404 y0,
3405 1u32 << log2_cb_width,
3406 1u32 << log2_cb_height,
3407 CuSideInfo {
3408 pred_mode: CuPredMode::Ibc,
3409 cbf_luma,
3410 mv_l0_x: mv_l.x,
3411 mv_l0_y: mv_l.y,
3412 ..Default::default()
3413 },
3414 );
3415 let _ = hmvp; Ok(())
3426}
3427
3428#[allow(clippy::too_many_arguments)]
3436fn apply_inter_prediction(
3437 pic: &mut YuvPicture,
3438 inputs: &InterDecodeInputs<'_, '_>,
3439 x0: u32,
3440 y0: u32,
3441 n_cb_w: usize,
3442 n_cb_h: usize,
3443 pred_l0: Option<(MotionVector, u32)>,
3444 pred_l1: Option<(MotionVector, u32)>,
3445 residual_y: &[i32],
3446 residual_cb: &[i32],
3447 residual_cr: &[i32],
3448) -> Result<()> {
3449 let bit_depth = inputs.decode.bit_depth_luma;
3450 let mut buf_l0 = vec![0i32; n_cb_w * n_cb_h];
3451 let mut buf_l1 = vec![0i32; n_cb_w * n_cb_h];
3452 let ref_l0_resolved = match pred_l0 {
3453 Some((_, idx)) => inputs.ref_l0(idx).ok_or_else(|| {
3454 Error::invalid(format!(
3455 "evc inter decode: ref_idx_l0 {idx} out of range (list has {} entries)",
3456 inputs.ref_list_l0.len()
3457 ))
3458 })?,
3459 None => inputs.ref_list_l0[0],
3460 };
3461 let ref_l1_resolved = match pred_l1 {
3462 Some((_, idx)) => Some(inputs.ref_l1(idx).ok_or_else(|| {
3463 Error::invalid(format!(
3464 "evc inter decode: ref_idx_l1 {idx} out of range (list has {} entries)",
3465 inputs.ref_list_l1.len()
3466 ))
3467 })?),
3468 None => None,
3469 };
3470 if let Some((mv, _ref_idx)) = pred_l0 {
3471 let mv16 = mv.quarter_to_sixteenth();
3472 interpolate_luma_block(
3473 ref_l0_resolved,
3474 x0 as i32,
3475 y0 as i32,
3476 mv16,
3477 n_cb_w,
3478 n_cb_h,
3479 bit_depth,
3480 &mut buf_l0,
3481 )?;
3482 }
3483 if let Some((mv, _ref_idx)) = pred_l1 {
3484 let refp = ref_l1_resolved.expect("L1 ref is required for B inter CU");
3485 let mv16 = mv.quarter_to_sixteenth();
3486 interpolate_luma_block(
3487 refp,
3488 x0 as i32,
3489 y0 as i32,
3490 mv16,
3491 n_cb_w,
3492 n_cb_h,
3493 bit_depth,
3494 &mut buf_l1,
3495 )?;
3496 }
3497 let n = n_cb_w * n_cb_h;
3498 let mut combined = vec![0i32; n];
3499 match (pred_l0.is_some(), pred_l1.is_some()) {
3500 (true, false) => combined.copy_from_slice(&buf_l0),
3501 (false, true) => combined.copy_from_slice(&buf_l1),
3502 (true, true) => average_bipred(&buf_l0, &buf_l1, &mut combined),
3503 (false, false) => return Err(Error::invalid("evc inter decode: CU has no active list")),
3504 }
3505 if !residual_y.is_empty() {
3506 if residual_y.len() != n {
3507 return Err(Error::invalid(format!(
3508 "evc inter decode: luma residual len {} != {}",
3509 residual_y.len(),
3510 n
3511 )));
3512 }
3513 for (a, b) in combined.iter_mut().zip(residual_y.iter()) {
3514 *a += *b;
3515 }
3516 }
3517 pic.store_block(x0, y0, n_cb_w, n_cb_h, 0, &combined);
3518 if inputs.walk.chroma_format_idc != 0 {
3519 let (sub_w, sub_h) = match inputs.walk.chroma_format_idc {
3520 1 => (2u32, 2u32),
3521 2 => (2u32, 1u32),
3522 3 => (1u32, 1u32),
3523 _ => (1u32, 1u32),
3524 };
3525 let cw = n_cb_w / sub_w as usize;
3526 let ch = n_cb_h / sub_h as usize;
3527 let nc = cw * ch;
3528 for c_idx in 1..=2u32 {
3529 let mut cbuf_l0 = vec![0i32; nc];
3530 let mut cbuf_l1 = vec![0i32; nc];
3531 if let Some((mv, _)) = pred_l0 {
3532 let mv16 = mv.quarter_to_sixteenth();
3533 let mvc = derive_chroma_mv(mv16, inputs.walk.chroma_format_idc);
3534 interpolate_chroma_block(
3535 ref_l0_resolved,
3536 c_idx,
3537 (x0 / sub_w) as i32,
3538 (y0 / sub_h) as i32,
3539 mvc,
3540 cw,
3541 ch,
3542 inputs.decode.bit_depth_chroma,
3543 &mut cbuf_l0,
3544 )?;
3545 }
3546 if let Some((mv, _)) = pred_l1 {
3547 let refp = ref_l1_resolved.unwrap();
3548 let mv16 = mv.quarter_to_sixteenth();
3549 let mvc = derive_chroma_mv(mv16, inputs.walk.chroma_format_idc);
3550 interpolate_chroma_block(
3551 refp,
3552 c_idx,
3553 (x0 / sub_w) as i32,
3554 (y0 / sub_h) as i32,
3555 mvc,
3556 cw,
3557 ch,
3558 inputs.decode.bit_depth_chroma,
3559 &mut cbuf_l1,
3560 )?;
3561 }
3562 let mut ccomb = vec![0i32; nc];
3563 match (pred_l0.is_some(), pred_l1.is_some()) {
3564 (true, false) => ccomb.copy_from_slice(&cbuf_l0),
3565 (false, true) => ccomb.copy_from_slice(&cbuf_l1),
3566 (true, true) => average_bipred(&cbuf_l0, &cbuf_l1, &mut ccomb),
3567 (false, false) => unreachable!(),
3568 }
3569 let res = if c_idx == 1 { residual_cb } else { residual_cr };
3570 if !res.is_empty() {
3571 if res.len() != nc {
3572 return Err(Error::invalid(format!(
3573 "evc inter decode: chroma residual len {} != {}",
3574 res.len(),
3575 nc
3576 )));
3577 }
3578 for (a, b) in ccomb.iter_mut().zip(res.iter()) {
3579 *a += *b;
3580 }
3581 }
3582 pic.store_block(x0 / sub_w, y0 / sub_h, cw, ch, c_idx, &ccomb);
3583 }
3584 }
3585 Ok(())
3586}
3587
3588#[cfg(test)]
3589mod tests {
3590 use super::*;
3591
3592 #[test]
3603 fn walker_terminates_cleanly_on_min_idr_slice() {
3604 let inputs = SliceWalkInputs {
3617 pic_width: 4,
3618 pic_height: 4,
3619 ctb_log2_size_y: 5,
3620 min_cb_log2_size_y: 2,
3621 max_tb_log2_size_y: 5,
3622 chroma_format_idc: 1,
3623 cu_qp_delta_enabled: false,
3624 ..Default::default()
3625 };
3626 let res = walk_baseline_idr_slice(&[0u8; 0], inputs);
3629 assert!(res.is_err());
3630 }
3631
3632 #[test]
3635 fn rejects_unsupported_ctb_size() {
3636 let inputs = SliceWalkInputs {
3637 pic_width: 64,
3638 pic_height: 64,
3639 ctb_log2_size_y: 4, min_cb_log2_size_y: 2,
3641 max_tb_log2_size_y: 6,
3642 chroma_format_idc: 1,
3643 cu_qp_delta_enabled: true,
3644 ..Default::default()
3645 };
3646 let res = walk_baseline_idr_slice(&[0u8; 4], inputs);
3647 assert!(res.is_err());
3648 }
3649
3650 #[test]
3653 fn rejects_zero_ctus() {
3654 let inputs = SliceWalkInputs {
3655 pic_width: 0,
3656 pic_height: 0,
3657 ctb_log2_size_y: 6,
3658 min_cb_log2_size_y: 2,
3659 max_tb_log2_size_y: 6,
3660 chroma_format_idc: 1,
3661 cu_qp_delta_enabled: true,
3662 ..Default::default()
3663 };
3664 let res = walk_baseline_idr_slice(&[0u8; 4], inputs);
3665 assert!(res.is_err());
3666 }
3667
3668 #[test]
3674 fn engine_inits_from_zero_rbsp() {
3675 let inputs = SliceWalkInputs {
3676 pic_width: 64,
3677 pic_height: 64,
3678 ctb_log2_size_y: 6,
3679 min_cb_log2_size_y: 2,
3680 max_tb_log2_size_y: 6,
3681 chroma_format_idc: 1,
3682 cu_qp_delta_enabled: false,
3683 ..Default::default()
3684 };
3685 let bs = vec![0u8; 1024];
3688 let res = walk_baseline_idr_slice(&bs, inputs);
3689 assert!(res.is_err(), "expected exhaustion error, got {res:?}");
3690 }
3691
3692 #[test]
3696 fn engine_handles_all_ones_rbsp() {
3697 let inputs = SliceWalkInputs {
3698 pic_width: 32,
3699 pic_height: 32,
3700 ctb_log2_size_y: 5,
3701 min_cb_log2_size_y: 2,
3702 max_tb_log2_size_y: 5,
3703 chroma_format_idc: 1,
3704 cu_qp_delta_enabled: false,
3705 ..Default::default()
3706 };
3707 let bs = vec![0xFFu8; 1024];
3708 let _ = walk_baseline_idr_slice(&bs, inputs);
3711 }
3712
3713 #[test]
3736 fn fixture_split_ctu_idr_slice_consumes_all_bins() {
3737 use crate::cabac::CabacEncoder;
3738 let mut enc = CabacEncoder::new();
3739 enc.encode_decision(0, 0, 1);
3741 for _ in 0..4 {
3745 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
3750 enc.encode_terminate(true);
3751 let rbsp = enc.finish();
3752
3753 let inputs = SliceWalkInputs {
3754 pic_width: 32,
3755 pic_height: 32,
3756 ctb_log2_size_y: 5,
3757 min_cb_log2_size_y: 4, max_tb_log2_size_y: 5,
3759 chroma_format_idc: 1,
3760 cu_qp_delta_enabled: false,
3761 ..Default::default()
3762 };
3763 let stats = walk_baseline_idr_slice(&rbsp, inputs).unwrap();
3764 assert_eq!(stats.ctus, 1);
3765 assert_eq!(stats.split_cu_flag_bins, 1, "one split decision at the CTB");
3766 assert_eq!(stats.coding_units, 8, "4 children × (luma + chroma) = 8");
3767 assert_eq!(stats.intra_pred_mode_bins, 4);
3768 assert_eq!(stats.cbf_luma_bins, 4);
3769 assert_eq!(stats.cbf_chroma_bins, 8);
3770 assert_eq!(stats.cu_qp_delta_abs_bins, 0);
3771 assert_eq!(stats.coeff_runs, 0);
3772 }
3773
3774 #[test]
3779 fn fixture_two_ctu_split_idr_slice_consumes_all_bins() {
3780 use crate::cabac::CabacEncoder;
3781 let mut enc = CabacEncoder::new();
3782 for _ in 0..2 {
3783 enc.encode_decision(0, 0, 1); for _ in 0..4 {
3785 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
3790 }
3791 enc.encode_terminate(true);
3792 let rbsp = enc.finish();
3793
3794 let inputs = SliceWalkInputs {
3795 pic_width: 64,
3796 pic_height: 32,
3797 ctb_log2_size_y: 5,
3798 min_cb_log2_size_y: 4,
3799 max_tb_log2_size_y: 5,
3800 chroma_format_idc: 1,
3801 cu_qp_delta_enabled: false,
3802 ..Default::default()
3803 };
3804 let stats = walk_baseline_idr_slice(&rbsp, inputs).unwrap();
3805 assert_eq!(stats.ctus, 2);
3806 assert_eq!(stats.split_cu_flag_bins, 2);
3807 assert_eq!(stats.coding_units, 16); assert_eq!(stats.intra_pred_mode_bins, 8);
3809 assert_eq!(stats.cbf_luma_bins, 8);
3810 assert_eq!(stats.cbf_chroma_bins, 16);
3811 }
3812
3813 #[test]
3818 fn fixture_split_ctu_monochrome_consumes_all_bins() {
3819 use crate::cabac::CabacEncoder;
3820 let mut enc = CabacEncoder::new();
3821 enc.encode_decision(0, 0, 1); for _ in 0..4 {
3823 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
3827 enc.encode_terminate(true);
3828 let rbsp = enc.finish();
3829
3830 let inputs = SliceWalkInputs {
3831 pic_width: 32,
3832 pic_height: 32,
3833 ctb_log2_size_y: 5,
3834 min_cb_log2_size_y: 4,
3835 max_tb_log2_size_y: 5,
3836 chroma_format_idc: 0, cu_qp_delta_enabled: false,
3838 ..Default::default()
3839 };
3840 let stats = walk_baseline_idr_slice(&rbsp, inputs).unwrap();
3841 assert_eq!(stats.ctus, 1);
3842 assert_eq!(stats.split_cu_flag_bins, 1);
3843 assert_eq!(stats.coding_units, 8);
3844 assert_eq!(stats.intra_pred_mode_bins, 4);
3845 assert_eq!(stats.cbf_luma_bins, 4);
3846 assert_eq!(stats.cbf_chroma_bins, 0, "no chroma at chroma_format_idc=0");
3847 }
3848
3849 #[test]
3855 fn round4_end_to_end_decode_p_slice_zero_mv_copies_reference() {
3856 use crate::cabac::CabacEncoder;
3857 use crate::inter::RefPictureView;
3858 let mut ref_y = vec![0u8; 32 * 32];
3861 for j in 0..32 {
3862 for i in 0..32 {
3863 ref_y[j * 32 + i] = ((i * 4 + j) & 0xFF) as u8;
3864 }
3865 }
3866 let mut ref_cb = vec![0u8; 16 * 16];
3867 let mut ref_cr = vec![0u8; 16 * 16];
3868 for j in 0..16 {
3869 for i in 0..16 {
3870 ref_cb[j * 16 + i] = (100 + (i + j)) as u8;
3871 ref_cr[j * 16 + i] = (200 - (i + j)) as u8;
3872 }
3873 }
3874 let ref_view = RefPictureView {
3875 y: &ref_y,
3876 cb: &ref_cb,
3877 cr: &ref_cr,
3878 width: 32,
3879 height: 32,
3880 y_stride: 32,
3881 c_stride: 16,
3882 chroma_format_idc: 1,
3883 };
3884 let mut enc = CabacEncoder::new();
3895 enc.encode_decision(0, 0, 1); for _ in 0..4 {
3897 enc.encode_decision(0, 0, 1); for _ in 0..3 {
3900 enc.encode_decision(0, 0, 1);
3901 }
3902 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
3906 enc.encode_terminate(true);
3907 let rbsp = enc.finish();
3908
3909 let walk = SliceWalkInputs {
3910 pic_width: 32,
3911 pic_height: 32,
3912 ctb_log2_size_y: 5,
3913 min_cb_log2_size_y: 4,
3914 max_tb_log2_size_y: 5,
3915 chroma_format_idc: 1,
3916 cu_qp_delta_enabled: false,
3917 ..Default::default()
3918 };
3919 let decode = SliceDecodeInputs {
3920 slice_qp: 22,
3921 bit_depth_luma: 8,
3922 bit_depth_chroma: 8,
3923 enable_deblock: false,
3924 slice_cb_qp_offset: 0,
3925 slice_cr_qp_offset: 0,
3926 ..Default::default()
3927 };
3928 let ref_list_l0 = [ref_view];
3929 let inputs = InterDecodeInputs {
3930 walk,
3931 decode,
3932 slice_is_b: false,
3933 num_ref_idx_active_minus1_l0: 0,
3934 num_ref_idx_active_minus1_l1: 0,
3935 ref_list_l0: &ref_list_l0,
3936 ref_list_l1: &[],
3937 };
3938 let (pic, stats) = decode_baseline_inter_slice(&rbsp, inputs).unwrap();
3939 assert_eq!(pic.width, 32);
3940 assert_eq!(pic.height, 32);
3941 assert_eq!(stats.ctus, 1);
3942 assert_eq!(stats.coding_units, 4);
3943 assert_eq!(stats.cu_skip_flag_bins, 4);
3944 assert_eq!(stats.mvp_idx_bins, 4);
3945 assert_eq!(stats.uni_pred_cus, 4);
3946 assert_eq!(stats.bi_pred_cus, 0);
3947 assert_eq!(stats.hmvp_cand_count_final, 4);
3951 assert_eq!(pic.y, ref_y, "Y plane must match reference");
3953 assert_eq!(pic.cb, ref_cb, "Cb plane must match reference");
3954 assert_eq!(pic.cr, ref_cr, "Cr plane must match reference");
3955 let mse: f64 = pic
3957 .y
3958 .iter()
3959 .zip(ref_y.iter())
3960 .map(|(&a, &b)| (a as f64 - b as f64).powi(2))
3961 .sum::<f64>()
3962 / pic.y.len() as f64;
3963 assert_eq!(mse, 0.0);
3964 }
3965
3966 #[test]
3971 fn round4_end_to_end_decode_b_slice_zero_mv_averages_references() {
3972 use crate::cabac::CabacEncoder;
3973 use crate::inter::RefPictureView;
3974 let ref0_y = vec![100u8; 16 * 16];
3975 let ref0_cb = vec![100u8; 8 * 8];
3976 let ref0_cr = vec![100u8; 8 * 8];
3977 let ref1_y = vec![200u8; 16 * 16];
3978 let ref1_cb = vec![200u8; 8 * 8];
3979 let ref1_cr = vec![200u8; 8 * 8];
3980 let view0 = RefPictureView {
3981 y: &ref0_y,
3982 cb: &ref0_cb,
3983 cr: &ref0_cr,
3984 width: 16,
3985 height: 16,
3986 y_stride: 16,
3987 c_stride: 8,
3988 chroma_format_idc: 1,
3989 };
3990 let view1 = RefPictureView {
3991 y: &ref1_y,
3992 cb: &ref1_cb,
3993 cr: &ref1_cr,
3994 width: 16,
3995 height: 16,
3996 y_stride: 16,
3997 c_stride: 8,
3998 chroma_format_idc: 1,
3999 };
4000 let mut enc = CabacEncoder::new();
4008 enc.encode_decision(0, 0, 1); for _ in 0..3 {
4010 enc.encode_decision(0, 0, 1); }
4012 for _ in 0..3 {
4013 enc.encode_decision(0, 0, 1); }
4015 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
4019 let rbsp = enc.finish();
4020 let walk = SliceWalkInputs {
4021 pic_width: 16,
4022 pic_height: 16,
4023 ctb_log2_size_y: 5,
4024 min_cb_log2_size_y: 4,
4025 max_tb_log2_size_y: 5,
4026 chroma_format_idc: 1,
4027 cu_qp_delta_enabled: false,
4028 ..Default::default()
4029 };
4030 let decode = SliceDecodeInputs {
4031 slice_qp: 22,
4032 bit_depth_luma: 8,
4033 bit_depth_chroma: 8,
4034 enable_deblock: false,
4035 slice_cb_qp_offset: 0,
4036 slice_cr_qp_offset: 0,
4037 ..Default::default()
4038 };
4039 let ref_list_l0 = [view0];
4040 let ref_list_l1 = [view1];
4041 let inputs = InterDecodeInputs {
4042 walk,
4043 decode,
4044 slice_is_b: true,
4045 num_ref_idx_active_minus1_l0: 0,
4046 num_ref_idx_active_minus1_l1: 0,
4047 ref_list_l0: &ref_list_l0,
4048 ref_list_l1: &ref_list_l1,
4049 };
4050 let (pic, stats) = decode_baseline_inter_slice(&rbsp, inputs).unwrap();
4051 assert_eq!(stats.coding_units, 1);
4052 assert_eq!(stats.bi_pred_cus, 1);
4053 assert!(pic.y.iter().all(|&v| v == 150), "Y must be 150");
4055 assert!(pic.cb.iter().all(|&v| v == 150), "Cb must be 150");
4056 assert!(pic.cr.iter().all(|&v| v == 150), "Cr must be 150");
4057 }
4058
4059 #[test]
4064 fn zigzag_scan_4x4_matches_spec() {
4065 let s = zigzag_scan(4, 4);
4066 let expected = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
4075 assert_eq!(s, expected);
4076 }
4077
4078 #[test]
4090 fn residual_coding_rle_single_coeff_dc() {
4091 use crate::cabac::CabacEncoder;
4092 let mut enc = CabacEncoder::new();
4093 enc.encode_decision(0, 0, 0); for _ in 0..4 {
4095 enc.encode_decision(0, 0, 1); }
4097 enc.encode_decision(0, 0, 0); enc.encode_bypass(0); enc.encode_decision(0, 0, 1); enc.encode_terminate(true);
4101 let rbsp = enc.finish();
4102 let mut eng = CabacEngine::new(&rbsp).unwrap();
4103 let mut levels = vec![0i32; 16];
4104 let mut runs = 0u32;
4105 decode_residual_coding_rle(&mut eng, &mut levels, &mut runs, 2, 2).unwrap();
4106 assert_eq!(runs, 1);
4107 assert_eq!(levels[0].abs(), 5, "decoded level magnitude wrong");
4112 for (i, &v) in levels.iter().enumerate().skip(1) {
4113 assert_eq!(v, 0, "non-DC coeff {i} should be zero, got {v}");
4114 }
4115 }
4116
4117 #[test]
4124 fn idr_decode_with_residual_dc_only() {
4125 use crate::cabac::CabacEncoder;
4126 let mut enc = CabacEncoder::new();
4127 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_bypass(0); enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
4143 let rbsp = enc.finish();
4144 let walk = SliceWalkInputs {
4145 pic_width: 4,
4146 pic_height: 4,
4147 ctb_log2_size_y: 5,
4148 min_cb_log2_size_y: 2,
4149 max_tb_log2_size_y: 5,
4150 chroma_format_idc: 1,
4151 cu_qp_delta_enabled: false,
4152 ..Default::default()
4153 };
4154 let decode = SliceDecodeInputs {
4155 slice_qp: 22,
4156 bit_depth_luma: 8,
4157 bit_depth_chroma: 8,
4158 enable_deblock: false,
4159 slice_cb_qp_offset: 0,
4160 slice_cr_qp_offset: 0,
4161 ..Default::default()
4162 };
4163 let (pic, stats) = decode_baseline_idr_slice(&rbsp, walk, decode).unwrap();
4164 assert_eq!(stats.coding_units, 2, "luma + chroma trees");
4165 assert_eq!(stats.cbf_luma_bins, 1);
4166 assert_eq!(stats.coeff_runs, 1);
4167 let any_nonzero_residual = pic.y.iter().any(|&v| v != 128);
4173 let _ = any_nonzero_residual;
4178 assert!(pic.cb.iter().all(|&v| v == 128));
4180 assert!(pic.cr.iter().all(|&v| v == 128));
4181 }
4182
4183 #[test]
4193 fn inter_decode_with_residual_dc_only_p_slice() {
4194 use crate::cabac::CabacEncoder;
4195 let ref_y = vec![200u8; 4 * 4];
4196 let ref_cb = vec![100u8; 2 * 2];
4197 let ref_cr = vec![80u8; 2 * 2];
4198 let view = RefPictureView {
4199 y: &ref_y,
4200 cb: &ref_cb,
4201 cr: &ref_cr,
4202 width: 4,
4203 height: 4,
4204 y_stride: 4,
4205 c_stride: 2,
4206 chroma_format_idc: 1,
4207 };
4208 let mut enc = CabacEncoder::new();
4209 enc.encode_decision(0, 0, 1); for _ in 0..3 {
4219 enc.encode_decision(0, 0, 1); }
4221 enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_bypass(0); enc.encode_decision(0, 0, 1); enc.encode_terminate(true);
4229 let rbsp = enc.finish();
4230 let walk = SliceWalkInputs {
4231 pic_width: 4,
4232 pic_height: 4,
4233 ctb_log2_size_y: 5,
4234 min_cb_log2_size_y: 2,
4235 max_tb_log2_size_y: 5,
4236 chroma_format_idc: 1,
4237 cu_qp_delta_enabled: false,
4238 ..Default::default()
4239 };
4240 let decode = SliceDecodeInputs {
4241 slice_qp: 22,
4242 bit_depth_luma: 8,
4243 bit_depth_chroma: 8,
4244 enable_deblock: false,
4245 slice_cb_qp_offset: 0,
4246 slice_cr_qp_offset: 0,
4247 ..Default::default()
4248 };
4249 let ref_list_l0 = [view];
4250 let inputs = InterDecodeInputs {
4251 walk,
4252 decode,
4253 slice_is_b: false,
4254 num_ref_idx_active_minus1_l0: 0,
4255 num_ref_idx_active_minus1_l1: 0,
4256 ref_list_l0: &ref_list_l0,
4257 ref_list_l1: &[],
4258 };
4259 match decode_baseline_inter_slice(&rbsp, inputs) {
4265 Ok((pic, stats)) => {
4266 assert_eq!(stats.coding_units, 1);
4267 assert_eq!(stats.coeff_runs, 1);
4268 assert_eq!(stats.cbf_luma_bins, 1);
4269 assert!(pic.cb.iter().all(|&v| v == 100));
4272 assert!(pic.cr.iter().all(|&v| v == 80));
4273 assert_eq!(pic.y.len(), 4 * 4);
4274 }
4275 Err(_) => {
4276 }
4281 }
4282 }
4283
4284 #[test]
4290 fn idr_decode_with_deblock_enabled_no_op() {
4291 use crate::cabac::CabacEncoder;
4292 let mut enc = CabacEncoder::new();
4296 enc.encode_decision(0, 0, 1); for _ in 0..4 {
4298 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
4304 enc.encode_terminate(true);
4305 let rbsp = enc.finish();
4306 let walk = SliceWalkInputs {
4307 pic_width: 64,
4308 pic_height: 64,
4309 ctb_log2_size_y: 6,
4310 min_cb_log2_size_y: 2,
4311 max_tb_log2_size_y: 5,
4312 chroma_format_idc: 1,
4313 cu_qp_delta_enabled: false,
4314 ..Default::default()
4315 };
4316 let decode = SliceDecodeInputs {
4317 slice_qp: 32,
4318 bit_depth_luma: 8,
4319 bit_depth_chroma: 8,
4320 enable_deblock: true,
4321 slice_cb_qp_offset: 0,
4322 slice_cr_qp_offset: 0,
4323 ..Default::default()
4324 };
4325 let (pic, stats) = decode_baseline_idr_slice(&rbsp, walk, decode).unwrap();
4326 assert_eq!(stats.deblock_edges, 960);
4334 assert!(pic.y.iter().all(|&v| v == 128));
4336 assert!(pic.cb.iter().all(|&v| v == 128));
4337 assert!(pic.cr.iter().all(|&v| v == 128));
4338 }
4339
4340 #[test]
4347 fn idr_decode_64x64_ctu_with_zero_residual() {
4348 use crate::cabac::CabacEncoder;
4349 let mut enc = CabacEncoder::new();
4350 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
4360 let rbsp = enc.finish();
4361 let walk = SliceWalkInputs {
4362 pic_width: 64,
4363 pic_height: 64,
4364 ctb_log2_size_y: 6, min_cb_log2_size_y: 4,
4366 max_tb_log2_size_y: 6, chroma_format_idc: 1,
4368 cu_qp_delta_enabled: false,
4369 ..Default::default()
4370 };
4371 let decode = SliceDecodeInputs {
4372 slice_qp: 22,
4373 bit_depth_luma: 8,
4374 bit_depth_chroma: 8,
4375 enable_deblock: false,
4376 slice_cb_qp_offset: 0,
4377 slice_cr_qp_offset: 0,
4378 ..Default::default()
4379 };
4380 let (pic, stats) = decode_baseline_idr_slice(&rbsp, walk, decode).unwrap();
4381 assert_eq!(stats.ctus, 1);
4382 assert_eq!(stats.coding_units, 2);
4383 assert!(pic.y.iter().all(|&v| v == 128));
4384 assert!(pic.cb.iter().all(|&v| v == 128));
4385 assert!(pic.cr.iter().all(|&v| v == 128));
4386 }
4387
4388 #[test]
4397 fn round9_hmvp_fallback_overrides_unavailable_neighbour() {
4398 let mut hmvp = crate::hmvp::HmvpCandList::new();
4399 hmvp.update(crate::hmvp::HmvpCandidate {
4400 mv_l0: MotionVector::quarter_pel(40, -20),
4401 mv_l1: MotionVector::default(),
4402 ref_idx_l0: 0,
4403 ref_idx_l1: -1,
4404 });
4405 let mv = baseline_amvp_select_with_hmvp(0, &hmvp, 0, 0);
4408 assert_eq!(mv, MotionVector::quarter_pel(40, -20));
4409 let mv = baseline_amvp_select_with_hmvp(3, &hmvp, 0, 0);
4412 assert_eq!(mv, MotionVector::default());
4413 }
4414
4415 #[test]
4418 fn round9_hmvp_fallback_noop_on_empty_list() {
4419 let hmvp = crate::hmvp::HmvpCandList::new();
4420 let mv = baseline_amvp_select_with_hmvp(0, &hmvp, 0, 0);
4421 assert_eq!(mv, MotionVector::quarter_pel(1, 1));
4422 }
4423
4424 #[test]
4429 fn round10_spatial_neighbour_left_drives_amvp_slot_0() {
4430 let mut grid = SideInfoGrid::new(64, 64);
4431 grid.stamp_block(
4434 12,
4435 28,
4436 4,
4437 4,
4438 CuSideInfo {
4439 pred_mode: CuPredMode::Inter,
4440 cbf_luma: 0,
4441 mv_l0_x: 24,
4442 mv_l0_y: -12,
4443 mv_l1_x: 0,
4444 mv_l1_y: 0,
4445 ref_idx_l0: 0,
4446 ref_idx_l1: -1,
4447 },
4448 );
4449 let hmvp = crate::hmvp::HmvpCandList::new();
4450 let mv = baseline_amvp_select_with_grid_and_hmvp(0, &grid, &hmvp, 16, 16, 16, 16, 0, 0);
4453 assert_eq!(mv, MotionVector::quarter_pel(24, -12));
4454 let mv = baseline_amvp_select_with_grid_and_hmvp(1, &grid, &hmvp, 16, 16, 16, 16, 0, 0);
4458 assert_eq!(mv, MotionVector::quarter_pel(1, 1));
4459 }
4460
4461 #[test]
4465 fn round10_spatial_neighbour_ref_idx_mismatch_is_unavailable() {
4466 let mut grid = SideInfoGrid::new(64, 64);
4467 grid.stamp_block(
4468 12,
4469 28,
4470 4,
4471 4,
4472 CuSideInfo {
4473 pred_mode: CuPredMode::Inter,
4474 cbf_luma: 0,
4475 mv_l0_x: 24,
4476 mv_l0_y: -12,
4477 mv_l1_x: 0,
4478 mv_l1_y: 0,
4479 ref_idx_l0: 2, ref_idx_l1: -1,
4481 },
4482 );
4483 let hmvp = crate::hmvp::HmvpCandList::new();
4484 let mv = baseline_amvp_select_with_grid_and_hmvp(0, &grid, &hmvp, 16, 16, 16, 16, 0, 0);
4485 assert_eq!(mv, MotionVector::quarter_pel(1, 1));
4486 }
4487
4488 #[test]
4491 fn round10_spatial_amvp_falls_through_to_hmvp() {
4492 let grid = SideInfoGrid::new(64, 64);
4493 let mut hmvp = crate::hmvp::HmvpCandList::new();
4494 hmvp.update(crate::hmvp::HmvpCandidate {
4495 mv_l0: MotionVector::quarter_pel(8, 8),
4496 mv_l1: MotionVector::default(),
4497 ref_idx_l0: 0,
4498 ref_idx_l1: -1,
4499 });
4500 let mv = baseline_amvp_select_with_grid_and_hmvp(0, &grid, &hmvp, 16, 16, 16, 16, 0, 0);
4501 assert_eq!(mv, MotionVector::quarter_pel(8, 8));
4502 }
4503
4504 #[test]
4506 fn round10_spatial_neighbour_above_right_drives_slot_2() {
4507 let mut grid = SideInfoGrid::new(64, 64);
4508 grid.stamp_block(
4510 32,
4511 12,
4512 4,
4513 4,
4514 CuSideInfo {
4515 pred_mode: CuPredMode::Inter,
4516 cbf_luma: 0,
4517 mv_l0_x: -16,
4518 mv_l0_y: 4,
4519 mv_l1_x: 0,
4520 mv_l1_y: 0,
4521 ref_idx_l0: 0,
4522 ref_idx_l1: -1,
4523 },
4524 );
4525 let hmvp = crate::hmvp::HmvpCandList::new();
4526 let mv = baseline_amvp_select_with_grid_and_hmvp(2, &grid, &hmvp, 16, 16, 16, 16, 0, 0);
4527 assert_eq!(mv, MotionVector::quarter_pel(-16, 4));
4528 }
4529
4530 #[test]
4539 fn round9_multiref_dpb_two_entry_l0() {
4540 use crate::cabac::CabacEncoder;
4541 use crate::inter::RefPictureView;
4542 let ref0_y = vec![200u8; 16 * 16];
4543 let ref0_cb = vec![100u8; 8 * 8];
4544 let ref0_cr = vec![80u8; 8 * 8];
4545 let ref1_y = vec![50u8; 16 * 16];
4546 let ref1_cb = vec![60u8; 8 * 8];
4547 let ref1_cr = vec![70u8; 8 * 8];
4548 let view0 = RefPictureView {
4549 y: &ref0_y,
4550 cb: &ref0_cb,
4551 cr: &ref0_cr,
4552 width: 16,
4553 height: 16,
4554 y_stride: 16,
4555 c_stride: 8,
4556 chroma_format_idc: 1,
4557 };
4558 let view1 = RefPictureView {
4559 y: &ref1_y,
4560 cb: &ref1_cb,
4561 cr: &ref1_cr,
4562 width: 16,
4563 height: 16,
4564 y_stride: 16,
4565 c_stride: 8,
4566 chroma_format_idc: 1,
4567 };
4568 let mut enc = CabacEncoder::new();
4569 enc.encode_decision(0, 0, 1); for _ in 0..3 {
4573 enc.encode_decision(0, 0, 1); }
4575 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
4579 let rbsp = enc.finish();
4580 let walk = SliceWalkInputs {
4581 pic_width: 16,
4582 pic_height: 16,
4583 ctb_log2_size_y: 5,
4584 min_cb_log2_size_y: 4,
4585 max_tb_log2_size_y: 5,
4586 chroma_format_idc: 1,
4587 cu_qp_delta_enabled: false,
4588 ..Default::default()
4589 };
4590 let decode = SliceDecodeInputs {
4591 slice_qp: 22,
4592 bit_depth_luma: 8,
4593 bit_depth_chroma: 8,
4594 enable_deblock: false,
4595 slice_cb_qp_offset: 0,
4596 slice_cr_qp_offset: 0,
4597 ..Default::default()
4598 };
4599 let ref_list_l0 = [view0, view1];
4600 let inputs = InterDecodeInputs {
4601 walk,
4602 decode,
4603 slice_is_b: false,
4604 num_ref_idx_active_minus1_l0: 1, num_ref_idx_active_minus1_l1: 0,
4606 ref_list_l0: &ref_list_l0,
4607 ref_list_l1: &[],
4608 };
4609 let (pic, stats) = decode_baseline_inter_slice(&rbsp, inputs).unwrap();
4610 assert_eq!(stats.coding_units, 1);
4611 assert_eq!(stats.uni_pred_cus, 1);
4612 assert!(pic.y.iter().all(|&v| v == 200));
4614 assert!(pic.cb.iter().all(|&v| v == 100));
4615 assert!(pic.cr.iter().all(|&v| v == 80));
4616 }
4617
4618 #[test]
4621 fn round9_rejects_empty_ref_list_l0() {
4622 let walk = SliceWalkInputs {
4623 pic_width: 16,
4624 pic_height: 16,
4625 ctb_log2_size_y: 5,
4626 min_cb_log2_size_y: 4,
4627 max_tb_log2_size_y: 5,
4628 chroma_format_idc: 1,
4629 cu_qp_delta_enabled: false,
4630 ..Default::default()
4631 };
4632 let decode = SliceDecodeInputs {
4633 slice_qp: 22,
4634 bit_depth_luma: 8,
4635 bit_depth_chroma: 8,
4636 enable_deblock: false,
4637 slice_cb_qp_offset: 0,
4638 slice_cr_qp_offset: 0,
4639 ..Default::default()
4640 };
4641 let inputs = InterDecodeInputs {
4642 walk,
4643 decode,
4644 slice_is_b: false,
4645 num_ref_idx_active_minus1_l0: 0,
4646 num_ref_idx_active_minus1_l1: 0,
4647 ref_list_l0: &[],
4648 ref_list_l1: &[],
4649 };
4650 let err = decode_baseline_inter_slice(&[], inputs).unwrap_err();
4651 assert!(format!("{err}").contains("ref_list_l0"));
4652 }
4653
4654 #[test]
4657 fn round9_rejects_oversized_active_count() {
4658 use crate::inter::RefPictureView;
4659 let ref0_y = vec![100u8; 16 * 16];
4660 let ref0_cb = vec![100u8; 64];
4661 let ref0_cr = vec![100u8; 64];
4662 let view = RefPictureView {
4663 y: &ref0_y,
4664 cb: &ref0_cb,
4665 cr: &ref0_cr,
4666 width: 16,
4667 height: 16,
4668 y_stride: 16,
4669 c_stride: 8,
4670 chroma_format_idc: 1,
4671 };
4672 let walk = SliceWalkInputs {
4673 pic_width: 16,
4674 pic_height: 16,
4675 ctb_log2_size_y: 5,
4676 min_cb_log2_size_y: 4,
4677 max_tb_log2_size_y: 5,
4678 chroma_format_idc: 1,
4679 cu_qp_delta_enabled: false,
4680 ..Default::default()
4681 };
4682 let decode = SliceDecodeInputs {
4683 slice_qp: 22,
4684 bit_depth_luma: 8,
4685 bit_depth_chroma: 8,
4686 enable_deblock: false,
4687 slice_cb_qp_offset: 0,
4688 slice_cr_qp_offset: 0,
4689 ..Default::default()
4690 };
4691 let ref_list_l0 = [view];
4692 let inputs = InterDecodeInputs {
4693 walk,
4694 decode,
4695 slice_is_b: false,
4696 num_ref_idx_active_minus1_l0: 1, num_ref_idx_active_minus1_l1: 0,
4698 ref_list_l0: &ref_list_l0,
4699 ref_list_l1: &[],
4700 };
4701 let err = decode_baseline_inter_slice(&[], inputs).unwrap_err();
4702 assert!(format!("{err}").contains("num_ref_idx_active_minus1_l0"));
4703 }
4704
4705 fn encode_egk0_bypass(enc: &mut crate::cabac::CabacEncoder, mut val: u32) {
4716 if val == 0 {
4717 enc.encode_bypass(0);
4718 return;
4719 }
4720 let mut k = 0u32;
4721 while val >= (1u32 << k) {
4722 enc.encode_bypass(1);
4723 val -= 1u32 << k;
4724 k += 1;
4725 }
4726 enc.encode_bypass(0);
4727 for i in (0..k).rev() {
4729 enc.encode_bypass(((val >> i) & 1) as u8);
4730 }
4731 }
4732
4733 #[test]
4738 fn round90_egk0_bypass_roundtrip() {
4739 use crate::cabac::{CabacEncoder, CabacEngine};
4740 for &val in &[0u32, 1, 2, 3, 4, 7, 8, 15, 31] {
4741 let mut enc = CabacEncoder::new();
4742 encode_egk0_bypass(&mut enc, val);
4743 enc.encode_terminate(true);
4744 let rbsp = enc.finish();
4745 let mut eng = CabacEngine::new(&rbsp).unwrap();
4746 let decoded = eng.decode_egk_bypass(0).unwrap();
4747 assert_eq!(decoded, val, "egk0 round-trip failed for {val}");
4748 }
4749 }
4750
4751 #[test]
4758 fn round90_idr_decode_without_ibc_flag_consumes_no_ibc_bins() {
4759 use crate::cabac::CabacEncoder;
4760 let mut enc = CabacEncoder::new();
4761 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
4766 let rbsp = enc.finish();
4767 let walk = SliceWalkInputs {
4768 pic_width: 4,
4769 pic_height: 4,
4770 ctb_log2_size_y: 5,
4771 min_cb_log2_size_y: 2,
4772 max_tb_log2_size_y: 5,
4773 chroma_format_idc: 0,
4774 cu_qp_delta_enabled: false,
4775 sps_ibc_flag: false,
4776 log2_max_ibc_cand_size: 0,
4777 ..Default::default()
4778 };
4779 let decode = SliceDecodeInputs {
4780 slice_qp: 22,
4781 ..Default::default()
4782 };
4783 let (pic, stats) = decode_baseline_idr_slice(&rbsp, walk, decode).unwrap();
4784 assert_eq!(
4785 stats.ibc_flag_bins, 0,
4786 "no ibc_flag bin when SPS gate is off"
4787 );
4788 assert_eq!(stats.ibc_cus, 0);
4789 assert_eq!(stats.ibc_abs_mvd_bins, 0);
4790 assert_eq!(stats.ibc_mvd_sign_bins, 0);
4791 assert_eq!(stats.intra_pred_mode_bins, 1);
4792 assert!(pic.y.iter().all(|&v| v == 128));
4793 }
4794
4795 #[test]
4800 fn round90_idr_decode_skips_ibc_flag_when_cu_exceeds_cand_size() {
4801 use crate::cabac::CabacEncoder;
4802 let mut enc = CabacEncoder::new();
4803 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
4809 let rbsp = enc.finish();
4810 let walk = SliceWalkInputs {
4811 pic_width: 4,
4812 pic_height: 4,
4813 ctb_log2_size_y: 5,
4814 min_cb_log2_size_y: 2,
4815 max_tb_log2_size_y: 5,
4816 chroma_format_idc: 0,
4817 cu_qp_delta_enabled: false,
4818 sps_ibc_flag: true,
4819 log2_max_ibc_cand_size: 1,
4820 ..Default::default()
4821 };
4822 let decode = SliceDecodeInputs {
4823 slice_qp: 22,
4824 sps_ibc_flag: true,
4825 log2_max_ibc_cand_size: 1,
4826 ..Default::default()
4827 };
4828 let (_pic, stats) = decode_baseline_idr_slice(&rbsp, walk, decode).unwrap();
4829 assert_eq!(stats.ibc_flag_bins, 0, "size gate suppresses ibc_flag");
4830 assert_eq!(stats.ibc_cus, 0);
4831 assert_eq!(stats.intra_pred_mode_bins, 1);
4832 }
4833
4834 #[test]
4843 fn round90_ibc_branch_predicts_from_left_neighbour() {
4844 let mut pic = YuvPicture::new(8, 4, 0, 8).unwrap();
4845 let cu0_samples: [u8; 16] = [
4848 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160,
4852 ];
4853 for j in 0..4 {
4854 for i in 0..4 {
4855 pic.y[j * 8 + i] = cu0_samples[j * 4 + i];
4856 }
4857 }
4858 let mut side_info = SideInfoGrid::new(8, 4);
4859 let walk = SliceWalkInputs {
4860 pic_width: 8,
4861 pic_height: 4,
4862 ctb_log2_size_y: 5,
4863 min_cb_log2_size_y: 2,
4864 max_tb_log2_size_y: 5,
4865 chroma_format_idc: 0,
4866 cu_qp_delta_enabled: false,
4867 sps_ibc_flag: true,
4868 log2_max_ibc_cand_size: 5,
4869 ..Default::default()
4870 };
4871 let decode = SliceDecodeInputs {
4872 slice_qp: 22,
4873 sps_ibc_flag: true,
4874 log2_max_ibc_cand_size: 5,
4875 ..Default::default()
4876 };
4877 let mvd = MotionVector { x: -4, y: 0 };
4880 let zero_levels = vec![0i32; 16];
4882 apply_ibc_branch_predict_and_reconstruct(
4883 &mut pic,
4884 &mut side_info,
4885 &walk,
4886 &decode,
4887 4, 0, 2, 2, TreeType::DualTreeLuma,
4892 mvd,
4893 0,
4894 &zero_levels,
4895 decode.slice_qp.clamp(0, 51),
4896 )
4897 .unwrap();
4898 for j in 0..4 {
4900 for i in 0..4 {
4901 let expected = cu0_samples[j * 4 + i];
4902 let actual = pic.y[j * 8 + (4 + i)];
4903 assert_eq!(
4904 actual, expected,
4905 "IBC copy mismatch at (j={j}, i={i}): expected {expected}, got {actual}"
4906 );
4907 }
4908 }
4909 let cell = side_info.at(1, 0);
4912 assert_eq!(
4913 cell.pred_mode,
4914 CuPredMode::Ibc,
4915 "side-info stamp must mark MODE_IBC"
4916 );
4917 assert_eq!(
4919 cell.mv_l0_x, -64,
4920 "mv_l0_x should be the §8.6.2.1 eq.1039 << 4"
4921 );
4922 assert_eq!(cell.mv_l0_y, 0);
4923 }
4924
4925 #[test]
4929 fn round90_ibc_branch_rejects_non_conformant_bv() {
4930 let mut pic = YuvPicture::new(8, 4, 0, 8).unwrap();
4931 let mut side_info = SideInfoGrid::new(8, 4);
4932 let walk = SliceWalkInputs {
4933 pic_width: 8,
4934 pic_height: 4,
4935 ctb_log2_size_y: 5,
4936 min_cb_log2_size_y: 2,
4937 max_tb_log2_size_y: 5,
4938 chroma_format_idc: 0,
4939 cu_qp_delta_enabled: false,
4940 sps_ibc_flag: true,
4941 log2_max_ibc_cand_size: 5,
4942 ..Default::default()
4943 };
4944 let decode = SliceDecodeInputs {
4945 slice_qp: 22,
4946 sps_ibc_flag: true,
4947 log2_max_ibc_cand_size: 5,
4948 ..Default::default()
4949 };
4950 let mvd_overlap = MotionVector { x: 0, y: 0 };
4953 let zero_levels = vec![0i32; 16];
4954 let err = apply_ibc_branch_predict_and_reconstruct(
4955 &mut pic,
4956 &mut side_info,
4957 &walk,
4958 &decode,
4959 4,
4960 0,
4961 2,
4962 2,
4963 TreeType::DualTreeLuma,
4964 mvd_overlap,
4965 0,
4966 &zero_levels,
4967 decode.slice_qp.clamp(0, 51),
4968 )
4969 .unwrap_err();
4970 let msg = format!("{err}");
4971 assert!(
4972 msg.contains("ibc") && (msg.contains("above-or-left") || msg.contains("eq. 1113")),
4973 "expected above-or-left conformance error, got: {msg}"
4974 );
4975 assert!(pic.y.iter().all(|&v| v == 128));
4978 assert_eq!(side_info.at(1, 0).pred_mode, CuPredMode::Intra);
4980 }
4981
4982 #[test]
4987 fn round90_luma_cell_is_ibc_probe() {
4988 let mut side_info = SideInfoGrid::new(8, 4);
4989 assert!(!luma_cell_is_ibc(&side_info, 0, 0));
4991 assert!(!luma_cell_is_ibc(&side_info, 4, 0));
4992 side_info.stamp_block(
4994 4,
4995 0,
4996 4,
4997 4,
4998 CuSideInfo {
4999 pred_mode: CuPredMode::Ibc,
5000 ..Default::default()
5001 },
5002 );
5003 assert!(luma_cell_is_ibc(&side_info, 4, 0));
5005 assert!(!luma_cell_is_ibc(&side_info, 0, 0));
5006 assert!(!luma_cell_is_ibc(&side_info, 100, 100));
5008 }
5009
5010 #[test]
5014 fn round90_add_chroma_residual_clips_to_bit_depth() {
5015 let mut pic = YuvPicture::new(8, 8, 1, 8).unwrap();
5016 for j in 0..4 {
5019 for i in 0..4 {
5020 pic.cb[j * 4 + i] = 200;
5021 pic.cr[j * 4 + i] = 50;
5022 }
5023 }
5024 let res_pos = vec![100i32; 16];
5026 let res_neg = vec![-100i32; 16];
5027 add_chroma_residual_to_block(&mut pic, 0, 0, 3, 3, 1, &res_pos).unwrap();
5028 add_chroma_residual_to_block(&mut pic, 0, 0, 3, 3, 2, &res_neg).unwrap();
5029 for j in 0..4 {
5031 for i in 0..4 {
5032 assert_eq!(pic.cb[j * 4 + i], 255, "Cb clip at ({i},{j})");
5033 assert_eq!(pic.cr[j * 4 + i], 0, "Cr clip at ({i},{j})");
5034 }
5035 }
5036 }
5037
5038 #[test]
5056 fn round95_inter_decode_without_ibc_flag_consumes_no_ibc_bins() {
5057 use crate::cabac::CabacEncoder;
5058 use crate::inter::RefPictureView;
5059 let ref_y = vec![200u8; 32 * 32];
5061 let ref_cb = vec![128u8; 16 * 16];
5062 let ref_cr = vec![128u8; 16 * 16];
5063 let ref_view = RefPictureView {
5064 y: &ref_y,
5065 cb: &ref_cb,
5066 cr: &ref_cr,
5067 width: 32,
5068 height: 32,
5069 y_stride: 32,
5070 c_stride: 16,
5071 chroma_format_idc: 1,
5072 };
5073 let mut enc = CabacEncoder::new();
5078 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
5085 let rbsp = enc.finish();
5086 let walk = SliceWalkInputs {
5087 pic_width: 32,
5088 pic_height: 32,
5089 ctb_log2_size_y: 5,
5090 min_cb_log2_size_y: 4,
5091 max_tb_log2_size_y: 5,
5092 chroma_format_idc: 1,
5093 cu_qp_delta_enabled: false,
5094 sps_ibc_flag: false,
5095 log2_max_ibc_cand_size: 0,
5096 ..Default::default()
5097 };
5098 let decode = SliceDecodeInputs {
5099 slice_qp: 22,
5100 ..Default::default()
5101 };
5102 let ref_list_l0 = [ref_view];
5103 let inputs = InterDecodeInputs {
5104 walk,
5105 decode,
5106 slice_is_b: false,
5107 num_ref_idx_active_minus1_l0: 0,
5108 num_ref_idx_active_minus1_l1: 0,
5109 ref_list_l0: &ref_list_l0,
5110 ref_list_l1: &[],
5111 };
5112 let (_pic, stats) = decode_baseline_inter_slice(&rbsp, inputs).unwrap();
5113 assert_eq!(
5114 stats.ibc_flag_bins, 0,
5115 "no ibc_flag bin when SPS gate is off (P slice)"
5116 );
5117 assert_eq!(stats.ibc_cus, 0);
5118 assert_eq!(stats.ibc_abs_mvd_bins, 0);
5119 assert_eq!(stats.ibc_mvd_sign_bins, 0);
5120 }
5121
5122 #[test]
5130 fn round100_inter_skip_cu_consumes_no_cu_qp_delta_bins() {
5131 use crate::cabac::CabacEncoder;
5132 use crate::inter::RefPictureView;
5133 let ref_y = vec![200u8; 32 * 32];
5134 let ref_cb = vec![128u8; 16 * 16];
5135 let ref_cr = vec![128u8; 16 * 16];
5136 let ref_view = RefPictureView {
5137 y: &ref_y,
5138 cb: &ref_cb,
5139 cr: &ref_cr,
5140 width: 32,
5141 height: 32,
5142 y_stride: 32,
5143 c_stride: 16,
5144 chroma_format_idc: 1,
5145 };
5146 let mut enc = CabacEncoder::new();
5147 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
5154 let rbsp = enc.finish();
5155 let walk = SliceWalkInputs {
5156 pic_width: 32,
5157 pic_height: 32,
5158 ctb_log2_size_y: 5,
5159 min_cb_log2_size_y: 4,
5160 max_tb_log2_size_y: 5,
5161 chroma_format_idc: 1,
5162 cu_qp_delta_enabled: true,
5165 sps_ibc_flag: false,
5166 log2_max_ibc_cand_size: 0,
5167 ..Default::default()
5168 };
5169 let decode = SliceDecodeInputs {
5170 slice_qp: 22,
5171 ..Default::default()
5172 };
5173 let ref_list_l0 = [ref_view];
5174 let inputs = InterDecodeInputs {
5175 walk,
5176 decode,
5177 slice_is_b: false,
5178 num_ref_idx_active_minus1_l0: 0,
5179 num_ref_idx_active_minus1_l1: 0,
5180 ref_list_l0: &ref_list_l0,
5181 ref_list_l1: &[],
5182 };
5183 let (pic, stats) = decode_baseline_inter_slice(&rbsp, inputs).unwrap();
5184 assert_eq!(
5185 stats.cu_qp_delta_abs_bins, 0,
5186 "cu_qp_delta must not be decoded for a zero-CBF skip CU"
5187 );
5188 assert!(pic.y.iter().all(|&v| v == 200), "skip copy of reference Y");
5190 }
5191
5192 #[test]
5206 fn round100_inter_cu_qp_delta_abs_zero_decodes_as_single_u_bin() {
5207 use crate::cabac::{CabacEncoder, CabacEngine};
5208 let mut enc = CabacEncoder::new();
5209 enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
5212 let rbsp = enc.finish();
5213 let mut eng = CabacEngine::new(&rbsp).unwrap();
5214 let cbf_luma = eng.decode_decision(0, 0).unwrap();
5215 assert_eq!(cbf_luma, 1, "cbf_luma decision");
5216 let qp_delta_abs = eng.decode_u_regular(0, |_| 0).unwrap();
5218 assert_eq!(
5219 qp_delta_abs, 0,
5220 "cu_qp_delta_abs = 0 → single U \"0\" terminator, no sign bit"
5221 );
5222 }
5223
5224 #[test]
5235 fn round100_inter_cu_qp_delta_signed_magnitude_and_clamp() {
5236 let derive = |slice_qp: i32, abs: u32, sign: u8| -> i32 {
5238 let mut qp_delta: i32 = 0;
5239 if abs > 0 {
5240 qp_delta = if sign != 0 { -(abs as i32) } else { abs as i32 };
5241 }
5242 (slice_qp + qp_delta).clamp(0, 51)
5243 };
5244 assert_eq!(derive(22, 3, 0), 25);
5246 assert_eq!(derive(22, 3, 1), 19);
5248 assert_eq!(derive(22, 0, 0), 22);
5250 assert_eq!(derive(1, 5, 1), 0);
5253 assert_eq!(derive(50, 10, 0), 51);
5255 }
5256
5257 #[test]
5281 fn round103_idr_ibc_branch_cu_qp_delta_abs_zero_is_single_u_bin() {
5282 use crate::cabac::{CabacEncoder, CabacEngine};
5283 let mut enc = CabacEncoder::new();
5284 enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
5288 let rbsp = enc.finish();
5289 let mut eng = CabacEngine::new(&rbsp).unwrap();
5290 let qp_delta_abs = eng.decode_u_regular(0, |_| 0).unwrap();
5292 assert_eq!(
5293 qp_delta_abs, 0,
5294 "cu_qp_delta_abs = 0 → single U \"0\" terminator, no sign bit"
5295 );
5296 }
5297
5298 #[test]
5304 fn round103_ibc_cu_qp_delta_signed_magnitude_and_clamp() {
5305 let derive = |slice_qp: i32, abs: u32, sign: u8| -> i32 {
5306 let mut qp_delta: i32 = 0;
5307 if abs > 0 {
5308 qp_delta = if sign != 0 { -(abs as i32) } else { abs as i32 };
5309 }
5310 (slice_qp + qp_delta).clamp(0, 51)
5311 };
5312 assert_eq!(derive(22, 4, 0), 26); assert_eq!(derive(22, 4, 1), 18); assert_eq!(derive(22, 0, 0), 22); assert_eq!(derive(2, 9, 1), 0); assert_eq!(derive(48, 9, 0), 51); }
5318
5319 #[test]
5327 fn round103_idr_ibc_apply_threads_cu_qp_into_residual_scaling() {
5328 let mk_pic = || {
5331 let mut pic = YuvPicture::new(8, 4, 0, 8).unwrap();
5332 for j in 0..4 {
5333 for i in 0..4 {
5334 pic.y[j * 8 + i] = 100; }
5336 }
5337 pic
5338 };
5339 let walk = SliceWalkInputs {
5340 pic_width: 8,
5341 pic_height: 4,
5342 ctb_log2_size_y: 5,
5343 min_cb_log2_size_y: 2,
5344 max_tb_log2_size_y: 5,
5345 chroma_format_idc: 0,
5346 cu_qp_delta_enabled: true,
5347 sps_ibc_flag: true,
5348 log2_max_ibc_cand_size: 5,
5349 ..Default::default()
5350 };
5351 let decode = SliceDecodeInputs {
5352 slice_qp: 22,
5353 sps_ibc_flag: true,
5354 log2_max_ibc_cand_size: 5,
5355 ..Default::default()
5356 };
5357 let mvd = MotionVector { x: -4, y: 0 };
5358 let mut levels = vec![0i32; 16];
5361 levels[0] = 5;
5362 let run = |qp: i32| -> Vec<u8> {
5363 let mut pic = mk_pic();
5364 let mut side_info = SideInfoGrid::new(8, 4);
5365 apply_ibc_branch_predict_and_reconstruct(
5366 &mut pic,
5367 &mut side_info,
5368 &walk,
5369 &decode,
5370 4,
5371 0,
5372 2,
5373 2,
5374 TreeType::DualTreeLuma,
5375 mvd,
5376 1,
5377 &levels,
5378 qp,
5379 )
5380 .unwrap();
5381 (0..4)
5382 .flat_map(|j| (0..4).map(move |i| (j, i)))
5383 .map(|(j, i)| pic.y[j * 8 + (4 + i)])
5384 .collect()
5385 };
5386 let recon_lo = run(22);
5387 let recon_hi = run(40);
5388 assert_ne!(
5389 recon_lo, recon_hi,
5390 "per-CU QP must change the IBC residual reconstruction"
5391 );
5392 let dev = |r: &[u8]| -> i32 { r.iter().map(|&v| (v as i32 - 100).abs()).sum() };
5396 assert!(
5397 dev(&recon_hi) > dev(&recon_lo),
5398 "higher QP → larger residual deviation from the predictor"
5399 );
5400 }
5401
5402 #[test]
5407 fn round103_inter_ibc_apply_threads_cu_qp_into_residual_scaling() {
5408 let mk_pic = || {
5409 let mut pic = YuvPicture::new(8, 4, 0, 8).unwrap();
5410 for j in 0..4 {
5411 for i in 0..4 {
5412 pic.y[j * 8 + i] = 100;
5413 }
5414 }
5415 pic
5416 };
5417 let walk = SliceWalkInputs {
5418 pic_width: 8,
5419 pic_height: 4,
5420 ctb_log2_size_y: 5,
5421 min_cb_log2_size_y: 2,
5422 max_tb_log2_size_y: 5,
5423 chroma_format_idc: 0,
5424 cu_qp_delta_enabled: true,
5425 sps_ibc_flag: true,
5426 log2_max_ibc_cand_size: 5,
5427 ..Default::default()
5428 };
5429 let decode = SliceDecodeInputs {
5430 slice_qp: 22,
5431 sps_ibc_flag: true,
5432 log2_max_ibc_cand_size: 5,
5433 ..Default::default()
5434 };
5435 let mvd = MotionVector { x: -4, y: 0 };
5436 let mut levels = vec![0i32; 16];
5437 levels[0] = 5;
5438 let empty_c: Vec<i32> = Vec::new();
5439 let run = |qp: i32| -> Vec<u8> {
5440 let mut pic = mk_pic();
5441 let mut side_info = SideInfoGrid::new(8, 4);
5442 let mut hmvp = crate::hmvp::HmvpCandList::new();
5443 apply_inter_ibc_branch_predict_and_reconstruct(
5444 &mut pic,
5445 &mut side_info,
5446 &mut hmvp,
5447 &walk,
5448 &decode,
5449 4,
5450 0,
5451 2,
5452 2,
5453 mvd,
5454 1,
5455 &levels,
5456 0,
5457 &empty_c,
5458 0,
5459 &empty_c,
5460 qp,
5461 )
5462 .unwrap();
5463 (0..4)
5464 .flat_map(|j| (0..4).map(move |i| (j, i)))
5465 .map(|(j, i)| pic.y[j * 8 + (4 + i)])
5466 .collect()
5467 };
5468 let recon_lo = run(22);
5469 let recon_hi = run(40);
5470 assert_ne!(
5471 recon_lo, recon_hi,
5472 "per-CU QP must change the inter IBC residual reconstruction"
5473 );
5474 let dev = |r: &[u8]| -> i32 { r.iter().map(|&v| (v as i32 - 100).abs()).sum() };
5475 assert!(
5476 dev(&recon_hi) > dev(&recon_lo),
5477 "higher QP → larger residual deviation from the predictor"
5478 );
5479 }
5480
5481 #[test]
5486 fn round95_inter_decode_skips_ibc_flag_when_cu_exceeds_cand_size() {
5487 use crate::cabac::CabacEncoder;
5488 use crate::inter::RefPictureView;
5489 let ref_y = vec![200u8; 32 * 32];
5490 let ref_cb = vec![128u8; 16 * 16];
5491 let ref_cr = vec![128u8; 16 * 16];
5492 let ref_view = RefPictureView {
5493 y: &ref_y,
5494 cb: &ref_cb,
5495 cr: &ref_cr,
5496 width: 32,
5497 height: 32,
5498 y_stride: 32,
5499 c_stride: 16,
5500 chroma_format_idc: 1,
5501 };
5502 let mut enc = CabacEncoder::new();
5503 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
5514 let rbsp = enc.finish();
5515 let walk = SliceWalkInputs {
5516 pic_width: 32,
5517 pic_height: 32,
5518 ctb_log2_size_y: 5,
5519 min_cb_log2_size_y: 4,
5520 max_tb_log2_size_y: 5,
5521 chroma_format_idc: 1,
5522 cu_qp_delta_enabled: false,
5523 sps_ibc_flag: true,
5524 log2_max_ibc_cand_size: 1, ..Default::default()
5526 };
5527 let decode = SliceDecodeInputs {
5528 slice_qp: 22,
5529 sps_ibc_flag: true,
5530 log2_max_ibc_cand_size: 1,
5531 ..Default::default()
5532 };
5533 let ref_list_l0 = [ref_view];
5534 let inputs = InterDecodeInputs {
5535 walk,
5536 decode,
5537 slice_is_b: false,
5538 num_ref_idx_active_minus1_l0: 0,
5539 num_ref_idx_active_minus1_l1: 0,
5540 ref_list_l0: &ref_list_l0,
5541 ref_list_l1: &[],
5542 };
5543 let (_pic, stats) = decode_baseline_inter_slice(&rbsp, inputs).unwrap();
5544 assert_eq!(
5545 stats.ibc_flag_bins, 0,
5546 "size gate suppresses ibc_flag inside cu_skip path"
5547 );
5548 assert_eq!(stats.ibc_cus, 0);
5549 }
5550
5551 #[test]
5563 fn round95_inter_ibc_branch_predicts_from_left_neighbour() {
5564 let mut pic = YuvPicture::new(8, 4, 0, 8).unwrap();
5565 let cu0_samples: [u8; 16] = [
5566 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160,
5570 ];
5571 for j in 0..4 {
5572 for i in 0..4 {
5573 pic.y[j * 8 + i] = cu0_samples[j * 4 + i];
5574 }
5575 }
5576 let mut side_info = SideInfoGrid::new(8, 4);
5577 let mut hmvp = crate::hmvp::HmvpCandList::new();
5578 let walk = SliceWalkInputs {
5579 pic_width: 8,
5580 pic_height: 4,
5581 ctb_log2_size_y: 5,
5582 min_cb_log2_size_y: 2,
5583 max_tb_log2_size_y: 5,
5584 chroma_format_idc: 0,
5585 cu_qp_delta_enabled: false,
5586 sps_ibc_flag: true,
5587 log2_max_ibc_cand_size: 5,
5588 ..Default::default()
5589 };
5590 let decode = SliceDecodeInputs {
5591 slice_qp: 22,
5592 sps_ibc_flag: true,
5593 log2_max_ibc_cand_size: 5,
5594 ..Default::default()
5595 };
5596 let mvd = MotionVector { x: -4, y: 0 };
5597 let zero_levels = vec![0i32; 16];
5598 let zero_chroma = Vec::<i32>::new();
5599 apply_inter_ibc_branch_predict_and_reconstruct(
5600 &mut pic,
5601 &mut side_info,
5602 &mut hmvp,
5603 &walk,
5604 &decode,
5605 4,
5606 0,
5607 2,
5608 2,
5609 mvd,
5610 0,
5611 &zero_levels,
5612 0,
5613 &zero_chroma,
5614 0,
5615 &zero_chroma,
5616 decode.slice_qp.clamp(0, 51),
5617 )
5618 .unwrap();
5619 for j in 0..4 {
5620 for i in 0..4 {
5621 let expected = cu0_samples[j * 4 + i];
5622 let actual = pic.y[j * 8 + (4 + i)];
5623 assert_eq!(
5624 actual, expected,
5625 "inter IBC copy mismatch at (j={j}, i={i}): expected {expected}, got {actual}"
5626 );
5627 }
5628 }
5629 let cell = side_info.at(1, 0);
5630 assert_eq!(
5631 cell.pred_mode,
5632 CuPredMode::Ibc,
5633 "side-info stamp must mark MODE_IBC inside the inter walker"
5634 );
5635 assert_eq!(cell.mv_l0_x, -64);
5637 assert_eq!(cell.mv_l0_y, 0);
5638 assert_eq!(hmvp.len(), 0, "IBC CU must not append to HMVP list");
5641 }
5642
5643 #[test]
5648 fn round95_inter_ibc_branch_rejects_non_conformant_bv() {
5649 let mut pic = YuvPicture::new(8, 4, 0, 8).unwrap();
5650 let mut side_info = SideInfoGrid::new(8, 4);
5651 let mut hmvp = crate::hmvp::HmvpCandList::new();
5652 let walk = SliceWalkInputs {
5653 pic_width: 8,
5654 pic_height: 4,
5655 ctb_log2_size_y: 5,
5656 min_cb_log2_size_y: 2,
5657 max_tb_log2_size_y: 5,
5658 chroma_format_idc: 0,
5659 cu_qp_delta_enabled: false,
5660 sps_ibc_flag: true,
5661 log2_max_ibc_cand_size: 5,
5662 ..Default::default()
5663 };
5664 let decode = SliceDecodeInputs {
5665 slice_qp: 22,
5666 sps_ibc_flag: true,
5667 log2_max_ibc_cand_size: 5,
5668 ..Default::default()
5669 };
5670 let mvd_overlap = MotionVector { x: 0, y: 0 };
5671 let zero_levels = vec![0i32; 16];
5672 let zero_chroma = Vec::<i32>::new();
5673 let err = apply_inter_ibc_branch_predict_and_reconstruct(
5674 &mut pic,
5675 &mut side_info,
5676 &mut hmvp,
5677 &walk,
5678 &decode,
5679 4,
5680 0,
5681 2,
5682 2,
5683 mvd_overlap,
5684 0,
5685 &zero_levels,
5686 0,
5687 &zero_chroma,
5688 0,
5689 &zero_chroma,
5690 decode.slice_qp.clamp(0, 51),
5691 )
5692 .unwrap_err();
5693 let msg = format!("{err}");
5694 assert!(
5695 msg.contains("ibc") && (msg.contains("above-or-left") || msg.contains("eq. 1113")),
5696 "expected above-or-left conformance error, got: {msg}"
5697 );
5698 assert!(pic.y.iter().all(|&v| v == 128));
5700 assert_eq!(side_info.at(1, 0).pred_mode, CuPredMode::Intra);
5702 assert_eq!(hmvp.len(), 0);
5703 }
5704
5705 #[test]
5710 fn round95_inter_ibc_branch_chroma_residual_roundtrips() {
5711 let mut pic = YuvPicture::new(16, 8, 1, 8).unwrap();
5712 for j in 0..8 {
5715 for i in 0..8 {
5716 pic.y[j * 16 + i] = ((i + j * 8) as u8).wrapping_add(40);
5717 }
5718 }
5719 for j in 0..4 {
5722 for i in 0..4 {
5723 pic.cb[j * 8 + i] = 100;
5724 pic.cr[j * 8 + i] = 150;
5725 }
5726 }
5727 let mut side_info = SideInfoGrid::new(16, 8);
5728 let mut hmvp = crate::hmvp::HmvpCandList::new();
5729 let walk = SliceWalkInputs {
5730 pic_width: 16,
5731 pic_height: 8,
5732 ctb_log2_size_y: 5,
5733 min_cb_log2_size_y: 2,
5734 max_tb_log2_size_y: 5,
5735 chroma_format_idc: 1,
5736 cu_qp_delta_enabled: false,
5737 sps_ibc_flag: true,
5738 log2_max_ibc_cand_size: 5,
5739 ..Default::default()
5740 };
5741 let decode = SliceDecodeInputs {
5742 slice_qp: 22,
5743 sps_ibc_flag: true,
5744 log2_max_ibc_cand_size: 5,
5745 ..Default::default()
5746 };
5747 let mvd = MotionVector { x: -8, y: 0 };
5748 let zero_y = vec![0i32; 64];
5751 let zero_c = vec![0i32; 16];
5752 apply_inter_ibc_branch_predict_and_reconstruct(
5753 &mut pic,
5754 &mut side_info,
5755 &mut hmvp,
5756 &walk,
5757 &decode,
5758 8,
5759 0,
5760 3,
5761 3,
5762 mvd,
5763 0,
5764 &zero_y,
5765 0,
5766 &zero_c,
5767 0,
5768 &zero_c,
5769 decode.slice_qp.clamp(0, 51),
5770 )
5771 .unwrap();
5772 for j in 0..8 {
5774 for i in 0..8 {
5775 let expected = ((i + j * 8) as u8).wrapping_add(40);
5776 let actual = pic.y[j * 16 + (8 + i)];
5777 assert_eq!(
5778 actual, expected,
5779 "luma copy mismatch at (i={i}, j={j}): expected {expected}, got {actual}"
5780 );
5781 }
5782 }
5783 for j in 0..4 {
5785 for i in 0..4 {
5786 assert_eq!(pic.cb[j * 8 + (4 + i)], 100, "Cb copy at ({i},{j})");
5787 assert_eq!(pic.cr[j * 8 + (4 + i)], 150, "Cr copy at ({i},{j})");
5788 }
5789 }
5790 let cell = side_info.at(2, 0);
5792 assert_eq!(cell.pred_mode, CuPredMode::Ibc);
5793 assert_eq!(hmvp.len(), 0);
5794 }
5795
5796 #[test]
5805 fn round107_ctu_alf_no_map_consumes_no_bins() {
5806 use crate::cabac::CabacEncoder;
5807 let mut enc = CabacEncoder::new();
5808 enc.encode_terminate(true);
5810 let rbsp = enc.finish();
5811 let mut eng = CabacEngine::new(&rbsp).unwrap();
5812 let inputs = SliceWalkInputs::default(); let mut stats = AlfCtbStats::default();
5814 let flags = decode_coding_tree_unit_alf(&mut eng, &inputs, &mut stats).unwrap();
5815 assert_eq!(stats.luma_bins, 0);
5816 assert_eq!(stats.chroma_cb_bins, 0);
5817 assert_eq!(stats.chroma_cr_bins, 0);
5818 assert_eq!(stats.luma_on_ctus, 0);
5819 assert!(!flags.luma);
5820 assert!(eng.decode_terminate().unwrap());
5822 }
5823
5824 #[test]
5829 fn round107_ctu_alf_map_without_enable_infers_off() {
5830 use crate::cabac::CabacEncoder;
5831 let mut enc = CabacEncoder::new();
5832 enc.encode_terminate(true);
5833 let rbsp = enc.finish();
5834 let mut eng = CabacEngine::new(&rbsp).unwrap();
5835 let inputs = SliceWalkInputs {
5836 slice_alf_enabled_flag: false,
5837 slice_alf_map_flag: true,
5838 ..Default::default()
5839 };
5840 let mut stats = AlfCtbStats::default();
5841 let flags = decode_coding_tree_unit_alf(&mut eng, &inputs, &mut stats).unwrap();
5842 assert_eq!(stats.luma_bins, 0, "enable off ⇒ no luma bin");
5843 assert!(!flags.luma);
5844 }
5845
5846 #[test]
5851 fn round107_ctu_alf_luma_map_reads_one_bin() {
5852 use crate::cabac::CabacEncoder;
5853 let mut enc = CabacEncoder::new();
5855 enc.encode_decision(0, 0, 1);
5856 enc.encode_decision(0, 0, 0);
5857 enc.encode_terminate(true);
5858 let rbsp = enc.finish();
5859 let mut eng = CabacEngine::new(&rbsp).unwrap();
5860 let inputs = SliceWalkInputs {
5861 slice_alf_enabled_flag: true,
5862 slice_alf_map_flag: true,
5863 ..Default::default()
5864 };
5865 let mut stats = AlfCtbStats::default();
5866 let first = decode_coding_tree_unit_alf(&mut eng, &inputs, &mut stats).unwrap();
5867 assert_eq!(stats.luma_bins, 1);
5868 assert_eq!(stats.luma_on_ctus, 1);
5869 assert!(first.luma, "coded 1 ⇒ ALF on");
5870 assert_eq!(stats.chroma_cb_bins, 0, "Baseline: no chroma map bin");
5871 assert_eq!(stats.chroma_cr_bins, 0);
5872 let second = decode_coding_tree_unit_alf(&mut eng, &inputs, &mut stats).unwrap();
5873 assert_eq!(stats.luma_bins, 2);
5874 assert_eq!(stats.luma_on_ctus, 1, "second CTB coded 0 ⇒ still 1 on");
5875 assert!(!second.luma, "coded 0 ⇒ ALF off");
5876 assert!(eng.decode_terminate().unwrap());
5877 }
5878
5879 #[test]
5884 fn round107_ctu_alf_chroma3_reads_three_bins() {
5885 use crate::cabac::CabacEncoder;
5886 let mut enc = CabacEncoder::new();
5887 enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0);
5893 enc.encode_decision(0, 0, 0);
5894 enc.encode_terminate(true);
5895 let rbsp = enc.finish();
5896 let mut eng = CabacEngine::new(&rbsp).unwrap();
5897 let inputs = SliceWalkInputs {
5898 slice_alf_enabled_flag: true,
5899 slice_alf_map_flag: true,
5900 slice_chroma_alf_enabled_flag: true,
5901 slice_alf_chroma_map_flag: true,
5902 slice_chroma2_alf_enabled_flag: true,
5903 slice_alf_chroma2_map_flag: true,
5904 ..Default::default()
5905 };
5906 let mut stats = AlfCtbStats::default();
5907 let flags = decode_coding_tree_unit_alf(&mut eng, &inputs, &mut stats).unwrap();
5908 assert_eq!(stats.luma_bins, 1);
5909 assert_eq!(stats.chroma_cb_bins, 1);
5910 assert_eq!(stats.chroma_cr_bins, 1);
5911 assert!(flags.luma);
5912 assert!(!flags.chroma_cb);
5913 assert!(flags.chroma_cr);
5914 }
5915
5916 #[test]
5925 fn round107_idr_decode_reads_alf_ctb_flag_bin() {
5926 use crate::cabac::CabacEncoder;
5927 let mut enc = CabacEncoder::new();
5928 enc.encode_decision(0, 0, 1);
5930 enc.encode_decision(0, 0, 1);
5932 for _ in 0..4 {
5934 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
5937 enc.encode_terminate(true);
5938 let rbsp = enc.finish();
5939
5940 let walk = SliceWalkInputs {
5941 pic_width: 32,
5942 pic_height: 32,
5943 ctb_log2_size_y: 5,
5944 min_cb_log2_size_y: 4,
5945 max_tb_log2_size_y: 5,
5946 chroma_format_idc: 0,
5947 cu_qp_delta_enabled: false,
5948 slice_alf_enabled_flag: true,
5949 slice_alf_map_flag: true,
5950 ..Default::default()
5951 };
5952 let decode = SliceDecodeInputs {
5953 slice_qp: 22,
5954 ..Default::default()
5955 };
5956 let (pic, stats) = decode_baseline_idr_slice(&rbsp, walk, decode).unwrap();
5957 assert_eq!(stats.ctus, 1);
5958 assert_eq!(stats.alf_ctb.luma_bins, 1, "one alf_ctb_flag bin consumed");
5959 assert_eq!(stats.alf_ctb.luma_on_ctus, 1);
5960 assert_eq!(stats.alf_ctb.chroma_cb_bins, 0);
5961 assert_eq!(stats.split_cu_flag_bins, 1);
5962 assert_eq!(stats.intra_pred_mode_bins, 4);
5963 assert_eq!(stats.cbf_luma_bins, 4);
5964 assert!(pic.y.iter().all(|&v| v == 128), "grey IDR DC pred");
5965 }
5966
5967 #[test]
5972 fn round107_idr_decode_without_alf_map_reads_no_alf_bins() {
5973 use crate::cabac::CabacEncoder;
5974 let mut enc = CabacEncoder::new();
5975 enc.encode_decision(0, 0, 1); for _ in 0..4 {
5977 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
5980 enc.encode_terminate(true);
5981 let rbsp = enc.finish();
5982 let walk = SliceWalkInputs {
5983 pic_width: 32,
5984 pic_height: 32,
5985 ctb_log2_size_y: 5,
5986 min_cb_log2_size_y: 4,
5987 max_tb_log2_size_y: 5,
5988 chroma_format_idc: 0,
5989 ..Default::default()
5990 };
5991 let decode = SliceDecodeInputs {
5992 slice_qp: 22,
5993 ..Default::default()
5994 };
5995 let (_pic, stats) = decode_baseline_idr_slice(&rbsp, walk, decode).unwrap();
5996 assert_eq!(stats.alf_ctb.luma_bins, 0);
5997 assert_eq!(stats.alf_ctb.luma_on_ctus, 0);
5998 assert_eq!(stats.split_cu_flag_bins, 1);
5999 assert_eq!(stats.cbf_luma_bins, 4);
6000 }
6001
6002 #[test]
6007 fn round113_idr_decode_populates_alf_ctb_map() {
6008 use crate::cabac::CabacEncoder;
6009 let mut enc = CabacEncoder::new();
6010 enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 1); for _ in 0..4 {
6013 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
6016 enc.encode_terminate(true);
6017 let rbsp = enc.finish();
6018 let walk = SliceWalkInputs {
6019 pic_width: 32,
6020 pic_height: 32,
6021 ctb_log2_size_y: 5,
6022 min_cb_log2_size_y: 4,
6023 max_tb_log2_size_y: 5,
6024 chroma_format_idc: 0,
6025 slice_alf_enabled_flag: true,
6026 slice_alf_map_flag: true,
6027 ..Default::default()
6028 };
6029 let decode = SliceDecodeInputs {
6030 slice_qp: 22,
6031 ..Default::default()
6032 };
6033 let (_pic, stats) = decode_baseline_idr_slice(&rbsp, walk, decode).unwrap();
6034 let map = &stats.alf_ctb_map;
6035 assert_eq!(map.ctbs_wide, 1);
6036 assert_eq!(map.ctbs_high, 1);
6037 assert_eq!(map.luma.len(), 1);
6038 assert!(map.luma[0], "CTU 0 luma alf_ctb_flag recorded on");
6039 assert!(map.any_luma_on());
6040 }
6041
6042 #[test]
6047 fn round113_idr_two_ctb_map_drives_masked_alf_apply() {
6048 use crate::cabac::CabacEncoder;
6049 let mut enc = CabacEncoder::new();
6050 enc.encode_decision(0, 0, 1); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_terminate(true);
6060 let rbsp = enc.finish();
6061 let walk = SliceWalkInputs {
6062 pic_width: 64,
6063 pic_height: 32,
6064 ctb_log2_size_y: 5,
6065 min_cb_log2_size_y: 5,
6066 max_tb_log2_size_y: 5,
6067 chroma_format_idc: 0,
6068 slice_alf_enabled_flag: true,
6069 slice_alf_map_flag: true,
6070 ..Default::default()
6071 };
6072 let decode = SliceDecodeInputs {
6073 slice_qp: 22,
6074 ..Default::default()
6075 };
6076 let (mut pic, stats) = decode_baseline_idr_slice(&rbsp, walk, decode).unwrap();
6077 let map = &stats.alf_ctb_map;
6078 assert_eq!(map.ctbs_wide, 2);
6079 assert!(map.luma[0], "left CTB ALF on");
6080 assert!(!map.luma[1], "right CTB ALF off");
6081 assert_eq!(stats.alf_ctb.luma_bins, 2, "two alf_ctb_flag bins");
6082 assert_eq!(stats.alf_ctb.luma_on_ctus, 1);
6083
6084 let mut filter = crate::alf::AlfLumaFilter { coef: [0; 13] };
6088 filter.coef[12] = 8;
6091 crate::alf::apply_alf_luma_masked(&mut pic, &filter, map, 8);
6092 let stride = pic.y_stride();
6093 for row in 0..32usize {
6094 for col in 0..32usize {
6095 assert_eq!(pic.y[row * stride + col], 2, "left CTB filtered");
6096 }
6097 for col in 32..64usize {
6098 assert_eq!(pic.y[row * stride + col], 128, "right CTB untouched");
6099 }
6100 }
6101 }
6102
6103 use crate::pps::{
6109 compute_col_bd, compute_col_widths, compute_ctb_addr_rs_to_ts, compute_ctb_addr_ts_to_rs,
6110 compute_num_ctus_in_tile, compute_row_bd, compute_row_heights, compute_tile_index_maps,
6111 };
6112
6113 fn uniform_tile_lists(
6117 cols_minus1: u32,
6118 rows_minus1: u32,
6119 pic_w_ctbs: u32,
6120 pic_h_ctbs: u32,
6121 ) -> (Vec<u32>, Vec<u32>, Vec<u32>, u32) {
6122 let col_w = compute_col_widths(true, cols_minus1, &[], pic_w_ctbs);
6123 let row_h = compute_row_heights(true, rows_minus1, &[], pic_h_ctbs);
6124 let col_bd = compute_col_bd(&col_w);
6125 let row_bd = compute_row_bd(&row_h);
6126 let rs_to_ts = compute_ctb_addr_rs_to_ts(&col_w, &row_h, &col_bd, &row_bd, pic_w_ctbs);
6127 let ts_to_rs = compute_ctb_addr_ts_to_rs(&rs_to_ts);
6128 let num_ctus = compute_num_ctus_in_tile(&col_w, &row_h);
6129 let tile_id: Vec<u32> = {
6131 let mut v = Vec::new();
6134 for (idx, &n) in num_ctus.iter().enumerate() {
6135 for _ in 0..n {
6136 v.push(idx as u32);
6137 }
6138 }
6139 v
6140 };
6141 let maps = compute_tile_index_maps(&tile_id);
6142 (maps.first_ctb_addr_ts, num_ctus, ts_to_rs, pic_w_ctbs)
6143 }
6144
6145 #[test]
6146 fn round292_slice_tile_walk_single_tile_is_raster_order() {
6147 let (first, num_ctus, ts_to_rs, _pw) = uniform_tile_lists(0, 0, 3, 2);
6150 let order = resolve_slice_tile_walk_order(&[0], &first, &num_ctus, &ts_to_rs).unwrap();
6151 assert_eq!(order.segments.len(), 1);
6152 let seg = &order.segments[0];
6153 assert_eq!(seg.tile_idx, 0);
6154 assert_eq!(seg.first_ctb_addr_ts, 0);
6155 assert_eq!(seg.num_ctus, 6);
6156 assert_eq!(seg.ctb_addr_in_rs, vec![0, 1, 2, 3, 4, 5]);
6157 assert!(
6158 !seg.byte_align_after,
6159 "last (only) tile has no byte_alignment"
6160 );
6161 assert_eq!(order.total_ctus(), 6);
6162 assert_eq!(order.ctb_addr_in_rs_flat(), vec![0, 1, 2, 3, 4, 5]);
6163 }
6164
6165 #[test]
6166 fn round292_slice_tile_walk_full_picture_3x2_grid_hand_trace() {
6167 let (first, num_ctus, ts_to_rs, pw) = uniform_tile_lists(2, 1, 6, 4);
6172 assert_eq!(pw, 6);
6173 assert_eq!(first, vec![0, 4, 8, 12, 16, 20]);
6174 assert_eq!(num_ctus, vec![4, 4, 4, 4, 4, 4]);
6175 let slice_tile_idx = vec![0, 1, 2, 3, 4, 5];
6177 let order =
6178 resolve_slice_tile_walk_order(&slice_tile_idx, &first, &num_ctus, &ts_to_rs).unwrap();
6179 assert_eq!(order.segments.len(), 6);
6180 assert_eq!(order.total_ctus(), 24);
6181 assert_eq!(order.segments[0].ctb_addr_in_rs, vec![0, 1, 6, 7]);
6183 assert_eq!(order.segments[1].ctb_addr_in_rs, vec![2, 3, 8, 9]);
6185 assert_eq!(order.segments[5].ctb_addr_in_rs, vec![16, 17, 22, 23]);
6187 for (i, seg) in order.segments.iter().enumerate() {
6189 assert_eq!(seg.byte_align_after, i + 1 < 6, "segment {i} byte_align");
6190 }
6191 let mut flat = order.ctb_addr_in_rs_flat();
6193 assert_eq!(flat.len(), 24);
6194 flat.sort_unstable();
6195 assert_eq!(flat, (0..24).collect::<Vec<u32>>());
6196 }
6197
6198 #[test]
6199 fn round292_slice_tile_walk_sub_rectangle_two_tiles() {
6200 let (first, num_ctus, ts_to_rs, _pw) = uniform_tile_lists(2, 1, 6, 4);
6203 let order = resolve_slice_tile_walk_order(&[1, 4], &first, &num_ctus, &ts_to_rs).unwrap();
6204 assert_eq!(order.segments.len(), 2);
6205 assert_eq!(order.total_ctus(), 8);
6206 assert_eq!(order.segments[0].tile_idx, 1);
6207 assert_eq!(order.segments[0].ctb_addr_in_rs, vec![2, 3, 8, 9]);
6208 assert!(order.segments[0].byte_align_after);
6209 assert_eq!(order.segments[1].tile_idx, 4);
6210 assert_eq!(order.segments[1].ctb_addr_in_rs, vec![14, 15, 20, 21]);
6212 assert!(!order.segments[1].byte_align_after);
6213 }
6214
6215 #[test]
6216 fn round292_slice_tile_walk_matches_single_tile_raster_walker() {
6217 let (first, num_ctus, ts_to_rs, _pw) = uniform_tile_lists(0, 0, 4, 3);
6221 let order = resolve_slice_tile_walk_order(&[0], &first, &num_ctus, &ts_to_rs).unwrap();
6222 let expected: Vec<u32> = (0..12).collect();
6223 assert_eq!(order.ctb_addr_in_rs_flat(), expected);
6224 }
6225
6226 #[test]
6227 fn round292_slice_tile_walk_consumes_slice_header_indices() {
6228 use crate::slice_header::{compute_slice_tile_dims, compute_slice_tile_indices};
6232 let cols_minus1 = 2u32;
6233 let rows_minus1 = 1u32;
6234 let pic_w_ctbs = 6u32;
6235 let pic_h_ctbs = 4u32;
6236 let col_w = compute_col_widths(true, cols_minus1, &[], pic_w_ctbs);
6237 let row_h = compute_row_heights(true, rows_minus1, &[], pic_h_ctbs);
6238 let col_bd = compute_col_bd(&col_w);
6239 let row_bd = compute_row_bd(&row_h);
6240 let rs_to_ts = compute_ctb_addr_rs_to_ts(&col_w, &row_h, &col_bd, &row_bd, pic_w_ctbs);
6241 let ts_to_rs = compute_ctb_addr_ts_to_rs(&rs_to_ts);
6242 let num_ctus = compute_num_ctus_in_tile(&col_w, &row_h);
6243 let mut tile_id = Vec::new();
6244 for (idx, &n) in num_ctus.iter().enumerate() {
6245 for _ in 0..n {
6246 tile_id.push(idx as u32);
6247 }
6248 }
6249 let maps = compute_tile_index_maps(&tile_id);
6250 let num_tiles_in_pic = (cols_minus1 + 1) * (rows_minus1 + 1);
6251 let dims = compute_slice_tile_dims(1, 4, &maps, cols_minus1, num_tiles_in_pic).unwrap();
6254 let slice_tile_idx =
6255 compute_slice_tile_indices(1, &maps, cols_minus1, num_tiles_in_pic, &dims).unwrap();
6256 assert_eq!(slice_tile_idx, vec![1, 4]);
6257 let order = resolve_slice_tile_walk_order(
6258 &slice_tile_idx,
6259 &maps.first_ctb_addr_ts,
6260 &num_ctus,
6261 &ts_to_rs,
6262 )
6263 .unwrap();
6264 assert_eq!(order.total_ctus(), 8);
6265 assert_eq!(order.segments[0].ctb_addr_in_rs, vec![2, 3, 8, 9]);
6266 assert_eq!(order.segments[1].ctb_addr_in_rs, vec![14, 15, 20, 21]);
6267 }
6268
6269 #[test]
6270 fn round292_slice_tile_walk_rejects_out_of_range_tile_idx() {
6271 let (first, num_ctus, ts_to_rs, _pw) = uniform_tile_lists(0, 0, 3, 2);
6272 let err = resolve_slice_tile_walk_order(&[1], &first, &num_ctus, &ts_to_rs).unwrap_err();
6274 assert!(
6275 format!("{err}").contains("out of FirstCtbAddrTs range"),
6276 "got: {err}"
6277 );
6278 }
6279
6280 #[test]
6281 fn round292_slice_tile_walk_rejects_ts_overrun() {
6282 let first = vec![0u32];
6286 let num_ctus = vec![10u32];
6287 let ts_to_rs = vec![0u32, 1, 2, 3]; let err = resolve_slice_tile_walk_order(&[0], &first, &num_ctus, &ts_to_rs).unwrap_err();
6289 assert!(
6290 format!("{err}").contains("overruns CtbAddrTsToRs"),
6291 "got: {err}"
6292 );
6293 }
6294
6295 #[test]
6296 fn round292_slice_tile_walk_empty_slice_is_empty_order() {
6297 let order = resolve_slice_tile_walk_order(&[], &[0], &[1], &[0]).unwrap();
6298 assert!(order.segments.is_empty());
6299 assert_eq!(order.total_ctus(), 0);
6300 assert!(order.ctb_addr_in_rs_flat().is_empty());
6301 }
6302
6303 fn uniform_tile_maps(
6314 cols_minus1: u32,
6315 rows_minus1: u32,
6316 pic_w_ctbs: u32,
6317 pic_h_ctbs: u32,
6318 ) -> (Vec<u32>, Vec<u32>, crate::pps::TileIndexMaps, Vec<u32>, u32) {
6319 let col_w = compute_col_widths(true, cols_minus1, &[], pic_w_ctbs);
6320 let row_h = compute_row_heights(true, rows_minus1, &[], pic_h_ctbs);
6321 let col_bd = compute_col_bd(&col_w);
6322 let row_bd = compute_row_bd(&row_h);
6323 let rs_to_ts = compute_ctb_addr_rs_to_ts(&col_w, &row_h, &col_bd, &row_bd, pic_w_ctbs);
6324 let ts_to_rs = compute_ctb_addr_ts_to_rs(&rs_to_ts);
6325 let tile_id = crate::pps::compute_tile_id(&col_bd, &row_bd, &rs_to_ts, pic_w_ctbs, None);
6327 let maps = compute_tile_index_maps(&tile_id);
6328 (rs_to_ts, tile_id, maps, ts_to_rs, pic_w_ctbs)
6329 }
6330
6331 #[test]
6332 fn round309_x_first_ctb_single_tile_is_left_column() {
6333 let (rs_to_ts, tile_id, maps, ts_to_rs, pw) = uniform_tile_maps(0, 0, 3, 2);
6338 for rs in 0..6u32 {
6339 let x_first =
6340 derive_x_first_ctb(rs, &rs_to_ts, &tile_id, &maps, &ts_to_rs, pw, 5).unwrap();
6341 assert_eq!(x_first, 0, "single-tile CtbAddrInRs {rs} → xFirstCtb 0");
6342 }
6343 }
6344
6345 #[test]
6346 fn round309_x_first_ctb_multi_tile_hand_trace() {
6347 let (rs_to_ts, tile_id, maps, ts_to_rs, pw) = uniform_tile_maps(2, 1, 6, 4);
6352 assert_eq!(pw, 6);
6353 let cases = [
6357 (0u32, 0u32), (1, 0), (2, 64), (3, 64), (4, 128), (5, 128), (6, 0), (9, 64), (16, 128), (23, 128), ];
6368 for (rs, expected) in cases {
6369 let x_first =
6370 derive_x_first_ctb(rs, &rs_to_ts, &tile_id, &maps, &ts_to_rs, pw, 5).unwrap();
6371 assert_eq!(x_first, expected, "CtbAddrInRs {rs}");
6372 }
6373 }
6374
6375 #[test]
6376 fn round309_x_first_ctb_ctb_log2_scales_the_column() {
6377 let (rs_to_ts, tile_id, maps, ts_to_rs, pw) = uniform_tile_maps(2, 1, 6, 4);
6380 assert_eq!(
6381 derive_x_first_ctb(2, &rs_to_ts, &tile_id, &maps, &ts_to_rs, pw, 6).unwrap(),
6382 128
6383 );
6384 assert_eq!(
6385 derive_x_first_ctb(4, &rs_to_ts, &tile_id, &maps, &ts_to_rs, pw, 6).unwrap(),
6386 256
6387 );
6388 }
6389
6390 #[test]
6391 fn round309_x_first_ctb_agrees_with_tiled_walk_segment_shortcut() {
6392 let (rs_to_ts, tile_id, maps, ts_to_rs, pw) = uniform_tile_maps(2, 1, 6, 4);
6398 let col_w = compute_col_widths(true, 2, &[], 6);
6399 let row_h = compute_row_heights(true, 1, &[], 4);
6400 let num_ctus = compute_num_ctus_in_tile(&col_w, &row_h);
6401 let slice_tile_idx = vec![0u32, 1, 2, 3, 4, 5];
6402 let order = resolve_slice_tile_walk_order(
6403 &slice_tile_idx,
6404 &maps.first_ctb_addr_ts,
6405 &num_ctus,
6406 &ts_to_rs,
6407 )
6408 .unwrap();
6409 for seg in &order.segments {
6410 let first_rs = *seg.ctb_addr_in_rs.first().unwrap();
6412 let shortcut_x_first = (first_rs % pw) << 5;
6413 for &rs in &seg.ctb_addr_in_rs {
6414 let derived =
6415 derive_x_first_ctb(rs, &rs_to_ts, &tile_id, &maps, &ts_to_rs, pw, 5).unwrap();
6416 assert_eq!(
6417 derived, shortcut_x_first,
6418 "tile {} CtbAddrInRs {rs}: derived xFirstCtb must match segment shortcut",
6419 seg.tile_idx
6420 );
6421 }
6422 }
6423 }
6424
6425 #[test]
6426 fn round309_x_first_ctb_explicit_tile_ids_resolve_through_tile_id_to_idx() {
6427 let col_w = compute_col_widths(true, 2, &[], 6);
6433 let row_h = compute_row_heights(true, 1, &[], 4);
6434 let col_bd = compute_col_bd(&col_w);
6435 let row_bd = compute_row_bd(&row_h);
6436 let rs_to_ts = compute_ctb_addr_rs_to_ts(&col_w, &row_h, &col_bd, &row_bd, 6);
6437 let ts_to_rs = compute_ctb_addr_ts_to_rs(&rs_to_ts);
6438 let explicit = [10u32, 20, 30, 40, 50, 60];
6439 let tile_id = crate::pps::compute_tile_id(&col_bd, &row_bd, &rs_to_ts, 6, Some(&explicit));
6440 let maps = compute_tile_index_maps(&tile_id);
6441 assert_eq!(
6443 derive_x_first_ctb(2, &rs_to_ts, &tile_id, &maps, &ts_to_rs, 6, 5).unwrap(),
6444 64
6445 );
6446 assert_eq!(
6448 derive_x_first_ctb(4, &rs_to_ts, &tile_id, &maps, &ts_to_rs, 6, 5).unwrap(),
6449 128
6450 );
6451 assert_eq!(
6453 derive_x_first_ctb(23, &rs_to_ts, &tile_id, &maps, &ts_to_rs, 6, 5).unwrap(),
6454 128
6455 );
6456 }
6457
6458 #[test]
6459 fn round309_x_first_ctb_rejects_out_of_range_raster_address() {
6460 let (rs_to_ts, tile_id, maps, ts_to_rs, pw) = uniform_tile_maps(0, 0, 3, 2);
6461 let err = derive_x_first_ctb(6, &rs_to_ts, &tile_id, &maps, &ts_to_rs, pw, 5).unwrap_err();
6463 assert!(
6464 format!("{err}").contains("out of CtbAddrRsToTs range"),
6465 "got: {err}"
6466 );
6467 }
6468
6469 #[test]
6470 fn round309_x_first_ctb_rejects_zero_pic_width() {
6471 let (rs_to_ts, tile_id, maps, ts_to_rs, _pw) = uniform_tile_maps(0, 0, 3, 2);
6472 let err = derive_x_first_ctb(0, &rs_to_ts, &tile_id, &maps, &ts_to_rs, 0, 5).unwrap_err();
6473 assert!(
6474 format!("{err}").contains("PicWidthInCtbsY == 0"),
6475 "got: {err}"
6476 );
6477 }
6478
6479 #[test]
6480 fn round309_x_first_ctb_rejects_unknown_tile_id() {
6481 let (rs_to_ts, _tile_id, _maps, ts_to_rs, pw) = uniform_tile_maps(0, 0, 3, 2);
6485 let bogus_tile_id = vec![99u32; 6];
6486 let empty_maps = compute_tile_index_maps(&[]); let err = derive_x_first_ctb(0, &rs_to_ts, &bogus_tile_id, &empty_maps, &ts_to_rs, pw, 5)
6488 .unwrap_err();
6489 assert!(
6490 format!("{err}").contains("names no tile in TileIdToIdx"),
6491 "got: {err}"
6492 );
6493 }
6494
6495 fn encode_one_split_ctu_tile_subset() -> Vec<u8> {
6505 use crate::cabac::CabacEncoder;
6506 let mut enc = CabacEncoder::new();
6507 enc.encode_decision(0, 0, 1); for _ in 0..4 {
6509 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
6514 enc.encode_terminate(true);
6515 enc.finish()
6516 }
6517
6518 fn two_tile_inputs() -> SliceWalkInputs {
6519 SliceWalkInputs {
6521 pic_width: 64,
6522 pic_height: 32,
6523 ctb_log2_size_y: 5,
6524 min_cb_log2_size_y: 4,
6525 max_tb_log2_size_y: 5,
6526 chroma_format_idc: 1,
6527 cu_qp_delta_enabled: false,
6528 ..Default::default()
6529 }
6530 }
6531
6532 #[test]
6533 fn round298_tiled_walk_two_tiles_decodes_both_subsets() {
6534 let sub0 = encode_one_split_ctu_tile_subset();
6537 let sub1 = encode_one_split_ctu_tile_subset();
6538 let split = sub0.len();
6539 let mut rbsp = sub0;
6540 rbsp.extend_from_slice(&sub1);
6541 let subset_ranges = vec![0..split, split..rbsp.len()];
6542
6543 let order = SliceTileWalkOrder {
6546 segments: vec![
6547 SliceTileWalkSegment {
6548 tile_idx: 0,
6549 first_ctb_addr_ts: 0,
6550 num_ctus: 1,
6551 ctb_addr_in_rs: vec![0],
6552 byte_align_after: true,
6553 },
6554 SliceTileWalkSegment {
6555 tile_idx: 1,
6556 first_ctb_addr_ts: 1,
6557 num_ctus: 1,
6558 ctb_addr_in_rs: vec![1],
6559 byte_align_after: false,
6560 },
6561 ],
6562 };
6563
6564 let stats = walk_baseline_idr_slice_tiled(&rbsp, two_tile_inputs(), &order, &subset_ranges)
6565 .unwrap();
6566 assert_eq!(stats.ctus, 2);
6568 assert_eq!(stats.split_cu_flag_bins, 2); assert_eq!(stats.coding_units, 16); assert_eq!(stats.intra_pred_mode_bins, 8);
6571 assert_eq!(stats.cbf_luma_bins, 8);
6572 assert_eq!(stats.cbf_chroma_bins, 16);
6573 assert_eq!(stats.end_of_tile_bits, 2);
6576 assert_eq!(stats.tile_byte_alignments, 1);
6577 }
6578
6579 #[test]
6580 fn round298_tiled_walk_single_tile_matches_raster_walker() {
6581 let inputs = SliceWalkInputs {
6584 pic_width: 32,
6585 pic_height: 32,
6586 ctb_log2_size_y: 5,
6587 min_cb_log2_size_y: 4,
6588 max_tb_log2_size_y: 5,
6589 chroma_format_idc: 1,
6590 cu_qp_delta_enabled: false,
6591 ..Default::default()
6592 };
6593 let rbsp = encode_one_split_ctu_tile_subset();
6594 let raster = walk_baseline_idr_slice(&rbsp, inputs).unwrap();
6595
6596 let order = SliceTileWalkOrder {
6597 segments: vec![SliceTileWalkSegment {
6598 tile_idx: 0,
6599 first_ctb_addr_ts: 0,
6600 num_ctus: 1,
6601 ctb_addr_in_rs: vec![0],
6602 byte_align_after: false,
6603 }],
6604 };
6605 let range = 0..rbsp.len();
6606 let ranges = core::slice::from_ref(&range);
6607 let tiled = walk_baseline_idr_slice_tiled(&rbsp, inputs, &order, ranges).unwrap();
6608
6609 assert_eq!(tiled.ctus, raster.ctus);
6610 assert_eq!(tiled.split_cu_flag_bins, raster.split_cu_flag_bins);
6611 assert_eq!(tiled.coding_units, raster.coding_units);
6612 assert_eq!(tiled.cbf_luma_bins, raster.cbf_luma_bins);
6613 assert_eq!(tiled.cbf_chroma_bins, raster.cbf_chroma_bins);
6614 assert_eq!(tiled.end_of_tile_bits, raster.end_of_tile_bits);
6615 assert_eq!(tiled.end_of_tile_bits, 1);
6616 assert_eq!(tiled.tile_byte_alignments, 0);
6617 }
6618
6619 #[test]
6620 fn round298_tiled_walk_rejects_subset_count_mismatch() {
6621 let order = SliceTileWalkOrder {
6622 segments: vec![
6623 SliceTileWalkSegment {
6624 tile_idx: 0,
6625 first_ctb_addr_ts: 0,
6626 num_ctus: 1,
6627 ctb_addr_in_rs: vec![0],
6628 byte_align_after: true,
6629 },
6630 SliceTileWalkSegment {
6631 tile_idx: 1,
6632 first_ctb_addr_ts: 1,
6633 num_ctus: 1,
6634 ctb_addr_in_rs: vec![1],
6635 byte_align_after: false,
6636 },
6637 ],
6638 };
6639 let range = 0..8;
6641 let ranges = core::slice::from_ref(&range);
6642 let err = walk_baseline_idr_slice_tiled(&[0u8; 8], two_tile_inputs(), &order, ranges)
6643 .unwrap_err();
6644 assert!(
6645 format!("{err}").contains("tile subset ranges for"),
6646 "got: {err}"
6647 );
6648 }
6649
6650 #[test]
6651 fn round298_tiled_walk_rejects_subset_range_out_of_bounds() {
6652 let order = SliceTileWalkOrder {
6653 segments: vec![SliceTileWalkSegment {
6654 tile_idx: 0,
6655 first_ctb_addr_ts: 0,
6656 num_ctus: 1,
6657 ctb_addr_in_rs: vec![0],
6658 byte_align_after: false,
6659 }],
6660 };
6661 let range = 0..16;
6663 let ranges = core::slice::from_ref(&range);
6664 let err = walk_baseline_idr_slice_tiled(&[0u8; 4], two_tile_inputs(), &order, ranges)
6665 .unwrap_err();
6666 assert!(
6667 format!("{err}").contains("outside slice data"),
6668 "got: {err}"
6669 );
6670 }
6671
6672 #[test]
6673 fn round298_tiled_walk_rejects_ctb_addr_outside_picture() {
6674 let sub = encode_one_split_ctu_tile_subset();
6675 let order = SliceTileWalkOrder {
6677 segments: vec![SliceTileWalkSegment {
6678 tile_idx: 0,
6679 first_ctb_addr_ts: 0,
6680 num_ctus: 1,
6681 ctb_addr_in_rs: vec![99],
6682 byte_align_after: false,
6683 }],
6684 };
6685 let range = 0..sub.len();
6686 let ranges = core::slice::from_ref(&range);
6687 let err =
6688 walk_baseline_idr_slice_tiled(&sub, two_tile_inputs(), &order, ranges).unwrap_err();
6689 assert!(format!("{err}").contains("CtbAddrInRs 99"), "got: {err}");
6690 }
6691
6692 #[test]
6693 fn round298_tiled_walk_rejects_empty_order() {
6694 let order = SliceTileWalkOrder { segments: vec![] };
6695 let err =
6696 walk_baseline_idr_slice_tiled(&[0u8; 4], two_tile_inputs(), &order, &[]).unwrap_err();
6697 assert!(
6698 format!("{err}").contains("empty tile walk order"),
6699 "got: {err}"
6700 );
6701 }
6702
6703 fn encode_one_split_ctu(enc: &mut crate::cabac::CabacEncoder) {
6716 enc.encode_decision(0, 0, 1); for _ in 0..4 {
6718 enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); enc.encode_decision(0, 0, 0); }
6723 }
6724
6725 fn hmvp_inputs(pic_width: u32, pic_height: u32) -> SliceWalkInputs {
6728 SliceWalkInputs {
6729 pic_width,
6730 pic_height,
6731 ctb_log2_size_y: 5,
6732 min_cb_log2_size_y: 4,
6733 max_tb_log2_size_y: 5,
6734 chroma_format_idc: 1,
6735 cu_qp_delta_enabled: false,
6736 ..Default::default()
6737 }
6738 }
6739
6740 #[test]
6744 fn round305_single_tile_hmvp_reset_once_per_row() {
6745 use crate::cabac::CabacEncoder;
6746 let inputs = hmvp_inputs(64, 96);
6748 let mut enc = CabacEncoder::new();
6749 for _ in 0..6 {
6750 encode_one_split_ctu(&mut enc);
6751 }
6752 enc.encode_terminate(true);
6753 let rbsp = enc.finish();
6754
6755 let stats = walk_baseline_idr_slice(&rbsp, inputs).unwrap();
6756 assert_eq!(stats.ctus, 6);
6757 assert_eq!(stats.hmvp_resets, 3, "one NumHmvpCand reset per CTB row");
6759 }
6760
6761 #[test]
6764 fn round305_single_row_resets_once() {
6765 use crate::cabac::CabacEncoder;
6766 let inputs = hmvp_inputs(96, 32);
6768 let mut enc = CabacEncoder::new();
6769 for _ in 0..3 {
6770 encode_one_split_ctu(&mut enc);
6771 }
6772 enc.encode_terminate(true);
6773 let rbsp = enc.finish();
6774
6775 let stats = walk_baseline_idr_slice(&rbsp, inputs).unwrap();
6776 assert_eq!(stats.ctus, 3);
6777 assert_eq!(stats.hmvp_resets, 1, "only the first CTB has xCtb == 0");
6778 }
6779
6780 #[test]
6786 fn round305_multi_tile_hmvp_reset_keyed_on_tile_first_column() {
6787 use crate::cabac::CabacEncoder;
6788 let inputs = hmvp_inputs(64, 64);
6792 let mut e0 = CabacEncoder::new();
6793 encode_one_split_ctu(&mut e0);
6794 encode_one_split_ctu(&mut e0);
6795 e0.encode_terminate(true);
6796 let sub0 = e0.finish();
6797 let mut e1 = CabacEncoder::new();
6798 encode_one_split_ctu(&mut e1);
6799 encode_one_split_ctu(&mut e1);
6800 e1.encode_terminate(true);
6801 let sub1 = e1.finish();
6802 let split = sub0.len();
6803 let mut rbsp = sub0;
6804 rbsp.extend_from_slice(&sub1);
6805 let subset_ranges = vec![0..split, split..rbsp.len()];
6806
6807 let order = SliceTileWalkOrder {
6808 segments: vec![
6809 SliceTileWalkSegment {
6810 tile_idx: 0,
6811 first_ctb_addr_ts: 0,
6812 num_ctus: 2,
6813 ctb_addr_in_rs: vec![0, 2], byte_align_after: true,
6815 },
6816 SliceTileWalkSegment {
6817 tile_idx: 1,
6818 first_ctb_addr_ts: 2,
6819 num_ctus: 2,
6820 ctb_addr_in_rs: vec![1, 3], byte_align_after: false,
6822 },
6823 ],
6824 };
6825
6826 let stats = walk_baseline_idr_slice_tiled(&rbsp, inputs, &order, &subset_ranges).unwrap();
6827 assert_eq!(stats.ctus, 4);
6828 assert_eq!(
6833 stats.hmvp_resets, 4,
6834 "reset keyed on each tile's own xFirstCtb"
6835 );
6836 assert_eq!(stats.end_of_tile_bits, 2);
6837 assert_eq!(stats.tile_byte_alignments, 1);
6838 }
6839
6840 #[test]
6846 fn round305_multi_column_tile_resets_per_row_not_per_ctb() {
6847 use crate::cabac::CabacEncoder;
6848 let inputs = hmvp_inputs(64, 96);
6850 let mut enc = CabacEncoder::new();
6851 for _ in 0..6 {
6852 encode_one_split_ctu(&mut enc);
6853 }
6854 enc.encode_terminate(true);
6855 let rbsp = enc.finish();
6856
6857 let order = SliceTileWalkOrder {
6859 segments: vec![SliceTileWalkSegment {
6860 tile_idx: 0,
6861 first_ctb_addr_ts: 0,
6862 num_ctus: 6,
6863 ctb_addr_in_rs: vec![0, 1, 2, 3, 4, 5],
6864 byte_align_after: false,
6865 }],
6866 };
6867 let range = 0..rbsp.len();
6868 let ranges = core::slice::from_ref(&range);
6869 let stats = walk_baseline_idr_slice_tiled(&rbsp, inputs, &order, ranges).unwrap();
6870 assert_eq!(stats.ctus, 6);
6871 assert_eq!(stats.hmvp_resets, 3, "leftmost-column CTBs only");
6873 let raster = walk_baseline_idr_slice(&rbsp, inputs).unwrap();
6875 assert_eq!(stats.hmvp_resets, raster.hmvp_resets);
6876 }
6877}