oximedia_codec/
gop_structure.rs1#![allow(dead_code)]
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
10pub enum PyramidLevel {
11 Anchor = 0,
13 L1 = 1,
15 L2 = 2,
17 L3 = 3,
19}
20
21impl PyramidLevel {
22 #[must_use]
24 pub fn qp_delta(self) -> i8 {
25 match self {
26 Self::Anchor => 0,
27 Self::L1 => 1,
28 Self::L2 => 2,
29 Self::L3 => 4,
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct GopFrame {
37 pub position: u32,
39 pub is_keyframe: bool,
41 pub is_b_frame: bool,
43 pub pyramid_level: PyramidLevel,
45}
46
47impl GopFrame {
48 #[must_use]
50 pub fn keyframe(position: u32) -> Self {
51 Self {
52 position,
53 is_keyframe: true,
54 is_b_frame: false,
55 pyramid_level: PyramidLevel::Anchor,
56 }
57 }
58
59 #[must_use]
61 pub fn p_frame(position: u32) -> Self {
62 Self {
63 position,
64 is_keyframe: false,
65 is_b_frame: false,
66 pyramid_level: PyramidLevel::Anchor,
67 }
68 }
69
70 #[must_use]
72 pub fn b_frame(position: u32, level: PyramidLevel) -> Self {
73 Self {
74 position,
75 is_keyframe: false,
76 is_b_frame: true,
77 pyramid_level: level,
78 }
79 }
80}
81
82#[derive(Debug, Clone, Default)]
84pub struct GopStatistics {
85 pub total_frames: u32,
87 pub i_frame_count: u32,
89 pub p_frame_count: u32,
91 pub b_frame_count: u32,
93 pub avg_pyramid_level: f32,
95}
96
97impl GopStatistics {
98 #[must_use]
100 pub fn b_ratio(&self) -> f32 {
101 if self.total_frames == 0 {
102 return 0.0;
103 }
104 self.b_frame_count as f32 / self.total_frames as f32
105 }
106}
107
108#[must_use]
113pub fn plan_gop(gop_size: u32, anchor_interval: u32) -> Vec<GopFrame> {
114 let mut frames = Vec::with_capacity(gop_size as usize);
115 if gop_size == 0 {
116 return frames;
117 }
118 for pos in 0..gop_size {
119 let frame = if pos == 0 {
120 GopFrame::keyframe(pos)
121 } else if anchor_interval == 0 || pos % anchor_interval == 0 {
122 GopFrame::p_frame(pos)
123 } else {
124 let offset = pos % anchor_interval;
125 let half = anchor_interval / 2;
126 if offset == half {
127 GopFrame::b_frame(pos, PyramidLevel::L1)
128 } else if offset % 2 == 0 {
129 GopFrame::b_frame(pos, PyramidLevel::L2)
130 } else {
131 GopFrame::b_frame(pos, PyramidLevel::L3)
132 }
133 };
134 frames.push(frame);
135 }
136 frames
137}
138
139#[must_use]
141pub fn compute_statistics(frames: &[GopFrame]) -> GopStatistics {
142 let total = frames.len() as u32;
143 let mut i_count = 0u32;
144 let mut p_count = 0u32;
145 let mut b_count = 0u32;
146 let mut level_sum = 0u32;
147
148 for f in frames {
149 if f.is_keyframe {
150 i_count += 1;
151 } else if f.is_b_frame {
152 b_count += 1;
153 } else {
154 p_count += 1;
155 }
156 level_sum += f.pyramid_level as u32;
157 }
158
159 let avg_level = if total > 0 {
160 level_sum as f32 / total as f32
161 } else {
162 0.0
163 };
164
165 GopStatistics {
166 total_frames: total,
167 i_frame_count: i_count,
168 p_frame_count: p_count,
169 b_frame_count: b_count,
170 avg_pyramid_level: avg_level,
171 }
172}
173
174#[derive(Debug)]
177pub struct SceneChangeDetector {
178 pub threshold: u64,
180 pub min_keyframe_interval: u32,
182 frames_since_last_key: u32,
183}
184
185impl SceneChangeDetector {
186 #[must_use]
188 pub fn new(threshold: u64, min_keyframe_interval: u32) -> Self {
189 Self {
190 threshold,
191 min_keyframe_interval,
192 frames_since_last_key: 0,
193 }
194 }
195
196 pub fn update(&mut self, sad: u64) -> bool {
199 self.frames_since_last_key += 1;
200 let is_change =
201 self.frames_since_last_key >= self.min_keyframe_interval && sad >= self.threshold;
202 if is_change {
203 self.frames_since_last_key = 0;
204 }
205 is_change
206 }
207
208 pub fn reset(&mut self) {
210 self.frames_since_last_key = 0;
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_pyramid_level_ordering() {
220 assert!(PyramidLevel::Anchor < PyramidLevel::L1);
221 assert!(PyramidLevel::L1 < PyramidLevel::L2);
222 assert!(PyramidLevel::L2 < PyramidLevel::L3);
223 }
224
225 #[test]
226 fn test_pyramid_qp_delta_increases() {
227 assert!(PyramidLevel::L3.qp_delta() > PyramidLevel::L2.qp_delta());
228 assert!(PyramidLevel::L2.qp_delta() > PyramidLevel::L1.qp_delta());
229 assert!(PyramidLevel::L1.qp_delta() > PyramidLevel::Anchor.qp_delta());
230 }
231
232 #[test]
233 fn test_gop_frame_keyframe() {
234 let f = GopFrame::keyframe(0);
235 assert!(f.is_keyframe);
236 assert!(!f.is_b_frame);
237 }
238
239 #[test]
240 fn test_gop_frame_b_frame() {
241 let f = GopFrame::b_frame(3, PyramidLevel::L2);
242 assert!(f.is_b_frame);
243 assert_eq!(f.pyramid_level, PyramidLevel::L2);
244 }
245
246 #[test]
247 fn test_plan_gop_first_frame_is_keyframe() {
248 let frames = plan_gop(16, 4);
249 assert!(frames[0].is_keyframe);
250 }
251
252 #[test]
253 fn test_plan_gop_length() {
254 let frames = plan_gop(30, 5);
255 assert_eq!(frames.len(), 30);
256 }
257
258 #[test]
259 fn test_plan_gop_zero_returns_empty() {
260 let frames = plan_gop(0, 4);
261 assert!(frames.is_empty());
262 }
263
264 #[test]
265 fn test_statistics_total_equals_i_plus_p_plus_b() {
266 let frames = plan_gop(16, 4);
267 let stats = compute_statistics(&frames);
268 assert_eq!(stats.total_frames, 16);
269 assert_eq!(
270 stats.i_frame_count + stats.p_frame_count + stats.b_frame_count,
271 stats.total_frames
272 );
273 }
274
275 #[test]
276 fn test_statistics_i_frame_count_at_least_one() {
277 let frames = plan_gop(10, 4);
278 let stats = compute_statistics(&frames);
279 assert!(stats.i_frame_count >= 1);
280 }
281
282 #[test]
283 fn test_b_ratio_range() {
284 let frames = plan_gop(20, 4);
285 let stats = compute_statistics(&frames);
286 assert!(stats.b_ratio() >= 0.0 && stats.b_ratio() <= 1.0);
287 }
288
289 #[test]
290 fn test_scene_change_not_triggered_below_interval() {
291 let mut det = SceneChangeDetector::new(1000, 5);
292 for _ in 0..4 {
294 assert!(!det.update(u64::MAX));
295 }
296 }
297
298 #[test]
299 fn test_scene_change_triggered_above_threshold() {
300 let mut det = SceneChangeDetector::new(500, 2);
301 det.update(0); let triggered = det.update(1000); assert!(triggered);
304 }
305
306 #[test]
307 fn test_scene_change_reset() {
308 let mut det = SceneChangeDetector::new(100, 1);
309 det.update(200); let triggered = det.update(200);
312 assert!(triggered); det.reset();
314 assert!(det.update(200));
316 }
317
318 #[test]
319 fn test_statistics_empty_gop() {
320 let stats = compute_statistics(&[]);
321 assert_eq!(stats.total_frames, 0);
322 assert!((stats.b_ratio() - 0.0).abs() < 1e-6);
323 }
324}