hal: Add support for Fluence Far Field Voice

Add support for 6mic Fluence pre-processing of
captured stream using Far Field Voice (FFV) algorithm.

Change-Id: I7a643014f3e090482db7772dce2fc812fd469b2d
diff --git a/configure.ac b/configure.ac
index 978d235..9a17458 100644
--- a/configure.ac
+++ b/configure.ac
@@ -115,6 +115,7 @@
 AM_CONDITIONAL([AUDIO_PARSER], [test x$AUDIO_FEATURE_ENABLED_PARSER = xtrue])
 AM_CONDITIONAL([DTSHD_PARSER], [test x$AUDIO_FEATURE_ENABLED_DTSHD_PARSER = xtrue])
 AM_CONDITIONAL([QAP], [test x$AUDIO_FEATURE_ENABLED_QAP = xtrue])
+AM_CONDITIONAL([AUDIO_HW_FFV], [test x$AUDIO_FEATURE_ENABLED_FFV = xtrue])
 
 AC_CONFIG_FILES([ \
         Makefile \
diff --git a/hal/Makefile.am b/hal/Makefile.am
index 1d60f2d..0096bf7 100644
--- a/hal/Makefile.am
+++ b/hal/Makefile.am
@@ -189,6 +189,12 @@
 c_sources += audio_extn/hw_loopback.c
 endif
 
+if AUDIO_HW_FFV
+AM_CFLAGS += -DFFV_ENABLED \
+             -I $(PKG_CONFIG_SYSROOT_DIR)/usr/include/ffv/
+c_sources += audio_extn/ffv.c
+endif
+
 h_sources = audio_extn/audio_defs.h \
             audio_extn/audio_extn.h \
             audio_hw.h \
diff --git a/hal/audio_extn/audio_extn.c b/hal/audio_extn/audio_extn.c
index 84de66f..92b7c4f 100644
--- a/hal/audio_extn/audio_extn.c
+++ b/hal/audio_extn/audio_extn.c
@@ -838,6 +838,7 @@
    if (adev->offload_effects_set_parameters != NULL)
        adev->offload_effects_set_parameters(parms);
    audio_extn_set_aptx_dec_bt_addr(adev, parms);
+   audio_extn_ffv_set_parameters(adev, parms);
 }
 
 void audio_extn_get_parameters(const struct audio_device *adev,
@@ -1254,6 +1255,8 @@
     ssr_supported = audio_extn_ssr_check_usecase(in);
     if (ssr_supported) {
         return audio_extn_ssr_set_usecase(in, config, update_params);
+    } else if (audio_extn_ffv_check_usecase(in)) {
+        return audio_extn_ffv_set_usecase(in);
     } else {
         return audio_extn_set_multichannel_mask(adev, in, config,
                                                 update_params);
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index 117d270..94a6ba7 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -973,4 +973,44 @@
 {
 }
 #endif
+
+#ifndef FFV_ENABLED
+#define audio_extn_ffv_init(adev) (0)
+#define audio_extn_ffv_deinit() (0)
+#define audio_extn_ffv_check_usecase(in) (0)
+#define audio_extn_ffv_set_usecase(in) (0)
+#define audio_extn_ffv_stream_init(in) (0)
+#define audio_extn_ffv_stream_deinit() (0)
+#define audio_extn_ffv_update_enabled() (0)
+#define audio_extn_ffv_get_enabled() (0)
+#define audio_extn_ffv_read(stream, buffer, bytes) (0)
+#define audio_extn_ffv_set_parameters(adev, parms) (0)
+#define audio_extn_ffv_get_stream() (0)
+#define audio_extn_ffv_update_pcm_config(config) (0)
+#define audio_extn_ffv_init_ec_ref_loopback(adev, snd_device) (0)
+#define audio_extn_ffv_deinit_ec_ref_loopback(adev, snd_device) (0)
+#define audio_extn_ffv_check_and_append_ec_ref_dev(device_name) (0)
+#define audio_extn_ffv_get_capture_snd_device() (0)
+#else
+int32_t audio_extn_ffv_init(struct audio_device *adev);
+int32_t audio_extn_ffv_deinit();
+bool audio_extn_ffv_check_usecase(struct stream_in *in);
+int audio_extn_ffv_set_usecase(struct stream_in *in);
+int32_t audio_extn_ffv_stream_init(struct stream_in *in);
+int32_t audio_extn_ffv_stream_deinit();
+void audio_extn_ffv_update_enabled();
+bool audio_extn_ffv_get_enabled();
+int32_t audio_extn_ffv_read(struct audio_stream_in *stream,
+                       void *buffer, size_t bytes);
+void audio_extn_ffv_set_parameters(struct audio_device *adev,
+                                   struct str_parms *parms);
+struct stream_in *audio_extn_ffv_get_stream();
+void audio_extn_ffv_update_pcm_config(struct pcm_config *config);
+int audio_extn_ffv_init_ec_ref_loopback(struct audio_device *adev,
+                                        snd_device_t snd_device);
+int audio_extn_ffv_deinit_ec_ref_loopback(struct audio_device *adev,
+                                          snd_device_t snd_device);
+void audio_extn_ffv_check_and_append_ec_ref_dev(char *device_name);
+snd_device_t audio_extn_ffv_get_capture_snd_device();
+#endif
 #endif /* AUDIO_EXTN_H */
diff --git a/hal/audio_extn/ffv.c b/hal/audio_extn/ffv.c
new file mode 100644
index 0000000..fa55b5a
--- /dev/null
+++ b/hal/audio_extn/ffv.c
@@ -0,0 +1,891 @@
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.
+ *   * Neither the name of The Linux Foundation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_TAG "audio_hw_ffv"
+/*#define LOG_NDEBUG 0*/
+#define LOG_NDDEBUG 0
+/*#define VERY_VERY_VERBOSE_LOGGING*/
+#ifdef VERY_VERY_VERBOSE_LOGGING
+#define ALOGVV ALOGV
+#else
+#define ALOGVV(a...) do { } while(0)
+#endif
+
+#include <errno.h>
+#include <cutils/properties.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <cutils/str_parms.h>
+#include <cutils/log.h>
+#include <pthread.h>
+#include <sys/resource.h>
+
+#include "audio_hw.h"
+#include "platform.h"
+#include "platform_api.h"
+
+#include "ffv_interface.h"
+
+#define AUDIO_PARAMETER_FFV_MODE_ON "ffvOn"
+#define AUDIO_PARAMETER_FFV_SPLIT_EC_REF_DATA "ffv_split_ec_ref_data"
+#define AUDIO_PARAMETER_FFV_EC_REF_CHANNEL_COUNT "ffv_ec_ref_channel_count"
+#define AUDIO_PARAMETER_FFV_EC_REF_DEVICE "ffv_ec_ref_dev"
+#define AUDIO_PARAMETER_FFV_CHANNEL_INDEX "ffv_channel_index"
+
+#define FFV_LIB "libffv.so"
+#define FFV_CONFIG_FILE_PATH "/etc/BF_1out.cfg"
+#define FFV_SAMPLING_RATE_16000 16000
+#define FFV_EC_REF_LOOPBACK_DEVICE_MONO "ec-ref-loopback-mono"
+#define FFV_EC_REF_LOOPBACK_DEVICE_STEREO "ec-ref-loopback-stereo"
+
+#define FFV_CHANNEL_MODE_MONO 1
+#define FFV_CHANNEL_MODE_STEREO 2
+#define FFV_CHANNEL_MODE_HEX 6
+#define FFV_CHANNEL_MODE_OCT 8
+
+#define FFV_PCM_BUFFER_DURATION_MS 160
+#define FFV_PCM_PERIOD_COUNT (8)
+#define FFV_PCM_PERIOD_SIZE \
+    ((((FFV_SAMPLING_RATE_16000 * FFV_PCM_BUFFER_DURATION_MS) \
+       /(FFV_PCM_PERIOD_COUNT * 1000)) + 0x1f) & ~0x1f)
+
+#define ALIGN(number, align) \
+        ((number + align - 1) & ~(align - 1))
+#define CALCULATE_PERIOD_SIZE(duration_ms, sample_rate, period_cnt, align) \
+       (ALIGN(((sample_rate * duration_ms) /(period_cnt * 1000)), align))
+
+#define FFV_PCM_MAX_RETRY 10
+#define FFV_PCM_SLEEP_WAIT 1000
+
+#define DLSYM(handle, name, err) \
+do {\
+    const char* error; \
+    *(void**)&name##_fn = dlsym(handle, #name);\
+    if ((error = dlerror())) {\
+        ALOGE("%s: dlsym failed for %s error %s", __func__, #name, error);\
+        err = -ENODEV;\
+    }\
+} while(0)\
+
+/* uncomment to collect pcm dumps */
+//#define FFV_PCM_DUMP
+
+static FfvStatusType (*ffv_init_fn)(void** handle, int num_tx_in_ch,
+    int num_out_ch, int num_ec_ref_ch, int frame_len, int sample_rate,
+    const char *config_file_name, char *svaModelBuffer,
+    uint32_t svaModelSize, int* totMemSize);
+static void (*ffv_deinit_fn)(void* handle);
+static void (*ffv_process_fn)(void *handle, const int16_t *in_pcm,
+    int16_t *out_pcm, const int16_t *ec_ref_pcm);
+static int (*ffv_read_fn)(void* handle, int16_t *buf_pcm,
+    int max_buf_len);
+static FfvStatusType (*ffv_get_param_fn)(void *handle, char *params_buffer_ptr,
+    int param_id, int buffer_size, int *param_size_ptr);
+static FfvStatusType (*ffv_set_param_fn)(void *handle, char *params_buffer_ptr,
+    int param_id, int param_size);
+static FfvStatusType (*ffv_register_event_callback_fn)(void *handle,
+    ffv_event_callback_fn_t *fun_ptr);
+
+struct ffvmodule {
+    void *ffv_lib_handle;
+    unsigned char *in_buf;
+    unsigned int in_buf_size;
+    unsigned char *ec_ref_buf;
+    unsigned int ec_ref_buf_size;
+    unsigned char *split_in_buf;
+    unsigned int split_in_buf_size;
+    unsigned char *out_buf;
+    unsigned int out_buf_size;
+
+    struct pcm_config capture_config;
+    struct pcm_config out_config;
+    struct pcm_config ec_ref_config;
+
+    int ec_ref_pcm_id;
+    struct pcm *ec_ref_pcm;
+    int ec_ref_ch_cnt;
+    audio_devices_t ec_ref_dev;
+    bool split_ec_ref_data;
+
+    bool is_ffv_enabled;
+    bool buffers_allocated;
+    struct stream_in *in;
+    bool is_ffvmode_on;
+    void *handle;
+    pthread_mutex_t init_lock;
+    bool capture_started;
+    int target_ch_idx;
+
+#ifdef FFV_PCM_DUMP
+    FILE *fp_input;
+    FILE *fp_ecref;
+    FILE *fp_split_input;
+    FILE *fp_output;
+#endif
+};
+
+static struct ffvmodule ffvmod = {
+    .ffv_lib_handle = NULL,
+    .in_buf = NULL,
+    .in_buf_size = 0,
+    .ec_ref_buf = NULL,
+    .ec_ref_buf_size = 0,
+    .split_in_buf = NULL,
+    .split_in_buf_size = 0,
+    .out_buf = NULL,
+    .out_buf_size = 0,
+
+    .ec_ref_pcm = NULL,
+    .ec_ref_ch_cnt = 1,
+    .ec_ref_dev = AUDIO_DEVICE_OUT_SPEAKER,
+    .is_ffv_enabled = false,
+    .buffers_allocated = false,
+    .in = NULL,
+    .is_ffvmode_on = false,
+    .handle = NULL,
+    .capture_started = false,
+    .target_ch_idx = -1,
+};
+
+static struct pcm_config ffv_pcm_config = {
+    .channels = FFV_CHANNEL_MODE_MONO,
+    .rate = FFV_SAMPLING_RATE_16000,
+    .period_size = FFV_PCM_PERIOD_SIZE,
+    .period_count = FFV_PCM_PERIOD_COUNT,
+    .format = PCM_FORMAT_S16_LE,
+};
+
+static int32_t ffv_init_lib()
+{
+    int status = 0;
+
+    if (ffvmod.ffv_lib_handle) {
+        ALOGE("%s: FFV library is already initialized", __func__);
+        return 0;
+    }
+
+    ffvmod.ffv_lib_handle = dlopen(FFV_LIB, RTLD_NOW);
+    if (!ffvmod.ffv_lib_handle) {
+        ALOGE("%s: Unable to open %s, error %s", __func__, FFV_LIB,
+            dlerror());
+        status = -ENOENT;
+        goto exit;
+    }
+
+    dlerror(); /* clear errors */
+    DLSYM(ffvmod.ffv_lib_handle, ffv_init, status);
+    if (status)
+        goto exit;
+    DLSYM(ffvmod.ffv_lib_handle, ffv_deinit, status);
+    if (status)
+        goto exit;
+    DLSYM(ffvmod.ffv_lib_handle, ffv_process, status);
+    if (status)
+        goto exit;
+    DLSYM(ffvmod.ffv_lib_handle, ffv_read, status);
+    if (status)
+        goto exit;
+    DLSYM(ffvmod.ffv_lib_handle, ffv_get_param, status);
+    if (status)
+        goto exit;
+    DLSYM(ffvmod.ffv_lib_handle, ffv_set_param, status);
+    if (status)
+        goto exit;
+    DLSYM(ffvmod.ffv_lib_handle, ffv_register_event_callback, status);
+    if (status)
+        goto exit;
+
+    return status;
+
+exit:
+    if (ffvmod.ffv_lib_handle)
+        dlclose(ffvmod.ffv_lib_handle);
+    ffvmod.ffv_lib_handle = NULL;
+
+    return status;
+}
+
+static int deallocate_buffers()
+{
+    if (ffvmod.in_buf) {
+        free(ffvmod.in_buf);
+        ffvmod.in_buf = NULL;
+    }
+
+    if (ffvmod.split_in_buf) {
+        free(ffvmod.split_in_buf);
+        ffvmod.split_in_buf = NULL;
+    }
+
+    if (ffvmod.ec_ref_buf) {
+        free(ffvmod.ec_ref_buf);
+        ffvmod.ec_ref_buf = NULL;
+    }
+
+    if (ffvmod.out_buf) {
+        free(ffvmod.out_buf);
+        ffvmod.out_buf = NULL;
+    }
+
+    ffvmod.buffers_allocated = false;
+    return 0;
+}
+
+static int allocate_buffers()
+{
+    int status = 0;
+
+    /* in_buf - buffer read from capture session */
+    ffvmod.in_buf_size = ffvmod.capture_config.period_size * ffvmod.capture_config.channels *
+                              (pcm_format_to_bits(ffvmod.capture_config.format) >> 3);
+    ffvmod.in_buf = (unsigned char *)calloc(1, ffvmod.in_buf_size);
+    if (!ffvmod.in_buf) {
+        ALOGE("%s: ERROR. Can not allocate in buffer size %d", __func__, ffvmod.in_buf_size);
+        status = -ENOMEM;
+        goto error_exit;
+    }
+    ALOGD("%s: Allocated in buffer size bytes =%d",
+          __func__, ffvmod.in_buf_size);
+
+    /* ec_buf - buffer read from ec ref capture session */
+    ffvmod.ec_ref_buf_size = ffvmod.ec_ref_config.period_size * ffvmod.ec_ref_config.channels *
+                              (pcm_format_to_bits(ffvmod.ec_ref_config.format) >> 3);
+    ffvmod.ec_ref_buf = (unsigned char *)calloc(1, ffvmod.ec_ref_buf_size);
+    if (!ffvmod.ec_ref_buf) {
+        ALOGE("%s: ERROR. Can not allocate ec ref buffer size %d",
+               __func__, ffvmod.ec_ref_buf_size);
+        status = -ENOMEM;
+        goto error_exit;
+    }
+    ALOGD("%s: Allocated ec ref buffer size bytes =%d",
+          __func__, ffvmod.ec_ref_buf_size);
+
+    if (ffvmod.split_ec_ref_data) {
+        ffvmod.split_in_buf_size = ffvmod.in_buf_size - ffvmod.ec_ref_buf_size;
+        ffvmod.split_in_buf = (unsigned char *)calloc(1, ffvmod.split_in_buf_size);
+        if (!ffvmod.split_in_buf) {
+            ALOGE("%s: ERROR. Can not allocate split in buffer size %d",
+                   __func__, ffvmod.split_in_buf_size);
+            status = -ENOMEM;
+            goto error_exit;
+        }
+        ALOGD("%s: Allocated split in buffer size bytes =%d",
+               __func__, ffvmod.split_in_buf_size);
+    }
+
+    /* out_buf - output buffer from FFV + SVA library */
+    ffvmod.out_buf_size = ffvmod.out_config.period_size * ffvmod.out_config.channels *
+                              (pcm_format_to_bits(ffvmod.out_config.format) >> 3);
+    ffvmod.out_buf = (unsigned char *)calloc(1, ffvmod.out_buf_size);
+    if (!ffvmod.out_buf) {
+        ALOGE("%s: ERROR. Can not allocate out buffer size %d", __func__, ffvmod.out_buf_size);
+        status = -ENOMEM;
+        goto error_exit;
+    }
+    ALOGD("%s: Allocated out buffer size bytes =%d",
+          __func__, ffvmod.out_buf_size);
+
+    ffvmod.buffers_allocated = true;
+    return 0;
+
+error_exit:
+    deallocate_buffers();
+    return status;
+}
+
+void audio_extn_ffv_update_enabled()
+{
+    char ffv_enabled[PROPERTY_VALUE_MAX] = "false";
+
+    property_get("ro.qc.sdk.audio.ffv", ffv_enabled, "0");
+    if (!strncmp("true", ffv_enabled, 4)) {
+        ALOGD("%s: ffv is supported", __func__);
+        ffvmod.is_ffv_enabled = true;
+    } else {
+        ALOGD("%s: ffv is not supported", __func__);
+        ffvmod.is_ffv_enabled = false;
+    }
+}
+
+bool audio_extn_ffv_get_enabled()
+{
+    ALOGV("%s: is_ffv_enabled:%d is_ffvmode_on:%d ", __func__, ffvmod.is_ffv_enabled, ffvmod.is_ffvmode_on);
+
+    if(ffvmod.is_ffv_enabled && ffvmod.is_ffvmode_on)
+        return true;
+
+    return false;
+}
+
+bool  audio_extn_ffv_check_usecase(struct stream_in *in) {
+    int ret = false;
+    int channel_count = audio_channel_count_from_in_mask(in->channel_mask);
+    audio_devices_t devices = in->device;
+    audio_source_t source = in->source;
+
+    if ((audio_extn_ffv_get_enabled()) &&
+            (channel_count == 1) &&
+            (AUDIO_SOURCE_MIC == source) &&
+            ((AUDIO_DEVICE_IN_BUILTIN_MIC == devices) || (AUDIO_DEVICE_IN_BACK_MIC == devices)) &&
+            (in->format == AUDIO_FORMAT_PCM_16_BIT) &&
+            (in->sample_rate == FFV_SAMPLING_RATE_16000)) {
+        in->config.channels = channel_count;
+        in->config.period_count = FFV_PCM_PERIOD_COUNT;
+        in->config.period_size = FFV_PCM_PERIOD_SIZE;
+        ALOGD("%s: FFV enabled", __func__);
+        ret = true;
+    }
+    return ret;
+}
+
+int audio_extn_ffv_set_usecase(struct stream_in *in)
+{
+    int ret = -EINVAL;
+
+    if (audio_extn_ffv_check_usecase(in)) {
+        if (!audio_extn_ffv_stream_init(in)) {
+            ALOGD("%s: Created FFV session succesfully", __func__);
+            ret = 0;
+        } else {
+            ALOGE("%s: Unable to start FFV record session", __func__);
+        }
+    }
+    return ret;
+}
+
+struct stream_in *audio_extn_ffv_get_stream()
+{
+    return ffvmod.in;
+}
+
+void audio_extn_ffv_update_pcm_config(struct pcm_config *config)
+{
+    config->channels = ffvmod.capture_config.channels;
+    config->period_count = ffvmod.capture_config.period_count;
+    config->period_size = ffvmod.capture_config.period_size;
+}
+
+int32_t audio_extn_ffv_init(struct audio_device *adev)
+{
+    int ret = 0;
+
+    ret = ffv_init_lib();
+    if (ret)
+        ALOGE("%s: ERROR. ffv_init_lib ret %d", __func__, ret);
+
+    pthread_mutex_init(&ffvmod.init_lock, NULL);
+    return ret;
+}
+
+int32_t audio_extn_ffv_deinit()
+{
+    pthread_mutex_destroy(&ffvmod.init_lock);
+    if (ffvmod.ffv_lib_handle) {
+        dlclose(ffvmod.ffv_lib_handle);
+        ffvmod.ffv_lib_handle = NULL;
+    }
+    return 0;
+}
+
+int32_t audio_extn_ffv_stream_init(struct stream_in *in)
+{
+    uint32_t ret = -EINVAL;
+    int num_tx_in_ch, num_out_ch, num_ec_ref_ch;
+    int frame_len;
+    int sample_rate;
+    const char *config_file_path = FFV_CONFIG_FILE_PATH;
+    int total_mem_size;
+    FfvStatusType status_type;
+    const char *sm_buffer = "DISABLE_KEYWORD_DETECTION";
+    ffv_target_channel_index_param_t ch_index_param;
+    char *params_buffer_ptr = NULL;
+    int param_size = 0;
+    int param_id;
+
+    if (!audio_extn_ffv_get_enabled()) {
+        ALOGE("Rejecting FFV -- init is called without enabling FFV");
+        goto fail;
+    }
+
+    if (ffvmod.handle != NULL) {
+        ALOGV("%s: reinitializing ffv library", __func__);
+        audio_extn_ffv_stream_deinit();
+    }
+
+    ffvmod.capture_config = ffv_pcm_config;
+    ffvmod.ec_ref_config = ffv_pcm_config;
+    ffvmod.out_config = ffv_pcm_config;
+    /* configure capture session with 6/8 channels */
+    ffvmod.capture_config.channels = ffvmod.split_ec_ref_data ?
+        FFV_CHANNEL_MODE_OCT : FFV_CHANNEL_MODE_HEX;
+    ffvmod.capture_config.period_size =
+                   CALCULATE_PERIOD_SIZE(FFV_PCM_BUFFER_DURATION_MS,
+                                         ffvmod.capture_config.rate,
+                                         FFV_PCM_PERIOD_COUNT, 32);
+
+    /* Update channels with ec ref channel count */
+    ffvmod.ec_ref_config.channels = ffvmod.ec_ref_ch_cnt;
+    ffvmod.ec_ref_config.period_size =
+               CALCULATE_PERIOD_SIZE(FFV_PCM_BUFFER_DURATION_MS,
+                                     ffvmod.ec_ref_config.rate,
+                                     FFV_PCM_PERIOD_COUNT, 32);
+    ret = allocate_buffers();
+    if (ret)
+        goto fail;
+
+    num_ec_ref_ch = ffvmod.ec_ref_config.channels;
+    num_tx_in_ch = ffvmod.split_ec_ref_data ?
+      (ffvmod.capture_config.channels - num_ec_ref_ch) :
+      ffvmod.capture_config.channels;
+    num_out_ch = ffvmod.out_config.channels;
+    frame_len = ffvmod.capture_config.period_size;
+    sample_rate = ffvmod.capture_config.rate;
+
+    ALOGD("%s: ec_ref_ch %d, tx_in_ch %d, out_ch %d, frame_len %d, sample_rate %d",
+           __func__, num_ec_ref_ch, num_tx_in_ch, num_out_ch, frame_len, sample_rate);
+    ALOGD("%s: config file path %s", __func__, config_file_path);
+    status_type = ffv_init_fn(&ffvmod.handle, num_tx_in_ch, num_out_ch, num_ec_ref_ch,
+                      frame_len, sample_rate, config_file_path, sm_buffer, 0,
+                      &total_mem_size);
+    if (status_type) {
+        ALOGE("%s: ERROR. ffv_init returned %d", __func__, status_type);
+        ret = -EINVAL;
+        goto fail;
+    }
+    ALOGD("%s: ffv_init success %p", __func__, ffvmod.handle);
+
+    /* set target channel index if received as part of setparams */
+    if (ffvmod.target_ch_idx != -1) {
+        ALOGD("%s: target channel index %d", __func__, ffvmod.target_ch_idx);
+        ch_index_param.target_chan_idx = ffvmod.target_ch_idx;
+        params_buffer_ptr = (char *)&ch_index_param;
+        param_size = sizeof(ch_index_param);
+        param_id = FFV_TARGET_CHANNEL_INDEX_PARAM;
+        status_type = ffv_set_param_fn(ffvmod.handle, params_buffer_ptr,
+                                       param_id, param_size);
+        if (status_type) {
+            ALOGE("%s: ERROR. ffv_set_param_fn ret %d", __func__, status_type);
+            ret = -EINVAL;
+            goto fail;
+        }
+    }
+
+    ffvmod.in = in;
+
+#ifdef FFV_PCM_DUMP
+    if (!ffvmod.fp_input) {
+        ALOGD("%s: Opening input dump file \n", __func__);
+        ffvmod.fp_input = fopen("/data/misc/audio/ffv_input.pcm", "wb");
+    }
+    if (!ffvmod.fp_ecref) {
+        ALOGD("%s: Opening ecref dump file \n", __func__);
+        ffvmod.fp_ecref = fopen("/data/misc/audio/ffv_ecref.pcm", "wb");
+    }
+    if (!ffvmod.fp_split_input && ffvmod.split_ec_ref_data) {
+        ALOGD("%s: Opening split input dump file \n", __func__);
+        ffvmod.fp_split_input = fopen("/data/misc/audio/ffv_split_input.pcm", "wb");
+    }
+    if (!ffvmod.fp_output) {
+        ALOGD("%s: Opening output dump file \n", __func__);
+        ffvmod.fp_output = fopen("/data/misc/audio/ffv_output.pcm", "wb");
+    }
+#endif
+    ALOGV("%s: exit", __func__);
+    return 0;
+
+fail:
+    audio_extn_ffv_stream_deinit();
+    return ret;
+}
+
+int32_t audio_extn_ffv_stream_deinit()
+{
+    ALOGV("%s: entry", __func__);
+
+#ifdef FFV_PCM_DUMP
+    if (ffvmod.fp_input)
+        fclose(ffvmod.fp_input);
+
+    if (ffvmod.fp_ecref)
+        fclose(ffvmod.fp_ecref);
+
+    if (ffvmod.fp_split_input)
+        fclose(ffvmod.fp_split_input);
+
+    if (ffvmod.fp_output)
+        fclose(ffvmod.fp_output);
+#endif
+
+    if (ffvmod.handle)
+        ffv_deinit_fn(ffvmod.handle);
+
+    if (ffvmod.buffers_allocated)
+        deallocate_buffers();
+
+    ffvmod.in = NULL;
+    ALOGV("%s: exit", __func__);
+    return 0;
+}
+
+snd_device_t audio_extn_ffv_get_capture_snd_device()
+{
+    if (ffvmod.capture_config.channels == FFV_CHANNEL_MODE_OCT) {
+        return SND_DEVICE_IN_HANDSET_8MIC;
+    } else if (ffvmod.capture_config.channels == FFV_CHANNEL_MODE_HEX) {
+        return SND_DEVICE_IN_HANDSET_6MIC;
+    } else {
+        ALOGE("%s: Invalid channels configured for capture", __func__);
+        return SND_DEVICE_NONE;
+    }
+}
+
+int audio_extn_ffv_init_ec_ref_loopback(struct audio_device *adev,
+                                        snd_device_t snd_device)
+{
+    struct audio_usecase *uc_info_tx = NULL;
+    snd_device_t in_snd_device;
+    char *params_buffer_ptr = NULL;
+    int param_id = FFV_RESET_AEC_PARAM;
+    int param_size = 0;
+    FfvStatusType status_type;
+    int ret = 0;
+
+    ALOGV("%s: entry", __func__);
+    /* notify library to reset AEC during each start */
+    status_type = ffv_set_param_fn(ffvmod.handle, params_buffer_ptr,
+                      param_id, param_size);
+    if (status_type) {
+        ALOGE("%s: ERROR. ffv_set_param_fn ret %d", __func__, status_type);
+        return -EINVAL;
+    }
+
+    if (ffvmod.split_ec_ref_data) {
+        ALOGV("%s: Ignore ec ref loopback init", __func__);
+        return 0;
+    }
+
+    in_snd_device = platform_get_ec_ref_loopback_snd_device(ffvmod.ec_ref_ch_cnt);
+    uc_info_tx = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
+    if (!uc_info_tx) {
+        return -ENOMEM;
+    }
+
+    pthread_mutex_lock(&ffvmod.init_lock);
+    uc_info_tx->id = USECASE_AUDIO_EC_REF_LOOPBACK;
+    uc_info_tx->type = PCM_CAPTURE;
+    uc_info_tx->in_snd_device = in_snd_device;
+    uc_info_tx->out_snd_device = SND_DEVICE_NONE;
+    ffvmod.ec_ref_pcm = NULL;
+    list_add_tail(&adev->usecase_list, &uc_info_tx->list);
+    enable_snd_device(adev, in_snd_device);
+    enable_audio_route(adev, uc_info_tx);
+
+    ffvmod.ec_ref_pcm_id = platform_get_pcm_device_id(uc_info_tx->id, PCM_CAPTURE);
+    if (ffvmod.ec_ref_pcm_id < 0) {
+        ALOGE("%s: Invalid pcm device for usecase (%d)",
+              __func__, uc_info_tx->id);
+        ret = -ENODEV;
+        goto exit;
+    }
+
+    ffvmod.ec_ref_pcm = pcm_open(adev->snd_card,
+                             ffvmod.ec_ref_pcm_id,
+                             PCM_IN, &ffvmod.ec_ref_config);
+    if (ffvmod.ec_ref_pcm && !pcm_is_ready(ffvmod.ec_ref_pcm)) {
+        ALOGE("%s: %s", __func__, pcm_get_error(ffvmod.ec_ref_pcm));
+        ret = -EIO;
+        goto exit;
+    }
+
+    if (pcm_prepare(ffvmod.ec_ref_pcm) < 0) {
+        ALOGE("%s: pcm prepare for ec ref loopback failed", __func__);
+        ret = -EINVAL;
+    }
+
+    ffvmod.capture_started = false;
+    pthread_mutex_unlock(&ffvmod.init_lock);
+    ALOGV("%s: exit", __func__);
+    return 0;
+
+exit:
+    if (ffvmod.ec_ref_pcm) {
+        pcm_close(ffvmod.ec_ref_pcm);
+        ffvmod.ec_ref_pcm = NULL;
+    }
+    list_remove(&uc_info_tx->list);
+    disable_snd_device(adev, in_snd_device);
+    disable_audio_route(adev, uc_info_tx);
+    free(uc_info_tx);
+    pthread_mutex_unlock(&ffvmod.init_lock);
+    return ret;
+}
+
+void audio_extn_ffv_append_ec_ref_dev_name(char *device_name)
+{
+    if (ffvmod.ec_ref_dev == AUDIO_DEVICE_OUT_AUX_LINE)
+        strlcat(device_name, " lineout",  DEVICE_NAME_MAX_SIZE);
+    ALOGD("%s: ec ref dev name %s", __func__, device_name);
+}
+
+int audio_extn_ffv_deinit_ec_ref_loopback(struct audio_device *adev,
+                                          snd_device_t snd_device)
+{
+    struct audio_usecase *uc_info_tx = NULL;
+    snd_device_t in_snd_device;
+    int ret = 0;
+
+    ALOGV("%s: entry", __func__);
+    if (ffvmod.split_ec_ref_data) {
+        ALOGV("%s: Ignore ec ref loopback init", __func__);
+        return 0;
+    }
+
+    in_snd_device = platform_get_ec_ref_loopback_snd_device(ffvmod.ec_ref_ch_cnt);
+    uc_info_tx = get_usecase_from_list(adev, USECASE_AUDIO_EC_REF_LOOPBACK);
+    pthread_mutex_lock(&ffvmod.init_lock);
+    if (ffvmod.ec_ref_pcm) {
+        pcm_close(ffvmod.ec_ref_pcm);
+        ffvmod.ec_ref_pcm = NULL;
+    }
+    disable_snd_device(adev, in_snd_device);
+    if (uc_info_tx) {
+        list_remove(&uc_info_tx->list);
+        disable_audio_route(adev, uc_info_tx);
+        free(uc_info_tx);
+    }
+    pthread_mutex_unlock(&ffvmod.init_lock);
+    ALOGV("%s: exit", __func__);
+    return ret;
+}
+
+int32_t audio_extn_ffv_read(struct audio_stream_in *stream,
+                       void *buffer, size_t bytes)
+{
+    int status = 0;
+    int16_t *in_ptr = NULL, *process_in_ptr = NULL, *process_out_ptr = NULL;
+    int16_t *process_ec_ref_ptr = NULL;
+    size_t in_buf_size, out_buf_size, bytes_to_copy;
+    int retry_num = 0;
+    int i, j, ch;
+    int total_in_ch, in_ch, ec_ref_ch;
+
+    if (!ffvmod.ffv_lib_handle) {
+        ALOGE("%s: ffv_lib_handle not initialized", __func__);
+        return -EINVAL;
+    }
+
+    if (!ffvmod.handle) {
+        ALOGE("%s: ffv module handle not initialized", __func__);
+        return -EINVAL;
+    }
+
+    if (!ffvmod.in || !ffvmod.in->pcm) {
+        ALOGE("%s: capture session not initiliazed", __func__);
+        return -EINVAL;
+    }
+
+    if (!ffvmod.split_ec_ref_data && !ffvmod.ec_ref_pcm) {
+        ALOGE("%s: ec ref session not initiliazed", __func__);
+        return -EINVAL;
+    }
+
+    if (!ffvmod.capture_started) {
+        /* pcm_start of capture and ec ref session before read to reduce drift */
+        pcm_start(ffvmod.in->pcm);
+        while (status && (retry_num < FFV_PCM_MAX_RETRY)) {
+            usleep(FFV_PCM_SLEEP_WAIT);
+            retry_num++;
+            ALOGI("%s: pcm_start retrying..status %d errno %d, retry cnt %d",
+                   __func__, status, errno, retry_num);
+            status = pcm_start(ffvmod.in->pcm);
+        }
+        if (status) {
+            ALOGE("%s: ERROR. pcm_start failed, returned status %d - %s",
+                  __func__, status, pcm_get_error(ffvmod.in->pcm));
+            return status;
+        }
+        retry_num = 0;
+
+        if (!ffvmod.split_ec_ref_data) {
+            pcm_start(ffvmod.ec_ref_pcm);
+            while (status && (retry_num < FFV_PCM_MAX_RETRY)) {
+                usleep(FFV_PCM_SLEEP_WAIT);
+                retry_num++;
+                ALOGI("%s: pcm_start retrying..status %d errno %d, retry cnt %d",
+                      __func__, status, errno, retry_num);
+                status = pcm_start(ffvmod.ec_ref_pcm);
+            }
+            if (status) {
+                ALOGE("%s: ERROR. pcm_start failed, returned status %d - %s",
+                       __func__, status, pcm_get_error(ffvmod.ec_ref_pcm));
+                return status;
+            }
+        }
+        ffvmod.capture_started = true;
+    }
+
+    ALOGVV("%s: pcm_read reading bytes=%d", __func__, ffvmod.in_buf_size);
+    status = pcm_read(ffvmod.in->pcm, ffvmod.in_buf, ffvmod.in_buf_size);
+    if (status) {
+        ALOGE("%s: pcm read failed status %d - %s", __func__, status,
+              pcm_get_error(ffvmod.in->pcm));
+        goto exit;
+    }
+    ALOGVV("%s: pcm_read done", __func__);
+
+    if (!ffvmod.split_ec_ref_data) {
+        /* read EC ref data */
+        ALOGVV("%s: ec ref pcm_read reading bytes=%d", __func__, ffvmod.ec_ref_buf_size);
+        status = pcm_read(ffvmod.ec_ref_pcm, ffvmod.ec_ref_buf, ffvmod.ec_ref_buf_size);
+        if (status) {
+            ALOGE("%s: ec ref pcm read failed status %d - %s", __func__, status,
+                   pcm_get_error(ffvmod.ec_ref_pcm));
+            goto exit;
+        }
+        ALOGVV("%s: ec ref pcm_read done", __func__);
+        process_in_ptr = (int16_t *)ffvmod.in_buf;
+        process_ec_ref_ptr = (int16_t *)ffvmod.ec_ref_buf;
+        in_buf_size = ffvmod.in_buf_size;
+    } else {
+        /* split input buffer into actual input channels and EC ref channels */
+        in_ptr = (int16_t *)ffvmod.in_buf;
+        process_in_ptr = (int16_t *)ffvmod.split_in_buf;
+        process_ec_ref_ptr = (int16_t *)ffvmod.ec_ref_buf;
+        total_in_ch = ffvmod.capture_config.channels;
+        ec_ref_ch = ffvmod.ec_ref_config.channels;
+        in_ch = total_in_ch - ec_ref_ch;
+        for (i = 0; i < ffvmod.capture_config.period_size; i++) {
+            for (ch = 0; ch < in_ch; ch++) {
+                process_in_ptr[i*in_ch+ch] =
+                          in_ptr[i*total_in_ch+ch];
+            }
+            for (ch = 0; ch < ec_ref_ch; ch++) {
+                process_ec_ref_ptr[i*ec_ref_ch+ch] =
+                          in_ptr[i*total_in_ch+in_ch+ch];
+            }
+        }
+        in_buf_size = ffvmod.split_in_buf_size;
+    }
+    process_out_ptr = (int16_t *)ffvmod.out_buf;
+
+    ffv_process_fn(ffvmod.handle, process_in_ptr,
+            process_out_ptr, process_ec_ref_ptr);
+    out_buf_size = ffvmod.out_buf_size;
+    bytes_to_copy = (bytes <= out_buf_size) ? bytes : out_buf_size;
+    memcpy(buffer, process_out_ptr, bytes_to_copy);
+    if (bytes_to_copy != out_buf_size)
+        ALOGD("%s: out buffer data dropped, copied %d bytes",
+               __func__, bytes_to_copy);
+
+#ifdef FFV_PCM_DUMP
+    if (ffvmod.fp_input)
+        fwrite(ffvmod.in_buf, 1, ffvmod.in_buf_size, ffvmod.fp_input);
+    if (ffvmod.fp_ecref)
+        fwrite(ffvmod.ec_ref_buf, 1, ffvmod.ec_ref_buf_size, ffvmod.fp_ecref);
+    if (ffvmod.fp_split_input)
+        fwrite(ffvmod.split_in_buf, 1, ffvmod.split_in_buf_size, ffvmod.fp_split_input);
+    if (ffvmod.fp_output)
+        fwrite(process_out_ptr, 1, bytes_to_copy, ffvmod.fp_output);
+#endif
+
+exit:
+    return status;
+}
+
+void audio_extn_ffv_set_parameters(struct audio_device *adev __unused,
+                                   struct str_parms *parms)
+{
+    int err;
+    int val;
+    int ret = 0;
+    char value[128];
+
+    /* FFV params are required to be set before start of recording */
+    if (!ffvmod.handle) {
+        ret = str_parms_get_str(parms, AUDIO_PARAMETER_FFV_MODE_ON, value,
+                                sizeof(value));
+        if (ret >= 0) {
+            str_parms_del(parms, AUDIO_PARAMETER_FFV_MODE_ON);
+            if (strcmp(value, "true") == 0) {
+                ALOGD("%s: Setting FFV mode to true", __func__);
+                ffvmod.is_ffvmode_on = true;
+            } else {
+                ALOGD("%s: Resetting FFV mode to false", __func__);
+                ffvmod.is_ffvmode_on = false;
+            }
+        }
+
+        ret = str_parms_get_str(parms, AUDIO_PARAMETER_FFV_SPLIT_EC_REF_DATA, value,
+                                sizeof(value));
+        if (ret >= 0) {
+            str_parms_del(parms, AUDIO_PARAMETER_FFV_SPLIT_EC_REF_DATA);
+            if (strcmp(value, "true") == 0) {
+                ALOGD("%s: ec ref is packed with mic captured data", __func__);
+                ffvmod.split_ec_ref_data = true;
+            } else {
+                ALOGD("%s: ec ref is captured separately", __func__);
+                ffvmod.split_ec_ref_data = false;
+            }
+        }
+        ret = str_parms_get_int(parms, AUDIO_PARAMETER_FFV_EC_REF_CHANNEL_COUNT, &val);
+        if (ret >= 0) {
+            str_parms_del(parms, AUDIO_PARAMETER_FFV_EC_REF_CHANNEL_COUNT);
+            if (val == 1) {
+                ALOGD("%s: mono ec ref", __func__);
+                ffvmod.ec_ref_ch_cnt = FFV_CHANNEL_MODE_MONO;
+            } else if (val == 2) {
+                ALOGD("%s: stereo ec ref", __func__);
+                ffvmod.ec_ref_ch_cnt = FFV_CHANNEL_MODE_STEREO;
+            } else {
+                ALOGE("%s: Invalid ec ref", __func__);
+            }
+        }
+
+        ret = str_parms_get_int(parms, AUDIO_PARAMETER_FFV_EC_REF_DEVICE, &val);
+        if (ret >= 0) {
+            str_parms_del(parms, AUDIO_PARAMETER_FFV_EC_REF_DEVICE);
+            if (val & AUDIO_DEVICE_OUT_SPEAKER) {
+                ALOGD("%s: capture ec ref from speaker", __func__);
+                ffvmod.ec_ref_dev = AUDIO_DEVICE_OUT_SPEAKER;
+            } else if (val & AUDIO_DEVICE_OUT_AUX_LINE) {
+                ALOGD("%s: capture ec ref from line out", __func__);
+                ffvmod.ec_ref_dev = AUDIO_DEVICE_OUT_AUX_LINE;
+            } else {
+                ALOGE("%s: Invalid ec ref out device", __func__);
+            }
+        }
+
+        ret = str_parms_get_int(parms, AUDIO_PARAMETER_FFV_CHANNEL_INDEX, &val);
+        if (ret >= 0) {
+            str_parms_del(parms, AUDIO_PARAMETER_FFV_CHANNEL_INDEX);
+            ALOGD("%s: set target chan index %d", __func__, val);
+            ffvmod.target_ch_idx = val;
+        }
+    }
+}
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index a7d36c6..32eb459 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -327,6 +327,8 @@
     [USECASE_AUDIO_PLAYBACK_INTERACTIVE_STREAM6] = "audio-interactive-stream6",
     [USECASE_AUDIO_PLAYBACK_INTERACTIVE_STREAM7] = "audio-interactive-stream7",
     [USECASE_AUDIO_PLAYBACK_INTERACTIVE_STREAM8] = "audio-interactive-stream8",
+
+    [USECASE_AUDIO_EC_REF_LOOPBACK] = "ec-ref-audio-capture"
 };
 
 static const audio_usecase_t offload_usecases[] = {
@@ -930,6 +932,11 @@
                                               "true-native-mode");
             adev->native_playback_enabled = true;
         }
+        if ((snd_device == SND_DEVICE_IN_HANDSET_6MIC) &&
+            (audio_extn_ffv_get_stream() == adev->active_input)) {
+            ALOGD("%s: init ec ref loopback", __func__);
+            audio_extn_ffv_init_ec_ref_loopback(adev, snd_device);
+        }
     }
     return 0;
 }
