]> Repos - portaudio/commitdiff
alsa:
authordmitrykos <dmitrykos@0f58301d-fd10-0410-b4af-bbb618454e57>
Fri, 30 Jul 2010 10:44:18 +0000 (10:44 +0000)
committerdmitrykos <dmitrykos@0f58301d-fd10-0410-b4af-bbb618454e57>
Fri, 30 Jul 2010 10:44:18 +0000 (10:44 +0000)
 - 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

index 906d26d29c53b0274ec04dc4d526e7883917ea6d..90bf7a0f365424d69504585e2fd18b89772f797c 100644 (file)
@@ -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 */