SDL: wasapi: Use IAudioClient3 if possible to make use of sample_frames

From 8252f596684d5923c80659e6f65f85fafe398e8f Mon Sep 17 00:00:00 2001
From: hwsmm <[EMAIL REDACTED]>
Date: Fri, 19 Apr 2024 23:36:37 +0900
Subject: [PATCH] wasapi: Use IAudioClient3 if possible to make use of
 sample_frames

---
 src/audio/wasapi/SDL_wasapi.c | 46 ++++++++++++++++++++++++++++++++---
 1 file changed, 42 insertions(+), 4 deletions(-)

diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c
index ee1f25b7cb69b..5206c09aacd8d 100644
--- a/src/audio/wasapi/SDL_wasapi.c
+++ b/src/audio/wasapi/SDL_wasapi.c
@@ -46,6 +46,7 @@
 // Some GUIDs we need to know without linking to libraries that aren't available before Vista.
 static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
 static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
+static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } };
 
 
 // WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
@@ -632,7 +633,42 @@ static int mgmtthrtask_PrepDevice(void *userdata)
     newspec.freq = waveformat->nSamplesPerSec;
 
     streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
-    ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
+
+    int new_sample_frames = 0;
+    SDL_bool iaudioclient3_initialized = SDL_FALSE;
+
+#ifdef __IAudioClient3_INTERFACE_DEFINED__
+    // Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED
+    if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
+        IAudioClient3 *client3 = NULL;
+        ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, &client3);
+        if (SUCCEEDED(ret)) {
+            UINT32 default_period_in_frames = 0;
+            UINT32 fundamental_period_in_frames = 0;
+            UINT32 min_period_in_frames = 0;
+            UINT32 max_period_in_frames = 0;
+            ret = IAudioClient3_GetSharedModeEnginePeriod(client3, waveformat,
+                                                          &default_period_in_frames, &fundamental_period_in_frames, &min_period_in_frames, &max_period_in_frames);
+            if (SUCCEEDED(ret)) {
+                // IAudioClient3_InitializeSharedAudioStream only accepts the integral multiple of fundamental_period_in_frames
+                UINT32 period_in_frames = fundamental_period_in_frames * (UINT32)SDL_round((double)device->sample_frames / fundamental_period_in_frames);
+                period_in_frames = SDL_clamp(period_in_frames, min_period_in_frames, max_period_in_frames);
+
+                ret = IAudioClient3_InitializeSharedAudioStream(client3, streamflags, period_in_frames, waveformat, NULL);
+                if (SUCCEEDED(ret)) {
+                    new_sample_frames = (int)period_in_frames;
+                    iaudioclient3_initialized = SDL_TRUE;
+                }
+            }
+            
+            IAudioClient3_Release(client3);
+        }
+    }
+#endif
+
+    if (!iaudioclient3_initialized)
+        ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
+
     if (FAILED(ret)) {
         return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
     }
@@ -650,9 +686,11 @@ static int mgmtthrtask_PrepDevice(void *userdata)
 
     // Match the callback size to the period size to cut down on the number of
     // interrupts waited for in each call to WaitDevice
-    const float period_millis = default_period / 10000.0f;
-    const float period_frames = period_millis * newspec.freq / 1000.0f;
-    int new_sample_frames = (int) SDL_ceilf(period_frames);
+    if (new_sample_frames <= 0) {
+        const float period_millis = default_period / 10000.0f;
+        const float period_frames = period_millis * newspec.freq / 1000.0f;
+        new_sample_frames = (int) SDL_ceilf(period_frames);
+    }
 
     // regardless of what we calculated for the period size, clamp it to the expected hardware buffer size.
     if (new_sample_frames > (int) bufsize) {