Pi/Linux – Enumerating Sound Devices

In my exploration of the Linux ALSA API, I had to diagnose why I was unable to hear sounds. The problems was that the sound was playing to the wrong device. I found that the default output device varied by the Raspberry Pi that I used. Each Pi was running in uniquely configured hardware (a uConsole, Pocket Console 35, so on). It is possible to configure the default output device for a Pi. I ended up handling this problem in software. To do this, I had to enumerate the output devices. The code that follows was run on both a Pi and on an x64 Ubuntu Linux instance. If given a choice, you should rely on the software on your device to find the output devices. Learning how to do this from the APIs opens the possibility of software being able to select an output device independent of the system defaults.

The command to list your audio devices is aplay --list-devices. If that’s all you wanted to know, read no further. If you want to know how to do this from your own applications, continue reading.

Enumerating the Devices

The first thing that I did in furtherance of of this problem was enumerate the audio output devices. There is a single API function that we need to retrieve this information. It is snd_device_name_hint. The function takes 3 parameters.

  1. Device Index.
  2. Device Class
  3. Output Pointer

Each audio device in a system is assigned an ID number. You might hear each device referred to as a “card” for historical reasons (audio rendering devices were often called “sound cards” in earlier eras where they were on a separate circuit board). If you want information on a specific card, you can pass its index to this function. If you want information on all cards use an index of -1.

The Device Class is a string that identifies what type of device you are looking for. For cards that are playing back wave-form data, the string value “pcm” is used here. Other acceptable values include "seq". "rawmidi", and "timer". These other values are not discussed in this series.

The final argument is an output pointer. This function call will allocate memory that will be populated with the information of interest. Note that we will need to notify when the memory can be freed by passing the value that gets assigned to this pointer to snd_device_name_free_hint. The return value received is a pointer to a null-terminated array values that can be used to retrieve information on specific audio cards. We can extract these other values by calling snd_device_name_get_hint, passing one of these values as the first parameter and the property name that we want to received ("NAME", "DESC", or "IOID"). If successful, the function will return a pointer to a string containing the name. We also need to free this string by calling free().

The output pointer, being a null-terminated array, provides a quick way to count the number of devices on a system. We can iterate through the array until we encounter a null and count how many items were not null.

    void** deviceNames;
    int err;
    err = snd_device_name_hint(-1,"pcm",(&deviceNames));
    void **n = deviceNames;
    int deviceCount = 0;
    for(int i=0; n[i] != NULL; i++) {
        deviceCount++;
    }
std::wcout << "Number of PCM devices: " <<  ANSI_COLOR_GREEN << deviceCount << ANSI_COLOR_RESET << std::endl;

Reading the Device Names

These pointers do not point directly to the data that we want to see. Another function call is needed to extract the attributes. The function snd_device_name_get_hint takes a pointer value and an attribute name. If the call is successful, it returns a pointer to a string array containing the data that we asked for. It is up to us to call free() on this returned string. I’ve got a bias against managing memory when not necessary. For the sake of stability and ensuring I don’t forget to deallocate the memory, I copy the value to a std::wstring and immediately free the memory that the char sequence was in.

Plain text
while (*n != NULL) {
std::wstring name_str;
std::wstring desc_str;
std::wstring ioid_str;
char* str;
str = snd_device_name_get_hint(*n, "NAME");
name_str = c(std::string(str ? str : "N/A"));
if(str) free(str);str=NULL;
str = snd_device_name_get_hint(*n, "DESC");
desc_str = c(std::string(str ? str : "N/A"));
if(str) free(str);str=NULL;
str = snd_device_name_get_hint(*n, "IOID");
ioid_str = c(std::string(str ? str : "N/A"));
if(str) free(str);str=NULL;
std::wcout << ANSI_COLOR_CYAN << "Index:" << i << ANSI_COLOR_RESET << std::endl;
std::wcout << ANSI_COLOR_BLUE << "Name: " << ANSI_COLOR_YELLOW << name_str << ANSI_COLOR_RESET << std::endl;
std::wcout << ANSI_COLOR_BLUE << "Description: " << ANSI_COLOR_YELLOW << desc_str << ANSI_COLOR_RESET << std::endl;
std::wcout << ANSI_COLOR_BLUE << "IOID: " << ANSI_COLOR_YELLOW << ioid_str << ANSI_COLOR_RESET << std::endl << std::endl;
++i;
++n;
}

Now that the information on all the devices has been printed, We must deallocate that array of pointers.

C++
snd_device_name_free_hint(deviceNames);

With that, we can now query the sound devices available from the device.


Posts may contain products with affiliate links. When you make purchases using these links, we receive a small commission at no extra cost to you. Thank you for your support.

Mastodon: @j2inet@masto.ai
Instagram: @j2inet
Facebook: @j2inet
YouTube: @j2inet
Telegram: j2inet
Bluesky: @j2i.net

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.