diff --git a/ql-application/threadx/interface/custom_app/vad_det.c b/ql-application/threadx/interface/custom_app/vad_det.c index c6046c4..486101e 100644 --- a/ql-application/threadx/interface/custom_app/vad_det.c +++ b/ql-application/threadx/interface/custom_app/vad_det.c @@ -29,6 +29,9 @@ // 在文件顶部添加状态变量 static uint8_t speech_detect_count = 0; // 连续语音帧计数器 #define SPEECH_DETECT_THRESHOLD 20 // 连续检测到语音的阈值 +#define PLAYBACK_VAD_GUARD_MS 800 // 屏蔽播放结束后的尾音和音频通路切换噪声 +#define VAD_FRAME_BYTES 640 // 20ms @ 16kHz, 16-bit mono +#define VAD_PREROLL_FRAMES 25 // 保留500ms预录音,避免吞掉用户首字 uint8_t no_speech_timeout = 0; extern int g_mic_working; @@ -57,6 +60,9 @@ static int64_t no_sound_start_time = 0; static int64_t cmd_wait_start_time = 0; static bool first_sound_detected = false; static int64_t recording_start_time = 0; // 记录录音开始时间(首次检测到声音时) +static uint8_t vad_preroll[VAD_PREROLL_FRAMES][VAD_FRAME_BYTES]; +static size_t vad_preroll_next = 0; +static size_t vad_preroll_count = 0; static int vad_tts_playback_finished_status = 0; // 统计 @@ -101,6 +107,9 @@ void set_state(system_state_t new_state) { no_sound_start_time = now; first_sound_detected = false; speech_frame_count = 0; + speech_detect_count = 0; + vad_preroll_next = 0; + vad_preroll_count = 0; g_mic_working = 0; break; case STATE_RECORDING_ACTIVE: @@ -299,8 +308,8 @@ void vad_audio_process(void) { int64_t now = hal_os_up_time_ms(); static size_t buffer_offset = 0; - uint8_t mic_buf[640]={0}; - int record_len = 640; + uint8_t mic_buf[VAD_FRAME_BYTES]={0}; + int record_len = VAD_FRAME_BYTES; uint16 frame[320]; int index = 0, i = 0; @@ -326,15 +335,19 @@ void vad_audio_process(void) { //每次读取640字节音频数据 ql_audio_record_read(record_hd, mic_buf, &record_len); - //如果检测到声音再去传输 - // if(first_sound_detected){ - //实际上每次编码需要输入1152字节的PCM数据 - if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){ - - //quec_usbcdc_write((uint8*)mic_buf, record_len); - ql_rtos_semaphore_release(sender_sem); //告诉线程去获取录音数据 + if (g_state == STATE_RECORDING_WAIT_SOUND) { + /* 首次开口确认前不向服务端发送静音。只在本地保留最近500ms, + * 确认开口时再回填,防止截掉用户首字。 */ + memcpy(vad_preroll[vad_preroll_next], mic_buf, VAD_FRAME_BYTES); + vad_preroll_next = (vad_preroll_next + 1) % VAD_PREROLL_FRAMES; + if (vad_preroll_count < VAD_PREROLL_FRAMES) { + vad_preroll_count++; + } + } else { + if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){ + ql_rtos_semaphore_release(sender_sem); + } } - // } #if 1 for(index=0; index<320; index++) { @@ -347,6 +360,12 @@ void vad_audio_process(void) { // 首次声音检测 if (!first_sound_detected) { + /* 播放刚结束时扬声器尾音和PA切换噪声容易被FVAD认为用户开口。 */ + if ((now - no_sound_start_time) < PLAYBACK_VAD_GUARD_MS) { + speech_detect_count = 0; + buffer_offset = 0; + break; + } if (current_vad == VAD_STATE_ACTIVE) { speech_detect_count++; printf("Speech frame count: %d\r\n", speech_detect_count); @@ -363,8 +382,19 @@ void vad_audio_process(void) { } #endif - first_sound_detected = true; - set_state(STATE_RECORDING_ACTIVE); + first_sound_detected = true; + { + size_t first = (vad_preroll_next + VAD_PREROLL_FRAMES - vad_preroll_count) % VAD_PREROLL_FRAMES; + size_t n; + for (n = 0; n < vad_preroll_count; n++) { + size_t frame_index = (first + n) % VAD_PREROLL_FRAMES; + if (hal_os_rb_write(ring_sender, vad_preroll[frame_index], VAD_FRAME_BYTES, 25) > 0) { + ql_rtos_semaphore_release(sender_sem); + } + } + vad_preroll_count = 0; + } + set_state(STATE_RECORDING_ACTIVE); printf("First speech confirmed by FVAD (continuous %d frames)", SPEECH_DETECT_THRESHOLD); } } else {