OddTTS v2.0 更新:正式支持音色克隆!

引言

这两个星期,在 大白菜 大佬和 Kid 大佬的提点下,完成了 Audio8MOSS-TTS-Nano 两个轻量级 TTS 引擎的集成工作,也让一直定位于轻量级语音合成的 OddTTS 的引擎矩阵越来越丰富。与此同时刚才这两个引擎都支持音色克隆(zero-shot voice cloning),昨天晚上想起来这摊子事后,就决定把 OddTTS 的音色克隆功能给搞一下,并将这个能力给暴露出来。

今天搞了一天,还算是比较顺利的把音色克隆功能完整实现了,并作为 v2.0 的核心特性发布。

现在,你只需要上传一段 3-10 秒的参考音频,就能在 OddTTS 中注册一个自定义音色,像内置音色一样随时调用——无论是通过 Web 界面还是 OpenAI 兼容 API。

在这个周末,俺终于可以说 OddTTS 完成了从 v1.x 到 v2.0 的跨越。
音色克隆功能首批支持 MOSS-TTS-Nano、Audio8 0.1B ONNX INT8、Audio8 0.6B ONNX INT4 三个引擎。
后面如果有更多轻量级的 TTS 引擎和模型,我也会持续的将一些适合的引擎一个个的加进 OddTTSk 中,以支持更多引擎。

快速开始使用 OddTTS

OddTTS 是一个功能强大的多引擎语音合成服务,提供统一的 API 接口和友好的 Web 界面,一套接口搞定多种主流 TTS 引擎,包括 EdgeTTSKokoro-82M-v1.1-zhAudio8MOSS-TTS-NanoChatTTSBert-VITS2GptSovits v2 等,同时也支持 OpenAI TTS API 的调用。

  • 仓库: https://github.com/oddmeta/oddtts
  • 版本: v2.0.13
  • 安装: 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(input="你好,欢迎使用OddTTS小奥语音合成服务。")

curl 测试

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

一、音色克隆是什么?

1.1 Zero-shot Voice Cloning

音色克隆(Voice Cloning)是指通过一段参考音频,让 TTS 模型学会模仿该音频中说话人的音色、语调、韵律等特征,然后用这个”克隆”出来的音色去合成新的文本,简单理解就是,可以用你自己的音频来clone一个音色,然后使用这个音色去合成新的文本。

本次 OddTTS 集成的三个引擎(MOSS-TTS-Nano、Audio8 0.1B、Audio8 0.6B)的 ONNX Runtime 都原生支持 prompt_audio_path 参数——你只需要传入一段参考音频的路径,模型就能自动克隆该音色进行合成。

1.2 为什么需要音色克隆?

在实际应用中,内置音色往往无法满足所有需求。比如:

  • 个人品牌:用自己的声音做配音
  • 角色扮演:为游戏角色、动画角色定制声音
  • 多语言场景:同一个人说不同语言,保持音色一致性
  • 隐私保护:用克隆音色替代真人录音

音色克隆让 TTS 从”用别人的音色”变成了”用我自己的音色”。

二、整体架构设计

2.1 设计思路

核心思路是音色库管理:一次上传 → 注册为自定义音色 → 像内置音色一样选择使用。

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────────┐
│   前端 UI       │────▶│  API 层          │────▶│  VoiceCloneManager  │
│  (上传/管理)    │     │ (上传/列表/删除)  │     │  (文件存储+元数据)   │
└─────────────────┘     └──────────────────┘     └─────────────────────┘
                                                         │
                              ┌────────────────────────┘
                              ▼
                    ┌─────────────────┐
                    │  tts_moss_nano  │
                    │ get_voices()    │◀── 合并内置+自定义音色
                    │ _synthesize()   │◀── 根据voice名找prompt_audio_path
                    └─────────────────┘

2.2 文件结构变化

本次更新新增和修改了以下文件:

