hotplug wmme: Change StopStream() timeout policy. For callback streams the maximum wait times have been tweaked. Total wait time has been reduced (Old algorithm wait time: [max(2*totalBuffersDur*1.5, 2*1second)], new algorithm wait time: [totalBuffersDur*1.5+min(totalBuffersDur*1.5, 1second)]). For blocking read write streams, the maximum wait time has been reduced to approximately totalBuffersDur*1.5. Added test patest_unplug_readwrite.c for testing stopping read/write streams after unplug. Tweaked patest_unplug.c to build on Windows, and to use 1 input channel (for headset). (Timeout changes made with reference to Jitsi [lyubomir a45b993, Thank you!].
This commit is contained in:
parent
0448f3e7b7
commit
6a1f1b4057
3 changed files with 343 additions and 19 deletions
|
|
@ -3768,11 +3768,13 @@ static PaError StopStream( PaStream *s )
|
|||
{
|
||||
PaError result = paNoError;
|
||||
PaWinMmeStream *stream = (PaWinMmeStream*)s;
|
||||
int timeout;
|
||||
DWORD timeoutMs, totalTimeoutMs;
|
||||
DWORD waitStartTime, elapsedMs;
|
||||
DWORD waitResult;
|
||||
MMRESULT mmresult;
|
||||
signed int hostOutputBufferIndex;
|
||||
unsigned int channel, waitCount, i;
|
||||
unsigned int channel, waitCount, i;
|
||||
unsigned int maxWaitCount;
|
||||
|
||||
/** @todo
|
||||
REVIEW: the error checking in this function needs review. the basic
|
||||
|
|
@ -3785,23 +3787,26 @@ static PaError StopStream( PaStream *s )
|
|||
{
|
||||
/* callback stream */
|
||||
|
||||
/* First-chance timeout is for draining the the buffer cleanly. Use a time longer than to total buffers duration. */
|
||||
timeoutMs = (DWORD)(stream->allBuffersDurationMs * 1.5) + 1;
|
||||
|
||||
/* Tell processing thread to stop generating more data and to let current data play out. */
|
||||
stream->stopProcessing = 1;
|
||||
|
||||
/* Calculate timeOut longer than longest time it could take to return all buffers. */
|
||||
timeout = (int)(stream->allBuffersDurationMs * 1.5);
|
||||
if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ )
|
||||
timeout = PA_MME_MIN_TIMEOUT_MSEC_;
|
||||
|
||||
PA_DEBUG(("WinMME StopStream: waiting for background thread.\n"));
|
||||
|
||||
waitResult = WaitForSingleObject( stream->processingThread, timeout );
|
||||
waitResult = WaitForSingleObject( stream->processingThread, timeoutMs );
|
||||
if( waitResult == WAIT_TIMEOUT )
|
||||
{
|
||||
/* try to abort */
|
||||
|
||||
/* Last-chance timeout results in no more than PA_MME_MIN_TIMEOUT_MSEC_ of additional waiting. */
|
||||
if( timeoutMs > PA_MME_MIN_TIMEOUT_MSEC_ )
|
||||
timeoutMs = PA_MME_MIN_TIMEOUT_MSEC_;
|
||||
|
||||
stream->abortProcessing = 1;
|
||||
SetEvent( stream->abortEvent );
|
||||
waitResult = WaitForSingleObject( stream->processingThread, timeout );
|
||||
waitResult = WaitForSingleObject( stream->processingThread, timeoutMs );
|
||||
if( waitResult == WAIT_TIMEOUT )
|
||||
{
|
||||
PA_DEBUG(("WinMME StopStream: timed out while waiting for background thread to finish.\n"));
|
||||
|
|
@ -3853,22 +3858,57 @@ static PaError StopStream( PaStream *s )
|
|||
}
|
||||
|
||||
|
||||
timeout = (stream->allBuffersDurationMs / stream->output.bufferCount) + 1;
|
||||
if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ )
|
||||
timeout = PA_MME_MIN_TIMEOUT_MSEC_;
|
||||
/*
|
||||
The purpose of the following wait/poll loop is to wait for
|
||||
the output to play out (all queued buffers to be returned).
|
||||
We expect all queued buffers to be returned to us within
|
||||
allBuffersDurationMs (plus some margin to avoid cutting off
|
||||
the tail).
|
||||
|
||||
When functioning as intended, WaitForSingleObject will wake
|
||||
after each buffer completes, taking at most bufferCount
|
||||
loop iterations. We're done when NoBuffersAreQueued() returns true.
|
||||
|
||||
When not functioning as intended, we want to bound the
|
||||
duration of the wait, but also poll frequently enough to
|
||||
pick up an early-exit from NoBuffersAreQueued.
|
||||
|
||||
Two mechanisms are used to ensure that this loop does not hang:
|
||||
|
||||
- The total elapsed wait time is limited to totalTimeoutMs.
|
||||
- The loop is limited to maxWaitCount iterations, and the
|
||||
timeout for each iteration equals totalTimeoutMs/maxWaitCount.
|
||||
|
||||
This combination should cover cases where the timers or
|
||||
timeout times are unreliable (e.g. if WFSO takes longer
|
||||
than timeoutMs to return).
|
||||
*/
|
||||
|
||||
totalTimeoutMs = (DWORD)(1.5 * stream->allBuffersDurationMs);
|
||||
|
||||
/* poll every 1.5 buffer durations. */
|
||||
timeoutMs = (DWORD)((1.5 * stream->allBuffersDurationMs) / stream->output.bufferCount) + 1;
|
||||
/* for a total of maxWaitCount iterations, duration: maxWaitCount*timeoutMs = 1.5 * stream->allBuffersDurationMs */
|
||||
maxWaitCount = stream->output.bufferCount;
|
||||
|
||||
waitStartTime = GetTickCount();
|
||||
waitCount = 0;
|
||||
while( !NoBuffersAreQueued( &stream->output ) && waitCount <= stream->output.bufferCount )
|
||||
while( !NoBuffersAreQueued( &stream->output ) && waitCount <= maxWaitCount )
|
||||
{
|
||||
/* wait for MME to signal that a buffer is available */
|
||||
waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout );
|
||||
waitResult = WaitForSingleObject( stream->output.bufferEvent, timeoutMs );
|
||||
if( waitResult == WAIT_FAILED )
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if( waitResult == WAIT_TIMEOUT )
|
||||
{
|
||||
/* keep waiting */
|
||||
elapsedMs = GetTickCount() - waitStartTime;
|
||||
if( elapsedMs >= totalTimeoutMs )
|
||||
{
|
||||
result = paTimedOut;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++waitCount;
|
||||
|
|
|
|||
Loading…
Reference in a new issue