blob: c45acd008bbad3431c0798947ed4f0f06155996b [file] [log] [blame]
bryant_liuba2b4392014-06-11 16:49:30 +08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "AudioPolicyEffects"
Eric Laurent897a4082014-07-11 16:51:53 -070018//#define LOG_NDEBUG 0
bryant_liuba2b4392014-06-11 16:49:30 +080019
20#include <stdlib.h>
21#include <stdio.h>
22#include <string.h>
23#include <cutils/misc.h>
24#include <media/AudioEffect.h>
25#include <system/audio.h>
26#include <hardware/audio_effect.h>
27#include <audio_effects/audio_effects_conf.h>
28#include <utils/Vector.h>
29#include <utils/SortedVector.h>
30#include <cutils/config_utils.h>
31#include "AudioPolicyEffects.h"
32#include "ServiceUtilities.h"
33
34namespace android {
35
36// ----------------------------------------------------------------------------
37// AudioPolicyEffects Implementation
38// ----------------------------------------------------------------------------
39
40AudioPolicyEffects::AudioPolicyEffects()
41{
42 // load automatic audio effect modules
43 if (access(AUDIO_EFFECT_VENDOR_CONFIG_FILE, R_OK) == 0) {
44 loadAudioEffectConfig(AUDIO_EFFECT_VENDOR_CONFIG_FILE);
45 } else if (access(AUDIO_EFFECT_DEFAULT_CONFIG_FILE, R_OK) == 0) {
46 loadAudioEffectConfig(AUDIO_EFFECT_DEFAULT_CONFIG_FILE);
47 }
48}
49
50
51AudioPolicyEffects::~AudioPolicyEffects()
52{
53 size_t i = 0;
54 // release audio input processing resources
55 for (i = 0; i < mInputSources.size(); i++) {
56 delete mInputSources.valueAt(i);
57 }
58 mInputSources.clear();
59
60 for (i = 0; i < mInputs.size(); i++) {
61 mInputs.valueAt(i)->mEffects.clear();
62 delete mInputs.valueAt(i);
63 }
64 mInputs.clear();
65
66 // release audio output processing resources
67 for (i = 0; i < mOutputStreams.size(); i++) {
68 delete mOutputStreams.valueAt(i);
69 }
70 mOutputStreams.clear();
71
72 for (i = 0; i < mOutputSessions.size(); i++) {
73 mOutputSessions.valueAt(i)->mEffects.clear();
74 delete mOutputSessions.valueAt(i);
75 }
76 mOutputSessions.clear();
77}
78
79
80status_t AudioPolicyEffects::addInputEffects(audio_io_handle_t input,
81 audio_source_t inputSource,
82 int audioSession)
83{
84 status_t status = NO_ERROR;
85
86 // create audio pre processors according to input source
87 audio_source_t aliasSource = (inputSource == AUDIO_SOURCE_HOTWORD) ?
88 AUDIO_SOURCE_VOICE_RECOGNITION : inputSource;
89
90 ssize_t index = mInputSources.indexOfKey(aliasSource);
91 if (index < 0) {
92 ALOGV("addInputEffects(): no processing needs to be attached to this source");
93 return status;
94 }
95 ssize_t idx = mInputs.indexOfKey(input);
96 EffectVector *inputDesc;
97 if (idx < 0) {
98 inputDesc = new EffectVector(audioSession);
99 mInputs.add(input, inputDesc);
100 } else {
bryant_liu890a5632014-08-20 18:06:13 +0800101 // EffectVector is existing and we just need to increase ref count
bryant_liuba2b4392014-06-11 16:49:30 +0800102 inputDesc = mInputs.valueAt(idx);
103 }
bryant_liu890a5632014-08-20 18:06:13 +0800104 inputDesc->mRefCount++;
105
106 ALOGV("addInputEffects(): input: %d, refCount: %d", input, inputDesc->mRefCount);
bryant_liuba2b4392014-06-11 16:49:30 +0800107
108 Vector <EffectDesc *> effects = mInputSources.valueAt(index)->mEffects;
109 for (size_t i = 0; i < effects.size(); i++) {
110 EffectDesc *effect = effects[i];
111 sp<AudioEffect> fx = new AudioEffect(NULL, &effect->mUuid, -1, 0, 0, audioSession, input);
112 status_t status = fx->initCheck();
113 if (status != NO_ERROR && status != ALREADY_EXISTS) {
114 ALOGW("addInputEffects(): failed to create Fx %s on source %d",
115 effect->mName, (int32_t)aliasSource);
116 // fx goes out of scope and strong ref on AudioEffect is released
117 continue;
118 }
119 for (size_t j = 0; j < effect->mParams.size(); j++) {
120 fx->setParameter(effect->mParams[j]);
121 }
122 ALOGV("addInputEffects(): added Fx %s on source: %d", effect->mName, (int32_t)aliasSource);
123 inputDesc->mEffects.add(fx);
124 }
125 setProcessorEnabled(inputDesc, true);
126
127 return status;
128}
129
130
131status_t AudioPolicyEffects::releaseInputEffects(audio_io_handle_t input)
132{
133 status_t status = NO_ERROR;
134
135 ssize_t index = mInputs.indexOfKey(input);
136 if (index < 0) {
137 return status;
138 }
139 EffectVector *inputDesc = mInputs.valueAt(index);
bryant_liu890a5632014-08-20 18:06:13 +0800140 inputDesc->mRefCount--;
141 ALOGV("releaseInputEffects(): input: %d, refCount: %d", input, inputDesc->mRefCount);
142 if (inputDesc->mRefCount == 0) {
143 setProcessorEnabled(inputDesc, false);
144 delete inputDesc;
145 mInputs.removeItemsAt(index);
146 ALOGV("releaseInputEffects(): all effects released");
147 }
bryant_liuba2b4392014-06-11 16:49:30 +0800148 return status;
149}
150
151status_t AudioPolicyEffects::queryDefaultInputEffects(int audioSession,
152 effect_descriptor_t *descriptors,
153 uint32_t *count)
154{
155 status_t status = NO_ERROR;
156
157 size_t index;
158 for (index = 0; index < mInputs.size(); index++) {
159 if (mInputs.valueAt(index)->mSessionId == audioSession) {
160 break;
161 }
162 }
163 if (index == mInputs.size()) {
164 *count = 0;
165 return BAD_VALUE;
166 }
167 Vector< sp<AudioEffect> > effects = mInputs.valueAt(index)->mEffects;
168
169 for (size_t i = 0; i < effects.size(); i++) {
170 effect_descriptor_t desc = effects[i]->descriptor();
171 if (i < *count) {
172 descriptors[i] = desc;
173 }
174 }
175 if (effects.size() > *count) {
176 status = NO_MEMORY;
177 }
178 *count = effects.size();
179 return status;
180}
181
182
183status_t AudioPolicyEffects::queryDefaultOutputSessionEffects(int audioSession,
184 effect_descriptor_t *descriptors,
185 uint32_t *count)
186{
187 status_t status = NO_ERROR;
188
189 size_t index;
190 for (index = 0; index < mOutputSessions.size(); index++) {
191 if (mOutputSessions.valueAt(index)->mSessionId == audioSession) {
192 break;
193 }
194 }
195 if (index == mOutputSessions.size()) {
196 *count = 0;
197 return BAD_VALUE;
198 }
199 Vector< sp<AudioEffect> > effects = mOutputSessions.valueAt(index)->mEffects;
200
201 for (size_t i = 0; i < effects.size(); i++) {
202 effect_descriptor_t desc = effects[i]->descriptor();
203 if (i < *count) {
204 descriptors[i] = desc;
205 }
206 }
207 if (effects.size() > *count) {
208 status = NO_MEMORY;
209 }
210 *count = effects.size();
211 return status;
212}
213
214
215status_t AudioPolicyEffects::addOutputSessionEffects(audio_io_handle_t output,
216 audio_stream_type_t stream,
217 int audioSession)
218{
219 status_t status = NO_ERROR;
220
221 // create audio processors according to stream
222 ssize_t index = mOutputStreams.indexOfKey(stream);
223 if (index < 0) {
224 ALOGV("addOutputSessionEffects(): no output processing needed for this stream");
225 return NO_ERROR;
226 }
227
228 ssize_t idx = mOutputSessions.indexOfKey(audioSession);
229 EffectVector *procDesc;
230 if (idx < 0) {
231 procDesc = new EffectVector(audioSession);
232 mOutputSessions.add(audioSession, procDesc);
233 } else {
bryant_liu890a5632014-08-20 18:06:13 +0800234 // EffectVector is existing and we just need to increase ref count
bryant_liuba2b4392014-06-11 16:49:30 +0800235 procDesc = mOutputSessions.valueAt(idx);
236 }
bryant_liu890a5632014-08-20 18:06:13 +0800237 procDesc->mRefCount++;
238
239 ALOGV("addOutputSessionEffects(): session: %d, refCount: %d", audioSession, procDesc->mRefCount);
bryant_liuba2b4392014-06-11 16:49:30 +0800240
241 Vector <EffectDesc *> effects = mOutputStreams.valueAt(index)->mEffects;
242 for (size_t i = 0; i < effects.size(); i++) {
243 EffectDesc *effect = effects[i];
244 sp<AudioEffect> fx = new AudioEffect(NULL, &effect->mUuid, 0, 0, 0, audioSession, output);
245 status_t status = fx->initCheck();
246 if (status != NO_ERROR && status != ALREADY_EXISTS) {
247 ALOGE("addOutputSessionEffects(): failed to create Fx %s on session %d",
248 effect->mName, audioSession);
249 // fx goes out of scope and strong ref on AudioEffect is released
250 continue;
251 }
252 ALOGV("addOutputSessionEffects(): added Fx %s on session: %d for stream: %d",
253 effect->mName, audioSession, (int32_t)stream);
254 procDesc->mEffects.add(fx);
255 }
256
257 setProcessorEnabled(procDesc, true);
258
259 return status;
260}
261
262status_t AudioPolicyEffects::releaseOutputSessionEffects(audio_io_handle_t output,
263 audio_stream_type_t stream,
264 int audioSession)
265{
266 status_t status = NO_ERROR;
267 (void) output; // argument not used for now
268 (void) stream; // argument not used for now
269
270 ssize_t index = mOutputSessions.indexOfKey(audioSession);
271 if (index < 0) {
272 ALOGV("releaseOutputSessionEffects: no output processing was attached to this stream");
273 return NO_ERROR;
274 }
275
276 EffectVector *procDesc = mOutputSessions.valueAt(index);
bryant_liu890a5632014-08-20 18:06:13 +0800277 procDesc->mRefCount--;
278 ALOGV("releaseOutputSessionEffects(): session: %d, refCount: %d", audioSession, procDesc->mRefCount);
279 if (procDesc->mRefCount == 0) {
280 setProcessorEnabled(procDesc, false);
281 procDesc->mEffects.clear();
282 delete procDesc;
283 mOutputSessions.removeItemsAt(index);
284 ALOGV("releaseOutputSessionEffects(): output processing released from session: %d",
285 audioSession);
286 }
bryant_liuba2b4392014-06-11 16:49:30 +0800287 return status;
288}
289
290
291void AudioPolicyEffects::setProcessorEnabled(const EffectVector *effectVector, bool enabled)
292{
293 const Vector<sp<AudioEffect> > &fxVector = effectVector->mEffects;
294 for (size_t i = 0; i < fxVector.size(); i++) {
295 fxVector.itemAt(i)->setEnabled(enabled);
296 }
297}
298
299
300// ----------------------------------------------------------------------------
301// Audio processing configuration
302// ----------------------------------------------------------------------------
303
304/*static*/ const char * const AudioPolicyEffects::kInputSourceNames[AUDIO_SOURCE_CNT -1] = {
305 MIC_SRC_TAG,
306 VOICE_UL_SRC_TAG,
307 VOICE_DL_SRC_TAG,
308 VOICE_CALL_SRC_TAG,
309 CAMCORDER_SRC_TAG,
310 VOICE_REC_SRC_TAG,
311 VOICE_COMM_SRC_TAG
312};
313
314// returns the audio_source_t enum corresponding to the input source name or
315// AUDIO_SOURCE_CNT is no match found
316audio_source_t AudioPolicyEffects::inputSourceNameToEnum(const char *name)
317{
318 int i;
319 for (i = AUDIO_SOURCE_MIC; i < AUDIO_SOURCE_CNT; i++) {
320 if (strcmp(name, kInputSourceNames[i - AUDIO_SOURCE_MIC]) == 0) {
321 ALOGV("inputSourceNameToEnum found source %s %d", name, i);
322 break;
323 }
324 }
325 return (audio_source_t)i;
326}
327
328const char *AudioPolicyEffects::kStreamNames[AUDIO_STREAM_CNT+1] = {
329 AUDIO_STREAM_DEFAULT_TAG,
330 AUDIO_STREAM_VOICE_CALL_TAG,
331 AUDIO_STREAM_SYSTEM_TAG,
332 AUDIO_STREAM_RING_TAG,
333 AUDIO_STREAM_MUSIC_TAG,
334 AUDIO_STREAM_ALARM_TAG,
335 AUDIO_STREAM_NOTIFICATION_TAG,
336 AUDIO_STREAM_BLUETOOTH_SCO_TAG,
337 AUDIO_STREAM_ENFORCED_AUDIBLE_TAG,
338 AUDIO_STREAM_DTMF_TAG,
339 AUDIO_STREAM_TTS_TAG
340};
341
342// returns the audio_stream_t enum corresponding to the output stream name or
343// AUDIO_STREAM_CNT is no match found
344audio_stream_type_t AudioPolicyEffects::streamNameToEnum(const char *name)
345{
346 int i;
347 for (i = AUDIO_STREAM_DEFAULT; i < AUDIO_STREAM_CNT; i++) {
348 if (strcmp(name, kStreamNames[i - AUDIO_STREAM_DEFAULT]) == 0) {
349 ALOGV("streamNameToEnum found stream %s %d", name, i);
350 break;
351 }
352 }
353 return (audio_stream_type_t)i;
354}
355
356// ----------------------------------------------------------------------------
357// Audio Effect Config parser
358// ----------------------------------------------------------------------------
359
360size_t AudioPolicyEffects::growParamSize(char *param,
361 size_t size,
362 size_t *curSize,
363 size_t *totSize)
364{
365 // *curSize is at least sizeof(effect_param_t) + 2 * sizeof(int)
366 size_t pos = ((*curSize - 1 ) / size + 1) * size;
367
368 if (pos + size > *totSize) {
369 while (pos + size > *totSize) {
370 *totSize += ((*totSize + 7) / 8) * 4;
371 }
372 param = (char *)realloc(param, *totSize);
373 }
374 *curSize = pos + size;
375 return pos;
376}
377
378size_t AudioPolicyEffects::readParamValue(cnode *node,
379 char *param,
380 size_t *curSize,
381 size_t *totSize)
382{
383 if (strncmp(node->name, SHORT_TAG, sizeof(SHORT_TAG) + 1) == 0) {
384 size_t pos = growParamSize(param, sizeof(short), curSize, totSize);
385 *(short *)((char *)param + pos) = (short)atoi(node->value);
386 ALOGV("readParamValue() reading short %d", *(short *)((char *)param + pos));
387 return sizeof(short);
388 } else if (strncmp(node->name, INT_TAG, sizeof(INT_TAG) + 1) == 0) {
389 size_t pos = growParamSize(param, sizeof(int), curSize, totSize);
390 *(int *)((char *)param + pos) = atoi(node->value);
391 ALOGV("readParamValue() reading int %d", *(int *)((char *)param + pos));
392 return sizeof(int);
393 } else if (strncmp(node->name, FLOAT_TAG, sizeof(FLOAT_TAG) + 1) == 0) {
394 size_t pos = growParamSize(param, sizeof(float), curSize, totSize);
395 *(float *)((char *)param + pos) = (float)atof(node->value);
396 ALOGV("readParamValue() reading float %f",*(float *)((char *)param + pos));
397 return sizeof(float);
398 } else if (strncmp(node->name, BOOL_TAG, sizeof(BOOL_TAG) + 1) == 0) {
399 size_t pos = growParamSize(param, sizeof(bool), curSize, totSize);
400 if (strncmp(node->value, "false", strlen("false") + 1) == 0) {
401 *(bool *)((char *)param + pos) = false;
402 } else {
403 *(bool *)((char *)param + pos) = true;
404 }
405 ALOGV("readParamValue() reading bool %s",*(bool *)((char *)param + pos) ? "true" : "false");
406 return sizeof(bool);
407 } else if (strncmp(node->name, STRING_TAG, sizeof(STRING_TAG) + 1) == 0) {
408 size_t len = strnlen(node->value, EFFECT_STRING_LEN_MAX);
409 if (*curSize + len + 1 > *totSize) {
410 *totSize = *curSize + len + 1;
411 param = (char *)realloc(param, *totSize);
412 }
413 strncpy(param + *curSize, node->value, len);
414 *curSize += len;
415 param[*curSize] = '\0';
416 ALOGV("readParamValue() reading string %s", param + *curSize - len);
417 return len;
418 }
419 ALOGW("readParamValue() unknown param type %s", node->name);
420 return 0;
421}
422
423effect_param_t *AudioPolicyEffects::loadEffectParameter(cnode *root)
424{
425 cnode *param;
426 cnode *value;
427 size_t curSize = sizeof(effect_param_t);
428 size_t totSize = sizeof(effect_param_t) + 2 * sizeof(int);
429 effect_param_t *fx_param = (effect_param_t *)malloc(totSize);
430
431 param = config_find(root, PARAM_TAG);
432 value = config_find(root, VALUE_TAG);
433 if (param == NULL && value == NULL) {
434 // try to parse simple parameter form {int int}
435 param = root->first_child;
436 if (param != NULL) {
437 // Note: that a pair of random strings is read as 0 0
438 int *ptr = (int *)fx_param->data;
439 int *ptr2 = (int *)((char *)param + sizeof(effect_param_t));
440 ALOGW("loadEffectParameter() ptr %p ptr2 %p", ptr, ptr2);
441 *ptr++ = atoi(param->name);
442 *ptr = atoi(param->value);
443 fx_param->psize = sizeof(int);
444 fx_param->vsize = sizeof(int);
445 return fx_param;
446 }
447 }
448 if (param == NULL || value == NULL) {
449 ALOGW("loadEffectParameter() invalid parameter description %s", root->name);
450 goto error;
451 }
452
453 fx_param->psize = 0;
454 param = param->first_child;
455 while (param) {
456 ALOGV("loadEffectParameter() reading param of type %s", param->name);
457 size_t size = readParamValue(param, (char *)fx_param, &curSize, &totSize);
458 if (size == 0) {
459 goto error;
460 }
461 fx_param->psize += size;
462 param = param->next;
463 }
464
465 // align start of value field on 32 bit boundary
466 curSize = ((curSize - 1 ) / sizeof(int) + 1) * sizeof(int);
467
468 fx_param->vsize = 0;
469 value = value->first_child;
470 while (value) {
471 ALOGV("loadEffectParameter() reading value of type %s", value->name);
472 size_t size = readParamValue(value, (char *)fx_param, &curSize, &totSize);
473 if (size == 0) {
474 goto error;
475 }
476 fx_param->vsize += size;
477 value = value->next;
478 }
479
480 return fx_param;
481
482error:
483 delete fx_param;
484 return NULL;
485}
486
487void AudioPolicyEffects::loadEffectParameters(cnode *root, Vector <effect_param_t *>& params)
488{
489 cnode *node = root->first_child;
490 while (node) {
491 ALOGV("loadEffectParameters() loading param %s", node->name);
492 effect_param_t *param = loadEffectParameter(node);
493 if (param == NULL) {
494 node = node->next;
495 continue;
496 }
497 params.add(param);
498 node = node->next;
499 }
500}
501
502
503AudioPolicyEffects::EffectDescVector *AudioPolicyEffects::loadEffectConfig(
504 cnode *root,
505 const Vector <EffectDesc *>& effects)
506{
507 cnode *node = root->first_child;
508 if (node == NULL) {
509 ALOGW("loadInputSource() empty element %s", root->name);
510 return NULL;
511 }
512 EffectDescVector *desc = new EffectDescVector();
513 while (node) {
514 size_t i;
515 for (i = 0; i < effects.size(); i++) {
516 if (strncmp(effects[i]->mName, node->name, EFFECT_STRING_LEN_MAX) == 0) {
517 ALOGV("loadEffectConfig() found effect %s in list", node->name);
518 break;
519 }
520 }
521 if (i == effects.size()) {
522 ALOGV("loadEffectConfig() effect %s not in list", node->name);
523 node = node->next;
524 continue;
525 }
526 EffectDesc *effect = new EffectDesc(*effects[i]); // deep copy
527 loadEffectParameters(node, effect->mParams);
528 ALOGV("loadEffectConfig() adding effect %s uuid %08x",
529 effect->mName, effect->mUuid.timeLow);
530 desc->mEffects.add(effect);
531 node = node->next;
532 }
533 if (desc->mEffects.size() == 0) {
534 ALOGW("loadEffectConfig() no valid effects found in config %s", root->name);
535 delete desc;
536 return NULL;
537 }
538 return desc;
539}
540
541status_t AudioPolicyEffects::loadInputEffectConfigurations(cnode *root,
542 const Vector <EffectDesc *>& effects)
543{
544 cnode *node = config_find(root, PREPROCESSING_TAG);
545 if (node == NULL) {
546 return -ENOENT;
547 }
548 node = node->first_child;
549 while (node) {
550 audio_source_t source = inputSourceNameToEnum(node->name);
551 if (source == AUDIO_SOURCE_CNT) {
552 ALOGW("loadInputSources() invalid input source %s", node->name);
553 node = node->next;
554 continue;
555 }
556 ALOGV("loadInputSources() loading input source %s", node->name);
557 EffectDescVector *desc = loadEffectConfig(node, effects);
558 if (desc == NULL) {
559 node = node->next;
560 continue;
561 }
562 mInputSources.add(source, desc);
563 node = node->next;
564 }
565 return NO_ERROR;
566}
567
568status_t AudioPolicyEffects::loadStreamEffectConfigurations(cnode *root,
569 const Vector <EffectDesc *>& effects)
570{
571 cnode *node = config_find(root, OUTPUT_SESSION_PROCESSING_TAG);
572 if (node == NULL) {
573 return -ENOENT;
574 }
575 node = node->first_child;
576 while (node) {
577 audio_stream_type_t stream = streamNameToEnum(node->name);
578 if (stream == AUDIO_STREAM_CNT) {
579 ALOGW("loadStreamEffectConfigurations() invalid output stream %s", node->name);
580 node = node->next;
581 continue;
582 }
583 ALOGV("loadStreamEffectConfigurations() loading output stream %s", node->name);
584 EffectDescVector *desc = loadEffectConfig(node, effects);
585 if (desc == NULL) {
586 node = node->next;
587 continue;
588 }
589 mOutputStreams.add(stream, desc);
590 node = node->next;
591 }
592 return NO_ERROR;
593}
594
595AudioPolicyEffects::EffectDesc *AudioPolicyEffects::loadEffect(cnode *root)
596{
597 cnode *node = config_find(root, UUID_TAG);
598 if (node == NULL) {
599 return NULL;
600 }
601 effect_uuid_t uuid;
602 if (AudioEffect::stringToGuid(node->value, &uuid) != NO_ERROR) {
603 ALOGW("loadEffect() invalid uuid %s", node->value);
604 return NULL;
605 }
606 return new EffectDesc(root->name, uuid);
607}
608
609status_t AudioPolicyEffects::loadEffects(cnode *root, Vector <EffectDesc *>& effects)
610{
611 cnode *node = config_find(root, EFFECTS_TAG);
612 if (node == NULL) {
613 return -ENOENT;
614 }
615 node = node->first_child;
616 while (node) {
617 ALOGV("loadEffects() loading effect %s", node->name);
618 EffectDesc *effect = loadEffect(node);
619 if (effect == NULL) {
620 node = node->next;
621 continue;
622 }
623 effects.add(effect);
624 node = node->next;
625 }
626 return NO_ERROR;
627}
628
629status_t AudioPolicyEffects::loadAudioEffectConfig(const char *path)
630{
631 cnode *root;
632 char *data;
633
634 data = (char *)load_file(path, NULL);
635 if (data == NULL) {
636 return -ENODEV;
637 }
638 root = config_node("", "");
639 config_load(root, data);
640
641 Vector <EffectDesc *> effects;
642 loadEffects(root, effects);
643 loadInputEffectConfigurations(root, effects);
644 loadStreamEffectConfigurations(root, effects);
645
646 config_free(root);
647 free(root);
648 free(data);
649
650 return NO_ERROR;
651}
652
653
654}; // namespace android