From ae6b5d89ea93b95c2990895077cf5e8f0bba9ad9 Mon Sep 17 00:00:00 2001 From: Guenter Geiger Date: Mon, 2 Feb 2004 11:28:02 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r1301, which included commits to RCS files with non-trunk default branches. svn path=/trunk/; revision=1302 --- pd/portaudio_v18/pa_sgi/pa_sgi.c | 1069 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1069 insertions(+) create mode 100644 pd/portaudio_v18/pa_sgi/pa_sgi.c (limited to 'pd/portaudio_v18/pa_sgi/pa_sgi.c') diff --git a/pd/portaudio_v18/pa_sgi/pa_sgi.c b/pd/portaudio_v18/pa_sgi/pa_sgi.c new file mode 100644 index 00000000..fa2978b5 --- /dev/null +++ b/pd/portaudio_v18/pa_sgi/pa_sgi.c @@ -0,0 +1,1069 @@ +/* + * $Id: pa_sgi.c,v 1.2.4.2 2003/03/13 00:56:47 pieter Exp $ + * PortAudio Portable Real-Time Audio Library. Copyright (c) 1999-2001 Phil Burk. + * Latest Version at: http://www.portaudio.com + * + * Silicon Graphics (SGI) IRIX implementation by Pieter Suurmond. + * This implementation uses sproc()-spawning, not the POSIX-threads. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * +MODIFICATIONS: + 8/12/2001 - Pieter Suurmond - took the v15 pa_linux_oss.c file and started to adapt for IRIX 6.2. + 8/17/2001 - v15, first unstable alpha release for IRIX, sent to Phil & Ross. + 9/23/2001 - Many fixes and changes: POLLIN for input, not POLLOUT! + 7/04/2002 - Implemented multiple user buffers per host buffer to allow clients that + request smaller buffersizes. + 3/13/2003 - Fixed clicks in full-duplex (wire) mode. Fixed some uninitialised vars, got rid of + all GCC-warnings (-Wall). Tested with MIPS compiler and GCC 3.0.4. on IRIX 6.5 (AL v7). +TODO: + - Dynamically switch to 32 bit float as native format when appropriate (let SGI do the conversion), + and maybe also the other natively supported formats? (might increase performance) + - Implement fancy callback block adapter as described in the PDF by Stephane Letz in the ASIO dir. + +REFERENCES: + - IRIX 6.2 man pages regarding SGI AL library. + - IRIS Digital Media Programming Guide (online books and man-pages come + with IRIX 6.2 and may not be publically available on the internet). +*/ + +#include /* Standard libraries. */ +#include + +#include "../pa_common/portaudio.h" /* (Makefile fails to find in subdirs, -I doesn't work?). */ +#include "../pa_common/pa_host.h" +#include "../pa_common/pa_trace.h" + +#include /* Needed for int oserror(void);. */ +#include +#include +#include +#include /* For schedctl(NDPRI, NDPHIMIN). */ +#include /* fcntl.h needed. */ +#include /* For streams, ioctl(), etc. */ +#include +#include +#include /* System specific (IRIX 6.2-6.5). */ + +/*----------------- MACROS --------------------*/ +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +#define MAX_CHARS_DEVNAME (16) +#define MAX_SAMPLE_RATES (8) /* Known from SGI AL there are 7. */ + /* Constants used in 'Pa_GetMinNumBuffers()' below: */ +#define MIN_LATENCY_MSEC (200) /* Used if 'getenv("PA_MIN_LATENCY_MSEC")' fails. */ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") /* Same names as in file pa_unix.h. */ + +/*------------------------------- IRIX AL specific device info: --------------------------------------*/ +typedef struct internalPortAudioDevice +{ + PaDeviceID pad_DeviceID; /* THIS "ID" IS NEW HERE. */ + long pad_ALdevice; /* SGI-number! */ + double pad_SampleRates[MAX_SAMPLE_RATES]; /* For pointing to from pad_Info */ + char pad_DeviceName[MAX_CHARS_DEVNAME+1]; /* +1 for \0, one more than OSS. */ + PaDeviceInfo pad_Info; /* pad_Info (v15) contains: */ + struct internalPortAudioDevice* pad_Next; /* Singly linked list (NULL=end). */ +} internalPortAudioDevice; + +/*----------------- Structure containing all SGI IRIX specific data: ---------------------------------------*/ +typedef struct PaHostSoundControl +{ + ALconfig pahsc_ALconfigIN, /* IRIX-audio-library-datatype. Configuration */ + pahsc_ALconfigOUT; /* stucts separate for input and output ports. */ + ALport pahsc_ALportIN, /* IRIX-audio-library-datatype. ALports can only be */ + pahsc_ALportOUT; /* unidirectional, so we sometimes need 2 of them. */ + int pahsc_threadPID; /* Sproc()-result, written by PaHost_StartEngine(). */ + + unsigned int pahsc_UserBuffersPerHostBuffer, + pahsc_SamplesPerInputHostBuffer, /* Channels per frame are accounted for. */ + pahsc_SamplesPerOutputHostBuffer, + pahsc_BytesPerInputHostBuffer, /* Size per sample are accounted for. */ + pahsc_BytesPerOutputHostBuffer; + short *pahsc_InputHostBuffer, /* Allocated here, in this file, if necessary. */ + *pahsc_OutputHostBuffer; + + struct itimerval pahsc_EntryTime, /* For measuring CPU utilization (same as linux). */ + pahsc_LastExitTime; + long pahsc_InsideCountSum, + pahsc_TotalCountSum; +} PaHostSoundControl; + +/*-------------------------------------------------------- Shared Data -------------------------------*/ +static internalPortAudioDevice* sDeviceList = NULL; /* FIXME - put Mutex around this shared data. */ +static int sPaHostError = 0; /* Maybe more than one process writing errs!? */ +usema_t *SendSema, /* These variables are shared between the */ + *RcvSema; /* audio handling process and main process. */ +/*--------------------------*/ +long Pa_GetHostError(void) +{ + return (long)sPaHostError; +} + +/*----------------------------- BEGIN CPU UTILIZATION MEASUREMENT -----------------*/ +/* (copied from source pa_linux_oss/pa_linux_oss.c) */ +static void Pa_StartUsageCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + getitimer( ITIMER_REAL, &pahsc->pahsc_EntryTime ); +} + +static long SubtractTime_AminusB( struct itimerval *timeA, struct itimerval *timeB ) +{ + long secs = timeA->it_value.tv_sec - timeB->it_value.tv_sec; + long usecs = secs * 1000000; + usecs += (timeA->it_value.tv_usec - timeB->it_value.tv_usec); + return usecs; +} + +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ + struct itimerval currentTime; + long insideCount; + long totalCount; /* Measure CPU utilization during this callback. */ + +#define LOWPASS_COEFFICIENT_0 (0.95) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if (pahsc == NULL) + return; + if (getitimer( ITIMER_REAL, ¤tTime ) == 0 ) + { + if (past->past_IfLastExitValid) + { + insideCount = SubtractTime_AminusB( &pahsc->pahsc_EntryTime, ¤tTime ); + pahsc->pahsc_InsideCountSum += insideCount; + totalCount = SubtractTime_AminusB( &pahsc->pahsc_LastExitTime, ¤tTime ); + pahsc->pahsc_TotalCountSum += totalCount; + /* DBUG(("insideCount = %d, totalCount = %d\n", insideCount, totalCount )); */ + /* Low pass filter the result because sometimes we get called several times in a row. */ + /* That can cause the TotalCount to be very low which can cause the usage to appear */ + /* unnaturally high. So we must filter numerator and denominator separately!!! */ + if (pahsc->pahsc_InsideCountSum > 0) + { + past->past_AverageInsideCount = ((LOWPASS_COEFFICIENT_0 * past->past_AverageInsideCount) + + (LOWPASS_COEFFICIENT_1 * pahsc->pahsc_InsideCountSum)); + past->past_AverageTotalCount = ((LOWPASS_COEFFICIENT_0 * past->past_AverageTotalCount) + + (LOWPASS_COEFFICIENT_1 * pahsc->pahsc_TotalCountSum)); + past->past_Usage = past->past_AverageInsideCount / past->past_AverageTotalCount; + pahsc->pahsc_InsideCountSum = 0; + pahsc->pahsc_TotalCountSum = 0; + } + } + past->past_IfLastExitValid = 1; + } + pahsc->pahsc_LastExitTime.it_value.tv_sec = 100; + pahsc->pahsc_LastExitTime.it_value.tv_usec = 0; + setitimer( ITIMER_REAL, &pahsc->pahsc_LastExitTime, NULL ); + past->past_IfLastExitValid = 1; +} /*----------- END OF CPU UTILIZATION CODE (from pa_linux_oss/pa_linux_oss.c v15)--------------------*/ + + +/*--------------------------------------------------------------------------------------*/ +PaError translateSGIerror(void) /* Calls oserror(), may be used after an SGI AL-library */ +{ /* call to report via ERR_RPT(), yields a PaError-num. */ + const char* a = "SGI AL "; /* (Not absolutely sure errno came from THIS thread! */ + switch(oserror()) /* Read IRIX man-pages about the _SGI_MP_SOURCE macro.) */ + { + case AL_BAD_OUT_OF_MEM: + ERR_RPT(("%sout of memory.\n", a)); + return paInsufficientMemory; /* Known PaError. */ + case AL_BAD_CONFIG: + ERR_RPT(("%sconfiguration invalid or NULL.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_CHANNELS: + ERR_RPT(("%schannels not 1,2 or 4.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_NO_PORTS: + ERR_RPT(("%sout of audio ports.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_DEVICE: + ERR_RPT(("%swrong device number.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_DEVICE_ACCESS: + ERR_RPT(("%swrong device access.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_DIRECTION: + ERR_RPT(("%sinvalid direction.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_SAMPFMT: + ERR_RPT(("%sdoesn't accept sampleformat.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_FLOATMAX: + ERR_RPT(("%smax float value is zero.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_WIDTH: + ERR_RPT(("%sunsupported samplewidth.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_QSIZE: + ERR_RPT(("%sinvalid queue size.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_PVBUFFER: + ERR_RPT(("%sPVbuffer null.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_BUFFERLENGTH_NEG: + ERR_RPT(("%snegative bufferlength.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_BUFFERLENGTH_ODD: + ERR_RPT(("%sodd bufferlength.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_PARAM: + ERR_RPT(("%sparameter not valid for device.\n", a)); + return paHostError; /* Generic PaError. */ + default: + ERR_RPT(("%sunknown error.\n", a)); + return paHostError; /* Generic PaError. */ + } +} + +/*------------------------------------------------------------------------------------------*/ +/* Tries to set various rates and formats and fill in the device info structure. */ +static PaError Pa_sgiQueryDevice(long ALdev, /* (AL_DEFAULT_DEVICE) */ + PaDeviceID id, /* (DefaultI|ODeviceID()) */ + char* name, /* (for example "SGI AL") */ + internalPortAudioDevice* pad) /* Result written to pad. */ +{ + long min, max; /* To catch hardware characteristics. */ + ALseterrorhandler(0); /* 0 = turn off the default error handler. */ + /*--------------------------------------------------------------------------------------*/ + pad->pad_ALdevice = ALdev; /* Set the AL device number. */ + pad->pad_DeviceID = id; /* Set the PA device number. */ + if (strlen(name) > MAX_CHARS_DEVNAME) /* MAX_CHARS defined above. */ + { + ERR_RPT(("Pa_QueryDevice(): name too long (%s).\n", name)); + return paHostError; + } + strcpy(pad->pad_DeviceName, name); /* Write name-string. */ + pad->pad_Info.name = pad->pad_DeviceName; /* Set pointer,..hmmm. */ + /*--------------------------------- natively supported sample formats: -----------------*/ + pad->pad_Info.nativeSampleFormats = paInt16; /* Later also include paFloat32 | ..| etc. */ + /* Then also choose other CallConvertXX()! */ + /*--------------------------------- number of available i/o channels: ------------------*/ + if (ALgetminmax(ALdev, AL_INPUT_COUNT, &min, &max)) + return translateSGIerror(); + pad->pad_Info.maxInputChannels = max; + DBUG(("Pa_QueryDevice: maxInputChannels = %d\n", pad->pad_Info.maxInputChannels)) + if (ALgetminmax(ALdev, AL_OUTPUT_COUNT, &min, &max)) + return translateSGIerror(); + pad->pad_Info.maxOutputChannels = max; + DBUG(("Pa_QueryDevice: maxOutputChannels = %d\n", pad->pad_Info.maxOutputChannels)) + /*--------------------------------- supported samplerates: ----------------------*/ + pad->pad_Info.numSampleRates = 7; + pad->pad_Info.sampleRates = pad->pad_SampleRates; + pad->pad_SampleRates[0] = (double)AL_RATE_8000; /* long -> double. */ + pad->pad_SampleRates[1] = (double)AL_RATE_11025; + pad->pad_SampleRates[2] = (double)AL_RATE_16000; + pad->pad_SampleRates[3] = (double)AL_RATE_22050; + pad->pad_SampleRates[4] = (double)AL_RATE_32000; + pad->pad_SampleRates[5] = (double)AL_RATE_44100; + pad->pad_SampleRates[6] = (double)AL_RATE_48000; + if (ALgetminmax(ALdev, AL_INPUT_RATE, &min, &max)) /* Ask INPUT rate-max. */ + return translateSGIerror(); /* double -> long. */ + if (max != (long)(0.5 + pad->pad_SampleRates[6])) /* FP-compare not recommndd. */ + goto weird; + if (ALgetminmax(ALdev, AL_OUTPUT_RATE, &min, &max)) /* Ask OUTPUT rate-max. */ + return translateSGIerror(); + if (max != (long)(0.5 + pad->pad_SampleRates[6])) + { +weird: ERR_RPT(("Pa_sgiQueryDevice() did not confirm max samplerate (%ld)\n",max)); + return paHostError; /* Or make it a warning and just carry on... */ + } + /*-------------------------------------------------------------------------------*/ + return paNoError; +} + + +/*--------------------------------------------------------------------------------*/ +int Pa_CountDevices() /* Name of this function suggests it only counts and */ +{ /* is NOT destructive, it however resets whole PA ! */ + int numDevices = 0; /* Let 's not do that here. */ + internalPortAudioDevice* currentDevice = sDeviceList; /* COPY GLOBAL VAR. */ +#if 0 /* Remains from linux_oss v15: Pa_Initialize(), on */ + if (!currentDevice) /* its turn, calls PaHost_Init() via file pa_lib.c. */ + Pa_Initialize(); /* Isn't that a bit too 'rude'? Don't be too */ +#endif /* friendly to clients that forgot to initialize PA. */ + while (currentDevice) /* Slower but more elegant than the sNumDevices-way: */ + { + numDevices++; + currentDevice = currentDevice->pad_Next; + } + return numDevices; +} + +/*-------------------------------------------------------------------------------*/ +static internalPortAudioDevice *Pa_GetInternalDevice(PaDeviceID id) +{ + int numDevices = 0; + internalPortAudioDevice *res = (internalPortAudioDevice*)NULL; + internalPortAudioDevice *pad = sDeviceList; /* COPY GLOBAL VAR. */ + while (pad) /* pad may be NULL, that's ok, return 0. */ + { /* (Added ->pad_DeviceID field to the pad-struct, Pieter, 2001.) */ + if (pad->pad_DeviceID == id) /* This the device we were looking for? */ + res = pad; /* But keep on(!) counting so we don't */ + numDevices++; /* have to call Pa_CountDevices() later. */ + pad = pad->pad_Next; /* Advance to the next device or NULL. */ + } /* No assumptions about order of ID's in */ + if (!res) /* the list. */ + ERR_RPT(("Pa_GetInternalDevice() could not find specified ID (%d).\n",id)); + if ((id < 0) || (id >= numDevices)) + { + ERR_RPT(("Pa_GetInternalDevice() supplied with an illegal ID (%d).\n",id)); +#if 1 /* Be strict, even when found, */ + res = (internalPortAudioDevice*)NULL; /* do not accept illegal ID's. */ +#endif + } + return res; +} + +/*----------------------------------------------------------------------*/ +const PaDeviceInfo* Pa_GetDeviceInfo(PaDeviceID id) +{ + PaDeviceInfo* res = (PaDeviceInfo*)NULL; + internalPortAudioDevice* pad = Pa_GetInternalDevice(id); /* Call. */ + if (pad) + res = &pad->pad_Info; /* Not finding the specified ID is not */ + if (!res) /* the same as &pad->pad_Info == NULL. */ + ERR_RPT(("Pa_GetDeviceInfo() could not find it (ID=%d).\n", id)); + return res; /* So (maybe) a second/third ERR_RPT(). */ +} + +/*------------------------------------------------*/ +PaDeviceID Pa_GetDefaultInputDeviceID(void) +{ + return 0; /* 0 is the default device ID. */ +} +/*------------------------------------------------*/ +PaDeviceID Pa_GetDefaultOutputDeviceID(void) +{ + return 0; +} + +/*-------------------------------------------------------------------------------------------------*/ +/* Build linked a list with all the available audio devices on this SGI machine (only 1 for now). */ +PaError PaHost_Init(void) /* Called by Pa_Initialize() from pa_lib.c. */ +{ + internalPortAudioDevice* pad; + PaError r = paNoError; + int audioLibFileID; /* To test for the presence of audio. */ + + if (sDeviceList) /* Allow re-init, only warn, no error. */ + { + ERR_RPT(("Warning: PaHost_Init() did not really re-init PA.\n")); + return r; + } + /*------------- ADD THE SGI DEFAULT DEVICE TO THE LIST: ---------------------------------------*/ + audioLibFileID = open("/dev/hdsp/hdsp0master", O_RDONLY); /* Try to open Indigo style audio */ + if (audioLibFileID < 0) /* IO port. On failure, machine */ + { /* has no audio ability. */ + ERR_RPT(("PaHost_Init(): This machine has no (Indigo-style) audio abilities.\n")); + return paHostError; + } + close(audioLibFileID); /* Allocate fast mem to hold device info. */ + pad = PaHost_AllocateFastMemory(sizeof(internalPortAudioDevice)); + if (pad == NULL) + return paInsufficientMemory; + memset(pad, 0, sizeof(internalPortAudioDevice)); /* "pad->pad_Next = NULL" is more elegant. */ + r = Pa_sgiQueryDevice(AL_DEFAULT_DEVICE, /* Set AL device num (AL_DEFAULT_DEVICE). */ + Pa_GetDefaultOutputDeviceID(),/* Set PA device num (or InputDeviceID()). */ + "AL default", /* A suitable name. */ + pad); /* Write args and queried info into pad. */ + if (r != paNoError) + { + ERR_RPT(("Pa_QueryDevice for '%s' returned: %d\n", pad->pad_DeviceName, r)); + PaHost_FreeFastMemory(pad, sizeof(internalPortAudioDevice)); /* sDeviceList still NULL ! */ + } + else + sDeviceList = pad; /* First element in linked list. pad->pad_Next already NULL. */ + /*------------- QUERY AND ADD MORE POSSIBLE SGI DEVICES TO THE LINKED LIST: -------------------*/ + /*---------------------------------------------------------------------------------------------*/ + return r; +} + +/*--------------------------------------------------------------------------------------------*/ +#define MIN(a,b) ((a)<(b)?(a):(b)) /* MIN()-function is used below. */ +#define kPollSEMA 0 /* To index the pollfd-array, reads nicer than just */ +#define kPollOUT 1 /* numbers. */ +#define kPollIN 2 +void Pa_SgiAudioProcess(void *v) /* This function is sproc-ed by PaHost_StartEngine() */ +{ /* as a separate thread. (Argument must be void*). */ + short evtLoop; /* Reset by parent indirectly, or at local errors. */ + PaError result; + struct pollfd PollFD[3]; /* To catch kPollSEMA-, kPollOUT- and kPollIN-events. */ + internalPortAudioStream *past = (internalPortAudioStream*)v; /* Copy void-ptr-argument. */ + PaHostSoundControl *pahsc; + short n, inputEvent, outputEvent, ioEvent, semaEvent = 0; + short *inBuffer, *outBuffer; /* Only 16 bit for now, may change... */ + unsigned int samplesPerInputUserBuffer, samplesPerOutputUserBuffer; + + DBUG(("Entering sproc-thread.\n")); + if (!past) + { + sPaHostError = paInternalError; /* Or paBadStreamPtr ? */ + ERR_RPT(("argument NULL!\n")); + goto noPast; + } + pahsc = (PaHostSoundControl*)past->past_DeviceData; + if (!pahsc) + { + sPaHostError = paInternalError; /* The only way is to signal error to shared area?! */ + ERR_RPT(("past_DeviceData NULL!\n")); + goto noPahsc; /* Sproc-ed threads MAY NOT RETURN paInternalError. */ + } + /*----------------------------- open AL-ports here, after sproc(): -----------------------*/ + if (past->past_NumInputChannels > 0) /* Open input port. */ + { + pahsc->pahsc_ALportIN = ALopenport("PA sgi in", "r", pahsc->pahsc_ALconfigIN); + if (!pahsc->pahsc_ALportIN) + { + ERR_RPT(("Failed to open AL input port.\n")); + sPaHostError = paInternalError; + goto skip; + } + DBUG(("Opened %d input channel(s).\n", past->past_NumInputChannels)); + samplesPerInputUserBuffer = pahsc->pahsc_SamplesPerInputHostBuffer / + pahsc->pahsc_UserBuffersPerHostBuffer; + } + else + samplesPerInputUserBuffer = 0; /* Added 2003. */ + if (past->past_NumOutputChannels > 0) /* Open output port. */ + { + pahsc->pahsc_ALportOUT = ALopenport("PA sgi out", "w", pahsc->pahsc_ALconfigOUT); + if (!pahsc->pahsc_ALportOUT) + { + ERR_RPT(("Failed to open AL output port.\n")); + sPaHostError = paInternalError; /* Assume pahsc_ALconfigs are the */ + goto skip; /* same for IN and OUT in case */ + } /* both ports are opened (bidir). */ + DBUG(("Opened %d output channel(s).\n", past->past_NumOutputChannels)); + samplesPerOutputUserBuffer = pahsc->pahsc_SamplesPerOutputHostBuffer / + pahsc->pahsc_UserBuffersPerHostBuffer; + DBUG(("samplesPerOutputUserBuffer = %d\n", samplesPerOutputUserBuffer)); + } + else + samplesPerOutputUserBuffer = 0; /* Added 2003. */ + /*-----------------------------------------------------------------------*/ + past->past_IsActive = 1; /* Wasn't this already done by the calling parent?! */ + PollFD[kPollIN].fd = ALgetfd(pahsc->pahsc_ALportIN); /* ALgetfd returns -1 on failures */ + PollFD[kPollIN].events = POLLIN; /* such as ALport not there. */ + PollFD[kPollOUT].fd = ALgetfd(pahsc->pahsc_ALportOUT); + PollFD[kPollOUT].events = POLLOUT; /* .events = POLLOUT is OK. */ + schedctl(NDPRI, NDPHIMIN); /* Sets non-degrading priority for this process. */ + PollFD[kPollSEMA].fd = usopenpollsema(SendSema, 0777); /* To communicate with parent. */ + PollFD[kPollSEMA].events = POLLIN; /* .events = POLLIN is OK. */ + uspsema(SendSema); /* Blocks until ... MUST be here, this uspsema(). */ + evtLoop = ((past->past_StopNow | past->past_StopSoon) == 0); + while (evtLoop) + { + /*----------------------------- SET FILLPOINTS AND WAIT UNTIL SOMETHING HAPPENS: ---------*/ + if (pahsc->pahsc_InputHostBuffer) /* Then pahsc_ALportIN should also be there. */ + { + /* For input port, fill point is number of locations in the sample queue that must be */ + /* filled in order to trigger a return from select(). (or poll()) */ + /* Notice IRIX docs mention number of samples as argument, not number of sampleframes.*/ + if (ALsetfillpoint(pahsc->pahsc_ALportIN, pahsc->pahsc_SamplesPerInputHostBuffer)) + { /* Multiple amount as transferred per time. */ + ERR_RPT(("ALsetfillpoint() for ALportIN failed.\n")); + sPaHostError = paInternalError; /* (Using exit(-1) would be a bit rude.) */ + goto skip; + } + } + /* 'else' added march 2003: set only one of both fillpoints: input or output. When */ + /* setting both fillpoints (as in earlier version) clicks occur at full duplex-mode. */ + else if (pahsc->pahsc_OutputHostBuffer) /* Then pahsc_ALportOUT should also be there. */ + { + /* For output port, fill point is number of locations that must be free in order to */ + /* wake up from select(). (or poll()) */ + if (ALsetfillpoint(pahsc->pahsc_ALportOUT, pahsc->pahsc_SamplesPerOutputHostBuffer)) + { + ERR_RPT(("ALsetfillpoint() for ALportOUT failed.\n")); + sPaHostError = paInternalError; + goto skip; + } + } /* poll() with timeout=-1 makes it block until a requested */ + poll(PollFD, 3, -1); /* event occurs or until call is interrupted. If fd-value in */ + /* array <0, events is ignored and revents is set to 0. */ + /*---------------------------- MESSAGE-EVENT FROM PARENT THREAD: -------------------------*/ + semaEvent = PollFD[kPollSEMA].revents & POLLIN; + if (semaEvent) + { + if (past->past_StopSoon) + evtLoop = 0; + if (past->past_StopNow) + goto skip; + } + /*------------------------------------- FILLED-EVENT FROM INPUT BUFFER: --------------------------*/ + inputEvent = PollFD[kPollIN].revents & POLLIN; + if (inputEvent) /* Don't need to check (pahsc->pahsc_InputHostBuffer): */ + { /* if buffer was not there, ALport not there, no events! */ + if (ALreadsamps(pahsc->pahsc_ALportIN, (void*)pahsc->pahsc_InputHostBuffer, + pahsc->pahsc_SamplesPerInputHostBuffer)) + { /* Here again: number of samples instead of number of frames. */ + ERR_RPT(("ALreadsamps() failed.\n")); + sPaHostError = paInternalError; + goto skip; + } + } + outputEvent = PollFD[kPollOUT].revents & POLLOUT; + ioEvent = (inputEvent | outputEvent); /* Binary or is ok. */ + /*------------------------------------- USER-CALLBACK-ROUTINE: -----------------------------------*/ + if (ioEvent) /* Always true? Or can some other system-event awaken the */ + { /* poll? Sure it wasn't just a "sema"- (i.e. user)-event? */ + Pa_StartUsageCalculation(past); /* Convert 16 bit native data to */ + /* user data and call user routine. */ + inBuffer = pahsc->pahsc_InputHostBuffer; /* Short pointers for now, care! */ + outBuffer = pahsc->pahsc_OutputHostBuffer; + n = pahsc->pahsc_UserBuffersPerHostBuffer; /* 'n' may never start at NULL ! */ + do { + result = Pa_CallConvertInt16(past, inBuffer, outBuffer); + if (result) /* This is apparently NOT an error! Just letting the userCallBack stop us. */ + { DBUG(("Pa_CallConvertInt16() returned %d, stopping...\n", result)); goto skip; } + inBuffer += samplesPerInputUserBuffer; /* Num channels is accounted for. */ + outBuffer += samplesPerOutputUserBuffer; + } while (--n); + Pa_EndUsageCalculation(past); + } + /*------------------------------------ FREE-EVENT FROM OUTPUT BUFFER: ---------------------------*/ + if (pahsc->pahsc_OutputHostBuffer && ioEvent) /* Don't wait for outputEvent solely (that may cause clicks). */ + { /* Just assume it's time to write, outputEvent may not yet be there. */ + if (ALwritesamps(pahsc->pahsc_ALportOUT, (void*)pahsc->pahsc_OutputHostBuffer, + pahsc->pahsc_SamplesPerOutputHostBuffer)) + { + ERR_RPT(("ALwritesamps() failed.\n")); /* Better use SEMAS for messaging back to parent! */ + sPaHostError = paInternalError; + goto skip; + } + } + } +skip: + /*------------------------------- close AL-ports ----------------------------*/ + if (pahsc->pahsc_ALportIN) + { + if (ALcloseport(pahsc->pahsc_ALportIN)) + translateSGIerror(); /* Translates SGI AL-code to PA-code and ERR_RPTs string. */ + else /* But go on anyway... to release other stuff... */ + pahsc->pahsc_ALportIN = (ALport)0; + } + if (pahsc->pahsc_ALportOUT) + { + if (ALcloseport(pahsc->pahsc_ALportOUT)) + translateSGIerror(); + else + pahsc->pahsc_ALportOUT = (ALport)0; + } +noPahsc: + past->past_IsActive = 0; + if (semaEvent) + { + uspsema(SendSema); /* StopEngine() was still waiting for this acknowledgement. */ + usvsema(RcvSema); /* (semaEvent initialized with 0.) */ + } +noPast: + DBUG(("Leaving sproc-thread.\n")); +} + + +/*--------------------------------------------------------------------------------------*/ +PaError PaHost_OpenStream(internalPortAudioStream *past) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + unsigned int minNumBuffers; + internalPortAudioDevice *padIN, *padOUT; /* For looking up native AL-numbers. */ + long pvbuf[8], sr, alq; /* To get/set hardware configs. */ + + DBUG(("PaHost_OpenStream() called.\n")); /* Alloc FASTMEM and init host data. */ + if (!past) + { + ERR_RPT(("Streampointer NULL!\n")); + result = paBadStreamPtr; goto done; + } + pahsc = (PaHostSoundControl*)PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); + if (pahsc == NULL) + { + ERR_RPT(("FAST Memory allocation failed.\n")); /* Pass trough some ERR_RPT-exit- */ + result = paInsufficientMemory; goto done; /* code (nothing will be freed). */ + } + memset(pahsc, 0, sizeof(PaHostSoundControl)); + pahsc->pahsc_threadPID = -1; /* Should pahsc_threadPID be inited to */ + past->past_DeviceData = (void*)pahsc; /* -1 instead of 0 ?? */ + /*------------------------------------------ Manipulate hardware if necessary and allowed: --*/ + ALseterrorhandler(0); /* 0 = turn off the default error handler. */ + pvbuf[0] = AL_INPUT_RATE; + pvbuf[2] = AL_INPUT_COUNT; + pvbuf[4] = AL_OUTPUT_RATE; /* TO FIX: rates may be logically, not always in Hz! */ + pvbuf[6] = AL_OUTPUT_COUNT; + sr = (long)(past->past_SampleRate + 0.5); /* Common for both input and output :-) */ + /*-----------------------------------------------------------------------------*/ + /* OVERWRITE 'past_NumUserBuffers'-field in the struct supplied by the caller. */ + /* This field may be set to zero by a client application to ask for minimum */ + /* latency. It is used below, to set both input- and output-AL-queuesizes. */ + minNumBuffers = Pa_GetMinNumBuffers(past->past_FramesPerUserBuffer, + past->past_SampleRate); /* Take biggest. */ + past->past_NumUserBuffers = (minNumBuffers > past->past_NumUserBuffers) ? + minNumBuffers : past->past_NumUserBuffers; + DBUG(("past->past_NumUserBuffers=%d\n", past->past_NumUserBuffers)); + /*----------------------------------------------------------------------------------*/ + pahsc->pahsc_UserBuffersPerHostBuffer = past->past_NumUserBuffers >> 1; + DBUG(("pahsc_UserBuffersPerHostBuffer=%d\n",pahsc->pahsc_UserBuffersPerHostBuffer)); + /* 1 is minimum because Pa_GetMinNumBuffers() returns >= 2. + Callback will be called 'pahsc_UserBuffersPerHostBuffer' times (with 'past_FramesPerUserBuffer') + per host transfer. */ + /*---------------------------------------------------- SET INPUT CONFIGURATION: ---------------------*/ + if (past->past_NumInputChannels > 0) /* We need to lookup the corre- */ + { /* sponding native AL-number(s). */ + /*--------------------------------------------------- Allocate native buffers: --------------*/ + pahsc->pahsc_SamplesPerInputHostBuffer = pahsc->pahsc_UserBuffersPerHostBuffer * + past->past_FramesPerUserBuffer * /* Needed by the */ + past->past_NumInputChannels; /* audio-thread. */ + DBUG(("pahsc_SamplesPerInputHostBuffer=%d\n", pahsc->pahsc_SamplesPerInputHostBuffer)); + pahsc->pahsc_BytesPerInputHostBuffer = pahsc->pahsc_SamplesPerInputHostBuffer * sizeof(short); + pahsc->pahsc_InputHostBuffer = (short*)PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerInputHostBuffer); + if (!pahsc->pahsc_InputHostBuffer) + { + ERR_RPT(("Fast memory allocation failed (in).\n")); + result = paInsufficientMemory; + goto done; + } + padIN = Pa_GetInternalDevice(past->past_InputDeviceID); + if (!padIN) + { + ERR_RPT(("Pa_GetInternalDevice() for input failed.\n")); + result = paHostError; + goto done; + } + if (ALgetparams(padIN->pad_ALdevice, &pvbuf[0], 4)) /* Although input and output will both be on */ + goto sgiError; /* the same AL-device, the AL-library might */ + if (pvbuf[1] != sr) /* contain more than AL_DEFAULT_DEVICE in */ + { /* Rate different from current harware-rate? the future. Therefore 2 seperate queries. */ + if (pvbuf[3] > 0) /* Means, there's other clients using AL-input-ports */ + { + ERR_RPT(("Sorry, not allowed to switch input-hardware to %ld Hz because \ +another process is currently using input at %ld Hz.\n", sr, pvbuf[1])); + result = paHostError; + goto done; + } + pvbuf[1] = sr; /* Then set input-rate. */ + if (ALsetparams(padIN->pad_ALdevice, &pvbuf[0], 2)) + goto sgiError; /* WHETHER THIS SAMPLERATE WAS REALLY PRESENT IN OUR ARRAY OF RATES, */ + } /* IS NOT CHECKED, AT LEAST NOT BY ME, WITHIN THIS FILE! Does PA do? */ + pahsc->pahsc_ALconfigIN = ALnewconfig(); /* Released at PaHost_CloseStream(). */ + if (pahsc->pahsc_ALconfigIN == (ALconfig)0) + goto sgiError; + if (ALsetsampfmt(pahsc->pahsc_ALconfigIN, AL_SAMPFMT_TWOSCOMP))/* Choose paInt16 as native i/o-format. */ + goto sgiError; + if (ALsetwidth (pahsc->pahsc_ALconfigIN, AL_SAMPLE_16)) /* Only meaningful when sample format for */ + goto sgiError; /* config is set to two's complement format. */ + /************************ Future versions might (dynamically) switch to 32-bit floats? ******* + if (ALsetsampfmt(pahsc_ALconfigIN, AL_SAMPFMT_FLOAT)) (Then also call another CallConvert-func.) + goto sgiError; + if (ALsetfloatmax (pahsc_ALconfigIN, 1.0)) Only meaningful when sample format for config + goto sgiError; is set to AL_SAMPFMT_FLOAT or AL_SAMPFMT_DOUBLE. */ + /*--------- Set internal AL queuesize (in samples, not in frames!) -------------------------------*/ + alq = (long)past->past_NumUserBuffers * past->past_FramesPerUserBuffer * past->past_NumInputChannels; + DBUG(("AL input queuesize = %ld samples.\n", alq)); + if (ALsetqueuesize(pahsc->pahsc_ALconfigIN, alq)) + goto sgiError; + if (ALsetchannels (pahsc->pahsc_ALconfigIN, (long)(past->past_NumInputChannels))) + goto sgiError; /* Returns 0 on success, -1 on failure. */ + } + else + pahsc->pahsc_InputHostBuffer = (short*)NULL; /* Added 2003! Is checked in callback-routine. */ + /*---------------------------------------------------- SET OUTPUT CONFIGURATION: ------------------------*/ + if (past->past_NumOutputChannels > 0) /* CARE: padOUT/IN may NOT be NULL if Channels <= 0! */ + { /* We use padOUT/IN later on, or at least 1 of both. */ + pahsc->pahsc_SamplesPerOutputHostBuffer = pahsc->pahsc_UserBuffersPerHostBuffer * + past->past_FramesPerUserBuffer * /* Needed by the */ + past->past_NumOutputChannels; /* audio-thread. */ + DBUG(("pahsc_SamplesPerOutputHostBuffer=%d\n", pahsc->pahsc_SamplesPerOutputHostBuffer)); + pahsc->pahsc_BytesPerOutputHostBuffer = pahsc->pahsc_SamplesPerOutputHostBuffer * sizeof(short); + pahsc->pahsc_OutputHostBuffer = (short*)PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerOutputHostBuffer); + if (!pahsc->pahsc_OutputHostBuffer) + { + ERR_RPT(("Fast memory allocation failed (out).\n")); + result = paInsufficientMemory; + goto done; + } + padOUT = Pa_GetInternalDevice(past->past_OutputDeviceID); + if (!padOUT) + { + ERR_RPT(("Pa_GetInternalDevice() for output failed.\n")); + result = paHostError; + goto done; + } + if (ALgetparams(padOUT->pad_ALdevice,&pvbuf[4], 4)) + goto sgiError; + if (pvbuf[5] != sr) + { /* Output needed and rate different from current harware-rate. */ + if (pvbuf[7] > 0) /* Means, there's other clients using AL-output-ports */ + { + ERR_RPT(("Sorry, not allowed to switch output-hardware to %ld Hz because \ +another process is currently using output at %ld Hz.\n", sr, pvbuf[5])); + result = paHostError; + goto done; /* Will free again the inputbuffer */ + } /* that was just created above. */ + pvbuf[5] = sr; /* Then set output-rate. */ + if (ALsetparams(padOUT->pad_ALdevice, &pvbuf[4], 2)) + goto sgiError; + } + pahsc->pahsc_ALconfigOUT = ALnewconfig(); /* Released at PaHost_CloseStream(). */ + if (pahsc->pahsc_ALconfigOUT == (ALconfig)0) + goto sgiError; + if (ALsetsampfmt(pahsc->pahsc_ALconfigOUT, AL_SAMPFMT_TWOSCOMP)) /* Choose paInt16 as native i/o-format. */ + goto sgiError; + if (ALsetwidth (pahsc->pahsc_ALconfigOUT, AL_SAMPLE_16)) /* Only meaningful when sample format for */ + goto sgiError; /* config is set to two's complement format. */ + /** Future versions might (dynamically) switch to 32-bit floats. **/ + alq = (long)past->past_NumUserBuffers * past->past_FramesPerUserBuffer * past->past_NumOutputChannels; + DBUG(("AL output queuesize = %ld samples.\n", alq)); + if (ALsetqueuesize(pahsc->pahsc_ALconfigOUT, alq)) + goto sgiError; + if (ALsetchannels (pahsc->pahsc_ALconfigOUT, (long)(past->past_NumOutputChannels))) + goto sgiError; + } + else + pahsc->pahsc_OutputHostBuffer = (short*)NULL; + /*----------------------------------------------- TEST DEVICE ID's: --------------------*/ + if ((past->past_OutputDeviceID != past->past_InputDeviceID) && /* Who SETS these devive-numbers? */ + (past->past_NumOutputChannels > 0) && (past->past_NumInputChannels > 0)) + { + ERR_RPT(("Cannot setup bidirectional stream between different devices.\n")); + result = paHostError; + goto done; + } + goto done; /* (no errors occured) */ +sgiError: + result = translateSGIerror(); /* Translates SGI AL-code to PA-code and ERR_RPTs string. */ +done: + if (result != paNoError) + PaHost_CloseStream(past); /* Frees memory (only if really allocated!). */ + return result; +} + +/*-----------------------------------------------------*/ +PaError PaHost_StartOutput(internalPortAudioStream *past) +{ + return paNoError; /* Hmm, not implemented yet? */ +} +PaError PaHost_StartInput(internalPortAudioStream *past) +{ + return paNoError; +} + +/*------------------------------------------------------------------------------*/ +PaError PaHost_StartEngine(internalPortAudioStream *past) +{ + PaHostSoundControl *pahsc; + usptr_t *arena; + if (!past) /* Test argument. */ + { + ERR_RPT(("PaHost_StartEngine(NULL)!\n")); + return paBadStreamPtr; + } + pahsc = (PaHostSoundControl*)past->past_DeviceData; + if (!pahsc) + { + ERR_RPT(("PaHost_StartEngine(arg): arg->past_DeviceData = NULL!\n")); + return paHostError; + } + past->past_StopSoon = 0; /* Assume SGI ALport is already opened! */ + past->past_StopNow = 0; /* Why don't we check pahsc for NULL? */ + past->past_IsActive = 1; + + /* Although the pthread_create() function, as well as , may be */ + /* available in IRIX, use sproc() on SGI to create audio-background-thread. */ + /* (Linux/oss uses pthread_create() instead of __clone() because: */ + /* - pthread_create also works for other UNIX systems like Solaris, */ + /* - Java HotSpot VM crashes in pthread_setcanceltype() using __clone().) */ + + usconfig(CONF_ARENATYPE, US_SHAREDONLY); /* (From SGI-AL-examples, file */ + arena = usinit(tmpnam(0)); /* motifexample.c, function */ + SendSema = usnewpollsema(arena, 0); /* InitializeAudioProcess().) */ + RcvSema = usnewsema(arena, 1); /* 1= common mutual exclusion semaphore, where 1 and only 1 process + will be permitted through a semaphore at a time. Values > 1 + imply that up to val resources may be simultaneously used, but requests + for more than val resources cause the calling process to block until a + resource comes free (by a process holding a resource performing a + usvsema(). IS THIS usnewsema() TOO PLATFORM SPECIFIC? */ + prctl(PR_SETEXITSIG, 0); /* No not (void*)9, but 0, which doesn't kill the parent! */ + /* PR_SETEXITSIG controls whether all members of a share group will be + signaled if any one of them leaves the share group (either via exit() + or exec()). If 2nd arg, interpreted as an int is 0, then normal IRIX + process termination rules apply, namely that the parent is sent a + SIGCLD upon death of child, but no indication of death of parent is + given. If the second argument is a valid signal number then if any + member of a share group leaves the share group, a signal is + sent to ALL surviving members of the share group. */ + /* SPAWN AUDIO-CHILD: */ + pahsc->pahsc_threadPID = sproc(Pa_SgiAudioProcess, /* Returns process ID of */ + PR_SALL, /* new process, or -1. */ + (void*)past); /* Pass past as optional */ /* IS THIS SAFE, will past never */ + if (pahsc->pahsc_threadPID == -1) /* third void-ptr-arg. */ /* be moved around in memory???? */ + { + ERR_RPT(("PaHost_StartEngine() failed to spawn audio-thread.\n")); + sPaHostError = oserror(); /* Pass native error-number to shared area. */ + return paHostError; /* But return the generic error-number. */ + } + return paNoError; /* Hmmm, errno may come from other threads in same group! */ +} /* ("man sproc" in IRIX6.2 to read about _SGI_MP_SOURCE.) */ + +/*------------------------------------------------------------------------------*/ +PaError PaHost_StopEngine(internalPortAudioStream *past, int abort) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + + DBUG(("PaHost_StopEngine() called.\n")); + if (!past) + return paBadStreamPtr; + pahsc = (PaHostSoundControl*)past->past_DeviceData; + /* Prevent from doing this twice!! */ + if ((!pahsc) || /* Some tests call this CLOSE twice!! */ + (!past->past_IsActive) || + past->past_StopSoon || past->past_StopNow) + return result; /* paNoError (already stopped, no err?). */ + past->past_StopSoon = 1; /* Tell background thread to stop generating */ + if (abort) /* more and to let current data play out. If */ + past->past_StopNow = 1; /* aborting, tell backgrnd thread to stop NOW! */ + /*---- USE SEMAPHORE LOCK TO COMMUNICATE: -----*/ + usvsema(SendSema); /* Increments count associated with SendSema. */ + /* Wait for the response. */ + uspsema(RcvSema); /* Decrements count of previously allocated */ + /* semaphore specified by RcvSema. */ + while (past->past_IsActive) /* REALLY WAIT. */ + { + /* DBUG(("wait 1 ms for audio-thread to stop.\n")); */ + Pa_Sleep(1); + } + +#if 0 /* We don't need to KILL(), just COMMUNICATE and be patient... */ + if (pahsc->pahsc_threadPID != -1) /* Did we really init it to -1 somewhere? */ + { + DBUG(("PaHost_StopEngine() is about to kill(SIGKILL) audio-thread.\n")); + if (kill(pahsc->pahsc_threadPID, SIGKILL)) /* Or SIGTERM or SIGQUIT(core) */ + { /* Returns -1 in case of error. */ + result = paHostError; + sPaHostError = oserror(); /* Hmmm, other threads may also write here! */ + ERR_RPT(("PaHost_StopEngine() failed to kill audio-thread.\n")); + } + else + pahsc->pahsc_threadPID = -1; /* Notify that we've killed this thread. */ + } +#endif + past->past_IsActive = 0; /* Even when kill() failed and pahsc_threadPID still there??? */ + return result; +} + +/*---------------------------------------------------------------*/ +PaError PaHost_StopOutput(internalPortAudioStream *past, int abort) +{ + return paNoError; /* Not implemented yet? */ +} +PaError PaHost_StopInput(internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + +/*******************************************************************/ +PaError PaHost_CloseStream(internalPortAudioStream *past) +{ + PaHostSoundControl *pahsc; + PaError result = paNoError; + + DBUG(("PaHost_CloseStream() called.\n")); + if (!past) + return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if (!pahsc) /* If pahsc not NULL, past_DeviceData will be freed, and set to NULL. */ + return result; /* This test prevents from freeing NULL-pointers. */ + + if (pahsc->pahsc_ALconfigIN) + { /* Release configuration structs, only if allocated. */ + ALfreeconfig(pahsc->pahsc_ALconfigIN); + pahsc->pahsc_ALconfigIN = NULL; + } + if (pahsc->pahsc_ALconfigOUT) + { + ALfreeconfig(pahsc->pahsc_ALconfigOUT); /* (Al-ports were already closed by audioProcess). */ + pahsc->pahsc_ALconfigOUT = NULL; + } + if (pahsc->pahsc_InputHostBuffer) + { + PaHost_FreeFastMemory(pahsc->pahsc_InputHostBuffer, pahsc->pahsc_BytesPerInputHostBuffer); + pahsc->pahsc_InputHostBuffer = NULL; + } + if (pahsc->pahsc_OutputHostBuffer) + { + PaHost_FreeFastMemory(pahsc->pahsc_OutputHostBuffer, pahsc->pahsc_BytesPerOutputHostBuffer); + pahsc->pahsc_OutputHostBuffer = NULL; + } + PaHost_FreeFastMemory(pahsc, sizeof(PaHostSoundControl)); + past->past_DeviceData = NULL; /* PaHost_OpenStream() allocated FAST MEM. */ + return result; +} + + +/*------------------------------------------------------------------------*/ +/* Determine minimum number of buffers required for (SGI) host based on */ +/* minimum latency. Latency can be optionally set by user by setting an */ +/* environment variable. For example, to set my latency to 200 msec, I've */ +/* put this line in my '.cshrc' file: setenv PA_MIN_LATENCY_MSEC 200 */ +/* It always calls the 'PRINT' macro. */ +/* The minimum number that is returned is 2. */ +/* This number is directly proportional to the AL-queue sizes to set up. */ +/* It is one more than the number of user buffers per host buffer - in */ +/* case minimum is returned, or, twice the user buffers per host buffer. */ +/*------------------------------------------------------------------------*/ +int Pa_GetMinNumBuffers(int framesPerUserBuffer, double framesPerSecond) +{ + int minBuffers, minLatencyMsec; + char *minLatencyText; + double actualLatency; + + minLatencyText = getenv(PA_LATENCY_ENV_NAME); /* Defined at top of file. */ + if (minLatencyText) + { + minLatencyMsec = atoi(minLatencyText); + if (minLatencyMsec < 10) + { /* 10 is the minimum. */ + minLatencyMsec = 10; + PRINT (("Environment variable 'PA_MIN_LATENCY_MSEC' below minimum of %d milliseconds.\n", + minLatencyMsec)); + } + else if (minLatencyMsec > 4000) + { /* 4000 is the maximum. */ + minLatencyMsec = 4000; + PRINT (("Environment variable 'PA_MIN_LATENCY_MSEC' above maximum of %d milliseconds.\n", + minLatencyMsec)); + } + else + PRINT (("Using environment variable 'PA_MIN_LATENCY_MSEC' (set to %d milliseconds).\n", + minLatencyMsec)); + } + else + { + minLatencyMsec = MIN_LATENCY_MSEC; /* Defined at top of this file. */ + PRINT (("Environment variable 'PA_MIN_LATENCY_MSEC' not found.\nUsing default of %d milliseconds\n", + minLatencyMsec)); + } + minBuffers = (int)((minLatencyMsec * framesPerSecond) / + (1000.0 * framesPerUserBuffer)); + if (minBuffers < 2) + minBuffers = 2; + actualLatency = 1000.0 * minBuffers * framesPerUserBuffer / framesPerSecond; + PRINT (("Actual AL latency set to %.2f milliseconds\n", actualLatency)); + return minBuffers; +} + +/*---------------------------------------------------------------------*/ +PaError PaHost_Term(void) /* Frees all of the linked audio-devices. */ +{ /* Called by Pa_Terminate() from pa_lib.c. */ + internalPortAudioDevice *pad = sDeviceList, + *nxt; + while (pad) + { + DBUG(("PaHost_Term: freeing %s\n", pad->pad_DeviceName)); + nxt = pad->pad_Next; + PaHost_FreeFastMemory(pad, sizeof(internalPortAudioDevice)); + pad = nxt; /* PaHost_Init allocated this fast mem.*/ + } + sDeviceList = (internalPortAudioDevice*)NULL; + return 0; +} + +/***********************************************************************/ +void Pa_Sleep( long msec ) /* Sleep requested number of milliseconds. */ +{ +#if 0 + struct timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + select(0, NULL, NULL, NULL, &timeout); +#else + long usecs = msec * 1000; + usleep( usecs ); +#endif +} + +/*---------------------------------------------------------------------------------------*/ +/* Allocate memory that can be accessed in real-time. This may need to be held in physi- */ +/* cal memory so that it is not paged to virtual memory. This call MUST be balanced with */ +/* a call to PaHost_FreeFastMemory(). */ +void *PaHost_AllocateFastMemory(long numBytes) +{ + void *addr = malloc(numBytes); /* mpin() reads into memory all pages over the given */ + if (addr) /* range and locks the pages into memory. A counter */ + { /* is incremented each time the page is locked. The */ + if (mpin(addr, numBytes)) /* superuser can lock as many pages as it wishes, */ + { /* others are limited to the configurable PLOCK_MA. */ + ERR_RPT(("PaHost_AllocateFastMemory() failed to mpin() memory.\n")); +#if 1 + free(addr); /* You MAY cut out these 2 lines to be less strict, */ + addr = NULL; /* you then only get the warning but PA goes on... */ +#endif /* Only problem then may be corresponding munpin() */ + } /* call at PaHost_FreeFastMemory(), below. */ + memset(addr, 0, numBytes); /* Locks established with mlock are not inherited by */ + } /* a child process after a fork. Furthermore, IRIX- */ + return addr; /* man-pages warn against mixing both mpin and mlock */ +} /* in 1 piece of code, so stick to mpin()/munpin() ! */ + + +/*---------------------------------------------------------------------------------------*/ +/* Free memory that could be accessed in real-time. This call MUST be balanced with a */ +/* call to PaHost_AllocateFastMemory(). */ +void PaHost_FreeFastMemory(void *addr, long numBytes) +{ + if (addr) + { + if (munpin(addr, numBytes)) /* Will munpin() fail when it was never mpinned? */ + ERR_RPT(("WARNING: PaHost_FreeFastMemory() failed to munpin() memory.\n")); + free(addr); /* But go on, try to release it, just warn... */ + } +} + +/*----------------------------------------------------------*/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + if (past == NULL) + return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if (pahsc == NULL) + return paInternalError; + return (PaError)(past->past_IsActive != 0); +} + +/*-------------------------------------------------------------------*/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + internalPortAudioStream *past = (internalPortAudioStream *) stream; +/* FIXME - return actual frames played, not frames generated. +** Need to query the output device somehow. +*/ + return past->past_FrameCount; +} -- cgit v1.2.1