| /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <stdlib.h> |
| #include <syslog.h> |
| #include "cras_dsp_ini.h" |
| |
| #define MAX_INI_KEY_LENGTH 64 /* names like "output_source:output_0" */ |
| #define MAX_NR_PORT 128 /* the max number of ports for a plugin */ |
| #define MAX_PORT_NAME_LENGTH 20 /* names like "output_32" */ |
| |
| /* Format of the ini file (See dsp.ini.sample for an example). |
| |
| - Each section in the ini file specifies a plugin. The section name is |
| just an identifier. The "library" and "label" attributes in a |
| section must be defined. The "library" attribute is the name of the |
| shared library from which this plugin will be loaded, or a special |
| value "builtin" for built-in plugins. The "label" attribute specify |
| which plugin inside the shared library should be loaded. |
| |
| - Built-in plugins have an attribute "label" which has value "source" |
| or "sink". It defines where the audio data flows into and flows out |
| of the pipeline. Built-in plugins also have a attribute "purpose" |
| which has the value "playback" or "capture". It defines which |
| pipeline these plugins belong to. |
| |
| - Each plugin can have an optional "disable expression", which defines |
| under which conditions the plugin is disabled. |
| |
| - Each plugin have some ports which specify the parameters for the |
| plugin or to specify connections to other plugins. The ports in each |
| plugin are numbered from 0. Each port is either an input port or an |
| output port, and each port is either an audio port or a control |
| port. The connections between two ports are expressed by giving the |
| same value to both ports. For audio ports, the value should be |
| "{identifier}". For control ports, the value shoule be |
| "<identifier>". For example, the following fragment |
| |
| [plugin1] |
| ... |
| output_4={audio_left} |
| output_5={audio_right} |
| |
| [plugin2] |
| ... |
| input_0={audio_left} |
| |
| [plugin3] |
| ... |
| input_2={audio_right} |
| |
| specifies these connections: |
| port 4 of plugin1 --> port 0 of plugin2 |
| port 5 of plugin1 --> port 2 of plugin3 |
| |
| */ |
| |
| static const char *getstring(struct ini *ini, const char *sec_name, |
| const char *key) |
| { |
| char full_key[MAX_INI_KEY_LENGTH]; |
| snprintf(full_key, sizeof(full_key), "%s:%s", sec_name, key); |
| return iniparser_getstring(ini->dict, full_key, NULL); |
| } |
| |
| static int lookup_flow(struct ini *ini, const char *name) |
| { |
| int i; |
| const struct flow *flow; |
| |
| FOR_ARRAY_ELEMENT(&ini->flows, i, flow) { |
| if (strcmp(flow->name, name) == 0) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| static int lookup_or_add_flow(struct ini *ini, const char *name) |
| { |
| struct flow *flow; |
| int i = lookup_flow(ini, name); |
| if (i != -1) |
| return i; |
| i = ARRAY_COUNT(&ini->flows); |
| flow = ARRAY_APPEND_ZERO(&ini->flows); |
| flow->name = name; |
| return i; |
| } |
| |
| static int parse_ports(struct ini *ini, const char *sec_name, |
| struct plugin *plugin) |
| { |
| char key[MAX_PORT_NAME_LENGTH]; |
| const char *str; |
| int i; |
| struct port *p; |
| int direction; |
| |
| for (i = 0; i < MAX_NR_PORT; i++) { |
| direction = PORT_INPUT; |
| snprintf(key, sizeof(key), "input_%d", i); |
| str = getstring(ini, sec_name, key); |
| if (str == NULL) { |
| direction = PORT_OUTPUT; |
| snprintf(key, sizeof(key), "output_%d", i); |
| str = getstring(ini, sec_name, key); |
| if (str == NULL) |
| break; /* no more ports */ |
| } |
| |
| if (*str == '\0') { |
| syslog(LOG_ERR, "empty value for %s:%s", sec_name, key); |
| return -1; |
| } |
| |
| if (str[0] == '<' || str[0] == '{') { |
| p = ARRAY_APPEND_ZERO(&plugin->ports); |
| p->type = (str[0] == '<') ? PORT_CONTROL : PORT_AUDIO; |
| p->flow_id = lookup_or_add_flow(ini, str); |
| p->init_value = 0; |
| } else { |
| char *endptr; |
| float init_value = strtof(str, &endptr); |
| if (endptr == str) { |
| syslog(LOG_ERR, "cannot parse number from '%s'", |
| str); |
| } |
| p = ARRAY_APPEND_ZERO(&plugin->ports); |
| p->type = PORT_CONTROL; |
| p->flow_id = INVALID_FLOW_ID; |
| p->init_value = init_value; |
| } |
| p->direction = direction; |
| } |
| |
| return 0; |
| } |
| |
| static int parse_plugin_section(struct ini *ini, const char *sec_name, |
| struct plugin *p) |
| { |
| p->title = sec_name; |
| p->library = getstring(ini, sec_name, "library"); |
| p->label = getstring(ini, sec_name, "label"); |
| p->purpose = getstring(ini, sec_name, "purpose"); |
| p->disable_expr = cras_expr_expression_parse( |
| getstring(ini, sec_name, "disable")); |
| |
| if (p->library == NULL || p->label == NULL) { |
| syslog(LOG_ERR, "A plugin must have library and label: %s", |
| sec_name); |
| return -1; |
| } |
| |
| if (parse_ports(ini, sec_name, p) < 0) { |
| syslog(LOG_ERR, "Failed to parse ports: %s", sec_name); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void fill_flow_info(struct ini *ini) |
| { |
| int i, j; |
| struct plugin *plugin; |
| struct port *port; |
| struct flow *flow; |
| struct plugin **pplugin; |
| int *pport; |
| |
| FOR_ARRAY_ELEMENT(&ini->plugins, i, plugin) { |
| FOR_ARRAY_ELEMENT(&plugin->ports, j, port) { |
| int flow_id = port->flow_id; |
| if (flow_id == INVALID_FLOW_ID) |
| continue; |
| flow = ARRAY_ELEMENT(&ini->flows, flow_id); |
| flow->type = port->type; |
| if (port->direction == PORT_INPUT) { |
| pplugin = &flow->to; |
| pport = &flow->to_port; |
| } else { |
| pplugin = &flow->from; |
| pport = &flow->from_port; |
| } |
| *pplugin = plugin; |
| *pport = j; |
| } |
| } |
| } |
| |
| |
| struct ini *cras_dsp_ini_create(const char *ini_filename) |
| { |
| struct ini *ini; |
| dictionary *dict; |
| int nsec, i; |
| const char *sec_name; |
| struct plugin *plugin; |
| |
| ini = calloc(1, sizeof(struct ini)); |
| if (!ini) { |
| syslog(LOG_ERR, "no memory for ini struct"); |
| return NULL; |
| } |
| |
| dict = iniparser_load((char *)ini_filename); |
| if (!dict) { |
| syslog(LOG_ERR, "no ini file %s", ini_filename); |
| goto bail; |
| } |
| ini->dict = dict; |
| |
| /* Parse the plugin sections */ |
| nsec = iniparser_getnsec(dict); |
| for (i = 0; i < nsec; i++) { |
| sec_name = iniparser_getsecname(dict, i); |
| plugin = ARRAY_APPEND_ZERO(&ini->plugins); |
| if (parse_plugin_section(ini, sec_name, plugin) < 0) |
| goto bail; |
| } |
| |
| /* Fill flow info now because now the plugin array won't change */ |
| fill_flow_info(ini); |
| |
| return ini; |
| bail: |
| cras_dsp_ini_free(ini); |
| return NULL; |
| } |
| |
| void cras_dsp_ini_free(struct ini *ini) |
| { |
| struct plugin *p; |
| int i; |
| |
| /* free plugins */ |
| FOR_ARRAY_ELEMENT(&ini->plugins, i, p) { |
| cras_expr_expression_free(p->disable_expr); |
| ARRAY_FREE(&p->ports); |
| } |
| ARRAY_FREE(&ini->plugins); |
| ARRAY_FREE(&ini->flows); |
| |
| if (ini->dict) { |
| iniparser_freedict(ini->dict); |
| ini->dict = NULL; |
| } |
| |
| free(ini); |
| } |
| |
| static const char *port_direction_str(enum port_direction port_direction) |
| { |
| switch (port_direction) { |
| case PORT_INPUT: return "input"; |
| case PORT_OUTPUT: return "output"; |
| default: return "unknown"; |
| } |
| } |
| |
| static const char *port_type_str(enum port_type port_type) |
| { |
| switch (port_type) { |
| case PORT_CONTROL: return "control"; |
| case PORT_AUDIO: return "audio"; |
| default: return "unknown"; |
| } |
| } |
| |
| static const char *plugin_title(struct plugin *plugin) |
| { |
| if (plugin == NULL) |
| return "(null)"; |
| return plugin->title; |
| } |