1use std::time::{Duration, Instant};
4
5#[derive(Debug, Clone, Copy, Default)]
7pub struct BlockMetrics {
8 pub width: usize,
10 pub height: usize,
12 pub sad: u32,
14 pub sse: u64,
16 pub variance: f64,
18 pub mean: f64,
20}
21
22impl BlockMetrics {
23 #[must_use]
25 pub fn calculate(pixels: &[u8], width: usize, height: usize) -> Self {
26 let mut metrics = Self {
27 width,
28 height,
29 ..Default::default()
30 };
31
32 if pixels.is_empty() {
33 return metrics;
34 }
35
36 metrics.mean = pixels.iter().map(|&p| f64::from(p)).sum::<f64>() / pixels.len() as f64;
38
39 metrics.variance = pixels
41 .iter()
42 .map(|&p| {
43 let diff = f64::from(p) - metrics.mean;
44 diff * diff
45 })
46 .sum::<f64>()
47 / pixels.len() as f64;
48
49 metrics.sse = pixels
50 .iter()
51 .map(|&p| {
52 let diff = i32::from(p) - metrics.mean as i32;
53 (diff * diff) as u64
54 })
55 .sum();
56
57 metrics
58 }
59
60 #[must_use]
62 pub fn psnr(&self) -> f64 {
63 if self.sse == 0 {
64 return f64::INFINITY;
65 }
66
67 let num_pixels = self.width * self.height;
68 let mse = self.sse as f64 / num_pixels as f64;
69 10.0 * (255.0 * 255.0 / mse).log10()
70 }
71
72 #[must_use]
74 pub fn is_flat(&self, threshold: f64) -> bool {
75 self.variance < threshold
76 }
77
78 #[must_use]
80 pub fn is_textured(&self, threshold: f64) -> bool {
81 self.variance > threshold
82 }
83}
84
85#[derive(Debug, Clone, Default)]
87pub struct FrameMetrics {
88 pub width: usize,
90 pub height: usize,
92 pub total_bits: u64,
94 pub qp_values: Vec<u8>,
96 pub avg_qp: f64,
98 pub psnr: f64,
100 pub ssim: f64,
102 pub encoding_time: Duration,
104}
105
106impl FrameMetrics {
107 #[must_use]
109 pub fn new(width: usize, height: usize) -> Self {
110 Self {
111 width,
112 height,
113 ..Default::default()
114 }
115 }
116
117 pub fn add_qp(&mut self, qp: u8) {
119 self.qp_values.push(qp);
120 self.avg_qp =
121 self.qp_values.iter().map(|&q| f64::from(q)).sum::<f64>() / self.qp_values.len() as f64;
122 }
123
124 pub fn set_encoding_time(&mut self, duration: Duration) {
126 self.encoding_time = duration;
127 }
128
129 #[must_use]
131 pub fn bits_per_pixel(&self) -> f64 {
132 let num_pixels = (self.width * self.height) as f64;
133 if num_pixels > 0.0 {
134 self.total_bits as f64 / num_pixels
135 } else {
136 0.0
137 }
138 }
139
140 #[must_use]
142 pub fn encoding_fps(&self) -> f64 {
143 let secs = self.encoding_time.as_secs_f64();
144 if secs > 0.0 {
145 1.0 / secs
146 } else {
147 0.0
148 }
149 }
150}
151
152#[derive(Debug, Clone, Default)]
154pub struct OptimizationStats {
155 pub frames_encoded: usize,
157 pub total_time: Duration,
159 pub frame_metrics: Vec<FrameMetrics>,
161 pub total_bits: u64,
163 pub avg_psnr: f64,
165 pub avg_ssim: f64,
167}
168
169impl OptimizationStats {
170 #[must_use]
172 pub fn new() -> Self {
173 Self::default()
174 }
175
176 pub fn add_frame(&mut self, metrics: FrameMetrics) {
178 self.total_bits += metrics.total_bits;
179 self.total_time += metrics.encoding_time;
180 self.frames_encoded += 1;
181 self.frame_metrics.push(metrics);
182
183 self.calculate_averages();
185 }
186
187 fn calculate_averages(&mut self) {
188 if self.frames_encoded == 0 {
189 return;
190 }
191
192 self.avg_psnr =
193 self.frame_metrics.iter().map(|m| m.psnr).sum::<f64>() / self.frames_encoded as f64;
194
195 self.avg_ssim =
196 self.frame_metrics.iter().map(|m| m.ssim).sum::<f64>() / self.frames_encoded as f64;
197 }
198
199 #[must_use]
201 pub fn avg_bitrate(&self, fps: f64) -> f64 {
202 if self.frames_encoded == 0 {
203 return 0.0;
204 }
205
206 (self.total_bits as f64 / self.frames_encoded as f64) * fps
207 }
208
209 #[must_use]
211 pub fn avg_fps(&self) -> f64 {
212 let secs = self.total_time.as_secs_f64();
213 if secs > 0.0 {
214 self.frames_encoded as f64 / secs
215 } else {
216 0.0
217 }
218 }
219
220 #[must_use]
222 pub fn compression_ratio(&self) -> f64 {
223 if self.frame_metrics.is_empty() {
224 return 1.0;
225 }
226
227 let first_frame = &self.frame_metrics[0];
228 let uncompressed_bits =
229 (first_frame.width * first_frame.height * 8 * self.frames_encoded) as f64;
230
231 if self.total_bits > 0 {
232 uncompressed_bits / self.total_bits as f64
233 } else {
234 1.0
235 }
236 }
237
238 pub fn print_summary(&self) {
240 println!("Optimization Statistics:");
241 println!(" Frames: {}", self.frames_encoded);
242 println!(" Total bits: {}", self.total_bits);
243 println!(" Avg PSNR: {:.2} dB", self.avg_psnr);
244 println!(" Avg SSIM: {:.4}", self.avg_ssim);
245 println!(" Avg FPS: {:.2}", self.avg_fps());
246 println!(" Compression: {:.2}x", self.compression_ratio());
247 }
248}
249
250#[derive(Debug)]
252pub struct Timer {
253 start: Instant,
254 label: String,
255}
256
257impl Timer {
258 #[must_use]
260 pub fn new(label: impl Into<String>) -> Self {
261 Self {
262 start: Instant::now(),
263 label: label.into(),
264 }
265 }
266
267 #[must_use]
269 pub fn elapsed(&self) -> Duration {
270 self.start.elapsed()
271 }
272
273 #[must_use]
275 pub fn stop(self) -> Duration {
276 let elapsed = self.elapsed();
277 println!("{}: {:?}", self.label, elapsed);
278 elapsed
279 }
280}
281
282#[derive(Debug, Clone)]
284pub struct Histogram {
285 bins: Vec<u32>,
286 min_value: f64,
287 max_value: f64,
288 bin_width: f64,
289}
290
291impl Histogram {
292 #[must_use]
294 pub fn new(num_bins: usize, min_value: f64, max_value: f64) -> Self {
295 let bin_width = (max_value - min_value) / num_bins as f64;
296 Self {
297 bins: vec![0; num_bins],
298 min_value,
299 max_value,
300 bin_width,
301 }
302 }
303
304 pub fn add(&mut self, value: f64) {
306 if value < self.min_value || value >= self.max_value {
307 return;
308 }
309
310 let bin = ((value - self.min_value) / self.bin_width) as usize;
311 if bin < self.bins.len() {
312 self.bins[bin] += 1;
313 }
314 }
315
316 #[must_use]
318 pub fn get_bin(&self, index: usize) -> u32 {
319 self.bins.get(index).copied().unwrap_or(0)
320 }
321
322 #[must_use]
324 pub fn total_count(&self) -> u32 {
325 self.bins.iter().sum()
326 }
327
328 #[must_use]
330 pub fn mean(&self) -> f64 {
331 let total: u32 = self.total_count();
332 if total == 0 {
333 return 0.0;
334 }
335
336 let weighted_sum: f64 = self
337 .bins
338 .iter()
339 .enumerate()
340 .map(|(i, &count)| {
341 let bin_center = self.min_value + (i as f64 + 0.5) * self.bin_width;
342 bin_center * f64::from(count)
343 })
344 .sum();
345
346 weighted_sum / f64::from(total)
347 }
348
349 #[must_use]
351 pub fn median(&self) -> f64 {
352 let total = self.total_count();
353 if total == 0 {
354 return 0.0;
355 }
356
357 let median_count = total / 2;
358 let mut cumulative = 0;
359
360 for (i, &count) in self.bins.iter().enumerate() {
361 cumulative += count;
362 if cumulative >= median_count {
363 return self.min_value + (i as f64 + 0.5) * self.bin_width;
364 }
365 }
366
367 self.max_value
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_block_metrics_flat() {
377 let pixels = vec![128u8; 64];
378 let metrics = BlockMetrics::calculate(&pixels, 8, 8);
379 assert_eq!(metrics.mean, 128.0);
380 assert_eq!(metrics.variance, 0.0);
381 assert!(metrics.is_flat(10.0));
382 }
383
384 #[test]
385 fn test_block_metrics_varied() {
386 let pixels: Vec<u8> = (0..64).map(|i| i as u8).collect();
387 let metrics = BlockMetrics::calculate(&pixels, 8, 8);
388 assert!(metrics.variance > 0.0);
389 assert!(metrics.is_textured(100.0));
390 }
391
392 #[test]
393 fn test_block_metrics_psnr() {
394 let pixels = vec![128u8; 64];
395 let metrics = BlockMetrics::calculate(&pixels, 8, 8);
396 assert!(metrics.psnr().is_infinite()); }
398
399 #[test]
400 fn test_frame_metrics() {
401 let mut metrics = FrameMetrics::new(1920, 1080);
402 metrics.add_qp(26);
403 metrics.add_qp(28);
404 metrics.add_qp(24);
405 assert_eq!(metrics.avg_qp, 26.0);
406 }
407
408 #[test]
409 fn test_frame_metrics_bpp() {
410 let mut metrics = FrameMetrics::new(1920, 1080);
411 metrics.total_bits = 1920 * 1080; assert_eq!(metrics.bits_per_pixel(), 1.0);
413 }
414
415 #[test]
416 fn test_optimization_stats() {
417 let mut stats = OptimizationStats::new();
418 let mut frame1 = FrameMetrics::new(1920, 1080);
419 frame1.psnr = 40.0;
420 frame1.ssim = 0.95;
421 stats.add_frame(frame1);
422
423 let mut frame2 = FrameMetrics::new(1920, 1080);
424 frame2.psnr = 42.0;
425 frame2.ssim = 0.96;
426 stats.add_frame(frame2);
427
428 assert_eq!(stats.frames_encoded, 2);
429 assert_eq!(stats.avg_psnr, 41.0);
430 assert_eq!(stats.avg_ssim, 0.955);
431 }
432
433 #[test]
434 fn test_timer() {
435 let timer = Timer::new("test");
436 std::thread::sleep(Duration::from_millis(10));
437 let elapsed = timer.elapsed();
438 assert!(elapsed >= Duration::from_millis(10));
439 }
440
441 #[test]
442 fn test_histogram() {
443 let mut hist = Histogram::new(10, 0.0, 100.0);
444 hist.add(25.0);
445 hist.add(35.0);
446 hist.add(25.0);
447
448 assert_eq!(hist.total_count(), 3);
449 assert_eq!(hist.get_bin(2), 2); assert_eq!(hist.get_bin(3), 1); }
452
453 #[test]
454 fn test_histogram_mean() {
455 let mut hist = Histogram::new(10, 0.0, 100.0);
456 hist.add(20.0);
457 hist.add(30.0);
458 hist.add(40.0);
459
460 let mean = hist.mean();
463 assert!((mean - 35.0).abs() < 1.0); }
465}