Merge remote-tracking branch 'origin/master' into feat/x11/clipboard-file/init

Signed-off-by: 蔡略 <cailue@bupt.edu.cn>
This commit is contained in:
蔡略
2023-09-08 20:09:57 +08:00
302 changed files with 14257 additions and 6606 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -54,7 +54,7 @@ fn execute(path: PathBuf, args: Vec<String>) {
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.spawn()
.ok();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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