Switching between ALSA cards/devices


Right now Peppy audio player assumes that the whole ALSA configuration should be done manually.
To simplify that task I'm trying to investigate what is the best way to show the list of available ALSA cards/devices in Peppy Player UI using Python.
Also ideally it shoud be possible to switch between cards/devices without rebooting a Raspberry Pi.

I'm not sure how to add a dtoverlay entry (/boot/config.txt) for a specific audio board.

Here are the options which I've found so far to display the list of cards/devices/mixers and set the default card:

1. Parse the output of the 'aplay -l' command. It provides the info about card/device/subdevice:
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: b1 [bcm2835 HDMI 1], device 0: bcm2835 HDMI 1 [bcm2835 HDMI 1]
  Subdevices: 4/4
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
card 1: Headphones [bcm2835 Headphones], device 0: bcm2835 Headphones [bcm2835 Headphones]
  Subdevices: 4/4
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
card 2: PCM2702 [Burr-Brown Japan PCM2702], device 0: USB Audio [USB Audio]
  Subdevices: 0/1
  Subdevice #0: subdevice #0

Very similar approach is to read/parse ALSA files. Though there is no just one file which could provide full info about card/device/subdevice (like aplay). For example the following file shows only card info. To get the info about devices another file should be parsed. There is no file with subdevices (?)
$ cat /proc/asound/cards
0 [b1]: bcm2835_hdmi - bcm2835 HDMI 1 bcm2835 HDMI 1
1 [Headphones]: bcm2835_headpho - bcm2835 Headphones bcm2835 Headphones
2 [PCM2702]: USB-Audio - Burr-Brown Japan PCM2702 Burr-Brown Japan Burr-Brown Japan PCM2702 at usb-0000:01:00.0-1.1, full speed

To assign new card/device to the default ALSA device the corresponding values should be defined in the file .asoundrc

No need to use any third-party library
Works only on Linux (not Windows, not sure about Mac OS).
Need to modify the config file (.asoundrc) to switch to another card .
Fragile text parser, not sure if the aplay output has the same format on all Linux flavors.
Should the system be rebooted after changing the .asoundrc file?

2. Use pyalsaaudio library. Here is the example of using the library:
>>> import alsaaudio
>>> alsaaudio.cards()
['b1', 'Headphones', 'PCM2702']
>>> for i in alsaaudio.card_indexes():
...     (name, longname) = alsaaudio.card_name(i)
...     print("  %d: %s (%s)" % (i, name, longname))
  0: bcm2835 HDMI 1 (bcm2835 HDMI 1)
  1: bcm2835 Headphones (bcm2835 Headphones)
  2: Burr-Brown Japan PCM2702 (Burr-Brown Japan Burr-Brown Japan PCM2702 at usb-0000:01:00.0-1.1, full speed)
>>> alsaaudio.mixers(cardindex=2)
>>> mixer = alsaaudio.Mixer('Master')
>>> mixer.volumecap()
>>> mixer.switchcap()
['Playback Mute', 'Joined Playback Mute']

Very small ALSA binding Python library.
Provides card name and mixers info.
A mixer object can be used to mute/unmute and change volume.
Works only on Linux.
Doesn't provide card ID (e.g. PCM2702). The card ID could be used in .asoundrc file.
No info about devices/subdevices.
I didn't find info how to change a 'default' ALSA device to different card/device using the library. Though that would not be helpful as the player is using .asoundrc file.

3. Use sounddevice library which is a PortAudio (don't confuse with PulseAudio) binding Python library. Here is the example of using the library:
>>> import sounddevice as sd
>>> sd.query_devices()
< 0 bcm2835 HDMI 1: - (hw:0,0), ALSA (0 in, 8 out)
  1 bcm2835 Headphones: - (hw:1,0), ALSA (0 in, 8 out)
  2 sysdefault, ALSA (0 in, 128 out)
  3 dmix, ALSA (0 in, 2 out)
To switch to a different card/device:
>>> sd.default.device = 'digital output'

Multi-platform, works on Linux, Windows and Mac OS.
Can be used to switch to a different ALSA default device
Depends on PortAudio
Outputs a very confusing list of cards/devices. For example in the above example it's not clear that device #2 (sysdefault) is actually the USB device.
It's not clear if mixer can be handled by the library.
So, right now it looks like the option #1 is a preferable solution as it doesn't need any third-party library/dependency.
The list of the cards/devices will be obtained by parsing the output of the 'aplay -l' command and will be displayed in UI in a form of combo-box. For example, it will display the following values:

b1 [bcm2835 HDMI 1]
Headphones [bcm2835 Headphones]
PCM2702 [Burr-Brown Japan PCM2702]

The default ALSA device should be highlighted in the list. Right now in the Peppy player the default ALSA device (card/device) should be specified in the file .asoundrc:
That file should be parsed in order to get the default card/device. Another approach could be to save default card/device in a separate file to avoid parsing of the .asoundrc file.

When an user selects a different device from the combo-box the corresponding device ID will be defined in the .asoundrc file. For example when user selected the option:
PCM2702 [Burr-Brown Japan PCM2702]
The value in the .asoundrc file will be changed
type hw card 0
type hw card PCM2702
and from:
slave.pcm "plughw:0,0"
slave.pcm "plughw:pCM2702,0"

After changing the .asoundrc file is there any way to reload ALSA without rebooting the system?

Another approach to change the default ALSA device is through environment variables as explained here:
The same question in this case - is there any way to reload ALSA without rebooting the system?
IIUC alsa config is cached on process (i.e. alsa-lib load) level.

This config can be reloaded by snd_config_update https://www.alsa-project.org/alsa-d...onfig.html#ga41a3d2202cfb9016e33aa85ea70a4c9c . But please note the description to https://www.alsa-project.org/alsa-d...onfig.html#ga6cf7955d3a072d354dab4d7b536c7831 :

This variable is initialized or updated by snd_config_update. Functions like snd_pcm_open (that use a device name from the global configuration) automatically call snd_config_update.

That means that every opening any device makes alsa-lib re-parse the config files. But I have not done any tests as that would require some coding (repeated calling of aplay creates different processes).
It looks like any ALSA manipulations cannot do anything with currently running player (e.g. VLC) unless you stop that player. So, the easiest way is probably to ask user to reload the player rather than re-creating the player in software (stop, initialize new instance, start) :)