]> Repos - portaudio/commitdiff
pa_win_wmme.c fixed bug in stream info inputLatency see ticket #178. improved interpr...
authorrossb <rossb@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 28 Jul 2011 12:03:09 +0000 (12:03 +0000)
committerrossb <rossb@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 28 Jul 2011 12:03:09 +0000 (12:03 +0000)
src/hostapi/wmme/pa_win_wmme.c

index 813af503d566903687eda93c8a7df9520427e140..f22fbcbd749887dc68cf15b30c8d11b4b41568d1 100644 (file)
 #define PA_MME_USE_HIGH_DEFAULT_LATENCY_    (0)  /* For debugging glitches. */
 
 #if PA_MME_USE_HIGH_DEFAULT_LATENCY_
- #define PA_MME_WIN_9X_DEFAULT_LATENCY_                     (0.4)
- #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_               (4)
- #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_       (4)
- #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_       (4)
- #define PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_       (16)
- #define PA_MME_MAX_HOST_BUFFER_SECS_                                  (0.3)       /* Do not exceed unless user buffer exceeds */
- #define PA_MME_MAX_HOST_BUFFER_BYTES_                                 (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
+ #define PA_MME_WIN_9X_DEFAULT_LATENCY_                             (0.4)
+ #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_                       (4)
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_               (4)
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_               (4)
+ #define PA_MME_HOST_BUFFER_GRANULARITY_FRAMES_WHEN_UNSPECIFIED_       (16)
+ #define PA_MME_MAX_HOST_BUFFER_SECS_                                          (0.3)       /* Do not exceed unless user buffer exceeds */
+ #define PA_MME_MAX_HOST_BUFFER_BYTES_                                         (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
 #else
- #define PA_MME_WIN_9X_DEFAULT_LATENCY_                     (0.2)
- #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_               (2)
- #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_       (3)
- #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_       (2)
- #define PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_       (16)
- #define PA_MME_MAX_HOST_BUFFER_SECS_                                  (0.1)       /* Do not exceed unless user buffer exceeds */
- #define PA_MME_MAX_HOST_BUFFER_BYTES_                                 (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
+ #define PA_MME_WIN_9X_DEFAULT_LATENCY_                             (0.2)
+ #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_                       (2)
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_               (3)         /* always use at least 3 input buffers for full duplex */
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_               (2)
+ #define PA_MME_HOST_BUFFER_GRANULARITY_FRAMES_WHEN_UNSPECIFIED_       (16)
+ #define PA_MME_MAX_HOST_BUFFER_SECS_                                          (0.1)       /* Do not exceed unless user buffer exceeds */
+ #define PA_MME_MAX_HOST_BUFFER_BYTES_                                         (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
 #endif
 
 /* Use higher latency for NT because it is even worse at real-time
 #define PA_MME_WIN_NT_DEFAULT_LATENCY_      (PA_MME_WIN_9X_DEFAULT_LATENCY_ * 2)
 #define PA_MME_WIN_WDM_DEFAULT_LATENCY_     (PA_MME_WIN_9X_DEFAULT_LATENCY_)
 
+/* When client suggestedLatency could result in many host buffers, we aim to have around 8, 
+   based off Windows documentation that suggests that the kmixer uses 8 buffers. This choice
+   is somewhat arbitrary here, since we havn't observed significant stability degredation 
+   with using either more, or less buffers.     
+*/
+
+#define PA_MME_TARGET_HOST_BUFFER_COUNT_    8 
 
 #define PA_MME_MIN_TIMEOUT_MSEC_        (1000)
 
@@ -791,7 +798,9 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme
     MMRESULT mmresult;
     WAVEOUTCAPS woc;
     PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo;
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
     int wdmksDeviceOutputChannelCountIsKnown;
+#endif
 
     *success = 0;
 
