Revert "Sync from GitHub"

Revert submission 1483902-sync tinyalsa

Reason for revert: b/174994516, b/175089243
Reverted Changes:
Id3370e3c7:fix build breakage
I296a47fcb:Sync from GitHub
I6b86da455:reorg file structure to match it on GitHub
I5155d7856:add const qualifier to the pcm_mask variables and ...

Change-Id: Ie050892efdadd2a8e199c5375611fae954c2de99
diff --git a/src/mixer.c b/src/mixer.c
index fe590e8..f3fdb62 100644
--- a/src/mixer.c
+++ b/src/mixer.c
@@ -26,758 +26,236 @@
 ** DAMAGE.
 */
 
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
-#include <stdbool.h>
 #include <string.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <ctype.h>
-#include <limits.h>
-#include <time.h>
 #include <poll.h>
 
 #include <sys/ioctl.h>
 
 #include <linux/ioctl.h>
-
-#ifndef __force
 #define __force
-#endif
-
-#ifndef __bitwise
 #define __bitwise
-#endif
-
-#ifndef __user
 #define __user
-#endif
-
 #include <sound/asound.h>
 
-#include <tinyalsa/mixer.h>
-#include <tinyalsa/plugin.h>
+#ifndef SNDRV_CTL_ELEM_ID_NAME_MAXLEN
+#define SNDRV_CTL_ELEM_ID_NAME_MAXLEN 44
+#endif
 
-#include "mixer_io.h"
+#include <tinyalsa/asoundlib.h>
 
-/** A mixer control.
- * @ingroup libtinyalsa-mixer
- */
 struct mixer_ctl {
-    /** The mixer that the mixer control belongs to */
     struct mixer *mixer;
-    /** Information on the control's value (i.e. type, number of values) */
-    struct snd_ctl_elem_info info;
-    /** A list of string representations of enumerated values (only valid for enumerated controls) */
+    struct snd_ctl_elem_info *info;
     char **ename;
-    /** Pointer to the group that the control belongs to */
-    struct mixer_ctl_group *grp;
+    bool info_retrieved;
 };
 
-struct mixer_ctl_group {
-    /** A continuous array of mixer controls */
-    struct mixer_ctl *ctl;
-    /** The number of mixer controls */
-    unsigned int count;
-    /** The number of events associated with this group */
-    unsigned int event_cnt;
-    /** The operations corresponding to this group */
-    const struct mixer_ops *ops;
-    /** Private data for storing group specific data */
-    void *data;
-};
-
-/** A mixer handle.
- * @ingroup libtinyalsa-mixer
- */
 struct mixer {
-    /** File descriptor for the card */
     int fd;
-    /** Card information */
     struct snd_ctl_card_info card_info;
-    /* Hardware (kernel interface) mixer control group */
-    struct mixer_ctl_group *h_grp;
-    /* Virtual (Plugin interface) mixer control group */
-    struct mixer_ctl_group *v_grp;
-    /* Total count of mixer controls from both  groups */
-    unsigned int total_count;
-    /* Flag to track if card information is already retrieved */
-    bool is_card_info_retrieved;
-
+    struct snd_ctl_elem_info *elem_info;
+    struct mixer_ctl *ctl;
+    unsigned int count;
 };
 
-static void mixer_cleanup_control(struct mixer_ctl *ctl)
-{
-    unsigned int m;
-
-    if (ctl->ename) {
-        unsigned int max = ctl->info.value.enumerated.items;
-        for (m = 0; m < max; m++)
-            free(ctl->ename[m]);
-        free(ctl->ename);
-    }
-}
-
-static void mixer_grp_close(struct mixer *mixer, struct mixer_ctl_group *grp)
-{
-    unsigned int n;
-
-    if (!grp)
-        return;
-
-    if (grp->ctl) {
-        for (n = 0; n < grp->count; n++)
-            mixer_cleanup_control(&grp->ctl[n]);
-        free(grp->ctl);
-    }
-
-    free(grp);
-
-    mixer->is_card_info_retrieved = false;
-}
-
-/** Closes a mixer returned by @ref mixer_open.
- * @param mixer A mixer handle.
- * @ingroup libtinyalsa-mixer
- */
 void mixer_close(struct mixer *mixer)
 {
+    unsigned int n,m;
+
     if (!mixer)
         return;
 
-    if (mixer->fd >= 0 && mixer->h_grp)
-        mixer->h_grp->ops->close(mixer->h_grp->data);
-    mixer_grp_close(mixer, mixer->h_grp);
+    if (mixer->fd >= 0)
+        close(mixer->fd);
 
-#ifdef TINYALSA_USES_PLUGINS
-    if (mixer->v_grp)
-        mixer->v_grp->ops->close(mixer->v_grp->data);
-    mixer_grp_close(mixer, mixer->v_grp);
-#endif
+    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 */
 }
 
