1 /*
2 * Copyright (C) 2013 The Android Open Source Project
3 * Inspired by TinyHW, written by Mark Brown at Wolfson Micro
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #define LOG_TAG "audio_route"
19 /*#define LOG_NDEBUG 0*/
20
21 #include <errno.h>
22 #include <expat.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include <cutils/log.h>
28
29 #include <tinyalsa/asoundlib.h>
30
31 #define BUF_SIZE 1024
32 #define MIXER_XML_PATH "/system/etc/mixer_paths.xml"
33 #define INITIAL_MIXER_PATH_SIZE 8
34
35 struct mixer_state {
36 struct mixer_ctl *ctl;
37 unsigned int num_values;
38 int *old_value;
39 int *new_value;
40 int *reset_value;
41 };
42
43 struct mixer_setting {
44 unsigned int ctl_index;
45 unsigned int num_values;
46 int *value;
47 };
48
49 struct mixer_value {
50 unsigned int ctl_index;
51 int index;
52 int value;
53 };
54
55 struct mixer_path {
56 char *name;
57 unsigned int size;
58 unsigned int length;
59 struct mixer_setting *setting;
60 };
61
62 struct audio_route {
63 struct mixer *mixer;
64 unsigned int num_mixer_ctls;
65 struct mixer_state *mixer_state;
66
67 unsigned int mixer_path_size;
68 unsigned int num_mixer_paths;
69 struct mixer_path *mixer_path;
70 };
71
72 struct config_parse_state {
73 struct audio_route *ar;
74 struct mixer_path *path;
75 int level;
76 };
77
78 /* path functions */
79
is_supported_ctl_type(enum mixer_ctl_type type)80 bool is_supported_ctl_type(enum mixer_ctl_type type)
81 {
82 switch (type) {
83 case MIXER_CTL_TYPE_BOOL:
84 case MIXER_CTL_TYPE_INT:
85 case MIXER_CTL_TYPE_ENUM:
86 return true;
87 default:
88 return false;
89 }
90 }
91
index_to_ctl(struct audio_route * ar,unsigned int ctl_index)92 static inline struct mixer_ctl *index_to_ctl(struct audio_route *ar,
93 unsigned int ctl_index)
94 {
95 return ar->mixer_state[ctl_index].ctl;
96 }
97
path_print(struct audio_route * ar,struct mixer_path * path)98 static void path_print(struct audio_route *ar, struct mixer_path *path)
99 {
100 unsigned int i;
101 unsigned int j;
102
103 ALOGE("Path: %s, length: %d", path->name, path->length);
104 for (i = 0; i < path->length; i++) {
105 struct mixer_ctl *ctl = index_to_ctl(ar, path->setting[i].ctl_index);
106
107 ALOGE(" id=%d: ctl=%s", i, mixer_ctl_get_name(ctl));
108 for (j = 0; j < path->setting[i].num_values; j++)
109 ALOGE(" id=%d value=%d", j, path->setting[i].value[j]);
110 }
111 }
112
path_free(struct audio_route * ar)113 static void path_free(struct audio_route *ar)
114 {
115 unsigned int i;
116
117 for (i = 0; i < ar->num_mixer_paths; i++) {
118 if (ar->mixer_path[i].name)
119 free(ar->mixer_path[i].name);
120 if (ar->mixer_path[i].setting) {
121 if (ar->mixer_path[i].setting->value)
122 free(ar->mixer_path[i].setting->value);
123 free(ar->mixer_path[i].setting);
124 }
125 }
126 free(ar->mixer_path);
127 }
128
path_get_by_name(struct audio_route * ar,const char * name)129 static struct mixer_path *path_get_by_name(struct audio_route *ar,
130 const char *name)
131 {
132 unsigned int i;
133
134 for (i = 0; i < ar->num_mixer_paths; i++)
135 if (strcmp(ar->mixer_path[i].name, name) == 0)
136 return &ar->mixer_path[i];
137
138 return NULL;
139 }
140
path_create(struct audio_route * ar,const char * name)141 static struct mixer_path *path_create(struct audio_route *ar, const char *name)
142 {
143 struct mixer_path *new_mixer_path = NULL;
144
145 if (path_get_by_name(ar, name)) {
146 ALOGE("Path name '%s' already exists", name);
147 return NULL;
148 }
149
150 /* check if we need to allocate more space for mixer paths */
151 if (ar->mixer_path_size <= ar->num_mixer_paths) {
152 if (ar->mixer_path_size == 0)
153 ar->mixer_path_size = INITIAL_MIXER_PATH_SIZE;
154 else
155 ar->mixer_path_size *= 2;
156
157 new_mixer_path = realloc(ar->mixer_path, ar->mixer_path_size *
158 sizeof(struct mixer_path));
159 if (new_mixer_path == NULL) {
160 ALOGE("Unable to allocate more paths");
161 return NULL;
162 } else {
163 ar->mixer_path = new_mixer_path;
164 }
165 }
166
167 /* initialise the new mixer path */
168 ar->mixer_path[ar->num_mixer_paths].name = strdup(name);
169 ar->mixer_path[ar->num_mixer_paths].size = 0;
170 ar->mixer_path[ar->num_mixer_paths].length = 0;
171 ar->mixer_path[ar->num_mixer_paths].setting = NULL;
172
173 /* return the mixer path just added, then increment number of them */
174 return &ar->mixer_path[ar->num_mixer_paths++];
175 }
176
find_ctl_index_in_path(struct mixer_path * path,unsigned int ctl_index)177 static int find_ctl_index_in_path(struct mixer_path *path,
178 unsigned int ctl_index)
179 {
180 unsigned int i;
181
182 for (i = 0; i < path->length; i++)
183 if (path->setting[i].ctl_index == ctl_index)
184 return i;
185
186 return -1;
187 }
188
alloc_path_setting(struct mixer_path * path)189 static int alloc_path_setting(struct mixer_path *path)
190 {
191 struct mixer_setting *new_path_setting;
192 int path_index;
193
194 /* check if we need to allocate more space for path settings */
195 if (path->size <= path->length) {
196 if (path->size == 0)
197 path->size = INITIAL_MIXER_PATH_SIZE;
198 else
199 path->size *= 2;
200
201 new_path_setting = realloc(path->setting,
202 path->size * sizeof(struct mixer_setting));
203 if (new_path_setting == NULL) {
204 ALOGE("Unable to allocate more path settings");
205 return -1;
206 } else {
207 path->setting = new_path_setting;
208 }
209 }
210
211 path_index = path->length;
212 path->length++;
213
214 return path_index;
215 }
216
path_add_setting(struct audio_route * ar,struct mixer_path * path,struct mixer_setting * setting)217 static int path_add_setting(struct audio_route *ar, struct mixer_path *path,
218 struct mixer_setting *setting)
219 {
220 int path_index;
221
222 if (find_ctl_index_in_path(path, setting->ctl_index) != -1) {
223 struct mixer_ctl *ctl = index_to_ctl(ar, setting->ctl_index);
224
225 ALOGE("Control '%s' already exists in path '%s'",
226 mixer_ctl_get_name(ctl), path->name);
227 return -1;
228 }
229
230 path_index = alloc_path_setting(path);
231 if (path_index < 0)
232 return -1;
233
234 path->setting[path_index].ctl_index = setting->ctl_index;
235 path->setting[path_index].num_values = setting->num_values;
236 path->setting[path_index].value = malloc(setting->num_values * sizeof(int));
237 /* copy all values */
238 memcpy(path->setting[path_index].value, setting->value,
239 setting->num_values * sizeof(int));
240
241 return 0;
242 }
243
path_add_value(struct audio_route * ar,struct mixer_path * path,struct mixer_value * mixer_value)244 static int path_add_value(struct audio_route *ar, struct mixer_path *path,
245 struct mixer_value *mixer_value)
246 {
247 unsigned int i;
248 int path_index;
249 unsigned int num_values;
250 struct mixer_ctl *ctl;
251
252 /* Check that mixer value index is within range */
253 ctl = index_to_ctl(ar, mixer_value->ctl_index);
254 num_values = mixer_ctl_get_num_values(ctl);
255 if (mixer_value->index >= (int)num_values) {
256 ALOGE("mixer index %d is out of range for '%s'", mixer_value->index,
257 mixer_ctl_get_name(ctl));
258 return -1;
259 }
260
261 path_index = find_ctl_index_in_path(path, mixer_value->ctl_index);
262 if (path_index < 0) {
263 /* New path */
264
265 path_index = alloc_path_setting(path);
266 if (path_index < 0)
267 return -1;
268
269 /* initialise the new path setting */
270 path->setting[path_index].ctl_index = mixer_value->ctl_index;
271 path->setting[path_index].num_values = num_values;
272 path->setting[path_index].value = malloc(num_values * sizeof(int));
273 path->setting[path_index].value[0] = mixer_value->value;
274 }
275
276 if (mixer_value->index == -1) {
277 /* set all values the same */
278 for (i = 0; i < num_values; i++)
279 path->setting[path_index].value[i] = mixer_value->value;
280 } else {
281 /* set only one value */
282 path->setting[path_index].value[mixer_value->index] = mixer_value->value;
283 }
284
285 return 0;
286 }
287
path_add_path(struct audio_route * ar,struct mixer_path * path,struct mixer_path * sub_path)288 static int path_add_path(struct audio_route *ar, struct mixer_path *path,
289 struct mixer_path *sub_path)
290 {
291 unsigned int i;
292
293 for (i = 0; i < sub_path->length; i++)
294 if (path_add_setting(ar, path, &sub_path->setting[i]) < 0)
295 return -1;
296
297 return 0;
298 }
299
path_apply(struct audio_route * ar,struct mixer_path * path)300 static int path_apply(struct audio_route *ar, struct mixer_path *path)
301 {
302 unsigned int i;
303 unsigned int ctl_index;
304 struct mixer_ctl *ctl;
305 enum mixer_ctl_type type;
306
307 for (i = 0; i < path->length; i++) {
308 ctl_index = path->setting[i].ctl_index;
309 ctl = index_to_ctl(ar, ctl_index);
310 type = mixer_ctl_get_type(ctl);
311 if (!is_supported_ctl_type(type))
312 continue;
313
314 /* apply the new value(s) */
315 memcpy(ar->mixer_state[ctl_index].new_value, path->setting[i].value,
316 path->setting[i].num_values * sizeof(int));
317 }
318
319 return 0;
320 }
321
path_reset(struct audio_route * ar,struct mixer_path * path)322 static int path_reset(struct audio_route *ar, struct mixer_path *path)
323 {
324 unsigned int i;
325 unsigned int j;
326 unsigned int ctl_index;
327 struct mixer_ctl *ctl;
328 enum mixer_ctl_type type;
329
330 for (i = 0; i < path->length; i++) {
331 ctl_index = path->setting[i].ctl_index;
332 ctl = index_to_ctl(ar, ctl_index);
333 type = mixer_ctl_get_type(ctl);
334 if (!is_supported_ctl_type(type))
335 continue;
336
337 /* reset the value(s) */
338 memcpy(ar->mixer_state[ctl_index].new_value,
339 ar->mixer_state[ctl_index].reset_value,
340 ar->mixer_state[ctl_index].num_values * sizeof(int));
341 }
342
343 return 0;
344 }
345
346 /* mixer helper function */
mixer_enum_string_to_value(struct mixer_ctl * ctl,const char * string)347 static int mixer_enum_string_to_value(struct mixer_ctl *ctl, const char *string)
348 {
349 unsigned int i;
350
351 /* Search the enum strings for a particular one */
352 for (i = 0; i < mixer_ctl_get_num_enums(ctl); i++) {
353 if (strcmp(mixer_ctl_get_enum_string(ctl, i), string) == 0)
354 break;
355 }
356
357 return i;
358 }
359
start_tag(void * data,const XML_Char * tag_name,const XML_Char ** attr)360 static void start_tag(void *data, const XML_Char *tag_name,
361 const XML_Char **attr)
362 {
363 const XML_Char *attr_name = NULL;
364 const XML_Char *attr_id = NULL;
365 const XML_Char *attr_value = NULL;
366 struct config_parse_state *state = data;
367 struct audio_route *ar = state->ar;
368 unsigned int i;
369 unsigned int ctl_index;
370 struct mixer_ctl *ctl;
371 int value;
372 unsigned int id;
373 struct mixer_value mixer_value;
374 enum mixer_ctl_type type;
375
376 /* Get name, id and value attributes (these may be empty) */
377 for (i = 0; attr[i]; i += 2) {
378 if (strcmp(attr[i], "name") == 0)
379 attr_name = attr[i + 1];
380 if (strcmp(attr[i], "id") == 0)
381 attr_id = attr[i + 1];
382 else if (strcmp(attr[i], "value") == 0)
383 attr_value = attr[i + 1];
384 }
385
386 /* Look at tags */
387 if (strcmp(tag_name, "path") == 0) {
388 if (attr_name == NULL) {
389 ALOGE("Unnamed path!");
390 } else {
391 if (state->level == 1) {
392 /* top level path: create and stash the path */
393 state->path = path_create(ar, (char *)attr_name);
394 } else {
395 /* nested path */
396 struct mixer_path *sub_path = path_get_by_name(ar, attr_name);
397 path_add_path(ar, state->path, sub_path);
398 }
399 }
400 }
401
402 else if (strcmp(tag_name, "ctl") == 0) {
403 /* Obtain the mixer ctl and value */
404 ctl = mixer_get_ctl_by_name(ar->mixer, attr_name);
405 if (ctl == NULL) {
406 ALOGE("Control '%s' doesn't exist - skipping", attr_name);
407 goto done;
408 }
409
410 switch (mixer_ctl_get_type(ctl)) {
411 case MIXER_CTL_TYPE_BOOL:
412 case MIXER_CTL_TYPE_INT:
413 value = atoi((char *)attr_value);
414 break;
415 case MIXER_CTL_TYPE_ENUM:
416 value = mixer_enum_string_to_value(ctl, (char *)attr_value);
417 break;
418 default:
419 value = 0;
420 break;
421 }
422
423 /* locate the mixer ctl in the list */
424 for (ctl_index = 0; ctl_index < ar->num_mixer_ctls; ctl_index++) {
425 if (ar->mixer_state[ctl_index].ctl == ctl)
426 break;
427 }
428
429 if (state->level == 1) {
430 /* top level ctl (initial setting) */
431
432 type = mixer_ctl_get_type(ctl);
433 if (is_supported_ctl_type(type)) {
434 /* apply the new value */
435 if (attr_id) {
436 /* set only one value */
437 id = atoi((char *)attr_id);
438 if (id < ar->mixer_state[ctl_index].num_values)
439 ar->mixer_state[ctl_index].new_value[id] = value;
440 else
441 ALOGE("value id out of range for mixer ctl '%s'",
442 mixer_ctl_get_name(ctl));
443 } else {
444 /* set all values the same */
445 for (i = 0; i < ar->mixer_state[ctl_index].num_values; i++)
446 ar->mixer_state[ctl_index].new_value[i] = value;
447 }
448 }
449 } else {
450 /* nested ctl (within a path) */
451 mixer_value.ctl_index = ctl_index;
452 mixer_value.value = value;
453 if (attr_id)
454 mixer_value.index = atoi((char *)attr_id);
455 else
456 mixer_value.index = -1;
457 path_add_value(ar, state->path, &mixer_value);
458 }
459 }
460
461 done:
462 state->level++;
463 }
464
end_tag(void * data,const XML_Char * tag_name)465 static void end_tag(void *data, const XML_Char *tag_name)
466 {
467 struct config_parse_state *state = data;
468 (void)tag_name;
469
470 state->level--;
471 }
472
alloc_mixer_state(struct audio_route * ar)473 static int alloc_mixer_state(struct audio_route *ar)
474 {
475 unsigned int i;
476 unsigned int j;
477 unsigned int num_values;
478 struct mixer_ctl *ctl;
479 enum mixer_ctl_type type;
480
481 ar->num_mixer_ctls = mixer_get_num_ctls(ar->mixer);
482 ar->mixer_state = malloc(ar->num_mixer_ctls * sizeof(struct mixer_state));
483 if (!ar->mixer_state)
484 return -1;
485
486 for (i = 0; i < ar->num_mixer_ctls; i++) {
487 ctl = mixer_get_ctl(ar->mixer, i);
488 num_values = mixer_ctl_get_num_values(ctl);
489
490 ar->mixer_state[i].ctl = ctl;
491 ar->mixer_state[i].num_values = num_values;
492
493 /* Skip unsupported types that are not supported yet in XML */
494 type = mixer_ctl_get_type(ctl);
495
496 if (!is_supported_ctl_type(type))
497 continue;
498
499 ar->mixer_state[i].old_value = malloc(num_values * sizeof(int));
500 ar->mixer_state[i].new_value = malloc(num_values * sizeof(int));
501 ar->mixer_state[i].reset_value = malloc(num_values * sizeof(int));
502
503 if (type == MIXER_CTL_TYPE_ENUM)
504 ar->mixer_state[i].old_value[0] = mixer_ctl_get_value(ctl, 0);
505 else
506 mixer_ctl_get_array(ctl, ar->mixer_state[i].old_value, num_values);
507 memcpy(ar->mixer_state[i].new_value, ar->mixer_state[i].old_value,
508 num_values * sizeof(int));
509 }
510
511 return 0;
512 }
513
free_mixer_state(struct audio_route * ar)514 static void free_mixer_state(struct audio_route *ar)
515 {
516 unsigned int i;
517 enum mixer_ctl_type type;
518
519 for (i = 0; i < ar->num_mixer_ctls; i++) {
520 type = mixer_ctl_get_type(ar->mixer_state[i].ctl);
521 if (!is_supported_ctl_type(type))
522 continue;
523
524 free(ar->mixer_state[i].old_value);
525 free(ar->mixer_state[i].new_value);
526 free(ar->mixer_state[i].reset_value);
527 }
528
529 free(ar->mixer_state);
530 ar->mixer_state = NULL;
531 }
532
533 /* Update the mixer with any changed values */
audio_route_update_mixer(struct audio_route * ar)534 int audio_route_update_mixer(struct audio_route *ar)
535 {
536 unsigned int i;
537 unsigned int j;
538 struct mixer_ctl *ctl;
539
540 for (i = 0; i < ar->num_mixer_ctls; i++) {
541 unsigned int num_values = ar->mixer_state[i].num_values;
542 enum mixer_ctl_type type;
543
544 ctl = ar->mixer_state[i].ctl;
545
546 /* Skip unsupported types */
547 type = mixer_ctl_get_type(ctl);
548 if (!is_supported_ctl_type(type))
549 continue;
550
551 /* if the value has changed, update the mixer */
552 bool changed = false;
553 for (j = 0; j < num_values; j++) {
554 if (ar->mixer_state[i].old_value[j] != ar->mixer_state[i].new_value[j]) {
555 changed = true;
556 break;
557 }
558 }
559 if (changed) {
560 if (type == MIXER_CTL_TYPE_ENUM)
561 mixer_ctl_set_value(ctl, 0, ar->mixer_state[i].new_value[0]);
562 else
563 mixer_ctl_set_array(ctl, ar->mixer_state[i].new_value, num_values);
564 memcpy(ar->mixer_state[i].old_value, ar->mixer_state[i].new_value,
565 num_values * sizeof(int));
566 }
567 }
568
569 return 0;
570 }
571
572 /* saves the current state of the mixer, for resetting all controls */
save_mixer_state(struct audio_route * ar)573 static void save_mixer_state(struct audio_route *ar)
574 {
575 unsigned int i;
576 enum mixer_ctl_type type;
577
578 for (i = 0; i < ar->num_mixer_ctls; i++) {
579 type = mixer_ctl_get_type(ar->mixer_state[i].ctl);
580 if (!is_supported_ctl_type(type))
581 continue;
582
583 memcpy(ar->mixer_state[i].reset_value, ar->mixer_state[i].new_value,
584 ar->mixer_state[i].num_values * sizeof(int));
585 }
586 }
587
588 /* Reset the audio routes back to the initial state */
audio_route_reset(struct audio_route * ar)589 void audio_route_reset(struct audio_route *ar)
590 {
591 unsigned int i;
592 enum mixer_ctl_type type;
593
594 /* load all of the saved values */
595 for (i = 0; i < ar->num_mixer_ctls; i++) {
596 type = mixer_ctl_get_type(ar->mixer_state[i].ctl);
597 if (!is_supported_ctl_type(type))
598 continue;
599
600 memcpy(ar->mixer_state[i].new_value, ar->mixer_state[i].reset_value,
601 ar->mixer_state[i].num_values * sizeof(int));
602 }
603 }
604
605 /* Apply an audio route path by name */
audio_route_apply_path(struct audio_route * ar,const char * name)606 int audio_route_apply_path(struct audio_route *ar, const char *name)
607 {
608 struct mixer_path *path;
609
610 if (!ar) {
611 ALOGE("invalid audio_route");
612 return -1;
613 }
614
615 path = path_get_by_name(ar, name);
616 if (!path) {
617 ALOGE("unable to find path '%s'", name);
618 return -1;
619 }
620
621 path_apply(ar, path);
622
623 return 0;
624 }
625
626 /* Reset an audio route path by name */
audio_route_reset_path(struct audio_route * ar,const char * name)627 int audio_route_reset_path(struct audio_route *ar, const char *name)
628 {
629 struct mixer_path *path;
630
631 if (!ar) {
632 ALOGE("invalid audio_route");
633 return -1;
634 }
635
636 path = path_get_by_name(ar, name);
637 if (!path) {
638 ALOGE("unable to find path '%s'", name);
639 return -1;
640 }
641
642 path_reset(ar, path);
643
644 return 0;
645 }
646
audio_route_init(unsigned int card,const char * xml_path)647 struct audio_route *audio_route_init(unsigned int card, const char *xml_path)
648 {
649 struct config_parse_state state;
650 XML_Parser parser;
651 FILE *file;
652 int bytes_read;
653 void *buf;
654 int i;
655 struct audio_route *ar;
656
657 ar = calloc(1, sizeof(struct audio_route));
658 if (!ar)
659 goto err_calloc;
660
661 ar->mixer = mixer_open(card);
662 if (!ar->mixer) {
663 ALOGE("Unable to open the mixer, aborting.");
664 goto err_mixer_open;
665 }
666
667 ar->mixer_path = NULL;
668 ar->mixer_path_size = 0;
669 ar->num_mixer_paths = 0;
670
671 /* allocate space for and read current mixer settings */
672 if (alloc_mixer_state(ar) < 0)
673 goto err_mixer_state;
674
675 /* use the default XML path if none is provided */
676 if (xml_path == NULL)
677 xml_path = MIXER_XML_PATH;
678
679 file = fopen(xml_path, "r");
680
681 if (!file) {
682 ALOGE("Failed to open %s", xml_path);
683 goto err_fopen;
684 }
685
686 parser = XML_ParserCreate(NULL);
687 if (!parser) {
688 ALOGE("Failed to create XML parser");
689 goto err_parser_create;
690 }
691
692 memset(&state, 0, sizeof(state));
693 state.ar = ar;
694 XML_SetUserData(parser, &state);
695 XML_SetElementHandler(parser, start_tag, end_tag);
696
697 for (;;) {
698 buf = XML_GetBuffer(parser, BUF_SIZE);
699 if (buf == NULL)
700 goto err_parse;
701
702 bytes_read = fread(buf, 1, BUF_SIZE, file);
703 if (bytes_read < 0)
704 goto err_parse;
705
706 if (XML_ParseBuffer(parser, bytes_read,
707 bytes_read == 0) == XML_STATUS_ERROR) {
708 ALOGE("Error in mixer xml (%s)", MIXER_XML_PATH);
709 goto err_parse;
710 }
711
712 if (bytes_read == 0)
713 break;
714 }
715
716 /* apply the initial mixer values, and save them so we can reset the
717 mixer to the original values */
718 audio_route_update_mixer(ar);
719 save_mixer_state(ar);
720
721 XML_ParserFree(parser);
722 fclose(file);
723 return ar;
724
725 err_parse:
726 XML_ParserFree(parser);
727 err_parser_create:
728 fclose(file);
729 err_fopen:
730 free_mixer_state(ar);
731 err_mixer_state:
732 mixer_close(ar->mixer);
733 err_mixer_open:
734 free(ar);
735 ar = NULL;
736 err_calloc:
737 return NULL;
738 }
739
audio_route_free(struct audio_route * ar)740 void audio_route_free(struct audio_route *ar)
741 {
742 free_mixer_state(ar);
743 mixer_close(ar->mixer);
744 free(ar);
745 }
746