]> Repos - portaudio/commitdiff
wasapi: Improved audio output stability in Exclusive mode for some UAC2 DACs with...
authordmitrykos <dmitrykos@neutroncode.com>
Sat, 23 Feb 2019 21:02:38 +0000 (23:02 +0200)
committerdmitrykos <dmitrykos@neutroncode.com>
Sat, 23 Feb 2019 21:02:38 +0000 (23:02 +0200)
src/hostapi/wasapi/pa_win_wasapi.c

index e9d41b36852a3b66fe6e25b1e3d066e8ff6bc841..1dc888e4ba1c0380f66b4fe81c53a145eb4610ad 100644 (file)
@@ -1,10 +1,10 @@
 /*
  * Portable Audio I/O Library WASAPI implementation
  * Copyright (c) 2006-2010 David Viens
- * Copyright (c) 2010-2018 Dmitry Kostjuchenko
+ * Copyright (c) 2010-2019 Dmitry Kostjuchenko
  *
  * Based on the Open Source API proposed by Ross Bencina
- * Copyright (c) 1999-2018 Ross Bencina, Phil Burk
+ * Copyright (c) 1999-2019 Ross Bencina, Phil Burk
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files
        #define PA_WASAPI_MAX_CONST_DEVICE_COUNT 0 // Force basic behavior by defining 0 if not defined by user
 #endif
 
+// Fallback from Event to the Polling method in case if latency is higher than 21.33ms, as it allows to use 
+// 100% of CPU inside the PA's callback.
+// Note: Some USB DAC drivers are buggy when Polling method is forced in Exclusive mode, audio output becomes 
+//       unstable with a lot of interruptions, therefore this define is optional. The default behavior is to 
+//       not change the Event mode to Polling and use the mode which user provided.
+//#define PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER
+
 // WinRT
 #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
        #define PA_WINRT
@@ -1686,12 +1693,12 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA
 
 #else
        // Determine number of available devices by activating AudioClient for render and capture data flows
-       if (!FAILED(ActivateAudioInterface_WINRT(eRender, GetAudioClientIID(), &tmpClient)))
+       if (SUCCEEDED(ActivateAudioInterface_WINRT(eRender, GetAudioClientIID(), &tmpClient)))
        {
                paWasapi->deviceCount++;
                SAFE_RELEASE(tmpClient);
        }
-       if (!FAILED(ActivateAudioInterface_WINRT(eCapture, GetAudioClientIID(), &tmpClient)))
+       if (SUCCEEDED(ActivateAudioInterface_WINRT(eCapture, GetAudioClientIID(), &tmpClient)))
        {
                paWasapi->deviceCount++;
                SAFE_RELEASE(tmpClient);
@@ -2034,7 +2041,7 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
 #ifndef PA_WINRT
     if (!SetupAVRT())
        {
-        PRINT(("WASAPI: No AVRT! (not VISTA?)"));
+        PRINT(("WASAPI: No AVRT! (not VISTA?)\n"));
         return paNoError;
     }
 #endif
@@ -2872,8 +2879,7 @@ static void _RecalculateBuffersCount(PaWasapiSubStream *sub, UINT32 userFramesPe
 }
 
 // ------------------------------------------------------------------------------------------
-static void _CalculateAlignedPeriod(PaWasapiSubStream *pSub, UINT32 *nFramesPerLatency,
-                                                                       ALIGN_FUNC pAlignFunc)
+static void _CalculateAlignedPeriod(PaWasapiSubStream *pSub, UINT32 *nFramesPerLatency, ALIGN_FUNC pAlignFunc)
 {
        // Align frames to HD Audio packet size of 128 bytes for Exclusive mode only.
        // Not aligning on Windows Vista will cause Event timeout, although Windows 7 will
@@ -2889,21 +2895,66 @@ static void _CalculateAlignedPeriod(PaWasapiSubStream *pSub, UINT32 *nFramesPerL
        pSub->period = MakeHnsPeriod((*nFramesPerLatency), pSub->wavex.Format.nSamplesPerSec);
 }
 
+// ------------------------------------------------------------------------------------------
+static void _CalculatePeriodicity(PaWasapiSubStream *pSub, BOOL output, REFERENCE_TIME *periodicity)
+{
+       // Note: according Microsoft docs for IAudioClient::Initialize we can set periodicity of the buffer
+       // only for Exclusive mode. By setting periodicity almost equal to the user buffer frames we can 
+       // achieve high quality (less glitchy) low-latency audio.
+       if (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE)
+       {
+               const PaWasapiDeviceInfo *pInfo = pSub->params.device_info;
+
+               // By default periodicity equals to the full buffer (legacy PA WASAPI's behavior)
+               (*periodicity) = pSub->period;
+
+               // Try make buffer ready for I/O once we request the buffer readiness for it. Only Polling mode
+               // because for Event mode buffer size and periodicity must be equal according Microsoft 
+               // documentation for IAudioClient::Initialize.
+               // 
+               // TO-DO: try spread to capture and full-duplex cases (not tested and therefore disabled)
+               //
+               if (((pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0) && 
+                       (output && !pSub->params.full_duplex))
+               {       
+                       UINT32 alignedFrames;
+                       REFERENCE_TIME userPeriodicity;
+
+                       // Align frames backwards, so device will likely make buffer read ready when we are ready
+                       // to read it (our sheduling will wait for amount of millisoconds of frames_per_buffer)
+                       alignedFrames = AlignFramesPerBuffer(pSub->params.frames_per_buffer,
+                               pSub->wavex.Format.nSamplesPerSec, pSub->wavex.Format.nBlockAlign, ALIGN_BWD);
+
+                       userPeriodicity = MakeHnsPeriod(alignedFrames, pSub->wavex.Format.nSamplesPerSec);
+
+                       // Must not be larger than buffer size
+                       if (userPeriodicity > pSub->period)
+                               userPeriodicity = pSub->period;
+
+                       // Must not be smaller than minimum supported by the device
+                       if (userPeriodicity < pInfo->MinimumDevicePeriod)
+                               userPeriodicity = pInfo->MinimumDevicePeriod;
+                       
+                       (*periodicity) = userPeriodicity;
+               }
+       }
+       else
+               (*periodicity) = 0;
+}
+
 // ------------------------------------------------------------------------------------------
 static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSub, BOOL output, PaError *pa_error)
 {
        PaError error;
     HRESULT hr;
-
        const PaWasapiDeviceInfo *pInfo  = pSub->params.device_info;
        const PaStreamParameters *params = &pSub->params.stream_params;
-       UINT32 framesPerLatency          = pSub->params.frames_per_buffer;
-       double sampleRate                = pSub->params.sample_rate;
-       //BOOL blocking                    = pSub->params.blocking;
-       BOOL fullDuplex                  = pSub->params.full_duplex;
-
-       const UINT32 userFramesPerBuffer = framesPerLatency;
-    IAudioClient *audioClient       = NULL;
+       const double sampleRate          = pSub->params.sample_rate;
+       const BOOL fullDuplex            = pSub->params.full_duplex;
+       const UINT32 userFramesPerBuffer = pSub->params.frames_per_buffer;
+       UINT32 framesPerLatency          = userFramesPerBuffer;
+       IAudioClient *audioClient            = NULL;
+       REFERENCE_TIME eventPeriodicity  = 0;
 
        // Assume default failure due to some reason
        (*pa_error) = paInvalidDevice;
@@ -2921,8 +2972,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
        }
 
     // Get the audio client
-    hr = ActivateAudioInterface(pInfo, &audioClient);
-       if (hr != S_OK)
+       if (FAILED(hr = ActivateAudioInterface(pInfo, &audioClient)))
        {
                (*pa_error) = paInsufficientMemory;
                LogHostError(hr);
@@ -2940,12 +2990,6 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
        // Check for Mono <<>> Stereo workaround
        if ((params->channelCount == 1) && (pSub->wavex.Format.nChannels == 2))
        {
-               /*if (blocking)
-               {
-                       LogHostError(hr = AUDCLNT_E_UNSUPPORTED_FORMAT);
-                       goto done; // fail, blocking mode not supported
-               }*/
-
                // select mixer
                pSub->monoMixer = GetMonoToStereoMixer(&pSub->wavex, (pInfo->flow == eRender ? MIX_DIR__1TO2 : MIX_DIR__2TO1_L));
                if (pSub->monoMixer == NULL)
@@ -2956,10 +3000,6 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
                }
        }
 
-#if 0
-       // Add suggestd latency
-       framesPerLatency += MakeFramesFromHns(SecondsTonano100(params->suggestedLatency), pSub->wavex.Format.nSamplesPerSec);
-#else
        // Calculate host buffer size
        if ((pSub->shareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) &&
                (!pSub->streamFlags || ((pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0)))
@@ -2970,13 +3010,16 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
        }
        else
        {
+       #ifdef PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER
                REFERENCE_TIME overall;
+       #endif
 
                // Work 1:1 with user buffer (only polling allows to use >1)
                framesPerLatency += MakeFramesFromHns(SecondsTonano100(params->suggestedLatency), pSub->wavex.Format.nSamplesPerSec);
 
-               // Use Polling if overall latency is >= 21.33ms as it allows to use 100% CPU in a callback,
-               // or user specified latency parameter
+               // Force Polling if overall latency is >= 21.33ms as it allows to use 100% CPU in a callback,
+               // or user specified latency parameter.
+       #ifdef PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER
                overall = MakeHnsPeriod(framesPerLatency, pSub->wavex.Format.nSamplesPerSec);
                if (overall >= (106667 * 2)/*21.33ms*/)
                {
@@ -2988,8 +3031,8 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
                        pSub->streamFlags &= ~AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
                        PRINT(("WASAPI: CreateAudioClient: forcing POLL mode\n"));
                }
+       #endif
        }
