perf(player): cache CoreAudio nominal rate query for 750ms

evaluate_stream_recreate runs on every track change on the audio
thread, and each call issued two HAL syscalls
(resolve_output_device_id + get_nominal_sample_rate). Cache the result
per-thread for 750ms — long enough to absorb the bulk of repeats
within an album, short enough to notice a system rate change.

Cache invalidates on device-name change, so switching the output
device still triggers a fresh query.
This commit is contained in:
Afonso Jorge Ramos committed 2026-05-13 09:23:39 +01:00
1 parent 1edde8a28a
commit a7b40bf6bb
1 file changed
+57 -12
+57 -12
View File
@@ -542,25 +542,70 @@ fn uses_coreaudio_system_default(settings: &AudioSettings) -> bool {
== AudioBackendType::SystemDefault
}
/// `evaluate_stream_recreate` runs every track change on the audio thread,
/// and `coreaudio_nominal_rate` issues two CoreAudio HAL queries per call
/// (`resolve_output_device_id` + `get_nominal_sample_rate`). A short-lived
/// cache absorbs the bulk of those repeats — long enough to skip duplicate
/// queries within an album, short enough to notice when the user changes the
/// system audio rate.
#[cfg(target_os = "macos")]
const COREAUDIO_RATE_CACHE_TTL: std::time::Duration = std::time::Duration::from_millis(750);
#[cfg(target_os = "macos")]
struct CachedNominalRate {
cached_at: std::time::Instant,
device_name: Option<String>,
rate: Option<u32>,
}
#[cfg(target_os = "macos")]
std::thread_local! {
static COREAUDIO_NOMINAL_RATE_CACHE: std::cell::RefCell<Option<CachedNominalRate>> =
const { std::cell::RefCell::new(None) };
}
#[cfg(target_os = "macos")]
fn query_macos_nominal_rate(device_name: Option<&str>) -> Option<u32> {
let device_id = qbz_audio::coreaudio_direct::resolve_output_device_id(device_name)
.inspect_err(|e| {
log::warn!(
"[CoreAudio] Failed to resolve output device for rate check: {}",
e
)
})
.ok()?;
qbz_audio::coreaudio_direct::get_nominal_sample_rate(device_id)
.inspect_err(|e| log::warn!("[CoreAudio] Failed to query nominal rate: {}", e))
.ok()
}
#[cfg(target_os = "macos")]
fn coreaudio_nominal_rate(settings: &AudioSettings) -> Option<u32> {
if !uses_coreaudio_system_default(settings) {
return None;
}
let device_id =
qbz_audio::coreaudio_direct::resolve_output_device_id(settings.output_device.as_deref())
.inspect_err(|e| {
log::warn!(
"[CoreAudio] Failed to resolve output device for rate check: {}",
e
)
})
.ok()?;
let device_name = settings.output_device.clone();
let now = std::time::Instant::now();
qbz_audio::coreaudio_direct::get_nominal_sample_rate(device_id)
.inspect_err(|e| log::warn!("[CoreAudio] Failed to query nominal rate: {}", e))
.ok()
COREAUDIO_NOMINAL_RATE_CACHE.with(|cell| {
let mut cell = cell.borrow_mut();
if let Some(cached) = cell.as_ref() {
if cached.device_name == device_name
&& now.duration_since(cached.cached_at) < COREAUDIO_RATE_CACHE_TTL
{
return cached.rate;
}
}
let rate = query_macos_nominal_rate(device_name.as_deref());
*cell = Some(CachedNominalRate {
cached_at: now,
device_name,
rate,
});
rate
})
}
#[cfg(target_os = "macos")]