-static void *mixer_realloc_z(void *ptr, size_t curnum, size_t newnum, size_t size)
-{
-        int8_t *newp;
-
-        newp = realloc(ptr, size * newnum);
-        if (!newp)
-            return NULL;
-
-        memset(newp + (curnum * size), 0, (newnum - curnum) * size);
-        return newp;
-}
-
-static int add_controls(struct mixer *mixer, struct mixer_ctl_group *grp)
+struct mixer *mixer_open(unsigned int card)
 {
     struct snd_ctl_elem_list elist;
     struct snd_ctl_elem_id *eid = NULL;
-    struct mixer_ctl *ctl;
-    const unsigned int old_count = grp->count;
-    unsigned int new_count;
+    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 (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
+    if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
         goto fail;
 
-    if (old_count == elist.count)
-        return 0; /* no new controls return unchanged */
-
-    if (old_count > elist.count)
-        return -1; /* driver has removed controls - this is bad */
-
-    ctl = mixer_realloc_z(grp->ctl, old_count, elist.count,
-                          sizeof(struct mixer_ctl));
-    if (!ctl)
-        goto fail;
-
-    grp->ctl = ctl;
-
-    /* ALSA drivers are not supposed to remove or re-order controls that
-     * have already been created so we know that any new controls must
-     * be after the ones we have already collected
-     */
-    new_count = elist.count;
-    elist.space = new_count - old_count; /* controls we haven't seen before */
-    elist.offset = old_count; /* first control we haven't seen */
-
-    eid = calloc(elist.space, sizeof(struct snd_ctl_elem_id));
-    if (!eid)
-        goto fail;
-
-    elist.pids = eid;
-
-    if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
-        goto fail;
-
-    for (n = old_count; n < new_count; n++) {
-        struct snd_ctl_elem_info *ei = &grp->ctl[n].info;
-        ei->id.numid = eid[n - old_count].numid;
-        if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
-            goto fail_extend;
-        ctl[n].mixer = mixer;
-        ctl[n].grp = grp;
-    }
-
-    grp->count = new_count;
-    mixer->total_count += (new_count - old_count);
-
-    free(eid);
-    return 0;
-
-fail_extend:
-    /* cleanup the control we failed on but leave the ones that were already
-     * added. Also no advantage to shrinking the resized memory block, we
-     * might want to extend the controls again later
-     */
-    mixer_cleanup_control(&ctl[n]);
-
-    grp->count = n;   /* keep controls we successfully added */
-    mixer->total_count += (n - old_count);
-    /* fall through... */
-fail:
-    free(eid);
-    return -1;
-}
-
-static int mixer_grp_open(struct mixer *mixer, unsigned int card, bool is_hw)
-{
-    struct mixer_ctl_group *grp = NULL;
-    const struct mixer_ops *ops = NULL;
-    void *data = NULL;
-    int fd, ret;
-
-    grp = calloc(1, sizeof(*grp));
-    if (!grp)
-        return -ENOMEM;
-
-    if (is_hw) {
-        mixer->fd = -1;
-        fd = mixer_hw_open(card, &data, &ops);
-        if (fd < 0) {
-            ret = fd;
-            goto err_open;
-        }
-        mixer->fd = fd;
-        mixer->h_grp = grp;
-    }
-#ifdef TINYALSA_USES_PLUGINS
-    else {
-        ret = mixer_plugin_open(card, &data, &ops);
-        if (ret < 0)
-            goto err_open;
-        mixer->v_grp = grp;
-    }
-#endif
-    grp->ops = ops;
-    grp->data = data;
-
-    if (!mixer->is_card_info_retrieved) {
-        ret = grp->ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO,
-                              &mixer->card_info);
-        if (ret < 0)
-            goto err_card_info;
-        mixer->is_card_info_retrieved = true;
-    }
-
-    ret = add_controls(mixer, grp);
-    if (ret < 0)
-        goto err_card_info;
-
-    return 0;
-
-err_card_info:
-    grp->ops->close(grp->data);
-
-err_open:
-    free(grp);
-    return ret;
-
-}
-
-/** Opens a mixer for a given card.
- * @param card The card to open the mixer for.
- * @returns An initialized mixer handle.
- * @ingroup libtinyalsa-mixer
- */
-struct mixer *mixer_open(unsigned int card)
-{
-    struct mixer *mixer = NULL;
-    int h_status, v_status = -1;
-
     mixer = calloc(1, sizeof(*mixer));
     if (!mixer)
         goto fail;
 
-    h_status = mixer_grp_open(mixer, card, true);
-
-#ifdef TINYALSA_USES_PLUGINS
-    v_status = mixer_grp_open(mixer, card, false);
-#endif
-
-    /* both hw and virtual should fail for mixer_open to fail */
-    if (h_status < 0 && v_status < 0)
+    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);
-
-    return NULL;
-}
-
-/** Some controls may not be present at boot time, e.g. controls from runtime
- * loadable DSP firmware. This function adds any new controls that have appeared
- * since mixer_open() or the last call to this function. This assumes a well-
- * behaved codec driver that does not delete controls that already exists, so
- * any added controls must be after the last one we already saw. Scanning only
- * the new controls is much faster than calling mixer_close() then mixer_open()
- * to re-scan all controls.
- *
- * NOTE: this invalidates any struct mixer_ctl pointers previously obtained
- * from mixer_get_ctl() and mixer_get_ctl_by_name(). Either refresh all your
- * stored pointers after calling mixer_update_ctls(), or (better) do not
- * store struct mixer_ctl pointers, instead lookup the control by name or
- * id only when you are about to use it. The overhead of lookup by id
- * using mixer_get_ctl() is negligible.
- * @param mixer An initialized mixer handle.
- * @returns 0 on success, -1 on failure
- */
-int mixer_add_new_ctls(struct mixer *mixer)
-{
-    int rc1 = 0, rc2 = 0;
-
-    if (!mixer)
-        return 0;
-
-    /* add the h_grp controls */
-    if (mixer->h_grp)
-        rc1 = add_controls(mixer, mixer->h_grp);
-
-#ifdef TINYALSA_USES_PLUGINS
-    /* add the v_grp controls */
-    if (mixer->v_grp)
-        rc2 = add_controls(mixer, mixer->v_grp);
-#endif
-
-    if (rc1 < 0)
-        return rc1;
-    if (rc2 < 0)
-        return rc2;
-
+    else if (fd >= 0)
+        close(fd);
     return 0;
 }
 
-/** Gets the name of the mixer's card.
- * @param mixer An initialized mixer handle.
- * @returns The name of the mixer's card.
- * @ingroup libtinyalsa-mixer
- */
-const char *mixer_get_name(const struct mixer *mixer)
+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;
 }
 
-/** Gets the number of mixer controls for a given mixer.
- * @param mixer An initialized mixer handle.
- * @returns The number of mixer controls for the given mixer.
- * @ingroup libtinyalsa-mixer
- */
-unsigned int mixer_get_num_ctls(const struct mixer *mixer)
+unsigned int mixer_get_num_ctls(struct mixer *mixer)
 {
     if (!mixer)
         return 0;
 
-    return mixer->total_count;
+    return mixer->count;
 }
 
-/** Gets the number of mixer controls, that go by a specified name, for a given mixer.
- * @param mixer An initialized mixer handle.
- * @param name The name of the mixer control
- * @returns The number of mixer controls, specified by @p name, for the given mixer.
- * @ingroup libtinyalsa-mixer
- */
-unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *name)
-{
-    struct mixer_ctl_group *grp;
-    unsigned int n;
-    unsigned int count = 0;
-    struct mixer_ctl *ctl;
-
-    if (!mixer)
-        return 0;
-
-    if (mixer->h_grp) {
-        grp = mixer->h_grp;
-        ctl = grp->ctl;
-
-        for (n = 0; n < grp->count; n++)
-            if (!strcmp(name, (char*) ctl[n].info.id.name))
-                count++;
-    }
-#ifdef TINYALSA_USES_PLUGINS
-    if (mixer->v_grp) {
-        grp = mixer->v_grp;
-        ctl = grp->ctl;
-
-        for (n = 0; n < grp->count; n++)
-            if (!strcmp(name, (char*) ctl[n].info.id.name))
-                count++;
-    }
-#endif
-
-    return count;
-}
-
-/** 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)
-{
-    struct mixer_ctl_group *grp;
-
-    if (mixer->h_grp) {
-        grp = mixer->h_grp;
-        if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
-            return -1;
-    }
-
-#ifdef TINYALSA_USES_PLUGINS
-    if (mixer->v_grp) {
-        grp = mixer->v_grp;
-        if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
-            return -1;
-    }
-#endif
-    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;
-    struct mixer_ctl_group *grp;
-    int count = 0, num_fds = 0, i, ret = 0;
-
-    if (mixer->fd >= 0)
-        num_fds++;
-
-#ifdef TINYALSA_USES_PLUGINS
-    if (mixer->v_grp)
-        num_fds++;
-#endif
-
-    pfd = (struct pollfd *)calloc(num_fds, sizeof(struct pollfd));
-    if (!pfd)
-        return -ENOMEM;
-
-    if (mixer->fd >= 0) {
-        pfd[count].fd = mixer->fd;
-        pfd[count].events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
-        count++;
-    }
-
-#ifdef TINYALSA_USES_PLUGINS
-    if (mixer->v_grp) {
-        grp = mixer->v_grp;
-        if (!grp->ops->get_poll_fd(grp->data, pfd, count)) {
-            pfd[count].events = POLLIN | POLLERR | POLLNVAL;
-            count++;
-        }
-    }
-#endif
-
-    if (!count)
-        goto exit;
-
-    for (;;) {
-        int err;
-        err = poll(pfd, count, timeout);
-        if (err < 0) {
-            ret = -errno;
-            goto exit;
-        }
-        if (!err)
-            goto exit;
-
-        for (i = 0; i < count; i++) {
-            if (pfd[i].revents & (POLLERR | POLLNVAL)) {
-                ret = -EIO;
-                goto exit;
-            }
-            if (pfd[i].revents & (POLLIN | POLLOUT)) {
-                if ((i == 0) && mixer->fd >= 0) {
-                    grp = mixer->h_grp;
-                    grp->event_cnt++;
-                }
-#ifdef TINYALSA_USES_PLUGINS
-                 else {
-                    grp = mixer->v_grp;
-                    grp->event_cnt++;
-                }
-#endif
-                ret = 1;
-                goto exit;
-            }
-        }
-    }
-exit:
-    free(pfd);
-    return ret;
-}
-
-/** 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;
-
-    return mixer_read_event(mixer, &ev);
-}
-
-int mixer_read_event(struct mixer *mixer, struct snd_ctl_event *ev)
-{
-    struct mixer_ctl_group *grp;
-    ssize_t count = 0;
-
-    if (mixer->h_grp) {
-        grp = mixer->h_grp;
-        if (grp->event_cnt) {
-            grp->event_cnt--;
-            count = grp->ops->read_event(grp->data, ev, sizeof(*ev));
-            return (count >= 0) ? 0 : -errno;
-        }
-    }
-#ifdef TINYALSA_USES_PLUGINS
-    if (mixer->v_grp) {
-        grp = mixer->v_grp;
-        if (grp->event_cnt) {
-            grp->event_cnt--;
-            count = grp->ops->read_event(grp->data, ev, sizeof(*ev));
-            return (count >= 0) ? 0 : -errno;
-        }
-    }
-#endif
-    return 0;
-}
-
-static unsigned int mixer_grp_get_count(struct mixer_ctl_group *grp)
-{
-    if (!grp)
-        return 0;
-
-    return grp->count;
-}
-
-/** Gets a mixer control handle, by the mixer control's id.
- * For non-const access, see @ref mixer_get_ctl
- * @param mixer An initialized mixer handle.
- * @param id The control's id in the given mixer.
- * @returns A handle to the mixer control.
- * @ingroup libtinyalsa-mixer
- */
-const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned int id)
-{
-    unsigned int h_count;
-
-    if (!mixer || (id >= mixer->total_count))
-        return NULL;
-
-    h_count = mixer_grp_get_count(mixer->h_grp);
-
-    if (id < h_count)
-        return mixer->h_grp->ctl + id;
-#ifdef TINYALSA_USES_PLUGINS
-    else {
-        unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
-	    if ((id - h_count) < v_count)
-            return mixer->v_grp->ctl + (id - h_count);
-    }
-#endif
-
-    return NULL;
-}
-
-/** Gets a mixer control handle, by the mixer control's id.
- * For const access, see @ref mixer_get_ctl_const
- * @param mixer An initialized mixer handle.
- * @param id The control's id in the given mixer.
- * @returns A handle to the mixer control.
- * @ingroup libtinyalsa-mixer
- */
 struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
 {
-    unsigned int h_count;
+    struct mixer_ctl *ctl;
 
-    if (!mixer || (id >= mixer->total_count))
+    if (!mixer || (id >= mixer->count))
         return NULL;
 
-    h_count = mixer_grp_get_count(mixer->h_grp);
+    ctl = mixer->ctl + id;
+    if (!mixer_ctl_get_elem_info(ctl))
+        return NULL;
 
-    if (id < h_count)
-        return mixer->h_grp->ctl + id;
-#ifdef TINYALSA_USES_PLUGINS
-    else {
-        unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
-	    if ((id - h_count) < v_count)
-            return mixer->v_grp->ctl + (id - h_count);
-    }
-#endif
-    return NULL;
+    return ctl;
 }
 
