From f13e8318d321d46214e64aba246747a9126a1402 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Mon, 20 Mar 2006 12:55:09 +0000 Subject: Added MacOS related files to PortAudio git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@339 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/src/pjmedia/portaudio/pa_lib.c | 827 +++++++++++++++++++++++++ pjmedia/src/pjmedia/portaudio/pa_mac_core.c | 896 ++++++++++++++++++++++++++++ pjmedia/src/pjmedia/portaudio/ringbuffer.c | 199 ++++++ pjmedia/src/pjmedia/portaudio/ringbuffer.h | 101 ++++ 4 files changed, 2023 insertions(+) create mode 100644 pjmedia/src/pjmedia/portaudio/pa_lib.c create mode 100644 pjmedia/src/pjmedia/portaudio/pa_mac_core.c create mode 100644 pjmedia/src/pjmedia/portaudio/ringbuffer.c create mode 100644 pjmedia/src/pjmedia/portaudio/ringbuffer.h diff --git a/pjmedia/src/pjmedia/portaudio/pa_lib.c b/pjmedia/src/pjmedia/portaudio/pa_lib.c new file mode 100644 index 00000000..86601592 --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/pa_lib.c @@ -0,0 +1,827 @@ +/* + * Portable Audio I/O Library + * Host Independant Layer + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 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. + * + * 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. + * 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. + * + */ + +/* Modification History: + PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC +*/ + +#include +#include +#include +#include + +/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */ +#ifdef _WIN32 +#ifndef __MWERKS__ +#include +#endif /* __MWERKS__ */ +#else /* !_WIN32 */ +#include +#endif /* _WIN32 */ + +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +/* The reason we might NOT want to validate the rate before opening the stream + * is because many DirectSound drivers lie about the rates they actually support. + */ +#define PA_VALIDATE_RATE (0) /* If true validate sample rate against driver info. */ + +/* +O- maybe not allocate past_InputBuffer and past_OutputBuffer if not needed for conversion +*/ + +#ifndef FALSE + #define FALSE (0) + #define TRUE (!FALSE) +#endif + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +static int gInitCount = 0; /* Count number of times Pa_Initialize() called to allow nesting and overlapping. */ + +static PaError Pa_KillStream( PortAudioStream *stream, int abort ); + +/***********************************************************************/ +int PaHost_FindClosestTableEntry( double allowableError, const double *rateTable, int numRates, double frameRate ) +{ + double err, minErr = allowableError; + int i, bestFit = -1; + + for( i=0; inumSampleRates == -1 ) + { + /* Is it out of range? */ + if( (requestedFrameRate < pdi->sampleRates[0]) || + (requestedFrameRate > pdi->sampleRates[1]) ) + { + return paInvalidSampleRate; + } + + *closestFrameRatePtr = requestedFrameRate; + } + else + { + bestRateIndex = PaHost_FindClosestTableEntry( 1.0, pdi->sampleRates, pdi->numSampleRates, requestedFrameRate ); + if( bestRateIndex < 0 ) return paInvalidSampleRate; + *closestFrameRatePtr = pdi->sampleRates[bestRateIndex]; + } + return paNoError; +} + +/*************************************************************************/ +DLL_API PaError Pa_OpenStream( + PortAudioStream** streamPtrPtr, + PaDeviceID inputDeviceID, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDeviceID, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + unsigned long streamFlags, + PortAudioCallback *callback, + void *userData ) +{ + internalPortAudioStream *past = NULL; + PaError result = paNoError; + int bitsPerInputSample; + int bitsPerOutputSample; + /* Print passed parameters. */ + DBUG(("Pa_OpenStream( %p, %d, %d, %d, %p, /* input */ \n", + streamPtrPtr, inputDeviceID, numInputChannels, + inputSampleFormat, inputDriverInfo )); + DBUG((" %d, %d, %d, %p, /* output */\n", + outputDeviceID, numOutputChannels, + outputSampleFormat, outputDriverInfo )); + DBUG((" %g, %d, %d, 0x%x, , %p )\n", + sampleRate, framesPerBuffer, numberOfBuffers, + streamFlags, userData )); + + /* Check for parameter errors. */ + if( (streamFlags & ~(paClipOff | paDitherOff)) != 0 ) return paInvalidFlag; + if( streamPtrPtr == NULL ) return paBadStreamPtr; + if( inputDriverInfo != NULL ) return paHostError; /* REVIEW */ + if( outputDriverInfo != NULL ) return paHostError; /* REVIEW */ + if( (inputDeviceID < 0) && ( outputDeviceID < 0) ) return paInvalidDeviceId; + if( (outputDeviceID >= Pa_CountDevices()) || (inputDeviceID >= Pa_CountDevices()) ) return paInvalidDeviceId; + if( (numInputChannels <= 0) && ( numOutputChannels <= 0) ) return paInvalidChannelCount; + +#if SUPPORT_AUDIO_CAPTURE + if( inputDeviceID >= 0 ) + { + PaError size = Pa_GetSampleSize( inputSampleFormat ); + if( size < 0 ) return size; + bitsPerInputSample = 8 * size; + if( (numInputChannels <= 0) ) return paInvalidChannelCount; + } +#else + if( inputDeviceID >= 0 ) + { + return paInvalidChannelCount; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + else + { + if( numInputChannels > 0 ) return paInvalidChannelCount; + bitsPerInputSample = 0; + } + + if( outputDeviceID >= 0 ) + { + PaError size = Pa_GetSampleSize( outputSampleFormat ); + if( size < 0 ) return size; + bitsPerOutputSample = 8 * size; + if( (numOutputChannels <= 0) ) return paInvalidChannelCount; + } + else + { + if( numOutputChannels > 0 ) return paInvalidChannelCount; + bitsPerOutputSample = 0; + } + + if( callback == NULL ) return paNullCallback; + + /* Allocate and clear stream structure. */ + past = (internalPortAudioStream *) PaHost_AllocateFastMemory( sizeof(internalPortAudioStream) ); + if( past == NULL ) return paInsufficientMemory; + memset( past, 0, sizeof(internalPortAudioStream) ); + AddTraceMessage("Pa_OpenStream: past", (long) past ); + + past->past_Magic = PA_MAGIC; /* Set ID to catch bugs. */ + past->past_FramesPerUserBuffer = framesPerBuffer; + past->past_NumUserBuffers = numberOfBuffers; /* NOTE - PaHost_OpenStream() NMUST CHECK FOR ZERO! */ + past->past_Callback = callback; + past->past_UserData = userData; + past->past_OutputSampleFormat = outputSampleFormat; + past->past_InputSampleFormat = inputSampleFormat; + past->past_OutputDeviceID = outputDeviceID; + past->past_InputDeviceID = inputDeviceID; + past->past_NumInputChannels = numInputChannels; + past->past_NumOutputChannels = numOutputChannels; + past->past_Flags = streamFlags; + + /* Check for absurd sample rates. */ + if( (sampleRate < 1000.0) || (sampleRate > 200000.0) ) + { + result = paInvalidSampleRate; + goto cleanup; + } + + /* Allocate buffers that may be used for format conversion from user to native buffers. */ + if( numInputChannels > 0 ) + { + +#if PA_VALIDATE_RATE + result = PaHost_ValidateSampleRate( inputDeviceID, sampleRate, &past->past_SampleRate ); + if( result < 0 ) + { + goto cleanup; + } +#else + past->past_SampleRate = sampleRate; +#endif + /* Allocate single Input buffer. */ + past->past_InputBufferSize = framesPerBuffer * numInputChannels * ((bitsPerInputSample+7) / 8); + past->past_InputBuffer = PaHost_AllocateFastMemory(past->past_InputBufferSize); + if( past->past_InputBuffer == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + } + else + { + past->past_InputBuffer = NULL; + } + + /* Allocate single Output buffer. */ + if( numOutputChannels > 0 ) + { +#if PA_VALIDATE_RATE + result = PaHost_ValidateSampleRate( outputDeviceID, sampleRate, &past->past_SampleRate ); + if( result < 0 ) + { + goto cleanup; + } +#else + past->past_SampleRate = sampleRate; +#endif + past->past_OutputBufferSize = framesPerBuffer * numOutputChannels * ((bitsPerOutputSample+7) / 8); + past->past_OutputBuffer = PaHost_AllocateFastMemory(past->past_OutputBufferSize); + if( past->past_OutputBuffer == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + } + else + { + past->past_OutputBuffer = NULL; + } + + result = PaHost_OpenStream( past ); + if( result < 0 ) goto cleanup; + + *streamPtrPtr = (void *) past; + + return result; + +cleanup: + if( past != NULL ) Pa_CloseStream( past ); + *streamPtrPtr = NULL; + return result; +} + + +/*************************************************************************/ +DLL_API PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ) +{ + return Pa_OpenStream( + stream, + ((numInputChannels > 0) ? Pa_GetDefaultInputDeviceID() : paNoDevice), + numInputChannels, sampleFormat, NULL, + ((numOutputChannels > 0) ? Pa_GetDefaultOutputDeviceID() : paNoDevice), + numOutputChannels, sampleFormat, NULL, + sampleRate, framesPerBuffer, numberOfBuffers, paNoFlag, callback, userData ); +} + +/*************************************************************************/ +DLL_API PaError Pa_CloseStream( PortAudioStream* stream) +{ + PaError result; + internalPortAudioStream *past; + + DBUG(("Pa_CloseStream()\n")); + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + Pa_AbortStream( past ); + result = PaHost_CloseStream( past ); + + if( past->past_InputBuffer ) PaHost_FreeFastMemory( past->past_InputBuffer, past->past_InputBufferSize ); + if( past->past_OutputBuffer ) PaHost_FreeFastMemory( past->past_OutputBuffer, past->past_OutputBufferSize ); + PaHost_FreeFastMemory( past, sizeof(internalPortAudioStream) ); + + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_StartStream( PortAudioStream *stream ) +{ + PaError result = paHostError; + internalPortAudioStream *past; + + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + past->past_FrameCount = 0.0; + + if( past->past_NumInputChannels > 0 ) + { + result = PaHost_StartInput( past ); + DBUG(("Pa_StartStream: PaHost_StartInput returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + if( past->past_NumOutputChannels > 0 ) + { + result = PaHost_StartOutput( past ); + DBUG(("Pa_StartStream: PaHost_StartOutput returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + result = PaHost_StartEngine( past ); + DBUG(("Pa_StartStream: PaHost_StartEngine returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + + return paNoError; + +error: + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_StopStream( PortAudioStream *stream ) +{ + return Pa_KillStream( stream, 0 ); +} + +/*************************************************************************/ +DLL_API PaError Pa_AbortStream( PortAudioStream *stream ) +{ + return Pa_KillStream( stream, 1 ); +} + +/*************************************************************************/ +static PaError Pa_KillStream( PortAudioStream *stream, int abort ) +{ + PaError result = paNoError; + internalPortAudioStream *past; + + DBUG(("Pa_StopStream().\n")); + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + if( (past->past_NumInputChannels > 0) || (past->past_NumOutputChannels > 0) ) + { + result = PaHost_StopEngine( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopEngine returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + if( past->past_NumInputChannels > 0 ) + { + result = PaHost_StopInput( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopInput returned = 0x%X.\n", result)); + if( result != paNoError ) goto error; + } + + if( past->past_NumOutputChannels > 0 ) + { + result = PaHost_StopOutput( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopOutput returned = 0x%X.\n", result)); + if( result != paNoError ) goto error; + } + +error: + past->past_Usage = 0; + past->past_IfLastExitValid = 0; + + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_StreamActive( PortAudioStream *stream ) +{ + internalPortAudioStream *past; + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return PaHost_StreamActive( past ); +} + +/*************************************************************************/ +DLL_API const char *Pa_GetErrorText( PaError errnum ) +{ + const char *msg; + + switch(errnum) + { + case paNoError: msg = "Success"; break; + case paHostError: msg = "Host error."; break; + case paInvalidChannelCount: msg = "Invalid number of channels."; break; + case paInvalidSampleRate: msg = "Invalid sample rate."; break; + case paInvalidDeviceId: msg = "Invalid device ID."; break; + case paInvalidFlag: msg = "Invalid flag."; break; + case paSampleFormatNotSupported: msg = "Sample format not supported"; break; + case paBadIODeviceCombination: msg = "Illegal combination of I/O devices."; break; + case paInsufficientMemory: msg = "Insufficient memory."; break; + case paBufferTooBig: msg = "Buffer too big."; break; + case paBufferTooSmall: msg = "Buffer too small."; break; + case paNullCallback: msg = "No callback routine specified."; break; + case paBadStreamPtr: msg = "Invalid stream pointer."; break; + case paTimedOut : msg = "Wait Timed Out."; break; + case paInternalError: msg = "Internal PortAudio Error."; break; + default: msg = "Illegal error number."; break; + } + return msg; +} + +/* + Get CPU Load as a fraction of total CPU time. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + The amount may vary depending on CPU load. + This function may be called from the callback function. +*/ +DLL_API double Pa_GetCPULoad( PortAudioStream* stream) +{ + internalPortAudioStream *past; + if( stream == NULL ) return (double) paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return past->past_Usage; +} + +/************************************************************* +** Calculate 2 LSB dither signal with a triangular distribution. +** Ranged properly for adding to a 32 bit integer prior to >>15. +*/ +#define DITHER_BITS (15) +#define DITHER_SCALE (1.0f / ((1<>(32-DITHER_BITS)) + (((long)randSeed2)>>(32-DITHER_BITS)); + /* High pass filter to reduce audibility. */ + highPass = current - previous; + previous = current; + return highPass; +} + +/************************************************************************* +** Called by host code. +** Convert input from Int16, call user code, then convert output +** to Int16 format for native use. +** Assumes host native format is paInt16. +** Returns result from user callback. +*/ +long Pa_CallConvertInt16( internalPortAudioStream *past, + short *nativeInputBuffer, + short *nativeOutputBuffer ) +{ + long temp; + long bytesEmpty = 0; + long bytesFilled = 0; + int userResult; + unsigned int i; + void *inputBuffer = NULL; + void *outputBuffer = NULL; + +#if SUPPORT_AUDIO_CAPTURE + /* Get native data from DirectSound. */ + if( (past->past_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + /* Convert from native format to PA format. */ + unsigned int samplesPerBuffer = past->past_FramesPerUserBuffer * past->past_NumInputChannels; + switch(past->past_InputSampleFormat) + { + + case paFloat32: + { + float *inBufPtr = (float *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + for( i=0; ipast_InputBuffer; + inputBuffer = past->past_InputBuffer; + for( i=0; ipast_InputBuffer; + inputBuffer = past->past_InputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i> 8); + } + } + else + { + for( i=0; i> 7; + temp = ((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + inBufPtr[i] = (char)(temp >> 8); + } + } + break; + } + + case paUInt8: + { + /* Convert 16 bit data to 8 bit unsigned chars */ + unsigned char *inBufPtr = (unsigned char *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i> 8)) + 0x80; + } + } + else + { + /* If you dither then you have to clip because dithering could push the signal out of range! */ + for( i=0; i> 7; + temp = ((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + inBufPtr[i] = (unsigned char)(temp + 0x80); + } + } + break; + } + + default: + break; + } + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + + /* Are we doing output time? */ + if( (past->past_NumOutputChannels > 0) && (nativeOutputBuffer != NULL) ) + { + /* May already be in native format so just write directly to native buffer. */ + outputBuffer = (past->past_OutputSampleFormat == paInt16) ? + nativeOutputBuffer : past->past_OutputBuffer; + } + /* + AddTraceMessage("Pa_CallConvertInt16: inputBuffer = ", (int) inputBuffer ); + AddTraceMessage("Pa_CallConvertInt16: outputBuffer = ", (int) outputBuffer ); + */ + /* Call user callback routine. */ + userResult = past->past_Callback( + inputBuffer, + outputBuffer, + past->past_FramesPerUserBuffer, + past->past_FrameCount, + past->past_UserData ); + + past->past_FrameCount += (PaTimestamp) past->past_FramesPerUserBuffer; + + /* Convert to native format if necessary. */ + if( outputBuffer != NULL ) + { + unsigned int samplesPerBuffer = past->past_FramesPerUserBuffer * past->past_NumOutputChannels; + switch(past->past_OutputSampleFormat) + { + case paFloat32: + { + float *outBufPtr = (float *) past->past_OutputBuffer; + if( past->past_Flags & paDitherOff ) + { + if( past->past_Flags & paClipOff ) /* NOTHING */ + { + for( i=0; i 0x7FFF) ? 0x7FFF : temp)); + } + } + } + else + { + /* If you dither then you have to clip because dithering could push the signal out of range! */ + for( i=0; i 0x7FFF) ? 0x7FFF : temp)); + } + } + break; + } + + case paInt32: + { + int *outBufPtr = (int *) past->past_OutputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i> 16 ); + } + } + else + { + for( i=0; i> 1) + Pa_TriangularDither(); + temp = temp >> 15; + *nativeOutputBuffer++ = (short)((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + } + } + break; + } + + case paInt8: + { + char *outBufPtr = (char *) past->past_OutputBuffer; + for( i=0; ipast_OutputBuffer; + for( i=0; ipast_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + inputBuffer = nativeInputBuffer; // FIXME + } + + /* Are we doing output time? */ + if( (past->past_NumOutputChannels > 0) && (nativeOutputBuffer != NULL) ) + { + /* May already be in native format so just write directly to native buffer. */ + outputBuffer = (past->past_OutputSampleFormat == paFloat32) ? + nativeOutputBuffer : past->past_OutputBuffer; + } + /* + AddTraceMessage("Pa_CallConvertInt16: inputBuffer = ", (int) inputBuffer ); + AddTraceMessage("Pa_CallConvertInt16: outputBuffer = ", (int) outputBuffer ); + */ + /* Call user callback routine. */ + userResult = past->past_Callback( + inputBuffer, + outputBuffer, + past->past_FramesPerUserBuffer, + past->past_FrameCount, + past->past_UserData ); + + past->past_FrameCount += (PaTimestamp) past->past_FramesPerUserBuffer; + + /* Convert to native format if necessary. */ // FIXME + return userResult; +} + +/*************************************************************************/ +DLL_API PaError Pa_Initialize( void ) +{ + if( gInitCount++ > 0 ) return paNoError; + ResetTraceMessages(); + return PaHost_Init(); +} + +DLL_API PaError Pa_Terminate( void ) +{ + PaError result = paNoError; + + if( gInitCount == 0 ) return paNoError; + else if( --gInitCount == 0 ) + { + result = PaHost_Term(); + DumpTraceMessages(); + } + return result; +} + +/*************************************************************************/ +DLL_API PaError Pa_GetSampleSize( PaSampleFormat format ) +{ + int size; + switch(format ) + { + + case paUInt8: + case paInt8: + size = 1; + break; + + case paInt16: + size = 2; + break; + + case paPackedInt24: + size = 3; + break; + + case paFloat32: + case paInt32: + case paInt24: + size = 4; + break; + + default: + size = paSampleFormatNotSupported; + break; + } + return (PaError) size; +} + + diff --git a/pjmedia/src/pjmedia/portaudio/pa_mac_core.c b/pjmedia/src/pjmedia/portaudio/pa_mac_core.c new file mode 100644 index 00000000..020b5aeb --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/pa_mac_core.c @@ -0,0 +1,896 @@ +/* + * $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. + * + * 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. + * 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. + * + */ + +#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; + 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) +{ + 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; + } +} + +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; + } +} + +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); + } +} + +// 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 ); + + AudioBuffer const *inputBuffer = &inInputData->mBuffers[0]; + unsigned long frameCount = inputBuffer->mDataByteSize / (inputBuffer->mNumberChannels * sizeof(Float32)); + + CopyInputData(clientData, inInputData, frameCount); + clientData->callback(clientData->inputBuffer, NULL, frameCount, timeInfo, paNoFlag, clientData->userData); + + PaUtil_EndCpuLoadMeasurement( &clientData->stream->cpuLoadMeasurer, frameCount ); +} + +// 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 ); +} + +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; +} \ No newline at end of file diff --git a/pjmedia/src/pjmedia/portaudio/ringbuffer.c b/pjmedia/src/pjmedia/portaudio/ringbuffer.c new file mode 100644 index 00000000..16031fef --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/ringbuffer.c @@ -0,0 +1,199 @@ +/* + * $Id$ + * ringbuffer.c + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * 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. + * + */ +#include +#include +#include +#include "ringbuffer.h" +#include + +/*************************************************************************** + * Initialize FIFO. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ) +{ + if( ((numBytes-1) & numBytes) != 0) return -1; /* Not Power of two. */ + rbuf->bufferSize = numBytes; + rbuf->buffer = (char *)dataPtr; + RingBuffer_Flush( rbuf ); + rbuf->bigMask = (numBytes*2)-1; + rbuf->smallMask = (numBytes)-1; + return 0; +} +/*************************************************************************** +** Return number of bytes available for reading. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ) +{ + return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask ); +} +/*************************************************************************** +** Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ) +{ + return ( rbuf->bufferSize - RingBuffer_GetReadAvailable(rbuf)); +} + +/*************************************************************************** +** Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ) +{ + rbuf->writeIndex = rbuf->readIndex = 0; +} + +/*************************************************************************** +** Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetWriteAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if write is not contiguous. */ + index = rbuf->writeIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} + + +/*************************************************************************** +*/ +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->writeIndex = (rbuf->writeIndex + numBytes) & rbuf->bigMask; +} + +/*************************************************************************** +** Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetReadAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if read is not contiguous. */ + index = rbuf->readIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} +/*************************************************************************** +*/ +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->readIndex = (rbuf->readIndex + numBytes) & rbuf->bigMask; +} + +/*************************************************************************** +** Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numWritten; + void *data1, *data2; + numWritten = RingBuffer_GetWriteRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + + memcpy( data1, data, size1 ); + data = ((char *)data) + size1; + memcpy( data2, data, size2 ); + } + else + { + memcpy( data1, data, size1 ); + } + RingBuffer_AdvanceWriteIndex( rbuf, numWritten ); + return numWritten; +} + +/*************************************************************************** +** Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numRead; + void *data1, *data2; + numRead = RingBuffer_GetReadRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + memcpy( data, data1, size1 ); + data = ((char *)data) + size1; + memcpy( data, data2, size2 ); + } + else + { + memcpy( data, data1, size1 ); + } + RingBuffer_AdvanceReadIndex( rbuf, numRead ); + return numRead; +} diff --git a/pjmedia/src/pjmedia/portaudio/ringbuffer.h b/pjmedia/src/pjmedia/portaudio/ringbuffer.h new file mode 100644 index 00000000..dd769bee --- /dev/null +++ b/pjmedia/src/pjmedia/portaudio/ringbuffer.h @@ -0,0 +1,101 @@ +#ifndef _RINGBUFFER_H +#define _RINGBUFFER_H +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id$ + * ringbuffer.h + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program is distributed with the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * 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. + * + * 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. + * 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. + * + */ +#include +#include +#include +#include "ringbuffer.h" +#include + +typedef struct +{ + long bufferSize; /* Number of bytes in FIFO. Power of 2. Set by RingBuffer_Init. */ + long writeIndex; /* Index of next writable byte. Set by RingBuffer_AdvanceWriteIndex. */ + 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; +} +RingBuffer; +/* + * Initialize Ring Buffer. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ); + +/* Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ); + +/* Return number of bytes available for writing. */ +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 ); +/* Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ); + +/* Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ); + +/* Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); + +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _RINGBUFFER_H */ -- cgit v1.2.3