diff options
Diffstat (limited to 'pd/portaudio/pa_unix_oss')
-rw-r--r-- | pd/portaudio/pa_unix_oss/pa_unix_oss.c | 1934 |
1 files changed, 1328 insertions, 606 deletions
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 ); } |