8
votes

How do you listen to changes in volume on the Master channel on the default sound card? I'd like to be notified through dbus or a callback or something that the volume has changed.

I have tried looking and the ALSA and PulseAudio APIs and they only seem to allow you to set and get the volume, but not listen for changes in the volume.

Any programming language is fine.

3

3 Answers

8
votes

Edit: In the second example, an event isn't generated for me when volume is below 5% or above 100%. The first example works perfectly as far as I know.

pactl subscribe will print out data about the sinks when the volume changes. What I'm doing now is piping the output to a small C program that will run a script.

run.sh:

pactl subscribe | grep --line-buffered "sink" | ./prog

or for a specific sink, e.g. 3:

pactl subscribe | grep --line-buffered "sink #3" | ./prog

prog.c:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[]){
    while(1){
        while(getchar() != '\n');
        system("./volume_notify.sh");
    }
}

When the volume of the sink is changed, pactl will print a line, which will cause the program to run the script.

-or-

Here's an example based on the amixer monitor, as referenced by CL. The while loop will iterate each time the volume changes, so put your callback in there.

#include <stdio.h>
#include <alsa/asoundlib.h>

#define MAX_CARDS 256

int monitor_native(char const *name);
int open_ctl(const char *name, snd_ctl_t **ctlp);
void close_all(snd_ctl_t* ctls[], int ncards);

int main(int argc, char* argv[]){

    const char *ctl_name = "hw:0";

    while(monitor_native(ctl_name) == 1){
        //volume has been changed, do something
        system("~/.volume_notify.sh");
    }

    return 0;
}

int monitor_native(char const *name) {
    snd_ctl_t *ctls[MAX_CARDS];
    int ncards = 0;
    int i, err = 0;

    if (!name) {
        int card = -1;
        while (snd_card_next(&card) >= 0 && card >= 0) {
            char cardname[16];
            if (ncards >= MAX_CARDS) {
                fprintf(stderr, "alsactl: too many cards\n");
                close_all(ctls, ncards);
                return -E2BIG;
            }
            sprintf(cardname, "hw:%d", card);
            err = open_ctl(cardname, &ctls[ncards]);
            if (err < 0) {
                close_all(ctls, ncards);
                return err;
            }
            ncards++;
        }
    } else {
        err = open_ctl(name, &ctls[0]);
        if (err < 0) {
            close_all(ctls, ncards);
            return err;
        }
        ncards++;
    }

    for (;ncards > 0;) {
        pollfd* fds = new pollfd[ncards];

        for (i = 0; i < ncards; i++) {
            snd_ctl_poll_descriptors(ctls[i], &fds[i], 1);
        }

        err = poll(fds, ncards, -1);
        if (err <= 0) {
            err = 0;
            break;
        }

        for (i = 0; i < ncards; i++) {
            unsigned short revents;
            snd_ctl_poll_descriptors_revents(ctls[i], &fds[i], 1, &revents);
            if (revents & POLLIN) {
                snd_ctl_event_t *event;
                snd_ctl_event_alloca(&event);

                if (snd_ctl_read(ctls[i], event) < 0) {
                    continue;
                }
                if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM) {
                    continue;
                }

                unsigned int mask = snd_ctl_event_elem_get_mask(event);
                if (mask & SND_CTL_EVENT_MASK_VALUE) {
                    close_all(ctls, ncards);
                    return 1;
                }
            }
        }
    }

    close_all(ctls, ncards);
    return 0;
}

int open_ctl(const char *name, snd_ctl_t **ctlp) {
    snd_ctl_t *ctl;
    int err;

    err = snd_ctl_open(&ctl, name, SND_CTL_READONLY);
    if (err < 0) {
        fprintf(stderr, "Cannot open ctl %s\n", name);
        return err;
    }
    err = snd_ctl_subscribe_events(ctl, 1);
    if (err < 0) {
        fprintf(stderr, "Cannot open subscribe events to ctl %s\n", name);
        snd_ctl_close(ctl);
        return err;
    }
    *ctlp = ctl;
    return 0;
}

void close_all(snd_ctl_t* ctls[], int ncards) {
    for (ncards -= 1; ncards >= 0; --ncards) {
        snd_ctl_close(ctls[ncards]);
    }
}
7
votes

This is possible with the ALSA API.

When you have a control device, call snd_ctl_subscribe_events() to enable events. Then use snd_ctl_read() to read events; to wait for them, use blocking mode or poll(). If the event is of type SND_CTL_EVENT_ELEM, and if its event bit mask contains SND_CTL_EVENT_MASK_VALUE, that element's value has changed.

See the implementation of amixer monitor for an example.

3
votes

Similar to @sealj553's answer, but does not need a C-program:

pactl subscribe | grep --line-buffered "sink" | xargs -n1 ./volume_notify.sh