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

12 KiB
Raw Permalink Blame History

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_PAUSEAPP → 本) com.lila.intent.action.KWS_PAUSE silence_ms: long(默认 3000Networking 场景 60000/ reason: String
KWS_RESUMEAPP → 本) 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++ 实现时:

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.lilaAPP 团队已确认,改了协议失效
  2. 不要改 Action 名 com.lila.intent.action.*:双方协议契约
  3. 不要把 sherpa-onnx 源码包名 com.k2fsa.sherpa.onnx 改掉JNI native 库 binding 的硬编码包名
  4. 不要在前台 Service 里调耗时操作vTaskDelay 类)阻塞 main looperStateMachine 的 timeout 用 Handler.postDelayed
  5. 不要在 AudioCapture 线程里同步调网络 / 文件 IO:会丢音频帧
  6. 不要禁用 START_STICKYService 异常重启依赖此标志
  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 的后验平滑窗口从 2 帧改为 3 帧"
  • "把 build.gradle.kts 的 minSdk 从 26 改为 24"
  • "新增一个 keywords 重载接口,让 KwsEngine.kt 支持热更新唤醒词"
  • "MainActivity.kt 状态展示太丑,帮我用 Compose 重写"
  • "Build 报错 Unresolved reference: KeywordSpotter,怎么改?"
  • "帮我加一个 logcat 过滤器,只看 KWS 命中日志"

需要主项目协调的请求(不要自己改本工程

  • "改协议字段名" → 让用户回主项目讨论
  • "升级方案 B" → 涉及 SDK 集成,回主项目
  • "对接第三方新需求" → 协议要先改

十二、运行时验证

# 装到板子
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"