更新记录

1.0.0(2026-10-22)

1.0.0(2026-10-22)首个版本:

  1. 基于 sherpa-onnx 1.13.8 的 Android 离线流式语音识别;
  2. 实时文本 getRealtimeText,配合 150ms 轮询实现实时字幕;
  3. 端点规则自动断句,vadSilenceMs 可调;
  4. 内置 26MB 中文 int8 模型,首次运行自动释放到应用私有目录;
  5. 兼容 HBuilderX 4.25+ 的回调回收机制(onPartial/onFinal/onError);
  6. 提供可选 JS 适配层与模型下载脚本;
  7. iOS / H5 / 小程序 / HarmonyOS 为占位实现,调用抛出明确异常。

平台兼容性

uni-app(4.25)

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

wt-sherpa-asr 离线语音识别(UTS 插件)

基于 sherpa-onnx 的 uni-app UTS API 插件。把流式语音识别封装成统一的 UTS 接口,模型随 App 一起打包,完全本地推理,不联网、不上传任何语音或文本。

当前版本的原生实现位于 Android 端;iOS / H5 / 小程序 / HarmonyOS 为占位实现,调用时会抛出明确的“不支持”异常,不会静默失败。


一、特性

能力 说明
流式识别 边说边出字,配合 getRealtimeText() 轮询即可做实时字幕
自动断句 基于 sherpa-onnx 端点规则(endpoint),静音达到阈值即切句
热词增强 start(hotwords) 或 hotwords 选项,提升专有名词识别率
完全离线 模型打进 APK,首次运行释放到应用私有目录,无任何网络请求
轻量模型 内置 Zipformer2 CTC 中文 int8 模型(约 26 MB),中低端机可实时
双版本兼容 同时提供事件式与轮询式两种结果读取方式

平台支持

平台 状态 说明
App-Android ✅ 已实现 AudioRecord 采集 16 kHz 单声道 PCM,sherpa-onnx 流式推理
App-iOS ⛔ 占位 原生实现尚未提供,调用 new AsrEngine() 会抛异常
App-HarmonyOS ⛔ 占位 未实现
H5 / Web ⛔ 占位 未实现,建议改用 Web Speech API
小程序 ⛔ 占位 未实现

由于原生库的存在,必须使用自定义基座或正式打包运行,标准基座不含本插件的原生代码。


二、目录结构

uni_modules/wt-sherpa-asr/
├── package.json                     插件元数据(插件市场发布用)
├── readme.md                        本文档
├── changelog.md                     更新日志
├── license.md                       许可与第三方组件说明
├── utssdk/
│   ├── interface.uts                ★ 跨平台统一接口(对外契约)
│   ├── app-android/
│   │   ├── index.uts                Android 原生实现
│   │   ├── config.json              Gradle 依赖
│   │   └── libs/
│   │       └── sherpa-onnx-1.13.8.aar
│   ├── app-ios/index.uts            iOS 占位
│   ├── app-harmony/index.uts        鸿蒙占位
│   ├── web/index.uts                H5 占位
│   └── mp-weixin/index.uts          小程序占位
├── static/models/
│   ├── zipformer-zh-int8/           内置中文识别模型
│   │   ├── model.int8.onnx
│   │   ├── tokens.txt
│   │   └── bbpe.model
│   └── vad.onnx                     VAD 模型文件(当前版本未加载,预留)
├── js_sdk/
│   └── wt-sherpa-asr-sdk/index.js   ★ 可选 JS 适配层(推荐直接使用)
└── scripts/
    └── download-models.js           模型下载 / 替换脚本

三、安装

方式 A:插件市场导入

在 HBuilderX 中打开项目 → 右键 uni_modules 目录 → 从插件市场导入 → 搜索 wt-sherpa-asr。

方式 B:手动拷贝

把整个 wt-sherpa-asr 目录放到项目的 uni_modules/ 下:

你的项目/
└── uni_modules/
    └── wt-sherpa-asr/

必须完成的配置

1)Android 录音权限

在 manifest.json → app-plus → distribute → android → permissions 中加入:

{
  "app-plus": {
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>"
        ]
      }
    }
  }
}

