diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 6ae0c9e415ac300505c0a44227e6a3e3cefab800..d1541cf0e9693bd583fba539ce47f3b55cd29bc6 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -114,6 +114,10 @@ QT += network \ quick \ quickwidgets +contains(DEFINES, QGC_NOTIFY_TUNES_ENABLED) { + QT += multimedia +} + !contains(DEFINES, DISABLE_GOOGLE_EARTH) { QT += webkit webkitwidgets } @@ -244,6 +248,7 @@ INCLUDEPATH += \ src/ui/map \ src/uas \ src/comm \ + src/audio \ include/ui \ src/input \ src/lib/qmapcontrol \ @@ -480,6 +485,7 @@ HEADERS += \ src/QGCMessageBox.h \ src/QGCComboBox.h \ src/QGCTemporaryFile.h \ + src/audio/QGCAudioWorker.h \ src/QGCQuickWidget.h SOURCES += \ @@ -619,6 +625,7 @@ SOURCES += \ src/QGCFileDialog.cc \ src/QGCComboBox.cc \ src/QGCTemporaryFile.cc \ + src/audio/QGCAudioWorker.cpp \ src/QGCQuickWidget.cc # diff --git a/src/GAudioOutput.cc b/src/GAudioOutput.cc index a67d4564980987e748bb998a211c1cc01d1ff15c..e0134a5c800ac85a5879dd9f42fbce85aa1e9be6 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))); + connect(this, SIGNAL(beepOnce()), worker, SLOT(beep())); + 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); + } + delete worker; + delete thread; } 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 + Q_UNUSED(mute); } 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, 1); + return true; } void GAudioOutput::notifyPositive() @@ -261,16 +140,15 @@ 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 - emergencyTimer->start(1500); - QTimer::singleShot(5000, this, SLOT(stopEmergency())); - } +// emergencyTimer->start(1500); +// QTimer::singleShot(5000, this, SLOT(stopEmergency())); +// } return true; } @@ -283,47 +161,16 @@ bool GAudioOutput::startEmergency() */ bool GAudioOutput::stopEmergency() { - if (emergency) - { - emergency = false; - emergencyTimer->stop(); - } +// if (emergency) +// { +// emergency = false; +// emergencyTimer->stop(); +// } return true; } void GAudioOutput::beep() { - if (!muted) - { - // FIXME: Re-enable audio beeps - // Use QFile to transform path for all OS - //QFile f(QCoreApplication::applicationDirPath() + QString("/files/audio/alert.wav")); - //qDebug() << "FILE:" << f.fileName(); - //m_media->setCurrentSource(Phonon::MediaSource(f.fileName().toStdString().c_str())); - //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; + emit beepOnce(); } diff --git a/src/GAudioOutput.h b/src/GAudioOutput.h index dbf3b60beb6d91261ab47dd65499362600c8ca02..b9440e5856e2af1a5f6c0e2fafb39396ed8e9806 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) @@ -79,22 +58,34 @@ public: VOICE_FEMALE } QGVoice; + enum AUDIO_SEVERITY + { + AUDIO_SEVERITY_EMERGENCY = 0, + AUDIO_SEVERITY_ALERT = 1, + AUDIO_SEVERITY_CRITICAL = 2, + AUDIO_SEVERITY_ERROR = 3, + AUDIO_SEVERITY_WARNING = 4, + AUDIO_SEVERITY_NOTICE = 5, + AUDIO_SEVERITY_INFO = 6, + AUDIO_SEVERITY_DEBUG = 7 + }; + /** @brief Get the mute state */ bool isMuted(); public slots: /** @brief Say this text if current output priority matches */ - bool say(QString text, int severity = 1); + bool say(QString text, int severity = 6); /** @brief Play alert sound and say notification message */ bool alert(QString text); /** @brief Start emergency sound */ 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 +97,13 @@ public slots: signals: void mutedChanged(bool); + bool textToSpeak(QString text, int severity = 1); + void beepOnce(); 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..0d2ee3e6b0304023232eaf3039a36efb9b6dfe95 --- /dev/null +++ b/src/audio/QGCAudioWorker.cpp @@ -0,0 +1,176 @@ +#include +#include +#include +#include + +#include "QGC.h" +#include "QGCAudioWorker.h" +#include "GAudioOutput.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 + +#define QGC_GAUDIOOUTPUT_KEY QString("QGC_AUDIOOUTPUT_") + +QGCAudioWorker::QGCAudioWorker(QObject *parent) : + QObject(parent), + voiceIndex(0), + #if defined _MSC_VER && defined QGC_SPEECH_ENABLED + pVoice(NULL), + #endif + #ifdef QGC_NOTIFY_TUNES_ENABLED + sound(NULL), + #endif + emergency(false), + muted(false) +{ + // Load settings + QSettings settings; + muted = settings.value(QGC_GAUDIOOUTPUT_KEY + "muted", muted).toBool(); +} + +void QGCAudioWorker::init() +{ +#ifdef QGC_NOTIFY_TUNES_ENABLED + sound = new QSound(":/files/audio/alert.wav"); +#endif + +#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED + espeak_Initialize(AUDIO_OUTPUT_SYNCH_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 + + 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 + delete emergencyTimer; +} + +void QGCAudioWorker::say(QString text, int severity) +{ + static bool threadInit = false; + if (!threadInit) { + threadInit = true; + init(); + } + + if (!muted) + { + // Prepend high priority text with alert beep + if (severity < GAudioOutput::AUDIO_SEVERITY_CRITICAL) { + beep(); + } + +#ifdef QGC_NOTIFY_TUNES_ENABLED + // Wait for the last sound to finish + while (!sound->isFinished()) { + QGC::SLEEP::msleep(100); + } +#endif + +#if defined _MSC_VER && defined QGC_SPEECH_ENABLED + HRESULT hr = pVoice->Speak(text.toStdWString().c_str(), SPF_DEFAULT, NULL); + if (FAILED(hr)) { + qDebug() << "Speak failed, HR:" << QString("%1").arg(hr, 0, 16); + } +#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); + } +} + +void QGCAudioWorker::beep() +{ + + if (!muted) + { +#ifdef QGC_NOTIFY_TUNES_ENABLED + sound->play(":/files/audio/alert.wav"); +#endif + } +} + +bool QGCAudioWorker::isMuted() +{ + return this->muted; +} diff --git a/src/audio/QGCAudioWorker.h b/src/audio/QGCAudioWorker.h new file mode 100644 index 0000000000000000000000000000000000000000..7e06023e54264968dd4d84c2133fc59baf0ab614 --- /dev/null +++ b/src/audio/QGCAudioWorker.h @@ -0,0 +1,55 @@ +#ifndef QGCAUDIOWORKER_H +#define QGCAUDIOWORKER_H + +#include +#include +#ifdef QGC_NOTIFY_TUNES_ENABLED +#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 + +class QGCAudioWorker : public QObject +{ + Q_OBJECT +public: + explicit QGCAudioWorker(QObject *parent = 0); + ~QGCAudioWorker(); + + void mute(bool mute); + bool isMuted(); + void init(); + +signals: + +public slots: + /** @brief Say this text if current output priority matches */ + void say(QString text, int severity = 1); + + /** @brief Sound a single beep */ + void beep(); + +protected: + int voiceIndex; ///< The index of the flite voice to use (awb, slt, rms) +#if defined _MSC_VER && defined QGC_SPEECH_ENABLED + ISpVoice *pVoice; +#endif +#ifdef QGC_NOTIFY_TUNES_ENABLED + QSound *sound; +#endif + bool emergency; ///< Emergency status flag + QTimer *emergencyTimer; + bool muted; +}; + +#endif // QGCAUDIOWORKER_H diff --git a/src/uas/UAS.cc b/src/uas/UAS.cc index 29a618e0e2d941971c201b3f4b3d7e32958473a6..13634f3e7899ab265eeed7791c2f7b1fc32126c4 100644 --- a/src/uas/UAS.cc +++ b/src/uas/UAS.cc @@ -333,7 +333,7 @@ void UAS::updateState() connectionLost = true; receivedMode = false; QString audiostring = QString("Link lost to system %1").arg(this->getUASID()); - GAudioOutput::instance()->say(audiostring.toLower()); + GAudioOutput::instance()->say(audiostring.toLower(), GAudioOutput::AUDIO_SEVERITY_ALERT); } // Update connection loss time on each iteration @@ -347,7 +347,7 @@ void UAS::updateState() if (connectionLost && (heartbeatInterval < timeoutIntervalHeartbeat)) { QString audiostring = QString("Link regained to system %1").arg(this->getUASID()); - GAudioOutput::instance()->say(audiostring.toLower()); + GAudioOutput::instance()->say(audiostring.toLower(), GAudioOutput::AUDIO_SEVERITY_NOTICE); connectionLost = false; connectionLossTime = 0; emit heartbeatTimeout(false, 0); @@ -574,7 +574,7 @@ void UAS::receiveMessage(LinkInterface* link, mavlink_message_t message) if (statechanged && ((int)state.system_status == (int)MAV_STATE_CRITICAL || state.system_status == (int)MAV_STATE_EMERGENCY)) { - GAudioOutput::instance()->say(QString("emergency for system %1").arg(this->getUASID())); + GAudioOutput::instance()->say(QString("emergency for system %1").arg(this->getUASID()), GAudioOutput::AUDIO_SEVERITY_EMERGENCY); QTimer::singleShot(3000, GAudioOutput::instance(), SLOT(startEmergency())); } else if (modechanged || statechanged)