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