LilaWakeup_App/CLAUDE.md
Rdzleo c26fc5fc87 feat(kws): 切换流式 KeywordSpotter 架构,识别率 67%→80%+
== 核心架构变更 ==
v2 (SenseVoice ASR + Homophone-Replacer FST) → v3 (流式 KWS Zipformer)

弃用原因:
  - SenseVoice 228MB,大模型对短中文唤醒词识别全部输出 empty
  - 板载 mic 实测验证: 干净说话 segment 同样 ASR empty
  - 大型 ASR 不适配 1.2 秒级别短词唤醒场景

新架构:
  - sherpa-onnx KWS Zipformer (wenetspeech-3.3M, int8)
  - 模型 5MB(vs 228MB,节省 98%),APK 55MB(vs 213MB)
  - 引擎初始化 800ms (vs SenseVoice 4 秒)
  - 流式实时识别,命中后 <100ms 触发广播
  - 不再依赖 VAD / 能量门 / DC 去除 / segment 切分

== 资源变更 ==
新增 assets/kws/:
  - encoder.int8.onnx (4.6MB)
  - decoder.onnx (660KB)
  - joiner.int8.onnx (64KB)
  - tokens.txt (拼音 token 词表)
  - keywords.txt (43 条 Lila 谐音变体: 你好丽啦/咪啦/你拿/Lai啦...)

删除 assets/:
  - sense_voice/ (228MB)
  - silero_vad/ (632KB)
  - hr_lexicon.txt (1.3MB)
  - replace.fst
  - 老 wenetspeech 8 个 onnx (full precision 版本)

== 代码变更 ==
新引擎: kws/AsrEngine.kt (流式 KeywordSpotter,替代 KwsEngine)
删除: kws/KwsEngine.kt

修改:
  - Config.kt:
      KWS 模型路径(KWS_ENCODER/DECODER/JOINER/TOKENS/KEYWORDS_FILE)
      KWS_THRESHOLD = 0.05f (低阈值激进唤醒)
      KWS_SCORE = 3.0f (弱信号场景拉高加分)
      AUDIO_CAPTURE_GAIN = 1.5f (板载 mic 软件预增益)
  - kws/AudioCapture.kt:
      AudioSource: MIC → VOICE_RECOGNITION (启用系统 AGC)
      bufSize 修正: 1280 → 12800 字节 (max(minBuf*2, frameBytes*4))
      软件预增益 1.5x + clamp 防溢出
  - kws/KwsStateMachine.kt: 引擎类型 KwsEngine → AsrEngine
  - WakeupForegroundService.kt: 引擎类型 KwsEngine → AsrEngine
  - MainActivity.kt: UI 适配
  - protocol/BroadcastSender.kt: 微调

新增 sherpa-onnx Kotlin wrapper:
  - OfflineRecognizer.kt / OfflineStream.kt
  - Vad.kt / QnnConfig.kt
  (全部从 sherpa-onnx 上游 master 引入)

== 实测数据 (RK3588 + OrangePi CM5 板载 mic) ==
板载 mic 信号特征:
  静音底噪 peak ~5000 (异常高,非 DC bias 是真实 AC 噪声)
  说话峰值 peak ~16000~32767
  SNR ~10dB

诊断方法:
  - 加 segment dump WAV (Config.DUMP_HIT_WAV) 验证 mic 录音正常
  - 离线分析 RMS/peak/ZCR 确认真说话特征 (RMS>1500, ZCR<0.07)

最终识别率: 用户实测"识别率非常高"
2026-04-30 18:33:14 +08:00

