From d9d12f6b825c7e950affd58e787a9e22a11c0217 Mon Sep 17 00:00:00 2001 From: dmitrykos Date: Fri, 30 Jul 2010 10:44:18 +0000 Subject: [PATCH] alsa: - improved host buffer size calculation: taking into account obligatory double buffering utilising formula (userFrames + MAX( userFrames, SR * suggestedLatency)), aligning host buffer with user buffer size, additional testing of host buffer size to avoid failure on snd_pcm_hw_params_set_period_size_near, additional logging for more convenient debugging --- src/hostapi/alsa/pa_linux_alsa.c | 240 +++++++++++++++++++++++++++---- 1 file changed, 213 insertions(+), 27 deletions(-) diff --git a/src/hostapi/alsa/pa_linux_alsa.c b/src/hostapi/alsa/pa_linux_alsa.c index 906d26d..90bf7a0 100644 --- a/src/hostapi/alsa/pa_linux_alsa.c +++ b/src/hostapi/alsa/pa_linux_alsa.c @@ -1477,9 +1477,9 @@ static int CalculatePollTimeout( const PaAlsaStream *stream, unsigned long frame /** Align value in backward direction. * * @param v: Value to align. - * @param align: Alignment value. + * @param align: Alignment. */ -static unsigned long ALIGN_BWD(unsigned long v, unsigned long align) +static unsigned long PaAlsa_AlignBackward(unsigned long v, unsigned long align) { return ((v - (align ? v % align : 0))); } @@ -1487,14 +1487,25 @@ static unsigned long ALIGN_BWD(unsigned long v, unsigned long align) /** Align value in forward direction. * * @param v: Value to align. - * @param align: Alignment value. + * @param align: Alignment. */ -static unsigned long ALIGN_FWD(unsigned long v, unsigned long align) +static unsigned long PaAlsa_AlignForward(unsigned long v, unsigned long align) { unsigned long remainder = (align ? (v % align) : 0); - if (remainder == 0) - return v; - return v + (align - remainder); + return (remainder != 0 ? v + (align - remainder) : v); +} + +/** Get size of host buffer maintained from the number of user frames, sample rate and suggested latency. Minimum double buffering + * is maintained to allow 100% CPU usage inside user callback. + * + * @param userFramesPerBuffer: User buffer size in number of frames. + * @param suggestedLatency: User provided desired latency. + * @param sampleRate: Sample rate. + */ +static unsigned long PaAlsa_GetFramesPerHostBuffer(unsigned long userFramesPerBuffer, PaTime suggestedLatency, double sampleRate) +{ + unsigned long frames = userFramesPerBuffer + PA_MAX( userFramesPerBuffer, (unsigned long)(suggestedLatency * sampleRate) ); + return frames; } /** Determine size per host buffer. @@ -1507,51 +1518,208 @@ static PaError PaAlsaStreamComponent_DetermineFramesPerBuffer( PaAlsaStreamCompo unsigned long framesPerUserBuffer, double sampleRate, snd_pcm_hw_params_t* hwParams, int* accurate ) { PaError result = paNoError; - unsigned long bufferSize = framesPerUserBuffer + params->suggestedLatency * sampleRate, framesPerHostBuffer; + unsigned long bufferSize, framesPerHostBuffer; int dir = 0; - PA_DEBUG(( "%s: frames per user-buffer = %lu\n", __FUNCTION__, framesPerUserBuffer )); - PA_DEBUG(( "%s: suggested latency = %f\n", __FUNCTION__, params->suggestedLatency )); - PA_DEBUG(( "%s: suggested host buffer size = %lu\n", __FUNCTION__, bufferSize )); + /* Calculate host buffer size */ + bufferSize = PaAlsa_GetFramesPerHostBuffer(framesPerUserBuffer, params->suggestedLatency, sampleRate); + + /* Log */ + PA_DEBUG(( "%s: user-buffer (frames) = %lu\n", __FUNCTION__, framesPerUserBuffer )); + PA_DEBUG(( "%s: user-buffer (sec) = %f\n", __FUNCTION__, (double)(framesPerUserBuffer / sampleRate) )); + PA_DEBUG(( "%s: suggested latency (sec) = %f\n", __FUNCTION__, params->suggestedLatency )); + PA_DEBUG(( "%s: suggested host buffer (frames) = %lu\n", __FUNCTION__, bufferSize )); + PA_DEBUG(( "%s: suggested host buffer (sec) = %f\n", __FUNCTION__, (double)(bufferSize / sampleRate) )); + +#ifdef PA_ALSA_USE_OBSOLETE_HOST_BUFFER_CALC + + if( framesPerUserBuffer != paFramesPerBufferUnspecified ) + { + /* Preferably the host buffer size should be a multiple of the user buffer size */ + + if( bufferSize > framesPerUserBuffer ) + { + snd_pcm_uframes_t remainder = bufferSize % framesPerUserBuffer; + if( remainder > framesPerUserBuffer / 2. ) + bufferSize += framesPerUserBuffer - remainder; + else + bufferSize -= remainder; + + assert( bufferSize % framesPerUserBuffer == 0 ); + } + else if( framesPerUserBuffer % bufferSize != 0 ) + { + /* Find a good compromise between user specified latency and buffer size */ + if( bufferSize > framesPerUserBuffer * .75 ) + { + bufferSize = framesPerUserBuffer; + } + else + { + snd_pcm_uframes_t newSz = framesPerUserBuffer; + while( newSz / 2 >= bufferSize ) + { + if( framesPerUserBuffer % (newSz / 2) != 0 ) + { + /* No use dividing any further */ + break; + } + newSz /= 2; + } + bufferSize = newSz; + } + + assert( framesPerUserBuffer % bufferSize == 0 ); + } + } + +#endif { - unsigned numPeriods = numPeriods_, maxPeriods = 0; + unsigned numPeriods = numPeriods_, maxPeriods = 0, minPeriods = numPeriods_; + /* It may be that the device only supports 2 periods for instance */ dir = 0; + ENSURE_( snd_pcm_hw_params_get_periods_min( hwParams, &minPeriods, &dir ), paUnanticipatedHostError ) ENSURE_( snd_pcm_hw_params_get_periods_max( hwParams, &maxPeriods, &dir ), paUnanticipatedHostError ); assert( maxPeriods > 1 ); - numPeriods = PA_MIN( maxPeriods, numPeriods ); - /* Align to 16 bytes for faster copying */ - framesPerHostBuffer = ALIGN_FWD((bufferSize / numPeriods), 16); + /* Clamp to min/max */ + numPeriods = PA_MIN(maxPeriods, PA_MAX(minPeriods, numPeriods)); + + PA_DEBUG(( "%s: periods min = %lu, max = %lu, req = %lu \n", __FUNCTION__, minPeriods, maxPeriods, numPeriods )); + +#ifndef PA_ALSA_USE_OBSOLETE_HOST_BUFFER_CALC + + /* Calculate period size */ + framesPerHostBuffer = (bufferSize / numPeriods); + + /* Align & test size */ + if( framesPerUserBuffer != paFramesPerBufferUnspecified ) + { + /* Align to user buffer size */ + framesPerHostBuffer = PaAlsa_AlignForward(framesPerHostBuffer, framesPerUserBuffer); + + /* Test (borrowed from older implementation) */ + if( framesPerHostBuffer < framesPerUserBuffer ) + { + assert( framesPerUserBuffer % framesPerHostBuffer == 0 ); + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 ) + { + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer * 2, 0 ) == 0 ) + framesPerHostBuffer *= 2; + else + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer / 2, 0 ) == 0 ) + framesPerHostBuffer /= 2; + } + } + else + { + assert( framesPerHostBuffer % framesPerUserBuffer == 0 ); + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 ) + { + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer + framesPerUserBuffer, 0 ) == 0 ) + framesPerHostBuffer += framesPerUserBuffer; + else + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer - framesPerUserBuffer, 0 ) == 0 ) + framesPerHostBuffer -= framesPerUserBuffer; + } + } + } +#endif + +#ifdef PA_ALSA_USE_OBSOLETE_HOST_BUFFER_CALC + + if( framesPerUserBuffer != paFramesPerBufferUnspecified ) + { + /* Try to get a power-of-two of the user buffer size. */ + framesPerHostBuffer = framesPerUserBuffer; + if( framesPerHostBuffer < bufferSize ) + { + while( bufferSize / framesPerHostBuffer > numPeriods ) + { + framesPerHostBuffer *= 2; + } + /* One extra period is preferrable to one less (should be more robust) */ + if( bufferSize / framesPerHostBuffer < numPeriods ) + { + framesPerHostBuffer /= 2; + } + } + else + { + while( bufferSize / framesPerHostBuffer < numPeriods ) + { + if( framesPerUserBuffer % (framesPerHostBuffer / 2) != 0 ) + { + /* Can't be divided any further */ + break; + } + framesPerHostBuffer /= 2; + } + } + + if( framesPerHostBuffer < framesPerUserBuffer ) + { + assert( framesPerUserBuffer % framesPerHostBuffer == 0 ); + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 ) + { + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer * 2, 0 ) == 0 ) + framesPerHostBuffer *= 2; + else if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer / 2, 0 ) == 0 ) + framesPerHostBuffer /= 2; + } + } + else + { + assert( framesPerHostBuffer % framesPerUserBuffer == 0 ); + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer, 0 ) < 0 ) + { + if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer + framesPerUserBuffer, 0 ) == 0 ) + framesPerHostBuffer += framesPerUserBuffer; + else if( snd_pcm_hw_params_test_period_size( self->pcm, hwParams, framesPerHostBuffer - framesPerUserBuffer, 0 ) == 0 ) + framesPerHostBuffer -= framesPerUserBuffer; + } + } + } + else + { + framesPerHostBuffer = bufferSize / numPeriods; + } + + /* non-mmap mode needs a reasonably-sized buffer or it'll stutter */ + if( !self->canMmap && framesPerHostBuffer < 2048 ) + framesPerHostBuffer = 2048; +#endif + PA_DEBUG(( "%s: suggested host buffer period = %lu \n", __FUNCTION__, framesPerHostBuffer )); } { + /* Get min/max period sizes and adjust our chosen */ snd_pcm_uframes_t min = 0, max = 0; ENSURE_( snd_pcm_hw_params_get_period_size_min( hwParams, &min, NULL ), paUnanticipatedHostError ); ENSURE_( snd_pcm_hw_params_get_period_size_max( hwParams, &max, NULL ), paUnanticipatedHostError ); if( framesPerHostBuffer < min ) { - PA_DEBUG(( "%s: The determined period size (%lu) is less than minimum (%lu)\n", __FUNCTION__, - framesPerHostBuffer, min )); + PA_DEBUG(( "%s: The determined period size (%lu) is less than minimum (%lu)\n", __FUNCTION__, framesPerHostBuffer, min )); framesPerHostBuffer = min; } - else if( framesPerHostBuffer > max ) + else + if( framesPerHostBuffer > max ) { - PA_DEBUG(( "%s: The determined period size (%lu) is greater than maximum (%lu)\n", __FUNCTION__, - framesPerHostBuffer, max )); + PA_DEBUG(( "%s: The determined period size (%lu) is greater than maximum (%lu)\n", __FUNCTION__, framesPerHostBuffer, max )); framesPerHostBuffer = max; } - PA_DEBUG(( "%s: device period minimum = %lu\n", __FUNCTION__, min )); - PA_DEBUG(( "%s: device period maximum = %lu\n", __FUNCTION__, max )); - PA_DEBUG(( "%s: host buffer period = %lu\n", __FUNCTION__, framesPerHostBuffer )); + PA_DEBUG(( "%s: device period minimum = %lu\n", __FUNCTION__, min )); + PA_DEBUG(( "%s: device period maximum = %lu\n", __FUNCTION__, max )); + PA_DEBUG(( "%s: host buffer period = %lu\n", __FUNCTION__, framesPerHostBuffer )); + PA_DEBUG(( "%s: host buffer period latency = %f\n", __FUNCTION__, (double)(framesPerHostBuffer / sampleRate) )); - assert( framesPerHostBuffer >= min && framesPerHostBuffer <= max ); + /* Try set period size */ dir = 0; - ENSURE_( snd_pcm_hw_params_set_period_size_near( self->pcm, hwParams, &framesPerHostBuffer, &dir ), - paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_set_period_size_near( self->pcm, hwParams, &framesPerHostBuffer, &dir ), paUnanticipatedHostError ); if( dir != 0 ) { PA_DEBUG(( "%s: The configured period size is non-integer.\n", __FUNCTION__, dir )); @@ -1944,6 +2112,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->streamRepresentation.streamInfo.outputLatency = outputLatency + (PaTime)( PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor ) / sampleRate); + PA_DEBUG(( "%s: Stream: framesPerBuffer = %lu, maxFramesPerHostBuffer = %lu, latency = i(%f)/o(%f), \n", __FUNCTION__, framesPerBuffer, stream->maxFramesPerHostBuffer, stream->streamRepresentation.streamInfo.inputLatency, stream->streamRepresentation.streamInfo.outputLatency)); + *s = (PaStream*)stream; return result; @@ -2273,6 +2443,7 @@ static double GetStreamCpuLoad( PaStream* s ) static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwParams, double sampleRate ) { + PaError result = paNoError; unsigned long approx = (unsigned long) sampleRate; int dir = 0; double fraction = sampleRate - approx; @@ -2290,7 +2461,22 @@ static int SetApproximateSampleRate( snd_pcm_t *pcm, snd_pcm_hw_params_t *hwPara dir = 1; } - return snd_pcm_hw_params_set_rate( pcm, hwParams, approx, dir ); + ENSURE_( snd_pcm_hw_params_set_rate( pcm, hwParams, approx, dir ), paUnanticipatedHostError ); + +end: + return result; + +error: + + /* Log */ + { + unsigned int _min = 0, _max = 0; int _dir = 0; + ENSURE_( snd_pcm_hw_params_get_rate_min( hwParams, &_min, &_dir ), paUnanticipatedHostError ); + ENSURE_( snd_pcm_hw_params_get_rate_max( hwParams, &_max, &_dir ), paUnanticipatedHostError ); + PA_DEBUG(( "%s: SR min = %d, max = %d, req = %lu\n", __FUNCTION__, _min, _max, approx )); + } + + goto end; } /* Return exact sample rate in param sampleRate */ -- 2.43.0