Merge branch 'master' of github.com:asur4s/rustdesk

This commit is contained in:
Asura
2022-07-11 08:17:17 -07:00
52 changed files with 2351 additions and 865 deletions

View File

@@ -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 {

View File

@@ -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(),
}
}
}

View File

@@ -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
);
}
_ => {}
}
}

View File

@@ -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", "插入"),

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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();
}

View File

@@ -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ť"),

View File

@@ -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", ""),

View File

@@ -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"),

View File

@@ -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", "插入"),

View File

@@ -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[..]);

View File

@@ -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(())

View File

@@ -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();

View File

@@ -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
View 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
}
}
}

View File

@@ -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)
}

View File

@@ -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();
}
}

View File

@@ -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");

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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;