AIUI ASR 实战:从语音命名到“乐奇”唤醒

作者:张润胜

开源示例:sengmitnick/aiui-asr-pet-demo

适用环境:Rokid Craft / Rokid AI 眼镜

一、项目简介:把两种语音入口讲清楚

在 AI 眼镜上,“让用户说一个名字”和“等待用户唤醒”虽然都依赖 ASR,但实际上是两条截然不同的交互链路。

  • 主动识别:用户已经进入明确的语音输入步骤,页面可以直接开始收音;
  • 系统唤醒:页面平时保持安静,收到 AIUI 的唤醒事件后,再识别用户接下来的完整指令。

如果混用这两个入口,应用很容易出现重复创建识别器、旧回调覆盖新状态、页面离开后麦克风仍未释放等问题。
为此,我从赛博宠物的真实命名和唤醒场景中,抽取出一个最小可运行示例。它面向两类读者:一类是正在开发 Rokid AIUI 智能体的开发者,另一类是需要在眼镜上实现语音表单、角色命名、唤醒后对话等流程的产品设计者。示例仅申请麦克风权限,让每一步都易于理解、复现和验证。

二、场景与用户意图:什么时候主动听,什么时候等待唤醒

场景 1:首次进入应用,用语音完成命名

用户已进入命名页面,当前意图非常明确:说出一个名称并确认。页面无需再次要求唤醒,可以在渲染稳定后主动启动 ASR:

进入命名页 → 主动开始 ASR → 展示中间结果 → 用户确认或重说

中间结果仅用于告知用户“系统正在听”,最终结果才会成为候选名称。候选名称不会立刻写入业务数据,只有用户确认后才保存,避免一次误听直接修改名称。

场景 2:首页说“乐奇”,继续表达完整指令

首页不持续开启 SpeechRecognition,也不自行录一段语音后再判断 text.includes('乐奇')。唤醒词识别交给 AIUI 和设备层,页面只承接系统事件:

用户说“乐奇”
      ↓
AIUI / 设备层识别唤醒词
      ↓
页面收到 onVoiceWakeup(event)
      ↓
开启一轮 ASR,识别后续完整指令

这种分工让“何时开始交互”和“用户说了什么”各自拥有清晰边界,也减少了无效的麦克风占用。

三、方案设计:三个页面、两条链路、一套状态机

这个示例智能体由三个 Ink 页面组成:

pages/start/start.ink            启动封面
pages/onboarding/onboarding.ink  主动语音命名
pages/index/index.ink            “乐奇”唤醒后的 ASR

启动页只保留“听见,便有回应”“开始体验”和“单击镜腿开始”三项信息。之后进入命名页,完成语音输入与确认,再进入首页体验系统唤醒。

两条语音链路共享四种可见状态:

idle → listening → result
  ↑         ↓
  └──── error
  • idle:等待进入或等待唤醒;
  • listening:正在识别,并持续展示中间文本;
  • result:得到最终结果,允许用户确认下一步;
  • error:识别失败,提供可理解的重试反馈。

能力调用关系如下:

页面主动进入命名 ─────────────┐
                             ├→ SpeechRecognition → 页面状态 → 用户确认
AIUI onVoiceWakeup 系统事件 ──┘

应用清单只声明麦克风权限:

{
  "pages": [
    "pages/start/start",
    "pages/onboarding/onboarding",
    "pages/index/index"
  ],
  "requiredPermissions": {
    "microphone": true
  }
}

相机、蓝牙和网络能力都没有加入示例,方便将问题定位在 ASR 入口、页面状态或资源生命周期,而不是其他业务模块。

四、AIUI 实现:Ink 页面与关键 API

1. 主动语音命名

命名页显示后短暂延迟,再启动识别,让页面切换和收音状态不会同时争抢首帧:

onLoad() {
  this._startTimer = setTimeout(() => this.beginVoiceName(), 400);
}

识别器使用中文、单轮识别和中间结果:

const recognition = new SpeechRecognition();
recognition.lang = 'zh-CN';
recognition.continuous = false;
recognition.interimResults = true;

onresult 聚合识别文本;只有最后一个结果进入 isFinal 后,页面才切换到确认状态。确认后通过 localStorage 保存名称,选择“重新说”则释放旧实例并开始新一轮识别。

2. 系统唤醒后的单轮 ASR

首页只接收 AIUI 提供的 onVoiceWakeup

onVoiceWakeup(event) {
  if (this.data.listening) {
    this.refreshAsrIdleTimer();
    return;
  }
  this.beginVoiceTurn('wakeup');
}

