diff options
Diffstat (limited to 'audio/audio_aec.c')
-rw-r--r-- | audio/audio_aec.c | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/audio/audio_aec.c b/audio/audio_aec.c new file mode 100644 index 0000000..ab99c93 --- /dev/null +++ b/audio/audio_aec.c @@ -0,0 +1,700 @@ +/* + * 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*/ |