aboutsummaryrefslogtreecommitdiff
path: root/pd/portaudio/pa_unix_oss/pa_unix_oss.c
diff options
context:
space:
mode:
authorMiller Puckette <millerpuckette@users.sourceforge.net>2005-05-30 03:04:19 +0000
committerMiller Puckette <millerpuckette@users.sourceforge.net>2005-05-30 03:04:19 +0000
commit05607e31243e5e85a3801d4513192bb1f2150b14 (patch)
tree0f810a621cb9967e1e53b349410b0d07be0cea13 /pd/portaudio/pa_unix_oss/pa_unix_oss.c
parent47729b52cb85e8a52bf2e6bbf8ee9a810ed331e1 (diff)
Remembered to update all the edited files. Should now be in sync... will
have to test it though. svn path=/trunk/; revision=3092
Diffstat (limited to 'pd/portaudio/pa_unix_oss/pa_unix_oss.c')
-rw-r--r--pd/portaudio/pa_unix_oss/pa_unix_oss.c1934
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 );
}