AI文字语音项目:搭建一个支持情感控制、可二次封装的TTS服务

在这里插入图片描述

📦 第一阶段:环境准备与模型部署

1. 创建项目并安装核心依赖
打开你的终端,执行以下命令:

# 1. 创建项目目录
mkdir MyEmotionalTTS && cd MyEmotionalTTS

# 2. 创建Python虚拟环境(推荐)
python -m venv venv

# 在Linux/Mac上激活:
source venv/bin/activate
# 在Windows上激活:
# venv\Scripts\activate

# 3. 安装PyTorch (根据你的CUDA版本选择,以CUDA 12.1为例)
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu121

# 4. 安装ChatTTS及其他依赖
pip install ChatTTS transformers soundfile ipython

2. 下载并初始化ChatTTS模型
创建一个名为 init_model.py 的脚本,写入以下代码:

import ChatTTS
import torch
import warnings
warnings.filterwarnings("ignore")

# 初始化ChatTTS
chat = ChatTTS.Chat()

# 加载模型(自动下载权重,约2GB)
chat.load_models(compile=False) # `compile=False` 可避免特定环境下的错误

# 查看可用模型参数(可选)
print("模型加载成功!")
print(f"设备: {chat.device}")

# 将模型设为推理模式(重要)
chat.eval()

# 保存模型对象以供后续使用(示例,实际我们会在封装类中管理)
# import pickle
# with open('chat_model.pkl', 'wb') as f:
#     pickle.dump(chat, f)

运行它来下载和验证模型:

python init_model.py

🧱 第二阶段:核心封装与情感控制接口

在这里插入图片描述

创建一个核心封装类 EmotionalTTS.py,这是二次封装的精髓。

import ChatTTS
import torch
import numpy as np
import soundfile as sf
from typing import List, Optional, Dict
import warnings
warnings.filterwarnings("ignore")

class EmotionalTTS:
    """
    情感TTS二次封装类。
    提供易于使用的接口,用于控制情感、音色和语速。
    """
    
    def __init__(self, model_path: str = None, device: str = None):
        """
        初始化TTS引擎。
        
        Args:
            model_path: 预加载的模型路径(暂无用处,ChatTTS自动下载)。
            device: 指定设备,如 'cuda', 'cpu'。为None则自动选择。
        """
        self.chat = ChatTTS.Chat()
        # 加载模型
        if device:
            self.chat.load_models(compile=False, device=device)
        else:
            self.chat.load_models(compile=False)
        
        # 设置为评估模式
        self.chat.eval()
        
        # 情感-参数映射字典 (你可以根据效果扩展这个字典)
        self.emotion_params_map = {
            'happy': {'temperature': 0.7, 'spk_emb': None}, # 开心,语速稍快
            'sad': {'temperature': 0.3, 'spk_emb': None},   # 悲伤,语速慢
            'angry': {'temperature': 0.9, 'spk_emb': None}, # 生气,音调高
            'neutral': {'temperature': 0.5, 'spk_emb': None}, # 中性
            'friendly': {'temperature': 0.6, 'spk_emb': None}, # 友好
        }
        
        print(f"[初始化完成] 模型运行在: {self.chat.device}")
    
    def synthesize(self,
                   text: str,
                   emotion: str = 'neutral',
                   speaker_embedding: Optional[np.ndarray] = None,
                   speed: float = 1.0,
                   sample_rate: int = 24000,
                   save_path: Optional[str] = None) -> np.ndarray:
        """
        核心合成函数。
        
        Args:
            text: 要合成的文本。
            emotion: 情感标签,从 `emotion_params_map` 中选择。
            speaker_embedding: 可选,特定说话人音色嵌入。
            speed: 语速因子 ( >1 加速, <1 减速)。
            sample_rate: 输出音频采样率。
            save_path: 如需直接保存,提供.wav文件路径。
            
        Returns:
            audio_data: 合成的音频波形数据 (numpy数组)。
        """
        # 1. 文本预处理 (ChatTTS要求特殊处理)
        texts = [text]
        
        # 2. 情感参数注入 (通过`infer_seed`控制)
        params = self.emotion_params_map.get(emotion, self.emotion_params_map['neutral'])
        
        # 3. 生成随机种子以实现不同的情感/音色 (可控的随机性)
        rand_spk = np.random.randint(0, 100000) if speaker_embedding is None else None
        
        # 4. 模型推理
        with torch.no_grad():
            wavs, _ = self.chat.infer(
                texts,
                params_refine_text={
                    'prompt': f'[speaker_emo={emotion}]' # 提示词控制情感
                },
                params_infer_code={
                    'spk_emb': speaker_embedding,
                    'seed': rand_spk,
                    'temperature': params['temperature'],
                },
                do_text_normalization=True,
                return_duration=True
            )
        audio_data = wavs.squeeze() # 从 [1, samples] 变为 [samples]
        
        # 5. 语速调整 (简单的重采样,生产环境可用更优算法)
        if speed != 1.0:
            from scipy import signal
            new_length = int(len(audio_data) / speed)
            audio_data = signal.resample(audio_data, new_length)
        
        # 6. 保存文件(如果提供了路径)
        if save_path:
            if not save_path.endswith('.wav'):
                save_path += '.wav'
            sf.write(save_path, audio_data, samplerate=sample_rate)
            print(f"[音频已保存] -> {save_path}")
        
        return audio_data, sample_rate
    
    def batch_synthesize(self,
                         texts: List[str],
                         emotions: Optional[List[str]] = None,
                         save_dir: str = "./output_batch") -> List[str]:
        """
        批量合成文本。
        
        Args:
            texts: 文本列表。
            emotions: 对应的情感列表,为None则全部使用中性。
            save_dir: 输出目录。
            
        Returns:
            file_paths: 保存的音频文件路径列表。
        """
        import os
        os.makedirs(save_dir, exist_ok=True)
        
        if emotions is None:
            emotions = ['neutral'] * len(texts)
        
        file_paths = []
        for i, (text, emotion) in enumerate(zip(texts, emotions)):
            print(f"处理中 ({i+1}/{len(texts)}): {text[:30]}... [{emotion}]")
            save_path = os.path.join(save_dir, f"batch_{i:03d}_{emotion}.wav")
            self.synthesize(text, emotion=emotion, save_path=save_path)
            file_paths.append(save_path)
        
        return file_paths
    
    def get_available_emotions(self) -> List[str]:
        """返回预定义的情感标签列表。"""
        return list(self.emotion_params_map.keys())

