use super::reconstruction::ReconBuffer;
#[derive(Debug, Clone)]
pub struct ColocatedMvCell {
pub ref_idx_l0: i8,
pub mv_l0_x: i16,
pub mv_l0_y: i16,
}
impl ColocatedMvCell {
pub const INTRA: Self = Self {
ref_idx_l0: -1,
mv_l0_x: 0,
mv_l0_y: 0,
};
}
#[derive(Debug, Clone)]
pub struct ColocatedMvGrid {
pub mb_w: u32,
pub mb_h: u32,
pub cells: Vec<ColocatedMvCell>,
}
impl ColocatedMvGrid {
pub fn new(mb_w: u32, mb_h: u32) -> Self {
Self {
mb_w,
mb_h,
cells: vec![ColocatedMvCell::INTRA; (mb_w * mb_h) as usize],
}
}
pub fn get(&self, mb_x: u32, mb_y: u32) -> &ColocatedMvCell {
&self.cells[(mb_y * self.mb_w + mb_x) as usize]
}
pub fn set(&mut self, mb_x: u32, mb_y: u32, cell: ColocatedMvCell) {
self.cells[(mb_y * self.mb_w + mb_x) as usize] = cell;
}
}
#[derive(Debug, Clone)]
pub struct ReconFrame {
pub width: u32,
pub height: u32,
pub y: Vec<u8>,
pub cb: Vec<u8>,
pub cr: Vec<u8>,
pub motion_grid: Option<ColocatedMvGrid>,
}
impl ReconFrame {
pub fn snapshot(recon: &ReconBuffer) -> Self {
Self {
width: recon.width,
height: recon.height,
y: recon.y.clone(),
cb: recon.cb.clone(),
cr: recon.cr.clone(),
motion_grid: None,
}
}
pub fn with_motion_grid(mut self, grid: ColocatedMvGrid) -> Self {
self.motion_grid = Some(grid);
self
}
pub fn y_at(&self, x: u32, y: u32) -> u8 {
self.y[(y * self.width + x) as usize]
}
pub fn chroma_at(&self, component: u8, x: u32, y: u32) -> u8 {
let stride = self.width / 2;
let plane = if component == 0 { &self.cb } else { &self.cr };
plane[(y * stride + x) as usize]
}
}
#[derive(Debug, Default)]
pub struct ReferenceBuffer {
pub last_ref: Option<ReconFrame>,
pub last_ref_frame_num: Option<u8>,
pub past_anchor: Option<ReconFrame>,
pub past_anchor_frame_num: Option<u8>,
}
impl ReferenceBuffer {
pub fn new() -> Self {
Self {
last_ref: None,
last_ref_frame_num: None,
past_anchor: None,
past_anchor_frame_num: None,
}
}
pub fn reset(&mut self) {
self.last_ref = None;
self.last_ref_frame_num = None;
self.past_anchor = None;
self.past_anchor_frame_num = None;
}
pub fn promote(&mut self, recon: &ReconBuffer, frame_num: u8) {
self.past_anchor = self.last_ref.take();
self.past_anchor_frame_num = self.last_ref_frame_num.take();
self.last_ref = Some(ReconFrame::snapshot(recon));
self.last_ref_frame_num = Some(frame_num);
}
pub fn promote_with_motion(
&mut self,
recon: &ReconBuffer,
frame_num: u8,
motion_grid: ColocatedMvGrid,
) {
self.past_anchor = self.last_ref.take();
self.past_anchor_frame_num = self.last_ref_frame_num.take();
self.last_ref = Some(
ReconFrame::snapshot(recon).with_motion_grid(motion_grid),
);
self.last_ref_frame_num = Some(frame_num);
}
pub fn has_reference(&self) -> bool {
self.last_ref.is_some()
}
pub fn has_b_references(&self) -> bool {
self.past_anchor.is_some() && self.last_ref.is_some()
}
pub fn b_references(&self) -> Option<(&ReconFrame, &ReconFrame)> {
match (self.past_anchor.as_ref(), self.last_ref.as_ref()) {
(Some(l0), Some(l1)) => Some((l0, l1)),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotRole {
Past,
Future,
}
#[derive(Debug, Clone)]
pub struct DpbSlot {
pub recon: ReconFrame,
pub frame_num: u8,
pub full_poc: u32,
pub role: SlotRole,
}
#[derive(Debug)]
pub struct MultiSlotDpb {
slots: Vec<Option<DpbSlot>>,
}
impl MultiSlotDpb {
pub fn with_capacity(capacity: usize) -> Self {
assert!(capacity > 0, "MultiSlotDpb capacity must be > 0");
Self {
slots: vec![None; capacity],
}
}
pub fn capacity(&self) -> usize {
self.slots.len()
}
pub fn occupied(&self) -> usize {
self.slots.iter().filter(|s| s.is_some()).count()
}
pub fn reset(&mut self) {
for s in self.slots.iter_mut() {
*s = None;
}
}
pub fn promote(
&mut self,
recon: &ReconBuffer,
frame_num: u8,
full_poc: u32,
role: SlotRole,
) {
let new_slot = DpbSlot {
recon: ReconFrame::snapshot(recon),
frame_num,
full_poc,
role,
};
if let Some(empty) = self.slots.iter_mut().find(|s| s.is_none()) {
*empty = Some(new_slot);
return;
}
let oldest_idx = self
.slots
.iter()
.enumerate()
.filter_map(|(i, s)| s.as_ref().map(|sl| (i, sl.full_poc)))
.min_by_key(|(_, poc)| *poc)
.map(|(i, _)| i)
.expect("DPB is non-empty (capacity > 0)");
self.slots[oldest_idx] = Some(new_slot);
}
pub fn get(&self, idx: usize) -> Option<&DpbSlot> {
self.slots.get(idx).and_then(|s| s.as_ref())
}
pub fn most_recent_past(&self) -> Option<&DpbSlot> {
self.slots
.iter()
.filter_map(|s| s.as_ref())
.filter(|s| s.role == SlotRole::Past)
.max_by_key(|s| s.full_poc)
}
pub fn soonest_future(&self, current_poc: u32) -> Option<&DpbSlot> {
self.slots
.iter()
.filter_map(|s| s.as_ref())
.filter(|s| s.role == SlotRole::Future && s.full_poc > current_poc)
.min_by_key(|s| s.full_poc)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_recon(width: u32, height: u32, y_fill: u8, c_fill: u8) -> ReconBuffer {
let mut b = ReconBuffer::new(width, height).unwrap();
for v in b.y.iter_mut() {
*v = y_fill;
}
for v in b.cb.iter_mut() {
*v = c_fill;
}
for v in b.cr.iter_mut() {
*v = c_fill;
}
b
}
#[test]
fn reference_buffer_new_is_empty() {
let rb = ReferenceBuffer::new();
assert!(!rb.has_reference());
assert!(rb.last_ref.is_none());
assert!(rb.last_ref_frame_num.is_none());
}
#[test]
fn promote_captures_pixels() {
let mut rb = ReferenceBuffer::new();
let recon = make_recon(32, 32, 100, 128);
rb.promote(&recon, 0);
assert!(rb.has_reference());
assert_eq!(rb.last_ref_frame_num, Some(0));
let frame = rb.last_ref.as_ref().unwrap();
assert_eq!(frame.width, 32);
assert_eq!(frame.height, 32);
assert_eq!(frame.y_at(5, 5), 100);
assert_eq!(frame.chroma_at(0, 5, 5), 128);
assert_eq!(frame.chroma_at(1, 5, 5), 128);
}
#[test]
fn reset_clears_dpb() {
let mut rb = ReferenceBuffer::new();
let recon = make_recon(32, 32, 100, 128);
rb.promote(&recon, 5);
rb.reset();
assert!(!rb.has_reference());
assert_eq!(rb.last_ref_frame_num, None);
}
#[test]
fn promote_shifts_last_ref_to_past_anchor() {
let mut rb = ReferenceBuffer::new();
rb.promote(&make_recon(32, 32, 50, 128), 0);
rb.promote(&make_recon(32, 32, 200, 100), 1);
let frame = rb.last_ref.as_ref().unwrap();
assert_eq!(frame.y_at(0, 0), 200);
assert_eq!(frame.chroma_at(0, 0, 0), 100);
assert_eq!(rb.last_ref_frame_num, Some(1));
let past = rb.past_anchor.as_ref().unwrap();
assert_eq!(past.y_at(0, 0), 50);
assert_eq!(past.chroma_at(0, 0, 0), 128);
assert_eq!(rb.past_anchor_frame_num, Some(0));
}
#[test]
fn ibpbp_anchor_sequence_makes_b_references_available() {
let mut rb = ReferenceBuffer::new();
rb.reset();
assert!(!rb.has_b_references(),
"B-references unavailable after IDR (only one anchor possible)");
rb.promote(&make_recon(32, 32, 10, 128), 0);
assert!(!rb.has_b_references(),
"B-references unavailable after only one anchor");
rb.promote(&make_recon(32, 32, 90, 128), 1);
assert!(rb.has_b_references(),
"B-references available after I+P pair");
let (l0, l1) = rb.b_references().unwrap();
assert_eq!(l0.y_at(0, 0), 10, "L0 = past_anchor = I");
assert_eq!(l1.y_at(0, 0), 90, "L1 = last_ref = P0");
rb.promote(&make_recon(32, 32, 170, 128), 2);
let (l0, l1) = rb.b_references().unwrap();
assert_eq!(l0.y_at(0, 0), 90, "L0 = past_anchor = P0");
assert_eq!(l1.y_at(0, 0), 170, "L1 = last_ref = P1");
rb.reset();
assert!(!rb.has_b_references());
assert!(!rb.has_reference());
}
#[test]
fn snapshot_is_independent_of_source() {
let mut recon = make_recon(32, 32, 100, 128);
let mut rb = ReferenceBuffer::new();
rb.promote(&recon, 0);
for v in recon.y.iter_mut() {
*v = 200;
}
let frame = rb.last_ref.as_ref().unwrap();
assert_eq!(frame.y_at(0, 0), 100);
}
#[test]
fn multi_slot_dpb_starts_empty() {
let dpb = MultiSlotDpb::with_capacity(3);
assert_eq!(dpb.capacity(), 3);
assert_eq!(dpb.occupied(), 0);
assert!(dpb.get(0).is_none());
assert!(dpb.most_recent_past().is_none());
assert!(dpb.soonest_future(0).is_none());
}
#[test]
fn multi_slot_dpb_promote_fills_empty_slots() {
let mut dpb = MultiSlotDpb::with_capacity(3);
let recon0 = make_recon(32, 32, 50, 128);
dpb.promote(&recon0, 0, 0, SlotRole::Past);
assert_eq!(dpb.occupied(), 1);
let recon1 = make_recon(32, 32, 100, 128);
dpb.promote(&recon1, 1, 4, SlotRole::Future);
assert_eq!(dpb.occupied(), 2);
let past = dpb.most_recent_past().unwrap();
assert_eq!(past.full_poc, 0);
let future = dpb.soonest_future(0).unwrap();
assert_eq!(future.full_poc, 4);
}
#[test]
fn multi_slot_dpb_evicts_oldest_when_full() {
let mut dpb = MultiSlotDpb::with_capacity(2);
let r = make_recon(32, 32, 0, 128);
dpb.promote(&r, 0, 0, SlotRole::Past);
dpb.promote(&r, 1, 4, SlotRole::Past);
assert_eq!(dpb.occupied(), 2);
dpb.promote(&r, 2, 8, SlotRole::Past);
assert_eq!(dpb.occupied(), 2);
assert_eq!(dpb.most_recent_past().unwrap().full_poc, 8);
let pocs: Vec<u32> = (0..dpb.capacity())
.filter_map(|i| dpb.get(i).map(|s| s.full_poc))
.collect();
assert!(!pocs.contains(&0));
assert!(pocs.contains(&4));
assert!(pocs.contains(&8));
}
#[test]
fn multi_slot_dpb_reset_clears() {
let mut dpb = MultiSlotDpb::with_capacity(3);
let r = make_recon(32, 32, 100, 128);
dpb.promote(&r, 0, 0, SlotRole::Past);
dpb.promote(&r, 1, 4, SlotRole::Future);
assert_eq!(dpb.occupied(), 2);
dpb.reset();
assert_eq!(dpb.occupied(), 0);
assert!(dpb.most_recent_past().is_none());
}
#[test]
fn multi_slot_dpb_filters_role_correctly() {
let mut dpb = MultiSlotDpb::with_capacity(3);
let r = make_recon(32, 32, 0, 128);
dpb.promote(&r, 0, 0, SlotRole::Past);
dpb.promote(&r, 1, 4, SlotRole::Future);
dpb.promote(&r, 2, 2, SlotRole::Past);
assert_eq!(dpb.most_recent_past().unwrap().full_poc, 2);
assert_eq!(dpb.soonest_future(2).unwrap().full_poc, 4);
assert!(dpb.soonest_future(4).is_none());
}
#[test]
fn multi_slot_dpb_capacity_one_acts_like_single_slot() {
let mut dpb = MultiSlotDpb::with_capacity(1);
let r0 = make_recon(32, 32, 50, 128);
let r1 = make_recon(32, 32, 200, 100);
dpb.promote(&r0, 0, 0, SlotRole::Past);
assert_eq!(dpb.occupied(), 1);
assert_eq!(dpb.get(0).unwrap().recon.y_at(0, 0), 50);
dpb.promote(&r1, 1, 2, SlotRole::Past);
assert_eq!(dpb.occupied(), 1);
assert_eq!(dpb.get(0).unwrap().recon.y_at(0, 0), 200);
}
}