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