From 3b619c6ed499f2fc6ef650a5b11dde71b4f3e6a0 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Thu, 20 Dec 2018 22:30:11 -0500 Subject: navigation: Add 4A support To properly support 4A in the navigation app, add a patch to pull in the 4A binding, add some code to open / close the "navigation" role, and use a simple gstreamer pipeline to play the generated files. The existing playback script templates have had their playback commands removed, but are retained for driving the voice file generation. Note that a patch is being used for now, as gpsnavi is managed outside of AGL on github. Additionally, that repository has no flounder branch, and its HEAD does not work on flounder at present, complicating things. Change-Id: Ie5c63c2ae22237a80dc034b98d7279d3c203273f Signed-off-by: Scott Murray (cherry picked from commit 6f1a4e94f9b978639e858fbe8ce98012a1781ee5) --- .../navigation/0001-add-4A-playback-support.patch | 361 +++++++++++++++++++++ recipes-demo-hmi/navigation/navigation_git.bb | 3 +- 2 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 recipes-demo-hmi/navigation/navigation/0001-add-4A-playback-support.patch diff --git a/recipes-demo-hmi/navigation/navigation/0001-add-4A-playback-support.patch b/recipes-demo-hmi/navigation/navigation/0001-add-4A-playback-support.patch new file mode 100644 index 000000000..b4192358c --- /dev/null +++ b/recipes-demo-hmi/navigation/navigation/0001-add-4A-playback-support.patch @@ -0,0 +1,361 @@ +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); ++} diff --git a/recipes-demo-hmi/navigation/navigation_git.bb b/recipes-demo-hmi/navigation/navigation_git.bb index d4e69b566..119d7d102 100755 --- a/recipes-demo-hmi/navigation/navigation_git.bb +++ b/recipes-demo-hmi/navigation/navigation_git.bb @@ -9,7 +9,7 @@ LIC_FILES_CHKSUM="file://LICENSE;md5=3595e9c703a847d990664d2b396a9df0 \ DEPENDS = " \ glib-2.0 freetype sqlite3 wayland zlib expat openssl virtual/libgles2 virtual/libgl virtual/egl \ - wayland libdbus-c++ af-main af-binder libwindowmanager libhomescreen \ + wayland libdbus-c++ af-main af-binder libwindowmanager libhomescreen gstreamer1.0 \ " RDEPENDS_${PN} = " flite openjtalk glib-2.0 freetype sqlite3 wayland zlib expat openssl \ @@ -20,6 +20,7 @@ RDEPENDS_${PN} += " agl-service-navigation " SRCREV="2d83844150cce0e245a3d90af67f43385604b569" SRC_URI="git://github.com/AGLExport/gpsnavi.git;branch=agl \ + file://0001-add-4A-playback-support.patch \ file://download_mapdata_jp.sh \ file://download_mapdata_uk.sh \ file://org.agl.naviapi.conf \ -- cgit 1.2.3-korg