oximedia_codec/
frame_types.rs1#![allow(dead_code)]
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum CodingFrameType {
11 I,
13 P,
15 B,
17 Idr,
19}
20
21impl CodingFrameType {
22 #[must_use]
24 pub fn is_reference(self) -> bool {
25 !matches!(self, Self::B)
26 }
27
28 #[must_use]
30 pub fn is_intra(self) -> bool {
31 matches!(self, Self::I | Self::Idr)
32 }
33
34 #[must_use]
36 pub fn label(self) -> &'static str {
37 match self {
38 Self::I => "I",
39 Self::P => "P",
40 Self::B => "B",
41 Self::Idr => "IDR",
42 }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct FrameDescriptor {
49 pub display_index: u64,
51 pub frame_type: CodingFrameType,
53 pub decode_index: u64,
55 pub qp_override: Option<u8>,
57}
58
59impl FrameDescriptor {
60 #[must_use]
62 pub fn new(index: u64, frame_type: CodingFrameType) -> Self {
63 Self {
64 display_index: index,
65 frame_type,
66 decode_index: index,
67 qp_override: None,
68 }
69 }
70
71 #[must_use]
73 pub fn with_decode_index(mut self, decode_index: u64) -> Self {
74 self.decode_index = decode_index;
75 self
76 }
77
78 #[must_use]
80 pub fn with_qp(mut self, qp: u8) -> Self {
81 self.qp_override = Some(qp);
82 self
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct GopConfig {
89 pub max_gop_size: u32,
91 pub b_frames: u32,
93 pub closed_gop: bool,
95 pub adaptive_keyframes: bool,
97}
98
99impl GopConfig {
100 #[must_use]
102 pub fn intra_only() -> Self {
103 Self {
104 max_gop_size: 1,
105 b_frames: 0,
106 closed_gop: true,
107 adaptive_keyframes: false,
108 }
109 }
110
111 #[must_use]
113 pub fn ip_only(gop_size: u32) -> Self {
114 Self {
115 max_gop_size: gop_size,
116 b_frames: 0,
117 closed_gop: true,
118 adaptive_keyframes: true,
119 }
120 }
121
122 #[must_use]
124 pub fn with_b_frames(gop_size: u32, b_frames: u32) -> Self {
125 Self {
126 max_gop_size: gop_size,
127 b_frames,
128 closed_gop: false,
129 adaptive_keyframes: true,
130 }
131 }
132}
133
134#[must_use]
141pub fn generate_gop_sequence(config: &GopConfig, num_frames: u64) -> Vec<FrameDescriptor> {
142 let mut descriptors = Vec::with_capacity(num_frames as usize);
143 let gop = config.max_gop_size as u64;
144 let b = config.b_frames as u64;
145
146 for i in 0..num_frames {
147 let frame_type = if i == 0 {
148 CodingFrameType::Idr
149 } else if gop <= 1 {
150 CodingFrameType::I
152 } else if i % gop == 0 {
153 CodingFrameType::I
154 } else if b > 0 {
155 let pos_in_gop = i % gop;
157 let sub_period = b + 1;
158 if pos_in_gop % sub_period == 0 {
159 CodingFrameType::P
160 } else {
161 CodingFrameType::B
162 }
163 } else {
164 CodingFrameType::P
165 };
166 descriptors.push(FrameDescriptor::new(i, frame_type));
167 }
168 descriptors
169}
170
171#[derive(Debug, Default)]
173pub struct ReferenceFramePool {
174 frames: Vec<u64>, pub capacity: usize,
177}
178
179impl ReferenceFramePool {
180 #[must_use]
182 pub fn new(capacity: usize) -> Self {
183 Self {
184 frames: Vec::with_capacity(capacity),
185 capacity,
186 }
187 }
188
189 pub fn insert(&mut self, display_index: u64) {
192 if self.frames.len() == self.capacity {
193 self.frames.remove(0);
194 }
195 self.frames.push(display_index);
196 }
197
198 #[must_use]
200 pub fn contains(&self, display_index: u64) -> bool {
201 self.frames.contains(&display_index)
202 }
203
204 #[must_use]
206 pub fn len(&self) -> usize {
207 self.frames.len()
208 }
209
210 #[must_use]
212 pub fn is_empty(&self) -> bool {
213 self.frames.is_empty()
214 }
215
216 pub fn clear(&mut self) {
218 self.frames.clear();
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_i_frame_is_intra() {
228 assert!(CodingFrameType::I.is_intra());
229 assert!(CodingFrameType::Idr.is_intra());
230 }
231
232 #[test]
233 fn test_p_frame_not_intra() {
234 assert!(!CodingFrameType::P.is_intra());
235 }
236
237 #[test]
238 fn test_b_frame_not_reference() {
239 assert!(!CodingFrameType::B.is_reference());
240 }
241
242 #[test]
243 fn test_i_and_p_are_references() {
244 assert!(CodingFrameType::I.is_reference());
245 assert!(CodingFrameType::P.is_reference());
246 assert!(CodingFrameType::Idr.is_reference());
247 }
248
249 #[test]
250 fn test_frame_type_labels() {
251 assert_eq!(CodingFrameType::I.label(), "I");
252 assert_eq!(CodingFrameType::P.label(), "P");
253 assert_eq!(CodingFrameType::B.label(), "B");
254 assert_eq!(CodingFrameType::Idr.label(), "IDR");
255 }
256
257 #[test]
258 fn test_frame_descriptor_defaults() {
259 let fd = FrameDescriptor::new(5, CodingFrameType::P);
260 assert_eq!(fd.display_index, 5);
261 assert_eq!(fd.decode_index, 5);
262 assert_eq!(fd.qp_override, None);
263 }
264
265 #[test]
266 fn test_frame_descriptor_with_qp() {
267 let fd = FrameDescriptor::new(0, CodingFrameType::I).with_qp(22);
268 assert_eq!(fd.qp_override, Some(22));
269 }
270
271 #[test]
272 fn test_gop_sequence_starts_with_idr() {
273 let cfg = GopConfig::ip_only(30);
274 let seq = generate_gop_sequence(&cfg, 10);
275 assert_eq!(seq[0].frame_type, CodingFrameType::Idr);
276 }
277
278 #[test]
279 fn test_intra_only_all_idr_or_i() {
280 let cfg = GopConfig::intra_only();
281 let seq = generate_gop_sequence(&cfg, 5);
282 for (i, fd) in seq.iter().enumerate() {
283 if i == 0 {
284 assert_eq!(fd.frame_type, CodingFrameType::Idr);
285 } else {
286 assert!(fd.frame_type.is_intra());
287 }
288 }
289 }
290
291 #[test]
292 fn test_ip_sequence_no_b_frames() {
293 let cfg = GopConfig::ip_only(8);
294 let seq = generate_gop_sequence(&cfg, 16);
295 for fd in &seq {
296 assert!(!matches!(fd.frame_type, CodingFrameType::B));
297 }
298 }
299
300 #[test]
301 fn test_reference_pool_capacity_eviction() {
302 let mut pool = ReferenceFramePool::new(3);
303 pool.insert(0);
304 pool.insert(1);
305 pool.insert(2);
306 pool.insert(3); assert!(!pool.contains(0));
308 assert!(pool.contains(3));
309 }
310
311 #[test]
312 fn test_reference_pool_len() {
313 let mut pool = ReferenceFramePool::new(4);
314 assert!(pool.is_empty());
315 pool.insert(10);
316 pool.insert(11);
317 assert_eq!(pool.len(), 2);
318 }
319
320 #[test]
321 fn test_reference_pool_clear_on_idr() {
322 let mut pool = ReferenceFramePool::new(4);
323 pool.insert(0);
324 pool.insert(1);
325 pool.clear();
326 assert!(pool.is_empty());
327 }
328}