pub mod q14 {
pub const SCALE: f64 = 16384.0;
pub const SCALE_F32: f32 = 16384.0;
#[inline]
pub fn to_float(q14_value: u16) -> f64 {
q14_value as f64 / SCALE
}
#[inline]
pub fn to_per_mille(q14_value: u16) -> f32 {
q14_value as f32 / (SCALE_F32 / 1000.0) }
#[inline]
pub fn from_float(ratio: f64) -> u16 {
(ratio * SCALE).clamp(0.0, SCALE) as u16
}
#[inline]
pub fn from_per_mille(per_mille: f32) -> u16 {
from_float((per_mille / 1000.0) as f64)
}
}
use crate::neteq::Operation;
use serde::{Deserialize, Serialize};
use web_time::{Duration, Instant};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct OperationCounters {
pub normal_per_sec: f32,
pub expand_per_sec: f32,
pub accelerate_per_sec: f32,
pub fast_accelerate_per_sec: f32,
pub preemptive_expand_per_sec: f32,
pub merge_per_sec: f32,
pub comfort_noise_per_sec: f32,
pub dtmf_per_sec: f32,
pub undefined_per_sec: f32,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NetworkStatistics {
pub current_buffer_size_ms: u16,
pub preferred_buffer_size_ms: u16,
pub jitter_peaks_found: u16,
pub expand_rate: u16,
pub speech_expand_rate: u16,
pub preemptive_rate: u16,
pub accelerate_rate: u16,
pub mean_waiting_time_ms: i32,
pub median_waiting_time_ms: i32,
pub min_waiting_time_ms: i32,
pub max_waiting_time_ms: i32,
pub reordered_packets: u32,
pub total_packets_received: u32,
pub reorder_rate_permyriad: u16, pub max_reorder_distance: u16, pub operation_counters: OperationCounters,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LifetimeStatistics {
pub total_samples_received: u64,
pub concealed_samples: u64,
pub concealment_events: u64,
pub jitter_buffer_delay_ms: u64,
pub jitter_buffer_emitted_count: u64,
pub jitter_buffer_target_delay_ms: u64,
pub inserted_samples_for_deceleration: u64,
pub removed_samples_for_acceleration: u64,
pub silent_concealed_samples: u64,
pub relative_packet_arrival_delay_ms: u64,
pub jitter_buffer_packets_received: u64,
pub buffer_flushes: u64,
pub late_packets_discarded: u64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct OperationStatistics {
pub preemptive_samples: u64,
pub accelerate_samples: u64,
pub packet_buffer_flushes: u64,
pub discarded_primary_packets: u64,
pub last_waiting_time_ms: u64,
pub current_buffer_size_ms: u64,
pub current_frame_size_ms: u64,
pub next_packet_available: bool,
}
#[derive(Debug)]
pub struct StatisticsCalculator {
network_stats: NetworkStatistics,
lifetime_stats: LifetimeStatistics,
operation_stats: OperationStatistics,
start_time: Instant,
waiting_times: Vec<i32>,
_last_update: Instant,
total_output_samples: u64,
total_expanded_samples: u64,
operation_counts: [u32; 9], last_operation_update: Instant,
operation_window_duration: Duration,
}
impl Default for StatisticsCalculator {
fn default() -> Self {
Self::new()
}
}
impl StatisticsCalculator {
pub fn new() -> Self {
let now = Instant::now();
Self {
network_stats: NetworkStatistics::default(),
lifetime_stats: LifetimeStatistics::default(),
operation_stats: OperationStatistics::default(),
start_time: now,
waiting_times: Vec::new(),
_last_update: now,
total_output_samples: 0,
total_expanded_samples: 0,
operation_counts: [0; 9],
last_operation_update: now,
operation_window_duration: Duration::from_secs(1),
}
}
pub fn update_buffer_size(&mut self, current_ms: u16, preferred_ms: u16) {
self.network_stats.current_buffer_size_ms = current_ms;
self.network_stats.preferred_buffer_size_ms = preferred_ms;
self.operation_stats.current_buffer_size_ms = current_ms as u64;
}
pub fn packet_arrived(&mut self, arrival_delay_ms: i32) {
self.lifetime_stats.jitter_buffer_packets_received += 1;
self.waiting_times.push(arrival_delay_ms);
if self.waiting_times.len() > 100 {
self.waiting_times.remove(0);
}
self.update_waiting_time_stats();
}
pub fn jitter_buffer_delay(&mut self, delay_ms: u64, emitted_samples: u64) {
self.lifetime_stats.jitter_buffer_delay_ms += delay_ms;
self.lifetime_stats.jitter_buffer_emitted_count += emitted_samples;
}
pub fn concealment_event(&mut self, concealed_samples: u64, is_silent: bool) {
self.lifetime_stats.concealment_events += 1;
self.lifetime_stats.concealed_samples += concealed_samples;
if is_silent {
self.lifetime_stats.silent_concealed_samples += concealed_samples;
}
}
pub fn time_stretch_operation(&mut self, operation: TimeStretchOperation, samples: u64) {
self.total_output_samples += samples;
match operation {
TimeStretchOperation::Accelerate => {
self.lifetime_stats.removed_samples_for_acceleration += samples;
self.operation_stats.accelerate_samples += samples;
if self.total_output_samples > 0 {
let ratio = self.lifetime_stats.removed_samples_for_acceleration as f64
/ self.total_output_samples as f64;
self.network_stats.accelerate_rate = q14::from_float(ratio);
}
}
TimeStretchOperation::PreemptiveExpand => {
self.lifetime_stats.inserted_samples_for_deceleration += samples;
self.operation_stats.preemptive_samples += samples;
if self.total_output_samples > 0 {
let ratio = self.lifetime_stats.inserted_samples_for_deceleration as f64
/ self.total_output_samples as f64;
self.network_stats.preemptive_rate = q14::from_float(ratio);
}
}
TimeStretchOperation::Expand => {
self.total_expanded_samples += samples;
if self.total_output_samples > 0 {
let ratio =
self.total_expanded_samples as f64 / self.total_output_samples as f64;
self.network_stats.expand_rate = q14::from_float(ratio);
}
}
}
}
pub fn record_decode_operation(&mut self, operation: Operation) {
let now = Instant::now();
let index = match operation {
Operation::Normal => 0,
Operation::Merge => 1,
Operation::Expand => 2,
Operation::ExpandStart => 3,
Operation::ExpandEnd => 4,
Operation::Accelerate => 5,
Operation::FastAccelerate => 6,
Operation::PreemptiveExpand => 7,
Operation::TimeStretchBuffer => 8,
Operation::ComfortNoise => 9,
Operation::Dtmf => 10,
Operation::Undefined => 11,
};
self.operation_counts[index] += 1;
if now.duration_since(self.last_operation_update) >= self.operation_window_duration {
self.update_operation_rates(now);
}
}
fn update_operation_rates(&mut self, now: Instant) {
let elapsed = now.duration_since(self.last_operation_update);
let elapsed_secs = elapsed.as_secs_f32();
if elapsed_secs > 0.0 {
self.network_stats.operation_counters.normal_per_sec =
self.operation_counts[0] as f32 / elapsed_secs;
self.network_stats.operation_counters.merge_per_sec =
self.operation_counts[1] as f32 / elapsed_secs;
self.network_stats.operation_counters.expand_per_sec =
self.operation_counts[2] as f32 / elapsed_secs;
self.network_stats.operation_counters.accelerate_per_sec =
self.operation_counts[3] as f32 / elapsed_secs;
self.network_stats
.operation_counters
.fast_accelerate_per_sec = self.operation_counts[4] as f32 / elapsed_secs;
self.network_stats
.operation_counters
.preemptive_expand_per_sec = self.operation_counts[5] as f32 / elapsed_secs;
self.network_stats.operation_counters.comfort_noise_per_sec =
self.operation_counts[6] as f32 / elapsed_secs;
self.network_stats.operation_counters.dtmf_per_sec =
self.operation_counts[7] as f32 / elapsed_secs;
self.network_stats.operation_counters.undefined_per_sec =
self.operation_counts[8] as f32 / elapsed_secs;
self.operation_counts.fill(0);
self.last_operation_update = now;
}
}
pub fn buffer_flush(&mut self) {
self.lifetime_stats.buffer_flushes += 1;
self.operation_stats.packet_buffer_flushes += 1;
}
pub fn packet_discarded(&mut self, is_late: bool) {
if is_late {
self.lifetime_stats.late_packets_discarded += 1;
}
self.operation_stats.discarded_primary_packets += 1;
}
pub fn packet_reordered(&mut self, sequence_distance: u16) {
self.network_stats.reordered_packets += 1;
self.network_stats.total_packets_received += 1;
if sequence_distance > self.network_stats.max_reorder_distance {
self.network_stats.max_reorder_distance = sequence_distance;
}
let rate = (self.network_stats.reordered_packets as f64
/ self.network_stats.total_packets_received as f64)
* 10000.0;
self.network_stats.reorder_rate_permyriad = rate as u16;
}
pub fn packet_in_order(&mut self) {
self.network_stats.total_packets_received += 1;
let rate = (self.network_stats.reordered_packets as f64
/ self.network_stats.total_packets_received as f64)
* 10000.0;
self.network_stats.reorder_rate_permyriad = rate as u16;
}
pub fn network_statistics(&self) -> &NetworkStatistics {
&self.network_stats
}
pub fn lifetime_statistics(&self) -> &LifetimeStatistics {
&self.lifetime_stats
}
pub fn operation_statistics(&self) -> &OperationStatistics {
&self.operation_stats
}
pub fn uptime(&self) -> Duration {
self.start_time.elapsed()
}
pub fn reset(&mut self) {
*self = Self::new();
}
fn update_waiting_time_stats(&mut self) {
if self.waiting_times.is_empty() {
return;
}
let mut sorted_times = self.waiting_times.clone();
sorted_times.sort_unstable();
self.network_stats.min_waiting_time_ms = *sorted_times.first().unwrap();
self.network_stats.max_waiting_time_ms = *sorted_times.last().unwrap();
let sum: i32 = sorted_times.iter().sum();
self.network_stats.mean_waiting_time_ms = sum / sorted_times.len() as i32;
let median_idx = sorted_times.len() / 2;
self.network_stats.median_waiting_time_ms = sorted_times[median_idx];
self.operation_stats.last_waiting_time_ms = *self.waiting_times.last().unwrap() as u64;
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TimeStretchOperation {
Accelerate,
PreemptiveExpand,
Expand,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_statistics_calculator() {
let mut calc = StatisticsCalculator::new();
calc.update_buffer_size(100, 120);
assert_eq!(calc.network_statistics().current_buffer_size_ms, 100);
assert_eq!(calc.network_statistics().preferred_buffer_size_ms, 120);
calc.packet_arrived(50);
assert_eq!(calc.lifetime_statistics().jitter_buffer_packets_received, 1);
assert_eq!(calc.network_statistics().mean_waiting_time_ms, 50);
calc.concealment_event(160, false);
assert_eq!(calc.lifetime_statistics().concealment_events, 1);
assert_eq!(calc.lifetime_statistics().concealed_samples, 160);
calc.time_stretch_operation(TimeStretchOperation::Accelerate, 80);
assert_eq!(
calc.lifetime_statistics().removed_samples_for_acceleration,
80
);
calc.buffer_flush();
assert_eq!(calc.lifetime_statistics().buffer_flushes, 1);
}
#[test]
fn test_waiting_time_statistics() {
let mut calc = StatisticsCalculator::new();
calc.packet_arrived(10);
calc.packet_arrived(20);
calc.packet_arrived(30);
calc.packet_arrived(15);
calc.packet_arrived(25);
let stats = calc.network_statistics();
assert_eq!(stats.min_waiting_time_ms, 10);
assert_eq!(stats.max_waiting_time_ms, 30);
assert_eq!(stats.mean_waiting_time_ms, 20); assert_eq!(stats.median_waiting_time_ms, 20); }
#[test]
fn test_expand_rate_calculation_fix() {
let mut stats = StatisticsCalculator::new();
for _ in 0..10 {
stats.time_stretch_operation(TimeStretchOperation::Accelerate, 160);
}
stats.network_stats.accelerate_rate = 0;
stats.lifetime_stats.removed_samples_for_acceleration = 0;
stats.time_stretch_operation(TimeStretchOperation::Expand, 160);
stats.time_stretch_operation(TimeStretchOperation::Expand, 160);
let expected_q14 = ((320.0 / 1920.0) * 16384.0) as u16;
assert_eq!(stats.network_stats.expand_rate, expected_q14);
let ui_display_rate = stats.network_stats.expand_rate as f32 / 16.384;
assert!(
(ui_display_rate - 166.7).abs() < 1.0,
"Expected ~166.7‰, got {ui_display_rate:.1}‰"
);
println!("✅ Expand rate calculation fix verified:");
println!(" Q14 value: {}", stats.network_stats.expand_rate);
println!(" UI display: {ui_display_rate:.1}‰ (was showing 7864.0)");
}
#[test]
fn test_expand_rate_no_longer_spikes_to_7864() {
let mut stats = StatisticsCalculator::new();
for _ in 0..5 {
stats.time_stretch_operation(TimeStretchOperation::Accelerate, 160);
}
stats.network_stats.accelerate_rate = 0;
stats.lifetime_stats.removed_samples_for_acceleration = 0;
stats.time_stretch_operation(TimeStretchOperation::Expand, 480);
let ui_rate = stats.network_stats.expand_rate as f32 / 16.384;
assert!(
ui_rate < 1000.0,
"Expand rate still too high: {ui_rate:.1}‰ (was 7864.0)"
);
let expected_rate = (480.0 / 1280.0) * 1000.0; assert!(
(ui_rate - expected_rate).abs() < 50.0,
"Expected ~{expected_rate:.1}‰, got {ui_rate:.1}‰"
);
println!("🚀 Bug fix verified: expand rate no longer spikes to 7864.0");
println!(" Old broken value: 7864.0‰");
println!(" New correct value: {ui_rate:.1}‰");
}
#[test]
fn test_q14_documentation_examples() {
use super::q14;
println!("🧮 Testing Q14 documentation examples...");
assert_eq!(q14::to_float(0), 0.0);
assert_eq!(q14::to_float(4096), 0.25);
assert_eq!(q14::to_float(8192), 0.5);
assert_eq!(q14::to_float(16384), 1.0);
assert_eq!(q14::to_per_mille(0), 0.0);
assert!((q14::to_per_mille(4096) - 250.0).abs() < 0.01);
assert!((q14::to_per_mille(8192) - 500.0).abs() < 0.01);
assert!((q14::to_per_mille(16384) - 1000.0).abs() < 0.01);
assert_eq!(q14::from_float(0.0), 0);
assert_eq!(q14::from_float(0.25), 4096);
assert_eq!(q14::from_float(0.5), 8192);
assert_eq!(q14::from_float(1.0), 16384);
assert_eq!(q14::from_float(2.0), 16384); assert_eq!(q14::from_float(-1.0), 0);
assert_eq!(q14::from_per_mille(0.0), 0);
assert_eq!(q14::from_per_mille(250.0), 4096);
assert_eq!(q14::from_per_mille(500.0), 8192);
assert_eq!(q14::from_per_mille(1000.0), 16384);
let ratio = 320.0 / 1280.0; let q14_value = q14::from_float(ratio); let display_rate = q14::to_per_mille(q14_value);
assert_eq!(q14_value, 4096);
assert!((display_rate - 250.0).abs() < 0.01);
println!("✅ All Q14 documentation examples validated!");
let precision = 1.0 / q14::SCALE;
println!("📏 Q14 precision: {precision:.6} (~0.00006)");
let webrtc_conversion = 4096.0 / 16384.0; let our_conversion = q14::to_float(4096); assert_eq!(webrtc_conversion, our_conversion);
println!("🎯 WebRTC compatibility: {webrtc_conversion} == {our_conversion}");
}
#[test]
fn test_q14_real_world_scenarios() {
use super::q14;
println!("🌍 Testing real-world Q14 scenarios...");
let expanded_samples = 150_u64;
let total_samples = 1000_u64;
let ratio = expanded_samples as f64 / total_samples as f64;
let q14_rate = q14::from_float(ratio);
let ui_display = q14::to_per_mille(q14_rate);
println!("📊 15% expansion: {expanded_samples} samples / {total_samples} total");
println!(" Ratio: {ratio:.3}");
println!(" Q14: {q14_rate}");
println!(" UI: {ui_display:.1}‰");
assert_eq!(ratio, 0.15);
assert_eq!(q14_rate, 2457); assert!((ui_display - 150.0).abs() < 0.1);
let bug_scenario_ratio = 480.0 / 1000.0;
let bug_q14 = q14::from_float(bug_scenario_ratio);
let bug_display = q14::to_per_mille(bug_q14);
println!("🐛 Original bug scenario:");
println!(
" WebRTC raw magic: {:.1} (broken)",
bug_scenario_ratio * 16384.0
);
println!(" Our Q14 value: {bug_q14}");
println!(" Our UI display: {bug_display:.1}‰ (correct)");
assert_eq!(bug_q14, 7864); assert!((bug_display - 480.0).abs() < 0.1);
assert_ne!(7864.0, bug_display);
println!("✅ Real-world scenarios validated!");
}
}