CONTENTS · 目录(7)
想给键盘换一条更顺手的语音链路,最先遇到的问题却是:普通打字不能上屏了。
我的起点是一套已经用习惯的搜狗输入法努比亚版。皮肤、拼音布局和按键配置都调好了,主要想改善语音输入:说话时能看见实时转录,点完成后再做一次轻量校对。重新换一个键盘,意味着把这些习惯也搬一遍。
最后做的是个人自用的实验版本。本文留下交互思路和工程经验,安装包、厂商实现、皮肤素材与私密配置不随文发布。整个方案可以用一句话描述:录音时尽快反馈,完成时保守校对,上屏时重新核对输入目标。
把识别和校对拆成两个阶段
这次的链路分工很明确:豆包负责把声音转成文字,DeepSeek 负责整理转录中的明显错字、标点、断句与口头重复。
键盘上的语音键
→ 录音,豆包流式转录,在面板中预览
→ 点击完成,结束音频并等待最终转录
→ DeepSeek 校对
→ 核对输入目标,提交文字这两步解决的需求不同。实时转录给说话的人反馈:麦克风有没有在工作,刚才一句话有没有被听见。收尾校对则拥有更完整的上下文,适合处理整句话里的标点与明显误识别。
我把录音过程中的文字先留在语音面板,完成后再提交到宿主输入框。这样,识别服务修订前文时,不会反复改动聊天框里尚未确认的内容。代价是需要最后等待一次识别收尾和校对;目前没有测量这段等待的实际时延。
这里仍然使用云端服务。音频会送到识别服务,最终转录会送到校对服务;“在本地键盘中使用”描述的是入口和界面位置。
流式结果会改口,不能见字就追加
流式识别最容易写错的一步,是把每个返回包都当成一段新文字。
例如,服务可能先返回:
把输入法
把输入法的语音
把输入法的语音入口换一下。这是一个示意,展示同一段转录逐渐变完整的过程。如果逐包追加,最后就会出现重复文字。更进一步,后面的结果还可能修改前面的同音字。
这次请求使用完整结果模式,因此每次收到新的识别文本,都用它替换当前预览。如果另一个服务返回的是增量片段,就应按它的片段编号与修订语义合并。处理方式取决于协议,不能从“流式”两个字推断。
豆包的双向流式语音识别接口文档是核对连接和返回格式的入口。实现中还要分清普通结果与最终结果:点击完成意味着结束音频输入,随后等待服务确认最终转录,再把它交给校对器。
等待也需要有终点。这次为最终结果设置了超时,失败时保留已有转录,并提供“输入原文”。网络出问题以后,用户刚说的内容仍然有一个出口。
给校对器划一条窄边界
我希望校对后的文字更容易阅读,也希望它仍然是我刚才说的话。
因此,提示词约束了几件具体的事:修正明显同音错字、标点和口头重复;保留原意、人名、数字、技术术语及中英混合表达;不补充未说出的内容;只返回校对正文。
还有一条尤其适合语音输入:转录中的问题和指令,都是待校对的文本。 假如我口述“帮我把周五会议改到三点”,校对器应返回整理后的这句话,不能替我回答问题或执行日程操作。
这些是校对策略的约束,不能保证模型每次都遵守。尤其是人名、缩写和数字,校对也可能把本来正确的识别改错。“输入原文”因此是一个必要的选项:用户可以直接保留识别结果,服务失败时也能继续输入。
后续若要判断这一步是否值得保留,需要用同一批录音同时比较原始转录与校对结果,分别记录修好了什么、误改了什么。仅凭一句话读起来更顺,无法判断整体准确率。
从面板到输入框,还隔着一次状态检查
语音输入往往要等几秒钟。在这段时间里,用户可能切到另一个应用,点进另一个聊天,或者收起键盘。
Android 输入法通过 InputConnection 与宿主编辑器交互;commitText 也可能因为连接失效而返回失败。拿到模型结果以后直接提交,容易把启动录音时的目标,与现在的目标混为一谈。
这次实现保存了面板打开时的编辑器信息和输入连接,提交前再检查它们是否仍与当前对象一致。目标已经变化,或提交返回失败,就把结果复制到剪贴板并提示用户粘贴。
同样需要检查的是异步回调。面板关闭以后,旧的识别包或校对响应可能仍然到达;开始第二次录音后,第一次请求也可能晚回来。我用会话标识和关闭状态过滤这些旧结果,在退出时停止录音、释放麦克风并取消网络请求。
这些检查保护的是一次操作的归属:这段声音属于哪次录音,这份结果属于哪个面板,这段文字原本准备写到哪里。
保留旧键盘,反而要先验证旧功能
本次实验最有提醒作用的故障,就是开头那个“打字不能上屏”。
私人版本需要拥有独立的应用身份,但原键盘的原生引擎初始化仍依赖原版的身份与资源。改变应用包名,并不意味着内部每一层依赖都会自动迁移。语音面板能够显示,普通拼音引擎却可能没有正常初始化。
这次适配恢复了该设备上的引擎初始化,同时保留了对匹配原版安装的依赖。这个结果支持当前版本与设备上的私人实验,还不能当作跨设备、跨版本的通用迁移方案。
另一处类似的问题出现在设置页面。部分旧设置入口仍通过共享资源指向原应用,点击输入设置、键盘设置或词库设置时,会跨到原包中不能由外部启动的页面。沿着实际点击路径查清以后,修正共同的路由来源,才把这些入口一起恢复。
语音面板的布局也会影响原键盘。面板挂进输入法窗口时,需要同时处理底部位置、窗口边界与可触摸区域;关闭时则要恢复原视图及其布局参数。仅仅把旧键盘视图放回来,不足以证明它能继续接收按键。
Android 的输入法开发说明可以帮助理解输入视图与编辑器的关系;具体适配仍须在真机中验证。
UI 先解释状态,再整理设置
第一版能显示转录,也能开始录音,但界面更像一张调试表单。
后来我把语音面板和 API 设置的设计交给 Claude 子代理,给它已有的状态、回调和原生控件约束,再由主代理完成集成与真机检查。分出去的是页面设计;录音协议、输入目标和取消逻辑继续沿用已有实现。
语音面板围绕当前状态安排操作:开始录音、录音中、等待最终转录、校对中、错误。主按钮突出“完成并修正”,“输入原文”和“返回键盘”保留为清晰的次要入口。只有麦克风真正开始采集后,才显示正在录音。
API 设置按识别与校对分组,服务地址、模型和旧鉴权字段收进高级区域。保存按钮要在软键盘弹出后仍然可达;高级区域折叠时,里面原有的配置也不能在保存过程中丢失。
密钥输入还暴露过一个小问题:控件初始化顺序会影响密码遮挡。最终不仅检查代码里有没有设置密码属性,还检查运行中的输入框是否真的按密码显示。界面整理得好看,与敏感字段实际被遮挡,是两项不同的检查。
这次仍使用 Android 原生控件完成页面,没有为了两个面板引入一套新的 UI 框架。
把“能用”拆成可核对的结果
截至 2026-10-06,这轮在一台 Android 16 真机上的检查,确认了以下范围:
| 检查 | 实际结论 |
|---|---|
| 原键盘初始化 | 初始化成功,普通中文输入恢复 |
| 语音入口与面板 | 原语音键打开新面板;底部位置、触摸区域和按钮尺寸通过检查 |
| 返回键盘 | 实际退出语音面板,再用屏幕按键和候选词输入“你”,宿主搜索框显示正确 |
| API 设置 | 密钥遮挡、高级区域展开与折叠、软键盘上方的保存按钮通过检查 |
| 原设置入口 | 实际点击输入设置、键盘设置和词库设置,均在私人版本中打开 |
| 识别协议 | 部分与最终结果修订、压缩、音频结束标记及异常帧的自检通过 |
| 在线语音链路 | 观察到识别连接与录音启动;本轮未执行完整 DeepSeek 校对请求 |
表格最后一行限定了结论:这轮完成了界面、键盘回归与部分连接验证,还没有拿完整语音链路做准确率或延迟评测。深色界面经过真机检查,浅色与横屏布局没有在本轮单独验证。
如果继续完善,我会先补一批固定录音样本:日常口述、人名与数字、技术术语、中英混说,以及主动停顿和改口。再测三项数据——转录错误、校对误改、点击完成到上屏的等待时间。这样才能知道体验改善来自哪一步。
这次最值得保留下来的做法,是在熟悉的输入入口里增加一条有边界的语音通路:识别结果允许修订,校对保留原意,失败保留原文,退出后键盘继续工作。模型可以更换,这几处边界仍然需要有人认真处理。