OddTTS更新:集成ZipVoice,纯CPU跑中英混合语音合成!

引言

上周把 Audio8/MOSS-TTS-Nano 集成到 OddTTS 了,测试一下 Audio8/MOSS-TTS-Nano 效果确实好,但 CPU 跑 RTF 3.26(55字合成耗时 27.85 秒),实在没法用于实时场景。

除了这两个模型个,kid 大佬推荐的模型里还差了一个 ZipVoice,123M 参数,Flow Matching 架构,号称"Small and Fast"。

这个周末在家就搞了一下这个模型。

然而,这个 zipvoice 的集成过程并不顺利,甚至可以说一波三折——踩了好多个大坑,从 PyTorch 推理跑不动,到 vocoder 选型错误合成出噪音,再到模型目录验证 bug 导致 voices 为空。这篇文章把这些坑都记录下来,希望能帮到同样想集成 ZipVoice 的同学。

最终方案:sherpa-onnx 推理后端 + ModelScope 模型下载 + 自动 vocoder 下载,pip 安装后一键启动,纯 CPU 运行,RTF 约 1.3-3.0。

快速开始使用 OddTTS

OddTTS 是一个功能强大的多引擎语音合成服务,提供统一的API接口和友好的Web界面,一套接口搞定多种主流TTS引擎,包括 EdgeTTS、Kokoro-82M-v1.1-zh、ZipVoice、MOSS-TTS、Audio8、ChatTTS、Bert-VITS2、GptSovits v2等,同时也支持OpenAI TTS API的调用。

  • 仓库: https://github.com/oddmeta/oddtts
  • Python版本: 3.12
  • 版本: v2.1.15
  • 安装: pip install oddtts
  • 运行: oddtts
  • 演示: http://localhost:9001

Python测试

import openai
client = openai.OpenAI(base_url="http://localhost:9001/v1", api_key="dummy")
response = client.audio.speech.create(
    model="oddtts-zipvoice",
    voice="news-female",
    input="你好,欢迎使用OddTTS小奥语音合成服务。"
)

curl测试

curl http://127.0.0.1:9001/v1/audio/speech -H "Content-Type: application/json" -d "{\"input\": \"你好,欢迎使用OddTTS小奥语音合成服务。\", \"voice\": \"news-female\"}" --output speech.wav

直接听合成的效果

合成文本:关注我的公众号:奥德元,一起学习 AI,一起追赶时代。Good good study, day day up!

zipvoice-cctv-voice1.wav(news-female 音色,RTF=1.551)

zipvoice-cctv-voice2.wav(news-female-2 音色,RTF=1.410)

zipvoice-leijun-voice.wav(leijun-1 音色,RTF=1.307)

一、ZipVoice 模型概览

1.1 基本信息

特性 值
参数量 ~123M
架构 Flow Matching (4步迭代)
量化方式 INT8 (sherpa-onnx 预编译)
采样率 24000 Hz
支持语言 中文 + 英文(中英混合)
音色克隆 零样本,仅需 3 秒参考音频
推理后端 sherpa-onnx (ONNX Runtime)
模型大小 ~186 MB (encoder + decoder + vocoder)
内存占用 ~500-800 MB

1.2 架构

ZipVoice 采用 Flow Matching 架构,核心流程:

文本 → Tokenization (espeak-ng) → Encoder → Decoder (Flow Matching, 4步) → Vocoder (Vocos) → 音频
                                          ↑
                                    参考音频 (音色克隆)

相比传统扩散模型,Flow Matching 仅需 4 步迭代即可生成频谱,推理速度大幅提升。

1.3 模型文件

sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/
├── encoder.int8.onnx     # 文本编码器 (INT8)
├── decoder.int8.onnx     # Flow Matching 解码器 (INT8)
├── tokens.txt            # Token 词汇表
├── lexicon.txt           # 发音词典
├── espeak-ng-data/       # espeak-ng 音素数据
├── test_wavs/            # 内置参考音频
│   ├── prompt.txt        # 音色列表
│   ├── news-female.wav
│   ├── news-female-2.wav
│   └── leijun-1.wav
└── vocos_24khz.onnx      # Vocos 声码器 (100-mel, 24kHz)

1.4 内置音色

音色名称 描述
news-female 新闻联播女声,正式风格
news-female-2 新闻联播女声2,另一播音风格
leijun-1 雷军演讲风格

二、踩坑过程

这是本文的重点。集成 ZipVoice 的过程并不顺利,遇到了几个比较典型的问题。

2.1 坑1:PyTorch 官方仓库 CPU 跑不动

时间:2026-09-12

最初集成时,直接用 ZipVoice 官方 GitHub 仓库的推理代码。需要的依赖:

pip install torch torchaudio vocos pypinyin jieba safetensors huggingface_hub

模型从 HuggingFace 自动下载,推理用官方 infer_zipvoice 脚本。跑是跑通了,但速度完全无法接受:

