Increase default bitrate and optimize QoS for better image quality
- Increase BR_BALANCED from 0.67 to 1.0 for higher default bitrate
- Refactor calc_bitrate formula to be monotonically increasing
(old formula had non-monotonic region near threshold)
- Add new connection protection: don't decrease bitrate in first 6 seconds
- Add BR_MIN_AUTO (0.3) and MIN_AUTO_FPS (5) for auto adjustment limits
- Reduce RttCalculator::MIN_SAMPLES from 10 to 5 for faster stabilization
Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
parent
6306f83316
commit
8fa9e8294d
6 changed files with 62 additions and 69 deletions
|
|
@ -891,7 +891,9 @@ pub fn allow_d3d_render() -> bool {
|
|||
}
|
||||
|
||||
pub const BR_BEST: f32 = 1.5;
|
||||
pub const BR_BALANCED: f32 = 0.67;
|
||||
// Changed from 0.67 to 1.0 to increase default encoder bitrate
|
||||
// This provides better quality separation between Best/Balanced/Speed modes
|
||||
pub const BR_BALANCED: f32 = 1.0;
|
||||
pub const BR_SPEED: f32 = 0.5;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -253,30 +253,21 @@ impl HwRamEncoder {
|
|||
}
|
||||
|
||||
pub fn calc_bitrate(width: usize, height: usize, ratio: f32, h264: bool) -> u32 {
|
||||
let base = base_bitrate(width as _, height as _) as f32 * ratio;
|
||||
let threshold = 2000.0;
|
||||
let decay_rate = 0.001; // 1000 * 0.001 = 1
|
||||
let base = base_bitrate(width as _, height as _) as f32;
|
||||
// Monotonically increasing formula: factor = 1.0 + C / (1.0 + base / scale)
|
||||
// d(bitrate)/d(base) = 1 + C/(1+base/scale)² > 0, always positive
|
||||
// Hardware encoder needs higher bitrate for better fluency and quality
|
||||
let scale = 2000.0;
|
||||
let factor: f32 = if cfg!(target_os = "android") {
|
||||
// https://stackoverflow.com/questions/26110337/what-are-valid-bit-rates-to-set-for-mediacodec?rq=3
|
||||
if base > threshold {
|
||||
1.0 + 4.0 / (1.0 + (base - threshold) * decay_rate)
|
||||
} else {
|
||||
5.0
|
||||
}
|
||||
1.0 + 6.0 / (1.0 + base / scale)
|
||||
} else if h264 {
|
||||
if base > threshold {
|
||||
1.0 + 1.0 / (1.0 + (base - threshold) * decay_rate)
|
||||
} else {
|
||||
2.0
|
||||
}
|
||||
1.0 + 1.8 / (1.0 + base / scale)
|
||||
} else {
|
||||
if base > threshold {
|
||||
1.0 + 0.5 / (1.0 + (base - threshold) * decay_rate)
|
||||
} else {
|
||||
1.5
|
||||
}
|
||||
1.0 + 1.2 / (1.0 + base / scale)
|
||||
};
|
||||
(base * factor) as u32
|
||||
// ratio is multiplied at the end for linear scaling with quality settings
|
||||
(base * factor * ratio) as u32
|
||||
}
|
||||
|
||||
pub fn check_bitrate_range(_config: &HwRamEncoderConfig, bitrate: u32) -> u32 {
|
||||
|
|
|
|||
|
|
@ -1745,7 +1745,7 @@ pub struct LoginConfigHandler {
|
|||
pub save_ab_password_to_recent: bool, // true: connected with ab password
|
||||
pub other_server: Option<(String, String, String)>,
|
||||
pub custom_fps: Arc<Mutex<Option<usize>>>,
|
||||
pub last_auto_fps: Option<usize>,
|
||||
pub last_send_fps: Option<usize>,
|
||||
pub adapter_luid: Option<i64>,
|
||||
pub mark_unsupported: Vec<CodecFormat>,
|
||||
pub selected_windows_session_id: Option<u32>,
|
||||
|
|
@ -2444,6 +2444,7 @@ impl LoginConfigHandler {
|
|||
self.save_config(config);
|
||||
}
|
||||
*self.custom_fps.lock().unwrap() = Some(fps as _);
|
||||
self.last_send_fps = Some(fps as _);
|
||||
msg_out
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1173,24 +1173,24 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||
if limited_fps > custom_fps {
|
||||
limited_fps = custom_fps;
|
||||
}
|
||||
let last_auto_fps = self.handler.lc.read().unwrap().last_auto_fps.clone();
|
||||
let last_send_fps = self.handler.lc.read().unwrap().last_send_fps.clone();
|
||||
let displays = self.video_threads.keys().cloned().collect::<Vec<_>>();
|
||||
let mut fps_trending = |display: usize| {
|
||||
let thread = self.video_threads.get_mut(&display)?;
|
||||
let ctl = &mut thread.fps_control;
|
||||
let len = thread.video_queue.read().unwrap().len();
|
||||
let decode_fps = thread.decode_fps.read().unwrap().clone()?;
|
||||
let last_auto_fps = last_auto_fps.clone().unwrap_or(custom_fps as _);
|
||||
let last_send_fps = last_send_fps.clone().unwrap_or(custom_fps as _);
|
||||
if ctl.inactive_counter > inactive_threshold {
|
||||
return None;
|
||||
}
|
||||
if len > 1 && last_auto_fps > limited_fps || len > std::cmp::max(1, decode_fps / 2) {
|
||||
if len > 1 && last_send_fps > limited_fps || len > std::cmp::max(1, decode_fps / 2) {
|
||||
ctl.idle_counter = 0;
|
||||
return Some(false);
|
||||
}
|
||||
if len <= 1 {
|
||||
ctl.idle_counter += 1;
|
||||
if ctl.idle_counter > 3 && last_auto_fps + 3 <= limited_fps {
|
||||
if ctl.idle_counter > 3 && last_send_fps + 3 <= limited_fps {
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -1202,7 +1202,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||
let trendings: Vec<_> = displays.iter().map(|k| fps_trending(*k)).collect();
|
||||
let should_decrease = trendings.iter().any(|v| *v == Some(false));
|
||||
let should_increase = !should_decrease && trendings.iter().any(|v| *v == Some(true));
|
||||
if last_auto_fps.is_none() || should_decrease || should_increase {
|
||||
if last_send_fps.is_none() || should_decrease || should_increase {
|
||||
// limited_fps to ensure decoding is faster than encoding
|
||||
let mut auto_fps = limited_fps;
|
||||
if should_decrease && limited_fps < max_queue_len {
|
||||
|
|
@ -1211,7 +1211,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||
if auto_fps < 1 {
|
||||
auto_fps = 1;
|
||||
}
|
||||
if Some(auto_fps) != last_auto_fps {
|
||||
if Some(auto_fps) != last_send_fps {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(OptionMessage {
|
||||
custom_fps: auto_fps as _,
|
||||
|
|
@ -1221,7 +1221,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||
msg.set_misc(misc);
|
||||
self.sender.send(Data::Message(msg)).ok();
|
||||
log::info!("Set fps to {}", auto_fps);
|
||||
self.handler.lc.write().unwrap().last_auto_fps = Some(auto_fps);
|
||||
self.handler.lc.write().unwrap().last_send_fps = Some(auto_fps);
|
||||
}
|
||||
}
|
||||
// send refresh
|
||||
|
|
|
|||
|
|
@ -29,20 +29,21 @@ delay:
|
|||
|
||||
// Constants
|
||||
pub const FPS: u32 = 30;
|
||||
pub const MIN_FPS: u32 = 1;
|
||||
pub const MIN_FPS: u32 = 1; // Absolute minimum fps, used for final clamp
|
||||
pub const MIN_AUTO_FPS: u32 = 5; // Minimum fps for auto adjustment, prevents fps from dropping too low
|
||||
pub const MAX_FPS: u32 = 120;
|
||||
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 BR_MIN: f32 = 0.2; // Absolute minimum bitrate ratio (user can set this low)
|
||||
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
|
||||
const NEW_CONNECTION_PROTECT_SECS: u64 = RttCalculator::MIN_SAMPLES as u64 + 1; // Don't decrease bitrate within first N seconds of new connection.
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct UserDelay {
|
||||
|
|
@ -157,7 +158,7 @@ impl VideoQoS {
|
|||
|
||||
// Get current bitrate ratio with bounds checking
|
||||
pub fn ratio(&mut self) -> f32 {
|
||||
if self.ratio < BR_MIN_HIGH_RESOLUTION || self.ratio > BR_MAX {
|
||||
if self.ratio < BR_MIN || self.ratio > BR_MAX {
|
||||
self.ratio = BR_BALANCED;
|
||||
}
|
||||
self.ratio
|
||||
|
|
@ -321,7 +322,7 @@ impl VideoQoS {
|
|||
user.delay.quick_increase_fps_count = 0;
|
||||
}
|
||||
|
||||
fps = fps.clamp(MIN_FPS, highest_fps);
|
||||
fps = fps.clamp(MIN_AUTO_FPS.min(highest_fps), highest_fps);
|
||||
// first network delay message
|
||||
adjust_ratio = user.delay.fps.is_none();
|
||||
user.delay.fps = Some(fps);
|
||||
|
|
@ -428,13 +429,6 @@ impl VideoQoS {
|
|||
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)
|
||||
|
|
@ -443,29 +437,24 @@ impl VideoQoS {
|
|||
};
|
||||
|
||||
// Set minimum ratio based on quality mode
|
||||
// Best(0.6) > Balanced(0.4) > Low(0.25) >= Custom(0.2~0.6)
|
||||
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;
|
||||
}
|
||||
Quality::Best => BR_BEST / 2.5,
|
||||
Quality::Balanced => BR_BALANCED / 2.5,
|
||||
Quality::Low => BR_SPEED / 2.0,
|
||||
Quality::Custom(v) => {
|
||||
if v >= BR_BEST {
|
||||
BR_BEST / 2.5
|
||||
} else if v >= BR_BALANCED {
|
||||
BR_BALANCED / 2.5
|
||||
} else if v >= BR_SPEED {
|
||||
BR_SPEED / 2.0
|
||||
} else {
|
||||
BR_MIN
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
.max(BR_MIN);
|
||||
let max = target_ratio * MAX_BR_MULTIPLE;
|
||||
|
||||
let mut v = current_ratio;
|
||||
|
|
@ -503,7 +492,15 @@ impl VideoQoS {
|
|||
}
|
||||
}
|
||||
|
||||
self.ratio = v.clamp(min, max);
|
||||
// Protect new connections from bitrate decrease due to network fluctuation.
|
||||
// User-initiated quality changes (e.g., Best -> Balanced) will still take effect.
|
||||
let new_connection_protection =
|
||||
self.new_user_instant.elapsed().as_secs() < NEW_CONNECTION_PROTECT_SECS;
|
||||
if new_connection_protection && v < current_ratio {
|
||||
v = current_ratio;
|
||||
}
|
||||
|
||||
self.ratio = v.clamp(min.min(max), max);
|
||||
self.adjust_ratio_instant = Instant::now();
|
||||
}
|
||||
|
||||
|
|
@ -519,8 +516,9 @@ impl VideoQoS {
|
|||
.unwrap_or(INIT_FPS);
|
||||
|
||||
if self.users.iter().any(|u| u.1.delay.response_delayed) {
|
||||
if fps > MIN_FPS + 1 {
|
||||
fps = MIN_FPS + 1;
|
||||
// Use MIN_AUTO_FPS instead of MIN_FPS for response delay fallback
|
||||
if fps > MIN_AUTO_FPS + 1 {
|
||||
fps = MIN_AUTO_FPS + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -532,7 +530,7 @@ impl VideoQoS {
|
|||
}
|
||||
|
||||
// Ensure fps stays within valid range
|
||||
self.fps = fps.clamp(MIN_FPS, highest_fps);
|
||||
self.fps = fps.clamp(MIN_FPS.min(highest_fps), highest_fps);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -546,7 +544,7 @@ struct RttCalculator {
|
|||
|
||||
impl RttCalculator {
|
||||
const WINDOW_SAMPLES: usize = 60; // Keep last 60 samples
|
||||
const MIN_SAMPLES: usize = 10; // Require at least 10 samples
|
||||
const MIN_SAMPLES: usize = 5; // Require at least 5 samples
|
||||
const ALPHA: f32 = 0.5; // Smoothing factor for weighted average
|
||||
|
||||
/// Update RTT estimates with a new sample
|
||||
|
|
|
|||
|
|
@ -474,14 +474,15 @@ impl<T: InvokeUiSession> Session<T> {
|
|||
}
|
||||
|
||||
pub fn save_image_quality(&self, value: String) {
|
||||
let prev_image_quality = self.lc.read().unwrap().image_quality.clone();
|
||||
let msg = self.lc.write().unwrap().save_image_quality(value.clone());
|
||||
if let Some(msg) = msg {
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
if value != "custom" {
|
||||
let last_auto_fps = self.lc.read().unwrap().last_auto_fps;
|
||||
if last_auto_fps.unwrap_or(usize::MAX) >= 30 {
|
||||
// non custom quality use 30 fps
|
||||
// When switching from custom to non-custom, reset fps to 30 if it was different
|
||||
if prev_image_quality == "custom" && value != "custom" {
|
||||
let last_send_fps = self.lc.read().unwrap().last_send_fps;
|
||||
if last_send_fps.unwrap_or(usize::MAX) != 30 {
|
||||
let msg = self.lc.write().unwrap().set_custom_fps(30, false);
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue