yukawa: Add HAL support for ECHO_REFERENCE audio source.
Bug: b/143616861
Test: Manual, plus tests found at go/ag/9507428.
Change-Id: Iac114c0cdc706da88e88075dbe7225183b8e0a81
diff --git a/audio/Android.mk b/audio/Android.mk
index e8c2129..357f12b 100644
--- a/audio/Android.mk
+++ b/audio/Android.mk
@@ -27,8 +27,10 @@
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_VENDOR_MODULE := true
-LOCAL_SRC_FILES := audio_hw.c
-LOCAL_SHARED_LIBRARIES := liblog libcutils libtinyalsa libaudioroute
+LOCAL_SRC_FILES := audio_hw.c \
+ audio_aec.c \
+ fifo_wrapper.cpp
+LOCAL_SHARED_LIBRARIES := liblog libcutils libtinyalsa libaudioroute libaudioutils
LOCAL_CFLAGS := -Wno-unused-parameter
LOCAL_C_INCLUDES += \
external/tinyalsa/include \
@@ -38,10 +40,7 @@
system/media/audio_effects/include
ifneq ($(findstring google_aec, $(call all-makefiles-under,$(TOPDIR)vendor/amlogic/yukawa)),)
- LOCAL_SRC_FILES += \
- audio_aec.c \
- fifo_wrapper.cpp
- LOCAL_SHARED_LIBRARIES += google_aec libaudioutils
+ LOCAL_SHARED_LIBRARIES += google_aec
LOCAL_CFLAGS += -DAEC_HAL
endif
diff --git a/audio/audio_aec.c b/audio/audio_aec.c
index 2728be8..ab99c93 100644
--- a/audio/audio_aec.c
+++ b/audio/audio_aec.c
@@ -73,13 +73,23 @@
#include <malloc.h>
#include <sys/time.h>
#include <tinyalsa/asoundlib.h>
+#include <unistd.h>
#include <log/log.h>
#include "audio_aec.h"
-#include "audio_aec_process.h"
-#define DEBUG_AEC 0
+#ifdef AEC_HAL
+#include "audio_aec_process.h"
+#else
+#define aec_spk_mic_init(...) ((int)0)
+#define aec_spk_mic_reset(...) ((void)0)
+#define aec_spk_mic_process(...) ((int32_t)0)
+#define aec_spk_mic_release(...) ((void)0)
+#endif
+
#define MAX_TIMESTAMP_DIFF_USEC 200000
+#define MAX_READ_WAIT_TIME_MSEC 80
+
uint64_t timespec_to_usec(struct timespec ts) {
return (ts.tv_sec * 1e6L + ts.tv_nsec/1000);
}
@@ -133,6 +143,38 @@
aec->read_write_diff_bytes = 0;
}
+void aec_set_spk_running_no_lock(struct aec_t* aec, bool state) {
+ aec->spk_running = state;
+}
+
+bool aec_get_spk_running_no_lock(struct aec_t* aec) {
+ return aec->spk_running;
+}
+
+void destroy_aec_reference_config_no_lock(struct aec_t* aec) {
+ if (!aec->spk_initialized) {
+ return;
+ }
+ aec_set_spk_running_no_lock(aec, false);
+ fifo_release(aec->spk_fifo);
+ fifo_release(aec->ts_fifo);
+ memset(&aec->last_spk_info, 0, sizeof(struct aec_info));
+ aec->spk_initialized = false;
+}
+
+void destroy_aec_mic_config_no_lock(struct aec_t* aec) {
+ if (!aec->mic_initialized) {
+ return;
+ }
+ release_resampler(aec->spk_resampler);
+ free(aec->mic_buf);
+ free(aec->spk_buf);
+ free(aec->spk_buf_playback_format);
+ free(aec->spk_buf_resampler_out);
+ memset(&aec->last_mic_info, 0, sizeof(struct aec_info));
+ aec->mic_initialized = false;
+}
+
struct aec_t *init_aec_interface() {
ALOGV("%s enter", __func__);
struct aec_t *aec = (struct aec_t *)calloc(1, sizeof(struct aec_t));
@@ -149,8 +191,8 @@
void release_aec_interface(struct aec_t *aec) {
ALOGV("%s enter", __func__);
pthread_mutex_lock(&aec->lock);
- destroy_aec_mic_config(aec);
- destroy_aec_reference_config(aec);
+ destroy_aec_mic_config_no_lock(aec);
+ destroy_aec_reference_config_no_lock(aec);
pthread_mutex_unlock(&aec->lock);
free(aec);
ALOGV("%s exit", __func__);
@@ -207,6 +249,10 @@
int ret = 0;
pthread_mutex_lock(&aec->lock);
+ if (aec->spk_initialized) {
+ destroy_aec_reference_config_no_lock(aec);
+ }
+
aec->spk_fifo = fifo_init(
out->config.period_count * out->config.period_size *
audio_stream_out_frame_size(&out->stream),
@@ -229,12 +275,169 @@
aec->spk_sampling_rate = out->config.rate;
aec->spk_frame_size_bytes = audio_stream_out_frame_size(&out->stream);
aec->spk_num_channels = out->config.channels;
+ aec->spk_initialized = true;
exit:
pthread_mutex_unlock(&aec->lock);
ALOGV("%s exit", __func__);
return ret;
}
+void destroy_aec_reference_config(struct aec_t* aec) {
+ ALOGV("%s enter", __func__);
+ if (aec == NULL) {
+ ALOGV("%s exit", __func__);
+ return;
+ }
+ pthread_mutex_lock(&aec->lock);
+ destroy_aec_reference_config_no_lock(aec);
+ pthread_mutex_unlock(&aec->lock);
+ ALOGV("%s exit", __func__);
+}
+
+int write_to_reference_fifo(struct aec_t* aec, void* buffer, struct aec_info* info) {
+ ALOGV("%s enter", __func__);
+ int ret = 0;
+ size_t bytes = info->bytes;
+
+ /* Write audio samples to FIFO */
+ ssize_t written_bytes = fifo_write(aec->spk_fifo, buffer, bytes);
+ if (written_bytes != bytes) {
+ ALOGE("Could only write %zu of %zu bytes", written_bytes, bytes);
+ ret = -ENOMEM;
+ }
+
+ /* Write timestamp to FIFO */
+ info->bytes = written_bytes;
+ ALOGV("Speaker timestamp: %ld s, %ld nsec", info->timestamp.tv_sec, info->timestamp.tv_nsec);
+ ssize_t ts_bytes = fifo_write(aec->ts_fifo, info, sizeof(struct aec_info));
+ ALOGV("Wrote TS bytes: %zu", ts_bytes);
+ print_queue_status_to_log(aec, true);
+ ALOGV("%s exit", __func__);
+ return ret;
+}
+
+void get_spk_timestamp(struct aec_t* aec, ssize_t read_bytes, uint64_t* spk_time) {
+ *spk_time = 0;
+ uint64_t spk_time_offset = 0;
+ float usec_per_byte = 1E6 / ((float)(aec->spk_frame_size_bytes * aec->spk_sampling_rate));
+ if (aec->read_write_diff_bytes < 0) {
+ /* We're still reading a previous write packet. (We only need the first sample's timestamp,
+ * so even if we straddle packets we only care about the first one)
+ * So we just use the previous timestamp, with an appropriate offset
+ * based on the number of bytes remaining to be read from that write packet. */
+ spk_time_offset = (aec->last_spk_info.bytes + aec->read_write_diff_bytes) * usec_per_byte;
+ ALOGV("Reusing previous timestamp, calculated offset (usec) %" PRIu64, spk_time_offset);
+ } else {
+ /* If read_write_diff_bytes > 0, there are no new writes, so there won't be timestamps in
+ * the FIFO, and the check below will fail. */
+ if (!fifo_available_to_read(aec->ts_fifo)) {
+ ALOGE("Timestamp error: no new timestamps!");
+ return;
+ }
+ /* We just read valid data, so if we're here, we should have a valid timestamp to use. */
+ ssize_t ts_bytes = fifo_read(aec->ts_fifo, &aec->last_spk_info, sizeof(struct aec_info));
+ ALOGV("Read TS bytes: %zd, expected %zu", ts_bytes, sizeof(struct aec_info));
+ aec->read_write_diff_bytes -= aec->last_spk_info.bytes;
+ }
+
+ *spk_time = timespec_to_usec(aec->last_spk_info.timestamp) + spk_time_offset;
+
+ aec->read_write_diff_bytes += read_bytes;
+ struct aec_info spk_info = aec->last_spk_info;
+ while (aec->read_write_diff_bytes > 0) {
+ /* If read_write_diff_bytes > 0, it means that there are more write packet timestamps
+ * in FIFO (since there we read more valid data the size of the current timestamp's
+ * packet). Keep reading timestamps from FIFO to get to the most recent one. */
+ if (!fifo_available_to_read(aec->ts_fifo)) {
+ /* There are no more timestamps, we have the most recent one. */
+ ALOGV("At the end of timestamp FIFO, breaking...");
+ break;
+ }
+ fifo_read(aec->ts_fifo, &spk_info, sizeof(struct aec_info));
+ ALOGV("Fast-forwarded timestamp by %zd bytes, remaining bytes: %zd,"
+ " new timestamp (usec) %" PRIu64,
+ spk_info.bytes, aec->read_write_diff_bytes, timespec_to_usec(spk_info.timestamp));
+ aec->read_write_diff_bytes -= spk_info.bytes;
+ }
+ aec->last_spk_info = spk_info;
+}
+
+int get_reference_samples(struct aec_t* aec, void* buffer, struct aec_info* info) {
+ ALOGV("%s enter", __func__);
+
+ if (!aec->spk_initialized) {
+ ALOGE("%s called with no reference initialized", __func__);
+ return -EINVAL;
+ }
+
+ size_t bytes = info->bytes;
+ const size_t frames = bytes / aec->mic_frame_size_bytes;
+ const size_t sample_rate_ratio = aec->spk_sampling_rate / aec->mic_sampling_rate;
+
+ /* Read audio samples from FIFO */
+ const size_t req_bytes = frames * sample_rate_ratio * aec->spk_frame_size_bytes;
+ ssize_t available_bytes = 0;
+ unsigned int wait_count = MAX_READ_WAIT_TIME_MSEC;
+ while (true) {
+ available_bytes = fifo_available_to_read(aec->spk_fifo);
+ if (available_bytes >= req_bytes) {
+ break;
+ } else if (available_bytes < 0) {
+ ALOGE("fifo_read returned code %zu ", available_bytes);
+ return -ENOMEM;
+ }
+
+ ALOGV("Sleeping, required bytes: %zu, available bytes: %zd", req_bytes, available_bytes);
+ usleep(1000);
+ if ((wait_count--) == 0) {
+ ALOGE("Timed out waiting for read from reference FIFO");
+ return -ETIMEDOUT;
+ }
+ }
+
+ const size_t read_bytes = fifo_read(aec->spk_fifo, aec->spk_buf_playback_format, req_bytes);
+
+ /* Get timestamp*/
+ get_spk_timestamp(aec, read_bytes, &info->timestamp_usec);
+
+ /* Get reference - could be mono, downmixed from multichannel.
+ * Reference stored at spk_buf_playback_format */
+ const size_t resampler_in_frames = frames * sample_rate_ratio;
+ get_reference_audio_in_place(aec, resampler_in_frames);
+
+ int16_t* resampler_out_buf;
+ /* Resample to mic sampling rate (16-bit resampler) */
+ if (aec->spk_resampler != NULL) {
+ size_t in_frame_count = resampler_in_frames;
+ size_t out_frame_count = frames;
+ aec->spk_resampler->resample_from_input(aec->spk_resampler, aec->spk_buf_playback_format,
+ &in_frame_count, aec->spk_buf_resampler_out,
+ &out_frame_count);
+ resampler_out_buf = aec->spk_buf_resampler_out;
+ } else {
+ if (sample_rate_ratio != 1) {
+ ALOGE("Speaker sample rate %d, mic sample rate %d but no resampler defined!",
+ aec->spk_sampling_rate, aec->mic_sampling_rate);
+ }
+ resampler_out_buf = aec->spk_buf_playback_format;
+ }
+
+ /* Convert to 32 bit */
+ int16_t* src16 = resampler_out_buf;
+ int32_t* dst32 = buffer;
+ size_t frame, ch;
+ for (frame = 0; frame < frames; frame++) {
+ for (ch = 0; ch < aec->num_reference_channels; ch++) {
+ *dst32++ = ((int32_t)*src16++) << 16;
+ }
+ }
+
+ info->bytes = bytes;
+
+ ALOGV("%s exit", __func__);
+ return 0;
+}
+
int init_aec_mic_config(struct aec_t *aec, struct alsa_stream_in *in) {
ALOGV("%s enter", __func__);
#if DEBUG_AEC
@@ -251,6 +454,9 @@
int ret = 0;
pthread_mutex_lock(&aec->lock);
+ if (aec->mic_initialized) {
+ destroy_aec_mic_config_no_lock(aec);
+ }
aec->mic_sampling_rate = in->config.rate;
aec->mic_frame_size_bytes = audio_stream_in_frame_size(&in->stream);
aec->mic_num_channels = in->config.channels;
@@ -287,21 +493,25 @@
goto exit_3;
}
- int resampler_ret = create_resampler(
- aec->spk_sampling_rate,
- in->config.rate,
- aec->num_reference_channels,
- RESAMPLER_QUALITY_MAX - 1, /* MAX - 1 is the real max */
- NULL, /* resampler_buffer_provider */
- &aec->spk_resampler);
- if (resampler_ret) {
- ALOGE("AEC: Resampler initialization failed! Error code %d", resampler_ret);
- ret = resampler_ret;
- goto exit_4;
+ /* Don't use resampler if it's not required */
+ if (in->config.rate == aec->spk_sampling_rate) {
+ aec->spk_resampler = NULL;
+ } else {
+ int resampler_ret = create_resampler(
+ aec->spk_sampling_rate, in->config.rate, aec->num_reference_channels,
+ RESAMPLER_QUALITY_MAX - 1, /* MAX - 1 is the real max */
+ NULL, /* resampler_buffer_provider */
+ &aec->spk_resampler);
+ if (resampler_ret) {
+ ALOGE("AEC: Resampler initialization failed! Error code %d", resampler_ret);
+ ret = resampler_ret;
+ goto exit_4;
+ }
}
flush_aec_fifos(aec);
aec_spk_mic_reset();
+ aec->mic_initialized = true;
exit:
pthread_mutex_unlock(&aec->lock);
@@ -324,7 +534,7 @@
void aec_set_spk_running(struct aec_t *aec, bool state) {
ALOGV("%s enter", __func__);
pthread_mutex_lock(&aec->lock);
- aec->spk_running = state;
+ aec_set_spk_running_no_lock(aec, state);
pthread_mutex_unlock(&aec->lock);
ALOGV("%s exit", __func__);
}
@@ -332,113 +542,26 @@
bool aec_get_spk_running(struct aec_t *aec) {
ALOGV("%s enter", __func__);
pthread_mutex_lock(&aec->lock);
- bool state = aec->spk_running;
+ bool state = aec_get_spk_running_no_lock(aec);
pthread_mutex_unlock(&aec->lock);
ALOGV("%s exit", __func__);
return state;
}
-void destroy_aec_reference_config(struct aec_t *aec) {
+void destroy_aec_mic_config(struct aec_t* aec) {
ALOGV("%s enter", __func__);
if (aec == NULL) {
ALOGV("%s exit", __func__);
return;
}
+
pthread_mutex_lock(&aec->lock);
- aec_set_spk_running(aec, false);
- fifo_release(aec->spk_fifo);
- fifo_release(aec->ts_fifo);
- memset(&aec->last_spk_info, 0, sizeof(struct aec_info));
+ destroy_aec_mic_config_no_lock(aec);
pthread_mutex_unlock(&aec->lock);
ALOGV("%s exit", __func__);
}
-void destroy_aec_mic_config(struct aec_t *aec) {
- ALOGV("%s enter", __func__);
- if (aec == NULL) {
- ALOGV("%s exit", __func__);
- return;
- }
- pthread_mutex_lock(&aec->lock);
- release_resampler(aec->spk_resampler);
- free(aec->mic_buf);
- free(aec->spk_buf);
- free(aec->spk_buf_playback_format);
- free(aec->spk_buf_resampler_out);
- memset(&aec->last_mic_info, 0, sizeof(struct aec_info));
- pthread_mutex_unlock(&aec->lock);
- ALOGV("%s exit", __func__);
-}
-
-int write_to_reference_fifo (struct aec_t *aec, void *buffer, struct aec_info *info) {
- ALOGV("%s enter", __func__);
- int ret = 0;
- size_t bytes = info->bytes;
-
- /* Write audio samples to FIFO */
- ssize_t written_bytes = fifo_write(aec->spk_fifo, buffer, bytes);
- if (written_bytes != bytes) {
- ALOGE("Could only write %zu of %zu bytes", written_bytes, bytes);
- ret = -ENOMEM;
- }
-
- /* Write timestamp to FIFO */
- info->bytes = written_bytes;
- ALOGV("Speaker timestamp: %ld s, %ld nsec", info->timestamp.tv_sec, info->timestamp.tv_nsec);
- ssize_t ts_bytes = fifo_write(aec->ts_fifo, info, sizeof(struct aec_info));
- ALOGV("Wrote TS bytes: %zu", ts_bytes);
- print_queue_status_to_log(aec, true);
- ALOGV("%s exit", __func__);
- return ret;
-}
-
-void get_spk_timestamp(struct aec_t *aec, ssize_t read_bytes, uint64_t *spk_time) {
- *spk_time = 0;
- uint64_t spk_time_offset = 0;
- float usec_per_byte = 1E6 / ((float)(aec->spk_frame_size_bytes * aec->spk_sampling_rate));
- if (aec->read_write_diff_bytes < 0) {
- /* We're still reading a previous write packet. (We only need the first sample's timestamp,
- * so even if we straddle packets we only care about the first one)
- * So we just use the previous timestamp, with an appropriate offset
- * based on the number of bytes remaining to be read from that write packet. */
- spk_time_offset = (aec->last_spk_info.bytes + aec->read_write_diff_bytes) * usec_per_byte;
- ALOGV("Reusing previous timestamp, calculated offset (usec) %"PRIu64, spk_time_offset);
- } else {
- /* If read_write_diff_bytes > 0, there are no new writes, so there won't be timestamps in
- * the FIFO, and the check below will fail. */
- if (!fifo_available_to_read(aec->ts_fifo)) {
- ALOGE("Timestamp error: no new timestamps!");
- return;
- }
- /* We just read valid data, so if we're here, we should have a valid timestamp to use. */
- ssize_t ts_bytes = fifo_read(aec->ts_fifo, &aec->last_spk_info,
- sizeof(struct aec_info));
- ALOGV("Read TS bytes: %zd, expected %zu", ts_bytes, sizeof(struct aec_info));
- aec->read_write_diff_bytes -= aec->last_spk_info.bytes;
- }
-
- *spk_time = timespec_to_usec(aec->last_spk_info.timestamp) + spk_time_offset;
-
- aec->read_write_diff_bytes += read_bytes;
- struct aec_info spk_info = aec->last_spk_info;
- while (aec->read_write_diff_bytes > 0) {
- /* If read_write_diff_bytes > 0, it means that there are more write packet timestamps
- * in FIFO (since there we read more valid data the size of the current timestamp's
- * packet). Keep reading timestamps from FIFO to get to the most recent one. */
- if (!fifo_available_to_read(aec->ts_fifo)) {
- /* There are no more timestamps, we have the most recent one. */
- ALOGV("At the end of timestamp FIFO, breaking...");
- break;
- }
- fifo_read(aec->ts_fifo, &spk_info, sizeof(struct aec_info));
- ALOGV("Fast-forwarded timestamp by %zd bytes, remaining bytes: %zd,"
- " new timestamp (usec) %"PRIu64,
- spk_info.bytes, aec->read_write_diff_bytes, timespec_to_usec(spk_info.timestamp));
- aec->read_write_diff_bytes -= spk_info.bytes;
- }
- aec->last_spk_info = spk_info;
-}
-
+#ifdef AEC_HAL
int process_aec(struct aec_t *aec, void* buffer, struct aec_info *info) {
ALOGV("%s enter", __func__);
int ret = 0;
@@ -448,6 +571,12 @@
return -EINVAL;
}
+ if ((!aec->mic_initialized) || (!aec->spk_initialized)) {
+ ALOGE("%s called with initialization: mic: %d, spk: %d", __func__, aec->mic_initialized,
+ aec->spk_initialized);
+ return -EINVAL;
+ }
+
size_t bytes = info->bytes;
size_t frame_size = aec->mic_frame_size_bytes;
@@ -477,11 +606,6 @@
flush_aec_fifos(aec);
}
- size_t spk_frame_size_bytes = aec->spk_frame_size_bytes;
- size_t sample_rate_ratio = aec->spk_sampling_rate / aec->mic_sampling_rate;
- size_t resampler_in_frames = in_frames * sample_rate_ratio;
- size_t req_bytes = resampler_in_frames * spk_frame_size_bytes;
-
/* If there's no data in FIFO, exit */
if (fifo_available_to_read(aec->spk_fifo) <= 0) {
ALOGV("Echo reference buffer empty, zeroing reference....");
@@ -490,48 +614,18 @@
print_queue_status_to_log(aec, false);
- /* Read from FIFO */
- ssize_t read_bytes = fifo_read(aec->spk_fifo, aec->spk_buf_playback_format, req_bytes);
- get_spk_timestamp(aec, read_bytes, &spk_time);
+ /* Get reference, with format and sample rate required by AEC */
+ struct aec_info spk_info;
+ spk_info.bytes = bytes;
+ int ref_ret = get_reference_samples(aec, aec->spk_buf, &spk_info);
+ spk_time = spk_info.timestamp_usec;
- if (read_bytes < req_bytes) {
- ALOGI("Could only read %zd of %zu bytes", read_bytes, req_bytes);
- if (read_bytes > 0) {
- memmove(aec->spk_buf_playback_format + req_bytes - read_bytes,
- aec->spk_buf_playback_format, read_bytes);
- memset(aec->spk_buf_playback_format, 0, req_bytes - read_bytes);
- } else {
- ALOGE("Fifo read returned code %zd ", read_bytes);
- ret = -ENOMEM;
- goto exit;
- }
+ if (ref_ret) {
+ ALOGE("get_reference_samples returned code %d", ref_ret);
+ ret = -ENOMEM;
+ goto exit;
}
- /* Get reference - could be mono, downmixed from multichannel.
- * Reference stored at spk_buf_playback_format */
- get_reference_audio_in_place(aec, resampler_in_frames);
-
- /* Resample to mic sampling rate (16-bit resampler) */
- size_t in_frame_count = resampler_in_frames;
- size_t out_frame_count = in_frames;
- aec->spk_resampler->resample_from_input(
- aec->spk_resampler,
- aec->spk_buf_playback_format,
- &in_frame_count,
- aec->spk_buf_resampler_out,
- &out_frame_count);
-
- /* Convert to 32 bit */
- int16_t *src16 = aec->spk_buf_resampler_out;
- int32_t *dst32 = aec->spk_buf;
- size_t frame, ch;
- for (frame = 0; frame < in_frames; frame++) {
- for (ch = 0; ch < aec->num_reference_channels; ch++) {
- *dst32++ = ((int32_t)*src16++) << 16;
- }
- }
-
-
int64_t time_diff = (mic_time > spk_time) ? (mic_time - spk_time) : (spk_time - mic_time);
if ((spk_time == 0) || (mic_time == 0) || (time_diff > MAX_TIMESTAMP_DIFF_USEC)) {
ALOGV("Speaker-mic timestamps diverged, skipping AEC");
@@ -602,3 +696,5 @@
ALOGV("%s exit", __func__);
return ret;
}
+
+#endif /*#ifdef AEC_HAL*/
diff --git a/audio/audio_aec.h b/audio/audio_aec.h
index 9cfc9aa..daa7fb2 100644
--- a/audio/audio_aec.h
+++ b/audio/audio_aec.h
@@ -36,12 +36,14 @@
struct aec_t {
pthread_mutex_t lock;
size_t num_reference_channels;
+ bool mic_initialized;
int32_t *mic_buf;
size_t mic_num_channels;
size_t mic_buf_size_bytes;
size_t mic_frame_size_bytes;
uint32_t mic_sampling_rate;
struct aec_info last_mic_info;
+ bool spk_initialized;
int32_t *spk_buf;
size_t spk_num_channels;
size_t spk_buf_size_bytes;
@@ -58,41 +60,33 @@
bool prev_spk_running;
};
-#ifdef AEC_HAL
-
-/* Write audio samples to AEC reference FIFO for use in AEC.
- * Both audio samples and timestamps are added in FIFO fashion.
- * Must be called after every write to PCM. */
-int write_to_reference_fifo (struct aec_t *aec, void *buffer, struct aec_info *info);
-
-/* Processing function call for AEC.
- * AEC output is updated at location pointed to by 'buffer'.
- * This function does not run AEC when there is no playback -
- * as communicated to this AEC interface using aec_set_spk_running().*/
-int process_aec (struct aec_t *aec, void* buffer, struct aec_info *info);
-
/* Initialize AEC object.
* This must be called when the audio device is opened.
- * ALSA device mutex must be held before calling this API.*/
+ * ALSA device mutex must be held before calling this API.
+ * Returns -EINVAL if AEC object fails to initialize, else returns 0. */
int init_aec (int sampling_rate, int num_reference_channels,
int num_microphone_channels, struct aec_t **);
/* Release AEC object.
* This must be called when the audio device is closed. */
-void release_aec(struct aec_t *aec);
+void release_aec(struct aec_t* aec);
/* Initialize reference configuration for AEC.
- * Must be called when a new output stream is opened. */
+ * Must be called when a new output stream is opened.
+ * Returns -EINVAL if any processing block fails to initialize,
+ * else returns 0. */
int init_aec_reference_config (struct aec_t *aec, struct alsa_stream_out *out);
-/* Initialize microphone configuration for AEC.
- * Must be called when a new input stream is opened. */
-int init_aec_mic_config (struct aec_t *aec, struct alsa_stream_in *in);
-
/* Clear reference configuration for AEC.
* Must be called when the output stream is closed. */
void destroy_aec_reference_config (struct aec_t *aec);
+/* Initialize microphone configuration for AEC.
+ * Must be called when a new input stream is opened.
+ * Returns -EINVAL if any processing block fails to initialize,
+ * else returns 0. */
+int init_aec_mic_config(struct aec_t* aec, struct alsa_stream_in* in);
+
/* Clear microphone configuration for AEC.
* Must be called when the input stream is closed. */
void destroy_aec_mic_config (struct aec_t *aec);
@@ -101,17 +95,34 @@
* This is used by process_aec() to determine if AEC processing is to be run. */
void aec_set_spk_running (struct aec_t *aec, bool state);
+/* Write audio samples to AEC reference FIFO for use in AEC.
+ * Both audio samples and timestamps are added in FIFO fashion.
+ * Must be called after every write to PCM.
+ * Returns -ENOMEM if the write fails, else returns 0. */
+int write_to_reference_fifo(struct aec_t* aec, void* buffer, struct aec_info* info);
+
+/* Get reference audio samples + timestamp, in the format expected by AEC,
+ * i.e. same sample rate and bit rate as microphone audio.
+ * Timestamp is updated in field 'timestamp_usec', and not in 'timestamp'.
+ * Returns:
+ * -EINVAL if the AEC object is invalid.
+ * -ENOMEM if the reference FIFO overflows or is corrupted.
+ * -ETIMEDOUT if we timed out waiting for the requested number of bytes
+ * 0 otherwise */
+int get_reference_samples(struct aec_t* aec, void* buffer, struct aec_info* info);
+
+#ifdef AEC_HAL
+
+/* Processing function call for AEC.
+ * AEC output is updated at location pointed to by 'buffer'.
+ * This function does not run AEC when there is no playback -
+ * as communicated to this AEC interface using aec_set_spk_running().
+ * Returns -EINVAL if processing fails, else returns 0. */
+int process_aec(struct aec_t* aec, void* buffer, struct aec_info* info);
+
#else /* #ifdef AEC_HAL */
-#define write_to_reference_fifo(...) ((int)0)
#define process_aec(...) ((int)0)
-#define init_aec(...) ((int)0)
-#define release_aec(...) ((void)0)
-#define init_aec_reference_config(...) ((int)0)
-#define init_aec_mic_config(...) ((int)0)
-#define destroy_aec_reference_config(...) ((void)0)
-#define destroy_aec_mic_config(...) ((void)0)
-#define aec_set_spk_running(...) ((void)0)
#endif /* #ifdef AEC_HAL */
diff --git a/audio/audio_hw.c b/audio/audio_hw.c
index cd72172..ac886db 100644
--- a/audio/audio_hw.c
+++ b/audio/audio_hw.c
@@ -384,12 +384,10 @@
return -ENOSYS;
}
-static size_t get_input_buffer_size(audio_format_t format,
- audio_channel_mask_t channel_mask)
-{
+static size_t get_input_buffer_size(size_t frames, audio_format_t format,
+ audio_channel_mask_t channel_mask) {
/* return the closest majoring multiple of 16 frames, as
* audioflinger expects audio buffers to be a multiple of 16 frames */
- size_t frames = CAPTURE_PERIOD_SIZE;
frames = ((frames + 15) / 16) * 16;
size_t bytes_per_frame = audio_channel_count_from_in_mask(channel_mask) *
audio_bytes_per_sample(format);
@@ -418,8 +416,14 @@
static size_t in_get_buffer_size(const struct audio_stream *stream)
{
- size_t buffer_size = get_input_buffer_size(stream->get_format(stream),
- stream->get_channels(stream));
+ struct alsa_stream_in* in = (struct alsa_stream_in*)stream;
+ size_t frames = CAPTURE_PERIOD_SIZE;
+ if (in->source == AUDIO_SOURCE_ECHO_REFERENCE) {
+ frames = CAPTURE_PERIOD_SIZE * PLAYBACK_CODEC_SAMPLING_RATE / CAPTURE_CODEC_SAMPLING_RATE;
+ }
+
+ size_t buffer_size =
+ get_input_buffer_size(frames, stream->get_format(stream), stream->get_channels(stream));
ALOGV("in_get_buffer_size: %zu", buffer_size);
return buffer_size;
}
@@ -474,14 +478,42 @@
static ssize_t in_read(struct audio_stream_in *stream, void* buffer,
size_t bytes)
{
- ALOGV("in_read: bytes %zu", bytes);
-
int ret;
struct alsa_stream_in *in = (struct alsa_stream_in *)stream;
struct alsa_audio_device *adev = in->dev;
size_t frame_size = audio_stream_in_frame_size(stream);
size_t in_frames = bytes / frame_size;
+ ALOGV("in_read: stream: %d, bytes %zu", in->source, bytes);
+
+ /* Special handling for Echo Reference: simply get the reference from FIFO.
+ * The format and sample rate should be specified by arguments to adev_open_input_stream. */
+ if (in->source == AUDIO_SOURCE_ECHO_REFERENCE) {
+ struct aec_info info;
+ info.bytes = bytes;
+
+ if (!adev->aec->spk_running) {
+ memset(buffer, 0, bytes);
+ usleep((int64_t)bytes * 1000000 / audio_stream_in_frame_size(stream) /
+ in_get_sample_rate(&stream->common));
+ } else {
+ get_reference_samples(adev->aec, buffer, &info);
+ }
+
+#if DEBUG_AEC
+ FILE* fp_ref = fopen("/data/local/traces/aec_ref.pcm", "a+");
+ if (fp_ref) {
+ fwrite((char*)buffer, 1, bytes, fp_ref);
+ fclose(fp_ref);
+ } else {
+ ALOGE("AEC debug: Could not open file aec_ref.pcm!");
+ }
+#endif
+ return info.bytes;
+ }
+
+ /* Microphone input stream read */
+
/* acquiring hw device mutex systematically is useful if a low priority thread is waiting
* on the input stream mutex - e.g. executing select_mode() while holding the hw device
* mutex
@@ -525,6 +557,7 @@
struct aec_info info;
get_pcm_timestamp(in->pcm, in->config.rate, &info);
info.bytes = bytes;
+
int aec_ret = process_aec(adev->aec, buffer, &info);
if (aec_ret) {
ALOGE("process_aec returned error code %d", aec_ret);
@@ -532,6 +565,16 @@
}
}
+#if DEBUG_AEC && !defined(AEC_HAL)
+ FILE* fp_in = fopen("/data/local/traces/aec_in.pcm", "a+");
+ if (fp_in) {
+ fwrite((char*)buffer, 1, bytes, fp_in);
+ fclose(fp_in);
+ } else {
+ ALOGE("AEC debug: Could not open file aec_in.pcm!");
+ }
+#endif
+
return bytes;
}
@@ -740,21 +783,17 @@
static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev,
const struct audio_config *config)
{
- size_t buffer_size = get_input_buffer_size(config->format, config->channel_mask);
+ size_t buffer_size =
+ get_input_buffer_size(CAPTURE_PERIOD_SIZE, config->format, config->channel_mask);
ALOGV("adev_get_input_buffer_size: %zu", buffer_size);
return buffer_size;
}
-static int adev_open_input_stream(struct audio_hw_device *dev,
- audio_io_handle_t handle,
- audio_devices_t devices,
- struct audio_config *config,
- struct audio_stream_in **stream_in,
- audio_input_flags_t flags __unused,
- const char *address __unused,
- audio_source_t source __unused)
-{
-
+static int adev_open_input_stream(struct audio_hw_device* dev, audio_io_handle_t handle,
+ audio_devices_t devices, struct audio_config* config,
+ struct audio_stream_in** stream_in,
+ audio_input_flags_t flags __unused, const char* address __unused,
+ audio_source_t source) {
ALOGV("adev_open_input_stream...");
struct alsa_audio_device *ladev = (struct alsa_audio_device *)dev;
@@ -787,7 +826,11 @@
in->stream.get_input_frames_lost = in_get_input_frames_lost;
in->config.channels = CHANNEL_STEREO;
- in->config.rate = CAPTURE_CODEC_SAMPLING_RATE;
+ if (source == AUDIO_SOURCE_ECHO_REFERENCE) {
+ in->config.rate = PLAYBACK_CODEC_SAMPLING_RATE;
+ } else {
+ in->config.rate = CAPTURE_CODEC_SAMPLING_RATE;
+ }
in->config.format = PCM_FORMAT_S32_LE;
in->config.period_size = CAPTURE_PERIOD_SIZE;
in->config.period_count = CAPTURE_PERIOD_COUNT;
@@ -798,18 +841,26 @@
ret = -EINVAL;
}
- ALOGI("adev_open_input_stream selects channels=%d rate=%d format=%d",
- in->config.channels, in->config.rate, in->config.format);
+ ALOGI("adev_open_input_stream selects channels=%d rate=%d format=%d source=%d",
+ in->config.channels, in->config.rate, in->config.format, source);
in->dev = ladev;
in->standby = true;
in->unavailable = false;
+ in->source = source;
config->format = in_get_format(&in->stream.common);
config->channel_mask = in_get_channels(&in->stream.common);
config->sample_rate = in_get_sample_rate(&in->stream.common);
- if (ret == 0) {
+ /* If AEC is in the app, only configure based on ECHO_REFERENCE spec.
+ * If AEC is in the HAL, configure using the given mic stream. */
+ bool aecInput = true;
+#if !defined(AEC_HAL)
+ aecInput = (in->source == AUDIO_SOURCE_ECHO_REFERENCE);
+#endif
+
+ if ((ret == 0) && aecInput) {
int aec_ret = init_aec_mic_config(ladev->aec, in);
if (aec_ret) {
ALOGE("AEC: Mic config init failed!");
@@ -823,6 +874,10 @@
*stream_in = &in->stream;
}
+#if DEBUG_AEC
+ remove("/data/local/traces/aec_ref.pcm");
+ remove("/data/local/traces/aec_in.pcm");
+#endif
return ret;
}
diff --git a/audio/audio_hw.h b/audio/audio_hw.h
index 0eb786d..5ef6584 100644
--- a/audio/audio_hw.h
+++ b/audio/audio_hw.h
@@ -30,7 +30,15 @@
#define CODEC_BASE_FRAME_COUNT 32
#define CHANNEL_STEREO 2
+
+#ifdef AEC_HAL
#define NUM_AEC_REFERENCE_CHANNELS 1
+#else
+/* App AEC uses 2-channel reference */
+#define NUM_AEC_REFERENCE_CHANNELS 2
+#endif /* #ifdef AEC_HAL */
+
+#define DEBUG_AEC 0
#define PCM_OPEN_RETRIES 100
#define PCM_OPEN_WAIT_TIME_MS 20
@@ -80,6 +88,7 @@
struct alsa_audio_device *dev;
int read_threshold;
unsigned int read;
+ audio_source_t source;
};
struct alsa_stream_out {
@@ -102,6 +111,7 @@
* datatypes as the corresponding arguments to that function. */
struct aec_info {
struct timespec timestamp;
+ uint64_t timestamp_usec;
unsigned int available;
size_t bytes;
};
diff --git a/audio/audio_policy_configuration.xml b/audio/audio_policy_configuration.xml
index 6c696cb..b62a329 100644
--- a/audio/audio_policy_configuration.xml
+++ b/audio/audio_policy_configuration.xml
@@ -49,6 +49,7 @@
<item>Speaker</item>
<item>Built-In Mic</item>
<item>Built-In Back Mic</item>
+ <item>Echo Reference</item>
</attachedDevices>
<defaultOutputDevice>Speaker</defaultOutputDevice>
<mixPorts>
@@ -82,6 +83,16 @@
samplingRates="8000,11025,12000,16000,22050,24000,32000,44100,48000"
channelMasks="AUDIO_CHANNEL_IN_MONO,AUDIO_CHANNEL_IN_STEREO,AUDIO_CHANNEL_IN_FRONT_BACK"/>
</mixPort>
+ <mixPort name="built-in mic" role="sink">
+ <profile name="" format="AUDIO_FORMAT_PCM_32_BIT"
+ samplingRates="16000"
+ channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
+ </mixPort>
+ <mixPort name="echo reference" role="sink">
+ <profile name="echo_reference" format="AUDIO_FORMAT_PCM_32_BIT"
+ samplingRates="48000"
+ channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
+ </mixPort>
<mixPort name="voice_rx" role="sink">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="8000,16000" channelMasks="AUDIO_CHANNEL_IN_MONO"/>
@@ -123,9 +134,9 @@
</devicePort>
<devicePort tagName="Built-In Mic" type="AUDIO_DEVICE_IN_BUILTIN_MIC" role="source">
- <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
- samplingRates="8000,11025,12000,16000,22050,24000,32000,44100,48000"
- channelMasks="AUDIO_CHANNEL_IN_MONO,AUDIO_CHANNEL_IN_STEREO,AUDIO_CHANNEL_IN_FRONT_BACK"/>
+ <profile name="" format="AUDIO_FORMAT_PCM_32_BIT"
+ samplingRates="16000"
+ channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
</devicePort>
<devicePort tagName="Built-In Back Mic" type="AUDIO_DEVICE_IN_BACK_MIC" role="source">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
@@ -145,6 +156,11 @@
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="8000,16000" channelMasks="AUDIO_CHANNEL_IN_MONO"/>
</devicePort>
+ <devicePort tagName="Echo Reference" type="AUDIO_DEVICE_IN_ECHO_REFERENCE" role="source">
+ <profile name="echo_reference" format="AUDIO_FORMAT_PCM_32_BIT"
+ samplingRates="48000"
+ channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
+ </devicePort>
</devicePorts>
<!-- route declaration, i.e. list all available sources for a given sink -->
<routes>
@@ -158,6 +174,10 @@
sources="primary output,deep_buffer,compressed_offload,BT SCO Headset Mic,Telephony Rx"/>
<route type="mix" sink="primary input"
sources="Built-In Mic,Built-In Back Mic,Wired Headset Mic,BT SCO Headset Mic"/>
+ <route type="mix" sink="built-in mic"
+ sources="Built-In Mic"/>
+ <route type="mix" sink="echo reference"
+ sources="Echo Reference"/>
<route type="mix" sink="Telephony Tx"
sources="Built-In Mic,Built-In Back Mic,Wired Headset Mic,BT SCO Headset Mic,voice_tx"/>
<route type="mix" sink="voice_rx"
diff --git a/device-yukawa.mk b/device-yukawa.mk
index 263b9fc..b6fd8c4 100644
--- a/device-yukawa.mk
+++ b/device-yukawa.mk
@@ -16,3 +16,6 @@
PRODUCT_COPY_FILES += $(LOCAL_DTB):meson-sm1-sei610.dtb
+# Feature permissions
+PRODUCT_COPY_FILES += \
+ device/amlogic/yukawa/permissions/yukawa.xml:/system/etc/sysconfig/yukawa.xml
diff --git a/permissions/yukawa.xml b/permissions/yukawa.xml
new file mode 100644
index 0000000..41aad35
--- /dev/null
+++ b/permissions/yukawa.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<permissions>
+ <feature name="com.google.android.feature.ECHO_REFERENCE" />
+</permissions>