mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge remote-tracking branch 'origin/master' into feat/x11/clipboard-file/init
Signed-off-by: 蔡略 <cailue@bupt.edu.cn>
This commit is contained in:
@@ -610,12 +610,15 @@ fn ret_to_result(ret: u32) -> Result<(), CliprdrError> {
|
||||
e => Err(CliprdrError::Unknown(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_clipboard(context: &mut CliprdrClientContext, conn_id: i32) -> bool {
|
||||
unsafe { TRUE == empty_cliprdr(context, conn_id as u32) }
|
||||
pub fn empty_clipboard(context: &mut CliprdrClientContext, conn_id: i32) -> bool {
|
||||
unsafe { TRUE == cliprdr::empty_cliprdr(context, conn_id as u32) }
|
||||
}
|
||||
|
||||
fn server_clip_file(context: &mut CliprdrClientContext, conn_id: i32, msg: ClipboardFile) -> u32 {
|
||||
pub fn server_clip_file(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
msg: ClipboardFile,
|
||||
) -> u32 {
|
||||
let mut ret = 0;
|
||||
match msg {
|
||||
ClipboardFile::NotifyCallback { .. } => {
|
||||
@@ -737,7 +740,7 @@ fn server_clip_file(context: &mut CliprdrClientContext, conn_id: i32, msg: Clipb
|
||||
ret
|
||||
}
|
||||
|
||||
fn server_monitor_ready(context: &mut CliprdrClientContext, conn_id: i32) -> u32 {
|
||||
pub fn server_monitor_ready(context: &mut CliprdrClientContext, conn_id: i32) -> u32 {
|
||||
unsafe {
|
||||
let monitor_ready = CLIPRDR_MONITOR_READY {
|
||||
connID: conn_id as UINT32,
|
||||
@@ -754,7 +757,7 @@ fn server_monitor_ready(context: &mut CliprdrClientContext, conn_id: i32) -> u32
|
||||
}
|
||||
}
|
||||
|
||||
fn server_format_list(
|
||||
pub fn server_format_list(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
format_list: Vec<(i32, String)>,
|
||||
@@ -808,7 +811,7 @@ fn server_format_list(
|
||||
}
|
||||
}
|
||||
|
||||
fn server_format_list_response(
|
||||
pub fn server_format_list_response(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
msg_flags: i32,
|
||||
@@ -829,7 +832,7 @@ fn server_format_list_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn server_format_data_request(
|
||||
pub fn server_format_data_request(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
requested_format_id: i32,
|
||||
@@ -850,7 +853,7 @@ fn server_format_data_request(
|
||||
}
|
||||
}
|
||||
|
||||
fn server_format_data_response(
|
||||
pub fn server_format_data_response(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
msg_flags: i32,
|
||||
@@ -872,7 +875,7 @@ fn server_format_data_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn server_file_contents_request(
|
||||
pub fn server_file_contents_request(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
stream_id: i32,
|
||||
@@ -907,7 +910,7 @@ fn server_file_contents_request(
|
||||
}
|
||||
}
|
||||
|
||||
fn server_file_contents_response(
|
||||
pub fn server_file_contents_response(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
msg_flags: i32,
|
||||
@@ -932,12 +935,12 @@ fn server_file_contents_response(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn create_cliprdr_context(
|
||||
pub fn create_cliprdr_context(
|
||||
enable_files: bool,
|
||||
enable_others: bool,
|
||||
response_wait_timeout_secs: u32,
|
||||
) -> ResultType<Box<dyn CliprdrServiceContext>> {
|
||||
let boxed = CliprdrClientContext::create(
|
||||
) -> ResultType<Box<CliprdrClientContext>> {
|
||||
Ok(CliprdrClientContext::create(
|
||||
enable_files,
|
||||
enable_others,
|
||||
response_wait_timeout_secs,
|
||||
@@ -948,9 +951,7 @@ pub(super) fn create_cliprdr_context(
|
||||
Some(client_format_data_response),
|
||||
Some(client_file_contents_request),
|
||||
Some(client_file_contents_response),
|
||||
)? as Box<dyn CliprdrServiceContext>;
|
||||
|
||||
Ok(boxed)
|
||||
)?)
|
||||
}
|
||||
|
||||
extern "C" fn notify_callback(conn_id: UINT32, msg: *const NOTIFICATION_MESSAGE) -> UINT {
|
||||
|
||||
@@ -24,6 +24,7 @@ directories-next = "2.0"
|
||||
rand = "0.8"
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
lazy_static = "1.4"
|
||||
confy = { git = "https://github.com/open-trade/confy" }
|
||||
dirs-next = "2.0"
|
||||
@@ -37,11 +38,11 @@ libc = "0.2"
|
||||
dlopen = "0.1"
|
||||
toml = "0.7"
|
||||
uuid = { version = "1.3", features = ["v4"] }
|
||||
sysinfo = "0.29"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
mac_address = "1.1"
|
||||
machine-uid = "0.3"
|
||||
sysinfo = "0.29"
|
||||
machine-uid = { git = "https://github.com/21pages/machine-uid" }
|
||||
|
||||
[features]
|
||||
quic = []
|
||||
@@ -56,5 +57,3 @@ winapi = { version = "0.3", features = ["winuser"] }
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
osascript = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -97,7 +97,6 @@ message PeerInfo {
|
||||
int32 current_display = 5;
|
||||
bool sas_enabled = 6;
|
||||
string version = 7;
|
||||
int32 conn_id = 8;
|
||||
Features features = 9;
|
||||
SupportedEncoding encoding = 10;
|
||||
SupportedResolutions resolutions = 11;
|
||||
@@ -112,6 +111,46 @@ message LoginResponse {
|
||||
}
|
||||
}
|
||||
|
||||
message TouchScaleUpdate {
|
||||
// The delta scale factor relative to the previous scale.
|
||||
// delta * 1000
|
||||
// 0 means scale end
|
||||
int32 scale = 1;
|
||||
}
|
||||
|
||||
message TouchPanStart {
|
||||
int32 x = 1;
|
||||
int32 y = 2;
|
||||
}
|
||||
|
||||
message TouchPanUpdate {
|
||||
// The delta x position relative to the previous position.
|
||||
int32 x = 1;
|
||||
// The delta y position relative to the previous position.
|
||||
int32 y = 2;
|
||||
}
|
||||
|
||||
message TouchPanEnd {
|
||||
int32 x = 1;
|
||||
int32 y = 2;
|
||||
}
|
||||
|
||||
message TouchEvent {
|
||||
oneof union {
|
||||
TouchScaleUpdate scale_update = 1;
|
||||
TouchPanStart pan_start = 2;
|
||||
TouchPanUpdate pan_update = 3;
|
||||
TouchPanEnd pan_end = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message PointerDeviceEvent {
|
||||
oneof union {
|
||||
TouchEvent touch_event = 1;
|
||||
}
|
||||
repeated ControlKey modifiers = 2;
|
||||
}
|
||||
|
||||
message MouseEvent {
|
||||
int32 mask = 1;
|
||||
sint32 x = 2;
|
||||
@@ -358,6 +397,7 @@ message FileTransferReceiveRequest {
|
||||
string path = 2; // path written to
|
||||
repeated FileEntry files = 3;
|
||||
int32 file_num = 4;
|
||||
uint64 total_size = 5;
|
||||
}
|
||||
|
||||
message FileRemoveDir {
|
||||
@@ -643,6 +683,9 @@ message Misc {
|
||||
Resolution change_resolution = 24;
|
||||
PluginRequest plugin_request = 25;
|
||||
PluginFailure plugin_failure = 26;
|
||||
uint32 full_speed_fps = 27;
|
||||
uint32 auto_adjust_fps = 28;
|
||||
bool client_record_status = 29;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,5 +726,6 @@ message Message {
|
||||
VoiceCallRequest voice_call_request = 23;
|
||||
VoiceCallResponse voice_call_response = 24;
|
||||
PeerInfo peer_info = 25;
|
||||
PointerDeviceEvent pointer_device_event = 26;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
io::{Read, Write},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
ops::{Deref, DerefMut},
|
||||
path::{Path, PathBuf},
|
||||
@@ -13,14 +14,16 @@ use rand::Rng;
|
||||
use regex::Regex;
|
||||
use serde as de;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use sodiumoxide::base64;
|
||||
use sodiumoxide::crypto::sign;
|
||||
|
||||
use crate::{
|
||||
compress::{compress, decompress},
|
||||
log,
|
||||
password_security::{
|
||||
decrypt_str_or_original, decrypt_vec_or_original, encrypt_str_or_original,
|
||||
encrypt_vec_or_original,
|
||||
encrypt_vec_or_original, symmetric_crypt,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,6 +34,11 @@ pub const REG_INTERVAL: i64 = 12_000;
|
||||
pub const COMPRESS_LEVEL: i32 = 3;
|
||||
const SERIAL: i32 = 3;
|
||||
const PASSWORD_ENC_VERSION: &str = "00";
|
||||
const ENCRYPT_MAX_LEN: usize = 128;
|
||||
|
||||
// config2 options
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const CONFIG_OPTION_ALLOW_LINUX_HEADLESS: &str = "allow-linux-headless";
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
lazy_static::lazy_static! {
|
||||
@@ -49,10 +57,11 @@ lazy_static::lazy_static! {
|
||||
Some(key) if !key.is_empty() => key,
|
||||
_ => "",
|
||||
}.to_owned()));
|
||||
pub static ref EXE_RENDEZVOUS_SERVER: Arc<RwLock<String>> = Default::default();
|
||||
pub static ref APP_NAME: Arc<RwLock<String>> = Arc::new(RwLock::new("RustDesk".to_owned()));
|
||||
static ref KEY_PAIR: Arc<Mutex<Option<KeyPair>>> = Default::default();
|
||||
static ref HW_CODEC_CONFIG: Arc<RwLock<HwCodecConfig>> = Arc::new(RwLock::new(HwCodecConfig::load()));
|
||||
static ref USER_DEFAULT_CONFIG: Arc<RwLock<(UserDefaultConfig, Instant)>> = Arc::new(RwLock::new((UserDefaultConfig::load(), Instant::now())));
|
||||
pub static ref NEW_STORED_PEER_CONFIG: Arc<Mutex<HashSet<String>>> = Default::default();
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -81,11 +90,7 @@ const CHARS: &[char] = &[
|
||||
'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
];
|
||||
|
||||
pub const RENDEZVOUS_SERVERS: &[&str] = &[
|
||||
"rs-ny.rustdesk.com",
|
||||
"rs-sg.rustdesk.com",
|
||||
"rs-cn.rustdesk.com",
|
||||
];
|
||||
pub const RENDEZVOUS_SERVERS: &[&str] = &["rs-ny.rustdesk.com"];
|
||||
|
||||
pub const RS_PUB_KEY: &str = match option_env!("RS_PUB_KEY") {
|
||||
Some(key) if !key.is_empty() => key,
|
||||
@@ -209,7 +214,7 @@ pub struct Resolution {
|
||||
pub h: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct PeerConfig {
|
||||
#[serde(default, deserialize_with = "deserialize_vec_u8")]
|
||||
pub password: Vec<u8>,
|
||||
@@ -291,6 +296,38 @@ pub struct PeerConfig {
|
||||
pub transfer: TransferSerde,
|
||||
}
|
||||
|
||||
impl Default for PeerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
password: Default::default(),
|
||||
size: Default::default(),
|
||||
size_ft: Default::default(),
|
||||
size_pf: Default::default(),
|
||||
view_style: Self::default_view_style(),
|
||||
scroll_style: Self::default_scroll_style(),
|
||||
image_quality: Self::default_image_quality(),
|
||||
custom_image_quality: Self::default_custom_image_quality(),
|
||||
show_remote_cursor: Default::default(),
|
||||
lock_after_session_end: Default::default(),
|
||||
privacy_mode: Default::default(),
|
||||
allow_swap_key: Default::default(),
|
||||
port_forwards: Default::default(),
|
||||
direct_failures: Default::default(),
|
||||
disable_audio: Default::default(),
|
||||
disable_clipboard: Default::default(),
|
||||
enable_file_transfer: Default::default(),
|
||||
show_quality_monitor: Default::default(),
|
||||
keyboard_mode: Default::default(),
|
||||
view_only: Default::default(),
|
||||
custom_resolutions: Default::default(),
|
||||
options: Self::default_options(),
|
||||
ui_flutter: Default::default(),
|
||||
info: Default::default(),
|
||||
transfer: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct PeerInfoSerde {
|
||||
#[serde(default, deserialize_with = "deserialize_string")]
|
||||
@@ -310,7 +347,7 @@ pub struct TransferSerde {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_online_statue() -> i64 {
|
||||
pub fn get_online_state() -> i64 {
|
||||
*ONLINE.lock().unwrap().values().max().unwrap_or(&0)
|
||||
}
|
||||
|
||||
@@ -376,7 +413,8 @@ impl Config2 {
|
||||
fn store(&self) {
|
||||
let mut config = self.clone();
|
||||
if let Some(mut socks) = config.socks {
|
||||
socks.password = encrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION);
|
||||
socks.password =
|
||||
encrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN);
|
||||
config.socks = Some(socks);
|
||||
}
|
||||
Config::store_(&config, "2");
|
||||
@@ -400,10 +438,15 @@ impl Config2 {
|
||||
pub fn load_path<T: serde::Serialize + serde::de::DeserializeOwned + Default + std::fmt::Debug>(
|
||||
file: PathBuf,
|
||||
) -> T {
|
||||
let cfg = match confy::load_path(file) {
|
||||
let cfg = match confy::load_path(&file) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
log::error!("Failed to load config: {}", err);
|
||||
if let confy::ConfyError::GeneralLoadError(err) = &err {
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
return T::default();
|
||||
}
|
||||
}
|
||||
log::error!("Failed to load config '{}': {}", file.display(), err);
|
||||
T::default()
|
||||
}
|
||||
};
|
||||
@@ -480,8 +523,9 @@ impl Config {
|
||||
|
||||
fn store(&self) {
|
||||
let mut config = self.clone();
|
||||
config.password = encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
config.enc_id = encrypt_str_or_original(&config.id, PASSWORD_ENC_VERSION);
|
||||
config.password =
|
||||
encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN);
|
||||
config.enc_id = encrypt_str_or_original(&config.id, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN);
|
||||
config.id = "".to_owned();
|
||||
Config::store_(&config, "");
|
||||
}
|
||||
@@ -608,7 +652,10 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn get_rendezvous_server() -> String {
|
||||
let mut rendezvous_server = Self::get_option("custom-rendezvous-server");
|
||||
let mut rendezvous_server = EXE_RENDEZVOUS_SERVER.read().unwrap().clone();
|
||||
if rendezvous_server.is_empty() {
|
||||
rendezvous_server = Self::get_option("custom-rendezvous-server");
|
||||
}
|
||||
if rendezvous_server.is_empty() {
|
||||
rendezvous_server = PROD_RENDEZVOUS_SERVER.read().unwrap().clone();
|
||||
}
|
||||
@@ -628,6 +675,10 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn get_rendezvous_servers() -> Vec<String> {
|
||||
let s = EXE_RENDEZVOUS_SERVER.read().unwrap().clone();
|
||||
if !s.is_empty() {
|
||||
return vec![s];
|
||||
}
|
||||
let s = Self::get_option("custom-rendezvous-server");
|
||||
if !s.is_empty() {
|
||||
return vec![s];
|
||||
@@ -959,7 +1010,12 @@ impl PeerConfig {
|
||||
config
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to load config: {}", err);
|
||||
if let confy::ConfyError::GeneralLoadError(err) = &err {
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
return Default::default();
|
||||
}
|
||||
}
|
||||
log::error!("Failed to load peer config '{}': {}", id, err);
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
@@ -968,15 +1024,17 @@ impl PeerConfig {
|
||||
pub fn store(&self, id: &str) {
|
||||
let _lock = CONFIG.read().unwrap();
|
||||
let mut config = self.clone();
|
||||
config.password = encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
config.password =
|
||||
encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN);
|
||||
for opt in ["rdp_password", "os-username", "os-password"] {
|
||||
if let Some(v) = config.options.get_mut(opt) {
|
||||
*v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION)
|
||||
*v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN)
|
||||
}
|
||||
}
|
||||
if let Err(err) = store_path(Self::path(id), config) {
|
||||
log::error!("Failed to store config: {}", err);
|
||||
}
|
||||
NEW_STORED_PEER_CONFIG.lock().unwrap().insert(id.to_owned());
|
||||
}
|
||||
|
||||
pub fn remove(id: &str) {
|
||||
@@ -1002,7 +1060,7 @@ impl PeerConfig {
|
||||
Config::with_extension(Config::path(path))
|
||||
}
|
||||
|
||||
pub fn peers() -> Vec<(String, SystemTime, PeerConfig)> {
|
||||
pub fn peers(id_filters: Option<Vec<String>>) -> Vec<(String, SystemTime, PeerConfig)> {
|
||||
if let Ok(peers) = Config::path(PEERS).read_dir() {
|
||||
if let Ok(peers) = peers
|
||||
.map(|res| res.map(|e| e.path()))
|
||||
@@ -1015,7 +1073,6 @@ impl PeerConfig {
|
||||
&& p.extension().map(|p| p.to_str().unwrap_or("")) == Some("toml")
|
||||
})
|
||||
.map(|p| {
|
||||
let t = crate::get_modified_time(p);
|
||||
let id = p
|
||||
.file_stem()
|
||||
.map(|p| p.to_str().unwrap_or(""))
|
||||
@@ -1029,12 +1086,21 @@ impl PeerConfig {
|
||||
} else {
|
||||
id
|
||||
};
|
||||
|
||||
let c = PeerConfig::load(&id_decoded_string);
|
||||
(id_decoded_string, p)
|
||||
})
|
||||
.filter(|(id, _)| {
|
||||
let Some(filters) = &id_filters else {
|
||||
return true;
|
||||
};
|
||||
filters.contains(id)
|
||||
})
|
||||
.map(|(id, p)| {
|
||||
let t = crate::get_modified_time(p);
|
||||
let c = PeerConfig::load(&id);
|
||||
if c.info.platform.is_empty() {
|
||||
fs::remove_file(p).ok();
|
||||
}
|
||||
(id_decoded_string, t, c)
|
||||
(id, t, c)
|
||||
})
|
||||
.filter(|p| !p.2.info.platform.is_empty())
|
||||
.collect();
|
||||
@@ -1045,6 +1111,10 @@ impl PeerConfig {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn exists(id: &str) -> bool {
|
||||
Self::path(id).exists()
|
||||
}
|
||||
|
||||
serde_field_string!(
|
||||
default_view_style,
|
||||
deserialize_view_style,
|
||||
@@ -1074,7 +1144,7 @@ impl PeerConfig {
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let v: Vec<i32> = de::Deserialize::deserialize(deserializer)?;
|
||||
if v.len() == 1 && v[0] >= 10 && v[0] <= 100 {
|
||||
if v.len() == 1 && v[0] >= 10 && v[0] <= 0xFFF {
|
||||
Ok(v)
|
||||
} else {
|
||||
Ok(Self::default_custom_image_quality())
|
||||
@@ -1086,6 +1156,17 @@ impl PeerConfig {
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let mut mp: HashMap<String, String> = de::Deserialize::deserialize(deserializer)?;
|
||||
Self::insert_default_options(&mut mp);
|
||||
Ok(mp)
|
||||
}
|
||||
|
||||
fn default_options() -> HashMap<String, String> {
|
||||
let mut mp: HashMap<String, String> = Default::default();
|
||||
Self::insert_default_options(&mut mp);
|
||||
return mp;
|
||||
}
|
||||
|
||||
fn insert_default_options(mp: &mut HashMap<String, String>) {
|
||||
let mut key = "codec-preference";
|
||||
if !mp.contains_key(key) {
|
||||
mp.insert(key.to_owned(), UserDefaultConfig::read().get(key));
|
||||
@@ -1098,7 +1179,10 @@ impl PeerConfig {
|
||||
if !mp.contains_key(key) {
|
||||
mp.insert(key.to_owned(), UserDefaultConfig::read().get(key));
|
||||
}
|
||||
Ok(mp)
|
||||
key = "touch-mode";
|
||||
if !mp.contains_key(key) {
|
||||
mp.insert(key.to_owned(), UserDefaultConfig::read().get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,7 +1340,7 @@ impl LocalConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_flutter_config(k: &str) -> String {
|
||||
pub fn get_flutter_option(k: &str) -> String {
|
||||
if let Some(v) = LOCAL_CONFIG.read().unwrap().ui_flutter.get(k) {
|
||||
v.clone()
|
||||
} else {
|
||||
@@ -1264,7 +1348,7 @@ impl LocalConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_flutter_config(k: String, v: String) {
|
||||
pub fn set_flutter_option(k: String, v: String) {
|
||||
let mut config = LOCAL_CONFIG.write().unwrap();
|
||||
let v2 = if v.is_empty() { None } else { Some(&v) };
|
||||
if v2 != config.ui_flutter.get(&k) {
|
||||
@@ -1351,18 +1435,8 @@ impl HwCodecConfig {
|
||||
Config::store_(self, "_hwcodec");
|
||||
}
|
||||
|
||||
pub fn remove() {
|
||||
std::fs::remove_file(Config::file_("_hwcodec")).ok();
|
||||
}
|
||||
|
||||
/// refresh current global HW_CODEC_CONFIG, usually uesd after HwCodecConfig::remove()
|
||||
pub fn refresh() {
|
||||
*HW_CODEC_CONFIG.write().unwrap() = HwCodecConfig::load();
|
||||
log::debug!("HW_CODEC_CONFIG refreshed successfully");
|
||||
}
|
||||
|
||||
pub fn get() -> HwCodecConfig {
|
||||
return HW_CODEC_CONFIG.read().unwrap().clone();
|
||||
pub fn clear() {
|
||||
HwCodecConfig::default().store();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1398,7 +1472,7 @@ impl UserDefaultConfig {
|
||||
"codec-preference" => {
|
||||
self.get_string(key, "auto", vec!["vp8", "vp9", "av1", "h264", "h265"])
|
||||
}
|
||||
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0),
|
||||
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 0xFFF as f64),
|
||||
"custom-fps" => self.get_double_string(key, 30.0, 5.0, 120.0),
|
||||
_ => self
|
||||
.options
|
||||
@@ -1443,6 +1517,109 @@ impl UserDefaultConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct AbPeer {
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub id: String,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub hash: String,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub username: String,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub hostname: String,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub platform: String,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub alias: String,
|
||||
#[serde(default, deserialize_with = "deserialize_vec_string")]
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct Ab {
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub access_token: String,
|
||||
#[serde(default, deserialize_with = "deserialize_vec_abpeer")]
|
||||
pub peers: Vec<AbPeer>,
|
||||
#[serde(default, deserialize_with = "deserialize_vec_string")]
|
||||
pub tags: Vec<String>,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub tag_colors: String,
|
||||
}
|
||||
|
||||
impl Ab {
|
||||
fn path() -> PathBuf {
|
||||
let filename = format!("{}_ab", APP_NAME.read().unwrap().clone());
|
||||
Config::path(filename)
|
||||
}
|
||||
|
||||
pub fn store(json: String) {
|
||||
if let Ok(mut file) = std::fs::File::create(Self::path()) {
|
||||
let data = compress(json.as_bytes());
|
||||
let max_len = 64 * 1024 * 1024;
|
||||
if data.len() > max_len {
|
||||
// maxlen of function decompress
|
||||
return;
|
||||
}
|
||||
if let Ok(data) = symmetric_crypt(&data, true) {
|
||||
file.write_all(&data).ok();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn load() -> Ab {
|
||||
if let Ok(mut file) = std::fs::File::open(Self::path()) {
|
||||
let mut data = vec![];
|
||||
if file.read_to_end(&mut data).is_ok() {
|
||||
if let Ok(data) = symmetric_crypt(&data, false) {
|
||||
let data = decompress(&data);
|
||||
if let Ok(ab) = serde_json::from_str::<Ab>(&String::from_utf8_lossy(&data)) {
|
||||
return ab;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Self::remove();
|
||||
Ab::default()
|
||||
}
|
||||
|
||||
pub fn remove() {
|
||||
std::fs::remove_file(Self::path()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
// use default value when field type is wrong
|
||||
macro_rules! deserialize_default {
|
||||
($func_name:ident, $return_type:ty) => {
|
||||
@@ -1462,6 +1639,7 @@ deserialize_default!(deserialize_vec_u8, Vec<u8>);
|
||||
deserialize_default!(deserialize_vec_string, Vec<String>);
|
||||
deserialize_default!(deserialize_vec_i32_string_i32, Vec<(i32, String, i32)>);
|
||||
deserialize_default!(deserialize_vec_discoverypeer, Vec<DiscoveryPeer>);
|
||||
deserialize_default!(deserialize_vec_abpeer, Vec<AbPeer>);
|
||||
deserialize_default!(deserialize_keypair, KeyPair);
|
||||
deserialize_default!(deserialize_size, Size);
|
||||
deserialize_default!(deserialize_hashmap_string_string, HashMap<String, String>);
|
||||
|
||||
@@ -4,9 +4,10 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use tokio::{fs::File, io::*};
|
||||
|
||||
use crate::{bail, get_version_number, message_proto::*, ResultType, Stream};
|
||||
use crate::{anyhow::anyhow, bail, get_version_number, message_proto::*, ResultType, Stream};
|
||||
// https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html
|
||||
use crate::{
|
||||
compress::{compress, decompress},
|
||||
@@ -194,7 +195,8 @@ pub fn can_enable_overwrite_detection(version: i64) -> bool {
|
||||
version >= get_version_number("1.1.10")
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransferJob {
|
||||
pub id: i32,
|
||||
pub remote: String,
|
||||
@@ -203,10 +205,13 @@ pub struct TransferJob {
|
||||
pub is_remote: bool,
|
||||
pub is_last_job: bool,
|
||||
pub file_num: i32,
|
||||
#[serde(skip_serializing)]
|
||||
pub files: Vec<FileEntry>,
|
||||
pub conn_id: i32, // server only
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
file: Option<File>,
|
||||
total_size: u64,
|
||||
pub total_size: u64,
|
||||
finished_size: u64,
|
||||
transferred: u64,
|
||||
enable_overwrite_detection: bool,
|
||||
@@ -403,10 +408,18 @@ impl TransferJob {
|
||||
}
|
||||
if block.compressed {
|
||||
let tmp = decompress(&block.data);
|
||||
self.file.as_mut().unwrap().write_all(&tmp).await?;
|
||||
self.file
|
||||
.as_mut()
|
||||
.ok_or(anyhow!("file is None"))?
|
||||
.write_all(&tmp)
|
||||
.await?;
|
||||
self.finished_size += tmp.len() as u64;
|
||||
} else {
|
||||
self.file.as_mut().unwrap().write_all(&block.data).await?;
|
||||
self.file
|
||||
.as_mut()
|
||||
.ok_or(anyhow!("file is None"))?
|
||||
.write_all(&block.data)
|
||||
.await?;
|
||||
self.finished_size += block.data.len() as u64;
|
||||
}
|
||||
self.transferred += block.data.len() as u64;
|
||||
@@ -456,7 +469,13 @@ impl TransferJob {
|
||||
let mut compressed = false;
|
||||
let mut offset: usize = 0;
|
||||
loop {
|
||||
match self.file.as_mut().unwrap().read(&mut buf[offset..]).await {
|
||||
match self
|
||||
.file
|
||||
.as_mut()
|
||||
.ok_or(anyhow!("file is None"))?
|
||||
.read(&mut buf[offset..])
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
self.file_num += 1;
|
||||
self.file = None;
|
||||
@@ -501,7 +520,12 @@ impl TransferJob {
|
||||
async fn send_current_digest(&mut self, stream: &mut Stream) -> ResultType<()> {
|
||||
let mut msg = Message::new();
|
||||
let mut resp = FileResponse::new();
|
||||
let meta = self.file.as_ref().unwrap().metadata().await?;
|
||||
let meta = self
|
||||
.file
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("file is None"))?
|
||||
.metadata()
|
||||
.await?;
|
||||
let last_modified = meta
|
||||
.modified()?
|
||||
.duration_since(SystemTime::UNIX_EPOCH)?
|
||||
@@ -676,13 +700,20 @@ pub fn new_send_confirm(r: FileTransferSendConfirmRequest) -> Message {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_receive(id: i32, path: String, file_num: i32, files: Vec<FileEntry>) -> Message {
|
||||
pub fn new_receive(
|
||||
id: i32,
|
||||
path: String,
|
||||
file_num: i32,
|
||||
files: Vec<FileEntry>,
|
||||
total_size: u64,
|
||||
) -> Message {
|
||||
let mut action = FileAction::new();
|
||||
action.set_receive(FileTransferReceiveRequest {
|
||||
id,
|
||||
path,
|
||||
files,
|
||||
file_num,
|
||||
total_size,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
@@ -729,10 +760,16 @@ pub fn get_job(id: i32, jobs: &mut [TransferJob]) -> Option<&mut TransferJob> {
|
||||
jobs.iter_mut().find(|x| x.id() == id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_job_immutable(id: i32, jobs: &[TransferJob]) -> Option<&TransferJob> {
|
||||
jobs.iter().find(|x| x.id() == id)
|
||||
}
|
||||
|
||||
pub async fn handle_read_jobs(
|
||||
jobs: &mut Vec<TransferJob>,
|
||||
stream: &mut crate::Stream,
|
||||
) -> ResultType<()> {
|
||||
) -> ResultType<String> {
|
||||
let mut job_log = Default::default();
|
||||
let mut finished = Vec::new();
|
||||
for job in jobs.iter_mut() {
|
||||
if job.is_last_job {
|
||||
@@ -749,14 +786,16 @@ pub async fn handle_read_jobs(
|
||||
}
|
||||
Ok(None) => {
|
||||
if job.job_completed() {
|
||||
job_log = serialize_transfer_job(job, true, false, "");
|
||||
finished.push(job.id());
|
||||
let err = job.job_error();
|
||||
if err.is_some() {
|
||||
stream
|
||||
.send(&new_error(job.id(), err.unwrap(), job.file_num()))
|
||||
.await?;
|
||||
} else {
|
||||
stream.send(&new_done(job.id(), job.file_num())).await?;
|
||||
match job.job_error() {
|
||||
Some(err) => {
|
||||
job_log = serialize_transfer_job(job, false, false, &err);
|
||||
stream
|
||||
.send(&new_error(job.id(), err, job.file_num()))
|
||||
.await?
|
||||
}
|
||||
None => stream.send(&new_done(job.id(), job.file_num())).await?,
|
||||
}
|
||||
} else {
|
||||
// waiting confirmation.
|
||||
@@ -767,7 +806,7 @@ pub async fn handle_read_jobs(
|
||||
for id in finished {
|
||||
remove_job(id, jobs);
|
||||
}
|
||||
Ok(())
|
||||
Ok(job_log)
|
||||
}
|
||||
|
||||
pub fn remove_all_empty_dir(path: &PathBuf) -> ResultType<()> {
|
||||
@@ -842,3 +881,20 @@ pub fn is_write_need_confirmation(
|
||||
Ok(DigestCheckResult::NoSuchFile)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_transfer_jobs(jobs: &[TransferJob]) -> String {
|
||||
let mut v = vec![];
|
||||
for job in jobs {
|
||||
let value = serde_json::to_value(job).unwrap_or_default();
|
||||
v.push(value);
|
||||
}
|
||||
serde_json::to_string(&v).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn serialize_transfer_job(job: &TransferJob, done: bool, cancel: bool, error: &str) -> String {
|
||||
let mut value = serde_json::to_value(job).unwrap_or_default();
|
||||
value["done"] = json!(done);
|
||||
value["cancel"] = json!(cancel);
|
||||
value["error"] = json!(error);
|
||||
serde_json::to_string(&value).unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ pub mod keyboard;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use dlopen;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use machine_uid;
|
||||
pub use sysinfo;
|
||||
pub use toml;
|
||||
pub use uuid;
|
||||
@@ -126,7 +127,7 @@ impl AddrMangle {
|
||||
SocketAddr::V4(addr_v4) => {
|
||||
let tm = (SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.unwrap_or(std::time::Duration::ZERO)
|
||||
.as_micros() as u32) as u128;
|
||||
let ip = u32::from_le_bytes(addr_v4.ip().octets()) as u128;
|
||||
let port = addr.port() as u128;
|
||||
@@ -159,9 +160,9 @@ impl AddrMangle {
|
||||
if bytes.len() != 18 {
|
||||
return Config::get_any_listen_addr(false);
|
||||
}
|
||||
let tmp: [u8; 2] = bytes[16..].try_into().unwrap();
|
||||
let tmp: [u8; 2] = bytes[16..].try_into().unwrap_or_default();
|
||||
let port = u16::from_le_bytes(tmp);
|
||||
let tmp: [u8; 16] = bytes[..16].try_into().unwrap();
|
||||
let tmp: [u8; 16] = bytes[..16].try_into().unwrap_or_default();
|
||||
let ip = std::net::Ipv6Addr::from(tmp);
|
||||
return SocketAddr::new(IpAddr::V6(ip), port);
|
||||
}
|
||||
@@ -289,16 +290,24 @@ pub fn get_time() -> i64 {
|
||||
|
||||
#[inline]
|
||||
pub fn is_ipv4_str(id: &str) -> bool {
|
||||
regex::Regex::new(r"^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:\d+)?$")
|
||||
.unwrap()
|
||||
.is_match(id)
|
||||
if let Ok(reg) = regex::Regex::new(
|
||||
r"^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:\d+)?$",
|
||||
) {
|
||||
reg.is_match(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_ipv6_str(id: &str) -> bool {
|
||||
regex::Regex::new(r"^((([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4})|(\[([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4}\]:\d+))$")
|
||||
.unwrap()
|
||||
.is_match(id)
|
||||
if let Ok(reg) = regex::Regex::new(
|
||||
r"^((([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4})|(\[([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4}\]:\d+))$",
|
||||
) {
|
||||
reg.is_match(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -311,11 +320,13 @@ pub fn is_domain_port_str(id: &str) -> bool {
|
||||
// modified regex for RFC1123 hostname. check https://stackoverflow.com/a/106223 for original version for hostname.
|
||||
// according to [TLD List](https://data.iana.org/TLD/tlds-alpha-by-domain.txt) version 2023011700,
|
||||
// there is no digits in TLD, and length is 2~63.
|
||||
regex::Regex::new(
|
||||
if let Ok(reg) = regex::Regex::new(
|
||||
r"(?i)^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z-]{0,61}[a-z]:\d{1,5}$",
|
||||
)
|
||||
.unwrap()
|
||||
.is_match(id)
|
||||
) {
|
||||
reg.is_match(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_log(_is_async: bool, _name: &str) -> Option<flexi_logger::LoggerHandle> {
|
||||
|
||||
@@ -84,13 +84,16 @@ pub fn hide_cm() -> bool {
|
||||
|
||||
const VERSION_LEN: usize = 2;
|
||||
|
||||
pub fn encrypt_str_or_original(s: &str, version: &str) -> String {
|
||||
pub fn encrypt_str_or_original(s: &str, version: &str, max_len: usize) -> String {
|
||||
if decrypt_str_or_original(s, version).1 {
|
||||
log::error!("Duplicate encryption!");
|
||||
return s.to_owned();
|
||||
}
|
||||
if s.bytes().len() > max_len {
|
||||
return String::default();
|
||||
}
|
||||
if version == "00" {
|
||||
if let Ok(s) = encrypt(s.as_bytes()) {
|
||||
if let Ok(s) = encrypt(s.as_bytes(), max_len) {
|
||||
return version.to_owned() + &s;
|
||||
}
|
||||
}
|
||||
@@ -100,15 +103,16 @@ pub fn encrypt_str_or_original(s: &str, version: &str) -> String {
|
||||
// String: password
|
||||
// bool: whether decryption is successful
|
||||
// bool: whether should store to re-encrypt when load
|
||||
// note: s.len() return length in bytes, s.chars().count() return char count
|
||||
// &[..2] return the left 2 bytes, s.chars().take(2) return the left 2 chars
|
||||
pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool, bool) {
|
||||
if s.len() > VERSION_LEN {
|
||||
let version = &s[..VERSION_LEN];
|
||||
if version == "00" {
|
||||
if s.starts_with("00") {
|
||||
if let Ok(v) = decrypt(s[VERSION_LEN..].as_bytes()) {
|
||||
return (
|
||||
String::from_utf8_lossy(&v).to_string(),
|
||||
true,
|
||||
version != current_version,
|
||||
"00" != current_version,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -117,13 +121,16 @@ pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool,
|
||||
(s.to_owned(), false, !s.is_empty())
|
||||
}
|
||||
|
||||
pub fn encrypt_vec_or_original(v: &[u8], version: &str) -> Vec<u8> {
|
||||
pub fn encrypt_vec_or_original(v: &[u8], version: &str, max_len: usize) -> Vec<u8> {
|
||||
if decrypt_vec_or_original(v, version).1 {
|
||||
log::error!("Duplicate encryption!");
|
||||
return v.to_owned();
|
||||
}
|
||||
if v.len() > max_len {
|
||||
return vec![];
|
||||
}
|
||||
if version == "00" {
|
||||
if let Ok(s) = encrypt(v) {
|
||||
if let Ok(s) = encrypt(v, max_len) {
|
||||
let mut version = version.to_owned().into_bytes();
|
||||
version.append(&mut s.into_bytes());
|
||||
return version;
|
||||
@@ -148,8 +155,8 @@ pub fn decrypt_vec_or_original(v: &[u8], current_version: &str) -> (Vec<u8>, boo
|
||||
(v.to_owned(), false, !v.is_empty())
|
||||
}
|
||||
|
||||
fn encrypt(v: &[u8]) -> Result<String, ()> {
|
||||
if !v.is_empty() {
|
||||
fn encrypt(v: &[u8], max_len: usize) -> Result<String, ()> {
|
||||
if !v.is_empty() && v.len() <= max_len {
|
||||
symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original))
|
||||
} else {
|
||||
Err(())
|
||||
@@ -164,7 +171,7 @@ fn decrypt(v: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result<Vec<u8>, ()> {
|
||||
pub fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result<Vec<u8>, ()> {
|
||||
use sodiumoxide::crypto::secretbox;
|
||||
use std::convert::TryInto;
|
||||
|
||||
@@ -185,12 +192,15 @@ mod test {
|
||||
#[test]
|
||||
fn test() {
|
||||
use super::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::time::Instant;
|
||||
|
||||
let version = "00";
|
||||
let max_len = 128;
|
||||
|
||||
println!("test str");
|
||||
let data = "Hello World";
|
||||
let encrypted = encrypt_str_or_original(data, version);
|
||||
let data = "1ü1111";
|
||||
let encrypted = encrypt_str_or_original(data, version, max_len);
|
||||
let (decrypted, succ, store) = decrypt_str_or_original(&encrypted, version);
|
||||
println!("data: {data}");
|
||||
println!("encrypted: {encrypted}");
|
||||
@@ -202,11 +212,14 @@ mod test {
|
||||
let (_, _, store) = decrypt_str_or_original(&encrypted, "99");
|
||||
assert!(store);
|
||||
assert!(!decrypt_str_or_original(&decrypted, version).1);
|
||||
assert_eq!(encrypt_str_or_original(&encrypted, version), encrypted);
|
||||
assert_eq!(
|
||||
encrypt_str_or_original(&encrypted, version, max_len),
|
||||
encrypted
|
||||
);
|
||||
|
||||
println!("test vec");
|
||||
let data: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
|
||||
let encrypted = encrypt_vec_or_original(&data, version);
|
||||
let data: Vec<u8> = "1ü1111".as_bytes().to_vec();
|
||||
let encrypted = encrypt_vec_or_original(&data, version, max_len);
|
||||
let (decrypted, succ, store) = decrypt_vec_or_original(&encrypted, version);
|
||||
println!("data: {data:?}");
|
||||
println!("encrypted: {encrypted:?}");
|
||||
@@ -218,7 +231,10 @@ mod test {
|
||||
let (_, _, store) = decrypt_vec_or_original(&encrypted, "99");
|
||||
assert!(store);
|
||||
assert!(!decrypt_vec_or_original(&decrypted, version).1);
|
||||
assert_eq!(encrypt_vec_or_original(&encrypted, version), encrypted);
|
||||
assert_eq!(
|
||||
encrypt_vec_or_original(&encrypted, version, max_len),
|
||||
encrypted
|
||||
);
|
||||
|
||||
println!("test original");
|
||||
let data = version.to_string() + "Hello World";
|
||||
@@ -238,5 +254,42 @@ mod test {
|
||||
let (_, succ, store) = decrypt_vec_or_original(&[], version);
|
||||
assert!(!store);
|
||||
assert!(!succ);
|
||||
let data = "1ü1111";
|
||||
assert_eq!(decrypt_str_or_original(data, version).0, data);
|
||||
let data: Vec<u8> = "1ü1111".as_bytes().to_vec();
|
||||
assert_eq!(decrypt_vec_or_original(&data, version).0, data);
|
||||
|
||||
println!("test speed");
|
||||
let test_speed = |len: usize, name: &str| {
|
||||
let mut data: Vec<u8> = vec![];
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0..len {
|
||||
data.push(rng.gen_range(0..255));
|
||||
}
|
||||
let start: Instant = Instant::now();
|
||||
let encrypted = encrypt_vec_or_original(&data, version, len);
|
||||
assert_ne!(data, decrypted);
|
||||
let t1 = start.elapsed();
|
||||
let start = Instant::now();
|
||||
let (decrypted, _, _) = decrypt_vec_or_original(&encrypted, version);
|
||||
let t2 = start.elapsed();
|
||||
assert_eq!(data, decrypted);
|
||||
println!("{name}");
|
||||
println!("encrypt:{:?}, decrypt:{:?}", t1, t2);
|
||||
|
||||
let start: Instant = Instant::now();
|
||||
let encrypted = base64::encode(&data, base64::Variant::Original);
|
||||
let t1 = start.elapsed();
|
||||
let start = Instant::now();
|
||||
let decrypted = base64::decode(&encrypted, base64::Variant::Original).unwrap();
|
||||
let t2 = start.elapsed();
|
||||
assert_eq!(data, decrypted);
|
||||
println!("base64, encrypt:{:?}, decrypt:{:?}", t1, t2,);
|
||||
};
|
||||
test_speed(128, "128");
|
||||
test_speed(1024, "1k");
|
||||
test_speed(1024 * 1024, "1M");
|
||||
test_speed(10 * 1024 * 1024, "10M");
|
||||
test_speed(100 * 1024 * 1024, "100M");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,15 @@ pub fn is_active(sid: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_active_and_seat0(sid: &str) -> bool {
|
||||
if let Ok(output) = run_loginctl(Some(vec!["show-session", sid])) {
|
||||
String::from_utf8_lossy(&output.stdout).contains("State=active")
|
||||
&& String::from_utf8_lossy(&output.stdout).contains("Seat=seat0")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_cmds(cmds: &str) -> ResultType<String> {
|
||||
let output = std::process::Command::new("sh")
|
||||
.args(vec!["-c", cmds])
|
||||
|
||||
@@ -4,6 +4,9 @@ pub mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
use crate::{config::Config, log};
|
||||
#[cfg(not(debug_assertions))]
|
||||
@@ -28,6 +31,7 @@ extern "C" fn breakdown_signal_handler(sig: i32) {
|
||||
s.contains(&"nouveau_pushbuf_kick")
|
||||
|| s.to_lowercase().contains("nvidia")
|
||||
|| s.contains("gdk_window_end_draw_frame")
|
||||
|| s.contains("glGetString")
|
||||
}) {
|
||||
Config::set_option("allow-always-software-render".to_string(), "Y".to_string());
|
||||
info = "Always use software rendering will be set.".to_string();
|
||||
|
||||
154
libs/hbb_common/src/platform/windows.rs
Normal file
154
libs/hbb_common/src/platform/windows.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
os::windows::raw::HANDLE,
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
use winapi::{
|
||||
shared::minwindef::{DWORD, FALSE},
|
||||
um::{
|
||||
handleapi::CloseHandle,
|
||||
pdh::{
|
||||
PdhAddCounterA, PdhCloseQuery, PdhCollectQueryData, PdhCollectQueryDataEx,
|
||||
PdhGetFormattedCounterValue, PdhOpenQueryA, PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE,
|
||||
PDH_HCOUNTER, PDH_HQUERY,
|
||||
},
|
||||
synchapi::{CreateEventA, WaitForSingleObject},
|
||||
winbase::{INFINITE, WAIT_OBJECT_0},
|
||||
},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CPU_USAGE_ONE_MINUTE: Arc<Mutex<Option<(f64, Instant)>>> = Arc::new(Mutex::new(None));
|
||||
}
|
||||
|
||||
// https://github.com/mgostIH/process_list/blob/master/src/windows/mod.rs
|
||||
#[repr(transparent)]
|
||||
pub struct RAIIHandle(pub HANDLE);
|
||||
|
||||
impl Drop for RAIIHandle {
|
||||
fn drop(&mut self) {
|
||||
// This never gives problem except when running under a debugger.
|
||||
unsafe { CloseHandle(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(self) struct RAIIPDHQuery(pub PDH_HQUERY);
|
||||
|
||||
impl Drop for RAIIPDHQuery {
|
||||
fn drop(&mut self) {
|
||||
unsafe { PdhCloseQuery(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_cpu_performance_monitor() {
|
||||
// Code from:
|
||||
// https://learn.microsoft.com/en-us/windows/win32/perfctrs/collecting-performance-data
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/pdh/nf-pdh-pdhcollectquerydataex
|
||||
// Why value lower than taskManager:
|
||||
// https://aaron-margosis.medium.com/task-managers-cpu-numbers-are-all-but-meaningless-2d165b421e43
|
||||
// Therefore we should compare with Precess Explorer rather than taskManager
|
||||
|
||||
let f = || unsafe {
|
||||
// load avg or cpu usage, test with prime95.
|
||||
// Prefer cpu usage because we can get accurate value from Precess Explorer.
|
||||
// const COUNTER_PATH: &'static str = "\\System\\Processor Queue Length\0";
|
||||
const COUNTER_PATH: &'static str = "\\Processor(_total)\\% Processor Time\0";
|
||||
const SAMPLE_INTERVAL: DWORD = 2; // 2 second
|
||||
|
||||
let mut ret;
|
||||
let mut query: PDH_HQUERY = std::mem::zeroed();
|
||||
ret = PdhOpenQueryA(std::ptr::null() as _, 0, &mut query);
|
||||
if ret != 0 {
|
||||
log::error!("PdhOpenQueryA failed: 0x{:X}", ret);
|
||||
return;
|
||||
}
|
||||
let _query = RAIIPDHQuery(query);
|
||||
let mut counter: PDH_HCOUNTER = std::mem::zeroed();
|
||||
ret = PdhAddCounterA(query, COUNTER_PATH.as_ptr() as _, 0, &mut counter);
|
||||
if ret != 0 {
|
||||
log::error!("PdhAddCounterA failed: 0x{:X}", ret);
|
||||
return;
|
||||
}
|
||||
ret = PdhCollectQueryData(query);
|
||||
if ret != 0 {
|
||||
log::error!("PdhCollectQueryData failed: 0x{:X}", ret);
|
||||
return;
|
||||
}
|
||||
let mut _counter_type: DWORD = 0;
|
||||
let mut counter_value: PDH_FMT_COUNTERVALUE = std::mem::zeroed();
|
||||
let event = CreateEventA(std::ptr::null_mut(), FALSE, FALSE, std::ptr::null() as _);
|
||||
if event.is_null() {
|
||||
log::error!("CreateEventA failed");
|
||||
return;
|
||||
}
|
||||
let _event: RAIIHandle = RAIIHandle(event);
|
||||
ret = PdhCollectQueryDataEx(query, SAMPLE_INTERVAL, event);
|
||||
if ret != 0 {
|
||||
log::error!("PdhCollectQueryDataEx failed: 0x{:X}", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut queue: VecDeque<f64> = VecDeque::new();
|
||||
let mut recent_valid: VecDeque<bool> = VecDeque::new();
|
||||
loop {
|
||||
// latest one minute
|
||||
if queue.len() == 31 {
|
||||
queue.pop_front();
|
||||
}
|
||||
if recent_valid.len() == 31 {
|
||||
recent_valid.pop_front();
|
||||
}
|
||||
// allow get value within one minute
|
||||
if queue.len() > 0 && recent_valid.iter().filter(|v| **v).count() > queue.len() / 2 {
|
||||
let sum: f64 = queue.iter().map(|f| f.to_owned()).sum();
|
||||
let avg = sum / (queue.len() as f64);
|
||||
*CPU_USAGE_ONE_MINUTE.lock().unwrap() = Some((avg, Instant::now()));
|
||||
} else {
|
||||
*CPU_USAGE_ONE_MINUTE.lock().unwrap() = None;
|
||||
}
|
||||
if WAIT_OBJECT_0 != WaitForSingleObject(event, INFINITE) {
|
||||
recent_valid.push_back(false);
|
||||
continue;
|
||||
}
|
||||
if PdhGetFormattedCounterValue(
|
||||
counter,
|
||||
PDH_FMT_DOUBLE,
|
||||
&mut _counter_type,
|
||||
&mut counter_value,
|
||||
) != 0
|
||||
|| counter_value.CStatus != 0
|
||||
{
|
||||
recent_valid.push_back(false);
|
||||
continue;
|
||||
}
|
||||
queue.push_back(counter_value.u.doubleValue().clone());
|
||||
recent_valid.push_back(true);
|
||||
}
|
||||
};
|
||||
use std::sync::Once;
|
||||
static ONCE: Once = Once::new();
|
||||
ONCE.call_once(|| {
|
||||
std::thread::spawn(f);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn cpu_uage_one_minute() -> Option<f64> {
|
||||
let v = CPU_USAGE_ONE_MINUTE.lock().unwrap().clone();
|
||||
if let Some((v, instant)) = v {
|
||||
if instant.elapsed().as_secs() < 30 {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn sync_cpu_usage(cpu_usage: Option<f64>) {
|
||||
let v = match cpu_usage {
|
||||
Some(cpu_usage) => Some((cpu_usage, Instant::now())),
|
||||
None => None,
|
||||
};
|
||||
*CPU_USAGE_ONE_MINUTE.lock().unwrap() = v;
|
||||
log::info!("cpu usage synced: {:?}", cpu_usage);
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
extern crate embed_resource;
|
||||
use std::fs;
|
||||
|
||||
fn main() {
|
||||
embed_resource::compile("icon.rc", embed_resource::NONE);
|
||||
let runner_res_path = "Runner.res";
|
||||
match fs::metadata(runner_res_path) {
|
||||
Ok(_) => println!("cargo:rustc-link-lib=dylib:+verbatim=./libs/portable/Runner.res"),
|
||||
Err(_) => embed_resource::compile("icon.rc", embed_resource::NONE),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ fn execute(path: PathBuf, args: Vec<String>) {
|
||||
.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.spawn()
|
||||
.ok();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ mediacodec = ["ndk"]
|
||||
linux-pkg-config = ["dep:pkg-config"]
|
||||
|
||||
[dependencies]
|
||||
block = "0.1"
|
||||
cfg-if = "1.0"
|
||||
num_cpus = "1.15"
|
||||
lazy_static = "1.4"
|
||||
@@ -27,6 +26,9 @@ version = "0.3"
|
||||
default-features = true
|
||||
features = ["dxgi", "dxgi1_2", "dxgi1_5", "d3d11", "winuser", "winerror", "errhandlingapi", "libloaderapi"]
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
block = "0.1"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.13"
|
||||
jni = "0.21"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use docopt::Docopt;
|
||||
use hbb_common::env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV};
|
||||
use scrap::{
|
||||
aom::{AomDecoder, AomDecoderConfig, AomEncoder, AomEncoderConfig},
|
||||
codec::{EncoderApi, EncoderCfg},
|
||||
aom::{AomDecoder, AomEncoder, AomEncoderConfig},
|
||||
codec::{EncoderApi, EncoderCfg, Quality as Q},
|
||||
Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig,
|
||||
VpxVideoCodecId::{self, *},
|
||||
STRIDE_ALIGN,
|
||||
@@ -15,13 +15,14 @@ const USAGE: &'static str = "
|
||||
Codec benchmark.
|
||||
|
||||
Usage:
|
||||
benchmark [--count=COUNT] [--bitrate=KBS] [--hw-pixfmt=PIXFMT]
|
||||
benchmark [--count=COUNT] [--quality=QUALITY] [--hw-pixfmt=PIXFMT]
|
||||
benchmark (-h | --help)
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
--count=COUNT Capture frame count [default: 100].
|
||||
--bitrate=KBS Video bitrate in kilobits per second [default: 5000].
|
||||
--quality=QUALITY Video quality [default: Balanced].
|
||||
Valid values: Best, Balanced, Low.
|
||||
--hw-pixfmt=PIXFMT Hardware codec pixfmt. [default: i420]
|
||||
Valid values: i420, nv12.
|
||||
";
|
||||
@@ -29,7 +30,7 @@ Options:
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Args {
|
||||
flag_count: usize,
|
||||
flag_bitrate: usize,
|
||||
flag_quality: Quality,
|
||||
flag_hw_pixfmt: Pixfmt,
|
||||
}
|
||||
|
||||
@@ -39,20 +40,32 @@ enum Pixfmt {
|
||||
NV12,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
enum Quality {
|
||||
Best,
|
||||
Balanced,
|
||||
Low,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info"));
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
.and_then(|d| d.deserialize())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
let bitrate_k = args.flag_bitrate;
|
||||
let quality = args.flag_quality;
|
||||
let yuv_count = args.flag_count;
|
||||
let (yuvs, width, height) = capture_yuv(yuv_count);
|
||||
println!(
|
||||
"benchmark {}x{} bitrate:{}k hw_pixfmt:{:?}",
|
||||
width, height, bitrate_k, args.flag_hw_pixfmt
|
||||
"benchmark {}x{} quality:{:?}k hw_pixfmt:{:?}",
|
||||
width, height, quality, args.flag_hw_pixfmt
|
||||
);
|
||||
[VP8, VP9].map(|c| test_vpx(c, &yuvs, width, height, bitrate_k, yuv_count));
|
||||
test_av1(&yuvs, width, height, bitrate_k, yuv_count);
|
||||
let quality = match quality {
|
||||
Quality::Best => Q::Best,
|
||||
Quality::Balanced => Q::Balanced,
|
||||
Quality::Low => Q::Low,
|
||||
};
|
||||
[VP8, VP9].map(|c| test_vpx(c, &yuvs, width, height, quality, yuv_count));
|
||||
test_av1(&yuvs, width, height, quality, yuv_count);
|
||||
#[cfg(feature = "hwcodec")]
|
||||
{
|
||||
use hwcodec::AVPixelFormat;
|
||||
@@ -61,7 +74,7 @@ fn main() {
|
||||
Pixfmt::NV12 => AVPixelFormat::AV_PIX_FMT_NV12,
|
||||
};
|
||||
let yuvs = hw::vpx_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt);
|
||||
hw::test(&yuvs, width, height, bitrate_k, yuv_count, hw_pixfmt);
|
||||
hw::test(&yuvs, width, height, quality, yuv_count, hw_pixfmt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,14 +108,15 @@ fn test_vpx(
|
||||
yuvs: &Vec<Vec<u8>>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
bitrate_k: usize,
|
||||
quality: Q,
|
||||
yuv_count: usize,
|
||||
) {
|
||||
let config = EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
bitrate: bitrate_k as _,
|
||||
quality,
|
||||
codec: codec_id,
|
||||
keyframe_interval: None,
|
||||
});
|
||||
let mut encoder = VpxEncoder::new(config).unwrap();
|
||||
let mut vpxs = vec![];
|
||||
@@ -129,11 +143,7 @@ fn test_vpx(
|
||||
size / yuv_count
|
||||
);
|
||||
|
||||
let mut decoder = VpxDecoder::new(VpxDecoderConfig {
|
||||
codec: codec_id,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
})
|
||||
.unwrap();
|
||||
let mut decoder = VpxDecoder::new(VpxDecoderConfig { codec: codec_id }).unwrap();
|
||||
let start = Instant::now();
|
||||
for vpx in vpxs {
|
||||
let _ = decoder.decode(&vpx);
|
||||
@@ -146,11 +156,12 @@ fn test_vpx(
|
||||
);
|
||||
}
|
||||
|
||||
fn test_av1(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize, yuv_count: usize) {
|
||||
fn test_av1(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, quality: Q, yuv_count: usize) {
|
||||
let config = EncoderCfg::AOM(AomEncoderConfig {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
bitrate: bitrate_k as _,
|
||||
quality,
|
||||
keyframe_interval: None,
|
||||
});
|
||||
let mut encoder = AomEncoder::new(config).unwrap();
|
||||
let start = Instant::now();
|
||||
@@ -171,10 +182,7 @@ fn test_av1(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize,
|
||||
start.elapsed() / yuv_count as _,
|
||||
size / yuv_count
|
||||
);
|
||||
let mut decoder = AomDecoder::new(AomDecoderConfig {
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
})
|
||||
.unwrap();
|
||||
let mut decoder = AomDecoder::new().unwrap();
|
||||
let start = Instant::now();
|
||||
for av1 in av1s {
|
||||
let _ = decoder.decode(&av1);
|
||||
@@ -195,6 +203,7 @@ mod hw {
|
||||
RateControl::*,
|
||||
};
|
||||
use scrap::{
|
||||
codec::codec_thread_num,
|
||||
convert::{
|
||||
hw::{hw_bgra_to_i420, hw_bgra_to_nv12},
|
||||
i420_to_bgra,
|
||||
@@ -206,21 +215,23 @@ mod hw {
|
||||
yuvs: &Vec<Vec<u8>>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
bitrate_k: usize,
|
||||
quality: Q,
|
||||
yuv_count: usize,
|
||||
pixfmt: AVPixelFormat,
|
||||
) {
|
||||
let bitrate = scrap::hwcodec::HwEncoder::convert_quality(quality);
|
||||
let ctx = EncodeContext {
|
||||
name: String::from(""),
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
pixfmt,
|
||||
align: 0,
|
||||
bitrate: (bitrate_k * 1000) as _,
|
||||
bitrate: bitrate as i32 * 1000,
|
||||
timebase: [1, 30],
|
||||
gop: 60,
|
||||
quality: Quality_Default,
|
||||
rc: RC_DEFAULT,
|
||||
thread_count: codec_thread_num() as _,
|
||||
};
|
||||
|
||||
let encoders = Encoder::available_encoders(ctx.clone());
|
||||
@@ -273,6 +284,7 @@ mod hw {
|
||||
let ctx = DecodeContext {
|
||||
name: info.name,
|
||||
device_type: info.hwdevice,
|
||||
thread_count: codec_thread_num() as _,
|
||||
};
|
||||
|
||||
let mut decoder = Decoder::new(ctx.clone()).unwrap();
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::time::{Duration, Instant};
|
||||
use std::{io, thread};
|
||||
|
||||
use docopt::Docopt;
|
||||
use scrap::codec::{EncoderApi, EncoderCfg};
|
||||
use scrap::codec::{EncoderApi, EncoderCfg, Quality as Q};
|
||||
use webm::mux;
|
||||
use webm::mux::Track;
|
||||
|
||||
@@ -24,17 +24,18 @@ const USAGE: &'static str = "
|
||||
Simple WebM screen capture.
|
||||
|
||||
Usage:
|
||||
record-screen <path> [--time=<s>] [--fps=<fps>] [--bv=<kbps>] [--ba=<kbps>] [--codec CODEC]
|
||||
record-screen <path> [--time=<s>] [--fps=<fps>] [--quality=<quality>] [--ba=<kbps>] [--codec CODEC]
|
||||
record-screen (-h | --help)
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
--time=<s> Recording duration in seconds.
|
||||
--fps=<fps> Frames per second [default: 30].
|
||||
--bv=<kbps> Video bitrate in kilobits per second [default: 5000].
|
||||
--ba=<kbps> Audio bitrate in kilobits per second [default: 96].
|
||||
--codec CODEC Configure the codec used. [default: vp9]
|
||||
Valid values: vp8, vp9.
|
||||
-h --help Show this screen.
|
||||
--time=<s> Recording duration in seconds.
|
||||
--fps=<fps> Frames per second [default: 30].
|
||||
--quality=<quality> Video quality [default: Balanced].
|
||||
Valid values: Best, Balanced, Low.
|
||||
--ba=<kbps> Audio bitrate in kilobits per second [default: 96].
|
||||
--codec CODEC Configure the codec used. [default: vp9]
|
||||
Valid values: vp8, vp9.
|
||||
";
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
@@ -43,7 +44,14 @@ struct Args {
|
||||
flag_codec: Codec,
|
||||
flag_time: Option<u64>,
|
||||
flag_fps: u64,
|
||||
flag_bv: u32,
|
||||
flag_quality: Quality,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
enum Quality {
|
||||
Best,
|
||||
Balanced,
|
||||
Low,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
@@ -97,12 +105,17 @@ fn main() -> io::Result<()> {
|
||||
let mut vt = webm.add_video_track(width, height, None, mux_codec);
|
||||
|
||||
// Setup the encoder.
|
||||
|
||||
let quality = match args.flag_quality {
|
||||
Quality::Best => Q::Best,
|
||||
Quality::Balanced => Q::Balanced,
|
||||
Quality::Low => Q::Low,
|
||||
};
|
||||
let mut vpx = vpx_encode::VpxEncoder::new(EncoderCfg::VPX(vpx_encode::VpxEncoderConfig {
|
||||
width,
|
||||
height,
|
||||
bitrate: args.flag_bv,
|
||||
quality,
|
||||
codec: vpx_codec,
|
||||
keyframe_interval: None,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -99,9 +99,11 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onVideoFrameUpd
|
||||
buffer: JObject,
|
||||
) {
|
||||
let jb = JByteBuffer::from(buffer);
|
||||
let data = env.get_direct_buffer_address(&jb).unwrap();
|
||||
let len = env.get_direct_buffer_capacity(&jb).unwrap();
|
||||
VIDEO_RAW.lock().unwrap().update(data, len);
|
||||
if let Ok(data) = env.get_direct_buffer_address(&jb) {
|
||||
if let Ok(len) = env.get_direct_buffer_capacity(&jb) {
|
||||
VIDEO_RAW.lock().unwrap().update(data, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -111,9 +113,11 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onAudioFrameUpd
|
||||
buffer: JObject,
|
||||
) {
|
||||
let jb = JByteBuffer::from(buffer);
|
||||
let data = env.get_direct_buffer_address(&jb).unwrap();
|
||||
let len = env.get_direct_buffer_capacity(&jb).unwrap();
|
||||
AUDIO_RAW.lock().unwrap().update(data, len);
|
||||
if let Ok(data) = env.get_direct_buffer_address(&jb) {
|
||||
if let Ok(len) = env.get_direct_buffer_capacity(&jb) {
|
||||
AUDIO_RAW.lock().unwrap().update(data, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -142,25 +146,26 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_init(
|
||||
ctx: JObject,
|
||||
) {
|
||||
log::debug!("MainService init from java");
|
||||
let jvm = env.get_java_vm().unwrap();
|
||||
|
||||
*JVM.write().unwrap() = Some(jvm);
|
||||
|
||||
let context = env.new_global_ref(ctx).unwrap();
|
||||
*MAIN_SERVICE_CTX.write().unwrap() = Some(context);
|
||||
if let Ok(jvm) = env.get_java_vm() {
|
||||
*JVM.write().unwrap() = Some(jvm);
|
||||
if let Ok(context) = env.new_global_ref(ctx) {
|
||||
*MAIN_SERVICE_CTX.write().unwrap() = Some(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_main_service_mouse_input(mask: i32, x: i32, y: i32) -> JniResult<()> {
|
||||
pub fn call_main_service_pointer_input(kind: &str, mask: i32, x: i32, y: i32) -> JniResult<()> {
|
||||
if let (Some(jvm), Some(ctx)) = (
|
||||
JVM.read().unwrap().as_ref(),
|
||||
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
||||
) {
|
||||
let mut env = jvm.attach_current_thread_as_daemon()?;
|
||||
let kind = env.new_string(kind)?;
|
||||
env.call_method(
|
||||
ctx,
|
||||
"rustMouseInput",
|
||||
"(III)V",
|
||||
&[JValue::Int(mask), JValue::Int(x), JValue::Int(y)],
|
||||
"rustPointerInput",
|
||||
"(Ljava/lang/String;III)V",
|
||||
&[JValue::Object(&JObject::from(kind)), JValue::Int(mask), JValue::Int(x), JValue::Int(y)],
|
||||
)?;
|
||||
return Ok(());
|
||||
} else {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/aom_ffi.rs"));
|
||||
|
||||
use crate::codec::{base_bitrate, codec_thread_num, Quality};
|
||||
use crate::{codec::EncoderApi, EncodeFrame, STRIDE_ALIGN};
|
||||
use crate::{common::GoogleImage, generate_call_macro, generate_call_ptr_macro, Error, Result};
|
||||
use hbb_common::{
|
||||
@@ -43,7 +44,8 @@ impl Default for aom_image_t {
|
||||
pub struct AomEncoderConfig {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub bitrate: u32,
|
||||
pub quality: Quality,
|
||||
pub keyframe_interval: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct AomEncoder {
|
||||
@@ -56,7 +58,6 @@ pub struct AomEncoder {
|
||||
mod webrtc {
|
||||
use super::*;
|
||||
|
||||
const kQpMin: u32 = 10;
|
||||
const kUsageProfile: u32 = AOM_USAGE_REALTIME;
|
||||
const kMinQindex: u32 = 145; // Min qindex threshold for QP scaling.
|
||||
const kMaxQindex: u32 = 205; // Max qindex threshold for QP scaling.
|
||||
@@ -65,26 +66,8 @@ mod webrtc {
|
||||
const kRtpTicksPerSecond: i32 = 90000;
|
||||
const kMinimumFrameRate: f64 = 1.0;
|
||||
|
||||
const kQpMax: u32 = 25; // to-do: webrtc use dynamic value, no more than 63
|
||||
|
||||
fn number_of_threads(width: u32, height: u32, number_of_cores: usize) -> u32 {
|
||||
// Keep the number of encoder threads equal to the possible number of
|
||||
// column/row tiles, which is (1, 2, 4, 8). See comments below for
|
||||
// AV1E_SET_TILE_COLUMNS/ROWS.
|
||||
if width * height >= 640 * 360 && number_of_cores > 4 {
|
||||
return 4;
|
||||
} else if width * height >= 320 * 180 && number_of_cores > 2 {
|
||||
return 2;
|
||||
} else {
|
||||
// Use 2 threads for low res on ARM.
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64", target_os = "android"))]
|
||||
if width * height >= 320 * 180 && number_of_cores > 2 {
|
||||
return 2;
|
||||
}
|
||||
// 1 thread less than VGA.
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
pub const DEFAULT_Q_MAX: u32 = 56; // no more than 63
|
||||
pub const DEFAULT_Q_MIN: u32 = 12; // no more than 63, litter than q_max
|
||||
|
||||
// Only positive speeds, range for real-time coding currently is: 6 - 8.
|
||||
// Lower means slower/better quality, higher means fastest/lower quality.
|
||||
@@ -119,14 +102,31 @@ mod webrtc {
|
||||
// Overwrite default config with input encoder settings & RTC-relevant values.
|
||||
c.g_w = cfg.width;
|
||||
c.g_h = cfg.height;
|
||||
c.g_threads = number_of_threads(cfg.width, cfg.height, num_cpus::get());
|
||||
c.g_threads = codec_thread_num() as _;
|
||||
c.g_timebase.num = 1;
|
||||
c.g_timebase.den = kRtpTicksPerSecond;
|
||||
c.rc_target_bitrate = cfg.bitrate; // kilobits/sec.
|
||||
c.g_input_bit_depth = kBitDepth;
|
||||
c.kf_mode = aom_kf_mode::AOM_KF_DISABLED;
|
||||
c.rc_min_quantizer = kQpMin;
|
||||
c.rc_max_quantizer = kQpMax;
|
||||
if let Some(keyframe_interval) = cfg.keyframe_interval {
|
||||
c.kf_min_dist = 0;
|
||||
c.kf_max_dist = keyframe_interval as _;
|
||||
} else {
|
||||
c.kf_mode = aom_kf_mode::AOM_KF_DISABLED;
|
||||
}
|
||||
let (q_min, q_max, b) = AomEncoder::convert_quality(cfg.quality);
|
||||
if q_min > 0 && q_min < q_max && q_max < 64 {
|
||||
c.rc_min_quantizer = q_min;
|
||||
c.rc_max_quantizer = q_max;
|
||||
} else {
|
||||
c.rc_min_quantizer = DEFAULT_Q_MIN;
|
||||
c.rc_max_quantizer = DEFAULT_Q_MAX;
|
||||
}
|
||||
let base_bitrate = base_bitrate(cfg.width as _, cfg.height as _);
|
||||
let bitrate = base_bitrate * b / 100;
|
||||
if bitrate > 0 {
|
||||
c.rc_target_bitrate = bitrate;
|
||||
} else {
|
||||
c.rc_target_bitrate = base_bitrate;
|
||||
}
|
||||
c.rc_undershoot_pct = 50;
|
||||
c.rc_overshoot_pct = 50;
|
||||
c.rc_buf_initial_sz = 600;
|
||||
@@ -259,11 +259,24 @@ impl EncoderApi for AomEncoder {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()> {
|
||||
let mut new_enc_cfg = unsafe { *self.ctx.config.enc.to_owned() };
|
||||
new_enc_cfg.rc_target_bitrate = bitrate;
|
||||
call_aom!(aom_codec_enc_config_set(&mut self.ctx, &new_enc_cfg));
|
||||
return Ok(());
|
||||
fn set_quality(&mut self, quality: Quality) -> ResultType<()> {
|
||||
let mut c = unsafe { *self.ctx.config.enc.to_owned() };
|
||||
let (q_min, q_max, b) = Self::convert_quality(quality);
|
||||
if q_min > 0 && q_min < q_max && q_max < 64 {
|
||||
c.rc_min_quantizer = q_min;
|
||||
c.rc_max_quantizer = q_max;
|
||||
}
|
||||
let bitrate = base_bitrate(self.width as _, self.height as _) * b / 100;
|
||||
if bitrate > 0 {
|
||||
c.rc_target_bitrate = bitrate;
|
||||
}
|
||||
call_aom!(aom_codec_enc_config_set(&mut self.ctx, &c));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bitrate(&self) -> u32 {
|
||||
let c = unsafe { *self.ctx.config.enc.to_owned() };
|
||||
c.rc_target_bitrate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,6 +332,35 @@ impl AomEncoder {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_quality(quality: Quality) -> (u32, u32, u32) {
|
||||
// we can use lower bitrate for av1
|
||||
match quality {
|
||||
Quality::Best => (12, 25, 100),
|
||||
Quality::Balanced => (12, 35, 100 * 2 / 3),
|
||||
Quality::Low => (18, 45, 50),
|
||||
Quality::Custom(b) => {
|
||||
let (q_min, q_max) = Self::calc_q_values(b);
|
||||
(q_min, q_max, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calc_q_values(b: u32) -> (u32, u32) {
|
||||
let b = std::cmp::min(b, 200);
|
||||
let q_min1: i32 = 24;
|
||||
let q_min2 = 5;
|
||||
let q_max1 = 45;
|
||||
let q_max2 = 25;
|
||||
|
||||
let t = b as f32 / 200.0;
|
||||
|
||||
let q_min: u32 = ((1.0 - t) * q_min1 as f32 + t * q_min2 as f32).round() as u32;
|
||||
let q_max = ((1.0 - t) * q_max1 as f32 + t * q_max2 as f32).round() as u32;
|
||||
|
||||
(q_min, q_max)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AomEncoder {
|
||||
@@ -360,24 +402,16 @@ impl<'a> Iterator for EncodeFrames<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AomDecoderConfig {
|
||||
pub num_threads: u32,
|
||||
}
|
||||
|
||||
pub struct AomDecoder {
|
||||
ctx: aom_codec_ctx_t,
|
||||
}
|
||||
|
||||
impl AomDecoder {
|
||||
pub fn new(cfg: AomDecoderConfig) -> Result<Self> {
|
||||
pub fn new() -> Result<Self> {
|
||||
let i = call_aom_ptr!(aom_codec_av1_dx());
|
||||
let mut ctx = Default::default();
|
||||
let cfg = aom_codec_dec_cfg_t {
|
||||
threads: if cfg.num_threads == 0 {
|
||||
num_cpus::get() as _
|
||||
} else {
|
||||
cfg.num_threads
|
||||
},
|
||||
threads: codec_thread_num() as _,
|
||||
w: 0,
|
||||
h: 0,
|
||||
allow_lowbitdepth: 1,
|
||||
|
||||
@@ -11,22 +11,23 @@ use crate::mediacodec::{
|
||||
MediaCodecDecoder, MediaCodecDecoders, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT,
|
||||
};
|
||||
use crate::{
|
||||
aom::{self, AomDecoder, AomDecoderConfig, AomEncoder, AomEncoderConfig},
|
||||
aom::{self, AomDecoder, AomEncoder, AomEncoderConfig},
|
||||
common::GoogleImage,
|
||||
vpxcodec::{self, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig, VpxVideoCodecId},
|
||||
CodecName, ImageRgb,
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::sysinfo::{System, SystemExt};
|
||||
use hbb_common::{
|
||||
anyhow::anyhow,
|
||||
bail,
|
||||
config::PeerConfig,
|
||||
log,
|
||||
message_proto::{
|
||||
supported_decoding::PreferCodec, video_frame, EncodedVideoFrames, Message,
|
||||
SupportedDecoding, SupportedEncoding,
|
||||
},
|
||||
sysinfo::{System, SystemExt},
|
||||
tokio::time::Instant,
|
||||
ResultType,
|
||||
};
|
||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
||||
@@ -35,6 +36,7 @@ use hbb_common::{config::Config2, lazy_static};
|
||||
lazy_static::lazy_static! {
|
||||
static ref PEER_DECODINGS: Arc<Mutex<HashMap<i32, SupportedDecoding>>> = Default::default();
|
||||
static ref CODEC_NAME: Arc<Mutex<CodecName>> = Arc::new(Mutex::new(CodecName::VP9));
|
||||
static ref THREAD_LOG_TIME: Arc<Mutex<Option<Instant>>> = Arc::new(Mutex::new(None));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -42,7 +44,8 @@ pub struct HwEncoderConfig {
|
||||
pub name: String,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub bitrate: i32,
|
||||
pub quality: Quality,
|
||||
pub keyframe_interval: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -61,7 +64,9 @@ pub trait EncoderApi {
|
||||
|
||||
fn use_yuv(&self) -> bool;
|
||||
|
||||
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()>;
|
||||
fn set_quality(&mut self, quality: Quality) -> ResultType<()>;
|
||||
|
||||
fn bitrate(&self) -> u32;
|
||||
}
|
||||
|
||||
pub struct Encoder {
|
||||
@@ -83,9 +88,9 @@ impl DerefMut for Encoder {
|
||||
}
|
||||
|
||||
pub struct Decoder {
|
||||
vp8: VpxDecoder,
|
||||
vp9: VpxDecoder,
|
||||
av1: AomDecoder,
|
||||
vp8: Option<VpxDecoder>,
|
||||
vp9: Option<VpxDecoder>,
|
||||
av1: Option<AomDecoder>,
|
||||
#[cfg(feature = "hwcodec")]
|
||||
hw: HwDecoders,
|
||||
#[cfg(feature = "hwcodec")]
|
||||
@@ -190,7 +195,6 @@ impl Encoder {
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut auto_codec = CodecName::VP9;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if vp8_useable && System::new_all().total_memory() <= 4 * 1024 * 1024 * 1024 {
|
||||
// 4 Gb
|
||||
auto_codec = CodecName::VP8
|
||||
@@ -274,18 +278,13 @@ impl Decoder {
|
||||
pub fn new() -> Decoder {
|
||||
let vp8 = VpxDecoder::new(VpxDecoderConfig {
|
||||
codec: VpxVideoCodecId::VP8,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
})
|
||||
.unwrap();
|
||||
.ok();
|
||||
let vp9 = VpxDecoder::new(VpxDecoderConfig {
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
})
|
||||
.unwrap();
|
||||
let av1 = AomDecoder::new(AomDecoderConfig {
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
})
|
||||
.unwrap();
|
||||
.ok();
|
||||
let av1 = AomDecoder::new().ok();
|
||||
Decoder {
|
||||
vp8,
|
||||
vp9,
|
||||
@@ -315,13 +314,25 @@ impl Decoder {
|
||||
) -> ResultType<bool> {
|
||||
match frame {
|
||||
video_frame::Union::Vp8s(vp8s) => {
|
||||
Decoder::handle_vpxs_video_frame(&mut self.vp8, vp8s, rgb)
|
||||
if let Some(vp8) = &mut self.vp8 {
|
||||
Decoder::handle_vpxs_video_frame(vp8, vp8s, rgb)
|
||||
} else {
|
||||
bail!("vp8 decoder not available");
|
||||
}
|
||||
}
|
||||
video_frame::Union::Vp9s(vp9s) => {
|
||||
Decoder::handle_vpxs_video_frame(&mut self.vp9, vp9s, rgb)
|
||||
if let Some(vp9) = &mut self.vp9 {
|
||||
Decoder::handle_vpxs_video_frame(vp9, vp9s, rgb)
|
||||
} else {
|
||||
bail!("vp9 decoder not available");
|
||||
}
|
||||
}
|
||||
video_frame::Union::Av1s(av1s) => {
|
||||
Decoder::handle_av1s_video_frame(&mut self.av1, av1s, rgb)
|
||||
if let Some(av1) = &mut self.av1 {
|
||||
Decoder::handle_av1s_video_frame(av1, av1s, rgb)
|
||||
} else {
|
||||
bail!("av1 decoder not available");
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
video_frame::Union::H264s(h264s) => {
|
||||
@@ -471,3 +482,72 @@ fn enable_hwcodec_option() -> bool {
|
||||
}
|
||||
return true; // default is true
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Quality {
|
||||
Best,
|
||||
Balanced,
|
||||
Low,
|
||||
Custom(u32),
|
||||
}
|
||||
|
||||
impl Default for Quality {
|
||||
fn default() -> Self {
|
||||
Self::Balanced
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_bitrate(width: u32, height: u32) -> u32 {
|
||||
#[allow(unused_mut)]
|
||||
let mut base_bitrate = ((width * height) / 1000) as u32; // same as 1.1.9
|
||||
if base_bitrate == 0 {
|
||||
base_bitrate = 1920 * 1080 / 1000;
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
// fix when android screen shrinks
|
||||
let fix = crate::Display::fix_quality() as u32;
|
||||
log::debug!("Android screen, fix quality:{}", fix);
|
||||
base_bitrate = base_bitrate * fix;
|
||||
}
|
||||
base_bitrate
|
||||
}
|
||||
|
||||
pub fn codec_thread_num() -> usize {
|
||||
let max: usize = num_cpus::get();
|
||||
let mut res;
|
||||
let info;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
res = 0;
|
||||
let percent = hbb_common::platform::windows::cpu_uage_one_minute();
|
||||
info = format!("cpu usage:{:?}", percent);
|
||||
if let Some(pecent) = percent {
|
||||
if pecent < 100.0 {
|
||||
res = ((100.0 - pecent) * (max as f64) / 200.0).round() as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let s = System::new_all();
|
||||
// https://man7.org/linux/man-pages/man3/getloadavg.3.html
|
||||
let avg = s.load_average();
|
||||
info = format!("cpu loadavg:{}", avg.one);
|
||||
res = (((max as f64) - avg.one) * 0.5).round() as usize;
|
||||
}
|
||||
res = std::cmp::min(res, max / 2);
|
||||
if res == 0 {
|
||||
res = 1;
|
||||
}
|
||||
// avoid frequent log
|
||||
let log = match THREAD_LOG_TIME.lock().unwrap().clone() {
|
||||
Some(instant) => instant.elapsed().as_secs() > 1,
|
||||
None => true,
|
||||
};
|
||||
if log {
|
||||
log::info!("cpu num:{max}, {info}, codec thread:{res}");
|
||||
*THREAD_LOG_TIME.lock().unwrap() = Some(Instant::now());
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
codec::{EncoderApi, EncoderCfg},
|
||||
codec::{base_bitrate, codec_thread_num, EncoderApi, EncoderCfg},
|
||||
hw, ImageFormat, ImageRgb, HW_STRIDE_ALIGN,
|
||||
};
|
||||
use hbb_common::{
|
||||
@@ -34,6 +34,9 @@ pub struct HwEncoder {
|
||||
yuv: Vec<u8>,
|
||||
pub format: DataFormat,
|
||||
pub pixfmt: AVPixelFormat,
|
||||
width: u32,
|
||||
height: u32,
|
||||
bitrate: u32, //kbs
|
||||
}
|
||||
|
||||
impl EncoderApi for HwEncoder {
|
||||
@@ -43,17 +46,25 @@ impl EncoderApi for HwEncoder {
|
||||
{
|
||||
match cfg {
|
||||
EncoderCfg::HW(config) => {
|
||||
let b = Self::convert_quality(config.quality);
|
||||
let base_bitrate = base_bitrate(config.width as _, config.height as _);
|
||||
let mut bitrate = base_bitrate * b / 100;
|
||||
if base_bitrate <= 0 {
|
||||
bitrate = base_bitrate;
|
||||
}
|
||||
let gop = config.keyframe_interval.unwrap_or(DEFAULT_GOP as _) as i32;
|
||||
let ctx = EncodeContext {
|
||||
name: config.name.clone(),
|
||||
width: config.width as _,
|
||||
height: config.height as _,
|
||||
pixfmt: DEFAULT_PIXFMT,
|
||||
align: HW_STRIDE_ALIGN as _,
|
||||
bitrate: config.bitrate * 1000,
|
||||
bitrate: bitrate as i32 * 1000,
|
||||
timebase: DEFAULT_TIME_BASE,
|
||||
gop: DEFAULT_GOP,
|
||||
gop,
|
||||
quality: DEFAULT_HW_QUALITY,
|
||||
rc: DEFAULT_RC,
|
||||
thread_count: codec_thread_num() as _, // ffmpeg's thread_count is used for cpu
|
||||
};
|
||||
let format = match Encoder::format_from_name(config.name.clone()) {
|
||||
Ok(format) => format,
|
||||
@@ -70,6 +81,9 @@ impl EncoderApi for HwEncoder {
|
||||
yuv: vec![],
|
||||
format,
|
||||
pixfmt: ctx.pixfmt,
|
||||
width: ctx.width as _,
|
||||
height: ctx.height as _,
|
||||
bitrate,
|
||||
}),
|
||||
Err(_) => Err(anyhow!(format!("Failed to create encoder"))),
|
||||
}
|
||||
@@ -114,10 +128,19 @@ impl EncoderApi for HwEncoder {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()> {
|
||||
self.encoder.set_bitrate((bitrate * 1000) as _).ok();
|
||||
fn set_quality(&mut self, quality: crate::codec::Quality) -> ResultType<()> {
|
||||
let b = Self::convert_quality(quality);
|
||||
let bitrate = base_bitrate(self.width as _, self.height as _) * b / 100;
|
||||
if bitrate > 0 {
|
||||
self.encoder.set_bitrate((bitrate * 1000) as _).ok();
|
||||
self.bitrate = bitrate;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bitrate(&self) -> u32 {
|
||||
self.bitrate
|
||||
}
|
||||
}
|
||||
|
||||
impl HwEncoder {
|
||||
@@ -159,6 +182,16 @@ impl HwEncoder {
|
||||
Err(_) => Ok(Vec::<EncodeFrame>::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_quality(quality: crate::codec::Quality) -> u32 {
|
||||
use crate::codec::Quality;
|
||||
match quality {
|
||||
Quality::Best => 150,
|
||||
Quality::Balanced => 100,
|
||||
Quality::Low => 50,
|
||||
Quality::Custom(b) => b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HwDecoder {
|
||||
@@ -208,6 +241,7 @@ impl HwDecoder {
|
||||
let ctx = DecodeContext {
|
||||
name: info.name.clone(),
|
||||
device_type: info.hwdevice.clone(),
|
||||
thread_count: codec_thread_num() as _,
|
||||
};
|
||||
match Decoder::new(ctx) {
|
||||
Ok(decoder) => Ok(HwDecoder { decoder, info }),
|
||||
@@ -281,7 +315,7 @@ impl HwDecoderImage<'_> {
|
||||
}
|
||||
|
||||
fn get_config(k: &str) -> ResultType<CodecInfos> {
|
||||
let v = HwCodecConfig::get()
|
||||
let v = HwCodecConfig::load()
|
||||
.options
|
||||
.get(k)
|
||||
.unwrap_or(&"".to_owned())
|
||||
@@ -304,6 +338,7 @@ pub fn check_config() {
|
||||
gop: DEFAULT_GOP,
|
||||
quality: DEFAULT_HW_QUALITY,
|
||||
rc: DEFAULT_RC,
|
||||
thread_count: 4,
|
||||
};
|
||||
let encoders = CodecInfo::score(Encoder::available_encoders(ctx));
|
||||
let decoders = CodecInfo::score(Decoder::available_decoders());
|
||||
@@ -329,37 +364,28 @@ pub fn check_config() {
|
||||
}
|
||||
|
||||
pub fn check_config_process() {
|
||||
use hbb_common::sysinfo::{ProcessExt, System, SystemExt};
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// Remove to avoid checking process errors
|
||||
use std::sync::Once;
|
||||
let f = || {
|
||||
// Clear to avoid checking process errors
|
||||
// But when the program is just started, the configuration file has not been updated, and the new connection will read an empty configuration
|
||||
HwCodecConfig::remove();
|
||||
HwCodecConfig::clear();
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(file_name) = exe.file_name().to_owned() {
|
||||
let s = System::new_all();
|
||||
if let Some(_) = exe.file_name().to_owned() {
|
||||
let arg = "--check-hwcodec-config";
|
||||
for process in s.processes_by_name(&file_name.to_string_lossy().to_string()) {
|
||||
if process.cmd().iter().any(|cmd| cmd.contains(arg)) {
|
||||
log::warn!("already have process {}", arg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Ok(mut child) = std::process::Command::new(exe).arg(arg).spawn() {
|
||||
// wait up to 10 seconds
|
||||
for _ in 0..10 {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(Some(status)) = child.try_wait() {
|
||||
if status.success() {
|
||||
HwCodecConfig::refresh();
|
||||
}
|
||||
if let Ok(Some(_)) = child.try_wait() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
allow_err!(child.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => log::info!("Check hwcodec config, exit with: {status}"),
|
||||
Ok(Some(status)) => {
|
||||
log::info!("Check hwcodec config, exit with: {status}")
|
||||
}
|
||||
Ok(None) => {
|
||||
log::info!(
|
||||
"Check hwcodec config, status not ready yet, let's really wait"
|
||||
@@ -371,9 +397,12 @@ pub fn check_config_process() {
|
||||
log::error!("Check hwcodec config, error attempting to wait: {e}")
|
||||
}
|
||||
}
|
||||
HwCodecConfig::refresh();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
static ONCE: Once = Once::new();
|
||||
ONCE.call_once(|| {
|
||||
std::thread::spawn(f);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,19 +7,21 @@ use hbb_common::log;
|
||||
use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame};
|
||||
use hbb_common::ResultType;
|
||||
|
||||
use crate::codec::EncoderApi;
|
||||
use crate::codec::{base_bitrate, codec_thread_num, EncoderApi, Quality};
|
||||
use crate::{GoogleImage, STRIDE_ALIGN};
|
||||
|
||||
use super::vpx::{vpx_codec_err_t::*, *};
|
||||
use super::vpx::{vp8e_enc_control_id::*, vpx_codec_err_t::*, *};
|
||||
use crate::{generate_call_macro, generate_call_ptr_macro, Error, Result};
|
||||
use hbb_common::bytes::Bytes;
|
||||
use std::os::raw::c_uint;
|
||||
use std::os::raw::{c_int, c_uint};
|
||||
use std::{ptr, slice};
|
||||
|
||||
generate_call_macro!(call_vpx, false);
|
||||
generate_call_macro!(call_vpx_allow_err, true);
|
||||
generate_call_ptr_macro!(call_vpx_ptr);
|
||||
|
||||
const DEFAULT_QP_MAX: u32 = 56; // no more than 63
|
||||
const DEFAULT_QP_MIN: u32 = 12; // no more than 63
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum VpxVideoCodecId {
|
||||
VP8,
|
||||
@@ -54,11 +56,56 @@ impl EncoderApi for VpxEncoder {
|
||||
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
|
||||
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
|
||||
};
|
||||
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
||||
|
||||
let c = match config.codec {
|
||||
VpxVideoCodecId::VP8 => webrtc::vp8::enc_cfg(i, &config)?,
|
||||
VpxVideoCodecId::VP9 => webrtc::vp9::enc_cfg(i, &config)?,
|
||||
};
|
||||
// https://www.webmproject.org/docs/encoder-parameters/
|
||||
// default: c.rc_min_quantizer = 0, c.rc_max_quantizer = 63
|
||||
// try rc_resize_allowed later
|
||||
|
||||
c.g_w = config.width;
|
||||
c.g_h = config.height;
|
||||
c.g_timebase.num = 1;
|
||||
c.g_timebase.den = 1000; // Output timestamp precision
|
||||
c.rc_undershoot_pct = 95;
|
||||
// When the data buffer falls below this percentage of fullness, a dropped frame is indicated. Set the threshold to zero (0) to disable this feature.
|
||||
// In dynamic scenes, low bitrate gets low fps while high bitrate gets high fps.
|
||||
c.rc_dropframe_thresh = 25;
|
||||
c.g_threads = codec_thread_num() as _;
|
||||
c.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
|
||||
// https://developers.google.com/media/vp9/bitrate-modes/
|
||||
// Constant Bitrate mode (CBR) is recommended for live streaming with VP9.
|
||||
c.rc_end_usage = vpx_rc_mode::VPX_CBR;
|
||||
if let Some(keyframe_interval) = config.keyframe_interval {
|
||||
c.kf_min_dist = 0;
|
||||
c.kf_max_dist = keyframe_interval as _;
|
||||
} else {
|
||||
c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED; // reduce bandwidth a lot
|
||||
}
|
||||
|
||||
let (q_min, q_max, b) = Self::convert_quality(config.quality);
|
||||
if q_min > 0 && q_min < q_max && q_max < 64 {
|
||||
c.rc_min_quantizer = q_min;
|
||||
c.rc_max_quantizer = q_max;
|
||||
} else {
|
||||
c.rc_min_quantizer = DEFAULT_QP_MIN;
|
||||
c.rc_max_quantizer = DEFAULT_QP_MAX;
|
||||
}
|
||||
let base_bitrate = base_bitrate(config.width as _, config.height as _);
|
||||
let bitrate = base_bitrate * b / 100;
|
||||
if bitrate > 0 {
|
||||
c.rc_target_bitrate = bitrate;
|
||||
} else {
|
||||
c.rc_target_bitrate = base_bitrate;
|
||||
}
|
||||
|
||||
/*
|
||||
The VPX encoder supports two-pass encoding for rate control purposes.
|
||||
In two-pass encoding, the entire encoding process is performed twice.
|
||||
The first pass generates new control parameters for the second pass.
|
||||
|
||||
This approach enables the best PSNR at the same bit rate.
|
||||
*/
|
||||
|
||||
let mut ctx = Default::default();
|
||||
call_vpx!(vpx_codec_enc_init_ver(
|
||||
@@ -68,9 +115,50 @@ impl EncoderApi for VpxEncoder {
|
||||
0,
|
||||
VPX_ENCODER_ABI_VERSION as _
|
||||
));
|
||||
match config.codec {
|
||||
VpxVideoCodecId::VP8 => webrtc::vp8::set_control(&mut ctx, &c)?,
|
||||
VpxVideoCodecId::VP9 => webrtc::vp9::set_control(&mut ctx, &c)?,
|
||||
|
||||
if config.codec == VpxVideoCodecId::VP9 {
|
||||
// set encoder internal speed settings
|
||||
// in ffmpeg, it is --speed option
|
||||
/*
|
||||
set to 0 or a positive value 1-16, the codec will try to adapt its
|
||||
complexity depending on the time it spends encoding. Increasing this
|
||||
number will make the speed go up and the quality go down.
|
||||
Negative values mean strict enforcement of this
|
||||
while positive values are adaptive
|
||||
*/
|
||||
/* https://developers.google.com/media/vp9/live-encoding
|
||||
Speed 5 to 8 should be used for live / real-time encoding.
|
||||
Lower numbers (5 or 6) are higher quality but require more CPU power.
|
||||
Higher numbers (7 or 8) will be lower quality but more manageable for lower latency
|
||||
use cases and also for lower CPU power devices such as mobile.
|
||||
*/
|
||||
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, 7,));
|
||||
// set row level multi-threading
|
||||
/*
|
||||
as some people in comments and below have already commented,
|
||||
more recent versions of libvpx support -row-mt 1 to enable tile row
|
||||
multi-threading. This can increase the number of tiles by up to 4x in VP9
|
||||
(since the max number of tile rows is 4, regardless of video height).
|
||||
To enable this, use -tile-rows N where N is the number of tile rows in
|
||||
log2 units (so -tile-rows 1 means 2 tile rows and -tile-rows 2 means 4 tile
|
||||
rows). The total number of active threads will then be equal to
|
||||
$tile_rows * $tile_columns
|
||||
*/
|
||||
call_vpx!(vpx_codec_control_(
|
||||
&mut ctx,
|
||||
VP9E_SET_ROW_MT as _,
|
||||
1 as c_int
|
||||
));
|
||||
|
||||
call_vpx!(vpx_codec_control_(
|
||||
&mut ctx,
|
||||
VP9E_SET_TILE_COLUMNS as _,
|
||||
4 as c_int
|
||||
));
|
||||
} else if config.codec == VpxVideoCodecId::VP8 {
|
||||
// https://github.com/webmproject/libvpx/blob/972149cafeb71d6f08df89e91a0130d6a38c4b15/vpx/vp8cx.h#L172
|
||||
// https://groups.google.com/a/webmproject.org/g/webm-discuss/c/DJhSrmfQ61M
|
||||
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, 12,));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
@@ -108,11 +196,24 @@ impl EncoderApi for VpxEncoder {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()> {
|
||||
let mut new_enc_cfg = unsafe { *self.ctx.config.enc.to_owned() };
|
||||
new_enc_cfg.rc_target_bitrate = bitrate;
|
||||
call_vpx!(vpx_codec_enc_config_set(&mut self.ctx, &new_enc_cfg));
|
||||
return Ok(());
|
||||
fn set_quality(&mut self, quality: Quality) -> ResultType<()> {
|
||||
let mut c = unsafe { *self.ctx.config.enc.to_owned() };
|
||||
let (q_min, q_max, b) = Self::convert_quality(quality);
|
||||
if q_min > 0 && q_min < q_max && q_max < 64 {
|
||||
c.rc_min_quantizer = q_min;
|
||||
c.rc_max_quantizer = q_max;
|
||||
}
|
||||
let bitrate = base_bitrate(self.width as _, self.height as _) * b / 100;
|
||||
if bitrate > 0 {
|
||||
c.rc_target_bitrate = bitrate;
|
||||
}
|
||||
call_vpx!(vpx_codec_enc_config_set(&mut self.ctx, &c));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bitrate(&self) -> u32 {
|
||||
let c = unsafe { *self.ctx.config.enc.to_owned() };
|
||||
c.rc_target_bitrate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +290,34 @@ impl VpxEncoder {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_quality(quality: Quality) -> (u32, u32, u32) {
|
||||
match quality {
|
||||
Quality::Best => (6, 45, 150),
|
||||
Quality::Balanced => (12, 56, 100 * 2 / 3),
|
||||
Quality::Low => (18, 56, 50),
|
||||
Quality::Custom(b) => {
|
||||
let (q_min, q_max) = Self::calc_q_values(b);
|
||||
(q_min, q_max, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calc_q_values(b: u32) -> (u32, u32) {
|
||||
let b = std::cmp::min(b, 200);
|
||||
let q_min1: i32 = 36;
|
||||
let q_min2 = 0;
|
||||
let q_max1 = 56;
|
||||
let q_max2 = 37;
|
||||
|
||||
let t = b as f32 / 200.0;
|
||||
|
||||
let q_min: u32 = ((1.0 - t) * q_min1 as f32 + t * q_min2 as f32).round() as u32;
|
||||
let q_max = ((1.0 - t) * q_max1 as f32 + t * q_max2 as f32).round() as u32;
|
||||
|
||||
(q_min, q_max)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VpxEncoder {
|
||||
@@ -218,16 +347,17 @@ pub struct VpxEncoderConfig {
|
||||
pub width: c_uint,
|
||||
/// The height (in pixels).
|
||||
pub height: c_uint,
|
||||
/// The target bitrate (in kilobits per second).
|
||||
pub bitrate: c_uint,
|
||||
/// The image quality
|
||||
pub quality: Quality,
|
||||
/// The codec
|
||||
pub codec: VpxVideoCodecId,
|
||||
/// keyframe interval
|
||||
pub keyframe_interval: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct VpxDecoderConfig {
|
||||
pub codec: VpxVideoCodecId,
|
||||
pub num_threads: u32,
|
||||
}
|
||||
|
||||
pub struct EncodeFrames<'a> {
|
||||
@@ -274,11 +404,7 @@ impl VpxDecoder {
|
||||
};
|
||||
let mut ctx = Default::default();
|
||||
let cfg = vpx_codec_dec_cfg_t {
|
||||
threads: if config.num_threads == 0 {
|
||||
num_cpus::get() as _
|
||||
} else {
|
||||
config.num_threads
|
||||
},
|
||||
threads: codec_thread_num() as _,
|
||||
w: 0,
|
||||
h: 0,
|
||||
};
|
||||
@@ -417,371 +543,3 @@ impl Drop for Image {
|
||||
}
|
||||
|
||||
unsafe impl Send for vpx_codec_ctx_t {}
|
||||
|
||||
mod webrtc {
|
||||
use super::*;
|
||||
|
||||
const K_QP_MAX: u32 = 25; // worth adjusting
|
||||
const MODE: VideoCodecMode = VideoCodecMode::KScreensharing;
|
||||
const K_RTP_TICKS_PER_SECOND: i32 = 90000;
|
||||
const NUMBER_OF_TEMPORAL_LAYERS: u32 = 1;
|
||||
const DENOISING_ON: bool = true;
|
||||
const FRAME_DROP_ENABLED: bool = false;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum VideoCodecMode {
|
||||
KRealtimeVideo,
|
||||
KScreensharing,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum VideoCodecComplexity {
|
||||
KComplexityLow = -1,
|
||||
KComplexityNormal = 0,
|
||||
KComplexityHigh = 1,
|
||||
KComplexityHigher = 2,
|
||||
KComplexityMax = 3,
|
||||
}
|
||||
|
||||
// https://webrtc.googlesource.com/src/+/refs/heads/main/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc
|
||||
pub mod vp9 {
|
||||
use super::*;
|
||||
const SVC: bool = false;
|
||||
// https://webrtc.googlesource.com/src/+/refs/heads/main/api/video_codecs/video_encoder.cc#35
|
||||
const KEY_FRAME_INTERVAL: u32 = 3000;
|
||||
const ADAPTIVE_QP_MODE: bool = true;
|
||||
|
||||
pub fn enc_cfg(
|
||||
i: *const vpx_codec_iface_t,
|
||||
cfg: &VpxEncoderConfig,
|
||||
) -> ResultType<vpx_codec_enc_cfg_t> {
|
||||
let mut c: vpx_codec_enc_cfg_t =
|
||||
unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
||||
|
||||
// kProfile0
|
||||
c.g_bit_depth = vpx_bit_depth::VPX_BITS_8;
|
||||
c.g_profile = 0;
|
||||
c.g_input_bit_depth = 8;
|
||||
|
||||
c.g_w = cfg.width;
|
||||
c.g_h = cfg.height;
|
||||
c.rc_target_bitrate = cfg.bitrate; // in kbit/s
|
||||
c.g_error_resilient = if SVC { VPX_ERROR_RESILIENT_DEFAULT } else { 0 };
|
||||
c.g_timebase.num = 1;
|
||||
c.g_timebase.den = K_RTP_TICKS_PER_SECOND;
|
||||
c.g_lag_in_frames = 0;
|
||||
c.rc_dropframe_thresh = if FRAME_DROP_ENABLED { 30 } else { 0 };
|
||||
c.rc_end_usage = vpx_rc_mode::VPX_CBR;
|
||||
c.g_pass = vpx_enc_pass::VPX_RC_ONE_PASS;
|
||||
c.rc_min_quantizer = if MODE == VideoCodecMode::KScreensharing {
|
||||
8
|
||||
} else {
|
||||
2
|
||||
};
|
||||
c.rc_max_quantizer = K_QP_MAX;
|
||||
c.rc_undershoot_pct = 50;
|
||||
c.rc_overshoot_pct = 50;
|
||||
c.rc_buf_initial_sz = 500;
|
||||
c.rc_buf_optimal_sz = 600;
|
||||
c.rc_buf_sz = 1000;
|
||||
// Key-frame interval is enforced manually by this wrapper.
|
||||
c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED;
|
||||
// TODO(webm:1592): work-around for libvpx issue, as it can still
|
||||
// put some key-frames at will even in VPX_KF_DISABLED kf_mode.
|
||||
c.kf_max_dist = KEY_FRAME_INTERVAL;
|
||||
c.kf_min_dist = c.kf_max_dist;
|
||||
c.rc_resize_allowed = 0;
|
||||
// Determine number of threads based on the image size and #cores.
|
||||
c.g_threads = number_of_threads(c.g_w, c.g_h, num_cpus::get());
|
||||
|
||||
c.temporal_layering_mode =
|
||||
vp9e_temporal_layering_mode::VP9E_TEMPORAL_LAYERING_MODE_NOLAYERING as _;
|
||||
c.ts_number_layers = 1;
|
||||
c.ts_rate_decimator[0] = 1;
|
||||
c.ts_periodicity = 1;
|
||||
c.ts_layer_id[0] = 0;
|
||||
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
pub fn set_control(ctx: *mut vpx_codec_ctx_t, cfg: &vpx_codec_enc_cfg_t) -> ResultType<()> {
|
||||
use vp8e_enc_control_id::*;
|
||||
|
||||
macro_rules! call_ctl {
|
||||
($ctx:expr, $vpxe:expr, $arg:expr) => {{
|
||||
call_vpx_allow_err!(vpx_codec_control_($ctx, $vpxe as i32, $arg));
|
||||
}};
|
||||
}
|
||||
call_ctl!(
|
||||
ctx,
|
||||
VP8E_SET_MAX_INTRA_BITRATE_PCT,
|
||||
max_intra_target(cfg.rc_buf_optimal_sz)
|
||||
);
|
||||
call_ctl!(ctx, VP9E_SET_AQ_MODE, if ADAPTIVE_QP_MODE { 3 } else { 0 });
|
||||
call_ctl!(ctx, VP9E_SET_FRAME_PARALLEL_DECODING, 0);
|
||||
#[cfg(not(any(target_arch = "arm", target_arch = "aarch64", target_os = "android")))]
|
||||
call_ctl!(ctx, VP9E_SET_SVC_GF_TEMPORAL_REF, 0);
|
||||
call_ctl!(
|
||||
ctx,
|
||||
VP8E_SET_CPUUSED,
|
||||
get_default_performance_flags(cfg.g_w, cfg.g_h).0
|
||||
);
|
||||
call_ctl!(ctx, VP9E_SET_TILE_COLUMNS, cfg.g_threads >> 1);
|
||||
// Turn on row-based multithreading.
|
||||
call_ctl!(ctx, VP9E_SET_ROW_MT, 1);
|
||||
let denoising = DENOISING_ON
|
||||
&& allow_denoising()
|
||||
&& get_default_performance_flags(cfg.g_w, cfg.g_h).1;
|
||||
call_ctl!(
|
||||
ctx,
|
||||
VP9E_SET_NOISE_SENSITIVITY,
|
||||
if denoising { 1 } else { 0 }
|
||||
);
|
||||
if MODE == VideoCodecMode::KScreensharing {
|
||||
call_ctl!(ctx, VP9E_SET_TUNE_CONTENT, 1);
|
||||
}
|
||||
// Enable encoder skip of static/low content blocks.
|
||||
call_ctl!(ctx, VP8E_SET_STATIC_THRESHOLD, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// return (base_layer_speed, allow_denoising)
|
||||
fn get_default_performance_flags(width: u32, height: u32) -> (u32, bool) {
|
||||
if cfg!(any(
|
||||
target_arch = "arm",
|
||||
target_arch = "aarch64",
|
||||
target_os = "android"
|
||||
)) {
|
||||
(8, true)
|
||||
} else if width * height < 352 * 288 {
|
||||
(5, true)
|
||||
} else if width * height < 1920 * 1080 {
|
||||
(7, true)
|
||||
} else {
|
||||
(9, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn allow_denoising() -> bool {
|
||||
// Do not enable the denoiser on ARM since optimization is pending.
|
||||
// Denoiser is on by default on other platforms.
|
||||
if cfg!(any(
|
||||
target_arch = "arm",
|
||||
target_arch = "aarch64",
|
||||
target_os = "android"
|
||||
)) {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn number_of_threads(width: u32, height: u32, number_of_cores: usize) -> u32 {
|
||||
// Keep the number of encoder threads equal to the possible number of column
|
||||
// tiles, which is (1, 2, 4, 8). See comments below for VP9E_SET_TILE_COLUMNS.
|
||||
if width * height >= 1280 * 720 && number_of_cores > 4 {
|
||||
return 4;
|
||||
} else if width * height >= 640 * 360 && number_of_cores > 2 {
|
||||
return 2;
|
||||
} else {
|
||||
// Use 2 threads for low res on ARM.
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64", target_os = "android"))]
|
||||
if width * height >= 320 * 180 && number_of_cores > 2 {
|
||||
return 2;
|
||||
}
|
||||
// 1 thread less than VGA.
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://webrtc.googlesource.com/src/+/refs/heads/main/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc
|
||||
pub mod vp8 {
|
||||
use super::*;
|
||||
// https://webrtc.googlesource.com/src/+/refs/heads/main/api/video_codecs/video_encoder.cc#23
|
||||
const DISABLE_KEY_FRAME_INTERVAL: bool = true;
|
||||
const KEY_FRAME_INTERVAL: u32 = 3000;
|
||||
const COMPLEXITY: VideoCodecComplexity = VideoCodecComplexity::KComplexityNormal;
|
||||
const K_TOKEN_PARTITIONS: vp8e_token_partitions =
|
||||
vp8e_token_partitions::VP8_ONE_TOKENPARTITION;
|
||||
|
||||
pub fn enc_cfg(
|
||||
i: *const vpx_codec_iface_t,
|
||||
cfg: &VpxEncoderConfig,
|
||||
) -> ResultType<vpx_codec_enc_cfg_t> {
|
||||
let mut c: vpx_codec_enc_cfg_t =
|
||||
unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
||||
|
||||
c.g_w = cfg.width;
|
||||
c.g_h = cfg.height;
|
||||
c.g_timebase.num = 1;
|
||||
c.g_timebase.den = K_RTP_TICKS_PER_SECOND;
|
||||
c.g_lag_in_frames = 0;
|
||||
c.g_error_resilient = if NUMBER_OF_TEMPORAL_LAYERS > 1 {
|
||||
VPX_ERROR_RESILIENT_DEFAULT
|
||||
} else {
|
||||
0
|
||||
};
|
||||
c.rc_end_usage = vpx_rc_mode::VPX_CBR;
|
||||
c.g_pass = vpx_enc_pass::VPX_RC_ONE_PASS;
|
||||
c.rc_resize_allowed = 0;
|
||||
c.rc_min_quantizer = if MODE == VideoCodecMode::KScreensharing {
|
||||
12
|
||||
} else {
|
||||
2
|
||||
};
|
||||
c.rc_max_quantizer = K_QP_MAX;
|
||||
c.rc_undershoot_pct = 100;
|
||||
c.rc_overshoot_pct = 15;
|
||||
c.rc_buf_initial_sz = 500;
|
||||
c.rc_buf_optimal_sz = 600;
|
||||
c.rc_buf_sz = 1000;
|
||||
if !DISABLE_KEY_FRAME_INTERVAL && KEY_FRAME_INTERVAL > 0 {
|
||||
c.kf_mode = vpx_kf_mode::VPX_KF_AUTO;
|
||||
c.kf_max_dist = KEY_FRAME_INTERVAL;
|
||||
} else {
|
||||
c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED;
|
||||
}
|
||||
c.g_threads = number_of_threads(c.g_w, c.g_h, num_cpus::get());
|
||||
c.rc_target_bitrate = cfg.bitrate;
|
||||
c.rc_dropframe_thresh = if FRAME_DROP_ENABLED { 30 } else { 0 };
|
||||
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
pub fn set_control(ctx: *mut vpx_codec_ctx_t, cfg: &vpx_codec_enc_cfg_t) -> ResultType<()> {
|
||||
use vp8e_enc_control_id::*;
|
||||
|
||||
macro_rules! call_ctl {
|
||||
($ctx:expr, $vpxe:expr, $arg:expr) => {{
|
||||
call_vpx_allow_err!(vpx_codec_control_($ctx, $vpxe as i32, $arg));
|
||||
}};
|
||||
}
|
||||
call_ctl!(
|
||||
ctx,
|
||||
VP8E_SET_STATIC_THRESHOLD,
|
||||
if MODE == VideoCodecMode::KScreensharing {
|
||||
100
|
||||
} else {
|
||||
1
|
||||
}
|
||||
);
|
||||
call_ctl!(
|
||||
ctx,
|
||||
VP8E_SET_CPUUSED,
|
||||
get_cpu_speed(cfg.g_w, cfg.g_h, num_cpus::get())
|
||||
);
|
||||
|
||||
call_ctl!(ctx, VP8E_SET_TOKEN_PARTITIONS, K_TOKEN_PARTITIONS);
|
||||
call_ctl!(
|
||||
ctx,
|
||||
VP8E_SET_MAX_INTRA_BITRATE_PCT,
|
||||
max_intra_target(cfg.rc_buf_optimal_sz)
|
||||
);
|
||||
call_ctl!(
|
||||
ctx,
|
||||
VP8E_SET_SCREEN_CONTENT_MODE,
|
||||
if MODE == VideoCodecMode::KScreensharing {
|
||||
2 // On with more aggressive rate control.
|
||||
} else {
|
||||
0
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_cpu_speed_default() -> i32 {
|
||||
match COMPLEXITY {
|
||||
VideoCodecComplexity::KComplexityHigh => -5,
|
||||
VideoCodecComplexity::KComplexityHigher => -4,
|
||||
VideoCodecComplexity::KComplexityMax => -3,
|
||||
_ => -6,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cpu_speed(width: u32, height: u32, number_of_cores: usize) -> i32 {
|
||||
if cfg!(any(
|
||||
target_arch = "arm",
|
||||
target_arch = "aarch64",
|
||||
target_os = "android"
|
||||
)) {
|
||||
if number_of_cores <= 3 {
|
||||
-12
|
||||
} else if width * height <= 352 * 288 {
|
||||
-8
|
||||
} else if width * height <= 640 * 480 {
|
||||
-10
|
||||
} else {
|
||||
-12
|
||||
}
|
||||
} else {
|
||||
let cpu_speed_default = get_cpu_speed_default();
|
||||
if width * height < 352 * 288 {
|
||||
if cpu_speed_default < -4 {
|
||||
-4
|
||||
} else {
|
||||
cpu_speed_default
|
||||
}
|
||||
} else {
|
||||
cpu_speed_default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn number_of_threads(width: u32, height: u32, cpus: usize) -> u32 {
|
||||
if cfg!(target_os = "android") {
|
||||
if width * height >= 320 * 180 {
|
||||
if cpus >= 4 {
|
||||
// 3 threads for CPUs with 4 and more cores since most of times only 4
|
||||
// cores will be active.
|
||||
3
|
||||
} else if cpus == 3 || cpus == 2 {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
} else {
|
||||
1
|
||||
}
|
||||
} else {
|
||||
if width * height >= 1920 * 1080 && cpus > 8 {
|
||||
8 // 8 threads for 1080p on high perf machines.
|
||||
} else if width * height > 1280 * 960 && cpus >= 6 {
|
||||
// 3 threads for 1080p.
|
||||
return 3;
|
||||
} else if width * height > 640 * 480 && cpus >= 3 {
|
||||
// Default 2 threads for qHD/HD, but allow 3 if core count is high enough,
|
||||
// as this will allow more margin for high-core/low clock machines or if
|
||||
// not built with highest optimization.
|
||||
if cpus >= 6 {
|
||||
3
|
||||
} else {
|
||||
2
|
||||
}
|
||||
} else {
|
||||
// 1 thread for VGA or less.
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn max_intra_target(optimal_buffer_size: u32) -> u32 {
|
||||
const MAX_FRAMERATE: u32 = 60; // TODO
|
||||
let scale_par: f32 = 0.5;
|
||||
let target_pct: u32 =
|
||||
((optimal_buffer_size as f32) * scale_par * MAX_FRAMERATE as f32 / 10.0) as u32;
|
||||
let min_intra_size: u32 = 300;
|
||||
if target_pct < min_intra_size {
|
||||
min_intra_size
|
||||
} else {
|
||||
target_pct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ impl MagInterface {
|
||||
unsafe {
|
||||
// load lib
|
||||
let lib_file_name = "Magnification.dll";
|
||||
let lib_file_name_c = CString::new(lib_file_name).unwrap();
|
||||
let lib_file_name_c = CString::new(lib_file_name)?;
|
||||
s.lib_handle = LoadLibraryExA(
|
||||
lib_file_name_c.as_ptr() as _,
|
||||
NULL,
|
||||
@@ -189,7 +189,7 @@ impl MagInterface {
|
||||
}
|
||||
|
||||
unsafe fn load_func(lib_module: HMODULE, func_name: &str) -> Result<FARPROC> {
|
||||
let func_name_c = CString::new(func_name).unwrap();
|
||||
let func_name_c = CString::new(func_name)?;
|
||||
let func = GetProcAddress(lib_module, func_name_c.as_ptr() as _);
|
||||
if func.is_null() {
|
||||
return Err(Error::new(
|
||||
@@ -442,7 +442,7 @@ impl CapturerMag {
|
||||
}
|
||||
|
||||
pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> {
|
||||
let name_c = CString::new(name).unwrap();
|
||||
let name_c = CString::new(name)?;
|
||||
unsafe {
|
||||
let mut hwnd = if cls.len() == 0 {
|
||||
FindWindowExA(NULL as _, NULL as _, NULL as _, name_c.as_ptr())
|
||||
|
||||
@@ -594,13 +594,14 @@ fn request_screen_cast(
|
||||
}
|
||||
let fd_res = fd_res.lock().unwrap();
|
||||
let streams_res = streams_res.lock().unwrap();
|
||||
if fd_res.is_some() && !streams_res.is_empty() {
|
||||
Ok((conn, fd_res.clone().unwrap(), streams_res.clone()))
|
||||
} else {
|
||||
Err(Box::new(DBusError(
|
||||
"Failed to obtain screen capture.".into(),
|
||||
)))
|
||||
if let Some(fd_res) = fd_res.clone() {
|
||||
if !streams_res.is_empty() {
|
||||
return Ok((conn, fd_res, streams_res.clone()));
|
||||
}
|
||||
}
|
||||
Err(Box::new(DBusError(
|
||||
"Failed to obtain screen capture.".into(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn get_capturables(capture_cursor: bool) -> Result<Vec<PipeWireCapturable>, Box<dyn Error>> {
|
||||
|
||||
@@ -30,7 +30,12 @@ fn prompt_input() -> u8 {
|
||||
unsafe fn plug_in(index: idd::UINT, edid: idd::UINT) {
|
||||
println!("Plug in monitor begin");
|
||||
if idd::FALSE == idd::MonitorPlugIn(index, edid, 25) {
|
||||
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
CStr::from_ptr(idd::GetLastMsg())
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
);
|
||||
} else {
|
||||
println!("Plug in monitor done");
|
||||
|
||||
@@ -46,7 +51,12 @@ unsafe fn plug_in(index: idd::UINT, edid: idd::UINT) {
|
||||
sync: 60 as idd::DWORD,
|
||||
});
|
||||
if idd::FALSE == idd::MonitorModesUpdate(index, modes.len() as u32, modes.as_mut_ptr()) {
|
||||
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
CStr::from_ptr(idd::GetLastMsg())
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +65,12 @@ unsafe fn plug_in(index: idd::UINT, edid: idd::UINT) {
|
||||
unsafe fn plug_out(index: idd::UINT) {
|
||||
println!("Plug out monitor begin");
|
||||
if idd::FALSE == idd::MonitorPlugOut(index) {
|
||||
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
CStr::from_ptr(idd::GetLastMsg())
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
);
|
||||
} else {
|
||||
println!("Plug out monitor done");
|
||||
}
|
||||
@@ -64,7 +79,13 @@ unsafe fn plug_out(index: idd::UINT) {
|
||||
fn main() {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let abs_path = Path::new(DRIVER_INSTALL_PATH).canonicalize().unwrap();
|
||||
let abs_path = match Path::new(DRIVER_INSTALL_PATH).canonicalize() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
println!("Failed to get absolute path of driver install: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let invalid_device = 0 as idd::HSWDEVICE;
|
||||
@@ -86,7 +107,12 @@ fn main() {
|
||||
if idd::InstallUpdate(full_inf_path.as_ptr() as _, &mut reboot_required)
|
||||
== idd::FALSE
|
||||
{
|
||||
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
CStr::from_ptr(idd::GetLastMsg())
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"Install or update driver done, reboot is {} required",
|
||||
@@ -104,7 +130,12 @@ fn main() {
|
||||
if idd::Uninstall(full_inf_path.as_ptr() as _, &mut reboot_required)
|
||||
== idd::FALSE
|
||||
{
|
||||
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
CStr::from_ptr(idd::GetLastMsg())
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"Uninstall driver done, reboot is {} required",
|
||||
@@ -123,7 +154,12 @@ fn main() {
|
||||
continue;
|
||||
}
|
||||
if idd::FALSE == idd::DeviceCreate(&mut h_sw_device) {
|
||||
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
CStr::from_ptr(idd::GetLastMsg())
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
);
|
||||
idd::DeviceClose(h_sw_device);
|
||||
h_sw_device = invalid_device;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user