diff --git a/src/hostapi/dsound/pa_win_ds.c b/src/hostapi/dsound/pa_win_ds.c index b0ab18725..bebe2297f 100644 --- a/src/hostapi/dsound/pa_win_ds.c +++ b/src/hostapi/dsound/pa_win_ds.c @@ -1725,7 +1725,7 @@ static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *devic static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames, unsigned long *pollingPeriodFrames, - int isFullDuplex, + int hasInput, int hasOutput, unsigned long suggestedInputLatencyFrames, unsigned long suggestedOutputLatencyFrames, double sampleRate, unsigned long userFramesPerBuffer ) @@ -1734,62 +1734,43 @@ static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames, unsigned long maximumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS); unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS); - if( userFramesPerBuffer == paFramesPerBufferUnspecified ) + unsigned long adjustedSuggestedOutputLatencyFrames = suggestedOutputLatencyFrames; + if( userFramesPerBuffer != paFramesPerBufferUnspecified && hasInput && hasOutput ) { - unsigned long targetBufferingLatencyFrames = max( suggestedInputLatencyFrames, suggestedOutputLatencyFrames ); - - *pollingPeriodFrames = targetBufferingLatencyFrames / 4; - if( *pollingPeriodFrames < minimumPollingPeriodFrames ) - { - *pollingPeriodFrames = minimumPollingPeriodFrames; - } - else if( *pollingPeriodFrames > maximumPollingPeriodFrames ) - { - *pollingPeriodFrames = maximumPollingPeriodFrames; - } - - *hostBufferSizeFrames = *pollingPeriodFrames - + max( *pollingPeriodFrames + pollingJitterFrames, targetBufferingLatencyFrames); + /* 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. + */ + adjustedSuggestedOutputLatencyFrames -= min( userFramesPerBuffer, adjustedSuggestedOutputLatencyFrames ); } - else - { - unsigned long targetBufferingLatencyFrames = 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; + const unsigned long targetBufferingLatencyFrames = max( suggestedInputLatencyFrames, adjustedSuggestedOutputLatencyFrames ); - /* maximum of input and adjusted output suggested latency */ - if( adjustedSuggestedOutputLatencyFrames > targetBufferingLatencyFrames ) - targetBufferingLatencyFrames = adjustedSuggestedOutputLatencyFrames; - } - } - else - { - /* maximum of input and output suggested latency */ - if( suggestedOutputLatencyFrames > suggestedInputLatencyFrames ) - targetBufferingLatencyFrames = suggestedOutputLatencyFrames; - } + *pollingPeriodFrames = (userFramesPerBuffer == paFramesPerBufferUnspecified) ? + targetBufferingLatencyFrames / 4 : + max( max( 1, userFramesPerBuffer / 4 ), targetBufferingLatencyFrames / 16 ); + *pollingPeriodFrames = min( max( *pollingPeriodFrames, minimumPollingPeriodFrames ), maximumPollingPeriodFrames ); - *hostBufferSizeFrames = userFramesPerBuffer - + max( userFramesPerBuffer + pollingJitterFrames, targetBufferingLatencyFrames); + const unsigned long intendedUserFramesPerBuffer = + (userFramesPerBuffer == paFramesPerBufferUnspecified) ? + *pollingPeriodFrames : + userFramesPerBuffer; + *hostBufferSizeFrames = intendedUserFramesPerBuffer + + max( intendedUserFramesPerBuffer + pollingJitterFrames, targetBufferingLatencyFrames ); - *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), targetBufferingLatencyFrames / 16 ); + /* In some (most?) systems, DirectSound has an odd limitation where it always uses + a fixed 31.25 ms granularity for the read cursor, regardless of parameters. + This in turn means that if we allocate an input buffer that is less than 31.25 ms, + the read cursor will stay stuck at zero. See https://github.com/PortAudio/portaudio/issues/775 + To work around this problem, ensure that the input host buffer is large enough + for at least two 31.25 ms buffer "halves". - if( *pollingPeriodFrames > maximumPollingPeriodFrames ) - { - *pollingPeriodFrames = maximumPollingPeriodFrames; - } + On pre-Vista Windows, we don't do this because DirectSound is implemented very + differently, and is therefore unlikely to suffer from the same issue. + */ + if( hasInput && PaWinUtil_GetOsVersion() >= paOsVersionWindowsVistaServer2008 ) + { + *hostBufferSizeFrames = max( *hostBufferSizeFrames, 2 * 0.03125 * sampleRate ); } } @@ -2120,7 +2101,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, else { CalculateBufferSettings( (unsigned long*)&stream->hostBufferSizeFrames, &pollingPeriodFrames, - /* isFullDuplex = */ (inputParameters && outputParameters), + /* hasInput = */ !!inputParameters, + /* hasOutput = */ !!outputParameters, suggestedInputLatencyFrames, suggestedOutputLatencyFrames, sampleRate, framesPerBuffer );