mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge branch 'master' of github.com:asur4s/rustdesk
This commit is contained in:
@@ -12,6 +12,11 @@ use cpal::{
|
||||
Device, Host, StreamConfig,
|
||||
};
|
||||
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
||||
use scrap::{
|
||||
codec::{Decoder, DecoderCfg},
|
||||
VpxDecoderConfig, VpxVideoCodecId,
|
||||
};
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -30,13 +35,12 @@ use hbb_common::{
|
||||
tokio::time::Duration,
|
||||
AddrMangle, ResultType, Stream,
|
||||
};
|
||||
use scrap::{Decoder, Image, VideoCodecId};
|
||||
|
||||
pub use super::lang::*;
|
||||
pub mod file_trait;
|
||||
pub use file_trait::FileManager;
|
||||
pub mod helper;
|
||||
pub use helper::LatencyController;
|
||||
pub use helper::*;
|
||||
pub const SEC30: Duration = Duration::from_secs(30);
|
||||
|
||||
pub struct Client;
|
||||
@@ -717,7 +721,12 @@ pub struct VideoHandler {
|
||||
impl VideoHandler {
|
||||
pub fn new(latency_controller: Arc<Mutex<LatencyController>>) -> Self {
|
||||
VideoHandler {
|
||||
decoder: Decoder::new(VideoCodecId::VP9, (num_cpus::get() / 2) as _).unwrap(),
|
||||
decoder: Decoder::new(DecoderCfg {
|
||||
vpx: VpxDecoderConfig {
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
},
|
||||
}),
|
||||
latency_controller,
|
||||
rgb: Default::default(),
|
||||
}
|
||||
@@ -731,33 +740,18 @@ impl VideoHandler {
|
||||
.update_video(vf.timestamp);
|
||||
}
|
||||
match &vf.union {
|
||||
Some(video_frame::Union::vp9s(vp9s)) => self.handle_vp9s(vp9s),
|
||||
Some(frame) => self.decoder.handle_video_frame(frame, &mut self.rgb),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_vp9s(&mut self, vp9s: &VP9s) -> ResultType<bool> {
|
||||
let mut last_frame = Image::new();
|
||||
for vp9 in vp9s.frames.iter() {
|
||||
for frame in self.decoder.decode(&vp9.data)? {
|
||||
drop(last_frame);
|
||||
last_frame = frame;
|
||||
}
|
||||
}
|
||||
for frame in self.decoder.flush()? {
|
||||
drop(last_frame);
|
||||
last_frame = frame;
|
||||
}
|
||||
if last_frame.is_null() {
|
||||
Ok(false)
|
||||
} else {
|
||||
last_frame.rgb(1, true, &mut self.rgb);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.decoder = Decoder::new(VideoCodecId::VP9, 1).unwrap();
|
||||
self.decoder = Decoder::new(DecoderCfg {
|
||||
vpx: VpxDecoderConfig {
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -886,6 +880,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 +914,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] << 8;
|
||||
n += 1;
|
||||
}
|
||||
if self.get_toggle_option("show-remote-cursor") {
|
||||
msg.show_remote_cursor = BoolOption::Yes.into();
|
||||
@@ -952,6 +941,11 @@ impl LoginConfigHandler {
|
||||
msg.disable_clipboard = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
}
|
||||
// TODO: add option
|
||||
let state = Decoder::video_codec_state();
|
||||
msg.video_codec_state = hbb_common::protobuf::MessageField::some(state);
|
||||
n += 1;
|
||||
|
||||
if n > 0 {
|
||||
Some(msg)
|
||||
} else {
|
||||
@@ -988,6 +982,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 +1005,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, image_quality: i32) -> Message {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(OptionMessage {
|
||||
custom_image_quality: bitrate << 8 | quantizer,
|
||||
custom_image_quality: image_quality << 8,
|
||||
..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![image_quality as _];
|
||||
self.save_config(config);
|
||||
msg_out
|
||||
}
|
||||
@@ -1170,9 +1166,11 @@ where
|
||||
|
||||
let latency_controller = LatencyController::new();
|
||||
let latency_controller_cl = latency_controller.clone();
|
||||
// Create video_handler out of the thread below to ensure that the handler exists before client start.
|
||||
// It will take a few tenths of a second for the first time, and then tens of milliseconds.
|
||||
let mut video_handler = VideoHandler::new(latency_controller);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut video_handler = VideoHandler::new(latency_controller);
|
||||
loop {
|
||||
if let Ok(data) = video_receiver.recv() {
|
||||
match data {
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use hbb_common::log;
|
||||
use hbb_common::{log, message_proto::{VideoFrame, video_frame}};
|
||||
|
||||
const MAX_LATENCY: i64 = 500;
|
||||
const MIN_LATENCY: i64 = 100;
|
||||
@@ -57,3 +57,33 @@ impl LatencyController {
|
||||
self.allow_audio
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum CodecFormat {
|
||||
VP9,
|
||||
H264,
|
||||
H265,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<&VideoFrame> for CodecFormat {
|
||||
fn from(it: &VideoFrame) -> Self {
|
||||
match it.union {
|
||||
Some(video_frame::Union::vp9s(_)) => CodecFormat::VP9,
|
||||
Some(video_frame::Union::h264s(_)) => CodecFormat::H264,
|
||||
Some(video_frame::Union::h265s(_)) => CodecFormat::H265,
|
||||
_ => CodecFormat::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for CodecFormat {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
CodecFormat::VP9 => "VP9".into(),
|
||||
CodecFormat::H264 => "H264".into(),
|
||||
CodecFormat::H265 => "H265".into(),
|
||||
CodecFormat::Unknown => "Unknow".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ pub enum FS {
|
||||
WriteOffset {
|
||||
id: i32,
|
||||
file_num: i32,
|
||||
offset_blk: u32
|
||||
offset_blk: u32,
|
||||
},
|
||||
CheckDigest {
|
||||
id: i32,
|
||||
@@ -336,6 +336,7 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
.await
|
||||
);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "关于"),
|
||||
("Mute", "静音"),
|
||||
("Audio Input", "音频输入"),
|
||||
("Enhancements", "增强功能"),
|
||||
("Hardware Codec", "硬件编解码"),
|
||||
("Adaptive Bitrate", "自适应码率"),
|
||||
("ID Server", "ID服务器"),
|
||||
("Relay Server", "中继服务器"),
|
||||
("API Server", "API服务器"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "优化反应时间"),
|
||||
("Custom", "自定义画质"),
|
||||
("Show remote cursor", "显示远程光标"),
|
||||
("Show quality monitor", "显示质量监测"),
|
||||
("Disable clipboard", "禁止剪贴板"),
|
||||
("Lock after session end", "断开后锁定远程电脑"),
|
||||
("Insert", "插入"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "O aplikaci"),
|
||||
("Mute", "Ztlumit"),
|
||||
("Audio Input", "Vstup zvuku"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "Server pro identif."),
|
||||
("Relay Server", "Předávací (relay) server"),
|
||||
("API Server", "Server s API rozhraním"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Optimalizovat pro co nejnižší prodlevu odezvy"),
|
||||
("Custom", "Uživatelsky určené"),
|
||||
("Show remote cursor", "Zobrazovat ukazatel myši z protějšku"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Vypnout schránku"),
|
||||
("Lock after session end", "Po ukončení relace zamknout plochu"),
|
||||
("Insert", "Vložit"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "Omkring"),
|
||||
("Mute", "Sluk for mikrofonen"),
|
||||
("Audio Input", "Lydindgang"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "identifikations Server"),
|
||||
("Relay Server", "Relæ Server"),
|
||||
("API Server", "API Server"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Optimeret responstid"),
|
||||
("Custom", "Brugerdefineret"),
|
||||
("Show remote cursor", "Vis fjernbetjeningskontrolleret markør"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Deaktiver udklipsholder"),
|
||||
("Lock after session end", "Lås efter afslutningen af fjernstyring"),
|
||||
("Insert", "Indsæt"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "Über"),
|
||||
("Mute", "Stummschalten"),
|
||||
("Audio Input", "Audio-Eingang"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "ID Server"),
|
||||
("Relay Server", "Verbindungsserver Server"),
|
||||
("API Server", "API Server"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Optimierte Reaktionszeit"),
|
||||
("Custom", "Benutzerdefiniert"),
|
||||
("Show remote cursor", "Ferngesteuerten Cursor anzeigen"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Zwischenablage deaktivieren"),
|
||||
("Lock after session end", "Sperren nach Sitzungsende"),
|
||||
("Insert", "Einfügen"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "Pri"),
|
||||
("Mute", "Muta"),
|
||||
("Audio Input", "Aŭdia enigo"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "Servilo de identigiloj"),
|
||||
("Relay Server", "Relajsa servilo"),
|
||||
("API Server", "Servilo de API"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Optimigi reakcia tempo"),
|
||||
("Custom", "Personigi bilda kvalito"),
|
||||
("Show remote cursor", "Montri foran kursoron"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Malebligi poŝon"),
|
||||
("Lock after session end", "Ŝlosi foran komputilon post malkonektado"),
|
||||
("Insert", "Enmeti"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "Sobre"),
|
||||
("Mute", "Silencio"),
|
||||
("Audio Input", "Entrada de audio"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "ID server"),
|
||||
("Relay Server", "Server relay"),
|
||||
("API Server", "Server API"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Optimizar el tiempo de reacción"),
|
||||
("Custom", "Personalizado"),
|
||||
("Show remote cursor", "Mostrar cursor remoto"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Deshabilitar portapapeles"),
|
||||
("Lock after session end", "Bloquear después del final de la sesión"),
|
||||
("Insert", "Insertar"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "À propos de"),
|
||||
("Mute", "Muet"),
|
||||
("Audio Input", "Entrée audio"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "Serveur ID"),
|
||||
("Relay Server", "Serveur relais"),
|
||||
("API Server", "Serveur API"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Optimiser le temps de réaction"),
|
||||
("Custom", "Qualité d'image personnalisée"),
|
||||
("Show remote cursor", "Afficher le curseur distant"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Désactiver le presse-papier"),
|
||||
("Lock after session end", "Verrouiller l'ordinateur distant après la déconnexion"),
|
||||
("Insert", "Insérer"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "Tentang"),
|
||||
("Mute", "Bisukan"),
|
||||
("Audio Input", "Masukkan Audio"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "Server ID"),
|
||||
("Relay Server", "Server Relay"),
|
||||
("API Server", "API Server"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Optimalkan waktu reaksi"),
|
||||
("Custom", "Custom"),
|
||||
("Show remote cursor", "Tampilkan remote kursor"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Matikan papan klip"),
|
||||
("Lock after session end", "Kunci setelah sesi berakhir"),
|
||||
("Insert", "Menyisipkan"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "Informazioni"),
|
||||
("Mute", "Silenzia"),
|
||||
("Audio Input", "Input audio"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "ID server"),
|
||||
("Relay Server", "Server relay"),
|
||||
("API Server", "Server API"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Ottimizza il tempo di reazione"),
|
||||
("Custom", "Personalizzato"),
|
||||
("Show remote cursor", "Mostra il cursore remoto"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Disabilita appunti"),
|
||||
("Lock after session end", "Blocca al termine della sessione"),
|
||||
("Insert", "Inserisci"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "Sobre"),
|
||||
("Mute", "Emudecer"),
|
||||
("Audio Input", "Entrada de Áudio"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "Servidor de ID"),
|
||||
("Relay Server", "Servidor de Relay"),
|
||||
("API Server", "Servidor da API"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Otimizar tempo de reação"),
|
||||
("Custom", "Personalizado"),
|
||||
("Show remote cursor", "Mostrar cursor remoto"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Desabilitar área de transferência"),
|
||||
("Lock after session end", "Bloquear após o fim da sessão"),
|
||||
("Insert", "Inserir"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "О RustDesk"),
|
||||
("Mute", "Отключить звук"),
|
||||
("Audio Input", "Аудиовход"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "ID-сервер"),
|
||||
("Relay Server", "Сервер ретрансляции"),
|
||||
("API Server", "API-сервер"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Оптимизировать время реакции"),
|
||||
("Custom", "Пользовательский"),
|
||||
("Show remote cursor", "Показать удаленный курсор"),
|
||||
("Show quality monitor", "Показать качество"),
|
||||
("Disable clipboard", "Отключить буфер обмена"),
|
||||
("Lock after session end", "Выход из учётной записи после завершения сеанса"),
|
||||
("Insert", "Вставить"),
|
||||
@@ -121,9 +125,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to connect via relay server", "Не удалось подключиться через сервер ретрансляции"),
|
||||
("Failed to make direct connection to remote desktop", "Не удалось установить прямое подключение к удаленному рабочему столу"),
|
||||
("Set Password", "Установить пароль"),
|
||||
("OS Password", "Пароль операционной системы"),
|
||||
("OS Password", "Пароль ОС"),
|
||||
("install_tip", "В некоторых случаях из-за UAC RustDesk может работать некорректно на удаленном узле. Чтобы избежать UAC, нажмите кнопку ниже, чтобы установить RustDesk в системе"),
|
||||
("Click to upgrade", "Нажмите, чтобы проверить на наличие обновлений"),
|
||||
("Click to upgrade", "Нажмите, чтобы проверить наличие обновлений"),
|
||||
("Click to download", "Нажмите, чтобы скачать"),
|
||||
("Click to update", "Нажмите, чтобы обновить"),
|
||||
("Configure", "Настроить"),
|
||||
@@ -132,14 +136,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Installing ...", "Устанавливается..."),
|
||||
("Install", "Установить"),
|
||||
("Installation", "Установка"),
|
||||
("Installation Path", "Папка установки"),
|
||||
("Installation Path", "Путь установки"),
|
||||
("Create start menu shortcuts", "Создать ярлыки меню \"Пуск\""),
|
||||
("Create desktop icon", "Создать значок на рабочем столе"),
|
||||
("agreement_tip", "Если вы начнете установку, примите лицензионное соглашение"),
|
||||
("Accept and Install", "Принять и установить"),
|
||||
("End-user license agreement", "Лицензионное соглашение с конечным пользователем"),
|
||||
("Generating ...", "Генерация..."),
|
||||
("Your installation is lower version.", "Ваша инсталяция является более ранней версией"),
|
||||
("Your installation is lower version.", "Ваша установка более ранней версии"),
|
||||
("not_close_tcp_tip", "Не закрывать это окно при использовании туннеля"),
|
||||
("Listening ...", "Ожидаем..."),
|
||||
("Remote Host", "Удаленная машина"),
|
||||
@@ -159,11 +163,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Allow hearing sound", "Разрешить передачу звука"),
|
||||
("Allow file copy and paste", "Разрешить копирование и вставку файлов"),
|
||||
("Connected", "Подключено"),
|
||||
("Direct and encrypted connection", "Прямое и шифрованное соединение"),
|
||||
("Relayed and encrypted connection", "Коммутируемое и зашифрованное соединение"),
|
||||
("Direct and encrypted connection", "Прямое и зашифрованное соединение"),
|
||||
("Relayed and encrypted connection", "Ретранслируемое и зашифрованное соединение"),
|
||||
("Direct and unencrypted connection", "Прямое и незашифрованное соединение"),
|
||||
("Relayed and unencrypted connection", "Коммутируемое и незашифрованное соединение"),
|
||||
("Enter Remote ID", "Введите удаленный идентификатор"),
|
||||
("Relayed and unencrypted connection", "Ретранслируемое и незашифрованное соединение"),
|
||||
("Enter Remote ID", "Введите удаленный ID"),
|
||||
("Enter your password", "Введите пароль"),
|
||||
("Logging in...", "Вход..."),
|
||||
("Enable RDP session sharing", "Включить общий доступ к сеансу RDP"),
|
||||
@@ -219,12 +223,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Paste", "Вставить"),
|
||||
("Paste here?", "Вставить сюда?"),
|
||||
("Are you sure to close the connection?", "Вы уверены, что хотите закрыть соединение?"),
|
||||
("Download new version", "Загрузить новую версию"),
|
||||
("Download new version", "Скачать новую версию"),
|
||||
("Touch mode", "Сенсорный режим"),
|
||||
("Mouse mode", "Режим мыши"),
|
||||
("One-Finger Tap", "Касание одним пальцем"),
|
||||
("Left Mouse", "Левая кнопка мыши"),
|
||||
("One-Long Tap", "Одно долгое касание пальцем"),
|
||||
("One-Long Tap", "Одно долгое нажатие пальцем"),
|
||||
("Two-Finger Tap", "Касание двумя пальцами"),
|
||||
("Right Mouse", "Правая мышь"),
|
||||
("One-Finger Move", "Движение одним пальцем"),
|
||||
@@ -255,7 +259,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you accept?", "Вы согласны?"),
|
||||
("Open System Setting", "Открыть настройки системы"),
|
||||
("How to get Android input permission?", "Как получить разрешение на ввод Android?"),
|
||||
("android_input_permission_tip1", "Чтобы удаленное устройство могло управлять вашим Android-устройством с помощью мыши или касания, вам необходимо разрешить RustDesk использовать службу «Специальные возможности»."),
|
||||
("android_input_permission_tip1", "Чтобы удаленное устройство могло управлять вашим Android-устройством с помощью мыши или касания, вам необходимо разрешить RustDesk использовать службу \"Специальные возможности\"."),
|
||||
("android_input_permission_tip2", "Перейдите на следующую страницу системных настроек, найдите и войдите в [Установленные службы], включите службу [RustDesk Input]."),
|
||||
("android_new_connection_tip", "Получен новый запрос на управление вашим текущим устройством."),
|
||||
("android_service_will_start_tip", "Включение захвата экрана автоматически запускает службу, позволяя другим устройствам запрашивать соединение с этого устройства."),
|
||||
@@ -264,21 +268,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_start_service_tip", "Нажмите [Запуск промежуточного сервера] или ОТКРЫТЬ разрешение [Захват экрана], чтобы запустить службу демонстрации экрана."),
|
||||
("Account", "Аккаунт"),
|
||||
("Overwrite", "Перезаписать"),
|
||||
("This file exists, skip or overwrite this file?", "Этот файл существует, пропустить или перезаписать этот файл?"),
|
||||
("This file exists, skip or overwrite this file?", "Этот файл существует, пропустить или перезаписать файл?"),
|
||||
("Quit", "Выйти"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/ru/manual/mac/#включение-разрешений"),
|
||||
("Help", "Помощь"),
|
||||
("Failed", "Неуспешный"),
|
||||
("Failed", "Не удалось"),
|
||||
("Succeeded", "Успешно"),
|
||||
("Someone turns on privacy mode, exit", "Кто-то включает режим конфиденциальности, выйдите"),
|
||||
("Unsupported", "Не поддерживается"),
|
||||
("Peer denied", "Отказано в пире"),
|
||||
("Please install plugins", "Пожалуйста, установите плагины"),
|
||||
("Peer exit", "Одноранговый выход"),
|
||||
("Failed to turn off", "Не удалось отключить"),
|
||||
("Failed to turn off", "Не удалось выключить"),
|
||||
("Turned off", "Выключен"),
|
||||
("In privacy mode", "В режиме конфиденциальности"),
|
||||
("Out privacy mode", "Выход из режима конфиденциальности"),
|
||||
("Language", ""),
|
||||
("Language", "Язык"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "O RustDesk"),
|
||||
("Mute", "Stíšiť"),
|
||||
("Audio Input", "Zvukový vstup"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "ID server"),
|
||||
("Relay Server", "Prepojovací server"),
|
||||
("API Server", "API server"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Optimalizované pre čas odozvy"),
|
||||
("Custom", "Vlastné"),
|
||||
("Show remote cursor", "Zobrazovať vzdialený ukazovateľ myši"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Vypnúť schránku"),
|
||||
("Lock after session end", "Po skončení uzamknúť plochu"),
|
||||
("Insert", "Vložiť"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", ""),
|
||||
("Mute", ""),
|
||||
("Audio Input", ""),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", ""),
|
||||
("Relay Server", ""),
|
||||
("API Server", ""),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", ""),
|
||||
("Custom", ""),
|
||||
("Show remote cursor", ""),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", ""),
|
||||
("Lock after session end", ""),
|
||||
("Insert", ""),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "Hakkında"),
|
||||
("Mute", "Sesi Kapat"),
|
||||
("Audio Input", "Ses Girişi"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
("Adaptive Bitrate", ""),
|
||||
("ID Server", "ID Sunucu"),
|
||||
("Relay Server", "Relay Sunucu"),
|
||||
("API Server", "API Sunucu"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Tepki süresini optimize et"),
|
||||
("Custom", "Özel"),
|
||||
("Show remote cursor", "Uzaktaki fare imlecini göster"),
|
||||
("Show quality monitor", ""),
|
||||
("Disable clipboard", "Hafızadaki kopyalanmışları engelle"),
|
||||
("Lock after session end", "Bağlantıdan sonra kilitle"),
|
||||
("Insert", "Ekle"),
|
||||
|
||||
@@ -35,6 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About", "關於"),
|
||||
("Mute", "靜音"),
|
||||
("Audio Input", "音訊輸入"),
|
||||
("Enhancements", "增強功能"),
|
||||
("Hardware Codec", "硬件編解碼"),
|
||||
("Adaptive Bitrate", "自適應碼率"),
|
||||
("ID Server", "ID 伺服器"),
|
||||
("Relay Server", "轉送伺服器"),
|
||||
("API Server", "API 伺服器"),
|
||||
@@ -105,6 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "回應速度最佳化"),
|
||||
("Custom", "自訂"),
|
||||
("Show remote cursor", "顯示遠端游標"),
|
||||
("Show quality monitor", "顯示質量監測"),
|
||||
("Disable clipboard", "停用剪貼簿"),
|
||||
("Lock after session end", "工作階段結束後鎖定電腦"),
|
||||
("Insert", "插入"),
|
||||
|
||||
@@ -151,6 +151,10 @@ fn main() {
|
||||
ipc::set_password(args[1].to_owned()).unwrap();
|
||||
}
|
||||
return;
|
||||
} else if args[0] == "--check-hwcodec-config" {
|
||||
#[cfg(feature = "hwcodec")]
|
||||
scrap::hwcodec::check_config();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ui::start(&mut args[..]);
|
||||
|
||||
@@ -1020,6 +1020,22 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{start_menu}\\\"
|
||||
// https://docs.microsoft.com/zh-cn/windows/win32/msi/uninstall-registry-key?redirectedfrom=MSDNa
|
||||
// https://www.windowscentral.com/how-edit-registry-using-command-prompt-windows-10
|
||||
// https://www.tenforums.com/tutorials/70903-add-remove-allowed-apps-through-windows-firewall-windows-10-a.html
|
||||
// Note: without if exist, the bat may exit in advance on some Windows7 https://github.com/rustdesk/rustdesk/issues/895
|
||||
let dels = format!(
|
||||
"
|
||||
if exist \"{mk_shortcut}\" del /f /q \"{mk_shortcut}\"
|
||||
if exist \"{uninstall_shortcut}\" del /f /q \"{uninstall_shortcut}\"
|
||||
if exist \"{tray_shortcut}\" del /f /q \"{tray_shortcut}\"
|
||||
if exist \"{tmp_path}\\{app_name}.lnk\" del /f /q \"{tmp_path}\\{app_name}.lnk\"
|
||||
if exist \"{tmp_path}\\Uninstall {app_name}.lnk\" del /f /q \"{tmp_path}\\Uninstall {app_name}.lnk\"
|
||||
if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name} Tray.lnk\"
|
||||
",
|
||||
mk_shortcut = mk_shortcut,
|
||||
uninstall_shortcut = uninstall_shortcut,
|
||||
tray_shortcut = tray_shortcut,
|
||||
tmp_path = tmp_path,
|
||||
app_name = crate::get_app_name(),
|
||||
);
|
||||
let cmds = format!(
|
||||
"
|
||||
{uninstall_str}
|
||||
@@ -1048,12 +1064,7 @@ cscript \"{tray_shortcut}\"
|
||||
copy /Y \"{tmp_path}\\{app_name} Tray.lnk\" \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\\"
|
||||
{shortcuts}
|
||||
copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\"
|
||||
del /f \"{mk_shortcut}\"
|
||||
del /f \"{uninstall_shortcut}\"
|
||||
del /f \"{tray_shortcut}\"
|
||||
del /f \"{tmp_path}\\{app_name}.lnk\"
|
||||
del /f \"{tmp_path}\\Uninstall {app_name}.lnk\"
|
||||
del /f \"{tmp_path}\\{app_name} Tray.lnk\"
|
||||
{dels}
|
||||
sc create {app_name} binpath= \"\\\"{exe}\\\" --import-config \\\"{config_path}\\\"\" start= auto DisplayName= \"{app_name} Service\"
|
||||
sc start {app_name}
|
||||
sc stop {app_name}
|
||||
@@ -1086,7 +1097,12 @@ sc delete {app_name}
|
||||
"timeout 300"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
},
|
||||
dels=if debug {
|
||||
""
|
||||
} else {
|
||||
&dels
|
||||
},
|
||||
);
|
||||
run_cmds(cmds, debug, "install")?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(2000));
|
||||
@@ -1132,10 +1148,10 @@ fn get_uninstall() -> String {
|
||||
"
|
||||
{before_uninstall}
|
||||
reg delete {subkey} /f
|
||||
rd /s /q \"{path}\"
|
||||
rd /s /q \"{start_menu}\"
|
||||
del /f /q \"%PUBLIC%\\Desktop\\{app_name}*\"
|
||||
del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\"
|
||||
if exist \"{path}\" rd /s /q \"{path}\"
|
||||
if exist \"{start_menu}\" rd /s /q \"{start_menu}\"
|
||||
if exist \"%PUBLIC%\\Desktop\\{app_name}.lnk\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}.lnk\"
|
||||
if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\"
|
||||
",
|
||||
before_uninstall=get_before_uninstall(),
|
||||
subkey=subkey,
|
||||
@@ -1182,11 +1198,8 @@ fn run_cmds(cmds: String, show: bool, tip: &str) -> ResultType<()> {
|
||||
.show(show)
|
||||
.force_prompt(true)
|
||||
.status();
|
||||
// leave the file for debug if execution failed
|
||||
if let Ok(res) = res {
|
||||
if res.success() {
|
||||
allow_err!(std::fs::remove_file(tmp));
|
||||
}
|
||||
if !show {
|
||||
allow_err!(std::fs::remove_file(tmp));
|
||||
}
|
||||
let _ = res?;
|
||||
Ok(())
|
||||
|
||||
@@ -38,6 +38,7 @@ pub const NAME_POS: &'static str = "";
|
||||
|
||||
mod connection;
|
||||
mod service;
|
||||
mod video_qos;
|
||||
pub mod video_service;
|
||||
|
||||
use hbb_common::tcp::new_listener;
|
||||
@@ -320,6 +321,15 @@ pub async fn start_server(is_server: bool) {
|
||||
std::process::exit(-1);
|
||||
}
|
||||
});
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
std::thread::spawn(move || {
|
||||
std::process::Command::new(exe)
|
||||
.arg("--check-hwcodec-config")
|
||||
.status()
|
||||
.ok()
|
||||
});
|
||||
}
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::bootstrap();
|
||||
input_service::fix_key_down_timeout_loop();
|
||||
|
||||
@@ -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,8 @@ 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);
|
||||
scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
|
||||
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);
|
||||
}
|
||||
@@ -665,7 +667,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));
|
||||
}
|
||||
@@ -781,6 +783,22 @@ impl Connection {
|
||||
if let Some(message::Union::login_request(lr)) = msg.union {
|
||||
if let Some(o) = lr.option.as_ref() {
|
||||
self.update_option(o).await;
|
||||
if let Some(q) = o.video_codec_state.clone().take() {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::State(q),
|
||||
);
|
||||
} else {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||
);
|
||||
}
|
||||
self.video_ack_required = lr.video_ack_required;
|
||||
if self.authorized {
|
||||
@@ -887,10 +905,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 {
|
||||
@@ -1066,7 +1085,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 });
|
||||
@@ -1076,7 +1095,7 @@ impl Connection {
|
||||
}
|
||||
Some(misc::Union::refresh_video(r)) => {
|
||||
if r {
|
||||
super::video_service::refresh();
|
||||
video_service::refresh();
|
||||
}
|
||||
}
|
||||
Some(misc::Union::video_received(_)) => {
|
||||
@@ -1096,13 +1115,20 @@ 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 image_quality;
|
||||
if let ImageQuality::NotSet = q {
|
||||
if o.custom_image_quality > 0 {
|
||||
image_quality = o.custom_image_quality;
|
||||
} else {
|
||||
image_quality = ImageQuality::Balanced.value();
|
||||
}
|
||||
} else {
|
||||
image_quality = q.value();
|
||||
}
|
||||
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 {
|
||||
|
||||
219
src/server/video_qos.rs
Normal file
219
src/server/video_qos.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
const FPS: u8 = 30;
|
||||
trait Percent {
|
||||
fn as_percent(&self) -> u32;
|
||||
}
|
||||
|
||||
impl Percent for ImageQuality {
|
||||
fn as_percent(&self) -> u32 {
|
||||
match self {
|
||||
ImageQuality::NotSet => 0,
|
||||
ImageQuality::Low => 50,
|
||||
ImageQuality::Balanced => 66,
|
||||
ImageQuality::Best => 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: DelayState,
|
||||
debounce_count: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum DelayState {
|
||||
Normal = 0,
|
||||
LowDelay = 200,
|
||||
HighDelay = 500,
|
||||
Broken = 1000,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VideoQoS {
|
||||
fn default() -> Self {
|
||||
VideoQoS {
|
||||
fps: FPS,
|
||||
user_image_quality: ImageQuality::Balanced.as_percent(),
|
||||
current_image_quality: ImageQuality::Balanced.as_percent(),
|
||||
enable_abr: false,
|
||||
width: 0,
|
||||
height: 0,
|
||||
current_delay: 0,
|
||||
target_bitrate: 0,
|
||||
updated: false,
|
||||
state: DelayState::Normal,
|
||||
debounce_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Duration::from_secs_f32(1. / (self.fps as f32))
|
||||
}
|
||||
|
||||
// update_network_delay periodically
|
||||
// decrease the bitrate when the delay gets bigger
|
||||
pub fn update_network_delay(&mut self, delay: u32) {
|
||||
if self.current_delay.eq(&0) {
|
||||
self.current_delay = delay;
|
||||
return;
|
||||
}
|
||||
|
||||
self.current_delay = delay / 2 + self.current_delay / 2;
|
||||
log::trace!(
|
||||
"VideoQoS update_network_delay:{}, {}, state:{:?}",
|
||||
self.current_delay,
|
||||
delay,
|
||||
self.state,
|
||||
);
|
||||
|
||||
// ABR
|
||||
if !self.enable_abr {
|
||||
return;
|
||||
}
|
||||
let current_state = DelayState::from_delay(self.current_delay);
|
||||
if current_state != self.state && self.debounce_count > 5 {
|
||||
log::debug!(
|
||||
"VideoQoS state changed:{:?} -> {:?}",
|
||||
self.state,
|
||||
current_state
|
||||
);
|
||||
self.state = current_state;
|
||||
self.debounce_count = 0;
|
||||
self.refresh_quality();
|
||||
} else {
|
||||
self.debounce_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_quality(&mut self) {
|
||||
match self.state {
|
||||
DelayState::Normal => {
|
||||
self.fps = FPS;
|
||||
self.current_image_quality = self.user_image_quality;
|
||||
}
|
||||
DelayState::LowDelay => {
|
||||
self.fps = FPS;
|
||||
self.current_image_quality = std::cmp::min(self.user_image_quality, 50);
|
||||
}
|
||||
DelayState::HighDelay => {
|
||||
self.fps = FPS / 2;
|
||||
self.current_image_quality = std::cmp::min(self.user_image_quality, 25);
|
||||
}
|
||||
DelayState::Broken => {
|
||||
self.fps = FPS / 4;
|
||||
self.current_image_quality = 10;
|
||||
}
|
||||
}
|
||||
let _ = self.generate_bitrate().ok();
|
||||
self.updated = true;
|
||||
}
|
||||
|
||||
// handle image_quality change from peer
|
||||
pub fn update_image_quality(&mut self, image_quality: i32) {
|
||||
let image_quality = Self::convert_quality(image_quality) as _;
|
||||
log::debug!("VideoQoS update_image_quality: {}", image_quality);
|
||||
if self.current_image_quality != image_quality {
|
||||
self.current_image_quality = image_quality;
|
||||
let _ = self.generate_bitrate().ok();
|
||||
self.updated = true;
|
||||
}
|
||||
|
||||
self.user_image_quality = self.current_image_quality;
|
||||
}
|
||||
|
||||
pub fn generate_bitrate(&mut self) -> ResultType<u32> {
|
||||
// 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.as_percent();
|
||||
}
|
||||
|
||||
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.current_image_quality / 100;
|
||||
Ok(self.target_bitrate)
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
pub fn check_abr_config(&mut self) -> bool {
|
||||
self.enable_abr = if let Some(v) = Config2::get().options.get("enable-abr") {
|
||||
v != "N"
|
||||
} else {
|
||||
true // default is true
|
||||
};
|
||||
self.enable_abr
|
||||
}
|
||||
|
||||
pub fn convert_quality(q: i32) -> i32 {
|
||||
if q == ImageQuality::Balanced.value() {
|
||||
100 * 2 / 3
|
||||
} else if q == ImageQuality::Low.value() {
|
||||
100 / 2
|
||||
} else if q == ImageQuality::Best.value() {
|
||||
100
|
||||
} else {
|
||||
(q >> 8 & 0xFF) * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,16 @@
|
||||
// to-do:
|
||||
// https://slhck.info/video/2017/03/01/rate-control.html
|
||||
|
||||
use super::*;
|
||||
use super::{video_qos::VideoQoS, *};
|
||||
use hbb_common::tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
Mutex as TokioMutex,
|
||||
};
|
||||
use scrap::{Capturer, Config, Display, EncodeFrame, Encoder, Frame, VideoCodecId, STRIDE_ALIGN};
|
||||
use scrap::{
|
||||
codec::{Encoder, EncoderCfg, HwEncoderConfig},
|
||||
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
|
||||
Capturer, Display, Frame,
|
||||
};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
io::{ErrorKind::WouldBlock, Result},
|
||||
@@ -38,14 +42,13 @@ lazy_static::lazy_static! {
|
||||
static ref CURRENT_DISPLAY: Arc<Mutex<usize>> = Arc::new(Mutex::new(usize::MAX));
|
||||
static ref LAST_ACTIVE: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
|
||||
static ref SWITCH: Arc<Mutex<bool>> = Default::default();
|
||||
static ref TEST_LATENCIES: Arc<Mutex<HashMap<i32, i64>>> = Default::default();
|
||||
static ref IMAGE_QUALITIES: Arc<Mutex<HashMap<i32, i32>>> = Default::default();
|
||||
static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option<Instant>)>, Arc<TokioMutex<UnboundedReceiver<(i32, Option<Instant>)>>>) = {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
(tx, Arc::new(TokioMutex::new(rx)))
|
||||
};
|
||||
static ref PRIVACY_MODE_CONN_ID: Mutex<i32> = Mutex::new(0);
|
||||
static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported();
|
||||
pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
|
||||
}
|
||||
|
||||
fn is_capturer_mag_supported() -> bool {
|
||||
@@ -125,7 +128,7 @@ impl VideoFrameController {
|
||||
}
|
||||
|
||||
trait TraitCapturer {
|
||||
fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result<Frame<'a>>;
|
||||
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>>;
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_gdi(&self) -> bool;
|
||||
@@ -134,8 +137,8 @@ trait TraitCapturer {
|
||||
}
|
||||
|
||||
impl TraitCapturer for Capturer {
|
||||
fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result<Frame<'a>> {
|
||||
self.frame(timeout_ms)
|
||||
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>> {
|
||||
self.frame(timeout)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -151,7 +154,7 @@ impl TraitCapturer for Capturer {
|
||||
|
||||
#[cfg(windows)]
|
||||
impl TraitCapturer for scrap::CapturerMag {
|
||||
fn frame<'a>(&'a mut self, _timeout_ms: u32) -> Result<Frame<'a>> {
|
||||
fn frame<'a>(&'a mut self, _timeout_ms: Duration) -> Result<Frame<'a>> {
|
||||
self.frame(_timeout_ms)
|
||||
}
|
||||
|
||||
@@ -201,9 +204,11 @@ fn check_display_changed(
|
||||
}
|
||||
|
||||
// Capturer object is expensive, avoiding to create it frequently.
|
||||
fn create_capturer(privacy_mode_id: i32, display: Display) -> ResultType<Box<dyn TraitCapturer>> {
|
||||
let use_yuv = true;
|
||||
|
||||
fn create_capturer(
|
||||
privacy_mode_id: i32,
|
||||
display: Display,
|
||||
use_yuv: bool,
|
||||
) -> ResultType<Box<dyn TraitCapturer>> {
|
||||
#[cfg(not(windows))]
|
||||
let c: Option<Box<dyn TraitCapturer>> = None;
|
||||
#[cfg(windows)]
|
||||
@@ -292,7 +297,7 @@ pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
|
||||
let test_begin = Instant::now();
|
||||
while test_begin.elapsed().as_millis() < timeout_millis as _ {
|
||||
if let Ok((_, _, display)) = get_current_display() {
|
||||
if let Ok(_) = create_capturer(privacy_mode_id, display) {
|
||||
if let Ok(_) = create_capturer(privacy_mode_id, display, true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -320,9 +325,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!(
|
||||
@@ -336,6 +338,38 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
num_cpus::get(),
|
||||
);
|
||||
|
||||
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()?;
|
||||
let abr = video_qos.check_abr_config();
|
||||
drop(video_qos);
|
||||
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
|
||||
|
||||
let encoder_cfg = match Encoder::current_hw_encoder_name() {
|
||||
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
|
||||
codec_name,
|
||||
width,
|
||||
height,
|
||||
bitrate: bitrate as _,
|
||||
}),
|
||||
None => EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
timebase: [1, 1000], // Output timestamp precision
|
||||
bitrate,
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut encoder;
|
||||
match Encoder::new(encoder_cfg) {
|
||||
Ok(x) => encoder = x,
|
||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||
}
|
||||
|
||||
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||
#[cfg(not(windows))]
|
||||
let captuerer_privacy_mode_id = privacy_mode_id;
|
||||
@@ -355,26 +389,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
} else {
|
||||
log::info!("In privacy mode, the peer side cannot watch the screen");
|
||||
}
|
||||
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 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,
|
||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||
}
|
||||
let mut c = create_capturer(captuerer_privacy_mode_id, display, encoder.use_yuv())?;
|
||||
|
||||
if *SWITCH.lock().unwrap() {
|
||||
log::debug!("Broadcasting display switch");
|
||||
@@ -401,10 +416,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
|
||||
);
|
||||
encoder.set_bitrate(video_qos.target_bitrate).unwrap();
|
||||
spf = video_qos.spf();
|
||||
}
|
||||
}
|
||||
|
||||
if *SWITCH.lock().unwrap() {
|
||||
bail!("SWITCH");
|
||||
}
|
||||
@@ -413,9 +442,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 +463,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;
|
||||
@@ -448,7 +474,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
}
|
||||
scrap::Frame::RAW(data) => {
|
||||
if (data.len() != 0) {
|
||||
let send_conn_ids = handle_one_frame(&sp, data, ms, &mut vpx)?;
|
||||
let send_conn_ids = handle_one_frame(&sp, data, ms, &mut encoder)?;
|
||||
frame_controller.set_send(now, send_conn_ids);
|
||||
}
|
||||
}
|
||||
@@ -460,11 +486,11 @@ 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;
|
||||
let send_conn_ids = handle_one_frame(&sp, &frame, ms, &mut vpx)?;
|
||||
let send_conn_ids = handle_one_frame(&sp, &frame, ms, &mut encoder)?;
|
||||
frame_controller.set_send(now, send_conn_ids);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -531,7 +557,6 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> {
|
||||
let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||
if privacy_mode_id != privacy_mode_id_2 {
|
||||
@@ -547,10 +572,11 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn create_msg(vp9s: Vec<VP9>) -> Message {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
fn create_msg(vp9s: Vec<EncodedVideoFrame>) -> Message {
|
||||
let mut msg_out = Message::new();
|
||||
let mut vf = VideoFrame::new();
|
||||
vf.set_vp9s(VP9s {
|
||||
vf.set_vp9s(EncodedVideoFrames {
|
||||
frames: vp9s.into(),
|
||||
..Default::default()
|
||||
});
|
||||
@@ -559,22 +585,12 @@ fn create_msg(vp9s: Vec<VP9>) -> Message {
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn create_frame(frame: &EncodeFrame) -> VP9 {
|
||||
VP9 {
|
||||
data: frame.data.to_vec(),
|
||||
key: frame.key,
|
||||
pts: frame.pts,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle_one_frame(
|
||||
sp: &GenericService,
|
||||
frame: &[u8],
|
||||
ms: i64,
|
||||
vpx: &mut Encoder,
|
||||
encoder: &mut Encoder,
|
||||
) -> ResultType<HashSet<i32>> {
|
||||
sp.snapshot(|sps| {
|
||||
// so that new sub and old sub share the same encoder after switch
|
||||
@@ -585,20 +601,8 @@ fn handle_one_frame(
|
||||
})?;
|
||||
|
||||
let mut send_conn_ids: HashSet<i32> = Default::default();
|
||||
let mut frames = Vec::new();
|
||||
for ref frame in vpx
|
||||
.encode(ms, frame, STRIDE_ALIGN)
|
||||
.with_context(|| "Failed to encode")?
|
||||
{
|
||||
frames.push(create_frame(frame));
|
||||
}
|
||||
for ref frame in vpx.flush().with_context(|| "Failed to flush")? {
|
||||
frames.push(create_frame(frame));
|
||||
}
|
||||
|
||||
// to-do: flush periodically, e.g. 1 second
|
||||
if frames.len() > 0 {
|
||||
send_conn_ids = sp.send_video_frame(create_msg(frames));
|
||||
if let Ok(msg) = encoder.encode_to_message(frame, ms) {
|
||||
send_conn_ids = sp.send_video_frame(msg);
|
||||
}
|
||||
Ok(send_conn_ids)
|
||||
}
|
||||
@@ -618,7 +622,7 @@ pub fn handle_one_frame_encoded(
|
||||
Ok(())
|
||||
})?;
|
||||
let mut send_conn_ids: HashSet<i32> = Default::default();
|
||||
let vp9_frame = VP9 {
|
||||
let vp9_frame = EncodedVideoFrame {
|
||||
data: frame.to_vec(),
|
||||
key: true,
|
||||
pts: ms,
|
||||
@@ -748,82 +752,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<i32, i64>) {
|
||||
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<i32>) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -754,6 +754,13 @@ impl UI {
|
||||
)
|
||||
}
|
||||
|
||||
fn has_hwcodec(&self) -> bool {
|
||||
#[cfg(not(feature = "hwcodec"))]
|
||||
return false;
|
||||
#[cfg(feature = "hwcodec")]
|
||||
return true;
|
||||
}
|
||||
|
||||
fn get_langs(&self) -> String {
|
||||
crate::lang::LANGS.to_string()
|
||||
}
|
||||
@@ -833,6 +840,7 @@ impl sciter::EventHandler for UI {
|
||||
fn discover();
|
||||
fn get_lan_peers();
|
||||
fn get_uuid();
|
||||
fn has_hwcodec();
|
||||
fn get_langs();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +159,7 @@ class Header: Reactor.Component {
|
||||
<li #custom type="image-quality"><span>{svg_checkmark}</span>{translate('Custom')}</li>
|
||||
<div .separator />
|
||||
<li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>
|
||||
<li #show-quality-monitor .toggle-option><span>{svg_checkmark}</span>{translate('Show quality monitor')}</li>
|
||||
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>{translate('Mute')}</li> : ""}
|
||||
{is_win && pi.platform == 'Windows' && file_enabled ? <li #enable-file-transfer .toggle-option><span>{svg_checkmark}</span>{translate('Allow file copy and paste')}</li> : ""}
|
||||
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
|
||||
@@ -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")) {
|
||||
@@ -333,15 +336,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 = (tmp[0] || 50);
|
||||
msgbox("custom", "Custom Image Quality", "<div .form> \
|
||||
<div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate0 + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% bitrate</div> \
|
||||
<div><input type=\"hslider\" style=\"width: 50%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
|
||||
<div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% Bitrate</div> \
|
||||
</div>", 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);
|
||||
toggleMenuState();
|
||||
});
|
||||
}
|
||||
@@ -357,7 +358,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 +426,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");
|
||||
|
||||
@@ -199,6 +199,42 @@ class Languages: Reactor.Component {
|
||||
}
|
||||
}
|
||||
|
||||
var enhancementsMenu;
|
||||
class Enhancements: Reactor.Component {
|
||||
function this() {
|
||||
enhancementsMenu = this;
|
||||
}
|
||||
|
||||
function render() {
|
||||
var has_hwcodec = handler.has_hwcodec();
|
||||
var me = this;
|
||||
self.timer(1ms, function() { me.toggleMenuState() });
|
||||
return <li>{translate('Enhancements')}
|
||||
<menu #enhancements-menu>
|
||||
{has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")}{"(beta)"}</li> : ""}
|
||||
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive Bitrate")}{"(beta)"}</li>
|
||||
</menu>
|
||||
</li>;
|
||||
}
|
||||
|
||||
function toggleMenuState() {
|
||||
for (var el in $$(menu#enhancements-menu>li)) {
|
||||
if (el.id && el.id.indexOf("enable-") == 0) {
|
||||
var enabled = handler.get_option(el.id) != "N";
|
||||
el.attributes.toggleClass("selected", enabled);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
event click $(menu#enhancements-menu>li) (_, me) {
|
||||
var v = me.id;
|
||||
if (v.indexOf("enable-") == 0) {
|
||||
handler.set_option(v, handler.get_option(v) != 'N' ? 'N' : '');
|
||||
}
|
||||
this.toggleMenuState();
|
||||
}
|
||||
}
|
||||
|
||||
function getUserName() {
|
||||
try {
|
||||
@@ -239,6 +275,7 @@ class MyIdMenu: Reactor.Component {
|
||||
<li #enable-file-transfer><span>{svg_checkmark}</span>{translate('Enable File Transfer')}</li>
|
||||
<li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
|
||||
<AudioInputs />
|
||||
<Enhancements />
|
||||
<li #allow-remote-config-modification><span>{svg_checkmark}</span>{translate('Enable remote configuration modification')}</li>
|
||||
<div .separator />
|
||||
<li #custom-server>{translate('ID/Relay Server')}</li>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
<html window-resizable window-frame="extended">
|
||||
<head>
|
||||
<style>
|
||||
@import url(common.css);
|
||||
@import url(remote.css);
|
||||
@import url(file_transfer.css);
|
||||
@import url(header.css);
|
||||
</style>
|
||||
<script type="text/tiscript">
|
||||
|
||||
<head>
|
||||
<style>
|
||||
@import url(common.css);
|
||||
@import url(remote.css);
|
||||
@import url(file_transfer.css);
|
||||
@import url(header.css);
|
||||
</style>
|
||||
<script type="text/tiscript">
|
||||
include "common.tis";
|
||||
include "msgbox.tis";
|
||||
include "remote.tis";
|
||||
@@ -15,23 +16,28 @@
|
||||
include "grid.tis";
|
||||
include "header.tis";
|
||||
</script>
|
||||
</head>
|
||||
<header>
|
||||
<div.window-icon role="window-icon"><icon /></div>
|
||||
</head>
|
||||
<header>
|
||||
<div.window-icon role="window-icon">
|
||||
<icon />
|
||||
</div>
|
||||
<caption role="window-caption" />
|
||||
<div.window-toolbar />
|
||||
<div.window-buttons />
|
||||
</header>
|
||||
<body>
|
||||
<div #video-wrapper>
|
||||
<video #handler>
|
||||
<div style="position: relative">
|
||||
<img #cursor src="in-memory:cursor" />
|
||||
</div>
|
||||
</video>
|
||||
</div>
|
||||
<div #file-transfer-wrapper>
|
||||
</div>
|
||||
<div #msgbox />
|
||||
</body>
|
||||
</html>
|
||||
</header>
|
||||
|
||||
<body>
|
||||
<div #video-wrapper>
|
||||
<video #handler>
|
||||
<div #quality-monitor style="position: absolute; display: none" />
|
||||
<div style="position: relative">
|
||||
<img #cursor src="in-memory:cursor" />
|
||||
</div>
|
||||
</video>
|
||||
</div>
|
||||
<div #file-transfer-wrapper>
|
||||
</div>
|
||||
<div #msgbox />
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -2,7 +2,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
ops::Deref,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
};
|
||||
@@ -223,7 +223,7 @@ impl sciter::EventHandler for Handler {
|
||||
fn get_custom_image_quality();
|
||||
fn save_view_style(String);
|
||||
fn save_image_quality(String);
|
||||
fn save_custom_image_quality(i32, i32);
|
||||
fn save_custom_image_quality(i32);
|
||||
fn refresh_video();
|
||||
fn get_toggle_option(String);
|
||||
fn is_privacy_mode_supported();
|
||||
@@ -234,6 +234,15 @@ impl sciter::EventHandler for Handler {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct QualityStatus {
|
||||
speed: Option<String>,
|
||||
fps: Option<i32>,
|
||||
delay: Option<i32>,
|
||||
target_bitrate: Option<i32>,
|
||||
codec_format: Option<CodecFormat>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub fn new(cmd: String, id: String, args: Vec<String>) -> Self {
|
||||
let me = Self {
|
||||
@@ -249,6 +258,21 @@ impl Handler {
|
||||
me
|
||||
}
|
||||
|
||||
fn update_quality_status(&self, status: QualityStatus) {
|
||||
self.call2(
|
||||
"updateQualityStatus",
|
||||
&make_args!(
|
||||
status.speed.map_or(Value::null(), |it| it.into()),
|
||||
status.fps.map_or(Value::null(), |it| it.into()),
|
||||
status.delay.map_or(Value::null(), |it| it.into()),
|
||||
status.target_bitrate.map_or(Value::null(), |it| it.into()),
|
||||
status
|
||||
.codec_format
|
||||
.map_or(Value::null(), |it| it.to_string().into())
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn start_keyboard_hook(&self) {
|
||||
if self.is_port_forward() || self.is_file_transfer() {
|
||||
return;
|
||||
@@ -533,12 +557,12 @@ impl Handler {
|
||||
self.send(Data::Message(LoginConfigHandler::refresh()));
|
||||
}
|
||||
|
||||
fn save_custom_image_quality(&mut self, bitrate: i32, quantizer: i32) {
|
||||
fn save_custom_image_quality(&mut self, custom_image_quality: i32) {
|
||||
let msg = self
|
||||
.lc
|
||||
.write()
|
||||
.unwrap()
|
||||
.save_custom_image_quality(bitrate, quantizer);
|
||||
.save_custom_image_quality(custom_image_quality);
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
@@ -1296,7 +1320,10 @@ async fn io_loop(handler: Handler) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
let (video_sender, audio_sender) = start_video_audio_threads(|data: &[u8]| {
|
||||
let frame_count = Arc::new(AtomicUsize::new(0));
|
||||
let frame_count_cl = frame_count.clone();
|
||||
let (video_sender, audio_sender) = start_video_audio_threads(move |data: &[u8]| {
|
||||
frame_count_cl.fetch_add(1, Ordering::Relaxed);
|
||||
VIDEO
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -1319,6 +1346,9 @@ async fn io_loop(handler: Handler) {
|
||||
first_frame: false,
|
||||
#[cfg(windows)]
|
||||
clipboard_file_context: None,
|
||||
data_count: Arc::new(AtomicUsize::new(0)),
|
||||
frame_count,
|
||||
video_format: CodecFormat::Unknown,
|
||||
};
|
||||
remote.io_loop(&key, &token).await;
|
||||
remote.sync_jobs_status_to_local().await;
|
||||
@@ -1369,6 +1399,9 @@ struct Remote {
|
||||
first_frame: bool,
|
||||
#[cfg(windows)]
|
||||
clipboard_file_context: Option<Box<CliprdrClientContext>>,
|
||||
data_count: Arc<AtomicUsize>,
|
||||
frame_count: Arc<AtomicUsize>,
|
||||
video_format: CodecFormat,
|
||||
}
|
||||
|
||||
impl Remote {
|
||||
@@ -1394,6 +1427,8 @@ impl Remote {
|
||||
#[cfg(windows)]
|
||||
let mut rx_clip_client = get_rx_clip_client().lock().await;
|
||||
|
||||
let mut status_timer = time::interval(Duration::new(1, 0));
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = peer.next() => {
|
||||
@@ -1406,6 +1441,7 @@ impl Remote {
|
||||
}
|
||||
Ok(ref bytes) => {
|
||||
last_recv_time = Instant::now();
|
||||
self.data_count.fetch_add(bytes.len(), Ordering::Relaxed);
|
||||
if !self.handle_msg_from_peer(bytes, &mut peer).await {
|
||||
break
|
||||
}
|
||||
@@ -1450,6 +1486,16 @@ impl Remote {
|
||||
self.timer = time::interval_at(Instant::now() + SEC30, SEC30);
|
||||
}
|
||||
}
|
||||
_ = status_timer.tick() => {
|
||||
let speed = self.data_count.swap(0, Ordering::Relaxed);
|
||||
let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
|
||||
let fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
|
||||
self.handler.update_quality_status(QualityStatus {
|
||||
speed:Some(speed),
|
||||
fps:Some(fps),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("Exit io_loop of id={}", self.handler.id);
|
||||
@@ -1977,6 +2023,14 @@ impl Remote {
|
||||
self.handler.call2("closeSuccess", &make_args!());
|
||||
self.handler.call("adaptSize", &make_args!());
|
||||
}
|
||||
let incomming_format = CodecFormat::from(&vf);
|
||||
if self.video_format != incomming_format {
|
||||
self.video_format = incomming_format.clone();
|
||||
self.handler.update_quality_status(QualityStatus {
|
||||
codec_format: Some(incomming_format),
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
self.video_sender.send(MediaData::VideoFrame(vf)).ok();
|
||||
}
|
||||
Some(message::Union::hash(hash)) => {
|
||||
@@ -2549,7 +2603,14 @@ impl Interface for Handler {
|
||||
}
|
||||
|
||||
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {
|
||||
handle_test_delay(t, peer).await;
|
||||
if !t.from_client {
|
||||
self.update_quality_status(QualityStatus {
|
||||
delay: Some(t.last_delay as _),
|
||||
target_bitrate: Some(t.target_bitrate as _),
|
||||
..Default::default()
|
||||
});
|
||||
handle_test_delay(t, peer).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -456,6 +456,49 @@ function self.closing() {
|
||||
if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h);
|
||||
}
|
||||
|
||||
var qualityMonitor;
|
||||
var qualityMonitorData = [];
|
||||
|
||||
class QualityMonitor: Reactor.Component
|
||||
{
|
||||
function this() {
|
||||
qualityMonitor = this;
|
||||
if (handler.get_toggle_option("show-quality-monitor")) {
|
||||
$(#quality-monitor).style.set{ display: "block" };
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
return <div >
|
||||
<div>
|
||||
Speed: {qualityMonitorData[0]}
|
||||
</div>
|
||||
<div>
|
||||
FPS: {qualityMonitorData[1]}
|
||||
</div>
|
||||
<div>
|
||||
Delay: {qualityMonitorData[2]} ms
|
||||
</div>
|
||||
<div>
|
||||
Target Bitrate: {qualityMonitorData[3]}kb
|
||||
</div>
|
||||
<div>
|
||||
Codec: {qualityMonitorData[4]}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
$(#quality-monitor).content(<QualityMonitor />);
|
||||
handler.updateQualityStatus = function(speed, fps, delay, bitrate, codec_format) {
|
||||
speed ? qualityMonitorData[0] = speed:null;
|
||||
fps ? qualityMonitorData[1] = fps:null;
|
||||
delay ? qualityMonitorData[2] = delay:null;
|
||||
bitrate ? qualityMonitorData[3] = bitrate:null;
|
||||
codec_format ? qualityMonitorData[4] = codec_format:null;
|
||||
qualityMonitor.update();
|
||||
}
|
||||
|
||||
handler.setPermission = function(name, enabled) {
|
||||
self.timer(60ms, function() {
|
||||
if (name == "keyboard") keyboard_enabled = enabled;
|
||||
|
||||
Reference in New Issue
Block a user