blob: 284758936a08a1028a627de3b377935d3bfefa54 [file] [log] [blame] [edit]
/*
* module_process.c - Processing loop of output modules.
*
* Copyright (C) 2020-2023, 2025 Samuel Thibault <samuel.thibault@ens-lyon.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY Samuel Thibault AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <pthread.h>
#include <spd_audio.h>
#include "spd_module_main.h"
pthread_mutex_t module_stdout_mutex = PTHREAD_MUTEX_INITIALIZER;
static int module_should_stop;
/* This sends some text to the server, taking the mutex to avoid intermixing
* between multi-line answers and asynchronous sends. */
void module_send(const char *format, ...)
{
va_list ap;
va_start(ap, format);
pthread_mutex_lock(&module_stdout_mutex);
vprintf(format, ap);
pthread_mutex_unlock(&module_stdout_mutex);
va_end(ap);
fflush(stdout);
}
/* Whether we will send the audio to the server */
static int audio_server;
void module_audio_set_server(void)
{
audio_server = 1;
}
static int module_audio_set_through_server(const char *cur_item, const char *cur_value) {
if (strcmp(cur_item, "audio_output_method") != 0)
/* We only support the audio output method parameter */
return -1;
if (strcmp(cur_value, "server") != 0)
/* We only support server audio output method */
return -1;
return 0;
}
static void module_tts_output_send_server(const AudioTrack *track, AudioFormat format)
{
const char *p, *end;
size_t size = track->num_channels * track->num_samples * track->bits / 8;
pthread_mutex_lock(&module_stdout_mutex);
printf("705-bits=%d\n", track->bits);
printf("705-num_channels=%d\n", track->num_channels);
printf("705-sample_rate=%d\n", track->sample_rate);
printf("705-num_samples=%d\n", track->num_samples);
printf("705-big_endian=%d\n", format);
printf("705-AUDIO");
putc(0, stdout);
p = (const char *) track->samples;
end = p + size;
/* HDLC escaping: prefix NL and escapes with escape, and invert their bit 5. */
const char escape = 0x7d;
const char invert = 1<<5;
while (p < end) {
const char *stop, *nl, *next;
stop = memchr(p, escape, end - p);
nl = memchr(p, '\n', end - p);
if (nl && (!stop || nl < stop))
stop = nl;
if (stop) {
next = stop+1;
} else {
next = stop = end;
}
fwrite(p, 1, stop - p, stdout);
/* Escape NL or escape */
if (stop < end) {
putc(escape, stdout);
putc((*stop) ^ invert, stdout);
}
p = next;
}
putc('\n', stdout);
printf("705 AUDIO\n");
pthread_mutex_unlock(&module_stdout_mutex);
fflush(stdout);
}
/* Arbitrary chunk size in bytes, large enough to get efficient transfer
* but small enough to be reactive. */
#define MAX_CHUNK 10000
void module_tts_output_server(const AudioTrack *track, AudioFormat format)
{
AudioTrack mytrack = *track;
size_t sample_size = track->num_channels * track->bits / 8;
int samplepos = 0;
int num_samples;
while (samplepos < track->num_samples) {
if (module_should_stop)
/* We are requested to stop, ignore the rest of audio */
break;
num_samples = MAX_CHUNK / sample_size;
if (num_samples > track->num_samples - samplepos)
num_samples = track->num_samples - samplepos;
mytrack.num_samples = num_samples;
mytrack.samples = (void*) track->samples + samplepos * sample_size;
samplepos += num_samples;
module_tts_output_send_server(&mytrack, format);
module_process(STDIN_FILENO, 0);
}
}
/*
* This only parses the SSIP protocol from the server, and calls the
* corresponding functions provided by the module or by module_utils.c
*/
#define print(fmt, ...) module_send(fmt "\n", ## __VA_ARGS__)
#define BAD_SYNTAX "302 ERROR BAD SYNTAX"
#define BAD_PARAM "303 ERROR INVALID PARAMETER OR VALUE"
#define BAD_MULTILINE "305 DATA MORE THAN ONE LINE"
#define bad_syntax() print(BAD_SYNTAX)
#define bad_param() print(BAD_PARAM)
#define bad_multiline() print(BAD_MULTILINE)
#define bad_internal() print("401 ERROR INTERNAL")
#define bad_memory() print("402 ERROR OUT OF MEMORY")
/* some text
* at will
* .
*/
static void cmd_speak(int fd, SPDMessageType msgtype)
{
size_t text_allocated = 128, new_allocated;
char *text = malloc(text_allocated), *new_text;
size_t text_len = 0;
size_t len;
int ret;
int nlines = 0;
print("202 OK RECEIVING MESSAGE");
while (1) {
char *line = module_readline(fd, 1);
int offset = 0;
if (!line) {
/* EOF */
free(text);
return;
}
if (!strcmp(line, ".\n")) {
/* Replace \n at the end with a \0 */
if (text_len) {
text_len--;
text[text_len] = 0;
}
free(line);
break;
}
if (line[0] == '.') {
offset++;
line++;
}
nlines++;
len = strlen(line);
new_allocated = text_allocated;
while (text_len + len > new_allocated)
new_allocated *= 2;
if (new_allocated > text_allocated) {
new_text = realloc(text, new_allocated);
if (!new_text) {
free(line);
free(text);
bad_internal();
return;
}
text = new_text;
text_allocated = new_allocated;
}
memcpy(text + text_len, line, len);
text_len += len;
free(line - offset);
}
if (!text_len) {
free(text);
print("301 ERROR CANT SPEAK");
return;
}
if (msgtype != SPD_MSGTYPE_TEXT && nlines > 1) {
free(text);
print("305 DATA MORE THAN ONE LINE");
return;
}
if (msgtype == SPD_MSGTYPE_KEY || msgtype == SPD_MSGTYPE_CHAR) {
if (!strcmp(text, "space")) {
free(text);
text = strdup(" ");
text_len = 1;
}
}
module_should_stop = 0;
#pragma weak module_speak_sync
#pragma weak module_speak
if (module_speak_sync) {
module_speak_sync(text, text_len, msgtype);
} else {
pthread_mutex_lock(&module_stdout_mutex);
ret = module_speak(text, text_len, msgtype);
if (ret > 0)
printf("200 OK SPEAKING\n");
else
printf("301 ERROR CANT SPEAK\n");
fflush(stdout);
pthread_mutex_unlock(&module_stdout_mutex);
}
free(text);
}
static void cmd_speak_text(int fd)
{
return cmd_speak(fd, SPD_MSGTYPE_TEXT);
}
static void cmd_speak_sound_icon(int fd)
{
return cmd_speak(fd, SPD_MSGTYPE_SOUND_ICON);
}
static void cmd_speak_char(int fd)
{
return cmd_speak(fd, SPD_MSGTYPE_CHAR);
}
static void cmd_speak_key(int fd)
{
return cmd_speak(fd, SPD_MSGTYPE_KEY);
}
void module_speak_ok(void)
{
print("200 OK SPEAKING");
}
void module_speak_error(void)
{
print("301 ERROR CANT SPEAK");
}
static void cmd_stop(void)
{
module_should_stop = 1;
module_stop();
}
static void cmd_pause(void)
{
module_should_stop = 1;
module_pause();
}
static void cmd_list_voices(char *line)
{
SPDVoice **voices, **voice;
int one = 0;;
char *dumb;
char *requested_language;
char *requested_variant = NULL;
char *save = NULL;
voices = module_list_voices();
if (!voices) {
print("304 CANT LIST VOICES");
return;
}
dumb = strtok_r(line, " \n", &save);
if (strcmp(dumb, "LIST")) {
bad_internal();
return;
}
dumb = strtok_r(NULL, " \n", &save);
if (strcmp(dumb, "VOICES")) {
bad_internal();
return;
}
requested_language = strtok_r(NULL, " \n", &save);
if (requested_language)
requested_variant = strtok_r(NULL, " \n", &save);
pthread_mutex_lock(&module_stdout_mutex);
for (voice = voices; *voice; voice++) {
const char *name = (*voice)->name;
const char *language = (*voice)->language;
const char *variant = (*voice)->variant;
if (!name)
/* Ok, skip this */
continue;
if (!language)
language = "none";
if (!variant)
variant = "none";
if (requested_language) {
if (strcasecmp(requested_language, language))
{
/* Not exactly the requested locale, but maybe the language? */
const char *dash = strchr(language, '-');
size_t langlen = dash - language;
if (strlen(requested_language) != langlen
|| strncasecmp(requested_language, language, langlen))
/* Not the requested language */
continue;
}
if (requested_variant)
if (strcasecmp(requested_variant, variant))
/* Not the requested variant, skip */
continue;
}
one = 1;
printf("200-%s\t%s\t%s\n", name, language, variant);
}
if (one)
printf("200 OK VOICE LIST SENT\n");
else
printf("304 CANT LIST VOICES\n");
pthread_mutex_unlock(&module_stdout_mutex);
fflush(stdout);
}
/* FOO1=bar1
* FOO2=bar2
* .
*/
static int cmd_params(int fd, int ack, const char *type, int (*set)(const char *var, const char *val))
{
char *var, *val, *save;
const char *err = NULL;
print("%u OK RECEIVING %sSETTINGS", ack, type);
while (1) {
char *line = module_readline(fd, 1);
if (!line)
/* EOF */
return -1;
if (!strcmp(line, ".\n")) {
free(line);
if (!err)
return 0;
print("%s", err);
return -1;
}
save = NULL;
var = strtok_r(line, "=", &save);
if (!var) {
err = BAD_SYNTAX;
free(line);
continue;
}
val = strtok_r(NULL, "\n", &save);
if (!val) {
err = BAD_SYNTAX;
free(line);
continue;
}
if (set(var, val) != 0)
err = BAD_PARAM;
free(line);
}
}
static void cmd_set(int fd)
{
if (cmd_params(fd, 203, "", module_set) != 0)
return;
print("203 OK SETTINGS RECEIVED");
}
static void cmd_audio(int fd)
{
char *status = NULL;
int ret;
if (audio_server) {
if (cmd_params(fd, 207, "AUDIO ", module_audio_set_through_server) != 0)
return;
ret = 0;
} else {
if (cmd_params(fd, 207, "AUDIO ", module_audio_set) != 0)
return;
ret = module_audio_init(&status);
}
if (!ret)
print("203 OK AUDIO INITIALIZED");
else
print("300-%s\n300 MODULE ERROR", status);
free(status);
}
static void cmd_loglevel(int fd)
{
if (cmd_params(fd, 207, "LOGLEVEL ", module_loglevel_set) != 0)
return;
print("203 OK LOGLEVEL SET");
}
/* DEBUG ON /some/file
* or
* DEBUG OFF
*/
static void cmd_debug(char *line)
{
char *debug, *on, *file = NULL, *save = NULL;
int enable = 0;
debug = strtok_r(line, " \n", &save);
if (!debug) {
bad_syntax();
return;
}
if (strcmp(debug, "DEBUG")) {
bad_internal();
return;
}
on = strtok_r(NULL, " \n", &save);
if (!on) {
bad_syntax();
return;
}
if (!strcmp(on, "ON")) {
enable = 1;
file = strtok_r(NULL, " \n", &save);
if (!file) {
bad_syntax();
return;
}
} else if (strcmp(on, "OFF")) {
bad_syntax();
return;
}
if (module_debug(enable, file) != 0)
print("303 CANT OPEN CUSTOM DEBUG FILE");
else
print("200 OK DEBUGGING %s", on);
}
static void cmd_quit(void)
{
module_close();
print("210 OK QUIT");
}
int module_process(int fd, int block)
{
while (1) {
char *line = module_readline(fd, block);
if (line == NULL)
return -1;
if (!strcmp(line, "SPEAK\n"))
cmd_speak_text(fd);
else if (!strcmp(line, "SOUND_ICON\n"))
cmd_speak_sound_icon(fd);
else if (!strcmp(line, "CHAR\n"))
cmd_speak_char(fd);
else if (!strcmp(line, "KEY\n"))
cmd_speak_key(fd);
else if (!strcmp(line, "STOP\n"))
cmd_stop();
else if (!strcmp(line, "PAUSE\n"))
cmd_pause();
else if (!strncmp(line, "LIST VOICES", 11))
cmd_list_voices(line);
else if (!strcmp(line, "SET\n"))
cmd_set(fd);
else if (!strcmp(line, "AUDIO\n"))
cmd_audio(fd);
else if (!strcmp(line, "LOGLEVEL\n"))
cmd_loglevel(fd);
else if (!strncmp(line, "DEBUG", 5))
cmd_debug(line);
else if (!strcmp(line, "QUIT\n")) {
free(line);
cmd_quit();
fflush(stdout);
return 0;
}
else
print("300 ERR UNKNOWN COMMAND");
free(line);
fflush(stdout);
}
}
/* Report index */
void module_report_index_mark(const char *mark)
{
if (!mark)
return;
print("700-%s\n700 INDEX MARK", mark);
}
/* Report speak start */
void module_report_event_begin(void)
{
print("701 BEGIN");
}
/* Report speak end */
void module_report_event_end(void)
{
print("702 END");
}
/* Report speak stop */
void module_report_event_stop(void)
{
print("703 STOP");
}
/* Report speak pause */
void module_report_event_pause(void)
{
print("704 PAUSE");
}
/* Report sound icon */
void module_report_icon(const char *icon)
{
if (!icon)
return;
print("706-%s\n706 ICON", icon);
}