From: luozheng <1732036298@qq.com> Date: Fri, 22 Aug 2025 02:19:56 +0000 (+0800) Subject: 雷鸟vad集成,已用实时录音验证通过 X-Git-Tag: leiniao_debug_1.0.0_20250922_020716~152^2~11 X-Git-Url: http://git.snicker.com.cn/css/static/gitweb.css?a=commitdiff_plain;h=refs%2Fchanges%2F2124%2F1;p=mirrors%2FFD230.git 雷鸟vad集成,已用实时录音验证通过 --- diff --git a/code/sdk/application/brandy/brandy_standard/CMakeLists.txt b/code/sdk/application/brandy/brandy_standard/CMakeLists.txt index 92a47bdc5..16800934f 100644 --- a/code/sdk/application/brandy/brandy_standard/CMakeLists.txt +++ b/code/sdk/application/brandy/brandy_standard/CMakeLists.txt @@ -44,6 +44,7 @@ set(PRIVATE_HEADER ${ROOT_DIR}/zhihui/4g/cat1Service/include ${ROOT_DIR}/zhihui/4g/cat1Service/Service ${ROOT_DIR}/zhihui/4gTest + ${ROOT_DIR}/zhihui/leiniao/imsl_ln/ ) set(PUBLIC_HEADER diff --git a/code/sdk/application/brandy/brandy_standard/service_init.c b/code/sdk/application/brandy/brandy_standard/service_init.c index d6f1d7d88..db162c760 100644 --- a/code/sdk/application/brandy/brandy_standard/service_init.c +++ b/code/sdk/application/brandy/brandy_standard/service_init.c @@ -96,6 +96,9 @@ #ifdef SUPPORT_AT_BP #include "bp_at_cmd.h" #endif +#ifdef SUPPORT_AT_VAD +#include "vad_at_cmd.h" +#endif static void register_at_service(void) { #ifdef SUPPORT_AT_GRAPHIC @@ -222,6 +225,11 @@ static void register_at_service(void) at_cmd_table_t *bp_table = get_bp_cmd_table(&bp_cmd_count); at_cmd_add_moudle(bp_table, bp_cmd_count); #endif +#ifdef SUPPORT_AT_VAD + uint32_t vad_cmd_count; + at_cmd_table_t *vad_table = get_vad_cmd_table(&vad_cmd_count); + at_cmd_add_moudle(vad_table, vad_cmd_count); +#endif } #ifdef CONFIG_OTA_UPDATE_SUPPORT diff --git a/code/sdk/build/config/target_config/brandy/config.py b/code/sdk/build/config/target_config/brandy/config.py index f2cbfba48..0d37dd32f 100644 --- a/code/sdk/build/config/target_config/brandy/config.py +++ b/code/sdk/build/config/target_config/brandy/config.py @@ -421,6 +421,7 @@ target = { "ppg_sensor_hx3695", # "ppgBleService", "mag_sensor_mmc5603", + "thunderbird_toys",#雷鸟 "vibrator", "vibrator_service", # "-:lwip", diff --git a/code/sdk/middleware/services/media/foundation/audio_capturer/interfaces/kits/audio_capturer_lite/CMakeLists.txt b/code/sdk/middleware/services/media/foundation/audio_capturer/interfaces/kits/audio_capturer_lite/CMakeLists.txt index e474e779d..8cac94bf2 100644 --- a/code/sdk/middleware/services/media/foundation/audio_capturer/interfaces/kits/audio_capturer_lite/CMakeLists.txt +++ b/code/sdk/middleware/services/media/foundation/audio_capturer/interfaces/kits/audio_capturer_lite/CMakeLists.txt @@ -38,6 +38,8 @@ set(PRIVATE_HEADER ${ROOTDIR}/open_source/openharmony/foundation/multimedia/audio_capturer/interfaces/kits ${ROOT_DIR}/application/wearable/service/xiaodu_voice + # IMSL VAD/KWS headers + ${ROOTDIR}/zhihui/leiniao/imsl_ln/include ) set(CCDEFINES ${CCDEFINES} "_GNU_SOURCE" "_XOPEN_SOURCE=700" "UNSUPPORT_AUDIO_DECODER") diff --git a/code/sdk/zhihui/CMakeLists.txt b/code/sdk/zhihui/CMakeLists.txt index 2ad2f1472..8155e7046 100644 --- a/code/sdk/zhihui/CMakeLists.txt +++ b/code/sdk/zhihui/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory_if_exist(utilscpp) add_subdirectory_if_exist(drivers) add_subdirectory_if_exist(config) add_subdirectory_if_exist(UI) +add_subdirectory_if_exist(leiniao) if("TEST_4G_SDK" IN_LIST DEFINES) add_subdirectory_if_exist(4gsdk) diff --git a/code/sdk/zhihui/leiniao/CMakeLists.txt b/code/sdk/zhihui/leiniao/CMakeLists.txt new file mode 100644 index 000000000..03ce15ec1 --- /dev/null +++ b/code/sdk/zhihui/leiniao/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory_if_exist(imsl_ln) \ No newline at end of file diff --git a/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt new file mode 100644 index 000000000..a860f8c95 --- /dev/null +++ b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt @@ -0,0 +1,68 @@ +#=============================================================================== +# @brief imsl_ln component cmake file +# Copyright (c) @CompanyNameMagicTag 2023-2023. All rights reserved. +#=============================================================================== +set(COMPONENT_NAME "thunderbird_toys") + +# 源文件:系统适配层实现 +set(SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/imsl_system_adapter.c + ${CMAKE_CURRENT_SOURCE_DIR}/vad_at_cmd.c +) + +# 公共头文件路径(供其他模块引用) +set(PUBLIC_HEADER + ${CMAKE_CURRENT_SOURCE_DIR}/include # 包含imsl_vad.h和imsl_kws.h的目录 +) + +# 公共宏定义(可选) +set(PUBLIC_DEFINES + # 例如:IMSL_LN_ENABLE=1 + THUNDERBIRD_TOYS + SUPPORT_AT_VAD +) + +# 私有头文件路径(仅当前组件内部使用) +set(PRIVATE_HEADER + # 如无特殊私有头文件,可留空 +) + +# 私有宏定义(可选) +set(PRIVATE_DEFINES + # 如无特殊私有宏,可留空 +) + +# 公共编译选项(可选) +set(COMPONENT_PUBLIC_CCFLAGS +) + +# 组件专属编译选项 +set(COMPONENT_CCFLAGS + -Wno-incompatible-function-pointer-types + -Wno-unused-function # 屏蔽未使用函数的警告(根据实际情况调整) +) + +# 是否启用全量链接(静态库通常需要) +set(WHOLE_LINK + true +) + +# 是否为主组件(通常为false,主组件一般是app) +set(MAIN_COMPONENT + false +) + +# 安装SDK时包含的文件(头文件和库文件) +# install_sdk(${CMAKE_CURRENT_SOURCE_DIR}/include "*.h") # 安装头文件 +# install_sdk(${CMAKE_CURRENT_SOURCE_DIR}/lib "*.a") # 安装静态库(假设.a在lib目录) +install_sdk(${CMAKE_CURRENT_SOURCE_DIR} "*") + +# 直接指定静态库全路径(目录下现有文件名为 libimslvad.a、libimslkws.a、libimslnn.a) +set(LIBS + ${CMAKE_CURRENT_SOURCE_DIR}/lib/libimslvad.a + ${CMAKE_CURRENT_SOURCE_DIR}/lib/libimslkws.a + ${CMAKE_CURRENT_SOURCE_DIR}/lib/libimslnn.a +) + +# 构建组件 +build_component() \ No newline at end of file diff --git a/code/sdk/zhihui/leiniao/imsl_ln/REAL_VAD_TEST_GUIDE.md b/code/sdk/zhihui/leiniao/imsl_ln/REAL_VAD_TEST_GUIDE.md new file mode 100644 index 000000000..936e9ac7e --- /dev/null +++ b/code/sdk/zhihui/leiniao/imsl_ln/REAL_VAD_TEST_GUIDE.md @@ -0,0 +1,273 @@ +# 🎤 真实语音数据验证 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`: 静音阈值 (默认: 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 # 测试音频数据 +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 功能的正确性和性能了!🎯 diff --git a/code/sdk/zhihui/leiniao/imsl_ln/imsl_system_adapter.c b/code/sdk/zhihui/leiniao/imsl_ln/imsl_system_adapter.c new file mode 100644 index 000000000..a57546a9f --- /dev/null +++ b/code/sdk/zhihui/leiniao/imsl_ln/imsl_system_adapter.c @@ -0,0 +1,36 @@ +/** + * @file imsl_system_adapter.c + * @brief IMSL 库系统适配层,提供系统内存管理接口 + * @copyright Copyright (c) 2023-2023. All rights reserved. + */ + +#include +#include + +/** + * @brief 初始化系统堆内存 + * @note IMSL 库内部调用,这里提供空实现 + */ +void sys_heap_init(void) +{ + // 空实现,IMSL 库内部调用 +} + +/** + * @brief 系统内存分配 + * @param size 请求的内存大小 + * @return 分配的内存指针,失败返回 NULL + */ +void *sys_malloc(size_t size) +{ + return malloc(size); +} + +/** + * @brief 系统内存释放 + * @param ptr 要释放的内存指针 + */ +void sys_free(void *ptr) +{ + free(ptr); +} diff --git a/code/sdk/zhihui/leiniao/imsl_ln/include/imsl_kws.h b/code/sdk/zhihui/leiniao/imsl_ln/include/imsl_kws.h new file mode 100644 index 000000000..43ce640e4 --- /dev/null +++ b/code/sdk/zhihui/leiniao/imsl_ln/include/imsl_kws.h @@ -0,0 +1,140 @@ +#ifndef _IMSL_KWS_DLL +#define _IMSL_KWS_DLL + +#ifndef _IMSL_NN_COMMON +#define _IMSL_NN_COMMON +#include + +typedef int (*imsl_nn_printf_func)(const char *format, ...); + +struct AuthInfo +{ + char dmp_token[64]; +}; +#endif + +struct KwsConfig +{ + int min_activate_len; // 最小检测多少ms的激活才算检测到关键词 + int max_activate_gap; // 关键词激活点最长持续时间 比如 100ms 时检测到关键词但是未达到最短长度,直到100ms + max_activate_gap 这个时间点使激活失效 + int active_threshold; // 激活阈值 + + int kws_log_level; // kws log打印控制, 0 不打印,1 INFO,2 INFO DEBUG + + struct AuthInfo auth_info; +}; + +/* +初始化kws解码器,默认配置见下方的example +*/ +int init_kws(const struct KwsConfig* conf); + +int auth_kws(const struct AuthInfo* auth_info); + +void run_kws(const int16_t* data, const int data_len); + +int kws_detect(void); + +void kws_clear(void); + +void free_kws(void); + +#endif + +/* example 只使用kws +// 设置log函数 +extern imsl_nn_printf_func imsl_nn_printf; +imsl_nn_printf = printf; + +// 初始化 +struct KwsConfig kws_conf; +kws_conf.min_activate_len = 10; +kws_conf.active_threshold = 32; +kws_conf.max_activate_gap = 160; +kws_conf.kws_log_level = 0; + +int kws_status = init_kws(&kws_conf); + +struct AuthInfo auth_info; +strcpy(auth_info.dmp_token, dmp_token); +auth_kws(&auth_info); + +// 在每次接收语音时调用 注意 如果是void* 或 int8* 的指针,长度 / 2 +// 运行结束后获取kws状态 +run_kws(wave, chunk_size / 2); +int kws_status = kws_detect(); +if (kws_status == 1) { + kws_clear(); +} + +// 不使用kws或kws暂时停用时调用 +free_kws(); + +*/ + + +/* example 2 配合vad使用 +// 初始化 + + +struct VadConfig vad_conf; +vad_conf.sil_threshold = 63; +vad_conf.energy_threshold = 5000; +vad_conf.min_speech_length = 240; +vad_conf.silence_detect_length = 300; +vad_conf.rule1_min_tailing_silence = 480; +vad_conf.rule2_min_tailing_silence = 960; +vad_conf.rule3_min_tailing_silence = 960; +vad_conf.vad_log_level = 2; + +int vad_status = init_vad(&vad_conf); + +struct KwsConfig kws_conf; +kws_conf.min_activate_len = 10; +kws_conf.active_threshold = 32; +kws_conf.max_activate_gap = 160; +kws_conf.kws_log_level = 0; + +int kws_status = init_kws(&kws_conf); + +const int chunk_size = 480; // 这里的chunk size每次送入kws语音的点数 比如 30ms 就是 30 * 16 +const int chunk_cache = 9; // 由于vad存在一定延时,需要cache一些chunk防止丢失头部语音 +const int cache_size = chunk_cache * chunk_size; // chunk_cache 的值需要根据chunk_size的值变化 9*480/chunk_size +int16_t wave_cache[cache_size] = {0}; // !!!!!!!!!注意我这边是int16_t +int prev_vad_status = 0; // 记录vad状态,只在vad刚检测到语音时使用cache语音 + + +// 每次接收到语音时 +run_vad(wave + i, chunk_size); + +if (vad_status == 1) { + if (prev_vad_status == 0 || prev_vad_status == 2) { + for (j = 0; j < chunk_cache; j++) { + u32 time4 = timer_get_ms(); + run_kws(wave_cache + j * chunk_size, chunk_size); + u32 time5 = timer_get_ms(); + printf("run kws takes %d ms\n", time5 - time4); + } + } + run_kws(wave + i, chunk_size); + int kws_status = kws_detect(); + if (kws_status == 1) { + printf("!!!!!!!!!!!!!!!!!!!!!!keyword detected!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); + kws_clear(); + } +} else if (vad_status == 2) { + vad_clear(); +} + +prev_vad_status = vad_status; +memcpy(wave_cache, wave_cache + chunk_size, sizeof(int16_t) * (cache_size - chunk_size)); +memcpy(wave_cache + (cache_size - chunk_size), wave + i, sizeof(int16_t) * chunk_size); + + +// kws暂停使用时 +free_kws(); + +// vad暂停使用时 +free_vad(); + +*/ diff --git a/code/sdk/zhihui/leiniao/imsl_ln/include/imsl_vad.h b/code/sdk/zhihui/leiniao/imsl_ln/include/imsl_vad.h new file mode 100644 index 000000000..0fb9e2017 --- /dev/null +++ b/code/sdk/zhihui/leiniao/imsl_ln/include/imsl_vad.h @@ -0,0 +1,103 @@ +#ifndef _IMSL_VAD_DLL +#define _IMSL_VAD_DLL +#include + +#ifndef _IMSL_NN_COMMON +#define _IMSL_NN_COMMON +typedef int (*imsl_nn_printf_func)(const char *format, ...); + +struct AuthInfo +{ + char dmp_token[64]; +}; +#endif + +// 注意这个必须要与imsl_vad.h中的定义一致 +struct VadConfig +{ + int vad_log_level; // 0 不启用 1 INFO 2 INFO、DEBUG + int sil_threshold; // 静音阈值 推荐值 63 (0-127) (修改意义不大) + int energy_threshold; // 能量阈值 推荐值 10000 + int min_speech_length; // 最短语音长度 240 (须小于 rule1_min_tailing_silence) + int silence_detect_length; // vad_silence_detect 接口判定的尾部静音长度, 如果不需要vad_silence_detect接口可以忽略这个参数 + int rule1_min_tailing_silence; // 多长时间没有检测到语音重置VAD状态 (ms) 480 + int rule2_min_tailing_silence; // 0-10s最短多少静音判定语音结束 (ms) 960 + int rule3_min_tailing_silence; // 10s以上最短多少静音判定语音结束 (ms) 960 + + struct AuthInfo auth_info; +}; + +/* 初始化vad +详细参数设置参照 imsl_vad.h +返回值 : + 0 初始化失败 + 1 试用模式 (最长30分钟) + // 2 授权成功 (如果授权失败控制台会打印错误信息,同时转为试用模式) +*/ +int init_vad(const struct VadConfig* conf); + +/* 授权vad +返回值: + 0 初始化失败 + 1 初始化成功 +*/ +int auth_vad(const struct AuthInfo* auth_info); + +// 运行vad函数,data为16khz 16bit的pcm音频数据 +// 注意data_len是int16_t的data_len,如果传入u8*或void* 数据data_len需要 / 2 +void run_vad(const int16_t* data, int data_len); + +// 获取VAD的状态 +// 0 静音 1 语音 2 语音结束 +int vad_detect(void); + +// 获取尾部静音的状态 +// 1 尾部静音超过规定时长,0 尾部静音未超过规定时长 +// 注意如果要调用 vad_silence_detect,需要在vad_clear前调用 +int vad_silence_detect(void); + +// 清除尾部静音,调用这个函数后尾部静音长度会清0 +void vad_silence_clear(void); + +// 重置VAD的状态 每次在vad_detect为2时调用 +void vad_clear(void); + +// 释放VAD内存,不使用VAD或暂停使用VAD时调用 +void free_vad(void); + +#endif + +/*example : + #include "imsl_vad.h" + + // 设置log函数 + extern imsl_nn_printf_func imsl_nn_printf; + imsl_nn_printf = printf; + + // 启用VAD时初始化 + struct VadConfig conf; + conf.sil_threshold = 63; + conf.energy_threshold = 10000; + conf.min_speech_length = 240; + conf.silence_detect_length = 300; + conf.rule1_min_tailing_silence = 480; + conf.rule2_min_tailing_silence = 960; + conf.rule3_min_tailing_silence = 960; + conf.vad_log_level = 2; + init_vad(&conf); + + struct AuthInfo auth_info; + strcpy(auth_info.dmp_token, dmp_token); + auth_vad(&auth_info); + + // 每次获得语音后调用, 语音长度最好小于100ms + // wave为u8*或void*指针时需要 / 2 + run_vad(wave, wave_len / 2); + int status = vad_detect(); + if (status == 2) { + vad_clear(); + } + + // 不使用或暂停使用VAD时调用 + free_vad(); +*/ \ No newline at end of file diff --git a/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslkws.a b/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslkws.a new file mode 100644 index 000000000..6c074350d Binary files /dev/null and b/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslkws.a differ diff --git a/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslnn.a b/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslnn.a new file mode 100644 index 000000000..0f737239c Binary files /dev/null and b/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslnn.a differ diff --git a/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslvad.a b/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslvad.a new file mode 100644 index 000000000..293b4e009 Binary files /dev/null and b/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslvad.a differ diff --git a/code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.c b/code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.c new file mode 100644 index 000000000..dadac514d --- /dev/null +++ b/code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.c @@ -0,0 +1,475 @@ +#include "vad_at_cmd.h" +#include "at_cmd_api.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 \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 +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 \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 - 分析已有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; +} diff --git a/code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.h b/code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.h new file mode 100644 index 000000000..d95097f08 --- /dev/null +++ b/code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.h @@ -0,0 +1,24 @@ +#ifndef VAD_AT_CMD_H +#define VAD_AT_CMD_H + +#include +#include +#include "errcode.h" +#include "soc_osal.h" +#include "at_cmd_api.h" + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif /* __cplusplus */ +#endif /* __cplusplus */ + +at_cmd_table_t *get_vad_cmd_table(uint32_t *cmd_count); + +#ifdef __cplusplus +#if __cplusplus +} +#endif /* __cplusplus */ +#endif /* __cplusplus */ + +#endif