/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <string.h>
#define LOG_TAG "PreProcessing"
//#define LOG_NDEBUG 0
#include <audio_effects/effect_aec.h>
#include <audio_effects/effect_agc.h>
#include <hardware/audio_effect.h>
#include <utils/Log.h>
#include <utils/Timers.h>
#include <audio_effects/effect_agc2.h>
#include <audio_effects/effect_ns.h>
#include <audio_processing.h>
#include <module_common_types.h>

// undefine to perform multi channels API functional tests
//#define DUAL_MIC_TEST

//------------------------------------------------------------------------------
// local definitions
//------------------------------------------------------------------------------

// maximum number of sessions
#define PREPROC_NUM_SESSIONS 8

// types of pre processing modules
enum preproc_id {
    PREPROC_AGC,  // Automatic Gain Control
    PREPROC_AGC2,  // Automatic Gain Control 2
    PREPROC_AEC,  // Acoustic Echo Canceler
    PREPROC_NS,   // Noise Suppressor
    PREPROC_NUM_EFFECTS
};

// Session state
enum preproc_session_state {
    PREPROC_SESSION_STATE_INIT,   // initialized
    PREPROC_SESSION_STATE_CONFIG  // configuration received
};

// Effect/Preprocessor state
enum preproc_effect_state {
    PREPROC_EFFECT_STATE_INIT,     // initialized
    PREPROC_EFFECT_STATE_CREATED,  // webRTC engine created
    PREPROC_EFFECT_STATE_CONFIG,   // configuration received/disabled
    PREPROC_EFFECT_STATE_ACTIVE    // active/enabled
};

// handle on webRTC engine
typedef void* preproc_fx_handle_t;

typedef struct preproc_session_s preproc_session_t;
typedef struct preproc_effect_s preproc_effect_t;
typedef struct preproc_ops_s preproc_ops_t;

// Effect operation table. Functions for all pre processors are declared in sPreProcOps[] table.
// Function pointer can be null if no action required.
struct preproc_ops_s {
    int (*create)(preproc_effect_t* fx);
    int (*init)(preproc_effect_t* fx);
    int (*reset)(preproc_effect_t* fx);
    void (*enable)(preproc_effect_t* fx);
    void (*disable)(preproc_effect_t* fx);
    int (*set_parameter)(preproc_effect_t* fx, void* param, void* value);
    int (*get_parameter)(preproc_effect_t* fx, void* param, uint32_t* size, void* value);
    int (*set_device)(preproc_effect_t* fx, uint32_t device);
};

// Effect context
struct preproc_effect_s {
    const struct effect_interface_s* itfe;
    uint32_t procId;             // type of pre processor (enum preproc_id)
    uint32_t state;              // current state (enum preproc_effect_state)
    preproc_session_t* session;  // session the effect is on
    const preproc_ops_t* ops;    // effect ops table
    preproc_fx_handle_t engine;  // handle on webRTC engine
    uint32_t type;               // subtype of effect
#ifdef DUAL_MIC_TEST
    bool aux_channels_on;       // support auxiliary channels
    size_t cur_channel_config;  // current auciliary channel configuration
#endif
};

// Session context
struct preproc_session_s {
    struct preproc_effect_s effects[PREPROC_NUM_EFFECTS];  // effects in this session
    uint32_t state;                // current state (enum preproc_session_state)
    int id;                        // audio session ID
    int io;                        // handle of input stream this session is on
    rtc::scoped_refptr<webrtc::AudioProcessing>
            apm;  // handle on webRTC audio processing module (APM)
    // Audio Processing module builder
    webrtc::AudioProcessingBuilder ap_builder;
    // frameCount represents the size of the buffers used for processing, and must represent 10ms.
    size_t frameCount;
    uint32_t samplingRate;     // sampling rate at effect process interface
    uint32_t inChannelCount;   // input channel count
    uint32_t outChannelCount;  // output channel count
    uint32_t createdMsk;       // bit field containing IDs of crested pre processors
    uint32_t enabledMsk;       // bit field containing IDs of enabled pre processors
    uint32_t processedMsk;     // bit field containing IDs of pre processors already
                               // processed in current round
    // audio config strucutre
    webrtc::AudioProcessing::Config config;
    webrtc::StreamConfig inputConfig;   // input stream configuration
    webrtc::StreamConfig outputConfig;  // output stream configuration
    uint32_t revChannelCount;  // number of channels on reverse stream
    uint32_t revEnabledMsk;    // bit field containing IDs of enabled pre processors
                               // with reverse channel
    uint32_t revProcessedMsk;  // bit field containing IDs of pre processors with reverse
                               // channel already processed in current round
    webrtc::StreamConfig revConfig;     // reverse stream configuration.
};

#ifdef DUAL_MIC_TEST
enum {
    PREPROC_CMD_DUAL_MIC_ENABLE = EFFECT_CMD_FIRST_PROPRIETARY,  // enable dual mic mode
    PREPROC_CMD_DUAL_MIC_PCM_DUMP_START,                         // start pcm capture
    PREPROC_CMD_DUAL_MIC_PCM_DUMP_STOP                           // stop pcm capture
};

enum {
    CHANNEL_CFG_MONO,
    CHANNEL_CFG_STEREO,
    CHANNEL_CFG_MONO_AUX,
    CHANNEL_CFG_STEREO_AUX,
    CHANNEL_CFG_CNT,
    CHANNEL_CFG_FIRST_AUX = CHANNEL_CFG_MONO_AUX,
};

const channel_config_t sDualMicConfigs[CHANNEL_CFG_CNT] = {
        {AUDIO_CHANNEL_IN_MONO, 0},
        {AUDIO_CHANNEL_IN_STEREO, 0},
        {AUDIO_CHANNEL_IN_FRONT, AUDIO_CHANNEL_IN_BACK},
        {AUDIO_CHANNEL_IN_STEREO, AUDIO_CHANNEL_IN_RIGHT}};

bool sHasAuxChannels[PREPROC_NUM_EFFECTS] = {
        false,  // PREPROC_AGC
        false,  // PREPROC_AGC2
        true,   // PREPROC_AEC
        true,   // PREPROC_NS
};

bool gDualMicEnabled;
FILE* gPcmDumpFh;
static pthread_mutex_t gPcmDumpLock = PTHREAD_MUTEX_INITIALIZER;
#endif

//------------------------------------------------------------------------------
// Effect descriptors
//------------------------------------------------------------------------------

// UUIDs for effect types have been generated from http://www.itu.int/ITU-T/asn1/uuid.html
// as the pre processing effects are not defined by OpenSL ES

// Automatic Gain Control
static const effect_descriptor_t sAgcDescriptor = {
        {0x0a8abfe0, 0x654c, 0x11e0, 0xba26, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},  // type
        {0xaa8130e0, 0x66fc, 0x11e0, 0xbad0, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},  // uuid
        EFFECT_CONTROL_API_VERSION,
        (EFFECT_FLAG_TYPE_PRE_PROC | EFFECT_FLAG_DEVICE_IND),
        0,  // FIXME indicate CPU load
        0,  // FIXME indicate memory usage
        "Automatic Gain Control",
        "The Android Open Source Project"};

// Automatic Gain Control 2
static const effect_descriptor_t sAgc2Descriptor = {
        {0xae3c653b, 0xbe18, 0x4ab8, 0x8938, {0x41, 0x8f, 0x0a, 0x7f, 0x06, 0xac}},  // type
        {0x89f38e65, 0xd4d2, 0x4d64, 0xad0e, {0x2b, 0x3e, 0x79, 0x9e, 0xa8, 0x86}},  // uuid
        EFFECT_CONTROL_API_VERSION,
        (EFFECT_FLAG_TYPE_PRE_PROC | EFFECT_FLAG_DEVICE_IND),
        0,  // FIXME indicate CPU load
        0,  // FIXME indicate memory usage
        "Automatic Gain Control 2",
        "The Android Open Source Project"};

