From 503a0f184ca5b12acb355f3b3cf96ca0a5cda200 Mon Sep 17 00:00:00 2001 From: sunpenghao Date: Mon, 1 Apr 2024 17:20:47 +0800 Subject: [PATCH] ao_wasapi: add `--wasapi-exclusive-buffer` option This allows users to set buffer duration in exclusive mode. We have been using the default device period as the buffer size and it is robust enough in most cases. However, on some devices there are horrible glitches after a stream reset. Unfortunately, the issue is not consistently reproducible, but using a smaller buffer size (e.g., the minimum device period) seems to resolve the problem. Fixes #13715. --- .../wasapi-exclusive-buffer.txt | 1 + DOCS/man/ao.rst | 15 ++++++++++ audio/out/ao_wasapi.c | 6 ++++ audio/out/ao_wasapi.h | 1 + audio/out/ao_wasapi_utils.c | 29 +++++++++++++------ 5 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 DOCS/interface-changes/wasapi-exclusive-buffer.txt diff --git a/DOCS/interface-changes/wasapi-exclusive-buffer.txt b/DOCS/interface-changes/wasapi-exclusive-buffer.txt new file mode 100644 index 0000000000..161f2271c7 --- /dev/null +++ b/DOCS/interface-changes/wasapi-exclusive-buffer.txt @@ -0,0 +1 @@ +add `--wasapi-exclusive-buffer` option diff --git a/DOCS/man/ao.rst b/DOCS/man/ao.rst index 4ac1cc52ee..78dfed3b51 100644 --- a/DOCS/man/ao.rst +++ b/DOCS/man/ao.rst @@ -297,3 +297,18 @@ Available audio output drivers are: ``wasapi`` Audio output to the Windows Audio Session API. + + The following global options are supported by this audio output: + + ``--wasapi-exclusive-buffer=`` + Set buffer duration in exclusive mode (i.e., with + ``--audio-exclusive=yes``). ``default`` and ``min`` use the default and + minimum device period reported by WASAPI, respectively. You can also + directly specify the buffer duration in microseconds, in which case a + duration shorter than the minimum device period will be rounded up to + the minimum period. + + The default buffer duration should provide robust playback in most + cases, but reportedly on some devices there are glitches following + stream resets under the default setting. In such cases, specifying a + shorter duration might help. diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index d986d80226..58d2acd09a 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -526,4 +526,10 @@ const struct ao_driver audio_out_wasapi = { .hotplug_init = hotplug_init, .hotplug_uninit = hotplug_uninit, .priv_size = sizeof(wasapi_state), + .options_prefix = "wasapi", + .options = (const struct m_option[]) { + {"exclusive-buffer", OPT_CHOICE(opt_exclusive_buffer, + {"default", 0}, {"min", -1}), M_RANGE(1, 2000000)}, + {0} + }, }; diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index 4e5e9c8a67..e294e317c3 100644 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -91,6 +91,7 @@ typedef struct wasapi_state { // ao options int opt_exclusive; + int opt_exclusive_buffer; // exclusive mode buffer duration in us // format info WAVEFORMATEXTENSIBLE format; diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 7e85f75eab..8ff6523303 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -640,16 +640,25 @@ static HRESULT fix_format(struct ao *ao, bool align_hack) struct wasapi_state *state = ao->priv; MP_DBG(state, "IAudioClient::GetDevicePeriod\n"); - REFERENCE_TIME devicePeriod; - HRESULT hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&devicePeriod, - NULL); - MP_VERBOSE(state, "Device period: %.2g ms\n", - (double) devicePeriod / 10000.0 ); + REFERENCE_TIME defaultPeriod, minPeriod; + HRESULT hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&defaultPeriod, + &minPeriod); + MP_VERBOSE(state, "Device period: default %lld us, minimum %lld us\n", + defaultPeriod / 10, minPeriod / 10); - REFERENCE_TIME bufferDuration = devicePeriod; + REFERENCE_TIME bufferDuration; if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) { // for shared mode, use integer multiple of device period close to 50ms - bufferDuration = devicePeriod * ceil(50.0 * 10000.0 / devicePeriod); + bufferDuration = defaultPeriod * ceil(50.0 * 10000.0 / defaultPeriod); + } else if (state->opt_exclusive_buffer == 0) { + bufferDuration = defaultPeriod; + } else { + if (state->opt_exclusive_buffer > 0 && !align_hack) { + MP_VERBOSE(state, "Requested buffer duration: %d us\n", + state->opt_exclusive_buffer); + } + bufferDuration = MPMAX((REFERENCE_TIME) state->opt_exclusive_buffer * 10, + minPeriod); } // handle unsupported buffer size if AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED was @@ -662,6 +671,8 @@ static HRESULT fix_format(struct ao *ao, bool align_hack) * state->bufferFrameCount)); } + MP_VERBOSE(state, "Trying buffer duration %lld us\n", bufferDuration / 10); + REFERENCE_TIME bufferPeriod = state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE ? bufferDuration : 0; @@ -694,8 +705,8 @@ static HRESULT fix_format(struct ao *ao, bool align_hack) bufferDuration = (REFERENCE_TIME) (0.5 + (10000.0 * 1000 / state->format.Format.nSamplesPerSec * state->bufferFrameCount)); - MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%.2g ms)\n", - state->bufferFrameCount, (double) bufferDuration / 10000.0 ); + MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%lld us)\n", + state->bufferFrameCount, bufferDuration / 10); hr = init_clock(state); EXIT_ON_ERROR(hr);