EarLLM One — 构建与维护
概述
EarLLM One 是一个多模块 Android 应用(Kotlin + Jetpack Compose),从蓝牙耳机采集语音,将其转写,发送给大语言模型(LLM),并把回复以语音方式播报回来。本技能用于构建、维护和扩展该项目。
何时使用
当用户提到 "earllm"、"earbudllm"、"earbud app"、"voice pipeline kotlin"、"bluetooth audio android"、"sco microphone" 或相关主题时使用。
不适用场景
任务与 earllm 构建无关时;有更简单、更具体的工具可处理该请求时;用户需要的是无需领域专业知识的通用协助时。
工作原理
项目位置(资料提供路径):C:\Users\renat\earbudllm
模块依赖关系
app ──→ voice ──→ audio ──→ core-logging │ │ ├──→ bluetooth ──→ core-logging └──→ llm ──→ core-logging
模块与关键文件
core-logging:结构化日志与性能跟踪;关键文件 EarLogger.kt、PerformanceTracker.kt。
bluetooth:蓝牙发现、配对、A2DP/HFP 配置文件;关键文件 BluetoothController.kt、BluetoothState.kt、BluetoothPermissions.kt。
audio:音频路由(SCO/BLE)、采集、耳机按键;关键文件 AudioRouteController.kt、VoiceCaptureController.kt、HeadsetButtonController.kt。
voice:STT(SpeechRecognizer + Vosk 桩)、TTS、管线;关键文件 SpeechToTextController.kt、TextToSpeechController.kt、VoicePipeline.kt。
llm:LLM 接口、桩、OpenAI 兼容客户端;关键文件 LlmClient.kt、StubLlmClient.kt、RealLlmClient.kt、SecureTokenStore.kt。
app:UI、ViewModel、Service、设置与全部界面;关键文件 MainViewModel.kt、EarLlmForegroundService.kt,以及 6 个 Compose 界面。
构建配置
SDK:minSdk 26、targetSdk 34、compileSdk 34;构建工具:AGP 8.2.2、Kotlin 1.9.22、Gradle 8.5;Compose BOM:2024.02.00;关键依赖:OkHttp、AndroidX Security(EncryptedSharedPreferences)、DataStore、Media。
目标硬件
手机:Samsung Galaxy S24 Ultra,Android 14、One UI 6.1、Snapdragon 8 Gen 3。
耳机:Xiaomi Redmi Buds 6 Pro,BT 5.3,支持 A2DP/HFP/AVRCP、ANC、LDAC。
关键技术事实(资料称来自官方文档与设备测试)
多数设备的蓝牙 SCO 输入上限为 8kHz 单声道,部分支持 16kHz mSBC;BLE Audio(Android 12+,TYPE_BLE_HEADSET = 26)最高支持 32kHz 立体声,应优先使用 BLE Audio。
startBluetoothSco() 自 Android 12(API 31)起弃用,改用 AudioManager.setCommunicationDevice(AudioDeviceInfo) 与 clearCommunicationDevice();项目已在 AudioRouteController.kt 中实现两条路径。
Samsung One UI 7/8 存在已知 HFP 损坏缺陷:A2DP 播放会破坏 SCO 链路。应用通过静音检测与自动回退到手机内置麦克风来处理。
Redmi Buds 6 Pro 的轻触控制必须在小米耳机配套应用中设为 "Default"(播放/暂停)。若设为 ANC 或自定义功能,事件由耳机内部处理,不会传到 Android。
Android 14+ 需要 FOREGROUND_SERVICE_MICROPHONE 权限,并在服务声明中设置 foregroundServiceType="microphone";RECORD_AUDIO 必须在 startForeground() 之前授予。
VOICE_COMMUNICATION 音源启用 AEC(回声消除),这对防止 TTS 音频输出回灌到 STT 麦克风输入至关重要,未理解回声影响前不要更改该音源。
不要在 SCO 录音的同时播放 TTS(A2DP)。正确顺序是:停止播放 → 切换到 HFP → 录音 → 切换到 A2DP → 播放回复。
数据流
耳机按键轻触 → MediaSession(HeadsetButtonController)→ TapAction.RECORD_TOGGLE → VoicePipeline.toggleRecording() → VoiceCaptureController 采集 PCM(16kHz 单声道)→ stopRecording() 返回 ByteArray → SpeechToTextController.transcribe(pcmData) → LlmClient.chat(messages) → TextToSpeechController.speak(response) → 通过 A2DP 向耳机输出音频。
新增功能
确定受影响模块;先阅读这些模块的现有代码;遵循 StateFlow 模式,通过 MutableStateFlow / StateFlow 暴露状态;若功能需要 UI 集成则更新 MainViewModel.kt;在模块的 src/test/ 目录添加单元测试;若功能改变行为则更新文档。
修改音频采集
VoiceCaptureController.kt 负责 16kHz 单声道 PCM 录音;WAV 头使用十六进制字节值(而非字符字面量)以避免 shell 引号问题;VU 表:RMS 计算 → dB 转换 → 归一化到 0-1 范围;缓冲区大小:getMinBufferSize().coerceAtLeast(4096)。
修改蓝牙行为
BluetoothController.kt 管理发现、配对与配置文件代理;耳机检测使用名称启发式:"buds"、"earbuds"、"tws"、"pods"、"ear";必须同时处理 Bluetooth Classic 与 BLE Audio 两条路径。
修改 LLM 集成
LlmClient.kt 定义接口,保持通用;StubLlmClient.kt 用于离线测试(模拟 500ms 延迟);RealLlmClient.kt 使用 OkHttp 调用 OpenAI 兼容 API;API 密钥存放于 SecureTokenStore.kt(EncryptedSharedPreferences)。
生成构建产物
代码变更后重新生成 ZIP(自项目根目录执行):
powershell -Command "Remove-Item 'EarLLM_One_v1.0.zip' -Force -ErrorAction SilentlyContinue; Compress-Archive -Path (Get-ChildItem -Exclude '*.zip','_zip_verify','.git') -DestinationPath 'EarLLM_One_v1.0.zip' -Force"
运行测试
./gradlew test --stacktrace # 单元测试 ./gradlew connectedAndroidTest # 仪器化测试(需要设备)
Phase 2 路线图
通过耳机与 LLM 进行实时流式语音对话;智能助理:将语音分类为会议、购物清单、备忘录、邮件;集成 Vosk 离线 STT(目前为桩);唤醒词检测,以避免持续占用 SCO;流式 TTS(Android 内置 TTS 不支持流式)。
STT 引擎参考
Vosk small-en:40 MB,WER 约 10%,支持流式,适合实时移动端。
Vosk lgraph:128 MB,WER 约 8%,支持流式,准确率更好。
Whisper tiny:40 MB,WER 约 10-12%,不支持流式(批处理),适合话语后润色。
Android SpeechRecognizer:0 MB,WER 视情况而定,支持流式,适合在线、无额外依赖的场景。
最佳实践
提供关于项目与需求的清晰、具体上下文;应用所有建议到生产代码前先审阅;结合其他互补技能进行全面分析。
常见陷阱
将该技能用于领域之外的任务;未理解自身具体上下文就套用建议;未提供足够项目上下文导致分析不准确。
限制
仅在任务明确匹配上述范围时使用本技能;不要将输出视为环境特定验证、测试或专家评审的替代品;若缺少必需输入、权限、安全边界或成功标准,应停止并请求澄清。