mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-22 20:51:17 +03:00
opt qos (#10459)
* Adjust bitrate and fps based on TestDelay messages. * Bitrate is adjusted every 3 seconds, fps is adjusted every second and when receiving test lag. * Latency optimized at high resolutions. However, when the network is poor, the delay when just connecting or sliding static pages is still obvious. Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
@@ -228,7 +228,6 @@ pub struct Connection {
|
||||
#[cfg(target_os = "linux")]
|
||||
linux_headless_handle: LinuxHeadlessHandle,
|
||||
closed: bool,
|
||||
delay_response_instant: Instant,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
start_cm_ipc_para: Option<StartCmIpcPara>,
|
||||
auto_disconnect_timer: Option<(Instant, u64)>,
|
||||
@@ -376,7 +375,6 @@ impl Connection {
|
||||
#[cfg(target_os = "linux")]
|
||||
linux_headless_handle,
|
||||
closed: false,
|
||||
delay_response_instant: Instant::now(),
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
start_cm_ipc_para: Some(StartCmIpcPara {
|
||||
rx_to_cm,
|
||||
@@ -736,7 +734,11 @@ impl Connection {
|
||||
});
|
||||
conn.send(msg_out.into()).await;
|
||||
}
|
||||
video_service::VIDEO_QOS.lock().unwrap().user_delay_response_elapsed(conn.inner.id(), conn.delay_response_instant.elapsed().as_millis());
|
||||
if conn.is_authed_remote_conn() {
|
||||
if let Some(last_test_delay) = conn.last_test_delay {
|
||||
video_service::VIDEO_QOS.lock().unwrap().user_delay_response_elapsed(id, last_test_delay.elapsed().as_millis());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1877,7 +1879,6 @@ impl Connection {
|
||||
.user_network_delay(self.inner.id(), new_delay);
|
||||
self.network_delay = new_delay;
|
||||
}
|
||||
self.delay_response_instant = Instant::now();
|
||||
}
|
||||
} else if let Some(message::Union::SwitchSidesResponse(_s)) = msg.union {
|
||||
#[cfg(feature = "flutter")]
|
||||
@@ -3322,6 +3323,13 @@ impl Connection {
|
||||
session_id: self.lr.session_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_authed_remote_conn(&self) -> bool {
|
||||
if let Some(id) = self.authed_conn_id.as_ref() {
|
||||
return id.conn_type() == AuthConnType::Remote;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
@@ -3809,10 +3817,6 @@ mod raii {
|
||||
fn drop(&mut self) {
|
||||
let mut active_conns_lock = ALIVE_CONNS.lock().unwrap();
|
||||
active_conns_lock.retain(|&c| c != self.0);
|
||||
video_service::VIDEO_QOS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.on_connection_close(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3830,6 +3834,12 @@ mod raii {
|
||||
_ONCE.call_once(|| {
|
||||
shutdown_hooks::add_shutdown_hook(connection_shutdown_hook);
|
||||
});
|
||||
if conn_type == AuthConnType::Remote {
|
||||
video_service::VIDEO_QOS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.on_connection_open(conn_id);
|
||||
}
|
||||
Self(conn_id, conn_type)
|
||||
}
|
||||
|
||||
@@ -3927,12 +3937,20 @@ mod raii {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn conn_type(&self) -> AuthConnType {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AuthedConnID {
|
||||
fn drop(&mut self) {
|
||||
if self.1 == AuthConnType::Remote {
|
||||
scrap::codec::Encoder::update(scrap::codec::EncodingUpdate::Remove(self.0));
|
||||
video_service::VIDEO_QOS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.on_connection_close(self.0);
|
||||
}
|
||||
AUTHED_CONNS.lock().unwrap().retain(|c| c.0 != self.0);
|
||||
let remote_count = AUTHED_CONNS
|
||||
|
||||
@@ -1,287 +1,222 @@
|
||||
use super::*;
|
||||
use scrap::codec::Quality;
|
||||
use std::time::Duration;
|
||||
use scrap::codec::{Quality, BR_BALANCED, BR_BEST, BR_SPEED};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
/*
|
||||
FPS adjust:
|
||||
a. new user connected =>set to INIT_FPS
|
||||
b. TestDelay receive => update user's fps according to network delay
|
||||
When network delay < DELAY_THRESHOLD_150MS, set minimum fps according to image quality, and increase fps;
|
||||
When network delay >= DELAY_THRESHOLD_150MS, set minimum fps according to image quality, and decrease fps;
|
||||
c. second timeout / TestDelay receive => update real fps to the minimum fps from all users
|
||||
|
||||
ratio adjust:
|
||||
a. user set image quality => update to the maximum ratio of the latest quality
|
||||
b. 3 seconds timeout => update ratio according to network delay
|
||||
When network delay < DELAY_THRESHOLD_150MS, increase ratio, max 150kbps;
|
||||
When network delay >= DELAY_THRESHOLD_150MS, decrease ratio;
|
||||
|
||||
adjust betwen FPS and ratio:
|
||||
When network delay < DELAY_THRESHOLD_150MS, fps is always higher than the minimum fps, and ratio is increasing;
|
||||
When network delay >= DELAY_THRESHOLD_150MS, fps is always lower than the minimum fps, and ratio is decreasing;
|
||||
|
||||
delay:
|
||||
use delay minus RTT as the actual network delay
|
||||
*/
|
||||
|
||||
// Constants
|
||||
pub const FPS: u32 = 30;
|
||||
pub const MIN_FPS: u32 = 1;
|
||||
pub const MAX_FPS: u32 = 120;
|
||||
trait Percent {
|
||||
fn as_percent(&self) -> u32;
|
||||
pub const INIT_FPS: u32 = 15;
|
||||
|
||||
// Bitrate ratio constants for different quality levels
|
||||
const BR_MAX: f32 = 40.0; // 2000 * 2 / 100
|
||||
const BR_MIN: f32 = 0.2;
|
||||
const BR_MIN_HIGH_RESOLUTION: f32 = 0.1; // For high resolution, BR_MIN is still too high, so we set a lower limit
|
||||
const MAX_BR_MULTIPLE: f32 = 1.0;
|
||||
|
||||
const HISTORY_DELAY_LEN: usize = 2;
|
||||
const ADJUST_RATIO_INTERVAL: usize = 3; // Adjust quality ratio every 3 seconds
|
||||
const DYNAMIC_SCREEN_THRESHOLD: usize = 2; // Allow increase quality ratio if encode more than 2 times in one second
|
||||
const DELAY_THRESHOLD_150MS: u32 = 150; // 150ms is the threshold for good network condition
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct UserDelay {
|
||||
response_delayed: bool,
|
||||
delay_history: VecDeque<u32>,
|
||||
fps: Option<u32>,
|
||||
rtt_calculator: RttCalculator,
|
||||
quick_increase_fps_count: usize,
|
||||
increase_fps_count: usize,
|
||||
}
|
||||
|
||||
impl Percent for ImageQuality {
|
||||
fn as_percent(&self) -> u32 {
|
||||
match self {
|
||||
ImageQuality::NotSet => 0,
|
||||
ImageQuality::Low => 50,
|
||||
ImageQuality::Balanced => 66,
|
||||
ImageQuality::Best => 100,
|
||||
impl UserDelay {
|
||||
fn add_delay(&mut self, delay: u32) {
|
||||
self.rtt_calculator.update(delay);
|
||||
if self.delay_history.len() > HISTORY_DELAY_LEN {
|
||||
self.delay_history.pop_front();
|
||||
}
|
||||
self.delay_history.push_back(delay);
|
||||
}
|
||||
|
||||
// Average delay minus RTT
|
||||
fn avg_delay(&self) -> u32 {
|
||||
let len = self.delay_history.len();
|
||||
if len > 0 {
|
||||
let avg_delay = self.delay_history.iter().sum::<u32>() / len as u32;
|
||||
|
||||
// If RTT is available, subtract it from average delay to get actual network latency
|
||||
if let Some(rtt) = self.rtt_calculator.get_rtt() {
|
||||
if avg_delay > rtt {
|
||||
avg_delay - rtt
|
||||
} else {
|
||||
avg_delay
|
||||
}
|
||||
} else {
|
||||
avg_delay
|
||||
}
|
||||
} else {
|
||||
DELAY_THRESHOLD_150MS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
struct Delay {
|
||||
state: DelayState,
|
||||
staging_state: DelayState,
|
||||
delay: u32,
|
||||
counter: u32,
|
||||
slower_than_old_state: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
// User session data structure
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct UserData {
|
||||
auto_adjust_fps: Option<u32>, // reserve for compatibility
|
||||
custom_fps: Option<u32>,
|
||||
quality: Option<(i64, Quality)>, // (time, quality)
|
||||
delay: Option<Delay>,
|
||||
response_delayed: bool,
|
||||
delay: UserDelay,
|
||||
record: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct DisplayData {
|
||||
send_counter: usize, // Number of times encode during period
|
||||
support_changing_quality: bool,
|
||||
}
|
||||
|
||||
// Main QoS controller structure
|
||||
pub struct VideoQoS {
|
||||
fps: u32,
|
||||
quality: Quality,
|
||||
ratio: f32,
|
||||
users: HashMap<i32, UserData>,
|
||||
displays: HashMap<usize, DisplayData>,
|
||||
bitrate_store: u32,
|
||||
support_abr: HashMap<usize, bool>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
enum DelayState {
|
||||
Normal = 0,
|
||||
LowDelay = 200,
|
||||
HighDelay = 500,
|
||||
Broken = 1000,
|
||||
}
|
||||
|
||||
impl Default for DelayState {
|
||||
fn default() -> Self {
|
||||
DelayState::Normal
|
||||
}
|
||||
}
|
||||
|
||||
impl DelayState {
|
||||
fn from_delay(delay: u32) -> Self {
|
||||
if delay > DelayState::Broken as u32 {
|
||||
DelayState::Broken
|
||||
} else if delay > DelayState::HighDelay as u32 {
|
||||
DelayState::HighDelay
|
||||
} else if delay > DelayState::LowDelay as u32 {
|
||||
DelayState::LowDelay
|
||||
} else {
|
||||
DelayState::Normal
|
||||
}
|
||||
}
|
||||
adjust_ratio_instant: Instant,
|
||||
abr_config: bool,
|
||||
new_user_instant: Instant,
|
||||
}
|
||||
|
||||
impl Default for VideoQoS {
|
||||
fn default() -> Self {
|
||||
VideoQoS {
|
||||
fps: FPS,
|
||||
quality: Default::default(),
|
||||
ratio: 1.0,
|
||||
users: Default::default(),
|
||||
displays: Default::default(),
|
||||
bitrate_store: 0,
|
||||
support_abr: Default::default(),
|
||||
adjust_ratio_instant: Instant::now(),
|
||||
abr_config: true,
|
||||
new_user_instant: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum RefreshType {
|
||||
SetImageQuality,
|
||||
}
|
||||
|
||||
// Basic functionality
|
||||
impl VideoQoS {
|
||||
// Calculate seconds per frame based on current FPS
|
||||
pub fn spf(&self) -> Duration {
|
||||
Duration::from_secs_f32(1. / (self.fps() as f32))
|
||||
}
|
||||
|
||||
// Get current FPS within valid range
|
||||
pub fn fps(&self) -> u32 {
|
||||
if self.fps >= MIN_FPS && self.fps <= MAX_FPS {
|
||||
self.fps
|
||||
let fps = self.fps;
|
||||
if fps >= MIN_FPS && fps <= MAX_FPS {
|
||||
fps
|
||||
} else {
|
||||
FPS
|
||||
}
|
||||
}
|
||||
|
||||
// Store bitrate for later use
|
||||
pub fn store_bitrate(&mut self, bitrate: u32) {
|
||||
self.bitrate_store = bitrate;
|
||||
}
|
||||
|
||||
// Get stored bitrate
|
||||
pub fn bitrate(&self) -> u32 {
|
||||
self.bitrate_store
|
||||
}
|
||||
|
||||
pub fn quality(&self) -> Quality {
|
||||
self.quality
|
||||
// Get current bitrate ratio with bounds checking
|
||||
pub fn ratio(&mut self) -> f32 {
|
||||
if self.ratio < BR_MIN_HIGH_RESOLUTION || self.ratio > BR_MAX {
|
||||
self.ratio = BR_BALANCED;
|
||||
}
|
||||
self.ratio
|
||||
}
|
||||
|
||||
// Check if any user is in recording mode
|
||||
pub fn record(&self) -> bool {
|
||||
self.users.iter().any(|u| u.1.record)
|
||||
}
|
||||
|
||||
pub fn set_support_abr(&mut self, display_idx: usize, support: bool) {
|
||||
self.support_abr.insert(display_idx, support);
|
||||
pub fn set_support_changing_quality(&mut self, display_idx: usize, support: bool) {
|
||||
if let Some(display) = self.displays.get_mut(&display_idx) {
|
||||
display.support_changing_quality = support;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if variable bitrate encoding is supported and enabled
|
||||
pub fn in_vbr_state(&self) -> bool {
|
||||
Config::get_option("enable-abr") != "N" && self.support_abr.iter().all(|e| *e.1)
|
||||
self.abr_config && self.displays.iter().all(|e| e.1.support_changing_quality)
|
||||
}
|
||||
}
|
||||
|
||||
// User session management
|
||||
impl VideoQoS {
|
||||
// Initialize new user session
|
||||
pub fn on_connection_open(&mut self, id: i32) {
|
||||
self.users.insert(id, UserData::default());
|
||||
self.abr_config = Config::get_option("enable-abr") != "N";
|
||||
self.new_user_instant = Instant::now();
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self, typ: Option<RefreshType>) {
|
||||
// fps
|
||||
let user_fps = |u: &UserData| {
|
||||
// custom_fps
|
||||
let mut fps = u.custom_fps.unwrap_or(FPS);
|
||||
// auto adjust fps
|
||||
if let Some(auto_adjust_fps) = u.auto_adjust_fps {
|
||||
if fps == 0 || auto_adjust_fps < fps {
|
||||
fps = auto_adjust_fps;
|
||||
}
|
||||
}
|
||||
// delay
|
||||
if let Some(delay) = u.delay {
|
||||
fps = match delay.state {
|
||||
DelayState::Normal => fps,
|
||||
DelayState::LowDelay => fps * 3 / 4,
|
||||
DelayState::HighDelay => fps / 2,
|
||||
DelayState::Broken => fps / 4,
|
||||
}
|
||||
}
|
||||
// delay response
|
||||
if u.response_delayed {
|
||||
if fps > MIN_FPS + 2 {
|
||||
fps = MIN_FPS + 2;
|
||||
}
|
||||
}
|
||||
return fps;
|
||||
};
|
||||
let mut fps = self
|
||||
.users
|
||||
.iter()
|
||||
.map(|(_, u)| user_fps(u))
|
||||
.filter(|u| *u >= MIN_FPS)
|
||||
.min()
|
||||
.unwrap_or(FPS);
|
||||
if fps > MAX_FPS {
|
||||
fps = MAX_FPS;
|
||||
// Clean up user session
|
||||
pub fn on_connection_close(&mut self, id: i32) {
|
||||
self.users.remove(&id);
|
||||
if self.users.is_empty() {
|
||||
*self = Default::default();
|
||||
}
|
||||
self.fps = fps;
|
||||
|
||||
// quality
|
||||
// latest image quality
|
||||
let latest_quality = self
|
||||
.users
|
||||
.iter()
|
||||
.map(|(_, u)| u.quality)
|
||||
.filter(|q| *q != None)
|
||||
.max_by(|a, b| a.unwrap_or_default().0.cmp(&b.unwrap_or_default().0))
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default()
|
||||
.1;
|
||||
let mut quality = latest_quality;
|
||||
|
||||
// network delay
|
||||
let abr_enabled = self.in_vbr_state();
|
||||
if abr_enabled && typ != Some(RefreshType::SetImageQuality) {
|
||||
// max delay
|
||||
let delay = self
|
||||
.users
|
||||
.iter()
|
||||
.map(|u| u.1.delay)
|
||||
.filter(|d| d.is_some())
|
||||
.max_by(|a, b| {
|
||||
(a.unwrap_or_default().state as u32).cmp(&(b.unwrap_or_default().state as u32))
|
||||
});
|
||||
let delay = delay.unwrap_or_default().unwrap_or_default().state;
|
||||
if delay != DelayState::Normal {
|
||||
match self.quality {
|
||||
Quality::Best => {
|
||||
quality = if delay == DelayState::Broken {
|
||||
Quality::Low
|
||||
} else {
|
||||
Quality::Balanced
|
||||
};
|
||||
}
|
||||
Quality::Balanced => {
|
||||
quality = Quality::Low;
|
||||
}
|
||||
Quality::Low => {
|
||||
quality = Quality::Low;
|
||||
}
|
||||
Quality::Custom(b) => match delay {
|
||||
DelayState::LowDelay => {
|
||||
quality =
|
||||
Quality::Custom(if b >= 150 { 100 } else { std::cmp::min(50, b) });
|
||||
}
|
||||
DelayState::HighDelay => {
|
||||
quality =
|
||||
Quality::Custom(if b >= 100 { 50 } else { std::cmp::min(25, b) });
|
||||
}
|
||||
DelayState::Broken => {
|
||||
quality =
|
||||
Quality::Custom(if b >= 50 { 25 } else { std::cmp::min(10, b) });
|
||||
}
|
||||
DelayState::Normal => {}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
match self.quality {
|
||||
Quality::Low => {
|
||||
if latest_quality == Quality::Best {
|
||||
quality = Quality::Balanced;
|
||||
}
|
||||
}
|
||||
Quality::Custom(current_b) => {
|
||||
if let Quality::Custom(latest_b) = latest_quality {
|
||||
if current_b < latest_b / 2 {
|
||||
quality = Quality::Custom(latest_b / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.quality = quality;
|
||||
}
|
||||
|
||||
pub fn user_custom_fps(&mut self, id: i32, fps: u32) {
|
||||
if fps < MIN_FPS {
|
||||
if fps < MIN_FPS || fps > MAX_FPS {
|
||||
return;
|
||||
}
|
||||
if let Some(user) = self.users.get_mut(&id) {
|
||||
user.custom_fps = Some(fps);
|
||||
} else {
|
||||
self.users.insert(
|
||||
id,
|
||||
UserData {
|
||||
custom_fps: Some(fps),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
self.refresh(None);
|
||||
}
|
||||
|
||||
pub fn user_auto_adjust_fps(&mut self, id: i32, fps: u32) {
|
||||
if fps < MIN_FPS || fps > MAX_FPS {
|
||||
return;
|
||||
}
|
||||
if let Some(user) = self.users.get_mut(&id) {
|
||||
user.auto_adjust_fps = Some(fps);
|
||||
} else {
|
||||
self.users.insert(
|
||||
id,
|
||||
UserData {
|
||||
auto_adjust_fps: Some(fps),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
self.refresh(None);
|
||||
}
|
||||
|
||||
pub fn user_image_quality(&mut self, id: i32, image_quality: i32) {
|
||||
// https://github.com/rustdesk/rustdesk/blob/d716e2b40c38737f1aa3f16de0dec67394a6ac68/src/server/video_service.rs#L493
|
||||
let convert_quality = |q: i32| {
|
||||
let convert_quality = |q: i32| -> Quality {
|
||||
if q == ImageQuality::Balanced.value() {
|
||||
Quality::Balanced
|
||||
} else if q == ImageQuality::Low.value() {
|
||||
@@ -289,92 +224,16 @@ impl VideoQoS {
|
||||
} else if q == ImageQuality::Best.value() {
|
||||
Quality::Best
|
||||
} else {
|
||||
let mut b = (q >> 8 & 0xFFF) * 2;
|
||||
b = std::cmp::max(b, 20);
|
||||
b = std::cmp::min(b, 8000);
|
||||
Quality::Custom(b as u32)
|
||||
let b = ((q >> 8 & 0xFFF) * 2) as f32 / 100.0;
|
||||
Quality::Custom(b.clamp(BR_MIN, BR_MAX))
|
||||
}
|
||||
};
|
||||
|
||||
let quality = Some((hbb_common::get_time(), convert_quality(image_quality)));
|
||||
if let Some(user) = self.users.get_mut(&id) {
|
||||
user.quality = quality;
|
||||
} else {
|
||||
self.users.insert(
|
||||
id,
|
||||
UserData {
|
||||
quality,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
self.refresh(Some(RefreshType::SetImageQuality));
|
||||
}
|
||||
|
||||
pub fn user_network_delay(&mut self, id: i32, delay: u32) {
|
||||
let state = DelayState::from_delay(delay);
|
||||
let debounce = 3;
|
||||
if let Some(user) = self.users.get_mut(&id) {
|
||||
if let Some(d) = &mut user.delay {
|
||||
d.delay = (delay + d.delay) / 2;
|
||||
let new_state = DelayState::from_delay(d.delay);
|
||||
let slower_than_old_state = new_state as i32 - d.staging_state as i32;
|
||||
let slower_than_old_state = if slower_than_old_state > 0 {
|
||||
Some(true)
|
||||
} else if slower_than_old_state < 0 {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if d.slower_than_old_state == slower_than_old_state {
|
||||
let old_counter = d.counter;
|
||||
d.counter += delay / 1000 + 1;
|
||||
if old_counter < debounce && d.counter >= debounce {
|
||||
d.counter = 0;
|
||||
d.state = d.staging_state;
|
||||
d.staging_state = new_state;
|
||||
}
|
||||
if d.counter % debounce == 0 {
|
||||
self.refresh(None);
|
||||
}
|
||||
} else {
|
||||
d.counter = 0;
|
||||
d.staging_state = new_state;
|
||||
d.slower_than_old_state = slower_than_old_state;
|
||||
}
|
||||
} else {
|
||||
user.delay = Some(Delay {
|
||||
state: DelayState::Normal,
|
||||
staging_state: state,
|
||||
delay,
|
||||
counter: 0,
|
||||
slower_than_old_state: None,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self.users.insert(
|
||||
id,
|
||||
UserData {
|
||||
delay: Some(Delay {
|
||||
state: DelayState::Normal,
|
||||
staging_state: state,
|
||||
delay,
|
||||
counter: 0,
|
||||
slower_than_old_state: None,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_delay_response_elapsed(&mut self, id: i32, elapsed: u128) {
|
||||
if let Some(user) = self.users.get_mut(&id) {
|
||||
let old = user.response_delayed;
|
||||
user.response_delayed = elapsed > 3000;
|
||||
if old != user.response_delayed {
|
||||
self.refresh(None);
|
||||
}
|
||||
// update ratio directly
|
||||
self.ratio = self.latest_quality().ratio();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,8 +243,348 @@ impl VideoQoS {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_connection_close(&mut self, id: i32) {
|
||||
self.users.remove(&id);
|
||||
self.refresh(None);
|
||||
pub fn user_network_delay(&mut self, id: i32, delay: u32) {
|
||||
let highest_fps = self.highest_fps();
|
||||
let target_ratio = self.latest_quality().ratio();
|
||||
|
||||
// For bad network, small fps means quick reaction and high quality
|
||||
let (min_fps, normal_fps) = if target_ratio >= BR_BEST {
|
||||
(8, 16)
|
||||
} else if target_ratio >= BR_BALANCED {
|
||||
(10, 20)
|
||||
} else {
|
||||
(12, 24)
|
||||
};
|
||||
|
||||
// Calculate minimum acceptable delay-fps product
|
||||
let dividend_ms = DELAY_THRESHOLD_150MS * min_fps;
|
||||
|
||||
let mut adjust_ratio = false;
|
||||
if let Some(user) = self.users.get_mut(&id) {
|
||||
let delay = delay.max(10);
|
||||
let old_avg_delay = user.delay.avg_delay();
|
||||
user.delay.add_delay(delay);
|
||||
let mut avg_delay = user.delay.avg_delay();
|
||||
avg_delay = avg_delay.max(10);
|
||||
let mut fps = self.fps;
|
||||
|
||||
// Adaptive FPS adjustment based on network delay:
|
||||
if avg_delay < 50 {
|
||||
user.delay.quick_increase_fps_count += 1;
|
||||
let mut step = if fps < normal_fps { 1 } else { 0 };
|
||||
if user.delay.quick_increase_fps_count >= 3 {
|
||||
// After 3 consecutive good samples, increase more aggressively
|
||||
user.delay.quick_increase_fps_count = 0;
|
||||
step = 5;
|
||||
}
|
||||
fps = min_fps.max(fps + step);
|
||||
} else if avg_delay < 100 {
|
||||
let step = if avg_delay < old_avg_delay {
|
||||
if fps < normal_fps {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
fps = min_fps.max(fps + step);
|
||||
} else if avg_delay < DELAY_THRESHOLD_150MS {
|
||||
fps = min_fps.max(fps);
|
||||
} else {
|
||||
let devide_fps = ((fps as f32) / (avg_delay as f32 / DELAY_THRESHOLD_150MS as f32))
|
||||
.ceil() as u32;
|
||||
if avg_delay < 200 {
|
||||
fps = min_fps.max(devide_fps);
|
||||
} else if avg_delay < 300 {
|
||||
fps = min_fps.min(devide_fps);
|
||||
} else if avg_delay < 600 {
|
||||
fps = dividend_ms / avg_delay;
|
||||
} else {
|
||||
fps = (dividend_ms / avg_delay).min(devide_fps);
|
||||
}
|
||||
}
|
||||
|
||||
if avg_delay < DELAY_THRESHOLD_150MS {
|
||||
user.delay.increase_fps_count += 1;
|
||||
} else {
|
||||
user.delay.increase_fps_count = 0;
|
||||
}
|
||||
if user.delay.increase_fps_count >= 3 {
|
||||
// After 3 stable samples, try increasing FPS
|
||||
user.delay.increase_fps_count = 0;
|
||||
fps += 1;
|
||||
}
|
||||
|
||||
// Reset quick increase counter if network condition worsens
|
||||
if avg_delay > 50 {
|
||||
user.delay.quick_increase_fps_count = 0;
|
||||
}
|
||||
|
||||
fps = fps.clamp(MIN_FPS, highest_fps);
|
||||
// first network delay message
|
||||
adjust_ratio = user.delay.fps.is_none();
|
||||
user.delay.fps = Some(fps);
|
||||
}
|
||||
self.adjust_fps();
|
||||
if adjust_ratio {
|
||||
self.adjust_ratio(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_delay_response_elapsed(&mut self, id: i32, elapsed: u128) {
|
||||
if let Some(user) = self.users.get_mut(&id) {
|
||||
user.delay.response_delayed = elapsed > 2000;
|
||||
if user.delay.response_delayed {
|
||||
user.delay.add_delay(elapsed as u32);
|
||||
self.adjust_fps();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common adjust functions
|
||||
impl VideoQoS {
|
||||
pub fn new_display(&mut self, display_idx: usize) {
|
||||
self.displays.insert(display_idx, DisplayData::default());
|
||||
}
|
||||
|
||||
pub fn remove_display(&mut self, display_idx: usize) {
|
||||
self.displays.remove(&display_idx);
|
||||
}
|
||||
|
||||
pub fn update_display_data(&mut self, display_idx: usize, send_counter: usize) {
|
||||
if let Some(display) = self.displays.get_mut(&display_idx) {
|
||||
display.send_counter += send_counter;
|
||||
}
|
||||
self.adjust_fps();
|
||||
let abr_enabled = self.in_vbr_state();
|
||||
if abr_enabled {
|
||||
if self.adjust_ratio_instant.elapsed().as_secs() >= ADJUST_RATIO_INTERVAL as u64 {
|
||||
let dynamic_screen = self
|
||||
.displays
|
||||
.iter()
|
||||
.any(|d| d.1.send_counter >= ADJUST_RATIO_INTERVAL * DYNAMIC_SCREEN_THRESHOLD);
|
||||
self.displays.iter_mut().for_each(|d| {
|
||||
d.1.send_counter = 0;
|
||||
});
|
||||
self.adjust_ratio(dynamic_screen);
|
||||
}
|
||||
} else {
|
||||
self.ratio = self.latest_quality().ratio();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn highest_fps(&self) -> u32 {
|
||||
let user_fps = |u: &UserData| {
|
||||
let mut fps = u.custom_fps.unwrap_or(FPS);
|
||||
if let Some(auto_adjust_fps) = u.auto_adjust_fps {
|
||||
if fps == 0 || auto_adjust_fps < fps {
|
||||
fps = auto_adjust_fps;
|
||||
}
|
||||
}
|
||||
fps
|
||||
};
|
||||
|
||||
let fps = self
|
||||
.users
|
||||
.iter()
|
||||
.map(|(_, u)| user_fps(u))
|
||||
.filter(|u| *u >= MIN_FPS)
|
||||
.min()
|
||||
.unwrap_or(FPS);
|
||||
|
||||
fps.clamp(MIN_FPS, MAX_FPS)
|
||||
}
|
||||
|
||||
// Get latest quality settings from all users
|
||||
pub fn latest_quality(&self) -> Quality {
|
||||
self.users
|
||||
.iter()
|
||||
.map(|(_, u)| u.quality)
|
||||
.filter(|q| *q != None)
|
||||
.max_by(|a, b| a.unwrap_or_default().0.cmp(&b.unwrap_or_default().0))
|
||||
.flatten()
|
||||
.unwrap_or((0, Quality::Balanced))
|
||||
.1
|
||||
}
|
||||
|
||||
// Adjust quality ratio based on network delay and screen changes
|
||||
fn adjust_ratio(&mut self, dynamic_screen: bool) {
|
||||
// Get maximum delay from all users
|
||||
let max_delay = self.users.iter().map(|u| u.1.delay.avg_delay()).max();
|
||||
let Some(max_delay) = max_delay else {
|
||||
return;
|
||||
};
|
||||
|
||||
let target_quality = self.latest_quality();
|
||||
let target_ratio = self.latest_quality().ratio();
|
||||
let current_ratio = self.ratio;
|
||||
let current_bitrate = self.bitrate();
|
||||
|
||||
// Calculate minimum ratio for high resolution (1Mbps baseline)
|
||||
let ratio_1mbps = if current_bitrate > 0 {
|
||||
Some((current_ratio * 1000.0 / current_bitrate as f32).max(BR_MIN_HIGH_RESOLUTION))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Calculate ratio for adding 150kbps bandwidth
|
||||
let ratio_add_150kbps = if current_bitrate > 0 {
|
||||
Some((current_bitrate + 150) as f32 * current_ratio / current_bitrate as f32)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Set minimum ratio based on quality mode
|
||||
let min = match target_quality {
|
||||
Quality::Best => {
|
||||
// For Best quality, ensure minimum 1Mbps for high resolution
|
||||
let mut min = BR_BEST / 2.5;
|
||||
if let Some(ratio_1mbps) = ratio_1mbps {
|
||||
if min > ratio_1mbps {
|
||||
min = ratio_1mbps;
|
||||
}
|
||||
}
|
||||
min.max(BR_MIN)
|
||||
}
|
||||
Quality::Balanced => {
|
||||
let mut min = (BR_BALANCED / 2.0).min(0.4);
|
||||
if let Some(ratio_1mbps) = ratio_1mbps {
|
||||
if min > ratio_1mbps {
|
||||
min = ratio_1mbps;
|
||||
}
|
||||
}
|
||||
min.max(BR_MIN_HIGH_RESOLUTION)
|
||||
}
|
||||
Quality::Low => BR_MIN_HIGH_RESOLUTION,
|
||||
Quality::Custom(_) => BR_MIN_HIGH_RESOLUTION,
|
||||
};
|
||||
let max = target_ratio * MAX_BR_MULTIPLE;
|
||||
|
||||
let mut v = current_ratio;
|
||||
|
||||
// Adjust ratio based on network delay thresholds
|
||||
if max_delay < 50 {
|
||||
if dynamic_screen {
|
||||
v = current_ratio * 1.15;
|
||||
}
|
||||
} else if max_delay < 100 {
|
||||
if dynamic_screen {
|
||||
v = current_ratio * 1.1;
|
||||
}
|
||||
} else if max_delay < DELAY_THRESHOLD_150MS {
|
||||
if dynamic_screen {
|
||||
v = current_ratio * 1.05;
|
||||
}
|
||||
} else if max_delay < 200 {
|
||||
v = current_ratio * 0.95;
|
||||
} else if max_delay < 300 {
|
||||
v = current_ratio * 0.9;
|
||||
} else if max_delay < 500 {
|
||||
v = current_ratio * 0.85;
|
||||
} else {
|
||||
v = current_ratio * 0.8;
|
||||
}
|
||||
|
||||
// Limit quality increase rate for better stability
|
||||
if let Some(ratio_add_150kbps) = ratio_add_150kbps {
|
||||
if v > ratio_add_150kbps
|
||||
&& ratio_add_150kbps > current_ratio
|
||||
&& current_ratio >= BR_SPEED
|
||||
{
|
||||
v = ratio_add_150kbps;
|
||||
}
|
||||
}
|
||||
|
||||
self.ratio = v.clamp(min, max);
|
||||
self.adjust_ratio_instant = Instant::now();
|
||||
}
|
||||
|
||||
// Adjust fps based on network delay and user response time
|
||||
fn adjust_fps(&mut self) {
|
||||
let highest_fps = self.highest_fps();
|
||||
// Get minimum fps from all users
|
||||
let mut fps = self
|
||||
.users
|
||||
.iter()
|
||||
.map(|u| u.1.delay.fps.unwrap_or(INIT_FPS))
|
||||
.min()
|
||||
.unwrap_or(INIT_FPS);
|
||||
|
||||
if self.users.iter().any(|u| u.1.delay.response_delayed) {
|
||||
if fps > MIN_FPS + 1 {
|
||||
fps = MIN_FPS + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// For new connections (within 1 second), cap fps to INIT_FPS to ensure stability
|
||||
if self.new_user_instant.elapsed().as_secs() < 1 {
|
||||
if fps > INIT_FPS {
|
||||
fps = INIT_FPS;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure fps stays within valid range
|
||||
self.fps = fps.clamp(MIN_FPS, highest_fps);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct RttCalculator {
|
||||
min_rtt: Option<u32>, // Historical minimum RTT ever observed
|
||||
window_min_rtt: Option<u32>, // Minimum RTT within last 60 samples
|
||||
smoothed_rtt: Option<u32>, // Smoothed RTT estimation
|
||||
samples: VecDeque<u32>, // Last 60 RTT samples
|
||||
}
|
||||
|
||||
impl RttCalculator {
|
||||
const WINDOW_SAMPLES: usize = 60; // Keep last 60 samples
|
||||
const MIN_SAMPLES: usize = 10; // Require at least 10 samples
|
||||
const ALPHA: f32 = 0.5; // Smoothing factor for weighted average
|
||||
|
||||
/// Update RTT estimates with a new sample
|
||||
pub fn update(&mut self, delay: u32) {
|
||||
// 1. Update historical minimum RTT
|
||||
match self.min_rtt {
|
||||
Some(min_rtt) if delay < min_rtt => self.min_rtt = Some(delay),
|
||||
None => self.min_rtt = Some(delay),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// 2. Update sample window
|
||||
if self.samples.len() >= Self::WINDOW_SAMPLES {
|
||||
self.samples.pop_front();
|
||||
}
|
||||
self.samples.push_back(delay);
|
||||
|
||||
// 3. Calculate minimum RTT within the window
|
||||
self.window_min_rtt = self.samples.iter().min().copied();
|
||||
|
||||
// 4. Calculate smoothed RTT
|
||||
// Use weighted average if we have enough samples
|
||||
if self.samples.len() >= Self::WINDOW_SAMPLES {
|
||||
if let (Some(min), Some(window_min)) = (self.min_rtt, self.window_min_rtt) {
|
||||
// Weighted average of historical minimum and window minimum
|
||||
let new_srtt =
|
||||
((1.0 - Self::ALPHA) * min as f32 + Self::ALPHA * window_min as f32) as u32;
|
||||
self.smoothed_rtt = Some(new_srtt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current RTT estimate
|
||||
/// Returns None if no valid estimation is available
|
||||
pub fn get_rtt(&self) -> Option<u32> {
|
||||
if let Some(rtt) = self.smoothed_rtt {
|
||||
return Some(rtt);
|
||||
}
|
||||
if self.samples.len() >= Self::MIN_SAMPLES {
|
||||
if let Some(rtt) = self.min_rtt {
|
||||
return Some(rtt);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ use scrap::vram::{VRamEncoder, VRamEncoderConfig};
|
||||
use scrap::Capturer;
|
||||
use scrap::{
|
||||
aom::AomEncoderConfig,
|
||||
codec::{Encoder, EncoderCfg, Quality},
|
||||
codec::{Encoder, EncoderCfg},
|
||||
record::{Recorder, RecorderContext},
|
||||
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
|
||||
CodecFormat, Display, EncodeInput, TraitCapturer,
|
||||
@@ -413,9 +413,8 @@ fn run(vs: VideoService) -> ResultType<()> {
|
||||
c.set_gdi();
|
||||
}
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
video_qos.refresh(None);
|
||||
let mut spf;
|
||||
let mut quality = video_qos.quality();
|
||||
let mut spf = video_qos.spf();
|
||||
let mut quality = video_qos.ratio();
|
||||
let record_incoming = config::option2bool(
|
||||
"allow-auto-record-incoming",
|
||||
&Config::get_option("allow-auto-record-incoming"),
|
||||
@@ -461,7 +460,7 @@ fn run(vs: VideoService) -> ResultType<()> {
|
||||
VIDEO_QOS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_support_abr(display_idx, encoder.support_abr());
|
||||
.set_support_changing_quality(display_idx, encoder.support_changing_quality());
|
||||
log::info!("initial quality: {quality:?}");
|
||||
|
||||
if sp.is_option_true(OPTION_REFRESH) {
|
||||
@@ -489,32 +488,20 @@ fn run(vs: VideoService) -> ResultType<()> {
|
||||
let mut first_frame = true;
|
||||
let capture_width = c.width;
|
||||
let capture_height = c.height;
|
||||
let (mut second_instant, mut send_counter) = (Instant::now(), 0);
|
||||
|
||||
while sp.ok() {
|
||||
#[cfg(windows)]
|
||||
check_uac_switch(c.privacy_mode_id, c._capturer_privacy_mode_id)?;
|
||||
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
spf = video_qos.spf();
|
||||
if quality != video_qos.quality() {
|
||||
log::debug!("quality: {:?} -> {:?}", quality, video_qos.quality());
|
||||
quality = video_qos.quality();
|
||||
if encoder.support_changing_quality() {
|
||||
allow_err!(encoder.set_quality(quality));
|
||||
video_qos.store_bitrate(encoder.bitrate());
|
||||
} else {
|
||||
if !video_qos.in_vbr_state() && !quality.is_custom() {
|
||||
log::info!("switch to change quality");
|
||||
bail!("SWITCH");
|
||||
}
|
||||
}
|
||||
}
|
||||
if client_record != video_qos.record() {
|
||||
log::info!("switch due to record changed");
|
||||
bail!("SWITCH");
|
||||
}
|
||||
drop(video_qos);
|
||||
|
||||
check_qos(
|
||||
&mut encoder,
|
||||
&mut quality,
|
||||
&mut spf,
|
||||
client_record,
|
||||
&mut send_counter,
|
||||
&mut second_instant,
|
||||
display_idx,
|
||||
)?;
|
||||
if sp.is_option_true(OPTION_REFRESH) {
|
||||
let _ = try_broadcast_display_changed(&sp, display_idx, &c, true);
|
||||
log::info!("switch to refresh");
|
||||
@@ -582,6 +569,7 @@ fn run(vs: VideoService) -> ResultType<()> {
|
||||
capture_height,
|
||||
)?;
|
||||
frame_controller.set_send(now, send_conn_ids);
|
||||
send_counter += 1;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -640,6 +628,7 @@ fn run(vs: VideoService) -> ResultType<()> {
|
||||
capture_height,
|
||||
)?;
|
||||
frame_controller.set_send(now, send_conn_ids);
|
||||
send_counter += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -691,6 +680,7 @@ struct Raii(usize);
|
||||
|
||||
impl Raii {
|
||||
fn new(display_idx: usize) -> Self {
|
||||
VIDEO_QOS.lock().unwrap().new_display(display_idx);
|
||||
Raii(display_idx)
|
||||
}
|
||||
}
|
||||
@@ -701,14 +691,14 @@ impl Drop for Raii {
|
||||
VRamEncoder::set_not_use(self.0, false);
|
||||
#[cfg(feature = "vram")]
|
||||
Encoder::update(scrap::codec::EncodingUpdate::Check);
|
||||
VIDEO_QOS.lock().unwrap().set_support_abr(self.0, true);
|
||||
VIDEO_QOS.lock().unwrap().remove_display(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_encoder(
|
||||
c: &CapturerInfo,
|
||||
display_idx: usize,
|
||||
quality: Quality,
|
||||
quality: f32,
|
||||
client_record: bool,
|
||||
record_incoming: bool,
|
||||
last_portable_service_running: bool,
|
||||
@@ -737,7 +727,7 @@ fn setup_encoder(
|
||||
fn get_encoder_config(
|
||||
c: &CapturerInfo,
|
||||
_display_idx: usize,
|
||||
quality: Quality,
|
||||
quality: f32,
|
||||
record: bool,
|
||||
_portable_service: bool,
|
||||
) -> EncoderCfg {
|
||||
@@ -1061,3 +1051,40 @@ pub fn make_display_changed_msg(
|
||||
msg_out.set_misc(misc);
|
||||
Some(msg_out)
|
||||
}
|
||||
|
||||
fn check_qos(
|
||||
encoder: &mut Encoder,
|
||||
ratio: &mut f32,
|
||||
spf: &mut Duration,
|
||||
client_record: bool,
|
||||
send_counter: &mut usize,
|
||||
second_instant: &mut Instant,
|
||||
display_idx: usize,
|
||||
) -> ResultType<()> {
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
*spf = video_qos.spf();
|
||||
if *ratio != video_qos.ratio() {
|
||||
*ratio = video_qos.ratio();
|
||||
if encoder.support_changing_quality() {
|
||||
allow_err!(encoder.set_quality(*ratio));
|
||||
video_qos.store_bitrate(encoder.bitrate());
|
||||
} else {
|
||||
// Now only vaapi doesn't support changing quality
|
||||
if !video_qos.in_vbr_state() && !video_qos.latest_quality().is_custom() {
|
||||
log::info!("switch to change quality");
|
||||
bail!("SWITCH");
|
||||
}
|
||||
}
|
||||
}
|
||||
if client_record != video_qos.record() {
|
||||
log::info!("switch due to record changed");
|
||||
bail!("SWITCH");
|
||||
}
|
||||
if second_instant.elapsed() > Duration::from_secs(1) {
|
||||
*second_instant = Instant::now();
|
||||
video_qos.update_display_data(display_idx, *send_counter);
|
||||
*send_counter = 0;
|
||||
}
|
||||
drop(video_qos);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user