@@ -1324,126 +1333,135 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 }
 
 
-
-static void SelectBufferSizeAndCount( unsigned long baseBufferSize,
-    unsigned long requestedLatency,
-    unsigned long baseBufferCount, unsigned long minimumBufferCount,
-    unsigned long maximumBufferSize, unsigned long *hostBufferSize,
-    unsigned long *hostBufferCount )
+static unsigned long ComputeHostBufferCountForFixedBufferSizeFrames(
+        unsigned long suggestedLatencyFrames,
+        unsigned long hostBufferSizeFrames,
+        unsigned long minimumBufferCount )
 {
-    unsigned long sizeMultiplier, bufferCount, latency;
-    unsigned long nextLatency, nextBufferSize;
-    int baseBufferSizeIsPowerOfTwo;
-    
-    sizeMultiplier = 1;
-    bufferCount = baseBufferCount;
+    /* Calculate the number of buffers of length hostFramesPerBuffer 
+       that fit in suggestedLatencyFrames, rounding up to the next integer.
 
-    /* count-1 below because latency is always determined by one less
-        than the total number of buffers.
+       The exact formula for round-up is:
+       ((suggestedLatencyFrames + (hostFramesPerBuffer - 1)) / hostFramesPerBuffer)
+       However we subtract 2 instead of 1 below to account for rounding errors.
+       This ensures we don't erroneously overallocate an extra buffer due to a one-sample rounding error.
     */
-    latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1);
 
