123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- #ifdef UNIX
- /*
- * $Id: pa_unix_util.c 1510 2010-06-10 08:05:29Z dmitrykos $
- * 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 asigning 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
- #endif
|