]> Repos - portaudio/commitdiff
Fixed directsound PaDeviceInfo default latency and PaStreamInfo latency fields. Fixed...
authorrossb <rossb@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 25 Aug 2011 15:59:32 +0000 (15:59 +0000)
committerrossb <rossb@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 25 Aug 2011 15:59:32 +0000 (15:59 +0000)
include/pa_win_ds.h
src/hostapi/dsound/pa_win_ds.c

index cfbf464516922184b8cf3af0ac5a0e7ca8d23a6b..2410658a010facc6cfe703011bc66b838c69e043 100644 (file)
@@ -59,15 +59,14 @@ extern "C"
 typedef struct PaWinDirectSoundStreamInfo{\r
     unsigned long size;             /**< sizeof(PaWinDirectSoundStreamInfo) */\r
     PaHostApiTypeId hostApiType;    /**< paDirectSound */\r
-    unsigned long version;          /**< 1 */\r
+    unsigned long version;          /**< 2 */\r
 \r
     unsigned long flags;\r
 \r
     /* low-level latency setting support\r
-        TODO ** NOT IMPLEMENTED **\r
-        These settings control the number and size of host buffers in order\r
-        to set latency. They will be used instead of the generic parameters\r
-        to Pa_OpenStream() if flags contains the paWinDirectSoundUseLowLevelLatencyParameters\r
+       Control the size of host buffers in order to set latency. They will \r
+        be used instead of the generic parameters to Pa_OpenStream() if \r
+        flags contains the paWinDirectSoundUseLowLevelLatencyParameters\r
         flag.\r
 \r
         If PaWinDirectSoundStreamInfo structures with paWinDirectSoundUseLowLevelLatencyParameters\r
@@ -76,9 +75,8 @@ typedef struct PaWinDirectSoundStreamInfo{
         two must be a multiple of the smaller, otherwise a\r
         paIncompatibleHostApiSpecificStreamInfo error will be returned from\r
         Pa_OpenStream().\r
-\r
-    unsigned long framesPerBuffer;\r
     */\r
+    unsigned long framesPerBuffer;  /* NOT IMPLEMENTED see http://www.portaudio.com/trac/ticket/129 */\r
 \r
     /*\r
         support for WAVEFORMATEXTENSIBLE channel masks. If flags contains\r
index 5a7fee3ca8db41af0a430e4bfb5b8afcab6144e3..c6f209cdd525701975cfeeef5d86ab9daf7b8ec4 100644 (file)
@@ -45,7 +45,7 @@
    We're replacing this with a new implementation using a thread and a different timer mechanim.
    Defining PA_WIN_DS_USE_WMME_TIMER uses the old (pre-May 2011) behavior.
 */
-//#define PA_WIN_DS_USE_WMME_TIMER 
+//#define PA_WIN_DS_USE_WMME_TIMER
 
 #include <assert.h>
 #include <stdio.h>
@@ -56,6 +56,7 @@
 #include <windows.h>
 #include <objbase.h>
 
+
 /*
   Use the earliest version of DX required, no need to polute the namespace
 */
@@ -142,17 +143,17 @@ PA_THREAD_FUNC ProcessingThreadProc( void *pArg );
 
 #define PA_USE_HIGH_LATENCY   (0)
 #if PA_USE_HIGH_LATENCY
-#define PA_WIN_9X_LATENCY     (500)
-#define PA_WIN_NT_LATENCY     (600)
+#define PA_DS_WIN_9X_DEFAULT_LATENCY_     (.500)
+#define PA_DS_WIN_NT_DEFAULT_LATENCY_     (.600)
 #else
-#define PA_WIN_9X_LATENCY     (140)
-#define PA_WIN_NT_LATENCY     (280)
+#define PA_DS_WIN_9X_DEFAULT_LATENCY_     (.140)
+#define PA_DS_WIN_NT_DEFAULT_LATENCY_     (.280)
 #endif
 
-#define PA_WIN_WDM_LATENCY       (120)
+#define PA_DS_WIN_WDM_DEFAULT_LATENCY_    (.120)
 
 #define SECONDS_PER_MSEC      (0.001)
-#define MSEC_PER_SECOND       (1000)
+#define MSECS_PER_SECOND       (1000)
 
 /* prototypes for functions declared in this file */
 
@@ -254,7 +255,7 @@ typedef struct PaWinDsStream
     LPDIRECTSOUNDBUFFER  pDirectSoundOutputBuffer;
     DWORD                outputBufferWriteOffsetBytes;     /* last write position */
     INT                  outputBufferSizeBytes;
-    INT                  bytesPerOutputFrame;
+    INT                  outputFrameSizeBytes;
     /* Try to detect play buffer underflows. */
     LARGE_INTEGER        perfCounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */
     LARGE_INTEGER        previousPlayTime;
@@ -266,14 +267,15 @@ typedef struct PaWinDsStream
 /* Input */
     LPDIRECTSOUNDCAPTURE pDirectSoundCapture;
     LPDIRECTSOUNDCAPTUREBUFFER   pDirectSoundInputBuffer;
-    INT                  bytesPerInputFrame;
+    INT                  inputFrameSizeBytes;
     UINT                 readOffset;      /* last read position */
-    UINT                 inputSize;
+    UINT                 inputBufferSizeBytes;
 
     
-    int              framesPerDSBuffer;
+    int              hostBufferSizeFrames; /* input and output host ringbuffers have the same number of frames */
     double           framesWritten;
     double           secondsPerHostByte; /* Used to optimize latency calculation for outTime */
+    double           pollingPeriodSeconds;
 
     PaStreamCallbackFlags callbackFlags;
 
@@ -304,6 +306,77 @@ typedef struct PaWinDsStream
 } PaWinDsStream;
 
 
