summaryrefslogtreecommitdiff
path: root/portaudio/src/os/unix
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2022-08-25 14:54:53 -0500
committersanine <sanine.not@pm.me>2022-08-25 14:54:53 -0500
commit37c97e345d12f95dde44e1d1a4c2f2aadd4615bc (patch)
treee1bb25bc855883062bdd7847ff2c04290f71c840 /portaudio/src/os/unix
parent5634c7b04da619669f2f29f6798c03982be05180 (diff)
add initial structure
Diffstat (limited to 'portaudio/src/os/unix')
-rw-r--r--portaudio/src/os/unix/pa_unix_hostapis.c103
-rw-r--r--portaudio/src/os/unix/pa_unix_util.c710
-rw-r--r--portaudio/src/os/unix/pa_unix_util.h224
3 files changed, 1037 insertions, 0 deletions
diff --git a/portaudio/src/os/unix/pa_unix_hostapis.c b/portaudio/src/os/unix/pa_unix_hostapis.c
new file mode 100644
index 0000000..7f1a51f
--- /dev/null
+++ b/portaudio/src/os/unix/pa_unix_hostapis.c
@@ -0,0 +1,103 @@
+/*
+ * $Id$
+ * Portable Audio I/O Library UNIX initialization table
+ *
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 1999-2002 Ross Bencina, 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.
+ *
+ * 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.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * 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. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/** @file
+ @ingroup unix_src
+*/
+
+#include "pa_hostapi.h"
+
+PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+/* Added for IRIX, Pieter, oct 2, 2003: */
+PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+/* Linux AudioScience HPI */
+PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+
+/** Note that on Linux, ALSA is placed before OSS so that the former is preferred over the latter.
+ */
+
+PaUtilHostApiInitializer *paHostApiInitializers[] =
+ {
+#ifdef __linux__
+
+#if PA_USE_ALSA
+ PaAlsa_Initialize,
+#endif
+
+#if PA_USE_OSS
+ PaOSS_Initialize,
+#endif
+
+#else /* __linux__ */
+
+#if PA_USE_OSS
+ PaOSS_Initialize,
+#endif
+
+#if PA_USE_ALSA
+ PaAlsa_Initialize,
+#endif
+
+#endif /* __linux__ */
+
+#if PA_USE_JACK
+ PaJack_Initialize,
+#endif
+ /* Added for IRIX, Pieter, oct 2, 2003: */
+#if PA_USE_SGI
+ PaSGI_Initialize,
+#endif
+
+#if PA_USE_ASIHPI
+ PaAsiHpi_Initialize,
+#endif
+
+#if PA_USE_COREAUDIO
+ PaMacCore_Initialize,
+#endif
+
+#if PA_USE_SKELETON
+ PaSkeleton_Initialize,
+#endif
+
+ 0 /* NULL terminated array */
+ };
diff --git a/portaudio/src/os/unix/pa_unix_util.c b/portaudio/src/os/unix/pa_unix_util.c
new file mode 100644
index 0000000..459b2be
--- /dev/null
+++ b/portaudio/src/os/unix/pa_unix_util.c
@@ -0,0 +1,710 @@
+/*
+ * $Id$
+ * Portable Audio I/O Library
+ * UNIX platform-specific support functions
+ *
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 1999-2000 Ross Bencina
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * 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. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/** @file
+ @ingroup unix_src
+*/
+
+#include <pthread.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <string.h> /* For memset */
+#include <math.h>
+#include <errno.h>
+
+#if defined(__APPLE__) && !defined(HAVE_MACH_ABSOLUTE_TIME)
+#define HAVE_MACH_ABSOLUTE_TIME
+#endif
+#ifdef HAVE_MACH_ABSOLUTE_TIME
+#include <mach/mach_time.h>
+#endif
+
+#include "pa_util.h"
+#include "pa_unix_util.h"
+#include "pa_debugprint.h"
+
+/*
+ Track memory allocations to avoid leaks.
+ */
+
+#if PA_TRACK_MEMORY
+static int numAllocations_ = 0;
+#endif
+
+
+void *PaUtil_AllocateMemory( long size )
+{
+ void *result = malloc( size );
+
+#if PA_TRACK_MEMORY
+ if( result != NULL ) numAllocations_ += 1;
+#endif
+ return result;
+}
+
+
+void PaUtil_FreeMemory( void *block )
+{
+ if( block != NULL )
+ {
+ free( block );
+#if PA_TRACK_MEMORY
+ numAllocations_ -= 1;
+#endif
+
+ }
+}
+
+
+int PaUtil_CountCurrentlyAllocatedBlocks( void )
+{
+#if PA_TRACK_MEMORY
+ return numAllocations_;
+#else
+ return 0;
+#endif
+}
+
+
+void Pa_Sleep( long msec )
+{
+#ifdef HAVE_NANOSLEEP
+ struct timespec req = {0}, rem = {0};
+ PaTime time = msec / 1.e3;
+ req.tv_sec = (time_t)time;
+ assert(time - req.tv_sec < 1.0);
+ req.tv_nsec = (long)((time - req.tv_sec) * 1.e9);
+ nanosleep(&req, &rem);
+ /* XXX: Try sleeping the remaining time (contained in rem) if interrupted by a signal? */
+#else
+ while( msec > 999 ) /* For OpenBSD and IRIX, argument */
+ { /* to usleep must be < 1000000. */
+ usleep( 999000 );
+ msec -= 999;
+ }
+ usleep( msec * 1000 );
+#endif
+}
+
+#ifdef HAVE_MACH_ABSOLUTE_TIME
+/*
+ Discussion on the CoreAudio mailing list suggests that calling
+ gettimeofday (or anything else in the BSD layer) is not real-time
+ safe, so we use mach_absolute_time on OSX. This implementation is
+ based on these two links:
+
+ Technical Q&A QA1398 - Mach Absolute Time Units
+ http://developer.apple.com/mac/library/qa/qa2004/qa1398.html
+
+ Tutorial: Performance and Time.
+ http://www.macresearch.org/tutorial_performance_and_time
+*/
+
+/* Scaler to convert the result of mach_absolute_time to seconds */
+static double machSecondsConversionScaler_ = 0.0;
+#endif
+
+void PaUtil_InitializeClock( void )
+{
+#ifdef HAVE_MACH_ABSOLUTE_TIME
+ mach_timebase_info_data_t info;
+ kern_return_t err = mach_timebase_info( &info );
+ if( err == 0 )
+ machSecondsConversionScaler_ = 1e-9 * (double) info.numer / (double) info.denom;
+#endif
+}
+
+
+PaTime PaUtil_GetTime( void )
+{
+#ifdef HAVE_MACH_ABSOLUTE_TIME
+ return mach_absolute_time() * machSecondsConversionScaler_;
+#elif defined(HAVE_CLOCK_GETTIME)
+ struct timespec tp;
+ clock_gettime(CLOCK_REALTIME, &tp);
+ return (PaTime)(tp.tv_sec + tp.tv_nsec * 1e-9);
+#else
+ struct timeval tv;
+ gettimeofday( &tv, NULL );
+ return (PaTime) tv.tv_usec * 1e-6 + tv.tv_sec;
+#endif
+}
+
+PaError PaUtil_InitializeThreading( PaUtilThreading *threading )
+{
+ (void) paUtilErr_;
+ return paNoError;
+}
+
+void PaUtil_TerminateThreading( PaUtilThreading *threading )
+{
+}
+
+PaError PaUtil_StartThreading( PaUtilThreading *threading, void *(*threadRoutine)(void *), void *data )
+{
+ pthread_create( &threading->callbackThread, NULL, threadRoutine, data );
+ return paNoError;
+}
+
+PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *exitResult )
+{
+ PaError result = paNoError;
+ void *pret;
+
+ if( exitResult )
+ *exitResult = paNoError;
+
+ /* If pthread_cancel is not supported (Android platform) whole this function can lead to indefinite waiting if
+ working thread (callbackThread) has'n received any stop signals from outside, please keep
+ this in mind when considering using PaUtil_CancelThreading
+ */
+#ifdef PTHREAD_CANCELED
+ /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */
+ if( !wait )
+ pthread_cancel( threading->callbackThread ); /* XXX: Safe to call this if the thread has exited on its own? */
+#endif
+ pthread_join( threading->callbackThread, &pret );
+
+#ifdef PTHREAD_CANCELED
+ if( pret && PTHREAD_CANCELED != pret )
+#else
+ /* !wait means the thread may have been canceled */
+ if( pret && wait )
+#endif
+ {
+ if( exitResult )
+ *exitResult = *(PaError *) pret;
+ free( pret );
+ }
+
+ return result;
+}
+
+/* Threading */
+/* paUnixMainThread
+ * We have to be a bit careful with defining this global variable,
+ * as explained below. */
+#ifdef __APPLE__
+/* apple/gcc has a "problem" with global vars and dynamic libs.
+ Initializing it seems to fix the problem.
+ Described a bit in this thread:
+ http://gcc.gnu.org/ml/gcc/2005-06/msg00179.html
+*/
+pthread_t paUnixMainThread = 0;
+#else
+/*pthreads are opaque. We don't know that assigning it an int value
+ always makes sense, so we don't initialize it unless we have to.*/
+pthread_t paUnixMainThread = 0;
+#endif
+
+PaError PaUnixThreading_Initialize( void )
+{
+ paUnixMainThread = pthread_self();
+ return paNoError;
+}
+
+static PaError BoostPriority( PaUnixThread* self )
+{
+ PaError result = paNoError;
+ struct sched_param spm = { 0 };
+ /* Priority should only matter between contending FIFO threads? */
+ spm.sched_priority = 1;
+
+ assert( self );
+
+ if( pthread_setschedparam( self->thread, SCHED_FIFO, &spm ) != 0 )
+ {
+ PA_UNLESS( errno == EPERM, paInternalError ); /* Lack permission to raise priority */
+ PA_DEBUG(( "Failed bumping priority\n" ));
+ result = 0;
+ }
+ else
+ {
+ result = 1; /* Success */
+ }
+error:
+ return result;
+}
+
+PaError PaUnixThread_New( PaUnixThread* self, void* (*threadFunc)( void* ), void* threadArg, PaTime waitForChild,
+ int rtSched )
+{
+ PaError result = paNoError;
+ pthread_attr_t attr;
+ int started = 0;
+
+ memset( self, 0, sizeof (PaUnixThread) );
+ PaUnixMutex_Initialize( &self->mtx );
+ PA_ASSERT_CALL( pthread_cond_init( &self->cond, NULL ), 0 );
+
+ self->parentWaiting = 0 != waitForChild;
+
+ /* Spawn thread */
+
+/* Temporarily disabled since we should test during configuration for presence of required mman.h header */
+#if 0
+#if defined _POSIX_MEMLOCK && (_POSIX_MEMLOCK != -1)
+ if( rtSched )
+ {
+ if( mlockall( MCL_CURRENT | MCL_FUTURE ) < 0 )
+ {
+ int savedErrno = errno; /* In case errno gets overwritten */
+ assert( savedErrno != EINVAL ); /* Most likely a programmer error */
+ PA_UNLESS( (savedErrno == EPERM), paInternalError );
+ PA_DEBUG(( "%s: Failed locking memory\n", __FUNCTION__ ));
+ }
+ else
+ PA_DEBUG(( "%s: Successfully locked memory\n", __FUNCTION__ ));
+ }
+#endif
+#endif
+
+ PA_UNLESS( !pthread_attr_init( &attr ), paInternalError );
+ /* Priority relative to other processes */
+ PA_UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError );
+
+ PA_UNLESS( !pthread_create( &self->thread, &attr, threadFunc, threadArg ), paInternalError );
+ started = 1;
+
+ if( rtSched )
+ {
+#if 0
+ if( self->useWatchdog )
+ {
+ int err;
+ struct sched_param wdSpm = { 0 };
+ /* Launch watchdog, watchdog sets callback thread priority */
+ int prio = PA_MIN( self->rtPrio + 4, sched_get_priority_max( SCHED_FIFO ) );
+ wdSpm.sched_priority = prio;
+
+ PA_UNLESS( !pthread_attr_init( &attr ), paInternalError );
+ PA_UNLESS( !pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ), paInternalError );
+ PA_UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError );
+ PA_UNLESS( !pthread_attr_setschedpolicy( &attr, SCHED_FIFO ), paInternalError );
+ PA_UNLESS( !pthread_attr_setschedparam( &attr, &wdSpm ), paInternalError );
+ if( (err = pthread_create( &self->watchdogThread, &attr, &WatchdogFunc, self )) )
+ {
+ PA_UNLESS( err == EPERM, paInternalError );
+ /* Permission error, go on without realtime privileges */
+ PA_DEBUG(( "Failed bumping priority\n" ));
+ }
+ else
+ {
+ int policy;
+ self->watchdogRunning = 1;
+ PA_ENSURE_SYSTEM( pthread_getschedparam( self->watchdogThread, &policy, &wdSpm ), 0 );
+ /* Check if priority is right, policy could potentially differ from SCHED_FIFO (but that's alright) */
+ if( wdSpm.sched_priority != prio )
+ {
+ PA_DEBUG(( "Watchdog priority not set correctly (%d)\n", wdSpm.sched_priority ));
+ PA_ENSURE( paInternalError );
+ }
+ }
+ }
+ else
+#endif
+ PA_ENSURE( BoostPriority( self ) );
+
+ {
+ int policy;
+ struct sched_param spm;
+ pthread_getschedparam(self->thread, &policy, &spm);
+ }
+ }
+
+ if( self->parentWaiting )
+ {
+ PaTime till;
+ struct timespec ts;
+ int res = 0;
+ PaTime now;
+
+ PA_ENSURE( PaUnixMutex_Lock( &self->mtx ) );
+
+ /* Wait for stream to be started */
+ now = PaUtil_GetTime();
+ till = now + waitForChild;
+
+ while( self->parentWaiting && !res )
+ {
+ if( waitForChild > 0 )
+ {
+ ts.tv_sec = (time_t) floor( till );
+ ts.tv_nsec = (long) ((till - floor( till )) * 1e9);
+ res = pthread_cond_timedwait( &self->cond, &self->mtx.mtx, &ts );
+ }
+ else
+ {
+ res = pthread_cond_wait( &self->cond, &self->mtx.mtx );
+ }
+ }
+
+ PA_ENSURE( PaUnixMutex_Unlock( &self->mtx ) );
+
+ PA_UNLESS( !res || ETIMEDOUT == res, paInternalError );
+ PA_DEBUG(( "%s: Waited for %g seconds for stream to start\n", __FUNCTION__, PaUtil_GetTime() - now ));
+ if( ETIMEDOUT == res )
+ {
+ PA_ENSURE( paTimedOut );
+ }
+ }
+
+end:
+ return result;
+error:
+ if( started )
+ {
+ PaUnixThread_Terminate( self, 0, NULL );
+ }
+
+ goto end;
+}
+
+PaError PaUnixThread_Terminate( PaUnixThread* self, int wait, PaError* exitResult )
+{
+ PaError result = paNoError;
+ void* pret;
+
+ if( exitResult )
+ {
+ *exitResult = paNoError;
+ }
+#if 0
+ if( watchdogExitResult )
+ *watchdogExitResult = paNoError;
+
+ if( th->watchdogRunning )
+ {
+ pthread_cancel( th->watchdogThread );
+ PA_ENSURE_SYSTEM( pthread_join( th->watchdogThread, &pret ), 0 );
+
+ if( pret && pret != PTHREAD_CANCELED )
+ {
+ if( watchdogExitResult )
+ *watchdogExitResult = *(PaError *) pret;
+ free( pret );
+ }
+ }
+#endif
+
+ /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */
+ /* TODO: Make join time out */
+ self->stopRequested = wait;
+ if( !wait )
+ {
+ PA_DEBUG(( "%s: Canceling thread %d\n", __FUNCTION__, self->thread ));
+ /* XXX: Safe to call this if the thread has exited on its own? */
+#ifdef PTHREAD_CANCELED
+ pthread_cancel( self->thread );
+#endif
+ }
+ PA_DEBUG(( "%s: Joining thread %d\n", __FUNCTION__, self->thread ));
+ PA_ENSURE_SYSTEM( pthread_join( self->thread, &pret ), 0 );
+
+#ifdef PTHREAD_CANCELED
+ if( pret && PTHREAD_CANCELED != pret )
+#else
+ /* !wait means the thread may have been canceled */
+ if( pret && wait )
+#endif
+ {
+ if( exitResult )
+ {
+ *exitResult = *(PaError*)pret;
+ }
+ free( pret );
+ }
+
+error:
+ PA_ASSERT_CALL( PaUnixMutex_Terminate( &self->mtx ), paNoError );
+ PA_ASSERT_CALL( pthread_cond_destroy( &self->cond ), 0 );
+
+ return result;
+}
+
+PaError PaUnixThread_PrepareNotify( PaUnixThread* self )
+{
+ PaError result = paNoError;
+ PA_UNLESS( self->parentWaiting, paInternalError );
+
+ PA_ENSURE( PaUnixMutex_Lock( &self->mtx ) );
+ self->locked = 1;
+
+error:
+ return result;
+}
+
+PaError PaUnixThread_NotifyParent( PaUnixThread* self )
+{
+ PaError result = paNoError;
+ PA_UNLESS( self->parentWaiting, paInternalError );
+
+ if( !self->locked )
+ {
+ PA_ENSURE( PaUnixMutex_Lock( &self->mtx ) );
+ self->locked = 1;
+ }
+ self->parentWaiting = 0;
+ pthread_cond_signal( &self->cond );
+ PA_ENSURE( PaUnixMutex_Unlock( &self->mtx ) );
+ self->locked = 0;
+
+error:
+ return result;
+}
+
+int PaUnixThread_StopRequested( PaUnixThread* self )
+{
+ return self->stopRequested;
+}
+
+PaError PaUnixMutex_Initialize( PaUnixMutex* self )
+{
+ PaError result = paNoError;
+ PA_ASSERT_CALL( pthread_mutex_init( &self->mtx, NULL ), 0 );
+ return result;
+}
+
+PaError PaUnixMutex_Terminate( PaUnixMutex* self )
+{
+ PaError result = paNoError;
+ PA_ASSERT_CALL( pthread_mutex_destroy( &self->mtx ), 0 );
+ return result;
+}
+
+/** Lock mutex.
+ *
+ * We're disabling thread cancellation while the thread is holding a lock, so mutexes are
+ * properly unlocked at termination time.
+ */
+PaError PaUnixMutex_Lock( PaUnixMutex* self )
+{
+ PaError result = paNoError;
+
+#ifdef PTHREAD_CANCEL
+ int oldState;
+ PA_ENSURE_SYSTEM( pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldState ), 0 );
+#endif
+ PA_ENSURE_SYSTEM( pthread_mutex_lock( &self->mtx ), 0 );
+
+error:
+ return result;
+}
+
+/** Unlock mutex.
+ *
+ * Thread cancellation is enabled again after the mutex is properly unlocked.
+ */
+PaError PaUnixMutex_Unlock( PaUnixMutex* self )
+{
+ PaError result = paNoError;
+
+ PA_ENSURE_SYSTEM( pthread_mutex_unlock( &self->mtx ), 0 );
+#ifdef PTHREAD_CANCEL
+ int oldState;
+ PA_ENSURE_SYSTEM( pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &oldState ), 0 );
+#endif
+
+error:
+ return result;
+}
+
+
+#if 0
+static void OnWatchdogExit( void *userData )
+{
+ PaAlsaThreading *th = (PaAlsaThreading *) userData;
+ struct sched_param spm = { 0 };
+ assert( th );
+
+ PA_ASSERT_CALL( pthread_setschedparam( th->callbackThread, SCHED_OTHER, &spm ), 0 ); /* Lower before exiting */
+ PA_DEBUG(( "Watchdog exiting\n" ));
+}
+
+static void *WatchdogFunc( void *userData )
+{
+ PaError result = paNoError, *pres = NULL;
+ int err;
+ PaAlsaThreading *th = (PaAlsaThreading *) userData;
+ unsigned intervalMsec = 500;
+ const PaTime maxSeconds = 3.; /* Max seconds between callbacks */
+ PaTime timeThen = PaUtil_GetTime(), timeNow, timeElapsed, cpuTimeThen, cpuTimeNow, cpuTimeElapsed;
+ double cpuLoad, avgCpuLoad = 0.;
+ int throttled = 0;
+
+ assert( th );
+
+ /* Execute OnWatchdogExit when exiting */
+ pthread_cleanup_push( &OnWatchdogExit, th );
+
+ /* Boost priority of callback thread */
+ PA_ENSURE( result = BoostPriority( th ) );
+ if( !result )
+ {
+ /* Boost failed, might as well exit */
+ pthread_exit( NULL );
+ }
+
+ cpuTimeThen = th->callbackCpuTime;
+ {
+ int policy;
+ struct sched_param spm = { 0 };
+ pthread_getschedparam( pthread_self(), &policy, &spm );
+ PA_DEBUG(( "%s: Watchdog priority is %d\n", __FUNCTION__, spm.sched_priority ));
+ }
+
+ while( 1 )
+ {
+ double lowpassCoeff = 0.9, lowpassCoeff1 = 0.99999 - lowpassCoeff;
+
+ /* Test before and after in case whatever underlying sleep call isn't interrupted by pthread_cancel */
+ pthread_testcancel();
+ Pa_Sleep( intervalMsec );
+ pthread_testcancel();
+
+ if( PaUtil_GetTime() - th->callbackTime > maxSeconds )
+ {
+ PA_DEBUG(( "Watchdog: Terminating callback thread\n" ));
+ /* Tell thread to terminate */
+ err = pthread_kill( th->callbackThread, SIGKILL );
+ pthread_exit( NULL );
+ }
+
+ PA_DEBUG(( "%s: PortAudio reports CPU load: %g\n", __FUNCTION__, PaUtil_GetCpuLoad( th->cpuLoadMeasurer ) ));
+
+ /* Check if we should throttle, or unthrottle :P */
+ cpuTimeNow = th->callbackCpuTime;
+ cpuTimeElapsed = cpuTimeNow - cpuTimeThen;
+ cpuTimeThen = cpuTimeNow;
+
+ timeNow = PaUtil_GetTime();
+ timeElapsed = timeNow - timeThen;
+ timeThen = timeNow;
+ cpuLoad = cpuTimeElapsed / timeElapsed;
+ avgCpuLoad = avgCpuLoad * lowpassCoeff + cpuLoad * lowpassCoeff1;
+ /*
+ if( throttled )
+ PA_DEBUG(( "Watchdog: CPU load: %g, %g\n", avgCpuLoad, cpuTimeElapsed ));
+ */
+ if( PaUtil_GetCpuLoad( th->cpuLoadMeasurer ) > .925 )
+ {
+ static int policy;
+ static struct sched_param spm = { 0 };
+ static const struct sched_param defaultSpm = { 0 };
+ PA_DEBUG(( "%s: Throttling audio thread, priority %d\n", __FUNCTION__, spm.sched_priority ));
+
+ pthread_getschedparam( th->callbackThread, &policy, &spm );
+ if( !pthread_setschedparam( th->callbackThread, SCHED_OTHER, &defaultSpm ) )
+ {
+ throttled = 1;
+ }
+ else
+ PA_DEBUG(( "Watchdog: Couldn't lower priority of audio thread: %s\n", strerror( errno ) ));
+
+ /* Give other processes a go, before raising priority again */
+ PA_DEBUG(( "%s: Watchdog sleeping for %lu msecs before unthrottling\n", __FUNCTION__, th->throttledSleepTime ));
+ Pa_Sleep( th->throttledSleepTime );
+
+ /* Reset callback priority */
+ if( pthread_setschedparam( th->callbackThread, SCHED_FIFO, &spm ) != 0 )
+ {
+ PA_DEBUG(( "%s: Couldn't raise priority of audio thread: %s\n", __FUNCTION__, strerror( errno ) ));
+ }
+
+ if( PaUtil_GetCpuLoad( th->cpuLoadMeasurer ) >= .99 )
+ intervalMsec = 50;
+ else
+ intervalMsec = 100;
+
+ /*
+ lowpassCoeff = .97;
+ lowpassCoeff1 = .99999 - lowpassCoeff;
+ */
+ }
+ else if( throttled && avgCpuLoad < .8 )
+ {
+ intervalMsec = 500;
+ throttled = 0;
+
+ /*
+ lowpassCoeff = .9;
+ lowpassCoeff1 = .99999 - lowpassCoeff;
+ */
+ }
+ }
+
+ pthread_cleanup_pop( 1 ); /* Execute cleanup on exit */
+
+error:
+ /* Shouldn't get here in the normal case */
+
+ /* Pass on error code */
+ pres = malloc( sizeof (PaError) );
+ *pres = result;
+
+ pthread_exit( pres );
+}
+
+static void CallbackUpdate( PaAlsaThreading *th )
+{
+ th->callbackTime = PaUtil_GetTime();
+ th->callbackCpuTime = PaUtil_GetCpuLoad( th->cpuLoadMeasurer );
+}
+
+/*
+static void *CanaryFunc( void *userData )
+{
+ const unsigned intervalMsec = 1000;
+ PaUtilThreading *th = (PaUtilThreading *) userData;
+
+ while( 1 )
+ {
+ th->canaryTime = PaUtil_GetTime();
+
+ pthread_testcancel();
+ Pa_Sleep( intervalMsec );
+ }
+
+ pthread_exit( NULL );
+}
+*/
+#endif
diff --git a/portaudio/src/os/unix/pa_unix_util.h b/portaudio/src/os/unix/pa_unix_util.h
new file mode 100644
index 0000000..2228cb3
--- /dev/null
+++ b/portaudio/src/os/unix/pa_unix_util.h
@@ -0,0 +1,224 @@
+/*
+ * $Id$
+ * Portable Audio I/O Library
+ * UNIX platform-specific support functions
+ *
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 1999-2000 Ross Bencina
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * 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. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/** @file
+ @ingroup unix_src
+*/
+
+#ifndef PA_UNIX_UTIL_H
+#define PA_UNIX_UTIL_H
+
+#include "pa_cpuload.h"
+#include <assert.h>
+#include <pthread.h>
+#include <signal.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#define PA_MIN(x,y) ( (x) < (y) ? (x) : (y) )
+#define PA_MAX(x,y) ( (x) > (y) ? (x) : (y) )
+
+/* Utilize GCC branch prediction for error tests */
+#if defined __GNUC__ && __GNUC__ >= 3
+#define UNLIKELY(expr) __builtin_expect( (expr), 0 )
+#else
+#define UNLIKELY(expr) (expr)
+#endif
+
+#define STRINGIZE_HELPER(expr) #expr
+#define STRINGIZE(expr) STRINGIZE_HELPER(expr)
+
+#define PA_UNLESS(expr, code) \
+ do { \
+ if( UNLIKELY( (expr) == 0 ) ) \
+ { \
+ PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+ result = (code); \
+ goto error; \
+ } \
+ } while (0);
+
+static PaError paUtilErr_; /* Used with PA_ENSURE */
+
+/* Check PaError */
+#define PA_ENSURE(expr) \
+ do { \
+ if( UNLIKELY( (paUtilErr_ = (expr)) < paNoError ) ) \
+ { \
+ PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+ result = paUtilErr_; \
+ goto error; \
+ } \
+ } while (0);
+
+#define PA_ASSERT_CALL(expr, success) \
+ paUtilErr_ = (expr); \
+ assert( success == paUtilErr_ );
+
+#define PA_ENSURE_SYSTEM(expr, success) \
+ do { \
+ if( UNLIKELY( (paUtilErr_ = (expr)) != success ) ) \
+ { \
+ /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \
+ if( pthread_equal(pthread_self(), paUnixMainThread) ) \
+ { \
+ PaUtil_SetLastHostErrorInfo( paALSA, paUtilErr_, strerror( paUtilErr_ ) ); \
+ } \
+ PaUtil_DebugPrint( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" ); \
+ result = paUnanticipatedHostError; \
+ goto error; \
+ } \
+ } while( 0 );
+
+typedef struct {
+ pthread_t callbackThread;
+} PaUtilThreading;
+
+PaError PaUtil_InitializeThreading( PaUtilThreading *threading );
+void PaUtil_TerminateThreading( PaUtilThreading *threading );
+PaError PaUtil_StartThreading( PaUtilThreading *threading, void *(*threadRoutine)(void *), void *data );
+PaError PaUtil_CancelThreading( PaUtilThreading *threading, int wait, PaError *exitResult );
+
+/* State accessed by utility functions */
+
+/*
+void PaUnix_SetRealtimeScheduling( int rt );
+
+void PaUtil_InitializeThreading( PaUtilThreading *th, PaUtilCpuLoadMeasurer *clm );
+
+PaError PaUtil_CreateCallbackThread( PaUtilThreading *th, void *(*CallbackThreadFunc)( void * ), PaStream *s );
+
+PaError PaUtil_KillCallbackThread( PaUtilThreading *th, PaError *exitResult );
+
+void PaUtil_CallbackUpdate( PaUtilThreading *th );
+*/
+
+extern pthread_t paUnixMainThread;
+
+typedef struct
+{
+ pthread_mutex_t mtx;
+} PaUnixMutex;
+
+PaError PaUnixMutex_Initialize( PaUnixMutex* self );
+PaError PaUnixMutex_Terminate( PaUnixMutex* self );
+PaError PaUnixMutex_Lock( PaUnixMutex* self );
+PaError PaUnixMutex_Unlock( PaUnixMutex* self );
+
+typedef struct
+{
+ pthread_t thread;
+ int parentWaiting;
+ int stopRequested;
+ int locked;
+ PaUnixMutex mtx;
+ pthread_cond_t cond;
+ volatile sig_atomic_t stopRequest;
+} PaUnixThread;
+
+/** Initialize global threading state.
+ */
+PaError PaUnixThreading_Initialize( void );
+
+/** Perish, passing on eventual error code.
+ *
+ * A thin wrapper around pthread_exit, will automatically pass on any error code to the joining thread.
+ * If the result indicates an error, i.e. it is not equal to paNoError, this function will automatically
+ * allocate a pointer so the error is passed on with pthread_exit. If the result indicates that all is
+ * well however, only a NULL pointer will be handed to pthread_exit. Thus, the joining thread should
+ * check whether a non-NULL result pointer is obtained from pthread_join and make sure to free it.
+ * @param result: The error code to pass on to the joining thread.
+ */
+#define PaUnixThreading_EXIT(result) \
+ do { \
+ PaError* pres = NULL; \
+ if( paNoError != (result) ) \
+ { \
+ pres = malloc( sizeof (PaError) ); \
+ *pres = (result); \
+ } \
+ pthread_exit( pres ); \
+ } while (0);
+
+/** Spawn a thread.
+ *
+ * Intended for spawning the callback thread from the main thread. This function can even block (for a certain
+ * time or indefinitely) until notified by the callback thread (using PaUnixThread_NotifyParent), which can be
+ * useful in order to make sure that callback has commenced before returning from Pa_StartStream.
+ * @param threadFunc: The function to be executed in the child thread.
+ * @param waitForChild: If not 0, wait for child thread to call PaUnixThread_NotifyParent. Less than 0 means
+ * wait for ever, greater than 0 wait for the specified time.
+ * @param rtSched: Enable realtime scheduling?
+ * @return: If timed out waiting on child, paTimedOut.
+ */
+PaError PaUnixThread_New( PaUnixThread* self, void* (*threadFunc)( void* ), void* threadArg, PaTime waitForChild,
+ int rtSched );
+
+/** Terminate thread.
+ *
+ * @param wait: If true, request that background thread stop and wait until it does, else cancel it.
+ * @param exitResult: If non-null this will upon return contain the exit status of the thread.
+ */
+PaError PaUnixThread_Terminate( PaUnixThread* self, int wait, PaError* exitResult );
+
+/** Prepare to notify waiting parent thread.
+ *
+ * An internal lock must be held before the parent is notified in PaUnixThread_NotifyParent, call this to
+ * acquire it beforehand.
+ * @return: If parent is not waiting, paInternalError.
+ */
+PaError PaUnixThread_PrepareNotify( PaUnixThread* self );
+
+/** Notify waiting parent thread.
+ *
+ * @return: If parent timed out waiting, paTimedOut. If parent was never waiting, paInternalError.
+ */
+PaError PaUnixThread_NotifyParent( PaUnixThread* self );
+
+/** Has the parent thread requested this thread to stop?
+ */
+int PaUnixThread_StopRequested( PaUnixThread* self );
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif