Added MacOS related files to PortAudio
git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@339 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
parent
e67d99a58a
commit
37bf5b0834
|
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */
|
||||
#ifdef _WIN32
|
||||
#ifndef __MWERKS__
|
||||
#include <memory.h>
|
||||
#endif /* __MWERKS__ */
|
||||
#else /* !_WIN32 */
|
||||
#include <memory.h>
|
||||
#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; i<numRates; i++ )
|
||||
{
|
||||
err = fabs( frameRate - rateTable[i] );
|
||||
if( err < minErr )
|
||||
{
|
||||
minErr = err;
|
||||
bestFit = i;
|
||||
}
|
||||
}
|
||||
return bestFit;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
** Make sure sample rate is legal and also convert to enumeration for driver.
|
||||
*/
|
||||
PaError PaHost_ValidateSampleRate( PaDeviceID id, double requestedFrameRate,
|
||||
double *closestFrameRatePtr )
|
||||
{
|
||||
long bestRateIndex;
|
||||
const PaDeviceInfo *pdi;
|
||||
pdi = Pa_GetDeviceInfo( id );
|
||||
if( pdi == NULL ) return paInvalidDeviceId;
|
||||
|
||||
if( pdi->numSampleRates == -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<<DITHER_BITS)-1))
|
||||
static long Pa_TriangularDither( void )
|
||||
{
|
||||
static unsigned long previous = 0;
|
||||
static unsigned long randSeed1 = 22222;
|
||||
static unsigned long randSeed2 = 5555555;
|
||||
long current, highPass;
|
||||
/* Generate two random numbers. */
|
||||
randSeed1 = (randSeed1 * 196314165) + 907633515;
|
||||
randSeed2 = (randSeed2 * 196314165) + 907633515;
|
||||
/* Generate triangular distribution about 0. */
|
||||
current = (((long)randSeed1)>>(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; i<samplesPerBuffer; i++ )
|
||||
{
|
||||
inBufPtr[i] = nativeInputBuffer[i] * (1.0f / 32767.0f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case paInt32:
|
||||
{
|
||||
/* Convert 16 bit data to 32 bit integers */
|
||||
int *inBufPtr = (int *) past->past_InputBuffer;
|
||||
inputBuffer = past->past_InputBuffer;
|
||||
for( i=0; i<samplesPerBuffer; i++ )
|
||||
{
|
||||
inBufPtr[i] = nativeInputBuffer[i] << 16;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case paInt16:
|
||||
{
|
||||
/* Already in correct format so don't copy. */
|
||||
inputBuffer = nativeInputBuffer;
|
||||
break;
|
||||
}
|
||||
|
||||
case paInt8:
|
||||
{
|
||||
/* Convert 16 bit data to 8 bit chars */
|
||||
char *inBufPtr = (char *) past->past_InputBuffer;
|
||||
inputBuffer = past->past_InputBuffer;
|
||||
if( past->past_Flags & paDitherOff )
|
||||
{
|
||||
for( i=0; i<samplesPerBuffer; i++ )
|
||||
{
|
||||
inBufPtr[i] = (char)(nativeInputBuffer[i] >> 8);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i=0; i<samplesPerBuffer; i++ )
|
||||
{
|
||||
temp = nativeInputBuffer[i];
|
||||
temp += Pa_TriangularDither() >> 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<samplesPerBuffer; i++ )
|
||||
{
|
||||
inBufPtr[i] = ((unsigned char)(nativeInputBuffer[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<samplesPerBuffer; i++ )
|
||||
{
|
||||
temp = nativeInputBuffer[i];
|
||||
temp += Pa_TriangularDither() >> 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<samplesPerBuffer; i++ )
|
||||
{
|
||||
*nativeOutputBuffer++ = (short) (outBufPtr[i] * (32767.0f));
|
||||
}
|
||||
}
|
||||
else /* CLIP */
|
||||
{
|
||||
for( i=0; i<samplesPerBuffer; i++ )
|
||||
{
|
||||
temp = (long)(outBufPtr[i] * 32767.0f);
|
||||
*nativeOutputBuffer++ = (short)((temp < -0x8000) ? -0x8000 : ((temp > 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<samplesPerBuffer; i++ )
|
||||
{
|
||||
float dither = Pa_TriangularDither()*DITHER_SCALE;
|
||||
float dithered = (outBufPtr[i] * (32767.0f)) + dither;
|
||||
temp = (long) (dithered);
|
||||
*nativeOutputBuffer++ = (short)((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case paInt32:
|
||||
{
|
||||
int *outBufPtr = (int *) past->past_OutputBuffer;
|
||||
if( past->past_Flags & paDitherOff )
|
||||
{
|
||||
for( i=0; i<samplesPerBuffer; i++ )
|
||||
{
|
||||
*nativeOutputBuffer++ = (short) (outBufPtr[i] >> 16 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i=0; i<samplesPerBuffer; i++ )
|
||||
{
|
||||
/* Shift one bit down before dithering so that we have room for overflow from add. */
|
||||
temp = (outBufPtr[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; i<samplesPerBuffer; i++ )
|
||||
{
|
||||
*nativeOutputBuffer++ = ((short)outBufPtr[i]) << 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case paUInt8:
|
||||
{
|
||||
unsigned char *outBufPtr = (unsigned char *) past->past_OutputBuffer;
|
||||
for( i=0; i<samplesPerBuffer; i++ )
|
||||
{
|
||||
*nativeOutputBuffer++ = ((short)(outBufPtr[i] - 0x80)) << 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return userResult;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
** Called by host code.
|
||||
** Convert input from Float32, call user code, then convert output
|
||||
** to Float32 format for native use.
|
||||
** Assumes host native format is Float32.
|
||||
** Returns result from user callback.
|
||||
** FIXME - Unimplemented for formats other than paFloat32!!!!
|
||||
*/
|
||||
long Pa_CallConvertFloat32( internalPortAudioStream *past,
|
||||
float *nativeInputBuffer,
|
||||
float *nativeOutputBuffer )
|
||||
{
|
||||
long bytesEmpty = 0;
|
||||
long bytesFilled = 0;
|
||||
int userResult;
|
||||
void *inputBuffer = NULL;
|
||||
void *outputBuffer = NULL;
|
||||
|
||||
/* Get native data from DirectSound. */
|
||||
if( (past->past_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;
|
||||
}
|
||||
|
||||
|
|
@ -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 <CoreAudio/CoreAudio.h>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "ringbuffer.h"
|
||||
#include <string.h>
|
||||
|
||||
/***************************************************************************
|
||||
* 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;
|
||||
}
|
|
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "ringbuffer.h"
|
||||
#include <string.h>
|
||||
|
||||
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 */
|
Loading…
Reference in New Issue