+/* Set minimal latency based on the current OS version.
+ * NT has higher latency.
+ */
+static double PaWinDS_GetMinSystemLatencySeconds( void )
+{
+    double minLatencySeconds;
+    /* Set minimal latency based on whether NT or other OS.
+     * NT has higher latency.
+     */
+    OSVERSIONINFO osvi;
+       osvi.dwOSVersionInfoSize = sizeof( osvi );
+       GetVersionEx( &osvi );
+    DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
+    DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
+    DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
+    /* Check for NT */
+       if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
+       {
+               minLatencySeconds = PA_DS_WIN_NT_DEFAULT_LATENCY_;
+       }
+       else if(osvi.dwMajorVersion >= 5)
+       {
+               minLatencySeconds = PA_DS_WIN_WDM_DEFAULT_LATENCY_;
+       }
+       else
+       {
+               minLatencySeconds = PA_DS_WIN_9X_DEFAULT_LATENCY_;
+       }
+    return minLatencySeconds;
+}
+
+
+/*************************************************************************
+** Return minimum workable latency required for this host. This is returned
+** As the default stream latency in PaDeviceInfo.
+** Latency can be optionally set by user by setting an environment variable. 
+** For example, to set latency to 200 msec, put:
+**
+**    set PA_MIN_LATENCY_MSEC=200
+**
+** in the AUTOEXEC.BAT file and reboot.
+** If the environment variable is not set, then the latency will be determined
+** based on the OS. Windows NT has higher latency than Win95.
+*/
+#define PA_LATENCY_ENV_NAME  ("PA_MIN_LATENCY_MSEC")
+#define PA_ENV_BUF_SIZE  (32)
+
+static double PaWinDs_GetMinLatencySeconds( double sampleRate )
+{
+    char      envbuf[PA_ENV_BUF_SIZE];
+    DWORD     hresult;
+    double    minLatencySeconds = 0;
+
+    /* Let user determine minimal latency by setting environment variable. */
+    hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
+    if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
+    {
+        minLatencySeconds = atoi( envbuf ) * SECONDS_PER_MSEC;
+    }
+    else
+    {
+        minLatencySeconds = PaWinDS_GetMinSystemLatencySeconds();
+#if PA_USE_HIGH_LATENCY
+        PRINT(("PA - Minimum Latency set to %f msec!\n", minLatencySeconds * MSECS_PER_SECOND ));
+#endif
+    }
+
+    return minLatencySeconds;
+}
+
+
 /************************************************************************************
 ** Duplicate the input string using the allocations allocator.
 ** A NULL string is converted to a zero length string.
@@ -482,6 +555,7 @@ static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID,
     return TRUE;
 }
 
+
 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
 
 static void *DuplicateWCharString( PaUtilAllocationGroup *allocations, wchar_t *source )
@@ -816,11 +890,6 @@ static PaError AddOutputDeviceInfoFromDirectSound(
                 }
 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
 
-                deviceInfo->defaultLowInputLatency = 0.;    /** @todo IMPLEMENT ME */
-                deviceInfo->defaultLowOutputLatency = 0.;   /** @todo IMPLEMENT ME */
-                deviceInfo->defaultHighInputLatency = 0.;   /** @todo IMPLEMENT ME */
-                deviceInfo->defaultHighOutputLatency = 0.;  /** @todo IMPLEMENT ME */
-                
                 /* initialize defaultSampleRate */
                 
                 if( caps.dwFlags & DSCAPS_CONTINUOUSRATE )
@@ -847,7 +916,7 @@ static PaError AddOutputDeviceInfoFromDirectSound(
                         ** But it supports continuous sampling.
                         ** So fake range of rates, and hope it really supports it.
                         */
-                        deviceInfo->defaultSampleRate = 44100.0f;
+                        deviceInfo->defaultSampleRate = 48000.0f;  /* assume 48000 as the default */
 
                         DBUG(("PA - Reported rates both zero. Setting to fake values for device #%s\n", name ));
                     }
@@ -862,14 +931,19 @@ static PaError AddOutputDeviceInfoFromDirectSound(
                     ** But we know that they really support a range of rates!
                     ** So when we see a ridiculous set of rates, assume it is a range.
                     */
-                  deviceInfo->defaultSampleRate = 44100.0f;
+                  deviceInfo->defaultSampleRate = 48000.0f;  /* assume 48000 as the default */
                   DBUG(("PA - Sample rate range used instead of two odd values for device #%s\n", name ));
                 }
                 else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
 
-
                 //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate );
                 // dwFlags | DSCAPS_CONTINUOUSRATE 
+
+                deviceInfo->defaultLowInputLatency = 0.;
+                deviceInfo->defaultHighInputLatency = 0.;
+
+                deviceInfo->defaultLowOutputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate );
+                deviceInfo->defaultHighOutputLatency = deviceInfo->defaultLowOutputLatency * 2;
             }
         }
 
@@ -978,11 +1052,6 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture(
                 }
 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
 
-                deviceInfo->defaultLowInputLatency = 0.;    /** @todo IMPLEMENT ME */
-                deviceInfo->defaultLowOutputLatency = 0.;   /** @todo IMPLEMENT ME */
-                deviceInfo->defaultHighInputLatency = 0.;   /** @todo IMPLEMENT ME */
-                deviceInfo->defaultHighOutputLatency = 0.;  /** @todo IMPLEMENT ME */
-
 /*  constants from a WINE patch by Francois Gouget, see:
     http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html
 
@@ -1024,7 +1093,7 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture(
                     else if( caps.dwFormats & WAVE_FORMAT_96S16 )
                         deviceInfo->defaultSampleRate = 96000.0;
                     else
-                        deviceInfo->defaultSampleRate = 0.;
+                        deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
                 }
                 else if( caps.dwChannels == 1 )
                 {
@@ -1039,9 +1108,15 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture(
                     else if( caps.dwFormats & WAVE_FORMAT_96M16 )
                         deviceInfo->defaultSampleRate = 96000.0;
                     else
-                        deviceInfo->defaultSampleRate = 0.;
+                        deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
                 }
-                else deviceInfo->defaultSampleRate = 0.;
+                else deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
+
+                deviceInfo->defaultLowInputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate );
+                deviceInfo->defaultHighInputLatency = deviceInfo->defaultLowInputLatency * 2;
+        
+                deviceInfo->defaultLowOutputLatency = 0.;
+                deviceInfo->defaultHighOutputLatency = 0.;
             }
         }
         
@@ -1250,38 +1325,6 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
     PaWinDs_TerminateDSoundEntryPoints();
 }
 
-
-/* Set minimal latency based on whether NT or Win95.
- * NT has higher latency.
- */
-static int PaWinDS_GetMinSystemLatency( void )
-{
-    int minLatencyMsec;
-    /* Set minimal latency based on whether NT or other OS.
-     * NT has higher latency.
-     */
-    OSVERSIONINFO osvi;
-       osvi.dwOSVersionInfoSize = sizeof( osvi );
-       GetVersionEx( &osvi );
-    DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
-    DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
-    DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
-    /* Check for NT */
-       if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
-       {
-               minLatencyMsec = PA_WIN_NT_LATENCY;
-       }
-       else if(osvi.dwMajorVersion >= 5)
-       {
-               minLatencyMsec = PA_WIN_WDM_LATENCY;
-       }
-       else
-       {
-               minLatencyMsec = PA_WIN_9X_LATENCY;
-       }
-    return minLatencyMsec;
-}
-
 static PaError ValidateWinDirectSoundSpecificStreamInfo(
         const PaStreamParameters *streamParameters,
         const PaWinDirectSoundStreamInfo *streamInfo )
@@ -1289,7 +1332,7 @@ static PaError ValidateWinDirectSoundSpecificStreamInfo(
        if( streamInfo )
        {
            if( streamInfo->size != sizeof( PaWinDirectSoundStreamInfo )
-                   || streamInfo->version != 1 )
+                   || streamInfo->version != 2 )
            {
                return paIncompatibleHostApiSpecificStreamInfo;
            }
@@ -1394,45 +1437,6 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 }
 
 
-/*************************************************************************
-** Determine minimum number of buffers required for this host based
-** on minimum latency. Latency can be optionally set by user by setting
-** an environment variable. For example, to set latency to 200 msec, put:
-**
-**    set PA_MIN_LATENCY_MSEC=200
-**
-** in the AUTOEXEC.BAT file and reboot.
-** If the environment variable is not set, then the latency will be determined
-** based on the OS. Windows NT has higher latency than Win95.
-*/
-#define PA_LATENCY_ENV_NAME  ("PA_MIN_LATENCY_MSEC")
-#define PA_ENV_BUF_SIZE  (32)
-
-static int PaWinDs_GetMinLatencyFrames( double sampleRate )
-{
-    char      envbuf[PA_ENV_BUF_SIZE];
-    DWORD     hresult;
-    int       minLatencyMsec = 0;
-
-    /* Let user determine minimal latency by setting environment variable. */
-    hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
-    if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
-    {
-        minLatencyMsec = atoi( envbuf );
-    }
-    else
-    {
-        minLatencyMsec = PaWinDS_GetMinSystemLatency();
-#if PA_USE_HIGH_LATENCY
-        PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec ));
-#endif
-
-    }
-
-    return (int) (minLatencyMsec * sampleRate * SECONDS_PER_MSEC);
-}
-
-
 #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
 static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream,
                                        PaWinDsDeviceInfo *inputDevice,
@@ -1668,6 +1672,119 @@ error:
     return result;
 }
 
