mirror of
https://github.com/rhasspy/espeak-ng.git
synced 2026-10-31 05:35:35 +00:00
Use pcaudiolib for the audio API layer.
This commit is contained in:
+5
-2
@@ -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:
|
||||
|
||||
|
||||
+8
-31
@@ -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` |
|
||||
|
||||
|
||||
+15
-93
@@ -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:
|
||||
|
||||
+31
-12
@@ -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 <gcasse@oralux.org>
|
||||
* 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 <gcasse@oralux.org>
|
||||
* 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 <gcasse@oralux.org>
|
||||
* 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
|
||||
Reference in New Issue
Block a user