Fixed the audio 'output latency' project setting not appearing when using the WASAPI audio driver. Added variable output latency support to the WASAPI audio driver for systems that support it.

This commit is contained in:
Ben Armstrong 2020-04-26 08:21:26 +10:00
parent ec1bf96e41
commit 880d4703a4
2 changed files with 131 additions and 18 deletions

View file

@ -35,8 +35,60 @@
#include "core/os/os.h"
#include "core/project_settings.h"
#include <stdint.h> // INT32_MAX
#include <functiondiscoverykeys.h>
// Define IAudioClient3 if not already defined by MinGW headers
#if defined __MINGW32__ || defined __MINGW64__
#ifndef __IAudioClient3_FWD_DEFINED__
#define __IAudioClient3_FWD_DEFINED__
typedef interface IAudioClient3 IAudioClient3;
#endif // __IAudioClient3_FWD_DEFINED__
#ifndef __IAudioClient3_INTERFACE_DEFINED__
#define __IAudioClient3_INTERFACE_DEFINED__
MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42")
IAudioClient3 : public IAudioClient2 {
public:
virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
/* [annotation][in] */
_In_ const WAVEFORMATEX *pFormat,
/* [annotation][out] */
_Out_ UINT32 *pDefaultPeriodInFrames,
/* [annotation][out] */
_Out_ UINT32 *pFundamentalPeriodInFrames,
/* [annotation][out] */
_Out_ UINT32 *pMinPeriodInFrames,
/* [annotation][out] */
_Out_ UINT32 *pMaxPeriodInFrames) = 0;
virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
/* [unique][annotation][out] */
_Out_ WAVEFORMATEX * *ppFormat,
/* [annotation][out] */
_Out_ UINT32 * pCurrentPeriodInFrames) = 0;
virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
/* [annotation][in] */
_In_ DWORD StreamFlags,
/* [annotation][in] */
_In_ UINT32 PeriodInFrames,
/* [annotation][in] */
_In_ const WAVEFORMATEX *pFormat,
/* [annotation][in] */
_In_opt_ LPCGUID AudioSessionGuid) = 0;
};
__CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B, 0x7A, 0x59, 0x87, 0xAD, 0x42)
#endif // __IAudioClient3_INTERFACE_DEFINED__
#endif // __MINGW32__ || __MINGW64__
#ifndef PKEY_Device_FriendlyName
#undef DEFINE_PROPERTYKEY
@ -51,6 +103,7 @@ DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioClient3 = __uuidof(IAudioClient3);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
@ -221,7 +274,22 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
ERR_PRINT("WASAPI: RegisterEndpointNotificationCallback error");
}
hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client);
bool using_audio_client_3 = !p_capture; // IID_IAudioClient3 is only used for adjustable output latency (not input)
if (using_audio_client_3) {
hr = device->Activate(IID_IAudioClient3, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client);
if (hr != S_OK) {
// IID_IAudioClient3 will never activate on OS versions before Windows 10.
// Older Windows versions should fall back gracefully.
using_audio_client_3 = false;
print_verbose("WASAPI: Couldn't activate device with IAudioClient3 interface, falling back to IAudioClient interface");
} else {
print_verbose("WASAPI: Activated device using IAudioClient3 interface");
}
}
if (!using_audio_client_3) {
hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client);
}
SAFE_RELEASE(device)
if (reinit) {
@ -232,6 +300,16 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
}
if (using_audio_client_3) {
AudioClientProperties audioProps;
audioProps.cbSize = sizeof(AudioClientProperties);
audioProps.bIsOffload = FALSE;
audioProps.eCategory = AudioCategory_GameEffects;
hr = ((IAudioClient3 *)p_device->audio_client)->SetClientProperties(&audioProps);
ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: SetClientProperties failed with error 0x" + String::num_uint64(hr, 16) + ".");
}
hr = p_device->audio_client->GetMixFormat(&pwfex);
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
@ -285,15 +363,55 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
}
}
DWORD streamflags = 0;
if ((DWORD)mix_rate != pwfex->nSamplesPerSec) {
streamflags |= AUDCLNT_STREAMFLAGS_RATEADJUST;
pwfex->nSamplesPerSec = mix_rate;
pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nChannels * (pwfex->wBitsPerSample / 8);
}
if (!using_audio_client_3) {
DWORD streamflags = 0;
if ((DWORD)mix_rate != pwfex->nSamplesPerSec) {
streamflags |= AUDCLNT_STREAMFLAGS_RATEADJUST;
pwfex->nSamplesPerSec = mix_rate;
pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nChannels * (pwfex->wBitsPerSample / 8);
}
hr = p_device->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, streamflags, p_capture ? REFTIMES_PER_SEC : 0, 0, pwfex, nullptr);
ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + ".");
UINT32 max_frames;
HRESULT hr = p_device->audio_client->GetBufferSize(&max_frames);
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
hr = p_device->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, streamflags, p_capture ? REFTIMES_PER_SEC : 0, 0, pwfex, nullptr);
ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + ".");
// Due to WASAPI Shared Mode we have no control of the buffer size
buffer_frames = max_frames;
} else {
IAudioClient3 *device_audio_client_3 = (IAudioClient3 *)p_device->audio_client;
// AUDCLNT_STREAMFLAGS_RATEADJUST is an invalid flag with IAudioClient3, therefore we have to use
// the closest supported mix rate supported by the audio driver.
mix_rate = pwfex->nSamplesPerSec;
print_verbose("WASAPI: mix_rate = " + itos(mix_rate));
UINT32 default_period_frames, fundamental_period_frames, min_period_frames, max_period_frames;
hr = device_audio_client_3->GetSharedModeEnginePeriod(
pwfex,
&default_period_frames,
&fundamental_period_frames,
&min_period_frames,
&max_period_frames);
ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: GetSharedModeEnginePeriod failed with error 0x" + String::num_uint64(hr, 16) + ".");
// Period frames must be an integral multiple of fundamental_period_frames or IAudioClient3 initialization will fail,
// so we need to select the closest multiple to the user-specified latency.
UINT32 desired_period_frames = target_latency_ms * mix_rate / 1000;
UINT32 period_frames = (desired_period_frames / fundamental_period_frames) * fundamental_period_frames;
if (ABS((int64_t)period_frames - (int64_t)desired_period_frames) > ABS((int64_t)(period_frames + fundamental_period_frames) - (int64_t)desired_period_frames)) {
period_frames = period_frames + fundamental_period_frames;
}
period_frames = CLAMP(period_frames, min_period_frames, max_period_frames);
print_verbose("WASAPI: fundamental_period_frames = " + itos(fundamental_period_frames));
print_verbose("WASAPI: min_period_frames = " + itos(min_period_frames));
print_verbose("WASAPI: max_period_frames = " + itos(max_period_frames));
print_verbose("WASAPI: selected a period frame size of " + itos(period_frames));
buffer_frames = period_frames;
hr = device_audio_client_3->InitializeSharedAudioStream(0, period_frames, pwfex, nullptr);
ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: InitializeSharedAudioStream failed with error 0x" + String::num_uint64(hr, 16) + ".");
}
if (p_capture) {
hr = p_device->audio_client->GetService(IID_IAudioCaptureClient, (void **)&p_device->capture_client);
@ -328,13 +446,6 @@ Error AudioDriverWASAPI::init_render_device(bool reinit) {
break;
}
UINT32 max_frames;
HRESULT hr = audio_output.audio_client->GetBufferSize(&max_frames);
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
// Due to WASAPI Shared Mode we have no control of the buffer size
buffer_frames = max_frames;
// Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels)
samples_in.resize(buffer_frames * channels);
@ -367,7 +478,6 @@ Error AudioDriverWASAPI::audio_device_finish(AudioDeviceWASAPI *p_device) {
if (p_device->audio_client) {
p_device->audio_client->Stop();
}
p_device->active = false;
}
@ -389,6 +499,8 @@ Error AudioDriverWASAPI::finish_capture_device() {
Error AudioDriverWASAPI::init() {
mix_rate = GLOBAL_GET("audio/mix_rate");
target_latency_ms = GLOBAL_GET("audio/output_latency");
Error err = init_render_device();
if (err != OK) {
ERR_PRINT("WASAPI: init_render_device error");

View file

@ -71,6 +71,7 @@ class AudioDriverWASAPI : public AudioDriver {
unsigned int channels = 0;
int mix_rate = 0;
int buffer_frames = 0;
int target_latency_ms = 0;
bool thread_exited = false;
mutable bool exit_thread = false;
@ -114,5 +115,5 @@ public:
AudioDriverWASAPI();
};
#endif // WASAPI_ENABLED
#endif // AUDIO_DRIVER_WASAPI_H
#endif