From: rossb Date: Wed, 11 May 2011 10:00:52 +0000 (+0000) Subject: Replaced MM timer with a high priority thread for audio compute, based on a patch... X-Git-Tag: pa_stable_v19_20111121_r1788~80 X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=28397f5c4a44a7003d0ffeac290c56fd34a45c4c;p=portaudio Replaced MM timer with a high priority thread for audio compute, based on a patch by Dmitry Kostjuchenko. Reason: there is one global MM timer thread per process and doing audio compute in it was interfering with timer accuracy and also with running multiple DSound streams. The old MM timer based implementation is still available if you define PA_WIN_DS_USE_WMME_TIMER -- this may be removed in future. --- diff --git a/src/hostapi/dsound/pa_win_ds.c b/src/hostapi/dsound/pa_win_ds.c index df9cad5..217d111 100644 --- a/src/hostapi/dsound/pa_win_ds.c +++ b/src/hostapi/dsound/pa_win_ds.c @@ -41,10 +41,17 @@ @ingroup hostapi_src */ +/* Until May 2011 PA/DS has used a multimedia timer to perform the callback. + 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 + #include #include #include /* strlen() */ +#define _WIN32_WINNT 0x0400 /* required to get waitable timer APIs */ #include /* make sure ds guids get defined */ #include #include @@ -61,6 +68,11 @@ #ifdef PAWIN_USE_WDMKS_DEVICE_INFO #include #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ +#ifndef PA_WIN_DS_USE_WMME_TIMER +#ifndef UNDER_CE +#include +#endif +#endif #include "pa_util.h" #include "pa_allocation.h" @@ -79,8 +91,39 @@ #if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ #pragma comment( lib, "dsound.lib" ) #pragma comment( lib, "winmm.lib" ) +#pragma comment( lib, "kernel32.lib" ) +#endif + +/* use CreateThread for CYGWIN, _beginthreadex for all others */ +#ifndef PA_WIN_DS_USE_WMME_TIMER + +#if !defined(__CYGWIN__) && !defined(UNDER_CE) +#define CREATE_THREAD (HANDLE)_beginthreadex +#undef CLOSE_THREAD_HANDLE /* as per documentation we don't call CloseHandle on a thread created with _beginthreadex */ +#define PA_THREAD_FUNC static unsigned WINAPI +#define PA_THREAD_ID unsigned +#else +#define CREATE_THREAD CreateThread +#define CLOSE_THREAD_HANDLE CloseHandle +#define PA_THREAD_FUNC static DWORD WINAPI +#define PA_THREAD_ID DWORD +#endif + +#if (defined(UNDER_CE)) +#pragma comment(lib, "Coredll.lib") +#elif (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ +#pragma comment(lib, "winmm.lib") #endif +PA_THREAD_FUNC ProcessingThreadProc( void *pArg ); + +#if !defined(UNDER_CE) +#define PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT /* use waitable timer where possible, otherwise we use a WaitForSingleObject timeout */ +#endif + +#endif /* !PA_WIN_DS_USE_WMME_TIMER */ + + /* provided in newer platform sdks and x64 */ @@ -228,7 +271,6 @@ typedef struct PaWinDsStream UINT inputSize; - MMRESULT timerID; int framesPerDSBuffer; double framesWritten; double secondsPerHostByte; /* Used to optimize latency calculation for outTime */ @@ -244,6 +286,21 @@ typedef struct PaWinDsStream volatile int isActive; volatile int stopProcessing; /* stop thread once existing buffers have been returned */ volatile int abortProcessing; /* stop thread immediately */ + + UINT timerPeriod; /* set to 0 if we were unable to set the timer period */ + +#ifdef PA_WIN_DS_USE_WMME_TIMER + MMRESULT timerID; +#else + +#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT + HANDLE waitableTimer; +#endif + HANDLE processingThread; + PA_THREAD_ID processingThreadId; + HANDLE processingThreadCompleted; +#endif + } PaWinDsStream; @@ -1164,23 +1221,10 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde return result; error: - if( winDsHostApi ) - { - if( winDsHostApi->allocations ) - { - PaUtil_FreeAllAllocations( winDsHostApi->allocations ); - PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); - } - - PaWinUtil_CoUninitialize( paDirectSound, &winDsHostApi->comInitializationResult ); - - PaUtil_FreeMemory( winDsHostApi ); - } - TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); - PaWinDs_TerminateDSoundEntryPoints(); + Terminate( winDsHostApi ); return result; } @@ -1191,20 +1235,17 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) { PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; - /* - IMPLEMENT ME: - - clean up any resources not handled by the allocation group - */ - - if( winDsHostApi->allocations ) - { - PaUtil_FreeAllAllocations( winDsHostApi->allocations ); - PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); - } + if( winDsHostApi ){ + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } - PaWinUtil_CoUninitialize( paDirectSound, &winDsHostApi->comInitializationResult ); + PaWinUtil_CoUninitialize( paDirectSound, &winDsHostApi->comInitializationResult ); - PaUtil_FreeMemory( winDsHostApi ); + PaUtil_FreeMemory( winDsHostApi ); + } PaWinDs_TerminateDSoundEntryPoints(); } @@ -1844,8 +1885,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, 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 ) { @@ -1853,6 +1895,26 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, goto error; } +#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT + stream->waitableTimer = (HANDLE)CreateWaitableTimer( 0, FALSE, NULL ); + if( stream->waitableTimer == NULL ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); + goto error; + } +#endif + +#ifndef PA_WIN_DS_USE_WMME_TIMER + stream->processingThreadCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL ); + if( stream->processingThreadCompleted == NULL ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); + goto error; + } +#endif + /* Get system minimum latency. */ minLatencyFrames = PaWinDs_GetMinLatencyFrames( sampleRate ); @@ -2032,6 +2094,17 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, goto error; } } + + { + /* set the windows scheduler granularity using timeBeginPeriod */ + TIMECAPS timecaps; + /* set windows scheduler granularity to as fine as possible */ + if( timeGetDevCaps( &timecaps, sizeof(TIMECAPS) == MMSYSERR_NOERROR && timecaps.wPeriodMin > 0 ) ) + { + if( timeBeginPeriod( timecaps.wPeriodMin ) == MMSYSERR_NOERROR ) + stream->timerPeriod = timecaps.wPeriodMin; /* save the period so we can reset it later */ + } + } } *s = (PaStream*)stream; @@ -2041,9 +2114,22 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, error: if( stream ) { + if( stream->timerPeriod > 0 ) + timeEndPeriod( stream->timerPeriod ); + if( stream->processingCompleted != NULL ) CloseHandle( stream->processingCompleted ); +#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT + if( stream->waitableTimer != NULL ) + CloseHandle( stream->waitableTimer ); +#endif + +#ifndef PA_WIN_DS_USE_WMME_TIMER + if( stream->processingThreadCompleted != NULL ) + CloseHandle( stream->processingThreadCompleted ); +#endif + if( stream->pDirectSoundOutputBuffer ) { IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer ); @@ -2247,7 +2333,6 @@ static int TimeSlice( PaWinDsStream *stream ) if( framesToXfer > 0 ) { - PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); /* The outputBufferDacTime parameter should indicates the time at which @@ -2452,6 +2537,75 @@ static void CALLBACK TimerCallback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD } } +#ifndef PA_WIN_DS_USE_WMME_TIMER + +#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT + +static void CALLBACK WaitableTimerAPCProc( + LPVOID lpArg, // Data value + DWORD dwTimerLowValue, // Timer low value + DWORD dwTimerHighValue ) // Timer high value + +{ + (void)dwTimerLowValue; + (void)dwTimerHighValue; + + TimerCallback( 0, 0, (DWORD_PTR)lpArg, 0, 0 ); +} + +#endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */ + + +PA_THREAD_FUNC ProcessingThreadProc( void *pArg ) +{ + PaWinDsStream *stream = (PaWinDsStream *)pArg; + MMRESULT mmResult; + HANDLE hWaitableTimer; + LARGE_INTEGER dueTime; + int framesPerWakeup, msecPerWakeup; + + 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; + +#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT + assert( stream->waitableTimer != NULL ); + + /* invoke first timeout immediately */ + dueTime.LowPart = msecPerWakeup * 1000 * 10; + dueTime.HighPart = 0; + + /* tick using waitable timer */ + if( SetWaitableTimer( stream->waitableTimer, &dueTime, msecPerWakeup, 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 ); + + }while( wfsoResult == WAIT_TIMEOUT || wfsoResult == WAIT_IO_COMPLETION ); + } + + CancelWaitableTimer( stream->waitableTimer ); + +#else + + /* tick using WaitForSingleObject timout */ + while ( WaitForSingleObject( stream->processingCompleted, msecPerWakeup ) == WAIT_TIMEOUT ) + { + TimerCallback( 0, 0, (DWORD_PTR)pArg, 0, 0 ); + } +#endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */ + + SetEvent( stream->processingThreadCompleted ); + + return 0; +} + +#endif /* !PA_WIN_DS_USE_WMME_TIMER */ + /*********************************************************************************** When CloseStream() is called, the multi-api layer ensures that the stream has already been stopped or aborted. @@ -2463,6 +2617,15 @@ static PaError CloseStream( PaStream* s ) CloseHandle( stream->processingCompleted ); +#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT + if( stream->waitableTimer != NULL ) + CloseHandle( stream->waitableTimer ); +#endif + +#ifndef PA_WIN_DS_USE_WMME_TIMER + CloseHandle( stream->processingThreadCompleted ); +#endif + // Cleanup the sound buffers if( stream->pDirectSoundOutputBuffer ) { @@ -2557,6 +2720,10 @@ static PaError StartStream( PaStream *s ) ResetEvent( stream->processingCompleted ); +#ifndef PA_WIN_DS_USE_WMME_TIMER + ResetEvent( stream->processingThreadCompleted ); +#endif + if( stream->bufferProcessor.inputChannelCount > 0 ) { // Start the buffer capture @@ -2579,7 +2746,6 @@ static PaError StartStream( PaStream *s ) stream->abortProcessing = 0; stream->stopProcessing = 0; - stream->isActive = 1; if( stream->bufferProcessor.outputChannelCount > 0 ) { @@ -2622,7 +2788,9 @@ static PaError StartStream( PaStream *s ) if( stream->streamRepresentation.streamCallback ) { +#ifdef PA_WIN_DS_USE_WMME_TIMER /* Create timer that will wake us up so we can fill the DSound buffer. */ + int resolution; int framesPerWakeup = stream->framesPerDSBuffer / 4; int msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate; @@ -2636,14 +2804,51 @@ static PaError StartStream( PaStream *s ) { stream->isActive = 0; result = paUnanticipatedHostError; - PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); + goto error; + } +#else + /* Create processing thread which calls TimerCallback */ + + stream->processingThread = CREATE_THREAD( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId ); + if( !stream->processingThread ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); + goto error; + } + + if( !SetThreadPriority( stream->processingThread, THREAD_PRIORITY_TIME_CRITICAL ) ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); goto error; } +#endif } - stream->isStarted = TRUE; + stream->isActive = 1; + stream->isStarted = 1; + + assert( result == paNoError ); + return result; error: + + if( stream->pDirectSoundOutputBuffer != NULL && stream->outputIsRunning ) + IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer ); + stream->outputIsRunning = FALSE; + +#ifndef PA_WIN_DS_USE_WMME_TIMER + if( stream->processingThread ) + { +#ifdef CLOSE_THREAD_HANDLE + CLOSE_THREAD_HANDLE( stream->processingThread ); /* Delete thread. */ +#endif + stream->processingThread = NULL; + } +#endif + return result; } @@ -2666,12 +2871,25 @@ static PaError StopStream( PaStream *s ) WaitForSingleObject( stream->processingCompleted, timeoutMsec ); } +#ifdef PA_WIN_DS_USE_WMME_TIMER if( stream->timerID != 0 ) { timeKillEvent(stream->timerID); /* Stop callback timer. */ stream->timerID = 0; } +#else + if( stream->processingThread ) + { + if( WaitForSingleObject( stream->processingThreadCompleted, 30*100 ) == WAIT_TIMEOUT ) + return paUnanticipatedHostError; +#ifdef CLOSE_THREAD_HANDLE + CloseHandle( stream->processingThread ); /* Delete thread. */ + stream->processingThread = NULL; +#endif + + } +#endif if( stream->bufferProcessor.outputChannelCount > 0 ) { @@ -2697,7 +2915,7 @@ static PaError StopStream( PaStream *s ) } } - stream->isStarted = FALSE; + stream->isStarted = 0; return result; }