+
+static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames, 
+                                    unsigned long *pollingPeriodFrames,
+                                    int isFullDuplex,
+                                    unsigned long suggestedInputLatencyFrames,
+                                    unsigned long suggestedOutputLatencyFrames,
+                                    double sampleRate, unsigned long userFramesPerBuffer )
+{
+    /* we allow the polling period to range between 1 and 100ms.
+       prior to August 2011 we limited the minimum polling period to 10ms.
+    */
+    unsigned long minimumPollingPeriodFrames = sampleRate / 1000; /* 1ms */
+    unsigned long maximumPollingPeriodFrames = sampleRate / 10; /* 100ms */ 
+    unsigned long pollingJitterFrames = sampleRate / 1000; /* 1ms */
+    
+    if( userFramesPerBuffer == paFramesPerBufferUnspecified )
+    {
+        unsigned long suggestedLatencyFrames = max( suggestedInputLatencyFrames, suggestedOutputLatencyFrames );
+
+        *pollingPeriodFrames = suggestedLatencyFrames / 4;
+        if( *pollingPeriodFrames < minimumPollingPeriodFrames )
+        {
+            *pollingPeriodFrames = minimumPollingPeriodFrames;
+        }
+        else if( *pollingPeriodFrames > maximumPollingPeriodFrames )
+        {
+            *pollingPeriodFrames = maximumPollingPeriodFrames;
+        }
+
+        *hostBufferSizeFrames = *pollingPeriodFrames 
+                + max( *pollingPeriodFrames + pollingJitterFrames, suggestedLatencyFrames);
+    }
+    else
+    {
+        unsigned long suggestedLatencyFrames = suggestedInputLatencyFrames;
+        if( isFullDuplex )
+        {
+            /* in full duplex streams we know that the buffer adapter adds userFramesPerBuffer
+               extra fixed latency. so we subtract it here as a fixed latency before computing
+               the buffer size. being careful not to produce an unrepresentable negative result.
+               
+               Note: this only works as expected if output latency is greater than input latency.
+               Otherwise we use input latency anyway since we do max(in,out).
+            */
+
+            if( userFramesPerBuffer < suggestedOutputLatencyFrames )
+            {
+                unsigned long adjustedSuggestedOutputLatencyFrames = 
+                        suggestedOutputLatencyFrames - userFramesPerBuffer;
+
+                /* maximum of input and adjusted output suggested latency */
+                if( adjustedSuggestedOutputLatencyFrames > suggestedInputLatencyFrames )
+                    suggestedLatencyFrames = adjustedSuggestedOutputLatencyFrames;
+            }
+        }
+        else
+        {
+            /* maximum of input and output suggested latency */
+            if( suggestedOutputLatencyFrames > suggestedInputLatencyFrames )
+                suggestedLatencyFrames = suggestedOutputLatencyFrames;
+        }   
+
+        *hostBufferSizeFrames = userFramesPerBuffer 
+                + max( userFramesPerBuffer + pollingJitterFrames, suggestedLatencyFrames);
+
+        *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), suggestedLatencyFrames / 16 );
+
+        if( *pollingPeriodFrames > maximumPollingPeriodFrames )
+        {
+            *pollingPeriodFrames = maximumPollingPeriodFrames;
+        }
+    } 
+}
+
+
+static void SetStreamInfoLatencies( PaWinDsStream *stream, 
+                                   unsigned long userFramesPerBuffer, 
+                                   unsigned long pollingPeriodFrames,
+                                   double sampleRate )
+{
+    /* compute the stream info actual latencies based on framesPerBuffer, polling period, hostBufferSizeFrames, 
+    and the configuration of the buffer processor */
+
+    unsigned long effectiveFramesPerBuffer = (userFramesPerBuffer == paFramesPerBufferUnspecified)
+                                             ? pollingPeriodFrames
+                                             : userFramesPerBuffer;
+
+    if( stream->bufferProcessor.inputChannelCount > 0 )
+    {
+        /* 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)
+                    + effectiveFramesPerBuffer) / sampleRate;
+    }
+    else
+    {
+        stream->streamRepresentation.streamInfo.inputLatency = 0;
+    }
+
+    if( stream->bufferProcessor.outputChannelCount > 0 )
+    {
+        stream->streamRepresentation.streamInfo.outputLatency =
+                (double)(PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)
+                    + (stream->hostBufferSizeFrames - effectiveFramesPerBuffer)) / sampleRate;
+    }
+    else
+    {
+        stream->streamRepresentation.streamInfo.outputLatency = 0;
+    }
+}
+
+
 /***********************************************************************************/
 /* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */
 
@@ -1694,6 +1811,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames;
     PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo;
     PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask;
+    unsigned long pollingPeriodFrames = 0;
 
     if( inputParameters )
     {
@@ -1833,7 +1951,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     {
         /* IMPLEMENT ME - establish which  host formats are available */
         PaSampleFormat nativeInputFormats = paInt16;
-        //PaSampleFormat nativeFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32;
+        /* PaSampleFormat nativeFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */
 
         hostInputSampleFormat =
             PaUtil_SelectClosestAvailableFormat( nativeInputFormats, inputParameters->sampleFormat );
@@ -1847,7 +1965,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     {
         /* IMPLEMENT ME - establish which  host formats are available */
         PaSampleFormat nativeOutputFormats = paInt16;
-        //PaSampleFormat nativeOutputFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32;
+        /* PaSampleFormat nativeOutputFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */
 
         hostOutputSampleFormat =
             PaUtil_SelectClosestAvailableFormat( nativeOutputFormats, outputParameters->sampleFormat );
@@ -1861,7 +1979,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                     inputChannelCount, inputSampleFormat, hostInputSampleFormat,
                     outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
                     sampleRate, streamFlags, framesPerBuffer,
-                    framesPerBuffer, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */
+                    0, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */
                 /* This next mode is required because DS can split the host buffer when it wraps around. */
                     paUtilVariableHostBufferSizePartialUsageAllowed,
                     streamCallback, userData );
@@ -1870,24 +1988,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 
     bufferProcessorIsInitialized = 1;
 
-    stream->streamRepresentation.streamInfo.inputLatency =
-            (PaTime)PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor) / sampleRate;   /* FIXME: only includes buffer processor latency */
-    stream->streamRepresentation.streamInfo.outputLatency =
-            (PaTime)PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor) / sampleRate;    /* FIXME: only includes buffer processor latency */
-    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
-
-    
+   
 /* DirectSound specific initialization */ 
     {
         HRESULT          hr;
-        int              bytesPerDirectSoundInputBuffer, bytesPerDirectSoundOutputBuffer;
-        int              userLatencyFrames;
-        int              minLatencyFrames;
         unsigned long    integerSampleRate = (unsigned long) (sampleRate + 0.5);
-    
-#ifdef PA_WIN_DS_USE_WMME_TIMER
-        stream->timerID = 0;
-#endif
+        
         stream->processingCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL );
         if( stream->processingCompleted == NULL )
         {
@@ -1895,6 +2001,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
             goto error;
         }
 
+#ifdef PA_WIN_DS_USE_WMME_TIMER
+        stream->timerID = 0;
+#endif
+
 #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
         stream->waitableTimer = (HANDLE)CreateWaitableTimer( 0, FALSE, NULL );
         if( stream->waitableTimer == NULL )
@@ -1915,41 +2025,15 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         }
 #endif
 
-    /* Get system minimum latency. */
-        minLatencyFrames = PaWinDs_GetMinLatencyFrames( sampleRate );
-
-    /* Let user override latency by passing latency parameter. */
-        userLatencyFrames = (suggestedInputLatencyFrames > suggestedOutputLatencyFrames)
-                    ? suggestedInputLatencyFrames
-                    : suggestedOutputLatencyFrames;
-        if( userLatencyFrames > 0 ) minLatencyFrames = userLatencyFrames;
-
-    /* Calculate stream->framesPerDSBuffer depending on framesPerBuffer */
-        if( framesPerBuffer == paFramesPerBufferUnspecified )
-        {
-        /* App support variable framesPerBuffer */
-            stream->framesPerDSBuffer = minLatencyFrames;
-
-            stream->streamRepresentation.streamInfo.outputLatency = (double)(minLatencyFrames - 1) / sampleRate;
-        }
-        else
-        {
-        /* Round up to number of buffers needed to guarantee that latency. */
-            int numUserBuffers = (minLatencyFrames + framesPerBuffer - 1) / framesPerBuffer;
-            if( numUserBuffers < 1 ) numUserBuffers = 1;
-            numUserBuffers += 1; /* So we have latency worth of buffers ahead of current buffer. */
-            stream->framesPerDSBuffer = framesPerBuffer * numUserBuffers;
+        /* set up i/o parameters */
 
-            stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerBuffer * (numUserBuffers-1)) / sampleRate;
-        }
+        CalculateBufferSettings( &stream->hostBufferSizeFrames, &pollingPeriodFrames,
+                /* isFullDuplex = */ (inputParameters && outputParameters),
+                suggestedInputLatencyFrames,
+                suggestedOutputLatencyFrames, 
+                sampleRate, framesPerBuffer );
 
