| /* Copyright 2018 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <inttypes.h> |
| #include <string.h> |
| #include <syslog.h> |
| |
| #include <webrtc-apm/webrtc_apm.h> |
| |
| #include "byte_buffer.h" |
| #include "cras_apm_list.h" |
| #include "cras_audio_area.h" |
| #include "cras_audio_format.h" |
| #include "cras_dsp_pipeline.h" |
| #include "cras_iodev.h" |
| #include "cras_iodev_list.h" |
| #include "dsp_util.h" |
| #include "dumper.h" |
| #include "float_buffer.h" |
| #include "iniparser_wrapper.h" |
| #include "utlist.h" |
| |
| #define AEC_CONFIG_NAME "aec.ini" |
| #define APM_CONFIG_NAME "apm.ini" |
| |
| /* |
| * Structure holding a WebRTC audio processing module and necessary |
| * info to process and transfer input buffer from device to stream. |
| * |
| * Below chart describes the buffer structure inside APM and how an input buffer |
| * flows from a device through the APM to stream. APM processes audio buffers in |
| * fixed 10ms width, and that's the main reason we need two copies of the |
| * buffer: |
| * (1) to cache input buffer from device until 10ms size is filled. |
| * (2) to store the interleaved buffer, of 10ms size also, after APM processing. |
| * |
| * ________ _______ _______________________________ |
| * | | | | |_____________APM ____________| |
| * |input |-> | DSP |---> || | | || -> stream 1 |
| * |device| | | | || float buf | -> | byte buf || |
| * |______| |_____| | ||___________| |__________|| |
| * | |_____________________________| |
| * | _______________________________ |
| * |-> | APM 2 | -> stream 2 |
| * | |_____________________________| |
| * | ... |
| * | |
| * |------------------------------------> stream N |
| * |
| * Members: |
| * apm_ptr - An APM instance from libwebrtc_audio_processing |
| * dev_ptr - Pointer to the device this APM is associated with. |
| * buffer - Stores the processed/interleaved data ready for stream to read. |
| * fbuffer - Stores the floating pointer buffer from input device waiting |
| * for APM to process. |
| * dev_fmt - The format used by the iodev this APM attaches to. |
| * fmt - The audio data format configured for this APM. |
| * area - The cras_audio_area used for copying processed data to client |
| * stream. |
| * work_queue - A task queue instance created and destroyed by |
| * libwebrtc_apm. |
| * use_tuned_settings - True if this APM uses settings tuned specifically |
| * for this hardware in AEC use case. Otherwise it uses the generic |
| * settings like run inside browser. |
| */ |
| struct cras_apm { |
| webrtc_apm apm_ptr; |
| void *dev_ptr; |
| struct byte_buffer *buffer; |
| struct float_buffer *fbuffer; |
| struct cras_audio_format dev_fmt; |
| struct cras_audio_format fmt; |
| struct cras_audio_area *area; |
| void *work_queue; |
| bool use_tuned_settings; |
| struct cras_apm *prev, *next; |
| }; |
| |
| /* |
| * Lists of cras_apm instances created for a stream. A stream may |
| * have more than one cras_apm when multiple input devices are |
| * enabled. The most common scenario is the silent input iodev be |
| * enabled when CRAS switches active input device. |
| * |
| * Note that cras_apm_list is owned and modified in main thread. |
| * Only in synchronized audio thread event this cras_apm_list is safe |
| * to access for passing single APM instance between threads. |
| */ |
| struct cras_apm_list { |
| void *stream_ptr; |
| uint64_t effects; |
| struct cras_apm *apms; |
| struct cras_apm_list *prev, *next; |
| }; |
| |
| /* |
| * Wrappers of APM instances that are active, which means it is associated |
| * to a dev/stream pair in audio thread and ready for processing. |
| * |
| * Members: |
| * apm - The APM for audio data processing. |
| * stream_ptr - Stream pointer from the associated dev/stream pair. |
| * effects - The effecets bit map of APM. |
| */ |
| struct active_apm { |
| struct cras_apm *apm; |
| void *stream_ptr; |
| int effects; |
| struct active_apm *prev, *next; |
| } * active_apms; |
| |
| /* |
| * Object used to analyze playback audio from output iodev. It is responsible |
| * to get buffer containing latest output data and provide it to the APM |
| * instances which want to analyze reverse stream. |
| * Member: |
| * ext - The interface implemented to process reverse(output) stream |
| * data in various formats. |
| * fbuf - Middle buffer holding reverse data for APMs to analyze. |
| * odev - Pointer to the output iodev playing audio as the reverse |
| * stream. NULL if there's no playback stream. |
| * dev_rate - The sample rate odev is opened for. |
| * process_reverse - Flag to indicate if there's APM has effect that |
| * needs to process reverse stream. |
| */ |
| struct cras_apm_reverse_module { |
| struct ext_dsp_module ext; |
| struct float_buffer *fbuf; |
| struct cras_iodev *odev; |
| unsigned int dev_rate; |
| unsigned process_reverse; |
| }; |
| |
| static struct cras_apm_reverse_module *rmodule = NULL; |
| static const char *aec_config_dir = NULL; |
| static char ini_name[MAX_INI_NAME_LENGTH + 1]; |
| static dictionary *aec_ini = NULL; |
| static dictionary *apm_ini = NULL; |
| |
| /* Update the global process reverse flag. Should be called when apms are added |
| * or removed. */ |
| static void update_process_reverse_flag() |
| { |
| struct active_apm *active; |
| |
| if (!rmodule) |
| return; |
| rmodule->process_reverse = 0; |
| DL_FOREACH (active_apms, active) { |
| rmodule->process_reverse |= |
| !!(active->effects & APM_ECHO_CANCELLATION); |
| } |
| } |
| |
| static void apm_destroy(struct cras_apm **apm) |
| { |
| if (*apm == NULL) |
| return; |
| byte_buffer_destroy(&(*apm)->buffer); |
| float_buffer_destroy(&(*apm)->fbuffer); |
| cras_audio_area_destroy((*apm)->area); |
| |
| /* Any unfinished AEC dump handle will be closed. */ |
| webrtc_apm_destroy((*apm)->apm_ptr); |
| free(*apm); |
| *apm = NULL; |
| } |
| |
| struct cras_apm_list *cras_apm_list_create(void *stream_ptr, uint64_t effects) |
| { |
| struct cras_apm_list *list; |
| |
| if (effects == 0) |
| return NULL; |
| |
| list = (struct cras_apm_list *)calloc(1, sizeof(*list)); |
| if (list == NULL) { |
| syslog(LOG_ERR, "No memory in creating apm list"); |
| return NULL; |
| } |
| list->stream_ptr = stream_ptr; |
| list->effects = effects; |
| list->apms = NULL; |
| |
| return list; |
| } |
| |
| static struct active_apm *get_active_apm(void *stream_ptr, void *dev_ptr) |
| { |
| struct active_apm *active; |
| |
| DL_FOREACH (active_apms, active) { |
| if ((active->apm->dev_ptr == dev_ptr) && |
| (active->stream_ptr == stream_ptr)) |
| return active; |
| } |
| return NULL; |
| } |
| |
| struct cras_apm *cras_apm_list_get_active_apm(void *stream_ptr, void *dev_ptr) |
| { |
| struct active_apm *active = get_active_apm(stream_ptr, dev_ptr); |
| return active ? active->apm : NULL; |
| } |
| |
| uint64_t cras_apm_list_get_effects(struct cras_apm_list *list) |
| { |
| if (list == NULL) |
| return 0; |
| else |
| return list->effects; |
| } |
| |
| void cras_apm_list_remove_apm(struct cras_apm_list *list, void *dev_ptr) |
| { |
| struct cras_apm *apm; |
| |
| DL_FOREACH (list->apms, apm) { |
| if (apm->dev_ptr == dev_ptr) { |
| DL_DELETE(list->apms, apm); |
| apm_destroy(&apm); |
| } |
| } |
| } |
| |
| /* |
| * WebRTC APM handles no more than stereo + keyboard mic channels. |
| * Ignore keyboard mic feature for now because that requires processing on |
| * mixed buffer from two input devices. Based on that we should modify the best |
| * channel layout for APM use. |
| * Args: |
| * apm_fmt - Pointer to a format struct already filled with the value of |
| * the open device format. Its content may be modified for APM use. |
| */ |
| static void get_best_channels(struct cras_audio_format *apm_fmt) |
| { |
| int ch; |
| int8_t layout[CRAS_CH_MAX]; |
| |
| /* Using the format from dev_fmt is dangerous because input device |
| * could have wild configurations like unuse the 1st channel and |
| * connects 2nd channel to the only mic. Data in the first channel |
| * is what APM cares about so always construct a new channel layout |
| * containing subset of original channels that matches either FL, FR, |
| * or FC. |
| * TODO(hychao): extend the logic when we have a stream that wants |
| * to record channels like RR(rear right). |
| */ |
| for (ch = 0; ch < CRAS_CH_MAX; ch++) |
| layout[ch] = -1; |
| |
| apm_fmt->num_channels = 0; |
| if (apm_fmt->channel_layout[CRAS_CH_FL] != -1) |
| layout[CRAS_CH_FL] = apm_fmt->num_channels++; |
| if (apm_fmt->channel_layout[CRAS_CH_FR] != -1) |
| layout[CRAS_CH_FR] = apm_fmt->num_channels++; |
| if (apm_fmt->channel_layout[CRAS_CH_FC] != -1) |
| layout[CRAS_CH_FC] = apm_fmt->num_channels++; |
| |
| for (ch = 0; ch < CRAS_CH_MAX; ch++) |
| apm_fmt->channel_layout[ch] = layout[ch]; |
| } |
| |
| struct cras_apm *cras_apm_list_add_apm(struct cras_apm_list *list, |
| void *dev_ptr, |
| const struct cras_audio_format *dev_fmt, |
| bool is_aec_use_case) |
| { |
| struct cras_apm *apm; |
| |
| DL_FOREACH (list->apms, apm) |
| if (apm->dev_ptr == dev_ptr) |
| return apm; |
| |
| // TODO(hychao): Remove the check when we enable more effects. |
| if (!(list->effects & APM_ECHO_CANCELLATION)) |
| return NULL; |
| |
| apm = (struct cras_apm *)calloc(1, sizeof(*apm)); |
| |
| /* Configures APM to the format used by input device. If the channel |
| * count is larger than stereo, use the standard channel count/layout |
| * in APM. */ |
| apm->dev_fmt = *dev_fmt; |
| apm->fmt = *dev_fmt; |
| get_best_channels(&apm->fmt); |
| |
| /* Use tuned settings only when the forward dev(capture) and reverse |
| * dev(playback) both are in typical AEC use case. */ |
| apm->use_tuned_settings = is_aec_use_case; |
| if (rmodule->odev) { |
| apm->use_tuned_settings &= |
| cras_iodev_is_aec_use_case(rmodule->odev->active_node); |
| } |
| |
| /* Use the configs tuned specifically for internal device. Otherwise |
| * just pass NULL so every other settings will be default. */ |
| apm->apm_ptr = |
| apm->use_tuned_settings ? |
| webrtc_apm_create(apm->fmt.num_channels, |
| apm->fmt.frame_rate, aec_ini, |
| apm_ini) : |
| webrtc_apm_create(apm->fmt.num_channels, |
| apm->fmt.frame_rate, NULL, NULL); |
| if (apm->apm_ptr == NULL) { |
| syslog(LOG_ERR, |
| "Fail to create webrtc apm for ch %zu" |
| " rate %zu effect %" PRIu64, |
| dev_fmt->num_channels, dev_fmt->frame_rate, |
| list->effects); |
| free(apm); |
| return NULL; |
| } |
| |
| apm->dev_ptr = dev_ptr; |
| apm->work_queue = NULL; |
| |
| /* WebRTC APM wants 10 ms equivalence of data to process. */ |
| apm->buffer = byte_buffer_create(10 * apm->fmt.frame_rate / 1000 * |
| cras_get_format_bytes(&apm->fmt)); |
| apm->fbuffer = float_buffer_create(10 * apm->fmt.frame_rate / 1000, |
| apm->fmt.num_channels); |
| apm->area = cras_audio_area_create(apm->fmt.num_channels); |
| cras_audio_area_config_channels(apm->area, &apm->fmt); |
| |
| DL_APPEND(list->apms, apm); |
| |
| return apm; |
| } |
| |
| void cras_apm_list_start_apm(struct cras_apm_list *list, void *dev_ptr) |
| { |
| struct active_apm *active; |
| struct cras_apm *apm; |
| |
| if (list == NULL) |
| return; |
| |
| /* Check if this apm has already been started. */ |
| apm = cras_apm_list_get_active_apm(list->stream_ptr, dev_ptr); |
| if (apm) |
| return; |
| |
| DL_SEARCH_SCALAR(list->apms, apm, dev_ptr, dev_ptr); |
| if (apm == NULL) |
| return; |
| |
| active = (struct active_apm *)calloc(1, sizeof(*active)); |
| if (active == NULL) { |
| syslog(LOG_ERR, "No memory to start apm."); |
| return; |
| } |
| active->apm = apm; |
| active->stream_ptr = list->stream_ptr; |
| active->effects = list->effects; |
| DL_APPEND(active_apms, active); |
| |
| update_process_reverse_flag(); |
| } |
| |
| void cras_apm_list_stop_apm(struct cras_apm_list *list, void *dev_ptr) |
| { |
| struct active_apm *active; |
| |
| if (list == NULL) |
| return; |
| |
| active = get_active_apm(list->stream_ptr, dev_ptr); |
| if (active) { |
| DL_DELETE(active_apms, active); |
| free(active); |
| } |
| |
| update_process_reverse_flag(); |
| } |
| |
| int cras_apm_list_destroy(struct cras_apm_list *list) |
| { |
| struct cras_apm *apm; |
| |
| DL_FOREACH (list->apms, apm) { |
| DL_DELETE(list->apms, apm); |
| apm_destroy(&apm); |
| } |
| free(list); |
| |
| return 0; |
| } |
| |
| /* |
| * Determines the iodev to be used as the echo reference for APM reverse |
| * analysis. If there exists the special purpose "echo reference dev" then |
| * use it. Otherwise just use this output iodev. |
| */ |
| static struct cras_iodev *get_echo_reference_target(struct cras_iodev *iodev) |
| { |
| return iodev->echo_reference_dev ? iodev->echo_reference_dev : iodev; |
| } |
| |
| /* |
| * Updates the first enabled output iodev in the list, determine the echo |
| * reference target base on this output iodev, and register rmodule as ext dsp |
| * module to this echo reference target. |
| * When this echo reference iodev is opened and audio data flows through its |
| * dsp pipeline, APMs will anaylize the reverse stream. This is expected to be |
| * called in main thread when output devices enable/dsiable state changes. |
| */ |
| static void update_first_output_dev_to_process() |
| { |
| struct cras_iodev *echo_ref; |
| struct cras_iodev *iodev = |
| cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT); |
| |
| if (iodev == NULL) |
| return; |
| |
| echo_ref = get_echo_reference_target(iodev); |
| |
| /* If rmodule is already tracking echo_ref, do nothing. */ |
| if (rmodule->odev == echo_ref) |
| return; |
| |
| /* Detach from the old iodev that rmodule was tracking. */ |
| if (rmodule->odev) { |
| cras_iodev_set_ext_dsp_module(rmodule->odev, NULL); |
| rmodule->odev = NULL; |
| } |
| |
| rmodule->odev = echo_ref; |
| cras_iodev_set_ext_dsp_module(echo_ref, &rmodule->ext); |
| } |
| |
| static void handle_device_enabled(struct cras_iodev *iodev, void *cb_data) |
| { |
| if (iodev->direction != CRAS_STREAM_OUTPUT) |
| return; |
| |
| /* Register to the first enabled output device. */ |
| update_first_output_dev_to_process(); |
| } |
| |
| static void handle_device_disabled(struct cras_iodev *iodev, void *cb_data) |
| { |
| struct cras_iodev *echo_ref; |
| |
| if (iodev->direction != CRAS_STREAM_OUTPUT) |
| return; |
| |
| echo_ref = get_echo_reference_target(iodev); |
| |
| if (rmodule->odev == echo_ref) { |
| cras_iodev_set_ext_dsp_module(echo_ref, NULL); |
| rmodule->odev = NULL; |
| } |
| |
| /* Register to the first enabled output device. */ |
| update_first_output_dev_to_process(); |
| } |
| |
| static int process_reverse(struct float_buffer *fbuf, unsigned int frame_rate) |
| { |
| struct active_apm *active; |
| int ret; |
| float *const *wp; |
| |
| if (float_buffer_writable(fbuf)) |
| return 0; |
| |
| wp = float_buffer_write_pointer(fbuf); |
| |
| DL_FOREACH (active_apms, active) { |
| if (!(active->effects & APM_ECHO_CANCELLATION)) |
| continue; |
| |
| ret = webrtc_apm_process_reverse_stream_f(active->apm->apm_ptr, |
| fbuf->num_channels, |
| frame_rate, wp); |
| if (ret) { |
| syslog(LOG_ERR, "APM process reverse err"); |
| return ret; |
| } |
| } |
| float_buffer_reset(fbuf); |
| return 0; |
| } |
| |
| void reverse_data_run(struct ext_dsp_module *ext, unsigned int nframes) |
| { |
| struct cras_apm_reverse_module *rmod = |
| (struct cras_apm_reverse_module *)ext; |
| unsigned int writable; |
| int i, offset = 0; |
| float *const *wp; |
| |
| if (!rmod->process_reverse) |
| return; |
| |
| while (nframes) { |
| process_reverse(rmod->fbuf, rmod->dev_rate); |
| writable = float_buffer_writable(rmod->fbuf); |
| writable = MIN(nframes, writable); |
| wp = float_buffer_write_pointer(rmod->fbuf); |
| for (i = 0; i < rmod->fbuf->num_channels; i++) |
| memcpy(wp[i], ext->ports[i] + offset, |
| writable * sizeof(float)); |
| |
| offset += writable; |
| float_buffer_written(rmod->fbuf, writable); |
| nframes -= writable; |
| } |
| } |
| |
| void reverse_data_configure(struct ext_dsp_module *ext, |
| unsigned int buffer_size, unsigned int num_channels, |
| unsigned int rate) |
| { |
| struct cras_apm_reverse_module *rmod = |
| (struct cras_apm_reverse_module *)ext; |
| if (rmod->fbuf) |
| float_buffer_destroy(&rmod->fbuf); |
| rmod->fbuf = float_buffer_create(rate / 100, num_channels); |
| rmod->dev_rate = rate; |
| } |
| |
| static void get_aec_ini(const char *config_dir) |
| { |
| snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", config_dir, |
| AEC_CONFIG_NAME); |
| ini_name[MAX_INI_NAME_LENGTH] = '\0'; |
| |
| if (aec_ini) { |
| iniparser_freedict(aec_ini); |
| aec_ini = NULL; |
| } |
| aec_ini = iniparser_load_wrapper(ini_name); |
| if (aec_ini == NULL) |
| syslog(LOG_INFO, "No aec ini file %s", ini_name); |
| } |
| |
| static void get_apm_ini(const char *config_dir) |
| { |
| snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", config_dir, |
| APM_CONFIG_NAME); |
| ini_name[MAX_INI_NAME_LENGTH] = '\0'; |
| |
| if (apm_ini) { |
| iniparser_freedict(apm_ini); |
| apm_ini = NULL; |
| } |
| apm_ini = iniparser_load_wrapper(ini_name); |
| if (apm_ini == NULL) |
| syslog(LOG_INFO, "No apm ini file %s", ini_name); |
| } |
| |
| int cras_apm_list_init(const char *device_config_dir) |
| { |
| if (rmodule == NULL) { |
| rmodule = (struct cras_apm_reverse_module *)calloc( |
| 1, sizeof(*rmodule)); |
| rmodule->ext.run = reverse_data_run; |
| rmodule->ext.configure = reverse_data_configure; |
| } |
| |
| aec_config_dir = device_config_dir; |
| get_aec_ini(aec_config_dir); |
| get_apm_ini(aec_config_dir); |
| |
| update_first_output_dev_to_process(); |
| cras_iodev_list_set_device_enabled_callback( |
| handle_device_enabled, handle_device_disabled, rmodule); |
| |
| return 0; |
| } |
| |
| void cras_apm_list_reload_aec_config() |
| { |
| if (NULL == aec_config_dir) |
| return; |
| |
| get_aec_ini(aec_config_dir); |
| get_apm_ini(aec_config_dir); |
| |
| /* Dump the config content at reload only, for debug. */ |
| webrtc_apm_dump_configs(apm_ini, aec_ini); |
| } |
| |
| int cras_apm_list_deinit() |
| { |
| if (rmodule) { |
| if (rmodule->fbuf) |
| float_buffer_destroy(&rmodule->fbuf); |
| free(rmodule); |
| rmodule = NULL; |
| } |
| return 0; |
| } |
| |
| int cras_apm_list_process(struct cras_apm *apm, struct float_buffer *input, |
| unsigned int offset) |
| { |
| unsigned int writable, nframes, nread; |
| int ch, i, j, ret; |
| float *const *wp; |
| float *const *rp; |
| |
| nread = float_buffer_level(input); |
| if (nread < offset) { |
| syslog(LOG_ERR, "Process offset exceeds read level"); |
| return -EINVAL; |
| } |
| |
| writable = float_buffer_writable(apm->fbuffer); |
| writable = MIN(nread - offset, writable); |
| |
| nframes = writable; |
| while (nframes) { |
| nread = nframes; |
| wp = float_buffer_write_pointer(apm->fbuffer); |
| rp = float_buffer_read_pointer(input, offset, &nread); |
| |
| for (i = 0; i < apm->fbuffer->num_channels; i++) { |
| /* Look up the channel position and copy from |
| * the correct index of |input| buffer. |
| */ |
| for (ch = 0; ch < CRAS_CH_MAX; ch++) |
| if (apm->fmt.channel_layout[ch] == i) |
| break; |
| if (ch == CRAS_CH_MAX) |
| continue; |
| |
| j = apm->dev_fmt.channel_layout[ch]; |
| if (j == -1) |
| continue; |
| |
| memcpy(wp[i], rp[j], nread * sizeof(float)); |
| } |
| |
| nframes -= nread; |
| offset += nread; |
| |
| float_buffer_written(apm->fbuffer, nread); |
| } |
| |
| /* process and move to int buffer */ |
| if ((float_buffer_writable(apm->fbuffer) == 0) && |
| (buf_queued(apm->buffer) == 0)) { |
| nread = float_buffer_level(apm->fbuffer); |
| rp = float_buffer_read_pointer(apm->fbuffer, 0, &nread); |
| ret = webrtc_apm_process_stream_f(apm->apm_ptr, |
| apm->fmt.num_channels, |
| apm->fmt.frame_rate, rp); |
| if (ret) { |
| syslog(LOG_ERR, "APM process stream f err"); |
| return ret; |
| } |
| |
| dsp_util_interleave(rp, buf_write_pointer(apm->buffer), |
| apm->fbuffer->num_channels, apm->fmt.format, |
| nread); |
| buf_increment_write(apm->buffer, |
| nread * cras_get_format_bytes(&apm->fmt)); |
| float_buffer_reset(apm->fbuffer); |
| } |
| |
| return writable; |
| } |
| |
| struct cras_audio_area *cras_apm_list_get_processed(struct cras_apm *apm) |
| { |
| uint8_t *buf_ptr; |
| |
| buf_ptr = buf_read_pointer_size(apm->buffer, &apm->area->frames); |
| apm->area->frames /= cras_get_format_bytes(&apm->fmt); |
| cras_audio_area_config_buf_pointers(apm->area, &apm->fmt, buf_ptr); |
| return apm->area; |
| } |
| |
| void cras_apm_list_put_processed(struct cras_apm *apm, unsigned int frames) |
| { |
| buf_increment_read(apm->buffer, |
| frames * cras_get_format_bytes(&apm->fmt)); |
| } |
| |
| struct cras_audio_format *cras_apm_list_get_format(struct cras_apm *apm) |
| { |
| return &apm->fmt; |
| } |
| |
| bool cras_apm_list_get_use_tuned_settings(struct cras_apm *apm) |
| { |
| return apm->use_tuned_settings; |
| } |
| |
| void cras_apm_list_set_aec_dump(struct cras_apm_list *list, void *dev_ptr, |
| int start, int fd) |
| { |
| struct cras_apm *apm; |
| char file_name[256]; |
| int rc; |
| FILE *handle; |
| |
| DL_SEARCH_SCALAR(list->apms, apm, dev_ptr, dev_ptr); |
| if (apm == NULL) |
| return; |
| |
| if (start) { |
| handle = fdopen(fd, "w"); |
| if (handle == NULL) { |
| syslog(LOG_ERR, "Create dump handle fail, errno %d", |
| errno); |
| return; |
| } |
| /* webrtc apm will own the FILE handle and close it. */ |
| rc = webrtc_apm_aec_dump(apm->apm_ptr, &apm->work_queue, start, |
| handle); |
| if (rc) |
| syslog(LOG_ERR, "Fail to dump debug file %s, rc %d", |
| file_name, rc); |
| } else { |
| rc = webrtc_apm_aec_dump(apm->apm_ptr, &apm->work_queue, 0, |
| NULL); |
| if (rc) |
| syslog(LOG_ERR, "Failed to stop apm debug, rc %d", rc); |
| } |
| } |