更新记录

1.1.0(2026-08-11)

  • 修复在鸿蒙4.x系统中播放无声的问题

1.0.9(2026-06-11)

  • 修复安卓端在uniapp项目上云打包报错的问题

1.0.8(2026-06-06)

  • feat-重要更新:新增matchaIcefall模型,主打快速起播,过程流畅,温柔女生音色
查看更多

平台兼容性

uni-app(5.07)

Vue2 Vue2插件版本 Vue3 Vue3插件版本 Chrome Safari app-vue app-nvue Android Android插件版本 iOS iOS插件版本 鸿蒙 鸿蒙插件版本
√ 1.0.0 √ 1.0.0 - - - - 5.0 1.0.0 13 1.0.5 19 1.0.1
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(5.07)

Chrome Safari Android Android插件版本 iOS iOS插件版本 鸿蒙 鸿蒙插件版本 微信小程序
- - 5.0 1.0.0 13 1.0.5 19 1.0.1 -

xwq-sherpa-onnx-tts

xwq-sherpa-onnx-tts 是一个基于 sherpa-onnx 的离线 TTS 播报插件。

推荐优先使用 matchaIcefall 模型。当前项目实测它的播报速度非常快,首包响应更积极,适合作为默认首选模型。

matchaIcefall 模型初始化需要下载两个模型文件搭配使用:

第一个模型下载:
https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/matcha-icefall-zh-en.tar.bz2

解压后目录如下:
-rw-r--r--@   1 fangjun  staff    58K  4 Dec 14:29 date-zh.fst
drwxr-xr-x@ 122 fangjun  staff   3.8K 28 Nov  2023 espeak-ng-data
-rw-r--r--@   1 fangjun  staff   1.3M  4 Dec 14:29 lexicon.txt
-rw-r--r--@   1 fangjun  staff    72M  4 Dec 14:29 model-steps-3.onnx
-rw-r--r--@   1 fangjun  staff    63K  4 Dec 14:29 number-zh.fst
-rw-r--r--@   1 fangjun  staff    87K  4 Dec 14:29 phone-zh.fst
-rw-r--r--@   1 fangjun  staff   2.0K  4 Dec 14:29 README.md
-rw-r--r--@   1 fangjun  staff    21K  4 Dec 14:29 tokens.txt

第二个模型下载:
https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos-16khz-univ.onnx

解压后目录如下:
-rw-r--r--@ 1 fangjun  staff    51M  4 Dec 14:54 vocos-16khz-univ.onnx

插件能力概览

  • 支持基于本地 onnx 模型的离线文本转语音播报
  • 支持初始化模型、开始播报、暂停、继续、停止
  • 支持自定义 sid 说话人和 speed 语速
  • 支持传入 number.fst、phone.fst、date.fst 文本归一化规则文件
  • finished 回调在当前实现中会尽量等待 AudioTrack 实际播放完成后再触发

对外暴露的方法

插件推荐通过下面方式引入:

import {
  initSherpaOnxxTts,
  start,
  pause,
  resume,
  stop
} from "@/uni_modules/xwq-sherpa-onnx-tts"

initSherpaOnxxTts(opt)

初始化 TTS 引擎和模型资源。

  • 入参:initTTsOptions
  • 返回:无
  • 说明:建议页面加载后先调用一次,初始化成功后再调用 start

start(opt)

开始播报文本内容。

  • 入参:PlayParams
  • 返回:无
  • 说明:当前实现会把文本送入离线模型生成语音,并自动写入音频播放器

pause()

暂停当前播报。

  • 入参:无
  • 返回:无

resume()

继续当前播报。

  • 入参:无
  • 返回:无

stop()

停止当前播报。

  • 入参:无
  • 返回:无

回调与类型说明

FailCallBack

type FailCallBack = {
  msg: string
  code: number
}

字段说明:

  • msg:错误信息
  • code:错误码,便于业务侧区分初始化失败、播放失败等场景

PlayParams

type PlayParams = {
  text: string
  fail?: ((val: FailCallBack) => void) | null
  finished?: (() => void) | null
}

字段说明:

  • text:需要播报的文本内容
  • fail:播报失败时触发
  • finished:当前播报完成时触发

