In one of our projects we have a sound generator attached to a Raspberry Pi, and the mission is to control the sound volume in real time, (think turning a knob).

alt text

The obvious way to achieve this is using system() and invoke amixer as a separate process. The problem with this approach is that it’s, well, hacky, and also has some known security implications

The alternative is to use the ALSA API to write a native Linux C library that we can easily integrate into our existing code. Furthermore this approach has the added benefit that it can easily be interoped from managed languages.

The code is simplified to work on devices that only have one hardware audio interface, but if needed it can support multiple interfaces with a few tweaks.

Enough talking, here’s an example on how to set the volume:

snd_mixer_t *handle;            // ALSA mixer sound card handle
snd_mixer_elem_t *elem;         // ALSA mixer
snd_mixer_selem_id_t *mixer_id; // ALSA mixer simple element

// Open an empty mixer
snd_mixer_open(handle, SND_MIXER_ELEM_SIMPLE);
// Open the default interface, see: $ cat /proc/asound/cards
// alternatively, this can be "hw:0" instead of "default"
snd_mixer_attach(*handle, "default");
snd_mixer_selem_register(*handle, NULL, NULL);
// Load the mixer elements
snd_mixer_selem_id_set_index(*mixer_id, 0);
// PCM is the default interface, see: $ amixer
snd_mixer_selem_id_set_name(*mixer_id, "PCM");
*elem = snd_mixer_find_selem(*handle, *mixer_id);
// sets the volume for all the channels to the assigned dV value
snd_mixer_selem_set_playback_dB_all(elem, dB_value, 0);

// detach a previously attached HCTL to an opened mixer freeing all related resources.
snd_mixer_detach(*handle, "default");
// close a mixer and free all related resources.

Source code: