From 011312406096edae98aeb079a12932f2b720e10b Mon Sep 17 00:00:00 2001 From: Guenter Geiger Date: Mon, 9 Dec 2002 18:30:29 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r284, which included commits to RCS files with non-trunk default branches. svn path=/trunk/; revision=285 --- pd/portaudio/pa_mac_core/notes.txt | 14 +- pd/portaudio/pa_mac_core/pa_mac_core.c | 1512 ++++++++++++++++++++------------ 2 files changed, 980 insertions(+), 546 deletions(-) (limited to 'pd/portaudio') diff --git a/pd/portaudio/pa_mac_core/notes.txt b/pd/portaudio/pa_mac_core/notes.txt index c79b90e6..3b557d9a 100644 --- a/pd/portaudio/pa_mac_core/notes.txt +++ b/pd/portaudio/pa_mac_core/notes.txt @@ -2,25 +2,25 @@ Notes on Core Audio Implementation of PortAudio by Phil Burk and Darren Gibbs -Document last updated March 20, 2002 +Document last updated October 18, 2002 WHAT WORKS Output with very low latency, <10 msec. Half duplex input or output. -Full duplex on the same CoreAudio device. +Full duplex The paFLoat32, paInt16, paInt8, paUInt8 sample formats. Pa_GetCPULoad() Pa_StreamTime() KNOWN BUGS OR LIMITATIONS -We do not yet support simultaneous input and output on different -devices. Note that some CoreAudio devices like the Roland UH30 look -like one device but are actually two different CoreAudio devices. The -BuiltIn audio is typically one CoreAudio device. +The iMic supports multiple sample rates. +But there is a bug when changing sample rates: + Run patest_record.c at rate A - it works. + Then run patest_record.c at rate B - it FAIL! + Then run patest_record.c again at rate B - it works! -Mono doesn't work. DEVICE MAPPING diff --git a/pd/portaudio/pa_mac_core/pa_mac_core.c b/pd/portaudio/pa_mac_core/pa_mac_core.c index 9a4b1488..de781d09 100644 --- a/pd/portaudio/pa_mac_core/pa_mac_core.c +++ b/pd/portaudio/pa_mac_core/pa_mac_core.c @@ -1,5 +1,5 @@ /* - * $Id: pa_mac_core.c,v 1.8 2002/04/12 18:07:20 philburk Exp $ + * $Id: pa_mac_core.c,v 1.8.4.3 2002/10/18 01:29:06 philburk Exp $ * pa_mac_core.c * Implementation of PortAudio for Mac OS X Core Audio * @@ -7,7 +7,18 @@ * Latest Version at: http://www.portaudio.com * * Authors: Ross Bencina and Phil Burk - * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * Copyright (c) 1999-2002 Ross Bencina and Phil Burk + * + * Theory of Operation + * + * This code uses the HAL (Hardware Access Layer) of the Apple CoreAudio library. + * This is the layer closes to the hardware. + * The HAL layer only supports the native HW supported sample rates. + * So if the chip only supports 44100 Hz, then the HAL only supports 44100. + * To provide other rates we use the handy Apple AUConverter which provides + * sample rate conversion, mono-to-stereo conversion, and buffer size adaptation. + * + * License * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -43,18 +54,28 @@ audio input works if using same CoreAudio device (some HW devices make separate CoreAudio devices). 2.22.2002 - Stephane Letz - Explicit cast needed for compilation with Code Warrior 7 3.19.2002 - Phil Burk - Added paInt16, paInt8, format using new "pa_common/pa_convert.c" file. - Return error if opened in mono mode cuz not supported. + Return error if opened in mono mode cuz not supported. [Supported 10.12.2002] Add support for Pa_GetCPULoad(); Fixed timestamp in callback and Pa_StreamTime() (Thanks n++k for the advice!) Check for invalid sample rates and return an error. - Check for getenv("PA_MIN_LATEWNCY_MSEC") to set latency externally. + Check for getenv("PA_MIN_LATENCY_MSEC") to set latency externally. Better error checking for invalid channel counts and invalid devices. 3.29.2002 - Phil Burk - Fixed Pa_GetCPULoad() for small buffers. 3.31.2002 - Phil Burk - Use getrusage() instead of gettimeofday() for CPU Load calculation. + 10.12.2002 - Phil Burk - Use AudioConverter to allow wide range of sample rates, and mono. + Use FIFO (from pablio/rinbuffer.h) so that we can pull data through converter. + Added PaOSX_FixVolumeScalar() to make iMic audible. + 10.17.2002 - Phil Burk - Support full duplex between two different devices. + Name internal functions PaOSX_* + Dumped useless PA_MIN_LATENCY_MSEC environment variable. + Use kAudioDevicePropertyStreamFormatMatch to determine max channels. TODO: -O- how do mono output? -O- FIFO between input and output callbacks if different devices, like in pa_mac.c +O- debug problem when changing sample rates on iMic +O- add support for paInt32 format +O- Why does iMic have grunge for the first second or two then clears up? +O- request notification when formats change or device unplugged +O- Why does patest_wire.c on iMic chop up sound when SR=34567Hz? */ #include @@ -62,10 +83,14 @@ O- FIFO between input and output callbacks if different devices, like in pa_mac. #include #include #include +#include +#include +#include #include "portaudio.h" #include "pa_host.h" #include "pa_trace.h" +#include "ringbuffer.h" /************************************************* Constants ********/ @@ -73,37 +98,45 @@ O- FIFO between input and output callbacks if different devices, like in pa_mac. #define PA_TRACE_RUN (0) #define PA_TRACE_START_STOP (1) -#define PA_MIN_LATENCY_MSEC (8) +#define PA_MIN_LATENCY_MSEC (1) #define MIN_TIMEOUT_MSEC (1000) #define PRINT(x) { printf x; fflush(stdout); } -#define ERR_RPT(x) PRINT(x) -#define DBUG(x) /* PRINT(x) /**/ -#define DBUGX(x) /* PRINT(x) /**/ +#define PRINT_ERR( msg, err ) PRINT(( msg ": error = 0x%0lX = '%s'\n", (err), ErrorToString(err)) ) +#define DBUG(x) /* PRINT(x) */ +#define DBUGBACK(x) /* if( sMaxBackgroundErrorMessages-- > 0 ) PRINT(x) */ +#define DBUGX(x) // define value of isInput passed to CoreAudio routines #define IS_INPUT (true) #define IS_OUTPUT (false) +typedef struct PaHostInOut +{ + AudioDeviceID audioDeviceID; // CoreAudio specific ID + int bytesPerUserNativeBuffer; /* User buffer size in native host format. Depends on numChannels. */ + AudioConverterRef converter; + void *converterBuffer; +} PaHostInOut; + /************************************************************** * Structure for internal host specific stream data. * This is allocated on a per stream basis. */ typedef struct PaHostSoundControl { - AudioDeviceID pahsc_AudioDeviceID; // Must be the same for input and output for now. - /* Input -------------- */ - int pahsc_BytesPerUserNativeInputBuffer; /* native buffer size in bytes per user chunk */ - /* Output -------------- */ - int pahsc_BytesPerUserNativeOutputBuffer; /* native buffer size in bytes per user chunk */ - /* Init Time -------------- */ - int pahsc_FramesPerHostBuffer; - int pahsc_UserBuffersPerHostBuffer; + PaHostInOut input; + PaHostInOut output; + AudioDeviceID primaryDeviceID; + Boolean usingSecondDevice; + int framesPerHostBuffer; + /* For sample rate, format conversion, or when using two devices. */ + RingBuffer ringBuffer; + char *ringBufferData; /* For measuring CPU utilization. */ - struct rusage pahsc_EntryRusage; - double pahsc_InverseMicrosPerHostBuffer; /* 1/Microseconds of real-time audio per user buffer. */ -} -PaHostSoundControl; + struct rusage entryRusage; + double inverseMicrosPerHostBuffer; /* 1/Microseconds of real-time audio per user buffer. */ +} PaHostSoundControl; /************************************************************** * Structure for internal extended device info. @@ -125,14 +158,18 @@ static int sNumOutputDevices = 0; static PaHostDeviceInfo *sDeviceInfos = NULL; static int sDefaultInputDeviceID = paNoDevice; static int sDefaultOutputDeviceID = paNoDevice; -static int sPaHostError = 0; - +static int sSavedHostError = 0; static int sNumCoreDevices = 0; static AudioDeviceID *sCoreDeviceIDs; // Array of Core AudioDeviceIDs +static const double supportedSampleRateRange[] = { 8000.0, 96000.0 }; static const char sMapperSuffixInput[] = " - Input"; static const char sMapperSuffixOutput[] = " - Output"; +/* Debug support. */ +//static int sMaxBackgroundErrorMessages = 100; +//static int sCoverageCounter = 1; // used to check code coverage during validation + /* We index the input devices first, then the output devices. */ #define LOWEST_INPUT_DEVID (0) #define HIGHEST_INPUT_DEVID (sNumInputDevices - 1) @@ -143,15 +180,64 @@ static const char sMapperSuffixOutput[] = " - Output"; /************************************************* Prototypes **********/ -static PaError Pa_QueryDevices( void ); -PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past ); +static PaError PaOSX_QueryDevices( void ); +static int PaOSX_ScanDevices( Boolean isInput ); +static int PaOSX_QueryDeviceInfo( PaHostDeviceInfo *hostDeviceInfo, int coreDeviceIndex, Boolean isInput ); +static PaDeviceID PaOSX_QueryDefaultInputDevice( void ); +static PaDeviceID PaOSX_QueryDefaultOutputDevice( void ); +static void PaOSX_CalcHostBufferSize( internalPortAudioStream *past ); + +/**********************************************************************/ +/* OS X errors are 4 character ID that can be printed. + * Note that uses a static pad so result must be printed immediately. + */ +static OSStatus statusText[2] = { 0, 0 }; +static const char *ErrorToString( OSStatus err ) +{ + const char *str; + + switch (err) + { + case kAudioHardwareUnspecifiedError: + str = "kAudioHardwareUnspecifiedError"; + break; + case kAudioHardwareNotRunningError: + str = "kAudioHardwareNotRunningError"; + break; + case kAudioHardwareUnknownPropertyError: + str = "kAudioHardwareUnknownPropertyError"; + break; + case kAudioDeviceUnsupportedFormatError: + str = "kAudioDeviceUnsupportedFormatError"; + break; + case kAudioHardwareBadPropertySizeError: + str = "kAudioHardwareBadPropertySizeError"; + break; + case kAudioHardwareIllegalOperationError: + str = "kAudioHardwareIllegalOperationError"; + break; + default: + str = "Unknown CoreAudio Error!"; + statusText[0] = err; + str = (const char *)statusText; + break; + } -static int PaHost_ScanDevices( Boolean isInput ); -static int PaHost_QueryDeviceInfo( PaHostDeviceInfo *hostDeviceInfo, int coreDeviceIndex, Boolean isInput ); + return str; +} -static PaDeviceID Pa_QueryDefaultInputDevice( void ); -static PaDeviceID Pa_QueryDefaultOutputDevice( void ); -static void PaHost_CalcHostBufferSize( internalPortAudioStream *past ); +/**********************************************************************/ +static unsigned long RoundUpToNextPowerOf2( unsigned long n ) +{ + long numBits = 0; + if( ((n-1) & n) == 0) return n; /* Already Power of two. */ + while( n > 0 ) + { + n= n>>1; + numBits++; + } + return (1<past_DeviceData; if( pahsc == NULL ) return; /* Query user CPU timer for usage analysis and to prevent overuse of CPU. */ - getrusage( RUSAGE_SELF, &pahsc->pahsc_EntryRusage ); + getrusage( RUSAGE_SELF, &pahsc->entryRusage ); } static long SubtractTime_AminusB( struct timeval *timeA, struct timeval *timeB ) @@ -188,10 +274,10 @@ static void Pa_EndUsageCalculation( internalPortAudioStream *past ) if( getrusage( RUSAGE_SELF, ¤tRusage ) == 0 ) { - usecsElapsed = SubtractTime_AminusB( ¤tRusage.ru_utime, &pahsc->pahsc_EntryRusage.ru_utime ); + usecsElapsed = SubtractTime_AminusB( ¤tRusage.ru_utime, &pahsc->entryRusage.ru_utime ); /* Use inverse because it is faster than the divide. */ - newUsage = usecsElapsed * pahsc->pahsc_InverseMicrosPerHostBuffer; + newUsage = usecsElapsed * pahsc->inverseMicrosPerHostBuffer; past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + (LOWPASS_COEFFICIENT_1 * newUsage); @@ -200,92 +286,104 @@ static void Pa_EndUsageCalculation( internalPortAudioStream *past ) /****************************************** END CPU UTILIZATION *******/ /************************************************************************/ -static PaDeviceID Pa_QueryDefaultInputDevice( void ) +static PaDeviceID PaOSX_QueryDefaultInputDevice( void ) { - OSStatus err = noErr; - UInt32 count; - int i; + OSStatus err = noErr; + UInt32 count; + int i; AudioDeviceID tempDeviceID = kAudioDeviceUnknown; - PaDeviceID defaultDeviceID = paNoDevice; + PaDeviceID defaultDeviceID = paNoDevice; // get the default output device for the HAL // it is required to pass the size of the data to be returned count = sizeof(tempDeviceID); err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultInputDevice, &count, (void *) &tempDeviceID); - if (err != noErr) goto Bail; + if (err != noErr) goto error; // scan input devices to see which one matches this device defaultDeviceID = paNoDevice; for( i=LOWEST_INPUT_DEVID; i<=HIGHEST_INPUT_DEVID; i++ ) { - DBUG(("Pa_QueryDefaultInputDevice: i = %d, aDevId = %d\n", i, sDeviceInfos[i].audioDeviceID )); + DBUG(("PaOSX_QueryDefaultInputDevice: i = %d, aDevId = %ld\n", i, sDeviceInfos[i].audioDeviceID )); if( sDeviceInfos[i].audioDeviceID == tempDeviceID ) { defaultDeviceID = i; break; } } -Bail: +error: return defaultDeviceID; } /************************************************************************/ -static PaDeviceID Pa_QueryDefaultOutputDevice( void ) +static PaDeviceID PaOSX_QueryDefaultOutputDevice( void ) { - OSStatus err = noErr; - UInt32 count; - int i; + OSStatus err = noErr; + UInt32 count; + int i; AudioDeviceID tempDeviceID = kAudioDeviceUnknown; - PaDeviceID defaultDeviceID = paNoDevice; + PaDeviceID defaultDeviceID = paNoDevice; // get the default output device for the HAL // it is required to pass the size of the data to be returned count = sizeof(tempDeviceID); err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice, &count, (void *) &tempDeviceID); - if (err != noErr) goto Bail; + if (err != noErr) goto error; // scan output devices to see which one matches this device defaultDeviceID = paNoDevice; for( i=LOWEST_OUTPUT_DEVID; i<=HIGHEST_OUTPUT_DEVID; i++ ) { - DBUG(("Pa_QueryDefaultOutputDevice: i = %d, aDevId = %d\n", i, sDeviceInfos[i].audioDeviceID )); + DBUG(("PaOSX_QueryDefaultOutputDevice: i = %d, aDevId = %ld\n", i, sDeviceInfos[i].audioDeviceID )); if( sDeviceInfos[i].audioDeviceID == tempDeviceID ) { defaultDeviceID = i; break; } } -Bail: +error: return defaultDeviceID; } /******************************************************************/ -static PaError Pa_QueryDevices( void ) +static PaError PaOSX_QueryDevices( void ) { OSStatus err = noErr; UInt32 outSize; Boolean outWritable; - int numBytes; + int numBytes; // find out how many Core Audio devices there are, if any + outSize = sizeof(outWritable); err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &outSize, &outWritable); if (err != noErr) - ERR_RPT(("Couldn't get info about list of audio devices\n")); - + { + PRINT_ERR("Couldn't get info about list of audio devices", err); + sSavedHostError = err; + return paHostError; + } + // calculate the number of device available sNumCoreDevices = outSize / sizeof(AudioDeviceID); // Bail if there aren't any devices if (sNumCoreDevices < 1) - ERR_RPT(("No Devices Available\n")); - + { + PRINT(("No Devices Available")); + return paHostError; + } + // make space for the devices we are about to get sCoreDeviceIDs = (AudioDeviceID *)malloc(outSize); // get an array of AudioDeviceIDs err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &outSize, (void *)sCoreDeviceIDs); if (err != noErr) - ERR_RPT(("Couldn't get list of audio device IDs\n")); + { + PRINT_ERR("Couldn't get list of audio device IDs", err); + sSavedHostError = err; + return paHostError; + } // Allocate structures to hold device info pointers. // There will be a maximum of two Pa devices per Core Audio device, input and/or output. @@ -295,35 +393,22 @@ static PaError Pa_QueryDevices( void ) // Scan all the Core Audio devices to see which support input and allocate a // PaHostDeviceInfo structure for each one. - PaHost_ScanDevices( IS_INPUT ); + PaOSX_ScanDevices( IS_INPUT ); sNumInputDevices = sNumPaDevices; // Now scan all the output devices. - PaHost_ScanDevices( IS_OUTPUT ); + PaOSX_ScanDevices( IS_OUTPUT ); sNumOutputDevices = sNumPaDevices - sNumInputDevices; // Figure out which of the devices that we scanned is the default device. - sDefaultInputDeviceID = Pa_QueryDefaultInputDevice(); - sDefaultOutputDeviceID = Pa_QueryDefaultOutputDevice(); + sDefaultInputDeviceID = PaOSX_QueryDefaultInputDevice(); + sDefaultOutputDeviceID = PaOSX_QueryDefaultOutputDevice(); return paNoError; } -/************************************************************************************/ -long Pa_GetHostError() -{ - return sPaHostError; -} - -/*************************************************************************/ -int Pa_CountDevices() -{ - if( sNumPaDevices <= 0 ) Pa_Initialize(); - return sNumPaDevices; -} - /*************************************************************************/ /* Allocate a string containing the device name. */ -static char *PaHost_DeviceNameFromID(AudioDeviceID deviceID, Boolean isInput ) +static char *PaOSX_DeviceNameFromID(AudioDeviceID deviceID, Boolean isInput ) { OSStatus err = noErr; UInt32 outSize; @@ -339,87 +424,18 @@ static char *PaHost_DeviceNameFromID(AudioDeviceID deviceID, Boolean isInput ) { err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyDeviceName, &outSize, deviceName); if (err != noErr) - ERR_RPT(("Couldn't get audio device name.\n")); + PRINT_ERR("Couldn't get audio device name", err); } } return deviceName; } -/*************************************************************************/ -// An AudioStreamBasicDescription is passed in to query whether or not -// the format is supported. A kAudioDeviceUnsupportedFormatError will -// be returned if the format is not supported and kAudioHardwareNoError -// will be returned if it is supported. AudioStreamBasicDescription -// fields set to 0 will be ignored in the query, but otherwise values -// must match exactly. - -Boolean deviceDoesSupportFormat(AudioDeviceID deviceID, AudioStreamBasicDescription *desc, Boolean isInput ) -{ - OSStatus err = noErr; - UInt32 outSize; - - outSize = sizeof(*desc); - err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyStreamFormatSupported, &outSize, desc); - - if (err == kAudioHardwareNoError) - return true; - else - return false; -} - -/*************************************************************************/ -// return an error string -char* coreAudioErrorString (int errCode ) -{ - char *str; - - switch (errCode) - { - case kAudioHardwareUnspecifiedError: - str = "kAudioHardwareUnspecifiedError"; - break; - case kAudioHardwareNotRunningError: - str = "kAudioHardwareNotRunningError"; - break; - case kAudioHardwareUnknownPropertyError: - str = "kAudioHardwareUnknownPropertyError"; - break; - case kAudioDeviceUnsupportedFormatError: - str = "kAudioDeviceUnsupportedFormatError"; - break; - case kAudioHardwareBadPropertySizeError: - str = "kAudioHardwareBadPropertySizeError"; - break; - case kAudioHardwareIllegalOperationError: - str = "kAudioHardwareIllegalOperationError"; - break; - default: - str = "Unknown CoreAudio Error!"; - break; - } - - return str; -} - -/************************************************************************* -** PaDeviceInfo structures have already been created -** so just return the pointer. -** -*/ -const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) -{ - if( id < 0 || id >= sNumPaDevices ) - return NULL; - - return &sDeviceInfos[id].paInfo; -} - /************************************************************************* ** Scan all of the Core Audio devices to see which support input or output. ** Changes sNumDevices, and fills in sDeviceInfos. */ -static int PaHost_ScanDevices( Boolean isInput ) +static int PaOSX_ScanDevices( Boolean isInput ) { int coreDeviceIndex; int result; @@ -430,8 +446,8 @@ static int PaHost_ScanDevices( Boolean isInput ) { // try to fill in next PaHostDeviceInfo hostDeviceInfo = &sDeviceInfos[sNumPaDevices]; - result = PaHost_QueryDeviceInfo( hostDeviceInfo, coreDeviceIndex, isInput ); - DBUG(("PaHost_ScanDevices: paDevId = %d, coreDevId = %d\n", sNumPaDevices, hostDeviceInfo->audioDeviceID )); + result = PaOSX_QueryDeviceInfo( hostDeviceInfo, coreDeviceIndex, isInput ); + DBUGX(("PaOSX_ScanDevices: paDevId = %d, coreDevId = %d\n", sNumPaDevices, coreDeviceIndex )); if( result > 0 ) { sNumPaDevices += 1; // bump global counter if we got one @@ -442,6 +458,7 @@ static int PaHost_ScanDevices( Boolean isInput ) return numAdded; } + /************************************************************************* ** Try to fill in the device info for this device. ** Return 1 if a good device that PA can use. @@ -449,59 +466,55 @@ static int PaHost_ScanDevices( Boolean isInput ) ** or return negative error. ** */ -static int PaHost_QueryDeviceInfo( PaHostDeviceInfo *hostDeviceInfo, int coreDeviceIndex, Boolean isInput ) +static int PaOSX_QueryDeviceInfo( PaHostDeviceInfo *hostDeviceInfo, int coreDeviceIndex, Boolean isInput ) { - OSErr err; - int index; - UInt32 outSize; + OSStatus err; + UInt32 outSize; AudioStreamBasicDescription formatDesc; - Boolean result; AudioDeviceID devID; - double *sampleRates = NULL; /* non-const ptr */ - - PaDeviceInfo *deviceInfo = &hostDeviceInfo->paInfo; - double possibleSampleRates[] = {8000.0, 11025.0, 22050.0, 44100.0, 48000.0, 88200.0, 96000.0}; - int maxNumSampleRates = sizeof( possibleSampleRates ) / sizeof( double ); + PaDeviceInfo *deviceInfo = &hostDeviceInfo->paInfo; deviceInfo->structVersion = 1; deviceInfo->maxInputChannels = 0; deviceInfo->maxOutputChannels = 0; + + deviceInfo->sampleRates = supportedSampleRateRange; // because we use sample rate converter to get continuous rates deviceInfo->numSampleRates = -1; devID = sCoreDeviceIDs[ coreDeviceIndex ]; hostDeviceInfo->audioDeviceID = devID; + DBUG(("PaOSX_QueryDeviceInfo: coreDeviceIndex = %d, devID = %d, isInput = %d\n", + coreDeviceIndex, devID, isInput )); + // Get data format info from the device. + outSize = sizeof(formatDesc); + err = AudioDeviceGetProperty(devID, 0, isInput, kAudioDevicePropertyStreamFormat, &outSize, &formatDesc); + // This just may not be an appropriate device for input or output so leave quietly. + if( (err != noErr) || (formatDesc.mChannelsPerFrame == 0) ) goto error; - // Figure out supported sample rates - // Make room in case device supports all rates. - sampleRates = (double*)PaHost_AllocateFastMemory( maxNumSampleRates * sizeof(double) ); - if( sampleRates == NULL ) return paInsufficientMemory; - - deviceInfo->sampleRates = sampleRates; - deviceInfo->numSampleRates = 0; - - // Loop through the possible sampling rates and check each to see if the device supports it. - for (index = 0; index < maxNumSampleRates; index ++) + // Right now the Core Audio headers only define one formatID: LinearPCM + // Apparently LinearPCM must be Float32 for now. + if( (formatDesc.mFormatID == kAudioFormatLinearPCM) && + (formatDesc.mFormatFlags & kLinearPCMFormatFlagIsFloat) ) { - memset( &formatDesc, 0, sizeof(AudioStreamBasicDescription) ); - formatDesc.mSampleRate = possibleSampleRates[index]; - result = deviceDoesSupportFormat( devID, &formatDesc, isInput ); - - if (result == true) - { - deviceInfo->numSampleRates += 1; - *sampleRates = possibleSampleRates[index]; - sampleRates++; - } + deviceInfo->nativeSampleFormats = paFloat32; + } + else + { + return paSampleFormatNotSupported; } - // If no sample rates supported, then not a very good device. - if( deviceInfo->numSampleRates == 0 ) goto error; - // Get data format info from the device. + // Determine maximum number of channels supported. + memset( &formatDesc, 0, sizeof(formatDesc)); + formatDesc.mChannelsPerFrame = 256; // FIXME - what about device with > 256 channels outSize = sizeof(formatDesc); - err = AudioDeviceGetProperty(devID, 0, isInput, kAudioDevicePropertyStreamFormat, &outSize, &formatDesc); - - // If no channels supported, then not a very good device. - if( (err != noErr) || (formatDesc.mChannelsPerFrame == 0) ) goto error; + err = AudioDeviceGetProperty( devID, 0, + isInput, kAudioDevicePropertyStreamFormatMatch, &outSize, &formatDesc); + if( err != noErr ) + { + PRINT_ERR("PaOSX_QueryDeviceInfo: Could not get device format match", err); + sSavedHostError = err; + return paHostError; + } if( isInput ) { @@ -512,205 +525,268 @@ static int PaHost_QueryDeviceInfo( PaHostDeviceInfo *hostDeviceInfo, int coreDev deviceInfo->maxOutputChannels = formatDesc.mChannelsPerFrame; } - // FIXME - where to put current sample rate?: formatDesc.mSampleRate - - // Right now the Core Audio headers only define one formatID: LinearPCM - // Apparently LinearPCM must be Float32 for now. - switch (formatDesc.mFormatID) - { - case kAudioFormatLinearPCM: - deviceInfo->nativeSampleFormats = paFloat32; - - // FIXME - details about the format are in these flags. - // formatDesc.mFormatFlags - - // here are the possibilities - // kLinearPCMFormatFlagIsFloat // set for floating point, clear for integer - // kLinearPCMFormatFlagIsBigEndian // set for big endian, clear for little - // kLinearPCMFormatFlagIsSignedInteger // set for signed integer, clear for unsigned integer, - // only valid if kLinearPCMFormatFlagIsFloat is clear - // kLinearPCMFormatFlagIsPacked // set if the sample bits are packed as closely together as possible, - // clear if they are high or low aligned within the channel - // kLinearPCMFormatFlagIsAlignedHigh // set if the sample bits are placed - break; - - default: - deviceInfo->nativeSampleFormats = paFloat32; // FIXME - break; - } - // Get the device name - deviceInfo->name = PaHost_DeviceNameFromID( devID, isInput ); + deviceInfo->name = PaOSX_DeviceNameFromID( devID, isInput ); return 1; error: - if( sampleRates != NULL ) free( sampleRates ); return 0; } -/************************************************************************* -** Returns recommended device ID. -** On the PC, the recommended device can be specified by the user by -** setting an environment variable. For example, to use device #1. -** -** set PA_RECOMMENDED_OUTPUT_DEVICE=1 -** -** The user should first determine the available device ID by using -** the supplied application "pa_devs". -*/ -#define PA_ENV_BUF_SIZE (32) -#define PA_REC_IN_DEV_ENV_NAME ("PA_RECOMMENDED_INPUT_DEVICE") -#define PA_REC_OUT_DEV_ENV_NAME ("PA_RECOMMENDED_OUTPUT_DEVICE") - -static PaDeviceID PaHost_GetEnvDefaultDeviceID( char *envName ) -{ -#if 0 - UInt32 hresult; - char envbuf[PA_ENV_BUF_SIZE]; - PaDeviceID recommendedID = paNoDevice; - - /* Let user determine default device by setting environment variable. */ - hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE ); - if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) - { - recommendedID = atoi( envbuf ); - } - - return recommendedID; -#endif - return paNoDevice; -} - -static PaError Pa_MaybeQueryDevices( void ) +/**********************************************************************/ +static PaError PaOSX_MaybeQueryDevices( void ) { if( sNumPaDevices == 0 ) { - return Pa_QueryDevices(); + return PaOSX_QueryDevices(); } return 0; } +static char zeroPad[256] = { 0 }; + /********************************************************************** -** Check for environment variable, else query devices and use result. +** This is the proc that supplies the data to the AudioConverterFillBuffer call. +** We can pass back arbitrarily sized blocks so if the FIFO region is split +** just pass back the first half. */ -PaDeviceID Pa_GetDefaultInputDeviceID( void ) +static OSStatus PaOSX_InputConverterCallbackProc (AudioConverterRef inAudioConverter, + UInt32* outDataSize, + void** outData, + void* inUserData) { - PaError result; - result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME ); - if( result < 0 ) + internalPortAudioStream *past = (internalPortAudioStream *) inUserData; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + void *dataPtr1; + long size1; + void *dataPtr2; + long size2; + + /* Pass contiguous region from FIFO directly to converter. */ + RingBuffer_GetReadRegions( &pahsc->ringBuffer, *outDataSize, + &dataPtr1, &size1, &dataPtr2, &size2 ); + + if( size1 > 0 ) { - result = Pa_MaybeQueryDevices(); - if( result < 0 ) return result; - result = sDefaultInputDeviceID; + *outData = dataPtr1; + *outDataSize = size1; + RingBuffer_AdvanceReadIndex( &pahsc->ringBuffer, size1 ); + DBUGX(("PaOSX_InputConverterCallbackProc: read %ld bytes from FIFO.\n", size1 )); } - return result; + else + { + DBUGBACK(("PaOSX_InputConverterCallbackProc: got no data!\n")); + *outData = zeroPad; // Give it zero data to keep it happy. + *outDataSize = sizeof(zeroPad); + } + return noErr; } -PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +/***************************************************************************** +** Get audio input, if any, from passed in buffer, or from converter or from FIFO +** and run PA callback. +*/ +static OSStatus PaOSX_LoadAndProcess( internalPortAudioStream *past, + void *inputBuffer, void *outputBuffer ) { - PaError result; - result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME ); - if( result < 0 ) + OSStatus err = noErr; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + if( past->past_StopSoon ) { - result = Pa_MaybeQueryDevices(); - if( result < 0 ) return result; - result = sDefaultOutputDeviceID; + if( outputBuffer ) + { + /* Clear remainder of audio buffer if we are waiting for stop. */ + AddTraceMessage("PaOSX_HandleInputOutput: zero rest of wave buffer ", i ); + memset( outputBuffer, 0, pahsc->output.bytesPerUserNativeBuffer ); + } } - return result; + else + { + /* Do we need data from the converted input? */ + if( pahsc->input.converter != NULL ) + { + UInt32 size = pahsc->input.bytesPerUserNativeBuffer; + err = AudioConverterFillBuffer( + pahsc->input.converter, + PaOSX_InputConverterCallbackProc, + past, + &size, + pahsc->input.converterBuffer); + if( err != noErr ) return err; + inputBuffer = pahsc->input.converterBuffer; + } + /* Or should just get the data directly from the FIFO? */ + else if( pahsc->ringBufferData != NULL ) + { + if( RingBuffer_GetReadAvailable( &pahsc->ringBuffer ) >= pahsc->input.bytesPerUserNativeBuffer) + { + RingBuffer_Read( &pahsc->ringBuffer, pahsc->input.converterBuffer, pahsc->input.bytesPerUserNativeBuffer ); + inputBuffer = pahsc->input.converterBuffer; + } + } + + /* Fill part of audio converter buffer by converting input to user format, + * calling user callback, then converting output to native format. */ + if( PaConvert_Process( past, inputBuffer, outputBuffer )) + { + past->past_StopSoon = 1; + } + } + return err; } -/********************************************************************** -** Initialize Host dependant part of API. +/***************************************************************************** +** This is the proc that supplies the data to the AudioConverterFillBuffer call */ - -PaError PaHost_Init( void ) +static OSStatus PaOSX_OutputConverterCallbackProc (AudioConverterRef inAudioConverter, + UInt32* outDataSize, + void** outData, + void* inUserData) { - return Pa_MaybeQueryDevices(); + internalPortAudioStream *past = (internalPortAudioStream *) inUserData; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + *outData = pahsc->output.converterBuffer; + *outDataSize = pahsc->output.bytesPerUserNativeBuffer; + + return PaOSX_LoadAndProcess ( past, pahsc->input.converterBuffer, pahsc->output.converterBuffer ); } /********************************************************************** ** Fill any available output buffers and use any available ** input buffers by calling user callback. +** Will set past->past_StopSoon if user callback indicates that it is finished. */ -static PaError Pa_TimeSlice( internalPortAudioStream *past, const AudioBufferList* inInputData, - AudioBufferList* outOutputData ) +static OSStatus PaOSX_HandleInputOutput( internalPortAudioStream *past, + const AudioBufferList* inInputData, + AudioBufferList* outOutputData ) { - PaError result = 0; - char *inputNativeBufferfPtr = NULL; - char *outputNativeBufferfPtr = NULL; - int i; - int buffersProcessed = 0; - int done = 0; + OSStatus err = noErr; + char *inputNativeBufferfPtr = NULL; + char *outputNativeBufferfPtr = NULL; + int i; PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; - if( pahsc == NULL ) return paInternalError; - - past->past_NumCallbacks += 1; - -#if PA_TRACE_RUN - AddTraceMessage("Pa_TimeSlice: past_NumCallbacks ", past->past_NumCallbacks ); -#endif - - Pa_StartUsageCalculation( past ); /* If we are using output, then we need an empty output buffer. */ - if( past->past_NumOutputChannels > 0 ) + if( outOutputData->mNumberBuffers > 0 ) { outputNativeBufferfPtr = (char*)outOutputData->mBuffers[0].mData; } - /* If we are using input, then we need a full input buffer. */ - if( past->past_NumInputChannels > 0 ) + if( inInputData->mNumberBuffers > 0 ) { inputNativeBufferfPtr = (char*)inInputData->mBuffers[0].mData; + + /* If there is a FIFO for input then write to it. */ + if( (pahsc->ringBufferData != NULL) && !pahsc->usingSecondDevice ) + { + long writeRoom = RingBuffer_GetWriteAvailable( &pahsc->ringBuffer ); + long numBytes = inInputData->mBuffers[0].mDataByteSize; + if( numBytes <= writeRoom ) + { + RingBuffer_Write( &pahsc->ringBuffer, inputNativeBufferfPtr, numBytes ); + DBUGBACK(("PaOSX_HandleInputOutput: wrote %ld bytes to FIFO.\n", inInputData->mBuffers[0].mDataByteSize)); + } // FIXME else ??? + } } - - buffersProcessed += 1; - - /* Each host buffer contains multiple user buffers so do them all now. */ - for( i=0; ipahsc_UserBuffersPerHostBuffer; i++ ) + + if( pahsc->output.converter != NULL ) { - if( done ) + /* Using output and input converter. */ + UInt32 size = outOutputData->mBuffers[0].mDataByteSize; + err = AudioConverterFillBuffer( + pahsc->output.converter, + PaOSX_OutputConverterCallbackProc, + past, + &size, + outputNativeBufferfPtr); + if( err != noErr ) { - if( outputNativeBufferfPtr ) - { - /* Clear remainder of wave buffer if we are waiting for stop. */ - AddTraceMessage("Pa_TimeSlice: zero rest of wave buffer ", i ); - memset( outputNativeBufferfPtr, 0, pahsc->pahsc_BytesPerUserNativeOutputBuffer ); - } + PRINT_ERR("PaOSX_HandleInputOutput: AudioConverterFillBuffer failed", err); + goto error; } - else + } + else if( (pahsc->input.converter != NULL) && !pahsc->usingSecondDevice) + { + /* Using just an input converter. */ + /* Generate user buffers as long as we have a half full input FIFO. */ + long gotHalf = pahsc->ringBuffer.bufferSize / 2; + while( (RingBuffer_GetReadAvailable( &pahsc->ringBuffer ) >= gotHalf) && + (past->past_StopSoon == 0) ) { - /* Convert 32 bit native data to user data and call user routine. */ - result = PaConvert_Process( past, inputNativeBufferfPtr, outputNativeBufferfPtr ); - if( result != 0) done = 1; + err = PaOSX_LoadAndProcess ( past, NULL, outputNativeBufferfPtr ); + if( err != noErr ) goto error; + if( outputNativeBufferfPtr) outputNativeBufferfPtr += pahsc->output.bytesPerUserNativeBuffer; } - if( inputNativeBufferfPtr ) inputNativeBufferfPtr += pahsc->pahsc_BytesPerUserNativeInputBuffer; - if( outputNativeBufferfPtr) outputNativeBufferfPtr += pahsc->pahsc_BytesPerUserNativeOutputBuffer; } - - Pa_EndUsageCalculation( past ); - -#if PA_TRACE_RUN - AddTraceMessage("Pa_TimeSlice: buffersProcessed ", buffersProcessed ); -#endif - - return (result != 0) ? result : done; + else + { + /* No AUConverters used. */ + /* Each host buffer contains multiple user buffers so do them all now. */ + for( i=0; ipast_NumUserBuffers; i++ ) + { + err = PaOSX_LoadAndProcess ( past, inputNativeBufferfPtr, outputNativeBufferfPtr ); + if( err != noErr ) goto error; + if( inputNativeBufferfPtr ) inputNativeBufferfPtr += pahsc->input.bytesPerUserNativeBuffer; + if( outputNativeBufferfPtr) outputNativeBufferfPtr += pahsc->output.bytesPerUserNativeBuffer; + } + } + +error: + return err; } -OSStatus appIOProc (AudioDeviceID inDevice, const AudioTimeStamp* inNow, +/****************************************************************** + * This callback is used when two separate devices are used for input and output. + * This often happens when using USB devices which present as two devices: input and output. + * It just writes its data to a FIFO so that it can be read by the main callback + * proc PaOSX_CoreAudioIOCallback(). + */ +static OSStatus PaOSX_CoreAudioInputCallback (AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* contextPtr) { + internalPortAudioStream *past = (internalPortAudioStream *) contextPtr; + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + /* If there is a FIFO for input then write to it. */ + if( pahsc->ringBufferData != NULL ) + { + long writeRoom = RingBuffer_GetWriteAvailable( &pahsc->ringBuffer ); + long numBytes = inInputData->mBuffers[0].mDataByteSize; + if( numBytes <= writeRoom ) + { + RingBuffer_Write( &pahsc->ringBuffer, inInputData->mBuffers[0].mData, inInputData->mBuffers[0].mDataByteSize ); + } + else + { + DBUGBACK(("PaOSX_CoreAudioInputCallback: FIFO too full to write!\n")); + } + } + + return noErr; +} - PaError result = 0; +/****************************************************************** + * This is the primary callback for CoreAudio. + * It can handle input and/or output for a single device. + * It takes input from CoreAudio, converts it and passes it to the + * PortAudio callback. Then takes the PA results and passes it back to CoreAudio. + */ +static OSStatus PaOSX_CoreAudioIOCallback (AudioDeviceID inDevice, const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, + void* contextPtr) +{ + OSStatus err = noErr; internalPortAudioStream *past; PaHostSoundControl *pahsc; past = (internalPortAudioStream *) contextPtr; pahsc = (PaHostSoundControl *) past->past_DeviceData; -// printf("Num input Buffers: %d; Num output Buffers: %d.\n", inInputData->mNumberBuffers, outOutputData->mNumberBuffers); - /* Has someone asked us to abort by calling Pa_AbortStream()? */ if( past->past_StopNow ) { @@ -721,7 +797,7 @@ OSStatus appIOProc (AudioDeviceID inDevice, const AudioTimeStamp* inNow, */ else if( past->past_StopSoon ) { - // FIXME - pretend all done + // FIXME - Pretend all done. Should wait for audio to play out but CoreAudio latency very low. past->past_IsActive = 0; /* Will cause thread to return. */ } else @@ -732,63 +808,220 @@ OSStatus appIOProc (AudioDeviceID inDevice, const AudioTimeStamp* inNow, past->past_FrameCount = inOutputTime->mSampleTime; } + /* Measure CPU load. */ + Pa_StartUsageCalculation( past ); + past->past_NumCallbacks += 1; + /* Process full input buffer and fill up empty output buffers. */ - if( (result = Pa_TimeSlice( past, inInputData, outOutputData )) != 0) - { - /* User callback has asked us to stop. */ -#if PA_TRACE_START_STOP - AddTraceMessage( "Pa_OutputThreadProc: TimeSlice() returned ", result ); -#endif - past->past_StopSoon = 1; /* Request that audio play out then stop. */ - result = paNoError; - } + err = PaOSX_HandleInputOutput( past, inInputData, outOutputData ); + + Pa_EndUsageCalculation( past ); } - // FIXME PaHost_UpdateStreamTime( pahsc ); - - return result; -} + // FIXME PaOSX_UpdateStreamTime( pahsc ); + if( err != 0 ) DBUG(("PaOSX_CoreAudioIOCallback: returns %ld.\n", err )); -#if 0 -static int PaHost_CalcTimeOut( internalPortAudioStream *past ) -{ - /* Calculate timeOut longer than longest time it could take to play all buffers. */ - int timeOut = (UInt32) (1500.0 * PaHost_GetTotalBufferFrames( past ) / past->past_SampleRate); - if( timeOut < MIN_TIMEOUT_MSEC ) timeOut = MIN_TIMEOUT_MSEC; - return timeOut; + return err; } -#endif - /*******************************************************************/ /* Attempt to set device sample rate. */ -static PaError PaHost_SetSampleRate( AudioDeviceID devID, Boolean isInput, double sampleRate ) +static PaError PaOSX_SetSampleRate( AudioDeviceID devID, Boolean isInput, double sampleRate ) { AudioStreamBasicDescription formatDesc; + PaError result = paNoError; OSStatus err; + UInt32 dataSize; + + // try to set to desired rate memset( &formatDesc, 0, sizeof(AudioStreamBasicDescription) ); formatDesc.mSampleRate = sampleRate; err = AudioDeviceSetProperty( devID, 0, 0, isInput, kAudioDevicePropertyStreamFormat, sizeof(formatDesc), &formatDesc); + if (err != noErr) + { + result = paInvalidSampleRate; + + /* Could not set to desired rate so query for closest match. */ + DBUG(("PaOSX_SetSampleRate: couldn't set to %f. Try to find match.\n", sampleRate )); + dataSize = sizeof(formatDesc); + AudioDeviceGetProperty( devID, 0, + isInput, kAudioDevicePropertyStreamFormatMatch, &dataSize, &formatDesc); + formatDesc.mSampleRate = sampleRate; + err = AudioDeviceGetProperty( devID, 0, + isInput, kAudioDevicePropertyStreamFormatMatch, &dataSize, &formatDesc); + if (err == noErr) + { + /* Set to that matching rate. */ + sampleRate = formatDesc.mSampleRate; + DBUG(("PaOSX_SetSampleRate: match succeeded, set to %f instead\n", sampleRate )); + memset( &formatDesc, 0, sizeof(AudioStreamBasicDescription) ); + formatDesc.mSampleRate = sampleRate; + AudioDeviceSetProperty( devID, 0, 0, + isInput, kAudioDevicePropertyStreamFormat, sizeof(formatDesc), &formatDesc); + } + } + return result; +} + +/******************************************************************* + * Check volume level of device. If below threshold, then set to newLevel. + * Using volume instead of decibels because decibel range varies by device. + */ +static void PaOSX_FixVolumeScalars( AudioDeviceID devID, Boolean isInput, + int numChannels, double threshold, double newLevel ) +{ + OSStatus err = noErr; + UInt32 dataSize; + int iChannel; + +/* The master channel is 0. Left and right are channels 1 and 2. */ +/* Fix volume. */ + for( iChannel = 0; iChannel<=numChannels; iChannel++ ) + { + Float32 fdata32; + dataSize = sizeof( fdata32 ); + err = AudioDeviceGetProperty( devID, iChannel, isInput, + kAudioDevicePropertyVolumeScalar, &dataSize, &fdata32 ); + if( err == noErr ) + { + DBUG(("kAudioDevicePropertyVolumeScalar for channel %d = %f\n", iChannel, fdata32)); + if( fdata32 <= (Float32) threshold ) + { + dataSize = sizeof( fdata32 ); + fdata32 = (Float32) newLevel; + err = AudioDeviceSetProperty( devID, 0, iChannel, isInput, + kAudioDevicePropertyVolumeScalar, dataSize, &fdata32 ); + if( err != noErr ) + { + PRINT(("Warning: audio volume is very low and could not be turned up.\n")); + } + else + { + PRINT(("Volume for audio channel %d was <= %4.2f so set to %4.2f by PortAudio!\n", + iChannel, threshold, newLevel )); + } + } + } + } +/* Unmute if muted. */ + for( iChannel = 0; iChannel<=numChannels; iChannel++ ) + { + UInt32 uidata32; + dataSize = sizeof( uidata32 ); + err = AudioDeviceGetProperty( devID, iChannel, isInput, + kAudioDevicePropertyMute, &dataSize, &uidata32 ); + if( err == noErr ) + { + DBUG(("uidata32 for channel %d = %ld\n", iChannel, uidata32)); + if( uidata32 == 1 ) // muted? + { + dataSize = sizeof( uidata32 ); + uidata32 = 0; // unmute + err = AudioDeviceSetProperty( devID, 0, iChannel, isInput, + kAudioDevicePropertyMute, dataSize, &uidata32 ); + if( err != noErr ) + { + PRINT(("Warning: audio is muted and could not be unmuted!\n")); + } + else + { + PRINT(("Audio channel %d was unmuted by PortAudio!\n", iChannel )); + } + } + } + } - if (err != kAudioHardwareNoError) return paInvalidSampleRate; - else return paNoError; } +#if 0 +static void PaOSX_DumpDeviceInfo( AudioDeviceID devID, Boolean isInput ) +{ + OSStatus err = noErr; + UInt32 dataSize; + UInt32 uidata32; + Float32 fdata32; + AudioValueRange audioRange; + + dataSize = sizeof( uidata32 ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyLatency, &dataSize, &uidata32 ); + if( err != noErr ) + { + PRINT_ERR("Error reading kAudioDevicePropertyLatency", err); + return; + } + PRINT(("kAudioDevicePropertyLatency = %d\n", (int)uidata32 )); + + dataSize = sizeof( fdata32 ); + err = AudioDeviceGetProperty( devID, 1, isInput, + kAudioDevicePropertyVolumeScalar, &dataSize, &fdata32 ); + if( err != noErr ) + { + PRINT_ERR("Error reading kAudioDevicePropertyVolumeScalar", err); + return; + } + PRINT(("kAudioDevicePropertyVolumeScalar = %f\n", fdata32 )); + + dataSize = sizeof( uidata32 ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyBufferSize, &dataSize, &uidata32 ); + if( err != noErr ) + { + PRINT_ERR("Error reading buffer size", err); + return; + } + PRINT(("kAudioDevicePropertyBufferSize = %d bytes\n", (int)uidata32 )); + + dataSize = sizeof( audioRange ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyBufferSizeRange, &dataSize, &audioRange ); + if( err != noErr ) + { + PRINT_ERR("Error reading buffer size range", err); + return; + } + PRINT(("kAudioDevicePropertyBufferSizeRange = %g to %g bytes\n", audioRange.mMinimum, audioRange.mMaximum )); + + dataSize = sizeof( uidata32 ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyBufferFrameSize, &dataSize, &uidata32 ); + if( err != noErr ) + { + PRINT_ERR("Error reading buffer size", err); + return; + } + PRINT(("kAudioDevicePropertyBufferFrameSize = %d frames\n", (int)uidata32 )); + + dataSize = sizeof( audioRange ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyBufferFrameSizeRange, &dataSize, &audioRange ); + if( err != noErr ) + { + PRINT_ERR("Error reading buffer size range", err); + return; + } + PRINT(("kAudioDevicePropertyBufferFrameSizeRange = %g to %g frames\n", audioRange.mMinimum, audioRange.mMaximum )); + + return; +} +#endif + /*******************************************************************/ -PaError PaHost_OpenInputStream( internalPortAudioStream *past ) +static PaError PaOSX_OpenInputDevice( internalPortAudioStream *past ) { PaHostSoundControl *pahsc; const PaHostDeviceInfo *hostDeviceInfo; PaError result = paNoError; - UInt32 bytesPerHostBuffer; UInt32 dataSize; OSStatus err = noErr; - int bytesPerInputFrame; + int needConverter = 0; + double deviceRate = past->past_SampleRate; + + DBUG(("PaOSX_OpenInputDevice: -------------\n")); pahsc = (PaHostSoundControl *) past->past_DeviceData; - DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_InputDeviceID)); if( (past->past_InputDeviceID < LOWEST_INPUT_DEVID) || (past->past_InputDeviceID > HIGHEST_INPUT_DEVID) ) { @@ -796,68 +1029,134 @@ PaError PaHost_OpenInputStream( internalPortAudioStream *past ) } hostDeviceInfo = &sDeviceInfos[past->past_InputDeviceID]; + PaOSX_FixVolumeScalars( pahsc->input.audioDeviceID, IS_INPUT, + hostDeviceInfo->paInfo.maxInputChannels, 0.1, 0.9 ); + /* Try to set sample rate. */ - result = PaHost_SetSampleRate( hostDeviceInfo->audioDeviceID, IS_INPUT, past->past_SampleRate ); - if( result != paNoError ) return result; + result = PaOSX_SetSampleRate( pahsc->input.audioDeviceID, IS_INPUT, past->past_SampleRate ); + if( result != paNoError ) + { + DBUG(("PaOSX_OpenInputDevice: Need converter for sample rate = %f\n", past->past_SampleRate )); + needConverter = 1; + result = paNoError; + } + else + { + DBUG(("PaOSX_OpenInputDevice: successfully set sample rate to %f\n", past->past_SampleRate )); + } - if( past->past_NumInputChannels != hostDeviceInfo->paInfo.maxInputChannels ) + /* Try to set number of channels. */ + if( past->past_NumInputChannels > hostDeviceInfo->paInfo.maxInputChannels ) { -#if 1 - return paInvalidChannelCount; // FIXME - how support mono? -#else -FIXME - should this be set on a stream basis? Is it possible to change? - /* Attempt to set number of channels. */ + return paInvalidChannelCount; /* Too many channels! */ + } + else if( past->past_NumInputChannels < hostDeviceInfo->paInfo.maxInputChannels ) + { + AudioStreamBasicDescription formatDesc; OSStatus err; memset( &formatDesc, 0, sizeof(AudioStreamBasicDescription) ); formatDesc.mChannelsPerFrame = past->past_NumInputChannels; - - err = AudioDeviceSetProperty( hostDeviceInfo->audioDeviceID, 0, 0, + err = AudioDeviceSetProperty( pahsc->input.audioDeviceID, 0, 0, IS_INPUT, kAudioDevicePropertyStreamFormat, sizeof(formatDesc), &formatDesc); - if (err != kAudioHardwareNoError) + if (err != noErr) { - result = paInvalidChannelCount; - goto error; + needConverter = 1; } -#endif } - // calculate native buffer sizes in bytes - bytesPerInputFrame = Pa_GetSampleSize(paFloat32) * past->past_NumInputChannels; - pahsc->pahsc_BytesPerUserNativeInputBuffer = past->past_FramesPerUserBuffer * bytesPerInputFrame; - bytesPerHostBuffer = pahsc->pahsc_FramesPerHostBuffer * bytesPerInputFrame; - - // Change the bufferSize of the device! Is this per device or just for our stream? - dataSize = sizeof(UInt32); - err = AudioDeviceSetProperty( hostDeviceInfo->audioDeviceID, 0, 0, IS_INPUT, - kAudioDevicePropertyBufferSize, dataSize, &bytesPerHostBuffer); + /* Try to set the I/O bufferSize of the device. */ + dataSize = sizeof(pahsc->framesPerHostBuffer); + err = AudioDeviceSetProperty( pahsc->input.audioDeviceID, 0, 0, IS_INPUT, + kAudioDevicePropertyBufferFrameSize, dataSize, + &pahsc->framesPerHostBuffer); if( err != noErr ) { - ERR_RPT(("Could not force buffer size!")); - result = paHostError; - goto error; + DBUG(("PaOSX_OpenInputDevice: Need converter for buffer size = %d\n", pahsc->framesPerHostBuffer)); + needConverter = 1; } - - // setup conversion procedure + + // setup PA conversion procedure result = PaConvert_SetupInput( past, paFloat32 ); + + if( needConverter ) + { + AudioStreamBasicDescription sourceStreamFormat, destStreamFormat; + + /* Get source device format */ + dataSize = sizeof(sourceStreamFormat); + err = AudioDeviceGetProperty(pahsc->input.audioDeviceID, 0, IS_INPUT, + kAudioDevicePropertyStreamFormat, &dataSize, &sourceStreamFormat); + if( err != noErr ) + { + PRINT_ERR("PaOSX_OpenInputDevice: Could not get input device format", err); + sSavedHostError = err; + return paHostError; + } + deviceRate = sourceStreamFormat.mSampleRate; + DBUG(("PaOSX_OpenInputDevice: current device sample rate = %f\n", deviceRate )); + + /* Set target user format. */ + destStreamFormat = sourceStreamFormat; + destStreamFormat.mSampleRate = past->past_SampleRate; // sample rate of the user synthesis code + destStreamFormat.mChannelsPerFrame = past->past_NumInputChannels; // the number of channels in each frame + + err = AudioConverterNew ( + &sourceStreamFormat, + &destStreamFormat, + &pahsc->input.converter); + if( err != noErr ) + { + PRINT_ERR("Could not create input format converter", err); + sSavedHostError = err; + return paHostError; + } + } + + /* Allocate FIFO between Device callback and Converter callback so that device can push data + * and converter can pull data. + */ + if( needConverter || pahsc->usingSecondDevice ) + { + double sampleRateRatio; + long minSize, numBytes; + + /* Allocate an input buffer because we need it between the user callback and the converter. */ + pahsc->input.converterBuffer = PaHost_AllocateFastMemory( pahsc->input.bytesPerUserNativeBuffer ); + if( pahsc->input.converterBuffer == NULL ) + { + return paInsufficientMemory; + } - return result; - -error: + sampleRateRatio = deviceRate / past->past_SampleRate; + minSize = pahsc->input.bytesPerUserNativeBuffer * 4 * sampleRateRatio; + numBytes = RoundUpToNextPowerOf2( minSize ); + DBUG(("PaOSX_OpenInputDevice: FIFO numBytes = %ld\n", numBytes)); + pahsc->ringBufferData = PaHost_AllocateFastMemory( numBytes ); + if( pahsc->ringBufferData == NULL ) + { + return paInsufficientMemory; + } + RingBuffer_Init( &pahsc->ringBuffer, numBytes, pahsc->ringBufferData ); + // make it look full at beginning + RingBuffer_AdvanceWriteIndex( &pahsc->ringBuffer, numBytes ); + } + return result; } /*******************************************************************/ -PaError PaHost_OpenOutputStream( internalPortAudioStream *past ) +static PaError PaOSX_OpenOutputDevice( internalPortAudioStream *past ) { PaHostSoundControl *pahsc; const PaHostDeviceInfo *hostDeviceInfo; PaError result = paNoError; - UInt32 bytesPerHostBuffer; UInt32 dataSize; OSStatus err = noErr; - int bytesPerOutputFrame; + int needConverter = 0; + DBUG(("PaOSX_OpenOutputDevice: -------------\n")); + pahsc = (PaHostSoundControl *) past->past_DeviceData; DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_OutputDeviceID)); @@ -866,101 +1165,141 @@ PaError PaHost_OpenOutputStream( internalPortAudioStream *past ) { return paInvalidDeviceId; } + hostDeviceInfo = &sDeviceInfos[past->past_OutputDeviceID]; + + //PaOSX_DumpDeviceInfo( pahsc->output.audioDeviceID, IS_OUTPUT ); + PaOSX_FixVolumeScalars( pahsc->output.audioDeviceID, IS_OUTPUT, + hostDeviceInfo->paInfo.maxOutputChannels, 0.1, 0.9 ); + /* Try to set sample rate. */ - result = PaHost_SetSampleRate( hostDeviceInfo->audioDeviceID, IS_OUTPUT, past->past_SampleRate ); - if( result != paNoError ) return result; + result = PaOSX_SetSampleRate( pahsc->output.audioDeviceID, IS_OUTPUT, past->past_SampleRate ); + if( result != paNoError ) + { + DBUG(("PaOSX_OpenOutputDevice: Need converter for sample rate = %f\n", past->past_SampleRate )); + needConverter = 1; + result = paNoError; + } + else + { + DBUG(("PaOSX_OpenOutputDevice: successfully set sample rate to %f\n", past->past_SampleRate )); + } - if( past->past_NumOutputChannels != hostDeviceInfo->paInfo.maxOutputChannels ) + if( past->past_NumOutputChannels > hostDeviceInfo->paInfo.maxOutputChannels ) + { + return paInvalidChannelCount; /* Too many channels! */ + } + else { -#if 1 - return paInvalidChannelCount; // FIXME - how support mono? -#else /* Attempt to set number of channels. */ AudioStreamBasicDescription formatDesc; OSStatus err; memset( &formatDesc, 0, sizeof(AudioStreamBasicDescription) ); formatDesc.mChannelsPerFrame = past->past_NumOutputChannels; - err = AudioDeviceSetProperty( hostDeviceInfo->audioDeviceID, 0, 0, + err = AudioDeviceSetProperty( pahsc->output.audioDeviceID, 0, 0, IS_OUTPUT, kAudioDevicePropertyStreamFormat, sizeof(formatDesc), &formatDesc); - if (err != kAudioHardwareNoError) { - result = paInvalidChannelCount; - goto error; + DBUG(("PaOSX_OpenOutputDevice: Need converter for num channels.\n")); + needConverter = 1; } -#endif } - // calculate buffer sizes in bytes - bytesPerOutputFrame = Pa_GetSampleSize(paFloat32) * past->past_NumOutputChannels; - pahsc->pahsc_BytesPerUserNativeOutputBuffer = past->past_FramesPerUserBuffer * bytesPerOutputFrame; - bytesPerHostBuffer = pahsc->pahsc_FramesPerHostBuffer * bytesPerOutputFrame; - - // Change the bufferSize of the device! Is this per device or just for our stream? - dataSize = sizeof(bytesPerHostBuffer); - err = AudioDeviceSetProperty( hostDeviceInfo->audioDeviceID, 0, 0, IS_OUTPUT, - kAudioDevicePropertyBufferSize, dataSize, &bytesPerHostBuffer); + /* Change the I/O bufferSize of the device. */ + dataSize = sizeof(pahsc->framesPerHostBuffer); + err = AudioDeviceSetProperty( pahsc->output.audioDeviceID, 0, 0, IS_OUTPUT, + kAudioDevicePropertyBufferFrameSize, dataSize, + &pahsc->framesPerHostBuffer); if( err != noErr ) { - ERR_RPT(("Could not force buffer size!")); - result = paHostError; - goto error; + DBUG(("PaOSX_OpenOutputDevice: Need converter for buffer size = %d\n", pahsc->framesPerHostBuffer)); + needConverter = 1; } - + // setup conversion procedure result = PaConvert_SetupOutput( past, paFloat32 ); - return result; + if( needConverter ) + { + AudioStreamBasicDescription sourceStreamFormat, destStreamFormat; + DBUG(("PaOSX_OpenOutputDevice: using AUConverter!\n")); + /* Get target device format */ + dataSize = sizeof(destStreamFormat); + err = AudioDeviceGetProperty(pahsc->output.audioDeviceID, 0, IS_OUTPUT, + kAudioDevicePropertyStreamFormat, &dataSize, &destStreamFormat); + if( err != noErr ) + { + PRINT_ERR("PaOSX_OpenOutputDevice: Could not get output device format", err); + sSavedHostError = err; + return paHostError; + } -error: + /* Set source user format. */ + sourceStreamFormat = destStreamFormat; + sourceStreamFormat.mSampleRate = past->past_SampleRate; // sample rate of the user synthesis code + sourceStreamFormat.mChannelsPerFrame = past->past_NumOutputChannels; // the number of channels in each frame + + /* Allocate an output buffer because we need it between the user callback and the converter. */ + pahsc->output.converterBuffer = PaHost_AllocateFastMemory( pahsc->output.bytesPerUserNativeBuffer ); + err = AudioConverterNew ( + &sourceStreamFormat, + &destStreamFormat, + &pahsc->output.converter); + if( err != noErr ) + { + PRINT_ERR("Could not create output format converter", err); + sSavedHostError = err; + return paHostError; + } + } + return result; } -/*******************************************************************/ -PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past ) -{ - PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; - return pahsc->pahsc_FramesPerHostBuffer; -} - - /******************************************************************* * Determine how many User Buffers we can put into our CoreAudio stream buffer. * Uses: * past->past_FramesPerUserBuffer, etc. * Sets: * past->past_NumUserBuffers -* pahsc->pahsc_UserBuffersPerHostBuffer -* pahsc->pahsc_FramesPerHostBuffer +* pahsc->framesPerHostBuffer +* pahsc->input.bytesPerUserNativeBuffer +* pahsc->output.bytesPerUserNativeBuffer */ -static void PaHost_CalcHostBufferSize( internalPortAudioStream *past ) +static void PaOSX_CalcHostBufferSize( internalPortAudioStream *past ) { PaHostSoundControl *pahsc = ( PaHostSoundControl *)past->past_DeviceData; - unsigned int minNumUserBuffers; - // Determine number of user buffers based on minimum latency. - minNumUserBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); - // Compare to user requested number in user wants more than the minimum. - past->past_NumUserBuffers = ( minNumUserBuffers > past->past_NumUserBuffers ) ? - minNumUserBuffers : past->past_NumUserBuffers; - DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); + // Determine number of user buffers based strictly on minimum reasonable buffer size. + // We ignore the Pa_OpenStream numBuffer parameter because CoreAudio has a big + // mix buffer and handles latency automatically. + past->past_NumUserBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); - // For CoreAudio, we only have one Host buffer, so... - pahsc->pahsc_UserBuffersPerHostBuffer = past->past_NumUserBuffers; // Calculate size of CoreAudio buffer. - pahsc->pahsc_FramesPerHostBuffer = past->past_FramesPerUserBuffer * past->past_NumUserBuffers; + pahsc->framesPerHostBuffer = past->past_FramesPerUserBuffer * past->past_NumUserBuffers; - DBUG(("PaHost_CalcNumHostBuffers: pahsc_UserBuffersPerHostBuffer = %d\n", pahsc->pahsc_UserBuffersPerHostBuffer )); - DBUG(("PaHost_CalcNumHostBuffers: pahsc_FramesPerHostBuffer = %d\n", pahsc->pahsc_FramesPerHostBuffer )); + // calculate buffer sizes in bytes + pahsc->input.bytesPerUserNativeBuffer = past->past_FramesPerUserBuffer * + Pa_GetSampleSize(paFloat32) * past->past_NumInputChannels; + pahsc->output.bytesPerUserNativeBuffer = past->past_FramesPerUserBuffer * + Pa_GetSampleSize(paFloat32) * past->past_NumOutputChannels; + + DBUG(("PaOSX_CalcNumHostBuffers: past_NumUserBuffers = %ld\n", past->past_NumUserBuffers )); + DBUG(("PaOSX_CalcNumHostBuffers: framesPerHostBuffer = %d\n", pahsc->framesPerHostBuffer )); + DBUG(("PaOSX_CalcNumHostBuffers: input.bytesPerUserNativeBuffer = %d\n", pahsc->input.bytesPerUserNativeBuffer )); + DBUG(("PaOSX_CalcNumHostBuffers: output.bytesPerUserNativeBuffer = %d\n", pahsc->output.bytesPerUserNativeBuffer )); } -/*******************************************************************/ +/*****************************************************************************/ +/************** Internal Host API ********************************************/ +/*****************************************************************************/ PaError PaHost_OpenStream( internalPortAudioStream *past ) { PaError result = paNoError; PaHostSoundControl *pahsc; + Boolean useInput; + Boolean useOutput; /* Allocate and initialize host data. */ pahsc = (PaHostSoundControl *) malloc(sizeof(PaHostSoundControl)); @@ -971,43 +1310,53 @@ PaError PaHost_OpenStream( internalPortAudioStream *past ) } memset( pahsc, 0, sizeof(PaHostSoundControl) ); past->past_DeviceData = (void *) pahsc; + pahsc->primaryDeviceID = kAudioDeviceUnknown; + pahsc->input.audioDeviceID = kAudioDeviceUnknown; + pahsc->output.audioDeviceID = kAudioDeviceUnknown; + + PaOSX_CalcHostBufferSize( past ); + + /* Setup constants for CPU load measurement. */ + pahsc->inverseMicrosPerHostBuffer = past->past_SampleRate / (1000000.0 * pahsc->framesPerHostBuffer); - // If we are using both input and out, then they must be on the same CoreAudio device, - // until we implement a FIFO between two devices. - if( (past->past_OutputDeviceID != paNoDevice) && (past->past_InputDeviceID != paNoDevice) ) + useOutput = (past->past_OutputDeviceID != paNoDevice) && (past->past_NumOutputChannels > 0); + useInput = (past->past_InputDeviceID != paNoDevice) && (past->past_NumInputChannels > 0); + + // Set device IDs + if( useOutput ) { - AudioDeviceID inputID = sDeviceInfos[past->past_InputDeviceID].audioDeviceID; - AudioDeviceID outputID = sDeviceInfos[past->past_OutputDeviceID].audioDeviceID; - if( inputID != outputID ) + pahsc->output.audioDeviceID = sDeviceInfos[past->past_OutputDeviceID].audioDeviceID; + pahsc->primaryDeviceID = pahsc->output.audioDeviceID; + if( useInput ) { - ERR_RPT(("PortAudio: input and output must use same CoreAudio device!\n")); - return paInvalidDeviceId; + pahsc->input.audioDeviceID = sDeviceInfos[past->past_InputDeviceID].audioDeviceID; + if( pahsc->input.audioDeviceID != pahsc->primaryDeviceID ) + { + pahsc->usingSecondDevice = TRUE; // Use two separate devices! + } } } - - PaHost_CalcHostBufferSize( past ); - + else { - int msecLatency = (int) ((PaHost_GetTotalBufferFrames(past) * 1000) / past->past_SampleRate); - PRINT(("PortAudio on OS X - Latency = %d frames, %d msec\n", PaHost_GetTotalBufferFrames(past), msecLatency )); + /* Just input, not output. */ + pahsc->input.audioDeviceID = sDeviceInfos[past->past_InputDeviceID].audioDeviceID; + pahsc->primaryDeviceID = pahsc->input.audioDeviceID; } + DBUG(("outputDeviceID = %ld\n", pahsc->output.audioDeviceID )); + DBUG(("inputDeviceID = %ld\n", pahsc->input.audioDeviceID )); + DBUG(("primaryDeviceID = %ld\n", pahsc->primaryDeviceID )); - /* Setup constants for CPU load measurement. */ - pahsc->pahsc_InverseMicrosPerHostBuffer = past->past_SampleRate / (1000000.0 * pahsc->pahsc_FramesPerHostBuffer); - /* ------------------ OUTPUT */ - if( (past->past_OutputDeviceID != paNoDevice) && (past->past_NumOutputChannels > 0) ) + if( useOutput ) { - pahsc->pahsc_AudioDeviceID = sDeviceInfos[past->past_OutputDeviceID].audioDeviceID; - result = PaHost_OpenOutputStream( past ); + result = PaOSX_OpenOutputDevice( past ); if( result < 0 ) goto error; } /* ------------------ INPUT */ - if( (past->past_InputDeviceID != paNoDevice) && (past->past_NumInputChannels > 0) ) + if( useInput ) { - pahsc->pahsc_AudioDeviceID = sDeviceInfos[past->past_InputDeviceID].audioDeviceID; - result = PaHost_OpenInputStream( past ); + result = PaOSX_OpenInputDevice( past ); if( result < 0 ) goto error; } @@ -1041,21 +1390,49 @@ PaError PaHost_StartEngine( internalPortAudioStream *past ) past->past_StopNow = 0; past->past_IsActive = 1; +/* If full duplex and using two separate devices then start input device. */ + if( pahsc->usingSecondDevice ) + { + // Associate an IO proc with the device and pass a pointer to the audio data context + err = AudioDeviceAddIOProc(pahsc->input.audioDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioInputCallback, past); + if (err != noErr) + { + PRINT_ERR("PaHost_StartEngine: AudioDeviceAddIOProc secondary failed", err ); + goto error; + } + + // start playing sound through the device + err = AudioDeviceStart(pahsc->input.audioDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioInputCallback); + if (err != noErr) + { + PRINT_ERR("PaHost_StartEngine: AudioDeviceStart secondary failed", err ); + PRINT(("The program may succeed if you run it again!\n")); + goto error; + } + } + // Associate an IO proc with the device and pass a pointer to the audio data context - err = AudioDeviceAddIOProc(pahsc->pahsc_AudioDeviceID, (AudioDeviceIOProc)appIOProc, past); - if (err != noErr) goto error; + err = AudioDeviceAddIOProc(pahsc->primaryDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioIOCallback, past); + if (err != noErr) + { + PRINT_ERR("PaHost_StartEngine: AudioDeviceAddIOProc primary failed", err ); + goto error; + } // start playing sound through the device - err = AudioDeviceStart(pahsc->pahsc_AudioDeviceID, (AudioDeviceIOProc)appIOProc); - if (err != noErr) goto error; + err = AudioDeviceStart(pahsc->primaryDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioIOCallback); + if (err != noErr) + { + PRINT_ERR("PaHost_StartEngine: AudioDeviceStart primary failed", err ); + PRINT(("The program may succeed if you run it again!\n")); + goto error; + } + return result; -#if PA_TRACE_START_STOP - AddTraceMessage( "PaHost_StartEngine: TimeSlice() returned ", result ); -#endif - error: - return paHostError; // FIXME - save host error + sSavedHostError = err; + return paHostError; } /*************************************************************************/ @@ -1077,16 +1454,29 @@ PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) #endif // FIXME - we should ask proc to stop instead of stopping abruptly - err = AudioDeviceStop(pahsc->pahsc_AudioDeviceID, (AudioDeviceIOProc)appIOProc); - if (err != noErr) goto Bail; + err = AudioDeviceStop(pahsc->primaryDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioIOCallback); + if (err != noErr) + { + goto error; + } - err = AudioDeviceRemoveIOProc(pahsc->pahsc_AudioDeviceID, (AudioDeviceIOProc)appIOProc); - if (err != noErr) goto Bail; + err = AudioDeviceRemoveIOProc(pahsc->primaryDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioIOCallback); + if (err != noErr) goto error; + +/* If full duplex and using two separate devices then start input device. */ + if( pahsc->usingSecondDevice ) + { + err = AudioDeviceStop(pahsc->input.audioDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioInputCallback); + if (err != noErr) goto error; + err = AudioDeviceRemoveIOProc(pahsc->input.audioDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioInputCallback); + if (err != noErr) goto error; + } return paNoError; -Bail: - return paHostError; // FIXME - save err +error: + sSavedHostError = err; + return paHostError; } /*************************************************************************/ @@ -1109,70 +1499,47 @@ PaError PaHost_CloseStream( internalPortAudioStream *past ) if( past == NULL ) return paBadStreamPtr; pahsc = (PaHostSoundControl *) past->past_DeviceData; if( pahsc == NULL ) return paNoError; + + //PaOSX_DumpDeviceInfo( sDeviceInfos[past->past_OutputDeviceID].audioDeviceID, IS_OUTPUT ); #if PA_TRACE_START_STOP AddTraceMessage( "PaHost_CloseStream: pahsc_HWaveOut ", (int) pahsc->pahsc_HWaveOut ); #endif + if( pahsc->output.converterBuffer != NULL ) + { + PaHost_FreeFastMemory( pahsc->output.converterBuffer, pahsc->output.bytesPerUserNativeBuffer ); + } + if( pahsc->input.converterBuffer != NULL ) + { + PaHost_FreeFastMemory( pahsc->input.converterBuffer, pahsc->input.bytesPerUserNativeBuffer ); + } + if( pahsc->ringBufferData != NULL ) + { + PaHost_FreeFastMemory( pahsc->ringBufferData, pahsc->ringBuffer.bufferSize ); + } + if( pahsc->output.converter != NULL ) + { + verify_noerr(AudioConverterDispose (pahsc->output.converter)); + } + if( pahsc->input.converter != NULL ) + { + verify_noerr(AudioConverterDispose (pahsc->input.converter)); + } + free( pahsc ); past->past_DeviceData = NULL; return paNoError; } -/************************************************************************* -** Determine minimum number of buffers required for this host based -** on minimum latency. Latency can be optionally set by user by setting -** an environment variable. For example, to set latency to 200 msec, put: -** -** set PA_MIN_LATENCY_MSEC=200 -** -** in the cshrc file. -*/ -#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") - -#if 1 -int Pa_GetMinNumBuffers( int framesPerBuffer, double framesPerSecond ) -{ - int minBuffers; - int denominator; - int minLatencyMsec = PA_MIN_LATENCY_MSEC; - char *minLatencyText = getenv(PA_LATENCY_ENV_NAME); - if( minLatencyText != NULL ) - { - PRINT(("PA_MIN_LATENCY_MSEC = %s\n", minLatencyText )); - minLatencyMsec = atoi( minLatencyText ); - if( minLatencyMsec < 1 ) minLatencyMsec = 1; - else if( minLatencyMsec > 5000 ) minLatencyMsec = 5000; - } - - denominator = 1000.0 * framesPerBuffer; - minBuffers = (int) (((minLatencyMsec * framesPerSecond) + denominator - 1) / denominator ); - if( minBuffers < 1 ) minBuffers = 1; - return minBuffers; -} -#else -/************************************************************************* -** Determine minimum number of buffers required for this host based -** on minimum latency. +/********************************************************************** +** Initialize Host dependant part of API. */ -int Pa_GetMinNumBuffers( int framesPerUserBuffer, double sampleRate ) +PaError PaHost_Init( void ) { - int minUserBuffers; - int minFramesPerHostBuffer; - - // Calculate minimum and maximum sizes based on timing and sample rate. - minFramesPerHostBuffer = (int) (PA_MIN_LATENCY_MSEC * sampleRate / 1000.0); - // round up to nearest multiple of 8 - minFramesPerHostBuffer = (minFramesPerHostBuffer + 7) & ~7; - DBUG(("Pa_GetMinNumBuffers: minFramesPerHostBuffer = %d\n", minFramesPerHostBuffer )); - - minUserBuffers = (minFramesPerHostBuffer + framesPerUserBuffer - 1) / framesPerUserBuffer; - if( minUserBuffers < 1 ) minUserBuffers = 1; - - return minUserBuffers; + return PaOSX_MaybeQueryDevices(); } -#endif /************************************************************************* ** Cleanup device info. @@ -1185,8 +1552,10 @@ PaError PaHost_Term( void ) { for( i=0; ipast_IsActive; } +/*******************************************************************/ +PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + return pahsc->framesPerHostBuffer; +} + +/*****************************************************************************/ +/************** External User API ********************************************/ +/*****************************************************************************/ + +/********************************************************************** +** Query devices and use result. +*/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + PaError result = PaOSX_MaybeQueryDevices(); + if( result < 0 ) return result; + return sDefaultInputDeviceID; +} + +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + PaError result = PaOSX_MaybeQueryDevices(); + if( result < 0 ) return result; + return sDefaultOutputDeviceID; +} + + +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Because CoreAudio manages latency, this just sets +** a reasonable small buffer size. +*/ +int Pa_GetMinNumBuffers( int framesPerBuffer, double framesPerSecond ) +{ + int minBuffers; + double denominator; + int minLatencyMsec = PA_MIN_LATENCY_MSEC; + denominator = 1000.0 * framesPerBuffer; + minBuffers = (int) (((minLatencyMsec * framesPerSecond) + denominator - 1) / denominator ); + if( minBuffers < 1 ) minBuffers = 1; + return minBuffers; +} + +/*************************************************************************/ +void Pa_Sleep( long msec ) +{ + usleep( msec * 1000 ); +} + /*************************************************************************/ PaTimestamp Pa_StreamTime( PortAudioStream *stream ) { @@ -1252,10 +1659,37 @@ PaTimestamp Pa_StreamTime( PortAudioStream *stream ) if( past == NULL ) return paBadStreamPtr; pahsc = (PaHostSoundControl *) past->past_DeviceData; - AudioDeviceGetCurrentTime(pahsc->pahsc_AudioDeviceID, &timeStamp); + AudioDeviceGetCurrentTime(pahsc->primaryDeviceID, &timeStamp); streamTime = ( timeStamp.mFlags & kAudioTimeStampSampleTimeValid) ? timeStamp.mSampleTime : past->past_FrameCount; return streamTime; } + +/************************************************************************************/ +long Pa_GetHostError() +{ + return sSavedHostError; +} + +/*************************************************************************/ +int Pa_CountDevices() +{ + if( sNumPaDevices <= 0 ) Pa_Initialize(); + return sNumPaDevices; +} + +/************************************************************************* +** PaDeviceInfo structures have already been created +** so just return the pointer. +** +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + if( id < 0 || id >= sNumPaDevices ) + return NULL; + + return &sDeviceInfos[id].paInfo; +} + -- cgit v1.2.1