initTTsOptions 参数表

字段名 类型 必填 说明
model string 是 onnx 模型文件路径,例如 model.onnx
voices string | null 否 音色或附加模型文件路径。vitsMelo 下可传 null;matchaIcefall 下这里需要传 vocoder 路径,例如 vocos-16khz-univ.onnx
tokens string 是 词表文件路径,例如 tokens.txt
dataDir string | null 否 发音引擎数据目录。当前 Android 的 VitsMelo 实现未使用该字段,保留接口兼容
numberFst string | null 否 数字文本归一化规则文件路径,例如 number.fst
phoneFst string | null 否 电话号码文本归一化规则文件路径,例如 phone.fst
dateFst string | null 否 日期文本归一化规则文件路径,例如 date.fst
dictDir string | null 否 分词字典目录。当前 Android 的 VitsMelo 实现未使用该字段,保留接口兼容
lexicon string 是 词典文件路径,用于修正特定词汇的发音
content string | null 否 预留字段,当前初始化流程未使用。实际播报文本请通过 start({ text }) 传入
sid number | null 否 说话人 ID。未传时默认使用 0
speed number | null 否 语速。未传时默认使用 1.0
fail ((val: FailCallBack) => void) | null 否 初始化失败回调
success (() => void) | null 否 初始化成功回调

对应类型定义如下:

type initTTsOptions = {
  model: string
  voices?: string | null
  tokens: string
  dataDir?: string | null
  numberFst?: string | null
  phoneFst?: string | null
  dateFst?: string | null
  dictDir?: string | null
  lexicon: string
  content?: string | null
  sid?: number | null
  speed?: number | null
  fail?: ((val: FailCallBack) => void) | null
  success?: (() => void) | null
}

模型参数对照表

vitsMelo

字段 是否必填 说明 当前示例值
modelType 是 固定传 vitsMelo vitsMelo
model 是 声学模型文件 model.onnx
voices 否 当前模型未使用,可传 null null
tokens 是 词表文件 tokens.txt
lexicon 是 发音词典 lexicon.txt
numberFst 否 数字归一化规则 number.fst
phoneFst 否 电话归一化规则 phone.fst
dateFst 否 日期归一化规则 date.fst
dataDir 否 当前 Android 示例未使用,可传 null 或空字符串 null
dictDir 否 当前 Android 示例未使用,可传 null 或空字符串 null
sid 否 说话人 ID 0
speed 否 语速 1.0

建议目录结构:

static/
  vits-melo-tts-zh_en/
    model.onnx
    tokens.txt
    lexicon.txt
    number.fst
    phone.fst
    date.fst

matchaIcefall

字段 是否必填 说明 当前示例值
modelType 是 固定传 matchaIcefall matchaIcefall
model 是 声学模型文件 model-steps-3.onnx
voices 是 这里不是 voices.bin,而是 vocoder 模型文件 vocos-16khz-univ.onnx
tokens 是 词表文件 tokens.txt
lexicon 是 发音词典 lexicon.txt
numberFst 否 数字归一化规则 number-zh.fst
phoneFst 否 电话归一化规则 phone-zh.fst
dateFst 否 日期归一化规则 date-zh.fst
dataDir 建议传 espeak-ng-data 目录,用于中英混合文本发音支持 espeak-ng-data/
dictDir 否 当前示例未使用,可传空字符串 ""
sid 否 当前模型一般传 0 0
speed 否 语速 1.0

建议目录结构:

static/
  matcha-icefall-zh-en/
    model-steps-3.onnx
    vocos-16khz-univ.onnx
    tokens.txt
    lexicon.txt
    number-zh.fst
    phone-zh.fst
    date-zh.fst
    espeak-ng-data/

