Coverage Report

Created: 2026-06-10 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/espeak-ng/src/libespeak-ng/fifo.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2007, Gilles Casse <gcasse@oralux.org>
3
 * Copyright (C) 2013-2016 Reece H. Dunn
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, see: <http://www.gnu.org/licenses/>.
17
 */
18
19
// This source file is only used for asynchronous modes
20
21
#include "config.h"
22
23
#include <assert.h>
24
#include <errno.h>
25
#include <pthread.h>
26
#include <stdbool.h>
27
#include <stdint.h>
28
#include <stdlib.h>
29
#include <string.h>
30
#include <sys/time.h>
31
#include <time.h>
32
#include <unistd.h>
33
34
#include <espeak-ng/espeak_ng.h>
35
36
#include "speech.h"
37
#include "espeak_command.h"
38
#include "fifo.h"
39
#include "event.h"
40
41
#if USE_ASYNC
42
43
// my_mutex: protects my_thread_is_talking,
44
// my_stop_is_required, and the command fifo
45
static pthread_mutex_t my_mutex;
46
static bool my_command_is_running = false;
47
static pthread_cond_t my_cond_command_is_running;
48
static bool my_stop_is_required = false;
49
static bool my_terminate_is_required = 0;
50
51
// my_thread: reads commands from the fifo, and runs them.
52
static pthread_t my_thread;
53
54
static pthread_cond_t my_cond_start_is_required;
55
static bool my_start_is_required = false;
56
57
static pthread_cond_t my_cond_stop_is_acknowledged;
58
static bool my_stop_is_acknowledged = false;
59
60
static void *say_thread(void *);
61
62
static espeak_ng_STATUS push(t_espeak_command *the_command);
63
static t_espeak_command *pop(void);
64
static void init(int process_parameters);
65
static int node_counter = 0;
66
static bool thread_inited = false;
67
68
enum {
69
  MAX_NODE_COUNTER = 400,
70
  INACTIVITY_TIMEOUT = 50, // in ms, check that the stream is inactive
71
  MAX_INACTIVITY_CHECK = 2
72
};
73
74
void fifo_init(void)
75
0
{
76
  // security
77
0
  pthread_mutex_init(&my_mutex, (const pthread_mutexattr_t *)NULL);
78
0
  init(0);
79
80
0
  int a_status;
81
0
  a_status = pthread_cond_init(&my_cond_command_is_running, NULL);
82
0
  assert(-1 != a_status);
83
0
  a_status = pthread_cond_init(&my_cond_start_is_required, NULL);
84
0
  assert(-1 != a_status);
85
0
  a_status = pthread_cond_init(&my_cond_stop_is_acknowledged, NULL);
86
0
  assert(-1 != a_status);
87
88
0
  pthread_attr_t a_attrib;
89
0
  if (pthread_attr_init(&a_attrib)
90
0
      || pthread_attr_setdetachstate(&a_attrib, PTHREAD_CREATE_JOINABLE)
91
0
      || pthread_create(&my_thread,
92
0
                        &a_attrib,
93
0
                        say_thread,
94
0
                        (void *)NULL)) {
95
0
    assert(0);
96
0
  }
97
0
  thread_inited = true;
98
99
0
  pthread_attr_destroy(&a_attrib);
100
101
  // leave once the thread is actually started
102
0
  a_status = pthread_mutex_lock(&my_mutex);
103
0
  assert(-1 != a_status);
104
0
  (void)a_status;
105
0
  while (my_stop_is_acknowledged == false) {
106
0
    while ((pthread_cond_wait(&my_cond_stop_is_acknowledged, &my_mutex) == -1) && errno == EINTR)
107
0
      ;
108
0
  }
109
0
  my_stop_is_acknowledged = false;
110
0
  pthread_mutex_unlock(&my_mutex);
111
0
}
112
113
espeak_ng_STATUS fifo_add_command(t_espeak_command *the_command)
114
0
{
115
0
  espeak_ng_STATUS status;
116
0
  if ((status = pthread_mutex_lock(&my_mutex)) != ENS_OK)
117
0
    return status;
118
119
0
  if ((status = push(the_command)) != ENS_OK) {
120
0
    pthread_mutex_unlock(&my_mutex);
121
0
    return status;
122
0
  }
123
  
124
0
  my_start_is_required = true;
125
0
  pthread_cond_signal(&my_cond_start_is_required);
126
127
0
  while (my_start_is_required && !my_command_is_running) {
128
0
    if((status = pthread_cond_wait(&my_cond_command_is_running, &my_mutex)) != ENS_OK && errno != EINTR) {
129
0
      pthread_mutex_unlock(&my_mutex);
130
0
      return status;
131
0
    }
132
0
  }
133
0
  if ((status = pthread_mutex_unlock(&my_mutex)) != ENS_OK)
134
0
    return status;
135
136
0
  return ENS_OK;
137
0
}
138
139
espeak_ng_STATUS fifo_add_commands(t_espeak_command *command1, t_espeak_command *command2)
140
0
{
141
0
  espeak_ng_STATUS status;
142
0
  if (!thread_inited) {
143
0
    return ENS_NOT_INITIALIZED;
144
0
  }
145
0
  if ((status = pthread_mutex_lock(&my_mutex)) != ENS_OK)
146
0
    return status;
147
148
0
  if (node_counter+1 >= MAX_NODE_COUNTER) {
149
0
    pthread_mutex_unlock(&my_mutex);
150
0
    return ENS_FIFO_BUFFER_FULL;
151
0
  }
152
153
0
  if ((status = push(command1)) != ENS_OK) {
154
0
    pthread_mutex_unlock(&my_mutex);
155
0
    return status;
156
0
  }
157
158
0
  if ((status = push(command2)) != ENS_OK) {
159
0
    pthread_mutex_unlock(&my_mutex);
160
0
    return status;
161
0
  }
162
163
0
  my_start_is_required = true;
164
0
  pthread_cond_signal(&my_cond_start_is_required);
165
  
166
0
  while (my_start_is_required && !my_command_is_running) {
167
0
    if((status = pthread_cond_wait(&my_cond_command_is_running, &my_mutex)) != ENS_OK && errno != EINTR) {
168
0
      pthread_mutex_unlock(&my_mutex);
169
0
      return status;
170
0
    }
171
0
  }
172
0
  if ((status = pthread_mutex_unlock(&my_mutex)) != ENS_OK)
173
0
    return status;
174
175
0
  return ENS_OK;
176
0
}
177
178
espeak_ng_STATUS fifo_stop(void)
179
0
{
180
0
  if (!thread_inited) return ENS_OK;
181
0
  espeak_ng_STATUS status;
182
0
  if ((status = pthread_mutex_lock(&my_mutex)) != ENS_OK)
183
0
    return status;
184
185
0
  bool a_command_is_running = false;
186
0
  if (my_command_is_running) {
187
0
    a_command_is_running = true;
188
0
    my_stop_is_required = true;
189
0
    my_stop_is_acknowledged = false;
190
0
  }
191
192
0
  if (a_command_is_running) {
193
0
    while (my_stop_is_acknowledged == false) {
194
0
      while ((pthread_cond_wait(&my_cond_stop_is_acknowledged, &my_mutex) == -1) && errno == EINTR)
195
0
        continue; // Restart when interrupted by handler
196
0
    }
197
0
  }
198
199
0
  my_stop_is_required = false;
200
0
  if ((status = pthread_mutex_unlock(&my_mutex)) != ENS_OK)
201
0
    return status;
202
203
0
  return ENS_OK;
204
0
}
205
206
int fifo_is_busy(void)
207
0
{
208
0
  if (!thread_inited) return false;
209
0
  pthread_mutex_lock(&my_mutex);
210
0
  bool running = my_command_is_running;
211
0
  pthread_mutex_unlock(&my_mutex);
212
0
  return running;
213
0
}
214
215
static int sleep_until_start_request_or_inactivity(void)
216
0
{
217
0
  int a_start_is_required = false;
218
219
  // Wait for the start request (my_cond_start_is_required).
220
  // Besides this, if the audio stream is still busy,
221
  // check from time to time its end.
222
  // The end of the stream is confirmed by several checks
223
  // for filtering underflow.
224
  //
225
0
  int i = 0;
226
0
  int err = pthread_mutex_lock(&my_mutex);
227
0
  assert(err != -1);
228
0
  while ((i <= MAX_INACTIVITY_CHECK) && !a_start_is_required) {
229
0
    i++;
230
231
0
    struct timespec ts;
232
233
0
    clock_gettime2(&ts);
234
235
0
    add_time_in_ms(&ts, INACTIVITY_TIMEOUT);
236
237
0
    while ((err = pthread_cond_timedwait(&my_cond_start_is_required, &my_mutex, &ts)) == -1
238
0
           && errno == EINTR)
239
0
      continue;
240
241
0
    if (err == 0)
242
0
      a_start_is_required = true;
243
0
  }
244
0
  pthread_mutex_unlock(&my_mutex);
245
0
  return a_start_is_required;
246
0
}
247
248
static espeak_ng_STATUS close_stream(void)
249
0
{
250
0
  espeak_ng_STATUS status = pthread_mutex_lock(&my_mutex);
251
0
  if (status != ENS_OK)
252
0
    return status;
253
254
0
  bool a_stop_is_required = my_stop_is_required;
255
0
  if (!a_stop_is_required)
256
0
    my_command_is_running = true;
257
258
0
  status = pthread_mutex_unlock(&my_mutex);
259
260
0
  if (!a_stop_is_required) {
261
0
    int a_status = pthread_mutex_lock(&my_mutex);
262
0
    if (status == ENS_OK)
263
0
      status = a_status;
264
265
0
    my_command_is_running = false;
266
0
    a_stop_is_required = my_stop_is_required;
267
268
0
    a_status = pthread_mutex_unlock(&my_mutex);
269
0
    if (status == ENS_OK)
270
0
      status = a_status;
271
272
0
    if (a_stop_is_required) {
273
      // cancel the audio early, to be more responsive when using eSpeak NG
274
      // for audio.
275
0
      cancel_audio();
276
277
      // acknowledge the stop request
278
0
      if((a_status = pthread_mutex_lock(&my_mutex)) != ENS_OK)
279
0
        return a_status;
280
281
0
      my_stop_is_acknowledged = true;
282
0
      a_status = pthread_cond_signal(&my_cond_stop_is_acknowledged);
283
0
      if(a_status != ENS_OK)
284
0
        return a_status;
285
0
      a_status = pthread_mutex_unlock(&my_mutex);
286
0
      if (status == ENS_OK)
287
0
        status = a_status;
288
      
289
0
    }
290
0
  }
291
292
0
  return status;
293
0
}
294
295
static void *say_thread(void *p)
296
0
{
297
0
  (void)p; // unused
298
299
0
  int a_status;
300
301
  // announce that thread is started
302
0
  a_status = pthread_mutex_lock(&my_mutex);
303
0
  assert(-1 != a_status);
304
0
  my_stop_is_acknowledged = true;
305
0
  a_status = pthread_cond_signal(&my_cond_stop_is_acknowledged);
306
0
  assert(-1 != a_status);
307
0
  a_status = pthread_mutex_unlock(&my_mutex);
308
0
  assert(-1 != a_status);
309
310
0
  bool look_for_inactivity = false;
311
312
0
  while (!my_terminate_is_required) {
313
0
    bool a_start_is_required = false;
314
0
    if (look_for_inactivity) {
315
0
      a_start_is_required = sleep_until_start_request_or_inactivity();
316
0
      if (!a_start_is_required)
317
0
        close_stream();
318
0
    }
319
0
    look_for_inactivity = true;
320
321
0
    a_status = pthread_mutex_lock(&my_mutex);
322
0
    assert(!a_status);
323
324
0
    if (!a_start_is_required) {
325
0
      while (my_start_is_required == false && my_terminate_is_required == false) {
326
0
        while ((pthread_cond_wait(&my_cond_start_is_required, &my_mutex) == -1) && errno == EINTR)
327
0
          continue; // Restart when interrupted by handler
328
0
      }
329
0
    }
330
331
332
0
    my_command_is_running = true;
333
334
0
    a_status = pthread_cond_broadcast(&my_cond_command_is_running);
335
0
    assert(-1 != a_status);
336
0
    a_status = pthread_mutex_unlock(&my_mutex);
337
0
    assert(-1 != a_status);
338
339
0
    while (my_command_is_running && !my_terminate_is_required) {
340
0
      a_status = pthread_mutex_lock(&my_mutex);
341
0
      assert(!a_status);
342
0
      t_espeak_command *a_command = (t_espeak_command *)pop();
343
344
0
      if (a_command == NULL) {
345
0
        my_command_is_running = false;
346
0
        a_status = pthread_mutex_unlock(&my_mutex);
347
0
      } else {
348
0
        my_start_is_required = false;
349
350
0
        if (my_stop_is_required)
351
0
          my_command_is_running = false;
352
0
        a_status = pthread_mutex_unlock(&my_mutex);
353
354
0
        if (my_command_is_running)
355
0
          process_espeak_command(a_command);
356
0
        delete_espeak_command(a_command);
357
0
      }
358
0
    }
359
360
0
    if (my_stop_is_required || my_terminate_is_required) {
361
      // no mutex required since the stop command is synchronous
362
      // and waiting for my_cond_stop_is_acknowledged
363
0
      init(1);
364
365
0
      a_status = pthread_mutex_lock(&my_mutex);
366
0
      assert(-1 != a_status);
367
0
      my_start_is_required = false;
368
369
      // acknowledge the stop request
370
0
      my_stop_is_acknowledged = true;
371
0
      a_status = pthread_cond_signal(&my_cond_stop_is_acknowledged);
372
0
      assert(a_status != -1);
373
0
      pthread_mutex_unlock(&my_mutex);
374
375
0
    }
376
    // and wait for the next start
377
0
  }
378
0
  (void)a_status;
379
380
0
  return NULL;
381
0
}
382
383
int fifo_is_command_enabled(void)
384
0
{
385
0
  return 0 == my_stop_is_required;
386
0
}
387
388
typedef struct t_node {
389
  t_espeak_command *data;
390
  struct t_node *next;
391
} node;
392
393
static node *head = NULL;
394
static node *tail = NULL;
395
396
static espeak_ng_STATUS push(t_espeak_command *the_command)
397
0
{
398
0
  assert((!head && !tail) || (head && tail));
399
400
0
  if (the_command == NULL)
401
0
    return EINVAL;
402
403
0
  if (node_counter >= MAX_NODE_COUNTER)
404
0
    return ENS_FIFO_BUFFER_FULL;
405
406
0
  node *n = (node *)malloc(sizeof(node));
407
0
  if (n == NULL)
408
0
    return ENOMEM;
409
410
0
  if (head == NULL) {
411
0
    head = n;
412
0
    tail = n;
413
0
  } else {
414
0
    tail->next = n;
415
0
    tail = n;
416
0
  }
417
418
0
  tail->next = NULL;
419
0
  tail->data = the_command;
420
421
0
  node_counter++;
422
423
0
  the_command->state = CS_PENDING;
424
425
0
  return ENS_OK;
426
0
}
427
428
static t_espeak_command *pop(void)
429
0
{
430
0
  t_espeak_command *the_command = NULL;
431
432
0
  assert((!head && !tail) || (head && tail));
433
434
0
  if (head != NULL) {
435
0
    node *n = head;
436
0
    the_command = n->data;
437
0
    head = n->next;
438
0
    free(n);
439
0
    node_counter--;
440
0
  }
441
442
0
  if (head == NULL)
443
0
    tail = NULL;
444
445
0
  return the_command;
446
0
}
447
448
static void init(int process_parameters)
449
0
{
450
0
  t_espeak_command *c = NULL;
451
0
  c = pop();
452
0
  while (c != NULL) {
453
0
    if (process_parameters && (c->type == ET_PARAMETER || c->type == ET_VOICE_NAME || c->type == ET_VOICE_SPEC))
454
0
      process_espeak_command(c);
455
0
    delete_espeak_command(c);
456
0
    c = pop();
457
0
  }
458
0
  node_counter = 0;
459
0
}
460
461
void fifo_terminate(void)
462
0
{
463
0
  if (!thread_inited) return;
464
465
0
  pthread_mutex_lock(&my_mutex);
466
0
  my_terminate_is_required = true;
467
0
  pthread_mutex_unlock(&my_mutex);
468
0
  pthread_cond_signal(&my_cond_start_is_required);
469
0
  pthread_join(my_thread, NULL);
470
0
  my_terminate_is_required = false;
471
0
  thread_inited = false;
472
473
0
  pthread_mutex_destroy(&my_mutex);
474
0
  pthread_cond_destroy(&my_cond_start_is_required);
475
0
  pthread_cond_destroy(&my_cond_stop_is_acknowledged);
476
477
0
  init(0); // purge fifo
478
0
}
479
480
#endif