@@ -993,7 +1000,11 @@
             disable_asrc_mode(adev);
             audio_route_apply_and_update_path(adev->audio_route, "hph-lowpower-mode");
         }
-
+        if ((snd_device == SND_DEVICE_IN_HANDSET_6MIC) &&
+            (audio_extn_ffv_get_stream() == adev->active_input)) {
+            ALOGD("%s: deinit ec ref loopback", __func__);
+            audio_extn_ffv_deinit_ec_ref_loopback(adev, snd_device);
+        }
         audio_extn_dev_arbi_release(snd_device);
         audio_extn_sound_trigger_update_device_status(snd_device,
                                         ST_EVENT_SND_DEVICE_FREE);
@@ -2049,14 +2060,17 @@
     int ret = 0;
     struct audio_usecase *uc_info;
     struct audio_device *adev = in->dev;
+    int snd_card_status;
+    struct pcm_config config = in->config;
+    int usecase;
 
-    int usecase = platform_update_usecase_from_source(in->source,in->usecase);
+    snd_card_status = get_snd_card_state(adev);
+    usecase = platform_update_usecase_from_source(in->source,in->usecase);
     if (get_usecase_from_list(adev, usecase) == NULL)
         in->usecase = usecase;
     ALOGD("%s: enter: stream(%p)usecase(%d: %s)",
           __func__, &in->stream, in->usecase, use_case_table[in->usecase]);
 
