diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cc14b98a..d2dcbb8e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - ALSA(process_output): pass `silent=true` to `PCM.try_recover`, so it doesn't write to stderr - WASAPI: Expose IMMDevice from WASAPI host Device. +- CoreAudio: `Device::supported_configs` now returns a single element containing the available sample rate range when all elements have the same `mMinimum` and `mMaximum` values (which is the most common case). # Version 0.16.0 (2025-06-07) diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index 1897a7040..764ca7f76 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -236,6 +236,13 @@ impl Device { let sample_format = SampleFormat::F32; // Get available sample rate ranges. + // The property "kAudioDevicePropertyAvailableNominalSampleRates" returns a list of pairs of + // minimum and maximum sample rates but most of the devices returns pairs of same values though the underlying mechanism is unclear. + // This may cause issues when, for example, sorting the configs by the sample rates. + // We follows the implementation of RtAudio, which returns single element of config + // when all the pairs have the same values and returns multiple elements otherwise. + // See https://github.com/thestk/rtaudio/blob/master/RtAudio.cpp#L1369C1-L1375C39 + property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; let data_size = 0u32; let status = AudioObjectGetPropertyDataSize( @@ -278,19 +285,42 @@ impl Device { let buffer_size = get_io_buffer_frame_size_range(&audio_unit)?; // Collect the supported formats for the device. - let mut fmts = vec![]; - for range in ranges { + + let contains_different_sample_rates = ranges.iter().any(|r| r.mMinimum != r.mMaximum); + if ranges.is_empty() { + Ok(vec![].into_iter()) + } else if contains_different_sample_rates { + let res = ranges.iter().map(|range| SupportedStreamConfigRange { + channels: n_channels as ChannelCount, + min_sample_rate: SampleRate(range.mMinimum as u32), + max_sample_rate: SampleRate(range.mMaximum as u32), + buffer_size, + sample_format, + }); + Ok(res.collect::>().into_iter()) + } else { let fmt = SupportedStreamConfigRange { channels: n_channels as ChannelCount, - min_sample_rate: SampleRate(range.mMinimum as _), - max_sample_rate: SampleRate(range.mMaximum as _), + min_sample_rate: SampleRate( + ranges + .iter() + .map(|v| v.mMinimum as u32) + .min() + .expect("the list must not be empty"), + ), + max_sample_rate: SampleRate( + ranges + .iter() + .map(|v| v.mMaximum as u32) + .max() + .expect("the list must not be empty"), + ), buffer_size, sample_format, }; - fmts.push(fmt); - } - Ok(fmts.into_iter()) + Ok(vec![fmt].into_iter()) + } } }