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 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 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; 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. 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 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 ) 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 ); 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 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; 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