use super::reconstruction::ReconBuffer;
#[derive(Debug, Clone)]
pub struct ReconFrame {
pub width: u32,
pub height: u32,
pub y: Vec<u8>,
pub cb: Vec<u8>,
pub cr: Vec<u8>,
}
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(),
}
}
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>,
}
impl ReferenceBuffer {
pub fn new() -> Self {
Self {
last_ref: None,
last_ref_frame_num: None,
}
}
pub fn reset(&mut self) {
self.last_ref = None;
self.last_ref_frame_num = None;
}
pub fn promote(&mut self, recon: &ReconBuffer, frame_num: u8) {
self.last_ref = Some(ReconFrame::snapshot(recon));
self.last_ref_frame_num = Some(frame_num);
}
pub fn has_reference(&self) -> bool {
self.last_ref.is_some()
}
}
#[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_replaces_previous() {
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));
}
#[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);
}
}