--- /dev/null
+# 🎤 真实语音数据验证 VAD 功能指南
+
+## 🎯 目标
+使用真实语音数据验证 VAD 功能,确保语音活动检测的准确性和实时性。
+
+## 📋 完整的 VAD AT 指令集
+
+### 1. 基础指令
+```
+AT^VAD_INIT # 初始化 VAD
+AT^VAD_STATUS # 查询 VAD 状态
+AT^VAD_CLEANUP # 清理 VAD 资源
+AT^VAD_HELP # 显示帮助信息
+```
+
+### 2. 配置指令
+```
+AT^VAD_CONFIG <sil_threshold> <energy_threshold> <min_speech_length> <silence_detect_length> <rule1_min_tailing_silence> <rule2_min_tailing_silence> <rule3_min_tailing_silence>
+```
+
+**参数说明:**
+- `sil_threshold`: 静音阈值 (默认: 63, 建议: 50-70)
+- `energy_threshold`: 能量阈值 (默认: 10000, 建议: 5000-20000)
+- `min_speech_length`: 最小语音长度 (默认: 240, 建议: 120-480)
+- `silence_detect_length`: 静音检测长度 (默认: 300, 建议: 200-500)
+- `rule1_min_tailing_silence`: 规则1最小尾部静音 (默认: 480, 建议: 240-960)
+- `rule2_min_tailing_silence`: 规则2最小尾部静音 (默认: 960, 建议: 480-1920)
+- `rule3_min_tailing_silence`: 规则3最小尾部静音 (默认: 960, 建议: 480-1920)
+
+### 3. 测试指令
+```
+AT^VAD_TEST_AUDIO <audio_type> # 测试音频数据
+AT^VAD_RECORD_START # 开始录音测试
+AT^VAD_RECORD_STOP # 停止录音测试
+```
+
+**audio_type 参数:**
+- `0`: 静音测试
+- `1`: 语音测试
+- `2`: 混合测试(静音+语音)
+
+## 🎵 真实语音数据验证方法
+
+### 方法 1:使用真实麦克风录音
+
+#### 步骤 1:初始化 VAD
+```bash
+AT^VAD_INIT
+```
+**预期输出:**
+```
+=== VAD AT 指令测试 ===
+✓ 日志回调设置成功
+✓ VAD 初始化成功 (状态: 1)
+```
+
+#### 步骤 2:配置优化参数(可选)
+```bash
+AT^VAD_CONFIG 50 5000 120 300 480 960 960
+```
+**参数说明:**
+- 降低静音阈值:63 → 50(更容易检测到语音)
+- 降低能量阈值:10000 → 5000(对低音量语音更敏感)
+- 降低最小语音长度:240 → 120(更快响应短语音)
+
+#### 步骤 3:开始录音测试
+```bash
+AT^VAD_RECORD_START
+```
+**预期输出:**
+```
+=== VAD 录音测试开始 ===
+请对着麦克风说话,系统将实时检测语音活动
+按任意键停止录音测试
+```
+
+#### 步骤 4:进行语音测试
+1. **静音测试**:保持安静 3-5 秒
+ - 预期:VAD 状态 = 0(静音)
+
+2. **语音测试**:清晰地说 "你好" 或 "测试"
+ - 预期:VAD 状态 = 1(语音)
+
+3. **语音结束测试**:停止说话,保持安静
+ - 预期:VAD 状态 = 2(语音结束),然后自动变为 0
+
+#### 步骤 5:停止录音测试
+```bash
+AT^VAD_RECORD_STOP
+```
+
+### 方法 2:使用预录制的语音文件
+
+#### 步骤 1:准备测试音频文件
+- 格式:16kHz, 16bit, 单声道 PCM
+- 内容:包含静音-语音-静音的完整序列
+- 时长:建议 10-30 秒
+
+#### 步骤 2:音频数据处理
+```c
+// 读取音频文件并分帧处理
+FILE *audio_file = fopen("test_audio.pcm", "rb");
+int16_t audio_frame[160]; // 10ms @ 16kHz
+
+while (fread(audio_frame, sizeof(int16_t), 160, audio_file) == 160) {
+ // 运行 VAD
+ run_vad(audio_frame, 160);
+
+ // 获取状态
+ int status = vad_detect();
+ printf("帧 %d: VAD状态 = %d\n", frame_count++, status);
+
+ // 处理语音结束
+ if (status == 2) {
+ printf("检测到语音结束\n");
+ vad_clear();
+ }
+}
+```
+
+### 方法 3:实时音频流测试
+
+#### 步骤 1:配置音频采集
+```c
+// 配置音频采集参数
+struct AudioConfig audio_conf = {
+ .sample_rate = 16000,
+ .bit_depth = 16,
+ .channels = 1,
+ .frame_size = 160 // 10ms
+};
+```
+
+#### 步骤 2:实时 VAD 处理
+```c
+// 音频采集回调函数
+void audio_callback(int16_t *audio_data, int data_len) {
+ // 分帧处理(每帧 160 采样点)
+ for (int i = 0; i < data_len; i += 160) {
+ int16_t frame[160];
+ memcpy(frame, &audio_data[i], 160 * sizeof(int16_t));
+
+ // 运行 VAD
+ run_vad(frame, 160);
+ int status = vad_detect();
+
+ // 实时输出状态
+ if (status != prev_status) {
+ printf("VAD状态变化: %d -> %d\n", prev_status, status);
+ prev_status = status;
+
+ if (status == 2) {
+ printf("语音结束\n");
+ vad_clear();
+ }
+ }
+ }
+}
+```
+
+## 🔍 VAD 状态验证要点
+
+### 状态 0:静音
+- **触发条件**:音频能量低,特征不明显
+- **验证方法**:保持安静,观察状态是否稳定为 0
+- **预期行为**:状态稳定,无频繁变化
+
+### 状态 1:语音
+- **触发条件**:检测到语音特征,能量超过阈值
+- **验证方法**:清晰说话,观察状态是否变为 1
+- **预期行为**:语音开始后状态变为 1,持续到语音结束
+
+### 状态 2:语音结束
+- **触发条件**:语音特征消失,静音持续一定时间
+- **验证方法**:停止说话,观察状态是否变为 2
+- **预期行为**:状态变为 2 后自动调用 `vad_clear()`,然后变为 0
+
+## 📊 性能验证指标
+
+### 响应时间
+- **目标**:< 100ms
+- **测试方法**:从说话开始到状态变为 1 的时间
+- **测量工具**:秒表或系统时钟
+
+### 准确率
+- **目标**:> 95%
+- **测试方法**:统计正确检测的语音片段数量
+- **计算公式**:正确检测数 / 总测试数 × 100%
+
+### 误检率
+- **目标**:< 5%
+- **测试方法**:统计误报的语音检测数量
+- **计算公式**:误报数 / 总测试数 × 100%
+
+## 🚀 高级测试场景
+
+### 场景 1:环境噪声测试
+- **背景**:播放环境噪声(空调、风扇、交通等)
+- **目标**:确保 VAD 不会误报环境噪声为语音
+- **方法**:在噪声环境中保持静音,观察状态
+
+### 场景 2:多人语音测试
+- **背景**:多人同时说话
+- **目标**:验证 VAD 对混合语音的处理能力
+- **方法**:2-3 人同时说话,观察状态变化
+
+### 场景 3:音量变化测试
+- **背景**:从轻声到大声的语音变化
+- **目标**:验证 VAD 对不同音量语音的适应性
+- **方法**:逐渐提高说话音量,观察检测效果
+
+## 🛠️ 故障排除
+
+### 问题 1:VAD 状态始终为 0
+**可能原因:**
+- 能量阈值过高
+- 静音阈值过低
+- 音频数据格式不正确
+
+**解决方案:**
+```bash
+AT^VAD_CONFIG 50 3000 120 300 480 960 960
+```
+
+### 问题 2:VAD 状态频繁变化
+**可能原因:**
+- 阈值设置不当
+- 环境噪声干扰
+- 音频质量差
+
+**解决方案:**
+```bash
+AT^VAD_CONFIG 60 8000 240 400 600 1200 1200
+```
+
+### 问题 3:语音结束检测延迟
+**可能原因:**
+- 尾部静音阈值过高
+- 静音检测长度过长
+
+**解决方案:**
+```bash
+AT^VAD_CONFIG 50 5000 120 200 300 600 600
+```
+
+## 🎉 成功验证标志
+
+当 VAD 功能正常工作时,你应该看到:
+
+1. **初始化成功**:`✓ VAD 初始化成功 (状态: 1)`
+2. **状态变化正常**:静音(0) → 语音(1) → 结束(2) → 静音(0)
+3. **响应及时**:语音开始后 100ms 内状态变化
+4. **误检率低**:环境噪声不会误报为语音
+5. **资源管理**:正确调用 `vad_clear()` 和 `free_vad()`
+
+## 📝 测试记录模板
+
+```
+测试时间: _____________
+测试环境: _____________
+测试人员: _____________
+
+测试结果:
+□ 静音检测正常
+□ 语音检测正常
+□ 语音结束检测正常
+□ 响应时间 < 100ms
+□ 误检率 < 5%
+
+备注: _____________
+```
+
+按照这个指南进行测试,你就能全面验证 VAD 功能的正确性和性能了!🎯
--- /dev/null
+#ifndef _IMSL_KWS_DLL\r
+#define _IMSL_KWS_DLL\r
+\r
+#ifndef _IMSL_NN_COMMON\r
+#define _IMSL_NN_COMMON\r
+#include <stdint.h>\r
+\r
+typedef int (*imsl_nn_printf_func)(const char *format, ...);\r
+\r
+struct AuthInfo\r
+{\r
+ char dmp_token[64];\r
+};\r
+#endif\r
+\r
+struct KwsConfig\r
+{\r
+ int min_activate_len; // 最小检测多少ms的激活才算检测到关键词\r
+ int max_activate_gap; // 关键词激活点最长持续时间 比如 100ms 时检测到关键词但是未达到最短长度,直到100ms + max_activate_gap 这个时间点使激活失效\r
+ int active_threshold; // 激活阈值\r
+\r
+ int kws_log_level; // kws log打印控制, 0 不打印,1 INFO,2 INFO DEBUG\r
+\r
+ struct AuthInfo auth_info;\r
+};\r
+\r
+/*\r
+初始化kws解码器,默认配置见下方的example\r
+*/\r
+int init_kws(const struct KwsConfig* conf);\r
+\r
+int auth_kws(const struct AuthInfo* auth_info);\r
+\r
+void run_kws(const int16_t* data, const int data_len);\r
+\r
+int kws_detect(void);\r
+\r
+void kws_clear(void);\r
+\r
+void free_kws(void);\r
+\r
+#endif\r
+\r
+/* example 只使用kws\r
+// 设置log函数\r
+extern imsl_nn_printf_func imsl_nn_printf;\r
+imsl_nn_printf = printf;\r
+\r
+// 初始化\r
+struct KwsConfig kws_conf;\r
+kws_conf.min_activate_len = 10;\r
+kws_conf.active_threshold = 32;\r
+kws_conf.max_activate_gap = 160;\r
+kws_conf.kws_log_level = 0;\r
+\r
+int kws_status = init_kws(&kws_conf);\r
+\r
+struct AuthInfo auth_info;\r
+strcpy(auth_info.dmp_token, dmp_token);\r
+auth_kws(&auth_info);\r
+\r
+// 在每次接收语音时调用 注意 如果是void* 或 int8* 的指针,长度 / 2\r
+// 运行结束后获取kws状态\r
+run_kws(wave, chunk_size / 2);\r
+int kws_status = kws_detect();\r
+if (kws_status == 1) {\r
+ kws_clear();\r
+}\r
+\r
+// 不使用kws或kws暂时停用时调用\r
+free_kws();\r
+\r
+*/\r
+\r
+\r
+/* example 2 配合vad使用\r
+// 初始化\r
+\r
+\r
+struct VadConfig vad_conf;\r
+vad_conf.sil_threshold = 63;\r
+vad_conf.energy_threshold = 5000;\r
+vad_conf.min_speech_length = 240;\r
+vad_conf.silence_detect_length = 300;\r
+vad_conf.rule1_min_tailing_silence = 480;\r
+vad_conf.rule2_min_tailing_silence = 960;\r
+vad_conf.rule3_min_tailing_silence = 960;\r
+vad_conf.vad_log_level = 2;\r
+\r
+int vad_status = init_vad(&vad_conf);\r
+\r
+struct KwsConfig kws_conf;\r
+kws_conf.min_activate_len = 10;\r
+kws_conf.active_threshold = 32;\r
+kws_conf.max_activate_gap = 160;\r
+kws_conf.kws_log_level = 0;\r
+\r
+int kws_status = init_kws(&kws_conf);\r
+\r
+const int chunk_size = 480; // 这里的chunk size每次送入kws语音的点数 比如 30ms 就是 30 * 16\r
+const int chunk_cache = 9; // 由于vad存在一定延时,需要cache一些chunk防止丢失头部语音\r
+const int cache_size = chunk_cache * chunk_size; // chunk_cache 的值需要根据chunk_size的值变化 9*480/chunk_size\r
+int16_t wave_cache[cache_size] = {0}; // !!!!!!!!!注意我这边是int16_t\r
+int prev_vad_status = 0; // 记录vad状态,只在vad刚检测到语音时使用cache语音\r
+\r
+\r
+// 每次接收到语音时\r
+run_vad(wave + i, chunk_size);\r
+\r
+if (vad_status == 1) {\r
+ if (prev_vad_status == 0 || prev_vad_status == 2) {\r
+ for (j = 0; j < chunk_cache; j++) {\r
+ u32 time4 = timer_get_ms();\r
+ run_kws(wave_cache + j * chunk_size, chunk_size);\r
+ u32 time5 = timer_get_ms();\r
+ printf("run kws takes %d ms\n", time5 - time4);\r
+ }\r
+ }\r
+ run_kws(wave + i, chunk_size);\r
+ int kws_status = kws_detect();\r
+ if (kws_status == 1) {\r
+ printf("!!!!!!!!!!!!!!!!!!!!!!keyword detected!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");\r
+ kws_clear();\r
+ }\r
+} else if (vad_status == 2) {\r
+ vad_clear();\r
+}\r
+\r
+prev_vad_status = vad_status;\r
+memcpy(wave_cache, wave_cache + chunk_size, sizeof(int16_t) * (cache_size - chunk_size));\r
+memcpy(wave_cache + (cache_size - chunk_size), wave + i, sizeof(int16_t) * chunk_size);\r
+\r
+\r
+// kws暂停使用时\r
+free_kws();\r
+\r
+// vad暂停使用时\r
+free_vad();\r
+\r
+*/\r
--- /dev/null
+#include "vad_at_cmd.h"
+#include "at_cmd_api.h"
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include "imsl_vad.h"
+#include "media_thread_adapt.h"
+
+#define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0]))
+#define VAD_MAX_ARGC 8
+#define VAD_MAX_ARGV_LEN 64
+#define VAD_FAILED (-1)
+#define VAD_SUCCESS (0)
+
+/* Indicate that a local variable is not used */
+#define VAD_UNUSED(a) ((void)(a))
+
+// 全局 VAD 状态
+static bool g_vad_initialized = false;
+static int g_vad_status = -1;
+static struct VadConfig g_vad_config = {0};
+
+// 音频缓冲区(用于真实语音测试)
+#define VAD_AUDIO_BUFFER_SIZE 3200 // 200ms @ 16kHz
+static int16_t g_audio_buffer[VAD_AUDIO_BUFFER_SIZE];
+static int g_audio_buffer_pos = 0;
+
+/* 录音封装(由 C++ 提供,C 接口声明) */
+extern int32_t AudioCaptureSample(int32_t argc, const char *argv[]);
+
+/* 将 PCM 文件按 10ms(160点) 分帧喂给 VAD 并打印结果 */
+static int AnalyzePcmFileWithVad(const char *pcm_path)
+{
+ FILE *fp = fopen(pcm_path, "rb");
+ if (fp == NULL) {
+ printf("open pcm failed: %s\n", pcm_path);
+ return VAD_FAILED;
+ }
+
+ int16_t frame[160];
+ size_t total_frames = 0;
+ int prev = -1;
+ int count_speech = 0;
+ int count_end = 0;
+
+ while (1) {
+ size_t n = fread(frame, sizeof(int16_t), 160, fp);
+ if (n < 160) {
+ break;
+ }
+ run_vad(frame, 160);
+ int st = vad_detect();
+ if (st != prev) {
+ printf("VAD状态变化: %d -> %d 于帧%zu\n", prev, st, total_frames);
+ prev = st;
+ }
+ if (st == 1) {
+ count_speech++;
+ } else if (st == 2) {
+ count_end++;
+ vad_clear();
+ }
+ total_frames++;
+ }
+ fclose(fp);
+ printf("总帧数:%zu, 语音帧:%d, 结束帧:%d\n", total_frames, count_speech, count_end);
+ return VAD_SUCCESS;
+}
+
+/* ---------------- 实时录音并同步喂给 VAD(基于文件尾随) ---------------- */
+static volatile bool g_vad_live_running = false;
+static MediaThreadIdHandle g_vad_tail_thread = NULL;
+static char g_vad_live_path[256] = "/user/vad_live.pcm";
+static int g_vad_prev_status = -1;
+
+static void *VadTailThread(void *arg)
+{
+ printf("VAD TailThread\n");
+ (void)arg;
+ FILE *fp = NULL;
+ int16_t frame[160];
+ size_t filled = 0;
+
+ /* 等待文件可读 */
+ while (g_vad_live_running && fp == NULL) {
+ fp = fopen(g_vad_live_path, "rb");
+ if (fp == NULL) {
+ usleep(50 * 1000);
+ }
+ }
+ if (!g_vad_live_running) {
+ if (fp) fclose(fp);
+ return NULL;
+ }
+
+ /* 从头开始读,持续尾随追加的数据 */
+ while (g_vad_live_running) {
+ int16_t sample;
+ size_t n = fread(&sample, sizeof(int16_t), 1, fp);
+ if (n == 1) {
+ frame[filled++] = sample;
+ if (filled == 160) {
+ run_vad(frame, 160);
+ int st = vad_detect();
+ if (st != g_vad_prev_status) {
+ printf("[VAD-LIVE] 状态变化: %d -> %d\n", g_vad_prev_status, st);
+ g_vad_prev_status = st;
+ if (st == 2) {
+ printf("[VAD-LIVE] 检测到语音结束,vad_clear()\n");
+ vad_clear();
+ }
+ }
+ filled = 0;
+ }
+ } else {
+ /* 到达当前 EOF,短暂等待数据追加 */
+ clearerr(fp);
+ usleep(10 * 1000);
+ }
+ }
+ if (fp) fclose(fp);
+ return NULL;
+}
+
+static void *StartRecordThread(void *arg)
+{
+ printf("VAD StartRecordThread\n");
+ (void)arg;
+ const char *startArgs[6] = {"01", "pcm", "16000", "1", "16", g_vad_live_path};
+ (void)AudioCaptureSample(6, startArgs);
+ return NULL;
+}
+
+static void EnsureParentDir(const char *file_path)
+{
+ if (file_path == NULL) {
+ return;
+ }
+ const char *slash = strrchr(file_path, '/');
+ if (slash == NULL || slash == file_path) {
+ return; // no parent or root
+ }
+ char dir[256];
+ size_t len = (size_t)(slash - file_path);
+ if (len >= sizeof(dir)) {
+ return;
+ }
+ memcpy(dir, file_path, len);
+ dir[len] = '\0';
+ struct stat st;
+ if (stat(dir, &st) == 0) {
+ return; // exists
+ }
+ (void)mkdir(dir, 0777);
+}
+
+static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN], uint32_t *argc)
+{
+ uint32_t pos = 0;
+ *argc = 0;
+
+ for (uint32_t i = 0; i < paraLen; i++)
+ {
+ if ((para[i] == ' ' || para[i] == '\n') && pos != 0)
+ {
+ argv[*argc][pos] = '\0';
+ *argc += 1;
+ pos = 0;
+ if (*argc >= VAD_MAX_ARGC)
+ {
+ return VAD_FAILED;
+ }
+ }
+ else
+ {
+ argv[*argc][pos] = para[i];
+ pos++;
+ if (pos >= VAD_MAX_ARGV_LEN)
+ {
+ return VAD_FAILED;
+ }
+ }
+ }
+ if (pos != 0)
+ {
+ *argc += 1;
+ }
+
+ return VAD_SUCCESS;
+}
+
+// AT^VAD_INIT - 初始化 VAD
+static uint32_t vad_init(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ VAD_UNUSED(strCommonCmd);
+ uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0};
+ uint32_t argc = 0;
+
+ int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
+ if (ret != VAD_SUCCESS)
+ {
+ printf("VADTEST ATCMD Convert Command Failed\n");
+ return ERRCODE_FAIL;
+ }
+
+ if (g_vad_initialized)
+ {
+ printf("VAD already initialized\n");
+ return ERRCODE_SUCC;
+ }
+
+ printf("=== VAD AT 指令测试 ===\n");
+
+ // 设置日志回调
+ extern imsl_nn_printf_func imsl_nn_printf;
+ imsl_nn_printf = printf;
+ printf("✓ 日志回调设置成功\n");
+
+ // 初始化 VAD 配置
+ g_vad_config.sil_threshold = 63;
+ g_vad_config.energy_threshold = 10000;
+ g_vad_config.min_speech_length = 240;
+ g_vad_config.silence_detect_length = 300;
+ g_vad_config.rule1_min_tailing_silence = 480;
+ g_vad_config.rule2_min_tailing_silence = 960;
+ g_vad_config.rule3_min_tailing_silence = 960;
+ g_vad_config.vad_log_level = 2;
+
+ int init_result = init_vad(&g_vad_config);
+ if (init_result == 0)
+ {
+ printf("❌ VAD 初始化失败\n");
+ return ERRCODE_FAIL;
+ }
+
+ printf("✓ VAD 初始化成功 (状态: %d)\n", init_result);
+ g_vad_initialized = true;
+ g_vad_status = 0;
+
+ return ERRCODE_SUCC;
+}
+
+// AT^VAD_CONFIG - 配置 VAD 参数
+static uint32_t vad_config(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ VAD_UNUSED(strCommonCmd);
+ uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0};
+ uint32_t argc = 0;
+
+ int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
+ if (ret != VAD_SUCCESS)
+ {
+ printf("VADTEST ATCMD Convert Command Failed\n");
+ return ERRCODE_FAIL;
+ }
+
+ if (!g_vad_initialized)
+ {
+ printf("VAD not initialized, call AT^VAD_INIT first\n");
+ return ERRCODE_FAIL;
+ }
+
+ if (argc != 7)
+ {
+ printf("Usage: AT^VAD_CONFIG <sil_threshold> <energy_threshold> <min_speech_length> <silence_detect_length> <rule1_min_tailing_silence> <rule2_min_tailing_silence> <rule3_min_tailing_silence>\n");
+ return ERRCODE_FAIL;
+ }
+
+ // 解析参数
+ g_vad_config.sil_threshold = atoi((char *)argv[0]);
+ g_vad_config.energy_threshold = atoi((char *)argv[1]);
+ g_vad_config.min_speech_length = atoi((char *)argv[2]);
+ g_vad_config.silence_detect_length = atoi((char *)argv[3]);
+ g_vad_config.rule1_min_tailing_silence = atoi((char *)argv[4]);
+ g_vad_config.rule2_min_tailing_silence = atoi((char *)argv[5]);
+ g_vad_config.rule3_min_tailing_silence = atoi((char *)argv[6]);
+
+ printf("✓ VAD 配置更新成功\n");
+ printf(" sil_threshold: %d\n", g_vad_config.sil_threshold);
+ printf(" energy_threshold: %d\n", g_vad_config.energy_threshold);
+ printf(" min_speech_length: %d\n", g_vad_config.min_speech_length);
+
+ return ERRCODE_SUCC;
+}
+
+// AT^VAD_STATUS - 查询 VAD 状态
+static uint32_t vad_status(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ VAD_UNUSED(para);
+ VAD_UNUSED(paraLen);
+ VAD_UNUSED(strCommonCmd);
+
+ if (!g_vad_initialized)
+ {
+ printf("VAD not initialized, call AT^VAD_INIT first\n");
+ return ERRCODE_FAIL;
+ }
+
+ printf("=== VAD 状态信息 ===\n");
+ printf("初始化状态: %s\n", g_vad_initialized ? "已初始化" : "未初始化");
+ printf("当前状态: %d (0=静音, 1=语音, 2=结束)\n", g_vad_status);
+ printf("配置参数:\n");
+ printf(" sil_threshold: %d\n", g_vad_config.sil_threshold);
+ printf(" energy_threshold: %d\n", g_vad_config.energy_threshold);
+ printf(" min_speech_length: %d\n", g_vad_config.min_speech_length);
+
+ return ERRCODE_SUCC;
+}
+
+// AT^VAD_ANALYZE_PCM <pcm_path>
+static uint32_t vad_analyze_pcm(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ VAD_UNUSED(strCommonCmd);
+ if (!g_vad_initialized) {
+ printf("VAD not initialized, call AT^VAD_INIT first\n");
+ return ERRCODE_FAIL;
+ }
+ uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0};
+ uint32_t argc = 0;
+ if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS || argc != 1) {
+ printf("Usage: AT^VAD_ANALYZE_PCM <pcm_path>\n");
+ return ERRCODE_FAIL;
+ }
+ const char *pcm_path = (const char*)argv[0];
+ printf("VAD 分析现有 PCM: %s\n", pcm_path);
+ int ret = AnalyzePcmFileWithVad(pcm_path);
+ return (ret == VAD_SUCCESS) ? ERRCODE_SUCC : ERRCODE_FAIL;
+}
+
+// AT^VAD_RECORD_START - 开始录音测试
+static uint32_t vad_record_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ VAD_UNUSED(strCommonCmd);
+ if (!g_vad_initialized) {
+ printf("VAD not initialized, call AT^VAD_INIT first\n");
+ return ERRCODE_FAIL;
+ }
+
+ if (g_vad_live_running) {
+ printf("VAD live already running\n");
+ return ERRCODE_SUCC;
+ }
+
+ /* 可选自定义输出路径 */
+ const char *out_path = (const char*)g_vad_live_path;
+ uint8_t argvbuf[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0};
+ uint32_t argc = 0;
+ if (para != NULL && paraLen > 0 && ConvertCmdToParam(para, paraLen, argvbuf, &argc) == VAD_SUCCESS && argc >= 1) {
+ strncpy(g_vad_live_path, (const char*)argvbuf[0], sizeof(g_vad_live_path) - 1);
+ g_vad_live_path[sizeof(g_vad_live_path) - 1] = '\0';
+ out_path = g_vad_live_path;
+ }
+
+ printf("=== VAD 实时录音开始 ===\n");
+ printf("PCM 输出: %s\n", out_path);
+
+ /* 1) 确保目录存在并异步启动系统录音保存为 PCM(避免在 AT 线程上堆栈溢出) */
+ EnsureParentDir(out_path);
+ strncpy(g_vad_live_path, out_path, sizeof(g_vad_live_path) - 1);
+ g_vad_live_path[sizeof(g_vad_live_path) - 1] = '\0';
+ StartRecordThread(NULL);
+
+ /* 2) 启动尾随线程实时喂给 VAD */
+ g_vad_prev_status = -1;
+ g_vad_live_running = true;
+ {
+ MediaThreadattr attr = { "VADTail", 0x1000, THREAD_SCHED_INVALID, 0, true };
+ g_vad_tail_thread = MediaThreadCreate(VadTailThread, NULL, &attr);
+ if (g_vad_tail_thread == NULL) {
+ printf("create tail thread failed\n");
+ g_vad_live_running = false;
+ return ERRCODE_FAIL;
+ }
+ }
+ return ERRCODE_SUCC;
+}
+
+// AT^VAD_RECORD_STOP - 停止录音测试
+static uint32_t vad_record_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ VAD_UNUSED(para);
+ VAD_UNUSED(paraLen);
+ VAD_UNUSED(strCommonCmd);
+
+ printf("=== 停止 VAD 实时录音 ===\n");
+
+ /* 1) 停止尾随线程 */
+ if (g_vad_live_running) {
+ g_vad_live_running = false;
+ if (g_vad_tail_thread != NULL) {
+ MediaThreadJoin(&g_vad_tail_thread);
+ g_vad_tail_thread = NULL;
+ }
+ }
+
+ /* 2) 停止系统录音 */
+ {
+ const char *stopArg = "stop";
+ (void)AudioCaptureSample(1, &stopArg);
+ }
+
+ printf("VAD 实时录音已停止\n");
+ return ERRCODE_SUCC;
+}
+
+// AT^VAD_CLEANUP - 清理 VAD 资源
+static uint32_t vad_cleanup(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ VAD_UNUSED(para);
+ VAD_UNUSED(paraLen);
+ VAD_UNUSED(strCommonCmd);
+
+ if (!g_vad_initialized)
+ {
+ printf("VAD not initialized\n");
+ return ERRCODE_SUCC;
+ }
+
+ free_vad();
+ printf("✓ VAD 资源释放成功\n");
+ g_vad_initialized = false;
+ g_vad_status = -1;
+
+ return ERRCODE_SUCC;
+}
+
+// AT^VAD_HELP - 显示帮助信息
+static uint32_t vad_help(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ VAD_UNUSED(para);
+ VAD_UNUSED(paraLen);
+ VAD_UNUSED(strCommonCmd);
+
+ printf("=== VAD AT 指令帮助 ===\n");
+ printf("AT^VAD_INIT - 初始化 VAD\n");
+ printf("AT^VAD_CONFIG - 配置 VAD 参数\n");
+ printf("AT^VAD_STATUS - 查询 VAD 状态\n");
+ printf("AT^VAD_ANALYZE_PCM <in.pcm> - 分析已有PCM\n");
+ printf("AT^VAD_RECORD_START - 开始录音测试\n");
+ printf("AT^VAD_RECORD_STOP - 停止录音测试\n");
+ printf("AT^VAD_CLEANUP - 清理 VAD 资源\n");
+ printf("AT^VAD_HELP - 显示此帮助\n");
+ printf("\n=== 使用示例 ===\n");
+ printf("AT^VAD_INIT # 初始化\n");
+ printf("AT^VAD_CONFIG 50 5000 120 300 480 960 960 # 配置参数\n");
+ printf("AT^VAD_TEST_AUDIO 1 # 测试语音\n");
+ printf("AT^VAD_STATUS # 查看状态\n");
+ printf("AT^VAD_CLEANUP # 清理资源\n");
+ printf("=====================\n");
+
+ return ERRCODE_SUCC;
+}
+
+static const at_cmd_table_t g_vad_cmd[] = {
+ {(char *)"AT^VAD_INIT", vad_init, (char *)"初始化 VAD"},
+ {(char *)"AT^VAD_CONFIG", vad_config, (char *)"配置 VAD 参数"},
+ {(char *)"AT^VAD_STATUS", vad_status, (char *)"查询 VAD 状态"},
+ {(char *)"AT^VAD_ANALYZE_PCM", vad_analyze_pcm, (char *)"分析现有PCM"},
+ {(char *)"AT^VAD_RECORD_START", vad_record_start, (char *)"开始录音测试"},
+ {(char *)"AT^VAD_RECORD_STOP", vad_record_stop, (char *)"停止录音测试"},
+ {(char *)"AT^VAD_CLEANUP", vad_cleanup, (char *)"清理 VAD 资源"},
+ {(char *)"AT^VAD_HELP", vad_help, (char *)"显示帮助信息"},
+};
+
+at_cmd_table_t *get_vad_cmd_table(uint32_t *cmd_count)
+{
+ *cmd_count = ARRAY_COUNT(g_vad_cmd);
+ return (at_cmd_table_t *)g_vad_cmd;
+}