-
     if (CARD_STATUS_OFFLINE == in->card_status||
         CARD_STATUS_OFFLINE == adev->card_status) {
         ALOGW("in->card_status or adev->card_status offline, try again");
@@ -2143,6 +2157,10 @@
             flags |= PCM_MMAP | PCM_NOIRQ;
         }
 
+        if (audio_extn_ffv_get_stream() == in) {
+           ALOGD("%s: ffv stream, update pcm config", __func__);
+           audio_extn_ffv_update_pcm_config(&config);
+        }
         ALOGV("%s: Opening PCM device card_id(%d) device_id(%d), channels %d",
               __func__, adev->snd_card, in->pcm_device_id, in->config.channels);
 
@@ -4771,6 +4789,8 @@
             ret = audio_extn_compr_cap_read(in, buffer, bytes);
         } else if (use_mmap) {
             ret = pcm_mmap_read(in->pcm, buffer, bytes);
+        } else if (audio_extn_ffv_get_stream() == in) {
+            ret = audio_extn_ffv_read(stream, buffer, bytes);
         } else {
             ret = pcm_read(in->pcm, buffer, bytes);
             /* data from DSP comes in 24_8 format, convert it to 8_24 */
@@ -6178,7 +6198,7 @@
             ret = -EINVAL;
             goto err_open;
         }
