更新记录

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。
  • 中文简繁控制:simplifiedtraditionalauto

当前的 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.cppggml 模型文件,也就是 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 的多语言模型。

文件路径

modelPathaudioPath 都支持项目资源相对路径和设备本地文件路径。路径可以直接传普通路径,也可以传 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:默认识别语言,例如 zhenauto
  • 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 或 Android content:// URI。
  • language:语言代码,例如 zhenauto
  • prompt:提示词,可用于提示输出风格。
  • timestamps:是否输出分段时间戳。
  • outputScript:中文简繁控制。

onProgressonSegment 是类型定义中保留的回调字段。当前 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
  • vadvadThreshold:类型中预留的 VAD 参数,具体效果以当前原生实现为准。
  • getRealtimeStateJson():返回当前实时状态 JSON 字符串,包含 latestPartial.textvolumeerror 等字段。云打包建议用这个接口轮询,避免不同端桥接对象序列化差异。

onPartialonSegmentonVolumeonError 是类型定义中保留的回调字段。当前推荐使用 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 真流式解码,所以中间结果可能重复或被修正。

隐私、权限声明

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

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

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

暂无用户评论。