aboutsummaryrefslogtreecommitdiff
path: root/pd/portaudio/pa_linux_alsa
diff options
context:
space:
mode:
authorMiller Puckette <millerpuckette@users.sourceforge.net>2005-05-30 03:04:19 +0000
committerMiller Puckette <millerpuckette@users.sourceforge.net>2005-05-30 03:04:19 +0000
commit05607e31243e5e85a3801d4513192bb1f2150b14 (patch)
tree0f810a621cb9967e1e53b349410b0d07be0cea13 /pd/portaudio/pa_linux_alsa
parent47729b52cb85e8a52bf2e6bbf8ee9a810ed331e1 (diff)
Remembered to update all the edited files. Should now be in sync... will
have to test it though. svn path=/trunk/; revision=3092
Diffstat (limited to 'pd/portaudio/pa_linux_alsa')
-rw-r--r--pd/portaudio/pa_linux_alsa/pa_linux_alsa.c2922
-rw-r--r--pd/portaudio/pa_linux_alsa/pa_linux_alsa.h36
2 files changed, 1727 insertions, 1231 deletions
diff --git a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c
index c3f3f550..0d0aa1ce 100644
--- a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c
+++ b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c
@@ -1,10 +1,11 @@
/*
- * $Id: pa_linux_alsa.c,v 1.1.2.37 2004/08/13 11:22:09 aknudsen Exp $
+ * $Id: pa_linux_alsa.c,v 1.1.2.71 2005/04/15 18:20:18 aknudsen Exp $
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
- * ALSA implementation by Joshua Haberman
+ * ALSA implementation by Joshua Haberman and Arve Knudsen
*
* Copyright (c) 2002 Joshua Haberman <joshua@haberman.com>
+ * Copyright (c) 2005 Arve Knudsen <aknuds-1@broadpark.no>
*
* Based on the Open Source API proposed by Ross Bencina
* Copyright (c) 1999-2002 Ross Bencina, Phil Burk
@@ -47,10 +48,11 @@
#include <signal.h>
#include <time.h>
#include <sys/mman.h>
+#include <signal.h> /* For sig_atomic_t */
#include "portaudio.h"
#include "pa_util.h"
-/*#include "../pa_unix/pa_unix_util.h"*/
+#include "../pa_unix/pa_unix_util.h"
#include "pa_allocation.h"
#include "pa_hostapi.h"
#include "pa_stream.h"
@@ -59,58 +61,34 @@
#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_ ) \
+#define ENSURE_(expr, code) \
+ do { \
+ if( UNLIKELY( (aErr_ = (expr)) < 0 ) ) \
{ \
- PaUtil_SetLastHostErrorInfo( paALSA, aErr_, snd_strerror( aErr_ ) ); \
+ /* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \
+ if( (code) == paUnanticipatedHostError && pthread_self() != callbackThread_ ) \
+ { \
+ PaUtil_SetLastHostErrorInfo( paALSA, aErr_, snd_strerror( aErr_ ) ); \
+ } \
+ PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+ result = (code); \
+ goto error; \
} \
- 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; \
- }
+ } while( 0 );
-#define UNLESS(exp, code) \
- if( (exp) == 0 ) \
- { \
- PaUtil_DebugPrint(( "Expression '" #exp "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
- result = (code); \
- goto error; \
- }
+#define ASSERT_CALL_(expr, success) \
+ aErr_ = (expr); \
+ assert( aErr_ == success );
-static int aErr_; /* Used with ENSURE */
-static PaError paErr_; /* Used with PA_ENSURE */
-static int rtPrio_ = -1;
-static pthread_t mainThread_;
+static int aErr_; /* Used with ENSURE_ */
+static pthread_t callbackThread_;
typedef enum
{
- streamIn,
- streamOut
-} StreamIO;
+ StreamDirection_In,
+ StreamDirection_Out
+} StreamDirection;
/* Threading utility struct */
typedef struct PaAlsaThreading
@@ -119,6 +97,7 @@ typedef struct PaAlsaThreading
pthread_t callbackThread;
int watchdogRunning;
int rtSched;
+ int rtPrio;
int useWatchdog;
unsigned long throttledSleepTime;
volatile PaTime callbackTime;
@@ -126,6 +105,25 @@ typedef struct PaAlsaThreading
PaUtilCpuLoadMeasurer *cpuLoadMeasurer;
} PaAlsaThreading;
+typedef struct
+{
+ PaSampleFormat hostSampleFormat;
+ unsigned long framesPerBuffer;
+ int numUserChannels, numHostChannels;
+ int userInterleaved, hostInterleaved;
+
+ snd_pcm_t *pcm;
+ snd_pcm_uframes_t bufferSize;
+ snd_pcm_format_t nativeFormat;
+ unsigned int nfds;
+ int ready; /* Marked ready from poll */
+ void **userBuffers;
+ snd_pcm_uframes_t offset;
+ StreamDirection streamDir;
+
+ snd_pcm_channel_area_t *channelAreas; /* Needed for channel adaption */
+} PaAlsaStreamComponent;
+
/* Implementation specific stream structure */
typedef struct PaAlsaStream
{
@@ -134,49 +132,32 @@ typedef struct PaAlsaStream
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? */
+ unsigned long framesPerUserBuffer;
- 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 */
+ int primeBuffers;
+ int callbackMode; /* bool: are we running in callback mode? */
+ int pcmsSynced; /* Have we successfully synced pcms */
/* 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;
+ /* Used in communication between threads */
+ volatile sig_atomic_t callback_finished; /* bool: are we in the "callback finished" state? */
+ volatile sig_atomic_t callbackAbort; /* Drop frames? */
+ volatile sig_atomic_t callbackStop; /* Signal a stop */
+ volatile sig_atomic_t isActive; /* Is stream in active state? (Between StartStream and StopStream || !paContinue) */
+ 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 */
- 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;
+
+ PaAlsaStreamComponent capture, playback;
}
PaAlsaStream;
@@ -199,6 +180,8 @@ typedef struct PaAlsaDeviceInfo
PaDeviceInfo commonDeviceInfo;
char *alsaName;
int isPlug;
+ int minInputChannels;
+ int minOutputChannels;
}
PaAlsaDeviceInfo;
@@ -214,13 +197,11 @@ static void InitializeThreading( PaAlsaThreading *th, PaUtilCpuLoadMeasurer *clm
th->throttledSleepTime = 0;
th->cpuLoadMeasurer = clm;
- if (rtPrio_ < 0) {
- rtPrio_ = (sched_get_priority_max( SCHED_FIFO ) - sched_get_priority_min( SCHED_FIFO )) / 2
+ th->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 )
+static PaError KillCallbackThread( PaAlsaThreading *th, int wait, PaError *exitResult, PaError *watchdogExitResult )
{
PaError result = paNoError;
void *pret;
@@ -233,7 +214,7 @@ static PaError KillCallbackThread( PaAlsaThreading *th, PaError *exitResult, PaE
if( th->watchdogRunning )
{
pthread_cancel( th->watchdogThread );
- UNLESS( !pthread_join( th->watchdogThread, &pret ), paInternalError );
+ ASSERT_CALL_( pthread_join( th->watchdogThread, &pret ), 0 );
if( pret && pret != PTHREAD_CANCELED )
{
@@ -243,8 +224,11 @@ static PaError KillCallbackThread( PaAlsaThreading *th, PaError *exitResult, PaE
}
}
- pthread_cancel( th->callbackThread );
- UNLESS( !pthread_join( th->callbackThread, &pret ), paInternalError );
+ /* Only kill the thread if it isn't in the process of stopping (flushing adaptation buffers) */
+ /* TODO: Make join time out */
+ if( !wait )
+ pthread_cancel( th->callbackThread ); /* XXX: Safe to call this if the thread has exited on its own? */
+ ASSERT_CALL_( pthread_join( th->callbackThread, &pret ), 0 );
if( pret && pret != PTHREAD_CANCELED )
{
@@ -253,19 +237,16 @@ static PaError KillCallbackThread( PaAlsaThreading *th, PaError *exitResult, PaE
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 */
-
+ ASSERT_CALL_( pthread_setschedparam( th->callbackThread, SCHED_OTHER, &spm ), 0 ); /* Lower before exiting */
PA_DEBUG(( "Watchdog exiting\n" ));
}
@@ -273,13 +254,13 @@ static PaError BoostPriority( PaAlsaThreading *th )
{
PaError result = paNoError;
struct sched_param spm = { 0 };
- spm.sched_priority = rtPrio_;
+ spm.sched_priority = th->rtPrio;
assert( th );
if( pthread_setschedparam( th->callbackThread, SCHED_FIFO, &spm ) != 0 )
{
- UNLESS( errno == EPERM, paInternalError );
+ PA_UNLESS( errno == EPERM, paInternalError ); /* Lack permission to raise priority */
PA_DEBUG(( "Failed bumping priority\n" ));
result = 0;
}
@@ -312,7 +293,6 @@ static void *WatchdogFunc( void *userData )
}
cpuTimeThen = th->callbackCpuTime;
-
{
int policy;
struct sched_param spm = { 0 };
@@ -403,6 +383,8 @@ static void *WatchdogFunc( void *userData )
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;
@@ -410,7 +392,7 @@ error:
pthread_exit( pres );
}
-static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*CallbackThreadFunc)( void * ), PaStream *s )
+static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*callbackThreadFunc)( void * ), PaStream *s )
{
PaError result = paNoError;
pthread_attr_t attr;
@@ -421,7 +403,9 @@ static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*CallbackThread
{
if( mlockall( MCL_CURRENT | MCL_FUTURE ) < 0 )
{
- UNLESS( (errno == EPERM), paInternalError );
+ 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
@@ -429,11 +413,11 @@ static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*CallbackThread
}
#endif
- UNLESS( !pthread_attr_init( &attr ), paInternalError );
+ PA_UNLESS( !pthread_attr_init( &attr ), paInternalError );
/* Priority relative to other processes */
- UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError );
+ PA_UNLESS( !pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ), paInternalError );
- UNLESS( !pthread_create( &th->callbackThread, &attr, CallbackThreadFunc, s ), paInternalError );
+ PA_UNLESS( !pthread_create( &th->callbackThread, &attr, callbackThreadFunc, s ), paInternalError );
started = 1;
if( th->rtSched )
@@ -443,20 +427,32 @@ static PaError CreateCallbackThread( PaAlsaThreading *th, void *(*CallbackThread
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 );
+ int prio = PA_MIN( th->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( &th->watchdogThread, &attr, &WatchdogFunc, th )) )
{
- UNLESS( err == EPERM, paInternalError );
+ PA_UNLESS( err == EPERM, paInternalError );
/* Permission error, go on without realtime privileges */
PA_DEBUG(( "Failed bumping priority\n" ));
- } else
+ }
+ else
+ {
+ int policy;
th->watchdogRunning = 1;
+ ASSERT_CALL_( pthread_getschedparam( th->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
PA_ENSURE( BoostPriority( th ) );
@@ -466,7 +462,7 @@ end:
return result;
error:
if( started )
- KillCallbackThread( th, NULL, NULL );
+ KillCallbackThread( th, 0, NULL, NULL );
goto end;
}
@@ -479,7 +475,6 @@ static void CallbackUpdate( PaAlsaThreading *th )
/* 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,
@@ -503,7 +498,6 @@ 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 );
@@ -517,14 +511,19 @@ static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames
static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames );
+static const PaAlsaDeviceInfo *GetDeviceInfo( const PaUtilHostApiRepresentation *hostApi, int device )
+{
+ return (const PaAlsaDeviceInfo *)hostApi->deviceInfos[device];
+}
+
PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
{
PaError result = paNoError;
PaAlsaHostApiRepresentation *alsaHostApi = NULL;
- UNLESS( alsaHostApi = (PaAlsaHostApiRepresentation*) PaUtil_AllocateMemory(
+ PA_UNLESS( alsaHostApi = (PaAlsaHostApiRepresentation*) PaUtil_AllocateMemory(
sizeof(PaAlsaHostApiRepresentation) ), paInsufficientMemory );
- UNLESS( alsaHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
+ PA_UNLESS( alsaHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
alsaHostApi->hostApiIndex = hostApiIndex;
*hostApi = (PaUtilHostApiRepresentation*)alsaHostApi;
@@ -536,6 +535,8 @@ PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
+ PA_ENSURE( BuildDeviceList( alsaHostApi ) );
+
PaUtil_InitializeStreamInterface( &alsaHostApi->callbackStreamInterface,
CloseStream, StartStream,
StopStream, AbortStream,
@@ -554,10 +555,6 @@ PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex
GetStreamReadAvailable,
GetStreamWriteAvailable );
- PA_ENSURE( BuildDeviceList( alsaHostApi ) );
-
- mainThread_ = pthread_self();
-
return result;
error:
@@ -575,53 +572,74 @@ error:
return result;
}
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+ PaAlsaHostApiRepresentation *alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi;
-/*! \brief Determine max channels and default latencies
+ assert( hostApi );
+
+ if( alsaHostApi->allocations )
+ {
+ PaUtil_FreeAllAllocations( alsaHostApi->allocations );
+ PaUtil_DestroyAllocationGroup( alsaHostApi->allocations );
+ }
+
+ PaUtil_FreeMemory( alsaHostApi );
+ snd_config_update_free_global();
+}
+
+/*! 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,
+static PaError GropeDevice( snd_pcm_t *pcm, int *minChannels, int *maxChannels, 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;
+ snd_pcm_uframes_t lowLatency = 512, highLatency = 2048;
+ unsigned int minChans, maxChans;
+ double defaultSr = *defaultSampleRate;
assert( pcm );
- ENSURE( snd_pcm_nonblock( pcm, 0 ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_nonblock( pcm, 0 ), paUnanticipatedHostError );
snd_pcm_hw_params_alloca( &hwParams );
snd_pcm_hw_params_any( pcm, hwParams );
- if (*defaultSampleRate != 0.)
+ if( defaultSr >= 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 )
+ if( SetApproximateSampleRate( pcm, hwParams, defaultSr ) < 0 )
{
- *defaultSampleRate = 0.;
+ defaultSr = -1.;
PA_DEBUG(( "%s: Original default samplerate failed, trying again ..\n", __FUNCTION__ ));
}
}
- if( *defaultSampleRate == 0. ) /* Default sample rate not set */
+ if( defaultSr < 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_set_rate_near( pcm, hwParams, &sampleRate, NULL ), paUnanticipatedHostError );
+ ENSURE_( GetExactSampleRate( hwParams, &defaultSr ), 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,
+ ENSURE_( snd_pcm_hw_params_get_channels_min( hwParams, &minChans ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_hw_params_get_channels_max( hwParams, &maxChans ), paUnanticipatedHostError );
+ assert( maxChans <= INT_MAX );
+ assert( maxChans > 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 ));
+
+ /* XXX: Limit to sensible number (ALSA plugins accept a crazy amount of channels)? */
+ if( isPlug && maxChans > 128 )
+ {
+ maxChans = 128;
+ PA_DEBUG(( "%s: Limiting number of plugin channels to %u\n", __FUNCTION__, maxChans ));
+ }
/* TWEAKME:
*
@@ -642,12 +660,15 @@ static PaError GropeDevice( snd_pcm_t *pcm, int *channels, double *defaultLowLat
* select the nearest setting that will work at stream
* config time.
*/
- ENSURE( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &lowLatency ), paUnanticipatedHostError );
+ 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 );
+ ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &highLatency ), paUnanticipatedHostError );
+ *minChannels = (int)minChans;
+ *maxChannels = (int)maxChans;
+ *defaultSampleRate = defaultSr;
*defaultLowLatency = (double) lowLatency / *defaultSampleRate;
*defaultHighLatency = (double) highLatency / *defaultSampleRate;
@@ -656,32 +677,88 @@ end:
return result;
error:
- *channels = 0;
goto end;
}
+/* Initialize device info with invalid values (maxInputChannels and maxOutputChannels are set to zero since these indicate
+ * wether input/output is available) */
+static void InitializeDeviceInfo( PaDeviceInfo *deviceInfo )
+{
+ deviceInfo->structVersion = -1;
+ deviceInfo->name = NULL;
+ deviceInfo->hostApi = -1;
+ deviceInfo->maxInputChannels = 0;
+ deviceInfo->maxOutputChannels = 0;
+ deviceInfo->defaultLowInputLatency = -1.;
+ deviceInfo->defaultLowOutputLatency = -1.;
+ deviceInfo->defaultHighInputLatency = -1.;
+ deviceInfo->defaultHighOutputLatency = -1.;
+ deviceInfo->defaultSampleRate = -1.;
+}
+
/* Helper struct */
typedef struct
{
char *alsaName;
char *name;
int isPlug;
+ int hasPlayback;
+ int hasCapture;
} DeviceNames;
+static PaError PaAlsa_StrDup( PaAlsaHostApiRepresentation *alsaApi,
+ char **dst,
+ const char *src)
+{
+ PaError result = paNoError;
+ int len = strlen( src ) + 1;
+
+ /* PA_DEBUG(("PaStrDup %s %d\n", src, len)); */
+
+ PA_UNLESS( *dst = (char *)PaUtil_GroupAllocateMemory( alsaApi->allocations, len ),
+ paInsufficientMemory );
+ strncpy( *dst, src, len );
+
+error:
+ return result;
+}
+
+/* Disregard standard plugins
+ * XXX: Might want to make the "default" plugin available, if we can make it work
+ */
+static int IgnorePlugin( const char *pluginId )
+{
+#define numIgnored 10
+ static const char *ignoredPlugins[numIgnored] = {"hw", "plughw", "plug", "default", "dsnoop", "dmix", "tee",
+ "file", "null", "shm"};
+ int i;
+
+ for( i = 0; i < numIgnored; ++i )
+ {
+ if( !strcmp( pluginId, ignoredPlugins[i] ) )
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
/* 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;
+ snd_ctl_card_info_t *cardInfo;
PaError result = paNoError;
size_t numDeviceNames = 0, maxDeviceNames = 1, i;
DeviceNames *deviceNames = NULL;
- snd_config_t *top;
+ snd_config_t *topNode = NULL;
+ snd_pcm_info_t *pcmInfo;
int res;
int blocking = SND_PCM_NONBLOCK;
+ char alsaCardName[50];
if( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) && atoi( getenv( "PA_ALSA_INITIALIZE_BLOCK" ) ) )
blocking = 0;
@@ -698,95 +775,130 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
*
* The function itself returns 0 if it succeeded. */
cardIdx = -1;
- snd_ctl_card_info_alloca( &card_info );
+ snd_ctl_card_info_alloca( &cardInfo );
+ snd_pcm_info_alloca( &pcmInfo );
while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 )
{
- const char *cardName;
- char *alsaDeviceName, *deviceName;
+ char *cardName;
+ int devIdx = -1;
+ snd_ctl_t *ctl;
+ char buf[50];
- UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations,
- 50 ), paInsufficientMemory );
- snprintf( alsaDeviceName, 50, "hw:%d", cardIdx );
+ snprintf( alsaCardName, sizeof (alsaCardName), "hw:%d", cardIdx );
/* Acquire name of card */
- if( snd_ctl_open( &ctl, alsaDeviceName, 0 ) < 0 )
+ if( snd_ctl_open( &ctl, alsaCardName, 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 );
+ snd_ctl_card_info( ctl, cardInfo );
- UNLESS( deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations,
- strlen(cardName) + 1 ), paInsufficientMemory );
- strcpy( deviceName, cardName );
+ PA_ENSURE( PaAlsa_StrDup( alsaApi, &cardName, snd_ctl_card_info_get_name( cardInfo )) );
- ++numDeviceNames;
- if( !deviceNames || numDeviceNames > maxDeviceNames )
+ while( snd_ctl_pcm_next_device( ctl, &devIdx ) == 0 && devIdx >= 0 )
{
- maxDeviceNames *= 2;
- UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ),
+ char *alsaDeviceName, *deviceName;
+ size_t len;
+ int hasPlayback = 0, hasCapture = 0;
+ snprintf( buf, sizeof (buf), "%s:%d,%d", "hw", cardIdx, devIdx );
+
+ /* Obtain info about this particular device */
+ snd_pcm_info_set_device( pcmInfo, devIdx );
+ snd_pcm_info_set_subdevice( pcmInfo, 0 );
+ snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_CAPTURE );
+ if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 )
+ hasCapture = 1;
+
+ snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_PLAYBACK );
+ if( snd_ctl_pcm_info( ctl, pcmInfo ) >= 0 )
+ hasPlayback = 1;
+
+ if( !hasPlayback && !hasCapture )
+ {
+ continue; /* Error */
+ }
+
+ /* The length of the string written by snprintf plus terminating 0 */
+ len = snprintf( NULL, 0, "%s: %s (%s)", cardName, snd_pcm_info_get_name( pcmInfo ), buf ) + 1;
+ PA_UNLESS( deviceName = (char *)PaUtil_GroupAllocateMemory( alsaApi->allocations, len ),
paInsufficientMemory );
- }
+ snprintf( deviceName, len, "%s: %s (%s)", cardName,
+ snd_pcm_info_get_name( pcmInfo ), buf );
+
+ ++numDeviceNames;
+ if( !deviceNames || numDeviceNames > maxDeviceNames )
+ {
+ maxDeviceNames *= 2;
+ PA_UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ),
+ paInsufficientMemory );
+ }
+
+ PA_ENSURE( PaAlsa_StrDup( alsaApi, &alsaDeviceName, buf ) );
- deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName;
- deviceNames[ numDeviceNames - 1 ].name = deviceName;
- deviceNames[ numDeviceNames - 1 ].isPlug = 0;
+ deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName;
+ deviceNames[ numDeviceNames - 1 ].name = deviceName;
+ deviceNames[ numDeviceNames - 1 ].isPlug = 0;
+ deviceNames[ numDeviceNames - 1 ].hasPlayback = hasPlayback;
+ deviceNames[ numDeviceNames - 1 ].hasCapture = hasCapture;
+ }
+ snd_ctl_close( ctl );
}
/* Iterate over plugin devices */
- if( (res = snd_config_search( snd_config, "pcm", &top )) >= 0 )
+ snd_config_update();
+ if( (res = snd_config_search( snd_config, "pcm", &topNode )) >= 0 )
{
snd_config_iterator_t i, next;
- const char *s;
- snd_config_for_each( i, next, top )
+ snd_config_for_each( i, next, topNode )
{
+ const char *tpStr = NULL, *idStr = NULL;
char *alsaDeviceName, *deviceName;
- snd_config_t *n = snd_config_iterator_entry( i ), *tp;
+ snd_config_t *n = snd_config_iterator_entry( i ), *tp = NULL;
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;
+ ENSURE_( snd_config_search( n, "type", &tp ), paUnanticipatedHostError );
+ ENSURE_( snd_config_get_string( tp, &tpStr ), paUnanticipatedHostError );
- /* 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" ) )
+ ENSURE_( snd_config_get_id( n, &idStr ), paUnanticipatedHostError );
+ if( IgnorePlugin( idStr ) )
+ {
+ PA_DEBUG(( "%s: Ignoring ALSA plugin device %s of type %s\n", __FUNCTION__, idStr, tpStr ));
continue;
+ }
+
+ PA_DEBUG(( "%s: Found plugin %s of type %s\n", __FUNCTION__, idStr, tpStr ));
- 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 );
+ PA_UNLESS( alsaDeviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations,
+ strlen(idStr) + 6 ), paInsufficientMemory );
+ strcpy( alsaDeviceName, idStr );
+ PA_UNLESS( deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations,
+ strlen(idStr) + 1 ), paInsufficientMemory );
+ strcpy( deviceName, idStr );
++numDeviceNames;
if( !deviceNames || numDeviceNames > maxDeviceNames )
{
maxDeviceNames *= 2;
- UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ),
+ PA_UNLESS( deviceNames = (DeviceNames *) realloc( deviceNames, maxDeviceNames * sizeof (DeviceNames) ),
paInsufficientMemory );
}
- deviceNames[ numDeviceNames - 1 ].alsaName = alsaDeviceName;
- deviceNames[ numDeviceNames - 1 ].name = deviceName;
- deviceNames[ numDeviceNames - 1 ].isPlug = 1;
+ deviceNames[numDeviceNames - 1].alsaName = alsaDeviceName;
+ deviceNames[numDeviceNames - 1].name = deviceName;
+ deviceNames[numDeviceNames - 1].isPlug = 1;
+ deviceNames[numDeviceNames - 1].hasPlayback = 1;
+ deviceNames[numDeviceNames - 1].hasCapture = 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(
+ PA_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(
+ PA_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),
@@ -796,51 +908,52 @@ static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi )
for( i = 0, devIdx = 0; i < numDeviceNames; ++i )
{
snd_pcm_t *pcm;
- PaAlsaDeviceInfo *deviceInfo = &deviceInfoArray[ devIdx ];
+ PaAlsaDeviceInfo *deviceInfo = &deviceInfoArray[devIdx];
PaDeviceInfo *commonDeviceInfo = &deviceInfo->commonDeviceInfo;
/* Zero fields */
- memset( commonDeviceInfo, 0, sizeof (PaDeviceInfo) );
+ InitializeDeviceInfo( commonDeviceInfo );
/* 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( deviceNames[i].hasCapture &&
+ snd_pcm_open( &pcm, deviceNames[i].alsaName, SND_PCM_STREAM_CAPTURE, blocking ) >= 0 )
{
- if( GropeDevice( pcm, &commonDeviceInfo->maxInputChannels,
+ if( GropeDevice( pcm, &deviceInfo->minInputChannels, &commonDeviceInfo->maxInputChannels,
&commonDeviceInfo->defaultLowInputLatency, &commonDeviceInfo->defaultHighInputLatency,
- &commonDeviceInfo->defaultSampleRate, deviceNames[ i ].isPlug ) != paNoError )
+ &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( deviceNames[i].hasPlayback &&
+ snd_pcm_open( &pcm, deviceNames[i].alsaName, SND_PCM_STREAM_PLAYBACK, blocking ) >= 0 )
{
- if( GropeDevice( pcm, &commonDeviceInfo->maxOutputChannels,
+ if( GropeDevice( pcm, &deviceInfo->minOutputChannels, &commonDeviceInfo->maxOutputChannels,
&commonDeviceInfo->defaultLowOutputLatency, &commonDeviceInfo->defaultHighOutputLatency,
- &commonDeviceInfo->defaultSampleRate, deviceNames[ i ].isPlug ) != paNoError )
+ &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;
+ commonDeviceInfo->name = deviceNames[i].name;
+ deviceInfo->alsaName = deviceNames[i].alsaName;
+ deviceInfo->isPlug = deviceNames[i].isPlug;
/* 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( commonDeviceInfo->maxInputChannels > 0 || commonDeviceInfo->maxOutputChannels > 0 )
{
- if( commonApi->info.defaultInputDevice == paNoDevice )
+ if( commonApi->info.defaultInputDevice == paNoDevice && commonDeviceInfo->maxInputChannels > 0 )
commonApi->info.defaultInputDevice = devIdx;
-
- if( commonApi->info.defaultOutputDevice == paNoDevice )
+ if( commonApi->info.defaultOutputDevice == paNoDevice && commonDeviceInfo->maxOutputChannels > 0 )
commonApi->info.defaultOutputDevice = devIdx;
- commonApi->deviceInfos[ devIdx++ ] = (PaDeviceInfo *) deviceInfo;
+ commonApi->deviceInfos[devIdx++] = (PaDeviceInfo *) deviceInfo;
}
}
free( deviceNames );
@@ -854,56 +967,41 @@ 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 )
+static PaError ValidateParameters( const PaStreamParameters *parameters, PaUtilHostApiRepresentation *hostApi, StreamDirection mode )
{
+ PaError result = paNoError;
int maxChans;
-
+ const PaAlsaDeviceInfo *deviceInfo = NULL;
assert( parameters );
- if( streamInfo )
+ if( parameters->device != paUseHostApiSpecificDeviceSpecification )
{
- if( streamInfo->size != sizeof (PaAlsaStreamInfo) || streamInfo->version != 1 )
- return paIncompatibleHostApiSpecificStreamInfo;
- if( parameters->device != paUseHostApiSpecificDeviceSpecification )
- return paInvalidDevice;
+ assert( parameters->device < hostApi->info.deviceCount );
+ PA_UNLESS( parameters->hostApiSpecificStreamInfo == NULL, paBadIODeviceCombination );
+ deviceInfo = GetDeviceInfo( hostApi, parameters->device );
}
- if( parameters->device == paUseHostApiSpecificDeviceSpecification )
+ else
{
- if( streamInfo )
- return paNoError; /* Skip further checking */
+ const PaAlsaStreamInfo *streamInfo = parameters->hostApiSpecificStreamInfo;
+
+ PA_UNLESS( parameters->device == paUseHostApiSpecificDeviceSpecification, paInvalidDevice );
+ PA_UNLESS( streamInfo->size == sizeof (PaAlsaStreamInfo) && streamInfo->version == 1,
+ paIncompatibleHostApiSpecificStreamInfo );
- return paInvalidDevice;
+ return paNoError; /* Skip further checking */
}
- maxChans = (io == streamIn ? deviceInfo->commonDeviceInfo.maxInputChannels :
+ assert( deviceInfo );
+ assert( parameters->hostApiSpecificStreamInfo == NULL );
+ maxChans = (StreamDirection_In == mode ? deviceInfo->commonDeviceInfo.maxInputChannels :
deviceInfo->commonDeviceInfo.maxOutputChannels);
- if( parameters->channelCount > maxChans )
- {
- return paInvalidChannelCount;
- }
+ PA_UNLESS( parameters->channelCount <= maxChans, paInvalidChannelCount );
- return paNoError;
+error:
+ return result;
}
-
/* Given an open stream, what sample formats are available? */
static PaSampleFormat GetAvailableFormats( snd_pcm_t *pcm )
@@ -935,7 +1033,6 @@ static PaSampleFormat GetAvailableFormats( snd_pcm_t *pcm )
return available;
}
-
static snd_pcm_format_t Pa2AlsaFormat( PaSampleFormat paFormat )
{
switch( paFormat )
@@ -963,22 +1060,25 @@ static snd_pcm_format_t Pa2AlsaFormat( PaSampleFormat paFormat )
}
}
-/* \brief Open an ALSA pcm handle
+/** 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 )
+static PaError AlsaOpen( const PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *params, StreamDirection
+ streamDir, snd_pcm_t **pcm )
{
PaError result = paNoError;
int ret;
const char *deviceName = alloca( 50 );
+ const PaAlsaDeviceInfo *deviceInfo = NULL;
+ PaAlsaStreamInfo *streamInfo = (PaAlsaStreamInfo *)params->hostApiSpecificStreamInfo;
if( !streamInfo )
{
int usePlug = 0;
+ deviceInfo = GetDeviceInfo( hostApi, params->device );
/* 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" ) )
@@ -991,12 +1091,13 @@ static PaError AlsaOpen(snd_pcm_t **pcm, const PaAlsaDeviceInfo *deviceInfo, con
else
deviceName = streamInfo->deviceString;
- if( (ret = snd_pcm_open( pcm, deviceName, streamType, SND_PCM_NONBLOCK )) < 0 )
+ if( (ret = snd_pcm_open( pcm, deviceName, streamDir == StreamDirection_In ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NONBLOCK )) < 0 )
{
*pcm = NULL; /* Not to be closed */
- ENSURE( ret, ret == -EBUSY ? paDeviceUnavailable : paBadIODeviceCombination );
+ ENSURE_( ret, ret == -EBUSY ? paDeviceUnavailable : paBadIODeviceCombination );
}
- ENSURE( snd_pcm_nonblock( *pcm, 0 ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_nonblock( *pcm, 0 ), paUnanticipatedHostError );
end:
return result;
@@ -1005,26 +1106,49 @@ error:
goto end;
}
-static PaError TestParameters( const PaStreamParameters *parameters, const PaAlsaDeviceInfo *deviceInfo, const PaAlsaStreamInfo
- *streamInfo, double sampleRate, snd_pcm_stream_t streamType )
+static PaError TestParameters( const PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *parameters,
+ double sampleRate, StreamDirection streamDir )
{
PaError result = paNoError;
snd_pcm_t *pcm = NULL;
PaSampleFormat availableFormats;
- PaSampleFormat paFormat;
- snd_pcm_hw_params_t *params;
- snd_pcm_hw_params_alloca( &params );
+ /* We are able to adapt to a number of channels less than what the device supports */
+ unsigned int numHostChannels;
+ PaSampleFormat hostFormat;
+ snd_pcm_hw_params_t *hwParams;
+ snd_pcm_hw_params_alloca( &hwParams );
+
+ if( !parameters->hostApiSpecificStreamInfo )
+ {
+ const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, parameters->device );
+ numHostChannels = PA_MAX( parameters->channelCount, StreamDirection_In == streamDir ?
+ devInfo->minInputChannels : devInfo->minOutputChannels );
+ }
+ else
+ numHostChannels = parameters->channelCount;
+
+ PA_ENSURE( AlsaOpen( hostApi, parameters, streamDir, &pcm ) );
- PA_ENSURE( AlsaOpen( &pcm, deviceInfo, streamInfo, streamType ) );
+ snd_pcm_hw_params_any( pcm, hwParams );
- snd_pcm_hw_params_any( pcm, params );
+ if( SetApproximateSampleRate( pcm, hwParams, sampleRate ) < 0 )
+ {
+ result = paInvalidSampleRate;
+ goto error;
+ }
- ENSURE( SetApproximateSampleRate( pcm, params, sampleRate ), paInvalidSampleRate );
- ENSURE( snd_pcm_hw_params_set_channels( pcm, params, parameters->channelCount ), paInvalidChannelCount );
+ if( snd_pcm_hw_params_set_channels( pcm, hwParams, numHostChannels ) < 0 )
+ {
+ result = paInvalidChannelCount;
+ goto error;
+ }
/* See if we can find a best possible match */
availableFormats = GetAvailableFormats( pcm );
- PA_ENSURE( paFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, parameters->sampleFormat ) );
+ PA_ENSURE( hostFormat = PaUtil_SelectClosestAvailableFormat( availableFormats, parameters->sampleFormat ) );
+ ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, Pa2AlsaFormat( hostFormat ) ), paUnanticipatedHostError );
+
+ ENSURE_( snd_pcm_hw_params( pcm, hwParams ), paUnanticipatedHostError );
end:
if( pcm )
@@ -1035,7 +1159,6 @@ error:
goto end;
}
-
static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
const PaStreamParameters *inputParameters,
const PaStreamParameters *outputParameters,
@@ -1044,20 +1167,10 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
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 ) );
+ PA_ENSURE( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) );
inputChannelCount = inputParameters->channelCount;
inputSampleFormat = inputParameters->sampleFormat;
@@ -1065,63 +1178,86 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
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 ) );
+ PA_ENSURE( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ) );
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
+ if( inputChannelCount )
+ {
+ if( (result = TestParameters( hostApi, inputParameters, sampleRate, StreamDirection_In ))
+ != paNoError )
+ goto error;
+ }
+ if ( outputChannelCount )
+ {
+ if( (result = TestParameters( hostApi, outputParameters, sampleRate, StreamDirection_Out ))
+ != paNoError )
+ goto error;
+ }
- - check that the device supports sampleRate
+ return paFormatIsSupported;
- 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.
+error:
+ return result;
+}
- - check that input device can support inputSampleFormat, or that
- we have the capability to convert from outputSampleFormat to
- a native format
+static PaError PaAlsaStreamComponent_Initialize( PaAlsaStreamComponent *self, PaAlsaHostApiRepresentation *alsaApi,
+ const PaStreamParameters *params, StreamDirection streamDir, int callbackMode )
+{
+ PaError result = paNoError;
+ PaSampleFormat userSampleFormat = params->sampleFormat, hostSampleFormat;
+ assert( params->channelCount > 0 );
- - check that output device can support outputSampleFormat, or that
- we have the capability to convert from outputSampleFormat to
- a native format
- */
+ /* Make sure things have an initial value */
+ memset( self, 0, sizeof (PaAlsaStreamComponent) );
- if( inputChannelCount )
+ if( NULL == params->hostApiSpecificStreamInfo )
{
- PA_ENSURE( TestParameters( inputParameters, inputDeviceInfo, inputStreamInfo, sampleRate, SND_PCM_STREAM_CAPTURE ) );
+ const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( &alsaApi->commonHostApiRep, params->device );
+ self->numHostChannels = PA_MAX( params->channelCount, StreamDirection_In == streamDir ? devInfo->minInputChannels
+ : devInfo->minOutputChannels );
}
-
- if ( outputChannelCount )
+ else
{
- PA_ENSURE( TestParameters( outputParameters, outputDeviceInfo, outputStreamInfo, sampleRate, SND_PCM_STREAM_PLAYBACK ) );
+ /* We're blissfully unaware of the minimum channelCount */
+ self->numHostChannels = params->channelCount;
}
- return paFormatIsSupported;
+ PA_ENSURE( AlsaOpen( &alsaApi->commonHostApiRep, params, streamDir, &self->pcm ) );
+ self->nfds = snd_pcm_poll_descriptors_count( self->pcm );
+ hostSampleFormat = PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( self->pcm ), userSampleFormat );
+
+ self->hostSampleFormat = hostSampleFormat;
+ self->nativeFormat = Pa2AlsaFormat( hostSampleFormat );
+ self->hostInterleaved = self->userInterleaved = !(userSampleFormat & paNonInterleaved);
+ self->numUserChannels = params->channelCount;
+ self->streamDir = streamDir;
+
+ if( !callbackMode && !self->userInterleaved )
+ {
+ /* Pre-allocate non-interleaved user provided buffers */
+ PA_UNLESS( self->userBuffers = PaUtil_AllocateMemory( sizeof (void *) * self->numUserChannels ),
+ paInsufficientMemory );
+ }
error:
return result;
}
+static void PaAlsaStreamComponent_Terminate( PaAlsaStreamComponent *self )
+{
+ snd_pcm_close( self->pcm );
+ if( self->userBuffers )
+ PaUtil_FreeMemory( self->userBuffers );
+}
-/* 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 )
+/** Configure the associated ALSA pcm.
+ *
+ */
+static PaError PaAlsaStreamComponent_Configure( PaAlsaStreamComponent *self, const PaStreamParameters *params, unsigned long
+ framesPerHostBuffer, int primeBuffers, int callbackMode, double *sampleRate, PaTime *returnedLatency )
{
/*
int numPeriods;
@@ -1129,34 +1265,44 @@ static PaError ConfigureStream( snd_pcm_t *pcm, int channels, int *interleaved,
if( getenv("PA_NUMPERIODS") != NULL )
numPeriods = atoi( getenv("PA_NUMPERIODS") );
else
- numPeriods = ( (*latency * sampleRate) / *framesPerBuffer ) + 1;
+ numPeriods = ( (latency * sampleRate) / *framesPerBuffer ) + 1;
- PA_DEBUG(( "latency: %f, rate: %f, framesPerBuffer: %d\n", *latency, sampleRate, *framesPerBuffer ));
+ 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.
+ /* 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. */
+ * 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;
+ unsigned int numPeriods, minPeriods = 2;
+ int dir = 0;
+ snd_pcm_t *pcm = self->pcm;
+ PaTime latency = params->suggestedLatency;
+ double sr = *sampleRate;
+ *returnedLatency = -1.;
snd_pcm_hw_params_alloca( &hwParams );
snd_pcm_sw_params_alloca( &swParams );
+ self->framesPerBuffer = framesPerHostBuffer;
+
/* ... fill up the configuration space with all possibile
* combinations of parameters this device will accept */
- ENSURE( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError );
+
+ ENSURE_( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError );
- if( *interleaved )
+ if( self->userInterleaved )
{
accessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED;
alternateAccessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED;
@@ -1168,75 +1314,70 @@ static PaError ConfigureStream( snd_pcm_t *pcm, int channels, int *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 */
+ if( snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 )
+ {
+ ENSURE_( snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode ), paUnanticipatedHostError );
+ /* Flip mode */
+ self->hostInterleaved = !self->userInterleaved;
}
- /* 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 );
+ ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, self->nativeFormat ), paUnanticipatedHostError );
- /* ... set the sample rate */
- ENSURE( SetApproximateSampleRate( pcm, hwParams, *sampleRate ), paInvalidSampleRate );
- ENSURE( GetExactSampleRate( hwParams, sampleRate ), paUnanticipatedHostError );
+ ENSURE_( SetApproximateSampleRate( pcm, hwParams, sr ), paInvalidSampleRate );
+ ENSURE_( GetExactSampleRate( hwParams, &sr ), paUnanticipatedHostError );
+ /* reject if there's no sample rate within 1% of the one requested */
+ if( (fabs( *sampleRate - sr ) / *sampleRate) > 0.01 )
+ {
+ PA_DEBUG(("%s: Wanted %f, closest sample rate was %d\n", __FUNCTION__, sampleRate, sr ));
+ PA_ENSURE( paInvalidSampleRate );
+ }
- /* ... set the number of channels */
- ENSURE( snd_pcm_hw_params_set_channels( pcm, hwParams, channels ), paInvalidChannelCount );
+ ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParams, self->numHostChannels ), 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 );
+ /* I think there should be at least 2 periods (even though ALSA doesn't appear to enforce this) */
+ dir = 0;
+ ENSURE_( snd_pcm_hw_params_set_periods_min( pcm, hwParams, &minPeriods, &dir ), paUnanticipatedHostError );
+ dir = 0;
+ ENSURE_( snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &self->framesPerBuffer, &dir ), 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;
- }
- }
- }
- */
+ numPeriods = (latency * sr) / self->framesPerBuffer + 1;
+ dir = 0;
+ ENSURE_( snd_pcm_hw_params_set_periods_near( pcm, hwParams, &numPeriods, &dir ), paUnanticipatedHostError );
+ /* Minimum of periods should already be 2 */
+ PA_UNLESS( numPeriods >= 2, paInternalError );
/* Set the parameters! */
- ENSURE( snd_pcm_hw_params( pcm, hwParams ), paUnanticipatedHostError );
- ENSURE( snd_pcm_hw_params_get_buffer_size( hwParams, bufferSize ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_hw_params( pcm, hwParams ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_hw_params_get_buffer_size( hwParams, &self->bufferSize ), paUnanticipatedHostError );
/* Latency in seconds, one period is not counted as latency */
- *latency = (numPeriods - 1) * framesPerBuffer / *sampleRate;
+ latency = (numPeriods - 1) * self->framesPerBuffer / sr;
/* Now software parameters... */
- ENSURE( snd_pcm_sw_params_current( pcm, swParams ), paUnanticipatedHostError );
+ 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 );
+ ENSURE_( snd_pcm_sw_params_set_start_threshold( pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_sw_params_set_stop_threshold( pcm, swParams, self->bufferSize ), paUnanticipatedHostError );
/* Silence buffer in the case of underrun */
- if( !primeBuffers )
+ if( !primeBuffers ) /* XXX: Make sense? */
{
- 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 );
+ snd_pcm_uframes_t boundary;
+ ENSURE_( snd_pcm_sw_params_get_boundary( swParams, &boundary ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_sw_params_set_silence_threshold( pcm, swParams, 0 ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_sw_params_set_silence_size( pcm, swParams, boundary ), 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 );
+ ENSURE_( snd_pcm_sw_params_set_avail_min( pcm, swParams, self->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 );
+ ENSURE_( snd_pcm_sw_params( pcm, swParams ), paUnanticipatedHostError );
+
+ *sampleRate = sr;
+ *returnedLatency = latency;
end:
return result;
@@ -1245,347 +1386,434 @@ 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 )
+static PaError PaAlsaStream_Initialize( PaAlsaStream *self, PaAlsaHostApiRepresentation *alsaApi, const PaStreamParameters *inParams,
+ const PaStreamParameters *outParams, double sampleRate, unsigned long framesPerUserBuffer, PaStreamCallback callback,
+ PaStreamFlags streamFlags, 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;
+ assert( self );
- if( inputParameters )
- {
- if( inputParameters->device != paUseHostApiSpecificDeviceSpecification )
- {
- assert( inputParameters->device < hostApi->info.deviceCount );
- inputDeviceInfo = (PaAlsaDeviceInfo*)hostApi->deviceInfos[ inputParameters->device ];
- }
- else
- inputStreamInfo = inputParameters->hostApiSpecificStreamInfo;
+ memset( self, 0, sizeof (PaAlsaStream) );
- PA_ENSURE( ValidateParameters( inputParameters, inputDeviceInfo, streamIn, inputStreamInfo ) );
-
- numInputChannels = inputParameters->channelCount;
- inputSampleFormat = inputParameters->sampleFormat;
+ if( NULL != callback )
+ {
+ PaUtil_InitializeStreamRepresentation( &self->streamRepresentation,
+ &alsaApi->callbackStreamInterface,
+ callback, userData );
+ self->callbackMode = 1;
}
- if( outputParameters )
+ else
{
- if( outputParameters->device != paUseHostApiSpecificDeviceSpecification )
- {
- assert( outputParameters->device < hostApi->info.deviceCount );
- outputDeviceInfo = (PaAlsaDeviceInfo*)hostApi->deviceInfos[ outputParameters->device ];
- }
- else
- outputStreamInfo = outputParameters->hostApiSpecificStreamInfo;
+ PaUtil_InitializeStreamRepresentation( &self->streamRepresentation,
+ &alsaApi->blockingStreamInterface,
+ NULL, userData );
+ }
+
+ self->framesPerUserBuffer = framesPerUserBuffer;
+ self->neverDropInput = streamFlags & paNeverDropInput;
+ /* XXX: Ignore paPrimeOutputBuffersUsingStreamCallback untill buffer priming is fully supported in pa_process.c */
+ /*
+ if( outParams & streamFlags & paPrimeOutputBuffersUsingStreamCallback )
+ self->primeBuffers = 1;
+ */
+ memset( &self->capture, 0, sizeof (PaAlsaStreamComponent) );
+ memset( &self->playback, 0, sizeof (PaAlsaStreamComponent) );
+ if( inParams )
+ PA_ENSURE( PaAlsaStreamComponent_Initialize( &self->capture, alsaApi, inParams, StreamDirection_In, NULL != callback ) );
+ if( outParams )
+ PA_ENSURE( PaAlsaStreamComponent_Initialize( &self->playback, alsaApi, outParams, StreamDirection_Out, NULL != callback ) );
- PA_ENSURE( ValidateParameters( outputParameters, outputDeviceInfo, streamOut, outputStreamInfo ) );
+ assert( self->capture.nfds || self->playback.nfds );
- numOutputChannels = outputParameters->channelCount;
- outputSampleFormat = outputParameters->sampleFormat;
- }
+ PA_UNLESS( self->pfds = (struct pollfd*)PaUtil_AllocateMemory( (self->capture.nfds +
+ self->playback.nfds) * sizeof (struct pollfd) ), paInsufficientMemory );
- /* validate platform specific flags */
- if( (streamFlags & paPlatformSpecificFlags) != 0 )
- return paInvalidFlag; /* unexpected platform specific flag */
+ PaUtil_InitializeCpuLoadMeasurer( &self->cpuLoadMeasurer, sampleRate );
+ InitializeThreading( &self->threading, &self->cpuLoadMeasurer );
+ ASSERT_CALL_( pthread_mutex_init( &self->stateMtx, NULL ), 0 );
+ ASSERT_CALL_( pthread_mutex_init( &self->startMtx, NULL ), 0 );
+ ASSERT_CALL_( pthread_cond_init( &self->startCond, NULL ), 0 );
- /* allocate and do basic initialization of the stream structure */
+error:
+ return result;
+}
- UNLESS( stream = (PaAlsaStream*)PaUtil_AllocateMemory( sizeof(PaAlsaStream) ), paInsufficientMemory );
- InitializeStream( stream, (int) callback, streamFlags ); /* Initialize structure */
+/** Free resources associated with stream, and eventually stream itself.
+ *
+ * Frees allocated memory, and terminates individual StreamComponents.
+ */
+static void PaAlsaStream_Terminate( PaAlsaStream *self )
+{
+ assert( self );
- if( callback )
+ if( self->capture.pcm )
{
- PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
- &alsaHostApi->callbackStreamInterface,
- callback, userData );
+ PaAlsaStreamComponent_Terminate( &self->capture );
}
- else
+ if( self->playback.pcm )
{
- PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
- &alsaHostApi->blockingStreamInterface,
- callback, userData );
+ PaAlsaStreamComponent_Terminate( &self->playback );
}
- PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
+ PaUtil_FreeMemory( self->pfds );
+ ASSERT_CALL_( pthread_mutex_destroy( &self->stateMtx ), 0 );
+ ASSERT_CALL_( pthread_mutex_destroy( &self->startMtx ), 0 );
+ ASSERT_CALL_( pthread_cond_destroy( &self->startCond ), 0 );
- /* open the devices now, so we can obtain info about the available formats */
+ PaUtil_FreeMemory( self );
+}
- if( numInputChannels > 0 )
- {
- PA_ENSURE( AlsaOpen( &stream->pcm_capture, inputDeviceInfo, inputStreamInfo, SND_PCM_STREAM_CAPTURE ) );
+/** Calculate polling timeout
+ *
+ * @param frames Time to wait
+ * @return Polling timeout in milliseconds
+ */
+static int CalculatePollTimeout( const PaAlsaStream *stream, unsigned long frames )
+{
+ assert( stream->streamRepresentation.streamInfo.sampleRate > 0.0 );
+ /* Period in msecs, rounded up */
+ return (int)ceil( 1000 * frames / stream->streamRepresentation.streamInfo.sampleRate );
+}
- stream->capture_nfds = snd_pcm_poll_descriptors_count( stream->pcm_capture );
+/** Set up ALSA stream parameters.
+ *
+ */
+static PaError PaAlsaStream_Configure( PaAlsaStream *self, const PaStreamParameters *inParams, const PaStreamParameters
+ *outParams, double sampleRate, unsigned long framesPerHostBuffer, double *inputLatency, double *outputLatency,
+ unsigned long *maxHostBufferSize )
+{
+ PaError result = paNoError;
+ double realSr = sampleRate;
- hostInputSampleFormat =
- PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( stream->pcm_capture ),
- inputSampleFormat );
- }
+ if( self->capture.pcm )
+ PA_ENSURE( PaAlsaStreamComponent_Configure( &self->capture, inParams, framesPerHostBuffer, self->primeBuffers,
+ self->callbackMode, &realSr, inputLatency ) );
+ if( self->playback.pcm )
+ PA_ENSURE( PaAlsaStreamComponent_Configure( &self->playback, outParams, framesPerHostBuffer, self->primeBuffers,
+ self->callbackMode, &realSr, outputLatency ) );
- if( numOutputChannels > 0 )
+ /* Should be exact now */
+ self->streamRepresentation.streamInfo.sampleRate = realSr;
+
+ /* 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( self->callbackMode && self->capture.pcm && self->playback.pcm )
{
- PA_ENSURE( AlsaOpen( &stream->pcm_playback, outputDeviceInfo, outputStreamInfo, SND_PCM_STREAM_PLAYBACK ) );
+ int err = snd_pcm_link( self->capture.pcm, self->playback.pcm );
+ if( err >= 0 )
+ self->pcmsSynced = 1;
+ else
+ PA_DEBUG(( "%s: Unable to sync pcms: %s\n", __FUNCTION__, snd_strerror( err ) ));
+ }
- stream->playback_nfds = snd_pcm_poll_descriptors_count( stream->pcm_playback );
+ /* Frames per host buffer for the stream is set as a compromise between the two directions */
+ framesPerHostBuffer = PA_MIN( self->capture.pcm ? self->capture.framesPerBuffer : ULONG_MAX,
+ self->playback.pcm ? self->playback.framesPerBuffer : ULONG_MAX );
+ self->pollTimeout = CalculatePollTimeout( self, framesPerHostBuffer ); /* Period in msecs, rounded up */
- hostOutputSampleFormat =
- PaUtil_SelectClosestAvailableFormat( GetAvailableFormats( stream->pcm_playback ),
- outputSampleFormat );
- stream->playbackNativeFormat = Pa2AlsaFormat( hostOutputSampleFormat );
- }
+ *maxHostBufferSize = PA_MAX( self->capture.pcm ? self->capture.bufferSize : 0,
+ self->playback.pcm ? self->playback.bufferSize : 0 );
- /* 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 )
+ /* Time before watchdog unthrottles realtime thread == 1/4 of period time in msecs */
+ self->threading.throttledSleepTime = (unsigned long) (framesPerHostBuffer / sampleRate / 4 * 1000);
+
+ if( self->callbackMode )
{
- if( getenv("PA_ALSA_PERIODSIZE") != NULL )
- framesPerHostBuffer = atoi( getenv("PA_ALSA_PERIODSIZE") );
- else
+ /* If the user expects a certain number of frames per callback we will either have to rely on block adaption
+ * (framesPerHostBuffer is not an integer multiple of framesPerBuffer) or we can simply align the number
+ * of host buffer frames with what the user specified */
+ if( self->framesPerUserBuffer != paFramesPerBufferUnspecified )
{
- /* 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.
- */
+ /* self->alignFrames = 1; */
- 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 );
+ /* Unless the ratio between number of host and user buffer frames is an integer we will have to rely
+ * on block adaption */
+ /*
+ if( framesPerHostBuffer % framesPerBuffer != 0 || (self->capture.pcm && self->playback.pcm &&
+ self->capture.framesPerBuffer != self->playback.framesPerBuffer) )
+ self->useBlockAdaption = 1;
+ else
+ self->alignFrames = 1;
+ */
+ }
+ }
- desiredLatency = MIN( desiredLatency, maxBufferSize );
- }
+error:
+ return result;
+}
- /* Find the closest power of 2 */
- e = ilogb( minPeriodSize );
- if( minPeriodSize & (minPeriodSize - 1) )
- e += 1;
+/* 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.
+ *
+ * In the full-duplex case it is possible that the routine was unable
+ * to find a number of frames per buffer acceptable to both devices
+ * TODO: Implement an algorithm to find the value closest to acceptance
+ * by both devices, to minimize difference between period sizes?
+ */
+static PaError DetermineFramesPerBuffer( const PaAlsaStream *stream, double sampleRate, const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters, unsigned long *determinedFrames, const PaUtilHostApiRepresentation *hostApi )
+{
+ PaError result = paNoError;
+ unsigned long framesPerBuffer = 0;
+ int numHostInputChannels = 0, numHostOutputChannels = 0;
- 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! */
+ /* XXX: Clean this up */
+ if( stream->capture.pcm )
+ {
+ const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, inputParameters->device );
+ numHostInputChannels = PA_MAX( inputParameters->channelCount, devInfo->minInputChannels );
+ }
+ if( stream->playback.pcm )
+ {
+ const PaAlsaDeviceInfo *devInfo = GetDeviceInfo( hostApi, outputParameters->device );
+ numHostOutputChannels = PA_MAX( outputParameters->channelCount, devInfo->minOutputChannels );
+ }
- periodSize *= 2;
- }
+ if( stream->capture.pcm && stream->playback.pcm )
+ {
+ snd_pcm_uframes_t desiredLatency, e;
+ snd_pcm_uframes_t minPeriodSize, minPlayback, minCapture, maxPeriodSize, maxPlayback, maxCapture,
+ optimalPeriodSize, periodSize;
+ int dir = 0;
+ unsigned int minPeriods = 2;
- /* 4 periods considered optimal */
- optimalPeriodSize = MAX( desiredLatency / 4, minPeriodSize );
- optimalPeriodSize = MIN( optimalPeriodSize, maxPeriodSize );
+ 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->playback.pcm;
+ snd_pcm_hw_params_any( pcm, hwParamsPlayback );
+ ENSURE_( SetApproximateSampleRate( pcm, hwParamsPlayback, sampleRate ), paInvalidSampleRate );
+ ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParamsPlayback, numHostOutputChannels ),
+ 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_set_periods_min( pcm, hwParamsPlayback, &minPeriods, &dir ), 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->capture.pcm;
+ ENSURE_( snd_pcm_hw_params_any( pcm, hwParamsCapture ), paUnanticipatedHostError );
+ ENSURE_( SetApproximateSampleRate( pcm, hwParamsCapture, sampleRate ), paBadIODeviceCombination );
+ ENSURE_( snd_pcm_hw_params_set_channels( pcm, hwParamsCapture, numHostInputChannels ),
+ 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_set_periods_min( pcm, hwParamsCapture, &minPeriods, &dir ), 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 = PA_MAX( minPlayback, minCapture );
+ maxPeriodSize = PA_MIN( maxPlayback, maxCapture );
+
+ desiredLatency = (snd_pcm_uframes_t) (PA_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, &maxBufferSize ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_hw_params_get_buffer_size_max( hwParamsCapture, &tmp ), paUnanticipatedHostError );
+ maxBufferSize = PA_MIN( maxBufferSize, tmp );
- /* Find the closest power of 2 */
- e = ilogb( optimalPeriodSize );
- if( optimalPeriodSize & (optimalPeriodSize - 1) )
- e += 1;
+ desiredLatency = PA_MIN( desiredLatency, maxBufferSize );
+ }
- 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;
+ /* Find the closest power of 2 */
+ e = ilogb( minPeriodSize );
+ if( minPeriodSize & (minPeriodSize - 1) )
+ e += 1;
+ periodSize = (snd_pcm_uframes_t) pow( 2, e );
- pcm = stream->pcm_capture;
- if( snd_pcm_hw_params_test_period_size( pcm, hwParamsCapture, optimalPeriodSize, 0 ) >= 0 )
- break;
+ while( periodSize <= maxPeriodSize )
+ {
+ if( snd_pcm_hw_params_test_period_size( stream->playback.pcm, hwParamsPlayback, periodSize, 0 ) >= 0 &&
+ snd_pcm_hw_params_test_period_size( stream->capture.pcm, hwParamsCapture, periodSize, 0 ) >= 0 )
+ break; /* Ok! */
- optimalPeriodSize /= 2;
- }
+ periodSize *= 2;
+ }
- if( optimalPeriodSize > periodSize )
- periodSize = optimalPeriodSize;
+ /* 4 periods considered optimal */
+ optimalPeriodSize = PA_MAX( desiredLatency / 4, minPeriodSize );
+ optimalPeriodSize = PA_MIN( optimalPeriodSize, maxPeriodSize );
- 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;
+ /* Find the closest power of 2 */
+ e = ilogb( optimalPeriodSize );
+ if( optimalPeriodSize & (optimalPeriodSize - 1) )
+ e += 1;
+ optimalPeriodSize = (snd_pcm_uframes_t) pow( 2, e );
- snd_pcm_hw_params_alloca( &hwParams );
+ while( optimalPeriodSize >= periodSize )
+ {
+ pcm = stream->playback.pcm;
+ if( snd_pcm_hw_params_test_period_size( pcm, hwParamsPlayback, optimalPeriodSize, 0 ) < 0 )
+ continue;
- 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;
- }
+ pcm = stream->capture.pcm;
+ if( snd_pcm_hw_params_test_period_size( pcm, hwParamsCapture, optimalPeriodSize, 0 ) >= 0 )
+ break;
- 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 );
+ optimalPeriodSize /= 2;
+ }
- ENSURE( snd_pcm_hw_params_set_period_size_integer( pcm, hwParams ), paUnanticipatedHostError );
- ENSURE( snd_pcm_hw_params_set_periods_integer( pcm, hwParams ), paUnanticipatedHostError );
+ if( optimalPeriodSize > periodSize )
+ periodSize = optimalPeriodSize;
- /* 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 );
- }
+ if( periodSize <= maxPeriodSize )
+ {
+ /* Looks good */
+ framesPerBuffer = periodSize;
+ }
+ else
+ {
+ /* Unable to find a common period size, oh well */
+ optimalPeriodSize = PA_MAX( desiredLatency / 4, minPeriodSize );
+ optimalPeriodSize = PA_MIN( optimalPeriodSize, maxPeriodSize );
+
+ /* ConfigureStream should find individual period sizes acceptable for each device */
+ framesPerBuffer = optimalPeriodSize;
+ /* PA_ENSURE( paBadIODeviceCombination ); */
}
}
- else
+ else /* half-duplex is a slightly simpler case */
{
- framesPerHostBuffer = framesPerBuffer;
+ unsigned long bufferSize, channels;
+ snd_pcm_t *pcm;
+ snd_pcm_hw_params_t *hwParams;
+
+ snd_pcm_hw_params_alloca( &hwParams );
+
+ if( stream->capture.pcm )
+ {
+ pcm = stream->capture.pcm;
+ bufferSize = inputParameters->suggestedLatency * sampleRate;
+ channels = numHostInputChannels;
+ }
+ else
+ {
+ pcm = stream->playback.pcm;
+ bufferSize = outputParameters->suggestedLatency * sampleRate;
+ channels = numHostOutputChannels;
+ }
+
+ ENSURE_( snd_pcm_hw_params_any( pcm, hwParams ), paUnanticipatedHostError );
+ ENSURE_( SetApproximateSampleRate( pcm, hwParams, sampleRate ), paInvalidSampleRate );
+ 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 */
+ framesPerBuffer = bufferSize / 4;
+ bufferSize += framesPerBuffer; /* 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, &framesPerBuffer, NULL ), paUnanticipatedHostError );
}
- /* Will fill in correct values later */
- stream->streamRepresentation.streamInfo.inputLatency = 0.;
- stream->streamRepresentation.streamInfo.outputLatency = 0.;
+ PA_UNLESS( framesPerBuffer != 0, paInternalError );
+ *determinedFrames = framesPerBuffer;
- if( numInputChannels > 0 )
+error:
+ return result;
+}
+
+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;
+ PaAlsaStream *stream = NULL;
+ PaSampleFormat hostInputSampleFormat = 0, hostOutputSampleFormat = 0;
+ PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0;
+ int numInputChannels = 0, numOutputChannels = 0;
+ PaTime inputLatency, outputLatency;
+ unsigned long framesPerHostBuffer;
+ PaUtilHostBufferSizeMode hostBufferSizeMode = paUtilBoundedHostBufferSize;
+ unsigned long maxHostBufferSize; /* Upper bound of host buffer size */
+
+ if( (streamFlags & paPlatformSpecificFlags) != 0 )
+ return paInvalidFlag;
+
+ if( inputParameters )
{
- int interleaved = !(inputSampleFormat & paNonInterleaved);
- PaSampleFormat plain_format = hostInputSampleFormat & ~paNonInterleaved;
+ PA_ENSURE( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) );
- 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 ) );
+ numInputChannels = inputParameters->channelCount;
+ inputSampleFormat = inputParameters->sampleFormat;
+ }
+ if( outputParameters )
+ {
+ PA_ENSURE( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ) );
- stream->capture_interleaved = interleaved;
+ numOutputChannels = outputParameters->channelCount;
+ outputSampleFormat = outputParameters->sampleFormat;
}
- if( numOutputChannels > 0 )
+ /* XXX: Why do we support this anyway? */
+ if( framesPerBuffer == paFramesPerBufferUnspecified && getenv( "PA_ALSA_PERIODSIZE" ) != NULL )
{
- int interleaved = !(outputSampleFormat & paNonInterleaved);
- PaSampleFormat plain_format = hostOutputSampleFormat & ~paNonInterleaved;
- int primeBuffers = streamFlags & paPrimeOutputBuffersUsingStreamCallback;
+ PA_DEBUG(( "%s: Getting framesPerBuffer from environment\n", __FUNCTION__ ));
+ framesPerBuffer = atoi( getenv("PA_ALSA_PERIODSIZE") );
+ }
+ framesPerHostBuffer = framesPerBuffer;
- outputLatency = outputParameters->suggestedLatency; /* Real latency in seconds returned from ConfigureStream */
+ PA_UNLESS( stream = (PaAlsaStream*)PaUtil_AllocateMemory( sizeof(PaAlsaStream) ), paInsufficientMemory );
+ PA_ENSURE( PaAlsaStream_Initialize( stream, alsaHostApi, inputParameters, outputParameters, sampleRate,
+ framesPerBuffer, callback, streamFlags, userData ) );
+
+ /* 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 )
+ {
+ PA_ENSURE( DetermineFramesPerBuffer( stream, sampleRate, inputParameters, outputParameters, &framesPerHostBuffer,
+ hostApi ) );
+ }
- PA_ENSURE( ConfigureStream( stream->pcm_playback, numOutputChannels, &interleaved,
- &sampleRate, plain_format, framesPerHostBuffer, &stream->playbackBufferSize,
- &outputLatency, primeBuffers, stream->callback_mode ) );
+ PA_ENSURE( PaAlsaStream_Configure( stream, inputParameters, outputParameters, sampleRate, framesPerHostBuffer,
+ &inputLatency, &outputLatency, &maxHostBufferSize ) );
+ hostInputSampleFormat = stream->capture.hostSampleFormat;
+ hostOutputSampleFormat = stream->playback.hostSampleFormat;
- /* 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;
+ if( framesPerHostBuffer != framesPerBuffer )
+ {
+ PA_DEBUG(( "%s: Number of frames per user and host buffer differs\n", __FUNCTION__ ));
}
- /* 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 ) );
+ sampleRate, streamFlags, framesPerBuffer, maxHostBufferSize,
+ hostBufferSizeMode, callback, userData ) );
/* Ok, buffer processor is initialized, now we can deduce it's latency */
if( numInputChannels > 0 )
@@ -1595,38 +1823,17 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
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 );
+ PaAlsaStream_Terminate( 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;
@@ -1635,7 +1842,7 @@ static PaError CloseStream( PaStream* s )
PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
- CleanUpStream( stream );
+ PaAlsaStream_Terminate( stream );
return result;
}
@@ -1643,14 +1850,14 @@ static PaError CloseStream( PaStream* s )
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_uframes_t frames = (snd_pcm_uframes_t)snd_pcm_avail_update( stream->playback.pcm ), offset;
- 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 );
+ snd_pcm_mmap_begin( stream->playback.pcm, &areas, &offset, &frames );
+ snd_pcm_areas_silence( areas, offset, stream->playback.numHostChannels, frames, stream->playback.nativeFormat );
+ snd_pcm_mmap_commit( stream->playback.pcm, offset, frames );
}
-/*! \brief Start/prepare pcm(s) for streaming
+/** 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
@@ -1665,26 +1872,26 @@ static PaError AlsaStart( PaAlsaStream *stream, int priming )
{
PaError result = paNoError;
- if( stream->pcm_playback )
+ if( stream->playback.pcm )
{
- if( stream->callback_mode )
+ if( stream->callbackMode )
{
- /* We're not priming buffer, so prepare and silence */
if( !priming )
{
- ENSURE( snd_pcm_prepare( stream->pcm_playback ), paUnanticipatedHostError );
+ /* Buffer isn't primed, so prepare and silence */
+ ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError );
SilenceBuffer( stream );
}
- ENSURE( snd_pcm_start( stream->pcm_playback ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError );
}
else
- ENSURE( snd_pcm_prepare( stream->pcm_playback ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError );
}
- if( stream->pcm_capture && !stream->pcmsSynced )
+ if( stream->capture.pcm && !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 );
+ ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError );
+ /* For a blocking stream we want to start capture as well, since nothing will happen otherwise */
+ ENSURE_( snd_pcm_start( stream->capture.pcm ), paUnanticipatedHostError );
}
end:
@@ -1693,16 +1900,17 @@ error:
goto end;
}
-/*! \brief Utility function for determining if pcms are in running state
+/** 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 )
+ ASSERT_CALL_( pthread_mutex_lock( &stream->stateMtx ), 0 ); /* Synchronize access to pcm state */
+ if( stream->capture.pcm )
{
- snd_pcm_state_t capture_state = snd_pcm_state( stream->pcm_capture );
+ snd_pcm_state_t capture_state = snd_pcm_state( stream->capture.pcm );
if( capture_state == SND_PCM_STATE_RUNNING || capture_state == SND_PCM_STATE_XRUN
|| capture_state == SND_PCM_STATE_DRAINING )
@@ -1712,9 +1920,9 @@ static int IsRunning( PaAlsaStream *stream )
}
}
- if( stream->pcm_playback )
+ if( stream->playback.pcm )
{
- snd_pcm_state_t playback_state = snd_pcm_state( stream->pcm_playback );
+ snd_pcm_state_t playback_state = snd_pcm_state( stream->playback.pcm );
if( playback_state == SND_PCM_STATE_RUNNING || playback_state == SND_PCM_STATE_XRUN
|| playback_state == SND_PCM_STATE_DRAINING )
@@ -1725,7 +1933,7 @@ static int IsRunning( PaAlsaStream *stream )
}
end:
- pthread_mutex_unlock( &stream->stateMtx );
+ ASSERT_CALL_( pthread_mutex_unlock( &stream->stateMtx ), 0 );
return result;
}
@@ -1742,7 +1950,7 @@ static PaError StartStream( PaStream *s )
/* Set now, so we can test for activity further down */
stream->isActive = 1;
- if( stream->callback_mode )
+ if( stream->callbackMode )
{
int res = 0;
PaTime pt = PaUtil_GetTime();
@@ -1758,13 +1966,16 @@ static PaError StartStream( PaStream *s )
/* 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 ) )
+ ASSERT_CALL_( pthread_mutex_lock( &stream->startMtx ), 0 );
+
+ /* Due to possible spurious wakeups, we enclose in a loop */
+ while( !IsRunning( stream ) && IsStreamActive( s ) && !res )
{
res = pthread_cond_timedwait( &stream->startCond, &stream->startMtx, &ts );
- UNLESS( !res || res == ETIMEDOUT, paInternalError );
}
- UNLESS( !pthread_mutex_unlock( &stream->startMtx ), paInternalError );
+ ASSERT_CALL_( pthread_mutex_unlock( &stream->startMtx ), 0 );
+
+ PA_UNLESS( !res || res == ETIMEDOUT, paInternalError );
PA_DEBUG(( "%s: Waited for %g seconds for stream to start\n", __FUNCTION__, PaUtil_GetTime() - pt ));
if( res == ETIMEDOUT )
@@ -1794,19 +2005,19 @@ static PaError AlsaStop( PaAlsaStream *stream, int abort )
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 );
+ if( stream->playback.pcm )
+ ENSURE_( snd_pcm_drop( stream->playback.pcm ), paUnanticipatedHostError );
+ if( stream->capture.pcm && !stream->pcmsSynced )
+ ENSURE_( snd_pcm_drop( stream->capture.pcm ), 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 );
+ if( stream->playback.pcm )
+ ENSURE_( snd_pcm_drain( stream->playback.pcm ), paUnanticipatedHostError );
+ if( stream->capture.pcm && !stream->pcmsSynced )
+ ENSURE_( snd_pcm_drain( stream->capture.pcm ), paUnanticipatedHostError );
}
end:
@@ -1815,7 +2026,7 @@ error:
goto end;
}
-/*! \brief Stop or abort stream
+/** 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
@@ -1831,17 +2042,23 @@ static PaError RealStop( PaAlsaStream *stream, int abort )
/* First deal with the callback thread, cancelling and/or joining
* it if necessary
*/
- if( stream->callback_mode )
+ if( stream->callbackMode )
{
PaError threadRes, watchdogRes;
stream->callbackAbort = abort;
- PA_ENSURE( KillCallbackThread( &stream->threading, &threadRes, &watchdogRes ) );
+ if( !abort )
+ {
+ PA_DEBUG(( "Stopping callback\n" ));
+ stream->callbackStop = 1;
+ }
+ PA_ENSURE( KillCallbackThread( &stream->threading, !abort, &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->callbackStop = 0; /* The deed is done */
stream->callback_finished = 0;
}
else
@@ -1868,19 +2085,16 @@ 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
+/** 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;
+ return !IsStreamActive( s ) && !stream->callback_finished;
}
static PaError IsStreamActive( PaStream *s )
@@ -1900,25 +2114,22 @@ static PaTime GetStreamTime( PaStream *s )
/* 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.
+ * libasound routines are being called from multiple threads.
* need to verify that libasound is thread-safe. */
- if( stream->pcm_capture )
+ if( stream->capture.pcm )
{
- snd_pcm_status( stream->pcm_capture, status );
+ snd_pcm_status( stream->capture.pcm, status );
}
- else if( stream->pcm_playback )
+ else if( stream->playback.pcm )
{
- snd_pcm_status( stream->pcm_playback, status );
+ snd_pcm_status( stream->playback.pcm, status );
}
snd_pcm_status_get_tstamp( status, &timestamp );
- PA_DEBUG(( "Time in secs: %d\n", timestamp.tv_sec ));
-
- return timestamp.tv_sec + (PaTime) timestamp.tv_usec/1000000;
+ return timestamp.tv_sec + (PaTime)timestamp.tv_usec / 1000000.0;
}
-
static double GetStreamCpuLoad( PaStream* s )
{
PaAlsaStream *stream = (PaAlsaStream*)s;
@@ -1926,32 +2137,6 @@ static double GetStreamCpuLoad( PaStream* 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;
@@ -1988,7 +2173,6 @@ static int GetExactSampleRate( snd_pcm_hw_params_t *hwParams, double *sampleRate
return err;
}
-
/* Utility functions for blocking/callback interfaces */
/* Atomic restart of stream (we don't want the intermediate state visible) */
@@ -1996,23 +2180,21 @@ static PaError AlsaRestart( PaAlsaStream *stream )
{
PaError result = paNoError;
- PA_DEBUG(( "Restarting audio\n" ));
-
- UNLESS( !pthread_mutex_lock( &stream->stateMtx ), paInternalError );
+ ASSERT_CALL_( pthread_mutex_lock( &stream->stateMtx ), 0 );
PA_ENSURE( AlsaStop( stream, 0 ) );
PA_ENSURE( AlsaStart( stream, 0 ) );
- PA_DEBUG(( "Restarted audio\n" ));
+ PA_DEBUG(( "%s: Restarted audio\n", __FUNCTION__ ));
-end:
- if( pthread_mutex_unlock( &stream->stateMtx ) != 0 )
- result = paInternalError;
- return result;
error:
- goto end;
+ ASSERT_CALL_( pthread_mutex_unlock( &stream->stateMtx ), 0 );
+ return result;
}
-static PaError HandleXrun( PaAlsaStream *stream )
+/** Recover from xrun state.
+ *
+ */
+static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self )
{
PaError result = paNoError;
snd_pcm_status_t *st;
@@ -2021,26 +2203,26 @@ static PaError HandleXrun( PaAlsaStream *stream )
snd_pcm_status_alloca( &st );
- if( stream->pcm_playback )
+ if( self->playback.pcm )
{
- snd_pcm_status( stream->pcm_playback, st );
+ snd_pcm_status( self->playback.pcm, 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);
+ self->underrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000);
}
}
- if( stream->pcm_capture )
+ if( self->capture.pcm )
{
- snd_pcm_status( stream->pcm_capture, st );
+ snd_pcm_status( self->capture.pcm, 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);
+ self->overrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000);
}
}
- PA_ENSURE( AlsaRestart( stream ) );
+ PA_ENSURE( AlsaRestart( self ) );
end:
return result;
@@ -2048,535 +2230,835 @@ 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 )
+/** Decide if we should continue polling for specified direction, eventually adjust the poll timeout.
+ *
+ */
+static PaError ContinuePoll( const PaAlsaStream *stream, StreamDirection streamDir, int *pollTimeout, int *continuePoll )
{
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;
+ snd_pcm_sframes_t delay, margin;
+ int err;
+ const PaAlsaStreamComponent *component = NULL, *otherComponent = NULL;
- if( stream->pcm_playback )
- pollPlayback = 1;
+ *continuePoll = 1;
- while( pollPlayback || pollCapture )
+ if( StreamDirection_In == streamDir )
{
- unsigned short revents;
- int totalFds = 0;
- int pfdOfs = 0;
+ component = &stream->capture;
+ otherComponent = &stream->playback;
+ }
+ else
+ {
+ component = &stream->playback;
+ otherComponent = &stream->capture;
+ }
- /* 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 )
+ /* ALSA docs say that negative delay should indicate xrun, but in my experience snd_pcm_delay returns -EPIPE */
+ if( (err = snd_pcm_delay( otherComponent->pcm, &delay )) < 0 )
+ {
+ if( err == -EPIPE )
{
- snd_pcm_poll_descriptors( stream->pcm_capture, stream->pfds, stream->capture_nfds );
- pfdOfs += stream->capture_nfds;
- totalFds += stream->capture_nfds;
+ /* Xrun */
+ *continuePoll = 0;
+ goto error;
}
- if( stream->pcm_playback && pollPlayback )
+
+ ENSURE_( err, paUnanticipatedHostError );
+ }
+
+ if( StreamDirection_Out == streamDir )
+ {
+ /* Number of eligible frames before capture overrun */
+ delay = otherComponent->bufferSize - delay;
+ }
+ margin = delay - otherComponent->framesPerBuffer / 2;
+
+ if( margin < 0 )
+ {
+ PA_DEBUG(( "%s: Stopping poll for %s\n", __FUNCTION__, StreamDirection_In == streamDir ? "capture" : "playback" ));
+ *continuePoll = 0;
+ }
+ else if( margin < otherComponent->framesPerBuffer )
+ {
+ *pollTimeout = CalculatePollTimeout( stream, margin );
+ PA_DEBUG(( "%s: Trying to poll again for %s frames, pollTimeout: %d\n",
+ __FUNCTION__, StreamDirection_In == streamDir ? "capture" : "playback", *pollTimeout ));
+ }
+
+error:
+ return result;
+}
+
+/* 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;
+}
+
+static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *timeInfo )
+{
+ snd_pcm_status_t *capture_status, *playback_status;
+ snd_timestamp_t capture_timestamp, playback_timestamp;
+ PaTime capture_time = 0., playback_time = 0.;
+
+ snd_pcm_status_alloca( &capture_status );
+ snd_pcm_status_alloca( &playback_status );
+
+ if( stream->capture.pcm )
+ {
+ snd_pcm_sframes_t capture_delay;
+
+ snd_pcm_status( stream->capture.pcm, capture_status );
+ snd_pcm_status_get_tstamp( capture_status, &capture_timestamp );
+
+ capture_time = capture_timestamp.tv_sec +
+ ((PaTime)capture_timestamp.tv_usec / 1000000.0);
+ 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->playback.pcm )
+ {
+ snd_pcm_sframes_t playback_delay;
+
+ snd_pcm_status( stream->playback.pcm, playback_status );
+ snd_pcm_status_get_tstamp( playback_status, &playback_timestamp );
+
+ playback_time = playback_timestamp.tv_sec +
+ ((PaTime)playback_timestamp.tv_usec / 1000000.0);
+
+ if( stream->capture.pcm ) /* Full duplex */
{
- snd_pcm_poll_descriptors( stream->pcm_playback, stream->pfds + pfdOfs, stream->playback_nfds );
- totalFds += stream->playback_nfds;
+ /* 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;
- /* if the main thread has requested that we stop, do so now */
- pthread_testcancel();
+ playback_delay = snd_pcm_status_get_delay( playback_status );
+ timeInfo->outputBufferDacTime = timeInfo->currentTime +
+ (PaTime)playback_delay / stream->streamRepresentation.streamInfo.sampleRate;
+ }
+}
- /* now poll on the combination of playback and capture fds. */
- if( poll( stream->pfds, totalFds, stream->pollTimeout ) < 0 )
- {
- if( errno == EINTR ) {
- continue;
- }
+/** Called after buffer processing is finished.
+ *
+ * A number of mmapped frames is committed, it is possible that an xrun has occurred in the meantime.
+ *
+ * @param numFrames The number of frames that has been processed
+ * @param xrun Return whether an xrun has occurred
+ */
+static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self, unsigned long numFrames, int *xrun )
+{
+ PaError result = paNoError;
+ int res;
- PA_ENSURE( paInternalError );
- }
+ /* @concern FullDuplex It is possible that only one direction is marked ready after polling, and processed
+ * afterwards
+ */
+ if( !self->ready )
+ goto end;
- pthread_testcancel();
+ res = snd_pcm_mmap_commit( self->pcm, self->offset, numFrames );
+ if( res == -EPIPE || res == -ESTRPIPE )
+ {
+ *xrun = 1;
+ }
+ else
+ {
+ ENSURE_( res, paUnanticipatedHostError );
+ }
- /* 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;
+end:
+error:
+ return result;
+}
- pollCapture = 0;
- }
- else if( stream->pcm_playback ) /* Timed out, go on with playback? */
+/* 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;
+}
+
+/** Do necessary adaption between user and host channels.
+ *
+ @concern ChannelAdaption Adapting between user and host channels can involve silencing unused channels and
+ duplicating mono information if host outputs come in pairs.
+ */
+static PaError PaAlsaStreamComponent_DoChannelAdaption( PaAlsaStreamComponent *self, PaUtilBufferProcessor *bp, int numFrames )
+{
+ PaError result = paNoError;
+ unsigned char *p;
+ int i;
+ int unusedChans = self->numHostChannels - self->numUserChannels;
+ unsigned char *src, *dst;
+ int convertMono = (self->numHostChannels % 2) == 0 && (self->numUserChannels % 2) != 0;
+
+ assert( StreamDirection_Out == self->streamDir );
+
+ if( self->hostInterleaved )
+ {
+ int swidth = snd_pcm_format_size( self->nativeFormat, 1 );
+ unsigned char *buffer = ExtractAddress( self->channelAreas, self->offset );
+
+ /* Start after the last user channel */
+ p = buffer + self->numUserChannels * swidth;
+
+ if( convertMono )
+ {
+ /* Convert the last user channel into stereo pair */
+ src = buffer + (self->numUserChannels - 1) * swidth;
+ for( i = 0; i < numFrames; ++i )
{
- /* 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* ... */
- }
+ dst = src + swidth;
+ memcpy( dst, src, swidth );
+ src += self->numHostChannels * swidth;
}
+
+ /* Don't touch the channel we just wrote to */
+ p += swidth;
+ --unusedChans;
}
- if( pollPlayback )
+ if( unusedChans > 0 )
{
- unsigned short revents;
- ENSURE( snd_pcm_poll_descriptors_revents( stream->pcm_playback, stream->pfds +
- pfdOfs, stream->playback_nfds, &revents ), paUnanticipatedHostError );
- if( revents )
+ /* Silence unused output channels */
+ for( i = 0; i < numFrames; ++i )
{
- 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 .. */
- }
+ memset( p, 0, swidth * unusedChans );
+ p += self->numHostChannels * swidth;
}
}
}
+ else
+ {
+ /* We extract the last user channel */
+ if( convertMono )
+ {
+ ENSURE_( snd_pcm_area_copy( self->channelAreas + self->numUserChannels, self->offset, self->channelAreas +
+ (self->numUserChannels - 1), self->offset, numFrames, self->nativeFormat ), paUnanticipatedHostError );
+ --unusedChans;
+ }
+ if( unusedChans > 0 )
+ {
+ snd_pcm_areas_silence( self->channelAreas + (self->numHostChannels - unusedChans), self->offset, unusedChans, numFrames,
+ self->nativeFormat );
+ }
+ }
- /* we have now established that there are buffers ready to be
- * operated on. Now determine how many frames are available.
- */
- if( stream->pcm_capture )
+error:
+ return result;
+}
+
+static PaError PaAlsaStream_EndProcessing( PaAlsaStream *self, unsigned long numFrames, int *xrunOccurred )
+{
+ PaError result = paNoError;
+ int xrun = 0;
+
+ if( self->capture.pcm )
{
- if( (captureAvail = snd_pcm_avail_update( stream->pcm_capture )) == -EPIPE )
- xrun = 1;
- else
- ENSURE( captureAvail, paUnanticipatedHostError );
+ PA_ENSURE( PaAlsaStreamComponent_EndProcessing( &self->capture, numFrames, &xrun ) );
+ }
+ if( self->playback.pcm )
+ {
+ if( self->playback.numHostChannels > self->playback.numUserChannels )
+ PA_ENSURE( PaAlsaStreamComponent_DoChannelAdaption( &self->playback, &self->bufferProcessor, numFrames ) );
+ PA_ENSURE( PaAlsaStreamComponent_EndProcessing( &self->playback, numFrames, &xrun ) );
+ }
- if( !captureAvail )
- PA_DEBUG(( "Wait: captureAvail: 0\n" ));
+error:
+ *xrunOccurred = xrun;
+ return result;
+}
- captureAvail = captureAvail == 0 ? INT_MAX : captureAvail; /* Disregard if zero */
+/** Update the number of available frames.
+ *
+ */
+static PaError PaAlsaStreamComponent_GetAvailableFrames( PaAlsaStreamComponent *self, unsigned long *numFrames, int *xrunOccurred )
+{
+ PaError result = paNoError;
+ snd_pcm_sframes_t framesAvail = snd_pcm_avail_update( self->pcm );
+ *xrunOccurred = 0;
+
+ if( -EPIPE == framesAvail )
+ {
+ *xrunOccurred = 1;
+ framesAvail = 0;
}
+ else
+ ENSURE_( framesAvail, paUnanticipatedHostError );
+
+ *numFrames = framesAvail;
+
+error:
+ return result;
+}
+
+/** Fill in pollfd objects.
+ */
+static PaError PaAlsaStreamComponent_BeginPolling( PaAlsaStreamComponent *self, struct pollfd *pfds )
+{
+ PaError result = paNoError;
+ int ret = snd_pcm_poll_descriptors( self->pcm, pfds, self->nfds );
+ assert( ret == self->nfds );
+
+ self->ready = 0;
+
+ return result;
+}
+
+/** Examine results from poll().
+ *
+ * @param pfds pollfds to inspect
+ * @param shouldPoll Should we continue to poll
+ * @param xrun Has an xrun occurred
+ */
+static PaError PaAlsaStreamComponent_EndPolling( PaAlsaStreamComponent *self, struct pollfd *pfds, int *shouldPoll, int *xrun )
+{
+ PaError result = paNoError;
+ unsigned short revents;
- if( stream->pcm_playback )
+ ENSURE_( snd_pcm_poll_descriptors_revents( self->pcm, pfds, self->nfds, &revents ), paUnanticipatedHostError );
+ if( revents != 0 )
{
- if( (playbackAvail = snd_pcm_avail_update( stream->pcm_playback )) == -EPIPE )
- xrun = 1;
+ if( revents & POLLERR )
+ {
+ *xrun = 1;
+ }
else
- ENSURE( playbackAvail, paUnanticipatedHostError );
-
- if( !playbackAvail )
- PA_DEBUG(( "Wait: playbackAvail: 0\n" ));
+ self->ready = 1;
- playbackAvail = playbackAvail == 0 ? INT_MAX : playbackAvail; /* Disregard if zero */
+ *shouldPoll = 0;
}
-
- commonAvail = MIN( captureAvail, playbackAvail );
- commonAvail -= commonAvail % stream->frames_per_period;
+error:
+ return result;
+}
- if( xrun )
+/** Return the number of available frames for this stream.
+ *
+ * @concern FullDuplex The minimum available for the two directions is calculated, it might be desirable to ignore
+ * one direction however (not marked ready from poll), so this is controlled by queryCapture and queryPlayback.
+ *
+ * @param queryCapture Check available for capture
+ * @param queryPlayback Check available for playback
+ * @param available The returned number of frames
+ * @param xrunOccurred Return whether an xrun has occurred
+ */
+static PaError PaAlsaStream_GetAvailableFrames( PaAlsaStream *self, int queryCapture, int queryPlayback, unsigned long
+ *available, int *xrunOccurred )
+{
+ PaError result = paNoError;
+ unsigned long captureFrames, playbackFrames;
+ *xrunOccurred = 0;
+
+ assert( queryCapture || queryPlayback );
+
+ if( queryCapture )
+ {
+ assert( self->capture.pcm );
+ PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &self->capture, &captureFrames, xrunOccurred ) );
+ if( *xrunOccurred )
+ goto end;
+ }
+ if( queryPlayback )
{
- HandleXrun( stream );
- commonAvail = 0; /* Wait will be called again, to obtain the number of available frames */
+ assert( self->playback.pcm );
+ PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &self->playback, &playbackFrames, xrunOccurred ) );
+ if( *xrunOccurred )
+ goto end;
}
- assert( commonAvail >= 0 );
- *frames = commonAvail;
+ if( queryCapture && queryPlayback )
+ {
+ *available = PA_MIN( captureFrames, playbackFrames );
+ }
+ else if( queryCapture )
+ {
+ *available = captureFrames;
+ }
+ else
+ {
+ *available = playbackFrames;
+ }
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;
+ return result;
}
-/*! \brief Get buffers from ALSA for read/write, and determine the amount of frames available.
+/** Wait for and report available buffer space from ALSA.
*
- * 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 )
+ * Unless ALSA reports a minimum of frames available for I/O, we poll the ALSA filedescriptors for more.
+ * Both of these operations can uncover xrun conditions.
+ *
+ * @concern Xruns Both polling and querying available frames can report an xrun condition.
+ *
+ * @param framesAvail Return the number of available frames
+ * @param xrunOccurred Return whether an xrun has occurred
+ */
+static PaError PaAlsaStream_WaitForFrames( PaAlsaStream *self, unsigned long *framesAvail, int *xrunOccurred )
{
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;
+ int pollPlayback = self->playback.pcm != NULL, pollCapture = self->capture.pcm != NULL;
+ int pollTimeout = self->pollTimeout;
+ int xrun = 0;
- assert( stream && frames );
+ assert( self );
+ assert( framesAvail );
- if( stream->pcm_capture )
+ if( !self->callbackMode )
{
- ENSURE( snd_pcm_mmap_begin( stream->pcm_capture, &areas, &stream->capture_offset, &captureFrames ),
- paUnanticipatedHostError );
+ /* In blocking mode we will only wait if necessary */
+ PA_ENSURE( PaAlsaStream_GetAvailableFrames( self, self->capture.pcm != NULL, self->playback.pcm != NULL,
+ framesAvail, &xrun ) );
+ if( xrun )
+ {
+ goto end;
+ }
- if( stream->capture_interleaved )
+ if( *framesAvail > 0 )
{
- buffer = ExtractAddress( areas, stream->capture_offset );
- PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor,
- 0, /* starting at channel 0 */
- buffer,
- 0 /* default numInputChannels */
- );
+ /* Mark pcms ready from poll */
+ if( self->capture.pcm )
+ self->capture.ready = 1;
+ if( self->playback.pcm )
+ self->playback.ready = 1;
+
+ goto end;
}
- else
- /* noninterleaved */
- for( i = 0; i < stream->capture_channels; ++i )
+ }
+
+ while( pollPlayback || pollCapture )
+ {
+ int totalFds = 0;
+ struct pollfd *capturePfds = NULL, *playbackPfds = NULL;
+
+ pthread_testcancel();
+
+ if( pollCapture )
+ {
+ capturePfds = self->pfds;
+ PA_ENSURE( PaAlsaStreamComponent_BeginPolling( &self->capture, capturePfds ) );
+ totalFds += self->capture.nfds;
+ }
+ if( pollPlayback )
+ {
+ playbackPfds = self->pfds + (self->capture.pcm ? self->capture.nfds : 0);
+ PA_ENSURE( PaAlsaStreamComponent_BeginPolling( &self->playback, playbackPfds ) );
+ totalFds += self->playback.nfds;
+ }
+
+ if( poll( self->pfds, totalFds, pollTimeout ) < 0 )
+ {
+ /* XXX: Depend on preprocessor condition? */
+ if( errno == EINTR ) { /* gdb */
+ continue;
+ }
+
+ /* TODO: Add macro for checking system calls */
+ PA_ENSURE( paInternalError );
+ }
+
+ /* check the return status of our pfds */
+ if( pollCapture )
+ {
+ PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->capture, capturePfds, &pollCapture, &xrun ) );
+ }
+ if( pollPlayback )
+ {
+ PA_ENSURE( PaAlsaStreamComponent_EndPolling( &self->playback, playbackPfds, &pollPlayback, &xrun ) );
+ }
+ if( xrun )
+ {
+ break;
+ }
+
+ /* @concern FullDuplex If only one of two pcms is ready we may want to compromise between the two.
+ * If there is less than half a period's worth of samples left of frames in the other pcm's buffer we will
+ * stop polling.
+ */
+ if( self->capture.pcm && self->playback.pcm )
+ {
+ if( pollCapture && !pollPlayback )
+ {
+ PA_ENSURE( ContinuePoll( self, StreamDirection_In, &pollTimeout, &pollCapture ) );
+ }
+ else if( pollPlayback && !pollCapture )
{
- area = &areas[i];
- buffer = ExtractAddress( area, stream->capture_offset );
- PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor,
- i,
- buffer );
+ PA_ENSURE( ContinuePoll( self, StreamDirection_Out, &pollTimeout, &pollPlayback ) );
}
+ }
}
- if( stream->pcm_playback )
+ if( !xrun )
{
- ENSURE( snd_pcm_mmap_begin( stream->pcm_playback, &areas, &stream->playback_offset, &playbackFrames ),
- paUnanticipatedHostError );
+ /* Get the number of available frames for the pcms that are marked ready.
+ * @concern FullDuplex If only one direction is marked ready (from poll), the number of frames available for
+ * the other direction is returned. This under the assumption that input is dropped earlier if paNeverDropInput
+ * is not specified.
+ */
+ int captureReady = self->capture.pcm ? self->capture.ready : 0,
+ playbackReady = self->playback.pcm ? self->playback.ready : 0;
+ PA_ENSURE( PaAlsaStream_GetAvailableFrames( self, captureReady, playbackReady, framesAvail, &xrun ) );
- if( stream->playback_interleaved )
+ if( self->capture.pcm && self->playback.pcm )
{
- 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 )
+ if( !self->playback.ready && !self->neverDropInput )
{
- area = &areas[i];
- buffer = ExtractAddress( area, stream->playback_offset );
- PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor,
- i,
- buffer );
+ /* TODO: Drop input */
}
+ }
+ }
+
+end:
+error:
+ if( xrun )
+ {
+ /* Recover from the xrun state */
+ PA_ENSURE( PaAlsaStream_HandleXrun( self ) );
+ *framesAvail = 0;
}
+ *xrunOccurred = xrun;
+
+ return result;
+}
+
+/** Register per-channel ALSA buffer information with buffer processor.
+ *
+ * Mmapped buffer space is acquired from ALSA, and registered with the buffer processor. Differences between the
+ * number of host and user channels is taken into account.
+ *
+ * @param numFrames On entrance the number of requested frames, on exit the number of contiguously accessible frames.
+ */
+static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent *self, PaUtilBufferProcessor *bp,
+ unsigned long *numFrames, int *xrun )
+{
+ PaError result = paNoError;
+ const snd_pcm_channel_area_t *areas, *area;
+ void (*setChannel)(PaUtilBufferProcessor *, unsigned int, void *, unsigned int) =
+ StreamDirection_In == self->streamDir ? PaUtil_SetInputChannel : PaUtil_SetOutputChannel;
+ unsigned char *buffer, *p;
+ int i;
+ unsigned long framesAvail;
- if( alignFrames )
+ /* This _must_ be called before mmap_begin */
+ PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( self, &framesAvail, xrun ) );
+ if( *xrun )
{
- playbackFrames -= (playbackFrames % stream->frames_per_period);
- captureFrames -= (captureFrames % stream->frames_per_period);
+ *numFrames = 0;
+ goto end;
}
- commonFrames = MIN( captureFrames, playbackFrames );
- if( stream->pcm_playback && stream->pcm_capture )
+ ENSURE_( snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError );
+
+ if( self->hostInterleaved )
{
- /* 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 )
+ int swidth = snd_pcm_format_size( self->nativeFormat, 1 );
+
+ p = buffer = ExtractAddress( areas, self->offset );
+ for( i = 0; i < self->numUserChannels; ++i )
{
- if( !captureFrames ) /* Input underflow */
- commonFrames = playbackFrames; /* We still want output */
- else if( stream->neverDropInput ) /* Output underflow, but do not drop input */
- commonFrames = captureFrames;
+ /* We're setting the channels up to userChannels, but the stride will be hostChannels samples */
+ setChannel( bp, i, p, self->numHostChannels );
+ p += swidth;
}
- 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 )
+ else
{
- if( playbackFrames || !commonFrames ) /* We have output, or neither */
- PaUtil_SetOutputFrameCount( &stream->bufferProcessor, commonFrames );
- else /* We have output underflow, but keeping input data (paNeverDropInput) */
+ for( i = 0; i < self->numUserChannels; ++i )
{
- PaUtil_SetNoOutput( &stream->bufferProcessor );
+ area = areas + i;
+ buffer = ExtractAddress( area, self->offset );
+ setChannel( bp, i, buffer, 1 );
}
}
- /* 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;
+ /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */
+ self->channelAreas = (snd_pcm_channel_area_t *)areas;
end:
- return result;
error:
- goto end;
+ return result;
}
-/* Callback interface */
-
-static void OnExit( void *data )
+/** Initiate buffer processing.
+ *
+ * ALSA buffers are registered with the PA buffer processor and the buffer size (in frames) set.
+ *
+ * @concern FullDuplex If both directions are being processed, the minimum amount of frames for the two directions is
+ * calculated.
+ *
+ * @param numFrames On entrance the number of available frames, on exit the number of received frames
+ * @param xrunOccurred Return whether an xrun has occurred
+ */
+static PaError PaAlsaStream_SetUpBuffers( PaAlsaStream *self, unsigned long *numFrames, int *xrunOccurred )
{
- PaAlsaStream *stream = (PaAlsaStream *) data;
+ PaError result = paNoError;
+ unsigned long captureFrames = ULONG_MAX, playbackFrames = ULONG_MAX, commonFrames = 0;
+ int xrun = 0;
- assert( data );
+ /* Extract per-channel ALSA buffer pointers and register them with the buffer processor.
+ * It is possible that a direction is not marked ready however, because it is out of sync with the other.
+ */
+ if( self->capture.pcm && self->capture.ready )
+ {
+ captureFrames = *numFrames;
+ PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->capture, &self->bufferProcessor, &captureFrames,
+ &xrun ) );
+ }
+ if( self->playback.pcm && self->playback.ready )
+ {
+ playbackFrames = *numFrames;
+ PA_ENSURE( PaAlsaStreamComponent_RegisterChannels( &self->playback, &self->bufferProcessor, &playbackFrames,
+ &xrun ) );
+ }
+ if( xrun )
+ {
+ /* Nothing more to do */
+ assert( 0 == commonFrames );
+ goto end;
+ }
- PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer );
+ commonFrames = PA_MIN( captureFrames, playbackFrames );
+ assert( commonFrames <= *numFrames );
- stream->callback_finished = 1; /* Let the outside world know stream was stopped in callback */
- AlsaStop( stream, stream->callbackAbort );
- stream->callbackAbort = 0; /* Clear state */
+ /* Inform PortAudio of the number of frames we got.
+ * @concern FullDuplex 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( self->capture.pcm )
+ {
+ if( self->capture.ready )
+ {
+ PaUtil_SetInputFrameCount( &self->bufferProcessor, commonFrames );
+ }
+ else
+ {
+ /* We have input underflow */
+ PaUtil_SetNoInput( &self->bufferProcessor );
+ }
+ }
+ if( self->playback.pcm )
+ {
+ if( self->playback.ready )
+ {
+ PaUtil_SetOutputFrameCount( &self->bufferProcessor, commonFrames );
+ }
+ else
+ {
+ /* We have output underflow, but keeping input data (paNeverDropInput) */
+ /* assert( self->neverDropInput ); */
+ PaUtil_SetNoOutput( &self->bufferProcessor );
+ }
+ }
- PA_DEBUG(( "OnExit: Stoppage\n" ));
+end:
+ *numFrames = commonFrames;
+error:
+ if( xrun )
+ {
+ PA_ENSURE( PaAlsaStream_HandleXrun( self ) );
+ *numFrames = 0;
+ }
+ *xrunOccurred = xrun;
- /* Eventually notify user all buffers have played */
- if( stream->streamRepresentation.streamFinishedCallback )
- stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
- stream->isActive = 0;
+ return result;
}
-/* \brief Callback thread's function
+/** 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.
+ * Roughly, the workflow can be described in the following way: The number of available frames that can be processed
+ * directly is obtained from ALSA, we then request as much directly accessible memory as possible within this amount
+ * from ALSA. The buffer memory is registered with the PA buffer processor and processing is carried out with
+ * PaUtil_EndBufferProcessing. Finally, the number of processed frames is reported to ALSA. The processing can
+ * happen in several iterations untill we have consumed the known number of available frames (or an xrun is detected).
*/
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;
+ PaStreamCallbackTimeInfo timeInfo = {0, 0, 0};
+ snd_pcm_sframes_t startThreshold = 0;
+ int callbackResult = paContinue;
+ PaStreamCallbackFlags cbFlags = 0; /* We might want to keep state across iterations */
+ int streamStarted = 0;
- assert( userData );
+ assert( stream );
- /* Allocation should happen here, once per iteration is no good */
- snd_pcm_status_alloca( &capture_status );
- snd_pcm_status_alloca( &playback_status );
+ callbackThread_ = pthread_self();
+ /* Execute OnExit when exiting */
+ pthread_cleanup_push( &OnExit, stream );
- pthread_cleanup_push( &OnExit, stream ); /* Execute OnExit when exiting */
+ /* Not implemented */
+ assert( !stream->primeBuffers );
- if( startThreshold <= 0 )
+ /* @concern StreamStart If the output is being primed the output pcm needs to be prepared, otherwise the
+ * stream is started immediately. The latter involves signaling the waiting main thread.
+ */
+ if( stream->primeBuffers )
{
- 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 );
+ snd_pcm_sframes_t avail;
+
+ if( stream->playback.pcm )
+ ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError );
+ if( stream->capture.pcm && !stream->pcmsSynced )
+ ENSURE_( snd_pcm_prepare( stream->capture.pcm ), paUnanticipatedHostError );
+
+ /* We can't be certain that the whole ring buffer is available for priming, but there should be
+ * at least one period */
+ avail = snd_pcm_avail_update( stream->playback.pcm );
+ startThreshold = avail - (avail % stream->playback.framesPerBuffer);
+ assert( startThreshold >= stream->playback.framesPerBuffer );
}
- else /* Priming output? Prepare first */
+ else
{
- 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 );
+ ASSERT_CALL_( pthread_mutex_lock( &stream->startMtx ), 0 );
+ PA_ENSURE( AlsaStart( stream, 0 ) ); /* Buffer will be zeroed */
+ ASSERT_CALL_( pthread_cond_signal( &stream->startCond ), 0 );
+ ASSERT_CALL_( pthread_mutex_unlock( &stream->startMtx ), 0 );
+
+ streamStarted = 1;
}
while( 1 )
{
- PaError callbackResult;
- PaStreamCallbackTimeInfo timeInfo = {0,0,0};
- PaStreamCallbackFlags cbFlags = 0;
+ unsigned long framesAvail, framesGot;
+ int xrun = 0;
pthread_testcancel();
+ /* @concern StreamStop if the main thread has requested a stop and the stream has not been effectively
+ * stopped we signal this condition by modifying callbackResult (we'll want to flush buffered output).
+ */
+ if( stream->callbackStop && paContinue == callbackResult )
{
- /* 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;
- }
+ PA_DEBUG(( "Setting callbackResult to paComplete\n" ));
+ callbackResult = paComplete;
}
- /* Set callback flags *after* one of these has been detected */
- if( stream->underrun != 0.0 )
+ if( paContinue != callbackResult )
{
- cbFlags |= paOutputUnderflow;
- stream->underrun = 0.0;
+ stream->callbackAbort = (paAbort == callbackResult);
+ if( stream->callbackAbort ||
+ /** @concern BlockAdaption Go on if adaption buffers are empty */
+ PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
+ goto end;
+
+ PA_DEBUG(( "%s: Flushing buffer processor\n", __FUNCTION__ ));
+ /* There is still buffered output that needs to be processed */
}
- if( stream->overrun != 0.0 )
+
+ /* Wait for data to become available, this comes down to polling the ALSA file descriptors untill we have
+ * a number of available frames.
+ */
+ PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) );
+ if( xrun )
{
- cbFlags |= paInputOverflow;
- stream->overrun = 0.0;
+ assert( 0 == framesAvail );
+ continue;
+
+ /* XXX: Report xruns to the user? A situation is conceivable where the callback is never invoked due
+ * to constant xruns, it might be desirable to notify the user of this.
+ */
}
- PA_ENSURE( Wait( stream, &framesAvail ) );
+ /* Consume buffer space. Once we have a number of frames available for consumption we must retrieve the
+ * mmapped buffers from ALSA, this is contiguously accessible memory however, so we may receive smaller
+ * portions at a time than is available as a whole. Therefore we should be prepared to process several
+ * chunks successively. The buffers are passed to the PA buffer processor.
+ */
while( framesAvail > 0 )
{
+ xrun = 0;
+
pthread_testcancel();
- /* Priming output */
- if( startThreshold > 0 )
+ /** @concern Xruns Under/overflows are to be reported to the callback */
+ if( stream->underrun > 0.0 )
{
- PA_DEBUG(( "CallbackThreadFunc: Priming\n" ));
- cbFlags |= paPrimingOutput;
- framesAvail = MIN( (int)framesAvail, (int)startThreshold );
+ cbFlags |= paOutputUnderflow;
+ stream->underrun = 0.0;
}
-
- /* 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->overrun > 0.0 )
+ {
+ cbFlags |= paInputOverflow;
+ stream->overrun = 0.0;
+ }
+ if( stream->capture.pcm && stream->playback.pcm )
{
- if( !stream->captureAvail )
+ /** @concern FullDuplex It's possible that only one direction is being processed to avoid an
+ * under- or overflow, this should be reported correspondingly */
+ if( !stream->capture.ready )
{
cbFlags |= paInputUnderflow;
- PA_DEBUG(( "Input underflow\n" ));
+ PA_DEBUG(( "%s: Input underflow\n", __FUNCTION__ ));
}
- if( !stream->playbackAvail )
+ else if( !stream->playback.ready )
{
- 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" ));
- }
+ cbFlags |= paOutputOverflow;
+ PA_DEBUG(( "%s: Output overflow\n", __FUNCTION__ ));
}
}
- PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
-
- callbackResult = paContinue;
+ CallbackUpdate( &stream->threading );
+ CalculateTimeInfo( stream, &timeInfo );
+ PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, cbFlags );
+ cbFlags = 0;
- CallbackUpdate( &stream->threading ); /* Report to watchdog */
- /* this calls the callback */
- framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor,
- &callbackResult );
- PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed );
+ /* CPU load measurement should include processing activivity external to the stream callback */
+ PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
- /* 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 );
+ framesGot = framesAvail;
+ PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) );
+ framesAvail -= framesGot;
- /* 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 )
+ if( framesGot > 0 )
{
- int res = snd_pcm_mmap_commit( stream->pcm_playback, stream->playback_offset, stream->playbackAvail );
+ assert( !xrun );
- /* Non-fatal error? Terminate loop (go back to polling for frames) */
- if( res == -EPIPE || res == -ESTRPIPE )
- framesAvail = 0;
- else
- ENSURE( res, paUnanticipatedHostError );
+ PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult );
+ PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) );
}
+ PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesGot );
- /* If threshold for starting stream specified (priming buffer), decrement and compare */
- if( startThreshold > 0 )
+ if( framesGot == 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( !xrun )
+ PA_DEBUG(( "%s: Received less frames than reported from ALSA!\n", __FUNCTION__ ));
- if( callbackResult != paContinue )
+ /* Go back to polling for more frames */
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;
-
+ if( paContinue != callbackResult )
+ break;
}
}
- /* 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. */
+ /* Match pthread_cleanup_push */
pthread_cleanup_pop( 1 );
end:
@@ -2592,197 +3074,175 @@ error:
/* Blocking interface */
-static PaError ReadStream( PaStream* s,
- void *buffer,
- unsigned long frames )
+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;
+ unsigned long framesGot, framesAvail;
void *userBuffer;
- int i;
- snd_pcm_t *save;
+ snd_pcm_t *save = stream->playback.pcm;
assert( stream );
- /* Disregard playback */
- save = stream->pcm_playback;
- stream->pcm_playback = NULL;
-
- if( !stream->pcm_capture )
- PA_ENSURE( paCanNotReadFromAnOutputOnlyStream );
+ PA_UNLESS( stream->capture.pcm, paCanNotReadFromAnOutputOnlyStream );
+ /* Disregard playback */
+ stream->playback.pcm = NULL;
- if( stream->overrun )
+ if( stream->overrun > 0. )
{
result = paInputOverflowed;
stream->overrun = 0.0;
}
- if( stream->bufferProcessor.userInputIsInterleaved )
+ if( stream->capture.userInterleaved )
userBuffer = buffer;
- else /* Copy channels into local array */
+ else
{
- 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];
+ /* Copy channels into local array */
+ userBuffer = stream->capture.userBuffers;
+ memcpy( userBuffer, buffer, sizeof (void *) * stream->capture.numUserChannels );
}
/* Start stream if in prepared state */
- if( snd_pcm_state( stream->pcm_capture ) == SND_PCM_STATE_PREPARED )
+ if( snd_pcm_state( stream->capture.pcm ) == SND_PCM_STATE_PREPARED )
{
- ENSURE( snd_pcm_start( stream->pcm_capture ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_start( stream->capture.pcm ), 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;
+ int xrun = 0;
+ PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) );
+ framesGot = PA_MIN( framesAvail, frames );
- 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;
+ PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) );
+ if( framesGot > 0 )
+ {
+ framesGot = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesGot );
+ PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) );
+ frames -= framesGot;
+ }
}
end:
- stream->pcm_playback = save;
+ stream->playback.pcm = save;
return result;
error:
goto end;
}
-static PaError WriteStream( PaStream* s,
- const void *buffer,
- unsigned long frames )
+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;
+ snd_pcm_t *save = stream->capture.pcm;
- /* Disregard capture */
- save = stream->pcm_capture;
- stream->pcm_capture = NULL;
-
assert( stream );
- if( !stream->pcm_playback )
- PA_ENSURE( paCanNotWriteToAnInputOnlyStream );
- if( stream->underrun )
+ PA_UNLESS( stream->playback.pcm, paCanNotWriteToAnInputOnlyStream );
+
+ /* Disregard capture */
+ stream->capture.pcm = NULL;
+
+ if( stream->underrun > 0. )
{
result = paOutputUnderflowed;
stream->underrun = 0.0;
}
- if( stream->bufferProcessor.userOutputIsInterleaved )
+ if( stream->playback.userInterleaved )
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];
+ userBuffer = stream->playback.userBuffers;
+ memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->playback.numUserChannels );
}
while( frames > 0 )
{
+ int xrun = 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 );
+ PA_ENSURE( PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun ) );
+ framesGot = PA_MIN( framesAvail, frames );
- frames -= framesGot;
+ PA_ENSURE( PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun ) );
+ if( framesGot > 0 )
+ {
+ framesGot = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, framesGot );
+ PA_ENSURE( PaAlsaStream_EndProcessing( stream, framesGot, &xrun ) );
+ frames -= framesGot;
+ }
/* Frames residing in buffer */
PA_ENSURE( err = GetStreamWriteAvailable( stream ) );
framesAvail = err;
- hwAvail = stream->playbackBufferSize - framesAvail;
+ hwAvail = stream->playback.bufferSize - 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 )
+ if( snd_pcm_state( stream->playback.pcm ) == SND_PCM_STATE_PREPARED &&
+ hwAvail >= stream->playback.framesPerBuffer )
{
- ENSURE( snd_pcm_start( stream->pcm_playback ), paUnanticipatedHostError );
+ ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError );
}
}
end:
- stream->pcm_capture = save;
+ stream->capture.pcm = 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 );
+ unsigned long avail;
+ int xrun;
- if( avail < 0 )
+ PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->capture, &avail, &xrun ) );
+ if( xrun )
{
- if( avail == -EPIPE )
- {
- PA_ENSURE( HandleXrun( stream ) );
- avail = snd_pcm_avail_update( stream->pcm_capture );
- }
-
- if( avail == -EPIPE )
+ PA_ENSURE( PaAlsaStream_HandleXrun( stream ) );
+ PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->capture, &avail, &xrun ) );
+ if( xrun )
PA_ENSURE( paInputOverflowed );
- ENSURE( avail, paUnanticipatedHostError );
}
- return avail;
+ return (signed long)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 );
+ unsigned long avail;
+ int xrun;
- if( avail < 0 )
+ PA_ENSURE( PaAlsaStreamComponent_GetAvailableFrames( &stream->playback, &avail, &xrun ) );
+ if( xrun )
{
- if( avail == -EPIPE )
- {
- PA_ENSURE( HandleXrun( stream ) );
- avail = snd_pcm_avail_update( stream->pcm_playback );
- }
+ snd_pcm_sframes_t savail;
+
+ PA_ENSURE( PaAlsaStream_HandleXrun( stream ) );
+ savail = snd_pcm_avail_update( stream->playback.pcm );
+
+ /* savail should not contain -EPIPE now, since PaAlsaStream_HandleXrun will only prepare the pcm */
+ ENSURE_( savail, paUnanticipatedHostError );
- /* avail should not contain -EPIPE now, since HandleXrun will only prepare the pcm */
- ENSURE( avail, paUnanticipatedHostError );
+ avail = (unsigned long) savail;
}
- return avail;
+ return (signed long)avail;
error:
return result;
diff --git a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h
index 46dd0790..e6f44b16 100644
--- a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h
+++ b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h
@@ -1,6 +1,42 @@
#ifndef PA_LINUX_ALSA_H
#define PA_LINUX_ALSA_H
+/*
+ * $Id: pa_linux_alsa.h,v 1.1.2.12 2004/09/25 14:15:25 aknudsen Exp $
+ * PortAudio Portable Real-Time Audio Library
+ * ALSA-specific extensions
+ *
+ * Copyright (c) 1999-2000 Ross Bencina and 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.
+ *
+ */
+
+/** @file
+ * ALSA-specific PortAudio API extension header file.
+ */
+
#ifdef __cplusplus
extern "C" {
#endif