Commit 7b3e89d4 authored by David Reid's avatar David Reid

ALSA: Improvements to device enumeration.

 * By default, device enumeration will now only enumerate over unique
   card/device pairs. Applications can enable verbose device
   enumeration by setting the alsa.useVerboseDeviceInteration context
   config variable.
 * By default, the "null" device is excluded from enumeration. This can
   be changed by setting the alsa.includeNullDevice context config
   variable.

Relates to issue #2.
parent 60a8e3fd
...@@ -585,7 +585,8 @@ typedef struct ...@@ -585,7 +585,8 @@ typedef struct
struct struct
{ {
mal_bool32 useVerbsoseDeviceEnumeration; mal_bool32 useVerboseDeviceEnumeration;
mal_bool32 includeNullDevice; // The "null" device is explicitly excluded by default. Setting this to true includes it.
} alsa; } alsa;
} mal_context_config; } mal_context_config;
...@@ -1481,6 +1482,14 @@ typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); ...@@ -1481,6 +1482,14 @@ typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)();
#endif #endif
#endif #endif
#ifndef mal_realloc
#ifdef MAL_WIN32
#define mal_realloc(p, sz) (((sz) > 0) ? ((p) ? HeapReAlloc(GetProcessHeap(), 0, (p), (sz)) : HeapAlloc(GetProcessHeap(), 0, (sz))) : ((VOID*)(SIZE_T)(HeapFree(GetProcessHeap(), 0, (p)) & 0)))
#else
#define mal_realloc(p, sz) realloc((p), (sz))
#endif
#endif
#ifndef mal_free #ifndef mal_free
#ifdef MAL_WIN32 #ifdef MAL_WIN32
#define mal_free(p) HeapFree(GetProcessHeap(), 0, (p)) #define mal_free(p) HeapFree(GetProcessHeap(), 0, (p))
...@@ -1510,6 +1519,33 @@ typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); ...@@ -1510,6 +1519,33 @@ typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)();
// 34: ERANGE // 34: ERANGE
// //
// Not using symbolic constants for errors because I want to avoid #including errno.h // Not using symbolic constants for errors because I want to avoid #including errno.h
static int mal_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src)
{
if (dst == 0) {
return 22;
}
if (dstSizeInBytes == 0) {
return 34;
}
if (src == 0) {
dst[0] = '\0';
return 22;
}
size_t i;
for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
if (i < dstSizeInBytes) {
dst[i] = '\0';
return 0;
}
dst[0] = '\0';
return 34;
}
static int mal_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) static int mal_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
{ {
if (dst == 0) { if (dst == 0) {
...@@ -5455,6 +5491,46 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -5455,6 +5491,46 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice)
static mal_bool32 mal_is_device_name_in_hw_format__alsa(const char* hwid)
{
// This function is just checking whether or not hwid is in "hw:%d,%d" format.
if (hwid == NULL) {
return MAL_FALSE;
}
if (hwid[0] != 'h' || hwid[1] != 'w' || hwid[2] != ':') {
return MAL_FALSE;
}
hwid += 3;
int commaPos;
const char* dev = mal_find_char(hwid, ',', &commaPos);
if (dev == NULL) {
return MAL_FALSE;
} else {
dev += 1; // Skip past the ",".
}
// Check if the part between the ":" and the "," contains only numbers. If not, return false.
for (int i = 0; i < commaPos; ++i) {
if (hwid[i] < '0' || hwid[i] > '9') {
return MAL_FALSE;
}
}
// Check if everything after the "," is numeric. If not, return false.
int i = 0;
while (dev[i] != '\0') {
if (dev[i] < '0' || dev[i] > '9') {
return MAL_FALSE;
}
i += 1;
}
return MAL_TRUE;
}
static int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, char* dst, size_t dstSize, const char* src) // Returns 0 on success, non-0 on error. static int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, char* dst, size_t dstSize, const char* src) // Returns 0 on success, non-0 on error.
{ {
...@@ -5466,6 +5542,11 @@ static int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, cha ...@@ -5466,6 +5542,11 @@ static int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, cha
*dst = '\0'; // Safety. *dst = '\0'; // Safety.
if (src == NULL) return -1; if (src == NULL) return -1;
// If the input name is already in "hw:%d,%d" format, just return that verbatim.
if (mal_is_device_name_in_hw_format__alsa(src)) {
return mal_strcpy_s(dst, dstSize, src);
}
int colonPos; int colonPos;
src = mal_find_char(src, ':', &colonPos); src = mal_find_char(src, ':', &colonPos);
...@@ -5508,6 +5589,19 @@ static int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, cha ...@@ -5508,6 +5589,19 @@ static int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, cha
return 0; return 0;
} }
static mal_bool32 mal_does_id_exist_in_list__alsa(mal_device_id* pUniqueIDs, mal_uint32 count, const char* pHWID)
{
mal_assert(pHWID != NULL);
for (mal_uint32 i = 0; i < count; ++i) {
if (mal_strcmp(pUniqueIDs[i].alsa, pHWID) == 0) {
return MAL_TRUE;
}
}
return MAL_FALSE;
}
static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo)
{ {
(void)pContext; (void)pContext;
...@@ -5520,6 +5614,8 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_ ...@@ -5520,6 +5614,8 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_
return MAL_NO_BACKEND; return MAL_NO_BACKEND;
} }
mal_device_id* pUniqueIDs = NULL;
mal_uint32 uniqueIDCount = 0;
char** ppNextDeviceHint = ppDeviceHints; char** ppNextDeviceHint = ppDeviceHints;
while (*ppNextDeviceHint != NULL) { while (*ppNextDeviceHint != NULL) {
...@@ -5527,10 +5623,16 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_ ...@@ -5527,10 +5623,16 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_
char* DESC = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "DESC"); char* DESC = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "DESC");
char* IOID = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "IOID"); char* IOID = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "IOID");
// Only include devices if they are of the correct type. Special cases for "default", "null" and "pulse" - these are always included. // Only include devices if they are of the correct type. Special cases for "default", "null" and "pulse" - these are included for both playback and capture
// regardless of the IOID setting.
mal_bool32 includeThisDevice = MAL_FALSE; mal_bool32 includeThisDevice = MAL_FALSE;
if (strcmp(NAME, "default") == 0 || strcmp(NAME, "null") == 0 || strcmp(NAME, "pulse") == 0) { if (strcmp(NAME, "default") == 0 || strcmp(NAME, "pulse") == 0 || strcmp(NAME, "null") == 0) {
includeThisDevice = MAL_TRUE; includeThisDevice = MAL_TRUE;
// Exclude the "null" device if requested.
if (strcmp(NAME, "null") == 0 && !pContext->config.alsa.includeNullDevice) {
includeThisDevice = MAL_FALSE;
}
} else { } else {
if ((type == mal_device_type_playback && (IOID == NULL || strcmp(IOID, "Output") == 0)) || if ((type == mal_device_type_playback && (IOID == NULL || strcmp(IOID, "Output") == 0)) ||
(type == mal_device_type_capture && (IOID != NULL && strcmp(IOID, "Input" ) == 0))) { (type == mal_device_type_capture && (IOID != NULL && strcmp(IOID, "Input" ) == 0))) {
...@@ -5538,23 +5640,82 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_ ...@@ -5538,23 +5640,82 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_
} }
} }
if (includeThisDevice) { if (includeThisDevice) {
#if 0 #if 0
printf("NAME: %s\n", NAME); printf("NAME: %s\n", NAME);
printf("DESC: %s\n", DESC); printf("DESC: %s\n", DESC);
printf("IOID: %s\n", IOID); printf("IOID: %s\n", IOID);
char hwid[256]; char hwid2[256];
mal_convert_device_name_to_hw_format__alsa(pContext, hwid, sizeof(hwid), NAME); mal_convert_device_name_to_hw_format__alsa(pContext, hwid2, sizeof(hwid2), NAME);
printf("DEVICE ID: %s\n\n", hwid); printf("DEVICE ID: %s\n\n", hwid2);
#endif #endif
char hwid[sizeof(pUniqueIDs->alsa)];
if (NAME != NULL) {
if (pContext->config.alsa.useVerboseDeviceEnumeration) {
mal_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1);
} else {
if (mal_convert_device_name_to_hw_format__alsa(pContext, hwid, sizeof(hwid), NAME) != 0) {
mal_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1);
}
if (mal_does_id_exist_in_list__alsa(pUniqueIDs, uniqueIDCount, hwid)) {
goto next_device; // The device has already been enumerated. Move on to the next one.
} else {
// The device has not yet been enumerated. Make sure it's added to our list so that it's not enumerated again.
mal_device_id* pNewUniqueIDs = mal_realloc(pUniqueIDs, sizeof(*pUniqueIDs) * (uniqueIDCount + 1));
if (pNewUniqueIDs == NULL) {
goto next_device; // Failed to allocate memory.
}
pUniqueIDs = pNewUniqueIDs;
mal_copy_memory(pUniqueIDs[uniqueIDCount].alsa, hwid, sizeof(hwid));
uniqueIDCount += 1;
}
}
} else {
mal_zero_memory(hwid, sizeof(hwid));
}
if (pInfo != NULL) { if (pInfo != NULL) {
if (infoSize > 0) { if (infoSize > 0) {
mal_zero_object(pInfo); mal_zero_object(pInfo);
mal_strncpy_s(pInfo->id.alsa, sizeof(pInfo->id.alsa), NAME ? NAME : "", (size_t)-1); // NAME is the ID. mal_strncpy_s(pInfo->id.alsa, sizeof(pInfo->id.alsa), hwid, (size_t)-1);
mal_strncpy_s(pInfo->name, sizeof(pInfo->name), DESC ? DESC : "", (size_t)-1); // DESC is the friendly name.
// 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
// between the subdevices of each card/dev pair. In simplified mode, however, we only want the first part of the
// description.
//
// The value in DESC seems to be split into two lines, with the first line being the name of the device and the
// second line being a description of the device. I don't like having the description be across two lines because
// it makes formatting ugly and annoying. I'm therefore deciding to put it all on a single line with the second line
// being put into parentheses. In simplified mode I'm just stripping the second line entirely.
if (DESC != NULL) {
int lfPos;
const char* line2 = mal_find_char(DESC, '\n', &lfPos);
if (line2 != NULL) {
line2 += 1; // Skip past the new-line character.
if (pContext->config.alsa.useVerboseDeviceEnumeration) {
// Verbose mode. Put the second line in brackets.
mal_strncpy_s(pInfo->name, sizeof(pInfo->name), DESC, lfPos);
mal_strcat_s (pInfo->name, sizeof(pInfo->name), " (");
mal_strcat_s (pInfo->name, sizeof(pInfo->name), line2);
mal_strcat_s (pInfo->name, sizeof(pInfo->name), ")");
} else {
// Simplified mode. Strip the second line entirely.
mal_strncpy_s(pInfo->name, sizeof(pInfo->name), DESC, lfPos);
}
} else {
// There's no second line. Just copy the whole description.
mal_strcpy_s(pInfo->name, sizeof(pInfo->name), DESC);
}
}
pInfo += 1; pInfo += 1;
infoSize -= 1; infoSize -= 1;
} }
...@@ -5563,12 +5724,15 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_ ...@@ -5563,12 +5724,15 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_
*pCount += 1; *pCount += 1;
} }
next_device:
free(NAME); free(NAME);
free(DESC); free(DESC);
free(IOID); free(IOID);
ppNextDeviceHint += 1; ppNextDeviceHint += 1;
} }
mal_free(pUniqueIDs);
((mal_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints); ((mal_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints);
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -5634,7 +5798,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t ...@@ -5634,7 +5798,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t
} else { } else {
// Try falling back to "hw:%d,%d" format. // Try falling back to "hw:%d,%d" format.
char hwid[256]; char hwid[256];
if (mal_convert_device_name_to_hw_format__alsa(pContext, hwid, sizeof(hwid), deviceName) < 0) { if (mal_convert_device_name_to_hw_format__alsa(pContext, hwid, sizeof(hwid), deviceName) != 0) {
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed.", MAL_ALSA_FAILED_TO_OPEN_DEVICE); return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed.", MAL_ALSA_FAILED_TO_OPEN_DEVICE);
} else { } else {
...@@ -9936,6 +10100,11 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count) ...@@ -9936,6 +10100,11 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count)
// configuring the context. The works in the same kind of way as the device config. The rationale for this // configuring the context. The works in the same kind of way as the device config. The rationale for this
// change is to give applications better control over context-level properties, add support for backend- // change is to give applications better control over context-level properties, add support for backend-
// specific configurations, and support extensibility without breaking the API. // specific configurations, and support extensibility without breaking the API.
// - ALSA: By default, device enumeration will now only enumerate over unique card/device pairs. Applications
// can enable verbose device enumeration by setting the alsa.useVerboseDeviceInteration context config
// variable.
// - ALSA: By default, the "null" device is excluded from enumeration. This can be changed by setting the
// alsa.includeNullDevice context config variable.
// //
// v0.4 - 2017-11-05 // v0.4 - 2017-11-05
// - API CHANGE: The log callback is now per-context rather than per-device and as is thus now passed to // - API CHANGE: The log callback is now per-context rather than per-device and as is thus now passed to
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment