Commit dd587ce2 authored by dogmaphobic's avatar dogmaphobic

Text to Speech Work

parent c2a3cd0b
...@@ -270,6 +270,11 @@ else:WindowsBuild { ...@@ -270,6 +270,11 @@ else:WindowsBuild {
DEFINES += QGC_SPEECH_ENABLED DEFINES += QGC_SPEECH_ENABLED
LIBS += -lOle32 LIBS += -lOle32
} }
# Android supports speech through native (Java) API.
else:AndroidBuild {
message("Including support for speech output")
DEFINES += QGC_SPEECH_ENABLED
}
# #
# [OPTIONAL] Zeroconf for UDP links # [OPTIONAL] Zeroconf for UDP links
......
...@@ -43,12 +43,15 @@ import android.content.IntentFilter; ...@@ -43,12 +43,15 @@ import android.content.IntentFilter;
import android.hardware.usb.*; import android.hardware.usb.*;
import android.widget.Toast; import android.widget.Toast;
import android.util.Log; import android.util.Log;
//-- Text To Speech
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import com.hoho.android.usbserial.driver.*; import com.hoho.android.usbserial.driver.*;
import org.qtproject.qt5.android.bindings.QtActivity; import org.qtproject.qt5.android.bindings.QtActivity;
import org.qtproject.qt5.android.bindings.QtApplication; import org.qtproject.qt5.android.bindings.QtApplication;
public class UsbDeviceJNI extends QtActivity public class UsbDeviceJNI extends QtActivity implements TextToSpeech.OnInitListener
{ {
public static int BAD_PORT = 0; public static int BAD_PORT = 0;
private static UsbDeviceJNI m_instance; private static UsbDeviceJNI m_instance;
...@@ -61,6 +64,7 @@ public class UsbDeviceJNI extends QtActivity ...@@ -61,6 +64,7 @@ public class UsbDeviceJNI extends QtActivity
private BroadcastReceiver m_UsbReceiver = null; private BroadcastReceiver m_UsbReceiver = null;
private final static ExecutorService m_Executor = Executors.newSingleThreadExecutor(); private final static ExecutorService m_Executor = Executors.newSingleThreadExecutor();
private static final String TAG = "QGC_UsbDeviceJNI"; private static final String TAG = "QGC_UsbDeviceJNI";
private static TextToSpeech m_tts;
private final static UsbIoManager.Listener m_Listener = private final static UsbIoManager.Listener m_Listener =
new UsbIoManager.Listener() new UsbIoManager.Listener()
...@@ -98,6 +102,34 @@ public class UsbDeviceJNI extends QtActivity ...@@ -98,6 +102,34 @@ public class UsbDeviceJNI extends QtActivity
Log.i(TAG, "Instance created"); Log.i(TAG, "Instance created");
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Text To Speech
// Pigback a ride for providing TTS to QGC
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
m_tts = new TextToSpeech(this,this);
}
@Override
protected void onDestroy() {
super.onDestroy();
m_tts.shutdown();
}
public void onInit(int status) {
}
public static void say(String msg)
{
Log.i(TAG, "Say: " + msg);
m_tts.speak(msg, TextToSpeech.QUEUE_FLUSH, null);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// Find all current devices that match the device filter described in the androidmanifest.xml and the // Find all current devices that match the device filter described in the androidmanifest.xml and the
......
...@@ -38,42 +38,51 @@ This file is part of the QGROUNDCONTROL project ...@@ -38,42 +38,51 @@ This file is part of the QGROUNDCONTROL project
#include "QGCApplication.h" #include "QGCApplication.h"
#include "QGC.h" #include "QGC.h"
#if defined __android__
#include <QtAndroidExtras/QtAndroidExtras>
#include <QtAndroidExtras/QAndroidJniObject>
#endif
IMPLEMENT_QGC_SINGLETON(GAudioOutput, GAudioOutput) IMPLEMENT_QGC_SINGLETON(GAudioOutput, GAudioOutput)
const char* GAudioOutput::_mutedKey = "AudioMuted"; const char* GAudioOutput::_mutedKey = "AudioMuted";
GAudioOutput::GAudioOutput(QObject *parent) : GAudioOutput::GAudioOutput(QObject *parent)
QGCSingleton(parent), : QGCSingleton(parent)
muted(false), , muted(false)
thread(new QThread()), #ifndef __android__
worker(new QGCAudioWorker()) , thread(new QThread())
, worker(new QGCAudioWorker())
#endif
{ {
QSettings settings; QSettings settings;
muted = settings.value(_mutedKey, false).toBool(); muted = settings.value(_mutedKey, false).toBool();
muted |= qgcApp()->runningUnitTests(); muted |= qgcApp()->runningUnitTests();
#ifndef __android__
worker->moveToThread(thread); worker->moveToThread(thread);
connect(this, &GAudioOutput::textToSpeak, worker, &QGCAudioWorker::say); connect(this, &GAudioOutput::textToSpeak, worker, &QGCAudioWorker::say);
connect(thread, &QThread::finished, thread, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater);
connect(thread, &QThread::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, worker, &QObject::deleteLater);
thread->start(); thread->start();
#endif
} }
GAudioOutput::~GAudioOutput() GAudioOutput::~GAudioOutput()
{ {
#ifndef __android__
thread->quit(); thread->quit();
#endif
} }
void GAudioOutput::mute(bool mute) void GAudioOutput::mute(bool mute)
{ {
QSettings settings; QSettings settings;
muted = mute; muted = mute;
settings.setValue(_mutedKey, mute); settings.setValue(_mutedKey, mute);
#ifndef __android__
emit mutedChanged(mute); emit mutedChanged(mute);
#endif
} }
bool GAudioOutput::isMuted() bool GAudioOutput::isMuted()
...@@ -81,10 +90,24 @@ bool GAudioOutput::isMuted() ...@@ -81,10 +90,24 @@ bool GAudioOutput::isMuted()
return muted; return muted;
} }
bool GAudioOutput::say(const QString& text, int severity) bool GAudioOutput::say(const QString& inText, int severity)
{ {
if (!muted) { if (!muted) {
emit textToSpeak(text, severity); #if defined __android__
#if defined QGC_SPEECH_ENABLED
static const char V_jniClassName[] {"org/qgroundcontrol/qgchelper/UsbDeviceJNI"};
QAndroidJniEnvironment env;
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
QString text = QGCAudioWorker::fixTextMessageForAudio(inText);
QAndroidJniObject javaMessage = QAndroidJniObject::fromString(text);
QAndroidJniObject::callStaticMethod<void>(V_jniClassName, "say", "(Ljava/lang/String;)V", javaMessage.object<jstring>());
#endif
#else
emit textToSpeak(inText, severity);
#endif
} }
return true; return true;
} }
...@@ -88,8 +88,11 @@ signals: ...@@ -88,8 +88,11 @@ signals:
protected: protected:
bool muted; bool muted;
#if !defined __android__
QThread* thread; QThread* thread;
QGCAudioWorker* worker; QGCAudioWorker* worker;
#endif
private: private:
GAudioOutput(QObject *parent = NULL); GAudioOutput(QObject *parent = NULL);
......
...@@ -10,6 +10,47 @@ ...@@ -10,6 +10,47 @@
#if defined Q_OS_MAC && defined QGC_SPEECH_ENABLED #if defined Q_OS_MAC && defined QGC_SPEECH_ENABLED
#include <ApplicationServices/ApplicationServices.h> #include <ApplicationServices/ApplicationServices.h>
static SpeechChannel sc;
static Fixed volume;
static void speechDone(SpeechChannel sc2, void *) {
if (sc2 == sc)
{
DisposeSpeechChannel(sc);
}
}
class MacSpeech
{
public:
MacSpeech()
{
setVolume(100);
}
~MacSpeech()
{
}
void say(const char* words)
{
while (SpeechBusy()) {
QGC::SLEEP::msleep(100);
}
NewSpeechChannel(NULL, &sc);
SetSpeechInfo(sc, soVolume, &volume);
SetSpeechInfo(sc, soSpeechDoneCallBack, reinterpret_cast<void *>(speechDone));
CFStringRef cfstr = CFStringCreateWithCString(NULL, words, kCFStringEncodingUTF8);
SpeakCFString(sc, cfstr, NULL);
}
void setVolume(int v)
{
volume = FixRatio(v, 100);
}
};
//-- Singleton
MacSpeech macSpeech;
#endif #endif
// Speech synthesis is only supported with MSVC compiler // Speech synthesis is only supported with MSVC compiler
...@@ -18,7 +59,7 @@ ...@@ -18,7 +59,7 @@
#include <sapi.h> #include <sapi.h>
#endif #endif
#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED #if defined Q_OS_LINUX && !defined __android__ && defined QGC_SPEECH_ENABLED
// Using eSpeak for speech synthesis: following https://github.com/mondhs/espeak-sample/blob/master/sampleSpeak.cpp // Using eSpeak for speech synthesis: following https://github.com/mondhs/espeak-sample/blob/master/sampleSpeak.cpp
#include <espeak/speak_lib.h> #include <espeak/speak_lib.h>
#endif #endif
...@@ -48,7 +89,7 @@ void QGCAudioWorker::init() ...@@ -48,7 +89,7 @@ void QGCAudioWorker::init()
sound = new QSound(":/res/Alert"); sound = new QSound(":/res/Alert");
#endif #endif
#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED #if defined Q_OS_LINUX && !defined __android__ && 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_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 *espeak_voice = espeak_GetCurrentVoice();
espeak_voice->languages = "en-uk"; // Default to British English espeak_voice->languages = "en-uk"; // Default to British English
...@@ -92,6 +133,10 @@ QGCAudioWorker::~QGCAudioWorker() ...@@ -92,6 +133,10 @@ QGCAudioWorker::~QGCAudioWorker()
void QGCAudioWorker::say(QString inText, int severity) void QGCAudioWorker::say(QString inText, int severity)
{ {
#ifdef __android__
Q_UNUSED(inText);
Q_UNUSED(severity);
#else
static bool threadInit = false; static bool threadInit = false;
if (!threadInit) { if (!threadInit) {
threadInit = true; threadInit = true;
...@@ -100,7 +145,7 @@ void QGCAudioWorker::say(QString inText, int severity) ...@@ -100,7 +145,7 @@ void QGCAudioWorker::say(QString inText, int severity)
if (!muted) if (!muted)
{ {
QString text = _fixTextMessageForAudio(inText); QString text = fixTextMessageForAudio(inText);
// Prepend high priority text with alert beep // Prepend high priority text with alert beep
if (severity < GAudioOutput::AUDIO_SEVERITY_CRITICAL) { if (severity < GAudioOutput::AUDIO_SEVERITY_CRITICAL) {
beep(); beep();
...@@ -124,26 +169,13 @@ void QGCAudioWorker::say(QString inText, int severity) ...@@ -124,26 +169,13 @@ void QGCAudioWorker::say(QString inText, int severity)
espeak_Synth(text.toStdString().c_str(), espeak_size, 0, POS_CHARACTER, 0, espeakCHARS_AUTO, NULL, NULL); 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 #elif defined Q_OS_MAC && defined QGC_SPEECH_ENABLED
// Slashes necessary to have the right start to the sentence macSpeech.say(text.toStdString().c_str());
// 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 #else
// Make sure there isn't an unused variable warning when speech output is disabled // Make sure there isn't an unused variable warning when speech output is disabled
Q_UNUSED(inText); Q_UNUSED(inText);
#endif #endif
} }
#endif // __android__
} }
void QGCAudioWorker::mute(bool mute) void QGCAudioWorker::mute(bool mute)
...@@ -187,7 +219,7 @@ bool QGCAudioWorker::_getMillisecondString(const QString& string, QString& match ...@@ -187,7 +219,7 @@ bool QGCAudioWorker::_getMillisecondString(const QString& string, QString& match
return false; return false;
} }
QString QGCAudioWorker::_fixTextMessageForAudio(const QString& string) { QString QGCAudioWorker::fixTextMessageForAudio(const QString& string) {
QString match; QString match;
QString newNumber; QString newNumber;
QString result = string; QString result = string;
...@@ -204,7 +236,9 @@ QString QGCAudioWorker::_fixTextMessageForAudio(const QString& string) { ...@@ -204,7 +236,9 @@ QString QGCAudioWorker::_fixTextMessageForAudio(const QString& string) {
if(result.contains("ALTCTL", Qt::CaseInsensitive)) { if(result.contains("ALTCTL", Qt::CaseInsensitive)) {
result.replace("ALTCTL", "Altitude Control", Qt::CaseInsensitive); result.replace("ALTCTL", "Altitude Control", Qt::CaseInsensitive);
} }
if(result.contains("RTL", Qt::CaseInsensitive)) { if(result.contains("AUTO_RTL", Qt::CaseInsensitive)) {
result.replace("AUTO_RTL", "auto Return To Land", Qt::CaseInsensitive);
} else if(result.contains("RTL", Qt::CaseInsensitive)) {
result.replace("RTL", "Return To Land", Qt::CaseInsensitive); result.replace("RTL", "Return To Land", Qt::CaseInsensitive);
} }
if(result.contains("ACCEL ", Qt::CaseInsensitive)) { if(result.contains("ACCEL ", Qt::CaseInsensitive)) {
......
...@@ -7,13 +7,6 @@ ...@@ -7,13 +7,6 @@
#include <QSound> #include <QSound>
#endif #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 #if defined _MSC_VER && defined QGC_SPEECH_ENABLED
// Documentation: http://msdn.microsoft.com/en-us/library/ee125082%28v=VS.85%29.aspx // Documentation: http://msdn.microsoft.com/en-us/library/ee125082%28v=VS.85%29.aspx
#include <basetyps.h> #include <basetyps.h>
...@@ -31,6 +24,8 @@ public: ...@@ -31,6 +24,8 @@ public:
bool isMuted(); bool isMuted();
void init(); void init();
static QString fixTextMessageForAudio(const QString& string);
signals: signals:
public slots: public slots:
...@@ -52,8 +47,7 @@ protected: ...@@ -52,8 +47,7 @@ protected:
QTimer *emergencyTimer; QTimer *emergencyTimer;
bool muted; bool muted;
private: private:
QString _fixTextMessageForAudio(const QString& string); static bool _getMillisecondString(const QString& string, QString& match, int& number);
bool _getMillisecondString(const QString& string, QString& match, int& number);
}; };
#endif // QGCAUDIOWORKER_H #endif // QGCAUDIOWORKER_H
...@@ -422,7 +422,7 @@ void UAS::receiveMessage(mavlink_message_t message) ...@@ -422,7 +422,7 @@ void UAS::receiveMessage(mavlink_message_t message)
modechanged = true; modechanged = true;
this->base_mode = state.base_mode; this->base_mode = state.base_mode;
this->custom_mode = state.custom_mode; this->custom_mode = state.custom_mode;
modeAudio = " is now in " + audiomodeText + "flight mode"; modeAudio = " is now in " + audiomodeText + " flight mode";
} }
// We got the mode // We got the mode
...@@ -2470,10 +2470,6 @@ void UAS::unsetRCToParameterMap() ...@@ -2470,10 +2470,6 @@ void UAS::unsetRCToParameterMap()
void UAS::_say(const QString& text, int severity) void UAS::_say(const QString& text, int severity)
{ {
#ifndef UNITTEST_BUILD if (!qgcApp()->runningUnitTests())
GAudioOutput::instance()->say(text, severity); GAudioOutput::instance()->say(text, severity);
#else
Q_UNUSED(text)
Q_UNUSED(severity)
#endif
} }
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