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 \
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
#
......
......@@ -33,28 +33,8 @@ This file is part of the QGROUNDCONTROL project
#include <QApplication>
#include <QSettings>
#include "GAudioOutput.h"
#include "MG.h"
#include <QDebug>
#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
#include <QGC.h>
/**
* 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();
}
......@@ -34,31 +34,10 @@ This file is part of the PIXHAWK project
#include <QObject>
#include <QTimer>
#include <QThread>
#include <QStringList>
#ifdef Q_OS_MAC
//#include <MediaObject>
//#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
#include <QGCAudioWorker.h>
/**
* @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();
......
#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()
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)
......
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