补充说明:

  • matchaIcefall 至少需要两份 onnx:model 和 voices(vocoder)
  • iOS 侧接入 matchaIcefall 时,voices 传入的 vocoder 文件名应和真实输出采样率保持一致,例如 vocos-16khz-univ.onnx 对应 16kHz
  • 如果 vocoder 资源和真实采样率不匹配,iOS 播放时可能出现“像快放、音调变高”的现象;排查时优先核对 model 与 vocoder 是否来自同一套模型资源
  • 当前插件会优先根据 vocoder 文件名中的 16khz、24khz、22050 等关键字推断 iOS 播放采样率,重命名模型文件时请保留这类采样率信息
  • vitsMelo 当前只需要一份主模型 model.onnx
  • 如果业务层想做模型切换,最少要同步切换 modelType、model、voices 和对应规则文件路径

接入步骤

1. 准备模型资源

建议把模型目录放到项目 static 下。默认推荐先接入 matchaIcefall,例如:

static/
  matcha-icefall-zh-en/
    model-steps-3.onnx
    vocos-16khz-univ.onnx
    tokens.txt
    lexicon.txt
    number-zh.fst
    phone-zh.fst
    date-zh.fst
    espeak-ng-data/

如果你想保留另一套中英双语模型做对比,也可以继续接入 vits-melo-tts-zh_en。

2. 页面中初始化插件

先解析静态资源目录,再调用 initSherpaOnxxTts。

  • vitsMelo:modelType 传 vitsMelo,voices 可传 null
  • matchaIcefall:modelType 传 matchaIcefall,model 传声学模型,voices 传 vocoder 模型
  • Android / iOS 端通常需要先通过 plus.io.convertLocalFileSystemURL('/static/...') 把静态资源目录转换成应用内可访问的绝对路径,再把该路径拼接给 model、tokens、lexicon 等字段
  • 鸿蒙端模型资源通常放在插件 resources/rawfile 目录下,因此页面层直接传相对目录即可,例如 vits-melo-tts-zh_en/model.onnx 或 matcha-icefall-zh-en/model-steps-3.onnx
  • 当前鸿蒙实现已经验证可直接把这些 rawfile 相对路径传给 sherpa-onnx,不再额外复制到应用沙箱
  • 如果页面层要做跨平台模型切换,建议把“资源根目录解析”单独封装:Android / iOS 返回本地绝对路径,鸿蒙返回 rawfile 相对目录

3. 业务触发播报

通过 start({ text }) 播放文本;如果有播放控制需求,可接入 pause、resume、stop。

模型下载地址

vits模型(支持中英文):https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-melo-tts-zh_en.tar.bz2

推荐模型说明

  • 强烈推荐优先使用 matchaIcefall 模型,尤其适合对首包速度、整体播报速度和交互响应时间有要求的业务场景
  • matchaIcefall 在当前项目中的实际体验非常快,初始化后播报启动更积极,适合实时播报、消息通知、短文本连续朗读等场景
  • 如果你的业务重点是“尽快出声”,matchaIcefall 应该作为首选模型
  • vitsMelo 仍然可以继续使用,适合做兼容对比或保留另一套中英双语模型方案,但默认推荐顺序应优先考虑 matchaIcefall

TTS说明

  • 模型下载地址:TTS模型下载
  • 强烈推荐使用 matchaIcefall 模型,速度非常快,适合作为默认首选模型
  • vits-melo-tts-zh_en 模型同样支持中英双语,可作为备用模型或效果对比模型
  • 暂停/恢复功能依赖于底层模型实现,部分模型可能不支持真正的暂停

uni-app x 页面用例

vitsMelo 页面调用示例

<template>
  <view class="page">
    <button @click="initTTs">初始化TTS</button>
    <button @click="playTts">开始播报</button>
    <button @click="pauseTts">暂停</button>
    <button @click="resumeTts">继续</button>
    <button @click="stopTts">停止</button>
  </view>
</template>

<script setup>
import {
  initSherpaOnxxTts,
  start,
  pause,
  resume,
  stop
} from "@/uni_modules/xwq-sherpa-onnx-tts"

const content = ref("清晨推开窗,一股清冽的寒气扑面而来")

