diff --git a/alvr/dashboard/src/dashboard/components/statistics.rs b/alvr/dashboard/src/dashboard/components/statistics.rs index a19638b52a..4f82403a23 100644 --- a/alvr/dashboard/src/dashboard/components/statistics.rs +++ b/alvr/dashboard/src/dashboard/components/statistics.rs @@ -248,14 +248,16 @@ impl StatisticsTab { draw_lines(painter, client_fps_points, graph_colors::CLIENT_FPS); }, |ui, stats| { - ui.colored_label( - graph_colors::SERVER_FPS, - format!("Streamer FPS: {:.2}", stats.server_fps), - ); - ui.colored_label( - graph_colors::CLIENT_FPS, - format!("Client FPS: {:.2}", stats.client_fps), - ); + Grid::new("fps_tooltip").num_columns(2).show(ui, |ui| { + fn label(ui: &mut Ui, text: &str, value: f32, color: Color32) { + ui.colored_label(color, text); + ui.colored_label(color, format!("{:.2}Hz", value)); + ui.end_row(); + } + + label(ui, "Server FPS", stats.server_fps, graph_colors::SERVER_FPS); + label(ui, "Client FPS", stats.client_fps, graph_colors::CLIENT_FPS); + }); }, ); } @@ -264,107 +266,168 @@ impl StatisticsTab { let mut data = statistics::Data::new( self.history .iter() - .map(|stats| stats.actual_bitrate_bps as f64) + .map(|stats| stats.throughput_bps as f64) .collect::>(), ); self.draw_graph( ui, available_width, - "Bitrate", - 0.0..=(data.quantile(UPPER_QUANTILE) * 2.0) as f32 / 1e6, + "Bitrate and Throughput", + 0.0..=(data.quantile(UPPER_QUANTILE) * 1.2) as f32 / 1e6, |painter, to_screen_trans| { let mut scaled_calculated = Vec::with_capacity(GRAPH_HISTORY_SIZE); let mut decoder_latency_limiter = Vec::with_capacity(GRAPH_HISTORY_SIZE); let mut network_latency_limiter = Vec::with_capacity(GRAPH_HISTORY_SIZE); let mut encoder_latency_limiter = Vec::with_capacity(GRAPH_HISTORY_SIZE); - let mut manual_max = Vec::with_capacity(GRAPH_HISTORY_SIZE); - let mut manual_min = Vec::with_capacity(GRAPH_HISTORY_SIZE); - let mut requested = Vec::with_capacity(GRAPH_HISTORY_SIZE); - let mut actual = Vec::with_capacity(GRAPH_HISTORY_SIZE); + let mut max_throughput = Vec::with_capacity(GRAPH_HISTORY_SIZE); + let mut min_throughput = Vec::with_capacity(GRAPH_HISTORY_SIZE); + let mut requested_bitrate = Vec::with_capacity(GRAPH_HISTORY_SIZE); + let mut recorded_throughput = Vec::with_capacity(GRAPH_HISTORY_SIZE); + let mut recorded_bitrate = Vec::with_capacity(GRAPH_HISTORY_SIZE); for i in 0..GRAPH_HISTORY_SIZE { - let nom_br = &self.history[i].nominal_bitrate; + let d = &self.history[i].bitrate_directives; - if let Some(value) = nom_br.scaled_calculated_bps { + if let Some(value) = d.scaled_calculated_throughput_bps { scaled_calculated.push(to_screen_trans * pos2(i as f32, value / 1e6)) } - if let Some(value) = nom_br.decoder_latency_limiter_bps { + if let Some(value) = d.decoder_latency_limiter_bps { decoder_latency_limiter.push(to_screen_trans * pos2(i as f32, value / 1e6)) } - if let Some(value) = nom_br.network_latency_limiter_bps { + if let Some(value) = d.network_latency_limiter_bps { network_latency_limiter.push(to_screen_trans * pos2(i as f32, value / 1e6)) } - if let Some(value) = nom_br.encoder_latency_limiter_bps { + if let Some(value) = d.encoder_latency_limiter_bps { encoder_latency_limiter.push(to_screen_trans * pos2(i as f32, value / 1e6)) } - if let Some(value) = nom_br.manual_max_bps { - manual_max.push(to_screen_trans * pos2(i as f32, value / 1e6)) + if let Some(value) = d.manual_max_throughput_bps { + max_throughput.push(to_screen_trans * pos2(i as f32, value / 1e6)) } - if let Some(value) = nom_br.manual_min_bps { - manual_min.push(to_screen_trans * pos2(i as f32, value / 1e6)) + if let Some(value) = d.manual_min_throughput_bps { + min_throughput.push(to_screen_trans * pos2(i as f32, value / 1e6)) } - requested.push(to_screen_trans * pos2(i as f32, nom_br.requested_bps / 1e6)); - actual.push( - to_screen_trans * pos2(i as f32, self.history[i].actual_bitrate_bps / 1e6), + requested_bitrate + .push(to_screen_trans * pos2(i as f32, d.requested_bitrate_bps / 1e6)); + recorded_throughput.push( + to_screen_trans * pos2(i as f32, self.history[i].throughput_bps / 1e6), ); + recorded_bitrate + .push(to_screen_trans * pos2(i as f32, self.history[i].bitrate_bps / 1e6)); } - draw_lines(painter, scaled_calculated, Color32::GRAY); - draw_lines(painter, encoder_latency_limiter, graph_colors::TRANSCODE); - draw_lines(painter, network_latency_limiter, graph_colors::NETWORK); - draw_lines(painter, decoder_latency_limiter, graph_colors::TRANSCODE); - draw_lines(painter, manual_max, graph_colors::RENDER); - draw_lines(painter, manual_min, graph_colors::RENDER); - draw_lines(painter, requested, theme::OK_GREEN); - draw_lines(painter, actual, theme::FG); - }, - |ui, stats| { - fn maybe_label( - ui: &mut Ui, - text: &str, - maybe_value_bps: Option, - color: Color32, - ) { - if let Some(value) = maybe_value_bps { - ui.colored_label(color, format!("{text}: {:.2} Mbps", value / 1e6)); - } - } - - let n = &stats.nominal_bitrate; - - maybe_label( - ui, - "Initial calculated", - n.scaled_calculated_bps, - Color32::GRAY, + draw_lines( + painter, + scaled_calculated, + graph_colors::INITIAL_CALCULATED_THROUGHPUT, + ); + draw_lines( + painter, + encoder_latency_limiter, + graph_colors::ENCODER_DECODER_LATENCY_LIMITER, + ); + draw_lines( + painter, + network_latency_limiter, + graph_colors::NETWORK_LATENCY_LIMITER, ); - maybe_label( - ui, - "Encoder latency limiter", - n.encoder_latency_limiter_bps, - graph_colors::TRANSCODE, + draw_lines( + painter, + decoder_latency_limiter, + graph_colors::ENCODER_DECODER_LATENCY_LIMITER, ); - maybe_label( - ui, - "Network latency limiter", - n.network_latency_limiter_bps, - graph_colors::NETWORK, + draw_lines( + painter, + max_throughput, + graph_colors::MIN_MAX_LATENCY_THROUGHPUT, ); - maybe_label( - ui, - "Decoder latency limiter", - n.decoder_latency_limiter_bps, - graph_colors::TRANSCODE, + draw_lines( + painter, + min_throughput, + graph_colors::MIN_MAX_LATENCY_THROUGHPUT, ); - maybe_label(ui, "Manual max", n.manual_max_bps, graph_colors::RENDER); - maybe_label(ui, "Manual min", n.manual_min_bps, graph_colors::RENDER); - maybe_label(ui, "Requested", Some(n.requested_bps), theme::OK_GREEN); - maybe_label( - ui, - "Actual recorded", - Some(stats.actual_bitrate_bps), - theme::FG, + draw_lines(painter, requested_bitrate, graph_colors::REQUESTED_BITRATE); + draw_lines( + painter, + recorded_throughput, + graph_colors::RECORDED_THROUGHPUT, ); + draw_lines(painter, recorded_bitrate, theme::FG); + }, + |ui, stats| { + Grid::new("bitrate_tooltip").num_columns(2).show(ui, |ui| { + fn maybe_label( + ui: &mut Ui, + text: &str, + maybe_value_bps: Option, + color: Color32, + ) { + if let Some(value) = maybe_value_bps { + ui.colored_label(color, text); + ui.colored_label(color, format!("{:.2} Mbps", value / 1e6)); + ui.end_row(); + } + } + + let td = &stats.bitrate_directives; + + maybe_label( + ui, + "Initial calculated throughput", + td.scaled_calculated_throughput_bps, + graph_colors::INITIAL_CALCULATED_THROUGHPUT, + ); + maybe_label( + ui, + "Encoder latency limiter", + td.encoder_latency_limiter_bps, + graph_colors::ENCODER_DECODER_LATENCY_LIMITER, + ); + maybe_label( + ui, + "Network latency limiter", + td.network_latency_limiter_bps, + graph_colors::NETWORK_LATENCY_LIMITER, + ); + maybe_label( + ui, + "Decoder latency limiter", + td.decoder_latency_limiter_bps + .filter(|l| *l < stats.throughput_bps), + graph_colors::ENCODER_DECODER_LATENCY_LIMITER, + ); + maybe_label( + ui, + "Manual max throughput", + td.manual_max_throughput_bps, + graph_colors::MIN_MAX_LATENCY_THROUGHPUT, + ); + maybe_label( + ui, + "Manual min throughput", + td.manual_min_throughput_bps, + graph_colors::MIN_MAX_LATENCY_THROUGHPUT, + ); + maybe_label( + ui, + "Requested bitrate", + Some(td.requested_bitrate_bps), + graph_colors::REQUESTED_BITRATE, + ); + maybe_label( + ui, + "Recorded throughput", + Some(stats.throughput_bps), + graph_colors::RECORDED_THROUGHPUT, + ); + maybe_label( + ui, + "Recorded bitrate", + Some(stats.bitrate_bps), + graph_colors::RECORDED_BITRATE, + ); + }); + + ui.small("Note: throughput is the peak bitrate, packet_size/network_latency."); }, ) } diff --git a/alvr/events/src/lib.rs b/alvr/events/src/lib.rs index adeca9d8d6..02ec1798e0 100644 --- a/alvr/events/src/lib.rs +++ b/alvr/events/src/lib.rs @@ -24,14 +24,14 @@ pub struct StatisticsSummary { // Bitrate statistics minus the empirical output value #[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct NominalBitrateStats { - pub scaled_calculated_bps: Option, +pub struct BitrateDirectives { + pub scaled_calculated_throughput_bps: Option, pub decoder_latency_limiter_bps: Option, pub network_latency_limiter_bps: Option, pub encoder_latency_limiter_bps: Option, - pub manual_max_bps: Option, - pub manual_min_bps: Option, - pub requested_bps: f32, + pub manual_max_throughput_bps: Option, + pub manual_min_throughput_bps: Option, + pub requested_bitrate_bps: f32, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] @@ -47,8 +47,9 @@ pub struct GraphStatistics { pub vsync_queue_s: f32, pub client_fps: f32, pub server_fps: f32, - pub nominal_bitrate: NominalBitrateStats, - pub actual_bitrate_bps: f32, + pub bitrate_directives: BitrateDirectives, + pub throughput_bps: f32, + pub bitrate_bps: f32, } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/alvr/gui_common/src/theme.rs b/alvr/gui_common/src/theme.rs index 572573d9cb..8b49181886 100644 --- a/alvr/gui_common/src/theme.rs +++ b/alvr/gui_common/src/theme.rs @@ -35,6 +35,14 @@ pub mod graph_colors { pub const SERVER_FPS: Color32 = Color32::LIGHT_BLUE; pub const CLIENT_FPS: Color32 = Color32::KHAKI; + + pub const INITIAL_CALCULATED_THROUGHPUT: Color32 = Color32::GRAY; + pub const ENCODER_DECODER_LATENCY_LIMITER: Color32 = TRANSCODE; + pub const NETWORK_LATENCY_LIMITER: Color32 = NETWORK; + pub const MIN_MAX_LATENCY_THROUGHPUT: Color32 = Color32::RED; + pub const REQUESTED_BITRATE: Color32 = Color32::GREEN; + pub const RECORDED_THROUGHPUT: Color32 = Color32::KHAKI; + pub const RECORDED_BITRATE: Color32 = super::FG; } pub fn set_theme(ctx: &Context) { diff --git a/alvr/server_core/src/bitrate.rs b/alvr/server_core/src/bitrate.rs index 7acb134547..10d51fa177 100644 --- a/alvr/server_core/src/bitrate.rs +++ b/alvr/server_core/src/bitrate.rs @@ -1,5 +1,5 @@ use alvr_common::SlidingWindowAverage; -use alvr_events::NominalBitrateStats; +use alvr_events::BitrateDirectives; use alvr_session::{ settings_schema::Switch, BitrateAdaptiveFramerateConfig, BitrateConfig, BitrateMode, }; @@ -11,7 +11,7 @@ use std::{ const UPDATE_INTERVAL: Duration = Duration::from_secs(1); pub struct DynamicEncoderParams { - pub bitrate_bps: u64, + pub bitrate_bps: f32, pub framerate: f32, } @@ -20,14 +20,14 @@ pub struct BitrateManager { frame_interval_average: SlidingWindowAverage, // note: why packet_sizes_bits_history is a queue and not a sliding average? Because some // network samples will be dropped but not any packet size sample - packet_sizes_bits_history: VecDeque<(Duration, usize)>, - encoder_latency_average: SlidingWindowAverage, + packet_bytes_history: VecDeque<(Duration, usize)>, + packet_bytes_average: SlidingWindowAverage, network_latency_average: SlidingWindowAverage, - bitrate_average: SlidingWindowAverage, + encoder_latency_average: SlidingWindowAverage, decoder_latency_overstep_count: usize, last_frame_instant: Instant, last_update_instant: Instant, - dynamic_max_bitrate: f32, + dynamic_decoder_max_bytes_per_frame: f32, previous_config: Option, update_needed: bool, } @@ -40,20 +40,20 @@ impl BitrateManager { Duration::from_millis(16), max_history_size, ), - packet_sizes_bits_history: VecDeque::new(), - encoder_latency_average: SlidingWindowAverage::new( + packet_bytes_history: VecDeque::new(), + packet_bytes_average: SlidingWindowAverage::new(50000.0, max_history_size), + network_latency_average: SlidingWindowAverage::new( Duration::from_millis(5), max_history_size, ), - network_latency_average: SlidingWindowAverage::new( + encoder_latency_average: SlidingWindowAverage::new( Duration::from_millis(5), max_history_size, ), - bitrate_average: SlidingWindowAverage::new(30_000_000.0, max_history_size), decoder_latency_overstep_count: 0, last_frame_instant: Instant::now(), last_update_instant: Instant::now(), - dynamic_max_bitrate: f32::MAX, + dynamic_decoder_max_bytes_per_frame: f32::MAX, previous_config: None, update_needed: true, } @@ -91,8 +91,7 @@ impl BitrateManager { ) { self.encoder_latency_average.submit_sample(encoder_latency); - self.packet_sizes_bits_history - .push_back((timestamp, size_bytes * 8)); + self.packet_bytes_history.push_back((timestamp, size_bytes)); } // decoder_latency is used to learn a suitable maximum bitrate bound to avoid decoder runaway @@ -108,18 +107,16 @@ impl BitrateManager { return; } - self.network_latency_average.submit_sample(network_latency); + while let Some(&(history_timestamp, size_bytes)) = self.packet_bytes_history.front() { + if history_timestamp == timestamp { + self.packet_bytes_average.submit_sample(size_bytes as f32); + self.network_latency_average.submit_sample(network_latency); - while let Some(&(timestamp_, size_bits)) = self.packet_sizes_bits_history.front() { - if timestamp_ == timestamp { - self.bitrate_average - .submit_sample(size_bits as f32 / network_latency.as_secs_f32()); - - self.packet_sizes_bits_history.pop_front(); + self.packet_bytes_history.pop_front(); break; } else { - self.packet_sizes_bits_history.pop_front(); + self.packet_bytes_history.pop_front(); } } @@ -132,9 +129,11 @@ impl BitrateManager { self.decoder_latency_overstep_count += 1; if self.decoder_latency_overstep_count == config.latency_overstep_frames { - self.dynamic_max_bitrate = - f32::min(self.bitrate_average.get_average(), self.dynamic_max_bitrate) - * config.latency_overstep_multiplier; + self.dynamic_decoder_max_bytes_per_frame = f32::min( + self.packet_bytes_average.get_average() as f32, + self.dynamic_decoder_max_bytes_per_frame, + ) * config + .latency_overstep_multiplier; self.update_needed = true; @@ -149,7 +148,7 @@ impl BitrateManager { pub fn get_encoder_params( &mut self, config: &BitrateConfig, - ) -> Option<(DynamicEncoderParams, NominalBitrateStats)> { + ) -> Option<(DynamicEncoderParams, BitrateDirectives)> { let now = Instant::now(); if self @@ -170,79 +169,89 @@ impl BitrateManager { self.last_update_instant = now; self.update_needed = false; - let mut stats = NominalBitrateStats::default(); + let frame_interval = if config.adapt_to_framerate.enabled() { + self.frame_interval_average.get_average() + } else { + self.nominal_frame_interval + }; + + let mut bitrate_directives = BitrateDirectives::default(); let bitrate_bps = match &config.mode { BitrateMode::ConstantMbps(bitrate_mbps) => *bitrate_mbps as f32 * 1e6, BitrateMode::Adaptive { saturation_multiplier, - max_bitrate_mbps, - min_bitrate_mbps, + max_throughput_mbps, + min_throughput_mbps, max_network_latency_ms, encoder_latency_limiter, - .. + decoder_latency_limiter, } => { - let initial_bitrate_average_bps = self.bitrate_average.get_average(); - - let mut bitrate_bps = initial_bitrate_average_bps * saturation_multiplier; - stats.scaled_calculated_bps = Some(bitrate_bps); - - bitrate_bps = f32::min(bitrate_bps, self.dynamic_max_bitrate); - stats.decoder_latency_limiter_bps = Some(self.dynamic_max_bitrate); + let packet_bytes_average = self.packet_bytes_average.get_average(); + let network_latency_average_s = + self.network_latency_average.get_average().as_secs_f32(); + + let mut throughput_bps = + packet_bytes_average * 8.0 * saturation_multiplier / network_latency_average_s; + bitrate_directives.scaled_calculated_throughput_bps = Some(throughput_bps); + + if decoder_latency_limiter.enabled() { + throughput_bps = + f32::min(throughput_bps, self.dynamic_decoder_max_bytes_per_frame); + bitrate_directives.decoder_latency_limiter_bps = + Some(self.dynamic_decoder_max_bytes_per_frame); + } if let Switch::Enabled(max_ms) = max_network_latency_ms { - let max = initial_bitrate_average_bps * (*max_ms as f32 / 1000.0) - / self.network_latency_average.get_average().as_secs_f32(); - bitrate_bps = f32::min(bitrate_bps, max); + let max_bps = + throughput_bps * (*max_ms as f32 / 1000.0) / network_latency_average_s; + throughput_bps = f32::min(throughput_bps, max_bps); - stats.network_latency_limiter_bps = Some(max); + bitrate_directives.network_latency_limiter_bps = Some(max_bps); } if let Switch::Enabled(config) = encoder_latency_limiter { + // Note: this assumes linear relationship between bitrate and encoder latency + // but this may not be the case let saturation = self.encoder_latency_average.get_average().as_secs_f32() / self.nominal_frame_interval.as_secs_f32(); - let max = - initial_bitrate_average_bps * config.max_saturation_multiplier / saturation; - stats.encoder_latency_limiter_bps = Some(max); + let max_bps = throughput_bps * config.max_saturation_multiplier / saturation; + bitrate_directives.encoder_latency_limiter_bps = Some(max_bps); if saturation > config.max_saturation_multiplier { - // Note: this assumes linear relationship between bitrate and encoder - // latency but this may not be the case - bitrate_bps = f32::min(bitrate_bps, max); + throughput_bps = f32::min(throughput_bps, max_bps); } } - if let Switch::Enabled(max) = max_bitrate_mbps { - let max = *max as f32 * 1e6; - bitrate_bps = f32::min(bitrate_bps, max); + if let Switch::Enabled(max) = max_throughput_mbps { + let max_bps = *max as f32 * 1e6; + throughput_bps = f32::min(throughput_bps, max_bps); - stats.manual_max_bps = Some(max); + bitrate_directives.manual_max_throughput_bps = Some(max_bps); } - if let Switch::Enabled(min) = min_bitrate_mbps { - let min = *min as f32 * 1e6; - bitrate_bps = f32::max(bitrate_bps, min); + if let Switch::Enabled(min) = min_throughput_mbps { + let min_bps = *min as f32 * 1e6; + throughput_bps = f32::max(throughput_bps, min_bps); - stats.manual_min_bps = Some(min); + bitrate_directives.manual_min_throughput_bps = Some(min_bps); } - bitrate_bps + // NB: Here we assign the calculated throughput to the requested bitrate. This is + // crucial for the working of the adaptive bitrate algorithm. The goal is to + // optimally occupy the available bandwidth, which is when the bitrate corresponds + // to the throughput. + throughput_bps } }; - stats.requested_bps = bitrate_bps; - - let frame_interval = if config.adapt_to_framerate.enabled() { - self.frame_interval_average.get_average() - } else { - self.nominal_frame_interval - }; + bitrate_directives.requested_bitrate_bps = bitrate_bps; Some(( DynamicEncoderParams { - bitrate_bps: bitrate_bps as u64, - framerate: 1.0 / frame_interval.as_secs_f32().min(1.0), + bitrate_bps, + framerate: 1.0 / f32::min(frame_interval.as_secs_f32(), 1.0), }, - stats, + bitrate_directives, )) } } diff --git a/alvr/server_core/src/c_api.rs b/alvr/server_core/src/c_api.rs index 529cad5189..c3d0af85cf 100644 --- a/alvr/server_core/src/c_api.rs +++ b/alvr/server_core/src/c_api.rs @@ -149,7 +149,7 @@ pub struct AlvrDeviceConfig { #[repr(C)] pub struct AlvrDynamicEncoderParams { - bitrate_bps: u64, + bitrate_bps: f32, framerate: f32, } diff --git a/alvr/server_core/src/lib.rs b/alvr/server_core/src/lib.rs index 1ec761b392..f640637c15 100644 --- a/alvr/server_core/src/lib.rs +++ b/alvr/server_core/src/lib.rs @@ -395,7 +395,7 @@ impl ServerCoreContext { if let Some((params, stats)) = pair { if let Some(stats_manager) = &mut *self.connection_context.statistics_manager.lock() { - stats_manager.report_nominal_bitrate_stats(stats); + stats_manager.report_throughput_stats(stats); } Some(params) diff --git a/alvr/server_core/src/statistics.rs b/alvr/server_core/src/statistics.rs index 4a236d0806..8d25f88edf 100644 --- a/alvr/server_core/src/statistics.rs +++ b/alvr/server_core/src/statistics.rs @@ -1,5 +1,5 @@ use alvr_common::{SlidingWindowAverage, HEAD_ID}; -use alvr_events::{EventType, GraphStatistics, NominalBitrateStats, StatisticsSummary}; +use alvr_events::{BitrateDirectives, EventType, GraphStatistics, StatisticsSummary}; use alvr_packets::ClientStatistics; use std::{ collections::{HashMap, VecDeque}, @@ -7,6 +7,7 @@ use std::{ }; const FULL_REPORT_INTERVAL: Duration = Duration::from_millis(500); +const EPS_INTERVAL: Duration = Duration::from_micros(1); pub struct HistoryFrame { target_timestamp: Duration, @@ -56,7 +57,7 @@ pub struct StatisticsManager { total_pipeline_latency_average: SlidingWindowAverage, last_vsync_time: Instant, frame_interval: Duration, - last_nominal_bitrate_stats: NominalBitrateStats, + last_throughput_directives: BitrateDirectives, } impl StatisticsManager { @@ -88,7 +89,7 @@ impl StatisticsManager { ), last_vsync_time: Instant::now(), frame_interval: nominal_server_frame_interval, - last_nominal_bitrate_stats: NominalBitrateStats::default(), + last_throughput_directives: BitrateDirectives::default(), } } @@ -176,8 +177,8 @@ impl StatisticsManager { }; } - pub fn report_nominal_bitrate_stats(&mut self, stats: NominalBitrateStats) { - self.last_nominal_bitrate_stats = stats; + pub fn report_throughput_stats(&mut self, stats: BitrateDirectives) { + self.last_throughput_directives = stats; } // Called every frame. Some statistics are reported once every frame @@ -218,16 +219,10 @@ impl StatisticsManager { + client_stats.vsync_queue, ); - let client_fps = 1.0 - / client_stats - .frame_interval - .max(Duration::from_millis(1)) - .as_secs_f32(); - let server_fps = 1.0 - / self - .last_frame_present_interval - .max(Duration::from_millis(1)) - .as_secs_f32(); + let client_fps = + 1.0 / Duration::max(client_stats.frame_interval, EPS_INTERVAL).as_secs_f32(); + let server_fps = + 1.0 / Duration::max(self.last_frame_present_interval, EPS_INTERVAL).as_secs_f32(); if self.last_full_report_instant + FULL_REPORT_INTERVAL < Instant::now() { self.last_full_report_instant += FULL_REPORT_INTERVAL; @@ -271,13 +266,11 @@ impl StatisticsManager { self.packets_lost_partial_sum = 0; } - // While not accurate, this prevents NaNs and zeros that would cause a crash or pollute - // the graph - let bitrate_bps = if network_latency != Duration::ZERO { - frame.video_packet_bytes as f32 * 8.0 / network_latency.as_secs_f32() - } else { - 0.0 - }; + let packet_bits = frame.video_packet_bytes as f32 * 8.0; + let throughput_bps = + packet_bits / Duration::max(network_latency, EPS_INTERVAL).as_secs_f32(); + let bitrate_bps = packet_bits + / Duration::max(self.last_frame_present_interval, EPS_INTERVAL).as_secs_f32(); // todo: use target timestamp in nanoseconds. the dashboard needs to use the first // timestamp as the graph time origin. @@ -293,8 +286,9 @@ impl StatisticsManager { vsync_queue_s: client_stats.vsync_queue.as_secs_f32(), client_fps, server_fps, - nominal_bitrate: self.last_nominal_bitrate_stats.clone(), - actual_bitrate_bps: bitrate_bps, + bitrate_directives: self.last_throughput_directives.clone(), + throughput_bps, + bitrate_bps, })); (network_latency, game_time_latency) diff --git a/alvr/server_openvr/src/lib.rs b/alvr/server_openvr/src/lib.rs index 1ca8bc70a9..8894261943 100644 --- a/alvr/server_openvr/src/lib.rs +++ b/alvr/server_openvr/src/lib.rs @@ -349,7 +349,7 @@ extern "C" fn get_dynamic_encoder_params() -> FfiDynamicEncoderParams { if let Some(params) = context.get_dynamic_encoder_params() { FfiDynamicEncoderParams { updated: 1, - bitrate_bps: params.bitrate_bps, + bitrate_bps: params.bitrate_bps as u64, framerate: params.framerate, } } else { diff --git a/alvr/session/src/settings.rs b/alvr/session/src/settings.rs index 41b231c7e8..2c124d6fa0 100644 --- a/alvr/session/src/settings.rs +++ b/alvr/session/src/settings.rs @@ -339,12 +339,12 @@ pub enum BitrateMode { #[schema(strings(display_name = "Maximum bitrate"))] #[schema(flag = "real-time")] #[schema(gui(slider(min = 1, max = 1000, logarithmic)), suffix = "Mbps")] - max_bitrate_mbps: Switch, + max_throughput_mbps: Switch, #[schema(strings(display_name = "Minimum bitrate"))] #[schema(flag = "real-time")] #[schema(gui(slider(min = 1, max = 100, logarithmic)), suffix = "Mbps")] - min_bitrate_mbps: Switch, + min_throughput_mbps: Switch, #[schema(strings(display_name = "Maximum network latency"))] #[schema(flag = "real-time")] @@ -1278,11 +1278,11 @@ pub fn session_settings_default() -> SettingsDefault { Adaptive: BitrateModeAdaptiveDefault { gui_collapsed: true, saturation_multiplier: 0.95, - max_bitrate_mbps: SwitchDefault { + max_throughput_mbps: SwitchDefault { enabled: false, content: 100, }, - min_bitrate_mbps: SwitchDefault { + min_throughput_mbps: SwitchDefault { enabled: false, content: 5, },