更新记录
1.0.0(2026-05-17)
- 基于 whisper.cpp 实现 Android、iOS 离线语音识别。
- 支持音频文件转录、录音式准实时转录和最终整段转录。
- 支持模型初始化、导入、安装、删除、校验和运行时信息查询。
- 支持转录结果导出为 Text、SRT、VTT、JSON。
- 增加 getRealtimeStateJson,用于跨端稳定轮询实时识别状态。
平台兼容性
uni-app x(5.07)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | 6.0 | 13 | - | - |
em-whisper 使用文档
什么是 Whisper
Whisper 是 OpenAI 发布的语音识别模型,可以把音频中的语音转成文字,支持多语言识别、时间戳片段和中文等常见语言场景。它适合用在语音输入、录音转写、字幕生成、会议纪要等需要自动语音识别的功能中。
whisper.cpp 是 Whisper 的本地推理实现,可以在移动端直接加载 ggml 模型文件运行识别,不需要把音频上传到服务器。
em-whisper 是基于 whisper.cpp 封装的 UTS 离线语音识别插件,支持 uni-app x 在 Android 和 iOS 端进行本地语音转文字。
功能
- 离线音频文件转录。
- 滑动窗口准实时转录:录音中持续输出最近窗口的临时识别结果,停止后返回整段最终结果。
- 模型初始化、导入、删除、校验和查询。
- 音频信息读取和音频转 WAV。
- 转录结果导出为纯文本、SRT、VTT、JSON。
- 中文简繁控制:
simplified、traditional、auto。
当前的 startRealtime() / stopRealtime() 采用滑动窗口切片准实时方案:录音中每隔 stepMs 转录最近 windowMs 的音频并更新 getRealtimeStateJson() 状态,停止后再对整段录音做最终转录。
模型文件
Whisper 模型文件不内置在插件里。建议放在 uni-app 项目根目录的资源目录:
assets/models/ggml-tiny.bin
assets/models/ggml-base.bin
初始化时传相对路径:
await initialize({
modelPath: 'assets/models/ggml-tiny.bin',
language: 'zh'
})
也可以传设备上的绝对路径。Android 会尝试从应用 assets 中复制模型到 app files 后加载;iOS 会从 App Bundle 中解析模型路径。
模型下载地址
本插件使用 whisper.cpp 的 ggml 模型文件,也就是 ggml-*.bin。OpenAI Python 版 Whisper 的 .pt 模型不能直接作为本插件的 modelPath 使用。
官方模型仓库:
https://huggingface.co/ggerganov/whisper.cpp/tree/main
官方下载规则:
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-{model}.bin
常用多语言模型:
| 模型 | 文件名 | 下载地址 |
|---|---|---|
| tiny | ggml-tiny.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin |
| base | ggml-base.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin |
| small | ggml-small.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin |
| medium | ggml-medium.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.bin |
| large-v3-turbo | ggml-large-v3-turbo.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin |
| large-v3 | ggml-large-v3.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin |
移动端优先尝试较小模型或量化模型:
| 模型 | 文件名 | 下载地址 |
|---|---|---|
| tiny-q5_1 | ggml-tiny-q5_1.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q5_1.bin |
| base-q5_1 | ggml-base-q5_1.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base-q5_1.bin |
| small-q5_1 | ggml-small-q5_1.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small-q5_1.bin |
| medium-q5_0 | ggml-medium-q5_0.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium-q5_0.bin |
| large-v3-turbo-q5_0 | ggml-large-v3-turbo-q5_0.bin |
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q5_0.bin |
英文专用模型文件名带 .en,例如:
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en.bin
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.en.bin
如果只做中文识别,优先使用不带 .en 的多语言模型。
文件路径
modelPath 和 audioPath 都支持项目资源相对路径和设备本地文件路径。路径可以直接传普通路径,也可以传 file:// 开头的本地文件 URL。
模型路径示例:
await initialize({
modelPath: 'assets/models/ggml-tiny.bin'
})
await initialize({
modelPath: '/var/mobile/Containers/Data/Application/<app>/Documents/ggml-tiny.bin'
})
await initialize({
modelPath: 'file:///var/mobile/Containers/Data/Application/<app>/Documents/ggml-tiny.bin'
})
音频路径示例:
await transcribeFile({
audioPath: 'assets/mp3/hh.mp3'
})
await transcribeFile({
audioPath: 'assets/audio/record.m4a'
})
await transcribeFile({
audioPath: '/storage/emulated/0/Android/data/<package>/files/record.wav'
})
await transcribeFile({
audioPath: 'file:///storage/emulated/0/Android/data/<package>/files/record.wav'
})
内置资源建议放在这些目录:
assets/models/ggml-tiny.bin
assets/mp3/demo.mp3
assets/audio/demo.m4a
assets/wav/demo.wav
文件转录不支持直接传网络 URL,例如 https://.../demo.mp3。需要先下载到本地文件,再把本地路径传给 transcribeFile()。Android 的 content://... URI 也不要直接传入,建议先复制成应用可读的本地文件路径。
transcribeFile() 只能处理平台可解码的音频文件。普通文档、无音轨视频、损坏音频或平台解码器不支持的格式不会被识别为可转录音频。
基本使用
先引入插件:
import {
cancel,
convertAudioToWav,
deleteModel,
exportJson,
exportSrt,
exportText,
exportVtt,
getAudioInfo,
getModel,
getRealtimeState,
getRealtimeStateJson,
getRuntimeInfo,
importModel,
initialize,
installModel,
isModelLoaded,
listModels,
pauseRealtime,
release,
resumeRealtime,
startRealtime,
stopRealtime,
transcribeFile,
verifyModel
} from '@/uni_modules/em-whisper'
初始化模型:
await initialize({
modelPath: 'assets/models/ggml-tiny.bin',
language: 'zh',
nThreads: 4,
useGpu: true,
useCoreML: false,
useVulkan: true,
coreMLFallbackToCpu: true
})
转录音频文件:
const result = await transcribeFile({
audioPath: 'assets/mp3/hh.mp3',
language: 'zh',
timestamps: true,
outputScript: 'simplified'
})
console.log(result.text)
console.log(result.segments)
页面销毁或不再使用时释放模型:
await release()
初始化参数
type WhisperInitOptions = {
modelPath: string
language?: string
translate?: boolean
nThreads?: number
useGpu?: boolean
useCoreML?: boolean
useVulkan?: boolean
coreMLFallbackToCpu?: boolean
maxContextTokens?: number
prompt?: string
}
常用参数:
modelPath:模型路径,支持项目资源相对路径或设备绝对路径。language:默认识别语言,例如zh、en、auto。nThreads:CPU 线程数,默认使用设备可用核心数并限制在合理范围内。useGpu:GPU 推理开关;iOS 对应 Metal,Android 通常配合useVulkan。useCoreML:iOS CoreML 编码器开关,需要配套*.mlmodelc。useVulkan:Android Vulkan 后端开关。coreMLFallbackToCpu:CoreML 初始化失败时是否回退 CPU。prompt:初始化级提示词,后续转录也可以单独传prompt覆盖。
初始化结果:
type WhisperInitResult = {
modelPath: string
modelName: string
modelSize: number
backend: 'cpu' | 'coreml' | 'metal' | 'vulkan'
language: string
error?: string
}
中文简繁控制
language: 'zh' 只表示中文,不保证简体或繁体。推荐在中文转录时传 outputScript:
await transcribeFile({
audioPath: 'assets/mp3/hh.mp3',
language: 'zh',
outputScript: 'simplified'
})
可选值:
outputScript: 'simplified' // 输出简体
outputScript: 'traditional' // 输出繁体
outputScript: 'auto' // 不做简繁转换,保留模型原始输出
如果 language 或模型识别结果是中文,并且没有显式设置 outputScript,插件会默认转成简体。
文件转录参数
type WhisperTranscribeFileOptions = {
audioPath: string
language?: string
translate?: boolean
prompt?: string
timestamps?: boolean
splitOnWord?: boolean
maxSegmentLengthMs?: number
temperature?: number
beamSize?: number
bestOf?: number
noSpeechThreshold?: number
outputScript?: 'auto' | 'simplified' | 'traditional'
?: (event: WhisperProgressEvent) => void
onSegment?: (segment: WhisperSegment) => void
}
常用参数:
audioPath:音频文件路径,支持项目资源相对路径、设备本地绝对路径和file://本地文件 URL;不支持直接传网络 URL 或 Androidcontent://URI。language:语言代码,例如zh、en、auto。prompt:提示词,可用于提示输出风格。timestamps:是否输出分段时间戳。outputScript:中文简繁控制。
onProgress、onSegment 是类型定义中保留的回调字段。当前 Android/iOS 原生桥接主要通过 Promise 返回最终结果,云打包场景不要依赖这两个回调作为主流程。
返回结果结构:
type WhisperToken = {
text: string
startMs?: number
endMs?: number
probability?: number
}
type WhisperSegment = {
id: number
text: string
startMs: number
endMs: number
confidence?: number
tokens?: WhisperToken[]
}
type WhisperResult = {
text: string
segments: WhisperSegment[]
language?: string
durationMs: number
processingMs: number
backend: 'cpu' | 'coreml' | 'metal' | 'vulkan'
modelPath: string
cancelled: boolean
error?: string
}
录音式实时转录
当前实时转录是滑动窗口准实时方案:录音过程中按 stepMs 间隔转录最近 windowMs 的音频窗口;调用 stopRealtime() 后会对整段录音再做一次最终转录。
开始录音:
await startRealtime({
language: 'zh',
sampleRate: 16000,
channels: 1,
stepMs: 1000,
windowMs: 5000,
keepContext: true,
outputScript: 'simplified'
})
const timer = setInterval(() => {
const state = JSON.parse(getRealtimeStateJson())
console.log(state.latestPartial.text, state.volume)
}, 500)
实时转录参数:
type WhisperRealtimeOptions = {
language?: string
translate?: boolean
sampleRate?: number
channels?: number
vad?: boolean
vadThreshold?: number
windowMs?: number
stepMs?: number
keepContext?: boolean
prompt?: string
outputScript?: 'auto' | 'simplified' | 'traditional'
onPartial?: (event: WhisperPartialEvent) => void
onSegment?: (segment: WhisperSegment) => void
onVolume?: (volume: number) => void
onError?: (error: WhisperError) => void
}
stepMs:切片转录间隔,默认1000。手机端建议1000-1500。windowMs:每次转录最近多少毫秒音频,默认5000。keepContext:为true时,把上一次窗口文本作为prompt追加给下一次窗口,帮助连续性。sampleRate:录音采样率,默认按平台录音器处理,建议使用16000。channels:录音声道数,通常使用1。vad、vadThreshold:类型中预留的 VAD 参数,具体效果以当前原生实现为准。getRealtimeStateJson():返回当前实时状态 JSON 字符串,包含latestPartial.text、volume、error等字段。云打包建议用这个接口轮询,避免不同端桥接对象序列化差异。
onPartial、onSegment、onVolume、onError 是类型定义中保留的回调字段。当前推荐使用 getRealtimeStateJson() 轮询实时状态。
实时状态结构:
type WhisperRealtimeState = {
running: boolean
sequence: number
updatedAt: number
windowStartMs?: number
windowDurationMs?: number
volume: number
latestPartial: {
text: string
stableText?: string
unstableText?: string
segments: WhisperSegment[]
}
error?: string
}
getRealtimeState() 也会返回实时状态对象,但不同端桥接对象序列化表现可能不同。跨端和云打包优先使用 getRealtimeStateJson()。
停止录音并转录:
clearInterval(timer)
const result = await stopRealtime()
console.log(result.text)
暂停和恢复录音:
await pauseRealtime()
await resumeRealtime()
取消当前任务:
await cancel()
模型管理
查询运行时信息:
const runtime = getRuntimeInfo()
console.log(runtime)
判断模型是否已加载:
const loaded = isModelLoaded()
列出已安装模型:
const models = await listModels()
查询单个模型:
const model = await getModel('base')
从 URL 安装模型:
const model = await installModel({
id: 'base',
url: 'https://example.com/ggml-base.bin',
sha256: '<sha256>',
fileName: 'ggml-base.bin',
overwrite: false
})
安装参数:
type WhisperDownloadModelOptions = {
id: string
url: string
sha256?: string
fileName?: string
overwrite?: boolean
?: (event: WhisperDownloadProgressEvent) => void
}
installModel() 会把模型下载到插件管理目录并写入模型元数据;onProgress 是类型定义中保留的回调字段,当前不要依赖它作为下载进度主流程。
导入本地模型:
const model = await importModel('/path/to/ggml-base.bin', 'base')
删除模型:
await deleteModel('base')
校验模型:
const ok = await verifyModel('/path/to/ggml-base.bin', '<sha256>')
音频工具
获取音频信息:
const info = await getAudioInfo('assets/mp3/hh.mp3')
转换音频为 WAV:
const wavPath = await convertAudioToWav('assets/mp3/hh.mp3')
导出结果
const text = exportText(result)
const srt = exportSrt(result)
const vtt = exportVtt(result)
const json = exportJson(result)
常见问题
为什么中文输出是繁体?
Whisper 的 zh 不区分简体和繁体。传入:
outputScript: 'simplified'
即可输出简体。
为什么第一次初始化较慢?
首次加载模型和初始化 Metal/CoreML 后端会有开销。模型越大,加载越慢。
没有 CoreML 模型会报错吗?
不会。没有 *-encoder.mlmodelc 时会跳过 CoreML 编码器,继续使用可用后端。
.pcm does not exist 是什么?
这是 Xcode 调试第三方 framework 时缺失预编译模块调试信息的警告,通常只影响调试类型信息,不影响插件转录功能。
支持真正边说边出字吗?
当前版本支持滑动窗口准实时:startRealtime() 录音中会持续输出最近窗口的临时识别结果,stopRealtime() 停止后返回整段最终结果。它不是 whisper.cpp 真流式解码,所以中间结果可能重复或被修正。

收藏人数:
购买源码授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 400
赞赏 0
下载 11940382
赞赏 1914
赞赏
京公网安备:11010802035340号