summaryrefslogtreecommitdiffstats
path: root/src/plugins/tts/common/qtexttospeechprocessor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/tts/common/qtexttospeechprocessor.cpp')
-rw-r--r--src/plugins/tts/common/qtexttospeechprocessor.cpp310
1 files changed, 310 insertions, 0 deletions
diff --git a/src/plugins/tts/common/qtexttospeechprocessor.cpp b/src/plugins/tts/common/qtexttospeechprocessor.cpp
new file mode 100644
index 0000000..cbb7819
--- /dev/null
+++ b/src/plugins/tts/common/qtexttospeechprocessor.cpp
@@ -0,0 +1,310 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Speech module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtexttospeechprocessor_p.h"
+
+#include <QtCore/QDateTime>
+
+QT_BEGIN_NAMESPACE
+
+QTextToSpeechProcessor::QTextToSpeechProcessor():
+ m_stop(true),
+ m_idle(true),
+ m_paused(false),
+ m_rate(0),
+ m_pitch(0),
+ m_volume(100),
+ m_audio(0),
+ m_audioBuffer(0)
+{
+}
+
+QTextToSpeechProcessor::~QTextToSpeechProcessor()
+{
+}
+
+void QTextToSpeechProcessor::say(const QString &text, int voiceId)
+{
+ if (isInterruptionRequested())
+ return;
+ QMutexLocker lock(&m_lock);
+ bool wasPaused = m_paused;
+ m_stop = true; // Cancel any previous utterance
+ m_idle = false;
+ m_paused = false;
+ m_nextText = text;
+ m_nextVoice = voiceId;
+ // If the speech was paused, one signal is needed to release the pause
+ // and another to start processing the new text.
+ m_speakSem.release(wasPaused ? 2 : 1);
+}
+
+void QTextToSpeechProcessor::stop()
+{
+ QMutexLocker lock(&m_lock);
+ m_stop = true;
+ m_paused = false;
+ m_nextText.clear();
+ m_speakSem.release();
+}
+
+void QTextToSpeechProcessor::pause()
+{
+ QMutexLocker lock(&m_lock);
+ m_paused = true;
+ m_speakSem.release();
+}
+
+void QTextToSpeechProcessor::resume()
+{
+ QMutexLocker lock(&m_lock);
+ m_paused = false;
+ m_speakSem.release();
+}
+
+bool QTextToSpeechProcessor::setRate(float rate)
+{
+ QMutexLocker lock(&m_lock);
+ if (rate >= -1.0 && rate <= 1.0) {
+ if (updateRate(rate)) {
+ m_rate = rate;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool QTextToSpeechProcessor::setPitch(float pitch)
+{
+ QMutexLocker lock(&m_lock);
+ if (pitch >= -1.0 && pitch <= 1.0) {
+ if (updatePitch(pitch)) {
+ m_pitch = pitch;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool QTextToSpeechProcessor::setVolume(int volume)
+{
+ QMutexLocker lock(&m_lock);
+ if (volume >= 0 && volume <= 100) {
+ if (updateVolume(volume)) {
+ m_volume = volume;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool QTextToSpeechProcessor::isIdle() const
+{
+ QMutexLocker lock(&m_lock);
+ return m_idle;
+}
+
+float QTextToSpeechProcessor::rate() const
+{
+ QMutexLocker lock(&m_lock);
+ return m_rate;
+}
+
+float QTextToSpeechProcessor::pitch() const
+{
+ QMutexLocker lock(&m_lock);
+ return m_pitch;
+}
+
+int QTextToSpeechProcessor::volume() const
+{
+ QMutexLocker lock(&m_lock);
+ return m_volume;
+}
+
+void QTextToSpeechProcessor::start(QThread::Priority priority)
+{
+ QThread::start(priority);
+}
+
+void QTextToSpeechProcessor::exit(int retcode)
+{
+ QThread::exit(retcode);
+ QThread::requestInterruption();
+ stop();
+ if (!QThread::wait(5000)) {
+ QThread::terminate();
+ QThread::wait();
+ }
+}
+
+void QTextToSpeechProcessor::run()
+{
+ int statusCode = 0;
+ forever {
+ m_lock.lock();
+ if (!m_speakSem.tryAcquire()) {
+ m_idle = true;
+ m_lock.unlock();
+ emit notSpeaking(statusCode); // Going idle
+ m_speakSem.acquire();
+ m_lock.lock();
+ }
+ if (isInterruptionRequested()) {
+ if (m_audio) {
+ delete m_audio;
+ m_audio = 0;
+ m_audioBuffer = 0;
+ }
+ m_lock.unlock();
+ break;
+ }
+ m_stop = false;
+ if (!m_nextText.isEmpty()) {
+ QString text = m_nextText;
+ int voice = m_nextVoice;
+ m_nextText.clear();
+ m_lock.unlock();
+ statusCode = processText(text, voice);
+ } else {
+ m_lock.unlock();
+ }
+ }
+}
+
+bool QTextToSpeechProcessor::audioStart(int sampleRate, int channelCount, QString *errorString)
+{
+ QMutexLocker lock(&m_lock);
+ QAudioFormat format;
+ format.setSampleRate(sampleRate);
+ format.setChannelCount(channelCount);
+ format.setSampleSize(16);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setCodec("audio/pcm");
+ if (errorString)
+ *errorString = QString();
+ if (m_audio)
+ delete m_audio;
+ m_audio = new QAudioOutput(format);
+ m_audioBuffer = m_audio->start();
+ updateVolume(m_volume);
+ if (m_audioBuffer && m_audio->state() == QAudio::IdleState)
+ return true;
+ if (errorString)
+ *errorString = QLatin1String("Failed to start audio output (error ")
+ + QString::number(m_audio->error()) + QLatin1Char(')');
+ delete m_audio;
+ m_audio = 0;
+ m_audioBuffer = 0;
+ return false;
+}
+
+bool QTextToSpeechProcessor::audioOutput(const char *data, qint64 dataSize, QString *errorString)
+{
+ bool ret = true;
+ int bytesWritten = 0;
+ QString error;
+ forever {
+ m_lock.lock();
+ if (m_paused) {
+ m_audio->suspend();
+ do {
+ m_lock.unlock();
+ m_speakSem.acquire(); // Wait for any command
+ m_lock.lock();
+ } while (m_paused);
+ m_audio->resume();
+ }
+ if (m_stop || !m_audioBuffer
+ || m_audio->state() == QAudio::StoppedState || isInterruptionRequested()) {
+ if (m_audio->error() != QAudio::NoError) {
+ error = QLatin1String("Audio error (")
+ + QString::number(m_audio->error()) + QLatin1Char(')');
+ }
+ m_lock.unlock();
+ ret = false;
+ break;
+ }
+ bytesWritten += m_audioBuffer->write(data + bytesWritten, dataSize - bytesWritten);
+ m_lock.unlock();
+ if (bytesWritten >= dataSize)
+ break;
+ QThread::msleep(50);
+ }
+ if (errorString)
+ *errorString = error;
+ return ret;
+}
+
+void QTextToSpeechProcessor::audioStop(bool abort)
+{
+ QMutexLocker lock(&m_lock);
+ if (m_audio) {
+ if (abort) {
+ m_audio->reset(); // Discard buffered audio
+ } else {
+ // TODO: Find a way to reliably check if all the audio has been written out before stopping
+ m_audioBuffer->write(QByteArray(1024, 0));
+ QThread::msleep(200);
+ m_audio->stop();
+ }
+ delete m_audio;
+ m_audio = 0;
+ m_audioBuffer = 0;
+ }
+}
+
+bool QTextToSpeechProcessor::updateRate(float rate)
+{
+ Q_UNUSED(rate);
+ return true;
+}
+
+bool QTextToSpeechProcessor::updatePitch(float pitch)
+{
+ Q_UNUSED(pitch);
+ return true;
+}
+
+bool QTextToSpeechProcessor::updateVolume(int volume)
+{
+ if (m_audio)
+ m_audio->setVolume(((qreal)volume) / 100.0);
+ return true;
+}
+
+
+QT_END_NAMESPACE