yukawa: Add loudspeaker equalization

This filter attenuates 200-400Hz by 18dB,
and some 6dB notch attenuation at 2.25kHz, 3.8kHz, 6.6kHz.

Full frequency response here: https://b.corp.google.com/issues/159714063#comment3

EQ can be disabled/modify at runtime with the following steps:

1. (Disable) Rename file /vendor/etc/speaker_eq_sei610.fir to a different name/ext.
   (Modify) Modify contents of /vendor/etc/speaker_eq_sei610.fir (max 512 taps).
2. Run 'killall audioserver' on device.

Bug: b/159714063
Test: Yukawa passed ART Eraser test.

Change-Id: I32ccb20c9fd1d6ff51086d93babbd9cf828edc0d
diff --git a/device-yukawa.mk b/device-yukawa.mk
index ed817a5..c2edd61 100644
--- a/device-yukawa.mk
+++ b/device-yukawa.mk
@@ -22,3 +22,7 @@
 # Feature permissions
 PRODUCT_COPY_FILES += \
     device/amlogic/yukawa/permissions/yukawa.xml:/system/etc/sysconfig/yukawa.xml
+
+# Speaker EQ
+PRODUCT_COPY_FILES += \
+    device/amlogic/yukawa/hal/audio/speaker_eq_sei610.fir:$(TARGET_COPY_OUT_VENDOR)/etc/speaker_eq_sei610.fir
diff --git a/hal/audio/Android.mk b/hal/audio/Android.mk
index 357f12b..67a248d 100644
--- a/hal/audio/Android.mk
+++ b/hal/audio/Android.mk
@@ -29,7 +29,8 @@
 
 LOCAL_SRC_FILES := audio_hw.c \
     audio_aec.c \
-    fifo_wrapper.cpp
+    fifo_wrapper.cpp \
+    fir_filter.c
 LOCAL_SHARED_LIBRARIES := liblog libcutils libtinyalsa libaudioroute libaudioutils
 LOCAL_CFLAGS := -Wno-unused-parameter
 LOCAL_C_INCLUDES += \
diff --git a/hal/audio/audio_hw.c b/hal/audio/audio_hw.c
index d145b42..bbfb608 100644
--- a/hal/audio/audio_hw.c
+++ b/hal/audio/audio_hw.c
@@ -46,13 +46,14 @@
 
 #include <sys/ioctl.h>
 
-#include "audio_hw.h"
 #include "audio_aec.h"
+#include "audio_hw.h"
 
 static int adev_get_mic_mute(const struct audio_hw_device* dev, bool* state);
 static int adev_get_microphones(const struct audio_hw_device* dev,
                                 struct audio_microphone_characteristic_t* mic_array,
                                 size_t* mic_count);
+static size_t out_get_buffer_size(const struct audio_stream* stream);
 
 static int get_audio_output_port(audio_devices_t devices) {
     /* Prefer HDMI, default to internal speaker */
@@ -101,6 +102,56 @@
     return ret;
 }
 
