2
* Helper and utility functions for pa_mac_core.c (Apple AUHAL implementation)
4
* PortAudio Portable Real-Time Audio Library
5
* Latest Version at: http://www.portaudio.com
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)
10
* Dominic's code was based on code by Phil Burk, Darren Gibbs,
11
* Gord Peters, Stephane Letz, and Greg Pfiel.
13
* The following people also deserve acknowledgements:
15
* Olivier Tristan for feedback and testing
16
* Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O
20
* Based on the Open Source API proposed by Ross Bencina
21
* Copyright (c) 1999-2002 Ross Bencina, Phil Burk
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:
31
* The above copyright notice and this permission notice shall be
32
* included in all copies or substantial portions of the Software.
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.
44
* The text above constitutes the entire PortAudio license; however,
45
* the PortAudio community also makes the following non-binding requests:
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
59
#include "pa_mac_core_utilities.h"
60
#include "pa_mac_core_internal.h"
61
#include <libkern/OSAtomic.h>
66
PaError PaMacCore_SetUnixError( int err, int line )
69
const char *errorText;
77
errorText = strerror( err );
79
/** Map Unix error to PaError. Pretty much the only one that maps
82
ret = paInsufficientMemory;
84
ret = paInternalError;
86
DBUG(("%d on line %d: msg='%s'\n", err, line, errorText));
87
PaUtil_SetLastHostErrorInfo( paCoreAudio, err, errorText );
93
* Translates MacOS generated errors into PaErrors
95
PaError PaMacCore_SetError(OSStatus error, int line, int isError)
97
/*FIXME: still need to handle possible ComponentResult values.*/
98
/* unfortunately, they don't seem to be documented anywhere.*/
100
const char *errorType;
101
const char *errorText;
104
case kAudioHardwareNoError:
106
case kAudioHardwareNotRunningError:
107
errorText = "Audio Hardware Not Running";
108
result = paInternalError; break;
109
case kAudioHardwareUnspecifiedError:
110
errorText = "Unspecified Audio Hardware Error";
111
result = paInternalError; break;
112
case kAudioHardwareUnknownPropertyError:
113
errorText = "Audio Hardware: Unknown Property";
114
result = paInternalError; break;
115
case kAudioHardwareBadPropertySizeError:
116
errorText = "Audio Hardware: Bad Property Size";
117
result = paInternalError; break;
118
case kAudioHardwareIllegalOperationError:
119
errorText = "Audio Hardware: Illegal Operation";
120
result = paInternalError; break;
121
case kAudioHardwareBadDeviceError:
122
errorText = "Audio Hardware: Bad Device";
123
result = paInvalidDevice; break;
124
case kAudioHardwareBadStreamError:
125
errorText = "Audio Hardware: BadStream";
126
result = paBadStreamPtr; break;
127
case kAudioHardwareUnsupportedOperationError:
128
errorText = "Audio Hardware: Unsupported Operation";
129
result = paInternalError; break;
130
case kAudioDeviceUnsupportedFormatError:
131
errorText = "Audio Device: Unsupported Format";
132
result = paSampleFormatNotSupported; break;
133
case kAudioDevicePermissionsError:
134
errorText = "Audio Device: Permissions Error";
135
result = paDeviceUnavailable; break;
136
/* Audio Unit Errors: http://developer.apple.com/documentation/MusicAudio/Reference/CoreAudio/audio_units/chapter_5_section_3.html */
137
case kAudioUnitErr_InvalidProperty:
138
errorText = "Audio Unit: Invalid Property";
139
result = paInternalError; break;
140
case kAudioUnitErr_InvalidParameter:
141
errorText = "Audio Unit: Invalid Parameter";
142
result = paInternalError; break;
143
case kAudioUnitErr_NoConnection:
144
errorText = "Audio Unit: No Connection";
145
result = paInternalError; break;
146
case kAudioUnitErr_FailedInitialization:
147
errorText = "Audio Unit: Initialization Failed";
148
result = paInternalError; break;
149
case kAudioUnitErr_TooManyFramesToProcess:
150
errorText = "Audio Unit: Too Many Frames";
151
result = paInternalError; break;
152
case kAudioUnitErr_IllegalInstrument:
153
errorText = "Audio Unit: Illegal Instrument";
154
result = paInternalError; break;
155
case kAudioUnitErr_InstrumentTypeNotFound:
156
errorText = "Audio Unit: Instrument Type Not Found";
157
result = paInternalError; break;
158
case kAudioUnitErr_InvalidFile:
159
errorText = "Audio Unit: Invalid File";
160
result = paInternalError; break;
161
case kAudioUnitErr_UnknownFileType:
162
errorText = "Audio Unit: Unknown File Type";
163
result = paInternalError; break;
164
case kAudioUnitErr_FileNotSpecified:
165
errorText = "Audio Unit: File Not Specified";
166
result = paInternalError; break;
167
case kAudioUnitErr_FormatNotSupported:
168
errorText = "Audio Unit: Format Not Supported";
169
result = paInternalError; break;
170
case kAudioUnitErr_Uninitialized:
171
errorText = "Audio Unit: Unitialized";
172
result = paInternalError; break;
173
case kAudioUnitErr_InvalidScope:
174
errorText = "Audio Unit: Invalid Scope";
175
result = paInternalError; break;
176
case kAudioUnitErr_PropertyNotWritable:
177
errorText = "Audio Unit: PropertyNotWritable";
178
result = paInternalError; break;
179
case kAudioUnitErr_InvalidPropertyValue:
180
errorText = "Audio Unit: Invalid Property Value";
181
result = paInternalError; break;
182
case kAudioUnitErr_PropertyNotInUse:
183
errorText = "Audio Unit: Property Not In Use";
184
result = paInternalError; break;
185
case kAudioUnitErr_Initialized:
186
errorText = "Audio Unit: Initialized";
187
result = paInternalError; break;
188
case kAudioUnitErr_InvalidOfflineRender:
189
errorText = "Audio Unit: Invalid Offline Render";
190
result = paInternalError; break;
191
case kAudioUnitErr_Unauthorized:
192
errorText = "Audio Unit: Unauthorized";
193
result = paInternalError; break;
194
case kAudioUnitErr_CannotDoInCurrentContext:
195
errorText = "Audio Unit: cannot do in current context";
196
result = paInternalError; break;
198
errorText = "Unknown Error";
199
result = paInternalError;
205
errorType = "Warning";
208
// see if it appears to be a 4-char-code
209
*(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error);
210
if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4]))
212
str[0] = str[5] = '\'';
215
// no, format it as an integer
216
sprintf(str, "%d", (int)error);
219
DBUG(("%s on line %d: err='%s', msg=%s\n", errorType, line, str, errorText));
221
PaUtil_SetLastHostErrorInfo( paCoreAudio, error, errorText );
227
* This function computes an appropriate ring buffer size given
228
* a requested latency (in seconds), sample rate and framesPerBuffer.
230
* The returned ringBufferSize is computed using the following
232
* - it must be at least 4.
233
* - it must be at least 3x framesPerBuffer.
234
* - it must be at least 2x the suggestedLatency.
235
* - it must be a power of 2.
236
* This function attempts to compute the minimum such size.
238
* FEEDBACK: too liberal/conservative/another way?
240
long computeRingBufferSize( const PaStreamParameters *inputParameters,
241
const PaStreamParameters *outputParameters,
242
long inputFramesPerBuffer,
243
long outputFramesPerBuffer,
249
double latencyTimesChannelCount ;
250
long framesPerBufferTimesChannelCount ;
252
VVDBUG(( "computeRingBufferSize()\n" ));
254
assert( inputParameters || outputParameters );
256
if( outputParameters && inputParameters )
258
latencyTimesChannelCount = MAX(
259
inputParameters->suggestedLatency * inputParameters->channelCount,
260
outputParameters->suggestedLatency * outputParameters->channelCount );
261
framesPerBufferTimesChannelCount = MAX(
262
inputFramesPerBuffer * inputParameters->channelCount,
263
outputFramesPerBuffer * outputParameters->channelCount );
265
else if( outputParameters )
267
latencyTimesChannelCount
268
= outputParameters->suggestedLatency * outputParameters->channelCount;
269
framesPerBufferTimesChannelCount
270
= outputFramesPerBuffer * outputParameters->channelCount;
272
else /* we have inputParameters */
274
latencyTimesChannelCount
275
= inputParameters->suggestedLatency * inputParameters->channelCount;
276
framesPerBufferTimesChannelCount
277
= inputFramesPerBuffer * inputParameters->channelCount;
280
ringSize = (long) ( latencyTimesChannelCount * sampleRate * 2 + .5);
281
VDBUG( ( "suggested latency * channelCount: %d\n", (int) (latencyTimesChannelCount*sampleRate) ) );
282
if( ringSize < framesPerBufferTimesChannelCount * 3 )
283
ringSize = framesPerBufferTimesChannelCount * 3 ;
284
VDBUG(("framesPerBuffer*channelCount:%d\n",(int)framesPerBufferTimesChannelCount));
285
VDBUG(("Ringbuffer size (1): %d\n", (int)ringSize ));
287
/* make sure it's at least 4 */
288
ringSize = MAX( ringSize, 4 );
290
/* round up to the next power of 2 */
292
for( i=0; i<sizeof(long)*8; ++i )
293
if( ringSize >> i & 0x01 )
296
if( ringSize <= ( 0x01 << index ) )
297
ringSize = 0x01 << index ;
299
ringSize = 0x01 << ( index + 1 );
301
VDBUG(( "Final Ringbuffer size (2): %d\n", (int)ringSize ));
307
* Durring testing of core audio, I found that serious crashes could occur
308
* if properties such as sample rate were changed multiple times in rapid
309
* succession. The function below could be used to with a condition variable.
310
* to prevent propertychanges from happening until the last property
311
* change is acknowledged. Instead, I implemented a busy-wait, which is simpler
312
* to implement b/c in second round of testing (nov '09) property changes occured
313
* quickly and so there was no real way to test the condition variable implementation.
314
* therefore, this function is not used, but it is aluded to in commented code below,
315
* since it represents a theoretically better implementation.
318
OSStatus propertyProc(
319
AudioDeviceID inDevice,
322
AudioDevicePropertyID inPropertyID,
325
// this is where we would set the condition variable
329
/* sets the value of the given property and waits for the change to
330
be acknowledged, and returns the final value, which is not guaranteed
331
by this function to be the same as the desired value. Obviously, this
332
function can only be used for data whose input and output are the
333
same size and format, and their size and format are known in advance.
334
whether or not the call succeeds, if the data is successfully read,
335
it is returned in outPropertyData. If it is not read successfully,
336
outPropertyData is zeroed, which may or may not be useful in
337
determining if the property was read. */
338
PaError AudioDeviceSetPropertyNowAndWaitForChange(
339
AudioDeviceID inDevice,
342
AudioDevicePropertyID inPropertyID,
343
UInt32 inPropertyDataSize,
344
const void *inPropertyData,
345
void *outPropertyData )
348
UInt32 outPropertyDataSize = inPropertyDataSize;
350
/* First, see if it already has that value. If so, return. */
351
macErr = AudioDeviceGetProperty( inDevice, inChannel,
352
isInput, inPropertyID,
353
&outPropertyDataSize, outPropertyData );
355
memset( outPropertyData, 0, inPropertyDataSize );
358
if( inPropertyDataSize!=outPropertyDataSize )
359
return paInternalError;
360
if( 0==memcmp( outPropertyData, inPropertyData, outPropertyDataSize ) )
363
/* Ideally, we'd use a condition variable to determine changes.
364
we could set that up here. */
366
/* If we were using a cond variable, we'd do something useful here,
367
but for now, this is just to make 10.6 happy. */
368
macErr = AudioDeviceAddPropertyListener( inDevice, inChannel, isInput,
369
inPropertyID, propertyProc,
372
/* we couldn't add a listener. */
376
macErr = AudioDeviceSetProperty( inDevice, NULL, inChannel,
377
isInput, inPropertyID,
378
inPropertyDataSize, inPropertyData );
382
/* busy-wait up to 30 seconds for the property to change */
383
/* busy-wait is justified here only because the correct alternative (condition variable)
384
was hard to test, since most of the waiting ended up being for setting rather than
385
getting in OS X 10.5. This was not the case in earlier OS versions. */
386
struct timeval tv1, tv2;
387
gettimeofday( &tv1, NULL );
388
memcpy( &tv2, &tv1, sizeof( struct timeval ) );
389
while( tv2.tv_sec - tv1.tv_sec < 30 ) {
390
/* now read the property back out */
391
macErr = AudioDeviceGetProperty( inDevice, inChannel,
392
isInput, inPropertyID,
393
&outPropertyDataSize, outPropertyData );
395
memset( outPropertyData, 0, inPropertyDataSize );
399
if( 0==memcmp( outPropertyData, inPropertyData, outPropertyDataSize ) ) {
400
AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, inPropertyID, propertyProc );
403
/* No match yet, so let's sleep and try again. */
405
gettimeofday( &tv2, NULL );
407
DBUG( ("Timeout waiting for device setting.\n" ) );
409
AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, inPropertyID, propertyProc );
413
AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, inPropertyID, propertyProc );
414
return ERR( macErr );
418
* Sets the sample rate the HAL device.
419
* if requireExact: set the sample rate or fail.
421
* otherwise : set the exact sample rate.
422
* If that fails, check for available sample rates, and choose one
423
* higher than the requested rate. If there isn't a higher one,
424
* just use the highest available.
426
PaError setBestSampleRateForDevice( const AudioDeviceID device,
428
const bool requireExact,
429
const Float64 desiredSrate )
431
const bool isInput = isOutput ? 0 : 1;
433
UInt32 propsize = sizeof( Float64 );
435
AudioValueRange *ranges;
437
Float64 max = -1; /*the maximum rate available*/
438
Float64 best = -1; /*the lowest sample rate still greater than desired rate*/
439
VDBUG(("Setting sample rate for device %ld to %g.\n",device,(float)desiredSrate));
441
/* -- try setting the sample rate -- */
443
err = AudioDeviceSetPropertyNowAndWaitForChange(
445
kAudioDevicePropertyNominalSampleRate,
446
propsize, &desiredSrate, &srate );
448
/* -- if the rate agrees, and was changed, we are done -- */
449
if( srate != 0 && srate == desiredSrate )
451
/* -- if the rate agrees, and we got no errors, we are done -- */
452
if( !err && srate == desiredSrate )
454
/* -- we've failed if the rates disagree and we are setting input -- */
456
return paInvalidSampleRate;
458
/* -- generate a list of available sample rates -- */
459
err = AudioDeviceGetPropertyInfo( device, 0, isInput,
460
kAudioDevicePropertyAvailableNominalSampleRates,
464
ranges = (AudioValueRange *)calloc( 1, propsize );
466
return paInsufficientMemory;
467
err = AudioDeviceGetProperty( device, 0, isInput,
468
kAudioDevicePropertyAvailableNominalSampleRates,
475
VDBUG(("Requested sample rate of %g was not available.\n", (float)desiredSrate));
476
VDBUG(("%lu Available Sample Rates are:\n",propsize/sizeof(AudioValueRange)));
477
#ifdef MAC_CORE_VERBOSE_DEBUG
478
for( i=0; i<propsize/sizeof(AudioValueRange); ++i )
480
(float) ranges[i].mMinimum,
481
(float) ranges[i].mMaximum ) );
485
/* -- now pick the best available sample rate -- */
486
for( i=0; i<propsize/sizeof(AudioValueRange); ++i )
488
if( ranges[i].mMaximum > max ) max = ranges[i].mMaximum;
489
if( ranges[i].mMinimum > desiredSrate ) {
491
best = ranges[i].mMinimum;
492
else if( ranges[i].mMinimum < best )
493
best = ranges[i].mMinimum;
498
VDBUG( ("Maximum Rate %g. best is %g.\n", max, best ) );
501
/* -- set the sample rate -- */
502
propsize = sizeof( best );
504
err = AudioDeviceSetPropertyNowAndWaitForChange(
506
kAudioDevicePropertyNominalSampleRate,
507
propsize, &best, &srate );
509
/* -- if the set rate matches, we are done -- */
510
if( srate != 0 && srate == best )
516
/* -- otherwise, something wierd happened: we didn't set the rate, and we got no errors. Just bail. */
517
return paInternalError;
522
Attempts to set the requestedFramesPerBuffer. If it can't set the exact
523
value, it settles for something smaller if available. If nothing smaller
524
is available, it uses the smallest available size.
525
actualFramesPerBuffer will be set to the actual value on successful return.
526
OK to pass NULL to actualFramesPerBuffer.
527
The logic is very simmilar too setBestSampleRate only failure here is
528
not usually catastrophic.
530
PaError setBestFramesPerBuffer( const AudioDeviceID device,
532
UInt32 requestedFramesPerBuffer,
533
UInt32 *actualFramesPerBuffer )
536
const bool isInput = !isOutput;
537
UInt32 propsize = sizeof(UInt32);
539
Float64 min = -1; /*the min blocksize*/
540
Float64 best = -1; /*the best blocksize*/
542
AudioValueRange *ranges;
544
if( actualFramesPerBuffer == NULL )
545
actualFramesPerBuffer = &afpb;
548
/* -- try and set exact FPB -- */
549
err = AudioDeviceSetProperty( device, NULL, 0, isInput,
550
kAudioDevicePropertyBufferFrameSize,
551
propsize, &requestedFramesPerBuffer);
552
err = AudioDeviceGetProperty( device, 0, isInput,
553
kAudioDevicePropertyBufferFrameSize,
554
&propsize, actualFramesPerBuffer);
557
if( *actualFramesPerBuffer == requestedFramesPerBuffer )
558
return paNoError; /* we are done */
560
/* -- fetch available block sizes -- */
561
err = AudioDeviceGetPropertyInfo( device, 0, isInput,
562
kAudioDevicePropertyBufferSizeRange,
566
ranges = (AudioValueRange *)calloc( 1, propsize );
568
return paInsufficientMemory;
569
err = AudioDeviceGetProperty( device, 0, isInput,
570
kAudioDevicePropertyBufferSizeRange,
577
VDBUG(("Requested block size of %lu was not available.\n",
578
requestedFramesPerBuffer ));
579
VDBUG(("%lu Available block sizes are:\n",propsize/sizeof(AudioValueRange)));
580
#ifdef MAC_CORE_VERBOSE_DEBUG
581
for( i=0; i<propsize/sizeof(AudioValueRange); ++i )
583
(float) ranges[i].mMinimum,
584
(float) ranges[i].mMaximum ) );
588
/* --- now pick the best available framesPerBuffer -- */
589
for( i=0; i<propsize/sizeof(AudioValueRange); ++i )
591
if( min == -1 || ranges[i].mMinimum < min ) min = ranges[i].mMinimum;
592
if( ranges[i].mMaximum < requestedFramesPerBuffer ) {
594
best = ranges[i].mMaximum;
595
else if( ranges[i].mMaximum > best )
596
best = ranges[i].mMaximum;
601
VDBUG( ("Minimum FPB %g. best is %g.\n", min, best ) );
604
/* --- set the buffer size (ignore errors) -- */
605
requestedFramesPerBuffer = (UInt32) best ;
606
propsize = sizeof( UInt32 );
607
err = AudioDeviceSetProperty( device, NULL, 0, isInput,
608
kAudioDevicePropertyBufferSize,
609
propsize, &requestedFramesPerBuffer );
610
/* --- read the property to check that it was set -- */
611
err = AudioDeviceGetProperty( device, 0, isInput,
612
kAudioDevicePropertyBufferSize,
613
&propsize, actualFramesPerBuffer );
621
/**********************
625
**********************/
627
struct PaMacXRunListNode_s {
628
PaMacCoreStream *stream;
629
struct PaMacXRunListNode_s *next;
632
typedef struct PaMacXRunListNode_s PaMacXRunListNode;
634
/** Always empty, so that it can always be the one returned by
635
addToXRunListenerList. note that it's not a pointer. */
636
static PaMacXRunListNode firstXRunListNode;
637
static int xRunListSize;
638
static pthread_mutex_t xrunMutex;
640
OSStatus xrunCallback(
641
AudioDeviceID inDevice,
644
AudioDevicePropertyID inPropertyID,
647
PaMacXRunListNode *node = (PaMacXRunListNode *) inClientData;
649
int ret = pthread_mutex_trylock( &xrunMutex ) ;
653
node = node->next ; //skip the first node
655
for( ; node; node=node->next ) {
656
PaMacCoreStream *stream = node->stream;
658
if( stream->state != ACTIVE )
659
continue; //if the stream isn't active, we don't care if the device is dropping
662
if( stream->inputDevice == inDevice )
663
OSAtomicOr32( paInputOverflow, (uint32_t *)&(stream->xrunFlags) );
665
if( stream->outputDevice == inDevice )
666
OSAtomicOr32( paOutputUnderflow, (uint32_t *)&(stream->xrunFlags) );
670
pthread_mutex_unlock( &xrunMutex );
676
int initializeXRunListenerList()
679
bzero( (void *) &firstXRunListNode, sizeof(firstXRunListNode) );
680
return pthread_mutex_init( &xrunMutex, NULL );
682
int destroyXRunListenerList()
684
PaMacXRunListNode *node;
685
node = firstXRunListNode.next;
687
PaMacXRunListNode *tmp = node;
692
return pthread_mutex_destroy( &xrunMutex );
695
void *addToXRunListenerList( void *stream )
697
pthread_mutex_lock( &xrunMutex );
698
PaMacXRunListNode *newNode;
700
newNode = (PaMacXRunListNode *) malloc( sizeof( PaMacXRunListNode ) );
701
newNode->stream = (PaMacCoreStream *) stream;
702
newNode->next = firstXRunListNode.next;
704
firstXRunListNode.next = newNode;
705
pthread_mutex_unlock( &xrunMutex );
707
return &firstXRunListNode;
710
int removeFromXRunListenerList( void *stream )
712
pthread_mutex_lock( &xrunMutex );
713
PaMacXRunListNode *node, *prev;
714
prev = &firstXRunListNode;
715
node = firstXRunListNode.next;
717
if( node->stream == stream ) {
720
prev->next = node->next;
722
pthread_mutex_unlock( &xrunMutex );
729
pthread_mutex_unlock( &xrunMutex );