更新记录

1.0.0(2026-09-13) 下载此版本

初版


平台兼容性

uni-app(3.8.3)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - - - - - 4.4 - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

其他

多语言 暗黑模式 宽屏模式

sl-uniplugin-asr 离线语音识别插件

简介

基于 Vosk 的离线中文语音识别(ASR) UTS 插件,无需网络服务,实时返回识别结果。

引擎 模型 采样率
Vosk (Kaldi) 中文声学模型 + 语言模型 16000Hz

目录结构

sl-uniplugin-asr/
├── package.json
└── utssdk/app-android/
    ├── index.uts              # ASR 实现
    ├── config.json           # 依赖与构建配置
    ├── AndroidManifest.xml   # 权限声明
    └── assets/model/         # Vosk 中文模型
        ├── am/final.mdl      # 声学模型
        ├── conf/             # MFCC 配置
        ├── graph/            # 语言模型 (Gr.fst, HCLr.fst)
        ├── ivector/          # ivector 适配器
        └── uuid              # 模型版本标识

权限

<uses-permission android:name="android.permission.RECORD_AUDIO" />

Android 6.0+ 需运行时动态申请麦克风权限,插件已内置权限检查与申请逻辑。

API

initModel(options, callback)

初始化语音识别模型。通过 Vosk StorageService 从 assets 解压模型到内部存储。

参数 类型 说明
options Object 无必填参数
callback Function 回调函数

回调返回值:

{ "code": 0, "success": true, "msg": "Model loaded" }

示例:

import { initModel } from '@/uni_modules/sl-uniplugin-asr'

initModel({}, (res) => {
  if (res.code === 0) {
    console.log('ASR 模型就绪')
  } else {
    console.error('初始化失败:', res.msg)
  }
})

首次初始化会解压模型到 /data/data/<pkg>/files/model/,之后启动会跳过解压。


startListening(options, callback)

开始离线语音识别。

⚠️ 重要:callback 一次性限制 在 uni-app Vue3(非 uni-app x)项目 + App-Android 真机环境下,UTS callback 只能被 JS 端接收一次。Vosk 的多次识别结果(partial/result/final/timeout)无法通过 callback 传递到 JS 端。

插件已内置 getLastResult() 轮询方案绕过此限制,前端需用 setInterval 轮询获取结果,详见下文。

参数 类型 说明
options Object 无必填参数
callback Function 仅触发一次,返回监听启动状态

callback 返回值(仅一次):

// 监听启动成功
{ "code": 0, "msg": "Listening" }

// 监听启动失败
{ "code": 1, "msg": "模型未初始化" }
{ "code": 1, "msg": "正在请求麦克风权限,请授权后重试" }

后续识别结果需通过 getLastResult() 轮询获取:

// partial(说话过程中,实时更新)
{ "code": 0, "type": "partial", "text": "你好", "isFinal": false, "timestamp": 1694000000000 }

// result(检测到一个语音段落后的结果)
{ "code": 0, "type": "result", "text": "你好世界", "isFinal": true, "timestamp": 1694000000500 }

// final(停止监听后的最终结果)
{ "code": 0, "type": "final", "text": "你好世界", "isFinal": true, "timestamp": 1694000001000 }

// timeout(15秒无语音自动超时)
{ "code": 0, "type": "timeout", "text": "", "isFinal": true, "timestamp": 1694000002000 }

// error
{ "code": 1, "type": "error", "text": "", "isFinal": true, "msg": "错误信息", "timestamp": 1694000003000 }

结果类型说明:

type 含义 是否结束监听
partial 说话过程中的部分识别结果
result 检测到一个语音段落后的结果
final 调用 stopListening 后的最终结果
timeout 15秒无语音自动超时
error 发生错误

完整示例(包含轮询):

import { startListening, getLastResult } from '@/uni_modules/sl-uniplugin-asr'

let listening = false
let lastTimestamp = 0
let pollTimer = null

