Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@member.fsf.org>2009-03-06 07:59:49 (GMT)
committer Aleksey Lim <alsroot@member.fsf.org>2009-03-06 07:59:49 (GMT)
commitb956b2b089a7515a917ee91eb11aa815f3bb46bd (patch)
treeabfbbb641434bc82d412cbb4d7d045a0b1e03f2c
parentfe9b23849552856353cc861e675cd29a6429cfbc (diff)
Use round-robin queue to synch; process text by chunks
-rw-r--r--src/Makefile.am2
-rw-r--r--src/espeak.c130
-rw-r--r--src/espeak.h27
-rw-r--r--src/gstespeak.c99
-rw-r--r--src/gstespeak.h6
-rw-r--r--src/spin.c321
-rw-r--r--src/spin.h32
-rw-r--r--src/text.h107
8 files changed, 579 insertions, 145 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 45e3704..b6c9ff9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,6 @@
plugin_LTLIBRARIES = libgstespeak.la
-libgstespeak_la_SOURCES = gstespeak.c espeak.c
+libgstespeak_la_SOURCES = spin.c espeak.c gstespeak.c
libgstespeak_la_CFLAGS = $(GST_CFLAGS) $(GIO_CFLAGS) $(GST_AUDIO_CFLAGS)
libgstespeak_la_LIBADD = $(GST_LIBS) $(GIO_LIBS) $(GST_AUDIO_LIBS)
diff --git a/src/espeak.c b/src/espeak.c
index 265a243..f49cd6d 100644
--- a/src/espeak.c
+++ b/src/espeak.c
@@ -21,67 +21,99 @@
#include <glib.h>
#include <gio/gio.h>
#include <espeak/speak_lib.h>
+#include <gst/gst.h>
-struct Espeak
+#include "spin.h"
+#include "espeak.h"
+
+struct _Espeak
{
- GOutputStream *buffer;
+ Econtext *context;
+ guint rate;
+ guint pitch;
+ const gchar *voice;
};
-pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-static GOutputStream *buffer = NULL;
-static gint sample_rate = 0;
-static const espeak_VOICE **voices = NULL;
+static gint espeak_sample_rate = 0;
+static const espeak_VOICE **espeak_voices = NULL;
+static GOutputStream *espeak_buffer = NULL;
static gint
-read_cb(short * data, int numsamples, espeak_EVENT * events)
+synth_cb(short * data, int numsamples, espeak_EVENT * events)
{
if (data == NULL)
return 0;
if (numsamples > 0)
- g_output_stream_write(buffer, data, numsamples*2, NULL, NULL);
+ g_output_stream_write(espeak_buffer, data, numsamples*2, NULL, NULL);
+
+ GST_DEBUG("numsamples=%d data_size=%ld", numsamples*2,
+ g_memory_output_stream_get_data_size(G_MEMORY_OUTPUT_STREAM(
+ espeak_buffer)));
return 0;
}
static void
+synth(const gchar *text, GMemoryOutputStream *sound, gpointer self_)
+{
+ Espeak *self = (Espeak*)self_;
+
+ espeak_SetParameter(espeakPITCH, self->pitch, 0);
+ espeak_SetParameter(espeakRATE, self->rate, 0);
+ espeak_SetVoiceByName(self->voice);
+ espeak_buffer = G_OUTPUT_STREAM(sound);
+
+ espeak_Synth(text, strlen(text)+1, 0, POS_WORD, 0, espeakCHARS_UTF8,
+ NULL, NULL);
+}
+
+static void
init()
{
static volatile gsize initialized = 0;
- pthread_mutex_lock(&mutex);
if (initialized == 0)
{
++initialized;
- sample_rate = espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 4096, NULL, 0);
- espeak_SetSynthCallback(read_cb);
- voices = espeak_ListVoices(NULL);
+ espeak_sample_rate = espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 4096,
+ NULL, 0);
+ espeak_SetSynthCallback(synth_cb);
+ espeak_voices = espeak_ListVoices(NULL);
+ spin_init(synth);
}
- pthread_mutex_unlock(&mutex);
}
-struct Espeak*
+Espeak*
espeak_new()
{
init();
- if (sample_rate == EE_INTERNAL_ERROR)
- return NULL;
+ Espeak *self = g_new0(Espeak, 1);
+ self->context = spin_new(self);
+ self->pitch = ESPEAK_DEFAULT_PITCH;
+ self->rate = ESPEAK_DEFAULT_RATE;
+ self->voice = ESPEAK_DEFAULT_VOICE;
- struct Espeak *es = g_new(struct Espeak, 1);
- es->buffer = g_memory_output_stream_new(NULL, 0, realloc, free);
+ return self;
+}
- return es;
+void
+espeak_unref(Espeak *self)
+{
+ spin_unref(self->context);
+ memset(self, 0, sizeof(Espeak));
+ g_free(self);
}
gint
-espeak_sample_rate()
+espeak_get_sample_rate()
{
- return sample_rate;
+ return espeak_sample_rate;
}
gchar**
-espeak_voices()
+espeak_get_voices()
{
gsize count = 0;
const espeak_VOICE **i;
@@ -89,52 +121,40 @@ espeak_voices()
init();
- for (i = voices; *i; ++i) ++count;
+ for (i = espeak_voices; *i; ++i) ++count;
out = j = g_new0(gchar*, count);
- for (i = voices; *i; ++i)
+ for (i = espeak_voices; *i; ++i)
*j++ = g_strconcat((*i)->name, ":", (*i)->languages+1, NULL);
return out;
}
-gboolean
-espeak_say(struct Espeak *es, const gchar *text, const gchar *voice,
- guint pitch, guint rate)
+void
+espeak_set_pitch(Espeak *self, guint value)
{
- g_seekable_seek(G_SEEKABLE(es->buffer), 0, G_SEEK_SET, NULL, NULL);
-
- pthread_mutex_lock(&mutex);
- buffer = es->buffer;
- espeak_SetParameter(espeakPITCH, pitch, 0);
- espeak_SetParameter(espeakRATE, rate, 0);
- espeak_SetVoiceByName(voice);
- gint status = espeak_Synth(text, strlen(text)+1, 0, POS_WORD, 0,
- espeakCHARS_AUTO, NULL, NULL);
- buffer = NULL;
- pthread_mutex_unlock(&mutex);
-
- if (status != EE_OK)
- return FALSE;
-
- return TRUE;
+ self->pitch = value;
}
-gpointer
-espeak_hear(struct Espeak *es, goffset offset, guint *size)
+void
+espeak_set_rate(Espeak *self, guint value)
{
- GMemoryOutputStream *mb = (GMemoryOutputStream*)es->buffer;
-
- gpointer out = g_memory_output_stream_get_data(mb) + offset;
- *size = MIN(g_memory_output_stream_get_data_size(mb) - offset, *size);
+ self->rate = value;
+}
- return out;
+void
+espeak_set_voice(Espeak *self, const gchar *value)
+{
+ self->voice = value;
}
void
-espeak_unref(struct Espeak *es)
+espeak_say(Espeak *self, const gchar *text)
+{
+ spin_in(self->context, text);
+}
+
+gpointer
+espeak_hear(Espeak *self, gsize size)
{
- g_output_stream_close(es->buffer, NULL, NULL);
- g_object_unref(es->buffer);
- es->buffer = 0;
- g_free(es);
+ return spin_out(self->context, &size);
}
diff --git a/src/espeak.h b/src/espeak.h
index 6258ebd..b0e3f65 100644
--- a/src/espeak.h
+++ b/src/espeak.h
@@ -15,17 +15,24 @@
* Boston, MA 02111-1307, USA.
*/
-#ifndef __ESPEAK_H__
-#define __ESPEAK_H__
+#ifndef ESPEAK_H
+#define ESPEAK_H
-struct Espeak;
+#define ESPEAK_DEFAULT_PITCH 50
+#define ESPEAK_DEFAULT_RATE 170
+#define ESPEAK_DEFAULT_VOICE "default"
-struct Espeak* espeak_new();
-gint espeak_sample_rate();
-gchar** espeak_voices();
-gboolean espeak_say(struct Espeak*, const gchar *text, const gchar *voice,
- guint pitch, guint rate);
-gpointer espeak_hear(struct Espeak*, goffset offset, guint *size);
-void espeak_unref(struct Espeak*);
+struct _Espeak;
+typedef struct _Espeak Espeak;
+
+struct _Espeak* espeak_new();
+void espeak_unref(struct _Espeak*);
+gint espeak_get_sample_rate();
+gchar** espeak_get_voices();
+void espeak_set_pitch(struct _Espeak*, guint);
+void espeak_set_rate(struct _Espeak*, guint);
+void espeak_set_voice(struct _Espeak*, const gchar*);
+void espeak_say(struct _Espeak*, const gchar *text);
+gpointer espeak_hear(struct _Espeak*, gsize);
#endif
diff --git a/src/gstespeak.c b/src/gstespeak.c
index 8fd36eb..8f5d8e5 100644
--- a/src/gstespeak.c
+++ b/src/gstespeak.c
@@ -112,7 +112,7 @@ gst_espeak_class_init (GstEspeakClass * klass)
g_object_class_install_property(gobject_class, PROP_TEXT,
g_param_spec_string("text", "Text",
"Text to pronounce", NULL,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property(gobject_class, PROP_PITCH,
g_param_spec_uint("pitch", "Pitch adjustment",
"Pitch adjustment", 0, 99, 50,
@@ -144,16 +144,14 @@ static void
gst_espeak_init (GstEspeak * self,
GstEspeakClass * gclass)
{
- self->text = NULL;
- self->uri = NULL;
- self->pitch = 50;
- self->rate = 170;
+ self->pitch = ESPEAK_DEFAULT_PITCH;
+ self->rate = ESPEAK_DEFAULT_RATE;
+ self->voice = g_strdup(ESPEAK_DEFAULT_VOICE);
+ self->voices = espeak_get_voices();
self->speak = espeak_new();
- self->voice = g_strdup("default");
- self->voices = espeak_voices();
self->caps = gst_caps_new_simple("audio/x-raw-int",
- "rate", G_TYPE_INT, espeak_sample_rate(),
+ "rate", G_TYPE_INT, espeak_get_sample_rate(),
"channels", G_TYPE_INT, 1,
"endianness", G_TYPE_INT, G_BYTE_ORDER,
"width", G_TYPE_INT, 16,
@@ -169,8 +167,6 @@ gst_espeak_finalize(GObject * self_)
gst_caps_unref(self->caps); self->caps = NULL;
espeak_unref(self->speak); self->speak = NULL;
- g_free(self->text); self->text = NULL;
- g_free(self->uri); self->uri = NULL;
g_free(self->voice); self->voice = NULL;
g_strfreev(self->voices); self->voices = NULL;
@@ -179,57 +175,27 @@ gst_espeak_finalize(GObject * self_)
/******************************************************************************/
-static gboolean
-gst_espeak_set_text(GstEspeak *self, const gchar *text)
-{
- GstState state;
-
- /* the element must be stopped in order to do this */
- GST_OBJECT_LOCK(self);
- state = GST_STATE(self);
- GST_OBJECT_UNLOCK (self);
-
- if (state != GST_STATE_READY && state != GST_STATE_NULL)
- {
- GST_DEBUG_OBJECT(self, "setting text in wrong state");
- return FALSE;
- }
-
- g_free(self->text);
- g_free(self->uri);
-
- if (text == NULL) {
- self->text = NULL;
- self->uri = NULL;
- } else {
- self->text = g_strdup(text);
- self->uri = gst_uri_construct ("espeak", self->text);
- }
-
- g_object_notify(G_OBJECT (self), "text");
- gst_uri_handler_new_uri(GST_URI_HANDLER(self), self->uri);
-
- return TRUE;
-}
-
static void
-gst_espeak_set_property (GObject *object, guint prop_id,
+gst_espeak_set_property(GObject *object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstEspeak *self = GST_ESPEAK(object);
switch (prop_id) {
case PROP_TEXT:
- gst_espeak_set_text(self, g_value_get_string(value));
+ espeak_say(self->speak, g_value_get_string(value));
break;
case PROP_PITCH:
self->pitch = g_value_get_uint(value);
+ espeak_set_pitch(self->speak, self->pitch);
break;
case PROP_RATE:
self->rate = g_value_get_uint(value);
+ espeak_set_rate(self->speak, self->rate);
break;
case PROP_VOICE:
self->voice = g_strdup(g_value_get_string(value));
+ espeak_set_voice(self->speak, self->voice);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -238,15 +204,12 @@ gst_espeak_set_property (GObject *object, guint prop_id,
}
static void
-gst_espeak_get_property (GObject * object, guint prop_id,
+gst_espeak_get_property(GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstEspeak *self = GST_ESPEAK(object);
switch (prop_id) {
- case PROP_TEXT:
- g_value_set_string(value, self->text);
- break;
case PROP_PITCH:
g_value_set_uint(value, self->pitch);
break;
@@ -271,43 +234,37 @@ gst_espeak_get_property (GObject * object, guint prop_id,
/******************************************************************************/
static GstFlowReturn
-gst_espeak_create (GstBaseSrc * self_, guint64 offset, guint size,
- GstBuffer ** buf)
+gst_espeak_create(GstBaseSrc * self_, guint64 offset, guint size,
+ GstBuffer **buf)
{
GstEspeak *self = GST_ESPEAK(self_);
- gpointer ptr = espeak_hear(self->speak, offset, &size);
+ gpointer sound = espeak_hear(self->speak, size);
- if (size == 0)
+ if (sound == NULL)
return GST_FLOW_UNEXPECTED;
*buf = gst_buffer_new();
- GST_BUFFER_DATA (*buf) = ptr;
- GST_BUFFER_SIZE (*buf) = size;
+ GST_BUFFER_DATA(*buf) = sound;
+ GST_BUFFER_SIZE(*buf) = size;
return GST_FLOW_OK;
}
static gboolean
-gst_espeak_start (GstBaseSrc * self_)
+gst_espeak_start(GstBaseSrc * self_)
{
- GstEspeak *self = GST_ESPEAK(self_);
-
- if (self->text == NULL || self->text[0] == 0)
- return FALSE;
-
- return espeak_say(self->speak, self->text, self->voice, self->pitch,
- self->rate);
+ return TRUE;
}
static gboolean
-gst_espeak_stop (GstBaseSrc * self)
+gst_espeak_stop(GstBaseSrc * self)
{
return TRUE;
}
static gboolean
-gst_espeak_is_seekable (GstBaseSrc * src)
+gst_espeak_is_seekable(GstBaseSrc * src)
{
return FALSE;
}
@@ -334,13 +291,6 @@ gst_espeak_uri_get_protocols(void)
return protocols;
}
-static const gchar *
-gst_espeak_uri_get_uri(GstURIHandler *handler)
-{
- GstEspeak *self = GST_ESPEAK(handler);
- return self->uri;
-}
-
static gboolean
gst_espeak_uri_set_uri(GstURIHandler *handler, const gchar *uri)
{
@@ -357,10 +307,10 @@ gst_espeak_uri_set_uri(GstURIHandler *handler, const gchar *uri)
if (!text)
return FALSE;
- ret = gst_espeak_set_text(GST_ESPEAK(handler), text);
+ espeak_say(GST_ESPEAK(handler)->speak, text);
g_free (text);
- return ret;
+ return TRUE;
}
static void
@@ -370,7 +320,6 @@ gst_espeak_uri_handler_init(gpointer g_iface, gpointer iface_data)
iface->get_type = gst_espeak_uri_get_type;
iface->get_protocols = gst_espeak_uri_get_protocols;
- iface->get_uri = gst_espeak_uri_get_uri;
iface->set_uri = gst_espeak_uri_set_uri;
}
diff --git a/src/gstespeak.h b/src/gstespeak.h
index a0776aa..499270b 100644
--- a/src/gstespeak.h
+++ b/src/gstespeak.h
@@ -37,14 +37,12 @@ G_BEGIN_DECLS
typedef struct _GstEspeak GstEspeak;
typedef struct _GstEspeakClass GstEspeakClass;
-struct Espeak;
+struct _Espeak;
struct _GstEspeak
{
GstAudioSrc parent;
- struct Espeak *speak;
- gchar *text;
- gchar *uri;
+ struct _Espeak *speak;
guint pitch;
guint rate;
gchar *voice;
diff --git a/src/spin.c b/src/spin.c
new file mode 100644
index 0000000..8d03ca9
--- /dev/null
+++ b/src/spin.c
@@ -0,0 +1,321 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <gst/gst.h>
+
+#define SPIN_QUEUE_SIZE 2
+#define SPIN_FRAME_SIZE 128
+
+#include "text.h"
+
+typedef void (*EspeakCallBack)(const gchar*, GMemoryOutputStream*, gpointer);
+
+typedef enum
+{
+ IN = 1,
+ PROCESS = 2,
+ OUT = 4,
+ PLAY = 8
+} SpinState;
+
+typedef enum
+{
+ CLOSE = 1,
+ INPROCESS = 2
+} ContextState;
+
+typedef struct
+{
+ SpinState state;
+ Text text;
+ GMemoryOutputStream *sound;
+ gsize sound_pos;
+} Espin;
+
+typedef struct
+{
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ ContextState state;
+
+ Espin queue[SPIN_QUEUE_SIZE];
+ Espin *in;
+ Espin *process;
+ Espin *out;
+
+ GSList *in_queue;
+ GSList *process_chunk;
+ gpointer closure;
+} Econtext;
+
+static inline void
+spinning(Espin *base, Espin **i)
+{
+ if (++(*i) == base + SPIN_QUEUE_SIZE)
+ *i = base;
+}
+
+static void process_push(Econtext*);
+
+// -----------------------------------------------------------------------------
+
+Econtext*
+spin_new(gpointer closure)
+{
+ Econtext *self = g_new0(Econtext, 1);
+ gint i;
+
+ for (i = SPIN_QUEUE_SIZE; i--;)
+ {
+ self->queue[i].sound = G_MEMORY_OUTPUT_STREAM(
+ g_memory_output_stream_new(NULL, 0, realloc, free));
+ self->queue[i].state = IN;
+ }
+
+ self->in = self->queue;
+ self->process = self->queue;
+ self->out = self->queue;
+
+ self->process_chunk = g_slist_alloc();
+ self->process_chunk->data = self;
+ self->closure = closure;
+
+ pthread_mutex_init(&self->lock, NULL);
+ pthread_cond_init(&self->cond, NULL);
+
+ GST_DEBUG("[%p]", self);
+
+ return self;
+}
+
+void
+spin_unref(Econtext *self)
+{
+ GST_DEBUG("[%p]", self);
+
+ gint i;
+
+ for (i = SPIN_QUEUE_SIZE; i--;)
+ {
+ g_output_stream_close(G_OUTPUT_STREAM(self->queue[i].sound),
+ NULL, NULL);
+ g_object_unref(self->queue[i].sound);
+ }
+
+ pthread_cond_destroy(&self->cond);
+ pthread_mutex_destroy(&self->lock);
+
+ g_slist_free(self->process_chunk);
+
+ g_free(self);
+ memset(self, 0, sizeof(Econtext));
+}
+
+// in/out ----------------------------------------------------------------------
+
+void
+spin_in(Econtext *self, const gchar *str_)
+{
+ GST_DEBUG("[%p] str=%s", self, str_);
+
+ if (str_ == NULL || *str_ == 0)
+ return;
+
+ Text *str = string_new(str_);
+
+ if (self->in_queue)
+ {
+ self->in_queue = g_slist_append(self->in_queue, str);
+ return;
+ }
+
+ gsize orig_frame_len = str->frame_len;
+
+ pthread_mutex_lock(&self->lock);
+
+ while (str->frame_len && self->in->state == IN)
+ {
+ Espin *spin = self->in;
+
+ GST_DEBUG("[%p] str->offset=%ld str->frame_len=%ld "
+ "spin->text.offset=%ld spin->text.frame_len=%ld", self,
+ str->offset, str->frame_len,
+ spin->text.offset, spin->text.frame_len);
+
+ string_chunk(str, &spin->text, SPIN_FRAME_SIZE);
+ spin->state = PROCESS;
+
+ GST_DEBUG("[%p] str->offset=%ld str->frame_len=%ld "
+ "spin->text.offset=%ld spin->text.frame_len=%ld", self,
+ str->offset, str->frame_len,
+ spin->text.offset, spin->text.frame_len);
+
+ spinning(self->queue, &self->in);
+ }
+
+ if ((orig_frame_len != str->frame_len) && (self->state & INPROCESS) == 0)
+ {
+ GST_DEBUG("[%p] orig_frame_len=%ld str->len=%ld", self, orig_frame_len,
+ str->len);
+
+ self->state |= INPROCESS;
+ process_push(self);
+ }
+
+ pthread_mutex_unlock(&self->lock);
+
+ if (!string_nil(str))
+ self->in_queue = g_slist_append(self->in_queue, str);
+}
+
+gpointer
+spin_out(Econtext *self, gsize *size_to_play)
+{
+ GST_DEBUG("[%p] size_to_play=%d", self, *size_to_play);
+
+ gpointer out = NULL;
+ pthread_mutex_lock(&self->lock);
+
+ for (;;)
+ {
+ while ((self->state & CLOSE) == 0 && (self->out->state & (PLAY|OUT))
+ == 0)
+ pthread_cond_wait(&self->cond, &self->lock);
+
+ GST_DEBUG("[%p] self->state=%d self->out->state=%d", self,
+ self->state, self->out->state);
+
+ if (self->state & CLOSE)
+ break;
+
+ Espin *spin = self->out;
+ gsize spin_size = g_memory_output_stream_get_data_size(spin->sound);
+
+ if (spin->state == PLAY && spin->sound_pos >= spin_size)
+ {
+ spin->state = IN;
+ string_unref(&spin->text);
+ spinning(self->queue, &self->out);
+
+ GST_DEBUG("[%p] self->out->state=%d", self, self->out->state);
+
+ continue;
+ }
+
+ spin->state = PLAY;
+ *size_to_play = MIN(*size_to_play, spin_size);
+
+ out = (guchar*)g_memory_output_stream_get_data(spin->sound) +
+ spin->sound_pos;
+
+ spin->sound_pos += *size_to_play;
+
+ GST_DEBUG("[%p] *size_to_play=%ld spin_size=%ld tell=%ld",
+ self, *size_to_play, spin_size, spin->sound_pos);
+
+ break;
+ }
+
+ pthread_mutex_unlock(&self->lock);
+
+ return out;
+}
+
+// process ----------------------------------------------------------------------
+
+static pthread_t process_tid;
+static pthread_mutex_t process_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t process_cond = PTHREAD_COND_INITIALIZER;
+static GSList *process_queue = NULL;
+static EspeakCallBack process_espeak_cb = NULL;
+
+static void*
+process(void *data)
+{
+ pthread_mutex_lock(&process_lock);
+
+ for (;;)
+ {
+ while (process_queue == NULL)
+ pthread_cond_wait(&process_cond, &process_lock);
+
+ while (process_queue)
+ {
+ Econtext *context = (Econtext*)process_queue->data;
+ Espin *spin = context->process;
+
+ process_queue = g_slist_remove_link(process_queue, process_queue);
+ gboolean next = FALSE;
+
+ pthread_mutex_unlock(&process_lock);
+ gchar *text = spin->text.body + spin->text.offset;
+ gchar last_char = text[spin->text.frame_len];
+ text[spin->text.frame_len] = 0;
+
+ GST_DEBUG("[%p] text=%s", context, text);
+
+ g_seekable_seek(G_SEEKABLE(spin->sound), 0, G_SEEK_SET,
+ NULL, NULL);
+ process_espeak_cb(text, spin->sound, context->closure);
+ spin->sound_pos = 0;
+
+ text[spin->text.frame_len] = last_char;
+
+ pthread_mutex_lock(&context->lock);
+ spin->state = OUT;
+ spinning(context->queue, &context->process);
+ next = context->process->state == PROCESS;
+ if (!next)
+ context->state |= INPROCESS;
+ pthread_mutex_unlock(&context->lock);
+ pthread_mutex_lock(&process_lock);
+
+ if (next)
+ process_queue = g_slist_concat(process_queue,
+ context->process_chunk);
+ }
+ }
+
+ pthread_mutex_unlock(&process_lock);
+ return NULL;
+}
+
+void
+spin_init(EspeakCallBack espeak_cb)
+{
+ process_espeak_cb = espeak_cb;
+
+ pthread_attr_t attr;
+ g_assert(pthread_attr_init(&attr) == 0);
+ g_assert(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0);
+ g_assert(pthread_create(&process_tid, &attr, process, NULL) == 0);
+ g_assert(pthread_attr_destroy(&attr) == 0);
+}
+
+static void
+process_push(Econtext *context)
+{
+ pthread_mutex_lock(&process_lock);
+ process_queue = g_slist_concat(process_queue, context->process_chunk);
+ pthread_cond_signal(&process_cond);
+ pthread_mutex_unlock(&process_lock);
+}
diff --git a/src/spin.h b/src/spin.h
new file mode 100644
index 0000000..6158e52
--- /dev/null
+++ b/src/spin.h
@@ -0,0 +1,32 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SPIN_H
+#define SPIN_H
+
+struct _Econtext;
+typedef struct _Econtext Econtext;
+
+typedef void (*EspeakCallBack)(const gchar*, GMemoryOutputStream*, gpointer);
+
+void spin_init(EspeakCallBack);
+struct _Econtext* spin_new();
+void spin_unref(struct _Econtext*);
+void spin_in(struct _Econtext*, const gchar *str);
+gpointer spin_out(struct _Econtext*, gsize *size_to_play);
+
+#endif
diff --git a/src/text.h b/src/text.h
new file mode 100644
index 0000000..0261848
--- /dev/null
+++ b/src/text.h
@@ -0,0 +1,107 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef TEXT_H
+#define TEXT_H
+
+#include <string.h>
+
+typedef struct
+{
+ goffset offset;
+ gsize frame_len;
+ gsize len;
+ gchar *body;
+} Text;
+
+inline Text*
+string_new(const gchar *src)
+{
+ if (!src)
+ return NULL;
+
+ gsize len = strlen(src);
+
+ Text *out = (Text*)g_malloc(sizeof(Text) + len + 1);
+ gchar *body = (gchar*)out + sizeof(Text);
+
+ out->offset = 0;
+ out->frame_len = len;
+ out->len = len;
+ out->body = body;
+
+ memcpy(body, src, len + 1);
+
+ GST_DEBUG("[%p] len=%ld", out, len);
+
+ return out;
+}
+
+inline void
+string_chunk(Text *src, Text *dst, gsize len)
+{
+ memcpy(dst, src, sizeof(Text));
+
+ gsize dst_len = MIN(len, src->frame_len);
+ gchar *dst_last = dst->body + dst->offset + dst_len;
+ gchar *i;
+
+ if (dst_len < src->frame_len)
+ for (i = dst_last; dst_len; --dst_len, --i)
+ if (g_ascii_isspace(*i))
+ break;
+
+ if (dst_len)
+ dst->frame_len = dst_len;
+ else
+ {
+ dst_last = g_utf8_prev_char(dst_last + 1);
+ dst->frame_len = dst_last - (dst->body + dst->offset);
+ }
+
+ src->offset += dst->frame_len;
+ src->frame_len -= dst->frame_len;
+
+ GST_DEBUG("[%p] len=%ld dst_len=%ld dst_last=%ld", src, len, dst_len,
+ dst_last-dst->body);
+}
+
+inline gboolean
+string_nil(Text *str)
+{
+ return str->frame_len == 0;
+}
+
+inline void
+string_unref(Text *str)
+{
+ if (string_nil(str))
+ return;
+
+ gpointer data = NULL;
+
+ if (str->offset + str->frame_len >= str->len)
+ data = str->body - sizeof(Text);
+
+ memset(str, 0, sizeof(Text));
+
+ GST_DEBUG("[%p]", data);
+
+ g_free(data);
+}
+
+#endif