Android 6.0+ 还必须在运行时申请权限,插件已提供 requestPermission()。

2)制作自定义调试基座

HBuilderX 菜单:运行 → 运行到手机或模拟器 → 制作自定义调试基座。

只点“运行到自定义基座”不会更新原生代码;原生依赖(AAR)或模型变更后都必须重新制作基座。

3)用真机调试

模拟器没有真实麦克风输入,无法验证识别效果。


四、快速开始

4.1 推荐用法:使用内置 JS 适配层

适配层已固化真机验证过的最佳实践(静态导入、new AsrEngine、轮询实时文本、释放资源),可以直接复制使用。

import asr from '@/uni_modules/wt-sherpa-asr/js_sdk/wt-sherpa-asr-sdk'

export default {
  data() {
    return {
      text: '',
      recording: false
    }
  },

  async onLoad() {
    if (!asr.isSupported()) {
      uni.showToast({ title: '当前平台不支持离线识别', icon: 'none' })
      return
    }
    uni.showLoading({ title: '初始化模型…' })
    const ok = await asr.setup({ numThreads: 2, vadSilenceMs: 700 })
    uni.hideLoading()
    if (!ok) {
      uni.showToast({ title: '初始化失败,请检查权限与模型', icon: 'none' })
    }
  },

  // ★ 必须释放,否则原生内存泄漏
  onUnload() {
    asr.destroy()
  },

  methods: {
    async startRecord() {
      this.text = ''
      const ok = await asr.start({
        // full = 本次录音到此刻的完整文本(含当前未断句部分)
        onText: (full) => {
          this.text = full
        },
        onError: (msg) => {
          this.recording = false
          uni.showToast({ title: msg, icon: 'none' })
        }
      })
      this.recording = ok
    },

    async stopRecord() {
      this.recording = false
      const text = await asr.stop()   // 完整文本,已含收尾解码
      this.text = text
      console.log('性能:', asr.stats())  // { rtf, totalDurationMs, audioDurationMs }
    }
  }
}

4.2 直接调用 UTS API

// ⚠️ 必须静态 import;动态 await import() 会被构建期解析成 null
import {
  AsrEngine,
  initDefaultModels,
  requestPermission
} from '@/uni_modules/wt-sherpa-asr'

let engine = null
let timer = null

async function init() {
  // 1) 权限
  const granted = await requestPermission()
  if (!granted) return false

  // 2) 释放内置模型,得到绝对路径(首次约 1 秒,之后幂等跳过)
  const root = await initDefaultModels()

  // 3) 建引擎:必须用 new,不要用 createEngine()
  engine = new AsrEngine({
    modelDir: root + '/zipformer-zh-int8',
    modelType: 'zipformer_ctc',
    sampleRate: 16000,
    numThreads: 2,
    vadSilenceMs: 700,
    enablePunctuation: false
  })
  return true
}

async function start(onText) {
  await engine.start()
  // 4) 轮询完整实时文本(150ms 一次,纯读取、不依赖回调生命周期)
  timer = setInterval(() => {
    onText(engine.getRealtimeText())
  }, 150)
}

async function stop() {
  clearInterval(timer)
  const res = await engine.stop()   // res.text 是本次录音的完整文本
  return res.text
}

function destroy() {
  clearInterval(timer)
  if (engine) engine.release()      // 或 releaseEngine(engine)
  engine = null
}

五、为什么必须这样用(重要)

这三点是真机踩坑后的结论,绕开任意一条都会出现“引擎建不起来 / 只出一帧字 / 内存泄漏”。

5.1 必须静态 import

UTS 插件要在构建期解析原生模块,await import('@/uni_modules/wt-sherpa-asr') 会被静默解析失败(返回 null),表现为“没有报错但引擎永远建不起来”。

5.2 必须 new AsrEngine(options)

createEngine() 在桥接层的声明是“返回 JSON 对象”,原生类实例穿过这层后会退化成空壳对象,engine.start / engine.on 全部变成 undefined。AsrEngine 才是可用的代理类。

5.3 实时字幕用轮询,不用 on('partial')

HBuilderX 4.25+ 起,UTS 插件方法里的回调触发一次即回收,on('partial', cb) 只能收到第一帧,之后报“回调函数已释放”。