-/** Gets the first instance of mixer control handle, by the mixer control's name.
- * @param mixer An initialized mixer handle.
- * @param name The control's name in the given mixer.
- * @returns A handle to the mixer control.
- * @ingroup libtinyalsa-mixer
- */
 struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name)
 {
-    return mixer_get_ctl_by_name_and_index(mixer, name, 0);
-}
-
-/** Gets an instance of mixer control handle, by the mixer control's name and index.
- *  For instance, if two controls have the name of 'Volume', then and index of 1 would return the second control.
- * @param mixer An initialized mixer handle.
- * @param name The control's name in the given mixer.
- * @param index The control's index.
- * @returns A handle to the mixer control.
- * @ingroup libtinyalsa-mixer
- */
-struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer,
-                                                  const char *name,
-                                                  unsigned int index)
-{
-    struct mixer_ctl_group *grp;
     unsigned int n;
-    struct mixer_ctl *ctl;
 
     if (!mixer)
         return NULL;
 
-    if (mixer->h_grp) {
-        grp = mixer->h_grp;
-        ctl = grp->ctl;
+    for (n = 0; n < mixer->count; n++)
+        if (!strcmp(name, (char*) mixer->elem_info[n].id.name))
+            return mixer_get_ctl(mixer, n);
 
-        for (n = 0; n < grp->count; n++)
-            if (!strcmp(name, (char*) ctl[n].info.id.name))
-                if (index-- == 0)
-                    return ctl + n;
-    }
-
-#ifdef TINYALSA_USES_PLUGINS
-    if (mixer->v_grp) {
-        grp = mixer->v_grp;
-        ctl = grp->ctl;
-
-        for (n = 0; n < grp->count; n++)
-            if (!strcmp(name, (char*) ctl[n].info.id.name))
-                if (index-- == 0)
-                    return ctl + n;
-    }
-#endif
     return NULL;
 }
 
