[BRLTTY] threaded eSpeak driver
Nicolas Pitre
nico at fluxnic.net
Wed Aug 15 17:29:35 EDT 2012
While playing with BRLTTY's keyboard support and speech output together
on my laptop, I've noticed some typed letters appear to be repeated
twicee or even tttrice which is very annoying. The culprit appears to
be the espeak library which might not return quickly enough, and by
doing so is preventing BRLTTY's keyboard key handling code from running
in time to filter and pass along released key events back to the kernel
before the kernel decides to trigger its own key auto-repeat mechanism.
This issue can be mitigated greatly by making the eSpeak driver speech
processing call into a thread of its own. Attached is a patch to make
the eSpeak driver threaded.
Is this enough to ensure the repeated key problem will never happen
again? Maybe the Linux keyboard filter code would benefit from being
moved into a thread of its own as well.
Nicolas
-------------- next part --------------
diff --git a/Drivers/Speech/eSpeak/Makefile.in b/Drivers/Speech/eSpeak/Makefile.in
index 821a1c1..69bc58d 100644
--- a/Drivers/Speech/eSpeak/Makefile.in
+++ b/Drivers/Speech/eSpeak/Makefile.in
@@ -19,7 +19,7 @@
DRIVER_CODE = es
DRIVER_NAME = eSpeak
DRIVER_COMMENT = text to speech engine
-DRIVER_VERSION = 0.2
+DRIVER_VERSION = 0.3
DRIVER_DEVELOPERS = Nicolas Pitre <nico at fluxnic.net>
SPK_OBJS = @speech_libraries_es@
include $(SRC_TOP)speech.mk
diff --git a/Drivers/Speech/eSpeak/speech.c b/Drivers/Speech/eSpeak/speech.c
index 695633a..267f229 100644
--- a/Drivers/Speech/eSpeak/speech.c
+++ b/Drivers/Speech/eSpeak/speech.c
@@ -22,6 +22,10 @@
#include <stdlib.h>
#include <string.h>
+#ifdef HAVE_POSIX_THREADS
+#include <pthread.h>
+#endif
+
#include "log.h"
#include "parse.h"
@@ -48,79 +52,98 @@ typedef enum {
#endif
static int maxrate = espeakRATE_MAXIMUM;
-static int IndexPos;
+static volatile int IndexPos;
-static int SynthCallback(short *audio, int numsamples, espeak_EVENT *events)
+#ifdef HAVE_POSIX_THREADS
+
+static pthread_t request_thread;
+static pthread_mutex_t queue_lock;
+static pthread_cond_t queue_cond;
+static volatile int alive;
+
+struct request {
+ struct request *next;
+ unsigned buflength;
+ unsigned char buffer[0];
+};
+
+static struct request *request_queue;
+
+static void free_queue(void)
{
- while (events->type != espeakEVENT_LIST_TERMINATED) {
- if (events->type == espeakEVENT_WORD)
- IndexPos = events->text_position - 1;
- events++;
+ struct request *curr = request_queue;
+ request_queue = NULL;
+ while (curr) {
+ struct request *next = curr->next;
+ free(curr);
+ curr = next;
}
- return 0;
}
-static int spk_construct(SpeechSynthesizer *spk, char **parameters)
+static void *process_request(void *unused)
{
- char *data_path, *voicename, *punctlist;
+ struct request *curr;
int result;
- logMessage(LOG_INFO, "eSpeak version %s", espeak_Info(NULL));
-
- data_path = parameters[PARM_PATH];
- if (data_path && !*data_path)
- data_path = NULL;
- result = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, 0);
- if (result < 0) {
- logMessage(LOG_ERR, "eSpeak: initialization failed");
- return 0;
+ pthread_mutex_lock(&queue_lock);
+ while (alive) {
+ curr = request_queue;
+ if (!curr) {
+ pthread_cond_wait(&queue_cond, &queue_lock);
+ continue;
+ }
+ request_queue = curr->next;
+ pthread_mutex_unlock(&queue_lock);
+ if (curr->buflength) {
+ result = espeak_Synth(curr->buffer, curr->buflength, 0,
+ POS_CHARACTER, 0, espeakCHARS_UTF8,
+ NULL, NULL);
+ if (result != EE_OK)
+ logMessage(LOG_ERR, "eSpeak: Synth() returned error %d", result);
+ } else {
+ espeak_Cancel();
+ }
+ free(curr);
+ pthread_mutex_lock(&queue_lock);
}
+ pthread_mutex_unlock(&queue_lock);
- voicename = parameters[PARM_VOICE];
- if(!voicename || !*voicename)
- voicename = "default";
- result = espeak_SetVoiceByName(voicename);
- if (result != EE_OK) {
- espeak_VOICE voice_select;
- memset(&voice_select, 0, sizeof(voice_select));
- voice_select.languages = voicename;
- result = espeak_SetVoiceByProperties(&voice_select);
- }
- if (result != EE_OK) {
- logMessage(LOG_ERR, "eSpeak: unable to load voice '%s'", voicename);
- return 0;
- }
-
- punctlist = parameters[PARM_PUNCTLIST];
- if (punctlist && *punctlist) {
- wchar_t w_punctlist[strlen(punctlist) + 1];
- int i = 0;
- while ((w_punctlist[i] = punctlist[i]) != 0) i++;
- espeak_SetPunctuationList(w_punctlist);
- }
-
- if (parameters[PARM_MAXRATE]) {
- int val = atoi(parameters[PARM_MAXRATE]);
- if (val > espeakRATE_MINIMUM) maxrate = val;
- }
-
- espeak_SetSynthCallback(SynthCallback);
-
- return 1;
+ return NULL;
}
-static void spk_destruct(SpeechSynthesizer *spk)
-{
- espeak_Cancel();
- espeak_Synchronize();
- espeak_Terminate();
-}
+#endif /* HAVE_POSIX_THREADS */
static void
spk_say(SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes)
{
+#ifdef HAVE_POSIX_THREADS
+ struct request *new, **prev_next;
+
+ IndexPos = 0;
+
+ if (!length)
+ return;
+
+ new = malloc(sizeof(*new) + length + 1);
+ if (!new) {
+ logSystemError("eSpeak: malloc");
+ return;
+ }
+ new->next = NULL;
+ new->buflength = length + 1;
+ memcpy(new->buffer, buffer, length);
+ new->buffer[length] = 0;
+
+ pthread_mutex_lock(&queue_lock);
+ prev_next = &request_queue;
+ while (*prev_next)
+ prev_next = &(*prev_next)->next;
+ *prev_next = new;
+ pthread_cond_signal(&queue_cond);
+ pthread_mutex_unlock(&queue_lock);
+#else
int result;
-
+
IndexPos = 0;
/* add 1 to the length in order to pass along the trailing zero */
@@ -128,12 +151,38 @@ spk_say(SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size
espeakCHARS_UTF8, NULL, NULL);
if (result != EE_OK)
logMessage(LOG_ERR, "eSpeak: Synth() returned error %d", result);
+#endif
}
static void
spk_mute(SpeechSynthesizer *spk)
{
+#ifdef HAVE_POSIX_THREADS
+ struct request *stop = malloc(sizeof(*stop));
+ if (!stop) {
+ logSystemError("eSpeak: malloc");
+ return;
+ }
+ stop->next = NULL;
+ stop->buflength = 0;
+ pthread_mutex_lock(&queue_lock);
+ free_queue();
+ request_queue = stop;
+ pthread_cond_signal(&queue_cond);
+ pthread_mutex_unlock(&queue_lock);
+#else
espeak_Cancel();
+#endif /* HAVE_POSIX_THREADS */
+}
+
+static int SynthCallback(short *audio, int numsamples, espeak_EVENT *events)
+{
+ while (events->type != espeakEVENT_LIST_TERMINATED) {
+ if (events->type == espeakEVENT_WORD)
+ IndexPos = events->text_position - 1;
+ events++;
+ }
+ return 0;
}
static void
@@ -187,3 +236,86 @@ spk_setPunctuation(SpeechSynthesizer *spk, SpeechPunctuation setting)
punct = espeakPUNCT_SOME;
espeak_SetParameter(espeakPUNCTUATION, punct, 0);
}
+
+static int spk_construct(SpeechSynthesizer *spk, char **parameters)
+{
+ char *data_path, *voicename, *punctlist;
+ int result;
+
+ logMessage(LOG_INFO, "eSpeak version %s", espeak_Info(NULL));
+
+ data_path = parameters[PARM_PATH];
+ if (data_path && !*data_path)
+ data_path = NULL;
+ result = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, 0);
+ if (result < 0) {
+ logMessage(LOG_ERR, "eSpeak: initialization failed");
+ return 0;
+ }
+
+ voicename = parameters[PARM_VOICE];
+ if(!voicename || !*voicename)
+ voicename = "default";
+ result = espeak_SetVoiceByName(voicename);
+ if (result != EE_OK) {
+ espeak_VOICE voice_select;
+ memset(&voice_select, 0, sizeof(voice_select));
+ voice_select.languages = voicename;
+ result = espeak_SetVoiceByProperties(&voice_select);
+ }
+ if (result != EE_OK) {
+ logMessage(LOG_ERR, "eSpeak: unable to load voice '%s'", voicename);
+ return 0;
+ }
+
+ punctlist = parameters[PARM_PUNCTLIST];
+ if (punctlist && *punctlist) {
+ wchar_t w_punctlist[strlen(punctlist) + 1];
+ int i = 0;
+ while ((w_punctlist[i] = punctlist[i]) != 0) i++;
+ espeak_SetPunctuationList(w_punctlist);
+ }
+
+ if (parameters[PARM_MAXRATE]) {
+ int val = atoi(parameters[PARM_MAXRATE]);
+ if (val > espeakRATE_MINIMUM) maxrate = val;
+ }
+
+ espeak_SetSynthCallback(SynthCallback);
+
+#ifdef HAVE_POSIX_THREADS
+
+ pthread_mutex_init(&queue_lock, NULL);
+ pthread_cond_init(&queue_cond, NULL);
+
+ alive = 1;
+ result = pthread_create(&request_thread, NULL, process_request, NULL);
+ if (result) {
+ logMessage(LOG_ERR, "eSpeak: unable to create thread");
+ espeak_Terminate();
+ pthread_mutex_destroy(&queue_lock);
+ pthread_cond_destroy(&queue_cond);
+ return 0;
+ }
+
+#endif /* HAVE_POSIX_THREADS */
+
+ return 1;
+}
+
+static void spk_destruct(SpeechSynthesizer *spk)
+{
+#ifdef HAVE_POSIX_THREADS
+ pthread_mutex_lock(&queue_lock);
+ alive = 0;
+ pthread_cond_signal(&queue_cond);
+ pthread_mutex_unlock(&queue_lock);
+ pthread_join(request_thread, NULL);
+ pthread_mutex_destroy(&queue_lock);
+ pthread_cond_destroy(&queue_cond);
+ free_queue();
+#endif /* HAVE_POSIX_THREADS */
+
+ espeak_Cancel();
+ espeak_Terminate();
+}
More information about the BRLTTY
mailing list