Adds a direct WASAPI microphone capture path for the Windows desktop build that opens the default communications endpoint via IMMDeviceEnumerator -> IAudioClient2 -> SetClientProperties with AudioCategory_Communications, turning on Windows's communications audio processing chain (AEC, noise suppression, automatic gain control). The communications AEC operates at the OS level and uses the system render mix as the reference signal, so echo from our existing CPAL playback stream is cancelled automatically with no per-process reference plumbing. Architecture: - New crates/wzp-client/src/audio_wasapi.rs module (~280 lines). Event-driven capture loop on a dedicated thread; pushes PCM into the same lock-free AudioRing used by the CPAL path. Same public API as audio_io::AudioCapture so downstream code is unchanged. - New `windows-aec` feature in wzp-client that pulls in the `windows` crate (Microsoft's official Rust COM bindings) gated to target_os = "windows" only. Enabling the feature on non-Windows targets is a no-op since both the module and the dep are cfg(target_os = "windows"). - lib.rs re-exports WasapiAudioCapture as AudioCapture when the feature is on, otherwise falls back to the CPAL AudioCapture. AudioPlayback is always the CPAL one — no reason to swap it. - desktop/src-tauri/Cargo.toml Windows target enables the new feature: `features = ["audio", "windows-aec"]`. Implementation notes: - Uses eCommunications role (not eConsole) for GetDefaultAudioEndpoint — the user-configured "communications" device that Teams/Zoom pick up, and the one Windows's AEC is tuned for. - Requests 48 kHz mono i16 with AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM + SRC_DEFAULT_QUALITY so Windows handles any format conversion in the audio engine instead of rejecting our format. - Event-driven with SetEventHandle / WaitForSingleObject — no polling, minimal CPU cost between packets. - 200 ms wait timeout so the capture thread polls `running` often enough for Drop to stop cleanly even if the audio engine stalls (e.g. device unplug). Task #24. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
99 lines
4.3 KiB
TOML
99 lines
4.3 KiB
TOML
[package]
|
|
name = "wzp-desktop"
|
|
version = "0.1.0"
|
|
edition = "2024"
|
|
description = "WarzonePhone Desktop — encrypted VoIP client"
|
|
default-run = "wzp-desktop"
|
|
|
|
# Library target — required for Tauri mobile (Android/iOS link the app as a cdylib)
|
|
# and also used by the desktop binary below.
|
|
#
|
|
# `staticlib` was DROPPED from crate-type because rust-lang/rust#104707
|
|
# documents that having staticlib alongside cdylib leaks non-exported
|
|
# symbols from staticlibs into the cdylib. Bionic's private `__init_tcb`
|
|
# / `pthread_create` symbols end up bound LOCALLY inside our .so instead
|
|
# of resolved dynamically against libc.so at dlopen time — which crashes
|
|
# at launch as soon as tao tries to std::thread::spawn() from the JNI
|
|
# onCreate callback. The legacy wzp-android crate uses ["cdylib", "rlib"]
|
|
# and runs fine on the same phone with the same NDK + Rust toolchain.
|
|
#
|
|
# iOS Tauri builds that actually need staticlib can re-add it behind a
|
|
# target cfg if we ever ship on iOS.
|
|
[lib]
|
|
name = "wzp_desktop_lib"
|
|
crate-type = ["cdylib", "rlib"]
|
|
|
|
[[bin]]
|
|
name = "wzp-desktop"
|
|
path = "src/main.rs"
|
|
|
|
[build-dependencies]
|
|
tauri-build = { version = "2", features = [] }
|
|
# cc is no longer needed — all C++ moved to crates/wzp-native (built with
|
|
# cargo-ndk and loaded via libloading at runtime). wzp-desktop's .so on
|
|
# Android is now pure Rust.
|
|
|
|
[dependencies]
|
|
tauri = { version = "2", features = [] }
|
|
tauri-plugin-shell = "2"
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
tokio = { version = "1", features = ["full"] }
|
|
tracing = "0.1"
|
|
tracing-subscriber = "0.3"
|
|
anyhow = "1"
|
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
|
|
|
|
# WarzonePhone crates — protocol layer is platform-independent
|
|
wzp-proto = { path = "../../crates/wzp-proto" }
|
|
wzp-codec = { path = "../../crates/wzp-codec" }
|
|
wzp-fec = { path = "../../crates/wzp-fec" }
|
|
wzp-crypto = { path = "../../crates/wzp-crypto" }
|
|
wzp-transport = { path = "../../crates/wzp-transport" }
|
|
|
|
# wzp-client pulls in CPAL on every desktop target and, additionally on
|
|
# macOS, VoiceProcessingIO (coreaudio-rs behind the "vpio" feature). The
|
|
# vpio feature MUST NOT be enabled on Windows / Linux because coreaudio-rs
|
|
# is Apple-framework-only and will fail to build. Task #24 will add a
|
|
# matching Windows Voice Capture DSP path behind its own feature; until
|
|
# then, Windows desktops use plain CPAL with AEC disabled.
|
|
|
|
# macOS: CPAL + VoiceProcessingIO (hardware AEC via Core Audio).
|
|
[target.'cfg(target_os = "macos")'.dependencies]
|
|
wzp-client = { path = "../../crates/wzp-client", features = ["audio", "vpio"] }
|
|
|
|
# Windows: CPAL for playback + direct WASAPI for capture with OS-level
|
|
# AEC (AudioCategory_Communications). The wzp-client `windows-aec`
|
|
# feature swaps the default CPAL AudioCapture for a WASAPI one that
|
|
# opens the mic under AudioCategory_Communications, turning on Windows's
|
|
# communications audio processing chain (AEC, NS, AGC). The reference
|
|
# signal for AEC is the system render mix, so echo from our CPAL
|
|
# playback is cancelled automatically without extra plumbing.
|
|
[target.'cfg(target_os = "windows")'.dependencies]
|
|
wzp-client = { path = "../../crates/wzp-client", features = ["audio", "windows-aec"] }
|
|
|
|
# Linux: same as Windows for now — plain CPAL.
|
|
[target.'cfg(target_os = "linux")'.dependencies]
|
|
wzp-client = { path = "../../crates/wzp-client", features = ["audio"] }
|
|
|
|
# Android: no CPAL, no vpio — audio goes through the standalone wzp-native
|
|
# cdylib that we dlopen via libloading at runtime. See the wzp_native
|
|
# module in src/.
|
|
[target.'cfg(target_os = "android")'.dependencies]
|
|
wzp-client = { path = "../../crates/wzp-client", default-features = false }
|
|
# libloading: runtime dlopen of libwzp_native.so — the standalone cdylib
|
|
# crate that owns all C++ (Oboe bridge). Keeps wzp-desktop's .so free of
|
|
# any C/C++ static archives that would otherwise leak bionic's internal
|
|
# pthread_create into our cdylib and trigger the __init_tcb crash.
|
|
libloading = "0.8"
|
|
# jni + ndk-context: called from android_audio.rs to invoke
|
|
# AudioManager.setSpeakerphoneOn on the JVM side at runtime, so the
|
|
# Oboe playout stream (opened with Usage::VoiceCommunication) can route
|
|
# between earpiece and loud speaker without restarting.
|
|
jni = "0.21"
|
|
ndk-context = "0.1"
|
|
|
|
[features]
|
|
default = ["custom-protocol"]
|
|
custom-protocol = ["tauri/custom-protocol"]
|