-#endif
 
        // For full-duplex output resize buffer to be the same as for input
        if (output && fullDuplex)
@@ -3019,6 +3062,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
                if (pSub->period < pInfo->DefaultDevicePeriod)
                {
                        pSub->period = pInfo->DefaultDevicePeriod;
+
                        // Recalculate aligned period
                        framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec);
                        _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD);
@@ -3029,6 +3073,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
                if (pSub->period < pInfo->MinimumDevicePeriod)
                {
                        pSub->period = pInfo->MinimumDevicePeriod;
+
                        // Recalculate aligned period
                        framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec);
                        _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_FWD);
@@ -3050,21 +3095,25 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
                        static const REFERENCE_TIME MAX_BUFFER_EVENT_DURATION = 500  * 10000;
                        static const REFERENCE_TIME MAX_BUFFER_POLL_DURATION  = 2000 * 10000;
 
-                       if (pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)      // pull mode, max 500ms
+                       // Pull mode, max 500ms
+                       if (pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)
                        {
                                if (pSub->period > MAX_BUFFER_EVENT_DURATION)
                                {
                                        pSub->period = MAX_BUFFER_EVENT_DURATION;
+
                                        // Recalculate aligned period
                                        framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec);
                                        _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD);
                                }
                        }
-                       else                                                                                                            // push mode, max 2000ms
+                       // Push mode, max 2000ms
+                       else
                        {
                                if (pSub->period > MAX_BUFFER_POLL_DURATION)
                                {
                                        pSub->period = MAX_BUFFER_POLL_DURATION;
+
                                        // Recalculate aligned period
                                        framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec);
                                        _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD);
@@ -3093,8 +3142,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
                        break;
                }
 
-               hr = IAudioClient2_SetClientProperties((IAudioClient2 *)audioClient, (AudioClientProperties *)&audioProps);
-               if (hr != S_OK)
+               if (FAILED(hr = IAudioClient2_SetClientProperties((IAudioClient2 *)audioClient, (AudioClientProperties *)&audioProps)))
                {
                        PRINT(("WASAPI: IAudioClient2_SetClientProperties(IsOffload = %d, Category = %d, Options = %d) failed\n", audioProps.bIsOffload, audioProps.eCategory, audioProps.Options));
                        LogHostError(hr);
@@ -3106,36 +3154,38 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
        }
 #endif
 
+       // Set device scheduling period (always 0 in Shared mode according Microsoft docs)
+       _CalculatePeriodicity(pSub, output, &eventPeriodicity);
+       
        // Open the stream and associate it with an audio session
     hr = IAudioClient_Initialize(audioClient,
         pSub->shareMode,
         pSub->streamFlags,
                pSub->period,
-               (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? pSub->period : 0),
+               eventPeriodicity,
                &pSub->wavex.Format,
         NULL);
        
        // [Output only] Check if buffer size is the one we requested in Exclusive mode, for UAC1 USB DACs WASAPI 
-       // allocates internal buffer equal to 8 times of pSub->period that has to be corrected in order to match 
+       // can allocate internal buffer equal to 8 times of pSub->period that has to be corrected in order to match 
        // the requested latency
-       if (output && (hr == S_OK) && (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE))
+       if (output && SUCCEEDED(hr) && (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE))
        {
-               UINT32 maxBufferSize = 0;
+               UINT32 maxBufferFrames;
 
-               hr = IAudioClient_GetBufferSize(audioClient, &maxBufferSize);
-               if (hr != S_OK)
+               if (FAILED(hr = IAudioClient_GetBufferSize(audioClient, &maxBufferFrames)))
                {
                        (*pa_error) = paInvalidDevice;
                        LogHostError(hr);
                        goto done;
                }
 
-               // For Exclusive mode for UAC1 devices maxBufferSize will be framesPerLatency * 8 but check any difference
+               // For Exclusive mode for UAC1 devices maxBufferFrames may be framesPerLatency * 8 but check any difference
                // to be able to guarantee the latency user requested and also resulted framesPerLatency may be bigger than
                // 2 seconds that will cause audio client not operational (GetCurrentPadding() will return always 0)
-               if (maxBufferSize >= (framesPerLatency * 2))
+               if (maxBufferFrames >= (framesPerLatency * 2))
                {
-                       UINT32 ratio = maxBufferSize / framesPerLatency;
+                       UINT32 ratio = maxBufferFrames / framesPerLatency;
 
                        PRINT(("WASAPI: CreateAudioClient: detected %d times larger buffer than requested, correct to match user latency\n", ratio));
                        
@@ -3151,20 +3201,22 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
                        SAFE_RELEASE(audioClient);
 
                        // Create a new audio client
-                       hr = ActivateAudioInterface(pInfo, &audioClient);
-               if (hr != S_OK)
+               if (FAILED(hr = ActivateAudioInterface(pInfo, &audioClient)))
                        {
                                (*pa_error) = paInsufficientMemory;
                                LogHostError(hr);
                                goto done;
                        }
 
+                       // Set device scheduling period (always 0 in Shared mode according Microsoft docs)
+                       _CalculatePeriodicity(pSub, output, &eventPeriodicity);
+
                        // Open the stream and associate it with an audio session
                        hr = IAudioClient_Initialize(audioClient,
                                pSub->shareMode,
                                pSub->streamFlags,
                                pSub->period,
-                               (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? pSub->period : 0),
+                               eventPeriodicity,
                                &pSub->wavex.Format,
                                NULL);
                }
@@ -3189,132 +3241,61 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
         SAFE_RELEASE(audioClient);
 
         // Create a new audio client
-        hr = ActivateAudioInterface(pInfo, &audioClient);
-       if (hr != S_OK)
+       if (FAILED(hr = ActivateAudioInterface(pInfo, &audioClient)))
                {
                        (*pa_error) = paInsufficientMemory;
                        LogHostError(hr);
                        goto done;
                }
 
+               // Set device scheduling period (always 0 in Shared mode according Microsoft docs)
+               _CalculatePeriodicity(pSub, output, &eventPeriodicity);
+
                // Open the stream and associate it with an audio session
                hr = IAudioClient_Initialize(audioClient,
                        pSub->shareMode,
                        pSub->streamFlags,
                        pSub->period,
-                       (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? pSub->period : 0),
+                       eventPeriodicity,
                        &pSub->wavex.Format,
                        NULL);
        }
 
-       /*! WASAPI buffer size failure. Fallback to using default size.
+       /*! WASAPI buffer size or alignment failure. Fallback to using default size and alignment.
        */
-       if (hr == AUDCLNT_E_BUFFER_SIZE_ERROR)
+       if ((hr == AUDCLNT_E_BUFFER_SIZE_ERROR) || (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED))
        {
                // Use default
                pSub->period = pInfo->DefaultDevicePeriod;
 
-               PRINT(("WASAPI: CreateAudioClient: correcting buffer size to device default\n"));
+               PRINT(("WASAPI: CreateAudioClient: correcting buffer size/alignment to device default\n"));
 
         // Release the previous allocations
         SAFE_RELEASE(audioClient);
 
         // Create a new audio client
-        hr = ActivateAudioInterface(pInfo, &audioClient);
-       if (hr != S_OK)
+       if (FAILED(hr = ActivateAudioInterface(pInfo, &audioClient)))
                {
                        (*pa_error) = paInsufficientMemory;
                        LogHostError(hr);
                        goto done;
                }
 
+               // Set device scheduling period (always 0 in Shared mode according Microsoft docs)
+               _CalculatePeriodicity(pSub, output, &eventPeriodicity);
+
                // Open the stream and associate it with an audio session
                hr = IAudioClient_Initialize(audioClient,
                        pSub->shareMode,
                        pSub->streamFlags,
                        pSub->period,
-                       (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? pSub->period : 0),
+                       eventPeriodicity,
                        &pSub->wavex.Format,
                        NULL);
        }
 
-    /*! If the requested buffer size is not aligned. Can be triggered by Windows 7 and up.
-           Should not be be triggered ever as we do align buffers always with _CalculateAlignedPeriod.
-       */
-    if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
-    {
-               UINT32 frames = 0;
-
-        // Get the next aligned frame
-        hr = IAudioClient_GetBufferSize(audioClient, &frames);
-               if (hr != S_OK)
-               {
-                       (*pa_error) = paInvalidDevice;
-                       LogHostError(hr);
-                       goto done;
-               }
-
-               PRINT(("WASAPI: CreateAudioClient: aligning buffer size to % frames\n", frames));
-
-        // Release the previous allocations
-        SAFE_RELEASE(audioClient);
-
-        // Create a new audio client
-        hr = ActivateAudioInterface(pInfo, &audioClient);
-       if (hr != S_OK)
-               {
-                       (*pa_error) = paInsufficientMemory;
-                       LogHostError(hr);
-                       goto done;
-               }
-
-               // Get closest format
-               if ((error = GetClosestFormat(audioClient, sampleRate, params, pSub->shareMode, &pSub->wavex, output)) != paFormatIsSupported)
-               {
-                       (*pa_error) = error;
-                       LogHostError(hr = AUDCLNT_E_UNSUPPORTED_FORMAT); // fail, format not supported
-                       goto done;
-               }
-
-               // Check for Mono >> Stereo workaround
-               if ((params->channelCount == 1) && (pSub->wavex.Format.nChannels == 2))
-               {
-                       /*if (blocking)
-                       {
-                               LogHostError(hr = AUDCLNT_E_UNSUPPORTED_FORMAT);
-                               goto done; // fail, blocking mode not supported
-                       }*/
-
-                       // Select mixer
-                       pSub->monoMixer = GetMonoToStereoMixer(&pSub->wavex, (pInfo->flow == eRender ? MIX_DIR__1TO2 : MIX_DIR__2TO1_L));
-                       if (pSub->monoMixer == NULL)
-                       {
-                               (*pa_error) = paInvalidChannelCount;
-                               LogHostError(hr = AUDCLNT_E_UNSUPPORTED_FORMAT);
-                               goto done; // fail, no mixer for format
-                       }
-               }
-
-               // Calculate period
-               pSub->period = MakeHnsPeriod(frames, pSub->wavex.Format.nSamplesPerSec);
-
-        // Open the stream and associate it with an audio session
-        hr = IAudioClient_Initialize(audioClient,
-            pSub->shareMode,
-            pSub->streamFlags,
-                       pSub->period,
-                       (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? pSub->period : 0),
-            &pSub->wavex.Format,
-            NULL);
-       if (hr != S_OK)
-               {
-                       (*pa_error) = paInvalidDevice;
-                       LogHostError(hr);
-                       goto done;
-               }
-    }
-    else
-       if (hr != S_OK)
+       // Error has no workaround, fail completely
+       if (FAILED(hr))
     {
                (*pa_error) = paInvalidDevice;
                LogHostError(hr);
@@ -3326,9 +3307,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu
     IAudioClient_AddRef(pSub->clientParent);
 
        // Recalculate buffers count
-       _RecalculateBuffersCount(pSub,
-               userFramesPerBuffer,
-               MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec),
+       _RecalculateBuffersCount(pSub, userFramesPerBuffer, MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec),
                fullDuplex);
 
        // No error, client is succesfully created
@@ -3346,14 +3325,12 @@ static PaError ActivateAudioClientOutput(PaWasapiStream *stream)
 {
        HRESULT hr;
        PaError result;
-
-       UINT32 maxBufferSize   = 0;
-       PaTime buffer_latency  = 0;
-       UINT32 framesPerBuffer = stream->out.params.frames_per_buffer;
+       UINT32 maxBufferSize;
+       PaTime bufferLatency;
+       const UINT32 framesPerBuffer = stream->out.params.frames_per_buffer;
 
        // Create Audio client
-       hr = CreateAudioClient(stream, &stream->out, TRUE, &result);
-    if (hr != S_OK)
+    if (FAILED(hr = CreateAudioClient(stream, &stream->out, TRUE, &result)))
        {
                LogPaError(result);
                goto error;
@@ -3369,8 +3346,7 @@ static PaError ActivateAudioClientOutput(PaWasapiStream *stream)
         return paInvalidDevice;*/
 
     // Get max possible buffer size to check if it is not less than that we request
-    hr = IAudioClient_GetBufferSize(stream->out.clientParent, &maxBufferSize);
-    if (hr != S_OK)
+    if (FAILED(hr = IAudioClient_GetBufferSize(stream->out.clientParent, &maxBufferSize)))
        {
                LogHostError(hr);
                LogPaError(result = paInvalidDevice);
@@ -3380,10 +3356,8 @@ static PaError ActivateAudioClientOutput(PaWasapiStream *stream)
     // Correct buffer to max size if it maxed out result of GetBufferSize
        stream->out.bufferSize = maxBufferSize;
 
-       // Get interface latency (actually uneeded as we calculate latency from the size
-       // of maxBufferSize).
-    hr = IAudioClient_GetStreamLatency(stream->out.clientParent, &stream->out.deviceLatency);
-    if (hr != S_OK)
+       // Get interface latency (actually uneeded as we calculate latency from the size of maxBufferSize)
+    if (FAILED(hr = IAudioClient_GetStreamLatency(stream->out.clientParent, &stream->out.deviceLatency)))
        {
                LogHostError(hr);
                LogPaError(result = paInvalidDevice);
@@ -3399,10 +3373,10 @@ static PaError ActivateAudioClientOutput(PaWasapiStream *stream)
                (stream->out.userBufferAndHostMatch ? stream->out.framesPerHostCallback : framesPerBuffer);
 
        // Calculate buffer latency
-       buffer_latency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec;
+       bufferLatency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec;
 
        // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)
-       stream->out.latencySeconds = buffer_latency;
+       stream->out.latencySeconds = bufferLatency;
 
        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")));
 
@@ -3418,14 +3392,12 @@ static PaError ActivateAudioClientInput(PaWasapiStream *stream)
 {
        HRESULT hr;
        PaError result;
-
-       UINT32 maxBufferSize   = 0;
-       PaTime buffer_latency  = 0;
-       UINT32 framesPerBuffer = stream->in.params.frames_per_buffer;
+       UINT32 maxBufferSize;
+       PaTime bufferLatency;
+       const UINT32 framesPerBuffer = stream->in.params.frames_per_buffer;
 
        // Create Audio client
-       hr = CreateAudioClient(stream, &stream->in, FALSE, &result);
-    if (hr != S_OK)
+    if (FAILED(hr = CreateAudioClient(stream, &stream->in, FALSE, &result)))
        {
                LogPaError(result);
                goto error;
@@ -3441,8 +3413,7 @@ static PaError ActivateAudioClientInput(PaWasapiStream *stream)
         return paInvalidDevice;*/
 
     // Get max possible buffer size to check if it is not less than that we request
-    hr = IAudioClient_GetBufferSize(stream->in.clientParent, &maxBufferSize);
-    if (hr != S_OK)
+    if (FAILED(hr = IAudioClient_GetBufferSize(stream->in.clientParent, &maxBufferSize)))
        {
                LogHostError(hr);
                LogPaError(result = paInvalidDevice);
@@ -3454,8 +3425,7 @@ static PaError ActivateAudioClientInput(PaWasapiStream *stream)
 
        // Get interface latency (actually uneeded as we calculate latency from the size
        // of maxBufferSize).
-    hr = IAudioClient_GetStreamLatency(stream->in.clientParent, &stream->in.deviceLatency);
-    if (hr != S_OK)
+    if (FAILED(hr = IAudioClient_GetStreamLatency(stream->in.clientParent, &stream->in.deviceLatency)))
        {
                LogHostError(hr);
                LogPaError(result = paInvalidDevice);
@@ -3471,10 +3441,10 @@ static PaError ActivateAudioClientInput(PaWasapiStream *stream)
                (stream->in.userBufferAndHostMatch ? stream->in.framesPerHostCallback : framesPerBuffer);
 
        // Calculate buffer latency
-       buffer_latency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec;
+       bufferLatency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec;
 
        // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)
-       stream->in.latencySeconds = buffer_latency;
+       stream->in.latencySeconds = bufferLatency;
 
        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")));
 
@@ -3617,8 +3587,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                stream->in.params.wow64_workaround  = paWasapi->useWOW64Workaround;
 
                // Create and activate audio client
-               hr = ActivateAudioClientInput(stream);
-        if (hr != S_OK)
+        if (FAILED(hr = ActivateAudioClientInput(stream)))
                {
                        LogPaError(result = paInvalidDevice);
                        goto error;
@@ -3636,8 +3605,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         }
 
                // Only get IAudioCaptureClient input once here instead of getting it at multiple places based on the use
-               hr = IAudioClient_GetService(stream->in.clientParent, &pa_IID_IAudioCaptureClient, (void **)&stream->captureClientParent);
-               if (hr != S_OK)
+               if (FAILED(hr = IAudioClient_GetService(stream->in.clientParent, &pa_IID_IAudioCaptureClient, (void **)&stream->captureClientParent)))
                {
                        LogHostError(hr);
                        LogPaError(result = paUnanticipatedHostError);
@@ -3747,8 +3715,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                stream->out.params.wow64_workaround  = paWasapi->useWOW64Workaround;
 
                // Create and activate audio client
-               hr = ActivateAudioClientOutput(stream);
-        if (hr != S_OK)
+        if (FAILED(hr = ActivateAudioClientOutput(stream)))
                {
                        LogPaError(result = paInvalidDevice);
                        goto error;
@@ -3766,8 +3733,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         }
 
                // Only get IAudioCaptureClient output once here instead of getting it at multiple places based on the use
-               hr = IAudioClient_GetService(stream->out.clientParent, &pa_IID_IAudioRenderClient, (void **)&stream->renderClientParent);
-               if (hr != S_OK)
+               if (FAILED(hr = IAudioClient_GetService(stream->out.clientParent, &pa_IID_IAudioRenderClient, (void **)&stream->renderClientParent)))
                {
                        LogHostError(hr);
                        LogPaError(result = paUnanticipatedHostError);
@@ -5320,13 +5286,15 @@ static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor
        // Get buffer
        if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, frames, &data)) != S_OK)
        {
+               // Both modes, Shared and Exclusive, can fail with AUDCLNT_E_BUFFER_TOO_LARGE error
+       #if 0
                if (stream->out.shareMode == AUDCLNT_SHAREMODE_SHARED)
                {
                        // Using GetCurrentPadding to overcome AUDCLNT_E_BUFFER_TOO_LARGE in
                        // shared mode results in no sound in Event-driven mode (MSDN does not
                        // document this, or is it WASAPI bug?), thus we better
                        // try to acquire buffer next time when GetBuffer allows to do so.
-#if 0
+               #if 0
                        // Get Read position
                        UINT32 padding = 0;
                        hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &padding);
@@ -5340,13 +5308,19 @@ static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor
 
                        if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, frames, &data)) != S_OK)
                                return LogHostError(hr);
-#else
+               #else
                        if (hr == AUDCLNT_E_BUFFER_TOO_LARGE)
                                return S_OK; // be silent in shared mode, try again next time
-#endif
+               #endif
                }
                else
                        return LogHostError(hr);
+       #else
+               if (hr == AUDCLNT_E_BUFFER_TOO_LARGE)
+                       return S_OK; // try again next time
+
+               return LogHostError(hr);
+       #endif
        }
 
        // Process data
@@ -5713,7 +5687,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
        ThreadIdleScheduler scheduler;
 
        // Calculate the actual duration of the allocated buffer.
-       DWORD sleep_ms     = 0;
+       DWORD sleep_ms = 0;
        DWORD sleep_ms_in;
        DWORD sleep_ms_out;
 
@@ -5829,18 +5803,19 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
                                }
                                else
                                {
-                                       if (frames != 0)
-                                       {
-                                               // USB DACs report large buffer in Exclusive mode and if it is filled fully will stuck in
-                                               // non playing state, e.g. IAudioClient_GetCurrentPadding() will start reporting max buffer size
-                                               // constantly, thus preload data size equal to the user buffer to allow process going
-                                               if ((stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) && (frames > stream->out.framesPerBuffer))
-                                                       frames = stream->out.framesPerBuffer;
+                                       // Some devices may not start (will get stuck with 0 ready frames) if data not prefetched
+                                       if (frames == 0)
+                                               frames = stream->out.framesPerBuffer;
 
-                                               if ((hr = ProcessOutputBuffer(stream, processor, frames)) != S_OK)
-                                               {
-                                                       LogHostError(hr); // not fatal, just log
-                                               }
+                                       // USB DACs report large buffer in Exclusive mode and if it is filled fully will stuck in
+                                       // non playing state, e.g. IAudioClient_GetCurrentPadding() will start reporting max buffer size
+                                       // constantly, thus preload data size equal to the user buffer to allow process going
+                                       if ((stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) && (frames >= (stream->out.framesPerBuffer * 2)))
+                                               frames -= stream->out.framesPerBuffer;
+
+                                       if ((hr = ProcessOutputBuffer(stream, processor, frames)) != S_OK)
+                                       {
+                                               LogHostError(hr); // not fatal, just log
                                        }
                                }
             }
@@ -5869,15 +5844,14 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
 
        if (!PA_WASAPI__IS_FULLDUPLEX(stream))
        {
-               // Processing Loop
                UINT32 next_sleep = sleep_ms;
+
+               // Processing Loop
                while (WaitForSingleObject(stream->hCloseRequest, next_sleep) == WAIT_TIMEOUT)
                {
                        // Get next sleep time
                        if (sleep_ms == 0)
-                       {
                                next_sleep = ThreadIdleScheduler_NextSleep(&scheduler);
-                       }
 
                        for (i = 0; i < S_COUNT; ++i)
                        {
@@ -5905,14 +5879,14 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
                                        if (stream->renderClient == NULL)
                                                break;
 
-                                       // get available frames
+                                       // Get available frames
                                        if ((hr = _PollGetOutputFramesAvailable(stream, &frames)) != S_OK)
                                        {
                                                LogHostError(hr);
                                                goto thread_error;
                                        }
 
-                                       // output
+                                       // Output data to the user callback
                                        if (stream->bufferMode == paUtilFixedHostBufferSize)
                                        {
                                                while (frames >= stream->out.framesPerBuffer)
@@ -5927,14 +5901,12 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
                                                }
                                        }
                                        else
+                                       if (frames != 0)
                                        {
-                                               if (frames != 0)
+                                               if ((hr = ProcessOutputBuffer(stream, processor, frames)) != S_OK)
                                                {
-                                                       if ((hr = ProcessOutputBuffer(stream, processor, frames)) != S_OK)
-                                                       {
-                                                               LogHostError(hr);
-                                                               goto thread_error;
-                                                       }
+                                                       LogHostError(hr);
+                                                       goto thread_error;
                                                }
                                        }