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