/*************************************************************************** * * Copyright 2015-2019 BES. * All rights reserved. All unpublished rights reserved. * * No part of this work may be used or reproduced in any form or by any * means, or stored in a database or retrieval system, without prior written * permission of BES. * * Use of this work is governed by a license granted by BES. * This work contains confidential and proprietary information of * BES. which is protected by copyright, trade secret, * trademark and other intellectual property rights. * ****************************************************************************/ #include "plat_types.h" #include "codec_int.h" #include "hal_codec.h" #include "hal_trace.h" #include "hal_sleep.h" #include "stdbool.h" #include "analog.h" #include "tgt_hardware.h" #define CODEC_TRACE(n, s, ...) TRACE(n, s, ##__VA_ARGS__) #define CODEC_INT_INST HAL_CODEC_ID_0 #ifndef CODEC_OUTPUT_DEV #define CODEC_OUTPUT_DEV CFG_HW_AUD_OUTPUT_PATH_SPEAKER_DEV #endif #ifdef __CODEC_ASYNC_CLOSE__ #include "cmsis_os.h" #define CODEC_ASYNC_CLOSE_DELAY (5000) static void codec_timehandler(void const *param); osTimerDef (CODEC_TIMER, codec_timehandler); static osTimerId codec_timer; static CODEC_CLOSE_HANDLER close_hdlr; enum CODEC_HW_STATE_T { CODEC_HW_STATE_CLOSED, CODEC_HW_STATE_CLOSE_PENDING, CODEC_HW_STATE_OPENED, }; enum CODEC_HW_STATE_T codec_hw_state = CODEC_HW_STATE_CLOSED; #endif enum CODEC_USER_T { CODEC_USER_STREAM = (1 << 0), CODEC_USER_ANC = (1 << 1), CODEC_USER_VAD = (1 << 2), }; struct CODEC_CONFIG_T { enum CODEC_USER_T user_map; bool resample_en; bool mute_state[AUD_STREAM_NUM]; bool chan_vol_set[AUD_STREAM_NUM]; struct STREAM_CONFIG_T { bool opened; bool started; struct HAL_CODEC_CONFIG_T codec_cfg; } stream_cfg[AUD_STREAM_NUM]; }; static struct CODEC_CONFIG_T codec_int_cfg = { .user_map = 0, .resample_en = false, .mute_state = { false, false, }, .chan_vol_set = { false, false, }, //playback .stream_cfg[AUD_STREAM_PLAYBACK] = { .opened = false, .started = false, .codec_cfg = { .sample_rate = AUD_SAMPRATE_NULL, } }, //capture .stream_cfg[AUD_STREAM_CAPTURE] = { .opened = false, .started = false, .codec_cfg = { .sample_rate = AUD_SAMPRATE_NULL, } } }; static bool anc_ff_enabled; static bool anc_fb_enabled; static enum AUD_SAMPRATE_T codec_anc_samp_rate; static CODEC_ANC_HANDLER codec_anc_handler; #ifdef VOICE_DETECTOR_EN static enum AUD_VAD_TYPE_T vad_type; #endif #ifdef CODEC_ANC_BOOST static CODEC_ANC_BOOST_DELAY_FUNC boost_delay; void codec_set_anc_boost_delay_func(CODEC_ANC_BOOST_DELAY_FUNC delay_func) { boost_delay = delay_func; } #endif uint32_t codec_int_stream_open(enum AUD_STREAM_T stream) { CODEC_TRACE(2,"%s: stream=%d", __func__, stream); codec_int_cfg.stream_cfg[stream].opened = true; return 0; } uint32_t codec_int_stream_setup(enum AUD_STREAM_T stream, struct HAL_CODEC_CONFIG_T *cfg) { enum AUD_CHANNEL_MAP_T ch_map; CODEC_TRACE(2,"%s: stream=%d", __func__, stream); if (codec_int_cfg.stream_cfg[stream].codec_cfg.sample_rate == AUD_SAMPRATE_NULL) { // Codec uninitialized -- all config items should be set codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag = HAL_CODEC_CONFIG_ALL; } else { // Codec initialized before -- only different config items need to be set codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag = HAL_CODEC_CONFIG_NULL; } // Always config sample rate, for the pll setting might have been changed by the other stream CODEC_TRACE(2,"[sample_rate]old=%d new=%d", codec_int_cfg.stream_cfg[stream].codec_cfg.sample_rate, cfg->sample_rate); if (codec_int_cfg.user_map & CODEC_USER_ANC) { // Check ANC sample rate if (codec_anc_handler) { enum AUD_SAMPRATE_T cfg_rate; enum AUD_SAMPRATE_T old_rate; cfg_rate = hal_codec_anc_convert_rate(cfg->sample_rate); if (cfg_rate != codec_anc_samp_rate) { old_rate = codec_anc_samp_rate; codec_anc_handler(stream, cfg_rate, NULL, NULL); codec_anc_samp_rate = cfg_rate; TRACE(5,"%s: ANC sample rate changes from %u to %u due to stream=%d samp_rate=%u", __func__, old_rate, codec_anc_samp_rate, stream, cfg->sample_rate); } } } codec_int_cfg.stream_cfg[stream].codec_cfg.sample_rate = cfg->sample_rate; codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag |= HAL_CODEC_CONFIG_SAMPLE_RATE; if(codec_int_cfg.stream_cfg[stream].codec_cfg.bits != cfg->bits) { CODEC_TRACE(2,"[bits]old=%d new=%d", codec_int_cfg.stream_cfg[stream].codec_cfg.bits, cfg->bits); codec_int_cfg.stream_cfg[stream].codec_cfg.bits = cfg->bits; codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag |= HAL_CODEC_CONFIG_BITS; } if(codec_int_cfg.stream_cfg[stream].codec_cfg.channel_num != cfg->channel_num) { CODEC_TRACE(2,"[channel_num]old=%d new=%d", codec_int_cfg.stream_cfg[stream].codec_cfg.channel_num, cfg->channel_num); codec_int_cfg.stream_cfg[stream].codec_cfg.channel_num = cfg->channel_num; codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag |= HAL_CODEC_CONFIG_CHANNEL_NUM; } ch_map = cfg->channel_map; if (ch_map == 0) { if (stream == AUD_STREAM_PLAYBACK) { ch_map = (enum AUD_CHANNEL_MAP_T)CODEC_OUTPUT_DEV; } else { ch_map = (enum AUD_CHANNEL_MAP_T)hal_codec_get_input_path_cfg(cfg->io_path); } ch_map &= AUD_CHANNEL_MAP_ALL; } if(codec_int_cfg.stream_cfg[stream].codec_cfg.channel_map != ch_map) { CODEC_TRACE(2,"[channel_map]old=0x%x new=0x%x", codec_int_cfg.stream_cfg[stream].codec_cfg.channel_map, ch_map); codec_int_cfg.stream_cfg[stream].codec_cfg.channel_map = ch_map; codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag |= HAL_CODEC_CONFIG_CHANNEL_MAP | HAL_CODEC_CONFIG_VOL | HAL_CODEC_CONFIG_BITS; } if(codec_int_cfg.stream_cfg[stream].codec_cfg.use_dma != cfg->use_dma) { CODEC_TRACE(2,"[use_dma]old=%d new=%d", codec_int_cfg.stream_cfg[stream].codec_cfg.use_dma, cfg->use_dma); codec_int_cfg.stream_cfg[stream].codec_cfg.use_dma = cfg->use_dma; } if(codec_int_cfg.stream_cfg[stream].codec_cfg.vol != cfg->vol) { CODEC_TRACE(3,"[vol]old=%d new=%d chan_vol_set=%d", codec_int_cfg.stream_cfg[stream].codec_cfg.vol, cfg->vol, codec_int_cfg.chan_vol_set[stream]); codec_int_cfg.stream_cfg[stream].codec_cfg.vol = cfg->vol; if (!codec_int_cfg.chan_vol_set[stream]) { codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag |= HAL_CODEC_CONFIG_VOL; } } if(codec_int_cfg.stream_cfg[stream].codec_cfg.io_path != cfg->io_path) { CODEC_TRACE(2,"[io_path]old=%d new=%d", codec_int_cfg.stream_cfg[stream].codec_cfg.io_path, cfg->io_path); codec_int_cfg.stream_cfg[stream].codec_cfg.io_path = cfg->io_path; } CODEC_TRACE(3,"[%s]stream=%d set_flag=0x%x",__func__,stream,codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag); hal_codec_setup_stream(CODEC_INT_INST, stream, &(codec_int_cfg.stream_cfg[stream].codec_cfg)); return 0; } void codec_int_stream_mute(enum AUD_STREAM_T stream, bool mute) { bool anc_on; CODEC_TRACE(3,"%s: stream=%d mute=%d", __func__, stream, mute); if (mute == codec_int_cfg.mute_state[stream]) { CODEC_TRACE(2,"[%s] Codec already in mute status: %d", __func__, mute); return; } anc_on = !!(codec_int_cfg.user_map & CODEC_USER_ANC); if (stream == AUD_STREAM_PLAYBACK) { if (mute) { if (!anc_on) { analog_aud_codec_mute(); } hal_codec_dac_mute(true); } else { hal_codec_dac_mute(false); if (!anc_on) { analog_aud_codec_nomute(); } } } else { hal_codec_adc_mute(mute); } codec_int_cfg.mute_state[stream] = mute; } void codec_int_stream_set_chan_vol(enum AUD_STREAM_T stream, enum AUD_CHANNEL_MAP_T ch_map, uint8_t vol) { CODEC_TRACE(4,"%s: stream=%d ch_map=0x%X vol=%u", __func__, stream, ch_map, vol); codec_int_cfg.chan_vol_set[stream] = true; hal_codec_set_chan_vol(stream, ch_map, vol); } void codec_int_stream_restore_chan_vol(enum AUD_STREAM_T stream) { CODEC_TRACE(2,"%s: stream=%d", __func__, stream); if (codec_int_cfg.chan_vol_set[stream]) { codec_int_cfg.chan_vol_set[stream] = false; // Restore normal volume codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag = HAL_CODEC_CONFIG_VOL; hal_codec_setup_stream(CODEC_INT_INST, stream, &(codec_int_cfg.stream_cfg[stream].codec_cfg)); } } static void codec_hw_start(enum AUD_STREAM_T stream) { CODEC_TRACE(2,"%s: stream=%d", __func__, stream); if (stream == AUD_STREAM_PLAYBACK) { // Enable DAC before starting stream (specifically before enabling PA) analog_aud_codec_dac_enable(true); } hal_codec_start_stream(CODEC_INT_INST, stream); } static void codec_hw_stop(enum AUD_STREAM_T stream) { CODEC_TRACE(2,"%s: stream=%d", __func__, stream); hal_codec_stop_stream(CODEC_INT_INST, stream); if (stream == AUD_STREAM_PLAYBACK) { // Disable DAC after stopping stream (specifically after disabling PA) analog_aud_codec_dac_enable(false); } } uint32_t codec_int_stream_start(enum AUD_STREAM_T stream) { CODEC_TRACE(2,"%s: stream=%d", __func__, stream); codec_int_cfg.stream_cfg[stream].started = true; if (stream == AUD_STREAM_CAPTURE) { analog_aud_codec_adc_enable(codec_int_cfg.stream_cfg[stream].codec_cfg.io_path, codec_int_cfg.stream_cfg[stream].codec_cfg.channel_map, true); } hal_codec_start_interface(CODEC_INT_INST, stream, codec_int_cfg.stream_cfg[stream].codec_cfg.use_dma); if ((codec_int_cfg.user_map & CODEC_USER_ANC) == 0) { codec_hw_start(stream); } return 0; } uint32_t codec_int_stream_stop(enum AUD_STREAM_T stream) { CODEC_TRACE(2,"%s: stream=%d", __func__, stream); hal_codec_stop_interface(CODEC_INT_INST, stream); if ((codec_int_cfg.user_map & CODEC_USER_ANC) == 0) { codec_hw_stop(stream); } if (stream == AUD_STREAM_CAPTURE) { analog_aud_codec_adc_enable(codec_int_cfg.stream_cfg[stream].codec_cfg.io_path, codec_int_cfg.stream_cfg[stream].codec_cfg.channel_map, false); } codec_int_cfg.stream_cfg[stream].started = false; return 0; } uint32_t codec_int_stream_close(enum AUD_STREAM_T stream) { //close all stream CODEC_TRACE(2,"%s: stream=%d", __func__, stream); if (codec_int_cfg.stream_cfg[stream].started) { codec_int_stream_stop(stream); } codec_int_stream_restore_chan_vol(stream); codec_int_cfg.stream_cfg[stream].opened = false; return 0; } static void codec_hw_open(enum CODEC_USER_T user) { enum CODEC_USER_T old_map; #ifdef __AUDIO_RESAMPLE__ bool resample_en = !!hal_cmu_get_audio_resample_status(); #endif old_map = codec_int_cfg.user_map; codec_int_cfg.user_map |= user; if (old_map) { #ifdef __AUDIO_RESAMPLE__ ASSERT(codec_int_cfg.resample_en == resample_en, "%s: Bad resamp status %d for user 0x%X (old_map=0x%X)", __func__, resample_en, user, old_map); #endif return; } #ifdef __AUDIO_RESAMPLE__ codec_int_cfg.resample_en = resample_en; #endif CODEC_TRACE(1,"%s", __func__); #ifdef __CODEC_ASYNC_CLOSE__ if (codec_timer == NULL) { codec_timer = osTimerCreate (osTimer(CODEC_TIMER), osTimerOnce, NULL); } osTimerStop(codec_timer); #endif // Audio resample: Might have different clock source, so must be reconfigured here hal_codec_open(CODEC_INT_INST); #ifdef __CODEC_ASYNC_CLOSE__ CODEC_TRACE(2,"%s: codec_hw_state=%d", __func__, codec_hw_state); if (codec_hw_state == CODEC_HW_STATE_CLOSED) { codec_hw_state = CODEC_HW_STATE_OPENED; // Continue to open the codec hardware } else if (codec_hw_state == CODEC_HW_STATE_CLOSE_PENDING) { // Still opened codec_hw_state = CODEC_HW_STATE_OPENED; return; } else { // Already opened return; } #endif analog_aud_codec_open(); } static void codec_hw_close(enum CODEC_CLOSE_TYPE_T type, enum CODEC_USER_T user) { codec_int_cfg.user_map &= ~user; if (type == CODEC_CLOSE_NORMAL) { if (codec_int_cfg.user_map) { return; } } #ifdef __CODEC_ASYNC_CLOSE__ CODEC_TRACE(3,"%s: type=%d codec_hw_state=%d", __func__, type, codec_hw_state); #else CODEC_TRACE(2,"%s: type=%d", __func__, type); #endif if (type == CODEC_CLOSE_NORMAL) { // Audio resample: Might have different clock source, so close now and reconfigure when open hal_codec_close(CODEC_INT_INST); #ifdef CODEC_POWER_DOWN memset(&codec_int_cfg, 0, sizeof(codec_int_cfg)); #endif #ifdef __CODEC_ASYNC_CLOSE__ ASSERT(codec_hw_state == CODEC_HW_STATE_OPENED, "%s: (type=%d) Bad codec_hw_state=%d", __func__, type, codec_hw_state); // Start a timer to close the codec hardware codec_hw_state = CODEC_HW_STATE_CLOSE_PENDING; osTimerStop(codec_timer); osTimerStart(codec_timer, CODEC_ASYNC_CLOSE_DELAY); return; } else if (type == CODEC_CLOSE_ASYNC_REAL) { if (codec_hw_state != CODEC_HW_STATE_CLOSE_PENDING) { // Already closed or reopened return; } codec_hw_state = CODEC_HW_STATE_CLOSED; #endif } else if (type == CODEC_CLOSE_FORCED) { hal_codec_crash_mute(); } analog_aud_codec_close(); } uint32_t codec_int_open(void) { CODEC_TRACE(2,"%s: user_map=0x%X", __func__, codec_int_cfg.user_map); codec_hw_open(CODEC_USER_STREAM); return 0; } uint32_t codec_int_close(enum CODEC_CLOSE_TYPE_T type) { CODEC_TRACE(3,"%s: type=%d user_map=0x%X", __func__, type, codec_int_cfg.user_map); if (type == CODEC_CLOSE_NORMAL) { if (codec_int_cfg.stream_cfg[AUD_STREAM_PLAYBACK].opened == false && codec_int_cfg.stream_cfg[AUD_STREAM_CAPTURE].opened == false){ codec_hw_close(type, CODEC_USER_STREAM); } } else { codec_hw_close(type, CODEC_USER_STREAM); } return 0; } #ifdef __CODEC_ASYNC_CLOSE__ void codec_int_set_close_handler(CODEC_CLOSE_HANDLER hdlr) { close_hdlr = hdlr; } static void codec_timehandler(void const *param) { if (close_hdlr) { close_hdlr(); } } #endif int codec_anc_open(enum ANC_TYPE_T type, enum AUD_SAMPRATE_T dac_rate, enum AUD_SAMPRATE_T adc_rate, CODEC_ANC_HANDLER hdlr) { bool anc_running = false; CODEC_TRACE(4,"%s: type=%d dac_rate=%d adc_rate=%d", __func__, type, dac_rate, adc_rate); dac_rate = hal_codec_anc_convert_rate(dac_rate); adc_rate = hal_codec_anc_convert_rate(adc_rate); ASSERT(dac_rate == adc_rate, "%s: Unmatched rates: dac_rate=%u adc_rate=%u", __func__, dac_rate, adc_rate); if (hdlr && (codec_int_cfg.user_map & CODEC_USER_STREAM)) { enum AUD_SAMPRATE_T cfg_dac_rate, cfg_adc_rate; if (codec_int_cfg.stream_cfg[AUD_STREAM_PLAYBACK].opened) { cfg_dac_rate = hal_codec_anc_convert_rate(codec_int_cfg.stream_cfg[AUD_STREAM_PLAYBACK].codec_cfg.sample_rate); } else { cfg_dac_rate = dac_rate; } if (codec_int_cfg.stream_cfg[AUD_STREAM_CAPTURE].opened) { cfg_adc_rate = hal_codec_anc_convert_rate(codec_int_cfg.stream_cfg[AUD_STREAM_CAPTURE].codec_cfg.sample_rate); } else { cfg_adc_rate = adc_rate; } if (codec_int_cfg.stream_cfg[AUD_STREAM_PLAYBACK].opened && codec_int_cfg.stream_cfg[AUD_STREAM_CAPTURE].opened) { ASSERT(cfg_dac_rate == cfg_adc_rate, "%s: Unmatched cfg rates: dac_rate=%u adc_rate=%u", __func__, cfg_dac_rate, cfg_adc_rate); } else if (codec_int_cfg.stream_cfg[AUD_STREAM_CAPTURE].opened) { cfg_dac_rate = cfg_adc_rate; } if (dac_rate != cfg_dac_rate) { hdlr(AUD_STREAM_PLAYBACK, cfg_dac_rate, NULL, NULL); TRACE(3,"%s: ANC sample rate changes from %u to %u", __func__, dac_rate, cfg_dac_rate); dac_rate = cfg_dac_rate; } } codec_anc_samp_rate = dac_rate; codec_anc_handler = hdlr; if (anc_ff_enabled || anc_fb_enabled) { anc_running = true; } if (type == ANC_FEEDFORWARD) { anc_ff_enabled = true; } else if (type == ANC_FEEDBACK) { anc_fb_enabled = true; } if (!anc_running) { hal_chip_wake_lock(HAL_CHIP_WAKE_LOCK_USER_ANC); hal_cmu_anc_enable(HAL_CMU_ANC_CLK_USER_ANC); enum AUD_STREAM_T stream; enum AUD_CHANNEL_NUM_T play_chan_num; codec_hw_open(CODEC_USER_ANC); if (CODEC_OUTPUT_DEV == (AUD_CHANNEL_MAP_CH0 | AUD_CHANNEL_MAP_CH1)) { play_chan_num = AUD_CHANNEL_NUM_2; } else { play_chan_num = AUD_CHANNEL_NUM_1; } for (stream = AUD_STREAM_PLAYBACK; stream <= AUD_STREAM_CAPTURE; stream++) { if (codec_int_cfg.stream_cfg[stream].opened) { if (stream == AUD_STREAM_PLAYBACK) { /* ASSERT(codec_int_cfg.stream_cfg[stream].codec_cfg.channel_num == play_chan_num, "Invalid existing ANC channel num %d != %d for stream %d", codec_int_cfg.stream_cfg[stream].codec_cfg.channel_num, play_chan_num, stream);*/ } } else { codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag = 0; codec_int_cfg.stream_cfg[stream].codec_cfg.sample_rate = codec_anc_samp_rate; codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag |= HAL_CODEC_CONFIG_SAMPLE_RATE; if (stream == AUD_STREAM_PLAYBACK) { codec_int_cfg.stream_cfg[stream].codec_cfg.vol = TGT_VOLUME_LEVEL_10; } else { codec_int_cfg.stream_cfg[stream].codec_cfg.vol = CODEC_SADC_VOL; } codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag |= HAL_CODEC_CONFIG_VOL; if (stream == AUD_STREAM_PLAYBACK) { codec_int_cfg.stream_cfg[stream].codec_cfg.channel_num = play_chan_num; codec_int_cfg.stream_cfg[stream].codec_cfg.channel_map = CODEC_OUTPUT_DEV; } else { codec_int_cfg.stream_cfg[stream].codec_cfg.channel_num = 0; codec_int_cfg.stream_cfg[stream].codec_cfg.channel_map = 0; } codec_int_cfg.stream_cfg[stream].codec_cfg.set_flag |= HAL_CODEC_CONFIG_CHANNEL_NUM; hal_codec_setup_stream(CODEC_INT_INST, stream, &(codec_int_cfg.stream_cfg[stream].codec_cfg)); } } // Start play first, then start capture last for (stream = AUD_STREAM_PLAYBACK; stream <= AUD_STREAM_CAPTURE; stream++) { if (!codec_int_cfg.stream_cfg[stream].started) { codec_hw_start(stream); } } } hal_codec_anc_adc_enable(type); analog_aud_codec_anc_enable(type, true); if (!anc_running) { // Enable pa if dac muted before if (codec_int_cfg.mute_state[AUD_STREAM_PLAYBACK]) { analog_aud_codec_nomute(); } #ifdef CODEC_ANC_BOOST analog_aud_codec_anc_boost(true, (ANALOG_ANC_BOOST_DELAY_FUNC)boost_delay); #endif } return 0; } int codec_anc_close(enum ANC_TYPE_T type) { CODEC_TRACE(2,"%s: type=%d", __func__, type); if (type == ANC_FEEDFORWARD) { anc_ff_enabled = false; } else if (type == ANC_FEEDBACK) { anc_fb_enabled = false; } hal_codec_anc_adc_disable(type); analog_aud_codec_anc_enable(type, false); if (anc_ff_enabled || anc_fb_enabled) { return 0; } #ifdef CODEC_ANC_BOOST analog_aud_codec_anc_boost(false, (ANALOG_ANC_BOOST_DELAY_FUNC)boost_delay); #endif enum AUD_STREAM_T stream; // Stop capture first, then stop play last for (stream = AUD_STREAM_CAPTURE; stream >= AUD_STREAM_PLAYBACK && stream <= AUD_STREAM_CAPTURE; stream--) { if (!codec_int_cfg.stream_cfg[stream].started) { codec_hw_stop(stream); } } codec_hw_close(CODEC_CLOSE_NORMAL, CODEC_USER_ANC); hal_cmu_anc_disable(HAL_CMU_ANC_CLK_USER_ANC); hal_chip_wake_unlock(HAL_CHIP_WAKE_LOCK_USER_ANC); // Disable pa if dac muted before if (codec_int_cfg.mute_state[AUD_STREAM_PLAYBACK]) { analog_aud_codec_mute(); } codec_anc_handler = NULL; return 0; } #ifdef VOICE_DETECTOR_EN int codec_vad_open(const struct AUD_VAD_CONFIG_T *cfg) { if (cfg->type == AUD_VAD_TYPE_NONE) { return codec_vad_close(); } if (vad_type == cfg->type) { return 0; } vad_type = cfg->type; if (codec_int_cfg.user_map == 0) { codec_hw_open(CODEC_USER_VAD); } hal_codec_vad_open(cfg); codec_int_cfg.user_map |= (1 << CODEC_USER_VAD); return 0; } int codec_vad_close(void) { if (vad_type == AUD_VAD_TYPE_NONE) { return 0; } vad_type = AUD_VAD_TYPE_NONE; codec_int_cfg.user_map &= ~(1 << CODEC_USER_VAD); if (codec_int_cfg.user_map == 0) { codec_hw_close(CODEC_CLOSE_NORMAL, CODEC_USER_VAD); } return 0; } #endif