aboutsummaryrefslogtreecommitdiff
path: root/src/mixer.c
diff options
context:
space:
mode:
authorRichard Fitzgerald <rf@opensource.wolfsonmicro.com>2014-09-09 17:14:09 +0100
committerRichard Fitzgerald <rf@opensource.wolfsonmicro.com>2016-12-02 16:39:38 +0000
commitfd3290357ea02f871a10eec208697fb97d578fe6 (patch)
treeb35ed3a9c05cd0e14d51e28b2cd7c01b2f76d296 /src/mixer.c
parent15e6231a1b3455fff50c9912f227efae91666d1f (diff)
mixer: add ability to update control list with new controls
New controls could appear during runtime, for example if a new firmware is downloaded to a DSP. Since ALSA drivers are not supposed to delete or renumber existing controls we can assume that any new controls will be after any controls we already know about. We can use this to enable extending our current list of controls, which is more efficient than closing the mixer session and recreating it. Signed-off-by: Richard Fitzgerald <rf@opensource.wolfsonmicro.com>
Diffstat (limited to 'src/mixer.c')
-rw-r--r--src/mixer.c166
1 files changed, 125 insertions, 41 deletions
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.