const initTTs = () => {
  const path = "static/vits-melo-tts-zh_en"
  const staticPath = UTSAndroid.getResourcePath(path)
  if (staticPath == null || staticPath == "") {
    console.log("VITS 静态资源路径解析失败")
    return
  }

  initSherpaOnxxTts({
    modelType: "vitsMelo",
    model: staticPath + "/model.onnx",
    voices: null,
    tokens: staticPath + "/tokens.txt",
    dataDir: null,
    lexicon: staticPath + "/lexicon.txt",
    numberFst: staticPath + "/number.fst",
    phoneFst: staticPath + "/phone.fst",
    dateFst: staticPath + "/date.fst",
    sid: 0,
    speed: 1.0,
    success: () => {
      console.log("初始化成功")
    },
    fail: (result) => {
      console.log("初始化失败", result)
    }
  })
}

const playTts = () => {
  start({
    text: content.value,
    finished: () => {
      console.log("播放完成")
    },
    fail: (result) => {
      console.log("播放失败", result)
    }
  })
}

const pauseTts = () => {
  pause()
}

const resumeTts = () => {
  resume()
}

const stopTts = () => {
  stop()
}
</script>

<style>
.page {
  display: flex;
  flex-direction: column;
  padding: 24px;
  gap: 12px;
}
</style>

matchaIcefall 页面调用示例

<template>
  <view class="page">
    <button @click="initTTs">初始化TTS</button>
    <button @click="playTts">开始播报</button>
    <button @click="pauseTts">暂停</button>
    <button @click="resumeTts">继续</button>
    <button @click="stopTts">停止</button>
  </view>
</template>

<script setup>
import { ref } from "vue"
import {
  initSherpaOnxxTts,
  start,
  pause,
  resume,
  stop
} from "@/uni_modules/xwq-sherpa-onnx-tts"

const content = ref("中英文合成测试。Today is a bright day, welcome to try the matcha icefall zh en model.")

const initTTs = () => {
  const path = "static/matcha-icefall-zh-en"
  const staticPath = UTSAndroid.getResourcePath(path)
  if (staticPath == null || staticPath == "") {
    console.log("matcha 静态资源路径解析失败")
    return
  }

  initSherpaOnxxTts({
    modelType: "matchaIcefall",
    model: staticPath + "/model-steps-3.onnx",
    voices: staticPath + "/vocos-16khz-univ.onnx",
    tokens: staticPath + "/tokens.txt",
    dataDir: staticPath + "/espeak-ng-data",
    dictDir: "",
    lexicon: staticPath + "/lexicon.txt",
    numberFst: staticPath + "/number-zh.fst",
    phoneFst: staticPath + "/phone-zh.fst",
    dateFst: staticPath + "/date-zh.fst",
    sid: 0,
    speed: 1.0,
    success: () => {
      console.log("matcha 初始化成功")
    },
    fail: (result) => {
      console.log("matcha 初始化失败", result)
    }
  })
}

const playTts = () => {
  start({
    text: content.value,
    finished: () => {
      console.log("播放完成")
    },
    fail: (result) => {
      console.log("播放失败", result)
    }
  })
}

const pauseTts = () => {
  pause()
}

const resumeTts = () => {
  resume()
}

const stopTts = () => {
  stop()
}
</script>

<style>
.page {
  display: flex;
  flex-direction: column;
  padding: 24px;
  gap: 12px;
}
</style>

uni-app 页面用例

如果是传统 uni-app 页面,静态资源路径通常可以通过 plus.io.convertLocalFileSystemURL 获取。

vitsMelo 页面调用示例

<template>
  <view class="page">
    <button @click="initTTs">初始化TTS</button>
    <button @click="playTts">开始播报</button>
    <button @click="pauseTts">暂停</button>
    <button @click="resumeTts">继续</button>
    <button @click="stopTts">停止</button>
  </view>
</template>

<script>
import {
  initSherpaOnxxTts,
  start,
  pause,
  resume,
  stop
} from "@/uni_modules/xwq-sherpa-onnx-tts"

