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.
PulseAudio is a general purpose sound server intended to run as a middleware between your applications and your hardware devices, either using ALSA or OSS. It also offers easy network streaming across local devices using Avahi if enabled.
In Pulseaudio Volume Control (pavucontrol), under the "Playback" tab, change the output of an application to <name>, and in the recording tab change the input of an application to "Monitor of <name>". Audio will now be outputted from one application into the other.
It states that alsa (aplay) is configured to send its output to pulseaudio (which is your sound server) by default. When your sound server is not running you will not be able to hear a sound as you have observed.
Similar to @sealj553's answer, but does not need a C-program:
pactl subscribe | grep --line-buffered "sink" | xargs -n1 ./volume_notify.sh
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]);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With