// Acoustic Echo Cancellation
static const effect_descriptor_t sAecDescriptor = {
        {0x7b491460, 0x8d4d, 0x11e0, 0xbd61, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},  // type
        {0xbb392ec0, 0x8d4d, 0x11e0, 0xa896, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},  // uuid
        EFFECT_CONTROL_API_VERSION,
        (EFFECT_FLAG_TYPE_PRE_PROC | EFFECT_FLAG_DEVICE_IND),
        0,  // FIXME indicate CPU load
        0,  // FIXME indicate memory usage
        "Acoustic Echo Canceler",
        "The Android Open Source Project"};

// Noise suppression
static const effect_descriptor_t sNsDescriptor = {
        {0x58b4b260, 0x8e06, 0x11e0, 0xaa8e, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},  // type
        {0xc06c8400, 0x8e06, 0x11e0, 0x9cb6, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},  // uuid
        EFFECT_CONTROL_API_VERSION,
        (EFFECT_FLAG_TYPE_PRE_PROC | EFFECT_FLAG_DEVICE_IND),
        0,  // FIXME indicate CPU load
        0,  // FIXME indicate memory usage
        "Noise Suppression",
        "The Android Open Source Project"};

static const effect_descriptor_t* sDescriptors[PREPROC_NUM_EFFECTS] = {&sAgcDescriptor,
                                                                       &sAgc2Descriptor,
                                                                       &sAecDescriptor,
                                                                       &sNsDescriptor};

//------------------------------------------------------------------------------
// Helper functions
//------------------------------------------------------------------------------

const effect_uuid_t* const sUuidToPreProcTable[PREPROC_NUM_EFFECTS] = {FX_IID_AGC,
                                                                       FX_IID_AGC2,
                                                                       FX_IID_AEC, FX_IID_NS};

const effect_uuid_t* ProcIdToUuid(int procId) {
    if (procId >= PREPROC_NUM_EFFECTS) {
        return EFFECT_UUID_NULL;
    }
    return sUuidToPreProcTable[procId];
}

uint32_t UuidToProcId(const effect_uuid_t* uuid) {
    size_t i;
    for (i = 0; i < PREPROC_NUM_EFFECTS; i++) {
        if (memcmp(uuid, sUuidToPreProcTable[i], sizeof(*uuid)) == 0) {
            break;
        }
    }
    return i;
}

bool HasReverseStream(uint32_t procId) {
    if (procId == PREPROC_AEC) {
        return true;
    }
    return false;
}

//------------------------------------------------------------------------------
// Automatic Gain Control (AGC)
//------------------------------------------------------------------------------

static const int kAgcDefaultTargetLevel = 3;
static const int kAgcDefaultCompGain = 9;
static const bool kAgcDefaultLimiter = true;

int Agc2Init(preproc_effect_t* effect) {
    ALOGV("Agc2Init");
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.gain_controller2.fixed_digital.gain_db = 0.f;
    effect->session->apm->ApplyConfig(effect->session->config);
    return 0;
}

int AgcInit(preproc_effect_t* effect) {
    ALOGV("AgcInit");
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.gain_controller1.target_level_dbfs = kAgcDefaultTargetLevel;
    effect->session->config.gain_controller1.compression_gain_db = kAgcDefaultCompGain;
    effect->session->config.gain_controller1.enable_limiter = kAgcDefaultLimiter;
    effect->session->apm->ApplyConfig(effect->session->config);
    return 0;
}

int Agc2Create(preproc_effect_t* effect) {
    Agc2Init(effect);
    return 0;
}

int AgcCreate(preproc_effect_t* effect) {
    AgcInit(effect);
    return 0;
}

int Agc2GetParameter(preproc_effect_t* effect, void* pParam, uint32_t* pValueSize, void* pValue) {
    int status = 0;
    uint32_t param = *(uint32_t*)pParam;
    agc2_settings_t* pProperties = (agc2_settings_t*)pValue;

    switch (param) {
        case AGC2_PARAM_FIXED_DIGITAL_GAIN:
            if (*pValueSize < sizeof(float)) {
                *pValueSize = 0.f;
                return -EINVAL;
            }
            break;
        case AGC2_PARAM_ADAPT_DIGI_LEVEL_ESTIMATOR:
            if (*pValueSize < sizeof(int32_t)) {
                *pValueSize = 0;
                return -EINVAL;
            }
            break;
        case AGC2_PARAM_ADAPT_DIGI_EXTRA_SATURATION_MARGIN:
            if (*pValueSize < sizeof(float)) {
                *pValueSize = 0.f;
                return -EINVAL;
            }
            break;
        case AGC2_PARAM_PROPERTIES:
            if (*pValueSize < sizeof(agc2_settings_t)) {
                *pValueSize = 0;
                return -EINVAL;
            }
            break;

        default:
            ALOGW("Agc2GetParameter() unknown param %08x", param);
            status = -EINVAL;
            break;
    }

    effect->session->config = effect->session->apm->GetConfig();
    switch (param) {
        case AGC2_PARAM_FIXED_DIGITAL_GAIN:
            *(float*)pValue =
                    (float)(effect->session->config.gain_controller2.fixed_digital.gain_db);
            ALOGV("Agc2GetParameter() target level %f dB", *(float*)pValue);
            break;
        case AGC2_PARAM_ADAPT_DIGI_LEVEL_ESTIMATOR:
            // WebRTC only supports RMS level estimator now
            *(uint32_t*)pValue = (uint32_t)(0);
            ALOGV("Agc2GetParameter() level estimator RMS");
            break;
        case AGC2_PARAM_ADAPT_DIGI_EXTRA_SATURATION_MARGIN:
            *(float*)pValue = (float)(2.0);
            ALOGV("Agc2GetParameter() extra saturation margin %f dB", *(float*)pValue);
            break;
        case AGC2_PARAM_PROPERTIES:
            pProperties->fixedDigitalGain =
                    (float)(effect->session->config.gain_controller2.fixed_digital.gain_db);
            pProperties->level_estimator = 0;
            pProperties->extraSaturationMargin = 2.0;
            break;
        default:
            ALOGW("Agc2GetParameter() unknown param %d", param);
            status = -EINVAL;
            break;
    }

    return status;
}

int AgcGetParameter(preproc_effect_t* effect, void* pParam, uint32_t* pValueSize, void* pValue) {
    int status = 0;
    uint32_t param = *(uint32_t*)pParam;
    t_agc_settings* pProperties = (t_agc_settings*)pValue;

    switch (param) {
        case AGC_PARAM_TARGET_LEVEL:
        case AGC_PARAM_COMP_GAIN:
            if (*pValueSize < sizeof(int16_t)) {
                *pValueSize = 0;
                return -EINVAL;
            }
            break;
        case AGC_PARAM_LIMITER_ENA:
            if (*pValueSize < sizeof(bool)) {
                *pValueSize = 0;
                return -EINVAL;
            }
            break;
        case AGC_PARAM_PROPERTIES:
            if (*pValueSize < sizeof(t_agc_settings)) {
                *pValueSize = 0;
                return -EINVAL;
            }
            break;

        default:
            ALOGW("AgcGetParameter() unknown param %08x", param);
            status = -EINVAL;
            break;
    }

    effect->session->config = effect->session->apm->GetConfig();
    switch (param) {
        case AGC_PARAM_TARGET_LEVEL:
            *(int16_t*)pValue =
                    (int16_t)(effect->session->config.gain_controller1.target_level_dbfs * -100);
            ALOGV("AgcGetParameter() target level %d milliBels", *(int16_t*)pValue);
            break;
        case AGC_PARAM_COMP_GAIN:
            *(int16_t*)pValue =
                    (int16_t)(effect->session->config.gain_controller1.compression_gain_db * -100);
            ALOGV("AgcGetParameter() comp gain %d milliBels", *(int16_t*)pValue);
            break;
        case AGC_PARAM_LIMITER_ENA:
            *(bool*)pValue = (bool)(effect->session->config.gain_controller1.enable_limiter);
            ALOGV("AgcGetParameter() limiter enabled %s",
                  (*(int16_t*)pValue != 0) ? "true" : "false");
            break;
        case AGC_PARAM_PROPERTIES:
            pProperties->targetLevel =
                    (int16_t)(effect->session->config.gain_controller1.target_level_dbfs * -100);
            pProperties->compGain =
                    (int16_t)(effect->session->config.gain_controller1.compression_gain_db * -100);
            pProperties->limiterEnabled =
                    (bool)(effect->session->config.gain_controller1.enable_limiter);
            break;
        default:
            ALOGW("AgcGetParameter() unknown param %d", param);
            status = -EINVAL;
            break;
    }
    return status;
}

int Agc2SetParameter(preproc_effect_t* effect, void* pParam, void* pValue) {
    int status = 0;
    uint32_t param = *(uint32_t*)pParam;
    float valueFloat = 0.f;
    agc2_settings_t* pProperties = (agc2_settings_t*)pValue;
    effect->session->config = effect->session->apm->GetConfig();
    switch (param) {
        case AGC2_PARAM_FIXED_DIGITAL_GAIN:
            valueFloat = (float)(*(int32_t*)pValue);
            ALOGV("Agc2SetParameter() fixed digital gain %f dB", valueFloat);
            effect->session->config.gain_controller2.fixed_digital.gain_db = valueFloat;
            break;
        case AGC2_PARAM_ADAPT_DIGI_LEVEL_ESTIMATOR:
            ALOGV("Agc2SetParameter() level estimator %d", *(uint32_t*)pValue);
            if (*(uint32_t*)pValue != 0) {
              // only RMS is supported
              status = -EINVAL;
            }
            break;
        case AGC2_PARAM_ADAPT_DIGI_EXTRA_SATURATION_MARGIN:
            valueFloat = (float)(*(int32_t*)pValue);
            ALOGV("Agc2SetParameter() extra saturation margin %f dB", valueFloat);
            if (valueFloat != 2.0) {
              // extra_staturation_margin_db is no longer configurable in webrtc
              status = -EINVAL;
            }
            break;
        case AGC2_PARAM_PROPERTIES:
            ALOGV("Agc2SetParameter() properties gain %f, level %d margin %f",
                  pProperties->fixedDigitalGain, pProperties->level_estimator,
                  pProperties->extraSaturationMargin);
            effect->session->config.gain_controller2.fixed_digital.gain_db =
                    pProperties->fixedDigitalGain;
            if (pProperties->level_estimator != 0 || pProperties->extraSaturationMargin != 2.0) {
              status = -EINVAL;
            }
            break;
        default:
            ALOGW("Agc2SetParameter() unknown param %08x value %08x", param, *(uint32_t*)pValue);
            status = -EINVAL;
            break;
    }
    effect->session->apm->ApplyConfig(effect->session->config);

    ALOGV("Agc2SetParameter() done status %d", status);

    return status;
}

int AgcSetParameter(preproc_effect_t* effect, void* pParam, void* pValue) {
    int status = 0;
    uint32_t param = *(uint32_t*)pParam;
    t_agc_settings* pProperties = (t_agc_settings*)pValue;
    effect->session->config = effect->session->apm->GetConfig();
    switch (param) {
        case AGC_PARAM_TARGET_LEVEL:
            ALOGV("AgcSetParameter() target level %d milliBels", *(int16_t*)pValue);
            effect->session->config.gain_controller1.target_level_dbfs =
                    (-(*(int16_t*)pValue / 100));
            break;
        case AGC_PARAM_COMP_GAIN:
            ALOGV("AgcSetParameter() comp gain %d milliBels", *(int16_t*)pValue);
            effect->session->config.gain_controller1.compression_gain_db =
                    (*(int16_t*)pValue / 100);
            break;
        case AGC_PARAM_LIMITER_ENA:
            ALOGV("AgcSetParameter() limiter enabled %s", *(bool*)pValue ? "true" : "false");
            effect->session->config.gain_controller1.enable_limiter = (*(bool*)pValue);
            break;
        case AGC_PARAM_PROPERTIES:
            ALOGV("AgcSetParameter() properties level %d, gain %d limiter %d",
                  pProperties->targetLevel, pProperties->compGain, pProperties->limiterEnabled);
            effect->session->config.gain_controller1.target_level_dbfs =
                    -(pProperties->targetLevel / 100);
            effect->session->config.gain_controller1.compression_gain_db =
                    pProperties->compGain / 100;
            effect->session->config.gain_controller1.enable_limiter = pProperties->limiterEnabled;
            break;
        default:
            ALOGW("AgcSetParameter() unknown param %08x value %08x", param, *(uint32_t*)pValue);
            status = -EINVAL;
            break;
    }
    effect->session->apm->ApplyConfig(effect->session->config);

    ALOGV("AgcSetParameter() done status %d", status);

    return status;
}

void Agc2Enable(preproc_effect_t* effect) {
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.gain_controller2.enabled = true;
    effect->session->apm->ApplyConfig(effect->session->config);
}

void AgcEnable(preproc_effect_t* effect) {
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.gain_controller1.enabled = true;
    effect->session->apm->ApplyConfig(effect->session->config);
}

void Agc2Disable(preproc_effect_t* effect) {
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.gain_controller2.enabled = false;
    effect->session->apm->ApplyConfig(effect->session->config);
}

void AgcDisable(preproc_effect_t* effect) {
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.gain_controller1.enabled = false;
    effect->session->apm->ApplyConfig(effect->session->config);
}

static const preproc_ops_t sAgcOps = {AgcCreate,       AgcInit,         NULL, AgcEnable, AgcDisable,
                                      AgcSetParameter, AgcGetParameter, NULL};

static const preproc_ops_t sAgc2Ops = {Agc2Create,       Agc2Init,    NULL,
                                       Agc2Enable,       Agc2Disable, Agc2SetParameter,
                                       Agc2GetParameter, NULL};

//------------------------------------------------------------------------------
// Acoustic Echo Canceler (AEC)
//------------------------------------------------------------------------------


int AecInit(preproc_effect_t* effect) {
    ALOGV("AecInit");
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.echo_canceller.mobile_mode = true;
    effect->session->apm->ApplyConfig(effect->session->config);
    return 0;
}

int AecCreate(preproc_effect_t* effect) {
    AecInit(effect);
    return 0;
}

int AecGetParameter(preproc_effect_t* effect, void* pParam, uint32_t* pValueSize, void* pValue) {
    int status = 0;
    uint32_t param = *(uint32_t*)pParam;

    if (*pValueSize < sizeof(uint32_t)) {
        return -EINVAL;
    }
    switch (param) {
        case AEC_PARAM_ECHO_DELAY:
        case AEC_PARAM_PROPERTIES:
            *(uint32_t*)pValue = 1000 * effect->session->apm->stream_delay_ms();
            ALOGV("AecGetParameter() echo delay %d us", *(uint32_t*)pValue);
            break;
        case AEC_PARAM_MOBILE_MODE:
            effect->session->config = effect->session->apm->GetConfig();
            *(uint32_t*)pValue = effect->session->config.echo_canceller.mobile_mode;
            ALOGV("AecGetParameter() mobile mode %d us", *(uint32_t*)pValue);
            break;
        default:
            ALOGW("AecGetParameter() unknown param %08x value %08x", param, *(uint32_t*)pValue);
            status = -EINVAL;
            break;
    }
    return status;
}

