Skip to content
Star

AIDL 通信(小企鹅联动)

说点啥(asr-keyboard)提供标准 AIDL 服务,允许其他应用(如修改版小企鹅输入法)调用说点啥的语音识别能力。

服务端为手写 Binder 协议,但与 AIDL 生成的代理完全兼容;客户端可直接使用 .aidl 生成的 Stub/Proxy,也可以像小企鹅一样用 transact 纯 Binder 调用。

用户使用指南

目前仅有说点啥魔改的小企鹅输入法支持该功能,使用步骤如下:

  1. 下载并安装说点啥最新版(开源版或 Pro 版均可,优先调用 Pro 版)
  2. 在说点啥中启用外部联动功能:设置 → 输入设置 → 允许外部输入法联动,打开开关
  3. 下载并安装修改版小企鹅输入法
  4. 在小企鹅输入法中启用说点啥联动:设置 → 虚拟键盘 → 空格键长按行为 → 语音输入(AIDL)
  5. 使用小企鹅输入法时,长按空格键开始语音输入,松手结束

包名优先级(与小企鹅实现一致):

  1. com.brycewg.asrkb.pro
  2. com.brycewg.asrkb

客户端应按顺序尝试绑定,优先使用已安装的 Pro 包(接口与行为一致)。

开发者指南

服务接口(IExternalSpeechService)

接口描述符com.brycewg.asrkb.aidl.IExternalSpeechService

事务码(与 AIDL Stub 保持一致):

方法事务码说明
startSessionFIRST_CALL_TRANSACTION + 0服务端录音模式会话
stopSessionFIRST_CALL_TRANSACTION + 1停止当前会话
cancelSessionFIRST_CALL_TRANSACTION + 2取消当前会话
isRecordingFIRST_CALL_TRANSACTION + 3指定会话是否正在录音
isAnyRecordingFIRST_CALL_TRANSACTION + 4是否存在任意录音会话
getVersionFIRST_CALL_TRANSACTION + 5获取应用版本名
startPcmSessionFIRST_CALL_TRANSACTION + 6推送 PCM 模式会话
writePcmFIRST_CALL_TRANSACTION + 7推送一帧 PCM 音频数据
finishPcmFIRST_CALL_TRANSACTION + 8结束 PCM 推送并进入处理

对应 AIDL 方法签名:

kotlin
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: Boolean
  • punctuationEnabled: Boolean?
  • autoStopOnSilence: Boolean?
  • sessionTag: String?

当前实现说明

  • startSession / startPcmSession 均忽略vendorId=="mock" 外的全部配置,完全跟随说点啥应用内当前设置。
  • 只有 startSession 支持 vendorId=="mock" 的联通测试模式(见下文)。

回调接口(ISpeechCallback)

接口描述符com.brycewg.asrkb.aidl.ISpeechCallback

kotlin
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:结束录音/输入,进入处理阶段(如有),稍后会回 onFinalonError
  • cancelSession:取消并清理会话;不保证不会回 onFinal(AIDL 注释为“不可保证产生最终结果”)。

isRecording / isAnyRecording

  • isRecording(sessionId):指定会话是否正在录音/输入中。
  • isAnyRecording():是否存在任意活动会话。

getVersion

返回说点啥的语义化版本名(BuildConfig.VERSION_NAME),例如 "1.6.0"

回调状态与错误

onState 的 state 取值

state(Int)说明常见 message
0Idle/结束final/canceled
1Recordingrecording
2Processingprocessing
3Error错误文本

onError 的 code 取值

code说明
401说点啥缺少录音权限(仅 startSession 可能回)
403外部联动功能未启用
500服务端内部错误(引擎/网络等)

启用条件

外部 API 联动仅要求:

  • Prefs.externalAidlEnabled == true

开关入口:设置 → 输入设置 → 外部输入法联动

供应商与流式决策

外部调用完全跟随说点啥当前设置(忽略 SpeechConfig):

云端供应商

  • Volcprefs.volcStreamingEnabled 决定是否走 VolcStreamAsrEngine
  • DashScopeprefs.dashStreamingEnabled
  • Sonioxprefs.sonioxStreamingEnabled
  • ElevenLabsprefs.elevenStreamingEnabled
  • OpenAI / Gemini / SiliconFlow / Zhipu:固定非流式文件引擎

本地供应商

  • Paraformer / Zipformer:固定流式
  • SenseVoice / Telespeech:固定非流式文件引擎(伪流式仅用于说点啥自身 UI,不暴露给外部)

结果过滤

最终结果(onFinal)会经过统一末处理:

  1. 若开启 trimFinalTrailingPunct,去除尾部标点/emoji。
  2. 语音预设替换:命中时直接替换为预设文本。
  3. 若开启 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.procom.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

使用建议

客户端最佳实践

  1. 绑定服务时使用 Context.BIND_AUTO_CREATE,按 Pro → 开源顺序尝试。
  2. startSession/startPcmSession 成功后保存返回的 sessionId;不要自行生成或复用旧 id。
  3. 焦点/窗口切换时主动 cancelSession(sessionId)
  4. onPartial 用于实时预览;onFinal/onError 后应解绑或清理资源。
  5. 如果希望客户端掌控录音(IME 场景推荐),优先使用推送 PCM 模式。

Released under the Apache 2.0 License.