[package] name = "chanora_audio" description = "Chanora audio subsystem — cpal-based capture/playback, audiopus encode, tsclientlib AudioHandler for decode + jitter buffer + mix. DEC-011, DEC-011.1." version.workspace = true edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true repository.workspace = true publish.workspace = true [dependencies] chanora_protocol = { path = "../chanora_protocol" } thiserror.workspace = true tracing.workspace = true # Cross-platform audio I/O (DEC-011.1). cpal = "0.16" # Opus encoder. tsclientlib already pulls this; we depend explicitly so # this crate can compile against it without going through tsclientlib. audiopus = "0.3.0-rc.0" # AudioHandler lives in the tsclientlib crate behind the `audio` # feature. We import the crate just for the AudioHandler type; the # Connection type stays inside chanora_protocol. tsclientlib = { git = "https://github.com/ReSpeak/tsclientlib.git", rev = "04aa2491", default-features = false, features = ["audio"] } # Force the reqwest TLS backend to native-tls (Security.framework on # Apple, SChannel on Windows, system OpenSSL on Linux/BSD) instead of # the rustls + aws-lc-rs combination tsclientlib's `default-tls` # feature would otherwise pick. aws-lc-sys does not cross-compile # cleanly to aarch64-apple-ios, and native-tls is the standard # answer for "TLS that just works on every desktop+mobile platform # without a vendored C dependency". Cargo's feature unification means # this single direct dep applies to the transitive # tsclientlib -> reqwest chain too. reqwest = { version = "0.13", default-features = false, features = ["charset", "http2", "native-tls"] } tokio = { version = "1", features = ["sync", "rt", "macros", "time"] } [target.'cfg(target_os = "ios")'.dependencies] # Direct CoreAudio AudioUnit access on iOS (DEC-011.x follow-up). # cpal's iOS backend is unsuitable for VoIP: it opens # kAudioUnitSubType_RemoteIO with a mono-only output element and no # control over buffer size / sample rate, AND its AudioUnit stays # bound to the route present at construction time so user-driven # `overrideOutputAudioPort` flips do not actually move audio to the # new transducer. Every production iOS VoIP client (Linphone, Mumble # iOS, Signal, Jitsi, WebRTC reference) instead drives # `kAudioUnitSubType_VoiceProcessingIO` (a.k.a. VPIO) directly. VPIO # is Apple's recommended voice unit; it ships hardware AEC + AGC + NS # and honours route changes natively because it IS the canonical # voice unit on iOS. `coreaudio-rs` (RustAudio org, same maintainers # as `cpal`, 8.6M downloads) gives us a safe wrapper around the # AudioUnit C API. We use it on iOS only; cpal stays on macOS where # its CoreAudio backend works well against HAL units. # # Default features keep `audio_toolbox` + `core_audio`, both required # for AudioUnit construction + property access. coreaudio-rs = "0.14" [target.'cfg(target_os = "android")'.dependencies] # JNI bindings to flip Android's AudioManager into MODE_IN_COMMUNICATION # when the voice-comm preset is requested. ndk_context is initialised # by the bridge crate's android_init shim. jni = { version = "0.21", default-features = false } ndk-context = "0.1" [target.'cfg(target_os = "windows")'.dependencies] # Real Windows global PTT (SDD-083 / SDD-084): RegisterRawInputDevices # + WM_INPUT translation backed by a hidden message-only window, and # SetWindowsHookExW(WH_KEYBOARD_LL / WH_MOUSE_LL) fallback. Both # require a per-backend OS thread that owns a message pump. windows = { version = "0.54", features = [ "Win32_Foundation", "Win32_Graphics_Gdi", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_UI_Input", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_WindowsAndMessaging", ] } [dev-dependencies] # `test-util` enables `start_paused` / virtual-clock tests used by # the missed-key-up watchdog unit tests. tokio = { version = "1", features = ["sync", "rt", "macros", "time", "test-util"] } # Cross-platform recording Layer for the SDD-090 / DEC-027 privacy # invariant integration test (`tests/ptt_privacy.rs`). tracing-subscriber = { version = "0.3", features = ["registry"] } [target.'cfg(target_os = "linux")'.dependencies] # GNOME-on-Wayland Global Push-to-Talk uses the freedesktop # `org.freedesktop.portal.GlobalShortcuts` interface over D-Bus. # `zbus` is the standard async D-Bus crate; the `tokio` runtime # selector is mandatory in zbus 5; we share the tokio runtime # the rest of the audio + core crates already depend on. The # `blocking-api` feature is retained so the audio-engine # probe path can do a synchronous portal-version read without # starting an async runtime; the live session flow uses the # async surface. zbus = { version = "5", default-features = false, features = ["tokio", "blocking-api"] } # Stream / sink utilities for consuming portal signals on the # async path. futures-util = { version = "0.3", default-features = false, features = ["std"] } # Random token bytes for the portal handle_token / session_handle_token # options. The portal recommends fresh tokens to scope its own # object paths per call. rand = "0.8" # SDL2 audio for Linux. Replaces the cpal capture / playback paths # on Linux only; cpal stays in use on Windows/macOS. Rationale: the # cpal Linux backend opens raw ALSA `default`, which on most Arch # / Fedora / Debian installs routes through `dmix` + `plug` with # nearest-neighbour resampling and very small period sizes — the # combination produces audible crackling/popping. SDL2 on the same # systems routes through PipeWire's PulseAudio compat bridge (or # real PulseAudio), both of which carry a high-quality resampler # and a sensible default period. The upstream tsclientlib audio # example (`tsclientlib/examples/audio_utils/ts_to_audio.rs`) and # the official Qint client both use SDL2 in exactly this shape; # this dep brings Chanora in line with that pattern. # # `bundled` is OFF deliberately — we link against the system # libSDL2.so. Arch ships `sdl2-compat`; Debian/Ubuntu ship # `libsdl2-2.0-0`; Fedora ships `SDL2`. The chanora-flutter Linux # build documentation lists this as a runtime dependency. sdl2 = { version = "0.37", default-features = false }