保活规则是“方法名以 on 开头 + 仅一个 callback 参数”,因此插件额外提供了 onPartial() / onFinal() / onError();但最稳的方式仍是轮询 getRealtimeText()——纯读取接口,不涉及回调生命周期。


六、API 摘要

完整签名、返回值与错误信息见插件包内的 docs/API参考.md。

顶层函数

函数 返回 说明
initDefaultModels(force?) Promise<string> 把内置模型复制到应用私有目录,返回模型根目录绝对路径(含 zipformer-zh-int8/)
requestPermission() Promise<boolean> 申请麦克风权限
checkPermission() Promise<boolean> 检查麦克风权限
getVersionInfo() AsrVersionInfo { pluginVersion, sherpaOnnxVersion, platform }
getBundledModels() Promise<AsrModelInfo[]> 内置模型清单
releaseEngine(engine) void 释放引擎,等价于 engine.release()

引擎 AsrEngine

成员 说明
new AsrEngine(options) 同步加载模型(约 1~3 秒),建议放在 loading 中
start(hotwords?) 开始录音,未授权麦克风会 reject
stop() 停止录音并做收尾解码,res.text 为本次录音完整文本
cancel() 取消本次识别并丢弃音频
getRealtimeText() 本次录音至今的完整文本(已断句段 + 当前未断句部分)
onPartial(cb) / onFinal(cb) / onError(cb) 可重复触发的事件(回调保活)
on(event, cb) / off(event) 通用事件接口(4.25+ 回调仅触发一次,不推荐用于高频事件)
stats() { rtf, totalDurationMs, audioDurationMs }
release() 释放全部原生资源
state 0 IDLE / 1 READY / 2 RECORDING / 3 DECODING / 4 RELEASED / 5 ERROR

AsrOptions

字段 类型 默认值 说明
modelDir string 必填 模型目录绝对路径
modelType string 'zipformer_ctc' 当前仅支持 zipformer_ctc(见“已知限制”)
sampleRate number 16000 固定 16 kHz,修改无效
numThreads number 1 推理线程数,建议 1~2
enableVad boolean true 预留项,当前版本断句由端点规则实现
vadSilenceMs number 700 静音多久判定一句话结束(端点规则 1 的 trailing silence)
enablePunctuation boolean true 需自备 punc 模型,包内未内置
hotwords string '' 热词,逗号分隔

七、内置模型与替换

内置模型

路径 大小 用途
static/models/zipformer-zh-int8/model.int8.onnx 25.1 MB 中文流式识别模型(int8)
static/models/zipformer-zh-int8/tokens.txt 13 KB 词表
static/models/zipformer-zh-int8/bbpe.model 249 KB 分词模型
static/models/vad.onnx 2.2 MB Silero VAD v5,当前版本未加载,为后续版本预留

合计约 28 MB,随 APK 分发;首次运行 initDefaultModels() 会复制到应用私有目录(约 1 秒)。

使用标点模型(可选)

插件包内不含标点模型(CT-Transformer int8 约 72 MB)。需要时:

cd uni_modules/wt-sherpa-asr/scripts
node download-models.js --extra      # 下载 punc(同时会补 vad)

下载完成后 static/models/punc/model.int8.onnx 就位,initDefaultModels() 会把它一并复制到沙盒,此时把 enablePunctuation 设为 true 即可生效。

更换识别模型

cd uni_modules/wt-sherpa-asr/scripts

node download-models.js --list                    # 查看可选模型
node download-models.js --model zipformer-bilingual

注意:当前 Android 实现只装配 Zipformer2 CTC 流式模型(model.int8.onnx + tokens.txt)。下载的双语 / Paraformer / SenseVoice 模型需要自行修改 utssdk/app-android/index.uts 中的装配逻辑,否则无法直接使用。


八、常见问题

Q1:模拟器或标准基座里一调用就报错

必须制作自定义基座或正式打包,标准基座不含 AAR 与模型。

Q2:engine.start / engine.on 是 undefined

用了 createEngine()。改成 new AsrEngine(options)。

Q3:实时字幕只出第一帧,之后报“回调函数已释放”

