diff options
-rw-r--r-- | api/franca/hmi/speechservice/SpeechOutput.fidl | 12 | ||||
-rw-r--r-- | api/franca/navigation/navigationcore/NavigationCoreTypes.fidl | 4 | ||||
-rw-r--r-- | src/speech/CMakeLists.txt | 116 | ||||
-rw-r--r-- | src/speech/main.cpp | 254 | ||||
-rwxr-xr-x | src/speech/test-speech-capi.py | 88 |
5 files changed, 467 insertions, 7 deletions
diff --git a/api/franca/hmi/speechservice/SpeechOutput.fidl b/api/franca/hmi/speechservice/SpeechOutput.fidl index 1cac5ef..b41f27f 100644 --- a/api/franca/hmi/speechservice/SpeechOutput.fidl +++ b/api/franca/hmi/speechservice/SpeechOutput.fidl @@ -141,7 +141,8 @@ interface SpeechOutput { */ <** @description : Notifies the connection status **> - broadcast notifyConnectionStatus selective { +// broadcast notifyConnectionStatus selective { + broadcast notifyConnectionStatus { out { <** @description : Client connection status. **> ConnectionStatus connectionStatus @@ -149,7 +150,8 @@ interface SpeechOutput { } <** @description : Notifies the last reached marker. **> - broadcast notifyMarkerReached selective { +// broadcast notifyMarkerReached selective { + broadcast notifyMarkerReached { out { <** @description : ID of the processed chunk. **> ChunkID chunkID @@ -159,7 +161,8 @@ interface SpeechOutput { } <** @description : Notifies the queue status. **> - broadcast notifyQueueStatus selective { +// broadcast notifyQueueStatus selective { + broadcast notifyQueueStatus { out { <** @description : Fill status of the text buffer. **> QueueStatus queueStatus @@ -167,7 +170,8 @@ interface SpeechOutput { } <** @description : Notifies the TTS engine status. **> - broadcast notifyTTSStatus selective { +// broadcast notifyTTSStatus selective { + broadcast notifyTTSStatus { out { <** @description : Current status of the TTS. **> TtsStatus ttsStatus diff --git a/api/franca/navigation/navigationcore/NavigationCoreTypes.fidl b/api/franca/navigation/navigationcore/NavigationCoreTypes.fidl index 98ae3f4..e04be6e 100644 --- a/api/franca/navigation/navigationcore/NavigationCoreTypes.fidl +++ b/api/franca/navigation/navigationcore/NavigationCoreTypes.fidl @@ -24,7 +24,7 @@ typeCollection NavigationCoreTypes { ALTITUDE = 162 } - enumeration Notification { + enumeration Notification extends NotificationNavigation{ GUIDANCE_ERROR_NOMANEUVER = 0 GUIDANCE_ERROR_VOICENOTALLOWED LOCATIONINPUT_ERROR_NOMORELOCATIONINPUTHANDLES @@ -38,8 +38,6 @@ typeCollection NavigationCoreTypes { ROUTING_ERROR_ROUTEPREFERENCENOTSUPPORTED ROUTING_ERROR_WAYPOINTCANNOTBECHANGED ROUTING_ERROR_TOOMANYWAYPOINTS - SESSION_ERROR_NOMORESESSIONHANDLES - SESSION_ERROR_SESSIONNOTAVAILABLE } } diff --git a/src/speech/CMakeLists.txt b/src/speech/CMakeLists.txt new file mode 100644 index 0000000..5e092ba --- /dev/null +++ b/src/speech/CMakeLists.txt @@ -0,0 +1,116 @@ +########################################################################### +# @licence app begin@ +# SPDX-License-Identifier: MPL-2.0 +# +# Component Name: poi-server-capi +# +# Author: Philippe Colliot +# +# Copyright (C) 2015, PCA Peugeot Citroën +# +# License: +# This Source Code Form is subject to the terms of the +# Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with +# this file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# @licence end@ +########################################################################### +project(speech-output-server) +cmake_minimum_required(VERSION 2.8) + +message(STATUS ${PROJECT_NAME}) + +set(CMAKE_VERBOSE_MAKEFILE on) +set(CMAKE_CXX_FLAGS "-Wall -std=c++0x") + +set(API_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../api") +set(FRANCA_DIR "${API_DIR}/franca") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) + +# DBus Path +if(DBUS_LIB_PATH) + message(STATUS "DBUS_LIB_PATH = " ${DBUS_LIB_PATH}) + set(DBUS_INCLUDE_DIRS ${DBUS_LIB_PATH}/include/dbus-1.0 ${DBUS_LIB_PATH}/lib/dbus-1.0/include) + set(DBUS_LIBDIR ${DBUS_LIB_PATH}/lib) + set(DBUS_LIBRARIES ${DBUS_LIB_PATH}/lib/libdbus-1.so) +else() + message(FATAL_ERROR "Please specify the path to your patched DBus library using -DDBUS_LIB_PATH=yourPath") +endif() + +# Packages +find_package(PkgConfig REQUIRED) +find_package(CommonAPI 3.1.5 REQUIRED) +find_package(CommonAPI-DBus 3.1.5 REQUIRED) + +#pkg_check_modules(DBUS "dbus-1 >= 1.8.4") // #to be fixed, it doesn't work so the paths are set manually (see above) +pkg_check_modules(COMMONAPI "CommonAPI >= 3.1.5") +pkg_check_modules(COMMONAPI_DBUS "CommonAPI-DBus >= 3.1.5") +pkg_check_modules(GOBJECT gobject-2.0) +pkg_check_modules(GLIB REQUIRED glib-2.0) + +# Add the Franca files (that will generate the CommonAPI stuff) +set(COMMONAPI_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/src-gen") +add_subdirectory(${FRANCA_DIR}/hmi/speechservice "${CMAKE_CURRENT_BINARY_DIR}/franca") + +# Path to the generated files +set(API_VERSION_MAJOR 4) +set(API_VERSION "v${API_VERSION_MAJOR}") +set(PRJ_SRC_GEN_ROOT_PATH ${COMMONAPI_GEN_DIR}/${API_VERSION}/org/genivi) #files shared by all the APIs +set(PRJ_SRC_GEN_HMI_PATH ${PRJ_SRC_GEN_ROOT_PATH}/hmi) #files shared by the hmi APIs +set(PRJ_SRC_GEN_SPEECHOUTPUTSERVICE_PATH ${PRJ_SRC_GEN_HMI_PATH}/speechoutputservice) #files shared by the speechservice APIs + +# Source Files +FILE(GLOB PRJ_LOCAL_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +FILE(GLOB PRJ_STUB_GEN_SRCS + ${PRJ_SRC_GEN_ROOT_PATH}/*DBusStub*.cpp ${PRJ_SRC_GEN_ROOT_PATH}/*Types.cpp ${PRJ_SRC_GEN_ROOT_PATH}/*DBusDeployment.cpp ${PRJ_SRC_GEN_ROOT_PATH}/*StubDefault.cpp + ${PRJ_SRC_GEN_HMI_PATH}/*DBusStub*.cpp ${PRJ_SRC_GEN_HMI_PATH}/*Types.cpp ${PRJ_SRC_GEN_HMI_PATH}/*DBusDeployment.cpp ${PRJ_SRC_GEN_HMI_PATH}/*StubDefault.cpp + ${PRJ_SRC_GEN_SPEECHOUTPUTSERVICE_PATH}/*DBusStub*.cpp ${PRJ_SRC_GEN_SPEECHOUTPUTSERVICE_PATH}/*Types.cpp ${PRJ_SRC_GEN_SPEECHOUTPUTSERVICE_PATH}/*DBusDeployment.cpp ${PRJ_SRC_GEN_SPEECHOUTPUTSERVICE_PATH}/*StubDefault.cpp + ) +FILE(GLOB PRJ_STUB_IMPL_SRCS + ${PRJ_SRC_GEN_ROOT_PATH}/*Stub*.cpp + ${PRJ_SRC_GEN_HMI_PATH}/*Stub*.cpp + ${PRJ_SRC_GEN_SPEECHOUTPUTSERVICE_PATH}/*Stub*.cpp + ) + +set(PRJ_SRCS ${PRJ_LOCAL_SRCS} ${PRJ_STUB_GEN_SRCS} ${PRJ_STUB_IMPL_SRCS}) + +set(FLITE_INCLUDE_DIRS /usr/include/flite) +set(FLITE_LIBDIR /usr/lib/x86_64-linux-gnu/) +set(FLITE_LIBRARIES /usr/lib/x86_64-linux-gnu/libflite.so /usr/lib/x86_64-linux-gnu/libflite_cmu_us_kal.so) + +include_directories( + ${COMMONAPI_GEN_DIR} + ${PRJ_SRC_GEN_ROOT_PATH} + ${PRJ_SRC_GEN_HMI_PATH} + ${PRJ_SRC_GEN_SPEECHOUTPUTSERVICE_PATH} + ${DBUS_INCLUDE_DIRS} + ${COMMONAPI_INCLUDE_DIRS} + ${COMMONAPI_DBUS_INCLUDE_DIRS} + ${GOBJECT_INCLUDE_DIRS} + ${GLIB_INCLUDE_DIRS} + ${FLITE_INCLUDE_DIRS} +) + +link_directories( + ${DBUS_LIBDIR} + ${COMMONAPI_LIBDIR} + ${COMMONAPI_DBUS_LIBDIR} + ${GOBJECT_LIBRARY_DIRS} + ${GLIB_LIBRARY_DIRS} + ${FLITE_LIBDIR} +) + +set(LIBRARIES + ${DBUS_LIBRARIES} + ${COMMONAPI_LIBRARIES} + ${COMMONAPI_DBUS_LIBRARIES} + ${GOBJECT_LIBRARIES} + ${GLIB_LIBRARIES} + ${FLITE_LIBRARIES} +) + +# Build service +add_executable(${PROJECT_NAME} ${PRJ_SRCS}) +target_link_libraries(${PROJECT_NAME} ${LIBRARIES}) +install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/src/speech/main.cpp b/src/speech/main.cpp new file mode 100644 index 0000000..3cf0856 --- /dev/null +++ b/src/speech/main.cpp @@ -0,0 +1,254 @@ +/** +* @licence app begin@ +* SPDX-License-Identifier: MPL-2.0 +* +* \copyright Copyright (C) 2013-2014, PCA Peugeot Citroen +* +* \file main.cpp +* +* \brief This file is part of the speech proof of concept. +* +* \author Philippe Colliot <philippe.colliot@mpsa.com> +* +* \version 1.1 +* +* This Source Code Form is subject to the terms of the +* Mozilla Public License (MPL), v. 2.0. +* If a copy of the MPL was not distributed with this file, +* You can obtain one at http://mozilla.org/MPL/2.0/. +* +* For further information see http://www.genivi.org/. +* +* List of changes: +* <date>, <name>, <description of change> +* +* @licence end@ +*/ + +#include <stdbool.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <sys/types.h> +#include <iostream> +#include <cmath> +#include <typeinfo> +#include <dbus-c++-1/dbus-c++/glib-integration.h> +#include <semaphore.h> +#include <flite.h> + +extern "C" { + cst_voice* register_cmu_us_kal(const char *voxdir); + void unregister_cmu_us_kal(cst_voice *vox); +} + + +#include <CommonAPI/CommonAPI.hpp> +#include <CommonTypes.hpp> +#include <SpeechOutputStubDefault.hpp> + +using namespace v4::org::genivi::hmi::speechoutputservice; +using namespace v4::org::genivi; + +static std::shared_ptr < CommonAPI::Runtime > runtime; +static const std::string domain = "local"; + +static int m_lenLastChunk; +static int m_slotCount; +static const uint32_t MAX_CHUNK_SIZE = 1024 + 1; /**< Maximum size of any chunk which can be stored within the queue. */ +static const uint32_t MAX_SLOT_COUNT = 16; /**< Maximum number of chunks which can be present in the queue at the same time. */ +static const uint32_t MAX_SLOT_COUNT_NO_WARN = (3 * MAX_SLOT_COUNT) / 4; /**< When pushing a chunk, TAQS_WARNING shall be reported if the new occupied slot count exceeds MAX_SLOT_COUNT_NO_WARN. */ +static const uint32_t MAX_CHUNK_LENGTH = 1024; /**< max length of a single prompt part */ +static const uint32_t MAX_MARKER_LENGTH = 32; /**< marker tag inside the prompt chunk */ +static const uint32_t PROCESS_CHUNKS_LOOP = 100; +static cst_voice* mp_voice; +static pthread_mutex_t mutex; + +static std::string m_chunkBuffer; /** max size = MAX_CHUNK_SIZE*MAX_SLOT_COUNT */ + + gboolean processChunks(gpointer data) { + //worker thread to process chunks in buffer + + pthread_mutex_lock(&mutex); + + if (m_chunkBuffer.length() > 0) + { + printf("\n>>> [server] processChunks()\n"); + std::string tempBuffer; + tempBuffer.assign(m_chunkBuffer); + m_chunkBuffer.clear(); //buffer empty + m_slotCount=0; //reset counter of slots + + pthread_mutex_unlock(&mutex); + + // pass string to TTS engine + flite_text_to_speech(tempBuffer.c_str(),mp_voice,"play"); + } + else + { + pthread_mutex_unlock(&mutex); + } + return(true); +} + +class SpeechOutputServerStub +: public SpeechOutputStubDefault +{ + +public: + + SpeechOutputServerStub() { + m_version.setVersionMajor(1); + m_version.setVersionMinor(0); + m_version.setVersionMicro(0); + m_version.setDate("12-10-2016"); + + m_lenLastChunk = 0; + m_slotCount = 0; + + flite_init(); + + mp_voice = register_cmu_us_kal(NULL); + + } + + ~SpeechOutputServerStub() { + unregister_cmu_us_kal(mp_voice); + } + + /** + * description: This method returns the API version implemented by the SpeechOutput. + */ + void getVersion(const std::shared_ptr<CommonAPI::ClientId> _client, getVersionReply_t _reply) { + _reply(m_version); + } + + /** + * description: Must be called to open a SpeechOutputService session and to get the audio + * connection. + */ + void openPrompter(const std::shared_ptr<CommonAPI::ClientId> _client, SpeechOutput::ConnectionType _connectionType, SpeechOutput::PreProcessingType _preProcessingType, openPrompterReply_t _reply) { + printf("\n>>> [server] openPrompter()\n"); + + _reply(); + } + + /** + * description: The prompter must be opened to trigger the playback of the provided prompt. + * + The prompt length must not exceed the length of a PromptChunk + * buffer. + Synthesizes the provided text or if using the escape sequence of the + * engine supplier a wave file in a supported sampling rate is provided, the + * system will back back also wave files. + The text will be normalized using the + * context identifier provided to openprompter. This applies to matching + * prerecorded files as well as the synthesis of number and words that are + * matched to a lexical dictionary. + The synthesize will start if the prompter is + * idle, if the prompter is already playing the playback will be delayed until + * all previously added text chunks are played back. For every text chunk + * provided a notification will be send. + */ + void addTextChunk(const std::shared_ptr<CommonAPI::ClientId> _client, SpeechOutput::Chunk _chunk, addTextChunkReply_t _reply) { + printf("\n>>> [server] addTextChunk()\n \"%s\"\n", _chunk.c_str()); + + SpeechOutput::ChunkID _chunkID; + + // todo: manage _chunkID + + SpeechOutput::QueueStatus qStatus = SpeechOutput::QueueStatus::QS_UNKNOWN; + + if (_chunk.size() > 0 && _chunk.size() < MAX_CHUNK_LENGTH) + { + if (m_slotCount < (int) MAX_SLOT_COUNT) + { + if (m_slotCount <= (int) MAX_SLOT_COUNT_NO_WARN) + { + qStatus = SpeechOutput::QueueStatus::QS_LOW_FILL; + } + else + { + qStatus = SpeechOutput::QueueStatus::QS_HIGH_FILL; + } + m_chunkBuffer.append(_chunk); + m_lenLastChunk = _chunk.size(); + ++m_slotCount; + } + else + { + qStatus = SpeechOutput::QueueStatus::QS_FULL; + } + } + + fireNotifyQueueStatusEvent(qStatus); + + _reply(_chunkID); + } + + /** + * description: A prompt must be playing to perform an abort action. If no prompting operation + * in progress there will be no reaction of the system. + */ + void abortPrompter(const std::shared_ptr<CommonAPI::ClientId> _client, abortPrompterReply_t _reply) { + printf("\n>>> [server] abortPrompter()\n"); + + _reply(); + } + + /** + * description: The prompter is closed after the last text chunk submitted has finished playing. + */ + void closePrompter(const std::shared_ptr<CommonAPI::ClientId> _client, closePrompterReply_t _reply) { + printf("\n>>> [server] closePrompter()\n"); + + _reply(); + } + + +// Specific methods + + +private: + CommonTypes::Version m_version; + +}; + + +int main(int argc , char** argv ) +{ + GMainLoop * mainloop ; + guint processChunksID; + + // Set the global C and C++ locale to the user-configured locale, + // so we can use std::cout with UTF-8, via Glib::ustring, without exceptions. + std::locale::global(std::locale("")); + + // Common API data init + runtime = CommonAPI::Runtime::get(); + bool successfullyRegistered; + + const std::string instanceSpeechOutput = "SpeechOutput"; + std::shared_ptr<SpeechOutputServerStub> myServiceSpeechOutput = std::make_shared<SpeechOutputServerStub>(); + successfullyRegistered = runtime->registerService(domain, instanceSpeechOutput, myServiceSpeechOutput); + while (!successfullyRegistered) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + successfullyRegistered = runtime->registerService(domain, instanceSpeechOutput, myServiceSpeechOutput); + } + + processChunksID = g_timeout_add(PROCESS_CHUNKS_LOOP,processChunks,NULL); + + // Create a new GMainLoop with default context and initial state of "not running " + mainloop = g_main_loop_new (g_main_context_default() , FALSE ); + + // Send a feedback to the user + std::cout << "speech server started" << std::endl; + + // loop listening + + g_main_loop_run ( mainloop ); + + + return EXIT_SUCCESS; +} diff --git a/src/speech/test-speech-capi.py b/src/speech/test-speech-capi.py new file mode 100755 index 0000000..3ed92e1 --- /dev/null +++ b/src/speech/test-speech-capi.py @@ -0,0 +1,88 @@ +#!/usr/bin/python + +""" +************************************************************************** +* @licence app begin@ +* SPDX-License-Identifier: MPL-2.0 +* +* \copyright Copyright (C) 2016, PSA GROUP +* +* \file test-speech-capi.py +* +* \brief This simple test shows how the speech +* could be easily tested using a python script +* +* \author Philippe Colliot <philippe.colliot@mpsa.com> +* +* \version 1.0 +* +* This Source Code Form is subject to the terms of the +* Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with +# this file, You can obtain one at http://mozilla.org/MPL/2.0/. +* List of changes: +* +* @licence end@ +************************************************************************** +""" + +import dbus +import gobject +import dbus.mainloop.glib +import time + +import pdb; +#pdb.set_trace() +#constants as defined in the Navigation API +GENIVI_Configuration_Settings_LOCALE = 37 +GENIVI_SearchStatusState_FINISHED = 1298 +GENIVI_SearchStatusState_NOT_STARTED = 1296 + +#constants used into the script +TIME_OUT = 10000 + +def catch_speech_notifyConnectionStatus_signal_handler(connectionStatus): + print("Connection status: " + str(int(connectionStatus))) + +def catch_speech_notifyMarkerReached_signal_handler(chunkID,marker): + print("Chunk ID: " + chunkID) + +def catch_speech_notifyQueueStatus_signal_handler(queueStatus): + print("Queue status: " + str(int(queueStatus))) + +def catch_speech_notifyTTSStatus_signal_handler(ttsStatus): + print("TTS status: " + str(int(ttsStatus))) + +#timeout +def timeout(): + print ('Timeout Expired') + print ('\nTest FAILED') + loop.quit() + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + +#connect to session bus +bus = dbus.SessionBus() + +bus.add_signal_receiver(catch_speech_notifyConnectionStatus_signal_handler, \ + dbus_interface = "org.genivi.hmi.speechoutputservice.SpeechOutput", \ + signal_name = "notifyConnectionStatus") +bus.add_signal_receiver(catch_speech_notifyMarkerReached_signal_handler, \ + dbus_interface = "org.genivi.hmi.speechoutputservice.SpeechOutput", \ + signal_name = "notifyMarkerReached") +bus.add_signal_receiver(catch_speech_notifyQueueStatus_signal_handler, \ + dbus_interface = "org.genivi.hmi.speechoutputservice.SpeechOutput", \ + signal_name = "notifyQueueStatus") +bus.add_signal_receiver(catch_speech_notifyTTSStatus_signal_handler, \ + dbus_interface = "org.genivi.hmi.speechoutputservice.SpeechOutput", \ + signal_name = "notifyTTSStatus") + +speech = bus.get_object('org.genivi.hmi.speechoutputservice.SpeechOutput_SpeechOutput','/SpeechOutput') +g_speech_interface = dbus.Interface(speech, dbus_interface='org.genivi.hmi.speechoutputservice.SpeechOutput') + +g_speech_interface.addTextChunk(dbus.String("Hello")) + +#main loop +gobject.timeout_add(TIME_OUT, timeout) +loop = gobject.MainLoop() +loop.run() |