mainwindow.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include "ttsclient.h"
  4. #include <QMessageBox>
  5. #include <QFile>
  6. #include <QFileDialog>
  7. #include <QDebug>
  8. MainWindow::MainWindow(QWidget* parent)
  9. : QMainWindow(parent)
  10. , ui(new Ui::MainWindow)
  11. , m_ttsClient(nullptr)
  12. , m_playerProcess(nullptr)
  13. , m_isConnected(false)
  14. , m_totalChunks(0)
  15. , m_receivedChunks(0)
  16. , m_nextExpectedChunk(0)
  17. , m_isPlaying(false)
  18. {
  19. ui->setupUi(this);
  20. m_ttsClient = new TTSClient(this);
  21. m_playerProcess = new QProcess(this);
  22. connect(ui->btnConnect, &QPushButton::clicked, this, &MainWindow::onConnectClicked);
  23. connect(ui->btnSynthesize, &QPushButton::clicked, this, &MainWindow::onSynthesizeClicked);
  24. connect(ui->btnBrowseRefAudio, &QPushButton::clicked, this, &MainWindow::onBrowseRefAudio);
  25. connect(m_playerProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &MainWindow::onPlayerFinished);
  26. connect(ui->actionExit, &QAction::triggered, this, &QMainWindow::close);
  27. connect(ui->actionAbout, &QAction::triggered, this, [this]() {
  28. QMessageBox::about(this, "About",
  29. "TTS Client v2.0 - Fast Concurrent TTS\n\n"
  30. "Enter text and click Synthesize");
  31. });
  32. connect(m_ttsClient, &TTSClient::connected, this, &MainWindow::onConnected);
  33. connect(m_ttsClient, &TTSClient::disconnected, this, &MainWindow::onDisconnected);
  34. connect(m_ttsClient, &TTSClient::connectionError, this, &MainWindow::onConnectionError);
  35. connect(m_ttsClient, &TTSClient::audioReceived, this, &MainWindow::onAudioReceived);
  36. connect(m_ttsClient, &TTSClient::synthesisFinished, this, &MainWindow::onSynthesisFinished);
  37. connect(m_ttsClient, &TTSClient::voicesReceived, this, &MainWindow::onVoicesReceived);
  38. ui->lineEditRefAudio->setText("/Users/wenhongquan/Downloads/zh.wav");
  39. m_refAudio = ui->lineEditRefAudio->text();
  40. updateUiState();
  41. QTimer::singleShot(500, this, [this]() {
  42. onConnectClicked();
  43. });
  44. }
  45. MainWindow::~MainWindow()
  46. {
  47. if (m_isConnected) {
  48. m_ttsClient->disconnectFromServer();
  49. }
  50. }
  51. void MainWindow::onConnectClicked()
  52. {
  53. if (m_isConnected) {
  54. m_ttsClient->disconnectFromServer();
  55. return;
  56. }
  57. QString host = ui->lineEditHost->text();
  58. quint16 port = ui->lineEditPort->text().toUShort();
  59. ui->labelStatus->setText("Connecting...");
  60. ui->btnConnect->setEnabled(false);
  61. m_ttsClient->connectToServer(host, port);
  62. }
  63. void MainWindow::onBrowseRefAudio()
  64. {
  65. QString filePath = QFileDialog::getOpenFileName(
  66. this,
  67. "Select Reference Audio",
  68. "/Users/wenhongquan/Downloads",
  69. "Audio Files (*.wav *.mp3 *.flac);;All Files (*)"
  70. );
  71. if (!filePath.isEmpty()) {
  72. ui->lineEditRefAudio->setText(filePath);
  73. m_refAudio = filePath;
  74. }
  75. }
  76. QStringList MainWindow::splitTextIntoChunks(const QString& text, int chunkSize)
  77. {
  78. QStringList chunks;
  79. QString delimiters = ",,。!?.!?\n";
  80. QString current;
  81. for (int i = 0; i < text.length(); i++) {
  82. QChar ch = text[i];
  83. current.append(ch);
  84. if (delimiters.contains(ch) && current.length() > 2) {
  85. chunks.append(current.trimmed());
  86. current.clear();
  87. } else if (current.length() >= chunkSize) {
  88. int lastSpace = current.lastIndexOf(' ');
  89. if (lastSpace > chunkSize / 2) {
  90. QString chunk = current.left(lastSpace).trimmed();
  91. if (!chunk.isEmpty()) chunks.append(chunk);
  92. current = current.mid(lastSpace + 1);
  93. } else {
  94. chunks.append(current.trimmed());
  95. current.clear();
  96. }
  97. }
  98. }
  99. if (!current.trimmed().isEmpty()) {
  100. chunks.append(current.trimmed());
  101. }
  102. return chunks;
  103. }
  104. void MainWindow::onSynthesizeClicked()
  105. {
  106. QString text = ui->textEditInput->toPlainText().trimmed();
  107. if (text.isEmpty()) {
  108. QMessageBox::warning(this, "Warning", "Please enter text to synthesize");
  109. return;
  110. }
  111. m_refAudio = ui->lineEditRefAudio->text().trimmed();
  112. QStringList chunks = splitTextIntoChunks(text, 50);
  113. if (chunks.isEmpty()) {
  114. QMessageBox::warning(this, "Warning", "No valid chunks to synthesize");
  115. return;
  116. }
  117. m_audioBuffer.clear();
  118. m_receivedChunkIds.clear();
  119. m_streamComplete.clear();
  120. m_totalChunks = chunks.size();
  121. m_receivedChunks = 0;
  122. m_nextExpectedChunk = 0;
  123. m_isPlaying = false;
  124. ui->labelStatus->setText(QString("Sending %1 chunks...").arg(chunks.size()));
  125. ui->btnSynthesize->setEnabled(false);
  126. qDebug() << "Sending" << chunks.size() << "chunks";
  127. for (int i = 0; i < chunks.size(); i++) {
  128. m_ttsClient->synthesize(chunks[i], m_refAudio, i);
  129. }
  130. }
  131. void MainWindow::onConnected()
  132. {
  133. m_isConnected = true;
  134. ui->labelStatus->setText("Connected");
  135. ui->btnConnect->setText("Disconnect");
  136. ui->btnConnect->setEnabled(true);
  137. updateUiState();
  138. m_ttsClient->requestVoices();
  139. }
  140. void MainWindow::onDisconnected()
  141. {
  142. m_isConnected = false;
  143. ui->labelStatus->setText("Disconnected");
  144. ui->btnConnect->setText("Connect");
  145. ui->btnConnect->setEnabled(true);
  146. updateUiState();
  147. m_audioBuffer.clear();
  148. m_receivedChunkIds.clear();
  149. m_streamComplete.clear();
  150. m_isPlaying = false;
  151. }
  152. void MainWindow::onConnectionError(const QString& error)
  153. {
  154. m_isConnected = false;
  155. ui->labelStatus->setText("Connection Error");
  156. ui->btnConnect->setText("Connect");
  157. ui->btnConnect->setEnabled(true);
  158. QMessageBox::warning(this, "Connection Error", error);
  159. updateUiState();
  160. }
  161. void MainWindow::onAudioReceived(const QByteArray& audioData, int sampleRate, int chunkIndex, bool isFirst, bool isLast)
  162. {
  163. Q_UNUSED(sampleRate);
  164. qDebug() << "Received chunk" << chunkIndex << ":" << audioData.size()
  165. << "bytes (first=" << isFirst << ", last=" << isLast << ")";
  166. m_audioMutex.lock();
  167. QString key = QString("%1_%2").arg(chunkIndex).arg(isFirst ? "first" : "sub");
  168. if (m_receivedChunkIds.contains(key)) {
  169. qDebug() << "Duplicate chunk" << key << "- ignored";
  170. m_audioMutex.unlock();
  171. return;
  172. }
  173. m_receivedChunkIds.insert(key);
  174. QString chunkKey = QString::number(chunkIndex);
  175. if (!m_audioBuffer.contains(chunkKey)) {
  176. m_audioBuffer[chunkKey] = audioData;
  177. if (isFirst) {
  178. m_receivedChunks++;
  179. ui->labelStatus->setText(QString("Received %1/%2 streams").arg(m_receivedChunks).arg(m_totalChunks));
  180. }
  181. } else {
  182. m_audioBuffer[chunkKey].append(audioData);
  183. }
  184. if (isLast) {
  185. m_streamComplete[chunkIndex] = true;
  186. }
  187. m_audioMutex.unlock();
  188. playNextInQueue();
  189. }
  190. void MainWindow::onSynthesisFinished(int chunkIndex)
  191. {
  192. qDebug() << "Chunk" << chunkIndex << "finished";
  193. }
  194. void MainWindow::onVoicesReceived(const QStringList& voices)
  195. {
  196. qDebug() << "Available voices:" << voices;
  197. }
  198. void MainWindow::onPlayerFinished()
  199. {
  200. m_isPlaying = false;
  201. playNextInQueue();
  202. }
  203. void MainWindow::playNextInQueue()
  204. {
  205. m_audioMutex.lock();
  206. if (m_isPlaying) {
  207. m_audioMutex.unlock();
  208. return;
  209. }
  210. QString key = QString::number(m_nextExpectedChunk);
  211. if (m_audioBuffer.contains(key) && m_streamComplete.value(m_nextExpectedChunk, false)) {
  212. QByteArray audio = m_audioBuffer.take(key);
  213. m_streamComplete.remove(m_nextExpectedChunk);
  214. m_isPlaying = true;
  215. m_nextExpectedChunk++;
  216. m_audioMutex.unlock();
  217. playAudio(audio);
  218. } else {
  219. m_audioMutex.unlock();
  220. if (m_receivedChunks == m_totalChunks && m_totalChunks > 0) {
  221. ui->btnSynthesize->setEnabled(true);
  222. ui->labelStatus->setText("Ready");
  223. }
  224. }
  225. }
  226. void MainWindow::playAudio(const QByteArray& wavData)
  227. {
  228. if (wavData.isEmpty()) {
  229. playNextInQueue();
  230. return;
  231. }
  232. QString tempPath = QDir::temp().filePath(QString("tts_%1.wav").arg(QDateTime::currentMSecsSinceEpoch()));
  233. QFile file(tempPath);
  234. if (file.open(QIODevice::WriteOnly)) {
  235. file.write(wavData);
  236. file.close();
  237. m_playerProcess->start("afplay", QStringList() << tempPath);
  238. qDebug() << "Playing:" << tempPath;
  239. } else {
  240. qWarning() << "Failed to save WAV file";
  241. playNextInQueue();
  242. }
  243. }
  244. void MainWindow::updateUiState()
  245. {
  246. ui->lineEditHost->setEnabled(!m_isConnected);
  247. ui->lineEditPort->setEnabled(!m_isConnected);
  248. ui->btnSynthesize->setEnabled(m_isConnected);
  249. }