用了 on('partial', cb)。改用 onPartial(cb),或直接轮询 getRealtimeText()。

Q4:ClassCastException: java.lang.Double cannot be cast to java.lang.Float

基座里的原生代码是旧版本。重新“制作自定义调试基座”,不能只重启。

Q5:报“模型文件不存在”

  1. 确认调用了 initDefaultModels() 并使用返回值;
  2. 确认 static/models/zipformer-zh-int8/ 下文件完整;
  3. 模型被 .gitignore / .npmignore 排除过会导致漏打包。

Q6:报“麦克风权限未授权”

Android 6.0+ 需要运行时权限:await requestPermission(),用户拒绝后引导去系统设置开启。

Q7:报“AudioRecord 初始化失败,可能被其他应用占用麦克风”

同一时间只能有一个录音通道。先停止其他录音(系统录音机、通话、其他语音 SDK),或先 destroy() 旧引擎。

Q8:识别结果为空

  1. 用系统录音机确认麦克风正常;
  2. 看 stats().audioDurationMs 是否在增长,不增长说明没采到音频;
  3. 模型文件是否完整;
  4. 换真机、调大 numThreads 再试。

Q9:识别慢 / 手机发烫

  • numThreads 降到 1;
  • 关闭标点(enablePunctuation: false);
  • 缩短录音时长,用 VAD 自动切句;
  • 避免多个页面同时持有引擎。

Q10:iOS 上为什么不可用

当前版本 iOS 原生尚未移植,调用会抛“iOS 端原生实现暂未提供”。业务侧可用 try/catch 降级到在线录音或键盘输入。


九、性能建议

场景 建议参数
中低端机 numThreads: 1,vadSilenceMs: 800
主流机型实时字幕 numThreads: 2,vadSilenceMs: 700
短指令(< 10 秒) 可不开标点,降低首字延迟
长录音 用 getRealtimeText() 展示,stop() 拿完整文本

stats().rtf 小于 1 表示推理速度已快于实时;大于 1 时会明显卡顿,应降低线程数或换更小的模型。


十、已知限制

  1. 仅 Android 原生可用;iOS / H5 / 小程序 / HarmonyOS 为占位实现。
  2. 必须自定义基座或正式打包,且必须真机验证。
  3. 当前仅支持 Zipformer2 CTC 流式模型;modelType 的其它取值暂未实现。
  4. enableVad 目前不影响行为,断句由端点规则(vadSilenceMs)控制;static/models/vad.onnx 暂未被加载。
  5. 包内不含标点模型,需要时按上文自行下载;未放置时即使传 enablePunctuation: true 也会静默跳过。
  6. decodeOnce() 为预留接口,当前返回空结果。
  7. 录音格式固定为 16 kHz / 单声道 / 16 bit PCM。
  8. initDefaultModels() 首次调用会复制约 26 MB 文件,建议放在启动页或首次进入语音页时展示 loading。

十一、许可

插件自身代码以 MIT 发布。核心引擎 sherpa-onnx(Apache-2.0)、ONNX Runtime(MIT)、Silero VAD(MIT)等第三方组件的许可说明见 license.md。使用与分发时请遵守各依赖的原始许可。

隐私、权限声明

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

android.permission.RECORD_AUDIO(录音权限) 用途:采集用户语音并在设备本地离线识别。 触发时机:仅在用户主动点击“开始识别”后启用,停止识别或退出页面即释放麦克风。 申请方式:Android 6.0 及以上通过插件提供的 requestPermission() 运行时动态申请。 除上述权限外,本插件不申请任何其他系统权限。

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

本插件不采集、不存储、不上传任何数据。 1. 语音音频仅在设备内存中实时送入本地 sherpa-onnx 模型推理,不写入文件、不对外发送; 2. 识别文本仅返回给调用方页面,由业务自行决定如何使用与保存; 3. 插件不包含任何服务器地址、不发起任何网络请求,不集成统计、埋点或第三方 SDK; 4. 不读取通讯录、相册、位置、设备标识等任何其他个人信息。

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

本插件不包含任何广告,也不集成任何广告 SDK,无开屏、插屏、激励视频或 banner 等广告形式。

暂无用户评论。