更新记录
1.0.0(2026-10-22)
1.0.0(2026-10-22)首个版本:
- 基于 sherpa-onnx 1.13.8 的 Android 离线流式语音识别;
- 实时文本 getRealtimeText,配合 150ms 轮询实现实时字幕;
- 端点规则自动断句,vadSilenceMs 可调;
- 内置 26MB 中文 int8 模型,首次运行自动释放到应用私有目录;
- 兼容 HBuilderX 4.25+ 的回调回收机制(onPartial/onFinal/onError);
- 提供可选 JS 适配层与模型下载脚本;
- 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:报“模型文件不存在”
- 确认调用了
initDefaultModels()并使用返回值; - 确认
static/models/zipformer-zh-int8/下文件完整; - 模型被
.gitignore/.npmignore排除过会导致漏打包。
Q6:报“麦克风权限未授权”
Android 6.0+ 需要运行时权限:await requestPermission(),用户拒绝后引导去系统设置开启。
Q7:报“AudioRecord 初始化失败,可能被其他应用占用麦克风”
同一时间只能有一个录音通道。先停止其他录音(系统录音机、通话、其他语音 SDK),或先 destroy() 旧引擎。
Q8:识别结果为空
- 用系统录音机确认麦克风正常;
- 看
stats().audioDurationMs是否在增长,不增长说明没采到音频; - 模型文件是否完整;
- 换真机、调大
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 时会明显卡顿,应降低线程数或换更小的模型。
十、已知限制
- 仅 Android 原生可用;iOS / H5 / 小程序 / HarmonyOS 为占位实现。
- 必须自定义基座或正式打包,且必须真机验证。
- 当前仅支持 Zipformer2 CTC 流式模型;
modelType的其它取值暂未实现。 enableVad目前不影响行为,断句由端点规则(vadSilenceMs)控制;static/models/vad.onnx暂未被加载。- 包内不含标点模型,需要时按上文自行下载;未放置时即使传
enablePunctuation: true也会静默跳过。 decodeOnce()为预留接口,当前返回空结果。- 录音格式固定为 16 kHz / 单声道 / 16 bit PCM。
initDefaultModels()首次调用会复制约 26 MB 文件,建议放在启动页或首次进入语音页时展示 loading。
十一、许可
插件自身代码以 MIT 发布。核心引擎 sherpa-onnx(Apache-2.0)、ONNX Runtime(MIT)、Silero VAD(MIT)等第三方组件的许可说明见 license.md。使用与分发时请遵守各依赖的原始许可。

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 0
赞赏 0
下载 12703700
赞赏 1957
赞赏
京公网安备:11010802035340号