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