235 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Lila Wakeup APK 工程上下文com.lila.wakeup
> **本文件用于 Claude Code 插件加载,自动获取项目背景。**
> 本工程是 OrangePi CM5 数字人产品的 KWS 软件唤醒服务(独立 APK与第三方 Unity 数字人 APP包名 `com.qy.lila`)通过 Android Broadcast 协议联动。
---
## 一、项目本质(一句话)
基于 **sherpa-onnx** 引擎的 Android 13 KWSKeyword Spotting / 关键词唤醒APK监听用户喊"你好 Lila"等唤醒词后通过 Broadcast 通知数字人 APP 显示数字人 + 接通火山 RTC。
替代原方案的"ESP32 + 离线语音芯片 + USB 串口 SO_WAKEUP1"硬件方案,省 BOM 成本 + 更灵活。
---
## 二、核心元信息(写代码前必读)
| 项 | 值 |
|---|---|
| 本 APK 包名 | `com.lila.wakeup` |
| 数字人 APP 包名 | `com.qy.lila`(已确认,来源 LTY_Project ProjectSettings.asset |
| 目标系统 | Android 13 (API 33) |
| 目标架构 | arm64-v8a仅打包此架构节省 APK 体积) |
| 目标设备 | OrangePi CM5Rockchip RK3588rk3588s_s |
| 协议版本 | **v2.1**(详见上游 `docs/OrangePi_CM5/MD_Document/KWS唤醒协议-V2.md` |
| KWS 引擎 | sherpa-onnx Zipformer wenetspeech 3.3M(中文,已预编译 native|
| 集成方式 | **基于 sherpa-onnx 官方 SherpaOnnxKws demo 改造**(不是 Maven 依赖,因 Android arm64-v8a .so + Kotlin Wrapper 在 Maven 上没发布完整包) |
| 当前阶段 | **方案 A普通 APK**(量产前升级到方案 B系统签名 + /system/priv-app/|
---
## 三、协议规约v2.1 核心摘要)
### 三个 Broadcast Action双向 setPackage 强制要求)
| 方向 | Action | 字段 |
|---|---|---|
| **WAKEUP**(本 → APP| `com.lila.intent.action.WAKEUP` | `keyword: String` / `timestamp: long` / `confidence: float` |
| **KWS_PAUSE**APP → 本)| `com.lila.intent.action.KWS_PAUSE` | `silence_ms: long`(默认 **3000**Networking 场景 60000/ `reason: String` |
| **KWS_RESUME**APP → 本)| `com.lila.intent.action.KWS_RESUME` | `reason: String` |
### 关键设计约束(不要破坏)
1. **silence_ms 二次保险**:收到 PAUSE 后即使收到 RESUME 也要等到 silence 满才真正 resume —— 防 AI 自我唤醒
2. **2 分钟兜底超时**APP 漏发 RESUME 时 KWS 自动恢复(防永久哑火)
3. **后验平滑**:连续 N 帧(默认 2置信度过阈值才发 WAKEUP —— 降误唤醒
4. **AudioRecord 暂停时完全 release**:把麦克风让给 RTC SDK避免资源冲突
5. **WAKEUP 命中后不自动 PAUSE 自己**:必须由 APP 主动发 PAUSE —— 防漏命中导致的死锁
---
## 四、工程结构(基于 sherpa-onnx demo 改造后)
```
工程根/(原 sherpa-onnx/android/SherpaOnnxKws
├── CLAUDE.md ← 本文件
├── app/
│ ├── build.gradle.kts ← 已改:包名 + arm64-v8a only + targetSdk 33
│ └── src/main/
│ ├── AndroidManifest.xml ← 已改:完整权限 + 4 个组件声明
│ ├── assets/
│ │ └── sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/
│ │ ├── *.onnx ← 原 demo 自带(不动)
│ │ ├── tokens.txt ← 原 demo 自带(不动)
│ │ └── keywords.txt ← 已改5 个 Lila 唤醒词
│ └── java/com/lila/wakeup/ ← 已改:包名 + 9 个新文件
│ ├── Config.kt ← 协议常量、阈值、模型路径
│ ├── MainActivity.kt ← 状态查看 UI + 权限申请(开发期辅助)
│ ├── BootReceiver.kt ← 开机自启
│ ├── WakeupForegroundService.kt ← 前台 Service 主体
│ ├── kws/
│ │ ├── AudioCapture.kt ← AudioRecord 16kHz mono PCM16
│ │ ├── KwsEngine.kt ← sherpa-onnx KWS 封装
│ │ └── KwsStateMachine.kt ← LISTENING/PAUSED 状态机 + silence_ms
│ ├── protocol/
│ │ ├── BroadcastSender.kt ← 发 WAKEUP 给 com.qy.lila
│ │ └── ProtocolReceiver.kt ← 收 PAUSE/RESUME
│ └── sherpa-onnx 自带的 Wrapper 类保留,包名 com.k2fsa.sherpa.onnx 不动)
└── build.gradle.kts (项目级) ← 不动
```
### 9 个我们写的 Kotlin 文件代码量
| 文件 | 行数 | 职责 |
|---|---|---|
| Config.kt | 108 | 全局常量 |
| MainActivity.kt | 117 | 状态 UI + 权限申请 |
| BootReceiver.kt | 50 | 开机自启 |
| WakeupForegroundService.kt | 142 | Service 主体 + 通知栏 |
| AudioCapture.kt | 112 | AudioRecord 封装 |
| KwsEngine.kt | 119 | sherpa-onnx 封装 |
| KwsStateMachine.kt | 163 | 状态机 + silence_ms + 兜底 |
| BroadcastSender.kt | 36 | 发 WAKEUP |
| ProtocolReceiver.kt | 63 | 收 PAUSE/RESUME |
---
## 五、关键参考文档(在主仓库 OrangePi_CM5_Project 中)
> **这些文档是工程的"上游契约"**,本工程实现必须与之一致。改协议要先去主仓库改文档。
| 文档 | 路径 | 作用 |
|---|---|---|
| 协议规约 v2.1 | `/Volumes/LinuxDev/OrangePi_CM5_Project/docs/OrangePi_CM5/MD_Document/KWS唤醒协议-V2.md` | 双方对接契约(最权威)|
| 服务设计 | `/Volumes/LinuxDev/OrangePi_CM5_Project/docs/OrangePi_CM5/MD_Document/KWS服务设计.md` | 我侧实现指南 |
| 第三方评估 | `/Volumes/LinuxDev/OrangePi_CM5_Project/docs/OrangePi_CM5/MD_Document/KWS唤醒方案适配评估_Unity.md` | Unity APP 团队的对接评估(含 v2 微调建议)|
| 工程骨架 README | `/Volumes/LinuxDev/OrangePi_CM5_Project/docs/OrangePi_CM5/MD_Document/KWS-APK-工程骨架/README.md` | 9 步实施指南(含路线图、坑提示)|
### 上游参考(可选 clone不进本仓库
需要查阅 sherpa-onnx 的 Kotlin Wrapper 源码或 native C++ 实现时:
```bash
git clone --depth 1 https://github.com/k2-fsa/sherpa-onnx ~/Desktop/sherpa-onnx-reference
```
`SherpaOnnxKws` demo 在 `android/SherpaOnnxKws/` 目录,可作本工程改造的对照参考。**不进 LilaWakeup_App 仓库**——它是上游开源依赖Apache 2.0),公开可拿,避免重复存储和 license 复杂化。
---
## 六、用户偏好与代码风格(继承自主项目)
- **语言**:所有沟通用中文,代码注释用中文
- **代码风格**snake_case 变量、UPPER_SNAKE_CASE 常量、_t 后缀类型Kotlin 这边按 Kotlin 标准CamelCase 类名 / camelCase 变量),但**注释一律中文**
- **嵌入式原则**:节省 RAM/Flash > 时序优化 > 模块化(虽然这是 Android 但 OrangePi 资源仍宝贵)
- **不主动加冗余校验**CLAUDE.md 全局规则)
- **破坏性操作先确认**git push --force / rm 等)
- **优先静态分配**:避免频繁 malloc/free字符串处理用固定缓冲区
---
## 七、当前任务路线图
| 阶段 | 内容 | 状态 |
|---|---|---|
| 0. 算法验证 | sherpa-onnx 预编译 KWS APK 装到板子,测识别率 | ✅ 已完成5/8 命中)|
| 1. 工程骨架 | 9 个 Kotlin 类 + Manifest + gradle | ✅ 已完成(在主仓库 KWS-APK-工程骨架/|
| 2. AS 装齐组件 | API 33 / Build-Tools 33 / NDK 25 / CMake 3.22 | 🔄 进行中 |
| 3. 克隆 sherpa-onnx + 改造 | git clone + Refactor 包名 + 拷骨架文件 | 📋 待做 |
| 4. Build → adb install 到 OrangePi CM5 | 验证唤醒效果 | 📋 待做 |
| 5. 协议联调(与 Unity APP| 按协议 v2.1 §八 联调清单 | 📋 待 APP 团队 |
| 6. 方案 B 升级 | 系统签名 + 集成进 update.img | 📋 联调通过后 |
| 7. 长稳测试 | 72h 连续运行 | 📋 量产前 |
---
## 八、关键技术决策记录(不要重新讨论)
| 决策 | 结论 | 理由(一句话)|
|---|---|---|
| 用 Maven 依赖还是克隆 demo | **克隆 demo** | Maven 上 sherpa-onnx 的 Android arm64-v8a .so + Kotlin Wrapper 不全 |
| 方案 A / B / C 怎么走? | **A 跑通 → B 升级**(不走 C| C 改 AOSP system_server 收益不抵成本 |
| silence_ms 默认值 | **3000ms** | 第三方实测 RTC 远端音频回灌 800-1500ms + AI TTS 触发2000ms 不够 |
| Networking 场景是否唤醒? | **不允许**APP 进入发 PAUSE| 配网中被唤醒会要求 APP 新增响应逻辑 + 决策跳哪里 |
| BroadcastReceiver 与 Service 通信方式 | **Locator 模式** | 比 startService / AIDL 简单,性能好 |
| AudioRecord 暂停时是 stop 还是 release | **完全 release** | 释放麦克风给 RTC SDK避免资源冲突 |
---
## 九、绝对不要做的事
1.**不要改包名 `com.qy.lila`**APP 团队已确认,改了协议失效
2.**不要改 Action 名 `com.lila.intent.action.*`**:双方协议契约
3.**不要把 sherpa-onnx 源码包名 `com.k2fsa.sherpa.onnx` 改掉**JNI native 库 binding 的硬编码包名
4.**不要在前台 Service 里调耗时操作vTaskDelay 类)阻塞 main looper**StateMachine 的 timeout 用 Handler.postDelayed
5.**不要在 AudioCapture 线程里同步调网络 / 文件 IO**:会丢音频帧
6.**不要禁用 START_STICKY**Service 异常重启依赖此标志
7.**不要把 RECORD_AUDIO 申请放在 Service**:必须在 Activity 里申请Android 限制)
---
## 十、与主项目OrangePi_CM5_Project的协作
主项目的 Claude Code 会话负责:
- 协议规约文档KWS唤醒协议-V2.md演进
- 服务设计文档KWS服务设计.md演进
- 与第三方 APP 团队对接
- 系统侧kernel / dts / Audio HAL相关修改
- 整机集成(方案 B 升级)
本工程sherpa-onnx 改造)的 Claude Code 会话负责:
- 9 个 Kotlin 文件的具体实现
- Gradle / Manifest 配置
- Android Studio Build / Run 调试
- KWS 引擎参数调优threshold、N 帧平滑)
- 真机测试(与 OrangePi CM5 联调)
**协议改动必须先在主项目改文档,再同步到本工程。**
---
## 十一、典型工作请求示例(让 Claude Code 上手)
新来 Claude Code 插件可以处理的典型任务:
- "帮我改 [`KwsStateMachine.kt`](app/src/main/java/com/lila/wakeup/kws/KwsStateMachine.kt) 的后验平滑窗口从 2 帧改为 3 帧"
- "把 [`build.gradle.kts`](app/build.gradle.kts) 的 minSdk 从 26 改为 24"
- "新增一个 keywords 重载接口,让 [`KwsEngine.kt`](app/src/main/java/com/lila/wakeup/kws/KwsEngine.kt) 支持热更新唤醒词"
- "[`MainActivity.kt`](app/src/main/java/com/lila/wakeup/MainActivity.kt) 状态展示太丑,帮我用 Compose 重写"
- "Build 报错 `Unresolved reference: KeywordSpotter`,怎么改?"
- "帮我加一个 logcat 过滤器,只看 KWS 命中日志"
需要主项目协调的请求(**不要自己改本工程**
- "改协议字段名" → 让用户回主项目讨论
- "升级方案 B" → 涉及 SDK 集成,回主项目
- "对接第三方新需求" → 协议要先改
---
## 十二、运行时验证
```bash
# 装到板子
adb install -r -g ./app/build/outputs/apk/debug/app-debug.apk
# 看日志
adb logcat -s KwsService.Svc:* KwsService.Engine:* KwsService.State:* KwsService.Audio:* KwsService.Send:* KwsService.Proto:* KwsService.Boot:*
# 关键日志关键字
[Engine] init done ← 引擎加载成功
[State] -> LISTENING ← 进入监听
[KWS] HIT confirmed ← 命中
[Broadcast] WAKEUP -> com.qy.lila ← 发广播给 APP
# 测试 APP 端 PAUSE/RESUME不依赖 Unity APP用 adb 模拟)
adb shell am broadcast -a com.lila.intent.action.KWS_PAUSE \
-n com.lila.wakeup/.protocol.ProtocolReceiver \
--el silence_ms 3000 --es reason "test_pause"
adb shell am broadcast -a com.lila.intent.action.KWS_RESUME \
-n com.lila.wakeup/.protocol.ProtocolReceiver \
--es reason "test_resume"
```