# Check for the necessary Murphy components.
PKG_CHECK_MODULES(MURPHY_COMMON, murphy-common)
PKG_CHECK_MODULES(MURPHY_PULSE, murphy-pulse)
+PKG_CHECK_MODULES(MURPHY_GLIB, murphy-glib)
PKG_CHECK_MODULES(MURPHY_RESOURCE, murphy-resource)
PKG_CHECK_MODULES(MURPHY_BREEDLINE, breedline-murphy)
AC_SUBST(MURPHY_COMMON_CFLAGS)
AC_SUBST(MURPHY_COMMON_LIBS)
AC_SUBST(MURPHY_PULSE_CFLAGS)
AC_SUBST(MURPHY_PULSE_LIBS)
+AC_SUBST(MURPHY_GLIB_CFLAGS)
+AC_SUBST(MURPHY_GLIB_LIBS)
AC_SUBST(MURPHY_RESOURCE_CFLAGS)
AC_SUBST(MURPHY_RESOURCE_LIBS)
AC_SUBST(MURPHY_BREEDLINE_CFLAGS)
Makefile
utils/Makefile
src/Makefile
+ src/plugins/native-client-interface/srs-native.pc
])
AC_OUTPUT
plugindir = $(libdir)/srs/plugins
plugin_LTLIBRARIES =
+lib_LTLIBRARIES =
+EXTRA_DIST =
+pkgconfig_DATA =
+pkgconfigdir = ${libdir}/pkgconfig
+
AM_CPPFLAGS = -I$(top_builddir)
AM_CFLAGS = $(GLIB_CFLAGS)
-rdynamic
if DBUS_ENABLED
-# SRS command line test clinet
+# SRS command line test client
bin_PROGRAMS += srs-client
srs_client_SOURCES = \
$(MURPHY_DBUS_LIBS)
endif
+# Native client interface plugin
+plugin_LTLIBRARIES += plugin-native-client.la
+
+plugin_native_client_la_SOURCES = \
+ plugins/native-client-interface/native-server.c \
+ plugins/native-client-interface/native-messages.c
+
+plugin_native_client_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(MURPHY_COMMON_CFLAGS)
+
+plugin_native_client_la_LDFLAGS = \
+ -module -avoid-version
+
+plugin_native_client_la_LIBADD = \
+ $(MURPHY_COMMON_LIBS)
+
+# Native client library
+lib_LTLIBRARIES += libsrs-native.la
+EXTRA_DIST += plugins/native-client-interface/srs-native.pc
+pkgconfig_DATA += plugins/native-client-interface/srs-native.pc
+
+libsrs_native_ladir = $(includedir)/srs
+libsrs_native_la_HEADERS = \
+ plugins/native-client-interface/native-client.h \
+ plugins/native-client-interface/native-config.h \
+ plugins/native-client-interface/native-messages.h
+
+libsrs_native_la_SOURCES = \
+ plugins/native-client-interface/native-client.c \
+ plugins/native-client-interface/native-messages.c
+
+libsrs_native_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(MURPHY_COMMON_CFLAGS)
+
+libsrs_native_la_LIBADD = \
+ $(MURPHY_COMMON_LIBS)
+
+# native client API command line test client
+bin_PROGRAMS += srs-native-client
+
+srs_native_client_SOURCES = \
+ plugins/native-client-interface/test-client.c
+
+srs_native_client_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(MURPHY_PULSE_CFLAGS) \
+ $(MURPHY_BREEDLINE_CFLAGS) \
+ $(MURPHY_COMMON_CFLAGS) \
+ $(MURPHY_GLIB_FLAGS)
+
+srs_native_client_LDADD = \
+ $(MURPHY_PULSE_LIBS) \
+ $(MURPHY_BREEDLINE_LIBS) \
+ $(MURPHY_COMMON_LIBS) \
+ $(MURPHY_GLIB_LIBS) \
+ $(PULSE_LIBS) \
+ libsrs-native.la
+
+libsrs_native_client_la_LDFLAGS = \
+ -version-info @SRS_VERSION_INFO@
+
# SRS fake speech engine plugin
plugin_LTLIBRARIES += plugin-fake-speech.la
--- /dev/null
+all:
+ $(MAKE) -C ../.. $@
+
+%:
+ $(MAKE) -C ../.. $(MAKECMDGOALS)
--- /dev/null
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/glib-glue.h>
+#include <murphy/common/transport.h>
+
+#include "native-messages.h"
+#include "native-client.h"
+#include "native-config.h"
+
+
+/*
+ * SRS client library context
+ */
+
+struct srs_s {
+ mrp_mainloop_t *ml; /* main loop */
+ mrp_transport_t *t; /* transport to server */
+ void *user_data; /* opaque client user data */
+ char *name; /* client name */
+ char *appclass; /* client application class */
+ char **commands; /* client speech command set */
+ size_t ncommand; /* number of speech commands */
+ srs_connect_notify_t conn_notify; /* connection notification callback */
+ srs_focus_notify_t focus_notify; /* focus notification callback */
+ srs_command_notify_t cmd_notify; /* command notification callback */
+ int registered:1; /* whether we're registered */
+ mrp_list_hook_t reqq; /* pending request queue */
+ uint32_t reqno; /* next request number */
+ mrp_list_hook_t voiceq; /* pending rendering queue */
+ uint32_t cvid; /* next client voice id */
+};
+
+
+typedef struct {
+ mrp_list_hook_t hook;
+ uint32_t svid;
+ uint32_t cvid;
+ srs_render_notify_t cb;
+ void *cb_data;
+ int cancelled : 1;
+} voice_req_t;
+
+
+typedef union {
+ voice_req_t voice_req;
+ struct {
+ srs_voiceqry_notify_t cb;
+ void *cb_data;
+ } voice_qry;
+ struct {
+ uint32_t id;
+ } voice_ccl;
+} request_data_t;
+
+typedef struct {
+ mrp_list_hook_t hook; /* hook to request queue */
+ uint32_t reqno; /* request number */
+ uint32_t type; /* request type */
+ request_data_t data; /* type-specific request data */
+} request_t;
+
+
+
+
+static void status_reply(srs_t *srs, srs_rpl_status_t *rpl);
+static void rendervoice_reply(srs_t *srs, srs_rpl_voice_t *rpl);
+static void queryvoices_reply(srs_t *srs, srs_rpl_voiceqry_t *rpl);
+static void focus_event(srs_t *srs, srs_evt_focus_t *evt);
+static void command_event(srs_t *srs, srs_evt_command_t *evt);
+static void voice_event(srs_t *srs, srs_evt_voice_t *evt);
+static mrp_mainloop_t *srs_mml; /* Murphy mainloop to use, if any */
+static GMainLoop *srs_gml; /* GMainLoop to use, if any */
+
+
+static void recvfrom_message(mrp_transport_t *t, void *data, uint32_t type_id,
+ mrp_sockaddr_t *addr, socklen_t addrlen,
+ void *user_data)
+{
+ MRP_UNUSED(t);
+ MRP_UNUSED(data);
+ MRP_UNUSED(type_id);
+ MRP_UNUSED(addr);
+ MRP_UNUSED(addrlen);
+ MRP_UNUSED(user_data);
+
+ mrp_log_info("received a message of type 0x%x from the server", type_id);
+}
+
+
+static void recv_message(mrp_transport_t *t, void *data, uint32_t type_id,
+ void *user_data)
+{
+ srs_t *srs = (srs_t *)user_data;
+ srs_msg_t *msg = (srs_msg_t *)data;
+ request_t *req;
+
+ MRP_UNUSED(t);
+ MRP_UNUSED(user_data);
+
+ mrp_log_info("received a message of type 0x%x from the server", type_id);
+
+ switch (msg->type) {
+ case SRS_REPLY_STATUS:
+ status_reply(srs, &msg->status_rpl);
+ break;
+ case SRS_REPLY_RENDERVOICE:
+ rendervoice_reply(srs, &msg->voice_rpl);
+ break;
+ case SRS_REPLY_QUERYVOICES:
+ queryvoices_reply(srs, &msg->voice_lst);
+ break;
+ case SRS_EVENT_FOCUS:
+ focus_event(srs, &msg->focus_evt);
+ break;
+ case SRS_EVENT_COMMAND:
+ command_event(srs, &msg->command_evt);
+ break;
+ case SRS_EVENT_VOICE:
+ voice_event(srs, &msg->voice_evt);
+ break;
+
+ default:
+ mrp_log_error("Received unknown message of type 0x%x.", type_id);
+ }
+
+}
+
+
+static void closed_event(mrp_transport_t *t, int error, void *user_data)
+{
+ srs_t *srs = (srs_t *)user_data;
+ int status = 0;
+ char *msg = !error ? "connection closed" : "connection error";
+
+ mrp_debug("transport closed by server");
+
+ srs->conn_notify(srs, status, msg, srs->user_data);
+}
+
+
+static int queue_request(srs_t *srs, srs_msg_t *req, request_data_t *data)
+{
+ request_t *r;
+
+ if ((r = mrp_allocz(sizeof(*r))) == NULL)
+ return -1;
+
+ mrp_list_init(&r->hook);
+ r->type = req->any_req.type;
+ r->reqno = req->any_req.reqno = srs->reqno++;
+
+ if (data != NULL)
+ r->data = *data;
+
+ if (send_message(srs->t, req) == 0) {
+ mrp_list_append(&srs->reqq, &r->hook);
+ return 0;
+ }
+ else {
+ mrp_free(r);
+ return -1;
+ }
+}
+
+
+static request_t *find_request(srs_t *srs, uint32_t reqno)
+{
+ mrp_list_hook_t *p, *n;
+ request_t *r;
+
+ mrp_list_foreach(&srs->reqq, p, n) {
+ r = mrp_list_entry(p, typeof(*r), hook);
+
+ if (r->reqno == reqno)
+ return r;
+ }
+
+ return NULL;
+}
+
+
+static void purge_request(request_t *r)
+{
+ mrp_list_delete(&r->hook);
+ mrp_free(r);
+}
+
+
+static void purge_reqq(srs_t *srs)
+{
+ mrp_list_hook_t *p, *n;
+ request_t *r;
+
+ mrp_list_foreach(&srs->reqq, p, n) {
+ r = mrp_list_entry(p, typeof(*r), hook);
+ purge_request(r);
+ }
+}
+
+
+static voice_req_t *add_voice_req(srs_t *srs, voice_req_t *req)
+{
+ voice_req_t *r;
+
+ if ((r = mrp_allocz(sizeof(*r))) == NULL)
+ return NULL;
+
+ mrp_list_init(&r->hook);
+ r->svid = req->svid;
+ r->cvid = req->cvid;
+ r->cb = req->cb;
+ r->cb_data = req->cb_data;
+
+ mrp_list_append(&srs->voiceq, &r->hook);
+
+ return r;
+}
+
+
+static voice_req_t *voice_req_for_cvid(srs_t *srs, uint32_t cvid)
+{
+ mrp_list_hook_t *p, *n;
+ voice_req_t *r;
+
+ mrp_list_foreach(&srs->voiceq, p, n) {
+ r = mrp_list_entry(p, typeof(*r), hook);
+
+ if (r->cvid == cvid)
+ return r;
+ }
+
+ return NULL;
+}
+
+
+static voice_req_t *voice_req_for_svid(srs_t *srs, uint32_t svid)
+{
+ mrp_list_hook_t *p, *n;
+ voice_req_t *r;
+
+ mrp_list_foreach(&srs->voiceq, p, n) {
+ r = mrp_list_entry(p, typeof(*r), hook);
+
+ if (r->svid == svid)
+ return r;
+ }
+
+ return NULL;
+}
+
+
+static request_t *request_for_cvid(srs_t *srs, uint32_t cvid)
+{
+ mrp_list_hook_t *p, *n;
+ request_t *r;
+
+ mrp_list_foreach(&srs->reqq, p, n) {
+ r = mrp_list_entry(p, typeof(*r), hook);
+
+ if (r->type == SRS_REQUEST_RENDERVOICE &&
+ r->data.voice_req.cvid == cvid)
+ return r;
+ }
+
+ return NULL;
+}
+
+
+static void purge_voice_req(voice_req_t *r)
+{
+ mrp_list_delete(&r->hook);
+ mrp_free(r);
+}
+
+
+static void purge_voiceq(srs_t *srs)
+{
+ mrp_list_hook_t *p, *n;
+ voice_req_t *r;
+
+ mrp_list_foreach(&srs->voiceq, p, n) {
+ r = mrp_list_entry(p, typeof(*r), hook);
+ purge_voice_req(r);
+ }
+}
+
+
+void srs_set_mainloop(mrp_mainloop_t *ml)
+{
+ if (srs_mml == NULL) {
+ srs_mml = ml;
+ return;
+ }
+
+ if (ml == NULL) {
+ srs_mml = NULL;
+ return;
+ }
+
+ if (srs_mml != ml)
+ mrp_log_warning("SRS native client mainloop already set (to %p).",
+ srs_mml);
+}
+
+
+void srs_set_gmainloop(GMainLoop *gml)
+{
+ if (srs_gml == NULL) {
+ srs_gml = g_main_loop_ref(gml);
+ return;
+ }
+
+ if (gml == NULL) {
+ g_main_loop_unref(srs_gml);
+ srs_gml = NULL;
+ return;
+ }
+
+ if (srs_gml != gml)
+ mrp_log_warning("SRS native client GMainLoop already set (to %p).",
+ srs_gml);
+}
+
+
+srs_t *srs_create(const char *name, const char *appclass, char **commands,
+ size_t ncommand, srs_connect_notify_t conn_notify,
+ srs_focus_notify_t focus_notify,
+ srs_command_notify_t cmd_notify, void *user_data)
+{
+ srs_t *srs;
+ int i;
+
+ if (conn_notify == NULL)
+ return NULL;
+
+ if ((srs = mrp_allocz(sizeof(*srs))) == NULL)
+ return NULL;
+
+ mrp_list_init(&srs->reqq);
+ mrp_list_init(&srs->voiceq);
+ srs->reqno = 1;
+ srs->cvid = 1;
+
+ if (srs_mml != NULL)
+ srs->ml = srs_mml;
+ else if (srs_gml != NULL)
+ srs->ml = mrp_mainloop_glib_get(srs_gml);
+ else
+ srs->ml = mrp_mainloop_create();
+
+ if (srs->ml == NULL)
+ goto fail;
+
+ srs->user_data = user_data;
+ srs->name = mrp_strdup(name);
+ srs->appclass = mrp_strdup(appclass);
+ srs->conn_notify = conn_notify;
+ srs->focus_notify = focus_notify;
+ srs->cmd_notify = cmd_notify;
+
+ if (srs->name == NULL || srs->appclass == NULL)
+ goto fail;
+
+ if (ncommand > 0) {
+ if (cmd_notify == NULL)
+ goto fail;
+
+ srs->ncommand = ncommand;
+ srs->commands = mrp_allocz_array(char *, ncommand);
+
+ if (srs->commands == NULL)
+ goto fail;
+
+ for (i = 0; i < srs->ncommand; i++) {
+ srs->commands[i] = mrp_strdup(commands[i]);
+
+ if (srs->commands[i] == NULL)
+ goto fail;
+ }
+ }
+
+ return srs;
+
+ fail:
+ srs_destroy(srs);
+
+ return NULL;
+}
+
+
+void srs_destroy(srs_t *srs)
+{
+ int i;
+
+ if (srs == NULL)
+ return;
+
+ mrp_transport_destroy(srs->t);
+ purge_reqq(srs);
+
+ if (srs->ml != srs_mml)
+ mrp_mainloop_destroy(srs->ml);
+
+ mrp_free(srs->name);
+ mrp_free(srs->appclass);
+
+ if (srs->commands != NULL) {
+ for (i = 0; i < srs->ncommand; i++)
+ mrp_free(srs->commands[i]);
+
+ mrp_free(srs->commands);
+ }
+
+ mrp_free(srs);
+}
+
+
+int srs_connect(srs_t *srs, const char *server, int reconnect)
+{
+ static mrp_transport_evt_t evt = {
+ { .recvnative = recv_message },
+ { .recvnativefrom = recvfrom_message },
+ .closed = closed_event ,
+ .connection = NULL
+ };
+
+ mrp_sockaddr_t addr;
+ socklen_t alen;
+ const char *atype, *opt, *val;
+ int flags;
+ void *typemap;
+ srs_req_register_t reg;
+
+ if (server == NULL)
+ server = DEFAULT_ADDRESS;
+
+ alen = mrp_transport_resolve(NULL, server, &addr, sizeof(addr), &atype);
+
+ if (alen <= 0)
+ return -1;
+
+ if ((typemap = register_message_types()) == NULL)
+ return -1;
+
+ flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_MODE_NATIVE;
+ srs->t = mrp_transport_create(srs->ml, atype, &evt, srs, flags);
+
+ if (srs->t == NULL)
+ return -1;
+
+ if (!mrp_transport_setopt(srs->t, "type-map", typemap))
+ return -1;
+
+ if (!mrp_transport_connect(srs->t, &addr, alen))
+ return -1;
+
+ reg.type = SRS_REQUEST_REGISTER;
+ reg.name = srs->name;
+ reg.appclass = srs->appclass;
+ reg.commands = srs->commands;
+ reg.ncommand = srs->ncommand;
+
+ return queue_request(srs, (srs_msg_t *)®, NULL);
+}
+
+
+void srs_disconnect(srs_t *srs)
+{
+ if (srs == NULL)
+ return;
+
+ mrp_transport_destroy(srs->t);
+ srs->t = NULL;
+ purge_reqq(srs);
+ srs->registered = 0;
+}
+
+
+static int check_connection(srs_t *srs)
+{
+ if (srs == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!srs->registered) {
+ errno = ENOTCONN;
+ return -1;
+ }
+}
+
+
+int srs_request_focus(srs_t *srs, srs_voice_focus_t focus)
+{
+ srs_req_focus_t req;
+
+ if (check_connection(srs) < 0)
+ return -1;
+
+ req.type = SRS_REQUEST_FOCUS;
+ req.focus = focus;
+
+ return queue_request(srs, (srs_msg_t *)&req, NULL);
+}
+
+
+uint32_t srs_render_voice(srs_t *srs, const char *msg, const char *voice,
+ int timeout, int events, srs_render_notify_t cb,
+ void *cb_data)
+{
+ srs_req_voice_t req;
+ request_data_t data;
+
+ if (check_connection(srs) < 0)
+ return -1;
+
+ req.type = SRS_REQUEST_RENDERVOICE;
+ req.msg = (char *)msg;
+ req.voice = (char *)voice;
+ req.timeout = timeout;
+ req.events = events;
+
+ data.voice_req.cvid = srs->cvid++;
+ data.voice_req.svid = SRS_VOICE_INVALID;
+ data.voice_req.cb = cb;
+ data.voice_req.cb_data = cb_data;
+
+ return queue_request(srs, (srs_msg_t *)&req, &data);
+}
+
+
+int srs_cancel_voice(srs_t *srs, uint32_t id)
+{
+ srs_ccl_voice_t req;
+ request_data_t data;
+ voice_req_t *vr;
+ request_t *vreq;
+
+ if (check_connection(srs) < 0)
+ return -1;
+
+ if ((vr = voice_req_for_cvid(srs, id)) != NULL)
+ id = vr->svid;
+ else {
+ if ((vreq = request_for_cvid(srs, id)) != NULL) {
+ vreq->data.voice_req.cancelled = TRUE;
+ return 0;
+ }
+ else {
+ errno = ENOENT;
+ return -1;
+ }
+ }
+
+ req.type = SRS_REQUEST_CANCELVOICE;
+ req.id = id;
+
+ data.voice_ccl.id = id;
+
+ return queue_request(srs, (srs_msg_t *)&req, &data);
+}
+
+
+int srs_query_voices(srs_t *srs, const char *language,
+ srs_voiceqry_notify_t cb, void *cb_data)
+{
+ srs_req_voiceqry_t req;
+ request_data_t data;
+
+ if (check_connection(srs) < 0)
+ return -1;
+
+ req.type = SRS_REQUEST_QUERYVOICES;
+ req.lang = (char *)(language ? language : "");
+
+ data.voice_qry.cb = cb;
+ data.voice_qry.cb_data = cb_data;
+
+ return queue_request(srs, (srs_msg_t *)&req, &data);
+}
+
+
+static void status_reply(srs_t *srs, srs_rpl_status_t *rpl)
+{
+ request_t *req = find_request(srs, rpl->reqno);
+ int status = rpl->status;
+
+ if (req == NULL) {
+ mrp_log_warning("Received reply for unkown request #%u.", rpl->reqno);
+ return;
+ }
+
+ switch (req->type) {
+ case SRS_REQUEST_REGISTER:
+ mrp_debug("Registration to server %s.",
+ status == 0 ? "successful" : "failed");
+
+ srs->registered = (status == 0);
+ srs->conn_notify(srs, status == 0, rpl->msg, srs->user_data);
+
+ if (!srs->registered) {
+ purge_reqq(srs);
+ purge_voiceq(srs);
+ }
+ break;
+
+ case SRS_REQUEST_UNREGISTER:
+ mrp_debug("Unregistering from server %s.",
+ rpl->status == SRS_STATUS_OK ? "successful" : "failed");
+
+ srs->registered = FALSE;
+ purge_reqq(srs);
+ purge_voiceq(srs);
+ break;
+
+ case SRS_REQUEST_FOCUS:
+ mrp_debug("Focus request %s on server.",
+ status == 0 ? "succeeded" : "failed");
+ break;
+
+ default:
+ mrp_log_warning("Dequeued request with invalid type 0x%x.", req->type);
+ }
+
+ purge_request(req);
+}
+
+
+static void rendervoice_reply(srs_t *srs, srs_rpl_voice_t *rpl)
+{
+ request_t *req = find_request(srs, rpl->reqno);
+ voice_req_t *vr;
+ srs_voice_event_t e;
+
+ if (req == NULL) {
+ mrp_log_warning("Got reply for unknown voice request #%u.", rpl->reqno);
+ return;
+ }
+
+ vr = &req->data.voice_req;
+
+ if (rpl->id == SRS_VOICE_INVALID) {
+ if (vr->cb != NULL) {
+ e.type = SRS_VOICE_EVENT_ABORTED;
+ e.id = vr->cvid;
+ e.data.progress.pcnt = 0;
+ e.data.progress.msec = 0;
+
+ vr->cb(srs, &e, srs->user_data, vr->cb_data);
+ }
+ }
+ else {
+ if (req->data.voice_req.cancelled)
+ srs_cancel_voice(srs, rpl->id);
+ else {
+ vr->svid = rpl->id;
+
+ if (add_voice_req(srs, vr) == NULL)
+ mrp_log_error("Failed to add active voice request #%u.",
+ vr->cvid);
+ }
+ }
+
+ purge_request(req);
+}
+
+
+static void voice_event(srs_t *srs, srs_evt_voice_t *evt)
+{
+ voice_req_t *vr = voice_req_for_svid(srs, evt->id);
+ srs_voice_event_t e;
+
+ mrp_debug("Got voice event 0x%x for #%u.", evt->event, evt->id);
+
+ if (vr == NULL) {
+ mrp_log_warning("Got event for unknown voice request #%u.", evt->id);
+ return;
+ }
+
+ e.type = evt->event;
+ e.id = vr->cvid;
+ e.data.progress.pcnt = evt->pcnt;
+ e.data.progress.msec = evt->msec;
+
+ vr->cb(srs, &e, srs->user_data, vr->cb_data);
+
+ if (evt->event == SRS_VOICE_EVENT_COMPLETED ||
+ evt->event == SRS_VOICE_EVENT_TIMEOUT ||
+ evt->event == SRS_VOICE_EVENT_ABORTED)
+ purge_voice_req(vr);
+}
+
+
+static void queryvoices_reply(srs_t *srs, srs_rpl_voiceqry_t *rpl)
+{
+ request_t *req = find_request(srs, rpl->reqno);
+ srs_voiceqry_notify_t cb;
+ void *cb_data;
+
+ mrp_debug("Got voice query response.");
+
+ if (req == NULL) {
+ mrp_log_warning("Received voice query response for unknown request.");
+ return;
+ }
+
+ cb = req->data.voice_qry.cb;
+ cb_data = req->data.voice_qry.cb_data;
+
+ cb(srs, rpl->actors, rpl->nactor, srs->user_data, cb_data);
+
+ purge_request(req);
+}
+
+
+static void focus_event(srs_t *srs, srs_evt_focus_t *evt)
+{
+ mrp_debug("Got focus 0x%x.", evt->focus);
+
+ if (srs->focus_notify != NULL)
+ srs->focus_notify(srs, evt->focus, srs->user_data);
+}
+
+
+static void command_event(srs_t *srs, srs_evt_command_t *evt)
+{
+ mrp_debug("Got command event #%u.", evt->idx);
+
+ if (srs->cmd_notify != NULL)
+ srs->cmd_notify(srs, evt->idx, evt->tokens, evt->ntoken,
+ srs->user_data);
+}
--- /dev/null
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SRS_NATIVE_CLIENT_H__
+#define __SRS_NATIVE_CLIENT_H__
+
+#include <murphy/common/macros.h>
+#include <glib.h>
+
+#include "srs/daemon/client-api-types.h"
+#include "srs/daemon/voice-api-types.h"
+
+MRP_CDECL_BEGIN
+
+/** Opaque SRS client context type. */
+typedef struct srs_s srs_t;
+
+/** Connection status notification callback type. */
+typedef void (*srs_connect_notify_t)(srs_t *srs, int status, const char *msg,
+ void *user_data);
+
+/** Command notification callback type. */
+typedef void (*srs_command_notify_t)(srs_t *srs, int idx, char **tokens,
+ int ntoken, void *user_data);
+
+/** Focus notification callback type. */
+typedef void (*srs_focus_notify_t)(srs_t *srs, srs_voice_focus_t granted,
+ void *user_data);
+
+/** Voice rendering notification callback type. */
+typedef void (*srs_render_notify_t)(srs_t *srs, srs_voice_event_t *event,
+ void *user_data, void *notify_data);
+
+/** Voice query notification callback type. */
+typedef void (*srs_voiceqry_notify_t)(srs_t *srs, srs_voice_actor_t *actors,
+ int nactor, void *user_data,
+ void *notify_data);
+
+/** Use the given Murphy mainloop as the underlying mainloop. */
+void srs_set_mainloop(mrp_mainloop_t *ml);
+
+/** Use the given GMainLoop as the underlying mainloop. */
+void srs_set_gmainloop(GMainLoop *gml);
+
+/** Create an SRS client context with the given class and commands. */
+srs_t *srs_create(const char *name, const char *appclass, char **commands,
+ size_t ncommand, srs_connect_notify_t connect_notify,
+ srs_focus_notify_t focus_notify,
+ srs_command_notify_t command_notify, void *user_data);
+
+/** Destroy the given SRS client context. */
+void srs_destroy(srs_t *srs);
+
+/** Try to establish a connection to the server at the given address. */
+int srs_connect(srs_t *srs, const char *server, int reconnect);
+
+/** Close connection to the server if there is one. */
+void srs_disconnect(srs_t *srs);
+
+/** Request the given type of focus. */
+int srs_request_focus(srs_t *srs, srs_voice_focus_t focus);
+
+/** Request rendering the given message, subscribing for the given events. */
+uint32_t srs_render_voice(srs_t *srs, const char *msg, const char *voice,
+ int timeout, int events, srs_render_notify_t cb,
+ void *cb_data);
+
+/** Cancel an ongoing voice render request. */
+int srs_cancel_voice(srs_t *srs, uint32_t id);
+
+/** Query the available voices for the given language (or all if omitted). */
+int srs_query_voices(srs_t *srs, const char *language,
+ srs_voiceqry_notify_t cb, void *cb_data);
+
+MRP_CDECL_END
+
+#endif /* __SRS_NATIVE_CLIENT_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SRS_NATIVE_CLIENT_CONFIG_H__
+#define __SRS_NATIVE_CLIENT_CONFIG_H__
+
+/** Configuration key for specifying the transport address. */
+#define CONFIG_ADDRESS "native.address"
+
+/** Default transport address. */
+/*#define DEFAULT_ADDRESS "unxs:@/srs/native-client"*/
+#define DEFAULT_ADDRESS "tcp4:127.0.0.1:4100"
+
+
+
+#endif /* __SRS_NATIVE_CLIENT_CONFIG_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <errno.h>
+
+#include <murphy/common/native-types.h>
+
+#include "native-messages.h"
+
+static mrp_typemap_t *map = NULL;
+
+mrp_typemap_t *register_message_types(void)
+{
+ static bool done = false;
+ static mrp_typemap_t type_map[] = {
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_REGISTER },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_UNREGISTER },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_FOCUS },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_RENDERVOICE },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_CANCELVOICE },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_QUERYVOICES },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REPLY_STATUS },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REPLY_RENDERVOICE },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_VOICE_ACTOR },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REPLY_QUERYVOICES },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_EVENT_FOCUS },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_EVENT_COMMAND },
+ { .typeid = MRP_INVALID_TYPE, .mapped = SRS_EVENT_VOICE },
+ { MRP_INVALID_TYPE, MRP_INVALID_TYPE },
+ };
+
+ MRP_NATIVE_TYPE(reg_req, srs_req_register_t,
+ MRP_UINT32(srs_req_register_t, type , DEFAULT),
+ MRP_UINT32(srs_req_register_t, reqno , DEFAULT),
+ MRP_STRING(srs_req_register_t, name , DEFAULT),
+ MRP_STRING(srs_req_register_t, appclass, DEFAULT),
+ MRP_ARRAY (srs_req_register_t, commands, DEFAULT, SIZED,
+ char *, ncommand),
+ MRP_UINT32(srs_req_register_t, ncommand, DEFAULT));
+
+ MRP_NATIVE_TYPE(bye_req, srs_req_unregister_t,
+ MRP_UINT32(srs_req_unregister_t, type , DEFAULT),
+ MRP_UINT32(srs_req_unregister_t, reqno , DEFAULT));
+
+ MRP_NATIVE_TYPE(status_rpl, srs_rpl_status_t,
+ MRP_UINT32(srs_rpl_status_t, type , DEFAULT),
+ MRP_UINT32(srs_rpl_status_t, reqno , DEFAULT),
+ MRP_UINT32(srs_rpl_status_t, status, DEFAULT),
+ MRP_STRING(srs_rpl_status_t, msg , DEFAULT));
+
+ MRP_NATIVE_TYPE(focus_req, srs_req_focus_t,
+ MRP_UINT32(srs_req_focus_t, type , DEFAULT),
+ MRP_UINT32(srs_req_focus_t, reqno , DEFAULT),
+ MRP_UINT32(srs_req_focus_t, focus, DEFAULT));
+
+ MRP_NATIVE_TYPE(focus_evt, srs_evt_focus_t,
+ MRP_UINT32(srs_evt_focus_t, type , DEFAULT),
+ MRP_UINT32(srs_evt_focus_t, focus, DEFAULT));
+
+ MRP_NATIVE_TYPE(voice_req, srs_req_voice_t,
+ MRP_UINT32(srs_req_voice_t, type , DEFAULT),
+ MRP_UINT32(srs_req_voice_t, reqno , DEFAULT),
+ MRP_STRING(srs_req_voice_t, msg , DEFAULT),
+ MRP_STRING(srs_req_voice_t, voice , DEFAULT),
+ MRP_UINT32(srs_req_voice_t, timeout , DEFAULT),
+ MRP_UINT32(srs_req_voice_t, events , DEFAULT));
+
+ MRP_NATIVE_TYPE(voice_rpl, srs_rpl_voice_t,
+ MRP_UINT32(srs_rpl_voice_t, type , DEFAULT),
+ MRP_UINT32(srs_rpl_voice_t, reqno, DEFAULT),
+ MRP_UINT32(srs_rpl_voice_t, id , DEFAULT));
+
+ MRP_NATIVE_TYPE(voice_ccl, srs_ccl_voice_t,
+ MRP_UINT32(srs_ccl_voice_t, type , DEFAULT),
+ MRP_UINT32(srs_ccl_voice_t, reqno, DEFAULT),
+ MRP_UINT32(srs_ccl_voice_t, id , DEFAULT));
+
+ MRP_NATIVE_TYPE(voice_evt, srs_evt_voice_t,
+ MRP_UINT32(srs_evt_voice_t, type , DEFAULT),
+ MRP_UINT32(srs_evt_voice_t, event, DEFAULT),
+ MRP_UINT32(srs_evt_voice_t, id , DEFAULT),
+ MRP_DOUBLE(srs_evt_voice_t, pcnt , DEFAULT),
+ MRP_UINT32(srs_evt_voice_t, msec , DEFAULT));
+
+ MRP_NATIVE_TYPE(voice_qry, srs_req_voiceqry_t,
+ MRP_UINT32(srs_req_voiceqry_t, type , DEFAULT),
+ MRP_UINT32(srs_req_voiceqry_t, reqno, DEFAULT),
+ MRP_STRING(srs_req_voiceqry_t, lang , DEFAULT));
+
+ MRP_NATIVE_TYPE(voice_act, srs_voice_actor_t,
+ MRP_UINT32(srs_voice_actor_t, id , DEFAULT),
+ MRP_STRING(srs_voice_actor_t, lang , DEFAULT),
+ MRP_STRING(srs_voice_actor_t, dialect , DEFAULT),
+ MRP_UINT16(srs_voice_actor_t, gender , DEFAULT),
+ MRP_UINT16(srs_voice_actor_t, age , DEFAULT),
+ MRP_STRING(srs_voice_actor_t, name , DEFAULT),
+ MRP_STRING(srs_voice_actor_t, description, DEFAULT));
+
+ MRP_NATIVE_TYPE(voiceqry_rpl, srs_rpl_voiceqry_t,
+ MRP_UINT32(srs_rpl_voiceqry_t, type , DEFAULT),
+ MRP_UINT32(srs_rpl_voiceqry_t, reqno , DEFAULT),
+ MRP_ARRAY (srs_rpl_voiceqry_t, actors, DEFAULT, SIZED,
+ srs_voice_actor_t, nactor),
+ MRP_UINT32(srs_rpl_voiceqry_t, nactor, DEFAULT));
+
+ MRP_NATIVE_TYPE(command_evt, srs_evt_command_t,
+ MRP_UINT32(srs_evt_command_t, type, DEFAULT),
+ MRP_UINT32(srs_evt_command_t, idx , DEFAULT),
+ MRP_ARRAY (srs_evt_command_t, tokens, DEFAULT, SIZED,
+ char *, ntoken),
+ MRP_UINT32(srs_evt_command_t, ntoken, DEFAULT));
+
+ struct {
+ uint32_t id;
+ mrp_native_type_t *type;
+ } types[SRS_MSG_MAX] = {
+ { SRS_REQUEST_REGISTER , ®_req },
+ { SRS_REQUEST_UNREGISTER , &bye_req },
+ { SRS_REQUEST_FOCUS , &focus_req },
+ { SRS_REQUEST_RENDERVOICE, &voice_req },
+ { SRS_REQUEST_CANCELVOICE, &voice_ccl },
+ { SRS_REQUEST_QUERYVOICES, &voice_qry },
+ { SRS_REPLY_STATUS , &status_rpl },
+ { SRS_REPLY_RENDERVOICE , &voice_rpl },
+ { SRS_VOICE_ACTOR , &voice_act },
+ { SRS_REPLY_QUERYVOICES , &voiceqry_rpl },
+ { SRS_EVENT_FOCUS , &focus_evt },
+ { SRS_EVENT_COMMAND , &command_evt },
+ { SRS_EVENT_VOICE , &voice_evt },
+ { MRP_INVALID_TYPE , NULL },
+ }, *t;
+ mrp_typemap_t *m;
+
+ if (done)
+ return map;
+
+ for (t = types, m = type_map; t->type != NULL; t++, m++) {
+ if ((m->typeid = mrp_register_native(t->type)) == MRP_INVALID_TYPE)
+ return NULL;
+ }
+
+ done = true;
+ return (map = &type_map[0]);
+}
+
+
+uint32_t message_typeid(uint32_t type)
+{
+ if (map != NULL && 0 < type && type < SRS_MSG_MAX)
+ return map[type - 1].typeid;
+ else
+ return MRP_INVALID_TYPE;
+}
+
+
+uint32_t message_type(uint32_t typeid)
+{
+ mrp_typemap_t *m;
+
+ if (map != NULL) {
+ for (m = map; m->typeid != MRP_INVALID_TYPE; m++)
+ if (m->typeid == typeid)
+ return m->mapped;
+ }
+
+ return MRP_INVALID_TYPE;
+}
+
+
+int send_message(mrp_transport_t *t, srs_msg_t *msg)
+{
+ uint32_t typeid = message_typeid(msg->type);
+
+ if (typeid != MRP_INVALID_TYPE) {
+ if (mrp_transport_sendnative(t, msg, typeid))
+ return 0;
+ }
+ else
+ errno = EINVAL;
+
+ return -1;
+}
--- /dev/null
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SRS_NATIVE_MESSAGES_H__
+#define __SRS_NATIVE_MESSAGES_H__
+
+#include <stdint.h>
+#include <murphy/common/native-types.h>
+#include <murphy/common/transport.h>
+
+#include "srs/daemon/voice-api-types.h"
+
+
+/*
+ * message types
+ */
+
+typedef enum {
+ SRS_MSG_UNKNOWN = 0,
+
+ SRS_REQUEST_REGISTER,
+ SRS_REQUEST_UNREGISTER,
+ SRS_REQUEST_FOCUS,
+ SRS_REQUEST_RENDERVOICE,
+ SRS_REQUEST_CANCELVOICE,
+ SRS_REQUEST_QUERYVOICES,
+
+ SRS_REPLY_STATUS,
+ SRS_REPLY_RENDERVOICE,
+ SRS_VOICE_ACTOR,
+ SRS_REPLY_QUERYVOICES,
+
+ SRS_EVENT_FOCUS,
+ SRS_EVENT_COMMAND,
+ SRS_EVENT_VOICE,
+
+ SRS_MSG_MAX
+} srs_msg_type_t;
+
+
+/*
+ * registration request
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_REQUEST_REGISTER */
+ uint32_t reqno; /* request number */
+ char *name; /* application name */
+ char *appclass; /* application class */
+ char **commands; /* speech commands */
+ uint32_t ncommand; /* number of speech commands */
+} srs_req_register_t;
+
+
+/*
+ * unregistration request
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_REQUEST_UNREGISTER */
+ uint32_t reqno; /* request number */
+} srs_req_unregister_t;
+
+
+/*
+ * error codes
+ */
+
+enum {
+ SRS_STATUS_OK = 0, /* success */
+ SRS_STATUS_FAILED, /* request failed */
+};
+
+
+/*
+ * a status reply
+ */
+
+typedef struct {
+ uint32_t type; /* message type */
+ uint32_t reqno; /* request number */
+ uint32_t status; /* an error code, or 0 */
+ char *msg; /* error message, if any */
+} srs_rpl_status_t;
+
+
+/*
+ * voice focus request
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_REQUEST_FOCUS */
+ uint32_t reqno; /* request number */
+ uint32_t focus; /* current voice focus */
+} srs_req_focus_t;
+
+
+/*
+ * voice focus notification
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_EVENT_FOCUS */
+ uint32_t focus; /* granted focus */
+} srs_evt_focus_t;
+
+
+/*
+ * voice render request
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_REQUEST_VOICERENDER */
+ uint32_t reqno; /* request number */
+ char *msg; /* message to render */
+ char *voice; /* voice to use */
+ uint32_t timeout; /* message timeout */
+ uint32_t events; /* mask of events to notify about */
+} srs_req_voice_t;
+
+
+/*
+ * reply to voice render request
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_REPLY_RENDERVOICE */
+ uint32_t reqno; /* request number */
+ uint32_t id; /* client-side id */
+} srs_rpl_voice_t;
+
+
+/*
+ * voice cancel request
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_REQUEST_VOICECANCEL */
+ uint32_t reqno; /* request number */
+ uint32_t id; /* voice render id */
+} srs_ccl_voice_t;
+
+
+/*
+ * voice progress notification event
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_EVENT_VOICE */
+ uint32_t event; /* SRS_VOICE_EVENT_* */
+ uint32_t id; /* voice render id */
+ double pcnt; /* progress in percentages */
+ uint32_t msec; /* progress in milliseconds */
+} srs_evt_voice_t;
+
+
+/*
+ * voice query request
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_REQUEST_QUERYVOICES */
+ uint32_t reqno; /* request number */
+ char *lang; /* language to request voices for */
+} srs_req_voiceqry_t;
+
+
+/*
+ * voice query reply
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_REPLY_QUERYVOICES */
+ uint32_t reqno; /* request number */
+ srs_voice_actor_t *actors; /* queried actors */
+ uint32_t nactor; /* number of actors */
+} srs_rpl_voiceqry_t;
+
+
+/*
+ * command notification event
+ */
+
+typedef struct {
+ uint32_t type; /* SRS_EVENT_COMMAND */
+ uint32_t idx; /* client command index */
+ char **tokens; /* command tokens */
+ uint32_t ntoken; /* number of tokens */
+} srs_evt_command_t;
+
+
+/*
+ * a generic request or reply
+ */
+
+typedef struct {
+ uint32_t type; /* message type */
+ uint32_t reqno; /* request number */
+} srs_req_any_t;
+
+typedef srs_req_any_t srs_rpl_any_t;
+
+/*
+ * message
+ */
+
+typedef union {
+ uint32_t type;
+ srs_req_any_t any_req;
+ srs_rpl_any_t any_rpl;
+ srs_req_register_t reg_req;
+ srs_req_unregister_t bye_req;
+ srs_rpl_status_t status_rpl;
+ srs_req_focus_t focus_req;
+ srs_evt_focus_t focus_evt;
+ srs_req_voice_t voice_req;
+ srs_rpl_voice_t voice_rpl;
+ srs_ccl_voice_t voice_ccl;
+ srs_evt_voice_t voice_evt;
+ srs_req_voiceqry_t voice_qry;
+ srs_rpl_voiceqry_t voice_lst;
+ srs_evt_command_t command_evt;
+} srs_msg_t;
+
+
+mrp_typemap_t *register_message_types(void);
+int send_message(mrp_transport_t *t, srs_msg_t *msg);
+uint32_t message_typeid(uint32_t type);
+uint32_t message_type(uint32_t typeid);
+
+#endif /* __SRS_NATIVE_MESSAGES_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/native-types.h>
+
+#include "src/daemon/plugin.h"
+#include "src/daemon/client.h"
+#include "native-messages.h"
+#include "native-config.h"
+
+#define PLUGIN_NAME "native-client"
+#define PLUGIN_DESCR "Native client plugin for SRS."
+#define PLUGIN_AUTHORS "Krisztian Litkey <kli@iki.fi>"
+#define PLUGIN_VERSION "0.0.1"
+
+
+/*
+ * server runtime context
+ */
+
+typedef struct {
+ srs_plugin_t *self; /* our plugin instance */
+ const char *address; /* our transport address */
+ mrp_transport_t *lt; /* transport we listen on */
+ mrp_list_hook_t clients; /* connected clients */
+ int next_id; /* next client id */
+} server_t;
+
+
+/*
+ * a connected client
+ */
+
+typedef struct {
+ srs_client_t *c; /* associated SRS client */
+ server_t *s; /* server runtime context */
+ mrp_transport_t *t; /* transport towards this client */
+ mrp_list_hook_t hook; /* to list of native clients */
+ int id; /* client id */
+} client_t;
+
+
+static int focus_notify(srs_client_t *c, srs_voice_focus_t focus);
+static int command_notify(srs_client_t *c, int idx, int ntoken, char **tokens,
+ uint32_t *start, uint32_t *end,
+ srs_audiobuf_t *audio);
+static int voice_notify(srs_client_t *c, srs_voice_event_t *event);
+
+static int reply_status(client_t *c, uint32_t reqno, int status,
+ const char *msg);
+#define reply_register reply_status
+#define reply_unregister reply_status
+#define reply_focus reply_status
+static int reply_render(client_t *c, uint32_t reqno, uint32_t id);
+static int reply_voiceqry(client_t *c, uint32_t reqno,
+ srs_voice_actor_t *actors, int nactor);
+
+static client_t *create_client(server_t *s, mrp_transport_t *lt)
+{
+ mrp_transport_t *t;
+ client_t *c;
+
+ c = mrp_allocz(sizeof(*c));
+
+ if (c != NULL) {
+ mrp_list_init(&c->hook);
+
+ c->s = s;
+ c->id = s->next_id++;
+ c->t = mrp_transport_accept(lt, c, MRP_TRANSPORT_REUSEADDR);
+
+ if (c->t != NULL) {
+ mrp_list_append(&s->clients, &c->hook);
+
+ return c;
+ }
+
+ mrp_free(c);
+ }
+ else {
+ t = mrp_transport_accept(lt, NULL, MRP_TRANSPORT_REUSEADDR);
+ mrp_transport_destroy(t);
+ }
+
+ return NULL;
+}
+
+
+static void destroy_client(client_t *c)
+{
+ mrp_list_delete(&c->hook);
+
+ mrp_transport_destroy(c->t);
+ client_destroy(c->c);
+
+ mrp_free(c);
+}
+
+
+static void register_client(client_t *c, srs_req_register_t *req)
+{
+ static srs_client_ops_t ops = {
+ .notify_focus = focus_notify,
+ .notify_command = command_notify,
+ .notify_render = voice_notify,
+ };
+
+ srs_context_t *srs = c->s->self->srs;
+ char *name = req->name;
+ char *appcls = req->appclass;
+ char **cmds = req->commands;
+ int ncmd = req->ncommand;
+ char id[64];
+
+ snprintf(id, sizeof(id), "native-client-%d", c->id);
+
+ mrp_debug("received register request from native client #%d", c->id);
+
+ c->c = client_create(srs, SRS_CLIENT_TYPE_EXTERNAL, name, appcls,
+ cmds, ncmd, id, &ops, c);
+
+ if (c->c != NULL)
+ reply_register(c, req->reqno, SRS_STATUS_OK, "OK");
+ else {
+ reply_register(c, req->reqno, SRS_STATUS_FAILED, "failed");
+ destroy_client(c);
+ }
+}
+
+
+static void unregister_client(client_t *c, srs_req_unregister_t *req)
+{
+ mrp_debug("received unregister request from native client #%d", c->id);
+
+ reply_unregister(c, req->reqno, SRS_STATUS_OK, "OK");
+ destroy_client(c);
+}
+
+
+static void request_focus(client_t *c, srs_req_focus_t *req)
+{
+ mrp_debug("received focus request from native client #%d", c->id);
+
+ if (client_request_focus(c->c, req->focus))
+ reply_focus(c, req->reqno, SRS_STATUS_OK, "OK");
+ else
+ reply_focus(c, req->reqno, SRS_STATUS_FAILED, "failed");
+}
+
+
+static void request_voice(client_t *c, srs_req_voice_t *req)
+{
+ const char *msg = req->msg;
+ const char *voice = req->voice;
+ int timeout = req->timeout;
+ int events = req->events;
+ uint32_t reqid;
+
+ mrp_debug("received voice render request from native client #%d", c->id);
+
+ reqid = client_render_voice(c->c, msg, voice, timeout, events);
+
+ if (reqid != SRS_VOICE_INVALID)
+ reply_render(c, req->reqno, reqid);
+ else
+ reply_status(c, req->reqno, SRS_STATUS_FAILED, "failed");
+}
+
+
+static void cancel_voice(client_t *c, srs_ccl_voice_t *req)
+{
+ mrp_debug("received voice cancel request from native client #%d", c->id);
+
+ client_cancel_voice(c->c, req->id);
+ reply_status(c, req->reqno, SRS_STATUS_OK, "OK");
+}
+
+
+static void query_voices(client_t *c, srs_req_voiceqry_t *req)
+{
+ srs_voice_actor_t *actors;
+ int nactor;
+
+ mrp_debug("received voice query request from native client #%d", c->id);
+
+ nactor = client_query_voices(c->c, req->lang, &actors);
+ reply_voiceqry(c, req->reqno, actors, nactor);
+ client_free_queried_voices(actors);
+}
+
+
+static int reply_status(client_t *c, uint32_t reqno, int status,
+ const char *msg)
+{
+ srs_rpl_status_t rpl;
+
+ mrp_debug("replying <%d, %s> to request #%d from native client #%d",
+ status, msg, reqno, c->id);
+
+ rpl.type = SRS_REPLY_STATUS;
+ rpl.reqno = reqno;
+ rpl.status = status;
+ rpl.msg = (char *)msg;
+
+ return send_message(c->t, (srs_msg_t *)&rpl);
+}
+
+
+static int reply_render(client_t *c, uint32_t reqno, uint32_t id)
+{
+ srs_rpl_voice_t rpl;
+
+ mrp_debug("replying <#%u> to request #%d from native client #%d", id,
+ reqno, c->id);
+
+ rpl.type = SRS_REPLY_RENDERVOICE;
+ rpl.reqno = reqno;
+ rpl.id = id;
+
+ return send_message(c->t, (srs_msg_t *)&rpl);
+}
+
+
+static int focus_notify(srs_client_t *client, srs_voice_focus_t focus)
+{
+ client_t *c = (client_t *)client->user_data;
+ srs_evt_focus_t evt;
+
+ mrp_debug("relaying focus event to native client #%d", c->id);
+
+ evt.type = SRS_EVENT_FOCUS;
+ evt.focus = focus;
+
+ return send_message(c->t, (srs_msg_t *)&evt);
+}
+
+
+static int command_notify(srs_client_t *client, int idx,
+ int ntoken, char **tokens, uint32_t *start,
+ uint32_t *end, srs_audiobuf_t *audio)
+{
+ client_t *c = (client_t *)client->user_data;
+ srs_evt_command_t evt;
+
+ MRP_UNUSED(start);
+ MRP_UNUSED(end);
+ MRP_UNUSED(audio);
+
+ mrp_debug("relaying command event to native client #%d", c->id);
+
+ evt.type = SRS_EVENT_COMMAND;
+ evt.idx = idx;
+ evt.tokens = tokens;
+ evt.ntoken = ntoken;
+
+ return send_message(c->t, (srs_msg_t *)&evt);
+}
+
+
+static int voice_notify(srs_client_t *client, srs_voice_event_t *event)
+{
+ client_t *c = (client_t *)client->user_data;
+ srs_evt_voice_t evt;
+
+ mrp_debug("relaying voice event to native client #%d", c->id);
+
+ evt.type = SRS_EVENT_VOICE;
+ evt.event = event->type;
+ evt.id = event->id;
+
+ if (event->type == SRS_VOICE_EVENT_PROGRESS) {
+ evt.pcnt = event->data.progress.pcnt;
+ evt.msec = event->data.progress.msec;
+ }
+ else {
+ evt.pcnt = 0;
+ evt.msec = 0;
+ }
+
+ return send_message(c->t, (srs_msg_t *)&evt);
+}
+
+
+static int reply_voiceqry(client_t *c, uint32_t reqno,
+ srs_voice_actor_t *actors, int nactor)
+{
+ srs_rpl_voiceqry_t rpl;
+
+ mrp_debug("replying to request #%u from native client #%d", reqno, c->id);
+
+ if (actors < 0)
+ actors = 0;
+
+ rpl.type = SRS_REPLY_QUERYVOICES;
+ rpl.reqno = reqno;
+ rpl.actors = actors;
+ rpl.nactor = nactor;
+
+ return send_message(c->t, (srs_msg_t *)&rpl);
+}
+
+
+static inline void dump_message(void *data, uint32_t type_id)
+{
+ char buf[1024];
+
+ if (mrp_print_native(buf, sizeof(buf), data, type_id) > 0)
+ mrp_debug("got message of type 0x%x: %s", type_id, buf);
+}
+
+
+static void connection_evt(mrp_transport_t *lt, void *user_data)
+{
+ server_t *s = (server_t *)user_data;
+ client_t *c;
+
+ c = create_client(s, lt);
+
+ if (c != NULL)
+ mrp_log_info("Accepted new native client connection.");
+ else
+ mrp_log_error("Failed to accept new native client connection.");
+}
+
+
+static void closed_evt(mrp_transport_t *t, int error, void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+
+ MRP_UNUSED(t);
+
+ if (error != 0)
+ mrp_log_error("Native client connection closed with error %d (%s).",
+ error, strerror(error));
+ else
+ mrp_log_info("Native client connection closed.");
+
+ destroy_client(c);
+}
+
+
+static void recv_evt(mrp_transport_t *t, void *data, uint32_t type_id,
+ void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+ srs_msg_t *req = (srs_msg_t *)data;
+
+ MRP_UNUSED(t);
+
+ dump_message(data, type_id);
+
+ switch (req->type) {
+ case SRS_REQUEST_REGISTER:
+ register_client(c, &req->reg_req);
+ break;
+
+ case SRS_REQUEST_UNREGISTER:
+ unregister_client(c, &req->bye_req);
+ break;
+
+ case SRS_REQUEST_FOCUS:
+ request_focus(c, &req->focus_req);
+ break;
+
+ case SRS_REQUEST_RENDERVOICE:
+ request_voice(c, &req->voice_req);
+ break;
+
+ case SRS_REQUEST_CANCELVOICE:
+ cancel_voice(c, &req->voice_ccl);
+ break;
+
+ case SRS_REQUEST_QUERYVOICES:
+ query_voices(c, &req->voice_qry);
+ break;
+
+ default:
+ break;
+ }
+
+}
+
+
+static int transport_setup(server_t *s)
+{
+ static mrp_transport_evt_t evt = {
+ { .recvnative = recv_evt },
+ { .recvnativefrom = NULL },
+ .connection = connection_evt,
+ .closed = closed_evt,
+ };
+
+ srs_context_t *srs = s->self->srs;
+ mrp_sockaddr_t addr;
+ socklen_t alen;
+ const char *type, *opt, *val;
+ int flags;
+ void *typemap;
+
+ alen = mrp_transport_resolve(NULL, s->address, &addr, sizeof(addr), &type);
+
+ if (alen < 0) {
+ mrp_log_error("Failed to resolve transport address '%s'.", s->address);
+ goto fail;
+ }
+
+ flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_MODE_NATIVE;
+ s->lt = mrp_transport_create(srs->ml, type, &evt, s, flags);
+
+ if (s->lt == NULL) {
+ mrp_log_error("Failed to create transport for native clients.");
+ return FALSE;
+ }
+
+ if ((typemap = register_message_types()) == NULL) {
+ mrp_log_error("Failed to register native messages.");
+ goto fail;
+ }
+
+ if (!mrp_transport_setopt(s->lt, "type-map", typemap)) {
+ mrp_log_error("Failed to set transport type map.");
+ goto fail;
+ }
+
+ if (mrp_transport_bind(s->lt, &addr, alen) &&
+ mrp_transport_listen(s->lt, 0)) {
+ mrp_log_info("Listening on transport '%s'...", s->address);
+
+ return TRUE;
+ }
+ else
+ mrp_log_error("Failed to bind/listen transport.");
+
+ fail:
+ if (s->lt) {
+ mrp_transport_destroy(s->lt);
+ s->lt = NULL;
+ }
+
+ return FALSE;
+}
+
+
+static void transport_cleanup(server_t *s)
+{
+ mrp_transport_destroy(s->lt);
+ s->lt = NULL;
+}
+
+
+static int create_native(srs_plugin_t *plugin)
+{
+ server_t *s;
+
+ mrp_debug("creating native client interface plugin");
+
+ if ((s = mrp_allocz(sizeof(*s))) == NULL)
+ return FALSE;
+
+ mrp_list_init(&s->clients);
+ s->self = plugin;
+
+ plugin->plugin_data = s;
+
+ return TRUE;
+
+ fail:
+ mrp_free(s);
+
+ return FALSE;
+}
+
+
+static int config_native(srs_plugin_t *plugin, srs_cfg_t *cfg)
+{
+ server_t *s = (server_t *)plugin->plugin_data;
+
+ mrp_debug("configure native client interface plugin");
+
+ s->address = srs_get_string_config(cfg, CONFIG_ADDRESS, DEFAULT_ADDRESS);
+ mrp_log_info("Using native client transport address: '%s'.", s->address);
+
+ return TRUE;
+}
+
+
+static int start_native(srs_plugin_t *plugin)
+{
+ server_t *s = (server_t *)plugin->plugin_data;
+
+ MRP_UNUSED(plugin);
+
+ return transport_setup(s);
+}
+
+
+static void stop_native(srs_plugin_t *plugin)
+{
+ MRP_UNUSED(plugin);
+}
+
+
+static void destroy_native(srs_plugin_t *plugin)
+{
+ server_t *s = (server_t *)plugin->plugin_data;
+
+ transport_cleanup(s);
+ mrp_free(s);
+}
+
+
+SRS_DECLARE_PLUGIN(PLUGIN_NAME, PLUGIN_DESCR, PLUGIN_AUTHORS, PLUGIN_VERSION,
+ create_native, config_native, start_native, stop_native,
+ destroy_native)
--- /dev/null
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdarg.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/pulse-glue.h>
+#include <murphy/common/glib-glue.h>
+
+#include <breedline/breedline-murphy.h>
+
+#include "native-client.h"
+
+static const char *default_commands[] = {
+ "hal open the pod bay doors",
+ "hal play music",
+ "hal stop music",
+ "hal exit",
+};
+
+typedef struct {
+ GMainLoop *gml;
+ pa_mainloop *pa;
+ mrp_mainloop_t *ml;
+ brl_t *brl;
+ srs_t *srs;
+ const char *app_class;
+ const char *app_name;
+ int exit_status;
+ int server_up : 1;
+ int registered : 1;
+ const char *focus;
+ char **commands;
+ int ncommand;
+ int autoregister : 1;
+ const char *autofocus;
+ uint32_t vreq;
+ int glib : 1;
+} client_t;
+
+
+static void connect_notify(srs_t *srs, int status, const char *msg,
+ void *user_data);
+
+static void command_notify(srs_t *srs, int idx, char **tokens, int ntoken,
+ void *user_data);
+
+static void focus_notify(srs_t *srs, srs_voice_focus_t focus, void *user_data);
+
+static void render_notify(srs_t *srs, srs_voice_event_t *e, void *user_data,
+ void *notify_data);
+
+
+static void execute_user_command(client_t *c, int narg, char **args);
+
+static void set_prompt(client_t *c, const char *prompt)
+{
+ brl_set_prompt(c->brl, prompt);
+}
+
+
+static void show_prompt(client_t *c)
+{
+ if (c->brl != NULL)
+ brl_show_prompt(c->brl);
+}
+
+
+static void hide_prompt(client_t *c)
+{
+ if (c->brl != NULL)
+ brl_hide_prompt(c->brl);
+}
+
+
+static void print(client_t *c, const char *format, ...)
+{
+ va_list ap;
+
+ hide_prompt(c);
+
+ va_start(ap, format);
+ vfprintf(stdout, format, ap);
+ fputc('\n', stdout);
+ fflush(stdout);
+ va_end(ap);
+
+ show_prompt(c);
+}
+
+
+static char *concat_tokens(char *buf, int size, int ntoken, char **tokens)
+{
+ char *p, *t;
+ int l, n;
+
+ p = buf;
+ t = "";
+ l = size - 1;
+
+ while (ntoken > 0) {
+ n = snprintf(p, l, "%s%s", t, tokens[0]);
+
+ if (n >= l)
+ return NULL;
+
+ p += n;
+ l -= n;
+ t = " ";
+ ntoken--;
+ tokens++;
+ }
+
+ buf[size - 1] = '\0';
+ return buf;
+}
+
+
+static void add_command(client_t *c, int ntoken, char **tokens)
+{
+ char command[1024];
+ size_t osize, nsize;
+
+ if (c->registered) {
+ print(c, "You need to unregister first to modify commands.");
+ return;
+ }
+
+ if (concat_tokens(command, sizeof(command), ntoken, tokens) == NULL) {
+ print(c, "Command too long.");
+ return;
+ }
+
+ osize = sizeof(*c->commands) * c->ncommand;
+ nsize = sizeof(*c->commands) * (c->ncommand + 1);
+
+ if (!mrp_reallocz(c->commands, osize, nsize)) {
+ print(c, "Failed to add new command.");
+ return;
+ }
+
+ c->commands[c->ncommand] = mrp_strdup(command);
+
+ if (c->commands[c->ncommand] != NULL) {
+ c->ncommand++;
+ print(c, "Command '%s' added to command set.", command);
+ }
+ else
+ print(c, "Failed to register new command.");
+}
+
+
+static void del_command(client_t *c, int ntoken, char **tokens)
+{
+ char command[1024];
+ int i;
+
+ if (c->registered) {
+ print(c, "You need to unregister first to modify commands.");
+ return;
+ }
+
+ if (concat_tokens(command, sizeof(command), ntoken, tokens) == NULL) {
+ print(c, "Command too long.");
+ return;
+ }
+
+ for (i = 0; i < c->ncommand; i++) {
+ if (!strcmp(c->commands[i], command)) {
+ if (i < c->ncommand - 1)
+ memmove(c->commands + i + 1, c->commands + i,
+ (c->ncommand - 1 - i) * sizeof(*c->commands));
+
+ c->ncommand--;
+ mrp_realloc(c->commands, sizeof(*c->commands) * c->ncommand);
+
+ print(c, "Command '%s' deleted.", command);
+ }
+ }
+}
+
+
+static void reset_commands(client_t *c)
+{
+ int i;
+
+ if (c->registered){
+ print(c, "You need to unregister first to modify commands.");
+ return;
+ }
+
+ for (i = 0; i < c->ncommand; i++)
+ mrp_free(c->commands[i]);
+ mrp_free(c->commands);
+
+ c->commands = NULL;
+ c->ncommand = 0;
+
+ print(c, "Commands resetted, no current commands.");
+}
+
+
+static void list_commands(client_t *c)
+{
+ int i;
+
+ if (c->ncommand > 0) {
+ print(c, "Command set:");
+ for (i = 0; i < c->ncommand; i++)
+ print(c, " %s", c->commands[i]);
+ }
+ else
+ print(c, "No commands defined.");
+}
+
+
+static void set_client_defaults(client_t *c, const char *argv0)
+{
+ int i;
+
+ c->app_class = "player";
+ c->app_name = strrchr(argv0, '/');
+
+ if (c->app_name != NULL)
+ c->app_name++;
+ else
+ c->app_name = argv0;
+
+ c->commands = mrp_allocz(sizeof(default_commands));
+ c->ncommand = MRP_ARRAY_SIZE(default_commands);
+
+ for (i = 0; i < c->ncommand; i++) {
+ c->commands[i] = mrp_strdup(default_commands[i]);
+ if (c->commands[i] == NULL) {
+ print(c, "Failed to initialize default command set.");
+ exit(1);
+ }
+ }
+}
+
+
+static void destroy_client(client_t *c)
+{
+ if (c != NULL) {
+ mrp_debug("destroying client");
+
+ if (c->ml != NULL)
+ mrp_mainloop_destroy(c->ml);
+
+ if (c->pa != NULL)
+ pa_mainloop_free(c->pa);
+
+ if (c->gml != NULL)
+ g_main_loop_unref(c->gml);
+
+ mrp_free(c);
+ }
+}
+
+
+static client_t *create_client(const char *argv0)
+{
+ client_t *c = mrp_allocz(sizeof(*c));
+
+ if (c != NULL)
+ set_client_defaults(c, argv0);
+
+ return c;
+}
+
+
+static int create_mainloop(client_t *c)
+{
+ if (!c->glib) {
+ c->pa = pa_mainloop_new();
+ c->ml = mrp_mainloop_pulse_get(pa_mainloop_get_api(c->pa));
+ }
+ else {
+ c->gml = g_main_loop_new(NULL, FALSE);
+ c->ml = mrp_mainloop_glib_get(c->gml);
+ }
+
+ if (c->ml != NULL)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+
+static void run_mainloop(client_t *c)
+{
+ if (c != NULL && c->pa != NULL)
+ pa_mainloop_run(c->pa, &c->exit_status);
+ else
+ g_main_loop_run(c->gml);
+}
+
+
+static void quit_mainloop(client_t *c, int exit_status)
+{
+ if (c != NULL) {
+ if (c->pa != NULL)
+ pa_mainloop_quit(c->pa, exit_status);
+ else
+ g_main_loop_quit(c->gml);
+ }
+ else
+ exit(exit_status);
+}
+
+
+static void sighandler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+
+ MRP_UNUSED(h);
+
+ switch (signum) {
+ case SIGINT:
+ printf("Received SIGINT, exiting...");
+ quit_mainloop(c, 0);
+ break;
+
+ case SIGTERM:
+ printf("Received SIGTERM, exiting...");
+ quit_mainloop(c, 0);
+ break;
+ }
+}
+
+
+static void setup_signals(client_t *c)
+{
+ mrp_add_sighandler(c->ml, SIGINT , sighandler, c);
+ mrp_add_sighandler(c->ml, SIGTERM, sighandler, c);
+}
+
+
+
+
+static int split_input(char *input, int narg, char **args)
+{
+ int n;
+ char *p;
+
+ n = 0;
+ p = input;
+
+ while (*p) {
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ args[n++] = p;
+
+ if (n >= narg) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ while (*p && *p != ' ' && *p != '\t')
+ p++;
+
+ if (*p)
+ *p++ = '\0';
+ }
+
+ return n;
+}
+
+
+static void process_input(brl_t *brl, const char *input, void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+ int len = input ? strlen(input) + 1 : 0;
+ char buf[len], *args[64];
+ int narg;
+
+ if (len > 1) {
+ brl_add_history(brl, input);
+ hide_prompt(c);
+
+ strcpy(buf, input);
+ narg = split_input(buf, MRP_ARRAY_SIZE(args), args);
+ if (narg > 0)
+ execute_user_command(c, narg, &args[0]);
+ else
+ printf("failed to parse input '%s'\n", input);
+
+ show_prompt(c);
+ }
+}
+
+
+static void setup_input(client_t *c)
+{
+ int fd;
+
+ fd = fileno(stdin);
+ c->brl = brl_create_with_murphy(fd, "disconnected", c->ml,
+ process_input, c);
+
+ if (c->brl != NULL)
+ brl_show_prompt(c->brl);
+ else {
+ fprintf(stderr, "Failed to initialize breedline for console input.");
+ exit(1);
+ }
+}
+
+
+static void cleanup_input(client_t *c)
+{
+ if (c != NULL && c->brl != NULL) {
+ brl_destroy(c->brl);
+ c->brl = NULL;
+ }
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+ va_list ap;
+ const char *exe;
+
+ if (fmt && *fmt) {
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+ }
+
+ exe = strrchr(argv0, '/');
+
+ printf("usage: %s [options]\n\n"
+ "The possible options are:\n"
+ " -N, --name=APPNAME application name to use\n"
+ " -C, --class=APPCLASS application class to use\n"
+ " -v, --verbose increase logging verbosity\n"
+ " -d, --debug enable debug messages\n"
+ " -R, --register automatically register to server\n"
+ " -F, --focus[=TYPE] automatically request focus\n"
+ " -h, --help show help on usage\n", exe);
+ printf("\n");
+
+ if (exit_code < 0)
+ return;
+ else
+ exit(exit_code);
+}
+
+
+static void parse_cmdline(client_t *c, int argc, char **argv)
+{
+# define OPTIONS "N:C:d:RFgh"
+ struct option options[] = {
+ { "name" , required_argument, NULL, 'N' },
+ { "class" , required_argument, NULL, 'C' },
+ { "debug" , required_argument, NULL, 'd' },
+ { "register" , no_argument , NULL, 'R' },
+ { "focus" , optional_argument, NULL, 'F' },
+ { "glib" , no_argument , NULL, 'g' },
+ { "help" , no_argument , NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ int opt;
+
+ while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+ switch (opt) {
+ case 'N':
+ c->app_name = optarg;
+ break;
+
+ case 'C':
+ c->app_class = optarg;
+ break;
+
+ case 'd':
+ mrp_debug_set_config(optarg);
+ mrp_debug_enable(TRUE);
+ break;
+
+ case 'R':
+ c->autoregister = TRUE;
+ break;
+
+ case 'F':
+ c->autofocus = optarg ? optarg : "shared";
+ break;
+
+ case 'g':
+ c->glib = TRUE;
+ break;
+
+ case 'h':
+ print_usage(argv[0], -1, "");
+ exit(0);
+ break;
+
+ default:
+ print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+ }
+ }
+}
+
+
+static void render_notify(srs_t *srs, srs_voice_event_t *e, void *user_data,
+ void *notify_data)
+{
+ client_t *c = (client_t *)user_data;
+
+ MRP_UNUSED(srs);
+ MRP_UNUSED(notify_data);
+
+ switch (e->type) {
+ case SRS_VOICE_EVENT_STARTED:
+ print(c, "Rendering of TTS #%u started...", e->id);
+ break;
+
+ case SRS_VOICE_EVENT_PROGRESS:
+ print(c, "%f %% (%u msec) of TTS #%u rendered...",
+ e->data.progress.pcnt, e->data.progress.msec, e->id);
+ break;
+
+ case SRS_VOICE_EVENT_COMPLETED:
+ print(c, "Rendering of TTS #%u completed.", e->id);
+ break;
+
+ case SRS_VOICE_EVENT_TIMEOUT:
+ print(c, "Rendering of TTS #%u timed out.", e->id);
+ break;
+
+ case SRS_VOICE_EVENT_ABORTED:
+ print(c, "Rendering of TTS #%u terminated abnormally.", e->id);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static void connect_notify(srs_t *srs, int status, const char *msg,
+ void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+
+ if (status == 1) {
+ set_prompt(c, "connected");
+ print(c, "Connection to server established.");
+ }
+ else {
+ set_prompt(c, "disconnected");
+ print(c, "Server connection down (error: %d, %s).", status,
+ msg ? msg : "<unknown>");
+ }
+}
+
+
+static void focus_notify(srs_t *srs, srs_voice_focus_t focus, void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+
+ print(c, "Client has now %sfocus.", !focus ? "no " :
+ focus == SRS_VOICE_FOCUS_SHARED ? "shared " : "exclusive ");
+}
+
+
+
+static void command_notify(srs_t *srs, int idx, char **tokens, int ntoken,
+ void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+ char cmd[1024], *p;
+ size_t size;
+ int i;
+
+ print(c, "Got command #%d: ", idx);
+ for (i = 0; i < ntoken; i++)
+ print(c, " token #%d: %s", i, tokens[i]);
+}
+
+
+static void register_client(client_t *c)
+{
+ static int mainloop_set = 0;
+
+ if (!mainloop_set) {
+ if (c->gml != NULL)
+ srs_set_gmainloop(c->gml);
+ else
+ srs_set_mainloop(c->ml);
+ mainloop_set = 1;
+ }
+
+ c->srs = srs_create(c->app_name, c->app_class, c->commands, c->ncommand,
+ connect_notify, focus_notify, command_notify, c);
+
+ if (c->srs == NULL) {
+ print(c, "Failed to create SRS client.");
+ return;
+ }
+
+ if (srs_connect(c->srs, NULL, 0) < 0)
+ print(c, "Failed to connect SRS client.");
+}
+
+
+static void unregister_client(client_t *c)
+{
+ srs_disconnect(c->srs);
+}
+
+
+static void query_voices_reply(srs_t *srs, srs_voice_actor_t *actors,
+ int nactor, void *user_data, void *notify_data)
+{
+ client_t *c = (client_t *)user_data;
+ srs_voice_actor_t *a;
+ int i;
+
+ MRP_UNUSED(notify_data);
+
+ print(c, "Server has %d available matching voices.", nactor);
+
+ for (i = 0, a = actors; i < nactor; i++, a++) {
+ print(c, "Actor %s:", a->name, a->id);
+ print(c, " language: %s", a->lang);
+ print(c, " dialect: %s", a->dialect);
+ print(c, " gender: %s",
+ a->gender == SRS_VOICE_GENDER_MALE ? "male" : "female");
+ print(c, " age: %u", a->age);
+ print(c, " description: %s", a->description);
+ }
+}
+
+
+static void query_voices(client_t *c, const char *language)
+{
+ if (language == NULL)
+ language = "";
+
+ if (srs_query_voices(c->srs, language, query_voices_reply, NULL) < 0)
+ print(c, "Voice query failed.");
+}
+
+
+static void request_focus(client_t *c, const char *focusstr)
+{
+ srs_voice_focus_t focus;
+
+ if (!strcmp(focusstr, "none"))
+ focus = SRS_VOICE_FOCUS_NONE;
+ else if (!strcmp(focusstr, "shared"))
+ focus = SRS_VOICE_FOCUS_SHARED;
+ else if (!strcmp(focusstr, "exclusive"))
+ focus = SRS_VOICE_FOCUS_EXCLUSIVE;
+ else {
+ print(c, "Unknown focus type '%s'", focusstr);
+ return;
+ }
+
+ srs_request_focus(c->srs, focus);
+}
+
+
+static void tts_progress_cb(srs_t *srs, srs_voice_event_t *event,
+ void *user_data, void *notify_data)
+{
+ client_t *c = (client_t *)user_data;
+
+ MRP_UNUSED(srs);
+ MRP_UNUSED(notify_data);
+
+ print(c, "Got voice rendering event 0x%x.", event->type);
+}
+
+
+static void request_tts(client_t *c, int ntoken, char **tokens)
+{
+ const char *sep = "";
+ const char *voice = "english";
+ int timeout = SRS_VOICE_QUEUE;
+ int events = FALSE;
+ char msg[1024], *t, *e, *p;
+ int i, o;
+ size_t l;
+ ssize_t n;
+
+ p = msg;
+ l = sizeof(msg);
+ for (i = 0; i < ntoken; i++) {
+ t = tokens[i];
+ if (*t == '-') {
+ if (!strncmp(t + 1, "timeout:", o=8)) {
+ timeout = strtol(t + 1 + o, &e, 10);
+ if (*e != '\0') {
+ print(c, "Invalid timeout: %s.", t + 1 + o);
+ return;
+ }
+ }
+ else if (!strncmp(t + 1, "events", o=6)) {
+ events = TRUE;
+ }
+ else if (!strncmp(t + 1, "voice:", o=6)) {
+ voice = t + 1 + o;
+ }
+ }
+ else {
+ n = snprintf(p, l, "%s%s", sep, t);
+ if (n >= l) {
+ print(c, "TTS message too long.");
+ return;
+ }
+
+ p += n;
+ l -= n;
+ sep = " ";
+ }
+ }
+
+ print(c, "Requesting TTS for message: '%s'.", msg);
+
+ c->vreq = srs_render_voice(c->srs, msg, voice, timeout,
+ events ? SRS_VOICE_MASK_ALL:SRS_VOICE_MASK_NONE,
+ render_notify, NULL);
+}
+
+
+static void cancel_tts(client_t *c, int ntoken, char **tokens)
+{
+ int i;
+ uint32_t vreq;
+ char *end;
+
+ if (ntoken == 0) {
+ if (c->vreq)
+ srs_cancel_voice(c->srs, c->vreq);
+ else
+ print(c, "No outstanding TTS request.");
+ }
+ else {
+ for (i = 0; i < ntoken; i++) {
+ vreq = strtoul(tokens[i], &end, 10);
+
+ if (end && !*end) {
+ print(c, "Cancelling TTS request %u.", vreq);
+ srs_cancel_voice(c->srs, vreq);
+ }
+ else
+ print(c, "TTS request id '%s' is invalid.", tokens[i]);
+ }
+ }
+}
+
+
+static void execute_user_command(client_t *c, int narg, char **args)
+{
+ const char *cmd;
+
+ cmd = args[0];
+ narg--;
+ args++;
+
+ switch (narg) {
+ case 0:
+ if (!strcmp(cmd, "register")) register_client(c);
+ else if (!strcmp(cmd, "unregister")) unregister_client(c);
+ else if (!strcmp(cmd, "exit")) quit_mainloop(c, 0);
+ else if (!strcmp(cmd, "quit")) quit_mainloop(c, 0);
+ else if (!strcmp(cmd, "help")) {
+ print(c, "Available commands:");
+ print(c, " register - register to server");
+ print(c, " unregister - unregister from server");
+ print(c, " focus none|shared|exclusive - request voice focus");
+ print(c, " add command <command> - add new command");
+ print(c, " del command <command> - delete a command");
+ print(c, " tts render '<msg>' timeout subscribe");
+ print(c, " tts cancel '<id>'");
+ print(c, " list commands - list commands set");
+ print(c, " help - show this help");
+ print(c, " exit - exit from client");
+ }
+ else
+ print(c, "Unknown command '%s'.", cmd);
+ break;
+
+ case 1:
+ if (!strcmp(cmd, "focus")) {
+ if (strcmp(args[0], "none") &&
+ strcmp(args[0], "shared") &&
+ strcmp(args[0], "exclusive")) {
+ print(c, "Invalid focus '%s', valid foci are: "
+ "none, shared, and exclusive.", args[0]);
+ }
+ else
+ request_focus(c, args[0]);
+ }
+ else if (!strcmp(cmd, "reset") && !strcmp(args[0], "commands"))
+ reset_commands(c);
+ else if (!strcmp(cmd, "list" ) && !strcmp(args[0], "commands"))
+ list_commands(c);
+ else if (!strcmp(cmd, "list" ) && !strcmp(args[0], "voices"))
+ query_voices(c, NULL);
+ else if (!strcmp(cmd, "cancel" ) && !strcmp(args[0], "tts"))
+ cancel_tts(c, 0, NULL);
+ else
+ print(c, "Invalid command.");
+ break;
+
+ case 2:
+ if (!strcmp(cmd, "list" ) && !strcmp(args[0], "voices"))
+ query_voices(c, args[1]);
+ else
+ print(c, "Invalid command.");
+ break;
+
+ default:
+ if (!strcmp(args[0], "command")) {
+ if (!strcmp(cmd, "add" ))
+ add_command(c, narg-1, args+1);
+ else if (!strcmp(cmd, "del" ) || !strcmp(cmd, "delete"))
+ del_command(c, narg-1, args+1);
+ else
+ print(c, "Invalid command.");
+ }
+ else if (!strcmp(args[0], "tts")) {
+ if (!strcmp(cmd, "render"))
+ request_tts(c, narg-1, args+1);
+ else if (!strcmp(cmd, "cancel"))
+ cancel_tts(c, narg-1, args+1);
+ else
+ print(c, "Invalid TTS command.");
+ }
+ else
+ print(c, "Invalid command.");
+ break;
+ }
+}
+
+
+int main(int argc, char *argv[])
+{
+ client_t *c;
+
+ c = create_client(argv[0]);
+
+ if (c == NULL) {
+ fprintf(stderr, "Failed to create client.");
+ exit(1);
+ }
+
+ parse_cmdline(c, argc, &argv[0]);
+ create_mainloop(c);
+ setup_signals(c);
+ setup_input(c);
+
+ if (c->glib)
+ print(c, "Using GMainLoop...");
+ else
+ print(c, "Using pa_manloop...");
+
+ run_mainloop(c);
+ cleanup_input(c);
+ destroy_client(c);
+
+ return 0;
+}