From: rossb Date: Thu, 25 Aug 2011 15:59:32 +0000 (+0000) Subject: Fixed directsound PaDeviceInfo default latency and PaStreamInfo latency fields. Fixed... X-Git-Tag: pa_stable_v19_20111121_r1788~31 X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=4939e412482837c441abd105b4eca3ae0557265e;p=portaudio Fixed directsound PaDeviceInfo default latency and PaStreamInfo latency fields. Fixed host buffer size calculations based on suggestedLatency parameters. Switched to using 48kHz as default sample rate when other info isn't available. Fixed edge case in TimeSlice() where a full duplex stream could read data even though either input or output reported zero bytes available. Renamed some variables for clarity. Fixes ticket #160 (PaStreamInfo latency). Addresses #122 (although we're still using old default latency values from V18 codebase). Addresses #99 (correct interpretation of suggestedLatency down to zero). --- diff --git a/include/pa_win_ds.h b/include/pa_win_ds.h index cfbf464..2410658 100644 --- a/include/pa_win_ds.h +++ b/include/pa_win_ds.h @@ -59,15 +59,14 @@ extern "C" typedef struct PaWinDirectSoundStreamInfo{ unsigned long size; /**< sizeof(PaWinDirectSoundStreamInfo) */ PaHostApiTypeId hostApiType; /**< paDirectSound */ - unsigned long version; /**< 1 */ + unsigned long version; /**< 2 */ unsigned long flags; /* low-level latency setting support - TODO ** NOT IMPLEMENTED ** - These settings control the number and size of host buffers in order - to set latency. They will be used instead of the generic parameters - to Pa_OpenStream() if flags contains the paWinDirectSoundUseLowLevelLatencyParameters + Control the size of host buffers in order to set latency. They will + be used instead of the generic parameters to Pa_OpenStream() if + flags contains the paWinDirectSoundUseLowLevelLatencyParameters flag. If PaWinDirectSoundStreamInfo structures with paWinDirectSoundUseLowLevelLatencyParameters @@ -76,9 +75,8 @@ typedef struct PaWinDirectSoundStreamInfo{ two must be a multiple of the smaller, otherwise a paIncompatibleHostApiSpecificStreamInfo error will be returned from Pa_OpenStream(). - - unsigned long framesPerBuffer; */ + unsigned long framesPerBuffer; /* NOT IMPLEMENTED see http://www.portaudio.com/trac/ticket/129 */ /* support for WAVEFORMATEXTENSIBLE channel masks. If flags contains diff --git a/src/hostapi/dsound/pa_win_ds.c b/src/hostapi/dsound/pa_win_ds.c index 5a7fee3..c6f209c 100644 --- a/src/hostapi/dsound/pa_win_ds.c +++ b/src/hostapi/dsound/pa_win_ds.c @@ -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 #include @@ -56,6 +56,7 @@ #include #include + /* 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 ); }