-    if( latency > requestedLatency )
-    {
+    unsigned long resultBufferCount = ((suggestedLatencyFrames + (hostBufferSizeFrames - 2)) / hostBufferSizeFrames);
 
-        /* reduce number of buffers without falling below suggested latency */
-
-        nextLatency = (baseBufferSize * sizeMultiplier) * (bufferCount-2);
-        while( bufferCount > minimumBufferCount && nextLatency >= requestedLatency )
-        {
-            --bufferCount;
-            nextLatency = (baseBufferSize * sizeMultiplier) * (bufferCount-2);
-        }
+    /* We always need one extra buffer for processing while the rest are queued/playing.
+       i.e. latency is framesPerBuffer * (bufferCount - 1)
+    */
+    resultBufferCount += 1;
 
-    }else if( latency < requestedLatency ){
+    if( resultBufferCount < minimumBufferCount ) /* clamp to minimum buffer count */
+        resultBufferCount = minimumBufferCount;
 
-        baseBufferSizeIsPowerOfTwo = (! (baseBufferSize & (baseBufferSize - 1)));
-        if( baseBufferSizeIsPowerOfTwo ){
+    return resultBufferCount;
+}
 
-            /* double size of buffers without exceeding requestedLatency */
 
-            nextBufferSize = (baseBufferSize * (sizeMultiplier*2));
-            nextLatency = nextBufferSize * (bufferCount-1);
-            while( nextBufferSize <= maximumBufferSize
-                    && nextLatency < requestedLatency )
-            {
-                sizeMultiplier *= 2;
-                nextBufferSize = (baseBufferSize * (sizeMultiplier*2));
-                nextLatency = nextBufferSize * (bufferCount-1);
-            }   
+static PaError SelectHostBufferSizeFramesAndHostBufferCount(
+        unsigned long suggestedLatencyFrames,
+        unsigned long userFramesPerBuffer,
+        unsigned long minimumBufferCount,
+        unsigned long preferredMaximumBufferSizeFrames, /* try not to exceed this. for example, don't exceed when coalescing buffers */
+        unsigned long absoluteMaximumBufferSizeFrames,  /* never exceed this, a hard limit */
+        unsigned long *hostBufferSizeFrames,
+        unsigned long *hostBufferCount )
+{
+    if( userFramesPerBuffer == paFramesPerBufferUnspecified ){
 
-        }else{
+        *hostBufferSizeFrames = PA_MME_HOST_BUFFER_GRANULARITY_FRAMES_WHEN_UNSPECIFIED_;
 
-            /* increase size of buffers upto first excess of requestedLatency */
+    }else{
 
-            nextBufferSize = (baseBufferSize * (sizeMultiplier+1));
-            nextLatency = nextBufferSize * (bufferCount-1);
-            while( nextBufferSize <= maximumBufferSize
-                    && nextLatency < requestedLatency )
-            {
-                ++sizeMultiplier;
-                nextBufferSize = (baseBufferSize * (sizeMultiplier+1));
-                nextLatency = nextBufferSize * (bufferCount-1);
-            }
+        *hostBufferSizeFrames = userFramesPerBuffer;
 
-            if( nextLatency < requestedLatency )
-                ++sizeMultiplier;            
+        if( *hostBufferSizeFrames > absoluteMaximumBufferSizeFrames ){
+            /* user has requested a user buffer that's larger than what we can handle */
+            
+            /* @todo FIXME right now we allow the user to request an oversize host buffer,
+                even though elsewhere in the code there are suggestions that oversize buffers
+                can cause crashes with some drivers. */
+            /* return paBufferTooBig; */
         }
+    }
+                        
+    /* compute a host buffer count based on suggestedLatencyFrames and our granularity */
 
-        /* increase number of buffers until requestedLatency is reached */
+    *hostBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+            suggestedLatencyFrames, *hostBufferSizeFrames, minimumBufferCount );
 
-        latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1);
-        while( latency < requestedLatency )
-        {
-            ++bufferCount;
-            latency = (baseBufferSize * sizeMultiplier) * (bufferCount-1);
+    /* consider coalescing multiple user buffers into each host buffer */
+
+    if( *hostBufferCount >= PA_MME_TARGET_HOST_BUFFER_COUNT_ * 2 ){
+        
+        /* If there are too many host buffers we would like to coalesce 
+           them by packing an integer number of user buffers into each host buffer.
+           We try to coalesce such that hostBufferCount will lie between 
+           PA_MME_TARGET_HOST_BUFFER_COUNT_ and (PA_MME_TARGET_HOST_BUFFER_COUNT_*2)-1.
+           We limit coalescing to avoid exceeding either absoluteMaximumBufferSizeFrames and
+           preferredMaximumBufferSizeFrames. 
+        */
+
+        unsigned long maxCoalescedBufferSizeFrames = (absoluteMaximumBufferSizeFrames < preferredMaximumBufferSizeFrames) /* min of our limits */
+                    ? absoluteMaximumBufferSizeFrames
+                    : preferredMaximumBufferSizeFrames;
+
+        unsigned long maxUserBuffersPerHostBuffer = maxCoalescedBufferSizeFrames / *hostBufferSizeFrames; /* don't coalesce more than this */
+
+        /* we truncate here to compute a conservative value of numUserBuffersPerHostBuffer 
+            then we recompute hostBufferCount after coalescing.
+        */
+        unsigned long numUserBuffersPerHostBuffer = *hostBufferCount / PA_MME_TARGET_HOST_BUFFER_COUNT_;
+        if( numUserBuffersPerHostBuffer > maxUserBuffersPerHostBuffer )
+            numUserBuffersPerHostBuffer = maxUserBuffersPerHostBuffer;
+
+        if( numUserBuffersPerHostBuffer > 1 ){
+            *hostBufferSizeFrames *= numUserBuffersPerHostBuffer;
+
+            /* recompute hostBufferCount to approximate suggestedLatencyFrames now that hostBufferSizeFrames is larger */
+            *hostBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+                    suggestedLatencyFrames, *hostBufferSizeFrames, minimumBufferCount );
         }
     }
 
-    *hostBufferSize = baseBufferSize * sizeMultiplier;
-    *hostBufferCount = bufferCount;
+    return paNoError;
 }
 
 
