diff options
Diffstat (limited to 'pd/portaudio')
-rw-r--r-- | pd/portaudio/pa_asio/pa_asio.cpp | 329 | ||||
-rw-r--r-- | pd/portaudio/pa_asio/pa_asio.h | 45 | ||||
-rw-r--r-- | pd/portaudio/pa_common/pa_allocation.c | 14 | ||||
-rw-r--r-- | pd/portaudio/pa_common/pa_converters.c | 15 | ||||
-rw-r--r-- | pd/portaudio/pa_common/pa_front.c | 6 | ||||
-rw-r--r-- | pd/portaudio/pa_common/pa_process.c | 226 | ||||
-rw-r--r-- | pd/portaudio/pa_common/pa_process.h | 20 | ||||
-rw-r--r-- | pd/portaudio/pa_common/portaudio.h | 23 | ||||
-rw-r--r-- | pd/portaudio/pa_jack/pa_jack.c | 1450 | ||||
-rw-r--r-- | pd/portaudio/pa_linux_alsa/pa_linux_alsa.c | 2922 | ||||
-rw-r--r-- | pd/portaudio/pa_linux_alsa/pa_linux_alsa.h | 36 | ||||
-rw-r--r-- | pd/portaudio/pa_mac_core/pa_mac_core.c | 6 | ||||
-rw-r--r-- | pd/portaudio/pa_unix/pa_unix_util.c | 71 | ||||
-rw-r--r-- | pd/portaudio/pa_unix_oss/pa_unix_oss.c | 1934 | ||||
-rw-r--r-- | pd/portaudio/pa_win/pa_win_hostapis.c | 9 | ||||
-rw-r--r-- | pd/portaudio/pa_win_ds/dsound_wrapper.h | 7 |
16 files changed, 4733 insertions, 2380 deletions
diff --git a/pd/portaudio/pa_asio/pa_asio.cpp b/pd/portaudio/pa_asio/pa_asio.cpp index 34a2d39b..f95f39af 100644 --- a/pd/portaudio/pa_asio/pa_asio.cpp +++ b/pd/portaudio/pa_asio/pa_asio.cpp @@ -1,5 +1,5 @@ /* - * $Id: pa_asio.cpp,v 1.7.2.59 2004/02/15 15:39:05 rossbencina Exp $ + * $Id: pa_asio.cpp,v 1.7.2.65 2005/02/21 08:07:10 rossbencina Exp $ * Portable Audio I/O Library for ASIO Drivers * * Author: Stephane Letz @@ -285,7 +285,7 @@ typedef struct void *systemSpecific; /* the ASIO C API only allows one ASIO driver to be open at a time, - so we kee track of whether we have the driver open here, and + so we keep track of whether we have the driver open here, and use this information to return errors from OpenStream if the driver is already open. @@ -364,6 +364,31 @@ static PaSampleFormat AsioSampleTypeToPaNativeSampleFormat(ASIOSampleType type) } } +void AsioSampleTypeLOG(ASIOSampleType type) +{ + switch (type) { + case ASIOSTInt16MSB: PA_DEBUG(("ASIOSTInt16MSB\n")); break; + case ASIOSTInt16LSB: PA_DEBUG(("ASIOSTInt16LSB\n")); break; + case ASIOSTFloat32MSB:PA_DEBUG(("ASIOSTFloat32MSB\n"));break; + case ASIOSTFloat32LSB:PA_DEBUG(("ASIOSTFloat32LSB\n"));break; + case ASIOSTFloat64MSB:PA_DEBUG(("ASIOSTFloat64MSB\n"));break; + case ASIOSTFloat64LSB:PA_DEBUG(("ASIOSTFloat64LSB\n"));break; + case ASIOSTInt32MSB: PA_DEBUG(("ASIOSTInt32MSB\n")); break; + case ASIOSTInt32LSB: PA_DEBUG(("ASIOSTInt32LSB\n")); break; + case ASIOSTInt32MSB16:PA_DEBUG(("ASIOSTInt32MSB16\n"));break; + case ASIOSTInt32LSB16:PA_DEBUG(("ASIOSTInt32LSB16\n"));break; + case ASIOSTInt32MSB18:PA_DEBUG(("ASIOSTInt32MSB18\n"));break; + case ASIOSTInt32MSB20:PA_DEBUG(("ASIOSTInt32MSB20\n"));break; + case ASIOSTInt32MSB24:PA_DEBUG(("ASIOSTInt32MSB24\n"));break; + case ASIOSTInt32LSB18:PA_DEBUG(("ASIOSTInt32LSB18\n"));break; + case ASIOSTInt32LSB20:PA_DEBUG(("ASIOSTInt32LSB20\n"));break; + case ASIOSTInt32LSB24:PA_DEBUG(("ASIOSTInt32LSB24\n"));break; + case ASIOSTInt24MSB: PA_DEBUG(("ASIOSTInt24MSB\n")); break; + case ASIOSTInt24LSB: PA_DEBUG(("ASIOSTInt24LSB\n")); break; + default: PA_DEBUG(("Custom Format%d\n",type));break; + + } +} static int BytesPerAsioSample( ASIOSampleType sampleType ) { @@ -867,6 +892,8 @@ typedef struct PaAsioDeviceInfo long maxBufferSize; long preferredBufferSize; long bufferGranularity; + + ASIOChannelInfo *asioChannelInfos; } PaAsioDeviceInfo; @@ -970,7 +997,7 @@ error: #define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ 13 /* must be the same number of elements as in the array below */ static ASIOSampleRate defaultSampleRateSearchOrder_[] = {44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, - 192000.0, 16000.0, 12000.0, 11025.0, 96000.0, 8000.0 }; + 192000.0, 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 }; PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) @@ -981,7 +1008,6 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex PaAsioDeviceInfo *deviceInfoArray; char **names; PaAsioDriverInfo paAsioDriverInfo; - ASIOChannelInfo asioChannelInfo; asioHostApi = (PaAsioHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaAsioHostApiRepresentation) ); if( !asioHostApi ) @@ -1066,13 +1092,16 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex deviceInfo->name = names[i]; PA_DEBUG(("PaAsio_Initialize: drv:%d name = %s\n", i,deviceInfo->name)); + PA_DEBUG(("PaAsio_Initialize: drv:%d inputChannels = %d\n", i, paAsioDriverInfo.inputChannelCount)); + PA_DEBUG(("PaAsio_Initialize: drv:%d outputChannels = %d\n", i, paAsioDriverInfo.outputChannelCount)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferMinSize = %d\n", i, paAsioDriverInfo.bufferMinSize)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferMaxSize = %d\n", i, paAsioDriverInfo.bufferMaxSize)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferPreferredSize = %d\n", i, paAsioDriverInfo.bufferPreferredSize)); + PA_DEBUG(("PaAsio_Initialize: drv:%d bufferGranularity = %d\n", i, paAsioDriverInfo.bufferGranularity)); deviceInfo->maxInputChannels = paAsioDriverInfo.inputChannelCount; deviceInfo->maxOutputChannels = paAsioDriverInfo.outputChannelCount; - PA_DEBUG(("PaAsio_Initialize: drv:%d inputChannels = %d\n", i, paAsioDriverInfo.inputChannelCount)); - PA_DEBUG(("PaAsio_Initialize: drv:%d outputChannels = %d\n", i, paAsioDriverInfo.outputChannelCount)); - deviceInfo->defaultSampleRate = 0.; bool foundDefaultSampleRate = false; for( int j=0; j < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++j ) @@ -1138,10 +1167,42 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex asioDeviceInfo->bufferGranularity = paAsioDriverInfo.bufferGranularity; - /* We assume that all channels have the same SampleType, so check the first, FIXME, probably shouldn't assume that */ - asioChannelInfo.channel = 0; - asioChannelInfo.isInput = 1; - ASIOGetChannelInfo( &asioChannelInfo ); /* FIXME, check return code */ + asioDeviceInfo->asioChannelInfos = (ASIOChannelInfo*)PaUtil_GroupAllocateMemory( + asioHostApi->allocations, + sizeof(ASIOChannelInfo) * (deviceInfo->maxInputChannels + + deviceInfo->maxOutputChannels) ); + if( !asioDeviceInfo->asioChannelInfos ) + { + result = paInsufficientMemory; + goto error; + } + + int a; + + for( a=0; a < deviceInfo->maxInputChannels; ++a ){ + asioDeviceInfo->asioChannelInfos[a].channel = a; + asioDeviceInfo->asioChannelInfos[a].isInput = ASIOTrue; + ASIOError asioError = ASIOGetChannelInfo( &asioDeviceInfo->asioChannelInfos[a] ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + } + + for( a=0; a < deviceInfo->maxOutputChannels; ++a ){ + int b = deviceInfo->maxInputChannels + a; + asioDeviceInfo->asioChannelInfos[b].channel = a; + asioDeviceInfo->asioChannelInfos[b].isInput = ASIOFalse; + ASIOError asioError = ASIOGetChannelInfo( &asioDeviceInfo->asioChannelInfos[b] ); + if( asioError != ASE_OK ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + goto error; + } + } /* unload the driver */ @@ -1256,8 +1317,9 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, asioDeviceIndex = inputParameters->device; /* validate inputStreamInfo */ - if( inputParameters->hostApiSpecificStreamInfo ) - return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + /** @todo do more validation here */ + // if( inputParameters->hostApiSpecificStreamInfo ) + // return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ } else { @@ -1283,8 +1345,9 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, asioDeviceIndex = outputParameters->device; /* validate outputStreamInfo */ - if( outputParameters->hostApiSpecificStreamInfo ) - return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + /** @todo do more validation here */ + // if( outputParameters->hostApiSpecificStreamInfo ) + // return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ } else { @@ -1447,7 +1510,7 @@ static unsigned long SelectHostBufferSize( unsigned long suggestedLatencyFrames, result = 2; while( result < suggestedLatencyFrames ) - result *= result; + result *= 2; if( result < (unsigned long)driverInfo->bufferMinSize ) result = driverInfo->bufferMinSize; @@ -1491,6 +1554,40 @@ static unsigned long SelectHostBufferSize( unsigned long suggestedLatencyFrames, } +/* returns channelSelectors if present */ + +static PaError ValidateAsioSpecificStreamInfo( + const PaStreamParameters *streamParameters, + const PaAsioStreamInfo *streamInfo, + int deviceChannelCount, + int **channelSelectors ) +{ + if( streamInfo ) + { + if( streamInfo->size != sizeof( PaAsioStreamInfo ) + || streamInfo->version != 1 ) + { + return paIncompatibleHostApiSpecificStreamInfo; + } + + if( streamInfo->flags & paAsioUseChannelSelectors ) + *channelSelectors = streamInfo->channelSelectors; + + if( !(*channelSelectors) ) + return paIncompatibleHostApiSpecificStreamInfo; + + for( int i=0; i < streamParameters->channelCount; ++i ){ + if( (*channelSelectors)[i] < 0 + || (*channelSelectors)[i] >= deviceChannelCount ){ + return paInvalidChannelCount; + } + } + } + + return paNoError; +} + + /* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, @@ -1506,6 +1603,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaError result = paNoError; PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi; PaAsioStream *stream = 0; + PaAsioStreamInfo *inputStreamInfo, *outputStreamInfo; unsigned long framesPerHostBuffer; int inputChannelCount, outputChannelCount; PaSampleFormat inputSampleFormat, outputSampleFormat; @@ -1519,36 +1617,48 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, int completedBuffersPlayedEventInited = 0; int i; PaAsioDriverInfo *driverInfo; + int *inputChannelSelectors = 0; + int *outputChannelSelectors = 0; /* unless we move to using lower level ASIO calls, we can only have one device open at a time */ - if( asioHostApi->openAsioDeviceIndex != paNoDevice ) + if( asioHostApi->openAsioDeviceIndex != paNoDevice ){ + PA_DEBUG(("OpenStream paDeviceUnavailable\n")); return paDeviceUnavailable; + } if( inputParameters && outputParameters ) { /* full duplex ASIO stream must use the same device for input and output */ - if( inputParameters->device != outputParameters->device ) + if( inputParameters->device != outputParameters->device ){ + PA_DEBUG(("OpenStream paBadIODeviceCombination\n")); return paBadIODeviceCombination; } + } if( inputParameters ) { inputChannelCount = inputParameters->channelCount; inputSampleFormat = inputParameters->sampleFormat; - suggestedInputLatencyFrames = (unsigned long)(inputParameters->suggestedLatency * sampleRate); - - asioDeviceIndex = inputParameters->device; + suggestedInputLatencyFrames = (unsigned long)((inputParameters->suggestedLatency * sampleRate)+0.5f); /* unless alternate device specification is supported, reject the use of paUseHostApiSpecificDeviceSpecification */ if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) return paInvalidDevice; + asioDeviceIndex = inputParameters->device; + + PaAsioDeviceInfo *asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[asioDeviceIndex]; + /* validate hostApiSpecificStreamInfo */ - if( inputParameters->hostApiSpecificStreamInfo ) - return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + inputStreamInfo = (PaAsioStreamInfo*)inputParameters->hostApiSpecificStreamInfo; + result = ValidateAsioSpecificStreamInfo( inputParameters, inputStreamInfo, + asioDeviceInfo->commonDeviceInfo.maxInputChannels, + &inputChannelSelectors + ); + if( result != paNoError ) return result; } else { @@ -1561,18 +1671,24 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, { outputChannelCount = outputParameters->channelCount; outputSampleFormat = outputParameters->sampleFormat; - suggestedOutputLatencyFrames = (unsigned long)(outputParameters->suggestedLatency * sampleRate); - - asioDeviceIndex = outputParameters->device; + suggestedOutputLatencyFrames = (unsigned long)((outputParameters->suggestedLatency * sampleRate)+0.5f); /* unless alternate device specification is supported, reject the use of paUseHostApiSpecificDeviceSpecification */ if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) return paInvalidDevice; + asioDeviceIndex = outputParameters->device; + + PaAsioDeviceInfo *asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[asioDeviceIndex]; + /* validate hostApiSpecificStreamInfo */ - if( outputParameters->hostApiSpecificStreamInfo ) - return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + outputStreamInfo = (PaAsioStreamInfo*)outputParameters->hostApiSpecificStreamInfo; + result = ValidateAsioSpecificStreamInfo( outputParameters, outputStreamInfo, + asioDeviceInfo->commonDeviceInfo.maxOutputChannels, + &outputChannelSelectors + ); + if( result != paNoError ) return result; } else { @@ -1590,8 +1706,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, driverInfo, asioHostApi->systemSpecific ); if( result == paNoError ) asioIsInitialized = 1; - else + else{ + PA_DEBUG(("OpenStream ERROR1\n")); goto error; + } /* check that input device can support inputChannelCount */ if( inputChannelCount > 0 ) @@ -1599,6 +1717,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( inputChannelCount > driverInfo->inputChannelCount ) { result = paInvalidChannelCount; + PA_DEBUG(("OpenStream ERROR2\n")); goto error; } } @@ -1609,17 +1728,21 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( outputChannelCount > driverInfo->outputChannelCount ) { result = paInvalidChannelCount; + PA_DEBUG(("OpenStream ERROR3\n")); goto error; } } + PA_DEBUG(("before ASIOSetSampleRate(%f)\n",sampleRate)); + asioError = ASIOSetSampleRate( sampleRate ); /* Set sample rate */ - if( ASIOSetSampleRate( sampleRate ) != ASE_OK ) + if( asioError != ASE_OK ) { result = paInvalidSampleRate; + PA_DEBUG(("ERROR: ASIOSetSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); goto error; } - + PA_DEBUG(("after ASIOSetSampleRate(%f)\n",sampleRate)); /* IMPLEMENT ME: - if a full duplex stream is requested, check that the combination @@ -1627,14 +1750,17 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, */ /* validate platform specific flags */ - if( (streamFlags & paPlatformSpecificFlags) != 0 ) + if( (streamFlags & paPlatformSpecificFlags) != 0 ){ + PA_DEBUG(("OpenStream invalid flags!!\n")); return paInvalidFlag; /* unexpected platform specific flag */ + } stream = (PaAsioStream*)PaUtil_AllocateMemory( sizeof(PaAsioStream) ); if( !stream ) { result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR5\n")); goto error; } @@ -1643,6 +1769,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, { result = paUnanticipatedHostError; PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + PA_DEBUG(("OpenStream ERROR6\n")); goto error; } completedBuffersPlayedEventInited = 1; @@ -1672,6 +1799,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( !stream->asioBufferInfos ) { result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR7\n")); goto error; } @@ -1681,7 +1809,15 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, ASIOBufferInfo *info = &stream->asioBufferInfos[i]; info->isInput = ASIOTrue; - info->channelNum = i; + + if( inputChannelSelectors ){ + // inputChannelSelectors values have already been validated in + // ValidateAsioSpecificStreamInfo() above + info->channelNum = inputChannelSelectors[i]; + }else{ + info->channelNum = i; + } + info->buffers[0] = info->buffers[1] = 0; } @@ -1689,7 +1825,15 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, ASIOBufferInfo *info = &stream->asioBufferInfos[inputChannelCount+i]; info->isInput = ASIOFalse; - info->channelNum = i; + + if( outputChannelSelectors ){ + // outputChannelSelectors values have already been validated in + // ValidateAsioSpecificStreamInfo() above + info->channelNum = outputChannelSelectors[i]; + }else{ + info->channelNum = i; + } + info->buffers[0] = info->buffers[1] = 0; } @@ -1699,6 +1843,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, ? suggestedInputLatencyFrames : suggestedOutputLatencyFrames), driverInfo ); + + PA_DEBUG(("PaAsioOpenStream: framesPerHostBuffer :%d\n", framesPerHostBuffer)); + asioError = ASIOCreateBuffers( stream->asioBufferInfos, inputChannelCount+outputChannelCount, framesPerHostBuffer, &asioCallbacks_ ); @@ -1706,6 +1853,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( asioError != ASE_OK && framesPerHostBuffer != (unsigned long)driverInfo->bufferPreferredSize ) { + PA_DEBUG(("ERROR: ASIOCreateBuffers: %s\n", PaAsio_GetAsioErrorText(asioError) )); /* Some buggy drivers (like the Hoontech DSP24) give incorrect [min, preferred, max] values They should work with the preferred size @@ -1715,6 +1863,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, framesPerHostBuffer = driverInfo->bufferPreferredSize; + PA_DEBUG(("PaAsioOpenStream: CORRECTED framesPerHostBuffer :%d\n", framesPerHostBuffer)); + ASIOError asioError2 = ASIOCreateBuffers( stream->asioBufferInfos, inputChannelCount+outputChannelCount, framesPerHostBuffer, &asioCallbacks_ ); @@ -1726,6 +1876,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, { result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + PA_DEBUG(("OpenStream ERROR9\n")); goto error; } @@ -1736,6 +1887,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( !stream->asioChannelInfos ) { result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR10\n")); goto error; } @@ -1748,6 +1900,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, { result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + PA_DEBUG(("OpenStream ERROR11\n")); goto error; } } @@ -1757,6 +1910,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( !stream->bufferPtrs ) { result = paInsufficientMemory; + PA_DEBUG(("OpenStream ERROR12\n")); goto error; } @@ -1799,6 +1953,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* FIXME: assume all channels use the same type for now */ ASIOSampleType inputType = stream->asioChannelInfos[0].type; + PA_DEBUG(("ASIO Input type:%d",inputType)); + AsioSampleTypeLOG(inputType); hostInputSampleFormat = AsioSampleTypeToPaNativeSampleFormat( inputType ); SelectAsioToPaConverter( inputType, &stream->inputBufferConverter, &stream->inputShift ); @@ -1814,6 +1970,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* FIXME: assume all channels use the same type for now */ ASIOSampleType outputType = stream->asioChannelInfos[inputChannelCount].type; + PA_DEBUG(("ASIO Output type:%d",outputType)); + AsioSampleTypeLOG(outputType); hostOutputSampleFormat = AsioSampleTypeToPaNativeSampleFormat( outputType ); SelectPaToAsioConverter( outputType, &stream->outputBufferConverter, &stream->outputShift ); @@ -1830,8 +1988,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, sampleRate, streamFlags, framesPerBuffer, framesPerHostBuffer, paUtilFixedHostBufferSize, streamCallback, userData ); - if( result != paNoError ) + if( result != paNoError ){ + PA_DEBUG(("OpenStream ERROR13\n")); goto error; + } ASIOGetLatencies( &stream->inputLatency, &stream->outputLatency ); @@ -1845,13 +2005,20 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->streamRepresentation.streamInfo.sampleRate = sampleRate; // the code below prints the ASIO latency which doesn't include the - // buffer processor latency. - PA_DEBUG(("PaAsio : ASIO InputLatency = %ld latency = %ld msec \n", + // buffer processor latency. it reports the added latency separately + PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", stream->inputLatency, - (long)((stream->inputLatency*1000)/ sampleRate))); - PA_DEBUG(("PaAsio : ASIO OuputLatency = %ld latency = %ld msec \n", + (long)((stream->inputLatency*1000)/ sampleRate), + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor), + (long)((PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)*1000)/ sampleRate) + )); + + PA_DEBUG(("PaAsio : ASIO OuputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", stream->outputLatency, - (long)((stream->outputLatency*1000)/ sampleRate))); + (long)((stream->outputLatency*1000)/ sampleRate), + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor), + (long)((PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)*1000)/ sampleRate) + )); stream->asioHostApi = asioHostApi; stream->framesPerHostCallback = framesPerHostBuffer; @@ -2129,9 +2296,18 @@ previousIndex = index; // asio systemTime is supposed to be measured according to the same // clock as timeGetTime paTimeInfo.currentTime = (ASIO64toDouble( timeInfo->timeInfo.systemTime ) * .000000001); + + /* patch from Paul Boege */ + paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime - + ((double)theAsioStream->inputLatency/theAsioStream->streamRepresentation.streamInfo.sampleRate); + + paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime + + ((double)theAsioStream->outputLatency/theAsioStream->streamRepresentation.streamInfo.sampleRate); + + /* old version is buggy because the buffer processor also adds in its latency to the time parameters paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime - theAsioStream->streamRepresentation.streamInfo.inputLatency; paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime + theAsioStream->streamRepresentation.streamInfo.outputLatency; - + */ #if 1 // detect underflows by checking inter-callback time > 2 buffer period static double previousTime = -1; @@ -2218,7 +2394,7 @@ previousTime = paTimeInfo.currentTime; /* Finish playback once currently queued audio has completed. */ theAsioStream->stopProcessing = true; - if( PaUtil_IsBufferProcessorOuputEmpty( &theAsioStream->bufferProcessor ) ) + if( PaUtil_IsBufferProcessorOutputEmpty( &theAsioStream->bufferProcessor ) ) { theAsioStream->zeroOutput = true; theAsioStream->stopPlayoutCount = 0; @@ -2245,6 +2421,7 @@ static void sampleRateChanged(ASIOSampleRate sRate) // You might have to update time/sample related conversion routines, etc. (void) sRate; /* unused parameter */ + PA_DEBUG( ("sampleRateChanged : %d \n", sRate)); } static long asioMessages(long selector, long value, void* message, double* opt) @@ -2256,6 +2433,8 @@ static long asioMessages(long selector, long value, void* message, double* opt) (void) message; /* unused parameters */ (void) opt; + PA_DEBUG( ("asioMessages : %d , %d \n", selector, value)); + switch(selector) { case kAsioSelectorSupported: @@ -2654,3 +2833,69 @@ error: return result; } + +PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + PaAsioDeviceInfo *asioDeviceInfo; + + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + goto error; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + if( result != paNoError ) + goto error; + + asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + if( channelIndex < 0 || channelIndex >= asioDeviceInfo->commonDeviceInfo.maxInputChannels ){ + result = paInvalidChannelCount; + goto error; + } + + *channelName = asioDeviceInfo->asioChannelInfos[channelIndex].name; + + return paNoError; + +error: + return result; +} + + +PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation *hostApi; + PaDeviceIndex hostApiDevice; + PaAsioDeviceInfo *asioDeviceInfo; + + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + goto error; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, device, hostApi ); + if( result != paNoError ) + goto error; + + asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; + + if( channelIndex < 0 || channelIndex >= asioDeviceInfo->commonDeviceInfo.maxOutputChannels ){ + result = paInvalidChannelCount; + goto error; + } + + *channelName = asioDeviceInfo->asioChannelInfos[ + asioDeviceInfo->commonDeviceInfo.maxInputChannels + channelIndex].name; + + return paNoError; + +error: + return result; +} diff --git a/pd/portaudio/pa_asio/pa_asio.h b/pd/portaudio/pa_asio/pa_asio.h index aed6f07b..230fb2d8 100644 --- a/pd/portaudio/pa_asio/pa_asio.h +++ b/pd/portaudio/pa_asio/pa_asio.h @@ -1,7 +1,7 @@ #ifndef PA_ASIO_H #define PA_ASIO_H /* - * $Id: pa_asio.h,v 1.1.2.5 2003/09/20 21:06:44 rossbencina Exp $ + * $Id: pa_asio.h,v 1.1.2.7 2005/01/01 19:35:33 rossbencina Exp $ * PortAudio Portable Real-Time Audio Library * ASIO specific extensions * @@ -72,6 +72,49 @@ PaError PaAsio_GetAvailableLatencyValues( PaDeviceIndex device, PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ); + + +/** Retrieve a pointer to a string containing the name of the specified + input channel. The string is valid until Pa_Terminate is called. + + The string will be no longer than 32 characters including the null terminator. +*/ +PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ); + + +/** Retrieve a pointer to a string containing the name of the specified + input channel. The string is valid until Pa_Terminate is called. + + The string will be no longer than 32 characters including the null terminator. +*/ +PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, + const char** channelName ); + + +#define paAsioUseChannelSelectors (0x01) + +typedef struct PaAsioStreamInfo{ + unsigned long size; /**< sizeof(PaAsioStreamInfo) */ + PaHostApiTypeId hostApiType; /**< paASIO */ + unsigned long version; /**< 1 */ + + unsigned long flags; + + /* Support for opening only specific channels of an ASIO device. + If the paAsioUseChannelSelectors flag is set, channelSelectors is a + pointer to an array of integers specifying the device channels to use. + When used, the length of the channelSelectors array must match the + corresponding channelCount parameter to Pa_OpenStream() otherwise a + crash may result. + The values in the selectors array must specify channels within the + range of supported channels for the device or paInvalidChannelCount will + result. + */ + int *channelSelectors; +}PaAsioStreamInfo; + + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/pd/portaudio/pa_common/pa_allocation.c b/pd/portaudio/pa_common/pa_allocation.c index 8bc7c72f..035b4d0b 100644 --- a/pd/portaudio/pa_common/pa_allocation.c +++ b/pd/portaudio/pa_common/pa_allocation.c @@ -1,5 +1,5 @@ /* - * $Id: pa_allocation.c,v 1.1.2.5 2003/09/20 21:04:44 rossbencina Exp $ + * $Id: pa_allocation.c,v 1.1.2.6 2004/12/20 12:07:51 rossbencina Exp $ * Portable Audio I/O Library allocation group implementation * memory allocation group for tracking allocation groups * @@ -184,12 +184,22 @@ void PaUtil_GroupFreeMemory( PaUtilAllocationGroup* group, void *buffer ) { if( current->buffer == buffer ) { - previous->next = current->next; + if( previous ) + { + previous->next = current->next; + } + else + { + group->allocations = current->next; + } current->buffer = 0; current->next = group->spareLinks; group->spareLinks = current; + + break; } + previous = current; current = current->next; } diff --git a/pd/portaudio/pa_common/pa_converters.c b/pd/portaudio/pa_common/pa_converters.c index 28ea106f..4ee73c9a 100644 --- a/pd/portaudio/pa_common/pa_converters.c +++ b/pd/portaudio/pa_common/pa_converters.c @@ -1,5 +1,5 @@ /* - * $Id: pa_converters.c,v 1.1.2.25 2004/04/15 10:05:06 rossbencina Exp $ + * $Id: pa_converters.c,v 1.1.2.26 2004/12/11 16:32:38 aknudsen Exp $ * Portable Audio I/O Library sample conversion mechanism * * Based on the Open Source API proposed by Ross Bencina @@ -52,6 +52,7 @@ #include "pa_converters.h" #include "pa_dither.h" #include "pa_endianness.h" +#include "pa_types.h" PaSampleFormat PaUtil_SelectClosestAvailableFormat( @@ -1659,8 +1660,8 @@ static void Copy_16_To_16( void *sourceBuffer, signed int sourceStride, unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) { - unsigned short *src = (unsigned short*)sourceBuffer; - unsigned short *dest = (unsigned short*)destinationBuffer; + PaUint16 *src = (PaUint16 *)sourceBuffer; + PaUint16 *dest = (PaUint16 *)destinationBuffer; (void) ditherGenerator; /* unused parameter */ @@ -1703,8 +1704,8 @@ static void Copy_32_To_32( void *sourceBuffer, signed int sourceStride, unsigned int count, struct PaUtilTriangularDitherGenerator *ditherGenerator ) { - unsigned long *dest = (unsigned long*)destinationBuffer; - unsigned long *src = (unsigned long*)sourceBuffer; + PaUint32 *dest = (PaUint32 *)destinationBuffer; + PaUint32 *src = (PaUint32 *)sourceBuffer; (void) ditherGenerator; /* unused parameter */ @@ -1868,7 +1869,7 @@ static void Zero8( void *destinationBuffer, signed int destinationStride, static void Zero16( void *destinationBuffer, signed int destinationStride, unsigned int count ) { - unsigned short *dest = (unsigned short*)destinationBuffer; + PaUint16 *dest = (PaUint16 *)destinationBuffer; while( count-- ) { @@ -1900,7 +1901,7 @@ static void Zero24( void *destinationBuffer, signed int destinationStride, static void Zero32( void *destinationBuffer, signed int destinationStride, unsigned int count ) { - unsigned long *dest = (unsigned long*)destinationBuffer; + PaUint32 *dest = (PaUint32 *)destinationBuffer; while( count-- ) { diff --git a/pd/portaudio/pa_common/pa_front.c b/pd/portaudio/pa_common/pa_front.c index 655c3bd0..d61c3008 100644 --- a/pd/portaudio/pa_common/pa_front.c +++ b/pd/portaudio/pa_common/pa_front.c @@ -1,5 +1,5 @@ /* - * $Id: pa_front.c,v 1.1.2.50 2004/02/13 07:50:20 rossbencina Exp $ + * $Id: pa_front.c,v 1.1.2.51 2005/02/05 15:52:12 rossbencina Exp $ * Portable Audio I/O Library Multi-Host API front end * Validate function parameters and manage multiple host APIs. * @@ -98,6 +98,7 @@ enquire about status on the PortAudio mailing list first. #include "portaudio.h" #include "pa_util.h" #include "pa_endianness.h" +#include "pa_types.h" #include "pa_hostapi.h" #include "pa_stream.h" @@ -374,6 +375,7 @@ PaError Pa_Initialize( void ) } else { + PA_VALIDATE_TYPE_SIZES; PA_VALIDATE_ENDIANNESS; PaUtil_InitializeClock(); @@ -787,7 +789,7 @@ PaDeviceIndex Pa_GetDefaultOutputDevice( void ) { PaHostApiIndex hostApi; PaDeviceIndex result; - + #ifdef PA_LOG_API_CALLS PaUtil_DebugPrint("Pa_GetDefaultOutputDevice called.\n" ); #endif diff --git a/pd/portaudio/pa_common/pa_process.c b/pd/portaudio/pa_common/pa_process.c index acbd9536..cf711d41 100644 --- a/pd/portaudio/pa_common/pa_process.c +++ b/pd/portaudio/pa_common/pa_process.c @@ -1,5 +1,5 @@ /* - * $Id: pa_process.c,v 1.1.2.45 2004/05/11 13:40:42 rossbencina Exp $ + * $Id: pa_process.c,v 1.1.2.48 2004/12/13 09:48:43 rossbencina Exp $ * Portable Audio I/O Library * streamCallback <-> host buffer processing adapter * @@ -145,9 +145,9 @@ PaError PaUtil_InitializeBufferProcessor( PaUtilBufferProcessor* bp, bp->hostBufferSizeMode = hostBufferSizeMode; - bp->hostInputChannels[0] = 0; - bp->hostOutputChannels[0] = 0; - + bp->hostInputChannels[0] = bp->hostInputChannels[1] = 0; + bp->hostOutputChannels[0] = bp->hostOutputChannels[1] = 0; + if( framesPerUserBuffer == 0 ) /* streamCallback will accept any buffer size */ { bp->useNonAdaptingProcess = 1; @@ -818,48 +818,42 @@ static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp, bp->timeInfo->outputBufferDacTime += frameCount * bp->samplePeriod; /* convert output data (user -> host) */ - if( bp->outputChannelCount != 0 ) + + if( bp->outputChannelCount != 0 && bp->hostOutputChannels[0][0].data ) { - if( !bp->hostOutputChannels[0][0].data ) + /* + could use more elaborate logic here and sometimes process + buffers in-place. + */ + + srcBytePtr = (unsigned char *)bp->tempOutputBuffer; + + if( bp->userOutputIsInterleaved ) { - /* do nothing, this are no host output buffers */ + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; } - else + else /* user output is not interleaved */ { - /* - could use more elaborate logic here and sometimes process - buffers in-place. - */ - - srcBytePtr = (unsigned char *)bp->tempOutputBuffer; - - if( bp->userOutputIsInterleaved ) - { - srcSampleStrideSamples = bp->outputChannelCount; - srcChannelStrideBytes = bp->bytesPerUserOutputSample; - } - else /* user output is not interleaved */ - { - srcSampleStrideSamples = 1; - srcChannelStrideBytes = frameCount * bp->bytesPerUserOutputSample; - } - - for( i=0; i<bp->outputChannelCount; ++i ) - { - bp->outputConverter( hostOutputChannels[i].data, - hostOutputChannels[i].stride, - srcBytePtr, srcSampleStrideSamples, - frameCount, &bp->ditherGenerator ); - - srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ - - /* advance dest ptr for next iteration */ - hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + - frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; - } + srcSampleStrideSamples = 1; + srcChannelStrideBytes = frameCount * bp->bytesPerUserOutputSample; } - } + for( i=0; i<bp->outputChannelCount; ++i ) + { + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + frameCount, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + } + framesProcessed += frameCount; framesToGo -= frameCount; @@ -870,12 +864,12 @@ static unsigned long NonAdaptingProcess( PaUtilBufferProcessor *bp, if( framesToGo > 0 ) { - /* zero any remaining frames. There will only be remaining frames + /* zero any remaining frames output. There will only be remaining frames if the callback has returned paComplete or paAbort */ frameCount = framesToGo; - if( bp->hostOutputChannels[0][0].data ) + if( bp->outputChannelCount != 0 && bp->hostOutputChannels[0][0].data ) { for( i=0; i<bp->outputChannelCount; ++i ) { @@ -1133,6 +1127,79 @@ static unsigned long AdaptingOutputOnlyProcess( PaUtilBufferProcessor *bp, return framesProcessed; } +/* CopyTempOutputBuffersToHostOutputBuffers is called from AdaptingProcess to copy frames from + tempOutputBuffer to hostOutputChannels. This includes data conversion + and interleaving. +*/ +static void CopyTempOutputBuffersToHostOutputBuffers( PaUtilBufferProcessor *bp) +{ + unsigned long maxFramesToCopy; + PaUtilChannelDescriptor *hostOutputChannels; + unsigned int frameCount; + unsigned char *srcBytePtr; + unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ + unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned int i; + + /* copy frames from user to host output buffers */ + while( bp->framesInTempOutputBuffer > 0 && + ((bp->hostOutputFrameCount[0] + bp->hostOutputFrameCount[1]) > 0) ) + { + maxFramesToCopy = bp->framesInTempOutputBuffer; + + /* select the output buffer set (1st or 2nd) */ + if( bp->hostOutputFrameCount[0] > 0 ) + { + hostOutputChannels = bp->hostOutputChannels[0]; + frameCount = PA_MIN_( bp->hostOutputFrameCount[0], maxFramesToCopy ); + } + else + { + hostOutputChannels = bp->hostOutputChannels[1]; + frameCount = PA_MIN_( bp->hostOutputFrameCount[1], maxFramesToCopy ); + } + + if( bp->userOutputIsInterleaved ) + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * bp->outputChannelCount * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = bp->outputChannelCount; + srcChannelStrideBytes = bp->bytesPerUserOutputSample; + } + else /* user output is not interleaved */ + { + srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + + bp->bytesPerUserOutputSample * + (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); + + srcSampleStrideSamples = 1; + srcChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; + } + + for( i=0; i<bp->outputChannelCount; ++i ) + { + bp->outputConverter( hostOutputChannels[i].data, + hostOutputChannels[i].stride, + srcBytePtr, srcSampleStrideSamples, + frameCount, &bp->ditherGenerator ); + + srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ + + /* advance dest ptr for next iteration */ + hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + + frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; + } + + if( bp->hostOutputFrameCount[0] > 0 ) + bp->hostOutputFrameCount[0] -= frameCount; + else + bp->hostOutputFrameCount[1] -= frameCount; + + bp->framesInTempOutputBuffer -= frameCount; + } +} /* AdaptingProcess is a full duplex adapting buffer processor. It converts @@ -1154,9 +1221,7 @@ static unsigned long AdaptingProcess( PaUtilBufferProcessor *bp, unsigned long maxFramesToCopy; PaUtilChannelDescriptor *hostInputChannels, *hostOutputChannels; unsigned int frameCount; - unsigned char *srcBytePtr, *destBytePtr; - unsigned int srcSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ - unsigned int srcChannelStrideBytes; /* stride from one channel to the next, in bytes */ + unsigned char *destBytePtr; unsigned int destSampleStrideSamples; /* stride from one sample to the next within a channel, in samples */ unsigned int destChannelStrideBytes; /* stride from one channel to the next, in bytes */ unsigned int i, j; @@ -1169,66 +1234,11 @@ static unsigned long AdaptingProcess( PaUtilBufferProcessor *bp, else endProcessingMinFrameCount = (bp->framesPerUserBuffer - 1); - while( framesAvailable > endProcessingMinFrameCount ) - { - /* copy frames from user to host output buffers */ - while( bp->framesInTempOutputBuffer > 0 && - ((bp->hostOutputFrameCount[0] + bp->hostOutputFrameCount[1]) > 0) ) - { - maxFramesToCopy = bp->framesInTempOutputBuffer; - - /* select the output buffer set (1st or 2nd) */ - if( bp->hostOutputFrameCount[0] > 0 ) - { - hostOutputChannels = bp->hostOutputChannels[0]; - frameCount = PA_MIN_( bp->hostOutputFrameCount[0], maxFramesToCopy ); - } - else - { - hostOutputChannels = bp->hostOutputChannels[1]; - frameCount = PA_MIN_( bp->hostOutputFrameCount[1], maxFramesToCopy ); - } - - if( bp->userOutputIsInterleaved ) - { - srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + - bp->bytesPerUserOutputSample * bp->outputChannelCount * - (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); - - srcSampleStrideSamples = bp->outputChannelCount; - srcChannelStrideBytes = bp->bytesPerUserOutputSample; - } - else /* user output is not interleaved */ - { - srcBytePtr = ((unsigned char*)bp->tempOutputBuffer) + - bp->bytesPerUserOutputSample * - (bp->framesPerUserBuffer - bp->framesInTempOutputBuffer); - - srcSampleStrideSamples = 1; - srcChannelStrideBytes = bp->framesPerUserBuffer * bp->bytesPerUserOutputSample; - } + /* Fill host output with remaining frames in user output (tempOutputBuffer) */ + CopyTempOutputBuffersToHostOutputBuffers( bp ); - for( i=0; i<bp->outputChannelCount; ++i ) - { - bp->outputConverter( hostOutputChannels[i].data, - hostOutputChannels[i].stride, - srcBytePtr, srcSampleStrideSamples, - frameCount, &bp->ditherGenerator ); - - srcBytePtr += srcChannelStrideBytes; /* skip to next source channel */ - - /* advance dest ptr for next iteration */ - hostOutputChannels[i].data = ((unsigned char*)hostOutputChannels[i].data) + - frameCount * hostOutputChannels[i].stride * bp->bytesPerHostOutputSample; - } - - if( bp->hostOutputFrameCount[0] > 0 ) - bp->hostOutputFrameCount[0] -= frameCount; - else - bp->hostOutputFrameCount[1] -= frameCount; - - bp->framesInTempOutputBuffer -= frameCount; - } + while( framesAvailable > endProcessingMinFrameCount ) + { if( bp->framesInTempOutputBuffer == 0 && *streamCallbackResult != paContinue ) { @@ -1384,6 +1394,12 @@ static unsigned long AdaptingProcess( PaUtilBufferProcessor *bp, bp->framesInTempInputBuffer = 0; } } + + /* copy frames from user (tempOutputBuffer) to host output buffers (hostOutputChannels) + Means to process the user output provided by the callback. Has to be called after + each callback. */ + CopyTempOutputBuffersToHostOutputBuffers( bp ); + } return framesProcessed; @@ -1562,7 +1578,7 @@ unsigned long PaUtil_EndBufferProcessing( PaUtilBufferProcessor* bp, int *stream } -int PaUtil_IsBufferProcessorOuputEmpty( PaUtilBufferProcessor* bp ) +int PaUtil_IsBufferProcessorOutputEmpty( PaUtilBufferProcessor* bp ) { return (bp->framesInTempOutputBuffer) ? 0 : 1; } diff --git a/pd/portaudio/pa_common/pa_process.h b/pd/portaudio/pa_common/pa_process.h index a54eccd7..c52e9ea0 100644 --- a/pd/portaudio/pa_common/pa_process.h +++ b/pd/portaudio/pa_common/pa_process.h @@ -1,7 +1,7 @@ #ifndef PA_PROCESS_H #define PA_PROCESS_H /* - * $Id: pa_process.h,v 1.1.2.27 2004/05/28 21:13:10 aknudsen Exp $ + * $Id: pa_process.h,v 1.1.2.30 2004/12/13 09:48:44 rossbencina Exp $ * Portable Audio I/O Library callback buffer processing adapters * * Based on the Open Source API proposed by Ross Bencina @@ -279,9 +279,17 @@ typedef struct { PaStreamCallbackFlags callbackStatusFlags; unsigned long hostInputFrameCount[2]; - PaUtilChannelDescriptor *hostInputChannels[2]; + PaUtilChannelDescriptor *hostInputChannels[2]; /**< pointers to arrays of channel descriptors. + pointers are NULL for half-duplex output processing. + hostInputChannels[i].data is NULL when the caller + calls PaUtil_SetNoInput() + */ unsigned long hostOutputFrameCount[2]; - PaUtilChannelDescriptor *hostOutputChannels[2]; + PaUtilChannelDescriptor *hostOutputChannels[2]; /**< pointers to arrays of channel descriptors. + pointers are NULL for half-duplex input processing. + hostOutputChannels[i].data is NULL when the caller + calls PaUtil_SetNoOutput() + */ PaUtilTriangularDitherGenerator ditherGenerator; @@ -540,7 +548,7 @@ void PaUtil_SetOutputChannel( PaUtilBufferProcessor* bufferProcessor, unsigned int channel, void *data, unsigned int stride ); -/** Provide the buffer processor with a pointer to an number of interleaved +/** Provide the buffer processor with a pointer to a number of interleaved host output channels. @param bufferProcessor The buffer processor. @@ -623,7 +631,7 @@ void PaUtil_BeginBufferProcessing( PaUtilBufferProcessor* bufferProcessor, paAbort}. If paComplete is passed, the stream callback will not be called but any audio that was generated by previous stream callbacks will be copied to the output buffer(s). You can check whether the buffer processor's internal - buffer is empty by calling PaUtil_IsBufferProcessorOuputEmpty. + buffer is empty by calling PaUtil_IsBufferProcessorOutputEmpty. If the stream callback is called its result is stored in *callbackResult. If the stream callback returns paComplete or paAbort, all output buffers will be @@ -650,7 +658,7 @@ unsigned long PaUtil_EndBufferProcessing( PaUtilBufferProcessor* bufferProcessor buffer and zero (0) when there internal buffer contains no callback generated data. */ -int PaUtil_IsBufferProcessorOuputEmpty( PaUtilBufferProcessor* bufferProcessor ); +int PaUtil_IsBufferProcessorOutputEmpty( PaUtilBufferProcessor* bufferProcessor ); /*@}*/ diff --git a/pd/portaudio/pa_common/portaudio.h b/pd/portaudio/pa_common/portaudio.h index e01c7aa8..341d92c9 100644 --- a/pd/portaudio/pa_common/portaudio.h +++ b/pd/portaudio/pa_common/portaudio.h @@ -1,7 +1,8 @@ + #ifndef PORTAUDIO_H #define PORTAUDIO_H /* - * $Id: portaudio.h,v 1.5.2.46 2004/02/14 10:19:01 rossbencina Exp $ + * $Id: portaudio.h,v 1.5.2.50 2004/12/13 11:50:40 rossbencina Exp $ * PortAudio Portable Real-Time Audio Library * PortAudio API Header File * Latest version available at: http://www.portaudio.com/ @@ -222,7 +223,9 @@ typedef enum PaHostApiTypeId paOSS=7, paALSA=8, paAL=9, - paBeOS=10 + paBeOS=10, + paWDMKS=11, + paJACK=12 } PaHostApiTypeId; @@ -360,7 +363,7 @@ PaDeviceIndex Pa_GetDeviceCount( void ); /** Retrieve the index of the default input device. The result can be used in the inputDevice parameter to Pa_OpenStream(). - @return The default input device index for the defualt host API, or paNoDevice + @return The default input device index for the default host API, or paNoDevice if no default input device is available or an error was encountered. */ PaDeviceIndex Pa_GetDefaultInputDevice( void ); @@ -486,11 +489,6 @@ typedef struct PaStreamParameters /** The sample format of the buffer provided to the stream callback, a_ReadStream() or Pa_WriteStream(). It may be any of the formats described by the PaSampleFormat enumeration. - FIXME: wrt below, what are we guaranteeing these days, if anything? - PortAudio guarantees support for - the device's native formats (nativeSampleFormats in the device info record) - and additionally 16 and 32 bit integer and 32 bit floating point formats. - Support for other formats is implementation defined. */ PaSampleFormat sampleFormat; @@ -717,7 +715,7 @@ typedef enum PaStreamCallbackResult @return The stream callback should return one of the values in the - PaStreamCallbackResult enumeration. To ensure that the callback is continues + PaStreamCallbackResult enumeration. To ensure that the callback continues to be called, it should return paContinue (0). Either paComplete or paAbort can be returned to finish stream processing, after either of these values is returned the callback will not be called again. If paAbort is returned the @@ -824,11 +822,8 @@ PaError Pa_OpenStream( PaStream** stream, @param sampleFormat The sample format of both the input and output buffers provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. - sampleFormat may be any of the formats described by the PaSampleFormat enumeration - (see above). - FIXME: the following may need to be rewritten - PortAudio guarantees support for - the device's native formats (nativeSampleFormats in the device info record) - and additionally 16 and 32 bit integer and 32 bit float + sampleFormat may be any of the formats described by the PaSampleFormat + enumeration. @param sampleRate Same as Pa_OpenStream parameter of the same name. @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. diff --git a/pd/portaudio/pa_jack/pa_jack.c b/pd/portaudio/pa_jack/pa_jack.c index c5b075c2..d50ad210 100644 --- a/pd/portaudio/pa_jack/pa_jack.c +++ b/pd/portaudio/pa_jack/pa_jack.c @@ -1,9 +1,11 @@ /* - * $Id: pa_jack.c,v 1.1.2.8 2003/09/20 22:59:29 rossbencina Exp $ + * $Id: pa_jack.c,v 1.1.2.19 2004/12/23 18:30:09 aknudsen Exp $ * PortAudio Portable Real-Time Audio Library * Latest Version at: http://www.portaudio.com * JACK Implementation by Joshua Haberman * + * Copyright (c) 2004 Stefan Westerfeld <stefan@space.twc.de> + * Copyright (c) 2004 Arve Knudsen <aknuds-1@broadpark.no> * Copyright (c) 2002 Joshua Haberman <joshua@haberman.com> * * Based on the Open Source API proposed by Ross Bencina @@ -37,6 +39,13 @@ #include <regex.h> #include <stdlib.h> #include <stdio.h> +#include <assert.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> /* EBUSY */ +#include <signal.h> /* sig_atomic_t */ +#include <math.h> +#include <semaphore.h> #include <jack/types.h> #include <jack/jack.h> @@ -47,9 +56,50 @@ #include "pa_process.h" #include "pa_allocation.h" #include "pa_cpuload.h" - -PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, - PaHostApiIndex hostApiIndex ); +#include "../pablio/ringbuffer.c" + +static int aErr_; +static PaError paErr_; /* For use with ENSURE_PA */ +static pthread_t mainThread_; +static char *jackErr_ = NULL; + +#define STRINGIZE_HELPER(expr) #expr +#define STRINGIZE(expr) STRINGIZE_HELPER(expr) + +/* Check PaError */ +#define ENSURE_PA(expr) \ + do { \ + if( (paErr_ = (expr)) < paNoError ) \ + { \ + if( (paErr_) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + assert( jackErr_ ); \ + PaUtil_SetLastHostErrorInfo( paJACK, -1, jackErr_ ); \ + } \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = paErr_; \ + goto error; \ + } \ + } while( 0 ) + +#define UNLESS(expr, code) \ + do { \ + if( (expr) == 0 ) \ + { \ + if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + assert( jackErr_ ); \ + PaUtil_SetLastHostErrorInfo( paJACK, -1, jackErr_ ); \ + } \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while( 0 ) + +#define ASSERT_CALL(expr, success) \ + aErr_ = (expr); \ + assert( aErr_ == success ); /* * Functions that directly map to the PortAudio stream interface @@ -75,35 +125,96 @@ static PaError StopStream( PaStream *stream ); static PaError AbortStream( PaStream *stream ); static PaError IsStreamStopped( PaStream *s ); static PaError IsStreamActive( PaStream *stream ); -static PaTime GetStreamInputLatency( PaStream *stream ); -static PaTime GetStreamOutputLatency( PaStream *stream ); +/*static PaTime GetStreamInputLatency( PaStream *stream );*/ +/*static PaTime GetStreamOutputLatency( PaStream *stream );*/ static PaTime GetStreamTime( PaStream *stream ); static double GetStreamCpuLoad( PaStream* stream ); -/* -static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); -static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); -static signed long GetStreamReadAvailable( PaStream* stream ); -static signed long GetStreamWriteAvailable( PaStream* stream ); -*/ /* * Data specific to this API */ +struct PaJackStream; + typedef struct { PaUtilHostApiRepresentation commonHostApiRep; PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; PaUtilAllocationGroup *deviceInfoMemory; jack_client_t *jack_client; + int jack_buffer_size; PaHostApiIndex hostApiIndex; + + pthread_mutex_t mtx; + pthread_cond_t cond; + unsigned long inputBase, outputBase; + + /* For dealing with the process thread */ + volatile int xrun; /* Received xrun notification from JACK? */ + struct PaJackStream * volatile toAdd, * volatile toRemove; + struct PaJackStream *processQueue; + volatile sig_atomic_t jackIsDown; } PaJackHostApiRepresentation; -#define MAX_CLIENTS 100 +/* PaJackStream - a stream data structure specifically for this implementation */ + +typedef struct PaJackStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilBufferProcessor bufferProcessor; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaJackHostApiRepresentation *hostApi; + + /* our input and output ports */ + jack_port_t **local_input_ports; + jack_port_t **local_output_ports; + + /* the input and output ports of the client we are connecting to */ + jack_port_t **remote_input_ports; + jack_port_t **remote_output_ports; + + int num_incoming_connections; + int num_outgoing_connections; + + jack_client_t *jack_client; + + /* The stream is running if it's still producing samples. + * The stream is active if samples it produced are still being heard. + */ + volatile sig_atomic_t is_running; + volatile sig_atomic_t is_active; + /* Used to signal processing thread that stream should start or stop, respectively */ + volatile sig_atomic_t doStart, doStop, doAbort; + + jack_nframes_t t0; + + PaUtilAllocationGroup *stream_memory; + + /* These are useful in the process callback */ + + int callbackResult; + int isSilenced; + int xrun; + + /* These are useful for the blocking API */ + + int isBlockingStream; + RingBuffer inFIFO; + RingBuffer outFIFO; + volatile sig_atomic_t data_available; + sem_t data_semaphore; + int bytesPerFrame; + int samplesPerFrame; + + struct PaJackStream *next; +} +PaJackStream; + #define TRUE 1 #define FALSE 0 @@ -111,17 +222,211 @@ PaJackHostApiRepresentation; * Functions specific to this API */ -static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi ); static int JackCallback( jack_nframes_t frames, void *userData ); - /* * * Implementation * */ +/* ---- blocking emulation layer ---- */ + +/* Allocate buffer. */ +static PaError BlockingInitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame ) +{ + long numBytes = numFrames * bytesPerFrame; + char *buffer = (char *) malloc( numBytes ); + if( buffer == NULL ) return paInsufficientMemory; + memset( buffer, 0, numBytes ); + return (PaError) RingBuffer_Init( rbuf, numBytes, buffer ); +} + +/* Free buffer. */ +static PaError BlockingTermFIFO( RingBuffer *rbuf ) +{ + if( rbuf->buffer ) free( rbuf->buffer ); + rbuf->buffer = NULL; + return paNoError; +} + +static int +BlockingCallback( const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + struct PaJackStream *stream = (PaJackStream *)userData; + long numBytes = stream->bytesPerFrame * framesPerBuffer; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer != NULL ) + { + RingBuffer_Write( &stream->inFIFO, inputBuffer, numBytes ); + } + if( outputBuffer != NULL ) + { + int numRead = RingBuffer_Read( &stream->outFIFO, outputBuffer, numBytes ); + /* Zero out remainder of buffer if we run out of data. */ + memset( (char *)outputBuffer + numRead, 0, numBytes - numRead ); + } + + if( !stream->data_available ) + { + stream->data_available = 1; + sem_post( &stream->data_semaphore ); + } + return paContinue; +} + +static PaError +BlockingBegin( PaJackStream *stream, int minimum_buffer_size ) +{ + long doRead = 0; + long doWrite = 0; + PaError result = paNoError; + long numFrames; + + doRead = stream->local_input_ports != NULL; + doWrite = stream->local_output_ports != NULL; + /* <FIXME> */ + stream->samplesPerFrame = 2; + stream->bytesPerFrame = sizeof(float) * stream->samplesPerFrame; + /* </FIXME> */ + numFrames = 32; + while (numFrames < minimum_buffer_size) + numFrames *= 2; + + if( doRead ) + { + ENSURE_PA( BlockingInitFIFO( &stream->inFIFO, numFrames, stream->bytesPerFrame ) ); + } + if( doWrite ) + { + long numBytes; + + ENSURE_PA( BlockingInitFIFO( &stream->outFIFO, numFrames, stream->bytesPerFrame ) ); + + /* Make Write FIFO appear full initially. */ + numBytes = RingBuffer_GetWriteAvailable( &stream->outFIFO ); + RingBuffer_AdvanceWriteIndex( &stream->outFIFO, numBytes ); + } + + stream->data_available = 0; + sem_init( &stream->data_semaphore, 0, 0 ); + +error: + return result; +} + +static void +BlockingEnd( PaJackStream *stream ) +{ + BlockingTermFIFO( &stream->inFIFO ); + BlockingTermFIFO( &stream->outFIFO ); + + sem_destroy( &stream->data_semaphore ); +} + +static PaError BlockingReadStream( PaStream* s, void *data, unsigned long numFrames ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream *)s; + + long bytesRead; + char *p = (char *) data; + long numBytes = stream->bytesPerFrame * numFrames; + while( numBytes > 0 ) + { + bytesRead = RingBuffer_Read( &stream->inFIFO, p, numBytes ); + numBytes -= bytesRead; + p += bytesRead; + if( numBytes > 0 ) + { + /* see write for an explanation */ + if( stream->data_available ) + stream->data_available = 0; + else + sem_wait( &stream->data_semaphore ); + } + } + + return result; +} + +static PaError BlockingWriteStream( PaStream* s, const void *data, unsigned long numFrames ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream *)s; + long bytesWritten; + char *p = (char *) data; + long numBytes = stream->bytesPerFrame * numFrames; + while( numBytes > 0 ) + { + bytesWritten = RingBuffer_Write( &stream->outFIFO, p, numBytes ); + numBytes -= bytesWritten; + p += bytesWritten; + if( numBytes > 0 ) + { + /* we use the following algorithm: + * (1) write data + * (2) if some data didn't fit into the ringbuffer, set data_available to 0 + * to indicate to the audio that if space becomes available, we want to know + * (3) retry to write data (because it might be that between (1) and (2) + * new space in the buffer became available) + * (4) if this failed, we are sure that the buffer is really empty and + * we will definitely receive a notification when it becomes available + * thus we can safely sleep + * + * if the algorithm bailed out in step (3) before, it leaks a count of 1 + * on the semaphore; however, it doesn't matter, because if we block in (4), + * we also do it in a loop + */ + if( stream->data_available ) + stream->data_available = 0; + else + sem_wait( &stream->data_semaphore ); + } + } + + return result; +} + +static signed long +BlockingGetStreamReadAvailable( PaStream* s ) +{ + PaJackStream *stream = (PaJackStream *)s; + + int bytesFull = RingBuffer_GetReadAvailable( &stream->inFIFO ); + return bytesFull / stream->bytesPerFrame; +} + +static signed long +BlockingGetStreamWriteAvailable( PaStream* s ) +{ + PaJackStream *stream = (PaJackStream *)s; + + int bytesEmpty = RingBuffer_GetWriteAvailable( &stream->outFIFO ); + return bytesEmpty / stream->bytesPerFrame; +} + +static PaError +BlockingWaitEmpty( PaStream *s ) +{ + PaJackStream *stream = (PaJackStream *)s; + + while( RingBuffer_GetReadAvailable( &stream->outFIFO ) > 0 ) + { + stream->data_available = 0; + sem_wait( &stream->data_semaphore ); + } + return 0; +} + +/* ---- jack driver ---- */ /* BuildDeviceList(): * @@ -139,23 +444,29 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi ) #define MALLOC(size) \ (PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, (size) )) - /* ... MEMVERIFY: make sure we didn't get NULL */ -#define MEMVERIFY(ptr) \ - if( (ptr) == NULL ) return paInsufficientMemory; - /* JACK has no concept of a device. To JACK, there are clients * which have an arbitrary number of ports. To make this * intelligible to PortAudio clients, we will group each JACK client * into a device, and make each port of that client a channel */ + PaError result = paNoError; PaUtilHostApiRepresentation *commonApi = &jackApi->commonHostApiRep; - const char **jack_ports; - char *client_names[MAX_CLIENTS]; - int num_clients = 0; + const char **jack_ports = NULL; + char **client_names = NULL; + char *regex_pattern = alloca( jack_client_name_size() + 3 ); int port_index, client_index, i; - double *globalSampleRate; + double globalSampleRate; regex_t port_regex; + unsigned long numClients = 0, numPorts = 0; + char *tmp_client_name = alloca( jack_client_name_size() ); + + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; + commonApi->info.deviceCount = 0; + + /* Parse the list of ports, using a regex to grab the client names */ + ASSERT_CALL( regcomp( &port_regex, "^[^:]*", REG_EXTENDED ), 0 ); /* since we are rebuilding the list of devices, free all memory * associated with the previous list */ @@ -164,140 +475,216 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi ) /* We can only retrieve the list of clients indirectly, by first * asking for a list of all ports, then parsing the port names * according to the client_name:port_name convention (which is - * enforced by jackd) */ - jack_ports = jack_get_ports( jackApi->jack_client, "", "", 0 ); - - if( jack_ports == NULL ) - return paUnanticipatedHostError; - - /* Parse the list of ports, using a regex to grab the client names */ - regcomp( &port_regex, "^[^:]*", REG_EXTENDED ); + * enforced by jackd) + * A: If jack_get_ports returns NULL, there's nothing for us to do */ + UNLESS( (jack_ports = jack_get_ports( jackApi->jack_client, "", "", 0 )) && jack_ports[0], paNoError ); + /* Find number of ports */ + while( jack_ports[numPorts] ) + ++numPorts; + /* At least there will be one port per client :) */ + UNLESS( client_names = alloca( numPorts * sizeof (char *) ), paInsufficientMemory ); /* Build a list of clients from the list of ports */ - for( port_index = 0; jack_ports[port_index] != NULL; port_index++ ) + for( numClients = 0, port_index = 0; jack_ports[port_index] != NULL; port_index++ ) { - int client_seen; + int client_seen = FALSE; regmatch_t match_info; - char tmp_client_name[100]; + const char *port = jack_ports[port_index]; /* extract the client name from the port name, using a regex * that parses the clientname:portname syntax */ - regexec( &port_regex, jack_ports[port_index], 1, &match_info, 0 ); - memcpy( tmp_client_name, &jack_ports[port_index][match_info.rm_so], + UNLESS( !regexec( &port_regex, port, 1, &match_info, 0 ), paInternalError ); + assert(match_info.rm_eo - match_info.rm_so < jack_client_name_size()); + memcpy( tmp_client_name, port + match_info.rm_so, match_info.rm_eo - match_info.rm_so ); - tmp_client_name[ match_info.rm_eo - match_info.rm_so ] = '\0'; + tmp_client_name[match_info.rm_eo - match_info.rm_so] = '\0'; /* do we know about this port's client yet? */ - client_seen = FALSE; - - for( i = 0; i < num_clients; i++ ) + for( i = 0; i < numClients; i++ ) + { if( strcmp( tmp_client_name, client_names[i] ) == 0 ) client_seen = TRUE; + } + + if (client_seen) + continue; /* A: Nothing to see here, move along */ - if( client_seen == FALSE ) + UNLESS( client_names[numClients] = (char*)MALLOC(strlen(tmp_client_name) + 1), paInsufficientMemory ); + + /* The alsa_pcm client should go in spot 0. If this + * is the alsa_pcm client AND we are NOT about to put + * it in spot 0 put it in spot 0 and move whatever + * was already in spot 0 to the end. */ + if( strcmp( "alsa_pcm", tmp_client_name ) == 0 && numClients > 0 ) { - client_names[num_clients] = (char*)MALLOC(strlen(tmp_client_name) + 1); - MEMVERIFY( client_names[num_clients] ); - - /* The alsa_pcm client should go in spot 0. If this - * is the alsa_pcm client AND we are NOT about to put - * it in spot 0 put it in spot 0 and move whatever - * was already in spot 0 to the end. */ - if( strcmp( "alsa_pcm", tmp_client_name ) == 0 && num_clients > 0 ) - { - /* alsa_pcm goes in spot 0 */ - strcpy( client_names[ num_clients ], client_names[0] ); - strcpy( client_names[0], "alsa_pcm" ); - num_clients++; - } - else - { - /* put the new client at the end of the client list */ - strcpy( client_names[ num_clients ], tmp_client_name ); - num_clients++; - } + /* alsa_pcm goes in spot 0 */ + strcpy( client_names[ numClients ], client_names[0] ); + strcpy( client_names[0], tmp_client_name ); + } + else + { + /* put the new client at the end of the client list */ + strcpy( client_names[ numClients ], tmp_client_name ); } + ++numClients; } - free( jack_ports ); /* Now we have a list of clients, which will become the list of * PortAudio devices. */ - commonApi->info.deviceCount = num_clients; - commonApi->info.defaultInputDevice = 0; - commonApi->info.defaultOutputDevice = 0; - /* there is one global sample rate all clients must conform to */ - globalSampleRate = (double*)MALLOC( sizeof(double) ); - MEMVERIFY( globalSampleRate ); - *globalSampleRate = jack_get_sample_rate( jackApi->jack_client ); + globalSampleRate = jack_get_sample_rate( jackApi->jack_client ); + UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)MALLOC( sizeof(PaDeviceInfo*) * + numClients ), paInsufficientMemory ); - commonApi->deviceInfos = (PaDeviceInfo**)MALLOC( sizeof(PaDeviceInfo*) * - num_clients ); - MEMVERIFY(commonApi->deviceInfos); + assert( commonApi->info.deviceCount == 0 ); /* Create a PaDeviceInfo structure for every client */ - for( client_index = 0; client_index < num_clients; client_index++ ) + for( client_index = 0; client_index < numClients; client_index++ ) { - char regex_pattern[100]; PaDeviceInfo *curDevInfo; + const char **clientPorts = NULL; - curDevInfo = (PaDeviceInfo*)MALLOC( sizeof(PaDeviceInfo) ); - MEMVERIFY( curDevInfo ); - - curDevInfo->name = (char*)MALLOC( strlen(client_names[client_index]) + 1 ); - MEMVERIFY( curDevInfo->name ); - strcpy( (char*)curDevInfo->name, client_names[client_index] ); + UNLESS( curDevInfo = (PaDeviceInfo*)MALLOC( sizeof(PaDeviceInfo) ), paInsufficientMemory ); + UNLESS( curDevInfo->name = (char*)MALLOC( strlen(client_names[client_index]) + 1 ), paInsufficientMemory ); + strcpy( (char *)curDevInfo->name, client_names[client_index] ); curDevInfo->structVersion = 2; curDevInfo->hostApi = jackApi->hostApiIndex; /* JACK is very inflexible: there is one sample rate the whole * system must run at, and all clients must speak IEEE float. */ - curDevInfo->defaultSampleRate = *globalSampleRate; + curDevInfo->defaultSampleRate = globalSampleRate; /* To determine how many input and output channels are available, * we re-query jackd with more specific parameters. */ sprintf( regex_pattern, "%s:.*", client_names[client_index] ); - /* ... what are your output ports (that we could input to)? */ - jack_ports = jack_get_ports( jackApi->jack_client, regex_pattern, + /* ... what are your output ports (that we could input from)? */ + clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern, NULL, JackPortIsOutput); curDevInfo->maxInputChannels = 0; - for( i = 0; jack_ports[i] != NULL ; i++) + curDevInfo->defaultLowInputLatency = 0.; + curDevInfo->defaultHighInputLatency = 0.; + if( clientPorts ) { - /* The number of ports returned is the number of output channels. - * We don't care what they are, we just care how many */ - curDevInfo->maxInputChannels++; + jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] ); + curDevInfo->defaultLowInputLatency = curDevInfo->defaultHighInputLatency = + jack_port_get_latency( p ) / globalSampleRate; + free( p ); + + for( i = 0; clientPorts[i] != NULL; i++) + { + /* The number of ports returned is the number of output channels. + * We don't care what they are, we just care how many */ + curDevInfo->maxInputChannels++; + } + free(clientPorts); } - free(jack_ports); /* ... what are your input ports (that we could output to)? */ - jack_ports = jack_get_ports( jackApi->jack_client, regex_pattern, + clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern, NULL, JackPortIsInput); curDevInfo->maxOutputChannels = 0; - for( i = 0; jack_ports[i] != NULL ; i++) + curDevInfo->defaultLowOutputLatency = 0.; + curDevInfo->defaultHighOutputLatency = 0.; + if( clientPorts ) { - /* The number of ports returned is the number of input channels. - * We don't care what they are, we just care how many */ - curDevInfo->maxOutputChannels++; - } - free(jack_ports); + jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] ); + curDevInfo->defaultLowOutputLatency = curDevInfo->defaultHighOutputLatency = + jack_port_get_latency( p ) / globalSampleRate; + free( p ); - curDevInfo->defaultLowInputLatency = 0.; /* IMPLEMENT ME */ - curDevInfo->defaultLowOutputLatency = 0.; /* IMPLEMENT ME */ - curDevInfo->defaultHighInputLatency = 0.; /* IMPLEMENT ME */ - curDevInfo->defaultHighOutputLatency = 0.; /* IMPLEMENT ME */ + for( i = 0; clientPorts[i] != NULL; i++) + { + /* The number of ports returned is the number of input channels. + * We don't care what they are, we just care how many */ + curDevInfo->maxOutputChannels++; + } + free(clientPorts); + } /* Add this client to the list of devices */ commonApi->deviceInfos[client_index] = curDevInfo; + ++commonApi->info.deviceCount; + if( commonApi->info.defaultInputDevice == paNoDevice && curDevInfo->maxInputChannels > 0 ) + commonApi->info.defaultInputDevice = client_index; + if( commonApi->info.defaultOutputDevice == paNoDevice && curDevInfo->maxOutputChannels > 0 ) + commonApi->info.defaultOutputDevice = client_index; } +error: + regfree( &port_regex ); + free( jack_ports ); + return result; +} #undef MALLOC -#undef MEMVERIFY - return paNoError; + +static void UpdateSampleRate( PaJackStream *stream, double sampleRate ) +{ + /* XXX: Maybe not the cleanest way of going about this? */ + stream->cpuLoadMeasurer.samplingPeriod = stream->bufferProcessor.samplePeriod = 1. / sampleRate; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; +} + +static void JackErrorCallback( const char *msg ) +{ + if( pthread_self() == mainThread_ ) + { + assert( msg ); + free( jackErr_ ); + jackErr_ = malloc( strlen( msg ) ); + sprintf( jackErr_, msg ); + } +} + +static void JackOnShutdown( void *arg ) +{ + PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg; + PaJackStream *stream = jackApi->processQueue; + + PA_DEBUG(( "%s: JACK server is shutting down\n", __FUNCTION__ )); + for( ; stream; stream = stream->next ) + { + stream->is_active = 0; + } + + /* Make sure that the main thread doesn't get stuck waiting on the condition */ + ASSERT_CALL( pthread_mutex_lock( &jackApi->mtx ), 0 ); + jackApi->jackIsDown = 1; + ASSERT_CALL( pthread_cond_signal( &jackApi->cond ), 0 ); + ASSERT_CALL( pthread_mutex_unlock( &jackApi->mtx ), 0 ); + +} + +static int JackSrCb( jack_nframes_t nframes, void *arg ) +{ + PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg; + double sampleRate = (double)nframes; + PaJackStream *stream = jackApi->processQueue; + + /* Update all streams in process queue */ + PA_DEBUG(( "%s: Acting on change in JACK samplerate: %f\n", __FUNCTION__, sampleRate )); + for( ; stream; stream = stream->next ) + { + if( stream->streamRepresentation.streamInfo.sampleRate != sampleRate ) + { + PA_DEBUG(( "%s: Updating samplerate\n", __FUNCTION__ )); + UpdateSampleRate( stream, sampleRate ); + } + } + + return 0; +} + +static int JackXRunCb(void *arg) { + PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)arg; + assert( hostApi ); + hostApi->xrun = TRUE; + PA_DEBUG(( "%s: JACK signalled xrun\n", __FUNCTION__ )); + return 0; } PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, @@ -305,53 +692,46 @@ PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, { PaError result = paNoError; PaJackHostApiRepresentation *jackHostApi; + int activated = 0; + char *clientName; + int written; + *hostApi = NULL; /* Initialize to NULL */ - jackHostApi = (PaJackHostApiRepresentation*) - PaUtil_AllocateMemory( sizeof(PaJackHostApiRepresentation) ); - if( !jackHostApi ) - { - result = paInsufficientMemory; - goto error; - } + UNLESS( jackHostApi = (PaJackHostApiRepresentation*) + PaUtil_AllocateMemory( sizeof(PaJackHostApiRepresentation) ), paInsufficientMemory ); jackHostApi->deviceInfoMemory = NULL; + mainThread_ = pthread_self(); + ASSERT_CALL( pthread_mutex_init( &jackHostApi->mtx, NULL ), 0 ); + ASSERT_CALL( pthread_cond_init( &jackHostApi->cond, NULL ), 0 ); + /* Try to become a client of the JACK server. If we cannot do - * this, than this API cannot be used. */ + * this, then this API cannot be used. */ - jackHostApi->jack_client = jack_client_new( "PortAudio client" ); - if( jackHostApi->jack_client == 0 ) + clientName = alloca( jack_client_name_size() ); + written = snprintf( clientName, jack_client_name_size(), "PortAudio-%d", getpid() ); + assert( written < jack_client_name_size() ); + jackHostApi->jack_client = jack_client_new( clientName ); + if( jackHostApi->jack_client == NULL ) { /* the V19 development docs say that if an implementation * detects that it cannot be used, it should return a NULL * interface and paNoError */ result = paNoError; - *hostApi = NULL; goto error; } - jackHostApi->deviceInfoMemory = PaUtil_CreateAllocationGroup(); - if( !jackHostApi->deviceInfoMemory ) - { - result = paInsufficientMemory; - goto error; - } - + UNLESS( jackHostApi->deviceInfoMemory = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); jackHostApi->hostApiIndex = hostApiIndex; *hostApi = &jackHostApi->commonHostApiRep; (*hostApi)->info.structVersion = 1; - (*hostApi)->info.type = paInDevelopment; + (*hostApi)->info.type = paJACK; (*hostApi)->info.name = "JACK Audio Connection Kit"; - (*hostApi)->info.defaultInputDevice = paNoDevice; /* set in BuildDeviceList() */ - (*hostApi)->info.defaultOutputDevice = paNoDevice; /* set in BuildDeviceList() */ - - (*hostApi)->info.deviceCount = 0; /* set in BuildDeviceList() */ /* Build a device list by querying the JACK server */ - result = BuildDeviceList( jackHostApi ); - if( result != paNoError ) - goto error; + ENSURE_PA( BuildDeviceList( jackHostApi ) ); /* Register functions */ @@ -368,11 +748,38 @@ PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + PaUtil_InitializeStreamInterface( &jackHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + BlockingReadStream, BlockingWriteStream, + BlockingGetStreamReadAvailable, BlockingGetStreamWriteAvailable ); + + jackHostApi->inputBase = jackHostApi->outputBase = 0; + jackHostApi->xrun = 0; + jackHostApi->toAdd = jackHostApi->toRemove = NULL; + jackHostApi->processQueue = NULL; + jackHostApi->jackIsDown = 0; + + jack_on_shutdown( jackHostApi->jack_client, JackOnShutdown, jackHostApi ); + jack_set_error_function( JackErrorCallback ); + jackHostApi->jack_buffer_size = jack_get_buffer_size ( jackHostApi->jack_client ); + UNLESS( !jack_set_sample_rate_callback( jackHostApi->jack_client, JackSrCb, jackHostApi ), paUnanticipatedHostError ); + UNLESS( !jack_set_xrun_callback( jackHostApi->jack_client, JackXRunCb, jackHostApi ), paUnanticipatedHostError ); + UNLESS( !jack_set_process_callback( jackHostApi->jack_client, JackCallback, jackHostApi ), paUnanticipatedHostError ); + UNLESS( !jack_activate( jackHostApi->jack_client ), paUnanticipatedHostError ); + activated = 1; + return result; error: + if( activated ) + ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 ); + if( jackHostApi ) { + if( jackHostApi->jack_client ) + ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 ); + if( jackHostApi->deviceInfoMemory ) { PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory ); @@ -389,7 +796,14 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) { PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi; - jack_client_close( jackHostApi->jack_client ); + /* note: this automatically disconnects all ports, since a deactivated + * client is not allowed to have any ports connected */ + ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 ); + + ASSERT_CALL( pthread_mutex_destroy( &jackHostApi->mtx ), 0 ); + ASSERT_CALL( pthread_cond_destroy( &jackHostApi->cond ), 0 ); + + ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 ); if( jackHostApi->deviceInfoMemory ) { @@ -398,6 +812,8 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) } PaUtil_FreeMemory( jackHostApi ); + + free( jackErr_ ); } static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, @@ -405,7 +821,7 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *outputParameters, double sampleRate ) { - int inputChannelCount, outputChannelCount; + int inputChannelCount = 0, outputChannelCount = 0; PaSampleFormat inputSampleFormat, outputSampleFormat; if( inputParameters ) @@ -479,47 +895,157 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, /* check that the device supports sampleRate */ #define ABS(x) ( (x) > 0 ? (x) : -(x) ) - if( ABS(sampleRate - hostApi->deviceInfos[0]->defaultSampleRate) > 1 ) + if( ABS(sampleRate - jack_get_sample_rate(((PaJackHostApiRepresentation *) hostApi)->jack_client )) > 1 ) return paInvalidSampleRate; #undef ABS return paFormatIsSupported; } -/* PaJackStream - a stream data structure specifically for this implementation */ +/* Basic stream initialization */ +static PaError InitializeStream( PaJackStream *stream, PaJackHostApiRepresentation *hostApi, int numInputChannels, + int numOutputChannels ) +{ + PaError result = paNoError; + assert( stream ); -typedef struct PaJackStream + memset( stream, 0, sizeof (PaJackStream) ); + UNLESS( stream->stream_memory = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + stream->jack_client = hostApi->jack_client; + stream->hostApi = hostApi; + + if( numInputChannels > 0 ) + { + UNLESS( stream->local_input_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ), + paInsufficientMemory ); + memset( stream->local_input_ports, 0, sizeof(jack_port_t*) * numInputChannels ); + UNLESS( stream->remote_output_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ), + paInsufficientMemory ); + memset( stream->remote_output_ports, 0, sizeof(jack_port_t*) * numInputChannels ); + } + if( numOutputChannels > 0 ) + { + UNLESS( stream->local_output_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ), + paInsufficientMemory ); + memset( stream->local_output_ports, 0, sizeof(jack_port_t*) * numOutputChannels ); + UNLESS( stream->remote_input_ports = + (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ), + paInsufficientMemory ); + memset( stream->remote_input_ports, 0, sizeof(jack_port_t*) * numOutputChannels ); + } + + stream->num_incoming_connections = numInputChannels; + stream->num_outgoing_connections = numOutputChannels; + +error: + return result; +} + +/*! + * Free resources associated with stream, and eventually stream itself. + * + * Frees allocated memory, and closes opened pcms. + */ +static void CleanUpStream( PaJackStream *stream, int terminateStreamRepresentation, int terminateBufferProcessor ) { - PaUtilStreamRepresentation streamRepresentation; - PaUtilBufferProcessor bufferProcessor; - PaUtilCpuLoadMeasurer cpuLoadMeasurer; + int i; + assert( stream ); - /* our input and output ports */ - jack_port_t **local_input_ports; - jack_port_t **local_output_ports; + if( stream->isBlockingStream ) + BlockingEnd( stream ); - /* the input and output ports of the client we are connecting to */ - jack_port_t **remote_input_ports; - jack_port_t **remote_output_ports; + for( i = 0; i < stream->num_incoming_connections; ++i ) + { + if( stream->local_input_ports[i] ) + ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_input_ports[i] ), 0 ); + free( stream->remote_output_ports[i] ); + } + for( i = 0; i < stream->num_outgoing_connections; ++i ) + { + if( stream->local_output_ports[i] ) + ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_output_ports[i] ), 0 ); + free( stream->remote_input_ports[i] ); + } - int num_incoming_connections; - int num_outgoing_connections; + if( terminateStreamRepresentation ) + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + if( terminateBufferProcessor ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); - jack_client_t *jack_client; + if( stream->stream_memory ) + { + PaUtil_FreeAllAllocations( stream->stream_memory ); + PaUtil_DestroyAllocationGroup( stream->stream_memory ); + } + PaUtil_FreeMemory( stream ); +} - /* The stream is running if it's still producing samples. - * The stream is active if samples it produced are still being heard. - */ - int is_running; - int is_active; +static PaError WaitCondition( PaJackHostApiRepresentation *hostApi ) +{ + PaError result = paNoError; + int err = 0; + PaTime pt = PaUtil_GetTime(); + struct timespec ts; - jack_nframes_t t0; - unsigned long total_frames_sent; + ts.tv_sec = (time_t) floor( pt + 1 ); + ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000); + /* XXX: Best enclose in loop, in case of spurious wakeups? */ + err = pthread_cond_timedwait( &hostApi->cond, &hostApi->mtx, &ts ); - PaUtilAllocationGroup *stream_memory; + /* Make sure we didn't time out */ + UNLESS( err != ETIMEDOUT, paTimedOut ); + UNLESS( !err, paInternalError ); + +error: + return result; } -PaJackStream; +static PaError AddStream( PaJackStream *stream ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *hostApi = stream->hostApi; + /* Add to queue of streams that should be processed */ + ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 ); + if( !hostApi->jackIsDown ) + { + hostApi->toAdd = stream; + /* Unlock mutex and await signal from processing thread */ + result = WaitCondition( stream->hostApi ); + } + ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 ); + ENSURE_PA( result ); + + UNLESS( !hostApi->jackIsDown, paDeviceUnavailable ); + +error: + return result; +} + +/* Remove stream from processing queue */ +static PaError RemoveStream( PaJackStream *stream ) +{ + PaError result = paNoError; + PaJackHostApiRepresentation *hostApi = stream->hostApi; + + /* Add to queue over streams that should be processed */ + ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 ); + if( !hostApi->jackIsDown ) + { + hostApi->toRemove = stream; + /* Unlock mutex and await signal from processing thread */ + result = WaitCondition( stream->hostApi ); + } + ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 ); + ENSURE_PA( result ); + +error: + return result; +} + +/* Add stream to processing queue */ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaStream** s, const PaStreamParameters *inputParameters, @@ -532,19 +1058,33 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, { PaError result = paNoError; PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi; - PaJackStream *stream = 0; - char port_string[100]; - char regex_pattern[100]; - const char **jack_ports; - int jack_max_buffer_size = jack_get_buffer_size( jackHostApi->jack_client ); + PaJackStream *stream = NULL; + char *port_string = alloca( jack_port_name_size() ); + unsigned long regexSz = jack_client_name_size() + 3; + char *regex_pattern = alloca( regexSz ); + const char **jack_ports = NULL; + /* int jack_max_buffer_size = jack_get_buffer_size( jackHostApi->jack_client ); */ int i; int inputChannelCount, outputChannelCount; - PaSampleFormat inputSampleFormat, outputSampleFormat; - - /* the client has no say over the frames per callback */ + const double jackSr = jack_get_sample_rate( jackHostApi->jack_client ); + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0; + int bpInitialized = 0, srInitialized = 0; /* Initialized buffer processor and stream representation? */ + unsigned long ofs; + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + if( (streamFlags & paPrimeOutputBuffersUsingStreamCallback) != 0 ) + { + streamFlags &= ~paPrimeOutputBuffersUsingStreamCallback; + /*return paInvalidFlag;*/ /* This implementation does not support buffer priming */ + } - if( framesPerBuffer == paFramesPerBufferUnspecified ) - framesPerBuffer = jack_max_buffer_size; + if( framesPerBuffer != paFramesPerBufferUnspecified ) + { + /* Jack operates with power of two buffers, and we don't support non-integer buffer adaption (yet) */ + /*UNLESS( !(framesPerBuffer & (framesPerBuffer - 1)), paBufferTooBig );*/ /* TODO: Add descriptive error code? */ + } /* Preliminary checks */ @@ -596,78 +1136,79 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, outputChannelCount = 0; } - /* ... check that the sample rate exactly matches the ONE acceptable rate */ + /* ... check that the sample rate exactly matches the ONE acceptable rate + * A: This rate isn't necessarily constant though? */ #define ABS(x) ( (x) > 0 ? (x) : -(x) ) - if( ABS(sampleRate - hostApi->deviceInfos[0]->defaultSampleRate) > 1 ) + if( ABS(sampleRate - jackSr) > 1 ) return paInvalidSampleRate; #undef ABS - /* Allocate memory for structuures */ - -#define MALLOC(size) \ - (PaUtil_GroupAllocateMemory( stream->stream_memory, (size) )) + UNLESS( stream = (PaJackStream*)PaUtil_AllocateMemory( sizeof(PaJackStream) ), paInsufficientMemory ); + ENSURE_PA( InitializeStream( stream, jackHostApi, inputChannelCount, outputChannelCount ) ); -#define MEMVERIFY(ptr) \ - if( (ptr) == NULL ) \ - { \ - result = paInsufficientMemory; \ - goto error; \ - } + /* the blocking emulation, if necessary */ + stream->isBlockingStream = !streamCallback; + if( stream->isBlockingStream ) + { + float latency = 0.001; /* 1ms is the absolute minimum we support */ + int minimum_buffer_frames = 0; - stream = (PaJackStream*)PaUtil_AllocateMemory( sizeof(PaJackStream) ); - MEMVERIFY( stream ); + if( inputParameters && inputParameters->suggestedLatency > latency ) + latency = inputParameters->suggestedLatency; + else if( outputParameters && outputParameters->suggestedLatency > latency ) + latency = outputParameters->suggestedLatency; - stream->stream_memory = PaUtil_CreateAllocationGroup(); - stream->jack_client = jackHostApi->jack_client; - PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + /* the latency the user asked for indicates the minimum buffer size in frames */ + minimum_buffer_frames = (int) (latency * jack_get_sample_rate( jackHostApi->jack_client )); - stream->local_input_ports = - (jack_port_t**) MALLOC(sizeof(jack_port_t*) * inputChannelCount ); - stream->local_output_ports = - (jack_port_t**) MALLOC( sizeof(jack_port_t*) * outputChannelCount ); - stream->remote_output_ports = - (jack_port_t**) MALLOC( sizeof(jack_port_t*) * inputChannelCount ); - stream->remote_input_ports = - (jack_port_t**) MALLOC( sizeof(jack_port_t*) * outputChannelCount ); + /* we also need to be able to store at least three full jack buffers to avoid dropouts */ + if( jackHostApi->jack_buffer_size * 3 > minimum_buffer_frames ) + minimum_buffer_frames = jackHostApi->jack_buffer_size * 3; - MEMVERIFY( stream->local_input_ports ); - MEMVERIFY( stream->local_output_ports ); - MEMVERIFY( stream->remote_input_ports ); - MEMVERIFY( stream->remote_output_ports ); + /* setup blocking API data structures (FIXME: can fail) */ + BlockingBegin( stream, minimum_buffer_frames ); - stream->num_incoming_connections = inputChannelCount; - stream->num_outgoing_connections = outputChannelCount; + /* install our own callback for the blocking API */ + streamCallback = BlockingCallback; + userData = stream; - if( streamCallback ) - { PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - &jackHostApi->callbackStreamInterface, streamCallback, userData ); + &jackHostApi->blockingStreamInterface, streamCallback, userData ); } else { - /* we do not support blocking I/O */ - return paBadIODeviceCombination; + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &jackHostApi->callbackStreamInterface, streamCallback, userData ); } + srInitialized = 1; + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, jackSr ); /* create the JACK ports. We cannot connect them until audio * processing begins */ + /* Register a unique set of ports for this stream + * TODO: Robust allocation of new port names */ + + ofs = jackHostApi->inputBase; for( i = 0; i < inputChannelCount; i++ ) { - sprintf( port_string, "in_%d", i ); - stream->local_input_ports[i] = jack_port_register( + snprintf( port_string, jack_port_name_size(), "in_%lu", ofs + i ); + UNLESS( stream->local_input_ports[i] = jack_port_register( jackHostApi->jack_client, port_string, - JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ), paInsufficientMemory ); } + jackHostApi->inputBase += inputChannelCount; + ofs = jackHostApi->outputBase; for( i = 0; i < outputChannelCount; i++ ) { - sprintf( port_string, "out_%d", i ); - stream->local_output_ports[i] = jack_port_register( + snprintf( port_string, jack_port_name_size(), "out_%lu", ofs + i ); + UNLESS( stream->local_output_ports[i] = jack_port_register( jackHostApi->jack_client, port_string, - JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ), paInsufficientMemory ); } + jackHostApi->outputBase += outputChannelCount; /* look up the jack_port_t's for the remote ports. We could do * this at stream start time, but doing it here ensures the @@ -675,44 +1216,53 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( inputChannelCount > 0 ) { + int err = 0; + /* ... remote output ports (that we input from) */ - sprintf( regex_pattern, "%s:.*", hostApi->deviceInfos[ inputParameters->device ]->name ); - jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern, - NULL, JackPortIsOutput); + snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ inputParameters->device ]->name ); + UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern, + NULL, JackPortIsOutput ), paUnanticipatedHostError ); for( i = 0; i < inputChannelCount && jack_ports[i]; i++ ) { - stream->remote_output_ports[i] = jack_port_by_name( - jackHostApi->jack_client, jack_ports[i] ); - } - if( i < inputChannelCount ) - { - /* we found fewer ports than we expected */ - return paInternalError; + if( (stream->remote_output_ports[i] = jack_port_by_name( + jackHostApi->jack_client, jack_ports[i] )) == NULL ) + { + err = 1; + break; + } } - free( jack_ports ); // XXX: this doesn't happen if we exit prematurely - } + free( jack_ports ); + UNLESS( !err, paInsufficientMemory ); + /* Fewer ports than expected? */ + UNLESS( i == inputChannelCount, paInternalError ); + } if( outputChannelCount > 0 ) { + int err = 0; + /* ... remote input ports (that we output to) */ - sprintf( regex_pattern, "%s:.*", hostApi->deviceInfos[ outputParameters->device ]->name ); - jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern, - NULL, JackPortIsInput); + snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ outputParameters->device ]->name ); + UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern, + NULL, JackPortIsInput ), paUnanticipatedHostError ); for( i = 0; i < outputChannelCount && jack_ports[i]; i++ ) { - stream->remote_input_ports[i] = jack_port_by_name( - jackHostApi->jack_client, jack_ports[i] ); - } - if( i < outputChannelCount ) - { - /* we found fewer ports than we expected */ - return paInternalError; + if( (stream->remote_input_ports[i] = jack_port_by_name( + jackHostApi->jack_client, jack_ports[i] )) == 0 ) + { + err = 1; + break; + } } - free( jack_ports ); // XXX: this doesn't happen if we exit prematurely + free( jack_ports ); + UNLESS( !err , paInsufficientMemory ); + + /* Fewer ports than expected? */ + UNLESS( i == outputChannelCount, paInternalError ); } - result = PaUtil_InitializeBufferProcessor( + ENSURE_PA( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, inputChannelCount, inputSampleFormat, @@ -720,158 +1270,302 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, outputChannelCount, outputSampleFormat, paFloat32, /* hostOutputSampleFormat */ - sampleRate, + jackSr, streamFlags, framesPerBuffer, - jack_max_buffer_size, - paUtilFixedHostBufferSize, + 0, /* Ignored */ + paUtilUnknownHostBufferSize, /* Buffer size may vary on JACK's discretion */ streamCallback, - userData ); + userData ) ); + bpInitialized = 1; - if( result != paNoError ) - goto error; - - stream->is_running = FALSE; - stream->t0 = -1;/* set the first time through the callback*/ - stream->total_frames_sent = 0; + if( stream->num_incoming_connections > 0 ) + stream->streamRepresentation.streamInfo.inputLatency = (jack_port_get_latency( stream->remote_output_ports[0] ) + - jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */ + + PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor )) / sampleRate; + if( stream->num_outgoing_connections > 0 ) + stream->streamRepresentation.streamInfo.outputLatency = (jack_port_get_latency( stream->remote_input_ports[0] ) + - jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */ + + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor )) / sampleRate; - jack_set_process_callback( jackHostApi->jack_client, JackCallback, stream ); + stream->streamRepresentation.streamInfo.sampleRate = jackSr; + stream->t0 = jack_frame_time( jackHostApi->jack_client ); /* A: Time should run from Pa_OpenStream */ + ENSURE_PA( AddStream( stream ) ); /* Add to queue over opened streams */ + *s = (PaStream*)stream; return result; error: if( stream ) - { - if( stream->stream_memory ) - { - PaUtil_FreeAllAllocations( stream->stream_memory ); - PaUtil_DestroyAllocationGroup( stream->stream_memory ); - } - - PaUtil_FreeMemory( stream ); - } + CleanUpStream( stream, srInitialized, bpInitialized ); return result; - -#undef MALLOC -#undef MEMVERIFY } +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaJackStream *stream = (PaJackStream*)s; -static int JackCallback( jack_nframes_t frames, void *userData ) + /* Remove this stream from the processing queue */ + ENSURE_PA( RemoveStream( stream ) ); + +error: + CleanUpStream( stream, 1, 1 ); + return result; +} + +static PaError RealProcess( PaJackStream *stream, jack_nframes_t frames ) { - PaJackStream *stream = (PaJackStream*)userData; - PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* IMPLEMENT ME */ - int callbackResult; + PaError result = paNoError; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; int chn; int framesProcessed; + const double sr = jack_get_sample_rate( stream->jack_client ); /* Shouldn't change during the process callback */ + PaStreamCallbackFlags cbFlags = 0; - if( stream->t0 == -1 ) + /* If the user has returned !paContinue from the callback we'll want to flush the internal buffers, + * when these are empty we can finally mark the stream as inactive */ + if( stream->callbackResult != paContinue && + PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) { - if( stream->num_outgoing_connections == 0 ) - { - /* TODO: how to handle stream time for capture-only operation? */ - } - else - { - /* the beginning time needs to be initialized */ - stream->t0 = jack_frame_time( stream->jack_client ) - - jack_frames_since_cycle_start( stream->jack_client) + - jack_port_get_total_latency( stream->jack_client, - stream->local_output_ports[0] ); - } + stream->is_active = 0; + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + PA_DEBUG(( "%s: Callback finished\n", __FUNCTION__ )); + + goto end; } + timeInfo.currentTime = (jack_frame_time( stream->jack_client ) - stream->t0) / sr; + if( stream->num_incoming_connections > 0 ) + timeInfo.inputBufferAdcTime = timeInfo.currentTime - jack_port_get_latency( stream->remote_output_ports[0] ) + / sr; + if( stream->num_outgoing_connections > 0 ) + timeInfo.outputBufferDacTime = timeInfo.currentTime + jack_port_get_latency( stream->remote_input_ports[0] ) + / sr; + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + if( stream->xrun ) + { + /* XXX: Any way to tell which of these occurred? */ + cbFlags = paOutputUnderflow | paInputOverflow; + stream->xrun = FALSE; + } PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, - 0 /* @todo pass underflow/overflow flags when necessary */ ); + cbFlags ); + + if( stream->num_incoming_connections > 0 ) + PaUtil_SetInputFrameCount( &stream->bufferProcessor, frames ); + if( stream->num_outgoing_connections > 0 ) + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, frames ); for( chn = 0; chn < stream->num_incoming_connections; chn++ ) { - jack_default_audio_sample_t *channel_buf; - channel_buf = (jack_default_audio_sample_t*) + jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*) jack_port_get_buffer( stream->local_input_ports[chn], - frames ); + frames ); PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor, - chn, - channel_buf ); + chn, + channel_buf ); } for( chn = 0; chn < stream->num_outgoing_connections; chn++ ) { - jack_default_audio_sample_t *channel_buf; - channel_buf = (jack_default_audio_sample_t*) + jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*) jack_port_get_buffer( stream->local_output_ports[chn], - frames ); + frames ); PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor, - chn, - channel_buf ); + chn, + channel_buf ); } - if( stream->num_incoming_connections > 0 ) - PaUtil_SetInputFrameCount( &stream->bufferProcessor, frames ); - - if( stream->num_outgoing_connections > 0 ) - PaUtil_SetOutputFrameCount( &stream->bufferProcessor, frames ); - - callbackResult = paContinue; framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, - &callbackResult ); + &stream->callbackResult ); + /* We've specified a host buffer size mode where every frame should be consumed by the buffer processor */ + assert( framesProcessed == frames ); PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); - stream->total_frames_sent += frames; +end: + return result; +} + +/* Alter the processing queue if necessary */ +static PaError UpdateQueue( PaJackHostApiRepresentation *hostApi ) +{ + PaError result = paNoError; + int queueModified = 0; + const double jackSr = jack_get_sample_rate( hostApi->jack_client ); + int err; - if( callbackResult == paContinue ) + if( (err = pthread_mutex_trylock( &hostApi->mtx )) != 0 ) { - /* nothing special */ + assert( err == EBUSY ); + return paNoError; } - else if( callbackResult == paAbort ) + + if( hostApi->toAdd ) { - /* finish playback immediately */ + if( hostApi->processQueue ) + { + PaJackStream *node = hostApi->processQueue; + /* Advance to end of queue */ + while( node->next ) + node = node->next; - /* TODO: memset 0 the outgoing samples to "cancel" them */ + node->next = hostApi->toAdd; + } + else + hostApi->processQueue = (PaJackStream *)hostApi->toAdd; - stream->is_active = FALSE; + /* If necessary, update stream state */ + if( hostApi->toAdd->streamRepresentation.streamInfo.sampleRate != jackSr ) + UpdateSampleRate( hostApi->toAdd, jackSr ); - /* return nonzero so we get deactivated (and the callback won't - * get called again) */ - return 1; + hostApi->toAdd = NULL; + queueModified = 1; } - else + if( hostApi->toRemove ) { - /* User callback has asked us to stop with paComplete or other non-zero value. */ + int removed = 0; + PaJackStream *node = hostApi->processQueue, *prev = NULL; + assert( hostApi->processQueue ); - stream->is_active = FALSE; + while( node ) + { + if( node == hostApi->toRemove ) + { + if( prev ) + prev->next = node->next; + else + hostApi->processQueue = (PaJackStream *)node->next; - /* return nonzero so we get deactivated (and the callback won't - * get called again) */ - return 1; + removed = 1; + break; + } + + prev = node; + node = node->next; + } + UNLESS( removed, paInternalError ); + hostApi->toRemove = NULL; + PA_DEBUG(( "%s: Removed stream from processing queue\n", __FUNCTION__ )); + queueModified = 1; + } + + if( queueModified ) + { + /* Signal that we've done what was asked of us */ + ASSERT_CALL( pthread_cond_signal( &hostApi->cond ), 0 ); } - return 0; -} +error: + ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 ); -/* - When CloseStream() is called, the multi-api layer ensures that - the stream has already been stopped or aborted. -*/ -static PaError CloseStream( PaStream* s ) + return result; +} + +static int JackCallback( jack_nframes_t frames, void *userData ) { PaError result = paNoError; - PaJackStream *stream = (PaJackStream*)s; + PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)userData; + PaJackStream *stream = NULL; + int xrun = hostApi->xrun; + hostApi->xrun = 0; - PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); - PaUtil_FreeMemory( stream ); + assert( hostApi ); - return result; -} + ENSURE_PA( UpdateQueue( hostApi ) ); + + /* Process each stream */ + stream = hostApi->processQueue; + for( ; stream; stream = stream->next ) + { + if( xrun ) /* Don't override if already set */ + stream->xrun = 1; + + /* See if this stream is to be started */ + if( stream->doStart ) + { + /* If we can't obtain a lock, we'll try next time */ + int err = pthread_mutex_trylock( &stream->hostApi->mtx ); + if( !err ) + { + if( stream->doStart ) /* Could potentially change before obtaining the lock */ + { + stream->is_active = 1; + stream->doStart = 0; + PA_DEBUG(( "%s: Starting stream\n", __FUNCTION__ )); + ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 ); + stream->callbackResult = paContinue; + stream->isSilenced = 0; + } + + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + } + else + assert( err == EBUSY ); + } + else if( stream->doStop || stream->doAbort ) /* Should we stop/abort stream? */ + { + if( stream->callbackResult == paContinue ) /* Ok, make it stop */ + { + PA_DEBUG(( "%s: Stopping stream\n", __FUNCTION__ )); + stream->callbackResult = stream->doStop ? paComplete : paAbort; + } + } + + if( stream->is_active ) + ENSURE_PA( RealProcess( stream, frames ) ); + /* If we have just entered inactive state, silence output */ + if( !stream->is_active && !stream->isSilenced ) + { + int i; + + /* Silence buffer after entering inactive state */ + PA_DEBUG(( "Silencing the output\n" )); + for( i = 0; i < stream->num_outgoing_connections; ++i ) + { + jack_default_audio_sample_t *buffer = jack_port_get_buffer( stream->local_output_ports[i], frames ); + memset( buffer, 0, sizeof (jack_default_audio_sample_t) * frames ); + } + + stream->isSilenced = 1; + } + + if( stream->doStop || stream->doAbort ) + { + /* See if RealProcess has acted on the request */ + if( !stream->is_active ) /* Ok, signal to the main thread that we've carried out the operation */ + { + /* If we can't obtain a lock, we'll try next time */ + int err = pthread_mutex_trylock( &stream->hostApi->mtx ); + if( !err ) + { + stream->doStop = stream->doAbort = 0; + ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 ); + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + } + else + assert( err == EBUSY ); + } + } + } + return 0; +error: + return -1; +} static PaError StartStream( PaStream *s ) { @@ -879,9 +1573,8 @@ static PaError StartStream( PaStream *s ) PaJackStream *stream = (PaJackStream*)s; int i; - /* start the audio thread */ - - jack_activate( stream->jack_client ); + /* Ready the processor */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); /* connect the ports */ @@ -890,76 +1583,122 @@ static PaError StartStream( PaStream *s ) if( stream->num_incoming_connections > 0 ) { for( i = 0; i < stream->num_incoming_connections; i++ ) - jack_connect( stream->jack_client, - jack_port_name(stream->remote_output_ports[i]), - jack_port_name(stream->local_input_ports[i] ) ); + UNLESS( jack_connect( stream->jack_client, + jack_port_name( stream->remote_output_ports[i] ), + jack_port_name( stream->local_input_ports[i] ) ) == 0, paUnanticipatedHostError ); } if( stream->num_outgoing_connections > 0 ) { for( i = 0; i < stream->num_outgoing_connections; i++ ) - jack_connect( stream->jack_client, - jack_port_name(stream->local_output_ports[i]), - jack_port_name(stream->remote_input_ports[i]) ); + UNLESS( jack_connect( stream->jack_client, + jack_port_name( stream->local_output_ports[i] ), + jack_port_name( stream->remote_input_ports[i] ) ) == 0, paUnanticipatedHostError ); } + stream->xrun = FALSE; + + /* Enable processing */ + + ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 ); + stream->doStart = 1; + + /* Wait for stream to be started */ + result = WaitCondition( stream->hostApi ); + /* + do + { + err = pthread_cond_timedwait( &stream->hostApi->cond, &stream->hostApi->mtx, &ts ); + } while( !stream->is_active && !err ); + */ + if( result != paNoError ) /* Something went wrong, call off the stream start */ + { + stream->doStart = 0; + stream->is_active = 0; /* Cancel any processing */ + } + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + + ENSURE_PA( result ); + stream->is_running = TRUE; + PA_DEBUG(( "%s: Stream started\n", __FUNCTION__ )); +error: return result; } - -static PaError StopStream( PaStream *s ) +static PaError RealStop( PaJackStream *stream, int abort ) { PaError result = paNoError; - PaJackStream *stream = (PaJackStream*)s; + int i; - /* note: this automatically disconnects all ports, since a deactivated - * client is not allowed to have any ports connected */ - jack_deactivate( stream->jack_client ); + if( stream->isBlockingStream ) + BlockingWaitEmpty ( stream ); + + ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 ); + if( abort ) + stream->doAbort = 1; + else + stream->doStop = 1; + + /* Wait for stream to be stopped */ + result = WaitCondition( stream->hostApi ); + ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 ); + ENSURE_PA( result ); + UNLESS( !stream->is_active, paInternalError ); + + PA_DEBUG(( "%s: Stream stopped\n", __FUNCTION__ )); + +error: stream->is_running = FALSE; - /* TODO: block until playback complete */ + /* Disconnect ports belonging to this stream */ - stream->is_active = FALSE; + if( !stream->hostApi->jackIsDown ) /* XXX: Well? */ + { + if( stream->num_incoming_connections > 0 ) + { + for( i = 0; i < stream->num_incoming_connections; i++ ) + UNLESS( !jack_disconnect( stream->jack_client, + jack_port_name( stream->remote_output_ports[i] ), + jack_port_name( stream->local_input_ports[i] ) ), paUnanticipatedHostError ); + } + if( stream->num_outgoing_connections > 0 ) + { + for( i = 0; i < stream->num_outgoing_connections; i++ ) + UNLESS( !jack_disconnect( stream->jack_client, + jack_port_name( stream->local_output_ports[i] ), + jack_port_name( stream->remote_input_ports[i] ) ), paUnanticipatedHostError ); + } + } return result; } +static PaError StopStream( PaStream *s ) +{ + assert(s); + return RealStop( (PaJackStream *)s, 0 ); +} static PaError AbortStream( PaStream *s ) { - PaError result = paNoError; - PaJackStream *stream = (PaJackStream*)s; - - /* There's no way to cancel samples already submitted, but we can - * return immediately */ - - /* note: this automatically disconnects all ports, since a deactivated - * client is not allowed to have any ports connected */ - jack_deactivate( stream->jack_client ); - - stream->is_running = FALSE; - stream->is_active = FALSE; - - return result; + assert(s); + return RealStop( (PaJackStream *)s, 1 ); } - static PaError IsStreamStopped( PaStream *s ) { PaJackStream *stream = (PaJackStream*)s; - - return stream->is_running == FALSE; + return !stream->is_running; } static PaError IsStreamActive( PaStream *s ) { PaJackStream *stream = (PaJackStream*)s; - - return stream->is_active == TRUE; + return stream->is_active; } @@ -967,16 +1706,13 @@ static PaTime GetStreamTime( PaStream *s ) { PaJackStream *stream = (PaJackStream*)s; - /* TODO: what if we're recording-only? */ - return jack_frame_time( stream->jack_client ) - stream->t0; + /* A: Is this relevant?? --> TODO: what if we're recording-only? */ + return (jack_frame_time( stream->jack_client ) - stream->t0) / (PaTime)jack_get_sample_rate( stream->jack_client ); } static double GetStreamCpuLoad( PaStream* s ) { PaJackStream *stream = (PaJackStream*)s; - return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); } - - diff --git a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c index c3f3f550..0d0aa1ce 100644 --- a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c +++ b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c @@ -1,10 +1,11 @@ /* - * $Id: pa_linux_alsa.c,v 1.1.2.37 2004/08/13 11:22:09 aknudsen Exp $ + * $Id: pa_linux_alsa.c,v 1.1.2.71 2005/04/15 18:20:18 aknudsen Exp $ * PortAudio Portable Real-Time Audio Library * Latest Version at: http://www.portaudio.com - * ALSA implementation by Joshua Haberman + * ALSA implementation by Joshua Haberman and Arve Knudsen * * Copyright (c) 2002 Joshua Haberman <joshua@haberman.com> + * Copyright (c) 2005 Arve Knudsen <aknuds-1@broadpark.no> * * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 1999-2002 Ross Bencina, Phil Burk @@ -47,10 +48,11 @@ #include <signal.h> #include <time.h> #include <sys/mman.h> +#include <signal.h> /* For sig_atomic_t */ #include "portaudio.h" #include "pa_util.h" -/*#include "../pa_unix/pa_unix_util.h"*/ +#include "../pa_unix/pa_unix_util.h" #include "pa_allocation.h" #include "pa_hostapi.h" #include "pa_stream.h" @@ -59,58 +61,34 @@ #include "pa_linux_alsa.h" -#undef PA_DEBUG -#define PA_DEBUG(x) globstring = "" -static char *globstring; - - -#define MIN(x,y) ( (x) < (y) ? (x) : (y) ) -#define MAX(x,y) ( (x) > (y) ? (x) : (y) ) - -#define STRINGIZE_HELPER(exp) #exp -#define STRINGIZE(exp) STRINGIZE_HELPER(exp) - /* Check return value of ALSA function, and map it to PaError */ -#define ENSURE(exp, code) \ - if( (aErr_ = (exp)) < 0 ) \ - { \ - /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ - if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ +#define ENSURE_(expr, code) \ + do { \ + if( UNLIKELY( (aErr_ = (expr)) < 0 ) ) \ { \ - PaUtil_SetLastHostErrorInfo( paALSA, aErr_, snd_strerror( aErr_ ) ); \ + /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ + if( (code) == paUnanticipatedHostError && pthread_self() != callbackThread_ ) \ + { \ + PaUtil_SetLastHostErrorInfo( paALSA, aErr_, snd_strerror( aErr_ ) ); \ + } \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ } \ - PaUtil_DebugPrint(( "Expression '" #exp "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ - result = (code); \ - goto error; \ - } - -/* Check PaError */ -#define PA_ENSURE(exp) \ - if( (paErr_ = (exp)) < paNoError ) \ - { \ - PaUtil_DebugPrint(( "Expression '" #exp "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ - result = paErr_; \ - goto error; \ - } + } while( 0 ); -#define UNLESS(exp, code) \ - if( (exp) == 0 ) \ - { \ - PaUtil_DebugPrint(( "Expression '" #exp "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ - result = (code); \ - goto error; \ - } +#define ASSERT_CALL_(expr, success) \ + aErr_ = (expr); \ + assert( aErr_ == success ); -static int aErr_; /* Used with ENSURE */ -static PaError paErr_; /* Used with PA_ENSURE */ -static int rtPrio_ = -1; -static pthread_t mainThread_; +static int aErr_; /* Used with ENSURE_ */ +static pthread_t callbackThread_; typedef enum { - streamIn, - streamOut -} StreamIO; + StreamDirection_In, + StreamDirection_Out +} StreamDirection; /* Threading utility struct */ typedef struct PaAlsaThreading @@ -119,6 +97,7 @@ typedef struct PaAlsaThreading pthread_t callbackThread; int watchdogRunning; int rtSched; + int rtPrio; int useWatchdog; unsigned long throttledSleepTime; volatile PaTime callbackTime; @@ -126,6 +105,25 @@ typedef struct PaAlsaThreading PaUtilCpuLoadMeasurer *cpuLoadMeasurer; } PaAlsaThreading; +typedef struct +{ + PaSampleFormat hostSampleFormat; + unsigned long framesPerBuffer; + int numUserChannels, numHostChannels; + int userInterleaved, hostInterleaved; + + snd_pcm_t *pcm; + snd_pcm_uframes_t bufferSize; + snd_pcm_format_t nativeFormat; + unsigned int nfds; + int ready; /* Marked ready from poll */ + void **userBuffers; + snd_pcm_uframes_t offset; + StreamDirection streamDir; + + snd_pcm_channel_area_t *channelAreas; /* Needed for channel adaption */ +} PaAlsaStreamComponent; + /* Implementation specific stream structure */ typedef struct PaAlsaStream { @@ -134,49 +132,32 @@ typedef struct PaAlsaStream PaUtilBufferProcessor bufferProcessor; PaAlsaThreading threading; - snd_pcm_t *pcm_capture; - snd_pcm_t *pcm_playback; - - snd_pcm_uframes_t frames_per_period; - snd_pcm_uframes_t playbackBufferSize; - snd_pcm_uframes_t captureBufferSize; - snd_pcm_format_t playbackNativeFormat; - - int capture_channels; - int playback_channels; - - int capture_interleaved; /* bool: is capture interleaved? */ - int playback_interleaved; /* bool: is playback interleaved? */ + unsigned long framesPerUserBuffer; - int callback_mode; /* bool: are we running in callback mode? */ - int callback_finished; /* bool: are we in the "callback finished" state? See if stream has been stopped in background */ + int primeBuffers; + int callbackMode; /* bool: are we running in callback mode? */ + int pcmsSynced; /* Have we successfully synced pcms */ /* the callback thread uses these to poll the sound device(s), waiting * for data to be ready/available */ - unsigned int capture_nfds; - unsigned int playback_nfds; struct pollfd *pfds; int pollTimeout; - /* these aren't really stream state, the callback uses them */ - snd_pcm_uframes_t capture_offset; - snd_pcm_uframes_t playback_offset; + /* Used in communication between threads */ + volatile sig_atomic_t callback_finished; /* bool: are we in the "callback finished" state? */ + volatile sig_atomic_t callbackAbort; /* Drop frames? */ + volatile sig_atomic_t callbackStop; /* Signal a stop */ + volatile sig_atomic_t isActive; /* Is stream in active state? (Between StartStream and StopStream || !paContinue) */ + pthread_mutex_t stateMtx; /* Used to synchronize access to stream state */ + pthread_mutex_t startMtx; /* Used to synchronize stream start in callback mode */ + pthread_cond_t startCond; /* Wait untill audio is started in callback thread */ - int pcmsSynced; /* Have we successfully synced pcms */ - int callbackAbort; /* Drop frames? */ - int isActive; /* Is stream in active state? (Between StartStream and StopStream || !paContinue) */ - snd_pcm_uframes_t startThreshold; - pthread_mutex_t stateMtx; /* Used to synchronize access to stream state */ - pthread_mutex_t startMtx; /* Used to synchronize stream start in callback mode */ - pthread_cond_t startCond; /* Wait untill audio is started in callback thread */ - - /* Used by callback thread for underflow/overflow handling */ - snd_pcm_sframes_t playbackAvail; - snd_pcm_sframes_t captureAvail; int neverDropInput; PaTime underrun; PaTime overrun; + + PaAlsaStreamComponent capture, playback; } PaAlsaStream; @@ -199,6 +180,8 @@ typedef struct PaAlsaDeviceInfo PaDeviceInfo commonDeviceInfo; char *alsaName; int isPlug; + int minInputChannels; + int minOutputChannels; } PaAlsaDeviceInfo; @@ -214,13 +197,11 @@ static void InitializeThreading( PaAlsaThreading *th, PaUtilCpuLoadMeasurer *clm th->throttledSleepTime = 0; th->cpuLoadMeasurer = clm; - if (rtPrio_ < 0) { - rtPrio_ = (sched_get_priority_max( SCHED_FIFO ) - sched_get_priority_min( SCHED_FIFO )) / 2 + th->rtPrio = (sched_get_priority_max( SCHED_FIFO ) - sched_get_priority_min( SCHED_FIFO )) / 2 + sched_get_priority_min( SCHED_FIFO ); - } } -static PaError KillCallbackThread( PaAlsaThreading *th, PaError *exitResult, PaError *watchdogExitResult ) +static PaError KillCallbackThread( PaAlsaThreading *th, int wait, PaError *exitResult, PaError *watchdogExitResult ) { PaError result = paNoError; void *pret; @@ -233,7 +214,7 @@ static PaError KillCallbackThread( PaAlsaThreading *th, PaError *exitResult, PaE if( th->watchdogRunning ) { pthread_cancel( th->watchdogThread ); - UNLESS( !pthread_join( th->watchdogThread, &pret ), paInternalError ); + ASSERT_CALL_( pthread_join( th->watchdogThread, &pret ), 0 ); if( pret && pret != PTHREAD_CANCELED ) { @@ -243,8 +224,11 @@ static PaError KillCallbackThread( PaAlsaThreading *th, PaError *exitResult, PaE } } - pthread_cancel( th->callbackThread ); - UNLESS( !pthread_join( th->callbackThread, &pret ), paInternalError ); + /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */ + /* TODO: Make join time out */ + if( !wait ) + pthread_cancel( th->callbackThread ); /* XXX: Safe to call this if the thread has exited on its own? */ + ASSERT_CALL_( pthread_join( th->callbackThread, &pret ), 0 ); if( pret && pret != PTHREAD_CANCELED ) { @@ -253,19 +237,16 @@ static PaError KillCallbackThread( PaAlsaThreading *th, PaError *exitResult, PaE free( pret ); } -error: return result; } static void OnWatchdogExit( void *userData ) { - int err; PaAlsaThreading *th = (PaAlsaThreading *) userData; struct sched_param spm = { 0 }; assert( th ); - err = pthread_setschedparam( th->callbackThread, SCHED_OTHER, &spm ); /* Lower before exiting */ - + ASSERT_CALL_( pthread_setschedparam( th->callbackThread, SCHED_OTHER, &spm ), 0 ); /* Lower before exiting */ PA_DEBUG(( "Watchdog exiting\n" )); } @@ -273,13 +254,13 @@ static PaError BoostPriority( PaAlsaThreading *th ) { PaError result = paNoError; struct sched_param spm = { 0 }; - spm.sched_priority = rtPrio_; + spm.sched_priority = th->rtPrio; assert( th ); if( pthread_setschedparam( th->callbackThread, SCHED_FIFO, &spm ) != 0 ) { - UNLESS( errno == EPERM, paInternalError ); + PA_UNLESS( errno == EPERM, paInternalError ); /* Lack permission to raise priority */ PA_DEBUG(( "Failed bumping priority\n" )); result = 0; } @@ -312,7 +293,6 @@ static void *WatchdogFunc( void *userData ) } cpuTimeThen = th->callbackCpuTime; - { int policy; struct sched_param spm = { 0 }; @@ -403,6 +383,8 @@ static void *WatchdogFunc( void *userData ) pthread_cleanup_pop( 1 ); /* Execute cleanup on exit */ error: + /* Shouldn't get here in the normal case */ + /* Pass on error code */ pres = malloc( sizeof (PaError) ); *pres = result; @@ -410,7 +392,7 @@ error: pthread_exit( pres ); } -static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*CallbackThreadFunc)( void * ), PaStream *s ) +static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*callbackThreadFunc)( void * ), PaStream *s ) { PaError result = paNoError; pthread_attr_t attr; @@ -421,7 +403,9 @@ static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*CallbackThread { if( mlockall( MCL_CURRENT | MCL_FUTURE ) < 0 ) { - UNLESS( (errno == EPERM), paInternalError ); + int savedErrno = errno; /* In case errno gets overwritten */ + assert( savedErrno != EINVAL ); /* Most likely a programmer error */ + PA_UNLESS( (savedErrno == EPERM), paInternalError ); PA_DEBUG(( "%s: Failed locking memory\n", __FUNCTION__ )); } else @@ -429,11 +413,11 @@ static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*CallbackThread } #endif - UNLESS( !pthread_attr_init( &attr ), paInternalError ); + PA_UNLESS( !pthread_attr_init( &attr ), paInternalError ); /* Priority relative to other processes */ - UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); + PA_UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); - UNLESS( !pthread_create( &th->callbackThread, &attr, CallbackThreadFunc, s ), paInternalError ); + PA_UNLESS( !pthread_create( &th->callbackThread, &attr, callbackThreadFunc, s ), paInternalError ); started = 1; if( th->rtSched ) @@ -443,20 +427,32 @@ static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*CallbackThread int err; struct sched_param wdSpm = { 0 }; /* Launch watchdog, watchdog sets callback thread priority */ - wdSpm.sched_priority = MIN( rtPrio_ + 4, sched_get_priority_max( SCHED_FIFO ) ); - - UNLESS( !pthread_attr_init( &attr ), paInternalError ); - UNLESS( !pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ), paInternalError ); - UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); - UNLESS( !pthread_attr_setschedpolicy( &attr, SCHED_FIFO ), paInternalError ); - UNLESS( !pthread_attr_setschedparam( &attr, &wdSpm ), paInternalError ); + int prio = PA_MIN( th->rtPrio + 4, sched_get_priority_max( SCHED_FIFO ) ); + wdSpm.sched_priority = prio; + + PA_UNLESS( !pthread_attr_init( &attr ), paInternalError ); + PA_UNLESS( !pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ), paInternalError ); + PA_UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); + PA_UNLESS( !pthread_attr_setschedpolicy( &attr, SCHED_FIFO ), paInternalError ); + PA_UNLESS( !pthread_attr_setschedparam( &attr, &wdSpm ), paInternalError ); if( (err = pthread_create( &th->watchdogThread, &attr, &WatchdogFunc, th )) ) { - UNLESS( err == EPERM, paInternalError ); + PA_UNLESS( err == EPERM, paInternalError ); /* Permission error, go on without realtime privileges */ PA_DEBUG(( "Failed bumping priority\n" )); - } else + } + else + { + int policy; th->watchdogRunning = 1; + ASSERT_CALL_( pthread_getschedparam( th->watchdogThread, &policy, &wdSpm ), 0 ); + /* Check if priority is right, policy could potentially differ from SCHED_FIFO (but that's alright) */ + if( wdSpm.sched_priority != prio ) + { + PA_DEBUG(( "Watchdog priority not set correctly (%d)\n", wdSpm.sched_priority )); + PA_ENSURE( paInternalError ); + } + } } else PA_ENSURE( BoostPriority( th ) ); @@ -466,7 +462,7 @@ end: return result; error: if( started ) - KillCallbackThread( th, NULL, NULL ); + KillCallbackThread( th, 0, NULL, NULL ); goto end; } @@ -479,7 +475,6 @@ static void CallbackUpdate( PaAlsaThreading *th ) /* prototypes for functions declared in this file */ -PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ); static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *inputParameters, @@ -503,7 +498,6 @@ static PaError IsStreamActive( PaStream *stream ); static PaTime GetStreamTime( PaStream *stream ); static double GetStreamCpuLoad( PaStream* stream ); static PaError BuildDeviceList( PaAlsaHostApiRepresentation *hostApi ); -static void CleanUpStream( PaAlsaStream *stream ); static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ); static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate ); @@ -517,14 +511,19 @@ static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static const PaAlsaDeviceInfo *GetDeviceInfo( const PaUtilHostApiRepresentation *hostApi, int device ) +{ + return (const PaAlsaDeviceInfo *)hostApi->deviceInfos[device]; +} + PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) { PaError result = paNoError; PaAlsaHostApiRepresentation *alsaHostApi = NULL; - UNLESS( alsaHostApi = (PaAlsaHostApiRepresentation*) PaUtil_AllocateMemory( + PA_UNLESS( alsaHostApi = (PaAlsaHostApiRepresentation*) PaUtil_AllocateMemory( sizeof(PaAlsaHostApiRepresentation) ), paInsufficientMemory ); - UNLESS( alsaHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + PA_UNLESS( alsaHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); alsaHostApi->hostApiIndex = hostApiIndex; *hostApi = (PaUtilHostApiRepresentation*)alsaHostApi; @@ -536,6 +535,8 @@ PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + PA_ENSURE( BuildDeviceList( alsaHostApi ) ); + PaUtil_InitializeStreamInterface( &alsaHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, @@ -554,10 +555,6 @@ PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex GetStreamReadAvailable, GetStreamWriteAvailable ); - PA_ENSURE( BuildDeviceList( alsaHostApi ) ); - - mainThread_ = pthread_self(); - return result; error: @@ -575,53 +572,74 @@ error: return result; } +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; -/*! \brief Determine max channels and default latencies + assert( hostApi ); + + if( alsaHostApi->allocations ) + { + PaUtil_FreeAllAllocations( alsaHostApi->allocations ); + PaUtil_DestroyAllocationGroup( alsaHostApi->allocations ); + } + + PaUtil_FreeMemory( alsaHostApi ); + snd_config_update_free_global(); +} + +/*! Determine max channels and default latencies. * * This function provides functionality to grope an opened (might be opened for capture or playback) pcm device for * traits like max channels, suitable default latencies and default sample rate. Upon error, max channels is set to zero, * and a suitable result returned. The device is closed before returning. */ -static PaError GropeDevice( snd_pcm_t *pcm, int *channels, double *defaultLowLatency, +static PaError GropeDevice( snd_pcm_t *pcm, int *minChannels, int *maxChannels, double *defaultLowLatency, double *defaultHighLatency, double *defaultSampleRate, int isPlug ) { PaError result = paNoError; snd_pcm_hw_params_t *hwParams; - snd_pcm_uframes_t lowLatency = 1024, highLatency = 16384; - unsigned int uchans; + snd_pcm_uframes_t lowLatency = 512, highLatency = 2048; + unsigned int minChans, maxChans; + double defaultSr = *defaultSampleRate; assert( pcm ); - ENSURE( snd_pcm_nonblock( pcm, 0 ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_nonblock( pcm, 0 ), paUnanticipatedHostError ); snd_pcm_hw_params_alloca( &hwParams ); snd_pcm_hw_params_any( pcm, hwParams ); - if (*defaultSampleRate != 0.) + if( defaultSr >= 0 ) { /* Could be that the device opened in one mode supports samplerates that the other mode wont have, * so try again .. */ - if( SetApproximateSampleRate( pcm, hwParams, *defaultSampleRate ) < 0 ) + if( SetApproximateSampleRate( pcm, hwParams, defaultSr ) < 0 ) { - *defaultSampleRate = 0.; + defaultSr = -1.; PA_DEBUG(( "%s: Original default samplerate failed, trying again ..\n", __FUNCTION__ )); } } - if( *defaultSampleRate == 0. ) /* Default sample rate not set */ + if( defaultSr < 0. ) /* Default sample rate not set */ { unsigned int sampleRate = 44100; /* Will contain approximate rate returned by alsa-lib */ - ENSURE( snd_pcm_hw_params_set_rate_near( pcm, hwParams, &sampleRate, NULL ), paUnanticipatedHostError ); - ENSURE( GetExactSampleRate( hwParams, defaultSampleRate ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_rate_near( pcm, hwParams, &sampleRate, NULL ), paUnanticipatedHostError ); + ENSURE_( GetExactSampleRate( hwParams, &defaultSr ), paUnanticipatedHostError ); } - ENSURE( snd_pcm_hw_params_get_channels_max( hwParams, &uchans ), paUnanticipatedHostError ); - assert( uchans <= INT_MAX ); - assert( uchans > 0 ); /* Weird linking issue could cause wrong version of ALSA symbols to be called, + ENSURE_( snd_pcm_hw_params_get_channels_min( hwParams, &minChans ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_channels_max( hwParams, &maxChans ), paUnanticipatedHostError ); + assert( maxChans <= INT_MAX ); + assert( maxChans > 0 ); /* Weird linking issue could cause wrong version of ALSA symbols to be called, resulting in zeroed values */ - *channels = isPlug ? 128 : uchans; /* XXX: Limit to sensible number (ALSA plugins accept a crazy amount of channels)? */ - if( isPlug ) - PA_DEBUG(( "%s: Limiting number of plugin channels to %d\n", __FUNCTION__, *channels )); + + /* XXX: Limit to sensible number (ALSA plugins accept a crazy amount of channels)? */ + if( isPlug && maxChans > 128 ) + { + maxChans = 128; + PA_DEBUG(( "%s: Limiting number of plugin channels to %u\n", __FUNCTION__, maxChans )); + } /* TWEAKME: * @@ -642,12 +660,15 @@ static PaError GropeDevice( snd_pcm_t *pcm, int *channels, double *defaultLowLat * select the nearest setting that will work at stream * config time. */ - ENSURE( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &lowLatency ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &lowLatency ), paUnanticipatedHostError ); /* Have to reset hwParams, to set new buffer size */ - ENSURE( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &highLatency ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &highLatency ), paUnanticipatedHostError ); + *minChannels = (int)minChans; + *maxChannels = (int)maxChans; + *defaultSampleRate = defaultSr; *defaultLowLatency = (double) lowLatency / *defaultSampleRate; *defaultHighLatency = (double) highLatency / *defaultSampleRate; @@ -656,32 +677,88 @@ end: return result; error: - *channels = 0; goto end; } +/* Initialize device info with invalid values (maxInputChannels and maxOutputChannels are set to zero since these indicate + * wether input/output is available) */ +static void InitializeDeviceInfo( PaDeviceInfo *deviceInfo ) +{ + deviceInfo->structVersion = -1; + deviceInfo->name = NULL; + deviceInfo->hostApi = -1; + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + deviceInfo->defaultLowInputLatency = -1.; + deviceInfo->defaultLowOutputLatency = -1.; + deviceInfo->defaultHighInputLatency = -1.; + deviceInfo->defaultHighOutputLatency = -1.; + deviceInfo->defaultSampleRate = -1.; +} + /* Helper struct */ typedef struct { char *alsaName; char *name; int isPlug; + int hasPlayback; + int hasCapture; } DeviceNames; +static PaError PaAlsa_StrDup( PaAlsaHostApiRepresentation *alsaApi, + char **dst, + const char *src) +{ + PaError result = paNoError; + int len = strlen( src ) + 1; + + /* PA_DEBUG(("PaStrDup %s %d\n", src, len)); */ + + PA_UNLESS( *dst = (char *)PaUtil_GroupAllocateMemory( alsaApi->allocations, len ), + paInsufficientMemory ); + strncpy( *dst, src, len ); + +error: + return result; +} + +/* Disregard standard plugins + * XXX: Might want to make the "default" plugin available, if we can make it work + */ +static int IgnorePlugin( const char *pluginId ) +{ +#define numIgnored 10 + static const char *ignoredPlugins[numIgnored] = {"hw", "plughw", "plug", "default", "dsnoop", "dmix", "tee", + "file", "null", "shm"}; + int i; + + for( i = 0; i < numIgnored; ++i ) + { + if( !strcmp( pluginId, ignoredPlugins[i] ) ) + { + return 1; + } + } + + return 0; +} + /* Build PaDeviceInfo list, ignore devices for which we cannot determine capabilities (possibly busy, sigh) */ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi ) { PaUtilHostApiRepresentation *commonApi = &alsaApi->commonHostApiRep; PaAlsaDeviceInfo *deviceInfoArray; int cardIdx = -1, devIdx = 0; - snd_ctl_t *ctl; - snd_ctl_card_info_t *card_info; + snd_ctl_card_info_t *cardInfo; PaError result = paNoError; size_t numDeviceNames = 0, maxDeviceNames = 1, i; DeviceNames *deviceNames = NULL; - snd_config_t *top; + snd_config_t *topNode = NULL; + snd_pcm_info_t *pcmInfo; int res; int blocking = SND_PCM_NONBLOCK; + char alsaCardName[50]; if( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) && atoi( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) ) ) blocking = 0; @@ -698,95 +775,130 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi ) * * The function itself returns 0 if it succeeded. */ cardIdx = -1; - snd_ctl_card_info_alloca( &card_info ); + snd_ctl_card_info_alloca( &cardInfo ); + snd_pcm_info_alloca( &pcmInfo ); while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 ) { - const char *cardName; - char *alsaDeviceName, *deviceName; + char *cardName; + int devIdx = -1; + snd_ctl_t *ctl; + char buf[50]; - UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, - 50 ), paInsufficientMemory ); - snprintf( alsaDeviceName, 50, "hw:%d", cardIdx ); + snprintf( alsaCardName, sizeof (alsaCardName), "hw:%d", cardIdx ); /* Acquire name of card */ - if( snd_ctl_open( &ctl, alsaDeviceName, 0 ) < 0 ) + if( snd_ctl_open( &ctl, alsaCardName, 0 ) < 0 ) continue; /* Unable to open card :( */ - snd_ctl_card_info( ctl, card_info ); - snd_ctl_close( ctl ); - cardName = snd_ctl_card_info_get_name( card_info ); + snd_ctl_card_info( ctl, cardInfo ); - UNLESS( deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, - strlen(cardName) + 1 ), paInsufficientMemory ); - strcpy( deviceName, cardName ); + PA_ENSURE( PaAlsa_StrDup( alsaApi, &cardName, snd_ctl_card_info_get_name( cardInfo )) ); - ++numDeviceNames; - if( !deviceNames || numDeviceNames > maxDeviceNames ) + while( snd_ctl_pcm_next_device( ctl, &devIdx ) == 0 && devIdx >= 0 ) { - maxDeviceNames *= 2; - UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), + char *alsaDeviceName, *deviceName; + size_t len; + int hasPlayback = 0, hasCapture = 0; + snprintf( buf, sizeof (buf), "%s:%d,%d", "hw", cardIdx, devIdx ); + + /* Obtain info about this particular device */ + snd_pcm_info_set_device( pcmInfo, devIdx ); + snd_pcm_info_set_subdevice( pcmInfo, 0 ); + snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_CAPTURE ); + if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 ) + hasCapture = 1; + + snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_PLAYBACK ); + if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 ) + hasPlayback = 1; + + if( !hasPlayback && !hasCapture ) + { + continue; /* Error */ + } + + /* The length of the string written by snprintf plus terminating 0 */ + len = snprintf( NULL, 0, "%s: %s (%s)", cardName, snd_pcm_info_get_name( pcmInfo ), buf ) + 1; + PA_UNLESS( deviceName = (char *)PaUtil_GroupAllocateMemory( alsaApi->allocations, len ), paInsufficientMemory ); - } + snprintf( deviceName, len, "%s: %s (%s)", cardName, + snd_pcm_info_get_name( pcmInfo ), buf ); + + ++numDeviceNames; + if( !deviceNames || numDeviceNames > maxDeviceNames ) + { + maxDeviceNames *= 2; + PA_UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), + paInsufficientMemory ); + } + + PA_ENSURE( PaAlsa_StrDup( alsaApi, &alsaDeviceName, buf ) ); - deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName; - deviceNames[ numDeviceNames - 1 ].name = deviceName; - deviceNames[ numDeviceNames - 1 ].isPlug = 0; + deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName; + deviceNames[ numDeviceNames - 1 ].name = deviceName; + deviceNames[ numDeviceNames - 1 ].isPlug = 0; + deviceNames[ numDeviceNames - 1 ].hasPlayback = hasPlayback; + deviceNames[ numDeviceNames - 1 ].hasCapture = hasCapture; + } + snd_ctl_close( ctl ); } /* Iterate over plugin devices */ - if( (res = snd_config_search( snd_config, "pcm", &top )) >= 0 ) + snd_config_update(); + if( (res = snd_config_search( snd_config, "pcm", &topNode )) >= 0 ) { snd_config_iterator_t i, next; - const char *s; - snd_config_for_each( i, next, top ) + snd_config_for_each( i, next, topNode ) { + const char *tpStr = NULL, *idStr = NULL; char *alsaDeviceName, *deviceName; - snd_config_t *n = snd_config_iterator_entry( i ), *tp; + snd_config_t *n = snd_config_iterator_entry( i ), *tp = NULL; if( snd_config_get_type( n ) != SND_CONFIG_TYPE_COMPOUND ) continue; - /* Restrict search to nodes of type "plug" for now */ - ENSURE( snd_config_search( n, "type", &tp ), paUnanticipatedHostError ); - ENSURE( snd_config_get_string( tp, &s ), paUnanticipatedHostError ); - if( strcmp( s, "plug" ) ) - continue; + ENSURE_( snd_config_search( n, "type", &tp ), paUnanticipatedHostError ); + ENSURE_( snd_config_get_string( tp, &tpStr ), paUnanticipatedHostError ); - /* Disregard standard plugins - * XXX: Might want to make the "default" plugin available, if we can make it work - */ - ENSURE( snd_config_get_id( n, &s ), paUnanticipatedHostError ); - if( !strcmp( s, "plughw" ) || !strcmp( s, "plug" ) || !strcmp( s, "default" ) ) + ENSURE_( snd_config_get_id( n, &idStr ), paUnanticipatedHostError ); + if( IgnorePlugin( idStr ) ) + { + PA_DEBUG(( "%s: Ignoring ALSA plugin device %s of type %s\n", __FUNCTION__, idStr, tpStr )); continue; + } + + PA_DEBUG(( "%s: Found plugin %s of type %s\n", __FUNCTION__, idStr, tpStr )); - UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, - strlen(s) + 6 ), paInsufficientMemory ); - strcpy( alsaDeviceName, s ); - UNLESS( deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, - strlen(s) + 1 ), paInsufficientMemory ); - strcpy( deviceName, s ); + PA_UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(idStr) + 6 ), paInsufficientMemory ); + strcpy( alsaDeviceName, idStr ); + PA_UNLESS( deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(idStr) + 1 ), paInsufficientMemory ); + strcpy( deviceName, idStr ); ++numDeviceNames; if( !deviceNames || numDeviceNames > maxDeviceNames ) { maxDeviceNames *= 2; - UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), + PA_UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), paInsufficientMemory ); } - deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName; - deviceNames[ numDeviceNames - 1 ].name = deviceName; - deviceNames[ numDeviceNames - 1 ].isPlug = 1; + deviceNames[numDeviceNames - 1].alsaName = alsaDeviceName; + deviceNames[numDeviceNames - 1].name = deviceName; + deviceNames[numDeviceNames - 1].isPlug = 1; + deviceNames[numDeviceNames - 1].hasPlayback = 1; + deviceNames[numDeviceNames - 1].hasCapture = 1; } } else PA_DEBUG(( "%s: Iterating over ALSA plugins failed: %s\n", __FUNCTION__, snd_strerror( res ) )); /* allocate deviceInfo memory based on the number of devices */ - UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + PA_UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( alsaApi->allocations, sizeof(PaDeviceInfo*) * (numDeviceNames) ), paInsufficientMemory ); /* allocate all device info structs in a contiguous block */ - UNLESS( deviceInfoArray = (PaAlsaDeviceInfo*)PaUtil_GroupAllocateMemory( + PA_UNLESS( deviceInfoArray = (PaAlsaDeviceInfo*)PaUtil_GroupAllocateMemory( alsaApi->allocations, sizeof(PaAlsaDeviceInfo) * numDeviceNames ), paInsufficientMemory ); /* Loop over list of cards, filling in info, if a device is deemed unavailable (can't get name), @@ -796,51 +908,52 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi ) for( i = 0, devIdx = 0; i < numDeviceNames; ++i ) { snd_pcm_t *pcm; - PaAlsaDeviceInfo *deviceInfo = &deviceInfoArray[ devIdx ]; + PaAlsaDeviceInfo *deviceInfo = &deviceInfoArray[devIdx]; PaDeviceInfo *commonDeviceInfo = &deviceInfo->commonDeviceInfo; /* Zero fields */ - memset( commonDeviceInfo, 0, sizeof (PaDeviceInfo) ); + InitializeDeviceInfo( commonDeviceInfo ); /* to determine device capabilities, we must open the device and query the * hardware parameter configuration space */ /* Query capture */ - if( snd_pcm_open( &pcm, deviceNames[ i ].alsaName, SND_PCM_STREAM_CAPTURE, blocking ) >= 0 ) + if( deviceNames[i].hasCapture && + snd_pcm_open( &pcm, deviceNames[i].alsaName, SND_PCM_STREAM_CAPTURE, blocking ) >= 0 ) { - if( GropeDevice( pcm, &commonDeviceInfo->maxInputChannels, + if( GropeDevice( pcm, &deviceInfo->minInputChannels, &commonDeviceInfo->maxInputChannels, &commonDeviceInfo->defaultLowInputLatency, &commonDeviceInfo->defaultHighInputLatency, - &commonDeviceInfo->defaultSampleRate, deviceNames[ i ].isPlug ) != paNoError ) + &commonDeviceInfo->defaultSampleRate, deviceNames[i].isPlug ) != paNoError ) continue; /* Error */ } - + /* Query playback */ - if( snd_pcm_open( &pcm, deviceNames[ i ].alsaName, SND_PCM_STREAM_PLAYBACK, blocking ) >= 0 ) + if( deviceNames[i].hasPlayback && + snd_pcm_open( &pcm, deviceNames[i].alsaName, SND_PCM_STREAM_PLAYBACK, blocking ) >= 0 ) { - if( GropeDevice( pcm, &commonDeviceInfo->maxOutputChannels, + if( GropeDevice( pcm, &deviceInfo->minOutputChannels, &commonDeviceInfo->maxOutputChannels, &commonDeviceInfo->defaultLowOutputLatency, &commonDeviceInfo->defaultHighOutputLatency, - &commonDeviceInfo->defaultSampleRate, deviceNames[ i ].isPlug ) != paNoError ) + &commonDeviceInfo->defaultSampleRate, deviceNames[i].isPlug ) != paNoError ) continue; /* Error */ } commonDeviceInfo->structVersion = 2; commonDeviceInfo->hostApi = alsaApi->hostApiIndex; - deviceInfo->alsaName = deviceNames[ i ].alsaName; - deviceInfo->isPlug = deviceNames[ i ].isPlug; - commonDeviceInfo->name = deviceNames[ i ].name; + commonDeviceInfo->name = deviceNames[i].name; + deviceInfo->alsaName = deviceNames[i].alsaName; + deviceInfo->isPlug = deviceNames[i].isPlug; /* A: Storing pointer to PaAlsaDeviceInfo object as pointer to PaDeviceInfo object. * Should now be safe to add device info, unless the device supports neither capture nor playback */ - if( commonDeviceInfo->maxInputChannels || commonDeviceInfo->maxOutputChannels ) + if( commonDeviceInfo->maxInputChannels > 0 || commonDeviceInfo->maxOutputChannels > 0 ) { - if( commonApi->info.defaultInputDevice == paNoDevice ) + if( commonApi->info.defaultInputDevice == paNoDevice && commonDeviceInfo->maxInputChannels > 0 ) commonApi->info.defaultInputDevice = devIdx; - - if( commonApi->info.defaultOutputDevice == paNoDevice ) + if( commonApi->info.defaultOutputDevice == paNoDevice && commonDeviceInfo->maxOutputChannels > 0 ) commonApi->info.defaultOutputDevice = devIdx; - commonApi->deviceInfos[ devIdx++ ] = (PaDeviceInfo *) deviceInfo; + commonApi->deviceInfos[devIdx++] = (PaDeviceInfo *) deviceInfo; } } free( deviceNames ); @@ -854,56 +967,41 @@ error: goto end; /* No particular action */ } - -static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) -{ - PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; - - assert( hostApi ); - - if( alsaHostApi->allocations ) - { - PaUtil_FreeAllAllocations( alsaHostApi->allocations ); - PaUtil_DestroyAllocationGroup( alsaHostApi->allocations ); - } - - PaUtil_FreeMemory( alsaHostApi ); -} - /* Check against known device capabilities */ -static PaError ValidateParameters( const PaStreamParameters *parameters, const PaAlsaDeviceInfo *deviceInfo, StreamIO io, - const PaAlsaStreamInfo *streamInfo ) +static PaError ValidateParameters( const PaStreamParameters *parameters, PaUtilHostApiRepresentation *hostApi, StreamDirection mode ) { + PaError result = paNoError; int maxChans; - + const PaAlsaDeviceInfo *deviceInfo = NULL; assert( parameters ); - if( streamInfo ) + if( parameters->device != paUseHostApiSpecificDeviceSpecification ) { - if( streamInfo->size != sizeof (PaAlsaStreamInfo) || streamInfo->version != 1 ) - return paIncompatibleHostApiSpecificStreamInfo; - if( parameters->device != paUseHostApiSpecificDeviceSpecification ) - return paInvalidDevice; + assert( parameters->device < hostApi->info.deviceCount ); + PA_UNLESS( parameters->hostApiSpecificStreamInfo == NULL, paBadIODeviceCombination ); + deviceInfo = GetDeviceInfo( hostApi, parameters->device ); } - if( parameters->device == paUseHostApiSpecificDeviceSpecification ) + else { - if( streamInfo ) - return paNoError; /* Skip further checking */ + const PaAlsaStreamInfo *streamInfo = parameters->hostApiSpecificStreamInfo; + + PA_UNLESS( parameters->device == paUseHostApiSpecificDeviceSpecification, paInvalidDevice ); + PA_UNLESS( streamInfo->size == sizeof (PaAlsaStreamInfo) && streamInfo->version == 1, + paIncompatibleHostApiSpecificStreamInfo ); - return paInvalidDevice; + return paNoError; /* Skip further checking */ } - maxChans = (io == streamIn ? deviceInfo->commonDeviceInfo.maxInputChannels : + assert( deviceInfo ); + assert( parameters->hostApiSpecificStreamInfo == NULL ); + maxChans = (StreamDirection_In == mode ? deviceInfo->commonDeviceInfo.maxInputChannels : deviceInfo->commonDeviceInfo.maxOutputChannels); - if( parameters->channelCount > maxChans ) - { - return paInvalidChannelCount; - } + PA_UNLESS( parameters->channelCount <= maxChans, paInvalidChannelCount ); - return paNoError; +error: + return result; } - /* Given an open stream, what sample formats are available? */ static PaSampleFormat GetAvailableFormats( snd_pcm_t *pcm ) @@ -935,7 +1033,6 @@ static PaSampleFormat GetAvailableFormats( snd_pcm_t *pcm ) return available; } - static snd_pcm_format_t Pa2AlsaFormat( PaSampleFormat paFormat ) { switch( paFormat ) @@ -963,22 +1060,25 @@ static snd_pcm_format_t Pa2AlsaFormat( PaSampleFormat paFormat ) } } -/* \brief Open an ALSA pcm handle +/** Open an ALSA pcm handle. * * The device to be open can be specified in a custom PaAlsaStreamInfo struct, or it will be a device number. In case of a * device number, it maybe specified through an env variable (PA_ALSA_PLUGHW) that we should open the corresponding plugin * device. */ -static PaError AlsaOpen(snd_pcm_t **pcm, const PaAlsaDeviceInfo *deviceInfo, const PaAlsaStreamInfo - *streamInfo, snd_pcm_stream_t streamType ) +static PaError AlsaOpen( const PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *params, StreamDirection + streamDir, snd_pcm_t **pcm ) { PaError result = paNoError; int ret; const char *deviceName = alloca( 50 ); + const PaAlsaDeviceInfo *deviceInfo = NULL; + PaAlsaStreamInfo *streamInfo = (PaAlsaStreamInfo *)params->hostApiSpecificStreamInfo; if( !streamInfo ) { int usePlug = 0; + deviceInfo = GetDeviceInfo( hostApi, params->device ); /* If device name starts with hw: and PA_ALSA_PLUGHW is 1, we open the plughw device instead */ if( !strncmp( "hw:", deviceInfo->alsaName, 3 ) && getenv( "PA_ALSA_PLUGHW" ) ) @@ -991,12 +1091,13 @@ static PaError AlsaOpen(snd_pcm_t **pcm, const PaAlsaDeviceInfo *deviceInfo, con else deviceName = streamInfo->deviceString; - if( (ret = snd_pcm_open( pcm, deviceName, streamType, SND_PCM_NONBLOCK )) < 0 ) + if( (ret = snd_pcm_open( pcm, deviceName, streamDir == StreamDirection_In ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK )) < 0 ) { *pcm = NULL; /* Not to be closed */ - ENSURE( ret, ret == -EBUSY ? paDeviceUnavailable : paBadIODeviceCombination ); + ENSURE_( ret, ret == -EBUSY ? paDeviceUnavailable : paBadIODeviceCombination ); } - ENSURE( snd_pcm_nonblock( *pcm, 0 ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_nonblock( *pcm, 0 ), paUnanticipatedHostError ); end: return result; @@ -1005,26 +1106,49 @@ error: goto end; } -static PaError TestParameters( const PaStreamParameters *parameters, const PaAlsaDeviceInfo *deviceInfo, const PaAlsaStreamInfo - *streamInfo, double sampleRate, snd_pcm_stream_t streamType ) +static PaError TestParameters( const PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *parameters, + double sampleRate, StreamDirection streamDir ) { PaError result = paNoError; snd_pcm_t *pcm = NULL; PaSampleFormat availableFormats; - PaSampleFormat paFormat; - snd_pcm_hw_params_t *params; - snd_pcm_hw_params_alloca( ¶ms ); + /* We are able to adapt to a number of channels less than what the device supports */ + unsigned int numHostChannels; + PaSampleFormat hostFormat; + snd_pcm_hw_params_t *hwParams; + snd_pcm_hw_params_alloca( &hwParams ); + + if( !parameters->hostApiSpecificStreamInfo ) + { + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, parameters->device ); + numHostChannels = PA_MAX( parameters->channelCount, StreamDirection_In == streamDir ? + devInfo->minInputChannels : devInfo->minOutputChannels ); + } + else + numHostChannels = parameters->channelCount; + + PA_ENSURE( AlsaOpen( hostApi, parameters, streamDir, &pcm ) ); - PA_ENSURE( AlsaOpen( &pcm, deviceInfo, streamInfo, streamType ) ); + snd_pcm_hw_params_any( pcm, hwParams ); - snd_pcm_hw_params_any( pcm, params ); + if( SetApproximateSampleRate( pcm, hwParams, sampleRate ) < 0 ) + { + result = paInvalidSampleRate; + goto error; + } - ENSURE( SetApproximateSampleRate( pcm, params, sampleRate ), paInvalidSampleRate ); - ENSURE( snd_pcm_hw_params_set_channels( pcm, params, parameters->channelCount ), paInvalidChannelCount ); + if( snd_pcm_hw_params_set_channels( pcm, hwParams, numHostChannels ) < 0 ) + { + result = paInvalidChannelCount; + goto error; + } /* See if we can find a best possible match */ availableFormats = GetAvailableFormats( pcm ); - PA_ENSURE( paFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, parameters->sampleFormat ) ); + PA_ENSURE( hostFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, parameters->sampleFormat ) ); + ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, Pa2AlsaFormat( hostFormat ) ), paUnanticipatedHostError ); + + ENSURE_( snd_pcm_hw_params( pcm, hwParams ), paUnanticipatedHostError ); end: if( pcm ) @@ -1035,7 +1159,6 @@ error: goto end; } - static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, @@ -1044,20 +1167,10 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, int inputChannelCount = 0, outputChannelCount = 0; PaSampleFormat inputSampleFormat, outputSampleFormat; PaError result = paFormatIsSupported; - const PaAlsaDeviceInfo *inputDeviceInfo = NULL, *outputDeviceInfo = NULL; - const PaAlsaStreamInfo *inputStreamInfo = NULL, *outputStreamInfo = NULL; if( inputParameters ) { - if( inputParameters->device != paUseHostApiSpecificDeviceSpecification ) - { - assert( inputParameters->device < hostApi->info.deviceCount ); - inputDeviceInfo = (PaAlsaDeviceInfo *)hostApi->deviceInfos[ inputParameters->device ]; - } - else - inputStreamInfo = inputParameters->hostApiSpecificStreamInfo; - - PA_ENSURE( ValidateParameters( inputParameters, inputDeviceInfo, streamIn, inputStreamInfo ) ); + PA_ENSURE( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) ); inputChannelCount = inputParameters->channelCount; inputSampleFormat = inputParameters->sampleFormat; @@ -1065,63 +1178,86 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, if( outputParameters ) { - if( outputParameters->device != paUseHostApiSpecificDeviceSpecification ) - { - assert( outputParameters->device < hostApi->info.deviceCount ); - outputDeviceInfo = (PaAlsaDeviceInfo *)hostApi->deviceInfos[ outputParameters->device ]; - } - else - outputStreamInfo = outputParameters->hostApiSpecificStreamInfo; - - PA_ENSURE( ValidateParameters( outputParameters, outputDeviceInfo, streamOut, outputStreamInfo ) ); + PA_ENSURE( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ) ); outputChannelCount = outputParameters->channelCount; outputSampleFormat = outputParameters->sampleFormat; } - /* - IMPLEMENT ME: - - - if a full duplex stream is requested, check that the combination - of input and output parameters is supported if necessary + if( inputChannelCount ) + { + if( (result = TestParameters( hostApi, inputParameters, sampleRate, StreamDirection_In )) + != paNoError ) + goto error; + } + if ( outputChannelCount ) + { + if( (result = TestParameters( hostApi, outputParameters, sampleRate, StreamDirection_Out )) + != paNoError ) + goto error; + } - - check that the device supports sampleRate + return paFormatIsSupported; - Because the buffer adapter handles conversion between all standard - sample formats, the following checks are only required if paCustomFormat - is implemented, or under some other unusual conditions. +error: + return result; +} - - check that input device can support inputSampleFormat, or that - we have the capability to convert from outputSampleFormat to - a native format +static PaError PaAlsaStreamComponent_Initialize( PaAlsaStreamComponent *self, PaAlsaHostApiRepresentation *alsaApi, + const PaStreamParameters *params, StreamDirection streamDir, int callbackMode ) +{ + PaError result = paNoError; + PaSampleFormat userSampleFormat = params->sampleFormat, hostSampleFormat; + assert( params->channelCount > 0 ); - - check that output device can support outputSampleFormat, or that - we have the capability to convert from outputSampleFormat to - a native format - */ + /* Make sure things have an initial value */ + memset( self, 0, sizeof (PaAlsaStreamComponent) ); - if( inputChannelCount ) + if( NULL == params->hostApiSpecificStreamInfo ) { - PA_ENSURE( TestParameters( inputParameters, inputDeviceInfo, inputStreamInfo, sampleRate, SND_PCM_STREAM_CAPTURE ) ); + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( &alsaApi->commonHostApiRep, params->device ); + self->numHostChannels = PA_MAX( params->channelCount, StreamDirection_In == streamDir ? devInfo->minInputChannels + : devInfo->minOutputChannels ); } - - if ( outputChannelCount ) + else { - PA_ENSURE( TestParameters( outputParameters, outputDeviceInfo, outputStreamInfo, sampleRate, SND_PCM_STREAM_PLAYBACK ) ); + /* We're blissfully unaware of the minimum channelCount */ + self->numHostChannels = params->channelCount; } - return paFormatIsSupported; + PA_ENSURE( AlsaOpen( &alsaApi->commonHostApiRep, params, streamDir, &self->pcm ) ); + self->nfds = snd_pcm_poll_descriptors_count( self->pcm ); + hostSampleFormat = PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( self->pcm ), userSampleFormat ); + + self->hostSampleFormat = hostSampleFormat; + self->nativeFormat = Pa2AlsaFormat( hostSampleFormat ); + self->hostInterleaved = self->userInterleaved = !(userSampleFormat & paNonInterleaved); + self->numUserChannels = params->channelCount; + self->streamDir = streamDir; + + if( !callbackMode && !self->userInterleaved ) + { + /* Pre-allocate non-interleaved user provided buffers */ + PA_UNLESS( self->userBuffers = PaUtil_AllocateMemory( sizeof (void *) * self->numUserChannels ), + paInsufficientMemory ); + } error: return result; } +static void PaAlsaStreamComponent_Terminate( PaAlsaStreamComponent *self ) +{ + snd_pcm_close( self->pcm ); + if( self->userBuffers ) + PaUtil_FreeMemory( self->userBuffers ); +} -/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ - -static PaError ConfigureStream( snd_pcm_t *pcm, int channels, int *interleaved, double *sampleRate, - PaSampleFormat paFormat, unsigned long framesPerBuffer, snd_pcm_uframes_t - *bufferSize, PaTime *latency, int primeBuffers, int callbackMode ) +/** Configure the associated ALSA pcm. + * + */ +static PaError PaAlsaStreamComponent_Configure( PaAlsaStreamComponent *self, const PaStreamParameters *params, unsigned long + framesPerHostBuffer, int primeBuffers, int callbackMode, double *sampleRate, PaTime *returnedLatency ) { /* int numPeriods; @@ -1129,34 +1265,44 @@ static PaError ConfigureStream( snd_pcm_t *pcm, int channels, int *interleaved, if( getenv("PA_NUMPERIODS") != NULL ) numPeriods = atoi( getenv("PA_NUMPERIODS") ); else - numPeriods = ( (*latency * sampleRate) / *framesPerBuffer ) + 1; + numPeriods = ( (latency * sampleRate) / *framesPerBuffer ) + 1; - PA_DEBUG(( "latency: %f, rate: %f, framesPerBuffer: %d\n", *latency, sampleRate, *framesPerBuffer )); + PA_DEBUG(( "latency: %f, rate: %f, framesPerBuffer: %d\n", latency, sampleRate, *framesPerBuffer )); if( numPeriods <= 1 ) numPeriods = 2; */ - /* configuration consists of setting all of ALSA's parameters. + /* Configuration consists of setting all of ALSA's parameters. * These parameters come in two flavors: hardware parameters * and software paramters. Hardware parameters will affect * the way the device is initialized, software parameters - * affect the way ALSA interacts with me, the user-level client. */ + * affect the way ALSA interacts with me, the user-level client. + */ snd_pcm_hw_params_t *hwParams; snd_pcm_sw_params_t *swParams; PaError result = paNoError; snd_pcm_access_t accessMode, alternateAccessMode; - snd_pcm_format_t alsaFormat; - unsigned int numPeriods; + unsigned int numPeriods, minPeriods = 2; + int dir = 0; + snd_pcm_t *pcm = self->pcm; + PaTime latency = params->suggestedLatency; + double sr = *sampleRate; + *returnedLatency = -1.; snd_pcm_hw_params_alloca( &hwParams ); snd_pcm_sw_params_alloca( &swParams ); + self->framesPerBuffer = framesPerHostBuffer; + /* ... fill up the configuration space with all possibile * combinations of parameters this device will accept */ - ENSURE( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + + ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError ); - if( *interleaved ) + if( self->userInterleaved ) { accessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; alternateAccessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; @@ -1168,75 +1314,70 @@ static PaError ConfigureStream( snd_pcm_t *pcm, int channels, int *interleaved, } /* If requested access mode fails, try alternate mode */ - if( snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 ) { - ENSURE( snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode ), paUnanticipatedHostError ); - *interleaved = !(*interleaved); /* Flip mode */ + if( snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 ) + { + ENSURE_( snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode ), paUnanticipatedHostError ); + /* Flip mode */ + self->hostInterleaved = !self->userInterleaved; } - /* set the format based on what the user selected */ - alsaFormat = Pa2AlsaFormat( paFormat ); - assert( alsaFormat != SND_PCM_FORMAT_UNKNOWN ); - ENSURE( snd_pcm_hw_params_set_format( pcm, hwParams, alsaFormat ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, self->nativeFormat ), paUnanticipatedHostError ); - /* ... set the sample rate */ - ENSURE( SetApproximateSampleRate( pcm, hwParams, *sampleRate ), paInvalidSampleRate ); - ENSURE( GetExactSampleRate( hwParams, sampleRate ), paUnanticipatedHostError ); + ENSURE_( SetApproximateSampleRate( pcm, hwParams, sr ), paInvalidSampleRate ); + ENSURE_( GetExactSampleRate( hwParams, &sr ), paUnanticipatedHostError ); + /* reject if there's no sample rate within 1% of the one requested */ + if( (fabs( *sampleRate - sr ) / *sampleRate) > 0.01 ) + { + PA_DEBUG(("%s: Wanted %f, closest sample rate was %d\n", __FUNCTION__, sampleRate, sr )); + PA_ENSURE( paInvalidSampleRate ); + } - /* ... set the number of channels */ - ENSURE( snd_pcm_hw_params_set_channels( pcm, hwParams, channels ), paInvalidChannelCount ); + ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParams, self->numHostChannels ), paInvalidChannelCount ); - /* Set buffer size */ - ENSURE( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_set_period_size( pcm, hwParams, framesPerBuffer, 0 ), paUnanticipatedHostError ); + /* I think there should be at least 2 periods (even though ALSA doesn't appear to enforce this) */ + dir = 0; + ENSURE_( snd_pcm_hw_params_set_periods_min( pcm, hwParams, &minPeriods, &dir ), paUnanticipatedHostError ); + dir = 0; + ENSURE_( snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &self->framesPerBuffer, &dir ), paUnanticipatedHostError ); /* Find an acceptable number of periods */ - numPeriods = (*latency * *sampleRate) / framesPerBuffer + 1; - numPeriods = MAX( numPeriods, 2 ); /* Should be at least 2 periods I think? */ - ENSURE( snd_pcm_hw_params_set_periods_near( pcm, hwParams, &numPeriods, NULL ), paUnanticipatedHostError ); - - /* - PA_DEBUG(( "numperiods: %d\n", numPeriods )); - if( snd_pcm_hw_params_set_periods ( pcm, hwParams, numPeriods, 0 ) < 0 ) - { - int i; - for( i = numPeriods; i >= 2; i-- ) - { - if( snd_pcm_hw_params_set_periods( pcm, hwParams, i, 0 ) >= 0 ) - { - PA_DEBUG(( "settled on %d periods\n", i )); - break; - } - } - } - */ + numPeriods = (latency * sr) / self->framesPerBuffer + 1; + dir = 0; + ENSURE_( snd_pcm_hw_params_set_periods_near( pcm, hwParams, &numPeriods, &dir ), paUnanticipatedHostError ); + /* Minimum of periods should already be 2 */ + PA_UNLESS( numPeriods >= 2, paInternalError ); /* Set the parameters! */ - ENSURE( snd_pcm_hw_params( pcm, hwParams ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_get_buffer_size( hwParams, bufferSize ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_buffer_size( hwParams, &self->bufferSize ), paUnanticipatedHostError ); /* Latency in seconds, one period is not counted as latency */ - *latency = (numPeriods - 1) * framesPerBuffer / *sampleRate; + latency = (numPeriods - 1) * self->framesPerBuffer / sr; /* Now software parameters... */ - ENSURE( snd_pcm_sw_params_current( pcm, swParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_current( pcm, swParams ), paUnanticipatedHostError ); - ENSURE( snd_pcm_sw_params_set_start_threshold( pcm, swParams, framesPerBuffer ), paUnanticipatedHostError ); - ENSURE( snd_pcm_sw_params_set_stop_threshold( pcm, swParams, *bufferSize ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_start_threshold( pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_stop_threshold( pcm, swParams, self->bufferSize ), paUnanticipatedHostError ); /* Silence buffer in the case of underrun */ - if( !primeBuffers ) + if( !primeBuffers ) /* XXX: Make sense? */ { - ENSURE( snd_pcm_sw_params_set_silence_threshold( pcm, swParams, 0 ), paUnanticipatedHostError ); - ENSURE( snd_pcm_sw_params_set_silence_size( pcm, swParams, INT_MAX ), paUnanticipatedHostError ); + snd_pcm_uframes_t boundary; + ENSURE_( snd_pcm_sw_params_get_boundary( swParams, &boundary ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_silence_threshold( pcm, swParams, 0 ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_silence_size( pcm, swParams, boundary ), paUnanticipatedHostError ); } - ENSURE( snd_pcm_sw_params_set_avail_min( pcm, swParams, framesPerBuffer ), paUnanticipatedHostError ); - ENSURE( snd_pcm_sw_params_set_xfer_align( pcm, swParams, 1 ), paUnanticipatedHostError ); - ENSURE( snd_pcm_sw_params_set_tstamp_mode( pcm, swParams, SND_PCM_TSTAMP_MMAP ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_avail_min( pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_xfer_align( pcm, swParams, 1 ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params_set_tstamp_mode( pcm, swParams, SND_PCM_TSTAMP_MMAP ), paUnanticipatedHostError ); /* Set the parameters! */ - ENSURE( snd_pcm_sw_params( pcm, swParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_sw_params( pcm, swParams ), paUnanticipatedHostError ); + + *sampleRate = sr; + *returnedLatency = latency; end: return result; @@ -1245,347 +1386,434 @@ error: goto end; /* No particular action */ } -static void InitializeStream( PaAlsaStream *stream, int callback, PaStreamFlags streamFlags ) -{ - assert( stream ); - - stream->pcm_capture = NULL; - stream->pcm_playback = NULL; - stream->callback_finished = 0; - stream->callback_mode = callback; - stream->capture_nfds = 0; - stream->playback_nfds = 0; - stream->pfds = NULL; - stream->pollTimeout = 0; - stream->pcmsSynced = 0; - stream->callbackAbort = 0; - stream->isActive = 0; - stream->startThreshold = 0; - pthread_mutex_init( &stream->stateMtx, NULL ); - pthread_mutex_init( &stream->startMtx, NULL ); - pthread_cond_init( &stream->startCond, NULL ); - stream->neverDropInput = streamFlags & paNeverDropInput; - stream->underrun = stream->overrun = 0.0; - - InitializeThreading( &stream->threading, &stream->cpuLoadMeasurer ); -} - -static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, - PaStream** s, - const PaStreamParameters *inputParameters, - const PaStreamParameters *outputParameters, - double sampleRate, - unsigned long framesPerBuffer, - PaStreamFlags streamFlags, - PaStreamCallback *callback, - void *userData ) +static PaError PaAlsaStream_Initialize( PaAlsaStream *self, PaAlsaHostApiRepresentation *alsaApi, const PaStreamParameters *inParams, + const PaStreamParameters *outParams, double sampleRate, unsigned long framesPerUserBuffer, PaStreamCallback callback, + PaStreamFlags streamFlags, void *userData ) { PaError result = paNoError; - PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; - const PaAlsaDeviceInfo *inputDeviceInfo = 0, *outputDeviceInfo = 0; - PaAlsaStream *stream = NULL; - PaSampleFormat hostInputSampleFormat = 0, hostOutputSampleFormat = 0; - PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0; - int numInputChannels = 0, numOutputChannels = 0; - unsigned long framesPerHostBuffer = framesPerBuffer; - PaAlsaStreamInfo *inputStreamInfo = NULL, *outputStreamInfo = NULL; - PaTime inputLatency, outputLatency; + assert( self ); - if( inputParameters ) - { - if( inputParameters->device != paUseHostApiSpecificDeviceSpecification ) - { - assert( inputParameters->device < hostApi->info.deviceCount ); - inputDeviceInfo = (PaAlsaDeviceInfo*)hostApi->deviceInfos[ inputParameters->device ]; - } - else - inputStreamInfo = inputParameters->hostApiSpecificStreamInfo; + memset( self, 0, sizeof (PaAlsaStream) ); - PA_ENSURE( ValidateParameters( inputParameters, inputDeviceInfo, streamIn, inputStreamInfo ) ); - - numInputChannels = inputParameters->channelCount; - inputSampleFormat = inputParameters->sampleFormat; + if( NULL != callback ) + { + PaUtil_InitializeStreamRepresentation( &self->streamRepresentation, + &alsaApi->callbackStreamInterface, + callback, userData ); + self->callbackMode = 1; } - if( outputParameters ) + else { - if( outputParameters->device != paUseHostApiSpecificDeviceSpecification ) - { - assert( outputParameters->device < hostApi->info.deviceCount ); - outputDeviceInfo = (PaAlsaDeviceInfo*)hostApi->deviceInfos[ outputParameters->device ]; - } - else - outputStreamInfo = outputParameters->hostApiSpecificStreamInfo; + PaUtil_InitializeStreamRepresentation( &self->streamRepresentation, + &alsaApi->blockingStreamInterface, + NULL, userData ); + } + + self->framesPerUserBuffer = framesPerUserBuffer; + self->neverDropInput = streamFlags & paNeverDropInput; + /* XXX: Ignore paPrimeOutputBuffersUsingStreamCallback untill buffer priming is fully supported in pa_process.c */ + /* + if( outParams & streamFlags & paPrimeOutputBuffersUsingStreamCallback ) + self->primeBuffers = 1; + */ + memset( &self->capture, 0, sizeof (PaAlsaStreamComponent) ); + memset( &self->playback, 0, sizeof (PaAlsaStreamComponent) ); + if( inParams ) + PA_ENSURE( PaAlsaStreamComponent_Initialize( &self->capture, alsaApi, inParams, StreamDirection_In, NULL != callback ) ); + if( outParams ) + PA_ENSURE( PaAlsaStreamComponent_Initialize( &self->playback, alsaApi, outParams, StreamDirection_Out, NULL != callback ) ); - PA_ENSURE( ValidateParameters( outputParameters, outputDeviceInfo, streamOut, outputStreamInfo ) ); + assert( self->capture.nfds || self->playback.nfds ); - numOutputChannels = outputParameters->channelCount; - outputSampleFormat = outputParameters->sampleFormat; - } + PA_UNLESS( self->pfds = (struct pollfd*)PaUtil_AllocateMemory( (self->capture.nfds + + self->playback.nfds) * sizeof (struct pollfd) ), paInsufficientMemory ); - /* validate platform specific flags */ - if( (streamFlags & paPlatformSpecificFlags) != 0 ) - return paInvalidFlag; /* unexpected platform specific flag */ + PaUtil_InitializeCpuLoadMeasurer( &self->cpuLoadMeasurer, sampleRate ); + InitializeThreading( &self->threading, &self->cpuLoadMeasurer ); + ASSERT_CALL_( pthread_mutex_init( &self->stateMtx, NULL ), 0 ); + ASSERT_CALL_( pthread_mutex_init( &self->startMtx, NULL ), 0 ); + ASSERT_CALL_( pthread_cond_init( &self->startCond, NULL ), 0 ); - /* allocate and do basic initialization of the stream structure */ +error: + return result; +} - UNLESS( stream = (PaAlsaStream*)PaUtil_AllocateMemory( sizeof(PaAlsaStream) ), paInsufficientMemory ); - InitializeStream( stream, (int) callback, streamFlags ); /* Initialize structure */ +/** Free resources associated with stream, and eventually stream itself. + * + * Frees allocated memory, and terminates individual StreamComponents. + */ +static void PaAlsaStream_Terminate( PaAlsaStream *self ) +{ + assert( self ); - if( callback ) + if( self->capture.pcm ) { - PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - &alsaHostApi->callbackStreamInterface, - callback, userData ); + PaAlsaStreamComponent_Terminate( &self->capture ); } - else + if( self->playback.pcm ) { - PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - &alsaHostApi->blockingStreamInterface, - callback, userData ); + PaAlsaStreamComponent_Terminate( &self->playback ); } - PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + PaUtil_FreeMemory( self->pfds ); + ASSERT_CALL_( pthread_mutex_destroy( &self->stateMtx ), 0 ); + ASSERT_CALL_( pthread_mutex_destroy( &self->startMtx ), 0 ); + ASSERT_CALL_( pthread_cond_destroy( &self->startCond ), 0 ); - /* open the devices now, so we can obtain info about the available formats */ + PaUtil_FreeMemory( self ); +} - if( numInputChannels > 0 ) - { - PA_ENSURE( AlsaOpen( &stream->pcm_capture, inputDeviceInfo, inputStreamInfo, SND_PCM_STREAM_CAPTURE ) ); +/** Calculate polling timeout + * + * @param frames Time to wait + * @return Polling timeout in milliseconds + */ +static int CalculatePollTimeout( const PaAlsaStream *stream, unsigned long frames ) +{ + assert( stream->streamRepresentation.streamInfo.sampleRate > 0.0 ); + /* Period in msecs, rounded up */ + return (int)ceil( 1000 * frames / stream->streamRepresentation.streamInfo.sampleRate ); +} - stream->capture_nfds = snd_pcm_poll_descriptors_count( stream->pcm_capture ); +/** Set up ALSA stream parameters. + * + */ +static PaError PaAlsaStream_Configure( PaAlsaStream *self, const PaStreamParameters *inParams, const PaStreamParameters + *outParams, double sampleRate, unsigned long framesPerHostBuffer, double *inputLatency, double *outputLatency, + unsigned long *maxHostBufferSize ) +{ + PaError result = paNoError; + double realSr = sampleRate; - hostInputSampleFormat = - PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( stream->pcm_capture ), - inputSampleFormat ); - } + if( self->capture.pcm ) + PA_ENSURE( PaAlsaStreamComponent_Configure( &self->capture, inParams, framesPerHostBuffer, self->primeBuffers, + self->callbackMode, &realSr, inputLatency ) ); + if( self->playback.pcm ) + PA_ENSURE( PaAlsaStreamComponent_Configure( &self->playback, outParams, framesPerHostBuffer, self->primeBuffers, + self->callbackMode, &realSr, outputLatency ) ); - if( numOutputChannels > 0 ) + /* Should be exact now */ + self->streamRepresentation.streamInfo.sampleRate = realSr; + + /* this will cause the two streams to automatically start/stop/prepare in sync. + * We only need to execute these operations on one of the pair. + * A: We don't want to do this on a blocking stream. + */ + if( self->callbackMode && self->capture.pcm && self->playback.pcm ) { - PA_ENSURE( AlsaOpen( &stream->pcm_playback, outputDeviceInfo, outputStreamInfo, SND_PCM_STREAM_PLAYBACK ) ); + int err = snd_pcm_link( self->capture.pcm, self->playback.pcm ); + if( err >= 0 ) + self->pcmsSynced = 1; + else + PA_DEBUG(( "%s: Unable to sync pcms: %s\n", __FUNCTION__, snd_strerror( err ) )); + } - stream->playback_nfds = snd_pcm_poll_descriptors_count( stream->pcm_playback ); + /* Frames per host buffer for the stream is set as a compromise between the two directions */ + framesPerHostBuffer = PA_MIN( self->capture.pcm ? self->capture.framesPerBuffer : ULONG_MAX, + self->playback.pcm ? self->playback.framesPerBuffer : ULONG_MAX ); + self->pollTimeout = CalculatePollTimeout( self, framesPerHostBuffer ); /* Period in msecs, rounded up */ - hostOutputSampleFormat = - PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( stream->pcm_playback ), - outputSampleFormat ); - stream->playbackNativeFormat = Pa2AlsaFormat( hostOutputSampleFormat ); - } + *maxHostBufferSize = PA_MAX( self->capture.pcm ? self->capture.bufferSize : 0, + self->playback.pcm ? self->playback.bufferSize : 0 ); - /* If the number of frames per buffer is unspecified, we have to come up with - * one. This is both a blessing and a curse: a blessing because we can optimize - * the number to best meet the requirements, but a curse because that's really - * hard to do well. For this reason we also support an interface where the user - * specifies these by setting environment variables. */ - if( framesPerBuffer == paFramesPerBufferUnspecified ) + /* Time before watchdog unthrottles realtime thread == 1/4 of period time in msecs */ + self->threading.throttledSleepTime = (unsigned long) (framesPerHostBuffer / sampleRate / 4 * 1000); + + if( self->callbackMode ) { - if( getenv("PA_ALSA_PERIODSIZE") != NULL ) - framesPerHostBuffer = atoi( getenv("PA_ALSA_PERIODSIZE") ); - else + /* If the user expects a certain number of frames per callback we will either have to rely on block adaption + * (framesPerHostBuffer is not an integer multiple of framesPerBuffer) or we can simply align the number + * of host buffer frames with what the user specified */ + if( self->framesPerUserBuffer != paFramesPerBufferUnspecified ) { - /* We need to determine how many frames per host buffer to use. Our - * goals are to provide the best possible performance, but also to - * most closely honor the requested latency settings. Therefore this - * decision is based on: - * - * - the period sizes that playback and/or capture support. The - * host buffer size has to be one of these. - * - the number of periods that playback and/or capture support. - * - * We want to make period_size*(num_periods-1) to be as close as possible - * to latency*rate for both playback and capture. - * - * This is one of those blocks of code that will just take a lot of - * refinement to be any good. - */ + /* self->alignFrames = 1; */ - if( stream->pcm_capture && stream->pcm_playback ) - { - snd_pcm_uframes_t desiredLatency, e; - snd_pcm_uframes_t minPeriodSize, minPlayback, minCapture, maxPeriodSize, maxPlayback, maxCapture, - optimalPeriodSize, periodSize; - int dir; - - snd_pcm_t *pcm; - snd_pcm_hw_params_t *hwParamsPlayback, *hwParamsCapture; - - snd_pcm_hw_params_alloca( &hwParamsPlayback ); - snd_pcm_hw_params_alloca( &hwParamsCapture ); - - /* Come up with a common desired latency */ - pcm = stream->pcm_playback; - snd_pcm_hw_params_any( pcm, hwParamsPlayback ); - ENSURE( SetApproximateSampleRate( pcm, hwParamsPlayback, sampleRate ), paBadIODeviceCombination ); - ENSURE( snd_pcm_hw_params_set_channels( pcm, hwParamsPlayback, outputParameters->channelCount ), - paBadIODeviceCombination ); - - ENSURE( snd_pcm_hw_params_set_period_size_integer( pcm, hwParamsPlayback ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_set_periods_integer( pcm, hwParamsPlayback ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_get_period_size_min( hwParamsPlayback, &minPlayback, &dir ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_get_period_size_max( hwParamsPlayback, &maxPlayback, &dir ), paUnanticipatedHostError ); - - pcm = stream->pcm_capture; - ENSURE( snd_pcm_hw_params_any( pcm, hwParamsCapture ), paUnanticipatedHostError ); - ENSURE( SetApproximateSampleRate( pcm, hwParamsCapture, sampleRate ), paBadIODeviceCombination ); - ENSURE( snd_pcm_hw_params_set_channels( pcm, hwParamsCapture, inputParameters->channelCount ), - paBadIODeviceCombination ); - - ENSURE( snd_pcm_hw_params_set_period_size_integer( pcm, hwParamsCapture ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_set_periods_integer( pcm, hwParamsCapture ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_get_period_size_min( hwParamsCapture, &minCapture, &dir ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_get_period_size_max( hwParamsCapture, &maxCapture, &dir ), paUnanticipatedHostError ); - - minPeriodSize = MAX( minPlayback, minCapture ); - maxPeriodSize = MIN( maxPlayback, maxCapture ); - - desiredLatency = (snd_pcm_uframes_t) (MIN( outputParameters->suggestedLatency, inputParameters->suggestedLatency ) - * sampleRate); - /* Clamp desiredLatency */ - { - snd_pcm_uframes_t tmp, maxBufferSize = ULONG_MAX; - ENSURE( snd_pcm_hw_params_get_buffer_size_max( hwParamsPlayback, &tmp ), paUnanticipatedHostError ); - maxBufferSize = MIN( maxBufferSize, tmp ); - ENSURE( snd_pcm_hw_params_get_buffer_size_max( hwParamsCapture, &tmp ), paUnanticipatedHostError ); - maxBufferSize = MIN( maxBufferSize, tmp ); + /* Unless the ratio between number of host and user buffer frames is an integer we will have to rely + * on block adaption */ + /* + if( framesPerHostBuffer % framesPerBuffer != 0 || (self->capture.pcm && self->playback.pcm && + self->capture.framesPerBuffer != self->playback.framesPerBuffer) ) + self->useBlockAdaption = 1; + else + self->alignFrames = 1; + */ + } + } - desiredLatency = MIN( desiredLatency, maxBufferSize ); - } +error: + return result; +} - /* Find the closest power of 2 */ - e = ilogb( minPeriodSize ); - if( minPeriodSize & (minPeriodSize - 1) ) - e += 1; +/* We need to determine how many frames per host buffer to use. Our + * goals are to provide the best possible performance, but also to + * most closely honor the requested latency settings. Therefore this + * decision is based on: + * + * - the period sizes that playback and/or capture support. The + * host buffer size has to be one of these. + * - the number of periods that playback and/or capture support. + * + * We want to make period_size*(num_periods-1) to be as close as possible + * to latency*rate for both playback and capture. + * + * This is one of those blocks of code that will just take a lot of + * refinement to be any good. + * + * In the full-duplex case it is possible that the routine was unable + * to find a number of frames per buffer acceptable to both devices + * TODO: Implement an algorithm to find the value closest to acceptance + * by both devices, to minimize difference between period sizes? + */ +static PaError DetermineFramesPerBuffer( const PaAlsaStream *stream, double sampleRate, const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, unsigned long *determinedFrames, const PaUtilHostApiRepresentation *hostApi ) +{ + PaError result = paNoError; + unsigned long framesPerBuffer = 0; + int numHostInputChannels = 0, numHostOutputChannels = 0; - periodSize = (snd_pcm_uframes_t) pow( 2, e ); - while( periodSize <= maxPeriodSize ) - { - if( snd_pcm_hw_params_test_period_size( stream->pcm_playback, hwParamsPlayback, periodSize, 0 ) >= 0 && - snd_pcm_hw_params_test_period_size( stream->pcm_capture, hwParamsCapture, periodSize, 0 ) >= 0 ) - break; /* Ok! */ + /* XXX: Clean this up */ + if( stream->capture.pcm ) + { + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, inputParameters->device ); + numHostInputChannels = PA_MAX( inputParameters->channelCount, devInfo->minInputChannels ); + } + if( stream->playback.pcm ) + { + const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, outputParameters->device ); + numHostOutputChannels = PA_MAX( outputParameters->channelCount, devInfo->minOutputChannels ); + } - periodSize *= 2; - } + if( stream->capture.pcm && stream->playback.pcm ) + { + snd_pcm_uframes_t desiredLatency, e; + snd_pcm_uframes_t minPeriodSize, minPlayback, minCapture, maxPeriodSize, maxPlayback, maxCapture, + optimalPeriodSize, periodSize; + int dir = 0; + unsigned int minPeriods = 2; - /* 4 periods considered optimal */ - optimalPeriodSize = MAX( desiredLatency / 4, minPeriodSize ); - optimalPeriodSize = MIN( optimalPeriodSize, maxPeriodSize ); + snd_pcm_t *pcm; + snd_pcm_hw_params_t *hwParamsPlayback, *hwParamsCapture; + + snd_pcm_hw_params_alloca( &hwParamsPlayback ); + snd_pcm_hw_params_alloca( &hwParamsCapture ); + + /* Come up with a common desired latency */ + pcm = stream->playback.pcm; + snd_pcm_hw_params_any( pcm, hwParamsPlayback ); + ENSURE_( SetApproximateSampleRate( pcm, hwParamsPlayback, sampleRate ), paInvalidSampleRate ); + ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParamsPlayback, numHostOutputChannels ), + paBadIODeviceCombination ); + + ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParamsPlayback ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParamsPlayback ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_min( pcm, hwParamsPlayback, &minPeriods, &dir ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParamsPlayback, &minPlayback, &dir ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParamsPlayback, &maxPlayback, &dir ), paUnanticipatedHostError ); + + pcm = stream->capture.pcm; + ENSURE_( snd_pcm_hw_params_any( pcm, hwParamsCapture ), paUnanticipatedHostError ); + ENSURE_( SetApproximateSampleRate( pcm, hwParamsCapture, sampleRate ), paBadIODeviceCombination ); + ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParamsCapture, numHostInputChannels ), + paBadIODeviceCombination ); + + ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParamsCapture ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParamsCapture ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_min( pcm, hwParamsCapture, &minPeriods, &dir ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParamsCapture, &minCapture, &dir ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParamsCapture, &maxCapture, &dir ), paUnanticipatedHostError ); + + minPeriodSize = PA_MAX( minPlayback, minCapture ); + maxPeriodSize = PA_MIN( maxPlayback, maxCapture ); + + desiredLatency = (snd_pcm_uframes_t) (PA_MIN( outputParameters->suggestedLatency, inputParameters->suggestedLatency ) + * sampleRate); + /* Clamp desiredLatency */ + { + snd_pcm_uframes_t tmp, maxBufferSize = ULONG_MAX; + ENSURE_( snd_pcm_hw_params_get_buffer_size_max( hwParamsPlayback, &maxBufferSize ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_buffer_size_max( hwParamsCapture, &tmp ), paUnanticipatedHostError ); + maxBufferSize = PA_MIN( maxBufferSize, tmp ); - /* Find the closest power of 2 */ - e = ilogb( optimalPeriodSize ); - if( optimalPeriodSize & (optimalPeriodSize - 1) ) - e += 1; + desiredLatency = PA_MIN( desiredLatency, maxBufferSize ); + } - optimalPeriodSize = (snd_pcm_uframes_t) pow( 2, e ); - while( optimalPeriodSize >= periodSize ) - { - pcm = stream->pcm_playback; - if( snd_pcm_hw_params_test_period_size( pcm, hwParamsPlayback, optimalPeriodSize, 0 ) < 0 ) - continue; + /* Find the closest power of 2 */ + e = ilogb( minPeriodSize ); + if( minPeriodSize & (minPeriodSize - 1) ) + e += 1; + periodSize = (snd_pcm_uframes_t) pow( 2, e ); - pcm = stream->pcm_capture; - if( snd_pcm_hw_params_test_period_size( pcm, hwParamsCapture, optimalPeriodSize, 0 ) >= 0 ) - break; + while( periodSize <= maxPeriodSize ) + { + if( snd_pcm_hw_params_test_period_size( stream->playback.pcm, hwParamsPlayback, periodSize, 0 ) >= 0 && + snd_pcm_hw_params_test_period_size( stream->capture.pcm, hwParamsCapture, periodSize, 0 ) >= 0 ) + break; /* Ok! */ - optimalPeriodSize /= 2; - } + periodSize *= 2; + } - if( optimalPeriodSize > periodSize ) - periodSize = optimalPeriodSize; + /* 4 periods considered optimal */ + optimalPeriodSize = PA_MAX( desiredLatency / 4, minPeriodSize ); + optimalPeriodSize = PA_MIN( optimalPeriodSize, maxPeriodSize ); - if( periodSize <= maxPeriodSize ) - { - /* Looks good */ - framesPerHostBuffer = periodSize; - } - else /* XXX: Some more descriptive error code might be appropriate */ - PA_ENSURE( paBadIODeviceCombination ); - } - else - { - /* half-duplex is a slightly simpler case */ - unsigned long bufferSize, channels; - snd_pcm_t *pcm; - snd_pcm_hw_params_t *hwParams; + /* Find the closest power of 2 */ + e = ilogb( optimalPeriodSize ); + if( optimalPeriodSize & (optimalPeriodSize - 1) ) + e += 1; + optimalPeriodSize = (snd_pcm_uframes_t) pow( 2, e ); - snd_pcm_hw_params_alloca( &hwParams ); + while( optimalPeriodSize >= periodSize ) + { + pcm = stream->playback.pcm; + if( snd_pcm_hw_params_test_period_size( pcm, hwParamsPlayback, optimalPeriodSize, 0 ) < 0 ) + continue; - if( stream->pcm_capture ) - { - pcm = stream->pcm_capture; - bufferSize = inputParameters->suggestedLatency * sampleRate; - channels = inputParameters->channelCount; - } - else - { - pcm = stream->pcm_playback; - bufferSize = outputParameters->suggestedLatency * sampleRate; - channels = outputParameters->channelCount; - } + pcm = stream->capture.pcm; + if( snd_pcm_hw_params_test_period_size( pcm, hwParamsCapture, optimalPeriodSize, 0 ) >= 0 ) + break; - ENSURE( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); - ENSURE( SetApproximateSampleRate( pcm, hwParams, sampleRate ), paBadIODeviceCombination ); - ENSURE( snd_pcm_hw_params_set_channels( pcm, hwParams, channels ), paBadIODeviceCombination ); + optimalPeriodSize /= 2; + } - ENSURE( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError ); + if( optimalPeriodSize > periodSize ) + periodSize = optimalPeriodSize; - /* Using 5 as a base number of periods, we try to approximate the suggested latency (+1 period), - finding a combination of period/buffer size which best fits these constraints */ - framesPerHostBuffer = bufferSize / 4; - bufferSize += framesPerHostBuffer; /* One period doesn't count as latency */ - ENSURE( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &bufferSize ), paUnanticipatedHostError ); - ENSURE( snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &framesPerHostBuffer, NULL ), paUnanticipatedHostError ); - } + if( periodSize <= maxPeriodSize ) + { + /* Looks good */ + framesPerBuffer = periodSize; + } + else + { + /* Unable to find a common period size, oh well */ + optimalPeriodSize = PA_MAX( desiredLatency / 4, minPeriodSize ); + optimalPeriodSize = PA_MIN( optimalPeriodSize, maxPeriodSize ); + + /* ConfigureStream should find individual period sizes acceptable for each device */ + framesPerBuffer = optimalPeriodSize; + /* PA_ENSURE( paBadIODeviceCombination ); */ } } - else + else /* half-duplex is a slightly simpler case */ { - framesPerHostBuffer = framesPerBuffer; + unsigned long bufferSize, channels; + snd_pcm_t *pcm; + snd_pcm_hw_params_t *hwParams; + + snd_pcm_hw_params_alloca( &hwParams ); + + if( stream->capture.pcm ) + { + pcm = stream->capture.pcm; + bufferSize = inputParameters->suggestedLatency * sampleRate; + channels = numHostInputChannels; + } + else + { + pcm = stream->playback.pcm; + bufferSize = outputParameters->suggestedLatency * sampleRate; + channels = numHostOutputChannels; + } + + ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( SetApproximateSampleRate( pcm, hwParams, sampleRate ), paInvalidSampleRate ); + ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParams, channels ), paBadIODeviceCombination ); + + ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError ); + + /* Using 5 as a base number of periods, we try to approximate the suggested latency (+1 period), + finding a combination of period/buffer size which best fits these constraints */ + framesPerBuffer = bufferSize / 4; + bufferSize += framesPerBuffer; /* One period doesn't count as latency */ + ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &bufferSize ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &framesPerBuffer, NULL ), paUnanticipatedHostError ); } - /* Will fill in correct values later */ - stream->streamRepresentation.streamInfo.inputLatency = 0.; - stream->streamRepresentation.streamInfo.outputLatency = 0.; + PA_UNLESS( framesPerBuffer != 0, paInternalError ); + *determinedFrames = framesPerBuffer; - if( numInputChannels > 0 ) +error: + return result; +} + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *callback, + void *userData ) +{ + PaError result = paNoError; + PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; + PaAlsaStream *stream = NULL; + PaSampleFormat hostInputSampleFormat = 0, hostOutputSampleFormat = 0; + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0; + int numInputChannels = 0, numOutputChannels = 0; + PaTime inputLatency, outputLatency; + unsigned long framesPerHostBuffer; + PaUtilHostBufferSizeMode hostBufferSizeMode = paUtilBoundedHostBufferSize; + unsigned long maxHostBufferSize; /* Upper bound of host buffer size */ + + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; + + if( inputParameters ) { - int interleaved = !(inputSampleFormat & paNonInterleaved); - PaSampleFormat plain_format = hostInputSampleFormat & ~paNonInterleaved; + PA_ENSURE( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) ); - inputLatency = inputParameters->suggestedLatency; /* Real latency in seconds returned from ConfigureStream */ - PA_ENSURE( ConfigureStream( stream->pcm_capture, numInputChannels, &interleaved, - &sampleRate, plain_format, framesPerHostBuffer, &stream->captureBufferSize, - &inputLatency, 0, stream->callback_mode ) ); + numInputChannels = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + if( outputParameters ) + { + PA_ENSURE( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ) ); - stream->capture_interleaved = interleaved; + numOutputChannels = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; } - if( numOutputChannels > 0 ) + /* XXX: Why do we support this anyway? */ + if( framesPerBuffer == paFramesPerBufferUnspecified && getenv( "PA_ALSA_PERIODSIZE" ) != NULL ) { - int interleaved = !(outputSampleFormat & paNonInterleaved); - PaSampleFormat plain_format = hostOutputSampleFormat & ~paNonInterleaved; - int primeBuffers = streamFlags & paPrimeOutputBuffersUsingStreamCallback; + PA_DEBUG(( "%s: Getting framesPerBuffer from environment\n", __FUNCTION__ )); + framesPerBuffer = atoi( getenv("PA_ALSA_PERIODSIZE") ); + } + framesPerHostBuffer = framesPerBuffer; - outputLatency = outputParameters->suggestedLatency; /* Real latency in seconds returned from ConfigureStream */ + PA_UNLESS( stream = (PaAlsaStream*)PaUtil_AllocateMemory( sizeof(PaAlsaStream) ), paInsufficientMemory ); + PA_ENSURE( PaAlsaStream_Initialize( stream, alsaHostApi, inputParameters, outputParameters, sampleRate, + framesPerBuffer, callback, streamFlags, userData ) ); + + /* If the number of frames per buffer is unspecified, we have to come up with + * one. This is both a blessing and a curse: a blessing because we can optimize + * the number to best meet the requirements, but a curse because that's really + * hard to do well. For this reason we also support an interface where the user + * specifies these by setting environment variables. */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + PA_ENSURE( DetermineFramesPerBuffer( stream, sampleRate, inputParameters, outputParameters, &framesPerHostBuffer, + hostApi ) ); + } - PA_ENSURE( ConfigureStream( stream->pcm_playback, numOutputChannels, &interleaved, - &sampleRate, plain_format, framesPerHostBuffer, &stream->playbackBufferSize, - &outputLatency, primeBuffers, stream->callback_mode ) ); + PA_ENSURE( PaAlsaStream_Configure( stream, inputParameters, outputParameters, sampleRate, framesPerHostBuffer, + &inputLatency, &outputLatency, &maxHostBufferSize ) ); + hostInputSampleFormat = stream->capture.hostSampleFormat; + hostOutputSampleFormat = stream->playback.hostSampleFormat; - /* If the user wants to prime the buffer before stream start, the start threshold will equal buffer size */ - if( primeBuffers ) - stream->startThreshold = stream->playbackBufferSize; - stream->playback_interleaved = interleaved; + if( framesPerHostBuffer != framesPerBuffer ) + { + PA_DEBUG(( "%s: Number of frames per user and host buffer differs\n", __FUNCTION__ )); } - /* Should be exact now */ - stream->streamRepresentation.streamInfo.sampleRate = sampleRate; - /* Time before watchdog unthrottles realtime thread == 1/4 of period time in msecs */ - stream->threading.throttledSleepTime = (unsigned long) (framesPerHostBuffer / sampleRate / 4 * 1000); PA_ENSURE( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, numInputChannels, inputSampleFormat, hostInputSampleFormat, numOutputChannels, outputSampleFormat, hostOutputSampleFormat, - sampleRate, streamFlags, framesPerBuffer, framesPerHostBuffer, - paUtilFixedHostBufferSize, callback, userData ) ); + sampleRate, streamFlags, framesPerBuffer, maxHostBufferSize, + hostBufferSizeMode, callback, userData ) ); /* Ok, buffer processor is initialized, now we can deduce it's latency */ if( numInputChannels > 0 ) @@ -1595,38 +1823,17 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->streamRepresentation.streamInfo.outputLatency = outputLatency + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor ); - /* this will cause the two streams to automatically start/stop/prepare in sync. - * We only need to execute these operations on one of the pair. - * A: We don't want to do this on a blocking stream. - */ - if( stream->callback_mode && stream->pcm_capture && stream->pcm_playback && - snd_pcm_link( stream->pcm_capture, stream->pcm_playback ) >= 0 ) - stream->pcmsSynced = 1; - - UNLESS( stream->pfds = (struct pollfd*)PaUtil_AllocateMemory( (stream->capture_nfds + - stream->playback_nfds) * sizeof(struct pollfd) ), paInsufficientMemory ); - - stream->frames_per_period = framesPerHostBuffer; - stream->capture_channels = numInputChannels; - stream->playback_channels = numOutputChannels; - stream->pollTimeout = (int) ceil( 1000 * stream->frames_per_period/sampleRate ); /* Period in msecs, rounded up */ - *s = (PaStream*)stream; return result; error: if( stream ) - CleanUpStream( stream ); + PaAlsaStream_Terminate( stream ); return result; } - -/* - When CloseStream() is called, the multi-api layer ensures that - the stream has already been stopped or aborted. -*/ static PaError CloseStream( PaStream* s ) { PaError result = paNoError; @@ -1635,7 +1842,7 @@ static PaError CloseStream( PaStream* s ) PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); - CleanUpStream( stream ); + PaAlsaStream_Terminate( stream ); return result; } @@ -1643,14 +1850,14 @@ static PaError CloseStream( PaStream* s ) static void SilenceBuffer( PaAlsaStream *stream ) { const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t frames = snd_pcm_avail_update( stream->pcm_playback ); + snd_pcm_uframes_t frames = (snd_pcm_uframes_t)snd_pcm_avail_update( stream->playback.pcm ), offset; - snd_pcm_mmap_begin( stream->pcm_playback, &areas, &stream->playback_offset, &frames ); - snd_pcm_areas_silence( areas, stream->playback_offset, stream->playback_channels, frames, stream->playbackNativeFormat ); - snd_pcm_mmap_commit( stream->pcm_playback, stream->playback_offset, frames ); + snd_pcm_mmap_begin( stream->playback.pcm, &areas, &offset, &frames ); + snd_pcm_areas_silence( areas, offset, stream->playback.numHostChannels, frames, stream->playback.nativeFormat ); + snd_pcm_mmap_commit( stream->playback.pcm, offset, frames ); } -/*! \brief Start/prepare pcm(s) for streaming +/** Start/prepare pcm(s) for streaming. * * Depending on wether the stream is in callback or blocking mode, we will respectively start or simply * prepare the playback pcm. If the buffer has _not_ been primed, we will in callback mode prepare and @@ -1665,26 +1872,26 @@ static PaError AlsaStart( PaAlsaStream *stream, int priming ) { PaError result = paNoError; - if( stream->pcm_playback ) + if( stream->playback.pcm ) { - if( stream->callback_mode ) + if( stream->callbackMode ) { - /* We're not priming buffer, so prepare and silence */ if( !priming ) { - ENSURE( snd_pcm_prepare( stream->pcm_playback ), paUnanticipatedHostError ); + /* Buffer isn't primed, so prepare and silence */ + ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); SilenceBuffer( stream ); } - ENSURE( snd_pcm_start( stream->pcm_playback ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); } else - ENSURE( snd_pcm_prepare( stream->pcm_playback ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); } - if( stream->pcm_capture && !stream->pcmsSynced ) + if( stream->capture.pcm && !stream->pcmsSynced ) { - ENSURE( snd_pcm_prepare( stream->pcm_capture ), paUnanticipatedHostError ); - /* We want to start capture for a blocking stream as well, since nothing will happen otherwise */ - ENSURE( snd_pcm_start( stream->pcm_capture ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError ); + /* For a blocking stream we want to start capture as well, since nothing will happen otherwise */ + ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError ); } end: @@ -1693,16 +1900,17 @@ error: goto end; } -/*! \brief Utility function for determining if pcms are in running state +/** Utility function for determining if pcms are in running state. + * */ static int IsRunning( PaAlsaStream *stream ) { int result = 0; - pthread_mutex_lock( &stream->stateMtx ); /* Synchronize access to pcm state */ - if( stream->pcm_capture ) + ASSERT_CALL_( pthread_mutex_lock( &stream->stateMtx ), 0 ); /* Synchronize access to pcm state */ + if( stream->capture.pcm ) { - snd_pcm_state_t capture_state = snd_pcm_state( stream->pcm_capture ); + snd_pcm_state_t capture_state = snd_pcm_state( stream->capture.pcm ); if( capture_state == SND_PCM_STATE_RUNNING || capture_state == SND_PCM_STATE_XRUN || capture_state == SND_PCM_STATE_DRAINING ) @@ -1712,9 +1920,9 @@ static int IsRunning( PaAlsaStream *stream ) } } - if( stream->pcm_playback ) + if( stream->playback.pcm ) { - snd_pcm_state_t playback_state = snd_pcm_state( stream->pcm_playback ); + snd_pcm_state_t playback_state = snd_pcm_state( stream->playback.pcm ); if( playback_state == SND_PCM_STATE_RUNNING || playback_state == SND_PCM_STATE_XRUN || playback_state == SND_PCM_STATE_DRAINING ) @@ -1725,7 +1933,7 @@ static int IsRunning( PaAlsaStream *stream ) } end: - pthread_mutex_unlock( &stream->stateMtx ); + ASSERT_CALL_( pthread_mutex_unlock( &stream->stateMtx ), 0 ); return result; } @@ -1742,7 +1950,7 @@ static PaError StartStream( PaStream *s ) /* Set now, so we can test for activity further down */ stream->isActive = 1; - if( stream->callback_mode ) + if( stream->callbackMode ) { int res = 0; PaTime pt = PaUtil_GetTime(); @@ -1758,13 +1966,16 @@ static PaError StartStream( PaStream *s ) /* Since we'll be holding a lock on the startMtx (when not waiting on the condition), IsRunning won't be checking * stream state at the same time as the callback thread affects it. We also check IsStreamActive, in the unlikely * case the callback thread exits in the meantime (the stream will be considered inactive after the thread exits) */ - UNLESS( !pthread_mutex_lock( &stream->startMtx ), paInternalError ); - while( !IsRunning( stream ) && res != ETIMEDOUT && IsStreamActive( s ) ) + ASSERT_CALL_( pthread_mutex_lock( &stream->startMtx ), 0 ); + + /* Due to possible spurious wakeups, we enclose in a loop */ + while( !IsRunning( stream ) && IsStreamActive( s ) && !res ) { res = pthread_cond_timedwait( &stream->startCond, &stream->startMtx, &ts ); - UNLESS( !res || res == ETIMEDOUT, paInternalError ); } - UNLESS( !pthread_mutex_unlock( &stream->startMtx ), paInternalError ); + ASSERT_CALL_( pthread_mutex_unlock( &stream->startMtx ), 0 ); + + PA_UNLESS( !res || res == ETIMEDOUT, paInternalError ); PA_DEBUG(( "%s: Waited for %g seconds for stream to start\n", __FUNCTION__, PaUtil_GetTime() - pt )); if( res == ETIMEDOUT ) @@ -1794,19 +2005,19 @@ static PaError AlsaStop( PaAlsaStream *stream, int abort ) if( abort ) { - if( stream->pcm_playback ) - ENSURE( snd_pcm_drop( stream->pcm_playback ), paUnanticipatedHostError ); - if( stream->pcm_capture && !stream->pcmsSynced ) - ENSURE( snd_pcm_drop( stream->pcm_capture ), paUnanticipatedHostError ); + if( stream->playback.pcm ) + ENSURE_( snd_pcm_drop( stream->playback.pcm ), paUnanticipatedHostError ); + if( stream->capture.pcm && !stream->pcmsSynced ) + ENSURE_( snd_pcm_drop( stream->capture.pcm ), paUnanticipatedHostError ); PA_DEBUG(( "Dropped frames\n" )); } else { - if( stream->pcm_playback ) - ENSURE( snd_pcm_drain( stream->pcm_playback ), paUnanticipatedHostError ); - if( stream->pcm_capture && !stream->pcmsSynced ) - ENSURE( snd_pcm_drain( stream->pcm_capture ), paUnanticipatedHostError ); + if( stream->playback.pcm ) + ENSURE_( snd_pcm_drain( stream->playback.pcm ), paUnanticipatedHostError ); + if( stream->capture.pcm && !stream->pcmsSynced ) + ENSURE_( snd_pcm_drain( stream->capture.pcm ), paUnanticipatedHostError ); } end: @@ -1815,7 +2026,7 @@ error: goto end; } -/*! \brief Stop or abort stream +/** Stop or abort stream. * * If a stream is in callback mode we will have to inspect wether the background thread has * finished, or we will have to take it out. In either case we join the thread before @@ -1831,17 +2042,23 @@ static PaError RealStop( PaAlsaStream *stream, int abort ) /* First deal with the callback thread, cancelling and/or joining * it if necessary */ - if( stream->callback_mode ) + if( stream->callbackMode ) { PaError threadRes, watchdogRes; stream->callbackAbort = abort; - PA_ENSURE( KillCallbackThread( &stream->threading, &threadRes, &watchdogRes ) ); + if( !abort ) + { + PA_DEBUG(( "Stopping callback\n" )); + stream->callbackStop = 1; + } + PA_ENSURE( KillCallbackThread( &stream->threading, !abort, &threadRes, &watchdogRes ) ); if( threadRes != paNoError ) PA_DEBUG(( "Callback thread returned: %d\n", threadRes )); if( watchdogRes != paNoError ) PA_DEBUG(( "Watchdog thread returned: %d\n", watchdogRes )); + stream->callbackStop = 0; /* The deed is done */ stream->callback_finished = 0; } else @@ -1868,19 +2085,16 @@ static PaError AbortStream( PaStream *s ) return RealStop( (PaAlsaStream * ) s, 1 ); } -/*! - * The stream is considered stopped before StartStream, or AFTER a call to Abort/StopStream (callback +/** The stream is considered stopped before StartStream, or AFTER a call to Abort/StopStream (callback * returning !paContinue is not considered) + * */ static PaError IsStreamStopped( PaStream *s ) { PaAlsaStream *stream = (PaAlsaStream *)s; - PaError res; /* callback_finished indicates we need to join callback thread (ie. in Abort/StopStream) */ - res = !IsStreamActive( s ) && !stream->callback_finished; - - return res; + return !IsStreamActive( s ) && !stream->callback_finished; } static PaError IsStreamActive( PaStream *s ) @@ -1900,25 +2114,22 @@ static PaTime GetStreamTime( PaStream *s ) /* TODO: what if we have both? does it really matter? */ /* TODO: if running in callback mode, this will mean - * libasound routines are being called form multiple threads. + * libasound routines are being called from multiple threads. * need to verify that libasound is thread-safe. */ - if( stream->pcm_capture ) + if( stream->capture.pcm ) { - snd_pcm_status( stream->pcm_capture, status ); + snd_pcm_status( stream->capture.pcm, status ); } - else if( stream->pcm_playback ) + else if( stream->playback.pcm ) { - snd_pcm_status( stream->pcm_playback, status ); + snd_pcm_status( stream->playback.pcm, status ); } snd_pcm_status_get_tstamp( status, ×tamp ); - PA_DEBUG(( "Time in secs: %d\n", timestamp.tv_sec )); - - return timestamp.tv_sec + (PaTime) timestamp.tv_usec/1000000; + return timestamp.tv_sec + (PaTime)timestamp.tv_usec / 1000000.0; } - static double GetStreamCpuLoad( PaStream* s ) { PaAlsaStream *stream = (PaAlsaStream*)s; @@ -1926,32 +2137,6 @@ static double GetStreamCpuLoad( PaStream* s ) return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); } -/*! - * \brief Free resources associated with stream, and eventually stream itself - * - * Frees allocated memory, and closes opened pcms. - */ -static void CleanUpStream( PaAlsaStream *stream ) -{ - assert( stream ); - - if( stream->pcm_capture ) - { - snd_pcm_close( stream->pcm_capture ); - } - if( stream->pcm_playback ) - { - snd_pcm_close( stream->pcm_playback ); - } - - PaUtil_FreeMemory( stream->pfds ); - pthread_mutex_destroy( &stream->stateMtx ); - pthread_mutex_destroy( &stream->startMtx ); - pthread_cond_destroy( &stream->startCond ); - - PaUtil_FreeMemory( stream ); -} - static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ) { unsigned long approx = (unsigned long) sampleRate; @@ -1988,7 +2173,6 @@ static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate return err; } - /* Utility functions for blocking/callback interfaces */ /* Atomic restart of stream (we don't want the intermediate state visible) */ @@ -1996,23 +2180,21 @@ static PaError AlsaRestart( PaAlsaStream *stream ) { PaError result = paNoError; - PA_DEBUG(( "Restarting audio\n" )); - - UNLESS( !pthread_mutex_lock( &stream->stateMtx ), paInternalError ); + ASSERT_CALL_( pthread_mutex_lock( &stream->stateMtx ), 0 ); PA_ENSURE( AlsaStop( stream, 0 ) ); PA_ENSURE( AlsaStart( stream, 0 ) ); - PA_DEBUG(( "Restarted audio\n" )); + PA_DEBUG(( "%s: Restarted audio\n", __FUNCTION__ )); -end: - if( pthread_mutex_unlock( &stream->stateMtx ) != 0 ) - result = paInternalError; - return result; error: - goto end; + ASSERT_CALL_( pthread_mutex_unlock( &stream->stateMtx ), 0 ); + return result; } -static PaError HandleXrun( PaAlsaStream *stream ) +/** Recover from xrun state. + * + */ +static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self ) { PaError result = paNoError; snd_pcm_status_t *st; @@ -2021,26 +2203,26 @@ static PaError HandleXrun( PaAlsaStream *stream ) snd_pcm_status_alloca( &st ); - if( stream->pcm_playback ) + if( self->playback.pcm ) { - snd_pcm_status( stream->pcm_playback, st ); + snd_pcm_status( self->playback.pcm, st ); if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN ) { snd_pcm_status_get_trigger_tstamp( st, &t ); - stream->underrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + self->underrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); } } - if( stream->pcm_capture ) + if( self->capture.pcm ) { - snd_pcm_status( stream->pcm_capture, st ); + snd_pcm_status( self->capture.pcm, st ); if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN ) { snd_pcm_status_get_trigger_tstamp( st, &t ); - stream->overrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + self->overrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); } } - PA_ENSURE( AlsaRestart( stream ) ); + PA_ENSURE( AlsaRestart( self ) ); end: return result; @@ -2048,535 +2230,835 @@ error: goto end; } -/*! \brief Poll on I/O filedescriptors - - Poll till we've determined there's data for read or write. In the full-duplex case, - we don't want to hang around forever waiting for either input or output frames, so - whenever we have a timed out filedescriptor we check if we're nearing under/overrun - for the other pcm (critical limit set at one buffer). If so, we exit the waiting state, - and go on with what we got. - */ -static PaError Wait( PaAlsaStream *stream, snd_pcm_uframes_t *frames ) +/** Decide if we should continue polling for specified direction, eventually adjust the poll timeout. + * + */ +static PaError ContinuePoll( const PaAlsaStream *stream, StreamDirection streamDir, int *pollTimeout, int *continuePoll ) { PaError result = paNoError; - int pollPlayback = 0, pollCapture = 0; - snd_pcm_sframes_t captureAvail = INT_MAX, playbackAvail = INT_MAX, commonAvail; - int xrun = 0; /* Under/overrun? */ - - assert( stream && frames && stream->pollTimeout > 0 ); - - if( stream->pcm_capture ) - pollCapture = 1; + snd_pcm_sframes_t delay, margin; + int err; + const PaAlsaStreamComponent *component = NULL, *otherComponent = NULL; - if( stream->pcm_playback ) - pollPlayback = 1; + *continuePoll = 1; - while( pollPlayback || pollCapture ) + if( StreamDirection_In == streamDir ) { - unsigned short revents; - int totalFds = 0; - int pfdOfs = 0; + component = &stream->capture; + otherComponent = &stream->playback; + } + else + { + component = &stream->playback; + otherComponent = &stream->capture; + } - /* get the fds, packing all applicable fds into a single array, - * so we can check them all with a single poll() call - */ - if( stream->pcm_capture && pollCapture ) + /* ALSA docs say that negative delay should indicate xrun, but in my experience snd_pcm_delay returns -EPIPE */ + if( (err = snd_pcm_delay( otherComponent->pcm, &delay )) < 0 ) + { + if( err == -EPIPE ) { - snd_pcm_poll_descriptors( stream->pcm_capture, stream->pfds, stream->capture_nfds ); - pfdOfs += stream->capture_nfds; - totalFds += stream->capture_nfds; + /* Xrun */ + *continuePoll = 0; + goto error; } - if( stream->pcm_playback && pollPlayback ) + + ENSURE_( err, paUnanticipatedHostError ); + } + + if( StreamDirection_Out == streamDir ) + { + /* Number of eligible frames before capture overrun */ + delay = otherComponent->bufferSize - delay; + } + margin = delay - otherComponent->framesPerBuffer / 2; + + if( margin < 0 ) + { + PA_DEBUG(( "%s: Stopping poll for %s\n", __FUNCTION__, StreamDirection_In == streamDir ? "capture" : "playback" )); + *continuePoll = 0; + } + else if( margin < otherComponent->framesPerBuffer ) + { + *pollTimeout = CalculatePollTimeout( stream, margin ); + PA_DEBUG(( "%s: Trying to poll again for %s frames, pollTimeout: %d\n", + __FUNCTION__, StreamDirection_In == streamDir ? "capture" : "playback", *pollTimeout )); + } + +error: + return result; +} + +/* Callback interface */ + +static void OnExit( void *data ) +{ + PaAlsaStream *stream = (PaAlsaStream *) data; + + assert( data ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + stream->callback_finished = 1; /* Let the outside world know stream was stopped in callback */ + AlsaStop( stream, stream->callbackAbort ); + stream->callbackAbort = 0; /* Clear state */ + + PA_DEBUG(( "OnExit: Stoppage\n" )); + + /* Eventually notify user all buffers have played */ + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + stream->isActive = 0; +} + +static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *timeInfo ) +{ + snd_pcm_status_t *capture_status, *playback_status; + snd_timestamp_t capture_timestamp, playback_timestamp; + PaTime capture_time = 0., playback_time = 0.; + + snd_pcm_status_alloca( &capture_status ); + snd_pcm_status_alloca( &playback_status ); + + if( stream->capture.pcm ) + { + snd_pcm_sframes_t capture_delay; + + snd_pcm_status( stream->capture.pcm, capture_status ); + snd_pcm_status_get_tstamp( capture_status, &capture_timestamp ); + + capture_time = capture_timestamp.tv_sec + + ((PaTime)capture_timestamp.tv_usec / 1000000.0); + timeInfo->currentTime = capture_time; + + capture_delay = snd_pcm_status_get_delay( capture_status ); + timeInfo->inputBufferAdcTime = timeInfo->currentTime - + (PaTime)capture_delay / stream->streamRepresentation.streamInfo.sampleRate; + } + if( stream->playback.pcm ) + { + snd_pcm_sframes_t playback_delay; + + snd_pcm_status( stream->playback.pcm, playback_status ); + snd_pcm_status_get_tstamp( playback_status, &playback_timestamp ); + + playback_time = playback_timestamp.tv_sec + + ((PaTime)playback_timestamp.tv_usec / 1000000.0); + + if( stream->capture.pcm ) /* Full duplex */ { - snd_pcm_poll_descriptors( stream->pcm_playback, stream->pfds + pfdOfs, stream->playback_nfds ); - totalFds += stream->playback_nfds; + /* Hmm, we have both a playback and a capture timestamp. + * Hopefully they are the same... */ + if( fabs( capture_time - playback_time ) > 0.01 ) + PA_DEBUG(("Capture time and playback time differ by %f\n", fabs(capture_time-playback_time))); } + else + timeInfo->currentTime = playback_time; - /* if the main thread has requested that we stop, do so now */ - pthread_testcancel(); + playback_delay = snd_pcm_status_get_delay( playback_status ); + timeInfo->outputBufferDacTime = timeInfo->currentTime + + (PaTime)playback_delay / stream->streamRepresentation.streamInfo.sampleRate; + } +} - /* now poll on the combination of playback and capture fds. */ - if( poll( stream->pfds, totalFds, stream->pollTimeout ) < 0 ) - { - if( errno == EINTR ) { - continue; - } +/** Called after buffer processing is finished. + * + * A number of mmapped frames is committed, it is possible that an xrun has occurred in the meantime. + * + * @param numFrames The number of frames that has been processed + * @param xrun Return whether an xrun has occurred + */ +static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self, unsigned long numFrames, int *xrun ) +{ + PaError result = paNoError; + int res; - PA_ENSURE( paInternalError ); - } + /* @concern FullDuplex It is possible that only one direction is marked ready after polling, and processed + * afterwards + */ + if( !self->ready ) + goto end; - pthread_testcancel(); + res = snd_pcm_mmap_commit( self->pcm, self->offset, numFrames ); + if( res == -EPIPE || res == -ESTRPIPE ) + { + *xrun = 1; + } + else + { + ENSURE_( res, paUnanticipatedHostError ); + } - /* check the return status of our pfds */ - if( pollCapture ) - { - ENSURE( snd_pcm_poll_descriptors_revents( stream->pcm_capture, stream->pfds, - stream->capture_nfds, &revents ), paUnanticipatedHostError ); - if( revents ) - { - if( revents & POLLERR ) - xrun = 1; +end: +error: + return result; +} - pollCapture = 0; - } - else if( stream->pcm_playback ) /* Timed out, go on with playback? */ +/* Extract buffer from channel area */ +static unsigned char *ExtractAddress( const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset ) +{ + return (unsigned char *) area->addr + (area->first + offset * area->step) / 8; +} + +/** Do necessary adaption between user and host channels. + * + @concern ChannelAdaption Adapting between user and host channels can involve silencing unused channels and + duplicating mono information if host outputs come in pairs. + */ +static PaError PaAlsaStreamComponent_DoChannelAdaption( PaAlsaStreamComponent *self, PaUtilBufferProcessor *bp, int numFrames ) +{ + PaError result = paNoError; + unsigned char *p; + int i; + int unusedChans = self->numHostChannels - self->numUserChannels; + unsigned char *src, *dst; + int convertMono = (self->numHostChannels % 2) == 0 && (self->numUserChannels % 2) != 0; + + assert( StreamDirection_Out == self->streamDir ); + + if( self->hostInterleaved ) + { + int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); + unsigned char *buffer = ExtractAddress( self->channelAreas, self->offset ); + + /* Start after the last user channel */ + p = buffer + self->numUserChannels * swidth; + + if( convertMono ) + { + /* Convert the last user channel into stereo pair */ + src = buffer + (self->numUserChannels - 1) * swidth; + for( i = 0; i < numFrames; ++i ) { - /* Less than 1 written period left? */ - if( (int)snd_pcm_avail_update( stream->pcm_playback ) >= (int)(stream->playbackBufferSize - stream->frames_per_period) ) - { - PA_DEBUG(( "Capture timed out, pollTimeOut: %d\n", stream->pollTimeout )); - pollCapture = 0; /* Go on without me .. *sob* ... */ - } + dst = src + swidth; + memcpy( dst, src, swidth ); + src += self->numHostChannels * swidth; } + + /* Don't touch the channel we just wrote to */ + p += swidth; + --unusedChans; } - if( pollPlayback ) + if( unusedChans > 0 ) { - unsigned short revents; - ENSURE( snd_pcm_poll_descriptors_revents( stream->pcm_playback, stream->pfds + - pfdOfs, stream->playback_nfds, &revents ), paUnanticipatedHostError ); - if( revents ) + /* Silence unused output channels */ + for( i = 0; i < numFrames; ++i ) { - if( revents & POLLERR ) - xrun = 1; - - pollPlayback = 0; - } - else if( stream->pcm_capture ) /* Timed out, go on with capture? */ - { - /* Less than 1 empty period left? */ - if( (int)snd_pcm_avail_update( stream->pcm_capture ) >= (int)(stream->captureBufferSize - stream->frames_per_period) ) - { - PA_DEBUG(( "Playback timed out\n" )); - pollPlayback = 0; /* Go on without me, son .. */ - } + memset( p, 0, swidth * unusedChans ); + p += self->numHostChannels * swidth; } } } + else + { + /* We extract the last user channel */ + if( convertMono ) + { + ENSURE_( snd_pcm_area_copy( self->channelAreas + self->numUserChannels, self->offset, self->channelAreas + + (self->numUserChannels - 1), self->offset, numFrames, self->nativeFormat ), paUnanticipatedHostError ); + --unusedChans; + } + if( unusedChans > 0 ) + { + snd_pcm_areas_silence( self->channelAreas + (self->numHostChannels - unusedChans), self->offset, unusedChans, numFrames, + self->nativeFormat ); + } + } - /* we have now established that there are buffers ready to be - * operated on. Now determine how many frames are available. - */ - if( stream->pcm_capture ) +error: + return result; +} + +static PaError PaAlsaStream_EndProcessing( PaAlsaStream *self, unsigned long numFrames, int *xrunOccurred ) +{ + PaError result = paNoError; + int xrun = 0; + + if( self->capture.pcm ) { - if( (captureAvail = snd_pcm_avail_update( stream->pcm_capture )) == -EPIPE ) - xrun = 1; - else - ENSURE( captureAvail, paUnanticipatedHostError ); + PA_ENSURE( PaAlsaStreamComponent_EndProcessing( &self->capture, numFrames, &xrun ) ); + } + if( self->playback.pcm ) + { + if( self->playback.numHostChannels > self->playback.numUserChannels ) + PA_ENSURE( PaAlsaStreamComponent_DoChannelAdaption( &self->playback, &self->bufferProcessor, numFrames ) ); + PA_ENSURE( PaAlsaStreamComponent_EndProcessing( &self->playback, numFrames, &xrun ) ); + } - if( !captureAvail ) - PA_DEBUG(( "Wait: captureAvail: 0\n" )); +error: + *xrunOccurred = xrun; + return result; +} - captureAvail = captureAvail == 0 ? INT_MAX : captureAvail; /* Disregard if zero */ +/** Update the number of available frames. + * + */ +static PaError PaAlsaStreamComponent_GetAvailableFrames( PaAlsaStreamComponent *self, unsigned long *numFrames, int *xrunOccurred ) +{ + PaError result = paNoError; + snd_pcm_sframes_t framesAvail = snd_pcm_avail_update( self->pcm ); + *xrunOccurred = 0; + + if( -EPIPE == framesAvail ) + { + *xrunOccurred = 1; + framesAvail = 0; } + else + ENSURE_( framesAvail, paUnanticipatedHostError ); + + *numFrames = framesAvail; + +error: + return result; +} + +/** Fill in pollfd objects. + */ +static PaError PaAlsaStreamComponent_BeginPolling( PaAlsaStreamComponent *self, struct pollfd *pfds ) +{ + PaError result = paNoError; + int ret = snd_pcm_poll_descriptors( self->pcm, pfds, self->nfds ); + assert( ret == self->nfds ); + + self->ready = 0; + + return result; +} + +/** Examine results from poll(). + * + * @param pfds pollfds to inspect + * @param shouldPoll Should we continue to poll + * @param xrun Has an xrun occurred + */ +static PaError PaAlsaStreamComponent_EndPolling( PaAlsaStreamComponent *self, struct pollfd *pfds, int *shouldPoll, int *xrun ) +{ + PaError result = paNoError; + unsigned short revents; - if( stream->pcm_playback ) + ENSURE_( snd_pcm_poll_descriptors_revents( self->pcm, pfds, self->nfds, &revents ), paUnanticipatedHostError ); + if( revents != 0 ) { - if( (playbackAvail = snd_pcm_avail_update( stream->pcm_playback )) == -EPIPE ) - xrun = 1; + if( revents & POLLERR ) + { + *xrun = 1; + } else - ENSURE( playbackAvail, paUnanticipatedHostError ); - - if( !playbackAvail ) - PA_DEBUG(( "Wait: playbackAvail: 0\n" )); + self->ready = 1; - playbackAvail = playbackAvail == 0 ? INT_MAX : playbackAvail; /* Disregard if zero */ + *shouldPoll = 0; } - - commonAvail = MIN( captureAvail, playbackAvail ); - commonAvail -= commonAvail % stream->frames_per_period; +error: + return result; +} - if( xrun ) +/** Return the number of available frames for this stream. + * + * @concern FullDuplex The minimum available for the two directions is calculated, it might be desirable to ignore + * one direction however (not marked ready from poll), so this is controlled by queryCapture and queryPlayback. + * + * @param queryCapture Check available for capture + * @param queryPlayback Check available for playback + * @param available The returned number of frames + * @param xrunOccurred Return whether an xrun has occurred + */ +static PaError PaAlsaStream_GetAvailableFrames( PaAlsaStream *self, int queryCapture, int queryPlayback, unsigned long + *available, int *xrunOccurred ) +{ + PaError result = paNoError; + unsigned long captureFrames, playbackFrames; + *xrunOccurred = 0; + + assert( queryCapture || queryPlayback ); + + if( queryCapture ) + { + assert( self->capture.pcm ); + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &self->capture, &captureFrames, xrunOccurred ) ); + if( *xrunOccurred ) + goto end; + } + if( queryPlayback ) { - HandleXrun( stream ); - commonAvail = 0; /* Wait will be called again, to obtain the number of available frames */ + assert( self->playback.pcm ); + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &self->playback, &playbackFrames, xrunOccurred ) ); + if( *xrunOccurred ) + goto end; } - assert( commonAvail >= 0 ); - *frames = commonAvail; + if( queryCapture && queryPlayback ) + { + *available = PA_MIN( captureFrames, playbackFrames ); + } + else if( queryCapture ) + { + *available = captureFrames; + } + else + { + *available = playbackFrames; + } end: - return result; error: - goto end; -} - -/* Extract buffer from channel area */ -static unsigned char *ExtractAddress( const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset ) -{ - return (unsigned char *) area->addr + (area->first + offset * area->step) / 8; + return result; } -/*! \brief Get buffers from ALSA for read/write, and determine the amount of frames available. +/** Wait for and report available buffer space from ALSA. * - * Request (up to) requested number of frames from ALSA, for opened pcms. The number of frames returned - * will normally be the lowest availble (possibly aligned) of available capture and playback frames. - * Underflow/underflow complicates matters however; if we are out of capture frames we will go on with - * output, input overflow will either result in discarded frames or we will deliver them (paNeverDropInput). -*/ -static PaError SetUpBuffers( PaAlsaStream *stream, snd_pcm_uframes_t requested, int alignFrames, - snd_pcm_uframes_t *frames ) + * Unless ALSA reports a minimum of frames available for I/O, we poll the ALSA filedescriptors for more. + * Both of these operations can uncover xrun conditions. + * + * @concern Xruns Both polling and querying available frames can report an xrun condition. + * + * @param framesAvail Return the number of available frames + * @param xrunOccurred Return whether an xrun has occurred + */ +static PaError PaAlsaStream_WaitForFrames( PaAlsaStream *self, unsigned long *framesAvail, int *xrunOccurred ) { PaError result = paNoError; - int i; - snd_pcm_uframes_t captureFrames = requested, playbackFrames = requested, commonFrames; - const snd_pcm_channel_area_t *areas, *area; - unsigned char *buffer; + int pollPlayback = self->playback.pcm != NULL, pollCapture = self->capture.pcm != NULL; + int pollTimeout = self->pollTimeout; + int xrun = 0; - assert( stream && frames ); + assert( self ); + assert( framesAvail ); - if( stream->pcm_capture ) + if( !self->callbackMode ) { - ENSURE( snd_pcm_mmap_begin( stream->pcm_capture, &areas, &stream->capture_offset, &captureFrames ), - paUnanticipatedHostError ); + /* In blocking mode we will only wait if necessary */ + PA_ENSURE( PaAlsaStream_GetAvailableFrames( self, self->capture.pcm != NULL, self->playback.pcm != NULL, + framesAvail, &xrun ) ); + if( xrun ) + { + goto end; + } - if( stream->capture_interleaved ) + if( *framesAvail > 0 ) { - buffer = ExtractAddress( areas, stream->capture_offset ); - PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, - 0, /* starting at channel 0 */ - buffer, - 0 /* default numInputChannels */ - ); + /* Mark pcms ready from poll */ + if( self->capture.pcm ) + self->capture.ready = 1; + if( self->playback.pcm ) + self->playback.ready = 1; + + goto end; } - else - /* noninterleaved */ - for( i = 0; i < stream->capture_channels; ++i ) + } + + while( pollPlayback || pollCapture ) + { + int totalFds = 0; + struct pollfd *capturePfds = NULL, *playbackPfds = NULL; + + pthread_testcancel(); + + if( pollCapture ) + { + capturePfds = self->pfds; + PA_ENSURE( PaAlsaStreamComponent_BeginPolling( &self->capture, capturePfds ) ); + totalFds += self->capture.nfds; + } + if( pollPlayback ) + { + playbackPfds = self->pfds + (self->capture.pcm ? self->capture.nfds : 0); + PA_ENSURE( PaAlsaStreamComponent_BeginPolling( &self->playback, playbackPfds ) ); + totalFds += self->playback.nfds; + } + + if( poll( self->pfds, totalFds, pollTimeout ) < 0 ) + { + /* XXX: Depend on preprocessor condition? */ + if( errno == EINTR ) { /* gdb */ + continue; + } + + /* TODO: Add macro for checking system calls */ + PA_ENSURE( paInternalError ); + } + + /* check the return status of our pfds */ + if( pollCapture ) + { + PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->capture, capturePfds, &pollCapture, &xrun ) ); + } + if( pollPlayback ) + { + PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->playback, playbackPfds, &pollPlayback, &xrun ) ); + } + if( xrun ) + { + break; + } + + /* @concern FullDuplex If only one of two pcms is ready we may want to compromise between the two. + * If there is less than half a period's worth of samples left of frames in the other pcm's buffer we will + * stop polling. + */ + if( self->capture.pcm && self->playback.pcm ) + { + if( pollCapture && !pollPlayback ) + { + PA_ENSURE( ContinuePoll( self, StreamDirection_In, &pollTimeout, &pollCapture ) ); + } + else if( pollPlayback && !pollCapture ) { - area = &areas[i]; - buffer = ExtractAddress( area, stream->capture_offset ); - PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor, - i, - buffer ); + PA_ENSURE( ContinuePoll( self, StreamDirection_Out, &pollTimeout, &pollPlayback ) ); } + } } - if( stream->pcm_playback ) + if( !xrun ) { - ENSURE( snd_pcm_mmap_begin( stream->pcm_playback, &areas, &stream->playback_offset, &playbackFrames ), - paUnanticipatedHostError ); + /* Get the number of available frames for the pcms that are marked ready. + * @concern FullDuplex If only one direction is marked ready (from poll), the number of frames available for + * the other direction is returned. This under the assumption that input is dropped earlier if paNeverDropInput + * is not specified. + */ + int captureReady = self->capture.pcm ? self->capture.ready : 0, + playbackReady = self->playback.pcm ? self->playback.ready : 0; + PA_ENSURE( PaAlsaStream_GetAvailableFrames( self, captureReady, playbackReady, framesAvail, &xrun ) ); - if( stream->playback_interleaved ) + if( self->capture.pcm && self->playback.pcm ) { - buffer = ExtractAddress( areas, stream->playback_offset ); - PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, - 0, /* starting at channel 0 */ - buffer, - 0 /* default numInputChannels */ - ); - } - else - /* noninterleaved */ - for( i = 0; i < stream->playback_channels; ++i ) + if( !self->playback.ready && !self->neverDropInput ) { - area = &areas[i]; - buffer = ExtractAddress( area, stream->playback_offset ); - PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor, - i, - buffer ); + /* TODO: Drop input */ } + } + } + +end: +error: + if( xrun ) + { + /* Recover from the xrun state */ + PA_ENSURE( PaAlsaStream_HandleXrun( self ) ); + *framesAvail = 0; } + *xrunOccurred = xrun; + + return result; +} + +/** Register per-channel ALSA buffer information with buffer processor. + * + * Mmapped buffer space is acquired from ALSA, and registered with the buffer processor. Differences between the + * number of host and user channels is taken into account. + * + * @param numFrames On entrance the number of requested frames, on exit the number of contiguously accessible frames. + */ +static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent *self, PaUtilBufferProcessor *bp, + unsigned long *numFrames, int *xrun ) +{ + PaError result = paNoError; + const snd_pcm_channel_area_t *areas, *area; + void (*setChannel)(PaUtilBufferProcessor *, unsigned int, void *, unsigned int) = + StreamDirection_In == self->streamDir ? PaUtil_SetInputChannel : PaUtil_SetOutputChannel; + unsigned char *buffer, *p; + int i; + unsigned long framesAvail; - if( alignFrames ) + /* This _must_ be called before mmap_begin */ + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( self, &framesAvail, xrun ) ); + if( *xrun ) { - playbackFrames -= (playbackFrames % stream->frames_per_period); - captureFrames -= (captureFrames % stream->frames_per_period); + *numFrames = 0; + goto end; } - commonFrames = MIN( captureFrames, playbackFrames ); - if( stream->pcm_playback && stream->pcm_capture ) + ENSURE_( snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError ); + + if( self->hostInterleaved ) { - /* Full-duplex, but we are starved for data in either end - * If we're out of input, go on. Input buffer will be zeroed. - * In the case of output underflow, drop input frames unless stream->neverDropInput. - * If we're starved for output, while keeping input, we'll discard output samples. - */ - if( !commonFrames ) + int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); + + p = buffer = ExtractAddress( areas, self->offset ); + for( i = 0; i < self->numUserChannels; ++i ) { - if( !captureFrames ) /* Input underflow */ - commonFrames = playbackFrames; /* We still want output */ - else if( stream->neverDropInput ) /* Output underflow, but do not drop input */ - commonFrames = captureFrames; + /* We're setting the channels up to userChannels, but the stride will be hostChannels samples */ + setChannel( bp, i, p, self->numHostChannels ); + p += swidth; } - else /* Safe to commit commonFrames for both */ - playbackFrames = captureFrames = commonFrames; } - - /* Inform PortAudio of the number of frames we got. - We might be experiencing underflow in either end; if its an input underflow, we go on - with output. If its output underflow however, depending on the paNeverDropInput flag, - we may want to simply discard the excess input or call the callback with - paOutputOverflow flagged. - */ - if( stream->pcm_capture ) - { - if( captureFrames || !commonFrames ) /* We have input, or neither */ - PaUtil_SetInputFrameCount( &stream->bufferProcessor, commonFrames ); - else /* We have input underflow */ - PaUtil_SetNoInput( &stream->bufferProcessor ); - } - if( stream->pcm_playback ) + else { - if( playbackFrames || !commonFrames ) /* We have output, or neither */ - PaUtil_SetOutputFrameCount( &stream->bufferProcessor, commonFrames ); - else /* We have output underflow, but keeping input data (paNeverDropInput) */ + for( i = 0; i < self->numUserChannels; ++i ) { - PaUtil_SetNoOutput( &stream->bufferProcessor ); + area = areas + i; + buffer = ExtractAddress( area, self->offset ); + setChannel( bp, i, buffer, 1 ); } } - /* PA_DEBUG(( "SetUpBuffers: captureAvail: %d, playbackAvail: %d, commonFrames: %d\n\n", captureFrames, playbackFrames, commonFrames )); */ - /* Either of these could be zero, otherwise equal to commonFrames */ - stream->playbackAvail = playbackFrames; - stream->captureAvail = captureFrames; - - *frames = commonFrames; + /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */ + self->channelAreas = (snd_pcm_channel_area_t *)areas; end: - return result; error: - goto end; + return result; } -/* Callback interface */ - -static void OnExit( void *data ) +/** Initiate buffer processing. + * + * ALSA buffers are registered with the PA buffer processor and the buffer size (in frames) set. + * + * @concern FullDuplex If both directions are being processed, the minimum amount of frames for the two directions is + * calculated. + * + * @param numFrames On entrance the number of available frames, on exit the number of received frames + * @param xrunOccurred Return whether an xrun has occurred + */ +static PaError PaAlsaStream_SetUpBuffers( PaAlsaStream *self, unsigned long *numFrames, int *xrunOccurred ) { - PaAlsaStream *stream = (PaAlsaStream *) data; + PaError result = paNoError; + unsigned long captureFrames = ULONG_MAX, playbackFrames = ULONG_MAX, commonFrames = 0; + int xrun = 0; - assert( data ); + /* Extract per-channel ALSA buffer pointers and register them with the buffer processor. + * It is possible that a direction is not marked ready however, because it is out of sync with the other. + */ + if( self->capture.pcm && self->capture.ready ) + { + captureFrames = *numFrames; + PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->capture, &self->bufferProcessor, &captureFrames, + &xrun ) ); + } + if( self->playback.pcm && self->playback.ready ) + { + playbackFrames = *numFrames; + PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->playback, &self->bufferProcessor, &playbackFrames, + &xrun ) ); + } + if( xrun ) + { + /* Nothing more to do */ + assert( 0 == commonFrames ); + goto end; + } - PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + commonFrames = PA_MIN( captureFrames, playbackFrames ); + assert( commonFrames <= *numFrames ); - stream->callback_finished = 1; /* Let the outside world know stream was stopped in callback */ - AlsaStop( stream, stream->callbackAbort ); - stream->callbackAbort = 0; /* Clear state */ + /* Inform PortAudio of the number of frames we got. + * @concern FullDuplex We might be experiencing underflow in either end; if its an input underflow, we go on + * with output. If its output underflow however, depending on the paNeverDropInput flag, we may want to simply + * discard the excess input or call the callback with paOutputOverflow flagged. + */ + if( self->capture.pcm ) + { + if( self->capture.ready ) + { + PaUtil_SetInputFrameCount( &self->bufferProcessor, commonFrames ); + } + else + { + /* We have input underflow */ + PaUtil_SetNoInput( &self->bufferProcessor ); + } + } + if( self->playback.pcm ) + { + if( self->playback.ready ) + { + PaUtil_SetOutputFrameCount( &self->bufferProcessor, commonFrames ); + } + else + { + /* We have output underflow, but keeping input data (paNeverDropInput) */ + /* assert( self->neverDropInput ); */ + PaUtil_SetNoOutput( &self->bufferProcessor ); + } + } - PA_DEBUG(( "OnExit: Stoppage\n" )); +end: + *numFrames = commonFrames; +error: + if( xrun ) + { + PA_ENSURE( PaAlsaStream_HandleXrun( self ) ); + *numFrames = 0; + } + *xrunOccurred = xrun; - /* Eventually notify user all buffers have played */ - if( stream->streamRepresentation.streamFinishedCallback ) - stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); - stream->isActive = 0; + return result; } -/* \brief Callback thread's function +/** Callback thread's function. * - * Roughly, the workflow consists of waiting untill ALSA reports available frames, and then consuming these frames in an inner loop - * till we must wait for more. If the inner loop detects an xrun condition however, the data consumption will stop and we go - * back to the waiting state. + * Roughly, the workflow can be described in the following way: The number of available frames that can be processed + * directly is obtained from ALSA, we then request as much directly accessible memory as possible within this amount + * from ALSA. The buffer memory is registered with the PA buffer processor and processing is carried out with + * PaUtil_EndBufferProcessing. Finally, the number of processed frames is reported to ALSA. The processing can + * happen in several iterations untill we have consumed the known number of available frames (or an xrun is detected). */ static void *CallbackThreadFunc( void *userData ) { PaError result = paNoError, *pres = NULL; PaAlsaStream *stream = (PaAlsaStream*) userData; - snd_pcm_uframes_t framesAvail, framesGot, framesProcessed; - snd_pcm_sframes_t startThreshold = stream->startThreshold; - snd_pcm_status_t *capture_status, *playback_status; + PaStreamCallbackTimeInfo timeInfo = {0, 0, 0}; + snd_pcm_sframes_t startThreshold = 0; + int callbackResult = paContinue; + PaStreamCallbackFlags cbFlags = 0; /* We might want to keep state across iterations */ + int streamStarted = 0; - assert( userData ); + assert( stream ); - /* Allocation should happen here, once per iteration is no good */ - snd_pcm_status_alloca( &capture_status ); - snd_pcm_status_alloca( &playback_status ); + callbackThread_ = pthread_self(); + /* Execute OnExit when exiting */ + pthread_cleanup_push( &OnExit, stream ); - pthread_cleanup_push( &OnExit, stream ); /* Execute OnExit when exiting */ + /* Not implemented */ + assert( !stream->primeBuffers ); - if( startThreshold <= 0 ) + /* @concern StreamStart If the output is being primed the output pcm needs to be prepared, otherwise the + * stream is started immediately. The latter involves signaling the waiting main thread. + */ + if( stream->primeBuffers ) { - UNLESS( !pthread_mutex_lock( &stream->startMtx ), paInternalError ); - PA_ENSURE( AlsaStart( stream, 0 ) ); /* Buffer will be zeroed */ - UNLESS( !pthread_cond_signal( &stream->startCond ), paInternalError ); - UNLESS( !pthread_mutex_unlock( &stream->startMtx ), paInternalError ); + snd_pcm_sframes_t avail; + + if( stream->playback.pcm ) + ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); + if( stream->capture.pcm && !stream->pcmsSynced ) + ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError ); + + /* We can't be certain that the whole ring buffer is available for priming, but there should be + * at least one period */ + avail = snd_pcm_avail_update( stream->playback.pcm ); + startThreshold = avail - (avail % stream->playback.framesPerBuffer); + assert( startThreshold >= stream->playback.framesPerBuffer ); } - else /* Priming output? Prepare first */ + else { - if( stream->pcm_playback ) - ENSURE( snd_pcm_prepare( stream->pcm_playback ), paUnanticipatedHostError ); - if( stream->pcm_capture && !stream->pcmsSynced ) - ENSURE( snd_pcm_prepare( stream->pcm_capture ), paUnanticipatedHostError ); + ASSERT_CALL_( pthread_mutex_lock( &stream->startMtx ), 0 ); + PA_ENSURE( AlsaStart( stream, 0 ) ); /* Buffer will be zeroed */ + ASSERT_CALL_( pthread_cond_signal( &stream->startCond ), 0 ); + ASSERT_CALL_( pthread_mutex_unlock( &stream->startMtx ), 0 ); + + streamStarted = 1; } while( 1 ) { - PaError callbackResult; - PaStreamCallbackTimeInfo timeInfo = {0,0,0}; - PaStreamCallbackFlags cbFlags = 0; + unsigned long framesAvail, framesGot; + int xrun = 0; pthread_testcancel(); + /* @concern StreamStop if the main thread has requested a stop and the stream has not been effectively + * stopped we signal this condition by modifying callbackResult (we'll want to flush buffered output). + */ + if( stream->callbackStop && paContinue == callbackResult ) { - /* calculate time info */ - snd_timestamp_t capture_timestamp, playback_timestamp; - PaTime capture_time = 0., playback_time = 0.; - - if( stream->pcm_capture ) - { - snd_pcm_sframes_t capture_delay; - - snd_pcm_status( stream->pcm_capture, capture_status ); - snd_pcm_status_get_tstamp( capture_status, &capture_timestamp ); - - capture_time = capture_timestamp.tv_sec + - ((PaTime)capture_timestamp.tv_usec/1000000); - timeInfo.currentTime = capture_time; - - capture_delay = snd_pcm_status_get_delay( capture_status ); - timeInfo.inputBufferAdcTime = timeInfo.currentTime - - (PaTime)capture_delay / stream->streamRepresentation.streamInfo.sampleRate; - } - if( stream->pcm_playback ) - { - snd_pcm_sframes_t playback_delay; - - snd_pcm_status( stream->pcm_playback, playback_status ); - snd_pcm_status_get_tstamp( playback_status, &playback_timestamp ); - - playback_time = playback_timestamp.tv_sec + - ((PaTime)playback_timestamp.tv_usec/1000000); - - if( stream->pcm_capture ) /* Full duplex */ - { - /* Hmm, we have both a playback and a capture timestamp. - * Hopefully they are the same... */ - if( fabs( capture_time - playback_time ) > 0.01 ) - PA_DEBUG(("Capture time and playback time differ by %f\n", fabs(capture_time-playback_time))); - } - else - timeInfo.currentTime = playback_time; - - playback_delay = snd_pcm_status_get_delay( playback_status ); - timeInfo.outputBufferDacTime = timeInfo.currentTime + - (PaTime)playback_delay / stream->streamRepresentation.streamInfo.sampleRate; - } + PA_DEBUG(( "Setting callbackResult to paComplete\n" )); + callbackResult = paComplete; } - /* Set callback flags *after* one of these has been detected */ - if( stream->underrun != 0.0 ) + if( paContinue != callbackResult ) { - cbFlags |= paOutputUnderflow; - stream->underrun = 0.0; + stream->callbackAbort = (paAbort == callbackResult); + if( stream->callbackAbort || + /** @concern BlockAdaption Go on if adaption buffers are empty */ + PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) + goto end; + + PA_DEBUG(( "%s: Flushing buffer processor\n", __FUNCTION__ )); + /* There is still buffered output that needs to be processed */ } - if( stream->overrun != 0.0 ) + + /* Wait for data to become available, this comes down to polling the ALSA file descriptors untill we have + * a number of available frames. + */ + PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); + if( xrun ) { - cbFlags |= paInputOverflow; - stream->overrun = 0.0; + assert( 0 == framesAvail ); + continue; + + /* XXX: Report xruns to the user? A situation is conceivable where the callback is never invoked due + * to constant xruns, it might be desirable to notify the user of this. + */ } - PA_ENSURE( Wait( stream, &framesAvail ) ); + /* Consume buffer space. Once we have a number of frames available for consumption we must retrieve the + * mmapped buffers from ALSA, this is contiguously accessible memory however, so we may receive smaller + * portions at a time than is available as a whole. Therefore we should be prepared to process several + * chunks successively. The buffers are passed to the PA buffer processor. + */ while( framesAvail > 0 ) { + xrun = 0; + pthread_testcancel(); - /* Priming output */ - if( startThreshold > 0 ) + /** @concern Xruns Under/overflows are to be reported to the callback */ + if( stream->underrun > 0.0 ) { - PA_DEBUG(( "CallbackThreadFunc: Priming\n" )); - cbFlags |= paPrimingOutput; - framesAvail = MIN( (int)framesAvail, (int)startThreshold ); + cbFlags |= paOutputUnderflow; + stream->underrun = 0.0; } - - /* now we know the soundcard is ready to produce/receive at least - * one period. we just need to get the buffers for the client - * to read/write. */ - PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, cbFlags ); - - PA_ENSURE( SetUpBuffers( stream, framesAvail, 1, &framesGot ) ); - /* Check for under/overflow */ - if( stream->pcm_playback && stream->pcm_capture ) + if( stream->overrun > 0.0 ) + { + cbFlags |= paInputOverflow; + stream->overrun = 0.0; + } + if( stream->capture.pcm && stream->playback.pcm ) { - if( !stream->captureAvail ) + /** @concern FullDuplex It's possible that only one direction is being processed to avoid an + * under- or overflow, this should be reported correspondingly */ + if( !stream->capture.ready ) { cbFlags |= paInputUnderflow; - PA_DEBUG(( "Input underflow\n" )); + PA_DEBUG(( "%s: Input underflow\n", __FUNCTION__ )); } - if( !stream->playbackAvail ) + else if( !stream->playback.ready ) { - if( !framesGot ) /* The normal case, dropping input */ - { - cbFlags |= paInputOverflow; - PA_DEBUG(( "Input overflow\n" )); - } - else /* Keeping input (paNeverDropInput) */ - { - cbFlags |= paOutputOverflow; - PA_DEBUG(( "Output overflow\n" )); - } + cbFlags |= paOutputOverflow; + PA_DEBUG(( "%s: Output overflow\n", __FUNCTION__ )); } } - PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); - - callbackResult = paContinue; + CallbackUpdate( &stream->threading ); + CalculateTimeInfo( stream, &timeInfo ); + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, cbFlags ); + cbFlags = 0; - CallbackUpdate( &stream->threading ); /* Report to watchdog */ - /* this calls the callback */ - framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, - &callbackResult ); - PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + /* CPU load measurement should include processing activivity external to the stream callback */ + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); - /* Inform ALSA how many frames we read/wrote - Now, this number may differ between capture and playback, due to under/overflow. - If we're dropping input frames, we effectively sink them here. - */ - if( stream->pcm_capture ) - { - int res = snd_pcm_mmap_commit( stream->pcm_capture, stream->capture_offset, stream->captureAvail ); + framesGot = framesAvail; + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + framesAvail -= framesGot; - /* Non-fatal error? Terminate loop (go back to polling for frames)*/ - if( res == -EPIPE || res == -ESTRPIPE ) - framesAvail = 0; - else - ENSURE( res, paUnanticipatedHostError ); - } - if( stream->pcm_playback ) + if( framesGot > 0 ) { - int res = snd_pcm_mmap_commit( stream->pcm_playback, stream->playback_offset, stream->playbackAvail ); + assert( !xrun ); - /* Non-fatal error? Terminate loop (go back to polling for frames) */ - if( res == -EPIPE || res == -ESTRPIPE ) - framesAvail = 0; - else - ENSURE( res, paUnanticipatedHostError ); + PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) ); } + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesGot ); - /* If threshold for starting stream specified (priming buffer), decrement and compare */ - if( startThreshold > 0 ) + if( framesGot == 0 ) { - if( (startThreshold -= framesGot) <= 0 ) - { - UNLESS( !pthread_mutex_lock( &stream->startMtx ), paInternalError ); - PA_ENSURE( AlsaStart( stream, 1 ) ); /* Buffer will be zeroed */ - UNLESS( !pthread_cond_signal( &stream->startCond ), paInternalError ); - UNLESS( !pthread_mutex_unlock( &stream->startMtx ), paInternalError ); - } - } + if( !xrun ) + PA_DEBUG(( "%s: Received less frames than reported from ALSA!\n", __FUNCTION__ )); - if( callbackResult != paContinue ) + /* Go back to polling for more frames */ break; - framesAvail -= framesGot; - } - - - /* - If you need to byte swap outputBuffer, you can do it here using - routines in pa_byteswappers.h - */ + } - if( callbackResult != paContinue ) - { - stream->callbackAbort = (callbackResult == paAbort); - goto end; - + if( paContinue != callbackResult ) + break; } } - /* This code is unreachable, but important to include regardless because it - * is possibly a macro with a closing brace to match the opening brace in - * pthread_cleanup_push() above. The documentation states that they must - * always occur in pairs. */ + /* Match pthread_cleanup_push */ pthread_cleanup_pop( 1 ); end: @@ -2592,197 +3074,175 @@ error: /* Blocking interface */ -static PaError ReadStream( PaStream* s, - void *buffer, - unsigned long frames ) +static PaError ReadStream( PaStream* s, void *buffer, unsigned long frames ) { PaError result = paNoError; - signed long err; PaAlsaStream *stream = (PaAlsaStream*)s; - snd_pcm_uframes_t framesGot, framesAvail; + unsigned long framesGot, framesAvail; void *userBuffer; - int i; - snd_pcm_t *save; + snd_pcm_t *save = stream->playback.pcm; assert( stream ); - /* Disregard playback */ - save = stream->pcm_playback; - stream->pcm_playback = NULL; - - if( !stream->pcm_capture ) - PA_ENSURE( paCanNotReadFromAnOutputOnlyStream ); + PA_UNLESS( stream->capture.pcm, paCanNotReadFromAnOutputOnlyStream ); + /* Disregard playback */ + stream->playback.pcm = NULL; - if( stream->overrun ) + if( stream->overrun > 0. ) { result = paInputOverflowed; stream->overrun = 0.0; } - if( stream->bufferProcessor.userInputIsInterleaved ) + if( stream->capture.userInterleaved ) userBuffer = buffer; - else /* Copy channels into local array */ + else { - int numBytes = sizeof (void *) * stream->capture_channels; - UNLESS( userBuffer = alloca( numBytes ), paInsufficientMemory ); - for( i = 0; i < stream->capture_channels; ++i ) - ((const void **) userBuffer)[i] = ((const void **) buffer)[i]; + /* Copy channels into local array */ + userBuffer = stream->capture.userBuffers; + memcpy( userBuffer, buffer, sizeof (void *) * stream->capture.numUserChannels ); } /* Start stream if in prepared state */ - if( snd_pcm_state( stream->pcm_capture ) == SND_PCM_STATE_PREPARED ) + if( snd_pcm_state( stream->capture.pcm ) == SND_PCM_STATE_PREPARED ) { - ENSURE( snd_pcm_start( stream->pcm_capture ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError ); } while( frames > 0 ) { - if( (err = GetStreamReadAvailable( stream )) == paInputOverflowed ) - err = 0; /* Wait will detect the (unlikely) xrun, and restart capture */ - PA_ENSURE( err ); - framesAvail = (snd_pcm_uframes_t) err; + int xrun = 0; + PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); + framesGot = PA_MIN( framesAvail, frames ); - if( framesAvail == 0 ) - PA_ENSURE( Wait( stream, &framesAvail ) ); - framesAvail = MIN( framesAvail, frames ); - - PA_ENSURE( SetUpBuffers( stream, framesAvail, 0, &framesGot ) ); - framesGot = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesGot ); - ENSURE( snd_pcm_mmap_commit( stream->pcm_capture, stream->capture_offset, framesGot ), - paUnanticipatedHostError ); - - frames -= framesGot; + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + if( framesGot > 0 ) + { + framesGot = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesGot ); + PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) ); + frames -= framesGot; + } } end: - stream->pcm_playback = save; + stream->playback.pcm = save; return result; error: goto end; } -static PaError WriteStream( PaStream* s, - const void *buffer, - unsigned long frames ) +static PaError WriteStream( PaStream* s, const void *buffer, unsigned long frames ) { PaError result = paNoError; signed long err; PaAlsaStream *stream = (PaAlsaStream*)s; snd_pcm_uframes_t framesGot, framesAvail; const void *userBuffer; - int i; - snd_pcm_t *save; + snd_pcm_t *save = stream->capture.pcm; - /* Disregard capture */ - save = stream->pcm_capture; - stream->pcm_capture = NULL; - assert( stream ); - if( !stream->pcm_playback ) - PA_ENSURE( paCanNotWriteToAnInputOnlyStream ); - if( stream->underrun ) + PA_UNLESS( stream->playback.pcm, paCanNotWriteToAnInputOnlyStream ); + + /* Disregard capture */ + stream->capture.pcm = NULL; + + if( stream->underrun > 0. ) { result = paOutputUnderflowed; stream->underrun = 0.0; } - if( stream->bufferProcessor.userOutputIsInterleaved ) + if( stream->playback.userInterleaved ) userBuffer = buffer; else /* Copy channels into local array */ { - int numBytes = sizeof (void *) * stream->playback_channels; - UNLESS( userBuffer = alloca( numBytes ), paInsufficientMemory ); - for( i = 0; i < stream->playback_channels; ++i ) - ((const void **) userBuffer)[i] = ((const void **) buffer)[i]; + userBuffer = stream->playback.userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->playback.numUserChannels ); } while( frames > 0 ) { + int xrun = 0; snd_pcm_uframes_t hwAvail; - PA_ENSURE( err = GetStreamWriteAvailable( stream ) ); - framesAvail = err; - if( framesAvail == 0 ) - PA_ENSURE( Wait( stream, &framesAvail ) ); - framesAvail = MIN( framesAvail, frames ); - - PA_ENSURE( SetUpBuffers( stream, framesAvail, 0, &framesGot ) ); - framesGot = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, framesGot ); - ENSURE( snd_pcm_mmap_commit( stream->pcm_playback, stream->playback_offset, framesGot ), - paUnanticipatedHostError ); + PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) ); + framesGot = PA_MIN( framesAvail, frames ); - frames -= framesGot; + PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) ); + if( framesGot > 0 ) + { + framesGot = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, framesGot ); + PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) ); + frames -= framesGot; + } /* Frames residing in buffer */ PA_ENSURE( err = GetStreamWriteAvailable( stream ) ); framesAvail = err; - hwAvail = stream->playbackBufferSize - framesAvail; + hwAvail = stream->playback.bufferSize - framesAvail; /* Start stream after one period of samples worth */ - if( snd_pcm_state( stream->pcm_playback ) == SND_PCM_STATE_PREPARED && - hwAvail >= stream->frames_per_period ) + if( snd_pcm_state( stream->playback.pcm ) == SND_PCM_STATE_PREPARED && + hwAvail >= stream->playback.framesPerBuffer ) { - ENSURE( snd_pcm_start( stream->pcm_playback ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); } } end: - stream->pcm_capture = save; + stream->capture.pcm = save; return result; error: goto end; } - /* Return frames available for reading. In the event of an overflow, the capture pcm will be restarted */ static signed long GetStreamReadAvailable( PaStream* s ) { PaError result = paNoError; PaAlsaStream *stream = (PaAlsaStream*)s; - snd_pcm_sframes_t avail = snd_pcm_avail_update( stream->pcm_capture ); + unsigned long avail; + int xrun; - if( avail < 0 ) + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->capture, &avail, &xrun ) ); + if( xrun ) { - if( avail == -EPIPE ) - { - PA_ENSURE( HandleXrun( stream ) ); - avail = snd_pcm_avail_update( stream->pcm_capture ); - } - - if( avail == -EPIPE ) + PA_ENSURE( PaAlsaStream_HandleXrun( stream ) ); + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->capture, &avail, &xrun ) ); + if( xrun ) PA_ENSURE( paInputOverflowed ); - ENSURE( avail, paUnanticipatedHostError ); } - return avail; + return (signed long)avail; error: return result; } - -/* Return frames available for writing. In the event of an underflow, the playback pcm will be prepared */ static signed long GetStreamWriteAvailable( PaStream* s ) { PaError result = paNoError; PaAlsaStream *stream = (PaAlsaStream*)s; - snd_pcm_sframes_t avail = snd_pcm_avail_update( stream->pcm_playback ); + unsigned long avail; + int xrun; - if( avail < 0 ) + PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->playback, &avail, &xrun ) ); + if( xrun ) { - if( avail == -EPIPE ) - { - PA_ENSURE( HandleXrun( stream ) ); - avail = snd_pcm_avail_update( stream->pcm_playback ); - } + snd_pcm_sframes_t savail; + + PA_ENSURE( PaAlsaStream_HandleXrun( stream ) ); + savail = snd_pcm_avail_update( stream->playback.pcm ); + + /* savail should not contain -EPIPE now, since PaAlsaStream_HandleXrun will only prepare the pcm */ + ENSURE_( savail, paUnanticipatedHostError ); - /* avail should not contain -EPIPE now, since HandleXrun will only prepare the pcm */ - ENSURE( avail, paUnanticipatedHostError ); + avail = (unsigned long) savail; } - return avail; + return (signed long)avail; error: return result; diff --git a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h index 46dd0790..e6f44b16 100644 --- a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h +++ b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h @@ -1,6 +1,42 @@ #ifndef PA_LINUX_ALSA_H #define PA_LINUX_ALSA_H +/* + * $Id: pa_linux_alsa.h,v 1.1.2.12 2004/09/25 14:15:25 aknudsen Exp $ + * PortAudio Portable Real-Time Audio Library + * ALSA-specific extensions + * + * 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. + * + */ + +/** @file + * ALSA-specific PortAudio API extension header file. + */ + #ifdef __cplusplus extern "C" { #endif diff --git a/pd/portaudio/pa_mac_core/pa_mac_core.c b/pd/portaudio/pa_mac_core/pa_mac_core.c index 2a7d10f7..ea2f0737 100644 --- a/pd/portaudio/pa_mac_core/pa_mac_core.c +++ b/pd/portaudio/pa_mac_core/pa_mac_core.c @@ -1,5 +1,5 @@ /* - * $Id: pa_mac_core.c,v 1.8.2.1 2004/05/27 22:39:58 gregpfeil Exp $ + * $Id: pa_mac_core.c,v 1.8.2.2 2005/02/15 07:59:45 rossbencina Exp $ * pa_mac_core.c * Implementation of PortAudio for Mac OS X CoreAudio * @@ -564,7 +564,9 @@ static PaError SetUpUnidirectionalStream(AudioDeviceID device, double sampleRate { PaError err = paNoError; err = SetSampleRate(device, sampleRate, isInput); - err = SetFramesPerBuffer(device, framesPerBuffer, isInput); + if( err == paNoError ) + err = SetFramesPerBuffer(device, framesPerBuffer, isInput); + return err; } // ===== PortAudio functions ===== diff --git a/pd/portaudio/pa_unix/pa_unix_util.c b/pd/portaudio/pa_unix/pa_unix_util.c index fdadfe87..f45848fb 100644 --- a/pd/portaudio/pa_unix/pa_unix_util.c +++ b/pd/portaudio/pa_unix/pa_unix_util.c @@ -1,5 +1,5 @@ /* - * $Id: pa_unix_util.c,v 1.1.2.4 2003/11/01 10:12:13 aknudsen Exp $ + * $Id: pa_unix_util.c,v 1.1.2.7 2005/03/31 15:02:48 aknudsen Exp $ * Portable Audio I/O Library * UNIX platform-specific support functions * @@ -31,12 +31,15 @@ */ +#include <pthread.h> #include <unistd.h> #include <stdlib.h> #include <sys/time.h> +#include <assert.h> +#include <string.h> /* For memset */ #include "pa_util.h" - +#include "pa_unix_util.h" /* Track memory allocations to avoid leaks. @@ -106,5 +109,67 @@ PaTime PaUtil_GetTime( void ) { struct timeval tv; gettimeofday( &tv, NULL ); - return (PaTime) tv.tv_usec / 1000000 + (PaTime) tv.tv_sec; + return (PaTime) tv.tv_usec / 1000000. + tv.tv_sec; +} + +PaError PaUtil_InitializeThreading( PaUtilThreading *threading ) +{ + (void) paUtilErr_; + return paNoError; +} + +void PaUtil_TerminateThreading( PaUtilThreading *threading ) +{ +} + +PaError PaUtil_StartThreading( PaUtilThreading *threading, void *(*threadRoutine)(void *), void *data ) +{ + pthread_create( &threading->callbackThread, NULL, threadRoutine, data ); + return paNoError; +} + +PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *exitResult ) +{ + PaError result = paNoError; + void *pret; + + if( exitResult ) + *exitResult = paNoError; + + /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */ + if( !wait ) + pthread_cancel( threading->callbackThread ); /* XXX: Safe to call this if the thread has exited on its own? */ + pthread_join( threading->callbackThread, &pret ); + +#ifdef PTHREAD_CANCELED + if( pret && PTHREAD_CANCELED != pret ) +#else + /* !wait means the thread may have been canceled */ + if( pret && wait ) +#endif + { + if( exitResult ) + *exitResult = *(PaError *) pret; + free( pret ); + } + + return result; +} + +/* +static void *CanaryFunc( void *userData ) +{ + const unsigned intervalMsec = 1000; + PaUtilThreading *th = (PaUtilThreading *) userData; + + while( 1 ) + { + th->canaryTime = PaUtil_GetTime(); + + pthread_testcancel(); + Pa_Sleep( intervalMsec ); + } + + pthread_exit( NULL ); } +*/ diff --git a/pd/portaudio/pa_unix_oss/pa_unix_oss.c b/pd/portaudio/pa_unix_oss/pa_unix_oss.c index b2e6d5a4..3eaccafb 100644 --- a/pd/portaudio/pa_unix_oss/pa_unix_oss.c +++ b/pd/portaudio/pa_unix_oss/pa_unix_oss.c @@ -1,11 +1,12 @@ /* - * $Id: pa_unix_oss.c,v 1.6.2.7 2003/09/23 21:17:22 rossbencina Exp $ + * $Id: pa_unix_oss.c,v 1.6.2.22 2005/03/08 21:26:53 aknudsen Exp $ * PortAudio Portable Real-Time Audio Library * Latest Version at: http://www.portaudio.com * OSS implementation by: * Douglas Repetto * Phil Burk * Dominic Mazzoni + * Arve Knudsen * * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 1999-2002 Ross Bencina, Phil Burk @@ -37,10 +38,19 @@ #include <stdio.h> #include <string.h> #include <math.h> -#include <sys/ioctl.h> #include <fcntl.h> +#include <sys/ioctl.h> #include <unistd.h> #include <pthread.h> +#include <alloca.h> +#include <malloc.h> +#include <assert.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/poll.h> +#include <limits.h> +#include <semaphore.h> #ifdef __linux__ # include <linux/soundcard.h> @@ -57,14 +67,42 @@ #include "pa_stream.h" #include "pa_cpuload.h" #include "pa_process.h" +#include "../pa_unix/pa_unix_util.h" + +static int sysErr_; +static pthread_t mainThread_; + +/* Check return value of system call, and map it to PaError */ +#define ENSURE_(expr, code) \ + do { \ + if( UNLIKELY( (sysErr_ = (expr)) < 0 ) ) \ + { \ + /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ + if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + PaUtil_SetLastHostErrorInfo( paALSA, sysErr_, strerror( errno ) ); \ + } \ + \ + PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } \ + } while( 0 ); -/* TODO: add error text handling -#define PA_UNIX_OSS_ERROR( errorCode, errorText ) \ - PaUtil_SetLastHostErrorInfo( paInDevelopment, errorCode, errorText ) -*/ - -#define PRINT(x) { printf x; fflush(stdout); } -#define DBUG(x) /* PRINT(x) */ +#ifndef AFMT_S16_NE +#define AFMT_S16_NE Get_AFMT_S16_NE() +/********************************************************************* + * Some versions of OSS do not define AFMT_S16_NE. So check CPU. + * PowerPC is Big Endian. X86 is Little Endian. + */ +static int Get_AFMT_S16_NE( void ) +{ + long testData = 1; + char *ptr = (char *) &testData; + int isLittle = ( *ptr == 1 ); /* Does address point to least significant byte? */ + return isLittle ? AFMT_S16_LE : AFMT_S16_BE; +} +#endif /* PaOSSHostApiRepresentation - host api datastructure specific to this implementation */ @@ -80,15 +118,67 @@ typedef struct } PaOSSHostApiRepresentation; -typedef struct PaOSS_DeviceList { - PaDeviceInfo *deviceInfo; - struct PaOSS_DeviceList *next; +/** Per-direction structure for PaOssStream. + * + * Aspect StreamChannels: In case the user requests to open the same device for both capture and playback, + * but with different number of channels we will have to adapt between the number of user and host + * channels for at least one direction, since the configuration space is the same for both directions + * of an OSS device. + */ +typedef struct +{ + int fd; + const char *devName; + int userChannelCount, hostChannelCount; + int userInterleaved; + void *buffer; + PaSampleFormat userFormat, hostFormat; + double latency; + unsigned long hostFrames, numBufs; + void **userBuffers; /* For non-interleaved blocking */ +} PaOssStreamComponent; + +/** Implementation specific representation of a PaStream. + * + */ +typedef struct PaOssStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + PaUtilThreading threading; + + int sharedDevice; + unsigned long framesPerHostBuffer; + int triggered; /* Have the devices been triggered yet (first start) */ + + int isActive; + int isStopped; + + int lastPosPtr; + double lastStreamBytes; + + int framesProcessed; + + double sampleRate; + + int callbackMode; + int callbackStop, callbackAbort; + + PaOssStreamComponent *capture, *playback; + unsigned long pollTimeout; + sem_t semaphore; } -PaOSS_DeviceList; +PaOssStream; + +typedef enum { + StreamMode_In, + StreamMode_Out +} StreamMode; /* prototypes for functions declared in this file */ -PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ); static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *inputParameters, @@ -118,39 +208,36 @@ static signed long GetStreamWriteAvailable( PaStream* stream ); static PaError BuildDeviceList( PaOSSHostApiRepresentation *hostApi ); +/** Initialize the OSS API implementation. + * + * This function will initialize host API datastructures and query host devices for information. + * + * Aspect DeviceCapabilities: Enumeration of host API devices is initiated from here + * + * Aspect FreeResources: If an error is encountered under way we have to free each resource allocated in this function, + * this happens with the usual "error" label. + */ PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) { PaError result = paNoError; - PaOSSHostApiRepresentation *ossHostApi; - - DBUG(("PaOSS_Initialize\n")); + PaOSSHostApiRepresentation *ossHostApi = NULL; - ossHostApi = (PaOSSHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaOSSHostApiRepresentation) ); - if( !ossHostApi ) - { - result = paInsufficientMemory; - goto error; - } - - ossHostApi->allocations = PaUtil_CreateAllocationGroup(); - if( !ossHostApi->allocations ) - { - result = paInsufficientMemory; - goto error; - } + PA_UNLESS( ossHostApi = (PaOSSHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaOSSHostApiRepresentation) ), + paInsufficientMemory ); + PA_UNLESS( ossHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + ossHostApi->hostApiIndex = hostApiIndex; + /* Initialize host API structure */ *hostApi = &ossHostApi->inheritedHostApiRep; (*hostApi)->info.structVersion = 1; (*hostApi)->info.type = paOSS; (*hostApi)->info.name = "OSS"; - ossHostApi->hostApiIndex = hostApiIndex; - - BuildDeviceList( ossHostApi ); - (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + PA_ENSURE( BuildDeviceList( ossHostApi ) ); + PaUtil_InitializeStreamInterface( &ossHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, GetStreamTime, GetStreamCpuLoad, @@ -163,6 +250,8 @@ PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex GetStreamTime, PaUtil_DummyGetCpuLoad, ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + mainThread_ = pthread_self(); + return result; error: @@ -179,116 +268,57 @@ error: return result; } -#ifndef AFMT_S16_NE -#define AFMT_S16_NE Get_AFMT_S16_NE() -/********************************************************************* - * Some versions of OSS do not define AFMT_S16_NE. So check CPU. - * PowerPC is Big Endian. X86 is Little Endian. - */ -static int Get_AFMT_S16_NE( void ) -{ - long testData = 1; - char *ptr = (char *) &testData; - int isLittle = ( *ptr == 1 ); /* Does address point to least significant byte? */ - return isLittle ? AFMT_S16_LE : AFMT_S16_BE; -} -#endif - -PaError PaOSS_SetFormat(const char *callingFunctionName, int deviceHandle, - char *deviceName, int inputChannelCount, int outputChannelCount, - double *sampleRate) +PaError PaUtil_InitializeDeviceInfo( PaDeviceInfo *deviceInfo, const char *name, PaHostApiIndex hostApiIndex, int maxInputChannels, + int maxOutputChannels, PaTime defaultLowInputLatency, PaTime defaultLowOutputLatency, PaTime defaultHighInputLatency, + PaTime defaultHighOutputLatency, double defaultSampleRate, PaUtilAllocationGroup *allocations ) { - int format; - int rate; - int temp; - - /* Attempt to set format to 16-bit */ - - format = AFMT_S16_NE; - if (ioctl(deviceHandle, SNDCTL_DSP_SETFMT, &format) == -1) { - DBUG(("%s: could not set format: %s\n", callingFunctionName, deviceName )); - return paSampleFormatNotSupported; - } - if (format != AFMT_S16_NE) { - DBUG(("%s: device does not support AFMT_S16_NE: %s\n", callingFunctionName, deviceName )); - return paSampleFormatNotSupported; - } - - /* try to set the number of channels */ - - if (inputChannelCount > 0) { - temp = inputChannelCount; - - if( ioctl(deviceHandle, SNDCTL_DSP_CHANNELS, &temp) < 0 ) { - DBUG(("%s: Couldn't set device %s to %d channels\n", callingFunctionName, deviceName, inputChannelCount )); - return paSampleFormatNotSupported; - } - } + PaError result = paNoError; - if (outputChannelCount > 0) { - temp = outputChannelCount; - - if( ioctl(deviceHandle, SNDCTL_DSP_CHANNELS, &temp) < 0 ) { - DBUG(("%s: Couldn't set device %s to %d channels\n", callingFunctionName, deviceName, outputChannelCount )); - return paSampleFormatNotSupported; - } - } - - /* try to set the sample rate */ - - rate = (int)(*sampleRate); - if (ioctl(deviceHandle, SNDCTL_DSP_SPEED, &rate) == -1) - { - DBUG(("%s: Device %s, couldn't set sample rate to %d\n", - callingFunctionName, deviceName, (int)*sampleRate )); - return paInvalidSampleRate; - } - - /* reject if there's no sample rate within 1% of the one requested */ - if ((fabs(*sampleRate - rate) / *sampleRate) > 0.01) + deviceInfo->structVersion = 2; + if( allocations ) { - DBUG(("%s: Device %s, wanted %d, closest sample rate was %d\n", - callingFunctionName, deviceName, (int)*sampleRate, rate )); - return paInvalidSampleRate; + size_t len = strlen( name ) + 1; + PA_UNLESS( deviceInfo->name = PaUtil_GroupAllocateMemory( allocations, len ), paInsufficientMemory ); + strncpy( (char *)deviceInfo->name, name, len ); } + else + deviceInfo->name = name; - *sampleRate = rate; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->maxInputChannels = maxInputChannels; + deviceInfo->maxOutputChannels = maxOutputChannels; + deviceInfo->defaultLowInputLatency = defaultLowInputLatency; + deviceInfo->defaultLowOutputLatency = defaultLowOutputLatency; + deviceInfo->defaultHighInputLatency = defaultHighInputLatency; + deviceInfo->defaultHighOutputLatency = defaultHighOutputLatency; + deviceInfo->defaultSampleRate = defaultSampleRate; - return paNoError; +error: + return result; } -static PaError PaOSS_QueryDevice(char *deviceName, PaDeviceInfo *deviceInfo) +static PaError QueryDirection( const char *deviceName, StreamMode mode, double *defaultSampleRate, int *maxChannelCount, + double *defaultLowLatency, double *defaultHighLatency ) { PaError result = paNoError; - int tempDevHandle; int numChannels, maxNumChannels; - int sampleRate; - int format; + int busy = 0; + int devHandle = -1; + int sr; + *maxChannelCount = 0; /* Default value in case this fails */ - /* douglas: - we have to do this querying in a slightly different order. apparently - some sound cards will give you different info based on their settins. - e.g. a card might give you stereo at 22kHz but only mono at 44kHz. - the correct order for OSS is: format, channels, sample rate - */ - - if ( (tempDevHandle = open(deviceName,O_WRONLY|O_NONBLOCK)) == -1 ) + if ( (devHandle = open( deviceName, (mode == StreamMode_In ? O_RDONLY : O_WRONLY) | O_NONBLOCK )) < 0 ) { - DBUG(("PaOSS_QueryDevice: could not open %s\n", deviceName )); - return paDeviceUnavailable; - } + if( errno == EBUSY || errno == EAGAIN ) + { + PA_DEBUG(( "%s: Device %s busy\n", __FUNCTION__, deviceName )); + } + else + { + PA_DEBUG(( "%s: Can't access device: %s\n", __FUNCTION__, strerror( errno ) )); + } - /* Attempt to set format to 16-bit */ - format = AFMT_S16_NE; - if (ioctl(tempDevHandle, SNDCTL_DSP_SETFMT, &format) == -1) { - DBUG(("PaOSS_QueryDevice: could not set format: %s\n", deviceName )); - result = paSampleFormatNotSupported; - goto error; - } - if (format != AFMT_S16_NE) { - DBUG(("PaOSS_QueryDevice: device does not support AFMT_S16_NE: %s\n", deviceName )); - result = paSampleFormatNotSupported; - goto error; + return paDeviceUnavailable; } /* Negotiate for the maximum number of channels for this device. PLB20010927 @@ -300,28 +330,37 @@ static PaError PaOSS_QueryDevice(char *deviceName, PaDeviceInfo *deviceInfo) for( numChannels = 1; numChannels <= 16; numChannels++ ) { int temp = numChannels; - DBUG(("PaOSS_QueryDevice: use SNDCTL_DSP_CHANNELS, numChannels = %d\n", numChannels )) - if(ioctl(tempDevHandle, SNDCTL_DSP_CHANNELS, &temp) < 0 ) + if( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ) < 0 ) { + busy = EAGAIN == errno || EBUSY == errno; /* ioctl() failed so bail out if we already have stereo */ - if( numChannels > 2 ) break; + if( maxNumChannels >= 2 ) + break; } else { /* ioctl() worked but bail out if it does not support numChannels. * We don't want to leave gaps in the numChannels supported. */ - if( (numChannels > 2) && (temp != numChannels) ) break; - DBUG(("PaOSS_QueryDevice: temp = %d\n", temp )) - if( temp > maxNumChannels ) maxNumChannels = temp; /* Save maximum. */ + if( (numChannels > 2) && (temp != numChannels) ) + break; + if( temp > maxNumChannels ) + maxNumChannels = temp; /* Save maximum. */ } } + /* A: We're able to open a device for capture if it's busy playing back and vice versa, + * but we can't configure anything */ + if( 0 == maxNumChannels && busy ) + { + result = paDeviceUnavailable; + goto error; + } /* The above negotiation may fail for an old driver so try this older technique. */ if( maxNumChannels < 1 ) { int stereo = 1; - if(ioctl(tempDevHandle, SNDCTL_DSP_STEREO, &stereo) < 0) + if( ioctl( devHandle, SNDCTL_DSP_STEREO, &stereo ) < 0 ) { maxNumChannels = 1; } @@ -329,125 +368,187 @@ static PaError PaOSS_QueryDevice(char *deviceName, PaDeviceInfo *deviceInfo) { maxNumChannels = (stereo) ? 2 : 1; } - DBUG(("PaOSS_QueryDevice: use SNDCTL_DSP_STEREO, maxNumChannels = %d\n", maxNumChannels )) + PA_DEBUG(( "%s: use SNDCTL_DSP_STEREO, maxNumChannels = %d\n", __FUNCTION__, maxNumChannels )) } - DBUG(("PaOSS_QueryDevice: maxNumChannels = %d\n", maxNumChannels)) - - deviceInfo->maxOutputChannels = maxNumChannels; - /* FIXME - for now, assume maxInputChannels = maxOutputChannels. - * Eventually do separate queries for O_WRONLY and O_RDONLY - */ - deviceInfo->maxInputChannels = deviceInfo->maxOutputChannels; - /* During channel negotiation, the last ioctl() may have failed. This can * also cause sample rate negotiation to fail. Hence the following, to return * to a supported number of channels. SG20011005 */ { - int temp = maxNumChannels; - if( temp > 2 ) temp = 2; /* use most reasonable default value */ - ioctl(tempDevHandle, SNDCTL_DSP_CHANNELS, &temp); + /* use most reasonable default value */ + int temp = PA_MIN( maxNumChannels, 2 ); + ENSURE_( ioctl( devHandle, SNDCTL_DSP_CHANNELS, &temp ), paUnanticipatedHostError ); } /* Get supported sample rate closest to 44100 Hz */ - sampleRate = 44100; - if (ioctl(tempDevHandle, SNDCTL_DSP_SPEED, &sampleRate) == -1) + if( *defaultSampleRate < 0 ) { - result = paUnanticipatedHostError; - goto error; + sr = 44100; + if( ioctl( devHandle, SNDCTL_DSP_SPEED, &sr ) < 0 ) + { + result = paUnanticipatedHostError; + goto error; + } + + *defaultSampleRate = sr; } - deviceInfo->defaultSampleRate = sampleRate; + *maxChannelCount = maxNumChannels; + /* TODO */ + *defaultLowLatency = 512. / *defaultSampleRate; + *defaultHighLatency = 2048. / *defaultSampleRate; - deviceInfo->structVersion = 2; +error: + if( devHandle >= 0 ) + close( devHandle ); - /* TODO */ - deviceInfo->defaultLowInputLatency = 128.0 / sampleRate; - deviceInfo->defaultLowOutputLatency = 128.0 / sampleRate; - deviceInfo->defaultHighInputLatency = 16384.0 / sampleRate; - deviceInfo->defaultHighOutputLatency = 16384.0 / sampleRate; + return result; +} - result = paNoError; +/** Query OSS device. + * + * This is where PaDeviceInfo objects are constructed and filled in with relevant information. + * + * Aspect DeviceCapabilities: The inferred device capabilities are recorded in a PaDeviceInfo object that is constructed + * in place. + */ +static PaError QueryDevice( char *deviceName, PaOSSHostApiRepresentation *ossApi, PaDeviceInfo **deviceInfo ) +{ + PaError result = paNoError; + double sampleRate = -1.; + int maxInputChannels, maxOutputChannels; + PaTime defaultLowInputLatency, defaultLowOutputLatency, defaultHighInputLatency, defaultHighOutputLatency; + PaError tmpRes = paNoError; + int busy = 0; + *deviceInfo = NULL; -error: - /* We MUST close the handle here or we won't be able to reopen it later!!! */ - close(tempDevHandle); + /* douglas: + we have to do this querying in a slightly different order. apparently + some sound cards will give you different info based on their settins. + e.g. a card might give you stereo at 22kHz but only mono at 44kHz. + the correct order for OSS is: format, channels, sample rate + */ + /* Aspect StreamChannels: The number of channels supported for a device may depend on the mode it is + * opened in, it may have more channels available for capture than playback and vice versa. Therefore + * we will open the device in both read- and write-only mode to determine the supported number. + */ + if( (tmpRes = QueryDirection( deviceName, StreamMode_In, &sampleRate, &maxInputChannels, &defaultLowInputLatency, + &defaultHighInputLatency )) != paNoError ) + { + if( tmpRes != paDeviceUnavailable ) + { + PA_DEBUG(( "%s: Querying device %s for capture failed!\n", __FUNCTION__, deviceName )); + /* PA_ENSURE( tmpRes ); */ + } + ++busy; + } + if( (tmpRes = QueryDirection( deviceName, StreamMode_Out, &sampleRate, &maxOutputChannels, &defaultLowOutputLatency, + &defaultHighOutputLatency )) != paNoError ) + { + if( tmpRes != paDeviceUnavailable ) + { + PA_DEBUG(( "%s: Querying device %s for playback failed!\n", __FUNCTION__, deviceName )); + /* PA_ENSURE( tmpRes ); */ + } + ++busy; + } + assert( 0 <= busy && busy <= 2 ); + if( 2 == busy ) /* Both directions are unavailable to us */ + { + result = paDeviceUnavailable; + goto error; + } + + PA_UNLESS( *deviceInfo = PaUtil_GroupAllocateMemory( ossApi->allocations, sizeof (PaDeviceInfo) ), paInsufficientMemory ); + PA_ENSURE( PaUtil_InitializeDeviceInfo( *deviceInfo, deviceName, ossApi->hostApiIndex, maxInputChannels, maxOutputChannels, + defaultLowInputLatency, defaultLowOutputLatency, defaultHighInputLatency, defaultHighOutputLatency, sampleRate, + ossApi->allocations ) ); + +error: return result; } +/** Query host devices. + * + * Loop over host devices and query their capabilitiesu + * + * Aspect DeviceCapabilities: This function calls QueryDevice on each device entry and receives a filled in PaDeviceInfo object + * per device, these are placed in the host api representation's deviceInfos array. + */ static PaError BuildDeviceList( PaOSSHostApiRepresentation *ossApi ) { + PaError result = paNoError; PaUtilHostApiRepresentation *commonApi = &ossApi->inheritedHostApiRep; - PaOSS_DeviceList *head = NULL, *tail = NULL, *entry; int i; - int numDevices; + int numDevices = 0, maxDeviceInfos = 1; + PaDeviceInfo **deviceInfos = NULL; - /* Find devices by calling PaOSS_QueryDevice on each - potential device names. When we find a valid one, - add it to a linked list. */ + /* These two will be set to the first working input and output device, respectively */ + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; - for(i=0; i<10; i++) { + /* Find devices by calling QueryDevice on each + * potential device names. When we find a valid one, + * add it to a linked list. + * A: Can there only be 10 devices? */ + + for( i = 0; i < 10; i++ ) + { char deviceName[32]; - PaDeviceInfo deviceInfo; + PaDeviceInfo *deviceInfo; int testResult; + struct stat stbuf; - if (i==0) - sprintf(deviceName, "%s", DEVICE_NAME_BASE); + if( i == 0 ) + snprintf(deviceName, sizeof (deviceName), "%s", DEVICE_NAME_BASE); else - sprintf(deviceName, "%s%d", DEVICE_NAME_BASE, i); - - DBUG(("PaOSS BuildDeviceList: trying device %s\n", deviceName )); - testResult = PaOSS_QueryDevice(deviceName, &deviceInfo); - DBUG(("PaOSS BuildDeviceList: PaOSS_QueryDevice returned %d\n", testResult )); - - if (testResult == paNoError) { - DBUG(("PaOSS BuildDeviceList: Adding device %s to list\n", deviceName)); - deviceInfo.hostApi = ossApi->hostApiIndex; - deviceInfo.name = PaUtil_GroupAllocateMemory( - ossApi->allocations, strlen(deviceName)+1); - strcpy((char *)deviceInfo.name, deviceName); - entry = (PaOSS_DeviceList *)PaUtil_AllocateMemory(sizeof(PaOSS_DeviceList)); - entry->deviceInfo = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( - ossApi->allocations, sizeof(PaDeviceInfo) ); - entry->next = NULL; - memcpy(entry->deviceInfo, &deviceInfo, sizeof(PaDeviceInfo)); - if (tail) - tail->next = entry; - else { - head = entry; - tail = entry; - } + snprintf(deviceName, sizeof (deviceName), "%s%d", DEVICE_NAME_BASE, i); + + /* PA_DEBUG(("PaOSS BuildDeviceList: trying device %s\n", deviceName )); */ + if( stat( deviceName, &stbuf ) < 0 ) + { + if( ENOENT != errno ) + PA_DEBUG(( "%s: Error stat'ing %s: %s\n", __FUNCTION__, deviceName, strerror( errno ) )); + continue; } - } + if( (testResult = QueryDevice( deviceName, ossApi, &deviceInfo )) != paNoError ) + { + if( testResult != paDeviceUnavailable ) + PA_ENSURE( testResult ); - /* Make an array of PaDeviceInfo pointers out of the linked list */ + continue; + } + + ++numDevices; + if( !deviceInfos || numDevices > maxDeviceInfos ) + { + maxDeviceInfos *= 2; + PA_UNLESS( deviceInfos = (PaDeviceInfo **) realloc( deviceInfos, maxDeviceInfos * sizeof (PaDeviceInfo *) ), + paInsufficientMemory ); + } + deviceInfos[numDevices - 1] = deviceInfo; - numDevices = 0; - entry = head; - while(entry) { - numDevices++; - entry = entry->next; + if( commonApi->info.defaultInputDevice == paNoDevice && deviceInfo->maxInputChannels > 0 ) + commonApi->info.defaultInputDevice = i; + if( commonApi->info.defaultOutputDevice == paNoDevice && deviceInfo->maxOutputChannels > 0 ) + commonApi->info.defaultOutputDevice = i; } - DBUG(("PaOSS BuildDeviceList: Total number of devices found: %d\n", numDevices)); + /* Make an array of PaDeviceInfo pointers out of the linked list */ + + PA_DEBUG(("PaOSS %s: Total number of devices found: %d\n", __FUNCTION__, numDevices)); commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( - ossApi->allocations, sizeof(PaDeviceInfo*) *numDevices ); - - entry = head; - i = 0; - while(entry) { - commonApi->deviceInfos[i] = entry->deviceInfo; - i++; - entry = entry->next; - } + ossApi->allocations, sizeof(PaDeviceInfo*) * numDevices ); + memcpy( commonApi->deviceInfos, deviceInfos, numDevices * sizeof (PaDeviceInfo *) ); commonApi->info.deviceCount = numDevices; - commonApi->info.defaultInputDevice = 0; - commonApi->info.defaultOutputDevice = 0; - return paNoError; +error: + free( deviceInfos ); + + return result; } static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) @@ -468,12 +569,12 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *outputParameters, double sampleRate ) { + PaError result = paNoError; PaDeviceIndex device; PaDeviceInfo *deviceInfo; - PaError result = paNoError; char *deviceName; int inputChannelCount, outputChannelCount; - int tempDevHandle = 0; + int tempDevHandle = -1; int flags; PaSampleFormat inputSampleFormat, outputSampleFormat; @@ -542,12 +643,14 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, /* open the device so we can do more tests */ - if (inputChannelCount > 0) { + if( inputChannelCount > 0 ) + { result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, inputParameters->device, hostApi); if (result != paNoError) return result; } - else { + else + { result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, outputParameters->device, hostApi); if (result != paNoError) return result; @@ -564,548 +667,1134 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, else flags |= O_WRONLY; - if ( (tempDevHandle = open(deviceInfo->name, flags)) == -1 ) + ENSURE_( tempDevHandle = open( deviceInfo->name, flags ), paDeviceUnavailable ); + + /* PaOssStream_Configure will do the rest of the checking for us */ + /* PA_ENSURE( PaOssStream_Configure( tempDevHandle, deviceName, outputChannelCount, &sampleRate ) ); */ + + /* everything succeeded! */ + + error: + if( tempDevHandle >= 0 ) + close( tempDevHandle ); + + return result; +} + +/** Validate stream parameters. + * + * Aspect StreamChannels: We verify that the number of channels is within the allowed range for the device + */ +static PaError ValidateParameters( const PaStreamParameters *parameters, const PaDeviceInfo *deviceInfo, StreamMode mode ) +{ + int maxChans; + + assert( parameters ); + + if( parameters->device == paUseHostApiSpecificDeviceSpecification ) { - DBUG(("PaOSS IsFormatSupported: could not open %s\n", deviceName )); - return paDeviceUnavailable; + return paInvalidDevice; } - /* PaOSS_SetFormat will do the rest of the checking for us */ - - if ((result = PaOSS_SetFormat("PaOSS IsFormatSupported", tempDevHandle, - deviceName, inputChannelCount, outputChannelCount, - &sampleRate)) != paNoError) + maxChans = (mode == StreamMode_In ? deviceInfo->maxInputChannels : + deviceInfo->maxOutputChannels); + if( parameters->channelCount > maxChans ) { - goto error; + return paInvalidChannelCount; } - /* everything succeeded! */ + return paNoError; +} - close(tempDevHandle); +static PaError PaOssStreamComponent_Initialize( PaOssStreamComponent *component, const PaStreamParameters *parameters, + int callbackMode, int fd, const char *deviceName ) +{ + PaError result = paNoError; + assert( component ); - return paFormatIsSupported; + memset( component, 0, sizeof (PaOssStreamComponent) ); - error: - if (tempDevHandle) - close(tempDevHandle); + component->fd = fd; + component->devName = deviceName; + component->userChannelCount = parameters->channelCount; + component->userFormat = parameters->sampleFormat; + component->latency = parameters->suggestedLatency; + component->userInterleaved = !(parameters->sampleFormat & paNonInterleaved); + + if( !callbackMode && !component->userInterleaved ) + { + /* Pre-allocate non-interleaved user provided buffers */ + PA_UNLESS( component->userBuffers = PaUtil_AllocateMemory( sizeof (void *) * component->userChannelCount ), + paInsufficientMemory ); + } - return paSampleFormatNotSupported; +error: + return result; } -/* PaOSSStream - a stream data structure specifically for this implementation */ +static void PaOssStreamComponent_Terminate( PaOssStreamComponent *component ) +{ + assert( component ); + + if( component->fd >= 0 ) + close( component->fd ); + if( component->buffer ) + PaUtil_FreeMemory( component->buffer ); -typedef struct PaOSSStream + if( component->userBuffers ) + PaUtil_FreeMemory( component->userBuffers ); + + PaUtil_FreeMemory( component ); +} + +static PaError ModifyBlocking( int fd, int blocking ) { - PaUtilStreamRepresentation streamRepresentation; - PaUtilCpuLoadMeasurer cpuLoadMeasurer; - PaUtilBufferProcessor bufferProcessor; + PaError result = paNoError; + int fflags; - int deviceHandle; + ENSURE_( fflags = fcntl( fd, F_GETFL ), paUnanticipatedHostError ); - int stopSoon; - int stopNow; - int isActive; + if( blocking ) + fflags &= ~O_NONBLOCK; + else + fflags |= O_NONBLOCK; - int inputChannelCount; - int outputChannelCount; + ENSURE_( fcntl( fd, F_SETFL, fflags ), paUnanticipatedHostError ); - pthread_t thread; +error: + return result; +} - void *inputBuffer; - void *outputBuffer; +static PaError OpenDevices( const char *idevName, const char *odevName, int *idev, int *odev ) +{ + PaError result = paNoError; + int flags = O_NONBLOCK, duplex = 0; + int enableBits = 0; + *idev = *odev = -1; - int lastPosPtr; - double lastStreamBytes; + if( idevName && odevName ) + { + duplex = 1; + flags |= O_RDWR; + } + else if( idevName ) + flags |= O_RDONLY; + else + flags |= O_WRONLY; - int framesProcessed; + /* open first in nonblocking mode, in case it's busy... + * A: then unset the non-blocking attribute */ + assert( flags & O_NONBLOCK ); + if( idevName ) + { + ENSURE_( *idev = open( idevName, flags ), paDeviceUnavailable ); + PA_ENSURE( ModifyBlocking( *idev, 1 ) ); /* Blocking */ - double sampleRate; + /* Initially disable */ + enableBits = ~PCM_ENABLE_INPUT; + ENSURE_( ioctl( *idev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + if( odevName ) + { + if( !idevName ) + { + ENSURE_( *odev = open( odevName, flags ), paDeviceUnavailable ); + PA_ENSURE( ModifyBlocking( *odev, 1 ) ); /* Blocking */ - unsigned long framesPerHostCallback; -} -PaOSSStream; + /* Initially disable */ + enableBits = ~PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( *odev, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + else + { + ENSURE_( *odev = dup( *idev ), paUnanticipatedHostError ); + } + } -/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ +error: + 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 ) +static PaError PaOssStream_Initialize( PaOssStream *stream, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, + PaStreamCallback callback, void *userData, PaStreamFlags streamFlags, + PaOSSHostApiRepresentation *ossApi ) { PaError result = paNoError; - PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi; - PaOSSStream *stream = 0; - PaDeviceIndex device; - PaDeviceInfo *deviceInfo; - audio_buf_info bufinfo; - int bytesPerHostBuffer; - int flags; - int deviceHandle = 0; - char *deviceName; - unsigned long framesPerHostBuffer; - int inputChannelCount, outputChannelCount; - PaSampleFormat inputSampleFormat = paInt16, outputSampleFormat = paInt16; - PaSampleFormat hostInputSampleFormat = paInt16, hostOutputSampleFormat = paInt16; + int idev, odev; + PaUtilHostApiRepresentation *hostApi = &ossApi->inheritedHostApiRep; + const char *idevName = NULL, *odevName = NULL; - if( inputParameters ) - { - inputChannelCount = inputParameters->channelCount; - inputSampleFormat = inputParameters->sampleFormat; + assert( stream ); - /* unless alternate device specification is supported, reject the use of - paUseHostApiSpecificDeviceSpecification */ + memset( stream, 0, sizeof (PaOssStream) ); + stream->isStopped = 1; - if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) - return paInvalidDevice; + PA_ENSURE( PaUtil_InitializeThreading( &stream->threading ) ); - /* check that input device can support inputChannelCount */ - if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) - return paInvalidChannelCount; + if( inputParameters && outputParameters ) + { + if( inputParameters->device == outputParameters->device ) + stream->sharedDevice = 1; + } - /* validate inputStreamInfo */ - if( inputParameters->hostApiSpecificStreamInfo ) - return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + if( inputParameters ) + idevName = hostApi->deviceInfos[inputParameters->device]->name; + if( outputParameters ) + odevName = hostApi->deviceInfos[outputParameters->device]->name; + PA_ENSURE( OpenDevices( idevName, odevName, &idev, &odev ) ); + if( inputParameters ) + { + PA_UNLESS( stream->capture = PaUtil_AllocateMemory( sizeof (PaOssStreamComponent) ), paInsufficientMemory ); + PA_ENSURE( PaOssStreamComponent_Initialize( stream->capture, inputParameters, callback != NULL, idev, idevName ) ); + } + if( outputParameters ) + { + PA_UNLESS( stream->playback = PaUtil_AllocateMemory( sizeof (PaOssStreamComponent) ), paInsufficientMemory ); + PA_ENSURE( PaOssStreamComponent_Initialize( stream->playback, outputParameters, callback != NULL, odev, odevName ) ); + } - hostInputSampleFormat = - PaUtil_SelectClosestAvailableFormat( paInt16, inputSampleFormat ); + if( callback != NULL ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &ossApi->callbackStreamInterface, callback, userData ); + stream->callbackMode = 1; } else { - inputChannelCount = 0; + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &ossApi->blockingStreamInterface, callback, userData ); + } + + ENSURE_( sem_init( &stream->semaphore, 0, 0 ), paInternalError ); + +error: + return result; +} + +static void PaOssStream_Terminate( PaOssStream *stream ) +{ + assert( stream ); + + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_TerminateThreading( &stream->threading ); + + if( stream->capture ) + PaOssStreamComponent_Terminate( stream->capture ); + if( stream->playback ) + PaOssStreamComponent_Terminate( stream->playback ); + + sem_destroy( &stream->semaphore ); + + PaUtil_FreeMemory( stream ); +} + +/** Translate from PA format to OSS native. + * + */ +static PaError Pa2OssFormat( PaSampleFormat paFormat, int *ossFormat ) +{ + switch( paFormat ) + { + case paUInt8: + *ossFormat = AFMT_U8; + break; + case paInt8: + *ossFormat = AFMT_S8; + break; + case paInt16: + *ossFormat = AFMT_S16_NE; + break; + default: + return paInternalError; /* This shouldn't happen */ } - if( outputParameters ) + return paNoError; +} + +/** Return the PA-compatible formats that this device can support. + * + */ +static PaError GetAvailableFormats( PaOssStreamComponent *component, PaSampleFormat *availableFormats ) +{ + PaError result = paNoError; + int mask = 0; + PaSampleFormat frmts = 0; + + ENSURE_( ioctl( component->fd, SNDCTL_DSP_GETFMTS, &mask ), paUnanticipatedHostError ); + if( mask & AFMT_U8 ) + frmts |= paUInt8; + if( mask & AFMT_S8 ) + frmts |= paInt8; + if( mask & AFMT_S16_NE ) + frmts |= paInt16; + else + result = paSampleFormatNotSupported; + + *availableFormats = frmts; + +error: + return result; +} + +static unsigned int PaOssStreamComponent_FrameSize( PaOssStreamComponent *component ) +{ + return Pa_GetSampleSize( component->hostFormat ) * component->hostChannelCount; +} + +/** Buffer size in bytes. + * + */ +static unsigned long PaOssStreamComponent_BufferSize( PaOssStreamComponent *component ) +{ + return PaOssStreamComponent_FrameSize( component ) * component->hostFrames * component->numBufs; +} + +static int CalcHigherLogTwo( int n ) +{ + int log2 = 0; + while( (1<<log2) < n ) log2++; + return log2; +} + +static PaError PaOssStreamComponent_Configure( PaOssStreamComponent *component, double sampleRate, unsigned long framesPerBuffer, + StreamMode streamMode, PaOssStreamComponent *master ) +{ + PaError result = paNoError; + int temp, nativeFormat; + int sr = (int)sampleRate; + PaSampleFormat availableFormats, hostFormat; + int chans = component->userChannelCount; + int frgmt; + int numBufs; + int bytesPerBuf; + double bufSz; + unsigned long fragSz; + audio_buf_info bufInfo; + + /* We may have a situation where only one component (the master) is configured, if both point to the same device. + * In that case, the second component will copy settings from the other */ + if( !master ) { - outputChannelCount = outputParameters->channelCount; - outputSampleFormat = outputParameters->sampleFormat; - - /* unless alternate device specification is supported, reject the use of - paUseHostApiSpecificDeviceSpecification */ + /* Aspect BufferSettings: If framesPerBuffer is unspecified we have to infer a suitable fragment size. + * The hardware need not respect the requested fragment size, so we may have to adapt. + */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + bufSz = component->latency * sampleRate; + fragSz = bufSz / 4; + } + else + { + fragSz = framesPerBuffer; + bufSz = component->latency * sampleRate + fragSz; /* Latency + 1 buffer */ + } - if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) - return paInvalidDevice; + PA_ENSURE( GetAvailableFormats( component, &availableFormats ) ); + hostFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, component->userFormat ); - /* check that output device can support inputChannelCount */ - if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) - return paInvalidChannelCount; + /* OSS demands at least 2 buffers, and 16 bytes per buffer */ + numBufs = PA_MAX( bufSz / fragSz, 2 ); + bytesPerBuf = PA_MAX( fragSz * Pa_GetSampleSize( hostFormat ) * chans, 16 ); - /* validate outputStreamInfo */ - if( outputParameters->hostApiSpecificStreamInfo ) - return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + /* The fragment parameters are encoded like this: + * Most significant byte: number of fragments + * Least significant byte: exponent of fragment size (i.e., for 256, 8) + */ + frgmt = (numBufs << 16) + (CalcHigherLogTwo( bytesPerBuf ) & 0xffff); + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SETFRAGMENT, &frgmt ), paUnanticipatedHostError ); + + /* A: according to the OSS programmer's guide parameters should be set in this order: + * format, channels, rate */ - hostOutputSampleFormat = - PaUtil_SelectClosestAvailableFormat( paInt16, outputSampleFormat ); + /* This format should be deemed good before we get this far */ + PA_ENSURE( Pa2OssFormat( hostFormat, &temp ) ); + nativeFormat = temp; + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SETFMT, &temp ), paUnanticipatedHostError ); + PA_UNLESS( temp == nativeFormat, paInternalError ); + + /* try to set the number of channels */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_CHANNELS, &chans ), paSampleFormatNotSupported ); /* XXX: Should be paInvalidChannelCount? */ + /* It's possible that the minimum number of host channels is greater than what the user requested */ + PA_UNLESS( chans >= component->userChannelCount, paInvalidChannelCount ); + + /* try to set the sample rate */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_SPEED, &sr ), paInvalidSampleRate ); + + /* reject if there's no sample rate within 1% of the one requested */ + if( (fabs( sampleRate - sr ) / sampleRate) > 0.01 ) + { + PA_DEBUG(("%s: Wanted %f, closest sample rate was %d\n", __FUNCTION__, sampleRate, sr )); + PA_ENSURE( paInvalidSampleRate ); + } + + ENSURE_( ioctl( component->fd, streamMode == StreamMode_In ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &bufInfo ), + paUnanticipatedHostError ); + component->numBufs = bufInfo.fragstotal; + + /* This needs to be the last ioctl call before the first read/write, according to the OSS programmer's guide */ + ENSURE_( ioctl( component->fd, SNDCTL_DSP_GETBLKSIZE, &bytesPerBuf ), paUnanticipatedHostError ); + + component->hostFrames = bytesPerBuf / Pa_GetSampleSize( hostFormat ) / chans; + component->hostChannelCount = chans; + component->hostFormat = hostFormat; } else { - outputChannelCount = 0; + component->hostFormat = master->hostFormat; + component->hostFrames = master->hostFrames; + component->hostChannelCount = master->hostChannelCount; + component->numBufs = master->numBufs; } - if( inputChannelCount == 0 && outputChannelCount == 0 ) + PA_UNLESS( component->buffer = PaUtil_AllocateMemory( PaOssStreamComponent_BufferSize( component ) ), + paInsufficientMemory ); + +error: + return result; +} + +static PaError PaOssStreamComponent_Read( PaOssStreamComponent *component, unsigned long *frames ) +{ + PaError result = paNoError; + size_t len = *frames * PaOssStreamComponent_FrameSize( component ); + ssize_t bytesRead; + + ENSURE_( bytesRead = read( component->fd, component->buffer, len ), paUnanticipatedHostError ); + *frames = bytesRead / PaOssStreamComponent_FrameSize( component ); + +error: + return result; +} + +static PaError PaOssStreamComponent_Write( PaOssStreamComponent *component, unsigned long *frames ) +{ + PaError result = paNoError; + size_t len = *frames * PaOssStreamComponent_FrameSize( component ); + ssize_t bytesWritten; + + ENSURE_( bytesWritten = write( component->fd, component->buffer, len ), paUnanticipatedHostError ); + *frames = bytesWritten / PaOssStreamComponent_FrameSize( component ); + +error: + return result; +} + +/** Configure the stream according to input/output parameters. + * + * Aspect StreamChannels: The minimum number of channels supported by the device may exceed that requested by + * the user, if so we'll record the actual number of host channels and adapt later. + */ +static PaError PaOssStream_Configure( PaOssStream *stream, double sampleRate, unsigned long framesPerBuffer, + double *inputLatency, double *outputLatency ) +{ + PaError result = paNoError; + int duplex = stream->capture && stream->playback; + unsigned long framesPerHostBuffer = 0; + + /* We should request full duplex first thing after opening the device */ + if( duplex && stream->sharedDevice ) + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETDUPLEX, 0 ), paUnanticipatedHostError ); + + if( stream->capture ) { - DBUG(("Both inputChannelCount and outputChannelCount are zero!\n")); - return paUnanticipatedHostError; + PaOssStreamComponent *component = stream->capture; + PaOssStreamComponent_Configure( component, sampleRate, framesPerBuffer, StreamMode_In, NULL ); + + assert( component->hostChannelCount > 0 ); + assert( component->hostFrames > 0 ); + + *inputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate; } + if( stream->playback ) + { + PaOssStreamComponent *component = stream->playback, *master = stream->sharedDevice ? stream->capture : NULL; + PA_ENSURE( PaOssStreamComponent_Configure( component, sampleRate, framesPerBuffer, StreamMode_Out, + master ) ); - /* validate platform specific flags */ - if( (streamFlags & paPlatformSpecificFlags) != 0 ) - return paInvalidFlag; /* unexpected platform specific flag */ + assert( component->hostChannelCount > 0 ); + assert( component->hostFrames > 0 ); - /* - * open the device and set parameters here - */ + *outputLatency = component->hostFrames * (component->numBufs - 1) / sampleRate; + } - if (inputChannelCount == 0 && outputChannelCount == 0) - return paInvalidChannelCount; + if( duplex ) + framesPerHostBuffer = PA_MIN( stream->capture->hostFrames, stream->playback->hostFrames ); + else if( stream->capture ) + framesPerHostBuffer = stream->capture->hostFrames; + else if( stream->playback ) + framesPerHostBuffer = stream->playback->hostFrames; - /* if full duplex, make sure that they're the same device */ + stream->framesPerHostBuffer = framesPerHostBuffer; + stream->pollTimeout = (int) ceil( 1e6 * framesPerHostBuffer / sampleRate ); /* Period in usecs, rounded up */ - if (inputChannelCount > 0 && outputChannelCount > 0 && - inputParameters->device != outputParameters->device) - return paInvalidDevice; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; - /* if full duplex, also make sure that they're the same number of channels */ +error: + return result; +} - if (inputChannelCount > 0 && outputChannelCount > 0 && - inputChannelCount != outputChannelCount) - return paInvalidChannelCount; +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ - /* note that inputParameters and outputParameters device indicies are - * already in host format */ - device = (inputChannelCount > 0 ) - ? inputParameters->device - : outputParameters->device; +/** Open a PA OSS stream. + * + * Aspect StreamChannels: The number of channels is specified per direction (in/out), and can differ between the + * two. However, OSS doesn't support separate configuration spaces for capture and playback so if both + * directions are the same device we will demand the same number of channels. The number of channels can range + * from 1 to the maximum supported by the device. + * + * Aspect BufferSettings: If framesPerBuffer != paFramesPerBufferUnspecified the number of frames per callback + * must reflect this, in addition the host latency per device should approximate the corresponding + * suggestedLatency. Based on these constraints we need to determine a number of frames per host buffer that + * both capture and playback can agree on (they can be different devices), the buffer processor can adapt + * between host and user buffer size, but the ratio should preferably be integral. + */ +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 result = paNoError; + PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi; + PaOssStream *stream = NULL; + int inputChannelCount = 0, outputChannelCount = 0; + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0, inputHostFormat = 0, outputHostFormat = 0; + const PaDeviceInfo *inputDeviceInfo = 0, *outputDeviceInfo = 0; + int bpInitialized = 0; + double inLatency, outLatency; - deviceInfo = hostApi->deviceInfos[device]; - deviceName = (char *)deviceInfo->name; + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ - flags = O_NONBLOCK; - if (inputChannelCount > 0 && outputChannelCount > 0) - flags |= O_RDWR; - else if (inputChannelCount > 0) - flags |= O_RDONLY; - else - flags |= O_WRONLY; + if( inputParameters ) + { + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + inputDeviceInfo = hostApi->deviceInfos[inputParameters->device]; + PA_ENSURE( ValidateParameters( inputParameters, inputDeviceInfo, StreamMode_In ) ); - /* open first in nonblocking mode, in case it's busy... */ - if ( (deviceHandle = open(deviceInfo->name, flags)) == -1 ) + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + if( outputParameters ) { - DBUG(("PaOSS OpenStream: could not open %s\n", deviceName )); - return paDeviceUnavailable; + outputDeviceInfo = hostApi->deviceInfos[outputParameters->device]; + PA_ENSURE( ValidateParameters( outputParameters, outputDeviceInfo, StreamMode_Out ) ); + + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; } - /* if that succeeded, immediately open it again in blocking mode */ - close(deviceHandle); - flags -= O_NONBLOCK; - if ( (deviceHandle = open(deviceInfo->name, flags)) == -1 ) + /* Aspect StreamChannels: We currently demand that number of input and output channels are the same, if the same + * device is opened for both directions + */ + if( inputChannelCount > 0 && outputChannelCount > 0 ) { - DBUG(("PaOSS OpenStream: could not open %s in blocking mode\n", deviceName )); - return paDeviceUnavailable; + if( inputParameters->device == outputParameters->device ) + { + if( inputParameters->channelCount != outputParameters->channelCount ) + return paInvalidChannelCount; + } } + + /* allocate and do basic initialization of the stream structure */ + PA_UNLESS( stream = (PaOssStream*)PaUtil_AllocateMemory( sizeof(PaOssStream) ), paInsufficientMemory ); + PaOssStream_Initialize( stream, inputParameters, outputParameters, streamCallback, userData, streamFlags, ossHostApi ); + + PA_ENSURE( PaOssStream_Configure( stream, sampleRate, framesPerBuffer, &inLatency, &outLatency ) ); - if ((result = PaOSS_SetFormat("PaOSS OpenStream", deviceHandle, - deviceName, inputChannelCount, outputChannelCount, - &sampleRate)) != paNoError) + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + if( inputParameters ) { - goto error; + inputHostFormat = stream->capture->hostFormat; + stream->streamRepresentation.streamInfo.inputLatency = inLatency + + PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor ) / sampleRate; + } + if( outputParameters ) + { + outputHostFormat = stream->playback->hostFormat; + stream->streamRepresentation.streamInfo.outputLatency = outLatency + + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor ) / sampleRate; } - /* Compute number of frames per host buffer - if we can't retrieve the - * value, use the user's value instead + /* Initialize buffer processor with fixed host buffer size. + * Aspect StreamSampleFormat: Here we commit the user and host sample formats, PA infrastructure will + * convert between the two. */ - - if ( ioctl(deviceHandle, SNDCTL_DSP_GETBLKSIZE, &bytesPerHostBuffer) == 0) + PA_ENSURE( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, inputHostFormat, outputChannelCount, outputSampleFormat, + outputHostFormat, sampleRate, streamFlags, framesPerBuffer, stream->framesPerHostBuffer, + paUtilFixedHostBufferSize, streamCallback, userData ) ); + bpInitialized = 1; + + *s = (PaStream*)stream; + + return result; + +error: + if( bpInitialized ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + if( stream ) + PaOssStream_Terminate( stream ); + + return result; +} + +/*! Poll on I/O filedescriptors. + + Poll till we've determined there's data for read or write. In the full-duplex case, + we don't want to hang around forever waiting for either input or output frames, so + whenever we have a timed out filedescriptor we check if we're nearing under/overrun + for the other direction (critical limit set at one buffer). If so, we exit the waiting + state, and go on with what we got. We align the number of frames on a host buffer + boundary because it is possible that the buffer size differs for the two directions and + the host buffer size is a compromise between the two. + */ +static PaError PaOssStream_WaitForFrames( PaOssStream *stream, unsigned long *frames ) +{ + PaError result = paNoError; + int pollPlayback = 0, pollCapture = 0; + int captureAvail = INT_MAX, playbackAvail = INT_MAX, commonAvail; + audio_buf_info bufInfo; + /* int ofs = 0, nfds = stream->nfds; */ + fd_set readFds, writeFds; + int nfds = 0; + struct timeval selectTimeval = {0, 0}; + unsigned long timeout = stream->pollTimeout; /* In usecs */ + int captureFd = -1, playbackFd = -1; + + assert( stream ); + assert( frames ); + + if( stream->capture ) { - framesPerHostBuffer = bytesPerHostBuffer / 2 / (inputChannelCount>0? inputChannelCount: outputChannelCount); + pollCapture = 1; + captureFd = stream->capture->fd; + /* stream->capture->pfd->events = POLLIN; */ + } + if( stream->playback ) + { + pollPlayback = 1; + playbackFd = stream->playback->fd; + /* stream->playback->pfd->events = POLLOUT; */ } - else - framesPerHostBuffer = framesPerBuffer; - /* Allocate stream and fill in structure */ + FD_ZERO( &readFds ); + FD_ZERO( &writeFds ); - stream = (PaOSSStream*)PaUtil_AllocateMemory( sizeof(PaOSSStream) ); - if( !stream ) + while( pollPlayback || pollCapture ) { - result = paInsufficientMemory; - goto error; + pthread_testcancel(); + + /* select may modify the timeout parameter */ + selectTimeval.tv_usec = timeout; + nfds = 0; + + if( pollCapture ) + { + FD_SET( captureFd, &readFds ); + nfds = captureFd + 1; + } + if( pollPlayback ) + { + FD_SET( playbackFd, &writeFds ); + nfds = PA_MAX( nfds, playbackFd + 1 ); + } + ENSURE_( select( nfds, &readFds, &writeFds, NULL, &selectTimeval ), paUnanticipatedHostError ); + /* + if( poll( stream->pfds + ofs, nfds, stream->pollTimeout ) < 0 ) + { + + ENSURE_( -1, paUnanticipatedHostError ); + } + */ + pthread_testcancel(); + + if( pollCapture ) + { + if( FD_ISSET( captureFd, &readFds ) ) + { + FD_CLR( captureFd, &readFds ); + pollCapture = 0; + } + /* + if( stream->capture->pfd->revents & POLLIN ) + { + --nfds; + ++ofs; + pollCapture = 0; + } + */ + else if( stream->playback ) /* Timed out, go on with playback? */ + { + /*PA_DEBUG(( "%s: Trying to poll again for capture frames, pollTimeout: %d\n", + __FUNCTION__, stream->pollTimeout ));*/ + } + } + if( pollPlayback ) + { + if( FD_ISSET( playbackFd, &writeFds ) ) + { + FD_CLR( playbackFd, &writeFds ); + pollPlayback = 0; + } + /* + if( stream->playback->pfd->revents & POLLOUT ) + { + --nfds; + pollPlayback = 0; + } + */ + else if( stream->capture ) /* Timed out, go on with capture? */ + { + /*PA_DEBUG(( "%s: Trying to poll again for playback frames, pollTimeout: %d\n\n", + __FUNCTION__, stream->pollTimeout ));*/ + } + } } - if( streamCallback ) + if( stream->capture ) { - PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - &ossHostApi->callbackStreamInterface, streamCallback, userData ); + ENSURE_( ioctl( captureFd, SNDCTL_DSP_GETISPACE, &bufInfo ), paUnanticipatedHostError ); + captureAvail = bufInfo.fragments * stream->capture->hostFrames; + if( !captureAvail ) + PA_DEBUG(( "%s: captureAvail: 0\n", __FUNCTION__ )); + + captureAvail = captureAvail == 0 ? INT_MAX : captureAvail; /* Disregard if zero */ } - else + if( stream->playback ) { - PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - &ossHostApi->blockingStreamInterface, streamCallback, userData ); - } - - stream->streamRepresentation.streamInfo.inputLatency = 0.; - stream->streamRepresentation.streamInfo.outputLatency = 0.; + ENSURE_( ioctl( playbackFd, SNDCTL_DSP_GETOSPACE, &bufInfo ), paUnanticipatedHostError ); + playbackAvail = bufInfo.fragments * stream->playback->hostFrames; + if( !playbackAvail ) + { + PA_DEBUG(( "%s: playbackAvail: 0\n", __FUNCTION__ )); + } - if (inputChannelCount > 0) { - if (ioctl( deviceHandle, SNDCTL_DSP_GETISPACE, &bufinfo) == 0) - stream->streamRepresentation.streamInfo.inputLatency = - (bufinfo.fragsize * bufinfo.fragstotal) / sampleRate; + playbackAvail = playbackAvail == 0 ? INT_MAX : playbackAvail; /* Disregard if zero */ } - if (outputChannelCount > 0) { - if (ioctl( deviceHandle, SNDCTL_DSP_GETOSPACE, &bufinfo) == 0) - stream->streamRepresentation.streamInfo.outputLatency = - (bufinfo.fragsize * bufinfo.fragstotal) / sampleRate; - } + commonAvail = PA_MIN( captureAvail, playbackAvail ); + if( commonAvail == INT_MAX ) + commonAvail = 0; + commonAvail -= commonAvail % stream->framesPerHostBuffer; - stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + assert( commonAvail != INT_MAX ); + assert( commonAvail >= 0 ); + *frames = commonAvail; - PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); +error: + return result; +} - /* we assume a fixed host buffer size in this example, but the buffer processor - can also support bounded and unknown host buffer sizes by passing - paUtilBoundedHostBufferSize or paUtilUnknownHostBufferSize instead of - paUtilFixedHostBufferSize below. */ - - result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, - inputChannelCount, inputSampleFormat, hostInputSampleFormat, - outputChannelCount, outputSampleFormat, hostOutputSampleFormat, - sampleRate, streamFlags, framesPerBuffer, - framesPerHostBuffer, paUtilFixedHostBufferSize, - streamCallback, userData ); - if( result != paNoError ) - goto error; +/** Prepare stream for capture/playback. + * + * In order to synchronize capture and playback properly we use the SETTRIGGER command. + */ +static PaError PaOssStream_Prepare( PaOssStream *stream ) +{ + PaError result = paNoError; + int enableBits = 0; - stream->framesPerHostCallback = framesPerHostBuffer; + if( stream->triggered ) + return result; - stream->stopSoon = 0; - stream->stopNow = 0; - stream->isActive = 0; - stream->thread = 0; - stream->lastPosPtr = 0; - stream->lastStreamBytes = 0; - stream->sampleRate = sampleRate; - stream->framesProcessed = 0; - stream->deviceHandle = deviceHandle; + if( stream->playback ) + { + size_t bufSz = PaOssStreamComponent_BufferSize( stream->playback ); + memset( stream->playback->buffer, 0, bufSz ); - if (inputChannelCount > 0) - stream->inputBuffer = PaUtil_AllocateMemory( 2 * framesPerHostBuffer * inputChannelCount ); - else - stream->inputBuffer = NULL; + /* Looks like we have to turn off blocking before we try this, but if we don't fill the buffer + * OSS will complain. */ + PA_ENSURE( ModifyBlocking( stream->playback->fd, 0 ) ); + while (1) + { + if( write( stream->playback->fd, stream->playback->buffer, bufSz ) < 0 ) + break; + } + PA_ENSURE( ModifyBlocking( stream->playback->fd, 1 ) ); + } - if (outputChannelCount > 0) - stream->outputBuffer = PaUtil_AllocateMemory( 2 * framesPerHostBuffer * outputChannelCount ); + if( stream->sharedDevice ) + { + enableBits = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } else - stream->outputBuffer = NULL; + { + if( stream->capture ) + { + enableBits = PCM_ENABLE_INPUT; + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + if( stream->playback ) + { + enableBits = PCM_ENABLE_OUTPUT; + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_SETTRIGGER, &enableBits ), paUnanticipatedHostError ); + } + } - stream->inputChannelCount = inputChannelCount; - stream->outputChannelCount = outputChannelCount; + /* Ok, we have triggered the stream */ + stream->triggered = 1; + +error: + return result; +} - *s = (PaStream*)stream; +/** Stop audio processing + * + */ +static PaError PaOssStream_Stop( PaOssStream *stream, int abort ) +{ + PaError result = paNoError; - result = paNoError; + /* Looks like the only safe way to stop audio without reopening the device is SNDCTL_DSP_POST. + * Also disable capture/playback till the stream is started again */ + if( stream->capture ) + { + ENSURE_( ioctl( stream->capture->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + } + if( stream->playback && !stream->sharedDevice ) + { + ENSURE_( ioctl( stream->playback->fd, SNDCTL_DSP_POST, 0 ), paUnanticipatedHostError ); + } +error: return result; +} -error: - if( stream ) - PaUtil_FreeMemory( stream ); +/** Clean up after thread exit. + * + * Aspect StreamState: If the user has registered a streamFinishedCallback it will be called here + */ +static void OnExit( void *data ) +{ + PaOssStream *stream = (PaOssStream *) data; + assert( data ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + PaOssStream_Stop( stream, stream->callbackAbort ); + + PA_DEBUG(( "OnExit: Stoppage\n" )); + + /* Eventually notify user all buffers have played */ + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + + stream->callbackAbort = 0; /* Clear state */ + stream->isActive = 0; +} - if( deviceHandle ) - close( deviceHandle ); +static PaError SetUpBuffers( PaOssStream *stream, unsigned long framesAvail ) +{ + PaError result = paNoError; + + if( stream->capture ) + { + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->capture->buffer, + stream->capture->hostChannelCount ); + PaUtil_SetInputFrameCount( &stream->bufferProcessor, framesAvail ); + } + if( stream->playback ) + { + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->playback->buffer, + stream->playback->hostChannelCount ); + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, framesAvail ); + } return result; } -static void *PaOSS_AudioThreadProc(void *userData) +/** Thread procedure for callback processing. + * + * Aspect StreamState: StartStream will wait on this to initiate audio processing, useful in case the + * callback should be used for buffer priming. When the stream is cancelled a separate function will + * take care of the transition to the Callback Finished state (the stream isn't considered Stopped + * before StopStream() or AbortStream() are called). + */ +static void *PaOSS_AudioThreadProc( void *userData ) { - PaOSSStream *stream = (PaOSSStream*)userData; + PaError result = paNoError; + PaOssStream *stream = (PaOssStream*)userData; + unsigned long framesAvail, framesProcessed; + int callbackResult = paContinue; + int triggered = stream->triggered; /* See if SNDCTL_DSP_TRIGGER has been issued already */ + int initiateProcessing = triggered; /* Already triggered? */ + PaStreamCallbackFlags cbFlags = 0; /* We might want to keep state across iterations */ + + /* +#if ( SOUND_VERSION > 0x030904 ) + audio_errinfo errinfo; +#endif +*/ + + assert( stream ); - DBUG(("PaOSS AudioThread: %d in, %d out\n", stream->inputChannelCount, stream->outputChannelCount)); + pthread_cleanup_push( &OnExit, stream ); /* Execute OnExit when exiting */ + + /* The first time the stream is started we use SNDCTL_DSP_TRIGGER to accurately start capture and + * playback in sync, when the stream is restarted after being stopped we simply start by reading/ + * writing. + */ + PA_ENSURE( PaOssStream_Prepare( stream ) ); - while( (stream->stopNow == 0) && (stream->stopSoon == 0) ) { + /* If we are to initiate processing implicitly by reading/writing data, we start off in blocking mode */ + if( initiateProcessing ) + { + /* Make sure devices are in blocking mode */ + if( stream->capture ) + ModifyBlocking( stream->capture->fd, 1 ); + if( stream->playback ) + ModifyBlocking( stream->playback->fd, 1 ); + } + + while( 1 ) + { PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* TODO: IMPLEMENT ME */ - int callbackResult; - unsigned long framesProcessed; - int bytesRequested; - int bytesRead, bytesWritten; - int delta; - int result; - count_info info; - - PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); - - PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, - 0 /* @todo pass underflow/overflow flags when necessary */ ); - - /* - depending on whether the host buffers are interleaved, non-interleaved - or a mixture, you will want to call PaUtil_SetInterleaved*Channels(), - PaUtil_SetNonInterleaved*Channel() or PaUtil_Set*Channel() here. - */ - if ( stream->inputChannelCount > 0 ) - { - bytesRequested = stream->framesPerHostCallback * 2 * stream->inputChannelCount; - bytesRead = read( stream->deviceHandle, stream->inputBuffer, bytesRequested ); - - PaUtil_SetInputFrameCount( &stream->bufferProcessor, bytesRead/(2*stream->inputChannelCount)); - PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, - 0, /* first channel of inputBuffer is channel 0 */ - stream->inputBuffer, - 0 ); /* 0 - use inputChannelCount passed to init buffer processor */ - } + pthread_testcancel(); - if ( stream->outputChannelCount > 0 ) + if( stream->callbackStop && callbackResult == paContinue ) { - PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); - PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, - 0, /* first channel of outputBuffer is channel 0 */ - stream->outputBuffer, - 0 ); /* 0 - use outputChannelCount passed to init buffer processor */ + PA_DEBUG(( "Setting callbackResult to paComplete\n" )); + callbackResult = paComplete; } - callbackResult = paContinue; - framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); - - PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); - - if( callbackResult == paContinue ) + /* Aspect StreamState: Because of the messy OSS scheme we can't explicitly trigger device start unless + * the stream has been recently started, we will have to go right ahead and read/write in blocking + * fashion to trigger operation. Therefore we begin with processing one host buffer before we switch + * to non-blocking mode. + */ + if( !initiateProcessing ) { - /* nothing special to do */ + PA_ENSURE( PaOssStream_WaitForFrames( stream, &framesAvail ) ); /* Wait on available frames */ + assert( framesAvail % stream->framesPerHostBuffer == 0 ); } - else if( callbackResult == paAbort ) + else { - /* once finished, call the finished callback */ - if( stream->streamRepresentation.streamFinishedCallback != 0 ) - stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); - - return NULL; /* return from the loop */ + framesAvail = stream->framesPerHostBuffer; } - else if ( callbackResult == paComplete ) + + while( framesAvail > 0 ) { - /* User callback has asked us to stop with paComplete or other non-zero value */ - - /* once finished, call the finished callback */ - if( stream->streamRepresentation.streamFinishedCallback != 0 ) - stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + unsigned long frames = framesAvail; + + pthread_testcancel(); + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* Read data */ + if ( stream->capture ) + { + PA_ENSURE( PaOssStreamComponent_Read( stream->capture, &frames ) ); + assert( frames == framesAvail ); + } + +#if ( SOUND_VERSION >= 0x030904 ) + /* + Check with OSS to see if there have been any under/overruns + since last time we checked. + */ + /* + if( ioctl( stream->deviceHandle, SNDCTL_DSP_GETERROR, &errinfo ) >= 0 ) + { + if( errinfo.play_underruns ) + cbFlags |= paOutputUnderflow ; + if( errinfo.record_underruns ) + cbFlags |= paInputUnderflow ; + } + else + PA_DEBUG(( "SNDCTL_DSP_GETERROR command failed: %s\n", strerror( errno ) )); + */ +#endif - stream->stopSoon = 1; - } + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, + cbFlags ); + cbFlags = 0; + PA_ENSURE( SetUpBuffers( stream, framesAvail ) ); - if ( stream->outputChannelCount > 0 ) { - /* write output samples AFTER we've checked the callback result code */ + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &callbackResult ); + assert( framesProcessed == framesAvail ); + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); - bytesRequested = stream->framesPerHostCallback * 2 * stream->outputChannelCount; - bytesWritten = write( stream->deviceHandle, stream->outputBuffer, bytesRequested ); + if ( stream->playback ) + { + frames = framesAvail; - /* TODO: handle bytesWritten != bytesRequested (slippage?) */ - } + PA_ENSURE( PaOssStreamComponent_Write( stream->playback, &frames ) ); + assert( frames == framesAvail ); - /* Update current stream time (using a double so that - we don't wrap around like info.bytes does) */ - if( stream->outputChannelCount > 0 ) - result = ioctl( stream->deviceHandle, SNDCTL_DSP_GETOPTR, &info); - else - result = ioctl( stream->deviceHandle, SNDCTL_DSP_GETIPTR, &info); + /* TODO: handle bytesWritten != bytesRequested (slippage?) */ + } - if (result == 0) { - delta = ( info.bytes - stream->lastPosPtr ) & 0x000FFFFF; - stream->lastStreamBytes += delta; - stream->lastPosPtr = info.bytes; + framesAvail -= framesProcessed; + stream->framesProcessed += framesProcessed; + + if( callbackResult != paContinue ) + break; } - stream->framesProcessed += stream->framesPerHostCallback; + if( initiateProcessing || !triggered ) + { + /* Non-blocking */ + if( stream->capture ) + PA_ENSURE( ModifyBlocking( stream->capture->fd, 0 ) ); + if( stream->playback && !stream->sharedDevice ) + PA_ENSURE( ModifyBlocking( stream->playback->fd, 0 ) ); + + initiateProcessing = 0; + sem_post( &stream->semaphore ); + } + + if( callbackResult != paContinue ) + { + stream->callbackAbort = callbackResult == paAbort; + if( stream->callbackAbort || PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) + break; + } } - return NULL; + pthread_cleanup_pop( 1 ); + +error: + pthread_exit( NULL ); } -/* - When CloseStream() is called, the multi-api layer ensures that - the stream has already been stopped or aborted. -*/ +/** Close the stream. + * + */ static PaError CloseStream( PaStream* s ) { PaError result = paNoError; - PaOSSStream *stream = (PaOSSStream*)s; + PaOssStream *stream = (PaOssStream*)s; - close(stream->deviceHandle); - - if ( stream->inputBuffer ) - PaUtil_FreeMemory( stream->inputBuffer ); - if ( stream->outputBuffer ) - PaUtil_FreeMemory( stream->outputBuffer ); + assert( stream ); PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); - PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); - PaUtil_FreeMemory( stream ); + PaOssStream_Terminate( stream ); return result; } - +/** Start the stream. + * + * Aspect StreamState: After returning, the stream shall be in the Active state, implying that an eventual + * callback will be repeatedly called in a separate thread. If a separate thread is started this function + * will block untill it has started processing audio, otherwise audio processing is started directly. + */ static PaError StartStream( PaStream *s ) { PaError result = paNoError; - PaOSSStream *stream = (PaOSSStream*)s; - int presult; + PaOssStream *stream = (PaOssStream*)s; stream->isActive = 1; + stream->isStopped = 0; stream->lastPosPtr = 0; stream->lastStreamBytes = 0; stream->framesProcessed = 0; - DBUG(("PaOSS StartStream\n")); - /* only use the thread for callback streams */ - if( stream->bufferProcessor.streamCallback ) { - presult = pthread_create(&stream->thread, - NULL /*pthread_attr_t * attr*/, - (void*)PaOSS_AudioThreadProc, (void *)stream); + if( stream->bufferProcessor.streamCallback ) + { + PA_ENSURE( PaUtil_StartThreading( &stream->threading, &PaOSS_AudioThreadProc, stream ) ); + sem_wait( &stream->semaphore ); } - + else + PA_ENSURE( PaOssStream_Prepare( stream ) ); + +error: return result; } - -static PaError StopStream( PaStream *s ) +static PaError RealStop( PaOssStream *stream, int abort ) { PaError result = paNoError; - PaOSSStream *stream = (PaOSSStream*)s; - stream->stopSoon = 1; + if( stream->callbackMode ) + { + if( abort ) + stream->callbackAbort = 1; + else + stream->callbackStop = 1; - /* only use the thread for callback streams */ - if( stream->bufferProcessor.streamCallback ) - pthread_join( stream->thread, NULL ); + PA_ENSURE( PaUtil_CancelThreading( &stream->threading, !abort, NULL ) ); - stream->stopSoon = 0; - stream->stopNow = 0; - stream->isActive = 0; + stream->callbackStop = stream->callbackAbort = 0; + } + else + PA_ENSURE( PaOssStream_Stop( stream, abort ) ); - DBUG(("PaOSS StopStream: Stopped stream\n")); + stream->isStopped = 1; +error: return result; } +/** Stop the stream. + * + * Aspect StreamState: This will cause the stream to transition to the Stopped state, playing all enqueued + * buffers. + */ +static PaError StopStream( PaStream *s ) +{ + return RealStop( (PaOssStream *)s, 0 ); +} +/** Abort the stream. + * + * Aspect StreamState: This will cause the stream to transition to the Stopped state, discarding all enqueued + * buffers. Note that the buffers are not currently correctly discarded, this is difficult without closing + * the OSS device. + */ static PaError AbortStream( PaStream *s ) { - PaError result = paNoError; - PaOSSStream *stream = (PaOSSStream*)s; - - stream->stopNow = 1; - - /* only use the thread for callback streams */ - if( stream->bufferProcessor.streamCallback ) - pthread_join( stream->thread, NULL ); - - stream->stopSoon = 0; - stream->stopNow = 0; - stream->isActive = 0; - - DBUG(("PaOSS AbortStream: Stopped stream\n")); - - return result; + return RealStop( (PaOssStream *)s, 1 ); } - +/** Is the stream in the Stopped state. + * + */ static PaError IsStreamStopped( PaStream *s ) { - PaOSSStream *stream = (PaOSSStream*)s; + PaOssStream *stream = (PaOssStream*)s; - return (!stream->isActive); + return (stream->isStopped); } - +/** Is the stream in the Active state. + * + */ static PaError IsStreamActive( PaStream *s ) { - PaOSSStream *stream = (PaOSSStream*)s; + PaOssStream *stream = (PaOssStream*)s; return (stream->isActive); } - static PaTime GetStreamTime( PaStream *s ) { - PaOSSStream *stream = (PaOSSStream*)s; + PaOssStream *stream = (PaOssStream*)s; count_info info; int delta; - if( stream->outputChannelCount > 0 ) { - if (ioctl( stream->deviceHandle, SNDCTL_DSP_GETOPTR, &info) == 0) { + if( stream->playback ) { + if( ioctl( stream->playback->fd, SNDCTL_DSP_GETOPTR, &info) == 0 ) { delta = ( info.bytes - stream->lastPosPtr ) & 0x000FFFFF; - return ( stream->lastStreamBytes + delta) / ( stream->outputChannelCount * 2 ) / stream->sampleRate; + return ( stream->lastStreamBytes + delta) / PaOssStreamComponent_FrameSize( stream->playback ) / stream->sampleRate; } } else { - if (ioctl( stream->deviceHandle, SNDCTL_DSP_GETIPTR, &info) == 0) { + if (ioctl( stream->capture->fd, SNDCTL_DSP_GETIPTR, &info) == 0) { delta = (info.bytes - stream->lastPosPtr) & 0x000FFFFF; - return ( stream->lastStreamBytes + delta) / ( stream->inputChannelCount * 2 ) / stream->sampleRate; + return ( stream->lastStreamBytes + delta) / PaOssStreamComponent_FrameSize( stream->capture ) / stream->sampleRate; } } @@ -1117,7 +1806,7 @@ static PaTime GetStreamTime( PaStream *s ) static double GetStreamCpuLoad( PaStream* s ) { - PaOSSStream *stream = (PaOSSStream*)s; + PaOssStream *stream = (PaOssStream*)s; return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); } @@ -1134,16 +1823,36 @@ static PaError ReadStream( PaStream* s, void *buffer, unsigned long frames ) { - PaOSSStream *stream = (PaOSSStream*)s; + PaOssStream *stream = (PaOssStream*)s; int bytesRequested, bytesRead; + unsigned long framesRequested; + void *userBuffer; + + /* If user input is non-interleaved, PaUtil_CopyInput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( stream->bufferProcessor.userInputIsInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + userBuffer = stream->capture->userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->capture->userChannelCount ); + } - bytesRequested = frames * 2 * stream->inputChannelCount; - bytesRead = read( stream->deviceHandle, stream->inputBuffer, bytesRequested ); + while( frames ) + { + framesRequested = PA_MIN( frames, stream->capture->hostFrames ); - if ( bytesRequested != bytesRead ) - return paUnanticipatedHostError; - else - return paNoError; + bytesRequested = framesRequested * PaOssStreamComponent_FrameSize( stream->capture ); + bytesRead = read( stream->capture->fd, stream->capture->buffer, bytesRequested ); + if ( bytesRequested != bytesRead ) + return paUnanticipatedHostError; + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, stream->capture->hostFrames ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->capture->buffer, stream->capture->hostChannelCount ); + PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesRequested ); + frames -= framesRequested; + } + return paNoError; } @@ -1151,46 +1860,59 @@ static PaError WriteStream( PaStream* s, const void *buffer, unsigned long frames ) { - PaOSSStream *stream = (PaOSSStream*)s; + PaOssStream *stream = (PaOssStream*)s; int bytesRequested, bytesWritten; + unsigned long framesConverted; + const void *userBuffer; + + /* If user output is non-interleaved, PaUtil_CopyOutput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( stream->bufferProcessor.userOutputIsInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + userBuffer = stream->playback->userBuffers; + memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->playback->userChannelCount ); + } - bytesRequested = frames * 2 * stream->outputChannelCount; - bytesWritten = write( stream->deviceHandle, buffer, bytesRequested ); + while( frames ) + { + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, stream->playback->hostFrames ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->playback->buffer, stream->playback->hostChannelCount ); - if ( bytesRequested != bytesWritten ) - return paUnanticipatedHostError; - else - return paNoError; + framesConverted = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, frames ); + frames -= framesConverted; + + bytesRequested = framesConverted * PaOssStreamComponent_FrameSize( stream->playback ); + bytesWritten = write( stream->playback->fd, stream->playback->buffer, bytesRequested ); + + if ( bytesRequested != bytesWritten ) + return paUnanticipatedHostError; + } + return paNoError; } static signed long GetStreamReadAvailable( PaStream* s ) { - PaOSSStream *stream = (PaOSSStream*)s; + PaOssStream *stream = (PaOssStream*)s; audio_buf_info info; - if ( ioctl(stream->deviceHandle, SNDCTL_DSP_GETISPACE, &info) == 0) - { - int bytesAvailable = info.fragments * info.fragsize; - return ( bytesAvailable / 2 / stream->inputChannelCount ); - } - else - return 0; /* TODO: is this right for "don't know"? */ + if( ioctl( stream->capture->fd, SNDCTL_DSP_GETISPACE, &info ) < 0 ) + return paUnanticipatedHostError; + return info.fragments * stream->capture->hostFrames; } +/* TODO: Compute number of allocated bytes somewhere else, can we use ODELAY with capture */ static signed long GetStreamWriteAvailable( PaStream* s ) { - PaOSSStream *stream = (PaOSSStream*)s; - - audio_buf_info info; + PaOssStream *stream = (PaOssStream*)s; + int delay = 0; - if ( ioctl(stream->deviceHandle, SNDCTL_DSP_GETOSPACE, &info) == 0) - { - int bytesAvailable = info.fragments * info.fragsize; - return ( bytesAvailable / 2 / stream->outputChannelCount ); - } - else - return 0; /* TODO: is this right for "don't know"? */ + if( ioctl( stream->playback->fd, SNDCTL_DSP_GETODELAY, &delay ) < 0 ) + return paUnanticipatedHostError; + + return (PaOssStreamComponent_BufferSize( stream->playback ) - delay) / PaOssStreamComponent_FrameSize( stream->playback ); } diff --git a/pd/portaudio/pa_win/pa_win_hostapis.c b/pd/portaudio/pa_win/pa_win_hostapis.c index fa6048e4..3db1e18a 100644 --- a/pd/portaudio/pa_win/pa_win_hostapis.c +++ b/pd/portaudio/pa_win/pa_win_hostapis.c @@ -1,5 +1,5 @@ /* - * $Id: pa_win_hostapis.c,v 1.1.2.9 2003/09/15 18:30:26 rossbencina Exp $ + * $Id: pa_win_hostapis.c,v 1.1.2.10 2004/09/08 17:31:37 rossbencina Exp $ * Portable Audio I/O Library Windows initialization table * * Based on the Open Source API proposed by Ross Bencina @@ -48,6 +48,7 @@ PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiI PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); #ifdef __cplusplus } @@ -69,6 +70,12 @@ PaUtilHostApiInitializer *paHostApiInitializers[] = PaAsio_Initialize, #endif +/* +#ifndef PA_NO_WDMKS + PaWinWdm_Initialize, +#endif +*/ + PaSkeleton_Initialize, /* just for testing */ 0 /* NULL terminated array */ diff --git a/pd/portaudio/pa_win_ds/dsound_wrapper.h b/pd/portaudio/pa_win_ds/dsound_wrapper.h index 0dad1592..9e3f565f 100644 --- a/pd/portaudio/pa_win_ds/dsound_wrapper.h +++ b/pd/portaudio/pa_win_ds/dsound_wrapper.h @@ -1,7 +1,7 @@ #ifndef __DSOUND_WRAPPER_H #define __DSOUND_WRAPPER_H /* - * $Id: dsound_wrapper.h,v 1.1.1.1.2.7 2003/09/07 13:04:53 rossbencina Exp $ + * $Id: dsound_wrapper.h,v 1.1.1.1.2.8 2005/01/16 20:48:37 rossbencina Exp $ * Simplified DirectSound interface. * * Author: Phil Burk & Robert Marsanyi @@ -44,6 +44,11 @@ #endif #endif +/* + We are only using DX3 in here, no need to polute the namespace - davidv +*/ +#define DIRECTSOUND_VERSION 0x0300 + #include <DSound.h> #ifdef __cplusplus |