int AecSetParameter(preproc_effect_t* effect, void* pParam, void* pValue) {
    int status = 0;
    uint32_t param = *(uint32_t*)pParam;
    uint32_t value = *(uint32_t*)pValue;

    switch (param) {
        case AEC_PARAM_ECHO_DELAY:
        case AEC_PARAM_PROPERTIES:
            status = effect->session->apm->set_stream_delay_ms(value / 1000);
            ALOGV("AecSetParameter() echo delay %d us, status %d", value, status);
            break;
        case AEC_PARAM_MOBILE_MODE:
            effect->session->config = effect->session->apm->GetConfig();
            effect->session->config.echo_canceller.mobile_mode = value;
            ALOGV("AecSetParameter() mobile mode %d us", value);
            effect->session->apm->ApplyConfig(effect->session->config);
            break;
        default:
            ALOGW("AecSetParameter() unknown param %08x value %08x", param, *(uint32_t*)pValue);
            status = -EINVAL;
            break;
    }
    return status;
}

void AecEnable(preproc_effect_t* effect) {
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.echo_canceller.enabled = true;
    effect->session->apm->ApplyConfig(effect->session->config);
}

void AecDisable(preproc_effect_t* effect) {
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.echo_canceller.enabled = false;
    effect->session->apm->ApplyConfig(effect->session->config);
}

int AecSetDevice(preproc_effect_t* effect, uint32_t device) {
    ALOGV("AecSetDevice %08x", device);

    if (audio_is_input_device(device)) {
        return 0;
    }

    return 0;
}

static const preproc_ops_t sAecOps = {AecCreate,       AecInit,     NULL,
                                      AecEnable,       AecDisable,  AecSetParameter,
                                      AecGetParameter, AecSetDevice};

//------------------------------------------------------------------------------
// Noise Suppression (NS)
//------------------------------------------------------------------------------

static const webrtc::AudioProcessing::Config::NoiseSuppression::Level kNsDefaultLevel =
        webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;

int NsInit(preproc_effect_t* effect) {
    ALOGV("NsInit");
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.noise_suppression.level = kNsDefaultLevel;
    effect->session->apm->ApplyConfig(effect->session->config);
    effect->type = NS_TYPE_SINGLE_CHANNEL;
    return 0;
}

int NsCreate(preproc_effect_t* effect) {
    NsInit(effect);
    return 0;
}

int NsGetParameter(preproc_effect_t* /*effect __unused*/, void* /*pParam __unused*/,
                   uint32_t* /*pValueSize __unused*/, void* /*pValue __unused*/) {
    int status = 0;
    return status;
}

int NsSetParameter(preproc_effect_t* effect, void* pParam, void* pValue) {
    int status = 0;
    uint32_t param = *(uint32_t*)pParam;
    uint32_t value = *(uint32_t*)pValue;
    effect->session->config = effect->session->apm->GetConfig();
    switch (param) {
        case NS_PARAM_LEVEL:
            effect->session->config.noise_suppression.level =
                    (webrtc::AudioProcessing::Config::NoiseSuppression::Level)value;
            ALOGV("NsSetParameter() level %d", value);
            break;
        default:
            ALOGW("NsSetParameter() unknown param %08x value %08x", param, value);
            status = -EINVAL;
    }
    effect->session->apm->ApplyConfig(effect->session->config);

    return status;
}

void NsEnable(preproc_effect_t* effect) {
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.noise_suppression.enabled = true;
    effect->session->apm->ApplyConfig(effect->session->config);
}

void NsDisable(preproc_effect_t* effect) {
    ALOGV("NsDisable");
    effect->session->config = effect->session->apm->GetConfig();
    effect->session->config.noise_suppression.enabled = false;
    effect->session->apm->ApplyConfig(effect->session->config);
}

static const preproc_ops_t sNsOps = {NsCreate,  NsInit,         NULL,           NsEnable,
                                     NsDisable, NsSetParameter, NsGetParameter, NULL};

static const preproc_ops_t* sPreProcOps[PREPROC_NUM_EFFECTS] = {&sAgcOps,
                                                                &sAgc2Ops,
                                                                &sAecOps, &sNsOps};

//------------------------------------------------------------------------------
// Effect functions
//------------------------------------------------------------------------------

void Session_SetProcEnabled(preproc_session_t* session, uint32_t procId, bool enabled);

extern "C" const struct effect_interface_s sEffectInterface;
extern "C" const struct effect_interface_s sEffectInterfaceReverse;

#define BAD_STATE_ABORT(from, to) LOG_ALWAYS_FATAL("Bad state transition from %d to %d", from, to);

int Effect_SetState(preproc_effect_t* effect, uint32_t state) {
    int status = 0;
    ALOGV("Effect_SetState proc %d, new %d old %d", effect->procId, state, effect->state);
    switch (state) {
        case PREPROC_EFFECT_STATE_INIT:
            switch (effect->state) {
                case PREPROC_EFFECT_STATE_ACTIVE:
                    effect->ops->disable(effect);
                    Session_SetProcEnabled(effect->session, effect->procId, false);
                    break;
                case PREPROC_EFFECT_STATE_CONFIG:
                case PREPROC_EFFECT_STATE_CREATED:
                case PREPROC_EFFECT_STATE_INIT:
                    break;
                default:
                    BAD_STATE_ABORT(effect->state, state);
            }
            break;
        case PREPROC_EFFECT_STATE_CREATED:
            switch (effect->state) {
                case PREPROC_EFFECT_STATE_INIT:
                    status = effect->ops->create(effect);
                    break;
                case PREPROC_EFFECT_STATE_CREATED:
                case PREPROC_EFFECT_STATE_ACTIVE:
                case PREPROC_EFFECT_STATE_CONFIG:
                    ALOGE("Effect_SetState invalid transition");
                    status = -ENOSYS;
                    break;
                default:
                    BAD_STATE_ABORT(effect->state, state);
            }
            break;
        case PREPROC_EFFECT_STATE_CONFIG:
            switch (effect->state) {
                case PREPROC_EFFECT_STATE_INIT:
                    ALOGE("Effect_SetState invalid transition");
                    status = -ENOSYS;
                    break;
                case PREPROC_EFFECT_STATE_ACTIVE:
                    effect->ops->disable(effect);
                    Session_SetProcEnabled(effect->session, effect->procId, false);
                    break;
                case PREPROC_EFFECT_STATE_CREATED:
                case PREPROC_EFFECT_STATE_CONFIG:
                    break;
                default:
                    BAD_STATE_ABORT(effect->state, state);
            }
            break;
        case PREPROC_EFFECT_STATE_ACTIVE:
            switch (effect->state) {
                case PREPROC_EFFECT_STATE_INIT:
                case PREPROC_EFFECT_STATE_CREATED:
                    ALOGE("Effect_SetState invalid transition");
                    status = -ENOSYS;
                    break;
                case PREPROC_EFFECT_STATE_ACTIVE:
                    // enabling an already enabled effect is just ignored
                    break;
                case PREPROC_EFFECT_STATE_CONFIG:
                    effect->ops->enable(effect);
                    Session_SetProcEnabled(effect->session, effect->procId, true);
                    break;
                default:
                    BAD_STATE_ABORT(effect->state, state);
            }
            break;
        default:
            BAD_STATE_ABORT(effect->state, state);
    }
    if (status == 0) {
        effect->state = state;
    }
    return status;
}

int Effect_Init(preproc_effect_t* effect, uint32_t procId) {
    if (HasReverseStream(procId)) {
        effect->itfe = &sEffectInterfaceReverse;
    } else {
        effect->itfe = &sEffectInterface;
    }
    effect->ops = sPreProcOps[procId];
    effect->procId = procId;
    effect->state = PREPROC_EFFECT_STATE_INIT;
    return 0;
}

