mirror of
https://github.com/m5stack/StackFlow.git
synced 2026-05-20 11:32:11 -07:00
优化g2p流程,可以处理多音字,中英混合的情况等等
This commit is contained in:
@@ -4,14 +4,20 @@
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
#include <iostream> // 用于日志输出
|
||||
|
||||
std::vector<std::string> split (const std::string &s, char delim) {
|
||||
// 使用引用传参优化split函数,避免不必要的拷贝
|
||||
std::vector<std::string> split(const std::string &s, char delim) {
|
||||
std::vector<std::string> result;
|
||||
std::stringstream ss (s);
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
while (getline (ss, item, delim)) {
|
||||
result.push_back (item);
|
||||
while (getline(ss, item, delim)) {
|
||||
if (!item.empty()) { // 避免添加空字符串
|
||||
result.push_back(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -19,134 +25,282 @@ std::vector<std::string> split (const std::string &s, char delim) {
|
||||
class Lexicon {
|
||||
private:
|
||||
std::unordered_map<std::string, std::pair<std::vector<int>, std::vector<int>>> lexicon;
|
||||
size_t max_phrase_length; // 追踪词典中最长的词组长度
|
||||
std::pair<std::vector<int>, std::vector<int>> unknown_token; // '_'的发音作为未知词的默认值
|
||||
std::unordered_map<int, std::string> reverse_tokens; // 用于将音素ID转回音素符号,用于日志
|
||||
|
||||
public:
|
||||
Lexicon(const std::string& lexicon_filename, const std::string& tokens_filename) {
|
||||
Lexicon(const std::string& lexicon_filename, const std::string& tokens_filename) : max_phrase_length(0) {
|
||||
std::unordered_map<std::string, int> tokens;
|
||||
|
||||
// 加载tokens
|
||||
std::ifstream ifs(tokens_filename);
|
||||
assert(ifs.is_open());
|
||||
|
||||
std::string line;
|
||||
while ( std::getline(ifs, line) ) {
|
||||
while (std::getline(ifs, line)) {
|
||||
auto splitted_line = split(line, ' ');
|
||||
tokens.insert({splitted_line[0], std::stoi(splitted_line[1])});
|
||||
if (splitted_line.size() >= 2) {
|
||||
int token_id = std::stoi(splitted_line[1]);
|
||||
tokens.insert({splitted_line[0], token_id});
|
||||
reverse_tokens[token_id] = splitted_line[0]; // 建立反向映射
|
||||
}
|
||||
}
|
||||
ifs.close();
|
||||
|
||||
// 加载lexicon
|
||||
ifs.open(lexicon_filename);
|
||||
assert(ifs.is_open());
|
||||
while ( std::getline(ifs, line) ) {
|
||||
while (std::getline(ifs, line)) {
|
||||
auto splitted_line = split(line, ' ');
|
||||
if (splitted_line.empty()) continue;
|
||||
|
||||
std::string word_or_phrase = splitted_line[0];
|
||||
|
||||
// 更新最长词组长度
|
||||
auto chars = splitEachChar(word_or_phrase);
|
||||
max_phrase_length = std::max(max_phrase_length, chars.size());
|
||||
|
||||
size_t phone_tone_len = splitted_line.size() - 1;
|
||||
size_t half_len = phone_tone_len / 2;
|
||||
std::vector<int> phones, tones;
|
||||
|
||||
for (size_t i = 0; i < phone_tone_len; i++) {
|
||||
auto phone_or_tone = splitted_line[i + 1];
|
||||
if (i < half_len) {
|
||||
phones.push_back(tokens[phone_or_tone]);
|
||||
if (tokens.find(phone_or_tone) != tokens.end()) {
|
||||
phones.push_back(tokens[phone_or_tone]);
|
||||
}
|
||||
} else {
|
||||
tones.push_back(std::stoi(phone_or_tone));
|
||||
}
|
||||
}
|
||||
|
||||
lexicon.insert({word_or_phrase, std::make_pair(phones, tones)});
|
||||
lexicon[word_or_phrase] = std::make_pair(phones, tones);
|
||||
}
|
||||
|
||||
// 添加特殊映射
|
||||
lexicon["呣"] = lexicon["母"];
|
||||
lexicon["嗯"] = lexicon["恩"];
|
||||
|
||||
// 添加标点符号
|
||||
const std::vector<std::string> punctuation{"!", "?", "…", ",", ".", "'", "-"};
|
||||
for (auto p : punctuation) {
|
||||
int i = tokens[p];
|
||||
int tone = 0;
|
||||
lexicon[p] = std::make_pair(std::vector<int>{i}, std::vector<int>{tone});
|
||||
for (const auto& p : punctuation) {
|
||||
if (tokens.find(p) != tokens.end()) {
|
||||
int i = tokens[p];
|
||||
lexicon[p] = std::make_pair(std::vector<int>{i}, std::vector<int>{0});
|
||||
}
|
||||
}
|
||||
lexicon[" "] = std::make_pair(std::vector<int>{tokens["_"]}, std::vector<int>{0});
|
||||
|
||||
// 设置'_'作为未知词的发音
|
||||
assert(tokens.find("_") != tokens.end()); // 确保tokens中包含"_"
|
||||
unknown_token = std::make_pair(std::vector<int>{tokens["_"]}, std::vector<int>{0});
|
||||
|
||||
// 空格映射到'_'的发音
|
||||
lexicon[" "] = unknown_token;
|
||||
|
||||
// 中文标点转换映射
|
||||
lexicon[","] = lexicon[","];
|
||||
lexicon["。"] = lexicon["."];
|
||||
lexicon["!"] = lexicon["!"];
|
||||
lexicon["?"] = lexicon["?"];
|
||||
|
||||
// 输出词典信息
|
||||
std::cout << "词典加载完成,包含 " << lexicon.size() << " 个条目,最长词组长度: " << max_phrase_length << std::endl;
|
||||
}
|
||||
|
||||
std::vector<std::string> splitEachChar(const std::string& text)
|
||||
{
|
||||
std::vector<std::string> splitEachChar(const std::string& text) {
|
||||
std::vector<std::string> words;
|
||||
std::string input(text);
|
||||
int len = input.length();
|
||||
int len = text.length();
|
||||
int i = 0;
|
||||
|
||||
while (i < len) {
|
||||
int next = 1;
|
||||
if ((input[i] & 0x80) == 0x00) {
|
||||
// std::cout << "one character: " << input[i] << std::endl;
|
||||
} else if ((input[i] & 0xE0) == 0xC0) {
|
||||
next = 2;
|
||||
// std::cout << "two character: " << input.substr(i, next) << std::endl;
|
||||
} else if ((input[i] & 0xF0) == 0xE0) {
|
||||
next = 3;
|
||||
// std::cout << "three character: " << input.substr(i, next) << std::endl;
|
||||
} else if ((input[i] & 0xF8) == 0xF0) {
|
||||
next = 4;
|
||||
// std::cout << "four character: " << input.substr(i, next) << std::endl;
|
||||
}
|
||||
words.push_back(input.substr(i, next));
|
||||
i += next;
|
||||
int next = 1;
|
||||
if ((text[i] & 0x80) == 0x00) {
|
||||
// ASCII
|
||||
} else if ((text[i] & 0xE0) == 0xC0) {
|
||||
next = 2; // 2字节UTF-8
|
||||
} else if ((text[i] & 0xF0) == 0xE0) {
|
||||
next = 3; // 3字节UTF-8
|
||||
} else if ((text[i] & 0xF8) == 0xF0) {
|
||||
next = 4; // 4字节UTF-8
|
||||
}
|
||||
words.push_back(text.substr(i, next));
|
||||
i += next;
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
bool is_english(std::string s) {
|
||||
if (s.size() == 1)
|
||||
return (s[0] >= 'A' && s[0] <= 'Z') || (s[0] >= 'a' && s[0] <= 'z');
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> merge_english(const std::vector<std::string>& splitted_text) {
|
||||
std::vector<std::string> words;
|
||||
int i = 0;
|
||||
while (i < splitted_text.size()) {
|
||||
std::string s;
|
||||
if (is_english(splitted_text[i])) {
|
||||
while (i < splitted_text.size()) {
|
||||
if (!is_english(splitted_text[i])) {
|
||||
break;
|
||||
}
|
||||
s += splitted_text[i];
|
||||
i++;
|
||||
}
|
||||
// to lowercase
|
||||
std::transform(s.begin(), s.end(), s.begin(),
|
||||
[](unsigned char c){ return std::tolower(c); });
|
||||
words.push_back(s);
|
||||
if (i >= splitted_text.size())
|
||||
break;
|
||||
}
|
||||
else {
|
||||
words.push_back(splitted_text[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return words;
|
||||
bool is_english(const std::string& s) {
|
||||
return s.size() == 1 && ((s[0] >= 'A' && s[0] <= 'Z') || (s[0] >= 'a' && s[0] <= 'z'));
|
||||
}
|
||||
|
||||
// 根据词典中的内容,使用最长匹配算法处理输入文本
|
||||
void convert(const std::string& text, std::vector<int>& phones, std::vector<int>& tones) {
|
||||
auto splitted_text = splitEachChar(text);
|
||||
auto zh_mix_en = merge_english(splitted_text);
|
||||
for (auto c : zh_mix_en) {
|
||||
std::string s{c};
|
||||
if (s == ",")
|
||||
s = ",";
|
||||
else if (s == "。")
|
||||
s = ".";
|
||||
else if (s == "!")
|
||||
s = "!";
|
||||
else if (s == "?")
|
||||
s = "?";
|
||||
std::cout << "\n开始处理文本: \"" << text << "\"" << std::endl;
|
||||
std::cout << "=======匹配结果=======" << std::endl;
|
||||
std::cout << "单元\t|\t音素\t|\t声调" << std::endl;
|
||||
std::cout << "-----------------------------" << std::endl;
|
||||
|
||||
// 在开头添加'_'边界标记
|
||||
phones.insert(phones.end(), unknown_token.first.begin(), unknown_token.first.end());
|
||||
tones.insert(tones.end(), unknown_token.second.begin(), unknown_token.second.end());
|
||||
std::cout << "<BOS>\t|\t" << phonesToString(unknown_token.first) << "\t|\t"
|
||||
<< tonesToString(unknown_token.second) << std::endl;
|
||||
|
||||
auto chars = splitEachChar(text);
|
||||
int i = 0;
|
||||
|
||||
while (i < chars.size()) {
|
||||
// 处理英文单词
|
||||
if (is_english(chars[i])) {
|
||||
std::string eng_word;
|
||||
int start = i;
|
||||
while (i < chars.size() && is_english(chars[i])) {
|
||||
eng_word += chars[i++];
|
||||
}
|
||||
|
||||
// 英文转小写
|
||||
std::string orig_word = eng_word; // 保留原始单词用于日志
|
||||
std::transform(eng_word.begin(), eng_word.end(), eng_word.begin(),
|
||||
[](unsigned char c){ return std::tolower(c); });
|
||||
|
||||
// 如果词典中有这个英文单词,使用它;否则使用'_'的发音
|
||||
if (lexicon.find(eng_word) != lexicon.end()) {
|
||||
auto& [eng_phones, eng_tones] = lexicon[eng_word];
|
||||
phones.insert(phones.end(), eng_phones.begin(), eng_phones.end());
|
||||
tones.insert(tones.end(), eng_tones.begin(), eng_tones.end());
|
||||
|
||||
// 打印匹配信息
|
||||
std::cout << orig_word << "\t|\t" << phonesToString(eng_phones) << "\t|\t"
|
||||
<< tonesToString(eng_tones) << std::endl;
|
||||
} else {
|
||||
// 未找到单词,使用'_'的发音
|
||||
phones.insert(phones.end(), unknown_token.first.begin(), unknown_token.first.end());
|
||||
tones.insert(tones.end(), unknown_token.second.begin(), unknown_token.second.end());
|
||||
|
||||
// 打印未匹配信息
|
||||
std::cout << orig_word << "\t|\t" << phonesToString(unknown_token.first) << " (未匹配)\t|\t"
|
||||
<< tonesToString(unknown_token.second) << std::endl;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// 处理非英文字符(如空格、标点)
|
||||
std::string c = chars[i++];
|
||||
if (c == " ") continue; // 跳过空格
|
||||
// 回退一步,用于最长匹配
|
||||
i--;
|
||||
|
||||
auto phones_and_tones = lexicon[" "];
|
||||
if (lexicon.find(s) != lexicon.end()) {
|
||||
phones_and_tones = lexicon[s];
|
||||
|
||||
// 最长匹配算法处理中文/日文
|
||||
bool matched = false;
|
||||
// 尝试从最长的词组开始匹配
|
||||
for (size_t len = std::min(max_phrase_length, chars.size() - i); len > 0 && !matched; --len) {
|
||||
std::string phrase;
|
||||
for (size_t j = 0; j < len; ++j) {
|
||||
phrase += chars[i + j];
|
||||
}
|
||||
|
||||
if (lexicon.find(phrase) != lexicon.end()) {
|
||||
auto& [phrase_phones, phrase_tones] = lexicon[phrase];
|
||||
phones.insert(phones.end(), phrase_phones.begin(), phrase_phones.end());
|
||||
tones.insert(tones.end(), phrase_tones.begin(), phrase_tones.end());
|
||||
|
||||
// 打印匹配信息
|
||||
std::cout << phrase << "\t|\t" << phonesToString(phrase_phones) << "\t|\t"
|
||||
<< tonesToString(phrase_tones) << std::endl;
|
||||
|
||||
i += len;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有匹配到任何词组,使用'_'的发音
|
||||
if (!matched) {
|
||||
std::string c = chars[i++];
|
||||
std::string s = c;
|
||||
|
||||
// 中文标点符号转换
|
||||
std::string orig_char = s; // 保留原始字符用于日志
|
||||
if (s == ",") s = ",";
|
||||
else if (s == "。") s = ".";
|
||||
else if (s == "!") s = "!";
|
||||
else if (s == "?") s = "?";
|
||||
|
||||
// 如果词典中找不到,则使用'_'的发音
|
||||
if (lexicon.find(s) != lexicon.end()) {
|
||||
auto& [char_phones, char_tones] = lexicon[s];
|
||||
phones.insert(phones.end(), char_phones.begin(), char_phones.end());
|
||||
tones.insert(tones.end(), char_tones.begin(), char_tones.end());
|
||||
|
||||
// 打印匹配信息
|
||||
std::cout << orig_char << "\t|\t" << phonesToString(char_phones) << "\t|\t"
|
||||
<< tonesToString(char_tones) << std::endl;
|
||||
} else {
|
||||
phones.insert(phones.end(), unknown_token.first.begin(), unknown_token.first.end());
|
||||
tones.insert(tones.end(), unknown_token.second.begin(), unknown_token.second.end());
|
||||
|
||||
// 打印未匹配信息
|
||||
std::cout << orig_char << "\t|\t" << phonesToString(unknown_token.first) << " (未匹配)\t|\t"
|
||||
<< tonesToString(unknown_token.second) << std::endl;
|
||||
}
|
||||
}
|
||||
phones.insert(phones.end(), phones_and_tones.first.begin(), phones_and_tones.first.end());
|
||||
tones.insert(tones.end(), phones_and_tones.second.begin(), phones_and_tones.second.end());
|
||||
}
|
||||
|
||||
// 在末尾添加'_'边界标记
|
||||
phones.insert(phones.end(), unknown_token.first.begin(), unknown_token.first.end());
|
||||
tones.insert(tones.end(), unknown_token.second.begin(), unknown_token.second.end());
|
||||
std::cout << "<EOS>\t|\t" << phonesToString(unknown_token.first) << "\t|\t"
|
||||
<< tonesToString(unknown_token.second) << std::endl;
|
||||
|
||||
// 汇总打印最终结果
|
||||
std::cout << "\n处理结果汇总:" << std::endl;
|
||||
std::cout << "原文: " << text << std::endl;
|
||||
std::cout << "音素: " << phonesToString(phones) << std::endl;
|
||||
std::cout << "声调: " << tonesToString(tones) << std::endl;
|
||||
std::cout << "====================" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
// 处理单个字符
|
||||
void processChar(const std::string& c, std::vector<int>& phones, std::vector<int>& tones) {
|
||||
std::string s = c;
|
||||
|
||||
// 中文标点符号转换
|
||||
if (s == ",") s = ",";
|
||||
else if (s == "。") s = ".";
|
||||
else if (s == "!") s = "!";
|
||||
else if (s == "?") s = "?";
|
||||
|
||||
// 如果词典中找不到,则使用'_'的发音
|
||||
auto& phones_and_tones = (lexicon.find(s) != lexicon.end()) ? lexicon[s] : unknown_token;
|
||||
|
||||
phones.insert(phones.end(), phones_and_tones.first.begin(), phones_and_tones.first.end());
|
||||
tones.insert(tones.end(), phones_and_tones.second.begin(), phones_and_tones.second.end());
|
||||
}
|
||||
|
||||
// 将音素ID数组转换为字符串用于日志输出
|
||||
std::string phonesToString(const std::vector<int>& phones) {
|
||||
std::string result;
|
||||
for (auto id : phones) {
|
||||
if (!result.empty()) result += " ";
|
||||
if (reverse_tokens.find(id) != reverse_tokens.end()) {
|
||||
result += reverse_tokens[id];
|
||||
} else {
|
||||
result += "<" + std::to_string(id) + ">";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 将声调数组转换为字符串用于日志输出
|
||||
std::string tonesToString(const std::vector<int>& tones) {
|
||||
std::string result;
|
||||
for (auto tone : tones) {
|
||||
if (!result.empty()) result += " ";
|
||||
result += std::to_string(tone);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
import socket
|
||||
import json
|
||||
import argparse
|
||||
import uuid
|
||||
import time
|
||||
|
||||
def create_tcp_connection(host, port):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((host, port))
|
||||
return sock
|
||||
|
||||
def send_json(sock, data):
|
||||
json_data = json.dumps(data, ensure_ascii=False) + '\n'
|
||||
print(f"Sending: {json_data}")
|
||||
sock.sendall(json_data.encode('utf-8'))
|
||||
|
||||
def receive_response(sock, timeout=None):
|
||||
"""接收响应,带可选的超时设置"""
|
||||
old_timeout = sock.gettimeout()
|
||||
try:
|
||||
if timeout is not None:
|
||||
sock.settimeout(timeout)
|
||||
response = ''
|
||||
while True:
|
||||
part = sock.recv(4096).decode('utf-8')
|
||||
if not part: # 连接已关闭
|
||||
return response.strip()
|
||||
response += part
|
||||
if '\n' in response:
|
||||
break
|
||||
return response.strip()
|
||||
except socket.timeout:
|
||||
return None
|
||||
finally:
|
||||
sock.settimeout(old_timeout)
|
||||
|
||||
def close_connection(sock):
|
||||
if sock:
|
||||
sock.close()
|
||||
|
||||
def create_melotts_setup_data(request_id="melotts_setup"):
|
||||
return {
|
||||
"request_id": request_id,
|
||||
"work_id": "melotts",
|
||||
"action": "setup",
|
||||
"object": "melotts.setup",
|
||||
"data": {
|
||||
"model": "melotts_zh-cn",
|
||||
"response_format": "sys.pcm",
|
||||
"input": "tts.utf-8",
|
||||
"enoutput": False
|
||||
}
|
||||
}
|
||||
|
||||
def list_available_tasks(sock):
|
||||
"""获取可用的任务列表"""
|
||||
request_id = str(uuid.uuid4())
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": "melotts",
|
||||
"action": "taskinfo"
|
||||
})
|
||||
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
return {"error": "No response received"}
|
||||
try:
|
||||
return json.loads(response)
|
||||
except:
|
||||
return {"error": "Failed to parse response"}
|
||||
|
||||
def parse_setup_response(response_data, sent_request_id):
|
||||
error = response_data.get('error')
|
||||
request_id = response_data.get('request_id')
|
||||
|
||||
if request_id != sent_request_id:
|
||||
print(f"Request ID mismatch: sent {sent_request_id}, received {request_id}")
|
||||
return None
|
||||
if error and error.get('code') != 0:
|
||||
print(f"Error Code: {error['code']}, Message: {error['message']}")
|
||||
return None
|
||||
return response_data.get('work_id')
|
||||
|
||||
def setup(sock, setup_data):
|
||||
sent_request_id = setup_data['request_id']
|
||||
send_json(sock, setup_data)
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
print("No response received during setup")
|
||||
return None
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
return parse_setup_response(response_data, sent_request_id)
|
||||
except json.JSONDecodeError:
|
||||
print(f"Invalid JSON response: {response}")
|
||||
return None
|
||||
|
||||
def melotts_tts_inference(sock, melotts_work_id, text, use_stream=False):
|
||||
request_id = str(uuid.uuid4())
|
||||
|
||||
# 根据文档,选择流式或非流式请求格式
|
||||
if use_stream:
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": melotts_work_id,
|
||||
"action": "inference",
|
||||
"object": "melotts.utf-8.stream",
|
||||
"data": {
|
||||
"delta": text,
|
||||
"index": 0,
|
||||
"finish": True
|
||||
}
|
||||
})
|
||||
else:
|
||||
# 非流式请求
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": melotts_work_id,
|
||||
"action": "inference",
|
||||
"object": "melotts.utf-8",
|
||||
"data": text
|
||||
})
|
||||
|
||||
# 关键更改:不等待响应或设置更长的超时时间
|
||||
# 由于使用sys.pcm格式,音频会直接播放,可能不会立即返回响应
|
||||
print("语音合成请求已发送,正在播放...")
|
||||
|
||||
# 可选:设置一个较短的超时来检查是否有响应,但不要因为没响应就认为失败
|
||||
response = receive_response(sock, timeout=0.5) # 设置短超时,只是尝试看有没有响应
|
||||
if response:
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
error = response_data.get('error')
|
||||
if error and error.get('code') != 0:
|
||||
print(f"收到错误响应: Code={error['code']}, Message={error['message']}")
|
||||
return False
|
||||
print("收到成功响应")
|
||||
except:
|
||||
print(f"收到非JSON响应: {response[:100]}...")
|
||||
else:
|
||||
# 不收到响应也视为成功,因为服务器可能正忙于播放音频
|
||||
print("未收到响应,但这不一定表示失败(服务器可能正忙于处理音频)")
|
||||
|
||||
# 这里给TTS处理一些时间
|
||||
# 根据文本长度估计播放时间
|
||||
estimated_time = len(text) * 0.1 # 假设每个字符需要0.1秒
|
||||
estimated_time = max(1.0, min(estimated_time, 10.0)) # 至少1秒,最多10秒
|
||||
print(f"等待大约 {estimated_time:.1f} 秒让音频播放完...")
|
||||
time.sleep(estimated_time)
|
||||
|
||||
return True
|
||||
|
||||
def exit_session(sock, melotts_work_id):
|
||||
send_json(sock, {
|
||||
"request_id": "melotts_exit",
|
||||
"work_id": melotts_work_id,
|
||||
"action": "exit"
|
||||
})
|
||||
response = receive_response(sock, timeout=2.0)
|
||||
if not response:
|
||||
print("退出命令已发送,但未收到响应")
|
||||
return True # 假设成功
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
print("Exit Response:", response_data)
|
||||
return response_data.get('error', {}).get('code', -1) == 0
|
||||
except:
|
||||
print("Failed to parse exit response")
|
||||
return False
|
||||
|
||||
def get_task_info(sock, work_id):
|
||||
"""获取任务的详细信息"""
|
||||
request_id = str(uuid.uuid4())
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": work_id,
|
||||
"action": "taskinfo"
|
||||
})
|
||||
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
return {"error": "No response received"}
|
||||
try:
|
||||
return json.loads(response)
|
||||
except:
|
||||
return {"error": "Failed to parse response"}
|
||||
|
||||
def main(host, port):
|
||||
sock = create_tcp_connection(host, port)
|
||||
try:
|
||||
print("Setting up MeloTTS...")
|
||||
setup_data = create_melotts_setup_data()
|
||||
melotts_work_id = setup(sock, setup_data)
|
||||
|
||||
if not melotts_work_id:
|
||||
print("Setup failed. Checking available tasks...")
|
||||
task_list = list_available_tasks(sock)
|
||||
print("Available tasks:", task_list)
|
||||
return
|
||||
|
||||
print(f"MeloTTS SETUP finished, work_id: {melotts_work_id}")
|
||||
|
||||
# 获取并显示任务详细信息
|
||||
task_info = get_task_info(sock, melotts_work_id)
|
||||
print("Task info:", task_info)
|
||||
|
||||
# 选择流式或非流式模式
|
||||
use_stream = input("是否使用流式输入? (y/n, 默认n): ").lower() == 'y'
|
||||
|
||||
while True:
|
||||
text = input("请输入你要合成语音的中文文本(输入exit退出):")
|
||||
if text.lower() == 'exit':
|
||||
break
|
||||
|
||||
print("正在合成语音...", flush=True)
|
||||
success = melotts_tts_inference(sock, melotts_work_id, text, use_stream)
|
||||
|
||||
if success:
|
||||
print("语音合成处理完成")
|
||||
else:
|
||||
print("语音合成处理失败")
|
||||
|
||||
# 每次请求间隔
|
||||
time.sleep(1)
|
||||
|
||||
# 退出会话
|
||||
if exit_session(sock, melotts_work_id):
|
||||
print("成功退出会话")
|
||||
else:
|
||||
print("退出会话可能有问题")
|
||||
|
||||
except Exception as e:
|
||||
print(f"程序异常: {e}")
|
||||
finally:
|
||||
close_connection(sock)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='TCP Client for MeloTTS Unit.')
|
||||
parser.add_argument('--host', type=str, default='localhost', help='Server hostname (default: localhost)')
|
||||
parser.add_argument('--port', type=int, default=10001, help='Server port (default: 10001)')
|
||||
args = parser.parse_args()
|
||||
main(args.host, args.port)
|
||||
@@ -0,0 +1,412 @@
|
||||
import socket
|
||||
import json
|
||||
import argparse
|
||||
import uuid
|
||||
import time
|
||||
import sys
|
||||
|
||||
def create_tcp_connection(host, port):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((host, port))
|
||||
return sock
|
||||
|
||||
def send_json(sock, data):
|
||||
json_data = json.dumps(data, ensure_ascii=False) + '\n'
|
||||
print(f"Sending: {json_data}")
|
||||
sock.sendall(json_data.encode('utf-8'))
|
||||
|
||||
def receive_response(sock, timeout=None):
|
||||
"""接收响应,带可选的超时设置"""
|
||||
old_timeout = sock.gettimeout()
|
||||
try:
|
||||
if timeout is not None:
|
||||
sock.settimeout(timeout)
|
||||
response = ''
|
||||
while True:
|
||||
part = sock.recv(4096).decode('utf-8')
|
||||
if not part: # 连接已关闭
|
||||
return response.strip()
|
||||
response += part
|
||||
if '\n' in response:
|
||||
break
|
||||
return response.strip()
|
||||
except socket.timeout:
|
||||
return None
|
||||
finally:
|
||||
sock.settimeout(old_timeout)
|
||||
|
||||
def close_connection(sock):
|
||||
if sock:
|
||||
sock.close()
|
||||
|
||||
def create_tts_setup_data(request_id=None, link_with=None):
|
||||
if request_id is None:
|
||||
request_id = str(uuid.uuid4())
|
||||
|
||||
# 基本设置
|
||||
data = {
|
||||
"model": "single_speaker_fast",
|
||||
"response_format": "sys.pcm",
|
||||
"input": "tts.utf-8",
|
||||
"enoutput": False
|
||||
}
|
||||
|
||||
# 如果需要链接其他单元
|
||||
if link_with:
|
||||
if isinstance(link_with, list):
|
||||
inputs = ["tts.utf-8"] + link_with
|
||||
data["input"] = inputs
|
||||
else:
|
||||
inputs = ["tts.utf-8", link_with]
|
||||
data["input"] = inputs
|
||||
|
||||
return {
|
||||
"request_id": request_id,
|
||||
"work_id": "tts",
|
||||
"action": "setup",
|
||||
"object": "tts.setup",
|
||||
"data": data
|
||||
}
|
||||
|
||||
def list_available_tasks(sock, work_id="tts"):
|
||||
"""获取可用的任务列表"""
|
||||
request_id = str(uuid.uuid4())
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": work_id,
|
||||
"action": "taskinfo"
|
||||
})
|
||||
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
return {"error": "No response received"}
|
||||
try:
|
||||
return json.loads(response)
|
||||
except:
|
||||
return {"error": "Failed to parse response"}
|
||||
|
||||
def parse_setup_response(response_data, sent_request_id):
|
||||
error = response_data.get('error')
|
||||
request_id = response_data.get('request_id')
|
||||
|
||||
if request_id != sent_request_id:
|
||||
print(f"Request ID mismatch: sent {sent_request_id}, received {request_id}")
|
||||
return None
|
||||
if error and error.get('code') != 0:
|
||||
print(f"Error Code: {error['code']}, Message: {error['message']}")
|
||||
return None
|
||||
return response_data.get('work_id')
|
||||
|
||||
def setup(sock, setup_data):
|
||||
sent_request_id = setup_data['request_id']
|
||||
send_json(sock, setup_data)
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
print("No response received during setup")
|
||||
return None
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
return parse_setup_response(response_data, sent_request_id)
|
||||
except json.JSONDecodeError:
|
||||
print(f"Invalid JSON response: {response}")
|
||||
return None
|
||||
|
||||
def link_units(sock, tts_work_id, target_work_id):
|
||||
"""链接TTS单元与其他单元"""
|
||||
request_id = str(uuid.uuid4())
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": tts_work_id,
|
||||
"action": "link",
|
||||
"object": "work_id",
|
||||
"data": target_work_id
|
||||
})
|
||||
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
print("No response received for link request")
|
||||
return False
|
||||
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
error = response_data.get('error', {})
|
||||
if error.get('code') == 0:
|
||||
print(f"成功链接 {tts_work_id} 与 {target_work_id}")
|
||||
return True
|
||||
else:
|
||||
print(f"链接失败: {error.get('message', '未知错误')}")
|
||||
return False
|
||||
except:
|
||||
print(f"Failed to parse link response: {response}")
|
||||
return False
|
||||
|
||||
def unlink_units(sock, tts_work_id, target_work_id):
|
||||
"""取消TTS单元与其他单元的链接"""
|
||||
request_id = str(uuid.uuid4())
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": tts_work_id,
|
||||
"action": "unlink",
|
||||
"object": "work_id",
|
||||
"data": target_work_id
|
||||
})
|
||||
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
print("No response received for unlink request")
|
||||
return False
|
||||
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
error = response_data.get('error', {})
|
||||
if error.get('code') == 0:
|
||||
print(f"成功取消链接 {tts_work_id} 与 {target_work_id}")
|
||||
return True
|
||||
else:
|
||||
print(f"取消链接失败: {error.get('message', '未知错误')}")
|
||||
return False
|
||||
except:
|
||||
print(f"Failed to parse unlink response: {response}")
|
||||
return False
|
||||
|
||||
def pause_unit(sock, tts_work_id):
|
||||
"""暂停TTS单元工作"""
|
||||
request_id = str(uuid.uuid4())
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": tts_work_id,
|
||||
"action": "pause"
|
||||
})
|
||||
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
print("No response received for pause request")
|
||||
return False
|
||||
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
error = response_data.get('error', {})
|
||||
if error.get('code') == 0:
|
||||
print(f"成功暂停 {tts_work_id}")
|
||||
return True
|
||||
else:
|
||||
print(f"暂停失败: {error.get('message', '未知错误')}")
|
||||
return False
|
||||
except:
|
||||
print(f"Failed to parse pause response: {response}")
|
||||
return False
|
||||
|
||||
def resume_unit(sock, tts_work_id):
|
||||
"""恢复TTS单元工作"""
|
||||
request_id = str(uuid.uuid4())
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": tts_work_id,
|
||||
"action": "work"
|
||||
})
|
||||
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
print("No response received for resume request")
|
||||
return False
|
||||
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
error = response_data.get('error', {})
|
||||
if error.get('code') == 0:
|
||||
print(f"成功恢复 {tts_work_id}")
|
||||
return True
|
||||
else:
|
||||
print(f"恢复失败: {error.get('message', '未知错误')}")
|
||||
return False
|
||||
except:
|
||||
print(f"Failed to parse resume response: {response}")
|
||||
return False
|
||||
|
||||
def tts_inference(sock, tts_work_id, text):
|
||||
request_id = str(uuid.uuid4())
|
||||
|
||||
# 非流式请求
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": tts_work_id,
|
||||
"action": "inference",
|
||||
"object": "tts.utf-8",
|
||||
"data": text
|
||||
})
|
||||
|
||||
print("语音合成请求已发送,正在播放...")
|
||||
|
||||
# 可选:设置一个较短的超时来检查是否有响应,但不要因为没响应就认为失败
|
||||
response = receive_response(sock, timeout=0.5) # 设置短超时,只是尝试看有没有响应
|
||||
if response:
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
error = response_data.get('error')
|
||||
if error and error.get('code') != 0:
|
||||
print(f"收到错误响应: Code={error['code']}, Message={error['message']}")
|
||||
return False
|
||||
print("收到成功响应")
|
||||
except:
|
||||
print(f"收到非JSON响应: {response[:100]}...")
|
||||
else:
|
||||
# 不收到响应也视为成功,因为服务器可能正忙于播放音频
|
||||
print("未收到响应,但这不一定表示失败(服务器可能正忙于处理音频)")
|
||||
|
||||
# 这里给TTS处理一些时间
|
||||
# 根据文本长度估计播放时间
|
||||
estimated_time = len(text) * 0.1 # 假设每个字符需要0.1秒
|
||||
estimated_time = max(1.0, min(estimated_time, 10.0)) # 至少1秒,最多10秒
|
||||
print(f"等待大约 {estimated_time:.1f} 秒让音频播放完...")
|
||||
time.sleep(estimated_time)
|
||||
|
||||
return True
|
||||
|
||||
def exit_session(sock, tts_work_id):
|
||||
request_id = str(uuid.uuid4())
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": tts_work_id,
|
||||
"action": "exit"
|
||||
})
|
||||
response = receive_response(sock, timeout=2.0)
|
||||
if not response:
|
||||
print("退出命令已发送,但未收到响应")
|
||||
return True # 假设成功
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
error = response_data.get('error', {})
|
||||
if error.get('code') == 0:
|
||||
print(f"成功退出 {tts_work_id}")
|
||||
return True
|
||||
else:
|
||||
print(f"退出失败: {error.get('message', '未知错误')}")
|
||||
return False
|
||||
except:
|
||||
print("Failed to parse exit response")
|
||||
return False
|
||||
|
||||
def get_task_info(sock, work_id):
|
||||
"""获取任务的详细信息"""
|
||||
request_id = str(uuid.uuid4())
|
||||
send_json(sock, {
|
||||
"request_id": request_id,
|
||||
"work_id": work_id,
|
||||
"action": "taskinfo"
|
||||
})
|
||||
|
||||
response = receive_response(sock)
|
||||
if not response:
|
||||
return {"error": "No response received"}
|
||||
try:
|
||||
return json.loads(response)
|
||||
except:
|
||||
return {"error": "Failed to parse response"}
|
||||
|
||||
def print_menu():
|
||||
print("\n===== TTS控制菜单 =====")
|
||||
print("1. 合成语音")
|
||||
print("2. 链接到其他单元")
|
||||
print("3. 取消链接")
|
||||
print("4. 暂停TTS单元")
|
||||
print("5. 恢复TTS单元")
|
||||
print("6. 获取任务信息")
|
||||
print("7. 退出TTS单元")
|
||||
print("0. 退出程序")
|
||||
print("======================")
|
||||
|
||||
def main(host, port):
|
||||
sock = create_tcp_connection(host, port)
|
||||
try:
|
||||
print("Setting up TTS...")
|
||||
setup_data = create_tts_setup_data()
|
||||
tts_work_id = setup(sock, setup_data)
|
||||
|
||||
if not tts_work_id:
|
||||
print("Setup failed. Checking available tasks...")
|
||||
task_list = list_available_tasks(sock)
|
||||
print("Available tasks:", task_list)
|
||||
if task_list.get('data') and isinstance(task_list.get('data'), list) and len(task_list.get('data')) > 0:
|
||||
tts_work_id = task_list.get('data')[0]
|
||||
print(f"使用已存在的TTS任务: {tts_work_id}")
|
||||
else:
|
||||
print("找不到可用的TTS任务,程序退出")
|
||||
return
|
||||
|
||||
print(f"TTS SETUP finished, work_id: {tts_work_id}")
|
||||
|
||||
# 获取并显示任务详细信息
|
||||
task_info = get_task_info(sock, tts_work_id)
|
||||
print("Task info:", task_info)
|
||||
|
||||
while True:
|
||||
print_menu()
|
||||
choice = input("请选择操作 (0-7): ")
|
||||
|
||||
if choice == '0':
|
||||
print("程序退出")
|
||||
break
|
||||
|
||||
elif choice == '1':
|
||||
text = input("请输入要合成语音的文本: ")
|
||||
if text:
|
||||
print("正在合成语音...", flush=True)
|
||||
success = tts_inference(sock, tts_work_id, text)
|
||||
if success:
|
||||
print("语音合成处理完成")
|
||||
else:
|
||||
print("语音合成处理失败")
|
||||
else:
|
||||
print("文本为空,取消合成")
|
||||
|
||||
elif choice == '2':
|
||||
target_id = input("请输入要链接的单元ID (例如 kws.1000): ")
|
||||
if target_id:
|
||||
link_units(sock, tts_work_id, target_id)
|
||||
else:
|
||||
print("单元ID为空,取消链接操作")
|
||||
|
||||
elif choice == '3':
|
||||
target_id = input("请输入要取消链接的单元ID (例如 kws.1000): ")
|
||||
if target_id:
|
||||
unlink_units(sock, tts_work_id, target_id)
|
||||
else:
|
||||
print("单元ID为空,取消操作")
|
||||
|
||||
elif choice == '4':
|
||||
pause_unit(sock, tts_work_id)
|
||||
|
||||
elif choice == '5':
|
||||
resume_unit(sock, tts_work_id)
|
||||
|
||||
elif choice == '6':
|
||||
task_info = get_task_info(sock, tts_work_id)
|
||||
print("Task info:", json.dumps(task_info, indent=2, ensure_ascii=False))
|
||||
|
||||
elif choice == '7':
|
||||
if exit_session(sock, tts_work_id):
|
||||
print("TTS单元已退出")
|
||||
# 重新检查可用任务
|
||||
task_list = list_available_tasks(sock)
|
||||
print("Available tasks:", task_list)
|
||||
else:
|
||||
print("TTS单元退出失败")
|
||||
|
||||
else:
|
||||
print("无效的选择,请重试")
|
||||
|
||||
# 每次操作间隔
|
||||
time.sleep(0.5)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n程序被用户中断")
|
||||
except Exception as e:
|
||||
print(f"程序异常: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='TCP Client for MeloTTS Unit.')
|
||||
parser.add_argument('--host', type=str, default='localhost', help='Server hostname (default: localhost)')
|
||||
parser.add_argument('--port', type=int, default=10001, help='Server port (default: 10001)')
|
||||
args = parser.parse_args()
|
||||
main(args.host, args.port)
|
||||
@@ -0,0 +1,145 @@
|
||||
import socket
|
||||
import json
|
||||
import argparse
|
||||
|
||||
|
||||
def create_tcp_connection(host, port):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((host, port))
|
||||
return sock
|
||||
|
||||
|
||||
def send_json(sock, data):
|
||||
json_data = json.dumps(data, ensure_ascii=False) + '\n'
|
||||
sock.sendall(json_data.encode('utf-8'))
|
||||
|
||||
|
||||
def receive_response(sock):
|
||||
response = ''
|
||||
while True:
|
||||
part = sock.recv(4096).decode('utf-8')
|
||||
response += part
|
||||
if '\n' in response:
|
||||
break
|
||||
return response.strip()
|
||||
|
||||
|
||||
def close_connection(sock):
|
||||
if sock:
|
||||
sock.close()
|
||||
|
||||
|
||||
def create_init_data():
|
||||
return {
|
||||
"request_id": "llm_001",
|
||||
"work_id": "llm",
|
||||
"action": "setup",
|
||||
"object": "llm.setup",
|
||||
"data": {
|
||||
"model": "qwen2.5-0.5B-prefill-20e",
|
||||
"response_format": "llm.utf-8.stream",
|
||||
"input": "llm.utf-8.stream",
|
||||
"enoutput": True,
|
||||
"max_token_len": 1023,
|
||||
"prompt": "You are a knowledgeable assistant capable of answering various questions and providing information."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def parse_setup_response(response_data, sent_request_id):
|
||||
error = response_data.get('error')
|
||||
request_id = response_data.get('request_id')
|
||||
|
||||
if request_id != sent_request_id:
|
||||
print(f"Request ID mismatch: sent {sent_request_id}, received {request_id}")
|
||||
return None
|
||||
|
||||
if error and error.get('code') != 0:
|
||||
print(f"Error Code: {error['code']}, Message: {error['message']}")
|
||||
return None
|
||||
|
||||
return response_data.get('work_id')
|
||||
|
||||
|
||||
def setup(sock, init_data):
|
||||
sent_request_id = init_data['request_id']
|
||||
send_json(sock, init_data)
|
||||
response = receive_response(sock)
|
||||
response_data = json.loads(response)
|
||||
return parse_setup_response(response_data, sent_request_id)
|
||||
|
||||
|
||||
def exit_session(sock, deinit_data):
|
||||
send_json(sock, deinit_data)
|
||||
response = receive_response(sock)
|
||||
response_data = json.loads(response)
|
||||
print("Exit Response:", response_data)
|
||||
|
||||
|
||||
def parse_inference_response(response_data):
|
||||
error = response_data.get('error')
|
||||
if error and error.get('code') != 0:
|
||||
print(f"Error Code: {error['code']}, Message: {error['message']}")
|
||||
return None
|
||||
|
||||
return response_data.get('data')
|
||||
|
||||
|
||||
def main(host, port):
|
||||
sock = create_tcp_connection(host, port)
|
||||
|
||||
try:
|
||||
print("Setup LLM...")
|
||||
init_data = create_init_data()
|
||||
llm_work_id = setup(sock, init_data)
|
||||
print("Setup LLM finished.")
|
||||
|
||||
while True:
|
||||
user_input = input("Enter your message (or 'exit' to quit): ")
|
||||
if user_input.lower() == 'exit':
|
||||
break
|
||||
|
||||
send_json(sock, {
|
||||
"request_id": "llm_001",
|
||||
"work_id": llm_work_id,
|
||||
"action": "inference",
|
||||
"object": "llm.utf-8.stream",
|
||||
"data": {
|
||||
"delta": user_input,
|
||||
"index": 0,
|
||||
"finish": True
|
||||
}
|
||||
})
|
||||
|
||||
while True:
|
||||
response = receive_response(sock)
|
||||
response_data = json.loads(response)
|
||||
|
||||
data = parse_inference_response(response_data)
|
||||
if data is None:
|
||||
break
|
||||
|
||||
delta = data.get('delta')
|
||||
finish = data.get('finish')
|
||||
print(delta, end='', flush=True)
|
||||
|
||||
if finish:
|
||||
print()
|
||||
break
|
||||
|
||||
exit_session(sock, {
|
||||
"request_id": "llm_exit",
|
||||
"work_id": llm_work_id,
|
||||
"action": "exit"
|
||||
})
|
||||
finally:
|
||||
close_connection(sock)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='TCP Client to send JSON data.')
|
||||
parser.add_argument('--host', type=str, default='localhost', help='Server hostname (default: localhost)')
|
||||
parser.add_argument('--port', type=int, default=10001, help='Server port (default: 10001)')
|
||||
|
||||
args = parser.parse_args()
|
||||
main(args.host, args.port)
|
||||
Reference in New Issue
Block a user