xaudio2: Implement the audio mixing thread.

This commit is contained in:
Andrew Eikum 2015-08-27 09:31:12 -05:00 committed by Alexandre Julliard
parent 997843700a
commit a4fd0a7080
1 changed files with 344 additions and 4 deletions

View File

@ -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;
}