speech-recognition: added a primitive voice-feedback API.
authorKrisztian Litkey <kli@iki.fi>
Thu, 6 Jun 2013 13:44:05 +0000 (16:44 +0300)
committerKrisztian Litkey <krisztian.litkey@intel.com>
Thu, 6 Jun 2013 13:58:01 +0000 (16:58 +0300)
src/Makefile.am
src/daemon/context.h
src/daemon/voice.c [new file with mode: 0644]
src/daemon/voice.h [new file with mode: 0644]
src/plugins/simple-voice/Makefile [new file with mode: 0644]
src/plugins/simple-voice/simple-voice.c [new file with mode: 0644]

index 7d947f2..d890112 100644 (file)
@@ -24,7 +24,8 @@ srs_daemon_SOURCES =                          \
                daemon/client.c                 \
                daemon/plugin.c                 \
                daemon/audiobuf.c               \
-               daemon/recognizer.c
+               daemon/recognizer.c             \
+               daemon/voice.c
 
 srs_daemon_CFLAGS =                            \
                $(AM_CFLAGS)                    \
@@ -177,6 +178,20 @@ plugin_search_client_la_LDFLAGS =                          \
 
 plugin_search_client_la_LIBADD  =
 
+# simple-voice synthesizer plugin
+plugin_LTLIBRARIES += plugin-simple-voice.la
+
+plugin_simple_voice_la_SOURCES =                               \
+               plugins/simple-voice/simple-voice.c
+
+plugin_simple_voice_la_CFLAGS  =                               \
+               $(AM_CFLAGS)
+
+plugin_simple_voice_la_LDFLAGS =                               \
+               -module -avoid-version
+
+plugin_simple_voice_la_LIBADD  =
+
 
 
 # cleanup
