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_unix_oss/Makefile | 32 + pd/portaudio_v18/pa_unix_oss/Makefile_freebsd | 36 + pd/portaudio_v18/pa_unix_oss/low_latency_tip.txt | Bin 0 -> 3111 bytes pd/portaudio_v18/pa_unix_oss/pa_unix.c | 1122 ++++++++++++++++++++++ pd/portaudio_v18/pa_unix_oss/pa_unix.h | 141 +++ pd/portaudio_v18/pa_unix_oss/pa_unix_oss.c | 385 ++++++++ pd/portaudio_v18/pa_unix_oss/pa_unix_solaris.c | 397 ++++++++ pd/portaudio_v18/pa_unix_oss/recplay.c | 114 +++ 8 files changed, 2227 insertions(+) create mode 100644 pd/portaudio_v18/pa_unix_oss/Makefile create mode 100644 pd/portaudio_v18/pa_unix_oss/Makefile_freebsd create mode 100644 pd/portaudio_v18/pa_unix_oss/low_latency_tip.txt create mode 100644 pd/portaudio_v18/pa_unix_oss/pa_unix.c create mode 100644 pd/portaudio_v18/pa_unix_oss/pa_unix.h create mode 100644 pd/portaudio_v18/pa_unix_oss/pa_unix_oss.c create mode 100644 pd/portaudio_v18/pa_unix_oss/pa_unix_solaris.c create mode 100644 pd/portaudio_v18/pa_unix_oss/recplay.c (limited to 'pd/portaudio_v18/pa_unix_oss') diff --git a/pd/portaudio_v18/pa_unix_oss/Makefile b/pd/portaudio_v18/pa_unix_oss/Makefile new file mode 100644 index 00000000..3603816a --- /dev/null +++ b/pd/portaudio_v18/pa_unix_oss/Makefile @@ -0,0 +1,32 @@ +# Make PortAudio for Linux +# +# by Phil Burk +# +# To compile a test program, make a target with a .app suffix. +# For example to compile "../pa_tests/pa_devs.c", enter: +# gmake pa_devs.app +# +# To compile and execute a test program, make a target with a .run suffix. +# For example to build and run "../pa_tests/pa_devs.c", enter: +# gmake pa_devs.run + +VPATH = ../pa_common ../pa_tests +CFLAGS = -g -Wall -I../pa_common +CC = gcc + +PAOBJS = pa_lib.o pa_unix_oss.o pa_unix.o +PAINC = portaudio.h pa_unix.h +PALIBS = -lm -lpthread + +all: patest_sine.run + +%.app: %.o $(PAOBJS) $(PAINC) Makefile + gcc $(CFLAGS) $*.o $(PAOBJS) $(PALIBS) -o $@ + +%.run: %.app + ./$*.app + +clean: + -rm *.app + -rm *.o + diff --git a/pd/portaudio_v18/pa_unix_oss/Makefile_freebsd b/pd/portaudio_v18/pa_unix_oss/Makefile_freebsd new file mode 100644 index 00000000..fc8b14db --- /dev/null +++ b/pd/portaudio_v18/pa_unix_oss/Makefile_freebsd @@ -0,0 +1,36 @@ +# Make PortAudio for FreeBSD + +LIBS = -lm -pthread + +CDEFINES = -I../pa_common +CFLAGS = -g +PASRC = ../pa_common/pa_lib.c pa_freebsd.c +PAINC = ../pa_common/portaudio.h + +# Tests that work. +#TESTC = $(PASRC) ../pa_tests/patest_sine.c +TESTC = $(PASRC) ../pa_tests/patest_sine_time.c +#TESTC = $(PASRC) ../pa_tests/patest_stop.c +#TESTC = $(PASRC) ../pa_tests/patest_sync.c +#TESTC = $(PASRC) ../pa_tests/patest_pink.c +#TESTC = $(PASRC) ../pa_tests/patest_leftright.c +#TESTC = $(PASRC) ../pa_tests/patest_clip.c +#TESTC = $(PASRC) ../pa_tests/patest_dither.c +#TESTC = $(PASRC) ../pa_tests/pa_devs.c +#TESTC = $(PASRC) ../pa_tests/patest_many.c +#TESTC = $(PASRC) ../pa_tests/patest_record.c +#TESTC = $(PASRC) ../pa_tests/patest_wire.c +#TESTC = $(PASRC) ../pa_tests/paqa_devs.c + +# Tests that do not yet work. + +TESTH = $(PAINC) + +all: patest + +patest: $(TESTC) $(TESTH) Makefile + gcc $(CFLAGS) $(TESTC) $(CDEFINES) $(LIBS) -o patest + +run: patest + ./patest + diff --git a/pd/portaudio_v18/pa_unix_oss/low_latency_tip.txt b/pd/portaudio_v18/pa_unix_oss/low_latency_tip.txt new file mode 100644 index 00000000..2d982b79 Binary files /dev/null and b/pd/portaudio_v18/pa_unix_oss/low_latency_tip.txt differ diff --git a/pd/portaudio_v18/pa_unix_oss/pa_unix.c b/pd/portaudio_v18/pa_unix_oss/pa_unix.c new file mode 100644 index 00000000..8d652b73 --- /dev/null +++ b/pd/portaudio_v18/pa_unix_oss/pa_unix.c @@ -0,0 +1,1122 @@ +/* + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * Linux OSS Implementation by douglas repetto and Phil Burk + * + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* +Modification History + 1/2001 - Phil Burk - initial hack for Linux + 2/2001 - Douglas Repetto - many improvements, initial query support + 4/2/2001 - Phil - stop/abort thread control, separate in/out native buffers + 5/28/2001 - Phil - use pthread_create() instead of clone(). Thanks Stephen Brandon! + use pthread_join() after thread shutdown. + 5/29/2001 - Phil - query for multiple devices, multiple formats, + input mode and input+output mode working, + Pa_GetCPULoad() implemented. + PLB20010817 - Phil & Janos Haber - don't halt if test of sample rate fails. + SB20010904 - Stephen Brandon - mods needed for GNUSTEP and SndKit + JH20010905 - Janos Haber - FreeBSD mods + 2001-09-22 - Heiko - (i.e. Heiko Purnhagen ;-) + added 24k and 16k to ratesToTry[] + fixed Pa_GetInternalDevice() + changed DEVICE_NAME_BASE from /dev/audio to /dev/dsp + handled SNDCTL_DSP_SPEED in Pq_QueryDevice() more graceful + fixed Pa_StreamTime() for paqa_errs.c + fixed numCannel=2 oddity and error handling in Pa_SetupDeviceFormat() + grep also for HP20010922 ... + PLB20010924 - Phil - merged Heiko's changes + removed sNumDevices and potential related bugs, + use getenv("PA_MIN_LATENCY_MSEC") to set desired latency, + simplify CPU Load calculation by comparing real-time to framesPerBuffer, + always close device when querying even if error occurs, + PLB20010927 - Phil - Improved negotiation for numChannels. + SG20011005 - Stewart Greenhill - set numChannels back to reasonable value after query. + DH20010115 - David Herring - fixed uninitialized handle. + + DM20020218 - Dominic Mazzoni - Try to open in nonblocking mode first, in case + the device is already open. New implementation of + Pa_StreamTime that uses SNDCTL_DSP_GETOPTR but + uses our own counter to avoid wraparound. + PLB20020222 - Phil Burk - Added WatchDog proc if audio running at high priority. + Check error return from read() and write(). + Check CPU endianness instead of assuming Little Endian. + 20020621 - pa_unix_oss.c split into pa_unix.c, pa_unix.h, pa_unix_oss.c by + Augustus Saunders. Return values from usleep() ignored by Sam Bayer + because not cross-platform compatible (at least until we get configure + going). Pa_SetupDeviceFormat split into input and output sides to + reflect capabilities of Solaris. + + 20030206 - Martin Rohrbach - various mods for Solaris + + 20030410 - Bjorn Dittmer-Roche - fixed numerous problems associated with pthread_t + + 20030630 - Thomas Richter - eliminated unused variable warnings. + +TODO +O- put semaphore lock around shared data? +O- handle native formats better +O- handle stereo-only device better ??? +O- what if input and output of a device capabilities differ (e.g. es1371) ??? +*/ + + +#include "pa_unix.h" + +typedef void *(*pthread_function_t)(void *); + +/************************************************* Shared Data ********/ +/* FIXME - put Mutex around this shared data. */ +static internalPortAudioDevice *sDeviceList = NULL; +static int sDefaultInputDeviceID = paNoDevice; +static int sDefaultOutputDeviceID = paNoDevice; +static int sPaHostError = 0; + +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +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. */ + gettimeofday( &pahsc->pahsc_EntryTime, NULL ); +} + +static long SubtractTime_AminusB( struct timeval *timeA, struct timeval *timeB ) +{ + long secs = timeA->tv_sec - timeB->tv_sec; + long usecs = secs * 1000000; + usecs += (timeA->tv_usec - timeB->tv_usec); + return usecs; +} + +/****************************************************************************** +** Measure fractional CPU load based on real-time it took to calculate +** buffers worth of output. +*/ +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ + struct timeval currentTime; + long usecsElapsed; + double newUsage; + +#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( gettimeofday( ¤tTime, NULL ) == 0 ) + { + usecsElapsed = SubtractTime_AminusB( ¤tTime, &pahsc->pahsc_EntryTime ); + /* Use inverse because it is faster than the divide. */ + newUsage = usecsElapsed * pahsc->pahsc_InverseMicrosPerBuffer; + + past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + + (LOWPASS_COEFFICIENT_1 * newUsage); + + } +} +/****************************************** END CPU UTILIZATION *******/ + +/********************************************************************* + * Determines the number of available devices by trying to open + * each "/dev/dsp#" or "/dsp/audio#" in order until it fails. + * Add each working device to a singly linked list of devices. + */ +PaError Pa_QueryDevices( void ) +{ + internalPortAudioDevice *pad, *lastPad; + int go = 1; + int numDevices = 0; + PaError testResult; + PaError result = paNoError; + char *envdev; + + sDefaultInputDeviceID = paNoDevice; + sDefaultOutputDeviceID = paNoDevice; + + lastPad = NULL; + + while( go ) + { + /* Allocate structure to hold device info. */ + pad = (internalPortAudioDevice *) + PaHost_AllocateFastMemory( sizeof(internalPortAudioDevice) ); + if( pad == NULL ) return paInsufficientMemory; + memset( pad, 0, sizeof(internalPortAudioDevice) ); + + /* Build name for device. */ + if( numDevices == 0 ) + { + sprintf( pad->pad_DeviceName, DEVICE_NAME_BASE); + } + else + { + sprintf( pad->pad_DeviceName, DEVICE_NAME_BASE "%d", numDevices ); + } + + DBUG(("Try device %s\n", pad->pad_DeviceName )); + testResult = Pa_QueryDevice( pad->pad_DeviceName, pad ); + DBUG(("Pa_QueryDevice returned %d\n", testResult )); + if( testResult != paNoError ) + { + if( lastPad == NULL ) + { + result = testResult; /* No good devices! */ + } + go = 0; + PaHost_FreeFastMemory( pad, sizeof(internalPortAudioDevice) ); + } + else + { + numDevices += 1; + /* Add to linked list of devices. */ + if( lastPad ) + { + lastPad->pad_Next = pad; + } + else + { + sDeviceList = pad; /* First element in linked list. */ + } + lastPad = pad; + } + } + + /* I'm sitting at a SunRay1 and I neither have /dev/audio# nor /dev/dsp#. + Instead, the correct audio device is stored in the environment variable + AUDIODEV and/or UTAUDIODEV, so check these devices as well if we haven't + checked them yet above - MR */ + + DBUG(("Checking for AUDIODEV and UTAUDIODEV\n")); + envdev = getenv("AUDIODEV"); + if (envdev != NULL && !strstr(envdev, DEVICE_NAME_BASE)) { + result = paNoError; + + /* Allocate structure to hold device info. */ + pad = (internalPortAudioDevice *) + PaHost_AllocateFastMemory( sizeof(internalPortAudioDevice) ); + if( pad == NULL ) return paInsufficientMemory; + memset( pad, 0, sizeof(internalPortAudioDevice) ); + + /* Build name for device. */ + strcpy(pad->pad_DeviceName, envdev); + + DBUG(("Try device %s\n", pad->pad_DeviceName )); + testResult = Pa_QueryDevice( pad->pad_DeviceName, pad ); + DBUG(("Pa_QueryDevice returned %d\n", testResult )); + if( testResult != paNoError ) + { + if( lastPad == NULL ) + { + result = testResult; /* No good devices! */ + } + PaHost_FreeFastMemory( pad, sizeof(internalPortAudioDevice) ); + } + else + { + numDevices += 1; + /* Add to linked list of devices. */ + if( lastPad ) + { + lastPad->pad_Next = pad; + } + else + { + sDeviceList = pad; /* First element in linked list. */ + } + lastPad = pad; + } + } + + envdev = getenv("UTAUDIODEV"); + if (envdev != NULL && !strstr(envdev, DEVICE_NAME_BASE) && getenv("AUDIODEV") != NULL && strcmp(envdev, getenv("AUDIODEV"))) { + result = paNoError; + + /* Allocate structure to hold device info. */ + pad = (internalPortAudioDevice *) + PaHost_AllocateFastMemory( sizeof(internalPortAudioDevice) ); + if( pad == NULL ) return paInsufficientMemory; + memset( pad, 0, sizeof(internalPortAudioDevice) ); + + /* Build name for device. */ + strcpy(pad->pad_DeviceName, envdev); + + DBUG(("Try device %s\n", pad->pad_DeviceName )); + testResult = Pa_QueryDevice( pad->pad_DeviceName, pad ); + DBUG(("Pa_QueryDevice returned %d\n", testResult )); + if( testResult != paNoError ) + { + if( lastPad == NULL ) + { + result = testResult; /* No good devices! */ + } + PaHost_FreeFastMemory( pad, sizeof(internalPortAudioDevice) ); + } + else + { + numDevices += 1; + /* Add to linked list of devices. */ + if( lastPad ) + { + lastPad->pad_Next = pad; + } + else + { + sDeviceList = pad; /* First element in linked list. */ + } + lastPad = pad; + } + } + + return result; +} + +/*************************************************************************/ +int Pa_CountDevices() +{ + int numDevices = 0; + internalPortAudioDevice *pad; + + if( sDeviceList == NULL ) Pa_Initialize(); + /* Count devices in list. */ + pad = sDeviceList; + while( pad != NULL ) + { + pad = pad->pad_Next; + numDevices++; + } + + return numDevices; +} + +/*************************************************************************/ +internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ) +{ + internalPortAudioDevice *pad; + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + pad = sDeviceList; + while( id > 0 ) + { + pad = pad->pad_Next; + id--; + } + return pad; +} + +/*************************************************************************/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + internalPortAudioDevice *pad; + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + pad = Pa_GetInternalDevice( id ); + return &pad->pad_Info ; +} + +static PaError Pa_MaybeQueryDevices( void ) +{ + if( sDeviceList == NULL ) + { + return Pa_QueryDevices(); + } + return 0; +} + +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + /* return paNoDevice; */ + return 0; +} + +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + return 0; +} + +/********************************************************************** +** Make sure that we have queried the device capabilities. +*/ + +PaError PaHost_Init( void ) +{ + return Pa_MaybeQueryDevices(); +} + +/******************************************************************************************* + * The ol' Canary in a Coal Mine trick. + * Just update the time periodically. + * Runs at low priority so if audio thread runs wild, this thread will get starved + * and the watchdog will detect it. + */ + +#define SCHEDULER_POLICY SCHED_RR +#define WATCHDOG_MAX_SECONDS (3) +#define WATCHDOG_INTERVAL_USEC (1000000) + +static int PaHost_CanaryProc( PaHostSoundControl *pahsc ) +{ + int result = 0; + +#ifdef GNUSTEP + GSRegisterCurrentThread(); /* SB20010904 */ +#endif + + while( pahsc->pahsc_CanaryRun) { + usleep( WATCHDOG_INTERVAL_USEC ); + gettimeofday( &pahsc->pahsc_CanaryTime, NULL ); + } + + DBUG(("PaHost_CanaryProc: exiting.\n")); + +#ifdef GNUSTEP + GSUnregisterCurrentThread(); /* SB20010904 */ +#endif + + return result; +} + +/******************************************************************************************* + * Monitor audio thread and lower its it if it hogs the CPU. + * To prevent getting killed, the audio thread must update a + * variable with a timer value. + * If the value is not recent enough, then the + * thread will get killed. + */ + +static PaError PaHost_WatchDogProc( PaHostSoundControl *pahsc ) +{ + struct sched_param schp = { 0 }; + int maxPri; + +#ifdef GNUSTEP + GSRegisterCurrentThread(); /* SB20010904 */ +#endif + +/* Run at a priority level above audio thread so we can still run if it hangs. */ +/* Rise more than 1 because of rumored off-by-one scheduler bugs. */ + schp.sched_priority = pahsc->pahsc_AudioPriority + 4; + maxPri = sched_get_priority_max(SCHEDULER_POLICY); + if( schp.sched_priority > maxPri ) schp.sched_priority = maxPri; + + if (sched_setscheduler(0, SCHEDULER_POLICY, &schp) != 0) + { + ERR_RPT(("PaHost_WatchDogProc: cannot set watch dog priority!\n")); + goto killAudio; + } + + /* Compare watchdog time with audio and canary thread times. */ + /* Sleep for a while or until thread cancelled. */ + while( pahsc->pahsc_WatchDogRun ) + { + + int delta; + struct timeval currentTime; + + usleep( WATCHDOG_INTERVAL_USEC ); + gettimeofday( ¤tTime, NULL ); + + /* If audio thread is not advancing, then it must be hung so kill it. */ + delta = currentTime.tv_sec - pahsc->pahsc_EntryTime.tv_sec; + DBUG(("PaHost_WatchDogProc: audio delta = %d\n", delta )); + if( delta > WATCHDOG_MAX_SECONDS ) + { + goto killAudio; + } + + /* If canary died, then lower audio priority and halt canary. */ + delta = currentTime.tv_sec - pahsc->pahsc_CanaryTime.tv_sec; + if( delta > WATCHDOG_MAX_SECONDS ) + { + ERR_RPT(("PaHost_WatchDogProc: canary died!\n")); + goto lowerAudio; + } + } + + DBUG(("PaHost_WatchDogProc: exiting.\n")); +#ifdef GNUSTEP + GSUnregisterCurrentThread(); /* SB20010904 */ +#endif + return 0; + +lowerAudio: + { + struct sched_param schat = { 0 }; + if( sched_setscheduler(pahsc->pahsc_AudioThreadPID, SCHED_OTHER, &schat) != 0) + { + ERR_RPT(("PaHost_WatchDogProc: failed to lower audio priority. errno = %d\n", errno )); + /* Fall through into killing audio thread. */ + } + else + { + ERR_RPT(("PaHost_WatchDogProc: lowered audio priority to prevent hogging of CPU.\n")); + goto cleanup; + } + } + +killAudio: + ERR_RPT(("PaHost_WatchDogProc: killing hung audio thread!\n")); + pthread_kill( pahsc->pahsc_AudioThread, SIGKILL ); + +cleanup: + pahsc->pahsc_CanaryRun = 0; + DBUG(("PaHost_WatchDogProc: cancel Canary\n")); + pthread_cancel( pahsc->pahsc_CanaryThread ); + DBUG(("PaHost_WatchDogProc: join Canary\n")); + pthread_join( pahsc->pahsc_CanaryThread, NULL ); + DBUG(("PaHost_WatchDogProc: forget Canary\n")); + pahsc->pahsc_IsCanaryThreadValid = 0; + +#ifdef GNUSTEP + GSUnregisterCurrentThread(); /* SB20010904 */ +#endif + return 0; +} + +/*******************************************************************************************/ +static void PaHost_StopWatchDog( PaHostSoundControl *pahsc ) +{ +/* Cancel WatchDog thread if there is one. */ + if( pahsc->pahsc_IsWatchDogThreadValid ) + { + pahsc->pahsc_WatchDogRun = 0; + DBUG(("PaHost_StopWatchDog: cancel WatchDog\n")); + pthread_cancel( pahsc->pahsc_WatchDogThread ); + pthread_join( pahsc->pahsc_WatchDogThread, NULL ); + pahsc->pahsc_IsWatchDogThreadValid = 0; + } +/* Cancel Canary thread if there is one. */ + if( pahsc->pahsc_IsCanaryThreadValid ) + { + pahsc->pahsc_CanaryRun = 0; + DBUG(("PaHost_StopWatchDog: cancel Canary\n")); + pthread_cancel( pahsc->pahsc_CanaryThread ); + DBUG(("PaHost_StopWatchDog: join Canary\n")); + pthread_join( pahsc->pahsc_CanaryThread, NULL ); + pahsc->pahsc_IsCanaryThreadValid = 0; + } +} + +/*******************************************************************************************/ +static PaError PaHost_StartWatchDog( PaHostSoundControl *pahsc ) +{ + int hres; + PaError result = 0; + + /* The watch dog watches for these timer updates */ + gettimeofday( &pahsc->pahsc_EntryTime, NULL ); + gettimeofday( &pahsc->pahsc_CanaryTime, NULL ); + + /* Launch a canary thread to detect priority abuse. */ + pahsc->pahsc_CanaryRun = 1; + hres = pthread_create(&(pahsc->pahsc_CanaryThread), + NULL /*pthread_attr_t * attr*/, + (pthread_function_t)PaHost_CanaryProc, pahsc); + if( hres != 0 ) + { + pahsc->pahsc_IsCanaryThreadValid = 0; + result = paHostError; + sPaHostError = hres; + goto error; + } + pahsc->pahsc_IsCanaryThreadValid = 1; + + /* Launch a watchdog thread to prevent runaway audio thread. */ + pahsc->pahsc_WatchDogRun = 1; + hres = pthread_create(&(pahsc->pahsc_WatchDogThread), + NULL /*pthread_attr_t * attr*/, + (pthread_function_t)PaHost_WatchDogProc, pahsc); + if( hres != 0 ) + { + pahsc->pahsc_IsWatchDogThreadValid = 0; + result = paHostError; + sPaHostError = hres; + goto error; + } + pahsc->pahsc_IsWatchDogThreadValid = 1; + return result; + +error: + PaHost_StopWatchDog( pahsc ); + return result; +} + +/******************************************************************************************* + * Bump priority of audio thread if running with superuser priveledges. + * if priority bumped then launch a watchdog. + */ +static PaError PaHost_BoostPriority( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + PaError result = paNoError; + struct sched_param schp = { 0 }; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + + pahsc->pahsc_AudioThreadPID = getpid(); + DBUG(("PaHost_BoostPriority: audio PID = %d\n", pahsc->pahsc_AudioThreadPID )); + + /* Choose a priority in the middle of the range. */ + pahsc->pahsc_AudioPriority = (sched_get_priority_max(SCHEDULER_POLICY) - + sched_get_priority_min(SCHEDULER_POLICY)) / 2; + schp.sched_priority = pahsc->pahsc_AudioPriority; + + if (sched_setscheduler(0, SCHEDULER_POLICY, &schp) != 0) + { + DBUG(("PortAudio: only superuser can use real-time priority.\n")); + } + else + { + DBUG(("PortAudio: audio callback priority set to level %d!\n", schp.sched_priority)); + /* We are running at high priority so we should have a watchdog in case audio goes wild. */ + result = PaHost_StartWatchDog( pahsc ); + } + + return result; +} + +/*******************************************************************************************/ +static PaError Pa_AudioThreadProc( internalPortAudioStream *past ) +{ + PaError result; + PaHostSoundControl *pahsc; + ssize_t bytes_read, bytes_written; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + +#ifdef GNUSTEP + GSRegisterCurrentThread(); /* SB20010904 */ +#endif + + result = PaHost_BoostPriority( past ); + if( result < 0 ) goto error; + + past->past_IsActive = 1; + DBUG(("entering thread.\n")); + + while( (past->past_StopNow == 0) && (past->past_StopSoon == 0) ) + { + /* Read data from device */ + if(pahsc->pahsc_NativeInputBuffer) + { + unsigned int totalread = 0; + DBUG(("Pa_AudioThreadProc: attempt to read %d bytes\n", pahsc->pahsc_BytesPerInputBuffer)); + do + { + bytes_read = read(pahsc->pahsc_InputHandle, + (char *)pahsc->pahsc_NativeInputBuffer + totalread, + pahsc->pahsc_BytesPerInputBuffer - totalread); + + if (bytes_read < 0) + { + ERR_RPT(("PortAudio: read interrupted!\n")); + break; + } + + totalread += bytes_read; + } while( totalread < pahsc->pahsc_BytesPerInputBuffer); + } + + /* Convert 16 bit native data to user data and call user routine. */ + DBUG(("converting...\n")); + Pa_StartUsageCalculation( past ); + result = Pa_CallConvertInt16( past, + pahsc->pahsc_NativeInputBuffer, + pahsc->pahsc_NativeOutputBuffer ); + Pa_EndUsageCalculation( past ); + if( result != 0) + { + DBUG(("hmm, Pa_CallConvertInt16() says: %d. i'm bailing.\n", + result)); + break; + } + + /* Write data to device. */ + if( pahsc->pahsc_NativeOutputBuffer ) + { + unsigned int totalwritten = 0; + do + { + bytes_written = write(pahsc->pahsc_OutputHandle, + (void *)pahsc->pahsc_NativeOutputBuffer, + pahsc->pahsc_BytesPerOutputBuffer); + if( bytes_written < 0 ) + { + ERR_RPT(("PortAudio: write interrupted!")); + break; + } + + totalwritten += bytes_written; + } while( totalwritten < pahsc->pahsc_BytesPerOutputBuffer); + } + + Pa_UpdateStreamTime(pahsc); + } + DBUG(("Pa_AudioThreadProc: left audio loop.\n")); + + past->past_IsActive = 0; + PaHost_StopWatchDog( pahsc ); + +error: + DBUG(("leaving audio thread.\n")); +#ifdef GNUSTEP + GSUnregisterCurrentThread(); /* SB20010904 */ +#endif + return result; +} + +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Latency can be optionally set by user by setting +** an environment variable. For example, to set latency to 200 msec, put: +** +** set PA_MIN_LATENCY_MSEC=200 +** +** in the cshrc file. +*/ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") + +int Pa_GetMinNumBuffers( int framesPerBuffer, double framesPerSecond ) +{ + int minBuffers; + int minLatencyMsec = MIN_LATENCY_MSEC; + char *minLatencyText = getenv(PA_LATENCY_ENV_NAME); + if( minLatencyText != NULL ) + { + PRINT(("PA_MIN_LATENCY_MSEC = %s\n", minLatencyText )); + minLatencyMsec = atoi( minLatencyText ); + if( minLatencyMsec < 1 ) minLatencyMsec = 1; + else if( minLatencyMsec > 5000 ) minLatencyMsec = 5000; + } + + minBuffers = (int) ((minLatencyMsec * framesPerSecond) / ( 1000.0 * framesPerBuffer )); + if( minBuffers < 2 ) minBuffers = 2; + return minBuffers; +} + +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + unsigned int minNumBuffers; + internalPortAudioDevice *pad; + DBUG(("PaHost_OpenStream() called.\n" )); + + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) malloc(sizeof(PaHostSoundControl)); + if( pahsc == NULL ) + { + result = paInsufficientMemory; + goto error; + } + memset( pahsc, 0, sizeof(PaHostSoundControl) ); + past->past_DeviceData = (void *) pahsc; + + pahsc->pahsc_OutputHandle = BAD_DEVICE_ID; /* No device currently opened. */ + pahsc->pahsc_InputHandle = BAD_DEVICE_ID; + pahsc->pahsc_IsAudioThreadValid = 0; + pahsc->pahsc_IsWatchDogThreadValid = 0; + + /* Allocate native buffers. */ + pahsc->pahsc_BytesPerInputBuffer = past->past_FramesPerUserBuffer * + past->past_NumInputChannels * sizeof(short); + if( past->past_NumInputChannels > 0) + { + pahsc->pahsc_NativeInputBuffer = (short *) malloc(pahsc->pahsc_BytesPerInputBuffer); + if( pahsc->pahsc_NativeInputBuffer == NULL ) + { + result = paInsufficientMemory; + goto error; + } + } + pahsc->pahsc_BytesPerOutputBuffer = past->past_FramesPerUserBuffer * + past->past_NumOutputChannels * sizeof(short); + if( past->past_NumOutputChannels > 0) + { + pahsc->pahsc_NativeOutputBuffer = (short *) malloc(pahsc->pahsc_BytesPerOutputBuffer); + if( pahsc->pahsc_NativeOutputBuffer == NULL ) + { + result = paInsufficientMemory; + goto error; + } + } + + /* DBUG(("PaHost_OpenStream: pahsc_MinFramesPerHostBuffer = %d\n", pahsc->pahsc_MinFramesPerHostBuffer )); */ + minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); + past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; + + pahsc->pahsc_InverseMicrosPerBuffer = past->past_SampleRate / (1000000.0 * past->past_FramesPerUserBuffer); + DBUG(("past_SampleRate = %g\n", past->past_SampleRate )); + DBUG(("past_FramesPerUserBuffer = %d\n", past->past_FramesPerUserBuffer )); + DBUG(("pahsc_InverseMicrosPerBuffer = %g\n", pahsc->pahsc_InverseMicrosPerBuffer )); + + /* ------------------------- OPEN DEVICE -----------------------*/ + + /* just output */ + if (past->past_OutputDeviceID == past->past_InputDeviceID) + { + + if ((past->past_NumOutputChannels > 0) && (past->past_NumInputChannels > 0) ) + { + pad = Pa_GetInternalDevice( past->past_OutputDeviceID ); + DBUG(("PaHost_OpenStream: attempt to open %s for O_RDWR\n", pad->pad_DeviceName )); + + /* dmazzoni: test it first in nonblocking mode to + make sure the device is not busy */ + pahsc->pahsc_InputHandle = open(pad->pad_DeviceName,O_RDWR|O_NONBLOCK); + if(pahsc->pahsc_InputHandle==-1) + { + ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDWR\n", pad->pad_DeviceName )); + result = paHostError; + goto error; + } + close(pahsc->pahsc_InputHandle); + + pahsc->pahsc_OutputHandle = pahsc->pahsc_InputHandle = + open(pad->pad_DeviceName,O_RDWR); + if(pahsc->pahsc_InputHandle==-1) + { + ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDWR\n", pad->pad_DeviceName )); + result = paHostError; + goto error; + } + Pa_SetLatency( pahsc->pahsc_OutputHandle, + past->past_NumUserBuffers, past->past_FramesPerUserBuffer, + past->past_NumOutputChannels ); + result = Pa_SetupDeviceFormat( pahsc->pahsc_OutputHandle, + past->past_NumOutputChannels, (int)past->past_SampleRate ); + } + } + else + { + if (past->past_NumOutputChannels > 0) + { + pad = Pa_GetInternalDevice( past->past_OutputDeviceID ); + DBUG(("PaHost_OpenStream: attempt to open %s for O_WRONLY\n", pad->pad_DeviceName )); + /* dmazzoni: test it first in nonblocking mode to + make sure the device is not busy */ + pahsc->pahsc_OutputHandle = open(pad->pad_DeviceName,O_WRONLY|O_NONBLOCK); + if(pahsc->pahsc_OutputHandle==-1) + { + ERR_RPT(("PaHost_OpenStream: could not open %s for O_WRONLY\n", pad->pad_DeviceName )); + result = paHostError; + goto error; + } + close(pahsc->pahsc_OutputHandle); + + pahsc->pahsc_OutputHandle = open(pad->pad_DeviceName,O_WRONLY); + if(pahsc->pahsc_OutputHandle==-1) + { + ERR_RPT(("PaHost_OpenStream: could not open %s for O_WRONLY\n", pad->pad_DeviceName )); + result = paHostError; + goto error; + } + Pa_SetLatency( pahsc->pahsc_OutputHandle, + past->past_NumUserBuffers, past->past_FramesPerUserBuffer, + past->past_NumOutputChannels ); + result = Pa_SetupOutputDeviceFormat( pahsc->pahsc_OutputHandle, + past->past_NumOutputChannels, (int)past->past_SampleRate ); + } + + if (past->past_NumInputChannels > 0) + { + pad = Pa_GetInternalDevice( past->past_InputDeviceID ); + DBUG(("PaHost_OpenStream: attempt to open %s for O_RDONLY\n", pad->pad_DeviceName )); + /* dmazzoni: test it first in nonblocking mode to + make sure the device is not busy */ + pahsc->pahsc_InputHandle = open(pad->pad_DeviceName,O_RDONLY|O_NONBLOCK); + if(pahsc->pahsc_InputHandle==-1) + { + ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDONLY\n", pad->pad_DeviceName )); + result = paHostError; + goto error; + } + close(pahsc->pahsc_InputHandle); + + pahsc->pahsc_InputHandle = open(pad->pad_DeviceName,O_RDONLY); + if(pahsc->pahsc_InputHandle==-1) + { + ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDONLY\n", pad->pad_DeviceName )); + result = paHostError; + goto error; + } + Pa_SetLatency( pahsc->pahsc_InputHandle, /* DH20010115 - was OutputHandle! */ + past->past_NumUserBuffers, past->past_FramesPerUserBuffer, + past->past_NumInputChannels ); + result = Pa_SetupInputDeviceFormat( pahsc->pahsc_InputHandle, + past->past_NumInputChannels, (int)past->past_SampleRate ); + } + } + + + DBUG(("PaHost_OpenStream: SUCCESS - result = %d\n", result )); + return result; + +error: + ERR_RPT(("PaHost_OpenStream: ERROR - result = %d\n", result )); + PaHost_CloseStream( past ); + return result; +} + +/*************************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + past = past; /* unused */ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + past = past; /* unused */ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + PaError result = paNoError; + int hres; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + past->past_StopSoon = 0; + past->past_StopNow = 0; + past->past_IsActive = 1; + + /* Use pthread_create() instead of __clone() because: + * - pthread_create also works for other UNIX systems like Solaris, + * - the Java HotSpot VM crashes in pthread_setcanceltype() when using __clone() + */ + hres = pthread_create(&(pahsc->pahsc_AudioThread), + NULL /*pthread_attr_t * attr*/, + (pthread_function_t)Pa_AudioThreadProc, past); + if( hres != 0 ) + { + result = paHostError; + sPaHostError = hres; + pahsc->pahsc_IsAudioThreadValid = 0; + goto error; + } + pahsc->pahsc_IsAudioThreadValid = 1; + +error: + return result; +} + +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + int hres; + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + if( pahsc == NULL ) return paNoError; + + /* Tell background thread to stop generating more data and to let current data play out. */ + past->past_StopSoon = 1; + /* If aborting, tell background thread to stop NOW! */ + if( abort ) past->past_StopNow = 1; + + /* Join thread to recover memory resources. */ + if( pahsc->pahsc_IsAudioThreadValid ) + { + /* This check is needed for GNUSTEP - SB20010904 */ + if ( !pthread_equal( pahsc->pahsc_AudioThread, pthread_self() ) ) + { + hres = pthread_join( pahsc->pahsc_AudioThread, NULL ); + } + else + { + DBUG(("Play thread was stopped from itself - can't do pthread_join()\n")); + hres = 0; + } + + if( hres != 0 ) + { + result = paHostError; + sPaHostError = hres; + } + pahsc->pahsc_IsAudioThreadValid = 0; + } + + past->past_IsActive = 0; + + return result; +} + +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + past = past; /* unused */ + abort = abort; /* unused */ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + past = past; /* unused */ + abort = abort; /* unused */ + return paNoError; +} + +/*******************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + if( pahsc->pahsc_OutputHandle != BAD_DEVICE_ID ) + { + int err = 0; + DBUG(("PaHost_CloseStream: attempt to close output device handle = %d\n", + pahsc->pahsc_OutputHandle )); + + Pa_FlushStream(pahsc->pahsc_OutputHandle); + + err = close(pahsc->pahsc_OutputHandle); + if( err < 0 ) + { + ERR_RPT(("PaHost_CloseStream: warning, closing output device failed.\n")); + } + } + + if( (pahsc->pahsc_InputHandle != BAD_DEVICE_ID) && + (pahsc->pahsc_InputHandle != pahsc->pahsc_OutputHandle) ) + { + int err = 0; + DBUG(("PaHost_CloseStream: attempt to close input device handle = %d\n", + pahsc->pahsc_InputHandle )); + + Pa_FlushStream(pahsc->pahsc_InputHandle); + + err = close(pahsc->pahsc_InputHandle); + if( err < 0 ) + { + ERR_RPT(("PaHost_CloseStream: warning, closing input device failed.\n")); + } + } + pahsc->pahsc_OutputHandle = BAD_DEVICE_ID; + pahsc->pahsc_InputHandle = BAD_DEVICE_ID; + + if( pahsc->pahsc_NativeInputBuffer ) + { + free( pahsc->pahsc_NativeInputBuffer ); + pahsc->pahsc_NativeInputBuffer = NULL; + } + if( pahsc->pahsc_NativeOutputBuffer ) + { + free( pahsc->pahsc_NativeOutputBuffer ); + pahsc->pahsc_NativeOutputBuffer = NULL; + } + + free( pahsc ); + past->past_DeviceData = NULL; + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_Term( void ) +{ + /* Free all of the linked devices. */ + internalPortAudioDevice *pad, *nextPad; + pad = sDeviceList; + while( pad != NULL ) + { + nextPad = pad->pad_Next; + DBUG(("PaHost_Term: freeing %s\n", pad->pad_DeviceName )); + PaHost_FreeFastMemory( pad, sizeof(internalPortAudioDevice) ); + pad = nextPad; + } + sDeviceList = NULL; + return 0; +} + +/************************************************************************* + * Sleep for the requested number of milliseconds. + */ +void Pa_Sleep( long msec ) +{ +#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 physical 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 ); /* FIXME - do we need physical, wired, non-virtual memory? */ + if( addr != NULL ) memset( addr, 0, numBytes ); + return addr; +} + +/************************************************************************* + * 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 ) +{ + numBytes = numBytes; /* unused */ + if( addr != NULL ) free( addr ); +} + + +/***********************************************************************/ +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); +} + +/***********************************************************************/ +long Pa_GetHostError( void ) +{ + return (long) sPaHostError; +} diff --git a/pd/portaudio_v18/pa_unix_oss/pa_unix.h b/pd/portaudio_v18/pa_unix_oss/pa_unix.h new file mode 100644 index 00000000..55b16d50 --- /dev/null +++ b/pd/portaudio_v18/pa_unix_oss/pa_unix.h @@ -0,0 +1,141 @@ +/* + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * Linux OSS Implementation by douglas repetto and Phil Burk + * + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* Modification history: + 20020621: pa_unix_oss.c split into pa_unix.c, pa_unix.h, pa_unix_oss.c by + Augustus Saunders. See pa_unix.c for previous history. */ + +/* + PROPOSED - should we add this to "portaudio.h". Problem with + Pa_QueryDevice() not having same driver name os Pa_OpenStream(). + + A PaDriverInfo structure can be passed to the underlying device + on the Pa_OpenStream() call. The contents and interpretation of + the structure is determined by the PA implementation. +*/ +typedef struct PaDriverInfo /* PROPOSED */ +{ + /* Size of structure. Allows driver to extend the structure without breaking existing applications. */ + int size; + /* Can be used to request a specific device name. */ + const char *name; + unsigned long data; +} +PaDriverInfo; + +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +#define BAD_DEVICE_ID (-1) + +#define MIN_LATENCY_MSEC (100) +#define MIN_TIMEOUT_MSEC (100) +#define MAX_TIMEOUT_MSEC (1000) + +/************************************************* Definitions ********/ +#ifdef __linux__ + #define DEVICE_NAME_BASE "/dev/dsp" +#else + #define DEVICE_NAME_BASE "/dev/audio" +#endif + +#define MAX_CHARS_DEVNAME (32) +#define MAX_SAMPLE_RATES (10) +typedef struct internalPortAudioDevice +{ + struct internalPortAudioDevice *pad_Next; /* Singly linked list. */ + double pad_SampleRates[MAX_SAMPLE_RATES]; /* for pointing to from pad_Info */ + char pad_DeviceName[MAX_CHARS_DEVNAME]; + PaDeviceInfo pad_Info; +} +internalPortAudioDevice; + +/* Define structure to contain all OSS and Linux specific data. */ +typedef struct PaHostSoundControl +{ + int pahsc_OutputHandle; + int pahsc_InputHandle; + int pahsc_AudioPriority; /* priority of background audio thread */ + pthread_t pahsc_AudioThread; /* background audio thread */ + int pahsc_IsAudioThreadValid; /* Is pahsc_AudioThread valid?*/ pid_t pahsc_AudioThreadPID; /* background audio thread */ + pthread_t pahsc_WatchDogThread; /* highest priority thread that protects system */ + int pahsc_IsWatchDogThreadValid; /* Is pahsc_WatchDogThread valid?*/ + int pahsc_WatchDogRun; /* Ask WatchDog to stop. */ + pthread_t pahsc_CanaryThread; /* low priority thread that detects abuse by audio */ + int pahsc_IsCanaryThreadValid; /* Is pahsc_CanaryThread valid?*/ + struct timeval pahsc_CanaryTime; + int pahsc_CanaryRun; /* Ask Canary to stop. */ + short *pahsc_NativeInputBuffer; + short *pahsc_NativeOutputBuffer; + unsigned int pahsc_BytesPerInputBuffer; /* native buffer size in bytes */ + unsigned int pahsc_BytesPerOutputBuffer; /* native buffer size in bytes */ + /* For measuring CPU utilization. */ + struct timeval pahsc_EntryTime; + double pahsc_InverseMicrosPerBuffer; /* 1/Microseconds of real-time audio per user buffer. */ + + /* For calculating stream time */ + int pahsc_LastPosPtr; + double pahsc_LastStreamBytes; +} +PaHostSoundControl; + +/************************************************* Prototypes **********/ + +internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ); +PaError Pa_QueryDevices( void ); +PaError Pa_QueryDevice( const char *deviceName, internalPortAudioDevice *pad ); +PaError Pa_SetupDeviceFormat( int devHandle, int numChannels, int sampleRate ); +PaError Pa_SetupInputDeviceFormat( int devHandle, int numChannels, int sampleRate ); +PaError Pa_SetupOutputDeviceFormat( int devHandle, int numChannels, int sampleRate ); +void Pa_SetLatency( int devHandle, int numBuffers, int framesPerBuffer, int channelsPerFrame ); +void Pa_UpdateStreamTime(PaHostSoundControl *pahsc); +int Pa_FlushStream(int devHandle); diff --git a/pd/portaudio_v18/pa_unix_oss/pa_unix_oss.c b/pd/portaudio_v18/pa_unix_oss/pa_unix_oss.c new file mode 100644 index 00000000..386cd75b --- /dev/null +++ b/pd/portaudio_v18/pa_unix_oss/pa_unix_oss.c @@ -0,0 +1,385 @@ +/* + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * Linux OSS Implementation by douglas repetto and Phil Burk + * + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* Modification history: + 20020621: pa_unix_oss.c split into pa_unix.c, pa_unix.h, pa_unix_oss.c by + Augustus Saunders. See pa_unix.c for previous history. Pa_FlushStream + added by Augustus Saunders for Solaris compatibility. + PLB20021018 - Fill device info table with actual sample rates instead of wished for rates. + - Allow stream to open if sample rate within 10% of desired rate. + 20030630 - Thomas Richter - eliminated unused variable warnings. +*/ + +#include "pa_unix.h" + +#ifdef __linux__ +#include +#else +#include /* JH20010905 */ +#endif + + +#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. + */ +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 /* AFMT_S16_NE */ + + +/********************************************************************* + * Try to open the named device. + * If it opens, try to set various rates and formats and fill in + * the device info structure. + */ +PaError Pa_QueryDevice( const char *deviceName, internalPortAudioDevice *pad ) +{ + int result = paHostError; + int tempDevHandle; + int numChannels, maxNumChannels; + int format; + int numSampleRates; + int sampleRate; + int numRatesToTry; + int lastRate; + int ratesToTry[9] = {96000, 48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; + int i; + + /* douglas: + we have to do this querying in a slightly different order. apparently + some sound cards will give you different info based on their settings. + 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 ) + { + DBUG(("Pa_QueryDevice: could not open %s\n", deviceName )); + return paHostError; + } + + /* Ask OSS what formats are supported by the hardware. */ + pad->pad_Info.nativeSampleFormats = 0; + + if (ioctl(tempDevHandle, SNDCTL_DSP_GETFMTS, &format) == -1) + { + ERR_RPT(("Pa_QueryDevice: could not get format info\n" )); + goto error; + } + if( format & AFMT_U8 ) pad->pad_Info.nativeSampleFormats |= paUInt8; + if( format & AFMT_S16_NE ) pad->pad_Info.nativeSampleFormats |= paInt16; + + /* Negotiate for the maximum number of channels for this device. PLB20010927 + * Consider up to 16 as the upper number of channels. + * Variable numChannels should contain the actual upper limit after the call. + * Thanks to John Lazzaro and Heiko Purnhagen for suggestions. + */ + maxNumChannels = 0; + for( numChannels = 1; numChannels <= 16; numChannels++ ) + { + int temp = numChannels; + DBUG(("Pa_QueryDevice: use SNDCTL_DSP_CHANNELS, numChannels = %d\n", numChannels )) + if(ioctl(tempDevHandle, SNDCTL_DSP_CHANNELS, &temp) < 0 ) + { + /* ioctl() failed so bail out if we already have stereo */ + if( numChannels > 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(("Pa_QueryDevice: temp = %d\n", temp )) + if( temp > maxNumChannels ) maxNumChannels = temp; /* Save maximum. */ + } + } + + /* 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) + { + maxNumChannels = 1; + } + else + { + maxNumChannels = (stereo) ? 2 : 1; + } + DBUG(("Pa_QueryDevice: use SNDCTL_DSP_STEREO, maxNumChannels = %d\n", maxNumChannels )) + } + + pad->pad_Info.maxOutputChannels = maxNumChannels; + DBUG(("Pa_QueryDevice: maxNumChannels = %d\n", maxNumChannels)) + + /* 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); + } + + /* FIXME - for now, assume maxInputChannels = maxOutputChannels. + * Eventually do separate queries for O_WRONLY and O_RDONLY + */ + pad->pad_Info.maxInputChannels = pad->pad_Info.maxOutputChannels; + + DBUG(("Pa_QueryDevice: maxInputChannels = %d\n", + pad->pad_Info.maxInputChannels)) + + + /* Determine available sample rates by trying each one and seeing result. + * OSS often supports funky rates such as 44188 instead of 44100! + */ + numSampleRates = 0; + lastRate = 0; + numRatesToTry = sizeof(ratesToTry)/sizeof(int); + for (i = 0; i < numRatesToTry; i++) + { + sampleRate = ratesToTry[i]; + + if (ioctl(tempDevHandle, SNDCTL_DSP_SPEED, &sampleRate) >= 0 ) /* PLB20010817 */ + { + /* Use whatever rate OSS tells us. PLB20021018 */ + if (sampleRate != lastRate) + { + DBUG(("Pa_QueryDevice: adding sample rate: %d\n", sampleRate)) + pad->pad_SampleRates[numSampleRates] = (float)sampleRate; + numSampleRates++; + lastRate = sampleRate; + } + else + { + DBUG(("Pa_QueryDevice: dang - got sample rate %d again!\n", sampleRate)) + } + } + } + + DBUG(("Pa_QueryDevice: final numSampleRates = %d\n", numSampleRates)) + if (numSampleRates==0) /* HP20010922 */ + { + /* Desparate attempt to keep running even though no good rates found! */ + ERR_RPT(("Pa_QueryDevice: no supported sample rate (or SNDCTL_DSP_SPEED ioctl call failed). Force 44100 Hz\n" )); + pad->pad_SampleRates[numSampleRates++] = 44100; + } + + pad->pad_Info.numSampleRates = numSampleRates; + pad->pad_Info.sampleRates = pad->pad_SampleRates; /* use pointer to embedded array */ + + pad->pad_Info.name = deviceName; + + result = paNoError; + +error: + /* We MUST close the handle here or we won't be able to reopen it later!!! */ + close(tempDevHandle); + + return result; +} + +/*******************************************************************************************/ +PaError Pa_SetupDeviceFormat( int devHandle, int numChannels, int sampleRate ) +{ + PaError result = paNoError; + int tmp; + + /* Set format, channels, and rate in this order to keep OSS happy. */ + /* Set data format. FIXME - handle more native formats. */ + tmp = AFMT_S16_NE; + if( ioctl(devHandle,SNDCTL_DSP_SETFMT,&tmp) == -1) + { + ERR_RPT(("Pa_SetupDeviceFormat: could not SNDCTL_DSP_SETFMT\n" )); + return paHostError; + } + if( tmp != AFMT_S16_NE ) + { + ERR_RPT(("Pa_SetupDeviceFormat: HW does not support AFMT_S16_NE\n" )); + return paHostError; + } + + + /* Set number of channels. */ + tmp = numChannels; + if (ioctl(devHandle, SNDCTL_DSP_CHANNELS, &numChannels) == -1) + { + ERR_RPT(("Pa_SetupDeviceFormat: could not SNDCTL_DSP_CHANNELS\n" )); + return paHostError; + } + if( tmp != numChannels) + { + ERR_RPT(("Pa_SetupDeviceFormat: HW does not support %d channels\n", numChannels )); + return paHostError; + } + + /* Set playing frequency. */ + tmp = sampleRate; + if( ioctl(devHandle,SNDCTL_DSP_SPEED,&tmp) == -1) + { + ERR_RPT(("Pa_SetupDeviceFormat: could not SNDCTL_DSP_SPEED\n" )); + return paHostError; + } + else if( tmp != sampleRate ) + { + int percentError = abs( (100 * (sampleRate - tmp)) / sampleRate ); + PRINT(("Pa_SetupDeviceFormat: warning - requested sample rate = %d Hz - closest = %d\n", + sampleRate, tmp )); + /* Allow sample rate within 10% off of requested rate. PLB20021018 + * Sometimes OSS uses a funky rate like 44188 instead of 44100. + */ + if( percentError > 10 ) + { + ERR_RPT(("Pa_SetupDeviceFormat: HW does not support %d Hz sample rate\n",sampleRate )); + return paHostError; + } + } + + return result; +} + +PaError Pa_SetupOutputDeviceFormat( int devHandle, int numChannels, int sampleRate ) +{ + return Pa_SetupDeviceFormat(devHandle, numChannels, sampleRate); +} + +PaError Pa_SetupInputDeviceFormat( int devHandle, int numChannels, int sampleRate ) +{ + return Pa_SetupDeviceFormat(devHandle, numChannels, sampleRate); +} + + +/******************************************************************************************* +** Set number of fragments and size of fragments to achieve desired latency. +*/ + +static int CalcHigherLogTwo( int n ) +{ + int log2 = 0; + while( (1< 8 ) + { + numBuffers = (numBuffers + 1) >> 1; + framesPerBuffer = framesPerBuffer << 1; + } + + /* calculate size of buffers in bytes */ + bufferSize = framesPerBuffer * channelsPerFrame * sizeof(short); /* FIXME - other sizes? */ + + /* Calculate next largest power of two */ + powerOfTwo = CalcHigherLogTwo( bufferSize ); + DBUG(("Pa_SetLatency: numBuffers = %d, framesPerBuffer = %d, powerOfTwo = %d\n", + numBuffers, framesPerBuffer, powerOfTwo )); + + /* Encode info into a single int */ + tmp=(numBuffers<<16) + powerOfTwo; + + if(ioctl(devHandle,SNDCTL_DSP_SETFRAGMENT,&tmp) == -1) + { + ERR_RPT(("Pa_SetLatency: could not SNDCTL_DSP_SETFRAGMENT\n" )); + /* Don't return an error. Best to just continue and hope for the best. */ + ERR_RPT(("Pa_SetLatency: numBuffers = %d, framesPerBuffer = %d, powerOfTwo = %d\n", + numBuffers, framesPerBuffer, powerOfTwo )); + } +} + +/***********************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + internalPortAudioStream *past = (internalPortAudioStream *) stream; + PaHostSoundControl *pahsc; + + count_info info; + int delta; + + if( past == NULL ) return paBadStreamPtr; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + if( pahsc->pahsc_NativeOutputBuffer ) + { + ioctl(pahsc->pahsc_OutputHandle, SNDCTL_DSP_GETOPTR, &info); + delta = (info.bytes - pahsc->pahsc_LastPosPtr) & 0x000FFFFF; + return (pahsc->pahsc_LastStreamBytes + delta) / (past->past_NumOutputChannels * sizeof(short)); + } + else + { + ioctl(pahsc->pahsc_InputHandle, SNDCTL_DSP_GETIPTR, &info); + delta = (info.bytes - pahsc->pahsc_LastPosPtr) & 0x000FFFFF; + return (pahsc->pahsc_LastStreamBytes + delta) / (past->past_NumInputChannels * sizeof(short)); + } +} + +void Pa_UpdateStreamTime(PaHostSoundControl *pahsc) +{ + count_info info; + int delta; + + /* Update current stream time (using a double so that + we don't wrap around like info.bytes does) */ + if( pahsc->pahsc_NativeOutputBuffer ) + { + ioctl(pahsc->pahsc_OutputHandle, SNDCTL_DSP_GETOPTR, &info); + } + else + { + ioctl(pahsc->pahsc_InputHandle, SNDCTL_DSP_GETIPTR, &info); + } + delta = (info.bytes - pahsc->pahsc_LastPosPtr) & 0x000FFFFF; + pahsc->pahsc_LastStreamBytes += delta; + pahsc->pahsc_LastPosPtr = info.bytes; +} + +PaError Pa_FlushStream(int devHandle) +{ + /* AS: This doesn't do anything under OSS; it was added for Solaris.*/ + devHandle = devHandle; /* unused */ + return paNoError; +} diff --git a/pd/portaudio_v18/pa_unix_oss/pa_unix_solaris.c b/pd/portaudio_v18/pa_unix_oss/pa_unix_solaris.c new file mode 100644 index 00000000..1e1846b3 --- /dev/null +++ b/pd/portaudio_v18/pa_unix_oss/pa_unix_solaris.c @@ -0,0 +1,397 @@ +/* + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * Linux OSS Implementation by douglas repetto and Phil Burk + * + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* Modification history: + 20020621: Initial cut at Solaris modifications jointly by Sam Bayer + and Augustus Saunders. + 20030206 - Martin Rohrbach - various mods for Solaris + */ + +#define __solaris_native__ + +#include "pa_unix.h" + +/* SAM 6/2/02: Docs say we should include sys/audio.h, but + that doesn't exist pre Solaris 2.8. These headers work fine. */ + +#include +#include + +/********************************************************************* + * Try to open the named device. + * If it opens, try to set various rates and formats and fill in + * the device info structure. + */ +PaError Pa_QueryDevice( const char *deviceName, internalPortAudioDevice *pad ) +{ + int result = paHostError; + int tempDevHandle; + int numChannels, maxNumChannels; + int numSampleRates; + int sampleRate; + int numRatesToTry; + int ratesToTry[9] = {96000, 48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; + int i; + audio_info_t solaris_info; + audio_device_t device_info; + + /* 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 + + */ + /* + to check a device for it's capabilities, it's probably better to use the + equivalent "-ctl"-descriptor - MR + */ + char devname[strlen(deviceName) + 4]; + if ( (tempDevHandle = open(strcat(strcpy(devname, deviceName), "ctl"), O_WRONLY|O_NONBLOCK)) == -1 ) + { + DBUG(("Pa_QueryDevice: could not open %s\n", deviceName )); + return paHostError; + } + + /* Ask OSS what formats are supported by the hardware. */ + pad->pad_Info.nativeSampleFormats = 0; + AUDIO_INITINFO(&solaris_info); + + /* SAM 12/31/01: Sparc native does mulaw, alaw and PCM. + I think PCM is signed. */ + + for (i = 8; i <= 32; i += 8) { + solaris_info.play.precision = i; + solaris_info.play.encoding = AUDIO_ENCODING_LINEAR; + /* If there are no errors, add the format. */ + if (ioctl(tempDevHandle, AUDIO_SETINFO, &solaris_info) > -1) { + switch (i) { + case 8: + pad->pad_Info.nativeSampleFormats |= paInt8; + break; + case 16: + pad->pad_Info.nativeSampleFormats |= paInt16; + break; + case 24: + pad->pad_Info.nativeSampleFormats |= paInt24; + break; + case 32: + pad->pad_Info.nativeSampleFormats |= paInt32; + break; + } + } + } + + maxNumChannels = 0; + for( numChannels = 1; numChannels <= 16; numChannels++ ) + { + int temp = numChannels; + DBUG(("Pa_QueryDevice: use SNDCTL_DSP_CHANNELS, numChannels = %d\n", numChannels )) + AUDIO_INITINFO(&solaris_info); + solaris_info.play.channels = temp; + if (ioctl(tempDevHandle, AUDIO_SETINFO, &solaris_info) < 0) + { + /* ioctl() failed so bail out if we already have stereo */ + if( numChannels > 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(("Pa_QueryDevice: temp = %d\n", temp )) + if( temp > maxNumChannels ) maxNumChannels = temp; /* Save maximum. */ + } + } + + pad->pad_Info.maxOutputChannels = maxNumChannels; + DBUG(("Pa_QueryDevice: maxNumChannels = %d\n", maxNumChannels)) + + /* FIXME - for now, assume maxInputChannels = maxOutputChannels. + * Eventually do separate queries for O_WRONLY and O_RDONLY + */ + pad->pad_Info.maxInputChannels = pad->pad_Info.maxOutputChannels; + + DBUG(("Pa_QueryDevice: maxInputChannels = %d\n", + pad->pad_Info.maxInputChannels)) + + + /* Determine available sample rates by trying each one and seeing result. + */ + numSampleRates = 0; + + AUDIO_INITINFO(&solaris_info); + + numRatesToTry = sizeof(ratesToTry)/sizeof(int); + for (i = 0; i < numRatesToTry; i++) + { + sampleRate = ratesToTry[i]; + + solaris_info.play.sample_rate = sampleRate; /* AS: We opened for Write, so set play */ + if (ioctl(tempDevHandle, AUDIO_SETINFO, &solaris_info) >= 0 ) /* PLB20010817 */ + { + if (sampleRate == ratesToTry[i]) + { + DBUG(("Pa_QueryDevice: got sample rate: %d\n", sampleRate)) + pad->pad_SampleRates[numSampleRates] = (float)ratesToTry[i]; + numSampleRates++; + } + } + } + + DBUG(("Pa_QueryDevice: final numSampleRates = %d\n", numSampleRates)) + if (numSampleRates==0) /* HP20010922 */ + { + ERR_RPT(("Pa_QueryDevice: no supported sample rate (or SNDCTL_DSP_SPEED ioctl call failed).\n" )); + goto error; + } + + pad->pad_Info.numSampleRates = numSampleRates; + pad->pad_Info.sampleRates = pad->pad_SampleRates; + + /* query for the device name instead of using the filesystem-device - MR */ + if (ioctl(tempDevHandle, AUDIO_GETDEV, &device_info) == -1) { + pad->pad_Info.name = deviceName; + } else { + char *pt = (char *)PaHost_AllocateFastMemory(strlen(device_info.name)); + strcpy(pt, device_info.name); + pad->pad_Info.name = pt; + } + + result = paNoError; + +error: + /* We MUST close the handle here or we won't be able to reopen it later!!! */ + close(tempDevHandle); + + return result; +} + +/*******************************************************************************************/ + +PaError Pa_SetupInputDeviceFormat( int devHandle, int numChannels, int sampleRate ) +{ + audio_info_t solaris_info; + AUDIO_INITINFO(&solaris_info); + + /* Sam Bayer/Bryan George 1/10/02: Various folks have + reported that on Solaris Ultra II, the not-right thing + happens on read unless you make sure the audio device is + flushed. The folks who wrote the Robust Audio Tool say: + + XXX driver issue - on Ultra II's if you don't drain + * the device before reading commences then the device + * reads in blocks of 500ms irrespective of the + * blocksize set. After a minute or so it flips into the + * correct mode, but obviously this is too late to be + * useful for most apps. grrr. + */ + /* AS: And the Solaris man audio pages say you should flush before changing formats + anyway. So there you go. */ + if (Pa_FlushStream(devHandle) != paNoError) + return paHostError; + + solaris_info.record.encoding = AUDIO_ENCODING_LINEAR; + solaris_info.record.sample_rate = sampleRate; + solaris_info.record.precision = 16; + solaris_info.record.channels = numChannels; + + if (ioctl(devHandle, AUDIO_SETINFO, &solaris_info) == -1) + { + ERR_RPT(("Pa_SetupDeviceFormat: could not set audio info\n" )); + return paHostError; + } + + return paNoError; +} + +PaError Pa_SetupOutputDeviceFormat( int devHandle, int numChannels, int sampleRate ) +{ + audio_info_t solaris_info; + AUDIO_INITINFO(&solaris_info); + + /* Sam Bayer/Bryan George 1/10/02: Various folks have + reported that on Solaris Ultra II, the not-right thing + happens on read unless you make sure the audio device is + flushed. The folks who wrote the Robust Audio Tool say: + + XXX driver issue - on Ultra II's if you don't drain + * the device before reading commences then the device + * reads in blocks of 500ms irrespective of the + * blocksize set. After a minute or so it flips into the + * correct mode, but obviously this is too late to be + * useful for most apps. grrr. + */ + /* AS: And the Solaris man audio pages say you should flush before changing formats + anyway. So there you go. */ + if (Pa_FlushStream(devHandle) != paNoError) + return paHostError; + + solaris_info.play.encoding = AUDIO_ENCODING_LINEAR; + solaris_info.play.sample_rate = sampleRate; + solaris_info.play.precision = 16; + solaris_info.play.channels = numChannels; + + if (ioctl(devHandle, AUDIO_SETINFO, &solaris_info) == -1) + { + ERR_RPT(("Pa_SetupDeviceFormat: could not set audio info\n" )); + return paHostError; + } + + return paNoError; +} + +PaError Pa_SetupDeviceFormat( int devHandle, int numChannels, int sampleRate ) +{ + PaError result = paNoError; + + result = Pa_SetupOutputDeviceFormat(devHandle, numChannels, sampleRate); + if (result != paNoError) + return result; + return Pa_SetupInputDeviceFormat(devHandle, numChannels, sampleRate); +} + +/******************************************************************************************* +** Set number of fragments and size of fragments to achieve desired latency. +*/ + +static PaError Pa_Unpause(int devHandle); +static PaError Pa_PauseAndFlush(int devHandle); + +void Pa_SetLatency( int devHandle, int numBuffers, int framesPerBuffer, int channelsPerFrame ) +{ + int bufferSize; + audio_info_t solaris_info; + + /* Increase size of buffers and reduce number of buffers to reduce latency inside driver. */ + while( numBuffers > 8 ) + { + numBuffers = (numBuffers + 1) >> 1; + framesPerBuffer = framesPerBuffer << 1; + } + + /* calculate size of buffers in bytes */ + bufferSize = framesPerBuffer * channelsPerFrame * sizeof(short); /* FIXME - other sizes? */ + + DBUG(("Pa_SetLatency: numBuffers = %d, framesPerBuffer = %d\n", + numBuffers, framesPerBuffer)); + + /* SAM 6/6/02: Documentation says to pause and flush before + changing buffer size. */ + + if (Pa_PauseAndFlush(devHandle) != paNoError) { + ERR_RPT(("Pa_SetLatency: could not pause audio\n" )); + return; + } + + AUDIO_INITINFO(&solaris_info); + + /* AS: Doesn't look like solaris has multiple buffers, + so I'm being conservative and + making one buffer. Might not be what we want... */ + + solaris_info.play.buffer_size = solaris_info.record.buffer_size = bufferSize; + + if (ioctl(devHandle, AUDIO_SETINFO, &solaris_info) == -1) + { + ERR_RPT(("Pa_SetLatency: could not set audio info\n" )); + } + Pa_Unpause(devHandle); +} + +/***********************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + internalPortAudioStream *past = (internalPortAudioStream *) stream; + PaHostSoundControl *pahsc; + audio_info_t solaris_info; + + if( past == NULL ) return paBadStreamPtr; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + ioctl(pahsc->pahsc_OutputHandle, AUDIO_GETINFO, &solaris_info); + return solaris_info.play.samples; +} + +void Pa_UpdateStreamTime(PaHostSoundControl *pahsc) +{ + /* AS: Don't need to do anytying for this under Solaris.*/ +} + +static PaError Pa_PauseAndFlush(int devHandle) +{ + audio_info_t solaris_info; + AUDIO_INITINFO(&solaris_info); + + solaris_info.play.pause = solaris_info.record.pause = 1; + + if (ioctl(devHandle, AUDIO_SETINFO, &solaris_info) == -1) + { + ERR_RPT(("Pa_FlushStream failed.\n")); + return paHostError; + } + + if (ioctl(devHandle, I_FLUSH, FLUSHRW) == -1) + { + ERR_RPT(("Pa_FlushStream failed.\n")); + + /* Unpause! */ + AUDIO_INITINFO(&solaris_info); + solaris_info.play.pause = solaris_info.record.pause = 0; + ioctl(devHandle, AUDIO_SETINFO, &solaris_info); + + return paHostError; + } + return paNoError; +} + +static PaError Pa_Unpause(int devHandle) +{ + audio_info_t solaris_info; + AUDIO_INITINFO(&solaris_info); + + solaris_info.play.pause = solaris_info.record.pause = 0; + + if (ioctl(devHandle, AUDIO_SETINFO, &solaris_info) == -1) + { + ERR_RPT(("Pa_FlushStream failed.\n")); + return paHostError; + } + + return paNoError; +} + +PaError Pa_FlushStream(int devHandle) +{ + PaError res = Pa_PauseAndFlush(devHandle); + if (res == paNoError) + return Pa_Unpause(devHandle); + else return res; +} diff --git a/pd/portaudio_v18/pa_unix_oss/recplay.c b/pd/portaudio_v18/pa_unix_oss/recplay.c new file mode 100644 index 00000000..9d4c78cf --- /dev/null +++ b/pd/portaudio_v18/pa_unix_oss/recplay.c @@ -0,0 +1,114 @@ +/* + * recplay.c + * Phil Burk + * Minimal record and playback test. + * + */ +#include +#include +#include +#ifndef __STDC__ +/* #include */ +#endif /* __STDC__ */ +#include +#ifdef __STDC__ +#include +#else /* __STDC__ */ +#include +#endif /* __STDC__ */ +#include + +#define NUM_BYTES (64*1024) +#define BLOCK_SIZE (4*1024) + +#define AUDIO "/dev/dsp" + +char buffer[NUM_BYTES]; + +int audioDev = 0; + +main (int argc, char *argv[]) +{ + int numLeft; + char *ptr; + int num; + int samplesize; + + /********** RECORD ********************/ + /* Open audio device. */ + audioDev = open (AUDIO, O_RDONLY, 0); + if (audioDev == -1) + { + perror (AUDIO); + exit (-1); + } + + /* Set to 16 bit samples. */ + samplesize = 16; + ioctl(audioDev, SNDCTL_DSP_SAMPLESIZE, &samplesize); + if (samplesize != 16) + { + perror("Unable to set the sample size."); + exit(-1); + } + + /* Record in blocks */ + printf("Begin recording.\n"); + numLeft = NUM_BYTES; + ptr = buffer; + while( numLeft >= BLOCK_SIZE ) + { + if ( (num = read (audioDev, ptr, BLOCK_SIZE)) < 0 ) + { + perror (AUDIO); + exit (-1); + } + else + { + printf("Read %d bytes\n", num); + ptr += num; + numLeft -= num; + } + } + + close( audioDev ); + + /********** PLAYBACK ********************/ + /* Open audio device for writing. */ + audioDev = open (AUDIO, O_WRONLY, 0); + if (audioDev == -1) + { + perror (AUDIO); + exit (-1); + } + + /* Set to 16 bit samples. */ + samplesize = 16; + ioctl(audioDev, SNDCTL_DSP_SAMPLESIZE, &samplesize); + if (samplesize != 16) + { + perror("Unable to set the sample size."); + exit(-1); + } + + /* Play in blocks */ + printf("Begin playing.\n"); + numLeft = NUM_BYTES; + ptr = buffer; + while( numLeft >= BLOCK_SIZE ) + { + if ( (num = write (audioDev, ptr, BLOCK_SIZE)) < 0 ) + { + perror (AUDIO); + exit (-1); + } + else + { + printf("Wrote %d bytes\n", num); + ptr += num; + numLeft -= num; + } + } + + close( audioDev ); +} -- cgit v1.2.1