
1. 服务端
将模型部署为服务,会用到 FastAPI 和 Uvicorn 两个工具:
- FastAPI:一款轻量、高效的 Python Web 框架,专门用于构建 API 服务。它能快速将模型推理功能封装为可调用的接口,上手难度低,适合初学者用于简单服务部署。
- Uvicorn:一款高性能的 Web 服务器,用于运行 FastAPI 应用。它负责接收客户端的请求并转发给 FastAPI 处理,是 FastAPI 应用部署的常用配套工具,启动简单且占用资源少。
# 禁止输出 DEBU 级别的日志
import logging
logging.disable(logging.DEBUG)
# 设置 onnxruntime 日志输出级别最高
import onnxruntime
onnxruntime.set_default_logger_severity(4)
# 禁止输出所有警告
import warnings
warnings.filterwarnings('ignore')
# 全局禁用所有 tqdm 进度条
import os
os.environ['TQDM_DISABLE'] = '1'
import sys
sys.path.append('CosyVoice')
sys.path.append('CosyVoice/third_party/Matcha-TTS')
from cosyvoice.cli.cosyvoice import CosyVoice3
from fastapi import FastAPI
from fastapi import Form
from fastapi.responses import StreamingResponse
from fastapi.responses import JSONResponse
import numpy as np
import uvicorn
import time
import json
import glob
app = FastAPI()
# 加载模型
cosyvoice = CosyVoice3(model_dir='Fun-CosyVoice3-0.5B-2512', fp16=True)
# 添加音色
speakers = json.load(open('audio/speakers.json'))
prefix = 'You are a helpful assistant.<|endofprompt|>'
available_speakers = []
for speaker in speakers:
cosyvoice.add_zero_shot_spk(prefix + speaker['txt'], f'audio/{speaker["wav"]}', zero_shot_spk_id=speaker['sid'])
available_speakers.append({'name': speaker['name'], 'sid': speaker['sid']})
def text_to_speech(txt, spk):
generator = cosyvoice.inference_zero_shot(txt, '', '', zero_shot_spk_id=spk)
for chunk in generator:
chunk = chunk['tts_speech'].numpy().squeeze()
yield chunk.tobytes()
@app.post('/tts')
def get_audio(txt: str = Form(...), spk: str = Form(...)):
return StreamingResponse(content=text_to_speech(txt, spk))
@app.get('/spk')
def get_speaker_list():
return JSONResponse(content=available_speakers)
if __name__ == '__main__':
uvicorn.run('server:app', host='0.0.0.0', port=8000)
2. 客户端
为了让 TTS 模型的使用更直观、无需编写代码就能操作,我们可以借助 Gradio 工具。它是一款轻量级 Python 可视化工具,核心作用是快速搭建机器学习应用的 Web 交互式界面,无需掌握复杂前端开发技术,仅需少量 Python 代码,就能生成包含文本输入框、音色选择器、语音播放器的客户端界面,既能方便自己调试模型,也能让非技术人员轻松使用,是快速演示和落地 TTS 模型功能的优选工具。
conda create -n gradio-env python=3.10 conda activate gradio-env pip install gradio numpy soundfile requests
import warnings
warnings.filterwarnings('ignore')
import gradio as gr
import requests
import soundfile as sf
import numpy as np
import glob
from pathlib import Path
import json
default, sample_rate = sf.read('audio/default.wav', dtype=np.float32)
def generate_audio(txt, spk):
try:
response = requests.post(':8000/tts', data={'txt': txt, 'spk': spk}, timeout=60, stream=True)
response.raise_for_status()
audio_bytes = bytearray()
for bytes in response.iter_content(chunk_size=4096):
audio_bytes.extend(bytes)
audio = np.frombuffer(audio_bytes, dtype=np.float32)
return (24000, audio)
except requests.RequestException:
return (sample_rate, default)
def page_load():
try:
response = requests.get(':8000/spk', timeout=10)
response.raise_for_status()
speakers = json.loads(response.content) # 转换为列表对象
choices = [(speaker['name'], speaker['sid']) for speaker in speakers]
return gr.update(choices=choices, value=choices[0][1])
except requests.RequestException:
return gr.update(choices=[('网络错误', 'error')], value='error')
# 加载示例文本
samples = { Path(fname).stem: open(fname, 'r', encoding='utf8').read() for fname in glob.glob('example/*.txt')}
with gr.Blocks(title='TTS 语音合成') as app:
gr.Markdown('<center><h1> 🎙️ 文本转语音 (TTS)</h1></center>')
audio = gr.Audio(label='播放语音', type='numpy', buttons=['download', ], autoplay=True)
with gr.Row():
radio = gr.Radio(choices=samples.keys(), show_label=False, interactive=True, scale=9)
speak = gr.Dropdown(show_label=False, scale=1)
inputs = gr.TextArea(show_label=False)
with gr.Row():
clear = gr.Button('清空', variant='secondary')
submit = gr.Button('生成', variant='primary')
# 绑定事件
disable = lambda: gr.update(interactive=False)
enable = lambda: gr.update(interactive=True)
submit.click(fn=disable, outputs=submit).then(fn=generate_audio, inputs=[inputs, speak], outputs=audio).then(fn=enable, outputs=submit)
clear.click(fn=lambda : '', outputs=inputs)
radio.change(fn=lambda title: samples.get(title, ''), inputs=radio, outputs=inputs)
app.load(fn=page_load, outputs=speak)
if __name__ == '__main__':
css = '''
.gradio-container {width: 1050px;margin: 0 auto;background-color:white;}
.control-wrapper{display:none;}
'''
app.launch(theme=gr.themes.Citrus(), css=css)

冀公网安备13050302001966号