blob: 554a89d60934ecad310aff3751e48627ed16f4ea [file] [log] [blame] [edit]
/*
* output.c - Output layer for Speech Dispatcher
*
* Copyright (C) 2001, 2002, 2003, 2007 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/>.
*
* $Id: output.c,v 1.38 2008-06-27 12:28:48 hanke Exp $
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fdsetconv.h>
#include <safe_io.h>
#include "output.h"
#include "parse.h"
#include "speak_queue.h"
#include "index_marking.h"
#ifndef HAVE_STRNDUP
/*
* Added by Willie Walker - strndup was a GNU libc extension, later adopted
* in the POSIX.1-2008 standard, but not yet found on all systems.
*/
char *strndup(const char *s, size_t n)
{
size_t nAvail;
char *p;
if (!s)
return 0;
if (strlen(s) > n)
nAvail = n + 1;
else
nAvail = strlen(s) + 1;
p = g_malloc(nAvail);
memcpy(p, s, nAvail);
p[nAvail - 1] = '\0';
return p;
}
#endif /* HAVE_STRNDUP */
static pthread_t output_thread;
static void *output_thread_func(void *data);
static int output_end_queued;
static int output_stop_requested;
static int output_pause_requested;
static int output_pause_queued;
static void output_open_audio(OutputModule *output)
{
void *pars[9] = { NULL };
char min_length[11];
char *error, *first_error = 0;
gchar **outputs;
int i;
pars[0] = GlobalFDSet.audio_oss_device;
pars[1] = GlobalFDSet.audio_alsa_device;
pars[2] = GlobalFDSet.audio_nas_server;
pars[3] = GlobalFDSet.audio_pulse_device;
snprintf(min_length, sizeof(min_length), "%u", GlobalFDSet.audio_pulse_min_length);
pars[4] = min_length;
pars[5] = output->name;
outputs = g_strsplit(GlobalFDSet.audio_output_method, ",", 0);
for (i = 0; NULL != outputs[i]; i++) {
output->audio =
spd_audio_open(outputs[i], pars, &error);
if (output->audio) {
DBG("Using %s audio output method", outputs[i]);
g_strfreev(outputs);
spd_audio_set_loglevel(output->audio, SpeechdOptions.log_level);
/* Volume is controlled by the synthesizer. Always play at normal on audio device. */
if (spd_audio_set_volume(output->audio, 85) < 0) {
DBG("Can't set volume. audio not initialized?");
}
g_free(first_error);
return;
}
DBG("Can't use %s: %s", outputs[i], error);
if (!first_error)
first_error = error;
else
g_free(error);
}
MSG(1, "Opening audio failed: %s\n", first_error);
g_free(first_error);
g_strfreev(outputs);
}
void output_set_speaking_monitor(TSpeechDMessage * msg, OutputModule * output)
{
/* Set the speaking-monitor so that we know who is speaking */
speaking_module = output;
if (output->audio) {
if (output->audio == AUDIOID_TOOPEN)
output_open_audio(output);
module_audio_id = output->audio;
}
speaking_uid = msg->settings.uid;
speaking_gid = msg->settings.reparted;
}
OutputModule *get_output_module_by_name(const char *name)
{
OutputModule *output;
int i;
for (i = 0; i < g_list_length(output_modules); i++) {
output = g_list_nth_data(output_modules, i);
if (!strcmp(output->name, name)) {
if (output->working)
return output;
else
return NULL;
}
}
return NULL;
}
OutputModule *get_some_output_module_by_name(const char *name)
{
OutputModule *output = NULL;
int i, len;
if (name != NULL) {
MSG(5, "Desired output module is %s", name);
output = get_output_module_by_name(name);
if ((output != NULL) && output->working)
return output;
}
MSG(3, "Warning: Didn't find preferred output module, using default");
// If the requested module was not found or is not working,
// first try to use the default output module
if (GlobalFDSet.output_module != NULL)
output = get_output_module_by_name(GlobalFDSet.output_module);
if (output != NULL && output->working)
return output;
MSG(3, "Couldn't load default output module, trying other modules");
/* Try all other output modules other than dummy */
len = g_list_length(output_modules);
for (i = 0; i < len; i++) {
output = g_list_nth_data(output_modules, i);
if (0 == strcmp(output->name, "dummy"))
continue;
if (output->working) {
MSG(3, "Output module %s seems to be working, using it",
output->name);
return output;
}
}
// if we get here there are no good modules use the dummy
// a pre-synthesized error message with some hints over and over).
if (output == NULL || !output->working)
output = get_output_module_by_name("dummy");
return output;
}
/* get_output_module tries to return a pointer to the
appropriate output module according to message context.
If it is not possible to find the required module,
it will subsequently try to get the default module,
any of the other remaining modules except dummy and
at last, the dummy output module.
Only if not even dummy output module is working
(serious issues), it will log an error message and return
a NULL pointer.
*/
OutputModule *get_output_module(const TSpeechDMessage * message)
{
OutputModule *output;
output = get_some_output_module_by_name(message->settings.output_module);
// Give up....
if (output == NULL)
MSG(1,
"Error: No output module working, not even dummy, no sound produced!\n");
return output;
}
/*
* Note: commands are send to the output modules both from the clients and from
* the speaking thread. They both use output_lock/unlock around sending a
* command and getting a reply, to avoid getting the reply for each other.
*
* During speech, the output module sends asynchronous events (marks,
* audio). This is handled along the way. We however need them to be handled
* also when there is no client or speaking command. We thus also start a thread
* that merely processes them.
*/
static int oldstate;
void
static output_lock(void)
{
if (pthread_self() == speak_thread)
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
pthread_mutex_lock(&output_layer_mutex);
}
void
static output_unlock(void)
{
pthread_mutex_unlock(&output_layer_mutex);
if (pthread_self() == speak_thread)
pthread_setcancelstate(oldstate, NULL);
}
#define OL_RET(value) \
do { output_unlock(); \
return (value); } while (0)
GString *output_read_message(OutputModule * output)
{
GString *rstr;
int bytes;
char *line = NULL;
size_t N = 0;
gboolean errors = FALSE;
rstr = g_string_new("");
/* Wait for activity on the socket, when there is some,
read all the message line by line */
do {
bytes = getline(&line, &N, output->stream_out);
if (bytes == -1) {
MSG(2, "Error: Broken pipe to module while reading message.");
output->working = 0;
output_check_module(output);
errors = TRUE; /* Broken pipe */
} else {
MSG(5, "Got %d bytes from output module over socket",
bytes);
g_string_append_len(rstr, line, bytes);
}
/* terminate if we reached the last line (without '-' after numcode) */
} while (!errors && !((strlen(line) < 4) || (line[3] == ' ')));
if (errors)
MSG(5, "Finished reading message, with errors");
else
MSG(5, "Finished reading message");
if (line != NULL)
free(line);
if (errors) {
g_string_free(rstr, TRUE);
rstr = NULL;
}
return rstr;
}
/*
* Read a message.
* If read_events is 1, we only return event messages
* If read_events is 0, we only return non-event messages
*/
/* This is run by the main thread, to get command replies. */
GString *output_read_reply(OutputModule * output)
{
GString *message = NULL;
pthread_mutex_lock(&output->read_mutex);
while (!message) {
while (output->reading_message && !output->reply)
/* Somebody reading events, wait for it */
pthread_cond_wait(&output->reply_cond, &output->read_mutex);
if (output->reply) {
/* The other thread got a message for us, consume it */
message = output->reply;
output->reply = NULL;
/* And tell the other thread we got it */
pthread_cond_signal(&output->event_cond);
break;
}
if (!output->reading_message) {
/* Nobody reading, do read */
/* We will be reading */
output->reading_message = TRUE;
pthread_mutex_unlock(&output->read_mutex);
message = output_read_message(output);
pthread_mutex_lock(&output->read_mutex);
output->reading_message = FALSE;
pthread_cond_signal(&output->reply_cond);
if (!message)
/* Module broke */
break;
if (message->str[0] == '7') {
if (!output->reading_events) {
MSG(2, "unexpected event |%s|", message->str);
g_string_free(message, TRUE);
message = NULL;
continue;
}
/* An event, leave it up to the event thread */
output->event = message;
message = NULL;
/* Tell it to consume it */
pthread_cond_signal(&output->event_cond);
/* Wait for it to consume it */
while (output->event && output->reading_events)
pthread_cond_wait(&output->reply_cond, &output->read_mutex);
if (output->event && !output->reading_events)
{
MSG(2, "eventually unexpected event |%s|", message->str);
g_string_free(output->event, TRUE);
output->event = NULL;
continue;
}
}
}
}
pthread_mutex_unlock(&output->read_mutex);
return message;
}
static void output_start_reading_events(OutputModule * output)
{
pthread_mutex_lock(&output->read_mutex);
output->reading_events = TRUE;
pthread_mutex_unlock(&output->read_mutex);
}
/* This is run by the module output thread during speech, to process events. */
static GString *output_read_event(OutputModule * output)
{
GString *message = NULL;
pthread_mutex_lock(&output->read_mutex);
while (!message) {
while (output->reading_message && !output->event)
/* Somebody reading replies, wait for it */
pthread_cond_wait(&output->event_cond, &output->read_mutex);
if (output->event) {
/* The other thread got a message for us, consume it */
message = output->event;
output->event = NULL;
/* And tell the other thread we got it */
pthread_cond_signal(&output->reply_cond);
break;
}
if (!output->reading_message) {
/* Nobody reading, do read */
/* We will be reading */
output->reading_message = TRUE;
pthread_mutex_unlock(&output->read_mutex);
message = output_read_message(output);
pthread_mutex_lock(&output->read_mutex);
output->reading_message = FALSE;
pthread_cond_signal(&output->reply_cond);
if (!message)
/* Module broke */
break;
if (message->str[0] != '7') {
if (!output->waiting_for_reply) {
MSG(2, "unexpected reply |%s|", message->str);
g_string_free(message, TRUE);
/* Module broke */
message = NULL;
break;
}
/* A reply, leave it up to the reply thread */
output->reply = message;
message = NULL;
/* Tell it to consume it */
pthread_cond_signal(&output->reply_cond);
/* Wait for it to consume it */
while (output->reply)
pthread_cond_wait(&output->event_cond, &output->read_mutex);
}
}
}
pthread_mutex_unlock(&output->read_mutex);
return message;
}
static void output_stop_reading_events(OutputModule * output)
{
pthread_mutex_lock(&output->read_mutex);
if (output->event)
{
g_string_free(output->event, TRUE);
output->event = NULL;
}
output->reading_events = FALSE;
pthread_cond_signal(&output->reply_cond);
pthread_mutex_unlock(&output->read_mutex);
}
int output_send_data(const char *cmd, OutputModule * output, int wfr)
{
int ret;
GString *response;
if (output == NULL)
return -1;
if (cmd == NULL)
return -1;
if (wfr) {
pthread_mutex_lock(&output->read_mutex);
output->waiting_for_reply = TRUE;
pthread_mutex_unlock(&output->read_mutex);
}
ret = safe_write(output->pipe_in[1], cmd, strlen(cmd));
if (ret == -1) {
MSG(2, "Error: Broken pipe to module while sending data.");
if (wfr) {
pthread_mutex_lock(&output->read_mutex);
output->waiting_for_reply = FALSE;
pthread_mutex_unlock(&output->read_mutex);
}
output->working = 0;
output_check_module(output);
return -1; /* Broken pipe */
}
MSG2(5, "output_module", "Command sent to output module: |%s| (%d)",
cmd, wfr);
if (wfr) { /* wait for reply? */
int ret = 0;
response = output_read_reply(output);
pthread_mutex_lock(&output->read_mutex);
output->waiting_for_reply = FALSE;
pthread_mutex_unlock(&output->read_mutex);
if (response == NULL)
return -1;
MSG2(5, "output_module", "Reply from output module: |%s|",
response->str);
switch (response->str[0]) {
case '3':
MSG(2,
"Error: Module reported error in request from speechd (code 3xx): %s.",
response->str);
ret = -2; /* User (speechd) side error */
break;
case '4':
MSG(2,
"Error: Module reported error in itself (code 4xx): %s",
response->str);
ret = -3; /* Module side error */
break;
case '2':
ret = 0;
break;
default: /* unknown response */
MSG(3, "Unknown response from output module!");
ret = -3;
break;
}
g_string_free(response, TRUE);
return ret;
}
return 0;
}
static void free_voice(gpointer data)
{
SPDVoice *voice = (SPDVoice *)data;
if (voice != NULL) {
if (voice->name != NULL)
g_free(voice->name);
if (voice->language != NULL)
g_free(voice->language);
if (voice->variant != NULL)
g_free(voice->variant);
g_free(voice);
}
}
SPDVoice **output_get_voices(OutputModule * output, const char *language, const char *variant)
{
SPDVoice **voice_dscr;
SPDVoice *voice;
GString *reply;
gchar **lines;
gchar **atoms;
GQueue *voices;
int i;
int numvoices = 0;
gboolean errors = FALSE;
int err;
char *command;
char *all_command = "LIST VOICES\n";
output_lock();
if (output == NULL) {
MSG(1, "ERROR: Can't list voices for broken output module");
OL_RET(NULL);
}
command = g_strdup_printf("LIST VOICES%s%s%s%s\n",
language ? " " : "",
language ? language : "",
language && variant ? " " : "",
variant ? variant : "");
retry:
pthread_mutex_lock(&output->read_mutex);
output->waiting_for_reply = TRUE;
pthread_mutex_unlock(&output->read_mutex);
err = output_send_data(command, output, 0);
if (command != all_command)
free(command);
if (err < 0) {
pthread_mutex_lock(&output->read_mutex);
output->waiting_for_reply = FALSE;
pthread_mutex_unlock(&output->read_mutex);
output_unlock();
return NULL;
}
reply = output_read_reply(output);
pthread_mutex_lock(&output->read_mutex);
output->waiting_for_reply = FALSE;
pthread_mutex_unlock(&output->read_mutex);
if (reply == NULL) {
output_unlock();
return NULL;
}
lines = g_strsplit(reply->str, "\n", -1);
g_string_free(reply, TRUE);
if (!strncmp(lines[0], "300", 3) && command != all_command) {
/* Old module that doesn't support filtering? Try to get it all
* instead */
g_strfreev(lines);
command = all_command;
goto retry;
}
voices = g_queue_new();
for (i = 0; !errors && (lines[i] != NULL); i++) {
MSG(1, "LINE here:|%s|", lines[i]);
if (strlen(lines[i]) <= 4) {
MSG(1,
"ERROR: Bad communication from driver in synth_voices");
errors = TRUE;
} else if (lines[i][3] == ' ')
break;
else if (lines[i][3] == '-') {
atoms = g_strsplit(&lines[i][4], "\t", 0);
// Name, language, variant
if ((atoms[0] == NULL) || (atoms[1] == NULL)
|| (atoms[2] == NULL)) {
errors = TRUE;
} else {
//Fill in VoiceDescription
voice = g_malloc(sizeof(SPDVoice));
voice->name = g_strdup(atoms[0]);
voice->language = g_strdup(atoms[1]);
voice->variant = g_strdup(atoms[2]);
g_queue_push_tail(voices, voice);
}
g_strfreev(atoms);
}
/* Should we do something in a final "else" branch? */
}
numvoices = g_queue_get_length(voices);
if (errors == TRUE) {
g_queue_free_full(voices, (GDestroyNotify)free_voice);
g_strfreev(lines);
output_unlock();
return NULL;
}
voice_dscr = g_malloc((numvoices + 1) * sizeof(SPDVoice *));
for (i = 0; i < numvoices; i++) {
voice_dscr[i] = g_queue_pop_head(voices);
}
voice_dscr[i] = NULL;
g_queue_free(voices);
g_strfreev(lines);
output_unlock();
return voice_dscr;
}
SPDVoice **output_list_voices(const char *module_name, const char *language, const char *variant)
{
OutputModule *module = get_some_output_module_by_name(module_name);
if (module == NULL) {
MSG(1, "ERROR: Can't list voices for module %s", module_name ? module_name : "default");
return NULL;
}
return output_get_voices(module, language, variant);
}
#define SEND_CMD_N(cmd) \
do { \
err = output_send_data(cmd"\n", output, 1); \
if (err < 0) { \
g_string_free(set_str, 1); \
return (err); \
} \
} while (0)
#define SEND_CMD(cmd) \
do { err = output_send_data(cmd"\n", output, 1); \
if (err < 0) OL_RET(err); } while (0)
#define SEND_DATA_N(data) \
do { \
err = output_send_data(data, output, 0); \
if (err < 0) { \
g_string_free(set_str, 1); \
return (err); \
} \
} while (0)
#define SEND_DATA(data) \
do { err = output_send_data(data, output, 0); \
if (err < 0) OL_RET(err); } while (0)
#define SEND_CMD_GET_VALUE(data) \
do { err = output_send_data(data"\n", output, 1); \
OL_RET(err); } while (0)
#define ADD_SET_INT(name) \
g_string_append_printf(set_str, #name"=%d\n", msg->settings.name)
#define ADD_SET_STR(name) \
do { \
if (msg->settings.name != NULL && msg->settings.name[0] != '\0') { \
g_string_append_printf(set_str, #name"=%s\n", msg->settings.name); \
}else{ \
g_string_append_printf(set_str, #name"=NULL\n"); \
} \
} while (0)
#define ADD_SET_STR_C(name, fconv) \
do { \
val = fconv(msg->settings.msg_settings.name); \
if (val != NULL && val[0] != '\0'){ \
g_string_append_printf(set_str, #name"=%s\n", val); \
} \
g_free(val); \
} while (0)
int output_send_settings(TSpeechDMessage * msg, OutputModule * output)
{
GString *set_str;
char *val;
int err;
MSG(4, "Module set parameters.");
set_str = g_string_new("");
g_string_append_printf(set_str, "pitch=%d\n",
msg->settings.msg_settings.pitch);
g_string_append_printf(set_str, "pitch_range=%d\n",
msg->settings.msg_settings.pitch_range);
g_string_append_printf(set_str, "rate=%d\n",
msg->settings.msg_settings.rate);
g_string_append_printf(set_str, "volume=%d\n",
msg->settings.msg_settings.volume);
ADD_SET_STR_C(punctuation_mode, EPunctMode2str);
ADD_SET_STR_C(spelling_mode, ESpellMode2str);
ADD_SET_STR_C(cap_let_recogn, ECapLetRecogn2str);
val = EVoice2str(msg->settings.msg_settings.voice_type);
if (val != NULL && val[0] != '\0') {
g_string_append_printf(set_str, "voice=%s\n", val);
}
g_free(val);
if (msg->settings.msg_settings.voice.language != NULL
&& msg->settings.msg_settings.voice.language[0] != '\0') {
g_string_append_printf(set_str, "language=%s\n",
msg->settings.msg_settings.voice.
language);
} else {
g_string_append_printf(set_str, "language=NULL\n");
}
if (msg->settings.msg_settings.voice.name != NULL
&& msg->settings.msg_settings.voice.name[0] != '\0') {
g_string_append_printf(set_str, "synthesis_voice=%s\n",
msg->settings.msg_settings.voice.name);
} else {
g_string_append_printf(set_str, "synthesis_voice=NULL\n");
}
SEND_CMD_N("SET");
SEND_DATA_N(set_str->str);
SEND_CMD_N(".");
g_string_free(set_str, 1);
return 0;
}
#undef ADD_SET_INT
#undef ADD_SET_STR
#define ADD_SET_INT(name) \
g_string_append_printf(set_str, #name"=%d\n", GlobalFDSet.name)
#define ADD_SET_STR(name) \
do { \
if (GlobalFDSet.name != NULL){ \
g_string_append_printf(set_str, #name"=%s\n", GlobalFDSet.name); \
}else{ \
g_string_append_printf(set_str, #name"=NULL\n"); \
} \
} while (0)
static int output_server_audio(OutputModule * output)
{
GString *set_str;
int err;
MSG(4, "Module set parameters.");
set_str = g_string_new("");
g_string_append_printf(set_str, "audio_output_method=server\n");
SEND_CMD_N("AUDIO");
SEND_DATA_N(set_str->str);
SEND_CMD_N(".");
g_string_free(set_str, 1);
output->audio = AUDIOID_TOOPEN;
MSG(3, "Initialized for server audio for %s\n", output->name);
return 0;
}
int output_send_audio_settings(OutputModule * output)
{
GString *set_str;
int err;
/* First try to get output through server */
MSG(4, "Trying to make output module use audio output through server.");
if (output_server_audio(output) == 0)
/* Went fine, good! */
return 0;
MSG(4, "Output module does not support audio output through server, making it open audio by itself.");
output->audio = NULL;
MSG(4, "Module set parameters.");
set_str = g_string_new("");
ADD_SET_STR(audio_output_method);
ADD_SET_STR(audio_oss_device);
ADD_SET_STR(audio_alsa_device);
ADD_SET_STR(audio_nas_server);
// TODO: restore AudioPulseServer option
//ADD_SET_STR(audio_pulse_server);
ADD_SET_STR(audio_pulse_device);
ADD_SET_INT(audio_pulse_min_length);
SEND_CMD_N("AUDIO");
SEND_DATA_N(set_str->str);
SEND_CMD_N(".");
g_string_free(set_str, 1);
return 0;
}
int output_send_loglevel_setting(OutputModule * output)
{
GString *set_str;
int err;
MSG(4, "Module set parameters.");
set_str = g_string_new("");
ADD_SET_INT(log_level);
SEND_CMD_N("LOGLEVEL");
SEND_DATA_N(set_str->str);
SEND_CMD_N(".");
g_string_free(set_str, 1);
return 0;
}
#undef ADD_SET_INT
#undef ADD_SET_STR
int output_send_debug(OutputModule * output, int flag, const char *log_path)
{
char *cmd_str;
int err;
MSG(4, "Module sending debug flag %d with file %s", flag, log_path);
output_lock();
if (flag) {
cmd_str = g_strdup_printf("DEBUG ON %s \n", log_path);
err = output_send_data(cmd_str, output, 1);
g_free(cmd_str);
if (err) {
MSG(3,
"ERROR: Can't set debugging on for output module %s",
output->name);
OL_RET(-1);
}
} else {
err = output_send_data("DEBUG OFF \n", output, 1);
if (err) {
MSG(3,
"ERROR: Can't switch debugging off for output module %s",
output->name);
OL_RET(-1);
}
}
OL_RET(0);
}
int output_speak(TSpeechDMessage * msg, OutputModule *output)
{
int err;
int ret;
char *newbuf;
if (msg == NULL)
return -1;
output_lock();
newbuf = escape_dot(msg->buf);
if (newbuf != msg->buf) {
g_free(msg->buf);
msg->buf = newbuf;
}
msg->bytes = -1;
output_set_speaking_monitor(msg, output);
if (module_audio_id) {
if (!module_speak_queue_before_synth()) {
MSG(3, "Warning: couldn't begin speak queue");
}
}
ret = output_send_settings(msg, output);
if (ret != 0)
OL_RET(ret);
MSG(4, "Module speak!");
switch (msg->settings.type) {
case SPD_MSGTYPE_TEXT:
SEND_CMD("SPEAK"); break;
case SPD_MSGTYPE_SOUND_ICON:
SEND_CMD("SOUND_ICON");
break;
case SPD_MSGTYPE_CHAR:
SEND_CMD("CHAR");
break;
case SPD_MSGTYPE_KEY:
SEND_CMD("KEY");
break;
default:
MSG(2, "Invalid message type in output_speak()!");
}
if (!strcmp(msg->buf, " "))
SEND_DATA("space");
else
SEND_DATA(msg->buf);
SEND_CMD("\n.");
/* Start a thread that will process the module events */
output_end_queued = 0;
output_stop_requested = 0;
output_pause_requested = 0;
output_pause_queued = 0;
output_start_reading_events(output);
spd_pthread_create(&output_thread, NULL, output_thread_func, output);
output_unlock();
return 0;
}
int output_stop()
{
int err;
OutputModule *output;
output_lock();
if (speaking_module == NULL)
OL_RET(0);
else
output = speaking_module;
if (output->audio)
{
if (output_end_queued) {
MSG(4, "module is already done, stop speak_queue directly");
module_speak_queue_stop();
OL_RET(0);
}
MSG(4, "stopping speak_queue");
output_stop_requested = 1;
module_speak_queue_flush();
}
MSG(4, "Module stop!");
SEND_DATA("STOP\n");
OL_RET(0);
}
size_t output_pause()
{
static int err;
static OutputModule *output;
output_lock();
if (speaking_module == NULL)
OL_RET(0);
else
output = speaking_module;
if (output->audio)
{
if (output_end_queued) {
MSG(4, "module is already done, pause speak_queue directly");
module_speak_queue_pause();
OL_RET(0);
}
MSG(4, "pausing speak_queue");
output_pause_requested = 1;
}
MSG(4, "Module pause!");
SEND_DATA("PAUSE\n");
OL_RET(0);
}
static GSList *playback_events = NULL;
static pthread_mutex_t playback_events_mutex = PTHREAD_MUTEX_INITIALIZER;
static speak_queue_entry *output_new_event(speak_queue_entry_type type)
{
speak_queue_entry *entry = g_new(speak_queue_entry, 1);
entry->type = type;
return entry;
}
static void output_queue_event(speak_queue_entry *entry)
{
char c = 0;
int ret;
/* Get the output module. This is done so that we have a local
* copy of the output module as 'speaking_module' could be set
* to NULL in another thread */
OutputModule *output = speaking_module;
if (output == NULL)
{
// We were cancelled
return;
}
pthread_mutex_lock(&playback_events_mutex);
playback_events = g_slist_append(playback_events, entry);
pthread_mutex_unlock(&playback_events_mutex);
ret = write(output->pipe_speak[1], &c, 1);
if (ret != 1)
MSG(1, "Warning: couldn't write to pipe_speak: %d returned, (errno = %d, %s)\n", ret, errno, strerror(errno));
}
static void output_queue_new_event(speak_queue_entry_type type)
{
speak_queue_entry *entry = output_new_event(type);
output_queue_event(entry);
}
void module_report_index_mark(const char *mark)
{
speak_queue_entry *entry = output_new_event(SPEAK_QUEUE_QET_INDEX_MARK);
entry->data.markId = g_strdup(mark);
output_queue_event(entry);
}
void module_report_event_begin(void)
{
output_queue_new_event(SPEAK_QUEUE_QET_BEGIN);
}
void module_report_event_end(void)
{
output_queue_new_event(SPEAK_QUEUE_QET_END);
}
void module_report_event_broken(void)
{
output_queue_new_event(SPEAK_QUEUE_QET_BROKEN);
}
void module_report_event_stop(void)
{
output_queue_new_event(SPEAK_QUEUE_QET_STOP);
}
void module_report_event_pause(void)
{
output_queue_new_event(SPEAK_QUEUE_QET_PAUSE);
}
void module_speak_queue_cancel(void)
{
/* Not needed */
}
static int output_module_is_speaking(OutputModule * output)
{
GString *response;
int retcode = -1;
MSG(5, "output_module_is_speaking()");
if (output == NULL) {
MSG(5, "output==NULL in output_module_is_speaking()");
module_report_event_broken();
return -1;
}
response = output_read_event(output);
if (response == NULL) {
module_report_event_broken();
return -1;
}
MSG2(5, "output_module", "Event from output module while speaking: |%s|",
response->str);
if (response->len < 4) {
MSG2(2, "output_module",
"Error: Wrong communication from output module! Event less than four bytes.");
g_string_free(response, TRUE);
module_report_event_broken();
return -1;
}
retcode = 1;
MSG2(5, "output_module", "Received event:\n %s", response->str);
if (!strncmp(response->str, "701", 3))
{
MSG2(5, "output_module", "got begin");
if (output->audio) {
if (!module_speak_queue_before_play())
MSG(3, "Warning: couldn't add begin to speak queue");
} else {
module_report_event_begin();
}
}
else if (!strncmp(response->str, "702", 3))
{
MSG2(5, "output_module", "got end");
if (output->audio) {
if (output_stop_requested) {
MSG(4, "we sent STOP too late, now tell the speak queue");
module_speak_queue_stop();
} else if (output_pause_requested) {
MSG(4, "we sent PAUSE too late, now tell the speak queue");
if (!output_pause_queued)
module_speak_queue_pause();
if (!module_speak_queue_add_end())
MSG(3, "Warning: couldn't add end to speak queue");
} else {
if (!module_speak_queue_add_end())
MSG(3, "Warning: couldn't add end to speak queue");
/* module is done, if stop is requested we'll have to
* tell speak_queue directly */
output_end_queued = 1;
}
} else {
module_report_event_end();
}
retcode = 0;
}
else if (!strncmp(response->str, "703", 3))
{
MSG2(5, "output_module", "got stopped");
if (output->audio) {
if (!output_pause_queued)
module_speak_queue_stop();
}
else
module_report_event_stop();
retcode = 0;
}
else if (!strncmp(response->str, "704", 3))
{
MSG2(5, "output_module", "got paused");
if (output->audio) {
if (!output_pause_queued)
module_speak_queue_pause();
if (!module_speak_queue_add_end())
MSG(3, "Warning: couldn't add end to speak queue");
} else
module_report_event_pause();
retcode = 0;
}
else if (!strncmp(response->str, "700", 3))
{
char *p, *index_mark;
p = strchr(response->str, '\n');
MSG2(5, "output_module", "response:|%s|\n p:|%s|",
response->str, p);
index_mark =
(char *)strndup(response->str + 4,
p - response->str - 4);
MSG2(5, "output_module", "Detected INDEX MARK: %s",
index_mark);
if (output->audio) {
if (!(output_stop_requested || (output_pause_requested && output_pause_queued))) {
if (!module_speak_queue_add_mark(index_mark))
MSG(3, "Warning: couldn't add mark to speak queue");
if (output_pause_requested &&
!strncmp(index_mark, SD_MARK_BODY, SD_MARK_BODY_LEN)) {
MSG(5, "Pausing the queue at mark %s", index_mark);
module_speak_queue_pause();
output_pause_queued = 1;
}
}
} else {
module_report_index_mark(index_mark);
}
free(index_mark);
}
else if (!strncmp(response->str, "706", 3))
{
char *p, *icon;
p = strchr(response->str, '\n');
MSG2(5, "output_module", "response:|%s|\n p:|%s|",
response->str, p);
icon =
(char *)strndup(response->str + 4,
p - response->str - 4);
MSG2(5, "output_module", "Detected sound icon: %s",
icon);
if (output->audio &&
!(output_stop_requested || (output_pause_requested && output_pause_queued))) {
if (!module_speak_queue_add_sound_icon(icon))
MSG(3, "Warning: couldn't add icon to speak queue");
}
free(icon);
}
else if (!strncmp(response->str, "705", 3))
{
AudioTrack track = { 0 };
AudioFormat format = 0;
char *p = response->str, *q;
char *end = response->str + response->len;
size_t size, filled;
MSG2(5, "output_module",
"Got audio: %d bytes", (int) response->len);
if (!output->audio) {
MSG2(2, "output_module",
"Audio event but server audio not set up");
retcode = -5;
goto out;
}
if (output_stop_requested || (output_pause_requested && output_pause_queued)) {
MSG2(5, "output_module", "Discarding audio still coming from the synth");
goto out;
}
while (1) {
if (strncmp(p, "705-", 4) != 0) {
MSG2(2, "output_module",
"ERROR: bogus audio parameter %s", p);
retcode = -5;
break;
}
q = memchr(p, '\n', end - p);
if (!q) {
MSG2(2, "output_module",
"ERROR: bogus audio end of line %s", p);
retcode = -5;
break;
}
if (strncmp(p, "705-AUDIO", strlen("705-AUDIO")) == 0 && p[strlen("705-AUDIO")] == '\0') {
p += strlen("705-AUDIO") + 1;
break;
}
if (strncmp(p, "705-big_endian=", strlen("705-big_endian=")) == 0) {
format = atoi(p + strlen("705-big_endian="));
}
#define SET_AUDIO_TRACK_PARAM(name) \
else if (strncmp(p, "705-"#name"=", strlen("705-"#name"=")) == 0) { \
track.name = atoi(p+4+strlen(#name)+1); \
MSG2(5, "output_module", \
"Got audio parameter "#name" %d", track.name); \
}
SET_AUDIO_TRACK_PARAM(bits)
SET_AUDIO_TRACK_PARAM(num_channels)
SET_AUDIO_TRACK_PARAM(sample_rate)
SET_AUDIO_TRACK_PARAM(num_samples)
else {
MSG2(2, "output_module",
"ERROR: unknown audio parameter %s", p);
retcode = -5;
break;
}
p = q + 1;
}
if (retcode < 0)
goto out;
end = memchr(p, '\n', end - p);
if (!end) {
MSG2(2, "output_module",
"ERROR: bogus audio end of line %s", p);
retcode = -5;
goto out;
}
size = track.num_channels * track.num_samples * track.bits / 8;
track.samples = malloc(size);
filled = 0;
char *data = (char*) track.samples;
/* HDLC escaping: invert bit 5 of escaped characters. */
const char escape = 0x7d;
const char invert = 1<<5;
while (p < end) {
size_t piece;
q = memchr(p, escape, end - p);
if (!q)
q = end;
piece = q - p;
if (filled + piece > size) {
MSG2(2, "output_module",
"ERROR: bogus audio content: %zd > %zd", filled + piece, size);
retcode = -5;
break;
}
memcpy(data + filled, p, piece);
filled += piece;
p = q;
while (p < end && *p == escape) {
p++;
if (p == end) {
MSG2(2, "output_module",
"ERROR: bogus audio escape at end");
retcode = -5;
break;
}
if (filled + 1 > size) {
MSG2(2, "output_module",
"ERROR: bogus audio content: %zd > %zd", filled + 1, size);
retcode = -5;
break;
}
data[filled++] = (*p) ^ invert;
p++;
}
if (retcode < 0)
break;
}
if (filled != size) {
MSG2(2, "output_module",
"ERROR: bogus audio content: %zd < %zd", filled, size);
retcode = -5;
}
if (retcode < 0) {
free(track.samples);
goto out;
}
MSG2(5, "output_module",
"Got audio: eventually %zd bytes", size);
gboolean ret = module_speak_queue_add_audio(&track, format);
free(track.samples);
if (!ret)
MSG2(2, "output_module", "Audio interrupted");
} else {
MSG2(2, "output_module",
"ERROR: Unknown event received from output module");
retcode = -5;
}
out:
if (retcode < 0)
module_report_event_broken();
g_string_free(response, TRUE);
return retcode;
}
/* For server-side audio, this is called in a separate thread, to consume output
* from the module in parallel of handling audio processing and feedback to client */
static void *output_thread_func(void *data)
{
OutputModule *output = data;
int ret;
spd_pthread_setname("output_thread_func");
while (1) {
ret = output_module_is_speaking(output);
if (ret < 0) {
MSG2(3, "output_module", "output_module_is_speaking error");
pthread_exit(NULL);
}
if (ret == 0) {
MSG2(4, "output_module", "finished getting data from output module");
pthread_exit(NULL);
}
}
}
int output_is_speaking(char **index_mark)
{
OutputModule *output = speaking_module;
speak_queue_entry *entry;
char c;
int end = 0, ret;
/* Wait for next event */
ret = read(output->pipe_speak[0], &c, 1);
if (ret != 1)
MSG(1, "Warning: couldn't read from pipe_speak: %d returned, (errno = %d, %s)\n", ret, errno, strerror(errno));
pthread_mutex_lock(&playback_events_mutex);
entry = playback_events->data;
playback_events = g_slist_remove(playback_events, entry);
pthread_mutex_unlock(&playback_events_mutex);
/* Process next event */
switch (entry->type) {
case SPEAK_QUEUE_QET_AUDIO:
MSG2(3, "output_module", "audio event ??");
g_free(entry->data.audio.track.samples);
*index_mark = (char *)g_strdup("no");
break;
case SPEAK_QUEUE_QET_INDEX_MARK:
*index_mark = entry->data.markId;
break;
case SPEAK_QUEUE_QET_SOUND_ICON:
MSG2(3, "output_module", "audio icon event ??");
g_free(entry->data.sound_icon_filename);
*index_mark = (char *)g_strdup("no");
break;
case SPEAK_QUEUE_QET_BEGIN:
*index_mark = (char *)g_strdup("__spd_begin");
break;
case SPEAK_QUEUE_QET_END:
*index_mark = (char *)g_strdup("__spd_end");
end = 1;
break;
case SPEAK_QUEUE_QET_PAUSE:
*index_mark = (char *)g_strdup("__spd_paused");
end = 1;
break;
case SPEAK_QUEUE_QET_STOP:
*index_mark = (char *)g_strdup("__spd_stopped");
end = 1;
break;
case SPEAK_QUEUE_QET_BROKEN:
*index_mark = NULL;
end = 1;
break;
}
g_free(entry);
if (end) {
/* Wait for all audio processing to terminate before cleaning
* everything */
pthread_join(output_thread, NULL);
output_stop_reading_events(output);
}
return 0;
}
/* Wait until the child _pid_ returns with timeout. Calls waitpid() each 100ms
until timeout is exceeded. This is not exact and you should not rely on the
exact time waited. */
int
waitpid_with_timeout(pid_t pid, int *status_ptr, int options, size_t timeout)
{
size_t i;
int ret;
for (i = 0; i <= timeout; i += 100) {
ret = waitpid(pid, status_ptr, options | WNOHANG);
if (ret > 0)
return ret;
if (ret < 0)
return ret;
usleep(100 * 1000); /* Sleep 100 ms */
}
return 0;
}
int output_close(OutputModule * module)
{
int err;
int ret;
OutputModule *output;
output = module;
if (output == NULL)
return -1;
output_lock();
assert(output->name != NULL);
MSG(3, "Closing module \"%s\"...", output->name);
if (output->working) {
SEND_DATA("STOP\n");
SEND_CMD("QUIT");
usleep(100);
/* So that the module has some time to exit() correctly */
}
MSG(4, "Waiting for module pid %d", module->pid);
ret = waitpid_with_timeout(module->pid, NULL, 0, 1000);
if (ret > 0) {
MSG(4, "Ok, module closed successfully.");
} else if (ret == 0) {
int ret2;
MSG(1, "ERROR: Timed out when waiting for child cancellation");
MSG(3, "Killing the module");
kill(module->pid, SIGKILL);
MSG(4, "Waiting until the child terminates.");
ret2 = waitpid_with_timeout(module->pid, NULL, 0, 1000);
if (ret2 > 0) {
MSG(3, "Module terminated");
} else {
MSG(1,
"ERROR: Module is not able to terminate, giving up.");
}
} else {
MSG(1,
"ERROR: waitpid() failed when waiting for child (module).");
}
OL_RET(0);
}
#undef SEND_CMD
#undef SEND_DATA
int output_check_module(OutputModule * output)
{
int ret;
int err;
int status;
if (output == NULL)
return -1;
MSG(4, "Output module working status: %d (pid:%d)", output->working,
output->pid);
if (output->working == 0) {
/* Investigate on why it crashed */
ret = waitpid(output->pid, &status, WNOHANG);
if (ret == 0) {
MSG(2, "Output module not running.");
return 0;
}
ret = WIFEXITED(status);
/* TODO: Linux kernel implementation of threads is not very good :( */
// if (ret == 0){
if (1) {
/* Module terminated abnormally */
MSG(2,
"Output module terminated abnormally, probably crashed.");
} else {
/* Module terminated normally, check status */
err = WEXITSTATUS(status);
if (err == 0)
MSG(2, "Module exited normally");
if (err == 1)
MSG(2, "Internal error in output module!");
if (err == 2) {
MSG(2,
"Output device not working. For software devices, this can mean"
"that they are not running or they are not accessible due to wrong"
"access permissions.");
}
if (err > 2)
MSG(2,
"Unknown error happened in output module, exit status: %d !",
err);
}
}
return 0;
}
char *escape_dot(char *otext)
{
char *seq;
GString *ntext;
char *ootext;
char *ret = NULL;
if (otext == NULL)
return NULL;
MSG2(5, "escaping", "Incoming text: |%s|", otext);
ootext = otext;
ntext = g_string_new("");
if (otext[0] == '.') {
g_string_append(ntext, "..");
otext += 1;
}
MSG2(6, "escaping", "Altering text (I): |%s|", ntext->str);
while ((seq = strstr(otext, "\n."))) {
*seq = 0;
g_string_append(ntext, otext);
g_string_append(ntext, "\n..");
otext = seq + 2;
}
MSG2(6, "escaping", "Altering text (II): |%s|", ntext->str);
if (otext == ootext) {
g_string_free(ntext, 1);
ret = otext;
} else {
g_string_append(ntext, otext);
ret = g_string_free(ntext, 0);
}
MSG2(6, "escaping", "Altered text: |%s|", ret);
return ret;
}