-        {
-            /** @todo REVIEW: this calculation seems incorrect to me - rossb. */
-            int msecLatency = (int) ((stream->framesPerDSBuffer * MSEC_PER_SECOND) / sampleRate);
-            PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", stream->framesPerDSBuffer, msecLatency ));
-        }
-
-        /* set up i/o parameters */
+        stream->pollingPeriodSeconds = pollingPeriodFrames / sampleRate;
 
         /* ------------------ OUTPUT */
         if( outputParameters )
@@ -1961,14 +2045,16 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
             DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device));
             */
             
-            int bytesPerSample = Pa_GetSampleSize(hostOutputSampleFormat);
-            bytesPerDirectSoundOutputBuffer = stream->framesPerDSBuffer * outputParameters->channelCount * bytesPerSample;
-            if( bytesPerDirectSoundOutputBuffer < DSBSIZE_MIN )
+            int sampleSizeBytes = Pa_GetSampleSize(hostOutputSampleFormat);
+            stream->outputFrameSizeBytes = outputParameters->channelCount * sampleSizeBytes;
+
+            stream->outputBufferSizeBytes = stream->hostBufferSizeFrames * stream->outputFrameSizeBytes;
+            if( stream->outputBufferSizeBytes < DSBSIZE_MIN )
             {
                 result = paBufferTooSmall;
                 goto error;
             }
