From 32ad458b25e6f79b7d3ff63b7981a9560a967d51 Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 19 Oct 2022 10:19:49 +0800 Subject: [PATCH] user fps adjust Signed-off-by: 21pages --- .../lib/desktop/widgets/remote_menubar.dart | 156 ++++++++++++++---- libs/hbb_common/protos/message.proto | 1 + src/client.rs | 28 +++- src/flutter_ffi.rs | 10 ++ src/server/connection.rs | 13 +- src/server/video_qos.rs | 43 ++++- src/ui_session_interface.rs | 12 +- 7 files changed, 218 insertions(+), 45 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 72cc56cad..e670e5d80 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -742,59 +742,147 @@ class _RemoteMenubarState extends State { await bind.sessionSetImageQuality(id: widget.id, value: newValue); } + double qualityInitValue = 50; + double fpsInitValue = 30; + bool qualitySet = false; + bool fpsSet = false; + setCustomValues({double? quality, double? fps}) async { + if (quality != null) { + qualitySet = true; + await bind.sessionSetCustomImageQuality( + id: widget.id, value: quality.toInt()); + } + if (fps != null) { + fpsSet = true; + await bind.sessionSetCustomFps(id: widget.id, fps: fps.toInt()); + } + if (!qualitySet) { + qualitySet = true; + await bind.sessionSetCustomImageQuality( + id: widget.id, value: qualityInitValue.toInt()); + } + if (!fpsSet) { + fpsSet = true; + await bind.sessionSetCustomFps( + id: widget.id, fps: fpsInitValue.toInt()); + } + } + if (newValue == 'custom') { - final btnCancel = msgBoxButton(translate('Close'), () { + final btnClose = msgBoxButton(translate('Close'), () async { + await setCustomValues(); widget.ffi.dialogManager.dismissAll(); }); + + // quality final quality = await bind.sessionGetCustomImageQuality(id: widget.id); - double initValue = quality != null && quality.isNotEmpty + qualityInitValue = quality != null && quality.isNotEmpty ? quality[0].toDouble() : 50.0; - const minValue = 10.0; - const maxValue = 100.0; - if (initValue < minValue) { - initValue = minValue; + const qualityMinValue = 10.0; + const qualityMaxValue = 100.0; + if (qualityInitValue < qualityMinValue) { + qualityInitValue = qualityMinValue; } - if (initValue > maxValue) { - initValue = maxValue; + if (qualityInitValue > qualityMaxValue) { + qualityInitValue = qualityMaxValue; } - final RxDouble sliderValue = RxDouble(initValue); - final rxReplay = rxdart.ReplaySubject(); - rxReplay + final RxDouble qualitySliderValue = RxDouble(qualityInitValue); + final qualityRxReplay = rxdart.ReplaySubject(); + qualityRxReplay .throttleTime(const Duration(milliseconds: 1000), trailing: true, leading: false) .listen((double v) { () async { - await bind.sessionSetCustomImageQuality( - id: widget.id, value: v.toInt()); + await setCustomValues(quality: v); }(); }); - final slider = Obx(() { - return Slider( - value: sliderValue.value, - min: minValue, - max: maxValue, - divisions: 90, - onChanged: (double value) { - sliderValue.value = value; - rxReplay.add(value); - }, - ); + final qualitySlider = Obx(() => Row( + children: [ + Slider( + value: qualitySliderValue.value, + min: qualityMinValue, + max: qualityMaxValue, + divisions: 90, + onChanged: (double value) { + qualitySliderValue.value = value; + qualityRxReplay.add(value); + }, + ), + SizedBox( + width: 90, + child: Obx(() => Text( + '${qualitySliderValue.value.round()}% Bitrate', + style: const TextStyle(fontSize: 15), + ))) + ], + )); + // fps + final fpsOption = + await bind.sessionGetOption(id: widget.id, arg: 'custom-fps'); + fpsInitValue = + fpsOption == null ? 30 : double.tryParse(fpsOption) ?? 30; + if (fpsInitValue < 10 || fpsInitValue > 120) { + fpsInitValue = 30; + } + final RxDouble fpsSliderValue = RxDouble(fpsInitValue); + final fpsRxReplay = rxdart.ReplaySubject(); + fpsRxReplay + .throttleTime(const Duration(milliseconds: 1000), + trailing: true, leading: false) + .listen((double v) { + () async { + await setCustomValues(fps: v); + }(); }); - final content = Row( - children: [ - slider, - SizedBox( - width: 90, - child: Obx(() => Text( - '${sliderValue.value.round()}% Bitrate', + bool? direct; + try { + direct = ConnectionTypeState.find(widget.id).direct.value == + ConnectionType.strDirect; + } catch (_) {} + final fpsSlider = Offstage( + offstage: + (await bind.mainIsUsingPublicServer() && direct != true) || + (await bind.versionToNumber( + v: widget.ffi.ffiModel.pi.version) < + await bind.versionToNumber(v: '1.2.0')), + child: Row( + children: [ + Obx((() => Slider( + value: fpsSliderValue.value, + min: 10, + max: 120, + divisions: 22, + onChanged: (double value) { + fpsSliderValue.value = value; + fpsRxReplay.add(value); + }, + ))), + SizedBox( + width: 90, + child: Obx(() { + final fps = fpsSliderValue.value.round(); + String text; + if (fps < 100) { + text = '$fps FPS'; + } else { + text = '$fps FPS'; + } + return Text( + text, style: const TextStyle(fontSize: 15), - ))) - ], + ); + })) + ], + ), + ); + + final content = Column( + children: [qualitySlider, fpsSlider], ); msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality', - content, [btnCancel]); + content, [btnClose]); } }, padding: padding, diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index a48ec9d14..4bb015866 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -480,6 +480,7 @@ message OptionMessage { BoolOption disable_clipboard = 8; BoolOption enable_file_transfer = 9; VideoCodecState video_codec_state = 10; + int32 custom_fps = 11; } message TestDelay { diff --git a/src/client.rs b/src/client.rs index 6b3917790..9e2627d55 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1092,7 +1092,12 @@ impl LoginConfigHandler { n += 1; } else if q == "custom" { let config = PeerConfig::load(&self.id); - msg.custom_image_quality = config.custom_image_quality[0] << 8; + let quality = if config.custom_image_quality.is_empty() { + 50 + } else { + config.custom_image_quality[0] + }; + msg.custom_image_quality = quality << 8; n += 1; } if self.get_toggle_option("show-remote-cursor") { @@ -1253,6 +1258,27 @@ impl LoginConfigHandler { res } + /// Create a [`Message`] for saving custom fps. + /// + /// # Arguments + /// + /// * `fps` - The given fps. + pub fn set_custom_fps(&mut self, fps: i32) -> Message { + let mut misc = Misc::new(); + misc.set_option(OptionMessage { + custom_fps: fps, + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + let mut config = self.load_config(); + config + .options + .insert("custom-fps".to_owned(), fps.to_string()); + self.save_config(config); + msg_out + } + pub fn get_option(&self, k: &str) -> String { if let Some(v) = self.config.options.get(k) { v.clone() diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 873248cca..5fdb3122c 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -186,6 +186,12 @@ pub fn session_set_custom_image_quality(id: String, value: i32) { } } +pub fn session_set_custom_fps(id: String, fps: i32) { + if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { + session.set_custom_fps(fps); + } +} + pub fn session_lock_screen(id: String) { if let Some(session) = SESSIONS.read().unwrap().get(&id) { session.lock_screen(); @@ -1000,6 +1006,10 @@ pub fn query_onlines(ids: Vec) { crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines) } +pub fn version_to_number(v: String) -> i64 { + hbb_common::get_version_number(&v) +} + pub fn main_is_installed() -> SyncReturn { SyncReturn(is_installed()) } diff --git a/src/server/connection.rs b/src/server/connection.rs index 4f122b59d..c4dc615be 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1316,16 +1316,25 @@ impl Connection { if o.custom_image_quality > 0 { image_quality = o.custom_image_quality; } else { - image_quality = ImageQuality::Balanced.value(); + image_quality = -1; } } else { image_quality = q.value(); } + if image_quality > 0 { + video_service::VIDEO_QOS + .lock() + .unwrap() + .update_image_quality(image_quality); + } + } + if o.custom_fps > 0 { video_service::VIDEO_QOS .lock() .unwrap() - .update_image_quality(image_quality); + .update_user_fps(o.custom_fps as _); } + if let Ok(q) = o.lock_after_session_end.enum_value() { if q != BoolOption::NotSet { self.lock_after_session_end = q == BoolOption::Yes; diff --git a/src/server/video_qos.rs b/src/server/video_qos.rs index b0e06bc03..7aaf12d92 100644 --- a/src/server/video_qos.rs +++ b/src/server/video_qos.rs @@ -1,6 +1,8 @@ use super::*; use std::time::Duration; const FPS: u8 = 30; +const MIN_FPS: u8 = 10; +const MAX_FPS: u8 = 120; trait Percent { fn as_percent(&self) -> u32; } @@ -23,7 +25,8 @@ pub struct VideoQoS { current_image_quality: u32, enable_abr: bool, pub current_delay: u32, - pub fps: u8, // abr + pub fps: u8, // abr + pub user_fps: u8, pub target_bitrate: u32, // abr updated: bool, state: DelayState, @@ -56,6 +59,7 @@ impl Default for VideoQoS { fn default() -> Self { VideoQoS { fps: FPS, + user_fps: FPS, user_image_quality: ImageQuality::Balanced.as_percent(), current_image_quality: ImageQuality::Balanced.as_percent(), enable_abr: false, @@ -80,12 +84,19 @@ impl VideoQoS { } pub fn spf(&mut self) -> Duration { - if self.fps <= 0 { - self.fps = FPS; + if self.fps < MIN_FPS || self.fps > MAX_FPS { + self.fps = self.base_fps(); } Duration::from_secs_f32(1. / (self.fps as f32)) } + fn base_fps(&self) -> u8 { + if self.user_fps >= MIN_FPS && self.user_fps <= MAX_FPS { + return self.user_fps; + } + return FPS; + } + // update_network_delay periodically // decrease the bitrate when the delay gets bigger pub fn update_network_delay(&mut self, delay: u32) { @@ -124,19 +135,19 @@ impl VideoQoS { fn refresh_quality(&mut self) { match self.state { DelayState::Normal => { - self.fps = FPS; + self.fps = self.base_fps(); self.current_image_quality = self.user_image_quality; } DelayState::LowDelay => { - self.fps = FPS; + self.fps = self.base_fps(); self.current_image_quality = std::cmp::min(self.user_image_quality, 50); } DelayState::HighDelay => { - self.fps = FPS / 2; + self.fps = self.base_fps() / 2; self.current_image_quality = std::cmp::min(self.user_image_quality, 25); } DelayState::Broken => { - self.fps = FPS / 4; + self.fps = self.base_fps() / 4; self.current_image_quality = 10; } } @@ -146,6 +157,14 @@ impl VideoQoS { // handle image_quality change from peer pub fn update_image_quality(&mut self, image_quality: i32) { + if image_quality == ImageQuality::Low.value() + || image_quality == ImageQuality::Balanced.value() + || image_quality == ImageQuality::Best.value() + { + // not custom + self.user_fps = FPS; + self.fps = FPS; + } let image_quality = Self::convert_quality(image_quality) as _; if self.current_image_quality != image_quality { self.current_image_quality = image_quality; @@ -156,6 +175,16 @@ impl VideoQoS { self.user_image_quality = self.current_image_quality; } + pub fn update_user_fps(&mut self, fps: u8) { + if fps >= MIN_FPS && fps <= MAX_FPS { + if self.user_fps != fps { + self.user_fps = fps; + self.fps = fps; + self.updated = true; + } + } + } + pub fn generate_bitrate(&mut self) -> ResultType { // https://www.nvidia.com/en-us/geforce/guides/broadcasting-guide/ if self.width == 0 || self.height == 0 { diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index cf550ed63..f95a743c2 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -134,6 +134,11 @@ impl Session { } } + pub fn set_custom_fps(&mut self, custom_fps: i32) { + let msg = self.lc.write().unwrap().set_custom_fps(custom_fps); + self.send(Data::Message(msg)); + } + pub fn get_remember(&self) -> bool { self.lc.read().unwrap().remember } @@ -1181,7 +1186,12 @@ impl Interface for Session { if self.is_file_transfer() { self.close_success(); } else if !self.is_port_forward() { - self.msgbox("success", "Successful", "Connected, waiting for image...", ""); + self.msgbox( + "success", + "Successful", + "Connected, waiting for image...", + "", + ); } #[cfg(windows)] {