-/** Updates the control's info.
- * This is useful for a program that may be idle for a period of time.
- * @param ctl An initialized control handle.
- * @ingroup libtinyalsa-mixer
- */
 void mixer_ctl_update(struct mixer_ctl *ctl)
 {
-    struct mixer_ctl_group *grp;
-
-    if (!ctl)
-        return;
-
-    grp  = ctl->grp;
-    grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &ctl->info);
+    ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info);
 }
 
-/** Checks the control for TLV Read/Write access.
- * @param ctl An initialized control handle.
- * @returns On success, non-zero.
- *  On failure, zero.
- * @ingroup libtinyalsa-mixer
- */
-int mixer_ctl_is_access_tlv_rw(const struct mixer_ctl *ctl)
-{
-    return (ctl->info.access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE);
-}
-
-/** Gets the control's ID.
- * @param ctl An initialized control handle.
- * @returns On success, the control's ID is returned.
- *  On error, UINT_MAX is returned instead.
- * @ingroup libtinyalsa-mixer
- */
-unsigned int mixer_ctl_get_id(const struct mixer_ctl *ctl)
-{
-    if (!ctl)
-        return UINT_MAX;
-
-    /* numid values start at 1, return a 0-base value that
-     * can be passed to mixer_get_ctl()
-     */
-    return ctl->info.id.numid - 1;
-}
-
-/** Gets the name of the control.
- * @param ctl An initialized control handle.
- * @returns On success, the name of the control.
- *  On error, NULL.
- * @ingroup libtinyalsa-mixer
- */
-const char *mixer_ctl_get_name(const struct mixer_ctl *ctl)
+const char *mixer_ctl_get_name(struct mixer_ctl *ctl)
 {
     if (!ctl)
         return NULL;
 
-    return (const char *)ctl->info.id.name;
+    return (const char *)ctl->info->id.name;
 }
 