-            else if( bytesPerDirectSoundOutputBuffer > DSBSIZE_MAX )
+            else if( stream->outputBufferSizeBytes > DSBSIZE_MAX )
             {
                 result = paBufferTooBig;
                 goto error;
@@ -1979,15 +2065,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                 (stream->bufferProcessor.bytesPerHostOutputSample *
                 outputChannelCount * sampleRate);
 
-            stream->outputBufferSizeBytes = bytesPerDirectSoundOutputBuffer;
             stream->outputIsRunning = FALSE;
             stream->outputUnderflowCount = 0;
-            stream->bytesPerOutputFrame = outputParameters->channelCount * bytesPerSample;
-
+            
             /* perfCounterTicksPerBuffer is used by QueryOutputSpace for overflow detection */
             if( QueryPerformanceFrequency( &counterFrequency ) )
             {
-                stream->perfCounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * stream->framesPerDSBuffer) / integerSampleRate;
+                stream->perfCounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * stream->hostBufferSizeFrames) / integerSampleRate;
             }
             else
             {
@@ -2003,22 +2087,20 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
             DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device));
             */
             
-            int bytesPerSample = Pa_GetSampleSize(hostInputSampleFormat);
-            bytesPerDirectSoundInputBuffer = stream->framesPerDSBuffer * inputParameters->channelCount * bytesPerSample;
-            if( bytesPerDirectSoundInputBuffer < DSBSIZE_MIN )
+            int sampleSizeBytes = Pa_GetSampleSize(hostInputSampleFormat);
+            stream->inputFrameSizeBytes = inputParameters->channelCount * sampleSizeBytes;
+
+            stream->inputBufferSizeBytes = stream->hostBufferSizeFrames * stream->inputFrameSizeBytes;
+            if( stream->inputBufferSizeBytes < DSBSIZE_MIN )
             {
                 result = paBufferTooSmall;
                 goto error;
             }
-            else if( bytesPerDirectSoundInputBuffer > DSBSIZE_MAX )
+            else if( stream->inputBufferSizeBytes > DSBSIZE_MAX )
             {
                 result = paBufferTooBig;
                 goto error;
             }
-
-            stream->bytesPerInputFrame = inputParameters->channelCount * bytesPerSample;
-
-            stream->inputSize = bytesPerDirectSoundInputBuffer;
         }
 
         /* open/create the DirectSound buffers */
@@ -2040,11 +2122,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
             hr = InitFullDuplexInputOutputBuffers( stream,
                                        (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device],
                                        hostInputSampleFormat,
-                                       (WORD)inputParameters->channelCount, bytesPerDirectSoundInputBuffer,
+                                       (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes,
                                        inputChannelMask,
                                        (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device],
                                        hostOutputSampleFormat,
-                                       (WORD)outputParameters->channelCount, bytesPerDirectSoundOutputBuffer,
+                                       (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes,
                                        outputChannelMask,
                                        integerSampleRate
                                         );
@@ -2066,7 +2148,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                                        (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device],
                                        hostOutputSampleFormat,
                                        integerSampleRate,
-                                       (WORD)outputParameters->channelCount, bytesPerDirectSoundOutputBuffer,
+                                       (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes,
                                        outputChannelMask );
             DBUG(("InitOutputBuffer() returns %x\n", hr));
             if( hr != DS_OK )
@@ -2083,7 +2165,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                                       (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device],
                                       hostInputSampleFormat,
                                       integerSampleRate,
-                                      (WORD)inputParameters->channelCount, bytesPerDirectSoundInputBuffer,
+                                      (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes,
                                       inputChannelMask );
             DBUG(("InitInputBuffer() returns %x\n", hr));
             if( hr != DS_OK )
@@ -2096,6 +2178,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         }
     }
 
+    SetStreamInfoLatencies( stream, framesPerBuffer, pollingPeriodFrames, sampleRate );
+
+    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
     *s = (PaStream*)stream;
 
     return result;
@@ -2256,7 +2342,7 @@ static int TimeSlice( PaWinDsStream *stream )
     long              bytesEmpty = 0;
     long              bytesFilled = 0;
     long              bytesToXfer = 0;
-    long              framesToXfer = 0;
+    long              framesToXfer = 0; /* the number of frames we'll process this tick */
     long              numInFramesReady = 0;
     long              numOutFramesReady = 0;
     long              bytesProcessed;
@@ -2289,12 +2375,12 @@ static int TimeSlice( PaWinDsStream *stream )
         if( hr == DS_OK )
         {
             filled = readPos - stream->readOffset;
-            if( filled < 0 ) filled += stream->inputSize; // unwrap offset
+            if( filled < 0 ) filled += stream->inputBufferSizeBytes; // unwrap offset
             bytesFilled = filled;
         }
             // FIXME: what happens if IDirectSoundCaptureBuffer_GetCurrentPosition fails?
 
-        framesToXfer = numInFramesReady = bytesFilled / stream->bytesPerInputFrame
+        framesToXfer = numInFramesReady = bytesFilled / stream->inputFrameSizeBytes
         outputLatency = ((double)bytesFilled) * stream->secondsPerHostByte;  // FIXME: this doesn't look right. we're calculating output latency in input branch. also secondsPerHostByte is only initialized for the output stream
 
         /** @todo Check for overflow */
