1 /*
2  * Implementation of the PortAudio API for Apple AUHAL
3  *
4  * PortAudio Portable Real-Time Audio Library
5  * Latest Version at: http://www.portaudio.com
6  *
7  * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code.
8  * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation)
9  *
10  * Dominic's code was based on code by Phil Burk, Darren Gibbs,
11  * Gord Peters, Stephane Letz, and Greg Pfiel.
12  *
13  * The following people also deserve acknowledgements:
14  *
15  * Olivier Tristan for feedback and testing
16  * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O
17  * interface.
18  *
19  *
20  * Based on the Open Source API proposed by Ross Bencina
21  * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
22  *
23  * Permission is hereby granted, free of charge, to any person obtaining
24  * a copy of this software and associated documentation files
25  * (the "Software"), to deal in the Software without restriction,
26  * including without limitation the rights to use, copy, modify, merge,
27  * publish, distribute, sublicense, and/or sell copies of the Software,
28  * and to permit persons to whom the Software is furnished to do so,
29  * subject to the following conditions:
30  *
31  * The above copyright notice and this permission notice shall be
32  * included in all copies or substantial portions of the Software.
33  *
34  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
35  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
36  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
37  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
38  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
39  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
40  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41  */
42 
43 /*
44  * The text above constitutes the entire PortAudio license; however,
45  * the PortAudio community also makes the following non-binding requests:
46  *
47  * Any person wishing to distribute modifications to the Software is
48  * requested to send the modifications to the original developer so that
49  * they can be incorporated into the canonical version. It is also
50  * requested that these non-binding requests be included along with the
51  * license above.
52  */
53 
54 /**
55  @file
56  @ingroup hostapi_src
57 
58  This file contains the implementation
59  required for blocking I/O. It is separated from pa_mac_core.c simply to ease
60  development.
61 */
62 
63 #include "pa_mac_core_blocking.h"
64 #include "pa_mac_core_internal.h"
65 #include <assert.h>
66 #ifdef MOSX_USE_NON_ATOMIC_FLAG_BITS
67 # define OSAtomicOr32( a, b ) ( (*(b)) |= (a) )
68 # define OSAtomicAnd32( a, b ) ( (*(b)) &= (a) )
69 #else
70 # include <libkern/OSAtomic.h>
71 #endif
72 
73 /*
74  * This function determines the size of a particular sample format.
75  * if the format is not recognized, this returns zero.
76  */
77 static size_t computeSampleSizeFromFormat( PaSampleFormat format )
78 {
79    switch( format & (~paNonInterleaved) ) {
80    case paFloat32: return 4;
81    case paInt32: return 4;
82    case paInt24: return 3;
83    case paInt16: return 2;
84    case paInt8: case paUInt8: return 1;
85    default: return 0;
86    }
87 }
88 /*
89  * Same as computeSampleSizeFromFormat, except that if
PaMacCore_SetupStreamInfo(PaMacCoreStreamInfo * data,const unsigned long flags)90  * the size is not a power of two, it returns the next power of two up
91  */
92 static size_t computeSampleSizeFromFormatPow2( PaSampleFormat format )
93 {
94    switch( format & (~paNonInterleaved) ) {
95    case paFloat32: return 4;
96    case paInt32: return 4;
97    case paInt24: return 4;
98    case paInt16: return 2;
99    case paInt8: case paUInt8: return 1;
100    default: return 0;
101    }
102 }
103 
PaMacCore_SetupChannelMap(PaMacCoreStreamInfo * data,const SInt32 * const channelMap,const unsigned long channelMapSize)104 
105 
106 /*
107  * Functions for initializing, resetting, and destroying BLIO structures.
108  *
109  */
110 
111 /**
112  * This should be called with the relevant info when initializing a stream for callback.
113  *
114  * @param ringBufferSizeInFrames must be a power of 2
115  */
116 PaError initializeBlioRingBuffers(
117                                        PaMacBlio *blio,
118                                        PaSampleFormat inputSampleFormat,
119                                        PaSampleFormat outputSampleFormat,
120                                        long ringBufferSizeInFrames,
121                                        int inChan,
122                                        int outChan )
123 {
124    void *data;
125    int result;
126    OSStatus err;
127 
128    /* zeroify things */
129    bzero( blio, sizeof( PaMacBlio ) );
130    /* this is redundant, but the buffers are used to check
131       if the buffers have been initialized, so we do it explicitly. */
132    blio->inputRingBuffer.buffer = NULL;
133    blio->outputRingBuffer.buffer = NULL;
134 
135    /* initialize simple data */
136    blio->ringBufferFrames = ringBufferSizeInFrames;
137    blio->inputSampleFormat = inputSampleFormat;
138    blio->inputSampleSizeActual = computeSampleSizeFromFormat(inputSampleFormat);
139    blio->inputSampleSizePow2 = computeSampleSizeFromFormatPow2(inputSampleFormat); // FIXME: WHY?
140    blio->outputSampleFormat = outputSampleFormat;
141    blio->outputSampleSizeActual = computeSampleSizeFromFormat(outputSampleFormat);
142    blio->outputSampleSizePow2 = computeSampleSizeFromFormatPow2(outputSampleFormat);
143 
144    blio->inChan = inChan;
145    blio->outChan = outChan;
146    blio->statusFlags = 0;
147    blio->errors = paNoError;
148 #ifdef PA_MAC_BLIO_MUTEX
149    blio->isInputEmpty = false;
150    blio->isOutputFull = false;
151 #endif
152 
153    /* setup ring buffers */
154 #ifdef PA_MAC_BLIO_MUTEX
155    result = PaMacCore_SetUnixError( pthread_mutex_init(&(blio->inputMutex),NULL), 0 );
156    if( result )
157       goto error;
158    result = UNIX_ERR( pthread_cond_init( &(blio->inputCond), NULL ) );
159    if( result )
160       goto error;
161    result = UNIX_ERR( pthread_mutex_init(&(blio->outputMutex),NULL) );
162    if( result )
163       goto error;
164    result = UNIX_ERR( pthread_cond_init( &(blio->outputCond), NULL ) );
165 #endif
166    if( inChan ) {
167       data = calloc( ringBufferSizeInFrames, blio->inputSampleSizePow2 * inChan );
168       if( !data )
169       {
170          result = paInsufficientMemory;
171          goto error;
172       }
173 
174       err = PaUtil_InitializeRingBuffer(
175             &blio->inputRingBuffer,
176             blio->inputSampleSizePow2 * inChan,
177             ringBufferSizeInFrames,
178             data );
179       assert( !err );
180    }
181    if( outChan ) {
182       data = calloc( ringBufferSizeInFrames, blio->outputSampleSizePow2 * outChan );
183       if( !data )
184       {
185          result = paInsufficientMemory;
186          goto error;
187       }
188 
189       err = PaUtil_InitializeRingBuffer(
190             &blio->outputRingBuffer,
191             blio->outputSampleSizePow2 * outChan,
192             ringBufferSizeInFrames,
193             data );
194       assert( !err );
195    }
196 
197    result = resetBlioRingBuffers( blio );
198    if( result )
199       goto error;
200 
201    return 0;
202 
203  error:
204    destroyBlioRingBuffers( blio );
205    return result;
206 }
207 
208 #ifdef PA_MAC_BLIO_MUTEX
209 PaError blioSetIsInputEmpty( PaMacBlio *blio, bool isEmpty )
210 {
211    PaError result = paNoError;
212    if( isEmpty == blio->isInputEmpty )
213       goto done;
214 
215    /* we need to update the value. Here's what we do:
216     * - Lock the mutex, so noone else can write.
217     * - update the value.
218     * - unlock.
219     * - broadcast to all listeners.
220     */
221    result = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) );
222    if( result )
223       goto done;
224    blio->isInputEmpty = isEmpty;
225    result = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) );
226    if( result )
227       goto done;
228    result = UNIX_ERR( pthread_cond_broadcast( &blio->inputCond ) );
229    if( result )
230       goto done;
231 
232  done:
233    return result;
234 }
235 PaError blioSetIsOutputFull( PaMacBlio *blio, bool isFull )
236 {
237    PaError result = paNoError;
PaMacCore_GetStreamInputDevice(PaStream * s)238    if( isFull == blio->isOutputFull )
239       goto done;
240 
241    /* we need to update the value. Here's what we do:
242     * - Lock the mutex, so noone else can write.
243     * - update the value.
244     * - unlock.
245     * - broadcast to all listeners.
PaMacCore_GetStreamOutputDevice(PaStream * s)246     */
247    result = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) );
248    if( result )
249       goto done;
250    blio->isOutputFull = isFull;
251    result = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) );
252    if( result )
253       goto done;
254    result = UNIX_ERR( pthread_cond_broadcast( &blio->outputCond ) );
255    if( result )
256       goto done;
257 
258  done:
259    return result;
260 }
261 #endif
262 
263 /* This should be called after stopping or aborting the stream, so that on next
264    start, the buffers will be ready. */
265 PaError resetBlioRingBuffers( PaMacBlio *blio )
266 {
267 #ifdef PA_MAC__BLIO_MUTEX
268    int result;
269 #endif
270    blio->statusFlags = 0;
271    if( blio->outputRingBuffer.buffer ) {
272        PaUtil_FlushRingBuffer( &blio->outputRingBuffer );
273        /* Fill the buffer with zeros. */
274        bzero( blio->outputRingBuffer.buffer,
275              blio->outputRingBuffer.bufferSize * blio->outputRingBuffer.elementSizeBytes );
276        PaUtil_AdvanceRingBufferWriteIndex( &blio->outputRingBuffer, blio->ringBufferFrames );
277 
278       /* Update isOutputFull. */
279 #ifdef PA_MAC__BLIO_MUTEX
280       result = blioSetIsOutputFull( blio, toAdvance == blio->outputRingBuffer.bufferSize );
281       if( result )
282          goto error;
283 #endif
284 /*
285       printf( "------%d\n" ,  blio->outChan );
286       printf( "------%d\n" ,  blio->outputSampleSize );
287 */
288    }
289    if( blio->inputRingBuffer.buffer ) {
290       PaUtil_FlushRingBuffer( &blio->inputRingBuffer );
291       bzero( blio->inputRingBuffer.buffer,
292              blio->inputRingBuffer.bufferSize * blio->inputRingBuffer.elementSizeBytes );
293       /* Update isInputEmpty. */
294 #ifdef PA_MAC__BLIO_MUTEX
295       result = blioSetIsInputEmpty( blio, true );
296       if( result )
297          goto error;
298 #endif
299    }
300    return paNoError;
301 #ifdef PA_MAC__BLIO_MUTEX
302  error:
303    return result;
304 #endif
305 }
306 
307 /*This should be called when you are done with the blio. It can safely be called
308   multiple times if there are no exceptions. */
309 PaError destroyBlioRingBuffers( PaMacBlio *blio )
310 {
311    PaError result = paNoError;
312    if( blio->inputRingBuffer.buffer ) {
313       free( blio->inputRingBuffer.buffer );
314 #ifdef PA_MAC__BLIO_MUTEX
startStopCallback(void * inRefCon,AudioUnit ci,AudioUnitPropertyID inID,AudioUnitScope inScope,AudioUnitElement inElement)315       result = UNIX_ERR( pthread_mutex_destroy( & blio->inputMutex ) );
316       if( result ) return result;
317       result = UNIX_ERR( pthread_cond_destroy( & blio->inputCond ) );
318       if( result ) return result;
319 #endif
320    }
321    blio->inputRingBuffer.buffer = NULL;
322    if( blio->outputRingBuffer.buffer ) {
323       free( blio->outputRingBuffer.buffer );
324 #ifdef PA_MAC__BLIO_MUTEX
325       result = UNIX_ERR( pthread_mutex_destroy( & blio->outputMutex ) );
326       if( result ) return result;
327       result = UNIX_ERR( pthread_cond_destroy( & blio->outputCond ) );
328       if( result ) return result;
329 #endif
330    }
331    blio->outputRingBuffer.buffer = NULL;
332 
333    return result;
334 }
335 
336 /*
337  * this is the BlioCallback function. It expects to recieve a PaMacBlio Object
338  * pointer as userData.
339  *
340  */
341 int BlioCallback( const void *input, void *output, unsigned long frameCount,
342 	const PaStreamCallbackTimeInfo* timeInfo,
343         PaStreamCallbackFlags statusFlags,
344         void *userData )
gatherDeviceInfo(PaMacAUHAL * auhalHostApi)345 {
346    PaMacBlio *blio = (PaMacBlio*)userData;
347    ring_buffer_size_t framesAvailable;
348    ring_buffer_size_t framesToTransfer;
349    ring_buffer_size_t framesTransferred;
350 
351    /* set flags returned by OS: */
352    OSAtomicOr32( statusFlags, &blio->statusFlags ) ;
353 
354    /* --- Handle Input Buffer --- */
355    if( blio->inChan ) {
356       framesAvailable = PaUtil_GetRingBufferWriteAvailable( &blio->inputRingBuffer );
357 
358       /* check for underflow */
359       if( framesAvailable < frameCount )
360       {
361           OSAtomicOr32( paInputOverflow, &blio->statusFlags );
362           framesToTransfer = framesAvailable;
363       }
364       else
365       {
366           framesToTransfer = (ring_buffer_size_t)frameCount;
367       }
368 
369       /* Copy the data from the audio input to the application ring buffer. */
370       /*printf( "reading %d\n", toRead );*/
371       framesTransferred = PaUtil_WriteRingBuffer( &blio->inputRingBuffer, input, framesToTransfer );
372       assert( framesToTransfer == framesTransferred );
373 #ifdef PA_MAC__BLIO_MUTEX
374       /* Priority inversion. See notes below. */
375       blioSetIsInputEmpty( blio, false );
376 #endif
377    }
378 
379 
380    /* --- Handle Output Buffer --- */
381    if( blio->outChan ) {
382       framesAvailable = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer );
383 
384       /* check for underflow */
385       if( framesAvailable < frameCount )
386       {
387           /* zero out the end of the output buffer that we do not have data for */
388           framesToTransfer = framesAvailable;
389 
390           size_t bytesPerFrame = blio->outputSampleSizeActual * blio->outChan;
391           size_t offsetInBytes = framesToTransfer * bytesPerFrame;
392           size_t countInBytes = (frameCount - framesToTransfer) * bytesPerFrame;
393           bzero( ((char *)output) + offsetInBytes, countInBytes );
394 
395           OSAtomicOr32( paOutputUnderflow, &blio->statusFlags );
396           framesToTransfer = framesAvailable;
397       }
398       else
399       {
400           framesToTransfer = (ring_buffer_size_t)frameCount;
401       }
402 
403       /* copy the data */
404       /*printf( "writing %d\n", toWrite );*/
405       framesTransferred = PaUtil_ReadRingBuffer( &blio->outputRingBuffer, output, framesToTransfer );
406       assert( framesToTransfer == framesTransferred );
407 #ifdef PA_MAC__BLIO_MUTEX
408       /* We have a priority inversion here. However, we will only have to
409          wait if this was true and is now false, which means we've got
410          some room in the buffer.
411          Hopefully problems will be minimized. */
412       blioSetIsOutputFull( blio, false );
413 #endif
414    }
415 
416    return paContinue;
417 }
418 
419 PaError ReadStream( PaStream* stream,
420                            void *buffer,
421                            unsigned long framesRequested )
422 {
423     PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio;
424     char *cbuf = (char *) buffer;
425     PaError ret = paNoError;
426     VVDBUG(("ReadStream()\n"));
427 
428     while( framesRequested > 0 ) {
429        ring_buffer_size_t framesAvailable;
430        ring_buffer_size_t framesToTransfer;
431        ring_buffer_size_t framesTransferred;
432        do {
433           framesAvailable = PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer );
ClipToDeviceBufferSize(AudioDeviceID macCoreDeviceId,int isInput,UInt32 desiredSize,UInt32 * allowedSize)434 /*
435           printf( "Read Buffer is %%%g full: %ld of %ld.\n",
436                   100 * (float)avail / (float) blio->inputRingBuffer.bufferSize,
437                   framesAvailable, blio->inputRingBuffer.bufferSize );
438 */
439           if( framesAvailable == 0 ) {
440 #ifdef PA_MAC_BLIO_MUTEX
441              /**block when empty*/
442              ret = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) );
443              if( ret )
444                 return ret;
445              while( blio->isInputEmpty ) {
446                 ret = UNIX_ERR( pthread_cond_wait( &blio->inputCond, &blio->inputMutex ) );
447                 if( ret )
448                    return ret;
449              }
450              ret = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) );
451              if( ret )
452                 return ret;
453 #else
454              Pa_Sleep( PA_MAC_BLIO_BUSY_WAIT_SLEEP_INTERVAL );
455 #endif
456           }
457        } while( framesAvailable == 0 );
458        framesToTransfer = (ring_buffer_size_t) MIN( framesAvailable, framesRequested );
459        framesTransferred = PaUtil_ReadRingBuffer( &blio->inputRingBuffer, (void *)cbuf, framesToTransfer );
460        cbuf += framesTransferred * blio->inputSampleSizeActual * blio->inChan;
461        framesRequested -= framesTransferred;
462 
463        if( framesToTransfer == framesAvailable ) {
464 #ifdef PA_MAC_BLIO_MUTEX
465           /* we just emptied the buffer, so we need to mark it as empty. */
466           ret = blioSetIsInputEmpty( blio, true );
467           if( ret )
468              return ret;
469           /* of course, in the meantime, the callback may have put some sats
470              in, so
471              so check for that, too, to avoid a race condition. */
472           /* FIXME - this does not seem to fix any race condition. */
473           if( PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ) ) {
474              blioSetIsInputEmpty( blio, false );
475              /* FIXME - why check? ret has not been set? */
476              if( ret )
477                 return ret;
478           }
479 #endif
480        }
481     }
482 
483     /*   Report either paNoError or paInputOverflowed. */
484     /*   may also want to report other errors, but this is non-standard. */
485     /* FIXME should not clobber ret, use if(blio->statusFlags & paInputOverflow) */
486     ret = blio->statusFlags & paInputOverflow;
487 
488     /* report underflow only once: */
489     if( ret ) {
490        OSAtomicAnd32( (uint32_t)(~paInputOverflow), &blio->statusFlags );
491        ret = paInputOverflowed;
492     }
493 
494     return ret;
495 }
496 
497 
498 PaError WriteStream( PaStream* stream,
499                             const void *buffer,
500                             unsigned long framesRequested )
501 {
502     PaMacCoreStream *macStream = (PaMacCoreStream*)stream;
503     PaMacBlio *blio = &macStream->blio;
504     char *cbuf = (char *) buffer;
505     PaError ret = paNoError;
506     VVDBUG(("WriteStream()\n"));
507 
CalculateFixedDeviceLatency(AudioDeviceID macCoreDeviceId,int isInput,UInt32 * fixedLatencyPtr)508     while( framesRequested > 0 && macStream->state != STOPPING ) {
509         ring_buffer_size_t framesAvailable;
510         ring_buffer_size_t framesToTransfer;
511         ring_buffer_size_t framesTransferred;
512 
513        do {
514           framesAvailable = PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer );
515 /*
516           printf( "Write Buffer is %%%g full: %ld of %ld.\n",
517                   100 - 100 * (float)avail / (float) blio->outputRingBuffer.bufferSize,
518                   framesAvailable, blio->outputRingBuffer.bufferSize );
519 */
520           if( framesAvailable == 0 ) {
521 #ifdef PA_MAC_BLIO_MUTEX
522              /*block while full*/
523              ret = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) );
524              if( ret )
525                 return ret;
526              while( blio->isOutputFull ) {
527                 ret = UNIX_ERR( pthread_cond_wait( &blio->outputCond, &blio->outputMutex ) );
528                 if( ret )
529                    return ret;
530              }
531              ret = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) );
532              if( ret )
533                 return ret;
534 #else
535              Pa_Sleep( PA_MAC_BLIO_BUSY_WAIT_SLEEP_INTERVAL );
536 #endif
537           }
538        } while( framesAvailable == 0 && macStream->state != STOPPING );
539 
540        if( macStream->state == STOPPING )
541        {
542            break;
543        }
544 
545        framesToTransfer = MIN( framesAvailable, framesRequested );
546        framesTransferred = PaUtil_WriteRingBuffer( &blio->outputRingBuffer, (void *)cbuf, framesToTransfer );
547        cbuf += framesTransferred * blio->outputSampleSizeActual * blio->outChan;
548        framesRequested -= framesTransferred;
549 
550 #ifdef PA_MAC_BLIO_MUTEX
551        if( framesToTransfer == framesAvailable ) {
552           /* we just filled up the buffer, so we need to mark it as filled. */
553           ret = blioSetIsOutputFull( blio, true );
554           if( ret )
555              return ret;
556           /* of course, in the meantime, we may have emptied the buffer, so
557              so check for that, too, to avoid a race condition. */
558           if( PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ) ) {
559              blioSetIsOutputFull( blio, false );
560               /* FIXME remove or review this code, does not fix race, ret not set! */
561              if( ret )
562                 return ret;
563           }
564        }
565 #endif
566     }
567 
568     if ( macStream->state == STOPPING )
569     {
570         ret = paInternalError;
571     }
572     else if (ret == paNoError )
573     {
574         /*   Test for underflow. */
575         ret = blio->statusFlags & paOutputUnderflow;
GetChannelInfo(PaMacAUHAL * auhalHostApi,PaDeviceInfo * deviceInfo,AudioDeviceID macCoreDeviceId,int isInput)576 
577         /* report underflow only once: */
578         if( ret )
579         {
580             OSAtomicAnd32( (uint32_t)(~paOutputUnderflow), &blio->statusFlags );
581             ret = paOutputUnderflowed;
582         }
583     }
584 
585     return ret;
586 }
587 
588 /*
589  * Wait until the data in the buffer has finished playing.
590  */
591 PaError waitUntilBlioWriteBufferIsEmpty( PaMacBlio *blio, double sampleRate,
592                                         size_t framesPerBuffer )
593 {
594     PaError result = paNoError;
595     if( blio->outputRingBuffer.buffer ) {
596         ring_buffer_size_t framesLeft = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer );
597 
598         /* Calculate when we should give up waiting. To be safe wait for two extra periods. */
599         PaTime now = PaUtil_GetTime();
600         PaTime startTime = now;
601         PaTime timeoutTime = startTime + (framesLeft + (2 * framesPerBuffer)) / sampleRate;
602 
603         long msecPerBuffer = 1 + (long)( 1000.0 * framesPerBuffer / sampleRate);
604         while( framesLeft > 0 && now < timeoutTime ) {
605             VDBUG(( "waitUntilBlioWriteBufferIsFlushed: framesLeft = %d, framesPerBuffer = %ld\n",
606                   framesLeft, framesPerBuffer ));
607             Pa_Sleep( msecPerBuffer );
608             framesLeft = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer );
609             now = PaUtil_GetTime();
610         }
611 
612         if( framesLeft > 0 )
613         {
614             VDBUG(( "waitUntilBlioWriteBufferIsFlushed: TIMED OUT - framesLeft = %d\n", framesLeft ));
615             result = paTimedOut;
616         }
617     }
618     return result;
619 }
620 
621 signed long GetStreamReadAvailable( PaStream* stream )
622 {
623     PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio;
624     VVDBUG(("GetStreamReadAvailable()\n"));
625 
626     return PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer );
627 }
628 
629 
630 signed long GetStreamWriteAvailable( PaStream* stream )
631 {
632     PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio;
633     VVDBUG(("GetStreamWriteAvailable()\n"));
634 
635     return PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer );
636 }
637 
638