mirror of
https://github.com/linux-speakup/espeakup.git
synced 2026-08-09 09:19:09 +02:00
Compare commits
25 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f266faaff7 | ||
|
|
c3c7c6a007 | ||
|
|
cc1447b16a | ||
|
|
76a2af5008 | ||
|
|
6835449642 | ||
|
|
71d0e1fa2a | ||
|
|
a504688f3d | ||
|
|
7405455baf | ||
|
|
e92edf96dc | ||
|
|
2b959e874e | ||
|
|
d1f7e2384b | ||
|
|
dc1f1b9efa |
||
|
|
fa848f509e | ||
|
|
98dc103749 | ||
|
|
d7c06a6f9f |
||
|
|
108c28ae08 | ||
|
|
78e561fae9 | ||
|
|
c99bfb8e45 | ||
|
|
ca234f2122 | ||
|
|
316e4fc51d | ||
|
|
e858481c0a | ||
|
|
8f18ad7f88 | ||
|
|
b7282c1a90 | ||
|
|
f077d0c042 | ||
|
|
3794e8e74b |
17 changed files with 292 additions and 57 deletions
|
|
@ -22,7 +22,7 @@ espeakup yet, espeakup just uses meson, so you should be able to
|
||||||
change to the source directory, then type:
|
change to the source directory, then type:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson . ./build
|
meson setup . ./build
|
||||||
cd ./build
|
cd ./build
|
||||||
ninja
|
ninja
|
||||||
sudo ninja install
|
sudo ninja install
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ espeak-ng can be found at [github](https://github.com/espeak-ng/espeak-ng)
|
||||||
|
|
||||||
William Hubbs <w.d.hubbs@gmail.com> is the author of espeakup.
|
William Hubbs <w.d.hubbs@gmail.com> is the author of espeakup.
|
||||||
|
|
||||||
This manual page was written by Chris Brannon <cmbrannon79@gmail.com>.
|
This manual page was written by Chris Brannon <chris@the-brannons.com>.
|
||||||
|
|
||||||
current authors and maintainers can be found at
|
current authors and maintainers can be found at
|
||||||
[github](https://github.com/linux-speakup/espeakup/graphs/contributors)
|
[github](https://github.com/linux-speakup/espeakup/graphs/contributors)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
ronn = find_program('ronn', required: get_option('man'))
|
ronn = find_program('ronn', required: get_option('man'))
|
||||||
|
|
||||||
if ronn.found()
|
if ronn.found()
|
||||||
custom_target('man', input:files('espeakup.8.ronn'),
|
custom_target('man',
|
||||||
output:'espeakup.8',
|
input : files('espeakup.8.ronn'),
|
||||||
command:[ronn, '--output-dir', '@OUTDIR@', '--roff', '@INPUT@'],
|
output : 'espeakup.8',
|
||||||
install: true, install_dir: join_paths(get_option('mandir'),'man8'))
|
command : [ronn, '--output-dir', '@OUTDIR@', '--roff', '@INPUT@'],
|
||||||
|
install : true, install_dir: join_paths(get_option('mandir'),'man8'))
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
16
meson.build
16
meson.build
|
|
@ -1,8 +1,12 @@
|
||||||
project('espeakup', 'c',
|
project('espeakup', 'c',
|
||||||
default_options : ['buildtype=debugoptimized', 'c_std=gnu11', 'warning_level=3'],
|
default_options : [
|
||||||
|
'buildtype=debugoptimized',
|
||||||
|
'c_std=gnu11',
|
||||||
|
'warning_level=3'
|
||||||
|
],
|
||||||
license : 'GPL-3.0-or-later',
|
license : 'GPL-3.0-or-later',
|
||||||
version : '0.90',
|
version : '0.90',
|
||||||
meson_version : '>=0.47.0')
|
meson_version : '>=0.51.0')
|
||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
thread_dep = dependency('threads')
|
thread_dep = dependency('threads')
|
||||||
|
|
@ -15,7 +19,7 @@ subdir('services')
|
||||||
subdir('src')
|
subdir('src')
|
||||||
|
|
||||||
executable('espeakup',
|
executable('espeakup',
|
||||||
espeakup_version,
|
espeakup_version,
|
||||||
espeakup_sources,
|
espeakup_sources,
|
||||||
dependencies : [thread_dep, espeak_dep, alsa_dep, math_dep],
|
dependencies : [thread_dep, espeak_dep, alsa_dep, math_dep],
|
||||||
install : true)
|
install : true)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
option('systemd', type : 'feature', value : 'auto',
|
|
||||||
description :'enable systemd support')
|
|
||||||
option('man', type : 'feature', value : 'auto',
|
option('man', type : 'feature', value : 'auto',
|
||||||
description : 'build manpage with ronn')
|
description : 'build manpage with ronn')
|
||||||
|
option('systemd', type : 'feature', value : 'auto',
|
||||||
|
description :'enable systemd support')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
systemd = dependency('systemd', required: get_option('systemd'))
|
systemd = dependency('systemd')
|
||||||
if systemd.found()
|
if (systemd.found() and get_option('systemd').allowed()) or get_option('systemd').enabled()
|
||||||
subdir('systemd')
|
subdir('systemd')
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Software speech output for Speakup
|
Description=Software speech output for Speakup
|
||||||
Documentation=man:espeakup(8)
|
Documentation=man:espeakup(8)
|
||||||
Wants=systemd-udev-settle.service
|
Wants=modprobe@speakup_soft.service
|
||||||
After=systemd-udev-settle.service sound.target
|
After=modprobe@speakup_soft.service sound.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=forking
|
Type=forking
|
||||||
PIDFile=/run/espeakup.pid
|
PIDFile=/run/espeakup.pid
|
||||||
Environment="default_voice="
|
Environment="default_voice="
|
||||||
ExecStartPre=+modprobe speakup_soft
|
|
||||||
ExecStart=@bindir@/espeakup --default-voice=${default_voice}
|
ExecStart=@bindir@/espeakup --default-voice=${default_voice}
|
||||||
ExecReload=kill -HUP $MAINPID
|
ExecReload=kill -HUP $MAINPID
|
||||||
Restart=always
|
Restart=always
|
||||||
|
Nice=-10
|
||||||
|
OOMScoreAdjust=-900
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=sound.target
|
WantedBy=sound.target
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
unitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir')
|
if systemd.found()
|
||||||
|
unitdir = systemd.get_variable(pkgconfig: 'systemdsystemunitdir')
|
||||||
|
else
|
||||||
|
unitdir = join_paths(prefixdir, get_option('libdir'), 'systemd', 'system')
|
||||||
|
endif
|
||||||
prefixdir = get_option('prefix')
|
prefixdir = get_option('prefix')
|
||||||
bindir = join_paths(prefixdir, get_option('bindir'))
|
bindir = join_paths(prefixdir, get_option('bindir'))
|
||||||
|
|
||||||
unit_conf = configuration_data()
|
unit_conf = configuration_data()
|
||||||
|
|
||||||
unit_conf.set('bindir', bindir)
|
unit_conf.set('bindir', bindir)
|
||||||
|
|
||||||
service_file = configure_file(
|
service_file = configure_file(
|
||||||
input : 'espeakup.service.in',
|
input : 'espeakup.service.in',
|
||||||
output : 'espeakup.service',
|
output : 'espeakup.service',
|
||||||
configuration : unit_conf
|
configuration : unit_conf
|
||||||
)
|
)
|
||||||
|
|
||||||
install_data(service_file,
|
install_data(service_file,
|
||||||
install_dir : unitdir
|
install_dir : unitdir
|
||||||
)
|
)
|
||||||
|
|
|
||||||
10
src/cli.c
10
src/cli.c
|
|
@ -23,6 +23,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "espeakup.h"
|
#include "espeakup.h"
|
||||||
|
#include "stringhandling.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
/* pid path */
|
/* pid path */
|
||||||
|
|
@ -72,18 +73,15 @@ static void show_version(void)
|
||||||
void process_cli(int argc, char **argv)
|
void process_cli(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int opt;
|
int opt;
|
||||||
char *cp;
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
opt = getopt_long(argc, argv, shortOptions, longOptions, NULL);
|
opt = getopt_long(argc, argv, shortOptions, longOptions, NULL);
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'p':
|
case 'P':
|
||||||
cp = strdup(optarg);
|
pidPath = dupeString(optarg);
|
||||||
if (cp != NULL)
|
|
||||||
pidPath = cp;
|
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
defaultVoice = strdup(optarg);
|
defaultVoice = dupeString(optarg);
|
||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
espeakup_mode = ESPEAKUP_MODE_ACSINT;
|
espeakup_mode = ESPEAKUP_MODE_ACSINT;
|
||||||
|
|
|
||||||
207
src/espeak.c
207
src/espeak.c
|
|
@ -21,9 +21,12 @@
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "espeakup.h"
|
#include "espeakup.h"
|
||||||
|
|
||||||
|
|
@ -47,9 +50,31 @@ const int volumeMultiplier = 22;
|
||||||
volatile int stop_requested = 0;
|
volatile int stop_requested = 0;
|
||||||
int paused_espeak = 1;
|
int paused_espeak = 1;
|
||||||
|
|
||||||
|
/* Wedged-engine detection. Espeak may legitimately refuse entries for a
|
||||||
|
* while (EE_BUFFER_FULL while a long backlog is being played back), so a
|
||||||
|
* failing entry is normally just retried. But when entries keep failing
|
||||||
|
* while the synth callback reports no progress at all, the audio output
|
||||||
|
* is most likely wedged (e.g. an ALSA device stuck returning EBUSY).
|
||||||
|
* After ESPEAK_STALL_RETRIES consecutive such retries (about one second
|
||||||
|
* each), restart the engine; after ESPEAK_MAX_RESTARTS restarts without
|
||||||
|
* the engine having been healthy for ESPEAK_HEALTHY_SECS in between,
|
||||||
|
* give up and exit, so that the init system can respawn us. */
|
||||||
|
#define ESPEAK_STALL_RETRIES 10
|
||||||
|
#define ESPEAK_MAX_RESTARTS 3
|
||||||
|
#define ESPEAK_HEALTHY_SECS 60
|
||||||
|
|
||||||
|
/* Set by the synth callback whenever espeak makes synthesis progress;
|
||||||
|
* used to tell a merely backlogged engine from a wedged one. The
|
||||||
|
* callback runs in espeak's own thread, so the flag is atomic. */
|
||||||
|
static atomic_int synth_progressed = 0;
|
||||||
|
static int stalled_retries = 0;
|
||||||
|
static int restart_attempts = 0;
|
||||||
|
static struct timespec last_restart;
|
||||||
|
|
||||||
static int callback(short *wav, int numsamples, espeak_EVENT *events)
|
static int callback(short *wav, int numsamples, espeak_EVENT *events)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
atomic_store(&synth_progressed, 1);
|
||||||
for (i = 0; events[i].type != espeakEVENT_LIST_TERMINATED; i++) {
|
for (i = 0; events[i].type != espeakEVENT_LIST_TERMINATED; i++) {
|
||||||
if (events[i].type == espeakEVENT_MARK) {
|
if (events[i].type == espeakEVENT_MARK) {
|
||||||
int mark = atoi(events[i].id.name);
|
int mark = atoi(events[i].id.name);
|
||||||
|
|
@ -108,12 +133,31 @@ static espeak_ERROR set_punctuation(struct synth_t *s, int punct,
|
||||||
enum adjust_t adj)
|
enum adjust_t adj)
|
||||||
{
|
{
|
||||||
espeak_ERROR rc;
|
espeak_ERROR rc;
|
||||||
|
espeak_PUNCT_TYPE espeak_punct;
|
||||||
|
|
||||||
if (adj == ADJ_DEC)
|
if (adj == ADJ_DEC)
|
||||||
punct = -punct;
|
punct = -punct;
|
||||||
if (adj != ADJ_SET)
|
if (adj != ADJ_SET)
|
||||||
punct += s->punct;
|
punct += s->punct;
|
||||||
rc = espeak_SetParameter(espeakPUNCTUATION, punct, 0);
|
|
||||||
|
switch (punct) {
|
||||||
|
case 0:
|
||||||
|
espeak_punct = espeakPUNCT_NONE;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
espeak_punct = espeakPUNCT_SOME;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
/* XXX: approximation */
|
||||||
|
espeak_punct = espeakPUNCT_SOME;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
default:
|
||||||
|
espeak_punct = espeakPUNCT_ALL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = espeak_SetParameter(espeakPUNCTUATION, espeak_punct, 0);
|
||||||
if (rc == EE_OK)
|
if (rc == EE_OK)
|
||||||
s->punct = punct;
|
s->punct = punct;
|
||||||
return rc;
|
return rc;
|
||||||
|
|
@ -258,15 +302,39 @@ static espeak_ERROR speak_text(struct synth_t *s)
|
||||||
synth_mode |= espeakSSML;
|
synth_mode |= espeakSSML;
|
||||||
|
|
||||||
if (espeakup_mode == ESPEAKUP_MODE_SPEAKUP && (s->len == 1)) {
|
if (espeakup_mode == ESPEAKUP_MODE_SPEAKUP && (s->len == 1)) {
|
||||||
char *buf;
|
char *buf = NULL;
|
||||||
int n;
|
int n;
|
||||||
if (s->buf[0] == ' ')
|
unsigned char c = s->buf[0];
|
||||||
|
if (c == ' ')
|
||||||
n = asprintf(&buf,
|
n = asprintf(&buf,
|
||||||
"<say-as interpret-as=\"tts:char\"> </say-as>");
|
"<say-as interpret-as=\"tts:char\"> </say-as>");
|
||||||
else
|
else if (c < 0x20 || c > 0x7e) {
|
||||||
n = asprintf(&buf,
|
/* Not a printable character; do not embed it in SSML, as
|
||||||
"<say-as interpret-as=\"characters\">%c</say-as>",
|
* that would produce invalid markup. Fall through to the
|
||||||
s->buf[0]);
|
* raw-synthesis path below. */
|
||||||
|
n = -1;
|
||||||
|
} else {
|
||||||
|
/* Escape characters that are special in XML/SSML so the
|
||||||
|
* resulting markup stays well-formed; otherwise espeak-ng
|
||||||
|
* misparses the <say-as> element, which is the source of
|
||||||
|
* the spurious high-pitched "ringing" on these characters. */
|
||||||
|
const char *entity = NULL;
|
||||||
|
switch (c) {
|
||||||
|
case '<': entity = "<"; break;
|
||||||
|
case '>': entity = ">"; break;
|
||||||
|
case '&': entity = "&"; break;
|
||||||
|
case '\'': entity = "'"; break;
|
||||||
|
case '"': entity = """; break;
|
||||||
|
}
|
||||||
|
if (entity)
|
||||||
|
n = asprintf(&buf,
|
||||||
|
"<say-as interpret-as=\"characters\">%s</say-as>",
|
||||||
|
entity);
|
||||||
|
else
|
||||||
|
n = asprintf(&buf,
|
||||||
|
"<say-as interpret-as=\"characters\">%c</say-as>",
|
||||||
|
c);
|
||||||
|
}
|
||||||
if (n == -1) {
|
if (n == -1) {
|
||||||
/* D'oh. Not much to do on allocation failure.
|
/* D'oh. Not much to do on allocation failure.
|
||||||
* Perhaps espeak will happen to say the character */
|
* Perhaps espeak will happen to say the character */
|
||||||
|
|
@ -301,7 +369,7 @@ static void synth_queue_clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reinitialize_espeak(struct synth_t *s)
|
static int reinitialize_espeak(struct synth_t *s)
|
||||||
{
|
{
|
||||||
int rate;
|
int rate;
|
||||||
|
|
||||||
|
|
@ -309,7 +377,7 @@ static void reinitialize_espeak(struct synth_t *s)
|
||||||
rate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, NULL, 0);
|
rate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, NULL, 0);
|
||||||
if (rate < 0) {
|
if (rate < 0) {
|
||||||
fprintf(stderr, "Unable to initialize espeak.\n");
|
fprintf(stderr, "Unable to initialize espeak.\n");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
espeak_SetSynthCallback(callback);
|
espeak_SetSynthCallback(callback);
|
||||||
|
|
@ -322,24 +390,88 @@ static void reinitialize_espeak(struct synth_t *s)
|
||||||
espeak_SetParameter(espeakVOLUME, (s->volume + 1) * volumeMultiplier, 0);
|
espeak_SetParameter(espeakVOLUME, (s->volume + 1) * volumeMultiplier, 0);
|
||||||
espeak_SetParameter(espeakCAPITALS, 0, 0);
|
espeak_SetParameter(espeakCAPITALS, 0, 0);
|
||||||
paused_espeak = 0;
|
paused_espeak = 0;
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wait for up to a second before retrying an entry which could not be
|
||||||
|
* processed, so that we do not busy-loop on a persistent error. Called
|
||||||
|
* and returns with queue_guard held. Wakes up immediately if a stop is
|
||||||
|
* requested. */
|
||||||
|
static void espeak_wait_retry(void)
|
||||||
|
{
|
||||||
|
struct timespec timeout;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &timeout);
|
||||||
|
timeout.tv_sec++;
|
||||||
|
pthread_cond_timedwait(&wake_stop, &queue_guard, &timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle an entry which could not be processed. Called and returns
|
||||||
|
* with queue_guard held.
|
||||||
|
* Normally just back off before the retry, but watch out for a wedged
|
||||||
|
* engine: if entries keep failing while the synth callback shows no
|
||||||
|
* progress at all, restart the engine, and if restarting does not help
|
||||||
|
* either, exit so that the init system respawns us in a clean state. */
|
||||||
|
static void espeak_handle_failure(struct synth_t *s)
|
||||||
|
{
|
||||||
|
if (atomic_exchange(&synth_progressed, 0)) {
|
||||||
|
/* Espeak is making progress, it is merely backlogged. */
|
||||||
|
stalled_retries = 0;
|
||||||
|
} else if (++stalled_retries >= ESPEAK_STALL_RETRIES) {
|
||||||
|
stalled_retries = 0;
|
||||||
|
if (++restart_attempts > ESPEAK_MAX_RESTARTS) {
|
||||||
|
fprintf(stderr, "espeakup: espeak keeps failing without "
|
||||||
|
"making progress and restarting it did not help, "
|
||||||
|
"aborting\n");
|
||||||
|
/* Use _exit because exit could hang in library destructors
|
||||||
|
* while the audio device is wedged. */
|
||||||
|
_exit(3);
|
||||||
|
}
|
||||||
|
fprintf(stderr, "espeakup: espeak has been failing without "
|
||||||
|
"making progress for %d seconds, restarting it\n",
|
||||||
|
ESPEAK_STALL_RETRIES);
|
||||||
|
/* Call into espeak with queue_guard released: these calls can
|
||||||
|
* take time, or block on a wedged audio device. If they do
|
||||||
|
* block forever, the stop-acknowledgement timeout in the
|
||||||
|
* softsynth thread is our last resort. */
|
||||||
|
pthread_mutex_unlock(&queue_guard);
|
||||||
|
if (!paused_espeak) {
|
||||||
|
espeak_Cancel();
|
||||||
|
espeak_Terminate();
|
||||||
|
paused_espeak = 1;
|
||||||
|
}
|
||||||
|
reinitialize_espeak(s);
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &last_restart);
|
||||||
|
pthread_mutex_lock(&queue_guard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
espeak_wait_retry();
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct espeak_entry_t *current = NULL;
|
||||||
static void queue_process_entry(struct synth_t *s)
|
static void queue_process_entry(struct synth_t *s)
|
||||||
{
|
{
|
||||||
espeak_ERROR error;
|
espeak_ERROR error = EE_OK;
|
||||||
char markbuff[50];
|
char markbuff[50];
|
||||||
static struct espeak_entry_t *current = NULL;
|
|
||||||
|
|
||||||
if (current != queue_peek(synth_queue)) {
|
if (current != queue_peek(synth_queue)) {
|
||||||
if (current)
|
if (current)
|
||||||
free_espeak_entry(current);
|
free_espeak_entry(current);
|
||||||
current = (struct espeak_entry_t *) queue_remove(synth_queue);
|
current = queue_peek(synth_queue);
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&queue_guard);
|
pthread_mutex_unlock(&queue_guard);
|
||||||
|
|
||||||
if (current->cmd != CMD_PAUSE && paused_espeak) {
|
if (current->cmd != CMD_PAUSE && paused_espeak) {
|
||||||
reinitialize_espeak(s);
|
if (reinitialize_espeak(s) < 0) {
|
||||||
|
/* Espeak is unavailable, so the entry cannot be processed.
|
||||||
|
* Calling espeak functions on a terminated engine would
|
||||||
|
* just fail (or worse). Leave the entry queued and retry
|
||||||
|
* after a small pause. */
|
||||||
|
pthread_mutex_lock(&queue_guard);
|
||||||
|
espeak_handle_failure(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (current->cmd) {
|
switch (current->cmd) {
|
||||||
|
|
@ -377,18 +509,48 @@ static void queue_process_entry(struct synth_t *s)
|
||||||
break;
|
break;
|
||||||
case CMD_PAUSE:
|
case CMD_PAUSE:
|
||||||
if (!paused_espeak) {
|
if (!paused_espeak) {
|
||||||
espeak_Cancel();
|
error = espeak_Cancel();
|
||||||
espeak_Terminate();
|
if (error == EE_OK)
|
||||||
paused_espeak = 1;
|
error = espeak_Terminate();
|
||||||
|
if (error == EE_OK)
|
||||||
|
paused_espeak = 1;
|
||||||
|
} else {
|
||||||
|
error = EE_OK;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
/* Uh? */
|
||||||
|
error = EE_OK;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&queue_guard);
|
||||||
if (error == EE_OK) {
|
if (error == EE_OK) {
|
||||||
|
/* Processed, drop it */
|
||||||
|
struct espeak_entry_t *unqueued = queue_remove(synth_queue);
|
||||||
|
assert(unqueued == current);
|
||||||
free_espeak_entry(current);
|
free_espeak_entry(current);
|
||||||
current = NULL;
|
current = NULL;
|
||||||
|
stalled_retries = 0;
|
||||||
|
if (restart_attempts) {
|
||||||
|
/* Forget about past restarts once the engine has been
|
||||||
|
* healthy for a while. Entries can spuriously succeed
|
||||||
|
* right after a restart while the audio output is still
|
||||||
|
* wedged (espeak's internal queue is empty again), so a
|
||||||
|
* quick success must not reset the counter. */
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
if (now.tv_sec - last_restart.tv_sec >= ESPEAK_HEALTHY_SECS)
|
||||||
|
restart_attempts = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (error != EE_BUFFER_FULL)
|
||||||
|
fprintf(stderr, "espeak error: %d\n", error);
|
||||||
|
/* The entry stays queued and will be retried. Give espeak a
|
||||||
|
* little break before that, whatever the error: previously only
|
||||||
|
* EE_BUFFER_FULL throttled, and any other persistent error made
|
||||||
|
* us retry the same entry in a tight loop, burning a whole CPU. */
|
||||||
|
espeak_handle_failure(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -450,7 +612,17 @@ void *espeak_thread(void *arg)
|
||||||
pthread_cond_wait(&runner_awake, &queue_guard);
|
pthread_cond_wait(&runner_awake, &queue_guard);
|
||||||
|
|
||||||
if (stop_requested) {
|
if (stop_requested) {
|
||||||
|
current = NULL;
|
||||||
|
/* Call into espeak with queue_guard released: espeak_Cancel
|
||||||
|
* can take time, or even block indefinitely when the audio
|
||||||
|
* output is wedged, and holding the lock here would prevent
|
||||||
|
* the other threads from ever making progress again. The
|
||||||
|
* queue cannot change concurrently: the only producer (the
|
||||||
|
* softsynth thread) is blocked waiting for stop_acknowledged
|
||||||
|
* as long as stop_requested is set. */
|
||||||
|
pthread_mutex_unlock(&queue_guard);
|
||||||
stop_speech();
|
stop_speech();
|
||||||
|
pthread_mutex_lock(&queue_guard);
|
||||||
synth_queue_clear();
|
synth_queue_clear();
|
||||||
stop_requested = 0;
|
stop_requested = 0;
|
||||||
pthread_cond_signal(&stop_acknowledged);
|
pthread_cond_signal(&stop_acknowledged);
|
||||||
|
|
@ -458,7 +630,6 @@ void *espeak_thread(void *arg)
|
||||||
|
|
||||||
while (should_run && queue_peek(synth_queue) && !stop_requested) {
|
while (should_run && queue_peek(synth_queue) && !stop_requested) {
|
||||||
queue_process_entry(s);
|
queue_process_entry(s);
|
||||||
pthread_mutex_lock(&queue_guard);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pthread_cond_signal(&stop_acknowledged);
|
pthread_cond_signal(&stop_acknowledged);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/file.h>
|
#include <sys/file.h>
|
||||||
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "espeakup.h"
|
#include "espeakup.h"
|
||||||
|
|
@ -40,7 +41,9 @@ volatile int should_run = 1;
|
||||||
espeak_AUDIO_OUTPUT audio_mode;
|
espeak_AUDIO_OUTPUT audio_mode;
|
||||||
|
|
||||||
pthread_cond_t runner_awake = PTHREAD_COND_INITIALIZER;
|
pthread_cond_t runner_awake = PTHREAD_COND_INITIALIZER;
|
||||||
pthread_cond_t stop_acknowledged = PTHREAD_COND_INITIALIZER;
|
/* Initialized in main: use the monotonic clock for timed waits. */
|
||||||
|
pthread_cond_t wake_stop;
|
||||||
|
pthread_cond_t stop_acknowledged;
|
||||||
pthread_mutex_t queue_guard = PTHREAD_MUTEX_INITIALIZER;
|
pthread_mutex_t queue_guard = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
int espeakup_start_daemon(void)
|
int espeakup_start_daemon(void)
|
||||||
|
|
@ -146,6 +149,18 @@ int main(int argc, char **argv)
|
||||||
struct synth_t s = {
|
struct synth_t s = {
|
||||||
.voice = "",
|
.voice = "",
|
||||||
};
|
};
|
||||||
|
pthread_condattr_t monotonic_attr;
|
||||||
|
|
||||||
|
/* Condition variables used with pthread_cond_timedwait must use the
|
||||||
|
* monotonic clock, so that wall-clock adjustments (NTP, an
|
||||||
|
* installer setting the system time) cannot make the timeouts fire
|
||||||
|
* too early or far too late. */
|
||||||
|
pthread_condattr_init(&monotonic_attr);
|
||||||
|
pthread_condattr_setclock(&monotonic_attr, CLOCK_MONOTONIC);
|
||||||
|
pthread_cond_init(&wake_stop, &monotonic_attr);
|
||||||
|
pthread_cond_init(&stop_acknowledged, &monotonic_attr);
|
||||||
|
pthread_condattr_destroy(&monotonic_attr);
|
||||||
|
|
||||||
synth_queue = new_queue();
|
synth_queue = new_queue();
|
||||||
|
|
||||||
if (!synth_queue) {
|
if (!synth_queue) {
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ extern int self_pipe_fds[2];
|
||||||
#define PIPE_WRITE_FD (self_pipe_fds[1])
|
#define PIPE_WRITE_FD (self_pipe_fds[1])
|
||||||
|
|
||||||
extern pthread_cond_t runner_awake;
|
extern pthread_cond_t runner_awake;
|
||||||
|
extern pthread_cond_t wake_stop;
|
||||||
extern pthread_cond_t stop_acknowledged;
|
extern pthread_cond_t stop_acknowledged;
|
||||||
extern pthread_mutex_t queue_guard;
|
extern pthread_mutex_t queue_guard;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
espeakup_version = vcs_tag(input : 'version.h.in', output : 'version.h')
|
|
||||||
|
|
||||||
|
|
||||||
espeakup_sources = files([
|
espeakup_sources = files([
|
||||||
'cli.c',
|
'cli.c',
|
||||||
'espeak.c',
|
'espeak.c',
|
||||||
|
|
@ -10,3 +7,4 @@ espeakup_sources = files([
|
||||||
'softsynth.c',
|
'softsynth.c',
|
||||||
'stringhandling.c'
|
'stringhandling.c'
|
||||||
])
|
])
|
||||||
|
espeakup_version = vcs_tag(input : 'version.h.in', output : 'version.h')
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,14 @@ void *signal_thread(void *arg)
|
||||||
case SIGTERM:
|
case SIGTERM:
|
||||||
pthread_mutex_lock(&queue_guard);
|
pthread_mutex_lock(&queue_guard);
|
||||||
should_run = 0;
|
should_run = 0;
|
||||||
|
/* Wake up any thread waiting on a condition variable so
|
||||||
|
* that it notices the shutdown request: the softsynth
|
||||||
|
* thread may be waiting for a stop acknowledgement, and
|
||||||
|
* the espeak thread may be waiting for work or throttling
|
||||||
|
* before a retry. */
|
||||||
|
pthread_cond_broadcast(&runner_awake);
|
||||||
|
pthread_cond_broadcast(&wake_stop);
|
||||||
|
pthread_cond_broadcast(&stop_acknowledged);
|
||||||
pthread_mutex_unlock(&queue_guard);
|
pthread_mutex_unlock(&queue_guard);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/select.h>
|
#include <sys/select.h>
|
||||||
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "espeakup.h"
|
#include "espeakup.h"
|
||||||
|
|
@ -223,14 +224,37 @@ static void process_buffer_acsint(struct synth_t *s, char *buf, ssize_t length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* How long to wait for the espeak thread to acknowledge a stop request
|
||||||
|
* before concluding that espeak is wedged beyond in-process recovery. */
|
||||||
|
static const int stopAckTimeout = 10;
|
||||||
|
|
||||||
static void request_espeak_stop(void)
|
static void request_espeak_stop(void)
|
||||||
{
|
{
|
||||||
|
struct timespec timeout;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
pthread_mutex_lock(&queue_guard);
|
pthread_mutex_lock(&queue_guard);
|
||||||
stop_requested = 1;
|
stop_requested = 1;
|
||||||
pthread_cond_signal(&runner_awake); // Wake runner, if necessary.
|
pthread_cond_signal(&runner_awake); // Wake runner, if necessary.
|
||||||
while (should_run && stop_requested)
|
pthread_cond_signal(&wake_stop); // Wake runner, if necessary.
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &timeout);
|
||||||
|
timeout.tv_sec += stopAckTimeout;
|
||||||
|
while (should_run && stop_requested && err != ETIMEDOUT)
|
||||||
// wait for acknowledgement.
|
// wait for acknowledgement.
|
||||||
pthread_cond_wait(&stop_acknowledged, &queue_guard);
|
err = pthread_cond_timedwait(&stop_acknowledged, &queue_guard,
|
||||||
|
&timeout);
|
||||||
|
if (should_run && stop_requested) {
|
||||||
|
/* The espeak thread is stuck in a call into espeak, most likely
|
||||||
|
* on a wedged audio device. There is no way to recover from
|
||||||
|
* within the process: exit so that the init system can respawn
|
||||||
|
* us in a clean state, rather than staying silent, ignoring
|
||||||
|
* SIGTERM, and stalling the whole console by not draining
|
||||||
|
* /dev/softsynth anymore. Use _exit because exit could hang in
|
||||||
|
* library destructors while the audio device is wedged. */
|
||||||
|
fprintf(stderr, "espeakup: espeak did not acknowledge a stop "
|
||||||
|
"request within %d seconds, aborting\n", stopAckTimeout);
|
||||||
|
_exit(3);
|
||||||
|
}
|
||||||
pthread_mutex_unlock(&queue_guard);
|
pthread_mutex_unlock(&queue_guard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,7 +273,7 @@ int open_softsynth(void)
|
||||||
// Kernel without unicode support? Try without unicode.
|
// Kernel without unicode support? Try without unicode.
|
||||||
softFD = open("/dev/softsynth", O_RDWR | O_NONBLOCK);
|
softFD = open("/dev/softsynth", O_RDWR | O_NONBLOCK);
|
||||||
if (softFD < 0) {
|
if (softFD < 0) {
|
||||||
perror("Unable to open the softsynth device");
|
perror("Unable to open the /dev/softsynth device");
|
||||||
rc = -1;
|
rc = -1;
|
||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,17 @@ void *reallocMem(void *p, size_t n)
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *dupeString(char *s)
|
||||||
|
{
|
||||||
|
char *c;
|
||||||
|
|
||||||
|
if (!(c = strdup(s))) {
|
||||||
|
fprintf(stderr, "Out of memory!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
char *initString(int *l)
|
char *initString(int *l)
|
||||||
{
|
{
|
||||||
*l = 0;
|
*l = 0;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ extern char *EMPTYSTRING;
|
||||||
|
|
||||||
void *allocMem(size_t n);
|
void *allocMem(size_t n);
|
||||||
void *reallocMem(void *p, size_t n);
|
void *reallocMem(void *p, size_t n);
|
||||||
|
char *dupeString(char *s);
|
||||||
char *initString(int *l);
|
char *initString(int *l);
|
||||||
void stringAndString(char **s, int *l, const char *t);
|
void stringAndString(char **s, int *l, const char *t);
|
||||||
void stringAndBytes(char **s, int *l, const char *t, int cnt);
|
void stringAndBytes(char **s, int *l, const char *t, int cnt);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue