Skip to content

Commit

Permalink
fix: improve audio device enumeration
Browse files Browse the repository at this point in the history
Replace range-based sample rate configs with explicit sample rates to:
- Remove duplicate ALSA device entries
- Filter out unreasonable sample rates (>384kHz)
- Remove phantom capabilities from software interfaces
- Sort output for consistent display

The enumeration now tries each standard sample rate (44.1kHz through
384kHz) explicitly rather than using ALSA's reported ranges, which
could indicate support for impossible rates like 4GHz.
  • Loading branch information
roderickvd committed Nov 25, 2024
1 parent 075be85 commit 84bfaa3
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
### Fixed
- [config] Hexademical base does not correlate to key length
- [player] Use pipe separator in device specs for ALSA compatibility
- [player] Clean up audio device enumeration output
- [repo] Fix pull request template format

### Security
Expand Down
75 changes: 50 additions & 25 deletions src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,26 @@ impl Player {
Ok((sink, stream))
}

/// The list of supported sample rates.
///
/// This list is used to filter out unreasonable sample rates.
/// Common sample rates in Hz:
/// * 44100 - CD audio, most streaming services
/// * 48000 - Professional digital audio, DVDs, most DAWs
/// * 88200/96000 - High resolution audio
/// * 176400/192000 - Studio quality
/// * 352800/384000 - Ultra high definition audio
const SAMPLE_RATES: [u32; 8] = [
44_100, 48_000, 88_200, 96_000, 176_400, 192_000, 352_800, 384_000,
];

#[must_use]
pub fn enumerate_devices() -> Vec<String> {
let hosts = cpal::available_hosts();
let mut result = Vec::new();

// Create a set to store the unique device names.
// On Alsa hosts, the same device may otherwise be enumerated multiple times.
let mut result = HashSet::new();

// Get the default host, device and config.
let default_host = cpal::default_host();
Expand All @@ -265,38 +281,47 @@ impl Player {
if let Ok(configs) = device.supported_output_configs() {
for config in configs {
if let Ok(device_name) = device.name() {
let max_sample_rate = config.with_max_sample_rate();
let mut line = format!(
"{}|{}|{}|{}",
host.id().name(),
device_name,
max_sample_rate.sample_rate().0,
max_sample_rate.sample_format(),
);

// Check if this is the default host, device
// and config.
if default_host.id() == host.id()
&& default_device.as_ref().is_some_and(|default_device| {
default_device
.name()
.is_ok_and(|default_name| default_name == device_name)
})
&& default_config.as_ref().is_some_and(|default_config| {
*default_config == max_sample_rate
})
{
line.push_str(" (default)");
for sample_rate in &Self::SAMPLE_RATES {
if let Some(config) =
config.try_with_sample_rate(cpal::SampleRate(*sample_rate))
{
let mut line = format!(
"{}|{}|{}|{}",
host.id().name(),
device_name,
config.sample_rate().0,
config.sample_format(),
);

// Check if this is the default host, device
// and config.
if default_host.id() == host.id()
&& default_device.as_ref().is_some_and(
|default_device| {
default_device.name().is_ok_and(
|default_name| default_name == device_name,
)
},
)
&& default_config.as_ref().is_some_and(
|default_config| *default_config == config,
)
{
line.push_str(" (default)");
}

result.insert(line);
}
}

result.push(line);
}
}
}
}
}
}

let mut result: Vec<_> = result.into_iter().collect();
result.sort();
result
}

Expand Down

0 comments on commit 84bfaa3

Please sign in to comment.