| /* mixer.c |
| ** |
| ** Copyright 2011, The Android Open Source Project |
| ** |
| ** Redistribution and use in source and binary forms, with or without |
| ** modification, are permitted provided that the following conditions are met: |
| ** * Redistributions of source code must retain the above copyright |
| ** notice, this list of conditions and the following disclaimer. |
| ** * Redistributions in binary form must reproduce the above copyright |
| ** notice, this list of conditions and the following disclaimer in the |
| ** documentation and/or other materials provided with the distribution. |
| ** * Neither the name of The Android Open Source Project nor the names of |
| ** its contributors may be used to endorse or promote products derived |
| ** from this software without specific prior written permission. |
| ** |
| ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND |
| ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE |
| ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| ** DAMAGE. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <ctype.h> |
| |
| #include <linux/ioctl.h> |
| #define __force |
| #define __bitwise |
| #define __user |
| #include <sound/asound.h> |
| |
| #include <tinyalsa/asoundlib.h> |
| |
| static const char *elem_iface_name(snd_ctl_elem_iface_t n) |
| { |
| switch (n) { |
| case SNDRV_CTL_ELEM_IFACE_CARD: return "CARD"; |
| case SNDRV_CTL_ELEM_IFACE_HWDEP: return "HWDEP"; |
| case SNDRV_CTL_ELEM_IFACE_MIXER: return "MIXER"; |
| case SNDRV_CTL_ELEM_IFACE_PCM: return "PCM"; |
| case SNDRV_CTL_ELEM_IFACE_RAWMIDI: return "MIDI"; |
| case SNDRV_CTL_ELEM_IFACE_TIMER: return "TIMER"; |
| case SNDRV_CTL_ELEM_IFACE_SEQUENCER: return "SEQ"; |
| default: return "???"; |
| } |
| } |
| |
| static const char *elem_type_name(snd_ctl_elem_type_t n) |
| { |
| switch (n) { |
| case SNDRV_CTL_ELEM_TYPE_NONE: return "NONE"; |
| case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return "BOOL"; |
| case SNDRV_CTL_ELEM_TYPE_INTEGER: return "INT32"; |
| case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return "ENUM"; |
| case SNDRV_CTL_ELEM_TYPE_BYTES: return "BYTES"; |
| case SNDRV_CTL_ELEM_TYPE_IEC958: return "IEC958"; |
| case SNDRV_CTL_ELEM_TYPE_INTEGER64: return "INT64"; |
| default: return "???"; |
| } |
| } |
| |
| |
| struct mixer_ctl { |
| struct mixer *mixer; |
| struct snd_ctl_elem_info *info; |
| char **ename; |
| }; |
| |
| struct mixer { |
| int fd; |
| struct snd_ctl_elem_info *info; |
| struct mixer_ctl *ctl; |
| unsigned int count; |
| }; |
| |
| void mixer_close(struct mixer *mixer) |
| { |
| unsigned int n,m; |
| |
| if (mixer->fd >= 0) |
| close(mixer->fd); |
| |
| if (mixer->ctl) { |
| for (n = 0; n < mixer->count; n++) { |
| if (mixer->ctl[n].ename) { |
| unsigned int max = mixer->ctl[n].info->value.enumerated.items; |
| for (m = 0; m < max; m++) |
| free(mixer->ctl[n].ename[m]); |
| free(mixer->ctl[n].ename); |
| } |
| } |
| free(mixer->ctl); |
| } |
| |
| if (mixer->info) |
| free(mixer->info); |
| |
| free(mixer); |
| |
| /* TODO: verify frees */ |
| } |
| |
| struct mixer *mixer_open(unsigned int device) |
| { |
| struct snd_ctl_elem_list elist; |
| struct snd_ctl_elem_info tmp; |
| struct snd_ctl_elem_id *eid = NULL; |
| struct mixer *mixer = NULL; |
| unsigned int n, m; |
| int fd; |
| |
| fd = open("/dev/snd/controlC0", O_RDWR); |
| if (fd < 0) |
| return 0; |
| |
| memset(&elist, 0, sizeof(elist)); |
| if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) |
| goto fail; |
| |
| mixer = calloc(1, sizeof(*mixer)); |
| if (!mixer) |
| goto fail; |
| |
| mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl)); |
| mixer->info = calloc(elist.count, sizeof(struct snd_ctl_elem_info)); |
| if (!mixer->ctl || !mixer->info) |
| goto fail; |
| |
| eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id)); |
| if (!eid) |
| goto fail; |
| |
| mixer->count = elist.count; |
| mixer->fd = fd; |
| elist.space = mixer->count; |
| elist.pids = eid; |
| if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) |
| goto fail; |
| |
| for (n = 0; n < mixer->count; n++) { |
| struct snd_ctl_elem_info *ei = mixer->info + n; |
| ei->id.numid = eid[n].numid; |
| if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) |
| goto fail; |
| mixer->ctl[n].info = ei; |
| mixer->ctl[n].mixer = mixer; |
| if (ei->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) { |
| char **enames = calloc(ei->value.enumerated.items, sizeof(char*)); |
| if (!enames) |
| goto fail; |
| mixer->ctl[n].ename = enames; |
| for (m = 0; m < ei->value.enumerated.items; m++) { |
| memset(&tmp, 0, sizeof(tmp)); |
| tmp.id.numid = ei->id.numid; |
| tmp.value.enumerated.item = m; |
| if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0) |
| goto fail; |
| enames[m] = strdup(tmp.value.enumerated.name); |
| if (!enames[m]) |
| goto fail; |
| } |
| } |
| } |
| |
| free(eid); |
| return mixer; |
| |
| fail: |
| /* TODO: verify frees in failure case */ |
| if (eid) |
| free(eid); |
| if (mixer) |
| mixer_close(mixer); |
| else if (fd >= 0) |
| close(fd); |
| return 0; |
| } |
| |
| int mixer_get_num_ctls(struct mixer *mixer) |
| { |
| return mixer->count; |
| } |
| |
| struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id) |
| { |
| if (id < mixer->count) |
| return mixer->ctl + id; |
| |
| return NULL; |
| } |
| |
| struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name) |
| { |
| unsigned int n; |
| for (n = 0; n < mixer->count; n++) |
| if (!strcmp(name, (char*) mixer->info[n].id.name)) |
| return mixer->ctl + n; |
| |
| return NULL; |
| } |
| |
| static int percent_to_int(struct snd_ctl_elem_info *ei, int percent) |
| { |
| int range; |
| |
| if (percent > 100) |
| percent = 100; |
| else if (percent < 0) |
| percent = 0; |
| |
| range = (ei->value.integer.max - ei->value.integer.min); |
| |
| return ei->value.integer.min + (range * percent) / 100; |
| } |
| |
| static int int_to_percent(struct snd_ctl_elem_info *ei, int value) |
| { |
| int range = (ei->value.integer.max - ei->value.integer.min); |
| |
| if (range == 0) |
| return 0; |
| |
| return ((value - ei->value.integer.min) / range) * 100; |
| } |
| |
| int mixer_ctl_get_percent(struct mixer_ctl *ctl) |
| { |
| if (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| return int_to_percent(ctl->info, mixer_ctl_get_int(ctl)); |
| } |
| |
| int mixer_ctl_set_percent(struct mixer_ctl *ctl, int percent) |
| { |
| if (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| return mixer_ctl_set_int(ctl, percent_to_int(ctl->info, percent)); |
| } |
| |
| int mixer_ctl_get_int(struct mixer_ctl *ctl) |
| { |
| struct snd_ctl_elem_value ev; |
| |
| memset(&ev, 0, sizeof(ev)); |
| ev.id.numid = ctl->info->id.numid; |
| if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev)) |
| return -1; |
| |
| switch (ctl->info->type) { |
| case SNDRV_CTL_ELEM_TYPE_BOOLEAN: |
| return !!ev.value.integer.value[0]; /* TODO: handle multiple return values */ |
| break; |
| |
| case SNDRV_CTL_ELEM_TYPE_INTEGER: |
| return ev.value.integer.value[0]; /* TODO: handle multiple return values */ |
| break; |
| |
| default: |
| errno = EINVAL; |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int mixer_ctl_set_int(struct mixer_ctl *ctl, int value) |
| { |
| struct snd_ctl_elem_value ev; |
| unsigned int n; |
| |
| memset(&ev, 0, sizeof(ev)); |
| ev.id.numid = ctl->info->id.numid; |
| |
| switch (ctl->info->type) { |
| case SNDRV_CTL_ELEM_TYPE_BOOLEAN: |
| for (n = 0; n < ctl->info->count; n++) |
| ev.value.integer.value[n] = !!value; |
| break; |
| |
| case SNDRV_CTL_ELEM_TYPE_INTEGER: |
| for (n = 0; n < ctl->info->count; n++) |
| ev.value.integer.value[n] = value; |
| break; |
| |
| default: |
| errno = EINVAL; |
| return -1; |
| } |
| |
| return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); |
| } |
| |
| int mixer_ctl_set_enum(struct mixer_ctl *ctl, unsigned int id) |
| { |
| struct snd_ctl_elem_value ev; |
| |
| if ((ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) || |
| (id >= ctl->info->value.enumerated.items)) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| memset(&ev, 0, sizeof(ev)); |
| ev.value.enumerated.item[0] = id; |
| ev.id.numid = ctl->info->id.numid; |
| if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev) < 0) |
| return -1; |
| return 0; |
| } |
| |
| int mixer_ctl_set_enum_by_name(struct mixer_ctl *ctl, const char *string) |
| { |
| unsigned int n, max; |
| struct snd_ctl_elem_value ev; |
| |
| if (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| max = ctl->info->value.enumerated.items; |
| for (n = 0; n < max; n++) { |
| if (!strcmp(string, ctl->ename[n])) { |
| memset(&ev, 0, sizeof(ev)); |
| ev.value.enumerated.item[0] = n; |
| ev.id.numid = ctl->info->id.numid; |
| if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev) < 0) |
| return -1; |
| return 0; |
| } |
| } |
| |
| errno = EINVAL; |
| return -1; |
| } |