[PATCH 2/4] xaudio2: Implement the audio mixing thread

Andrew Eikum aeikum at codeweavers.com
Thu Aug 27 09:31:12 CDT 2015


---
 dlls/xaudio2_7/xaudio_dll.c | 348 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 344 insertions(+), 4 deletions(-)

diff --git a/dlls/xaudio2_7/xaudio_dll.c b/dlls/xaudio2_7/xaudio_dll.c
index 94b42eb..8097ba3 100644
--- a/dlls/xaudio2_7/xaudio_dll.c
+++ b/dlls/xaudio2_7/xaudio_dll.c
@@ -48,6 +48,7 @@
 WINE_DEFAULT_DEBUG_CHANNEL(xaudio2);
 
 static ALCdevice *(ALC_APIENTRY *palcLoopbackOpenDeviceSOFT)(const ALCchar*);
+static void (ALC_APIENTRY *palcRenderSamplesSOFT)(ALCdevice*, ALCvoid*, ALCsizei);
 
 static HINSTANCE instance;
 
@@ -94,7 +95,8 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD reason, void *pReserved)
         DisableThreadLibraryCalls( hinstDLL );
 
         if(!alcIsExtensionPresent(NULL, "ALC_SOFT_loopback") ||
-                !(palcLoopbackOpenDeviceSOFT = alcGetProcAddress(NULL, "alcLoopbackOpenDeviceSOFT"))){
+                !(palcLoopbackOpenDeviceSOFT = alcGetProcAddress(NULL, "alcLoopbackOpenDeviceSOFT")) ||
+                !(palcRenderSamplesSOFT = alcGetProcAddress(NULL, "alcRenderSamplesSOFT"))){
             ERR("XAudio2 requires the ALC_SOFT_loopback extension (OpenAL-Soft >= 1.14)\n");
             return FALSE;
         }
@@ -121,6 +123,12 @@ HRESULT WINAPI DllUnregisterServer(void)
     return __wine_unregister_resources(instance);
 }
 