合成耗时: 74.70s, 音频时长: ~10s, RTF > 7
合成耗时: 89.04s, 音频时长: ~10s, RTF > 8
合成耗时: 98.31s, 音频时长: ~10s, RTF > 9

RTF > 7 意味着合成 1 秒音频需要 7 秒以上,完全不可用于实时场景。而且 PyTorch 依赖太重了,pip 安装后光 torch 就好几个 GB。

结论:PyTorch 推理方案在纯 CPU 场景下不可行。

2.2 坑2:sherpa-onnx 方案搞定了速度,但 vocoder 选型错误

时间:2026-09-13 上午

发现 k2-fsa 官方在 sherpa-onnx 中提供了 ZipVoice 的预编译 INT8 模型,仓库名 sherpa-onnx-zipvoice-distill-int8-zh-en-emilia。这意味着不需要 PyTorch,直接用 sherpa-onnx(底层是 ONNX Runtime)就能跑。

马上切换方案,用 sherpa-onnx 重写了 tts_zipvoice.py。模型从 ModelScope 下载(国内速度快),依赖只剩 sherpa-onnx 和 soundfile。

但合成出来的音频是噪音!

查了半天原因,最后发现是 vocoder 选型错误:

  • 模型仓库里自带了一个 vocos-22khz-univ.onnx,80 个 mel 频段
  • 但 ZipVoice decoder 输出的是 100 个 mel 频段
  • 80 mel 的 vocoder 去解码 100 mel 的频谱,结果就是噪音

正确的 vocoder 是 vocos_24khz.onnx(100 mel 频段,24kHz),需要单独下载。

2.3 坑3:vocoder 下载源踩坑

找到正确的 vocoder 后,需要自动下载。 给了一个 ModelScope URL:https://www.modelscope.cn/models/roarson/vocos_24khz.onnx。

第一次用 roarson/vocos_24khz 作为 repo ID 下载,ModelScope 返回 404。

# 错误的 repo ID
snapshot_download("roarson/vocos_24khz", local_dir=target_dir)
# 结果: 404 Not Found

试了好几个变体都不行。后来仔细看 URL,发现文件名就是 repo ID 的一部分:

# 正确的 repo ID(带 .onnx 后缀)
snapshot_download("roarson/vocos_24khz.onnx", local_dir=target_dir)
# 成功!vocos_24khz.onnx (51.6 MB)

教训:ModelScope 的 repo ID 不一定和文件名一致,需要看实际 URL。

2.4 坑4:_validate_model_dir 用错了 isfile vs isdir

时间:2026-09-13 下午

vocoder 问题解决后,pip 安装的用户反馈:voices 列表为空。

查日志发现 get_voices() 返回 [],但没有报错。因为原来的代码是:

async def get_voices(self):
    if self._builtin_voices is None:
        try:
            self._init_runtime()
        except Exception:
            pass  # ← 吞掉了所有异常!
    return self._builtin_voices or []

_init_runtime() 失败了但异常被静默吞掉,导致 voices 为空却看不到任何错误信息。

加上日志后发现问题根源:_validate_model_dir() 中检查 espeak-ng-data 时用了 os.path.isfile():

# 错误:espeak-ng-data 是目录,不是文件
return os.path.isfile(os.path.join(path, ESPEAK_DIR))  # 永远返回 False
# 正确:
return os.path.isdir(os.path.join(path, ESPEAK_DIR))

这导致模型目录验证永远失败,_get_model_dir() 返回 None,_init_runtime() 抛异常,get_voices() 静默返回空列表。

修复:os.path.isfile() → os.path.isdir(),同时在 get_voices() 中加上 logger.warning() 输出异常信息。

三、性能对比

3.1 测试环境

  • 测试文本:55字中文文本(含中英混合)
  • 测试条件:纯CPU,WAV格式
  • 硬件环境:普通PC(无GPU)

3.2 性能数据

引擎 采样率 合成耗时 音频时长 RTF(实时率)
Kokoro v1.1 24000 Hz 2.68秒 8.18秒 0.33
ZipVoice INT8 24000 Hz 13.26秒 8.55秒 1.551
Audio8 0.1B ONNX INT8 44100 Hz 27.85秒 8.54秒 3.26
Audio8 0.6B ONNX INT4 44100 Hz 42.34秒 8.59秒 4.93

3.3 ZipVoice 各音色实测

音色 音频时长 合成耗时 RTF
news-female 8.55s 13.26s 1.551
news-female-2 7.95s 11.21s 1.410
leijun-1 5.91s 7.72s 1.307

3.4 性能分析

  • Kokoro v1.1 仍是最快的,RTF=0.33,但不支持音色克隆
  • ZipVoice INT8 RTF≈1.3-3.0,比 Audio8 快 2 倍以上,且支持零样本音色克隆
  • Audio8 音质最好但最慢,适合对质量要求高的离线场景

ZipVoice 在速度和功能之间取得了不错的平衡:比 Kokoro 稍慢但支持音色克隆,比 Audio8 快得多且依赖更轻。

四、代码实现细节

4.1 引擎注册

