From 58d42b6fbe3277ec2de962353ee0eed4afcca627 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Fri, 13 Oct 2006 17:57:42 +0000 Subject: Updated PortAudio to latest version for Mac git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@770 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/src/pjmedia/portaudio/notes.txt | 145 ++ pjmedia/src/pjmedia/portaudio/pa_mac_core.c | 2581 ++++++++++++++------ pjmedia/src/pjmedia/portaudio/pa_mac_core.h | 118 + .../src/pjmedia/portaudio/pa_mac_core_blocking.c | 564 +++++ .../src/pjmedia/portaudio/pa_mac_core_blocking.h | 133 + .../src/pjmedia/portaudio/pa_mac_core_internal.h | 162 ++ pjmedia/src/pjmedia/portaudio/pa_mac_core_old.c | 914 +++++++ .../src/pjmedia/portaudio/pa_mac_core_utilities.c | 612 +++++ .../src/pjmedia/portaudio/pa_mac_core_utilities.h | 215 ++ pjmedia/src/pjmedia/portaudio/pa_mac_hostapis.c | 18 +- pjmedia/src/pjmedia/portaudio/ringbuffer.c | 83 +- pjmedia/src/pjmedia/portaudio/ringbuffer.h | 30 +- 12 files changed, 4837 insertions(+), 738 deletions(-) create mode 100644 pjmedia/src/pjmedia/portaudio/notes.txt create mode 100644 pjmedia/src/pjmedia/portaudio/pa_mac_core.h create mode 100644 pjmedia/src/pjmedia/portaudio/pa_mac_core_blocking.c create mode 100644 pjmedia/src/pjmedia/portaudio/pa_mac_core_blocking.h create mode 100644 pjmedia/src/pjmedia/portaudio/pa_mac_core_internal.h create mode 100644 pjmedia/src/pjmedia/portaudio/pa_mac_core_old.c create mode 100644 pjmedia/src/pjmedia/portaudio/pa_mac_core_utilities.c create mode 100644 pjmedia/src/pjmedia/portaudio/pa_mac_core_utilities.h (limited to 'pjmedia/src') diff --git a/pjmedia/src/pjmedia/portaudio/notes.txt b/pjmedia/src/pjmedia/portaudio/notes.txt new file mode 100644 index 00000000..ad66f358 --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/notes.txt @@ -0,0 +1,145 @@ +Notes on status of CoreAudio Implementation of PortAudio + +Document Last Updated December 9, 2005 + +There are currently two implementations of PortAudio for Mac Core Audio. + +The original is in pa_mac_core_old.c, and the newer, default implementation +is in pa_mac_core.c. +Only pa_mac_core.c is currently developed and supported as it uses apple's +current core audio technology. To select use the old implementation, replace +pa_mac_core.c with pa_mac_core_old.c (eg. "cp pa_mac_core_auhal.c +pa_mac_core.c"), then run configure and make as usual. + +---------------------------------------- + +Notes on Original implementation: + +by Phil Burk and Darren Gibbs + +Last updated March 20, 2002 + +WHAT WORKS + +Output with very low latency, <10 msec. +Half duplex input or output. +Full duplex on the same CoreAudio device. +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 +Built-In audio is typically one CoreAudio device. + +Mono doesn't work. + +DEVICE MAPPING + +CoreAudio devices can support both input and output. But the sample +rates supported may be different. So we have map one or two PortAudio +device to each CoreAudio device depending on whether it supports +input, output or both. + +When we query devices, we first get a list of CoreAudio devices. Then +we scan the list and add a PortAudio device for each CoreAudio device +that supports input. Then we make a scan for output devices. + +------------------------------------------- + +Notes on Newer/Default AUHAL implementation: + +by Bjorn Roche + +Last Updated December 9, 2005 + +Principle of Operation: + +This implementation uses AUHAL for audio I/O. To some extent, it also +operates at the "HAL" Layer, though this behavior can be limited by +platform specific flags (see pa_mac_core.h for details). The default +settings should be reasonable: they don't change the SR of the device and +don't cause interruptions if other devices are using the device. + +Major Software Elements Used: Apple's HAL AUs provide output SR +conversion transparently, however, only on output, so this +implementation uses AudioConverters to convert the sample rate on input. +A PortAudio ring buffer is used to buffer input when sample rate +conversion is required or when separate audio units are used for duplex +IO. Finally, a PortAudio buffer processor is used to convert formats and +provide additional buffers if needed. Internally, interleaved floating +point data streams are used exclusively - the audio unit converts from +the audio hardware's native format to interleaved float PCM and +PortAudio's Buffer processor is used for conversion to user formats. + +Simplex Input: Simplex input uses a single callback. If sample rate +conversion is required, a ring buffer and AudioConverter are used as +well. + +Simplex output: Simplex output uses a single callback. No ring buffer or +audio converter is used because AUHAL does its own output SR conversion. + +Duplex, one device (no SR conversion): When one device is used, a single +callback is used. This achieves very low latency. + +Duplex, separate devices or SR conversion: When SR conversion is +required, data must be buffered before it is converted and data is not +always available at the same times on input and output, so SR conversion +requires the same treatment as separate devices. The input callback +reads data and puts it in the ring buffer. The output callback reads the +data off the ring buffer, into an audio converter and finally to the +buffer processor. + +Platform Specific Options: + +By using the flags in pa_mac_core.h, the user may specify several options. +For example, the user can specify the sample-rate conversion quality, and +the extent to which PA will attempt to "play nice" and to what extent it +will interrupt other apps to improve performance. For example, if 44100 Hz +sample rate is requested but the device is set at 48000 Hz, PA can either +change the device for optimal playback ("Pro" mode), which may interrupt +other programs playing back audio, or simple use a sample-rate coversion, +which allows for friendlier sharing of the device ("Play Nice" mode). + + +Known issues: + +- Latency: Latency settings are ignored in most cases. Exceptions are when +doing I/O between different devices and as a hint for selecting a realtively +low or relatively high latency in conjunction with +paHostFramesPerBufferUnspecified. Latency settings are always automatically +bound to "safe" values, however, so setting extreme values here should not be +an issue. + +- Buffer Size: paHostFramesPerBufferUnspecified and specific host buffer sizes +are supported. paHostFramesPerBufferUnspecified works best in "pro" mode, +where the buffer size and sample rate of the audio device is most likely +to match the expected values. + +- Timing info. It reports on stream time, but I'm probably doing something +wrong since patest_sine_time often reports negative latency numbers. + +- xrun detection: The only xrun detection performed is when reading +and writing the ring buffer. There is probably more that can be done. + +- abort/stop issues: stopping a stream is always a complete operation, +but latency should be low enough to make the lack of a separate abort +unnecessary. Apple clarifies its AudioOutputUnitStop() call here: +http://lists.apple.com/archives/coreaudio-api/2005/Dec/msg00055.html + +- blocking interface: Not implemented. + +- multichannel: It has been tested successfully on multichannel hardware +from MOTU: traveler and 896HD. + +- sample rate conversion quality: By default, SR conversion is the maximum +available. This can be tweaked using flags pa_mac_core.h. Note that the AU +render quyality property is used to set the sample rat conversion quality +as "documented" here: +http://lists.apple.com/archives/coreaudio-api/2004/Jan/msg00141.html + +- x86: I haven't tested it on an x86 Mac myself, but users have reported +being able to comiple and run it. diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_core.c b/pjmedia/src/pjmedia/portaudio/pa_mac_core.c index ce55b477..2b2b9cc1 100644 --- a/pjmedia/src/pjmedia/portaudio/pa_mac_core.c +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_core.c @@ -1,13 +1,24 @@ /* - * $Id$ - * pa_mac_core.c - * Implementation of PortAudio for Mac OS X CoreAudio - * + * Implementation of the PortAudio API for Apple AUHAL + * * PortAudio Portable Real-Time Audio Library * Latest Version at: http://www.portaudio.com * - * Authors: Ross Bencina and Phil Burk - * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -20,10 +31,6 @@ * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * Any person wishing to distribute modifications to the Software is - * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. - * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. @@ -31,603 +38,956 @@ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. */ -#include -#include -#include -#include -#include -#include - -#include "portaudio.h" -#include "pa_trace.h" -#include "pa_util.h" -#include "pa_allocation.h" -#include "pa_hostapi.h" -#include "pa_stream.h" -#include "pa_cpuload.h" -#include "pa_process.h" - -// ===== constants ===== - -// ===== structs ===== -#pragma mark structs - -// PaMacCoreHostApiRepresentation - host api datastructure specific to this implementation -typedef struct PaMacCore_HAR -{ - PaUtilHostApiRepresentation inheritedHostApiRep; - PaUtilStreamInterface callbackStreamInterface; - PaUtilStreamInterface blockingStreamInterface; - - PaUtilAllocationGroup *allocations; - AudioDeviceID *macCoreDeviceIds; -} -PaMacCoreHostApiRepresentation; +/** + @file pa_mac_core + @ingroup hostapi_src + @author Bjorn Roche + @brief AUHAL implementation of PortAudio +*/ -typedef struct PaMacCore_DI -{ - PaDeviceInfo inheritedDeviceInfo; -} -PaMacCoreDeviceInfo; +/* FIXME: not all error conditions call PaUtil_SetLastHostErrorInfo() + * PaMacCore_SetError() will do this. + */ -// PaMacCoreStream - a stream data structure specifically for this implementation -typedef struct PaMacCore_S -{ - PaUtilStreamRepresentation streamRepresentation; - PaUtilCpuLoadMeasurer cpuLoadMeasurer; - PaUtilBufferProcessor bufferProcessor; - - int primeStreamUsingCallback; - - AudioDeviceID inputDevice; - AudioDeviceID outputDevice; - - // Processing thread management -------------- -// HANDLE abortEvent; -// HANDLE processingThread; -// DWORD processingThreadId; - - char throttleProcessingThreadOnOverload; // 0 -> don't throtte, non-0 -> throttle - int processingThreadPriority; - int highThreadPriority; - int throttledThreadPriority; - unsigned long throttledSleepMsecs; - - int isStopped; - volatile int isActive; - volatile int stopProcessing; // stop thread once existing buffers have been returned - volatile int abortProcessing; // stop thread immediately - -// DWORD allBuffersDurationMs; // used to calculate timeouts -} -PaMacCoreStream; +#include "pa_mac_core_internal.h" -// Data needed by the CoreAudio callback functions -typedef struct PaMacCore_CD -{ - PaMacCoreStream *stream; - PaStreamCallback *callback; - void *userData; - PaUtilConverter *inputConverter; - PaUtilConverter *outputConverter; - void *inputBuffer; - void *outputBuffer; - int inputChannelCount; - int outputChannelCount; - PaSampleFormat inputSampleFormat; - PaSampleFormat outputSampleFormat; - PaUtilTriangularDitherGenerator *ditherGenerator; -} -PaMacClientData; +#include /* strlen(), memcmp() etc. */ -// ===== CoreAudio-PortAudio bridge functions ===== -#pragma mark CoreAudio-PortAudio bridge functions +#include "pa_mac_core.h" +#include "pa_mac_core_utilities.h" +#include "pa_mac_core_blocking.h" -// Maps CoreAudio OSStatus codes to PortAudio PaError codes -static PaError conv_err(OSStatus error) -{ - PaError result; - - switch (error) { - case kAudioHardwareNoError: - result = paNoError; break; - case kAudioHardwareNotRunningError: - result = paInternalError; break; - case kAudioHardwareUnspecifiedError: - result = paInternalError; break; - case kAudioHardwareUnknownPropertyError: - result = paInternalError; break; - case kAudioHardwareBadPropertySizeError: - result = paInternalError; break; - case kAudioHardwareIllegalOperationError: - result = paInternalError; break; - case kAudioHardwareBadDeviceError: - result = paInvalidDevice; break; - case kAudioHardwareBadStreamError: - result = paBadStreamPtr; break; -// case kAudioHardwareUnsupportedOperationError: -// result = paInternalError; break; - case kAudioDeviceUnsupportedFormatError: - result = paSampleFormatNotSupported; break; - case kAudioDevicePermissionsError: - result = paDeviceUnavailable; break; - default: - result = paInternalError; - } - - return result; -} -static AudioStreamBasicDescription *InitializeStreamDescription(const PaStreamParameters *parameters, double sampleRate) -{ - struct AudioStreamBasicDescription *streamDescription = PaUtil_AllocateMemory(sizeof(AudioStreamBasicDescription)); - streamDescription->mSampleRate = sampleRate; - streamDescription->mFormatID = kAudioFormatLinearPCM; - streamDescription->mFormatFlags = 0; - streamDescription->mFramesPerPacket = 1; - - if (parameters->sampleFormat & paNonInterleaved) { - streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsNonInterleaved; - streamDescription->mChannelsPerFrame = 1; - streamDescription->mBytesPerFrame = Pa_GetSampleSize(parameters->sampleFormat); - streamDescription->mBytesPerPacket = Pa_GetSampleSize(parameters->sampleFormat); - } - else { - streamDescription->mChannelsPerFrame = parameters->channelCount; - } - - streamDescription->mBytesPerFrame = Pa_GetSampleSize(parameters->sampleFormat) * streamDescription->mChannelsPerFrame; - streamDescription->mBytesPerPacket = streamDescription->mBytesPerFrame * streamDescription->mFramesPerPacket; - - if (parameters->sampleFormat & paFloat32) { - streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsFloat; - streamDescription->mBitsPerChannel = 32; - } - else if (parameters->sampleFormat & paInt32) { - streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; - streamDescription->mBitsPerChannel = 32; - } - else if (parameters->sampleFormat & paInt24) { - streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; - streamDescription->mBitsPerChannel = 24; - } - else if (parameters->sampleFormat & paInt16) { - streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; - streamDescription->mBitsPerChannel = 16; - } - else if (parameters->sampleFormat & paInt8) { - streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; - streamDescription->mBitsPerChannel = 8; - } - else if (parameters->sampleFormat & paInt32) { - streamDescription->mBitsPerChannel = 8; - } - - return streamDescription; -} -static PaStreamCallbackTimeInfo *InitializeTimeInfo(const AudioTimeStamp* now, const AudioTimeStamp* inputTime, const AudioTimeStamp* outputTime) +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" { - PaStreamCallbackTimeInfo *timeInfo = PaUtil_AllocateMemory(sizeof(PaStreamCallbackTimeInfo)); - - timeInfo->inputBufferAdcTime = inputTime->mSampleTime; - timeInfo->currentTime = now->mSampleTime; - timeInfo->outputBufferDacTime = outputTime->mSampleTime; - - return timeInfo; +#endif /* __cplusplus */ + +PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus } +#endif /* __cplusplus */ -// ===== support functions ===== -#pragma mark support functions +#define RING_BUFFER_ADVANCE_DENOMINATOR (4) -static void CleanUp(PaMacCoreHostApiRepresentation *macCoreHostApi) +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static void setStreamStartTime( PaStream *stream ); +static OSStatus AudioIOProc( void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData ); +static double GetStreamCpuLoad( PaStream* stream ); + +static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi, + PaDeviceInfo *deviceInfo, + AudioDeviceID macCoreDeviceId, + int isInput); + +static PaError OpenAndSetupOneAudioUnit( + const PaStreamParameters *inStreamParams, + const PaStreamParameters *outStreamParams, + const unsigned long requestedFramesPerBuffer, + unsigned long *actualInputFramesPerBuffer, + unsigned long *actualOutputFramesPerBuffer, + const PaMacAUHAL *auhalHostApi, + AudioUnit *audioUnit, + AudioConverterRef *srConverter, + AudioDeviceID *audioDevice, + const double sampleRate, + void *refCon ); + +/* for setting errors. */ +#define PA_AUHAL_SET_LAST_HOST_ERROR( errorCode, errorText ) \ + PaUtil_SetLastHostErrorInfo( paInDevelopment, errorCode, errorText ) + + +/*currently, this is only used in initialization, but it might be modified + to be used when the list of devices changes.*/ +static PaError gatherDeviceInfo(PaMacAUHAL *auhalHostApi) { - if( macCoreHostApi->allocations ) + UInt32 size; + UInt32 propsize; + VVDBUG(("gatherDeviceInfo()\n")); + /* -- free any previous allocations -- */ + if( auhalHostApi->devIds ) + PaUtil_GroupFreeMemory(auhalHostApi->allocations, auhalHostApi->devIds); + auhalHostApi->devIds = NULL; + + /* -- figure out how many devices there are -- */ + AudioHardwareGetPropertyInfo( kAudioHardwarePropertyDevices, + &propsize, + NULL ); + auhalHostApi->devCount = propsize / sizeof( AudioDeviceID ); + + VDBUG( ( "Found %ld device(s).\n", auhalHostApi->devCount ) ); + + /* -- copy the device IDs -- */ + auhalHostApi->devIds = (AudioDeviceID *)PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, + propsize ); + if( !auhalHostApi->devIds ) + return paInsufficientMemory; + AudioHardwareGetProperty( kAudioHardwarePropertyDevices, + &propsize, + auhalHostApi->devIds ); +#ifdef MAC_CORE_VERBOSE_DEBUG { - PaUtil_FreeAllAllocations( macCoreHostApi->allocations ); - PaUtil_DestroyAllocationGroup( macCoreHostApi->allocations ); + int i; + for( i=0; idevCount; ++i ) + printf( "Device %d\t: %ld\n", i, auhalHostApi->devIds[i] ); } - - PaUtil_FreeMemory( macCoreHostApi ); +#endif + + size = sizeof(AudioDeviceID); + auhalHostApi->defaultIn = kAudioDeviceUnknown; + auhalHostApi->defaultOut = kAudioDeviceUnknown; + + /* determine the default device. */ + /* I am not sure how these calls to AudioHardwareGetProperty() + could fail, but in case they do, we use the first available + device as the default. */ + if( 0 != AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, + &size, + &auhalHostApi->defaultIn) ) { + int i; + auhalHostApi->defaultIn = kAudioDeviceUnknown; + VDBUG(("Failed to get default input device from OS.")); + VDBUG((" I will substitute the first available input Device.")); + for( i=0; idevCount; ++i ) { + PaDeviceInfo devInfo; + if( 0 != GetChannelInfo( auhalHostApi, &devInfo, + auhalHostApi->devIds[i], TRUE ) ) + if( devInfo.maxInputChannels ) { + auhalHostApi->defaultIn = auhalHostApi->devIds[i]; + break; + } + } + } + if( 0 != AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, + &size, + &auhalHostApi->defaultOut) ) { + int i; + auhalHostApi->defaultIn = kAudioDeviceUnknown; + VDBUG(("Failed to get default output device from OS.")); + VDBUG((" I will substitute the first available output Device.")); + for( i=0; idevCount; ++i ) { + PaDeviceInfo devInfo; + if( 0 != GetChannelInfo( auhalHostApi, &devInfo, + auhalHostApi->devIds[i], FALSE ) ) + if( devInfo.maxOutputChannels ) { + auhalHostApi->defaultOut = auhalHostApi->devIds[i]; + break; + } + } + } + + VDBUG( ( "Default in : %ld\n", auhalHostApi->defaultIn ) ); + VDBUG( ( "Default out: %ld\n", auhalHostApi->defaultOut ) ); + + return paNoError; } -static PaError GetChannelInfo(PaDeviceInfo *deviceInfo, AudioDeviceID macCoreDeviceId, int isInput) +static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi, + PaDeviceInfo *deviceInfo, + AudioDeviceID macCoreDeviceId, + int isInput) { UInt32 propSize; PaError err = paNoError; UInt32 i; int numChannels = 0; - AudioBufferList *buflist; + AudioBufferList *buflist = NULL; + UInt32 frameLatency; + + VVDBUG(("GetChannelInfo()\n")); + + /* Get the number of channels from the stream configuration. + Fail if we can't get this. */ + + err = ERR(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, NULL)); + if (err) + return err; - err = conv_err(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, NULL)); buflist = PaUtil_AllocateMemory(propSize); - err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, buflist)); - if (!err) { - for (i = 0; i < buflist->mNumberBuffers; ++i) { - numChannels += buflist->mBuffers[i].mNumberChannels; - } - - if (isInput) - deviceInfo->maxInputChannels = numChannels; - else - deviceInfo->maxOutputChannels = numChannels; - - int frameLatency; - propSize = sizeof(UInt32); - err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &frameLatency)); - if (!err) { - double secondLatency = frameLatency / deviceInfo->defaultSampleRate; - if (isInput) { - deviceInfo->defaultLowInputLatency = secondLatency; - deviceInfo->defaultHighInputLatency = secondLatency; - } - else { - deviceInfo->defaultLowOutputLatency = secondLatency; - deviceInfo->defaultHighOutputLatency = secondLatency; - } - } + if( !buflist ) + return paInsufficientMemory; + err = ERR(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, buflist)); + if (err) + goto error; + + for (i = 0; i < buflist->mNumberBuffers; ++i) + numChannels += buflist->mBuffers[i].mNumberChannels; + + if (isInput) + deviceInfo->maxInputChannels = numChannels; + else + deviceInfo->maxOutputChannels = numChannels; + + if (numChannels > 0) /* do not try to retrieve the latency if there is no channels. */ + { + /* Get the latency. Don't fail if we can't get this. */ + /* default to something reasonable */ + deviceInfo->defaultLowInputLatency = .01; + deviceInfo->defaultHighInputLatency = .10; + deviceInfo->defaultLowOutputLatency = .01; + deviceInfo->defaultHighOutputLatency = .10; + propSize = sizeof(UInt32); + err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &frameLatency)); + if (!err) + { + /** FEEDBACK: + * This code was arrived at by trial and error, and some extentive, but not exhaustive + * testing. Sebastien Beaulieu has suggested using + * kAudioDevicePropertyLatency + kAudioDevicePropertySafetyOffset + buffer size instead. + * At the time this code was written, many users were reporting dropouts with audio + * programs that probably used this formula. This was probably + * around 10.4.4, and the problem is probably fixed now. So perhaps + * his formula should be reviewed and used. + * */ + double secondLatency = frameLatency / deviceInfo->defaultSampleRate; + if (isInput) + { + deviceInfo->defaultLowInputLatency = 3 * secondLatency; + deviceInfo->defaultHighInputLatency = 3 * 10 * secondLatency; + } + else + { + deviceInfo->defaultLowOutputLatency = 3 * secondLatency; + deviceInfo->defaultHighOutputLatency = 3 * 10 * secondLatency; + } + } } - PaUtil_FreeMemory(buflist); - + PaUtil_FreeMemory( buflist ); + return paNoError; + error: + PaUtil_FreeMemory( buflist ); return err; } -static PaError InitializeDeviceInfo(PaMacCoreDeviceInfo *macCoreDeviceInfo, AudioDeviceID macCoreDeviceId, PaHostApiIndex hostApiIndex ) +static PaError InitializeDeviceInfo( PaMacAUHAL *auhalHostApi, + PaDeviceInfo *deviceInfo, + AudioDeviceID macCoreDeviceId, + PaHostApiIndex hostApiIndex ) { - PaDeviceInfo *deviceInfo = &macCoreDeviceInfo->inheritedDeviceInfo; - deviceInfo->structVersion = 2; - deviceInfo->hostApi = hostApiIndex; - + Float64 sampleRate; + char *name; PaError err = paNoError; UInt32 propSize; - err = conv_err(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, NULL)); - // FIXME: this allocation should be part of the allocations group - char *name = PaUtil_AllocateMemory(propSize); - err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, name)); - if (!err) { - deviceInfo->name = name; - } - - Float64 sampleRate; + VVDBUG(("InitializeDeviceInfo(): macCoreDeviceId=%ld\n", macCoreDeviceId)); + + memset(deviceInfo, 0, sizeof(deviceInfo)); + + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + /* Get the device name. Fail if we can't get it. */ + err = ERR(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, NULL)); + if (err) + return err; + + name = PaUtil_GroupAllocateMemory(auhalHostApi->allocations,propSize); + if ( !name ) + return paInsufficientMemory; + err = ERR(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, name)); + if (err) + return err; + deviceInfo->name = name; + + /* Try to get the default sample rate. Don't fail if we can't get this. */ propSize = sizeof(Float64); - err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyNominalSampleRate, &propSize, &sampleRate)); - if (!err) { + err = ERR(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyNominalSampleRate, &propSize, &sampleRate)); + if (err) + deviceInfo->defaultSampleRate = 0.0; + else deviceInfo->defaultSampleRate = sampleRate; - } + /* Get the maximum number of input and output channels. Fail if we can't get this. */ - // Get channel info - err = GetChannelInfo(deviceInfo, macCoreDeviceId, 1); - err = GetChannelInfo(deviceInfo, macCoreDeviceId, 0); + err = GetChannelInfo(auhalHostApi, deviceInfo, macCoreDeviceId, 1); + if (err) + return err; - return err; + err = GetChannelInfo(auhalHostApi, deviceInfo, macCoreDeviceId, 0); + if (err) + return err; + + return paNoError; } -static PaError InitializeDeviceInfos( PaMacCoreHostApiRepresentation *macCoreHostApi, PaHostApiIndex hostApiIndex ) +PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) { PaError result = paNoError; - PaUtilHostApiRepresentation *hostApi; - PaMacCoreDeviceInfo *deviceInfoArray; - - // initialise device counts and default devices under the assumption that there are no devices. These values are incremented below if and when devices are successfully initialized. - hostApi = &macCoreHostApi->inheritedHostApiRep; - hostApi->info.deviceCount = 0; - hostApi->info.defaultInputDevice = paNoDevice; - hostApi->info.defaultOutputDevice = paNoDevice; - - UInt32 propsize; - AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &propsize, NULL); - int numDevices = propsize / sizeof(AudioDeviceID); - hostApi->info.deviceCount = numDevices; - if (numDevices > 0) { - hostApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( - macCoreHostApi->allocations, sizeof(PaDeviceInfo*) * numDevices ); - if( !hostApi->deviceInfos ) + int i; + PaMacAUHAL *auhalHostApi; + PaDeviceInfo *deviceInfoArray; + + VVDBUG(("PaMacCore_Initialize(): hostApiIndex=%d\n", hostApiIndex)); + + auhalHostApi = (PaMacAUHAL*)PaUtil_AllocateMemory( sizeof(PaMacAUHAL) ); + if( !auhalHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + auhalHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !auhalHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + auhalHostApi->devIds = NULL; + auhalHostApi->devCount = 0; + + /* get the info we need about the devices */ + result = gatherDeviceInfo( auhalHostApi ); + if( result != paNoError ) + goto error; + + *hostApi = &auhalHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paCoreAudio; + (*hostApi)->info.name = "Core Audio"; + + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + (*hostApi)->info.deviceCount = 0; + + if( auhalHostApi->devCount > 0 ) + { + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo*) * auhalHostApi->devCount); + if( !(*hostApi)->deviceInfos ) { - return paInsufficientMemory; + result = paInsufficientMemory; + goto error; } - // allocate all device info structs in a contiguous block - deviceInfoArray = (PaMacCoreDeviceInfo*)PaUtil_GroupAllocateMemory( - macCoreHostApi->allocations, sizeof(PaMacCoreDeviceInfo) * numDevices ); + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo) * auhalHostApi->devCount ); if( !deviceInfoArray ) { - return paInsufficientMemory; + result = paInsufficientMemory; + goto error; } - - macCoreHostApi->macCoreDeviceIds = PaUtil_GroupAllocateMemory(macCoreHostApi->allocations, propsize); - AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &propsize, macCoreHostApi->macCoreDeviceIds); - AudioDeviceID defaultInputDevice, defaultOutputDevice; - propsize = sizeof(AudioDeviceID); - AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &propsize, &defaultInputDevice); - AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propsize, &defaultOutputDevice); - - UInt32 i; - for (i = 0; i < numDevices; ++i) { - if (macCoreHostApi->macCoreDeviceIds[i] == defaultInputDevice) { - hostApi->info.defaultInputDevice = i; + for( i=0; i < auhalHostApi->devCount; ++i ) + { + int err; + err = InitializeDeviceInfo( auhalHostApi, &deviceInfoArray[i], + auhalHostApi->devIds[i], + hostApiIndex ); + if (err == paNoError) + { /* copy some info and set the defaults */ + (*hostApi)->deviceInfos[(*hostApi)->info.deviceCount] = &deviceInfoArray[i]; + if (auhalHostApi->devIds[i] == auhalHostApi->defaultIn) + (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; + if (auhalHostApi->devIds[i] == auhalHostApi->defaultOut) + (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; + (*hostApi)->info.deviceCount++; } - if (macCoreHostApi->macCoreDeviceIds[i] == defaultOutputDevice) { - hostApi->info.defaultOutputDevice = i; + else + { /* there was an error. we need to shift the devices down, so we ignore this one */ + int j; + auhalHostApi->devCount--; + for( j=i; jdevCount; ++j ) + auhalHostApi->devIds[j] = auhalHostApi->devIds[j+1]; + i--; } - InitializeDeviceInfo(&deviceInfoArray[i], macCoreHostApi->macCoreDeviceIds[i], hostApiIndex); - hostApi->deviceInfos[i] = &(deviceInfoArray[i].inheritedDeviceInfo); } } - return result; -} + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &auhalHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, + IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &auhalHostApi->blockingStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, + IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, + GetStreamReadAvailable, + GetStreamWriteAvailable ); -static OSStatus CheckFormat(AudioDeviceID macCoreDeviceId, const PaStreamParameters *parameters, double sampleRate, int isInput) -{ - UInt32 propSize = sizeof(AudioStreamBasicDescription); - AudioStreamBasicDescription *streamDescription = PaUtil_AllocateMemory(propSize); - - streamDescription->mSampleRate = sampleRate; - streamDescription->mFormatID = 0; - streamDescription->mFormatFlags = 0; - streamDescription->mBytesPerPacket = 0; - streamDescription->mFramesPerPacket = 0; - streamDescription->mBytesPerFrame = 0; - streamDescription->mChannelsPerFrame = 0; - streamDescription->mBitsPerChannel = 0; - streamDescription->mReserved = 0; - - OSStatus result = AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamFormatSupported, &propSize, streamDescription); - PaUtil_FreeMemory(streamDescription); return result; -} -static OSStatus CopyInputData(PaMacClientData* destination, const AudioBufferList *source, unsigned long frameCount) -{ - int frameSpacing, channelSpacing; - if (destination->inputSampleFormat & paNonInterleaved) { - frameSpacing = 1; - channelSpacing = destination->inputChannelCount; - } - else { - frameSpacing = destination->inputChannelCount; - channelSpacing = 1; - } - - AudioBuffer const *inputBuffer = &source->mBuffers[0]; - void *coreAudioBuffer = inputBuffer->mData; - void *portAudioBuffer = destination->inputBuffer; - UInt32 i, streamNumber, streamChannel; - for (i = streamNumber = streamChannel = 0; i < destination->inputChannelCount; ++i, ++streamChannel) { - if (streamChannel >= inputBuffer->mNumberChannels) { - ++streamNumber; - inputBuffer = &source->mBuffers[streamNumber]; - coreAudioBuffer = inputBuffer->mData; - streamChannel = 0; +error: + if( auhalHostApi ) + { + if( auhalHostApi->allocations ) + { + PaUtil_FreeAllAllocations( auhalHostApi->allocations ); + PaUtil_DestroyAllocationGroup( auhalHostApi->allocations ); } - destination->inputConverter(portAudioBuffer, frameSpacing, coreAudioBuffer, inputBuffer->mNumberChannels, frameCount, destination->ditherGenerator); - coreAudioBuffer += sizeof(Float32); - portAudioBuffer += Pa_GetSampleSize(destination->inputSampleFormat) * channelSpacing; + + PaUtil_FreeMemory( auhalHostApi ); } - return 0; + return result; } -static OSStatus CopyOutputData(AudioBufferList* destination, PaMacClientData *source, unsigned long frameCount) -{ - int frameSpacing, channelSpacing; - if (source->outputSampleFormat & paNonInterleaved) { - frameSpacing = 1; - channelSpacing = source->outputChannelCount; - } - else { - frameSpacing = source->outputChannelCount; - channelSpacing = 1; - } - - AudioBuffer *outputBuffer = &destination->mBuffers[0]; - void *coreAudioBuffer = outputBuffer->mData; - void *portAudioBuffer = source->outputBuffer; - UInt32 i, streamNumber, streamChannel; - for (i = streamNumber = streamChannel = 0; i < source->outputChannelCount; ++i, ++streamChannel) { - if (streamChannel >= outputBuffer->mNumberChannels) { - ++streamNumber; - outputBuffer = &destination->mBuffers[streamNumber]; - coreAudioBuffer = outputBuffer->mData; - streamChannel = 0; - } - source->outputConverter(coreAudioBuffer, outputBuffer->mNumberChannels, portAudioBuffer, frameSpacing, frameCount, NULL); - coreAudioBuffer += sizeof(Float32); - portAudioBuffer += Pa_GetSampleSize(source->outputSampleFormat) * channelSpacing; - } - return 0; -} -static OSStatus AudioIOProc( AudioDeviceID inDevice, - const AudioTimeStamp* inNow, - const AudioBufferList* inInputData, - const AudioTimeStamp* inInputTime, - AudioBufferList* outOutputData, - const AudioTimeStamp* inOutputTime, - void* inClientData) +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) { - PaMacClientData *clientData = (PaMacClientData *)inClientData; - PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); - - PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); - - AudioBuffer *outputBuffer = &outOutputData->mBuffers[0]; - unsigned long frameCount = outputBuffer->mDataByteSize / (outputBuffer->mNumberChannels * sizeof(Float32)); + PaMacAUHAL *auhalHostApi = (PaMacAUHAL*)hostApi; - if (clientData->inputBuffer) { - CopyInputData(clientData, inInputData, frameCount); - } - PaStreamCallbackResult result = clientData->callback(clientData->inputBuffer, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); - if (clientData->outputBuffer) { - CopyOutputData(outOutputData, clientData, frameCount); - } + VVDBUG(("Terminate()\n")); - PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); - - if (result == paComplete || result == paAbort) { - Pa_StopStream(clientData->stream); - } - return 0; -} - -// This is not for input-only streams, this is for streams where the input device is different from the output device -// TODO: This needs to store the output data in a buffer, to be written to the device the next time AudioOutputProc is called -static OSStatus AudioInputProc( AudioDeviceID inDevice, - const AudioTimeStamp* inNow, - const AudioBufferList* inInputData, - const AudioTimeStamp* inInputTime, - AudioBufferList* outOutputData, - const AudioTimeStamp* inOutputTime, - void* inClientData) -{ - PaMacClientData *clientData = (PaMacClientData *)inClientData; - PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); - - PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + /* + IMPLEMENT ME: + - clean up any resources not handled by the allocation group + TODO: Double check that everything is handled by alloc group + */ - AudioBuffer const *inputBuffer = &inInputData->mBuffers[0]; - unsigned long frameCount = inputBuffer->mDataByteSize / (inputBuffer->mNumberChannels * sizeof(Float32)); + if( auhalHostApi->allocations ) + { + PaUtil_FreeAllAllocations( auhalHostApi->allocations ); + PaUtil_DestroyAllocationGroup( auhalHostApi->allocations ); + } - CopyInputData(clientData, inInputData, frameCount); - clientData->callback(clientData->inputBuffer, NULL, frameCount, timeInfo, paNoFlag, clientData->userData); - - PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); - return 0; + PaUtil_FreeMemory( auhalHostApi ); } -// This is not for output-only streams, this is for streams where the input device is different from the output device -static OSStatus AudioOutputProc( AudioDeviceID inDevice, - const AudioTimeStamp* inNow, - const AudioBufferList* inInputData, - const AudioTimeStamp* inInputTime, - AudioBufferList* outOutputData, - const AudioTimeStamp* inOutputTime, - void* inClientData) + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) { - PaMacClientData *clientData = (PaMacClientData *)inClientData; - PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + VVDBUG(("IsFormatSupported(): in chan=%d, in fmt=%ld, out chan=%d, out fmt=%ld sampleRate=%g\n", + inputParameters ? inputParameters->channelCount : -1, + inputParameters ? inputParameters->sampleFormat : -1, + outputParameters ? outputParameters->channelCount : -1, + outputParameters ? outputParameters->sampleFormat : -1, + (float) sampleRate )); + + /** These first checks are standard PA checks. We do some fancier checks + later. */ + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + } + else + { + inputChannelCount = 0; + } - PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; - AudioBuffer *outputBuffer = &outOutputData->mBuffers[0]; - unsigned long frameCount = outputBuffer->mDataByteSize / (outputBuffer->mNumberChannels * sizeof(Float32)); + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ - clientData->callback(NULL, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; - CopyOutputData(outOutputData, clientData, frameCount); + /* check that output device can support outputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; - PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); - return 0; -} + } + else + { + outputChannelCount = 0; + } + + /* FEEDBACK */ + /* I think the only way to check a given format SR combo is */ + /* to try opening it. This could be disruptive, is that Okay? */ + /* The alternative is to just read off available sample rates, */ + /* but this will not work %100 of the time (eg, a device that */ + /* supports N output at one rate but only N/2 at a higher rate.)*/ + + /* The following code opens the device with the requested parameters to + see if it works. */ + { + PaError err; + PaStream *s; + err = OpenStream( hostApi, &s, inputParameters, outputParameters, + sampleRate, 1024, 0, (PaStreamCallback *)1, NULL ); + if( err != paNoError && err != paInvalidSampleRate ) + DBUG( ( "OpenStream @ %g returned: %d: %s\n", + (float) sampleRate, err, Pa_GetErrorText( err ) ) ); + if( err ) + return err; + err = CloseStream( s ); + if( err ) { + /* FEEDBACK: is this more serious? should we assert? */ + DBUG( ( "WARNING: could not close Stream. %d: %s\n", + err, Pa_GetErrorText( err ) ) ); + } + } -static PaError SetSampleRate(AudioDeviceID device, double sampleRate, int isInput) -{ - PaError result = paNoError; - - double actualSampleRate; - UInt32 propSize = sizeof(double); - result = conv_err(AudioDeviceSetProperty(device, NULL, 0, isInput, kAudioDevicePropertyNominalSampleRate, propSize, &sampleRate)); - - result = conv_err(AudioDeviceGetProperty(device, 0, isInput, kAudioDevicePropertyNominalSampleRate, &propSize, &actualSampleRate)); - - if (result == paNoError && actualSampleRate != sampleRate) { - result = paInvalidSampleRate; - } - - return result; + return paFormatIsSupported; } -static PaError SetFramesPerBuffer(AudioDeviceID device, unsigned long framesPerBuffer, int isInput) +static PaError OpenAndSetupOneAudioUnit( + const PaStreamParameters *inStreamParams, + const PaStreamParameters *outStreamParams, + const unsigned long requestedFramesPerBuffer, + unsigned long *actualInputFramesPerBuffer, + unsigned long *actualOutputFramesPerBuffer, + const PaMacAUHAL *auhalHostApi, + AudioUnit *audioUnit, + AudioConverterRef *srConverter, + AudioDeviceID *audioDevice, + const double sampleRate, + void *refCon ) { - PaError result = paNoError; - UInt32 preferredFramesPerBuffer = framesPerBuffer; - // while (preferredFramesPerBuffer > UINT32_MAX) { - // preferredFramesPerBuffer /= 2; - // } - - UInt32 actualFramesPerBuffer; - UInt32 propSize = sizeof(UInt32); - result = conv_err(AudioDeviceSetProperty(device, NULL, 0, isInput, kAudioDevicePropertyBufferFrameSize, propSize, &preferredFramesPerBuffer)); - - result = conv_err(AudioDeviceGetProperty(device, 0, isInput, kAudioDevicePropertyBufferFrameSize, &propSize, &actualFramesPerBuffer)); - - if (result != paNoError) { - // do nothing - } - else if (actualFramesPerBuffer > framesPerBuffer) { - result = paBufferTooSmall; - } - else if (actualFramesPerBuffer < framesPerBuffer) { - result = paBufferTooBig; - } - - return result; -} - -static PaError SetUpUnidirectionalStream(AudioDeviceID device, double sampleRate, unsigned long framesPerBuffer, int isInput) -{ - PaError err = paNoError; - err = SetSampleRate(device, sampleRate, isInput); - if( err == paNoError ) - err = SetFramesPerBuffer(device, framesPerBuffer, isInput); - return err; -} + ComponentDescription desc; + Component comp; + /*An Apple TN suggests using CAStreamBasicDescription, but that is C++*/ + AudioStreamBasicDescription desiredFormat; + OSErr result = noErr; + PaError paResult = paNoError; + int line = 0; + UInt32 callbackKey; + AURenderCallbackStruct rcbs; + unsigned long macInputStreamFlags = paMacCorePlayNice; + unsigned long macOutputStreamFlags = paMacCorePlayNice; + + VVDBUG(("OpenAndSetupOneAudioUnit(): in chan=%d, in fmt=%ld, out chan=%d, out fmt=%ld, requestedFramesPerBuffer=%ld\n", + inStreamParams ? inStreamParams->channelCount : -1, + inStreamParams ? inStreamParams->sampleFormat : -1, + outStreamParams ? outStreamParams->channelCount : -1, + outStreamParams ? outStreamParams->sampleFormat : -1, + requestedFramesPerBuffer )); + + /* -- handle the degenerate case -- */ + if( !inStreamParams && !outStreamParams ) { + *audioUnit = NULL; + *audioDevice = kAudioDeviceUnknown; + return paNoError; + } -// ===== PortAudio functions ===== -#pragma mark PortAudio functions + /* -- get the user's api specific info, if they set any -- */ + if( inStreamParams && inStreamParams->hostApiSpecificStreamInfo ) + macInputStreamFlags= + ((paMacCoreStreamInfo*)inStreamParams->hostApiSpecificStreamInfo) + ->flags; + if( outStreamParams && outStreamParams->hostApiSpecificStreamInfo ) + macOutputStreamFlags= + ((paMacCoreStreamInfo*)outStreamParams->hostApiSpecificStreamInfo) + ->flags; + /* Override user's flags here, if desired for testing. */ + + /* + * The HAL AU is a Mac OS style "component". + * the first few steps deal with that. + * Later steps work on a combination of Mac OS + * components and the slightly lower level + * HAL. + */ + + /* -- describe the output type AudioUnit -- */ + /* Note: for the default AudioUnit, we could use the + * componentSubType value kAudioUnitSubType_DefaultOutput; + * but I don't think that's relevant here. + */ + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_HALOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + /* -- find the component -- */ + comp = FindNextComponent( NULL, &desc ); + if( !comp ) + { + DBUG( ( "AUHAL component not found." ) ); + *audioUnit = NULL; + *audioDevice = kAudioDeviceUnknown; + return paUnanticipatedHostError; + } + /* -- open it -- */ + result = OpenAComponent( comp, audioUnit ); + if( result ) + { + DBUG( ( "Failed to open AUHAL component." ) ); + *audioUnit = NULL; + *audioDevice = kAudioDeviceUnknown; + return ERR( result ); + } + /* -- prepare a little error handling logic / hackery -- */ +#define ERR_WRAP(mac_err) do { result = mac_err ; line = __LINE__ ; if ( result != noErr ) goto error ; } while(0) -#ifdef __cplusplus -extern "C" -{ -#endif // __cplusplus - - PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); - -#ifdef __cplusplus -} -#endif // __cplusplus + /* -- if there is input, we have to explicitly enable input -- */ + if( inStreamParams ) + { + UInt32 enableIO; + enableIO = 1; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + INPUT_ELEMENT, + &enableIO, + sizeof(enableIO) ) ); + } + /* -- if there is no output, we must explicitly disable output -- */ + if( !outStreamParams ) + { + UInt32 enableIO; + enableIO = 0; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + OUTPUT_ELEMENT, + &enableIO, + sizeof(enableIO) ) ); + } + /* -- set the devices -- */ + /* make sure input and output are the same device if we are doing input and + output. */ + if( inStreamParams && outStreamParams ) + assert( outStreamParams->device == inStreamParams->device ); + if( inStreamParams ) + { + *audioDevice = auhalHostApi->devIds[inStreamParams->device] ; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + INPUT_ELEMENT, + audioDevice, + sizeof(AudioDeviceID) ) ); + } + if( outStreamParams ) + { + *audioDevice = auhalHostApi->devIds[outStreamParams->device] ; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + OUTPUT_ELEMENT, + audioDevice, + sizeof(AudioDeviceID) ) ); + } -static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) -{ - PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation*)hostApi; - - CleanUp(macCoreHostApi); -} + /* -- set format -- */ + bzero( &desiredFormat, sizeof(desiredFormat) ); + desiredFormat.mFormatID = kAudioFormatLinearPCM ; + desiredFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; + desiredFormat.mFramesPerPacket = 1; + desiredFormat.mBitsPerChannel = sizeof( float ) * 8; + + result = 0; + /* set device format first, but only touch the device if the user asked */ + if( inStreamParams ) { + /*The callback never calls back if we don't set the FPB */ + /*This seems wierd, because I would think setting anything on the device + would be disruptive.*/ + paResult = setBestFramesPerBuffer( *audioDevice, FALSE, + requestedFramesPerBuffer, + actualInputFramesPerBuffer ); + if( paResult ) goto error; + if( macInputStreamFlags & paMacCore_ChangeDeviceParameters ) { + bool requireExact; + requireExact=macInputStreamFlags&paMacCore_FailIfConversionRequired; + paResult = setBestSampleRateForDevice( *audioDevice, FALSE, + requireExact, sampleRate ); + if( paResult ) goto error; + } + if( actualInputFramesPerBuffer && actualOutputFramesPerBuffer ) + *actualOutputFramesPerBuffer = *actualInputFramesPerBuffer ; + } + if( outStreamParams && !inStreamParams ) { + /*The callback never calls back if we don't set the FPB */ + /*This seems wierd, because I would think setting anything on the device + would be disruptive.*/ + paResult = setBestFramesPerBuffer( *audioDevice, TRUE, + requestedFramesPerBuffer, + actualOutputFramesPerBuffer ); + if( paResult ) goto error; + if( macOutputStreamFlags & paMacCore_ChangeDeviceParameters ) { + bool requireExact; + requireExact=macOutputStreamFlags&paMacCore_FailIfConversionRequired; + paResult = setBestSampleRateForDevice( *audioDevice, TRUE, + requireExact, sampleRate ); + if( paResult ) goto error; + } + } -static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, - const PaStreamParameters *inputParameters, - const PaStreamParameters *outputParameters, - double sampleRate ) -{ - PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation*)hostApi; - PaDeviceInfo *deviceInfo; - - PaError result = paNoError; - if (inputParameters) { - deviceInfo = macCoreHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device]; - if (inputParameters->channelCount > deviceInfo->maxInputChannels) - result = paInvalidChannelCount; - else if (CheckFormat(macCoreHostApi->macCoreDeviceIds[inputParameters->device], inputParameters, sampleRate, 1) != kAudioHardwareNoError) { - result = paInvalidSampleRate; - } + /* -- set the quality of the output converter -- */ + if( outStreamParams ) { + UInt32 value = kAudioConverterQuality_Max; + switch( macOutputStreamFlags & 0x0700 ) { + case 0x0100: /*paMacCore_ConversionQualityMin:*/ + value=kRenderQuality_Min; + break; + case 0x0200: /*paMacCore_ConversionQualityLow:*/ + value=kRenderQuality_Low; + break; + case 0x0300: /*paMacCore_ConversionQualityMedium:*/ + value=kRenderQuality_Medium; + break; + case 0x0400: /*paMacCore_ConversionQualityHigh:*/ + value=kRenderQuality_High; + break; + } + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioUnitProperty_RenderQuality, + kAudioUnitScope_Global, + OUTPUT_ELEMENT, + &value, + sizeof(value) ) ); } - if (outputParameters && result == paNoError) { - deviceInfo = macCoreHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device]; - if (outputParameters->channelCount > deviceInfo->maxOutputChannels) - result = paInvalidChannelCount; - else if (CheckFormat(macCoreHostApi->macCoreDeviceIds[outputParameters->device], outputParameters, sampleRate, 0) != kAudioHardwareNoError) { - result = paInvalidSampleRate; - } + /* now set the format on the Audio Units. */ + if( outStreamParams ) + { + desiredFormat.mSampleRate =sampleRate; + desiredFormat.mBytesPerPacket=sizeof(float)*outStreamParams->channelCount; + desiredFormat.mBytesPerFrame =sizeof(float)*outStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = outStreamParams->channelCount; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + OUTPUT_ELEMENT, + &desiredFormat, + sizeof(AudioStreamBasicDescription) ) ); + } + if( inStreamParams ) + { + AudioStreamBasicDescription sourceFormat; + UInt32 size = sizeof( AudioStreamBasicDescription ); + + /* keep the sample rate of the device, or we confuse AUHAL */ + ERR_WRAP( AudioUnitGetProperty( *audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + INPUT_ELEMENT, + &sourceFormat, + &size ) ); + desiredFormat.mSampleRate = sourceFormat.mSampleRate; + desiredFormat.mBytesPerPacket=sizeof(float)*inStreamParams->channelCount; + desiredFormat.mBytesPerFrame =sizeof(float)*inStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = inStreamParams->channelCount; + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &desiredFormat, + sizeof(AudioStreamBasicDescription) ) ); + } + /* set the maximumFramesPerSlice */ + /* not doing this causes real problems + (eg. the callback might not be called). The idea of setting both this + and the frames per buffer on the device is that we'll be most likely + to actually get the frame size we requested in the callback with the + minimum latency. */ + if( outStreamParams ) { + UInt32 size = sizeof( *actualOutputFramesPerBuffer ); + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Input, + OUTPUT_ELEMENT, + actualOutputFramesPerBuffer, + sizeof(unsigned long) ) ); + ERR_WRAP( AudioUnitGetProperty( *audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + OUTPUT_ELEMENT, + actualOutputFramesPerBuffer, + &size ) ); + } + if( inStreamParams ) { + /*UInt32 size = sizeof( *actualInputFramesPerBuffer );*/ + ERR_WRAP( AudioUnitSetProperty( *audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Output, + INPUT_ELEMENT, + actualInputFramesPerBuffer, + sizeof(unsigned long) ) ); +/* Don't know why this causes problems + ERR_WRAP( AudioUnitGetProperty( *audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, //Output, + INPUT_ELEMENT, + actualInputFramesPerBuffer, + &size ) ); +*/ } - return result; + /* -- if we have input, we may need to setup an SR converter -- */ + /* even if we got the sample rate we asked for, we need to do + the conversion in case another program changes the underlying SR. */ + /* FIXME: I think we need to monitor stream and change the converter if the incoming format changes. */ + if( inStreamParams ) { + AudioStreamBasicDescription desiredFormat; + AudioStreamBasicDescription sourceFormat; + UInt32 sourceSize = sizeof( sourceFormat ); + bzero( &desiredFormat, sizeof(desiredFormat) ); + desiredFormat.mSampleRate = sampleRate; + desiredFormat.mFormatID = kAudioFormatLinearPCM ; + desiredFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; + desiredFormat.mFramesPerPacket = 1; + desiredFormat.mBitsPerChannel = sizeof( float ) * 8; + desiredFormat.mBytesPerPacket=sizeof(float)*inStreamParams->channelCount; + desiredFormat.mBytesPerFrame =sizeof(float)*inStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = inStreamParams->channelCount; + + /* get the source format */ + ERR_WRAP( AudioUnitGetProperty( + *audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &sourceFormat, + &sourceSize ) ); + + if( desiredFormat.mSampleRate != sourceFormat.mSampleRate ) + { + UInt32 value = kAudioConverterQuality_Max; + switch( macInputStreamFlags & 0x0700 ) { + case 0x0100: /*paMacCore_ConversionQualityMin:*/ + value=kAudioConverterQuality_Min; + break; + case 0x0200: /*paMacCore_ConversionQualityLow:*/ + value=kAudioConverterQuality_Low; + break; + case 0x0300: /*paMacCore_ConversionQualityMedium:*/ + value=kAudioConverterQuality_Medium; + break; + case 0x0400: /*paMacCore_ConversionQualityHigh:*/ + value=kAudioConverterQuality_High; + break; + } + VDBUG(( "Creating sample rate converter for input" + " to convert from %g to %g\n", + (float)sourceFormat.mSampleRate, + (float)desiredFormat.mSampleRate ) ); + /* create our converter */ + ERR_WRAP( AudioConverterNew( + &sourceFormat, + &desiredFormat, + srConverter ) ); + /* Set quality */ + ERR_WRAP( AudioConverterSetProperty( + *srConverter, + kAudioConverterSampleRateConverterQuality, + sizeof( value ), + &value ) ); + } + } + /* -- set IOProc (callback) -- */ + callbackKey = outStreamParams ? kAudioUnitProperty_SetRenderCallback + : kAudioOutputUnitProperty_SetInputCallback ; + rcbs.inputProc = AudioIOProc; + rcbs.inputProcRefCon = refCon; + ERR_WRAP( AudioUnitSetProperty( + *audioUnit, + callbackKey, + kAudioUnitScope_Output, + outStreamParams ? OUTPUT_ELEMENT : INPUT_ELEMENT, + &rcbs, + sizeof(rcbs)) ); + + if( inStreamParams && outStreamParams && *srConverter ) + ERR_WRAP( AudioUnitSetProperty( + *audioUnit, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &rcbs, + sizeof(rcbs)) ); + + /*IMPLEMENTME: may need to worry about channel mapping.*/ + + /* initialize the audio unit */ + ERR_WRAP( AudioUnitInitialize(*audioUnit) ); + + if( inStreamParams && outStreamParams ) + VDBUG( ("Opened device %ld for input and output.\n", *audioDevice ) ); + else if( inStreamParams ) + VDBUG( ("Opened device %ld for input.\n", *audioDevice ) ); + else if( outStreamParams ) + VDBUG( ("Opened device %ld for output.\n", *audioDevice ) ); + return paNoError; +#undef ERR_WRAP + + error: + CloseComponent( *audioUnit ); + *audioUnit = NULL; + if( result ) + return PaMacCore_SetError( result, line, 1 ); + return paResult; } +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaStream** s, const PaStreamParameters *inputParameters, @@ -638,264 +998,1045 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaStreamCallback *streamCallback, void *userData ) { - PaError err = paNoError; - PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation *)hostApi; - PaMacCoreStream *stream = PaUtil_AllocateMemory(sizeof(PaMacCoreStream)); - stream->isActive = 0; - stream->isStopped = 1; - stream->inputDevice = kAudioDeviceUnknown; - stream->outputDevice = kAudioDeviceUnknown; - - PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - ( (streamCallback) - ? &macCoreHostApi->callbackStreamInterface - : &macCoreHostApi->blockingStreamInterface ), - streamCallback, userData ); - PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); - - *s = (PaStream*)stream; - PaMacClientData *clientData = PaUtil_AllocateMemory(sizeof(PaMacClientData)); - clientData->stream = stream; - clientData->callback = streamCallback; - clientData->userData = userData; - clientData->inputBuffer = 0; - clientData->outputBuffer = 0; - clientData->ditherGenerator = PaUtil_AllocateMemory(sizeof(PaUtilTriangularDitherGenerator)); - PaUtil_InitializeTriangularDitherState(clientData->ditherGenerator); - - if (inputParameters != NULL) { - stream->inputDevice = macCoreHostApi->macCoreDeviceIds[inputParameters->device]; - clientData->inputConverter = PaUtil_SelectConverter(paFloat32, inputParameters->sampleFormat, streamFlags); - clientData->inputBuffer = PaUtil_AllocateMemory(Pa_GetSampleSize(inputParameters->sampleFormat) * framesPerBuffer * inputParameters->channelCount); - clientData->inputChannelCount = inputParameters->channelCount; - clientData->inputSampleFormat = inputParameters->sampleFormat; - err = SetUpUnidirectionalStream(stream->inputDevice, sampleRate, framesPerBuffer, 1); - } - - if (err == paNoError && outputParameters != NULL) { - stream->outputDevice = macCoreHostApi->macCoreDeviceIds[outputParameters->device]; - clientData->outputConverter = PaUtil_SelectConverter(outputParameters->sampleFormat, paFloat32, streamFlags); - clientData->outputBuffer = PaUtil_AllocateMemory(Pa_GetSampleSize(outputParameters->sampleFormat) * framesPerBuffer * outputParameters->channelCount); - clientData->outputChannelCount = outputParameters->channelCount; - clientData->outputSampleFormat = outputParameters->sampleFormat; - err = SetUpUnidirectionalStream(stream->outputDevice, sampleRate, framesPerBuffer, 0); - } - - if (inputParameters == NULL || outputParameters == NULL || stream->inputDevice == stream->outputDevice) { - AudioDeviceID device = (inputParameters == NULL) ? stream->outputDevice : stream->inputDevice; - - AudioDeviceAddIOProc(device, AudioIOProc, clientData); - } - else { - // using different devices for input and output - AudioDeviceAddIOProc(stream->inputDevice, AudioInputProc, clientData); - AudioDeviceAddIOProc(stream->outputDevice, AudioOutputProc, clientData); - } - - return err; -} + PaError result = paNoError; + PaMacAUHAL *auhalHostApi = (PaMacAUHAL*)hostApi; + PaMacCoreStream *stream = 0; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + VVDBUG(("OpenStream(): in chan=%d, in fmt=%ld, out chan=%d, out fmt=%ld SR=%g, FPB=%ld\n", + inputParameters ? inputParameters->channelCount : -1, + inputParameters ? inputParameters->sampleFormat : -1, + outputParameters ? outputParameters->channelCount : -1, + outputParameters ? outputParameters->sampleFormat : -1, + (float) sampleRate, + framesPerBuffer )); + VDBUG( ("Opening Stream.\n") ); + + /*These first few bits of code are from paSkeleton with few modifications.*/ + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; -// Note: When CloseStream() is called, the multi-api layer ensures that the stream has already been stopped or aborted. -static PaError CloseStream( PaStream* s ) -{ - PaError err = paNoError; - PaMacCoreStream *stream = (PaMacCoreStream*)s; + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ - PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; - if (stream->inputDevice != kAudioDeviceUnknown) { - if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { - err = conv_err(AudioDeviceRemoveIOProc(stream->inputDevice, AudioIOProc)); - } - else { - err = conv_err(AudioDeviceRemoveIOProc(stream->inputDevice, AudioInputProc)); - err = conv_err(AudioDeviceRemoveIOProc(stream->outputDevice, AudioOutputProc)); - } + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* Host supports interleaved float32 */ + hostInputSampleFormat = paFloat32; } - else { - err = conv_err(AudioDeviceRemoveIOProc(stream->outputDevice, AudioIOProc)); + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paFloat32; /* Surpress 'uninitialised var' warnings. */ } - - return err; -} + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ -static PaError StartStream( PaStream *s ) -{ - PaError err = paNoError; - PaMacCoreStream *stream = (PaMacCoreStream*)s; + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; - if (stream->inputDevice != kAudioDeviceUnknown) { - if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { - err = conv_err(AudioDeviceStart(stream->inputDevice, AudioIOProc)); - } - else { - err = conv_err(AudioDeviceStart(stream->inputDevice, AudioInputProc)); - err = conv_err(AudioDeviceStart(stream->outputDevice, AudioOutputProc)); - } + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* Host supports interleaved float32 */ + hostOutputSampleFormat = paFloat32; + } + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paFloat32; /* Surpress 'uninitialized var' warnings. */ } - else { - err = conv_err(AudioDeviceStart(stream->outputDevice, AudioIOProc)); + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + stream = (PaMacCoreStream*)PaUtil_AllocateMemory( sizeof(PaMacCoreStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; } - - stream->isActive = 1; - stream->isStopped = 0; - return err; + + /* If we fail after this point, we my be left in a bad state, with + some data structures setup and others not. So, first thing we + do is initialize everything so that if we fail, we know what hasn't + been touched. + */ + + stream->inputAudioBufferList.mBuffers[0].mData = NULL; + stream->inputRingBuffer.buffer = NULL; + bzero( &stream->blio, sizeof( PaMacBlio ) ); +/* + stream->blio.inputRingBuffer.buffer = NULL; + stream->blio.outputRingBuffer.buffer = NULL; + stream->blio.inputSampleFormat = inputParameters?inputParameters->sampleFormat:0; + stream->blio.inputSampleSize = computeSampleSizeFromFormat(stream->blio.inputSampleFormat); + stream->blio.outputSampleFormat=outputParameters?outputParameters->sampleFormat:0; + stream->blio.outputSampleSize = computeSampleSizeFromFormat(stream->blio.outputSampleFormat); +*/ + stream->inputSRConverter = NULL; + stream->inputUnit = NULL; + stream->outputUnit = NULL; + stream->inputFramesPerBuffer = 0; + stream->outputFramesPerBuffer = 0; + stream->bufferProcessorIsInitialized = FALSE; + + /* assert( streamCallback ) ; */ /* only callback mode is implemented */ + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &auhalHostApi->callbackStreamInterface, + streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &auhalHostApi->blockingStreamInterface, + BlioCallback, &stream->blio ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + /* -- handle paFramesPerBufferUnspecified -- */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) { + long requested = 64; + if( inputParameters ) + requested = MAX( requested, inputParameters->suggestedLatency * sampleRate / 2 ); + if( outputParameters ) + requested = MAX( requested, outputParameters->suggestedLatency *sampleRate / 2 ); + VDBUG( ("Block Size unspecified. Based on Latency, the user wants a Block Size near: %ld.\n", + requested ) ); + if( requested <= 64 ) { + /*requested a realtively low latency. make sure this is in range of devices */ + /*try to get the device's min natural buffer size and use that (but no smaller than 64).*/ + AudioValueRange audioRange; + size_t size = sizeof( audioRange ); + if( inputParameters ) { + WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[inputParameters->device], + 0, + false, + kAudioDevicePropertyBufferFrameSizeRange, + &size, &audioRange ) ); + if( result ) + requested = MAX( requested, audioRange.mMinimum ); + } + if( outputParameters ) { + WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[outputParameters->device], + 0, + false, + kAudioDevicePropertyBufferFrameSizeRange, + &size, &audioRange ) ); + if( result ) + requested = MAX( requested, audioRange.mMinimum ); + } + } else { + /* requested a realtively high latency. make sure this is in range of devices */ + /*try to get the device's max natural buffer size and use that (but no larger than 1024).*/ + AudioValueRange audioRange; + size_t size = sizeof( audioRange ); + requested = MIN( requested, 1024 ); + if( inputParameters ) { + WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[inputParameters->device], + 0, + false, + kAudioDevicePropertyBufferFrameSizeRange, + &size, &audioRange ) ); + if( result ) + requested = MIN( requested, audioRange.mMaximum ); + } + if( outputParameters ) { + WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[outputParameters->device], + 0, + false, + kAudioDevicePropertyBufferFrameSizeRange, + &size, &audioRange ) ); + if( result ) + requested = MIN( requested, audioRange.mMaximum ); + } + } + /* -- double check ranges -- */ + if( requested > 1024 ) requested = 1024; + if( requested < 64 ) requested = 64; + VDBUG(("After querying hardware, setting block size to %ld.\n", requested)); + framesPerBuffer = requested; + } + + /* -- Now we actually open and setup streams. -- */ + if( inputParameters && outputParameters && outputParameters->device == inputParameters->device ) + { /* full duplex. One device. */ + result = OpenAndSetupOneAudioUnit( inputParameters, + outputParameters, + framesPerBuffer, + &(stream->inputFramesPerBuffer), + &(stream->outputFramesPerBuffer), + auhalHostApi, + &(stream->inputUnit), + &(stream->inputSRConverter), + &(stream->inputDevice), + sampleRate, + stream ); + stream->outputUnit = stream->inputUnit; + stream->outputDevice = stream->inputDevice; + if( result != paNoError ) + goto error; + } + else + { /* full duplex, different devices OR simplex */ + result = OpenAndSetupOneAudioUnit( NULL, + outputParameters, + framesPerBuffer, + NULL, + &(stream->outputFramesPerBuffer), + auhalHostApi, + &(stream->outputUnit), + NULL, + &(stream->outputDevice), + sampleRate, + stream ); + if( result != paNoError ) + goto error; + result = OpenAndSetupOneAudioUnit( inputParameters, + NULL, + framesPerBuffer, + &(stream->inputFramesPerBuffer), + NULL, + auhalHostApi, + &(stream->inputUnit), + &(stream->inputSRConverter), + &(stream->inputDevice), + sampleRate, + stream ); + if( result != paNoError ) + goto error; + } + + if( stream->inputUnit ) { + const size_t szfl = sizeof(float); + /* setup the AudioBufferList used for input */ + bzero( &stream->inputAudioBufferList, sizeof( AudioBufferList ) ); + stream->inputAudioBufferList.mNumberBuffers = 1; + stream->inputAudioBufferList.mBuffers[0].mNumberChannels + = inputChannelCount; + stream->inputAudioBufferList.mBuffers[0].mDataByteSize + = stream->inputFramesPerBuffer*inputChannelCount*szfl; + stream->inputAudioBufferList.mBuffers[0].mData + = (float *) calloc( + stream->inputFramesPerBuffer*inputChannelCount, + szfl ); + if( !stream->inputAudioBufferList.mBuffers[0].mData ) + { + result = paInsufficientMemory; + goto error; + } + + /* + * If input and output devs are different or we are doing SR conversion, + * we also need a + * ring buffer to store inpt data while waiting for output + * data. + */ + if( (stream->outputUnit && stream->inputUnit != stream->outputUnit) + || stream->inputSRConverter ) + { + /* May want the ringSize ot initial position in + ring buffer to depend somewhat on sample rate change */ + + void *data; + long ringSize; + + ringSize = computeRingBufferSize( inputParameters, + outputParameters, + stream->inputFramesPerBuffer, + stream->outputFramesPerBuffer, + sampleRate ); + /*ringSize <<= 4; *//*16x bigger, for testing */ + + + /*now, we need to allocate memory for the ring buffer*/ + data = calloc( ringSize, szfl ); + if( !data ) + { + result = paInsufficientMemory; + goto error; + } + + /* now we can initialize the ring buffer */ + assert( 0 == + RingBuffer_Init( &stream->inputRingBuffer, + ringSize*szfl, data ) ); + /* advance the read point a little, so we are reading from the + middle of the buffer */ + if( stream->outputUnit ) + RingBuffer_AdvanceWriteIndex( &stream->inputRingBuffer, ringSize*szfl / RING_BUFFER_ADVANCE_DENOMINATOR ); + } + } + + /* -- initialize Blio Buffer Processors -- */ + if( !streamCallback ) + { + long ringSize; + + ringSize = computeRingBufferSize( inputParameters, + outputParameters, + stream->inputFramesPerBuffer, + stream->outputFramesPerBuffer, + sampleRate ); + result = initializeBlioRingBuffers( &stream->blio, + inputParameters?inputParameters->sampleFormat:0 , + outputParameters?outputParameters->sampleFormat:0 , + MAX(stream->inputFramesPerBuffer,stream->outputFramesPerBuffer), + ringSize, + inputParameters?inputChannelCount:0 , + outputParameters?outputChannelCount:0 ) ; + if( result != paNoError ) + goto error; + } + + /* -- initialize Buffer Processor -- */ + { + unsigned long maxHostFrames = stream->inputFramesPerBuffer; + if( stream->outputFramesPerBuffer > maxHostFrames ) + maxHostFrames = stream->outputFramesPerBuffer; + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, + hostInputSampleFormat, + outputChannelCount, outputSampleFormat, + hostOutputSampleFormat, + sampleRate, + streamFlags, + framesPerBuffer, + /* If sample rate conversion takes place, the buffer size + will not be known. */ + maxHostFrames, + stream->inputSRConverter + ? paUtilUnknownHostBufferSize + : paUtilBoundedHostBufferSize, + streamCallback ? streamCallback : BlioCallback, + streamCallback ? userData : &stream->blio ); + if( result != paNoError ) + goto error; + } + stream->bufferProcessorIsInitialized = TRUE; + + /* + IMPLEMENT ME: initialise the following fields with estimated or actual + values. + I think this is okay the way it is br 12/1/05 + maybe need to change input latency estimate if IO devs differ + */ + stream->streamRepresentation.streamInfo.inputLatency = + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)/sampleRate; + stream->streamRepresentation.streamInfo.outputLatency = + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)/sampleRate; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + stream->sampleRate = sampleRate; + stream->outDeviceSampleRate = 0; + if( stream->outputUnit ) { + Float64 rate; + UInt32 size = sizeof( rate ); + result = ERR( AudioDeviceGetProperty( stream->outputDevice, + 0, + FALSE, + kAudioDevicePropertyNominalSampleRate, + &size, &rate ) ); + if( result ) + goto error; + stream->outDeviceSampleRate = rate; + } + stream->inDeviceSampleRate = 0; + if( stream->inputUnit ) { + Float64 rate; + UInt32 size = sizeof( rate ); + result = ERR( AudioDeviceGetProperty( stream->inputDevice, + 0, + TRUE, + kAudioDevicePropertyNominalSampleRate, + &size, &rate ) ); + if( result ) + goto error; + stream->inDeviceSampleRate = rate; + } + stream->userInChan = inputChannelCount; + stream->userOutChan = outputChannelCount; + + stream->isTimeSet = FALSE; + stream->state = STOPPED; + stream->xrunFlags = 0; + + *s = (PaStream*)stream; + + return result; + +error: + CloseStream( stream ); + return result; } -static PaError AbortStream( PaStream *s ) +PaTime GetStreamTime( PaStream *s ) { - PaError err = paNoError; + /* FIXME: I am not at all sure this timing info stuff is right. + patest_sine_time reports negative latencies, which is wierd.*/ PaMacCoreStream *stream = (PaMacCoreStream*)s; - - if (stream->inputDevice != kAudioDeviceUnknown) { - if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { - err = conv_err(AudioDeviceStop(stream->inputDevice, AudioIOProc)); - } - else { - err = conv_err(AudioDeviceStop(stream->inputDevice, AudioInputProc)); - err = conv_err(AudioDeviceStop(stream->outputDevice, AudioOutputProc)); - } - } - else { - err = conv_err(AudioDeviceStop(stream->outputDevice, AudioIOProc)); + AudioTimeStamp timeStamp; + + VVDBUG(("GetStreamTime()\n")); + + if ( !stream->isTimeSet ) + return (PaTime)0; + + if ( stream->outputDevice ) { + AudioDeviceGetCurrentTime( stream->outputDevice, &timeStamp); + return (PaTime)(timeStamp.mSampleTime - stream->startTime.mSampleTime)/stream->outDeviceSampleRate; + } else if ( stream->inputDevice ) { + AudioDeviceGetCurrentTime( stream->inputDevice, &timeStamp); + return (PaTime)(timeStamp.mSampleTime - stream->startTime.mSampleTime)/stream->inDeviceSampleRate; + } else { + return (PaTime)0; } - - stream->isActive = 0; - stream->isStopped = 1; - return err; -} +} -static PaError StopStream( PaStream *s ) +static void setStreamStartTime( PaStream *stream ) { - // TODO: this should be nicer than abort - return AbortStream(s); + /* FIXME: I am not at all sure this timing info stuff is right. + patest_sine_time reports negative latencies, which is wierd.*/ + PaMacCoreStream *s = (PaMacCoreStream *) stream; + VVDBUG(("setStreamStartTime()\n")); + if( s->outputDevice ) + AudioDeviceGetCurrentTime( s->outputDevice, &s->startTime); + else if( s->inputDevice ) + AudioDeviceGetCurrentTime( s->inputDevice, &s->startTime); + else + bzero( &s->startTime, sizeof( s->startTime ) ); + + //FIXME: we need a memory barier here + + s->isTimeSet = TRUE; } -static PaError IsStreamStopped( PaStream *s ) + +static PaTime TimeStampToSecs(PaMacCoreStream *stream, const AudioTimeStamp* timeStamp) { - PaMacCoreStream *stream = (PaMacCoreStream*)s; - - return stream->isStopped; + VVDBUG(("TimeStampToSecs()\n")); + //printf( "ATS: %lu, %g, %g\n", timeStamp->mFlags, timeStamp->mSampleTime, timeStamp->mRateScalar ); + if (timeStamp->mFlags & kAudioTimeStampSampleTimeValid) + return (timeStamp->mSampleTime / stream->sampleRate); + else + return 0; } +#define RING_BUFFER_EMPTY (1000) -static PaError IsStreamActive( PaStream *s ) +static OSStatus ringBufferIOProc( AudioConverterRef inAudioConverter, + UInt32*ioDataSize, + void** outData, + void*inUserData ) { - PaMacCoreStream *stream = (PaMacCoreStream*)s; + void *dummyData; + long dummySize; + RingBuffer *rb = (RingBuffer *) inUserData; + + VVDBUG(("ringBufferIOProc()\n")); + + assert( sizeof( UInt32 ) == sizeof( long ) ); + if( RingBuffer_GetReadAvailable( rb ) == 0 ) { + *outData = NULL; + *ioDataSize = 0; + return RING_BUFFER_EMPTY; + } + RingBuffer_GetReadRegions( rb, *ioDataSize, + outData, (long *)ioDataSize, + &dummyData, &dummySize ); + + assert( *ioDataSize ); + RingBuffer_AdvanceReadIndex( rb, *ioDataSize ); + + return noErr; +} - return stream->isActive; +/* + * Called by the AudioUnit API to process audio from the sound card. + * This is where the magic happens. + */ +/* FEEDBACK: there is a lot of redundant code here because of how all the cases differ. This makes it hard to maintain, so if there are suggestinos for cleaning it up, I'm all ears. */ +static OSStatus AudioIOProc( void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData ) +{ + unsigned long framesProcessed = 0; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; + PaMacCoreStream *stream = (PaMacCoreStream*)inRefCon; + const bool isRender = inBusNumber == OUTPUT_ELEMENT; + int callbackResult = paContinue ; + + VVDBUG(("AudioIOProc()\n")); + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* -----------------------------------------------------------------*\ + This output may be useful for debugging, + But printing durring the callback is a bad enough idea that + this is not enabled by enableing the usual debugging calls. + \* -----------------------------------------------------------------*/ + /* + static int renderCount = 0; + static int inputCount = 0; + printf( "------------------- starting reder/input\n" ); + if( isRender ) + printf("Render callback (%d):\t", ++renderCount); + else + printf("Input callback (%d):\t", ++inputCount); + printf( "Call totals: %d (input), %d (render)\n", inputCount, renderCount ); + + printf( "--- inBusNumber: %lu\n", inBusNumber ); + printf( "--- inNumberFrames: %lu\n", inNumberFrames ); + printf( "--- %x ioData\n", (unsigned) ioData ); + if( ioData ) + { + int i=0; + printf( "--- ioData.mNumBuffers %lu: \n", ioData->mNumberBuffers ); + for( i=0; imNumberBuffers; ++i ) + printf( "--- ioData buffer %d size: %lu.\n", i, ioData->mBuffers[i].mDataByteSize ); + } + ----------------------------------------------------------------- */ + + if( !stream->isTimeSet ) + setStreamStartTime( stream ); + + if( isRender ) { + AudioTimeStamp currentTime; + timeInfo.outputBufferDacTime = TimeStampToSecs(stream, inTimeStamp); + AudioDeviceGetCurrentTime(stream->outputDevice, ¤tTime); + timeInfo.currentTime = TimeStampToSecs(stream, ¤tTime); + } + if( isRender && stream->inputUnit == stream->outputUnit ) + timeInfo.inputBufferAdcTime = TimeStampToSecs(stream, inTimeStamp); + if( !isRender ) { + AudioTimeStamp currentTime; + timeInfo.inputBufferAdcTime = TimeStampToSecs(stream, inTimeStamp); + AudioDeviceGetCurrentTime(stream->inputDevice, ¤tTime); + timeInfo.currentTime = TimeStampToSecs(stream, ¤tTime); + } + + //printf( "---%g, %g, %g\n", timeInfo.inputBufferAdcTime, timeInfo.currentTime, timeInfo.outputBufferDacTime ); + + if( isRender && stream->inputUnit == stream->outputUnit + && !stream->inputSRConverter ) + { + /* --------- Full Duplex, One Device, no SR Conversion ------- + * + * This is the lowest latency case, and also the simplest. + * Input data and output data are available at the same time. + * we do not use the input SR converter or the input ring buffer. + * + */ + OSErr err = 0; + unsigned long frames; + + /* -- start processing -- */ + PaUtil_BeginBufferProcessing( &(stream->bufferProcessor), + &timeInfo, + stream->xrunFlags ); + stream->xrunFlags = 0; + + /* -- compute frames. do some checks -- */ + assert( ioData->mNumberBuffers == 1 ); + assert( ioData->mBuffers[0].mNumberChannels == stream->userOutChan ); + frames = ioData->mBuffers[0].mDataByteSize; + frames /= sizeof( float ) * ioData->mBuffers[0].mNumberChannels; + /* -- copy and process input data -- */ + err= AudioUnitRender(stream->inputUnit, + ioActionFlags, + inTimeStamp, + INPUT_ELEMENT, + inNumberFrames, + &stream->inputAudioBufferList ); + /* FEEDBACK: I'm not sure what to do when this call fails */ + assert( !err ); + + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + stream->inputAudioBufferList.mBuffers[0].mData, + stream->inputAudioBufferList.mBuffers[0].mNumberChannels); + /* -- Copy and process output data -- */ + PaUtil_SetOutputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedOutputChannels( &(stream->bufferProcessor), + 0, + ioData->mBuffers[0].mData, + ioData->mBuffers[0].mNumberChannels); + /* -- complete processing -- */ + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + else if( isRender ) + { + /* -------- Output Side of Full Duplex (Separate Devices or SR Conversion) + * -- OR Simplex Output + * + * This case handles output data as in the full duplex case, + * and, if there is input data, reads it off the ring buffer + * and into the PA buffer processor. If sample rate conversion + * is required on input, that is done here as well. + */ + unsigned long frames; + + /* Sometimes, when stopping a duplex stream we get erroneous + xrun flags, so if this is our last run, clear the flags. */ + int xrunFlags = stream->xrunFlags; +/* + if( xrunFlags & paInputUnderflow ) + printf( "input underflow.\n" ); + if( xrunFlags & paInputOverflow ) + printf( "input overflow.\n" ); +*/ + if( stream->state == STOPPING || stream->state == CALLBACK_STOPPED ) + xrunFlags = 0; + + /* -- start processing -- */ + PaUtil_BeginBufferProcessing( &(stream->bufferProcessor), + &timeInfo, + xrunFlags ); + stream->xrunFlags = 0; /* FEEDBACK: we only send flags to Buf Proc once */ + + /* -- Copy and process output data -- */ + assert( ioData->mNumberBuffers == 1 ); + frames = ioData->mBuffers[0].mDataByteSize; + frames /= sizeof( float ) * ioData->mBuffers[0].mNumberChannels; + assert( ioData->mBuffers[0].mNumberChannels == stream->userOutChan ); + PaUtil_SetOutputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedOutputChannels( &(stream->bufferProcessor), + 0, + ioData->mBuffers[0].mData, + ioData->mBuffers[0].mNumberChannels); + + /* -- copy and process input data, and complete processing -- */ + if( stream->inputUnit ) { + const int flsz = sizeof( float ); + /* Here, we read the data out of the ring buffer, through the + audio converter. */ + int inChan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels; + if( stream->inputSRConverter ) + { + OSStatus err; + UInt32 size; + float data[ inChan * frames ]; + size = sizeof( data ); + err = AudioConverterFillBuffer( + stream->inputSRConverter, + ringBufferIOProc, + &stream->inputRingBuffer, + &size, + (void *)&data ); + if( err == RING_BUFFER_EMPTY ) + { /*the ring buffer callback underflowed */ + err = 0; + bzero( ((char *)data) + size, sizeof(data)-size ); + stream->xrunFlags |= paInputUnderflow; + } + ERR( err ); + assert( !err ); + + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data, + inChan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + else + { + /* Without the AudioConverter is actually a bit more complex + because we have to do a little buffer processing that the + AudioConverter would otherwise handle for us. */ + void *data1, *data2; + long size1, size2; + RingBuffer_GetReadRegions( &stream->inputRingBuffer, + inChan*frames*flsz, + &data1, &size1, + &data2, &size2 ); + if( size1 / ( flsz * inChan ) == frames ) { + /* simplest case: all in first buffer */ + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data1, + inChan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + RingBuffer_AdvanceReadIndex(&stream->inputRingBuffer, size1 ); + } else if( ( size1 + size2 ) / ( flsz * inChan ) < frames ) { + /*we underflowed. take what data we can, zero the rest.*/ + float data[frames*inChan]; + if( size1 ) + memcpy( data, data1, size1 ); + if( size2 ) + memcpy( data+size1, data2, size2 ); + bzero( data+size1+size2, frames*flsz*inChan - size1 - size2 ); + + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), frames ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data, + inChan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + RingBuffer_AdvanceReadIndex( &stream->inputRingBuffer, + size1+size2 ); + /* flag underflow */ + stream->xrunFlags |= paInputUnderflow; + } else { + /*we got all the data, but split between buffers*/ + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), + size1 / ( flsz * inChan ) ); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data1, + inChan ); + PaUtil_Set2ndInputFrameCount( &(stream->bufferProcessor), + size2 / ( flsz * inChan ) ); + PaUtil_Set2ndInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data2, + inChan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + RingBuffer_AdvanceReadIndex(&stream->inputRingBuffer, size1+size2 ); + } + } + } else { + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + + } + else + { + /* ------------------ Input + * + * First, we read off the audio data and put it in the ring buffer. + * if this is an input-only stream, we need to process it more, + * otherwise, we let the output case deal with it. + */ + OSErr err = 0; + int chan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels ; + /* FIXME: looping here may not actually be necessary, but it was something I tried in testing. */ + do { + err= AudioUnitRender(stream->inputUnit, + ioActionFlags, + inTimeStamp, + INPUT_ELEMENT, + inNumberFrames, + &stream->inputAudioBufferList ); + if( err == -10874 ) + inNumberFrames /= 2; + } while( err == -10874 && inNumberFrames > 1 ); + /* FEEDBACK: I'm not sure what to do when this call fails */ + ERR( err ); + assert( !err ); + if( stream->inputSRConverter || stream->outputUnit ) + { + /* If this is duplex or we use a converter, put the data + into the ring buffer. */ + long bytesIn, bytesOut; + bytesIn = sizeof( float ) * inNumberFrames * chan; + bytesOut = RingBuffer_Write( &stream->inputRingBuffer, + stream->inputAudioBufferList.mBuffers[0].mData, + bytesIn ); + if( bytesIn != bytesOut ) + stream->xrunFlags |= paInputOverflow ; + } + else + { + /* for simplex input w/o SR conversion, + just pop the data into the buffer processor.*/ + PaUtil_BeginBufferProcessing( &(stream->bufferProcessor), + &timeInfo, + stream->xrunFlags ); + stream->xrunFlags = 0; + + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), inNumberFrames); + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + stream->inputAudioBufferList.mBuffers[0].mData, + chan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + if( !stream->outputUnit && stream->inputSRConverter ) + { + /* ------------------ Simplex Input w/ SR Conversion + * + * if this is a simplex input stream, we need to read off the buffer, + * do our sample rate conversion and pass the results to the buffer + * processor. + * The logic here is complicated somewhat by the fact that we don't + * know how much data is available, so we loop on reasonably sized + * chunks, and let the BufferProcessor deal with the rest. + * + */ + /*This might be too big or small depending on SR conversion*/ + float data[ chan * inNumberFrames ]; + OSStatus err; + do + { /*Run the buffer processor until we are out of data*/ + UInt32 size; + long f; + + size = sizeof( data ); + err = AudioConverterFillBuffer( + stream->inputSRConverter, + ringBufferIOProc, + &stream->inputRingBuffer, + &size, + (void *)data ); + if( err != RING_BUFFER_EMPTY ) + ERR( err ); + assert( err == 0 || err == RING_BUFFER_EMPTY ); + + f = size / ( chan * sizeof(float) ); + PaUtil_SetInputFrameCount( &(stream->bufferProcessor), f ); + if( f ) + { + PaUtil_BeginBufferProcessing( &(stream->bufferProcessor), + &timeInfo, + stream->xrunFlags ); + stream->xrunFlags = 0; + + PaUtil_SetInterleavedInputChannels( &(stream->bufferProcessor), + 0, + data, + chan ); + framesProcessed = + PaUtil_EndBufferProcessing( &(stream->bufferProcessor), + &callbackResult ); + } + } while( callbackResult == paContinue && !err ); + } + } + + switch( callbackResult ) + { + case paContinue: break; + case paComplete: + case paAbort: + stream->isTimeSet = FALSE; + stream->state = CALLBACK_STOPPED ; + if( stream->outputUnit ) + AudioOutputUnitStop(stream->outputUnit); + if( stream->inputUnit ) + AudioOutputUnitStop(stream->inputUnit); + break; + } + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + return noErr; } -static PaTime GetStreamTime( PaStream *s ) +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) { - OSStatus err; - PaTime result; + /* This may be called from a failed OpenStream. + Therefore, each piece of info is treated seperately. */ + PaError result = paNoError; PaMacCoreStream *stream = (PaMacCoreStream*)s; - AudioTimeStamp *timeStamp = PaUtil_AllocateMemory(sizeof(AudioTimeStamp)); - if (stream->inputDevice != kAudioDeviceUnknown) { - err = AudioDeviceGetCurrentTime(stream->inputDevice, timeStamp); - } - else { - err = AudioDeviceGetCurrentTime(stream->outputDevice, timeStamp); + VVDBUG(("CloseStream()\n")); + VDBUG( ( "Closing stream.\n" ) ); + + if( stream ) { + if( stream->outputUnit && stream->outputUnit != stream->inputUnit ) { + AudioUnitUninitialize( stream->outputUnit ); + CloseComponent( stream->outputUnit ); + } + stream->outputUnit = NULL; + if( stream->inputUnit ) + { + AudioUnitUninitialize( stream->inputUnit ); + CloseComponent( stream->inputUnit ); + stream->inputUnit = NULL; + } + if( stream->inputRingBuffer.buffer ) + free( (void *) stream->inputRingBuffer.buffer ); + stream->inputRingBuffer.buffer = NULL; + /*TODO: is there more that needs to be done on error + from AudioConverterDispose?*/ + if( stream->inputSRConverter ) + ERR( AudioConverterDispose( stream->inputSRConverter ) ); + stream->inputSRConverter = NULL; + if( stream->inputAudioBufferList.mBuffers[0].mData ) + free( stream->inputAudioBufferList.mBuffers[0].mData ); + stream->inputAudioBufferList.mBuffers[0].mData = NULL; + + result = destroyBlioRingBuffers( &stream->blio ); + if( result ) + return result; + if( stream->bufferProcessorIsInitialized ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); } - - result = err ? 0 : timeStamp->mSampleTime; - PaUtil_FreeMemory(timeStamp); return result; } -static double GetStreamCpuLoad( PaStream* s ) +static PaError StartStream( PaStream *s ) { PaMacCoreStream *stream = (PaMacCoreStream*)s; - - return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); -} + OSErr result = noErr; + VVDBUG(("StartStream()\n")); + VDBUG( ( "Starting stream.\n" ) ); +#define ERR_WRAP(mac_err) do { result = mac_err ; if ( result != noErr ) return ERR(result) ; } while(0) -// As separate stream interfaces are used for blocking and callback streams, the following functions can be guaranteed to only be called for blocking streams. + /*FIXME: maybe want to do this on close/abort for faster start? */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + if( stream->inputSRConverter ) + ERR_WRAP( AudioConverterReset( stream->inputSRConverter ) ); -static PaError ReadStream( PaStream* s, - void *buffer, - unsigned long frames ) -{ - return paInternalError; + /* -- start -- */ + stream->state = ACTIVE; + if( stream->inputUnit ) { + ERR_WRAP( AudioOutputUnitStart(stream->inputUnit) ); + } + if( stream->outputUnit && stream->outputUnit != stream->inputUnit ) { + ERR_WRAP( AudioOutputUnitStart(stream->outputUnit) ); + } + + //setStreamStartTime( stream ); + //stream->isTimeSet = TRUE; + + return paNoError; +#undef ERR_WRAP } -static PaError WriteStream( PaStream* s, - const void *buffer, - unsigned long frames ) +static PaError StopStream( PaStream *s ) { - return paInternalError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + OSErr result = noErr; + PaError paErr; + VVDBUG(("StopStream()\n")); + + VDBUG( ("Waiting for BLIO.\n") ); + waitUntilBlioWriteBufferIsFlushed( &stream->blio ); + VDBUG( ( "Stopping stream.\n" ) ); + + stream->isTimeSet = FALSE; + stream->state = STOPPING; + +#define ERR_WRAP(mac_err) do { result = mac_err ; if ( result != noErr ) return ERR(result) ; } while(0) + /* -- stop and reset -- */ + if( stream->inputUnit == stream->outputUnit && stream->inputUnit ) + { + ERR_WRAP( AudioOutputUnitStop(stream->inputUnit) ); + ERR_WRAP( AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 1) ); + ERR_WRAP( AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 0) ); + } + else + { + if( stream->inputUnit ) + { + ERR_WRAP(AudioOutputUnitStop(stream->inputUnit) ); + ERR_WRAP(AudioUnitReset(stream->inputUnit,kAudioUnitScope_Global,1)); + } + if( stream->outputUnit ) + { + ERR_WRAP(AudioOutputUnitStop(stream->outputUnit)); + ERR_WRAP(AudioUnitReset(stream->outputUnit,kAudioUnitScope_Global,0)); + } + } + if( stream->inputRingBuffer.buffer ) { + RingBuffer_Flush( &stream->inputRingBuffer ); + bzero( (void *)stream->inputRingBuffer.buffer, + stream->inputRingBuffer.bufferSize ); + /* advance the write point a little, so we are reading from the + middle of the buffer. We'll need extra at the end because + testing has shown that this helps. */ + if( stream->outputUnit ) + RingBuffer_AdvanceWriteIndex( &stream->inputRingBuffer, + stream->inputRingBuffer.bufferSize + / RING_BUFFER_ADVANCE_DENOMINATOR ); + } + + stream->xrunFlags = 0; + stream->state = STOPPED; + + paErr = resetBlioRingBuffers( &stream->blio ); + if( paErr ) + return paErr; + +/* + //stream->isTimeSet = FALSE; +*/ + + VDBUG( ( "Stream Stopped.\n" ) ); + return paNoError; +#undef ERR_WRAP +} + +static PaError AbortStream( PaStream *s ) +{ + VVDBUG(("AbortStream()->StopStream()\n")); + VDBUG( ( "Aborting stream.\n" ) ); + /* We have nothing faster than StopStream. */ + return StopStream(s); } -static signed long GetStreamReadAvailable( PaStream* s ) +static PaError IsStreamStopped( PaStream *s ) { - return paInternalError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + VVDBUG(("IsStreamStopped()\n")); + + return stream->state == STOPPED ? 1 : 0; } -static signed long GetStreamWriteAvailable( PaStream* s ) +static PaError IsStreamActive( PaStream *s ) { - return paInternalError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + VVDBUG(("IsStreamActive()\n")); + return ( stream->state == ACTIVE || stream->state == STOPPING ); } -// HostAPI-specific initialization function -PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) + +static double GetStreamCpuLoad( PaStream* s ) { - PaError result = paNoError; - PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation *)PaUtil_AllocateMemory( sizeof(PaMacCoreHostApiRepresentation) ); - if( !macCoreHostApi ) - { - result = paInsufficientMemory; - goto error; - } - - macCoreHostApi->allocations = PaUtil_CreateAllocationGroup(); - if( !macCoreHostApi->allocations ) - { - result = paInsufficientMemory; - goto error; - } - - *hostApi = &macCoreHostApi->inheritedHostApiRep; - (*hostApi)->info.structVersion = 1; - (*hostApi)->info.type = paCoreAudio; - (*hostApi)->info.name = "CoreAudio"; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + VVDBUG(("GetStreamCpuLoad()\n")); - result = InitializeDeviceInfos(macCoreHostApi, hostApiIndex); - if (result != paNoError) { - goto error; - } - - // Set up the proper callbacks to this HostApi's functions - (*hostApi)->Terminate = Terminate; - (*hostApi)->OpenStream = OpenStream; - (*hostApi)->IsFormatSupported = IsFormatSupported; - - PaUtil_InitializeStreamInterface( &macCoreHostApi->callbackStreamInterface, CloseStream, StartStream, - StopStream, AbortStream, IsStreamStopped, IsStreamActive, - GetStreamTime, GetStreamCpuLoad, - PaUtil_DummyRead, PaUtil_DummyWrite, - PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); - - PaUtil_InitializeStreamInterface( &macCoreHostApi->blockingStreamInterface, CloseStream, StartStream, - StopStream, AbortStream, IsStreamStopped, IsStreamActive, - GetStreamTime, PaUtil_DummyGetCpuLoad, - ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); - - return result; - -error: - if( macCoreHostApi ) { - CleanUp(macCoreHostApi); - } - - return result; + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); } diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_core.h b/pjmedia/src/pjmedia/portaudio/pa_mac_core.h new file mode 100644 index 00000000..f810e2f6 --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_core.h @@ -0,0 +1,118 @@ +/* + * PortAudio Portable Real-Time Audio Library + * Macintosh Core Audio specific extensions + * portaudio.h should be included before this file. + * + * Copyright (c) 2005-2006 Bjorn Roche + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * A pointer to a paMacCoreStreamInfo may be passed as + * the hostApiSpecificStreamInfo in the PaStreamParameters struct + * when opening a stream or querying the format. Use NULL, for the + * defaults. Note that for duplex streams, both infos should be the + * same or behaviour is undefined. + */ +typedef struct paMacCoreStreamInfo +{ + unsigned long size; /**size of whole structure including this header */ + PaHostApiTypeId hostApiType;/**host API for which this data is intended */ + unsigned long version; /**structure version */ + unsigned long flags; /* flags to modify behaviour */ +} paMacCoreStreamInfo; + +/* Use this function to initialize a paMacCoreStreamInfo struct + using the requested flags. */ +void paSetupMacCoreStreamInfo( paMacCoreStreamInfo *data, unsigned long flags ) +{ + bzero( data, sizeof( paMacCoreStreamInfo ) ); + data->size = sizeof( paMacCoreStreamInfo ); + data->hostApiType = paCoreAudio; + data->version = 0x01; + data->flags = flags; +} + +/* + * The following flags alter the behaviour of PA on the mac platform. + * they can be ORed together. These should work both for opening and + * checking a device. + */ +/* Allows PortAudio to change things like the device's frame size, + * which allows for much lower latency, but might disrupt the device + * if other programs are using it, even when you are just Querying + * the device. */ +const unsigned long paMacCore_ChangeDeviceParameters = 0x01; + +/* In combination with the above flag, + * causes the stream opening to fail, unless the exact sample rates + * are supported by the device. */ +const unsigned long paMacCore_FailIfConversionRequired = 0x02; + +/* These flags set the SR conversion quality, if required. The wierd ordering + * allows Maximum Quality to be the default.*/ +const unsigned long paMacCore_ConversionQualityMin = 0x0100; +const unsigned long paMacCore_ConversionQualityMedium = 0x0200; +const unsigned long paMacCore_ConversionQualityLow = 0x0300; +const unsigned long paMacCore_ConversionQualityHigh = 0x0400; +const unsigned long paMacCore_ConversionQualityMax = 0x0000; + +/* + * Here are some "preset" combinations of flags (above) to get to some + * common configurations. THIS IS OVERKILL, but if more flags are added + * it won't be. + */ +/*This is the default setting: do as much sample rate conversion as possible + * and as little mucking with the device as possible. */ +const unsigned long paMacCorePlayNice = 0x00; +/*This setting is tuned for pro audio apps. It allows SR conversion on input + and output, but it tries to set the appropriate SR on the device.*/ +const unsigned long paMacCorePro = 0x01; +/*This is a setting to minimize CPU usage and still play nice.*/ +const unsigned long paMacCoreMinimizeCPUButPlayNice = 0x0100; +/*This is a setting to minimize CPU usage, even if that means interrupting the device. */ +const unsigned long paMacCoreMinimizeCPU = 0x0101; + +#ifdef PA_OLD_CORE_AUDIO +# define kAudioOutputUnitProperty_EnableIO 2003 +# define kAudioOutputUnitProperty_SetInputCallback 2005 +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_core_blocking.c b/pjmedia/src/pjmedia/portaudio/pa_mac_core_blocking.c new file mode 100644 index 00000000..f474e7b0 --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_core_blocking.c @@ -0,0 +1,564 @@ +/* + * Implementation of the PortAudio API for Apple AUHAL + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file + @ingroup hostaip_src + + This file contains the implementation + required for blocking I/O. It is separated from pa_mac_core.c simply to ease + development. +*/ + +#include "pa_mac_core_blocking.h" +#include "pa_mac_core_internal.h" +#include +#ifdef MOSX_USE_NON_ATOMIC_FLAG_BITS +# define OSAtomicOr32( a, b ) ( (*(b)) |= (a) ) +# define OSAtomicAnd32( a, b ) ( (*(b)) &= (a) ) +#else +# include +#endif + +/* + * This fnuction determines the size of a particular sample format. + * if the format is not recognized, this returns zero. + */ +static size_t computeSampleSizeFromFormat( PaSampleFormat format ) +{ + switch( format ) { + case paFloat32: return 4; + case paInt32: return 4; + case paInt24: return 3; + case paInt16: return 2; + case paInt8: case paUInt8: return 1; + default: return 0; + } +} + + +/* + * Functions for initializing, resetting, and destroying BLIO structures. + * + */ + +/* This should be called with the relevant info when initializing a stream for + callback. */ +PaError initializeBlioRingBuffers( + PaMacBlio *blio, + PaSampleFormat inputSampleFormat, + PaSampleFormat outputSampleFormat, + size_t framesPerBuffer, + long ringBufferSize, + int inChan, + int outChan ) +{ + void *data; + int result; + + /* zeroify things */ + bzero( blio, sizeof( PaMacBlio ) ); + /* this is redundant, but the buffers are used to check + if the bufffers have been initialized, so we do it explicitly. */ + blio->inputRingBuffer.buffer = NULL; + blio->outputRingBuffer.buffer = NULL; + + /* initialize simple data */ + blio->inputSampleFormat = inputSampleFormat; + blio->inputSampleSize = computeSampleSizeFromFormat(inputSampleFormat); + blio->outputSampleFormat = outputSampleFormat; + blio->outputSampleSize = computeSampleSizeFromFormat(outputSampleFormat); + blio->framesPerBuffer = framesPerBuffer; + blio->inChan = inChan; + blio->outChan = outChan; + blio->statusFlags = 0; + blio->errors = paNoError; +#ifdef PA_MAC_BLIO_MUTEX + blio->isInputEmpty = false; + blio->isOutputFull = false; +#endif + + /* setup ring buffers */ +#ifdef PA_MAC_BLIO_MUTEX + result = PaMacCore_SetUnixError( pthread_mutex_init(&(blio->inputMutex),NULL), 0 ); + if( result ) + goto error; + result = UNIX_ERR( pthread_cond_init( &(blio->inputCond), NULL ) ); + if( result ) + goto error; + result = UNIX_ERR( pthread_mutex_init(&(blio->outputMutex),NULL) ); + if( result ) + goto error; + result = UNIX_ERR( pthread_cond_init( &(blio->outputCond), NULL ) ); +#endif + if( inChan ) { + data = calloc( ringBufferSize, blio->inputSampleSize ); + if( !data ) + { + result = paInsufficientMemory; + goto error; + } + + assert( 0 == RingBuffer_Init( + &blio->inputRingBuffer, + ringBufferSize*blio->inputSampleSize, + data ) ); + } + if( outChan ) { + data = calloc( ringBufferSize, blio->outputSampleSize ); + if( !data ) + { + result = paInsufficientMemory; + goto error; + } + + assert( 0 == RingBuffer_Init( + &blio->outputRingBuffer, + ringBufferSize*blio->outputSampleSize, + data ) ); + } + + result = resetBlioRingBuffers( blio ); + if( result ) + goto error; + + return 0; + + error: + destroyBlioRingBuffers( blio ); + return result; +} + +#ifdef PA_MAC_BLIO_MUTEX +PaError blioSetIsInputEmpty( PaMacBlio *blio, bool isEmpty ) +{ + PaError result = paNoError; + if( isEmpty == blio->isInputEmpty ) + goto done; + + /* we need to update the value. Here's what we do: + * - Lock the mutex, so noone else can write. + * - update the value. + * - unlock. + * - broadcast to all listeners. + */ + result = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) ); + if( result ) + goto done; + blio->isInputEmpty = isEmpty; + result = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) ); + if( result ) + goto done; + result = UNIX_ERR( pthread_cond_broadcast( &blio->inputCond ) ); + if( result ) + goto done; + + done: + return result; +} +PaError blioSetIsOutputFull( PaMacBlio *blio, bool isFull ) +{ + PaError result = paNoError; + if( isFull == blio->isOutputFull ) + goto done; + + /* we need to update the value. Here's what we do: + * - Lock the mutex, so noone else can write. + * - update the value. + * - unlock. + * - broadcast to all listeners. + */ + result = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) ); + if( result ) + goto done; + blio->isOutputFull = isFull; + result = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) ); + if( result ) + goto done; + result = UNIX_ERR( pthread_cond_broadcast( &blio->outputCond ) ); + if( result ) + goto done; + + done: + return result; +} +#endif + +/* This should be called after stopping or aborting the stream, so that on next + start, the buffers will be ready. */ +PaError resetBlioRingBuffers( PaMacBlio *blio ) +{ +#ifdef PA_MAC__BLIO_MUTEX + int result; +#endif + blio->statusFlags = 0; + if( blio->outputRingBuffer.buffer ) { + RingBuffer_Flush( &blio->outputRingBuffer ); + bzero( blio->outputRingBuffer.buffer, + blio->outputRingBuffer.bufferSize ); + /* Advance buffer */ + RingBuffer_AdvanceWriteIndex( &blio->outputRingBuffer, blio->outputRingBuffer.bufferSize ); + + /* Update isOutputFull. */ +#ifdef PA_MAC__BLIO_MUTEX + result = blioSetIsOutputFull( blio, toAdvance == blio->outputRingBuffer.bufferSize ); + if( result ) + goto error; +#endif +/* + printf( "------%d\n" , blio->framesPerBuffer ); + printf( "------%d\n" , blio->outChan ); + printf( "------%d\n" , blio->outputSampleSize ); + printf( "------%d\n" , blio->framesPerBuffer*blio->outChan*blio->outputSampleSize ); +*/ + } + if( blio->inputRingBuffer.buffer ) { + RingBuffer_Flush( &blio->inputRingBuffer ); + bzero( blio->inputRingBuffer.buffer, + blio->inputRingBuffer.bufferSize ); + /* Update isInputEmpty. */ +#ifdef PA_MAC__BLIO_MUTEX + result = blioSetIsInputEmpty( blio, true ); + if( result ) + goto error; +#endif + } + return paNoError; +#ifdef PA_MAC__BLIO_MUTEX + error: + return result; +#endif +} + +/*This should be called when you are done with the blio. It can safely be called + multiple times if there are no exceptions. */ +PaError destroyBlioRingBuffers( PaMacBlio *blio ) +{ + PaError result = paNoError; + if( blio->inputRingBuffer.buffer ) { + free( blio->inputRingBuffer.buffer ); +#ifdef PA_MAC__BLIO_MUTEX + result = UNIX_ERR( pthread_mutex_destroy( & blio->inputMutex ) ); + if( result ) return result; + result = UNIX_ERR( pthread_cond_destroy( & blio->inputCond ) ); + if( result ) return result; +#endif + } + blio->inputRingBuffer.buffer = NULL; + if( blio->outputRingBuffer.buffer ) { + free( blio->outputRingBuffer.buffer ); +#ifdef PA_MAC__BLIO_MUTEX + result = UNIX_ERR( pthread_mutex_destroy( & blio->outputMutex ) ); + if( result ) return result; + result = UNIX_ERR( pthread_cond_destroy( & blio->outputCond ) ); + if( result ) return result; +#endif + } + blio->outputRingBuffer.buffer = NULL; + + return result; +} + +/* + * this is the BlioCallback function. It expects to recieve a PaMacBlio Object + * pointer as userData. + * + */ +int BlioCallback( const void *input, void *output, unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + PaMacBlio *blio = (PaMacBlio*)userData; + long avail; + long toRead; + long toWrite; + + /* set flags returned by OS: */ + OSAtomicOr32( statusFlags, &blio->statusFlags ) ; + + /* --- Handle Input Buffer --- */ + if( blio->inChan ) { + avail = RingBuffer_GetWriteAvailable( &blio->inputRingBuffer ); + + /* check for underflow */ + if( avail < frameCount * blio->inputSampleSize * blio->inChan ) + OSAtomicOr32( paInputOverflow, &blio->statusFlags ); + + toRead = MIN( avail, frameCount * blio->inputSampleSize * blio->inChan ); + + /* copy the data */ + /*printf( "reading %d\n", toRead );*/ + assert( toRead == RingBuffer_Write( &blio->inputRingBuffer, input, toRead ) ); +#ifdef PA_MAC__BLIO_MUTEX + /* Priority inversion. See notes below. */ + blioSetIsInputEmpty( blio, false ); +#endif + } + + + /* --- Handle Output Buffer --- */ + if( blio->outChan ) { + avail = RingBuffer_GetReadAvailable( &blio->outputRingBuffer ); + + /* check for underflow */ + if( avail < frameCount * blio->outputSampleSize * blio->outChan ) + OSAtomicOr32( paOutputUnderflow, &blio->statusFlags ); + + toWrite = MIN( avail, frameCount * blio->outputSampleSize * blio->outChan ); + + if( toWrite != frameCount * blio->outputSampleSize * blio->outChan ) + bzero( ((char *)output)+toWrite, + frameCount * blio->outputSampleSize * blio->outChan - toWrite ); + /* copy the data */ + /*printf( "writing %d\n", toWrite );*/ + assert( toWrite == RingBuffer_Read( &blio->outputRingBuffer, output, toWrite ) ); +#ifdef PA_MAC__BLIO_MUTEX + /* We have a priority inversion here. However, we will only have to + wait if this was true and is now false, which means we've got + some room in the buffer. + Hopefully problems will be minimized. */ + blioSetIsOutputFull( blio, false ); +#endif + } + + return paContinue; +} + +PaError ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ) +{ + PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio; + char *cbuf = (char *) buffer; + PaError ret = paNoError; + VVDBUG(("ReadStream()\n")); + + while( frames > 0 ) { + long avail; + long toRead; + do { + avail = RingBuffer_GetReadAvailable( &blio->inputRingBuffer ); +/* + printf( "Read Buffer is %%%g full: %ld of %ld.\n", + 100 * (float)avail / (float) blio->inputRingBuffer.bufferSize, + avail, blio->inputRingBuffer.bufferSize ); +*/ + if( avail == 0 ) { +#ifdef PA_MAC_BLIO_MUTEX + /**block when empty*/ + ret = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) ); + if( ret ) + return ret; + while( blio->isInputEmpty ) { + ret = UNIX_ERR( pthread_cond_wait( &blio->inputCond, &blio->inputMutex ) ); + if( ret ) + return ret; + } + ret = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) ); + if( ret ) + return ret; +#else + Pa_Sleep( PA_MAC_BLIO_BUSY_WAIT_SLEEP_INTERVAL ); +#endif + } + } while( avail == 0 ); + toRead = MIN( avail, frames * blio->inputSampleSize * blio->inChan ); + toRead -= toRead % blio->inputSampleSize * blio->inChan ; + RingBuffer_Read( &blio->inputRingBuffer, (void *)cbuf, toRead ); + cbuf += toRead; + frames -= toRead / ( blio->inputSampleSize * blio->inChan ); + + if( toRead == avail ) { +#ifdef PA_MAC_BLIO_MUTEX + /* we just emptied the buffer, so we need to mark it as empty. */ + ret = blioSetIsInputEmpty( blio, true ); + if( ret ) + return ret; + /* of course, in the meantime, the callback may have put some sats + in, so + so check for that, too, to avoid a race condition. */ + if( RingBuffer_GetReadAvailable( &blio->inputRingBuffer ) ) { + blioSetIsInputEmpty( blio, false ); + if( ret ) + return ret; + } +#endif + } + } + + /* Report either paNoError or paInputOverflowed. */ + /* may also want to report other errors, but this is non-standard. */ + ret = blio->statusFlags & paInputOverflow; + + /* report underflow only once: */ + if( ret ) { + OSAtomicAnd32( ~paInputOverflow, &blio->statusFlags ); + ret = paInputOverflowed; + } + + return ret; +} + + +PaError WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ) +{ + PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio; + char *cbuf = (char *) buffer; + PaError ret = paNoError; + VVDBUG(("WriteStream()\n")); + + while( frames > 0 ) { + long avail = 0; + long toWrite; + + do { + avail = RingBuffer_GetWriteAvailable( &blio->outputRingBuffer ); +/* + printf( "Write Buffer is %%%g full: %ld of %ld.\n", + 100 - 100 * (float)avail / (float) blio->outputRingBuffer.bufferSize, + avail, blio->outputRingBuffer.bufferSize ); +*/ + if( avail == 0 ) { +#ifdef PA_MAC_BLIO_MUTEX + /*block while full*/ + ret = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) ); + if( ret ) + return ret; + while( blio->isOutputFull ) { + ret = UNIX_ERR( pthread_cond_wait( &blio->outputCond, &blio->outputMutex ) ); + if( ret ) + return ret; + } + ret = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) ); + if( ret ) + return ret; +#else + Pa_Sleep( PA_MAC_BLIO_BUSY_WAIT_SLEEP_INTERVAL ); +#endif + } + } while( avail == 0 ); + + toWrite = MIN( avail, frames * blio->outputSampleSize * blio->outChan ); + toWrite -= toWrite % blio->outputSampleSize * blio->outChan ; + RingBuffer_Write( &blio->outputRingBuffer, (void *)cbuf, toWrite ); + cbuf += toWrite; + frames -= toWrite / ( blio->outputSampleSize * blio->outChan ); + +#ifdef PA_MAC_BLIO_MUTEX + if( toWrite == avail ) { + /* we just filled up the buffer, so we need to mark it as filled. */ + ret = blioSetIsOutputFull( blio, true ); + if( ret ) + return ret; + /* of course, in the meantime, we may have emptied the buffer, so + so check for that, too, to avoid a race condition. */ + if( RingBuffer_GetWriteAvailable( &blio->outputRingBuffer ) ) { + blioSetIsOutputFull( blio, false ); + if( ret ) + return ret; + } + } +#endif + } + + /* Report either paNoError or paOutputUnderflowed. */ + /* may also want to report other errors, but this is non-standard. */ + ret = blio->statusFlags & paOutputUnderflow; + + /* report underflow only once: */ + if( ret ) { + OSAtomicAnd32( ~paOutputUnderflow, &blio->statusFlags ); + ret = paOutputUnderflowed; + } + + return ret; +} + +/* + * + */ +void waitUntilBlioWriteBufferIsFlushed( PaMacBlio *blio ) +{ + if( blio->outputRingBuffer.buffer ) { + long avail = RingBuffer_GetWriteAvailable( &blio->outputRingBuffer ); + while( avail != blio->outputRingBuffer.bufferSize ) { + if( avail == 0 ) + Pa_Sleep( PA_MAC_BLIO_BUSY_WAIT_SLEEP_INTERVAL ); + avail = RingBuffer_GetWriteAvailable( &blio->outputRingBuffer ); + } + } +} + + +signed long GetStreamReadAvailable( PaStream* stream ) +{ + PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio; + VVDBUG(("GetStreamReadAvailable()\n")); + + return RingBuffer_GetReadAvailable( &blio->inputRingBuffer ) + / ( blio->outputSampleSize * blio->outChan ); +} + + +signed long GetStreamWriteAvailable( PaStream* stream ) +{ + PaMacBlio *blio = & ((PaMacCoreStream*)stream) -> blio; + VVDBUG(("GetStreamWriteAvailable()\n")); + + return RingBuffer_GetWriteAvailable( &blio->outputRingBuffer ) + / ( blio->outputSampleSize * blio->outChan ); +} + diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_core_blocking.h b/pjmedia/src/pjmedia/portaudio/pa_mac_core_blocking.h new file mode 100644 index 00000000..8185e203 --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_core_blocking.h @@ -0,0 +1,133 @@ +/* + * Internal blocking interfaces for PortAudio Apple AUHAL implementation + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file + @ingroup hostaip_src +*/ + +#ifndef PA_MAC_CORE_BLOCKING_H_ +#define PA_MAC_CORE_BLOCKING_H_ + +#include "ringbuffer.h" +#include "portaudio.h" +#include "pa_mac_core_utilities.h" + +/* + * Number of miliseconds to busy wait whil waiting for data in blocking calls. + */ +#define PA_MAC_BLIO_BUSY_WAIT_SLEEP_INTERVAL (5) +/* + * Define exactly one of these blocking methods + * PA_MAC_BLIO_MUTEX is not actively maintained. + */ +#define PA_MAC_BLIO_BUSY_WAIT +/* +#define PA_MAC_BLIO_MUTEX +*/ + +typedef struct { + RingBuffer inputRingBuffer; + RingBuffer outputRingBuffer; + PaSampleFormat inputSampleFormat; + size_t inputSampleSize; + PaSampleFormat outputSampleFormat; + size_t outputSampleSize; + + size_t framesPerBuffer; + + int inChan; + int outChan; + + //PaStreamCallbackFlags statusFlags; + uint32_t statusFlags; + PaError errors; + + /* Here we handle blocking, using condition variables. */ +#ifdef PA_MAC_BLIO_MUTEX + volatile bool isInputEmpty; + pthread_mutex_t inputMutex; + pthread_cond_t inputCond; + + volatile bool isOutputFull; + pthread_mutex_t outputMutex; + pthread_cond_t outputCond; +#endif +} +PaMacBlio; + +/* + * These functions operate on condition and related variables. + */ + +PaError initializeBlioRingBuffers( + PaMacBlio *blio, + PaSampleFormat inputSampleFormat, + PaSampleFormat outputSampleFormat, + size_t framesPerBuffer, + long ringBufferSize, + int inChan, + int outChan ); +PaError destroyBlioRingBuffers( PaMacBlio *blio ); +PaError resetBlioRingBuffers( PaMacBlio *blio ); + +int BlioCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +void waitUntilBlioWriteBufferIsFlushed( PaMacBlio *blio ); + +#endif /*PA_MAC_CORE_BLOCKING_H_*/ diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_core_internal.h b/pjmedia/src/pjmedia/portaudio/pa_mac_core_internal.h new file mode 100644 index 00000000..095b3b38 --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_core_internal.h @@ -0,0 +1,162 @@ +/* + * Internal interfaces for PortAudio Apple AUHAL implementation + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file pa_mac_core + @ingroup hostapi_src + @author Bjorn Roche + @brief AUHAL implementation of PortAudio +*/ + +#ifndef PA_MAC_CORE_INTERNAL_H__ +#define PA_MAC_CORE_INTERNAL_H__ + +#include +#include + + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_allocation.h" +#include "pa_cpuload.h" +#include "pa_process.h" +#include "ringbuffer.h" + +#include "pa_mac_core_blocking.h" + +/* function prototypes */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#define RING_BUFFER_ADVANCE_DENOMINATOR (4) + +PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +signed long GetStreamReadAvailable( PaStream* stream ); +signed long GetStreamWriteAvailable( PaStream* stream ); +/* PaMacAUHAL - host api datastructure specific to this implementation */ +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + /* implementation specific data goes here */ + long devCount; + AudioDeviceID *devIds; /*array of all audio devices*/ + AudioDeviceID defaultIn; + AudioDeviceID defaultOut; +} +PaMacAUHAL; + + + +/* stream data structure specifically for this implementation */ +typedef struct PaMacCoreStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + /* implementation specific data goes here */ + bool bufferProcessorIsInitialized; + AudioUnit inputUnit; + AudioUnit outputUnit; + AudioDeviceID inputDevice; + AudioDeviceID outputDevice; + size_t userInChan; + size_t userOutChan; + size_t inputFramesPerBuffer; + size_t outputFramesPerBuffer; + PaMacBlio blio; + /* We use this ring buffer when input and out devs are different. */ + RingBuffer inputRingBuffer; + /* We may need to do SR conversion on input. */ + AudioConverterRef inputSRConverter; + /* We need to preallocate an inputBuffer for reading data. */ + AudioBufferList inputAudioBufferList; + AudioTimeStamp startTime; + volatile PaStreamCallbackFlags xrunFlags; + volatile bool isTimeSet; + volatile enum { + STOPPED = 0, /* playback is completely stopped, + and the user has called StopStream(). */ + CALLBACK_STOPPED = 1, /* callback has requested stop, + but user has not yet called StopStream(). */ + STOPPING = 2, /* The stream is in the process of closing. + This state is just used internally; + externally it is indistinguishable from + ACTIVE.*/ + ACTIVE = 3 /* The stream is active and running. */ + } state; + double sampleRate; + //these may be different from the stream sample rate due to SR conversion: + double outDeviceSampleRate; + double inDeviceSampleRate; +} +PaMacCoreStream; + +#endif /* PA_MAC_CORE_INTERNAL_H__ */ diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_core_old.c b/pjmedia/src/pjmedia/portaudio/pa_mac_core_old.c new file mode 100644 index 00000000..acb02c4a --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_core_old.c @@ -0,0 +1,914 @@ +/* + * $Id$ + * pa_mac_core.c + * Implementation of PortAudio for Mac OS X CoreAudio + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Authors: Ross Bencina and Phil Burk + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#include +#include +#include +#include +#include +#include + +#include "portaudio.h" +#include "pa_trace.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +// ===== constants ===== + +// ===== structs ===== +#pragma mark structs + +// PaMacCoreHostApiRepresentation - host api datastructure specific to this implementation +typedef struct PaMacCore_HAR +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + AudioDeviceID *macCoreDeviceIds; +} +PaMacCoreHostApiRepresentation; + +typedef struct PaMacCore_DI +{ + PaDeviceInfo inheritedDeviceInfo; +} +PaMacCoreDeviceInfo; + +// PaMacCoreStream - a stream data structure specifically for this implementation +typedef struct PaMacCore_S +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + int primeStreamUsingCallback; + + AudioDeviceID inputDevice; + AudioDeviceID outputDevice; + + // Processing thread management -------------- +// HANDLE abortEvent; +// HANDLE processingThread; +// DWORD processingThreadId; + + char throttleProcessingThreadOnOverload; // 0 -> don't throtte, non-0 -> throttle + int processingThreadPriority; + int highThreadPriority; + int throttledThreadPriority; + unsigned long throttledSleepMsecs; + + int isStopped; + volatile int isActive; + volatile int stopProcessing; // stop thread once existing buffers have been returned + volatile int abortProcessing; // stop thread immediately + +// DWORD allBuffersDurationMs; // used to calculate timeouts +} +PaMacCoreStream; + +// Data needed by the CoreAudio callback functions +typedef struct PaMacCore_CD +{ + PaMacCoreStream *stream; + PaStreamCallback *callback; + void *userData; + PaUtilConverter *inputConverter; + PaUtilConverter *outputConverter; + void *inputBuffer; + void *outputBuffer; + int inputChannelCount; + int outputChannelCount; + PaSampleFormat inputSampleFormat; + PaSampleFormat outputSampleFormat; + PaUtilTriangularDitherGenerator *ditherGenerator; +} +PaMacClientData; + +// ===== CoreAudio-PortAudio bridge functions ===== +#pragma mark CoreAudio-PortAudio bridge functions + +// Maps CoreAudio OSStatus codes to PortAudio PaError codes +static PaError conv_err(OSStatus error) +{ + PaError result; + + switch (error) { + case kAudioHardwareNoError: + result = paNoError; break; + case kAudioHardwareNotRunningError: + result = paInternalError; break; + case kAudioHardwareUnspecifiedError: + result = paInternalError; break; + case kAudioHardwareUnknownPropertyError: + result = paInternalError; break; + case kAudioHardwareBadPropertySizeError: + result = paInternalError; break; + case kAudioHardwareIllegalOperationError: + result = paInternalError; break; + case kAudioHardwareBadDeviceError: + result = paInvalidDevice; break; + case kAudioHardwareBadStreamError: + result = paBadStreamPtr; break; + //bennylp: + //case kAudioHardwareUnsupportedOperationError: + // result = paInternalError; break; + case kAudioDeviceUnsupportedFormatError: + result = paSampleFormatNotSupported; break; + case kAudioDevicePermissionsError: + result = paDeviceUnavailable; break; + default: + result = paInternalError; + } + + return result; +} + +/* This function is unused +static AudioStreamBasicDescription *InitializeStreamDescription(const PaStreamParameters *parameters, double sampleRate) +{ + struct AudioStreamBasicDescription *streamDescription = PaUtil_AllocateMemory(sizeof(AudioStreamBasicDescription)); + streamDescription->mSampleRate = sampleRate; + streamDescription->mFormatID = kAudioFormatLinearPCM; + streamDescription->mFormatFlags = 0; + streamDescription->mFramesPerPacket = 1; + + if (parameters->sampleFormat & paNonInterleaved) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsNonInterleaved; + streamDescription->mChannelsPerFrame = 1; + streamDescription->mBytesPerFrame = Pa_GetSampleSize(parameters->sampleFormat); + streamDescription->mBytesPerPacket = Pa_GetSampleSize(parameters->sampleFormat); + } + else { + streamDescription->mChannelsPerFrame = parameters->channelCount; + } + + streamDescription->mBytesPerFrame = Pa_GetSampleSize(parameters->sampleFormat) * streamDescription->mChannelsPerFrame; + streamDescription->mBytesPerPacket = streamDescription->mBytesPerFrame * streamDescription->mFramesPerPacket; + + if (parameters->sampleFormat & paFloat32) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsFloat; + streamDescription->mBitsPerChannel = 32; + } + else if (parameters->sampleFormat & paInt32) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 32; + } + else if (parameters->sampleFormat & paInt24) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 24; + } + else if (parameters->sampleFormat & paInt16) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 16; + } + else if (parameters->sampleFormat & paInt8) { + streamDescription->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + streamDescription->mBitsPerChannel = 8; + } + else if (parameters->sampleFormat & paInt32) { + streamDescription->mBitsPerChannel = 8; + } + + return streamDescription; +} +*/ + +static PaStreamCallbackTimeInfo *InitializeTimeInfo(const AudioTimeStamp* now, const AudioTimeStamp* inputTime, const AudioTimeStamp* outputTime) +{ + PaStreamCallbackTimeInfo *timeInfo = PaUtil_AllocateMemory(sizeof(PaStreamCallbackTimeInfo)); + + timeInfo->inputBufferAdcTime = inputTime->mSampleTime; + timeInfo->currentTime = now->mSampleTime; + timeInfo->outputBufferDacTime = outputTime->mSampleTime; + + return timeInfo; +} + +// ===== support functions ===== +#pragma mark support functions + +static void CleanUp(PaMacCoreHostApiRepresentation *macCoreHostApi) +{ + if( macCoreHostApi->allocations ) + { + PaUtil_FreeAllAllocations( macCoreHostApi->allocations ); + PaUtil_DestroyAllocationGroup( macCoreHostApi->allocations ); + } + + PaUtil_FreeMemory( macCoreHostApi ); +} + +static PaError GetChannelInfo(PaDeviceInfo *deviceInfo, AudioDeviceID macCoreDeviceId, int isInput) +{ + UInt32 propSize; + PaError err = paNoError; + UInt32 i; + int numChannels = 0; + AudioBufferList *buflist; + + err = conv_err(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, NULL)); + buflist = PaUtil_AllocateMemory(propSize); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, buflist)); + if (!err) { + for (i = 0; i < buflist->mNumberBuffers; ++i) { + numChannels += buflist->mBuffers[i].mNumberChannels; + } + + if (isInput) + deviceInfo->maxInputChannels = numChannels; + else + deviceInfo->maxOutputChannels = numChannels; + + int frameLatency; + propSize = sizeof(UInt32); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &frameLatency)); + if (!err) { + double secondLatency = frameLatency / deviceInfo->defaultSampleRate; + if (isInput) { + deviceInfo->defaultLowInputLatency = secondLatency; + deviceInfo->defaultHighInputLatency = secondLatency; + } + else { + deviceInfo->defaultLowOutputLatency = secondLatency; + deviceInfo->defaultHighOutputLatency = secondLatency; + } + } + } + PaUtil_FreeMemory(buflist); + + return err; +} + +static PaError InitializeDeviceInfo(PaMacCoreDeviceInfo *macCoreDeviceInfo, AudioDeviceID macCoreDeviceId, PaHostApiIndex hostApiIndex ) +{ + PaDeviceInfo *deviceInfo = &macCoreDeviceInfo->inheritedDeviceInfo; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + PaError err = paNoError; + UInt32 propSize; + + err = conv_err(AudioDeviceGetPropertyInfo(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, NULL)); + // FIXME: this allocation should be part of the allocations group + char *name = PaUtil_AllocateMemory(propSize); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyDeviceName, &propSize, name)); + if (!err) { + deviceInfo->name = name; + } + + Float64 sampleRate; + propSize = sizeof(Float64); + err = conv_err(AudioDeviceGetProperty(macCoreDeviceId, 0, 0, kAudioDevicePropertyNominalSampleRate, &propSize, &sampleRate)); + if (!err) { + deviceInfo->defaultSampleRate = sampleRate; + } + + + // Get channel info + err = GetChannelInfo(deviceInfo, macCoreDeviceId, 1); + err = GetChannelInfo(deviceInfo, macCoreDeviceId, 0); + + return err; +} + +static PaError InitializeDeviceInfos( PaMacCoreHostApiRepresentation *macCoreHostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaMacCoreDeviceInfo *deviceInfoArray; + + // initialise device counts and default devices under the assumption that there are no devices. These values are incremented below if and when devices are successfully initialized. + hostApi = &macCoreHostApi->inheritedHostApiRep; + hostApi->info.deviceCount = 0; + hostApi->info.defaultInputDevice = paNoDevice; + hostApi->info.defaultOutputDevice = paNoDevice; + + UInt32 propsize; + AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &propsize, NULL); + int numDevices = propsize / sizeof(AudioDeviceID); + hostApi->info.deviceCount = numDevices; + if (numDevices > 0) { + hostApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + macCoreHostApi->allocations, sizeof(PaDeviceInfo*) * numDevices ); + if( !hostApi->deviceInfos ) + { + return paInsufficientMemory; + } + + // allocate all device info structs in a contiguous block + deviceInfoArray = (PaMacCoreDeviceInfo*)PaUtil_GroupAllocateMemory( + macCoreHostApi->allocations, sizeof(PaMacCoreDeviceInfo) * numDevices ); + if( !deviceInfoArray ) + { + return paInsufficientMemory; + } + + macCoreHostApi->macCoreDeviceIds = PaUtil_GroupAllocateMemory(macCoreHostApi->allocations, propsize); + AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &propsize, macCoreHostApi->macCoreDeviceIds); + + AudioDeviceID defaultInputDevice, defaultOutputDevice; + propsize = sizeof(AudioDeviceID); + AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &propsize, &defaultInputDevice); + AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propsize, &defaultOutputDevice); + + UInt32 i; + for (i = 0; i < numDevices; ++i) { + if (macCoreHostApi->macCoreDeviceIds[i] == defaultInputDevice) { + hostApi->info.defaultInputDevice = i; + } + if (macCoreHostApi->macCoreDeviceIds[i] == defaultOutputDevice) { + hostApi->info.defaultOutputDevice = i; + } + InitializeDeviceInfo(&deviceInfoArray[i], macCoreHostApi->macCoreDeviceIds[i], hostApiIndex); + hostApi->deviceInfos[i] = &(deviceInfoArray[i].inheritedDeviceInfo); + } + } + + return result; +} + +static OSStatus CheckFormat(AudioDeviceID macCoreDeviceId, const PaStreamParameters *parameters, double sampleRate, int isInput) +{ + UInt32 propSize = sizeof(AudioStreamBasicDescription); + AudioStreamBasicDescription *streamDescription = PaUtil_AllocateMemory(propSize); + + streamDescription->mSampleRate = sampleRate; + streamDescription->mFormatID = 0; + streamDescription->mFormatFlags = 0; + streamDescription->mBytesPerPacket = 0; + streamDescription->mFramesPerPacket = 0; + streamDescription->mBytesPerFrame = 0; + streamDescription->mChannelsPerFrame = 0; + streamDescription->mBitsPerChannel = 0; + streamDescription->mReserved = 0; + + OSStatus result = AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyStreamFormatSupported, &propSize, streamDescription); + PaUtil_FreeMemory(streamDescription); + return result; +} + +static OSStatus CopyInputData(PaMacClientData* destination, const AudioBufferList *source, unsigned long frameCount) +{ + int frameSpacing, channelSpacing; + if (destination->inputSampleFormat & paNonInterleaved) { + frameSpacing = 1; + channelSpacing = destination->inputChannelCount; + } + else { + frameSpacing = destination->inputChannelCount; + channelSpacing = 1; + } + + AudioBuffer const *inputBuffer = &source->mBuffers[0]; + void *coreAudioBuffer = inputBuffer->mData; + void *portAudioBuffer = destination->inputBuffer; + UInt32 i, streamNumber, streamChannel; + for (i = streamNumber = streamChannel = 0; i < destination->inputChannelCount; ++i, ++streamChannel) { + if (streamChannel >= inputBuffer->mNumberChannels) { + ++streamNumber; + inputBuffer = &source->mBuffers[streamNumber]; + coreAudioBuffer = inputBuffer->mData; + streamChannel = 0; + } + destination->inputConverter(portAudioBuffer, frameSpacing, coreAudioBuffer, inputBuffer->mNumberChannels, frameCount, destination->ditherGenerator); + coreAudioBuffer += sizeof(Float32); + portAudioBuffer += Pa_GetSampleSize(destination->inputSampleFormat) * channelSpacing; + } + return noErr; +} + +static OSStatus CopyOutputData(AudioBufferList* destination, PaMacClientData *source, unsigned long frameCount) +{ + int frameSpacing, channelSpacing; + if (source->outputSampleFormat & paNonInterleaved) { + frameSpacing = 1; + channelSpacing = source->outputChannelCount; + } + else { + frameSpacing = source->outputChannelCount; + channelSpacing = 1; + } + + AudioBuffer *outputBuffer = &destination->mBuffers[0]; + void *coreAudioBuffer = outputBuffer->mData; + void *portAudioBuffer = source->outputBuffer; + UInt32 i, streamNumber, streamChannel; + for (i = streamNumber = streamChannel = 0; i < source->outputChannelCount; ++i, ++streamChannel) { + if (streamChannel >= outputBuffer->mNumberChannels) { + ++streamNumber; + outputBuffer = &destination->mBuffers[streamNumber]; + coreAudioBuffer = outputBuffer->mData; + streamChannel = 0; + } + source->outputConverter(coreAudioBuffer, outputBuffer->mNumberChannels, portAudioBuffer, frameSpacing, frameCount, NULL); + coreAudioBuffer += sizeof(Float32); + portAudioBuffer += Pa_GetSampleSize(source->outputSampleFormat) * channelSpacing; + } + return noErr; +} + +static OSStatus AudioIOProc( AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* inClientData) +{ + PaMacClientData *clientData = (PaMacClientData *)inClientData; + PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + + PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + + AudioBuffer *outputBuffer = &outOutputData->mBuffers[0]; + unsigned long frameCount = outputBuffer->mDataByteSize / (outputBuffer->mNumberChannels * sizeof(Float32)); + + if (clientData->inputBuffer) { + CopyInputData(clientData, inInputData, frameCount); + } + PaStreamCallbackResult result = clientData->callback(clientData->inputBuffer, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); + if (clientData->outputBuffer) { + CopyOutputData(outOutputData, clientData, frameCount); + } + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); + + if (result == paComplete || result == paAbort) { + Pa_StopStream(clientData->stream); + } + + PaUtil_FreeMemory( timeInfo ); + return noErr; +} + +// This is not for input-only streams, this is for streams where the input device is different from the output device +static OSStatus AudioInputProc( AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* inClientData) +{ + PaMacClientData *clientData = (PaMacClientData *)inClientData; + PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + + PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + + AudioBuffer const *inputBuffer = &inInputData->mBuffers[0]; + unsigned long frameCount = inputBuffer->mDataByteSize / (inputBuffer->mNumberChannels * sizeof(Float32)); + + CopyInputData(clientData, inInputData, frameCount); + PaStreamCallbackResult result = clientData->callback(clientData->inputBuffer, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); + if( result == paComplete || result == paAbort ) + Pa_StopStream(clientData->stream); + PaUtil_FreeMemory( timeInfo ); + return noErr; +} + +// This is not for output-only streams, this is for streams where the input device is different from the output device +static OSStatus AudioOutputProc( AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* inClientData) +{ + PaMacClientData *clientData = (PaMacClientData *)inClientData; + //PaStreamCallbackTimeInfo *timeInfo = InitializeTimeInfo(inNow, inInputTime, inOutputTime); + + PaUtil_BeginCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer ); + + AudioBuffer *outputBuffer = &outOutputData->mBuffers[0]; + unsigned long frameCount = outputBuffer->mDataByteSize / (outputBuffer->mNumberChannels * sizeof(Float32)); + + //clientData->callback(NULL, clientData->outputBuffer, frameCount, timeInfo, paNoFlag, clientData->userData); + + CopyOutputData(outOutputData, clientData, frameCount); + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); + return noErr; +} + +static PaError SetSampleRate(AudioDeviceID device, double sampleRate, int isInput) +{ + PaError result = paNoError; + + double actualSampleRate; + UInt32 propSize = sizeof(double); + result = conv_err(AudioDeviceSetProperty(device, NULL, 0, isInput, kAudioDevicePropertyNominalSampleRate, propSize, &sampleRate)); + + result = conv_err(AudioDeviceGetProperty(device, 0, isInput, kAudioDevicePropertyNominalSampleRate, &propSize, &actualSampleRate)); + + if (result == paNoError && actualSampleRate != sampleRate) { + result = paInvalidSampleRate; + } + + return result; +} + +static PaError SetFramesPerBuffer(AudioDeviceID device, unsigned long framesPerBuffer, int isInput) +{ + PaError result = paNoError; + UInt32 preferredFramesPerBuffer = framesPerBuffer; + // while (preferredFramesPerBuffer > UINT32_MAX) { + // preferredFramesPerBuffer /= 2; + // } + + UInt32 actualFramesPerBuffer; + UInt32 propSize = sizeof(UInt32); + result = conv_err(AudioDeviceSetProperty(device, NULL, 0, isInput, kAudioDevicePropertyBufferFrameSize, propSize, &preferredFramesPerBuffer)); + + result = conv_err(AudioDeviceGetProperty(device, 0, isInput, kAudioDevicePropertyBufferFrameSize, &propSize, &actualFramesPerBuffer)); + + if (result != paNoError) { + // do nothing + } + else if (actualFramesPerBuffer > framesPerBuffer) { + result = paBufferTooSmall; + } + else if (actualFramesPerBuffer < framesPerBuffer) { + result = paBufferTooBig; + } + + return result; +} + +static PaError SetUpUnidirectionalStream(AudioDeviceID device, double sampleRate, unsigned long framesPerBuffer, int isInput) +{ + PaError err = paNoError; + err = SetSampleRate(device, sampleRate, isInput); + if( err == paNoError ) + err = SetFramesPerBuffer(device, framesPerBuffer, isInput); + return err; +} + +// ===== PortAudio functions ===== +#pragma mark PortAudio functions + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + + PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif // __cplusplus + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation*)hostApi; + + CleanUp(macCoreHostApi); +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation*)hostApi; + PaDeviceInfo *deviceInfo; + + PaError result = paNoError; + if (inputParameters) { + deviceInfo = macCoreHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device]; + if (inputParameters->channelCount > deviceInfo->maxInputChannels) + result = paInvalidChannelCount; + else if (CheckFormat(macCoreHostApi->macCoreDeviceIds[inputParameters->device], inputParameters, sampleRate, 1) != kAudioHardwareNoError) { + result = paInvalidSampleRate; + } + } + if (outputParameters && result == paNoError) { + deviceInfo = macCoreHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device]; + if (outputParameters->channelCount > deviceInfo->maxOutputChannels) + result = paInvalidChannelCount; + else if (CheckFormat(macCoreHostApi->macCoreDeviceIds[outputParameters->device], outputParameters, sampleRate, 0) != kAudioHardwareNoError) { + result = paInvalidSampleRate; + } + } + + return result; +} + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError err = paNoError; + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation *)hostApi; + PaMacCoreStream *stream = PaUtil_AllocateMemory(sizeof(PaMacCoreStream)); + stream->isActive = 0; + stream->isStopped = 1; + stream->inputDevice = kAudioDeviceUnknown; + stream->outputDevice = kAudioDeviceUnknown; + + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + ( (streamCallback) + ? &macCoreHostApi->callbackStreamInterface + : &macCoreHostApi->blockingStreamInterface ), + streamCallback, userData ); + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + *s = (PaStream*)stream; + PaMacClientData *clientData = PaUtil_AllocateMemory(sizeof(PaMacClientData)); + clientData->stream = stream; + clientData->callback = streamCallback; + clientData->userData = userData; + clientData->inputBuffer = 0; + clientData->outputBuffer = 0; + clientData->ditherGenerator = PaUtil_AllocateMemory(sizeof(PaUtilTriangularDitherGenerator)); + PaUtil_InitializeTriangularDitherState(clientData->ditherGenerator); + + if (inputParameters != NULL) { + stream->inputDevice = macCoreHostApi->macCoreDeviceIds[inputParameters->device]; + clientData->inputConverter = PaUtil_SelectConverter(paFloat32, inputParameters->sampleFormat, streamFlags); + clientData->inputBuffer = PaUtil_AllocateMemory(Pa_GetSampleSize(inputParameters->sampleFormat) * framesPerBuffer * inputParameters->channelCount); + clientData->inputChannelCount = inputParameters->channelCount; + clientData->inputSampleFormat = inputParameters->sampleFormat; + err = SetUpUnidirectionalStream(stream->inputDevice, sampleRate, framesPerBuffer, 1); + } + + if (err == paNoError && outputParameters != NULL) { + stream->outputDevice = macCoreHostApi->macCoreDeviceIds[outputParameters->device]; + clientData->outputConverter = PaUtil_SelectConverter(outputParameters->sampleFormat, paFloat32, streamFlags); + clientData->outputBuffer = PaUtil_AllocateMemory(Pa_GetSampleSize(outputParameters->sampleFormat) * framesPerBuffer * outputParameters->channelCount); + clientData->outputChannelCount = outputParameters->channelCount; + clientData->outputSampleFormat = outputParameters->sampleFormat; + err = SetUpUnidirectionalStream(stream->outputDevice, sampleRate, framesPerBuffer, 0); + } + + if (inputParameters == NULL || outputParameters == NULL || stream->inputDevice == stream->outputDevice) { + AudioDeviceID device = (inputParameters == NULL) ? stream->outputDevice : stream->inputDevice; + + AudioDeviceAddIOProc(device, AudioIOProc, clientData); + } + else { + // using different devices for input and output + AudioDeviceAddIOProc(stream->inputDevice, AudioInputProc, clientData); + AudioDeviceAddIOProc(stream->outputDevice, AudioOutputProc, clientData); + } + + return err; +} + +// Note: When CloseStream() is called, the multi-api layer ensures that the stream has already been stopped or aborted. +static PaError CloseStream( PaStream* s ) +{ + PaError err = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + if (stream->inputDevice != kAudioDeviceUnknown) { + if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { + err = conv_err(AudioDeviceRemoveIOProc(stream->inputDevice, AudioIOProc)); + } + else { + err = conv_err(AudioDeviceRemoveIOProc(stream->inputDevice, AudioInputProc)); + err = conv_err(AudioDeviceRemoveIOProc(stream->outputDevice, AudioOutputProc)); + } + } + else { + err = conv_err(AudioDeviceRemoveIOProc(stream->outputDevice, AudioIOProc)); + } + + return err; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError err = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + if (stream->inputDevice != kAudioDeviceUnknown) { + if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { + err = conv_err(AudioDeviceStart(stream->inputDevice, AudioIOProc)); + } + else { + err = conv_err(AudioDeviceStart(stream->inputDevice, AudioInputProc)); + err = conv_err(AudioDeviceStart(stream->outputDevice, AudioOutputProc)); + } + } + else { + err = conv_err(AudioDeviceStart(stream->outputDevice, AudioIOProc)); + } + + stream->isActive = 1; + stream->isStopped = 0; + return err; +} + +static PaError AbortStream( PaStream *s ) +{ + PaError err = paNoError; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + if (stream->inputDevice != kAudioDeviceUnknown) { + if (stream->outputDevice == kAudioDeviceUnknown || stream->outputDevice == stream->inputDevice) { + err = conv_err(AudioDeviceStop(stream->inputDevice, AudioIOProc)); + } + else { + err = conv_err(AudioDeviceStop(stream->inputDevice, AudioInputProc)); + err = conv_err(AudioDeviceStop(stream->outputDevice, AudioOutputProc)); + } + } + else { + err = conv_err(AudioDeviceStop(stream->outputDevice, AudioIOProc)); + } + + stream->isActive = 0; + stream->isStopped = 1; + return err; +} + +static PaError StopStream( PaStream *s ) +{ + // TODO: this should be nicer than abort + return AbortStream(s); +} + +static PaError IsStreamStopped( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return stream->isStopped; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return stream->isActive; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + OSStatus err; + PaTime result; + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + AudioTimeStamp *timeStamp = PaUtil_AllocateMemory(sizeof(AudioTimeStamp)); + if (stream->inputDevice != kAudioDeviceUnknown) { + err = AudioDeviceGetCurrentTime(stream->inputDevice, timeStamp); + } + else { + err = AudioDeviceGetCurrentTime(stream->outputDevice, timeStamp); + } + + result = err ? 0 : timeStamp->mSampleTime; + PaUtil_FreeMemory(timeStamp); + + return result; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +// As separate stream interfaces are used for blocking and callback streams, the following functions can be guaranteed to only be called for blocking streams. + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + return paInternalError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + return paInternalError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + return paInternalError; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + return paInternalError; +} + +// HostAPI-specific initialization function +PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaMacCoreHostApiRepresentation *macCoreHostApi = (PaMacCoreHostApiRepresentation *)PaUtil_AllocateMemory( sizeof(PaMacCoreHostApiRepresentation) ); + if( !macCoreHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + macCoreHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !macCoreHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &macCoreHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paCoreAudio; + (*hostApi)->info.name = "CoreAudio"; + + result = InitializeDeviceInfos(macCoreHostApi, hostApiIndex); + if (result != paNoError) { + goto error; + } + + // Set up the proper callbacks to this HostApi's functions + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &macCoreHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &macCoreHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( macCoreHostApi ) { + CleanUp(macCoreHostApi); + } + + return result; +} diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_core_utilities.c b/pjmedia/src/pjmedia/portaudio/pa_mac_core_utilities.c new file mode 100644 index 00000000..5a0f8b8d --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_core_utilities.c @@ -0,0 +1,612 @@ +/* + * Helper and utility functions for pa_mac_core.c (Apple AUHAL implementation) + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file + @ingroup hostapi_src +*/ + +#include "pa_mac_core_utilities.h" + + +PaError PaMacCore_SetUnixError( int err, int line ) +{ + PaError ret; + const char *errorText; + + if( err == 0 ) + { + return paNoError; + } + + ret = paNoError; + errorText = strerror( err ); + + /** Map Unix error to PaError. Pretty much the only one that maps + is ENOMEM. */ + if( err == ENOMEM ) + ret = paInsufficientMemory; + else + ret = paInternalError; + + DBUG(("%d on line %d: msg='%s'\n", err, line, errorText)); + PaUtil_SetLastHostErrorInfo( paCoreAudio, err, errorText ); + + return ret; +} + +/* + * Translates MacOS generated errors into PaErrors + */ +PaError PaMacCore_SetError(OSStatus error, int line, int isError) +{ + /*FIXME: still need to handle possible ComponentResult values.*/ + /* unfortunately, they don't seem to be documented anywhere.*/ + PaError result; + const char *errorType; + const char *errorText; + + switch (error) { + case kAudioHardwareNoError: + return paNoError; + case kAudioHardwareNotRunningError: + errorText = "Audio Hardware Not Running"; + result = paInternalError; break; + case kAudioHardwareUnspecifiedError: + errorText = "Unspecified Audio Hardware Error"; + result = paInternalError; break; + case kAudioHardwareUnknownPropertyError: + errorText = "Audio Hardware: Unknown Property"; + result = paInternalError; break; + case kAudioHardwareBadPropertySizeError: + errorText = "Audio Hardware: Bad Property Size"; + result = paInternalError; break; + case kAudioHardwareIllegalOperationError: + errorText = "Audio Hardware: Illegal Operation"; + result = paInternalError; break; + case kAudioHardwareBadDeviceError: + errorText = "Audio Hardware: Bad Device"; + result = paInvalidDevice; break; + case kAudioHardwareBadStreamError: + errorText = "Audio Hardware: BadStream"; + result = paBadStreamPtr; break; + case kAudioHardwareUnsupportedOperationError: + errorText = "Audio Hardware: Unsupported Operation"; + result = paInternalError; break; + case kAudioDeviceUnsupportedFormatError: + errorText = "Audio Device: Unsupported Format"; + result = paSampleFormatNotSupported; break; + case kAudioDevicePermissionsError: + errorText = "Audio Device: Permissions Error"; + result = paDeviceUnavailable; break; + /* Audio Unit Errors: http://developer.apple.com/documentation/MusicAudio/Reference/CoreAudio/audio_units/chapter_5_section_3.html */ + case kAudioUnitErr_InvalidProperty: + errorText = "Audio Unit: Invalid Property"; + result = paInternalError; break; + case kAudioUnitErr_InvalidParameter: + errorText = "Audio Unit: Invalid Parameter"; + result = paInternalError; break; + case kAudioUnitErr_NoConnection: + errorText = "Audio Unit: No Connection"; + result = paInternalError; break; + case kAudioUnitErr_FailedInitialization: + errorText = "Audio Unit: Initialization Failed"; + result = paInternalError; break; + case kAudioUnitErr_TooManyFramesToProcess: + errorText = "Audio Unit: Too Many Frames"; + result = paInternalError; break; + case kAudioUnitErr_IllegalInstrument: + errorText = "Audio Unit: Illegal Instrument"; + result = paInternalError; break; + case kAudioUnitErr_InstrumentTypeNotFound: + errorText = "Audio Unit: Instrument Type Not Found"; + result = paInternalError; break; + case kAudioUnitErr_InvalidFile: + errorText = "Audio Unit: Invalid File"; + result = paInternalError; break; + case kAudioUnitErr_UnknownFileType: + errorText = "Audio Unit: Unknown File Type"; + result = paInternalError; break; + case kAudioUnitErr_FileNotSpecified: + errorText = "Audio Unit: File Not Specified"; + result = paInternalError; break; + case kAudioUnitErr_FormatNotSupported: + errorText = "Audio Unit: Format Not Supported"; + result = paInternalError; break; + case kAudioUnitErr_Uninitialized: + errorText = "Audio Unit: Unitialized"; + result = paInternalError; break; + case kAudioUnitErr_InvalidScope: + errorText = "Audio Unit: Invalid Scope"; + result = paInternalError; break; + case kAudioUnitErr_PropertyNotWritable: + errorText = "Audio Unit: PropertyNotWritable"; + result = paInternalError; break; + case kAudioUnitErr_InvalidPropertyValue: + errorText = "Audio Unit: Invalid Property Value"; + result = paInternalError; break; + case kAudioUnitErr_PropertyNotInUse: + errorText = "Audio Unit: Property Not In Use"; + result = paInternalError; break; + case kAudioUnitErr_Initialized: + errorText = "Audio Unit: Initialized"; + result = paInternalError; break; + case kAudioUnitErr_InvalidOfflineRender: + errorText = "Audio Unit: Invalid Offline Render"; + result = paInternalError; break; + case kAudioUnitErr_Unauthorized: + errorText = "Audio Unit: Unauthorized"; + result = paInternalError; break; + case kAudioUnitErr_CannotDoInCurrentContext: + errorText = "Audio Unit: cannot do in current context"; + result = paInternalError; break; + default: + errorText = "Unknown Error"; + result = paInternalError; + } + + if (isError) + errorType = "Error"; + else + errorType = "Warning"; + + if ((int)error < -99999 || (int)error > 99999) + DBUG(("%s on line %d: err='%4s', msg='%s'\n", errorType, line, (const char *)&error, errorText)); + else + DBUG(("%s on line %d: err=%d, 0x%x, msg='%s'\n", errorType, line, (int)error, (unsigned)error, errorText)); + + PaUtil_SetLastHostErrorInfo( paCoreAudio, error, errorText ); + + return result; +} + +/* + * This function computes an appropriate ring buffer size given + * a requested latency (in seconds), sample rate and framesPerBuffer. + * + * The returned ringBufferSize is computed using the following + * constraints: + * - it must be at least 4. + * - it must be at least 3x framesPerBuffer. + * - it must be at least 2x the suggestedLatency. + * - it must be a power of 2. + * This function attempts to compute the minimum such size. + * + * FEEDBACK: too liberal/conservative/another way? + */ +long computeRingBufferSize( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + long inputFramesPerBuffer, + long outputFramesPerBuffer, + double sampleRate ) +{ + long ringSize; + int index; + int i; + double latencyTimesChannelCount ; + long framesPerBufferTimesChannelCount ; + + VVDBUG(( "computeRingBufferSize()\n" )); + + assert( inputParameters || outputParameters ); + + if( outputParameters && inputParameters ) + { + latencyTimesChannelCount = MAX( + inputParameters->suggestedLatency * inputParameters->channelCount, + outputParameters->suggestedLatency * outputParameters->channelCount ); + framesPerBufferTimesChannelCount = MAX( + inputFramesPerBuffer * inputParameters->channelCount, + outputFramesPerBuffer * outputParameters->channelCount ); + } + else if( outputParameters ) + { + latencyTimesChannelCount + = outputParameters->suggestedLatency * outputParameters->channelCount; + framesPerBufferTimesChannelCount + = outputFramesPerBuffer * outputParameters->channelCount; + } + else /* we have inputParameters */ + { + latencyTimesChannelCount + = inputParameters->suggestedLatency * inputParameters->channelCount; + framesPerBufferTimesChannelCount + = inputFramesPerBuffer * inputParameters->channelCount; + } + + ringSize = (long) ( latencyTimesChannelCount * sampleRate * 2 + .5); + VDBUG( ( "suggested latency * channelCount: %d\n", (int) (latencyTimesChannelCount*sampleRate) ) ); + if( ringSize < framesPerBufferTimesChannelCount * 3 ) + ringSize = framesPerBufferTimesChannelCount * 3 ; + VDBUG(("framesPerBuffer*channelCount:%d\n",(int)framesPerBufferTimesChannelCount)); + VDBUG(("Ringbuffer size (1): %d\n", (int)ringSize )); + + /* make sure it's at least 4 */ + ringSize = MAX( ringSize, 4 ); + + /* round up to the next power of 2 */ + index = -1; + for( i=0; i> i & 0x01 ) + index = i; + assert( index > 0 ); + if( ringSize <= ( 0x01 << index ) ) + ringSize = 0x01 << index ; + else + ringSize = 0x01 << ( index + 1 ); + + VDBUG(( "Final Ringbuffer size (2): %d\n", (int)ringSize )); + return ringSize; +} + + +/* + * Durring testing of core audio, I found that serious crashes could occur + * if properties such as sample rate were changed multiple times in rapid + * succession. The function below has some fancy logic to make sure that changes + * are acknowledged before another is requested. That seems to help a lot. + */ + +OSStatus propertyProc( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData ) +{ + MutexAndBool *mab = (MutexAndBool *) inClientData; + mab->once = TRUE; + pthread_mutex_unlock( &(mab->mutex) ); + return noErr; +} + +/* sets the value of the given property and waits for the change to + be acknowledged, and returns the final value, which is not guaranteed + by this function to be the same as the desired value. Obviously, this + function can only be used for data whose input and output are the + same size and format, and their size and format are known in advance.*/ +PaError AudioDeviceSetPropertyNowAndWaitForChange( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + UInt32 inPropertyDataSize, + const void *inPropertyData, + void *outPropertyData ) +{ + OSStatus macErr; + int unixErr; + MutexAndBool mab; + UInt32 outPropertyDataSize = inPropertyDataSize; + + /* First, see if it already has that value. If so, return. */ + macErr = AudioDeviceGetProperty( inDevice, inChannel, + isInput, inPropertyID, + &outPropertyDataSize, outPropertyData ); + if( macErr ) + goto failMac2; + if( inPropertyDataSize!=outPropertyDataSize ) + return paInternalError; + if( 0==memcmp( outPropertyData, inPropertyData, outPropertyDataSize ) ) + return paNoError; + + /* setup and lock mutex */ + mab.once = FALSE; + unixErr = pthread_mutex_init( &mab.mutex, NULL ); + if( unixErr ) + goto failUnix2; + unixErr = pthread_mutex_lock( &mab.mutex ); + if( unixErr ) + goto failUnix; + + /* add property listener */ + macErr = AudioDeviceAddPropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc, + &mab ); + if( macErr ) + goto failMac; + /* set property */ + macErr = AudioDeviceSetProperty( inDevice, NULL, inChannel, + isInput, inPropertyID, + inPropertyDataSize, inPropertyData ); + if( macErr ) { + /* we couldn't set the property, so we'll just unlock the mutex + and move on. */ + pthread_mutex_unlock( &mab.mutex ); + } + + /* wait for property to change */ + unixErr = pthread_mutex_lock( &mab.mutex ); + if( unixErr ) + goto failUnix; + + /* now read the property back out */ + macErr = AudioDeviceGetProperty( inDevice, inChannel, + isInput, inPropertyID, + &outPropertyDataSize, outPropertyData ); + if( macErr ) + goto failMac; + /* cleanup */ + AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc ); + unixErr = pthread_mutex_unlock( &mab.mutex ); + if( unixErr ) + goto failUnix2; + unixErr = pthread_mutex_destroy( &mab.mutex ); + if( unixErr ) + goto failUnix2; + + return paNoError; + + failUnix: + pthread_mutex_destroy( &mab.mutex ); + AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc ); + + failUnix2: + DBUG( ("Error #%d while setting a device property: %s\n", unixErr, strerror( unixErr ) ) ); + return paUnanticipatedHostError; + + failMac: + pthread_mutex_destroy( &mab.mutex ); + AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc ); + failMac2: + return ERR( macErr ); +} + +/* + * Sets the sample rate the HAL device. + * if requireExact: set the sample rate or fail. + * + * otherwise : set the exact sample rate. + * If that fails, check for available sample rates, and choose one + * higher than the requested rate. If there isn't a higher one, + * just use the highest available. + */ +PaError setBestSampleRateForDevice( const AudioDeviceID device, + const bool isOutput, + const bool requireExact, + const Float64 desiredSrate ) +{ + /*FIXME: changing the sample rate is disruptive to other programs using the + device, so it might be good to offer a custom flag to not change the + sample rate and just do conversion. (in my casual tests, there is + no disruption unless the sample rate really does need to change) */ + const bool isInput = isOutput ? 0 : 1; + Float64 srate; + UInt32 propsize = sizeof( Float64 ); + OSErr err; + AudioValueRange *ranges; + int i=0; + Float64 max = -1; /*the maximum rate available*/ + Float64 best = -1; /*the lowest sample rate still greater than desired rate*/ + VDBUG(("Setting sample rate for device %ld to %g.\n",device,(float)desiredSrate)); + + /* -- try setting the sample rate -- */ + err = AudioDeviceSetPropertyNowAndWaitForChange( + device, 0, isInput, + kAudioDevicePropertyNominalSampleRate, + propsize, &desiredSrate, &srate ); + if( err ) + return err; + + /* -- if the rate agrees, and we got no errors, we are done -- */ + if( !err && srate == desiredSrate ) + return paNoError; + /* -- we've failed if the rates disagree and we are setting input -- */ + if( requireExact ) + return paInvalidSampleRate; + + /* -- generate a list of available sample rates -- */ + err = AudioDeviceGetPropertyInfo( device, 0, isInput, + kAudioDevicePropertyAvailableNominalSampleRates, + &propsize, NULL ); + if( err ) + return ERR( err ); + ranges = (AudioValueRange *)calloc( 1, propsize ); + if( !ranges ) + return paInsufficientMemory; + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyAvailableNominalSampleRates, + &propsize, ranges ); + if( err ) + { + free( ranges ); + return ERR( err ); + } + VDBUG(("Requested sample rate of %g was not available.\n", (float)desiredSrate)); + VDBUG(("%lu Available Sample Rates are:\n",propsize/sizeof(AudioValueRange))); +#ifdef MAC_CORE_VERBOSE_DEBUG + for( i=0; i max ) max = ranges[i].mMaximum; + if( ranges[i].mMinimum > desiredSrate ) { + if( best < 0 ) + best = ranges[i].mMinimum; + else if( ranges[i].mMinimum < best ) + best = ranges[i].mMinimum; + } + } + if( best < 0 ) + best = max; + VDBUG( ("Maximum Rate %g. best is %g.\n", max, best ) ); + free( ranges ); + + /* -- set the sample rate -- */ + propsize = sizeof( best ); + err = AudioDeviceSetPropertyNowAndWaitForChange( + device, 0, isInput, + kAudioDevicePropertyNominalSampleRate, + propsize, &best, &srate ); + if( err ) + return err; + + if( err ) + return ERR( err ); + /* -- if the set rate matches, we are done -- */ + if( srate == best ) + return paNoError; + + /* -- otherwise, something wierd happened: we didn't set the rate, and we got no errors. Just bail. */ + return paInternalError; +} + + +/* + Attempts to set the requestedFramesPerBuffer. If it can't set the exact + value, it settles for something smaller if available. If nothing smaller + is available, it uses the smallest available size. + actualFramesPerBuffer will be set to the actual value on successful return. + OK to pass NULL to actualFramesPerBuffer. + The logic is very simmilar too setBestSampleRate only failure here is + not usually catastrophic. +*/ +PaError setBestFramesPerBuffer( const AudioDeviceID device, + const bool isOutput, + unsigned long requestedFramesPerBuffer, + unsigned long *actualFramesPerBuffer ) +{ + UInt32 afpb; + const bool isInput = !isOutput; + UInt32 propsize = sizeof(UInt32); + OSErr err; + Float64 min = -1; /*the min blocksize*/ + Float64 best = -1; /*the best blocksize*/ + int i=0; + AudioValueRange *ranges; + + if( actualFramesPerBuffer == NULL ) + actualFramesPerBuffer = &afpb; + + + /* -- try and set exact FPB -- */ + err = AudioDeviceSetProperty( device, NULL, 0, isInput, + kAudioDevicePropertyBufferFrameSize, + propsize, &requestedFramesPerBuffer); + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyBufferFrameSize, + &propsize, actualFramesPerBuffer); + if( err ) + return ERR( err ); + if( *actualFramesPerBuffer == requestedFramesPerBuffer ) + return paNoError; /* we are done */ + + /* -- fetch available block sizes -- */ + err = AudioDeviceGetPropertyInfo( device, 0, isInput, + kAudioDevicePropertyBufferSizeRange, + &propsize, NULL ); + if( err ) + return ERR( err ); + ranges = (AudioValueRange *)calloc( 1, propsize ); + if( !ranges ) + return paInsufficientMemory; + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyBufferSizeRange, + &propsize, ranges ); + if( err ) + { + free( ranges ); + return ERR( err ); + } + VDBUG(("Requested block size of %lu was not available.\n", + requestedFramesPerBuffer )); + VDBUG(("%lu Available block sizes are:\n",propsize/sizeof(AudioValueRange))); +#ifdef MAC_CORE_VERBOSE_DEBUG + for( i=0; i best ) + best = ranges[i].mMaximum; + } + } + if( best == -1 ) + best = min; + VDBUG( ("Minimum FPB %g. best is %g.\n", min, best ) ); + free( ranges ); + + /* --- set the buffer size (ignore errors) -- */ + requestedFramesPerBuffer = (UInt32) best ; + propsize = sizeof( UInt32 ); + err = AudioDeviceSetProperty( device, NULL, 0, isInput, + kAudioDevicePropertyBufferSize, + propsize, &requestedFramesPerBuffer ); + /* --- read the property to check that it was set -- */ + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyBufferSize, + &propsize, actualFramesPerBuffer ); + + if( err ) + return ERR( err ); + + return paNoError; +} diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_core_utilities.h b/pjmedia/src/pjmedia/portaudio/pa_mac_core_utilities.h new file mode 100644 index 00000000..63ef22a9 --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_core_utilities.h @@ -0,0 +1,215 @@ +/* + * Helper and utility functions for pa_mac_core.c (Apple AUHAL implementation) + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file + @ingroup hostapi_src +*/ + +#ifndef PA_MAC_CORE_UTILITIES_H__ +#define PA_MAC_CORE_UTILITIES_H__ + +#include +#include "portaudio.h" +#include "pa_util.h" +#include +#include + +#ifndef MIN +#define MIN(a, b) (((a)<(b))?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a)<(b))?(b):(a)) +#endif + +#define ERR(mac_error) PaMacCore_SetError(mac_error, __LINE__, 1 ) +#define WARNING(mac_error) PaMacCore_SetError(mac_error, __LINE__, 0 ) + + +/* Help keep track of AUHAL element numbers */ +#define INPUT_ELEMENT (1) +#define OUTPUT_ELEMENT (0) + +/* Normal level of debugging: fine for most apps that don't mind the occational warning being printf'ed */ +/* + */ +#define MAC_CORE_DEBUG +#ifdef MAC_CORE_DEBUG +# define DBUG(MSG) do { printf("||PaMacCore (AUHAL)|| "); printf MSG ; fflush(stdout); } while(0) +#else +# define DBUG(MSG) +#endif + +/* Verbose Debugging: useful for developement */ +/* +#define MAC_CORE_VERBOSE_DEBUG +*/ +#ifdef MAC_CORE_VERBOSE_DEBUG +# define VDBUG(MSG) do { printf("||PaMacCore (v )|| "); printf MSG ; fflush(stdout); } while(0) +#else +# define VDBUG(MSG) +#endif + +/* Very Verbose Debugging: Traces every call. */ +/* +#define MAC_CORE_VERY_VERBOSE_DEBUG + */ +#ifdef MAC_CORE_VERY_VERBOSE_DEBUG +# define VVDBUG(MSG) do { printf("||PaMacCore (vv)|| "); printf MSG ; fflush(stdout); } while(0) +#else +# define VVDBUG(MSG) +#endif + + + + + +#define UNIX_ERR(err) PaMacCore_SetUnixError( err, __LINE__ ) + +PaError PaMacCore_SetUnixError( int err, int line ); + +/* + * Translates MacOS generated errors into PaErrors + */ +PaError PaMacCore_SetError(OSStatus error, int line, int isError); + +/* + * This function computes an appropriate ring buffer size given + * a requested latency (in seconds), sample rate and framesPerBuffer. + * + * The returned ringBufferSize is computed using the following + * constraints: + * - it must be at least 4. + * - it must be at least 3x framesPerBuffer. + * - it must be at least 2x the suggestedLatency. + * - it must be a power of 2. + * This function attempts to compute the minimum such size. + * + */ +long computeRingBufferSize( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + long inputFramesPerBuffer, + long outputFramesPerBuffer, + double sampleRate ); + +/* + * Durring testing of core audio, I found that serious crashes could occur + * if properties such as sample rate were changed multiple times in rapid + * succession. The function below has some fancy logic to make sure that changes + * are acknowledged before another is requested. That seems to help a lot. + */ + +typedef struct { + bool once; /* I didn't end up using this. bdr */ + pthread_mutex_t mutex; +} MutexAndBool ; + +OSStatus propertyProc( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData ); + +/* sets the value of the given property and waits for the change to + be acknowledged, and returns the final value, which is not guaranteed + by this function to be the same as the desired value. Obviously, this + function can only be used for data whose input and output are the + same size and format, and their size and format are known in advance.*/ +PaError AudioDeviceSetPropertyNowAndWaitForChange( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + UInt32 inPropertyDataSize, + const void *inPropertyData, + void *outPropertyData ); + +/* + * Sets the sample rate the HAL device. + * if requireExact: set the sample rate or fail. + * + * otherwise : set the exact sample rate. + * If that fails, check for available sample rates, and choose one + * higher than the requested rate. If there isn't a higher one, + * just use the highest available. + */ +PaError setBestSampleRateForDevice( const AudioDeviceID device, + const bool isOutput, + const bool requireExact, + const Float64 desiredSrate ); +/* + Attempts to set the requestedFramesPerBuffer. If it can't set the exact + value, it settles for something smaller if available. If nothing smaller + is available, it uses the smallest available size. + actualFramesPerBuffer will be set to the actual value on successful return. + OK to pass NULL to actualFramesPerBuffer. + The logic is very simmilar too setBestSampleRate only failure here is + not usually catastrophic. +*/ +PaError setBestFramesPerBuffer( const AudioDeviceID device, + const bool isOutput, + unsigned long requestedFramesPerBuffer, + unsigned long *actualFramesPerBuffer ); + +#ifdef PA_OLD_CORE_AUDIO +# define kAudioUnitErr_PropertyNotInUse 10850 +# define kAudioHardwareUnsupportedOperationError 1 +# define kAudioUnitErr_Initialized 10849 +# define kAudioUnitErr_InvalidOfflineRender 10848 +# define kAudioUnitErr_Unauthorized 10847 +#endif + +#endif /* PA_MAC_CORE_UTILITIES_H__*/ + diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_hostapis.c b/pjmedia/src/pjmedia/portaudio/pa_mac_hostapis.c index f8306c30..89622d6b 100644 --- a/pjmedia/src/pjmedia/portaudio/pa_mac_hostapis.c +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_hostapis.c @@ -1,6 +1,6 @@ /* * $Id$ - * Portable Audio I/O Library Windows initialization table + * Portable Audio I/O Library Macintosh initialization table * * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 1999-2002 Ross Bencina, Phil Burk @@ -16,10 +16,6 @@ * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * Any person wishing to distribute modifications to the Software is - * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. - * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. @@ -29,7 +25,19 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + /** @file + @ingroup macosx_src Mac OS host API initialization function table. */ diff --git a/pjmedia/src/pjmedia/portaudio/ringbuffer.c b/pjmedia/src/pjmedia/portaudio/ringbuffer.c index 16031fef..c66ec613 100644 --- a/pjmedia/src/pjmedia/portaudio/ringbuffer.c +++ b/pjmedia/src/pjmedia/portaudio/ringbuffer.c @@ -4,9 +4,13 @@ * Ring Buffer utility.. * * Author: Phil Burk, http://www.softsynth.com + * modified for SMP safety on Mac OS X by Bjorn Roche + * also, alowed for const where possible + * Note that this is safe only for a single-thread reader and a + * single-thread writer. * * This program uses the PortAudio Portable Audio Library. - * For more information see: http://www.audiomulch.com/portaudio/ + * For more information see: http://www.portaudio.com * Copyright (c) 1999-2000 Ross Bencina and Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining @@ -20,10 +24,6 @@ * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * Any person wishing to distribute modifications to the Software is - * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. - * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. @@ -31,14 +31,69 @@ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. */ + +/** + @file + @ingroup hostapi_src +*/ + #include #include #include #include "ringbuffer.h" #include +/* + * We can undefine this, to turn off memory barriers, but that + * is only useful if we know we don't need to be MP safe or + * we are interested in doing some kind of tests. + */ +#define MPSAFE + +/**************** + * First, we'll define some memory barrier primitives based on the system. + * right now only OS X and FreeBSD are supported. In addition to providing + * memory barriers, these functions should ensure that data cached in registers + * is written out to cache where it can be snooped by other CPUs. (ie, the volatile + * keyword should not be required) + * + * the primitives that must be defined are: + * + * FullMemoryBarrier() + * ReadMemoryBarrier() + * WriteMemoryBarrier() + * + ****************/ + +#if 1 +# define FullMemoryBarrier() +# define ReadMemoryBarrier() +# define WriteMemoryBarrier() +#elif defined(__APPLE__) || defined(__FreeBSD__) +# include + /* Here are the memory barrier functions. Mac OS X and FreeBSD only provide + full memory barriers, so the three types of barriers are the same. + The asm volatile may be redundant with the memory barrier, but + until I have proof of that, I'm leaving it. */ +# define FullMemoryBarrier() do{ asm volatile("":::"memory"); OSMemoryBarrier(); }while(false) +# define ReadMemoryBarrier() do{ asm volatile("":::"memory"); OSMemoryBarrier(); }while(false) +# define WriteMemoryBarrier() do{ asm volatile("":::"memory"); OSMemoryBarrier(); }while(false) +#else +# error Memory Barriers not defined on this system or system unknown +#endif + /*************************************************************************** * Initialize FIFO. * numBytes must be power of 2, returns -1 if not. @@ -57,12 +112,16 @@ long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ) ** Return number of bytes available for reading. */ long RingBuffer_GetReadAvailable( RingBuffer *rbuf ) { +#ifdef MPSAFE + ReadMemoryBarrier(); +#endif return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask ); } /*************************************************************************** ** Return number of bytes available for writing. */ long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ) { + /* Since we are calling RingBuffer_GetReadAvailable, we don't need an aditional MB */ return ( rbuf->bufferSize - RingBuffer_GetReadAvailable(rbuf)); } @@ -112,7 +171,13 @@ long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, */ long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ) { +#ifdef MPSAFE + /* we need to ensure that previous writes are seen before we update the write index */ + WriteMemoryBarrier(); return rbuf->writeIndex = (rbuf->writeIndex + numBytes) & rbuf->bigMask; +#else + return rbuf->writeIndex = (rbuf->writeIndex + numBytes) & rbuf->bigMask; +#endif } /*************************************************************************** @@ -152,12 +217,18 @@ long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, */ long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ) { +#ifdef MPSAFE + /* we need to ensure that previous writes are always seen before updating the index. */ + WriteMemoryBarrier(); + return rbuf->readIndex = (rbuf->readIndex + numBytes) & rbuf->bigMask; +#else return rbuf->readIndex = (rbuf->readIndex + numBytes) & rbuf->bigMask; +#endif } /*************************************************************************** ** Return bytes written. */ -long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ) +long RingBuffer_Write( RingBuffer *rbuf, const void *data, long numBytes ) { long size1, size2, numWritten; void *data1, *data2; diff --git a/pjmedia/src/pjmedia/portaudio/ringbuffer.h b/pjmedia/src/pjmedia/portaudio/ringbuffer.h index dd769bee..816520e3 100644 --- a/pjmedia/src/pjmedia/portaudio/ringbuffer.h +++ b/pjmedia/src/pjmedia/portaudio/ringbuffer.h @@ -11,9 +11,13 @@ extern "C" * Ring Buffer utility.. * * Author: Phil Burk, http://www.softsynth.com + * modified for SMP safety on OS X by Bjorn Roche. + * also allowed for const where possible. + * Note that this is safe only for a single-thread reader + * and a single-thread writer. * * This program is distributed with the PortAudio Portable Audio Library. - * For more information see: http://www.audiomulch.com/portaudio/ + * For more information see: http://www.portaudio.com * Copyright (c) 1999-2000 Ross Bencina and Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining @@ -27,10 +31,6 @@ extern "C" * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * - * Any person wishing to distribute modifications to the Software is - * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. - * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. @@ -38,8 +38,24 @@ extern "C" * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. */ + +/** + @file + @ingroup hostapi_src +*/ + #include #include #include @@ -53,7 +69,7 @@ typedef struct long readIndex; /* Index of next readable byte. Set by RingBuffer_AdvanceReadIndex. */ long bigMask; /* Used for wrapping indices with extra bit to distinguish full/empty. */ long smallMask; /* Used for fitting indices to buffer. */ - char *buffer; + char * buffer; } RingBuffer; /* @@ -70,7 +86,7 @@ long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ); /* Return number of bytes available for read. */ long RingBuffer_GetReadAvailable( RingBuffer *rbuf ); /* Return bytes written. */ -long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ); +long RingBuffer_Write( RingBuffer *rbuf, const void *data, long numBytes ); /* Return bytes read. */ long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ); -- cgit v1.2.3