aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/tinyalsa/mixer.h2
-rw-r--r--src/mixer.c166
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.