-        ALOGD("%s: created surround sound session succesfully",__func__);
+        ALOGD("%s: created multi-channel session succesfully",__func__);
     } else if (audio_extn_compr_cap_enabled() &&
             audio_extn_compr_cap_format_supported(config->format) &&
             (in->dev->mode != AUDIO_MODE_IN_COMMUNICATION)) {
@@ -6278,6 +6298,10 @@
         audio_extn_ssr_deinit();
     }
 
+    if (audio_extn_ffv_get_stream() == in) {
+        audio_extn_ffv_stream_deinit();
+    }
+
     if (audio_extn_compr_cap_enabled() &&
             audio_extn_compr_cap_format_supported(in->config.format))
         audio_extn_compr_cap_deinit();
@@ -6362,6 +6386,7 @@
         audio_extn_adsp_hdlr_deinit();
         audio_extn_snd_mon_deinit();
         audio_extn_hw_loopback_deinit(adev);
+        audio_extn_ffv_deinit();
         if (adev->device_cfg_params) {
             free(adev->device_cfg_params);
             adev->device_cfg_params = NULL;
@@ -6625,6 +6650,7 @@
     audio_extn_listen_init(adev, adev->snd_card);
     audio_extn_gef_init(adev);
     audio_extn_hw_loopback_init(adev);
+    audio_extn_ffv_init(adev);
 
     if (access(OFFLOAD_EFFECTS_BUNDLE_LIBRARY_PATH, R_OK) == 0) {
         adev->offload_effects_lib = dlopen(OFFLOAD_EFFECTS_BUNDLE_LIBRARY_PATH, RTLD_NOW);
diff --git a/hal/audio_hw.h b/hal/audio_hw.h
index f0b7077..fda0672 100644
--- a/hal/audio_hw.h
+++ b/hal/audio_hw.h
@@ -179,6 +179,8 @@
     USECASE_AUDIO_PLAYBACK_INTERACTIVE_STREAM6,
     USECASE_AUDIO_PLAYBACK_INTERACTIVE_STREAM7,
     USECASE_AUDIO_PLAYBACK_INTERACTIVE_STREAM8,
+
+    USECASE_AUDIO_EC_REF_LOOPBACK,
     AUDIO_USECASE_MAX
 };
 
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index 3e7b69c..6dfa0eb 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -393,7 +393,7 @@
                      {PLAYBACK_INTERACTIVE_STRM_DEVICE7, PLAYBACK_INTERACTIVE_STRM_DEVICE7},
     [USECASE_AUDIO_PLAYBACK_INTERACTIVE_STREAM8] =
                      {PLAYBACK_INTERACTIVE_STRM_DEVICE8, PLAYBACK_INTERACTIVE_STRM_DEVICE8},
-
+    [USECASE_AUDIO_EC_REF_LOOPBACK] = {-1, -1}, /* pcm id updated from platform info file */
 };
 
 /* Array to store sound devices */