在 oddtts_params.py 中新增枚举值:

class ODDTTS_TYPE(Enum):
    ODDTTS_ZIPVOICE = 11

在 base_tts_driver.py 中注册策略:

elif tts_type == ODDTTS_TYPE.ODDTTS_ZIPVOICE:
    tts.client = ZipVoiceAPI()
    return tts

4.2 sherpa-onnx 配置

import sherpa_onnx

config = sherpa_onnx.OfflineTtsConfig(
    model=sherpa_onnx.OfflineTtsModelConfig(
        zipvoice=sherpa_onnx.OfflineTtsZipvoiceModelConfig(
            tokens=os.path.join(model_dir, "tokens.txt"),
            encoder=os.path.join(model_dir, "encoder.int8.onnx"),
            decoder=os.path.join(model_dir, "decoder.int8.onnx"),
            data_dir=os.path.join(model_dir, "espeak-ng-data"),
            lexicon=os.path.join(model_dir, "lexicon.txt"),
            vocoder=vocoder_path,  # vocos_24khz.onnx
        ),
        num_threads=2,
    ),
)
tts = sherpa_onnx.OfflineTts(config)

4.3 模型自动下载

优先从 ModelScope 下载(国内友好),GitHub 作为 fallback:

ZIPVOICE_REPO_ID = "roarson/sherpa-onnx-zipvoice-distill-int8-zh-en-emilia"
# ModelScope → 自动下载到 models/zipvoice/

VOCODER_REPO_ID = "roarson/vocos_24khz.onnx"
# 单独下载 vocos_24khz.onnx 到模型目录

4.4 内置音色发现

从 test_wavs/prompt.txt 自动解析内置音色:

news-female.wav 各位村民, 大家新年好! 近期, 湖北省武汉市等多个地区
news-female-2.wav 本台消息, 中共中央国务院, 近日印发关于构建数据基础制度,
leijun-1.wav 那还是36年前, 1987年. 我呢考上了武汉大学的计算机系

每行格式:文件名.wav 参考文本,自动解析为音色列表。

4.5 合成调用

audio = self.tts.generate(
    text=text,
    prompt_text=ref_text,
    prompt_samples=samples.tolist(),  # 参考音频采样点
    sample_rate=sr,
    speed=1.0,
    num_steps=8,  # Flow Matching 迭代步数
)

五、实际应用指南

5.1 安装与配置

# 安装OddTTS(sherpa-onnx 会自动安装)
pip install oddtts

# 启动服务(首次启动会自动下载模型 + vocoder)
oddtts

5.2 切换引擎

在 Web 界面中选择引擎:Zipvoice

或在配置文件中修改:

# oddtts_config.py
"tts_type": ODDTTS_TYPE.ODDTTS_ZIPVOICE

5.3 API调用示例

import requests

# 生成语音文件
response = requests.post("http://localhost:9001/v1/audio/speech", json={
    "input": "关注我的公众号:奥德元,一起学习 AI,一起追赶时代。",
    "voice": "news-female",
    "response_format": "wav",
    "model": "oddtts-zipvoice"
})

# 保存音频文件
with open("output.wav", "wb") as f:
    f.write(response.content)

5.4 OpenAI 兼容 API

from openai import OpenAI

client = OpenAI(base_url="http://localhost:9001/v1", api_key="dummy")

response = client.audio.speech.create(
    model="oddtts-zipvoice",
    voice="news-female",
    input="Hello, this is a ZipVoice TTS test with Chinese and English mixed text. Good good study, day day up!"
)
response.stream_to_file("output.wav")

六、总结与展望

6.1 本次更新的意义

  1. 新引擎:为 OddTTS 增加了 ZipVoice 引擎,填补了"中等速度 + 音色克隆"的空白
  2. 轻量部署:sherpa-onnx 方案无需 PyTorch,pip 安装后一键启动
  3. 国内友好:模型从 ModelScope 下载,无需FQ
  4. 自动配置:vocoder 自动下载,模型目录自动检测,开箱即用

6.2 踩坑经验总结

问题 原因 解决方案
PyTorch CPU 太慢 官方仓库用 PyTorch 推理 切换 sherpa-onnx
合成噪音 vocoder mel 频段不匹配(80 vs 100) 使用正确的 vocos_24khz.onnx
vocoder 下载 404 ModelScope repo ID 带 .onnx 后缀 用 roarson/vocos_24khz.onnx
voices 为空 os.path.isfile() 检查目录 改为 os.path.isdir()
异常被吞掉 get_voices() 静默 catch 加 logger.warning()

6.3 后续优化方向

  1. 语音克隆:当前仅使用内置 preset voices,后续可开放零样本语音克隆功能
  2. 流式生成:实现真正的流式输出,减少首字延迟
  3. 性能优化:调整 num_steps 参数(4步 vs 8步)权衡速度和质量
  4. 更多音色:支持用户上传自定义参考音频

相关资源

posted @ 2026-09-13 14:53  程序员老奥  阅读(74)  评论(0)    收藏  举报