From b956b2b089a7515a917ee91eb11aa815f3bb46bd Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Fri, 06 Mar 2009 07:59:49 +0000 Subject: Use round-robin queue to synch; process text by chunks --- 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 #include #include +#include -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 +#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 = 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 + +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 -- cgit v0.9.1