| /* 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 <errno.h> |
| #include <stdlib.h> |
| #include <syslog.h> |
| #include "cras_dsp_ini.h" |
| #include "iniparser_wrapper.h" |
| |
| #define MAX_NR_PORT 128 /* the max number of ports for a plugin */ |
| #define MAX_PORT_NAME_LENGTH 20 /* names like "output_32" */ |
| #define MAX_MOCK_INI_CH 20 /* Max number of channels to create mock ini */ |
| |
| /* 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 + 1]; |
| 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; |
| |
| ARRAY_ELEMENT_FOREACH (&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; |
| |
| ARRAY_ELEMENT_FOREACH (&ini->plugins, i, plugin) { |
| ARRAY_ELEMENT_FOREACH (&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; |
| } |
| } |
| } |
| |
| /* Adds a port to a plugin with specified flow id and direction. */ |
| static void add_audio_port(struct ini *ini, struct plugin *plugin, int flow_id, |
| enum port_direction port_direction) |
| { |
| struct port *p; |
| p = ARRAY_APPEND_ZERO(&plugin->ports); |
| p->type = PORT_AUDIO; |
| p->flow_id = flow_id; |
| p->init_value = 0; |
| p->direction = port_direction; |
| } |
| |
| /* Fills fields for a swap_lr plugin.*/ |
| static void fill_swap_lr_plugin(struct ini *ini, struct plugin *plugin, |
| int input_flowid_0, int input_flowid_1, |
| int output_flowid_0, int output_flowid_1) |
| { |
| plugin->title = "swap_lr"; |
| plugin->library = "builtin"; |
| plugin->label = "swap_lr"; |
| plugin->purpose = "playback"; |
| plugin->disable_expr = cras_expr_expression_parse("swap_lr_disabled"); |
| |
| add_audio_port(ini, plugin, input_flowid_0, PORT_INPUT); |
| add_audio_port(ini, plugin, input_flowid_1, PORT_INPUT); |
| add_audio_port(ini, plugin, output_flowid_0, PORT_OUTPUT); |
| add_audio_port(ini, plugin, output_flowid_1, PORT_OUTPUT); |
| } |
| |
| /* Adds a new flow with name. If there is already a flow with the name, returns |
| * INVALID_FLOW_ID. |
| */ |
| static int add_new_flow(struct ini *ini, const char *name) |
| { |
| struct flow *flow; |
| int i = lookup_flow(ini, name); |
| if (i != -1) |
| return INVALID_FLOW_ID; |
| i = ARRAY_COUNT(&ini->flows); |
| flow = ARRAY_APPEND_ZERO(&ini->flows); |
| flow->name = name; |
| return i; |
| } |
| |
| /* Finds the first playback sink plugin in ini. */ |
| struct plugin *find_first_playback_sink_plugin(struct ini *ini) |
| { |
| int i; |
| struct plugin *plugin; |
| |
| ARRAY_ELEMENT_FOREACH (&ini->plugins, i, plugin) { |
| if (strcmp(plugin->library, "builtin") != 0) |
| continue; |
| if (strcmp(plugin->label, "sink") != 0) |
| continue; |
| if (!plugin->purpose || |
| strcmp(plugin->purpose, "playback") != 0) |
| continue; |
| return plugin; |
| } |
| |
| return NULL; |
| } |
| |
| /* Inserts a swap_lr plugin before sink. Handles the port change such that |
| * the port originally connects to sink will connect to swap_lr. |
| */ |
| static int insert_swap_lr_plugin(struct ini *ini) |
| { |
| struct plugin *swap_lr, *sink; |
| int sink_input_flowid_0, sink_input_flowid_1; |
| int swap_lr_output_flowid_0, swap_lr_output_flowid_1; |
| |
| /* Only add swap_lr plugin for two-channel playback dsp. |
| * TODO(cychiang): Handle multiple sinks if needed. |
| */ |
| sink = find_first_playback_sink_plugin(ini); |
| if ((sink == NULL) || ARRAY_COUNT(&sink->ports) != 2) |
| return 0; |
| |
| /* Gets the original flow ids of the sink input ports. */ |
| sink_input_flowid_0 = ARRAY_ELEMENT(&sink->ports, 0)->flow_id; |
| sink_input_flowid_1 = ARRAY_ELEMENT(&sink->ports, 1)->flow_id; |
| |
| /* Create new flow ids for swap_lr output ports. */ |
| swap_lr_output_flowid_0 = add_new_flow(ini, "{swap_lr_out:0}"); |
| swap_lr_output_flowid_1 = add_new_flow(ini, "{swap_lr_out:1}"); |
| |
| if (swap_lr_output_flowid_0 == INVALID_FLOW_ID || |
| swap_lr_output_flowid_1 == INVALID_FLOW_ID) { |
| syslog(LOG_ERR, "Can not create flow id for swap_lr_out"); |
| return -EINVAL; |
| } |
| |
| /* Creates a swap_lr plugin and sets the input and output ports. */ |
| swap_lr = ARRAY_APPEND_ZERO(&ini->plugins); |
| fill_swap_lr_plugin(ini, swap_lr, sink_input_flowid_0, |
| sink_input_flowid_1, swap_lr_output_flowid_0, |
| swap_lr_output_flowid_1); |
| |
| /* Look up first sink again because ini->plugins could be realloc'ed */ |
| sink = find_first_playback_sink_plugin(ini); |
| |
| /* The flow ids of sink input ports should be changed to flow ids of |
| * {swap_lr_out:0}, {swap_lr_out:1}. */ |
| ARRAY_ELEMENT(&sink->ports, 0)->flow_id = swap_lr_output_flowid_0; |
| ARRAY_ELEMENT(&sink->ports, 1)->flow_id = swap_lr_output_flowid_1; |
| |
| return 0; |
| } |
| |
| struct ini *create_mock_ini(const char *purpose, unsigned int num_channels) |
| { |
| static char mock_flow_names[MAX_MOCK_INI_CH][9] = { |
| "{tmp:0}", "{tmp:1}", "{tmp:2}", "{tmp:3}", "{tmp:4}", |
| "{tmp:5}", "{tmp:6}", "{tmp:7}", "{tmp:8}", "{tmp:9}", |
| "{tmp:10}", "{tmp:11}", "{tmp:12}", "{tmp:13}", "{tmp:14}", |
| "{tmp:15}", "{tmp:16}", "{tmp:17}", "{tmp:18}", "{tmp:19}", |
| }; |
| struct ini *ini; |
| struct plugin *source, *sink; |
| int tmp_flow_ids[MAX_MOCK_INI_CH]; |
| int i; |
| |
| if (num_channels > MAX_MOCK_INI_CH) { |
| syslog(LOG_ERR, "Unable to create %u channels of mock ini", |
| num_channels); |
| return NULL; |
| } |
| |
| ini = calloc(1, sizeof(struct ini)); |
| if (!ini) { |
| syslog(LOG_ERR, "no memory for ini struct"); |
| return NULL; |
| } |
| |
| for (i = 0; i < num_channels; i++) |
| tmp_flow_ids[i] = add_new_flow(ini, mock_flow_names[i]); |
| |
| source = ARRAY_APPEND_ZERO(&ini->plugins); |
| source->title = "source"; |
| source->library = "builtin"; |
| source->label = "source"; |
| source->purpose = purpose; |
| |
| for (i = 0; i < num_channels; i++) |
| add_audio_port(ini, source, tmp_flow_ids[i], PORT_OUTPUT); |
| |
| sink = ARRAY_APPEND_ZERO(&ini->plugins); |
| sink->title = "sink"; |
| sink->library = "builtin"; |
| sink->label = "sink"; |
| sink->purpose = purpose; |
| |
| for (i = 0; i < num_channels; i++) |
| add_audio_port(ini, sink, tmp_flow_ids[i], PORT_INPUT); |
| |
| fill_flow_info(ini); |
| |
| return ini; |
| } |
| |
| 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; |
| int rc; |
| |
| ini = calloc(1, sizeof(struct ini)); |
| if (!ini) { |
| syslog(LOG_ERR, "no memory for ini struct"); |
| return NULL; |
| } |
| |
| dict = iniparser_load_wrapper((char *)ini_filename); |
| if (!dict) { |
| syslog(LOG_DEBUG, "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; |
| } |
| |
| /* Insert a swap_lr plugin before sink. */ |
| rc = insert_swap_lr_plugin(ini); |
| if (rc < 0) { |
| syslog(LOG_ERR, "failed to insert swap_lr plugin"); |
| 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 */ |
| ARRAY_ELEMENT_FOREACH (&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; |
| } |
| |
| void cras_dsp_ini_dump(struct dumper *d, struct ini *ini) |
| { |
| int i, j; |
| struct plugin *plugin; |
| struct port *port; |
| const struct flow *flow; |
| |
| dumpf(d, "---- ini dump begin ---\n"); |
| dumpf(d, "ini->dict = %p\n", ini->dict); |
| |
| dumpf(d, "number of plugins = %d\n", ARRAY_COUNT(&ini->plugins)); |
| ARRAY_ELEMENT_FOREACH (&ini->plugins, i, plugin) { |
| dumpf(d, "[plugin %d: %s]\n", i, plugin->title); |
| dumpf(d, "library=%s\n", plugin->library); |
| dumpf(d, "label=%s\n", plugin->label); |
| dumpf(d, "purpose=%s\n", plugin->purpose); |
| dumpf(d, "disable=%p\n", plugin->disable_expr); |
| ARRAY_ELEMENT_FOREACH (&plugin->ports, j, port) { |
| dumpf(d, |
| " [%s port %d] type=%s, flow_id=%d, value=%g\n", |
| port_direction_str(port->direction), j, |
| port_type_str(port->type), port->flow_id, |
| port->init_value); |
| } |
| } |
| |
| dumpf(d, "number of flows = %d\n", ARRAY_COUNT(&ini->flows)); |
| ARRAY_ELEMENT_FOREACH (&ini->flows, i, flow) { |
| dumpf(d, " [flow %d] %s, %s, %s:%d -> %s:%d\n", i, flow->name, |
| port_type_str(flow->type), plugin_title(flow->from), |
| flow->from_port, plugin_title(flow->to), flow->to_port); |
| } |
| |
| dumpf(d, "---- ini dump end ----\n"); |
| } |