Skip to main content

oximedia_codec/
reference_frames.rs

1//! Reference frame management for H.264/H.265 decoded picture buffers (DPB).
2//!
3//! Provides [`RefFrameType`], [`RefFrame`], and [`RefFrameList`] to track
4//! short-term and long-term reference pictures used during decoding.
5
6#![allow(dead_code)]
7
8/// Indicates whether a reference frame is short-term or long-term.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum RefFrameType {
11    /// Short-term reference: identified by frame_num / PicNum.
12    ShortTerm,
13    /// Long-term reference: identified by a long-term frame index.
14    LongTerm,
15}
16
17impl RefFrameType {
18    /// Returns `true` if this is a long-term reference.
19    pub fn is_long_term(self) -> bool {
20        self == Self::LongTerm
21    }
22
23    /// Returns `true` if this is a short-term reference.
24    pub fn is_short_term(self) -> bool {
25        self == Self::ShortTerm
26    }
27}
28
29/// A reference frame entry in the decoded picture buffer.
30#[derive(Debug, Clone)]
31pub struct RefFrame {
32    /// Picture order count (POC).
33    pub poc: i32,
34    /// `frame_num` for short-term or long-term frame index for long-term.
35    pub frame_num: u32,
36    /// Reference type.
37    pub ref_type: RefFrameType,
38    /// `true` when this entry is actually in use (not an empty slot).
39    pub in_use: bool,
40    /// Opaque frame data buffer index (into a larger buffer pool).
41    pub buffer_index: usize,
42}
43
44impl RefFrame {
45    /// Creates a new short-term reference frame.
46    pub fn short_term(poc: i32, frame_num: u32, buffer_index: usize) -> Self {
47        Self {
48            poc,
49            frame_num,
50            ref_type: RefFrameType::ShortTerm,
51            in_use: true,
52            buffer_index,
53        }
54    }
55
56    /// Creates a new long-term reference frame.
57    pub fn long_term(poc: i32, long_term_frame_idx: u32, buffer_index: usize) -> Self {
58        Self {
59            poc,
60            frame_num: long_term_frame_idx,
61            ref_type: RefFrameType::LongTerm,
62            in_use: true,
63            buffer_index,
64        }
65    }
66
67    /// Returns `true` when this slot is occupied by a valid reference frame.
68    pub fn is_valid(&self) -> bool {
69        self.in_use
70    }
71
72    /// Marks this frame as unused (removes it from the DPB logically).
73    pub fn mark_unused(&mut self) {
74        self.in_use = false;
75    }
76}
77
78/// A list of reference frames maintained in the decoded picture buffer.
79///
80/// Limits itself to `max_size` entries; oldest short-term frames are evicted
81/// when capacity is exceeded.
82#[derive(Debug)]
83pub struct RefFrameList {
84    frames: Vec<RefFrame>,
85    max_size: usize,
86}
87
88impl RefFrameList {
89    /// Creates a new empty reference frame list with the given capacity.
90    pub fn new(max_size: usize) -> Self {
91        Self {
92            frames: Vec::with_capacity(max_size),
93            max_size,
94        }
95    }
96
97    /// Adds a reference frame to the list.
98    ///
99    /// If the list is full, the oldest short-term reference is removed first.
100    /// Returns `false` if no space could be made (all frames are long-term).
101    pub fn add(&mut self, frame: RefFrame) -> bool {
102        if self.frames.len() < self.max_size {
103            self.frames.push(frame);
104            return true;
105        }
106        // Try to evict oldest short-term
107        if self.remove_oldest() {
108            self.frames.push(frame);
109            true
110        } else {
111            false
112        }
113    }
114
115    /// Removes the oldest short-term reference frame (lowest frame_num).
116    ///
117    /// Returns `true` if a frame was removed.
118    pub fn remove_oldest(&mut self) -> bool {
119        let pos = self
120            .frames
121            .iter()
122            .enumerate()
123            .filter(|(_, f)| f.ref_type == RefFrameType::ShortTerm && f.in_use)
124            .min_by_key(|(_, f)| f.frame_num)
125            .map(|(i, _)| i);
126
127        if let Some(idx) = pos {
128            self.frames.remove(idx);
129            true
130        } else {
131            false
132        }
133    }
134
135    /// Finds the reference frame whose POC is closest to `target_poc`.
136    ///
137    /// Returns `None` if the list is empty.
138    pub fn find_closest_poc(&self, target_poc: i32) -> Option<&RefFrame> {
139        self.frames
140            .iter()
141            .filter(|f| f.in_use)
142            .min_by_key(|f| (f.poc - target_poc).unsigned_abs())
143    }
144
145    /// Returns the number of active reference frames in the list.
146    pub fn len(&self) -> usize {
147        self.frames.iter().filter(|f| f.in_use).count()
148    }
149
150    /// Returns `true` if the list contains no active reference frames.
151    pub fn is_empty(&self) -> bool {
152        self.len() == 0
153    }
154
155    /// Returns an iterator over all active reference frames.
156    pub fn iter(&self) -> impl Iterator<Item = &RefFrame> {
157        self.frames.iter().filter(|f| f.in_use)
158    }
159
160    /// Returns the number of long-term reference frames.
161    pub fn long_term_count(&self) -> usize {
162        self.frames
163            .iter()
164            .filter(|f| f.in_use && f.ref_type == RefFrameType::LongTerm)
165            .count()
166    }
167
168    /// Returns the number of short-term reference frames.
169    pub fn short_term_count(&self) -> usize {
170        self.frames
171            .iter()
172            .filter(|f| f.in_use && f.ref_type == RefFrameType::ShortTerm)
173            .count()
174    }
175
176    /// Clears all reference frames from the list (used on IDR).
177    pub fn clear(&mut self) {
178        self.frames.clear();
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_ref_frame_type_is_long_term() {
188        assert!(RefFrameType::LongTerm.is_long_term());
189        assert!(!RefFrameType::ShortTerm.is_long_term());
190    }
191
192    #[test]
193    fn test_ref_frame_type_is_short_term() {
194        assert!(RefFrameType::ShortTerm.is_short_term());
195        assert!(!RefFrameType::LongTerm.is_short_term());
196    }
197
198    #[test]
199    fn test_ref_frame_short_term_constructor() {
200        let f = RefFrame::short_term(10, 3, 0);
201        assert_eq!(f.poc, 10);
202        assert_eq!(f.frame_num, 3);
203        assert!(f.is_valid());
204        assert_eq!(f.ref_type, RefFrameType::ShortTerm);
205    }
206
207    #[test]
208    fn test_ref_frame_long_term_constructor() {
209        let f = RefFrame::long_term(20, 1, 5);
210        assert_eq!(f.ref_type, RefFrameType::LongTerm);
211        assert_eq!(f.frame_num, 1);
212        assert!(f.is_valid());
213    }
214
215    #[test]
216    fn test_ref_frame_mark_unused() {
217        let mut f = RefFrame::short_term(0, 0, 0);
218        f.mark_unused();
219        assert!(!f.is_valid());
220    }
221
222    #[test]
223    fn test_ref_frame_list_add_within_capacity() {
224        let mut list = RefFrameList::new(4);
225        assert!(list.add(RefFrame::short_term(0, 0, 0)));
226        assert_eq!(list.len(), 1);
227    }
228
229    #[test]
230    fn test_ref_frame_list_is_empty_initially() {
231        let list = RefFrameList::new(4);
232        assert!(list.is_empty());
233    }
234
235    #[test]
236    fn test_ref_frame_list_remove_oldest_evicts_min_frame_num() {
237        let mut list = RefFrameList::new(10);
238        list.add(RefFrame::short_term(4, 4, 0));
239        list.add(RefFrame::short_term(2, 2, 1));
240        list.add(RefFrame::short_term(6, 6, 2));
241        assert!(list.remove_oldest());
242        // frame_num=2 should be gone; remaining: 4, 6
243        assert_eq!(list.len(), 2);
244        assert!(list.iter().all(|f| f.frame_num != 2));
245    }
246
247    #[test]
248    fn test_ref_frame_list_add_evicts_oldest_when_full() {
249        let mut list = RefFrameList::new(2);
250        list.add(RefFrame::short_term(0, 0, 0));
251        list.add(RefFrame::short_term(2, 2, 1));
252        // Full → should evict frame_num=0 and add new
253        let ok = list.add(RefFrame::short_term(4, 4, 2));
254        assert!(ok);
255        assert_eq!(list.len(), 2);
256    }
257
258    #[test]
259    fn test_ref_frame_list_find_closest_poc() {
260        let mut list = RefFrameList::new(4);
261        list.add(RefFrame::short_term(0, 0, 0));
262        list.add(RefFrame::short_term(10, 1, 1));
263        list.add(RefFrame::short_term(20, 2, 2));
264        let closest = list.find_closest_poc(12).expect("should succeed");
265        assert_eq!(closest.poc, 10);
266    }
267
268    #[test]
269    fn test_ref_frame_list_find_closest_poc_empty_returns_none() {
270        let list = RefFrameList::new(4);
271        assert!(list.find_closest_poc(0).is_none());
272    }
273
274    #[test]
275    fn test_ref_frame_list_long_term_count() {
276        let mut list = RefFrameList::new(4);
277        list.add(RefFrame::short_term(0, 0, 0));
278        list.add(RefFrame::long_term(10, 0, 1));
279        assert_eq!(list.long_term_count(), 1);
280        assert_eq!(list.short_term_count(), 1);
281    }
282
283    #[test]
284    fn test_ref_frame_list_clear() {
285        let mut list = RefFrameList::new(4);
286        list.add(RefFrame::short_term(0, 0, 0));
287        list.add(RefFrame::short_term(2, 1, 1));
288        list.clear();
289        assert!(list.is_empty());
290    }
291
292    #[test]
293    fn test_ref_frame_list_iter_only_active() {
294        let mut list = RefFrameList::new(4);
295        let mut f = RefFrame::short_term(0, 0, 0);
296        f.mark_unused();
297        list.frames.push(f);
298        list.add(RefFrame::short_term(2, 1, 1));
299        assert_eq!(list.iter().count(), 1);
300    }
301}