@@ -2305,14 +2391,15 @@ static int TimeSlice( PaWinDsStream *stream )
     {
         UINT previousUnderflowCount = stream->outputUnderflowCount;
         QueryOutputSpace( stream, &bytesEmpty );
-        framesToXfer = numOutFramesReady = bytesEmpty / stream->bytesPerOutputFrame;
+        framesToXfer = numOutFramesReady = bytesEmpty / stream->outputFrameSizeBytes;
 
         /* Check for underflow */
         if( stream->outputUnderflowCount != previousUnderflowCount )
             stream->callbackFlags |= paOutputUnderflow;
     }
 
-    if( (numInFramesReady > 0) && (numOutFramesReady > 0) )
+    /* if it's a full duplex stream, set framesToXfer to the minimum of input and output frames ready */
+    if( stream->bufferProcessor.inputChannelCount > 0 && stream->bufferProcessor.outputChannelCount > 0 )
     {
         framesToXfer = (numOutFramesReady < numInFramesReady) ? numOutFramesReady : numInFramesReady;
     }
@@ -2333,7 +2420,7 @@ static int TimeSlice( PaWinDsStream *stream )
     /* Input */
         if( stream->bufferProcessor.inputChannelCount > 0 )
         {
-            bytesToXfer = framesToXfer * stream->bytesPerInputFrame;
+            bytesToXfer = framesToXfer * stream->inputFrameSizeBytes;
             hresult = IDirectSoundCaptureBuffer_Lock ( stream->pDirectSoundInputBuffer,
                 stream->readOffset, bytesToXfer,
                 (void **) &lpInBuf1, &dwInSize1,
@@ -2347,13 +2434,13 @@ static int TimeSlice( PaWinDsStream *stream )
                 goto error2;
             }
 
-            numFrames = dwInSize1 / stream->bytesPerInputFrame;
+            numFrames = dwInSize1 / stream->inputFrameSizeBytes;
             PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames );
             PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 );
         /* Is input split into two regions. */
             if( dwInSize2 > 0 )
             {
-                numFrames = dwInSize2 / stream->bytesPerInputFrame;
+                numFrames = dwInSize2 / stream->inputFrameSizeBytes;
                 PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames );
                 PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 );
             }
@@ -2362,7 +2449,7 @@ static int TimeSlice( PaWinDsStream *stream )
     /* Output */
         if( stream->bufferProcessor.outputChannelCount > 0 )
         {
-            bytesToXfer = framesToXfer * stream->bytesPerOutputFrame;
+            bytesToXfer = framesToXfer * stream->outputFrameSizeBytes;
             hresult = IDirectSoundBuffer_Lock ( stream->pDirectSoundOutputBuffer,
                 stream->outputBufferWriteOffsetBytes, bytesToXfer,
                 (void **) &lpOutBuf1, &dwOutSize1,
@@ -2376,14 +2463,14 @@ static int TimeSlice( PaWinDsStream *stream )
                 goto error1;
             }
 
-            numFrames = dwOutSize1 / stream->bytesPerOutputFrame;
+            numFrames = dwOutSize1 / stream->outputFrameSizeBytes;
             PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames );
             PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 );
 
         /* Is output split into two regions. */
             if( dwOutSize2 > 0 )
             {
-                numFrames = dwOutSize2 / stream->bytesPerOutputFrame;
+                numFrames = dwOutSize2 / stream->outputFrameSizeBytes;
                 PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames );
                 PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 );
             }
@@ -2397,7 +2484,7 @@ static int TimeSlice( PaWinDsStream *stream )
         /* FIXME: an underflow could happen here */
 
         /* Update our buffer offset and unlock sound buffer */
-            bytesProcessed = numFrames * stream->bytesPerOutputFrame;
+            bytesProcessed = numFrames * stream->outputFrameSizeBytes;
             stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + bytesProcessed) % stream->outputBufferSizeBytes;
             IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2);
         }
@@ -2408,8 +2495,8 @@ error1:
         /* FIXME: an overflow could happen here */
 
         /* Update our buffer offset and unlock sound buffer */
-            bytesProcessed = numFrames * stream->bytesPerInputFrame;
-            stream->readOffset = (stream->readOffset + bytesProcessed) % stream->inputSize;
+            bytesProcessed = numFrames * stream->inputFrameSizeBytes;
+            stream->readOffset = (stream->readOffset + bytesProcessed) % stream->inputBufferSizeBytes;
             IDirectSoundCaptureBuffer_Unlock( stream->pDirectSoundInputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2);
         }
 error2:
@@ -2548,28 +2635,27 @@ PA_THREAD_FUNC ProcessingThreadProc( void *pArg )
     MMRESULT mmResult;
     HANDLE hWaitableTimer;
     LARGE_INTEGER dueTime;
-    int framesPerWakeup, msecPerWakeup;
+    int timerPeriodMs;
 
