diff options
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; +} |