== 核心架构变更 ==
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)
最终识别率: 用户实测"识别率非常高"
235 lines
12 KiB
Markdown
235 lines
12 KiB
Markdown
# Lila Wakeup APK 工程上下文(com.lila.wakeup)
|
||
|
||
> **本文件用于 Claude Code 插件加载,自动获取项目背景。**
|
||
> 本工程是 OrangePi CM5 数字人产品的 KWS 软件唤醒服务(独立 APK),与第三方 Unity 数字人 APP(包名 `com.qy.lila`)通过 Android Broadcast 协议联动。
|
||
|
||
---
|
||
|
||
## 一、项目本质(一句话)
|
||
|
||
基于 **sherpa-onnx** 引擎的 Android 13 KWS(Keyword 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 CM5(Rockchip RK3588,rk3588s_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"
|
||
```
|