Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
44 changes: 37 additions & 7 deletions src/host/coreaudio/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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::<Vec<_>>().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())
}
}
}

Expand Down
Loading