From 5007286d053af1f5c900e5f5333022a6707bb199 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Sat, 07 Mar 2009 05:46:04 +0000 Subject: Add word notification events --- 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 #include #include -#include #include +#include + +#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 -#include -#include -#include -#include -#include - -#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 -- cgit v0.9.1