int Effect_Create(preproc_effect_t* effect, preproc_session_t* session,
                  effect_handle_t* interface) {
    effect->session = session;
    *interface = (effect_handle_t)&effect->itfe;
    return Effect_SetState(effect, PREPROC_EFFECT_STATE_CREATED);
}

int Effect_Release(preproc_effect_t* effect) {
    return Effect_SetState(effect, PREPROC_EFFECT_STATE_INIT);
}

//------------------------------------------------------------------------------
// Session functions
//------------------------------------------------------------------------------

#define RESAMPLER_QUALITY SPEEX_RESAMPLER_QUALITY_VOIP

static const int kPreprocDefaultSr = 16000;
static const int kPreProcDefaultCnl = 1;

int Session_Init(preproc_session_t* session) {
    size_t i;
    int status = 0;

    session->state = PREPROC_SESSION_STATE_INIT;
    session->id = 0;
    session->io = 0;
    session->createdMsk = 0;
    for (i = 0; i < PREPROC_NUM_EFFECTS && status == 0; i++) {
        status = Effect_Init(&session->effects[i], i);
    }
    return status;
}

extern "C" int Session_CreateEffect(preproc_session_t* session, int32_t procId,
                                    effect_handle_t* interface) {
    int status = -ENOMEM;

    ALOGV("Session_CreateEffect procId %d, createdMsk %08x", procId, session->createdMsk);

    if (session->createdMsk == 0) {
        session->apm = session->ap_builder.Create();
        if (session->apm == NULL) {
            ALOGW("Session_CreateEffect could not get apm engine");
            goto error;
        }
        session->frameCount = kPreprocDefaultSr / 100;
        session->samplingRate = kPreprocDefaultSr;
        session->inChannelCount = kPreProcDefaultCnl;
        session->outChannelCount = kPreProcDefaultCnl;
        session->inputConfig.set_sample_rate_hz(kPreprocDefaultSr);
        session->inputConfig.set_num_channels(kPreProcDefaultCnl);
        session->outputConfig.set_sample_rate_hz(kPreprocDefaultSr);
        session->outputConfig.set_num_channels(kPreProcDefaultCnl);
        session->revChannelCount = kPreProcDefaultCnl;
        session->revConfig.set_sample_rate_hz(kPreprocDefaultSr);
        session->revConfig.set_num_channels(kPreProcDefaultCnl);
        session->enabledMsk = 0;
        session->processedMsk = 0;
        session->revEnabledMsk = 0;
        session->revProcessedMsk = 0;
    }
    status = Effect_Create(&session->effects[procId], session, interface);
    if (status < 0) {
        goto error;
    }
    ALOGV("Session_CreateEffect OK");
    session->createdMsk |= (1 << procId);
    return status;

error:
    if (session->createdMsk == 0) {
        // Scoped_refptr will handle reference counting here
        session->apm = nullptr;
    }
    return status;
}

int Session_ReleaseEffect(preproc_session_t* session, preproc_effect_t* fx) {
    ALOGW_IF(Effect_Release(fx) != 0, " Effect_Release() failed for proc ID %d", fx->procId);
    session->createdMsk &= ~(1 << fx->procId);
    if (session->createdMsk == 0) {
        // Scoped_refptr will handle reference counting here
        session->apm = nullptr;
        session->id = 0;
    }

    return 0;
}

int Session_SetConfig(preproc_session_t* session, effect_config_t* config) {
    uint32_t inCnl = audio_channel_count_from_in_mask(config->inputCfg.channels);
    uint32_t outCnl = audio_channel_count_from_in_mask(config->outputCfg.channels);

    if (config->inputCfg.samplingRate != config->outputCfg.samplingRate ||
        config->inputCfg.format != config->outputCfg.format ||
        config->inputCfg.format != AUDIO_FORMAT_PCM_16_BIT) {
        return -EINVAL;
    }

    ALOGV("Session_SetConfig sr %d cnl %08x", config->inputCfg.samplingRate,
          config->inputCfg.channels);

    session->samplingRate = config->inputCfg.samplingRate;
    session->frameCount = session->samplingRate / 100;
    session->inChannelCount = inCnl;
    session->outChannelCount = outCnl;
    session->inputConfig.set_sample_rate_hz(session->samplingRate);
    session->inputConfig.set_num_channels(inCnl);
    session->outputConfig.set_sample_rate_hz(session->samplingRate);
    session->outputConfig.set_num_channels(inCnl);

    session->revChannelCount = inCnl;
    session->revConfig.set_sample_rate_hz(session->samplingRate);
    session->revConfig.set_num_channels(inCnl);

    session->state = PREPROC_SESSION_STATE_CONFIG;
    return 0;
}

void Session_GetConfig(preproc_session_t* session, effect_config_t* config) {
    memset(config, 0, sizeof(effect_config_t));
    config->inputCfg.samplingRate = config->outputCfg.samplingRate = session->samplingRate;
    config->inputCfg.format = config->outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
    config->inputCfg.channels = audio_channel_in_mask_from_count(session->inChannelCount);
    // "out" doesn't mean output device, so this is the correct API to convert channel count to mask
    config->outputCfg.channels = audio_channel_in_mask_from_count(session->outChannelCount);
    config->inputCfg.mask = config->outputCfg.mask =
            (EFFECT_CONFIG_SMP_RATE | EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT);
}

int Session_SetReverseConfig(preproc_session_t* session, effect_config_t* config) {
    if (config->inputCfg.samplingRate != config->outputCfg.samplingRate ||
        config->inputCfg.format != config->outputCfg.format ||
        config->inputCfg.format != AUDIO_FORMAT_PCM_16_BIT) {
        return -EINVAL;
    }

    ALOGV("Session_SetReverseConfig sr %d cnl %08x", config->inputCfg.samplingRate,
          config->inputCfg.channels);

    if (session->state < PREPROC_SESSION_STATE_CONFIG) {
        return -ENOSYS;
    }
    if (config->inputCfg.samplingRate != session->samplingRate ||
        config->inputCfg.format != AUDIO_FORMAT_PCM_16_BIT) {
        return -EINVAL;
    }
    uint32_t inCnl = audio_channel_count_from_out_mask(config->inputCfg.channels);
    session->revChannelCount = inCnl;

    return 0;
}

void Session_GetReverseConfig(preproc_session_t* session, effect_config_t* config) {
    memset(config, 0, sizeof(effect_config_t));
    config->inputCfg.samplingRate = config->outputCfg.samplingRate = session->samplingRate;
    config->inputCfg.format = config->outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
    config->inputCfg.channels = config->outputCfg.channels =
            audio_channel_in_mask_from_count(session->revChannelCount);
    config->inputCfg.mask = config->outputCfg.mask =
            (EFFECT_CONFIG_SMP_RATE | EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT);
}

void Session_SetProcEnabled(preproc_session_t* session, uint32_t procId, bool enabled) {
    if (enabled) {
        session->enabledMsk |= (1 << procId);
        if (HasReverseStream(procId)) {
            session->revEnabledMsk |= (1 << procId);
        }
    } else {
        session->enabledMsk &= ~(1 << procId);
        if (HasReverseStream(procId)) {
            session->revEnabledMsk &= ~(1 << procId);
        }
    }
    ALOGV("Session_SetProcEnabled proc %d, enabled %d enabledMsk %08x revEnabledMsk %08x", procId,
          enabled, session->enabledMsk, session->revEnabledMsk);
    session->processedMsk = 0;
    if (HasReverseStream(procId)) {
        session->revProcessedMsk = 0;
    }
}

//------------------------------------------------------------------------------
// Bundle functions
//------------------------------------------------------------------------------

static int sInitStatus = 1;
static preproc_session_t sSessions[PREPROC_NUM_SESSIONS];

preproc_session_t* PreProc_GetSession(int32_t procId, int32_t sessionId, int32_t ioId) {
    size_t i;
    for (i = 0; i < PREPROC_NUM_SESSIONS; i++) {
        if (sSessions[i].id == sessionId) {
            if (sSessions[i].createdMsk & (1 << procId)) {
                return NULL;
            }
            return &sSessions[i];
        }
    }
    for (i = 0; i < PREPROC_NUM_SESSIONS; i++) {
        if (sSessions[i].id == 0) {
            sSessions[i].id = sessionId;
            sSessions[i].io = ioId;
            return &sSessions[i];
        }
    }
    return NULL;
}

int PreProc_Init() {
    size_t i;
    int status = 0;

    if (sInitStatus <= 0) {
        return sInitStatus;
    }
    for (i = 0; i < PREPROC_NUM_SESSIONS && status == 0; i++) {
        status = Session_Init(&sSessions[i]);
    }
    sInitStatus = status;
    return sInitStatus;
}

const effect_descriptor_t* PreProc_GetDescriptor(const effect_uuid_t* uuid) {
    size_t i;
    for (i = 0; i < PREPROC_NUM_EFFECTS; i++) {
        if (memcmp(&sDescriptors[i]->uuid, uuid, sizeof(effect_uuid_t)) == 0) {
            return sDescriptors[i];
        }
    }
    return NULL;
}

extern "C" {

//------------------------------------------------------------------------------
// Effect Control Interface Implementation
//------------------------------------------------------------------------------

int PreProcessingFx_Process(effect_handle_t self, audio_buffer_t* inBuffer,
                            audio_buffer_t* outBuffer) {
    preproc_effect_t* effect = (preproc_effect_t*)self;

    if (effect == NULL) {
        ALOGV("PreProcessingFx_Process() ERROR effect == NULL");
        return -EINVAL;
    }
    preproc_session_t* session = (preproc_session_t*)effect->session;

    if (inBuffer == NULL || inBuffer->raw == NULL || outBuffer == NULL || outBuffer->raw == NULL) {
        ALOGW("PreProcessingFx_Process() ERROR bad pointer");
        return -EINVAL;
    }

    if (inBuffer->frameCount != outBuffer->frameCount) {
        ALOGW("inBuffer->frameCount %zu is not equal to outBuffer->frameCount %zu",
              inBuffer->frameCount, outBuffer->frameCount);
        return -EINVAL;
    }

    if (inBuffer->frameCount != session->frameCount) {
        ALOGW("inBuffer->frameCount %zu != %zu representing 10ms at sampling rate %d",
              inBuffer->frameCount, session->frameCount, session->samplingRate);
        return -EINVAL;
    }

    session->processedMsk |= (1 << effect->procId);

    //    ALOGV("PreProcessingFx_Process In %d frames enabledMsk %08x processedMsk %08x",
    //         inBuffer->frameCount, session->enabledMsk, session->processedMsk);
    if ((session->processedMsk & session->enabledMsk) == session->enabledMsk) {
        effect->session->processedMsk = 0;
        if (int status = effect->session->apm->ProcessStream(
                    (const int16_t* const)inBuffer->s16,
                    (const webrtc::StreamConfig)effect->session->inputConfig,
                    (const webrtc::StreamConfig)effect->session->outputConfig,
                    (int16_t* const)outBuffer->s16);
            status != 0) {
            ALOGE("Process Stream failed with error %d\n", status);
            return status;
        }
        return 0;
    } else {
        return -ENODATA;
    }
}

int PreProcessingFx_Command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
                            void* pCmdData, uint32_t* replySize, void* pReplyData) {
    preproc_effect_t* effect = (preproc_effect_t*)self;

    if (effect == NULL) {
        return -EINVAL;
    }

    // ALOGV("PreProcessingFx_Command: command %d cmdSize %d",cmdCode, cmdSize);

    switch (cmdCode) {
        case EFFECT_CMD_INIT:
            if (pReplyData == NULL || *replySize != sizeof(int)) {
                return -EINVAL;
            }
            if (effect->ops->init) {
                effect->ops->init(effect);
            }
            *(int*)pReplyData = 0;
            break;

        case EFFECT_CMD_SET_CONFIG: {
            if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) || pReplyData == NULL ||
                *replySize != sizeof(int)) {
                ALOGV("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_SET_CONFIG: ERROR");
                return -EINVAL;
            }
#ifdef DUAL_MIC_TEST
            // make sure that the config command is accepted by making as if all effects were
            // disabled: this is OK for functional tests
            uint32_t enabledMsk = effect->session->enabledMsk;
            if (gDualMicEnabled) {
                effect->session->enabledMsk = 0;
            }
#endif
            *(int*)pReplyData = Session_SetConfig(effect->session, (effect_config_t*)pCmdData);
#ifdef DUAL_MIC_TEST
            if (gDualMicEnabled) {
                effect->session->enabledMsk = enabledMsk;
            }
#endif
            if (*(int*)pReplyData != 0) {
                break;
            }
            if (effect->state != PREPROC_EFFECT_STATE_ACTIVE) {
                *(int*)pReplyData = Effect_SetState(effect, PREPROC_EFFECT_STATE_CONFIG);
            }
        } break;

        case EFFECT_CMD_GET_CONFIG:
            if (pReplyData == NULL || *replySize != sizeof(effect_config_t)) {
                ALOGV("\tLVM_ERROR : PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_GET_CONFIG: ERROR");
                return -EINVAL;
            }

            Session_GetConfig(effect->session, (effect_config_t*)pReplyData);
            break;

        case EFFECT_CMD_SET_CONFIG_REVERSE:
            if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) || pReplyData == NULL ||
                *replySize != sizeof(int)) {
                ALOGV("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_SET_CONFIG_REVERSE: ERROR");
                return -EINVAL;
            }
            *(int*)pReplyData =
                    Session_SetReverseConfig(effect->session, (effect_config_t*)pCmdData);
            if (*(int*)pReplyData != 0) {
                break;
            }
            break;

        case EFFECT_CMD_GET_CONFIG_REVERSE:
            if (pReplyData == NULL || *replySize != sizeof(effect_config_t)) {
                ALOGV("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_GET_CONFIG_REVERSE: ERROR");
                return -EINVAL;
            }
            Session_GetReverseConfig(effect->session, (effect_config_t*)pCmdData);
            break;

        case EFFECT_CMD_RESET:
            if (effect->ops->reset) {
                effect->ops->reset(effect);
            }
            break;

        case EFFECT_CMD_GET_PARAM: {
            effect_param_t* p = (effect_param_t*)pCmdData;

            if (pCmdData == NULL || cmdSize < sizeof(effect_param_t) ||
                cmdSize < (sizeof(effect_param_t) + p->psize) || pReplyData == NULL ||
                replySize == NULL || *replySize < (sizeof(effect_param_t) + p->psize)) {
                ALOGV("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_GET_PARAM: ERROR");
                return -EINVAL;
            }

            memcpy(pReplyData, pCmdData, sizeof(effect_param_t) + p->psize);

            p = (effect_param_t*)pReplyData;

            int voffset = ((p->psize - 1) / sizeof(int32_t) + 1) * sizeof(int32_t);

            if (effect->ops->get_parameter) {
                p->status =
                        effect->ops->get_parameter(effect, p->data, &p->vsize, p->data + voffset);
                *replySize = sizeof(effect_param_t) + voffset + p->vsize;
            }
        } break;

        case EFFECT_CMD_SET_PARAM: {
            if (pCmdData == NULL || cmdSize < sizeof(effect_param_t) || pReplyData == NULL ||
                replySize == NULL || *replySize != sizeof(int32_t)) {
                ALOGV("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_SET_PARAM: ERROR");
                return -EINVAL;
            }
            effect_param_t* p = (effect_param_t*)pCmdData;

            if (p->psize != sizeof(int32_t)) {
                ALOGV("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_SET_PARAM: ERROR, psize is not sizeof(int32_t)");
                return -EINVAL;
            }
            if (effect->ops->set_parameter) {
                *(int*)pReplyData =
                        effect->ops->set_parameter(effect, (void*)p->data, p->data + p->psize);
            }
        } break;

        case EFFECT_CMD_ENABLE:
            if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) {
                ALOGV("PreProcessingFx_Command cmdCode Case: EFFECT_CMD_ENABLE: ERROR");
                return -EINVAL;
            }
            *(int*)pReplyData = Effect_SetState(effect, PREPROC_EFFECT_STATE_ACTIVE);
            break;

        case EFFECT_CMD_DISABLE:
            if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) {
                ALOGV("PreProcessingFx_Command cmdCode Case: EFFECT_CMD_DISABLE: ERROR");
                return -EINVAL;
            }
            *(int*)pReplyData = Effect_SetState(effect, PREPROC_EFFECT_STATE_CONFIG);
            break;

        case EFFECT_CMD_SET_DEVICE:
        case EFFECT_CMD_SET_INPUT_DEVICE:
            if (pCmdData == NULL || cmdSize != sizeof(uint32_t)) {
                ALOGV("PreProcessingFx_Command cmdCode Case: EFFECT_CMD_SET_DEVICE: ERROR");
                return -EINVAL;
            }

            if (effect->ops->set_device) {
                effect->ops->set_device(effect, *(uint32_t*)pCmdData);
            }
            break;

        case EFFECT_CMD_SET_VOLUME:
        case EFFECT_CMD_SET_AUDIO_MODE:
            break;

