Skip to content

Commit

Permalink
wasapi: Added support for time slots logging in Poll mode if PA_WASAP…
Browse files Browse the repository at this point in the history
…I_LOG_TIME_SLOTS is defined, reverted workaround into GetSleepTime to poll 1/2 of the user buffer when user buffer is 1/2 of the host buffer, some cleanup
  • Loading branch information
dmitrykos committed Oct 1, 2020
1 parent 1e4ee20 commit 28ac8b4
Showing 1 changed file with 116 additions and 94 deletions.
210 changes: 116 additions & 94 deletions src/hostapi/wasapi/pa_win_wasapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -824,7 +827,7 @@ static void SystemTimer_InitializeTimeGetter()
}

//! Get high-resolution time in milliseconds (using QPC by default).
static inline LONGLONG SystemTimer_GetTimeMsec(SystemTimer *timer)
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)
Expand Down Expand Up @@ -5638,8 +5641,8 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
DWORD dwResult;
PaWasapiStream *stream = (PaWasapiStream *)param;
PaWasapiHostProcessor defaultProcessor;
BOOL set_event[S_COUNT] = { FALSE, FALSE };
BOOL bWaitAllEvents = FALSE;
BOOL setEvent[S_COUNT] = { FALSE, FALSE };
BOOL waitAllEvents = FALSE;
BOOL threadComInitialized = FALSE;
SystemTimer timer;

Expand All @@ -5656,7 +5659,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
// 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);
}

Expand All @@ -5673,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))
{
Expand All @@ -5690,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)
{
Expand All @@ -5711,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)
{
Expand Down Expand Up @@ -5749,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)
Expand Down Expand Up @@ -5826,73 +5829,102 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
}

// ------------------------------------------------------------------------------------------
static UINT32 ConfigureLoopSleepTimeAndScheduler(PaWasapiStream *stream, ThreadIdleScheduler *scheduler)
static UINT32 GetSleepTime(PaWasapiStream *stream, UINT32 sleepTimeIn, UINT32 sleepTimeOut, UINT32 userFramesOut)
{
DWORD sleepMs, sleepMsecIn, sleepMsecOut;
UINT32 sleepTime;

// 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)
{
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));
}
}

// Choose the smallest
if ((sleepTimeIn != 0) && (sleepTimeOut != 0))
sleepTime = min(sleepTimeIn, sleepTimeOut);
else
sleepTime = (sleepTimeIn ? sleepTimeIn : sleepTimeOut);

// Calculate timeout for next polling attempt.
sleepMsecIn = GetFramesSleepTime(stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec);
sleepMsecOut = GetFramesSleepTime(stream->out.framesPerBuffer, stream->out.wavex.Format.nSamplesPerSec);
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 (sleepMsecIn > 2)
sleepMsecIn = 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)
{
//sleepMsecIn = GetFramesSleepTime((stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : stream->in.params.frames_per_buffer), stream->in.wavex.Format.nSamplesPerSec);
sleepMsecOut = GetFramesSleepTime((stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : stream->out.params.frames_per_buffer), stream->out.wavex.Format.nSamplesPerSec);
userFramesOut = (stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer :
stream->out.params.frames_per_buffer);
}

// Choose smallest
if ((sleepMsecIn != 0) && (sleepMsecOut != 0))
sleepMs = min(sleepMsecIn, sleepMsecOut);
else
sleepMs = (sleepMsecIn ? sleepMsecIn : sleepMsecOut);
// 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;

// Make sure not 0, othervise use ThreadIdleScheduler
if (sleepMs == 0)
sleepTime = GetSleepTime(stream, sleepTimeIn, sleepTimeOut, userFramesOut);

// Make sure not 0, othervise use ThreadIdleScheduler to bounce between [0, 1] ms to avoid too busy loop
if (sleepTime == 0)
{
sleepMsecIn = GetFramesSleepTimeMicroseconds(stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec);
sleepMsecOut = GetFramesSleepTimeMicroseconds((stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : stream->out.params.frames_per_buffer), stream->out.wavex.Format.nSamplesPerSec);
sleepTimeIn = GetFramesSleepTimeMicroseconds(userFramesIn, stream->in.wavex.Format.nSamplesPerSec);
sleepTimeOut = GetFramesSleepTimeMicroseconds(userFramesOut, stream->out.wavex.Format.nSamplesPerSec);

// Choose smallest
if ((sleepMsecIn != 0) && (sleepMsecOut != 0))
sleepMs = min(sleepMsecIn, sleepMsecOut);
else
sleepMs = (sleepMsecIn ? sleepMsecIn : sleepMsecOut);
sleepTime = GetSleepTime(stream, sleepTimeIn, sleepTimeOut, userFramesOut);

// Setup thread sleep scheduler
ThreadIdleScheduler_Setup(scheduler, 1, sleepMs/* microseconds here */);
sleepMs = 0;
ThreadIdleScheduler_Setup(scheduler, 1, sleepTime/* microseconds here */);
sleepTime = 0;
}

return sleepMs;
return sleepTime;
}

// ------------------------------------------------------------------------------------------
static inline INT32 GetNextSleepTime(SystemTimer *timer, ThreadIdleScheduler *scheduler, LONGLONG startTimeMs,
UINT32 sleepMs)
static inline INT32 GetNextSleepTime(SystemTimer *timer, ThreadIdleScheduler *scheduler, LONGLONG startTime,
UINT32 sleepTime)
{
INT32 nextSleepMs;
INT32 nextSleepTime;

// Get next sleep time
if (sleepMs == 0)
nextSleepMs = ThreadIdleScheduler_NextSleep(scheduler);
if (sleepTime == 0)
nextSleepTime = ThreadIdleScheduler_NextSleep(scheduler);
else
nextSleepMs = sleepMs;
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;
// [ssss|p],[ssss|pp],[sss|p],[ssss|ppp],[s|p],[ssss|pppp],[p],[ssss|p]
nextSleepMs -= (INT32)(SystemTimer_GetTimeMsec(timer) - startTimeMs);
if (nextSleepMs < 0)
nextSleepMs = 0;
// 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 nextSleepMs;
return nextSleepTime;
}

// ------------------------------------------------------------------------------------------
Expand All @@ -5905,11 +5937,13 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
INT32 i;
ThreadIdleScheduler scheduler;
SystemTimer timer;
LONGLONG startTimeMs;
UINT32 sleepMs;
INT32 nextSleepMs;

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);
Expand All @@ -5922,7 +5956,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
SystemTimer_SetGranularity(&timer, 1);

// Claculate sleep time of the processing loop (inside WaitForSingleObject)
nextSleepMs = sleepMs = ConfigureLoopSleepTimeAndScheduler(stream, &scheduler);
sleepTime = ConfigureLoopSleepTimeAndScheduler(stream, &scheduler);

// Setup data processors
defaultProcessor.processor = WaspiHostProcessingLoop;
Expand All @@ -5943,6 +5977,10 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
}
}

#ifdef PA_WASAPI_LOG_TIME_SLOTS
startWaitTime = SystemTimer_GetTime(&timer);
#endif

// Initialize event & start OUTPUT stream
if (stream->out.clientProc)
{
Expand Down Expand Up @@ -6009,9 +6047,13 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
if (!PA_WASAPI__IS_FULLDUPLEX(stream))
{
// Processing Loop
while (WaitForSingleObject(stream->hCloseRequest, nextSleepMs) == WAIT_TIMEOUT)
while (WaitForSingleObject(stream->hCloseRequest, nextSleepTime) == WAIT_TIMEOUT)
{
startTimeMs = SystemTimer_GetTimeMsec(&timer);
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)
{
Expand Down Expand Up @@ -6058,7 +6100,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
// 1-2 iterations)
if (framesAvail < framesProc)
{
nextSleepMs = 0;
nextSleepTime = 0;
continue;
}

Expand Down Expand Up @@ -6088,19 +6130,27 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
}

// Get next sleep time
nextSleepMs = GetNextSleepTime(&timer, &scheduler, startTimeMs, sleepMs);
nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime);

#ifdef PA_WASAPI_LOG_TIME_SLOTS
startWaitTime = SystemTimer_GetTime(&timer);
#endif
}
}
else
{
// Processing Loop (full-duplex)
while (WaitForSingleObject(stream->hCloseRequest, nextSleepMs) == WAIT_TIMEOUT)
while (WaitForSingleObject(stream->hCloseRequest, nextSleepTime) == WAIT_TIMEOUT)
{
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;

startTimeMs = SystemTimer_GetTimeMsec(&timer);
startTime = SystemTimer_GetTime(&timer);

#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)
Expand Down Expand Up @@ -6225,7 +6275,11 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
}

// Get next sleep time
nextSleepMs = GetNextSleepTime(&timer, &scheduler, startTimeMs, sleepMs);
nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime);

#ifdef PA_WASAPI_LOG_TIME_SLOTS
startWaitTime = SystemTimer_GetTime(&timer);
#endif
}
}

Expand Down Expand Up @@ -6277,35 +6331,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

0 comments on commit 28ac8b4

Please sign in to comment.