-static void ReselectBufferCount( unsigned long bufferSize,
-    unsigned long requestedLatency,
-    unsigned long baseBufferCount, unsigned long minimumBufferCount,
-    unsigned long *hostBufferCount )
+static PaError CalculateMaxHostSampleFrameSizeBytes(
+        int channelCount,
+        PaSampleFormat hostSampleFormat,
+        const PaWinMmeStreamInfo *streamInfo,
+        int *hostSampleFrameSizeBytes )
 {
-    unsigned long bufferCount, latency;
-    unsigned long nextLatency;
-
-    bufferCount = baseBufferCount;
-
-    /* count-1 below because latency is always determined by one less
-        than the total number of buffers.
+    unsigned int i;
+    /* PA WMME streams may aggregate multiple WMME devices. When the stream addresses 
+       more than one device in a single direction, maxDeviceChannelCount is the maximum 
+       number of channels used by a single device.
     */
-    latency = bufferSize * (bufferCount-1);
-
-    if( latency > requestedLatency )
+    int maxDeviceChannelCount = channelCount;
+    int hostSampleSizeBytes = Pa_GetSampleSize( hostSampleFormat );
+    if( hostSampleSizeBytes < 0 )
     {
-        /* reduce number of buffers without falling below suggested latency */
+        return hostSampleSizeBytes; /* the value of hostSampleSize here is an error code, not a sample size */
+    }
 
-        nextLatency = bufferSize * (bufferCount-2);
-        while( bufferCount > minimumBufferCount && nextLatency >= requestedLatency )
+    if( streamInfo && ( streamInfo->flags & paWinMmeUseMultipleDevices ) )
+    {
+        maxDeviceChannelCount = streamInfo->devices[0].channelCount;
+        for( i=1; i< streamInfo->deviceCount; ++i )
         {
-            --bufferCount;
-            nextLatency = bufferSize * (bufferCount-2);
+            if( streamInfo->devices[i].channelCount > maxDeviceChannelCount )
+                maxDeviceChannelCount = streamInfo->devices[i].channelCount;
         }
-
-    }else if( latency < requestedLatency ){
-
-        /* increase number of buffers until requestedLatency is reached */
-
-        latency = bufferSize * (bufferCount-1);
-        while( latency < requestedLatency )
-        {
-            ++bufferCount;
-            latency = bufferSize * (bufferCount-1);
-        }                                                         
     }
 
-    *hostBufferCount = bufferCount;
+    *hostSampleFrameSizeBytes = hostSampleSizeBytes * maxDeviceChannelCount;
+
+    return paNoError;
 }
 
 
