[package] name = "chanora_audio" description = "Chanora audio subsystem — platform-native 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 sonora = "0.1" webrtc-vad = "0.4" # ndarray is required by ort's tensor construction API and by # Silero VAD ONNX inference on non-iOS targets. ndarray = "0.17" # 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"] } tokio = { version = "1", features = ["sync", "rt", "macros", "time"] } rustfft = "6.2.0" crossbeam = { version = "0.8", default-features = false, features = ["alloc", "crossbeam-queue"] } [target.'cfg(all(not(target_os = "android"), not(target_os = "ios"), not(target_os = "macos")))'.dependencies] # Desktop audio I/O for Windows capture/playback and Linux capture. # Linux playback uses SDL2; Apple platforms use direct VoiceProcessingIO # AudioUnits via `coreaudio-rs` for the voice path. cpal = "0.18.0" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] # Direct CoreAudio AudioUnit access on Apple platforms (DEC-011 follow-up). # cpal's Apple path does not expose the voice-processing controls Chanora # needs for VoIP. Use `kAudioUnitSubType_VoiceProcessingIO` directly via # `coreaudio-rs` so capture/playback share Apple's native AEC + AGC + NS # voice unit on both iOS and macOS. # # Default features keep `audio_toolbox` + `core_audio`, both required # for AudioUnit construction + property access. coreaudio-rs = "0.14" # Grand Central Dispatch bindings — used to run AudioUnit initialize/start # on the main queue to avoid the VPIO RPC timeout on iOS simulator. dispatch2 = "0.3" [target.'cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))'.dependencies] ort = { version = "2.0.0-rc.12", default-features = false, features = ["load-dynamic", "ndarray", "api-24"] } [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.22.4", default-features = false } ndk-context = "0.1" # Oboe-rs (Google Oboe wrapper) for low-latency voice capture + playback. # Primary backend for SDD-111..SDD-115. The pre-compiled static library # shipped with `oboe-sys` 0.6 covers armv7 / aarch64 / x86 / x86_64. # Default features keep the precompiled library + pregenerated bindings # so we avoid the clang-sys / libclang requirement on the build host. # # Using local fork edisonjwa/oboe-rs (v0.6.2) with: # - catch_unwind safety in callbacks # - unwrap_or_default in enum getters (no more SessionId panic) # - get_raw_session_id() for JNI hardware effect binding # - deduplicated macro impls # - PowerSavingOffloaded PerformanceMode variant oboe = { git = "https://github.com/EdisonJwa/oboe-rs", rev = "a14f9b83ecea8c93f5a692f2ee7808445b938c35" } # Safe slice reinterpret for the oboe stereo output callback. # bytemuck::cast_slice_mut replaces the raw-pointer cast from # `&mut [(f32, f32)]` to `&mut [f32]` with a provenance-correct # and UB-free transmute backed by `NoUninit`. bytemuck = { version = "1", features = ["derive"] } [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.62", 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"] } # SDD-120 §3 — criterion bench harness (realtime_capture / opus_codec / # resampler). `harness = false` per bench entry below disables the # default libtest harness so criterion can install its own. criterion = "0.8" # SDD-120 §3 item 1 — dhat is used as the global allocator inside # `benches/realtime_capture.rs` to count post-warmup heap allocations # on the realtime capture path. Dev-dep only — does NOT affect # production builds. dhat = "0.3" # SDD-120 §5 / §8 — JSON serialization for `emit_baseline` / # `compare_baseline` binaries that consume criterion's per-bench # `estimates.json` outputs and emit the SRS-217 baseline schema. serde_json = "1" [[bench]] name = "realtime_capture" harness = false path = "benches/realtime_capture.rs" [[bench]] name = "opus_codec" harness = false path = "benches/opus_codec.rs" [[bench]] name = "resampler" harness = false path = "benches/resampler.rs" [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.10" # SDL2 audio for Linux. Replaces the cpal playback path on Linux only; # cpal stays in use for Linux capture and Windows capture/playback. # Apple platforms use direct VoiceProcessingIO AudioUnits. 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.38", default-features = false }