基于ATS308X的无线语音交互对话功能实现

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 频谱降噪

  1. 每块采用 256 点 FFT,内部子窗移动 128 点;块边缘使用反射补边。

  2. 用 Hann 分析窗得到频谱,计算各频带功率。

  3. 平滑功率谱并跟踪局部最小值,估计持续噪声;瞬态期间冻结相关噪声更新,讲话时慢更新。

  4. 计算 Wiener 类型振幅增益,对噪声占比高的频带降低增益。

  5. 对增益做时间、频率平滑,并保留非零底限,降低断续和"音乐噪声"。一般谱增益底限为 0.15;语音保护期间全频最低保留 0.85,避免为降低背景声而明显削弱讲话。

  6. 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. 建议验证步骤

  1. **原样通过**:关闭 ANS,确认既有录音/Opus/转写/对话仍能完成。软件测试检查同一份输入在关闭状态下逐样本一致。

  2. **持续底噪**:固定麦克风和噪声来源,分别录制关闭/开启版本,比较安静段底噪与讲话清晰度。

  3. **无人讲话时敲击**:轻敲、重敲、不同位置各测试几次,查看瞬态计数与试听效果。

  4. **讲话时敲击**:保持正常说话,检查敲击是否减弱,以及人声辅音、爆破音是否被误伤。此场景允许残留敲击,不能只追求整体音量变小。

  5. **干净人声**:不敲击、不播放额外噪声,检查音色、清晰度与转写结果;单纯把所有声音压低不代表降噪成功。

  6. **边界条件**: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. 原始资料

BLE 语音对话:录音、PC 问答、屏幕显示与开发板播报

更新日期:2026-10-19

工程目录:`/application/bt_watch`

1. 功能说明

开发板录下问题后,通过 BLE 把 16 kHz、16 bit、单声道 PCM 送到 Windows PC。PC 先用 Vosk 做中文语音识别,再交给 PC 本地语言模型回答,最后通过同一条 BLE 连接完成两件事:

  1. 把"我说的问题"和"助手的回答"回传到开发板屏幕;

  2. 把 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

```

模型默认查找:

  1. `D:\models\vosk-model-small-cn-0.22`

  2. `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

```

程序流程:

  1. 启动或连接本机 Ollama,检查问答模型;

  2. 搜索并连接名称为 `zkBLE` 的开发板;

  3. 开发板进入录音状态,屏幕提示"录音中,请讲话";

  4. PC 收齐 PCM 后检查长度和 CRC32,并用 Vosk 转写;

  5. PC 先显示临时问题和"正在思考,请稍候...";

  6. 本地语言模型生成中文回答;

  7. PC 用 SAPI 合成 `answer.wav` 和 `answer.pcm`;

  8. PC 通过 BLE 分片发送回答 PCM,开发板播放;

  9. PC 提交最终文本,开发板屏幕显示:

```text

我:

今天为什么下雨?

助手:

水汽遇冷凝结成水滴,形成降雨。

```

  1. 播报完成后,"再次录音"按钮重新可用。

如果只需运行一轮并退出:

```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)。

相关推荐
博.闻广见1 小时前
第10篇:Agent系统的性能优化 —— 缓存、压缩与动态模型路由
java·人工智能·后端·spring·agent
海雅达手持终端PDA1 小时前
配电台区杆塔巡检老卡死?海雅达Model 4/HDT500实测
人工智能·物联网·集成测试·能源·政务
叶子丶苏1 小时前
提示词工程格式四段式与少样本-VibeCoding入门笔记06
人工智能·笔记
染指11101 小时前
151.Agent-LangGraph-Send发送(Map-Reduce模式)
人工智能·python·langchain·状态模式·agents
solidwork_s1 小时前
SOLIDWORKS 2027 Electrical电气新功能汇总
运维·服务器·人工智能·solidworks
语音之家1 小时前
NeurIPS 2026|RAIL:从人类认知能力视角重新审视大型音频语言模型的听觉智能
人工智能·机器人·语⾳agent·音频语言
橙光成长1 小时前
Context Mode 深度解析:用沙盒、SQLite 与钩子路由,把 AI 编码 Agent 的上下文消耗削减 98%
数据库·人工智能·sqlite
打工仔折腾 AI1 小时前
极空间部署Portainer实战:绕过Socket权限、解决9000端口冲突与固定内网访问
人工智能·后端·python·性能优化
遇码1 小时前
Rust 桌面应用自托管自动更新:latest.json 清单 + minisign 签名校验的完整做法
人工智能·ai·rust·大模型·agent