AudioPolicyEffects.cpp revision 84332aaa807037baca05340875f2d94fcc519ac4
1/*
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"
18//#define LOG_NDEBUG 0
19
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    Mutex::Autolock _l(mLock);
91    ssize_t index = mInputSources.indexOfKey(aliasSource);
92    if (index < 0) {
93        ALOGV("addInputEffects(): no processing needs to be attached to this source");
94        return status;
95    }
96    ssize_t idx = mInputs.indexOfKey(input);
97    EffectVector *inputDesc;
98    if (idx < 0) {
99        inputDesc = new EffectVector(audioSession);
100        mInputs.add(input, inputDesc);
101    } else {
102        // EffectVector is existing and we just need to increase ref count
103        inputDesc = mInputs.valueAt(idx);
104    }
105    inputDesc->mRefCount++;
106
107    ALOGV("addInputEffects(): input: %d, refCount: %d", input, inputDesc->mRefCount);
108    if (inputDesc->mRefCount == 1) {
109        Vector <EffectDesc *> effects = mInputSources.valueAt(index)->mEffects;
110        for (size_t i = 0; i < effects.size(); i++) {
111            EffectDesc *effect = effects[i];
112            sp<AudioEffect> fx = new AudioEffect(NULL, String16("android"), &effect->mUuid, -1, 0,
113                                                 0, audioSession, input);
114            status_t status = fx->initCheck();
115            if (status != NO_ERROR && status != ALREADY_EXISTS) {
116                ALOGW("addInputEffects(): failed to create Fx %s on source %d",
117                      effect->mName, (int32_t)aliasSource);
118                // fx goes out of scope and strong ref on AudioEffect is released
119                continue;
120            }
121            for (size_t j = 0; j < effect->mParams.size(); j++) {
122                fx->setParameter(effect->mParams[j]);
123            }
124            ALOGV("addInputEffects(): added Fx %s on source: %d",
125                  effect->mName, (int32_t)aliasSource);
126            inputDesc->mEffects.add(fx);
127        }
128        inputDesc->setProcessorEnabled(true);
129    }
130    return status;
131}
132
133
134status_t AudioPolicyEffects::releaseInputEffects(audio_io_handle_t input)
135{
136    status_t status = NO_ERROR;
137
138    Mutex::Autolock _l(mLock);
139    ssize_t index = mInputs.indexOfKey(input);
140    if (index < 0) {
141        return status;
142    }
143    EffectVector *inputDesc = mInputs.valueAt(index);
144    inputDesc->mRefCount--;
145    ALOGV("releaseInputEffects(): input: %d, refCount: %d", input, inputDesc->mRefCount);
146    if (inputDesc->mRefCount == 0) {
147        inputDesc->setProcessorEnabled(false);
148        delete inputDesc;
149        mInputs.removeItemsAt(index);
150        ALOGV("releaseInputEffects(): all effects released");
151    }
152    return status;
153}
154
155status_t AudioPolicyEffects::queryDefaultInputEffects(int audioSession,
156                                                      effect_descriptor_t *descriptors,
157                                                      uint32_t *count)
158{
159    status_t status = NO_ERROR;
160
161    Mutex::Autolock _l(mLock);
162    size_t index;
163    for (index = 0; index < mInputs.size(); index++) {
164        if (mInputs.valueAt(index)->mSessionId == audioSession) {
165            break;
166        }
167    }
168    if (index == mInputs.size()) {
169        *count = 0;
170        return BAD_VALUE;
171    }
172    Vector< sp<AudioEffect> > effects = mInputs.valueAt(index)->mEffects;
173
174    for (size_t i = 0; i < effects.size(); i++) {
175        effect_descriptor_t desc = effects[i]->descriptor();
176        if (i < *count) {
177            descriptors[i] = desc;
178        }
179    }
180    if (effects.size() > *count) {
181        status = NO_MEMORY;
182    }
183    *count = effects.size();
184    return status;
185}
186
187
188status_t AudioPolicyEffects::queryDefaultOutputSessionEffects(int audioSession,
189                         effect_descriptor_t *descriptors,
190                         uint32_t *count)
191{
192    status_t status = NO_ERROR;
193
194    Mutex::Autolock _l(mLock);
195    size_t index;
196    for (index = 0; index < mOutputSessions.size(); index++) {
197        if (mOutputSessions.valueAt(index)->mSessionId == audioSession) {
198            break;
199        }
200    }
201    if (index == mOutputSessions.size()) {
202        *count = 0;
203        return BAD_VALUE;
204    }
205    Vector< sp<AudioEffect> > effects = mOutputSessions.valueAt(index)->mEffects;
206
207    for (size_t i = 0; i < effects.size(); i++) {
208        effect_descriptor_t desc = effects[i]->descriptor();
209        if (i < *count) {
210            descriptors[i] = desc;
211        }
212    }
213    if (effects.size() > *count) {
214        status = NO_MEMORY;
215    }
216    *count = effects.size();
217    return status;
218}
219
220
221status_t AudioPolicyEffects::addOutputSessionEffects(audio_io_handle_t output,
222                         audio_stream_type_t stream,
223                         int audioSession)
224{
225    status_t status = NO_ERROR;
226
227    Mutex::Autolock _l(mLock);
228    // create audio processors according to stream
229    // FIXME: should we have specific post processing settings for internal streams?
230    // default to media for now.
231    if (stream >= AUDIO_STREAM_PUBLIC_CNT) {
232        stream = AUDIO_STREAM_MUSIC;
233    }
234    ssize_t index = mOutputStreams.indexOfKey(stream);
235    if (index < 0) {
236        ALOGV("addOutputSessionEffects(): no output processing needed for this stream");
237        return NO_ERROR;
238    }
239
240    ssize_t idx = mOutputSessions.indexOfKey(audioSession);
241    EffectVector *procDesc;
242    if (idx < 0) {
243        procDesc = new EffectVector(audioSession);
244        mOutputSessions.add(audioSession, procDesc);
245    } else {
246        // EffectVector is existing and we just need to increase ref count
247        procDesc = mOutputSessions.valueAt(idx);
248    }
249    procDesc->mRefCount++;
250
251    ALOGV("addOutputSessionEffects(): session: %d, refCount: %d",
252          audioSession, procDesc->mRefCount);
253    if (procDesc->mRefCount == 1) {
254        Vector <EffectDesc *> effects = mOutputStreams.valueAt(index)->mEffects;
255        for (size_t i = 0; i < effects.size(); i++) {
256            EffectDesc *effect = effects[i];
257            sp<AudioEffect> fx = new AudioEffect(NULL, String16("android"), &effect->mUuid, 0, 0, 0,
258                                                 audioSession, output);
259            status_t status = fx->initCheck();
260            if (status != NO_ERROR && status != ALREADY_EXISTS) {
261                ALOGE("addOutputSessionEffects(): failed to create Fx  %s on session %d",
262                      effect->mName, audioSession);
263                // fx goes out of scope and strong ref on AudioEffect is released
264                continue;
265            }
266            ALOGV("addOutputSessionEffects(): added Fx %s on session: %d for stream: %d",
267                  effect->mName, audioSession, (int32_t)stream);
268            procDesc->mEffects.add(fx);
269        }
270
271        procDesc->setProcessorEnabled(true);
272    }
273    return status;
274}
275
276status_t AudioPolicyEffects::releaseOutputSessionEffects(audio_io_handle_t output,
277                         audio_stream_type_t stream,
278                         int audioSession)
279{
280    status_t status = NO_ERROR;
281    (void) output; // argument not used for now
282    (void) stream; // argument not used for now
283
284    Mutex::Autolock _l(mLock);
285    ssize_t index = mOutputSessions.indexOfKey(audioSession);
286    if (index < 0) {
287        ALOGV("releaseOutputSessionEffects: no output processing was attached to this stream");
288        return NO_ERROR;
289    }
290
291    EffectVector *procDesc = mOutputSessions.valueAt(index);
292    procDesc->mRefCount--;
293    ALOGV("releaseOutputSessionEffects(): session: %d, refCount: %d",
294          audioSession, procDesc->mRefCount);
295    if (procDesc->mRefCount == 0) {
296        procDesc->setProcessorEnabled(false);
297        procDesc->mEffects.clear();
298        delete procDesc;
299        mOutputSessions.removeItemsAt(index);
300        ALOGV("releaseOutputSessionEffects(): output processing released from session: %d",
301              audioSession);
302    }
303    return status;
304}
305
306
307void AudioPolicyEffects::EffectVector::setProcessorEnabled(bool enabled)
308{
309    for (size_t i = 0; i < mEffects.size(); i++) {
310        mEffects.itemAt(i)->setEnabled(enabled);
311    }
312}
313
314
315// ----------------------------------------------------------------------------
316// Audio processing configuration
317// ----------------------------------------------------------------------------
318
319/*static*/ const char * const AudioPolicyEffects::kInputSourceNames[AUDIO_SOURCE_CNT -1] = {
320    MIC_SRC_TAG,
321    VOICE_UL_SRC_TAG,
322    VOICE_DL_SRC_TAG,
323    VOICE_CALL_SRC_TAG,
324    CAMCORDER_SRC_TAG,
325    VOICE_REC_SRC_TAG,
326    VOICE_COMM_SRC_TAG,
327    UNPROCESSED_SRC_TAG
328};
329
330// returns the audio_source_t enum corresponding to the input source name or
331// AUDIO_SOURCE_CNT is no match found
332/*static*/ audio_source_t AudioPolicyEffects::inputSourceNameToEnum(const char *name)
333{
334    int i;
335    for (i = AUDIO_SOURCE_MIC; i < AUDIO_SOURCE_CNT; i++) {
336        if (strcmp(name, kInputSourceNames[i - AUDIO_SOURCE_MIC]) == 0) {
337            ALOGV("inputSourceNameToEnum found source %s %d", name, i);
338            break;
339        }
340    }
341    return (audio_source_t)i;
342}
343
344const char *AudioPolicyEffects::kStreamNames[AUDIO_STREAM_PUBLIC_CNT+1] = {
345    AUDIO_STREAM_DEFAULT_TAG,
346    AUDIO_STREAM_VOICE_CALL_TAG,
347    AUDIO_STREAM_SYSTEM_TAG,
348    AUDIO_STREAM_RING_TAG,
349    AUDIO_STREAM_MUSIC_TAG,
350    AUDIO_STREAM_ALARM_TAG,
351    AUDIO_STREAM_NOTIFICATION_TAG,
352    AUDIO_STREAM_BLUETOOTH_SCO_TAG,
353    AUDIO_STREAM_ENFORCED_AUDIBLE_TAG,
354    AUDIO_STREAM_DTMF_TAG,
355    AUDIO_STREAM_TTS_TAG
356};
357
358// returns the audio_stream_t enum corresponding to the output stream name or
359// AUDIO_STREAM_PUBLIC_CNT is no match found
360audio_stream_type_t AudioPolicyEffects::streamNameToEnum(const char *name)
361{
362    int i;
363    for (i = AUDIO_STREAM_DEFAULT; i < AUDIO_STREAM_PUBLIC_CNT; i++) {
364        if (strcmp(name, kStreamNames[i - AUDIO_STREAM_DEFAULT]) == 0) {
365            ALOGV("streamNameToEnum found stream %s %d", name, i);
366            break;
367        }
368    }
369    return (audio_stream_type_t)i;
370}
371
372// ----------------------------------------------------------------------------
373// Audio Effect Config parser
374// ----------------------------------------------------------------------------
375
376size_t AudioPolicyEffects::growParamSize(char *param,
377                                         size_t size,
378                                         size_t *curSize,
379                                         size_t *totSize)
380{
381    // *curSize is at least sizeof(effect_param_t) + 2 * sizeof(int)
382    size_t pos = ((*curSize - 1 ) / size + 1) * size;
383
384    if (pos + size > *totSize) {
385        while (pos + size > *totSize) {
386            *totSize += ((*totSize + 7) / 8) * 4;
387        }
388        param = (char *)realloc(param, *totSize);
389    }
390    *curSize = pos + size;
391    return pos;
392}
393
394size_t AudioPolicyEffects::readParamValue(cnode *node,
395                                          char *param,
396                                          size_t *curSize,
397                                          size_t *totSize)
398{
399    if (strncmp(node->name, SHORT_TAG, sizeof(SHORT_TAG) + 1) == 0) {
400        size_t pos = growParamSize(param, sizeof(short), curSize, totSize);
401        *(short *)((char *)param + pos) = (short)atoi(node->value);
402        ALOGV("readParamValue() reading short %d", *(short *)((char *)param + pos));
403        return sizeof(short);
404    } else if (strncmp(node->name, INT_TAG, sizeof(INT_TAG) + 1) == 0) {
405        size_t pos = growParamSize(param, sizeof(int), curSize, totSize);
406        *(int *)((char *)param + pos) = atoi(node->value);
407        ALOGV("readParamValue() reading int %d", *(int *)((char *)param + pos));
408        return sizeof(int);
409    } else if (strncmp(node->name, FLOAT_TAG, sizeof(FLOAT_TAG) + 1) == 0) {
410        size_t pos = growParamSize(param, sizeof(float), curSize, totSize);
411        *(float *)((char *)param + pos) = (float)atof(node->value);
412        ALOGV("readParamValue() reading float %f",*(float *)((char *)param + pos));
413        return sizeof(float);
414    } else if (strncmp(node->name, BOOL_TAG, sizeof(BOOL_TAG) + 1) == 0) {
415        size_t pos = growParamSize(param, sizeof(bool), curSize, totSize);
416        if (strncmp(node->value, "false", strlen("false") + 1) == 0) {
417            *(bool *)((char *)param + pos) = false;
418        } else {
419            *(bool *)((char *)param + pos) = true;
420        }
421        ALOGV("readParamValue() reading bool %s",*(bool *)((char *)param + pos) ? "true" : "false");
422        return sizeof(bool);
423    } else if (strncmp(node->name, STRING_TAG, sizeof(STRING_TAG) + 1) == 0) {
424        size_t len = strnlen(node->value, EFFECT_STRING_LEN_MAX);
425        if (*curSize + len + 1 > *totSize) {
426            *totSize = *curSize + len + 1;
427            param = (char *)realloc(param, *totSize);
428        }
429        strncpy(param + *curSize, node->value, len);
430        *curSize += len;
431        param[*curSize] = '\0';
432        ALOGV("readParamValue() reading string %s", param + *curSize - len);
433        return len;
434    }
435    ALOGW("readParamValue() unknown param type %s", node->name);
436    return 0;
437}
438
439effect_param_t *AudioPolicyEffects::loadEffectParameter(cnode *root)
440{
441    cnode *param;
442    cnode *value;
443    size_t curSize = sizeof(effect_param_t);
444    size_t totSize = sizeof(effect_param_t) + 2 * sizeof(int);
445    effect_param_t *fx_param = (effect_param_t *)malloc(totSize);
446
447    param = config_find(root, PARAM_TAG);
448    value = config_find(root, VALUE_TAG);
449    if (param == NULL && value == NULL) {
450        // try to parse simple parameter form {int int}
451        param = root->first_child;
452        if (param != NULL) {
453            // Note: that a pair of random strings is read as 0 0
454            int *ptr = (int *)fx_param->data;
455            int *ptr2 = (int *)((char *)param + sizeof(effect_param_t));
456            ALOGW("loadEffectParameter() ptr %p ptr2 %p", ptr, ptr2);
457            *ptr++ = atoi(param->name);
458            *ptr = atoi(param->value);
459            fx_param->psize = sizeof(int);
460            fx_param->vsize = sizeof(int);
461            return fx_param;
462        }
463    }
464    if (param == NULL || value == NULL) {
465        ALOGW("loadEffectParameter() invalid parameter description %s", root->name);
466        goto error;
467    }
468
469    fx_param->psize = 0;
470    param = param->first_child;
471    while (param) {
472        ALOGV("loadEffectParameter() reading param of type %s", param->name);
473        size_t size = readParamValue(param, (char *)fx_param, &curSize, &totSize);
474        if (size == 0) {
475            goto error;
476        }
477        fx_param->psize += size;
478        param = param->next;
479    }
480
481    // align start of value field on 32 bit boundary
482    curSize = ((curSize - 1 ) / sizeof(int) + 1) * sizeof(int);
483
484    fx_param->vsize = 0;
485    value = value->first_child;
486    while (value) {
487        ALOGV("loadEffectParameter() reading value of type %s", value->name);
488        size_t size = readParamValue(value, (char *)fx_param, &curSize, &totSize);
489        if (size == 0) {
490            goto error;
491        }
492        fx_param->vsize += size;
493        value = value->next;
494    }
495
496    return fx_param;
497
498error:
499    delete fx_param;
500    return NULL;
501}
502
503void AudioPolicyEffects::loadEffectParameters(cnode *root, Vector <effect_param_t *>& params)
504{
505    cnode *node = root->first_child;
506    while (node) {
507        ALOGV("loadEffectParameters() loading param %s", node->name);
508        effect_param_t *param = loadEffectParameter(node);
509        if (param == NULL) {
510            node = node->next;
511            continue;
512        }
513        params.add(param);
514        node = node->next;
515    }
516}
517
518
519AudioPolicyEffects::EffectDescVector *AudioPolicyEffects::loadEffectConfig(
520                                                            cnode *root,
521                                                            const Vector <EffectDesc *>& effects)
522{
523    cnode *node = root->first_child;
524    if (node == NULL) {
525        ALOGW("loadInputSource() empty element %s", root->name);
526        return NULL;
527    }
528    EffectDescVector *desc = new EffectDescVector();
529    while (node) {
530        size_t i;
531        for (i = 0; i < effects.size(); i++) {
532            if (strncmp(effects[i]->mName, node->name, EFFECT_STRING_LEN_MAX) == 0) {
533                ALOGV("loadEffectConfig() found effect %s in list", node->name);
534                break;
535            }
536        }
537        if (i == effects.size()) {
538            ALOGV("loadEffectConfig() effect %s not in list", node->name);
539            node = node->next;
540            continue;
541        }
542        EffectDesc *effect = new EffectDesc(*effects[i]);   // deep copy
543        loadEffectParameters(node, effect->mParams);
544        ALOGV("loadEffectConfig() adding effect %s uuid %08x",
545              effect->mName, effect->mUuid.timeLow);
546        desc->mEffects.add(effect);
547        node = node->next;
548    }
549    if (desc->mEffects.size() == 0) {
550        ALOGW("loadEffectConfig() no valid effects found in config %s", root->name);
551        delete desc;
552        return NULL;
553    }
554    return desc;
555}
556
557status_t AudioPolicyEffects::loadInputEffectConfigurations(cnode *root,
558                                                           const Vector <EffectDesc *>& effects)
559{
560    cnode *node = config_find(root, PREPROCESSING_TAG);
561    if (node == NULL) {
562        return -ENOENT;
563    }
564    node = node->first_child;
565    while (node) {
566        audio_source_t source = inputSourceNameToEnum(node->name);
567        if (source == AUDIO_SOURCE_CNT) {
568            ALOGW("loadInputSources() invalid input source %s", node->name);
569            node = node->next;
570            continue;
571        }
572        ALOGV("loadInputSources() loading input source %s", node->name);
573        EffectDescVector *desc = loadEffectConfig(node, effects);
574        if (desc == NULL) {
575            node = node->next;
576            continue;
577        }
578        mInputSources.add(source, desc);
579        node = node->next;
580    }
581    return NO_ERROR;
582}
583
584status_t AudioPolicyEffects::loadStreamEffectConfigurations(cnode *root,
585                                                            const Vector <EffectDesc *>& effects)
586{
587    cnode *node = config_find(root, OUTPUT_SESSION_PROCESSING_TAG);
588    if (node == NULL) {
589        return -ENOENT;
590    }
591    node = node->first_child;
592    while (node) {
593        audio_stream_type_t stream = streamNameToEnum(node->name);
594        if (stream == AUDIO_STREAM_PUBLIC_CNT) {
595            ALOGW("loadStreamEffectConfigurations() invalid output stream %s", node->name);
596            node = node->next;
597            continue;
598        }
599        ALOGV("loadStreamEffectConfigurations() loading output stream %s", node->name);
600        EffectDescVector *desc = loadEffectConfig(node, effects);
601        if (desc == NULL) {
602            node = node->next;
603            continue;
604        }
605        mOutputStreams.add(stream, desc);
606        node = node->next;
607    }
608    return NO_ERROR;
609}
610
611AudioPolicyEffects::EffectDesc *AudioPolicyEffects::loadEffect(cnode *root)
612{
613    cnode *node = config_find(root, UUID_TAG);
614    if (node == NULL) {
615        return NULL;
616    }
617    effect_uuid_t uuid;
618    if (AudioEffect::stringToGuid(node->value, &uuid) != NO_ERROR) {
619        ALOGW("loadEffect() invalid uuid %s", node->value);
620        return NULL;
621    }
622    return new EffectDesc(root->name, uuid);
623}
624
625status_t AudioPolicyEffects::loadEffects(cnode *root, Vector <EffectDesc *>& effects)
626{
627    cnode *node = config_find(root, EFFECTS_TAG);
628    if (node == NULL) {
629        return -ENOENT;
630    }
631    node = node->first_child;
632    while (node) {
633        ALOGV("loadEffects() loading effect %s", node->name);
634        EffectDesc *effect = loadEffect(node);
635        if (effect == NULL) {
636            node = node->next;
637            continue;
638        }
639        effects.add(effect);
640        node = node->next;
641    }
642    return NO_ERROR;
643}
644
645status_t AudioPolicyEffects::loadAudioEffectConfig(const char *path)
646{
647    cnode *root;
648    char *data;
649
650    data = (char *)load_file(path, NULL);
651    if (data == NULL) {
652        return -ENODEV;
653    }
654    root = config_node("", "");
655    config_load(root, data);
656
657    Vector <EffectDesc *> effects;
658    loadEffects(root, effects);
659    loadInputEffectConfigurations(root, effects);
660    loadStreamEffectConfigurations(root, effects);
661
662    for (size_t i = 0; i < effects.size(); i++) {
663        delete effects[i];
664    }
665
666    config_free(root);
667    free(root);
668    free(data);
669
670    return NO_ERROR;
671}
672
673
674}; // namespace android
675