feat: macos, audio, loopback (#10025)
Some checks are pending
CI / ${{ matrix.job.target }} (${{ matrix.job.os }}) (map[os:ubuntu-20.04 target:x86_64-unknown-linux-gnu]) (push) Waiting to run
Full Flutter CI / run-ci (push) Waiting to run

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2024-11-23 18:41:27 +08:00 committed by GitHub
parent 02b046bdbf
commit 0973f51df9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 105 additions and 21 deletions

View File

@ -20,6 +20,7 @@ on:
env: env:
SCITER_RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503, also 1.78 has ABI change which causes our sciter version not working, https://blog.rust-lang.org/2024/03/30/i128-layout-update.html SCITER_RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503, also 1.78 has ABI change which causes our sciter version not working, https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
RUST_VERSION: "1.75" # sciter failed on m1 with 1.78 because of https://blog.rust-lang.org/2024/03/30/i128-layout-update.html RUST_VERSION: "1.75" # sciter failed on m1 with 1.78 because of https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
MAC_RUST_VERSION: "1.81" # 1.81 is requred for macos, because of https://github.com/yury/cidre requires 1.81
CARGO_NDK_VERSION: "3.1.2" CARGO_NDK_VERSION: "3.1.2"
SCITER_ARMV7_CMAKE_VERSION: "3.29.7" SCITER_ARMV7_CMAKE_VERSION: "3.29.7"
SCITER_NASM_DEBVERSION: "2.14-1" SCITER_NASM_DEBVERSION: "2.14-1"
@ -641,7 +642,7 @@ jobs:
target: aarch64-apple-darwin, target: aarch64-apple-darwin,
os: macos-latest, os: macos-latest,
# extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296 # extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296
extra-build-args: "", extra-build-args: "--screencapturekit",
arch: aarch64, arch: aarch64,
} }
steps: steps:
@ -720,7 +721,7 @@ jobs:
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@v1 uses: dtolnay/rust-toolchain@v1
with: with:
toolchain: ${{ env.RUST_VERSION }} toolchain: ${{ env.MAC_RUST_VERSION }}
targets: ${{ matrix.job.target }} targets: ${{ matrix.job.target }}
components: "rustfmt" components: "rustfmt"
@ -767,6 +768,13 @@ jobs:
- name: Build rustdesk - name: Build rustdesk
run: | run: |
if [ "${{ matrix.job.target }}" = "aarch64-apple-darwin" ]; then
MIN_MACOS_VERSION="12.3"
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET\=[0-9]*.[0-9]*/MACOSX_DEPLOYMENT_TARGET=${MIN_MACOS_VERSION}/" build.py
sed -i -e "s/platform :osx, '.*'/platform :osx, '${MIN_MACOS_VERSION}'/" flutter/macos/Podfile
sed -i -e "s/osx_minimum_system_version = \"[0-9]*.[0-9]*\"/osx_minimum_system_version = \"${MIN_MACOS_VERSION}\"/" Cargo.toml
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET = [0-9]*.[0-9]*;/MACOSX_DEPLOYMENT_TARGET = ${MIN_MACOS_VERSION};/" flutter/macos/Runner.xcodeproj/project.pbxproj
fi
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }} ./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
- name: create unsigned dmg - name: create unsigned dmg

22
Cargo.lock generated
View File

