diff options
author | Bhalchandra Gajare <gajare@codeaurora.org> | 2019-06-19 15:30:42 -0700 |
---|---|---|
committer | Rohit kumar <rohitkr@codeaurora.org> | 2020-02-07 12:53:00 +0530 |
commit | e7c627dd74f5d43439792491c291564e298cbb10 (patch) | |
tree | 3d63ee71cbd1bf6d51f0be517a27073fe00c9bf0 /src | |
parent | 986b8e338782a4c8aab83e626949ed5457eb5b69 (diff) |
tinyalsa: add support for mixer plugins
Update the mixer framework to support plugins. Add ability for physical
card to have either kernel registered mixer controls or plugin
registered mixer control or both. Split mixer controls into two groups,
one for kernel registered (hw_grp) and the other for plugin registered
(virtual_grp).
Signed-off-by: Rohit kumar <rohitkr@codeaurora.org>
Signed-off-by: Bhalchandra Gajare <gajare@codeaurora.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 6 | ||||
-rw-r--r-- | src/mixer.c | 392 | ||||
-rw-r--r-- | src/mixer_hw.c | 112 | ||||
-rw-r--r-- | src/mixer_io.h | 51 | ||||
-rw-r--r-- | src/mixer_plugin.c | 480 |
5 files changed, 966 insertions, 75 deletions
diff --git a/src/Makefile b/src/Makefile index b278f47..79628b5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -15,7 +15,7 @@ INCLUDE_DIRS = -I ../include override CFLAGS := $(WARNINGS) $(INCLUDE_DIRS) -fPIC $(CFLAGS) VPATH = ../include/tinyalsa -OBJECTS = limits.o mixer.o pcm.o pcm_plugin.o pcm_hw.o snd_card_plugin.o +OBJECTS = limits.o mixer.o pcm.o pcm_plugin.o pcm_hw.o snd_card_plugin.o mixer_plugin.o mixer_hw.o LIBVERSION_MAJOR = $(TINYALSA_VERSION_MAJOR) LIBVERSION = $(TINYALSA_VERSION) @@ -35,6 +35,10 @@ mixer.o: mixer.c mixer.h snd_card_plugin.o: snd_card_plugin.c snd_card_plugin.h +mixer_plugin.o: mixer_plugin.c mixer_io.h + +mixer_hw.o: mixer_hw.c mixer_io.h + libtinyalsa.a: $(OBJECTS) $(AR) $(ARFLAGS) $@ $^ diff --git a/src/mixer.c b/src/mixer.c index e496e18..1c9e63b 100644 --- a/src/mixer.c +++ b/src/mixer.c @@ -29,6 +29,7 @@ #include <stdio.h> #include <stdlib.h> #include <stdint.h> +#include <stdbool.h> #include <string.h> #include <unistd.h> #include <fcntl.h> @@ -57,6 +58,9 @@ #include <sound/asound.h> #include <tinyalsa/mixer.h> +#include <tinyalsa/plugin.h> + +#include "mixer_io.h" /** A mixer control. * @ingroup libtinyalsa-mixer @@ -68,6 +72,21 @@ struct mixer_ctl { struct snd_ctl_elem_info info; /** A list of string representations of enumerated values (only valid for enumerated controls) */ char **ename; + /** Pointer to the group that the control belongs to */ + struct mixer_ctl_group *grp; +}; + +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. @@ -78,10 +97,15 @@ struct mixer { int fd; /** Card information */ struct snd_ctl_card_info card_info; - /** A continuous array of mixer controls */ - struct mixer_ctl *ctl; - /** The number of mixer controls */ - unsigned int count; + /* 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; + }; static void mixer_cleanup_control(struct mixer_ctl *ctl) @@ -96,25 +120,40 @@ static void mixer_cleanup_control(struct mixer_ctl *ctl) } } +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); + } + + 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; - if (!mixer) return; - if (mixer->fd >= 0) - close(mixer->fd); + 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->ctl) { - for (n = 0; n < mixer->count; n++) - mixer_cleanup_control(&mixer->ctl[n]); - free(mixer->ctl); - } +#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 free(mixer); @@ -133,18 +172,17 @@ static void *mixer_realloc_z(void *ptr, size_t curnum, size_t newnum, size_t siz return newp; } -static int add_controls(struct mixer *mixer) +static int add_controls(struct mixer *mixer, struct mixer_ctl_group *grp) { 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; + const unsigned int old_count = grp->count; unsigned int new_count; unsigned int n; memset(&elist, 0, sizeof(elist)); - if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) + if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) goto fail; if (old_count == elist.count) @@ -153,12 +191,12 @@ static int add_controls(struct mixer *mixer) if (old_count > elist.count) return -1; /* driver has removed controls - this is bad */ - ctl = mixer_realloc_z(mixer->ctl, old_count, elist.count, + ctl = mixer_realloc_z(grp->ctl, old_count, elist.count, sizeof(struct mixer_ctl)); if (!ctl) goto fail; - mixer->ctl = ctl; + 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 @@ -174,18 +212,21 @@ static int add_controls(struct mixer *mixer) elist.pids = eid; - if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) + 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 = &mixer->ctl[n].info; + struct snd_ctl_elem_info *ei = &grp->ctl[n].info; ei->id.numid = eid[n - old_count].numid; - if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) + if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) goto fail_extend; ctl[n].mixer = mixer; + ctl[n].grp = grp; } - mixer->count = new_count; + grp->count = new_count; + mixer->total_count += (new_count - old_count); + free(eid); return 0; @@ -196,13 +237,69 @@ fail_extend: */ mixer_cleanup_control(&ctl[n]); - mixer->count = n; /* keep controls we successfully added */ + 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. @@ -211,24 +308,20 @@ fail: struct mixer *mixer_open(unsigned int card) { struct mixer *mixer = NULL; - int fd; - char fn[256]; - - snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card); - fd = open(fn, O_RDWR); - if (fd < 0) - return 0; + int h_status, v_status = -1; mixer = calloc(1, sizeof(*mixer)); if (!mixer) goto fail; - if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0) - goto fail; + h_status = mixer_grp_open(mixer, card, true); - mixer->fd = fd; +#ifdef TINYALSA_USES_PLUGINS + v_status = mixer_grp_open(mixer, card, false); +#endif - if (add_controls(mixer) != 0) + /* both hw and virtual should fail for mixer_open to fail */ + if (h_status < 0 && v_status < 0) goto fail; return mixer; @@ -236,8 +329,7 @@ struct mixer *mixer_open(unsigned int card) fail: if (mixer) mixer_close(mixer); - else if (fd >= 0) - close(fd); + return NULL; } @@ -260,10 +352,27 @@ fail: */ int mixer_add_new_ctls(struct mixer *mixer) { + int rc1 = 0, rc2 = 0; + if (!mixer) return 0; - return add_controls(mixer); + /* 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; + + return 0; } /** Gets the name of the mixer's card. @@ -286,7 +395,7 @@ unsigned int mixer_get_num_ctls(const struct mixer *mixer) if (!mixer) return 0; - return mixer->count; + return mixer->total_count; } /** Gets the number of mixer controls, that go by a specified name, for a given mixer. @@ -297,6 +406,7 @@ unsigned int mixer_get_num_ctls(const struct mixer *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; @@ -304,11 +414,24 @@ unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *n if (!mixer) return 0; - ctl = mixer->ctl; + if (mixer->h_grp) { + grp = mixer->h_grp; + ctl = grp->ctl; - for (n = 0; n < mixer->count; n++) - if (!strcmp(name, (char*) ctl[n].info.id.name)) - count++; + 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; } @@ -322,9 +445,21 @@ unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *n */ int mixer_subscribe_events(struct mixer *mixer, int subscribe) { - if (ioctl(mixer->fd, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) { - return -1; + 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; } @@ -338,25 +473,76 @@ int mixer_subscribe_events(struct mixer *mixer, int subscribe) */ int mixer_wait_event(struct mixer *mixer, int timeout) { - struct pollfd pfd; + struct pollfd *pfd; + struct mixer_ctl_group *grp; + int count = 0, num_fds = 0, i; - pfd.fd = mixer->fd; - pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL; + if (mixer->fd >= 0) + num_fds++; + +#ifdef TINYALSA_USES_PLUGINS + if (mixer->v_grp) + num_fds++; +#endif + + pfd = (struct pollfd *)calloc(sizeof(struct pollfd), num_fds); + 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) + return 0; for (;;) { int err; - err = poll(&pfd, 1, timeout); + err = poll(pfd, count, timeout); if (err < 0) return -errno; if (!err) return 0; - if (pfd.revents & (POLLERR | POLLNVAL)) - return -EIO; - if (pfd.revents & (POLLIN | POLLOUT)) - return 1; + for (i = 0; i < count; i++) { + if (pfd[i].revents & (POLLERR | POLLNVAL)) + return -EIO; + 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 + return 1; + } + } } } +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. @@ -366,8 +552,22 @@ int mixer_wait_event(struct mixer *mixer, int timeout) */ const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned int id) { - if (mixer && (id < mixer->count)) - return mixer->ctl + 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; } @@ -381,9 +581,22 @@ const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned */ struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id) { - if (mixer && (id < mixer->count)) - return mixer->ctl + 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; } @@ -410,19 +623,34 @@ 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; - ctl = mixer->ctl; + if (mixer->h_grp) { + grp = mixer->h_grp; + ctl = grp->ctl; - for (n = 0; n < mixer->count; n++) - if (!strcmp(name, (char*) ctl[n].info.id.name)) - if (index-- == 0) - return mixer->ctl + 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; } @@ -433,7 +661,13 @@ struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer, */ void mixer_ctl_update(struct mixer_ctl *ctl) { - ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info); + struct mixer_ctl_group *grp; + + if (!ctl) + return; + + grp = ctl->grp; + grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &ctl->info); } /** Checks the control for TLV Read/Write access. @@ -595,15 +829,17 @@ int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent) */ int mixer_ctl_get_value(const 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)) return -EINVAL; + grp = ctl->grp; memset(&ev, 0, sizeof(ev)); ev.id.numid = ctl->info.id.numid; - ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev); if (ret < 0) return ret; @@ -641,6 +877,7 @@ int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id) */ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count) { + struct mixer_ctl_group *grp; struct snd_ctl_elem_value ev; int ret = 0; size_t size; @@ -650,6 +887,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count) if (!ctl || !count || !array) return -EINVAL; + grp = ctl->grp; total_count = ctl->info.count; if ((ctl->info.type == SNDRV_CTL_ELEM_TYPE_BYTES) && @@ -667,7 +905,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count) 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); + ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev); if (ret < 0) return ret; size = sizeof(ev.value.integer.value[0]); @@ -687,7 +925,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count) return -ENOMEM; tlv->numid = ctl->info.id.numid; tlv->length = count; - ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_READ, tlv); + ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_READ, tlv); source = tlv->tlv; memcpy(array, source, count); @@ -696,7 +934,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count) return ret; } else { - ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev); if (ret < 0) return ret; size = sizeof(ev.value.bytes.data[0]); @@ -725,15 +963,17 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count) */ 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)) return -EINVAL; + grp = ctl->grp; memset(&ev, 0, sizeof(ev)); ev.id.numid = ctl->info.id.numid; - ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev); if (ret < 0) return ret; @@ -763,7 +1003,7 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value) return -EINVAL; } - return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); + return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); } /** Sets the contents of a control's value array. @@ -778,6 +1018,7 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value) */ 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; @@ -786,6 +1027,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) if ((!ctl) || !count || !array) return -EINVAL; + grp = ctl->grp; total_count = ctl->info.count; if ((ctl->info.type == SNDRV_CTL_ELEM_TYPE_BYTES) && @@ -821,7 +1063,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) tlv->length = count; memcpy(tlv->tlv, array, count); - ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_WRITE, tlv); + ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_WRITE, tlv); free(tlv); return ret; @@ -837,7 +1079,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) memcpy(dest, array, size * count); - return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); + return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); } /** Gets the minimum value of an control. @@ -887,6 +1129,7 @@ unsigned int mixer_ctl_get_num_enums(const struct mixer_ctl *ctl) 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; @@ -902,7 +1145,7 @@ int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl) memset(&tmp, 0, sizeof(tmp)); tmp.id.numid = ctl->info.id.numid; tmp.value.enumerated.item = m; - if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0) + 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]) @@ -949,6 +1192,7 @@ const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl, */ 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; @@ -957,13 +1201,14 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string) mixer_ctl_fill_enum_string(ctl) != 0) return -EINVAL; + grp = ctl->grp; 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); + ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); if (ret < 0) return ret; return 0; @@ -972,4 +1217,3 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string) return -EINVAL; } - diff --git a/src/mixer_hw.c b/src/mixer_hw.c new file mode 100644 index 0000000..2e86dfa --- /dev/null +++ b/src/mixer_hw.c @@ -0,0 +1,112 @@ +/* mixer_hw.c +** Copyright (c) 2019, The Linux Foundation. +** +** 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 Linux Foundation 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 "AS IS" AND ANY EXPRESS OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +** 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 <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdbool.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> +#include <sound/asound.h> + +#include "mixer_io.h" + +/** Store the hardware (kernel interface) mixer data */ +struct mixer_hw_data { + /* Card number for the mixer */ + unsigned int card; + /* File descriptor of the mixer device node */ + int fd; +}; + +static void mixer_hw_close(void *data) +{ + struct mixer_hw_data *hw_data = data; + + if (!hw_data) + return; + + if (hw_data->fd >= 0) + close(hw_data->fd); + + hw_data->fd = -1; + free(hw_data); + hw_data = NULL; +} + +static int mixer_hw_ioctl(void *data, unsigned int cmd, ...) +{ + struct mixer_hw_data *hw_data = data; + va_list ap; + void *arg; + + va_start(ap, cmd); + arg = va_arg(ap, void *); + va_end(ap); + + return ioctl(hw_data->fd, cmd, arg); +} + +static const struct mixer_ops mixer_hw_ops = { + .close = mixer_hw_close, + .ioctl = mixer_hw_ioctl, +}; + +int mixer_hw_open(unsigned int card, void **data, + const struct mixer_ops **ops) +{ + struct mixer_hw_data *hw_data; + int fd; + char fn[256]; + + snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card); + fd = open(fn, O_RDWR); + if (fd < 0) + return fd; + + hw_data = calloc(1, sizeof(*hw_data)); + if (!hw_data) + return -1; + + hw_data->card = card; + hw_data->fd = fd; + *data = hw_data; + *ops = &mixer_hw_ops; + + return fd; +} diff --git a/src/mixer_io.h b/src/mixer_io.h new file mode 100644 index 0000000..bb3bc44 --- /dev/null +++ b/src/mixer_io.h @@ -0,0 +1,51 @@ +/* mixer_io.h +** Copyright (c) 2019, The Linux Foundation. +** +** 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 Linux Foundation 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 "AS IS" AND ANY EXPRESS OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +** 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. +**/ + +#ifndef TINYALSA_SRC_MIXER_H +#define TINYALSA_SRC_MIXER_H + +#include <stdio.h> +#include <stdlib.h> +#include <sound/asound.h> + +struct mixer_ops; + +int mixer_hw_open(unsigned int card, void **data, + const struct mixer_ops **ops); +int mixer_plugin_open(unsigned int card, void **data, + const struct mixer_ops **ops); + +struct mixer_ops { + void (*close) (void *data); + int (*get_poll_fd) (void *data, struct pollfd *pfd, int count); + ssize_t (*read_event) (void *data, struct snd_ctl_event *ev, size_t size); + int (*ioctl) (void *data, unsigned int cmd, ...); +}; + +#endif /* TINYALSA_SRC_MIXER_H */ diff --git a/src/mixer_plugin.c b/src/mixer_plugin.c new file mode 100644 index 0000000..3437171 --- /dev/null +++ b/src/mixer_plugin.c @@ -0,0 +1,480 @@ +/* mixer_plugin.c +** Copyright (c) 2019, The Linux Foundation. +** +** 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 Linux Foundation 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 "AS IS" AND ANY EXPRESS OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +** 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 <tinyalsa/plugin.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <poll.h> +#include <dlfcn.h> +#include <string.h> +#include <sys/eventfd.h> +#include <sys/ioctl.h> + +#include <linux/ioctl.h> +#include <sound/asound.h> + +#include "snd_card_plugin.h" +#include "mixer_io.h" + +/** Encapulates the mixer plugin specific data */ +struct mixer_plug_data { + /** Card number associated with the plugin */ + int card; + /** Device node for mixer */ + void *mixer_node; + /** Pointer to the plugin's ops */ + const struct mixer_plugin_ops *ops; + /** Pointer to plugin responsible to service the controls */ + struct mixer_plugin *plugin; + /** Handle to the plugin library */ + void *dl_hdl; +}; + +static int mixer_plug_get_elem_id(struct mixer_plug_data *plug_data, + struct snd_ctl_elem_id *id, unsigned int offset) +{ + struct mixer_plugin *plugin = plug_data->plugin; + struct snd_control *ctl; + + if (offset >= plugin->num_controls) { + fprintf(stderr, "%s: invalid offset %u\n", + __func__, offset); + return -EINVAL; + } + + ctl = plugin->controls + offset; + id->numid = offset; + id->iface = ctl->iface; + + strncpy((char *)id->name, (char *)ctl->name, + sizeof(id->name)); + + return 0; +} + +static int mixer_plug_info_enum(struct snd_control *ctl, + struct snd_ctl_elem_info *einfo) +{ + struct snd_value_enum *val = ctl->value; + + einfo->count = 1; + einfo->value.enumerated.items = val->items; + + if (einfo->value.enumerated.item > val->items) + return -EINVAL; + + strncpy(einfo->value.enumerated.name, + val->texts[einfo->value.enumerated.item], + sizeof(einfo->value.enumerated.name)); + + return 0; +} + +static int mixer_plug_info_bytes(struct snd_control *ctl, + struct snd_ctl_elem_info *einfo) +{ + struct snd_value_bytes *val; + struct snd_value_tlv_bytes *val_tlv; + + if (ctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) { + val_tlv = ctl->value; + einfo->count = val_tlv->size; + } else { + val = ctl->value; + einfo->count = val->size; + } + + return 0; +} + +static int mixer_plug_info_integer(struct snd_control *ctl, + struct snd_ctl_elem_info *einfo) +{ + struct snd_value_int *val = ctl->value; + + einfo->count = val->count; + einfo->value.integer.min = val->min; + einfo->value.integer.max = val->max; + einfo->value.integer.step = val->step; + + return 0; +} + +void mixer_plug_notifier_cb(struct mixer_plugin *plugin) +{ + eventfd_write(plugin->eventfd, 1); + plugin->event_cnt++; +} + +/* In consume_event/read, do not call eventfd_read until all events are read from list. + This will make poll getting unblocked until all events are read */ +static ssize_t mixer_plug_read_event(void *data, struct snd_ctl_event *ev, size_t size) +{ + struct mixer_plug_data *plug_data = data; + struct mixer_plugin *plugin = plug_data->plugin; + eventfd_t evfd; + ssize_t result = 0; + + result = plug_data->ops->read_event(plugin, ev, size); + + if (result > 0) { + plugin->event_cnt -= result / sizeof(struct snd_ctl_event); + if (plugin->event_cnt == 0) + eventfd_read(plugin->eventfd, &evfd); + } + + return result; +} + +static int mixer_plug_subscribe_events(struct mixer_plug_data *plug_data, + int *subscribe) +{ + struct mixer_plugin *plugin = plug_data->plugin; + eventfd_t evfd; + + if (*subscribe < 0 || *subscribe > 1) { + *subscribe = plugin->subscribed; + return -EINVAL; + } + + if (*subscribe && !plugin->subscribed) { + plug_data->ops->subscribe_events(plugin, &mixer_plug_notifier_cb); + } else if (plugin->subscribed && !*subscribe) { + plug_data->ops->subscribe_events(plugin, NULL); + + if (plugin->event_cnt) + eventfd_read(plugin->eventfd, &evfd); + + plugin->event_cnt = 0; + } + + plugin->subscribed = *subscribe; + return 0; +} + +static int mixer_plug_get_poll_fd(void *data, struct pollfd *pfd, int count) +{ + struct mixer_plug_data *plug_data = data; + struct mixer_plugin *plugin = plug_data->plugin; + + if (plugin->eventfd != -1) { + pfd[count].fd = plugin->eventfd; + return 0; + } + return -ENODEV; +} + +static int mixer_plug_tlv_write(struct mixer_plug_data *plug_data, + struct snd_ctl_tlv *tlv) +{ + struct mixer_plugin *plugin = plug_data->plugin; + struct snd_control *ctl; + struct snd_value_tlv_bytes *val_tlv; + + ctl = plugin->controls + tlv->numid; + val_tlv = ctl->value; + + return val_tlv->put(plugin, ctl, tlv); +} + +static int mixer_plug_tlv_read(struct mixer_plug_data *plug_data, + struct snd_ctl_tlv *tlv) +{ + struct mixer_plugin *plugin = plug_data->plugin; + struct snd_control *ctl; + struct snd_value_tlv_bytes *val_tlv; + + ctl = plugin->controls + tlv->numid; + val_tlv = ctl->value; + + return val_tlv->get(plugin, ctl, tlv); +} + +static int mixer_plug_elem_write(struct mixer_plug_data *plug_data, + struct snd_ctl_elem_value *ev) +{ + struct mixer_plugin *plugin = plug_data->plugin; + struct snd_control *ctl; + int ret; + + ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid); + if (ret < 0) + return ret; + + ctl = plugin->controls + ev->id.numid; + + return ctl->put(plugin, ctl, ev); +} + +static int mixer_plug_elem_read(struct mixer_plug_data *plug_data, + struct snd_ctl_elem_value *ev) +{ + struct mixer_plugin *plugin = plug_data->plugin; + struct snd_control *ctl; + int ret; + + ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid); + if (ret < 0) + return ret; + + ctl = plugin->controls + ev->id.numid; + + return ctl->get(plugin, ctl, ev); + +} + +static int mixer_plug_get_elem_info(struct mixer_plug_data *plug_data, + struct snd_ctl_elem_info *einfo) +{ + struct mixer_plugin *plugin = plug_data->plugin; + struct snd_control *ctl; + int ret; + + ret = mixer_plug_get_elem_id(plug_data, &einfo->id, + einfo->id.numid); + if (ret < 0) + return ret; + + ctl = plugin->controls + einfo->id.numid; + einfo->type = ctl->type; + einfo->access = ctl->access; + + switch (einfo->type) { + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + ret = mixer_plug_info_enum(ctl, einfo); + if (ret < 0) + return ret; + break; + case SNDRV_CTL_ELEM_TYPE_BYTES: + ret = mixer_plug_info_bytes(ctl, einfo); + if (ret < 0) + return ret; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER: + ret = mixer_plug_info_integer(ctl, einfo); + if (ret < 0) + return ret; + break; + default: + fprintf(stderr,"%s: unknown type %d\n", + __func__, einfo->type); + return -EINVAL; + } + + return 0; +} + +static int mixer_plug_get_elem_list(struct mixer_plug_data *plug_data, + struct snd_ctl_elem_list *elist) +{ + struct mixer_plugin *plugin = plug_data->plugin; + unsigned int avail; + struct snd_ctl_elem_id *id; + int ret; + + elist->count = plugin->num_controls; + elist->used = 0; + avail = elist->space; + + while (avail > 0) { + id = elist->pids + elist->used; + ret = mixer_plug_get_elem_id(plug_data, id, elist->used); + if (ret < 0) + return ret; + + avail--; + elist->used++; + } + + return 0; +} + +static int mixer_plug_get_card_info(struct mixer_plug_data *plug_data, + struct snd_ctl_card_info *card_info) +{ + /*TODO: Fill card_info here from snd-card-def */ + memset(card_info, 0, sizeof(*card_info)); + card_info->card = plug_data->card; + memcpy(card_info->id, "card_id", sizeof(card_info->id)); + memcpy(card_info->driver, "mymixer-so-name", sizeof(card_info->driver)); + memcpy(card_info->name, "card-name", sizeof(card_info->name)); + memcpy(card_info->longname, "card-name", sizeof(card_info->longname)); + memcpy(card_info->mixername, "mixer-name", sizeof(card_info->mixername)); + + return 0; +} + +static void mixer_plug_close(void *data) +{ + struct mixer_plug_data *plug_data = data; + struct mixer_plugin *plugin = plug_data->plugin; + eventfd_t evfd; + + if (plugin->event_cnt) + eventfd_read(plugin->eventfd, &evfd); + + plug_data->ops->close(&plugin); + dlclose(plug_data->dl_hdl); + + free(plug_data); + plug_data = NULL; +} + +static int mixer_plug_ioctl(void *data, unsigned int cmd, ...) +{ + struct mixer_plug_data *plug_data = data; + int ret; + va_list ap; + void *arg; + + va_start(ap, cmd); + arg = va_arg(ap, void *); + va_end(ap); + + switch (cmd) { + case SNDRV_CTL_IOCTL_CARD_INFO: + ret = mixer_plug_get_card_info(plug_data, arg); + break; + case SNDRV_CTL_IOCTL_ELEM_LIST: + ret = mixer_plug_get_elem_list(plug_data, arg); + break; + case SNDRV_CTL_IOCTL_ELEM_INFO: + ret = mixer_plug_get_elem_info(plug_data, arg); + break; + case SNDRV_CTL_IOCTL_ELEM_READ: + ret = mixer_plug_elem_read(plug_data, arg); + break; + case SNDRV_CTL_IOCTL_ELEM_WRITE: + ret = mixer_plug_elem_write(plug_data, arg); + break; + case SNDRV_CTL_IOCTL_TLV_READ: + ret = mixer_plug_tlv_read(plug_data, arg); + break; + case SNDRV_CTL_IOCTL_TLV_WRITE: + ret = mixer_plug_tlv_write(plug_data, arg); + break; + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + ret = mixer_plug_subscribe_events(plug_data, arg); + break; + default: + /* TODO: plugin should support ioctl */ + ret = -EFAULT; + break; + } + + return ret; +} + +static const struct mixer_ops mixer_plug_ops = { + .close = mixer_plug_close, + .read_event = mixer_plug_read_event, + .get_poll_fd = mixer_plug_get_poll_fd, + .ioctl = mixer_plug_ioctl, +}; + +int mixer_plugin_open(unsigned int card, void **data, + const struct mixer_ops **ops) +{ + struct mixer_plug_data *plug_data; + struct mixer_plugin *plugin = NULL; + void *dl_hdl; + char *so_name; + int ret; + + plug_data = calloc(1, sizeof(*plug_data)); + if (!plug_data) + return -ENOMEM; + + plug_data->mixer_node = snd_utils_open_mixer(card); + if (!plug_data->mixer_node) { + /* Do not print error here. + * It is valid for card to not have virtual mixer node + */ + goto err_get_mixer_node; + } + + ret = snd_utils_get_str(plug_data->mixer_node, "so-name", + &so_name); + if(ret) { + fprintf(stderr, "%s: mixer so-name not found for card %u\n", + __func__, card); + goto err_get_lib_name; + + } + + dl_hdl = dlopen(so_name, RTLD_NOW); + if (!dl_hdl) { + fprintf(stderr, "%s: unable to open %s\n", + __func__, so_name); + goto err_dlopen; + } + + dlerror(); + plug_data->ops = dlsym(dl_hdl, "mixer_plugin_ops"); + if (!plug_data->ops) { + fprintf(stderr, "%s: dlsym open fn failed: %s\n", + __func__, dlerror()); + goto err_ops; + } + + ret = plug_data->ops->open(&plugin, card); + if (ret) { + fprintf(stderr, "%s: failed to open plugin, err: %d\n", + __func__, ret); + goto err_ops; + } + + plug_data->plugin = plugin; + plug_data->card = card; + plug_data->dl_hdl = dl_hdl; + plugin->eventfd = eventfd(0, 0); + + *data = plug_data; + *ops = &mixer_plug_ops; + + return 0; + +err_ops: + dlclose(dl_hdl); +err_dlopen: +err_get_lib_name: + snd_utils_close_dev_node(plug_data->mixer_node); +err_get_mixer_node: + free(plug_data); + return -1; +} |