# 示例:如何创建音色嵌入(高级功能,用于克隆特定音色)
    def create_speaker_embedding(self, reference_audio_path: str) -> np.ndarray:
        """
        从参考音频中提取说话人嵌入。
        
        Args:
            reference_audio_path: 参考音频文件路径(.wav)。
            
        Returns:
            spk_emb: 说话人嵌入向量。
        """
        # 注意:ChatTTS官方尚未直接提供此接口,此处为示意。
        # 实际可参考其 `infer` 方法中 `spk_emb` 的用法。
        # 这里返回一个随机向量作为占位符。
        print(f"[提示] 音色克隆功能需参考官方最新实现。")
        return np.random.randn(1, 1024).astype(np.float32) # 占位符

🚀 第三阶段:使用与测试

创建一个测试脚本 test_tts.py 来使用我们的封装类。
在这里插入图片描述

from EmotionalTTS import EmotionalTTS
import soundfile as sf
import simpleaudio as sa # 用于直接播放,安装: pip install simpleaudio

def main():
    # 1. 初始化引擎
    print("="*50)
    print("初始化情感TTS引擎...")
    tts_engine = EmotionalTTS(device='cuda') # 如果你有GPU
    # tts_engine = EmotionalTTS(device='cpu') # 使用CPU
    
    # 2. 查看支持的情感
    print("支持的情感:", tts_engine.get_available_emotions())
    print("="*50)
    
    # 3. 单句合成示例
    test_text = "你好,世界!这是一个测试,看看情感语音合成效果怎么样。"
    
    # 用不同的情感合成同一句话
    for emo in ['neutral', 'happy', 'sad', 'angry']:
        print(f"\n>>> 正在用「{emo}」情感合成...")
        audio_data, sr = tts_engine.synthesize(
            text=test_text,
            emotion=emo,
            speed=1.0 if emo != 'sad' else 0.9, # 悲伤时语速放慢
            save_path=f"./output/demo_{emo}.wav" # 保存文件
        )
        # 尝试播放(如果环境支持)
        try:
            play_obj = sa.play_buffer(audio_data, 1, 2, sr)
            play_obj.wait_done()
        except:
            print(f"音频已保存,如需播放请查看文件: demo_{emo}.wav")
    
    # 4. 批量合成示例
    print("\n" + "="*50)
    print("开始批量合成示例...")
    
    batch_texts = [
        "早上好,今天天气真不错。",
        "我对此感到非常失望。",
        "太棒了!我们终于成功了!",
        "请立即离开这个地方。"
    ]
    batch_emotions = ['friendly', 'sad', 'happy', 'angry']
    
    saved_files = tts_engine.batch_synthesize(
        texts=batch_texts,
        emotions=batch_emotions,
        save_dir="./output/batch"
    )
    print(f"批量合成完成,共生成 {len(saved_files)} 个文件。")
    
    # 5. 高级:尝试自定义情感参数(直接修改映射)
    print("\n" + "="*50)
    print("高级:自定义情感参数...")
    tts_engine.emotion_params_map['whisper'] = {'temperature': 0.2, 'spk_emb': None} # 耳语
    audio_custom, _ = tts_engine.synthesize(
        "这是一个秘密,我只告诉你一个人。",
        emotion='whisper',
        save_path="./output/whisper_secret.wav"
    )
    print("自定义情感「whisper」合成完成。")

if __name__ == "__main__":
    # 确保有输出目录
    import os
    os.makedirs("./output", exist_ok=True)
    os.makedirs("./output/batch", exist_ok=True)
    
    main()
    print("\n所有测试完成!请检查 './output' 目录下的音频文件。")

🔧 第四阶段:部署为API服务(Flask示例)

将你的封装部署为Web服务,以便其他程序调用。创建 api_service.py
在这里插入图片描述

from flask import Flask, request, jsonify, send_file
from EmotionalTTS import EmotionalTTS
import io
import soundfile as sf
import numpy as np
import uuid
import os

app = Flask(__name__)
tts_engine = None

def init_engine():
    global tts_engine
    print("正在加载TTS模型...")
    tts_engine = EmotionalTTS(device='cpu') # API服务通常用CPU
    print("模型加载完毕,API服务就绪。")

init_engine()

@app.route('/synthesize', methods=['POST'])
def synthesize():
    """API端点:文本转语音"""
    data = request.json
    
    # 解析请求参数
    text = data.get('text', '')
    emotion = data.get('emotion', 'neutral')
    speed = float(data.get('speed', 1.0))
    
    if not text:
        return jsonify({'error': '文本内容不能为空'}), 400
    
    # 调用引擎合成
    try:
        audio_data, sr = tts_engine.synthesize(
            text=text,
            emotion=emotion,
            speed=speed
        )
        
        # 将音频数据转为字节流返回
        audio_bytes = io.BytesIO()
        sf.write(audio_bytes, audio_data, samplerate=sr, format='WAV')
        audio_bytes.seek(0)
        
        # 也可以选择保存到文件后返回URL(生产环境建议)
        # filename = f"{uuid.uuid4()}.wav"
        # filepath = os.path.join('./audio_cache', filename)
        # sf.write(filepath, audio_data, sr)
        # return jsonify({'url': f'/audio/{filename}'})
        
        return send_file(
            audio_bytes,
            mimetype='audio/wav',
            as_attachment=True,
            download_name=f'speech_{emotion}.wav'
        )
        
    except Exception as e:
        return jsonify({'error': f'合成失败: {str(e)}'}), 500

@app.route('/emotions', methods=['GET'])
def list_emotions():
    """返回支持的情感列表"""
    return jsonify({'emotions': tts_engine.get_available_emotions()})

if __name__ == '__main__':
    os.makedirs('./audio_cache', exist_ok=True)
    # 生产环境请使用 waitress 或 gunicorn,不要用debug模式
    app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=False)

启动API服务:

python api_service.py

使用CURL测试API:

curl -X POST http://127.0.0.1:5000/synthesize \
  -H "Content-Type: application/json" \
  -d '{"text": "你好,欢迎使用情感TTS API服务", "emotion": "friendly", "speed": 1.1}' \
  --output output_api.wav

📝 重要补充与高级扩展

1. 如何提升效果与定制化

  • 优化情感:调整 emotion_params_map 中的 temperature 值(0.1-1.5),值越高声音越有表现力(可能不稳定),值越低保真稳定。
  • 细粒度控制:在文本中插入 ChatTTS特定的控制符,例如 [uv_break](短停顿)、[laugh](笑声),能让效果更生动。
  • 微调模型:如果想针对特定场景(如广播剧)优化,需准备高质量的 (文本, 音频, 情感标签) 配对数据,使用ChatTTS训练脚本进行微调。

2. 项目结构建议

MyEmotionalTTS/
├── EmotionalTTS.py      # 核心封装类
├── init_model.py        # 初始化脚本
├── test_tts.py          # 测试脚本
├── api_service.py       # Flask API服务
├── requirements.txt     # 依赖列表
├── output/              # 生成音频目录
└── README.md            # 项目说明

3. 生产环境注意事项

  • 性能:首次推理较慢,后续会缓存。如需高并发,考虑模型预热队列系统
  • 稳定性:API服务中务必添加异常处理输入验证
  • 内存:加载模型约占用2-3GB GPU内存(或更多CPU内存)。可尝试使用 torch.compile 或模型量化(如 torch.quantization)进行优化。

这个方案提供了从安装、封装、测试到部署的完整代码链路。可以直接复制代码运行,并根据注释进行修改和扩展。
在这里插入图片描述

评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百锦再@新空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值