index 53b8d0b..39b7c13 100644 (file)
@@ -60,6 +60,7 @@ struct srs_context_s {
     void              *cached_srec;      /* previously looked up backend */
     mrp_list_hook_t    disambiguators;   /* disambiguators */
     void              *default_disamb;   /* default disambiguator */
+    void              *voice;            /* active voice engine if any */
 
     /* files and directories */
     const char      *config_file;        /* configuration file */
diff --git a/src/daemon/voice.c b/src/daemon/voice.c
new file mode 100644 (file)
index 0000000..688331e
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2012, 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 <unistd.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+
+#include "src/daemon/context.h"
+#include "src/daemon/voice.h"
+
+/*
+ * voice engine instance
+ */
+
+typedef struct {
+    srs_context_t   *srs;                /* main context */
+    char            *name;               /* engine name */
+    srs_voice_api_t  api;                /* voice engine API */
+    void            *api_data;           /* opaque engine data */
+} srs_voice_t;
+
+
+int srs_register_voice(srs_context_t *srs, const char *name,
+                       srs_voice_api_t *api, void *api_data)
+{
+    srs_voice_t *voice;
+
+    if (srs->voice != NULL) {
+        errno = EEXIST;
+        return -1;
+    }
+
+    if (api == NULL || name == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    voice = mrp_allocz(sizeof(*voice));
+
+    if (voice == NULL)
+        return -1;
+
+    voice->srs      = srs;
+    voice->name     = mrp_strdup(name);
+    voice->api      = *api;
+    voice->api_data = api_data;
+
+    if (voice->name == NULL) {
+        mrp_free(voice);
+        return -1;
+    }
+
+    srs->voice = voice;
+
+    return 0;
+}
+
+
+void srs_unregister_voice(srs_context_t *srs, const char *name)
+{
+    srs_voice_t *voice;
+
+    if ((voice = srs->voice) != NULL) {
+        if (!strcmp(voice->name, name)) {
+            mrp_free(voice->name);
+            mrp_free(voice);
+
+            srs->voice = NULL;
+        }
+    }
+}
+
+
+uint32_t srs_load_sound(srs_context_t *srs, const char *path, int cache)
+{
+    srs_voice_t *voice = srs->voice;
+    uint32_t     id;
+
+    if (voice != NULL)
+        id = voice->api.load(path, cache, voice->api_data);
+    else
+        id = SRS_VOICE_INVALID;
+
+    return id;
+}
+
+
+uint32_t srs_play_sound(srs_context_t *srs, uint32_t id,
+                        srs_voice_notify_t notify, void *user_data)
+{
+    srs_voice_t *voice = srs->voice;
+
+    if (voice != NULL)
+        id = voice->api.play(id, notify, user_data, voice->api_data);
+    else
+        id = SRS_VOICE_INVALID;
+
+    return id;
+}
+
+
+uint32_t srs_play_sound_file(srs_context_t *srs, const char *path,
+                             srs_voice_notify_t notify, void *user_data)
+{
+    srs_voice_t *voice = srs->voice;
+    uint32_t     id;
+
+    if (voice != NULL)
+        id = voice->api.play_file(path, notify, user_data, voice->api_data);
+    else
+        id = SRS_VOICE_INVALID;
+
+    return id;
+}
+
+
+uint32_t srs_say_msg(srs_context_t *srs, const char *msg,
+                     srs_voice_notify_t notify, void *user_data)
+{
+    srs_voice_t *voice = srs->voice;
+    uint32_t     id;
+
+    if (voice != NULL)
+        id = voice->api.say(msg, notify, user_data, voice->api_data);
+    else
+        id = SRS_VOICE_INVALID;
+
+    return id;
+}
+
+
+void srs_cancel_voice(srs_context_t *srs, uint32_t id, int notify)
+{
+    srs_voice_t *voice = srs->voice;
+
+    if (voice != NULL)
+        voice->api.cancel(id, notify, voice->api_data);
+}
diff --git a/src/daemon/voice.h b/src/daemon/voice.h
new file mode 100644 (file)
index 0000000..2e4f7da
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2012, 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_DAEMON_VOICE_H__
+#define __SRS_DAEMON_VOICE_H__
+
+#include "src/daemon/context.h"
+
+/** Identifier indicating failure/invalid voice. */
+#define SRS_VOICE_INVALID ((uint32_t)-1)
+
+/** Voice completion notification callback type. */
+typedef void (*srs_voice_notify_t)(uint32_t id, void *user_data);
+
+/*
+ * API to voice backend
+ */
+typedef struct {
+    /** Load a sound from a file, attempt caching according to the hint. */
+    uint32_t (*load)(const char *path, int cache, void *api_data);
+    /** Play a sound, call the given notifier when done. */
+    uint32_t (*play)(uint32_t id, srs_voice_notify_t notify, void *user_data,
+                     void *api_data);
+    /** Play a sound from a file, call the given notifier when done. */
+    uint32_t (*play_file)(const char *file, srs_voice_notify_t notify,
+                          void *user_data, void *api_data);
+    /** Synthesize the given message, call the given notifier when done. */
+    uint32_t (*say)(const char *msg, srs_voice_notify_t notify, void *user_data,
+                    void *api_data);
+    /** Cancel a playing sound or message, notify completion if asked. */
+    void (*cancel)(uint32_t id, int notify, void *api_data);
+} srs_voice_api_t;
+
+
+/** Register a voice engine. */
+int srs_register_voice(srs_context_t *srs, const char *name,
+                       srs_voice_api_t *api, void *api_data);
+
+/** Unregister the given voice engine. */
+void srs_unregister_voice(srs_context_t *srs, const char *name);
+
+/** Load the given sound file, returning its identifier. */
+uint32_t srs_load_sound(srs_context_t *srs, const char *path, int cache);
+
+/** Play the given preloaded sound. */
+uint32_t srs_play_sound(srs_context_t *srs, uint32_t id,
+                        srs_voice_notify_t notify, void *user_data);
+
+/** Play the given file. */
+uint32_t srs_play_sound_file(srs_context_t *srs, const char *path,
+                             srs_voice_notify_t notify, void *user_data);
+
+/** Syntehsize the given message. */
+uint32_t srs_say_msg(srs_context_t *srs, const char *msg,
+                     srs_voice_notify_t notify, void *user_data);
+
+/** Cancel a playing sound or message. */
+void srs_cancel_voice(srs_context_t *srs, uint32_t id, int notify);
+
+#endif /* __SRS_DAEMON_VOICE_H__ */
diff --git a/src/plugins/simple-voice/Makefile b/src/plugins/simple-voice/Makefile
new file mode 100644 (file)
index 0000000..adcfe7e
--- /dev/null
@@ -0,0 +1,5 @@
+all:
+       $(MAKE) -C ../.. $@
+
+%:
+       $(MAKE) -C ../.. $(MAKECMDGOALS)
diff --git a/src/plugins/simple-voice/simple-voice.c b/src/plugins/simple-voice/simple-voice.c
new file mode 100644 (file)
index 0000000..1d57686
--- /dev/null
@@ -0,0 +1,485 @@
+/*
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/mainloop.h>
+
+#include "src/daemon/plugin.h"
+#include "src/daemon/voice.h"
+
+#define FESTIVAL "/usr/bin/festival --tts"
+#define PAPLAY   "/usr/bin/paplay"
+
+#define SYNTH_NAME    "simple-voice"
+#define SYNTH_DESCR   "A trivial voice/sound feedback plugin for SRS."
+#define SYNTH_AUTHORS "Krisztian Litkey <kli@iki.fi>"
+#define SYNTH_VERSION "0.0.1"
+
+#define SYNTH_TYPE_SOUND  0x80000000
+#define SYNTH_TYPE_MSG    0x40000000
+#define SYNTH_TYPE_ACTIVE 0x20000000
+#define SYNTH_INDEX(id)   ((int)((id) & 0x70000000))
+
+#define MAX_ARGC 32
+
+
+/*
+ * a loaded file
+ */
+
+typedef struct {
+    char         *path;                  /* path to sound file */
+    uint32_t      id;                    /* sound id */
+    unsigned int  cache : 1;             /* cache hint */
+} sound_t;
+
+
+/*
+ * an active message or sound
+ */
+
+typedef struct {
+    uint32_t            id;              /* active id */
+    pid_t               pid;             /* rendering child */
+    srs_voice_notify_t  notify;          /* completion notification callback */
+    void               *user_data;       /* opaque callback data */
+} active_t;
+
+
+/*
+ * synthesizer state
+ */
+
+typedef struct {
+    srs_plugin_t      *self;             /* us */
+    sound_t           *sounds;           /* loaded sounds */
+    int                nsound;           /* number of sounds */
+    char             **tts_argv;         /* TTS command/arguments */
+    int                tts_argc;         /* TTS argument count */
+    char             **play_argv;        /* sound command/arguments */
+    int                play_argc;        /* sound argument count */
+    active_t           active;           /* active sound/message */
+    mrp_sighandler_t  *sigh;             /* SIGCHLD handler */
+} synth_t;
+
+
+
+static pid_t fork_command(int *wfd, int argc, char **argv)
+{
+    pid_t   pid;
+    int     pfd[2], null, fd;
+    char    dummy;
+
+    if (pipe(pfd) < 0)
+        return 0;
+
+    switch ((pid = fork())) {
+    case -1:
+        pid = 0;
+        break;
+
+        /* child */
+    case 0:
+        null = open("/dev/null", O_RDWR);
+
+        if (null == -1)
+            exit(1);
+
+        dup2(pfd[0], 0);
+        dup2(null, 1);
+        dup2(null, 2);
+
+        close(pfd[1]);
+        for (fd = 3; fd < 1024; fd++)
+            close(fd);
+
+        if (read(0, &dummy, 1) != 1)
+            exit(2);
+
+        argv[argc] = NULL;
+        execv(argv[0], argv);
+        exit(3);
+
+        /* child */
+    default:
+        close(pfd[0]);
+        *wfd = pfd[1];
+        break;
+    }
+
+    return pid;
+}
+
+
+
+static uint32_t synth_load(const char *path, int cache, void *api_data)
+{
+    synth_t *synth = (synth_t *)api_data;
+    sound_t *snd;
+
+    if (mrp_reallocz(synth->sounds, synth->nsound, synth->nsound + 1) != NULL) {
+        snd = synth->sounds + synth->nsound;
+
+        snd->path  = mrp_strdup(path);
+        snd->cache = cache ? 1 : 0;
+        snd->id    = SYNTH_TYPE_SOUND | synth->nsound;
+
+        if (snd->path != NULL) {
+            synth->nsound++;
+
+            return snd->id;
+        }
+
+        mrp_reallocz(synth->sounds, synth->nsound + 1, synth->nsound);
+    }
+
+    return SRS_VOICE_INVALID;
+}
+
+
+static uint32_t synth_play_file(const char *path, srs_voice_notify_t notify,
+                                void *user_data, void *api_data)
+{
+#if 1
+    synth_t *synth = (synth_t *)api_data;
+    uint32_t id;
+    pid_t    pid, wfd;
+
+    if (synth->active.pid) {
+        errno = EBUSY;
+
+        return SRS_VOICE_INVALID;
+    }
+
+    mrp_log_info("Playing sound file '%s'.", path);
+
+    synth->play_argv[synth->play_argc] = (char *)path;
+
+    pid = fork_command(&wfd, synth->play_argc, synth->play_argv + 1);
+
+    if (pid > 0) {
+        write(wfd, "G", 1);
+
+        id = 1 | SYNTH_TYPE_ACTIVE;
+
+        synth->active.pid = pid;
+        synth->active.notify    = notify;
+        synth->active.user_data = user_data;
+        synth->active.id        = id;
+
+        close(wfd);
+    }
+    else
+        id = SRS_VOICE_INVALID;
+
+#else
+    uint32_t id = 0;
+    char cmd[1024];
+
+    snprintf(cmd, sizeof(cmd), "paplay %s", path);
+    system(cmd);
+#endif
+
+    return id;
+}
+
+
+static uint32_t synth_play(uint32_t id, srs_voice_notify_t notify,
+                           void *user_data, void *api_data)
+{
+    synth_t *synth = (synth_t *)api_data;
+    int      idx   = SYNTH_INDEX(id);
+    sound_t *snd;
+
+    if (idx < synth->nsound) {
+        snd = synth->sounds + idx;
+
+        return synth_play_file(snd->path, notify, user_data, api_data);
+    }
+    else
+        return SRS_VOICE_INVALID;
+}
+
+
+static uint32_t synth_tts(const char *msg, srs_voice_notify_t notify,
+                          void *user_data, void *api_data)
+{
+#if 1
+    synth_t *synth = (synth_t *)api_data;
+    uint32_t id;
+    pid_t    pid, wfd;
+
+    if (synth->active.pid) {
+        errno = EBUSY;
+
+        return SRS_VOICE_INVALID;
+    }
+
+    mrp_log_info("Synthesizing message '%s'.", msg);
+
+    pid = fork_command(&wfd, synth->tts_argc - 1, synth->tts_argv + 1);
+
+    if (pid > 0) {
+        write(wfd, "G", 1);
+
+        id = 1 | SYNTH_TYPE_ACTIVE;
+
+        synth->active.pid = pid;
+        synth->active.notify    = notify;
+        synth->active.user_data = user_data;
+        synth->active.id        = id;
+
+        dprintf(wfd, "%s\n", msg);
+
+        close(wfd);
+    }
+    else
+        id = SRS_VOICE_INVALID;
+
+#else
+    uint32_t id = 0;
+
+    char cmd[1024];
+    snprintf(cmd, sizeof(cmd), "echo \"%s\" | /usr/bin/festival --tts", msg);
+    system(cmd);
+#endif
+
+    return id;
+}
+
+
+static void synth_cancel(uint32_t id, int notify, void *api_data)
+{
+    MRP_UNUSED(id);
+    MRP_UNUSED(notify);
+    MRP_UNUSED(api_data);
+}
+
+
+static void sighandler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    synth_t            *synth = (synth_t *)user_data;
+    pid_t               pid;
+    uint32_t            id;
+    srs_voice_notify_t  cb;
+    void               *data;
+    int                 status, sts;
+
+    MRP_UNUSED(h);
+
+    if (signum != SIGCHLD)
+        return;
+
+    mrp_log_info("Received SIGCHLD signal.");
+
+    if (synth->active.pid != 0) {
+        pid = synth->active.pid;
+
+        if ((sts = waitpid(pid, &status, WNOHANG)) == pid) {
+            mrp_log_info("Active child (pid %u) exited with status %d.",
+                         synth->active.pid,
+                         WIFEXITED(status) ? WEXITSTATUS(status) : -1);
+
+            id   = synth->active.id;
+            cb   = synth->active.notify;
+            data = synth->active.user_data;
+
+            synth->active.pid       = 0;
+            synth->active.notify    = NULL;
+            synth->active.user_data = NULL;
+
+            if (cb != NULL)
+                cb(id, data);
+        }
+        else {
+            if (sts != 0)
+                mrp_log_error("waitpid(%u) failed (%d: %s)",
+                              synth->active.pid, errno, strerror(errno));
+        }
+    }
+}
+
+
+static int create_synth(srs_plugin_t *plugin)
+{
+    srs_context_t  *srs = plugin->srs;
+    mrp_mainloop_t *ml  = srs->ml;
+    synth_t        *synth;
+
+    mrp_debug("creating simple voice plugin");
+
+    synth = mrp_allocz(sizeof(*synth));
+
+    if (synth != NULL) {
+        synth->sigh = mrp_add_sighandler(ml, SIGCHLD, sighandler, synth);
+
+        if (synth->sigh != NULL) {
+            plugin->plugin_data = synth;
+
+            return TRUE;
+        }
+
+        mrp_free(synth);
+    }
+
+    return FALSE;
+}
+
+
+static int config_synth(srs_plugin_t *plugin, srs_cfg_t *settings)
+{
+    static char  ttscmd[1024], playcmd[1024];
+    static char *ttsargs[MAX_ARGC + 2] , *playargs[MAX_ARGC + 2];
+
+    synth_t    *synth = (synth_t *)plugin->plugin_data;
+    const char *tts, *play, *s;
+    char       *d, **v, *arg;
+    int         l, a;
+
+    mrp_debug("configure simple voice plugin");
+
+    tts  = srs_get_string_config(settings, "voice.say" , FESTIVAL);
+    play = srs_get_string_config(settings, "voice.play", PAPLAY);
+
+    mrp_log_info("voice plugin TTS play command: '%s'", tts);
+    mrp_log_info("voice plugin sound command: '%s'"   , play);
+
+    s = tts;
+    d = ttscmd;
+    l = sizeof(ttscmd) - 1;
+    a = 0;
+    v = ttsargs + 1;
+
+ parse:
+    while (*s && l > 0 && a < MAX_ARGC) {
+        while (*s && (*s == ' ' || *s == '\t'))
+            s++;
+
+        arg = d;
+        while (*s && *s != ' ' && *s != '\t' && l > 0) {
+            *d++ = *s++;
+            l--;
+        }
+
+        if (l == 0) {
+            mrp_log_error("Invalid command line give, too long.");
+            return FALSE;
+        }
+
+        *d++ = '\0';
+        l--;
+
+        v[a++] = arg;
+    }
+
+    if (a >= MAX_ARGC)
+        return FALSE;
+
+    if (ttscmd <= d && d <= ttscmd + sizeof(ttscmd)) {
+        ttsargs[0] = ttsargs[1];
+
+        synth->tts_argv = ttsargs;
+        synth->tts_argc = a + 1;
+
+        s = play;
+        d = playcmd;
+        l = sizeof(playcmd) - 1;
+        a = 0;
+        v = playargs + 1;
+
+        goto parse;
+    }
+    else {
+        playargs[0] = playargs[1];
+
+        synth->play_argv    = playargs;
+        synth->play_argc    = a + 1;
+    }
+
+    return TRUE;
+}
+
+
+static int start_synth(srs_plugin_t *plugin)
+{
+    static srs_voice_api_t api = {
+    load:      synth_load,
+    play:      synth_play,
+    play_file: synth_play_file,
+    say:       synth_tts,
+    cancel:    synth_cancel
+    };
+    synth_t       *synth = (synth_t *)plugin->plugin_data;
+    srs_context_t *srs   = plugin->srs;
+
+    mrp_debug("start simple-voice plugin");
+
+    if (synth != NULL) {
+        if (srs_register_voice(srs, SYNTH_NAME, &api, synth) == 0)
+            return TRUE;
+        else
+            mrp_free(synth);
+    }
+
+    return FALSE;
+}
+
+
+static void stop_synth(srs_plugin_t *plugin)
+{
+    srs_context_t *srs = plugin->srs;
+    synth_t       *synth = (synth_t *)plugin->plugin_data;
+
+    mrp_debug("stop simple-voice plugin");
+
+    srs_unregister_voice(srs, SYNTH_NAME);
+    mrp_del_sighandler(synth->sigh);
+}
+
+
+static void destroy_synth(srs_plugin_t *plugin)
+{
+    synth_t *synth = (synth_t *)plugin->plugin_data;
+
+    mrp_debug("destroy simple-voice plugin");
+
+    mrp_free(synth);
+}
+
+
+SRS_DECLARE_PLUGIN(SYNTH_NAME, SYNTH_DESCR, SYNTH_AUTHORS, SYNTH_VERSION,
+                   create_synth, config_synth, start_synth, stop_synth,
+                   destroy_synth)