mirror of
https://github.com/rhasspy/espeak-ng.git
synced 2026-11-09 07:52:27 +00:00
The debugging code is done inconsistently, mainly to trace the event and audio output logic. This makes it harder to understand the code flow for logic that is not enabled by default. As such the debugging code makes the code harder to maintain.
937 lines
24 KiB
C
937 lines
24 KiB
C
/*
|
|
* Copyright (C) 2007, Gilles Casse <gcasse@oralux.org>
|
|
* Copyright (C) 2015 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 "speech.h"
|
|
|
|
#ifdef USE_ASYNC
|
|
// This source file is only used for asynchronious modes
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <assert.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
|
|
#include "portaudio.h"
|
|
#ifdef PLATFORM_WINDOWS
|
|
#include <windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
#include "wave.h"
|
|
|
|
#ifdef NEED_STRUCT_TIMESPEC
|
|
#define HAVE_STRUCT_TIMESPEC 1
|
|
struct timespec {
|
|
long tv_sec;
|
|
long tv_nsec;
|
|
};
|
|
#endif
|
|
|
|
enum { ONE_BILLION = 1000000000 };
|
|
|
|
#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_init
|
|
static int pulse_running;
|
|
|
|
// wave.cpp (this file)
|
|
int wave_port_init(int);
|
|
void *wave_port_open(const char *the_api);
|
|
size_t wave_port_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize);
|
|
int wave_port_close(void *theHandler);
|
|
int wave_port_is_busy(void *theHandler);
|
|
void wave_port_terminate();
|
|
uint32_t wave_port_get_read_position(void *theHandler);
|
|
uint32_t wave_port_get_write_position(void *theHandler);
|
|
void wave_port_flush(void *theHandler);
|
|
void wave_port_set_callback_is_output_enabled(t_wave_callback *cb);
|
|
void *wave_port_test_get_write_buffer();
|
|
int wave_port_get_remaining_time(uint32_t sample, uint32_t *time);
|
|
|
|
// wave_pulse.cpp
|
|
int is_pulse_running();
|
|
int wave_pulse_init(int);
|
|
void *wave_pulse_open(const char *the_api);
|
|
size_t wave_pulse_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize);
|
|
int wave_pulse_close(void *theHandler);
|
|
int wave_pulse_is_busy(void *theHandler);
|
|
void wave_pulse_terminate();
|
|
uint32_t wave_pulse_get_read_position(void *theHandler);
|
|
uint32_t wave_pulse_get_write_position(void *theHandler);
|
|
void wave_pulse_flush(void *theHandler);
|
|
void wave_pulse_set_callback_is_output_enabled(t_wave_callback *cb);
|
|
void *wave_pulse_test_get_write_buffer();
|
|
int wave_pulse_get_remaining_time(uint32_t sample, uint32_t *time);
|
|
|
|
// wrappers
|
|
int wave_init(int srate)
|
|
{
|
|
pulse_running = is_pulse_running();
|
|
|
|
if (pulse_running)
|
|
return wave_pulse_init(srate);
|
|
else
|
|
return wave_port_init(srate);
|
|
}
|
|
|
|
void *wave_open(const char *the_api)
|
|
{
|
|
if (pulse_running)
|
|
return wave_pulse_open(the_api);
|
|
else
|
|
return wave_port_open(the_api);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int wave_is_busy(void *theHandler)
|
|
{
|
|
if (pulse_running)
|
|
return wave_pulse_is_busy(theHandler);
|
|
else
|
|
return wave_port_is_busy(theHandler);
|
|
}
|
|
|
|
void wave_terminate()
|
|
{
|
|
if (pulse_running)
|
|
wave_pulse_terminate();
|
|
else
|
|
wave_port_terminate();
|
|
}
|
|
|
|
uint32_t wave_get_read_position(void *theHandler)
|
|
{
|
|
if (pulse_running)
|
|
return wave_pulse_get_read_position(theHandler);
|
|
else
|
|
return wave_port_get_read_position(theHandler);
|
|
}
|
|
|
|
uint32_t wave_get_write_position(void *theHandler)
|
|
{
|
|
if (pulse_running)
|
|
return wave_pulse_get_write_position(theHandler);
|
|
else
|
|
return wave_port_get_write_position(theHandler);
|
|
}
|
|
|
|
void wave_flush(void *theHandler)
|
|
{
|
|
if (pulse_running)
|
|
wave_pulse_flush(theHandler);
|
|
else
|
|
wave_port_flush(theHandler);
|
|
}
|
|
|
|
void wave_set_callback_is_output_enabled(t_wave_callback *cb)
|
|
{
|
|
if (pulse_running)
|
|
wave_pulse_set_callback_is_output_enabled(cb);
|
|
else
|
|
wave_port_set_callback_is_output_enabled(cb);
|
|
}
|
|
|
|
void *wave_test_get_write_buffer()
|
|
{
|
|
if (pulse_running)
|
|
return wave_pulse_test_get_write_buffer();
|
|
else
|
|
return wave_port_test_get_write_buffer();
|
|
}
|
|
|
|
int wave_get_remaining_time(uint32_t sample, uint32_t *time)
|
|
{
|
|
if (pulse_running)
|
|
return wave_pulse_get_remaining_time(sample, time);
|
|
else
|
|
return wave_port_get_remaining_time(sample, time);
|
|
}
|
|
|
|
// rename functions to be wrapped
|
|
#define wave_init wave_port_init
|
|
#define wave_open wave_port_open
|
|
#define wave_write wave_port_write
|
|
#define wave_close wave_port_close
|
|
#define wave_is_busy wave_port_is_busy
|
|
#define wave_terminate wave_port_terminate
|
|
#define wave_get_read_position wave_port_get_read_position
|
|
#define wave_get_write_position wave_port_get_write_position
|
|
#define wave_flush wave_port_flush
|
|
#define wave_set_callback_is_output_enabled wave_port_set_callback_is_output_enabled
|
|
#define wave_test_get_write_buffer wave_port_test_get_write_buffer
|
|
#define wave_get_remaining_time wave_port_get_remaining_time
|
|
|
|
#endif
|
|
|
|
static t_wave_callback *my_callback_is_output_enabled = NULL;
|
|
|
|
#define N_WAV_BUF 10
|
|
#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 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 void 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
|
|
}
|
|
|
|
/* 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
|
|
{
|
|
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)
|
|
{
|
|
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
|
|
PaDeviceID playbackDevice = Pa_GetDefaultOutputDeviceID();
|
|
|
|
PaError err = Pa_OpenStream(&pa_stream,
|
|
// capture parameters
|
|
paNoDevice,
|
|
0,
|
|
paInt16,
|
|
NULL,
|
|
// playback parameters
|
|
playbackDevice,
|
|
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;
|
|
PaError err = Pa_OpenStream(&pa_stream,
|
|
// capture parameters
|
|
paNoDevice,
|
|
0,
|
|
paInt16,
|
|
NULL,
|
|
// playback parameters
|
|
playbackDevice,
|
|
out_channels,
|
|
paInt16,
|
|
NULL,
|
|
// general parameters
|
|
wave_samplerate, FRAMES_PER_BUFFER, 0,
|
|
paNoFlag,
|
|
pa_callback, (void *)userdata);
|
|
err = 0; // avoid warning
|
|
}
|
|
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;
|
|
}
|
|
|
|
#if (USE_PORTAUDIO == 19)
|
|
static void update_output_parameters(int selectedDevice, const PaDeviceInfo *deviceInfo)
|
|
{
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
static void select_device(const char *the_api)
|
|
{
|
|
#if (USE_PORTAUDIO == 19)
|
|
int numDevices = Pa_GetDeviceCount();
|
|
if (numDevices < 0)
|
|
assert(0);
|
|
|
|
PaDeviceIndex i = 0, selectedIndex = 0, defaultAlsaIndex = numDevices;
|
|
const PaDeviceInfo *deviceInfo = NULL;
|
|
const PaDeviceInfo *selectedDeviceInfo = NULL;
|
|
|
|
if (option_device_number >= 0) {
|
|
selectedIndex = option_device_number;
|
|
selectedDeviceInfo = Pa_GetDeviceInfo(selectedIndex);
|
|
}
|
|
|
|
if (selectedDeviceInfo == NULL) {
|
|
for (i = 0; i < numDevices; i++) {
|
|
deviceInfo = Pa_GetDeviceInfo(i);
|
|
|
|
if (deviceInfo == NULL)
|
|
break;
|
|
const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
|
|
|
|
if (hostInfo && hostInfo->type == paALSA) {
|
|
// Check (once) the default output device
|
|
if (defaultAlsaIndex == numDevices) {
|
|
defaultAlsaIndex = hostInfo->defaultOutputDevice;
|
|
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(defaultAlsaIndex);
|
|
update_output_parameters(defaultAlsaIndex, deviceInfo);
|
|
if (Pa_IsFormatSupported(NULL, &myOutputParameters, wave_samplerate) == 0) {
|
|
selectedIndex = defaultAlsaIndex;
|
|
selectedDeviceInfo = deviceInfo;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if the default output device does not match,
|
|
// look for the device with the highest number of output channels
|
|
|
|
update_output_parameters(i, deviceInfo);
|
|
|
|
if (Pa_IsFormatSupported(NULL, &myOutputParameters, wave_samplerate) == 0) {
|
|
if (!selectedDeviceInfo
|
|
|| (selectedDeviceInfo->maxOutputChannels < deviceInfo->maxOutputChannels)) {
|
|
selectedIndex = i;
|
|
selectedDeviceInfo = deviceInfo;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (selectedDeviceInfo)
|
|
update_output_parameters(selectedIndex, selectedDeviceInfo);
|
|
else {
|
|
i = Pa_GetDefaultOutputDevice();
|
|
deviceInfo = Pa_GetDeviceInfo(i);
|
|
update_output_parameters(i, deviceInfo);
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
void wave_set_callback_is_output_enabled(t_wave_callback *cb)
|
|
{
|
|
my_callback_is_output_enabled = cb;
|
|
}
|
|
|
|
int wave_init(int srate)
|
|
{
|
|
PaError err;
|
|
|
|
pa_stream = NULL;
|
|
wave_samplerate = srate;
|
|
mInCallbackFinishedState = false;
|
|
init_buffer();
|
|
|
|
// PortAudio sound output library
|
|
err = Pa_Initialize();
|
|
pa_init_err = err;
|
|
return err == paNoError;
|
|
}
|
|
|
|
void *wave_open(const char *the_api)
|
|
{
|
|
static int once = 0;
|
|
|
|
if (!once) {
|
|
select_device("alsa");
|
|
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)
|
|
i = 0;
|
|
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)
|
|
{
|
|
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;
|
|
} else if (!wave_is_busy(NULL))
|
|
my_stream_could_start = 1;
|
|
assert(BUFFER_LENGTH >= bytes_to_write);
|
|
|
|
if (myWrite >= myBuffer + BUFFER_LENGTH)
|
|
myWrite = myBuffer;
|
|
|
|
size_t aTotalFreeMem = 0;
|
|
char *aRead = myRead;
|
|
|
|
while (1) {
|
|
if (my_callback_is_output_enabled && (0 == my_callback_is_output_enabled()))
|
|
return 0;
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
int wave_is_busy(void *theHandler)
|
|
{
|
|
PaError active = 0;
|
|
|
|
if (pa_stream) {
|
|
#if USE_PORTAUDIO == 18
|
|
active = Pa_StreamActive(pa_stream)
|
|
&& (mInCallbackFinishedState == false);
|
|
#else
|
|
active = Pa_IsStreamActive(pa_stream)
|
|
&& (mInCallbackFinishedState == false);
|
|
#endif
|
|
}
|
|
|
|
return active == 1;
|
|
}
|
|
|
|
void wave_terminate()
|
|
{
|
|
Pa_Terminate();
|
|
}
|
|
|
|
uint32_t wave_get_read_position(void *theHandler)
|
|
{
|
|
return myReadPosition;
|
|
}
|
|
|
|
uint32_t wave_get_write_position(void *theHandler)
|
|
{
|
|
return myWritePosition;
|
|
}
|
|
|
|
int wave_get_remaining_time(uint32_t sample, uint32_t *time)
|
|
{
|
|
double a_time = 0;
|
|
|
|
if (!time || !pa_stream)
|
|
return -1;
|
|
|
|
if (sample > myReadPosition) {
|
|
// TBD: take in account time suplied by portaudio V18 API
|
|
a_time = sample - myReadPosition;
|
|
a_time = 0.5 + (a_time * 1000.0) / wave_samplerate;
|
|
} else
|
|
a_time = 0;
|
|
|
|
*time = (uint32_t)a_time;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void *wave_test_get_write_buffer()
|
|
{
|
|
return myWrite;
|
|
}
|
|
|
|
#else
|
|
|
|
int wave_init(int srate)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void *wave_open(const char *the_api)
|
|
{
|
|
return (void *)1;
|
|
}
|
|
|
|
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize)
|
|
{
|
|
return theSize;
|
|
}
|
|
|
|
int wave_close(void *theHandler)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int wave_is_busy(void *theHandler)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void wave_terminate()
|
|
{
|
|
}
|
|
|
|
uint32_t wave_get_read_position(void *theHandler)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
uint32_t wave_get_write_position(void *theHandler)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void wave_flush(void *theHandler)
|
|
{
|
|
}
|
|
|
|
typedef int (t_wave_callback)(void);
|
|
|
|
void wave_set_callback_is_output_enabled(t_wave_callback *cb)
|
|
{
|
|
}
|
|
|
|
extern void *wave_test_get_write_buffer()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
int wave_get_remaining_time(uint32_t sample, uint32_t *time)
|
|
{
|
|
if (!time) return -1;
|
|
*time = (uint32_t)0;
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
void clock_gettime2(struct timespec *ts)
|
|
{
|
|
struct timeval tv;
|
|
|
|
if (!ts)
|
|
return;
|
|
|
|
assert(gettimeofday(&tv, NULL) != -1);
|
|
ts->tv_sec = tv.tv_sec;
|
|
ts->tv_nsec = tv.tv_usec*1000;
|
|
}
|
|
|
|
void add_time_in_ms(struct timespec *ts, int time_in_ms)
|
|
{
|
|
if (!ts)
|
|
return;
|
|
|
|
uint64_t t_ns = (uint64_t)ts->tv_nsec + 1000000 * (uint64_t)time_in_ms;
|
|
while (t_ns >= ONE_BILLION) {
|
|
ts->tv_sec += 1;
|
|
t_ns -= ONE_BILLION;
|
|
}
|
|
ts->tv_nsec = (long int)t_ns;
|
|
}
|
|
|
|
#endif
|