| /* |
| * 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. |
| */ |
| |
| // clang-format off |
| /* |
| * Typical AEC signal flow: |
| * |
| * Microphone Audio |
| * Timestamps |
| * +--------------------------------------+ |
| * | | +---------------+ |
| * | Microphone +---------------+ | | | |
| * O|====== | Audio | Sample Rate | +-------> | |
| * (from . +--+ Samples | + | | | |
| * mic . +==================> Format |==============> | |
| * codec) . | Conversion | | | Cleaned |
| * O|====== | (if required) | | Acoustic | Audio |
| * +---------------+ | Echo | Samples |
| * | Canceller |===================> |
| * | (AEC) | |
| * Reference +---------------+ | | |
| * Audio | Sample Rate | | | |
| * Samples | + | | | |
| * +=============> Format |==============> | |
| * | | Conversion | | | |
| * | | (if required) | +-------> | |
| * | +---------------+ | | | |
| * | | +---------------+ |
| * | +-------------------------------+ |
| * | | Reference Audio |
| * | | Timestamps |
| * | | |
| * +--+----+---------+ AUDIO CAPTURE |
| * | Speaker | |
| * +------------+ Audio/Timestamp +---------------------------------------------------------------------------+ |
| * | Buffer | |
| * +--^----^---------+ AUDIO PLAYBACK |
| * | | |
| * | | |
| * | | |
| * | | |
| * |\ | | |
| * | +-+ | | |
| * (to | | +-----C----+ |
| * speaker | | | | Playback |
| * codec) | | <=====+================================================================+ Audio |
| * | +-+ Samples |
| * |/ |
| * |
| */ |
| // clang-format on |
| |
| #define LOG_TAG "audio_hw_aec" |
| // #define LOG_NDEBUG 0 |
| |
| #include <audio_utils/primitives.h> |
| #include <stdio.h> |
| #include <inttypes.h> |
| #include <errno.h> |
| #include <malloc.h> |
| #include <sys/time.h> |
| #include <tinyalsa/asoundlib.h> |
| #include <unistd.h> |
| #include <log/log.h> |
| #include "audio_aec.h" |
| |
| #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); |
| } |
| |
| void get_reference_audio_in_place(struct aec_t *aec, size_t frames) { |
| if (aec->num_reference_channels == aec->spk_num_channels) { |
| /* Reference count equals speaker channels, nothing to do here. */ |
| return; |
| } else if (aec->num_reference_channels != 1) { |
| /* We don't have a rule for non-mono references, show error on log */ |
| ALOGE("Invalid reference count - must be 1 or match number of playback channels!"); |
| return; |
| } |
| int16_t *src_Nch = &aec->spk_buf_playback_format[0]; |
| int16_t *dst_1ch = &aec->spk_buf_playback_format[0]; |
| int32_t num_channels = (int32_t)aec->spk_num_channels; |
| size_t frame, ch; |
| for (frame = 0; frame < frames; frame++) { |
| int32_t acc = 0; |
| for (ch = 0; ch < aec->spk_num_channels; ch++) { |
| acc += src_Nch[ch]; |
| } |
| *dst_1ch++ = clamp16(acc/num_channels); |
| src_Nch += aec->spk_num_channels; |
| } |
| } |
| |
| void print_queue_status_to_log(struct aec_t *aec, bool write_side) { |
| ssize_t q1 = fifo_available_to_read(aec->spk_fifo); |
| ssize_t q2 = fifo_available_to_read(aec->ts_fifo); |
| |
| ALOGV("Queue available %s: Spk %zd (count %zd) TS %zd (count %zd)", |
| (write_side) ? "(POST-WRITE)" : "(PRE-READ)", |
| q1, q1/aec->spk_frame_size_bytes/PLAYBACK_PERIOD_SIZE, |
| q2, q2/sizeof(struct aec_info)); |
| } |
| |
| void flush_aec_fifos(struct aec_t *aec) { |
| if (aec == NULL) { |
| return; |
| } |
| if (aec->spk_fifo != NULL) { |
| ALOGV("Flushing AEC Spk FIFO..."); |
| fifo_flush(aec->spk_fifo); |
| } |
| if (aec->ts_fifo != NULL) { |
| ALOGV("Flushing AEC Timestamp FIFO..."); |
| fifo_flush(aec->ts_fifo); |
| } |
| /* Reset FIFO read-write offset tracker */ |
| 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)); |
| if (aec == NULL) { |
| ALOGE("Failed to allocate memory for AEC interface!"); |
| } else { |
| pthread_mutex_init(&aec->lock, NULL); |
| } |
| |
| ALOGV("%s exit", __func__); |
| return aec; |
| } |
| |
| void release_aec_interface(struct aec_t *aec) { |
| ALOGV("%s enter", __func__); |
| pthread_mutex_lock(&aec->lock); |
| 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__); |
| } |
| |
| int init_aec(int sampling_rate, int num_reference_channels, |
| int num_microphone_channels, struct aec_t **aec_ptr) { |
| ALOGV("%s enter", __func__); |
| int ret = 0; |
| int aec_ret = aec_spk_mic_init( |
| sampling_rate, |
| num_reference_channels, |
| num_microphone_channels); |
| if (aec_ret) { |
| ALOGE("AEC object failed to initialize!"); |
| ret = -EINVAL; |
| } |
| struct aec_t *aec = init_aec_interface(); |
| if (!ret) { |
| aec->num_reference_channels = num_reference_channels; |
| /* Set defaults, will be overridden by settings in init_aec_(mic|referece_config) */ |
| /* Capture uses 2-ch, 32-bit frames */ |
| aec->mic_sampling_rate = CAPTURE_CODEC_SAMPLING_RATE; |
| aec->mic_frame_size_bytes = CHANNEL_STEREO * sizeof(int32_t); |
| aec->mic_num_channels = CHANNEL_STEREO; |
| |
| /* Playback uses 2-ch, 16-bit frames */ |
| aec->spk_sampling_rate = PLAYBACK_CODEC_SAMPLING_RATE; |
| aec->spk_frame_size_bytes = CHANNEL_STEREO * sizeof(int16_t); |
| aec->spk_num_channels = CHANNEL_STEREO; |
| } |
| |
| (*aec_ptr) = aec; |
| ALOGV("%s exit", __func__); |
| return ret; |
| } |
| |
| void release_aec(struct aec_t *aec) { |
| ALOGV("%s enter", __func__); |
| if (aec == NULL) { |
| return; |
| } |
| release_aec_interface(aec); |
| aec_spk_mic_release(); |
| ALOGV("%s exit", __func__); |
| } |
| |
| int init_aec_reference_config(struct aec_t *aec, struct alsa_stream_out *out) { |
| ALOGV("%s enter", __func__); |
| if (!aec) { |
| ALOGE("AEC: No valid interface found!"); |
| return -EINVAL; |
| } |
| |
| 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), |
| false /* reader_throttles_writer */); |
| if (aec->spk_fifo == NULL) { |
| ALOGE("AEC: Speaker loopback FIFO Init failed!"); |
| ret = -EINVAL; |
| goto exit; |
| } |
| aec->ts_fifo = fifo_init( |
| out->config.period_count * sizeof(struct aec_info), |
| false /* reader_throttles_writer */); |
| if (aec->ts_fifo == NULL) { |
| ALOGE("AEC: Speaker timestamp FIFO Init failed!"); |
| ret = -EINVAL; |
| fifo_release(aec->spk_fifo); |
| goto exit; |
| } |
| |
| 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 |
| remove("/data/local/traces/aec_in.pcm"); |
| remove("/data/local/traces/aec_out.pcm"); |
| remove("/data/local/traces/aec_ref.pcm"); |
| remove("/data/local/traces/aec_timestamps.txt"); |
| #endif /* #if DEBUG_AEC */ |
| |
| if (!aec) { |
| ALOGE("AEC: No valid interface found!"); |
| return -EINVAL; |
| } |
| |
| 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; |
| |
| aec->mic_buf_size_bytes = in->config.period_size * audio_stream_in_frame_size(&in->stream); |
| aec->mic_buf = (int32_t *)malloc(aec->mic_buf_size_bytes); |
| if (aec->mic_buf == NULL) { |
| ret = -ENOMEM; |
| goto exit; |
| } |
| memset(aec->mic_buf, 0, aec->mic_buf_size_bytes); |
| /* Reference buffer is the same number of frames as mic, |
| * only with a different number of channels in the frame. */ |
| aec->spk_buf_size_bytes = in->config.period_size * aec->spk_frame_size_bytes; |
| aec->spk_buf = (int32_t *)malloc(aec->spk_buf_size_bytes); |
| if (aec->spk_buf == NULL) { |
| ret = -ENOMEM; |
| goto exit_1; |
| } |
| memset(aec->spk_buf, 0, aec->spk_buf_size_bytes); |
| |
| /* Pre-resampler buffer */ |
| size_t spk_frame_out_format_bytes = aec->spk_sampling_rate / aec->mic_sampling_rate * |
| aec->spk_buf_size_bytes; |
| aec->spk_buf_playback_format = (int16_t *)malloc(spk_frame_out_format_bytes); |
| if (aec->spk_buf_playback_format == NULL) { |
| ret = -ENOMEM; |
| goto exit_2; |
| } |
| /* Resampler is 16-bit */ |
| aec->spk_buf_resampler_out = (int16_t *)malloc(aec->spk_buf_size_bytes); |
| if (aec->spk_buf_resampler_out == NULL) { |
| ret = -ENOMEM; |
| goto exit_3; |
| } |
| |
| /* 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); |
| ALOGV("%s exit", __func__); |
| return ret; |
| |
| exit_4: |
| free(aec->spk_buf_resampler_out); |
| exit_3: |
| free(aec->spk_buf_playback_format); |
| exit_2: |
| free(aec->spk_buf); |
| exit_1: |
| free(aec->mic_buf); |
| pthread_mutex_unlock(&aec->lock); |
| ALOGV("%s exit", __func__); |
| return ret; |
| } |
| |
| void aec_set_spk_running(struct aec_t *aec, bool state) { |
| ALOGV("%s enter", __func__); |
| pthread_mutex_lock(&aec->lock); |
| aec_set_spk_running_no_lock(aec, state); |
| pthread_mutex_unlock(&aec->lock); |
| ALOGV("%s exit", __func__); |
| } |
| |
| bool aec_get_spk_running(struct aec_t *aec) { |
| ALOGV("%s enter", __func__); |
| pthread_mutex_lock(&aec->lock); |
| bool state = aec_get_spk_running_no_lock(aec); |
| pthread_mutex_unlock(&aec->lock); |
| ALOGV("%s exit", __func__); |
| return state; |
| } |
| |
| 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); |
| destroy_aec_mic_config_no_lock(aec); |
| pthread_mutex_unlock(&aec->lock); |
| ALOGV("%s exit", __func__); |
| } |
| |
| #ifdef AEC_HAL |
| int process_aec(struct aec_t *aec, void* buffer, struct aec_info *info) { |
| ALOGV("%s enter", __func__); |
| int ret = 0; |
| |
| if (aec == NULL) { |
| ALOGE("AEC: Interface uninitialized! Cannot process."); |
| 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; |
| size_t in_frames = bytes / frame_size; |
| |
| /* Copy raw mic samples to AEC input buffer */ |
| memcpy(aec->mic_buf, buffer, bytes); |
| |
| uint64_t mic_time = timespec_to_usec(info->timestamp); |
| uint64_t spk_time = 0; |
| |
| /* |
| * Only run AEC if there is speaker playback. |
| * The first time speaker state changes to running, flush FIFOs, so we're not stuck |
| * processing stale reference input. |
| */ |
| bool spk_running = aec_get_spk_running(aec); |
| |
| if (!spk_running) { |
| /* No new playback samples, so don't run AEC. |
| * 'buffer' already contains input samples. */ |
| ALOGV("Speaker not running, skipping AEC.."); |
| goto exit; |
| } |
| |
| if (!aec->prev_spk_running) { |
| flush_aec_fifos(aec); |
| } |
| |
| /* If there's no data in FIFO, exit */ |
| if (fifo_available_to_read(aec->spk_fifo) <= 0) { |
| ALOGV("Echo reference buffer empty, zeroing reference...."); |
| goto exit; |
| } |
| |
| print_queue_status_to_log(aec, false); |
| |
| /* 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 (ref_ret) { |
| ALOGE("get_reference_samples returned code %d", ref_ret); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| 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"); |
| flush_aec_fifos(aec); |
| aec_spk_mic_reset(); |
| goto exit; |
| } |
| |
| ALOGV("Mic time: %"PRIu64", spk time: %"PRIu64, mic_time, spk_time); |
| |
| /* |
| * AEC processing call - output stored at 'buffer' |
| */ |
| int32_t aec_status = aec_spk_mic_process( |
| aec->spk_buf, spk_time, |
| aec->mic_buf, mic_time, |
| in_frames, |
| buffer); |
| |
| if (!aec_status) { |
| ALOGE("AEC processing failed!"); |
| ret = -EINVAL; |
| } |
| |
| exit: |
| aec->prev_spk_running = spk_running; |
| ALOGV("Mic time: %"PRIu64", spk time: %"PRIu64, mic_time, spk_time); |
| if (ret) { |
| /* Best we can do is copy over the raw mic signal */ |
| memcpy(buffer, aec->mic_buf, bytes); |
| flush_aec_fifos(aec); |
| aec_spk_mic_reset(); |
| } |
| |
| #if DEBUG_AEC |
| /* ref data is 32-bit at this point */ |
| size_t ref_bytes = in_frames*aec->num_reference_channels*sizeof(int32_t); |
| |
| FILE *fp_in = fopen("/data/local/traces/aec_in.pcm", "a+"); |
| if (fp_in) { |
| fwrite((char *)aec->mic_buf, 1, bytes, fp_in); |
| fclose(fp_in); |
| } else { |
| ALOGE("AEC debug: Could not open file aec_in.pcm!"); |
| } |
| FILE *fp_out = fopen("/data/local/traces/aec_out.pcm", "a+"); |
| if (fp_out) { |
| fwrite((char *)buffer, 1, bytes, fp_out); |
| fclose(fp_out); |
| } else { |
| ALOGE("AEC debug: Could not open file aec_out.pcm!"); |
| } |
| FILE *fp_ref = fopen("/data/local/traces/aec_ref.pcm", "a+"); |
| if (fp_ref) { |
| fwrite((char *)aec->spk_buf, 1, ref_bytes, fp_ref); |
| fclose(fp_ref); |
| } else { |
| ALOGE("AEC debug: Could not open file aec_ref.pcm!"); |
| } |
| FILE *fp_ts = fopen("/data/local/traces/aec_timestamps.txt", "a+"); |
| if (fp_ts) { |
| fprintf(fp_ts, "%"PRIu64",%"PRIu64"\n", mic_time, spk_time); |
| fclose(fp_ts); |
| } else { |
| ALOGE("AEC debug: Could not open file aec_timestamps.txt!"); |
| } |
| #endif /* #if DEBUG_AEC */ |
| ALOGV("%s exit", __func__); |
| return ret; |
| } |
| |
| #endif /*#ifdef AEC_HAL*/ |