[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