本地 TTS 语音合成实战:ChatTTS 与 CosyVoice

很多 AI 应用最后一步都要”开口说话”——有声书、智能客服、视频配音、播客生成,背后都离不开 TTS(Text-To-Speech,语音合成)。调用云厂商接口最省事,但按字符计费、有合规和隐私顾虑,还受网络波动影响。本文用一线的本地 TTS 方案,带你把文字转语音服务跑在自己机器上,覆盖 ChatTTS、CosyVoice2 两大主流开源模型的选型、部署与 API 封装。

一、为什么要在本地跑 TTS

云端 TTS 看似简单,落地到生产常踩三个坑:第一是成本,长文本按字符累加,有声书批量生成时账单惊人;第二是数据合规,把内部文案、用户对话送出去做语音合成,可能触碰隐私红线;第三是可用性,外网抖动会让整个播报链路雪崩。把 TTS 收到本地,一次部署、无限调用,延迟和费用都可控,配合你已经跑起来的本地大模型生态(参考用 Dify + Ollama 搭建私有 AI 知识库)就能组成完整的”听—说”闭环。

举个真实场景:一位做法律科普的博主,每天要把近两万字的稿子转成配音。用云端 TTS 按千字几分钱累计,一个月也要上百元,而且稿件里常有未公开案例,根本不敢往外传。换成一台闲置换下的游戏本跑 ChatTTS,电费几乎可以忽略,稿子全程不出本机。这正是本地 TTS 的甜区:高频、批量、对隐私敏感的生产任务。

二、主流本地 TTS 方案速览

开源社区这几年进展飞快,下面四种是工程里最常遇到的选型,按”是否需本地模型”分成两派:

方案是否需要本地模型中文自然度特色能力硬件门槛
ChatTTS是(约 1.5G)高,带语气词笑声、停顿标签可控消费级 GPU 即可
CosyVoice2是(0.5B~1.5B)很高,支持多语种音色克隆、情感指令建议 6G+ 显存
Bark中,偏口语音乐、非语言声音显存占用较大
edge-tts否(调微软接口)免模型、零部署仅联网,非真正本地

追求”真正离线 + 可控”,优先考虑 ChatTTS 与 CosyVoice2;只想免部署快速验证,edge-tts 仍是好补丁。下文重点讲前两者。

三、ChatTTS 快速上手

3.1 安装与基础调用

ChatTTS 是社区最易上手的本地 TTS,几行代码就能出声。先装依赖:

pip install ChatTTS torch soundfile
# 如需 GPU 加速,安装对应 CUDA 版本的 torch

基础合成只需要加载模型、喂文本、写出 wav:

from chattts import ChatTTS
import torch, soundfile as sf

chat = ChatTTS.Chat()
chat.load(compile=False)          # compile=True 可提速但首次较慢

texts = ["你好,这是一段本地合成的语音。"]
wavs = chat.infer(texts)
sf.write("out.wav", wavs[0], 24000)   # ChatTTS 输出采样率 24kHz

ChatTTS 的模型权重约 1.5G,首次 chat.load() 会下载并缓存到本地,之后启动只需几秒。它对显存很友好,4G 显存的入门卡就能实时合成;如果只有 CPU,速度会慢几倍,但批量转长音频完全够用。社区还提供了预编译的 WebUI,不熟悉 Python 也能图形化操作。

3.2 用语气标签让声音更自然

ChatTTS 的杀手锏是口语化标签:用 [laugh] 插入笑声、[uv_break] 控制停顿。配合随机种子还能固定某个”音色”:

texts = ["今天天气真好[laugh],我们一起去散步吧[uv_break]。"]
params = ChatTTS.Chat.InferCodeParams(
    spk_emb = chat.sample_random_speaker(),   # 固定说话人
    temperature = 0.3,
)
wavs = chat.infer(texts, params_infer_code=params)
sf.write("natural.wav", wavs[0], 24000)

四、CosyVoice2:跨语言与音色克隆

4.1 拉起推理服务

CosyVoice2 由阿里开源,强项是多语种零样本音色克隆。克隆只需 3–10 秒参考音频:

import sys, torchaudio
sys.path.append("CosyVoice")
from cosyvoice.cli.cosyvoice import CosyVoice2
from cosyvoice.utils.file_utils import load_wav

cosyvoice = CosyVoice2("models/CosyVoice2-0.5B")

# 基础指令式 TTS:用自然语言描述想要的语气
out = cosyvoice.inference_instruct(
    tts_text="欢迎使用本地语音合成服务。",
    instruct_text="用温柔的女声、语速稍慢地朗读。",
    spk_id="中文女",
)
torchaudio.save("cosy.wav", out["tts_speech"], 24000)

