alsalibalsa

ALSA lib: Getting mixer volume returns wrong value


I have a upnp streaming app, written in C, in which I use alsa-lib to get and set the ALSA Master output volume.

I use the APIs snd_mixer_selem_get_playback_volume and snd_mixer_selem_set_playback_volume_all to do so.

As long as I only get/set the volume from my app, everything is OK: there is no volume shifts between the "real" Master volume level and the value I get from alsa-lib API.

However, I have an issue if I modify the Master volume from outside my app (from alsamixer for example).

Let's take the following sequence as example:

  1. From my app, I set the Master volume to 50%. I see the Master being updated in alsamixer
  2. From my app, I get the Master volume, it is 50%
  3. From alsamixer, I increase the volume to 100%
  4. From my app, I get the Master volume, it is still 50%

Is it the expected behavior?

I expected the last step to return 100% but it looks like the mixer instance of my app is not updated when the ALSA Master volume is updated from elsewhere. I didn't find anything about the need of reloading/refreshing/updating my mixer instance.

I noticed that if I create a new instance of the mixer element on the fly each time I need to get the volume level, I get the right value. But I would prefer to avoid such a solution that looks more like a workaround than a clean solution.

Basically, here is how I create an instance and get/set volume:

// variables
long volume;
snd_mixer_t *handle;
snd_mixer_selem_id_t *sid;
const char *name = "default";
const char *selem_name = "Master";

// create instance
snd_mixer_open(&handle, 0);
snd_mixer_attach(handle, name);
snd_mixer_selem_register(handle, NULL, NULL);
snd_mixer_load(handle);

snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, selem_name);
snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);

// get set volume.
// get as MONO assuming all channels are at the same level.
snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &volume);
snd_mixer_selem_set_playback_volume_all(elem, volume);

The code runs under openstlinux Yocto image on Cortex-A7

What do I miss or do wrong?


Solution

  • After browsing again and again the internet, I found what I needed and it is quite simple in the end.

    I needed to call snd_mixer_handle_events function before calling snd_mixer_selem_get_playback_volume in order to handle external events that occured on the mixer and "refresh" my app instance.

    // variables
    long volume;
    snd_mixer_t *handle;
    snd_mixer_selem_id_t *sid;
    const char *name = "default";
    const char *selem_name = "Master";
    
    // create instance
    snd_mixer_open(&handle, 0);
    snd_mixer_attach(handle, name);
    snd_mixer_selem_register(handle, NULL, NULL);
    snd_mixer_load(handle);
    
    snd_mixer_selem_id_alloca(&sid);
    snd_mixer_selem_id_set_index(sid, 0);
    snd_mixer_selem_id_set_name(sid, selem_name);
    snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
    
    // get/set volume
    snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &volume);  // for instance returns 50
    volume = 70;
    snd_mixer_selem_set_playback_volume_all(elem, volume);
    snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &volume);  // returns 70
    // Set the volume to 100 from outside the code, from alsamixer for example
    snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &volume);  // returns 70 instead of 100
    snd_mixer_handle_events(handle);
    snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &volume);  // returns 100