Commit 4b888861 authored by Jörg Höhle's avatar Jörg Höhle Committed by Alexandre Julliard

winecoreaudio: Implement a lock-free callback design.

parent c9950119
......@@ -75,6 +75,7 @@ typedef struct _QueuedBufInfo {
typedef struct _AQBuffer {
AudioQueueBufferRef buf;
struct list entry;
BOOL used;
} AQBuffer;
struct ACImpl;
......@@ -145,12 +146,9 @@ struct ACImpl {
struct list entry;
struct list avail_buffers;
struct list queued_buffers; /* either in avail, queued or public_buffer */
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
* instead. OSSpinLock is not a recursive lock, so don't call
* synchronized functions while holding the lock. */
OSSpinLock lock;
};
......@@ -191,8 +189,7 @@ static CRITICAL_SECTION_DEBUG g_sessions_lock_debug =
static CRITICAL_SECTION g_sessions_lock = { &g_sessions_lock_debug, -1, 0, 0, 0, 0 };
static struct list g_sessions = LIST_INIT(g_sessions);
static HRESULT AudioClock_GetPosition_nolock(ACImpl *This, UINT64 *pos,
UINT64 *qpctime);
static HRESULT AudioCaptureClient_GetNextPacket(ACImpl *This, UINT32 *frames);
static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client);
static HRESULT ca_setvol(ACImpl *This, UINT32 index);
......@@ -487,6 +484,7 @@ HRESULT WINAPI AUDDRV_GetAudioEndpoint(AudioDeviceID *adevid, IMMDevice *dev,
IMMDevice_AddRef(This->parent);
list_init(&This->avail_buffers);
list_init(&This->queued_buffers);
list_init(&This->queued_bufinfos);
This->adevid = *adevid;
......@@ -575,6 +573,20 @@ static UINT64 get_current_aqbuffer_position(ACImpl *This, int mode)
return ret;
}
static void avail_update(ACImpl *This)
{
AQBuffer *buf, *next;
LIST_FOR_EACH_ENTRY_SAFE(buf, next, &This->queued_buffers, AQBuffer, entry){
if(buf->used)
break;
if(This->dataflow == eCapture)
This->inbuf_frames += buf->buf->mAudioDataByteSize / This->fmt->nBlockAlign;
list_remove(&buf->entry);
list_add_tail(&This->avail_buffers, &buf->entry);
}
}
static HRESULT WINAPI AudioClient_QueryInterface(IAudioClient *iface,
REFIID riid, void **ppv)
{
......@@ -622,6 +634,7 @@ static ULONG WINAPI AudioClient_Release(IAudioClient *iface)
AudioQueueStop(This->aqueue, 1);
/* Stopped synchronously, all buffers returned. */
list_move_tail(&This->avail_buffers, &This->queued_buffers);
LIST_FOR_EACH_ENTRY_SAFE(buf, next, &This->avail_buffers, AQBuffer, entry){
AudioQueueFreeBuffer(This->aqueue, buf->buf);
HeapFree(GetProcessHeap(), 0, buf);
......@@ -767,28 +780,26 @@ static HRESULT ca_get_audiodesc(AudioStreamBasicDescription *desc,
return S_OK;
}
/* We can't use debug printing or {Enter,Leave}CriticalSection from
* OSX callback threads. We may use OSSpinLock.
* OSSpinLock is not a recursive lock, so don't call
* synchronized functions while holding the lock. */
static void ca_out_buffer_cb(void *user, AudioQueueRef aqueue,
AudioQueueBufferRef buffer)
{
ACImpl *This = user;
AQBuffer *buf = buffer->mUserData;
OSSpinLockLock(&This->lock);
list_add_tail(&This->avail_buffers, &buf->entry);
OSSpinLockUnlock(&This->lock);
buf->used = FALSE;
}
static void ca_in_buffer_cb(void *user, AudioQueueRef aqueue,
AudioQueueBufferRef buffer, const AudioTimeStamp *start,
UInt32 ndesc, const AudioStreamPacketDescription *descs)
{
ACImpl *This = user;
AQBuffer *buf = buffer->mUserData;
OSSpinLockLock(&This->lock);
list_add_tail(&This->avail_buffers, &buf->entry);
This->inbuf_frames += buffer->mAudioDataByteSize / This->fmt->nBlockAlign;
OSSpinLockUnlock(&This->lock);
buf->used = FALSE;
/* let's update inbuf_frames synchronously without OSAddAtomic */
}
static HRESULT ca_setup_aqueue(AudioDeviceID did, EDataFlow flow,
......@@ -1037,12 +1048,13 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
}
buf->buf->mUserData = buf;
buf->used = TRUE;
sc = AudioQueueEnqueueBuffer(This->aqueue, buf->buf, 0, NULL);
if(sc != noErr){
ERR("Couldn't enqueue buffer: %lx\n", sc);
break;
}
list_add_tail(&This->queued_buffers, &buf->entry);
}
}
......@@ -1224,6 +1236,8 @@ static HRESULT AudioClient_GetCurrentPadding_nolock(ACImpl *This,
if(!This->aqueue)
return AUDCLNT_E_NOT_INITIALIZED;
avail_update(This);
if(This->dataflow == eRender){
UINT64 bufpos;
bufpos = get_current_aqbuffer_position(This, BUFPOS_RELATIVE);
......@@ -1408,10 +1422,8 @@ void CALLBACK ca_period_cb(void *user, BOOLEAN timer)
{
ACImpl *This = user;
OSSpinLockLock(&This->lock);
if(This->event)
SetEvent(This->event);
OSSpinLockUnlock(&This->lock);
}
static HRESULT WINAPI AudioClient_Start(IAudioClient *iface)
......@@ -1439,8 +1451,8 @@ static HRESULT WINAPI AudioClient_Start(IAudioClient *iface)
}
if(This->event)
if(!CreateTimerQueueTimer(&This->timer, g_timer_q,
ca_period_cb, This, 0, This->period_ms, 0)){
if(!CreateTimerQueueTimer(&This->timer, g_timer_q, ca_period_cb,
This, 0, This->period_ms, WT_EXECUTEINTIMERTHREAD)){
This->timer = NULL;
OSSpinLockUnlock(&This->lock);
WARN("Unable to create timer: %u\n", GetLastError());
......@@ -1449,16 +1461,13 @@ static HRESULT WINAPI AudioClient_Start(IAudioClient *iface)
This->playing = StateInTransition;
OSSpinLockUnlock(&This->lock);
sc = AudioQueueStart(This->aqueue, NULL);
if(sc != noErr){
OSSpinLockUnlock(&This->lock);
WARN("Unable to start audio queue: %lx\n", sc);
return E_FAIL;
}
OSSpinLockLock(&This->lock);
This->playing = StatePlaying;
OSSpinLockUnlock(&This->lock);
......@@ -1514,28 +1523,27 @@ static HRESULT WINAPI AudioClient_Stop(IAudioClient *iface)
}else
WARN("GetCurrentTime failed: %lx\n", sc);
OSSpinLockUnlock(&This->lock);
if(event && wait)
WaitForSingleObject(event, INFINITE);
CloseHandle(event);
sc = AudioQueueFlush(This->aqueue);
if(sc != noErr)
if(sc != noErr){
OSSpinLockUnlock(&This->lock);
WARN("Unable to flush audio queue: %lx\n", sc);
}
sc = AudioQueuePause(This->aqueue);
if(sc != noErr){
OSSpinLockUnlock(&This->lock);
WARN("Unable to pause audio queue: %lx\n", sc);
return E_FAIL;
}
OSSpinLockLock(&This->lock);
This->playing = StateStopped;
OSSpinLockUnlock(&This->lock);
if(event && wait)
WaitForSingleObject(event, INFINITE);
CloseHandle(event);
return S_OK;
}
......@@ -1573,14 +1581,15 @@ static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface)
HeapFree(GetProcessHeap(), 0, bufinfo);
}
OSSpinLockUnlock(&This->lock);
sc = AudioQueueReset(This->aqueue);
if(sc != noErr){
OSSpinLockUnlock(&This->lock);
WARN("Unable to reset audio queue: %lx\n", sc);
return E_FAIL;
}
OSSpinLockUnlock(&This->lock);
return S_OK;
}
......@@ -1754,7 +1763,7 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
{
ACImpl *This = impl_from_IAudioRenderClient(iface);
AQBuffer *buf;
UINT32 pad, bytes = frames * This->fmt->nBlockAlign;
UINT32 pad, bytes;
HRESULT hr;
OSStatus sc;
......@@ -1787,6 +1796,7 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
return AUDCLNT_E_BUFFER_TOO_LARGE;
}
bytes = frames * This->fmt->nBlockAlign;
LIST_FOR_EACH_ENTRY(buf, &This->avail_buffers, AQBuffer, entry){
if(buf->buf->mAudioDataBytesCapacity >= bytes){
This->public_buffer = buf->buf;
......@@ -1799,9 +1809,10 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
sc = AudioQueueAllocateBuffer(This->aqueue, bytes,
&This->public_buffer);
if(sc != noErr){
This->public_buffer = NULL;
OSSpinLockUnlock(&This->lock);
WARN("Unable to allocate buffer: %lx\n", sc);
return E_FAIL;
return E_OUTOFMEMORY;
}
buf = HeapAlloc(GetProcessHeap(), 0, sizeof(AQBuffer));
if(!buf){
......@@ -1810,13 +1821,13 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
OSSpinLockUnlock(&This->lock);
return E_OUTOFMEMORY;
}
buf->used = FALSE;
buf->buf = This->public_buffer;
This->public_buffer->mUserData = buf;
}
*data = This->public_buffer->mAudioData;
This->getbuf_last = frames;
*data = This->public_buffer->mAudioData;
OSSpinLockUnlock(&This->lock);
......@@ -1827,6 +1838,7 @@ static HRESULT WINAPI AudioRenderClient_ReleaseBuffer(
IAudioRenderClient *iface, UINT32 frames, DWORD flags)
{
ACImpl *This = impl_from_IAudioRenderClient(iface);
AQBuffer *buf;
AudioTimeStamp start_time;
OSStatus sc;
......@@ -1837,8 +1849,8 @@ static HRESULT WINAPI AudioRenderClient_ReleaseBuffer(
if(!frames){
This->getbuf_last = 0;
if(This->public_buffer){
AQBuffer *buf = This->public_buffer->mUserData;
list_add_tail(&This->avail_buffers, &buf->entry);
buf = This->public_buffer->mUserData;
list_add_head(&This->avail_buffers, &buf->entry);
This->public_buffer = NULL;
}
OSSpinLockUnlock(&This->lock);
......@@ -1870,13 +1882,16 @@ static HRESULT WINAPI AudioRenderClient_ReleaseBuffer(
This->public_buffer->mAudioDataByteSize = frames * This->fmt->nBlockAlign;
buf = This->public_buffer->mUserData;
buf->used = TRUE;
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;
ERR("Unable to enqueue buffer: %lx\n", sc);
return AUDCLNT_E_DEVICE_INVALIDATED;
}
list_add_tail(&This->queued_buffers, &buf->entry);
if(start_time.mFlags & kAudioTimeStampSampleTimeValid){
QueuedBufInfo *bufinfo;
......@@ -1946,11 +1961,14 @@ static ULONG WINAPI AudioCaptureClient_Release(IAudioCaptureClient *iface)
static HRESULT AudioCaptureClient_GetNextPacket(ACImpl *This, UINT32 *frames)
{
AQBuffer *buf;
OSStatus sc;
avail_update(This); /* once, not inside loop */
for(;;){
if(!This->public_buffer){
struct list *head = list_head(&This->avail_buffers);
AQBuffer *buf;
if(!head){
*frames = 0;
......@@ -1959,20 +1977,24 @@ static HRESULT AudioCaptureClient_GetNextPacket(ACImpl *This, UINT32 *frames)
buf = LIST_ENTRY(head, AQBuffer, entry);
This->public_buffer = buf->buf;
list_remove(&buf->entry);
}
}else
buf = This->public_buffer->mUserData;
*frames = This->public_buffer->mAudioDataByteSize / This->fmt->nBlockAlign;
if(*frames)
return S_OK;
WARN("empty packet\n");
/* fixme: for reasons not yet understood, the initially submitted
* packets are returned with 0 bytes. Resubmit them. */
buf->used = TRUE;
sc = AudioQueueEnqueueBuffer(This->aqueue, This->public_buffer, 0, NULL);
if(sc != noErr){
ERR("Unable to enqueue buffer: %lx\n", sc);
/* Release will free This->public_buffer */
return AUDCLNT_E_DEVICE_INVALIDATED;
}else
This->public_buffer = NULL;
list_add_tail(&This->queued_buffers, &buf->entry);
This->public_buffer = NULL;
}
}
......@@ -2003,14 +2025,17 @@ static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface,
}
if((This->getbuf_last = *frames)){
UINT64 pos;
*flags = 0;
*data = This->public_buffer->mAudioData;
if(devpos)
*devpos = This->written_frames;
if(qpcpos) /* fixme: qpc of recording time */
AudioClock_GetPosition_nolock(This, &pos, qpcpos);
if(qpcpos){ /* fixme: qpc of recording time */
LARGE_INTEGER stamp, freq;
QueryPerformanceCounter(&stamp);
QueryPerformanceFrequency(&freq);
*qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
}
}
OSSpinLockUnlock(&This->lock);
......@@ -2021,6 +2046,7 @@ static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer(
IAudioCaptureClient *iface, UINT32 done)
{
ACImpl *This = impl_from_IAudioCaptureClient(iface);
AQBuffer *buf;
OSStatus sc;
TRACE("(%p)->(%u)\n", This, done);
......@@ -2047,6 +2073,8 @@ static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer(
This->inbuf_frames -= done;
This->getbuf_last = 0;
buf = This->public_buffer->mUserData;
buf->used = TRUE;
sc = AudioQueueEnqueueBuffer(This->aqueue, This->public_buffer, 0, NULL);
if(sc != noErr){
OSSpinLockUnlock(&This->lock);
......@@ -2054,7 +2082,8 @@ static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer(
* GetBuffer will see that packet again and again. */
ERR("Unable to enqueue buffer: %lx\n", sc);
return AUDCLNT_E_DEVICE_INVALIDATED;
}
}else
list_add_tail(&This->queued_buffers, &buf->entry);
This->public_buffer = NULL;
OSSpinLockUnlock(&This->lock);
......@@ -2142,6 +2171,8 @@ static HRESULT WINAPI AudioClock_GetFrequency(IAudioClock *iface, UINT64 *freq)
static HRESULT AudioClock_GetPosition_nolock(ACImpl *This,
UINT64 *pos, UINT64 *qpctime)
{
avail_update(This);
if(This->dataflow == eRender)
*pos = get_current_aqbuffer_position(This, BUFPOS_ABSOLUTE);
else
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment