summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Mitchell <kevmitch@gmail.com>2016-02-19 11:04:29 -0800
committerKevin Mitchell <kevmitch@gmail.com>2016-02-26 15:43:51 -0800
commit31539884c829a0d2aec63d85647110282234a27a (patch)
tree2e84b0ffbb57d8dabdab134868471f6a65da8dae
parent5e124a4ac306292be698f8e1ebf7ce3dd15b02ba (diff)
downloadmpv-31539884c829a0d2aec63d85647110282234a27a.tar.bz2
mpv-31539884c829a0d2aec63d85647110282234a27a.tar.xz
ao_wasapi: avoid under-run cascade in exclusive mode.
Don't wait for WASAPI to send another feed event if we detect an underfull buffer. It seems that WASAPI doesn't always send extra feed events if something causes rendering to fall behind. This causes every subsequent playback buffer to under-run until playback is reset. The fix is simply to do a one-shot double feed when this happens, which allows rendering to catch up with playback. This was observed to happen when using MsgWaitForMultipleObjects to wait for the feed event and toggling fullscreen with vo=opengl:backend=win. This commit improves the behaviour in that specific case and more generally makes exclusive mode significantly more robust. This commit also moves the logic to avoid *over*filling the exclusive mode buffer into thread_feed right next to the above described underfil logic.
-rw-r--r--audio/out/ao_wasapi.c60
1 files changed, 36 insertions, 24 deletions
diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c
index 4615baa232..509a4d3e48 100644
--- a/audio/out/ao_wasapi.c
+++ b/audio/out/ao_wasapi.c
@@ -69,7 +69,11 @@ static HRESULT get_device_delay(struct wasapi_state *state, double *delay_us) {
"Ignoring it.\n", qpc_diff / 10000000.0);
}
- MP_TRACE(state, "Device delay: %g us\n", *delay_us);
+ if (sample_count > 0 && *delay_us <= 0) {
+ MP_WARN(state, "Under-run: Device delay: %g us\n", *delay_us);
+ } else {
+ MP_TRACE(state, "Device delay: %g us\n", *delay_us);
+ }
return S_OK;
exit_label:
@@ -77,22 +81,39 @@ exit_label:
return hr;
}
-static void thread_feed(struct ao *ao)
+static bool thread_feed(struct ao *ao)
{
struct wasapi_state *state = ao->priv;
HRESULT hr;
UINT32 frame_count = state->bufferFrameCount;
-
+ UINT32 padding;
+ hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding);
+ EXIT_ON_ERROR(hr);
+ bool refill = false;
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) {
- UINT32 padding = 0;
- hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding);
- EXIT_ON_ERROR(hr);
-
+ // Return if there's nothing to do.
+ if (frame_count <= padding)
+ return false;
+ // In shared mode, there is only one buffer of size bufferFrameCount.
+ // We must therefore take care not to overwrite the samples that have
+ // yet to play.
frame_count -= padding;
- MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n",
- frame_count, padding);
+ } else if (padding >= 2 * frame_count) {
+ // In exclusive mode, we exchange entire buffers of size
+ // bufferFrameCount with the device. If there are already two such
+ // full buffers waiting to play, there is no work to do.
+ return false;
+ } else if (padding < frame_count) {
+ // If there is not at least one full buffer of audio queued to play in
+ // exclusive mode, call this function again immediately to try and catch
+ // up and avoid a cascade of under-runs. WASAPI doesn't seem to be smart
+ // enough to send more feed events when it gets behind.
+ refill = true;
}
+ MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n",
+ frame_count, padding);
+
double delay_us;
hr = get_device_delay(state, &delay_us);
EXIT_ON_ERROR(hr);
@@ -117,12 +138,12 @@ static void thread_feed(struct ao *ao)
atomic_fetch_add(&state->sample_count, frame_count);
- return;
+ return refill;
exit_label:
MP_ERR(state, "Error feeding audio: %s\n", mp_HRESULT_to_str(hr));
MP_VERBOSE(ao, "Requesting ao reload\n");
ao_request_reload(ao);
- return;
+ return false;
}
static void thread_resume(struct ao *ao)
@@ -131,18 +152,7 @@ static void thread_resume(struct ao *ao)
HRESULT hr;
MP_DBG(state, "Thread Resume\n");
- UINT32 padding = 0;
- hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding);
- if (FAILED(hr)) {
- MP_ERR(state, "IAudioClient_GetCurrentPadding returned %s\n",
- mp_HRESULT_to_str(hr));
- }
-
- // Fill the buffer before starting, but only if there is no audio queued to
- // play. This prevents overfilling the buffer, which leads to problems in
- // exclusive mode
- if (padding < (UINT32) state->bufferFrameCount)
- thread_feed(ao);
+ thread_feed(ao);
// start feeding next wakeup if something else hasn't been requested
int expected = WASAPI_THREAD_RESUME;
@@ -199,7 +209,9 @@ static DWORD __stdcall AudioThread(void *lpParameter)
case WAIT_OBJECT_0:
switch (atomic_load(&state->thread_state)) {
case WASAPI_THREAD_FEED:
- thread_feed(ao);
+ // fill twice on under-full buffer (see comment in thread_feed)
+ if (thread_feed(ao) && thread_feed(ao))
+ MP_ERR(ao, "Unable to fill buffer fast enough\n");
break;
case WASAPI_THREAD_RESET:
thread_reset(ao);