diff --git a/configure.ac b/configure.ac index bcbbd640..515048ae 100644 --- a/configure.ac +++ b/configure.ac @@ -659,7 +659,7 @@ AC_ARG_WITH(ctl-plugins, [build control plugins (default = all)]), [ctl_plugins="$withval"], [ctl_plugins="all"]) -CTL_PLUGIN_LIST="shm ext" +CTL_PLUGIN_LIST="remap shm ext" build_ctl_plugin="no" for t in $CTL_PLUGIN_LIST; do @@ -681,6 +681,7 @@ if test "$ac_cv_header_sys_shm_h" != "yes"; then fi AM_CONDITIONAL([BUILD_CTL_PLUGIN], [test x$build_ctl_plugin = xyes]) +AM_CONDITIONAL([BUILD_CTL_PLUGIN_REMAP], [test x$build_ctl_remap = xyes]) AM_CONDITIONAL([BUILD_CTL_PLUGIN_SHM], [test x$build_ctl_shm = xyes]) AM_CONDITIONAL([BUILD_CTL_PLUGIN_EXT], [test x$build_ctl_ext = xyes]) diff --git a/include/control.h b/include/control.h index 386fbd7d..ccf906e2 100644 --- a/include/control.h +++ b/include/control.h @@ -312,7 +312,9 @@ typedef enum _snd_ctl_type { /** INET client CTL (not yet implemented) */ SND_CTL_TYPE_INET, /** External control plugin */ - SND_CTL_TYPE_EXT + SND_CTL_TYPE_EXT, + /** Control functionality remapping */ + SND_CTL_TYPE_REMAP, } snd_ctl_type_t; /** Non blocking mode (flag for open mode) \hideinitializer */ diff --git a/src/control/Makefile.am b/src/control/Makefile.am index 3d476a21..eefe013b 100644 --- a/src/control/Makefile.am +++ b/src/control/Makefile.am @@ -3,6 +3,9 @@ EXTRA_LTLIBRARIES = libcontrol.la libcontrol_la_SOURCES = cards.c tlv.c namehint.c hcontrol.c \ control.c control_hw.c setup.c ctlparse.c \ control_symbols.c +if BUILD_CTL_PLUGIN_REMAP +libcontrol_la_SOURCES += control_remap.c +endif if BUILD_CTL_PLUGIN_SHM libcontrol_la_SOURCES += control_shm.c endif diff --git a/src/control/control.c b/src/control/control.c index 08e8d346..1968d5a8 100644 --- a/src/control/control.c +++ b/src/control/control.c @@ -1345,7 +1345,7 @@ snd_ctl_t *snd_async_handler_get_ctl(snd_async_handler_t *handler) } static const char *const build_in_ctls[] = { - "hw", "shm", NULL + "hw", "remap", "shm", NULL }; static int snd_ctl_open_conf(snd_ctl_t **ctlp, const char *name, diff --git a/src/control/control_remap.c b/src/control/control_remap.c new file mode 100644 index 00000000..097009ea --- /dev/null +++ b/src/control/control_remap.c @@ -0,0 +1,1226 @@ +/* + * Control - Remap Controls + * Copyright (c) 2021 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include "control_local.h" + +#if 0 +#define REMAP_DEBUG 1 +#define debug(format, args...) fprintf(stderr, format, ##args) +#define debug_id(id, format, args...) do { \ + char *s = snd_ctl_ascii_elem_id_get(id); \ + fprintf(stderr, "%s: ", s); free(s); \ + fprintf(stderr, format, ##args); \ +} while (0) +#else +#define REMAP_DEBUG 0 +#define debug(format, args...) do { } while (0) +#define debug_id(id, format, args...) do { } while (0) +#endif + +#define EREMAPNOTFOUND (888899) + +#ifndef PIC +/* entry for static linking */ +const char *_snd_module_control_remap = ""; +#endif + +#ifndef DOC_HIDDEN +typedef struct { + unsigned int numid_child; + unsigned int numid_app; +} snd_ctl_numid_t; + +typedef struct { + snd_ctl_elem_id_t id_child; + snd_ctl_elem_id_t id_app; +} snd_ctl_remap_id_t; + +typedef struct { + snd_ctl_elem_id_t map_id; + snd_ctl_elem_type_t type; + size_t controls_items; + size_t controls_alloc; + struct snd_ctl_map_ctl { + snd_ctl_elem_id_t id_child; + size_t channel_map_items; + size_t channel_map_alloc; + long *channel_map; + } *controls; + unsigned int event_mask; +} snd_ctl_map_t; + +typedef struct { + snd_ctl_t *child; + int numid_remap_active; + unsigned int numid_app_last; + size_t numid_items; + size_t numid_alloc; + snd_ctl_numid_t *numid; + snd_ctl_numid_t numid_temp; + size_t remap_items; + size_t remap_alloc; + snd_ctl_remap_id_t *remap; + size_t map_items; + size_t map_alloc; + snd_ctl_map_t *map; + size_t map_read_queue_head; + size_t map_read_queue_tail; + snd_ctl_map_t **map_read_queue; +} snd_ctl_remap_t; +#endif + +static snd_ctl_numid_t *remap_numid_temp(snd_ctl_remap_t *priv, unsigned int numid) +{ + priv->numid_temp.numid_child = numid; + priv->numid_temp.numid_app = numid; + return &priv->numid_temp; +} + +static snd_ctl_numid_t *remap_find_numid_app(snd_ctl_remap_t *priv, unsigned int numid_app) +{ + snd_ctl_numid_t *numid; + size_t count; + + if (!priv->numid_remap_active) + return remap_numid_temp(priv, numid_app); + numid = priv->numid; + for (count = priv->numid_items; count > 0; count--, numid++) + if (numid_app == numid->numid_app) + return numid; + return NULL; +} + +static snd_ctl_numid_t *remap_numid_new(snd_ctl_remap_t *priv, unsigned int numid_child, + unsigned int numid_app) +{ + snd_ctl_numid_t *numid; + + if (priv->numid_alloc == priv->numid_items) { + numid = realloc(priv->numid, (priv->numid_alloc + 16) * sizeof(*numid)); + if (numid == NULL) + return NULL; + memset(numid + priv->numid_alloc, 0, sizeof(*numid) * 16); + priv->numid_alloc += 16; + priv->numid = numid; + } + numid = &priv->numid[priv->numid_items++]; + numid->numid_child = numid_child; + numid->numid_app = numid_app; + debug("new numid: child %u app %u\n", numid->numid_child, numid->numid_app); + return numid; +} + +static snd_ctl_numid_t *remap_numid_child_new(snd_ctl_remap_t *priv, unsigned int numid_child) +{ + unsigned int numid_app; + + if (numid_child == 0) + return NULL; + if (remap_find_numid_app(priv, numid_child)) { + while (remap_find_numid_app(priv, priv->numid_app_last)) + priv->numid_app_last++; + numid_app = priv->numid_app_last; + } else { + numid_app = numid_child; + } + return remap_numid_new(priv, numid_child, numid_app); +} + +static snd_ctl_numid_t *remap_find_numid_child(snd_ctl_remap_t *priv, unsigned int numid_child) +{ + snd_ctl_numid_t *numid; + size_t count; + + if (!priv->numid_remap_active) + return remap_numid_temp(priv, numid_child); + numid = priv->numid; + for (count = priv->numid_items; count > 0; count--, numid++) + if (numid_child == numid->numid_child) + return numid; + return remap_numid_child_new(priv, numid_child); +} + +static snd_ctl_remap_id_t *remap_find_id_child(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id) +{ + size_t count; + snd_ctl_remap_id_t *rid; + + if (id->numid > 0) { + rid = priv->remap; + for (count = priv->remap_items; count > 0; count--, rid++) + if (id->numid == rid->id_child.numid) + return rid; + } + rid = priv->remap; + for (count = priv->remap_items; count > 0; count--, rid++) + if (snd_ctl_elem_id_compare_set(id, &rid->id_child) == 0) + return rid; + return NULL; +} + +static snd_ctl_remap_id_t *remap_find_id_app(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id) +{ + size_t count; + snd_ctl_remap_id_t *rid; + + if (id->numid > 0) { + rid = priv->remap; + for (count = priv->remap_items; count > 0; count--, rid++) + if (id->numid == rid->id_app.numid) + return rid; + } + rid = priv->remap; + for (count = priv->remap_items; count > 0; count--, rid++) + if (snd_ctl_elem_id_compare_set(id, &rid->id_app) == 0) + return rid; + return NULL; +} + +static snd_ctl_map_t *remap_find_map_numid(snd_ctl_remap_t *priv, unsigned int numid) +{ + size_t count; + snd_ctl_map_t *map; + + if (numid == 0) + return NULL; + map = priv->map; + for (count = priv->map_items; count > 0; count--, map++) { + if (numid == map->map_id.numid) + return map; + } + return NULL; +} +static snd_ctl_map_t *remap_find_map_id(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id) +{ + size_t count; + snd_ctl_map_t *map; + + if (id->numid > 0) + return remap_find_map_numid(priv, id->numid); + map = priv->map; + for (count = priv->map_items; count > 0; count--, map++) + if (snd_ctl_elem_id_compare_set(id, &map->map_id) == 0) + return map; + return NULL; +} + +static int remap_id_to_child(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id, snd_ctl_remap_id_t **_rid) +{ + snd_ctl_remap_id_t *rid; + snd_ctl_numid_t *numid; + + debug_id(id, "%s enter\n", __func__); + rid = remap_find_id_app(priv, id); + if (rid) { + if (rid->id_app.numid == 0) { + numid = remap_find_numid_app(priv, id->numid); + if (numid) { + rid->id_child.numid = numid->numid_child; + rid->id_app.numid = numid->numid_app; + } + } + *id = rid->id_child; + } else { + numid = remap_find_numid_app(priv, id->numid); + if (numid) + id->numid = numid->numid_child; + else + id->numid = 0; + } + *_rid = rid; + debug_id(id, "%s leave\n", __func__); + return 0; +} + +static int remap_id_to_app(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id, snd_ctl_remap_id_t *rid, int err) +{ + snd_ctl_numid_t *numid; + + if (rid) { + if (err >= 0 && rid->id_app.numid == 0) { + numid = remap_numid_child_new(priv, id->numid); + if (numid == NULL) + return -EIO; + rid->id_child.numid = numid->numid_child; + rid->id_app.numid = numid->numid_app; + } + *id = rid->id_app; + } else { + if (err >= 0) { + numid = remap_find_numid_child(priv, id->numid); + if (numid == NULL) + return -EIO; + id->numid = numid->numid_app; + } + } + return err; +} + +static void remap_free(snd_ctl_remap_t *priv) +{ + size_t idx1, idx2; + snd_ctl_map_t *map; + + for (idx1 = 0; idx1 < priv->map_items; idx1++) { + map = &priv->map[idx1]; + for (idx2 = 0; idx2 < map->controls_items; idx2++) + free(map->controls[idx2].channel_map); + free(map->controls); + } + free(priv->map_read_queue); + free(priv->map); + free(priv->remap); + free(priv->numid); + free(priv); +} + +static int snd_ctl_remap_close(snd_ctl_t *ctl) +{ + snd_ctl_remap_t *priv = ctl->private_data; + int err = snd_ctl_close(priv->child); + remap_free(priv); + return err; +} + +static int snd_ctl_remap_nonblock(snd_ctl_t *ctl, int nonblock) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_nonblock(priv->child, nonblock); +} + +static int snd_ctl_remap_async(snd_ctl_t *ctl, int sig, pid_t pid) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_remap_async(priv->child, sig, pid); +} + +static int snd_ctl_remap_subscribe_events(snd_ctl_t *ctl, int subscribe) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_subscribe_events(priv->child, subscribe); +} + +static int snd_ctl_remap_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_card_info(priv->child, info); +} + +static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list) +{ + snd_ctl_remap_t *priv = ctl->private_data; + snd_ctl_elem_id_t *id; + snd_ctl_remap_id_t *rid; + snd_ctl_numid_t *numid; + snd_ctl_map_t *map; + unsigned int index; + size_t index2; + int err; + + err = snd_ctl_elem_list(priv->child, list); + if (err < 0) + return err; + for (index = 0; index < list->used; index++) { + id = &list->pids[index]; + rid = remap_find_id_child(priv, id); + if (rid) { + rid->id_app.numid = id->numid; + *id = rid->id_app; + } + numid = remap_find_numid_child(priv, id->numid); + if (numid == NULL) + return -EIO; + id->numid = numid->numid_app; + } + if (list->offset >= list->count + priv->map_items) + return 0; + index2 = 0; + if (list->offset > list->count) + index2 = list->offset - list->count; + for ( ; index < list->space && index2 < priv->map_items; index2++, index++) { + id = &list->pids[index]; + map = &priv->map[index2]; + *id = map->map_id; + list->used++; + } + list->count += priv->map_items; + return 0; +} + +#define ACCESS_BITS(bits) \ + (bits & (SNDRV_CTL_ELEM_ACCESS_READWRITE|\ + SNDRV_CTL_ELEM_ACCESS_VOLATILE|\ + SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)) + +static int remap_map_elem_info(snd_ctl_remap_t *priv, snd_ctl_elem_info_t *info) +{ + snd_ctl_map_t *map; + snd_ctl_elem_info_t info2, info3; + size_t item; + unsigned int access; + size_t count; + int owner, err; + + map = remap_find_map_id(priv, &info->id); + if (map == NULL) + return -EREMAPNOTFOUND; + debug_id(&info->id, "%s\n", __func__); + assert(map->controls_items > 0); + snd_ctl_elem_info_clear(&info2); + info2.id = map->controls[0].id_child; + debug_id(&info2.id, "%s controls[0]\n", __func__); + err = snd_ctl_elem_info(priv->child, &info2); + if (err < 0) + return err; + if (info2.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN && + info2.type != SNDRV_CTL_ELEM_TYPE_INTEGER && + info2.type != SNDRV_CTL_ELEM_TYPE_INTEGER64 && + info2.type != SNDRV_CTL_ELEM_TYPE_BYTES) + return -EIO; + map->controls[0].id_child.numid = info2.id.numid; + map->type = info2.type; + access = info2.access; + owner = info2.owner; + count = map->controls[0].channel_map_items; + for (item = 1; item < map->controls_items; item++) { + snd_ctl_elem_info_clear(&info3); + info3.id = map->controls[item].id_child; + debug_id(&info3.id, "%s controls[%zd]\n", __func__, item); + err = snd_ctl_elem_info(priv->child, &info3); + if (err < 0) + return err; + if (info2.type != info3.type) + return -EIO; + if (ACCESS_BITS(info2.access) != ACCESS_BITS(info3.access)) + return -EIO; + if (info2.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + info2.type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + if (memcmp(&info2.value.integer, &info3.value.integer, sizeof(info2.value.integer))) + return -EIO; + } else if (info2.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) { + if (memcmp(&info2.value.integer64, &info3.value.integer64, sizeof(info2.value.integer64))) + return -EIO; + } + access |= info3.access; + if (owner == 0) + owner = info3.owner; + if (count < map->controls[item].channel_map_items) + count = map->controls[item].channel_map_items; + } + snd_ctl_elem_info_clear(info); + info->id = map->map_id; + info->type = info2.type; + info->access = access; + info->count = count; + if (info2.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + info2.type == SNDRV_CTL_ELEM_TYPE_INTEGER) + info->value.integer = info2.value.integer; + else if (info2.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) + info->value.integer64 = info2.value.integer64; + if (access & SNDRV_CTL_ELEM_ACCESS_LOCK) + info->owner = owner; + return 0; +} + +static int snd_ctl_remap_elem_info(snd_ctl_t *ctl, snd_ctl_elem_info_t *info) +{ + snd_ctl_remap_t *priv = ctl->private_data; + snd_ctl_remap_id_t *rid; + int err; + + debug_id(&info->id, "%s\n", __func__); + err = remap_map_elem_info(priv, info); + if (err != -EREMAPNOTFOUND) + return err; + err = remap_id_to_child(priv, &info->id, &rid); + if (err < 0) + return err; + err = snd_ctl_elem_info(priv->child, info); + return remap_id_to_app(priv, &info->id, rid, err); +} + +static int remap_map_elem_read(snd_ctl_remap_t *priv, snd_ctl_elem_value_t *control) +{ + snd_ctl_map_t *map; + struct snd_ctl_map_ctl *mctl; + snd_ctl_elem_value_t control2; + size_t item, index; + int err; + + map = remap_find_map_id(priv, &control->id); + if (map == NULL) + return -EREMAPNOTFOUND; + debug_id(&control->id, "%s\n", __func__); + snd_ctl_elem_value_clear(control); + control->id = map->map_id; + for (item = 0; item < map->controls_items; item++) { + mctl = &map->controls[item]; + snd_ctl_elem_value_clear(&control2); + control2.id = mctl->id_child; + debug_id(&control2.id, "%s controls[%zd]\n", __func__, item); + err = snd_ctl_elem_read(priv->child, &control2); + if (err < 0) + return err; + if (map->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + map->type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (index = 0; index < mctl->channel_map_items; index++) { + long src = mctl->channel_map[index]; + if ((unsigned long)src < ARRAY_SIZE(control->value.integer.value)) + control->value.integer.value[index] = control2.value.integer.value[src]; + } + } else if (map->type == SNDRV_CTL_ELEM_TYPE_INTEGER64) { + for (index = 0; index < mctl->channel_map_items; index++) { + long src = mctl->channel_map[index]; + if ((unsigned long)src < ARRAY_SIZE(control->value.integer64.value)) + control->value.integer64.value[index] = control2.value.integer64.value[src]; + } + } else if (map->type == SNDRV_CTL_ELEM_TYPE_BYTES) { + for (index = 0; index < mctl->channel_map_items; index++) { + long src = mctl->channel_map[index]; + if ((unsigned long)src < ARRAY_SIZE(control->value.bytes.data)) + control->value.bytes.data[index] = control2.value.bytes.data[src]; + } + } + } + return 0; +} + +static int snd_ctl_remap_elem_read(snd_ctl_t *ctl, snd_ctl_elem_value_t *control) +{ + snd_ctl_remap_t *priv = ctl->private_data; + snd_ctl_remap_id_t *rid; + int err; + + debug_id(&control->id, "%s\n", __func__); + err = remap_map_elem_read(priv, control); + if (err != -EREMAPNOTFOUND) + return err; + err = remap_id_to_child(priv, &control->id, &rid); + if (err < 0) + return err; + err = snd_ctl_elem_read(priv->child, control); + return remap_id_to_app(priv, &control->id, rid, err); +} + +static int remap_map_elem_write(snd_ctl_remap_t *priv, snd_ctl_elem_value_t *control) +{ + snd_ctl_map_t *map; + struct snd_ctl_map_ctl *mctl; + snd_ctl_elem_value_t control2; + size_t item, index; + int err, changes; + + map = remap_find_map_id(priv, &control->id); + if (map == NULL) + return -EREMAPNOTFOUND; + debug_id(&control->id, "%s\n", __func__); + control->id = map->map_id; + for (item = 0; item < map->controls_items; item++) { + mctl = &map->controls[item]; + snd_ctl_elem_value_clear(&control2); + control2.id = mctl->id_child; + debug_id(&control2.id, "%s controls[%zd]\n", __func__, item); + err = snd_ctl_elem_read(priv->child, &control2); + if (err < 0) + return err; + changes = 0; + if (map->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + map->type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (index = 0; index < mctl->channel_map_items; index++) { + long dst = mctl->channel_map[index]; + if ((unsigned long)dst < ARRAY_SIZE(control->value.integer.value)) { + changes |= control2.value.integer.value[dst] != control->value.integer.value[index]; + control2.value.integer.value[dst] = control->value.integer.value[index]; + } + } + } else if (map->type == SNDRV_CTL_ELEM_TYPE_INTEGER64) { + for (index = 0; index < mctl->channel_map_items; index++) { + long dst = mctl->channel_map[index]; + if ((unsigned long)dst < ARRAY_SIZE(control->value.integer64.value)) { + changes |= control2.value.integer64.value[dst] != control->value.integer64.value[index]; + control2.value.integer64.value[dst] = control->value.integer64.value[index]; + } + } + } else if (map->type == SNDRV_CTL_ELEM_TYPE_BYTES) { + for (index = 0; index < mctl->channel_map_items; index++) { + long dst = mctl->channel_map[index]; + if ((unsigned long)dst < ARRAY_SIZE(control->value.bytes.data)) { + changes |= control2.value.bytes.data[dst] != control->value.bytes.data[index]; + control2.value.bytes.data[dst] = control->value.bytes.data[index]; + } + } + } + debug_id(&control2.id, "%s changes %d\n", __func__, changes); + if (changes > 0) { + err = snd_ctl_elem_write(priv->child, &control2); + if (err < 0) + return err; + } + } + return 0; +} + +static int snd_ctl_remap_elem_write(snd_ctl_t *ctl, snd_ctl_elem_value_t *control) +{ + snd_ctl_remap_t *priv = ctl->private_data; + snd_ctl_remap_id_t *rid; + int err; + + debug_id(&control->id, "%s\n", __func__); + err = remap_map_elem_write(priv, control); + if (err != -EREMAPNOTFOUND) + return err; + err = remap_id_to_child(priv, &control->id, &rid); + if (err < 0) + return err; + err = snd_ctl_elem_write(priv->child, control); + return remap_id_to_app(priv, &control->id, rid, err); +} + +static int snd_ctl_remap_elem_lock(snd_ctl_t *ctl, snd_ctl_elem_id_t *id) +{ + snd_ctl_remap_t *priv = ctl->private_data; + snd_ctl_remap_id_t *rid; + int err; + + debug_id(id, "%s\n", __func__); + err = remap_id_to_child(priv, id, &rid); + if (err < 0) + return err; + err = snd_ctl_elem_lock(priv->child, id); + return remap_id_to_app(priv, id, rid, err); +} + +static int snd_ctl_remap_elem_unlock(snd_ctl_t *ctl, snd_ctl_elem_id_t *id) +{ + snd_ctl_remap_t *priv = ctl->private_data; + snd_ctl_remap_id_t *rid; + int err; + + debug_id(id, "%s\n", __func__); + err = remap_id_to_child(priv, id, &rid); + if (err < 0) + return err; + err = snd_ctl_elem_unlock(priv->child, id); + return remap_id_to_app(priv, id, rid, err); +} + +static int remap_get_map_numid(snd_ctl_remap_t *priv, struct snd_ctl_map_ctl *mctl) +{ + snd_ctl_elem_info_t info; + snd_ctl_numid_t *numid; + int err; + + if (mctl->id_child.numid > 0) + return 0; + debug_id(&mctl->id_child, "%s get numid\n", __func__); + snd_ctl_elem_info_clear(&info); + info.id = mctl->id_child; + err = snd_ctl_elem_info(priv->child, &info); + if (err < 0) + return err; + numid = remap_find_numid_child(priv, info.id.numid); + if (numid == NULL) + return -EIO; + mctl->id_child.numid = info.id.numid; + return 0; +} + +static int remap_map_elem_tlv(snd_ctl_remap_t *priv, int op_flag, unsigned int numid, + unsigned int *tlv, unsigned int tlv_size) +{ + snd_ctl_map_t *map; + struct snd_ctl_map_ctl *mctl; + size_t item; + unsigned int *tlv2; + int err; + + map = remap_find_map_numid(priv, numid); + if (map == NULL) + return -EREMAPNOTFOUND; + if (op_flag != 0) /* read only */ + return -ENXIO; + debug("%s numid %d\n", __func__, numid); + mctl = &map->controls[0]; + err = remap_get_map_numid(priv, mctl); + if (err < 0) + return err; + memset(tlv, 0, tlv_size); + err = priv->child->ops->element_tlv(priv->child, op_flag, mctl->id_child.numid, tlv, tlv_size); + if (err < 0) + return err; + tlv2 = malloc(tlv_size); + if (tlv2 == NULL) + return -ENOMEM; + for (item = 1; item < map->controls_items; item++) { + mctl = &map->controls[item]; + err = remap_get_map_numid(priv, mctl); + if (err < 0) { + free(tlv2); + return err; + } + memset(tlv2, 0, tlv_size); + err = priv->child->ops->element_tlv(priv->child, op_flag, mctl->id_child.numid, tlv2, tlv_size); + if (err < 0) { + free(tlv2); + return err; + } + if (memcmp(tlv, tlv2, tlv_size) != 0) { + free(tlv2); + return -EIO; + } + } + free(tlv2); + return 0; +} + +static int snd_ctl_remap_elem_tlv(snd_ctl_t *ctl, int op_flag, + unsigned int numid, + unsigned int *tlv, unsigned int tlv_size) +{ + snd_ctl_remap_t *priv = ctl->private_data; + snd_ctl_numid_t *map_numid; + int err; + + debug("%s: numid = %d, op_flag = %d\n", __func__, numid, op_flag); + err = remap_map_elem_tlv(priv, op_flag, numid, tlv, tlv_size); + if (err != -EREMAPNOTFOUND) + return err; + map_numid = remap_find_numid_app(priv, numid); + if (map_numid == NULL) + return -ENOENT; + return priv->child->ops->element_tlv(priv->child, op_flag, map_numid->numid_child, tlv, tlv_size); +} + +static int snd_ctl_remap_hwdep_next_device(snd_ctl_t *ctl, int * device) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_hwdep_next_device(priv->child, device); +} + +static int snd_ctl_remap_hwdep_info(snd_ctl_t *ctl, snd_hwdep_info_t * info) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_hwdep_info(priv->child, info); +} + +static int snd_ctl_remap_pcm_next_device(snd_ctl_t *ctl, int * device) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_pcm_next_device(priv->child, device); +} + +static int snd_ctl_remap_pcm_info(snd_ctl_t *ctl, snd_pcm_info_t * info) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_pcm_info(priv->child, info); +} + +static int snd_ctl_remap_pcm_prefer_subdevice(snd_ctl_t *ctl, int subdev) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_pcm_prefer_subdevice(priv->child, subdev); +} + +static int snd_ctl_remap_rawmidi_next_device(snd_ctl_t *ctl, int * device) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_rawmidi_next_device(priv->child, device); +} + +static int snd_ctl_remap_rawmidi_info(snd_ctl_t *ctl, snd_rawmidi_info_t * info) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_rawmidi_info(priv->child, info); +} + +static int snd_ctl_remap_rawmidi_prefer_subdevice(snd_ctl_t *ctl, int subdev) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_rawmidi_prefer_subdevice(priv->child, subdev); +} + +static int snd_ctl_remap_set_power_state(snd_ctl_t *ctl, unsigned int state) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_set_power_state(priv->child, state); +} + +static int snd_ctl_remap_get_power_state(snd_ctl_t *ctl, unsigned int *state) +{ + snd_ctl_remap_t *priv = ctl->private_data; + return snd_ctl_get_power_state(priv->child, state); +} + +static void _next_ptr(size_t *ptr, size_t count) +{ + *ptr = (*ptr + 1) % count; +} + +static void remap_event_for_all_map_controls(snd_ctl_remap_t *priv, + snd_ctl_elem_id_t *id, + unsigned int event_mask) +{ + size_t count, index, head; + snd_ctl_map_t *map; + struct snd_ctl_map_ctl *mctl; + int found; + + if (event_mask == SNDRV_CTL_EVENT_MASK_REMOVE) + event_mask = SNDRV_CTL_EVENT_MASK_INFO; + map = priv->map; + for (count = priv->map_items; count > 0; count--, map++) { + for (index = 0; index < map->controls_items; index++) { + mctl = &map->controls[index]; + if (mctl->id_child.numid == 0) { + if (snd_ctl_elem_id_compare_set(id, &mctl->id_child)) + continue; + mctl->id_child.numid = id->numid; + } + if (id->numid != mctl->id_child.numid) + continue; + debug_id(&map->map_id, "%s found (all)\n", __func__); + map->event_mask |= event_mask; + found = 0; + for (head = priv->map_read_queue_head; + head != priv->map_read_queue_tail; + _next_ptr(&head, priv->map_items)) + if (priv->map_read_queue[head] == map) { + found = 1; + break; + } + if (found) + continue; + debug_id(&map->map_id, "%s marking for read\n", __func__); + priv->map_read_queue[priv->map_read_queue_tail] = map; + _next_ptr(&priv->map_read_queue_tail, priv->map_items); + } + } +} + +static int snd_ctl_remap_read(snd_ctl_t *ctl, snd_ctl_event_t *event) +{ + snd_ctl_remap_t *priv = ctl->private_data; + snd_ctl_remap_id_t *rid; + snd_ctl_numid_t *numid; + snd_ctl_map_t *map; + int err; + + if (priv->map_read_queue_head != priv->map_read_queue_tail) { + map = priv->map_read_queue[priv->map_read_queue_head]; + _next_ptr(&priv->map_read_queue_head, priv->map_items); + memset(event, 0, sizeof(*event)); + event->type = SNDRV_CTL_EVENT_ELEM; + event->data.elem.mask = map->event_mask; + event->data.elem.id = map->map_id; + map->event_mask = 0; + debug_id(&map->map_id, "%s queue read\n", __func__); + return 1; + } + err = snd_ctl_read(priv->child, event); + if (err < 0 || event->type != SNDRV_CTL_EVENT_ELEM) + return err; + if (event->data.elem.mask == SNDRV_CTL_EVENT_MASK_REMOVE || + (event->data.elem.mask & (SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO | + SNDRV_CTL_EVENT_MASK_ADD | SNDRV_CTL_EVENT_MASK_TLV)) != 0) { + debug_id(&event->data.elem.id, "%s event mask 0x%x\n", __func__, event->data.elem.mask); + remap_event_for_all_map_controls(priv, &event->data.elem.id, event->data.elem.mask); + rid = remap_find_id_child(priv, &event->data.elem.id); + if (rid) { + if (rid->id_child.numid == 0) { + numid = remap_find_numid_child(priv, event->data.elem.id.numid); + if (numid == NULL) + return -EIO; + rid->id_child.numid = numid->numid_child; + rid->id_app.numid = numid->numid_app; + } + event->data.elem.id = rid->id_app; + } else { + numid = remap_find_numid_child(priv, event->data.elem.id.numid); + if (numid == NULL) + return -EIO; + event->data.elem.id.numid = numid->numid_app; + } + } + return err; +} + +static const snd_ctl_ops_t snd_ctl_remap_ops = { + .close = snd_ctl_remap_close, + .nonblock = snd_ctl_remap_nonblock, + .async = snd_ctl_remap_async, + .subscribe_events = snd_ctl_remap_subscribe_events, + .card_info = snd_ctl_remap_card_info, + .element_list = snd_ctl_remap_elem_list, + .element_info = snd_ctl_remap_elem_info, + .element_read = snd_ctl_remap_elem_read, + .element_write = snd_ctl_remap_elem_write, + .element_lock = snd_ctl_remap_elem_lock, + .element_unlock = snd_ctl_remap_elem_unlock, + .element_tlv = snd_ctl_remap_elem_tlv, + .hwdep_next_device = snd_ctl_remap_hwdep_next_device, + .hwdep_info = snd_ctl_remap_hwdep_info, + .pcm_next_device = snd_ctl_remap_pcm_next_device, + .pcm_info = snd_ctl_remap_pcm_info, + .pcm_prefer_subdevice = snd_ctl_remap_pcm_prefer_subdevice, + .rawmidi_next_device = snd_ctl_remap_rawmidi_next_device, + .rawmidi_info = snd_ctl_remap_rawmidi_info, + .rawmidi_prefer_subdevice = snd_ctl_remap_rawmidi_prefer_subdevice, + .set_power_state = snd_ctl_remap_set_power_state, + .get_power_state = snd_ctl_remap_get_power_state, + .read = snd_ctl_remap_read, +}; + +static int add_to_remap(snd_ctl_remap_t *priv, + snd_ctl_elem_id_t *child, + snd_ctl_elem_id_t *app) +{ + snd_ctl_remap_id_t *rid; + + if (priv->remap_alloc == priv->remap_items) { + rid = realloc(priv->remap, (priv->remap_alloc + 16) * sizeof(*rid)); + if (rid == NULL) + return -ENOMEM; + memset(rid + priv->remap_alloc, 0, sizeof(*rid) * 16); + priv->remap_alloc += 16; + priv->remap = rid; + } + rid = &priv->remap[priv->remap_items++]; + rid->id_child = *child; + rid->id_app = *app; + debug_id(&rid->id_child, "%s remap child\n", __func__); + debug_id(&rid->id_app, "%s remap app\n", __func__); + return 0; +} + +static int parse_remap(snd_ctl_remap_t *priv, snd_config_t *conf) +{ + snd_config_iterator_t i, next; + snd_ctl_elem_id_t child, app; + int err; + + if (conf == NULL) + return 0; + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id, *str; + if (snd_config_get_id(n, &id) < 0) + continue; + if (snd_config_get_string(n, &str) < 0) { + SNDERR("expected string with the target control id!"); + return -EINVAL; + } + snd_ctl_elem_id_clear(&app); + err = snd_ctl_ascii_elem_id_parse(&app, str); + if (err < 0) { + SNDERR("unable to parse target id '%s'!", str); + return -EINVAL; + } + if (remap_find_id_app(priv, &app)) { + SNDERR("duplicate target id '%s'!", id); + return -EINVAL; + } + snd_ctl_elem_id_clear(&child); + err = snd_ctl_ascii_elem_id_parse(&child, id); + if (err < 0) { + SNDERR("unable to parse source id '%s'!", id); + return -EINVAL; + } + if (remap_find_id_child(priv, &app)) { + SNDERR("duplicate source id '%s'!", id); + return -EINVAL; + } + err = add_to_remap(priv, &child, &app); + if (err < 0) + return err; + } + + return 0; +} + +static int new_map(snd_ctl_remap_t *priv, snd_ctl_map_t **_map, snd_ctl_elem_id_t *id) +{ + snd_ctl_map_t *map; + snd_ctl_numid_t *numid; + + if (priv->map_alloc == priv->map_items) { + map = realloc(priv->map, (priv->map_alloc + 16) * sizeof(*map)); + if (map == NULL) + return -ENOMEM; + memset(map + priv->map_alloc, 0, sizeof(*map) * 16); + priv->map_alloc += 16; + priv->map = map; + } + map = &priv->map[priv->map_items++]; + map->map_id = *id; + numid = remap_numid_new(priv, 0, ++priv->numid_app_last); + if (numid == NULL) + return -ENOMEM; + map->map_id.numid = numid->numid_app; + debug_id(&map->map_id, "%s created\n", __func__); + *_map = map; + return 0; +} + +static int add_ctl_to_map(snd_ctl_map_t *map, struct snd_ctl_map_ctl **_mctl, snd_ctl_elem_id_t *id) +{ + struct snd_ctl_map_ctl *mctl; + + if (map->controls_alloc == map->controls_items) { + mctl = realloc(map->controls, (map->controls_alloc + 4) * sizeof(*mctl)); + if (mctl == NULL) + return -ENOMEM; + memset(mctl + map->controls_alloc, 0, sizeof(*mctl) * 4); + map->controls_alloc += 4; + map->controls = mctl; + } + mctl = &map->controls[map->controls_items++]; + mctl->id_child = *id; + *_mctl = mctl; + return 0; +} + +static int add_chn_to_map(struct snd_ctl_map_ctl *mctl, long idx, long val) +{ + size_t off; + long *map; + + if (mctl->channel_map_alloc <= (size_t)idx) { + map = realloc(mctl->channel_map, (idx + 4) * sizeof(*map)); + if (map == NULL) + return -ENOMEM; + mctl->channel_map = map; + off = mctl->channel_map_alloc; + mctl->channel_map_alloc = idx + 4; + for ( ; off < mctl->channel_map_alloc; off++) + map[off] = -1; + } + if ((size_t)idx >= mctl->channel_map_items) + mctl->channel_map_items = idx + 1; + mctl->channel_map[idx] = val; + return 0; +} + +static int parse_map_vindex(struct snd_ctl_map_ctl *mctl, snd_config_t *conf) +{ + snd_config_iterator_t i, next; + int err; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + long idx, chn; + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (safe_strtol(id, &idx) || snd_config_get_integer(n, &chn)) { + SNDERR("Wrong channel mapping (%ld -> %ld)", idx, chn); + return -EINVAL; + } + err = add_chn_to_map(mctl, idx, chn); + if (err < 0) + return err; + } + + return 0; +} + +static int parse_map_config(struct snd_ctl_map_ctl *mctl, snd_config_t *conf) +{ + snd_config_iterator_t i, next; + int err; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (strcmp(id, "vindex") == 0) { + err = parse_map_vindex(mctl, n); + if (err < 0) + return err; + } + } + return 0; +} + +static int parse_map1(snd_ctl_map_t *map, snd_config_t *conf) +{ + snd_config_iterator_t i, next; + snd_ctl_elem_id_t cid; + struct snd_ctl_map_ctl *mctl; + int err; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + snd_ctl_elem_id_clear(&cid); + err = snd_ctl_ascii_elem_id_parse(&cid, id); + if (err < 0) { + SNDERR("unable to parse control id '%s'!", id); + return -EINVAL; + } + err = add_ctl_to_map(map, &mctl, &cid); + if (err < 0) + return err; + err = parse_map_config(mctl, n); + if (err < 0) + return err; + } + + return 0; +} + +static int parse_map(snd_ctl_remap_t *priv, snd_config_t *conf) +{ + snd_config_iterator_t i, next; + snd_ctl_elem_id_t eid; + snd_ctl_map_t *map; + int err; + + if (conf == NULL) + return 0; + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + snd_ctl_elem_id_clear(&eid); + err = snd_ctl_ascii_elem_id_parse(&eid, id); + if (err < 0) { + SNDERR("unable to parse id '%s'!", id); + return -EINVAL; + } + err = new_map(priv, &map, &eid); + if (err < 0) + return 0; + err = parse_map1(map, n); + if (err < 0) + return err; + } + + return 0; +} + +int snd_ctl_remap_open(snd_ctl_t **handlep, const char *name, snd_config_t *remap, + snd_config_t *map, snd_ctl_t *child, int mode ATTRIBUTE_UNUSED) +{ + snd_ctl_remap_t *priv; + snd_ctl_t *ctl; + int result, err; + + priv = calloc(1, sizeof(*priv)); + if (priv == NULL) + return -ENOMEM; + + err = parse_remap(priv, remap); + if (err < 0) { + result = err; + goto _err; + } + + err = parse_map(priv, map); + if (err < 0) { + result = err; + goto _err; + } + + priv->map_read_queue = calloc(priv->map_items, sizeof(priv->map_read_queue[0])); + if (priv->map_read_queue == NULL) { + result = -ENOMEM; + goto _err; + } + + priv->numid_remap_active = priv->map_items > 0; + + priv->child = child; + err = snd_ctl_new(&ctl, SND_CTL_TYPE_REMAP, name); + if (err < 0) { + result = err; + goto _err; + } + ctl->ops = &snd_ctl_remap_ops; + ctl->private_data = priv; + ctl->poll_fd = child->poll_fd; + + *handlep = ctl; + return 0; + + _err: + remap_free(priv); + return result; +} + +int _snd_ctl_remap_open(snd_ctl_t **handlep, char *name, snd_config_t *root, snd_config_t *conf, int mode) +{ + snd_config_iterator_t i, next; + snd_config_t *child = NULL; + snd_config_t *remap = NULL; + snd_config_t *map = NULL; + snd_ctl_t *cctl; + int err; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (_snd_conf_generic_id(id)) + continue; + if (strcmp(id, "remap") == 0) { + remap = n; + continue; + } + if (strcmp(id, "map") == 0) { + map = n; + continue; + } + if (strcmp(id, "child") == 0) { + child = n; + continue; + } + SNDERR("Unknown field %s", id); + return -EINVAL; + } + if (!child) { + SNDERR("child is not defined"); + return -EINVAL; + } + if (!remap && !map) { + SNDERR("remap or create section is not defined"); + return -EINVAL; + } + err = _snd_ctl_open_child(&cctl, root, child, mode, conf); + if (err < 0) + return err; + err = snd_ctl_remap_open(handlep, name, remap, map, cctl, mode); + if (err < 0) + snd_ctl_close(cctl); + return err; +} +SND_DLSYM_BUILD_VERSION(_snd_ctl_remap_open, SND_CONTROL_DLSYM_VERSION); diff --git a/src/control/control_symbols.c b/src/control/control_symbols.c index a2431e65..10d4689a 100644 --- a/src/control/control_symbols.c +++ b/src/control/control_symbols.c @@ -21,6 +21,7 @@ #ifndef PIC extern const char *_snd_module_control_hw; +extern const char *_snd_module_control_remap; extern const char *_snd_module_control_shm; extern const char *_snd_module_control_ext;