From 598a0c0d3789450b612170257fe5d58855b48756 Mon Sep 17 00:00:00 2001 From: sonninnos <45124675+sonninnos@users.noreply.github.com> Date: Sun, 19 Nov 2023 18:44:25 +0200 Subject: [PATCH] (WASAPI) Shared buffer refactor + cleanup (#15929) --- audio/common/wasapi.c | 301 ++++++++++++++++++---------------------- audio/common/wasapi.h | 6 +- audio/drivers/wasapi.c | 54 ++++--- config.def.h | 2 +- configuration.c | 19 ++- configuration.h | 16 ++- menu/menu_displaylist.c | 6 +- menu/menu_setting.c | 71 +++++++--- runloop.c | 3 +- 9 files changed, 248 insertions(+), 230 deletions(-) diff --git a/audio/common/wasapi.c b/audio/common/wasapi.c index 1bcf987da6..1895516ab4 100644 --- a/audio/common/wasapi.c +++ b/audio/common/wasapi.c @@ -151,7 +151,35 @@ static const char* wasapi_data_flow_name(EDataFlow data_flow) } static void wasapi_set_format(WAVEFORMATEXTENSIBLE *wf, - bool float_fmt, unsigned rate, unsigned channels); + bool float_fmt, unsigned rate, unsigned channels) +{ + WORD wBitsPerSample = float_fmt ? 32 : 16; + WORD nBlockAlign = (channels * wBitsPerSample) / 8; + DWORD nAvgBytesPerSec = rate * nBlockAlign; + + wf->Format.nChannels = channels; + wf->Format.nSamplesPerSec = rate; + wf->Format.nAvgBytesPerSec = nAvgBytesPerSec; + wf->Format.nBlockAlign = nBlockAlign; + wf->Format.wBitsPerSample = wBitsPerSample; + + if (float_fmt) + { + wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wf->Format.cbSize = sizeof(WORD) + sizeof(DWORD) + sizeof(GUID); + wf->Samples.wValidBitsPerSample = wBitsPerSample; + wf->dwChannelMask = channels == 1 ? KSAUDIO_SPEAKER_MONO : KSAUDIO_SPEAKER_STEREO; + wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } + else + { + wf->Format.wFormatTag = WAVE_FORMAT_PCM; + wf->Format.cbSize = 0; + wf->Samples.wValidBitsPerSample = 0; + wf->dwChannelMask = 0; + memset(&wf->SubFormat, 0, sizeof(wf->SubFormat)); + } +} /** * @param[in] format The format to check. @@ -186,6 +214,7 @@ static bool wasapi_is_format_suitable(const WAVEFORMATEXTENSIBLE *format) return true; } + /** * Selects a sample format suitable for the given device. * @param[in,out] format The place where the chosen format will be written, @@ -201,7 +230,7 @@ static bool wasapi_select_device_format(WAVEFORMATEXTENSIBLE *format, IAudioClie { static const unsigned preferred_rates[] = { 48000, 44100, 96000, 192000, 32000 }; const bool preferred_formats[] = {format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE, format->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE}; - /* Try the requested sample format first, then try the other one */ + /* Try the requested sample format first, then try the other one. */ WAVEFORMATEXTENSIBLE *suggested_format = NULL; bool result = false; HRESULT hr = _IAudioClient_IsFormatSupported( @@ -213,15 +242,13 @@ static bool wasapi_select_device_format(WAVEFORMATEXTENSIBLE *format, IAudioClie switch (hr) { case S_OK: - /* The requested format is okay without any changes */ - RARCH_DBG("[WASAPI]: Desired format (%s, %u-channel, %uHz) can be used as-is.\n", - wave_format_name(format), format->Format.nChannels, format->Format.nSamplesPerSec); + /* The requested format is okay without any changes. */ result = true; break; case S_FALSE: /* The requested format is unsupported, but Windows has suggested a similar one. */ RARCH_DBG("[WASAPI]: Windows suggests a format of (%s, %u-channel, %uHz).\n", - wave_format_name(suggested_format), suggested_format->Format.nChannels, suggested_format->Format.nSamplesPerSec); + wave_format_name(suggested_format), suggested_format->Format.nChannels, suggested_format->Format.nSamplesPerSec); if (wasapi_is_format_suitable(suggested_format)) { *format = *suggested_format; @@ -234,10 +261,11 @@ static bool wasapi_select_device_format(WAVEFORMATEXTENSIBLE *format, IAudioClie } break; case AUDCLNT_E_UNSUPPORTED_FORMAT: - { /* The requested format is unsupported - * and Windows was unable to suggest another. - * Usually happens with exclusive mode. - * RetroArch will try selecting a format. */ + { + /* The requested format is unsupported + * and Windows was unable to suggest another. + * Usually happens with exclusive mode. + * RetroArch will try selecting a format. */ size_t i, j; WAVEFORMATEXTENSIBLE possible_format; HRESULT format_check_hr; @@ -253,60 +281,29 @@ static bool wasapi_select_device_format(WAVEFORMATEXTENSIBLE *format, IAudioClie *format = possible_format; result = true; RARCH_DBG("[WASAPI]: RetroArch suggests a format of (%s, %u-channel, %uHz).\n", - wave_format_name(format), format->Format.nChannels, format->Format.nSamplesPerSec); + wave_format_name(format), format->Format.nChannels, format->Format.nSamplesPerSec); goto done; } } } - RARCH_ERR("[WASAPI]: Failed to select client format: No suitable format available\n"); + RARCH_ERR("[WASAPI]: Failed to select client format: No suitable format available.\n"); result = false; break; } default: /* Something else went wrong. */ - RARCH_ERR("[WASAPI]: Failed to select client format: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: Failed to select client format: %s.\n", hresult_name(hr)); result = false; break; } done: - /* IAudioClient::IsFormatSupported allocates a format object */ + /* IAudioClient::IsFormatSupported allocates a format object. */ if (suggested_format) CoTaskMemFree(suggested_format); return result; } -static void wasapi_set_format(WAVEFORMATEXTENSIBLE *wf, - bool float_fmt, unsigned rate, unsigned channels) -{ - WORD wBitsPerSample = float_fmt ? 32 : 16; - WORD nBlockAlign = (channels * wBitsPerSample) / 8; - DWORD nAvgBytesPerSec = rate * nBlockAlign; - - wf->Format.nChannels = channels; - wf->Format.nSamplesPerSec = rate; - wf->Format.nAvgBytesPerSec = nAvgBytesPerSec; - wf->Format.nBlockAlign = nBlockAlign; - wf->Format.wBitsPerSample = wBitsPerSample; - - if (float_fmt) - { - wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - wf->Format.cbSize = sizeof(WORD) + sizeof(DWORD) + sizeof(GUID); - wf->Samples.wValidBitsPerSample = wBitsPerSample; - wf->dwChannelMask = channels == 1 ? KSAUDIO_SPEAKER_MONO : KSAUDIO_SPEAKER_STEREO; - wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - } - else - { - wf->Format.wFormatTag = WAVE_FORMAT_PCM; - wf->Format.cbSize = 0; - wf->Samples.wValidBitsPerSample = 0; - wf->dwChannelMask = 0; - memset(&wf->SubFormat, 0, sizeof(wf->SubFormat)); - } -} - static IAudioClient *wasapi_init_client_ex(IMMDevice *device, bool *float_fmt, unsigned *rate, unsigned latency, unsigned channels) { @@ -320,59 +317,54 @@ static IAudioClient *wasapi_init_client_ex(IMMDevice *device, if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: IMMDevice::Activate failed: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: IMMDevice::Activate failed: %s.\n", hresult_name(hr)); return NULL; } hr = _IAudioClient_GetDevicePeriod(client, NULL, &minimum_period); if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: Failed to get device period of exclusive-mode client: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: Failed to get minimum device period of exclusive client: %s.\n", hresult_name(hr)); goto error; } - /* buffer_duration is in 100ns units */ + /* Buffer_duration is in 100ns units. */ buffer_duration = latency * 10000.0; if (buffer_duration < minimum_period) buffer_duration = minimum_period; wasapi_set_format(&wf, *float_fmt, *rate, channels); - RARCH_DBG("[WASAPI]: Requesting format: %u-bit %u-channel client with %s samples at %uHz\n", - wf.Format.wBitsPerSample, - wf.Format.nChannels, wave_format_name(&wf), wf.Format.nSamplesPerSec); - - if (wasapi_select_device_format(&wf, client, AUDCLNT_SHAREMODE_EXCLUSIVE, channels)) - { - RARCH_DBG("[WASAPI]: Using format: %u-bit %u-channel client with %s samples at %uHz\n", + RARCH_DBG("[WASAPI]: Requesting exclusive %u-bit %u-channel client with %s samples at %uHz %ums.\n", wf.Format.wBitsPerSample, - wf.Format.nChannels, wave_format_name(&wf), wf.Format.nSamplesPerSec); - } - else + wf.Format.nChannels, wave_format_name(&wf), wf.Format.nSamplesPerSec, latency); + + if (!wasapi_select_device_format(&wf, client, AUDCLNT_SHAREMODE_EXCLUSIVE, channels)) { - RARCH_ERR("[WASAPI]: Failed to select a suitable device format\n"); + RARCH_ERR("[WASAPI]: Failed to select a suitable device format.\n"); goto error; } hr = _IAudioClient_Initialize(client, AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, buffer_duration, buffer_duration, (WAVEFORMATEX*)&wf, NULL); + if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { - RARCH_WARN("[WASAPI]: Unaligned buffer size: %s\n", hresult_name(hr)); + RARCH_WARN("[WASAPI]: Unaligned buffer size: %s.\n", hresult_name(hr)); hr = _IAudioClient_GetBufferSize(client, &buffer_length); if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: Failed to get buffer size of client: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: Failed to get buffer size of client: %s.\n", hresult_name(hr)); goto error; } IFACE_RELEASE(client); - hr = _IMMDevice_Activate(device, + hr = _IMMDevice_Activate(device, IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&client); if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: IMMDevice::Activate failed: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: IMMDevice::Activate failed: %s.\n", hresult_name(hr)); return NULL; } @@ -384,12 +376,12 @@ static IAudioClient *wasapi_init_client_ex(IMMDevice *device, if (hr == AUDCLNT_E_ALREADY_INITIALIZED) { IFACE_RELEASE(client); - hr = _IMMDevice_Activate(device, + hr = _IMMDevice_Activate(device, IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&client); if (FAILED(hr)) { - RARCH_ERR("[WASAPI] IMMDevice::Activate failed: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI] IMMDevice::Activate failed: %s.\n", hresult_name(hr)); return NULL; } @@ -408,16 +400,13 @@ static IAudioClient *wasapi_init_client_ex(IMMDevice *device, if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: Failed to create exclusive-mode client: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: IAudioClient::Initialize failed: %s.\n", hresult_name(hr)); goto error; } *float_fmt = wf.Format.wFormatTag != WAVE_FORMAT_PCM; *rate = wf.Format.nSamplesPerSec; - RARCH_DBG("[WASAPI]: Initialized exclusive %s client at %uHz, latency %ums\n", - *float_fmt ? "float" : "pcm", *rate, latency); - return client; error: @@ -430,27 +419,43 @@ static IAudioClient *wasapi_init_client_sh(IMMDevice *device, bool *float_fmt, unsigned *rate, unsigned latency, unsigned channels) { WAVEFORMATEXTENSIBLE wf; - IAudioClient *client = NULL; - bool float_fmt_res = *float_fmt; - unsigned rate_res = *rate; - HRESULT hr = _IMMDevice_Activate(device, - IID_IAudioClient, - CLSCTX_ALL, NULL, (void**)&client); + IAudioClient *client = NULL; + settings_t *settings = config_get_ptr(); + unsigned sh_buffer_length = settings->uints.audio_wasapi_sh_buffer_length; + REFERENCE_TIME default_period = 0; + REFERENCE_TIME buffer_duration = 0; + HRESULT hr = _IMMDevice_Activate(device, + IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&client); if (FAILED(hr)) - { /* If we couldn't create the IAudioClient... */ - RARCH_ERR("[WASAPI]: Failed to create %s IAudioClient: %s\n", hresult_name(hr)); + { + RARCH_ERR("[WASAPI]: IMMDevice::Activate failed: %s.\n", hresult_name(hr)); return NULL; } - wasapi_set_format(&wf, float_fmt_res, rate_res, channels); - - if (wasapi_select_device_format(&wf, client, AUDCLNT_SHAREMODE_SHARED, channels)) + hr = _IAudioClient_GetDevicePeriod(client, &default_period, NULL); + if (FAILED(hr)) { - RARCH_DBG("[WASAPI]: Requesting %u-channel shared-mode client with %s samples at %uHz.\n", - wf.Format.nChannels, wave_format_name(&wf), wf.Format.nSamplesPerSec); + RARCH_ERR("[WASAPI]: Failed to get default device period of shared client: %s.\n", hresult_name(hr)); + goto error; } - else + + /* Use audio latency setting for buffer size if allowed */ + if ( sh_buffer_length < WASAPI_SH_BUFFER_DEVICE_PERIOD + || sh_buffer_length > WASAPI_SH_BUFFER_CLIENT_BUFFER) + { + /* Buffer_duration is in 100ns units. */ + buffer_duration = latency * 10000.0; + if (buffer_duration < default_period) + buffer_duration = default_period; + } + + wasapi_set_format(&wf, *float_fmt, *rate, channels); + RARCH_DBG("[WASAPI]: Requesting shared %u-bit %u-channel client with %s samples at %uHz %ums.\n", + wf.Format.wBitsPerSample, + wf.Format.nChannels, wave_format_name(&wf), wf.Format.nSamplesPerSec, latency); + + if (!wasapi_select_device_format(&wf, client, AUDCLNT_SHAREMODE_SHARED, channels)) { RARCH_ERR("[WASAPI]: Failed to select a suitable device format.\n"); goto error; @@ -458,37 +463,34 @@ static IAudioClient *wasapi_init_client_sh(IMMDevice *device, hr = _IAudioClient_Initialize(client, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, 0, (WAVEFORMATEX*)&wf, NULL); + buffer_duration, 0, (WAVEFORMATEX*)&wf, NULL); if (hr == AUDCLNT_E_ALREADY_INITIALIZED) { IFACE_RELEASE(client); - hr = _IMMDevice_Activate(device, + hr = _IMMDevice_Activate(device, IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&client); if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: IMMDevice::Activate failed: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: IMMDevice::Activate failed: %s.\n", hresult_name(hr)); return NULL; } hr = _IAudioClient_Initialize(client, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, 0, (WAVEFORMATEX*)&wf, NULL); + buffer_duration, 0, (WAVEFORMATEX*)&wf, NULL); } if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: IAudioClient::Initialize failed: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: IAudioClient::Initialize failed: %s.\n", hresult_name(hr)); goto error; } *float_fmt = wf.Format.wFormatTag != WAVE_FORMAT_PCM; *rate = wf.Format.nSamplesPerSec; - RARCH_DBG("[WASAPI]: Initialized shared %s client at %uHz.\n", - wave_format_name(&wf), *rate); - return client; error: @@ -516,16 +518,17 @@ IMMDevice *wasapi_init_device(const char *id, EDataFlow data_flow) IID_IMMDeviceEnumerator, (void **)&enumerator); #else hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, - &IID_IMMDeviceEnumerator, (void **)&enumerator); + &IID_IMMDeviceEnumerator, (void **)&enumerator); #endif if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: Failed to create device enumerator: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: Failed to create device enumerator: %s.\n", hresult_name(hr)); goto error; } if (id) - { /* If a specific device was requested... */ + { + /* If a specific device was requested... */ int32_t idx_found = -1; struct string_list *list = (struct string_list*)mmdevice_list_new(NULL, data_flow); @@ -536,20 +539,21 @@ IMMDevice *wasapi_init_device(const char *id, EDataFlow data_flow) } if (list->elems) - { /* If any devices were found... */ + { + /* If any devices were found... */ unsigned d; for (d = 0; d < list->size; d++) { if (string_is_equal(id, list->elems[d].data)) { - RARCH_DBG("[WASAPI]: Found device #%d: \"%s\"\n", d, list->elems[d].data); + RARCH_DBG("[WASAPI]: Found device #%d: \"%s\".\n", d, list->elems[d].data); idx_found = d; break; } } + /* Index was not found yet based on name string, * just assume id is a one-character number index. */ - if (idx_found == -1 && isdigit(id[0])) { idx_found = strtoul(id, NULL, 0); @@ -565,14 +569,14 @@ IMMDevice *wasapi_init_device(const char *id, EDataFlow data_flow) data_flow, DEVICE_STATE_ACTIVE, &collection); if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: Failed to enumerate audio endpoints: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: Failed to enumerate audio endpoints: %s.\n", hresult_name(hr)); goto error; } hr = _IMMDeviceCollection_GetCount(collection, &dev_count); if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: Failed to count IMMDevices: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: Failed to count IMMDevices: %s.\n", hresult_name(hr)); goto error; } @@ -581,7 +585,7 @@ IMMDevice *wasapi_init_device(const char *id, EDataFlow data_flow) hr = _IMMDeviceCollection_Item(collection, i, &device); if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: Failed to get IMMDevice #%d: %s\n", i, hresult_name(hr)); + RARCH_ERR("[WASAPI]: Failed to get IMMDevice #%d: %s.\n", i, hresult_name(hr)); goto error; } @@ -597,7 +601,7 @@ IMMDevice *wasapi_init_device(const char *id, EDataFlow data_flow) enumerator, data_flow, eConsole, &device); if (FAILED(hr)) { - RARCH_ERR("[WASAPI]: Failed to get default audio endpoint: %s\n", hresult_name(hr)); + RARCH_ERR("[WASAPI]: Failed to get default audio endpoint: %s.\n", hresult_name(hr)); goto error; } } @@ -615,13 +619,9 @@ error: IFACE_RELEASE(enumerator); if (id) - { RARCH_WARN("[WASAPI]: Failed to initialize %s device \"%s\".\n", data_flow_name, id); - } else - { RARCH_ERR("[WASAPI]: Failed to initialize default %s device.\n", data_flow_name); - } return NULL; } @@ -631,14 +631,11 @@ IAudioClient *wasapi_init_client(IMMDevice *device, bool *exclusive, { HRESULT hr; IAudioClient *client; - double latency_res; - REFERENCE_TIME device_period = 0; - REFERENCE_TIME stream_latency = 0; - UINT32 buffer_length = 0; - - RARCH_DBG("[WASAPI]: Requesting %s %s client (rate=%uHz, latency=%ums).\n", - *exclusive ? "exclusive" : "shared", - *float_fmt ? "float" : "pcm", *rate, latency); + float latency_res; + REFERENCE_TIME device_period = 0; + REFERENCE_TIME device_period_min = 0; + REFERENCE_TIME stream_latency = 0; + UINT32 buffer_length = 0; if (*exclusive) { @@ -666,45 +663,24 @@ IAudioClient *wasapi_init_client(IMMDevice *device, bool *exclusive, if (!client) return NULL; - /* next calls are allowed to fail (we losing info only) */ + /* Remaining calls are for logging purposes. */ - if (*exclusive) + hr = _IAudioClient_GetDevicePeriod(client, &device_period, &device_period_min); + if (SUCCEEDED(hr)) { - hr = _IAudioClient_GetDevicePeriod(client, NULL, &device_period); - if (SUCCEEDED(hr)) - { - RARCH_DBG("[WASAPI]: Minimum exclusive-mode device period is %uns (%.1fms).\n", - device_period * 100, (double)device_period * 100 / 1e6); - } - /* device_period is in 100ns units */ + RARCH_DBG("[WASAPI]: Default device period is %.1fms.\n", (float)device_period * 100 / 1e6); + RARCH_DBG("[WASAPI]: Minimum device period is %.1fms.\n", (float)device_period_min * 100 / 1e6); } else - { - hr = _IAudioClient_GetDevicePeriod(client, &device_period, NULL); - if (SUCCEEDED(hr)) - { - RARCH_DBG("[WASAPI]: Default shared-mode device period is %uns (%.1fms).\n", - device_period * 100, (double)device_period * 100 / 1e6); - } - } - - if (FAILED(hr)) - { - RARCH_WARN("[WASAPI]: IAudioClient::GetDevicePeriod failed: %s\n", hresult_name(hr)); - } + RARCH_WARN("[WASAPI]: IAudioClient::GetDevicePeriod failed: %s.\n", hresult_name(hr)); if (!*exclusive) { hr = _IAudioClient_GetStreamLatency(client, &stream_latency); if (SUCCEEDED(hr)) - { - RARCH_DBG("[WASAPI]: Shared stream latency is %uns (%.1fms).\n", - stream_latency * 100, (double)stream_latency * 100 / 1e6); - } + RARCH_DBG("[WASAPI]: Shared stream latency is %.1fms.\n", (float)stream_latency * 100 / 1e6); else - { - RARCH_WARN("[WASAPI]: IAudioClient::GetStreamLatency failed: %s\n", hresult_name(hr)); - } + RARCH_WARN("[WASAPI]: IAudioClient::GetStreamLatency failed: %s.\n", hresult_name(hr)); } hr = _IAudioClient_GetBufferSize(client, &buffer_length); @@ -712,45 +688,38 @@ IAudioClient *wasapi_init_client(IMMDevice *device, bool *exclusive, { size_t num_samples = buffer_length * channels; size_t num_bytes = num_samples * (*float_fmt ? sizeof(float) : sizeof(int16_t)); - RARCH_DBG("[WASAPI]: Endpoint buffer size is %u frames (%u samples, %u bytes).\n", - buffer_length, num_samples, num_bytes); + RARCH_DBG("[WASAPI]: Endpoint buffer size is %u frames (%u samples, %u bytes, %.1f ms).\n", + buffer_length, num_samples, num_bytes, (float)buffer_length * 1000.0 / *rate); } else - { RARCH_WARN("[WASAPI]: IAudioClient::GetBufferSize failed: %s.\n", hresult_name(hr)); - } if (*exclusive) - latency_res = (double)buffer_length * 1000.0 / (*rate); + latency_res = (float)buffer_length * 1000.0 / (*rate); else { - settings_t *settings = config_get_ptr(); - int sh_buffer_length = settings->ints.audio_wasapi_sh_buffer_length; + settings_t *settings = config_get_ptr(); + unsigned sh_buffer_length = settings->uints.audio_wasapi_sh_buffer_length; - if (sh_buffer_length < 0) + switch (sh_buffer_length) { -#ifdef USE_CLIENT_BUFFER - latency_res = (double)buffer_length * 1000.0 / (*rate); -#else - latency_res = (double)(stream_latency + device_period) / 10000.0; -#endif + case WASAPI_SH_BUFFER_AUDIO_LATENCY: + case WASAPI_SH_BUFFER_CLIENT_BUFFER: + latency_res = (float)buffer_length * 1000.0 / (*rate); + break; + case WASAPI_SH_BUFFER_DEVICE_PERIOD: + latency_res = (float)(stream_latency + device_period) / 10000.0; + break; + default: + latency_res = (float)sh_buffer_length * 1000.0 / (*rate); + break; } - else if (sh_buffer_length > 0) - latency_res = sh_buffer_length * 1000.0 / (*rate); - else - latency_res = 0; } RARCH_LOG("[WASAPI]: Client initialized (%s, %s, %uHz, %.1fms).\n", *exclusive ? "exclusive" : "shared", - *float_fmt ? "float" : "pcm", + *float_fmt ? "FLOAT" : "PCM", *rate, latency_res); - RARCH_LOG("[WASAPI]: Client buffer length is %u frames (%.1fms).\n", - buffer_length, (double)buffer_length * 1000.0 / (*rate)); - - RARCH_LOG("[WASAPI]: Device period is %lld frames (%.1fms).\n", - device_period * (*rate) / 10000000, (double)device_period / 10000.0); - return client; } diff --git a/audio/common/wasapi.h b/audio/common/wasapi.h index f019131d73..9393e2a01e 100644 --- a/audio/common/wasapi.h +++ b/audio/common/wasapi.h @@ -26,8 +26,10 @@ #include "../common/mmdevice_common_inline.h" #include "boolean.h" -/* Get automatic buffer size from client buffer instead of device period */ -#define USE_CLIENT_BUFFER +/* Shared buffer size replacement placeholders */ +#define WASAPI_SH_BUFFER_AUDIO_LATENCY 0 +#define WASAPI_SH_BUFFER_DEVICE_PERIOD 32 +#define WASAPI_SH_BUFFER_CLIENT_BUFFER 64 const char *hresult_name(HRESULT hr); const char* wasapi_error(DWORD error); diff --git a/audio/drivers/wasapi.c b/audio/drivers/wasapi.c index 3e26d9761c..c79a524d6b 100644 --- a/audio/drivers/wasapi.c +++ b/audio/drivers/wasapi.c @@ -33,9 +33,9 @@ typedef struct IMMDevice *device; IAudioClient *client; IAudioRenderClient *renderer; - fifo_buffer_t *buffer; /* NULL in unbuffered shared mode */ - size_t frame_size; /* 4 or 8 only */ + fifo_buffer_t *buffer; size_t engine_buffer_size; + unsigned char frame_size; /* 4 or 8 only */ bool exclusive; bool nonblock; bool running; @@ -51,7 +51,7 @@ static void *wasapi_init(const char *dev_id, unsigned rate, unsigned latency, settings_t *settings = config_get_ptr(); bool float_format = settings->bools.audio_wasapi_float_format; bool exclusive_mode = settings->bools.audio_wasapi_exclusive_mode; - int sh_buffer_length = settings->ints.audio_wasapi_sh_buffer_length; + unsigned sh_buffer_length = settings->uints.audio_wasapi_sh_buffer_length; wasapi_t *w = (wasapi_t*)calloc(1, sizeof(wasapi_t)); if (!w) @@ -73,43 +73,39 @@ static void *wasapi_init(const char *dev_id, unsigned rate, unsigned latency, if (FAILED(hr)) goto error; - w->frame_size = float_format ? 8 : 4; - w->engine_buffer_size = frame_count * w->frame_size; + w->frame_size = float_format ? 8 : 4; + w->engine_buffer_size = frame_count * w->frame_size; if (w->exclusive) { w->buffer = fifo_new(w->engine_buffer_size); if (!w->buffer) goto error; - - RARCH_LOG("[WASAPI]: Intermediate buffer length is %u frames (%.1fms).\n", - frame_count, (double)frame_count * 1000.0 / rate); } - else if (sh_buffer_length) + else { - if (sh_buffer_length < 0) + switch (sh_buffer_length) { -#ifdef USE_CLIENT_BUFFER - sh_buffer_length = frame_count; -#else - hr = _IAudioClient_GetDevicePeriod(w->client, &dev_period, NULL); + case WASAPI_SH_BUFFER_AUDIO_LATENCY: + case WASAPI_SH_BUFFER_CLIENT_BUFFER: + sh_buffer_length = frame_count; + break; + case WASAPI_SH_BUFFER_DEVICE_PERIOD: + hr = _IAudioClient_GetDevicePeriod(w->client, &dev_period, NULL); - if (FAILED(hr)) - goto error; + if (FAILED(hr)) + goto error; - sh_buffer_length = dev_period * rate / 10000000; -#endif + sh_buffer_length = dev_period * rate / 10000000; + break; + default: + break; } w->buffer = fifo_new(sh_buffer_length * w->frame_size); if (!w->buffer) goto error; - - RARCH_LOG("[WASAPI]: Intermediate buffer length is %u frames (%.1fms).\n", - sh_buffer_length, (double)sh_buffer_length * 1000.0 / rate); } - else - RARCH_LOG("[WASAPI]: Intermediate buffer is off. \n"); w->write_event = CreateEventA(NULL, FALSE, FALSE, NULL); if (!w->write_event) @@ -203,7 +199,7 @@ static ssize_t wasapi_write_sh_buffer(wasapi_t *w, const void * data, size_t siz if (!write_avail) { - size_t read_avail = 0; + size_t read_avail = 0; if (!(WaitForSingleObject(w->write_event, INFINITE) == WAIT_OBJECT_0)) return -1; @@ -252,9 +248,9 @@ static ssize_t wasapi_write_sh(wasapi_t *w, const void * data, size_t size) static ssize_t wasapi_write_sh_nonblock(wasapi_t *w, const void * data, size_t size) { - ssize_t written = -1; - size_t write_avail = 0; - UINT32 padding = 0; + ssize_t written = -1; + size_t write_avail = 0; + UINT32 padding = 0; if (w->buffer) { @@ -320,6 +316,7 @@ static ssize_t wasapi_write_ex(wasapi_t *w, const void * data, size_t size, DWOR static ssize_t wasapi_write(void *wh, const void *data, size_t size) { size_t written = 0; + ssize_t ir = 0; wasapi_t *w = (wasapi_t*)wh; if (w->nonblock) @@ -331,7 +328,6 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size) if (w->exclusive) { - ssize_t ir; for (ir = -1; written < size; written += ir) { ir = wasapi_write_ex(w, (char*)data + written, size - written, INFINITE); @@ -341,7 +337,6 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size) } else { - ssize_t ir; if (w->buffer) { for (ir = -1; written < size; written += ir) @@ -426,7 +421,6 @@ static void wasapi_free(void *wh) if (ir == WAIT_FAILED) RARCH_ERR("[WASAPI]: WaitForSingleObject failed with error %d.\n", GetLastError()); - /* If event isn't signaled log and leak */ if (!(ir == WAIT_OBJECT_0)) return; diff --git a/config.def.h b/config.def.h index c54e2939e2..0b630cdaab 100644 --- a/config.def.h +++ b/config.def.h @@ -1147,7 +1147,7 @@ #define DEFAULT_WASAPI_EXCLUSIVE_MODE false #define DEFAULT_WASAPI_FLOAT_FORMAT false /* Automatic shared mode buffer */ -#define DEFAULT_WASAPI_SH_BUFFER_LENGTH -16 +#define DEFAULT_WASAPI_SH_BUFFER_LENGTH 0 #endif /* Automatically mute audio when fast forward diff --git a/configuration.c b/configuration.c index 50304482f7..ef8fc04fef 100644 --- a/configuration.c +++ b/configuration.c @@ -1783,7 +1783,6 @@ static struct config_bool_setting *populate_settings_bool( #ifdef HAVE_MICROPHONE SETTING_BOOL("microphone_enable", &settings->bools.microphone_enable, true, DEFAULT_MICROPHONE_ENABLE, false); - #ifdef HAVE_WASAPI SETTING_BOOL("microphone_wasapi_exclusive_mode", &settings->bools.microphone_wasapi_exclusive_mode, true, DEFAULT_WASAPI_EXCLUSIVE_MODE, false); SETTING_BOOL("microphone_wasapi_float_format", &settings->bools.microphone_wasapi_float_format, true, DEFAULT_WASAPI_FLOAT_FORMAT, false); @@ -2335,6 +2334,11 @@ static struct config_uint_setting *populate_settings_uint( SETTING_UINT("audio_resampler_quality", &settings->uints.audio_resampler_quality, true, DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL, false); SETTING_UINT("audio_block_frames", &settings->uints.audio_block_frames, true, 0, false); SETTING_UINT("midi_volume", &settings->uints.midi_volume, true, DEFAULT_MIDI_VOLUME, false); + +#ifdef HAVE_WASAPI + SETTING_UINT("audio_wasapi_sh_buffer_length", &settings->uints.audio_wasapi_sh_buffer_length, true, DEFAULT_WASAPI_SH_BUFFER_LENGTH, false); +#endif + #ifdef HAVE_MICROPHONE SETTING_UINT("microphone_latency", &settings->uints.microphone_latency, false, 0 /* TODO */, false); SETTING_UINT("microphone_resampler_quality", &settings->uints.microphone_resampler_quality, true, DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL, false); @@ -2549,9 +2553,6 @@ static struct config_int_setting *populate_settings_int( SETTING_INT("menu_xmb_title_margin_horizontal_offset", &settings->ints.menu_xmb_title_margin_horizontal_offset, true, DEFAULT_XMB_TITLE_MARGIN_HORIZONTAL_OFFSET, false); #endif -#ifdef HAVE_WASAPI - SETTING_INT("audio_wasapi_sh_buffer_length", &settings->ints.audio_wasapi_sh_buffer_length, true, DEFAULT_WASAPI_SH_BUFFER_LENGTH, false); -#endif SETTING_INT("crt_switch_center_adjust", &settings->ints.crt_switch_center_adjust, false, DEFAULT_CRT_SWITCH_CENTER_ADJUST, false); SETTING_INT("crt_switch_porch_adjust", &settings->ints.crt_switch_porch_adjust, false, DEFAULT_CRT_SWITCH_PORCH_ADJUST, false); #ifdef HAVE_WINDOW_OFFSET @@ -3770,6 +3771,16 @@ static bool config_load_file(global_t *global, audio_set_float(AUDIO_ACTION_MIXER_VOLUME_GAIN, settings->floats.audio_mixer_volume); #endif +#ifdef HAVE_WASAPI + { + /* Migrate from old deprecated negative value */ + int wasapi_sh_buffer_length = settings->uints.audio_wasapi_sh_buffer_length; + + if (wasapi_sh_buffer_length < 0) + settings->uints.audio_wasapi_sh_buffer_length = 0; + } +#endif + /* MIDI fallback for old OFF-string */ if (string_is_equal(settings->arrays.midi_input, "Off")) configuration_set_string(settings, diff --git a/configuration.h b/configuration.h index 3478a4cccf..2a2a6929e6 100644 --- a/configuration.h +++ b/configuration.h @@ -106,7 +106,6 @@ typedef struct settings int location_update_interval_distance; int state_slot; int replay_slot; - int audio_wasapi_sh_buffer_length; int crt_switch_center_adjust; int crt_switch_porch_adjust; #ifdef HAVE_VULKAN @@ -161,12 +160,18 @@ typedef struct settings unsigned audio_block_frames; unsigned audio_latency; +#ifdef HAVE_WASAPI + unsigned audio_wasapi_sh_buffer_length; +#endif + #ifdef HAVE_MICROPHONE unsigned microphone_sample_rate; unsigned microphone_block_frames; unsigned microphone_latency; - unsigned microphone_wasapi_sh_buffer_length; unsigned microphone_resampler_quality; +#ifdef HAVE_WASAPI + unsigned microphone_wasapi_sh_buffer_length; +#endif #endif unsigned fps_update_interval; @@ -623,11 +628,14 @@ typedef struct settings bool audio_enable_menu_scroll; bool audio_sync; bool audio_rate_control; - bool audio_wasapi_exclusive_mode; - bool audio_wasapi_float_format; bool audio_fastforward_mute; bool audio_fastforward_speedup; +#ifdef HAVE_WASAPI + bool audio_wasapi_exclusive_mode; + bool audio_wasapi_float_format; +#endif + #ifdef HAVE_MICROPHONE /* Microphone */ bool microphone_enable; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index a34578c047..c96c779fc7 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -7184,6 +7184,7 @@ unsigned menu_displaylist_build_list( MENU_ENUM_LABEL_AUDIO_BLOCK_FRAMES, PARSE_ONLY_UINT, false) == 0) count++; +#ifdef HAVE_WASAPI if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_AUDIO_WASAPI_EXCLUSIVE_MODE, PARSE_ONLY_BOOL, false) == 0) @@ -7194,8 +7195,9 @@ unsigned menu_displaylist_build_list( count++; if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_AUDIO_WASAPI_SH_BUFFER_LENGTH, - PARSE_ONLY_INT, false) == 0) + PARSE_ONLY_UINT, false) == 0) count++; +#endif } break; #ifdef HAVE_MICROPHONE @@ -7235,6 +7237,7 @@ unsigned menu_displaylist_build_list( MENU_ENUM_LABEL_MICROPHONE_BLOCK_FRAMES, PARSE_ONLY_UINT, false) == 0) count++; +#ifdef HAVE_WASAPI if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_MICROPHONE_WASAPI_EXCLUSIVE_MODE, PARSE_ONLY_BOOL, false) == 0) @@ -7247,6 +7250,7 @@ unsigned menu_displaylist_build_list( MENU_ENUM_LABEL_MICROPHONE_WASAPI_SH_BUFFER_LENGTH, PARSE_ONLY_UINT, false) == 0) count++; +#endif break; #endif case DISPLAYLIST_AUDIO_SYNCHRONIZATION_SETTINGS_LIST: diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 2d2017aa20..15ba96c0e9 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -51,6 +51,10 @@ #include #endif +#ifdef HAVE_WASAPI +#include "../audio/common/wasapi.h" +#endif + #include "../config.def.h" #include "../config.def.keybinds.h" @@ -4941,29 +4945,52 @@ static void setting_get_string_representation_uint_custom_viewport_height(rarch_ } #ifdef HAVE_WASAPI -static void setting_get_string_representation_int_audio_wasapi_sh_buffer_length(rarch_setting_t *setting, +static void setting_get_string_representation_uint_audio_wasapi_sh_buffer_length(rarch_setting_t *setting, char *s, size_t len) { - if (!setting) + settings_t *settings = config_get_ptr(); + + if (!setting || !settings) return; - if (*setting->value.target.integer > 0) - snprintf(s, len, "%d", *setting->value.target.integer); - else if (*setting->value.target.integer == 0) - strlcpy(s, "0 (Off)", len); - else - strlcpy(s, "Auto", len); + switch (*setting->value.target.integer) + { + case WASAPI_SH_BUFFER_AUDIO_LATENCY: + snprintf(s, len, "%u (%s)", + *setting->value.target.integer, + "Audio Latency"); + break; + case WASAPI_SH_BUFFER_DEVICE_PERIOD: + snprintf(s, len, "%u (%s)", + *setting->value.target.integer, + "Device Period"); + break; + case WASAPI_SH_BUFFER_CLIENT_BUFFER: + snprintf(s, len, "%u (%s)", + *setting->value.target.integer, + "Client Buffer"); + break; + default: + snprintf(s, len, "%u (%.1f ms)", + *setting->value.target.integer, + (float)*setting->value.target.integer * 1000 / settings->uints.audio_output_sample_rate); + break; + } } #ifdef HAVE_MICROPHONE static void setting_get_string_representation_uint_microphone_wasapi_sh_buffer_length(rarch_setting_t *setting, char *s, size_t len) { - if (!setting) + settings_t *settings = config_get_ptr(); + + if (!setting || !settings) return; - if (*setting->value.target.unsigned_integer > 0) - snprintf(s, len, "%u", *setting->value.target.unsigned_integer); + if (*setting->value.target.integer > 0) + snprintf(s, len, "%u (%.1f ms)", + *setting->value.target.integer, + (float)*setting->value.target.integer * 1000 / settings->uints.audio_output_sample_rate); else strlcpy(s, "Auto", len); } @@ -8315,9 +8342,11 @@ static void general_write_handler(rarch_setting_t *setting) break; case MENU_ENUM_LABEL_AUDIO_LATENCY: case MENU_ENUM_LABEL_AUDIO_OUTPUT_RATE: +#ifdef HAVE_WASAPI case MENU_ENUM_LABEL_AUDIO_WASAPI_EXCLUSIVE_MODE: case MENU_ENUM_LABEL_AUDIO_WASAPI_FLOAT_FORMAT: case MENU_ENUM_LABEL_AUDIO_WASAPI_SH_BUFFER_LENGTH: +#endif rarch_cmd = CMD_EVENT_AUDIO_REINIT; break; #ifdef HAVE_MICROPHONE @@ -14107,9 +14136,9 @@ static bool setting_append_list( SD_FLAG_NONE ); - CONFIG_INT( + CONFIG_UINT( list, list_info, - &settings->ints.audio_wasapi_sh_buffer_length, + &settings->uints.audio_wasapi_sh_buffer_length, MENU_ENUM_LABEL_AUDIO_WASAPI_SH_BUFFER_LENGTH, MENU_ENUM_LABEL_VALUE_AUDIO_WASAPI_SH_BUFFER_LENGTH, DEFAULT_WASAPI_SH_BUFFER_LENGTH, @@ -14118,11 +14147,12 @@ static bool setting_append_list( parent_group, general_write_handler, general_read_handler); - menu_settings_list_current_add_range(list, list_info, -16.0f, 0.0f, 16.0f, true, false); - SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ADVANCED); + (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint_special; (*list)[list_info->index - 1].get_string_representation = - &setting_get_string_representation_int_audio_wasapi_sh_buffer_length; - } + &setting_get_string_representation_uint_audio_wasapi_sh_buffer_length; + menu_settings_list_current_add_range(list, list_info, 0, 32.0f * 200, 32.0f, true, true); + SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ADVANCED); + } #endif END_SUB_GROUP(list, list_info, parent_group); @@ -14302,11 +14332,12 @@ static bool setting_append_list( parent_group, general_write_handler, general_read_handler); - menu_settings_list_current_add_range(list, list_info, 0.0f, 0.0f, 16.0f, true, false); - SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ADVANCED); + (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint_special; (*list)[list_info->index - 1].get_string_representation = &setting_get_string_representation_uint_microphone_wasapi_sh_buffer_length; - } + menu_settings_list_current_add_range(list, list_info, 0, 32.0f * 200, 32.0f, true, true); + SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ADVANCED); + } #endif END_SUB_GROUP(list, list_info, parent_group); diff --git a/runloop.c b/runloop.c index 25022c2631..2a506d1763 100644 --- a/runloop.c +++ b/runloop.c @@ -6005,8 +6005,7 @@ static enum runloop_state_enum runloop_check_state( menu->state = 0; } - if (settings->bools.audio_enable_menu - && !libretro_running) + if (!libretro_running) audio_driver_menu_sample(); }