Files
banban/ql-application/threadx/interface/custom_app/vad_det.c
T
2026-09-18 11:25:20 +08:00

444 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <stdlib.h>
#include <string.h>
#include <librws.h>
#include "cJSON.h"
#include "app_inc.h"
#include "ql_rtos.h"
#include "ql_application.h"
#include "ql_audio.h"
#include "ql_gpio.h"
#include "sockets.h"
#include "ql_uart.h"
#include "ql_func.h"
#include "netdb.h"
#include "ql_sim.h"
#include "ql_dev.h"
#include "ql_wtd.h"
#include "ql_fs.h"
#include "ql_fota.h"
#include "ql_sim.h"
#include "ql_ping.h"
#include "ql_nw.h"
#include "ql_atcmd.h"
#include "layer3.h"
#include "ql_uart.h"
#include "hal_api.h"
#include "fvad.h"
// 在文件顶部添加状态变量
static uint8_t speech_detect_count = 0; // 连续语音帧计数器
#define SPEECH_DETECT_THRESHOLD 20 // 连续检测到语音的阈值
uint8_t no_speech_timeout = 0;
extern int g_mic_working;
extern int key_start_leave;
static uint8_t vad_leave_message_flag = 0;//留言模式vad只做单次
// FVAD 实例
static Fvad *fvad_inst = NULL;
extern struct ringbuffer *ring_sender;
extern ql_sem_t sender_sem;
extern unsigned char wss_connect_start; //表示vad拾音结束 开始等待后台返回 如果超过5s没有返回 则报超时语音
// 配置
static int sample_rate = 16000; // FVAD 支持的采样率:8k/16k/32k/48k
static int frame_ms = 20; // 帧长:10ms/20ms/30ms
static size_t frame_samples; // 每帧采样点数
// 状态
static volatile system_state_t g_state = STATE_IDLE;
static volatile bool g_triggered = false;
// 计时器(微秒)
static int64_t silence_start_time = 0;
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 int vad_tts_playback_finished_status = 0;
// 统计
static uint32 frame_count = 0;
static uint32 speech_frame_count = 0;
extern int audio_packet_type;
// 内部函数声明
void set_state(system_state_t new_state);
static vad_state_t process_vad_frame(const int16 *data, size_t len);
static void fvad_init_config(void);
system_state_t get_state(void);
static audio_record_handle record_hd;
static void ql_audio_record_callback(acm_audio_track_handle handle, acm_audio_record_event_t event)
{
//printf("audio_record_callback event:%d\r\n", event);
}
void vad_tts_playback_finished()
{
vad_tts_playback_finished_status = 1;
}
void vad_set_speech_timeout_flag(uint8_t value)
{
no_speech_timeout = value;
}
// 状态转换
void set_state(system_state_t new_state) {
if (g_state != new_state) {
printf("State: %d -> %d", g_state, new_state);
g_state = new_state;
// 状态初始化
int64_t now = hal_os_up_time_ms();
switch (new_state) {
case STATE_RECORDING_WAIT_SOUND:
no_sound_start_time = now;
first_sound_detected = false;
speech_frame_count = 0;
g_mic_working = 0;
break;
case STATE_RECORDING_ACTIVE:
silence_start_time = now;
recording_start_time = now; // 记录进入活跃录音状态的时间
g_mic_working = 0;
break;
case STATE_WAITING_COMMAND:
cmd_wait_start_time = now;
break;
default:
break;
}
}
}
// FVAD 初始化配置
static void fvad_init_config(void) {
if (fvad_inst == NULL) {
fvad_inst = fvad_new();
if (fvad_inst == NULL) {
printf("Failed to create FVAD instance");
return;
}
}
// 设置采样率(16kHz 是推荐值,平衡性能和效果)
int ret = fvad_set_sample_rate(fvad_inst, sample_rate);
if (ret != 0) {
printf("Failed to set sample rate %d", sample_rate);
return;
}
// 设置 VAD 模式(0-3,越高级越严格,3 最严格)
// 0: 宽松(噪声环境) 1: 正常 2: 严格 3: 最严格
fvad_set_mode(fvad_inst, 3);
// 计算每帧采样点数
frame_samples = (sample_rate / 1000) * frame_ms;
printf("FVAD initialized: rate=%dHz, frame=%dms, samples=%d",
sample_rate, frame_ms, frame_samples);
}
// VAD处理单帧 - 使用 FVAD 替换自定义算法
static vad_state_t process_vad_frame(const int16_t *data, size_t len) {
// 确保长度符合 FVAD 要求
#if 1
if (len != frame_samples) {
printf("Frame length mismatch: %d vs expected %d", len, frame_samples);
// 如果长度不匹配,返回静音状态
return VAD_STATE_SILENCE;
}
#endif
// 调用 FVAD 处理
int result = fvad_process(fvad_inst, data, len);
frame_count++;
// result: 0 = 静音, 1 = 语音, -1 = 错误
if (result < 0) {
printf("FVAD process error");
return VAD_STATE_SILENCE;
}
return (result == 1) ? VAD_STATE_ACTIVE : VAD_STATE_SILENCE;
}
// 初始化
int vad_audio_init(void) {
printf("Initializing VAD Audio with FVAD...");
// 初始化 FVAD
fvad_init_config();
if (fvad_inst == NULL) {
return -1;
}
app_codec_pa_close();
record_hd = ql_audio_record_open(16000, 1, 0x00, ql_audio_record_callback);
return 0;
}
// 启动录音
int vad_audio_start(void) {
vad_audio_init();
frame_count = 0;
speech_frame_count = 0;
printf("Audio recording started with FVAD");
return 0;
}
// 停止录音
int vad_audio_stop(void) {
printf("Audio recording stopped, frames: %d, speech: %d",
frame_count, speech_frame_count);
vad_audio_deinit();
ql_audio_record_close(record_hd, 1);
wss_connect_start = 1;//用于判断后台响应超时
key_start_leave = 0;
//ql_rtos_semaphore_release(sender_sem);
return 0;
}
// 停止录音
int vad_audio_stop_no_wait_resp(void) {
printf("Audio recording stopped no_wait_resp, frames: %d, speech: %d",
frame_count, speech_frame_count);
vad_audio_deinit();
ql_audio_record_close(record_hd, 1);
wss_connect_start = 0;
return 0;
}
// 触发录音(按键触发)
void vad_audio_trigger_record(void) {
g_triggered = true;
printf("Record triggered");
}
// 强制停止
void vad_audio_stop_record(void) {
set_state(STATE_IDLE);
vad_audio_stop_no_wait_resp();
}
// 获取状态
system_state_t vad_audio_get_state(void) {
return g_state;
}
bool vad_audio_is_recording(void) {
return (g_state == STATE_RECORDING_WAIT_SOUND ||
g_state == STATE_RECORDING_ACTIVE);
}
// 设置 FVAD 模式(0-3)
void vad_set_mode(int mode) {
if (fvad_inst && mode >= 0 && mode <= 3) {
fvad_set_mode(fvad_inst, mode);
printf("FVAD mode set to %d", mode);
}
}
// 设置采样率(必须在初始化前调用)
void vad_set_sample_rate(int rate) {
// FVAD 支持:8000, 16000, 32000, 48000
if (rate == 8000 || rate == 16000 || rate == 32000 || rate == 48000) {
sample_rate = rate;
printf("Sample rate set to %d", rate);
} else {
printf("Invalid sample rate %d, use 8000/16000/32000/48000", rate);
}
}
// 设置帧长(必须在初始化前调用)
void vad_set_frame_ms(int ms) {
// FVAD 支持:10ms, 20ms, 30ms
if (ms == 10 || ms == 20 || ms == 30) {
frame_ms = ms;
printf("Frame time set to %dms", ms);
} else {
printf("Invalid frame time %d, use 10/20/30", ms);
}
}
void vad_leave_message(void)
{
if(vad_audio_is_recording()) return;
audio_packet_type = 4;
vad_leave_message_flag = 1;
vad_audio_trigger_record();
}
// 主处理函数 - 需要在循环中定期调用
void vad_audio_process(void) {
size_t bytes_read = 0;
int64_t now = hal_os_up_time_ms();
static size_t buffer_offset = 0;
uint8_t mic_buf[640]={0};
int record_len = 640;
uint16 frame[320];
int index = 0, i = 0;
// 状态机处理
switch (g_state) {
case STATE_IDLE:
no_speech_timeout = 0;
vad_leave_message_flag = 0;
speech_detect_count = 0; // 空闲时清零计数器
vad_tts_playback_finished_status = 0;
if (g_triggered) {
g_triggered = false;
vad_audio_start();
set_state(STATE_RECORDING_WAIT_SOUND);
buffer_offset = 0;
}
return; // 空闲时不读取数据
case STATE_RECORDING_WAIT_SOUND:
case STATE_RECORDING_ACTIVE:
memset(mic_buf, 0, sizeof(mic_buf));
//每次读取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 1
for(index=0; index<320; index++)
{
frame[index] = mic_buf[i] + (mic_buf[i+1]<<8);
i=i+2;
}
#endif
// 缓冲区已满,处理整帧
vad_state_t current_vad = process_vad_frame(frame, 320);
// 首次声音检测
if (!first_sound_detected) {
if (current_vad == VAD_STATE_ACTIVE) {
speech_detect_count++;
printf("Speech frame count: %d\r\n", speech_detect_count);
// 连续达到阈值才确认检测到声音
if (speech_detect_count >= SPEECH_DETECT_THRESHOLD) {
#if 0
//实际上每次编码需要输入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); //告诉线程去获取录音数据
}
#endif
first_sound_detected = true;
set_state(STATE_RECORDING_ACTIVE);
printf("First speech confirmed by FVAD (continuous %d frames)", SPEECH_DETECT_THRESHOLD);
}
} else {
// 出现静音帧,计数器清零
if (speech_detect_count > 0) {
printf("Speech interrupted, reset count\r\n");
speech_detect_count = 0;
}
// 检查无声音超时
if ((now - no_sound_start_time) > VAD_NO_SOUND_TIMEOUT_MS) {
printf("No speech for timeout, stopping");
no_speech_timeout = 1;
set_state(STATE_WAITING_COMMAND);
vad_audio_stop();
buffer_offset = 0;
return;
}
}
}
// ==========================================
// 活跃状态处理
if (g_state == STATE_RECORDING_ACTIVE) {
// 10秒录音时长限制
if ((now - recording_start_time) > RECORDING_MAX_TIME) {
printf("Recording time limit reached (10s), stopping");
set_state(STATE_WAITING_COMMAND);
vad_audio_stop();
buffer_offset = 0;
return;
}
// 更新静音计时
if (current_vad == VAD_STATE_ACTIVE) {
silence_start_time = now;
speech_frame_count++;
} else {
if ((now - silence_start_time) > VAD_SILENCE_TIMEOUT_MS) {
printf("Silence timeout, stopping output, waiting command");
set_state(STATE_WAITING_COMMAND);
vad_audio_stop();
buffer_offset = 0;
return;
}
}
}
// 重置缓冲区偏移
buffer_offset = 0;
break;
case STATE_WAITING_COMMAND:
if(vad_tts_playback_finished_status == 1) {
printf("finished_status:%d, no_speech_timeout:%d\n", vad_tts_playback_finished_status, no_speech_timeout);
set_state(STATE_IDLE);
ql_rtos_task_sleep_ms(50);
if(no_speech_timeout == 0&&vad_leave_message_flag==0) {
vad_audio_trigger_record();
}
}
key_start_leave = 0;
break;
}
}
// 清理资源(添加清理函数)
void vad_audio_deinit(void) {
if (fvad_inst) {
fvad_free(fvad_inst);
fvad_inst = NULL;
}
printf("VAD audio deinitialized");
}