@@ -537,6 +537,10 @@
     [SND_DEVICE_IN_UNPROCESSED_THREE_MIC] = "three-mic",
     [SND_DEVICE_IN_UNPROCESSED_QUAD_MIC] = "quad-mic",
     [SND_DEVICE_IN_UNPROCESSED_HEADSET_MIC] = "headset-mic",
+    [SND_DEVICE_IN_HANDSET_6MIC] = "handset-6mic",
+    [SND_DEVICE_IN_HANDSET_8MIC] = "handset-8mic",
+    [SND_DEVICE_IN_EC_REF_LOOPBACK_MONO] = "ec-ref-loopback-mono",
+    [SND_DEVICE_IN_EC_REF_LOOPBACK_STEREO] = "ec-ref-loopback-stereo",
 };
 
 // Platform specific backend bit width table
@@ -679,6 +683,10 @@
     [SND_DEVICE_IN_UNPROCESSED_THREE_MIC] = 145,
     [SND_DEVICE_IN_UNPROCESSED_QUAD_MIC] = 146,
     [SND_DEVICE_IN_UNPROCESSED_HEADSET_MIC] = 147,
+    [SND_DEVICE_IN_HANDSET_6MIC] = 4,
+    [SND_DEVICE_IN_HANDSET_8MIC] = 4,
+    [SND_DEVICE_IN_EC_REF_LOOPBACK_MONO] = 4,
+    [SND_DEVICE_IN_EC_REF_LOOPBACK_STEREO] = 4
 };
 
 struct name_to_index {
@@ -823,6 +831,10 @@
     {TO_NAME_INDEX(SND_DEVICE_IN_UNPROCESSED_THREE_MIC)},
     {TO_NAME_INDEX(SND_DEVICE_IN_UNPROCESSED_QUAD_MIC)},
     {TO_NAME_INDEX(SND_DEVICE_IN_UNPROCESSED_HEADSET_MIC)},
+    {TO_NAME_INDEX(SND_DEVICE_IN_HANDSET_6MIC)},
+    {TO_NAME_INDEX(SND_DEVICE_IN_HANDSET_8MIC)},
+    {TO_NAME_INDEX(SND_DEVICE_IN_EC_REF_LOOPBACK_MONO)},
+    {TO_NAME_INDEX(SND_DEVICE_IN_EC_REF_LOOPBACK_STEREO)},
 };
 
 static char * backend_tag_table[SND_DEVICE_MAX] = {0};
