From 2da60016affdbc13356aef162e0ff6d70b03d677 Mon Sep 17 00:00:00 2001 From: Andrew Eikum Date: Tue, 22 Nov 2011 09:50:48 -0600 Subject: [PATCH] winecoreaudio.drv: Make driver sample accurate. --- dlls/winecoreaudio.drv/mmdevdrv.c | 176 +++++++++++++++++++++++++++++++------- 1 file changed, 144 insertions(+), 32 deletions(-) diff --git a/dlls/winecoreaudio.drv/mmdevdrv.c b/dlls/winecoreaudio.drv/mmdevdrv.c index f0a38929cf4..ae85f37d92a 100644 --- a/dlls/winecoreaudio.drv/mmdevdrv.c +++ b/dlls/winecoreaudio.drv/mmdevdrv.c @@ -65,6 +65,13 @@ WINE_DEFAULT_DEBUG_CHANNEL(coreaudio); static const REFERENCE_TIME DefaultPeriod = 200000; static const REFERENCE_TIME MinimumPeriod = 100000; +typedef struct _QueuedBufInfo { + Float64 start_sampletime; + UINT64 start_pos; + UINT32 len_frames; + struct list entry; +} QueuedBufInfo; + typedef struct _AQBuffer { AudioQueueBufferRef buf; struct list entry; @@ -130,12 +137,15 @@ struct ACImpl { UINT32 getbuf_last; int playing; + Float64 highest_sampletime; + AudioSession *session; AudioSessionWrapper *session_wrapper; struct list entry; struct list avail_buffers; + struct list queued_bufinfos; /* We can't use debug printing or {Enter,Leave}CriticalSection from * OSX callback threads, so we use OSX's OSSpinLock for synchronization @@ -182,7 +192,7 @@ static CRITICAL_SECTION g_sessions_lock = { &g_sessions_lock_debug, -1, 0, 0, 0, static struct list g_sessions = LIST_INIT(g_sessions); static HRESULT AudioClock_GetPosition_nolock(ACImpl *This, UINT64 *pos, - UINT64 *qpctime, BOOL raw); + UINT64 *qpctime); static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client); static HRESULT ca_setvol(ACImpl *This, UINT32 index); @@ -477,6 +487,7 @@ HRESULT WINAPI AUDDRV_GetAudioEndpoint(AudioDeviceID *adevid, IMMDevice *dev, IMMDevice_AddRef(This->parent); list_init(&This->avail_buffers); + list_init(&This->queued_bufinfos); This->adevid = *adevid; @@ -486,6 +497,84 @@ HRESULT WINAPI AUDDRV_GetAudioEndpoint(AudioDeviceID *adevid, IMMDevice *dev, return S_OK; } +/* current position from start of stream */ +#define BUFPOS_ABSOLUTE 1 +/* current position from start of this buffer */ +#define BUFPOS_RELATIVE 2 + +static UINT64 get_current_aqbuffer_position(ACImpl *This, int mode) +{ + struct list *head; + QueuedBufInfo *bufinfo; + UINT64 ret; + + head = list_head(&This->queued_bufinfos); + if(!head){ + TRACE("No buffers queued\n"); + if(mode == BUFPOS_ABSOLUTE) + return This->written_frames; + return 0; + } + bufinfo = LIST_ENTRY(head, QueuedBufInfo, entry); + + if(This->playing == StatePlaying){ + AudioTimeStamp tstamp; + OSStatus sc; + + /* AudioQueueGetCurrentTime() is brain damaged. The returned + * mSampleTime member jumps backwards seemingly at random, so + * we record the highest sampletime and use that during these + * anomalies. + * + * It also behaves poorly when the queue is paused, jumping + * forwards during the pause and backwards again after resuming. + * So we record the sampletime when the queue is paused and use + * that. */ + sc = AudioQueueGetCurrentTime(This->aqueue, NULL, &tstamp, NULL); + if(sc != noErr){ + if(sc != kAudioQueueErr_InvalidRunState) + WARN("Unable to get current time: %lx\n", sc); + return 0; + } + + if(!(tstamp.mFlags & kAudioTimeStampSampleTimeValid)){ + FIXME("SampleTime not valid: %lx\n", tstamp.mFlags); + return 0; + } + + if(tstamp.mSampleTime > This->highest_sampletime) + This->highest_sampletime = tstamp.mSampleTime; + } + + while(This->highest_sampletime > bufinfo->start_sampletime + bufinfo->len_frames){ + This->inbuf_frames -= bufinfo->len_frames; + list_remove(&bufinfo->entry); + HeapFree(GetProcessHeap(), 0, bufinfo); + + head = list_head(&This->queued_bufinfos); + if(!head){ + TRACE("No buffers queued\n"); + if(mode == BUFPOS_ABSOLUTE) + return This->written_frames; + return 0; + } + bufinfo = LIST_ENTRY(head, QueuedBufInfo, entry); + } + + if(This->highest_sampletime < bufinfo->start_sampletime) + ret = 0; + else + ret = This->highest_sampletime - bufinfo->start_sampletime; + + if(mode == BUFPOS_ABSOLUTE) + ret += bufinfo->start_pos; + + TRACE("%llu frames (%s)\n", ret, + mode == BUFPOS_ABSOLUTE ? "absolute" : "relative"); + + return ret; +} + static HRESULT WINAPI AudioClient_QueryInterface(IAudioClient *iface, REFIID riid, void **ppv) { @@ -522,17 +611,26 @@ static ULONG WINAPI AudioClient_Release(IAudioClient *iface) if(!ref){ if(This->aqueue){ AQBuffer *buf, *next; + QueuedBufInfo *bufinfo, *bufinfo2; + if(This->public_buffer){ buf = This->public_buffer->mUserData; list_add_tail(&This->avail_buffers, &buf->entry); } + IAudioClient_Stop(iface); AudioQueueStop(This->aqueue, 1); + /* Stopped synchronously, all buffers returned. */ LIST_FOR_EACH_ENTRY_SAFE(buf, next, &This->avail_buffers, AQBuffer, entry){ AudioQueueFreeBuffer(This->aqueue, buf->buf); HeapFree(GetProcessHeap(), 0, buf); } + + LIST_FOR_EACH_ENTRY_SAFE(bufinfo, bufinfo2, &This->queued_bufinfos, + QueuedBufInfo, entry) + HeapFree(GetProcessHeap(), 0, bufinfo); + AudioQueueDispose(This->aqueue, 1); } if(This->session){ @@ -677,7 +775,6 @@ static void ca_out_buffer_cb(void *user, AudioQueueRef aqueue, OSSpinLockLock(&This->lock); list_add_tail(&This->avail_buffers, &buf->entry); - This->inbuf_frames -= buffer->mAudioDataByteSize / This->fmt->nBlockAlign; OSSpinLockUnlock(&This->lock); } @@ -1112,7 +1209,12 @@ static HRESULT AudioClient_GetCurrentPadding_nolock(ACImpl *This, if(!This->aqueue) return AUDCLNT_E_NOT_INITIALIZED; - *numpad = This->inbuf_frames; + if(This->dataflow == eRender){ + UINT64 bufpos; + bufpos = get_current_aqbuffer_position(This, BUFPOS_RELATIVE); + *numpad = This->inbuf_frames - bufpos; + }else + *numpad = This->inbuf_frames; return S_OK; } @@ -1359,6 +1461,7 @@ static HRESULT WINAPI AudioClient_Start(IAudioClient *iface) static HRESULT WINAPI AudioClient_Stop(IAudioClient *iface) { ACImpl *This = impl_from_IAudioClient(iface); + AudioTimeStamp tstamp; OSStatus sc; TRACE("(%p)\n", This); @@ -1387,6 +1490,16 @@ static HRESULT WINAPI AudioClient_Stop(IAudioClient *iface) This->playing = StateInTransition; + sc = AudioQueueGetCurrentTime(This->aqueue, NULL, &tstamp, NULL); + if(sc == noErr){ + if(tstamp.mFlags & kAudioTimeStampSampleTimeValid){ + if(tstamp.mSampleTime > This->highest_sampletime) + This->highest_sampletime = tstamp.mSampleTime; + }else + WARN("Returned tstamp mSampleTime not valid: %lx\n", tstamp.mFlags); + }else + WARN("GetCurrentTime failed: %lx\n", sc); + OSSpinLockUnlock(&This->lock); sc = AudioQueueFlush(This->aqueue); @@ -1411,8 +1524,8 @@ static HRESULT WINAPI AudioClient_Stop(IAudioClient *iface) static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface) { ACImpl *This = impl_from_IAudioClient(iface); - HRESULT hr; OSStatus sc; + QueuedBufInfo *bufinfo, *bufinfo2; TRACE("(%p)\n", This); @@ -1434,11 +1547,12 @@ static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface) } This->written_frames = 0; + This->inbuf_frames = 0; - hr = AudioClock_GetPosition_nolock(This, &This->last_time, NULL, TRUE); - if(FAILED(hr)){ - OSSpinLockUnlock(&This->lock); - return hr; + LIST_FOR_EACH_ENTRY_SAFE(bufinfo, bufinfo2, &This->queued_bufinfos, + QueuedBufInfo, entry){ + list_remove(&bufinfo->entry); + HeapFree(GetProcessHeap(), 0, bufinfo); } OSSpinLockUnlock(&This->lock); @@ -1695,6 +1809,7 @@ static HRESULT WINAPI AudioRenderClient_ReleaseBuffer( IAudioRenderClient *iface, UINT32 frames, DWORD flags) { ACImpl *This = impl_from_IAudioRenderClient(iface); + AudioTimeStamp start_time; OSStatus sc; TRACE("(%p)->(%u, %x)\n", This, frames, flags); @@ -1737,13 +1852,26 @@ static HRESULT WINAPI AudioRenderClient_ReleaseBuffer( This->public_buffer->mAudioDataByteSize = frames * This->fmt->nBlockAlign; - sc = AudioQueueEnqueueBuffer(This->aqueue, This->public_buffer, 0, NULL); + sc = AudioQueueEnqueueBufferWithParameters(This->aqueue, + This->public_buffer, 0, NULL, 0, 0, 0, NULL, NULL, &start_time); if(sc != noErr){ OSSpinLockUnlock(&This->lock); WARN("Unable to enqueue buffer: %lx\n", sc); return E_FAIL; } + if(start_time.mFlags & kAudioTimeStampSampleTimeValid){ + QueuedBufInfo *bufinfo; + + bufinfo = HeapAlloc(GetProcessHeap(), 0, sizeof(*bufinfo)); + bufinfo->start_sampletime = start_time.mSampleTime; + bufinfo->start_pos = This->written_frames; + bufinfo->len_frames = frames; + + list_add_tail(&This->queued_bufinfos, &bufinfo->entry); + }else + WARN("Start time didn't contain valid SampleTime member\n"); + if(This->playing == StateStopped) AudioQueuePrime(This->aqueue, 0, NULL); @@ -1842,7 +1970,7 @@ static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface, This->getbuf_last = 1; if(devpos || qpcpos) - AudioClock_GetPosition_nolock(This, devpos, qpcpos, FALSE); + AudioClock_GetPosition_nolock(This, devpos, qpcpos); OSSpinLockUnlock(&This->lock); @@ -1974,28 +2102,12 @@ static HRESULT WINAPI AudioClock_GetFrequency(IAudioClock *iface, UINT64 *freq) } static HRESULT AudioClock_GetPosition_nolock(ACImpl *This, - UINT64 *pos, UINT64 *qpctime, BOOL raw) + UINT64 *pos, UINT64 *qpctime) { - AudioTimeStamp time; - OSStatus sc; - - sc = AudioQueueGetCurrentTime(This->aqueue, NULL, &time, NULL); - if(sc == kAudioQueueErr_InvalidRunState){ - *pos = 0; - }else if(sc == noErr){ - if(!(time.mFlags & kAudioTimeStampSampleTimeValid)){ - FIXME("Sample time not valid, should calculate from something else\n"); - return E_FAIL; - } - - if(raw) - *pos = time.mSampleTime; - else - *pos = time.mSampleTime - This->last_time; - }else{ - WARN("Unable to get current time: %lx\n", sc); - return E_FAIL; - } + if(This->dataflow == eRender) + *pos = get_current_aqbuffer_position(This, BUFPOS_ABSOLUTE); + else + *pos = This->inbuf_frames + This->written_frames; if(qpctime){ LARGE_INTEGER stamp, freq; @@ -2020,7 +2132,7 @@ static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, OSSpinLockLock(&This->lock); - hr = AudioClock_GetPosition_nolock(This, pos, qpctime, FALSE); + hr = AudioClock_GetPosition_nolock(This, pos, qpctime); OSSpinLockUnlock(&This->lock); -- 2.11.4.GIT