diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 7d3cab5ba6ddbb8615f5eee9ce00d940159f6209..bd46c796eab39f71f3182e6e62eb591026e9929e 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -243,6 +243,7 @@ INCLUDEPATH += \ src/ui/map \ src/uas \ src/comm \ + src/audio \ include/ui \ src/input \ src/lib/qmapcontrol \ @@ -479,7 +480,8 @@ HEADERS += \ src/QGCFileDialog.h \ src/QGCMessageBox.h \ src/QGCComboBox.h \ - src/QGCTemporaryFile.h + src/QGCTemporaryFile.h \ + src/audio/QGCAudioWorker.h SOURCES += \ src/main.cc \ @@ -618,7 +620,8 @@ SOURCES += \ src/uas/QGXPX4UAS.cc \ src/QGCFileDialog.cc \ src/QGCComboBox.cc \ - src/QGCTemporaryFile.cc + src/QGCTemporaryFile.cc \ + src/audio/QGCAudioWorker.cpp # diff --git a/src/GAudioOutput.cc b/src/GAudioOutput.cc index a67d4564980987e748bb998a211c1cc01d1ff15c..78a2dd0de7afd5676426c3b52110037f98433d8f 100644 --- a/src/GAudioOutput.cc +++ b/src/GAudioOutput.cc @@ -33,28 +33,8 @@ This file is part of the QGROUNDCONTROL project #include #include #include "GAudioOutput.h" -#include "MG.h" - #include - -#if defined Q_OS_MAC && defined QGC_SPEECH_ENABLED -#include -#endif - -// Speech synthesis is only supported with MSVC compiler -#if defined _MSC_VER && defined QGC_SPEECH_ENABLED -// Documentation: http://msdn.microsoft.com/en-us/library/ee125082%28v=VS.85%29.aspx -#include -#endif - -#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED -// Using eSpeak for speech synthesis: following https://github.com/mondhs/espeak-sample/blob/master/sampleSpeak.cpp -#include -#endif - -#if defined _MSC_VER && defined QGC_SPEECH_ENABLED -ISpVoice *GAudioOutput::pVoice = NULL; -#endif +#include /** * This class follows the singleton design pattern @@ -78,134 +58,44 @@ GAudioOutput *GAudioOutput::instance() return _instance; } -#define QGC_GAUDIOOUTPUT_KEY QString("QGC_AUDIOOUTPUT_") - GAudioOutput::GAudioOutput(QObject *parent) : QObject(parent), - voiceIndex(0), - emergency(false), - muted(false) + muted(false), + thread(new QThread()), + worker(new QGCAudioWorker()) { - // Load settings - QSettings settings; - muted = settings.value(QGC_GAUDIOOUTPUT_KEY + "muted", muted).toBool(); - - -#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED - espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 500, NULL, 0); // initialize for playback with 500ms buffer and no options (see speak_lib.h) - espeak_VOICE *espeak_voice = espeak_GetCurrentVoice(); - espeak_voice->languages = "en-uk"; // Default to British English - espeak_voice->identifier = NULL; // no specific voice file specified - espeak_voice->name = "klatt"; // espeak voice name - espeak_voice->gender = 2; // Female - espeak_voice->age = 0; // age not specified - espeak_SetVoiceByProperties(espeak_voice); -#endif - -#if defined _MSC_VER && defined QGC_SPEECH_ENABLED - pVoice = NULL; - - if (FAILED(::CoInitialize(NULL))) - { - qDebug() << "ERROR: Creating COM object for audio output failed!"; - } - - else - { - - HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice); - - if (FAILED(hr)) - { - qDebug() << "ERROR: Initializing voice for audio output failed!"; - } - } - -#endif - - // Prepare regular emergency signal, will be fired off on calling startEmergency() - emergencyTimer = new QTimer(); - connect(emergencyTimer, SIGNAL(timeout()), this, SLOT(beep())); - - switch (voiceIndex) - { - case 0: - selectFemaleVoice(); - break; - - default: - selectMaleVoice(); - break; - } + worker->moveToThread(thread); + connect(this, SIGNAL(textToSpeak(QString,int)), worker, SLOT(say(QString,int))); + thread->start(); } GAudioOutput::~GAudioOutput() { -#if defined _MSC_VER && defined QGC_SPEECH_ENABLED - pVoice->Release(); - pVoice = NULL; - ::CoUninitialize(); -#endif + thread->quit(); + while (thread->isRunning()) { + QGC::SLEEP::usleep(100); + } + worker->deleteLater(); + thread->deleteLater(); + worker = NULL; + thread = NULL; } void GAudioOutput::mute(bool mute) { - if (mute != muted) - { - this->muted = mute; - QSettings settings; - settings.setValue(QGC_GAUDIOOUTPUT_KEY + "muted", this->muted); - emit mutedChanged(muted); - } + // XXX handle muting } bool GAudioOutput::isMuted() { - return this->muted; + // XXX return right stuff + return false; } bool GAudioOutput::say(QString text, int severity) { - if (!muted) - { - // TODO Add severity filter - Q_UNUSED(severity); - bool res = false; - - if (!emergency) - { - -#if defined _MSC_VER && defined QGC_SPEECH_ENABLED - pVoice->Speak(text.toStdWString().c_str(), SPF_ASYNC, NULL); - -#elif defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED - // Set size of string for espeak: +1 for the null-character - unsigned int espeak_size = strlen(text.toStdString().c_str()) + 1; - espeak_Synth(text.toStdString().c_str(), espeak_size, 0, POS_CHARACTER, 0, espeakCHARS_AUTO, NULL, NULL); - -#elif defined Q_OS_MAC && defined QGC_SPEECH_ENABLED - // Slashes necessary to have the right start to the sentence - // copying data prevents SpeakString from reading additional chars - text = "\\" + text; - std::wstring str = text.toStdWString(); - unsigned char str2[1024] = {}; - memcpy(str2, text.toLatin1().data(), str.length()); - SpeakString(str2); - res = true; - -#else - // Make sure there isn't an unused variable warning when speech output is disabled - Q_UNUSED(text); -#endif - } - - return res; - } - - else - { - return false; - } + emit textToSpeak(text, severity); + return true; } /** @@ -213,19 +103,8 @@ bool GAudioOutput::say(QString text, int severity) */ bool GAudioOutput::alert(QString text) { - if (!emergency || !muted) - { - // Play alert sound - beep(); - // Say alert message - say(text, 2); - return true; - } - - else - { - return false; - } + emit textToSpeak(text, 2); + return true; } void GAudioOutput::notifyPositive() @@ -261,16 +140,16 @@ void GAudioOutput::notifyNegative() */ bool GAudioOutput::startEmergency() { - if (!emergency) - { - emergency = true; +// if (!emergency) +// { +// emergency = true; - // Beep immediately and then start timer - if (!muted) beep(); +// // Beep immediately and then start timer +// if (!muted) beep(); - emergencyTimer->start(1500); - QTimer::singleShot(5000, this, SLOT(stopEmergency())); - } +// emergencyTimer->start(1500); +// QTimer::singleShot(5000, this, SLOT(stopEmergency())); +// } return true; } @@ -283,11 +162,11 @@ bool GAudioOutput::startEmergency() */ bool GAudioOutput::stopEmergency() { - if (emergency) - { - emergency = false; - emergencyTimer->stop(); - } +// if (emergency) +// { +// emergency = false; +// emergencyTimer->stop(); +// } return true; } @@ -304,26 +183,3 @@ void GAudioOutput::beep() //m_media->play(); } } - -void GAudioOutput::selectFemaleVoice() -{ -#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED - // FIXME: Enable selecting a female voice on all platforms - //this->voice = register_cmu_us_slt(NULL); -#endif -} - -void GAudioOutput::selectMaleVoice() -{ -#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED - // FIXME: Enable selecting a male voice on all platforms - //this->voice = register_cmu_us_rms(NULL); -#endif -} - -QStringList GAudioOutput::listVoices(void) -{ - // No voice selection is currently supported, so just return an empty list - QStringList l; - return l; -} diff --git a/src/GAudioOutput.h b/src/GAudioOutput.h index dbf3b60beb6d91261ab47dd65499362600c8ca02..ce21c904f2d6fe1e2a5f5adf5919f126e0d936f2 100644 --- a/src/GAudioOutput.h +++ b/src/GAudioOutput.h @@ -34,31 +34,10 @@ This file is part of the PIXHAWK project #include #include +#include #include -#ifdef Q_OS_MAC -//#include -//#include -#endif -#ifdef Q_OS_LINUX -//#include -//#include -#endif -#ifdef Q_OS_WIN -//#include -//#include -#endif - -/* For Snow leopard and later -#if defined Q_OS_MAC & defined QGC_SPEECH_ENABLED -#include -#endif - */ - - -#if defined _MSC_VER && defined QGC_SPEECH_ENABLED -// Documentation: http://msdn.microsoft.com/en-us/library/ee125082%28v=VS.85%29.aspx -#include -#endif + +#include /** * @brief Audio Output (speech synthesizer and "beep" output) @@ -91,10 +70,10 @@ public slots: bool startEmergency(); /** @brief Stop emergency sound */ bool stopEmergency(); - /** @brief Select female voice */ - void selectFemaleVoice(); - /** @brief Select male voice */ - void selectMaleVoice(); +// /** @brief Select female voice */ +// void selectFemaleVoice(); +// /** @brief Select male voice */ +// void selectMaleVoice(); /** @brief Play emergency sound once */ void beep(); /** @brief Notify about positive event */ @@ -106,23 +85,12 @@ public slots: signals: void mutedChanged(bool); + bool textToSpeak(QString text, int severity = 1); protected: -#if defined Q_OS_MAC && defined QGC_SPEECH_ENABLED - //NSSpeechSynthesizer -#endif -#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED - //cst_voice* voice; ///< The flite voice object -#endif -#if defined _MSC_VER && defined QGC_SPEECH_ENABLED - static ISpVoice *pVoice; -#endif - int voiceIndex; ///< The index of the flite voice to use (awb, slt, rms) - //Phonon::MediaObject *m_media; ///< The output object for audio - //Phonon::AudioOutput *m_audioOutput; - bool emergency; ///< Emergency status flag - QTimer *emergencyTimer; bool muted; + QThread* thread; + QGCAudioWorker* worker; private: GAudioOutput(QObject *parent = NULL); ~GAudioOutput(); diff --git a/src/audio/QGCAudioWorker.cpp b/src/audio/QGCAudioWorker.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eac54d57166174d3ae8f8371a1af7f82260edcb9 --- /dev/null +++ b/src/audio/QGCAudioWorker.cpp @@ -0,0 +1,143 @@ +#include +#include + +#include "QGC.h" +#include "QGCAudioWorker.h" + +#if defined Q_OS_MAC && defined QGC_SPEECH_ENABLED +#include +#endif + +// Speech synthesis is only supported with MSVC compiler +#if defined _MSC_VER && defined QGC_SPEECH_ENABLED +// Documentation: http://msdn.microsoft.com/en-us/library/ee125082%28v=VS.85%29.aspx +#include +#endif + +#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED +// Using eSpeak for speech synthesis: following https://github.com/mondhs/espeak-sample/blob/master/sampleSpeak.cpp +#include +#endif + +#if defined _MSC_VER && defined QGC_SPEECH_ENABLED +ISpVoice *GAudioOutput::pVoice = NULL; +#endif + +#define QGC_GAUDIOOUTPUT_KEY QString("QGC_AUDIOOUTPUT_") + +QGCAudioWorker::QGCAudioWorker(QObject *parent) : + QObject(parent), + voiceIndex(0), + emergency(false), + muted(false) +{ + // Load settings + QSettings settings; + muted = settings.value(QGC_GAUDIOOUTPUT_KEY + "muted", muted).toBool(); + + +#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED + espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 500, NULL, 0); // initialize for playback with 500ms buffer and no options (see speak_lib.h) + espeak_VOICE *espeak_voice = espeak_GetCurrentVoice(); + espeak_voice->languages = "en-uk"; // Default to British English + espeak_voice->identifier = NULL; // no specific voice file specified + espeak_voice->name = "klatt"; // espeak voice name + espeak_voice->gender = 2; // Female + espeak_voice->age = 0; // age not specified + espeak_SetVoiceByProperties(espeak_voice); +#endif + +#if defined _MSC_VER && defined QGC_SPEECH_ENABLED + pVoice = NULL; + + if (FAILED(::CoInitialize(NULL))) + { + qDebug() << "ERROR: Creating COM object for audio output failed!"; + } + + else + { + + HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice); + + if (FAILED(hr)) + { + qDebug() << "ERROR: Initializing voice for audio output failed!"; + } + } + +#endif + + // Prepare regular emergency signal, will be fired off on calling startEmergency() + emergencyTimer = new QTimer(); + connect(emergencyTimer, SIGNAL(timeout()), this, SLOT(beep())); +} + +QGCAudioWorker::~QGCAudioWorker() +{ +#if defined _MSC_VER && defined QGC_SPEECH_ENABLED + pVoice->Release(); + pVoice = NULL; + ::CoUninitialize(); +#endif +} + +void QGCAudioWorker::say(QString text, int severity) +{ + qDebug() << "TEXT" << text; + if (!muted) + { + // TODO Add severity filter + Q_UNUSED(severity); + bool res = false; + + if (!emergency) + { + + #if defined _MSC_VER && defined QGC_SPEECH_ENABLED + pVoice->Speak(text.toStdWString().c_str(), SPF_ASYNC, NULL); + + #elif defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED + // Set size of string for espeak: +1 for the null-character + unsigned int espeak_size = strlen(text.toStdString().c_str()) + 1; + espeak_Synth(text.toStdString().c_str(), espeak_size, 0, POS_CHARACTER, 0, espeakCHARS_AUTO, NULL, NULL); + + #elif defined Q_OS_MAC && defined QGC_SPEECH_ENABLED + // Slashes necessary to have the right start to the sentence + // copying data prevents SpeakString from reading additional chars + text = "\\" + text; + std::wstring str = text.toStdWString(); + unsigned char str2[1024] = {}; + memcpy(str2, text.toLatin1().data(), str.length()); + SpeakString(str2); + + // Block the thread while busy + // because we run in our own thread, this doesn't + // halt the main application + while (SpeechBusy()) { + QGC::SLEEP::msleep(100); + } + + #else + // Make sure there isn't an unused variable warning when speech output is disabled + Q_UNUSED(text); + #endif + } + } +} + +void QGCAudioWorker::mute(bool mute) +{ + if (mute != muted) + { + this->muted = mute; + QSettings settings; + settings.setValue(QGC_GAUDIOOUTPUT_KEY + "muted", this->muted); +// emit mutedChanged(muted); + } +} + +bool QGCAudioWorker::isMuted() +{ + return this->muted; +} diff --git a/src/audio/QGCAudioWorker.h b/src/audio/QGCAudioWorker.h new file mode 100644 index 0000000000000000000000000000000000000000..749dd6147df1955985106bb7f7aa037b364a988c --- /dev/null +++ b/src/audio/QGCAudioWorker.h @@ -0,0 +1,51 @@ +#ifndef QGCAUDIOWORKER_H +#define QGCAUDIOWORKER_H + +#include +#include + +/* For Snow leopard and later +#if defined Q_OS_MAC & defined QGC_SPEECH_ENABLED +#include +#endif + */ + + +#if defined _MSC_VER && defined QGC_SPEECH_ENABLED +// Documentation: http://msdn.microsoft.com/en-us/library/ee125082%28v=VS.85%29.aspx +#include +#endif + +class QGCAudioWorker : public QObject +{ + Q_OBJECT +public: + explicit QGCAudioWorker(QObject *parent = 0); + ~QGCAudioWorker(); + + void mute(bool mute); + bool isMuted(); + +signals: + +public slots: + /** @brief Say this text if current output priority matches */ + void say(QString text, int severity = 1); + +protected: +#if defined Q_OS_MAC && defined QGC_SPEECH_ENABLED + //NSSpeechSynthesizer +#endif +#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED + //cst_voice* voice; ///< The flite voice object +#endif +#if defined _MSC_VER && defined QGC_SPEECH_ENABLED + static ISpVoice *pVoice; +#endif + int voiceIndex; ///< The index of the flite voice to use (awb, slt, rms) + bool emergency; ///< Emergency status flag + QTimer *emergencyTimer; + bool muted; +}; + +#endif // QGCAUDIOWORKER_H