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 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 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 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 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 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 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. */ 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.*/ 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 */ 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. */ 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 */ 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 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 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 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 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 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 452 static const char *plugin_title(struct plugin *plugin) 453 { 454 if (plugin == NULL) 455 return "(null)"; 456 return plugin->title; 457 } 458 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