CosyVoice2 的 0.5B 版本对显存更友好,单张 6G 卡即可跑起来;若要更高音质或做服务端并发,建议 12G 以上并配合推理引擎的连续批处理。它和 ChatTTS 最大的区别在于”指令驱动”——你不用录参考音,直接用自然语言告诉它想要什么声线,适合快速试不同风格。

4.2 用参考音频克隆音色

如果不想写指令,直接给一段参考音频让模型”学声线”:

prompt = load_wav("ref.wav", 24000)   # 3~10 秒干净人声
out = cosyvoice.inference_zero_shot(
    tts_text="这段话会用参考音色念出来。",
    prompt_text="参考音频对应的原文。",
    prompt_speech=prompt,
)
torchaudio.save("clone.wav", out["tts_speech"], 24000)

五、封装成 HTTP 服务

单条脚本不够用,生产里通常包一层 API。下面用 FastAPI 把 ChatTTS 包成可并发调用的服务:

from fastapi import FastAPI, Query
from chattts import ChatTTS
import torch, io, soundfile as sf, base64

app = FastAPI()
chat = ChatTTS.Chat(); chat.load(compile=False)

@app.get("/tts")
def tts(text: str = Query(..., max_length=2000)):
    wav = chat.infer([text])[0]
    buf = io.BytesIO(); sf.write(buf, wav, 24000)
    return {"audio_b64": base64.b64encode(buf.getvalue()).decode()}

# 启动:uvicorn main:app --host 0.0.0.0 --port 8000

前端拿到 audio_b64 直接 new Audio("data:audio/wav;base64,"+...) 即可播放。若并发高、要 GPUs 调度,可参考大模型推理引擎选型(vLLM/SGLang/Ollama)的思路把模型服务独立部署。

真实业务里 TTS 往往是被其他服务调用的”下游”,所以接口设计要尽量简单可靠:入参只留文本,出参返回可直接播放的数据。上面用 base64 内联只是演示;生产环境更推荐把 wav 存到对象存储或本地磁盘,返回 URL,避免把大体积音频塞进 JSON。如果担心单点,把 ChatTTS 进程和 FastAPI 拆成独立容器,用消息队列削峰,稳定性会好很多。

六、接进你的 AI 应用

TTS 很少单独存在,它通常是 AI 管线的”出口”。典型组合是:大模型生成文案 → TTS 转语音 → 推流或存文件。把 TTS 服务和本地大模型放在同一内网,配合用 GGUF 与 llama.cpp 做本地量化部署跑出来的轻量模型,整条链路完全离线,既快又省。需要可视化编排时,Dify 的工作流也能直接挂一个 HTTP 节点调用上面的 /tts 接口。

一个落地的例子:先用本地大模型把产品文档总结成口播稿,再调 TTS 生成讲解音频,最后自动上传到播客平台。全程不依赖任何外部 API,文案和音频都不会离开你的服务器,既满足合规,又把边际成本压到接近零。这种”本地大模型 + 本地 TTS”的组合,正在成为很多个人开发者和中小企业做语音内容的标准打法。

七、性能与延迟预期

落地前对性能有个大致预期,能少踩很多坑。下面是消费级硬件上的经验值,实际会随模型与文本长度波动:

方案硬件单句延迟(约)实时倍数
ChatTTSRTX 3060 12G0.3–0.8s / 句5–10x
CosyVoice2 0.5BRTX 4060 8G0.5–1.2s / 句3–8x
ChatTTS(纯 CPU)i7 笔记本3–6s / 句约 1x

所谓”实时倍数”指合成速度相对音频时长的比例,大于 1 即比真人说话还快。做实时对话要保住 5x 以上;做离线批量配音,纯 CPU 也能扛。关键是把长文本先按句号切句,再并发或排队合成,避免单次塞入上千字导致显存峰值和截断。

八、上线前的避坑清单

本地 TTS 真上线,有几个高频雷区:

  • 中文标点:ChatTTS 对句号、逗号敏感,漏标点会导致吞字或断句怪异,生成前先用正则规整标点。
  • 显存峰值:CosyVoice2 推理时激活占用不低,批量任务用队列削峰,别一次性灌满。
  • 采样率:两个模型都输出 24kHz,前端播放器或下游编码要匹配,否则变调。
  • 长文本切分:单次超过模型上限会截断,按句号切句后逐句合成再拼接更稳。
  • 并发:单模型实例不是线程安全的,多 worker 用多进程或独立服务实例。

九、小结

本地 TTS 已经从”玩具”变成可落地的生产组件。轻量、要语气可控选 ChatTTS;要跨语言、音色克隆选 CosyVoice2。把它们封成 HTTP 服务,再接进你已有的本地大模型体系,就能拥有零成本、可离线、数据不出域的语音合成能力。下一步可以叠加声音克隆做品牌专属播报,或和 ASR 组成语音对话闭环。

上一篇 结对编程实战:从两个键盘到集体代码所有权
下一篇 视觉语言模型 VLM 实战:图文理解架构解析