@@ -1456,48 +1474,26 @@ static PaError CalculateBufferSettings(
         unsigned long *hostFramesPerInputBuffer, unsigned long *hostInputBufferCount,
         unsigned long *hostFramesPerOutputBuffer, unsigned long *hostOutputBufferCount,
         int inputChannelCount, PaSampleFormat hostInputSampleFormat,
-        PaTime suggestedInputLatency, PaWinMmeStreamInfo *inputStreamInfo,
+        PaTime suggestedInputLatency, const PaWinMmeStreamInfo *inputStreamInfo,
         int outputChannelCount, PaSampleFormat hostOutputSampleFormat,
-        PaTime suggestedOutputLatency, PaWinMmeStreamInfo *outputStreamInfo,
+        PaTime suggestedOutputLatency, const PaWinMmeStreamInfo *outputStreamInfo,
         double sampleRate, unsigned long userFramesPerBuffer )
 {
     PaError result = paNoError;
-    int effectiveInputChannelCount, effectiveOutputChannelCount;
-    int hostInputFrameSize = 0;
-    unsigned int i;
     
-    if( inputChannelCount > 0 )
+    if( inputChannelCount > 0 ) /* stream has input */
     {
-        int hostInputSampleSize = Pa_GetSampleSize( hostInputSampleFormat );
-        if( hostInputSampleSize < 0 )
-        {
-            result = hostInputSampleSize; /* this is an error code */
+        int hostInputFrameSizeBytes;
+        result = CalculateMaxHostSampleFrameSizeBytes( 
+                inputChannelCount, hostInputSampleFormat, inputStreamInfo, &hostInputFrameSizeBytes );
+        if( result != paNoError )
             goto error;
-        }
-
-        if( inputStreamInfo
-                && ( inputStreamInfo->flags & paWinMmeUseMultipleDevices ) )
-        {
-            /* set effectiveInputChannelCount to the largest number of
-                channels on any one device.
-            */
-            effectiveInputChannelCount = 0;
-            for( i=0; i< inputStreamInfo->deviceCount; ++i )
-            {
-                if( inputStreamInfo->devices[i].channelCount > effectiveInputChannelCount )
-                    effectiveInputChannelCount = inputStreamInfo->devices[i].channelCount;
-            }
-        }
-        else
-        {
-            effectiveInputChannelCount = inputChannelCount;
-        }
-
-        hostInputFrameSize = hostInputSampleSize * effectiveInputChannelCount;
 
         if( inputStreamInfo
                 && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) )
         {
+            /* input - using low level latency parameters if provided */
+
             if( inputStreamInfo->bufferCount <= 0
                     || inputStreamInfo->framesPerBuffer <= 0 )
             {
@@ -1510,28 +1506,25 @@ static PaError CalculateBufferSettings(
         }
         else
         {
-            unsigned long hostBufferSizeBytes, hostBufferCount;
+            /* input - not using low level latency parameters, so compute 
+               hostFramesPerInputBuffer and hostInputBufferCount
+               based on userFramesPerBuffer and suggestedInputLatency. */
+
             unsigned long minimumBufferCount = (outputChannelCount > 0)
                     ? PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_
                     : PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_;
 
-            unsigned long maximumBufferSize = (long) ((PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate) * hostInputFrameSize);
-            if( maximumBufferSize > PA_MME_MAX_HOST_BUFFER_BYTES_ )
-                maximumBufferSize = PA_MME_MAX_HOST_BUFFER_BYTES_;
-
-            /* compute the following in bytes, then convert back to frames */
-
-            SelectBufferSizeAndCount(
-                ((userFramesPerBuffer == paFramesPerBufferUnspecified)
-                    ? PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_
-                    : userFramesPerBuffer ) * hostInputFrameSize, /* baseBufferSize */
-                ((unsigned long)(suggestedInputLatency * sampleRate)) * hostInputFrameSize, /* suggestedLatency */
-                4, /* baseBufferCount */
-                minimumBufferCount, maximumBufferSize,
-                &hostBufferSizeBytes, &hostBufferCount );
-
-            *hostFramesPerInputBuffer = hostBufferSizeBytes / hostInputFrameSize;
-            *hostInputBufferCount = hostBufferCount;
+            result = SelectHostBufferSizeFramesAndHostBufferCount(
+                    (unsigned long)(suggestedInputLatency * sampleRate),
+                    userFramesPerBuffer,
+                    minimumBufferCount,
+                    (unsigned long)(PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate), /* in frames. preferred maximum */
+                    (PA_MME_MAX_HOST_BUFFER_BYTES_ / hostInputFrameSizeBytes),  /* in frames. a hard limit. note truncation due to 
+                                                                                division is intentional here to limit max bytes */
+                    hostFramesPerInputBuffer,
+                    hostInputBufferCount );
+            if( result != paNoError )
+                goto error;
         }
     }
     else
@@ -1540,11 +1533,13 @@ static PaError CalculateBufferSettings(
         *hostInputBufferCount = 0;
     }
 
-    if( outputChannelCount > 0 )
+    if( outputChannelCount > 0 ) /* stream has output */
     {
         if( outputStreamInfo
                 && ( outputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) )
         {
+            /* output - using low level latency parameters */
+
             if( outputStreamInfo->bufferCount <= 0
                     || outputStreamInfo->framesPerBuffer <= 0 )
             {
@@ -1555,16 +1550,17 @@ static PaError CalculateBufferSettings(
             *hostFramesPerOutputBuffer = outputStreamInfo->framesPerBuffer;
             *hostOutputBufferCount = outputStreamInfo->bufferCount;
 
-            
             if( inputChannelCount > 0 ) /* full duplex */
             {
+                /* harmonize hostFramesPerInputBuffer and hostFramesPerOutputBuffer */
+
                 if( *hostFramesPerInputBuffer != *hostFramesPerOutputBuffer )
                 {
                     if( inputStreamInfo
                             && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) )
                     { 
                         /* a custom StreamInfo was used for specifying both input
-                            and output buffer sizes, the larger buffer size
+                            and output buffer sizes. We require that the larger buffer size
                             must be a multiple of the smaller buffer size */
 
                         if( *hostFramesPerInputBuffer < *hostFramesPerOutputBuffer )
@@ -1588,111 +1584,72 @@ static PaError CalculateBufferSettings(
                     else
                     {
                         /* a custom StreamInfo was not used for specifying the input buffer size,
-                            so use the output buffer size, and approximately the same latency. */
+                            so use the output buffer size, and approximately the suggested input latency. */
 
                         *hostFramesPerInputBuffer = *hostFramesPerOutputBuffer;
-                        *hostInputBufferCount = (((unsigned long)(suggestedInputLatency * sampleRate)) / *hostFramesPerInputBuffer) + 1;
 
-                        if( *hostInputBufferCount < PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ )
-                            *hostInputBufferCount = PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_;
+                        *hostInputBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+                                (unsigned long)(suggestedInputLatency * sampleRate), 
+                                *hostFramesPerInputBuffer, 
+                                PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ );
                     }
                 }
             }
         }
         else
         {
-            unsigned long hostBufferSizeBytes, hostBufferCount;
-            unsigned long minimumBufferCount = PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_;
-            unsigned long maximumBufferSize;
-            int hostOutputFrameSize;
-            int hostOutputSampleSize;
-
-            hostOutputSampleSize = Pa_GetSampleSize( hostOutputSampleFormat );
-            if( hostOutputSampleSize < 0 )
-            {
-                result = hostOutputSampleSize;
-                goto error;
-            }
-
-            if( outputStreamInfo
-                && ( outputStreamInfo->flags & paWinMmeUseMultipleDevices ) )
-            {
-                /* set effectiveOutputChannelCount to the largest number of
-                    channels on any one device.
-                */
-                effectiveOutputChannelCount = 0;
-                for( i=0; i< outputStreamInfo->deviceCount; ++i )
-                {
-                    if( outputStreamInfo->devices[i].channelCount > effectiveOutputChannelCount )
-                        effectiveOutputChannelCount = outputStreamInfo->devices[i].channelCount;
-                }
-            }
-            else
-            {
-                effectiveOutputChannelCount = outputChannelCount;
-            }
-
-            hostOutputFrameSize = hostOutputSampleSize * effectiveOutputChannelCount;
-            
-            maximumBufferSize = (long) ((PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate) * hostOutputFrameSize);
-            if( maximumBufferSize > PA_MME_MAX_HOST_BUFFER_BYTES_ )
-                maximumBufferSize = PA_MME_MAX_HOST_BUFFER_BYTES_;
+            /* output - no low level latency parameters, so compute hostFramesPerOutputBuffer and hostOutputBufferCount
+                based on userFramesPerBuffer and suggestedOutputLatency. */
 
+            int hostOutputFrameSizeBytes;
+            result = CalculateMaxHostSampleFrameSizeBytes( 
+                    outputChannelCount, hostOutputSampleFormat, outputStreamInfo, &hostOutputFrameSizeBytes );
+            if( result != paNoError )
+                goto error;
 
-            /* compute the following in bytes, then convert back to frames */
-
-            SelectBufferSizeAndCount(
-                ((userFramesPerBuffer == paFramesPerBufferUnspecified)
-                    ? PA_MME_MIN_HOST_BUFFER_FRAMES_WHEN_UNSPECIFIED_
-                    : userFramesPerBuffer ) * hostOutputFrameSize, /* baseBufferSize */
-                ((unsigned long)(suggestedOutputLatency * sampleRate)) * hostOutputFrameSize, /* suggestedLatency */
-                4, /* baseBufferCount */
-                minimumBufferCount,
-                maximumBufferSize,
-                &hostBufferSizeBytes, &hostBufferCount );
-
-            *hostFramesPerOutputBuffer = hostBufferSizeBytes / hostOutputFrameSize;
-            *hostOutputBufferCount = hostBufferCount;
-
+            /* compute the output buffer size and count */
+
+            result = SelectHostBufferSizeFramesAndHostBufferCount(
+                    (unsigned long)(suggestedOutputLatency * sampleRate),
+                    userFramesPerBuffer,
+                    PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_,
+                    (unsigned long)(PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate), /* in frames. preferred maximum */
+                    (PA_MME_MAX_HOST_BUFFER_BYTES_ / hostOutputFrameSizeBytes),  /* in frames. a hard limit. note truncation due to 
+                                                                                 division is intentional here to limit max bytes */
+                    hostFramesPerOutputBuffer,
+                    hostOutputBufferCount );
+            if( result != paNoError )
+                goto error;
 
-            if( inputChannelCount > 0 )
+            if( inputChannelCount > 0 ) /* full duplex */
             {
+                /* harmonize hostFramesPerInputBuffer and hostFramesPerOutputBuffer */
+
                 /* ensure that both input and output buffer sizes are the same.
                     if they don't match at this stage, choose the smallest one
-                    and use that for input and output
+                    and use that for input and output and recompute the corresponding
+                    buffer count accordingly.
                 */
 
                 if( *hostFramesPerOutputBuffer != *hostFramesPerInputBuffer )
                 {
                     if( hostFramesPerInputBuffer < hostFramesPerOutputBuffer )
                     {
-                        unsigned long framesPerHostBuffer = *hostFramesPerInputBuffer;
-                        
-                        minimumBufferCount = PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_;
-                        ReselectBufferCount(
-                            framesPerHostBuffer * hostOutputFrameSize, /* bufferSize */
-                            ((unsigned long)(suggestedOutputLatency * sampleRate)) * hostOutputFrameSize, /* suggestedLatency */
-                            4, /* baseBufferCount */
-                            minimumBufferCount,
-                            &hostBufferCount );
-
-                        *hostFramesPerOutputBuffer = framesPerHostBuffer;
-                        *hostOutputBufferCount = hostBufferCount;
+                        *hostFramesPerOutputBuffer = *hostFramesPerInputBuffer;
+
+                        *hostOutputBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+                                (unsigned long)(suggestedOutputLatency * sampleRate), 
+                                *hostOutputBufferCount, 
+                                PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_ );
                     }
                     else
                     {
-                        unsigned long framesPerHostBuffer = *hostFramesPerOutputBuffer;
-                        
-                        minimumBufferCount = PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_;
-                        ReselectBufferCount(
-                            framesPerHostBuffer * hostInputFrameSize, /* bufferSize */
-                            ((unsigned long)(suggestedInputLatency * sampleRate)) * hostInputFrameSize, /* suggestedLatency */
-                            4, /* baseBufferCount */
-                            minimumBufferCount,
-                            &hostBufferCount );
-
-                        *hostFramesPerInputBuffer = framesPerHostBuffer;
-                        *hostInputBufferCount = hostBufferCount;
+                        *hostFramesPerInputBuffer = *hostFramesPerOutputBuffer;
+
+                        *hostInputBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+                                (unsigned long)(suggestedInputLatency * sampleRate), 
+                                *hostFramesPerInputBuffer, 
+                                PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ );
                     }
                 }   
             }
@@ -2476,12 +2433,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     
     bufferProcessorIsInitialized = 1;
 
+    /* stream info input latency is the minimum buffering latency (unlike suggested and default which are *maximums*) */
     stream->streamRepresentation.streamInfo.inputLatency =
             (double)(PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor)
-                +(framesPerHostInputBuffer * (hostInputBufferCount-1))) / sampleRate;
+                + framesPerHostInputBuffer) / sampleRate;
     stream->streamRepresentation.streamInfo.outputLatency =
             (double)(PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)
-                +(framesPerHostOutputBuffer * (hostOutputBufferCount-1))) / sampleRate;
+                + (framesPerHostOutputBuffer * (hostOutputBufferCount-1))) / sampleRate;
     stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
 
     stream->primeStreamUsingCallback = ( (streamFlags&paPrimeOutputBuffersUsingStreamCallback) && streamCallback ) ? 1 : 0;