-/** Gets the value type of the control.
- * @param ctl An initialized control handle
- * @returns On success, the type of mixer control.
- *  On failure, it returns @ref MIXER_CTL_TYPE_UNKNOWN
- * @ingroup libtinyalsa-mixer
- */
-enum mixer_ctl_type mixer_ctl_get_type(const struct mixer_ctl *ctl)
+enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl)
 {
     if (!ctl)
         return MIXER_CTL_TYPE_UNKNOWN;
 
-    switch (ctl->info.type) {
+    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;
@@ -788,17 +266,12 @@
     };
 }
 
-/** Gets the string that describes the value type of the control.
- * @param ctl An initialized control handle
- * @returns On success, a string describing type of mixer control.
- * @ingroup libtinyalsa-mixer
- */
-const char *mixer_ctl_get_type_string(const struct mixer_ctl *ctl)
+const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl)
 {
     if (!ctl)
         return "";
 
-    switch (ctl->info.type) {
+    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";
@@ -809,95 +282,69 @@
     };
 }
 
-/** Gets the number of values in the control.
- * @param ctl An initialized control handle
- * @returns The number of values in the mixer control
- * @ingroup libtinyalsa-mixer
- */
-unsigned int mixer_ctl_get_num_values(const struct mixer_ctl *ctl)
+unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl)
 {
     if (!ctl)
         return 0;
 
-    return ctl->info.count;
+    return ctl->info->count;
 }
 
-static int percent_to_int(const struct snd_ctl_elem_info *ei, int percent)
+static int percent_to_int(struct snd_ctl_elem_info *ei, int percent)
 {
-    if ((percent > 100) || (percent < 0)) {
-        return -EINVAL;
-    }
+    int range;
 
-    int range = (ei->value.integer.max - ei->value.integer.min);
+    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(const struct snd_ctl_elem_info *ei, int value)
+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) * 100) / range;
+    return ((value - ei->value.integer.min) / range) * 100;
 }
 
-/** Gets a percentage representation of a specified control value.
- * @param ctl An initialized control handle.
- * @param id The index of the value within the control.
- * @returns On success, the percentage representation of the control value.
- *  On failure, -EINVAL is returned.
- * @ingroup libtinyalsa-mixer
- */
-int mixer_ctl_get_percent(const struct mixer_ctl *ctl, unsigned int id)
+int mixer_ctl_get_percent(struct mixer_ctl *ctl, unsigned int id)
 {
-    if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
+    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER))
         return -EINVAL;
 
-    return int_to_percent(&ctl->info, mixer_ctl_get_value(ctl, id));
+    return int_to_percent(ctl->info, mixer_ctl_get_value(ctl, id));
 }
 
-/** Sets the value of a control by percent, specified by the value index.
- * @param ctl An initialized control handle.
- * @param id The index of the value to set
- * @param percent A percentage value between 0 and 100.
- * @returns On success, zero is returned.
- *  On failure, non-zero is returned.
- * @ingroup libtinyalsa-mixer
- */
 int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent)
 {
-    if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
+    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));
+    return mixer_ctl_set_value(ctl, id, percent_to_int(ctl->info, percent));
 }
 
