1/*
2 * Copyright (C) 2013 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 "offload_visualizer"
18/*#define LOG_NDEBUG 0*/
19#include <assert.h>
20#include <math.h>
21#include <stdlib.h>
22#include <string.h>
23#include <time.h>
24#include <sys/prctl.h>
25#include <dlfcn.h>
26
27#include <cutils/list.h>
28#include <cutils/log.h>
29#include <system/thread_defs.h>
30#include <tinyalsa/asoundlib.h>
31#include <audio_effects/effect_visualizer.h>
32
33#define LIB_ACDB_LOADER "libacdbloader.so"
34#define ACDB_DEV_TYPE_OUT 1
35#define AFE_PROXY_ACDB_ID 45
36
37static void* acdb_handle;
38
39typedef void (*acdb_send_audio_cal_t)(int, int);
40
41acdb_send_audio_cal_t acdb_send_audio_cal;
42
43enum {
44    EFFECT_STATE_UNINITIALIZED,
45    EFFECT_STATE_INITIALIZED,
46    EFFECT_STATE_ACTIVE,
47};
48
49typedef struct effect_context_s effect_context_t;
50typedef struct output_context_s output_context_t;
51
52/* effect specific operations. Only the init() and process() operations must be defined.
53 * Others are optional.
54 */
55typedef struct effect_ops_s {
56    int (*init)(effect_context_t *context);
57    int (*release)(effect_context_t *context);
58    int (*reset)(effect_context_t *context);
59    int (*enable)(effect_context_t *context);
60    int (*disable)(effect_context_t *context);
61    int (*start)(effect_context_t *context, output_context_t *output);
62    int (*stop)(effect_context_t *context, output_context_t *output);
63    int (*process)(effect_context_t *context, audio_buffer_t *in, audio_buffer_t *out);
64    int (*set_parameter)(effect_context_t *context, effect_param_t *param, uint32_t size);
65    int (*get_parameter)(effect_context_t *context, effect_param_t *param, uint32_t *size);
66    int (*command)(effect_context_t *context, uint32_t cmdCode, uint32_t cmdSize,
67            void *pCmdData, uint32_t *replySize, void *pReplyData);
68} effect_ops_t;
69
70struct effect_context_s {
71    const struct effect_interface_s *itfe;
72    struct listnode effects_list_node;  /* node in created_effects_list */
73    struct listnode output_node;  /* node in output_context_t.effects_list */
74    effect_config_t config;
75    const effect_descriptor_t *desc;
76    audio_io_handle_t out_handle;  /* io handle of the output the effect is attached to */
77    uint32_t state;
78    bool offload_enabled;  /* when offload is enabled we process VISUALIZER_CMD_CAPTURE command.
79                              Otherwise non offloaded visualizer has already processed the command
80                              and we must not overwrite the reply. */
81    effect_ops_t ops;
82};
83
84typedef struct output_context_s {
85    struct listnode outputs_list_node;  /* node in active_outputs_list */
86    audio_io_handle_t handle; /* io handle */
87    struct listnode effects_list; /* list of effects attached to this output */
88} output_context_t;
89
90
91/* maximum time since last capture buffer update before resetting capture buffer. This means
92  that the framework has stopped playing audio and we must start returning silence */
93#define MAX_STALL_TIME_MS 1000
94
95#define CAPTURE_BUF_SIZE 65536 /* "64k should be enough for everyone" */
96
97#define DISCARD_MEASUREMENTS_TIME_MS 2000 /* discard measurements older than this number of ms */
98
99/* maximum number of buffers for which we keep track of the measurements */
100#define MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS 25 /* note: buffer index is stored in uint8_t */
101
102typedef struct buffer_stats_s {
103    bool is_valid;
104    uint16_t peak_u16; /* the positive peak of the absolute value of the samples in a buffer */
105    float rms_squared; /* the average square of the samples in a buffer */
106} buffer_stats_t;
107
108typedef struct visualizer_context_s {
109    effect_context_t common;
110
111    uint32_t capture_idx;
112    uint32_t capture_size;
113    uint32_t scaling_mode;
114    uint32_t last_capture_idx;
115    uint32_t latency;
116    struct timespec buffer_update_time;
117    uint8_t capture_buf[CAPTURE_BUF_SIZE];
118    /* for measurements */
119    uint8_t channel_count; /* to avoid recomputing it every time a buffer is processed */
120    uint32_t meas_mode;
121    uint8_t meas_wndw_size_in_buffers;
122    uint8_t meas_buffer_idx;
123    buffer_stats_t past_meas[MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS];
124} visualizer_context_t;
125
126
127extern const struct effect_interface_s effect_interface;
128
129/* Offload visualizer UUID: 7a8044a0-1a71-11e3-a184-0002a5d5c51b */
130const effect_descriptor_t visualizer_descriptor = {
131        {0xe46b26a0, 0xdddd, 0x11db, 0x8afd, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},
132        {0x7a8044a0, 0x1a71, 0x11e3, 0xa184, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},
133        EFFECT_CONTROL_API_VERSION,
134        (EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_HW_ACC_TUNNEL ),
135        0, /* TODO */
136        1,
137        "QCOM MSM offload visualizer",
138        "The Android Open Source Project",
139};
140
141const effect_descriptor_t *descriptors[] = {
142        &visualizer_descriptor,
143        NULL,
144};
145
146
147pthread_once_t once = PTHREAD_ONCE_INIT;
148int init_status;
149
150/* list of created effects. Updated by visualizer_hal_start_output()
151 * and visualizer_hal_stop_output() */
152struct listnode created_effects_list;
153/* list of active output streams. Updated by visualizer_hal_start_output()
154 * and visualizer_hal_stop_output() */
155struct listnode active_outputs_list;
156
157/* thread capturing PCM from Proxy port and calling the process function on each enabled effect
158 * attached to an active output stream */
159pthread_t capture_thread;
160/* lock must be held when modifying or accessing created_effects_list or active_outputs_list */
161pthread_mutex_t lock;
162/* thread_lock must be held when starting or stopping the capture thread.
163 * Locking order: thread_lock -> lock */
164pthread_mutex_t thread_lock;
165/* cond is signaled when an output is started or stopped or an effect is enabled or disable: the
166 * capture thread will reevaluate the capture and effect rocess conditions. */
167pthread_cond_t cond;
168/* true when requesting the capture thread to exit */
169bool exit_thread;
170/* 0 if the capture thread was created successfully */
171int thread_status;
172
173
174#define DSP_OUTPUT_LATENCY_MS 0 /* Fudge factor for latency after capture point in audio DSP */
175
176/* Retry for delay for mixer open */
177#define RETRY_NUMBER 10
178#define RETRY_US 500000
179
180#define MIXER_CARD 0
181#define SOUND_CARD 0
182#define CAPTURE_DEVICE 8
183
184/* Proxy port supports only MMAP read and those fixed parameters*/
185#define AUDIO_CAPTURE_CHANNEL_COUNT 2
186#define AUDIO_CAPTURE_SMP_RATE 48000
187#define AUDIO_CAPTURE_PERIOD_SIZE (768)
188#define AUDIO_CAPTURE_PERIOD_COUNT 32
189
190struct pcm_config pcm_config_capture = {
191    .channels = AUDIO_CAPTURE_CHANNEL_COUNT,
192    .rate = AUDIO_CAPTURE_SMP_RATE,
193    .period_size = AUDIO_CAPTURE_PERIOD_SIZE,
194    .period_count = AUDIO_CAPTURE_PERIOD_COUNT,
195    .format = PCM_FORMAT_S16_LE,
196    .start_threshold = AUDIO_CAPTURE_PERIOD_SIZE / 4,
197    .stop_threshold = INT_MAX,
198    .avail_min = AUDIO_CAPTURE_PERIOD_SIZE / 4,
199};
200
201
202/*
203 *  Local functions
204 */
205
206static void init_once() {
207    list_init(&created_effects_list);
208    list_init(&active_outputs_list);
209
210    pthread_mutex_init(&lock, NULL);
211    pthread_mutex_init(&thread_lock, NULL);
212    pthread_cond_init(&cond, NULL);
213    exit_thread = false;
214    thread_status = -1;
215
216    init_status = 0;
217}
218
219int lib_init() {
220    pthread_once(&once, init_once);
221    return init_status;
222}
223
224bool effect_exists(effect_context_t *context) {
225    struct listnode *node;
226
227    list_for_each(node, &created_effects_list) {
228        effect_context_t *fx_ctxt = node_to_item(node,
229                                                     effect_context_t,
230                                                     effects_list_node);
231        if (fx_ctxt == context) {
232            return true;
233        }
234    }
235    return false;
236}
237
238output_context_t *get_output(audio_io_handle_t output) {
239    struct listnode *node;
240
241    list_for_each(node, &active_outputs_list) {
242        output_context_t *out_ctxt = node_to_item(node,
243                                                  output_context_t,
244                                                  outputs_list_node);
245        if (out_ctxt->handle == output) {
246            return out_ctxt;
247        }
248    }
249    return NULL;
250}
251
252void add_effect_to_output(output_context_t * output, effect_context_t *context) {
253    struct listnode *fx_node;
254
255    list_for_each(fx_node, &output->effects_list) {
256        effect_context_t *fx_ctxt = node_to_item(fx_node,
257                                                     effect_context_t,
258                                                     output_node);
259        if (fx_ctxt == context)
260            return;
261    }
262    list_add_tail(&output->effects_list, &context->output_node);
263    if (context->ops.start)
264        context->ops.start(context, output);
265}
266
267void remove_effect_from_output(output_context_t * output, effect_context_t *context) {
268    struct listnode *fx_node;
269
270    list_for_each(fx_node, &output->effects_list) {
271        effect_context_t *fx_ctxt = node_to_item(fx_node,
272                                                     effect_context_t,
273                                                     output_node);
274        if (fx_ctxt == context) {
275            if (context->ops.stop)
276                context->ops.stop(context, output);
277            list_remove(&context->output_node);
278            return;
279        }
280    }
281}
282
283bool effects_enabled() {
284    struct listnode *out_node;
285
286    list_for_each(out_node, &active_outputs_list) {
287        struct listnode *fx_node;
288        output_context_t *out_ctxt = node_to_item(out_node,
289                                                  output_context_t,
290                                                  outputs_list_node);
291
292        list_for_each(fx_node, &out_ctxt->effects_list) {
293            effect_context_t *fx_ctxt = node_to_item(fx_node,
294                                                         effect_context_t,
295                                                         output_node);
296            if (fx_ctxt->state == EFFECT_STATE_ACTIVE && fx_ctxt->ops.process != NULL)
297                return true;
298        }
299    }
300    return false;
301}
302
303int configure_proxy_capture(struct mixer *mixer, int value) {
304    const char *proxy_ctl_name = "AFE_PCM_RX Audio Mixer MultiMedia4";
305    struct mixer_ctl *ctl;
306
307    if (value && acdb_send_audio_cal)
308        acdb_send_audio_cal(AFE_PROXY_ACDB_ID, ACDB_DEV_TYPE_OUT);
309
310    ctl = mixer_get_ctl_by_name(mixer, proxy_ctl_name);
311    if (ctl == NULL) {
312        ALOGW("%s: could not get %s ctl", __func__, proxy_ctl_name);
313        return -EINVAL;
314    }
315    if (mixer_ctl_set_value(ctl, 0, value) != 0)
316        ALOGW("%s: error setting value %d on %s ", __func__, value, proxy_ctl_name);
317
318    return 0;
319}
320
321
322void *capture_thread_loop(void *arg __unused)
323{
324    int16_t data[AUDIO_CAPTURE_PERIOD_SIZE * AUDIO_CAPTURE_CHANNEL_COUNT * sizeof(int16_t)];
325    audio_buffer_t buf;
326    buf.frameCount = AUDIO_CAPTURE_PERIOD_SIZE;
327    buf.s16 = data;
328    bool capture_enabled = false;
329    struct mixer *mixer;
330    struct pcm *pcm = NULL;
331    int ret;
332    int retry_num = 0;
333
334    ALOGD("thread enter");
335
336    prctl(PR_SET_NAME, (unsigned long)"visualizer capture", 0, 0, 0);
337
338    pthread_mutex_lock(&lock);
339
340    mixer = mixer_open(MIXER_CARD);
341    while (mixer == NULL && retry_num < RETRY_NUMBER) {
342        usleep(RETRY_US);
343        mixer = mixer_open(MIXER_CARD);
344        retry_num++;
345    }
346    if (mixer == NULL) {
347        pthread_mutex_unlock(&lock);
348        return NULL;
349    }
350
351    for (;;) {
352        if (exit_thread) {
353            break;
354        }
355        if (effects_enabled()) {
356            if (!capture_enabled) {
357                ret = configure_proxy_capture(mixer, 1);
358                if (ret == 0) {
359                    pcm = pcm_open(SOUND_CARD, CAPTURE_DEVICE,
360                                   PCM_IN|PCM_MMAP|PCM_NOIRQ, &pcm_config_capture);
361                    if (pcm && !pcm_is_ready(pcm)) {
362                        ALOGW("%s: %s", __func__, pcm_get_error(pcm));
363                        pcm_close(pcm);
364                        pcm = NULL;
365                        configure_proxy_capture(mixer, 0);
366                    } else {
367                        capture_enabled = true;
368                        ALOGD("%s: capture ENABLED", __func__);
369                    }
370                }
371            }
372        } else {
373            if (capture_enabled) {
374                if (pcm != NULL)
375                    pcm_close(pcm);
376                configure_proxy_capture(mixer, 0);
377                ALOGD("%s: capture DISABLED", __func__);
378                capture_enabled = false;
379            }
380            pthread_cond_wait(&cond, &lock);
381        }
382        if (!capture_enabled)
383            continue;
384
385        pthread_mutex_unlock(&lock);
386        ret = pcm_mmap_read(pcm, data, sizeof(data));
387        pthread_mutex_lock(&lock);
388
389        if (ret == 0) {
390            struct listnode *out_node;
391
392            list_for_each(out_node, &active_outputs_list) {
393                output_context_t *out_ctxt = node_to_item(out_node,
394                                                          output_context_t,
395                                                          outputs_list_node);
396                struct listnode *fx_node;
397
398                list_for_each(fx_node, &out_ctxt->effects_list) {
399                    effect_context_t *fx_ctxt = node_to_item(fx_node,
400                                                                effect_context_t,
401                                                                output_node);
402                    if (fx_ctxt->ops.process != NULL)
403                        fx_ctxt->ops.process(fx_ctxt, &buf, &buf);
404                }
405            }
406        } else {
407            ALOGW("%s: read status %d %s", __func__, ret, pcm_get_error(pcm));
408        }
409    }
410
411    if (capture_enabled) {
412        if (pcm != NULL)
413            pcm_close(pcm);
414        configure_proxy_capture(mixer, 0);
415    }
416    mixer_close(mixer);
417    pthread_mutex_unlock(&lock);
418
419    ALOGD("thread exit");
420
421    return NULL;
422}
423
424/*
425 * Interface from audio HAL
426 */
427
428__attribute__ ((visibility ("default")))
429int visualizer_hal_start_output(audio_io_handle_t output, int pcm_id) {
430    int ret;
431    struct listnode *node;
432
433    ALOGV("%s output %d pcm_id %d", __func__, output, pcm_id);
434
435    if (lib_init() != 0)
436        return init_status;
437
438    pthread_mutex_lock(&thread_lock);
439    pthread_mutex_lock(&lock);
440    if (get_output(output) != NULL) {
441        ALOGW("%s output already started", __func__);
442        ret = -ENOSYS;
443        goto exit;
444    }
445
446    output_context_t *out_ctxt = (output_context_t *)malloc(sizeof(output_context_t));
447    out_ctxt->handle = output;
448    list_init(&out_ctxt->effects_list);
449
450    list_for_each(node, &created_effects_list) {
451        effect_context_t *fx_ctxt = node_to_item(node,
452                                                     effect_context_t,
453                                                     effects_list_node);
454        if (fx_ctxt->out_handle == output) {
455            if (fx_ctxt->ops.start)
456                fx_ctxt->ops.start(fx_ctxt, out_ctxt);
457            list_add_tail(&out_ctxt->effects_list, &fx_ctxt->output_node);
458        }
459    }
460    if (list_empty(&active_outputs_list)) {
461        exit_thread = false;
462        thread_status = pthread_create(&capture_thread, (const pthread_attr_t *) NULL,
463                        capture_thread_loop, NULL);
464    }
465    list_add_tail(&active_outputs_list, &out_ctxt->outputs_list_node);
466    pthread_cond_signal(&cond);
467
468exit:
469    pthread_mutex_unlock(&lock);
470    pthread_mutex_unlock(&thread_lock);
471    return ret;
472}
473
474__attribute__ ((visibility ("default")))
475int visualizer_hal_stop_output(audio_io_handle_t output, int pcm_id) {
476    int ret;
477    struct listnode *node;
478    struct listnode *fx_node;
479    output_context_t *out_ctxt;
480
481    ALOGV("%s output %d pcm_id %d", __func__, output, pcm_id);
482
483    if (lib_init() != 0)
484        return init_status;
485
486    pthread_mutex_lock(&thread_lock);
487    pthread_mutex_lock(&lock);
488
489    out_ctxt = get_output(output);
490    if (out_ctxt == NULL) {
491        ALOGW("%s output not started", __func__);
492        ret = -ENOSYS;
493        goto exit;
494    }
495    list_for_each(fx_node, &out_ctxt->effects_list) {
496        effect_context_t *fx_ctxt = node_to_item(fx_node,
497                                                 effect_context_t,
498                                                 output_node);
499        if (fx_ctxt->ops.stop)
500            fx_ctxt->ops.stop(fx_ctxt, out_ctxt);
501    }
502    list_remove(&out_ctxt->outputs_list_node);
503    pthread_cond_signal(&cond);
504
505    if (list_empty(&active_outputs_list)) {
506        if (thread_status == 0) {
507            exit_thread = true;
508            pthread_cond_signal(&cond);
509            pthread_mutex_unlock(&lock);
510            pthread_join(capture_thread, (void **) NULL);
511            pthread_mutex_lock(&lock);
512            thread_status = -1;
513        }
514    }
515
516    free(out_ctxt);
517
518exit:
519    pthread_mutex_unlock(&lock);
520    pthread_mutex_unlock(&thread_lock);
521    return ret;
522}
523
524
525/*
526 * Effect operations
527 */
528
529int set_config(effect_context_t *context, effect_config_t *config)
530{
531    if (config->inputCfg.samplingRate != config->outputCfg.samplingRate) return -EINVAL;
532    if (config->inputCfg.channels != config->outputCfg.channels) return -EINVAL;
533    if (config->inputCfg.format != config->outputCfg.format) return -EINVAL;
534    if (config->inputCfg.channels != AUDIO_CHANNEL_OUT_STEREO) return -EINVAL;
535    if (config->outputCfg.accessMode != EFFECT_BUFFER_ACCESS_WRITE &&
536            config->outputCfg.accessMode != EFFECT_BUFFER_ACCESS_ACCUMULATE) return -EINVAL;
537    if (config->inputCfg.format != AUDIO_FORMAT_PCM_16_BIT) return -EINVAL;
538
539    context->config = *config;
540
541    if (context->ops.reset)
542        context->ops.reset(context);
543
544    return 0;
545}
546
547void get_config(effect_context_t *context, effect_config_t *config)
548{
549    *config = context->config;
550}
551
552
553/*
554 * Visualizer operations
555 */
556
557uint32_t visualizer_get_delta_time_ms_from_updated_time(visualizer_context_t* visu_ctxt) {
558    uint32_t delta_ms = 0;
559    if (visu_ctxt->buffer_update_time.tv_sec != 0) {
560        struct timespec ts;
561        if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
562            time_t secs = ts.tv_sec - visu_ctxt->buffer_update_time.tv_sec;
563            long nsec = ts.tv_nsec - visu_ctxt->buffer_update_time.tv_nsec;
564            if (nsec < 0) {
565                --secs;
566                nsec += 1000000000;
567            }
568            delta_ms = secs * 1000 + nsec / 1000000;
569        }
570    }
571    return delta_ms;
572}
573
574int visualizer_reset(effect_context_t *context)
575{
576    visualizer_context_t * visu_ctxt = (visualizer_context_t *)context;
577
578    visu_ctxt->capture_idx = 0;
579    visu_ctxt->last_capture_idx = 0;
580    visu_ctxt->buffer_update_time.tv_sec = 0;
581    visu_ctxt->latency = DSP_OUTPUT_LATENCY_MS;
582    memset(visu_ctxt->capture_buf, 0x80, CAPTURE_BUF_SIZE);
583    return 0;
584}
585
586int visualizer_init(effect_context_t *context)
587{
588    int32_t i;
589
590    visualizer_context_t * visu_ctxt = (visualizer_context_t *)context;
591
592    context->config.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ;
593    context->config.inputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
594    context->config.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
595    context->config.inputCfg.samplingRate = 44100;
596    context->config.inputCfg.bufferProvider.getBuffer = NULL;
597    context->config.inputCfg.bufferProvider.releaseBuffer = NULL;
598    context->config.inputCfg.bufferProvider.cookie = NULL;
599    context->config.inputCfg.mask = EFFECT_CONFIG_ALL;
600    context->config.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE;
601    context->config.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
602    context->config.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
603    context->config.outputCfg.samplingRate = 44100;
604    context->config.outputCfg.bufferProvider.getBuffer = NULL;
605    context->config.outputCfg.bufferProvider.releaseBuffer = NULL;
606    context->config.outputCfg.bufferProvider.cookie = NULL;
607    context->config.outputCfg.mask = EFFECT_CONFIG_ALL;
608
609    visu_ctxt->capture_size = VISUALIZER_CAPTURE_SIZE_MAX;
610    visu_ctxt->scaling_mode = VISUALIZER_SCALING_MODE_NORMALIZED;
611
612    // measurement initialization
613    visu_ctxt->channel_count = audio_channel_count_from_out_mask(context->config.inputCfg.channels);
614    visu_ctxt->meas_mode = MEASUREMENT_MODE_NONE;
615    visu_ctxt->meas_wndw_size_in_buffers = MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS;
616    visu_ctxt->meas_buffer_idx = 0;
617    for (i=0 ; i<visu_ctxt->meas_wndw_size_in_buffers ; i++) {
618        visu_ctxt->past_meas[i].is_valid = false;
619        visu_ctxt->past_meas[i].peak_u16 = 0;
620        visu_ctxt->past_meas[i].rms_squared = 0;
621    }
622
623    set_config(context, &context->config);
624
625    if (acdb_handle == NULL) {
626        acdb_handle = dlopen(LIB_ACDB_LOADER, RTLD_NOW);
627        if (acdb_handle == NULL) {
628            ALOGE("%s: DLOPEN failed for %s", __func__, LIB_ACDB_LOADER);
629        } else {
630            acdb_send_audio_cal = (acdb_send_audio_cal_t)dlsym(acdb_handle,
631                                                    "acdb_loader_send_audio_cal");
632            if (!acdb_send_audio_cal)
633                ALOGE("%s: Could not find the symbol acdb_send_audio_cal from %s",
634                      __func__, LIB_ACDB_LOADER);
635            }
636    }
637
638    return 0;
639}
640
641int visualizer_get_parameter(effect_context_t *context, effect_param_t *p, uint32_t *size)
642{
643    visualizer_context_t *visu_ctxt = (visualizer_context_t *)context;
644
645    p->status = 0;
646    *size = sizeof(effect_param_t) + sizeof(uint32_t);
647    if (p->psize != sizeof(uint32_t)) {
648        p->status = -EINVAL;
649        return 0;
650    }
651    switch (*(uint32_t *)p->data) {
652    case VISUALIZER_PARAM_CAPTURE_SIZE:
653        ALOGV("%s get capture_size = %d", __func__, visu_ctxt->capture_size);
654        *((uint32_t *)p->data + 1) = visu_ctxt->capture_size;
655        p->vsize = sizeof(uint32_t);
656        *size += sizeof(uint32_t);
657        break;
658    case VISUALIZER_PARAM_SCALING_MODE:
659        ALOGV("%s get scaling_mode = %d", __func__, visu_ctxt->scaling_mode);
660        *((uint32_t *)p->data + 1) = visu_ctxt->scaling_mode;
661        p->vsize = sizeof(uint32_t);
662        *size += sizeof(uint32_t);
663        break;
664    case VISUALIZER_PARAM_MEASUREMENT_MODE:
665        ALOGV("%s get meas_mode = %d", __func__, visu_ctxt->meas_mode);
666        *((uint32_t *)p->data + 1) = visu_ctxt->meas_mode;
667        p->vsize = sizeof(uint32_t);
668        *size += sizeof(uint32_t);
669        break;
670    default:
671        p->status = -EINVAL;
672    }
673    return 0;
674}
675
676int visualizer_set_parameter(effect_context_t *context, effect_param_t *p, uint32_t size __unused)
677{
678    visualizer_context_t *visu_ctxt = (visualizer_context_t *)context;
679
680    if (p->psize != sizeof(uint32_t) || p->vsize != sizeof(uint32_t))
681        return -EINVAL;
682
683    switch (*(uint32_t *)p->data) {
684    case VISUALIZER_PARAM_CAPTURE_SIZE:
685        visu_ctxt->capture_size = *((uint32_t *)p->data + 1);
686        ALOGV("%s set capture_size = %d", __func__, visu_ctxt->capture_size);
687        break;
688    case VISUALIZER_PARAM_SCALING_MODE:
689        visu_ctxt->scaling_mode = *((uint32_t *)p->data + 1);
690        ALOGV("%s set scaling_mode = %d", __func__, visu_ctxt->scaling_mode);
691        break;
692    case VISUALIZER_PARAM_LATENCY:
693        /* Ignore latency as we capture at DSP output
694         * visu_ctxt->latency = *((uint32_t *)p->data + 1); */
695        ALOGV("%s set latency = %d", __func__, visu_ctxt->latency);
696        break;
697    case VISUALIZER_PARAM_MEASUREMENT_MODE:
698        visu_ctxt->meas_mode = *((uint32_t *)p->data + 1);
699        ALOGV("%s set meas_mode = %d", __func__, visu_ctxt->meas_mode);
700        break;
701    default:
702        return -EINVAL;
703    }
704    return 0;
705}
706
707/* Real process function called from capture thread. Called with lock held */
708int visualizer_process(effect_context_t *context,
709                       audio_buffer_t *inBuffer,
710                       audio_buffer_t *outBuffer)
711{
712    visualizer_context_t *visu_ctxt = (visualizer_context_t *)context;
713
714    if (!effect_exists(context))
715        return -EINVAL;
716
717    if (inBuffer == NULL || inBuffer->raw == NULL ||
718        outBuffer == NULL || outBuffer->raw == NULL ||
719        inBuffer->frameCount != outBuffer->frameCount ||
720        inBuffer->frameCount == 0) {
721        return -EINVAL;
722    }
723
724    // perform measurements if needed
725    if (visu_ctxt->meas_mode & MEASUREMENT_MODE_PEAK_RMS) {
726        // find the peak and RMS squared for the new buffer
727        uint32_t inIdx;
728        int16_t max_sample = 0;
729        float rms_squared_acc = 0;
730        for (inIdx = 0 ; inIdx < inBuffer->frameCount * visu_ctxt->channel_count ; inIdx++) {
731            if (inBuffer->s16[inIdx] > max_sample) {
732                max_sample = inBuffer->s16[inIdx];
733            } else if (-inBuffer->s16[inIdx] > max_sample) {
734                max_sample = -inBuffer->s16[inIdx];
735            }
736            rms_squared_acc += (inBuffer->s16[inIdx] * inBuffer->s16[inIdx]);
737        }
738        // store the measurement
739        visu_ctxt->past_meas[visu_ctxt->meas_buffer_idx].peak_u16 = (uint16_t)max_sample;
740        visu_ctxt->past_meas[visu_ctxt->meas_buffer_idx].rms_squared =
741                rms_squared_acc / (inBuffer->frameCount * visu_ctxt->channel_count);
742        visu_ctxt->past_meas[visu_ctxt->meas_buffer_idx].is_valid = true;
743        if (++visu_ctxt->meas_buffer_idx >= visu_ctxt->meas_wndw_size_in_buffers) {
744            visu_ctxt->meas_buffer_idx = 0;
745        }
746    }
747
748    /* all code below assumes stereo 16 bit PCM output and input */
749    int32_t shift;
750
751    if (visu_ctxt->scaling_mode == VISUALIZER_SCALING_MODE_NORMALIZED) {
752        /* derive capture scaling factor from peak value in current buffer
753         * this gives more interesting captures for display. */
754        shift = 32;
755        int len = inBuffer->frameCount * 2;
756        int i;
757        for (i = 0; i < len; i++) {
758            int32_t smp = inBuffer->s16[i];
759            if (smp < 0) smp = -smp - 1; /* take care to keep the max negative in range */
760            int32_t clz = __builtin_clz(smp);
761            if (shift > clz) shift = clz;
762        }
763        /* A maximum amplitude signal will have 17 leading zeros, which we want to
764         * translate to a shift of 8 (for converting 16 bit to 8 bit) */
765        shift = 25 - shift;
766        /* Never scale by less than 8 to avoid returning unaltered PCM signal. */
767        if (shift < 3) {
768            shift = 3;
769        }
770        /* add one to combine the division by 2 needed after summing
771         * left and right channels below */
772        shift++;
773    } else {
774        assert(visu_ctxt->scaling_mode == VISUALIZER_SCALING_MODE_AS_PLAYED);
775        shift = 9;
776    }
777
778    uint32_t capt_idx;
779    uint32_t in_idx;
780    uint8_t *buf = visu_ctxt->capture_buf;
781    for (in_idx = 0, capt_idx = visu_ctxt->capture_idx;
782         in_idx < inBuffer->frameCount;
783         in_idx++, capt_idx++) {
784        if (capt_idx >= CAPTURE_BUF_SIZE) {
785            /* wrap around */
786            capt_idx = 0;
787        }
788        int32_t smp = inBuffer->s16[2 * in_idx] + inBuffer->s16[2 * in_idx + 1];
789        smp = smp >> shift;
790        buf[capt_idx] = ((uint8_t)smp)^0x80;
791    }
792
793    /* XXX the following two should really be atomic, though it probably doesn't
794     * matter much for visualization purposes */
795    visu_ctxt->capture_idx = capt_idx;
796    /* update last buffer update time stamp */
797    if (clock_gettime(CLOCK_MONOTONIC, &visu_ctxt->buffer_update_time) < 0) {
798        visu_ctxt->buffer_update_time.tv_sec = 0;
799    }
800
801    if (context->state != EFFECT_STATE_ACTIVE) {
802        ALOGV("%s DONE inactive", __func__);
803        return -ENODATA;
804    }
805
806    return 0;
807}
808
809int visualizer_command(effect_context_t * context, uint32_t cmdCode, uint32_t cmdSize __unused,
810        void *pCmdData __unused, uint32_t *replySize, void *pReplyData)
811{
812    visualizer_context_t * visu_ctxt = (visualizer_context_t *)context;
813
814    switch (cmdCode) {
815    case VISUALIZER_CMD_CAPTURE:
816        if (pReplyData == NULL || *replySize != visu_ctxt->capture_size) {
817            ALOGV("%s VISUALIZER_CMD_CAPTURE error *replySize %d context->capture_size %d",
818                  __func__, *replySize, visu_ctxt->capture_size);
819            return -EINVAL;
820        }
821
822        if (!context->offload_enabled)
823            break;
824
825        if (context->state == EFFECT_STATE_ACTIVE) {
826            int32_t latency_ms = visu_ctxt->latency;
827            const uint32_t delta_ms = visualizer_get_delta_time_ms_from_updated_time(visu_ctxt);
828            latency_ms -= delta_ms;
829            if (latency_ms < 0) {
830                latency_ms = 0;
831            }
832            const uint32_t delta_smp = context->config.inputCfg.samplingRate * latency_ms / 1000;
833
834            int32_t capture_point = visu_ctxt->capture_idx - visu_ctxt->capture_size - delta_smp;
835            int32_t capture_size = visu_ctxt->capture_size;
836            if (capture_point < 0) {
837                int32_t size = -capture_point;
838                if (size > capture_size)
839                    size = capture_size;
840
841                memcpy(pReplyData,
842                       visu_ctxt->capture_buf + CAPTURE_BUF_SIZE + capture_point,
843                       size);
844                pReplyData = (void *)((size_t)pReplyData + size);
845                capture_size -= size;
846                capture_point = 0;
847            }
848            memcpy(pReplyData,
849                   visu_ctxt->capture_buf + capture_point,
850                   capture_size);
851
852
853            /* if audio framework has stopped playing audio although the effect is still
854             * active we must clear the capture buffer to return silence */
855            if ((visu_ctxt->last_capture_idx == visu_ctxt->capture_idx) &&
856                    (visu_ctxt->buffer_update_time.tv_sec != 0)) {
857                if (delta_ms > MAX_STALL_TIME_MS) {
858                    ALOGV("%s capture going to idle", __func__);
859                    visu_ctxt->buffer_update_time.tv_sec = 0;
860                    memset(pReplyData, 0x80, visu_ctxt->capture_size);
861                }
862            }
863            visu_ctxt->last_capture_idx = visu_ctxt->capture_idx;
864        } else {
865            memset(pReplyData, 0x80, visu_ctxt->capture_size);
866        }
867        break;
868
869    case VISUALIZER_CMD_MEASURE: {
870        uint16_t peak_u16 = 0;
871        float sum_rms_squared = 0.0f;
872        uint8_t nb_valid_meas = 0;
873        /* reset measurements if last measurement was too long ago (which implies stored
874         * measurements aren't relevant anymore and shouldn't bias the new one) */
875        const int32_t delay_ms = visualizer_get_delta_time_ms_from_updated_time(visu_ctxt);
876        if (delay_ms > DISCARD_MEASUREMENTS_TIME_MS) {
877            uint32_t i;
878            ALOGV("Discarding measurements, last measurement is %dms old", delay_ms);
879            for (i=0 ; i<visu_ctxt->meas_wndw_size_in_buffers ; i++) {
880                visu_ctxt->past_meas[i].is_valid = false;
881                visu_ctxt->past_meas[i].peak_u16 = 0;
882                visu_ctxt->past_meas[i].rms_squared = 0;
883            }
884            visu_ctxt->meas_buffer_idx = 0;
885        } else {
886            /* only use actual measurements, otherwise the first RMS measure happening before
887             * MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS have been played will always be artificially
888             * low */
889            uint32_t i;
890            for (i=0 ; i < visu_ctxt->meas_wndw_size_in_buffers ; i++) {
891                if (visu_ctxt->past_meas[i].is_valid) {
892                    if (visu_ctxt->past_meas[i].peak_u16 > peak_u16) {
893                        peak_u16 = visu_ctxt->past_meas[i].peak_u16;
894                    }
895                    sum_rms_squared += visu_ctxt->past_meas[i].rms_squared;
896                    nb_valid_meas++;
897                }
898            }
899        }
900        float rms = nb_valid_meas == 0 ? 0.0f : sqrtf(sum_rms_squared / nb_valid_meas);
901        int32_t* p_int_reply_data = (int32_t*)pReplyData;
902        /* convert from I16 sample values to mB and write results */
903        if (rms < 0.000016f) {
904            p_int_reply_data[MEASUREMENT_IDX_RMS] = -9600; //-96dB
905        } else {
906            p_int_reply_data[MEASUREMENT_IDX_RMS] = (int32_t) (2000 * log10(rms / 32767.0f));
907        }
908        if (peak_u16 == 0) {
909            p_int_reply_data[MEASUREMENT_IDX_PEAK] = -9600; //-96dB
910        } else {
911            p_int_reply_data[MEASUREMENT_IDX_PEAK] = (int32_t) (2000 * log10(peak_u16 / 32767.0f));
912        }
913        ALOGV("VISUALIZER_CMD_MEASURE peak=%d (%dmB), rms=%.1f (%dmB)",
914                peak_u16, p_int_reply_data[MEASUREMENT_IDX_PEAK],
915                rms, p_int_reply_data[MEASUREMENT_IDX_RMS]);
916        }
917        break;
918
919    default:
920        ALOGW("%s invalid command %d", __func__, cmdCode);
921        return -EINVAL;
922    }
923    return 0;
924}
925
926
927/*
928 * Effect Library Interface Implementation
929 */
930
931int effect_lib_create(const effect_uuid_t *uuid,
932                         int32_t sessionId __unused,
933                         int32_t ioId,
934                         effect_handle_t *pHandle) {
935    int ret;
936    int i;
937
938    if (lib_init() != 0)
939        return init_status;
940
941    if (pHandle == NULL || uuid == NULL)
942        return -EINVAL;
943
944    for (i = 0; descriptors[i] != NULL; i++) {
945        if (memcmp(uuid, &descriptors[i]->uuid, sizeof(effect_uuid_t)) == 0)
946            break;
947    }
948
949    if (descriptors[i] == NULL)
950        return -EINVAL;
951
952    effect_context_t *context;
953    if (memcmp(uuid, &visualizer_descriptor.uuid, sizeof(effect_uuid_t)) == 0) {
954        visualizer_context_t *visu_ctxt = (visualizer_context_t *)calloc(1,
955                                                                     sizeof(visualizer_context_t));
956        context = (effect_context_t *)visu_ctxt;
957        context->ops.init = visualizer_init;
958        context->ops.reset = visualizer_reset;
959        context->ops.process = visualizer_process;
960        context->ops.set_parameter = visualizer_set_parameter;
961        context->ops.get_parameter = visualizer_get_parameter;
962        context->ops.command = visualizer_command;
963        context->desc = &visualizer_descriptor;
964    } else {
965        return -EINVAL;
966    }
967
968    context->itfe = &effect_interface;
969    context->state = EFFECT_STATE_UNINITIALIZED;
970    context->out_handle = (audio_io_handle_t)ioId;
971
972    ret = context->ops.init(context);
973    if (ret < 0) {
974        ALOGW("%s init failed", __func__);
975        free(context);
976        return ret;
977    }
978
979    context->state = EFFECT_STATE_INITIALIZED;
980
981    pthread_mutex_lock(&lock);
982    list_add_tail(&created_effects_list, &context->effects_list_node);
983    output_context_t *out_ctxt = get_output(ioId);
984    if (out_ctxt != NULL)
985        add_effect_to_output(out_ctxt, context);
986    pthread_mutex_unlock(&lock);
987
988    *pHandle = (effect_handle_t)context;
989
990    ALOGV("%s created context %p", __func__, context);
991
992    return 0;
993
994}
995
996int effect_lib_release(effect_handle_t handle) {
997    effect_context_t *context = (effect_context_t *)handle;
998    int status;
999
1000    if (lib_init() != 0)
1001        return init_status;
1002
1003    ALOGV("%s context %p", __func__, handle);
1004    pthread_mutex_lock(&lock);
1005    status = -EINVAL;
1006    if (effect_exists(context)) {
1007        output_context_t *out_ctxt = get_output(context->out_handle);
1008        if (out_ctxt != NULL)
1009            remove_effect_from_output(out_ctxt, context);
1010        list_remove(&context->effects_list_node);
1011        if (context->ops.release)
1012            context->ops.release(context);
1013        free(context);
1014        status = 0;
1015    }
1016    pthread_mutex_unlock(&lock);
1017
1018    return status;
1019}
1020
1021int effect_lib_get_descriptor(const effect_uuid_t *uuid,
1022                                effect_descriptor_t *descriptor) {
1023    int i;
1024
1025    if (lib_init() != 0)
1026        return init_status;
1027
1028    if (descriptor == NULL || uuid == NULL) {
1029        ALOGV("%s called with NULL pointer", __func__);
1030        return -EINVAL;
1031    }
1032
1033    for (i = 0; descriptors[i] != NULL; i++) {
1034        if (memcmp(uuid, &descriptors[i]->uuid, sizeof(effect_uuid_t)) == 0) {
1035            *descriptor = *descriptors[i];
1036            return 0;
1037        }
1038    }
1039
1040    return  -EINVAL;
1041}
1042
1043/*
1044 * Effect Control Interface Implementation
1045 */
1046
1047 /* Stub function for effect interface: never called for offloaded effects */
1048int effect_process(effect_handle_t self,
1049                       audio_buffer_t *inBuffer __unused,
1050                       audio_buffer_t *outBuffer __unused)
1051{
1052    effect_context_t * context = (effect_context_t *)self;
1053    int status = 0;
1054
1055    ALOGW("%s Called ?????", __func__);
1056
1057    pthread_mutex_lock(&lock);
1058    if (!effect_exists(context)) {
1059        status = -EINVAL;
1060        goto exit;
1061    }
1062
1063    if (context->state != EFFECT_STATE_ACTIVE) {
1064        status = -EINVAL;
1065        goto exit;
1066    }
1067
1068exit:
1069    pthread_mutex_unlock(&lock);
1070    return status;
1071}
1072
1073int effect_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
1074        void *pCmdData, uint32_t *replySize, void *pReplyData)
1075{
1076
1077    effect_context_t * context = (effect_context_t *)self;
1078    int retsize;
1079    int status = 0;
1080
1081    pthread_mutex_lock(&lock);
1082
1083    if (!effect_exists(context)) {
1084        status = -EINVAL;
1085        goto exit;
1086    }
1087
1088    if (context == NULL || context->state == EFFECT_STATE_UNINITIALIZED) {
1089        status = -EINVAL;
1090        goto exit;
1091    }
1092
1093//    ALOGV_IF(cmdCode != VISUALIZER_CMD_CAPTURE,
1094//             "%s command %d cmdSize %d", __func__, cmdCode, cmdSize);
1095
1096    switch (cmdCode) {
1097    case EFFECT_CMD_INIT:
1098        if (pReplyData == NULL || *replySize != sizeof(int)) {
1099            status = -EINVAL;
1100            goto exit;
1101        }
1102        if (context->ops.init)
1103            *(int *) pReplyData = context->ops.init(context);
1104        else
1105            *(int *) pReplyData = 0;
1106        break;
1107    case EFFECT_CMD_SET_CONFIG:
1108        if (pCmdData == NULL || cmdSize != sizeof(effect_config_t)
1109                || pReplyData == NULL || *replySize != sizeof(int)) {
1110            status = -EINVAL;
1111            goto exit;
1112        }
1113        *(int *) pReplyData = set_config(context, (effect_config_t *) pCmdData);
1114        break;
1115    case EFFECT_CMD_GET_CONFIG:
1116        if (pReplyData == NULL ||
1117            *replySize != sizeof(effect_config_t)) {
1118            status = -EINVAL;
1119            goto exit;
1120        }
1121        if (!context->offload_enabled) {
1122            status = -EINVAL;
1123            goto exit;
1124        }
1125
1126        get_config(context, (effect_config_t *)pReplyData);
1127        break;
1128    case EFFECT_CMD_RESET:
1129        if (context->ops.reset)
1130            context->ops.reset(context);
1131        break;
1132    case EFFECT_CMD_ENABLE:
1133        if (pReplyData == NULL || *replySize != sizeof(int)) {
1134            status = -EINVAL;
1135            goto exit;
1136        }
1137        if (context->state != EFFECT_STATE_INITIALIZED) {
1138            status = -ENOSYS;
1139            goto exit;
1140        }
1141        context->state = EFFECT_STATE_ACTIVE;
1142        if (context->ops.enable)
1143            context->ops.enable(context);
1144        pthread_cond_signal(&cond);
1145        ALOGV("%s EFFECT_CMD_ENABLE", __func__);
1146        *(int *)pReplyData = 0;
1147        break;
1148    case EFFECT_CMD_DISABLE:
1149        if (pReplyData == NULL || *replySize != sizeof(int)) {
1150            status = -EINVAL;
1151            goto exit;
1152        }
1153        if (context->state != EFFECT_STATE_ACTIVE) {
1154            status = -ENOSYS;
1155            goto exit;
1156        }
1157        context->state = EFFECT_STATE_INITIALIZED;
1158        if (context->ops.disable)
1159            context->ops.disable(context);
1160        pthread_cond_signal(&cond);
1161        ALOGV("%s EFFECT_CMD_DISABLE", __func__);
1162        *(int *)pReplyData = 0;
1163        break;
1164    case EFFECT_CMD_GET_PARAM: {
1165        if (pCmdData == NULL ||
1166            cmdSize != (int)(sizeof(effect_param_t) + sizeof(uint32_t)) ||
1167            pReplyData == NULL ||
1168            *replySize < (int)(sizeof(effect_param_t) + sizeof(uint32_t) + sizeof(uint32_t))) {
1169            status = -EINVAL;
1170            goto exit;
1171        }
1172        if (!context->offload_enabled) {
1173            status = -EINVAL;
1174            goto exit;
1175        }
1176        memcpy(pReplyData, pCmdData, sizeof(effect_param_t) + sizeof(uint32_t));
1177        effect_param_t *p = (effect_param_t *)pReplyData;
1178        if (context->ops.get_parameter)
1179            context->ops.get_parameter(context, p, replySize);
1180        } break;
1181    case EFFECT_CMD_SET_PARAM: {
1182        if (pCmdData == NULL ||
1183            cmdSize != (int)(sizeof(effect_param_t) + sizeof(uint32_t) + sizeof(uint32_t)) ||
1184            pReplyData == NULL || *replySize != sizeof(int32_t)) {
1185            status = -EINVAL;
1186            goto exit;
1187        }
1188        *(int32_t *)pReplyData = 0;
1189        effect_param_t *p = (effect_param_t *)pCmdData;
1190        if (context->ops.set_parameter)
1191            *(int32_t *)pReplyData = context->ops.set_parameter(context, p, *replySize);
1192
1193        } break;
1194    case EFFECT_CMD_SET_DEVICE:
1195    case EFFECT_CMD_SET_VOLUME:
1196    case EFFECT_CMD_SET_AUDIO_MODE:
1197        break;
1198
1199    case EFFECT_CMD_OFFLOAD: {
1200        output_context_t *out_ctxt;
1201
1202        if (cmdSize != sizeof(effect_offload_param_t) || pCmdData == NULL
1203                || pReplyData == NULL || *replySize != sizeof(int)) {
1204            ALOGV("%s EFFECT_CMD_OFFLOAD bad format", __func__);
1205            status = -EINVAL;
1206            break;
1207        }
1208
1209        effect_offload_param_t* offload_param = (effect_offload_param_t*)pCmdData;
1210
1211        ALOGV("%s EFFECT_CMD_OFFLOAD offload %d output %d",
1212              __func__, offload_param->isOffload, offload_param->ioHandle);
1213
1214        *(int *)pReplyData = 0;
1215
1216        context->offload_enabled = offload_param->isOffload;
1217        if (context->out_handle == offload_param->ioHandle)
1218            break;
1219
1220        out_ctxt = get_output(context->out_handle);
1221        if (out_ctxt != NULL)
1222            remove_effect_from_output(out_ctxt, context);
1223
1224        context->out_handle = offload_param->ioHandle;
1225        out_ctxt = get_output(offload_param->ioHandle);
1226        if (out_ctxt != NULL)
1227            add_effect_to_output(out_ctxt, context);
1228
1229        } break;
1230
1231
1232    default:
1233        if (cmdCode >= EFFECT_CMD_FIRST_PROPRIETARY && context->ops.command)
1234            status = context->ops.command(context, cmdCode, cmdSize,
1235                                          pCmdData, replySize, pReplyData);
1236        else {
1237            ALOGW("%s invalid command %d", __func__, cmdCode);
1238            status = -EINVAL;
1239        }
1240        break;
1241    }
1242
1243exit:
1244    pthread_mutex_unlock(&lock);
1245
1246//    ALOGV_IF(cmdCode != VISUALIZER_CMD_CAPTURE,"%s DONE", __func__);
1247    return status;
1248}
1249
1250/* Effect Control Interface Implementation: get_descriptor */
1251int effect_get_descriptor(effect_handle_t   self,
1252                                    effect_descriptor_t *descriptor)
1253{
1254    effect_context_t *context = (effect_context_t *)self;
1255
1256    if (!effect_exists(context))
1257        return -EINVAL;
1258
1259    if (descriptor == NULL)
1260        return -EINVAL;
1261
1262    *descriptor = *context->desc;
1263
1264    return 0;
1265}
1266
1267/* effect_handle_t interface implementation for visualizer effect */
1268const struct effect_interface_s effect_interface = {
1269        effect_process,
1270        effect_command,
1271        effect_get_descriptor,
1272        NULL,
1273};
1274
1275__attribute__ ((visibility ("default")))
1276audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = {
1277    .tag = AUDIO_EFFECT_LIBRARY_TAG,
1278    .version = EFFECT_LIBRARY_API_VERSION,
1279    .name = "Visualizer Library",
1280    .implementor = "The Android Open Source Project",
1281    .create_effect = effect_lib_create,
1282    .release_effect = effect_lib_release,
1283    .get_descriptor = effect_lib_get_descriptor,
1284};
1285