export default {
  data() {
    return {
      content: "清晨推开窗,一股清冽的寒气扑面而来"
    }
  },
  methods: {
    initTTs() {
      const path = "/static/vits-melo-tts-zh_en"
      const staticPath = plus.io.convertLocalFileSystemURL(path)
      if (!staticPath) {
        console.log("VITS 静态资源路径解析失败")
        return
      }

      initSherpaOnxxTts({
        modelType: "vitsMelo",
        model: staticPath + "/model.onnx",
        voices: null,
        tokens: staticPath + "/tokens.txt",
        dataDir: null,
        lexicon: staticPath + "/lexicon.txt",
        numberFst: staticPath + "/number.fst",
        phoneFst: staticPath + "/phone.fst",
        dateFst: staticPath + "/date.fst",
        sid: 0,
        speed: 1.0,
        success: () => {
          console.log("初始化成功")
        },
        fail: (result) => {
          console.log("初始化失败", result)
        }
      })
    },
    playTts() {
      start({
        text: this.content,
        finished: () => {
          console.log("播放完成")
        },
        fail: (result) => {
          console.log("播放失败", result)
        }
      })
    },
    pauseTts() {
      pause()
    },
    resumeTts() {
      resume()
    },
    stopTts() {
      stop()
    }
  }
}
</script>

<style>
.page {
  padding: 24rpx;
}
</style>

matchaIcefall 页面调用示例

<template>
  <view class="page">
    <button @click="initTTs">初始化TTS</button>
    <button @click="playTts">开始播报</button>
    <button @click="pauseTts">暂停</button>
    <button @click="resumeTts">继续</button>
    <button @click="stopTts">停止</button>
  </view>
</template>

<script>
import {
  initSherpaOnxxTts,
  start,
  pause,
  resume,
  stop
} from "@/uni_modules/xwq-sherpa-onnx-tts"

export default {
  data() {
    return {
      content: "中英文合成测试。Today is a bright day, welcome to try the matcha icefall zh en model."
    }
  },
  methods: {
    initTTs() {
      const path = "/static/matcha-icefall-zh-en"
      const staticPath = plus.io.convertLocalFileSystemURL(path)
      if (!staticPath) {
        console.log("matcha 静态资源路径解析失败")
        return
      }

      initSherpaOnxxTts({
        modelType: "matchaIcefall",
        model: staticPath + "/model-steps-3.onnx",
        voices: staticPath + "/vocos-16khz-univ.onnx",
        tokens: staticPath + "/tokens.txt",
        dataDir: staticPath + "/espeak-ng-data",
        dictDir: "",
        lexicon: staticPath + "/lexicon.txt",
        numberFst: staticPath + "/number-zh.fst",
        phoneFst: staticPath + "/phone-zh.fst",
        dateFst: staticPath + "/date-zh.fst",
        sid: 0,
        speed: 1.0,
        success: () => {
          console.log("matcha 初始化成功")
        },
        fail: (result) => {
          console.log("matcha 初始化失败", result)
        }
      })
    },
    playTts() {
      start({
        text: this.content,
        finished: () => {
          console.log("播放完成")
        },
        fail: (result) => {
          console.log("播放失败", result)
        }
      })
    },
    pauseTts() {
      pause()
    },
    resumeTts() {
      resume()
    },
    stopTts() {
      stop()
    }
  }
}
</script>

<style>
.page {
  padding: 24rpx;
}
</style>

鸿蒙端 页面用例

使用前注意

 * 鸿蒙端使用sherpa_onnx语音识别特别说明: 
 * 将路径:unpackage\dist\dev\app-harmony\entry\build-profile.json5文件拷贝到根目录harmony-configs/entry下(目录没有的需要手动创建)
 * 
 * 在harmony-configs/entry/build-profile.json5中配置参数字段:buildOption下面新增以下内容
 * "sourceOption": {
 *     "workers": [
 *      '../uni_modules/xwq-sherpa-onnx/utssdk/app-harmony/TtsWorker.ets'
 *    ]
 *  }

 模型资源需要放在插件目录resources/rawfile目录下面
  • 鸿蒙端当前也已支持和 Android / iOS 一致的 modelType 分流策略
  • vitsMelo:modelType 传 vitsMelo,voices 可传 null
  • matchaIcefall:modelType 传 matchaIcefall,model 传声学模型,voices 传 vocoder 模型,例如 vocos-16khz-univ.onnx
  • 如果要在鸿蒙端做模型切换,至少要同步切换 modelType、model、voices、tokens、lexicon 以及对应的 fst / dataDir 路径
  • 当前鸿蒙端资源解析链路为“页面层传 rawfile 相对路径 -> worker 直接把路径传给 sherpa-onnx”,不再执行额外的资源复制

