注意监控ALSA/Pulseaudio中的音量变化

10

如何监听默认声卡上主通道音量的变化?我希望通过dbus或回调函数等方式得到通知,告知音量已经发生变化。

我尝试查找ALSA和PulseAudio的API,但它们似乎只允许您设置和获取音量,而不能监听音量的变化。

任何编程语言都可以。

3个回答

10

编辑:在第二个示例中,当音量低于5%或高于100%时,对我而言不会生成事件。据我所知,第一个示例完美运行。

pactl subscribe将在音量更改时打印有关sinks的数据。现在我正在将输出导入到一个小型C程序中,该程序将运行一个脚本。

run.sh:

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

或者对于特定的接收器,例如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");
    }
}
当水槽的音量改变时,pactl 会打印一行内容,这将导致程序运行脚本。
或者
以下是基于amixer monitor 的示例,由CL所提及。当音量发生变化时,while循环将迭代每次,因此将您的回调放在其中。
#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]);
    }
}

8
这是通过ALSA API实现的。
当你有一个控制设备时,调用snd_ctl_subscribe_events()启用事件。 然后使用snd_ctl_read()读取事件;要等待它们,请使用阻塞模式或poll()。 如果事件类型为SND_CTL_EVENT_ELEM,并且其事件位掩码包含SND_CTL_EVENT_MASK_VALUE,则该元素的值已更改。
请参见示例中的amixer monitor实现

如果有人想知道,这已经成为了一个node.js插件在这里 - Michael Eden

4
与@sealj553的答案类似,但不需要C程序:
pactl subscribe | grep --line-buffered "sink" | xargs -n1 ./volume_notify.sh

这个单行代码看起来几乎很好,但它会在每个事件上多次调用“volume_notify.sh”。我建议改用以下代码:pactl subscribe | grep --line-buffered "sink" | while read -r UNUSED_LINE; do ./volume_notify.sh; done - Ivan Mikhnovich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接