use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct FrameEncodingStats {
pub frame_number: u64,
pub is_keyframe: bool,
pub size_bytes: usize,
pub quality: u8,
pub psnr_y: f64,
pub psnr_u: f64,
pub psnr_v: f64,
pub ssim_y: f64,
pub encode_time: Duration,
pub intra_blocks: usize,
pub inter_blocks: usize,
pub skip_blocks: usize,
pub avg_mv_magnitude: f32,
pub bitrate: f64,
}
impl FrameEncodingStats {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
frame_number: u64,
is_keyframe: bool,
size_bytes: usize,
quality: u8,
encode_time: Duration,
) -> Self {
Self {
frame_number,
is_keyframe,
size_bytes,
quality,
psnr_y: 0.0,
psnr_u: 0.0,
psnr_v: 0.0,
ssim_y: 0.0,
encode_time,
intra_blocks: 0,
inter_blocks: 0,
skip_blocks: 0,
avg_mv_magnitude: 0.0,
bitrate: 0.0,
}
}
#[must_use]
pub fn avg_psnr(&self) -> f64 {
(self.psnr_y + self.psnr_u + self.psnr_v) / 3.0
}
#[must_use]
pub fn total_blocks(&self) -> usize {
self.intra_blocks + self.inter_blocks + self.skip_blocks
}
#[must_use]
pub fn intra_percentage(&self) -> f64 {
let total = self.total_blocks();
if total > 0 {
(self.intra_blocks as f64 / total as f64) * 100.0
} else {
0.0
}
}
}
#[derive(Debug, Clone)]
pub struct EncoderStats {
pub frames: Vec<FrameEncodingStats>,
pub total_time: Duration,
start_time: Option<Instant>,
}
impl EncoderStats {
#[must_use]
pub fn new() -> Self {
Self {
frames: Vec::new(),
total_time: Duration::ZERO,
start_time: None,
}
}
pub fn start_timing(&mut self) {
self.start_time = Some(Instant::now());
}
pub fn stop_timing(&mut self) {
if let Some(start) = self.start_time.take() {
self.total_time = start.elapsed();
}
}
pub fn add_frame(&mut self, stats: FrameEncodingStats) {
self.frames.push(stats);
}
#[must_use]
pub fn average_bitrate(&self) -> f64 {
if self.frames.is_empty() {
return 0.0;
}
let total_bytes: usize = self.frames.iter().map(|f| f.size_bytes).sum();
let total_bits = total_bytes * 8;
let duration_secs = self.total_time.as_secs_f64();
if duration_secs > 0.0 {
total_bits as f64 / duration_secs
} else {
0.0
}
}
#[must_use]
pub fn average_psnr(&self) -> f64 {
if self.frames.is_empty() {
return 0.0;
}
let sum: f64 = self.frames.iter().map(|f| f.avg_psnr()).sum();
sum / self.frames.len() as f64
}
#[must_use]
pub fn average_encode_time(&self) -> Duration {
if self.frames.is_empty() {
return Duration::ZERO;
}
let total_nanos: u128 = self.frames.iter().map(|f| f.encode_time.as_nanos()).sum();
Duration::from_nanos((total_nanos / self.frames.len() as u128) as u64)
}
#[must_use]
pub fn frames_per_second(&self) -> f64 {
let duration_secs = self.total_time.as_secs_f64();
if duration_secs > 0.0 {
self.frames.len() as f64 / duration_secs
} else {
0.0
}
}
#[must_use]
pub fn keyframe_interval_stats(&self) -> KeyframeIntervalStats {
let mut intervals = Vec::new();
let mut last_keyframe = 0usize;
for (i, frame) in self.frames.iter().enumerate() {
if frame.is_keyframe {
if i > 0 {
intervals.push(i - last_keyframe);
}
last_keyframe = i;
}
}
KeyframeIntervalStats::from_intervals(&intervals)
}
#[must_use]
pub fn summary(&self) -> String {
format!(
"Encoder Statistics:\n\
Total Frames: {}\n\
Total Time: {:.2}s\n\
Average Bitrate: {:.2} kbps\n\
Average PSNR: {:.2} dB\n\
Average Encode Time: {:.2}ms/frame\n\
Encoding Speed: {:.2} fps",
self.frames.len(),
self.total_time.as_secs_f64(),
self.average_bitrate() / 1000.0,
self.average_psnr(),
self.average_encode_time().as_secs_f64() * 1000.0,
self.frames_per_second()
)
}
}
impl Default for EncoderStats {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct KeyframeIntervalStats {
pub min: usize,
pub max: usize,
pub average: f64,
}
impl KeyframeIntervalStats {
#[must_use]
pub fn from_intervals(intervals: &[usize]) -> Self {
if intervals.is_empty() {
return Self {
min: 0,
max: 0,
average: 0.0,
};
}
let min = *intervals.iter().min().unwrap_or(&0);
let max = *intervals.iter().max().unwrap_or(&0);
let sum: usize = intervals.iter().sum();
let average = sum as f64 / intervals.len() as f64;
Self { min, max, average }
}
}
pub struct QualityMetrics;
impl QualityMetrics {
#[must_use]
pub fn calculate_psnr(
original: &[u8],
reconstructed: &[u8],
width: usize,
height: usize,
) -> f64 {
let size = width * height;
if size == 0 || original.len() < size || reconstructed.len() < size {
return 0.0;
}
let mut mse = 0.0;
for i in 0..size {
let diff = f64::from(original[i]) - f64::from(reconstructed[i]);
mse += diff * diff;
}
mse /= size as f64;
if mse < 1e-10 {
return 100.0; }
let max_val = 255.0;
20.0 * (max_val / mse.sqrt()).log10()
}
#[must_use]
pub fn calculate_ssim(
original: &[u8],
reconstructed: &[u8],
width: usize,
height: usize,
stride: usize,
) -> f64 {
const C1: f64 = 6.5025; const C2: f64 = 58.5225;
let mut ssim_sum = 0.0;
let mut count = 0;
for y in (0..height - 8).step_by(4) {
for x in (0..width - 8).step_by(4) {
let (mu_x, mu_y, sigma_x, sigma_y, sigma_xy) =
Self::calculate_window_stats(original, reconstructed, stride, x, y);
let numerator = (2.0 * mu_x * mu_y + C1) * (2.0 * sigma_xy + C2);
let denominator = (mu_x * mu_x + mu_y * mu_y + C1) * (sigma_x + sigma_y + C2);
if denominator > 0.0 {
ssim_sum += numerator / denominator;
count += 1;
}
}
}
if count > 0 {
ssim_sum / count as f64
} else {
0.0
}
}
fn calculate_window_stats(
original: &[u8],
reconstructed: &[u8],
stride: usize,
x: usize,
y: usize,
) -> (f64, f64, f64, f64, f64) {
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut sum_xx = 0.0;
let mut sum_yy = 0.0;
let mut sum_xy = 0.0;
let n = 64.0;
for dy in 0..8 {
for dx in 0..8 {
let offset = (y + dy) * stride + x + dx;
if offset < original.len() && offset < reconstructed.len() {
let px = f64::from(original[offset]);
let py = f64::from(reconstructed[offset]);
sum_x += px;
sum_y += py;
sum_xx += px * px;
sum_yy += py * py;
sum_xy += px * py;
}
}
}
let mu_x = sum_x / n;
let mu_y = sum_y / n;
let sigma_x = (sum_xx / n - mu_x * mu_x).max(0.0);
let sigma_y = (sum_yy / n - mu_y * mu_y).max(0.0);
let sigma_xy = sum_xy / n - mu_x * mu_y;
(mu_x, mu_y, sigma_x, sigma_y, sigma_xy)
}
}
#[derive(Debug, Clone)]
pub struct BitrateDistribution {
buckets: Vec<usize>,
bucket_size: usize,
}
impl BitrateDistribution {
#[must_use]
pub fn new(bucket_size: usize) -> Self {
Self {
buckets: vec![0; 100],
bucket_size,
}
}
pub fn add_frame(&mut self, bitrate: f64) {
let bucket_idx = (bitrate / 1000.0 / self.bucket_size as f64) as usize;
if bucket_idx < self.buckets.len() {
self.buckets[bucket_idx] += 1;
}
}
#[must_use]
pub fn mode(&self) -> usize {
self.buckets
.iter()
.enumerate()
.max_by_key(|(_, &count)| count)
.map(|(idx, _)| idx * self.bucket_size)
.unwrap_or(0)
}
#[must_use]
pub fn variance(&self) -> f64 {
let total: usize = self.buckets.iter().sum();
if total == 0 {
return 0.0;
}
let mean = self.mean();
let mut variance = 0.0;
for (idx, &count) in self.buckets.iter().enumerate() {
let value = (idx * self.bucket_size) as f64;
let diff = value - mean;
variance += diff * diff * count as f64;
}
variance / total as f64
}
#[must_use]
pub fn mean(&self) -> f64 {
let total: usize = self.buckets.iter().sum();
if total == 0 {
return 0.0;
}
let sum: usize = self
.buckets
.iter()
.enumerate()
.map(|(idx, &count)| idx * self.bucket_size * count)
.sum();
sum as f64 / total as f64
}
}
pub struct ComplexityAnalyzer {
history: Vec<f64>,
max_history: usize,
}
impl ComplexityAnalyzer {
#[must_use]
pub fn new(max_history: usize) -> Self {
Self {
history: Vec::new(),
max_history,
}
}
pub fn add_measurement(&mut self, complexity: f64) {
self.history.push(complexity);
if self.history.len() > self.max_history {
self.history.remove(0);
}
}
#[must_use]
pub fn average(&self) -> f64 {
if self.history.is_empty() {
return 0.0;
}
let sum: f64 = self.history.iter().sum();
sum / self.history.len() as f64
}
#[must_use]
pub fn trend(&self) -> f64 {
if self.history.len() < 2 {
return 0.0;
}
let half = self.history.len() / 2;
let first_half: f64 = self.history[..half].iter().sum::<f64>() / half as f64;
let second_half: f64 =
self.history[half..].iter().sum::<f64>() / (self.history.len() - half) as f64;
second_half - first_half
}
#[must_use]
pub fn std_dev(&self) -> f64 {
if self.history.is_empty() {
return 0.0;
}
let mean = self.average();
let variance: f64 = self
.history
.iter()
.map(|&x| (x - mean).powi(2))
.sum::<f64>()
/ self.history.len() as f64;
variance.sqrt()
}
}
#[derive(Debug, Clone, Default)]
pub struct PerformanceProfiler {
pub motion_estimation_time: Duration,
pub mode_decision_time: Duration,
pub transform_time: Duration,
pub quantization_time: Duration,
pub entropy_coding_time: Duration,
pub reconstruction_time: Duration,
}
impl PerformanceProfiler {
#[must_use]
pub fn total_time(&self) -> Duration {
self.motion_estimation_time
+ self.mode_decision_time
+ self.transform_time
+ self.quantization_time
+ self.entropy_coding_time
+ self.reconstruction_time
}
#[must_use]
pub fn percentage_breakdown(&self) -> ProfilerBreakdown {
let total = self.total_time().as_secs_f64();
if total < 1e-6 {
return ProfilerBreakdown::default();
}
ProfilerBreakdown {
motion_estimation: (self.motion_estimation_time.as_secs_f64() / total) * 100.0,
mode_decision: (self.mode_decision_time.as_secs_f64() / total) * 100.0,
transform: (self.transform_time.as_secs_f64() / total) * 100.0,
quantization: (self.quantization_time.as_secs_f64() / total) * 100.0,
entropy_coding: (self.entropy_coding_time.as_secs_f64() / total) * 100.0,
reconstruction: (self.reconstruction_time.as_secs_f64() / total) * 100.0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ProfilerBreakdown {
pub motion_estimation: f64,
pub mode_decision: f64,
pub transform: f64,
pub quantization: f64,
pub entropy_coding: f64,
pub reconstruction: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_stats() {
let stats = FrameEncodingStats::new(0, true, 10000, 30, Duration::from_millis(50));
assert_eq!(stats.frame_number, 0);
assert!(stats.is_keyframe);
assert_eq!(stats.size_bytes, 10000);
}
#[test]
fn test_encoder_stats() {
let mut stats = EncoderStats::new();
stats.start_timing();
stats.add_frame(FrameEncodingStats::new(
0,
true,
10000,
30,
Duration::from_millis(50),
));
stats.add_frame(FrameEncodingStats::new(
1,
false,
5000,
32,
Duration::from_millis(40),
));
assert_eq!(stats.frames.len(), 2);
assert!(stats.average_encode_time() > Duration::ZERO);
}
#[test]
fn test_psnr_calculation() {
let original = vec![100u8; 64];
let reconstructed = vec![100u8; 64];
let psnr = QualityMetrics::calculate_psnr(&original, &reconstructed, 8, 8);
assert!(psnr > 90.0); }
#[test]
fn test_bitrate_distribution() {
let mut dist = BitrateDistribution::new(100);
dist.add_frame(1500.0 * 1000.0); dist.add_frame(1600.0 * 1000.0); dist.add_frame(1550.0 * 1000.0);
let mode = dist.mode();
assert!(mode >= 1500 && mode <= 1600);
}
#[test]
fn test_complexity_analyzer() {
let mut analyzer = ComplexityAnalyzer::new(10);
analyzer.add_measurement(100.0);
analyzer.add_measurement(150.0);
analyzer.add_measurement(200.0);
assert!(analyzer.average() > 100.0);
assert!(analyzer.trend() > 0.0); }
#[test]
fn test_performance_profiler() {
let mut profiler = PerformanceProfiler::default();
profiler.motion_estimation_time = Duration::from_millis(100);
profiler.transform_time = Duration::from_millis(50);
assert_eq!(profiler.total_time(), Duration::from_millis(150));
let breakdown = profiler.percentage_breakdown();
assert!((breakdown.motion_estimation - 66.67).abs() < 0.1);
}
}