+typedef struct _XA2Buffer {
+    XAUDIO2_BUFFER xa2buffer;
+    DWORD offs_bytes;
+    UINT32 latest_al_buf;
+} XA2Buffer;
+
 typedef struct _IXAudio2Impl IXAudio2Impl;
 
 typedef struct _XA2SourceImpl {
@@ -134,6 +142,8 @@ typedef struct _XA2SourceImpl {
     CRITICAL_SECTION lock;
 
     WAVEFORMATEX *fmt;
+    ALenum al_fmt;
+    UINT32 submit_blocksize;
 
     IXAudio2VoiceCallback *cb;
 
@@ -142,6 +152,17 @@ typedef struct _XA2SourceImpl {
 
     BOOL running;
 
+    UINT64 played_frames;
+
+    XA2Buffer buffers[XAUDIO2_MAX_QUEUED_BUFFERS];
+    UINT32 first_buf, cur_buf, nbufs, in_al_bytes;
+
+    ALuint al_src;
+    /* most cases will only need about 4 AL buffers, but some corner cases
+     * could require up to MAX_QUEUED_BUFFERS */
+    ALuint al_bufs[XAUDIO2_MAX_QUEUED_BUFFERS];
+    DWORD first_al_buf, al_bufs_used;
+
     struct list entry;
 } XA2SourceImpl;
 
@@ -179,6 +200,9 @@ struct _IXAudio2Impl {
 
     CRITICAL_SECTION lock;
 
+    HANDLE engine, mmevt;
+    BOOL stop_engine;
+
     DWORD version;
 
     struct list source_voices;
@@ -192,6 +216,8 @@ struct _IXAudio2Impl {
     IAudioClient *aclient;
     IAudioRenderClient *render;
 
+    UINT32 period_frames;
+
     WAVEFORMATEXTENSIBLE fmt;
 
     ALCdevice *al_device;
@@ -199,6 +225,8 @@ struct _IXAudio2Impl {
 
     UINT32 ncbs;
     IXAudio2EngineCallback **cbs;
+
+    BOOL running;
 };
 
 static inline IXAudio2Impl *impl_from_IXAudio2(IXAudio2 *iface)
@@ -422,6 +450,7 @@ static void WINAPI XA2SRC_GetOutputMatrix(IXAudio2SourceVoice *iface,
 static void WINAPI XA2SRC_DestroyVoice(IXAudio2SourceVoice *iface)
 {
     XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
+    ALint processed;
 
     TRACE("%p\n", This);
 
@@ -436,8 +465,33 @@ static void WINAPI XA2SRC_DestroyVoice(IXAudio2SourceVoice *iface)
 
     This->running = FALSE;
 
+    IXAudio2SourceVoice_Stop(iface, 0, 0);
+
+    alSourceStop(This->al_src);
+
+    /* unqueue all buffers */
+    alSourcei(This->al_src, AL_BUFFER, AL_NONE);
+
+    alGetSourcei(This->al_src, AL_BUFFERS_PROCESSED, &processed);
+
+    if(processed > 0){
+        ALuint al_buffers[XAUDIO2_MAX_QUEUED_BUFFERS];
+
+        alSourceUnqueueBuffers(This->al_src, processed, al_buffers);
+    }
+
     HeapFree(GetProcessHeap(), 0, This->fmt);
 
+    alDeleteBuffers(XAUDIO2_MAX_QUEUED_BUFFERS, This->al_bufs);
+    alDeleteSources(1, &This->al_src);
+
+    This->in_al_bytes = 0;
+    This->al_bufs_used = 0;
+    This->played_frames = 0;
+    This->nbufs = 0;
+    This->first_buf = 0;
+    This->cur_buf = 0;
+
     LeaveCriticalSection(&This->lock);
 }
 
@@ -473,6 +527,59 @@ static HRESULT WINAPI XA2SRC_Stop(IXAudio2SourceVoice *iface, UINT32 Flags,
     return S_OK;
 }
 
+static ALenum get_al_format(const WAVEFORMATEX *fmt)
+{
+    WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)fmt;
+    if(fmt->wFormatTag == WAVE_FORMAT_PCM ||
+            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+             IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){
+        switch(fmt->wBitsPerSample){
+        case 8:
+            switch(fmt->nChannels){
+            case 1:
+                return AL_FORMAT_MONO8;
+            case 2:
+                return AL_FORMAT_STEREO8;
+            case 4:
+                return AL_FORMAT_QUAD8;
+            case 6:
+                return AL_FORMAT_51CHN8;
+            case 7:
+                return AL_FORMAT_61CHN8;
+            case 8:
+                return AL_FORMAT_71CHN8;
+            }
+        case 16:
+            switch(fmt->nChannels){
+            case 1:
+                return AL_FORMAT_MONO16;
+            case 2:
+                return AL_FORMAT_STEREO16;
+            case 4:
+                return AL_FORMAT_QUAD16;
+            case 6:
+                return AL_FORMAT_51CHN16;
+            case 7:
+                return AL_FORMAT_61CHN16;
+            case 8:
+                return AL_FORMAT_71CHN16;
+            }
+        }
+    }else if(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT ||
+            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+             IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))){
+        if(fmt->wBitsPerSample == 32){
+            switch(fmt->nChannels){
+            case 1:
+                return AL_FORMAT_MONO_FLOAT32;
+            case 2:
+                return AL_FORMAT_STEREO_FLOAT32;
+            }
+        }
+    }
+    return 0;
+}
+
 static HRESULT WINAPI XA2SRC_SubmitSourceBuffer(IXAudio2SourceVoice *iface,
         const XAUDIO2_BUFFER *pBuffer, const XAUDIO2_BUFFER_WMA *pBufferWMA)
 {
@@ -1243,6 +1350,13 @@ static ULONG WINAPI IXAudio2Impl_Release(IXAudio2 *iface)
         XA2SourceImpl *src, *src2;
         XA2SubmixImpl *sub, *sub2;
 
+        if(This->engine){
+            This->stop_engine = TRUE;
+            SetEvent(This->mmevt);
+            WaitForSingleObject(This->engine, INFINITE);
+            CloseHandle(This->engine);
+        }
+
         LIST_FOR_EACH_ENTRY_SAFE(src, src2, &This->source_voices, XA2SourceImpl, entry){
             HeapFree(GetProcessHeap(), 0, src->sends);
             IXAudio2SourceVoice_DestroyVoice(&src->IXAudio2SourceVoice_iface);
@@ -1265,6 +1379,8 @@ static ULONG WINAPI IXAudio2Impl_Release(IXAudio2 *iface)
         HeapFree(GetProcessHeap(), 0, This->devids);
         HeapFree(GetProcessHeap(), 0, This->cbs);
 
+        CloseHandle(This->mmevt);
+
         DeleteCriticalSection(&This->lock);
 
         HeapFree(GetProcessHeap(), 0, This);
@@ -1388,6 +1504,15 @@ static HRESULT WINAPI IXAudio2Impl_CreateSourceVoice(IXAudio2 *iface,
 
     src->cb = pCallback;
 
+    src->al_fmt = get_al_format(pSourceFormat);
+    if(!src->al_fmt){
+        src->in_use = FALSE;
+        WARN("OpenAL can't convert this format!\n");
+        return AUDCLNT_E_UNSUPPORTED_FORMAT;
+    }
+
+    src->submit_blocksize = pSourceFormat->nBlockAlign;
+
     src->fmt = copy_waveformat(pSourceFormat);
 
     hr = XA2SRC_SetOutputVoices(&src->IXAudio2SourceVoice_iface, pSendList);
@@ -1396,6 +1521,11 @@ static HRESULT WINAPI IXAudio2Impl_CreateSourceVoice(IXAudio2 *iface,
         return hr;
     }
 
+    alGenSources(1, &src->al_src);
+    alGenBuffers(XAUDIO2_MAX_QUEUED_BUFFERS, src->al_bufs);
+
+    alSourcePlay(src->al_src);
+
     if(This->version == 27)
         *ppSourceVoice = (IXAudio2SourceVoice*)&src->IXAudio27SourceVoice_iface;
     else
@@ -1485,6 +1615,7 @@ static HRESULT WINAPI IXAudio2Impl_CreateMasteringVoice(IXAudio2 *iface,
     HRESULT hr;
     WAVEFORMATEX *fmt;
     ALCint attrs[7];
+    REFERENCE_TIME period;
 
     TRACE("(%p)->(%p, %u, %u, 0x%x, %s, %p, 0x%x)\n", This,
             ppMasteringVoice, inputChannels, inputSampleRate, flags,
@@ -1580,6 +1711,22 @@ static HRESULT WINAPI IXAudio2Impl_CreateMasteringVoice(IXAudio2 *iface,
         goto exit;
     }
 
+    hr = IAudioClient_GetDevicePeriod(This->aclient, &period, NULL);
+    if(FAILED(hr)){
+        WARN("GetDevicePeriod failed: %08x\n", hr);
+        hr = XAUDIO2_E_DEVICE_INVALIDATED;
+        goto exit;
+    }
+
+    This->period_frames = MulDiv(period, inputSampleRate, 10000000);
+
+    hr = IAudioClient_SetEventHandle(This->aclient, This->mmevt);
+    if(FAILED(hr)){
+        WARN("Initialize failed: %08x\n", hr);
+        hr = XAUDIO2_E_DEVICE_INVALIDATED;
+        goto exit;
+    }
+
     hr = IAudioClient_GetService(This->aclient, &IID_IAudioRenderClient,
             (void**)&This->render);
     if(FAILED(hr)){
@@ -1675,20 +1822,29 @@ exit:
     return hr;
 }
 
+static DWORD WINAPI engine_threadproc(void *arg);
+
 static HRESULT WINAPI IXAudio2Impl_StartEngine(IXAudio2 *iface)
 {
     IXAudio2Impl *This = impl_from_IXAudio2(iface);
 
-    FIXME("(%p)->(): stub!\n", This);
+    TRACE("(%p)->()\n", This);
 
-    return E_NOTIMPL;
+    This->running = TRUE;
+
+    if(!This->engine)
+        This->engine = CreateThread(NULL, 0, engine_threadproc, This, 0, NULL);
+
+    return S_OK;
 }
 
 static void WINAPI IXAudio2Impl_StopEngine(IXAudio2 *iface)
 {
     IXAudio2Impl *This = impl_from_IXAudio2(iface);
 
-    FIXME("(%p)->(): stub!\n", This);
+    TRACE("(%p)->()\n", This);
+
+    This->running = FALSE;
 }
 
 static HRESULT WINAPI IXAudio2Impl_CommitChanges(IXAudio2 *iface,
@@ -2094,6 +2250,7 @@ static HRESULT WINAPI XAudio2CF_CreateInstance(IClassFactory *iface, IUnknown *p
     list_init(&object->source_voices);
     list_init(&object->submix_voices);
 
+    object->mmevt = CreateEventW(NULL, FALSE, FALSE, NULL);
     InitializeCriticalSection(&object->lock);
     object->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": IXAudio2Impl.lock");
 
@@ -2112,6 +2269,8 @@ static HRESULT WINAPI XAudio2CF_CreateInstance(IClassFactory *iface, IUnknown *p
     object->ncbs = 4;
     object->cbs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, object->ncbs * sizeof(*object->cbs));
 
+    IXAudio2_StartEngine(&object->IXAudio2_iface);
+
     return hr;
 }
 
@@ -2145,3 +2304,184 @@ HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
 
     return IClassFactory_QueryInterface(factory, riid, ppv);
 }
+
+/* returns TRUE if there is more data avilable in the buffer, FALSE if the
+ * buffer's data has all been queued */
+static BOOL xa2buffer_queue_period(XA2SourceImpl *src, XA2Buffer *buf, ALuint al_buf)
+{
+    UINT32 submit_bytes;
+    const BYTE *submit_buf = NULL;
+
+    if(buf->offs_bytes >= buf->xa2buffer.AudioBytes){
+        WARN("Shouldn't happen: Trying to push frames from a spent buffer?\n");
+        return FALSE;
+    }
+
+    submit_bytes = min(src->xa2->period_frames * src->submit_blocksize, buf->xa2buffer.AudioBytes - buf->offs_bytes);
+    submit_buf = buf->xa2buffer.pAudioData + buf->offs_bytes;
+    buf->offs_bytes += submit_bytes;
+
+    alBufferData(al_buf, src->al_fmt, submit_buf, submit_bytes,
+            src->fmt->nSamplesPerSec);
+
+    alSourceQueueBuffers(src->al_src, 1, &al_buf);
+
+    src->in_al_bytes += submit_bytes;
+    src->al_bufs_used++;
+
+    buf->latest_al_buf = al_buf;
+
+    TRACE("queueing %u bytes, now %u in AL\n", submit_bytes, src->in_al_bytes);
+
+    return buf->offs_bytes < buf->xa2buffer.AudioBytes;
+}
+
+static void update_source_state(XA2SourceImpl *src)
+{
+    int i;
+    ALint processed;
+    ALint bufpos;
+
+    alGetSourcei(src->al_src, AL_BUFFERS_PROCESSED, &processed);
+
+    if(processed > 0){
+        ALuint al_buffers[XAUDIO2_MAX_QUEUED_BUFFERS];
+
+        alSourceUnqueueBuffers(src->al_src, processed, al_buffers);
+        src->first_al_buf += processed;
+        src->first_al_buf %= XAUDIO2_MAX_QUEUED_BUFFERS;
+        src->al_bufs_used -= processed;
+
+        for(i = 0; i < processed; ++i){
+            ALint bufsize;
+
+            alGetBufferi(al_buffers[i], AL_SIZE, &bufsize);
+
+            src->in_al_bytes -= bufsize;
+            src->played_frames += bufsize / src->submit_blocksize;
+
+            if(al_buffers[i] == src->buffers[src->first_buf].latest_al_buf){
+                DWORD old_buf = src->first_buf;
+
+                src->first_buf++;
+                src->first_buf %= XAUDIO2_MAX_QUEUED_BUFFERS;
+                src->nbufs--;
+
+                TRACE("%p: done with buffer %u\n", src, old_buf);
+
+                if(src->cb){
+                    IXAudio2VoiceCallback_OnBufferEnd(src->cb,
+                            src->buffers[old_buf].xa2buffer.pContext);
+
+                    if(src->nbufs > 0)
+                        IXAudio2VoiceCallback_OnBufferStart(src->cb,
+                                src->buffers[src->first_buf].xa2buffer.pContext);
+                }
+            }
+        }
+    }
+
+    alGetSourcei(src->al_src, AL_BYTE_OFFSET, &bufpos);
+
+    /* maintain 4 periods in AL */
+    while(src->cur_buf != (src->first_buf + src->nbufs) % XAUDIO2_MAX_QUEUED_BUFFERS &&
+            src->in_al_bytes - bufpos < 4 * src->xa2->period_frames * src->submit_blocksize){
+        TRACE("%p: going to queue a period from buffer %u\n", src, src->cur_buf);
+
+        /* starting from an empty buffer */
+        if(src->cb && src->cur_buf == src->first_buf && src->buffers[src->cur_buf].offs_bytes == 0)
+            IXAudio2VoiceCallback_OnBufferStart(src->cb,
+                    src->buffers[src->first_buf].xa2buffer.pContext);
+
+        if(!xa2buffer_queue_period(src, &src->buffers[src->cur_buf],
+                    src->al_bufs[(src->first_al_buf + src->al_bufs_used) % XAUDIO2_MAX_QUEUED_BUFFERS])){
+            /* buffer is spent, move on */
+            src->cur_buf++;
+            src->cur_buf %= XAUDIO2_MAX_QUEUED_BUFFERS;
+        }
+    }
+}
+
+static void do_engine_tick(IXAudio2Impl *This)
+{
+    BYTE *buf;
+    XA2SourceImpl *src;
+    HRESULT hr;
+    UINT32 nframes, i, pad;
+
+    /* maintain up to 3 periods in mmdevapi */
+    hr = IAudioClient_GetCurrentPadding(This->aclient, &pad);
+    if(FAILED(hr)){
+        WARN("GetCurrentPadding failed: 0x%x\n", hr);
+        return;
+    }
+
+    nframes = This->period_frames * 3 - pad;
+    TRACE("going to render %u frames\n", nframes);
+
+    if(!nframes)
+        return;
+
+    for(i = 0; i < This->ncbs && This->cbs[i]; ++i)
+        IXAudio2EngineCallback_OnProcessingPassStart(This->cbs[i]);
+
+    LIST_FOR_EACH_ENTRY(src, &This->source_voices, XA2SourceImpl, entry){
+        ALint st = 0;
+
+        EnterCriticalSection(&src->lock);
+
+        if(!src->in_use || !src->running){
+            LeaveCriticalSection(&src->lock);
+            continue;
+        }
+
+        if(src->cb)
+            /* TODO: detect incoming underrun and inform callback */
+            IXAudio2VoiceCallback_OnVoiceProcessingPassStart(src->cb, 0);
+
+        update_source_state(src);
+
+        alGetSourcei(src->al_src, AL_SOURCE_STATE, &st);
+        if(st != AL_PLAYING)
+            alSourcePlay(src->al_src);
+
+        if(src->cb)
+            IXAudio2VoiceCallback_OnVoiceProcessingPassEnd(src->cb);
+
+        LeaveCriticalSection(&src->lock);
+    }
+
+    hr = IAudioRenderClient_GetBuffer(This->render, nframes, &buf);
+    if(FAILED(hr))
+        WARN("GetBuffer failed: %08x\n", hr);
+
+    palcRenderSamplesSOFT(This->al_device, buf, nframes);
+
+    hr = IAudioRenderClient_ReleaseBuffer(This->render, nframes, 0);
+    if(FAILED(hr))
+        WARN("ReleaseBuffer failed: %08x\n", hr);
+
+    for(i = 0; i < This->ncbs && This->cbs[i]; ++i)
+        IXAudio2EngineCallback_OnProcessingPassEnd(This->cbs[i]);
+}
+
+static DWORD WINAPI engine_threadproc(void *arg)
+{
+    IXAudio2Impl *This = arg;
+    while(1){
+        WaitForSingleObject(This->mmevt, INFINITE);
+
+        if(This->stop_engine)
+            break;
+
+        if(!This->running)
+            continue;
+
+        EnterCriticalSection(&This->lock);
+
+        do_engine_tick(This);
+
+        LeaveCriticalSection(&This->lock);
+    }
+    return 0;
+}
-- 
2.5.0





More information about the wine-patches mailing list