Home | History | Annotate | Download | only in server
      1 /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2  * Use of this source code is governed by a BSD-style license that can be
      3  * found in the LICENSE file.
      4  */
      5 
      6 #include <errno.h>
      7 #include <stdlib.h>
      8 #include <syslog.h>
      9 #include "cras_dsp_ini.h"
     10 
     11 #define MAX_INI_KEY_LENGTH 64  /* names like "output_source:output_0" */
     12 #define MAX_NR_PORT 128	/* the max number of ports for a plugin */
     13 #define MAX_PORT_NAME_LENGTH 20 /* names like "output_32" */
     14 
     15 /* Format of the ini file (See dsp.ini.sample for an example).
     16 
     17 - Each section in the ini file specifies a plugin. The section name is
     18   just an identifier. The "library" and "label" attributes in a
     19   section must be defined. The "library" attribute is the name of the
     20   shared library from which this plugin will be loaded, or a special
     21   value "builtin" for built-in plugins. The "label" attribute specify
     22   which plugin inside the shared library should be loaded.
     23 
     24 - Built-in plugins have an attribute "label" which has value "source"
     25   or "sink". It defines where the audio data flows into and flows out
     26   of the pipeline.  Built-in plugins also have a attribute "purpose"
     27   which has the value "playback" or "capture". It defines which
     28   pipeline these plugins belong to.
     29 
     30 - Each plugin can have an optional "disable expression", which defines
     31   under which conditions the plugin is disabled.
     32 
     33 - Each plugin have some ports which specify the parameters for the
     34   plugin or to specify connections to other plugins. The ports in each
     35   plugin are numbered from 0. Each port is either an input port or an
     36   output port, and each port is either an audio port or a control
     37   port. The connections between two ports are expressed by giving the
     38   same value to both ports. For audio ports, the value should be
     39   "{identifier}". For control ports, the value shoule be
     40   "<identifier>". For example, the following fragment
     41 
     42   [plugin1]
     43   ...
     44   output_4={audio_left}
     45   output_5={audio_right}
     46 
     47   [plugin2]
     48   ...
     49   input_0={audio_left}
     50 
     51   [plugin3]
     52   ...
     53   input_2={audio_right}
     54 
     55   specifies these connections:
     56   port 4 of plugin1 --> port 0 of plugin2
     57   port 5 of plugin1 --> port 2 of plugin3
     58 
     59 */
     60 
     61 static const char *getstring(struct ini *ini, const char *sec_name,
     62 			     const char *key)
     63 {
     64 	char full_key[MAX_INI_KEY_LENGTH];
     65 	snprintf(full_key, sizeof(full_key), "%s:%s", sec_name, key);
     66 	return iniparser_getstring(ini->dict, full_key, NULL);
     67 }
     68 
     69 static int lookup_flow(struct ini *ini, const char *name)
     70 {
     71 	int i;
     72 	const struct flow *flow;
     73 
     74 	FOR_ARRAY_ELEMENT(&ini->flows, i, flow) {
     75 		if (strcmp(flow->name, name) == 0)
     76 			return i;
     77 	}
     78 
     79 	return -1;
     80 }
     81 
     82 static int lookup_or_add_flow(struct ini *ini, const char *name)
     83 {
     84 	struct flow *flow;
     85 	int i = lookup_flow(ini, name);
     86 	if (i != -1)
     87 		return i;
     88 	i = ARRAY_COUNT(&ini->flows);
     89 	flow = ARRAY_APPEND_ZERO(&ini->flows);
     90 	flow->name = name;
     91 	return i;
     92 }
     93 
     94 static int parse_ports(struct ini *ini, const char *sec_name,
     95 		       struct plugin *plugin)
     96 {
     97 	char key[MAX_PORT_NAME_LENGTH];
     98 	const char *str;
     99 	int i;
    100 	struct port *p;
    101 	int direction;
    102 
    103 	for (i = 0; i < MAX_NR_PORT; i++) {
    104 		direction = PORT_INPUT;
    105 		snprintf(key, sizeof(key), "input_%d", i);
    106 		str = getstring(ini, sec_name, key);
    107 		if (str == NULL)  {
    108 			direction = PORT_OUTPUT;
    109 			snprintf(key, sizeof(key), "output_%d", i);
    110 			str = getstring(ini, sec_name, key);
    111 			if (str == NULL)
    112 				break; /* no more ports */
    113 		}
    114 
    115 		if (*str == '\0') {
    116 			syslog(LOG_ERR, "empty value for %s:%s", sec_name, key);
    117 			return -1;
    118 		}
    119 
    120 		if (str[0] == '<' || str[0] == '{') {
    121 			p = ARRAY_APPEND_ZERO(&plugin->ports);
    122 			p->type = (str[0] == '<') ? PORT_CONTROL : PORT_AUDIO;
    123 			p->flow_id = lookup_or_add_flow(ini, str);
    124 			p->init_value = 0;
    125 		} else {
    126 			char *endptr;
    127 			float init_value = strtof(str, &endptr);
    128 			if (endptr == str) {
    129 				syslog(LOG_ERR, "cannot parse number from '%s'",
    130 				       str);
    131 			}
    132 			p = ARRAY_APPEND_ZERO(&plugin->ports);
    133 			p->type = PORT_CONTROL;
    134 			p->flow_id = INVALID_FLOW_ID;
    135 			p->init_value = init_value;
    136 		}
    137 		p->direction = direction;
    138 	}
    139 
    140 	return 0;
    141 }
    142 
    143 static int parse_plugin_section(struct ini *ini, const char *sec_name,
    144 				struct plugin *p)
    145 {
    146 	p->title = sec_name;
    147 	p->library = getstring(ini, sec_name, "library");
    148 	p->label = getstring(ini, sec_name, "label");
    149 	p->purpose = getstring(ini, sec_name, "purpose");
    150 	p->disable_expr = cras_expr_expression_parse(
    151 		getstring(ini, sec_name, "disable"));
    152 
    153 	if (p->library == NULL || p->label == NULL) {
    154 		syslog(LOG_ERR, "A plugin must have library and label: %s",
    155 		       sec_name);
    156 		return -1;
    157 	}
    158 
    159 	if (parse_ports(ini, sec_name, p) < 0) {
    160 		syslog(LOG_ERR, "Failed to parse ports: %s", sec_name);
    161 		return -1;
    162 	}
    163 
    164 	return 0;
    165 }
    166 
    167 static void fill_flow_info(struct ini *ini)
    168 {
    169 	int i, j;
    170 	struct plugin *plugin;
    171 	struct port *port;
    172 	struct flow *flow;
    173 	struct plugin **pplugin;
    174 	int *pport;
    175 
    176 	FOR_ARRAY_ELEMENT(&ini->plugins, i, plugin) {
    177 		FOR_ARRAY_ELEMENT(&plugin->ports, j, port) {
    178 			int flow_id = port->flow_id;
    179 			if (flow_id == INVALID_FLOW_ID)
    180 				continue;
    181 			flow = ARRAY_ELEMENT(&ini->flows, flow_id);
    182 			flow->type = port->type;
    183 			if (port->direction == PORT_INPUT) {
    184 				pplugin = &flow->to;
    185 				pport = &flow->to_port;
    186 			} else {
    187 				pplugin = &flow->from;
    188 				pport = &flow->from_port;
    189 			}
    190 			*pplugin = plugin;
    191 			*pport = j;
    192 		}
    193 	}
    194 }
    195 
    196 /* Adds a port to a plugin with specified flow id and direction. */
    197 static void add_audio_port(struct ini *ini,
    198 			   struct plugin *plugin,
    199 			   int flow_id,
    200 			   enum port_direction port_direction)
    201 {
    202 	struct port *p;
    203 	p = ARRAY_APPEND_ZERO(&plugin->ports);
    204 	p->type = PORT_AUDIO;
    205 	p->flow_id = flow_id;
    206 	p->init_value = 0;
    207 	p->direction = port_direction;
    208 }
    209 
    210 /* Fills fields for a swap_lr plugin.*/
    211 static void fill_swap_lr_plugin(struct ini *ini,
    212 				struct plugin *plugin,
    213 				int input_flowid_0,
    214 				int input_flowid_1,
    215 				int output_flowid_0,
    216 				int output_flowid_1)
    217 {
    218 	plugin->title = "swap_lr";
    219 	plugin->library = "builtin";
    220 	plugin->label = "swap_lr";
    221 	plugin->purpose = "playback";
    222 	plugin->disable_expr = cras_expr_expression_parse("swap_lr_disabled");
    223 
    224 	add_audio_port(ini, plugin, input_flowid_0, PORT_INPUT);
    225 	add_audio_port(ini, plugin, input_flowid_1, PORT_INPUT);
    226 	add_audio_port(ini, plugin, output_flowid_0, PORT_OUTPUT);
    227 	add_audio_port(ini, plugin, output_flowid_1, PORT_OUTPUT);
    228 }
    229 
    230 /* Adds a new flow with name. If there is already a flow with the name, returns
    231  * INVALID_FLOW_ID.
    232  */
    233 static int add_new_flow(struct ini *ini, const char *name)
    234 {
    235 	struct flow *flow;
    236 	int i = lookup_flow(ini, name);
    237 	if (i != -1)
    238 		return INVALID_FLOW_ID;
    239 	i = ARRAY_COUNT(&ini->flows);
    240 	flow = ARRAY_APPEND_ZERO(&ini->flows);
    241 	flow->name = name;
    242 	return i;
    243 }
    244 
    245 /* Finds the first playback sink plugin in ini. */
    246 struct plugin *find_first_playback_sink_plugin(struct ini *ini)
    247 {
    248 	int i;
    249 	struct plugin *plugin;
    250 
    251 	FOR_ARRAY_ELEMENT(&ini->plugins, i, plugin) {
    252 		if (strcmp(plugin->library, "builtin") != 0)
    253 			continue;
    254 		if (strcmp(plugin->label, "sink") != 0)
    255 			continue;
    256 		if (!plugin->purpose ||
    257 		    strcmp(plugin->purpose, "playback") != 0)
    258 			continue;
    259 		return plugin;
    260 	}
    261 
    262 	return NULL;
    263 }
    264 
    265 /* Inserts a swap_lr plugin before sink. Handles the port change such that
    266  * the port originally connects to sink will connect to swap_lr.
    267  */
    268 static int insert_swap_lr_plugin(struct ini *ini)
    269 {
    270 	struct plugin *swap_lr, *sink;
    271 	int sink_input_flowid_0, sink_input_flowid_1;
    272 	int swap_lr_output_flowid_0, swap_lr_output_flowid_1;
    273 
    274 	/* Only add swap_lr plugin for two-channel playback dsp.
    275 	 * TODO(cychiang): Handle multiple sinks if needed.
    276 	 */
    277 	sink = find_first_playback_sink_plugin(ini);
    278 	if ((sink == NULL) || ARRAY_COUNT(&sink->ports) != 2)
    279 		return 0;
    280 
    281 	/* Gets the original flow ids of the sink input ports. */
    282 	sink_input_flowid_0 = ARRAY_ELEMENT(&sink->ports, 0)->flow_id;
    283 	sink_input_flowid_1 = ARRAY_ELEMENT(&sink->ports, 1)->flow_id;
    284 
    285 	/* Create new flow ids for swap_lr output ports. */
    286 	swap_lr_output_flowid_0 = add_new_flow(ini, "{swap_lr_out:0}");
    287 	swap_lr_output_flowid_1 = add_new_flow(ini, "{swap_lr_out:1}");
    288 
    289 	if (swap_lr_output_flowid_0 == INVALID_FLOW_ID ||
    290 	    swap_lr_output_flowid_1 == INVALID_FLOW_ID) {
    291 		syslog(LOG_ERR, "Can not create flow id for swap_lr_out");
    292 		return -EINVAL;
    293 	}
    294 
    295 	/* Creates a swap_lr plugin and sets the input and output ports. */
    296 	swap_lr = ARRAY_APPEND_ZERO(&ini->plugins);
    297 	fill_swap_lr_plugin(ini,
    298 			    swap_lr,
    299 			    sink_input_flowid_0,
    300 			    sink_input_flowid_1,
    301 			    swap_lr_output_flowid_0,
    302 			    swap_lr_output_flowid_1);
    303 
    304 	/* Look up first sink again because ini->plugins could be realloc'ed */
    305 	sink = find_first_playback_sink_plugin(ini);
    306 
    307 	/* The flow ids of sink input ports should be changed to flow ids of
    308 	 * {swap_lr_out:0}, {swap_lr_out:1}. */
    309 	ARRAY_ELEMENT(&sink->ports, 0)->flow_id = swap_lr_output_flowid_0;
    310 	ARRAY_ELEMENT(&sink->ports, 1)->flow_id = swap_lr_output_flowid_1;
    311 
    312 	return 0;
    313 }
    314 
    315 struct ini *cras_dsp_ini_create(const char *ini_filename)
    316 {
    317 	struct ini *ini;
    318 	dictionary *dict;
    319 	int nsec, i;
    320 	const char *sec_name;
    321 	struct plugin *plugin;
    322 	int rc;
    323 
    324 	ini = calloc(1, sizeof(struct ini));
    325 	if (!ini) {
    326 		syslog(LOG_ERR, "no memory for ini struct");
    327 		return NULL;
    328 	}
    329 
    330 	dict = iniparser_load((char *)ini_filename);
    331 	if (!dict) {
    332 		syslog(LOG_ERR, "no ini file %s", ini_filename);
    333 		goto bail;
    334 	}
    335 	ini->dict = dict;
    336 
    337 	/* Parse the plugin sections */
    338 	nsec = iniparser_getnsec(dict);
    339 	for (i = 0; i < nsec; i++) {
    340 		sec_name = iniparser_getsecname(dict, i);
    341 		plugin = ARRAY_APPEND_ZERO(&ini->plugins);
    342 		if (parse_plugin_section(ini, sec_name, plugin) < 0)
    343 			goto bail;
    344 	}
    345 
    346 	/* Insert a swap_lr plugin before sink. */
    347 	rc = insert_swap_lr_plugin(ini);
    348 	if (rc < 0) {
    349 		syslog(LOG_ERR, "failed to insert swap_lr plugin");
    350 		goto bail;
    351 	}
    352 
    353 	/* Fill flow info now because now the plugin array won't change */
    354 	fill_flow_info(ini);
    355 
    356 	return ini;
    357 bail:
    358 	cras_dsp_ini_free(ini);
    359 	return NULL;
    360 }
    361 
    362 void cras_dsp_ini_free(struct ini *ini)
    363 {
    364 	struct plugin *p;
    365 	int i;
    366 
    367 	/* free plugins */
    368 	FOR_ARRAY_ELEMENT(&ini->plugins, i, p) {
    369 		cras_expr_expression_free(p->disable_expr);
    370 		ARRAY_FREE(&p->ports);
    371 	}
    372 	ARRAY_FREE(&ini->plugins);
    373 	ARRAY_FREE(&ini->flows);
    374 
    375 	if (ini->dict) {
    376 		iniparser_freedict(ini->dict);
    377 		ini->dict = NULL;
    378 	}
    379 
    380 	free(ini);
    381 }
    382 
    383 static const char *port_direction_str(enum port_direction port_direction)
    384 {
    385 	switch (port_direction) {
    386 	case PORT_INPUT: return "input";
    387 	case PORT_OUTPUT: return "output";
    388 	default: return "unknown";
    389 	}
    390 }
    391 
    392 static const char *port_type_str(enum port_type port_type)
    393 {
    394 	switch (port_type) {
    395 	case PORT_CONTROL: return "control";
    396 	case PORT_AUDIO: return "audio";
    397 	default: return "unknown";
    398 	}
    399 }
    400 
    401 static const char *plugin_title(struct plugin *plugin)
    402 {
    403 	if (plugin == NULL)
    404 		return "(null)";
    405 	return plugin->title;
    406 }
    407 
    408 void cras_dsp_ini_dump(struct dumper *d, struct ini *ini)
    409 {
    410 	int i, j;
    411 	struct plugin *plugin;
    412 	struct port *port;
    413 	const struct flow *flow;
    414 
    415 	dumpf(d, "---- ini dump begin ---\n");
    416 	dumpf(d, "ini->dict = %p\n", ini->dict);
    417 
    418 	dumpf(d, "number of plugins = %d\n", ARRAY_COUNT(&ini->plugins));
    419 	FOR_ARRAY_ELEMENT(&ini->plugins, i, plugin) {
    420 		dumpf(d, "[plugin %d: %s]\n", i, plugin->title);
    421 		dumpf(d, "library=%s\n", plugin->library);
    422 		dumpf(d, "label=%s\n", plugin->label);
    423 		dumpf(d, "purpose=%s\n", plugin->purpose);
    424 		dumpf(d, "disable=%p\n", plugin->disable_expr);
    425 		FOR_ARRAY_ELEMENT(&plugin->ports, j, port) {
    426 			dumpf(d,
    427 			      "  [%s port %d] type=%s, flow_id=%d, value=%g\n",
    428 			      port_direction_str(port->direction), j,
    429 			      port_type_str(port->type), port->flow_id,
    430 			      port->init_value);
    431 		}
    432 	}
    433 
    434 	dumpf(d, "number of flows = %d\n", ARRAY_COUNT(&ini->flows));
    435 	FOR_ARRAY_ELEMENT(&ini->flows, i, flow) {
    436 		dumpf(d, "  [flow %d] %s, %s, %s:%d -> %s:%d\n",
    437 		      i, flow->name, port_type_str(flow->type),
    438 		      plugin_title(flow->from), flow->from_port,
    439 		      plugin_title(flow->to), flow->to_port);
    440 	}
    441 
    442 	dumpf(d, "---- ini dump end ----\n");
    443 }
    444