vitsMelo 页面调用示例

<template>
  <view class="page">
    <button @click="initTTs">初始化TTS</button>
    <button @click="playTts">开始播报</button>
    <button @click="pauseTts">暂停</button>
    <button @click="resumeTts">继续</button>
    <button @click="stopTts">停止</button>
  </view>
</template>

<script>
import {
  initSherpaOnxxTts,
  start,
  pause,
  resume,
  stop
} from "@/uni_modules/xwq-sherpa-onnx-tts"

export default {
  data() {
    return {
      content: "清晨推开窗,一股清冽的寒气扑面而来"
    }
  },
  methods: {
    initTTs() {
      const path = "vits-melo-tts-zh_en"

      initSherpaOnxxTts({
            modelType:"vitsMelo",
            model:path+"/model.onnx",
            voices:null,
            dataDir:"",
            dictDir:"",
            tokens:path+"/tokens.txt",
            lexicon:`${path}/lexicon.txt`,
            numberFst:`${path}/number.fst`,
            phoneFst:`${path}/phone.fst`,
            dateFst:`${path}/date.fst`,
            // sid:0,
            // speed:1.0,
            success: () => {
                console.log('初始化成功')
                // onnxStart()
            },
            fail: (result) => {
                console.log('初始化失败',result)
           }
       })
    },
    playTts() {
      start({
        text: this.content,
        finished: () => {
          console.log("播放完成")
        },
        fail: (result) => {
          console.log("播放失败", result)
        }
      })
    },
    pauseTts() {
      pause()
    },
    resumeTts() {
      resume()
    },
    stopTts() {
      stop()
    }
  }
}
</script>

<style>
.page {
  padding: 24rpx;
}
</style>

matchaIcefall 页面调用示例

<template>
  <view class="page">
    <button @click="initTTs">初始化TTS</button>
    <button @click="playTts">开始播报</button>
    <button @click="pauseTts">暂停</button>
    <button @click="resumeTts">继续</button>
    <button @click="stopTts">停止</button>
  </view>
</template>

<script>
import {
  initSherpaOnxxTts,
  start,
  pause,
  resume,
  stop
} from "@/uni_modules/xwq-sherpa-onnx-tts"

export default {
  data() {
    return {
      content: "中英文合成测试。Today is a bright day, welcome to try the matcha icefall zh en model."
    }
  },
  methods: {
    initTTs() {
      const path = "matcha-icefall-zh-en"

      initSherpaOnxxTts({
            modelType:"matchaIcefall",
            model:path+"/model-steps-3.onnx",
            voices:path+"/vocos-16khz-univ.onnx",
            tokens:path+"/tokens.txt",
            dataDir:path+"/espeak-ng-data",
            dictDir:"",
            lexicon:`${path}/lexicon.txt`,
            numberFst:`${path}/number-zh.fst`,
            phoneFst:`${path}/phone-zh.fst`,
            dateFst:`${path}/date-zh.fst`,
            success: () => {
                console.log('matcha 初始化成功')
            },
            fail: (result) => {
                console.log('matcha 初始化失败',result)
           }
       })
    },
    playTts() {
      start({
        text: this.content,
        finished: () => {
          console.log("播放完成")
        },
        fail: (result) => {
          console.log("播放失败", result)
        }
      })
    },
    pauseTts() {
      pause()
    },
    resumeTts() {
      resume()
    },
    stopTts() {
      stop()
    }
  }
}
</script>

<style>
.page {
  padding: 24rpx;
}
</style>

使用建议

  • 建议在页面进入后先调用一次 initSherpaOnxxTts
  • 初始化完成前不要直接调用 start
  • 模型、词表、词典、规则文件建议保持在同一静态目录下,便于维护
  • 如果是短文本连续播报,建议在上一次 finished 后再发起下一次播报,避免业务层抢占播放状态
  • 如果 fail 回调收到错误,请先检查模型路径、词表路径和静态资源是否已正确打包到应用内

隐私、权限声明

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

无

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

无

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

无