From: <st...@us...> - 2003-07-10 14:19:56
|
Update of /cvsroot/iaxclient/iaxclient/lib In directory sc8-pr-cvs1:/tmp/cvs-serv22059 Modified Files: audio_portaudio.c iaxclient_lib.c iaxclient_lib.h Log Message: New PortAudio driver. This does: 1) Adding a mechanism to list and select audio devices (I'll add some UI for this to wx.c). 2) Making the backend more flexible w.r.t. frame sizes (currently, things are hard-coded for 20ms frames, which are used for GSM, but I think iLBC wants 30ms frames, for some reason. 3) Starting/stopping (and opening/closing) the audio devices as necessary, instead of only at startup. 4) Making the audio driver abstraction a little cleaner, which might make other audio drivers (like to external phone interfaces) easier. Index: audio_portaudio.c =================================================================== RCS file: /cvsroot/iaxclient/iaxclient/lib/audio_portaudio.c,v retrieving revision 1.12 retrieving revision 1.13 diff -u -d -r1.12 -r1.13 --- audio_portaudio.c 23 Jun 2003 13:32:18 -0000 1.12 +++ audio_portaudio.c 10 Jul 2003 14:19:53 -0000 1.13 @@ -21,15 +21,20 @@ #include "iaxclient_lib.h" +static PortAudioStream *iStream, *oStream; + +static selectedInput, selectedOutput; + +#define FRAMES_PER_BUFFER 80 /* 80 frames == 10ms */ + +#define RBSZ 1024 /* Needs to be Pow(2), 1024 = 512 samples = 64ms */ +static char inRingBuf[RBSZ], outRingBuf[RBSZ]; +static RingBuffer inRing, outRing; -static PABLIO_Stream *iStream; -static PABLIO_Stream *oStream; static int oneStream; static int virtualMono; -static const PaDeviceInfo **inputDevices; -static const PaDeviceInfo **outputDevices; -static int nInputDevices; -static int nOutputDevices; + +static int running; /* scan devices and stash pointers to dev structures. * But, these structures only remain valid while Pa is initialized, @@ -38,40 +43,95 @@ * PaDeviceID's associated with devices (since their index in these * input/output arrays isn't the same as their index in the combined * array */ -static int pa_scan_devices() { +static int scan_devices(struct iaxc_audio_driver *d) { int nDevices; int i; - /* we may be called multiple times */ - if(inputDevices){ - free(inputDevices); - inputDevices=NULL; + d->nDevices = nDevices = Pa_CountDevices(); + d->devices = malloc(nDevices * sizeof(struct iaxc_audio_device)); + + for(i=0;i<nDevices;i++) + { + const PaDeviceInfo *pa; + struct iaxc_audio_device *dev; + + pa=Pa_GetDeviceInfo(i); + dev = &(d->devices[i]); + + dev->name = (char *)pa->name; + dev->devID = i; + dev->capabilities = 0; + + if(pa->maxInputChannels > 0) + dev->capabilities |= IAXC_AD_INPUT; + + if(pa->maxOutputChannels > 0) + dev->capabilities |= IAXC_AD_OUTPUT; + + if(i == Pa_GetDefaultInputDeviceID()) + dev->capabilities |= IAXC_AD_INPUT_DEFAULT; + + if(i == Pa_GetDefaultOutputDeviceID()) + dev->capabilities |= IAXC_AD_OUTPUT_DEFAULT; } +} - if(outputDevices){ - free(outputDevices); - outputDevices=NULL; +void mono2stereo(SAMPLE *out, SAMPLE *in, int nSamples) { + int i; + //fprintf(stderr, "mono2stereo: %d samples\n", nSamples); + for(i=0;i<nSamples;i++) { + *(out++) = *in; + *(out++) = *(in++); } +} - nInputDevices = nOutputDevices = 0; +void stereo2mono(SAMPLE *out, SAMPLE *in, int nSamples) { + int i; + //fprintf(stderr, "stereo2mono: %d samples\n", nSamples); + for(i=0;i<nSamples;i++) { + *(out) = *(in++); + out++; in++; + //*(out++) += *(in++); + } +} - nDevices = Pa_CountDevices(); +int pa_callback(void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, PaTimestamp outTime, void *userData ) { + int totBytes = framesPerBuffer * sizeof(SAMPLE); - /* allocate in/out arrays big enough for all devices */ - inputDevices = malloc(nDevices * sizeof(PaDeviceInfo *)); - outputDevices = malloc(nDevices * sizeof(PaDeviceInfo *)); + short virtualBuffer[FRAMES_PER_BUFFER * 2]; - for(i=0;i<nDevices;i++) - { - const PaDeviceInfo *d; - d=Pa_GetDeviceInfo(i); + if(virtualMono && framesPerBuffer > FRAMES_PER_BUFFER) { + fprintf(stderr, "ERROR: buffer in callback is too big!\n"); + exit(1); + } - if(d->maxInputChannels > 0) - inputDevices[nInputDevices++] = d; + if(inputBuffer) { + /* input overflow might happen here */ + if(virtualMono) { + stereo2mono(virtualBuffer, inputBuffer, framesPerBuffer); + RingBuffer_Write(&inRing, virtualBuffer, totBytes); + } else { + RingBuffer_Write(&inRing, inputBuffer, totBytes); + } + } + if(outputBuffer) + { + int bWritten; + /* output underflow might happen here */ + if(virtualMono) { + bWritten = RingBuffer_Read(&outRing, virtualBuffer, totBytes); + mono2stereo(outputBuffer, virtualBuffer, bWritten/2); + bWritten *=2; + } else { + bWritten = RingBuffer_Read(&outRing, outputBuffer, totBytes); + } - if(d->maxOutputChannels > 0) - outputDevices[nOutputDevices++] = d; + /* zero underflowed space [ silence might be more golden than garbage? ] */ + if(bWritten < totBytes) + memset(outputBuffer + bWritten, 0, totBytes - bWritten); } + return 0; } @@ -91,18 +151,22 @@ * Win32 works fine, in all cases, with a single stream and real mono, * so far. * */ - -int pa_initialize_audio() { - PaError err; - - /* Open simplified blocking I/O layer on top of PortAudio. */ - +int pa_openstreams (struct iaxc_audio_driver *d ) { + PaError err; #ifndef MACOSX /* first, try opening one stream for in/out, Mono */ /* except for MacOSX, which needs virtual stereo */ - err = OpenAudioStream( &iStream, SAMPLE_RATE, paInt16, - (PABLIO_READ | PABLIO_WRITE | PABLIO_MONO) ); - + err = Pa_OpenStream ( &iStream, + selectedInput, 1, paInt16, NULL, /* input info */ + selectedOutput, 1, paInt16, NULL, /* output info */ + 8000.0, + FRAMES_PER_BUFFER, /* frames per buffer -- 10ms */ + 0, /* numbuffers */ /* use default */ + 0, /* flags */ + pa_callback, + NULL /* userdata */ + ); + if( err == paNoError ) { /* if this works, set iStream, oStream to this stream */ oStream = iStream; @@ -115,8 +179,16 @@ #ifndef LINUX /* then, we try a single stream, virtual stereo. Except on linux, * see note above */ - err = OpenAudioStream( &iStream, SAMPLE_RATE, paInt16, - (PABLIO_READ | PABLIO_WRITE | PABLIO_STEREO) ); + err = Pa_OpenStream ( &iStream, + selectedInput, 2, paInt16, NULL, /* input info */ + selectedOutput, 2, paInt16, NULL, /* output info */ + 8000.0, + FRAMES_PER_BUFFER, /* frames per buffer -- 10ms */ + 0, /* numbuffers */ /* use default */ + 0, /* flags */ + pa_callback, + NULL /* userdata */ + ); if( err == paNoError ) { /* if this works, set iStream, oStream to this stream */ @@ -130,15 +202,32 @@ /* finally, we go to the worst case. Two opens, virtual mono */ oneStream = 0; virtualMono = 1; - err = OpenAudioStream( &iStream, SAMPLE_RATE, paInt16, - (PABLIO_READ | PABLIO_STEREO) ); + err = Pa_OpenStream ( &iStream, + selectedInput, 2, paInt16, NULL, /* input info */ + paNoDevice, 0, paInt16, NULL, /* output info */ + 8000.0, + FRAMES_PER_BUFFER, /* frames per buffer -- 10ms */ + 0, /* numbuffers */ /* use default */ + 0, /* flags */ + pa_callback, + NULL /* userdata */ + ); if( err != paNoError ) { handle_paerror(err, "opening separate input stream"); return -1; } - err = OpenAudioStream( &oStream, SAMPLE_RATE, paInt16, - (PABLIO_WRITE | PABLIO_STEREO) ); + + err = Pa_OpenStream ( &oStream, + paNoDevice, 0, paInt16, NULL, /* input info */ + selectedOutput, 2, paInt16, NULL, /* output info */ + 8000.0, + FRAMES_PER_BUFFER, /* frames per buffer -- 10ms */ + 0, /* numbuffers */ /* use default */ + 0, /* flags */ + pa_callback, + NULL /* userdata */ + ); if( err != paNoError ) { handle_paerror(err, "opening separate output stream"); @@ -148,6 +237,49 @@ return 0; } +int pa_start (struct iaxc_audio_driver *d ) { + PaError err; + + if(running) return 0; + + //fprintf(stderr, "starting pa\n"); + + if(pa_openstreams(d)) + return -1; + + err = Pa_StartStream(iStream); + if(err != paNoError) + return -1; + + if(!oneStream){ + err = Pa_StartStream(oStream); + if(err != paNoError) { + Pa_StopStream(iStream); + return -1; + } + } + + running = 1; + return 0; +} + +int pa_stop (struct iaxc_audio_driver *d ) { + PaError err; + + if(!running) return 0; + + err = Pa_AbortStream(iStream); + err = Pa_CloseStream(iStream); + + if(!oneStream){ + err = Pa_AbortStream(oStream); + err = Pa_CloseStream(oStream); + } + + running = 0; + return 0; +} + void pa_shutdown_audio() { CloseAudioStream( iStream ); if(!oneStream) CloseAudioStream( oStream ); @@ -157,67 +289,83 @@ fprintf(stderr, "PortAudio error at %s: %s\n", where, Pa_GetErrorText(err)); } -void pa_read_audio_input() { +int pa_input(struct iaxc_audio_driver *d, void *samples, int *nSamples) { + static SAMPLE *stereoBuf = NULL; + static int stereoBufSiz = 0; + int bytestoread; + + bytestoread = *nSamples * sizeof(SAMPLE); + + /* we don't return partial buffers */ + if(RingBuffer_GetReadAvailable(&inRing) < bytestoread) { + *nSamples = 0; + return 0; + } + + RingBuffer_Read(&inRing, samples, bytestoread); } +int pa_output(struct iaxc_audio_driver *d, void *samples, int nSamples) { + static SAMPLE *stereoBuf = NULL; + static int stereoBufSiz = 0; + int bytestowrite = nSamples * sizeof(SAMPLE); -void mono2stereo(SAMPLE *out, SAMPLE *in, int nSamples) { - int i; - //fprintf(stderr, "mono2stereo: %d samples\n", nSamples); - for(i=0;i<nSamples;i++) { - *(out++) = *in; - *(out++) = *(in++); + RingBuffer_Write(&outRing, samples, bytestowrite); + + return 0; +} + +int pa_select_input (struct iaxc_audio_driver *d, int input) { + selectedInput = input; + if(running) { + pa_stop(d); + pa_start(d); } } -void stereo2mono(SAMPLE *out, SAMPLE *in, int nSamples) { - int i; - //fprintf(stderr, "stereo2mono: %d samples\n", nSamples); - for(i=0;i<nSamples;i++) { - *(out) = *(in++); - out++; in++; - //*(out++) += *(in++); +int pa_select_output (struct iaxc_audio_driver *d, int output) { + selectedOutput = output; + if(running) { + pa_stop(d); + pa_start(d); } } -void pa_play_recv_audio(void *fr, int fr_size) { - - SAMPLE stereobuf[FRAMES_PER_BLOCK * 2]; - SAMPLE *buf; - if(GetAudioStreamWriteable(oStream) < FRAMES_PER_BLOCK) - { - //fprintf(stderr, "audio_portaudio: audio output overflow\n"); - return; - } +int pa_destroy (struct iaxc_audio_driver *d ) { + //implementme + return 0; +} - if(virtualMono) { - mono2stereo(stereobuf, (SAMPLE *)fr, FRAMES_PER_BLOCK); - buf = stereobuf; - } else { - buf = (SAMPLE *)fr; - } +/* initialize audio driver */ +int pa_initialize (struct iaxc_audio_driver *d ) { + PaError err; - // Play the audio as decoded - WriteAudioStream(oStream, buf, FRAMES_PER_BLOCK); -} + /* initialize portaudio */ + if(paNoError != (err = Pa_Initialize())) + return err; -void pa_send_audio(struct timeval *lastouttm, struct iaxc_call *most_recent_answer, int iEncodeType) { - SAMPLE samples[FRAMES_PER_BLOCK * 2]; // could be stereo - SAMPLE monobuf[FRAMES_PER_BLOCK]; - SAMPLE *buf; + /* scan devices */ + scan_devices(d); - /* send all available complete frames */ - while(GetAudioStreamReadable(iStream) >= (FRAMES_PER_BLOCK)) - { - ReadAudioStream(iStream, samples, FRAMES_PER_BLOCK); - if(virtualMono) { - stereo2mono(monobuf, samples, FRAMES_PER_BLOCK); - buf = monobuf; - } else { - buf = samples; - } - send_encoded_audio(most_recent_answer, buf, iEncodeType); - } -} + /* setup methods */ + d->initialize = pa_initialize; + d->destroy = pa_destroy; + d->select_input = pa_select_input; + d->select_output = pa_select_output; + d->start = pa_start; + d->stop = pa_stop; + d->output = pa_output; + d->input = pa_input; + + /* setup private data stuff */ + selectedInput = Pa_GetDefaultInputDeviceID(); + selectedOutput = Pa_GetDefaultOutputDeviceID(); + + RingBuffer_Init(&inRing, RBSZ, inRingBuf); + RingBuffer_Init(&outRing, RBSZ, outRingBuf); + + running = 0; + return 0; +} Index: iaxclient_lib.c =================================================================== RCS file: /cvsroot/iaxclient/iaxclient/lib/iaxclient_lib.c,v retrieving revision 1.29 retrieving revision 1.30 diff -u -d -r1.29 -r1.30 --- iaxclient_lib.c 23 Jun 2003 19:29:03 -0000 1.29 +++ iaxclient_lib.c 10 Jul 2003 14:19:53 -0000 1.30 @@ -19,6 +19,7 @@ struct iaxc_registration *registrations = NULL; +struct iaxc_audio_driver audio; static int iAudioType; static int iEncodeType; @@ -28,7 +29,6 @@ int netfd; int port; int c, i; -char rcmd[RBUFSIZE]; int iaxc_audio_output_mode = 0; // Normal @@ -230,14 +230,15 @@ gettimeofday(&lastouttm,NULL); switch (iAudioType) { - case AUDIO_INTERNAL: #ifdef USE_WIN_AUDIO + case AUDIO_INTERNAL: if (win_initialize_audio() != 0) return -1; -#endif break; +#endif + default: case AUDIO_INTERNAL_PA: - if (pa_initialize_audio() != 0) + if (pa_initialize(&audio)) return -1; break; } @@ -247,16 +248,9 @@ void iaxc_shutdown() { MUTEXLOCK(&iaxc_lock); iaxc_dump_all_calls(); - switch (iAudioType) { - case AUDIO_INTERNAL: -#ifdef USE_WIN_AUDIO - win_shutdown_audio(); -#endif - break; - case AUDIO_INTERNAL_PA: - pa_shutdown_audio(); - break; - } + + audio.destroy(&audio); + MUTEXUNLOCK(&iaxc_lock); MUTEXDESTROY(&iaxc_lock); } @@ -350,27 +344,36 @@ if( (calls[selected_call].state & IAXC_CALL_STATE_OUTGOING) || (calls[selected_call].state & IAXC_CALL_STATE_COMPLETE)) { - switch (iAudioType) { - case AUDIO_INTERNAL: - iaxc_service_network(netfd); -#ifdef USE_WIN_AUDIO - win_process_audio_buffers(&lastouttm, &calls[selected_call], iEncodeType); -#endif - iaxc_service_network(netfd); - break; - case AUDIO_INTERNAL_PA: - iaxc_service_network(netfd); - pa_send_audio(&lastouttm, &calls[selected_call], iEncodeType); - break; - default: - iaxc_service_network(netfd); - iaxc_external_service_audio(); - iaxc_service_network(netfd); - break; + /* FIXME: frame size should be variable? */ + short buf[160]; + int toRead; + + // make sure audio is running + if(audio.start(&audio)) + { + iaxc_usermsg(IAXC_ERROR, "Can't start audio"); + } + + for(;;) { + toRead = 160; + if(audio.input(&audio,buf,&toRead)) + { + iaxc_usermsg(IAXC_ERROR, "ERROR reading audio\n"); + break; } + if(!toRead) break; /* frame not available */ + /* currently, pa will always give us 0 or what we asked + * for samples */ + + send_encoded_audio(&calls[selected_call], buf, iEncodeType); + } + } else { - static int i=0; - if(i++ % 50 == 0) iaxc_do_levels_callback(-99,-99); + static int i=0; + if(i++ % 50 == 0) iaxc_do_levels_callback(-99,-99); + + // make sure audio is stopped + audio.stop(&audio); } return 0; } @@ -405,23 +408,10 @@ return; } else { /* its an audio packet to be output to user */ total_consumed += cur; + if(iaxc_audio_output_mode != 0) continue; - switch (iAudioType) { - case AUDIO_INTERNAL: -#ifdef USE_WIN_AUDIO - win_flush_audio_output_buffers(); - win_play_recv_audio(fr, sizeof(fr)); -#else -#endif - break; - case AUDIO_INTERNAL_PA: - pa_play_recv_audio(fr, sizeof(fr)); - break; - case AUDIO_EXTERNAL: - // Add external audio callback here - break; - } + audio.output(&audio,fr,sizeof(fr)/2); } } } Index: iaxclient_lib.h =================================================================== RCS file: /cvsroot/iaxclient/iaxclient/lib/iaxclient_lib.h,v retrieving revision 1.18 retrieving revision 1.19 diff -u -d -r1.18 -r1.19 --- iaxclient_lib.h 3 Jul 2003 18:17:14 -0000 1.18 +++ iaxclient_lib.h 10 Jul 2003 14:19:53 -0000 1.19 @@ -59,7 +59,6 @@ #endif -#define RBUFSIZE 256 #define MAXARGS 10 #define MAXARG 256 #define MAX_SESSIONS 4 @@ -76,16 +75,44 @@ void iaxc_do_levels_callback(float input, float output); -#if 0 -/* Audio Driver Abstraction TODO */ -struct audio_driver_struct { - char *name; - int (*initialize)(struct audio_driver_struct *d); +#define IAXC_AD_INPUT (1<<0) +#define IAXC_AD_OUTPUT (1<<1) +#define IAXC_AD_INPUT_DEFAULT (1<<2) +#define IAXC_AD_OUTPUT_DEFAULT (1<<3) + +struct iaxc_audio_device { + char *name; /* name of the device */ + long capabilities; /* flags, defined above */ + int devID; /* driver-specific ID */ +}; +struct iaxc_audio_driver { + /* data */ + char *name; /* driver name */ + struct iaxc_audio_device *devices; /* list of devices */ + int nDevices; /* count of devices */ + void *priv; /* pointer to private data */ + + /* methods */ + int (*initialize)(struct iaxc_audio_driver *d); + int (*destroy)(struct iaxc_audio_driver *d); /* free resources */ + int (*select_input)(struct iaxc_audio_driver *d, int devID); + int (*select_output)(struct iaxc_audio_driver *d, int devID); + + /* + * select_ring ? + * set_input_level + * set_output_level + * set_latency + */ + + int (*start)(struct iaxc_audio_driver *d); + int (*stop)(struct iaxc_audio_driver *d); + int (*output)(struct iaxc_audio_driver *d, void *samples, int nSamples); + int (*input)(struct iaxc_audio_driver *d, void *samples, int *nSamples); }; -typedef struct audio_driver_struct *iaxc_audio_driver; -#endif + #include "iaxclient.h" |