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