Skip to content

Commit 1c36862

Browse files
authored
wasapi: Refactored audio client instance creation by joining separate versions for input/output, removed unused code related to IAudioEndpointVolume, added comment about enforced double buffering on PA WASAPI side inside _CalculateFramesPerHostBuffer(). (#773)
1) Refactored audio client instance creation by joining separate versions for input/output. 2) Removed unused code related to `IAudioEndpointVolume`. 3) Left comment about double buffering on PA WASAPI side inside `_CalculateFramesPerHostBuffer`. 4) Removed unused jitter margin calculation in _GetFramesPerHostBuffer. Change 1. makes implementation more compact. Change 2. removed `IAudioEndpointVolume` related code which was always unused by PA implementation. Change 3. does not change PA WASAPI behavior, it just adds the comment about enforced double buffering. Change 4. does not change behavior because it was always adding 0.
1 parent 3bf68d0 commit 1c36862

File tree

1 file changed

+42
-104
lines changed

1 file changed

+42
-104
lines changed

src/hostapi/wasapi/pa_win_wasapi.c

Lines changed: 42 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* Portable Audio I/O Library WASAPI implementation
33
* Copyright (c) 2006-2010 David Viens
4-
* Copyright (c) 2010-2022 Dmitry Kostjuchenko
4+
* Copyright (c) 2010-2023 Dmitry Kostjuchenko
55
*
66
* Based on the Open Source API proposed by Ross Bencina
77
* Copyright (c) 1999-2019 Ross Bencina, Phil Burk
@@ -584,7 +584,6 @@ typedef struct PaWasapiStream
584584
IStream *captureClientStream;
585585
#endif
586586
IAudioCaptureClient *captureClient;
587-
IAudioEndpointVolume *inVol;
588587

589588
// output
590589
PaWasapiSubStream out;
@@ -593,7 +592,6 @@ typedef struct PaWasapiStream
593592
IStream *renderClientStream;
594593
#endif
595594
IAudioRenderClient *renderClient;
596-
IAudioEndpointVolume *outVol;
597595

598596
// Event handles for event-driven processing mode
599597
HANDLE event[S_COUNT];
@@ -3304,11 +3302,14 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
33043302
}
33053303

33063304
// ------------------------------------------------------------------------------------------
3307-
static PaUint32 _GetFramesPerHostBuffer(PaUint32 userFramesPerBuffer, PaTime suggestedLatency, double sampleRate, PaUint32 TimerJitterMs)
3305+
static UINT32 _CalculateFramesPerHostBuffer(UINT32 userFramesPerBuffer, PaTime suggestedLatency, double sampleRate)
33083306
{
3309-
PaUint32 frames = userFramesPerBuffer + max( userFramesPerBuffer, (PaUint32)(suggestedLatency * sampleRate) );
3310-
frames += (PaUint32)((sampleRate * 0.001) * TimerJitterMs);
3311-
return frames;
3307+
// Note: WASAPI tends to enforce double buffering by itself therefore enforcing double buffering
3308+
// on PA side looks as an excessive measure.
3309+
const BOOL doubleBuffering = TRUE;
3310+
3311+
return userFramesPerBuffer + max((doubleBuffering ? userFramesPerBuffer : 0),
3312+
(UINT32)(suggestedLatency * sampleRate));
33123313
}
33133314

33143315
// ------------------------------------------------------------------------------------------
@@ -3323,15 +3324,15 @@ static void _RecalculateBuffersCount(PaWasapiSubStream *sub, UINT32 userFramesPe
33233324
// Determine number of buffers used:
33243325
// - Full-duplex mode will lead to period difference, thus only 1
33253326
// - Input mode, only 1, as WASAPI allows extraction of only 1 packet
3326-
// - For Shared mode we use double buffering
3327+
// - For Shared mode we use double buffering (see _CalculateFramesPerHostBuffer)
33273328
if ((sub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) || fullDuplex)
33283329
{
33293330
BOOL eventMode = ((sub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == AUDCLNT_STREAMFLAGS_EVENTCALLBACK);
33303331

33313332
// Exclusive mode does not allow >1 buffers be used for Event interface, e.g. GetBuffer
33323333
// call must acquire max buffer size and it all must be processed.
33333334
if (eventMode)
3334-
sub->userBufferAndHostMatch = 1;
3335+
sub->userBufferAndHostMatch = TRUE;
33353336

33363337
// Full-duplex or Event mode: prefer paUtilBoundedHostBufferSize because exclusive mode will starve
33373338
// and produce glitchy audio
@@ -3468,9 +3469,8 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
34683469
if ((pSub->shareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) &&
34693470
(!pSub->streamFlags || ((pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0)))
34703471
{
3471-
framesPerLatency = _GetFramesPerHostBuffer(userFramesPerBuffer,
3472-
params->suggestedLatency, pSub->wavex.Format.nSamplesPerSec, 0/*,
3473-
(pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? 0 : 1)*/);
3472+
framesPerLatency = _CalculateFramesPerHostBuffer(userFramesPerBuffer, params->suggestedLatency,
3473+
pSub->wavex.Format.nSamplesPerSec);
34743474
}
34753475
else
34763476
{
@@ -3487,9 +3487,8 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
34873487
overall = MakeHnsPeriod(framesPerLatency, pSub->wavex.Format.nSamplesPerSec);
34883488
if (overall >= (106667 * 2)/*21.33ms*/)
34893489
{
3490-
framesPerLatency = _GetFramesPerHostBuffer(userFramesPerBuffer,
3491-
params->suggestedLatency, pSub->wavex.Format.nSamplesPerSec, 0/*,
3492-
(streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? 0 : 1)*/);
3490+
framesPerLatency = _CalculateFramesPerHostBuffer(userFramesPerBuffer, params->suggestedLatency,
3491+
pSub->wavex.Format.nSamplesPerSec);
34933492

34943493
// Use Polling interface
34953494
pSub->streamFlags &= ~AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
@@ -3753,123 +3752,64 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
37533752
}
37543753

37553754
// ------------------------------------------------------------------------------------------
3756-
static PaError ActivateAudioClientOutput(PaWasapiStream *stream)
3757-
{
3758-
HRESULT hr;
3759-
PaError result;
3760-
UINT32 maxBufferSize;
3761-
PaTime bufferLatency;
3762-
const UINT32 framesPerBuffer = stream->out.params.frames_per_buffer;
3763-
3764-
// Create Audio client
3765-
if (FAILED(hr = CreateAudioClient(stream, &stream->out, TRUE, &result)))
3766-
{
3767-
LogPaError(result);
3768-
goto error;
3769-
}
3770-
LogWAVEFORMATEXTENSIBLE(&stream->out.wavex);
3771-
3772-
// Activate volume
3773-
stream->outVol = NULL;
3774-
/*hr = info->device->Activate(
3775-
__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL,
3776-
(void**)&stream->outVol);
3777-
if (hr != S_OK)
3778-
return paInvalidDevice;*/
3779-
3780-
// Get max possible buffer size to check if it is not less than that we request
3781-
if (FAILED(hr = IAudioClient_GetBufferSize(stream->out.clientParent, &maxBufferSize)))
3782-
{
3783-
LogHostError(hr);
3784-
LogPaError(result = paInvalidDevice);
3785-
goto error;
3786-
}
3787-
3788-
// Correct buffer to max size if it maxed out result of GetBufferSize
3789-
stream->out.bufferSize = maxBufferSize;
3790-
3791-
// Number of frames that are required at each period
3792-
stream->out.framesPerHostCallback = maxBufferSize;
3793-
3794-
// Calculate frames per single buffer, if buffers > 1 then always framesPerBuffer
3795-
stream->out.framesPerBuffer =
3796-
(stream->out.userBufferAndHostMatch ? stream->out.framesPerHostCallback : framesPerBuffer);
3797-
3798-
// Calculate buffer latency
3799-
bufferLatency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec;
3800-
3801-
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)
3802-
stream->out.latencySeconds = bufferLatency;
3803-
3804-
PRINT(("WASAPI::OpenStream(output): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ] mode[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latencySeconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (stream->out.params.wow64_workaround ? "YES" : "NO"), (stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? "EVENT" : "POLL")));
3805-
3806-
return paNoError;
3807-
3808-
error:
3809-
3810-
return result;
3811-
}
3812-
3813-
// ------------------------------------------------------------------------------------------
3814-
static PaError ActivateAudioClientInput(PaWasapiStream *stream)
3755+
static PaError ActivateAudioClient(PaWasapiStream *stream, BOOL output)
38153756
{
38163757
HRESULT hr;
38173758
PaError result;
38183759
UINT32 maxBufferSize;
38193760
PaTime bufferLatency;
3820-
const UINT32 framesPerBuffer = stream->in.params.frames_per_buffer;
3761+
PaWasapiSubStream *subStream = (output ? &stream->out : &stream->in);
38213762

38223763
// Create Audio client
3823-
if (FAILED(hr = CreateAudioClient(stream, &stream->in, FALSE, &result)))
3764+
if (FAILED(hr = CreateAudioClient(stream, subStream, output, &result)))
38243765
{
38253766
LogPaError(result);
38263767
goto error;
38273768
}
3828-
LogWAVEFORMATEXTENSIBLE(&stream->in.wavex);
3829-
3830-
// Create volume mgr
3831-
stream->inVol = NULL;
3832-
/*hr = info->device->Activate(
3833-
__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL,
3834-
(void**)&stream->inVol);
3835-
if (hr != S_OK)
3836-
return paInvalidDevice;*/
3769+
LogWAVEFORMATEXTENSIBLE(&subStream->wavex);
38373770

38383771
// Get max possible buffer size to check if it is not less than that we request
3839-
if (FAILED(hr = IAudioClient_GetBufferSize(stream->in.clientParent, &maxBufferSize)))
3772+
if (FAILED(hr = IAudioClient_GetBufferSize(subStream->clientParent, &maxBufferSize)))
38403773
{
38413774
LogHostError(hr);
38423775
LogPaError(result = paInvalidDevice);
38433776
goto error;
38443777
}
38453778

38463779
// Correct buffer to max size if it maxed out result of GetBufferSize
3847-
stream->in.bufferSize = maxBufferSize;
3780+
subStream->bufferSize = maxBufferSize;
38483781

3849-
// Get interface latency (actually unneeded as we calculate latency from the size
3850-
// of maxBufferSize).
3851-
if (FAILED(hr = IAudioClient_GetStreamLatency(stream->in.clientParent, &stream->in.deviceLatency)))
3782+
if (!output)
38523783
{
3853-
LogHostError(hr);
3854-
LogPaError(result = paInvalidDevice);
3855-
goto error;
3784+
// Get interface latency (actually unneeded as we calculate latency from the size of maxBufferSize)
3785+
if (FAILED(hr = IAudioClient_GetStreamLatency(stream->in.clientParent, &stream->in.deviceLatency)))
3786+
{
3787+
LogHostError(hr);
3788+
LogPaError(result = paInvalidDevice);
3789+
goto error;
3790+
}
3791+
//stream->in.latencySeconds = nano100ToSeconds(stream->in.deviceLatency);
38563792
}
3857-
//stream->in.latencySeconds = nano100ToSeconds(stream->in.deviceLatency);
38583793

38593794
// Number of frames that are required at each period
3860-
stream->in.framesPerHostCallback = maxBufferSize;
3795+
subStream->framesPerHostCallback = maxBufferSize;
38613796

38623797
// Calculate frames per single buffer, if buffers > 1 then always framesPerBuffer
3863-
stream->in.framesPerBuffer =
3864-
(stream->in.userBufferAndHostMatch ? stream->in.framesPerHostCallback : framesPerBuffer);
3798+
subStream->framesPerBuffer = (subStream->userBufferAndHostMatch ?
3799+
subStream->framesPerHostCallback : subStream->params.frames_per_buffer);
38653800

38663801
// Calculate buffer latency
3867-
bufferLatency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec;
3802+
bufferLatency = (PaTime)maxBufferSize / subStream->wavex.Format.nSamplesPerSec;
38683803

38693804
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)
3870-
stream->in.latencySeconds = bufferLatency;
3805+
subStream->latencySeconds = bufferLatency;
38713806

3872-
PRINT(("WASAPI::OpenStream(input): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ] mode[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latencySeconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (stream->in.params.wow64_workaround ? "YES" : "NO"), (stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? "EVENT" : "POLL")));
3807+
PRINT(("WASAPI::OpenStream(%s): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ] mode[ %s ]\n",
3808+
(output ? "output" : "input"),
3809+
subStream->params.frames_per_buffer, subStream->framesPerHostCallback, (subStream->latencySeconds * 1000),
3810+
(subStream->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"),
3811+
(subStream->params.wow64_workaround ? "YES" : "NO"),
3812+
(subStream->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? "EVENT" : "POLL")));
38733813

38743814
return paNoError;
38753815

@@ -4025,7 +3965,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
40253965
stream->in.params.wow64_workaround = paWasapi->useWOW64Workaround;
40263966

40273967
// Create and activate audio client
4028-
if ((result = ActivateAudioClientInput(stream)) != paNoError)
3968+
if ((result = ActivateAudioClient(stream, FALSE)) != paNoError)
40293969
{
40303970
LogPaError(result);
40313971
goto error;
@@ -4158,7 +4098,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
41584098
stream->out.params.wow64_workaround = paWasapi->useWOW64Workaround;
41594099

41604100
// Create and activate audio client
4161-
if ((result = ActivateAudioClientOutput(stream)) != paNoError)
4101+
if ((result = ActivateAudioClient(stream, TRUE)) != paNoError)
41624102
{
41634103
LogPaError(result);
41644104
goto error;
@@ -4335,8 +4275,6 @@ static PaError CloseStream( PaStream* s )
43354275
SAFE_RELEASE(stream->renderClientParent);
43364276
SAFE_RELEASE(stream->out.clientParent);
43374277
SAFE_RELEASE(stream->in.clientParent);
4338-
SAFE_RELEASE(stream->inVol);
4339-
SAFE_RELEASE(stream->outVol);
43404278

43414279
CloseHandle(stream->event[S_INPUT]);
43424280
CloseHandle(stream->event[S_OUTPUT]);

0 commit comments

Comments
 (0)