mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
wasapi: initialise the shared client at the engine's minimum period via IAudioClient3
Shared mode passed hnsPeriodicity 0 to IAudioClient::Initialize, which means the engine's default period - 10 ms on a typical desktop. The write event fires once per engine period, so a client blocking on it is released on a 10 ms grid regardless of buffer size. With audio_sync on that is the grid the whole frame loop is released on, and at 60 fps it is the largest single source of frame-time jitter under VRR. Windows 10 1607 added IAudioClient3::InitializeSharedAudioStream, which lets a shared client run at a smaller engine period, down to the minimum GetSharedModeEnginePeriod reports - typically 128 frames, 2.67 ms at 48 kHz. Use it when it is there: query the interface, read the minimum, initialise at it. The event now fires every ~2.7 ms and the release grid is four times finer. Backwards compatibility, at each layer: Build. Everything new sits under __IAudioClient3_INTERFACE_DEFINED__, the guard the SDK header sets. An SDK without the interface compiles the IAudioClient path only, byte for byte as before. The IID is defined locally rather than through the SDK export, which mingw's libuuid lacks. Runtime. QueryInterface fails on pre-1607 Windows, or the period query or the initialise can fail on a driver that does not support it; in every case the client is released, re-activated fresh, and handed to the unchanged legacy path. Nothing downstream can tell the difference except the flag. Latency budget. The default FIFO sizing takes the engine buffer size, and under IAudioClient3 the engine sizes that buffer to the small period - a few ms, no longer reflecting audio_latency. When the low-latency path engaged, the FIFO is sized from audio_latency directly, so the user's buffer is what it always was and only the drain cadence changed. Exclusive mode and the microphone are untouched; the microphone passes NULL for the new out-parameter. Both COM macro branches (C lpVtbl and C++) carry the new methods.
This commit is contained in:
1 parent
99f381f4ac
commit
338abdf045
2 files changed
+109
-8
No files matched your search
@@ -116,6 +116,16 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0
|
||||
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); /* DEVPROP_TYPE_STRING */
|
||||
|
||||
/* IAudioClient3 (Windows 10 1607+): shared-mode streams at an engine
|
||||
* period smaller than the default. Only compiled where the SDK declares
|
||||
* the interface; older SDKs take the IAudioClient path unchanged. The
|
||||
* IID is defined here under our own name rather than relying on the
|
||||
* SDK's IID_IAudioClient3 export, which mingw's libuuid does not carry. */
|
||||
#ifdef __IAudioClient3_INTERFACE_DEFINED__
|
||||
static const GUID mmdevice_IID_IAudioClient3 =
|
||||
{ 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } };
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define _IMMDeviceCollection_Item(This,nDevice,ppdevice) (This)->Item(nDevice,ppdevice)
|
||||
#define _IAudioClient_Start(This) ( (This)->Start() )
|
||||
@@ -133,6 +143,14 @@ DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0
|
||||
#define _IAudioClient_GetDevicePeriod(This,phnsDefaultDevicePeriod,phnsMinimumDevicePeriod) ( (This)->GetDevicePeriod(phnsDefaultDevicePeriod,phnsMinimumDevicePeriod) )
|
||||
#define _IAudioClient_Initialize(This,ShareMode,StreamFlags,hnsBufferDuration,hnsPeriodicity,pFormat,AudioSessionGuid) \
|
||||
( (This)->Initialize(ShareMode,StreamFlags,hnsBufferDuration,hnsPeriodicity,pFormat,AudioSessionGuid))
|
||||
#define _IAudioClient_QueryInterface(This,riid,ppv) ( (This)->QueryInterface(riid,ppv) )
|
||||
#ifdef __IAudioClient3_INTERFACE_DEFINED__
|
||||
#define _IAudioClient3_GetSharedModeEnginePeriod(This,pFormat,pDefault,pFundamental,pMin,pMax) \
|
||||
( (This)->GetSharedModeEnginePeriod(pFormat,pDefault,pFundamental,pMin,pMax) )
|
||||
#define _IAudioClient3_InitializeSharedAudioStream(This,StreamFlags,PeriodInFrames,pFormat,AudioSessionGuid) \
|
||||
( (This)->InitializeSharedAudioStream(StreamFlags,PeriodInFrames,pFormat,AudioSessionGuid) )
|
||||
#define _IAudioClient3_Release(This) ( (This)->Release() )
|
||||
#endif
|
||||
#define _IAudioClient_IsFormatSupported(This,ShareMode,pFormat,ppClosestMatch) \
|
||||
( (This)->IsFormatSupported(ShareMode,pFormat,ppClosestMatch))
|
||||
#define _IMMDevice_Activate(This,iid,dwClsCtx,pActivationParams,ppv) ((This)->Activate(iid,(dwClsCtx),pActivationParams,ppv))
|
||||
@@ -167,6 +185,14 @@ DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0
|
||||
#define _IAudioClient_GetDevicePeriod(This,phnsDefaultDevicePeriod,phnsMinimumDevicePeriod) ( (This)->lpVtbl -> GetDevicePeriod(This,phnsDefaultDevicePeriod,phnsMinimumDevicePeriod) )
|
||||
#define _IAudioClient_Initialize(This,ShareMode,StreamFlags,hnsBufferDuration,hnsPeriodicity,pFormat,AudioSessionGuid) \
|
||||
( (This)->lpVtbl->Initialize(This,ShareMode,StreamFlags,hnsBufferDuration,hnsPeriodicity,pFormat,AudioSessionGuid))
|
||||
#define _IAudioClient_QueryInterface(This,riid,ppv) ( (This)->lpVtbl->QueryInterface(This,riid,ppv) )
|
||||
#ifdef __IAudioClient3_INTERFACE_DEFINED__
|
||||
#define _IAudioClient3_GetSharedModeEnginePeriod(This,pFormat,pDefault,pFundamental,pMin,pMax) \
|
||||
( (This)->lpVtbl->GetSharedModeEnginePeriod(This,pFormat,pDefault,pFundamental,pMin,pMax) )
|
||||
#define _IAudioClient3_InitializeSharedAudioStream(This,StreamFlags,PeriodInFrames,pFormat,AudioSessionGuid) \
|
||||
( (This)->lpVtbl->InitializeSharedAudioStream(This,StreamFlags,PeriodInFrames,pFormat,AudioSessionGuid))
|
||||
#define _IAudioClient3_Release(This) ( (This)->lpVtbl->Release(This) )
|
||||
#endif
|
||||
#define _IAudioClient_IsFormatSupported(This,ShareMode,pFormat,ppClosestMatch) \
|
||||
( (This)->lpVtbl->IsFormatSupported(This,ShareMode,pFormat,ppClosestMatch))
|
||||
#define _IMMDevice_Activate(This,iid,dwClsCtx,pActivationParams,ppv) ((This)->lpVtbl->Activate(This,&(iid),dwClsCtx,pActivationParams,ppv))
|
||||
|
||||
+83
-8
@@ -57,7 +57,11 @@ enum wasapi_flags
|
||||
{
|
||||
WASAPI_FLG_EXCLUSIVE = (1 << 0),
|
||||
WASAPI_FLG_NONBLOCK = (1 << 1),
|
||||
WASAPI_FLG_RUNNING = (1 << 2)
|
||||
WASAPI_FLG_RUNNING = (1 << 2),
|
||||
/* Shared client initialised via IAudioClient3 at the engine's minimum
|
||||
* period rather than its default. The client FIFO keeps its
|
||||
* audio_latency size; only the release cadence is finer. */
|
||||
WASAPI_FLG_LOWLAT = (1 << 3)
|
||||
};
|
||||
|
||||
typedef struct
|
||||
@@ -430,7 +434,8 @@ error:
|
||||
}
|
||||
|
||||
static IAudioClient *wasapi_init_client_sh(IMMDevice *device,
|
||||
bool *float_fmt, unsigned *rate, unsigned latency, unsigned channels)
|
||||
bool *float_fmt, unsigned *rate, unsigned latency, unsigned channels,
|
||||
bool *low_latency)
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE wf;
|
||||
IAudioClient *client = NULL;
|
||||
@@ -477,6 +482,60 @@ static IAudioClient *wasapi_init_client_sh(IMMDevice *device,
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (low_latency)
|
||||
*low_latency = false;
|
||||
|
||||
#ifdef __IAudioClient3_INTERFACE_DEFINED__
|
||||
/* Windows 10 1607+: ask the engine for its minimum shared-mode
|
||||
* period instead of the default 10 ms. The write event then fires
|
||||
* at that period, and a client blocking on it is released on a
|
||||
* grid several times finer - which, with audio_sync on, is the grid
|
||||
* the whole frame loop is released on. Any failure here falls
|
||||
* through to the IAudioClient path below on a fresh client, so a
|
||||
* system that cannot do this behaves exactly as before. */
|
||||
{
|
||||
IAudioClient3 *client3 = NULL;
|
||||
hr = _IAudioClient_QueryInterface(client,
|
||||
&mmdevice_IID_IAudioClient3, (void**)&client3);
|
||||
if (SUCCEEDED(hr) && client3)
|
||||
{
|
||||
UINT32 p_default = 0, p_fundamental = 0, p_min = 0, p_max = 0;
|
||||
hr = _IAudioClient3_GetSharedModeEnginePeriod(client3,
|
||||
(WAVEFORMATEX*)&wf, &p_default, &p_fundamental, &p_min, &p_max);
|
||||
if (SUCCEEDED(hr) && p_min > 0 && p_min < p_default)
|
||||
{
|
||||
hr = _IAudioClient3_InitializeSharedAudioStream(client3,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, p_min,
|
||||
(WAVEFORMATEX*)&wf, NULL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if (low_latency)
|
||||
*low_latency = true;
|
||||
RARCH_LOG("[WASAPI] Shared client at engine period %u frames (%.2f ms; default %u).\n",
|
||||
p_min, (double)p_min * 1000.0 / (double)wf.Format.nSamplesPerSec,
|
||||
p_default);
|
||||
}
|
||||
}
|
||||
_IAudioClient3_Release(client3);
|
||||
}
|
||||
|
||||
if (low_latency && *low_latency)
|
||||
goto initialized;
|
||||
|
||||
/* Not taken, or failed partway: start again from a fresh client
|
||||
* so the legacy path sees exactly the object it always has. */
|
||||
RELEASE(client);
|
||||
hr = _IMMDevice_Activate(device, IID_IAudioClient, CLSCTX_ALL, NULL,
|
||||
(void**)&client);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
RARCH_ERR("[WASAPI] IMMDevice::Activate failed: %s.\n",
|
||||
mmdevice_hresult_name(hr));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
hr = _IAudioClient_Initialize(client, AUDCLNT_SHAREMODE_SHARED,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
buffer_duration, 0, (WAVEFORMATEX*)&wf, NULL);
|
||||
@@ -507,6 +566,9 @@ static IAudioClient *wasapi_init_client_sh(IMMDevice *device,
|
||||
goto error;
|
||||
}
|
||||
|
||||
#ifdef __IAudioClient3_INTERFACE_DEFINED__
|
||||
initialized:
|
||||
#endif
|
||||
*float_fmt = wf.Format.wFormatTag != WAVE_FORMAT_PCM;
|
||||
*rate = wf.Format.nSamplesPerSec;
|
||||
return client;
|
||||
@@ -518,7 +580,8 @@ error:
|
||||
|
||||
|
||||
static IAudioClient *wasapi_init_client(IMMDevice *device, bool *exclusive,
|
||||
bool *float_fmt, unsigned *rate, unsigned latency, unsigned channels)
|
||||
bool *float_fmt, unsigned *rate, unsigned latency, unsigned channels,
|
||||
bool *low_latency)
|
||||
{
|
||||
HRESULT hr;
|
||||
IAudioClient *client;
|
||||
@@ -534,14 +597,14 @@ static IAudioClient *wasapi_init_client(IMMDevice *device, bool *exclusive,
|
||||
if (!client)
|
||||
{
|
||||
RARCH_WARN("[WASAPI] Failed to initialize exclusive client, attempting shared client.\n");
|
||||
client = wasapi_init_client_sh(device, float_fmt, rate, latency, channels);
|
||||
client = wasapi_init_client_sh(device, float_fmt, rate, latency, channels, low_latency);
|
||||
if (client)
|
||||
*exclusive = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
client = wasapi_init_client_sh(device, float_fmt, rate, latency, channels);
|
||||
client = wasapi_init_client_sh(device, float_fmt, rate, latency, channels, low_latency);
|
||||
if (!client)
|
||||
{
|
||||
RARCH_WARN("[WASAPI] Failed to initialize shared client, attempting exclusive client.\n");
|
||||
@@ -954,7 +1017,7 @@ static void *wasapi_microphone_open_mic(void *driver_context, const char *device
|
||||
}
|
||||
|
||||
mic->client = wasapi_init_client(mic->device,
|
||||
&mic->exclusive, &float_format, &rate, latency, 1);
|
||||
&mic->exclusive, &float_format, &rate, latency, 1, NULL);
|
||||
if (!mic->client)
|
||||
{
|
||||
RARCH_ERR("[WASAPI] Failed to open client for capture device \"%s\".\n", mic->device_name);
|
||||
@@ -1160,6 +1223,7 @@ static void *wasapi_init(const char *dev_id, unsigned rate, unsigned latency,
|
||||
bool exclusive_mode = settings->bools.audio_wasapi_exclusive_mode;
|
||||
bool audio_sync = settings->bools.audio_sync;
|
||||
unsigned sh_buffer_length = settings->uints.audio_wasapi_sh_buffer_length;
|
||||
bool low_latency = false;
|
||||
wasapi_t *w = (wasapi_t*)calloc(1, sizeof(wasapi_t));
|
||||
|
||||
if (!w)
|
||||
@@ -1172,10 +1236,12 @@ static void *wasapi_init(const char *dev_id, unsigned rate, unsigned latency,
|
||||
goto error;
|
||||
|
||||
if (!(w->client = wasapi_init_client(w->device,
|
||||
&exclusive_mode, &float_format, &rate, latency, 2)))
|
||||
&exclusive_mode, &float_format, &rate, latency, 2, &low_latency)))
|
||||
goto error;
|
||||
if (exclusive_mode)
|
||||
w->flags |= WASAPI_FLG_EXCLUSIVE;
|
||||
if (low_latency)
|
||||
w->flags |= WASAPI_FLG_LOWLAT;
|
||||
|
||||
hr = _IAudioClient_GetBufferSize(w->client, &frame_count);
|
||||
if (FAILED(hr))
|
||||
@@ -1196,7 +1262,16 @@ static void *wasapi_init(const char *dev_id, unsigned rate, unsigned latency,
|
||||
{
|
||||
case WASAPI_SH_BUFFER_AUDIO_LATENCY:
|
||||
case WASAPI_SH_BUFFER_CLIENT_BUFFER:
|
||||
sh_buffer_length = frame_count;
|
||||
/* Under IAudioClient3 the engine sizes its own buffer to the
|
||||
* small period, so frame_count is a few ms and no longer
|
||||
* reflects audio_latency. Keep the FIFO at the latency the
|
||||
* user asked for; the engine buffer is only the drain step. */
|
||||
if (w->flags & WASAPI_FLG_LOWLAT)
|
||||
sh_buffer_length = (unsigned)(((uint64_t)latency * rate) / 1000);
|
||||
else
|
||||
sh_buffer_length = frame_count;
|
||||
if (sh_buffer_length < frame_count)
|
||||
sh_buffer_length = frame_count;
|
||||
break;
|
||||
case WASAPI_SH_BUFFER_DEVICE_PERIOD:
|
||||
hr = _IAudioClient_GetDevicePeriod(w->client, &dev_period, NULL);
|
||||
|
||||
Reference in new issue
Block a user