• 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 
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 
getstring(struct ini * ini,const char * sec_name,const char * key)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 
lookup_flow(struct ini * ini,const char * name)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 
lookup_or_add_flow(struct ini * ini,const char * name)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 
parse_ports(struct ini * ini,const char * sec_name,struct plugin * plugin)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 
parse_plugin_section(struct ini * ini,const char * sec_name,struct plugin * p)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 
fill_flow_info(struct ini * ini)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. */
add_audio_port(struct ini * ini,struct plugin * plugin,int flow_id,enum port_direction port_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.*/
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,
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  */
add_new_flow(struct ini * ini,const char * name)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. */
find_first_playback_sink_plugin(struct ini * 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  */
insert_swap_lr_plugin(struct ini * ini)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 
cras_dsp_ini_create(const char * ini_filename)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 
cras_dsp_ini_free(struct ini * ini)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 
port_direction_str(enum port_direction port_direction)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 
port_type_str(enum port_type port_type)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 
plugin_title(struct plugin * plugin)401 static const char *plugin_title(struct plugin *plugin)
402 {
403 	if (plugin == NULL)
404 		return "(null)";
405 	return plugin->title;
406 }
407 
cras_dsp_ini_dump(struct dumper * d,struct ini * ini)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