-/** Gets the value of a control.
- * @param ctl An initialized control handle.
- * @param id The index of the control value.
- * @returns On success, the specified value is returned.
- *  On failure, -EINVAL is returned.
- * @ingroup libtinyalsa-mixer
- */
-int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id)
+int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id)
 {
-    struct mixer_ctl_group *grp;
     struct snd_ctl_elem_value ev;
     int ret;
 
-    if (!ctl || (id >= ctl->info.count))
+    if (!ctl || (id >= ctl->info->count))
         return -EINVAL;
 
-    grp = ctl->grp;
     memset(&ev, 0, sizeof(ev));
-    ev.id.numid = ctl->info.id.numid;
-    ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &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) {
+    switch (ctl->info->type) {
     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
         return !!ev.value.integer.value[id];
 
@@ -917,41 +364,40 @@
     return 0;
 }
 
-/** Gets the contents of a control's value array.
- * @param ctl An initialized control handle.
- * @param array A pointer to write the array data to.
- *  The size of this array must be equal to the number of items in the array
- *  multiplied by the size of each item.
- * @param count The number of items in the array.
- *  This parameter must match the number of items in the control.
- *  The number of items in the control may be accessed via @ref mixer_ctl_get_num_values
- * @returns On success, zero.
- *  On failure, non-zero.
- * @ingroup libtinyalsa-mixer
- */
-int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
+int mixer_ctl_is_access_tlv_rw(struct mixer_ctl *ctl)
 {
-    struct mixer_ctl_group *grp;
+    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)
+    if ((!ctl) || !count || !array)
         return -EINVAL;
 
-    grp = ctl->grp;
+    total_count = ctl->info->count;
 
-    if (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;
+    ev.id.numid = ctl->info->id.numid;
 
-    switch (ctl->info.type) {
+    switch (ctl->info->type) {
     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
     case SNDRV_CTL_ELEM_TYPE_INTEGER:
-        ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+        ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
         if (ret < 0)
             return ret;
         size = sizeof(ev.value.integer.value[0]);
@@ -966,14 +412,12 @@
 
             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->numid = ctl->info->id.numid;
             tlv->length = count;
-            ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_READ, tlv);
+            ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_READ, tlv);
 
             source = tlv->tlv;
             memcpy(array, source, count);
@@ -982,7 +426,7 @@
 
             return ret;
         } else {
-            ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+            ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
             if (ret < 0)
                 return ret;
             size = sizeof(ev.value.bytes.data[0]);
@@ -1004,43 +448,26 @@
     return 0;
 }
 
-/** Sets the value of a control, specified by the value index.
- * @param ctl An initialized control handle.
- * @param id The index of the value within the control.
- * @param value The value to set.
- *  This must be in a range specified by @ref mixer_ctl_get_range_min
- *  and @ref mixer_ctl_get_range_max.
- * @returns On success, zero is returned.
- *  On failure, non-zero is returned.
- * @ingroup libtinyalsa-mixer
- */
 int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
 {
-    struct mixer_ctl_group *grp;
     struct snd_ctl_elem_value ev;
     int ret;
 
-    if (!ctl || (id >= ctl->info.count))
+    if (!ctl || (id >= ctl->info->count))
         return -EINVAL;
 
-    grp = ctl->grp;
     memset(&ev, 0, sizeof(ev));
-    ev.id.numid = ctl->info.id.numid;
-    ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &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) {
+    switch (ctl->info->type) {
     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
         ev.value.integer.value[id] = !!value;
         break;
 
     case SNDRV_CTL_ELEM_TYPE_INTEGER:
-        if ((value < mixer_ctl_get_range_min(ctl)) ||
-            (value > mixer_ctl_get_range_max(ctl))) {
-            return -EINVAL;
-        }
-
         ev.value.integer.value[id] = value;
         break;
 
@@ -1056,38 +483,34 @@
         return -EINVAL;
     }
 
-    return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+    return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
 }
 
-/** Sets the contents of a control's value array.
- * @param ctl An initialized control handle.
- * @param array The array containing control values.
- * @param count The number of values in the array.
- *  This must match the number of values in the control.
- *  The number of values in a control may be accessed via @ref mixer_ctl_get_num_values
- * @returns On success, zero.
- *  On failure, non-zero.
- * @ingroup libtinyalsa-mixer
- */
 int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
 {
-    struct mixer_ctl_group *grp;
     struct snd_ctl_elem_value ev;
     size_t size;
     void *dest;
+    size_t total_count;
 
     if ((!ctl) || !count || !array)
         return -EINVAL;
 
-    grp = ctl->grp;
+    total_count = ctl->info->count;
 
-    if (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;
+    ev.id.numid = ctl->info->id.numid;
 
-    switch (ctl->info.type) {
+    switch (ctl->info->type) {
     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
     case SNDRV_CTL_ELEM_TYPE_INTEGER:
         size = sizeof(ev.value.integer.value[0]);
@@ -1099,19 +522,16 @@
         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->numid = ctl->info->id.numid;
             tlv->length = count;
             memcpy(tlv->tlv, array, count);
 
-            ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
+            ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
             free(tlv);
 
             return ret;
@@ -1132,136 +552,59 @@
 
     memcpy(dest, array, size * count);
 
-    return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+    return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
 }
 
-/** Gets the minimum value of an control.
- * The control must have an integer type.
- * The type of the control can be checked with @ref mixer_ctl_get_type.
- * @param ctl An initialized control handle.
- * @returns On success, the minimum value of the control.
- *  On failure, -EINVAL.
- * @ingroup libtinyalsa-mixer
- */
-int mixer_ctl_get_range_min(const struct mixer_ctl *ctl)
+int mixer_ctl_get_range_min(struct mixer_ctl *ctl)
 {
-    if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
+    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER))
         return -EINVAL;
 
-    return ctl->info.value.integer.min;
+    return ctl->info->value.integer.min;
 }
 
-/** Gets the maximum value of an control.
- * The control must have an integer type.
- * The type of the control can be checked with @ref mixer_ctl_get_type.
- * @param ctl An initialized control handle.
- * @returns On success, the maximum value of the control.
- *  On failure, -EINVAL.
- * @ingroup libtinyalsa-mixer
- */
-int mixer_ctl_get_range_max(const struct mixer_ctl *ctl)
+int mixer_ctl_get_range_max(struct mixer_ctl *ctl)
 {
-    if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
+    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER))
         return -EINVAL;
 
-    return ctl->info.value.integer.max;
+    return ctl->info->value.integer.max;
 }
 
