From 697fcddce5e9f1307711489f5b47b15c139c5e12 Mon Sep 17 00:00:00 2001 From: dmitrykos Date: Wed, 30 Sep 2020 12:55:28 +0300 Subject: [PATCH] wasapi: Improve stability of playback in Polling Shared mode with a low host latency requsted (22 ms): - Implement workaround for system timer coarse granularity causing underruns by using timeBeginPeriod/timeEndPeriod APIs inside the Event and Poll thread handlers. - Update next sleep time of the Poll rendering loop dynamically depending on time taken for processing/rendering of audio data into the available host buffer to avoid underruns and fit processing time into periodic polling time slots. - Add support for time slots logging in Poll mode if PA_WASAPI_LOG_TIME_SLOTS is defined. Fixed audible glitches by preloading the whole host buffer before stream is started. - Fix audible glitches on stream start by preloading the whole host buffer before stream is started. --- src/hostapi/wasapi/pa_win_wasapi.c | 595 +++++++++++++++-------------- 1 file changed, 314 insertions(+), 281 deletions(-) diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c index 7354f8a..17846bd 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -61,6 +61,9 @@ // not change the Event mode to Polling and use the mode which user provided. //#define PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER +//! Poll mode time slots logging. +//#define PA_WASAPI_LOG_TIME_SLOTS + // WinRT #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) #define PA_WINRT @@ -741,6 +744,7 @@ typedef struct ThreadIdleScheduler UINT32 m_resolution; //!< resolution in number of milliseconds } ThreadIdleScheduler; + //! Setup scheduler. static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolution, UINT32 microseconds) { @@ -754,11 +758,12 @@ static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolut sched->m_resolution = resolution; sched->m_next_sleep = (resolution * 1000) / microseconds; } + //! Iterate and check if can sleep. -static UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched) +static inline UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched) { // advance and check if thread can sleep - if (++ sched->m_i == sched->m_next_sleep) + if (++sched->m_i == sched->m_next_sleep) { sched->m_i = 0; return sched->m_resolution; @@ -766,6 +771,81 @@ static UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched) return 0; } +// ------------------------------------------------------------------------------------------ +typedef struct _SystemTimer +{ + UINT32 granularity; + +} SystemTimer; +static LARGE_INTEGER g_SystemTimerFrequency; +static BOOL g_SystemTimerUseQpc = FALSE; + +//! Set granularity of the system timer. +static BOOL SystemTimer_SetGranularity(SystemTimer *timer, UINT32 granularity) +{ +#ifndef PA_WINRT + TIMECAPS caps; + + timer->granularity = granularity; + + if (timeGetDevCaps(&caps, sizeof(caps)) == MMSYSERR_NOERROR) + { + if (timer->granularity < caps.wPeriodMin) + timer->granularity = caps.wPeriodMin; + } + + if (timeBeginPeriod(timer->granularity) != TIMERR_NOERROR) + { + PRINT(("SetSystemTimer: timeBeginPeriod(1) failed!\n")); + + timer->granularity = 0; + return FALSE; + } +#endif + + return TRUE; +} + +//! Restore granularity of the system timer. +static void SystemTimer_RestoreGranularity(SystemTimer *timer) +{ +#ifndef PA_WINRT + if (timer->granularity != 0) + { + if (timeEndPeriod(timer->granularity) != TIMERR_NOERROR) + { + PRINT(("RestoreSystemTimer: timeEndPeriod(1) failed!\n")); + } + } +#endif +} + +//! Initialize high-resolution time getter. +static void SystemTimer_InitializeTimeGetter() +{ + g_SystemTimerUseQpc = QueryPerformanceFrequency(&g_SystemTimerFrequency); +} + +//! Get high-resolution time in milliseconds (using QPC by default). +static inline LONGLONG SystemTimer_GetTime(SystemTimer *timer) +{ + // QPC: https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps + if (g_SystemTimerUseQpc) + { + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return (now.QuadPart * 1000LL) / g_SystemTimerFrequency.QuadPart; + } + else + { + #ifdef PA_WINRT + return GetTickCount64(); + #else + return timeGetTime(); + #endif + } +} + // ------------------------------------------------------------------------------------------ /*static double nano100ToMillis(REFERENCE_TIME ref) { @@ -2140,6 +2220,9 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd // Detect if platform workaround is required paWasapi->useWOW64Workaround = UseWOW64Workaround(); + // Initialize time getter + SystemTimer_InitializeTimeGetter(); + PaUtil_InitializeStreamInterface( &paWasapi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, GetStreamTime, GetStreamCpuLoad, @@ -5507,19 +5590,9 @@ void _StreamOnStop(PaWasapiStream *stream) } // ------------------------------------------------------------------------------------------ -PA_THREAD_FUNC ProcThreadEvent(void *param) +static BOOL PrepareComPointers(PaWasapiStream *stream, BOOL *threadComInitialized) { - PaWasapiHostProcessor processor[S_COUNT]; HRESULT hr; - DWORD dwResult; - PaWasapiStream *stream = (PaWasapiStream *)param; - PaWasapiHostProcessor defaultProcessor; - BOOL set_event[S_COUNT] = { FALSE, FALSE }; - BOOL bWaitAllEvents = FALSE; - BOOL bThreadComInitialized = FALSE; - - // Notify: state - NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS); /* If COM is already initialized CoInitialize will either return @@ -5532,22 +5605,61 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE)) { PRINT(("WASAPI: failed ProcThreadEvent CoInitialize")); - return (UINT32)paUnanticipatedHostError; + return FALSE; } if (hr != RPC_E_CHANGED_MODE) - bThreadComInitialized = TRUE; + *threadComInitialized = TRUE; // Unmarshal stream pointers for safe COM operation hr = UnmarshalStreamComPointers(stream); - if (hr != S_OK) { - PRINT(("Error unmarshaling stream COM pointers. HRESULT: %i\n", hr)); - goto thread_end; + if (hr != S_OK) + { + PRINT(("WASAPI: Error unmarshaling stream COM pointers. HRESULT: %i\n", hr)); + CoUninitialize(); + return FALSE; } + return TRUE; +} + +// ------------------------------------------------------------------------------------------ +static void FinishComPointers(PaWasapiStream *stream, BOOL threadComInitialized) +{ + // Release unmarshaled COM pointers + ReleaseUnmarshaledComPointers(stream); + + // Cleanup COM for this thread + if (threadComInitialized == TRUE) + CoUninitialize(); +} + +// ------------------------------------------------------------------------------------------ +PA_THREAD_FUNC ProcThreadEvent(void *param) +{ + PaWasapiHostProcessor processor[S_COUNT]; + HRESULT hr; + DWORD dwResult; + PaWasapiStream *stream = (PaWasapiStream *)param; + PaWasapiHostProcessor defaultProcessor; + BOOL setEvent[S_COUNT] = { FALSE, FALSE }; + BOOL waitAllEvents = FALSE; + BOOL threadComInitialized = FALSE; + SystemTimer timer; + + // Notify: state + NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS); + + // Prepare COM pointers + if (!PrepareComPointers(stream, &threadComInitialized)) + return (UINT32)paUnanticipatedHostError; + + // Request fine (1 ms) granularity of the system timer functions for precise operation of waitable timers + SystemTimer_SetGranularity(&timer, 1); + // Waiting on all events in case of Full-Duplex/Exclusive mode. if ((stream->in.clientProc != NULL) && (stream->out.clientProc != NULL)) { - bWaitAllEvents = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) && + waitAllEvents = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) && (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE); } @@ -5564,12 +5676,12 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) if (stream->event[S_OUTPUT] == NULL) { stream->event[S_OUTPUT] = CreateEvent(NULL, FALSE, FALSE, NULL); - set_event[S_OUTPUT] = TRUE; + setEvent[S_OUTPUT] = TRUE; } if (stream->event[S_INPUT] == NULL) { stream->event[S_INPUT] = CreateEvent(NULL, FALSE, FALSE, NULL); - set_event[S_INPUT] = TRUE; + setEvent[S_INPUT] = TRUE; } if ((stream->event[S_OUTPUT] == NULL) || (stream->event[S_INPUT] == NULL)) { @@ -5581,7 +5693,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) if (stream->in.clientProc) { // Create & set handle - if (set_event[S_INPUT]) + if (setEvent[S_INPUT]) { if ((hr = IAudioClient_SetEventHandle(stream->in.clientProc, stream->event[S_INPUT])) != S_OK) { @@ -5602,7 +5714,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) if (stream->out.clientProc) { // Create & set handle - if (set_event[S_OUTPUT]) + if (setEvent[S_OUTPUT]) { if ((hr = IAudioClient_SetEventHandle(stream->out.clientProc, stream->event[S_OUTPUT])) != S_OK) { @@ -5640,7 +5752,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) for (;;) { // 10 sec timeout (on timeout stream will auto-stop when processed by WAIT_TIMEOUT case) - dwResult = WaitForMultipleObjects(S_COUNT, stream->event, bWaitAllEvents, 10*1000); + dwResult = WaitForMultipleObjects(S_COUNT, stream->event, waitAllEvents, 10*1000); // Check for close event (after wait for buffers to avoid any calls to user // callback when hCloseRequest was set) @@ -5691,11 +5803,10 @@ thread_end: _StreamOnStop(stream); // Release unmarshaled COM pointers - ReleaseUnmarshaledComPointers(stream); + FinishComPointers(stream, threadComInitialized); - // Cleanup COM for this thread - if (bThreadComInitialized == TRUE) - CoUninitialize(); + // Restore system timer granularity + SystemTimer_RestoreGranularity(&timer); // Notify: not running stream->running = FALSE; @@ -5718,92 +5829,135 @@ thread_error: } // ------------------------------------------------------------------------------------------ -PA_THREAD_FUNC ProcThreadPoll(void *param) +static UINT32 GetSleepTime(PaWasapiStream *stream, UINT32 sleepTimeIn, UINT32 sleepTimeOut, UINT32 userFramesOut) { - PaWasapiHostProcessor processor[S_COUNT]; - HRESULT hr; - PaWasapiStream *stream = (PaWasapiStream *)param; - PaWasapiHostProcessor defaultProcessor; - INT32 i; - ThreadIdleScheduler scheduler; - - // Calculate the actual duration of the allocated buffer. - DWORD sleep_ms = 0; - DWORD sleep_ms_in; - DWORD sleep_ms_out; - - BOOL bThreadComInitialized = FALSE; - - // Notify: state - NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS); + UINT32 sleepTime; - /* - If COM is already initialized CoInitialize will either return - FALSE, or RPC_E_CHANGED_MODE if it was initialized in a different - threading mode. In either case we shouldn't consider it an error - but we need to be careful to not call CoUninitialize() if - RPC_E_CHANGED_MODE was returned. - */ - hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE)) + // According to the issue [https://github.com/PortAudio/portaudio/issues/303] glitches may occur when user frames + // equal to 1/2 of the host buffer frames, therefore the emperical workaround for this problem is to lower + // the sleep time by 2 + if (userFramesOut != 0) { - PRINT(("WASAPI: failed ProcThreadPoll CoInitialize")); - return (UINT32)paUnanticipatedHostError; + UINT32 chunks = stream->out.framesPerHostCallback / userFramesOut; + if (chunks <= 2) + { + sleepTimeOut /= 2; + PRINT(("WASAPI: underrun workaround, sleep [%d] ms - 1/2 of the user buffer[%d] | host buffer[%d]\n", sleepTimeOut, userFramesOut, stream->out.framesPerHostCallback)); + } } - if (hr != RPC_E_CHANGED_MODE) - bThreadComInitialized = TRUE; - // Unmarshal stream pointers for safe COM operation - hr = UnmarshalStreamComPointers(stream); - if (hr != S_OK) - { - PRINT(("Error unmarshaling stream COM pointers. HRESULT: %i\n", hr)); - return 0; - } + // Choose the smallest + if ((sleepTimeIn != 0) && (sleepTimeOut != 0)) + sleepTime = min(sleepTimeIn, sleepTimeOut); + else + sleepTime = (sleepTimeIn ? sleepTimeIn : sleepTimeOut); - // Calculate timeout for next polling attempt. - sleep_ms_in = GetFramesSleepTime(stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec); - sleep_ms_out = GetFramesSleepTime(stream->out.framesPerBuffer, stream->out.wavex.Format.nSamplesPerSec); - sleep_ms_out /= 2; // wait only for half of the buffer + return sleepTime; +} - // WASAPI Input packets tend to expire very easily, let's limit sleep time to 2 milliseconds - // for all cases. Please propose better solution if any. - if (sleep_ms_in > 2) - sleep_ms_in = 2; +// ------------------------------------------------------------------------------------------ +static UINT32 ConfigureLoopSleepTimeAndScheduler(PaWasapiStream *stream, ThreadIdleScheduler *scheduler) +{ + UINT32 sleepTime, sleepTimeIn, sleepTimeOut; + UINT32 userFramesIn = stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER; + UINT32 userFramesOut = stream->out.framesPerBuffer; - // Adjust polling time for non-paUtilFixedHostBufferSize. Input stream is not adjustable as it is being - // polled according its packet length. + // Adjust polling time for non-paUtilFixedHostBufferSize, input stream is not adjustable as it is being + // polled according its packet length if (stream->bufferMode != paUtilFixedHostBufferSize) { - //sleep_ms_in = GetFramesSleepTime((stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : stream->in.params.frames_per_buffer), stream->in.wavex.Format.nSamplesPerSec); - sleep_ms_out = GetFramesSleepTime((stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : stream->out.params.frames_per_buffer), stream->out.wavex.Format.nSamplesPerSec); - sleep_ms_out /= 2; // wait only for half of the buffer + userFramesOut = (stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : + stream->out.params.frames_per_buffer); } - // Choose smallest - if ((sleep_ms_in != 0) && (sleep_ms_out != 0)) - sleep_ms = min(sleep_ms_in, sleep_ms_out); - else - sleep_ms = (sleep_ms_in ? sleep_ms_in : sleep_ms_out); + // Calculate timeout for the next polling attempt + sleepTimeIn = GetFramesSleepTime(userFramesIn, stream->in.wavex.Format.nSamplesPerSec); + sleepTimeOut = GetFramesSleepTime(userFramesOut, stream->out.wavex.Format.nSamplesPerSec); + + // WASAPI input packets tend to expire very easily, let's limit sleep time to 2 milliseconds + // for all cases. Please propose better solution if any + if (sleepTimeIn > 2) + sleepTimeIn = 2; + + sleepTime = GetSleepTime(stream, sleepTimeIn, sleepTimeOut, userFramesOut); - // Make sure not 0, othervise use ThreadIdleScheduler - if (sleep_ms == 0) + // Make sure not 0, othervise use ThreadIdleScheduler to bounce between [0, 1] ms to avoid too busy loop + if (sleepTime == 0) { - sleep_ms_in = GetFramesSleepTimeMicroseconds(stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec); - sleep_ms_out = GetFramesSleepTimeMicroseconds((stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : stream->out.params.frames_per_buffer), stream->out.wavex.Format.nSamplesPerSec); - sleep_ms_out /= 2; // wait only for half of the buffer + sleepTimeIn = GetFramesSleepTimeMicroseconds(userFramesIn, stream->in.wavex.Format.nSamplesPerSec); + sleepTimeOut = GetFramesSleepTimeMicroseconds(userFramesOut, stream->out.wavex.Format.nSamplesPerSec); - // Choose smallest - if ((sleep_ms_in != 0) && (sleep_ms_out != 0)) - sleep_ms = min(sleep_ms_in, sleep_ms_out); - else - sleep_ms = (sleep_ms_in ? sleep_ms_in : sleep_ms_out); + sleepTime = GetSleepTime(stream, sleepTimeIn, sleepTimeOut, userFramesOut); // Setup thread sleep scheduler - ThreadIdleScheduler_Setup(&scheduler, 1, sleep_ms/* microseconds here */); - sleep_ms = 0; + ThreadIdleScheduler_Setup(scheduler, 1, sleepTime/* microseconds here */); + sleepTime = 0; } + return sleepTime; +} + +// ------------------------------------------------------------------------------------------ +static inline INT32 GetNextSleepTime(SystemTimer *timer, ThreadIdleScheduler *scheduler, LONGLONG startTime, + UINT32 sleepTime) +{ + INT32 nextSleepTime; + + // Get next sleep time + if (sleepTime == 0) + nextSleepTime = ThreadIdleScheduler_NextSleep(scheduler); + else + nextSleepTime = sleepTime; + + // Adjust next sleep time dynamically depending on how much time was spent in ProcessOutputBuffer/ProcessInputBuffer + // therefore periodicity will not jitter or be increased for the amount of time spent in processing; + // example when sleepTime is 10 ms where [] is polling time slot, {} processing time slot: + // + // [9],{2},[8],{1},[9],{1},[9],{3},[7],{2},[8],{3},[7],{2},[8],{2},[8],{3},[7],{2},[8],... + // + INT32 procTime = (INT32)(SystemTimer_GetTime(timer) - startTime); + nextSleepTime -= procTime; + if (nextSleepTime < 0) + nextSleepTime = 0; + +#ifdef PA_WASAPI_LOG_TIME_SLOTS + printf("{%d},", procTime); +#endif + + return nextSleepTime; +} + +// ------------------------------------------------------------------------------------------ +PA_THREAD_FUNC ProcThreadPoll(void *param) +{ + PaWasapiHostProcessor processor[S_COUNT]; + HRESULT hr; + PaWasapiStream *stream = (PaWasapiStream *)param; + PaWasapiHostProcessor defaultProcessor; + INT32 i; + ThreadIdleScheduler scheduler; + SystemTimer timer; + LONGLONG startTime; + UINT32 sleepTime; + INT32 nextSleepTime = 0; //! Do first loop without waiting as time could be spent when calling other APIs before ProcessXXXBuffer. + BOOL threadComInitialized = FALSE; +#ifdef PA_WASAPI_LOG_TIME_SLOTS + LONGLONG startWaitTime; +#endif + + // Notify: state + NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS); + + // Prepare COM pointers + if (!PrepareComPointers(stream, &threadComInitialized)) + return (UINT32)paUnanticipatedHostError; + + // Request fine (1 ms) granularity of the system timer functions to guarantee correct logic around WaitForSingleObject + SystemTimer_SetGranularity(&timer, 1); + + // Claculate sleep time of the processing loop (inside WaitForSingleObject) + sleepTime = ConfigureLoopSleepTimeAndScheduler(stream, &scheduler); + // Setup data processors defaultProcessor.processor = WaspiHostProcessingLoop; defaultProcessor.userData = stream; @@ -5832,17 +5986,20 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) { UINT32 frames = 0; if ((hr = _PollGetOutputFramesAvailable(stream, &frames)) == S_OK) - { + { if (stream->bufferMode == paUtilFixedHostBufferSize) { - if (frames >= stream->out.framesPerBuffer) + // It is important to preload whole host buffer to avoid underruns/glitches when stream is started, + // for more details see the discussion: https://github.com/PortAudio/portaudio/issues/303 + while (frames >= stream->out.framesPerBuffer) { - frames = stream->out.framesPerBuffer; - - if ((hr = ProcessOutputBuffer(stream, processor, frames)) != S_OK) + if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK) { LogHostError(hr); // not fatal, just log + break; } + + frames -= stream->out.framesPerBuffer; } } else @@ -5862,8 +6019,8 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) LogHostError(hr); // not fatal, just log } } - } - else + } + else { LogHostError(hr); // not fatal, just log } @@ -5886,16 +6043,20 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) // Notify: state NotifyStateChanged(stream, paWasapiStreamStateThreadStart, ERROR_SUCCESS); +#ifdef PA_WASAPI_LOG_TIME_SLOTS + startWaitTime = SystemTimer_GetTime(&timer); +#endif + if (!PA_WASAPI__IS_FULLDUPLEX(stream)) { - UINT32 next_sleep = sleep_ms; - // Processing Loop - while (WaitForSingleObject(stream->hCloseRequest, next_sleep) == WAIT_TIMEOUT) + while (WaitForSingleObject(stream->hCloseRequest, nextSleepTime) == WAIT_TIMEOUT) { - // Get next sleep time - if (sleep_ms == 0) - next_sleep = ThreadIdleScheduler_NextSleep(&scheduler); + startTime = SystemTimer_GetTime(&timer); + + #ifdef PA_WASAPI_LOG_TIME_SLOTS + printf("[%d|%d],", nextSleepTime, (INT32)(startTime - startWaitTime)); + #endif for (i = 0; i < S_COUNT; ++i) { @@ -5919,12 +6080,13 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) // Output stream case S_OUTPUT: { - UINT32 frames; + UINT32 framesAvail; + if (stream->renderClient == NULL) break; // Get available frames - if ((hr = _PollGetOutputFramesAvailable(stream, &frames)) != S_OK) + if ((hr = _PollGetOutputFramesAvailable(stream, &framesAvail)) != S_OK) { LogHostError(hr); goto thread_error; @@ -5933,21 +6095,33 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) // Output data to the user callback if (stream->bufferMode == paUtilFixedHostBufferSize) { - while (frames >= stream->out.framesPerBuffer) + UINT32 framesProc = stream->out.framesPerBuffer; + + // If we got less frames avoid sleeping again as it might be the corner case and buffer + // has sufficient number of frames now, in case 'out.framesPerBuffer' is 1/2 of the host + // buffer sleeping again may cause underruns. Do short busy waiting (normally might take + // 1-2 iterations) + if (framesAvail < framesProc) { - if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK) + nextSleepTime = 0; + continue; + } + + while (framesAvail >= framesProc) + { + if ((hr = ProcessOutputBuffer(stream, processor, framesProc)) != S_OK) { LogHostError(hr); goto thread_error; } - frames -= stream->out.framesPerBuffer; + framesAvail -= framesProc; } } else - if (frames != 0) + if (framesAvail != 0) { - if ((hr = ProcessOutputBuffer(stream, processor, frames)) != S_OK) + if ((hr = ProcessOutputBuffer(stream, processor, framesAvail)) != S_OK) { LogHostError(hr); goto thread_error; @@ -5957,143 +6131,29 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) break; } } } + + // Get next sleep time + nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime); + + #ifdef PA_WASAPI_LOG_TIME_SLOTS + startWaitTime = SystemTimer_GetTime(&timer); + #endif } } else { -#if 0 - // Processing Loop - while (WaitForSingleObject(stream->hCloseRequest, 1) == WAIT_TIMEOUT) + // Processing Loop (full-duplex) + while (WaitForSingleObject(stream->hCloseRequest, nextSleepTime) == WAIT_TIMEOUT) { - UINT32 i_frames = 0, i_processed = 0; + UINT32 i_frames = 0, i_processed = 0, o_frames = 0; BYTE *i_data = NULL, *o_data = NULL, *o_data_host = NULL; DWORD i_flags = 0; - UINT32 o_frames = 0; - - // get host input buffer - if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK) - { - if (hr == AUDCLNT_S_BUFFER_EMPTY) - continue; // no data in capture buffer - - LogHostError(hr); - break; - } - - // get available frames - if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK) - { - // release input buffer - IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0); - - LogHostError(hr); - break; - } - - // process equal ammount of frames - if (o_frames >= i_frames) - { - // process input ammount of frames - UINT32 o_processed = i_frames; - - // get host output buffer - if ((hr = IAudioRenderClient_GetBuffer(stream->procRCClient, o_processed, &o_data)) == S_OK) - { - // processed amount of i_frames - i_processed = i_frames; - o_data_host = o_data; - - // convert output mono - if (stream->out.monoMixer) - { - UINT32 mono_frames_size = o_processed * (stream->out.wavex.Format.wBitsPerSample / 8); - // expand buffer - if (mono_frames_size > stream->out.monoBufferSize) - { - stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); - if (stream->out.monoBuffer == NULL) - { - // release input buffer - IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0); - // release output buffer - IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0); - - LogPaError(paInsufficientMemory); - break; - } - } - - // replace buffer pointer - o_data = (BYTE *)stream->out.monoBuffer; - } - - // convert input mono - if (stream->in.monoMixer) - { - UINT32 mono_frames_size = i_processed * (stream->in.wavex.Format.wBitsPerSample / 8); - // expand buffer - if (mono_frames_size > stream->in.monoBufferSize) - { - stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); - if (stream->in.monoBuffer == NULL) - { - // release input buffer - IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0); - // release output buffer - IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0); - - LogPaError(paInsufficientMemory); - break; - } - } - - // mix 2 to 1 input channels - stream->in.monoMixer(stream->in.monoBuffer, i_data, i_processed); - - // replace buffer pointer - i_data = (BYTE *)stream->in.monoBuffer; - } - - // process - processor[S_FULLDUPLEX].processor(i_data, i_processed, o_data, o_processed, processor[S_FULLDUPLEX].userData); - - // mix 1 to 2 output channels - if (stream->out.monoBuffer) - stream->out.monoMixer(o_data_host, stream->out.monoBuffer, o_processed); - - // release host output buffer - if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, o_processed, 0)) != S_OK) - LogHostError(hr); - } - else - { - if (stream->out.shareMode != AUDCLNT_SHAREMODE_SHARED) - LogHostError(hr); // be silent in shared mode, try again next time - } - } - - // release host input buffer - if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, i_processed)) != S_OK) - { - LogHostError(hr); - break; - } - } -#else - // Processing Loop - UINT32 next_sleep = sleep_ms; - while (WaitForSingleObject(stream->hCloseRequest, next_sleep) == WAIT_TIMEOUT) - { - UINT32 i_frames = 0, i_processed = 0; - BYTE *i_data = NULL, *o_data = NULL, *o_data_host = NULL; - DWORD i_flags = 0; - UINT32 o_frames = 0; + + startTime = SystemTimer_GetTime(&timer); - // Get next sleep time - if (sleep_ms == 0) - { - next_sleep = ThreadIdleScheduler_NextSleep(&scheduler); - } + #ifdef PA_WASAPI_LOG_TIME_SLOTS + printf("[%d|%d],", nextSleepTime, (INT32)(startTime - startWaitTime)); + #endif // get available frames if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK) @@ -6216,8 +6276,14 @@ fd_release_buffer_in: if (i_processed == 0) break; } + + // Get next sleep time + nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime); + + #ifdef PA_WASAPI_LOG_TIME_SLOTS + startWaitTime = SystemTimer_GetTime(&timer); + #endif } -#endif } thread_end: @@ -6226,11 +6292,10 @@ thread_end: _StreamOnStop(stream); // Release unmarshaled COM pointers - ReleaseUnmarshaledComPointers(stream); + FinishComPointers(stream, threadComInitialized); - // Cleanup COM for this thread - if (bThreadComInitialized == TRUE) - CoUninitialize(); + // Restore system timer granularity + SystemTimer_RestoreGranularity(&timer); // Notify: not running stream->running = FALSE; @@ -6269,35 +6334,3 @@ void PaWasapi_FreeMemory(void *ptr) { free(ptr); } - -//#endif //VC 2005 - - - - -#if 0 - if(bFirst) { - float masteur; - hr = stream->outVol->GetMasterVolumeLevelScalar(&masteur); - if (hr != S_OK) - LogHostError(hr); - float chan1, chan2; - hr = stream->outVol->GetChannelVolumeLevelScalar(0, &chan1); - if (hr != S_OK) - LogHostError(hr); - hr = stream->outVol->GetChannelVolumeLevelScalar(1, &chan2); - if (hr != S_OK) - LogHostError(hr); - - BOOL bMute; - hr = stream->outVol->GetMute(&bMute); - if (hr != S_OK) - LogHostError(hr); - - stream->outVol->SetMasterVolumeLevelScalar(0.5, NULL); - stream->outVol->SetChannelVolumeLevelScalar(0, 0.5, NULL); - stream->outVol->SetChannelVolumeLevelScalar(1, 0.5, NULL); - stream->outVol->SetMute(FALSE, NULL); - bFirst = FALSE; - } -#endif -- 2.43.0