From bbb07474a70fad0e60d442fc2d7466922b3bb3de Mon Sep 17 00:00:00 2001 From: Anthony Minessale Date: Fri, 9 Dec 2005 19:23:04 +0000 Subject: [PATCH] adding some shit git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@100 d0543943-73ff-0310-b7d9-9358b9ac24b2 --- src/include/switch_module_interfaces.h | 7 + src/include/switch_utils.h | 1 + src/mod/mod_portaudio/mod_portaudio.c | 309 ++++++++++++----------- src/mod/mod_portaudio/pablio.c | 327 +++++++++++++++++++++++++ src/mod/mod_portaudio/pablio.h | 109 +++++++++ src/mod/mod_portaudio/ringbuffer.c | 199 +++++++++++++++ src/mod/mod_portaudio/ringbuffer.h | 102 ++++++++ w32/vsnet/mod_PortAudio.vcproj | 14 +- 8 files changed, 928 insertions(+), 140 deletions(-) create mode 100644 src/mod/mod_portaudio/pablio.c create mode 100644 src/mod/mod_portaudio/pablio.h create mode 100644 src/mod/mod_portaudio/ringbuffer.c create mode 100644 src/mod/mod_portaudio/ringbuffer.h diff --git a/src/include/switch_module_interfaces.h b/src/include/switch_module_interfaces.h index 378ebe3371..a005c688ab 100644 --- a/src/include/switch_module_interfaces.h +++ b/src/include/switch_module_interfaces.h @@ -251,4 +251,11 @@ struct switch_application_interface { const struct switch_application_interface *next; }; +struct switch_api_interface { + const char *interface_name; + const char *desc; + switch_status (*function)(char *in, char *out, size_t outlen); + const struct switch_api_interface *next; +}; + #endif diff --git a/src/include/switch_utils.h b/src/include/switch_utils.h index 05a3cb8c0e..266c022575 100644 --- a/src/include/switch_utils.h +++ b/src/include/switch_utils.h @@ -78,6 +78,7 @@ typedef typeof(tv.tv_usec) switch_suseconds_t; #define switch_copy_flags(dest, src, flags) (dest)->flags &= ~(flags); (dest)->flags |= ((src)->flags & (flags)) #define switch_strlen_zero(s) (s && *s != '\0') ? 0 : 1 #define switch_yield(ms) apr_sleep(ms * 10); apr_thread_yield(); +#define SWITCH_DECLARE_GLOBAL_STRING_FUNC(fname, vname) static void fname(char *string) { if (vname) {free(vname); vname = NULL;}vname = strdup(string);} SWITCH_DECLARE(unsigned int) switch_separate_string(char *buf, char delim, char **array, int arraylen); SWITCH_DECLARE(switch_status) switch_socket_create_pollfd(switch_pollfd_t *poll, switch_socket_t *sock, unsigned int flags, switch_memory_pool *pool); diff --git a/src/mod/mod_portaudio/mod_portaudio.c b/src/mod/mod_portaudio/mod_portaudio.c index 36cb1c88cb..d96c03d0cb 100644 --- a/src/mod/mod_portaudio/mod_portaudio.c +++ b/src/mod/mod_portaudio/mod_portaudio.c @@ -42,6 +42,8 @@ static const char modname[] = "mod_portaudio"; static switch_memory_pool *module_pool; static int running = 1; +#define SAMPLE_TYPE paInt16 +typedef short SAMPLE; typedef enum { TFLAG_IO = (1 << 0), @@ -60,9 +62,12 @@ typedef enum { static struct { int debug; int port; + char *cid_name; + char *cid_num; char *dialplan; - char *soundcard; unsigned int flags; + int indev; + int outdev; } globals; struct private_object { @@ -73,31 +78,18 @@ struct private_object { unsigned char databuf[1024]; switch_core_session *session; switch_caller_profile *caller_profile; - unsigned int codec; - unsigned int codecs; - switch_mutex_t *mutex; - switch_thread_cond_t *cond; + PaError err; + PABLIO_Stream *audio_in; + PABLIO_Stream *audio_out; + int indev; + int outdev; }; -static void set_global_dialplan(char *dialplan) -{ - if (globals.dialplan) { - free(globals.dialplan); - globals.dialplan = NULL; - } - - globals.dialplan = strdup(dialplan); -} +SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_dialplan, globals.dialplan) +SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_cid_name, globals.cid_name) +SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_cid_num, globals.cid_num) + -static void set_global_soundcard(char *soundcard) -{ - if (globals.soundcard) { - free(globals.soundcard); - globals.soundcard = NULL; - } - - globals.soundcard = strdup(soundcard); -} static const switch_endpoint_interface channel_endpoint_interface; static switch_status channel_on_init(switch_core_session *session); @@ -131,10 +123,6 @@ static switch_status channel_on_init(switch_core_session *session) switch_set_flag(tech_pvt, TFLAG_IO); - switch_mutex_init(&tech_pvt->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); - switch_thread_cond_create(&tech_pvt->cond, switch_core_session_get_pool(session)); - switch_mutex_lock(tech_pvt->mutex); - /* Move Channel's State Machine to RING */ switch_channel_set_state(channel, CS_RING); @@ -187,11 +175,13 @@ static switch_status channel_on_hangup(switch_core_session *session) assert(tech_pvt != NULL); switch_clear_flag(tech_pvt, TFLAG_IO); - switch_thread_cond_signal(tech_pvt->cond); switch_core_codec_destroy(&tech_pvt->read_codec); switch_core_codec_destroy(&tech_pvt->write_codec); - + + CloseAudioStream(tech_pvt->audio_in); + CloseAudioStream(tech_pvt->audio_out); + switch_console_printf(SWITCH_CHANNEL_CONSOLE, "%s CHANNEL HANGUP\n", switch_channel_get_name(channel)); return SWITCH_STATUS_SUCCESS; @@ -324,35 +314,21 @@ static switch_status channel_read_frame(switch_core_session *session, switch_fra { switch_channel *channel = NULL; struct private_object *tech_pvt = NULL; - + int t; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); + + if ((t = ReadAudioStream(tech_pvt->audio_in, tech_pvt->read_frame.data, tech_pvt->read_codec.implementation->samples_per_frame))) { + tech_pvt->read_frame.datalen = t * 2; + tech_pvt->read_frame.samples = t; + *frame = &tech_pvt->read_frame; + return SWITCH_STATUS_SUCCESS; + } + - for(;;) { - if (switch_test_flag(tech_pvt, TFLAG_IO)) { - switch_thread_cond_wait(tech_pvt->cond, tech_pvt->mutex); - if (!switch_test_flag(tech_pvt, TFLAG_IO)) { - return SWITCH_STATUS_FALSE; - } - while (switch_test_flag(tech_pvt, TFLAG_IO) && !switch_test_flag(tech_pvt, TFLAG_VOICE)) { - switch_yield(1000); - } - - if (switch_test_flag(tech_pvt, TFLAG_IO)) { - switch_clear_flag(tech_pvt, TFLAG_VOICE); - if(!tech_pvt->read_frame.datalen) { - continue; - } - - *frame = &tech_pvt->read_frame; - return SWITCH_STATUS_SUCCESS; - } - } - break; - } return SWITCH_STATUS_FALSE; } @@ -376,7 +352,8 @@ static switch_status channel_write_frame(switch_core_session *session, switch_fr switch_swap_linear(frame->data, (int)frame->datalen / 2); } */ - + + WriteAudioStream(tech_pvt->audio_out, (short *)frame->data, (int)(frame->datalen / sizeof(SAMPLE))); //XXX send voice return SWITCH_STATUS_SUCCESS; @@ -436,6 +413,8 @@ static const switch_loadable_module_interface channel_module_interface = { static int dump_info(void); static void make_call(char *dest); +static switch_status load_config(void); +static int get_dev_by_name(char *name, int in); SWITCH_MOD_DECLARE(switch_status) switch_module_load(const switch_loadable_module_interface **interface, char *filename) { @@ -443,8 +422,9 @@ SWITCH_MOD_DECLARE(switch_status) switch_module_load(const switch_loadable_modul switch_console_printf(SWITCH_CHANNEL_CONSOLE, "OH OH no pool\n"); return SWITCH_STATUS_TERM; } - - Pa_Initialize(); + + Pa_Initialize(); + load_config(); dump_info(); /* connect my internal structure to the blank pointer passed to me */ *interface = &channel_module_interface; @@ -473,8 +453,22 @@ static switch_status load_config(void) globals.debug = atoi(val); } else if (!strcmp(var, "dialplan")) { set_global_dialplan(val); - } else if (!strcmp(var, "soundcard")) { - set_global_soundcard(val); + } else if (!strcmp(var, "cid_name")) { + set_global_cid_name(val); + } else if (!strcmp(var, "cid_num")) { + set_global_cid_num(val); + } else if (!strcmp(var, "indev")) { + if (*val == '#') { + globals.indev = atoi(val+1); + } else { + globals.indev = get_dev_by_name(val, 1); + } + } else if (!strcmp(var, "outdev")) { + if (*val == '#') { + globals.outdev = atoi(val+1); + } else { + globals.outdev = get_dev_by_name(val, 1); + } } } } @@ -482,96 +476,118 @@ static switch_status load_config(void) if (!globals.dialplan) { set_global_dialplan("default"); } - + switch_config_close_file(&cfg); - + return SWITCH_STATUS_SUCCESS; } SWITCH_MOD_DECLARE(switch_status) switch_module_runtime(void) { - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "sleep\n"); - - switch_yield(500000); - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "sleep\n"); - - make_call("1000"); - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "sleep\n"); - + + switch_yield(50000); + make_call("8888"); return SWITCH_STATUS_TERM; } SWITCH_MOD_DECLARE(switch_status) switch_module_shutdown(void) { - Pa_Terminate(); + Pa_Terminate(); return SWITCH_STATUS_SUCCESS; } +static int get_dev_by_name(char *name, int in) +{ + int i; + int numDevices; + const PaDeviceInfo *pdi; + numDevices = Pa_CountDevices(); -static int dump_info(void) -{ - int i,j; - int numDevices; - const PaDeviceInfo *pdi; - PaError err; - numDevices = Pa_CountDevices(); - if( numDevices < 0 ) - { - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "ERROR: Pa_CountDevices returned 0x%x\n", numDevices ); - err = numDevices; - goto error; - } - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Number of devices = %d\n", numDevices ); - for( i=0; iname ); - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Max Inputs = %d", pdi->maxInputChannels ); - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, ", Max Outputs = %d\n", pdi->maxOutputChannels ); - if( pdi->numSampleRates == -1 ) - { - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Sample Rate Range = %f to %f\n", pdi->sampleRates[0], pdi->sampleRates[1] ); - } - else - { - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Sample Rates ="); - for( j=0; jnumSampleRates; j++ ) - { - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, " %8.2f,", pdi->sampleRates[j] ); - } - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "\n"); - } - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Native Sample Formats = "); - if( pdi->nativeSampleFormats & paInt8 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paInt8, "); - if( pdi->nativeSampleFormats & paUInt8 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paUInt8, "); - if( pdi->nativeSampleFormats & paInt16 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paInt16, "); - if( pdi->nativeSampleFormats & paInt32 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paInt32, "); - if( pdi->nativeSampleFormats & paFloat32 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paFloat32, "); - if( pdi->nativeSampleFormats & paInt24 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paInt24, "); - if( pdi->nativeSampleFormats & paPackedInt24 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paPackedInt24, "); - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "\n"); - } - - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "----------------------------------------------\n"); - return 0; -error: - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "An error occured while using the portaudio stream\n" ); - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Error number: %d\n", err ); - switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Error message: %s\n", Pa_GetErrorText( err ) ); - return err; -} - -static void make_call(char *dest) -{ + if( numDevices < 0 ) { + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "ERROR: Pa_CountDevices returned 0x%x\n", numDevices ); + return -2; + } + + for( i=0; iname, name)) { + if(in && pdi->maxInputChannels) { + return i; + } else if (!in && pdi->maxOutputChannels) { + return i; + } + } + } + return -1; +} + + +static int dump_info(void) +{ + int i,j; + int numDevices; + const PaDeviceInfo *pdi; + PaError err; + numDevices = Pa_CountDevices(); + if( numDevices < 0 ) + { + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "ERROR: Pa_CountDevices returned 0x%x\n", numDevices ); + err = numDevices; + goto error; + } + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Number of devices = %d\n", numDevices ); + for( i=0; iname ); + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Max Inputs = %d", pdi->maxInputChannels ); + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, ", Max Outputs = %d\n", pdi->maxOutputChannels ); + if( pdi->numSampleRates == -1 ) + { + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Sample Rate Range = %f to %f\n", pdi->sampleRates[0], pdi->sampleRates[1] ); + } + else + { + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Sample Rates ="); + for( j=0; jnumSampleRates; j++ ) + { + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, " %8.2f,", pdi->sampleRates[j] ); + } + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "\n"); + } + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Native Sample Formats = "); + if( pdi->nativeSampleFormats & paInt8 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paInt8, "); + if( pdi->nativeSampleFormats & paUInt8 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paUInt8, "); + if( pdi->nativeSampleFormats & paInt16 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paInt16, "); + if( pdi->nativeSampleFormats & paInt32 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paInt32, "); + if( pdi->nativeSampleFormats & paFloat32 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paFloat32, "); + if( pdi->nativeSampleFormats & paInt24 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paInt24, "); + if( pdi->nativeSampleFormats & paPackedInt24 ) switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "paPackedInt24, "); + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "\n"); + } + + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "----------------------------------------------\n"); + return 0; +error: + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "An error occured while using the portaudio stream\n" ); + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Error number: %d\n", err ); + switch_console_printf(SWITCH_CHANNEL_CONSOLE_CLEAN, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} + +static void make_call(char *dest) +{ switch_core_session *session; - + int sample_rate = 8000; + int codec_ms = 20; + switch_console_printf(SWITCH_CHANNEL_CONSOLE, "New Inbound Channel\n"); if ((session = switch_core_session_request(&channel_endpoint_interface, NULL))) { struct private_object *tech_pvt; @@ -587,11 +603,10 @@ static void make_call(char *dest) return; } - if ((tech_pvt->caller_profile = switch_caller_profile_new(session, globals.dialplan, - "Test This Shit", - "1231231234", + globals.cid_name, + globals.cid_num, NULL, NULL, dest))) { @@ -604,8 +619,8 @@ static void make_call(char *dest) if (switch_core_codec_init(&tech_pvt->read_codec, "L16", - 0, - 0, + sample_rate, + codec_ms, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, NULL) != SWITCH_STATUS_SUCCESS) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Can't load codec?\n"); @@ -613,8 +628,8 @@ static void make_call(char *dest) } else { if (switch_core_codec_init(&tech_pvt->write_codec, "L16", - 0, - 0, + sample_rate, + codec_ms, SWITCH_CODEC_FLAG_ENCODE |SWITCH_CODEC_FLAG_DECODE, NULL) != SWITCH_STATUS_SUCCESS) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Can't load codec?\n"); switch_core_codec_destroy(&tech_pvt->read_codec); @@ -622,9 +637,29 @@ static void make_call(char *dest) } } + tech_pvt->read_frame.codec = &tech_pvt->read_codec; + switch_core_session_set_read_codec(tech_pvt->session, &tech_pvt->read_codec); + switch_core_session_set_write_codec(tech_pvt->session, &tech_pvt->write_codec); - switch_channel_set_state(channel, CS_INIT); - switch_core_session_thread_launch(session); + tech_pvt->indev = globals.indev; + tech_pvt->outdev = globals.outdev; + + if ((tech_pvt->err = OpenAudioStream( &tech_pvt->audio_in, sample_rate, SAMPLE_TYPE, PABLIO_READ | PABLIO_MONO, tech_pvt->indev, -1)) == paNoError) { + if ((tech_pvt->err = OpenAudioStream(&tech_pvt->audio_out, sample_rate, SAMPLE_TYPE, PABLIO_WRITE | PABLIO_MONO, -1, tech_pvt->outdev)) != paNoError) { + switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Can't open audio out!\n"); + CloseAudioStream(tech_pvt->audio_in); + } + } else { + switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Can't open audio in!\n"); + } + if (tech_pvt->err == paNoError) { + switch_channel_set_state(channel, CS_INIT); + switch_core_session_thread_launch(session); + } else { + switch_core_codec_destroy(&tech_pvt->read_codec); + switch_core_codec_destroy(&tech_pvt->write_codec); + switch_core_session_destroy(&session); + } } - + } \ No newline at end of file diff --git a/src/mod/mod_portaudio/pablio.c b/src/mod/mod_portaudio/pablio.c new file mode 100644 index 0000000000..b5735daea0 --- /dev/null +++ b/src/mod/mod_portaudio/pablio.c @@ -0,0 +1,327 @@ +/* + * $Id: pablio.c,v 1.1.1.1.4.4 2003/03/13 17:28:33 philburk Exp $ + * pablio.c + * Portable Audio Blocking Input/Output utility. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* History: + * PLB021214 - check for valid stream in CloseAudioStream() to prevent hang. + * add timeOutMSec to CloseAudioStream() to prevent hang. + */ +#include +#include +#include +#include "portaudio.h" +#include "ringbuffer.h" +#include "pablio.h" +#include + +/************************************************************************/ +/******** Constants *****************************************************/ +/************************************************************************/ + +#define FRAMES_PER_BUFFER (256) + +/************************************************************************/ +/******** Prototypes ****************************************************/ +/************************************************************************/ + +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +static PaError PABLIO_InitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame ); +static PaError PABLIO_TermFIFO( RingBuffer *rbuf ); + +/************************************************************************/ +/******** Functions *****************************************************/ +/************************************************************************/ + +/* Called from PortAudio. + * Read and write data only if there is room in FIFOs. + */ +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + PABLIO_Stream *data = (PABLIO_Stream*)userData; + long numBytes = data->bytesPerFrame * framesPerBuffer; + (void) outTime; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer != NULL ) + { + RingBuffer_Write( &data->inFIFO, inputBuffer, numBytes ); + } + if( outputBuffer != NULL ) + { + int i; + int numRead = RingBuffer_Read( &data->outFIFO, outputBuffer, numBytes ); + /* Zero out remainder of buffer if we run out of data. */ + for( i=numRead; ibuffer ) free( rbuf->buffer ); + rbuf->buffer = NULL; + return paNoError; +} + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesWritten; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesWritten = RingBuffer_Write( &aStream->outFIFO, p, numBytes ); + numBytes -= bytesWritten; + p += bytesWritten; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesRead; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesRead = RingBuffer_Read( &aStream->inFIFO, p, numBytes ); + numBytes -= bytesRead; + p += bytesRead; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ) +{ + int bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + return bytesEmpty / aStream->bytesPerFrame; +} + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ) +{ + int bytesFull = RingBuffer_GetReadAvailable( &aStream->inFIFO ); + return bytesFull / aStream->bytesPerFrame; +} + +/************************************************************/ +static unsigned long RoundUpToNextPowerOf2( unsigned long n ) +{ + long numBits = 0; + if( ((n-1) & n) == 0) return n; /* Already Power of two. */ + while( n > 0 ) + { + n= n>>1; + numBits++; + } + return (1<samplesPerFrame = ((flags&PABLIO_MONO) != 0) ? 1 : 2; + aStream->bytesPerFrame = bytesPerSample * aStream->samplesPerFrame; + + /* Initialize PortAudio */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Warning: numFrames must be larger than amount of data processed per interrupt + * inside PA to prevent glitches. Just to be safe, adjust size upwards. + */ + minNumBuffers = 2 * Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate ); + numFrames = minNumBuffers * FRAMES_PER_BUFFER; + /* The PortAudio callback runs in a high priority thread. But PABLIO + * runs in a normal foreground thread. So we may have much worse + * latency in PABLIO. So adjust latency to a safe level. + */ + { + const int safeLatencyMSec = 200; + int minLatencyMSec = (int) ((1000 * numFrames) / sampleRate); + if( minLatencyMSec < safeLatencyMSec ) + { + numFrames = (int) ((safeLatencyMSec * sampleRate) / 1000); + } + } + numFrames = RoundUpToNextPowerOf2( numFrames ); + + /* Initialize Ring Buffers */ + doRead = ((flags & PABLIO_READ) != 0); + doWrite = ((flags & PABLIO_WRITE) != 0); + if(doRead) + { + err = PABLIO_InitFIFO( &aStream->inFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + } + if(doWrite) + { + long numBytes; + err = PABLIO_InitFIFO( &aStream->outFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + /* Make Write FIFO appear full initially. */ + numBytes = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + RingBuffer_AdvanceWriteIndex( &aStream->outFIFO, numBytes ); + } + + /* Open a PortAudio stream that we will use to communicate with the underlying + * audio drivers. */ + err = Pa_OpenStream( + &aStream->stream, + (doRead ? (indev > -1) ? indev : Pa_GetDefaultInputDeviceID() : paNoDevice), + (doRead ? aStream->samplesPerFrame : 0 ), + format, + NULL, + (doWrite ? (outdev > -1) ? outdev : Pa_GetDefaultOutputDeviceID() : paNoDevice), + (doWrite ? aStream->samplesPerFrame : 0 ), + format, + NULL, + sampleRate, + FRAMES_PER_BUFFER, + minNumBuffers, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + blockingIOCallback, + aStream ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( aStream->stream ); + if( err != paNoError ) goto error; + + *rwblPtr = aStream; + return paNoError; + +error: + CloseAudioStream( aStream ); + *rwblPtr = NULL; + return err; +} + +/************************************************************/ +PaError CloseAudioStream( PABLIO_Stream *aStream ) +{ + PaError err = paNoError; + int bytesEmpty; + int byteSize = aStream->outFIFO.bufferSize; + + if( aStream->stream != NULL ) /* Make sure stream was opened. PLB021214 */ + { + /* If we are writing data, make sure we play everything written. */ + if( byteSize > 0 ) + { + int timeOutMSec = 2000; + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + while( (bytesEmpty < byteSize) && (timeOutMSec > 0) ) + { + Pa_Sleep( 20 ); + timeOutMSec -= 20; + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + } + } + err = Pa_StopStream( aStream->stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( aStream->stream ); + } + +error: + Pa_Terminate(); + PABLIO_TermFIFO( &aStream->inFIFO ); + PABLIO_TermFIFO( &aStream->outFIFO ); + free( aStream ); + return err; +} diff --git a/src/mod/mod_portaudio/pablio.h b/src/mod/mod_portaudio/pablio.h new file mode 100644 index 0000000000..25bccf8998 --- /dev/null +++ b/src/mod/mod_portaudio/pablio.h @@ -0,0 +1,109 @@ +#ifndef _PABLIO_H +#define _PABLIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id: pablio.h,v 1.1.1.1 2002/01/22 00:52:53 phil Exp $ + * PABLIO.h + * Portable Audio Blocking read/write utility. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * Include file for PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" +#include "ringbuffer.h" +#include + +typedef struct +{ + RingBuffer inFIFO; + RingBuffer outFIFO; + PortAudioStream *stream; + int bytesPerFrame; + int samplesPerFrame; +} +PABLIO_Stream; + +/* Values for flags for OpenAudioStream(). */ +#define PABLIO_READ (1<<0) +#define PABLIO_WRITE (1<<1) +#define PABLIO_READ_WRITE (PABLIO_READ|PABLIO_WRITE) +#define PABLIO_MONO (1<<2) +#define PABLIO_STEREO (1<<3) + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ); + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ); + +/************************************************************ + * Opens a PortAudio stream with default characteristics. + * Allocates PABLIO_Stream structure. + * + * flags parameter can be an ORed combination of: + * PABLIO_READ, PABLIO_WRITE, or PABLIO_READ_WRITE, + * and either PABLIO_MONO or PABLIO_STEREO + */ +PaError OpenAudioStream( PABLIO_Stream **aStreamPtr, double sampleRate, + PaSampleFormat format, long flags, int indev, int outdev ); + +PaError CloseAudioStream( PABLIO_Stream *aStream ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _PABLIO_H */ diff --git a/src/mod/mod_portaudio/ringbuffer.c b/src/mod/mod_portaudio/ringbuffer.c new file mode 100644 index 0000000000..867b501965 --- /dev/null +++ b/src/mod/mod_portaudio/ringbuffer.c @@ -0,0 +1,199 @@ +/* + * $Id: ringbuffer.c,v 1.1.1.1 2002/01/22 00:52:53 phil Exp $ + * ringbuffer.c + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "ringbuffer.h" +#include + +/*************************************************************************** + * Initialize FIFO. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ) +{ + if( ((numBytes-1) & numBytes) != 0) return -1; /* Not Power of two. */ + rbuf->bufferSize = numBytes; + rbuf->buffer = (char *)dataPtr; + RingBuffer_Flush( rbuf ); + rbuf->bigMask = (numBytes*2)-1; + rbuf->smallMask = (numBytes)-1; + return 0; +} +/*************************************************************************** +** Return number of bytes available for reading. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ) +{ + return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask ); +} +/*************************************************************************** +** Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ) +{ + return ( rbuf->bufferSize - RingBuffer_GetReadAvailable(rbuf)); +} + +/*************************************************************************** +** Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ) +{ + rbuf->writeIndex = rbuf->readIndex = 0; +} + +/*************************************************************************** +** Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetWriteAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if write is not contiguous. */ + index = rbuf->writeIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} + + +/*************************************************************************** +*/ +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->writeIndex = (rbuf->writeIndex + numBytes) & rbuf->bigMask; +} + +/*************************************************************************** +** Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetReadAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if read is not contiguous. */ + index = rbuf->readIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} +/*************************************************************************** +*/ +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->readIndex = (rbuf->readIndex + numBytes) & rbuf->bigMask; +} + +/*************************************************************************** +** Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numWritten; + void *data1, *data2; + numWritten = RingBuffer_GetWriteRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + + memcpy( data1, data, size1 ); + data = ((char *)data) + size1; + memcpy( data2, data, size2 ); + } + else + { + memcpy( data1, data, size1 ); + } + RingBuffer_AdvanceWriteIndex( rbuf, numWritten ); + return numWritten; +} + +/*************************************************************************** +** Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numRead; + void *data1, *data2; + numRead = RingBuffer_GetReadRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + memcpy( data, data1, size1 ); + data = ((char *)data) + size1; + memcpy( data, data2, size2 ); + } + else + { + memcpy( data, data1, size1 ); + } + RingBuffer_AdvanceReadIndex( rbuf, numRead ); + return numRead; +} diff --git a/src/mod/mod_portaudio/ringbuffer.h b/src/mod/mod_portaudio/ringbuffer.h new file mode 100644 index 0000000000..6f65694471 --- /dev/null +++ b/src/mod/mod_portaudio/ringbuffer.h @@ -0,0 +1,102 @@ +#ifndef _RINGBUFFER_H +#define _RINGBUFFER_H +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id: ringbuffer.h,v 1.1.1.1.4.2 2003/04/28 17:45:34 philburk Exp $ + * ringbuffer.h + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program is distributed with the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "ringbuffer.h" +#include + +typedef struct +{ + long bufferSize; /* Number of bytes in FIFO. Power of 2. Set by RingBuffer_Init. */ +/* These are declared volatile because they are written by a different thread than the reader. */ + volatile long writeIndex; /* Index of next writable byte. Set by RingBuffer_AdvanceWriteIndex. */ + volatile long readIndex; /* Index of next readable byte. Set by RingBuffer_AdvanceReadIndex. */ + long bigMask; /* Used for wrapping indices with extra bit to distinguish full/empty. */ + long smallMask; /* Used for fitting indices to buffer. */ + char *buffer; +} +RingBuffer; +/* + * Initialize Ring Buffer. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ); + +/* Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ); + +/* Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ); +/* Return number of bytes available for read. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ); +/* Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ); +/* Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ); + +/* Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ); + +/* Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be read or numBytes, whichever is smaller. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); + +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _RINGBUFFER_H */ diff --git a/w32/vsnet/mod_PortAudio.vcproj b/w32/vsnet/mod_PortAudio.vcproj index d2da06e1d2..de624fc444 100644 --- a/w32/vsnet/mod_PortAudio.vcproj +++ b/w32/vsnet/mod_PortAudio.vcproj @@ -42,7 +42,7 @@ @@ -207,6 +207,14 @@ Filter="h;hpp;hxx;hm;inl;inc;xsd" UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > + + + +