/* 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 <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <ctype.h> #include <poll.h> #include <sys/ioctl.h> #include <linux/ioctl.h> #define __force #define __bitwise #define __user #include <sound/asound.h> #ifndef SNDRV_CTL_ELEM_ID_NAME_MAXLEN #define SNDRV_CTL_ELEM_ID_NAME_MAXLEN 44 #endif #include <tinyalsa/asoundlib.h> struct mixer_ctl { struct mixer *mixer; struct snd_ctl_elem_info *info; char **ename; bool info_retrieved; }; struct mixer { int fd; struct snd_ctl_card_info card_info; struct snd_ctl_elem_info *elem_info; struct mixer_ctl *ctl; unsigned int count; }; void mixer_close(struct mixer *mixer) { unsigned int n,m; if (!mixer) return; 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->elem_info) free(mixer->elem_info); free(mixer); /* TODO: verify frees */ } struct mixer *mixer_open(unsigned int card) { struct snd_ctl_elem_list elist; struct snd_ctl_elem_id *eid = NULL; struct mixer *mixer = NULL; unsigned int n; int fd; char fn[256]; snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card); fd = open(fn, 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->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info)); if (!mixer->ctl || !mixer->elem_info) goto fail; if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0) 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 mixer_ctl *ctl = mixer->ctl + n; ctl->mixer = mixer; ctl->info = mixer->elem_info + n; ctl->info->id.numid = eid[n].numid; strncpy((char *)ctl->info->id.name, (char *)eid[n].name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); ctl->info->id.name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; } 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; } static bool mixer_ctl_get_elem_info(struct mixer_ctl* ctl) { if (!ctl->info_retrieved) { if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info) < 0) return false; ctl->info_retrieved = true; } if (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED || ctl->ename) return true; struct snd_ctl_elem_info tmp; char** enames = calloc(ctl->info->value.enumerated.items, sizeof(char*)); if (!enames) return false; for (unsigned int i = 0; i < ctl->info->value.enumerated.items; i++) { memset(&tmp, 0, sizeof(tmp)); tmp.id.numid = ctl->info->id.numid; tmp.value.enumerated.item = i; if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0) goto fail; enames[i] = strdup(tmp.value.enumerated.name); if (!enames[i]) goto fail; } ctl->ename = enames; return true; fail: free(enames); return false; } const char *mixer_get_name(struct mixer *mixer) { return (const char *)mixer->card_info.name; } unsigned int mixer_get_num_ctls(struct mixer *mixer) { if (!mixer) return 0; return mixer->count; } struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id) { struct mixer_ctl *ctl; if (!mixer || (id >= mixer->count)) return NULL; ctl = mixer->ctl + id; if (!mixer_ctl_get_elem_info(ctl)) return NULL; return ctl; } struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name) { unsigned int n; if (!mixer) return NULL; for (n = 0; n < mixer->count; n++) if (!strcmp(name, (char*) mixer->elem_info[n].id.name)) return mixer_get_ctl(mixer, n); return NULL; } void mixer_ctl_update(struct mixer_ctl *ctl) { ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info); } const char *mixer_ctl_get_name(struct mixer_ctl *ctl) { if (!ctl) return NULL; return (const char *)ctl->info->id.name; } enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl) { if (!ctl) return MIXER_CTL_TYPE_UNKNOWN; switch (ctl->info->type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return MIXER_CTL_TYPE_BOOL; case SNDRV_CTL_ELEM_TYPE_INTEGER: return MIXER_CTL_TYPE_INT; case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return MIXER_CTL_TYPE_ENUM; case SNDRV_CTL_ELEM_TYPE_BYTES: return MIXER_CTL_TYPE_BYTE; case SNDRV_CTL_ELEM_TYPE_IEC958: return MIXER_CTL_TYPE_IEC958; case SNDRV_CTL_ELEM_TYPE_INTEGER64: return MIXER_CTL_TYPE_INT64; default: return MIXER_CTL_TYPE_UNKNOWN; }; } const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl) { if (!ctl) return ""; switch (ctl->info->type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return "BOOL"; case SNDRV_CTL_ELEM_TYPE_INTEGER: return "INT"; case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return "ENUM"; case SNDRV_CTL_ELEM_TYPE_BYTES: return "BYTE"; case SNDRV_CTL_ELEM_TYPE_IEC958: return "IEC958"; case SNDRV_CTL_ELEM_TYPE_INTEGER64: return "INT64"; default: return "Unknown"; }; } unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl) { if (!ctl) return 0; return ctl->info->count; } 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, unsigned int id) { if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) return -EINVAL; return int_to_percent(ctl->info, mixer_ctl_get_value(ctl, id)); } int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent) { if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) return -EINVAL; return mixer_ctl_set_value(ctl, id, percent_to_int(ctl->info, percent)); } int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id) { struct snd_ctl_elem_value ev; int ret; if (!ctl || (id >= ctl->info->count)) return -EINVAL; memset(&ev, 0, sizeof(ev)); ev.id.numid = ctl->info->id.numid; ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); if (ret < 0) return ret; switch (ctl->info->type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return !!ev.value.integer.value[id]; case SNDRV_CTL_ELEM_TYPE_INTEGER: return ev.value.integer.value[id]; case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return ev.value.enumerated.item[id]; case SNDRV_CTL_ELEM_TYPE_BYTES: return ev.value.bytes.data[id]; default: return -EINVAL; } return 0; } int mixer_ctl_is_access_tlv_rw(struct mixer_ctl *ctl) { return (ctl->info->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE); } int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count) { struct snd_ctl_elem_value ev; int ret = 0; size_t size; void *source; size_t total_count; if ((!ctl) || !count || !array) return -EINVAL; total_count = ctl->info->count; if ((ctl->info->type == SNDRV_CTL_ELEM_TYPE_BYTES) && mixer_ctl_is_access_tlv_rw(ctl)) { /* Additional two words is for the TLV header */ total_count += TLV_HEADER_SIZE; } if (count > total_count) return -EINVAL; memset(&ev, 0, sizeof(ev)); ev.id.numid = ctl->info->id.numid; switch (ctl->info->type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: case SNDRV_CTL_ELEM_TYPE_INTEGER: ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); if (ret < 0) return ret; size = sizeof(ev.value.integer.value[0]); source = ev.value.integer.value; break; case SNDRV_CTL_ELEM_TYPE_BYTES: /* check if this is new bytes TLV */ if (mixer_ctl_is_access_tlv_rw(ctl)) { struct snd_ctl_tlv *tlv; int ret; if (count > SIZE_MAX - sizeof(*tlv)) return -EINVAL; tlv = calloc(1, sizeof(*tlv) + count); if (!tlv) return -ENOMEM; tlv->numid = ctl->info->id.numid; tlv->length = count; ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_READ, tlv); source = tlv->tlv; memcpy(array, source, count); free(tlv); return ret; } else { ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); if (ret < 0) return ret; size = sizeof(ev.value.bytes.data[0]); source = ev.value.bytes.data; break; } case SNDRV_CTL_ELEM_TYPE_IEC958: size = sizeof(ev.value.iec958); source = &ev.value.iec958; break; default: return -EINVAL; } memcpy(array, source, size * count); return 0; } int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value) { struct snd_ctl_elem_value ev; int ret; if (!ctl || (id >= ctl->info->count)) return -EINVAL; memset(&ev, 0, sizeof(ev)); ev.id.numid = ctl->info->id.numid; ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); if (ret < 0) return ret; switch (ctl->info->type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: ev.value.integer.value[id] = !!value; break; case SNDRV_CTL_ELEM_TYPE_INTEGER: ev.value.integer.value[id] = value; break; case SNDRV_CTL_ELEM_TYPE_ENUMERATED: ev.value.enumerated.item[id] = value; break; case SNDRV_CTL_ELEM_TYPE_BYTES: ev.value.bytes.data[id] = value; break; default: return -EINVAL; } return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); } int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) { struct snd_ctl_elem_value ev; size_t size; void *dest; size_t total_count; if ((!ctl) || !count || !array) return -EINVAL; total_count = ctl->info->count; if ((ctl->info->type == SNDRV_CTL_ELEM_TYPE_BYTES) && mixer_ctl_is_access_tlv_rw(ctl)) { /* Additional two words is for the TLV header */ total_count += TLV_HEADER_SIZE; } if (count > total_count) return -EINVAL; memset(&ev, 0, sizeof(ev)); ev.id.numid = ctl->info->id.numid; switch (ctl->info->type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: case SNDRV_CTL_ELEM_TYPE_INTEGER: size = sizeof(ev.value.integer.value[0]); dest = ev.value.integer.value; break; case SNDRV_CTL_ELEM_TYPE_BYTES: /* check if this is new bytes TLV */ if (mixer_ctl_is_access_tlv_rw(ctl)) { struct snd_ctl_tlv *tlv; int ret = 0; if (count > SIZE_MAX - sizeof(*tlv)) return -EINVAL; tlv = calloc(1, sizeof(*tlv) + count); if (!tlv) return -ENOMEM; tlv->numid = ctl->info->id.numid; tlv->length = count; memcpy(tlv->tlv, array, count); ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_WRITE, tlv); free(tlv); return ret; } else { size = sizeof(ev.value.bytes.data[0]); dest = ev.value.bytes.data; } break; case SNDRV_CTL_ELEM_TYPE_IEC958: size = sizeof(ev.value.iec958); dest = &ev.value.iec958; break; default: return -EINVAL; } memcpy(dest, array, size * count); return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); } int mixer_ctl_get_range_min(struct mixer_ctl *ctl) { if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) return -EINVAL; return ctl->info->value.integer.min; } int mixer_ctl_get_range_max(struct mixer_ctl *ctl) { if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) return -EINVAL; return ctl->info->value.integer.max; } unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl) { if (!ctl) return 0; return ctl->info->value.enumerated.items; } const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl, unsigned int enum_id) { if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) || (enum_id >= ctl->info->value.enumerated.items)) return NULL; return (const char *)ctl->ename[enum_id]; } int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string) { unsigned int i, num_enums; struct snd_ctl_elem_value ev; int ret; if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED)) return -EINVAL; num_enums = ctl->info->value.enumerated.items; for (i = 0; i < num_enums; i++) { if (!strcmp(string, ctl->ename[i])) { memset(&ev, 0, sizeof(ev)); ev.value.enumerated.item[0] = i; ev.id.numid = ctl->info->id.numid; ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); if (ret < 0) return ret; return 0; } } return -EINVAL; } /** Subscribes for the mixer events. * @param mixer A mixer handle. * @param subscribe value indicating subscribe or unsubscribe for events * @returns On success, zero. * On failure, non-zero. * @ingroup libtinyalsa-mixer */ int mixer_subscribe_events(struct mixer *mixer, int subscribe) { if (ioctl(mixer->fd, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) { return -1; } return 0; } /** Wait for mixer events. * @param mixer A mixer handle. * @param timeout timout value * @returns On success, 1. * On failure, -errno. * On timeout, 0 * @ingroup libtinyalsa-mixer */ int mixer_wait_event(struct mixer *mixer, int timeout) { struct pollfd pfd; pfd.fd = mixer->fd; pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL; for (;;) { int err; err = poll(&pfd, 1, timeout); if (err < 0) return -errno; if (!err) return 0; if (pfd.revents & (POLLERR | POLLNVAL)) return -EIO; if (pfd.revents & (POLLIN | POLLOUT)) return 1; } } /** Consume a mixer event. * If mixer_subscribe_events has been called, * mixer_wait_event will identify when a control value has changed. * This function will clear a single event from the mixer so that * further events can be alerted. * * @param mixer A mixer handle. * @returns 0 on success. -errno on failure. * @ingroup libtinyalsa-mixer */ int mixer_consume_event(struct mixer *mixer) { struct snd_ctl_event ev; ssize_t count = read(mixer->fd, &ev, sizeof(ev)); // Exporting the actual event would require exposing snd_ctl_event // via the header file, and all associated structs. // The events generally tell you exactly which value changed, // but reading values you're interested isn't hard and simplifies // the interface greatly. return (count >= 0) ? 0 : -errno; }