pa_mac_core_blocking.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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. * The text above constitutes the entire PortAudio license; however,
  44. * the PortAudio community also makes the following non-binding requests:
  45. *
  46. * Any person wishing to distribute modifications to the Software is
  47. * requested to send the modifications to the original developer so that
  48. * they can be incorporated into the canonical version. It is also
  49. * requested that these non-binding requests be included along with the
  50. * license above.
  51. */
  52. /**
  53. @file
  54. @ingroup hostapi_src
  55. This file contains the implementation
  56. required for blocking I/O. It is separated from pa_mac_core.c simply to ease
  57. development.
  58. */
  59. #include "pa_mac_core_blocking.h"
  60. #include "pa_mac_core_internal.h"
  61. #include <assert.h>
  62. #ifdef MOSX_USE_NON_ATOMIC_FLAG_BITS
  63. # define OSAtomicOr32( a, b ) ( (*(b)) |= (a) )
  64. # define OSAtomicAnd32( a, b ) ( (*(b)) &= (a) )
  65. #else
  66. # include <libkern/OSAtomic.h>
  67. #endif
  68. /*
  69. * This function determines the size of a particular sample format.
  70. * if the format is not recognized, this returns zero.
  71. */
  72. static size_t computeSampleSizeFromFormat( PaSampleFormat format )
  73. {
  74. switch( format & (~paNonInterleaved) ) {
  75. case paFloat32: return 4;
  76. case paInt32: return 4;
  77. case paInt24: return 3;
  78. case paInt16: return 2;
  79. case paInt8: case paUInt8: return 1;
  80. default: return 0;
  81. }
  82. }
  83. /*
  84. * Same as computeSampleSizeFromFormat, except that if
  85. * the size is not a power of two, it returns the next power of two up
  86. */
  87. static size_t computeSampleSizeFromFormatPow2( PaSampleFormat format )
  88. {
  89. switch( format & (~paNonInterleaved) ) {
  90. case paFloat32: return 4;
  91. case paInt32: return 4;
  92. case paInt24: return 4;
  93. case paInt16: return 2;
  94. case paInt8: case paUInt8: return 1;
  95. default: return 0;
  96. }
  97. }
  98. /*
  99. * Functions for initializing, resetting, and destroying BLIO structures.
  100. *
  101. */
  102. /* This should be called with the relevant info when initializing a stream for
  103. callback. */
  104. PaError initializeBlioRingBuffers(
  105. PaMacBlio *blio,
  106. PaSampleFormat inputSampleFormat,
  107. PaSampleFormat outputSampleFormat,
  108. size_t framesPerBuffer,
  109. long ringBufferSize,
  110. int inChan,
  111. int outChan )
  112. {
  113. void *data;
  114. int result;
  115. OSStatus err;
  116. /* zeroify things */
  117. bzero( blio, sizeof( PaMacBlio ) );
  118. /* this is redundant, but the buffers are used to check
  119. if the bufffers have been initialized, so we do it explicitly. */
  120. blio->inputRingBuffer.buffer = NULL;
  121. blio->outputRingBuffer.buffer = NULL;
  122. /* initialize simple data */
  123. blio->ringBufferFrames = ringBufferSize;
  124. blio->inputSampleFormat = inputSampleFormat;
  125. blio->inputSampleSizeActual = computeSampleSizeFromFormat(inputSampleFormat);
  126. blio->inputSampleSizePow2 = computeSampleSizeFromFormatPow2(inputSampleFormat);
  127. blio->outputSampleFormat = outputSampleFormat;
  128. blio->outputSampleSizeActual = computeSampleSizeFromFormat(outputSampleFormat);
  129. blio->outputSampleSizePow2 = computeSampleSizeFromFormatPow2(outputSampleFormat);
  130. blio->framesPerBuffer = framesPerBuffer;
  131. blio->inChan = inChan;
  132. blio->outChan = outChan;
  133. blio->statusFlags = 0;
  134. blio->errors = paNoError;
  135. #ifdef PA_MAC_BLIO_MUTEX
  136. blio->isInputEmpty = false;
  137. blio->isOutputFull = false;
  138. #endif
  139. /* setup ring buffers */
  140. #ifdef PA_MAC_BLIO_MUTEX
  141. result = PaMacCore_SetUnixError( pthread_mutex_init(&(blio->inputMutex),NULL), 0 );
  142. if( result )
  143. goto error;
  144. result = UNIX_ERR( pthread_cond_init( &(blio->inputCond), NULL ) );
  145. if( result )
  146. goto error;
  147. result = UNIX_ERR( pthread_mutex_init(&(blio->outputMutex),NULL) );
  148. if( result )
  149. goto error;
  150. result = UNIX_ERR( pthread_cond_init( &(blio->outputCond), NULL ) );
  151. #endif
  152. if( inChan ) {
  153. data = calloc( ringBufferSize, blio->inputSampleSizePow2*inChan );
  154. if( !data )
  155. {
  156. result = paInsufficientMemory;
  157. goto error;
  158. }
  159. err = PaUtil_InitializeRingBuffer(
  160. &blio->inputRingBuffer,
  161. 1, ringBufferSize*blio->inputSampleSizePow2*inChan,
  162. data );
  163. assert( !err );
  164. }
  165. if( outChan ) {
  166. data = calloc( ringBufferSize, blio->outputSampleSizePow2*outChan );
  167. if( !data )
  168. {
  169. result = paInsufficientMemory;
  170. goto error;
  171. }
  172. err = PaUtil_InitializeRingBuffer(
  173. &blio->outputRingBuffer,
  174. 1, ringBufferSize*blio->outputSampleSizePow2*outChan,
  175. data );
  176. assert( !err );
  177. }
  178. result = resetBlioRingBuffers( blio );
  179. if( result )
  180. goto error;
  181. return 0;
  182. error:
  183. destroyBlioRingBuffers( blio );
  184. return result;
  185. }
  186. #ifdef PA_MAC_BLIO_MUTEX
  187. PaError blioSetIsInputEmpty( PaMacBlio *blio, bool isEmpty )
  188. {
  189. PaError result = paNoError;
  190. if( isEmpty == blio->isInputEmpty )
  191. goto done;
  192. /* we need to update the value. Here's what we do:
  193. * - Lock the mutex, so noone else can write.
  194. * - update the value.
  195. * - unlock.
  196. * - broadcast to all listeners.
  197. */
  198. result = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) );
  199. if( result )
  200. goto done;
  201. blio->isInputEmpty = isEmpty;
  202. result = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) );
  203. if( result )
  204. goto done;
  205. result = UNIX_ERR( pthread_cond_broadcast( &blio->inputCond ) );
  206. if( result )
  207. goto done;
  208. done:
  209. return result;
  210. }
  211. PaError blioSetIsOutputFull( PaMacBlio *blio, bool isFull )
  212. {
  213. PaError result = paNoError;
  214. if( isFull == blio->isOutputFull )
  215. goto done;
  216. /* we need to update the value. Here's what we do:
  217. * - Lock the mutex, so noone else can write.
  218. * - update the value.
  219. * - unlock.
  220. * - broadcast to all listeners.
  221. */
  222. result = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) );
  223. if( result )
  224. goto done;
  225. blio->isOutputFull = isFull;
  226. result = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) );
  227. if( result )
  228. goto done;
  229. result = UNIX_ERR( pthread_cond_broadcast( &blio->outputCond ) );
  230. if( result )
  231. goto done;
  232. done:
  233. return result;
  234. }
  235. #endif
  236. /* This should be called after stopping or aborting the stream, so that on next
  237. start, the buffers will be ready. */
  238. PaError resetBlioRingBuffers( PaMacBlio *blio )
  239. {
  240. #ifdef PA_MAC__BLIO_MUTEX
  241. int result;
  242. #endif
  243. blio->statusFlags = 0;
  244. if( blio->outputRingBuffer.buffer ) {
  245. PaUtil_FlushRingBuffer( &blio->outputRingBuffer );
  246. bzero( blio->outputRingBuffer.buffer,
  247. blio->outputRingBuffer.bufferSize );
  248. /* Advance buffer */
  249. PaUtil_AdvanceRingBufferWriteIndex( &blio->outputRingBuffer, blio->ringBufferFrames*blio->outputSampleSizeActual*blio->outChan );
  250. //PaUtil_AdvanceRingBufferWriteIndex( &blio->outputRingBuffer, blio->outputRingBuffer.bufferSize );
  251. /* Update isOutputFull. */
  252. #ifdef PA_MAC__BLIO_MUTEX
  253. result = blioSetIsOutputFull( blio, toAdvance == blio->outputRingBuffer.bufferSize );
  254. if( result )
  255. goto error;
  256. #endif
  257. /*
  258. printf( "------%d\n" , blio->framesPerBuffer );
  259. printf( "------%d\n" , blio->outChan );
  260. printf( "------%d\n" , blio->outputSampleSize );
  261. printf( "------%d\n" , blio->framesPerBuffer*blio->outChan*blio->outputSampleSize );
  262. */
  263. }
  264. if( blio->inputRingBuffer.buffer ) {
  265. PaUtil_FlushRingBuffer( &blio->inputRingBuffer );
  266. bzero( blio->inputRingBuffer.buffer,
  267. blio->inputRingBuffer.bufferSize );
  268. /* Update isInputEmpty. */
  269. #ifdef PA_MAC__BLIO_MUTEX
  270. result = blioSetIsInputEmpty( blio, true );
  271. if( result )
  272. goto error;
  273. #endif
  274. }
  275. return paNoError;
  276. #ifdef PA_MAC__BLIO_MUTEX
  277. error:
  278. return result;
  279. #endif
  280. }
  281. /*This should be called when you are done with the blio. It can safely be called
  282. multiple times if there are no exceptions. */
  283. PaError destroyBlioRingBuffers( PaMacBlio *blio )
  284. {
  285. PaError result = paNoError;
  286. if( blio->inputRingBuffer.buffer ) {
  287. free( blio->inputRingBuffer.buffer );
  288. #ifdef PA_MAC__BLIO_MUTEX
  289. result = UNIX_ERR( pthread_mutex_destroy( & blio->inputMutex ) );
  290. if( result ) return result;
  291. result = UNIX_ERR( pthread_cond_destroy( & blio->inputCond ) );
  292. if( result ) return result;
  293. #endif
  294. }
  295. blio->inputRingBuffer.buffer = NULL;
  296. if( blio->outputRingBuffer.buffer ) {
  297. free( blio->outputRingBuffer.buffer );
  298. #ifdef PA_MAC__BLIO_MUTEX
  299. result = UNIX_ERR( pthread_mutex_destroy( & blio->outputMutex ) );
  300. if( result ) return result;
  301. result = UNIX_ERR( pthread_cond_destroy( & blio->outputCond ) );
  302. if( result ) return result;
  303. #endif
  304. }
  305. blio->outputRingBuffer.buffer = NULL;
  306. return result;
  307. }
  308. /*
  309. * this is the BlioCallback function. It expects to recieve a PaMacBlio Object
  310. * pointer as userData.
  311. *
  312. */
  313. int BlioCallback( const void *input, void *output, unsigned long frameCount,
  314. const PaStreamCallbackTimeInfo* timeInfo,
  315. PaStreamCallbackFlags statusFlags,
  316. void *userData )
  317. {
  318. PaMacBlio *blio = (PaMacBlio*)userData;
  319. long avail;
  320. long toRead;
  321. long toWrite;
  322. long read;
  323. long written;
  324. /* set flags returned by OS: */
  325. OSAtomicOr32( statusFlags, &blio->statusFlags ) ;
  326. /* --- Handle Input Buffer --- */
  327. if( blio->inChan ) {
  328. avail = PaUtil_GetRingBufferWriteAvailable( &blio->inputRingBuffer );
  329. /* check for underflow */
  330. if( avail < frameCount * blio->inputSampleSizeActual * blio->inChan )
  331. {
  332. OSAtomicOr32( paInputOverflow, &blio->statusFlags );
  333. }
  334. toRead = MIN( avail, frameCount * blio->inputSampleSizeActual * blio->inChan );
  335. /* copy the data */
  336. /*printf( "reading %d\n", toRead );*/
  337. read = PaUtil_WriteRingBuffer( &blio->inputRingBuffer, input, toRead );
  338. assert( toRead == read );
  339. #ifdef PA_MAC__BLIO_MUTEX
  340. /* Priority inversion. See notes below. */
  341. blioSetIsInputEmpty( blio, false );
  342. #endif
  343. }
  344. /* --- Handle Output Buffer --- */
  345. if( blio->outChan ) {
  346. avail = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer );
  347. /* check for underflow */
  348. if( avail < frameCount * blio->outputSampleSizeActual * blio->outChan )
  349. OSAtomicOr32( paOutputUnderflow, &blio->statusFlags );
  350. toWrite = MIN( avail, frameCount * blio->outputSampleSizeActual * blio->outChan );
  351. if( toWrite != frameCount * blio->outputSampleSizeActual * blio->outChan )
  352. bzero( ((char *)output)+toWrite,
  353. frameCount * blio->outputSampleSizeActual * blio->outChan - toWrite );
  354. /* copy the data */
  355. /*printf( "writing %d\n", toWrite );*/
  356. written = PaUtil_ReadRingBuffer( &blio->outputRingBuffer, output, toWrite );
  357. assert( toWrite == written );
  358. #ifdef PA_MAC__BLIO_MUTEX
  359. /* We have a priority inversion here. However, we will only have to
  360. wait if this was true and is now false, which means we've got
  361. some room in the buffer.
  362. Hopefully problems will be minimized. */
  363. blioSetIsOutputFull( blio, false );
  364. #endif
  365. }
  366. return paContinue;
  367. }
  368. PaError ReadStream( PaStream* stream,
  369. void *buffer,
  370. unsigned long frames )
  371. {
  372. PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio;
  373. char *cbuf = (char *) buffer;
  374. PaError ret = paNoError;
  375. VVDBUG(("ReadStream()\n"));
  376. while( frames > 0 ) {
  377. long avail;
  378. long toRead;
  379. do {
  380. avail = PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer );
  381. /*
  382. printf( "Read Buffer is %%%g full: %ld of %ld.\n",
  383. 100 * (float)avail / (float) blio->inputRingBuffer.bufferSize,
  384. avail, blio->inputRingBuffer.bufferSize );
  385. */
  386. if( avail == 0 ) {
  387. #ifdef PA_MAC_BLIO_MUTEX
  388. /**block when empty*/
  389. ret = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) );
  390. if( ret )
  391. return ret;
  392. while( blio->isInputEmpty ) {
  393. ret = UNIX_ERR( pthread_cond_wait( &blio->inputCond, &blio->inputMutex ) );
  394. if( ret )
  395. return ret;
  396. }
  397. ret = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) );
  398. if( ret )
  399. return ret;
  400. #else
  401. Pa_Sleep( PA_MAC_BLIO_BUSY_WAIT_SLEEP_INTERVAL );
  402. #endif
  403. }
  404. } while( avail == 0 );
  405. toRead = MIN( avail, frames * blio->inputSampleSizeActual * blio->inChan );
  406. toRead -= toRead % blio->inputSampleSizeActual * blio->inChan ;
  407. PaUtil_ReadRingBuffer( &blio->inputRingBuffer, (void *)cbuf, toRead );
  408. cbuf += toRead;
  409. frames -= toRead / ( blio->inputSampleSizeActual * blio->inChan );
  410. if( toRead == avail ) {
  411. #ifdef PA_MAC_BLIO_MUTEX
  412. /* we just emptied the buffer, so we need to mark it as empty. */
  413. ret = blioSetIsInputEmpty( blio, true );
  414. if( ret )
  415. return ret;
  416. /* of course, in the meantime, the callback may have put some sats
  417. in, so
  418. so check for that, too, to avoid a race condition. */
  419. if( PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ) ) {
  420. blioSetIsInputEmpty( blio, false );
  421. if( ret )
  422. return ret;
  423. }
  424. #endif
  425. }
  426. }
  427. /* Report either paNoError or paInputOverflowed. */
  428. /* may also want to report other errors, but this is non-standard. */
  429. ret = blio->statusFlags & paInputOverflow;
  430. /* report underflow only once: */
  431. if( ret ) {
  432. OSAtomicAnd32( (uint32_t)(~paInputOverflow), &blio->statusFlags );
  433. ret = paInputOverflowed;
  434. }
  435. return ret;
  436. }
  437. PaError WriteStream( PaStream* stream,
  438. const void *buffer,
  439. unsigned long frames )
  440. {
  441. PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio;
  442. char *cbuf = (char *) buffer;
  443. PaError ret = paNoError;
  444. VVDBUG(("WriteStream()\n"));
  445. while( frames > 0 ) {
  446. long avail = 0;
  447. long toWrite;
  448. do {
  449. avail = PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer );
  450. /*
  451. printf( "Write Buffer is %%%g full: %ld of %ld.\n",
  452. 100 - 100 * (float)avail / (float) blio->outputRingBuffer.bufferSize,
  453. avail, blio->outputRingBuffer.bufferSize );
  454. */
  455. if( avail == 0 ) {
  456. #ifdef PA_MAC_BLIO_MUTEX
  457. /*block while full*/
  458. ret = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) );
  459. if( ret )
  460. return ret;
  461. while( blio->isOutputFull ) {
  462. ret = UNIX_ERR( pthread_cond_wait( &blio->outputCond, &blio->outputMutex ) );
  463. if( ret )
  464. return ret;
  465. }
  466. ret = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) );
  467. if( ret )
  468. return ret;
  469. #else
  470. Pa_Sleep( PA_MAC_BLIO_BUSY_WAIT_SLEEP_INTERVAL );
  471. #endif
  472. }
  473. } while( avail == 0 );
  474. toWrite = MIN( avail, frames * blio->outputSampleSizeActual * blio->outChan );
  475. toWrite -= toWrite % blio->outputSampleSizeActual * blio->outChan ;
  476. PaUtil_WriteRingBuffer( &blio->outputRingBuffer, (void *)cbuf, toWrite );
  477. cbuf += toWrite;
  478. frames -= toWrite / ( blio->outputSampleSizeActual * blio->outChan );
  479. #ifdef PA_MAC_BLIO_MUTEX
  480. if( toWrite == avail ) {
  481. /* we just filled up the buffer, so we need to mark it as filled. */
  482. ret = blioSetIsOutputFull( blio, true );
  483. if( ret )
  484. return ret;
  485. /* of course, in the meantime, we may have emptied the buffer, so
  486. so check for that, too, to avoid a race condition. */
  487. if( PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ) ) {
  488. blioSetIsOutputFull( blio, false );
  489. if( ret )
  490. return ret;
  491. }
  492. }
  493. #endif
  494. }
  495. /* Report either paNoError or paOutputUnderflowed. */
  496. /* may also want to report other errors, but this is non-standard. */
  497. ret = blio->statusFlags & paOutputUnderflow;
  498. /* report underflow only once: */
  499. if( ret ) {
  500. OSAtomicAnd32( (uint32_t)(~paOutputUnderflow), &blio->statusFlags );
  501. ret = paOutputUnderflowed;
  502. }
  503. return ret;
  504. }
  505. /*
  506. *
  507. */
  508. void waitUntilBlioWriteBufferIsFlushed( PaMacBlio *blio )
  509. {
  510. if( blio->outputRingBuffer.buffer ) {
  511. long avail = PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer );
  512. while( avail != blio->outputRingBuffer.bufferSize ) {
  513. if( avail == 0 )
  514. Pa_Sleep( PA_MAC_BLIO_BUSY_WAIT_SLEEP_INTERVAL );
  515. avail = PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer );
  516. }
  517. }
  518. }
  519. signed long GetStreamReadAvailable( PaStream* stream )
  520. {
  521. PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio;
  522. VVDBUG(("GetStreamReadAvailable()\n"));
  523. return PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer )
  524. / ( blio->inputSampleSizeActual * blio->inChan );
  525. }
  526. signed long GetStreamWriteAvailable( PaStream* stream )
  527. {
  528. PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio;
  529. VVDBUG(("GetStreamWriteAvailable()\n"));
  530. return PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer )
  531. / ( blio->outputSampleSizeActual * blio->outChan );
  532. }