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 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 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 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 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 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 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. */ 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.*/ 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 */ 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. */ 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 */ 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 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 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 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 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 401 static const char *plugin_title(struct plugin *plugin) 402 { 403 if (plugin == NULL) 404 return "(null)"; 405 return plugin->title; 406 } 407 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