@ -893,6 +893,20 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "cidre"
version = "0.4.0"
source = "git+https://github.com/yury/cidre.git?rev=f05c428#f05c4288f9870c9fab53272ddafd6ec01c7b2dbf"
dependencies = [
"cidre-macros",
"parking_lot",
]
[[package]]
name = "cidre-macros"
version = "0.1.0"
source = "git+https://github.com/yury/cidre.git?rev=f05c428#f05c4288f9870c9fab53272ddafd6ec01c7b2dbf"
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.4.4" version = "0.4.4"
@ -1276,10 +1290,10 @@ dependencies = [
[[package]] [[package]]
name = "cpal" name = "cpal"
version = "0.15.3" version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/rustdesk-org/cpal?branch=osx-screencapturekit#4d318ff778063ce14669fd4bd67a1673653fc6e5"
checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
dependencies = [ dependencies = [
"alsa", "alsa",
"cidre",
"core-foundation-sys 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation-sys 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
"coreaudio-rs", "coreaudio-rs",
"dasp_sample", "dasp_sample",
@ -3505,7 +3519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"windows-targets 0.52.5", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -4149,7 +4163,7 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
dependencies = [ dependencies = [
"proc-macro-crate 2.0.2", "proc-macro-crate 1.3.1",
"proc-macro2 1.0.86", "proc-macro2 1.0.86",
"quote 1.0.36", "quote 1.0.36",
"syn 2.0.68", "syn 2.0.68",

View File

@ -36,6 +36,7 @@ unix-file-copy-paste = [
"dep:once_cell", "dep:once_cell",
"clipboard/unix-file-copy-paste", "clipboard/unix-file-copy-paste",
] ]
screencapturekit = ["cpal/screencapturekit"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -77,7 +78,7 @@ fon = "0.6"
zip = "0.6" zip = "0.6"
shutdown_hooks = "0.1" shutdown_hooks = "0.1"
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] } totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
cpal = "0.15" cpal = { git = "https://github.com/rustdesk-org/cpal", branch = "osx-screencapturekit" }
ringbuf = "0.3" ringbuf = "0.3"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]

View File

@ -143,6 +143,12 @@ def make_parser():
"--package", "--package",
type=str type=str
) )
if osx:
parser.add_argument(
'--screencapturekit',
action='store_true',
help='Enable feature screencapturekit'
)
return parser return parser
@ -274,6 +280,9 @@ def get_features(args):
features.append('flutter') features.append('flutter')
if args.unix_file_copy_paste: if args.unix_file_copy_paste:
features.append('unix-file-copy-paste') features.append('unix-file-copy-paste')
if osx:
if args.screencapturekit:
features.append('screencapturekit')
print("features:", features) print("features:", features)
return features return features

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
const _kWindowsSystemSound = 'System Sound'; const _kSystemSound = 'System Sound';
typedef AudioINputSetDevice = void Function(String device); typedef AudioINputSetDevice = void Function(String device);
typedef AudioInputBuilder = Widget Function( typedef AudioInputBuilder = Widget Function(
@ -21,7 +21,7 @@ class AudioInput extends StatelessWidget {
: super(key: key); : super(key: key);
static String getDefault() { static String getDefault() {
if (isWindows) return translate('System Sound'); if (bind.mainAudioSupportLoopback()) return translate(_kSystemSound);
return ''; return '';
} }
@ -55,8 +55,8 @@ class AudioInput extends StatelessWidget {
static Future<Map<String, Object>> getDevicesInfo( static Future<Map<String, Object>> getDevicesInfo(
bool isCm, bool isVoiceCall) async { bool isCm, bool isVoiceCall) async {
List<String> devices = (await bind.mainGetSoundInputs()).toList(); List<String> devices = (await bind.mainGetSoundInputs()).toList();
if (isWindows) { if (bind.mainAudioSupportLoopback()) {
devices.insert(0, translate(_kWindowsSystemSound)); devices.insert(0, translate(_kSystemSound));
} }
String current = await getValue(isCm, isVoiceCall); String current = await getValue(isCm, isVoiceCall);
return {'devices': devices, 'current': current}; return {'devices': devices, 'current': current};

View File

@ -837,7 +837,7 @@ async fn check_software_update_() -> hbb_common::ResultType<()> {
let _ = crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, data); let _ = crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, data);
} }
} }
*SOFTWARE_UPDATE_URL.lock().unwrap() = response_url; *SOFTWARE_UPDATE_URL.lock().unwrap() = response_url;
} }
Ok(()) Ok(())
} }

View File