这里没有再次判断“乐奇”文本。页面已经处于监听状态时,再次收到唤醒只刷新空闲计时,不创建第二个识别器。这是最直接的防重入保护。

3. 成功、失败、超时和退出统一释放

ASR 不只有“成功识别”一种结束方式。识别错误、15 秒无输入、页面退出都要走同一套释放路径:

recognition.onstart = null;
recognition.onresult = null;
recognition.onerror = null;
recognition.onend = null;
recognition.abort();

页面的 onUnload() 同样清理识别器和计时器,防止旧回调继续修改已经离开的页面。

4. 页面级按键映射

两个按钮菜单共用 actionFromKey(),将设备按键先转换成语义动作:

if (code === 'Enter') return 'CONFIRM';
if (code === 'ArrowUp') return 'PREVIOUS';
if (code === 'ArrowDown') return 'NEXT';

页面只处理 PREVIOUS / NEXT / CONFIRM,再由 _selectedmoveSelection()enterSelected() 更新焦点或执行操作。滑动过程中出现的左右中间事件不会进入业务状态机。

路由页面使用标准 .ink 页面 SFC 的 <page> 根节点。<template> 用于自定义组件,不能代替页面容器;需要替换宿主默认滚动或焦点行为时,在页面级 onKeyUp 中调用 event.preventDefault()

五、交互与体验:让用户始终知道系统在做什么

完整体验路径只有三步:

  1. 单击镜腿进入体验;
  2. 说出名称,通过上下滑动选择“确认名字”或“重新说”,单击执行;
  3. 进入首页后说“乐奇”,继续说出完整指令,页面展示实时文本和最终结果。

Craft 完整交互演示

AIUI 语音交互

启动封面

启动封面
打开全屏

命名页:监听、结果与选择

命名页正在监听命名页显示最终名称

首页:等待唤醒与按钮焦点

请添加图片描述
请添加图片描述

眼镜显示对大面积光晕并不友好,因此页面移除了 box-shadow。按钮也不显示额外箭头:未选中项使用虚线边框和 0.8 透明度,选中项使用实线边框和完整不透明度。焦点清楚,但不会遮挡正文。

六、调试与验证:从 Craft 到真机

Craft 复现步骤

  1. 克隆开源仓库;
  2. Craft 中选择“导入本地文件夹”;
  3. 运行智能体并进入 Interactive InkView;
  4. 在启动页单击“开始体验”;
  5. 进入命名页后说出一个名称,观察中间结果和最终结果;
  6. 使用方向键切换按钮,单击进入首页;
  7. 使用 Craft 的“触发语音唤醒”模拟系统事件;
  8. 继续说一句完整指令,观察实时文本和最终结果;
  9. 连续触发唤醒,确认页面没有创建第二个识别会话;
  10. 保持安静直到超时,确认页面恢复等待态。

真机运行验证

vid-20260825-154940-88

真机调试中解决的三个问题

问题一:滑动时焦点正确,手势结束又触发一次。

最初页面误用了 <template>。按照 AIUI 页面规范改为 <page> 后,页面级 onKeyUp 才能稳定接管宿主默认行为。

问题二:一次滑动包含多个底层方向事件。

解决方式不是增加时间锁,而是先做按键语义映射,只让最终的 ArrowUp / ArrowDown 进入菜单状态机,忽略左右中间事件。

问题三:阴影在模拟器里柔和,在眼镜上却形成大片光晕。

最终移除所有 box-shadow,使用虚线/实线和透明度表达未选中/选中状态。

仓库使用 Node.js 内置测试运行器:

npm test

目前共有 11 项自动化测试,覆盖应用权限、页面 SFC 根节点、两条 ASR 调用链、单实例保护、资源释放、按键映射、复合滑动和真机焦点样式。AIX 也可通过 aiui-aix pack 完成打包。

七、成果与复盘:把真实项目经验沉淀成最小示例

本项目已经形成一套可验证成果:

  • 可直接导入 Craft 的完整示例;
  • 启动、命名、系统唤醒三个 Ink 页面;
  • Craft 运行截图和交互验证步骤;
  • 11 项自动化测试;
  • 可打包的 AIX 工程;
  • MIT License 开源仓库:sengmitnick/aiui-asr-pet-demo

这次实现最终沉淀出三条可复用原则:

  1. 系统事件决定何时进入交互:唤醒词交给 AIUI 与设备层;
  2. 页面状态决定如何承接交互:明确展示等待、监听、结果和错误;
  3. 生命周期决定功能是否稳定:单实例、防重入,并覆盖成功、失败、超时和退出释放。

相关资料:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