oddtts/
├── voice_clone/                  # 新增:音色克隆管理模块
│   ├── __init__.py
│   └── manager.py                # VoiceCloneManager(287行)
├── models/
│   ├── tts_moss_nano.py          # 修改:支持prompt_audio_path克隆
│   ├── tts_audio8_0_1b_onnx_int8.py   # 修改:支持音色克隆
│   └── tts_audio8_0_6b_onnx_int4.py   # 修改:支持音色克隆
├── oddtts_params.py              # 修改:TTSParams新增prompt_audio_path
├── oddtts_config.py              # 修改:新增voices_base_dir配置
├── router/
│   └── api.py                    # 修改:新增4个克隆API接口
├── templates/
│   └── index.html                # 修改:新增音色克隆标签页
└── tests/
    ├── test_voice_clone.py       # 新增:单元测试
    ├── test_voice_clone_api.py   # 新增:API集成测试
    └── test_voice_clone_check.py # 新增:功能验证测试

2.3 音色存储目录结构

克隆音色按引擎隔离存储,运行时自动创建:

voices/
  moss_nano/
    xiaoming/
      reference.wav       # 原始参考音频(统一转为WAV格式)
      meta.json           # {"name":"xiaoming","display_name":"小明",...}
  audio8_0_1b/
    my_voice/
      reference.wav
      meta.json

三、技术实现细节

3.1 VoiceCloneManager 核心管理器

VoiceCloneManager 是音色克隆的核心模块,职责包括:

  • 文件存储:保存上传的参考音频到 voices/<engine>/<voice_id>/
  • 元数据维护:每个克隆音色对应一个 meta.json 文件
  • 引擎隔离:不同引擎的克隆音色空间互相隔离,避免命名冲突
  • 音频转码:自动将上传的 mp3/flac 等格式转为 WAV(48kHz)
# oddtts/voice_clone/manager.py 核心逻辑
_TARGET_SAMPLE_RATE = 48000

def _sanitize_voice_id(name: str) -> str:
    """将用户输入的名称转换为安全的 voice_id"""
    sid = re.sub(r"[^a-zA-Z0-9_\-]", "_", name.strip())
    sid = re.sub(r"_+", "_", sid)
    sid = sid.strip("_")
    if not sid:
        sid = "voice_" + uuid.uuid4().hex[:8]
    return sid[:64]

def _ensure_wav(audio_path, output_path) -> Path:
    """确保音频为WAV格式,如需要则转码"""
    # 优先用soundfile,fallback到pydub
    ...

关键设计决策:

决策理由
按引擎隔离目录不同引擎的音色格式要求不同,隔离避免冲突
统一转为WAVONNX Runtime 只接受 WAV 输入
voice_id 白名单校验防止路径穿越等安全问题
meta.json 存储元数据方便前端展示和管理

3.2 TTSParams 扩展

为支持音色克隆,TTSParams 新增了可选字段:

class TTSParams:
    def __init__(self, ..., prompt_audio_path: str | None = None):
        self.prompt_audio_path = prompt_audio_path

这个字段支持两种使用模式:

  1. 音色库模式(推荐):通过 voice 参数传入克隆音色名称,后端自动查找对应的 reference.wav
  2. 即时上传模式:直接通过 prompt_audio_path 传入参考音频路径,无需注册

3.3 引擎改造:MOSS-TTS-Nano

MOSS-TTS-Nano 的 ONNX Runtime 原生支持 prompt_audio_path 参数,改造相对直接:

# get_voices() 合并内置 + 自定义音色
async def get_voices(self) -> list[dict[str, str]]:
    builtin = list(MossNano_voices.values())
    cloned = get_voice_clone_manager().list_voices("moss_nano")
    return builtin + cloned

