Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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.
- `Device::supported_configs` on CoreAudio now returns a single element which contains available samplerate range if the all element of list have same value for `mMinimum` and `mMaximum` (which is the most 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.into_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
.into_iter()
.map(|v| v.mMinimum as u32)
.min()
.expect("the list must not be empty"),
),
max_sample_rate: SampleRate(
ranges
.into_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