diff options
author | Miller Puckette <millerpuckette@users.sourceforge.net> | 2004-09-06 20:44:42 +0000 |
---|---|---|
committer | Miller Puckette <millerpuckette@users.sourceforge.net> | 2004-09-06 20:44:42 +0000 |
commit | cfc2f7d280ae57ef563dd69bad27c61a148d6ded (patch) | |
tree | 92c4c645399fcad23f4a6ba15dff91c0e6fccb29 /pd/portaudio/pa_linux_alsa | |
parent | ed932acb5860bf8b9296169676499562a55d139e (diff) |
... more changes to try to upload 0.38 test 5 to CVS
svn path=/trunk/; revision=2011
Diffstat (limited to 'pd/portaudio/pa_linux_alsa')
-rw-r--r-- | pd/portaudio/pa_linux_alsa/pa_linux_alsa.c | 2812 | ||||
-rw-r--r-- | pd/portaudio/pa_linux_alsa/pa_linux_alsa.h | 28 |
2 files changed, 2840 insertions, 0 deletions
diff --git a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c new file mode 100644 index 00000000..c3f3f550 --- /dev/null +++ b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c @@ -0,0 +1,2812 @@ +/* + * $Id: pa_linux_alsa.c,v 1.1.2.37 2004/08/13 11:22:09 aknudsen Exp $ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * ALSA implementation by Joshua Haberman + * + * Copyright (c) 2002 Joshua Haberman <joshua@haberman.com> + * + * 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. + * + * 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. + */ + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API +#include <alsa/asoundlib.h> +#undef ALSA_PCM_NEW_HW_PARAMS_API +#undef ALSA_PCM_NEW_SW_PARAMS_API + +#include <sys/poll.h> +#include <string.h> /* strlen() */ +#include <limits.h> +#include <math.h> +#include <pthread.h> +#include <signal.h> +#include <time.h> +#include <sys/mman.h> + +#include "portaudio.h" +#include "pa_util.h" +/*#include "../pa_unix/pa_unix_util.h"*/ +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_linux_alsa.h" + +#undef PA_DEBUG +#define PA_DEBUG(x) globstring = "" +static char *globstring; + + +#define MIN(x,y) ( (x) < (y) ? (x) : (y) ) +#define MAX(x,y) ( (x) > (y) ? (x) : (y) ) + +#define STRINGIZE_HELPER(exp) #exp +#define STRINGIZE(exp) STRINGIZE_HELPER(exp) + +/* Check return value of ALSA function, and map it to PaError */ +#define ENSURE(exp, code) \ + if( (aErr_ = (exp)) < 0 ) \ + { \ + /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \ + if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \ + { \ + PaUtil_SetLastHostErrorInfo( paALSA, aErr_, snd_strerror( aErr_ ) ); \ + } \ + PaUtil_DebugPrint(( "Expression '" #exp "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } + +/* Check PaError */ +#define PA_ENSURE(exp) \ + if( (paErr_ = (exp)) < paNoError ) \ + { \ + PaUtil_DebugPrint(( "Expression '" #exp "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = paErr_; \ + goto error; \ + } + +#define UNLESS(exp, code) \ + if( (exp) == 0 ) \ + { \ + PaUtil_DebugPrint(( "Expression '" #exp "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \ + result = (code); \ + goto error; \ + } + +static int aErr_; /* Used with ENSURE */ +static PaError paErr_; /* Used with PA_ENSURE */ +static int rtPrio_ = -1; +static pthread_t mainThread_; + +typedef enum +{ + streamIn, + streamOut +} StreamIO; + +/* Threading utility struct */ +typedef struct PaAlsaThreading +{ + pthread_t watchdogThread; + pthread_t callbackThread; + int watchdogRunning; + int rtSched; + int useWatchdog; + unsigned long throttledSleepTime; + volatile PaTime callbackTime; + volatile PaTime callbackCpuTime; + PaUtilCpuLoadMeasurer *cpuLoadMeasurer; +} PaAlsaThreading; + +/* Implementation specific stream structure */ +typedef struct PaAlsaStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + PaAlsaThreading threading; + + snd_pcm_t *pcm_capture; + snd_pcm_t *pcm_playback; + + snd_pcm_uframes_t frames_per_period; + snd_pcm_uframes_t playbackBufferSize; + snd_pcm_uframes_t captureBufferSize; + snd_pcm_format_t playbackNativeFormat; + + int capture_channels; + int playback_channels; + + int capture_interleaved; /* bool: is capture interleaved? */ + int playback_interleaved; /* bool: is playback interleaved? */ + + int callback_mode; /* bool: are we running in callback mode? */ + int callback_finished; /* bool: are we in the "callback finished" state? See if stream has been stopped in background */ + + /* the callback thread uses these to poll the sound device(s), waiting + * for data to be ready/available */ + unsigned int capture_nfds; + unsigned int playback_nfds; + struct pollfd *pfds; + int pollTimeout; + + /* these aren't really stream state, the callback uses them */ + snd_pcm_uframes_t capture_offset; + snd_pcm_uframes_t playback_offset; + + int pcmsSynced; /* Have we successfully synced pcms */ + int callbackAbort; /* Drop frames? */ + int isActive; /* Is stream in active state? (Between StartStream and StopStream || !paContinue) */ + snd_pcm_uframes_t startThreshold; + pthread_mutex_t stateMtx; /* Used to synchronize access to stream state */ + pthread_mutex_t startMtx; /* Used to synchronize stream start in callback mode */ + pthread_cond_t startCond; /* Wait untill audio is started in callback thread */ + + /* Used by callback thread for underflow/overflow handling */ + snd_pcm_sframes_t playbackAvail; + snd_pcm_sframes_t captureAvail; + int neverDropInput; + + PaTime underrun; + PaTime overrun; +} +PaAlsaStream; + +/* PaAlsaHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct PaAlsaHostApiRepresentation +{ + PaUtilHostApiRepresentation commonHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + PaHostApiIndex hostApiIndex; +} +PaAlsaHostApiRepresentation; + +typedef struct PaAlsaDeviceInfo +{ + PaDeviceInfo commonDeviceInfo; + char *alsaName; + int isPlug; +} +PaAlsaDeviceInfo; + +/* Threading utilities */ + +static void InitializeThreading( PaAlsaThreading *th, PaUtilCpuLoadMeasurer *clm ) +{ + th->watchdogRunning = 0; + th->rtSched = 0; + th->callbackTime = 0; + th->callbackCpuTime = 0; + th->useWatchdog = 1; + th->throttledSleepTime = 0; + th->cpuLoadMeasurer = clm; + + if (rtPrio_ < 0) { + rtPrio_ = (sched_get_priority_max( SCHED_FIFO ) - sched_get_priority_min( SCHED_FIFO )) / 2 + + sched_get_priority_min( SCHED_FIFO ); + } +} + +static PaError KillCallbackThread( PaAlsaThreading *th, PaError *exitResult, PaError *watchdogExitResult ) +{ + PaError result = paNoError; + void *pret; + + if( exitResult ) + *exitResult = paNoError; + if( watchdogExitResult ) + *watchdogExitResult = paNoError; + + if( th->watchdogRunning ) + { + pthread_cancel( th->watchdogThread ); + UNLESS( !pthread_join( th->watchdogThread, &pret ), paInternalError ); + + if( pret && pret != PTHREAD_CANCELED ) + { + if( watchdogExitResult ) + *watchdogExitResult = *(PaError *) pret; + free( pret ); + } + } + + pthread_cancel( th->callbackThread ); + UNLESS( !pthread_join( th->callbackThread, &pret ), paInternalError ); + + if( pret && pret != PTHREAD_CANCELED ) + { + if( exitResult ) + *exitResult = *(PaError *) pret; + free( pret ); + } + +error: + return result; +} + +static void OnWatchdogExit( void *userData ) +{ + int err; + PaAlsaThreading *th = (PaAlsaThreading *) userData; + struct sched_param spm = { 0 }; + assert( th ); + + err = pthread_setschedparam( th->callbackThread, SCHED_OTHER, &spm ); /* Lower before exiting */ + + PA_DEBUG(( "Watchdog exiting\n" )); +} + +static PaError BoostPriority( PaAlsaThreading *th ) +{ + PaError result = paNoError; + struct sched_param spm = { 0 }; + spm.sched_priority = rtPrio_; + + assert( th ); + + if( pthread_setschedparam( th->callbackThread, SCHED_FIFO, &spm ) != 0 ) + { + UNLESS( errno == EPERM, paInternalError ); + PA_DEBUG(( "Failed bumping priority\n" )); + result = 0; + } + else + result = 1; /* Success */ +error: + return result; +} + +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 ); + + pthread_cleanup_push( &OnWatchdogExit, th ); /* Execute OnWatchdogExit when exiting */ + + /* Boost priority of callback thread */ + PA_ENSURE( result = BoostPriority( th ) ); + if( !result ) + { + pthread_exit( NULL ); /* Boost failed, might as well exit */ + } + + 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: + /* Pass on error code */ + pres = malloc( sizeof (PaError) ); + *pres = result; + + pthread_exit( pres ); +} + +static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*CallbackThreadFunc)( void * ), PaStream *s ) +{ + PaError result = paNoError; + pthread_attr_t attr; + int started = 0; + +#if defined _POSIX_MEMLOCK && (_POSIX_MEMLOCK != -1) + if( th->rtSched ) + { + if( mlockall( MCL_CURRENT | MCL_FUTURE ) < 0 ) + { + UNLESS( (errno == EPERM), paInternalError ); + PA_DEBUG(( "%s: Failed locking memory\n", __FUNCTION__ )); + } + else + PA_DEBUG(( "%s: Successfully locked memory\n", __FUNCTION__ )); + } +#endif + + UNLESS( !pthread_attr_init( &attr ), paInternalError ); + /* Priority relative to other processes */ + UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); + + UNLESS( !pthread_create( &th->callbackThread, &attr, CallbackThreadFunc, s ), paInternalError ); + started = 1; + + if( th->rtSched ) + { + if( th->useWatchdog ) + { + int err; + struct sched_param wdSpm = { 0 }; + /* Launch watchdog, watchdog sets callback thread priority */ + wdSpm.sched_priority = MIN( rtPrio_ + 4, sched_get_priority_max( SCHED_FIFO ) ); + + UNLESS( !pthread_attr_init( &attr ), paInternalError ); + UNLESS( !pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ), paInternalError ); + UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError ); + UNLESS( !pthread_attr_setschedpolicy( &attr, SCHED_FIFO ), paInternalError ); + UNLESS( !pthread_attr_setschedparam( &attr, &wdSpm ), paInternalError ); + if( (err = pthread_create( &th->watchdogThread, &attr, &WatchdogFunc, th )) ) + { + UNLESS( err == EPERM, paInternalError ); + /* Permission error, go on without realtime privileges */ + PA_DEBUG(( "Failed bumping priority\n" )); + } else + th->watchdogRunning = 1; + } + else + PA_ENSURE( BoostPriority( th ) ); + } + +end: + return result; +error: + if( started ) + KillCallbackThread( th, NULL, NULL ); + + goto end; +} + +static void CallbackUpdate( PaAlsaThreading *th ) +{ + th->callbackTime = PaUtil_GetTime(); + th->callbackCpuTime = PaUtil_GetCpuLoad( th->cpuLoadMeasurer ); +} + +/* prototypes for functions declared in this file */ + +PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ); +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *callback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError BuildDeviceList( PaAlsaHostApiRepresentation *hostApi ); +static void CleanUpStream( PaAlsaStream *stream ); +static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ); +static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate ); + +/* Callback prototypes */ +static void *CallbackThreadFunc( void *userData ); + +/* Blocking prototypes */ +static signed long GetStreamReadAvailable( PaStream* s ); +static signed long GetStreamWriteAvailable( PaStream* s ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); + + +PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + PaAlsaHostApiRepresentation *alsaHostApi = NULL; + + UNLESS( alsaHostApi = (PaAlsaHostApiRepresentation*) PaUtil_AllocateMemory( + sizeof(PaAlsaHostApiRepresentation) ), paInsufficientMemory ); + UNLESS( alsaHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + alsaHostApi->hostApiIndex = hostApiIndex; + + *hostApi = (PaUtilHostApiRepresentation*)alsaHostApi; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paALSA; + (*hostApi)->info.name = "ALSA"; + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &alsaHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &alsaHostApi->blockingStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, + GetStreamReadAvailable, + GetStreamWriteAvailable ); + + PA_ENSURE( BuildDeviceList( alsaHostApi ) ); + + mainThread_ = pthread_self(); + + return result; + +error: + if( alsaHostApi ) + { + if( alsaHostApi->allocations ) + { + PaUtil_FreeAllAllocations( alsaHostApi->allocations ); + PaUtil_DestroyAllocationGroup( alsaHostApi->allocations ); + } + + PaUtil_FreeMemory( alsaHostApi ); + } + + return result; +} + + +/*! \brief Determine max channels and default latencies + * + * This function provides functionality to grope an opened (might be opened for capture or playback) pcm device for + * traits like max channels, suitable default latencies and default sample rate. Upon error, max channels is set to zero, + * and a suitable result returned. The device is closed before returning. + */ +static PaError GropeDevice( snd_pcm_t *pcm, int *channels, double *defaultLowLatency, + double *defaultHighLatency, double *defaultSampleRate, int isPlug ) +{ + PaError result = paNoError; + snd_pcm_hw_params_t *hwParams; + snd_pcm_uframes_t lowLatency = 1024, highLatency = 16384; + unsigned int uchans; + + assert( pcm ); + + ENSURE( snd_pcm_nonblock( pcm, 0 ), paUnanticipatedHostError ); + + snd_pcm_hw_params_alloca( &hwParams ); + snd_pcm_hw_params_any( pcm, hwParams ); + + if (*defaultSampleRate != 0.) + { + /* Could be that the device opened in one mode supports samplerates that the other mode wont have, + * so try again .. */ + if( SetApproximateSampleRate( pcm, hwParams, *defaultSampleRate ) < 0 ) + { + *defaultSampleRate = 0.; + PA_DEBUG(( "%s: Original default samplerate failed, trying again ..\n", __FUNCTION__ )); + } + } + + if( *defaultSampleRate == 0. ) /* Default sample rate not set */ + { + unsigned int sampleRate = 44100; /* Will contain approximate rate returned by alsa-lib */ + ENSURE( snd_pcm_hw_params_set_rate_near( pcm, hwParams, &sampleRate, NULL ), paUnanticipatedHostError ); + ENSURE( GetExactSampleRate( hwParams, defaultSampleRate ), paUnanticipatedHostError ); + } + + ENSURE( snd_pcm_hw_params_get_channels_max( hwParams, &uchans ), paUnanticipatedHostError ); + assert( uchans <= INT_MAX ); + assert( uchans > 0 ); /* Weird linking issue could cause wrong version of ALSA symbols to be called, + resulting in zeroed values */ + *channels = isPlug ? 128 : uchans; /* XXX: Limit to sensible number (ALSA plugins accept a crazy amount of channels)? */ + if( isPlug ) + PA_DEBUG(( "%s: Limiting number of plugin channels to %d\n", __FUNCTION__, *channels )); + + /* TWEAKME: + * + * Giving values for default min and max latency is not + * straightforward. Here are our objectives: + * + * * for low latency, we want to give the lowest value + * that will work reliably. This varies based on the + * sound card, kernel, CPU, etc. I think it is better + * to give sub-optimal latency than to give a number + * too low and cause dropouts. My conservative + * estimate at this point is to base it on 4096-sample + * latency at 44.1 kHz, which gives a latency of 23ms. + * * for high latency we want to give a large enough + * value that dropouts are basically impossible. This + * doesn't really require as much tweaking, since + * providing too large a number will just cause us to + * select the nearest setting that will work at stream + * config time. + */ + ENSURE( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &lowLatency ), paUnanticipatedHostError ); + + /* Have to reset hwParams, to set new buffer size */ + ENSURE( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &highLatency ), paUnanticipatedHostError ); + + *defaultLowLatency = (double) lowLatency / *defaultSampleRate; + *defaultHighLatency = (double) highLatency / *defaultSampleRate; + +end: + snd_pcm_close( pcm ); + return result; + +error: + *channels = 0; + goto end; +} + +/* Helper struct */ +typedef struct +{ + char *alsaName; + char *name; + int isPlug; +} DeviceNames; + +/* Build PaDeviceInfo list, ignore devices for which we cannot determine capabilities (possibly busy, sigh) */ +static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi ) +{ + PaUtilHostApiRepresentation *commonApi = &alsaApi->commonHostApiRep; + PaAlsaDeviceInfo *deviceInfoArray; + int cardIdx = -1, devIdx = 0; + snd_ctl_t *ctl; + snd_ctl_card_info_t *card_info; + PaError result = paNoError; + size_t numDeviceNames = 0, maxDeviceNames = 1, i; + DeviceNames *deviceNames = NULL; + snd_config_t *top; + int res; + int blocking = SND_PCM_NONBLOCK; + if( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) && atoi( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) ) ) + blocking = 0; + + /* These two will be set to the first working input and output device, respectively */ + commonApi->info.defaultInputDevice = paNoDevice; + commonApi->info.defaultOutputDevice = paNoDevice; + + /* count the devices by enumerating all the card numbers */ + + /* snd_card_next() modifies the integer passed to it to be: + * the index of the first card if the parameter is -1 + * the index of the next card if the parameter is the index of a card + * -1 if there are no more cards + * + * The function itself returns 0 if it succeeded. */ + cardIdx = -1; + snd_ctl_card_info_alloca( &card_info ); + while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 ) + { + const char *cardName; + char *alsaDeviceName, *deviceName; + + UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + 50 ), paInsufficientMemory ); + snprintf( alsaDeviceName, 50, "hw:%d", cardIdx ); + + /* Acquire name of card */ + if( snd_ctl_open( &ctl, alsaDeviceName, 0 ) < 0 ) + continue; /* Unable to open card :( */ + snd_ctl_card_info( ctl, card_info ); + snd_ctl_close( ctl ); + cardName = snd_ctl_card_info_get_name( card_info ); + + UNLESS( deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(cardName) + 1 ), paInsufficientMemory ); + strcpy( deviceName, cardName ); + + ++numDeviceNames; + if( !deviceNames || numDeviceNames > maxDeviceNames ) + { + maxDeviceNames *= 2; + UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), + paInsufficientMemory ); + } + + deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName; + deviceNames[ numDeviceNames - 1 ].name = deviceName; + deviceNames[ numDeviceNames - 1 ].isPlug = 0; + } + + /* Iterate over plugin devices */ + if( (res = snd_config_search( snd_config, "pcm", &top )) >= 0 ) + { + snd_config_iterator_t i, next; + const char *s; + + snd_config_for_each( i, next, top ) + { + char *alsaDeviceName, *deviceName; + snd_config_t *n = snd_config_iterator_entry( i ), *tp; + if( snd_config_get_type( n ) != SND_CONFIG_TYPE_COMPOUND ) + continue; + + /* Restrict search to nodes of type "plug" for now */ + ENSURE( snd_config_search( n, "type", &tp ), paUnanticipatedHostError ); + ENSURE( snd_config_get_string( tp, &s ), paUnanticipatedHostError ); + if( strcmp( s, "plug" ) ) + continue; + + /* Disregard standard plugins + * XXX: Might want to make the "default" plugin available, if we can make it work + */ + ENSURE( snd_config_get_id( n, &s ), paUnanticipatedHostError ); + if( !strcmp( s, "plughw" ) || !strcmp( s, "plug" ) || !strcmp( s, "default" ) ) + continue; + + UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(s) + 6 ), paInsufficientMemory ); + strcpy( alsaDeviceName, s ); + UNLESS( deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(s) + 1 ), paInsufficientMemory ); + strcpy( deviceName, s ); + + ++numDeviceNames; + if( !deviceNames || numDeviceNames > maxDeviceNames ) + { + maxDeviceNames *= 2; + UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ), + paInsufficientMemory ); + } + + deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName; + deviceNames[ numDeviceNames - 1 ].name = deviceName; + deviceNames[ numDeviceNames - 1 ].isPlug = 1; + } + } + else + PA_DEBUG(( "%s: Iterating over ALSA plugins failed: %s\n", __FUNCTION__, snd_strerror( res ) )); + + /* allocate deviceInfo memory based on the number of devices */ + UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + alsaApi->allocations, sizeof(PaDeviceInfo*) * (numDeviceNames) ), paInsufficientMemory ); + + /* allocate all device info structs in a contiguous block */ + UNLESS( deviceInfoArray = (PaAlsaDeviceInfo*)PaUtil_GroupAllocateMemory( + alsaApi->allocations, sizeof(PaAlsaDeviceInfo) * numDeviceNames ), paInsufficientMemory ); + + /* Loop over list of cards, filling in info, if a device is deemed unavailable (can't get name), + * it's ignored. + */ + /* while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 ) */ + for( i = 0, devIdx = 0; i < numDeviceNames; ++i ) + { + snd_pcm_t *pcm; + PaAlsaDeviceInfo *deviceInfo = &deviceInfoArray[ devIdx ]; + PaDeviceInfo *commonDeviceInfo = &deviceInfo->commonDeviceInfo; + + /* Zero fields */ + memset( commonDeviceInfo, 0, sizeof (PaDeviceInfo) ); + + /* to determine device capabilities, we must open the device and query the + * hardware parameter configuration space */ + + /* Query capture */ + if( snd_pcm_open( &pcm, deviceNames[ i ].alsaName, SND_PCM_STREAM_CAPTURE, blocking ) >= 0 ) + { + if( GropeDevice( pcm, &commonDeviceInfo->maxInputChannels, + &commonDeviceInfo->defaultLowInputLatency, &commonDeviceInfo->defaultHighInputLatency, + &commonDeviceInfo->defaultSampleRate, deviceNames[ i ].isPlug ) != paNoError ) + continue; /* Error */ + } + + /* Query playback */ + if( snd_pcm_open( &pcm, deviceNames[ i ].alsaName, SND_PCM_STREAM_PLAYBACK, blocking ) >= 0 ) + { + if( GropeDevice( pcm, &commonDeviceInfo->maxOutputChannels, + &commonDeviceInfo->defaultLowOutputLatency, &commonDeviceInfo->defaultHighOutputLatency, + &commonDeviceInfo->defaultSampleRate, deviceNames[ i ].isPlug ) != paNoError ) + continue; /* Error */ + } + + commonDeviceInfo->structVersion = 2; + commonDeviceInfo->hostApi = alsaApi->hostApiIndex; + deviceInfo->alsaName = deviceNames[ i ].alsaName; + deviceInfo->isPlug = deviceNames[ i ].isPlug; + commonDeviceInfo->name = deviceNames[ i ].name; + + /* A: Storing pointer to PaAlsaDeviceInfo object as pointer to PaDeviceInfo object. + * Should now be safe to add device info, unless the device supports neither capture nor playback + */ + if( commonDeviceInfo->maxInputChannels || commonDeviceInfo->maxOutputChannels ) + { + if( commonApi->info.defaultInputDevice == paNoDevice ) + commonApi->info.defaultInputDevice = devIdx; + + if( commonApi->info.defaultOutputDevice == paNoDevice ) + commonApi->info.defaultOutputDevice = devIdx; + + commonApi->deviceInfos[ devIdx++ ] = (PaDeviceInfo *) deviceInfo; + } + } + free( deviceNames ); + + commonApi->info.deviceCount = devIdx; /* Number of successfully queried devices */ + +end: + return result; + +error: + goto end; /* No particular action */ +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; + + assert( hostApi ); + + if( alsaHostApi->allocations ) + { + PaUtil_FreeAllAllocations( alsaHostApi->allocations ); + PaUtil_DestroyAllocationGroup( alsaHostApi->allocations ); + } + + PaUtil_FreeMemory( alsaHostApi ); +} + +/* Check against known device capabilities */ +static PaError ValidateParameters( const PaStreamParameters *parameters, const PaAlsaDeviceInfo *deviceInfo, StreamIO io, + const PaAlsaStreamInfo *streamInfo ) +{ + int maxChans; + + assert( parameters ); + + if( streamInfo ) + { + if( streamInfo->size != sizeof (PaAlsaStreamInfo) || streamInfo->version != 1 ) + return paIncompatibleHostApiSpecificStreamInfo; + if( parameters->device != paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + } + if( parameters->device == paUseHostApiSpecificDeviceSpecification ) + { + if( streamInfo ) + return paNoError; /* Skip further checking */ + + return paInvalidDevice; + } + + maxChans = (io == streamIn ? deviceInfo->commonDeviceInfo.maxInputChannels : + deviceInfo->commonDeviceInfo.maxOutputChannels); + if( parameters->channelCount > maxChans ) + { + return paInvalidChannelCount; + } + + return paNoError; +} + + +/* Given an open stream, what sample formats are available? */ + +static PaSampleFormat GetAvailableFormats( snd_pcm_t *pcm ) +{ + PaSampleFormat available = 0; + snd_pcm_hw_params_t *hwParams; + snd_pcm_hw_params_alloca( &hwParams ); + + snd_pcm_hw_params_any( pcm, hwParams ); + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_FLOAT ) >= 0) + available |= paFloat32; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S32 ) >= 0) + available |= paInt32; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S24 ) >= 0) + available |= paInt24; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S16 ) >= 0) + available |= paInt16; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_U8 ) >= 0) + available |= paUInt8; + + if( snd_pcm_hw_params_test_format( pcm, hwParams, SND_PCM_FORMAT_S8 ) >= 0) + available |= paInt8; + + return available; +} + + +static snd_pcm_format_t Pa2AlsaFormat( PaSampleFormat paFormat ) +{ + switch( paFormat ) + { + case paFloat32: + return SND_PCM_FORMAT_FLOAT; + + case paInt16: + return SND_PCM_FORMAT_S16; + + case paInt24: + return SND_PCM_FORMAT_S24; + + case paInt32: + return SND_PCM_FORMAT_S32; + + case paInt8: + return SND_PCM_FORMAT_S8; + + case paUInt8: + return SND_PCM_FORMAT_U8; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +/* \brief Open an ALSA pcm handle + * + * The device to be open can be specified in a custom PaAlsaStreamInfo struct, or it will be a device number. In case of a + * device number, it maybe specified through an env variable (PA_ALSA_PLUGHW) that we should open the corresponding plugin + * device. + */ +static PaError AlsaOpen(snd_pcm_t **pcm, const PaAlsaDeviceInfo *deviceInfo, const PaAlsaStreamInfo + *streamInfo, snd_pcm_stream_t streamType ) +{ + PaError result = paNoError; + int ret; + const char *deviceName = alloca( 50 ); + + if( !streamInfo ) + { + int usePlug = 0; + + /* If device name starts with hw: and PA_ALSA_PLUGHW is 1, we open the plughw device instead */ + if( !strncmp( "hw:", deviceInfo->alsaName, 3 ) && getenv( "PA_ALSA_PLUGHW" ) ) + usePlug = atoi( getenv( "PA_ALSA_PLUGHW" ) ); + if( usePlug ) + snprintf( (char *) deviceName, 50, "plug%s", deviceInfo->alsaName ); + else + deviceName = deviceInfo->alsaName; + } + else + deviceName = streamInfo->deviceString; + + if( (ret = snd_pcm_open( pcm, deviceName, streamType, SND_PCM_NONBLOCK )) < 0 ) + { + *pcm = NULL; /* Not to be closed */ + ENSURE( ret, ret == -EBUSY ? paDeviceUnavailable : paBadIODeviceCombination ); + } + ENSURE( snd_pcm_nonblock( *pcm, 0 ), paUnanticipatedHostError ); + +end: + return result; + +error: + goto end; +} + +static PaError TestParameters( const PaStreamParameters *parameters, const PaAlsaDeviceInfo *deviceInfo, const PaAlsaStreamInfo + *streamInfo, double sampleRate, snd_pcm_stream_t streamType ) +{ + PaError result = paNoError; + snd_pcm_t *pcm = NULL; + PaSampleFormat availableFormats; + PaSampleFormat paFormat; + snd_pcm_hw_params_t *params; + snd_pcm_hw_params_alloca( ¶ms ); + + PA_ENSURE( AlsaOpen( &pcm, deviceInfo, streamInfo, streamType ) ); + + snd_pcm_hw_params_any( pcm, params ); + + ENSURE( SetApproximateSampleRate( pcm, params, sampleRate ), paInvalidSampleRate ); + ENSURE( snd_pcm_hw_params_set_channels( pcm, params, parameters->channelCount ), paInvalidChannelCount ); + + /* See if we can find a best possible match */ + availableFormats = GetAvailableFormats( pcm ); + PA_ENSURE( paFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, parameters->sampleFormat ) ); + +end: + if( pcm ) + snd_pcm_close( pcm ); + return result; + +error: + goto end; +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount = 0, outputChannelCount = 0; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaError result = paFormatIsSupported; + const PaAlsaDeviceInfo *inputDeviceInfo = NULL, *outputDeviceInfo = NULL; + const PaAlsaStreamInfo *inputStreamInfo = NULL, *outputStreamInfo = NULL; + + if( inputParameters ) + { + if( inputParameters->device != paUseHostApiSpecificDeviceSpecification ) + { + assert( inputParameters->device < hostApi->info.deviceCount ); + inputDeviceInfo = (PaAlsaDeviceInfo *)hostApi->deviceInfos[ inputParameters->device ]; + } + else + inputStreamInfo = inputParameters->hostApiSpecificStreamInfo; + + PA_ENSURE( ValidateParameters( inputParameters, inputDeviceInfo, streamIn, inputStreamInfo ) ); + + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + + if( outputParameters ) + { + if( outputParameters->device != paUseHostApiSpecificDeviceSpecification ) + { + assert( outputParameters->device < hostApi->info.deviceCount ); + outputDeviceInfo = (PaAlsaDeviceInfo *)hostApi->deviceInfos[ outputParameters->device ]; + } + else + outputStreamInfo = outputParameters->hostApiSpecificStreamInfo; + + PA_ENSURE( ValidateParameters( outputParameters, outputDeviceInfo, streamOut, outputStreamInfo ) ); + + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + } + + /* + IMPLEMENT ME: + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported if necessary + + - check that the device supports sampleRate + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + + if( inputChannelCount ) + { + PA_ENSURE( TestParameters( inputParameters, inputDeviceInfo, inputStreamInfo, sampleRate, SND_PCM_STREAM_CAPTURE ) ); + } + + if ( outputChannelCount ) + { + PA_ENSURE( TestParameters( outputParameters, outputDeviceInfo, outputStreamInfo, sampleRate, SND_PCM_STREAM_PLAYBACK ) ); + } + + return paFormatIsSupported; + +error: + return result; +} + + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError ConfigureStream( snd_pcm_t *pcm, int channels, int *interleaved, double *sampleRate, + PaSampleFormat paFormat, unsigned long framesPerBuffer, snd_pcm_uframes_t + *bufferSize, PaTime *latency, int primeBuffers, int callbackMode ) +{ + /* + int numPeriods; + + if( getenv("PA_NUMPERIODS") != NULL ) + numPeriods = atoi( getenv("PA_NUMPERIODS") ); + else + numPeriods = ( (*latency * sampleRate) / *framesPerBuffer ) + 1; + + PA_DEBUG(( "latency: %f, rate: %f, framesPerBuffer: %d\n", *latency, sampleRate, *framesPerBuffer )); + if( numPeriods <= 1 ) + numPeriods = 2; + */ + + /* configuration consists of setting all of ALSA's parameters. + * These parameters come in two flavors: hardware parameters + * and software paramters. Hardware parameters will affect + * the way the device is initialized, software parameters + * affect the way ALSA interacts with me, the user-level client. */ + + snd_pcm_hw_params_t *hwParams; + snd_pcm_sw_params_t *swParams; + PaError result = paNoError; + snd_pcm_access_t accessMode, alternateAccessMode; + snd_pcm_format_t alsaFormat; + unsigned int numPeriods; + + snd_pcm_hw_params_alloca( &hwParams ); + snd_pcm_sw_params_alloca( &swParams ); + + /* ... fill up the configuration space with all possibile + * combinations of parameters this device will accept */ + ENSURE( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + + if( *interleaved ) + { + accessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + alternateAccessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + } + else + { + accessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + alternateAccessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + } + + /* If requested access mode fails, try alternate mode */ + if( snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 ) { + ENSURE( snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode ), paUnanticipatedHostError ); + *interleaved = !(*interleaved); /* Flip mode */ + } + + /* set the format based on what the user selected */ + alsaFormat = Pa2AlsaFormat( paFormat ); + assert( alsaFormat != SND_PCM_FORMAT_UNKNOWN ); + ENSURE( snd_pcm_hw_params_set_format( pcm, hwParams, alsaFormat ), paUnanticipatedHostError ); + + /* ... set the sample rate */ + ENSURE( SetApproximateSampleRate( pcm, hwParams, *sampleRate ), paInvalidSampleRate ); + ENSURE( GetExactSampleRate( hwParams, sampleRate ), paUnanticipatedHostError ); + + /* ... set the number of channels */ + ENSURE( snd_pcm_hw_params_set_channels( pcm, hwParams, channels ), paInvalidChannelCount ); + + /* Set buffer size */ + ENSURE( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_set_period_size( pcm, hwParams, framesPerBuffer, 0 ), paUnanticipatedHostError ); + + /* Find an acceptable number of periods */ + numPeriods = (*latency * *sampleRate) / framesPerBuffer + 1; + numPeriods = MAX( numPeriods, 2 ); /* Should be at least 2 periods I think? */ + ENSURE( snd_pcm_hw_params_set_periods_near( pcm, hwParams, &numPeriods, NULL ), paUnanticipatedHostError ); + + /* + PA_DEBUG(( "numperiods: %d\n", numPeriods )); + if( snd_pcm_hw_params_set_periods ( pcm, hwParams, numPeriods, 0 ) < 0 ) + { + int i; + for( i = numPeriods; i >= 2; i-- ) + { + if( snd_pcm_hw_params_set_periods( pcm, hwParams, i, 0 ) >= 0 ) + { + PA_DEBUG(( "settled on %d periods\n", i )); + break; + } + } + } + */ + + /* Set the parameters! */ + ENSURE( snd_pcm_hw_params( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_get_buffer_size( hwParams, bufferSize ), paUnanticipatedHostError ); + + /* Latency in seconds, one period is not counted as latency */ + *latency = (numPeriods - 1) * framesPerBuffer / *sampleRate; + + /* Now software parameters... */ + ENSURE( snd_pcm_sw_params_current( pcm, swParams ), paUnanticipatedHostError ); + + ENSURE( snd_pcm_sw_params_set_start_threshold( pcm, swParams, framesPerBuffer ), paUnanticipatedHostError ); + ENSURE( snd_pcm_sw_params_set_stop_threshold( pcm, swParams, *bufferSize ), paUnanticipatedHostError ); + + /* Silence buffer in the case of underrun */ + if( !primeBuffers ) + { + ENSURE( snd_pcm_sw_params_set_silence_threshold( pcm, swParams, 0 ), paUnanticipatedHostError ); + ENSURE( snd_pcm_sw_params_set_silence_size( pcm, swParams, INT_MAX ), paUnanticipatedHostError ); + } + + ENSURE( snd_pcm_sw_params_set_avail_min( pcm, swParams, framesPerBuffer ), paUnanticipatedHostError ); + ENSURE( snd_pcm_sw_params_set_xfer_align( pcm, swParams, 1 ), paUnanticipatedHostError ); + ENSURE( snd_pcm_sw_params_set_tstamp_mode( pcm, swParams, SND_PCM_TSTAMP_MMAP ), paUnanticipatedHostError ); + + /* Set the parameters! */ + ENSURE( snd_pcm_sw_params( pcm, swParams ), paUnanticipatedHostError ); + +end: + return result; + +error: + goto end; /* No particular action */ +} + +static void InitializeStream( PaAlsaStream *stream, int callback, PaStreamFlags streamFlags ) +{ + assert( stream ); + + stream->pcm_capture = NULL; + stream->pcm_playback = NULL; + stream->callback_finished = 0; + stream->callback_mode = callback; + stream->capture_nfds = 0; + stream->playback_nfds = 0; + stream->pfds = NULL; + stream->pollTimeout = 0; + stream->pcmsSynced = 0; + stream->callbackAbort = 0; + stream->isActive = 0; + stream->startThreshold = 0; + pthread_mutex_init( &stream->stateMtx, NULL ); + pthread_mutex_init( &stream->startMtx, NULL ); + pthread_cond_init( &stream->startCond, NULL ); + stream->neverDropInput = streamFlags & paNeverDropInput; + stream->underrun = stream->overrun = 0.0; + + InitializeThreading( &stream->threading, &stream->cpuLoadMeasurer ); +} + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *callback, + void *userData ) +{ + PaError result = paNoError; + PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; + const PaAlsaDeviceInfo *inputDeviceInfo = 0, *outputDeviceInfo = 0; + PaAlsaStream *stream = NULL; + PaSampleFormat hostInputSampleFormat = 0, hostOutputSampleFormat = 0; + PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0; + int numInputChannels = 0, numOutputChannels = 0; + unsigned long framesPerHostBuffer = framesPerBuffer; + PaAlsaStreamInfo *inputStreamInfo = NULL, *outputStreamInfo = NULL; + PaTime inputLatency, outputLatency; + + if( inputParameters ) + { + if( inputParameters->device != paUseHostApiSpecificDeviceSpecification ) + { + assert( inputParameters->device < hostApi->info.deviceCount ); + inputDeviceInfo = (PaAlsaDeviceInfo*)hostApi->deviceInfos[ inputParameters->device ]; + } + else + inputStreamInfo = inputParameters->hostApiSpecificStreamInfo; + + PA_ENSURE( ValidateParameters( inputParameters, inputDeviceInfo, streamIn, inputStreamInfo ) ); + + numInputChannels = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + } + if( outputParameters ) + { + if( outputParameters->device != paUseHostApiSpecificDeviceSpecification ) + { + assert( outputParameters->device < hostApi->info.deviceCount ); + outputDeviceInfo = (PaAlsaDeviceInfo*)hostApi->deviceInfos[ outputParameters->device ]; + } + else + outputStreamInfo = outputParameters->hostApiSpecificStreamInfo; + + PA_ENSURE( ValidateParameters( outputParameters, outputDeviceInfo, streamOut, outputStreamInfo ) ); + + numOutputChannels = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + } + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + /* allocate and do basic initialization of the stream structure */ + + UNLESS( stream = (PaAlsaStream*)PaUtil_AllocateMemory( sizeof(PaAlsaStream) ), paInsufficientMemory ); + InitializeStream( stream, (int) callback, streamFlags ); /* Initialize structure */ + + if( callback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &alsaHostApi->callbackStreamInterface, + callback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &alsaHostApi->blockingStreamInterface, + callback, userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + /* open the devices now, so we can obtain info about the available formats */ + + if( numInputChannels > 0 ) + { + PA_ENSURE( AlsaOpen( &stream->pcm_capture, inputDeviceInfo, inputStreamInfo, SND_PCM_STREAM_CAPTURE ) ); + + stream->capture_nfds = snd_pcm_poll_descriptors_count( stream->pcm_capture ); + + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( stream->pcm_capture ), + inputSampleFormat ); + } + + if( numOutputChannels > 0 ) + { + PA_ENSURE( AlsaOpen( &stream->pcm_playback, outputDeviceInfo, outputStreamInfo, SND_PCM_STREAM_PLAYBACK ) ); + + stream->playback_nfds = snd_pcm_poll_descriptors_count( stream->pcm_playback ); + + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( stream->pcm_playback ), + outputSampleFormat ); + stream->playbackNativeFormat = Pa2AlsaFormat( hostOutputSampleFormat ); + } + + /* If the number of frames per buffer is unspecified, we have to come up with + * one. This is both a blessing and a curse: a blessing because we can optimize + * the number to best meet the requirements, but a curse because that's really + * hard to do well. For this reason we also support an interface where the user + * specifies these by setting environment variables. */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + if( getenv("PA_ALSA_PERIODSIZE") != NULL ) + framesPerHostBuffer = atoi( getenv("PA_ALSA_PERIODSIZE") ); + else + { + /* We need to determine how many frames per host buffer to use. Our + * goals are to provide the best possible performance, but also to + * most closely honor the requested latency settings. Therefore this + * decision is based on: + * + * - the period sizes that playback and/or capture support. The + * host buffer size has to be one of these. + * - the number of periods that playback and/or capture support. + * + * We want to make period_size*(num_periods-1) to be as close as possible + * to latency*rate for both playback and capture. + * + * This is one of those blocks of code that will just take a lot of + * refinement to be any good. + */ + + if( stream->pcm_capture && stream->pcm_playback ) + { + snd_pcm_uframes_t desiredLatency, e; + snd_pcm_uframes_t minPeriodSize, minPlayback, minCapture, maxPeriodSize, maxPlayback, maxCapture, + optimalPeriodSize, periodSize; + int dir; + + snd_pcm_t *pcm; + snd_pcm_hw_params_t *hwParamsPlayback, *hwParamsCapture; + + snd_pcm_hw_params_alloca( &hwParamsPlayback ); + snd_pcm_hw_params_alloca( &hwParamsCapture ); + + /* Come up with a common desired latency */ + pcm = stream->pcm_playback; + snd_pcm_hw_params_any( pcm, hwParamsPlayback ); + ENSURE( SetApproximateSampleRate( pcm, hwParamsPlayback, sampleRate ), paBadIODeviceCombination ); + ENSURE( snd_pcm_hw_params_set_channels( pcm, hwParamsPlayback, outputParameters->channelCount ), + paBadIODeviceCombination ); + + ENSURE( snd_pcm_hw_params_set_period_size_integer( pcm, hwParamsPlayback ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_set_periods_integer( pcm, hwParamsPlayback ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_get_period_size_min( hwParamsPlayback, &minPlayback, &dir ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_get_period_size_max( hwParamsPlayback, &maxPlayback, &dir ), paUnanticipatedHostError ); + + pcm = stream->pcm_capture; + ENSURE( snd_pcm_hw_params_any( pcm, hwParamsCapture ), paUnanticipatedHostError ); + ENSURE( SetApproximateSampleRate( pcm, hwParamsCapture, sampleRate ), paBadIODeviceCombination ); + ENSURE( snd_pcm_hw_params_set_channels( pcm, hwParamsCapture, inputParameters->channelCount ), + paBadIODeviceCombination ); + + ENSURE( snd_pcm_hw_params_set_period_size_integer( pcm, hwParamsCapture ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_set_periods_integer( pcm, hwParamsCapture ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_get_period_size_min( hwParamsCapture, &minCapture, &dir ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_get_period_size_max( hwParamsCapture, &maxCapture, &dir ), paUnanticipatedHostError ); + + minPeriodSize = MAX( minPlayback, minCapture ); + maxPeriodSize = MIN( maxPlayback, maxCapture ); + + desiredLatency = (snd_pcm_uframes_t) (MIN( outputParameters->suggestedLatency, inputParameters->suggestedLatency ) + * sampleRate); + /* Clamp desiredLatency */ + { + snd_pcm_uframes_t tmp, maxBufferSize = ULONG_MAX; + ENSURE( snd_pcm_hw_params_get_buffer_size_max( hwParamsPlayback, &tmp ), paUnanticipatedHostError ); + maxBufferSize = MIN( maxBufferSize, tmp ); + ENSURE( snd_pcm_hw_params_get_buffer_size_max( hwParamsCapture, &tmp ), paUnanticipatedHostError ); + maxBufferSize = MIN( maxBufferSize, tmp ); + + desiredLatency = MIN( desiredLatency, maxBufferSize ); + } + + /* Find the closest power of 2 */ + e = ilogb( minPeriodSize ); + if( minPeriodSize & (minPeriodSize - 1) ) + e += 1; + + periodSize = (snd_pcm_uframes_t) pow( 2, e ); + while( periodSize <= maxPeriodSize ) + { + if( snd_pcm_hw_params_test_period_size( stream->pcm_playback, hwParamsPlayback, periodSize, 0 ) >= 0 && + snd_pcm_hw_params_test_period_size( stream->pcm_capture, hwParamsCapture, periodSize, 0 ) >= 0 ) + break; /* Ok! */ + + periodSize *= 2; + } + + /* 4 periods considered optimal */ + optimalPeriodSize = MAX( desiredLatency / 4, minPeriodSize ); + optimalPeriodSize = MIN( optimalPeriodSize, maxPeriodSize ); + + /* Find the closest power of 2 */ + e = ilogb( optimalPeriodSize ); + if( optimalPeriodSize & (optimalPeriodSize - 1) ) + e += 1; + + optimalPeriodSize = (snd_pcm_uframes_t) pow( 2, e ); + while( optimalPeriodSize >= periodSize ) + { + pcm = stream->pcm_playback; + if( snd_pcm_hw_params_test_period_size( pcm, hwParamsPlayback, optimalPeriodSize, 0 ) < 0 ) + continue; + + pcm = stream->pcm_capture; + if( snd_pcm_hw_params_test_period_size( pcm, hwParamsCapture, optimalPeriodSize, 0 ) >= 0 ) + break; + + optimalPeriodSize /= 2; + } + + if( optimalPeriodSize > periodSize ) + periodSize = optimalPeriodSize; + + if( periodSize <= maxPeriodSize ) + { + /* Looks good */ + framesPerHostBuffer = periodSize; + } + else /* XXX: Some more descriptive error code might be appropriate */ + PA_ENSURE( paBadIODeviceCombination ); + } + else + { + /* half-duplex is a slightly simpler case */ + unsigned long bufferSize, channels; + snd_pcm_t *pcm; + snd_pcm_hw_params_t *hwParams; + + snd_pcm_hw_params_alloca( &hwParams ); + + if( stream->pcm_capture ) + { + pcm = stream->pcm_capture; + bufferSize = inputParameters->suggestedLatency * sampleRate; + channels = inputParameters->channelCount; + } + else + { + pcm = stream->pcm_playback; + bufferSize = outputParameters->suggestedLatency * sampleRate; + channels = outputParameters->channelCount; + } + + ENSURE( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE( SetApproximateSampleRate( pcm, hwParams, sampleRate ), paBadIODeviceCombination ); + ENSURE( snd_pcm_hw_params_set_channels( pcm, hwParams, channels ), paBadIODeviceCombination ); + + ENSURE( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError ); + + /* Using 5 as a base number of periods, we try to approximate the suggested latency (+1 period), + finding a combination of period/buffer size which best fits these constraints */ + framesPerHostBuffer = bufferSize / 4; + bufferSize += framesPerHostBuffer; /* One period doesn't count as latency */ + ENSURE( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &bufferSize ), paUnanticipatedHostError ); + ENSURE( snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &framesPerHostBuffer, NULL ), paUnanticipatedHostError ); + } + } + } + else + { + framesPerHostBuffer = framesPerBuffer; + } + + /* Will fill in correct values later */ + stream->streamRepresentation.streamInfo.inputLatency = 0.; + stream->streamRepresentation.streamInfo.outputLatency = 0.; + + if( numInputChannels > 0 ) + { + int interleaved = !(inputSampleFormat & paNonInterleaved); + PaSampleFormat plain_format = hostInputSampleFormat & ~paNonInterleaved; + + inputLatency = inputParameters->suggestedLatency; /* Real latency in seconds returned from ConfigureStream */ + PA_ENSURE( ConfigureStream( stream->pcm_capture, numInputChannels, &interleaved, + &sampleRate, plain_format, framesPerHostBuffer, &stream->captureBufferSize, + &inputLatency, 0, stream->callback_mode ) ); + + stream->capture_interleaved = interleaved; + } + + if( numOutputChannels > 0 ) + { + int interleaved = !(outputSampleFormat & paNonInterleaved); + PaSampleFormat plain_format = hostOutputSampleFormat & ~paNonInterleaved; + int primeBuffers = streamFlags & paPrimeOutputBuffersUsingStreamCallback; + + outputLatency = outputParameters->suggestedLatency; /* Real latency in seconds returned from ConfigureStream */ + + PA_ENSURE( ConfigureStream( stream->pcm_playback, numOutputChannels, &interleaved, + &sampleRate, plain_format, framesPerHostBuffer, &stream->playbackBufferSize, + &outputLatency, primeBuffers, stream->callback_mode ) ); + + /* If the user wants to prime the buffer before stream start, the start threshold will equal buffer size */ + if( primeBuffers ) + stream->startThreshold = stream->playbackBufferSize; + stream->playback_interleaved = interleaved; + } + /* Should be exact now */ + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + /* Time before watchdog unthrottles realtime thread == 1/4 of period time in msecs */ + stream->threading.throttledSleepTime = (unsigned long) (framesPerHostBuffer / sampleRate / 4 * 1000); + + PA_ENSURE( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + numInputChannels, inputSampleFormat, hostInputSampleFormat, + numOutputChannels, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, framesPerHostBuffer, + paUtilFixedHostBufferSize, callback, userData ) ); + + /* Ok, buffer processor is initialized, now we can deduce it's latency */ + if( numInputChannels > 0 ) + stream->streamRepresentation.streamInfo.inputLatency = inputLatency + PaUtil_GetBufferProcessorInputLatency( + &stream->bufferProcessor ); + if( numOutputChannels > 0 ) + stream->streamRepresentation.streamInfo.outputLatency = outputLatency + PaUtil_GetBufferProcessorOutputLatency( + &stream->bufferProcessor ); + + /* this will cause the two streams to automatically start/stop/prepare in sync. + * We only need to execute these operations on one of the pair. + * A: We don't want to do this on a blocking stream. + */ + if( stream->callback_mode && stream->pcm_capture && stream->pcm_playback && + snd_pcm_link( stream->pcm_capture, stream->pcm_playback ) >= 0 ) + stream->pcmsSynced = 1; + + UNLESS( stream->pfds = (struct pollfd*)PaUtil_AllocateMemory( (stream->capture_nfds + + stream->playback_nfds) * sizeof(struct pollfd) ), paInsufficientMemory ); + + stream->frames_per_period = framesPerHostBuffer; + stream->capture_channels = numInputChannels; + stream->playback_channels = numOutputChannels; + stream->pollTimeout = (int) ceil( 1000 * stream->frames_per_period/sampleRate ); /* Period in msecs, rounded up */ + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + CleanUpStream( stream ); + + return result; +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + CleanUpStream( stream ); + + return result; +} + +static void SilenceBuffer( PaAlsaStream *stream ) +{ + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t frames = snd_pcm_avail_update( stream->pcm_playback ); + + snd_pcm_mmap_begin( stream->pcm_playback, &areas, &stream->playback_offset, &frames ); + snd_pcm_areas_silence( areas, stream->playback_offset, stream->playback_channels, frames, stream->playbackNativeFormat ); + snd_pcm_mmap_commit( stream->pcm_playback, stream->playback_offset, frames ); +} + +/*! \brief Start/prepare pcm(s) for streaming + * + * Depending on wether the stream is in callback or blocking mode, we will respectively start or simply + * prepare the playback pcm. If the buffer has _not_ been primed, we will in callback mode prepare and + * silence the buffer before starting playback. In blocking mode we simply prepare, as the playback will + * be started automatically as the user writes to output. + * + * The capture pcm, however, will simply be prepared and started. + * + * PaAlsaStream::startMtx makes sure access is synchronized (useful in callback mode) + */ +static PaError AlsaStart( PaAlsaStream *stream, int priming ) +{ + PaError result = paNoError; + + if( stream->pcm_playback ) + { + if( stream->callback_mode ) + { + /* We're not priming buffer, so prepare and silence */ + if( !priming ) + { + ENSURE( snd_pcm_prepare( stream->pcm_playback ), paUnanticipatedHostError ); + SilenceBuffer( stream ); + } + ENSURE( snd_pcm_start( stream->pcm_playback ), paUnanticipatedHostError ); + } + else + ENSURE( snd_pcm_prepare( stream->pcm_playback ), paUnanticipatedHostError ); + } + if( stream->pcm_capture && !stream->pcmsSynced ) + { + ENSURE( snd_pcm_prepare( stream->pcm_capture ), paUnanticipatedHostError ); + /* We want to start capture for a blocking stream as well, since nothing will happen otherwise */ + ENSURE( snd_pcm_start( stream->pcm_capture ), paUnanticipatedHostError ); + } + +end: + return result; +error: + goto end; +} + +/*! \brief Utility function for determining if pcms are in running state + */ +static int IsRunning( PaAlsaStream *stream ) +{ + int result = 0; + + pthread_mutex_lock( &stream->stateMtx ); /* Synchronize access to pcm state */ + if( stream->pcm_capture ) + { + snd_pcm_state_t capture_state = snd_pcm_state( stream->pcm_capture ); + + if( capture_state == SND_PCM_STATE_RUNNING || capture_state == SND_PCM_STATE_XRUN + || capture_state == SND_PCM_STATE_DRAINING ) + { + result = 1; + goto end; + } + } + + if( stream->pcm_playback ) + { + snd_pcm_state_t playback_state = snd_pcm_state( stream->pcm_playback ); + + if( playback_state == SND_PCM_STATE_RUNNING || playback_state == SND_PCM_STATE_XRUN + || playback_state == SND_PCM_STATE_DRAINING ) + { + result = 1; + goto end; + } + } + +end: + pthread_mutex_unlock( &stream->stateMtx ); + + return result; +} + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + int streamStarted = 0; /* So we can know wether we need to take the stream down */ + + /* Ready the processor */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + /* Set now, so we can test for activity further down */ + stream->isActive = 1; + + if( stream->callback_mode ) + { + int res = 0; + PaTime pt = PaUtil_GetTime(); + struct timespec ts; + + PA_ENSURE( CreateCallbackThread( &stream->threading, &CallbackThreadFunc, stream ) ); + streamStarted = 1; + + /* Wait for stream to be started */ + ts.tv_sec = (time_t) floor( pt + 1 ); + ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000); + + /* Since we'll be holding a lock on the startMtx (when not waiting on the condition), IsRunning won't be checking + * stream state at the same time as the callback thread affects it. We also check IsStreamActive, in the unlikely + * case the callback thread exits in the meantime (the stream will be considered inactive after the thread exits) */ + UNLESS( !pthread_mutex_lock( &stream->startMtx ), paInternalError ); + while( !IsRunning( stream ) && res != ETIMEDOUT && IsStreamActive( s ) ) + { + res = pthread_cond_timedwait( &stream->startCond, &stream->startMtx, &ts ); + UNLESS( !res || res == ETIMEDOUT, paInternalError ); + } + UNLESS( !pthread_mutex_unlock( &stream->startMtx ), paInternalError ); + PA_DEBUG(( "%s: Waited for %g seconds for stream to start\n", __FUNCTION__, PaUtil_GetTime() - pt )); + + if( res == ETIMEDOUT ) + { + PA_ENSURE( paTimedOut ); + } + } + else + { + PA_ENSURE( AlsaStart( stream, 0 ) ); + streamStarted = 1; + } + +end: + return result; +error: + if( streamStarted ) + AbortStream( stream ); + stream->isActive = 0; + + goto end; +} + +static PaError AlsaStop( PaAlsaStream *stream, int abort ) +{ + PaError result = paNoError; + + if( abort ) + { + if( stream->pcm_playback ) + ENSURE( snd_pcm_drop( stream->pcm_playback ), paUnanticipatedHostError ); + if( stream->pcm_capture && !stream->pcmsSynced ) + ENSURE( snd_pcm_drop( stream->pcm_capture ), paUnanticipatedHostError ); + + PA_DEBUG(( "Dropped frames\n" )); + } + else + { + if( stream->pcm_playback ) + ENSURE( snd_pcm_drain( stream->pcm_playback ), paUnanticipatedHostError ); + if( stream->pcm_capture && !stream->pcmsSynced ) + ENSURE( snd_pcm_drain( stream->pcm_capture ), paUnanticipatedHostError ); + } + +end: + return result; +error: + goto end; +} + +/*! \brief Stop or abort stream + * + * If a stream is in callback mode we will have to inspect wether the background thread has + * finished, or we will have to take it out. In either case we join the thread before + * returning. In blocking mode, we simply tell ALSA to stop abruptly (abort) or finish + * buffers (drain) + * + * Stream will be considered inactive (!PaAlsaStream::isActive) after a call to this function + */ +static PaError RealStop( PaAlsaStream *stream, int abort ) +{ + PaError result = paNoError; + + /* First deal with the callback thread, cancelling and/or joining + * it if necessary + */ + if( stream->callback_mode ) + { + PaError threadRes, watchdogRes; + stream->callbackAbort = abort; + + PA_ENSURE( KillCallbackThread( &stream->threading, &threadRes, &watchdogRes ) ); + if( threadRes != paNoError ) + PA_DEBUG(( "Callback thread returned: %d\n", threadRes )); + if( watchdogRes != paNoError ) + PA_DEBUG(( "Watchdog thread returned: %d\n", watchdogRes )); + + stream->callback_finished = 0; + } + else + { + PA_ENSURE( AlsaStop( stream, abort ) ); + } + + stream->isActive = 0; + +end: + return result; + +error: + goto end; +} + +static PaError StopStream( PaStream *s ) +{ + return RealStop( (PaAlsaStream *) s, 0 ); +} + +static PaError AbortStream( PaStream *s ) +{ + return RealStop( (PaAlsaStream * ) s, 1 ); +} + +/*! + * The stream is considered stopped before StartStream, or AFTER a call to Abort/StopStream (callback + * returning !paContinue is not considered) + */ +static PaError IsStreamStopped( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream *)s; + PaError res; + + /* callback_finished indicates we need to join callback thread (ie. in Abort/StopStream) */ + res = !IsStreamActive( s ) && !stream->callback_finished; + + return res; +} + +static PaError IsStreamActive( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + return stream->isActive; +} + +static PaTime GetStreamTime( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + snd_timestamp_t timestamp; + snd_pcm_status_t *status; + snd_pcm_status_alloca( &status ); + + /* TODO: what if we have both? does it really matter? */ + + /* TODO: if running in callback mode, this will mean + * libasound routines are being called form multiple threads. + * need to verify that libasound is thread-safe. */ + + if( stream->pcm_capture ) + { + snd_pcm_status( stream->pcm_capture, status ); + } + else if( stream->pcm_playback ) + { + snd_pcm_status( stream->pcm_playback, status ); + } + + snd_pcm_status_get_tstamp( status, ×tamp ); + PA_DEBUG(( "Time in secs: %d\n", timestamp.tv_sec )); + + return timestamp.tv_sec + (PaTime) timestamp.tv_usec/1000000; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + +/*! + * \brief Free resources associated with stream, and eventually stream itself + * + * Frees allocated memory, and closes opened pcms. + */ +static void CleanUpStream( PaAlsaStream *stream ) +{ + assert( stream ); + + if( stream->pcm_capture ) + { + snd_pcm_close( stream->pcm_capture ); + } + if( stream->pcm_playback ) + { + snd_pcm_close( stream->pcm_playback ); + } + + PaUtil_FreeMemory( stream->pfds ); + pthread_mutex_destroy( &stream->stateMtx ); + pthread_mutex_destroy( &stream->startMtx ); + pthread_cond_destroy( &stream->startCond ); + + PaUtil_FreeMemory( stream ); +} + +static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ) +{ + unsigned long approx = (unsigned long) sampleRate; + int dir = 0; + double fraction = sampleRate - approx; + + assert( pcm && hwParams ); + + if( fraction > 0.0 ) + { + if( fraction > 0.5 ) + { + ++approx; + dir = -1; + } + else + dir = 1; + } + + return snd_pcm_hw_params_set_rate( pcm, hwParams, approx, dir ); +} + +/* Return exact sample rate in param sampleRate */ +static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate ) +{ + unsigned int num, den; + int err; + + assert( hwParams ); + + err = snd_pcm_hw_params_get_rate_numden( hwParams, &num, &den ); + *sampleRate = (double) num / den; + + return err; +} + + +/* Utility functions for blocking/callback interfaces */ + +/* Atomic restart of stream (we don't want the intermediate state visible) */ +static PaError AlsaRestart( PaAlsaStream *stream ) +{ + PaError result = paNoError; + + PA_DEBUG(( "Restarting audio\n" )); + + UNLESS( !pthread_mutex_lock( &stream->stateMtx ), paInternalError ); + PA_ENSURE( AlsaStop( stream, 0 ) ); + PA_ENSURE( AlsaStart( stream, 0 ) ); + + PA_DEBUG(( "Restarted audio\n" )); + +end: + if( pthread_mutex_unlock( &stream->stateMtx ) != 0 ) + result = paInternalError; + return result; +error: + goto end; +} + +static PaError HandleXrun( PaAlsaStream *stream ) +{ + PaError result = paNoError; + snd_pcm_status_t *st; + PaTime now = PaUtil_GetTime(); + snd_timestamp_t t; + + snd_pcm_status_alloca( &st ); + + if( stream->pcm_playback ) + { + snd_pcm_status( stream->pcm_playback, st ); + if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN ) + { + snd_pcm_status_get_trigger_tstamp( st, &t ); + stream->underrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + } + } + if( stream->pcm_capture ) + { + snd_pcm_status( stream->pcm_capture, st ); + if( snd_pcm_status_get_state( st ) == SND_PCM_STATE_XRUN ) + { + snd_pcm_status_get_trigger_tstamp( st, &t ); + stream->overrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + } + } + + PA_ENSURE( AlsaRestart( stream ) ); + +end: + return result; +error: + goto end; +} + +/*! \brief Poll on I/O filedescriptors + + Poll till we've determined there's data for read or write. In the full-duplex case, + we don't want to hang around forever waiting for either input or output frames, so + whenever we have a timed out filedescriptor we check if we're nearing under/overrun + for the other pcm (critical limit set at one buffer). If so, we exit the waiting state, + and go on with what we got. + */ +static PaError Wait( PaAlsaStream *stream, snd_pcm_uframes_t *frames ) +{ + PaError result = paNoError; + int pollPlayback = 0, pollCapture = 0; + snd_pcm_sframes_t captureAvail = INT_MAX, playbackAvail = INT_MAX, commonAvail; + int xrun = 0; /* Under/overrun? */ + + assert( stream && frames && stream->pollTimeout > 0 ); + + if( stream->pcm_capture ) + pollCapture = 1; + + if( stream->pcm_playback ) + pollPlayback = 1; + + while( pollPlayback || pollCapture ) + { + unsigned short revents; + int totalFds = 0; + int pfdOfs = 0; + + /* get the fds, packing all applicable fds into a single array, + * so we can check them all with a single poll() call + */ + if( stream->pcm_capture && pollCapture ) + { + snd_pcm_poll_descriptors( stream->pcm_capture, stream->pfds, stream->capture_nfds ); + pfdOfs += stream->capture_nfds; + totalFds += stream->capture_nfds; + } + if( stream->pcm_playback && pollPlayback ) + { + snd_pcm_poll_descriptors( stream->pcm_playback, stream->pfds + pfdOfs, stream->playback_nfds ); + totalFds += stream->playback_nfds; + } + + /* if the main thread has requested that we stop, do so now */ + pthread_testcancel(); + + /* now poll on the combination of playback and capture fds. */ + if( poll( stream->pfds, totalFds, stream->pollTimeout ) < 0 ) + { + if( errno == EINTR ) { + continue; + } + + PA_ENSURE( paInternalError ); + } + + pthread_testcancel(); + + /* check the return status of our pfds */ + if( pollCapture ) + { + ENSURE( snd_pcm_poll_descriptors_revents( stream->pcm_capture, stream->pfds, + stream->capture_nfds, &revents ), paUnanticipatedHostError ); + if( revents ) + { + if( revents & POLLERR ) + xrun = 1; + + pollCapture = 0; + } + else if( stream->pcm_playback ) /* Timed out, go on with playback? */ + { + /* Less than 1 written period left? */ + if( (int)snd_pcm_avail_update( stream->pcm_playback ) >= (int)(stream->playbackBufferSize - stream->frames_per_period) ) + { + PA_DEBUG(( "Capture timed out, pollTimeOut: %d\n", stream->pollTimeout )); + pollCapture = 0; /* Go on without me .. *sob* ... */ + } + } + } + + if( pollPlayback ) + { + unsigned short revents; + ENSURE( snd_pcm_poll_descriptors_revents( stream->pcm_playback, stream->pfds + + pfdOfs, stream->playback_nfds, &revents ), paUnanticipatedHostError ); + if( revents ) + { + if( revents & POLLERR ) + xrun = 1; + + pollPlayback = 0; + } + else if( stream->pcm_capture ) /* Timed out, go on with capture? */ + { + /* Less than 1 empty period left? */ + if( (int)snd_pcm_avail_update( stream->pcm_capture ) >= (int)(stream->captureBufferSize - stream->frames_per_period) ) + { + PA_DEBUG(( "Playback timed out\n" )); + pollPlayback = 0; /* Go on without me, son .. */ + } + } + } + } + + /* we have now established that there are buffers ready to be + * operated on. Now determine how many frames are available. + */ + if( stream->pcm_capture ) + { + if( (captureAvail = snd_pcm_avail_update( stream->pcm_capture )) == -EPIPE ) + xrun = 1; + else + ENSURE( captureAvail, paUnanticipatedHostError ); + + if( !captureAvail ) + PA_DEBUG(( "Wait: captureAvail: 0\n" )); + + captureAvail = captureAvail == 0 ? INT_MAX : captureAvail; /* Disregard if zero */ + } + + if( stream->pcm_playback ) + { + if( (playbackAvail = snd_pcm_avail_update( stream->pcm_playback )) == -EPIPE ) + xrun = 1; + else + ENSURE( playbackAvail, paUnanticipatedHostError ); + + if( !playbackAvail ) + PA_DEBUG(( "Wait: playbackAvail: 0\n" )); + + playbackAvail = playbackAvail == 0 ? INT_MAX : playbackAvail; /* Disregard if zero */ + } + + + commonAvail = MIN( captureAvail, playbackAvail ); + commonAvail -= commonAvail % stream->frames_per_period; + + if( xrun ) + { + HandleXrun( stream ); + commonAvail = 0; /* Wait will be called again, to obtain the number of available frames */ + } + + assert( commonAvail >= 0 ); + *frames = commonAvail; + +end: + return result; +error: + goto end; +} + +/* Extract buffer from channel area */ +static unsigned char *ExtractAddress( const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset ) +{ + return (unsigned char *) area->addr + (area->first + offset * area->step) / 8; +} + +/*! \brief Get buffers from ALSA for read/write, and determine the amount of frames available. + * + * Request (up to) requested number of frames from ALSA, for opened pcms. The number of frames returned + * will normally be the lowest availble (possibly aligned) of available capture and playback frames. + * Underflow/underflow complicates matters however; if we are out of capture frames we will go on with + * output, input overflow will either result in discarded frames or we will deliver them (paNeverDropInput). +*/ +static PaError SetUpBuffers( PaAlsaStream *stream, snd_pcm_uframes_t requested, int alignFrames, + snd_pcm_uframes_t *frames ) +{ + PaError result = paNoError; + int i; + snd_pcm_uframes_t captureFrames = requested, playbackFrames = requested, commonFrames; + const snd_pcm_channel_area_t *areas, *area; + unsigned char *buffer; + + assert( stream && frames ); + + if( stream->pcm_capture ) + { + ENSURE( snd_pcm_mmap_begin( stream->pcm_capture, &areas, &stream->capture_offset, &captureFrames ), + paUnanticipatedHostError ); + + if( stream->capture_interleaved ) + { + buffer = ExtractAddress( areas, stream->capture_offset ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, + 0, /* starting at channel 0 */ + buffer, + 0 /* default numInputChannels */ + ); + } + else + /* noninterleaved */ + for( i = 0; i < stream->capture_channels; ++i ) + { + area = &areas[i]; + buffer = ExtractAddress( area, stream->capture_offset ); + PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor, + i, + buffer ); + } + } + + if( stream->pcm_playback ) + { + ENSURE( snd_pcm_mmap_begin( stream->pcm_playback, &areas, &stream->playback_offset, &playbackFrames ), + paUnanticipatedHostError ); + + if( stream->playback_interleaved ) + { + buffer = ExtractAddress( areas, stream->playback_offset ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, + 0, /* starting at channel 0 */ + buffer, + 0 /* default numInputChannels */ + ); + } + else + /* noninterleaved */ + for( i = 0; i < stream->playback_channels; ++i ) + { + area = &areas[i]; + buffer = ExtractAddress( area, stream->playback_offset ); + PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor, + i, + buffer ); + } + } + + if( alignFrames ) + { + playbackFrames -= (playbackFrames % stream->frames_per_period); + captureFrames -= (captureFrames % stream->frames_per_period); + } + commonFrames = MIN( captureFrames, playbackFrames ); + + if( stream->pcm_playback && stream->pcm_capture ) + { + /* Full-duplex, but we are starved for data in either end + * If we're out of input, go on. Input buffer will be zeroed. + * In the case of output underflow, drop input frames unless stream->neverDropInput. + * If we're starved for output, while keeping input, we'll discard output samples. + */ + if( !commonFrames ) + { + if( !captureFrames ) /* Input underflow */ + commonFrames = playbackFrames; /* We still want output */ + else if( stream->neverDropInput ) /* Output underflow, but do not drop input */ + commonFrames = captureFrames; + } + else /* Safe to commit commonFrames for both */ + playbackFrames = captureFrames = commonFrames; + } + + /* Inform PortAudio of the number of frames we got. + We might be experiencing underflow in either end; if its an input underflow, we go on + with output. If its output underflow however, depending on the paNeverDropInput flag, + we may want to simply discard the excess input or call the callback with + paOutputOverflow flagged. + */ + if( stream->pcm_capture ) + { + if( captureFrames || !commonFrames ) /* We have input, or neither */ + PaUtil_SetInputFrameCount( &stream->bufferProcessor, commonFrames ); + else /* We have input underflow */ + PaUtil_SetNoInput( &stream->bufferProcessor ); + } + if( stream->pcm_playback ) + { + if( playbackFrames || !commonFrames ) /* We have output, or neither */ + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, commonFrames ); + else /* We have output underflow, but keeping input data (paNeverDropInput) */ + { + PaUtil_SetNoOutput( &stream->bufferProcessor ); + } + } + + /* PA_DEBUG(( "SetUpBuffers: captureAvail: %d, playbackAvail: %d, commonFrames: %d\n\n", captureFrames, playbackFrames, commonFrames )); */ + /* Either of these could be zero, otherwise equal to commonFrames */ + stream->playbackAvail = playbackFrames; + stream->captureAvail = captureFrames; + + *frames = commonFrames; + +end: + return result; +error: + goto end; +} + +/* Callback interface */ + +static void OnExit( void *data ) +{ + PaAlsaStream *stream = (PaAlsaStream *) data; + + assert( data ); + + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + + stream->callback_finished = 1; /* Let the outside world know stream was stopped in callback */ + AlsaStop( stream, stream->callbackAbort ); + stream->callbackAbort = 0; /* Clear state */ + + PA_DEBUG(( "OnExit: Stoppage\n" )); + + /* Eventually notify user all buffers have played */ + if( stream->streamRepresentation.streamFinishedCallback ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + stream->isActive = 0; +} + +/* \brief Callback thread's function + * + * Roughly, the workflow consists of waiting untill ALSA reports available frames, and then consuming these frames in an inner loop + * till we must wait for more. If the inner loop detects an xrun condition however, the data consumption will stop and we go + * back to the waiting state. + */ +static void *CallbackThreadFunc( void *userData ) +{ + PaError result = paNoError, *pres = NULL; + PaAlsaStream *stream = (PaAlsaStream*) userData; + snd_pcm_uframes_t framesAvail, framesGot, framesProcessed; + snd_pcm_sframes_t startThreshold = stream->startThreshold; + snd_pcm_status_t *capture_status, *playback_status; + + assert( userData ); + + /* Allocation should happen here, once per iteration is no good */ + snd_pcm_status_alloca( &capture_status ); + snd_pcm_status_alloca( &playback_status ); + + pthread_cleanup_push( &OnExit, stream ); /* Execute OnExit when exiting */ + + if( startThreshold <= 0 ) + { + UNLESS( !pthread_mutex_lock( &stream->startMtx ), paInternalError ); + PA_ENSURE( AlsaStart( stream, 0 ) ); /* Buffer will be zeroed */ + UNLESS( !pthread_cond_signal( &stream->startCond ), paInternalError ); + UNLESS( !pthread_mutex_unlock( &stream->startMtx ), paInternalError ); + } + else /* Priming output? Prepare first */ + { + if( stream->pcm_playback ) + ENSURE( snd_pcm_prepare( stream->pcm_playback ), paUnanticipatedHostError ); + if( stream->pcm_capture && !stream->pcmsSynced ) + ENSURE( snd_pcm_prepare( stream->pcm_capture ), paUnanticipatedHostError ); + } + + while( 1 ) + { + PaError callbackResult; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; + PaStreamCallbackFlags cbFlags = 0; + + pthread_testcancel(); + + { + /* calculate time info */ + snd_timestamp_t capture_timestamp, playback_timestamp; + PaTime capture_time = 0., playback_time = 0.; + + if( stream->pcm_capture ) + { + snd_pcm_sframes_t capture_delay; + + snd_pcm_status( stream->pcm_capture, capture_status ); + snd_pcm_status_get_tstamp( capture_status, &capture_timestamp ); + + capture_time = capture_timestamp.tv_sec + + ((PaTime)capture_timestamp.tv_usec/1000000); + timeInfo.currentTime = capture_time; + + capture_delay = snd_pcm_status_get_delay( capture_status ); + timeInfo.inputBufferAdcTime = timeInfo.currentTime - + (PaTime)capture_delay / stream->streamRepresentation.streamInfo.sampleRate; + } + if( stream->pcm_playback ) + { + snd_pcm_sframes_t playback_delay; + + snd_pcm_status( stream->pcm_playback, playback_status ); + snd_pcm_status_get_tstamp( playback_status, &playback_timestamp ); + + playback_time = playback_timestamp.tv_sec + + ((PaTime)playback_timestamp.tv_usec/1000000); + + if( stream->pcm_capture ) /* Full duplex */ + { + /* Hmm, we have both a playback and a capture timestamp. + * Hopefully they are the same... */ + if( fabs( capture_time - playback_time ) > 0.01 ) + PA_DEBUG(("Capture time and playback time differ by %f\n", fabs(capture_time-playback_time))); + } + else + timeInfo.currentTime = playback_time; + + playback_delay = snd_pcm_status_get_delay( playback_status ); + timeInfo.outputBufferDacTime = timeInfo.currentTime + + (PaTime)playback_delay / stream->streamRepresentation.streamInfo.sampleRate; + } + } + + /* Set callback flags *after* one of these has been detected */ + if( stream->underrun != 0.0 ) + { + cbFlags |= paOutputUnderflow; + stream->underrun = 0.0; + } + if( stream->overrun != 0.0 ) + { + cbFlags |= paInputOverflow; + stream->overrun = 0.0; + } + + PA_ENSURE( Wait( stream, &framesAvail ) ); + while( framesAvail > 0 ) + { + pthread_testcancel(); + + /* Priming output */ + if( startThreshold > 0 ) + { + PA_DEBUG(( "CallbackThreadFunc: Priming\n" )); + cbFlags |= paPrimingOutput; + framesAvail = MIN( (int)framesAvail, (int)startThreshold ); + } + + /* now we know the soundcard is ready to produce/receive at least + * one period. we just need to get the buffers for the client + * to read/write. */ + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, cbFlags ); + + PA_ENSURE( SetUpBuffers( stream, framesAvail, 1, &framesGot ) ); + /* Check for under/overflow */ + if( stream->pcm_playback && stream->pcm_capture ) + { + if( !stream->captureAvail ) + { + cbFlags |= paInputUnderflow; + PA_DEBUG(( "Input underflow\n" )); + } + if( !stream->playbackAvail ) + { + if( !framesGot ) /* The normal case, dropping input */ + { + cbFlags |= paInputOverflow; + PA_DEBUG(( "Input overflow\n" )); + } + else /* Keeping input (paNeverDropInput) */ + { + cbFlags |= paOutputOverflow; + PA_DEBUG(( "Output overflow\n" )); + } + } + } + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + callbackResult = paContinue; + + CallbackUpdate( &stream->threading ); /* Report to watchdog */ + /* this calls the callback */ + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &callbackResult ); + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + /* Inform ALSA how many frames we read/wrote + Now, this number may differ between capture and playback, due to under/overflow. + If we're dropping input frames, we effectively sink them here. + */ + if( stream->pcm_capture ) + { + int res = snd_pcm_mmap_commit( stream->pcm_capture, stream->capture_offset, stream->captureAvail ); + + /* Non-fatal error? Terminate loop (go back to polling for frames)*/ + if( res == -EPIPE || res == -ESTRPIPE ) + framesAvail = 0; + else + ENSURE( res, paUnanticipatedHostError ); + } + if( stream->pcm_playback ) + { + int res = snd_pcm_mmap_commit( stream->pcm_playback, stream->playback_offset, stream->playbackAvail ); + + /* Non-fatal error? Terminate loop (go back to polling for frames) */ + if( res == -EPIPE || res == -ESTRPIPE ) + framesAvail = 0; + else + ENSURE( res, paUnanticipatedHostError ); + } + + /* If threshold for starting stream specified (priming buffer), decrement and compare */ + if( startThreshold > 0 ) + { + if( (startThreshold -= framesGot) <= 0 ) + { + UNLESS( !pthread_mutex_lock( &stream->startMtx ), paInternalError ); + PA_ENSURE( AlsaStart( stream, 1 ) ); /* Buffer will be zeroed */ + UNLESS( !pthread_cond_signal( &stream->startCond ), paInternalError ); + UNLESS( !pthread_mutex_unlock( &stream->startMtx ), paInternalError ); + } + } + + if( callbackResult != paContinue ) + break; + + framesAvail -= framesGot; + } + + + /* + If you need to byte swap outputBuffer, you can do it here using + routines in pa_byteswappers.h + */ + + if( callbackResult != paContinue ) + { + stream->callbackAbort = (callbackResult == paAbort); + goto end; + + } + } + + /* This code is unreachable, but important to include regardless because it + * is possibly a macro with a closing brace to match the opening brace in + * pthread_cleanup_push() above. The documentation states that they must + * always occur in pairs. */ + pthread_cleanup_pop( 1 ); + +end: + pthread_exit( pres ); + +error: + /* Pass on error code */ + pres = malloc( sizeof (PaError) ); + *pres = result; + + goto end; +} + +/* Blocking interface */ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaError result = paNoError; + signed long err; + PaAlsaStream *stream = (PaAlsaStream*)s; + snd_pcm_uframes_t framesGot, framesAvail; + void *userBuffer; + int i; + snd_pcm_t *save; + + assert( stream ); + + /* Disregard playback */ + save = stream->pcm_playback; + stream->pcm_playback = NULL; + + if( !stream->pcm_capture ) + PA_ENSURE( paCanNotReadFromAnOutputOnlyStream ); + + + if( stream->overrun ) + { + result = paInputOverflowed; + stream->overrun = 0.0; + } + + if( stream->bufferProcessor.userInputIsInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + int numBytes = sizeof (void *) * stream->capture_channels; + UNLESS( userBuffer = alloca( numBytes ), paInsufficientMemory ); + for( i = 0; i < stream->capture_channels; ++i ) + ((const void **) userBuffer)[i] = ((const void **) buffer)[i]; + } + + /* Start stream if in prepared state */ + if( snd_pcm_state( stream->pcm_capture ) == SND_PCM_STATE_PREPARED ) + { + ENSURE( snd_pcm_start( stream->pcm_capture ), paUnanticipatedHostError ); + } + + while( frames > 0 ) + { + if( (err = GetStreamReadAvailable( stream )) == paInputOverflowed ) + err = 0; /* Wait will detect the (unlikely) xrun, and restart capture */ + PA_ENSURE( err ); + framesAvail = (snd_pcm_uframes_t) err; + + if( framesAvail == 0 ) + PA_ENSURE( Wait( stream, &framesAvail ) ); + framesAvail = MIN( framesAvail, frames ); + + PA_ENSURE( SetUpBuffers( stream, framesAvail, 0, &framesGot ) ); + framesGot = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesGot ); + ENSURE( snd_pcm_mmap_commit( stream->pcm_capture, stream->capture_offset, framesGot ), + paUnanticipatedHostError ); + + frames -= framesGot; + } + +end: + stream->pcm_playback = save; + return result; +error: + goto end; +} + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaError result = paNoError; + signed long err; + PaAlsaStream *stream = (PaAlsaStream*)s; + snd_pcm_uframes_t framesGot, framesAvail; + const void *userBuffer; + int i; + snd_pcm_t *save; + + /* Disregard capture */ + save = stream->pcm_capture; + stream->pcm_capture = NULL; + + assert( stream ); + if( !stream->pcm_playback ) + PA_ENSURE( paCanNotWriteToAnInputOnlyStream ); + + if( stream->underrun ) + { + result = paOutputUnderflowed; + stream->underrun = 0.0; + } + + if( stream->bufferProcessor.userOutputIsInterleaved ) + userBuffer = buffer; + else /* Copy channels into local array */ + { + int numBytes = sizeof (void *) * stream->playback_channels; + UNLESS( userBuffer = alloca( numBytes ), paInsufficientMemory ); + for( i = 0; i < stream->playback_channels; ++i ) + ((const void **) userBuffer)[i] = ((const void **) buffer)[i]; + } + + while( frames > 0 ) + { + snd_pcm_uframes_t hwAvail; + + PA_ENSURE( err = GetStreamWriteAvailable( stream ) ); + framesAvail = err; + if( framesAvail == 0 ) + PA_ENSURE( Wait( stream, &framesAvail ) ); + framesAvail = MIN( framesAvail, frames ); + + PA_ENSURE( SetUpBuffers( stream, framesAvail, 0, &framesGot ) ); + framesGot = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, framesGot ); + ENSURE( snd_pcm_mmap_commit( stream->pcm_playback, stream->playback_offset, framesGot ), + paUnanticipatedHostError ); + + frames -= framesGot; + + /* Frames residing in buffer */ + PA_ENSURE( err = GetStreamWriteAvailable( stream ) ); + framesAvail = err; + hwAvail = stream->playbackBufferSize - framesAvail; + + /* Start stream after one period of samples worth */ + if( snd_pcm_state( stream->pcm_playback ) == SND_PCM_STATE_PREPARED && + hwAvail >= stream->frames_per_period ) + { + ENSURE( snd_pcm_start( stream->pcm_playback ), paUnanticipatedHostError ); + } + } + +end: + stream->pcm_capture = save; + return result; +error: + goto end; +} + + +/* Return frames available for reading. In the event of an overflow, the capture pcm will be restarted */ +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + snd_pcm_sframes_t avail = snd_pcm_avail_update( stream->pcm_capture ); + + if( avail < 0 ) + { + if( avail == -EPIPE ) + { + PA_ENSURE( HandleXrun( stream ) ); + avail = snd_pcm_avail_update( stream->pcm_capture ); + } + + if( avail == -EPIPE ) + PA_ENSURE( paInputOverflowed ); + ENSURE( avail, paUnanticipatedHostError ); + } + + return avail; + +error: + return result; +} + + +/* Return frames available for writing. In the event of an underflow, the playback pcm will be prepared */ +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + snd_pcm_sframes_t avail = snd_pcm_avail_update( stream->pcm_playback ); + + if( avail < 0 ) + { + if( avail == -EPIPE ) + { + PA_ENSURE( HandleXrun( stream ) ); + avail = snd_pcm_avail_update( stream->pcm_playback ); + } + + /* avail should not contain -EPIPE now, since HandleXrun will only prepare the pcm */ + ENSURE( avail, paUnanticipatedHostError ); + } + + return avail; + +error: + return result; +} + +/* Extensions */ + +/* Initialize host api specific structure */ +void PaAlsa_InitializeStreamInfo( PaAlsaStreamInfo *info ) +{ + info->size = sizeof (PaAlsaStreamInfo); + info->hostApiType = paALSA; + info->version = 1; + info->deviceString = NULL; +} + +void PaAlsa_EnableRealtimeScheduling( PaStream *s, int enable ) +{ + PaAlsaStream *stream = (PaAlsaStream *) s; + stream->threading.rtSched = enable; +} + +void PaAlsa_EnableWatchdog( PaStream *s, int enable ) +{ + PaAlsaStream *stream = (PaAlsaStream *) s; + stream->threading.useWatchdog = enable; +} diff --git a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h new file mode 100644 index 00000000..46dd0790 --- /dev/null +++ b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h @@ -0,0 +1,28 @@ +#ifndef PA_LINUX_ALSA_H +#define PA_LINUX_ALSA_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct PaAlsaStreamInfo +{ + unsigned long size; + PaHostApiTypeId hostApiType; + unsigned long version; + + const char *deviceString; +} +PaAlsaStreamInfo; + +void PaAlsa_InitializeStreamInfo( PaAlsaStreamInfo *info ); + +void PaAlsa_EnableRealtimeScheduling( PaStream *s, int enable ); + +void PaAlsa_EnableWatchdog( PaStream *s, int enable ); + +#ifdef __cplusplus +} +#endif + +#endif |