@ -774,13 +774,6 @@ pub fn main_get_sound_inputs() -> Vec<String> {
vec![String::from("")] vec![String::from("")]
} }
pub fn main_get_default_sound_input() -> Option<String> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return get_default_sound_input();
#[cfg(any(target_os = "android", target_os = "ios"))]
None
}
pub fn main_get_login_device_info() -> SyncReturn<String> { pub fn main_get_login_device_info() -> SyncReturn<String> {
SyncReturn(get_login_device_info_json()) SyncReturn(get_login_device_info_json())
} }
@ -2317,6 +2310,16 @@ pub fn session_request_new_display_init_msgs(session_id: SessionID, display: usi
} }
} }
pub fn main_audio_support_loopback() -> SyncReturn<bool> {
#[cfg(target_os = "windows")]
let is_surpport = true;
#[cfg(feature = "screencapturekit")]
let is_surpport = crate::audio_service::is_screen_capture_kit_available();
#[cfg(not(any(target_os = "windows", feature = "screencapturekit")))]
let is_surpport = false;
SyncReturn(is_surpport)
}
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub mod server_side { pub mod server_side {
use hbb_common::{config, log}; use hbb_common::{config, log};

View File

@ -156,6 +156,14 @@ mod pa_impl {
} }
} }
#[inline]
#[cfg(feature = "screencapturekit")]
pub fn is_screen_capture_kit_available() -> bool {
cpal::available_hosts()
.iter()
.any(|host| *host == cpal::HostId::ScreenCaptureKit)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))] #[cfg(not(any(target_os = "linux", target_os = "android")))]
mod cpal_impl { mod cpal_impl {
use self::service::{Reset, ServiceSwap}; use self::service::{Reset, ServiceSwap};
@ -170,6 +178,11 @@ mod cpal_impl {
static ref INPUT_BUFFER: Arc<Mutex<std::collections::VecDeque<f32>>> = Default::default(); static ref INPUT_BUFFER: Arc<Mutex<std::collections::VecDeque<f32>>> = Default::default();
} }
#[cfg(feature = "screencapturekit")]
lazy_static::lazy_static! {
static ref HOST_SCREEN_CAPTURE_KIT: Result<Host, cpal::HostUnavailable> = cpal::host_from_id(cpal::HostId::ScreenCaptureKit);
}
#[derive(Default)] #[derive(Default)]
pub struct State { pub struct State {
stream: Option<(Box<dyn StreamTrait>, Arc<Message>)>, stream: Option<(Box<dyn StreamTrait>, Arc<Message>)>,
@ -246,6 +259,27 @@ mod cpal_impl {
send_f32(&data, encoder, sp); send_f32(&data, encoder, sp);
} }
#[cfg(feature = "screencapturekit")]
fn get_device() -> ResultType<(Device, SupportedStreamConfig)> {
let audio_input = super::get_audio_input();
if !audio_input.is_empty() {
return get_audio_input(&audio_input);
}
if !is_screen_capture_kit_available() {
return get_audio_input("");
}
let device = HOST_SCREEN_CAPTURE_KIT
.as_ref()?
.default_input_device()
.with_context(|| "Failed to get default input device for loopback")?;
let format = device
.default_input_config()
.map_err(|e| anyhow!(e))
.with_context(|| "Failed to get input output format")?;
log::info!("Default input format: {:?}", format);
Ok((device, format))
}
#[cfg(windows)] #[cfg(windows)]
fn get_device() -> ResultType<(Device, SupportedStreamConfig)> { fn get_device() -> ResultType<(Device, SupportedStreamConfig)> {
let audio_input = super::get_audio_input(); let audio_input = super::get_audio_input();
@ -267,7 +301,7 @@ mod cpal_impl {
Ok((device, format)) Ok((device, format))
} }
#[cfg(not(windows))] #[cfg(not(any(windows, feature = "screencapturekit")))]
fn get_device() -> ResultType<(Device, SupportedStreamConfig)> { fn get_device() -> ResultType<(Device, SupportedStreamConfig)> {
let audio_input = super::get_audio_input(); let audio_input = super::get_audio_input();
get_audio_input(&audio_input) get_audio_input(&audio_input)
@ -275,7 +309,20 @@ mod cpal_impl {
fn get_audio_input(audio_input: &str) -> ResultType<(Device, SupportedStreamConfig)> { fn get_audio_input(audio_input: &str) -> ResultType<(Device, SupportedStreamConfig)> {
let mut device = None; let mut device = None;
if !audio_input.is_empty() { #[cfg(feature = "screencapturekit")]
if !audio_input.is_empty() && is_screen_capture_kit_available() {
for d in HOST_SCREEN_CAPTURE_KIT
.as_ref()?
.devices()
.with_context(|| "Failed to get audio devices")?
{
if d.name().unwrap_or("".to_owned()) == audio_input {
device = Some(d);
break;
}
}
}
if device.is_none() && !audio_input.is_empty() {
for d in HOST for d in HOST
.devices() .devices()
.with_context(|| "Failed to get audio devices")? .with_context(|| "Failed to get audio devices")?

View File

@ -321,6 +321,8 @@ pub fn get_sound_inputs() -> Vec<String> {
fn get_sound_inputs_() -> Vec<String> { fn get_sound_inputs_() -> Vec<String> {
let mut out = Vec::new(); let mut out = Vec::new();
use cpal::traits::{DeviceTrait, HostTrait}; use cpal::traits::{DeviceTrait, HostTrait};
// Do not use `cpal::host_from_id(cpal::HostId::ScreenCaptureKit)` for feature = "screencapturekit"
// Because we explicitly handle the "System Sound" device.
let host = cpal::default_host(); let host = cpal::default_host();
if let Ok(devices) = host.devices() { if let Ok(devices) = host.devices() {
for device in devices { for device in devices {