ALSA: Change to device enumeration.

In order to get detailed information about a device, that is supported
formats, channels and sample rates, the PCM needs to be opened. This
can fail sometimes, in which case enumeration would previously just not
enumerate the device.

This is OK, but it creates issues. The first is that the enumerated
devices will not be consistent with what's reported by `aplay`. The
other is that when a hardware device is opened, iteration will not
include that device because it'll be opened in exclusive mode. This
creates a practical issue when trying to get the name of an already
opened device.

This commit makes it so that these devices will still be enumerated,
only they'll be missing detailed format, channels and rate information.
This commit is contained in:
David Reid
2026-01-09 13:38:07 +10:00
parent 224a4c9d3a
commit 948967dcbb
+86 -80
View File
@@ -28912,110 +28912,116 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu
ma_uint32 iChannel;
int openMode = 0; /*MA_SND_PCM_NO_AUTO_RESAMPLE | MA_SND_PCM_NO_AUTO_CHANNELS | MA_SND_PCM_NO_AUTO_FORMAT;*/
/* For detailed info we need to open the device. */
if (pContextStateALSA->snd_pcm_open(&pPCM, deviceInfo.id.alsa, (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE, openMode) != 0) {
goto next_device;
}
/* We need to initialize a HW parameters object in order to know what formats are supported. */
pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(pContextStateALSA->snd_pcm_hw_params_sizeof(), ma_context_get_allocation_callbacks(pContext));
if (pHWParams == NULL) {
pContextStateALSA->snd_pcm_close(pPCM);
goto next_device; /* Out of memory. */
}
resultALSA = pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams);
if (resultALSA < 0) {
ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_pcm_close(pPCM);
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters for device \"%s\". snd_pcm_hw_params_any() failed. %s.", deviceInfo.id.alsa, pContextStateALSA->snd_strerror(resultALSA));
goto next_device;
}
/*
Some ALSA devices can support many permutations of formats, channels and rates. We only support
a fixed number of permutations which means we need to employ some strategies to ensure the best
combinations are returned. An example is the "pulse" device which can do its own data conversion
in software and as a result can support any combination of format, channels and rate.
For detailed info we need to open the device. I can think of two reasons why opening might fail:
We want to ensure that the first data formats are the best. We have a list of favored sample
formats and sample rates, so these will be the basis of our iteration.
1) The device is disabled or physically unplugged.
2) The device is a hardware device and is already opened in exclusive mode
When this happens we're still going to enumerate the device, but we're going to just not fill out
the detailed info.
*/
if (pContextStateALSA->snd_pcm_open(&pPCM, deviceInfo.id.alsa, (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE, openMode) == 0) {
/* We need to initialize a HW parameters object in order to know what formats are supported. */
pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(pContextStateALSA->snd_pcm_hw_params_sizeof(), ma_context_get_allocation_callbacks(pContext));
if (pHWParams == NULL) {
pContextStateALSA->snd_pcm_close(pPCM);
goto next_device; /* Out of memory. */
}
/* Formats. We just iterate over our standard formats and test them, making sure we reset the configuration space each iteration. */
for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); iFormat += 1) {
ma_format format = g_maFormatPriorities[iFormat];
resultALSA = pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams);
if (resultALSA < 0) {
ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_pcm_close(pPCM);
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters for device \"%s\". snd_pcm_hw_params_any() failed. %s.", deviceInfo.id.alsa, pContextStateALSA->snd_strerror(resultALSA));
goto next_device;
}
/*
For each format we need to make sure we reset the configuration space so we don't return
channel counts and rates that aren't compatible with a format.
Some ALSA devices can support many permutations of formats, channels and rates. We only support
a fixed number of permutations which means we need to employ some strategies to ensure the best
combinations are returned. An example is the "pulse" device which can do its own data conversion
in software and as a result can support any combination of format, channels and rate.
We want to ensure that the first data formats are the best. We have a list of favored sample
formats and sample rates, so these will be the basis of our iteration.
*/
pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams);
/* Test the format first. If this fails it means the format is not supported and we can skip it. */
if (pContextStateALSA->snd_pcm_hw_params_test_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)) == 0) {
/* The format is supported. */
unsigned int minChannels;
unsigned int maxChannels;
/* Formats. We just iterate over our standard formats and test them, making sure we reset the configuration space each iteration. */
for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); iFormat += 1) {
ma_format format = g_maFormatPriorities[iFormat];
/*
The configuration space needs to be restricted to this format so we can get an accurate
picture of which sample rates and channel counts are support with this format.
For each format we need to make sure we reset the configuration space so we don't return
channel counts and rates that aren't compatible with a format.
*/
pContextStateALSA->snd_pcm_hw_params_set_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format));
pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams);
/* Now we need to check for supported channels. */
pContextStateALSA->snd_pcm_hw_params_get_channels_min(pHWParams, &minChannels);
pContextStateALSA->snd_pcm_hw_params_get_channels_max(pHWParams, &maxChannels);
/* Test the format first. If this fails it means the format is not supported and we can skip it. */
if (pContextStateALSA->snd_pcm_hw_params_test_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)) == 0) {
/* The format is supported. */
unsigned int minChannels;
unsigned int maxChannels;
if (minChannels > MA_MAX_CHANNELS) {
continue; /* Too many channels. */
}
if (maxChannels < MA_MIN_CHANNELS) {
continue; /* Not enough channels. */
}
/*
The configuration space needs to be restricted to this format so we can get an accurate
picture of which sample rates and channel counts are support with this format.
*/
pContextStateALSA->snd_pcm_hw_params_set_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format));
/*
Make sure the channel count is clamped. This is mainly intended for the max channels
because some devices can report an unbound maximum.
*/
minChannels = ma_clamp(minChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS);
maxChannels = ma_clamp(maxChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS);
/* Now we need to check for supported channels. */
pContextStateALSA->snd_pcm_hw_params_get_channels_min(pHWParams, &minChannels);
pContextStateALSA->snd_pcm_hw_params_get_channels_max(pHWParams, &maxChannels);
if (minChannels == MA_MIN_CHANNELS && maxChannels == MA_MAX_CHANNELS) {
/* The device supports all channels. Don't iterate over every single one. Instead just set the channels to 0 which means all channels are supported. */
ma_context_iterate_rates_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, 0, 0, &deviceInfo); /* Intentionally setting the channel count to 0 as that means all channels are supported. */
} else {
/* The device only supports a specific set of channels. We need to iterate over all of them. */
for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) {
/* Test the channel before applying it to the configuration space. */
unsigned int channels = iChannel;
if (minChannels > MA_MAX_CHANNELS) {
continue; /* Too many channels. */
}
if (maxChannels < MA_MIN_CHANNELS) {
continue; /* Not enough channels. */
}
/* Make sure our channel range is reset before testing again or else we'll always fail the test. */
pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams);
pContextStateALSA->snd_pcm_hw_params_set_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format));
/*
Make sure the channel count is clamped. This is mainly intended for the max channels
because some devices can report an unbound maximum.
*/
minChannels = ma_clamp(minChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS);
maxChannels = ma_clamp(maxChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS);
if (pContextStateALSA->snd_pcm_hw_params_test_channels(pPCM, pHWParams, channels) == 0) {
/* The channel count is supported. */
if (minChannels == MA_MIN_CHANNELS && maxChannels == MA_MAX_CHANNELS) {
/* The device supports all channels. Don't iterate over every single one. Instead just set the channels to 0 which means all channels are supported. */
ma_context_iterate_rates_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, 0, 0, &deviceInfo); /* Intentionally setting the channel count to 0 as that means all channels are supported. */
} else {
/* The device only supports a specific set of channels. We need to iterate over all of them. */
for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) {
/* Test the channel before applying it to the configuration space. */
unsigned int channels = iChannel;
/* The configuration space now needs to be restricted to the channel count before extracting the sample rate. */
pContextStateALSA->snd_pcm_hw_params_set_channels(pPCM, pHWParams, channels);
/* Make sure our channel range is reset before testing again or else we'll always fail the test. */
pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams);
pContextStateALSA->snd_pcm_hw_params_set_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format));
/* Only after the configuration space has been restricted to the specific channel count should we iterate over our sample rates. */
ma_context_iterate_rates_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, channels, 0, &deviceInfo);
} else {
/* The channel count is not supported. Skip. */
if (pContextStateALSA->snd_pcm_hw_params_test_channels(pPCM, pHWParams, channels) == 0) {
/* The channel count is supported. */
/* The configuration space now needs to be restricted to the channel count before extracting the sample rate. */
pContextStateALSA->snd_pcm_hw_params_set_channels(pPCM, pHWParams, channels);
/* Only after the configuration space has been restricted to the specific channel count should we iterate over our sample rates. */
ma_context_iterate_rates_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, channels, 0, &deviceInfo);
} else {
/* The channel count is not supported. Skip. */
}
}
}
} else {
/* The format is not supported. Skip. */
}
} else {
/* The format is not supported. Skip. */
}
ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_pcm_close(pPCM);
}
ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_pcm_close(pPCM);
}
if (!ma_is_device_blacklisted__alsa(deviceType, NAME)) {