summaryrefslogtreecommitdiffstats
path: root/src/plugins/tts/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/tts/common')
-rw-r--r--src/plugins/tts/common/qtexttospeechprocessor.cpp310
-rw-r--r--src/plugins/tts/common/qtexttospeechprocessor_p.h132
2 files changed, 442 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
diff --git a/src/plugins/tts/common/qtexttospeechprocessor_p.h b/src/plugins/tts/common/qtexttospeechprocessor_p.h
new file mode 100644
index 0000000..094fe73
--- /dev/null
+++ b/src/plugins/tts/common/qtexttospeechprocessor_p.h
@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QTEXTTOSPEECHPROCESSOR_P_H
+#define QTEXTTOSPEECHPROCESSOR_P_H
+
+#include "qvoice.h"
+
+#include <QtCore/QString>
+#include <QtCore/QThread>
+#include <QtCore/QMutex>
+#include <QtCore/QSemaphore>
+#include <QtCore/QIODevice>
+#include <QtMultimedia/QAudioOutput>
+
+QT_BEGIN_NAMESPACE
+
+// A common base class for text-to-speech engine integrations
+// that require audio output implementation and thread handling.
+//
+// QAudioOutput is used for audio, and each call to say() cancels
+// any previous processing. The public interface is thread-safe.
+class QTextToSpeechProcessor : public QThread {
+ Q_OBJECT
+
+public:
+ struct VoiceInfo
+ {
+ int id;
+ QString name;
+ QString locale;
+ QVoice::Gender gender;
+ QVoice::Age age;
+ };
+ QTextToSpeechProcessor();
+ ~QTextToSpeechProcessor();
+ void say(const QString &text, int voiceId);
+ void stop();
+ void pause();
+ void resume();
+ bool isIdle() const;
+ bool setRate(float rate);
+ bool setPitch(float pitch);
+ bool setVolume(int volume);
+ float rate() const;
+ float pitch() const;
+ int volume() const;
+ virtual const QVector<VoiceInfo> &voices() const = 0;
+
+protected:
+ // These are re-implemented QThread methods.
+ // exit() waits until the processor thread finishes or the wait times out.
+ void start(QThread::Priority = QThread::InheritPriority);
+ void exit(int retcode = 0);
+
+ // These methods can be used for audio output.
+ // audioOutput() blocks until all the audio has been written or processing
+ // is interrupted.
+ bool audioStart(int sampleRate, int channelCount, QString *errorString = 0);
+ bool audioOutput(const char* data, qint64 dataSize, QString *errorString = 0);
+ void audioStop(bool abort = false);
+
+ // These methods should be re-implemented if the parameters need
+ // to be changed while TTS is speaking. By default, updateVolume() just
+ // changes the QAudioOutput volume. The other methods do nothing by default.
+ virtual bool updateRate(float rate);
+ virtual bool updatePitch(float pitch);
+ virtual bool updateVolume(int volume);
+
+ // This method is called from the internal processor thread, and should block
+ // until the given text has been processed or processing is interrupted.
+ virtual int processText(const QString &text, int voiceId) = 0;
+
+signals:
+ // This signal is emitted when the processor goes to idle state, i.e. when no
+ // new text is set to be spoken. The parameter is the latest return value of
+ // processText(). As the signal is emitted from the internal thread, the recipient
+ // should call isIdle() to get updated state.
+ void notSpeaking(int statusCode);
+
+private:
+ void run() Q_DECL_OVERRIDE;
+ mutable QMutex m_lock;
+ volatile bool m_stop;
+ volatile bool m_idle;
+ volatile bool m_paused;
+ float m_rate;
+ float m_pitch;
+ int m_volume;
+ QSemaphore m_speakSem;
+ QString m_nextText;
+ int m_nextVoice;
+ QAudioOutput *m_audio;
+ QIODevice *m_audioBuffer;
+};
+
+QT_END_NAMESPACE
+
+#endif