Adds CoreAudio Hog Mode integration on macOS so Exclusive Mode actually
takes the device exclusively (previously the macOS path used the
Linux-shaped logic without any kernel-level lock). Pins the CoreAudio
device id through Hog Mode acquisition, sample-rate switching, and
stream creation so System Default doesn't drift mid-session. Linux
behaviour is unchanged.
Review asks (all four resolved by Vudgekek + afonsojramos):
- set_coreaudio_hardware_volume falls back to software volume on Err
rather than reporting success, so knob-only DACs keep a working
volume slider via engine.set_volume().
- CoreAudioExclusiveGuard snapshots the device's hardware volume on
acquire and restores it before releasing Hog Mode, so leaving
Exclusive Mode returns the DAC to the user's pre-Exclusive level.
Guard is constructed before the FFI call so partial-acquire failures
still trigger Drop and release.
- Linux unused-variable warnings on stream_opt and exclusive_guard
cleaned up via cfg_attr(allow(unused_variables)) on the macOS-only
guard plumbing. cargo check is clean.
- Silent shared-mode regression on macOS when backend init fails:
init_device now sets_current_device(None) and returns None on
exclusive_mode==true so the user sees an error instead of
unknowingly playing in shared mode.
Bonus: 5f10b4ba adds real CoreAudio device-rate detection for macOS
Shared Mode, +90 lines in backend.rs.
Conflict resolution: single textual collision in
crates/qbz-audio/src/lib.rs (both branches added a pub use line).
Resolved by keeping both, alphabetical order — coreaudio_direct
re-export from pr-391, device_reservation re-export from pre-release.
All other auto-merges verified line-by-line: AlsaDirectStream's
DeviceReservation::acquire flow (alsa_direct.rs:60-97), the macOS
exclusive_mode guard (player/mod.rs:1077-1085 in qbz-player and the
mirror in src-tauri:1024), and CoreAudioExclusiveGuard's volume
snapshot/restore (coreaudio_direct.rs:351,369,373,388) all coexist
correctly because each branch touched different functions.
cargo check clean across all four crates. 9 device_reservation +
3 reserve_dac + 214 src-tauri unit tests pass.
cc
53 lines
1.4 KiB
TOML
53 lines
1.4 KiB
TOML
[package]
|
|
name = "qbz-audio"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
license.workspace = true
|
|
description = "Audio backend system for QBZ - bit-perfect playback"
|
|
|
|
[dependencies]
|
|
qbz-models = { path = "../qbz-models" }
|
|
|
|
# Async
|
|
tokio = { workspace = true }
|
|
async-trait = { workspace = true }
|
|
|
|
# Logging
|
|
log = { workspace = true }
|
|
|
|
# Serialization
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
|
|
# Error handling
|
|
thiserror = { workspace = true }
|
|
|
|
# Audio playback (f32 pipeline is now upstream in rodio 0.22)
|
|
rodio = { version = "0.22", features = ["symphonia-all"] }
|
|
|
|
# Audio analysis
|
|
symphonia = { workspace = true }
|
|
ebur128 = "0.1"
|
|
rustfft = "6.4"
|
|
|
|
# Database for loudness cache
|
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
|
|
|
# System directories
|
|
dirs = "6"
|
|
|
|
[target.'cfg(target_os = "linux")'.dependencies]
|
|
# ALSA for direct hardware access (bit-perfect)
|
|
alsa = "0.10"
|
|
# alsa-sys: direct FFI for snd_lib_error_set_handler (custom error routing)
|
|
alsa-sys = "0.3"
|
|
# zbus for org.freedesktop.ReserveDevice1 D-Bus client (device reservation).
|
|
# Pinned to v4 to share the transitive copy already pulled in by
|
|
# secret-service/souvlaki and avoid duplicate zbus compiles.
|
|
zbus = { version = "4", default-features = false, features = ["tokio"] }
|
|
|
|
[target.'cfg(target_os = "macos")'.dependencies]
|
|
# CoreAudio for device probing, sample rate switching, and (future) hog mode / exclusive mode
|
|
coreaudio-rs = "0.14.1"
|
|
objc2-core-audio = "0.3.2"
|