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