diff options
Diffstat (limited to 'src/plugins/tts/common/qtexttospeechprocessor.cpp')
-rw-r--r-- | src/plugins/tts/common/qtexttospeechprocessor.cpp | 310 |
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 |