• 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_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 	ARRAY_ELEMENT_FOREACH (&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 =
153 		cras_expr_expression_parse(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 	ARRAY_ELEMENT_FOREACH (&ini->plugins, i, plugin) {
179 		ARRAY_ELEMENT_FOREACH (&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, struct plugin *plugin, 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.*/
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)211 static void fill_swap_lr_plugin(struct ini *ini, struct plugin *plugin,
212 				int input_flowid_0, int input_flowid_1,
213 				int output_flowid_0, int output_flowid_1)
214 {
215 	plugin->title = "swap_lr";
216 	plugin->library = "builtin";
217 	plugin->label = "swap_lr";
218 	plugin->purpose = "playback";
219 	plugin->disable_expr = cras_expr_expression_parse("swap_lr_disabled");
220 
221 	add_audio_port(ini, plugin, input_flowid_0, PORT_INPUT);
222 	add_audio_port(ini, plugin, input_flowid_1, PORT_INPUT);
223 	add_audio_port(ini, plugin, output_flowid_0, PORT_OUTPUT);
224 	add_audio_port(ini, plugin, output_flowid_1, PORT_OUTPUT);
225 }
226 
227 /* Adds a new flow with name. If there is already a flow with the name, returns
228  * INVALID_FLOW_ID.
229  */
add_new_flow(struct ini * ini,const char * name)230 static int add_new_flow(struct ini *ini, const char *name)
231 {
232 	struct flow *flow;
233 	int i = lookup_flow(ini, name);
234 	if (i != -1)
235 		return INVALID_FLOW_ID;
236 	i = ARRAY_COUNT(&ini->flows);
237 	flow = ARRAY_APPEND_ZERO(&ini->flows);
238 	flow->name = name;
239 	return i;
240 }
241 
242 /* Finds the first playback sink plugin in ini. */
find_first_playback_sink_plugin(struct ini * ini)243 struct plugin *find_first_playback_sink_plugin(struct ini *ini)
244 {
245 	int i;
246 	struct plugin *plugin;
247 
248 	ARRAY_ELEMENT_FOREACH (&ini->plugins, i, plugin) {
249 		if (strcmp(plugin->library, "builtin") != 0)
250 			continue;
251 		if (strcmp(plugin->label, "sink") != 0)
252 			continue;
253 		if (!plugin->purpose ||
254 		    strcmp(plugin->purpose, "playback") != 0)
255 			continue;
256 		return plugin;
257 	}
258 
259 	return NULL;
260 }
261 
262 /* Inserts a swap_lr plugin before sink. Handles the port change such that
263  * the port originally connects to sink will connect to swap_lr.
264  */
insert_swap_lr_plugin(struct ini * ini)265 static int insert_swap_lr_plugin(struct ini *ini)
266 {
267 	struct plugin *swap_lr, *sink;
268 	int sink_input_flowid_0, sink_input_flowid_1;
269 	int swap_lr_output_flowid_0, swap_lr_output_flowid_1;
270 
271 	/* Only add swap_lr plugin for two-channel playback dsp.
272 	 * TODO(cychiang): Handle multiple sinks if needed.
273 	 */
274 	sink = find_first_playback_sink_plugin(ini);
275 	if ((sink == NULL) || ARRAY_COUNT(&sink->ports) != 2)
276 		return 0;
277 
278 	/* Gets the original flow ids of the sink input ports. */
279 	sink_input_flowid_0 = ARRAY_ELEMENT(&sink->ports, 0)->flow_id;
280 	sink_input_flowid_1 = ARRAY_ELEMENT(&sink->ports, 1)->flow_id;
281 
282 	/* Create new flow ids for swap_lr output ports. */
283 	swap_lr_output_flowid_0 = add_new_flow(ini, "{swap_lr_out:0}");
284 	swap_lr_output_flowid_1 = add_new_flow(ini, "{swap_lr_out:1}");
285 
286 	if (swap_lr_output_flowid_0 == INVALID_FLOW_ID ||
287 	    swap_lr_output_flowid_1 == INVALID_FLOW_ID) {
288 		syslog(LOG_ERR, "Can not create flow id for swap_lr_out");
289 		return -EINVAL;
290 	}
291 
292 	/* Creates a swap_lr plugin and sets the input and output ports. */
293 	swap_lr = ARRAY_APPEND_ZERO(&ini->plugins);
294 	fill_swap_lr_plugin(ini, swap_lr, sink_input_flowid_0,
295 			    sink_input_flowid_1, swap_lr_output_flowid_0,
296 			    swap_lr_output_flowid_1);
297 
298 	/* Look up first sink again because ini->plugins could be realloc'ed */
299 	sink = find_first_playback_sink_plugin(ini);
300 
301 	/* The flow ids of sink input ports should be changed to flow ids of
302 	 * {swap_lr_out:0}, {swap_lr_out:1}. */
303 	ARRAY_ELEMENT(&sink->ports, 0)->flow_id = swap_lr_output_flowid_0;
304 	ARRAY_ELEMENT(&sink->ports, 1)->flow_id = swap_lr_output_flowid_1;
305 
306 	return 0;
307 }
308 
create_dummy_ini(const char * purpose,unsigned int num_channels)309 struct ini *create_dummy_ini(const char *purpose, unsigned int num_channels)
310 {
311 	static char dummy_flow_names[MAX_DUMMY_INI_CH][8] = {
312 		"{tmp:0}", "{tmp:1}", "{tmp:2}", "{tmp:3}",
313 		"{tmp:4}", "{tmp:5}", "{tmp:6}", "{tmp:7}",
314 	};
315 	struct ini *ini;
316 	struct plugin *source, *sink;
317 	int tmp_flow_ids[MAX_DUMMY_INI_CH];
318 	int i;
319 
320 	if (num_channels > MAX_DUMMY_INI_CH) {
321 		syslog(LOG_ERR, "Unable to create %u channels of dummy ini",
322 		       num_channels);
323 		return NULL;
324 	}
325 
326 	ini = calloc(1, sizeof(struct ini));
327 	if (!ini) {
328 		syslog(LOG_ERR, "no memory for ini struct");
329 		return NULL;
330 	}
331 
332 	for (i = 0; i < num_channels; i++)
333 		tmp_flow_ids[i] = add_new_flow(ini, dummy_flow_names[i]);
334 
335 	source = ARRAY_APPEND_ZERO(&ini->plugins);
336 	source->title = "source";
337 	source->library = "builtin";
338 	source->label = "source";
339 	source->purpose = purpose;
340 
341 	for (i = 0; i < num_channels; i++)
342 		add_audio_port(ini, source, tmp_flow_ids[i], PORT_OUTPUT);
343 
344 	sink = ARRAY_APPEND_ZERO(&ini->plugins);
345 	sink->title = "sink";
346 	sink->library = "builtin";
347 	sink->label = "sink";
348 	sink->purpose = purpose;
349 
350 	for (i = 0; i < num_channels; i++)
351 		add_audio_port(ini, sink, tmp_flow_ids[i], PORT_INPUT);
352 
353 	fill_flow_info(ini);
354 
355 	return ini;
356 }
357 
cras_dsp_ini_create(const char * ini_filename)358 struct ini *cras_dsp_ini_create(const char *ini_filename)
359 {
360 	struct ini *ini;
361 	dictionary *dict;
362 	int nsec, i;
363 	const char *sec_name;
364 	struct plugin *plugin;
365 	int rc;
366 
367 	ini = calloc(1, sizeof(struct ini));
368 	if (!ini) {
369 		syslog(LOG_ERR, "no memory for ini struct");
370 		return NULL;
371 	}
372 
373 	dict = iniparser_load_wrapper((char *)ini_filename);
374 	if (!dict) {
375 		syslog(LOG_DEBUG, "no ini file %s", ini_filename);
376 		goto bail;
377 	}
378 	ini->dict = dict;
379 
380 	/* Parse the plugin sections */
381 	nsec = iniparser_getnsec(dict);
382 	for (i = 0; i < nsec; i++) {
383 		sec_name = iniparser_getsecname(dict, i);
384 		plugin = ARRAY_APPEND_ZERO(&ini->plugins);
385 		if (parse_plugin_section(ini, sec_name, plugin) < 0)
386 			goto bail;
387 	}
388 
389 	/* Insert a swap_lr plugin before sink. */
390 	rc = insert_swap_lr_plugin(ini);
391 	if (rc < 0) {
392 		syslog(LOG_ERR, "failed to insert swap_lr plugin");
393 		goto bail;
394 	}
395 
396 	/* Fill flow info now because now the plugin array won't change */
397 	fill_flow_info(ini);
398 
399 	return ini;
400 bail:
401 	cras_dsp_ini_free(ini);
402 	return NULL;
403 }
404 
cras_dsp_ini_free(struct ini * ini)405 void cras_dsp_ini_free(struct ini *ini)
406 {
407 	struct plugin *p;
408 	int i;
409 
410 	/* free plugins */
411 	ARRAY_ELEMENT_FOREACH (&ini->plugins, i, p) {
412 		cras_expr_expression_free(p->disable_expr);
413 		ARRAY_FREE(&p->ports);
414 	}
415 	ARRAY_FREE(&ini->plugins);
416 	ARRAY_FREE(&ini->flows);
417 
418 	if (ini->dict) {
419 		iniparser_freedict(ini->dict);
420 		ini->dict = NULL;
421 	}
422 
423 	free(ini);
424 }
425 
port_direction_str(enum port_direction port_direction)426 static const char *port_direction_str(enum port_direction port_direction)
427 {
428 	switch (port_direction) {
429 	case PORT_INPUT:
430 		return "input";
431 	case PORT_OUTPUT:
432 		return "output";
433 	default:
434 		return "unknown";
435 	}
436 }
437 
port_type_str(enum port_type port_type)438 static const char *port_type_str(enum port_type port_type)
439 {
440 	switch (port_type) {
441 	case PORT_CONTROL:
442 		return "control";
443 	case PORT_AUDIO:
444 		return "audio";
445 	default:
446 		return "unknown";
447 	}
448 }
449 
plugin_title(struct plugin * plugin)450 static const char *plugin_title(struct plugin *plugin)
451 {
452 	if (plugin == NULL)
453 		return "(null)";
454 	return plugin->title;
455 }
456 
cras_dsp_ini_dump(struct dumper * d,struct ini * ini)457 void cras_dsp_ini_dump(struct dumper *d, struct ini *ini)
458 {
459 	int i, j;
460 	struct plugin *plugin;
461 	struct port *port;
462 	const struct flow *flow;
463 
464 	dumpf(d, "---- ini dump begin ---\n");
465 	dumpf(d, "ini->dict = %p\n", ini->dict);
466 
467 	dumpf(d, "number of plugins = %d\n", ARRAY_COUNT(&ini->plugins));
468 	ARRAY_ELEMENT_FOREACH (&ini->plugins, i, plugin) {
469 		dumpf(d, "[plugin %d: %s]\n", i, plugin->title);
470 		dumpf(d, "library=%s\n", plugin->library);
471 		dumpf(d, "label=%s\n", plugin->label);
472 		dumpf(d, "purpose=%s\n", plugin->purpose);
473 		dumpf(d, "disable=%p\n", plugin->disable_expr);
474 		ARRAY_ELEMENT_FOREACH (&plugin->ports, j, port) {
475 			dumpf(d,
476 			      "  [%s port %d] type=%s, flow_id=%d, value=%g\n",
477 			      port_direction_str(port->direction), j,
478 			      port_type_str(port->type), port->flow_id,
479 			      port->init_value);
480 		}
481 	}
482 
483 	dumpf(d, "number of flows = %d\n", ARRAY_COUNT(&ini->flows));
484 	ARRAY_ELEMENT_FOREACH (&ini->flows, i, flow) {
485 		dumpf(d, "  [flow %d] %s, %s, %s:%d -> %s:%d\n", i, flow->name,
486 		      port_type_str(flow->type), plugin_title(flow->from),
487 		      flow->from_port, plugin_title(flow->to), flow->to_port);
488 	}
489 
490 	dumpf(d, "---- ini dump end ----\n");
491 }
492