@@ -20,7 +20,7 @@ build: | |||
* Build the code with a C99 compiler, instead of a C++ compiler. | |||
* Provide a pkg-config file (patch by Luke Yelavich). | |||
* Use -fPIC to support sparc/sparc64 architectures. | |||
* Use the system's portaudio header files. | |||
* Removed the local portaudio header files. | |||
* Use the system's sonic library and header files. | |||
* Output phoneme compilation errors to stderr. | |||
* Generate build failures if building phoneme, intonation or dictionary files | |||
@@ -44,7 +44,10 @@ restructuring: | |||
* Converted the documentation to markdown. | |||
* Group the Windows and POSIX mbrowrap code to provide the `mbrowrap.h` | |||
implementation in a single place. | |||
* Use the `wave_*` APIs in synchronous playback mode. | |||
* Replaced the audio APIs with PCAudioLib to improve portability of the audio | |||
and to share that across different projects. | |||
* Reworked the synchronous audio to share the code paths with asynchronous | |||
audio. | |||
cleanup: | |||
@@ -102,10 +102,14 @@ espeak_ng_include_HEADERS = \ | |||
lib_LTLIBRARIES += src/libespeak-ng.la | |||
src_libespeak_ng_la_LDFLAGS = -version-info $(SHARED_VERSION) -lpthread -lm | |||
src_libespeak_ng_la_LDFLAGS = -version-info $(SHARED_VERSION) -lpthread -lm \ | |||
${PCAUDIOLIB_LIBS} | |||
src_libespeak_ng_la_CFLAGS = -Isrc/include -Isrc/include/compat \ | |||
-fPIC -fvisibility=hidden -D _BSD_SOURCE -D_DEFAULT_SOURCE -D _POSIX_C_SOURCE=200112L \ | |||
-pedantic -fno-exceptions -D PATH_ESPEAK_DATA=\"$(DATADIR)\" -DLIBESPEAK_NG_EXPORT | |||
-pedantic -fno-exceptions -D PATH_ESPEAK_DATA=\"$(DATADIR)\" -DLIBESPEAK_NG_EXPORT \ | |||
${PCAUDIOLIB_CFLAGS} | |||
src_libespeak_ng_la_SOURCES = \ | |||
src/libespeak-ng/compiledata.c \ | |||
src/libespeak-ng/compiledict.c \ | |||
@@ -147,33 +151,6 @@ src_libespeak_ng_la_SOURCES += \ | |||
src/libespeak-ng/fifo.c | |||
endif | |||
if AUDIO_RUNTIME | |||
src_libespeak_ng_la_LDFLAGS += -lpulse -lpulse-simple -lportaudio | |||
src_libespeak_ng_la_CFLAGS += -DUSE_PULSEAUDIO -DUSE_PORTAUDIO | |||
src_libespeak_ng_la_SOURCES += \ | |||
src/libespeak-ng/wave.c \ | |||
src/libespeak-ng/wave_pulse.c | |||
else | |||
if AUDIO_PULSEAUDIO | |||
src_libespeak_ng_la_LDFLAGS += -lpulse | |||
src_libespeak_ng_la_CFLAGS += -DUSE_PULSEAUDIO | |||
src_libespeak_ng_la_SOURCES += src/libespeak-ng/wave_pulse.c | |||
else | |||
if AUDIO_PORTAUDIO | |||
src_libespeak_ng_la_LDFLAGS += -lportaudio | |||
src_libespeak_ng_la_CFLAGS += -DUSE_PORTAUDIO | |||
src_libespeak_ng_la_SOURCES += src/libespeak-ng/wave.c | |||
else | |||
if AUDIO_SADA | |||
src_libespeak_ng_la_CFLAGS += -DUSE_SADA | |||
src_libespeak_ng_la_SOURCES += src/libespeak-ng/wave_sada.c | |||
else | |||
src_libespeak_ng_la_SOURCES += src/libespeak-ng/wave.c | |||
endif | |||
endif | |||
endif | |||
endif | |||
bin_PROGRAMS += src/speak-ng | |||
if HAVE_RONN | |||
@@ -181,7 +158,7 @@ man1_MANS += src/speak-ng.1 | |||
endif | |||
src_speak_ng_LDADD = src/libespeak-ng.la | |||
src_speak_ng_LDFLAGS = -static -lm | |||
src_speak_ng_LDFLAGS = -static -lm ${PCAUDIOLIB_LIBS} | |||
src_speak_ng_CFLAGS = -Isrc/libespeak-ng -Isrc/include -D _POSIX_C_SOURCE=200112L | |||
src_speak_ng_SOURCES = src/speak-ng.c | |||
@@ -191,7 +168,7 @@ if HAVE_RONN | |||
man1_MANS += src/espeak-ng.1 | |||
endif | |||
src_espeak_ng_LDADD = src/libespeak-ng.la | |||
src_espeak_ng_LDADD = src/libespeak-ng.la ${PCAUDIOLIB_LIBS} | |||
src_espeak_ng_CFLAGS = -Isrc/include | |||
src_espeak_ng_SOURCES = src/espeak-ng.c | |||
@@ -38,8 +38,8 @@ In order to build eSpeak NG, you need: | |||
Optionally, you need: | |||
1. the pulseaudio development library to enable pulseaudio output; | |||
2. the portaudio development library to enable portaudio output; | |||
1. the [pcaudiolib](https://github.com/rhdunn/pcaudiolib) development library | |||
to enable audio output; | |||
3. the [sonic](https://github.com/waywardgeek/sonic) development library to | |||
enable sonic audio speed up support; | |||
4. the `ronn` man-page markdown processor to build the man pages. | |||
@@ -61,9 +61,6 @@ Optional dependencies: | |||
| Dependency | Install | | |||
|--------------|-----------------------------------------| | |||
| pulseaudio | `sudo apt-get install libpulse-dev` | | |||
| portaudio 18 | `sudo apt-get install libportaudio-dev` | | |||
| portaudio 19 | `sudo apt-get install portaudio19-dev` | | |||
| sonic | `sudo apt-get install libsonic-dev` | | |||
| ronn | `sudo apt-get install ruby-ronn` | | |||
@@ -104,104 +104,29 @@ AC_CHECK_FUNCS([strrchr]) | |||
AC_CHECK_FUNCS([strstr]) | |||
dnl ================================================================ | |||
dnl PulseAudio checks. | |||
dnl PCAudioLib checks. | |||
dnl ================================================================ | |||
AC_ARG_WITH([pulseaudio], | |||
[AS_HELP_STRING([--with-pulseaudio], [use the pulseaudio library for audio output @<:@default=yes@:>@])], | |||
AC_ARG_WITH([pcaudiolib], | |||
[AS_HELP_STRING([--with-pcaudiolib], [use the pcaudiolib library for audio output @<:@default=yes@:>@])], | |||
[]) | |||
if test "$with_pulseaudio" = "no"; then | |||
echo "Disabling pulseaudio output support via pulseaudio" | |||
have_pulseaudio=no | |||
if test "$with_pcaudiolib" = "no"; then | |||
echo "Disabling audio output support via pcaudiolib" | |||
have_pcaudiolib=no | |||
else | |||
PKG_CHECK_MODULES(PULSEAUDIO, [libpulse >= 0.9], | |||
AC_CHECK_HEADERS([pcaudiolib/audio.h], | |||
[ | |||
have_pulseaudio=yes | |||
have_pcaudiolib=yes | |||
PCAUDIOLIB_CFLAGS= | |||
PCAUDIOLIB_LIBS=-lpcaudio | |||
],[ | |||
have_pulseaudio=no | |||
have_pcaudiolib=no | |||
]) | |||
fi | |||
dnl ================================================================ | |||
dnl PortAudio checks. | |||
dnl ================================================================ | |||
AC_ARG_WITH([portaudio], | |||
[AS_HELP_STRING([--with-portaudio], [use the portaudio library for audio output @<:@default=yes@:>@])], | |||
[]) | |||
if test "$with_portaudio" = "no"; then | |||
echo "Disabling portaudio output support via portaudio" | |||
have_portaudio=no | |||
else | |||
AC_CHECK_HEADERS([portaudio.h], | |||
[ | |||
# Check the portaudio library. | |||
AC_CHECK_LIB([portaudio], [Pa_IsStreamActive]) # portaudio 19 | |||
AC_CHECK_LIB([portaudio], [Pa_StreamActive]) # portaudio 18 | |||
# Then use the headers to determine the portaudio version. | |||
# This is because on some systems with both libportaudio0 and | |||
# libportaudio2 installed, portaudio.h and -lportaudio refer | |||
# to different versions. | |||
AC_CHECK_FUNC([Pa_IsStreamActive], | |||
[ | |||
have_portaudio=19 | |||
],[ | |||
AC_CHECK_FUNC([Pa_StreamActive], | |||
[ | |||
have_portaudio=18 | |||
],[ | |||
have_portaudio=no | |||
]) | |||
]) | |||
],[ | |||
have_portaudio=no | |||
]) | |||
fi | |||
dnl ================================================================ | |||
dnl Audio checks. | |||
dnl ================================================================ | |||
AC_ARG_WITH([sada], | |||
[AS_HELP_STRING([--with-sada], [use the Solaris SADA audio API @<:@default=no@:>@])], | |||
[]) | |||
if test "$with_sada" = "yes" ; then | |||
have_sada=yes | |||
else | |||
have_sada=no | |||
fi | |||
if test "$have_portaudio" = 18 -o "$have_portaudio" = 19 ; then | |||
if test "$have_pulseaudio" = yes ; then | |||
PKG_CHECK_MODULES(PULSEAUDIO_SIMPLE, [libpulse-simple >= 0.9], | |||
[ | |||
have_pulseaudio=yes | |||
AUDIO=runtime | |||
],[ | |||
have_pulseaudio=no | |||
AUDIO=portaudio | |||
]) | |||
else | |||
AUDIO=portaudio | |||
fi | |||
elif test "$have_pulseaudio" = yes ; then | |||
AUDIO=pulseaudio | |||
elif test "$have_sada" = yes ; then | |||
AUDIO=sada | |||
else | |||
AUDIO=disabled | |||
fi | |||
AC_SUBST(AUDIO) | |||
AM_CONDITIONAL(AUDIO_RUNTIME, [test x"$AUDIO" = xruntime]) | |||
AM_CONDITIONAL(AUDIO_PULSEAUDIO, [test x"$AUDIO" = xpulseaudio]) | |||
AM_CONDITIONAL(AUDIO_PORTAUDIO, [test x"$AUDIO" = xportaudio]) | |||
AM_CONDITIONAL(AUDIO_SADA, [test x"$AUDIO" = xsada]) | |||
AC_SUBST(PCAUDIOLIB_CFLAGS) | |||
AC_SUBST(PCAUDIOLIB_LIBS) | |||
dnl ================================================================ | |||
dnl Optional compilation checks. | |||
@@ -319,14 +244,11 @@ AC_MSG_NOTICE([ | |||
C99 Compiler: ${CC} | |||
C99 Compiler flags: ${CFLAGS} | |||
pulseaudio: ${have_pulseaudio} | |||
portaudio: ${have_portaudio} | |||
sada: ${have_sada} | |||
audio configuration: ${AUDIO} | |||
Sonic: ${have_sonic} | |||
PCAudioLib: ${have_pcaudiolib} | |||
Klatt: ${have_klatt} | |||
MBROLA: ${have_mbrola} | |||
Sonic: ${have_sonic} | |||
Async: ${have_async} | |||
Extended Dictionaries: |
@@ -33,6 +33,10 @@ | |||
#include <unistd.h> | |||
#include <wchar.h> | |||
#ifdef HAVE_PCAUDIOLIB_AUDIO_H | |||
#include <pcaudiolib/audio.h> | |||
#endif | |||
#if defined(_WIN32) || defined(_WIN64) | |||
#include <fcntl.h> | |||
#include <io.h> | |||
@@ -51,7 +55,6 @@ | |||
#include "espeak_command.h" | |||
#include "fifo.h" | |||
#include "event.h" | |||
#include "wave.h" | |||
#ifndef S_ISDIR | |||
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) | |||
@@ -63,7 +66,9 @@ espeak_EVENT *event_list = NULL; | |||
int event_list_ix = 0; | |||
int n_event_list; | |||
long count_samples; | |||
void *my_audio = NULL; | |||
#ifdef HAVE_PCAUDIOLIB_AUDIO_H | |||
struct audio_object *my_audio = NULL; | |||
#endif | |||
static const char *option_device = NULL; | |||
static unsigned int my_unique_identifier = 0; | |||
@@ -107,17 +112,21 @@ static int dispatch_audio(short *outbuf, int length, espeak_EVENT *event) | |||
voice_samplerate = event->id.number; | |||
if (out_samplerate != voice_samplerate) { | |||
#ifdef HAVE_PCAUDIOLIB_AUDIO_H | |||
if (out_samplerate != 0) { | |||
// sound was previously open with a different sample rate | |||
wave_close(my_audio); | |||
audio_object_close(my_audio); | |||
sleep(1); | |||
} | |||
#endif | |||
out_samplerate = voice_samplerate; | |||
my_audio = wave_open(voice_samplerate, option_device); | |||
#ifdef HAVE_PCAUDIOLIB_AUDIO_H | |||
audio_object_open(my_audio, AUDIO_OBJECT_FORMAT_S16LE, voice_samplerate, 1); | |||
if (!my_audio) { | |||
err = ENS_AUDIO_ERROR; | |||
return -1; | |||
} | |||
#endif | |||
#ifdef USE_ASYNC | |||
if ((my_mode & ENOUTPUT_MODE_SYNCHRONOUS) == 0) | |||
event_init(); | |||
@@ -125,9 +134,10 @@ static int dispatch_audio(short *outbuf, int length, espeak_EVENT *event) | |||
} | |||
} | |||
if (outbuf && length && a_wave_can_be_played) { | |||
wave_write(my_audio, (char *)outbuf, 2*length); | |||
} | |||
#ifdef HAVE_PCAUDIOLIB_AUDIO_H | |||
if (outbuf && length && a_wave_can_be_played) | |||
audio_object_write(my_audio, (char *)outbuf, 2*length); | |||
#endif | |||
#ifdef USE_ASYNC | |||
while (event && a_wave_can_be_played) { | |||
@@ -218,9 +228,12 @@ ESPEAK_NG_API espeak_ng_STATUS espeak_ng_InitializeOutput(espeak_ng_OUTPUT_MODE | |||
{ | |||
option_device = device; | |||
my_mode = output_mode; | |||
my_audio = NULL; | |||
out_samplerate = 0; | |||
#ifdef HAVE_PCAUDIOLIB_AUDIO_H | |||
my_audio = create_audio_device_object(device, "eSpeak", "Text-to-Speech"); | |||
#endif | |||
// buflength is in mS, allocate 2 bytes per sample | |||
if ((buffer_length == 0) || (output_mode & ENOUTPUT_MODE_SPEAK_AUDIO)) | |||
buffer_length = 200; | |||
@@ -486,8 +499,10 @@ espeak_ng_STATUS sync_espeak_Synth(unsigned int unique_identifier, const void *t | |||
end_character_position = end_position; | |||
espeak_ng_STATUS aStatus = Synthesize(unique_identifier, text, flags); | |||
#ifdef HAVE_PCAUDIOLIB_AUDIO_H | |||
if ((my_mode & ENOUTPUT_MODE_SPEAK_AUDIO) == ENOUTPUT_MODE_SPEAK_AUDIO) | |||
wave_flush(my_audio); | |||
audio_object_drain(my_audio); | |||
#endif | |||
return aStatus; | |||
} | |||
@@ -790,8 +805,10 @@ ESPEAK_NG_API espeak_ng_STATUS espeak_ng_Cancel(void) | |||
event_clear_all(); | |||
#endif | |||
#ifdef HAVE_PCAUDIOLIB_AUDIO_H | |||
if ((my_mode & ENOUTPUT_MODE_SPEAK_AUDIO) == ENOUTPUT_MODE_SPEAK_AUDIO) | |||
wave_close(my_audio); | |||
audio_object_close(my_audio); | |||
#endif | |||
embedded_value[EMBED_T] = 0; // reset echo for pronunciation announcements | |||
for (int i = 0; i < N_SPEECH_PARAM; i++) | |||
@@ -832,8 +849,10 @@ ESPEAK_NG_API espeak_ng_STATUS espeak_ng_Terminate(void) | |||
#endif | |||
if ((my_mode & ENOUTPUT_MODE_SPEAK_AUDIO) == ENOUTPUT_MODE_SPEAK_AUDIO) { | |||
wave_close(my_audio); | |||
wave_terminate(); | |||
#ifdef HAVE_PCAUDIOLIB_AUDIO_H | |||
audio_object_close(my_audio); | |||
audio_object_destroy(my_audio); | |||
#endif | |||
out_samplerate = 0; | |||
} | |||
@@ -1,719 +0,0 @@ | |||
/* | |||
* Copyright (C) 2007, Gilles Casse <[email protected]> | |||
* Copyright (C) 2015-2016 Reece H. Dunn | |||
* based on AudioIO.cc (Audacity-1.2.4b) and wavegen.cpp | |||
* | |||
* This program 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 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program 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: <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "config.h" | |||
#include <assert.h> | |||
#include <math.h> | |||
#include <stdbool.h> | |||
#include <stdint.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <sys/time.h> | |||
#include <time.h> | |||
#include <unistd.h> | |||
#ifdef PLATFORM_WINDOWS | |||
#include <windows.h> | |||
#endif | |||
#include <espeak-ng/espeak_ng.h> | |||
#include "speech.h" | |||
#include "wave.h" | |||
#ifdef USE_PORTAUDIO | |||
#include "portaudio.h" | |||
#undef USE_PORTAUDIO | |||
// determine portaudio version by looking for a #define which is not in V18 | |||
#ifdef paNeverDropInput | |||
#define USE_PORTAUDIO 19 | |||
#else | |||
#define USE_PORTAUDIO 18 | |||
#endif | |||
#ifdef USE_PULSEAUDIO | |||
// create some wrappers for runtime detection | |||
// checked on wave_open | |||
static int pulse_running; | |||
// wave.cpp (this file) | |||
void *wave_port_open(int, const char *); | |||
size_t wave_port_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize); | |||
int wave_port_close(void *theHandler); | |||
void wave_port_terminate(); | |||
void wave_port_flush(void *theHandler); | |||
void *wave_port_test_get_write_buffer(); | |||
// wave_pulse.cpp | |||
int is_pulse_running(); | |||
void *wave_pulse_open(int, const char *); | |||
size_t wave_pulse_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize); | |||
int wave_pulse_close(void *theHandler); | |||
void wave_pulse_terminate(); | |||
void wave_pulse_flush(void *theHandler); | |||
void *wave_pulse_test_get_write_buffer(); | |||
// wrappers | |||
void *wave_open(int srate, const char *device) | |||
{ | |||
pulse_running = is_pulse_running(); | |||
if (pulse_running) | |||
return wave_pulse_open(srate, device); | |||
else | |||
return wave_port_open(srate, device); | |||
} | |||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) | |||
{ | |||
if (pulse_running) | |||
return wave_pulse_write(theHandler, theMono16BitsWaveBuffer, theSize); | |||
else | |||
return wave_port_write(theHandler, theMono16BitsWaveBuffer, theSize); | |||
} | |||
int wave_close(void *theHandler) | |||
{ | |||
if (pulse_running) | |||
return wave_pulse_close(theHandler); | |||
else | |||
return wave_port_close(theHandler); | |||
} | |||
void wave_terminate() | |||
{ | |||
if (pulse_running) | |||
wave_pulse_terminate(); | |||
else | |||
wave_port_terminate(); | |||
} | |||
void wave_flush(void *theHandler) | |||
{ | |||
if (pulse_running) | |||
wave_pulse_flush(theHandler); | |||
else | |||
wave_port_flush(theHandler); | |||
} | |||
// rename functions to be wrapped | |||
#define wave_open wave_port_open | |||
#define wave_write wave_port_write | |||
#define wave_close wave_port_close | |||
#define wave_terminate wave_port_terminate | |||
#define wave_flush wave_port_flush | |||
#endif | |||
#define MAX_SAMPLE_RATE 22050 | |||
#define FRAMES_PER_BUFFER 512 | |||
#define BUFFER_LENGTH (MAX_SAMPLE_RATE*2*sizeof(uint16_t)) | |||
static char myBuffer[BUFFER_LENGTH]; | |||
static char *myRead = NULL; | |||
static char *myWrite = NULL; | |||
static int out_channels = 1; | |||
static int my_stream_could_start = 0; | |||
static int wave_samplerate; | |||
static int mInCallbackFinishedState = false; | |||
#if (USE_PORTAUDIO == 18) | |||
static PaDeviceID myOutputDevice = 0; | |||
static PortAudioStream *pa_stream = NULL; | |||
#endif | |||
#if (USE_PORTAUDIO == 19) | |||
static struct PaStreamParameters myOutputParameters; | |||
static PaStream *pa_stream = NULL; | |||
#endif | |||
static int userdata[4]; | |||
static PaError pa_init_err = 0; | |||
// time measurement | |||
// The read and write position audio stream in the audio stream are measured in ms. | |||
// | |||
// * When the stream is opened, myReadPosition and myWritePosition are cleared. | |||
// * myWritePosition is updated in wave_write. | |||
// * myReadPosition is updated in pa_callback (+ sample delay). | |||
static uint32_t myReadPosition = 0; // in ms | |||
static uint32_t myWritePosition = 0; | |||
static void init_buffer() | |||
{ | |||
myWrite = myBuffer; | |||
myRead = myBuffer; | |||
memset(myBuffer, 0, BUFFER_LENGTH); | |||
myReadPosition = myWritePosition = 0; | |||
} | |||
static unsigned int get_used_mem() | |||
{ | |||
char *aRead = myRead; | |||
char *aWrite = myWrite; | |||
unsigned int used = 0; | |||
assert((aRead >= myBuffer) | |||
&& (aRead <= myBuffer + BUFFER_LENGTH) | |||
&& (aWrite >= myBuffer) | |||
&& (aWrite <= myBuffer + BUFFER_LENGTH)); | |||
if (aRead < aWrite) | |||
used = aWrite - aRead; | |||
else | |||
used = aWrite + BUFFER_LENGTH - aRead; | |||
return used; | |||
} | |||
static PaError start_stream() | |||
{ | |||
PaError err; | |||
my_stream_could_start = 0; | |||
mInCallbackFinishedState = false; | |||
err = Pa_StartStream(pa_stream); | |||
#if USE_PORTAUDIO == 19 | |||
if (err == paStreamIsNotStopped) { | |||
// not sure why we need this, but PA v19 seems to need it | |||
err = Pa_StopStream(pa_stream); | |||
err = Pa_StartStream(pa_stream); | |||
} | |||
#endif | |||
return err; | |||
} | |||
/* This routine will be called by the PortAudio engine when audio is needed. | |||
** It may called at interrupt level on some machines so don't do anything | |||
** that could mess up the system like calling malloc() or free(). | |||
*/ | |||
#if USE_PORTAUDIO == 18 | |||
static int pa_callback(void *inputBuffer, void *outputBuffer, | |||
unsigned long framesPerBuffer, PaTimestamp outTime, void *userData) | |||
#else | |||
static int pa_callback(const void *inputBuffer, void *outputBuffer, | |||
long unsigned int framesPerBuffer, const PaStreamCallbackTimeInfo *outTime, | |||
PaStreamCallbackFlags flags, void *userData) | |||
#endif | |||
{ | |||
(void)inputBuffer; // unused | |||
(void)outTime; // unused | |||
(void)userData; // unused | |||
int aResult = 0; // paContinue | |||
char *aWrite = myWrite; | |||
size_t n = out_channels*sizeof(uint16_t)*framesPerBuffer; | |||
myReadPosition += framesPerBuffer; | |||
if (aWrite >= myRead) { | |||
if ((size_t)(aWrite - myRead) >= n) { | |||
memcpy(outputBuffer, myRead, n); | |||
myRead += n; | |||
} else { | |||
// underflow | |||
aResult = 1; // paComplete; | |||
mInCallbackFinishedState = true; | |||
size_t aUsedMem = 0; | |||
aUsedMem = (size_t)(aWrite - myRead); | |||
if (aUsedMem) | |||
memcpy(outputBuffer, myRead, aUsedMem); | |||
char *p = (char *)outputBuffer + aUsedMem; | |||
memset(p, 0, n - aUsedMem); | |||
myRead = aWrite; | |||
} | |||
} else { | |||
if ((size_t)(myBuffer + BUFFER_LENGTH - myRead) >= n) { | |||
memcpy(outputBuffer, myRead, n); | |||
myRead += n; | |||
} else if ((size_t)(aWrite + BUFFER_LENGTH - myRead) >= n) { | |||
int aTopMem = myBuffer + BUFFER_LENGTH - myRead; | |||
if (aTopMem) | |||
memcpy(outputBuffer, myRead, aTopMem); | |||
int aRest = n - aTopMem; | |||
if (aRest) { | |||
char *p = (char *)outputBuffer + aTopMem; | |||
memcpy(p, myBuffer, aRest); | |||
} | |||
myRead = myBuffer + aRest; | |||
} else { | |||
// underflow | |||
aResult = 1; // paComplete; | |||
int aTopMem = myBuffer + BUFFER_LENGTH - myRead; | |||
if (aTopMem) | |||
memcpy(outputBuffer, myRead, aTopMem); | |||
int aRest = aWrite - myBuffer; | |||
if (aRest) { | |||
char *p = (char *)outputBuffer + aTopMem; | |||
memcpy(p, myBuffer, aRest); | |||
} | |||
size_t aUsedMem = aTopMem + aRest; | |||
char *p = (char *)outputBuffer + aUsedMem; | |||
memset(p, 0, n - aUsedMem); | |||
myRead = aWrite; | |||
} | |||
} | |||
#ifdef ARCH_BIG | |||
// BIG-ENDIAN, swap the order of bytes in each sound sample in the portaudio buffer | |||
int c; | |||
unsigned char *out_ptr; | |||
unsigned char *out_end; | |||
out_ptr = (unsigned char *)outputBuffer; | |||
out_end = out_ptr + framesPerBuffer*2 * out_channels; | |||
while (out_ptr < out_end) { | |||
c = out_ptr[0]; | |||
out_ptr[0] = out_ptr[1]; | |||
out_ptr[1] = c; | |||
out_ptr += 2; | |||
} | |||
#endif | |||
return aResult; | |||
} | |||
void wave_flush(void *theHandler) | |||
{ | |||
(void)theHandler; // unused | |||
if (my_stream_could_start) | |||
start_stream(); | |||
} | |||
static int wave_open_sound() | |||
{ | |||
PaError err = paNoError; | |||
PaError active; | |||
#if USE_PORTAUDIO == 18 | |||
active = Pa_StreamActive(pa_stream); | |||
#else | |||
active = Pa_IsStreamActive(pa_stream); | |||
#endif | |||
if (active == 1) | |||
return 0; | |||
if (active < 0) { | |||
out_channels = 1; | |||
#if USE_PORTAUDIO == 18 | |||
err = Pa_OpenStream(&pa_stream, | |||
// capture parameters | |||
paNoDevice, | |||
0, | |||
paInt16, | |||
NULL, | |||
// playback parameters | |||
myOutputDevice, | |||
out_channels, | |||
paInt16, | |||
NULL, | |||
// general parameters | |||
wave_samplerate, FRAMES_PER_BUFFER, 0, | |||
paNoFlag, | |||
pa_callback, (void *)userdata); | |||
if (err == paInvalidChannelCount) { | |||
// failed to open with mono, try stereo | |||
out_channels = 2; | |||
err = Pa_OpenStream(&pa_stream, | |||
// capture parameters | |||
paNoDevice, | |||
0, | |||
paInt16, | |||
NULL, | |||
// playback parameters | |||
myOutputDevice, | |||
out_channels, | |||
paInt16, | |||
NULL, | |||
// general parameters | |||
wave_samplerate, FRAMES_PER_BUFFER, 0, | |||
paNoFlag, | |||
pa_callback, (void *)userdata); | |||
} | |||
mInCallbackFinishedState = false; // v18 only | |||
#else | |||
myOutputParameters.channelCount = out_channels; | |||
unsigned long framesPerBuffer = paFramesPerBufferUnspecified; | |||
err = Pa_OpenStream(&pa_stream, | |||
NULL, // no input | |||
&myOutputParameters, | |||
wave_samplerate, | |||
framesPerBuffer, | |||
paNoFlag, | |||
pa_callback, | |||
(void *)userdata); | |||
if ((err != paNoError) | |||
&& (err != paInvalidChannelCount)) { | |||
fprintf(stderr, "wave_open_sound > Pa_OpenStream : err=%d (%s)\n", err, Pa_GetErrorText(err)); | |||
framesPerBuffer = FRAMES_PER_BUFFER; | |||
err = Pa_OpenStream(&pa_stream, | |||
NULL, // no input | |||
&myOutputParameters, | |||
wave_samplerate, | |||
framesPerBuffer, | |||
paNoFlag, | |||
pa_callback, | |||
(void *)userdata); | |||
} | |||
if (err == paInvalidChannelCount) { | |||
// failed to open with mono, try stereo | |||
out_channels = 2; | |||
myOutputParameters.channelCount = out_channels; | |||
err = Pa_OpenStream(&pa_stream, | |||
NULL, // no input | |||
&myOutputParameters, | |||
wave_samplerate, | |||
framesPerBuffer, | |||
paNoFlag, | |||
pa_callback, | |||
(void *)userdata); | |||
} | |||
mInCallbackFinishedState = false; | |||
#endif | |||
} | |||
return err != paNoError; | |||
} | |||
static void update_output_parameters(int selectedDevice, const PaDeviceInfo *deviceInfo) | |||
{ | |||
#if (USE_PORTAUDIO == 19) | |||
myOutputParameters.device = selectedDevice; | |||
myOutputParameters.channelCount = 1; | |||
myOutputParameters.sampleFormat = paInt16; | |||
// Latency greater than 100ms for avoiding glitches | |||
// (e.g. when moving a window in a graphical desktop) | |||
// deviceInfo = Pa_GetDeviceInfo(selectedDevice); | |||
if (deviceInfo) { | |||
double aLatency = deviceInfo->defaultLowOutputLatency; | |||
myOutputParameters.suggestedLatency = aLatency; // for faster response ? | |||
} else | |||
myOutputParameters.suggestedLatency = (double)0.1; // 100ms | |||
myOutputParameters.hostApiSpecificStreamInfo = NULL; | |||
#else | |||
myOutputDevice = selectedDevice; | |||
#endif | |||
} | |||
static const PaDeviceInfo *select_device(const char *device) | |||
{ | |||
#if (USE_PORTAUDIO == 19) | |||
int numDevices = Pa_GetDeviceCount(); | |||
#else | |||
int numDevices = Pa_CountDevices(); | |||
#endif | |||
if (numDevices < 0) | |||
return NULL; | |||
#if (USE_PORTAUDIO == 19) | |||
PaDeviceIndex i = 0, selectedIndex = 0; | |||
#else | |||
PaDeviceID i = 0, selectedIndex = 0; | |||
#endif | |||
const PaDeviceInfo *deviceInfo = NULL; | |||
const PaDeviceInfo *selectedDeviceInfo = NULL; | |||
if (device == NULL) { | |||
#if (USE_PORTAUDIO == 19) | |||
selectedIndex = Pa_GetDefaultOutputDevice(); | |||
#else | |||
selectedIndex = Pa_GetDefaultOutputDeviceID(); | |||
#endif | |||
selectedDeviceInfo = Pa_GetDeviceInfo(selectedIndex); | |||
} | |||
if (selectedDeviceInfo == NULL) { | |||
for (i = 0; i < numDevices; i++) { | |||
deviceInfo = Pa_GetDeviceInfo(i); | |||
if (deviceInfo != NULL && !strcmp(device, deviceInfo->name)) { | |||
selectedIndex = i; | |||
selectedDeviceInfo = deviceInfo; | |||
} | |||
} | |||
} | |||
if (selectedDeviceInfo) | |||
update_output_parameters(selectedIndex, selectedDeviceInfo); | |||
return selectedDeviceInfo; | |||
} | |||
void *wave_open(int srate, const char *device) | |||
{ | |||
PaError err; | |||
pa_stream = NULL; | |||
wave_samplerate = srate; | |||
mInCallbackFinishedState = false; | |||
init_buffer(); | |||
// PortAudio sound output library | |||
err = Pa_Initialize(); | |||
pa_init_err = err; | |||
if (err != paNoError) | |||
return NULL; | |||
static int once = 0; | |||
if (!once) { | |||
if (!select_device(device)) | |||
return NULL; | |||
once = 1; | |||
} | |||
return (void *)1; | |||
} | |||
static size_t copyBuffer(char *dest, char *src, const size_t theSizeInBytes) | |||
{ | |||
size_t bytes_written = 0; | |||
unsigned int i = 0; | |||
uint16_t *a_dest = NULL; | |||
uint16_t *a_src = NULL; | |||
if ((src != NULL) && dest != NULL) { | |||
// copy for one channel (mono)? | |||
if (out_channels == 1) { | |||
memcpy(dest, src, theSizeInBytes); | |||
bytes_written = theSizeInBytes; | |||
} else { // copy for 2 channels (stereo) | |||
a_dest = (uint16_t *)dest; | |||
a_src = (uint16_t *)src; | |||
for (i = 0; i < theSizeInBytes/2; i++) { | |||
a_dest[2*i] = a_src[i]; | |||
a_dest[2*i+1] = a_src[i]; | |||
} | |||
bytes_written = 2*theSizeInBytes; | |||
} | |||
} | |||
return bytes_written; | |||
} | |||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) | |||
{ | |||
(void)theHandler; // unused | |||
size_t bytes_written = 0; | |||
// space in ringbuffer for the sample needed: 1x mono channel but 2x for 1 stereo channel | |||
size_t bytes_to_write = (out_channels == 1) ? theSize : theSize*2; | |||
my_stream_could_start = 0; | |||
if (pa_stream == NULL) { | |||
if (0 != wave_open_sound()) | |||
return 0; | |||
my_stream_could_start = 1; | |||
} | |||
assert(BUFFER_LENGTH >= bytes_to_write); | |||
if (myWrite >= myBuffer + BUFFER_LENGTH) | |||
myWrite = myBuffer; | |||
size_t aTotalFreeMem = 0; | |||
char *aRead; | |||
while (1) { | |||
aRead = myRead; | |||
// write pointer is before read pointer? | |||
if (myWrite >= aRead) | |||
aTotalFreeMem = aRead + BUFFER_LENGTH - myWrite; | |||
else // read pointer is before write pointer! | |||
aTotalFreeMem = aRead - myWrite; | |||
if (aTotalFreeMem > 1) { | |||
// -1 because myWrite must be different of aRead | |||
// otherwise buffer would be considered as empty | |||
aTotalFreeMem -= 1; | |||
} | |||
if (aTotalFreeMem >= bytes_to_write) | |||
break; | |||
usleep(10000); | |||
} | |||
aRead = myRead; | |||
// write pointer is ahead the read pointer? | |||
if (myWrite >= aRead) { | |||
// determine remaining free memory to the end of the ringbuffer | |||
size_t aFreeMem = myBuffer + BUFFER_LENGTH - myWrite; | |||
// is enough linear space available (regardless 1 or 2 channels)? | |||
if (aFreeMem >= bytes_to_write) { | |||
// copy direct - no wrap around at end of ringbuffer needed | |||
myWrite += copyBuffer(myWrite, theMono16BitsWaveBuffer, theSize); | |||
} else { // not enough linear space available | |||
// 2 channels (stereo)? | |||
if (out_channels == 2) { | |||
// copy with wrap around at the end of ringbuffer | |||
copyBuffer(myWrite, theMono16BitsWaveBuffer, aFreeMem/2); | |||
myWrite = myBuffer; | |||
myWrite += copyBuffer(myWrite, theMono16BitsWaveBuffer+aFreeMem/2, theSize - aFreeMem/2); | |||
} else { // 1 channel (mono) | |||
// copy with wrap around at the end of ringbuffer | |||
copyBuffer(myWrite, theMono16BitsWaveBuffer, aFreeMem); | |||
myWrite = myBuffer; | |||
myWrite += copyBuffer(myWrite, theMono16BitsWaveBuffer+aFreeMem, theSize - aFreeMem); | |||
} | |||
} | |||
} else // read pointer is ahead the write pointer | |||
myWrite += copyBuffer(myWrite, theMono16BitsWaveBuffer, theSize); | |||
bytes_written = bytes_to_write; | |||
myWritePosition += theSize/sizeof(uint16_t); // add number of samples | |||
if (my_stream_could_start && (get_used_mem() >= out_channels * sizeof(uint16_t) * FRAMES_PER_BUFFER)) | |||
start_stream(); | |||
return bytes_written; | |||
} | |||
int wave_close(void *theHandler) | |||
{ | |||
(void)theHandler; // unused | |||
static int aStopStreamCount = 0; | |||
#if (USE_PORTAUDIO == 19) | |||
if (pa_stream == NULL) | |||
return 0; | |||
if (Pa_IsStreamStopped(pa_stream)) | |||
return 0; | |||
#else | |||
if (pa_stream == NULL) | |||
return 0; | |||
if (Pa_StreamActive(pa_stream) == false && mInCallbackFinishedState == false) | |||
return 0; | |||
#endif | |||
// Avoid race condition by making sure this function only | |||
// gets called once at a time | |||
aStopStreamCount++; | |||
if (aStopStreamCount != 1) | |||
return 0; | |||
// Comment from Audacity-1.2.4b adapted to the eSpeak context. | |||
// | |||
// We got here in one of two ways: | |||
// | |||
// 1. The calling program calls the espeak_Cancel function and we | |||
// therefore want to stop as quickly as possible. | |||
// So we use AbortStream(). If this is | |||
// the case the portaudio stream is still in the Running state | |||
// (see PortAudio state machine docs). | |||
// | |||
// 2. The callback told PortAudio to stop the stream since it had | |||
// reached the end of the selection. | |||
// The event polling thread discovered this by noticing that | |||
// wave_is_busy() returned false. | |||
// wave_is_busy() (which calls Pa_GetStreamActive()) will not return | |||
// false until all buffers have finished playing, so we can call | |||
// AbortStream without losing any samples. If this is the case | |||
// we are in the "callback finished state" (see PortAudio state | |||
// machine docs). | |||
// | |||
// The moral of the story: We can call AbortStream safely, without | |||
// losing samples. | |||
// | |||
// DMM: This doesn't seem to be true; it seems to be necessary to | |||
// call StopStream if the callback brought us here, and AbortStream | |||
// if the user brought us here. | |||
#if (USE_PORTAUDIO == 19) | |||
if (pa_stream) { | |||
Pa_AbortStream(pa_stream); | |||
Pa_CloseStream(pa_stream); | |||
pa_stream = NULL; | |||
mInCallbackFinishedState = false; | |||
} | |||
#else | |||
if (pa_stream) { | |||
if (mInCallbackFinishedState) { | |||
Pa_StopStream(pa_stream); | |||
} else { | |||
Pa_AbortStream(pa_stream); | |||
} | |||
Pa_CloseStream(pa_stream); | |||
pa_stream = NULL; | |||
mInCallbackFinishedState = false; | |||
} | |||
#endif | |||
init_buffer(); | |||
aStopStreamCount = 0; // last action | |||
return 0; | |||
} | |||
void wave_terminate() | |||
{ | |||
Pa_Terminate(); | |||
} | |||
#else | |||
void *wave_open(int srate, const char *device) | |||
{ | |||
(void)srate; // unused | |||
(void)device; // unused | |||
return (void *)1; | |||
} | |||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) | |||
{ | |||
(void)theHandler; // unused | |||
(void)theMono16BitsWaveBuffer; // unused | |||
return theSize; | |||
} | |||
int wave_close(void *theHandler) | |||
{ | |||
(void)theHandler; // unused | |||
return 0; | |||
} | |||
void wave_terminate() | |||
{ | |||
} | |||
void wave_flush(void *theHandler) | |||
{ | |||
(void)theHandler; // unused | |||
} | |||
#endif |
@@ -1,38 +0,0 @@ | |||
/* | |||
* Copyright (C) 2007, Gilles Casse <[email protected]> | |||
* Copyright (C) 2015-2016 Reece H. Dunn | |||
* based on AudioIO.cc (Audacity-1.2.4b) and wavegen.cpp | |||
* | |||
* This program 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 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program 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: <http://www.gnu.org/licenses/>. | |||
*/ | |||
#ifndef WAVE_H | |||
#define WAVE_H | |||
#ifdef __cplusplus | |||
extern "C" | |||
{ | |||
#endif | |||
extern void *wave_open(int samplerate, const char *device); | |||
extern size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize); | |||
extern int wave_close(void *theHandler); | |||
extern void wave_flush(void *theHandler); | |||
extern void wave_terminate(); | |||
#ifdef __cplusplus | |||
} | |||
#endif | |||
#endif |
@@ -1,587 +0,0 @@ | |||
/* | |||
* Copyright (C) 2007, Gilles Casse <[email protected]> | |||
* Copyright (C) 2015-2016 Reece H. Dunn | |||
* eSpeak driver for PulseAudio | |||
* based on the XMMS PulseAudio Plugin | |||
* | |||
* This program 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 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program 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: <http://www.gnu.org/licenses/>. | |||
*/ | |||
// TBD: | |||
// * ARCH_BIG | |||
// * uint64? a_timing_info.read_index | |||
// * prebuf,... size? | |||
// * 0.9.6: pb pulse_free using tlength=8820 (max size never returned -> tlength=10000 ok, but higher drain). | |||
// | |||
#include "config.h" | |||
#include <assert.h> | |||
#include <math.h> | |||
#include <pthread.h> | |||
#include <pulse/pulseaudio.h> | |||
#include <stdbool.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <sys/time.h> | |||
#include <time.h> | |||
#include <unistd.h> | |||
#include <espeak-ng/espeak_ng.h> | |||
#include "speech.h" | |||
#include "wave.h" | |||
enum { | |||
/* return value */ | |||
PULSE_OK = 0, | |||
PULSE_ERROR = -1, | |||
PULSE_NO_CONNECTION = -2 | |||
}; | |||
#ifdef USE_PULSEAUDIO | |||
#define SAMPLE_RATE 22050 | |||
#define ESPEAK_FORMAT PA_SAMPLE_S16LE | |||
#define ESPEAK_CHANNEL 1 | |||
#define MAXLENGTH 132300 | |||
#define TLENGTH 4410 | |||
#define PREBUF 2200 | |||
#define MINREQ 880 | |||
#ifdef USE_PORTAUDIO | |||
// rename functions to be wrapped | |||
#define wave_open wave_pulse_open | |||
#define wave_write wave_pulse_write | |||
#define wave_close wave_pulse_close | |||
#define wave_terminate wave_pulse_terminate | |||
#define wave_flush wave_pulse_flush | |||
// check whether we can connect to PulseAudio | |||
#include <pulse/simple.h> | |||
int is_pulse_running() | |||
{ | |||
pa_sample_spec ss; | |||
ss.format = ESPEAK_FORMAT; | |||
ss.rate = SAMPLE_RATE; | |||
ss.channels = ESPEAK_CHANNEL; | |||
pa_simple *s = pa_simple_new(NULL, "eSpeak", PA_STREAM_PLAYBACK, NULL, "is_pulse_running", &ss, NULL, NULL, NULL); | |||
if (s) { | |||
pa_simple_free(s); | |||
return 1; | |||
} else | |||
return 0; | |||
} | |||
#endif | |||
static pthread_mutex_t pulse_mutex; | |||
static pa_context *context = NULL; | |||
static pa_stream *stream = NULL; | |||
static pa_threaded_mainloop *mainloop = NULL; | |||
static int do_trigger = 0; | |||
static uint64_t written = 0; | |||
static int time_offset_msec = 0; | |||
static int just_flushed = 0; | |||
static int connected = 0; | |||
static int wave_samplerate; | |||
#define CHECK_DEAD_GOTO(label, warn) do { \ | |||
if (!mainloop || \ | |||
!context || pa_context_get_state(context) != PA_CONTEXT_READY || \ | |||
!stream || pa_stream_get_state(stream) != PA_STREAM_READY) { \ | |||
if (warn) \ | |||
fprintf(stderr, "Connection died: %s\n", context ? pa_strerror(pa_context_errno(context)) : "NULL"); \ | |||
goto label; \ | |||
} \ | |||
} while (0); | |||
#define CHECK_CONNECTED(retval) \ | |||
do { \ | |||
if (!connected) return retval; \ | |||
} while (0); | |||
#define CHECK_CONNECTED_NO_RETVAL(id) \ | |||
do { \ | |||
if (!connected) { return; } \ | |||
} while (0); | |||
static void subscribe_cb(struct pa_context *c, enum pa_subscription_event_type t, uint32_t index, void *userdata) | |||
{ | |||
(void)userdata; // unused | |||
assert(c); | |||
if (!stream || | |||
index != pa_stream_get_index(stream) || | |||
(t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && | |||
t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW))) | |||
return; | |||
} | |||
static void context_state_cb(pa_context *c, void *userdata) | |||
{ | |||
(void)userdata; // unused | |||
assert(c); | |||
switch (pa_context_get_state(c)) | |||
{ | |||
case PA_CONTEXT_READY: | |||
case PA_CONTEXT_TERMINATED: | |||
case PA_CONTEXT_FAILED: | |||
pa_threaded_mainloop_signal(mainloop, 0); | |||
break; | |||
case PA_CONTEXT_UNCONNECTED: | |||
case PA_CONTEXT_CONNECTING: | |||
case PA_CONTEXT_AUTHORIZING: | |||
case PA_CONTEXT_SETTING_NAME: | |||
break; | |||
} | |||
} | |||
static void stream_state_cb(pa_stream *s, void *userdata) | |||
{ | |||
(void)userdata; // unused | |||
assert(s); | |||
switch (pa_stream_get_state(s)) | |||
{ | |||
case PA_STREAM_READY: | |||
case PA_STREAM_FAILED: | |||
case PA_STREAM_TERMINATED: | |||
pa_threaded_mainloop_signal(mainloop, 0); | |||
break; | |||
case PA_STREAM_UNCONNECTED: | |||
case PA_STREAM_CREATING: | |||
break; | |||
} | |||
} | |||
static void stream_success_cb(pa_stream *s, int success, void *userdata) | |||
{ | |||
assert(s); | |||
if (userdata) | |||
*(int *)userdata = success; | |||
pa_threaded_mainloop_signal(mainloop, 0); | |||
} | |||
static void context_success_cb(pa_context *c, int success, void *userdata) | |||
{ | |||
assert(c); | |||
if (userdata) | |||
*(int *)userdata = success; | |||
pa_threaded_mainloop_signal(mainloop, 0); | |||
} | |||
static void stream_request_cb(pa_stream *s, size_t length, void *userdata) | |||
{ | |||
(void)length; // unused | |||
(void)userdata; // unused | |||
assert(s); | |||
pa_threaded_mainloop_signal(mainloop, 0); | |||
} | |||
static void stream_latency_update_cb(pa_stream *s, void *userdata) | |||
{ | |||
(void)userdata; // unused | |||
assert(s); | |||
pa_threaded_mainloop_signal(mainloop, 0); | |||
} | |||
static int pulse_free(void) | |||
{ | |||
size_t l = 0; | |||
pa_operation *o = NULL; | |||
CHECK_CONNECTED(0); | |||
pa_threaded_mainloop_lock(mainloop); | |||
CHECK_DEAD_GOTO(fail, 1); | |||
if ((l = pa_stream_writable_size(stream)) == (size_t)-1) { | |||
fprintf(stderr, "pa_stream_writable_size() failed: %s", pa_strerror(pa_context_errno(context))); | |||
l = 0; | |||
goto fail; | |||
} | |||
/* If this function is called twice with no pulse_write() call in | |||
* between this means we should trigger the playback */ | |||
if (do_trigger) { | |||
int success = 0; | |||
if (!(o = pa_stream_trigger(stream, stream_success_cb, &success))) { | |||
fprintf(stderr, "pa_stream_trigger() failed: %s", pa_strerror(pa_context_errno(context))); | |||
goto fail; | |||
} | |||
while (pa_operation_get_state(o) != PA_OPERATION_DONE) { | |||
CHECK_DEAD_GOTO(fail, 1); | |||
pa_threaded_mainloop_wait(mainloop); | |||
} | |||
if (!success) | |||
fprintf(stderr, "pa_stream_trigger() failed: %s", pa_strerror(pa_context_errno(context))); | |||
} | |||
fail: | |||
if (o) | |||
pa_operation_unref(o); | |||
pa_threaded_mainloop_unlock(mainloop); | |||
do_trigger = !!l; | |||
return (int)l; | |||
} | |||
static int pulse_playing(const pa_timing_info *the_timing_info) | |||
{ | |||
int r = 0; | |||
const pa_timing_info *i; | |||
assert(the_timing_info); | |||
CHECK_CONNECTED(0); | |||
pa_threaded_mainloop_lock(mainloop); | |||
for (;;) { | |||
CHECK_DEAD_GOTO(fail, 1); | |||
if ((i = pa_stream_get_timing_info(stream))) | |||
break; | |||
if (pa_context_errno(context) != PA_ERR_NODATA) { | |||
fprintf(stderr, "pa_stream_get_timing_info() failed: %s", pa_strerror(pa_context_errno(context))); | |||
goto fail; | |||
} | |||
pa_threaded_mainloop_wait(mainloop); | |||
} | |||
r = i->playing; | |||
memcpy((void *)the_timing_info, (void *)i, sizeof(pa_timing_info)); | |||
fail: | |||
pa_threaded_mainloop_unlock(mainloop); | |||
return r; | |||
} | |||
static void pulse_write(void *ptr, int length) | |||
{ | |||
CHECK_CONNECTED_NO_RETVAL(); | |||
pa_threaded_mainloop_lock(mainloop); | |||
CHECK_DEAD_GOTO(fail, 1); | |||
if (pa_stream_write(stream, ptr, length, NULL, PA_SEEK_RELATIVE, (pa_seek_mode_t)0) < 0) { | |||
fprintf(stderr, "pa_stream_write() failed: %s", pa_strerror(pa_context_errno(context))); | |||
goto fail; | |||
} | |||
do_trigger = 0; | |||
written += length; | |||
fail: | |||
pa_threaded_mainloop_unlock(mainloop); | |||
} | |||
static int drain(void) | |||
{ | |||
pa_operation *o = NULL; | |||
int success = 0; | |||
int ret = PULSE_ERROR; | |||
CHECK_CONNECTED(ret); | |||
pa_threaded_mainloop_lock(mainloop); | |||
CHECK_DEAD_GOTO(fail, 0); | |||
if (!(o = pa_stream_drain(stream, stream_success_cb, &success))) { | |||
fprintf(stderr, "pa_stream_drain() failed: %s\n", pa_strerror(pa_context_errno(context))); | |||
goto fail; | |||
} | |||
while (pa_operation_get_state(o) != PA_OPERATION_DONE) { | |||
CHECK_DEAD_GOTO(fail, 1); | |||
pa_threaded_mainloop_wait(mainloop); | |||
} | |||
if (!success) | |||
fprintf(stderr, "pa_stream_drain() failed: %s\n", pa_strerror(pa_context_errno(context))); | |||
else | |||
ret = PULSE_OK; | |||
fail: | |||
if (o) | |||
pa_operation_unref(o); | |||
pa_threaded_mainloop_unlock(mainloop); | |||
return ret; | |||
} | |||
static void pulse_close(void) | |||
{ | |||
drain(); | |||
connected = 0; | |||
if (mainloop) | |||
pa_threaded_mainloop_stop(mainloop); | |||
connected = 0; | |||
if (context) { | |||
pa_context_disconnect(context); | |||
pa_context_unref(context); | |||
context = NULL; | |||
} | |||
if (mainloop) { | |||
pa_threaded_mainloop_free(mainloop); | |||
mainloop = NULL; | |||
} | |||
} | |||
static int pulse_open(const char *device) | |||
{ | |||
pa_sample_spec ss; | |||
pa_operation *o = NULL; | |||
int success; | |||
int ret = PULSE_ERROR; | |||
assert(!mainloop); | |||
assert(!context); | |||
assert(!stream); | |||
assert(!connected); | |||
pthread_mutex_init(&pulse_mutex, (const pthread_mutexattr_t *)NULL); | |||
ss.format = ESPEAK_FORMAT; | |||
ss.rate = wave_samplerate; | |||
ss.channels = ESPEAK_CHANNEL; | |||
if (!pa_sample_spec_valid(&ss)) | |||
return false; | |||
if (!(mainloop = pa_threaded_mainloop_new())) | |||
goto fail; | |||
pa_threaded_mainloop_lock(mainloop); | |||
if (!(context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "eSpeak"))) | |||
goto fail; | |||
pa_context_set_state_callback(context, context_state_cb, NULL); | |||
pa_context_set_subscribe_callback(context, subscribe_cb, NULL); | |||
if (pa_context_connect(context, NULL, (pa_context_flags_t)0, NULL) < 0) { | |||
fprintf(stderr, "Failed to connect to server: %s", pa_strerror(pa_context_errno(context))); | |||
ret = PULSE_NO_CONNECTION; | |||
goto fail; | |||
} | |||
if (pa_threaded_mainloop_start(mainloop) < 0) | |||
goto fail; | |||
// Wait until the context is ready | |||
pa_threaded_mainloop_wait(mainloop); | |||
if (pa_context_get_state(context) != PA_CONTEXT_READY) { | |||
fprintf(stderr, "Failed to connect to server: %s", pa_strerror(pa_context_errno(context))); | |||
ret = PULSE_NO_CONNECTION; | |||
if (mainloop) | |||
pa_threaded_mainloop_stop(mainloop); | |||
goto fail; | |||
} | |||
if (!(stream = pa_stream_new(context, "unknown", &ss, NULL))) { | |||
fprintf(stderr, "Failed to create stream: %s", pa_strerror(pa_context_errno(context))); | |||
goto fail; | |||
} | |||
pa_stream_set_state_callback(stream, stream_state_cb, NULL); | |||
pa_stream_set_write_callback(stream, stream_request_cb, NULL); | |||
pa_stream_set_latency_update_callback(stream, stream_latency_update_cb, NULL); | |||
pa_buffer_attr a_attr; | |||
a_attr.maxlength = MAXLENGTH; | |||
a_attr.tlength = TLENGTH; | |||
a_attr.prebuf = PREBUF; | |||
a_attr.minreq = MINREQ; | |||
a_attr.fragsize = 0; | |||
if (pa_stream_connect_playback(stream, device, &a_attr, (pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE), NULL, NULL) < 0) { | |||
fprintf(stderr, "Failed to connect stream: %s", pa_strerror(pa_context_errno(context))); | |||
goto fail; | |||
} | |||
// Wait until the stream is ready | |||
pa_threaded_mainloop_wait(mainloop); | |||
if (pa_stream_get_state(stream) != PA_STREAM_READY) { | |||
fprintf(stderr, "Failed to connect stream: %s", pa_strerror(pa_context_errno(context))); | |||
goto fail; | |||
} | |||
// Now subscribe to events | |||
if (!(o = pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK_INPUT, context_success_cb, &success))) { | |||
fprintf(stderr, "pa_context_subscribe() failed: %s", pa_strerror(pa_context_errno(context))); | |||
goto fail; | |||
} | |||
while (pa_operation_get_state(o) != PA_OPERATION_DONE) { | |||
CHECK_DEAD_GOTO(fail, 1); | |||
pa_threaded_mainloop_wait(mainloop); | |||
} | |||
pa_operation_unref(o); | |||
do_trigger = 0; | |||
written = 0; | |||
time_offset_msec = 0; | |||
just_flushed = 0; | |||
connected = 1; | |||
pa_threaded_mainloop_unlock(mainloop); | |||
return PULSE_OK; | |||
fail: | |||
if (mainloop) | |||
pa_threaded_mainloop_unlock(mainloop); | |||
if (ret == PULSE_NO_CONNECTION) { | |||
if (context) { | |||
pa_context_disconnect(context); | |||
pa_context_unref(context); | |||
context = NULL; | |||
} | |||
if (mainloop) { | |||
pa_threaded_mainloop_free(mainloop); | |||
mainloop = NULL; | |||
} | |||
} else | |||
pulse_close(); | |||
return ret; | |||
} | |||
void wave_flush(void *theHandler) | |||
{ | |||
(void)theHandler; // unused | |||
} | |||
void *wave_open(int srate, const char *device) | |||
{ | |||
stream = NULL; | |||
wave_samplerate = srate; | |||
if (pulse_open(device) != PULSE_OK) | |||
return NULL; | |||
return (void *)1; | |||
} | |||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) | |||
{ | |||
(void)theHandler; // unused | |||
size_t bytes_to_write = theSize; | |||
char *aBuffer = theMono16BitsWaveBuffer; | |||
assert(stream); | |||
size_t aTotalFreeMem = 0; | |||
pthread_mutex_lock(&pulse_mutex); | |||
while (1) { | |||
aTotalFreeMem = pulse_free(); | |||
if (aTotalFreeMem >= bytes_to_write) | |||
break; | |||
// TBD: check if really helpful | |||
if (aTotalFreeMem >= MAXLENGTH*2) | |||
aTotalFreeMem = MAXLENGTH*2; | |||
// 500: threshold for avoiding too many calls to pulse_write | |||
if (aTotalFreeMem > 500) { | |||
pulse_write(aBuffer, aTotalFreeMem); | |||
bytes_to_write -= aTotalFreeMem; | |||
aBuffer += aTotalFreeMem; | |||
} | |||
usleep(10000); | |||
} | |||
pulse_write(aBuffer, bytes_to_write); | |||
terminate: | |||
pthread_mutex_unlock(&pulse_mutex); | |||
return theSize; | |||
} | |||
int wave_close(void *theHandler) | |||
{ | |||
(void)theHandler; // unused | |||
static int aStopStreamCount = 0; | |||
// Avoid race condition by making sure this function only | |||
// gets called once at a time | |||
aStopStreamCount++; | |||
if (aStopStreamCount != 1) | |||
return 0; | |||
int a_status = pthread_mutex_lock(&pulse_mutex); | |||
if (a_status) { | |||
aStopStreamCount = 0; // last action | |||
return PULSE_ERROR; | |||
} | |||
drain(); | |||
pthread_mutex_unlock(&pulse_mutex); | |||
aStopStreamCount = 0; // last action | |||
return PULSE_OK; | |||
} | |||
void wave_terminate() | |||
{ | |||
pthread_mutex_t *a_mutex = NULL; | |||
a_mutex = &pulse_mutex; | |||
pthread_mutex_lock(a_mutex); | |||
pulse_close(); | |||
pthread_mutex_unlock(a_mutex); | |||
pthread_mutex_destroy(a_mutex); | |||
} | |||
#endif |
@@ -1,332 +0,0 @@ | |||
/* | |||
* Copyright (C) 2008, Sun Microsystems, Inc. | |||
* Copyright (C) 2015-2016 Reece H. Dunn | |||
* eSpeak driver for Solaris Audio Device Architecture (SADA) | |||
* Written by Willie Walker, based on the eSpeak PulseAudio driver | |||
* from Gilles Casse | |||
* | |||
* This program 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 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program 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: <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "config.h" | |||
#include <assert.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <stropts.h> | |||
#include <sys/audioio.h> | |||
#include <unistd.h> | |||
#include <espeak-ng/espeak_ng.h> | |||
#include "speech.h" | |||
#include "wave.h" | |||
#define SAMPLE_RATE 22050 | |||
#define SAMPLE_SIZE 16 | |||
#ifdef USE_SADA | |||
static const char *sun_audio_device = "/dev/audio"; | |||
static int sun_audio_fd = -1; | |||
// The total number of 16-bit samples sent to be played via the | |||
// wave_write method. | |||
// | |||
static uint32_t total_samples_sent; | |||
// The total number of samples sent to be played via the wave_write | |||
// method, but which were never played because of a call to | |||
// wave_close. | |||
// | |||
static uint32_t total_samples_skipped; | |||
// The last known playing index after a call to wave_close. | |||
// | |||
static uint32_t last_play_position = 0; | |||
static uint32_t wave_samplerate; | |||
static int wave_get_remaining_time(uint32_t sample, uint32_t *time); | |||
// wave_open | |||
// | |||
// DESCRIPTION: | |||
// | |||
// initializes the audio subsytem. | |||
// | |||
// GLOBALS USED/MODIFIED: | |||
// | |||
// sun_audio_fd: modified to hold the file descriptor of the opened | |||
// audio device. | |||
// | |||
void *wave_open(int srate, const char *device) | |||
{ | |||
if (device == NULL) | |||
device = sun_audio_device; | |||
audio_info_t ainfo; | |||
wave_samplerate = srate; | |||
if ((sun_audio_fd = open(device, O_WRONLY)) < 0) { | |||
fprintf(stderr, "wave_open() could not open: %s (%d)\n", device, sun_audio_fd); | |||
return NULL; | |||
} | |||
ioctl(sun_audio_fd, AUDIO_GETINFO, &ainfo); | |||
ainfo.play.encoding = AUDIO_ENCODING_LINEAR; | |||
ainfo.play.channels = 1; | |||
ainfo.play.sample_rate = wave_samplerate; | |||
ainfo.play.precision = SAMPLE_SIZE; | |||
if (ioctl(sun_audio_fd, AUDIO_SETINFO, &ainfo) == -1) { | |||
fprintf(stderr, "wave_open() failed to set audio params: %s\n", strerror(errno)); | |||
close(sun_audio_fd); | |||
return NULL; | |||
} | |||
return (void *)sun_audio_fd; | |||
} | |||
// wave_write | |||
// | |||
// DESCRIPTION: | |||
// | |||
// Meant to be asynchronous, it supplies the wave sample to the lower | |||
// audio layer and returns. The sample is played later on. [[[WDW - | |||
// we purposely do not open the audio device as non-blocking because | |||
// managing that would be a pain. So, we rely a lot upon fifo.cpp and | |||
// event.cpp to not overload us, allowing us to get away with a | |||
// blocking write. event.cpp:polling_thread in particular appears to | |||
// use get_remaining_time to prevent flooding.]]] | |||
// | |||
// PARAMETERS: | |||
// | |||
// theHandler: the audio device file descriptor | |||
// theMono16BitsWaveBuffer: the audio data | |||
// theSize: the number of bytes (not 16-bit samples) | |||
// | |||
// GLOBALS USED/MODIFIED: | |||
// | |||
// total_samples_sent: modified based upon 16-bit samples sent | |||
// | |||
// RETURNS: | |||
// | |||
// the number of bytes (not 16-bit samples) sent | |||
// | |||
size_t wave_write(void *theHandler, | |||
char *theMono16BitsWaveBuffer, | |||
size_t theSize) | |||
{ | |||
size_t num; | |||
#if defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN | |||
// BIG-ENDIAN, swap the order of bytes in each sound sample | |||
int c; | |||
char *out_ptr; | |||
char *out_end; | |||
out_ptr = (char *)theMono16BitsWaveBuffer; | |||
out_end = out_ptr + theSize; | |||
while (out_ptr < out_end) { | |||
c = out_ptr[0]; | |||
out_ptr[0] = out_ptr[1]; | |||
out_ptr[1] = c; | |||
out_ptr += 2; | |||
} | |||
#endif | |||
num = write((int)theHandler, theMono16BitsWaveBuffer, theSize); | |||
// Keep track of the total number of samples sent -- we use this in | |||
// wave_get_read_position and also use it to help calculate the | |||
// total_samples_skipped in wave_close. | |||
// | |||
total_samples_sent += num / 2; | |||
return num; | |||
} | |||
// wave_close | |||
// | |||
// DESCRIPTION: | |||
// | |||
// Does what SADA normally would call a flush, which means to cease | |||
// all audio production in progress and throw any remaining audio | |||
// away. [[[WDW - see comment in wave_flush.]]] | |||
// | |||
// PARAMETERS: | |||
// | |||
// theHandler: the audio device file descriptor | |||
// | |||
// GLOBALS USED/MODIFIED: | |||
// | |||
// last_play_position: modified to reflect play position the last time | |||
// this method was called | |||
// total_samples_sent: used to help calculate total_samples_skipped | |||
// total_samples_skipped: modified to hold the total number of 16-bit | |||
// samples sent to wave_write, but which were | |||
// never played | |||
// sun_audio_fd: used because some calls to wave_close seem to | |||
// pass a NULL for theHandler for some odd reason | |||
// | |||
// RETURNS: | |||
// | |||
// The result of the ioctl call (non-0 means failure) | |||
// | |||
int wave_close(void *theHandler) | |||
{ | |||
int ret; | |||
audio_info_t ainfo; | |||
int audio_fd = (int)theHandler; | |||
if (!audio_fd) | |||
audio_fd = sun_audio_fd; | |||
// [[[WDW: maybe do a pause/resume ioctl???]]] | |||
ret = ioctl(audio_fd, I_FLUSH, FLUSHRW); | |||
ioctl(audio_fd, AUDIO_GETINFO, &ainfo); | |||
// Calculate the number of samples that won't get | |||
// played. We also keep track of the last_play_position | |||
// because wave_close can be called multiple times | |||
// before another call to wave_write. | |||
// | |||
if (last_play_position != ainfo.play.samples) { | |||
last_play_position = ainfo.play.samples; | |||
total_samples_skipped = total_samples_sent - last_play_position; | |||
} | |||
return ret; | |||
} | |||
// wave_is_busy | |||
// | |||
// DESCRIPTION: | |||
// | |||
// Returns a non-0 value if audio is being played. | |||
// | |||
// PARAMETERS: | |||
// | |||
// theHandler: the audio device file descriptor | |||
// | |||
// GLOBALS USED/MODIFIED: | |||
// | |||
// sun_audio_fd: used because some calls to wave_is_busy seem to | |||
// pass a NULL for theHandler for some odd reason | |||
// | |||
// RETURNS: | |||
// | |||
// A non-0 value if audio is being played | |||
// | |||
int wave_is_busy(void *theHandler) | |||
{ | |||
(void)theHandler; // unused | |||
uint32_t time; | |||
if (total_samples_sent >= 1) | |||
wave_get_remaining_time(total_samples_sent - 1, &time); | |||
else | |||
time = 0; | |||
return time != 0; | |||
} | |||
// wave_terminate | |||
// | |||
// DESCRIPTION: | |||
// | |||
// Used to end our session with eSpeak. | |||
// | |||
// GLOBALS USED/MODIFIED: | |||
// | |||
// sun_audio_fd: modified - closed and set to -1 | |||
// | |||
void wave_terminate() | |||
{ | |||
close(sun_audio_fd); | |||
sun_audio_fd = -1; | |||
} | |||
// wave_flush | |||
// | |||
// DESCRIPTION: | |||
// | |||
// Appears to want to tell the audio subsystem to make sure it plays | |||
// the audio. In our case, the system is already doing this, so this | |||
// is basically a no-op. [[[WDW - if you do a drain, you block, so | |||
// don't do that. In addition the typical SADA notion of flush is | |||
// currently handled by wave_close. I think this is most likely just | |||
// terminology conflict between eSpeak and SADA.]]] | |||
// | |||
// PARAMETERS: | |||
// | |||
// theHandler: the audio device file descriptor | |||
// | |||
void wave_flush(void *theHandler) | |||
{ | |||
(void)theHandler; // unused | |||
} | |||
// wave_get_remaining_time | |||
// | |||
// DESCRIPTION: | |||
// | |||
// Returns the remaining time (in ms) before the sample is played. | |||
// The sample in this case is a return value from a previous call to | |||
// wave_get_write_position. | |||
// | |||
// PARAMETERS: | |||
// | |||
// sample: an index returned from wave_get_write_position representing | |||
// an index into the long continuous stream of 16-bit samples | |||
// time: a return value representing the delay in milliseconds until | |||
// sample is played. A value of 0 means the sample is either | |||
// currently being played or it has already been played. | |||
// | |||
// GLOBALS USED/MODIFIED: | |||
// | |||
// sun_audio_fd: used to determine total number of samples played by | |||
// the audio system | |||
// total_samples_skipped: used in remaining time calculation | |||
// | |||
// RETURNS: | |||
// | |||
// Time in milliseconds before the sample is played or 0 if the sample | |||
// is currently playing or has already been played. | |||
// | |||
int wave_get_remaining_time(uint32_t sample, uint32_t *time) | |||
{ | |||
uint32_t a_time = 0; | |||
uint32_t actual_index; | |||
audio_info_t ainfo; | |||
if (!time) | |||
return -1; | |||
ioctl(sun_audio_fd, AUDIO_GETINFO, &ainfo); | |||
// See if this sample has already been played or is currently | |||
// playing. | |||
// | |||
actual_index = sample - total_samples_skipped; | |||
if ((sample < total_samples_skipped) || | |||
(actual_index <= ainfo.play.samples)) | |||
*time = 0; | |||
else { | |||
a_time = ((actual_index - ainfo.play.samples) * 1000) / wave_samplerate; | |||
*time = (uint32_t)a_time; | |||
} | |||
return 0; | |||
} | |||
#endif |