主要功能: - ✅ 离线语音识别 (ASR) - Paraformer中文模型 - ✅ 在线语音识别 - Streaming Paraformer中英文双语模型 - ✅ 语音合成 (TTS) - MeloTTS中英文混合模型 - ✅ 语音唤醒 (KWS) - Zipformer关键词检测模型 - ✅ 麦克风录音功能 - 支持多种格式和实时转换 - ✅ 模型设置界面 - 完整的图形化配置管理 KWS优化亮点: - 🎯 成功实现关键词检测 (测试成功率10%→预期50%+) - ⚙️ 可调参数: 阈值、活跃路径、尾随空白、分数权重、线程数 - 🔧 智能参数验证和实时反馈 - 📊 详细的调试信息和成功统计 - 🎛️ 用户友好的设置界面 技术架构: - 模块化设计: ASRManager, TTSManager, KWSManager - 实时音频处理: 自动格式转换 (任意格式→16kHz单声道) - 智能设备检测: 自动选择最佳音频格式 - 完整资源管理: 正确的创建和销毁流程 - 跨平台支持: macOS优化的音频权限处理 界面特性: - 2×2网格布局: ASR、TTS、录音、KWS四大功能模块 - 分离录音设置: 设备参数 + 输出格式独立配置 - 实时状态显示: 音频电平、处理次数、成功统计 - 详细的用户指导和错误提示
122 lines
4.2 KiB
C++
122 lines
4.2 KiB
C++
#include "TTSManager.h"
|
||
#include <QDir>
|
||
#include <QFile>
|
||
#include <QDebug>
|
||
|
||
TTSManager::TTSManager(QObject* parent) : QObject(parent) {
|
||
}
|
||
|
||
TTSManager::~TTSManager() {
|
||
cleanup();
|
||
}
|
||
|
||
bool TTSManager::initialize() {
|
||
QString dataPath = QDir::homePath() + "/.config/QSmartAssistant/Data/";
|
||
|
||
// 初始化TTS模型 - 支持中英文混合
|
||
// 优先尝试使用新的vits-melo-tts-zh_en模型
|
||
QString ttsModelPath = dataPath + "vits-melo-tts-zh_en/model.int8.onnx";
|
||
QString ttsLexiconPath = dataPath + "vits-melo-tts-zh_en/lexicon.txt";
|
||
QString ttsTokensPath = dataPath + "vits-melo-tts-zh_en/tokens.txt";
|
||
QString ttsDataDirPath = ""; // melo-tts模型不需要额外的数据目录
|
||
QString ttsDictDirPath = dataPath + "vits-melo-tts-zh_en/dict"; // jieba字典目录
|
||
|
||
// 如果新模型不存在,尝试其他中英文模型
|
||
if (!QFile::exists(ttsModelPath)) {
|
||
ttsModelPath = dataPath + "vits-zh-en/vits-zh-en.onnx";
|
||
ttsLexiconPath = dataPath + "vits-zh-en/lexicon.txt";
|
||
ttsTokensPath = dataPath + "vits-zh-en/tokens.txt";
|
||
ttsDataDirPath = dataPath + "vits-zh-en/espeak-ng-data";
|
||
ttsDictDirPath = "";
|
||
}
|
||
|
||
// 最后回退到中文模型
|
||
if (!QFile::exists(ttsModelPath)) {
|
||
ttsModelPath = dataPath + "vits-zh-aishell3/vits-aishell3.int8.onnx";
|
||
ttsLexiconPath = dataPath + "vits-zh-aishell3/lexicon.txt";
|
||
ttsTokensPath = dataPath + "vits-zh-aishell3/tokens.txt";
|
||
ttsDataDirPath = "";
|
||
ttsDictDirPath = "";
|
||
}
|
||
|
||
currentModelPath = ttsModelPath;
|
||
|
||
memset(&ttsConfig, 0, sizeof(ttsConfig));
|
||
ttsConfig.model.vits.noise_scale = 0.667;
|
||
ttsConfig.model.vits.noise_scale_w = 0.8;
|
||
ttsConfig.model.vits.length_scale = 1.0;
|
||
ttsConfig.model.num_threads = 2;
|
||
ttsConfig.model.provider = "cpu";
|
||
|
||
ttsModelPathStd = ttsModelPath.toStdString();
|
||
ttsLexiconPathStd = ttsLexiconPath.toStdString();
|
||
ttsTokensPathStd = ttsTokensPath.toStdString();
|
||
ttsDataDirPathStd = ttsDataDirPath.toStdString();
|
||
ttsDictDirPathStd = ttsDictDirPath.toStdString();
|
||
|
||
ttsConfig.model.vits.model = ttsModelPathStd.c_str();
|
||
ttsConfig.model.vits.lexicon = ttsLexiconPathStd.c_str();
|
||
ttsConfig.model.vits.tokens = ttsTokensPathStd.c_str();
|
||
|
||
// 如果有espeak数据目录,设置它以支持英文发音
|
||
if (!ttsDataDirPath.isEmpty()) {
|
||
ttsConfig.model.vits.data_dir = ttsDataDirPathStd.c_str();
|
||
}
|
||
|
||
// 如果有jieba字典目录,设置它以支持中文分词
|
||
if (!ttsDictDirPath.isEmpty()) {
|
||
ttsConfig.model.vits.dict_dir = ttsDictDirPathStd.c_str();
|
||
}
|
||
|
||
ttsSynthesizer = const_cast<SherpaOnnxOfflineTts*>(SherpaOnnxCreateOfflineTts(&ttsConfig));
|
||
|
||
qDebug() << "TTS合成器:" << (ttsSynthesizer ? "成功" : "失败");
|
||
qDebug() << "TTS模型类型:" << getModelType();
|
||
|
||
return ttsSynthesizer != nullptr;
|
||
}
|
||
|
||
bool TTSManager::synthesizeText(const QString& text, int speakerId, const QString& outputPath) {
|
||
if (!ttsSynthesizer) {
|
||
return false;
|
||
}
|
||
|
||
// 生成音频
|
||
const SherpaOnnxGeneratedAudio* audio = SherpaOnnxOfflineTtsGenerate(
|
||
ttsSynthesizer, text.toUtf8().constData(), speakerId, 1.0);
|
||
|
||
if (!audio || audio->n == 0) {
|
||
if (audio) SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio);
|
||
return false;
|
||
}
|
||
|
||
// 保存为WAV文件
|
||
bool success = SherpaOnnxWriteWave(audio->samples, audio->n, audio->sample_rate,
|
||
outputPath.toUtf8().constData());
|
||
|
||
SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio);
|
||
return success;
|
||
}
|
||
|
||
QString TTSManager::getModelType() const {
|
||
if (currentModelPath.contains("vits-melo-tts-zh_en")) {
|
||
return "MeloTTS中英文混合模型";
|
||
} else if (currentModelPath.contains("vits-zh-en")) {
|
||
return "VITS中英文混合模型";
|
||
} else {
|
||
return "中文模型";
|
||
}
|
||
}
|
||
|
||
bool TTSManager::isMultilingualModel() const {
|
||
return currentModelPath.contains("vits-melo-tts-zh_en") ||
|
||
currentModelPath.contains("vits-zh-en");
|
||
}
|
||
|
||
void TTSManager::cleanup() {
|
||
// 清理TTS合成器
|
||
if (ttsSynthesizer) {
|
||
SherpaOnnxDestroyOfflineTts(ttsSynthesizer);
|
||
ttsSynthesizer = nullptr;
|
||
}
|
||
} |