diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto
index 048301d7e..7dd746e3c 100644
--- a/libs/hbb_common/protos/message.proto
+++ b/libs/hbb_common/protos/message.proto
@@ -425,9 +425,9 @@ message PermissionInfo {
enum ImageQuality {
NotSet = 0;
- Low = 2;
- Balanced = 3;
- Best = 4;
+ Low = 50;
+ Balanced = 66;
+ Best = 100;
}
message OptionMessage {
@@ -441,15 +441,18 @@ message OptionMessage {
BoolOption show_remote_cursor = 3;
BoolOption privacy_mode = 4;
BoolOption block_input = 5;
- int32 custom_image_quality = 6;
+ uint32 custom_image_quality = 6;
BoolOption disable_audio = 7;
BoolOption disable_clipboard = 8;
BoolOption enable_file_transfer = 9;
+ BoolOption enable_abr = 10;
}
message TestDelay {
int64 time = 1;
bool from_client = 2;
+ uint32 last_delay = 3;
+ uint32 target_bitrate = 4;
}
message PublicKey {
diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs
index 1c14be303..068816dc5 100644
--- a/libs/hbb_common/src/config.rs
+++ b/libs/hbb_common/src/config.rs
@@ -139,6 +139,8 @@ pub struct PeerConfig {
pub disable_clipboard: bool,
#[serde(default)]
pub enable_file_transfer: bool,
+ #[serde(default)]
+ pub show_quality_monitor: bool,
// the other scalar value must before this
#[serde(default)]
diff --git a/libs/scrap/src/common/android.rs b/libs/scrap/src/common/android.rs
index 1975a6505..8322da3cd 100644
--- a/libs/scrap/src/common/android.rs
+++ b/libs/scrap/src/common/android.rs
@@ -3,8 +3,8 @@ use crate::rgba_to_i420;
use lazy_static::lazy_static;
use serde_json::Value;
use std::collections::HashMap;
-use std::io;
use std::sync::Mutex;
+use std::{io, time::Duration};
lazy_static! {
static ref SCREEN_SIZE: Mutex<(u16, u16, u16)> = Mutex::new((0, 0, 0)); // (width, height, scale)
@@ -33,7 +33,7 @@ impl Capturer {
self.display.height() as usize
}
- pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result> {
+ pub fn frame<'a>(&'a mut self, _timeout: Duration) -> io::Result> {
if let Some(buf) = get_video_raw() {
crate::would_block_if_equal(&mut self.saved_raw_data, buf)?;
rgba_to_i420(self.width(), self.height(), buf, &mut self.bgra);
diff --git a/libs/scrap/src/common/codec.rs b/libs/scrap/src/common/codec.rs
index f1533d7cf..2766243a3 100644
--- a/libs/scrap/src/common/codec.rs
+++ b/libs/scrap/src/common/codec.rs
@@ -107,17 +107,6 @@ impl Encoder {
c.rc_target_bitrate = config.bitrate;
c.rc_undershoot_pct = 95;
c.rc_dropframe_thresh = 25;
- if config.rc_min_quantizer > 0 {
- c.rc_min_quantizer = config.rc_min_quantizer;
- }
- if config.rc_max_quantizer > 0 {
- c.rc_max_quantizer = config.rc_max_quantizer;
- }
- let mut speed = config.speed;
- if speed <= 0 {
- speed = 6;
- }
-
c.g_threads = if num_threads == 0 {
num_cpus::get() as _
} else {
@@ -162,7 +151,7 @@ impl Encoder {
Higher numbers (7 or 8) will be lower quality but more manageable for lower latency
use cases and also for lower CPU power devices such as mobile.
*/
- call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, speed,));
+ call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, 7,));
// set row level multi-threading
/*
as some people in comments and below have already commented,
@@ -222,6 +211,19 @@ impl Encoder {
})
}
+ pub fn set_bitrate(&mut self, bitrate: c_uint) -> Result<()> {
+ // let mut cfg = self.ctx.config.enc;
+ let mut new_enc_cfg = unsafe { *self.ctx.config.enc.to_owned() };
+ new_enc_cfg.rc_target_bitrate = bitrate;
+ call_vpx!(vpx_codec_enc_config_set(&mut self.ctx, &new_enc_cfg));
+ return Ok(());
+ }
+
+ pub fn get_bitrate(&mut self) -> u32 {
+ let cfg = unsafe { *self.ctx.config.enc.to_owned() };
+ cfg.rc_target_bitrate
+ }
+
/// Notify the encoder to return any pending packets
pub fn flush(&mut self) -> Result {
call_vpx!(vpx_codec_encode(
@@ -273,9 +275,6 @@ pub struct Config {
pub bitrate: c_uint,
/// The codec
pub codec: VideoCodecId,
- pub rc_min_quantizer: u32,
- pub rc_max_quantizer: u32,
- pub speed: i32,
}
pub struct EncodeFrames<'a> {
diff --git a/libs/scrap/src/common/x11.rs b/libs/scrap/src/common/x11.rs
index f8217e3b7..255819902 100644
--- a/libs/scrap/src/common/x11.rs
+++ b/libs/scrap/src/common/x11.rs
@@ -1,5 +1,5 @@
use crate::x11;
-use std::{io, ops};
+use std::{io, ops, time::Duration};
pub struct Capturer(x11::Capturer);
@@ -16,7 +16,7 @@ impl Capturer {
self.0.display().rect().h as usize
}
- pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result> {
+ pub fn frame<'a>(&'a mut self, _timeout: Duration) -> io::Result> {
Ok(Frame(self.0.frame()?))
}
}
diff --git a/src/client.rs b/src/client.rs
index fed83cece..85bb2c0cb 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -886,6 +886,8 @@ impl LoginConfigHandler {
option.block_input = BoolOption::Yes.into();
} else if name == "unblock-input" {
option.block_input = BoolOption::No.into();
+ } else if name == "show-quality-monitor" {
+ config.show_quality_monitor = !config.show_quality_monitor;
} else {
let v = self.options.get(&name).is_some();
if v {
@@ -918,15 +920,8 @@ impl LoginConfigHandler {
n += 1;
} else if q == "custom" {
let config = PeerConfig::load(&self.id);
- let mut it = config.custom_image_quality.iter();
- let bitrate = it.next();
- let quantizer = it.next();
- if let Some(bitrate) = bitrate {
- if let Some(quantizer) = quantizer {
- msg.custom_image_quality = bitrate << 8 | quantizer;
- n += 1;
- }
- }
+ msg.custom_image_quality = config.custom_image_quality[0] as _;
+ n += 1;
}
if self.get_toggle_option("show-remote-cursor") {
msg.show_remote_cursor = BoolOption::Yes.into();
@@ -988,6 +983,8 @@ impl LoginConfigHandler {
self.config.disable_audio
} else if name == "disable-clipboard" {
self.config.disable_clipboard
+ } else if name == "show-quality-monitor" {
+ self.config.show_quality_monitor
} else {
!self.get_option(name).is_empty()
}
@@ -1009,17 +1006,17 @@ impl LoginConfigHandler {
msg_out
}
- pub fn save_custom_image_quality(&mut self, bitrate: i32, quantizer: i32) -> Message {
+ pub fn save_custom_image_quality(&mut self, custom_image_quality: u32) -> Message {
let mut misc = Misc::new();
misc.set_option(OptionMessage {
- custom_image_quality: bitrate << 8 | quantizer,
+ custom_image_quality,
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_misc(misc);
let mut config = self.load_config();
config.image_quality = "custom".to_owned();
- config.custom_image_quality = vec![bitrate, quantizer];
+ config.custom_image_quality = vec![custom_image_quality as _];
self.save_config(config);
msg_out
}
@@ -1214,14 +1211,6 @@ where
return (video_sender, audio_sender);
}
-pub async fn handle_test_delay(t: TestDelay, peer: &mut Stream) {
- if !t.from_client {
- let mut msg_out = Message::new();
- msg_out.set_test_delay(t);
- allow_err!(peer.send(&msg_out).await);
- }
-}
-
// mask = buttons << 3 | type
// type, 1: down, 2: up, 3: wheel
// buttons, 1: left, 2: right, 4: middle
diff --git a/src/server/connection.rs b/src/server/connection.rs
index 304b20655..21072e9cd 100644
--- a/src/server/connection.rs
+++ b/src/server/connection.rs
@@ -3,6 +3,7 @@ use super::{input_service::*, *};
use crate::clipboard_file::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::common::update_clipboard;
+use crate::video_service;
#[cfg(any(target_os = "android", target_os = "ios"))]
use crate::{common::MOBILE_INFO2, mobile::connection_manager::start_channel};
use crate::{ipc, VERSION};
@@ -69,7 +70,6 @@ pub struct Connection {
audio: bool,
file: bool,
last_test_delay: i64,
- image_quality: i32,
lock_after_session_end: bool,
show_remote_cursor: bool, // by peer
ip: String,
@@ -105,7 +105,7 @@ impl Subscriber for ConnInner {
}
}
-const TEST_DELAY_TIMEOUT: Duration = Duration::from_secs(3);
+const TEST_DELAY_TIMEOUT: Duration = Duration::from_secs(1);
const SEC30: Duration = Duration::from_secs(30);
const H1: Duration = Duration::from_secs(3600);
const MILLI1: Duration = Duration::from_millis(1);
@@ -154,7 +154,6 @@ impl Connection {
audio: Config::get_option("enable-audio").is_empty(),
file: Config::get_option("enable-file-transfer").is_empty(),
last_test_delay: 0,
- image_quality: ImageQuality::Balanced.value(),
lock_after_session_end: false,
show_remote_cursor: false,
ip: "".to_owned(),
@@ -376,8 +375,11 @@ impl Connection {
if time > 0 && conn.last_test_delay == 0 {
conn.last_test_delay = time;
let mut msg_out = Message::new();
+ let qos = video_service::VIDEO_QOS.lock().unwrap();
msg_out.set_test_delay(TestDelay{
time,
+ last_delay:qos.current_delay,
+ target_bitrate:qos.target_bitrate,
..Default::default()
});
conn.inner.send(msg_out.into());
@@ -394,8 +396,7 @@ impl Connection {
let _ = privacy_mode::turn_off_privacy(0);
}
video_service::notify_video_frame_feched(id, None);
- video_service::update_test_latency(id, 0);
- video_service::update_image_quality(id, None);
+ video_service::VIDEO_QOS.lock().unwrap().reset();
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
conn.on_close(&err.to_string(), false);
}
@@ -664,7 +665,7 @@ impl Connection {
res.set_peer_info(pi);
} else {
try_activate_screen();
- match super::video_service::get_displays() {
+ match video_service::get_displays() {
Err(err) => {
res.set_error(format!("X11 error: {}", err));
}
@@ -886,10 +887,11 @@ impl Connection {
self.inner.send(msg_out.into());
} else {
self.last_test_delay = 0;
- let latency = crate::get_time() - t.time;
- if latency > 0 {
- super::video_service::update_test_latency(self.inner.id(), latency);
- }
+ let new_delay = (crate::get_time() - t.time) as u32;
+ video_service::VIDEO_QOS
+ .lock()
+ .unwrap()
+ .update_network_delay(new_delay);
}
} else if self.authorized {
match msg.union {
@@ -1065,7 +1067,7 @@ impl Connection {
},
Some(message::Union::misc(misc)) => match misc.union {
Some(misc::Union::switch_display(s)) => {
- super::video_service::switch_display(s.display);
+ video_service::switch_display(s.display);
}
Some(misc::Union::chat_message(c)) => {
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
@@ -1075,7 +1077,7 @@ impl Connection {
}
Some(misc::Union::refresh_video(r)) => {
if r {
- super::video_service::refresh();
+ video_service::refresh();
}
}
Some(misc::Union::video_received(_)) => {
@@ -1095,13 +1097,18 @@ impl Connection {
async fn update_option(&mut self, o: &OptionMessage) {
log::info!("Option update: {:?}", o);
if let Ok(q) = o.image_quality.enum_value() {
- self.image_quality = q.value();
- super::video_service::update_image_quality(self.inner.id(), Some(q.value()));
- }
- let q = o.custom_image_quality;
- if q > 0 {
- self.image_quality = q;
- super::video_service::update_image_quality(self.inner.id(), Some(q));
+ let mut image_quality = None;
+ if let ImageQuality::NotSet = q {
+ if o.custom_image_quality > 0 {
+ image_quality = Some(o.custom_image_quality);
+ }
+ } else {
+ image_quality = Some(q.value() as _)
+ }
+ video_service::VIDEO_QOS
+ .lock()
+ .unwrap()
+ .update_image_quality(image_quality);
}
if let Ok(q) = o.lock_after_session_end.enum_value() {
if q != BoolOption::NotSet {
diff --git a/src/server/video_service.rs b/src/server/video_service.rs
index b486cd311..8dcf2db73 100644
--- a/src/server/video_service.rs
+++ b/src/server/video_service.rs
@@ -33,19 +33,249 @@ use std::{
use virtual_display;
pub const NAME: &'static str = "video";
+const FPS: u8 = 30;
lazy_static::lazy_static! {
static ref CURRENT_DISPLAY: Arc> = Arc::new(Mutex::new(usize::MAX));
static ref LAST_ACTIVE: Arc> = Arc::new(Mutex::new(Instant::now()));
static ref SWITCH: Arc> = Default::default();
- static ref TEST_LATENCIES: Arc>> = Default::default();
- static ref IMAGE_QUALITIES: Arc>> = Default::default();
static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option)>, Arc)>>>) = {
let (tx, rx) = unbounded_channel();
(tx, Arc::new(TokioMutex::new(rx)))
};
static ref PRIVACY_MODE_CONN_ID: Mutex = Mutex::new(0);
static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported();
+ pub static ref VIDEO_QOS: Arc> = Default::default();
+}
+
+pub struct VideoQoS {
+ width: u32,
+ height: u32,
+ user_image_quality: u32,
+ current_image_quality: u32,
+ enable_abr: bool,
+
+ pub current_delay: u32,
+ pub fps: u8, // abr
+ pub target_bitrate: u32, // abr
+ updated: bool,
+
+ state: AdaptiveState,
+ last_delay: u32,
+ count: u32,
+}
+
+#[derive(Debug)]
+enum AdaptiveState {
+ Normal,
+ LowDelay,
+ HeightDelay,
+}
+
+impl Default for VideoQoS {
+ fn default() -> Self {
+ VideoQoS {
+ fps: FPS,
+ user_image_quality: ImageQuality::Balanced.value() as _,
+ current_image_quality: ImageQuality::Balanced.value() as _,
+ enable_abr: false,
+ width: 0,
+ height: 0,
+ current_delay: 0,
+ target_bitrate: 0,
+ updated: false,
+ state: AdaptiveState::Normal,
+ last_delay: 0,
+ count: 0,
+ }
+ }
+}
+
+const MAX: f32 = 1.2;
+const MIN: f32 = 0.8;
+const MAX_COUNT: u32 = 3;
+const MAX_DELAY: u32 = 500;
+const MIN_DELAY: u32 = 50;
+
+impl VideoQoS {
+ pub fn set_size(&mut self, width: u32, height: u32) {
+ if width == 0 || height == 0 {
+ return;
+ }
+ self.width = width;
+ self.height = height;
+ }
+
+ pub fn spf(&mut self) -> Duration {
+ if self.fps <= 0 {
+ self.fps = FPS;
+ }
+ time::Duration::from_secs_f32(1. / (self.fps as f32))
+ }
+
+ // abr
+ pub fn update_network_delay(&mut self, delay: u32) {
+ if self.current_delay.eq(&0) {
+ self.current_delay = delay;
+ return;
+ }
+ let current_delay = self.current_delay as f32;
+
+ self.current_delay = delay / 2 + self.current_delay / 2;
+ log::debug!(
+ "update_network_delay:{}, {}, state:{:?},count:{}",
+ self.current_delay,
+ delay,
+ self.state,
+ self.count
+ );
+
+ if self.current_delay < MIN_DELAY {
+ if self.fps != 30 && self.current_image_quality != self.user_image_quality {
+ log::debug!("current_delay is normal, set to user_image_quality");
+ self.fps = 30;
+ self.current_image_quality = self.user_image_quality;
+ let _ = self.generate_bitrate().ok();
+ self.updated = true;
+ }
+ self.state = AdaptiveState::Normal;
+ } else if self.current_delay > MAX_DELAY {
+ if self.fps != 5 && self.current_image_quality != 25 {
+ log::debug!("current_delay is very height, set fps to 5, image_quality to 25");
+ self.fps = 5;
+ self.current_image_quality = 25;
+ let _ = self.generate_bitrate().ok();
+ self.updated = true;
+ }
+ } else {
+ let delay = delay as f32;
+ let last_delay = self.last_delay as f32;
+ match self.state {
+ AdaptiveState::Normal => {
+ if delay > current_delay * MAX {
+ self.state = AdaptiveState::HeightDelay;
+ } else if delay < current_delay * MIN
+ && self.current_image_quality < self.user_image_quality
+ {
+ self.state = AdaptiveState::LowDelay;
+ }
+ self.count = 1;
+ self.last_delay = self.current_delay
+ }
+ AdaptiveState::HeightDelay => {
+ if delay > last_delay {
+ if self.count > MAX_COUNT {
+ self.decrease_quality();
+ self.reset_state();
+ return;
+ }
+ self.count += 1;
+ } else {
+ self.reset_state();
+ }
+ }
+ AdaptiveState::LowDelay => {
+ if delay < last_delay * MIN {
+ if self.count > MAX_COUNT {
+ self.increase_quality();
+ self.reset_state();
+ return;
+ }
+ self.count += 1;
+ } else {
+ self.reset_state();
+ }
+ }
+ }
+ }
+ }
+
+ fn reset_state(&mut self) {
+ self.count = 0;
+ self.state = AdaptiveState::Normal;
+ }
+
+ fn increase_quality(&mut self) {
+ log::debug!("Adaptive increase quality");
+ if self.fps < FPS {
+ log::debug!("increase fps {} -> {}", self.fps, FPS);
+ self.fps = FPS;
+ } else {
+ self.current_image_quality += self.current_image_quality / 2;
+ let _ = self.generate_bitrate().ok();
+ log::debug!("increase quality:{}", self.current_image_quality);
+ }
+ self.updated = true;
+ }
+
+ fn decrease_quality(&mut self) {
+ log::debug!("Adaptive decrease quality");
+ if self.fps < 15 {
+ log::debug!("fps is low enough :{}", self.fps);
+ return;
+ }
+ if self.current_image_quality < ImageQuality::Low.value() as _ {
+ self.fps = self.fps / 2;
+ log::debug!("decrease fps:{}", self.fps);
+ } else {
+ self.current_image_quality -= self.current_image_quality / 2;
+ let _ = self.generate_bitrate().ok();
+ log::debug!("decrease quality:{}", self.current_image_quality);
+ };
+ self.updated = true;
+ }
+
+ pub fn update_image_quality(&mut self, image_quality: Option) {
+ if let Some(image_quality) = image_quality {
+ if image_quality < 10 || image_quality > 200 {
+ self.current_image_quality = ImageQuality::Balanced.value() as _;
+ }
+ if self.current_image_quality != image_quality {
+ self.current_image_quality = image_quality;
+ let _ = self.generate_bitrate().ok();
+ self.updated = true;
+ }
+ } else {
+ self.current_image_quality = ImageQuality::Balanced.value() as _;
+ }
+ self.user_image_quality = self.current_image_quality;
+ }
+
+ pub fn generate_bitrate(&mut self) -> ResultType {
+ // https://www.nvidia.com/en-us/geforce/guides/broadcasting-guide/
+ if self.width == 0 || self.height == 0 {
+ bail!("Fail to generate_bitrate, width or height is not set");
+ }
+ if self.current_image_quality == 0 {
+ self.current_image_quality = ImageQuality::Balanced.value() as _;
+ }
+
+ let base_bitrate = ((self.width * self.height) / 800) as u32;
+
+ #[cfg(target_os = "android")]
+ {
+ // fix when andorid screen shrinks
+ let fix = Display::fix_quality() as u32;
+ log::debug!("Android screen, fix quality:{}", fix);
+ let base_bitrate = base_bitrate * fix;
+ self.target_bitrate = base_bitrate * self.image_quality / 100;
+ Ok(self.target_bitrate)
+ }
+ self.target_bitrate = base_bitrate * self.current_image_quality / 100;
+ Ok(self.target_bitrate)
+ }
+
+ pub fn check_if_updated(&mut self) -> bool {
+ if self.updated {
+ self.updated = false;
+ return true;
+ }
+ return false;
+ }
+
+ pub fn reset(&mut self) {
+ *self = Default::default();
+ }
}
fn is_capturer_mag_supported() -> bool {
@@ -125,7 +355,7 @@ impl VideoFrameController {
}
trait TraitCapturer {
- fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result>;
+ fn frame<'a>(&'a mut self, timeout: Duration) -> Result>;
#[cfg(windows)]
fn is_gdi(&self) -> bool;
@@ -134,8 +364,8 @@ trait TraitCapturer {
}
impl TraitCapturer for Capturer {
- fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result> {
- self.frame(timeout_ms)
+ fn frame<'a>(&'a mut self, timeout: Duration) -> Result> {
+ self.frame(timeout)
}
#[cfg(windows)]
@@ -320,9 +550,6 @@ fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)]
ensure_close_virtual_device()?;
- let fps = 30;
- let wait = 1000 / fps;
- let spf = time::Duration::from_secs_f32(1. / (fps as f32));
let (ndisplay, current, display) = get_current_display()?;
let (origin, width, height) = (display.origin(), display.width(), display.height());
log::debug!(
@@ -357,24 +584,26 @@ fn run(sp: GenericService) -> ResultType<()> {
}
let mut c = create_capturer(captuerer_privacy_mode_id, display)?;
- let q = get_image_quality();
- let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q);
- log::info!("bitrate={}, rc_min_quantizer={}", bitrate, rc_min_quantizer);
+ let mut video_qos = VIDEO_QOS.lock().unwrap();
+
+ video_qos.set_size(width as _, height as _);
+ let mut spf = video_qos.spf();
+ let bitrate = video_qos.generate_bitrate()?;
+ drop(video_qos);
+
+ log::info!("init encoder, bitrate={}", bitrate);
let cfg = Config {
width: width as _,
height: height as _,
timebase: [1, 1000], // Output timestamp precision
bitrate,
codec: VideoCodecId::VP9,
- rc_min_quantizer,
- rc_max_quantizer,
- speed,
};
- let mut vpx;
- match Encoder::new(&cfg, (num_cpus::get() / 2) as _) {
- Ok(x) => vpx = x,
+
+ let mut vpx = match Encoder::new(&cfg, (num_cpus::get() / 2) as _) {
+ Ok(x) => x,
Err(err) => bail!("Failed to create encoder: {}", err),
- }
+ };
if *SWITCH.lock().unwrap() {
log::debug!("Broadcasting display switch");
@@ -401,10 +630,24 @@ fn run(sp: GenericService) -> ResultType<()> {
let mut try_gdi = 1;
#[cfg(windows)]
log::info!("gdi: {}", c.is_gdi());
+
while sp.ok() {
#[cfg(windows)]
check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
+ {
+ let mut video_qos = VIDEO_QOS.lock().unwrap();
+ if video_qos.check_if_updated() {
+ log::debug!(
+ "qos is updated, target_bitrate:{}, fps:{}",
+ video_qos.target_bitrate,
+ video_qos.fps
+ );
+ vpx.set_bitrate(video_qos.target_bitrate).unwrap();
+ spf = video_qos.spf();
+ }
+ }
+
if *SWITCH.lock().unwrap() {
bail!("SWITCH");
}
@@ -413,9 +656,6 @@ fn run(sp: GenericService) -> ResultType<()> {
bail!("SWITCH");
}
check_privacy_mode_changed(&sp, privacy_mode_id)?;
- if get_image_quality() != q {
- bail!("SWITCH");
- }
#[cfg(windows)]
{
if crate::platform::windows::desktop_changed() {
@@ -437,7 +677,7 @@ fn run(sp: GenericService) -> ResultType<()> {
frame_controller.reset();
#[cfg(any(target_os = "android", target_os = "ios"))]
- let res = match (*c).frame(wait as _) {
+ let res = match c.frame(spf) {
Ok(frame) => {
let time = now - start;
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
@@ -460,7 +700,7 @@ fn run(sp: GenericService) -> ResultType<()> {
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
- let res = match (*c).frame(wait as _) {
+ let res = match c.frame(spf) {
Ok(frame) => {
let time = now - start;
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
@@ -748,82 +988,3 @@ fn get_current_display() -> ResultType<(usize, usize, Display)> {
}
return Ok((n, current, displays.remove(current)));
}
-
-#[inline]
-fn update_latency(id: i32, latency: i64, latencies: &mut HashMap) {
- if latency <= 0 {
- latencies.remove(&id);
- } else {
- latencies.insert(id, latency);
- }
-}
-
-pub fn update_test_latency(id: i32, latency: i64) {
- update_latency(id, latency, &mut *TEST_LATENCIES.lock().unwrap());
-}
-
-fn convert_quality(q: i32) -> i32 {
- let q = {
- if q == ImageQuality::Balanced.value() {
- (100 * 2 / 3, 12)
- } else if q == ImageQuality::Low.value() {
- (100 / 2, 18)
- } else if q == ImageQuality::Best.value() {
- (100, 12)
- } else {
- let bitrate = q >> 8 & 0xFF;
- let quantizer = q & 0xFF;
- (bitrate * 2, (100 - quantizer) * 36 / 100)
- }
- };
- if q.0 <= 0 {
- 0
- } else {
- q.0 << 8 | q.1
- }
-}
-
-pub fn update_image_quality(id: i32, q: Option) {
- match q {
- Some(q) => {
- let q = convert_quality(q);
- if q > 0 {
- IMAGE_QUALITIES.lock().unwrap().insert(id, q);
- } else {
- IMAGE_QUALITIES.lock().unwrap().remove(&id);
- }
- }
- None => {
- IMAGE_QUALITIES.lock().unwrap().remove(&id);
- }
- }
-}
-
-fn get_image_quality() -> i32 {
- IMAGE_QUALITIES
- .lock()
- .unwrap()
- .values()
- .min()
- .unwrap_or(&convert_quality(ImageQuality::Balanced.value()))
- .clone()
-}
-
-#[inline]
-fn get_quality(w: usize, h: usize, q: i32) -> (u32, u32, u32, i32) {
- // https://www.nvidia.com/en-us/geforce/guides/broadcasting-guide/
- let bitrate = q >> 8 & 0xFF;
- let quantizer = q & 0xFF;
- let b = ((w * h) / 1000) as u32;
-
- #[cfg(target_os = "android")]
- {
- // fix when andorid screen shrinks
- let fix = Display::fix_quality() as u32;
- log::debug!("Android screen, fix quality:{}", fix);
- let b = b * fix;
- return (bitrate as u32 * b / 100, quantizer as _, 56, 7);
- }
-
- (bitrate as u32 * b / 100, quantizer as _, 56, 7)
-}
diff --git a/src/ui/header.tis b/src/ui/header.tis
index 4b2615a45..88ee8b6e6 100644
--- a/src/ui/header.tis
+++ b/src/ui/header.tis
@@ -159,6 +159,7 @@ class Header: Reactor.Component {
{svg_checkmark}{translate('Custom')}
{svg_checkmark}{translate('Show remote cursor')}
+ {svg_checkmark}{translate('Show quality monitor')}
{audio_enabled ? {svg_checkmark}{translate('Mute')} : ""}
{is_win && pi.platform == 'Windows' && file_enabled ? {svg_checkmark}{translate('Allow file copy and paste')} : ""}
{keyboard_enabled && clipboard_enabled ? {svg_checkmark}{translate('Disable clipboard')} : ""}
@@ -315,7 +316,9 @@ class Header: Reactor.Component {
handle_custom_image_quality();
} else if (me.id == "privacy-mode") {
togglePrivacyMode(me.id);
- } else if (me.attributes.hasClass("toggle-option")) {
+ } else if (me.id == "show-quality-monitor") {
+ toggleQualityMonitor(me.id);
+ }else if (me.attributes.hasClass("toggle-option")) {
handler.toggle_option(me.id);
toggleMenuState();
} else if (!me.attributes.hasClass("selected")) {
@@ -332,16 +335,13 @@ class Header: Reactor.Component {
}
function handle_custom_image_quality() {
- var tmp = handler.get_custom_image_quality();
- var bitrate0 = tmp[0] || 50;
- var quantizer0 = tmp.length > 1 ? tmp[1] : 100;
+ var bitrate = handler.get_custom_image_quality()[0] / 2;
msgbox("custom", "Custom Image Quality", "", function(res=null) {
if (!res) return;
if (!res.bitrate) return;
- handler.save_custom_image_quality(res.bitrate, res.quantizer);
+ handler.save_custom_image_quality(res.bitrate * 2);
toggleMenuState();
});
}
@@ -357,7 +357,7 @@ function toggleMenuState() {
for (var el in $$(menu#display-options>li)) {
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
}
- for (var id in ["show-remote-cursor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
+ for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
var el = self.select('#' + id);
if (el) {
var value = handler.get_toggle_option(id);
@@ -425,6 +425,17 @@ function togglePrivacyMode(privacy_id) {
}
}
+function toggleQualityMonitor(name) {
+ var show = handler.get_toggle_option(name);
+ if (show) {
+ $(#quality-monitor).style.set{ display: "none" };
+ } else {
+ $(#quality-monitor).style.set{ display: "block" };
+ }
+ handler.toggle_option(name);
+ toggleMenuState();
+}
+
handler.updateBlockInputState = function(input_blocked) {
if (!input_blocked) {
handler.toggle_option("block-input");
diff --git a/src/ui/remote.css b/src/ui/remote.css
index 617285e9c..66c5ce80f 100644
--- a/src/ui/remote.css
+++ b/src/ui/remote.css
@@ -9,6 +9,16 @@ div#video-wrapper {
background: #212121;
}
+div#quality-monitor {
+ top: 20px;
+ right: 20px;
+ background: #7571719c;
+ padding: 5px;
+ min-width: 150px;
+ color: azure;
+ border: solid azure;
+}
+
video#handler {
behavior: native-remote video;
size: *;
@@ -24,7 +34,7 @@ img#cursor {
}
.goup {
- transform: rotate(90deg);
+ transform: rotate(90deg);
}
table#remote-folder-view {
@@ -33,4 +43,4 @@ table#remote-folder-view {
table#local-folder-view {
context-menu: selector(menu#local-folder-view);
-}
+}
\ No newline at end of file
diff --git a/src/ui/remote.html b/src/ui/remote.html
index 32c1409e2..d58c3449b 100644
--- a/src/ui/remote.html
+++ b/src/ui/remote.html
@@ -1,12 +1,13 @@
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-