From: Stefan Schmidt stefan@slimlogic.co.uk
It allows switching audio settings between scenarios or uses-cases like listening to music and answering an incoming phone call. Made of control aliasing for playback, capture master and switch as well as the option to post- and prefix a sequence of control changes avoiding pops and other unwanted noise. Some example programs will be available in alsa-utils.
CC: Ian Molton ian@mnementh.co.uk CC: Graeme Gregory gg@slimlogic.co.uk Signed-off-by: Liam Girdwood lrg@slimlogic.co.uk Signed-off-by: Stefan Schmidt stefan@slimlogic.co.uk --- include/Makefile.am | 2 +- include/ascenario.h | 167 +++++++ src/Makefile.am | 2 +- src/ascenario.c | 1357 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1526 insertions(+), 2 deletions(-) create mode 100644 include/ascenario.h create mode 100644 src/ascenario.c
diff --git a/include/Makefile.am b/include/Makefile.am index a291503..572fbc9 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -3,7 +3,7 @@ SUBDIRS = sound sysincludedir = ${includedir}/sys alsaincludedir = ${includedir}/alsa
-alsainclude_HEADERS = asoundlib.h asoundef.h \ +alsainclude_HEADERS = asoundlib.h asoundef.h ascenario.h \ version.h global.h input.h output.h error.h \ conf.h control.h iatomic.h
diff --git a/include/ascenario.h b/include/ascenario.h new file mode 100644 index 0000000..49f1c48 --- /dev/null +++ b/include/ascenario.h @@ -0,0 +1,167 @@ +/* +* ALSA Scenario header file +* +* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* +* Copyright (C) 2008-2009 SlimLogic Ltd +* Authors: Liam Girdwood lrg@slimlogic.co.uk +* Stefan Schmidt stefan@slimlogic.co.uk +*/ + +/** Scenario ID's + * + * Standard Scenario ID's - Add new scenarios at the end. + */ + +#define SND_SCN_PLAYBACK_SPEAKER "playback speaker" +#define SND_SCN_PLAYBACK_HEADPHONES "playback headphone" +#define SND_SCN_PLAYBACK_HEADSET "playback headset" +#define SND_SCN_PLAYBACK_BLUETOOTH "playback bluetooth" +#define SND_SCN_PLAYBACK_HANDSET "playback handset" +#define SND_SCN_PLAYBACK_GSM "playback gsm" +#define SND_SCN_PLAYBACK_LINE "playback line" + +#define SND_SCN_CAPTURE_MIC "capture mic" +#define SND_SCN_CAPTURE_LINE "capture line" +#define SND_SCN_CAPTURE_HEADSET "capture headset" +#define SND_SCN_CAPTURE_HANDSET "capture handset" +#define SND_SCN_CAPTURE_BLUETOOTH "capture bluetooth" +#define SND_SCN_CAPTURE_GSM "capture gsm" + +#define SND_SCN_PHONECALL_HANDSET "phonecall handset" +#define SND_SCN_PHONECALL_HEADSET "phonecall headset" +#define SND_SCN_PHONECALL_BLUETOOTH "phonecall bluetooth" +#define SND_SCN_PHONECALL_IP "phonecall ip" + +/** QOS + * + * Defines Audio Quality of Service + */ +#define SND_QOS_HIFI 0 +#define SND_QOS_VOICE 1 +#define SND_QOS_SYSTEM 2 + +struct snd_scenario; + + +/* TODO: add notification */ + + +/** + * snd_scenario_open - open scenario core + * @card_name: sound card name. + * + * Open scenario manager core for sound card. + */ +struct snd_scenario *snd_scenario_open(const char *card_name); + +/** + * snd_scenario_reload - reload and reparse scenario configuration + * @scn: scenario + * + * Reloads and reparses sound card scenario configuration. + */ +int snd_scenario_reload(struct snd_scenario *scn); + +/** + * snd_scenario_close - close scenario core + * @scn: scenario + * + * Free scenario manager core for sound card. + */ +void snd_scenario_close(struct snd_scenario *scn); + +/** + * snd_scenario_set_scn - set scenario + * @scn: scenario + * @scenario: scenario name + * + * Set new scenario for sound card. + */ +int snd_scenario_set_scn(struct snd_scenario *scn, const char *scenario); + +/** + * snd_scenario_get_scn - get scenario + * @scn: scenario + * + * Get current sound card scenario. + */ +const char *snd_scenario_get_scn(struct snd_scenario *scn); + +/** + * snd_scenario_list - list supported scenarios + * @scn: scenario + * @list: list of supported scenario names. + * + * List supported scenarios for this sound card. + * Returns number of scenarios. + */ +int snd_scenario_list(struct snd_scenario *scn, const char **list[]); + +/** + * snd_scenario_set_qos - set qos + * @qos: qos + * + * Set Quality of Service for this scenario. + */ +int snd_scenario_set_qos(struct snd_scenario *scn, int qos); + +/** + * snd_scenario_get_qos - get qos + * @scn: scenario + * + * Get Quality of Service for this scenario. + */ +int snd_scenario_get_qos(struct snd_scenario *scn); + +/** + * snd_scenario_get_master_playback_volume - get playback volume + * @scn: scenario + * + * Get the master playback volume control name for the current scenario. + */ +int snd_scenario_get_master_playback_volume(struct snd_scenario *scn); + +/** + * snd_scenario_get_master_playback_switch - get playback switch + * @scn: scenario + * + * Get the master playback switch control name for the current scenario. + */ + int snd_scenario_get_master_playback_switch(struct snd_scenario *scn); + +/** + * snd_scenario_get_master_capture_volume - get capture volume + * @scn: scenario + * + * Get the master capture volume control name for the current scenario. + */ +int snd_scenario_get_master_capture_volume(struct snd_scenario *scn); + +/** + * snd_scenario_get_master_capture_switch - get capture switch + * @scn: scenario + * + * Get the master capture switch control name for the current scenario. + */ +int snd_scenario_get_master_capture_switch(struct snd_scenario *scn); + +/** + * snd_scenario_dump - dump + * @card_name: sound card name. + * + * Dump current sound card settings to stdout in scn format. + */ +int snd_scenario_dump(const char *card_name); diff --git a/src/Makefile.am b/src/Makefile.am index 3204fe4..be46cb3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,7 +14,7 @@ SYMFUNCS = endif
lib_LTLIBRARIES = libasound.la -libasound_la_SOURCES = conf.c confmisc.c input.c output.c async.c error.c dlmisc.c socket.c shmarea.c userfile.c names.c +libasound_la_SOURCES = conf.c confmisc.c input.c output.c async.c error.c dlmisc.c socket.c shmarea.c userfile.c names.c ascenario.c
SUBDIRS=control libasound_la_LIBADD = control/libcontrol.la diff --git a/src/ascenario.c b/src/ascenario.c new file mode 100644 index 0000000..d2b116f --- /dev/null +++ b/src/ascenario.c @@ -0,0 +1,1357 @@ +/* + * 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Copyright (C) 2008-2009 SlimLogic Ltd + * Authors: Liam Girdwood lrg@slimlogic.co.uk + * Stefan Schmidt stefan@slimlogic.co.uk + */ + +#define _GNU_SOURCE /* needed of O_NOATIME */ + +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> +#include <alsa/asoundlib.h> + +#include "../include/ascenario.h" + +#define PRE_SEQ 0 +#define POST_SEQ 1 +#define MAX_SCN 32 +#define MAX_NAME 64 +#define MAX_FILE 256 +#define MAX_BUF 256 +#define ALSA_SCN_DIR "/etc/alsa/scenario" + +/* + * Stores all scenario settings for 1 kcontrol. Hence we have a + * control_settings for each kcontrol in card. + */ +struct control_settings { + char name[MAX_NAME]; + int id; + snd_ctl_elem_type_t type; + int count; /* 1 = mono, 2 = stereo, etc */ + unsigned short *value; /* kcontrol value 2D array */ +}; + +/* + * If sleep is 0 the element contains the settings in control. Else sleep + * contains the sleep time in micro seconds. + */ +struct sequence_element { + unsigned int sleep; /* Sleep time in msecs if sleep element, else 0 */ + struct control_settings *control; + struct sequence_element *next; /* Pointer to next list element */ +}; + +/* + * Describes default mixers and qos for scenario. + * We have a scenario_info for each scenario loaded. + */ +struct scenario_info { + char *name; + char *file; + char *pre_sequence_file; + char *post_sequence_file; + short playback_volume_id; + short playback_switch_id; + short capture_volume_id; + short capture_switch_id; + int qos; +}; + +/* Describe a snd card and all it's scenarios. + */ +struct snd_scenario { + char *card_name; + int current_scenario; + int num_scenarios; /* number of supported scenarios */ + int num_kcontrols; /* number of kcontrols */ + struct sequence_element *pre_seq_list; /* Linked list for pre sequence */ + struct sequence_element *post_seq_list; /* Linked list for post sequence */ + const char **list; + struct scenario_info *scenario; /* var len array of scenario info */ + struct control_settings *control; /* var len array of controls */ +}; + +static void scn_error(const char *fmt,...) +{ + va_list va; + va_start(va, fmt); + fprintf(stderr, "scenario: "); + vfprintf(stderr, fmt, va); + va_end(va); +} + +static void scn_stdout(const char *fmt,...) +{ + va_list va; + va_start(va, fmt); + vfprintf(stdout, fmt, va); + va_end(va); +} + +static inline void set_value(struct snd_scenario *scn, + struct control_settings *control, int count, unsigned short val) +{ + int offset = scn->current_scenario * control->count; + control->value[offset + count] = val; +} + +static inline unsigned short get_value(struct snd_scenario *scn, + struct control_settings *control, int count) +{ + int offset = scn->current_scenario * control->count; + return control->value[offset + count]; +} + +static int dump_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id) +{ + int err, count, i; + snd_ctl_elem_info_t *info; + snd_ctl_elem_type_t type; + snd_ctl_elem_value_t *control; + + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_value_alloca(&control); + + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_info(handle, info); + if (err < 0) { + scn_stdout("%s: failed to get ctl info\n"); + return err; + } + + snd_ctl_elem_value_set_id(control, id); + snd_ctl_elem_read(handle, control); + + type = snd_ctl_elem_info_get_type(info); + count = snd_ctl_elem_info_get_count(info); + if (count == 0) + return 0; + + scn_stdout("%u:'%s':%d:", + snd_ctl_elem_id_get_numid(id), + snd_ctl_elem_id_get_name(id), count); + + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + for (i = 0; i < count - 1; i++) + scn_stdout("%d,", + snd_ctl_elem_value_get_boolean(control, i)); + scn_stdout("%d", snd_ctl_elem_value_get_boolean(control, i)); + break; + case SND_CTL_ELEM_TYPE_INTEGER: + for (i = 0; i < count - 1; i++) + scn_stdout("%d,", + snd_ctl_elem_value_get_integer(control, i)); + scn_stdout("%d", snd_ctl_elem_value_get_integer(control, i)); + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + for (i = 0; i < count - 1; i++) + scn_stdout("%ld,", + snd_ctl_elem_value_get_integer64(control, i)); + scn_stdout("%ld", + snd_ctl_elem_value_get_integer64(control, i)); + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + for (i = 0; i < count - 1; i++) + scn_stdout("%d,", + snd_ctl_elem_value_get_enumerated(control, i)); + scn_stdout("%d", + snd_ctl_elem_value_get_enumerated(control, i)); + break; + case SND_CTL_ELEM_TYPE_BYTES: + for (i = 0; i < count - 1; i++) + scn_stdout("%2.2x,", + snd_ctl_elem_value_get_byte(control, i)); + scn_stdout("%2.2x", snd_ctl_elem_value_get_byte(control, i)); + break; + default: + break; + } + scn_stdout("\n"); + return 0; +} + +/* + * Add new kcontrol from sound card into memory database. + */ +static int add_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, + struct control_settings *control_settings) +{ + int err; + snd_ctl_elem_info_t *info; + snd_ctl_elem_value_t *control; + + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_value_alloca(&control); + + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_info(handle, info); + if (err < 0) { + scn_stdout("%s: failed to get ctl info\n"); + return err; + } + + snd_ctl_elem_value_set_id(control, id); + snd_ctl_elem_read(handle, control); + + strncpy(control_settings->name, snd_ctl_elem_id_get_name(id), + MAX_NAME); + control_settings->count = snd_ctl_elem_info_get_count(info); + control_settings->type = snd_ctl_elem_info_get_type(info); + control_settings->id = snd_ctl_elem_id_get_numid(id); + return 0; +} + +static int parse_controls(struct snd_scenario *scn, FILE *f) +{ + struct control_settings *control; + char buf[MAX_BUF], name[MAX_NAME]; + int id, count, line = 1, i; + char *name_start, *name_end, *tbuf; + + while (fgets(buf, MAX_BUF, f) != NULL) { + + /* get id */ + tbuf = buf; + id = atoi(tbuf); + if (id == 0) { + scn_error("%s:id == 0 on line %d\n", __func__, line); + return -EINVAL; + } + for (i = 0; i < scn->num_kcontrols; i++) { + if (id == scn->control[i].id) { + control = &scn->control[i]; + goto get_name; + } + } + scn_error("%s:id not found at line %d\n", __func__, line); + return -EINVAL; +get_name: + /* get name start */ + while (*tbuf != 0 && *tbuf != ''') + tbuf++; + if (*tbuf == 0) + return -EINVAL; + name_start = ++tbuf; + + /* get name end */ + while (*tbuf != 0 && *tbuf != ''') + tbuf++; + if (*tbuf == 0) + return -EINVAL; + name_end = tbuf++; + + /* copy name */ + if ((name_end - name_start) > MAX_NAME) { + scn_error("%s:name too big at %d chars line %d\n", + __func__, name_end - name_start, line); + return -EINVAL; + } + strncpy(name, name_start, name_end - name_start); + name[name_end - name_start] = 0; + if (strcmp(name, control->name)) { + scn_error("%s: name %s and %s don't match at line %d\n", + __func__, name, control->name, line); + return -EINVAL; + } + + /* get count */ + tbuf++; + count = atoi(tbuf); + if (count == 0) { + scn_error("%s:count == 0 on line %d\n", __func__, + line); + return -EINVAL; + } + if (count != control->count) { + scn_error("%s:count does not match at line %d\n", + __func__, line); + return -EINVAL; + } + + /* get vals */ + control->value = malloc(control->count * scn->num_scenarios * + sizeof(unsigned short)); + if (control->value == NULL) + return -ENOMEM; + + while (*tbuf != 0 && *tbuf != ':') + tbuf++; + if (*tbuf == 0) + return -EINVAL; + tbuf++; + + for (i = 0; i < count; i++) { + set_value(scn, control, i, atoi(tbuf)); + while (*tbuf != 0 && *tbuf != ',') + tbuf++; + + if (*tbuf++ == 0 && i < (count - 1)) + return -EINVAL; + } + line++; + } + + return 0; +} + +static char *get_string (char *buf) +{ + char *str, *end; + + /* find '=' */ + while (isblank(*buf)) + buf++; + if (*buf == 0 || *buf != '=') { + scn_error("%s: missing '='\n", __func__); + return NULL; + } + + /* find leading '"' */ + buf++; + while (isblank(*buf)) + buf++; + if (*buf == 0 || *buf != '"') { + scn_error("%s: missing start '"'\n", __func__); + return NULL; + } + str = ++buf; + + /* get value */ + while (*buf != 0 && *buf != '"') + buf++; + end = buf; + + /* find '"' terminator */ + if (*buf == 0 || *buf != '"') { + scn_error("%s: missing terminator '"' %s\n", __func__, buf); + return NULL; + } + + *end = 0; + return strdup(str); +} + +static char *get_control_name (char *buf) +{ + char *str, *end; + + /* find leading '"' */ + buf++; + while (isblank(*buf)) + buf++; + if (*buf == 0 || *buf != '"') { + scn_error("%s: missing start '"'\n", __func__); + return NULL; + } + str = ++buf; + + /* get value */ + while (*buf != 0 && *buf != '"') + buf++; + end = buf; + + /* find '"' terminator */ + if (*buf == 0 || *buf != '"') { + scn_error("%s: missing terminator '"' %s\n", __func__, buf); + return NULL; + } + + *end = 0; + return strdup(str); +} + +static int get_int (char *buf) +{ + /* find '=' */ + while (isblank(*buf)) + buf++; + if (*buf == 0 || *buf != '=') { + scn_error("%s: missing '='\n", __func__); + return -EINVAL; + } + buf++; + return atoi(buf); +} + +static int get_enum (char *buf) +{ + /* find '=' */ + while (isblank(*buf)) + buf++; + if (*buf == 0 || *buf != '=') { + scn_error("%s: missing '='\n", __func__); + return -EINVAL; + } + buf++; + return 0; /* TODO */ +} + +static void seq_list_append(struct snd_scenario *scn, + struct sequence_element *curr, int position) +{ + struct sequence_element *last, *tmp; + + if (position) { + if (!scn->post_seq_list) + scn->post_seq_list = curr; + + else { + tmp = scn->post_seq_list; + while (tmp) { + last = tmp; + tmp = tmp->next; + } + last->next = curr; + } + } + else { + if (!scn->pre_seq_list) { + scn->pre_seq_list = curr; + } + else { + tmp = scn->pre_seq_list; + while (tmp) { + last = tmp; + tmp = tmp->next; + } + last->next = curr; + } + } +} + +static int parse_sequences(struct snd_scenario *scn, FILE *f, int position) +{ + char buf[MAX_BUF], *tbuf, *control_value; + int control_len, i; + struct sequence_element *curr; + + while (fgets(buf, MAX_BUF, f) != NULL) { + + /* Check for lines with comments and ignore */ + if (buf[0] == '#') + continue; + + /* Parse current line and skip blanks */ + tbuf = buf; + while (isblank(*tbuf)) + tbuf++; + + curr = malloc(sizeof(struct sequence_element)); + if (curr == NULL) + return -ENOMEM; + bzero(curr, sizeof(struct sequence_element)); + + curr->control = malloc(sizeof(struct control_settings)); + if (curr->control == NULL) + return -ENOMEM; + bzero(curr->control, sizeof(struct control_settings)); + + curr->control->value = malloc(curr->control->count * scn->num_scenarios + * sizeof(unsigned short)); + if (curr->control->value == NULL) + return -ENOMEM; + bzero(curr->control->value, curr->control->count * scn->num_scenarios + * sizeof(unsigned short)); + + if (strncmp(tbuf, "kcontrol", 8) == 0) { + strncpy(curr->control->name, get_control_name(tbuf + 8), MAX_NAME); + control_len = strlen(curr->control->name); + /* 11 = 8 from kcontrol + 2 quotes + 1 blank */ + control_value = get_string(tbuf + 11 + control_len); + + for (i = 0; i < scn->num_kcontrols; i++) { + if (strncmp(curr->control->name, scn->control[i].name, + control_len) == 0) { + curr->sleep = 0; + curr->control->id = scn->control[i].id; + curr->control->type = scn->control[i].type; + curr->control->count = scn->control[i].count; + set_value(scn, curr->control, curr->control->count, + atoi(control_value)); + seq_list_append(scn, curr, position); + } + } + + continue; + } + + if (strncmp(tbuf, "msleep", 6) == 0) { + curr->sleep = get_int(tbuf + 6); + + /* Free control elements as we only have a sleep element + * here */ + if (curr->control) { + if (curr->control->value) + free(curr->control->value); + free(curr->control); + } + + seq_list_append(scn, curr, position); + continue; + } + } + + return 0; +} + +/* load scenario i */ +static int read_scenario_file(struct snd_scenario *scn) +{ + int fd, ret; + FILE *f; + char filename[MAX_FILE]; + struct scenario_info *info = &scn->scenario[scn->current_scenario]; + + sprintf(filename, "%s/%s/%s", ALSA_SCN_DIR, scn->card_name, + info->file); + + fd = open(filename, O_RDONLY | O_NOATIME); + if (fd < 0) { + scn_error("%s: couldn't open %s\n", __func__, filename); + return fd; + } + + f = fdopen(fd, "r"); + if (f == NULL) { + ret = errno; + goto close; + } + + ret = parse_controls(scn, f); + fclose(f); +close: + close(fd); + return ret; +} + +static int read_sequence_file(struct snd_scenario *scn, int position) +{ + int fd, ret; + FILE *f; + char filename[MAX_FILE]; + struct scenario_info *info = &scn->scenario[scn->current_scenario]; + + if (position == PRE_SEQ) { + sprintf(filename, "%s/%s/%s", ALSA_SCN_DIR, scn->card_name, + info->pre_sequence_file); + } + else { + sprintf(filename, "%s/%s/%s", ALSA_SCN_DIR, scn->card_name, + info->post_sequence_file); + } + + fd = open(filename, O_RDONLY | O_NOATIME); + if (fd < 0) { + return fd; + } + + f = fdopen(fd, "r"); + if (f == NULL) { + ret = errno; + goto close; + } + + ret = parse_sequences(scn, f, position); + fclose(f); +close: + close(fd); + return ret; +} + +static int parse_scenario(struct snd_scenario *scn, FILE *f, int line_) +{ + struct scenario_info *info; + int line = line_ - 1, id = 0, file = 0; + char buf[MAX_BUF], *tbuf; + + scn->scenario = realloc(scn->scenario, + (scn->num_scenarios + 1) * sizeof(struct scenario_info)); + if (scn->scenario == NULL) + return -ENOMEM; + info = scn->scenario + scn->num_scenarios; + + /* Set sequence filename to NULL as it is optional and we want to check + * for NULL to avoid segfaults */ + info->pre_sequence_file = NULL; + info->post_sequence_file = NULL; + + while(fgets(buf, MAX_BUF, f) != NULL) { + + line++; + if (buf[0] == '#') + continue; + + tbuf = buf; + while (isblank(*tbuf)) + tbuf++; + + if (strncmp(tbuf, "Identifier", 10) == 0) { + info->name = get_string(tbuf + 10); + if (info->name == NULL) { + scn_error("%s: failed to get Identifer\n", + __func__); + goto err; + } + id = 1; + continue; + } + + if (strncmp(tbuf, "File", 4) == 0) { + info->file = get_string(tbuf + 4); + if (info->file == NULL) { + scn_error("%s: failed to get File\n", + __func__); + goto err; + } + file = 1; + continue; + } + + if (strncmp(tbuf, "QoS", 3) == 0) { + info->qos = get_enum(tbuf + 3); + if (info->qos < 0) { + scn_error("%s: failed to get QoS\n", + __func__); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterPlaybackVolume", 20) == 0) { + info->playback_volume_id = get_int(tbuf + 20); + if (info->playback_volume_id < 0) { + scn_error("%s: failed to get MasterPlaybackVolume\n", + __func__); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterPlaybackSwitch", 20) == 0) { + info->playback_switch_id = get_int(tbuf + 20); + if (info->playback_switch_id < 0) { + scn_error("%s: failed to get MasterPlaybackSwitch\n", + __func__); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterCaptureVolume", 19) == 0) { + info->capture_volume_id = get_int(tbuf + 19); + if (info->capture_volume_id < 0) { + scn_error("%s: failed to get MasterCaptureVolume\n", + __func__); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterCaptureSwitch", 19) == 0) { + info->capture_switch_id = get_int(tbuf + 19); + if (info->capture_switch_id < 0) { + scn_error("%s: failed to get MasterCaptureSwitch\n", + __func__); + goto err; + } + continue; + } + + if (strncmp(tbuf, "PreSequenceFile", 15) == 0) { + info->pre_sequence_file = get_string(tbuf + 15); + if (info->pre_sequence_file == NULL) { + scn_error("%s: failed to get PreSequenceFile\n", + __func__); + goto err; + } + continue; + } + + if (strncmp(tbuf, "PostSequenceFile", 16) == 0) { + info->post_sequence_file = get_string(tbuf + 16); + if (info->post_sequence_file == NULL) { + scn_error("%s: failed to get PostSequenceFile\n", + __func__); + goto err; + } + continue; + } + + if (strncmp(tbuf, "EndSection", 10) == 0) { + break; + } + } + + if (file & id) { + scn->num_scenarios++; + return 0; + } +err: + if (file) { + free(info->file); + info->file = NULL; + } + if (id) { + free(info->name); + info->name = NULL; + } + return -EINVAL; +} + +static int read_master_file(struct snd_scenario *scn, FILE *f) +{ + int line = 0, ret = 0, i; + char buf[MAX_BUF], *tbuf; + + /* parse master config sections */ + while(fgets(buf, MAX_BUF, f) != NULL) { + + if (buf[0] == '#') { + line++; + continue; + } + + if (strncmp(buf, "Section", 7) == 0) { + + tbuf = buf + 7; + while (isblank(*tbuf)) + tbuf++; + + if (strncmp(tbuf, ""Scenario"", 10) == 0) { + line = parse_scenario(scn, f, line); + if (line < 0) { + scn_error("%s: failed to parse " + "scenario\n", __func__); + goto err; + } + continue; + } + } + line++; + } + + /* copy ptrs to scenario names */ + scn->list = malloc(scn->num_scenarios * sizeof(char *)); + if (scn->list == NULL) + ret = -ENOMEM; + for (i = 0; i < scn->num_scenarios; i++) + scn->list[i] = scn->scenario[i].name; + +err: + if (ferror(f)) { + scn_error("%s: failed to read master\n", __func__); + return ferror(f); + } + return ret; +} + +/* load scenario i */ +static int import_master_config(struct snd_scenario *scn) +{ + int fd, ret; + FILE *f; + char filename[MAX_FILE]; + + sprintf(filename, "%s/%s.conf", ALSA_SCN_DIR, scn->card_name); + + fd = open(filename, O_RDONLY | O_NOATIME); + if (fd < 0) { + scn_error("%s: couldn't open %s\n", __func__, filename); + return fd; + } + + f = fdopen(fd, "r"); + if (f == NULL) { + ret = errno; + goto close; + } + + ret = read_master_file(scn, f); + fclose(f); +close: + close(fd); + return ret; +} + +/* parse_card_controls + * @scn: scenario + * + * Parse sound card and store control data in memory db. + */ +static int parse_card_controls(struct snd_scenario *scn) +{ + struct control_settings *control; + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + snd_ctl_elem_list_t *list; + int ret, i; + + snd_ctl_card_info_alloca(&info); + snd_ctl_elem_list_alloca(&list); + + /* open and load snd card */ + ret = snd_ctl_open(&handle, scn->card_name, SND_CTL_READONLY); + if (ret < 0) { + scn_error("%s: control %s open retor: %s\n", __func__, + scn->card_name, snd_strerror(ret)); + return ret; + } + + ret = snd_ctl_card_info(handle, info); + if (ret < 0) { + scn_error("%s :control %s local retor: %s\n", __func__, + scn->card_name, snd_strerror(ret)); + goto close; + } + + ret = snd_ctl_elem_list(handle, list); + if (ret < 0) { + scn_error("%s: cannot determine controls: %s\n", __func__, + snd_strerror(ret)); + goto close; + } + + scn->num_kcontrols = snd_ctl_elem_list_get_count(list); + if (scn->num_kcontrols < 0) { + ret = 0; + goto close; + } + + snd_ctl_elem_list_set_offset(list, 0); + if (snd_ctl_elem_list_alloc_space(list, scn->num_kcontrols) < 0) { + scn_error("%s: not enough memory...\n", __func__); + ret = -ENOMEM; + goto close; + } + if ((ret = snd_ctl_elem_list(handle, list)) < 0) { + scn_error("%s: cannot determine controls: %s\n", __func__, + snd_strerror(ret)); + goto free; + } + + /* allocate db memory for controls */ + scn->control = calloc(scn->num_kcontrols, + sizeof(struct control_settings)); + if (scn->control == NULL) { + ret = -ENOMEM; + goto free; + } + control = scn->control; + + /* iterate through each kcontrol and add to db */ + for (i = 0; i < scn->num_kcontrols; ++i) { + snd_ctl_elem_id_t *id; + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_list_get_id(list, i, id); + + ret = add_control(handle, id, control++); + if (ret < 0) { + scn_error("%s: failed to add control error %s\n", + __func__, snd_strerror(ret)); + goto close; + } + } +free: + snd_ctl_elem_list_free_space(list); +close: + snd_ctl_close(handle); + return ret; +} + +/* import_scenario_files - + * @scn: scenario + * + * Read and parse scenario_info files the store in memory. + */ +static int import_scenario_files(struct snd_scenario *scn) +{ + int ret; + + ret = import_master_config(scn); + if (ret < 0) { + scn_error("%s: failed to parse master scenario config\n", + __func__); + return ret; + } + + for (scn->current_scenario = 0; + scn->current_scenario < scn->num_scenarios; + scn->current_scenario++) { + + ret = read_scenario_file(scn); + if (ret < 0) { + scn_error("%s: failed to parse scenario %s\n", + __func__, + scn->scenario[scn->current_scenario].name); + scn->current_scenario = -1; + return ret; + } + + if (scn->scenario[scn->current_scenario].pre_sequence_file != NULL) { + ret = read_sequence_file(scn, PRE_SEQ); + if (ret < 0) { + scn_stdout("Warning: PreSequence file defined but" + " missing in scenario "%s"\n", + scn->scenario[scn->current_scenario].name); + } + } + + if (scn->scenario[scn->current_scenario].post_sequence_file != NULL) { + ret = read_sequence_file(scn, POST_SEQ); + if (ret < 0) { + scn_stdout("Warning: PostSequence file defined but" + " missing in scenario "%s"\n", + scn->scenario[scn->current_scenario].name); + } + } + + } + return 0; +} + +/* free all resorces */ +static void free_scn(struct snd_scenario *scn) +{ + //TODO: valgrind to make sure. + int i; + + if (scn == NULL) + return; + + if (scn->control) { + if (scn->control->value) + free(scn->control->value); + free(scn->control); + } + + if (scn->list) + free(scn->list); + if (scn->card_name) + free(scn->card_name); + if (scn->pre_seq_list) + free(scn->pre_seq_list); + if (scn->post_seq_list) + free(scn->post_seq_list); + + if (scn->scenario) { + for (i = 0; i < scn->num_scenarios; i++) { + struct scenario_info *info = &scn->scenario[i]; + + if (info->name) + free(info->name); + if (info->file) + free(info->file); + if (info->pre_sequence_file) + free(info->pre_sequence_file); + if (info->post_sequence_file) + free(info->post_sequence_file); + } + free(scn->scenario); + } + free(scn); +} + +/* + * Init sound card scenario db. + */ +struct snd_scenario *snd_scenario_open(const char *card_name) +{ + struct snd_scenario *scn; + int err; + + // TODO: locking and + // check if card_name scn is already loaded, + // if so reuse to conserve ram. + + scn = malloc(sizeof(struct snd_scenario)); + if (scn == NULL) + return NULL; + bzero(scn, sizeof(struct snd_scenario)); + scn->card_name = strdup(card_name); + if (scn->card_name == NULL) { + free(scn); + return NULL; + } + + /* get info about sound card */ + err = parse_card_controls(scn); + if (err < 0) { + free_scn(scn); + return NULL; + } + + /* get info on scenarios and verify against card */ + err = import_scenario_files(scn); + if (err < 0) { + free_scn(scn); + return NULL; + } + + return scn; +} + +/* + * Reload and reparse scenario db. + */ +int snd_scenario_reload(struct snd_scenario *scn) +{ + free_scn(scn); + + scn->num_kcontrols = parse_card_controls(scn); + if (scn->num_kcontrols <= 0) { + free_scn(scn); + return -EINVAL; + } + + scn->num_scenarios = import_scenario_files(scn); + if (scn->num_scenarios <= 0) { + return -EINVAL; + } + + return 0; +} + +void snd_scenario_close(struct snd_scenario *scn) +{ + free_scn(scn); +} + +static int set_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, + struct snd_scenario *scn) +{ + struct control_settings *setting; + int ret, count, i, idnum; + snd_ctl_elem_info_t *info; + snd_ctl_elem_type_t type; + snd_ctl_elem_value_t *control; + + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_value_alloca(&control); + + snd_ctl_elem_info_set_id(info, id); + ret = snd_ctl_elem_info(handle, info); + if (ret < 0) { + scn_error("%s: failed to get ctl info\n", __func__); + return ret; + } + + snd_ctl_elem_value_set_id(control, id); + snd_ctl_elem_read(handle, control); + + idnum = snd_ctl_elem_id_get_numid(id); + for (i = 0; i < scn->num_kcontrols; i++) { + setting = &scn->control[i]; + if (setting->id == idnum) + goto set_val; + } + scn_error("%s: failed to find control %d\n", __func__, idnum); + return 0; + +set_val: + type = snd_ctl_elem_info_get_type(info); + count = snd_ctl_elem_info_get_count(info); + if (count == 0) + return 0; + + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_boolean(control, i, + get_value(scn, setting, i)); + break; + case SND_CTL_ELEM_TYPE_INTEGER: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer(control, i, + get_value(scn, setting, i)); + + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer64(control, i, + get_value(scn, setting, i)); + + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_enumerated(control, i, + get_value(scn, setting, i)); + + break; + case SND_CTL_ELEM_TYPE_BYTES: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_byte(control, i, + get_value(scn, setting, i)); + break; + default: + break; + } + + ret = snd_ctl_elem_write(handle, control); + if (ret < 0) { + scn_error("%s: control %s failed: %s\n", __func__, + setting->name, snd_strerror(ret)); + scn_error("%s: count %d type: %d\n", __func__, + count, type); + for (i = 0; i < count; i++) + fprintf(stderr, "%d ", get_value(scn, setting, i)); + return ret; + } + return 0; +} + +static void exec_sequence(struct sequence_element *seq, struct snd_scenario + *scn, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + int count = snd_ctl_elem_list_get_count(list); + while (seq) { + if (seq->sleep) + usleep(seq->sleep); + else { + snd_ctl_elem_id_t *id; + snd_ctl_elem_id_alloca(&id); + int ret, i, numid; + /* Where is id lookup from numid if you need it? */ + for (i = 0; i < count; ++i) { + snd_ctl_elem_list_get_id(list, i, id); + numid = snd_ctl_elem_id_get_numid(id); + if (numid == seq->control->id) { + ret = set_control(handle, id, scn); + if (ret < 0) { + scn_error("%s: failed to set control %s\n", + __func__, scn->card_name); + } + break; + } + } + } + seq = seq->next; + } +} + +int snd_scenario_set_scn(struct snd_scenario *scn, const char *name) +{ + snd_ctl_card_info_t *info; + snd_ctl_elem_list_t *list; + snd_ctl_t *handle; + int ret, count, i; + + snd_ctl_card_info_alloca(&info); + snd_ctl_elem_list_alloca(&list); + + /* find scenario name */ + for (i = 0; i < scn->num_scenarios; i++) { + if (!strcmp(scn->scenario[i].name, name)) + goto found; + } + scn_error("%s: scenario %s not found\n", __func__, name); + return -EINVAL; + +found: + /* scenario found - now open card */ + scn->current_scenario = i; + ret = snd_ctl_open(&handle, scn->card_name, 0); + if (ret) { + scn_error("%s: control %s open error: %s\n", __func__, + scn->card_name, snd_strerror(ret)); + return ret; + } + + ret = snd_ctl_card_info(handle, info); + if (ret < 0) { + scn_error("%s :control %s local retor: %s\n", __func__, + scn->card_name, snd_strerror(ret)); + goto close; + } + + ret = snd_ctl_elem_list(handle, list); + if (ret < 0) { + scn_error("%s: cannot determine controls: %s\n", __func__, + snd_strerror(ret)); + goto close; + } + + count = snd_ctl_elem_list_get_count(list); + if (count < 0) { + ret = 0; + goto close; + } + + snd_ctl_elem_list_set_offset(list, 0); + if (snd_ctl_elem_list_alloc_space(list, count) < 0) { + scn_error("%s: not enough memory...\n", __func__); + ret = -ENOMEM; + goto close; + } + if ((ret = snd_ctl_elem_list(handle, list)) < 0) { + scn_error("%s: cannot determine controls: %s\n", __func__, + snd_strerror(ret)); + goto free; + } + + /* If we have a sequence list that should be executed before the new + * scenario is set do it now */ + if (scn->pre_seq_list) + exec_sequence(scn->pre_seq_list, scn, list, handle); + + /* iterate through each kcontrol and add to db */ + for (i = 0; i < count; ++i) { + snd_ctl_elem_id_t *id; + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_list_get_id(list, i, id); + + ret = set_control(handle, id, scn); + if (ret < 0) { + scn_error("%s: failed to set control %s\n", __func__, + scn->card_name); + } + } + + /* If we have a sequence list that should be executed after the new + * scenario is set do it now */ + if (scn->post_seq_list) + exec_sequence(scn->post_seq_list, scn, list, handle); + +free: + snd_ctl_elem_list_free_space(list); +close: + snd_ctl_close(handle); + return ret; +} + +int snd_scenario_dump(const char *card_name) +{ + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + snd_ctl_elem_list_t *list; + int ret, i, count; + + snd_ctl_card_info_alloca(&info); + snd_ctl_elem_list_alloca(&list); + + /* open and load snd card */ + ret = snd_ctl_open(&handle, card_name, SND_CTL_READONLY); + if (ret < 0) { + scn_error("%s: control %s open retor: %s\n", __func__, card_name, + snd_strerror(ret)); + return ret; + } + + ret = snd_ctl_card_info(handle, info); + if (ret < 0) { + scn_error("%s :control %s local retor: %s\n", __func__, + card_name, snd_strerror(ret)); + goto close; + } + + ret = snd_ctl_elem_list(handle, list); + if (ret < 0) { + scn_error("%s: cannot determine controls: %s\n", __func__, + snd_strerror(ret)); + goto close; + } + + count = snd_ctl_elem_list_get_count(list); + if (count < 0) { + ret = 0; + goto close; + } + + snd_ctl_elem_list_set_offset(list, 0); + if (snd_ctl_elem_list_alloc_space(list, count) < 0) { + scn_error("%s: not enough memory...\n", __func__); + ret = -ENOMEM; + goto close; + } + if ((ret = snd_ctl_elem_list(handle, list)) < 0) { + scn_error("%s: cannot determine controls: %s\n", __func__, + snd_strerror(ret)); + goto free; + } + + /* iterate through each kcontrol and add to db */ + for (i = 0; i < count; ++i) { + snd_ctl_elem_id_t *id; + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_list_get_id(list, i, id); + + ret = dump_control(handle, id); + if (ret < 0) { + scn_error("%s: cannot determine controls: %s\n", + __func__, snd_strerror(ret)); + goto free; + } + } +free: + snd_ctl_elem_list_free_space(list); +close: + snd_ctl_close(handle); + return ret; +} + +const char *snd_scenario_get_scn(struct snd_scenario *scn) +{ + if (scn->current_scenario > 0 && scn->current_scenario < MAX_SCN) + return scn->scenario[scn->current_scenario].name; + else + return NULL; +} + +int snd_scenario_set_qos(struct snd_scenario *scn, int qos) +{ + /* TODO: change QoS kcontrols */ + scn->scenario[scn->current_scenario].qos = qos; + return 0; +} + +int snd_scenario_get_qos(struct snd_scenario *scn) +{ + return scn->scenario[scn->current_scenario].qos; +} + +int snd_scenario_get_master_playback_volume(struct snd_scenario *scn) +{ + return scn->scenario[scn->current_scenario].playback_volume_id; +} + +int snd_scenario_get_master_playback_switch(struct snd_scenario *scn) +{ + return scn->scenario[scn->current_scenario].playback_switch_id; +} + +int snd_scenario_get_master_capture_volume(struct snd_scenario *scn) +{ + return scn->scenario[scn->current_scenario].capture_volume_id; +} + +int snd_scenario_get_master_capture_switch(struct snd_scenario *scn) +{ + return scn->scenario[scn->current_scenario].capture_switch_id; +} + +int snd_scenario_list(struct snd_scenario *scn, const char **list[]) +{ + *list = scn->list; + return scn->num_scenarios; +}