// 开始聆听
startListening({}, (res) => {
  // 这个 callback 只触发一次,用于确认监听是否启动
  if (res.code === 0) {
    listening = true
    console.log('正在聆听,请说话...')
    // 启动轮询获取识别结果
    lastTimestamp = 0
    pollTimer = setInterval(() => {
      const result = getLastResult()
      if (result == null) return
      const ts = result.timestamp || 0
      if (ts <= lastTimestamp) return  // 不是新结果
      lastTimestamp = ts

      const type = result.type
      const text = result.text
      const msg = result.msg

      if (result.code === 0) {
        // 显示识别结果
        if (text && String(text).trim()) {
          console.log('识别:' + String(text).trim())
        }
        // final 或 timeout 表示监听结束
        if (type === 'final' || type === 'timeout' || type === 'error') {
          listening = false
          clearInterval(pollTimer)
          pollTimer = null
          console.log('识别结束')
        }
      } else {
        console.error('失败:', msg)
        listening = false
        clearInterval(pollTimer)
        pollTimer = null
      }
    }, 200)  // 200ms 轮询
  } else {
    listening = false
    console.error('启动失败:', res.msg)
  }
})

// 页面卸载时清除轮询
// onUnload() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null } }

15 秒无语音自动超时停止。type: 'result' 不是结束信号,可以继续说下一句。


getLastResult()

获取最新识别结果。配合 setInterval 轮询使用,绕过 UTS callback 一次性限制。

返回值 说明
UTSJSONObject \| null 最新识别结果,包含 code/type/text/isFinal/msg/timestamp 字段

返回值结构:

{
  code: 0,          // 0=成功,1=失败
  success: true,    // 是否成功
  type: 'partial',  // partial/result/final/timeout/error
  text: '你好',      // 识别文本(可能为空字符串)
  isFinal: false,   // 是否为最终结果
  msg: '',          // 附加消息(错误时包含错误信息)
  timestamp: 1694000000000  // 毫秒时间戳,用于判断是否为新结果
}

说明:

  • 每次调用返回 UTS 端存储的最新结果(不会消费,重复调用返回同一对象直到有新结果)
  • 通过比较 timestamp 判断是否有新结果
  • 无结果时返回 null
  • 前端需在 startListening 的 callback 触发后启动轮询

轮询示例:

import { getLastResult } from '@/uni_modules/sl-uniplugin-asr'

let lastTimestamp = 0
const pollTimer = setInterval(() => {
  const result = getLastResult()
  if (result == null) return
  const ts = result.timestamp || 0
  if (ts <= lastTimestamp) return
  lastTimestamp = ts
  console.log('新结果:', result.type, result.text)
}, 200)

stopListening(options, callback)

停止语音识别。

示例:

import { stopListening } from '@/uni_modules/sl-uniplugin-asr'

stopListening({}, (res) => {
  console.log('已停止识别')
})

destroy(options, callback)

释放 Vosk 模型和 SpeechService 资源。释放后需重新 initModel() 才能使用。

示例:

import { destroy } from '@/uni_modules/sl-uniplugin-asr'

destroy({}, (res) => {
  console.log('资源已释放')
})

技术细节

  • 轮询机制:UTS callback 在 Weex 引擎下只能被 JS 端接收一次,插件用 lastResult 全局变量 + getLastResult() 导出函数 + 前端 setInterval 轮询的方式绕过此限制
  • 结果存储:每次 Vosk 回调(partial/result/final/timeout/error)时,结果存入 lastResult 全局变量,并附带 timestamp 时间戳
  • 文本解析:Vosk partial 结果用 "partial" 键,result/final 用 "text" 键,插件已统一处理
  • 空结果过滤:空 partial 结果不会更新 lastResult,减少无效轮询
  • 15秒超时:通过 Handler.postDelayed 定时,超时自动停止 SpeechService
  • 权限检查Build.VERSION.SDK_INT >= M 时检查 RECORD_AUDIO 权限,无权限时自动调用 requestPermissions
  • 模型不压缩config.json 配置 noCompress: ["mdl", "fst", "int", "ie", "mat", "dubm", "stats", "conf"]

注意事项

  1. minSdkVersion 21(Android 5.0+)
  2. 首次使用需授予麦克风权限
  3. startListening() 会自动停止上一次未完成的监听
  4. type: 'result' 不是结束信号,只有 finaltimeouterror 才表示监听结束
  5. 释放后需重新 initModel() 才能再次使用
  6. 必须用轮询方式获取识别结果startListening 的 callback 仅触发一次,用于确认监听是否启动;后续识别结果需调用 getLastResult() 轮询获取
  7. 轮询间隔建议 200ms:太小会增加 CPU 占用,太大会延迟显示结果
  8. 页面卸载时需清除轮询定时器,避免内存泄漏
  9. Vosk 中文模型输出格式:每个词之间用空格分隔(如 "你好 世界"),这是 Vosk 的中文输出特点

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。