From 476e4f464e0a94154aebb3f9f41d7e873304a8ed Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 15 Apr 2012 16:34:03 +0200 Subject: [PATCH] osd-alsa: A new client for adjusting ALSA mixer controls --- Makefile | 14 +-- README | 2 + osd-alsa.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 osd-alsa.c diff --git a/Makefile b/Makefile index 40b135a..eeee439 100644 --- a/Makefile +++ b/Makefile @@ -3,20 +3,24 @@ ARCHIVE=osdd-$(VERSION).tar.gz CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 -all: osdd osdc osd-batt +all: osdd osdc osd-batt osd-alsa osdd: osdd.o util.o osdc: osdc.o util.o client.o osd-batt: osd-batt.o util.o client.o +osd-alsa: osd-alsa.o util.o client.o osdd.o: CFLAGS+=$(shell xosd-config --cflags) -osdd: LDFLAGS+=$(shell xosd-config --libs) +osdd: LDLIBS+=$(shell xosd-config --libs) -osdc: LDFLAGS+=-lX11 -osd-batt: LDFLAGS+=-lX11 +osdc: LDLIBS+=-lX11 +osd-batt: LDLIBS+=-lX11 + +osd-alsa.o: CFLAGS+=$(shell pkg-config --cflags alsa) +osd-alsa: LDLIBS+=$(shell pkg-config --libs alsa) -lX11 clean: - rm -f *~ *.o TAGS core osdd osdc osd-batt + rm -f *~ *.o TAGS core osdd osdc osd-batt osd-alsa release: git tag v$(VERSION) diff --git a/README b/README index c9b974d..4e19316 100644 --- a/README +++ b/README @@ -69,3 +69,5 @@ osd-batt A slightly more complex client written in C, showing the current status of laptop batteries. It can be asked to show status immediately or to run in background and croak whenever the battery is low. + +osd-alsa Adjust volume of an ALSA mixer channel. Also written in C. diff --git a/osd-alsa.c b/osd-alsa.c new file mode 100644 index 0000000..3d7bc27 --- /dev/null +++ b/osd-alsa.c @@ -0,0 +1,243 @@ +/* + * A Simple ALSA Volume Control via OSD + * + * (c) 2012 Martin Mares + */ + +#undef DEBUG + +#include +#include +#include +#include +#include + +#include "osd.h" + +static char *alsa_device = "default"; +static char *mixer_control = "Master"; +static int adjust_by; +static int want_mute = -1; + +static snd_mixer_t *mixer; +static snd_mixer_elem_t *elem; + +static void init_mixer(void) +{ + int err; + + if (err = snd_mixer_open(&mixer, 0)) + die("snd_mixer_open failed: error %d", err); + + if (err = snd_mixer_attach(mixer, alsa_device)) + die("snd_mixer_attach failed: error %d", err); + + if (err = snd_mixer_selem_register(mixer, NULL, NULL)) + die("snd_mixer_selem_register failed: error %d", err); + + if (err = snd_mixer_load(mixer)) + die("snd_mixer_load: error %d", err); + + for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) + { + const char *name = snd_mixer_selem_get_name(elem); + int index = snd_mixer_selem_get_index(elem); + if (!strcmp(name, mixer_control) || index) + { + if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE) + die("Unable to handle non-simple mixer controls"); + if (!snd_mixer_selem_is_active(elem)) + die("Selected mixer control is not active"); + DBG("Found mixer control %s[%d]\n", name, index); + return; + } + } + + die("Unable to find mixer control %s", mixer_control); +} + +static int get_mute(void) +{ + int mute_on = 0, mute_off = 0; + + if (snd_mixer_selem_has_playback_switch(elem)) + { + for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++) + { + int val; + if (snd_mixer_selem_get_playback_switch(elem, ch, &val)) + { + if (val) + mute_off++; + else + mute_on++; + } + } + } + DBG("Mute: on=%d off=%d\n", mute_on, mute_off); + return !!mute_on; +} + +static long get_volume(long *pmin, long *pmax) +{ + long min, max, curr=0; + if (!snd_mixer_selem_has_playback_volume(elem) || + snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) + { + *pmin = *pmax = 0; + return 0; + } + + for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++) + { + long val; + if (snd_mixer_selem_get_playback_volume(elem, ch, &val)) + { + if (val > curr) + curr = val; + } + } + + DBG("Volume: min=%ld max=%ld curr=%ld\n", min, max, curr); + *pmin = min; + *pmax = max; + return curr; +} + +static int vol_to_perc(long curr, long min, long max) +{ + return (100LL * (curr-min) + (max-min)/2) / (max-min); +} + +static long perc_to_vol(int perc, long min, long max) +{ + return ((long long) perc * (max-min) + 50) / 100; +} + +static void show_mixer(void) +{ + long min, max; + long curr = get_volume(&min, &max); + int muted = get_mute(); + + struct osd_msg *msg = osd_new_msg(); + osd_add_line(msg, "min-duration", "0"); + char buf[256]; + snprintf(buf, sizeof(buf), "%s volume", mixer_control); + osd_add_line(msg, NULL, buf); + osd_add_line(msg, NULL, ""); + if (muted) + osd_add_line(msg, NULL, "[mute]"); + else if (min < max) + { + snprintf(buf, sizeof(buf), "%d", vol_to_perc(curr, min, max)); + osd_add_line(msg, "slider", buf); + } + osd_send(msg); +} + + +static void set_mute(void) +{ + if (want_mute < 0) + return; + + if (want_mute == 2) + want_mute = !get_mute(); + + if (snd_mixer_selem_has_playback_switch(elem)) + { + for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++) + snd_mixer_selem_set_playback_switch(elem, ch, !want_mute); + } +} + +static void set_volume(void) +{ + if (!adjust_by) + return; + + long min, max; + long curr = get_volume(&min, &max); + int perc = vol_to_perc(curr, min, max); + + DBG("Volume: have %d %ld\n", perc, curr); + perc += adjust_by; + if (perc < 0) + perc = 0; + if (perc > 100) + perc = 100; + curr = perc_to_vol(perc, min, max); + curr = min + (((long long) perc * (max-min) + 50) / 100); + DBG("Volume: want %d %ld\n", perc, curr); + + for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++) + snd_mixer_selem_set_playback_volume(elem, ch, curr); +} + +static void NONRET +usage(void) +{ + fprintf(stderr, "\ +Usage: osd-alsa \n\ +\n\ +Options:\n\ +-a, --adjust= Adjust the control by a given amount\n\ +-D, --device= ALSA device (default: `default')\n\ +-m, --mixer= Name of mixer control (default: `Master')\n\ +-0, --mute Mute the control\n\ +-t, --toggle Mute/unmute the control\n\ +-1, --unmute Unmute the control\n\ +"); + exit(1); +} + +static const char short_opts[] = "01a:c:D:t"; + +static const struct option long_opts[] = { + { "adjust", required_argument, NULL, 'a' }, + { "device", required_argument, NULL, 'D' }, + { "mixer", required_argument, NULL, 'm' }, + { "mute", no_argument, NULL, '0' }, + { "toggle", no_argument, NULL, 't' }, + { "unmute", no_argument, NULL, '1' }, + { NULL, 0, NULL, 0 }, +}; + +int main(int argc, char **argv) +{ + int opt; + while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0) + switch (opt) + { + case '0': + want_mute = 1; + break; + case '1': + want_mute = 0; + break; + case 'a': + adjust_by = atoi(optarg); + break; + case 'D': + alsa_device = optarg; + break; + case 'm': + mixer_control = optarg; + break; + case 't': + want_mute = 2; + break; + default: + usage(); + } + if (optind < argc) + usage(); + + init_mixer(); + osd_init(); + set_mute(); + set_volume(); + show_mixer(); + return 0; +} -- 2.39.2