From 1d096f9f1bbe2d8da8860003457d1161deb873f6 Mon Sep 17 00:00:00 2001 From: "Diogo Franco (Kovensky)" Date: Thu, 6 Mar 2014 15:50:22 -0300 Subject: ao_wasapi: Add device latency to get_delay The lack of device latency made get_delay report latencies shorter than they should; on systems with fast enough drivers, the delay is not perceptible, but high enough invisible delays would cause desyncs. I'm not yet completely sure whether this is 100% accurate, there are some issues involved when repeatedly pausing+unpausing (the delay might jump around by several dozen miliseconds), but seeking seems to be working correctly now. --- audio/out/ao_wasapi.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 7729eb2bda..fdca69c20c 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -35,6 +35,7 @@ #include "common/msg.h" #include "misc/ring.h" #include "ao.h" +#include "compat/atomics.h" #ifndef PKEY_Device_FriendlyName DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, @@ -116,6 +117,11 @@ typedef struct wasapi_state { DWORD taskIndex; /* AV task ID */ WAVEFORMATEXTENSIBLE format; + /* WASAPI internal clock information, for estimating delay */ + IAudioClock *pAudioClock; + UINT64 clock_frequency; /* scale for the "samples" returned by the clock */ + UINT64 sample_count; /* the amount of samples per channel written to a GetBuffer buffer */ + int opt_exclusive; int opt_list; char *opt_device; @@ -512,6 +518,28 @@ static int find_formats(struct ao *const ao) } } +static int init_clock(struct wasapi_state *state) { + HRESULT hr; + + hr = IAudioClient_GetService(state->pAudioClient, + &IID_IAudioClock, + (void **)&state->pAudioClock); + EXIT_ON_ERROR(hr); + hr = IAudioClock_GetFrequency(state->pAudioClock, &state->clock_frequency); + EXIT_ON_ERROR(hr); + + state->sample_count = 0; + + MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n", (uint64_t) state->clock_frequency); + + return 0; +exit_label: + MP_ERR(state, "init_clock failed with %s, unable to obtain the audio device's timing!\n", + explain_err(hr)); + SetEvent(state->fatal_error); + return 1; +} + static int fix_format(struct wasapi_state *state) { HRESULT hr; @@ -564,6 +592,10 @@ reinit: state->buffer_block_size = state->format.Format.nChannels * state->format.Format.wBitsPerSample / 8 * state->bufferFrameCount; + + if (init_clock(state)) + return 1; + state->hTask = state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex); MP_VERBOSE(state, "fix_format OK, using %lld byte buffer block size!\n", @@ -958,6 +990,9 @@ static void thread_pause(wasapi_state *state) { state->is_playing = 0; IAudioClient_Stop(state->pAudioClient); + IAudioClient_Reset(state->pAudioClient); + state->sample_count = 0; + mp_memory_barrier(); } /* force_feed - feed in even if available data is smaller than required buffer, to clear the buffer */ @@ -997,11 +1032,19 @@ static void thread_feed(wasapi_state *state,int force_feed) frame_count, AUDCLNT_BUFFERFLAGS_SILENT); EXIT_ON_ERROR(hr); + + mp_atomic_add_and_fetch(&state->sample_count, frame_count); + mp_memory_barrier(); + return; } hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient, frame_count, 0); EXIT_ON_ERROR(hr); + + mp_atomic_add_and_fetch(&state->sample_count, frame_count); + mp_memory_barrier(); + return; exit_label: MP_ERR(state, "thread_feed fails with %"PRIx32"!\n", (uint32_t)hr); @@ -1018,9 +1061,10 @@ static void thread_play(wasapi_state *state) static void thread_reset(wasapi_state *state) { - IAudioClient_Stop(state->pAudioClient); - IAudioClient_Reset(state->pAudioClient); - if (state->is_playing) { + int playing = state->is_playing; + thread_pause(state); + + if (playing) { thread_play(state); } } @@ -1056,6 +1100,8 @@ static void thread_uninit(wasapi_state *state) IAudioClient_Stop(state->pAudioClient); if (state->pRenderClient) IAudioRenderClient_Release(state->pRenderClient); + if (state->pAudioClock) + IAudioClock_Release(state->pAudioClock); if (state->pAudioClient) IAudioClient_Release(state->pAudioClient); if (state->pDevice) @@ -1315,12 +1361,40 @@ static int play(struct ao *ao, void **data, int samples, int flags) return ret / ao->sstride; } +static float get_device_delay(struct wasapi_state *state) { + /* where we pray that this hasn't desynced */ + mp_memory_barrier(); + UINT64 sample_count = state->sample_count; + UINT64 position; + HRESULT hr; + + switch (hr = IAudioClock_GetPosition(state->pAudioClock, &position, NULL)) { + case S_OK: case S_FALSE: + break; + default: + MP_ERR(state, "IAudioClock::GetPosition returned %s\n", explain_err(hr)); + } + + /* convert position to the same base as sample_count */ + position = position * state->format.Format.nSamplesPerSec / state->clock_frequency; + + uint32_t diff = sample_count - position; + float delay = diff / (float)state->format.Format.nSamplesPerSec; + + MP_TRACE(state, "device delay: %"PRIu32" samples (%g ms)\n", diff, delay * 1000); + + return delay; +} + static float get_delay(struct ao *ao) { if (!ao || !ao->priv) return -1.0f; + struct wasapi_state *state = (struct wasapi_state *)ao->priv; - return (float)(RING_BUFFER_COUNT * state->buffer_block_size - get_space(ao) * ao->sstride) / + + return get_device_delay(state) + + (float)(RING_BUFFER_COUNT * state->buffer_block_size - get_space(ao) * ao->sstride) / (float)state->format.Format.nAvgBytesPerSec; } -- cgit v1.2.3 From fe03981bbc20694fd6a4a61461727e02bf9192a8 Mon Sep 17 00:00:00 2001 From: "Diogo Franco (Kovensky)" Date: Thu, 6 Mar 2014 16:28:11 -0300 Subject: ao_wasapi: Slightly improve timer accuracy Use QueryPerformanceCounter to improve the accuracy of IAudioClock::GetPosition. While this is mainly for "realtime correctness" (usually the delay is a single sample or less), there are cases where IAudioClock::GetPosition takes a long time to return from its call (though the documentation doesn't define what a "long time" is), so correcting its value might be important in case the documented possible delay happens. --- audio/out/ao_wasapi.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index fdca69c20c..39199159b3 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -121,6 +121,7 @@ typedef struct wasapi_state { IAudioClock *pAudioClock; UINT64 clock_frequency; /* scale for the "samples" returned by the clock */ UINT64 sample_count; /* the amount of samples per channel written to a GetBuffer buffer */ + LARGE_INTEGER qpc_frequency; /* frequency of windows' high resolution timer */ int opt_exclusive; int opt_list; @@ -528,6 +529,8 @@ static int init_clock(struct wasapi_state *state) { hr = IAudioClock_GetFrequency(state->pAudioClock, &state->clock_frequency); EXIT_ON_ERROR(hr); + QueryPerformanceFrequency(&state->qpc_frequency); + state->sample_count = 0; MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n", (uint64_t) state->clock_frequency); @@ -1362,19 +1365,25 @@ static int play(struct ao *ao, void **data, int samples, int flags) } static float get_device_delay(struct wasapi_state *state) { - /* where we pray that this hasn't desynced */ + /* where we pray that sample_count hasn't desynced */ mp_memory_barrier(); UINT64 sample_count = state->sample_count; - UINT64 position; + UINT64 position, qpc_position; HRESULT hr; - switch (hr = IAudioClock_GetPosition(state->pAudioClock, &position, NULL)) { + switch (hr = IAudioClock_GetPosition(state->pAudioClock, &position, &qpc_position)) { case S_OK: case S_FALSE: break; default: MP_ERR(state, "IAudioClock::GetPosition returned %s\n", explain_err(hr)); } + LARGE_INTEGER qpc_count; + QueryPerformanceCounter(&qpc_count); + UINT64 qpc_diff = (qpc_count.QuadPart * 10000000 / state->qpc_frequency.QuadPart) - qpc_position; + + position += state->clock_frequency * qpc_diff / 10000000; + /* convert position to the same base as sample_count */ position = position * state->format.Format.nSamplesPerSec / state->clock_frequency; -- cgit v1.2.3