@@ -869,6 +881,7 @@
     {TO_NAME_INDEX(USECASE_AUDIO_SPKR_CALIB_RX)},
     {TO_NAME_INDEX(USECASE_AUDIO_PLAYBACK_AFE_PROXY)},
     {TO_NAME_INDEX(USECASE_AUDIO_RECORD_AFE_PROXY)},
+    {TO_NAME_INDEX(USECASE_AUDIO_EC_REF_LOOPBACK)},
 };
 
 #define NO_COLS 2
@@ -1986,6 +1999,8 @@
 {
     // support max to mono, example if max count is 3, usecase supports Three, dual and mono mic
     switch (my_data->max_mic_count) {
+        case 6:
+            my_data->source_mic_type |= SOURCE_HEX_MIC;
         case 4:
             my_data->source_mic_type |= SOURCE_QUAD_MIC;
         case 3:
@@ -2184,7 +2199,9 @@
     be_dai_name_table = NULL;
 
     property_get("ro.vendor.audio.sdk.fluencetype", my_data->fluence_cap, "");
-    if (!strncmp("fluencepro", my_data->fluence_cap, sizeof("fluencepro"))) {
+    if (!strncmp("fluenceffv", my_data->fluence_cap, sizeof("fluenceffv"))) {
+        my_data->fluence_type = FLUENCE_HEX_MIC | FLUENCE_QUAD_MIC | FLUENCE_DUAL_MIC;
+    } else if (!strncmp("fluencepro", my_data->fluence_cap, sizeof("fluencepro"))) {
         my_data->fluence_type = FLUENCE_QUAD_MIC | FLUENCE_DUAL_MIC;
     } else if (!strncmp("fluence", my_data->fluence_cap, sizeof("fluence"))) {
         my_data->fluence_type = FLUENCE_DUAL_MIC;
@@ -2432,6 +2449,7 @@
 
     /* Read one time ssr property */
     audio_extn_ssr_update_enabled();
+    audio_extn_ffv_update_enabled();
     audio_extn_spkr_prot_init(adev);
 
     /* init dap hal */
@@ -2679,6 +2697,10 @@
     if (snd_device >= SND_DEVICE_MIN && snd_device < SND_DEVICE_MAX) {
         strlcpy(device_name, device_table[snd_device], DEVICE_NAME_MAX_SIZE);
         hw_info_append_hw_type(my_data->hw_info, snd_device, device_name);
+
+        if ((snd_device == SND_DEVICE_IN_EC_REF_LOOPBACK_MONO) ||
+            (snd_device == SND_DEVICE_IN_EC_REF_LOOPBACK_STEREO))
+            audio_extn_ffv_append_ec_ref_dev_name(device_name);
     } else {
         strlcpy(device_name, "", DEVICE_NAME_MAX_SIZE);
         return -EINVAL;
@@ -2835,7 +2857,9 @@
     int ret = 0;
     struct platform_data *my_data = (struct platform_data *)platform;
 
-    if (my_data->fluence_type == FLUENCE_QUAD_MIC) {
+    if (my_data->fluence_type == FLUENCE_HEX_MIC) {
+        strlcpy(value, "hexmic", len);
+    } else if (my_data->fluence_type == FLUENCE_QUAD_MIC) {
         strlcpy(value, "quadmic", len);
     } else if (my_data->fluence_type == FLUENCE_DUAL_MIC) {
         strlcpy(value, "dualmic", len);
@@ -4288,9 +4312,13 @@
         }
     } else if (source == AUDIO_SOURCE_MIC) {
         if (in_device & AUDIO_DEVICE_IN_BUILTIN_MIC &&
-                channel_count == 1 ) {
+                channel_count == 1) {
             if(my_data->fluence_in_audio_rec) {
-                if ((my_data->fluence_type & FLUENCE_QUAD_MIC) &&
+                if ((my_data->fluence_type & FLUENCE_HEX_MIC) &&
+                    (my_data->source_mic_type & SOURCE_HEX_MIC) &&
+                    (audio_extn_ffv_get_stream() == adev->active_input)) {
+                    snd_device = audio_extn_ffv_get_capture_snd_device();
+                } else if ((my_data->fluence_type & FLUENCE_QUAD_MIC) &&
                     (my_data->source_mic_type & SOURCE_QUAD_MIC)) {
                     snd_device = SND_DEVICE_IN_HANDSET_QMIC;
                     platform_set_echo_reference(adev, true, out_device);
@@ -4781,6 +4809,7 @@
     audio_extn_usb_set_sidetone_gain(parms, value, len);
     audio_extn_hfp_set_parameters(my_data->adev, parms);
     true_32_bit_set_params(parms, value, len);
+    audio_extn_ffv_set_parameters(my_data->adev, parms);
     ALOGV("%s: exit with code(%d)", __func__, ret);
     return ret;
 }
@@ -7232,6 +7261,20 @@
     }
 }
 
+int platform_get_ec_ref_loopback_snd_device(int channel_count)
+{
+    snd_device_t snd_device;
+
+    if (channel_count == 1)
+        snd_device = SND_DEVICE_IN_EC_REF_LOOPBACK_MONO;
+    else if (channel_count == 2)
+        snd_device = SND_DEVICE_IN_EC_REF_LOOPBACK_STEREO;
+    else
+        snd_device = SND_DEVICE_NONE;
+
+    return snd_device;
+}
+
 int platform_set_sidetone(struct audio_device *adev,
                           snd_device_t out_snd_device,
                           bool enable,
diff --git a/hal/msm8916/platform.h b/hal/msm8916/platform.h
index a53a38f..d9f203c 100644
--- a/hal/msm8916/platform.h
+++ b/hal/msm8916/platform.h
@@ -25,6 +25,7 @@
     FLUENCE_NONE,
     FLUENCE_DUAL_MIC = 0x1,
     FLUENCE_QUAD_MIC = 0x2,
+    FLUENCE_HEX_MIC = 0x4,
 };
 
 enum {
@@ -37,6 +38,7 @@
     SOURCE_DUAL_MIC  = 0x2,            /* Target contains 2 mics */
     SOURCE_THREE_MIC = 0x4,            /* Target contains 3 mics */
     SOURCE_QUAD_MIC  = 0x8,            /* Target contains 4 mics */
+    SOURCE_HEX_MIC   = 0x16,           /* Target contains 6 mics */
 };
 
 enum {
@@ -222,6 +224,10 @@
     SND_DEVICE_IN_UNPROCESSED_THREE_MIC,
     SND_DEVICE_IN_UNPROCESSED_QUAD_MIC,
     SND_DEVICE_IN_UNPROCESSED_HEADSET_MIC,
+    SND_DEVICE_IN_HANDSET_6MIC,
+    SND_DEVICE_IN_HANDSET_8MIC,
+    SND_DEVICE_IN_EC_REF_LOOPBACK_MONO,
+    SND_DEVICE_IN_EC_REF_LOOPBACK_STEREO,
     SND_DEVICE_IN_END,
 
     SND_DEVICE_MAX = SND_DEVICE_IN_END,
diff --git a/hal/msm8974/platform.h b/hal/msm8974/platform.h
index b5bbbdf..113c0f2 100644
--- a/hal/msm8974/platform.h
+++ b/hal/msm8974/platform.h
@@ -25,6 +25,7 @@
     FLUENCE_NONE,
     FLUENCE_DUAL_MIC = 0x1,
     FLUENCE_QUAD_MIC = 0x2,
+    FLUENCE_HEX_MIC = 0x4,
 };
 
 enum {
@@ -37,6 +38,7 @@
     SOURCE_DUAL_MIC  = 0x2,            /* Target contains 2 mics */
     SOURCE_THREE_MIC = 0x4,            /* Target contains 3 mics */
     SOURCE_QUAD_MIC  = 0x8,            /* Target contains 4 mics */
+    SOURCE_HEX_MIC   = 0x16,           /* Target contains 6 mics */
 };
 
 enum {
@@ -222,6 +224,10 @@
     SND_DEVICE_IN_UNPROCESSED_THREE_MIC,
     SND_DEVICE_IN_UNPROCESSED_QUAD_MIC,
     SND_DEVICE_IN_UNPROCESSED_HEADSET_MIC,
+    SND_DEVICE_IN_HANDSET_6MIC,
+    SND_DEVICE_IN_HANDSET_8MIC,
+    SND_DEVICE_IN_EC_REF_LOOPBACK_MONO,
+    SND_DEVICE_IN_EC_REF_LOOPBACK_STEREO,
     SND_DEVICE_IN_END,
 
     SND_DEVICE_MAX = SND_DEVICE_IN_END,
diff --git a/hal/platform_api.h b/hal/platform_api.h
index 6f8cf7e..e36f994 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -239,4 +239,5 @@
 int platform_get_max_codec_backend();
 int platform_get_mmap_data_fd(void *platform, int dev, int dir,
                                int *fd, uint32_t *size);
+int platform_get_ec_ref_loopback_snd_device(int channel_count);
 #endif // AUDIO_PLATFORM_API_H
diff --git a/qahw_api/test/qahw_multi_record_test.c b/qahw_api/test/qahw_multi_record_test.c
index 86b75dc..23acdeb 100644
--- a/qahw_api/test/qahw_multi_record_test.c
+++ b/qahw_api/test/qahw_multi_record_test.c
@@ -73,6 +73,7 @@
     double record_delay;
     double record_length;
     char profile[50];
+    char kvpairs[256];
 };
 
 struct timed_params {
@@ -573,6 +574,7 @@
     printf(" -i  --interactive-mode                    - Use this flag if prefer configuring streams using interactive mode\n");
     printf("                                             All other flags passed would be ignore if this flag is used\n\n");
     printf(" -S  --source-tracking                     - Use this flag to show capture source tracking params for recordings\n\n");
+    printf(" -k --kvpairs                              - kvpairs to be set globally\n");
     printf(" -h  --help                                - Show this help\n\n");
     printf(" \n Examples \n");
     printf(" hal_rec_test     -> start a recording stream with default configurations\n\n");
@@ -584,6 +586,8 @@
     printf(" hal_rec_test -S -c 1 -r 48000 -t 30 -> Enable Sourcetracking\n");
     printf("                                      For mono channel 48kHz rate for 30seconds\n\n");
     printf(" hal_rec_test -F 1 --kpi-mode -> start a recording with low latency input flag and calculate latency KPIs\n\n");
+    printf(" hal_rec_test -c 1 -r 16000 -t 30 -k ffvOn=true;ffv_ec_ref_ch_cnt=2 -> Enable FFV with stereo ec ref\n");
+    printf("                                               For mono channel 16kHz rate for 30seconds\n\n");
 }
 
 int main(int argc, char* argv[]) {
@@ -615,6 +619,7 @@
         {"kpi-mode",        no_argument,          0, 'K'},
         {"interactive",     no_argument,          0, 'i'},
         {"source-tracking", no_argument,          0, 'S'},
+        {"kvpairs",         required_argument,    0, 'k'},
         {"help",            no_argument,          0, 'h'},
         {0, 0, 0, 0}
     };
@@ -623,7 +628,7 @@
     int option_index = 0;
     while ((opt = getopt_long(argc,
                               argv,
-                              "-d:f:F:r:c:s:p:t:D:l:KiSh",
+                              "-d:f:F:r:c:s:p:t:D:l:k:KiSh",
                               long_options,
                               &option_index)) != -1) {
             switch (opt) {
@@ -666,6 +671,9 @@
             case 'S':
                 source_tracking = true;
                 break;
+            case 'k':
+                snprintf(params[0].kvpairs, sizeof(params[0].kvpairs), "%s", optarg);
+                break;
             case 'h':
                 usage();
                 return 0;
@@ -776,6 +784,20 @@
         }
     }
 
+    /* set global setparams entered by user.
+     * Also other global setparams can be concatenated if required.
+     */
+    if (params[0].kvpairs != NULL) {
+        size_t len;
+        len = strcspn(params[0].kvpairs, ",");
+        while (len < strlen(params[0].kvpairs)) {
+            params[0].kvpairs[len] = ';';
+            len = strcspn(params[0].kvpairs, ",");
+        }
+        printf("param %s set to hal\n", params[0].kvpairs);
+        qahw_set_parameters(qahw_mod_handle, params[0].kvpairs);
+    }
+
     pthread_t tid[4];
     pthread_t sourcetrack_thread;
     int ret = -1;