Commit f5d2c570 authored by Lorenz Meier's avatar Lorenz Meier

Put audio output into a worker thread.

parent fa69b34c
...@@ -243,6 +243,7 @@ INCLUDEPATH += \ ...@@ -243,6 +243,7 @@ INCLUDEPATH += \
src/ui/map \ src/ui/map \
src/uas \ src/uas \
src/comm \ src/comm \
src/audio \
include/ui \ include/ui \
src/input \ src/input \
src/lib/qmapcontrol \ src/lib/qmapcontrol \
...@@ -479,7 +480,8 @@ HEADERS += \ ...@@ -479,7 +480,8 @@ HEADERS += \
src/QGCFileDialog.h \ src/QGCFileDialog.h \
src/QGCMessageBox.h \ src/QGCMessageBox.h \
src/QGCComboBox.h \ src/QGCComboBox.h \
src/QGCTemporaryFile.h src/QGCTemporaryFile.h \
src/audio/QGCAudioWorker.h
SOURCES += \ SOURCES += \
src/main.cc \ src/main.cc \
...@@ -618,7 +620,8 @@ SOURCES += \ ...@@ -618,7 +620,8 @@ SOURCES += \
src/uas/QGXPX4UAS.cc \ src/uas/QGXPX4UAS.cc \
src/QGCFileDialog.cc \ src/QGCFileDialog.cc \
src/QGCComboBox.cc \ src/QGCComboBox.cc \
src/QGCTemporaryFile.cc src/QGCTemporaryFile.cc \
src/audio/QGCAudioWorker.cpp
# #
......
...@@ -33,28 +33,8 @@ This file is part of the QGROUNDCONTROL project ...@@ -33,28 +33,8 @@ This file is part of the QGROUNDCONTROL project
#include <QApplication> #include <QApplication>
#include <QSettings> #include <QSettings>
#include "GAudioOutput.h" #include "GAudioOutput.h"
#include "MG.h"
#include <QDebug> #include <QDebug>
#include <QGC.h>
#if defined Q_OS_MAC && defined QGC_SPEECH_ENABLED
#include <ApplicationServices/ApplicationServices.h>
#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 <sapi.h>
#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 <espeak/speak_lib.h>
#endif
#if defined _MSC_VER && defined QGC_SPEECH_ENABLED
ISpVoice *GAudioOutput::pVoice = NULL;
#endif
/** /**
* This class follows the singleton design pattern * This class follows the singleton design pattern
...@@ -78,134 +58,44 @@ GAudioOutput *GAudioOutput::instance() ...@@ -78,134 +58,44 @@ GAudioOutput *GAudioOutput::instance()
return _instance; return _instance;
} }
#define QGC_GAUDIOOUTPUT_KEY QString("QGC_AUDIOOUTPUT_")
GAudioOutput::GAudioOutput(QObject *parent) : QObject(parent), GAudioOutput::GAudioOutput(QObject *parent) : QObject(parent),
voiceIndex(0), muted(false),
emergency(false), thread(new QThread()),
muted(false) worker(new QGCAudioWorker())
{ {
// Load settings worker->moveToThread(thread);
QSettings settings; connect(this, SIGNAL(textToSpeak(QString,int)), worker, SLOT(say(QString,int)));
muted = settings.value(QGC_GAUDIOOUTPUT_KEY + "muted", muted).toBool(); thread->start();
#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;
}
} }
GAudioOutput::~GAudioOutput() GAudioOutput::~GAudioOutput()
{ {
#if defined _MSC_VER && defined QGC_SPEECH_ENABLED thread->quit();
pVoice->Release(); while (thread->isRunning()) {
pVoice = NULL; QGC::SLEEP::usleep(100);
::CoUninitialize(); }
#endif worker->deleteLater();
thread->deleteLater();
worker = NULL;
thread = NULL;
} }
void GAudioOutput::mute(bool mute) void GAudioOutput::mute(bool mute)
{ {
if (mute != muted) // XXX handle muting
{
this->muted = mute;
QSettings settings;
settings.setValue(QGC_GAUDIOOUTPUT_KEY + "muted", this->muted);
emit mutedChanged(muted);
}
} }
bool GAudioOutput::isMuted() bool GAudioOutput::isMuted()
{ {
return this->muted; // XXX return right stuff
return false;
} }
bool GAudioOutput::say(QString text, int severity) bool GAudioOutput::say(QString text, int severity)
{ {
if (!muted) emit textToSpeak(text, severity);
{ return true;
// 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;
}
} }
/** /**
...@@ -213,19 +103,8 @@ bool GAudioOutput::say(QString text, int severity) ...@@ -213,19 +103,8 @@ bool GAudioOutput::say(QString text, int severity)
*/ */
bool GAudioOutput::alert(QString text) bool GAudioOutput::alert(QString text)
{ {
if (!emergency || !muted) emit textToSpeak(text, 2);
{ return true;
// Play alert sound
beep();
// Say alert message
say(text, 2);
return true;
}
else
{
return false;
}
} }
void GAudioOutput::notifyPositive() void GAudioOutput::notifyPositive()
...@@ -261,16 +140,16 @@ void GAudioOutput::notifyNegative() ...@@ -261,16 +140,16 @@ void GAudioOutput::notifyNegative()
*/ */
bool GAudioOutput::startEmergency() bool GAudioOutput::startEmergency()
{ {
if (!emergency) // if (!emergency)
{ // {
emergency = true; // emergency = true;
// Beep immediately and then start timer // // Beep immediately and then start timer
if (!muted) beep(); // if (!muted) beep();
emergencyTimer->start(1500); // emergencyTimer->start(1500);
QTimer::singleShot(5000, this, SLOT(stopEmergency())); // QTimer::singleShot(5000, this, SLOT(stopEmergency()));
} // }
return true; return true;
} }
...@@ -283,11 +162,11 @@ bool GAudioOutput::startEmergency() ...@@ -283,11 +162,11 @@ bool GAudioOutput::startEmergency()
*/ */
bool GAudioOutput::stopEmergency() bool GAudioOutput::stopEmergency()
{ {
if (emergency) // if (emergency)
{ // {
emergency = false; // emergency = false;
emergencyTimer->stop(); // emergencyTimer->stop();
} // }
return true; return true;
} }
...@@ -304,26 +183,3 @@ void GAudioOutput::beep() ...@@ -304,26 +183,3 @@ void GAudioOutput::beep()
//m_media->play(); //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;
}
...@@ -34,31 +34,10 @@ This file is part of the PIXHAWK project ...@@ -34,31 +34,10 @@ This file is part of the PIXHAWK project
#include <QObject> #include <QObject>
#include <QTimer> #include <QTimer>
#include <QThread>
#include <QStringList> #include <QStringList>
#ifdef Q_OS_MAC
//#include <MediaObject> #include <QGCAudioWorker.h>
//#include <AudioOutput>
#endif
#ifdef Q_OS_LINUX
//#include <phonon/MediaObject>
//#include <phonon/AudioOutput>
#endif
#ifdef Q_OS_WIN
//#include <Phonon/MediaObject>
//#include <Phonon/AudioOutput>
#endif
/* For Snow leopard and later
#if defined Q_OS_MAC & defined QGC_SPEECH_ENABLED
#include <NSSpeechSynthesizer.h>
#endif
*/
#if defined _MSC_VER && defined QGC_SPEECH_ENABLED
// Documentation: http://msdn.microsoft.com/en-us/library/ee125082%28v=VS.85%29.aspx
#include <sapi.h>
#endif
/** /**
* @brief Audio Output (speech synthesizer and "beep" output) * @brief Audio Output (speech synthesizer and "beep" output)
...@@ -91,10 +70,10 @@ public slots: ...@@ -91,10 +70,10 @@ public slots:
bool startEmergency(); bool startEmergency();
/** @brief Stop emergency sound */ /** @brief Stop emergency sound */
bool stopEmergency(); bool stopEmergency();
/** @brief Select female voice */ // /** @brief Select female voice */
void selectFemaleVoice(); // void selectFemaleVoice();
/** @brief Select male voice */ // /** @brief Select male voice */
void selectMaleVoice(); // void selectMaleVoice();
/** @brief Play emergency sound once */ /** @brief Play emergency sound once */
void beep(); void beep();
/** @brief Notify about positive event */ /** @brief Notify about positive event */
...@@ -106,23 +85,12 @@ public slots: ...@@ -106,23 +85,12 @@ public slots:
signals: signals:
void mutedChanged(bool); void mutedChanged(bool);
bool textToSpeak(QString text, int severity = 1);
protected: 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; bool muted;
QThread* thread;
QGCAudioWorker* worker;
private: private:
GAudioOutput(QObject *parent = NULL); GAudioOutput(QObject *parent = NULL);
~GAudioOutput(); ~GAudioOutput();
......
#include <QSettings>
#include <QDebug>
#include "QGC.h"
#include "QGCAudioWorker.h"
#if defined Q_OS_MAC && defined QGC_SPEECH_ENABLED
#include <ApplicationServices/ApplicationServices.h>
#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 <sapi.h>
#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 <espeak/speak_lib.h>
#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;
}
#ifndef QGCAUDIOWORKER_H
#define QGCAUDIOWORKER_H
#include <QObject>
#include <QTimer>
/* For Snow leopard and later
#if defined Q_OS_MAC & defined QGC_SPEECH_ENABLED
#include <NSSpeechSynthesizer.h>
#endif
*/
#if defined _MSC_VER && defined QGC_SPEECH_ENABLED
// Documentation: http://msdn.microsoft.com/en-us/library/ee125082%28v=VS.85%29.aspx
#include <sapi.h>
#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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment