blob: a51b2c613f40268ddca878d4201a9143c2e3f82c [file] [log] [blame]
/*
* cicero.c - Speech Dispatcher backend for Cicero French TTS engine
*
* Copyright (C) 2006 Brailcom, o.p.s.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This software 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @author: Olivier BERT
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <speechd_types.h>
#include <safe_io.h>
#include <errno.h>
#include <poll.h>
#include <fcntl.h>
#include <langinfo.h>
#include <sys/stat.h>
#include <semaphore.h>
#include "module_utils.h"
#define MODULE_NAME "cicero"
#define MODULE_VERSION "0.3"
// #define DEBUG_MODULE 1
DECLARE_DEBUG()
static int initialized = 0;
/* Thread and process control */
static int cicero_speaking = 0;
static pthread_t cicero_speaking_thread;
static sem_t *cicero_semaphore;
static char *cicero_message;
static SPDMessageType cicero_message_type;
static int cicero_position = 0;
static int cicero_pause_requested = 0;
signed int cicero_volume = 0;
static unsigned int CiceroMaxChunkLength = 500;
/* Internal functions prototypes */
static void cicero_set_rate(signed int rate);
static void *_cicero_speak(void *);
int cicero_stop = 0;
/*
** Config file options
*/
//MOD_OPTION_1_STR(CiceroWrapper);
MOD_OPTION_1_STR(CiceroExecutable)
MOD_OPTION_1_STR(CiceroExecutableLog)
/*
** Pipes to cicero
*/
static int fd1[2], fd2[2];
/*
** Some internal functions
*/
static long int
millisecondsBetween(const struct timeval *from, const struct timeval *to)
{
return ((to->tv_sec - from->tv_sec) * 1000) +
((to->tv_usec - from->tv_usec)
/ 1000);
}
long int millisecondsSince(const struct timeval *from)
{
struct timeval now;
gettimeofday(&now, NULL);
return millisecondsBetween(from, &now);
}
static int hasTimedOut(int milliseconds)
{
static struct timeval start = { 0, 0 };
if (milliseconds)
return millisecondsSince(&start) >= milliseconds;
gettimeofday(&start, NULL);
return 1;
}
static void mywrite(int fd, const void *buf, int len)
{
char *pos = (char *)buf;
int w;
if (fd < 0)
return;
hasTimedOut(0);
do {
if ((w = write(fd, pos, len)) < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
else if (errno == EPIPE) {
DBG("Broken pipe\n");
} else
perror("Pipe write");
return;
}
pos += w;
len -= w;
} while (len && !hasTimedOut(600));
if (len)
fprintf(stderr, "Pipe write timed out");
}
/* Public functions */
int module_load(void)
{
INIT_SETTINGS_TABLES();
REGISTER_DEBUG();
MOD_OPTION_1_STR_REG(CiceroExecutable, "/usr/bin/cicero");
MOD_OPTION_1_STR_REG(CiceroExecutableLog,
"/var/log/speech-dispatcher/cicero-executable.log");
return 0;
}
int module_init(char **status_info)
{
int ret;
int stderr_redirect;
DBG("Module init\n");
if (access(CiceroExecutable, X_OK) != 0) {
*status_info = g_strdup_printf("can not reach executable %s", CiceroExecutable);
DBG("ERROR: can not reach executable %s", CiceroExecutable);
return -1;
}
(void)signal(SIGPIPE, SIG_IGN);
DBG("call the pipe system call\n");
if (pipe(fd1) < 0 || pipe(fd2) < 0) {
*status_info = g_strdup("Could not create pipe");
DBG("Error pipe()\n");
return -1;
}
DBG("Call fork system call\n");
stderr_redirect = open(CiceroExecutableLog,
O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (stderr_redirect == -1) {
DBG("ERROR: Opening debug file for Cicero binary failed: (error=%d) %s", stderr_redirect, strerror(errno));
} else {
DBG("Cicero synthesizer logging to file %s",
CiceroExecutableLog);
}
switch (fork()) {
case -1:{
if (stderr_redirect >= 0)
close(stderr_redirect);
*status_info = g_strdup("Could not fork");
DBG("Error fork()\n");
return -1;
}
case 0:{
if (dup2(fd2[0], 0) < 0 /* stdin */
|| dup2(fd1[1], 1) < 0) { /* stdout */
DBG("Error dup2()\n");
exit(1);
}
if (stderr_redirect >= 0) {
if (dup2(stderr_redirect, 2) < 0)
DBG("ERROR: Couldn't redirect stderr, not logging for Cicero synthesizer.");
}
/*close(2);
close(fd2[1]);
close(fd1[0]); */
int i = 0;
for (i = 3; i < 256; i++)
close(i);
(void)signal(SIGPIPE, SIG_IGN);
execl(CiceroExecutable, CiceroExecutable, (void *)NULL);
DBG("Error execl()\n");
exit(1);
}
default:{
if (stderr_redirect >= 0)
close(stderr_redirect);
close(fd1[1]);
close(fd2[0]);
if (fcntl(fd2[1], F_SETFL, O_NDELAY) < 0
|| fcntl(fd1[0], F_SETFL, O_NDELAY) < 0) {
DBG("Error fcntl()\n");
return -1;
}
}
}
cicero_message = NULL;
char name[64];
snprintf(name, sizeof(name), "/speechd-modules-cicero-%d", getpid());
cicero_semaphore = sem_open(name, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 0);
sem_unlink(name);
DBG("Cicero: creating new thread for cicero_tracking\n");
cicero_speaking = 0;
ret =
spd_pthread_create(&cicero_speaking_thread, NULL, _cicero_speak, NULL);
if (ret != 0) {
DBG("Cicero: thread failed\n");
*status_info =
g_strdup("The module couldn't initialize threads "
"This can be either an internal problem or an "
"architecture problem. If you are sure your architecture "
"supports threads, please report a bug.");
return -1;
}
*status_info = g_strdup("Cicero initialized successfully.");
initialized = 1;
return 0;
}
SPDVoice **module_list_voices(void)
{
static SPDVoice voice = {
.name = "cicero",
.language = "fr",
};
static SPDVoice *voices[] = { &voice, NULL };
return voices;
}
int module_speak(gchar * data, size_t bytes, SPDMessageType msgtype)
{
DBG("Module speak\n");
/* The following should not happen */
if (cicero_speaking) {
DBG("Speaking when requested to write");
return 0;
}
DBG("Requested data: |%s|\n", data);
if (cicero_message != NULL) {
g_free(cicero_message);
cicero_message = NULL;
}
cicero_message = module_strip_ssml(data);
/* TODO: use a generic engine for SPELL, CHAR, KEY */
cicero_message_type = SPD_MSGTYPE_TEXT;
/* Setting voice */
/* UPDATE_PARAMETER(voice, cicero_set_voice); */
UPDATE_PARAMETER(rate, cicero_set_rate);
/* UPDATE_PARAMETER(pitch, cicero_set_pitch); */
/* Send semaphore signal to the speaking thread */
cicero_speaking = 1;
sem_post(cicero_semaphore);
DBG("Cicero: leaving module_speak() normally\n\r");
return 1;
}
int module_stop(void)
{
unsigned char c = 1;
DBG("cicero: stop()\n");
cicero_stop = 1;
mywrite(fd2[1], &c, 1);
return 0;
}
size_t module_pause(void)
{
DBG("pause requested\n");
if (cicero_speaking) {
DBG("Pause not supported by cicero\n");
cicero_pause_requested = 1;
module_stop();
return -1;
}
cicero_pause_requested = 0;
return 0;
}
int module_close(void)
{
DBG("cicero: close()\n");
if (cicero_speaking) {
module_stop();
}
if (!initialized)
return 0;
if (module_terminate_thread(cicero_speaking_thread) != 0)
return -1;
sem_close(cicero_semaphore);
initialized = 0;
return 0;
}
/* Internal functions */
void *_cicero_speak(void *nothing)
{
char stop_code = 1;
unsigned int pos = 0, inx = 0, len = 0;
int flag = 0;
int bytes;
int ret;
char buf[CiceroMaxChunkLength], l[5], b[2];
struct pollfd ufds = { fd1[0], POLLIN | POLLPRI, 0 };
spd_pthread_setname("_cicero_speak");
DBG("cicero: speaking thread starting.......\n");
/* Make interruptible */
set_speaking_thread_parameters();
while (1) {
sem_wait(cicero_semaphore);
DBG("Semaphore on\n");
len = strlen(cicero_message);
cicero_stop = 0;
cicero_speaking = 1;
cicero_position = 0;
pos = 0;
module_report_event_begin();
while (1) {
flag = 0;
if (cicero_stop) {
DBG("Stop in thread, terminating");
cicero_speaking = 0;
module_report_event_stop();
break;
}
if (pos >= len) { /* end of text */
DBG("End of text in speaking thread\n");
module_report_event_end();
cicero_speaking = 0;
break;
}
DBG("Call get_parts: pos=%d, msg=\"%s\" \n", pos,
cicero_message);
bytes =
module_get_message_part(cicero_message, buf, &pos,
CiceroMaxChunkLength,
".;?!");
DBG("Returned %d bytes from get_part\n", bytes);
if (bytes < 0) {
DBG("ERROR: Can't get message part, terminating");
cicero_speaking = 0;
module_report_event_stop();
break;
}
buf[bytes] = 0;
DBG("Text to synthesize is '%s'\n", buf);
if (bytes > 0) {
DBG("Speaking ...");
DBG("Trying to synthesize text");
l[0] = 4; /* say code for UTF-8 data */
l[1] = bytes >> 8;
l[2] = bytes & 0xFF;
l[3] = 0, l[4] = 0;
mywrite(fd2[1], &stop_code, 1);
mywrite(fd2[1], l, 5);
mywrite(fd2[1], buf, bytes);
cicero_position = 0;
while (1) {
ret = poll(&ufds, 1, 60);
if (ret)
DBG("poll() system call returned %d, events=%d\n", ret, ufds.events);
if (ret < 0) {
perror("poll");
module_report_event_stop();
flag = 1;
cicero_speaking = 0;
break;
}
if (ret > 0)
safe_read(fd1[0], b, 2);
if (cicero_stop) {
cicero_speaking = 0;
module_report_event_stop();
flag = 1;
break;
}
if (ret == 0)
continue;
inx = (b[0] << 8 | b[1]);
DBG("Tracking: index=%u, bytes=%d\n",
inx, bytes);
if (inx == bytes) {
cicero_speaking = 0;
break;
} else {
if (inx)
cicero_position = inx;
}
}
} else {
cicero_speaking = 0;
break;
}
if (flag)
break;
}
cicero_stop = 0;
}
cicero_speaking = 0;
DBG("cicero: tracking thread ended.......\n");
pthread_exit(NULL);
}
static void cicero_set_rate(signed int rate)
{
const float spkRateTable[] = {
0.3333,
0.3720,
0.4152,
0.4635,
0.5173,
0.5774,
0.6444,
0.7192,
0.8027,
0.8960,
1.0000,
1.1161,
1.2457,
1.3904,
1.5518,
1.7320,
1.9332,
2.1577,
2.4082,
2.6879,
3.0000
};
float expand;
rate -= 100;
rate = abs(rate);
rate /= 10;
expand = spkRateTable[rate];
unsigned char *p = (unsigned char *)&expand;
unsigned char l[5];
l[0] = 3;
l[1] = p[3];
l[2] = p[2];
l[3] = p[1];
l[4] = p[0];
mywrite(fd2[1], l, 5);
}