[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