From 4f676ed17503be57ce55e12dbc1d87f82f477d96 Mon Sep 17 00:00:00 2001 From: "Reece H. Dunn" Date: Sun, 7 Feb 2016 19:34:08 +0000 Subject: [PATCH] Use pcaudiolib for the audio API layer. --- CHANGELOG.md | 7 +- Makefile.am | 39 +- README.md | 7 +- configure.ac | 108 +---- src/libespeak-ng/speech.c | 43 +- src/libespeak-ng/wave.c | 719 ---------------------------------- src/libespeak-ng/wave.h | 38 -- src/libespeak-ng/wave_pulse.c | 587 --------------------------- src/libespeak-ng/wave_sada.c | 332 ---------------- 9 files changed, 61 insertions(+), 1819 deletions(-) delete mode 100644 src/libespeak-ng/wave.c delete mode 100644 src/libespeak-ng/wave.h delete mode 100644 src/libespeak-ng/wave_pulse.c delete mode 100644 src/libespeak-ng/wave_sada.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 6446231f..a01fddf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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: diff --git a/Makefile.am b/Makefile.am index 66dedef5..2ffb2511 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/README.md b/README.md index bfb76ca1..30189f22 100644 --- a/README.md +++ b/README.md @@ -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` | diff --git a/configure.ac b/configure.ac index f6852c13..85b0ee6b 100644 --- a/configure.ac +++ b/configure.ac @@ -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: diff --git a/src/libespeak-ng/speech.c b/src/libespeak-ng/speech.c index 7df135e1..81c83e7b 100644 --- a/src/libespeak-ng/speech.c +++ b/src/libespeak-ng/speech.c @@ -33,6 +33,10 @@ #include #include +#ifdef HAVE_PCAUDIOLIB_AUDIO_H +#include +#endif + #if defined(_WIN32) || defined(_WIN64) #include #include @@ -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; } diff --git a/src/libespeak-ng/wave.c b/src/libespeak-ng/wave.c deleted file mode 100644 index b154d8c8..00000000 --- a/src/libespeak-ng/wave.c +++ /dev/null @@ -1,719 +0,0 @@ -/* - * Copyright (C) 2007, Gilles Casse - * 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: . - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef PLATFORM_WINDOWS -#include -#endif - -#include - -#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 diff --git a/src/libespeak-ng/wave.h b/src/libespeak-ng/wave.h deleted file mode 100644 index 544e089f..00000000 --- a/src/libespeak-ng/wave.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2007, Gilles Casse - * 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: . - */ - -#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 diff --git a/src/libespeak-ng/wave_pulse.c b/src/libespeak-ng/wave_pulse.c deleted file mode 100644 index c7d3c43c..00000000 --- a/src/libespeak-ng/wave_pulse.c +++ /dev/null @@ -1,587 +0,0 @@ -/* - * Copyright (C) 2007, Gilles Casse - * 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: . - */ - -// 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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 -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 diff --git a/src/libespeak-ng/wave_sada.c b/src/libespeak-ng/wave_sada.c deleted file mode 100644 index 7e493b11..00000000 --- a/src/libespeak-ng/wave_sada.c +++ /dev/null @@ -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: . - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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