#ifdef DUAL_MIC_TEST
        ///// test commands start
        case PREPROC_CMD_DUAL_MIC_ENABLE: {
            if (pCmdData == NULL || cmdSize != sizeof(uint32_t) || pReplyData == NULL ||
                replySize == NULL) {
                ALOGE("PreProcessingFx_Command cmdCode Case: "
                      "PREPROC_CMD_DUAL_MIC_ENABLE: ERROR");
                *replySize = 0;
                return -EINVAL;
            }
            gDualMicEnabled = *(bool*)pCmdData;
            if (gDualMicEnabled) {
                effect->aux_channels_on = sHasAuxChannels[effect->procId];
            } else {
                effect->aux_channels_on = false;
            }
            effect->cur_channel_config =
                    (effect->session->inChannelCount == 1) ? CHANNEL_CFG_MONO : CHANNEL_CFG_STEREO;

            ALOGV("PREPROC_CMD_DUAL_MIC_ENABLE: %s", gDualMicEnabled ? "enabled" : "disabled");
            *replySize = sizeof(int);
            *(int*)pReplyData = 0;
        } break;
        case PREPROC_CMD_DUAL_MIC_PCM_DUMP_START: {
            if (pCmdData == NULL || pReplyData == NULL || replySize == NULL) {
                ALOGE("PreProcessingFx_Command cmdCode Case: "
                      "PREPROC_CMD_DUAL_MIC_PCM_DUMP_START: ERROR");
                *replySize = 0;
                return -EINVAL;
            }
            pthread_mutex_lock(&gPcmDumpLock);
            if (gPcmDumpFh != NULL) {
                fclose(gPcmDumpFh);
                gPcmDumpFh = NULL;
            }
            char* path = strndup((char*)pCmdData, cmdSize);
            gPcmDumpFh = fopen((char*)path, "wb");
            pthread_mutex_unlock(&gPcmDumpLock);
            ALOGV("PREPROC_CMD_DUAL_MIC_PCM_DUMP_START: path %s gPcmDumpFh %p", path, gPcmDumpFh);
            ALOGE_IF(gPcmDumpFh <= 0, "gPcmDumpFh open error %d %s", errno, strerror(errno));
            free(path);
            *replySize = sizeof(int);
            *(int*)pReplyData = 0;
        } break;
        case PREPROC_CMD_DUAL_MIC_PCM_DUMP_STOP: {
            if (pReplyData == NULL || replySize == NULL) {
                ALOGE("PreProcessingFx_Command cmdCode Case: "
                      "PREPROC_CMD_DUAL_MIC_PCM_DUMP_STOP: ERROR");
                *replySize = 0;
                return -EINVAL;
            }
            pthread_mutex_lock(&gPcmDumpLock);
            if (gPcmDumpFh != NULL) {
                fclose(gPcmDumpFh);
                gPcmDumpFh = NULL;
            }
            pthread_mutex_unlock(&gPcmDumpLock);
            ALOGV("PREPROC_CMD_DUAL_MIC_PCM_DUMP_STOP");
            *replySize = sizeof(int);
            *(int*)pReplyData = 0;
        } break;
            ///// test commands end

        case EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS: {
            if (!gDualMicEnabled) {
                return -EINVAL;
            }
            if (pCmdData == NULL || cmdSize != 2 * sizeof(uint32_t) || pReplyData == NULL ||
                replySize == NULL) {
                ALOGE("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS: ERROR");
                *replySize = 0;
                return -EINVAL;
            }
            if (*(uint32_t*)pCmdData != EFFECT_FEATURE_AUX_CHANNELS || !effect->aux_channels_on) {
                ALOGV("PreProcessingFx_Command feature EFFECT_FEATURE_AUX_CHANNELS not supported by"
                      " fx %d",
                      effect->procId);
                *(uint32_t*)pReplyData = -ENOSYS;
                *replySize = sizeof(uint32_t);
                break;
            }
            size_t num_configs = *((uint32_t*)pCmdData + 1);
            if (*replySize < (2 * sizeof(uint32_t) + num_configs * sizeof(channel_config_t))) {
                *replySize = 0;
                return -EINVAL;
            }

            *((uint32_t*)pReplyData + 1) = CHANNEL_CFG_CNT;
            if (num_configs < CHANNEL_CFG_CNT ||
                *replySize < (2 * sizeof(uint32_t) + CHANNEL_CFG_CNT * sizeof(channel_config_t))) {
                *(uint32_t*)pReplyData = -ENOMEM;
            } else {
                num_configs = CHANNEL_CFG_CNT;
                *(uint32_t*)pReplyData = 0;
            }
            ALOGV("PreProcessingFx_Command EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS num config %d",
                  num_configs);

            *replySize = 2 * sizeof(uint32_t) + num_configs * sizeof(channel_config_t);
            *((uint32_t*)pReplyData + 1) = num_configs;
            memcpy((uint32_t*)pReplyData + 2, &sDualMicConfigs,
                   num_configs * sizeof(channel_config_t));
        } break;
        case EFFECT_CMD_GET_FEATURE_CONFIG:
            if (!gDualMicEnabled) {
                return -EINVAL;
            }
            if (pCmdData == NULL || cmdSize != sizeof(uint32_t) || pReplyData == NULL ||
                replySize == NULL || *replySize < sizeof(uint32_t) + sizeof(channel_config_t)) {
                ALOGE("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_GET_FEATURE_CONFIG: ERROR");
                return -EINVAL;
            }
            if (*(uint32_t*)pCmdData != EFFECT_FEATURE_AUX_CHANNELS || !effect->aux_channels_on) {
                *(uint32_t*)pReplyData = -ENOSYS;
                *replySize = sizeof(uint32_t);
                break;
            }
            ALOGV("PreProcessingFx_Command EFFECT_CMD_GET_FEATURE_CONFIG");
            *(uint32_t*)pReplyData = 0;
            *replySize = sizeof(uint32_t) + sizeof(channel_config_t);
            memcpy((uint32_t*)pReplyData + 1, &sDualMicConfigs[effect->cur_channel_config],
                   sizeof(channel_config_t));
            break;
        case EFFECT_CMD_SET_FEATURE_CONFIG: {
            ALOGV("PreProcessingFx_Command EFFECT_CMD_SET_FEATURE_CONFIG: "
                  "gDualMicEnabled %d effect->aux_channels_on %d",
                  gDualMicEnabled, effect->aux_channels_on);
            if (!gDualMicEnabled) {
                return -EINVAL;
            }
            if (pCmdData == NULL || cmdSize != (sizeof(uint32_t) + sizeof(channel_config_t)) ||
                pReplyData == NULL || replySize == NULL || *replySize < sizeof(uint32_t)) {
                ALOGE("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_SET_FEATURE_CONFIG: ERROR\n"
                      "pCmdData %p cmdSize %d pReplyData %p replySize %p *replySize %d",
                      pCmdData, cmdSize, pReplyData, replySize, replySize ? *replySize : -1);
                return -EINVAL;
            }
            *replySize = sizeof(uint32_t);
            if (*(uint32_t*)pCmdData != EFFECT_FEATURE_AUX_CHANNELS || !effect->aux_channels_on) {
                *(uint32_t*)pReplyData = -ENOSYS;
                ALOGV("PreProcessingFx_Command cmdCode Case: "
                      "EFFECT_CMD_SET_FEATURE_CONFIG: ERROR\n"
                      "CmdData %d effect->aux_channels_on %d",
                      *(uint32_t*)pCmdData, effect->aux_channels_on);
                break;
            }
            size_t i;
            for (i = 0; i < CHANNEL_CFG_CNT; i++) {
                if (memcmp((uint32_t*)pCmdData + 1, &sDualMicConfigs[i],
                           sizeof(channel_config_t)) == 0) {
                    break;
                }
            }
            if (i == CHANNEL_CFG_CNT) {
                *(uint32_t*)pReplyData = -EINVAL;
                ALOGW("PreProcessingFx_Command EFFECT_CMD_SET_FEATURE_CONFIG invalid config"
                      "[%08x].[%08x]",
                      *((uint32_t*)pCmdData + 1), *((uint32_t*)pCmdData + 2));
            } else {
                effect->cur_channel_config = i;
                *(uint32_t*)pReplyData = 0;
                ALOGV("PreProcessingFx_Command EFFECT_CMD_SET_FEATURE_CONFIG New config"
                      "[%08x].[%08x]",
                      sDualMicConfigs[i].main_channels, sDualMicConfigs[i].aux_channels);
            }
        } break;
#endif
        default:
            return -EINVAL;
    }
    return 0;
}

int PreProcessingFx_GetDescriptor(effect_handle_t self, effect_descriptor_t* pDescriptor) {
    preproc_effect_t* effect = (preproc_effect_t*)self;

    if (effect == NULL || pDescriptor == NULL) {
        return -EINVAL;
    }

    *pDescriptor = *sDescriptors[effect->procId];

    return 0;
}

int PreProcessingFx_ProcessReverse(effect_handle_t self, audio_buffer_t* inBuffer,
                                   audio_buffer_t* outBuffer) {
    preproc_effect_t* effect = (preproc_effect_t*)self;

    if (effect == NULL) {
        ALOGW("PreProcessingFx_ProcessReverse() ERROR effect == NULL");
        return -EINVAL;
    }
    preproc_session_t* session = (preproc_session_t*)effect->session;

    if (inBuffer == NULL || inBuffer->raw == NULL) {
        ALOGW("PreProcessingFx_ProcessReverse() ERROR bad pointer");
        return -EINVAL;
    }

    if (inBuffer->frameCount != outBuffer->frameCount) {
        ALOGW("inBuffer->frameCount %zu is not equal to outBuffer->frameCount %zu",
              inBuffer->frameCount, outBuffer->frameCount);
        return -EINVAL;
    }

    if (inBuffer->frameCount != session->frameCount) {
        ALOGW("inBuffer->frameCount %zu != %zu representing 10ms at sampling rate %d",
              inBuffer->frameCount, session->frameCount, session->samplingRate);
        return -EINVAL;
    }

    session->revProcessedMsk |= (1 << effect->procId);

    //    ALOGV("PreProcessingFx_ProcessReverse In %d frames revEnabledMsk %08x revProcessedMsk
    //    %08x",
    //         inBuffer->frameCount, session->revEnabledMsk, session->revProcessedMsk);

    if ((session->revProcessedMsk & session->revEnabledMsk) == session->revEnabledMsk) {
        effect->session->revProcessedMsk = 0;
        if (int status = effect->session->apm->ProcessReverseStream(
                    (const int16_t* const)inBuffer->s16,
                    (const webrtc::StreamConfig)effect->session->revConfig,
                    (const webrtc::StreamConfig)effect->session->revConfig,
                    (int16_t* const)outBuffer->s16);
            status != 0) {
            ALOGE("Process Reverse Stream failed with error %d\n", status);
            return status;
        }
        return 0;
    } else {
        return -ENODATA;
    }
}

// effect_handle_t interface implementation for effect
const struct effect_interface_s sEffectInterface = {
        PreProcessingFx_Process, PreProcessingFx_Command, PreProcessingFx_GetDescriptor, NULL};

const struct effect_interface_s sEffectInterfaceReverse = {
        PreProcessingFx_Process, PreProcessingFx_Command, PreProcessingFx_GetDescriptor,
        PreProcessingFx_ProcessReverse};

//------------------------------------------------------------------------------
// Effect Library Interface Implementation
//------------------------------------------------------------------------------

int PreProcessingLib_Create(const effect_uuid_t* uuid, int32_t sessionId, int32_t ioId,
                            effect_handle_t* pInterface) {
    ALOGV("EffectCreate: uuid: %08x session %d IO: %d", uuid->timeLow, sessionId, ioId);

    int status;
    const effect_descriptor_t* desc;
    preproc_session_t* session;
    uint32_t procId;

    if (PreProc_Init() != 0) {
        return sInitStatus;
    }
    desc = PreProc_GetDescriptor(uuid);
    if (desc == NULL) {
        ALOGW("EffectCreate: fx not found uuid: %08x", uuid->timeLow);
        return -EINVAL;
    }
    procId = UuidToProcId(&desc->type);

    session = PreProc_GetSession(procId, sessionId, ioId);
    if (session == NULL) {
        ALOGW("EffectCreate: no more session available");
        return -EINVAL;
    }

    status = Session_CreateEffect(session, procId, pInterface);

    if (status < 0 && session->createdMsk == 0) {
        session->id = 0;
    }
    return status;
}

int PreProcessingLib_Release(effect_handle_t interface) {
    ALOGV("EffectRelease start %p", interface);
    if (PreProc_Init() != 0) {
        return sInitStatus;
    }

    preproc_effect_t* fx = (preproc_effect_t*)interface;

    if (fx->session->id == 0) {
        return -EINVAL;
    }
    return Session_ReleaseEffect(fx->session, fx);
}

int PreProcessingLib_GetDescriptor(const effect_uuid_t* uuid, effect_descriptor_t* pDescriptor) {
    if (pDescriptor == NULL || uuid == NULL) {
        return -EINVAL;
    }

    const effect_descriptor_t* desc = PreProc_GetDescriptor(uuid);
    if (desc == NULL) {
        ALOGV("PreProcessingLib_GetDescriptor() not found");
        return -EINVAL;
    }

    ALOGV("PreProcessingLib_GetDescriptor() got fx %s", desc->name);

    *pDescriptor = *desc;
    return 0;
}

// This is the only symbol that needs to be exported
__attribute__((visibility("default"))) audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = {
        .tag = AUDIO_EFFECT_LIBRARY_TAG,
        .version = EFFECT_LIBRARY_API_VERSION,
        .name = "Audio Preprocessing Library",
        .implementor = "The Android Open Source Project",
        .create_effect = PreProcessingLib_Create,
        .release_effect = PreProcessingLib_Release,
        .get_descriptor = PreProcessingLib_GetDescriptor};

};  // extern "C"
