gpsnavi: Add AGL 4A playback support To properly support 4A on AGL, pull in the binding, add some code to open / close the "navigation" role, and use a simple gstreamer pipeline to play the generated files. The existing script templates have had their playback commands removed, but are retained for driving the voice file generation. Signed-off-by: Scott Murray diff --git a/agl/config.xml b/agl/config.xml index 9d4c0ca..960f652 100755 --- a/agl/config.xml +++ b/agl/config.xml @@ -7,11 +7,13 @@ AISIN AW + + GPL diff --git a/configure.ac b/configure.ac index 33348c3..6b7b391 100755 --- a/configure.ac +++ b/configure.ac @@ -26,11 +26,13 @@ PKG_CHECK_MODULES([GLIB], [glib-2.0 gthread-2.0]) PKG_CHECK_MODULES([FREETYPE2], [freetype2]) PKG_CHECK_MODULES([WAYLAND], [wayland-client wayland-egl egl]) PKG_CHECK_MODULES([GL], [glesv2]) +PKG_CHECK_MODULES([GSTREAMER], [gstreamer-1.0]) PKG_CHECK_MODULES([ZLIB], [zlib]) PKG_CHECK_MODULES([SQLITE3], [sqlite3]) PKG_CHECK_MODULES([EXPAT], [expat]) PKG_CHECK_MODULES([OPENSSL], [openssl]) PKG_CHECK_MODULES([DBUSCXX], [dbus-c++-1]) +PKG_CHECK_MODULES([LIBAFBWSC], [libafbwsc]) PKG_CHECK_MODULES([WINDOWMANAGER], [libwindowmanager]) PKG_CHECK_MODULES([HOMESCREEN], [libhomescreen]) diff --git a/flite_agl.in b/flite_agl.in index 28b512c..d11b043 100644 --- a/flite_agl.in +++ b/flite_agl.in @@ -1,6 +1,3 @@ #!/bin/sh TMP=/tmp/navi.wav echo "$1" | flite_hts_engine -m @datadir@/Voice/us/cmu_us_arctic_slt.htsvoice -o $TMP -paplay --property='media.role=Navi' $TMP -rm -f $TMP - diff --git a/jtalk_agl.in b/jtalk_agl.in index 76900f4..857c824 100644 --- a/jtalk_agl.in +++ b/jtalk_agl.in @@ -1,6 +1,3 @@ #!/bin/sh TMP=/tmp/navi.wav echo "$1" | open_jtalk -ow $TMP -m @exec_prefix@/share/Voice/mei/mei_normal.htsvoice -x @exec_prefix@/share/dic/ -paplay --property='media.role=Navi' $TMP -rm -f $TMP - diff --git a/src/Makefile.am b/src/Makefile.am index affb9a5..6d0fa55 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,6 +26,7 @@ sms/sms-core/SMCoreDM/RG/RG_GuideNear.c \ sms/sms-core/SMCoreDM/RG/RG_ShareData.c \ sms/sms-core/SMCoreDM/RG/RG_GuideVoiceBuild_en.c \ sms/sms-core/SMCoreDM/RG/RG_GuideVoice.c \ +sms/sms-core/SMCoreDM/RG/RG_GuideVoice4A.cpp \ sms/sms-core/SMCoreDM/RG/RG_GuideApi.c \ sms/sms-core/SMCoreDM/SCRTThread.c \ sms/sms-core/SMCoreDAL/SMDALAreaCls.c \ @@ -537,6 +538,8 @@ libnavicore_la_CFLAGS = -fPIC \ @FREETYPE2_CFLAGS@ \ @WAYLAND_CFLAGS@ \ @GL_CFLAGS@ \ + @GSTREAMER_CFLAGS@ \ + @LIBAFBWSC_CFLAGS@ \ @ZLIB_CFLAGS@ \ @SQLITE3_CFLAGS@ \ @EXPAT_CFLAGS@ \ @@ -547,6 +550,8 @@ libnavicore_la_CXXFLAGS = -fPIC \ @FREETYPE2_CFLAGS@ \ @WAYLAND_CFLAGS@ \ @GL_CFLAGS@ \ + @GSTREAMER_CFLAGS@ \ + @LIBAFBWSC_CFLAGS@ \ @ZLIB_CFLAGS@ \ @SQLITE3_CFLAGS@ \ @EXPAT_CFLAGS@ \ @@ -555,6 +560,8 @@ libnavicore_la_CXXFLAGS = -fPIC \ libnavicore_la_LIBADD = \ @OPENSSL_LIBS@ \ @GL_LIBS@ \ + @GSTREAMER_LIBS@ \ + @LIBAFBWSC_LIBS@ \ @ZLIB_LIBS@ \ @SQLITE3_LIBS@ \ @EXPAT_LIBS@ diff --git a/src/sms/sms-core/SMCoreDM/RG/RG_GuideVoice.c b/src/sms/sms-core/SMCoreDM/RG/RG_GuideVoice.c index 3828d5c..36e6775 100755 --- a/src/sms/sms-core/SMCoreDM/RG/RG_GuideVoice.c +++ b/src/sms/sms-core/SMCoreDM/RG/RG_GuideVoice.c @@ -16,6 +16,8 @@ #include "sms-core/SMCoreDM/SMCoreDMInternal.h" +extern int play_voice(const char* voice_gen_cmd); + typedef struct _tts_text_tbl { INT32 code; @@ -205,9 +207,9 @@ E_SC_RESULT RG_CTL_CreateVoiceText(RT_NAME_t *in, INT32 language) } else { - strncat(tts_voice, "\" & ", (TTSMAX - len - 1)); + strncat(tts_voice, "\"", (TTSMAX - len - 1)); - system(tts_voice); + play_voice(tts_voice); } return (e_SC_RESULT_SUCCESS); diff --git a/src/sms/sms-core/SMCoreDM/RG/RG_GuideVoice4A.cpp b/src/sms/sms-core/SMCoreDM/RG/RG_GuideVoice4A.cpp new file mode 100644 index 0000000..6b59c0e --- /dev/null +++ b/src/sms/sms-core/SMCoreDM/RG/RG_GuideVoice4A.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2018 Konsulko Group + * Author: Scott Murray + * + * This program is licensed under GPL version 2 license. + * See the LICENSE file distributed with this source file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" +{ +#include +#include +#include +} + +#define NAVI_TMPFILE "/tmp/navi.wav" + +static struct afb_wsj1* ws; +static struct afb_wsj1_itf itf; +sd_event* loop; + +// port and token from src/glview/glview_wayland.cpp +extern long g_port; +extern std::string g_token; + +static int set_role_state(bool state); + +void play_voice_file(const char *output) +{ + if(!output) + return; + + // Initialize GStreamer + gst_init(NULL, NULL); + + std::string pipeline_str = "filesrc location="; + pipeline_str += NAVI_TMPFILE; + pipeline_str += " ! wavparse ! audioconvert ! audioresample ! alsasink device="; + pipeline_str += output; + GstElement *pipeline = gst_parse_launch(pipeline_str.c_str(), NULL); + if(!pipeline) { + std::cerr << "gstreamer pipeline construction failed!" << std::endl; + return; + } + + // Start pipeline + gst_element_set_state(pipeline, GST_STATE_PLAYING); + std::cerr << "Playing guidance" << std::endl; + + // Wait until error or EOS + GstBus *bus = gst_element_get_bus(pipeline); + GstMessage *msg = gst_bus_timed_pop_filtered(bus, + GST_CLOCK_TIME_NONE, + (GstMessageType) (GST_MESSAGE_ERROR | GST_MESSAGE_EOS)); + + // Free resources + if(msg != NULL) + gst_message_unref(msg); + gst_object_unref(bus); + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(pipeline); + + // Remove temporary file + unlink(NAVI_TMPFILE); + + return; +} + +static void on_hangup(void *closure, struct afb_wsj1 *wsj) +{ +} + +static void on_call(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg) +{ +} + +static void on_event(void* closure, const char* event, struct afb_wsj1_msg *msg) +{ +} + +static void on_reply(void *closure, struct afb_wsj1_msg *msg) +{ + bool state = (bool) closure; + + if(!state) { + // Role is closed, return + return; + } + + // We opened the role, play the file + struct json_object* reply = afb_wsj1_msg_object_j(msg); + if(reply) { + struct json_object* response; + int rc = json_object_object_get_ex(reply, "response", &response); + if(rc) { + struct json_object* val; + rc = json_object_object_get_ex(response, "device_uri", &val); + if (rc && json_object_get_string_len(val)) { + const char* jres_pcm = json_object_get_string(val); + play_voice_file(jres_pcm); + } + } + } + + // Give up role now that we're done + set_role_state(false); +} + +static void *event_loop_run(void *args) +{ + struct sd_event* loop = (struct sd_event*)(args); + + for(;;) + sd_event_run(loop, 30000000); +} + +static int start_event_loop(void) +{ + if(ws && loop) { + pthread_t thread_id; + if(pthread_create(&thread_id, NULL, event_loop_run, loop) != 0) { + return -1; + } else { + return thread_id; + } + } else { + return -1; + } +} + +static int init_ws(int port, std::string &token) +{ + loop = NULL; + std::string uri; + + if(sd_event_default(&loop) < 0) { + std::cerr << __FUNCTION__ << ": Failed to create event loop" << std::endl; + goto error; + } + + // Initialize interface for websocket + itf.on_hangup = on_hangup; + itf.on_call = on_call; + itf.on_event = on_event; + + uri = "ws://localhost:" + std::to_string(port) + "/api?token=" + token; + std::cerr << "Using URI: " << uri << std::endl; + ws = afb_ws_client_connect_wsj1(loop, uri.c_str(), &itf, NULL); + if(ws == NULL) { + std::cerr << __FUNCTION__ << ": Failed to create websocket connection" << std::endl; + goto error; + } + start_event_loop(); + return 0; +error: + if(loop) { + sd_event_unref(loop); + } + return -1; +} + +static int set_role_state(bool state) +{ + int rc; + json_object *jsonData = json_object_new_object(); + + json_object_object_add(jsonData, "action", json_object_new_string(state ? "open" : "close")); + rc = afb_wsj1_call_j(ws, "ahl-4a", "navigation", jsonData, on_reply, (void*) state); + if (rc < 0) { + std::cerr << __FUNCTION__ << ": Failed to call ahl-4a/navigation!" << std::endl; + } + return rc; +} + +pthread_mutex_t ws_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void *play_voice_handler(void *data) +{ + int rc; + char *voice_gen_cmd = (char*) data; + if(!voice_gen_cmd) + return NULL; + + pthread_mutex_lock(&ws_mutex); + if(!ws) { + rc = init_ws(g_port, g_token); + pthread_mutex_unlock(&ws_mutex); + if(rc < 0) + return NULL; + } + pthread_mutex_unlock(&ws_mutex); + + // Generate guidance voice file + rc = system(voice_gen_cmd); + free(voice_gen_cmd); + + // Try to get role and play file + set_role_state(true); + + return NULL; +} + +extern "C" int play_voice(const char* voice_gen_cmd) +{ + pthread_t handler_thread; + char *tmp; + + if(!voice_gen_cmd) + return -1; + + tmp = strdup(voice_gen_cmd); + return pthread_create(&handler_thread, NULL, play_voice_handler, (void*) tmp); +}