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 
getstring(struct ini * ini,const char * sec_name,const char * key)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 
lookup_flow(struct ini * ini,const char * name)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 
lookup_or_add_flow(struct ini * ini,const char * name)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 
parse_ports(struct ini * ini,const char * sec_name,struct plugin * plugin)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 
parse_plugin_section(struct ini * ini,const char * sec_name,struct plugin * p)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 
fill_flow_info(struct ini * ini)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. */
add_audio_port(struct ini * ini,struct plugin * plugin,int flow_id,enum port_direction port_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.*/
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)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  */
add_new_flow(struct ini * ini,const char * name)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. */
find_first_playback_sink_plugin(struct ini * 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  */
insert_swap_lr_plugin(struct ini * ini)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 
create_dummy_ini(const char * purpose,unsigned int num_channels)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 
cras_dsp_ini_create(const char * ini_filename)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 
cras_dsp_ini_free(struct ini * ini)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 
port_direction_str(enum port_direction port_direction)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 
port_type_str(enum port_type port_type)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 
plugin_title(struct plugin * plugin)452 static const char *plugin_title(struct plugin *plugin)
453 {
454 	if (plugin == NULL)
455 		return "(null)";
456 	return plugin->title;
457 }
458 
cras_dsp_ini_dump(struct dumper * d,struct ini * ini)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