python .\application\bt_watch\tools\ble_stt\ble_stt.py record --dialog --seconds 8


ATS3089P 录音 ANS 噪声抑制说明
更新日期:2026-10-19。工程:`/application/bt_watch`。
1. 本次实现与适用范围
本次增加在 **ATS3089P CPU 上运行**的轻量录音 ANS,使用普通串口命令开启、关闭和查询状态。板端先处理麦克风 PCM,再通过 BLE 发送到 PC;选择 Opus 时,处理发生在 Opus 编码之前。PC 不承担本次降噪计算,也不需要另装降噪模型。
当前接入以下路径,格式均为 **16 kHz、16 bit signed little-endian、单声道**:
| 录音用途 | 是否经过本次 ANS |
| --- | --- |
| `ble_audio.py record --format pcm` | 开启后处理,再传到 PC 保存 PCM/WAV |
| `ble_audio.py record --format opus` | 开启后处理,再压缩为 Opus、传到 PC |
| `ble_stt.py record` 中文转写 | 开启后处理,PC 识别处理后的 PCM |
| `ble_stt.py record --dialog` 语音对话 | 开启后处理,PC 识别、问答与回答播报继续按原流程执行 |
| 屏幕"再次录音" | 下一轮录音使用当时设置的 ANS 开关 |
| `ble_audio.py download` 已有文件下载 | 原样传输,不对历史文件补做降噪 |
| 原来的板内文件录音、双 MIC 立体声录音 | 本次不接入 |
| 蓝牙通话上行/下行、原厂 AEC 与通话 dump | 本次不接入 |
开机默认 **关闭**。设置保留至下次重启,不写入 Flash/NVRAM。正在录音时修改开关,从**下一次录音**生效,避免同一个文件前后使用不同算法状态。
2. 敲桌子为什么能被录到,ANS 能做到什么
桌面敲击可以通过空气传播,也可以经外壳、支架和 PCB 传导到麦克风。它通常包含低频振动与短时宽频冲击,属于瞬态噪声。风扇、空调和持续底噪则更接近平稳噪声,两者的处理难度不同。
本实现同时考虑低频振动、持续底噪与孤立冲击。预期可以削弱部分敲击声和背景噪声;**人声与敲击完全重叠时,单麦算法不能保证只去掉敲击而完整保留人声**。为了避免把"啪、噗"等语音爆破音误删,连续讲话期间会保守处理冲击,敲击可能仍然可闻。
如果麦克风或 ADC 已经因强冲击削顶,软件不能恢复被削掉的原始波形。实际使用中,麦克风减振、避免外壳接触桌面和合适的模拟增益也会影响结果。
ANS 不需要远端扬声器参考信号。AEC 则主要处理扬声器声音再次进入麦克风形成的回声;本次 ANS 开关与之前的原厂 AEC 开关是独立功能。
3. 常用算法与本平台的选择
| 方案 | 特点 | 本工程的取舍 |
| --- | --- | --- |
| 高通滤波 | 运算少,适合削弱直流与低频振动 | 作为前级使用,不能单独解决宽频敲击 |
| 谱减、Wiener 谱增益 | 估计噪声功率谱,逐频带压低噪声,资源较小 | 本次选择平滑 Wiener 类型增益并加入噪声跟踪 |
| 统计语音增强,例如 MMSE 系列 | 利用语音和噪声统计模型计算增益,参数与实现更复杂 | 可作为后续对比方案;本次不声称实现了完整 MMSE 算法 |
| 瞬态检测与抑制 | 根据短时能量和频谱识别冲击,针对敲击更直接 | 与谱抑制组合,加入语音保护,避免仅按音量静音 |
| RNNoise 等神经网络降噪 | 结合 DSP 与训练模型,需要模型和对应推理计算 | 本轮未移植,不能把 PC 上能运行直接等同于板端实时可用 |
本工程已有 BLE 编码、UI、语音对话等功能。改动前链接报告中,32 MiB PSRAM 已使用 **33,295,712 字节,静态余量 258,720 字节**;共享音频 RAM 余量也很小。因此本轮先选择无模型、无动态分配的传统算法。ARM 目标文件中,核心静态工作区为 **11596 字节**,另有 20 字节统计和 1 字节开关,含对齐约 **11620 字节 / 11.35 KiB**;整机新增占用还包括接入状态、对齐和代码,最终以链接报告为准。
现有固件配置包含 `CONFIG_CPU_CORTEX_M33=y`、`CONFIG_CPU_HAS_FPU=y`、`CONFIG_FPU=y` 和 `CONFIG_FPU_SHARING=y`,具备单精度浮点实现条件。是否满足整机实时负载,还需要结合处理耗时与 BLE 缓冲状态判断,不能只按主频或算法名称作保证。
4. 一条命令开启、关闭
在原来的**普通串口终端**发送,不要求通过 USB CDC 发命令:
```text
app ans on
```
关闭:
```text
app ans off
```
查询:
```text
app ans status
```
也可直接发送 `app ans` 查看状态。命令只设置 ANS,不自动开始录音;开始录音继续使用现有 PC 命令或屏幕"再次录音"按钮。
例如 PC 已运行语音对话程序,当前正在等待"再次录音"时,在普通串口发送 `app ans on`,然后点击屏幕按钮,即可让下一轮采用降噪。想对比原始录音时,发送 `app ans off` 后再录一轮。
5. 从 PC 录制并试听
PowerShell 进入 `` 工程根目录。PC 的 BLE 连接、Python 依赖与模型继续使用已经验证过的环境。
5.1 PCM/WAV 对比
先在普通串口发送 `app ans off`,再在 PC 执行:
```powershell
python .\application\bt_watch\tools\ble_audio\ble_audio.py record --format pcm --seconds 10 --output-dir D:\logs\ans_off
```
然后普通串口发送 `app ans on`,再执行:
```powershell
python .\application\bt_watch\tools\ble_audio\ble_audio.py record --format pcm --seconds 10 --output-dir D:\logs\ans_on
```
每次输出独立时间戳目录,包含 `audio.pcm`、`audio.wav` 与 `metadata.json`。PCM/WAV 的采样格式、样本顺序和时长保持一致;10 秒对应 160000 个单声道样本,即 320000 字节裸 PCM。
开启时这里保存的是**板端 ANS 处理后的音频**。本次不在一次采集中并行导出 ANS 前、后的两路音频;两次实录的说话与敲击内容不会完全相同。需要精确比较算法时,应使用同一份输入 PCM 的软件验证结果,不能仅凭两次手工敲击的幅度作定量结论。
建议先用 WAV 试听,避免把 PCM 误按 8 kHz、立体声或不同位宽导入。比较时保持相同播放音量,关闭播放器自动音量归一化。
5.2 Opus
```powershell
python .\application\bt_watch\tools\ble_audio\ble_audio.py record --format opus --seconds 10 --decode --output-dir D:\logs\ans_opus
```
得到 Opus 文件以及 PC 解码后的 PCM/WAV。Opus 文件的输入已经在板端经过 ANS。`--decode` 的 FFmpeg 准备方式见 BLE 无线音频说明(BLE_AUDIO_TRANSFER_README.md)。如果目的是单独评估 ANS,优先比较 PCM,减少有损编码对判断的影响。
5.3 中文转写与语音对话
```powershell
中文转写,默认保留屏幕"再次录音"
python .\application\bt_watch\tools\ble_stt\ble_stt.py record --seconds 8
中文转写 + PC 本地问答 + 开发板显示/播报
python .\application\bt_watch\tools\ble_stt\ble_stt.py record --dialog --seconds 8
```
不增加 PC 降噪参数,沿用现有 BLE 命令与连接。ANS 开关在板端通过普通串口设置。屏幕按钮的后续录音与首轮录音使用同一处理链路。
6. 处理原理与数据流
```text
MIC0 / ADC,16 kHz 单声道
↓ DMA 回调:仅把原始样本复制到环形队列
BLE 录音工作线程,每块最多 320 样本 / 20 ms
↓ 本次录音开始时锁存 ANS 开关
├─ off:PCM 原样通过
└─ on:90 Hz 高通 → 瞬态检测/保守衰减 → 平滑频谱降噪
↓
PCM:直接 BLE 通知发送
Opus:先编码,再 BLE 通知发送
↓
PC 保存;或者 Vosk 转写 → 本地问答 → 回传文字/回答语音
```
6.1 高频/低频与瞬态处理
前级采用 90 Hz 二阶 Butterworth 高通,主要削弱低频传导振动。它会改变低频能量,因此 ANS 开启后音色可能略有变化。
瞬态检测把一个 20 ms 块细分为约 1 ms 能量段,比较峰值段与块内中位能量,并结合频谱形状判断。谱形使用归一化的幅度和平方与谱能量之比,即 `L1² / (频点数 × L2²)`,作为频谱稀疏/平坦程度的代理指标;它不是经典的"几何均值/算术均值"谱平坦度。能量突变、短促且符合宽频冲击或低频冲击特征时,平滑降低冲击所在段的增益。
当前块检测到人声,或者最近约 120 ms 有人声时,冲击衰减的振幅增益最低保留 0.65;其他孤立冲击最低保留 0.12,实际使用平滑过渡。这里的语音保护是轻量启发式判断,不是神经网络的语音/噪声分类,也不代表已经准确分离出人声。
6.2 频谱降噪
-
每块采用 256 点 FFT,内部子窗移动 128 点;块边缘使用反射补边。
-
用 Hann 分析窗得到频谱,计算各频带功率。
-
平滑功率谱并跟踪局部最小值,估计持续噪声;瞬态期间冻结相关噪声更新,讲话时慢更新。
-
计算 Wiener 类型振幅增益,对噪声占比高的频带降低增益。
-
对增益做时间、频率平滑,并保留非零底限,降低断续和"音乐噪声"。一般谱增益底限为 0.15;语音保护期间全频最低保留 0.85,避免为降低背景声而明显削弱讲话。
-
IFFT 后加合成窗,按重叠窗权重归一化,写回当前 PCM 块。
块内反射补边可能让相邻块边缘的增益略有差异。语音期间,块开头增加约 2 ms 半余弦拼接校正,跟随真实输入的一阶变化平滑边界,减少块边界爆音;不把上次处理结果重新送入麦克风输入,也不插入额外音频样本。
噪声估计并不假定第一帧一定没人说话。开始录音后先安静约 0.5~1 秒,有助于观察底噪跟踪过程,但不是强制的静音校准步骤。
处理采用块内补边,不为算法额外插入整块 PCM 延迟;仍然要等待现有录音链路收齐处理块。输出不新增或丢弃样本,短尾块也按实际样本数处理,避免录音时长变化或末尾少一截。生产接入固定使用 320 点块,只允许最后一块不足 320 点;不要用连续小块任意替代 20 ms 调用,否则噪声估计更新速度和块内补边都会变化。
7. 代码在哪,如何修改
| 文件 | 作用 |
| --- | --- |
| `src/main/cpu_ans.h` | 采样格式约束、独立开关、单轮开始/结束、PCM 原地处理、统计接口 |
| `src/main/cpu_ans.c` | 高通、256 点 FFT/IFFT、噪声跟踪、谱增益、瞬态抑制、语音保护与静态工作区 |
| `src/main/ble_audio_transfer.c` | 开始录音时锁存设置,在 ADC 出队后、PCM 发送/Opus 编码前调用 ANS,测量处理耗时并输出状态 |
| `src/main/ble_audio_transfer.h` | 向普通串口壳层提供 ANS 状态查询接口 |
| `src/main/system_app_shell.c` | 注册和处理 `app ans on/off/status` |
| `src/main/CMakeLists.txt` | 编译 ANS 模块与局部优化选项 |
| `Kconfig` | `CONFIG_APP_CPU_ANS` 编译开关,依赖 BLE 音频传输功能 |
| `tests/cpu_ans/test_core.c` | 直接调用生产 ANS C 接口的 ARM 核心回归测试 |
| `tests/cpu_ans/run_core_tests.py` | 编译 ARM 测试、指令仿真、构造噪声与敲击样本、输出指标和试听 PCM/WAV |
新增、修改的重要位置使用 `//zk+ 中文说明` 与 `//zk+- 中文说明` 标记;CMake/Kconfig 使用对应的 `#zk+`、`#zk+-`。可以搜索 `zk+` 或 `CONFIG_APP_CPU_ANS` 找到接入点。
算法状态使用普通静态 BSS,不在 ADC DMA 中断里运行 FFT,也不动态申请大块堆内存。一个会话只有 BLE 录音工作线程修改算法状态。ANS 开关在开始录音时锁存,录音结束、取消、断链与失败路径均结束本轮状态。
需要进一步调整时,优先改 `cpu_ans.c` 中的高通系数、噪声更新速度、频谱增益底限与瞬态阈值。减小底限通常会增加抑制,也可能带来人声变薄、辅音丢失或断续,必须同时检查干净人声与讲话加噪声的结果。
8. 状态字段与实时性检查
普通串口 `app ans status` 输出两行,例如:
```text
ans: configured=1 session=1 active=0 rate=16000 channels=1 samples=160000 frames=500 transients=... speech_frames=...
ans: blocks=500 avg_us=... peak_us=... overruns=0 budget_us=20000 scope=ble_mic
```
上面是字段示例,省略号不是实际测量值。
| 字段 | 含义 |
| --- | --- |
| `configured` | 为下一轮设置的开关,1 开启、0 关闭 |
| `session` | 最近一轮录音开始时实际锁存的开关 |
| `active` | 是否仍处于本轮录音处理会话;结束后为 0 |
| `samples` | 本轮实际执行 ANS 的 PCM 样本数,关闭时为 0;实际录音长度查看 `app ble_audio status` 的 `samples` |
| `frames` | 本轮实际执行算法的处理块数,关闭时为 0 |
| `transients` | 检测到瞬态条件的处理块数,不等同于真实敲击次数 |
| `speech_frames` | 启发式判断为语音且非冲击的块数,不用于宣称识别准确率 |
| `blocks` | 耗时统计的处理调用次数 |
| `avg_us` / `peak_us` | 单次处理平均/最大耗时;包含工作线程被其他任务抢占的时间 |
| `overruns` | 超出该块实际音频时长预算的调用次数;完整块预算为 20000 µs |
| `scope=ble_mic` | 明确作用范围为 BLE 麦克风实时录音 |
开启后录制 10 秒,再查询状态,确认 `session=1`、`samples=160000`、算法块计数非零。查询 `app ble_audio status`,检查没有环形队列溢出或传输错误。
`overruns=0` 是重要观察项,但一次短录音不能代表所有整机负载;需结合屏幕刷新、Opus 编码、BLE 距离和持续录音验证。PC 算法验证耗时、ARM 指令仿真耗时都不能当作板端 CPU 实测值。
9. 建议验证步骤
-
**原样通过**:关闭 ANS,确认既有录音/Opus/转写/对话仍能完成。软件测试检查同一份输入在关闭状态下逐样本一致。
-
**持续底噪**:固定麦克风和噪声来源,分别录制关闭/开启版本,比较安静段底噪与讲话清晰度。
-
**无人讲话时敲击**:轻敲、重敲、不同位置各测试几次,查看瞬态计数与试听效果。
-
**讲话时敲击**:保持正常说话,检查敲击是否减弱,以及人声辅音、爆破音是否被误伤。此场景允许残留敲击,不能只追求整体音量变小。
-
**干净人声**:不敲击、不播放额外噪声,检查音色、清晰度与转写结果;单纯把所有声音压低不代表降噪成功。
-
**边界条件**:1 秒、10 秒、较长录音,PCM 与 Opus,屏幕再次录音,取消与断开 BLE 后重录,录音中切换开关后下一轮生效。
针对同一份 PCM 的算法软件验证可以量化噪声段能量、人声段幅度、削顶次数与输出长度;真实板卡的敲击效果和整机实时性仍需烧录后按上述条件验证。
10. 固件与验证记录
本次交付的 ANS 固件:
```text
application/bt_watch/outdir/ats3089p_dev_watch/_firmware/ats3089p_dev_watch_261019_cpu_ans_v1.fw
```
同目录提供 `ats3089p_dev_watch_261019_cpu_ans_v1_debug.fw`、`ats3089p_dev_watch_261019_cpu_ans_v1_SHA256.txt` 与 `ats3089p_dev_watch_261019_cpu_ans_v1_manifest.json`,编译日志为 `application/bt_watch/outdir/ans_record_build.log`。本次固件包含原有 v3 语音对话功能与本次 ANS;此前只含语音对话的 v3 固件没有 `app ans` 命令。
本轮已完成固件编译与打包,构建退出码为 0,日志出现 `build finished`。最终链接占用:
| 区域 | 已使用 |
| --- | --- |
| FLASH | 2,284,124 字节 |
| SRAM | 875,776 字节 |
| PSRAM | 33,307,360 字节;32 MiB 中剩余 247,072 字节 |
| SHARE_RAM | 20,948 字节,与改动前一致 |
整机 PSRAM 静态占用比改动前增加 11,648 字节,含 ANS 核心状态、接入状态和链接对齐。本次没有新增共享音频 RAM 工作区。
算法测试直接编译生产 C 源码并在 ARM 指令仿真中运行,使用合成底噪、合成低频衰减敲击和已有中文语音验证同源输入前后结果。试听 WAV、`metrics.json` 与测试可执行文件位于 `application/bt_watch/outdir/cpu_ans_tests/`。合成敲击只是可重复的算法测试输入,不等同于本板真实敲桌子的声学或结构传导模型。测试数值、构建结果和最终内存使用以本次清单为准。
本轮同源输入的软件验证结果:
| 测试输入 | 指标 | 结果与范围 |
| --- | --- | --- |
| 合成白噪声 | 学习后的噪声段能量衰减 | 约 6.68 dB |
| 合成 160 Hz 衰减振铃敲击 | 指定敲击区间能量衰减 | 约 3.65 dB;包含语音保护的保守设置,不代表真实桌面敲击效果 |
| 合成宽带双样本点击 | 指定点击区间能量衰减 | 约 11.42 dB;与低频振铃是不同测试模型,不能代替真实敲桌子结论 |
| 中文 SAPI 语音 | 全段能量损失 | 约 0.063 dB;没有额外叠加测试噪声 |
| 同一段中文语音 | 输入/输出相关系数 | 约 0.966;不是语音质量评分或识别率 |
| 无前导静音的中文语音 | 开头 0.5 秒能量损失 | 约 0.063 dB,检查立即讲话时不会把开头当成底噪压掉 |
核心回归通过 1617 项 C 断言,以及音频指标与块边界检查。边界检查中,处理后块边界最大相邻样本差为 1455,边界前后 2 ms 区域最大为 1578,对应连续原始信号最大为 1617;用于回归检查额外爆音,不是统一语音质量标准。能量损失很小并不证明所有音色细节完全不变,仍需试听。上述结果来自 ARM 生产代码的离线仿真,不是实板录音效果、CPU 利用率或端到端 BLE 延迟实测。
现有 Vosk 中文模型对同一份测试语音的处理前/后、叠加测试噪声的处理前/后,均返回"请问一加一等于几"。记录见 `outdir/cpu_ans_tests/vosk_comparison.json`。这证明该样本能继续通过转写链路,不代表多说话人、方言或真实敲击场景的系统识别准确率。
另已通过 BLE 录音接入回归 118995 项断言,覆盖关闭原样通过、开启处理、样本长度、切换设置与下轮生效等路径;既有 STT 服务 273 项、屏幕界面 225 项、回答音频播放 352 项断言、PC 转写/对话 63 项和 BLE 工具 23 项单元测试,以及 FindMy 音频回归通过。软件构建和这些测试不替代烧录后的真实麦克风、桌面与整机负载验证;本轮没有把未进行的实板烧录或 CPU 测时写为已通过。
工程当前测试环境已准备 ARM GCC、Unicorn/pyelftools 和中文语音验证样本,可从 `` 根目录重跑:
```powershell
python -B .\application\bt_watch\tests\cpu_ans\run_core_tests.py
```
脚本复用 `outdir/ble_record_tests/deps` 下的仿真依赖,以及 `outdir/ble_dialog_validation/pc_round_trip/question/answer.wav` 中文语音样本。复制工程到另一台电脑时需要同时准备这些测试条件;它们不是设备运行 ANS 的依赖。
当前实现面向算法评估与录音降噪,不在没有硬件采集证据的情况下声称已实测消除桌面敲击,也不声称任意噪声环境都能改善识别率。
11. 原始资料
-
Speex 官方使用手册:预处理器、降噪开关与抑制量控制(https://speex.org/docs/manual/speex-manual/node7.html)。可以看到噪声抑制、AGC 和残余回声处理有独立控制项。
-
SpeexDSP 官方预处理器接口(https://github.com/xiph/speexdsp/blob/master/include/speex/speex_preprocess.h)。本轮没有引入 SpeexDSP 库,用于理解通用处理能力与接口划分。
-
RNNoise 官方说明(https://github.com/xiph/rnnoise)。该项目使用循环神经网络,官方示例为 48 kHz 单声道裸 PCM,训练说明包含键盘等前景瞬态噪声。
-
RNNoise 作者论文:DSP 与深度学习结合的实时语音增强(https://arxiv.org/abs/1709.08243)。适合后续研究模型方案。
-
Arm CMSIS-DSP 官方 FFT 实现与说明(https://github.com/ARM-software/CMSIS-DSP/blob/main/Source/TransformFunctions/arm_rfft_fast_f32.c)。说明 Cortex-M 上 FFT 的实现方式;本轮轻量 FFT 为本工程自有 C 实现,不依赖新增 CMSIS 库链接。
BLE 语音对话:录音、PC 问答、屏幕显示与开发板播报
更新日期:2026-10-19
工程目录:`/application/bt_watch`
1. 功能说明
开发板录下问题后,通过 BLE 把 16 kHz、16 bit、单声道 PCM 送到 Windows PC。PC 先用 Vosk 做中文语音识别,再交给 PC 本地语言模型回答,最后通过同一条 BLE 连接完成两件事:
-
把"我说的问题"和"助手的回答"回传到开发板屏幕;
-
把 PC 生成的中文回答音频传给开发板,由开发板扬声器播放。
开发板不运行 Vosk、Ollama 或 TTS 模型。问答模型和语音合成在 PC 上运行,日常问答不需要联网。首次准备便携式 Ollama、模型和 Vosk 模型需要联网。
屏幕上的"再次录音"按钮默认保留。PC 启动后自动完成第一轮;当前对话显示完成且播报结束后,点击按钮即可继续下一轮,不需要手动发送按钮命令。录音、识别、问答、语音合成、音频传输和播放期间按钮会自动禁用。
当前实现使用单轮固定时长录音,默认 8 秒。它适合验证语音问答链路,不是持续唤醒式对话。
新增可选 CPU 录音降噪:烧录 ANS 固件后,普通串口执行 `app ans on`,后续首次录音及点击"再次录音"均先降噪再上传;`app ans off` 恢复原始录音,`app ans status` 查看状态。默认关闭,从下一次录音生效。算法原理、资源评估、对比试听和固件位置见 ANS_RECORD_README.md(ANS_RECORD_README.md)。
2. 准备软件
烧录本次 v3 固件:
```text
application/bt_watch/outdir/ats3089p_dev_watch/_firmware/ats3089p_dev_watch_261019_ble_voice_dialog_v3.fw
```
同目录提供调试固件、SHA256 和清单。旧版 v2 转写固件支持"再次录音",但没有本次 BLE 回答语音接收接口,需要更新固件才能在开发板上播报回答。
在 PowerShell 中从工程根目录运行:
```powershell
cd D:\Master\6project\ATS3089C_to_ATS3089P\
python -m pip install wheel==0.45.1
python -m pip install -r .\application\bt_watch\tools\ble_stt\requirements.txt
```
如果 Vosk 中文模型还没有安装:
```powershell
python .\application\bt_watch\tools\ble_stt\ble_stt.py setup-model
```
模型默认查找:
-
`D:\models\vosk-model-small-cn-0.22`
-
`application\bt_watch\outdir\ble_stt_models\vosk-model-small-cn-0.22`
3. 准备本地问答模型
推荐使用工程提供的 Windows 便携式准备脚本:
```powershell
python .\application\bt_watch\tools\ble_stt\setup_dialog.py
```
脚本会下载并校验 Ollama 官方完整 Windows x64 便携运行时(约 1.4 GiB),然后在工程输出目录准备默认模型 `qwen2.5:1.5b`(约 1 GiB)。首次需要下载并解压,耗时取决于网络和磁盘;以后会自动复用已有运行时和模型。运行时压缩包保留,解压后也占用磁盘,请预留数 GiB 空间。
默认文件位置:
```text
application\bt_watch\outdir\ble_dialog_runtime\ollama\ollama.exe
application\bt_watch\outdir\ble_dialog_runtime\models
application\bt_watch\outdir\ble_dialog_runtime\ollama_server.log
```
也可以自己安装 Ollama,并在本机执行:
```powershell
ollama pull qwen2.5:1.5b
ollama serve
```
工具默认访问 `http://127.0.0.1:11434`。若已有其它模型:
```powershell
python .\application\bt_watch\tools\ble_stt\ble_stt.py record --dialog --ollama-model qwen2.5:3b --seconds 8
```
模型只根据本地上下文回答,不自动查询实时天气、新闻或互联网信息。小模型回答速度较快但准确性和知识范围有限,建议先用短问题验证。
4. Windows 中文语音
工具默认使用 Windows SAPI 中文语音。已验证的系统音色为:
```text
Microsoft Huihui Desktop
```
SAPI 输出固定为 16 kHz、16 bit、单声道 PCM WAV,开发板无需转换。查看本机中文音色:
```powershell
Add-Type -AssemblyName System.Speech
$speaker = New-Object System.Speech.Synthesis.SpeechSynthesizer
speaker.GetInstalledVoices() \| ForEach-Object { _.VoiceInfo.Name }
$speaker.Dispose()
```
指定音色:
```powershell
python .\application\bt_watch\tools\ble_stt\ble_stt.py record --dialog --tts sapi --tts-voice "Microsoft Huihui Desktop"
```
只显示文字、不播放回答:
```powershell
python .\application\bt_watch\tools\ble_stt\ble_stt.py record --dialog --tts none
```
如果没有中文 SAPI 音色,请在 Windows 设置中安装中文语音包,或使用 `--tts none` 验证问答和屏幕显示。
5. 启动 BLE 语音对话
先烧录支持 BLE 语音对话的固件,开发板广播名称保持为 `zkBLE`。关闭手机上占用该设备的 BLE App,然后运行:
```powershell
python .\application\bt_watch\tools\ble_stt\ble_stt.py record --dialog --seconds 8
```
程序流程:
-
启动或连接本机 Ollama,检查问答模型;
-
搜索并连接名称为 `zkBLE` 的开发板;
-
开发板进入录音状态,屏幕提示"录音中,请讲话";
-
PC 收齐 PCM 后检查长度和 CRC32,并用 Vosk 转写;
-
PC 先显示临时问题和"正在思考,请稍候...";
-
本地语言模型生成中文回答;
-
PC 用 SAPI 合成 `answer.wav` 和 `answer.pcm`;
-
PC 通过 BLE 分片发送回答 PCM,开发板播放;
-
PC 提交最终文本,开发板屏幕显示:
```text
我:
今天为什么下雨?
助手:
水汽遇冷凝结成水滴,形成降雨。
```
- 播报完成后,"再次录音"按钮重新可用。
如果只需运行一轮并退出:
```powershell
python .\application\bt_watch\tools\ble_stt\ble_stt.py record --dialog --seconds 8 --once
```
默认命令会保持 BLE 连接并等待屏幕再次录音。按 `Ctrl+C` 会停止等待、取消当前音频传输并释放连接。
使用演示回答检查 BLE 链路时,可不安装 Ollama:
```powershell
python .\application\bt_watch\tools\ble_stt\ble_stt.py record --dialog --answer-backend demo --seconds 8
```
`demo` 只验证识别、屏幕、TTS 和播放链路,不是自由问答模型。
6. 纯 PC 问答和 TTS 自检
不连接开发板,只检查本地模型和中文语音:
```powershell
python .\application\bt_watch\tools\ble_stt\ble_stt.py ask --text "为什么会下雨?"
```
指定输出目录:
```powershell
python .\application\bt_watch\tools\ble_stt\ble_stt.py ask --text "你好" --output-dir D:\logs\ble_dialog
```
纯 PC 测试会保存回答文本、问答 JSON,以及启用 TTS 时的 `answer.wav` 和 `answer.pcm`。
7. PC 保存文件
每轮保存在 `--output-dir` 下的时间戳目录中:
| 文件 | 内容 |
| --- | --- |
| `audio.pcm` | 开发板采集的原始 16 kHz/16 bit/单声道 PCM |
| `audio.wav` | 原始录音试听文件 |
| `transcript.txt` | Vosk 中文识别结果 |
| `asr_result.json` | 识别参数、耗时、CRC 和屏幕确认状态 |
| `answer.txt` | PC 最终回答 |
| `answer.pcm` | TTS 生成的回答 PCM |
| `answer.wav` | TTS 生成的回答 WAV |
| `dialog_result.json` | 问题、回答、模型、TTS、播放和错误信息 |
问答全文可能比开发板屏幕限制长。板端显示文本最多 1024 个 UTF-8 字节,PC 会保留完整回答文件,并在屏幕提交前按完整 UTF-8 字符截断,避免半个汉字。
8. 传输原理
```text
MIC
→ BLE PCM 录音通知 1002
→ PC Vosk 中文识别
→ Ollama / 自定义回答程序
→ Windows SAPI 16k/16bit/mono WAV
→ BLE 1003 音频写入
→ 开发板音频缓冲
→ DAC/扬声器
→ 最终问答文本写入 1003
→ 屏幕显示并开放下一次录音
```
回答音频使用专用 BLE 音频传输帧:
| 帧 | 作用 |
| --- | --- |
| `BEGIN 0x20` | `opcode:u8, version:u8, seq:u32, total:u32, crc32:u32`,建立一次 PCM 传输 |
| `DATA 0x21` | `opcode:u8, seq:u32, offset:u32, PCM片段`,按偏移传输 |
| `END 0x22` | `opcode:u8, seq:u32`,收齐后排队播放 |
| `CANCEL 0x23` | `opcode:u8, seq:u32`,取消当前传输 |
| `STATUS 0x24` | `opcode:u8, seq:u32`,查询状态和已收字节数 |
音频数据通过现有 BLE 数据特征通知返回传输进度。开发板按 CRC32 和偏移检查数据;数据不完整、超时或序号重复时不会播放。PC 合成回答最多约 60 秒;板端缓冲区为 480000 字节,单段支持约 15 秒单声道 16 kHz 音频,因此 PC 会把较长回答拆成多个段。通话、其它音频轨、录音或 DAC 被占用时,设备会报告忙,不会强行抢占当前业务。断开 BLE 时会停止本次回答播放并清理缓冲。
播放通知固定 20 字节:`magic2="DP", version:u8=1, state:u8, seq:u32, result:i32, received:u32, total:u32`。状态依次为 1 接收、2 待播放、3 播放中、4 完成、5 失败、6 取消。PC 可重复发送 STATUS 查询,避免单次通知丢失后误判。上传 15 秒没有进展时失败;终态在板端保留 60 秒,重复序号不能重复播放。
问答期间仍复用原文字状态:`state=1` 为录音;`state=2` 表示 PC 识别、模型回答、TTS 合成、音频传输或播放未完成;`state=3` 表示最终文字和本轮播放流程完成。这样旧版 PC 状态解析仍可工作,页面不会在回答尚未播完时开放下一次录音。
9. 代码位置
| 文件 | 作用 |
| --- | --- |
| `tools/ble_stt/ble_stt.py` | BLE 录音主流程、Vosk 识别、多轮按钮、问答调用、TTS、回答播放 |
| `tools/ble_stt/ble_dialog.py` | Ollama/自定义回答后端、中文回答格式、Windows SAPI 合成 |
| `tools/ble_stt/local_dialog_runtime.py` | 工程内便携 Ollama 的启动和回收 |
| `tools/ble_stt/setup_dialog.py` | 首次下载、校验和准备便携问答运行时及模型 |
| `src/main/ble_dialog_audio.c/.h` | 板端 BLE 回答 PCM 接收、CRC、超时、播放队列和取消清理 |
| `src/main/transcript_service.c/.h` | 文字快照、状态、页面显示确认和再次录音会话 |
| `src/launcher/transcript_view.c/.h` | 屏幕问答页面、问题/回答滚动显示、状态提示和再次录音按钮 |
| `src/main/ble_record_service.c` | 原录音 PCM 和文字特征入口 |
| `tests/ble_stt/` | PC、文字服务和 GUI 回归验证 |
重要修改位置使用 `//zk+`、`//zk+-` 中文注释标记。
10. 常见问题
-
**提示无法连接 zkBLE**:关闭手机 BLE App,确认板子在广播且没有被其它电脑连接;Windows 蓝牙适配器需要支持 BLE GATT。
-
**提示 Ollama 模型不存在**:运行 `setup_dialog.py`,或先执行 `ollama pull qwen2.5:1.5b`;模型名不一致时传 `--ollama-model`。
-
**屏幕只有问题没有回答**:回答模型、TTS 或 BLE 音频传输尚未完成时属于 state=2;检查 PC 终端和 `dialog_result.json`。
-
**回答能显示但没有声音**:先用 `ask` 检查 `answer.wav`,再用 `--tts none` 区分 TTS 问题;确认开发板没有通话、音乐、录音等占用音频资源。
-
**按钮一直灰色**:等待当前问答和播报结束;确认使用默认命令或未使用 `--once`;PC 退出后按钮会失去会话控制。
-
**回答太长或模型响应太慢**:换更小模型、缩短问题,或在 `--answer-timeout` 增大超时。屏幕和单段 BLE 播放有长度限制,PC 仍会保存完整回答。
-
**想接入其它 PC 模型**:使用 `--answer-backend command --answer-command '"python","my_answer.py"'`。程序把识别问题通过标准输入传入,回答从标准输出读取;自定义程序不得输出日志到标准输出。
11. 边界和安全
PC 问答默认只访问本机 `127.0.0.1` Ollama,不自动联网搜索。工具不会把回答当作开发板命令执行,自定义回答程序使用参数列表执行,不经过 shell。开发板只接受校验通过、属于当前 BLE 连接且序号正确的音频帧。
同一 PC 进程保留最近 4 轮问答作为上下文,重启程序后重新开始。默认提示词让模型使用约 100 字以内的中文短回答,以减少 BLE 传输等待。每段音频先传完再播放,长回复拆段时会有传输等待,当前不是边生成边播放的流式对话。合成文本最多 900 个 UTF-8 字节,完整原文仍保留在 `answer.txt`;音频最长 60 秒。
取消命令的 ATT 写成功只表示开发板已接收请求,PC 还会等待板端 DP 终态确认音轨已关闭,再恢复录音按钮。若 3 秒内无法确认清理完成,会结束当前 BLE 连接,让板端停止自有音轨;不会立即开启下一轮录音。
12. 本次验证结果
本机已准备好工程内 Ollama 便携运行时和 `qwen2.5:1.5b`,直接运行第 5 节命令即可复用。复制工程到其它 PC 且没有包含 `outdir/ble_dialog_runtime` 时,请先执行 `setup_dialog.py`。
已实际执行"中文语音 → Vosk 转写 → Qwen 模型回答 → 中文 TTS"流程:输入为 SAPI 生成的"请问一加一等于几?",Vosk 识别为"请问一加一等于几",模型回答"一加一等于二。",并成功生成回复 PCM/WAV。结果位于 `outdir/ble_dialog_validation/pc_round_trip/validation.json`。这是 PC 真实模型和语音链路验证,输入不是本次开发板实录。
软件回归:文字服务 ARM 273 项、界面 ARM 225 项、回复音频 ARM 352 项断言通过;PC 转写和对话 63 项测试通过,原 BLE 音频工具 23 项通过,原 Find My 抢占/清理回归通过。测试覆盖重复点击、两轮对话、校验错误、来电/录音冲突、断连、超时、取消后清轨确认和短语音首帧。
已编译并打包 v3 固件。静态用量:FLASH 2280048 字节、SRAM 875776 字节、PSRAM 33295712 字节、SHARE_RAM 20948 字节。新增 480000 字节回复缓存位于普通 PSRAM,内部 SRAM 和 DSP 共享区用量与 v2 相同。尚未烧录本次固件验证物理 BLE 播放和屏幕显示,仍需在开发板上按第 5 节操作确认。
原厂资料:Ollama Windows 部署(https://docs.ollama.com/windows)、Ollama 对话接口(https://docs.ollama.com/api/chat)、Qwen2.5 模型(https://ollama.com/library/qwen2.5:1.5b)。