-/** Get the number of enumerated items in the control.
- * @param ctl An initialized control handle.
- * @returns The number of enumerated items in the control.
- * @ingroup libtinyalsa-mixer
- */
-unsigned int mixer_ctl_get_num_enums(const struct mixer_ctl *ctl)
+unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl)
 {
     if (!ctl)
         return 0;
 
-    return ctl->info.value.enumerated.items;
+    return ctl->info->value.enumerated.items;
 }
 
-int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl)
-{
-    struct mixer_ctl_group *grp = ctl->grp;
-    struct snd_ctl_elem_info tmp;
-    unsigned int m;
-    char **enames;
-
-    if (ctl->ename) {
-        return 0;
-    }
-
-    enames = calloc(ctl->info.value.enumerated.items, sizeof(char*));
-    if (!enames)
-        goto fail;
-    for (m = 0; m < ctl->info.value.enumerated.items; m++) {
-        memset(&tmp, 0, sizeof(tmp));
-        tmp.id.numid = ctl->info.id.numid;
-        tmp.value.enumerated.item = m;
-        if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
-            goto fail;
-        enames[m] = strdup(tmp.value.enumerated.name);
-        if (!enames[m])
-            goto fail;
-    }
-    ctl->ename = enames;
-    return 0;
-
-fail:
-    if (enames) {
-        for (m = 0; m < ctl->info.value.enumerated.items; m++) {
-            if (enames[m]) {
-                free(enames[m]);
-            }
-        }
-        free(enames);
-    }
-    return -1;
-}
-
-/** Gets the string representation of an enumerated item.
- * @param ctl An initialized control handle.
- * @param enum_id The index of the enumerated value.
- * @returns A string representation of the enumerated item.
- * @ingroup libtinyalsa-mixer
- */
 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) ||
-        mixer_ctl_fill_enum_string(ctl) != 0)
+    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];
 }
 
-/** Set an enumeration value by string value.
- * @param ctl An enumerated mixer control.
- * @param string The string representation of an enumeration.
- * @returns On success, zero.
- *  On failure, zero.
- * @ingroup libtinyalsa-mixer
- */
 int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
 {
-    struct mixer_ctl_group *grp;
     unsigned int i, num_enums;
     struct snd_ctl_elem_value ev;
     int ret;
 
-    if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) ||
-        mixer_ctl_fill_enum_string(ctl) != 0)
+    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED))
         return -EINVAL;
 
-    grp = ctl->grp;
-    num_enums = ctl->info.value.enumerated.items;
+    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 = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+            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;
@@ -1270,3 +613,68 @@
 
     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;
+}