AIDL 通信(小企鹅联动)
说点啥(asr-keyboard)提供标准 AIDL 服务,允许其他应用(如修改版小企鹅输入法)调用说点啥的语音识别能力。
服务端为手写 Binder 协议,但与 AIDL 生成的代理完全兼容;客户端可直接使用 .aidl 生成的 Stub/Proxy,也可以像小企鹅一样用 transact 纯 Binder 调用。
用户使用指南
目前仅有说点啥魔改的小企鹅输入法支持该功能,使用步骤如下:
- 下载并安装说点啥最新版(开源版或 Pro 版均可,优先调用 Pro 版)
- 在说点啥中启用外部联动功能:
设置 → 输入设置 → 允许外部输入法联动,打开开关 - 下载并安装修改版小企鹅输入法
- 在小企鹅输入法中启用说点啥联动:
设置 → 虚拟键盘 → 空格键长按行为 → 语音输入(AIDL) - 使用小企鹅输入法时,长按空格键开始语音输入,松手结束
包名优先级(与小企鹅实现一致):
com.brycewg.asrkb.procom.brycewg.asrkb
客户端应按顺序尝试绑定,优先使用已安装的 Pro 包(接口与行为一致)。
开发者指南
服务接口(IExternalSpeechService)
接口描述符:com.brycewg.asrkb.aidl.IExternalSpeechService
事务码(与 AIDL Stub 保持一致):
| 方法 | 事务码 | 说明 |
|---|---|---|
startSession | FIRST_CALL_TRANSACTION + 0 | 服务端录音模式会话 |
stopSession | FIRST_CALL_TRANSACTION + 1 | 停止当前会话 |
cancelSession | FIRST_CALL_TRANSACTION + 2 | 取消当前会话 |
isRecording | FIRST_CALL_TRANSACTION + 3 | 指定会话是否正在录音 |
isAnyRecording | FIRST_CALL_TRANSACTION + 4 | 是否存在任意录音会话 |
getVersion | FIRST_CALL_TRANSACTION + 5 | 获取应用版本名 |
startPcmSession | FIRST_CALL_TRANSACTION + 6 | 推送 PCM 模式会话 |
writePcm | FIRST_CALL_TRANSACTION + 7 | 推送一帧 PCM 音频数据 |
finishPcm | FIRST_CALL_TRANSACTION + 8 | 结束 PCM 推送并进入处理 |
对应 AIDL 方法签名:
fun startSession(config: SpeechConfig?, callback: ISpeechCallback): Int
fun stopSession(sessionId: Int)
fun cancelSession(sessionId: Int)
fun isRecording(sessionId: Int): Boolean
fun isAnyRecording(): Boolean
fun getVersion(): String
fun startPcmSession(config: SpeechConfig?, callback: ISpeechCallback): Int
fun writePcm(sessionId: Int, pcm: ByteArray, sampleRate: Int, channels: Int)
fun finishPcm(sessionId: Int)配置对象(SpeechConfig)
SpeechConfig 为可空 Parcelable,字段如下:
vendorId: String?streamingPreferred: BooleanpunctuationEnabled: Boolean?autoStopOnSilence: Boolean?sessionTag: String?
当前实现说明:
startSession/startPcmSession均忽略除vendorId=="mock"外的全部配置,完全跟随说点啥应用内当前设置。- 只有
startSession支持vendorId=="mock"的联通测试模式(见下文)。
回调接口(ISpeechCallback)
接口描述符:com.brycewg.asrkb.aidl.ISpeechCallback
fun onState(sessionId: Int, state: Int, message: String)
fun onPartial(sessionId: Int, text: String)
fun onFinal(sessionId: Int, text: String)
fun onError(sessionId: Int, code: Int, message: String)
fun onAmplitude(sessionId: Int, amplitude: Float)主要方法
startSession(服务端录音模式)
由说点啥负责录音与上行音频。
返回值:
>0:成功启动,返回服务端生成的sessionId-2:系统忙碌(已有会话正在录音)-3:功能未启用或引擎未就绪-4:说点啥缺少RECORD_AUDIO权限
回调:
-3(功能未启用)会先回调onError(-1, 403, "feature disabled")-4(权限)会先回调onError(-1, 401, "record permission denied")-2/-3(引擎未就绪)仅通过返回值提示,不额外回调
联通测试(mock)
当 SpeechConfig.vendorId == "mock" 时会跳过实际录音: 服务端直接回调 onPartial("【联通测试中】……") 与 onFinal("说点啥外部AIDL联通成功(mock)"),无需录音权限。
startPcmSession / writePcm / finishPcm(推送 PCM 模式)
由客户端自行录音,并持续向服务端推送 PCM 数据(小企鹅 bibi 使用该模式)。
startPcmSession 返回值:
>0:成功启动并返回sessionId-2:系统忙碌-3:功能未启用-5:当前供应商不支持推送 PCM(unsupported)
注意:
- 推送 PCM 模式 不会检查说点啥的录音权限;录音权限由客户端自己处理。
- 建议发送
PCM16LE / 16000Hz / mono,推荐 200ms 一包;服务端当前不强校验采样率与通道,但不匹配可能导致部分引擎效果异常。 finishPcm(sessionId)等价于stopSession(sessionId),表示音频输入结束,等待最终结果。
stopSession / cancelSession
两者均为 void,服务端不返回是否成功。
stopSession:结束录音/输入,进入处理阶段(如有),稍后会回onFinal或onError。cancelSession:取消并清理会话;不保证不会回onFinal(AIDL 注释为“不可保证产生最终结果”)。
isRecording / isAnyRecording
isRecording(sessionId):指定会话是否正在录音/输入中。isAnyRecording():是否存在任意活动会话。
getVersion
返回说点啥的语义化版本名(BuildConfig.VERSION_NAME),例如 "1.6.0"。
回调状态与错误
onState 的 state 取值
| state(Int) | 说明 | 常见 message |
|---|---|---|
0 | Idle/结束 | final/canceled |
1 | Recording | recording |
2 | Processing | processing |
3 | Error | 错误文本 |
onError 的 code 取值
| code | 说明 |
|---|---|
401 | 说点啥缺少录音权限(仅 startSession 可能回) |
403 | 外部联动功能未启用 |
500 | 服务端内部错误(引擎/网络等) |
启用条件
外部 API 联动仅要求:
Prefs.externalAidlEnabled == true
开关入口:设置 → 输入设置 → 外部输入法联动。
供应商与流式决策
外部调用完全跟随说点啥当前设置(忽略 SpeechConfig):
云端供应商:
- Volc:
prefs.volcStreamingEnabled决定是否走VolcStreamAsrEngine - DashScope:
prefs.dashStreamingEnabled - Soniox:
prefs.sonioxStreamingEnabled - ElevenLabs:
prefs.elevenStreamingEnabled - OpenAI / Gemini / SiliconFlow / Zhipu:固定非流式文件引擎
本地供应商:
- Paraformer / Zipformer:固定流式
- SenseVoice / Telespeech:固定非流式文件引擎(伪流式仅用于说点啥自身 UI,不暴露给外部)
结果过滤
最终结果(onFinal)会经过统一末处理:
- 若开启
trimFinalTrailingPunct,去除尾部标点/emoji。 - 语音预设替换:命中时直接替换为预设文本。
- 若开启
postProcessEnabled且 LLM 配置有效,则执行 AI 后处理;失败或返回空时回退到简单处理。
统一入口:AsrFinalFilters.applySimple / AsrFinalFilters.applyWithAi。
会话清理
服务端会在以下情况移除会话并释放资源:
onFinal后onError后cancelSession调用时立即清理
建议客户端在窗口/焦点变化时主动 cancelSession,避免挂起会话。
小企鹅输入法(bibi/lexi)集成示例
修改版小企鹅输入法 bibi 已集成说点啥外联(仓库内示例目录仍沿用旧名 fcitx5-android-lexi-keyboard)。
客户端实现文件:
fcitx5-android-lexi-keyboard/app/src/main/java/org/fcitx/fcitx5/android/link/AsrkbSpeechClient.kt主要特性:
- 绑定包名顺序:
com.brycewg.asrkb.pro→com.brycewg.asrkb - 组件名:
com.brycewg.asrkb.api.ExternalSpeechService - 纯 Binder
transact调用,不依赖 AIDL 生成类 - 使用 推送 PCM 模式:
startPcmSession(presence=0)→ 循环writePcm→ 松手时finishPcm/cancelSession - 空格键长按开始语音;抬起时若已推送过 PCM 帧则
finishPcm,否则cancelSession onPartial实时预览(setComposingText)onFinal提交文本(commitText)并解绑服务onAmplitude驱动覆盖层波形动画
错误处理:
-2:提示“系统忙碌”-3/403:提示“请在说点啥中启用外部联动功能”-5:提示“当前供应商不支持外部推送 PCM”- 录音权限由小企鹅侧自行申请;推送 PCM 模式下服务端不会回
401/-4
使用建议
客户端最佳实践
- 绑定服务时使用
Context.BIND_AUTO_CREATE,按 Pro → 开源顺序尝试。 startSession/startPcmSession成功后保存返回的sessionId;不要自行生成或复用旧 id。- 焦点/窗口切换时主动
cancelSession(sessionId)。 onPartial用于实时预览;onFinal/onError后应解绑或清理资源。- 如果希望客户端掌控录音(IME 场景推荐),优先使用推送 PCM 模式。
