From 5afa68835ade9f21f9c709f791319bf9d2e35265 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Mon, 21 Dec 2015 09:45:32 -0800 Subject: ao_wasapi: fix delay calculation Make sure that subtraction of performance counters is done correctly. Follow the *exact* instructions for converting performance counter to something comparable to the QPCposition returned by IAudioClient::GetPosition https://msdn.microsoft.com/en-us/library/windows/desktop/dd370889%28v=vs.85%29.aspx Also make sure that subtraction of unsigned integers is stored into a signed integer to avoid nastiness. Also be more careful about overflow in the conversion of the device position into number of samples. Avoid casting mp_time_us() to a double, and use llrint to convert the double precision delay_us back to integer for ao_read_data. Finally, actually check the return value of ao_read_data and add a verbose message if it is not the expected value. Unfortunately, there is no way to tell WASAPI when this happens since the frame_count in ReleaseBuffer must match GetBuffer. --- audio/out/ao_wasapi.c | 57 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 21 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index bafeae1d63..8e21bb3260 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -33,7 +34,15 @@ #include "osdep/timer.h" #include "osdep/io.h" -static HRESULT get_device_delay(struct wasapi_state *state, double *delay) { + +static UINT64 uint64_scale(UINT64 x, UINT64 num, UINT64 den) +{ + return (x / den) * num + + ((x % den) * (num / den)) + + ((x % den) * (num % den)) / den; +} + +static HRESULT get_device_delay(struct wasapi_state *state, double *delay_us) { UINT64 sample_count = atomic_load(&state->sample_count); UINT64 position, qpc_position; HRESULT hr; @@ -48,21 +57,24 @@ static HRESULT get_device_delay(struct wasapi_state *state, double *delay) { } EXIT_ON_ERROR(hr); - LARGE_INTEGER qpc_count; - QueryPerformanceCounter(&qpc_count); - double qpc_diff = (qpc_count.QuadPart * 1e7 / state->qpc_frequency.QuadPart) - - qpc_position; - - position += state->clock_frequency * (uint64_t) (qpc_diff / 1e7); - - // convert position to the same base as sample_count - position = position * state->format.Format.nSamplesPerSec - / state->clock_frequency; - - double diff = sample_count - position; - *delay = diff / state->format.Format.nSamplesPerSec; - - MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, *delay * 1000); + // convert position to number of samples careful to avoid overflow + UINT64 sample_position = uint64_scale(position, + state->format.Format.nSamplesPerSec, + state->clock_frequency); + INT64 diff = sample_count - sample_position; + *delay_us = diff * 1e6 / state->format.Format.nSamplesPerSec; + + // Correct for any delay in IAudioClock_GetPosition above. + // This should normally be very small (<1 us), but just in case. . . + LARGE_INTEGER qpc; + QueryPerformanceCounter(&qpc); + // apparently, we're supposed to allow the qpc scale to overflow to be + // comparable to qpc_position (100ns units), so don't do anything fancy + INT64 qpc_diff = qpc.QuadPart * 10000000 / state->qpc_frequency.QuadPart + - qpc_position; + *delay_us -= qpc_diff / 10.0; // convert to us + + MP_TRACE(state, "Device delay: %g us\n", *delay_us); return S_OK; exit_label: @@ -86,9 +98,11 @@ static void thread_feed(struct ao *ao) MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n", frame_count, padding); } - double delay; - hr = get_device_delay(state, &delay); + double delay_us; + hr = get_device_delay(state, &delay_us); EXIT_ON_ERROR(hr); + // add the buffer delay + delay_us += frame_count * 1e6 / state->format.Format.nSamplesPerSec; BYTE *pData; hr = IAudioRenderClient_GetBuffer(state->pRenderClient, @@ -97,10 +111,11 @@ static void thread_feed(struct ao *ao) BYTE *data[1] = {pData}; - ao_read_data(ao, (void**)data, frame_count, (int64_t) ( - mp_time_us() + delay * 1e6 + - frame_count * 1e6 / state->format.Format.nSamplesPerSec)); + ao_read_data(ao, (void **)data, frame_count, + mp_time_us() + (int64_t)llrint(delay_us)); + // note, we can't use ao_read_data return value here since we already + // commited to frame_count above in the GetBuffer call hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient, frame_count, 0); EXIT_ON_ERROR(hr); -- cgit v1.2.3