Skip to content

Commit 512a611

Browse files
CoreAudio: Optimize sample rate range handling with fallback mechanism
- Return single config when all sample rate ranges have identical min/max values - Improve iterator usage for better code clarity and consistency - Follow RtAudio approach for handling device sample rate inconsistencies Fixes issues with redundant sample rate configs that most CoreAudio devices report.
1 parent c77747d commit 512a611

File tree

2 files changed

+38
-7
lines changed

2 files changed

+38
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- ALSA(process_output): pass `silent=true` to `PCM.try_recover`, so it doesn't write to stderr
44
- WASAPI: Expose IMMDevice from WASAPI host Device.
5+
- 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).
56

67
# Version 0.16.0 (2025-06-07)
78

src/host/coreaudio/macos/mod.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ impl Device {
236236
let sample_format = SampleFormat::F32;
237237

238238
// Get available sample rate ranges.
239+
// The property "kAudioDevicePropertyAvailableNominalSampleRates" returns a list of pairs of
240+
// minimum and maximum sample rates but most of the devices returns pairs of same values though the underlying mechanism is unclear.
241+
// This may cause issues when, for example, sorting the configs by the sample rates.
242+
// We follows the implementation of RtAudio, which returns single element of config
243+
// when all the pairs have the same values and returns multiple elements otherwise.
244+
// See https://github.com/thestk/rtaudio/blob/master/RtAudio.cpp#L1369C1-L1375C39
245+
239246
property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
240247
let data_size = 0u32;
241248
let status = AudioObjectGetPropertyDataSize(
@@ -278,19 +285,42 @@ impl Device {
278285
let buffer_size = get_io_buffer_frame_size_range(&audio_unit)?;
279286

280287
// Collect the supported formats for the device.
281-
let mut fmts = vec![];
282-
for range in ranges {
288+
289+
let contains_different_sample_rates = ranges.iter().any(|r| r.mMinimum != r.mMaximum);
290+
if ranges.is_empty() {
291+
Ok(vec![].into_iter())
292+
} else if contains_different_sample_rates {
293+
let res = ranges.iter().map(|range| SupportedStreamConfigRange {
294+
channels: n_channels as ChannelCount,
295+
min_sample_rate: SampleRate(range.mMinimum as u32),
296+
max_sample_rate: SampleRate(range.mMaximum as u32),
297+
buffer_size,
298+
sample_format,
299+
});
300+
Ok(res.collect::<Vec<_>>().into_iter())
301+
} else {
283302
let fmt = SupportedStreamConfigRange {
284303
channels: n_channels as ChannelCount,
285-
min_sample_rate: SampleRate(range.mMinimum as _),
286-
max_sample_rate: SampleRate(range.mMaximum as _),
304+
min_sample_rate: SampleRate(
305+
ranges
306+
.iter()
307+
.map(|v| v.mMinimum as u32)
308+
.min()
309+
.expect("the list must not be empty"),
310+
),
311+
max_sample_rate: SampleRate(
312+
ranges
313+
.iter()
314+
.map(|v| v.mMaximum as u32)
315+
.max()
316+
.expect("the list must not be empty"),
317+
),
287318
buffer_size,
288319
sample_format,
289320
};
290-
fmts.push(fmt);
291-
}
292321

293-
Ok(fmts.into_iter())
322+
Ok(vec![fmt].into_iter())
323+
}
294324
}
295325
}
296326

0 commit comments

Comments
 (0)