use crate::error::DecodeError;
use crate::inter::MotionInfo;
use crate::rps::ShortTermRps;
use crate::yuv::YuvPlanes;
pub(crate) struct Frame {
pub(crate) planes: YuvPlanes,
pub(crate) poc: i32,
pub(crate) motion: Vec<MotionInfo>,
pub(crate) width4: usize,
pub(crate) height4: usize,
pub(crate) short_term: bool,
pub(crate) long_term: bool,
pub(crate) needed_for_output: bool,
pub(crate) latency: usize,
pub(crate) meta: Option<crate::video::FrameMeta>,
}
impl Frame {
#[inline]
pub(crate) fn is_reference(&self) -> bool {
self.short_term || self.long_term
}
}
#[derive(Clone, Copy)]
pub(crate) struct RefEntry {
pub(crate) _dpb_index: usize,
pub(crate) poc: i32,
pub(crate) long_term: bool,
}
pub(crate) struct Dpb {
pub(crate) frames: Vec<Frame>,
max_frames: usize,
max_reorder: usize,
max_latency: Option<usize>,
no_rasl_output: bool,
}
impl Dpb {
pub(crate) fn new(max_frames: usize) -> Self {
Dpb {
frames: Vec::new(),
max_frames: max_frames.max(1),
max_reorder: max_frames.max(1),
max_latency: None,
no_rasl_output: false,
}
}
pub(crate) fn configure(
&mut self,
buffering: usize,
reorder: usize,
max_latency: Option<usize>,
) {
self.max_frames = buffering.max(1);
self.max_reorder = reorder.min(self.max_frames);
self.max_latency = max_latency;
}
pub(crate) fn pending_no_rasl_output(&self) -> bool {
self.no_rasl_output
}
pub(crate) fn set_no_rasl_output(&mut self, v: bool) {
self.no_rasl_output = v;
}
pub(crate) fn discard_pending_output(&mut self) {
for f in &mut self.frames {
f.needed_for_output = false;
}
self.frames
.retain(|f| f.is_reference() || f.needed_for_output);
}
pub(crate) fn clear_refs(&mut self) {
for f in &mut self.frames {
f.short_term = false;
f.long_term = false;
}
}
pub(crate) fn push(&mut self, frame: Frame) -> usize {
for f in &mut self.frames {
if f.needed_for_output {
f.latency = f.latency.saturating_add(1);
}
}
self.frames.push(frame);
self.frames.len() - 1
}
pub(crate) fn bump_before_decode(&mut self) -> Vec<Frame> {
self.bump_before_picture(true)
}
pub(crate) fn bump_before_irap(&mut self) -> Vec<Frame> {
self.bump_before_picture(false)
}
fn bump_before_picture(&mut self, check_fullness: bool) -> Vec<Frame> {
self.frames
.retain(|f| f.is_reference() || f.needed_for_output);
let mut out = Vec::new();
loop {
let pending = self.frames.iter().filter(|f| f.needed_for_output).count();
let full = check_fullness && self.frames.len() >= self.max_frames && pending != 0;
let over_reorder = pending > self.max_reorder;
let over_latency = self.max_latency.is_some_and(|limit| {
self.frames
.iter()
.any(|f| f.needed_for_output && f.latency >= limit)
});
if !full && !over_reorder && !over_latency {
break;
}
let Some(frame) = self.bump_one() else { break };
out.push(frame);
}
self.frames
.retain(|f| f.is_reference() || f.needed_for_output);
out
}
fn find_by_poc(&self, poc: i32) -> Option<usize> {
self.frames
.iter()
.position(|f| f.poc == poc && f.is_reference())
}
pub(crate) fn apply_rps_lt(
&mut self,
cur_poc: i32,
rps: &ShortTermRps,
lt_refs: &[(i32, bool, bool, i32)],
max_poc_lsb: i32,
) -> RpsPocs {
let mut before = Vec::new(); let mut after = Vec::new(); let mut foll = Vec::new(); let mut lt = Vec::new(); let mut lt_keep = Vec::new();
for (d, &used) in rps.delta_poc_s0.iter().zip(&rps.used_s0) {
let poc = cur_poc + d;
if used {
before.push(poc);
} else {
foll.push(poc);
}
}
for (d, &used) in rps.delta_poc_s1.iter().zip(&rps.used_s1) {
let poc = cur_poc + d;
if used {
after.push(poc);
} else {
foll.push(poc);
}
}
for &(val, used, has_msb, delta_cycle) in lt_refs {
let matched = if has_msb && max_poc_lsb > 0 {
let cur_lsb = cur_poc.rem_euclid(max_poc_lsb);
let poc_lt = cur_poc - delta_cycle * max_poc_lsb - (cur_lsb - val);
self.frames.iter().find(|f| f.poc == poc_lt).map(|f| f.poc)
} else if max_poc_lsb > 0 {
self.frames
.iter()
.find(|f| f.poc.rem_euclid(max_poc_lsb) == val.rem_euclid(max_poc_lsb))
.map(|f| f.poc)
} else {
None
};
if let Some(poc) = matched {
lt_keep.push(poc);
if used {
lt.push(poc);
}
}
}
let keep: Vec<i32> = before
.iter()
.chain(after.iter())
.chain(foll.iter())
.copied()
.collect();
for f in &mut self.frames {
if lt_keep.contains(&f.poc) {
f.long_term = true;
f.short_term = false;
} else if f.long_term && !lt_keep.contains(&f.poc) {
f.long_term = false;
}
if f.short_term && !keep.contains(&f.poc) {
f.short_term = false;
}
}
RpsPocs { before, after, lt }
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn build_ref_lists(
&self,
pocs: &RpsPocs,
num_l0: usize,
num_l1: usize,
is_b: bool,
current: Option<RefEntry>,
list_mod_l0: &[u32],
list_mod_l1: &[u32],
) -> Result<(Vec<RefEntry>, Vec<RefEntry>), DecodeError> {
let mut temp0: Vec<RefEntry> = Vec::new();
for &poc in &pocs.before {
if let Some(i) = self.find_by_poc(poc) {
temp0.push(RefEntry {
_dpb_index: i,
poc,
long_term: false,
});
}
}
for &poc in &pocs.after {
if let Some(i) = self.find_by_poc(poc) {
temp0.push(RefEntry {
_dpb_index: i,
poc,
long_term: false,
});
}
}
let mut temp1: Vec<RefEntry> = Vec::new();
for &poc in &pocs.after {
if let Some(i) = self.find_by_poc(poc) {
temp1.push(RefEntry {
_dpb_index: i,
poc,
long_term: false,
});
}
}
for &poc in &pocs.before {
if let Some(i) = self.find_by_poc(poc) {
temp1.push(RefEntry {
_dpb_index: i,
poc,
long_term: false,
});
}
}
for &poc in &pocs.lt {
if let Some(i) = self.find_by_poc(poc) {
temp0.push(RefEntry {
_dpb_index: i,
poc,
long_term: true,
});
temp1.push(RefEntry {
_dpb_index: i,
poc,
long_term: true,
});
}
}
if let Some(curr) = current {
temp0.push(curr);
temp1.push(curr);
}
let mut list0 = finalize_list(&temp0, num_l0, list_mod_l0)?;
if let Some(curr) = current
&& list_mod_l0.is_empty()
&& num_l0 != 0
&& temp0.len() > num_l0
{
list0[num_l0 - 1] = curr;
}
let list1 = if is_b {
finalize_list(&temp1, num_l1, list_mod_l1)?
} else {
Vec::new()
};
Ok((list0, list1))
}
pub(crate) fn bump(&mut self, flush: bool) -> Vec<Frame> {
let mut out = Vec::new();
loop {
let pending = self.frames.iter().filter(|f| f.needed_for_output).count();
let over_reorder = pending > self.max_reorder;
let over_full = self.frames.len() > self.max_frames && pending > 0;
if !(flush && pending > 0) && !over_reorder && !over_full {
break;
}
let Some(frame) = self.bump_one() else { break };
out.push(frame);
if !flush {
break;
}
}
self.frames
.retain(|f| f.is_reference() || f.needed_for_output);
out
}
fn bump_one(&mut self) -> Option<Frame> {
let best = self
.frames
.iter()
.enumerate()
.filter(|(_, f)| f.needed_for_output)
.min_by_key(|(_, f)| f.poc)
.map(|(i, _)| i)?;
self.frames[best].needed_for_output = false;
if self.frames[best].is_reference() {
Some(self.frames[best].shallow_output())
} else {
Some(self.frames.remove(best))
}
}
}
impl Frame {
fn shallow_output(&self) -> Frame {
Frame {
planes: self.planes.clone(),
poc: self.poc,
motion: Vec::new(),
width4: 0,
height4: 0,
short_term: false,
long_term: false,
needed_for_output: false,
latency: 0,
meta: self.meta.clone(),
}
}
}
#[derive(Clone)]
pub(crate) struct RpsPocs {
pub(crate) before: Vec<i32>,
pub(crate) after: Vec<i32>,
pub(crate) lt: Vec<i32>,
}
fn finalize_list(
temp: &[RefEntry],
num: usize,
list_mod: &[u32],
) -> Result<Vec<RefEntry>, DecodeError> {
if temp.is_empty() || num == 0 {
return Ok(Vec::new());
}
let temp_len = num.max(temp.len());
let mut extended: Vec<RefEntry> = Vec::with_capacity(temp_len);
while extended.len() < temp_len {
for &e in temp {
if extended.len() >= temp_len {
break;
}
extended.push(e);
}
}
if list_mod.is_empty() {
extended.truncate(num);
Ok(extended)
} else {
list_mod
.iter()
.take(num)
.map(|&idx| {
extended
.get(idx as usize)
.copied()
.ok_or_else(|| DecodeError::Bitstream("ref list_entry out of range".into()))
})
.collect()
}
}
impl Clone for YuvPlanes {
fn clone(&self) -> Self {
YuvPlanes {
y: self.y.clone(),
cb: self.cb.clone(),
cr: self.cr.clone(),
width: self.width,
height: self.height,
chroma: self.chroma,
bit_depth: self.bit_depth,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fmt::{BitDepth, ChromaFormat};
fn frame(poc: i32, short_term: bool, needed_for_output: bool) -> Frame {
Frame {
planes: YuvPlanes {
y: vec![poc as u16],
cb: Vec::new(),
cr: Vec::new(),
width: 1,
height: 1,
chroma: ChromaFormat::Monochrome,
bit_depth: BitDepth::Eight,
},
poc,
motion: Vec::new(),
width4: 0,
height4: 0,
short_term,
long_term: false,
needed_for_output,
latency: 0,
meta: None,
}
}
#[test]
fn current_picture_is_an_active_long_term_reference() {
let dpb = Dpb::new(1);
let pocs = RpsPocs {
before: Vec::new(),
after: Vec::new(),
lt: Vec::new(),
};
let current = RefEntry {
_dpb_index: usize::MAX,
poc: 17,
long_term: true,
};
let (l0, l1) = dpb
.build_ref_lists(&pocs, 1, 1, true, Some(current), &[], &[])
.unwrap();
assert_eq!(l0.len(), 1);
assert_eq!(l1.len(), 1);
assert_eq!(l0[0].poc, 17);
assert_eq!(l1[0].poc, 17);
assert!(l0[0].long_term && l1[0].long_term);
}
#[test]
fn dead_output_reference_does_not_create_false_capacity_pressure() {
let mut dpb = Dpb::new(2);
dpb.configure(2, 2, None);
dpb.push(frame(0, false, false));
dpb.push(frame(2, true, true));
assert!(dpb.bump_before_decode().is_empty());
assert_eq!(dpb.frames.len(), 1);
assert_eq!(dpb.frames[0].poc, 2);
}
#[test]
fn irap_bumps_only_reorder_due_picture_before_discard() {
let mut dpb = Dpb::new(6);
dpb.configure(6, 4, None);
for poc in 3..=7 {
dpb.push(frame(poc, true, true));
}
let out = dpb.bump_before_irap();
assert_eq!(out.iter().map(|f| f.poc).collect::<Vec<_>>(), [3]);
assert_eq!(dpb.frames.iter().filter(|f| f.needed_for_output).count(), 4);
}
#[test]
fn max_latency_forces_output_before_capacity_limit() {
let mut dpb = Dpb::new(8);
dpb.configure(8, 8, Some(2));
dpb.push(frame(4, true, true));
dpb.push(frame(5, true, true)); assert!(dpb.bump_before_decode().is_empty());
dpb.push(frame(6, true, true)); let out = dpb.bump_before_decode();
assert_eq!(out.iter().map(|f| f.poc).collect::<Vec<_>>(), [4]);
}
}