diff options
-rw-r--r-- | include/tinyalsa/mixer.h | 2 | ||||
-rw-r--r-- | src/mixer.c | 166 |
2 files changed, 127 insertions, 41 deletions
diff --git a/include/tinyalsa/mixer.h b/include/tinyalsa/mixer.h index f743f6e..d82d4b1 100644 --- a/include/tinyalsa/mixer.h +++ b/include/tinyalsa/mixer.h @@ -69,6 +69,8 @@ struct mixer *mixer_open(unsigned int card); void mixer_close(struct mixer *mixer); +int mixer_add_new_ctls(struct mixer *mixer); + const char *mixer_get_name(const struct mixer *mixer); unsigned int mixer_get_num_ctls(const struct mixer *mixer); diff --git a/src/mixer.c b/src/mixer.c index 9e661c5..66881e9 100644 --- a/src/mixer.c +++ b/src/mixer.c @@ -73,13 +73,25 @@ struct mixer { 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); + } +} + /** 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; + unsigned int n; if (!mixer) return; @@ -88,14 +100,8 @@ void mixer_close(struct mixer *mixer) 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); - } - } + for (n = 0; n < mixer->count; n++) + mixer_cleanup_control(&mixer->ctl[n]); free(mixer->ctl); } @@ -104,6 +110,88 @@ void mixer_close(struct mixer *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 snd_ctl_elem_list elist; + struct snd_ctl_elem_id *eid = NULL; + struct mixer_ctl *ctl; + int fd = mixer->fd; + const unsigned int old_count = mixer->count; + unsigned int new_count; + unsigned int n; + + memset(&elist, 0, sizeof(elist)); + 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(mixer->ctl, old_count, elist.count, + sizeof(struct mixer_ctl)); + if (!ctl) + goto fail; + + mixer->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 (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) + goto fail; + + for (n = old_count; n < new_count; n++) { + struct snd_ctl_elem_info *ei = &mixer->ctl[n].info; + ei->id.numid = eid[n - old_count].numid; + if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) + goto fail_extend; + ctl[n].mixer = mixer; + } + + mixer->count = new_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]); + + mixer->count = n; /* keep controls we successfully added */ + /* fall through... */ +fail: + free(eid); + return -1; +} + /** Opens a mixer for a given card. * @param card The card to open the mixer for. * @returns An initialized mixer handle. @@ -111,10 +199,7 @@ void mixer_close(struct mixer *mixer) */ 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]; @@ -123,52 +208,51 @@ struct mixer *mixer_open(unsigned int card) 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)); - if (!mixer->ctl) - 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 snd_ctl_elem_info *ei = &mixer->ctl[n].info; - ei->id.numid = eid[n].numid; - if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) - goto fail; - mixer->ctl[n].mixer = mixer; - } + if (add_controls(mixer) != 0) + 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; + 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) +{ + if (!mixer) + return 0; + + return add_controls(mixer); } /** Gets the name of the mixer's card. |