-    framesPerWakeup = stream->framesPerDSBuffer / 4; /* always poll using quadruple buffering, probably not the best strategy */
-    msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate;
-    if( msecPerWakeup < 1 ) msecPerWakeup = 1;
-    else if( msecPerWakeup > 100 ) msecPerWakeup = 100;
+    timerPeriodMs = stream->pollingPeriodSeconds * MSECS_PER_SECOND;
+    if( timerPeriodMs < 1 )
+        timerPeriodMs = 1;
 
 #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
     assert( stream->waitableTimer != NULL );
 
     /* invoke first timeout immediately */
-    dueTime.LowPart = msecPerWakeup * 1000 * 10;
+    dueTime.LowPart = timerPeriodMs * 1000 * 10;
     dueTime.HighPart = 0;
 
     /* tick using waitable timer */
-    if( SetWaitableTimer( stream->waitableTimer, &dueTime, msecPerWakeup, WaitableTimerAPCProc, pArg, FALSE ) != 0 )
+    if( SetWaitableTimer( stream->waitableTimer, &dueTime, timerPeriodMs, WaitableTimerAPCProc, pArg, FALSE ) != 0 )
     {
         DWORD wfsoResult = 0;
         do
         {
             /* wait for processingCompleted to be signaled or our timer APC to be called */
-            wfsoResult = WaitForSingleObjectEx( stream->processingCompleted, msecPerWakeup * 10, /* alertable = */ TRUE );
+            wfsoResult = WaitForSingleObjectEx( stream->processingCompleted, timerPeriodMs * 10, /* alertable = */ TRUE );
 
         }while( wfsoResult == WAIT_TIMEOUT || wfsoResult == WAIT_IO_COMPLETION );
     }
@@ -2579,7 +2665,7 @@ PA_THREAD_FUNC ProcessingThreadProc( void *pArg )
 #else
 
     /* tick using WaitForSingleObject timout */
-    while ( WaitForSingleObject( stream->processingCompleted, msecPerWakeup ) == WAIT_TIMEOUT )
+    while ( WaitForSingleObject( stream->processingCompleted, timerPeriodMs ) == WAIT_TIMEOUT )
     {
         TimerCallback( 0, 0, (DWORD_PTR)pArg, 0, 0 );
     }
@@ -2775,13 +2861,9 @@ static PaError StartStream( PaStream *s )
     if( stream->streamRepresentation.streamCallback )
     {
         TIMECAPS timecaps;
-
-        int timerResolution;
-        int framesPerWakeup = stream->framesPerDSBuffer / 4;
-        int msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate;
-        if( msecPerWakeup < 10 ) msecPerWakeup = 10;
-        else if( msecPerWakeup > 100 ) msecPerWakeup = 100;
-        timerResolution = msecPerWakeup/4;
+        int timerPeriodMs = stream->pollingPeriodSeconds * MSECS_PER_SECOND;
+        if( timerPeriodMs < 1 )
+            timerPeriodMs = 1;
 
         /* set windows scheduler granularity only as fine as needed, no finer */
         /* Although this is not fully documented by MS, it appears that
@@ -2792,9 +2874,12 @@ static PaError StartStream( PaStream *s )
         assert( stream->systemTimerResolutionPeriodMs == 0 );
         if( timeGetDevCaps( &timecaps, sizeof(TIMECAPS) == MMSYSERR_NOERROR && timecaps.wPeriodMin > 0 ) )
         {
-            stream->systemTimerResolutionPeriodMs = timerResolution;
+            /* aim for resolution 4 times higher than polling rate */
+            stream->systemTimerResolutionPeriodMs = (stream->pollingPeriodSeconds * MSECS_PER_SECOND) / 4;
             if( stream->systemTimerResolutionPeriodMs < timecaps.wPeriodMin )
                 stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMin;
+            if( stream->systemTimerResolutionPeriodMs > timecaps.wPeriodMax )
+                stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMax;
 
             if( timeBeginPeriod( stream->systemTimerResolutionPeriodMs ) != MMSYSERR_NOERROR )
                 stream->systemTimerResolutionPeriodMs = 0; /* timeBeginPeriod failed, so we don't need to call timeEndPeriod() later */
@@ -2807,7 +2892,7 @@ static PaError StartStream( PaStream *s )
            are serialised onto a single thread. Which creates problems with multiple
            PA streams, or when also using timers for other time critical tasks
         */
-        stream->timerID = timeSetEvent( msecPerWakeup, timerResolution, (LPTIMECALLBACK) TimerCallback,
+        stream->timerID = timeSetEvent( timerPeriodMs, stream->systemTimerResolutionPeriodMs, (LPTIMECALLBACK) TimerCallback,
                                              (DWORD_PTR) stream, TIME_PERIODIC | TIME_KILL_SYNCHRONOUS );
     
         if( stream->timerID == 0 )
@@ -2876,7 +2961,7 @@ static PaError StopStream( PaStream *s )
         stream->stopProcessing = 1;
 
         /* Set timeout at 4 times maximum time we might wait. */
-        timeoutMsec = (int) (4 * MSEC_PER_SECOND * (stream->framesPerDSBuffer / stream->streamRepresentation.streamInfo.sampleRate));
+        timeoutMsec = (int) (4 * MSECS_PER_SECOND * (stream->hostBufferSizeFrames / stream->streamRepresentation.streamInfo.sampleRate));
 
         WaitForSingleObject( stream->processingCompleted, timeoutMsec );
     }