diff options
Diffstat (limited to 'pd/portaudio/src/hostapi/alsa')
-rw-r--r-- | pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c | 250 |
1 files changed, 190 insertions, 60 deletions
diff --git a/pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c b/pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c index 06b17ac1..6b4a7b9f 100644 --- a/pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c +++ b/pd/portaudio/src/hostapi/alsa/pa_linux_alsa.c @@ -1,11 +1,12 @@ /* - * $Id: pa_linux_alsa.c 1236 2007-06-24 20:39:26Z aknudsen $ + * $Id: pa_linux_alsa.c 1415 2009-06-03 18:57:56Z aknudsen $ * PortAudio Portable Real-Time Audio Library * Latest Version at: http://www.portaudio.com * ALSA implementation by Joshua Haberman and Arve Knudsen * * Copyright (c) 2002 Joshua Haberman <joshua@haberman.com> - * Copyright (c) 2005-2006 Arve Knudsen <aknuds-1@broadpark.no> + * Copyright (c) 2005-2009 Arve Knudsen <arve.knudsen@gmail.com> + * Copyright (c) 2008 Kevin Kofler <kevin.kofler@chello.at> * * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 1999-2002 Ross Bencina, Phil Burk @@ -62,6 +63,7 @@ #include <sys/mman.h> #include <signal.h> /* For sig_atomic_t */ +#include "portaudio.h" #include "pa_util.h" #include "pa_unix_util.h" #include "pa_allocation.h" @@ -98,6 +100,7 @@ static int aErr_; /* Used with ENSURE_ */ static int numPeriods_ = 4; +static int busyRetries_ = 100; int PaAlsa_SetNumPeriods( int numPeriods ) { @@ -117,6 +120,8 @@ typedef struct unsigned long framesPerBuffer; int numUserChannels, numHostChannels; int userInterleaved, hostInterleaved; + int canMmap; + void *nonMmapBuffer; PaDeviceIndex device; /* Keep the device index */ snd_pcm_t *pcm; @@ -320,7 +325,7 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) * and a suitable result returned. The device is closed before returning. */ static PaError GropeDevice( snd_pcm_t* pcm, int isPlug, StreamDirection mode, int openBlocking, - PaAlsaDeviceInfo* devInfo, int* canMmap ) + PaAlsaDeviceInfo* devInfo ) { PaError result = paNoError; snd_pcm_hw_params_t *hwParams; @@ -353,9 +358,6 @@ static PaError GropeDevice( snd_pcm_t* pcm, int isPlug, StreamDirection mode, in snd_pcm_hw_params_alloca( &hwParams ); snd_pcm_hw_params_any( pcm, hwParams ); - *canMmap = snd_pcm_hw_params_test_access( pcm, hwParams, SND_PCM_ACCESS_MMAP_INTERLEAVED ) >= 0 || - snd_pcm_hw_params_test_access( pcm, hwParams, SND_PCM_ACCESS_MMAP_NONINTERLEAVED ) >= 0; - if( defaultSr >= 0 ) { /* Could be that the device opened in one mode supports samplerates that the other mode wont have, @@ -538,7 +540,7 @@ static int IgnorePlugin( const char *pluginId ) **/ static int OpenPcm( snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode, int waitOnBusy ) { - int tries = 0, maxTries = waitOnBusy ? 100 : 0; + int tries = 0, maxTries = waitOnBusy ? busyRetries_ : 0; int ret = snd_pcm_open( pcmp, name, stream, mode ); for( tries = 0; tries < maxTries && -EBUSY == ret; ++tries ) { @@ -565,7 +567,6 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d PaError result = 0; PaDeviceInfo *baseDeviceInfo = &devInfo->baseDeviceInfo; snd_pcm_t *pcm; - int canMmap = -1; PaUtilHostApiRepresentation *baseApi = &alsaApi->baseHostApiRep; /* Zero fields */ @@ -579,8 +580,7 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d OpenPcm( &pcm, deviceName->alsaName, SND_PCM_STREAM_CAPTURE, blocking, 0 ) >= 0 ) { - if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_In, blocking, devInfo, - &canMmap ) != paNoError ) + if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_In, blocking, devInfo ) != paNoError ) { /* Error */ PA_DEBUG(("%s: Failed groping %s for capture\n", __FUNCTION__, deviceName->alsaName)); @@ -593,8 +593,7 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d OpenPcm( &pcm, deviceName->alsaName, SND_PCM_STREAM_PLAYBACK, blocking, 0 ) >= 0 ) { - if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_Out, blocking, devInfo, - &canMmap ) != paNoError ) + if( GropeDevice( pcm, deviceName->isPlug, StreamDirection_Out, blocking, devInfo ) != paNoError ) { /* Error */ PA_DEBUG(("%s: Failed groping %s for playback\n", __FUNCTION__, deviceName->alsaName)); @@ -602,12 +601,6 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d } } - if( 0 == canMmap ) - { - PA_DEBUG(("%s: Device %s doesn't support mmap\n", __FUNCTION__, deviceName->alsaName)); - goto end; - } - baseDeviceInfo->structVersion = 2; baseDeviceInfo->hostApi = alsaApi->hostApiIndex; baseDeviceInfo->name = deviceName->name; @@ -620,8 +613,12 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d if( baseDeviceInfo->maxInputChannels > 0 || baseDeviceInfo->maxOutputChannels > 0 ) { /* Make device default if there isn't already one or it is the ALSA "default" device */ - if( baseApi->info.defaultInputDevice == paNoDevice && baseDeviceInfo->maxInputChannels > 0 ) + if( (baseApi->info.defaultInputDevice == paNoDevice || !strcmp(deviceName->alsaName, + "default" )) && baseDeviceInfo->maxInputChannels > 0 ) + { baseApi->info.defaultInputDevice = *devIdx; + PA_DEBUG(("Default input device: %s\n", deviceName->name)); + } if( (baseApi->info.defaultOutputDevice == paNoDevice || !strcmp(deviceName->alsaName, "default" )) && baseDeviceInfo->maxOutputChannels > 0 ) { @@ -1192,6 +1189,8 @@ static PaError PaAlsaStreamComponent_Initialize( PaAlsaStreamComponent *self, Pa self->hostInterleaved = self->userInterleaved = !(userSampleFormat & paNonInterleaved); self->numUserChannels = params->channelCount; self->streamDir = streamDir; + self->canMmap = 0; + self->nonMmapBuffer = NULL; if( !callbackMode && !self->userInterleaved ) { @@ -1234,6 +1233,7 @@ static PaError PaAlsaStreamComponent_InitialConfigure( PaAlsaStreamComponent *se PaError result = paNoError; snd_pcm_access_t accessMode, alternateAccessMode; + snd_pcm_access_t rwAccessMode, alternateRwAccessMode; int dir = 0; snd_pcm_t *pcm = self->pcm; double sr = *sampleRate; @@ -1253,32 +1253,40 @@ static PaError PaAlsaStreamComponent_InitialConfigure( PaAlsaStreamComponent *se if( self->userInterleaved ) { accessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + rwAccessMode = SND_PCM_ACCESS_RW_INTERLEAVED; alternateAccessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + alternateRwAccessMode = SND_PCM_ACCESS_RW_NONINTERLEAVED; } else { accessMode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + rwAccessMode = SND_PCM_ACCESS_RW_NONINTERLEAVED; alternateAccessMode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + alternateRwAccessMode = SND_PCM_ACCESS_RW_INTERLEAVED; } /* If requested access mode fails, try alternate mode */ + self->canMmap = 1; if( snd_pcm_hw_params_set_access( pcm, hwParams, accessMode ) < 0 ) { - int err = 0; - if( (err = snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode )) < 0) + if( snd_pcm_hw_params_set_access( pcm, hwParams, rwAccessMode ) >= 0 ) + self->canMmap = 0; + else { - result = paUnanticipatedHostError; - if( -EINVAL == err ) - { - PaUtil_SetLastHostErrorInfo( paALSA, err, "PA ALSA requires that a device supports mmap access" ); - } - else + if( snd_pcm_hw_params_set_access( pcm, hwParams, alternateAccessMode ) < 0 ) { - PaUtil_SetLastHostErrorInfo( paALSA, err, snd_strerror( err ) ); + int err = 0; + if( (err = snd_pcm_hw_params_set_access( pcm, hwParams, alternateRwAccessMode )) >= 0) + self->canMmap = 0; + else + { + result = paUnanticipatedHostError; + PaUtil_SetLastHostErrorInfo( paALSA, err, snd_strerror( err ) ); + goto error; + } } - goto error; + /* Flip mode */ + self->hostInterleaved = !self->userInterleaved; } - /* Flip mode */ - self->hostInterleaved = !self->userInterleaved; } ENSURE_( snd_pcm_hw_params_set_format( pcm, hwParams, self->nativeFormat ), paUnanticipatedHostError ); @@ -1356,7 +1364,10 @@ static PaError PaAlsaStreamComponent_FinishConfigure( PaAlsaStreamComponent *sel ENSURE_( snd_pcm_sw_params_set_avail_min( self->pcm, swParams, self->framesPerBuffer ), paUnanticipatedHostError ); ENSURE_( snd_pcm_sw_params_set_xfer_align( self->pcm, swParams, 1 ), paUnanticipatedHostError ); - ENSURE_( snd_pcm_sw_params_set_tstamp_mode( self->pcm, swParams, SND_PCM_TSTAMP_MMAP ), paUnanticipatedHostError ); +#ifndef SND_PCM_TSTAMP_ENABLE /* old versions of ALSA called this something different */ +#define SND_PCM_TSTAMP_ENABLE SND_PCM_TSTAMP_MMAP +#endif + ENSURE_( snd_pcm_sw_params_set_tstamp_mode( self->pcm, swParams, SND_PCM_TSTAMP_ENABLE ), paUnanticipatedHostError ); /* Set the parameters! */ ENSURE_( snd_pcm_sw_params( self->pcm, swParams ), paUnanticipatedHostError ); @@ -1584,6 +1595,10 @@ static PaError PaAlsaStreamComponent_DetermineFramesPerBuffer( PaAlsaStreamCompo } } + /* non-mmap mode needs a reasonably-sized buffer or it'll stutter */ + if( !self->canMmap && framesPerHostBuffer < 2048 ) + framesPerHostBuffer = 2048; + assert( framesPerHostBuffer > 0 ); { snd_pcm_uframes_t min = 0, max = 0; @@ -1725,12 +1740,15 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double while( optimalPeriodSize >= periodSize ) { - if( snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, optimalPeriodSize, 0 ) < 0 ) - continue; - if( snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback, optimalPeriodSize, 0 ) >= 0 ) + if( snd_pcm_hw_params_test_period_size( self->capture.pcm, hwParamsCapture, optimalPeriodSize, 0 ) + >= 0 && snd_pcm_hw_params_test_period_size( self->playback.pcm, hwParamsPlayback, + optimalPeriodSize, 0 ) >= 0 ) + { break; + } optimalPeriodSize /= 2; } + if( optimalPeriodSize > periodSize ) periodSize = optimalPeriodSize; @@ -1823,12 +1841,13 @@ static PaError PaAlsaStream_DetermineFramesPerBuffer( PaAlsaStream* self, double PA_UNLESS( framesPerHostBuffer != 0, paInternalError ); self->maxFramesPerHostBuffer = framesPerHostBuffer; - if( !accurate ) + if( !self->playback.canMmap || !accurate ) { /* Don't know the exact size per host buffer */ *hostBufferSizeMode = paUtilBoundedHostBufferSize; /* Raise upper bound */ - ++self->maxFramesPerHostBuffer; + if( !accurate ) + ++self->maxFramesPerHostBuffer; } error: @@ -1987,11 +2006,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* Ok, buffer processor is initialized, now we can deduce it's latency */ if( numInputChannels > 0 ) - stream->streamRepresentation.streamInfo.inputLatency = inputLatency + PaUtil_GetBufferProcessorInputLatency( - &stream->bufferProcessor ); + stream->streamRepresentation.streamInfo.inputLatency = inputLatency + (PaTime)( + PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor ) / sampleRate); if( numOutputChannels > 0 ) - stream->streamRepresentation.streamInfo.outputLatency = outputLatency + PaUtil_GetBufferProcessorOutputLatency( - &stream->bufferProcessor ); + stream->streamRepresentation.streamInfo.outputLatency = outputLatency + (PaTime)( + PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor ) / sampleRate); *s = (PaStream*)stream; @@ -2051,9 +2070,11 @@ static PaError AlsaStart( PaAlsaStream *stream, int priming ) { /* Buffer isn't primed, so prepare and silence */ ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); - SilenceBuffer( stream ); + if( stream->playback.canMmap ) + SilenceBuffer( stream ); } - ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); + if( stream->playback.canMmap ) + ENSURE_( snd_pcm_start( stream->playback.pcm ), paUnanticipatedHostError ); } else ENSURE_( snd_pcm_prepare( stream->playback.pcm ), paUnanticipatedHostError ); @@ -2151,7 +2172,9 @@ error: static PaError AlsaStop( PaAlsaStream *stream, int abort ) { PaError result = paNoError; - /* XXX: Seems that draining the dmix device may trigger a race condition in ALSA */ + /* XXX: snd_pcm_drain tends to lock up, avoid it until we find out more */ + abort = 1; + /* if( stream->capture.pcm && !strcmp( Pa_GetDeviceInfo( stream->capture.device )->name, "dmix" ) ) { @@ -2162,6 +2185,7 @@ static PaError AlsaStop( PaAlsaStream *stream, int abort ) { abort = 1; } + */ if( abort ) { @@ -2379,6 +2403,7 @@ static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self ) snd_pcm_status_t *st; PaTime now = PaUtil_GetTime(); snd_timestamp_t t; + int errplayback = 0, errcapture = 0; snd_pcm_status_alloca( &st ); @@ -2389,6 +2414,7 @@ static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self ) { snd_pcm_status_get_trigger_tstamp( st, &t ); self->underrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + errplayback = snd_pcm_recover( self->playback.pcm, -EPIPE, 0 ); } } if( self->capture.pcm ) @@ -2398,10 +2424,12 @@ static PaError PaAlsaStream_HandleXrun( PaAlsaStream *self ) { snd_pcm_status_get_trigger_tstamp( st, &t ); self->overrun = now * 1000 - ((PaTime) t.tv_sec * 1000 + (PaTime) t.tv_usec / 1000); + errcapture = snd_pcm_recover( self->capture.pcm, -EPIPE, 0 ); } } - PA_ENSURE( AlsaRestart( self ) ); + if( errplayback || errcapture ) + PA_ENSURE( AlsaRestart( self ) ); end: return result; @@ -2552,7 +2580,7 @@ static void CalculateTimeInfo( PaAlsaStream *stream, PaStreamCallbackTimeInfo *t static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self, unsigned long numFrames, int *xrun ) { PaError result = paNoError; - int res; + int res = 0; /* @concern FullDuplex It is possible that only one direction is marked ready after polling, and processed * afterwards @@ -2560,7 +2588,34 @@ static PaError PaAlsaStreamComponent_EndProcessing( PaAlsaStreamComponent *self, if( !self->ready ) goto end; - res = snd_pcm_mmap_commit( self->pcm, self->offset, numFrames ); + if( !self->canMmap && StreamDirection_Out == self->streamDir ) + { + /* Play sound */ + if( self->hostInterleaved ) + res = snd_pcm_writei( self->pcm, self->nonMmapBuffer, numFrames ); + else + { + void *bufs[self->numHostChannels]; + int bufsize = snd_pcm_format_size( self->nativeFormat, self->framesPerBuffer + 1 ); + unsigned char *buffer = self->nonMmapBuffer; + int i; + for( i = 0; i < self->numHostChannels; ++i ) + { + bufs[i] = buffer; + buffer += bufsize; + } + res = snd_pcm_writen( self->pcm, bufs, numFrames ); + } + } + + if( self->canMmap ) + res = snd_pcm_mmap_commit( self->pcm, self->offset, numFrames ); + else + { + free( self->nonMmapBuffer ); + self->nonMmapBuffer = NULL; + } + if( res == -EPIPE || res == -ESTRPIPE ) { *xrun = 1; @@ -2600,7 +2655,7 @@ static PaError PaAlsaStreamComponent_DoChannelAdaption( PaAlsaStreamComponent *s if( self->hostInterleaved ) { int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); - unsigned char *buffer = ExtractAddress( self->channelAreas, self->offset ); + unsigned char *buffer = self->canMmap ? ExtractAddress( self->channelAreas, self->offset ) : self->nonMmapBuffer; /* Start after the last user channel */ p = buffer + self->numUserChannels * swidth; @@ -2980,13 +3035,23 @@ static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent* se goto end; } - ENSURE_( snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError ); + if( self->canMmap ) + { + ENSURE_( snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames ), paUnanticipatedHostError ); + /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */ + self->channelAreas = (snd_pcm_channel_area_t *)areas; + } + else + { + free( self->nonMmapBuffer ); + self->nonMmapBuffer = calloc( self->numHostChannels, snd_pcm_format_size( self->nativeFormat, self->framesPerBuffer + 1 ) ); + } if( self->hostInterleaved ) { int swidth = snd_pcm_format_size( self->nativeFormat, 1 ); - p = buffer = ExtractAddress( areas, self->offset ); + p = buffer = self->canMmap ? ExtractAddress( areas, self->offset ) : self->nonMmapBuffer; for( i = 0; i < self->numUserChannels; ++i ) { /* We're setting the channels up to userChannels, but the stride will be hostChannels samples */ @@ -2996,16 +3061,52 @@ static PaError PaAlsaStreamComponent_RegisterChannels( PaAlsaStreamComponent* se } else { - for( i = 0; i < self->numUserChannels; ++i ) + if( self->canMmap ) + for( i = 0; i < self->numUserChannels; ++i ) + { + area = areas + i; + buffer = ExtractAddress( area, self->offset ); + setChannel( bp, i, buffer, 1 ); + } + else { - area = areas + i; - buffer = ExtractAddress( area, self->offset ); - setChannel( bp, i, buffer, 1 ); + int bufsize = snd_pcm_format_size( self->nativeFormat, self->framesPerBuffer + 1 ); + buffer = self->nonMmapBuffer; + for( i = 0; i < self->numUserChannels; ++i ) + { + setChannel( bp, i, buffer, 1 ); + buffer += bufsize; + } } } - /* @concern ChannelAdaption Buffer address is recorded so we can do some channel adaption later */ - self->channelAreas = (snd_pcm_channel_area_t *)areas; + if( !self->canMmap && StreamDirection_In == self->streamDir ) + { + /* Read sound */ + int res; + if( self->hostInterleaved ) + res = snd_pcm_readi( self->pcm, self->nonMmapBuffer, *numFrames ); + else + { + void *bufs[self->numHostChannels]; + int bufsize = snd_pcm_format_size( self->nativeFormat, self->framesPerBuffer + 1 ); + unsigned char *buffer = self->nonMmapBuffer; + int i; + for( i = 0; i < self->numHostChannels; ++i ) + { + bufs[i] = buffer; + buffer += bufsize; + } + res = snd_pcm_readn( self->pcm, bufs, *numFrames ); + } + if( res == -EPIPE || res == -ESTRPIPE ) + { + *xrun = 1; + *numFrames = 0; + free( self->nonMmapBuffer ); + self->nonMmapBuffer = NULL; + } + } end: error: @@ -3519,10 +3620,31 @@ void PaAlsa_EnableWatchdog( PaStream *s, int enable ) } #endif +static PaError GetAlsaStreamPointer( PaStream* s, PaAlsaStream** stream ) +{ + PaError result = paNoError; + PaUtilHostApiRepresentation* hostApi; + PaAlsaHostApiRepresentation* alsaHostApi; + + PA_ENSURE( PaUtil_ValidateStreamPointer( s ) ); + PA_ENSURE( PaUtil_GetHostApiRepresentation( &hostApi, paALSA ) ); + alsaHostApi = (PaAlsaHostApiRepresentation*)hostApi; + + PA_UNLESS( PA_STREAM_REP( s )->streamInterface == &alsaHostApi->callbackStreamInterface + || PA_STREAM_REP( s )->streamInterface == &alsaHostApi->blockingStreamInterface, + paIncompatibleStreamHostApi ); + + *stream = (PaAlsaStream*)s; +error: + return paNoError; +} + PaError PaAlsa_GetStreamInputCard(PaStream* s, int* card) { - PaAlsaStream *stream = (PaAlsaStream *) s; - snd_pcm_info_t* pcmInfo; + PaAlsaStream *stream; PaError result = paNoError; + snd_pcm_info_t* pcmInfo; + + PA_ENSURE( GetAlsaStreamPointer( s, &stream ) ); /* XXX: More descriptive error? */ PA_UNLESS( stream->capture.pcm, paDeviceUnavailable ); @@ -3536,9 +3658,11 @@ error: } PaError PaAlsa_GetStreamOutputCard(PaStream* s, int* card) { - PaAlsaStream *stream = (PaAlsaStream *) s; - snd_pcm_info_t* pcmInfo; + PaAlsaStream *stream; PaError result = paNoError; + snd_pcm_info_t* pcmInfo; + + PA_ENSURE( GetAlsaStreamPointer( s, &stream ) ); /* XXX: More descriptive error? */ PA_UNLESS( stream->playback.pcm, paDeviceUnavailable ); @@ -3550,3 +3674,9 @@ PaError PaAlsa_GetStreamOutputCard(PaStream* s, int* card) { error: return result; } + +PaError PaAlsa_SetRetriesBusy( int retries ) +{ + busyRetries_ = retries; + return paNoError; +} |