ALSA: Include format information in device enumeration.

This commit is contained in:
David Reid
2025-07-20 16:20:07 +10:00
parent c257d19c86
commit 3add89fdd2
+116 -157
View File
@@ -29261,6 +29261,57 @@ static void ma_context_uninit__alsa(ma_context* pContext)
ma_free(pContextStateALSA, ma_context_get_allocation_callbacks(pContext)); ma_free(pContextStateALSA, ma_context_get_allocation_callbacks(pContext));
} }
static void ma_context_test_rate_and_add_native_data_format__alsa(ma_context_state_alsa* pContextStateALSA, ma_snd_pcm_t* pPCM, ma_snd_pcm_hw_params_t* pHWParams, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 flags, ma_device_info* pDeviceInfo)
{
MA_ASSERT(pPCM != NULL);
MA_ASSERT(pHWParams != NULL);
MA_ASSERT(pDeviceInfo != NULL);
if (pDeviceInfo->nativeDataFormatCount < ma_countof(pDeviceInfo->nativeDataFormats) && pContextStateALSA->snd_pcm_hw_params_test_rate(pPCM, pHWParams, sampleRate, 0) == 0) {
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format;
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels;
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate;
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = flags;
pDeviceInfo->nativeDataFormatCount += 1;
}
}
static void ma_context_iterate_rates_and_add_native_data_format__alsa(ma_context_state_alsa* pContextStateALSA, ma_snd_pcm_t* pPCM, ma_snd_pcm_hw_params_t* pHWParams, ma_format format, ma_uint32 channels, ma_uint32 flags, ma_device_info* pDeviceInfo)
{
ma_uint32 iSampleRate;
unsigned int minSampleRate;
unsigned int maxSampleRate;
int sampleRateDir; /* Not used. Just passed into snd_pcm_hw_params_get_rate_min/max(). */
/* There could be a range. */
pContextStateALSA->snd_pcm_hw_params_get_rate_min(pHWParams, &minSampleRate, &sampleRateDir);
pContextStateALSA->snd_pcm_hw_params_get_rate_max(pHWParams, &maxSampleRate, &sampleRateDir);
/* Make sure our sample rates are clamped to sane values. Stupid devices like "pulse" will reports rates like "1" which is ridiculous. */
minSampleRate = ma_clamp(minSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max);
maxSampleRate = ma_clamp(maxSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max);
for (iSampleRate = 0; iSampleRate < ma_countof(g_maStandardSampleRatePriorities); iSampleRate += 1) {
ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iSampleRate];
if (standardSampleRate >= minSampleRate && standardSampleRate <= maxSampleRate) {
ma_context_test_rate_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, channels, standardSampleRate, flags, pDeviceInfo);
}
}
/* Now make sure our min and max rates are included just in case they aren't in the range of our standard rates. */
if (!ma_is_standard_sample_rate(minSampleRate)) {
ma_context_test_rate_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, channels, minSampleRate, flags, pDeviceInfo);
}
if (!ma_is_standard_sample_rate(maxSampleRate) && maxSampleRate != minSampleRate) {
ma_context_test_rate_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, channels, maxSampleRate, flags, pDeviceInfo);
}
}
static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData)
{ {
ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(pContext); ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(pContext);
@@ -29340,18 +29391,23 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu
} }
MA_ZERO_OBJECT(&deviceInfo); MA_ZERO_OBJECT(&deviceInfo);
ma_strncpy_s(deviceInfo.id.alsa, sizeof(deviceInfo.id.alsa), hwid, (size_t)-1);
/* /*
There's no good way to determine whether or not a device is the default on Linux. We're just going to do something simple and Defualt.
just use the name of "default" as the indicator.
There's no good way to determine whether or not a device is the default with ALSA. We're just going to do
something simple and just use the name of "default" as the indicator.
*/ */
if (ma_strcmp(deviceInfo.id.alsa, "default") == 0) { if (ma_strcmp(deviceInfo.id.alsa, "default") == 0) {
deviceInfo.isDefault = MA_TRUE; deviceInfo.isDefault = MA_TRUE;
} }
/* ID. */
ma_strncpy_s(deviceInfo.id.alsa, sizeof(deviceInfo.id.alsa), hwid, (size_t)-1);
/* /*
Name.
DESC is the friendly name. We treat this slightly differently depending on whether or not we are using verbose DESC is the friendly name. We treat this slightly differently depending on whether or not we are using verbose
device enumeration. In verbose mode we want to take the entire description so that the end-user can distinguish device enumeration. In verbose mode we want to take the entire description so that the end-user can distinguish
between the subdevices of each card/dev pair. In simplified mode, however, we only want the first part of the between the subdevices of each card/dev pair. In simplified mode, however, we only want the first part of the
@@ -29384,172 +29440,29 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu
} }
} }
if (!ma_is_device_blacklisted__alsa(deviceType, NAME)) {
cbResult = callback(deviceType, &deviceInfo, pUserData);
}
/* /*
Some devices are both playback and capture, but they are only enumerated by ALSA once. We need to fire the callback Data Format.
again for the other device type in this case. We do this for known devices and where the IOID hint is NULL, which
means both Input and Output. Retrieving the data format requires us to open the device.
*/ */
if (cbResult) { {
if (ma_is_common_device_name__alsa(NAME) || IOID == NULL) {
if (deviceType == ma_device_type_playback) {
if (!ma_is_capture_device_blacklisted__alsa(NAME)) {
cbResult = callback(ma_device_type_capture, &deviceInfo, pUserData);
}
} else {
if (!ma_is_playback_device_blacklisted__alsa(NAME)) {
cbResult = callback(ma_device_type_playback, &deviceInfo, pUserData);
}
}
}
}
if (cbResult == MA_FALSE) {
stopEnumeration = MA_TRUE;
}
next_device:
free(NAME);
free(DESC);
free(IOID);
ppNextDeviceHint += 1;
/* We need to stop enumeration if the callback returned false. */
if (stopEnumeration) {
break;
}
}
ma_free(pUniqueIDs, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_device_name_free_hint((void**)ppDeviceHints);
ma_mutex_unlock(&pContextStateALSA->internalDeviceEnumLock);
return MA_SUCCESS;
}
typedef struct
{
ma_device_type deviceType;
const ma_device_id* pDeviceID;
ma_share_mode shareMode;
ma_device_info* pDeviceInfo;
ma_bool32 foundDevice;
} ma_context_get_device_info_enum_callback_data__alsa;
static ma_bool32 ma_context_get_device_info_enum_callback__alsa(ma_device_type deviceType, const ma_device_info* pDeviceInfo, void* pUserData)
{
ma_context_get_device_info_enum_callback_data__alsa* pData = (ma_context_get_device_info_enum_callback_data__alsa*)pUserData;
MA_ASSERT(pData != NULL);
if (pData->pDeviceID == NULL && ma_strcmp(pDeviceInfo->id.alsa, "default") == 0) {
ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1);
pData->foundDevice = MA_TRUE;
} else {
if (pData->deviceType == deviceType && (pData->pDeviceID != NULL && ma_strcmp(pData->pDeviceID->alsa, pDeviceInfo->id.alsa) == 0)) {
ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1);
pData->foundDevice = MA_TRUE;
}
}
/* Keep enumerating until we have found the device. */
return !pData->foundDevice;
}
static void ma_context_test_rate_and_add_native_data_format__alsa(ma_context_state_alsa* pContextStateALSA, ma_snd_pcm_t* pPCM, ma_snd_pcm_hw_params_t* pHWParams, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 flags, ma_device_info* pDeviceInfo)
{
MA_ASSERT(pPCM != NULL);
MA_ASSERT(pHWParams != NULL);
MA_ASSERT(pDeviceInfo != NULL);
if (pDeviceInfo->nativeDataFormatCount < ma_countof(pDeviceInfo->nativeDataFormats) && pContextStateALSA->snd_pcm_hw_params_test_rate(pPCM, pHWParams, sampleRate, 0) == 0) {
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format;
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels;
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate;
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = flags;
pDeviceInfo->nativeDataFormatCount += 1;
}
}
static void ma_context_iterate_rates_and_add_native_data_format__alsa(ma_context_state_alsa* pContextStateALSA, ma_snd_pcm_t* pPCM, ma_snd_pcm_hw_params_t* pHWParams, ma_format format, ma_uint32 channels, ma_uint32 flags, ma_device_info* pDeviceInfo)
{
ma_uint32 iSampleRate;
unsigned int minSampleRate;
unsigned int maxSampleRate;
int sampleRateDir; /* Not used. Just passed into snd_pcm_hw_params_get_rate_min/max(). */
/* There could be a range. */
pContextStateALSA->snd_pcm_hw_params_get_rate_min(pHWParams, &minSampleRate, &sampleRateDir);
pContextStateALSA->snd_pcm_hw_params_get_rate_max(pHWParams, &maxSampleRate, &sampleRateDir);
/* Make sure our sample rates are clamped to sane values. Stupid devices like "pulse" will reports rates like "1" which is ridiculous. */
minSampleRate = ma_clamp(minSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max);
maxSampleRate = ma_clamp(maxSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max);
for (iSampleRate = 0; iSampleRate < ma_countof(g_maStandardSampleRatePriorities); iSampleRate += 1) {
ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iSampleRate];
if (standardSampleRate >= minSampleRate && standardSampleRate <= maxSampleRate) {
ma_context_test_rate_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, channels, standardSampleRate, flags, pDeviceInfo);
}
}
/* Now make sure our min and max rates are included just in case they aren't in the range of our standard rates. */
if (!ma_is_standard_sample_rate(minSampleRate)) {
ma_context_test_rate_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, channels, minSampleRate, flags, pDeviceInfo);
}
if (!ma_is_standard_sample_rate(maxSampleRate) && maxSampleRate != minSampleRate) {
ma_context_test_rate_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, channels, maxSampleRate, flags, pDeviceInfo);
}
}
static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo)
{
ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(pContext);
ma_context_get_device_info_enum_callback_data__alsa data;
ma_result result; ma_result result;
int resultALSA;
ma_snd_pcm_t* pPCM; ma_snd_pcm_t* pPCM;
ma_snd_pcm_hw_params_t* pHWParams; ma_snd_pcm_hw_params_t* pHWParams;
ma_uint32 iFormat; ma_uint32 iFormat;
ma_uint32 iChannel; ma_uint32 iChannel;
MA_ASSERT(pContextStateALSA != NULL);
/* We just enumerate to find basic information about the device. */
data.deviceType = deviceType;
data.pDeviceID = pDeviceID;
data.pDeviceInfo = pDeviceInfo;
data.foundDevice = MA_FALSE;
result = ma_context_enumerate_devices__alsa(pContext, ma_context_get_device_info_enum_callback__alsa, &data);
if (result != MA_SUCCESS) {
return result;
}
if (!data.foundDevice) {
return MA_NO_DEVICE;
}
if (ma_strcmp(pDeviceInfo->id.alsa, "default") == 0) {
pDeviceInfo->isDefault = MA_TRUE;
}
/* For detailed info we need to open the device. */ /* For detailed info we need to open the device. */
result = ma_context_open_pcm__alsa(pContext, pContextStateALSA, ma_share_mode_shared, deviceType, pDeviceID, 0, &pPCM); result = ma_context_open_pcm__alsa(pContext, pContextStateALSA, ma_share_mode_shared, deviceType, &deviceInfo.id, 0, &pPCM);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
return result; goto next_device;
} }
/* We need to initialize a HW parameters object in order to know what formats are supported. */ /* 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)); pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(pContextStateALSA->snd_pcm_hw_params_sizeof(), ma_context_get_allocation_callbacks(pContext));
if (pHWParams == NULL) { if (pHWParams == NULL) {
pContextStateALSA->snd_pcm_close(pPCM); pContextStateALSA->snd_pcm_close(pPCM);
return MA_OUT_OF_MEMORY; goto next_device; /* Out of memory. */
} }
resultALSA = pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams); resultALSA = pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams);
@@ -29557,7 +29470,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic
ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_pcm_close(pPCM); pContextStateALSA->snd_pcm_close(pPCM);
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.");
return ma_result_from_errno(-resultALSA); goto next_device;
} }
/* /*
@@ -29612,7 +29525,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic
if (minChannels == MA_MIN_CHANNELS && maxChannels == MA_MAX_CHANNELS) { 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. */ /* 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, pDeviceInfo); /* Intentionally setting the channel count to 0 as that 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 { } else {
/* The device only supports a specific set of channels. We need to iterate over all of them. */ /* The device only supports a specific set of channels. We need to iterate over all of them. */
for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) { for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) {
@@ -29630,7 +29543,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic
pContextStateALSA->snd_pcm_hw_params_set_channels(pPCM, pHWParams, channels); 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. */ /* 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, pDeviceInfo); ma_context_iterate_rates_and_add_native_data_format__alsa(pContextStateALSA, pPCM, pHWParams, format, channels, 0, &deviceInfo);
} else { } else {
/* The channel count is not supported. Skip. */ /* The channel count is not supported. Skip. */
} }
@@ -29644,6 +29557,52 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic
ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_pcm_close(pPCM); pContextStateALSA->snd_pcm_close(pPCM);
}
if (!ma_is_device_blacklisted__alsa(deviceType, NAME)) {
cbResult = callback(deviceType, &deviceInfo, pUserData);
}
/*
Some devices are both playback and capture, but they are only enumerated by ALSA once. We need to fire the callback
again for the other device type in this case. We do this for known devices and where the IOID hint is NULL, which
means both Input and Output.
*/
if (cbResult) {
if (ma_is_common_device_name__alsa(NAME) || IOID == NULL) {
if (deviceType == ma_device_type_playback) {
if (!ma_is_capture_device_blacklisted__alsa(NAME)) {
cbResult = callback(ma_device_type_capture, &deviceInfo, pUserData);
}
} else {
if (!ma_is_playback_device_blacklisted__alsa(NAME)) {
cbResult = callback(ma_device_type_playback, &deviceInfo, pUserData);
}
}
}
}
if (cbResult == MA_FALSE) {
stopEnumeration = MA_TRUE;
}
next_device:
free(NAME);
free(DESC);
free(IOID);
ppNextDeviceHint += 1;
/* We need to stop enumeration if the callback returned false. */
if (stopEnumeration) {
break;
}
}
ma_free(pUniqueIDs, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_device_name_free_hint((void**)ppDeviceHints);
ma_mutex_unlock(&pContextStateALSA->internalDeviceEnumLock);
return MA_SUCCESS; return MA_SUCCESS;
} }
@@ -30472,7 +30431,7 @@ static ma_device_backend_vtable ma_gDeviceBackendVTable_ALSA =
ma_context_init__alsa, ma_context_init__alsa,
ma_context_uninit__alsa, ma_context_uninit__alsa,
ma_context_enumerate_devices__alsa, ma_context_enumerate_devices__alsa,
ma_context_get_device_info__alsa, NULL,
ma_device_init__alsa, ma_device_init__alsa,
ma_device_uninit__alsa, ma_device_uninit__alsa,
ma_device_start__alsa, ma_device_start__alsa,