Commit c11a5457 authored by Lorenz Meier's avatar Lorenz Meier

Merge pull request #1082 from mavlink/audio_thread

Put audio output into a worker thread.
parents dd3ea20d ad06d2dd
...@@ -114,6 +114,10 @@ QT += network \ ...@@ -114,6 +114,10 @@ QT += network \
quick \ quick \
quickwidgets quickwidgets
contains(DEFINES, QGC_NOTIFY_TUNES_ENABLED) {
QT += multimedia
}
!contains(DEFINES, DISABLE_GOOGLE_EARTH) { !contains(DEFINES, DISABLE_GOOGLE_EARTH) {
QT += webkit webkitwidgets QT += webkit webkitwidgets
} }
...@@ -244,6 +248,7 @@ INCLUDEPATH += \ ...@@ -244,6 +248,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 \
...@@ -480,6 +485,7 @@ HEADERS += \ ...@@ -480,6 +485,7 @@ HEADERS += \
src/QGCMessageBox.h \ src/QGCMessageBox.h \
src/QGCComboBox.h \ src/QGCComboBox.h \
src/QGCTemporaryFile.h \ src/QGCTemporaryFile.h \
src/audio/QGCAudioWorker.h \
src/QGCQuickWidget.h src/QGCQuickWidget.h
SOURCES += \ SOURCES += \
...@@ -619,6 +625,7 @@ SOURCES += \ ...@@ -619,6 +625,7 @@ SOURCES += \
src/QGCFileDialog.cc \ src/QGCFileDialog.cc \
src/QGCComboBox.cc \ src/QGCComboBox.cc \
src/QGCTemporaryFile.cc \ src/QGCTemporaryFile.cc \
src/audio/QGCAudioWorker.cpp \
src/QGCQuickWidget.cc src/QGCQuickWidget.cc
# #
......
...@@ -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(); connect(this, SIGNAL(beepOnce()), worker, SLOT(beep()));
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 delete worker;
delete thread;
} }
void GAudioOutput::mute(bool mute) void GAudioOutput::mute(bool mute)
{ {
if (mute != muted) // XXX handle muting
{ Q_UNUSED(mute);
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, 1);
{ 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,15 @@ void GAudioOutput::notifyNegative() ...@@ -261,16 +140,15 @@ 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();
emergencyTimer->start(1500); // emergencyTimer->start(1500);
QTimer::singleShot(5000, this, SLOT(stopEmergency())); // QTimer::singleShot(5000, this, SLOT(stopEmergency()));
} // }
return true; return true;
} }
...@@ -283,47 +161,16 @@ bool GAudioOutput::startEmergency() ...@@ -283,47 +161,16 @@ 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;
} }
void GAudioOutput::beep() void GAudioOutput::beep()
{ {
if (!muted) emit beepOnce();
{
// 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;
} }
...@@ -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)
...@@ -79,22 +58,34 @@ public: ...@@ -79,22 +58,34 @@ public:
VOICE_FEMALE VOICE_FEMALE
} QGVoice; } 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 */ /** @brief Get the mute state */
bool isMuted(); bool isMuted();
public slots: public slots:
/** @brief Say this text if current output priority matches */ /** @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 */ /** @brief Play alert sound and say notification message */
bool alert(QString text); bool alert(QString text);
/** @brief Start emergency sound */ /** @brief Start emergency sound */
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 +97,13 @@ public slots: ...@@ -106,23 +97,13 @@ public slots:
signals: signals:
void mutedChanged(bool); void mutedChanged(bool);
bool textToSpeak(QString text, int severity = 1);
void beepOnce();
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 <QCoreApplication>
#include <QFile>
#include "QGC.h"
#include "QGCAudioWorker.h"
#include "GAudioOutput.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
#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;
}
#ifndef QGCAUDIOWORKER_H
#define QGCAUDIOWORKER_H
#include <QObject>
#include <QTimer>
#ifdef QGC_NOTIFY_TUNES_ENABLED
#include <QSound>
#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
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
...@@ -333,7 +333,7 @@ void UAS::updateState() ...@@ -333,7 +333,7 @@ void UAS::updateState()
connectionLost = true; connectionLost = true;
receivedMode = false; receivedMode = false;
QString audiostring = QString("Link lost to system %1").arg(this->getUASID()); 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 // Update connection loss time on each iteration
...@@ -347,7 +347,7 @@ void UAS::updateState() ...@@ -347,7 +347,7 @@ void UAS::updateState()
if (connectionLost && (heartbeatInterval < timeoutIntervalHeartbeat)) if (connectionLost && (heartbeatInterval < timeoutIntervalHeartbeat))
{ {
QString audiostring = QString("Link regained to system %1").arg(this->getUASID()); 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; connectionLost = false;
connectionLossTime = 0; connectionLossTime = 0;
emit heartbeatTimeout(false, 0); emit heartbeatTimeout(false, 0);
...@@ -574,7 +574,7 @@ void UAS::receiveMessage(LinkInterface* link, mavlink_message_t message) ...@@ -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)) 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())); QTimer::singleShot(3000, GAudioOutput::instance(), SLOT(startEmergency()));
} }
else if (modechanged || statechanged) else if (modechanged || statechanged)
......
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