# _resolve_voice_and_prompt() 核心解析逻辑
def _resolve_voice_and_prompt(self, tts_params):
    voice = tts_params.voice
    prompt_path = None
    
    if voice not in MossNano_voices:
        # 克隆音色:从voice_clone库查找reference.wav
        prompt_path = get_voice_clone_manager().get_audio_path("moss_nano", voice)
    
    return (voice, prompt_path)

# synthesize() 调用时传入prompt_audio_path
result = self.runtime.synthesize(
    text=text,
    voice=voice,
    prompt_audio_path=prompt_path,  # 克隆音色时传入reference.wav路径
    ...
)

3.4 引擎改造:Audio8

Audio8 的两个模型(0.1B 和 0.6B)也支持参考音频克隆,但实现方式略有不同:

  • 0.1B 模型:自动将 reference_codes.npy 复制到 voices/default_voice/codes.npy 作为默认音色
  • 0.6B 模型:仅创建 meta.json 文件,不包含参考音色代码

改造后,Audio8 引擎同样支持通过 voice 参数传入克隆音色名称,后端自动查找对应的参考音频。

3.5 API 层新增接口

本次更新新增了 4 个音色克隆相关的 API 接口:

接口方法说明
/api/voice/clonePOST上传参考音频并注册音色(multipart/form-data)
/api/voice/clone/listGET列出自定义音色
/api/voice/clone/<engine>/<voice_id>DELETE删除克隆音色
/api/voice/clone/audio/<engine>/<voice_id>GET试听参考音频

上传接口示例

curl -X POST http://localhost:9001/api/voice/clone \
  -F "audio_file=@reference.wav" \
  -F "name=xiaoming" \
  -F "display_name=小明" \
  -F "engine=moss_nano"

使用克隆音色合成

# 通过 OpenAI 兼容 API 使用克隆音色
curl -X POST http://localhost:9001/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"input": "你好,这是克隆音色测试", "voice": "xiaoming"}' \
  --output cloned_speech.mp3

3.6 前端”音色克隆”标签页

Web 界面新增了”音色克隆”标签页,包含三个区域:

  1. 上传区:文件选择 + 音色标识 + 展示名称 + 语种 + 保存按钮
  2. 已克隆列表:卡片式展示,支持试听原音频 / 删除
  3. 合成区融合:克隆音色带 🎤 标记,与内置音色统一在下拉框中显示

交互流程:

用户打开"音色克隆"面板
  │
  ├── 上传一段参考音频(wav/mp3/flac,建议3-10秒)
  ├── 输入音色名称(如"小明")
  ├── 点击"保存音色"
  │     └── POST /api/voice/clone ──▶ 保存到 voices/moss_nano/xiaoming/
  │
  └── 切换到"语音合成"面板
        ├── 下拉框现在多了 "🎤 小明 (克隆)"
        ├── 选择"🎤 小明"
        ├── 输入文本
        └── 点击生成
              └── voice="xiaoming" ──▶ 后端找到 reference.wav
                                    ──▶ prompt_audio_path 传给 ONNX Runtime
                                    ──▶ 返回克隆后的合成音频

四、与 OpenAI 兼容 API 的关系

音色克隆功能与 OpenAI 兼容 API 完美融合。/v1/audio/speechvoice 字段可以直接传克隆音色名称,无需任何额外改动:

POST /v1/audio/speech
{
  "input": "你好,这是克隆音色测试",
  "voice": "xiaoming",
  "response_format": "wav"
}

这是音色库方案相比”每次上传参考音频”的最大优势——注册一次,永久使用。

五、今日提交记录

本次 v2.0 更新涉及 18 个文件,新增约 1415 行代码:

4702dda 2026-09-05 opt: 优化Audio8两个模型的语音克隆,首次编码注册后缓存,后续直接使用。
704b521 2026-09-05 fix: 修复 audio8 声音克隆失败问题。
165f802 2026-09-05 chore: 在oddtts_config中新增克隆音色根目录
cc18f6e 2026-09-05 chore: 切换模型时自动检测目标模型是否已存在
c66fe83 2026-09-05 opt: 模型路径统一改造总结。
1b9ef99 2026-09-05 feat: 支持 audio8 两个模型的基础音色克隆功能。
20c0782 2026-09-05 fix: 修复上传pypi被拒的问题。
faf60d7 2026-09-04 fix: 修复编译 whl 后缺失 vendor 目录问题。
3ba382c 2026-09-04 feat: 重大更新,加入音色克隆的支持。

六、支持克隆的引擎

引擎engine 参数值说明纯CPU
MOSS-TTS-Nano 0.1Bmoss_nanoOpenMOSS 轻量级多语言 TTS,近20种语言
Audio8 0.1B ONNX INT8audio8_0_1bAudio8 0.1B,11种语言,44.1kHz
Audio8 0.6B ONNX INT4audio8_0_6bAudio8 0.6B,11种语言,44.1kHz

七、实际应用指南

7.1 安装与配置

# 安装OddTTS
pip install oddtts

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

7.2 切换引擎

在 Web 界面或配置文件中切换到支持克隆的引擎:

# oddtts_config.py
"tts_type": ODDTTS_TYPE.ODDTTS_MOSS_NANO,  # 或 AUDIO8_0_1B_ONNX_INT8 等

7.3 通过 Web 界面克隆音色

  1. 打开 http://localhost:9001/
  2. 切换到”音色克隆”标签页
  3. 上传一段 3-10 秒的参考音频
  4. 输入音色标识(如 xiaoming)和展示名称(如 小明
  5. 点击”保存音色”
  6. 切换到”语音合成”标签页,下拉框中选择 🎤 小明
  7. 输入文本,点击生成

7.4 通过 API 克隆音色

import requests

# 1. 上传参考音频并注册音色
with open("reference.wav", "rb") as f:
    response = requests.post(
        "http://localhost:9001/api/voice/clone",
        files={"audio_file": f},
        data={
            "name": "xiaoming",
            "display_name": "小明",
            "engine": "moss_nano"
        }
    )
print(response.json())

# 2. 使用克隆音色合成语音
response = requests.post("http://localhost:9001/v1/audio/speech", json={
    "input": "你好,这是克隆音色测试",
    "voice": "xiaoming",
    "response_format": "wav"
})

with open("cloned_speech.wav", "wb") as f:
    f.write(response.content)

八、总结与展望

8.1 本次更新的意义

  1. 功能突破:OddTTS 首次支持音色克隆,将 TTS 从”用别人的音色”升级为”用我自己的音色”
  2. 架构扩展VoiceCloneManager 的设计为后续更多引擎的克隆支持预留了扩展空间
  3. API 兼容:克隆音色与内置音色统一管理,OpenAI 兼容 API 无需任何改动

8.2 性能权衡

  • MOSS-TTS-Nano:RTF 约 0.5,适合实时场景,克隆效果好
  • Audio8 0.1B:合成较慢(RTF ~3.26),但采样率更高(44.1kHz)
  • Audio8 0.6B:最慢(RTF ~4.93),但模型参数量最大,理论音质最好

8.3 后续优化方向

  1. 更多引擎支持:将音色克隆扩展到 Kokoro 等其他引擎
  2. 批量管理:支持批量上传、批量删除克隆音色
  3. 音色评估:自动评估克隆音色与参考音频的相似度
  4. 流式克隆:实现真正的流式克隆合成,减少首字延迟

8.4 社区贡献

欢迎开发者参与 OddTTS 的开发和优化:

结语

音色克隆功能的加入标志着 OddTTS 从一个”多引擎 TTS 聚合器”进化为一个”支持个性化音色的 TTS 平台”。无论是个人开发者还是企业用户,都可以用最少的成本获得专属的语音合成能力。

我们期待更多开发者加入,共同推动语音合成技术的发展。


相关资源

Leave a comment

Your email address will not be published. Required fields are marked *