Skip to content

Commit d0290d4

Browse files
committed
dsound: clamp input buffer size at 62.5 ms
This works around a DirectSound limitation where input host buffer sizes smaller than 31.25 ms are basically unworkable and make PortAudio hang. The workaround is to impose a minimal buffer size of 2*31.25 ms on input-only and full-duplex streams. This is enough for the read cursor to advance twice around the buffer, basically resulting in de facto double buffering. This change was tested with `paloopback` under a wide variety of half/full-duplex, framesPerBuffer, and suggested latency parameters. (Note the testing was done on top of #772 as otherwise paloopback is not usable.) Fixes #775
1 parent 060a0a6 commit d0290d4

File tree

1 file changed

+38
-14
lines changed

1 file changed

+38
-14
lines changed

src/hostapi/dsound/pa_win_ds.c

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ typedef struct PaWinDsStream
326326
/* Set minimal latency based on the current OS version.
327327
* NT has higher latency.
328328
*/
329-
static double PaWinDS_GetMinSystemLatencySeconds( void )
329+
static DWORD PaWinDS_GetWindowsMajorVersion( void )
330330
{
331331
/*
332332
NOTE: GetVersionEx() is deprecated as of Windows 8.1 and can not be used to reliably detect
@@ -338,23 +338,33 @@ See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprec
338338
*/
339339
#pragma warning (disable : 4996) /* use of GetVersionEx */
340340

341+
OSVERSIONINFO osvi;
342+
osvi.dwOSVersionInfoSize = sizeof(osvi);
343+
GetVersionEx(&osvi);
344+
return osvi.dwMajorVersion;
345+
346+
#pragma warning (default : 4996)
347+
}
348+
349+
350+
/* Set minimal latency based on the current OS version.
351+
* NT has higher latency.
352+
*/
353+
static double PaWinDS_GetMinSystemLatencySeconds( void )
354+
{
341355
double minLatencySeconds;
342356
/* Set minimal latency based on whether NT or other OS.
343357
* NT has higher latency.
344358
*/
345359

346-
OSVERSIONINFO osvi;
347-
osvi.dwOSVersionInfoSize = sizeof( osvi );
348-
GetVersionEx( &osvi );
349-
DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
350-
DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
351-
DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
360+
const DWORD windowsMajorVersion = PaWinDS_GetWindowsMajorVersion();
361+
DBUG(("PA - windowsMajorVersion = 0x%x\n", windowsMajorVersion));
352362
/* Check for NT */
353-
if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
363+
if( (windowsMajorVersion == 4) && (windowsMajorVersion == 2) )
354364
{
355365
minLatencySeconds = PA_DS_WIN_NT_DEFAULT_LATENCY_;
356366
}
357-
else if(osvi.dwMajorVersion >= 5)
367+
else if(windowsMajorVersion >= 5)
358368
{
359369
minLatencySeconds = PA_DS_WIN_WDM_DEFAULT_LATENCY_;
360370
}
@@ -363,8 +373,6 @@ See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprec
363373
minLatencySeconds = PA_DS_WIN_9X_DEFAULT_LATENCY_;
364374
}
365375
return minLatencySeconds;
366-
367-
#pragma warning (default : 4996)
368376
}
369377

370378

@@ -1741,7 +1749,7 @@ static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *devic
17411749

17421750
static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames,
17431751
unsigned long *pollingPeriodFrames,
1744-
int isFullDuplex,
1752+
int hasInput, int hasOutput,
17451753
unsigned long suggestedInputLatencyFrames,
17461754
unsigned long suggestedOutputLatencyFrames,
17471755
double sampleRate, unsigned long userFramesPerBuffer )
@@ -1751,7 +1759,7 @@ static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames,
17511759
unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS);
17521760

17531761
unsigned long adjustedSuggestedOutputLatencyFrames = suggestedOutputLatencyFrames;
1754-
if( userFramesPerBuffer != paFramesPerBufferUnspecified && isFullDuplex )
1762+
if( userFramesPerBuffer != paFramesPerBufferUnspecified && hasInput && hasOutput )
17551763
{
17561764
/* In full duplex streams we know that the buffer adapter adds userFramesPerBuffer
17571765
extra fixed latency. so we subtract it here as a fixed latency before computing
@@ -1773,6 +1781,21 @@ static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames,
17731781
userFramesPerBuffer;
17741782
*hostBufferSizeFrames = intendedUserFramesPerBuffer
17751783
+ max( intendedUserFramesPerBuffer + pollingJitterFrames, targetBufferingLatencyFrames );
1784+
1785+
/* In some (most?) systems, DirectSound has an odd limitation where it always uses
1786+
a fixed 31.25 ms granularity for the read cursor, regardless of parameters.
1787+
This in turn means that if we allocate an input buffer that is less than 31.25 ms,
1788+
the read cursor will stay stuck at zero. See https://github.com/PortAudio/portaudio/issues/775
1789+
To work around this problem, ensure that the input host buffer is large enough
1790+
for at least two 31.25 ms buffer "halves".
1791+
1792+
On pre-Vista Windows, we don't do this because DirectSound is implemented very
1793+
differently, and is therefore unlikely to suffer from the same issue.
1794+
*/
1795+
if( hasInput && PaWinDS_GetWindowsMajorVersion() >= 6 )
1796+
{
1797+
*hostBufferSizeFrames = max( *hostBufferSizeFrames, 2 * 0.03125 * sampleRate );
1798+
}
17761799
}
17771800

17781801

@@ -2108,7 +2131,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
21082131
else
21092132
{
21102133
CalculateBufferSettings( (unsigned long*)&stream->hostBufferSizeFrames, &pollingPeriodFrames,
2111-
/* isFullDuplex = */ (inputParameters && outputParameters),
2134+
/* hasInput = */ !!inputParameters,
2135+
/* hasOutput = */ !!outputParameters,
21122136
suggestedInputLatencyFrames,
21132137
suggestedOutputLatencyFrames,
21142138
sampleRate, framesPerBuffer );

0 commit comments

Comments
 (0)