
# 嵌入式语音关键词检测KWS引擎 — 工程实践讲解 项目名称longAsrLibSOC_ASR_AEC 模型IDGASR_320k 平台嵌入式 Linux / MCU 核心功能关键词检测Keyword Spotting 声学回声消除AEC---## 第一章项目概述与工程结构### 1.1 项目背景本项目是一个面向嵌入式设备的**语音关键词检测引擎**包含从音频采集、前端信号处理、神经网络推理到关键词解码的完整流程。代码同时支持 **PC Linux 离线文件处理** 和 **嵌入式实时流式处理**。### 1.2 核心功能- **KWSKeyword Spotting**从连续音频流中检测预定义的关键词- **AECAcoustic Echo Cancellation**消除扬声器播放音对麦克风采集的干扰- **NSNoise Suppression**背景噪声抑制- **VADVoice Activity Detection**人声活动检测支持多种策略分贝检测、特征检测等- **多麦克风支持**2/3/4 通道麦克风阵列- **CTC 解码 Recheck 机制**降低误触发率### 1.3 目录结构GASR_AEC/├── CMakeLists.txt # 顶层 CMake 构建├── build.sh # 编译脚本├── run.sh # 运行脚本手动模式├── all_run.sh # 全流程一键运行配置生成编译推理│├── ui_main.c # 主入口离线文件推理├── streaming.cpp # 主入口PC 实时录音流式推理├── kws_detect.c / .h # KWS API 封装层├── bsp_long_asr_ctrl.c / .h # 板级支持层ASR 初始化与控制├── linkqueue.c / .h # 链表队列工具用于未唤醒音频截取│├── appLangSrc/ # 语言模型资源解析层│ ├── long_u_lang_pc_main.c # 多语言模型资源创建/释放│ ├── long_language.c # 语言管理│ ├── long_keyword.c # 关键词列表解析│ ├── long_thres_list.c # 阈值列表解析│ ├── long_lang_model.c # 语言模型配置│ ├── long_parse_model_config.c # 模型配置文件解析│ └── long_parse_thres_list.c # 阈值列表文件解析│├── long_config/ # 生成的模型配置由 main_sdk_res_cfg 生成│ ├── model_config.h # 模型架构参数卷积、FSMN、特征等│ ├── long_alg_config.h # 算法开关配置│ ├── long_weights.c/.h # 模型权重│ ├── long_keyword.c/.h # 关键词数据结构│ ├── long_thres_list.c/.h # 阈值数据结构│ └── long_decoder_patch.c # 解码器 Recheck Patch│├── appResOut/ # SDK 资源输出模板│ ├── lib_long_asr.h # ASR 库 API 头文件│ ├── long_decoder_patch.c # 解码 Patch 模板│ └── long_alg_config.h # 算法配置模板│├── template/ # 通用模型模板│ ├── aec_weights_*.bin # AEC 权重│ ├── weights_ns_*.bin # NS 权重│ └── vad_tiny_*.bin # VAD 权重│├── tuning/works/ # 模型调优工作区│ └── G111_320K/ # 当前模型332KB│ ├── weights.bin # 主网络权重│ ├── long_keyword.bin # 关键词 bin│ ├── thres_list.bin # 阈值 bin│ └── model_config.h # 模型配置│└── wav/, audioCut/, pcm/ # 音频输入/输出/调试目录### 1.4 构建与运行流程all_run.sh 全流程① main_sdk_res_cfg → 生成 long_config/权重、关键词、阈值、配置② cp 配置文件 → app/projects/libuser_proj/③ build.shcmake make → 编译出 main 可执行文件④ ./main input.pcm weights.bin ... → 执行离线推理---## 第二章神经网络模型架构### 2.1 整体网络结构模型基于 **CNN FSMNFeedforward Sequential Memory Network** 混合架构接收 FBank 声学特征输出 CTC 后验概率。音频输入 (16kHz)↓FBank 特征提取 (80维, 40ms帧移)↓PCEN (Per-Channel Energy Normalization)↓Conv2D × 3 (时域卷积时间降采样)↓FSMN × 6 (双向记忆网络)↓线性层 Softmax↓CTC 解码 → 关键词检测### 2.2 卷积层Conv2D配置| 层 | 输入通道 | 输出通道 | 卷积核 | 步长 | 输出尺寸 ||---|---|---|---|---|---|| Conv2D-1 | 1 | 8 | (4, 3) | (1, 2) | (8, 4, 39) || Conv2D-2 | 8 | 16 | (3, 3) | (2, 2) | (16, 2, 19) || Conv2D-3 | 16 | 32 | (3, 3) | (2, 1) | (32, 1, 17) |- 每层输出经过 **ReLU6** 激活- 用于时间维度的降采样和局部模式提取- 缓存机制TOTAL_CACHE_SIZE 计算跨帧依赖### 2.3 FSMN 层核心建模单元- **6 层** FSMN每层隐层维度 **192**- 记忆维度P_DIM**108**- 前看帧数RIGHT_FRAMES**1**- 状态长度**7**左 3 右 1 自身 1- **FSMN V1 层无 bias**FSMN_V_BIAS 0FSMN 通过在隐层状态上叠加记忆单元来建模时序依赖比传统 RNN 更轻量、更适合嵌入式部署。### 2.4 CTC 输出- 类别数**66**中文音素 静音 空白符- 混合精度量化MixedPTSQ**46 个 Q 值****23 个混合精度层**- 模型总大小**332,876 字节**约 325KB### 2.5 PCEN 特征增强c#define long_PCEN_EN 1 // 启用 PCEN#define long_PCEN_MAX_SIZE 1 // PCEN 增益上限PCEN 替代传统 CMVN对动态范围进行逐频带自适应压缩在噪声环境下更鲁棒。---## 第三章音频前端处理### 3.1 多麦克风通道处理根据 long_DSP_MIC_MAX_NUM可配 1/2/3/4选择对应的通道分离函数c// ui_main.c: 通道分离实现long_ch_separate_process2 — 2通道LRlong_ch_separate_process3 — 3通道L,R,Mlong_ch_separate_process4 — 4通道L,R,M,N### 3.2 AEC声学回声消除c// long_alg_config.h#define long_AEC_CLOSE_T 0 // 关闭#define long_AEC_V1_T 1 // AEC V1 版本#define long_AEC_V2_T 2 // AEC V2 版本#define long_AEC_ONLY_T 6 // 仅 AEC#define long_ASR_UI_AEC_TYPE 1 // 当前启用 AECAEC 参考信号来自扬声器通道通过自适应滤波器建模回声路径从麦克风信号中减去。主麦克风和 AEC 参考麦克风的选择c// bsp_long_asr_ctrl.cslong_long_mcu_micMasterIdx(0); // 主麦克风slong_long_mcu_micAecIdx(1); // AEC 参考### 3.3 NS噪声抑制c#define long_NS_CLOSE_T 0#define long_NS_V1_T 1 // 神经网络噪声抑制#define long_NS_ONLY_T 6#define long_ASR_UI_NS_TYPE 0 // 当前未启用 NS模板中提供了 3k/5k/9k 三种规模的 NS 权重可按需切换。### 3.4 VAD语音活动检测支持多种 VAD 策略通过 long_ASR_UI_VAD_TYPE 选择| 值 | 宏 | 说明 ||---|---|---|| 0 | long_VAD_TYPE_CLOSE | 关闭 VAD || 1 | long_VAD_TYPE_PURE_DB | 纯分贝检测 || 2 | long_VAD_TYPE_HUMAN_VOL | 人声分贝检测 || 3 | long_VAD_TYPE_HUMAN_NS | 基于 NS 的人声检测 || 5 | long_VAD_TYPE_HUMAN_FEATURE | 基于特征的人声检测 || 6 | long_VAD_TYPE_ONLY_VAD | 仅 VAD |VAD 结束判决参数ui_main.cct_cfg_p-long_hm_no_cnt_max 40; // 连续 40×40ms 1.6s 静音判定结束t_cfg_p-long_wake_times_thd 2; // 唤醒次数阈值### 3.5 FBank 特征提取c#define SAMPLE_RATE 16000#define NFFT 512 // FFT 点数#define NFILT 70 // Mel 滤波器组维度#define long_FBANK_STRIDE 192 // 帧移12ms 16kHz#define WINDOW_STRIDE 4 // 特征帧移特征提取流程1. 预加重 → 分帧加窗Hanning2. 512 点 FFT → 功率谱3. Mel 滤波器组70 维→ Log 压缩4. PCEN 增强 → 输出1, 4, 70特征块---## 第四章关键词检测与解码### 4.1 关键词数据结构关键词在系统中以**音素序列**表示。关键词列表通过 long_keyword.bin 加载解析为 long_KWS_PARAM 结构体数组c// appLangSrc/long_keyword.htypedef struct {int16_t *phone_idx_seq; // 音素索引序列int16_t seq_len; // 序列长度char *label; // 关键词文本标签如关灯} long_KWS_PARAM;### 4.2 解码流程FBank 特征→ 神经网络前向 → CTC 后验概率矩阵 (T × 66)→ 贪婪解码 / 前缀束搜索 → 音素对齐路径→ 关键词匹配检测音素序列是否匹配→ Recheck 验证 → 输出触发事件### 4.3 Recheck 机制防误触long_decoder_patch.c 实现了三重验证1. **recheck_neg**负例验证- 检查两个识别音素之间的**时间间隔**是否过大40帧1.6s 拒识- 检查 CTC 最大路径与对齐路径的**不一致计数**是否超阈值2. **recheck_on_talk**起始验证- 使用 KWS_COMPLETE_FLAGprefix_flag254标记关键词结束- 检查起始音素前的负例帧计数3. **has_k_zeros**跳帧检测- 检测是否存在连续 K 帧 CTC 得分极低的情况### 4.4 阈值系统每个关键词对应一组阈值thres_list.bin包含- thres_neg_left / thres_neg_right负例数量阈值- 各层得分的门限值- 聚类配置WordsSimilarGroups计算关键词间的相似度生成聚类集### 4.5 流式处理与离线处理**离线模式**ui_main.ccwhile (fread(pcm_data, 2, buffer_len, pcm1_fin) buffer_len) {// 通道分离long_ch_separate_process4(pcm_data, samples, n_ch_diff_data);// 送入 ASR 引擎long_asr_wakeup_buf_write(n_ch_diff_data, samples, 1, 2, 0, 1);// 检测唤醒if (wakeup) { /* 保存语音片段 */ }}**流式模式**streaming.cpp- 使用 **RtAudio** 库采集实时音频6通道16kHzSINT16- 回调函数中提取通道数据后调用 kws_detect()- 适用于 ReSpeaker 4-Mic Array 等 USB 麦克风阵列### 4.6 KWS API 接口c// kws_detect.hint kws_init(); // 默认资源路径初始化int kws_init_with_res(weights, param, // 指定资源文件路径keyword, thres, recheck);int kws_detect(short *pcm, int len); // 逐帧检测int kws_exit(); // 清理资源---## 第五章工程实践与调试### 5.1 编译与运行#### 首次全流程bash# 1. 配置 all_run.sh 中的资源路径# 2. 一键执行./all_run.sh# → 自动完成配置生成 → 编译 → 推理#### 手动分步bash# 1. 编译 outFactory仅首次cd outFactory ./build.sh ./run.sh# 2. 生成 long_config/./main_sdk_res_cfg ...参数见 all_run.sh# 3. 编译主程序./build.sh# 4. 运行推理./main input.pcm weights.bin \outfile_disable_recEnable langNo:0 \dummy.pcm long_keyword.bin thres_list.bin \model_config.h audio_cut_enable \pcm_wake_enable123 pcm_unwake_enable123### 5.2 调试工具#### 内存检测bash# run.sh 中取消注释即可valgrind --toolmemcheck --leak-checkfull \--track-originsyes ./main ...#### 唤醒音频保存c// ui_main.c#define long_DEBUG_ASR_WAKE_FILE_EN 1 // 保存唤醒前后的 PCM#define long_DEBUG_ASR_UNWAKE_FILE_EN 1 // 保存未唤醒的 PCM用于负例分析保存位置./pcm/ 和 ./pcm_unwake/#### 音频分割c// ui_main.c: 命令行参数 audio_cut_enable// 使用 VAD 回调将每个语音片段保存到 ./audioCut/s_N.pcm### 5.3 关键配置项调整| 配置文件 | 配置项 | 作用 ||---|---|---|| long_alg_config.h | long_DSP_MIC_MAX_NUM | 麦克风通道数 || long_alg_config.h | long_ASR_UI_AEC_TYPE | AEC 开关 || long_alg_config.h | long_ASR_UI_VAD_TYPE | VAD 策略选择 || model_config.h | H_DIM, P_DIM | FSMN 隐层/记忆维度 || model_config.h | RIGHT_FRAMES | 前看帧数 || long_alg_patch_config.h | long_PATCH_USE_EN | Recheck 开关 |### 5.4 性能优化建议1. **内存优化**- 启用 long_VAD_NEED_UI_HEAP_EN / long_ASR_NEED_UI_HEAP_EN 使用堆内存- TOTAL_CACHE_SIZE 控制 Conv2D 缓存大小- long_PRIV_VAL_POOL_SIZE 控制私有内存池2. **计算优化**- 混合精度量化MixedPTSQ减少模型大小和推理时间- Conv2D 使用 img2col GEMM 加速在 DSP 平台上- FSMN 的 STATE_CACHE_LEN 控制状态缓存量3. **准确性调试**- 调整 thres_list.bin 中的阈值改善召回率与精度的平衡- 调整 t_cfg_p-long_hm_no_cnt_max 控制 VAD 断句时长- 调整 recheck_neg 中的 thres_neg_left/right 参数### 5.5 常见问题| 问题 | 排查方向 ||---|---|| 唤醒率低 | 检查 AEC/NS 是否正常降低 thres_list 阈值确认麦克风增益 || 误触发多 | 开启 Rechecklong_PATCH_USE_EN1提高阈值检查音频质量 || 编译失败 | 运行 sed -i -e s/\r$// build.sh run.sh 转换换行符 || 运行时崩溃 | 使用 Valgrind 检测内存泄漏检查权重文件路径是否正确 |---*本文档基于 etAsrLib 工程JLLIBETASR_AEC2 / G111_320k编写用于课程设计教学参考。*