+static int read_filter_from_file(const char* filename, int16_t* filter, int max_length) {
+    FILE* fp = fopen(filename, "r");
+    if (fp == NULL) {
+        ALOGI("%s: File %s not found.", __func__, filename);
+        return 0;
+    }
+    int num_taps = 0;
+    char* line = NULL;
+    size_t len = 0;
+    while (!feof(fp)) {
+        size_t size = getline(&line, &len, fp);
+        if ((line[0] == '#') || (size < 2)) {
+            continue;
+        }
+        int n = sscanf(line, "%" PRIi16 "\n", &filter[num_taps++]);
+        if (n < 1) {
+            ALOGE("Could not find coefficient %d! Exiting...", num_taps - 1);
+            return 0;
+        }
+        ALOGV("Coeff %d : %" PRIi16, num_taps, filter[num_taps - 1]);
+        if (num_taps == max_length) {
+            ALOGI("%s: max tap length %d reached.", __func__, max_length);
+            break;
+        }
+    }
+    free(line);
+    fclose(fp);
+    return num_taps;
+}
+
+static void out_set_eq(struct alsa_stream_out* out) {
+    out->speaker_eq = NULL;
+    int16_t* speaker_eq_coeffs = (int16_t*)calloc(SPEAKER_MAX_EQ_LENGTH, sizeof(int16_t));
+    if (speaker_eq_coeffs == NULL) {
+        ALOGE("%s: Failed to allocate speaker EQ", __func__);
+        return;
+    }
+    int num_taps = read_filter_from_file(SPEAKER_EQ_FILE, speaker_eq_coeffs, SPEAKER_MAX_EQ_LENGTH);
+    if (num_taps == 0) {
+        ALOGI("%s: Empty filter file or 0 taps set.", __func__);
+        free(speaker_eq_coeffs);
+        return;
+    }
+    out->speaker_eq = fir_init(
+            out->config.channels, FIR_SINGLE_FILTER, num_taps,
+            out_get_buffer_size(&out->stream.common) / out->config.channels / sizeof(int16_t),
+            speaker_eq_coeffs);
+    free(speaker_eq_coeffs);
+}
+
 /* must be called with hw device and output stream mutexes locked */
 static int start_output_stream(struct alsa_stream_out *out)
 {
@@ -185,6 +236,8 @@
 {
     struct alsa_audio_device *adev = out->dev;
 
+    fir_reset(out->speaker_eq);
+
     if (!out->standby) {
         pcm_close(out->pcm);
         out->pcm = NULL;
@@ -292,6 +345,9 @@
 
     pthread_mutex_unlock(&adev->lock);
 
+    if (out->speaker_eq != NULL) {
+        fir_process_interleaved(out->speaker_eq, (int16_t*)buffer, (int16_t*)buffer, out_frames);
+    }
 
     ret = pcm_write(out->pcm, buffer, out_frames * frame_size);
     if (ret == 0) {
@@ -793,6 +849,14 @@
 
     *stream_out = &out->stream;
 
+    out->speaker_eq = NULL;
+    if (out_port == PORT_INTERNAL_SPEAKER) {
+        out_set_eq(out);
+        if (out->speaker_eq == NULL) {
+            ALOGE("%s: Failed to initialize speaker EQ", __func__);
+        }
+    }
+
     /* TODO The retry mechanism isn't implemented in AudioPolicyManager/AudioFlinger. */
     ret = 0;
 
@@ -813,6 +877,8 @@
     ALOGV("adev_close_output_stream...");
     struct alsa_audio_device *adev = (struct alsa_audio_device *)dev;
     destroy_aec_reference_config(adev->aec);
+    struct alsa_stream_out* out = (struct alsa_stream_out*)stream;
+    fir_release(out->speaker_eq);
     free(stream);
 }
 
diff --git a/hal/audio/audio_hw.h b/hal/audio/audio_hw.h
index 5dd4f46..129bdbf 100644
--- a/hal/audio/audio_hw.h
+++ b/hal/audio/audio_hw.h
@@ -20,6 +20,8 @@
 #include <hardware/audio.h>
 #include <tinyalsa/asoundlib.h>
 
+#include "fir_filter.h"
+
 #define CARD_OUT 0
 #define PORT_HDMI 0
 #define PORT_INTERNAL_SPEAKER 1
@@ -65,6 +67,9 @@
 #define PLAYBACK_CODEC_SAMPLING_RATE 48000
 #define MIN_WRITE_SLEEP_US      5000
 
+#define SPEAKER_EQ_FILE "/vendor/etc/speaker_eq_sei610.fir"
+#define SPEAKER_MAX_EQ_LENGTH 512
+
 struct alsa_audio_device {
     struct audio_hw_device hw_device;
 
@@ -106,6 +111,7 @@
     int write_threshold;
     unsigned int frames_written;
     struct timespec timestamp;
+    fir_filter_t* speaker_eq;
 };
 
 /* 'bytes' are the number of bytes written to audio FIFO, for which 'timestamp' is valid.
diff --git a/hal/audio/fir_filter.c b/hal/audio/fir_filter.c
new file mode 100644
index 0000000..c648fc0
--- /dev/null
+++ b/hal/audio/fir_filter.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#define LOG_TAG "audio_hw_fir_filter"
+//#define LOG_NDEBUG 0
+
+#include <assert.h>
+#include <audio_utils/primitives.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <log/log.h>
+#include <malloc.h>
+#include <string.h>
+
+#include "fir_filter.h"
+
+#ifdef __ARM_NEON
+#include "arm_neon.h"
+#endif /* #ifdef __ARM_NEON */
+
+fir_filter_t* fir_init(uint32_t channels, fir_filter_mode_t mode, uint32_t filter_length,
+                       uint32_t input_length, int16_t* coeffs) {
+    if ((channels == 0) || (filter_length == 0) || (coeffs == NULL)) {
+        ALOGE("%s: Invalid channel count, filter length or coefficient array.", __func__);
+        return NULL;
+    }
+
+    fir_filter_t* fir = (fir_filter_t*)calloc(1, sizeof(fir_filter_t));
+    if (fir == NULL) {
+        ALOGE("%s: Unable to allocate memory for fir_filter.", __func__);
+        return NULL;
+    }
+
+    fir->channels = channels;
+    fir->filter_length = filter_length;
+    /* Default: same filter coeffs for all channels */
+    fir->mode = FIR_SINGLE_FILTER;
+    uint32_t coeff_bytes = fir->filter_length * sizeof(int16_t);
+    if (mode == FIR_PER_CHANNEL_FILTER) {
+        fir->mode = FIR_PER_CHANNEL_FILTER;
+        coeff_bytes = fir->filter_length * fir->channels * sizeof(int16_t);
+    }
+
+    fir->coeffs = (int16_t*)malloc(coeff_bytes);
+    if (fir->coeffs == NULL) {
+        ALOGE("%s: Unable to allocate memory for FIR coeffs", __func__);
+        goto exit_1;
+    }
+    memcpy(fir->coeffs, coeffs, coeff_bytes);
+
+    fir->buffer_size = (input_length + fir->filter_length) * fir->channels;
+    fir->state = (int16_t*)malloc(fir->buffer_size * sizeof(int16_t));
+    if (fir->state == NULL) {
+        ALOGE("%s: Unable to allocate memory for FIR state", __func__);
+        goto exit_2;
+    }
+
+#ifdef __ARM_NEON
+    ALOGI("%s: Using ARM Neon", __func__);
+#endif /* #ifdef __ARM_NEON */
+
+    fir_reset(fir);
+    return fir;
+
+exit_2:
+    free(fir->coeffs);
+exit_1:
+    free(fir);
+    return NULL;
+}
+
+void fir_release(fir_filter_t* fir) {
+    if (fir == NULL) {
+        return;
+    }
+    free(fir->state);
+    free(fir->coeffs);
+    free(fir);
+}
+
+void fir_reset(fir_filter_t* fir) {
+    if (fir == NULL) {
+        return;
+    }
+    memset(fir->state, 0, fir->buffer_size * sizeof(int16_t));
+}
+
+void fir_process_interleaved(fir_filter_t* fir, int16_t* input, int16_t* output, uint32_t samples) {
+    assert(fir != NULL);
+
+    int start_offset = (fir->filter_length - 1) * fir->channels;
+    memcpy(&fir->state[start_offset], input, samples * fir->channels * sizeof(int16_t));
+    // int ch;
+    bool use_2nd_set_coeffs = (fir->channels > 1) && (fir->mode == FIR_PER_CHANNEL_FILTER);
+    int16_t* p_coeff_A = &fir->coeffs[0];
+    int16_t* p_coeff_B = use_2nd_set_coeffs ? &fir->coeffs[fir->filter_length] : &fir->coeffs[0];
+    int16_t* p_output;
+    for (int ch = 0; ch < fir->channels; ch += 2) {
+        p_output = &output[ch];
+        int offset = start_offset + ch;
+        for (int s = 0; s < samples; s++) {
+            int32_t acc_A = 0;
+            int32_t acc_B = 0;
+
+#ifdef __ARM_NEON
+            int32x4_t acc_vec = vdupq_n_s32(0);
+            for (int k = 0; k < fir->filter_length; k++, offset -= fir->channels) {
+                int16x4_t coeff_vec = vdup_n_s16(p_coeff_A[k]);
+                coeff_vec = vset_lane_s16(p_coeff_B[k], coeff_vec, 1);
+                int16x4_t input_vec = vld1_s16(&fir->state[offset]);
+                acc_vec = vmlal_s16(acc_vec, coeff_vec, input_vec);
+            }
+            acc_A = vgetq_lane_s32(acc_vec, 0);
+            acc_B = vgetq_lane_s32(acc_vec, 1);
+#else
+            for (int k = 0; k < fir->filter_length; k++, offset -= fir->channels) {
+                int32_t input_A = (int32_t)(fir->state[offset]);
+                int32_t coeff_A = (int32_t)(p_coeff_A[k]);
+                int32_t input_B = (int32_t)(fir->state[offset + 1]);
+                int32_t coeff_B = (int32_t)(p_coeff_B[k]);
+                acc_A += (input_A * coeff_A);
+                acc_B += (input_B * coeff_B);
+            }
+#endif /* #ifdef __ARM_NEON */
+
+            *p_output = clamp16(acc_A >> 15);
+            if (ch < fir->channels - 1) {
+                *(p_output + 1) = clamp16(acc_B >> 15);
+            }
+            /* Move to next sample */
+            p_output += fir->channels;
+            offset += (fir->filter_length + 1) * fir->channels;
+        }
+        if (use_2nd_set_coeffs) {
+            p_coeff_A += (fir->filter_length << 1);
+            p_coeff_B += (fir->filter_length << 1);
+        }
+    }
+    memmove(fir->state, &fir->state[samples * fir->channels],
+            (fir->filter_length - 1) * fir->channels * sizeof(int16_t));
+}
diff --git a/hal/audio/fir_filter.h b/hal/audio/fir_filter.h
new file mode 100644
index 0000000..d8c6e91
--- /dev/null
+++ b/hal/audio/fir_filter.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#ifndef FIR_FILTER_H
+#define FIR_FILTER_H
+
+#include <stdint.h>
+
+typedef enum fir_filter_mode { FIR_SINGLE_FILTER = 0, FIR_PER_CHANNEL_FILTER } fir_filter_mode_t;
+
+typedef struct fir_filter {
+    fir_filter_mode_t mode;
+    uint32_t channels;
+    uint32_t filter_length;
+    uint32_t buffer_size;
+    int16_t* coeffs;
+    int16_t* state;
+} fir_filter_t;
+
+fir_filter_t* fir_init(uint32_t channels, fir_filter_mode_t mode, uint32_t filter_length,
+                       uint32_t input_length, int16_t* coeffs);
+void fir_release(fir_filter_t* fir);
+void fir_reset(fir_filter_t* fir);
+void fir_process_interleaved(fir_filter_t* fir, int16_t* input, int16_t* output, uint32_t samples);
+
+#endif /* #ifndef FIR_FILTER_H */
diff --git a/hal/audio/speaker_eq_sei610.fir b/hal/audio/speaker_eq_sei610.fir
new file mode 100644
index 0000000..2352c32
--- /dev/null
+++ b/hal/audio/speaker_eq_sei610.fir
@@ -0,0 +1,523 @@
+# FIR speaker EQ file for SEI-610
+# This filter attenuates 200-400Hz by 18dB,
+# and some 6dB notch attenuation at 2.25kHz, 3.8kHz, 6.6kHz.
+# Script to generate this file: https://drive.google.com/file/d/1_qvkZ8nU-c6tD6XrH80et2P12paardAz/view?usp=sharing
+
+# Full frequency response here: https://b.corp.google.com/issues/159714063#comment3
+
+# Each FIR coefficient is specified on one line (no leading spaces).
+# First line is 0th coefficient.
+# Values must be 16-bit integers. Currently, a max of 512 taps is supported.
+
+18976
+9870
+-12520
+2452
+-766
+-1023
+1122
+-2509
+316
+-1464
+95
+-817
+-1191
+-1882
+-2299
+-1806
+-1180
+-310
+-68
+-303
+-957
+-1544
+-1738
+-1490
+-973
+-517
+-285
+-261
+-247
+-68
+305
+729
+983
+931
+612
+210
+-63
+-100
+48
+234
+313
+244
+99
+3
+36
+183
+350
+435
+398
+286
+191
+188
+282
+409
+483
+454
+336
+192
+92
+63
+83
+100
+73
+2
+-75
+-114
+-93
+-27
+41
+73
+55
+9
+-30
+-38
+-14
+18
+30
+9
+-34
+-78
+-100
+-94
+-75
+-62
+-68
+-91
+-116
+-124
+-109
+-79
+-50
+-35
+-37
+-47
+-53
+-48
+-33
+-19
+-14
+-22
+-38
+-51
+-55
+-49
+-38
+-31
+-32
+-38
+-45
+-45
+-38
+-26
+-17
+-14
+-16
+-21
+-23
+-21
+-16
+-12
+-13
+-17
+-25
+-30
+-32
+-31
+-29
+-28
+-30
+-33
+-36
+-37
+-35
+-32
+-30
+-31
+-33
+-36
+-38
+-38
+-37
+-37
+-38
+-40
+-43
+-46
+-47
+-47
+-46
+-46
+-47
+-49
+-50
+-50
+-50
+-49
+-48
+-49
+-50
+-51
+-51
+-51
+-51
+-51
+-51
+-52
+-53
+-54
+-54
+-54
+-54
+-54
+-54
+-55
+-55
+-55
+-54
+-54
+-54
+-54
+-54
+-55
+-55
+-55
+-55
+-55
+-55
+-55
+-56
+-56
+-56
+-56
+-56
+-56
+-56
+-56
+-56
+-56
+-56
+-55
+-55
+-55
+-56
+-56
+-56
+-56
+-55
+-55
+-55
+-56
+-56
+-56
+-55
+-55
+-55
+-55
+-55
+-55
+-55
+-55
+-55
+-54
+-54
+-54
+-54
+-54
+-54
+-54
+-53
+-53
+-53
+-53
+-53
+-53
+-52
+-52
+-52
+-52
+-51
+-51
+-51
+-51
+-50
+-50
+-50
+-50
+-49
+-49
+-49
+-48
+-48
+-48
+-48
+-47
+-47
+-47
+-46
+-46
+-46
+-45
+-45
+-45
+-44
+-44
+-44
+-43
+-43
+-43
+-42
+-42
+-41
+-41
+-41
+-40
+-40
+-40
+-39
+-39
+-38
+-38
+-38
+-37
+-37
+-36
+-36
+-36
+-35
+-35
+-34
+-34
+-33
+-33
+-33
+-32
+-32
+-31
+-31
+-31
+-30
+-30
+-29
+-29
+-28
+-28
+-27
+-27
+-27
+-26
+-26
+-25
+-25
+-24
+-24
+-24
+-23
+-23
+-22
+-22
+-21
+-21
+-20
+-20
+-20
+-19
+-19
+-18
+-18
+-17
+-17
+-17
+-16
+-16
+-15
+-15
+-14
+-14
+-14
+-13
+-13
+-12
+-12
+-11
+-11
+-11
+-10
+-10
+-9
+-9
+-9
+-8
+-8
+-7
+-7
+-7
+-6
+-6
+-5
+-5
+-5
+-4
+-4
+-3
+-3
+-3
+-2
+-2
+-1
+-1
+-1
+0
+0
+0
+0
+0
+0
+1
+1
+1
+2
+2
+2
+3
+3
+3
+4
+4
+4
+5
+5
+5
+6
+6
+6
+7
+7
+7
+7
+8
+8
+8
+9
+9
+9
+9
+10
+10
+10
+10
+11
+11
+11
+11
+12
+12
+12
+12
+13
+13
+13
+13
+13
+14
+14
+14
+14
+14
+15
+15
+15
+15
+15
+16
+16
+16
+16
+16
+16
+17
+17
+17
+17
+17
+17
+17
+18
+18
+18
+18
+18
+18
+18
+18
+19
+19
+19
+19
+19
+19
+19
+19
+19
+19
+19
+20
+20
+20
+20
+20
+20
+20
+20
+20
+20
+20
+20
+20
+20
+20
+20
+20
+20
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+21
+20
+20
+20
+20
+20
+20