diff options
author | Viliam Mateicka <viliam.mateicka@gmail.com> | 2008-12-31 16:46:41 +0100 |
---|---|---|
committer | Viliam Mateicka <viliam.mateicka@gmail.com> | 2008-12-31 16:46:41 +0100 |
commit | 9a70c4d06d51cca24dfb93847d1f82187d454e31 (patch) | |
tree | 444672f0ab76eca96692560201287d6f7a1a73b5 /src/mixer | |
parent | dd9af72a744d9c93966f5088931d7edbf28e1d1d (diff) | |
download | mpd-9a70c4d06d51cca24dfb93847d1f82187d454e31.tar.gz mpd-9a70c4d06d51cca24dfb93847d1f82187d454e31.tar.xz mpd-9a70c4d06d51cca24dfb93847d1f82187d454e31.zip |
Moving mixers to audio outputs
Diffstat (limited to 'src/mixer')
-rw-r--r-- | src/mixer/alsa_mixer.c | 206 | ||||
-rw-r--r-- | src/mixer/oss_mixer.c | 197 |
2 files changed, 403 insertions, 0 deletions
diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer.c new file mode 100644 index 000000000..506daf794 --- /dev/null +++ b/src/mixer/alsa_mixer.c @@ -0,0 +1,206 @@ + +#include "../output_api.h" +#include "../mixer.h" + +#include <glib.h> +#include <alsa/asoundlib.h> + +#define VOLUME_MIXER_ALSA_DEFAULT "default" +#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" + +struct alsa_mixer { + char *device; + char *control; + snd_mixer_t *handle; + snd_mixer_elem_t *elem; + long volume_min; + long volume_max; + int volume_set; +}; + +struct alsa_mixer * +alsa_mixer_init(void) +{ + struct alsa_mixer *am = g_malloc(sizeof(struct alsa_mixer)); + am->device = NULL; + am->control = NULL; + am->handle = NULL; + am->elem = NULL; + am->volume_min = 0; + am->volume_max = 0; + am->volume_set = -1; + return am; +} + +void +alsa_mixer_finish(struct alsa_mixer *am) +{ + g_free(am); +} + +void +alsa_mixer_configure(struct alsa_mixer *am, ConfigParam *param) +{ + BlockParam *bp; + + if ((bp = getBlockParam(param, "mix_device"))) + am->device = bp->value; + if ((bp = getBlockParam(param, "mix_control"))) + am->control = bp->value; +} + +void +alsa_mixer_close(struct alsa_mixer *am) +{ + if (am->handle) snd_mixer_close(am->handle); + am->handle = NULL; +} + +bool +alsa_mixer_open(struct alsa_mixer *am) +{ + int err; + snd_mixer_elem_t *elem; + const char *control_name = VOLUME_MIXER_ALSA_CONTROL_DEFAULT; + const char *device = VOLUME_MIXER_ALSA_DEFAULT; + + if (am->device) { + device = am->device; + } + err = snd_mixer_open(&am->handle, 0); + snd_config_update_free_global(); + if (err < 0) { + g_warning("problems opening alsa mixer: %s\n", snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_attach(am->handle, device)) < 0) { + g_warning("problems attaching alsa mixer: %s\n", + snd_strerror(err)); + alsa_mixer_close(am); + return false; + } + + if ((err = snd_mixer_selem_register(am->handle, NULL, + NULL)) < 0) { + g_warning("problems snd_mixer_selem_register'ing: %s\n", + snd_strerror(err)); + alsa_mixer_close(am); + return false; + } + + if ((err = snd_mixer_load(am->handle)) < 0) { + g_warning("problems snd_mixer_selem_register'ing: %s\n", + snd_strerror(err)); + alsa_mixer_close(am); + return false; + } + + elem = snd_mixer_first_elem(am->handle); + + if (am->control) { + control_name = am->control; + } + + while (elem) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) { + if (strcasecmp(control_name, + snd_mixer_selem_get_name(elem)) == 0) { + break; + } + } + elem = snd_mixer_elem_next(elem); + } + + if (elem) { + am->elem = elem; + snd_mixer_selem_get_playback_volume_range(am->elem, + &am->volume_min, + &am->volume_max); + return true; + } + + g_warning("can't find alsa mixer control \"%s\"\n", control_name); + + alsa_mixer_close(am); + return false; +} + +bool +alsa_mixer_control(struct alsa_mixer *am, int cmd, void *arg) +{ + switch (cmd) { + case AC_MIXER_CONFIGURE: + alsa_mixer_configure(am, (ConfigParam *)arg); + if (am->handle) + alsa_mixer_close(am); + return true; + case AC_MIXER_GETVOL: + { + int err; + int ret, *volume = arg; + long level; + + if (!am->handle && !alsa_mixer_open(am)) { + return false; + } + if ((err = snd_mixer_handle_events(am->handle)) < 0) { + g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n", + snd_strerror(err), "handle_events"); + alsa_mixer_close(am); + return false; + } + if ((err = snd_mixer_selem_get_playback_volume(am->elem, + SND_MIXER_SCHN_FRONT_LEFT, &level)) < 0) { + g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n", + snd_strerror(err), "selem_get_playback_volume"); + alsa_mixer_close(am); + return false; + } + ret = ((am->volume_set / 100.0) * (am->volume_max - am->volume_min) + + am->volume_min) + 0.5; + if (am->volume_set > 0 && ret == level) { + ret = am->volume_set; + } else { + ret = (int)(100 * (((float)(level - am->volume_min)) / + (am->volume_max - am->volume_min)) + 0.5); + } + *volume = ret; + return true; + } + case AC_MIXER_SETVOL: + { + float vol; + long level; + int *volume = arg; + int err; + + if (!am->handle && !alsa_mixer_open(am)) { + return false; + } + vol = *volume; + + am->volume_set = vol + 0.5; + am->volume_set = am->volume_set > 100 ? 100 : + (am->volume_set < 0 ? 0 : am->volume_set); + + level = (long)(((vol / 100.0) * (am->volume_max - am->volume_min) + + am->volume_min) + 0.5); + level = level > am->volume_max ? am->volume_max : level; + level = level < am->volume_min ? am->volume_min : level; + + if ((err = snd_mixer_selem_set_playback_volume_all(am->elem, + level)) < 0) { + g_warning("problems setting alsa volume: %s\n", + snd_strerror(err)); + alsa_mixer_close(am); + return false; + } + return true; + } + default: + g_warning("Unsuported alsa control\n"); + break; + } + return false; +} diff --git a/src/mixer/oss_mixer.c b/src/mixer/oss_mixer.c new file mode 100644 index 000000000..ca4027f75 --- /dev/null +++ b/src/mixer/oss_mixer.c @@ -0,0 +1,197 @@ + +#include <glib.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include "../output_api.h" +#include "../mixer.h" + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" + +struct oss_mixer { + const char *device; + const char *control; + int device_fd; + int volume_control; +}; + +struct oss_mixer *oss_mixer_init(void); +void oss_mixer_finish(struct oss_mixer *am); +void oss_mixer_configure(struct oss_mixer *am, ConfigParam *param); +bool oss_mixer_open(struct oss_mixer *am); +bool oss_mixer_control(struct oss_mixer *am, int cmd, void *arg); +void oss_mixer_close(struct oss_mixer *am); + +struct oss_mixer * +oss_mixer_init(void) +{ + struct oss_mixer *om = g_malloc(sizeof(struct oss_mixer)); + om->device = NULL; + om->control = NULL; + om->device_fd = -1; + om->volume_control = SOUND_MIXER_PCM; + return om; +} + +void +oss_mixer_finish(struct oss_mixer *om) +{ + g_free(om); +} + +void +oss_mixer_configure(struct oss_mixer *om, ConfigParam *param) +{ + BlockParam *bp; + bp = getBlockParam(param, "mix_device"); + if (bp) { + om->device = bp->value; + } + bp = getBlockParam(param, "mix_control"); + if (bp) { + om->control = bp->value; + } +} + +void +oss_mixer_close(struct oss_mixer *om) +{ + if (om->device_fd != -1) + while (close(om->device_fd) && errno == EINTR) ; + om->device_fd = -1; +} + +static int +oss_find_mixer(const char *name) +{ + const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + size_t name_length = strlen(name); + + for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (strncasecmp(name, labels[i], name_length) == 0 && + (labels[i][name_length] == 0 || + labels[i][name_length] == ' ')) + return i; + } + return -1; +} + +bool +oss_mixer_open(struct oss_mixer *om) +{ + const char *device = VOLUME_MIXER_OSS_DEFAULT; + + if (om->device) { + device = om->device; + } + + if ((om->device_fd = open(device, O_RDONLY)) < 0) { + g_warning("Unable to open oss mixer \"%s\"\n", device); + return false; + } + + if (om->control) { + int i; + int devmask = 0; + + if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + g_warning("errors getting read_devmask for oss mixer\n"); + oss_mixer_close(om); + return false; + } + i = oss_find_mixer(om->control); + + if (i < 0) { + g_warning("mixer control \"%s\" not found\n", + om->control); + oss_mixer_close(om); + return false; + } else if (!((1 << i) & devmask)) { + g_warning("mixer control \"%s\" not usable\n", + om->control); + oss_mixer_close(om); + return false; + } + om->volume_control = i; + } + return true; +} + +bool +oss_mixer_control(struct oss_mixer *om, int cmd, void *arg) +{ + switch (cmd) { + case AC_MIXER_CONFIGURE: + oss_mixer_configure(om, (ConfigParam *)arg); + //if (om->device_fd >= 0) + oss_mixer_close(om); + return true; + break; + case AC_MIXER_GETVOL: + { + int left, right, level; + int *ret; + + if (om->device_fd < 0 && !oss_mixer_open(om)) { + return false; + } + + if (ioctl(om->device_fd, MIXER_READ(om->volume_control), &level) < 0) { + oss_mixer_close(om); + g_warning("unable to read oss volume\n"); + return false; + } + + left = level & 0xff; + right = (level & 0xff00) >> 8; + + if (left != right) { + g_warning("volume for left and right is not the same, \"%i\" and " + "\"%i\"\n", left, right); + } + ret = (int *) arg; + *ret = left; + return true; + } + case AC_MIXER_SETVOL: + { + int new; + int level; + int *value = arg; + + if (om->device_fd < 0 && !oss_mixer_open(om)) { + return false; + } + + new = *value; + if (new < 0) { + new = 0; + } else if (new > 100) { + new = 100; + } + + level = (new << 8) + new; + + if (ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level) < 0) { + g_warning("unable to set oss volume\n"); + oss_mixer_close(om); + return false; + } + return true; + } + default: + g_warning("Unsuported oss control\n"); + break; + } + return false; +} |