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-07 05:46:04 (GMT)
committer Aleksey Lim <alsroot@member.fsf.org>2009-03-07 05:46:04 (GMT)
commit5007286d053af1f5c900e5f5333022a6707bb199 (patch)
tree269b91ca78a45d1fbccc576b5b31fe54d5f0206a
parent6f32128f565a7160599e9dbb9bde6feac3147af6 (diff)
Add word notification events
-rw-r--r--src/Makefile.am12
-rw-r--r--src/espeak.c512
-rw-r--r--src/espeak.h24
-rw-r--r--src/gstespeak.c32
-rw-r--r--src/gstespeak.h5
-rw-r--r--src/marshal.list1
-rw-r--r--src/spin.c313
-rw-r--r--src/spin.h32
-rw-r--r--src/text.h20
9 files changed, 511 insertions, 440 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index b6c9ff9..fa72f07 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,14 @@
+.list.h:
+ glib-genmarshal --prefix=espeak --header $< > $*.h
+
+.list.c:
+ glib-genmarshal --prefix=espeak --body $< > $*.c
+
+marshal.c: marshal.h
+
plugin_LTLIBRARIES = libgstespeak.la
-libgstespeak_la_SOURCES = spin.c espeak.c gstespeak.c
+libgstespeak_la_SOURCES = marshal.c marshal.h espeak.c gstespeak.c
libgstespeak_la_CFLAGS = $(GST_CFLAGS) $(GIO_CFLAGS) $(GST_AUDIO_CFLAGS)
libgstespeak_la_LIBADD = $(GST_LIBS) $(GIO_LIBS) $(GST_AUDIO_LIBS)
@@ -9,3 +17,5 @@ libgstespeak_la_LIBTOOLFLAGS = --tag=disable-static
# headers we need but don't want installed
noinst_HEADERS = gstespeak.h espeak.h
+
+CLEANFILES = marshal.c marshal.h
diff --git a/src/espeak.c b/src/espeak.c
index f49cd6d..8212af5 100644
--- a/src/espeak.c
+++ b/src/espeak.c
@@ -20,90 +20,406 @@
#include <string.h>
#include <glib.h>
#include <gio/gio.h>
-#include <espeak/speak_lib.h>
#include <gst/gst.h>
+#include <espeak/speak_lib.h>
+
+#define SYNC_BUFFER_SIZE 4096
+
+#define SPIN_QUEUE_SIZE 2
+#define SPIN_FRAME_SIZE 256
-#include "spin.h"
#include "espeak.h"
+#include "text.h"
+
+typedef enum
+{
+ IN = 1,
+ PROCESS = 2,
+ OUT = 4,
+ PLAY = 8
+} SpinState;
-struct _Espeak
+typedef enum
{
- Econtext *context;
- guint rate;
- guint pitch;
- const gchar *voice;
+ CLOSE = 1,
+ INPROCESS = 2
+} ContextState;
+
+typedef struct
+{
+ volatile SpinState state;
+
+ Text text;
+
+ GMemoryOutputStream *sound;
+ goffset sound_offset;
+
+ GArray *events;
+ goffset events_pos;
+} Espin;
+
+struct _Econtext
+{
+ volatile ContextState state;
+
+ Espin queue[SPIN_QUEUE_SIZE];
+ Espin *in;
+ Espin *process;
+ Espin *out;
+
+ GSList *in_queue;
+ GSList *process_chunk;
+
+ volatile gint rate;
+ volatile gint pitch;
+ volatile const gchar *voice;
};
+static inline void
+spinning(Espin *base, Espin **i)
+{
+ if (++(*i) == base + SPIN_QUEUE_SIZE)
+ *i = base;
+}
+
+static void init();
+static void process_push(Econtext*);
+static void process_pop(Econtext*);
+
+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 gint espeak_sample_rate = 0;
static const espeak_VOICE **espeak_voices = NULL;
static GOutputStream *espeak_buffer = NULL;
+static GArray *espeak_events = NULL;
-static gint
-synth_cb(short * data, int numsamples, espeak_EVENT * events)
+// -----------------------------------------------------------------------------
+
+Econtext*
+espeak_new()
{
- if (data == NULL)
- return 0;
+ init();
- if (numsamples > 0)
- g_output_stream_write(espeak_buffer, data, numsamples*2, NULL, NULL);
+ Econtext *self = g_new0(Econtext, 1);
+ gint i;
- GST_DEBUG("numsamples=%d data_size=%ld", numsamples*2,
- g_memory_output_stream_get_data_size(G_MEMORY_OUTPUT_STREAM(
- espeak_buffer)));
+ for (i = SPIN_QUEUE_SIZE; i--;)
+ {
+ self->queue[i].state = IN;
+ self->queue[i].sound = G_MEMORY_OUTPUT_STREAM(
+ g_memory_output_stream_new(NULL, 0, realloc, free));
+ self->queue[i].events = g_array_new(FALSE, FALSE, sizeof(espeak_EVENT));
+ }
- return 0;
+ self->in = self->queue;
+ self->process = self->queue;
+ self->out = self->queue;
+
+ self->process_chunk = g_slist_alloc();
+ self->process_chunk->data = self;
+
+ self->pitch = ESPEAK_DEFAULT_PITCH;
+ self->rate = ESPEAK_DEFAULT_RATE;
+ self->voice = ESPEAK_DEFAULT_VOICE;
+
+ GST_DEBUG("[%p]", self);
+
+ return self;
}
-static void
-synth(const gchar *text, GMemoryOutputStream *sound, gpointer self_)
+void
+espeak_unref(Econtext *self)
{
- Espeak *self = (Espeak*)self_;
+ GST_DEBUG("[%p]", self);
- espeak_SetParameter(espeakPITCH, self->pitch, 0);
- espeak_SetParameter(espeakRATE, self->rate, 0);
- espeak_SetVoiceByName(self->voice);
- espeak_buffer = G_OUTPUT_STREAM(sound);
+ g_atomic_int_set(&self->state, g_atomic_int_get(&self->state) | CLOSE);
+ process_pop(self);
- espeak_Synth(text, strlen(text)+1, 0, POS_WORD, 0, espeakCHARS_UTF8,
- NULL, NULL);
+ 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);
+ text_unref(&self->queue[i].text);
+ g_array_free(self->queue[i].events, TRUE);
+ }
+
+ if (self->in_queue)
+ {
+ GSList *i;
+ for (i = self->in_queue; i; i = g_slist_next(i))
+ text_unref(i->data);
+ g_slist_free(self->in_queue);
+ }
+
+ g_slist_free(self->process_chunk);
+
+ memset(self, 0, sizeof(Econtext));
+ g_free(self);
}
+// in/out ----------------------------------------------------------------------
+
static void
-init()
+in_spinning(Econtext *self, Text *text)
{
- static volatile gsize initialized = 0;
+ GST_DEBUG("[%p] text.body=%s text.offset=%ld text.frame_len=%ld",
+ self, text->body, text->offset, text->frame_len);
- if (initialized == 0)
+ gboolean chunked = FALSE;
+
+ while (!text_eot(text) && g_atomic_int_get(&self->in->state) == IN)
{
- ++initialized;
- espeak_sample_rate = espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 4096,
- NULL, 0);
- espeak_SetSynthCallback(synth_cb);
- espeak_voices = espeak_ListVoices(NULL);
- spin_init(synth);
+ Espin *spin = self->in;
+ text_chunk(text, &spin->text, SPIN_FRAME_SIZE);
+ g_atomic_int_set(&spin->state, PROCESS);
+ spinning(self->queue, &self->in);
+ chunked = TRUE;
}
+
+ int self_status = g_atomic_int_get(&self->state);
+
+ if (chunked && (self_status & INPROCESS) == 0)
+ {
+ g_atomic_int_set(&self->state, self_status | INPROCESS);
+ process_push(self);
+ }
+
+ GST_DEBUG("[%p] text.body=%s text.offset=%ld text.frame_len=%ld",
+ self, text->body, text->offset, text->frame_len);
}
-Espeak*
-espeak_new()
+void
+espeak_in(Econtext *self, const gchar *str_)
{
- init();
+ GST_DEBUG("[%p] str=%s", self, str_);
- 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;
+ if (str_ == NULL || *str_ == 0)
+ return;
- return self;
+ Text *text = text_new(str_);
+
+ if (self->in_queue)
+ {
+ self->in_queue = g_slist_append(self->in_queue, text);
+ return;
+ }
+
+ in_spinning(self, text);
+
+ if (!text_eot(text))
+ {
+ GST_DEBUG("[%p] text_len=%d", self, text_len(text));
+ self->in_queue = g_slist_append(self->in_queue, text);
+ }
}
-void
-espeak_unref(Espeak *self)
+GstBuffer*
+play(Espin *spin, gsize size_to_play, gpointer emitter)
{
- spin_unref(self->context);
- memset(self, 0, sizeof(Espeak));
- g_free(self);
+ inline gsize whole(Espin *spin, gsize size_to_play)
+ {
+ gsize spin_size = g_memory_output_stream_get_data_size(spin->sound);
+ return MIN(size_to_play, spin_size);
+ }
+
+ inline gsize words(Espin *spin, gsize size_to_play, gpointer emitter)
+ {
+ gsize spin_size = g_memory_output_stream_get_data_size(spin->sound);
+ size_to_play = MIN(size_to_play, spin_size);
+
+ GST_DEBUG("spin_size=%ld size_to_play=%ld spin->events_pos=%ld",
+ spin_size, size_to_play, spin->events_pos);
+
+ goffset event;
+ goffset sample_offset = 0;
+ goffset text_offset = -1;
+ gsize text_len = 0;
+
+ for (event = spin->events_pos; TRUE; ++event)
+ {
+ espeak_EVENT *i = &g_array_index(spin->events, espeak_EVENT, event);
+
+ if (i->type == espeakEVENT_LIST_TERMINATED)
+ {
+ GST_DEBUG("i->sample=%d", i->sample*2);
+ sample_offset = spin_size;
+ break;
+ }
+ else if (i->type == espeakEVENT_WORD)
+ {
+ sample_offset = i[1].sample*2;
+ text_offset = spin->text.offset + i->text_position - 1;
+ text_len = i->length;
+
+ GST_DEBUG("sample_offset=%d txt_offset=%d txt_len=%d, txt=%s",
+ sample_offset, text_offset, text_len,
+ spin->text.body + text_offset);
+ break;
+ }
+ }
+
+ if (sample_offset - spin->sound_offset > size_to_play)
+ {
+ GST_DEBUG("sample_offset=%ld spin->sound_offset=%ld",
+ sample_offset, spin->sound_offset);
+ return size_to_play;
+ }
+
+ if (text_offset != -1)
+ {
+ GST_DEBUG("event=%ld", event);
+ g_signal_emit_by_name(emitter, "word",
+ text_offset, text_len, G_TYPE_NONE);
+ spin->events_pos = event + 1;
+ }
+
+ return sample_offset - spin->sound_offset;
+ }
+
+ g_atomic_int_set(&spin->state, PLAY);
+
+ if (emitter)
+ size_to_play = words(spin, size_to_play, emitter);
+ else
+ size_to_play = whole(spin, size_to_play);
+
+ GstBuffer *out = gst_buffer_new();
+ GST_BUFFER_DATA(out) =
+ (guchar*)g_memory_output_stream_get_data(spin->sound) +
+ spin->sound_offset;
+ GST_BUFFER_SIZE(out) = size_to_play;
+
+ spin->sound_offset += size_to_play;
+
+ GST_DEBUG("size_to_play=%ld tell=%ld", size_to_play, spin->sound_offset);
+
+ return out;
+}
+
+GstBuffer*
+espeak_out(Econtext *self, gsize size_to_play, gpointer emitter)
+{
+ GST_DEBUG("[%p] size_to_play=%d", self, size_to_play);
+
+ for (;;)
+ {
+ pthread_mutex_lock(&process_lock);
+ while ((g_atomic_int_get(&self->state) & CLOSE) == 0 &&
+ (g_atomic_int_get(&self->out->state) & (PLAY|OUT)) == 0)
+ pthread_cond_wait(&process_cond, &process_lock);
+ pthread_mutex_unlock(&process_lock);
+
+ if (g_atomic_int_get(&self->state) & CLOSE)
+ {
+ GST_DEBUG("[%p]", self);
+ return NULL;
+ }
+
+ Espin *spin = self->out;
+ gsize spin_size = g_memory_output_stream_get_data_size(spin->sound);
+
+ GST_DEBUG("[%p] spin->sound_offset=%ld spin_size=%ld", self,
+ spin->sound_offset, spin_size);
+
+ if (g_atomic_int_get(&spin->state) == PLAY &&
+ spin->sound_offset >= spin_size)
+ {
+ g_atomic_int_set(&spin->state, IN);
+ text_unref(&spin->text);
+ spinning(self->queue, &self->out);
+
+ if (self->in_queue)
+ {
+ Text *text = self->in_queue->data;
+ in_spinning(self, text);
+
+ if (text_eot(text))
+ {
+ self->in_queue = g_slist_delete_link(self->in_queue,
+ self->in_queue);
+ GST_DEBUG("[%p] in_queue=%d", self,
+ g_slist_length(self->in_queue));
+ }
+ }
+
+ GST_DEBUG("[%p]", self);
+
+ continue;
+ }
+
+ return play(spin, size_to_play, emitter);
+ }
+
+ return NULL;
+}
+
+// espeak ----------------------------------------------------------------------
+
+static gint
+synth_cb(short *data, int numsamples, espeak_EVENT *events)
+{
+ if (data == NULL)
+ return 0;
+
+ if (numsamples > 0)
+ {
+ g_output_stream_write(espeak_buffer, data, numsamples*2, NULL, NULL);
+
+ for (; events->type != espeakEVENT_LIST_TERMINATED; ++events)
+ {
+ GST_DEBUG("type=%d text_position=%d length=%d "
+ "audio_position=%d sample=%d",
+ events->type, events->text_position, events->length,
+ events->audio_position, events->sample*2);
+ g_array_append_val(espeak_events, *events);
+ }
+ }
+
+ 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(Econtext *self, Espin *spin)
+{
+ gchar *text = text_first(&spin->text);
+ gchar *last = text_last(&spin->text);
+
+ gchar old_last_char = *last;
+ *last = 0;
+
+ GST_DEBUG("[%p] text='%s' last=%d", self, text, last-text);
+
+ g_seekable_seek(G_SEEKABLE(spin->sound), 0, G_SEEK_SET,
+ NULL, NULL);
+ g_array_set_size(spin->events, 0);
+ spin->sound_offset = 0;
+ spin->events_pos = 0;
+
+ espeak_SetParameter(espeakPITCH, g_atomic_int_get(&self->pitch), 0);
+ espeak_SetParameter(espeakRATE, g_atomic_int_get(&self->rate), 0);
+ espeak_SetVoiceByName((gchar*)g_atomic_pointer_get(&self->voice));
+ espeak_buffer = G_OUTPUT_STREAM(spin->sound);
+ espeak_events = spin->events;
+
+ espeak_Synth(text, text_len(&spin->text), 0, POS_WORD, 0,
+ espeakCHARS_UTF8|espeakPHONEMES, NULL, NULL);
+
+ espeak_EVENT last_event = { espeakEVENT_LIST_TERMINATED };
+ last_event.sample = g_memory_output_stream_get_data_size(spin->sound) / 2;
+ g_array_append_val(spin->events, last_event);
+ *last = old_last_char;
}
gint
@@ -130,31 +446,105 @@ espeak_get_voices()
}
void
-espeak_set_pitch(Espeak *self, guint value)
+espeak_set_pitch(Econtext *self, guint value)
{
- self->pitch = value;
+ g_atomic_int_set(&self->pitch, value);
}
void
-espeak_set_rate(Espeak *self, guint value)
+espeak_set_rate(Econtext *self, guint value)
{
- self->rate = value;
+ g_atomic_int_set(&self->rate, value);
}
void
-espeak_set_voice(Espeak *self, const gchar *value)
+espeak_set_voice(Econtext *self, const gchar *value)
{
- self->voice = value;
+ g_atomic_pointer_set(&self->voice, value);
}
-void
-espeak_say(Espeak *self, const gchar *text)
+// process ----------------------------------------------------------------------
+
+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);
+
+ synth(context, spin);
+
+ g_atomic_int_set(&spin->state, OUT);
+ spinning(context->queue, &context->process);
+
+ if (g_atomic_int_get(&context->process->state) == PROCESS)
+ {
+ GST_DEBUG("[%p]", context);
+ process_queue = g_slist_concat(process_queue,
+ context->process_chunk);
+ }
+ else
+ {
+ GST_DEBUG("[%p]", context);
+ g_atomic_int_set(&context->state,
+ g_atomic_int_get(&context->state) & ~INPROCESS);
+ }
+ }
+
+ pthread_cond_broadcast(&process_cond);
+ }
+
+ pthread_mutex_unlock(&process_lock);
+
+ return NULL;
+}
+
+static void
+process_push(Econtext *context)
{
- spin_in(self->context, text);
+ pthread_mutex_lock(&process_lock);
+ process_queue = g_slist_concat(process_queue, context->process_chunk);
+ pthread_cond_broadcast(&process_cond);
+ pthread_mutex_unlock(&process_lock);
}
-gpointer
-espeak_hear(Espeak *self, gsize size)
+static void
+process_pop(Econtext *context)
{
- return spin_out(self->context, &size);
+ pthread_mutex_lock(&process_lock);
+ process_queue = g_slist_remove_link(process_queue, context->process_chunk);
+ pthread_mutex_unlock(&process_lock);
+}
+
+// -----------------------------------------------------------------------------
+
+static void
+init()
+{
+ static volatile gsize initialized = 0;
+
+ if (initialized == 0)
+ {
+ ++initialized;
+ espeak_sample_rate = espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS,
+ SYNC_BUFFER_SIZE, NULL, 0);
+ espeak_SetSynthCallback(synth_cb);
+ espeak_voices = espeak_ListVoices(NULL);
+
+ 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);
+ }
}
diff --git a/src/espeak.h b/src/espeak.h
index b0e3f65..e4f7d15 100644
--- a/src/espeak.h
+++ b/src/espeak.h
@@ -22,17 +22,19 @@
#define ESPEAK_DEFAULT_RATE 170
#define ESPEAK_DEFAULT_VOICE "default"
-struct _Espeak;
-typedef struct _Espeak Espeak;
+struct _Econtext;
+typedef struct _Econtext Econtext;
-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);
+Econtext* espeak_new();
+void espeak_unref(Econtext*);
+
+gint espeak_get_sample_rate();
+gchar** espeak_get_voices();
+void espeak_set_pitch(Econtext*, guint);
+void espeak_set_rate(Econtext*, guint);
+void espeak_set_voice(Econtext*, const gchar*);
+
+void espeak_in(Econtext*, const gchar *str);
+GstBuffer* espeak_out(Econtext*, gsize size_to_play, gpointer emitter);
#endif
diff --git a/src/gstespeak.c b/src/gstespeak.c
index 8f5d8e5..221abf4 100644
--- a/src/gstespeak.c
+++ b/src/gstespeak.c
@@ -37,12 +37,19 @@
#include "gstespeak.h"
#include "espeak.h"
+#include "marshal.h"
GST_DEBUG_CATEGORY_STATIC (gst_espeak_debug);
#define GST_CAT_DEFAULT gst_espeak_debug
enum
{
+ SIGNAL_WORD,
+ LAST_SIGNAL
+};
+
+enum
+{
PROP_0,
PROP_TEXT,
PROP_PITCH,
@@ -74,6 +81,8 @@ static GstCaps *gst_espeak_getcaps(GstBaseSrc*);
GST_BOILERPLATE_FULL(GstEspeak, gst_espeak, GstBaseSrc, GST_TYPE_BASE_SRC,
gst_espeak_init_uri);
+static gint signals[LAST_SIGNAL];
+
/******************************************************************************/
static void
@@ -133,6 +142,12 @@ gst_espeak_class_init (GstEspeakClass * klass)
g_param_spec_boxed ("caps", "Caps",
"Caps describing the format of the data.", GST_TYPE_CAPS,
G_PARAM_READABLE));
+
+ signals[SIGNAL_WORD] = g_signal_new("word",
+ G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET(GstEspeakClass, word),
+ NULL, NULL, espeak_VOID__UINT_UINT, G_TYPE_NONE,
+ 2, G_TYPE_UINT, G_TYPE_UINT);
}
/* initialize the new element
@@ -183,7 +198,7 @@ gst_espeak_set_property(GObject *object, guint prop_id,
switch (prop_id) {
case PROP_TEXT:
- espeak_say(self->speak, g_value_get_string(value));
+ espeak_in(self->speak, g_value_get_string(value));
break;
case PROP_PITCH:
self->pitch = g_value_get_uint(value);
@@ -238,17 +253,8 @@ gst_espeak_create(GstBaseSrc * self_, guint64 offset, guint size,
GstBuffer **buf)
{
GstEspeak *self = GST_ESPEAK(self_);
-
- gpointer sound = espeak_hear(self->speak, size);
-
- if (sound == NULL)
- return GST_FLOW_UNEXPECTED;
-
- *buf = gst_buffer_new();
- GST_BUFFER_DATA(*buf) = sound;
- GST_BUFFER_SIZE(*buf) = size;
-
- return GST_FLOW_OK;
+ *buf = espeak_out(self->speak, size, self);
+ return *buf ? GST_FLOW_OK : GST_FLOW_UNEXPECTED;
}
static gboolean
@@ -307,7 +313,7 @@ gst_espeak_uri_set_uri(GstURIHandler *handler, const gchar *uri)
if (!text)
return FALSE;
- espeak_say(GST_ESPEAK(handler)->speak, text);
+ espeak_in(GST_ESPEAK(handler)->speak, text);
g_free (text);
return TRUE;
diff --git a/src/gstespeak.h b/src/gstespeak.h
index 499270b..31986f4 100644
--- a/src/gstespeak.h
+++ b/src/gstespeak.h
@@ -37,12 +37,12 @@ G_BEGIN_DECLS
typedef struct _GstEspeak GstEspeak;
typedef struct _GstEspeakClass GstEspeakClass;
-struct _Espeak;
+struct _Econtext;
struct _GstEspeak
{
GstAudioSrc parent;
- struct _Espeak *speak;
+ struct _Econtext *speak;
guint pitch;
guint rate;
gchar *voice;
@@ -53,6 +53,7 @@ struct _GstEspeak
struct _GstEspeakClass
{
GstAudioSrcClass parent_class;
+ void (*word) (GstEspeak*, goffset, gsize);
};
GType gst_espeak_get_type (void);
diff --git a/src/marshal.list b/src/marshal.list
new file mode 100644
index 0000000..6cb7431
--- /dev/null
+++ b/src/marshal.list
@@ -0,0 +1 @@
+VOID:UINT,UINT
diff --git a/src/spin.c b/src/spin.c
deleted file mode 100644
index 3a51031..0000000
--- a/src/spin.c
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * 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 = text_new(str_);
-
- if (self->in_queue)
- {
- self->in_queue = g_slist_append(self->in_queue, str);
- return;
- }
-
- gboolean chunked = FALSE;
-
- pthread_mutex_lock(&self->lock);
-
- while (!text_eot(str) && self->in->state == IN)
- {
- Espin *spin = self->in;
- text_chunk(str, &spin->text, SPIN_FRAME_SIZE);
- spin->state = PROCESS;
- spinning(self->queue, &self->in);
- chunked = TRUE;
- }
-
- if (chunked && (self->state & INPROCESS) == 0)
- {
- self->state |= INPROCESS;
- process_push(self);
- }
-
- pthread_mutex_unlock(&self->lock);
-
- if (!text_eot(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;
- text_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 = text_first(&spin->text);
- gchar *last = text_last(&spin->text);
-
- gchar last_char = *last;
- *last = 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;
-
- *last = 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;
- GST_DEBUG("[%p] context->state=%d", context,
- context->state);
- }
- 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
deleted file mode 100644
index 6158e52..0000000
--- a/src/spin.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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
index 445875a..0a83985 100644
--- a/src/text.h
+++ b/src/text.h
@@ -94,27 +94,33 @@ text_last(Text *self)
}
inline gboolean
-text_eot(Text *str)
+text_eot(Text *self)
{
- return str->frame_len == 0;
+ return self->frame_len == 0;
}
inline void
-text_unref(Text *str)
+text_unref(Text *self)
{
- if (text_eot(str))
+ if (text_eot(self))
return;
gpointer data = NULL;
- if (str->offset + str->frame_len >= str->len)
- data = str->body - sizeof(Text);
+ if (self->offset + self->frame_len >= self->len)
+ data = self->body - sizeof(Text);
- memset(str, 0, sizeof(Text));
+ memset(self, 0, sizeof(Text));
GST_DEBUG("[%p]", data);
g_free(data);
}
+inline gsize
+text_len(Text *self)
+{
+ return self->frame_len;
+}
+
#endif