From: Mariusz Wachowicz Date: Wed, 13 Sep 2017 11:43:31 +0000 (+0200) Subject: Implement feedback voice X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fchanges%2F85%2F150385%2F12;p=platform%2Fcore%2Faccessibility%2Funiversal-switch.git Implement feedback voice Add ToggleVoiceFeedbackEnabledActivity class responsible for changing VConf keys Add TextToSpeech class responsible for communication with tts-engine Change-Id: Icea8ecce2fe72396c0dc4084a866ea9f2499f443 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b595e0..5548274 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,18 +10,19 @@ FIND_PACKAGE(PkgConfig REQUIRED) option(TESTS "enable/disable universal switch tests" ON) pkg_check_modules(pkgs REQUIRED + atspi-2 capi-appfw-service-application capi-appfw-application capi-media-player capi-telephony - elementary - ecore - sqlite3 - atspi-2 capi-ui-efl-util + ecore efl-extension - gobject-2.0 eldbus + elementary + gobject-2.0 + sqlite3 + tts ) SET(COMMON_FLAGS "-fdiagnostics-color=always -fPIC") diff --git a/packaging/org.tizen.universal-switch.spec b/packaging/org.tizen.universal-switch.spec index 18c4fb1..eb3e18e 100644 --- a/packaging/org.tizen.universal-switch.spec +++ b/packaging/org.tizen.universal-switch.spec @@ -11,6 +11,8 @@ BuildRequires: at-spi2-core BuildRequires: at-spi2-core-devel BuildRequires: cmake BuildRequires: gettext-tools +BuildRequires: tts +BuildRequires: tts-devel BuildRequires: pkgconfig(capi-appfw-service-application) BuildRequires: pkgconfig(capi-appfw-application) BuildRequires: pkgconfig(capi-media-player) diff --git a/src/MenuBuilder.cpp b/src/MenuBuilder.cpp index d65a723..6631c38 100644 --- a/src/MenuBuilder.cpp +++ b/src/MenuBuilder.cpp @@ -262,7 +262,7 @@ MenuMap::MenuMap() auto turnOnOffVoice = std::make_shared( std::vector {"IDS_TURN_ON_VOICE", "IDS_TURN_OFF_VOICE"}, defaultImg, - std::string {}/*TODO add activity*/, + std::string {"TOGGLE_VOICE_FEEDBACK_ENABLED_ACTIVITY"}, VconfKeyType::BOOL, std::vector {VCONF_KEY_FEEDBACK_VOICE_ENABLED}); auto turnOnOffSound = std::make_shared( diff --git a/src/RowScanner.cpp b/src/RowScanner.cpp index 7c82f52..f26927b 100644 --- a/src/RowScanner.cpp +++ b/src/RowScanner.cpp @@ -4,6 +4,7 @@ #include "RowScanner.hpp" #include "ScanningProperties.hpp" #include "Sound.hpp" +#include "TextToSpeech.hpp" #include "UniversalSwitch.hpp" #include "UniversalSwitchLog.hpp" #include "Window.hpp" @@ -11,8 +12,8 @@ #include #include -#include #include +#include static const int DASHED_LINE_LENGTH = 20; @@ -52,6 +53,19 @@ namespace } } +namespace feedback +{ + void playSoundFeedback() + { + auto tts = Singleton::instance().getTextToSpeech(); + if (tts->isEnabled()) { + auto elem = Singleton::instance().getCurrentElement(); + Singleton::instance().getTextToSpeech()->speak(elem); + } + Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED); + } +} + class RowScannerImpl : public ScreenScanner { public: @@ -149,7 +163,7 @@ Optional> RowScannerImpl::acceptAutoscanningPhase() if (state == State::ITEMS) continueScanning = true; stopScanning(); - Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED); + feedback::playSoundFeedback(); state = State::START; } @@ -160,13 +174,13 @@ Optional> RowScannerImpl::acceptAutoscanningPhase() return {}; case State::ROWS: stopScanning(); - Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED); startItemInRowScanning(); + feedback::playSoundFeedback(); state = State::ITEMS; return {}; case State::ITEMS: stopScanning(); - Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED); + feedback::playSoundFeedback(); state = State::END; DEBUG("Scanning complete"); return Singleton::instance().getCurrentElement(); @@ -329,7 +343,7 @@ bool RowScannerImpl::iterateToNextScanningElement() ASSERT(0, "State::END case should not be reached"); return false; } - Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED); + feedback::playSoundFeedback(); return true; } diff --git a/src/ScreenScannerManager.cpp b/src/ScreenScannerManager.cpp index 87bb38a..731a24c 100644 --- a/src/ScreenScannerManager.cpp +++ b/src/ScreenScannerManager.cpp @@ -2,6 +2,7 @@ #include "UniversalSwitchLog.hpp" #include "Window.hpp" #include "Sound.hpp" +#include "TextToSpeech.hpp" #include "UniversalSwitch.hpp" #include @@ -111,6 +112,7 @@ void ScreenScannerManager::acceptAutoscanning() } stopAutoscanning(); + Singleton::instance().getTextToSpeech()->speak(*element); notify(*element); } diff --git a/src/TextToSpeech.cpp b/src/TextToSpeech.cpp new file mode 100644 index 0000000..f9eeae6 --- /dev/null +++ b/src/TextToSpeech.cpp @@ -0,0 +1,137 @@ +#include "Atspi.hpp" +#include "TextToSpeech.hpp" +#include "UniversalSwitch.hpp" +#include "UniversalSwitchLog.hpp" +#include "utils.hpp" +#include "VConfKeys.hpp" + + +#define EXIT_IF_ERROR(error) \ + do { \ + if (error != TTS_ERROR_NONE) { \ + ERROR("error = %s", get_tts_error(error)); \ + return; \ + } \ + } while (0) + +namespace +{ + const char *get_tts_error(int r) + { + switch (r) { + case TTS_ERROR_NONE: + return "no error"; + case TTS_ERROR_INVALID_PARAMETER: + return "invalid parameter"; + case TTS_ERROR_OUT_OF_MEMORY: + return "out of memory"; + case TTS_ERROR_OPERATION_FAILED: + return "operation failed"; + case TTS_ERROR_INVALID_STATE: + return "invalid state"; + default: + return "unknown error"; + } + } +} + +TextToSpeech::TextToSpeech() +{ + auto error = tts_create(&ttsHandler); + EXIT_IF_ERROR(error); + error = tts_set_mode(ttsHandler, TTS_MODE_SCREEN_READER); + EXIT_IF_ERROR(error); + error = tts_prepare(ttsHandler); + EXIT_IF_ERROR(error); + + callbackHandles.push_back(Singleton::instance().registerAndGet(VCONF_KEY_FEEDBACK_VOICE_SPEECH_RATE, TTS_SPEED_AUTO, + [this](auto speed) { + int min, normal, max; + auto error = tts_get_speed_range(ttsHandler, &min, &normal, &max); + if (error != TTS_ERROR_NONE) { + DEBUG("error = %s", get_tts_error(error)); + return; + } + speechRate = utils::clamp(speed, min, max); + })); + + callbackHandles.push_back(Singleton::instance().registerAndGet(VCONF_KEY_FEEDBACK_VOICE_ENABLED, false, + [this](auto state) { + DEBUG("Feedback voice %s", state ? "enabled" : "disabled"); + enabled = state; + })); +} + +TextToSpeech::~TextToSpeech() +{ + if (!ttsHandler) + return; + + auto error = tts_stop(ttsHandler); + EXIT_IF_ERROR(error); + error = tts_destroy(ttsHandler); + EXIT_IF_ERROR(error); + ttsHandler = nullptr; +} + +//TODO: support for long texts +void TextToSpeech::speak(std::string text) const +{ + if (!enabled) { + DEBUG("Voice feedback is disabled"); + return; + } + + if (text.empty()) { + ERROR("Empty string passed to speak method"); + return; + } + + tts_state_e state; + auto error = tts_get_state(ttsHandler, &state); + EXIT_IF_ERROR(error); + if (state == TTS_STATE_PLAYING || state == TTS_STATE_PAUSED) { + error = tts_stop(ttsHandler); + EXIT_IF_ERROR(error); + } + + auto maxSize = 0u; + error = tts_get_max_text_size(ttsHandler, &maxSize); + EXIT_IF_ERROR(error); + if (text.size() > maxSize) { + ERROR("Text is too long. passed = %d max = %d", text.size(), maxSize); + //TODO: support for long texts + text.resize(maxSize); + } + + int utteranceID; + error = tts_add_text(ttsHandler, text.c_str(), language, TTS_VOICE_TYPE_AUTO, speechRate, &utteranceID); + EXIT_IF_ERROR(error); + DEBUG("speaking: %s", text.c_str()); + error = tts_play(ttsHandler); + EXIT_IF_ERROR(error); +} + +void TextToSpeech::speak(const std::shared_ptr &element) const +{ + if (!enabled) { + DEBUG("Voice feedback is disabled"); + return; + } + + if (!element) { + DEBUG("Element is nullptr"); + return; + } + + Singleton::instance().getAtspi()->getName(element->getObject(), + [ptr = shared_from_this()](Optional name) { + if (name) + ptr->speak(*name); + }); +} + +bool TextToSpeech::isEnabled() const +{ + return enabled; +} diff --git a/src/TextToSpeech.hpp b/src/TextToSpeech.hpp new file mode 100644 index 0000000..aebd2a7 --- /dev/null +++ b/src/TextToSpeech.hpp @@ -0,0 +1,33 @@ +#ifndef TEXT_TO_SPEECH_HPP +#define TEXT_TO_SPEECH_HPP + +#include "UIElement.hpp" +#include "VConf.hpp" + +#include + +#include +#include +#include + + +class TextToSpeech : public std::enable_shared_from_this +{ +public: + TextToSpeech(); + ~TextToSpeech(); + + bool isEnabled() const; + void speak(std::string text) const; + void speak(const std::shared_ptr &element) const; + +private: + tts_h ttsHandler = nullptr; + bool enabled = false; + int speechRate = TTS_SPEED_AUTO; + const char *language = "en_US"; //TODO synchronize with system parameters + + std::vector callbackHandles; +}; + +#endif diff --git a/src/ToggleVoiceFeedbackEnabledActivity.cpp b/src/ToggleVoiceFeedbackEnabledActivity.cpp new file mode 100644 index 0000000..697323c --- /dev/null +++ b/src/ToggleVoiceFeedbackEnabledActivity.cpp @@ -0,0 +1,22 @@ +#include "Activity.hpp" +#include "ActivityFactory.hpp" +#include "UniversalSwitchLog.hpp" +#include "VConf.hpp" +#include "VConfKeys.hpp" + + +class ToggleVoiceFeedbackEnabledActivity : public Activity, private RegisterActivity +{ +public: + constexpr static const char *activityType = "TOGGLE_VOICE_FEEDBACK_ENABLED_ACTIVITY"; + ToggleVoiceFeedbackEnabledActivity() + : Activity(activityType) + {} + + bool process() override + { + auto isEnabled = Singleton::instance().get(VCONF_KEY_FEEDBACK_VOICE_ENABLED, true); + Singleton::instance().set(VCONF_KEY_FEEDBACK_VOICE_ENABLED, !isEnabled); + return true; + } +}; diff --git a/src/UniversalSwitch.cpp b/src/UniversalSwitch.cpp index 8f92c0d..6c19c61 100644 --- a/src/UniversalSwitch.cpp +++ b/src/UniversalSwitch.cpp @@ -1,5 +1,6 @@ #include "AccessoriesSwitchProvider.hpp" #include "ActivityFactory.hpp" +#include "Atspi.hpp" #include "CameraSwitchProvider.hpp" #include "CompositeSwitchProvider.hpp" #include "DBusInterface.hpp" @@ -8,12 +9,11 @@ #include "SQLiteConfiguration.hpp" #include "SwitchConfigurationItem.hpp" #include "SwitchManager.hpp" +#include "TextToSpeech.hpp" #include "UniversalSwitch.hpp" #include "UniversalSwitchLog.hpp" -#include "Window.hpp" #include "VConfKeys.hpp" -#include "Atspi.hpp" - +#include "Window.hpp" void UniversalSwitch::initialize() { @@ -46,9 +46,10 @@ void UniversalSwitch::startScanning() auto activityFactory = ActivityFactory::getInstance(); switchManager = SwitchManager::create(compositeSwitchProvider, configuration, activityFactory); + textToSpeech = std::make_shared(); + screenScannerManager = std::make_shared(); screenScannerManager->startAutoscanning(); - } void UniversalSwitch::stopScanning() @@ -59,6 +60,7 @@ void UniversalSwitch::stopScanning() switchManager->terminate(); switchManager.reset(); } + textToSpeech.reset(); atspi.reset(); } @@ -83,6 +85,11 @@ std::shared_ptr UniversalSwitch::getDBusInterface() const return dbusInterface; } +std::shared_ptr UniversalSwitch::getTextToSpeech() const +{ + return textToSpeech; +} + void UniversalSwitch::setSwitchManager(const std::shared_ptr &sm) { switchManager = sm; diff --git a/src/UniversalSwitch.hpp b/src/UniversalSwitch.hpp index 1ff7ee6..aaf4b4f 100644 --- a/src/UniversalSwitch.hpp +++ b/src/UniversalSwitch.hpp @@ -6,13 +6,16 @@ #include -class SwitchManager; + +class Atspi; class CompositeSwitchProvider; class Configuration; class DBusInterface; class ScreenScannerManager; +class SwitchManager; +class TextToSpeech; class Window; -class Atspi; + class UniversalSwitch { @@ -25,6 +28,7 @@ public: std::shared_ptr getScreenScannerManager() const; std::shared_ptr getAtspi() const; std::shared_ptr getMainWindow(); + std::shared_ptr getTextToSpeech() const; void setSwitchManager(const std::shared_ptr &sm); void setCompositeSwitchProvider(const std::shared_ptr &csp); @@ -43,6 +47,7 @@ private: std::shared_ptr configuration; std::shared_ptr dbusInterface; std::shared_ptr atspi; + std::shared_ptr textToSpeech; std::shared_ptr screenScannerManager; std::weak_ptr mainWindow; diff --git a/src/VConfKeys.hpp b/src/VConfKeys.hpp index cacc244..1b595e5 100644 --- a/src/VConfKeys.hpp +++ b/src/VConfKeys.hpp @@ -65,4 +65,5 @@ #define VCONF_KEY_HID_DETECTED "memory/isf/hw_keyboard_input_detected" +#define VCONF_KEY_LANGUAGE "db/menu_widget/language" #endif diff --git a/src/utils.hpp b/src/utils.hpp new file mode 100644 index 0000000..98aed69 --- /dev/null +++ b/src/utils.hpp @@ -0,0 +1,19 @@ +#ifndef UTILS_HPP +#define UTILS_HPP + + +namespace utils +{ + // TODO it would be removed when c++17 came + template + T clamp(T v, const T &lo, const T &hi) + { + if (v < lo) + v = lo; + else if (v > hi) + v = hi; + return v; + } +} + +#endif diff --git a/utils/setVconfKeys.sh b/utils/setVconfKeys.sh index db55b31..5224326 100755 --- a/utils/setVconfKeys.sh +++ b/utils/setVconfKeys.sh @@ -35,8 +35,8 @@ $VCONFTOOL int "${VCONF_PROJECT_PREFIX}FEEDBACK_CURSOR_COLOR" 0 $VCONFTOOL bool "${VCONF_PROJECT_PREFIX}FEEDBACK_SOUND_ENABLED" 1 $VCONFTOOL double "${VCONF_PROJECT_PREFIX}FEEDBACK_SOUND_VOLUME" 1.0 -$VCONFTOOL bool "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_ENABLED" 0 -$VCONFTOOL double "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_SPEECH_RATE" 1.0 +$VCONFTOOL bool "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_ENABLED" 0 +$VCONFTOOL int "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_SPEECH_RATE" 8 # value beetwen 1 and 15, 0 = TTS_SPEED_AUTO $VCONFTOOL double "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_SPEECH_VOLUME" 1.0 $VCONFTOOL int "${VCONF_PROJECT_PREFIX}GRANULARITY_UNIT" 0 # 0 = CHARACTER, 1 = WORD, 2 = LINE, 3 = PARAGRAPH