1 /*
2  * $Id: pablio.c,v 1.3 2006/06/10 21:30:55 dmazzoni Exp $
3  * pablio.c
4  * Portable Audio Blocking Input/Output utility.
5  *
6  * Author: Phil Burk, http://www.softsynth.com
7  *
8  * This program uses the PortAudio Portable Audio Library.
9  * For more information see: http://www.audiomulch.com/portaudio/
10  * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining
13  * a copy of this software and associated documentation files
14  * (the "Software"), to deal in the Software without restriction,
15  * including without limitation the rights to use, copy, modify, merge,
16  * publish, distribute, sublicense, and/or sell copies of the Software,
17  * and to permit persons to whom the Software is furnished to do so,
18  * subject to the following conditions:
19  *
20  * The above copyright notice and this permission notice shall be
21  * included in all copies or substantial portions of the Software.
22  *
23  * Any person wishing to distribute modifications to the Software is
24  * requested to send the modifications to the original developer so that
25  * they can be incorporated into the canonical version.
26  *
27  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
31  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
32  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34  *
35  */
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <math.h>
39 #include "portaudio.h"
40 #include "ringbuffer.h"
41 #include "pablio.h"
42 #include <string.h>
43 
44 /************************************************************************/
45 /******** Constants *****************************************************/
46 /************************************************************************/
47 
48 #define FRAMES_PER_BUFFER    (256)
49 
50 /************************************************************************/
51 /******** Prototypes ****************************************************/
52 /************************************************************************/
53 
54 static int blockingIOCallback( void *inputBuffer, void *outputBuffer,
55                                unsigned long framesPerBuffer,
56                                PaTimestamp outTime, void *userData );
57 static PaError PABLIO_InitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame );
58 static PaError PABLIO_TermFIFO( RingBuffer *rbuf );
59 
60 /************************************************************************/
61 /******** Functions *****************************************************/
62 /************************************************************************/
63 
64 /* Called from PortAudio.
65  * Read and write data only if there is room in FIFOs.
66  */
blockingIOCallback(void * inputBuffer,void * outputBuffer,unsigned long framesPerBuffer,PaTimestamp outTime,void * userData)67 static int blockingIOCallback( void *inputBuffer, void *outputBuffer,
68                                unsigned long framesPerBuffer,
69                                PaTimestamp outTime, void *userData )
70 {
71     PABLIO_Stream *data = (PABLIO_Stream*)userData;
72     long numBytes = data->bytesPerFrame * framesPerBuffer;
73     (void) outTime;
74 
75     /* This may get called with NULL inputBuffer during initial setup. */
76     if( inputBuffer != NULL )
77     {
78         RingBuffer_Write( &data->inFIFO, inputBuffer, numBytes );
79     }
80     if( outputBuffer != NULL )
81     {
82         int i;
83         int numRead = RingBuffer_Read( &data->outFIFO, outputBuffer, numBytes );
84         /* Zero out remainder of buffer if we run out of data. */
85         for( i=numRead; i<numBytes; i++ )
86         {
87             ((char *)outputBuffer)[i] = 0;
88         }
89     }
90 
91     return 0;
92 }
93 
94 /* Allocate buffer. */
PABLIO_InitFIFO(RingBuffer * rbuf,long numFrames,long bytesPerFrame)95 static PaError PABLIO_InitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame )
96 {
97     long numBytes = numFrames * bytesPerFrame;
98     char *buffer = (char *) malloc( numBytes );
99     if( buffer == NULL ) return paInsufficientMemory;
100     memset( buffer, 0, numBytes );
101     return (PaError) RingBuffer_Init( rbuf, numBytes, buffer );
102 }
103 
104 /* Free buffer. */
PABLIO_TermFIFO(RingBuffer * rbuf)105 static PaError PABLIO_TermFIFO( RingBuffer *rbuf )
106 {
107     if( rbuf->buffer ) free( rbuf->buffer );
108     rbuf->buffer = NULL;
109     return paNoError;
110 }
111 
112 /************************************************************
113  * Write data to ring buffer.
114  * Will not return until all the data has been written.
115  */
WriteAudioStream(PABLIO_Stream * aStream,void * data,long numFrames)116 long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames )
117 {
118     long bytesWritten;
119     char *p = (char *) data;
120     long numBytes = aStream->bytesPerFrame * numFrames;
121     while( numBytes > 0)
122     {
123         bytesWritten = RingBuffer_Write( &aStream->outFIFO, p, numBytes );
124         numBytes -= bytesWritten;
125         p += bytesWritten;
126         if( numBytes > 0) Pa_Sleep(10);
127     }
128     return numFrames;
129 }
130 
131 /************************************************************
132  * Read data from ring buffer.
133  * Will not return until all the data has been read.
134  */
ReadAudioStream(PABLIO_Stream * aStream,void * data,long numFrames)135 long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames )
136 {
137     long bytesRead;
138     char *p = (char *) data;
139     long numBytes = aStream->bytesPerFrame * numFrames;
140     while( numBytes > 0)
141     {
142         bytesRead = RingBuffer_Read( &aStream->inFIFO, p, numBytes );
143         numBytes -= bytesRead;
144         p += bytesRead;
145         if( numBytes > 0) Pa_Sleep(10);
146     }
147     return numFrames;
148 }
149 
150 /************************************************************
151  * Return the number of frames that could be written to the stream without
152  * having to wait.
153  */
GetAudioStreamWriteable(PABLIO_Stream * aStream)154 long GetAudioStreamWriteable( PABLIO_Stream *aStream )
155 {
156     int bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO );
157     return bytesEmpty / aStream->bytesPerFrame;
158 }
159 
160 /************************************************************
161  * Return the number of frames that are available to be read from the
162  * stream without having to wait.
163  */
GetAudioStreamReadable(PABLIO_Stream * aStream)164 long GetAudioStreamReadable( PABLIO_Stream *aStream )
165 {
166     int bytesFull = RingBuffer_GetReadAvailable( &aStream->inFIFO );
167     return bytesFull / aStream->bytesPerFrame;
168 }
169 
170 /************************************************************/
RoundUpToNextPowerOf2(unsigned long n)171 static unsigned long RoundUpToNextPowerOf2( unsigned long n )
172 {
173     long numBits = 0;
174     if( ((n-1) & n) == 0) return n; /* Already Power of two. */
175     while( n > 0 )
176     {
177         n= n>>1;
178         numBits++;
179     }
180     return (1<<numBits);
181 }
182 
183 /************************************************************
184  * Opens a PortAudio stream with default characteristics.
185  * Allocates PABLIO_Stream structure.
186  *
187  * flags parameter can be an ORed combination of:
188  *    PABLIO_READ, PABLIO_WRITE, or PABLIO_READ_WRITE,
189  *    and either PABLIO_MONO or PABLIO_STEREO
190  */
OpenAudioStream(PABLIO_Stream ** rwblPtr,double sampleRate,PaSampleFormat format,long flags)191 PaError OpenAudioStream( PABLIO_Stream **rwblPtr, double sampleRate,
192                          PaSampleFormat format, long flags )
193 {
194     long   bytesPerSample;
195     long   doRead = 0;
196     long   doWrite = 0;
197     PaError err;
198     PABLIO_Stream *aStream;
199     long   minNumBuffers;
200     long   numFrames;
201 
202     /* Allocate PABLIO_Stream structure for caller. */
203     aStream = (PABLIO_Stream *) malloc( sizeof(PABLIO_Stream) );
204     if( aStream == NULL ) return paInsufficientMemory;
205     memset( aStream, 0, sizeof(PABLIO_Stream) );
206 
207     /* Determine size of a sample. */
208     bytesPerSample = Pa_GetSampleSize( format );
209     if( bytesPerSample < 0 )
210     {
211         err = (PaError) bytesPerSample;
212         goto error;
213     }
214     aStream->samplesPerFrame = ((flags&PABLIO_MONO) != 0) ? 1 : 2;
215     aStream->bytesPerFrame = bytesPerSample * aStream->samplesPerFrame;
216 
217     /* Initialize PortAudio  */
218     err = Pa_Initialize();
219     if( err != paNoError ) goto error;
220 
221     /* Warning: numFrames must be larger than amount of data processed per interrupt
222      *    inside PA to prevent glitches. Just to be safe, adjust size upwards.
223      */
224     minNumBuffers = 2 * Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate );
225     numFrames = minNumBuffers * FRAMES_PER_BUFFER;
226     numFrames = RoundUpToNextPowerOf2( numFrames );
227 
228     /* Initialize Ring Buffers */
229     doRead = ((flags & PABLIO_READ) != 0);
230     doWrite = ((flags & PABLIO_WRITE) != 0);
231     if(doRead)
232     {
233         err = PABLIO_InitFIFO( &aStream->inFIFO, numFrames, aStream->bytesPerFrame );
234         if( err != paNoError ) goto error;
235     }
236     if(doWrite)
237     {
238         long numBytes;
239         err = PABLIO_InitFIFO( &aStream->outFIFO, numFrames, aStream->bytesPerFrame );
240         if( err != paNoError ) goto error;
241         /* Make Write FIFO appear full initially. */
242         numBytes = RingBuffer_GetWriteAvailable( &aStream->outFIFO );
243         RingBuffer_AdvanceWriteIndex( &aStream->outFIFO, numBytes );
244     }
245 
246     /* Open a PortAudio stream that we will use to communicate with the underlying
247      * audio drivers. */
248     err = Pa_OpenStream(
249               &aStream->stream,
250               (doRead ? Pa_GetDefaultInputDeviceID() : paNoDevice),
251               (doRead ? aStream->samplesPerFrame : 0 ),
252               format,
253               NULL,
254               (doWrite ? Pa_GetDefaultOutputDeviceID() : paNoDevice),
255               (doWrite ? aStream->samplesPerFrame : 0 ),
256               format,
257               NULL,
258               sampleRate,
259               FRAMES_PER_BUFFER,
260               minNumBuffers,
261               paClipOff,       /* we won't output out of range samples so don't bother clipping them */
262               blockingIOCallback,
263               aStream );
264     if( err != paNoError ) goto error;
265 
266     err = Pa_StartStream( aStream->stream );
267     if( err != paNoError ) goto error;
268 
269     *rwblPtr = aStream;
270     return paNoError;
271 
272 error:
273     CloseAudioStream( aStream );
274     *rwblPtr = NULL;
275     return err;
276 }
277 
278 /************************************************************/
CloseAudioStream(PABLIO_Stream * aStream)279 PaError CloseAudioStream( PABLIO_Stream *aStream )
280 {
281     PaError err;
282     int bytesEmpty;
283     int byteSize = aStream->outFIFO.bufferSize;
284 
285     /* If we are writing data, make sure we play everything written. */
286     if( byteSize > 0 )
287     {
288         bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO );
289         while( bytesEmpty < byteSize )
290         {
291             Pa_Sleep( 10 );
292             bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO );
293         }
294     }
295 
296     err = Pa_StopStream( aStream->stream );
297     if( err != paNoError ) goto error;
298     err = Pa_CloseStream( aStream->stream );
299     if( err != paNoError ) goto error;
300     Pa_Terminate();
301 
302 error:
303     PABLIO_TermFIFO( &aStream->inFIFO );
304     PABLIO_TermFIFO( &aStream->outFIFO );
305     free( aStream );
306     return err;
307 }
308