diff --git a/README.md b/README.md index e1395b0..9028d0b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ espeakup yet, espeakup just uses meson, so you should be able to change to the source directory, then type: ```bash -meson setup . ./build +meson . ./build cd ./build ninja sudo ninja install diff --git a/doc/espeakup.8.ronn b/doc/espeakup.8.ronn index 598611c..40b9d9c 100644 --- a/doc/espeakup.8.ronn +++ b/doc/espeakup.8.ronn @@ -71,7 +71,7 @@ espeak-ng can be found at [github](https://github.com/espeak-ng/espeak-ng) William Hubbs is the author of espeakup. -This manual page was written by Chris Brannon . +This manual page was written by Chris Brannon . current authors and maintainers can be found at [github](https://github.com/linux-speakup/espeakup/graphs/contributors) diff --git a/doc/meson.build b/doc/meson.build index f055bb4..9645649 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -1,8 +1,8 @@ ronn = find_program('ronn', required: get_option('man')) + if ronn.found() - custom_target('man', - input : files('espeakup.8.ronn'), - output : 'espeakup.8', - command : [ronn, '--output-dir', '@OUTDIR@', '--roff', '@INPUT@'], - install : true, install_dir: join_paths(get_option('mandir'),'man8')) + custom_target('man', input:files('espeakup.8.ronn'), + output:'espeakup.8', + command:[ronn, '--output-dir', '@OUTDIR@', '--roff', '@INPUT@'], + install: true, install_dir: join_paths(get_option('mandir'),'man8')) endif diff --git a/meson.build b/meson.build index 38788d2..2ba7c86 100644 --- a/meson.build +++ b/meson.build @@ -1,12 +1,8 @@ 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', version : '0.90', - meson_version : '>=0.51.0') + meson_version : '>=0.47.0') cc = meson.get_compiler('c') thread_dep = dependency('threads') @@ -19,7 +15,7 @@ subdir('services') subdir('src') executable('espeakup', - espeakup_version, - espeakup_sources, - dependencies : [thread_dep, espeak_dep, alsa_dep, math_dep], - install : true) + espeakup_version, + espeakup_sources, + dependencies : [thread_dep, espeak_dep, alsa_dep, math_dep], + install : true) diff --git a/meson_options.txt b/meson_options.txt index c8560b6..c7e35d9 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,4 +1,4 @@ -option('man', type : 'feature', value : 'auto', - description : 'build manpage with ronn') option('systemd', type : 'feature', value : 'auto', description :'enable systemd support') +option('man', type : 'feature', value : 'auto', + description : 'build manpage with ronn') diff --git a/services/meson.build b/services/meson.build index 5eadcac..0be40f1 100644 --- a/services/meson.build +++ b/services/meson.build @@ -1,4 +1,4 @@ -systemd = dependency('systemd') -if (systemd.found() and get_option('systemd').allowed()) or get_option('systemd').enabled() +systemd = dependency('systemd', required: get_option('systemd')) +if systemd.found() subdir('systemd') endif diff --git a/services/systemd/espeakup.service.in b/services/systemd/espeakup.service.in index 075a717..ec5f4da 100644 --- a/services/systemd/espeakup.service.in +++ b/services/systemd/espeakup.service.in @@ -1,18 +1,17 @@ [Unit] Description=Software speech output for Speakup Documentation=man:espeakup(8) -Wants=modprobe@speakup_soft.service -After=modprobe@speakup_soft.service sound.target +Wants=systemd-udev-settle.service +After=systemd-udev-settle.service sound.target [Service] Type=forking PIDFile=/run/espeakup.pid Environment="default_voice=" +ExecStartPre=+modprobe speakup_soft ExecStart=@bindir@/espeakup --default-voice=${default_voice} ExecReload=kill -HUP $MAINPID Restart=always -Nice=-10 -OOMScoreAdjust=-900 [Install] WantedBy=sound.target diff --git a/services/systemd/meson.build b/services/systemd/meson.build index 1a15303..4a693ed 100644 --- a/services/systemd/meson.build +++ b/services/systemd/meson.build @@ -1,20 +1,17 @@ -if systemd.found() - unitdir = systemd.get_variable(pkgconfig: 'systemdsystemunitdir') -else - unitdir = join_paths(prefixdir, get_option('libdir'), 'systemd', 'system') -endif +unitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir') prefixdir = get_option('prefix') bindir = join_paths(prefixdir, get_option('bindir')) unit_conf = configuration_data() + unit_conf.set('bindir', bindir) service_file = configure_file( - input : 'espeakup.service.in', - output : 'espeakup.service', - configuration : unit_conf + input : 'espeakup.service.in', + output : 'espeakup.service', + configuration : unit_conf ) install_data(service_file, - install_dir : unitdir + install_dir : unitdir ) diff --git a/src/cli.c b/src/cli.c index 8453587..a4cf48f 100644 --- a/src/cli.c +++ b/src/cli.c @@ -23,7 +23,6 @@ #include #include "espeakup.h" -#include "stringhandling.h" #include "version.h" /* pid path */ @@ -73,15 +72,18 @@ static void show_version(void) void process_cli(int argc, char **argv) { int opt; + char *cp; do { opt = getopt_long(argc, argv, shortOptions, longOptions, NULL); switch (opt) { - case 'P': - pidPath = dupeString(optarg); + case 'p': + cp = strdup(optarg); + if (cp != NULL) + pidPath = cp; break; case 'V': - defaultVoice = dupeString(optarg); + defaultVoice = strdup(optarg); break; case 'a': espeakup_mode = ESPEAKUP_MODE_ACSINT; diff --git a/src/espeak.c b/src/espeak.c index c9ac908..135c6b5 100644 --- a/src/espeak.c +++ b/src/espeak.c @@ -21,12 +21,9 @@ #include #include #include -#include #include #include #include -#include -#include #include "espeakup.h" @@ -50,31 +47,9 @@ const int volumeMultiplier = 22; volatile int stop_requested = 0; 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) { int i; - atomic_store(&synth_progressed, 1); for (i = 0; events[i].type != espeakEVENT_LIST_TERMINATED; i++) { if (events[i].type == espeakEVENT_MARK) { int mark = atoi(events[i].id.name); @@ -133,31 +108,12 @@ static espeak_ERROR set_punctuation(struct synth_t *s, int punct, enum adjust_t adj) { espeak_ERROR rc; - espeak_PUNCT_TYPE espeak_punct; if (adj == ADJ_DEC) punct = -punct; if (adj != ADJ_SET) punct += s->punct; - - 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); + rc = espeak_SetParameter(espeakPUNCTUATION, punct, 0); if (rc == EE_OK) s->punct = punct; return rc; @@ -302,39 +258,15 @@ static espeak_ERROR speak_text(struct synth_t *s) synth_mode |= espeakSSML; if (espeakup_mode == ESPEAKUP_MODE_SPEAKUP && (s->len == 1)) { - char *buf = NULL; + char *buf; int n; - unsigned char c = s->buf[0]; - if (c == ' ') + if (s->buf[0] == ' ') n = asprintf(&buf, " "); - else if (c < 0x20 || c > 0x7e) { - /* Not a printable character; do not embed it in SSML, as - * that would produce invalid markup. Fall through to the - * 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 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, - "%s", - entity); - else - n = asprintf(&buf, - "%c", - c); - } + else + n = asprintf(&buf, + "%c", + s->buf[0]); if (n == -1) { /* D'oh. Not much to do on allocation failure. * Perhaps espeak will happen to say the character */ @@ -369,7 +301,7 @@ static void synth_queue_clear() } } -static int reinitialize_espeak(struct synth_t *s) +static void reinitialize_espeak(struct synth_t *s) { int rate; @@ -377,7 +309,7 @@ static int reinitialize_espeak(struct synth_t *s) rate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, NULL, 0); if (rate < 0) { fprintf(stderr, "Unable to initialize espeak.\n"); - return -1; + return; } espeak_SetSynthCallback(callback); @@ -390,88 +322,24 @@ static int reinitialize_espeak(struct synth_t *s) espeak_SetParameter(espeakVOLUME, (s->volume + 1) * volumeMultiplier, 0); espeak_SetParameter(espeakCAPITALS, 0, 0); paused_espeak = 0; - return 0; + return; } -/* 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) { - espeak_ERROR error = EE_OK; + espeak_ERROR error; char markbuff[50]; + static struct espeak_entry_t *current = NULL; if (current != queue_peek(synth_queue)) { if (current) free_espeak_entry(current); - current = queue_peek(synth_queue); + current = (struct espeak_entry_t *) queue_remove(synth_queue); } pthread_mutex_unlock(&queue_guard); if (current->cmd != CMD_PAUSE && paused_espeak) { - 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; - } + reinitialize_espeak(s); } switch (current->cmd) { @@ -509,48 +377,18 @@ static void queue_process_entry(struct synth_t *s) break; case CMD_PAUSE: if (!paused_espeak) { - error = espeak_Cancel(); - if (error == EE_OK) - error = espeak_Terminate(); - if (error == EE_OK) - paused_espeak = 1; - } else { - error = EE_OK; + espeak_Cancel(); + espeak_Terminate(); + paused_espeak = 1; } break; default: - /* Uh? */ - error = EE_OK; break; } - pthread_mutex_lock(&queue_guard); if (error == EE_OK) { - /* Processed, drop it */ - struct espeak_entry_t *unqueued = queue_remove(synth_queue); - assert(unqueued == current); free_espeak_entry(current); 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); } } @@ -612,17 +450,7 @@ void *espeak_thread(void *arg) pthread_cond_wait(&runner_awake, &queue_guard); 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(); - pthread_mutex_lock(&queue_guard); synth_queue_clear(); stop_requested = 0; pthread_cond_signal(&stop_acknowledged); @@ -630,6 +458,7 @@ void *espeak_thread(void *arg) while (should_run && queue_peek(synth_queue) && !stop_requested) { queue_process_entry(s); + pthread_mutex_lock(&queue_guard); } } pthread_cond_signal(&stop_acknowledged); diff --git a/src/espeakup.c b/src/espeakup.c index 4e077f5..86b3a88 100644 --- a/src/espeakup.c +++ b/src/espeakup.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include "espeakup.h" @@ -41,9 +40,7 @@ volatile int should_run = 1; espeak_AUDIO_OUTPUT audio_mode; pthread_cond_t runner_awake = PTHREAD_COND_INITIALIZER; -/* Initialized in main: use the monotonic clock for timed waits. */ -pthread_cond_t wake_stop; -pthread_cond_t stop_acknowledged; +pthread_cond_t stop_acknowledged = PTHREAD_COND_INITIALIZER; pthread_mutex_t queue_guard = PTHREAD_MUTEX_INITIALIZER; int espeakup_start_daemon(void) @@ -149,18 +146,6 @@ int main(int argc, char **argv) struct synth_t s = { .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(); if (!synth_queue) { diff --git a/src/espeakup.h b/src/espeakup.h index 62a5ca8..58d0f6e 100644 --- a/src/espeakup.h +++ b/src/espeakup.h @@ -99,7 +99,6 @@ extern int self_pipe_fds[2]; #define PIPE_WRITE_FD (self_pipe_fds[1]) extern pthread_cond_t runner_awake; -extern pthread_cond_t wake_stop; extern pthread_cond_t stop_acknowledged; extern pthread_mutex_t queue_guard; diff --git a/src/meson.build b/src/meson.build index 6221208..f1c4610 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,3 +1,6 @@ +espeakup_version = vcs_tag(input : 'version.h.in', output : 'version.h') + + espeakup_sources = files([ 'cli.c', 'espeak.c', @@ -7,4 +10,3 @@ espeakup_sources = files([ 'softsynth.c', 'stringhandling.c' ]) -espeakup_version = vcs_tag(input : 'version.h.in', output : 'version.h') diff --git a/src/signal.c b/src/signal.c index b6ddefc..f0b4d57 100644 --- a/src/signal.c +++ b/src/signal.c @@ -58,14 +58,6 @@ void *signal_thread(void *arg) case SIGTERM: pthread_mutex_lock(&queue_guard); 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); break; default: diff --git a/src/softsynth.c b/src/softsynth.c index e382b32..7d14708 100644 --- a/src/softsynth.c +++ b/src/softsynth.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include "espeakup.h" @@ -224,37 +223,14 @@ 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) { - struct timespec timeout; - int err = 0; - pthread_mutex_lock(&queue_guard); stop_requested = 1; pthread_cond_signal(&runner_awake); // Wake runner, if necessary. - 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) + while (should_run && stop_requested) // wait for acknowledgement. - 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_cond_wait(&stop_acknowledged, &queue_guard); pthread_mutex_unlock(&queue_guard); } @@ -273,7 +249,7 @@ int open_softsynth(void) // Kernel without unicode support? Try without unicode. softFD = open("/dev/softsynth", O_RDWR | O_NONBLOCK); if (softFD < 0) { - perror("Unable to open the /dev/softsynth device"); + perror("Unable to open the softsynth device"); rc = -1; } return rc; diff --git a/src/stringhandling.c b/src/stringhandling.c index 096b870..86ed965 100644 --- a/src/stringhandling.c +++ b/src/stringhandling.c @@ -62,17 +62,6 @@ void *reallocMem(void *p, size_t n) 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) { *l = 0; diff --git a/src/stringhandling.h b/src/stringhandling.h index 7f104ef..d138933 100644 --- a/src/stringhandling.h +++ b/src/stringhandling.h @@ -26,7 +26,6 @@ extern char *EMPTYSTRING; void *allocMem(size_t n); void *reallocMem(void *p, size_t n); -char *dupeString(char *s); char *initString(int *l); void stringAndString(char **s, int *l, const char *t); void stringAndBytes(char **s, int *l, const char *t, int cnt);