- reimplemented suggestedLatency and framesPerBuffer parameters in Pa_OpenStream call to opearate by the following formula - 'hostBufferFrames = userFramesPerBuffer + max(userFramesPerBuffer, (suggestedLatency * sampleRate))' as discussed on PA mailing-lists
 - it is now possible to achieve 5.33ms latency in Polling & Exclusive mode (WOW64 or Native)
 - Polling method for output will benefit from direct WASAPI buffer pointer exposure to a user space through a callback (no copying)
This commit is contained in:
dmitrykos 2010-07-06 07:28:48 +00:00
commit 0a4cf33379

View file

@ -479,6 +479,11 @@ typedef struct PaWasapiSubStream
UINT32 streamFlags; // AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ... UINT32 streamFlags; // AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ...
UINT32 flags; UINT32 flags;
// Buffers
UINT32 buffers; //!< number of buffers used (from host side)
UINT32 framesPerBuffer; //!< number of frames per 1 buffer
BOOL userBufferAndHostMatch;
// Used by blocking interface: // Used by blocking interface:
UINT32 prevTime; // time ms between calls of WriteStream UINT32 prevTime; // time ms between calls of WriteStream
UINT32 prevSleep; // time ms to sleep from frames written in previous call UINT32 prevSleep; // time ms to sleep from frames written in previous call
@ -521,6 +526,9 @@ typedef struct PaWasapiStream
// event handles for event-driven processing mode // event handles for event-driven processing mode
HANDLE event[S_COUNT]; HANDLE event[S_COUNT];
// buffer mode
PaUtilHostBufferSizeMode bufferMode;
// must be volatile to avoid race condition on user query while // must be volatile to avoid race condition on user query while
// thread is being started // thread is being started
volatile BOOL running; volatile BOOL running;
@ -1920,14 +1928,23 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
return paFormatIsSupported; return paFormatIsSupported;
} }
// ------------------------------------------------------------------------------------------
static PaUint32 PaUtil_GetFramesPerHostBuffer(PaUint32 userFramesPerBuffer, PaTime suggestedLatency, double sampleRate, PaUint32 TimerJitterMs)
{
PaUint32 frames = userFramesPerBuffer + max( userFramesPerBuffer, (PaUint32)(suggestedLatency * sampleRate) );
frames += (PaUint32)((sampleRate * 0.001) * TimerJitterMs);
return frames;
}
// ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------
static HRESULT CreateAudioClient(PaWasapiSubStream *pSubStream, PaWasapiDeviceInfo *info, static HRESULT CreateAudioClient(PaWasapiSubStream *pSubStream, PaWasapiDeviceInfo *info,
const PaStreamParameters *params, UINT32 framesPerLatency, double sampleRate, UINT32 streamFlags, const PaStreamParameters *params, UINT32 framesPerLatency, double sampleRate, UINT32 streamFlags,
BOOL blocking, BOOL output, PaError *pa_error) BOOL blocking, BOOL output, BOOL full_duplex, PaError *pa_error)
{ {
PaError error; PaError error;
HRESULT hr = S_OK; HRESULT hr = S_OK;
UINT32 nFrames = 0; UINT32 nFrames = 0;
UINT32 userFramesPerBuffer = framesPerLatency;
IAudioClient *pAudioClient = NULL; IAudioClient *pAudioClient = NULL;
double suggestedLatency = 0.0; double suggestedLatency = 0.0;
@ -1972,12 +1989,42 @@ static HRESULT CreateAudioClient(PaWasapiSubStream *pSubStream, PaWasapiDeviceIn
} }
} }
#if 0
// Correct latency to default device period (in Exclusive mode this is 10ms) if user selected 0 // Correct latency to default device period (in Exclusive mode this is 10ms) if user selected 0
//suggestedLatency = (params->suggestedLatency < 0.001 ? nano100ToSeconds(info->DefaultDevicePeriod) : params->suggestedLatency);
suggestedLatency = params->suggestedLatency; suggestedLatency = params->suggestedLatency;
// Add suggestd latency
// Add latency frames
framesPerLatency += MakeFramesFromHns(SecondsTonano100(suggestedLatency), pSubStream->wavex.Format.nSamplesPerSec); framesPerLatency += MakeFramesFromHns(SecondsTonano100(suggestedLatency), pSubStream->wavex.Format.nSamplesPerSec);
#else
// Calculate host buffer size
if ((pSubStream->shareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) &&
(!streamFlags || ((streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0)))
{
framesPerLatency = PaUtil_GetFramesPerHostBuffer(framesPerLatency,
params->suggestedLatency, pSubStream->wavex.Format.nSamplesPerSec, 0/*,
(streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? 0 : 1)*/);
}
else
{
// do nothing, work 1:1 with buffer (only polling allows to work with >1)
}
#endif
// Count buffers (must be at least 1)
pSubStream->buffers = (userFramesPerBuffer ? framesPerLatency / userFramesPerBuffer : 0);
if (pSubStream->buffers == 0)
pSubStream->buffers = 1;
// Determine amount of buffers to be used:
// - Exclusive mode does not allow >1 buffers be used, e.g. GetBuffer call must acquire
// max buffer size and it all must be processed.
// - Full-duplex mode will lead to period difference, thus only 1.
// - Input mode, only 1, as WASAPI allows extraction of only 1 packet.
// - For Shared mode we use safe double buffering (2 buffers).
if ((pSubStream->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) || full_duplex/* && output*/)
{
pSubStream->userBufferAndHostMatch = 1;
pSubStream->buffers = 1;
}
// Avoid 0 frames // Avoid 0 frames
if (framesPerLatency == 0) if (framesPerLatency == 0)
@ -2127,6 +2174,9 @@ static const REFERENCE_TIME MAX_BUFFER_POLL_DURATION = 2000 * 10000;
} }
} }
// Reset buffers count to single
pSubStream->buffers = 1;
// Calculate period // Calculate period
pSubStream->period = MakeHnsPeriod(nFrames, pSubStream->wavex.Format.nSamplesPerSec); pSubStream->period = MakeHnsPeriod(nFrames, pSubStream->wavex.Format.nSamplesPerSec);
@ -2266,7 +2316,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
} }
// Choose processing mode // Choose processing mode
stream->in.streamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; stream->in.streamFlags = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0);
if (paWasapi->useWOW64Workaround) if (paWasapi->useWOW64Workaround)
stream->in.streamFlags = 0; // polling interface stream->in.streamFlags = 0; // polling interface
else else
@ -2281,7 +2331,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
// Create Audio client // Create Audio client
hr = CreateAudioClient(&stream->in, info, inputParameters, framesPerBuffer/*framesPerLatency*/, hr = CreateAudioClient(&stream->in, info, inputParameters, framesPerBuffer/*framesPerLatency*/,
sampleRate, stream->in.streamFlags, (streamCallback == NULL), FALSE, &result); sampleRate, stream->in.streamFlags, (streamCallback == NULL), FALSE, fullDuplex, &result);
if (hr != S_OK) if (hr != S_OK)
{ {
LogHostError(hr); LogHostError(hr);
@ -2338,13 +2388,17 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
// Number of frames that are required at each period // Number of frames that are required at each period
stream->in.framesPerHostCallback = maxBufferSize; stream->in.framesPerHostCallback = maxBufferSize;
// Calculate frames per single buffer, if buffers > 1 then always framesPerBuffer
stream->in.framesPerBuffer =
(stream->in.userBufferAndHostMatch ? stream->in.framesPerHostCallback : framesPerBuffer);
// Calculate buffer latency // Calculate buffer latency
buffer_latency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec; buffer_latency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec;
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes) // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)
stream->in.latency_seconds += buffer_latency; stream->in.latency_seconds += buffer_latency;
PRINT(("WASAPI::OpenStream(input): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latency_seconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO"))); PRINT(("WASAPI::OpenStream(input): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latency_seconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));
} }
else else
{ {
@ -2380,7 +2434,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
} }
// Choose processing mode // Choose processing mode
stream->out.streamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; stream->out.streamFlags = (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0);
if (paWasapi->useWOW64Workaround) if (paWasapi->useWOW64Workaround)
stream->out.streamFlags = 0; // polling interface stream->out.streamFlags = 0; // polling interface
else else
@ -2395,7 +2449,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
// Create Audio client // Create Audio client
hr = CreateAudioClient(&stream->out, info, outputParameters, framesPerBuffer/*framesPerLatency*/, hr = CreateAudioClient(&stream->out, info, outputParameters, framesPerBuffer/*framesPerLatency*/,
sampleRate, stream->out.streamFlags, (streamCallback == NULL), TRUE, &result); sampleRate, stream->out.streamFlags, (streamCallback == NULL), TRUE, fullDuplex, &result);
if (hr != S_OK) if (hr != S_OK)
{ {
LogHostError(hr); LogHostError(hr);
@ -2452,13 +2506,17 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
// Number of frames that are required at each period // Number of frames that are required at each period
stream->out.framesPerHostCallback = maxBufferSize; stream->out.framesPerHostCallback = maxBufferSize;
// Calculate frames per single buffer, if buffers > 1 then always framesPerBuffer
stream->out.framesPerBuffer =
(stream->out.userBufferAndHostMatch ? stream->out.framesPerHostCallback : framesPerBuffer);
// Calculate buffer latency // Calculate buffer latency
buffer_latency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec; buffer_latency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec;
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes) // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)
stream->out.latency_seconds += buffer_latency; stream->out.latency_seconds += buffer_latency;
PRINT(("WASAPI::OpenStream(output): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latency_seconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO"))); PRINT(("WASAPI::OpenStream(output): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latency_seconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));
} }
else else
{ {
@ -2525,20 +2583,23 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
}*/ }*/
} }
framesPerHostCallback = (outputParameters) ? stream->out.framesPerHostCallback: // Calculate frames per host for processor
stream->in.framesPerHostCallback; framesPerHostCallback = (outputParameters ? stream->out.framesPerBuffer : stream->in.framesPerBuffer);
// Choose correct mode of buffer processing: // Choose correct mode of buffer processing:
// Exclusive/Shared non paWinWasapiPolling mode: paUtilFixedHostBufferSize - always fixed // Exclusive/Shared non paWinWasapiPolling mode: paUtilFixedHostBufferSize - always fixed
// Exclusive/Shared paWinWasapiPolling mode: paUtilBoundedHostBufferSize - may vary // Exclusive/Shared paWinWasapiPolling mode: paUtilBoundedHostBufferSize - may vary for Exclusive or Full-duplex
bufferMode = paUtilFixedHostBufferSize; bufferMode = paUtilFixedHostBufferSize;
if (inputParameters /*&& // !!! WASAPI IAudioCaptureClient::GetBuffer extracts not number of frames but 1 packet, thus we always must adapt if (inputParameters) // !!! WASAPI IAudioCaptureClient::GetBuffer extracts not number of frames but 1 packet, thus we always must adapt
(!stream->in.streamFlags || ((stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0))*/)
bufferMode = paUtilBoundedHostBufferSize; bufferMode = paUtilBoundedHostBufferSize;
else else
if (outputParameters && if (outputParameters)
(!stream->out.streamFlags || ((stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0))) {
bufferMode = paUtilBoundedHostBufferSize; if ((stream->out.buffers == 1) &&
(!stream->out.streamFlags || ((stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0)))
bufferMode = paUtilBoundedHostBufferSize;
}
stream->bufferMode = bufferMode;
// Initialize buffer processor // Initialize buffer processor
result = PaUtil_InitializeBufferProcessor( result = PaUtil_InitializeBufferProcessor(
@ -2565,12 +2626,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
// Set Input latency // Set Input latency
stream->streamRepresentation.streamInfo.inputLatency = stream->streamRepresentation.streamInfo.inputLatency =
PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)
+ ((inputParameters)?stream->in.latency_seconds :0); + ((inputParameters)?stream->in.latency_seconds : 0);
// Set Output latency // Set Output latency
stream->streamRepresentation.streamInfo.outputLatency = stream->streamRepresentation.streamInfo.outputLatency =
PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)
+ ((outputParameters)?stream->out.latency_seconds :0); + ((outputParameters)?stream->out.latency_seconds : 0);
// Set SR // Set SR
stream->streamRepresentation.streamInfo.sampleRate = sampleRate; stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
@ -3542,7 +3603,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
} }
// Preload buffer before start // Preload buffer before start
if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK) if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK)
{ {
LogHostError(hr); LogHostError(hr);
goto thread_error; goto thread_error;
@ -3593,7 +3654,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
case WAIT_OBJECT_0 + S_OUTPUT: { case WAIT_OBJECT_0 + S_OUTPUT: {
if (stream->rclient == NULL) if (stream->rclient == NULL)
break; break;
ProcessOutputBuffer(stream, processor, stream->out.framesPerHostCallback); ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer);
break; } break; }
} }
} }
@ -3652,14 +3713,26 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
// Calculate the actual duration of the allocated buffer. // Calculate the actual duration of the allocated buffer.
DWORD sleep_ms = 0; DWORD sleep_ms = 0;
DWORD sleep_ms_in = GetFramesSleepTime(stream->in.framesPerHostCallback, stream->in.wavex.Format.nSamplesPerSec); DWORD sleep_ms_in = GetFramesSleepTime(stream->in.framesPerBuffer, stream->in.wavex.Format.nSamplesPerSec);
DWORD sleep_ms_out = GetFramesSleepTime(stream->out.framesPerHostCallback, stream->out.wavex.Format.nSamplesPerSec); DWORD sleep_ms_out = GetFramesSleepTime(stream->out.framesPerBuffer, stream->out.wavex.Format.nSamplesPerSec);
// Adjust polling time
if (stream->bufferMode != paUtilFixedHostBufferSize)
{
sleep_ms_in = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->in.wavex.Format.nSamplesPerSec);
sleep_ms_out = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->out.wavex.Format.nSamplesPerSec);
}
// Choose smallest
if ((sleep_ms_in != 0) && (sleep_ms_out != 0)) if ((sleep_ms_in != 0) && (sleep_ms_out != 0))
sleep_ms = min(sleep_ms_in, sleep_ms_out); sleep_ms = min(sleep_ms_in, sleep_ms_out);
else else
{ {
sleep_ms = (sleep_ms_in ? sleep_ms_in : sleep_ms_out); sleep_ms = (sleep_ms_in ? sleep_ms_in : sleep_ms_out);
} }
// Make sure not 0
if (sleep_ms == 0)
sleep_ms = 1;
// Setup data processors // Setup data processors
defaultProcessor.processor = WaspiHostProcessingLoop; defaultProcessor.processor = WaspiHostProcessingLoop;
@ -3706,7 +3779,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
// when in full-duplex mode as it requires input processing as well // when in full-duplex mode as it requires input processing as well
if (!PA_WASAPI__IS_FULLDUPLEX(stream)) if (!PA_WASAPI__IS_FULLDUPLEX(stream))
{ {
if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK) if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK)
{ {
LogHostError(hr); LogHostError(hr);
goto thread_error; goto thread_error;
@ -3757,10 +3830,18 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
LogHostError(hr); LogHostError(hr);
break; break;
} }
// output // output
if (frames != 0) if (stream->bufferMode == paUtilFixedHostBufferSize)
ProcessOutputBuffer(stream, processor, frames); {
if (frames >= stream->out.framesPerBuffer)
ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer);
}
else
{
if (frames != 0)
ProcessOutputBuffer(stream, processor, frames);
}
break; } break; }
} }