From d5096175c520697b189d457bbcfcb60154d75c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=B5=A6=20=E7=9F=A5=E4=B9=9F=20Matsuura=20Tomoy?= =?UTF-8?q?a?= Date: Tue, 5 Aug 2025 15:24:25 +0900 Subject: [PATCH 1/7] changed the way to collect supported samplerate range --- src/host/coreaudio/macos/mod.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index 1897a7040..93a4c093d 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -278,19 +278,19 @@ 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 fmt = SupportedStreamConfigRange { - channels: n_channels as ChannelCount, - min_sample_rate: SampleRate(range.mMinimum as _), - max_sample_rate: SampleRate(range.mMaximum as _), - buffer_size, - sample_format, - }; - fmts.push(fmt); - } + let fmt = SupportedStreamConfigRange { + channels: n_channels as ChannelCount, + min_sample_rate: SampleRate( + ranges.iter().map(|v| v.mMinimum as u32).min().unwrap(), + ), + max_sample_rate: SampleRate( + ranges.iter().map(|v| v.mMaximum as u32).max().unwrap(), + ), + buffer_size, + sample_format, + }; - Ok(fmts.into_iter()) + Ok(vec![fmt].into_iter()) } } From 8c746b6fbc2cf30ed8300aff72bd11210f66c1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=B5=A6=20=E7=9F=A5=E4=B9=9F=20Matsuura=20Tomoy?= =?UTF-8?q?a?= Date: Tue, 5 Aug 2025 15:24:25 +0900 Subject: [PATCH 2/7] CoreAudio:Added fallback mechanism of available sample rates --- src/host/coreaudio/macos/mod.rs | 45 ++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index 93a4c093d..872211058 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,33 @@ impl Device { let buffer_size = get_io_buffer_frame_size_range(&audio_unit)?; // Collect the supported formats for the device. - let fmt = SupportedStreamConfigRange { - channels: n_channels as ChannelCount, - min_sample_rate: SampleRate( - ranges.iter().map(|v| v.mMinimum as u32).min().unwrap(), - ), - max_sample_rate: SampleRate( - ranges.iter().map(|v| v.mMaximum as u32).max().unwrap(), - ), - buffer_size, - sample_format, - }; - Ok(vec![fmt].into_iter()) + 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::>().into_iter()) + } else { + let values = ranges.into_iter().map(|v| v.mMinimum as u32); //assume that all mMinimum and mMaximum values are the same + let min = values.clone().min().expect("the list must not be empty"); + let max = values.max().expect("the list must not be empty"); + let fmt = SupportedStreamConfigRange { + channels: n_channels as ChannelCount, + min_sample_rate: SampleRate(min), + max_sample_rate: SampleRate(max), + buffer_size, + sample_format, + }; + + Ok(vec![fmt].into_iter()) + } } } From 75a0318ad705b7adb7f142849df53d8574a89ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=B5=A6=20=E7=9F=A5=E4=B9=9F=20Matsuura=20Tomoy?= =?UTF-8?q?a?= Date: Tue, 5 Aug 2025 15:24:25 +0900 Subject: [PATCH 3/7] removed redundunt cloning --- src/host/coreaudio/macos/mod.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index 872211058..71aa6d479 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -299,13 +299,10 @@ impl Device { }); Ok(res.collect::>().into_iter()) } else { - let values = ranges.into_iter().map(|v| v.mMinimum as u32); //assume that all mMinimum and mMaximum values are the same - let min = values.clone().min().expect("the list must not be empty"); - let max = values.max().expect("the list must not be empty"); let fmt = SupportedStreamConfigRange { channels: n_channels as ChannelCount, - min_sample_rate: SampleRate(min), - max_sample_rate: SampleRate(max), + 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, }; From 77c0d91ba6ca2b0771d044fdb36980293f6e60e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=B5=A6=20=E7=9F=A5=E4=B9=9F=20Matsuura=20Tomoy?= =?UTF-8?q?a?= Date: Tue, 5 Aug 2025 15:25:05 +0900 Subject: [PATCH 4/7] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cc14b98a..2a19e9cd8 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. +- `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) From bc57f606c166214ac327bd7b5e8ad27867fcd4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=B5=A6=20=E7=9F=A5=E4=B9=9F=20Matsuura=20Tomoy?= =?UTF-8?q?a?= Date: Tue, 5 Aug 2025 15:27:27 +0900 Subject: [PATCH 5/7] applied rustfmt --- src/host/coreaudio/macos/mod.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index 71aa6d479..e299d7de0 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -301,8 +301,20 @@ impl Device { } else { let fmt = SupportedStreamConfigRange { channels: n_channels as ChannelCount, - 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")), + 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, }; From 467555abfa73fc57920ce2335b38575af14b0242 Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Tue, 5 Aug 2025 11:38:04 +0200 Subject: [PATCH 6/7] refactor: use iter instead of into_iter for sample rate ranges --- src/host/coreaudio/macos/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index e299d7de0..764ca7f76 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -290,7 +290,7 @@ impl Device { if ranges.is_empty() { Ok(vec![].into_iter()) } else if contains_different_sample_rates { - let res = ranges.into_iter().map(|range| SupportedStreamConfigRange { + 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), @@ -303,14 +303,14 @@ impl Device { channels: n_channels as ChannelCount, min_sample_rate: SampleRate( ranges - .into_iter() + .iter() .map(|v| v.mMinimum as u32) .min() .expect("the list must not be empty"), ), max_sample_rate: SampleRate( ranges - .into_iter() + .iter() .map(|v| v.mMaximum as u32) .max() .expect("the list must not be empty"), From 6da53ff97fd4667977535d4a45e92581bb8e4463 Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Tue, 5 Aug 2025 11:44:33 +0200 Subject: [PATCH 7/7] docs: clarify CoreAudio supported_configs changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a19e9cd8..d2dcbb8e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +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). +- 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)