| /** | |
| * \file control/control_remap.c | |
| * \brief CTL Remap Plugin Interface | |
| * \author Jaroslav Kysela <perex@perex.cz> | |
| * \date 2021 | |
| */ | |
| /* | |
| * Control - Remap Controls | |
| * Copyright (c) 2021 by Jaroslav Kysela <perex@perex.cz> | |
| * | |
| * | |
| * 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 "control_local.h" | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <stdint.h> | |
| #include <stdarg.h> | |
| #include <unistd.h> | |
| #include <string.h> | |
| #ifndef DOC_HIDDEN | |
| #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) | |
| #endif /* DOC_HIDDEN */ | |
| #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 (priv->numid_remap_active && 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 { | |
| if (remap_find_id_child(priv, id)) | |
| return -ENOENT; | |
| 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_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; | |
| } | |
| #ifndef DOC_HIDDEN | |
| #define ACCESS_BITS(bits) \ | |
| (bits & (SNDRV_CTL_ELEM_ACCESS_READWRITE|\ | |
| SNDRV_CTL_ELEM_ACCESS_VOLATILE|\ | |
| SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)) | |
| #endif /* DOC_HIDDEN */ | |
| 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 = -1, chn = -1; | |
| 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; | |
| } | |
| /** | |
| * \brief Creates a new remap & map control handle | |
| * \param handlep Returns created control handle | |
| * \param name Name of control device | |
| * \param remap Remap configuration | |
| * \param map Map configuration | |
| * \param child child configuration root | |
| * \param mode Control handle mode | |
| * \retval zero on success otherwise a negative error code | |
| * \warning Using of this function might be dangerous in the sense | |
| * of compatibility reasons. The prototype might be freely | |
| * changed in future. | |
| */ | |
| 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) | |
| { | |
| snd_ctl_remap_t *priv; | |
| snd_ctl_t *ctl; | |
| int result, err; | |
| /* no-op, remove the plugin */ | |
| if (!remap && !map) | |
| goto _noop; | |
| 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; | |
| } | |
| /* no-op check, remove the plugin */ | |
| if (priv->map_items == 0 && priv->remap_items == 0) { | |
| remap_free(priv); | |
| _noop: | |
| free(child->name); | |
| child->name = name ? strdup(name) : NULL; | |
| if (name && !child->name) | |
| return -ENOMEM; | |
| *handlep = child; | |
| return 0; | |
| } | |
| 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, mode); | |
| 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; | |
| } | |
| /*! \page control_plugins | |
| \section control_plugins_remap Plugin: Remap & map | |
| This plugin can remap (rename) identifiers (except the numid part) for | |
| a child control to another. The plugin can also merge the multiple | |
| child controls to one or split one control to more. | |
| \code | |
| ctl.name { | |
| type remap # Route & Volume conversion PCM | |
| child STR # Slave name | |
| # or | |
| child { # Slave definition | |
| type STR | |
| ... | |
| } | |
| remap { | |
| # the ID strings are parsed in the amixer style like 'name="Headphone Playback Switch",index=2' | |
| SRC_ID1_STR DST_ID1_STR | |
| SRC_ID2_STR DST_ID2_STR | |
| ... | |
| } | |
| map { | |
| # join two stereo controls to one | |
| CREATE_ID1_STR { | |
| SRC_ID1_STR { | |
| vindex.0 0 # source channel 0 to merged channel 0 | |
| vindex.1 1 | |
| } | |
| SRC_ID2_STR { | |
| vindex.2 0 | |
| vindex.3 1 # source channel 1 to merged channel 3 | |
| } | |
| } | |
| # split stereo to mono | |
| CREATE_ID2_STR { | |
| SRC_ID3_STR { | |
| vindex.0 0 # stereo to mono (first channel) | |
| } | |
| } | |
| CREATE_ID3_STR { | |
| SRC_ID4_STR { | |
| vindex.0 1 # stereo to mono (second channel) | |
| } | |
| } | |
| } | |
| } | |
| \endcode | |
| \subsection control_plugins_route_funcref Function reference | |
| <UL> | |
| <LI>snd_ctl_remap_open() | |
| <LI>_snd_ctl_remap_open() | |
| </UL> | |
| */ | |
| /** | |
| * \brief Creates a new remap & map control plugin | |
| * \param handlep Returns created control handle | |
| * \param name Name of control | |
| * \param root Root configuration node | |
| * \param conf Configuration node with Route & Volume PCM description | |
| * \param mode Control handle mode | |
| * \retval zero on success otherwise a negative error code | |
| * \warning Using of this function might be dangerous in the sense | |
| * of compatibility reasons. The prototype might be freely | |
| * changed in future. | |
| */ | |
| 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; | |
| } | |
| 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; | |
| } | |
| #ifndef DOC_HIDDEN | |
| SND_DLSYM_BUILD_VERSION(_snd_ctl_remap_open, SND_CONTROL_DLSYM_VERSION); | |
| #endif |