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