xaudio2: Implement the audio mixing thread.
This commit is contained in:
parent
997843700a
commit
a4fd0a7080
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue