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.
This commit is contained in:
sunpenghao 2024-04-01 17:20:47 +08:00 committed by Kacper Michajłow
parent e5d683e187
commit 503a0f184c
5 changed files with 43 additions and 9 deletions

View File

@ -0,0 +1 @@
add `--wasapi-exclusive-buffer` option

View File

@ -297,3 +297,18 @@ Available audio output drivers are:
``wasapi`` ``wasapi``
Audio output to the Windows Audio Session API. Audio output to the Windows Audio Session API.
The following global options are supported by this audio output:
``--wasapi-exclusive-buffer=<default|min|1-2000000>``
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.

View File

@ -526,4 +526,10 @@ const struct ao_driver audio_out_wasapi = {
.hotplug_init = hotplug_init, .hotplug_init = hotplug_init,
.hotplug_uninit = hotplug_uninit, .hotplug_uninit = hotplug_uninit,
.priv_size = sizeof(wasapi_state), .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}
},
}; };

View File

@ -91,6 +91,7 @@ typedef struct wasapi_state {
// ao options // ao options
int opt_exclusive; int opt_exclusive;
int opt_exclusive_buffer; // exclusive mode buffer duration in us
// format info // format info
WAVEFORMATEXTENSIBLE format; WAVEFORMATEXTENSIBLE format;

View File

@ -640,16 +640,25 @@ static HRESULT fix_format(struct ao *ao, bool align_hack)
struct wasapi_state *state = ao->priv; struct wasapi_state *state = ao->priv;
MP_DBG(state, "IAudioClient::GetDevicePeriod\n"); MP_DBG(state, "IAudioClient::GetDevicePeriod\n");
REFERENCE_TIME devicePeriod; REFERENCE_TIME defaultPeriod, minPeriod;
HRESULT hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&devicePeriod, HRESULT hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&defaultPeriod,
NULL); &minPeriod);
MP_VERBOSE(state, "Device period: %.2g ms\n", MP_VERBOSE(state, "Device period: default %lld us, minimum %lld us\n",
(double) devicePeriod / 10000.0 ); defaultPeriod / 10, minPeriod / 10);
REFERENCE_TIME bufferDuration = devicePeriod; REFERENCE_TIME bufferDuration;
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) { if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) {
// for shared mode, use integer multiple of device period close to 50ms // 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 // 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)); * state->bufferFrameCount));
} }
MP_VERBOSE(state, "Trying buffer duration %lld us\n", bufferDuration / 10);
REFERENCE_TIME bufferPeriod = REFERENCE_TIME bufferPeriod =
state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE ? bufferDuration : 0; 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 + bufferDuration = (REFERENCE_TIME) (0.5 +
(10000.0 * 1000 / state->format.Format.nSamplesPerSec (10000.0 * 1000 / state->format.Format.nSamplesPerSec
* state->bufferFrameCount)); * state->bufferFrameCount));
MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%.2g ms)\n", MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%lld us)\n",
state->bufferFrameCount, (double) bufferDuration / 10000.0 ); state->bufferFrameCount, bufferDuration / 10);
hr = init_clock(state); hr = init_clock(state);
EXIT_ON_ERROR(hr); EXIT_ON_ERROR(hr);