From ec9c86f8c1528765dc933ff09d74ae1b1408a7b4 Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Mon, 6 Nov 2006 14:32:08 +0100 Subject: Alsa support for Maemo SDK (n770): External Control plugin This patch file adds an ALSA External Control plugin. This source uses the dsp-protocol implementation. The plugin probes for all communication channel at the start time. It will handle only channels specified into alsa configuration file. An configuration example is: # Mixer ctl.!default { type dsp_ctl playback_devices ["/dev/dsptask/pcm2"] recording_devices ["/dev/dsptask/pcm_rec"] } Signed-off-by: Eduardo Valentin --- maemo/dsp-ctl.c | 641 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 641 insertions(+) create mode 100644 maemo/dsp-ctl.c diff --git a/maemo/dsp-ctl.c b/maemo/dsp-ctl.c new file mode 100644 index 0000000..a7a2be0 --- /dev/null +++ b/maemo/dsp-ctl.c @@ -0,0 +1,641 @@ +/** + * @file dsp-ctl.c + * @brief CTL External plugin implementation. + *

+ * Copyright (C) 2006 Nokia Corporation + *

+ * Contact: Eduardo Bezerra Valentin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * */ +#include +#include +#include +#include +#include +#include "list.h" +#include "debug.h" +#include "dsp-protocol.h" +#include "constants.h" + +#define PLAYBACK_VOLUME_CONTROL_NAME "PCM Playback Volume" +#define PLAYBACK_MUTE_CONTROL_NAME "PCM Playback Switch" +#define RECORDING_CONTROL_NAME "Capture Switch" + +/** + * data structure to represent a dsp task device node. + */ +typedef struct { + dsp_protocol_t *dsp_protocol; + char *name; + int channels; + struct list_head list; +} control_list_t; + +/** + * data structure to represent this plugin information. + */ +typedef struct snd_ctl_dsp { + snd_ctl_ext_t ext; + int num_playbacks; + int num_recordings; + control_list_t **controls; + control_list_t playback_devices; + control_list_t recording_devices; +} snd_ctl_dsp_t; + +static snd_ctl_dsp_t *free_ref; +/** + * @param control_list control list to be freed. + * + * It passes through this control list and frees + * all its nodes. + * + * @return zero. success. + */ +int free_control_list(control_list_t * control_list) +{ + struct list_head *pos, *q; + control_list_t *tmp; + list_for_each_safe(pos, q, &control_list->list) { + tmp = list_entry(pos, control_list_t, list); + list_del(pos); + free(tmp->name); + close(tmp->dsp_protocol->fd); + dsp_protocol_destroy(&(tmp->dsp_protocol)); + free(tmp); + } + return 0; +} + +/** + * @param ext snd_ctl_ext_t structure. + * + * It is the close event handler for this plugin. + * It frees all the allocated memory. + * + * @return zero. success. + */ +static void dsp_ctl_close(snd_ctl_ext_t * ext) +{ + snd_ctl_dsp_t *dsp_ctl = ext->private_data; + DENTER(); + free(dsp_ctl->controls); + free_control_list(&(dsp_ctl->playback_devices)); + free_control_list(&(dsp_ctl->recording_devices)); +// free(dsp_ctl); + DLEAVE(0); +} + +/** + * @param ext snd_ctl_ext_t structure. + * + * It returns number of controls to be used by this + * plugin. It is based on number of recording and playback + * device nodes configured to be handled by this plugin. + * + * @return number of controls. + */ +static int dsp_ctl_elem_count(snd_ctl_ext_t * ext) +{ + snd_ctl_dsp_t *dsp_ctl = ext->private_data; + int ret = 2 * dsp_ctl->num_playbacks + dsp_ctl->num_recordings; + DENTER(); + DLEAVE(ret); + return ret; +} + +/** + * @param ext snd_ctl_ext_t structure. + * @param offset offset of current control. + * @param id id of current control element. + * + * It sets name and index for a control based + * of its offset. + * + * @return zero. success. + */ +static int dsp_ctl_elem_list(snd_ctl_ext_t * ext, unsigned int offset, + snd_ctl_elem_id_t * id) +{ + snd_ctl_dsp_t *dsp_ctl = ext->private_data; + int ret = 0; + + DENTER(); + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + if (offset < 2 * dsp_ctl->num_playbacks) { + if (offset % 2 == 1) + snd_ctl_elem_id_set_name(id, + PLAYBACK_MUTE_CONTROL_NAME); + else + snd_ctl_elem_id_set_name(id, + PLAYBACK_VOLUME_CONTROL_NAME); + offset /= 2; + } else { + offset -= 2 * dsp_ctl->num_playbacks; + snd_ctl_elem_id_set_name(id, RECORDING_CONTROL_NAME); + } + snd_ctl_elem_id_set_index(id, offset); + DLEAVE(ret); + return ret; +} + +/** + * @param ext snd_ctl_ext_t structure. + * @param id control element id from alsa-lib + * + * It searches for an control element using + * its name and index. If this control can + * be found, it returns a key to represent it + * for future use. This key is an index of an + * array of controls whose is composed of three + * types of elements: PCM Volume, PCM Switch and + * Capture Switch. This array is organized as + * follows: + * pv0, ps0, pv1, ps1, ..., pv_n, ps_n, cs0, cs1, ..., cs_m + * + * where, pvi is the i-th PCM Volume Control + * psi is the i-th PCM Switch Control + * csi is the i-th Capture Switch Control + * n - is the number of Playback devices + * m - is the number of Recording devices + * + * @return if control is not found, returns SND_CTL_EXT_KEY_NOT_FOUND. + * Otherwise, returns a key of control. + */ +static snd_ctl_ext_key_t dsp_ctl_find_elem(snd_ctl_ext_t * ext, + const snd_ctl_elem_id_t * id) +{ + snd_ctl_dsp_t *dsp_ctl = ext->private_data; + snd_ctl_ext_key_t ret = SND_CTL_EXT_KEY_NOT_FOUND; + int idx; + const char *name; + + DENTER(); + idx = snd_ctl_elem_id_get_index(id); + name = snd_ctl_elem_id_get_name(id); + if (strcmp(PLAYBACK_VOLUME_CONTROL_NAME, name) == 0) + ret = idx * 2; + else if (strcmp(PLAYBACK_MUTE_CONTROL_NAME, name) == 0) + ret = idx * 2 + 1; + else + ret = 2 * dsp_ctl->num_playbacks + idx; + if (ret == SND_CTL_EXT_KEY_NOT_FOUND) + DPRINT("Not Found name %s, index %d\n", + snd_ctl_elem_id_get_name(id), idx); + DLEAVE((int)ret); + return ret; +} + +/** + * @param ext snd_ctl_ext_t structure. + * @param key current control key to be handled. + * @param type type of this control. + * @param acc access type of this control. + * @param count number of channels of this control. + * + * It provides information about a control. + * Playback device node has an integer and a boolean + * control. Recording has only boolean control. + * + * @return zero. success. + */ +static int dsp_ctl_get_attribute(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, + int *type, unsigned int *acc, + unsigned int *count) +{ + snd_ctl_dsp_t *dsp_ctl = ext->private_data; + int ret = 0; + DENTER(); + + if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) + *type = SND_CTL_ELEM_TYPE_BOOLEAN; + else + *type = SND_CTL_ELEM_TYPE_INTEGER; + + *count = dsp_ctl->controls[key]->channels; + *acc = SND_CTL_EXT_ACCESS_READWRITE; + DLEAVE(ret); + return ret; +} + +/** + * @param ext snd_ctl_ext_t structure. + * @param key current control key to be handled. + * @param imin minimum value of this integer control. + * @param imax maximum value of this integer control. + * @param istep steps value of this integer control. + * + * It provides information about integer control. It + * consideres both boolean and integer controls. + * + * @return zero. success. + */ +static int dsp_ctl_get_integer_info(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, + long *imin, long *imax, long *istep) +{ + snd_ctl_dsp_t *dsp_ctl = ext->private_data; + DENTER(); + *imin = 0; + if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) + *imax = 1; + else + *imax = 100; + *istep = 0; + DLEAVE(0); + return 0; +} + +/** + * @param ext snd_ctl_ext_t structure. + * @param key current control key to be handled. + * @param value return value holder. + * + * It reads volume information from dsp task node and + * fills it into value to alsa-lib. + * + * @return zero. success. + */ +static int dsp_ctl_read_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, + long *value) +{ + int ret = 0; + char left, right; + snd_ctl_dsp_t *dsp_ctl = ext->private_data; + control_list_t *tmp = dsp_ctl->controls[key]; + + DENTER(); + if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) { + ret = dsp_protocol_get_mute(tmp->dsp_protocol); + if (ret >= 0) { + left = ret == 0 ? 1 : 0; + right = left; + } + } else + ret = dsp_protocol_get_volume(tmp->dsp_protocol, &left, &right); + if (ret >= 0) { + value[0] = left; + if (tmp->channels == 2) + value[1] = right; + } else { + value[0] = 0; + if (tmp->channels == 2) + value[1] = 0; + ret = 0; + } + + DLEAVE(ret); + return ret; +} + +/** + * @param ext snd_ctl_ext_t structure. + * @param key current control key to be handled. + * @param value volume value to be written. + * + * It writes volume information to dsp task node from + * value that comes from alsa-lib. It checks if value + * coming from alsa-lib is different of value in dsp + * side. + * + * @return zero if not updated. one if updated. + */ +static int dsp_ctl_write_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, + long *value) +{ + int ret; + char left, right; + snd_ctl_dsp_t *dsp_ctl = ext->private_data; + control_list_t *tmp = dsp_ctl->controls[key]; + + DENTER(); + if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) { + if ((ret = dsp_protocol_get_mute(tmp->dsp_protocol)) < 0) + goto zero; + left = ret == 0 ? 1 : 0; + right = left; + } else + if ((ret = + dsp_protocol_get_volume(tmp->dsp_protocol, &left, &right)) < 0) + goto zero; + + if (tmp->channels == 2) { + if (left == value[0] && right == value[1]) { + ret = 0; + goto out; + } + right = value[1]; + left = value[0]; + } else { + if (left == value[0]) { + ret = 0; + goto out; + } + right = left = value[0]; + } + + if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) { + if ((ret = + dsp_protocol_set_mute(tmp->dsp_protocol, + left == 0 ? 1 : 0)) < 0) + goto zero; + } else + if ((ret = + dsp_protocol_set_volume(tmp->dsp_protocol, left, right)) < 0) + goto zero; + ret = 1; + goto out; + zero: + value[0] = 0; + if (tmp->channels == 2) + value[1] = 0; + ret = 0; + out: + DLEAVE(ret); + return ret; +} + +/** + * @param ext + * @param id + * @param event_mask + * + * @return -EIO + */ +static int dsp_ctl_read_event(snd_ctl_ext_t * ext, snd_ctl_elem_id_t * id, + unsigned int *event_mask) +{ + return -EIO; +} + +/** + * @param n configuration file parse tree. + * @param device_list list of device files to be filled. + * + * It searches for device file names in given configuration parse + * tree. When one device file name is found, it is filled into device_list. + * + * @return zero if success, otherwise a negative error code. + */ +static int fill_control_list(snd_config_t * n, control_list_t * control_list) +{ + snd_config_iterator_t j, nextj; + control_list_t *tmp; + long idx = 0; + DENTER(); + INIT_LIST_HEAD(&control_list->list); + snd_config_for_each(j, nextj, n) { + snd_config_t *s = snd_config_iterator_entry(j); + const char *id_number; + long k; + if (snd_config_get_id(s, &id_number) < 0) + continue; + if (safe_strtol(id_number, &k) < 0) { + SNDERR("id of field %s is not an integer", id_number); + idx = -EINVAL; + goto out; + } + if (k == idx) { + idx++; + /* add to available dsp task nodes */ + tmp = (control_list_t *) malloc(sizeof(control_list_t)); + if (snd_config_get_ascii(s, &(tmp->name)) < 0) { + SNDERR("invalid ascii string for id %s\n", + id_number); + idx = -EINVAL; + goto out; + } + list_add(&(tmp->list), &(control_list->list)); + } + } + out: + DLEAVE((int)idx); + return idx; +} + +/** + * @param dsp_ctl snd_dsp_t structure. + * + * It probes all dsp tasks configured to be handled by this + * plugin. It gets all needed information about volume controling. + * + * @return zero if success, otherwise a negative error code. + */ +static int dsp_ctl_probe_dsp_task(snd_ctl_dsp_t * dsp_ctl) +{ + int err = 0, i; + control_list_t *tmp; + struct list_head *lists[2] = + { &(dsp_ctl->playback_devices.list), +&(dsp_ctl->recording_devices.list) }; + DENTER(); + DPRINT("Probing dsp device nodes \n"); + for (i = 0; i < 2; i++) { + list_for_each_entry(tmp, lists[i], list) { + DPRINT("Trying to use %s\n", tmp->name); + /* Initialise the dsp_protocol and create connection */ + if ((err = + dsp_protocol_create(&(tmp->dsp_protocol))) < 0) + goto out; + if ((tmp->channels = + dsp_protocol_probe_node(tmp->dsp_protocol, + tmp->name)) < 0) { + DPRINT("%s is not available now\n", tmp->name); + err = tmp->channels; + close(tmp->dsp_protocol->fd); //memory leak?!? + goto out; + } + } + } + if (err < 0) { + DPRINT("No valid dsp task nodes for now. Exiting.\n"); + } + out: + DLEAVE(err); + return err; +} + +/** + * @param dsp_ctl snd_dsp_t structure. + * + * It fills an array of controls to represent PCM Volume, + * PCM Switch and Capture Switch controls. + * + * @return zero if success, otherwise a negative error code. + */ +static int dsp_ctl_fill_controls(snd_ctl_dsp_t * dsp_ctl) +{ + int ret = 0; + int i; + int num_controls = 2 * dsp_ctl->num_playbacks + dsp_ctl->num_recordings; + DENTER(); + control_list_t *tmp; + dsp_ctl->controls = calloc(num_controls, sizeof(control_list_t *)); + if (dsp_ctl->controls == NULL) { + ret = -ENOMEM; + goto out; + } + i = 0; + /* Each pcm task node should have a PCM Volume and PCM Switch control */ + list_for_each_entry(tmp, &(dsp_ctl->playback_devices.list), list) { + dsp_ctl->controls[i] = tmp; + dsp_ctl->controls[i + 1] = tmp; + i += 2; + } + /* Each pcm_rec node should have a Capture Switch control */ + list_for_each_entry(tmp, &(dsp_ctl->recording_devices.list), list) + dsp_ctl->controls[i++] = tmp; + + out: + DLEAVE(ret); + return ret; +} + +/** + * Alsa-lib callback structure. + */ +static snd_ctl_ext_callback_t dsp_ctl_ext_callback = { + .close = dsp_ctl_close, + .elem_count = dsp_ctl_elem_count, + .elem_list = dsp_ctl_elem_list, + .find_elem = dsp_ctl_find_elem, + .get_attribute = dsp_ctl_get_attribute, + .get_integer_info = dsp_ctl_get_integer_info, + .read_integer = dsp_ctl_read_integer, + .write_integer = dsp_ctl_write_integer, + .read_event = dsp_ctl_read_event, +}; + +/** + * It initializes the alsa ctl plugin. It reads the parameters and creates the + * connection with the pcm device file. + * + * @return zero if success, otherwise a negative error code. + */ +SND_CTL_PLUGIN_DEFINE_FUNC(dsp_ctl) +{ + snd_config_iterator_t i, next; + snd_ctl_dsp_t *dsp_ctl; + int err; + int ret; + + DENTER(); + /* Allocate the structure */ + dsp_ctl = calloc(1, sizeof(*dsp_ctl)); + if (dsp_ctl == NULL) { + ret = -ENOMEM; + goto out; + } + /* Read the configuration searching for configurated devices */ + 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, "comment") == 0 || strcmp(id, "type") == 0) + continue; + if (strcmp(id, "playback_devices") == 0) { + if (snd_config_get_type(n) == SND_CONFIG_TYPE_COMPOUND){ + if ((dsp_ctl->num_playbacks = + fill_control_list(n, + &(dsp_ctl-> + playback_devices))) < 0) { + SNDERR("Could not fill control" + " list for playback devices\n"); + err = -EINVAL; + goto error; + } + } else { + SNDERR("Invalid type for %s", id); + err = -EINVAL; + goto error; + } + continue; + } + if (strcmp(id, "recording_devices") == 0) { + if (snd_config_get_type(n) == SND_CONFIG_TYPE_COMPOUND){ + if ((dsp_ctl->num_recordings = + fill_control_list(n, + &(dsp_ctl-> + recording_devices))) < 0) { + SNDERR("Could not fill string " + "list for recording devices\n"); + err = -EINVAL; + goto error; + } + } else { + SNDERR("Invalid type for %s", id); + err = -EINVAL; + goto error; + } + continue; + } + SNDERR("Unknown field %s", id); + err = -EINVAL; + goto error; + } + + if ((err = dsp_ctl_probe_dsp_task(dsp_ctl)) < 0) + goto error; + + if ((err = dsp_ctl_fill_controls(dsp_ctl)) < 0) + goto error; + dsp_ctl->ext.version = SND_CTL_EXT_VERSION; + dsp_ctl->ext.card_idx = 0; /* FIXME */ + strcpy(dsp_ctl->ext.id, "ALSA-DSP-CTL"); + strcpy(dsp_ctl->ext.name, "Alsa-DSP external ctl plugin"); + strcpy(dsp_ctl->ext.longname, "External Control Alsa plugin for DSP"); + strcpy(dsp_ctl->ext.mixername, "ALSA-DSP plugin Mixer"); + dsp_ctl->ext.callback = &dsp_ctl_ext_callback; + dsp_ctl->ext.private_data = dsp_ctl; + free_ref = dsp_ctl; + + if ((err = snd_ctl_ext_create(&dsp_ctl->ext, name, mode)) < 0) + goto error; + + *handlep = dsp_ctl->ext.handle; + ret = 0; + goto out; + error: + ret = err; + free(dsp_ctl); + out: + DLEAVE(ret); + return ret; +} +void dsp_ctl_descructor(void) __attribute__ ((destructor)); + +void dsp_ctl_descructor(void) +{ + DENTER(); + DPRINT("dsp ctl destructor\n"); + DPRINT("checking for memories leaks and releasing resources\n"); + if (free_ref) { + if (free_ref->controls) + free(free_ref->controls); + + free_control_list(&(free_ref->playback_devices)); + + free_control_list(&(free_ref->recording_devices)); + + free(free_ref); + free_ref = NULL; + } + DLEAVE(0); + +} + +SND_CTL_PLUGIN_SYMBOL(dsp_ctl); -- cgit