diff --git a/dlls/mmdevapi/Makefile.in b/dlls/mmdevapi/Makefile.in index 5f44f7ba53b..903b14335a9 100644 --- a/dlls/mmdevapi/Makefile.in +++ b/dlls/mmdevapi/Makefile.in @@ -6,6 +6,7 @@ EXTRADLLFLAGS = -mno-cygwin C_SRCS = \ audiovolume.c \ devenum.c \ - main.c + main.c \ + spatialaudio.c IDL_SRCS = mmdevapi_classes.idl diff --git a/dlls/mmdevapi/audiovolume.c b/dlls/mmdevapi/audiovolume.c index 9214980120e..6f403cf348a 100644 --- a/dlls/mmdevapi/audiovolume.c +++ b/dlls/mmdevapi/audiovolume.c @@ -33,6 +33,7 @@ #include "audioclient.h" #include "endpointvolume.h" #include "audiopolicy.h" +#include "spatialaudioclient.h" #include "mmdevapi.h" diff --git a/dlls/mmdevapi/devenum.c b/dlls/mmdevapi/devenum.c index 9e4a29816ce..07b4dca028b 100644 --- a/dlls/mmdevapi/devenum.c +++ b/dlls/mmdevapi/devenum.c @@ -35,6 +35,7 @@ #include "audioclient.h" #include "endpointvolume.h" #include "audiopolicy.h" +#include "spatialaudioclient.h" #include "mmdevapi.h" #include "devpkey.h" @@ -635,6 +636,10 @@ static HRESULT WINAPI MMDevice_Activate(IMMDevice *iface, REFIID riid, DWORD cls IDirectSoundCapture_Release((IDirectSoundCapture*)*ppv); } } + else if (IsEqualIID(riid, &IID_ISpatialAudioClient)) + { + hr = SpatialAudioClient_Create(iface, (ISpatialAudioClient**)ppv); + } else ERR("Invalid/unknown iid %s\n", debugstr_guid(riid)); diff --git a/dlls/mmdevapi/main.c b/dlls/mmdevapi/main.c index 247ebc3b001..eac1da28f18 100644 --- a/dlls/mmdevapi/main.c +++ b/dlls/mmdevapi/main.c @@ -37,6 +37,7 @@ #include "audiopolicy.h" #include "devpkey.h" #include "winreg.h" +#include "spatialaudioclient.h" #include "mmdevapi.h" #include "wine/debug.h" diff --git a/dlls/mmdevapi/mmdevapi.h b/dlls/mmdevapi/mmdevapi.h index bc9788e95c8..3bcf568cddf 100644 --- a/dlls/mmdevapi/mmdevapi.h +++ b/dlls/mmdevapi/mmdevapi.h @@ -71,5 +71,6 @@ typedef struct MMDevice { extern HRESULT AudioClient_Create(MMDevice *parent, IAudioClient **ppv) DECLSPEC_HIDDEN; extern HRESULT AudioEndpointVolume_Create(MMDevice *parent, IAudioEndpointVolumeEx **ppv) DECLSPEC_HIDDEN; +extern HRESULT SpatialAudioClient_Create(IMMDevice *device, ISpatialAudioClient **out) DECLSPEC_HIDDEN; extern const WCHAR drv_keyW[] DECLSPEC_HIDDEN; diff --git a/dlls/mmdevapi/spatialaudio.c b/dlls/mmdevapi/spatialaudio.c new file mode 100644 index 00000000000..cbca57b4890 --- /dev/null +++ b/dlls/mmdevapi/spatialaudio.c @@ -0,0 +1,988 @@ +/* + * Copyright 2020 Andrew Eikum for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define COBJMACROS +#define NONAMELESSUNION + +#include + +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winreg.h" +#include "wine/heap.h" +#include "wine/debug.h" +#include "wine/list.h" + +#include "ole2.h" +#include "mmdeviceapi.h" +#include "mmsystem.h" +#include "audioclient.h" +#include "endpointvolume.h" +#include "audiopolicy.h" +#include "spatialaudioclient.h" + +#include "mmdevapi.h" + +WINE_DEFAULT_DEBUG_CHANNEL(mmdevapi); + +#define MAX_PERIODS 3 + +static UINT32 AudioObjectType_to_index(AudioObjectType type) +{ + UINT32 o = 0; + while(type){ + type >>= 1; + ++o; + } + return o - 2; +} + +typedef struct SpatialAudioImpl SpatialAudioImpl; +typedef struct SpatialAudioStreamImpl SpatialAudioStreamImpl; +typedef struct SpatialAudioObjectImpl SpatialAudioObjectImpl; + +struct SpatialAudioObjectImpl { + ISpatialAudioObject ISpatialAudioObject_iface; + LONG ref; + + SpatialAudioStreamImpl *sa_stream; + AudioObjectType type; + UINT32 static_idx; + + float *buf; + + struct list entry; +}; + +struct SpatialAudioStreamImpl { + ISpatialAudioObjectRenderStream ISpatialAudioObjectRenderStream_iface; + LONG ref; + CRITICAL_SECTION lock; + + SpatialAudioImpl *sa_client; + SpatialAudioObjectRenderStreamActivationParams params; + + IAudioClient *client; + IAudioRenderClient *render; + + UINT32 period_frames, update_frames; + WAVEFORMATEXTENSIBLE stream_fmtex; + + float *buf; + + UINT32 static_object_map[17]; + + struct list objects; +}; + +struct SpatialAudioImpl { + ISpatialAudioClient ISpatialAudioClient_iface; + IAudioFormatEnumerator IAudioFormatEnumerator_iface; + IMMDevice *mmdev; + LONG ref; + WAVEFORMATEXTENSIBLE object_fmtex; +}; + +static inline SpatialAudioObjectImpl *impl_from_ISpatialAudioObject(ISpatialAudioObject *iface) +{ + return CONTAINING_RECORD(iface, SpatialAudioObjectImpl, ISpatialAudioObject_iface); +} + +static inline SpatialAudioStreamImpl *impl_from_ISpatialAudioObjectRenderStream(ISpatialAudioObjectRenderStream *iface) +{ + return CONTAINING_RECORD(iface, SpatialAudioStreamImpl, ISpatialAudioObjectRenderStream_iface); +} + +static inline SpatialAudioImpl *impl_from_ISpatialAudioClient(ISpatialAudioClient *iface) +{ + return CONTAINING_RECORD(iface, SpatialAudioImpl, ISpatialAudioClient_iface); +} + +static inline SpatialAudioImpl *impl_from_IAudioFormatEnumerator(IAudioFormatEnumerator *iface) +{ + return CONTAINING_RECORD(iface, SpatialAudioImpl, IAudioFormatEnumerator_iface); +} + +static HRESULT WINAPI SAO_QueryInterface(ISpatialAudioObject *iface, + REFIID riid, void **ppv) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + + TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISpatialAudioObjectBase) || + IsEqualIID(riid, &IID_ISpatialAudioObject)) { + *ppv = &This->ISpatialAudioObject_iface; + } + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*ppv); + + return S_OK; +} + +static ULONG WINAPI SAO_AddRef(ISpatialAudioObject *iface) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + ULONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) new ref %u\n", This, ref); + return ref; +} + +static ULONG WINAPI SAO_Release(ISpatialAudioObject *iface) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + ULONG ref = InterlockedDecrement(&This->ref); + TRACE("(%p) new ref %u\n", This, ref); + if(!ref){ + EnterCriticalSection(&This->sa_stream->lock); + list_remove(&This->entry); + LeaveCriticalSection(&This->sa_stream->lock); + + ISpatialAudioObjectRenderStream_Release(&This->sa_stream->ISpatialAudioObjectRenderStream_iface); + heap_free(This->buf); + heap_free(This); + } + return ref; +} + +static HRESULT WINAPI SAO_GetBuffer(ISpatialAudioObject *iface, + BYTE **buffer, UINT32 *bytes) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + + TRACE("(%p)->(%p, %p)\n", This, buffer, bytes); + + EnterCriticalSection(&This->sa_stream->lock); + + if(This->sa_stream->update_frames == ~0){ + LeaveCriticalSection(&This->sa_stream->lock); + return SPTLAUDCLNT_E_OUT_OF_ORDER; + } + + *buffer = (BYTE *)This->buf; + *bytes = This->sa_stream->update_frames * + This->sa_stream->sa_client->object_fmtex.Format.nBlockAlign; + + LeaveCriticalSection(&This->sa_stream->lock); + + return S_OK; +} + +static HRESULT WINAPI SAO_SetEndOfStream(ISpatialAudioObject *iface, UINT32 frames) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + FIXME("(%p)->(%u)\n", This, frames); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAO_IsActive(ISpatialAudioObject *iface, BOOL *active) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + FIXME("(%p)->(%p)\n", This, active); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAO_GetAudioObjectType(ISpatialAudioObject *iface, + AudioObjectType *type) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + + TRACE("(%p)->(%p)\n", This, type); + + *type = This->type; + + return S_OK; +} + +static HRESULT WINAPI SAO_SetPosition(ISpatialAudioObject *iface, float x, + float y, float z) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + FIXME("(%p)->(%f, %f, %f)\n", This, x, y, z); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAO_SetVolume(ISpatialAudioObject *iface, float vol) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + FIXME("(%p)->(%f)\n", This, vol); + return E_NOTIMPL; +} + +static ISpatialAudioObjectVtbl ISpatialAudioObject_vtbl = { + SAO_QueryInterface, + SAO_AddRef, + SAO_Release, + SAO_GetBuffer, + SAO_SetEndOfStream, + SAO_IsActive, + SAO_GetAudioObjectType, + SAO_SetPosition, + SAO_SetVolume, +}; + +static HRESULT WINAPI SAORS_QueryInterface(ISpatialAudioObjectRenderStream *iface, + REFIID riid, void **ppv) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + + TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStreamBase) || + IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStream)) { + *ppv = &This->ISpatialAudioObjectRenderStream_iface; + } + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*ppv); + + return S_OK; +} + +static ULONG WINAPI SAORS_AddRef(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + ULONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) new ref %u\n", This, ref); + return ref; +} + +static ULONG WINAPI SAORS_Release(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + ULONG ref = InterlockedDecrement(&This->ref); + TRACE("(%p) new ref %u\n", This, ref); + if(!ref){ + IAudioClient_Stop(This->client); + if(This->update_frames != ~0 && This->update_frames > 0) + IAudioRenderClient_ReleaseBuffer(This->render, This->update_frames, 0); + IAudioRenderClient_Release(This->render); + IAudioClient_Release(This->client); + if(This->params.NotifyObject) + ISpatialAudioObjectRenderStreamNotify_Release(This->params.NotifyObject); + heap_free((void*)This->params.ObjectFormat); + CloseHandle(This->params.EventHandle); + DeleteCriticalSection(&This->lock); + ISpatialAudioClient_Release(&This->sa_client->ISpatialAudioClient_iface); + heap_free(This); + } + return ref; +} + +static HRESULT WINAPI SAORS_GetAvailableDynamicObjectCount( + ISpatialAudioObjectRenderStream *iface, UINT32 *count) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + FIXME("(%p)->(%p)\n", This, count); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAORS_GetService(ISpatialAudioObjectRenderStream *iface, + REFIID riid, void **service) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + FIXME("(%p)->(%s, %p)\n", This, debugstr_guid(riid), service); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAORS_Start(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + HRESULT hr; + + TRACE("(%p)->()\n", This); + + hr = IAudioClient_Start(This->client); + if(FAILED(hr)){ + WARN("IAudioClient::Start failed: %08x\n", hr); + return hr; + } + + return S_OK; +} + +static HRESULT WINAPI SAORS_Stop(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + HRESULT hr; + + TRACE("(%p)->()\n", This); + + hr = IAudioClient_Stop(This->client); + if(FAILED(hr)){ + WARN("IAudioClient::Stop failed: %08x\n", hr); + return hr; + } + + return S_OK; +} + +static HRESULT WINAPI SAORS_Reset(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + FIXME("(%p)->()\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAORS_BeginUpdatingAudioObjects(ISpatialAudioObjectRenderStream *iface, + UINT32 *dyn_count, UINT32 *frames) +{ + static BOOL fixme_once = FALSE; + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + SpatialAudioObjectImpl *object; + UINT32 pad; + HRESULT hr; + + TRACE("(%p)->(%p, %p)\n", This, dyn_count, frames); + + EnterCriticalSection(&This->lock); + + if(This->update_frames != ~0){ + LeaveCriticalSection(&This->lock); + return SPTLAUDCLNT_E_OUT_OF_ORDER; + } + + hr = IAudioClient_GetCurrentPadding(This->client, &pad); + if(FAILED(hr)){ + WARN("GetCurrentPadding failed: %08x\n", hr); + LeaveCriticalSection(&This->lock); + return hr; + } + + if(pad < This->period_frames * MAX_PERIODS){ + This->update_frames = This->period_frames * MAX_PERIODS - pad; + }else{ + This->update_frames = 0; + } + + if(This->update_frames > 0){ + hr = IAudioRenderClient_GetBuffer(This->render, This->update_frames, (BYTE **)&This->buf); + if(FAILED(hr)){ + WARN("GetBuffer failed: %08x\n", hr); + This->update_frames = ~0; + LeaveCriticalSection(&This->lock); + return hr; + } + + LIST_FOR_EACH_ENTRY(object, &This->objects, SpatialAudioObjectImpl, entry){ + memset(object->buf, 0, This->update_frames * This->sa_client->object_fmtex.Format.nBlockAlign); + } + }else if (!fixme_once){ + fixme_once = TRUE; + FIXME("Zero frame update.\n"); + } + + *dyn_count = 0; + *frames = This->update_frames; + + LeaveCriticalSection(&This->lock); + + return S_OK; +} + +static void mix_static_object(SpatialAudioStreamImpl *stream, SpatialAudioObjectImpl *object) +{ + float *in = object->buf, *out; + UINT32 i; + if(object->static_idx == ~0 || + stream->static_object_map[object->static_idx] == ~0){ + WARN("Got unmapped static object?! Not mixing. Type: 0x%x\n", object->type); + return; + } + out = stream->buf + stream->static_object_map[object->static_idx]; + for(i = 0; i < stream->update_frames; ++i){ + *out += *in; + ++in; + out += stream->stream_fmtex.Format.nChannels; + } +} + +static HRESULT WINAPI SAORS_EndUpdatingAudioObjects(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + SpatialAudioObjectImpl *object; + HRESULT hr; + + TRACE("(%p)->()\n", This); + + EnterCriticalSection(&This->lock); + + if(This->update_frames == ~0){ + LeaveCriticalSection(&This->lock); + return SPTLAUDCLNT_E_OUT_OF_ORDER; + } + + if(This->update_frames > 0){ + LIST_FOR_EACH_ENTRY(object, &This->objects, SpatialAudioObjectImpl, entry){ + if(object->type != AudioObjectType_Dynamic) + mix_static_object(This, object); + else + WARN("Don't know how to mix dynamic object yet. %p\n", object); + } + + hr = IAudioRenderClient_ReleaseBuffer(This->render, This->update_frames, 0); + if(FAILED(hr)) + WARN("ReleaseBuffer failed: %08x\n", hr); + } + + This->update_frames = ~0; + + LeaveCriticalSection(&This->lock); + + return S_OK; +} + +static HRESULT WINAPI SAORS_ActivateSpatialAudioObject(ISpatialAudioObjectRenderStream *iface, + AudioObjectType type, ISpatialAudioObject **object) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + SpatialAudioObjectImpl *obj; + + TRACE("(%p)->(0x%x, %p)\n", This, type, object); + + if(type == AudioObjectType_Dynamic) + return SPTLAUDCLNT_E_NO_MORE_OBJECTS; + + if(type & ~This->params.StaticObjectTypeMask) + return SPTLAUDCLNT_E_STATIC_OBJECT_NOT_AVAILABLE; + + LIST_FOR_EACH_ENTRY(obj, &This->objects, SpatialAudioObjectImpl, entry){ + if(obj->static_idx == AudioObjectType_to_index(type)) + return SPTLAUDCLNT_E_OBJECT_ALREADY_ACTIVE; + } + + obj = heap_alloc_zero(sizeof(*obj)); + obj->ISpatialAudioObject_iface.lpVtbl = &ISpatialAudioObject_vtbl; + obj->ref = 1; + obj->type = type; + if(type == AudioObjectType_None){ + FIXME("AudioObjectType_None not implemented yet!\n"); + obj->static_idx = ~0; + }else{ + obj->static_idx = AudioObjectType_to_index(type); + } + + obj->sa_stream = This; + SAORS_AddRef(&This->ISpatialAudioObjectRenderStream_iface); + + obj->buf = heap_alloc_zero(This->period_frames * MAX_PERIODS * This->sa_client->object_fmtex.Format.nBlockAlign); + + EnterCriticalSection(&This->lock); + + list_add_tail(&This->objects, &obj->entry); + + LeaveCriticalSection(&This->lock); + + *object = &obj->ISpatialAudioObject_iface; + + return S_OK; +} + +static ISpatialAudioObjectRenderStreamVtbl ISpatialAudioObjectRenderStream_vtbl = { + SAORS_QueryInterface, + SAORS_AddRef, + SAORS_Release, + SAORS_GetAvailableDynamicObjectCount, + SAORS_GetService, + SAORS_Start, + SAORS_Stop, + SAORS_Reset, + SAORS_BeginUpdatingAudioObjects, + SAORS_EndUpdatingAudioObjects, + SAORS_ActivateSpatialAudioObject, +}; + +static HRESULT WINAPI SAC_QueryInterface(ISpatialAudioClient *iface, REFIID riid, void **ppv) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + + TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISpatialAudioClient)) { + *ppv = &This->ISpatialAudioClient_iface; + } + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*ppv); + + return S_OK; +} + +static ULONG WINAPI SAC_AddRef(ISpatialAudioClient *iface) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + ULONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) new ref %u\n", This, ref); + return ref; +} + +static ULONG WINAPI SAC_Release(ISpatialAudioClient *iface) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + ULONG ref = InterlockedDecrement(&This->ref); + TRACE("(%p) new ref %u\n", This, ref); + if (!ref) { + IMMDevice_Release(This->mmdev); + heap_free(This); + } + return ref; +} + +static HRESULT WINAPI SAC_GetStaticObjectPosition(ISpatialAudioClient *iface, + AudioObjectType type, float *x, float *y, float *z) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(0x%x, %p, %p, %p)\n", This, type, x, y, z); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAC_GetNativeStaticObjectTypeMask(ISpatialAudioClient *iface, + AudioObjectType *mask) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(%p)\n", This, mask); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAC_GetMaxDynamicObjectCount(ISpatialAudioClient *iface, + UINT32 *value) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(%p)\n", This, value); + + *value = 0; + + return S_OK; +} + +static HRESULT WINAPI SAC_GetSupportedAudioObjectFormatEnumerator( + ISpatialAudioClient *iface, IAudioFormatEnumerator **enumerator) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + + TRACE("(%p)->(%p)\n", This, enumerator); + + *enumerator = &This->IAudioFormatEnumerator_iface; + SAC_AddRef(iface); + + return S_OK; +} + +static HRESULT WINAPI SAC_GetMaxFrameCount(ISpatialAudioClient *iface, + const WAVEFORMATEX *format, UINT32 *count) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + + /* FIXME: should get device period from the device */ + static const REFERENCE_TIME period = 100000; + + TRACE("(%p)->(%p, %p)\n", This, format, count); + + *count = MulDiv(period, format->nSamplesPerSec, 10000000) * MAX_PERIODS; + + return S_OK; +} + +static HRESULT WINAPI SAC_IsAudioObjectFormatSupported(ISpatialAudioClient *iface, + const WAVEFORMATEX *format) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(%p)\n", This, format); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAC_IsSpatialAudioStreamAvailable(ISpatialAudioClient *iface, + REFIID stream_uuid, const PROPVARIANT *info) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(%s, %p)\n", This, debugstr_guid(stream_uuid), info); + return E_NOTIMPL; +} + +static WAVEFORMATEX *clone_fmtex(const WAVEFORMATEX *src) +{ + WAVEFORMATEX *r = heap_alloc(sizeof(WAVEFORMATEX) + src->cbSize); + memcpy(r, src, sizeof(WAVEFORMATEX) + src->cbSize); + return r; +} + +static const char *debugstr_fmtex(const WAVEFORMATEX *fmt) +{ + static char buf[2048]; + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt; + snprintf(buf, sizeof(buf), "tag: 0x%x (%s), ch: %u (mask: 0x%x), rate: %u, depth: %u", + fmt->wFormatTag, debugstr_guid(&fmtex->SubFormat), + fmt->nChannels, fmtex->dwChannelMask, fmt->nSamplesPerSec, + fmt->wBitsPerSample); + }else{ + snprintf(buf, sizeof(buf), "tag: 0x%x, ch: %u, rate: %u, depth: %u", + fmt->wFormatTag, fmt->nChannels, fmt->nSamplesPerSec, + fmt->wBitsPerSample); + } + return buf; +} + +static void static_mask_to_channels(AudioObjectType static_mask, WORD *count, DWORD *mask, UINT32 *map) +{ + UINT32 out_chan = 0, map_idx = 0; + *count = 0; + *mask = 0; +#define CONVERT_MASK(f, t) \ + if(static_mask & f){ \ + *count += 1; \ + *mask |= t; \ + map[map_idx++] = out_chan++; \ + TRACE("mapping 0x%x to %u\n", f, out_chan - 1); \ + }else{ \ + map[map_idx++] = ~0; \ + } + CONVERT_MASK(AudioObjectType_FrontLeft, SPEAKER_FRONT_LEFT); + CONVERT_MASK(AudioObjectType_FrontRight, SPEAKER_FRONT_RIGHT); + CONVERT_MASK(AudioObjectType_FrontCenter, SPEAKER_FRONT_CENTER); + CONVERT_MASK(AudioObjectType_LowFrequency, SPEAKER_LOW_FREQUENCY); + CONVERT_MASK(AudioObjectType_SideLeft, SPEAKER_SIDE_LEFT); + CONVERT_MASK(AudioObjectType_SideRight, SPEAKER_SIDE_RIGHT); + CONVERT_MASK(AudioObjectType_BackLeft, SPEAKER_BACK_LEFT); + CONVERT_MASK(AudioObjectType_BackRight, SPEAKER_BACK_RIGHT); + CONVERT_MASK(AudioObjectType_TopFrontLeft, SPEAKER_TOP_FRONT_LEFT); + CONVERT_MASK(AudioObjectType_TopFrontRight, SPEAKER_TOP_FRONT_RIGHT); + CONVERT_MASK(AudioObjectType_TopBackLeft, SPEAKER_TOP_BACK_LEFT); + CONVERT_MASK(AudioObjectType_TopBackRight, SPEAKER_TOP_BACK_RIGHT); + CONVERT_MASK(AudioObjectType_BackCenter, SPEAKER_BACK_CENTER); +} + +static HRESULT activate_stream(SpatialAudioStreamImpl *stream) +{ + WAVEFORMATEXTENSIBLE *object_fmtex = (WAVEFORMATEXTENSIBLE *)stream->params.ObjectFormat; + HRESULT hr; + REFERENCE_TIME period; + + if(!(object_fmtex->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + (object_fmtex->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&object_fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)))){ + FIXME("Only float formats are supported for now\n"); + return E_INVALIDARG; + } + + hr = IMMDevice_Activate(stream->sa_client->mmdev, &IID_IAudioClient, + CLSCTX_INPROC_SERVER, NULL, (void**)&stream->client); + if(FAILED(hr)){ + WARN("Activate failed: %08x\n", hr); + return hr; + } + + hr = IAudioClient_GetDevicePeriod(stream->client, &period, NULL); + if(FAILED(hr)){ + WARN("GetDevicePeriod failed: %08x\n", hr); + IAudioClient_Release(stream->client); + return hr; + } + + stream->stream_fmtex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + static_mask_to_channels(stream->params.StaticObjectTypeMask, + &stream->stream_fmtex.Format.nChannels, &stream->stream_fmtex.dwChannelMask, + stream->static_object_map); + stream->stream_fmtex.Format.nSamplesPerSec = stream->params.ObjectFormat->nSamplesPerSec; + stream->stream_fmtex.Format.wBitsPerSample = stream->params.ObjectFormat->wBitsPerSample; + stream->stream_fmtex.Format.nBlockAlign = (stream->stream_fmtex.Format.nChannels * stream->stream_fmtex.Format.wBitsPerSample) / 8; + stream->stream_fmtex.Format.nAvgBytesPerSec = stream->stream_fmtex.Format.nSamplesPerSec * stream->stream_fmtex.Format.nBlockAlign; + stream->stream_fmtex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + stream->stream_fmtex.Samples.wValidBitsPerSample = stream->stream_fmtex.Format.wBitsPerSample; + stream->stream_fmtex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + + hr = IAudioClient_Initialize(stream->client, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, + period * MAX_PERIODS, 0, &stream->stream_fmtex.Format, NULL); + if(FAILED(hr)){ + WARN("Initialize failed: %08x\n", hr); + IAudioClient_Release(stream->client); + return hr; + } + + hr = IAudioClient_SetEventHandle(stream->client, stream->params.EventHandle); + if(FAILED(hr)){ + WARN("SetEventHandle failed: %08x\n", hr); + IAudioClient_Release(stream->client); + return hr; + } + + hr = IAudioClient_GetService(stream->client, &IID_IAudioRenderClient, (void**)&stream->render); + if(FAILED(hr)){ + WARN("GetService(AudioRenderClient) failed: %08x\n", hr); + IAudioClient_Release(stream->client); + return hr; + } + + stream->period_frames = MulDiv(period, stream->stream_fmtex.Format.nSamplesPerSec, 10000000); + + return S_OK; +} + +static HRESULT WINAPI SAC_ActivateSpatialAudioStream(ISpatialAudioClient *iface, + const PROPVARIANT *prop, REFIID riid, void **stream) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + SpatialAudioObjectRenderStreamActivationParams *params; + HRESULT hr; + + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), stream); + + if(IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStream)){ + SpatialAudioStreamImpl *obj; + + if(prop && + (prop->vt != VT_BLOB || + prop->u.blob.cbSize != sizeof(SpatialAudioObjectRenderStreamActivationParams))){ + WARN("Got invalid params\n"); + *stream = NULL; + return E_INVALIDARG; + } + + params = (SpatialAudioObjectRenderStreamActivationParams*) prop->u.blob.pBlobData; + + if(params->StaticObjectTypeMask & AudioObjectType_Dynamic){ + *stream = NULL; + return E_INVALIDARG; + } + + if(params->EventHandle == INVALID_HANDLE_VALUE || + params->EventHandle == 0){ + *stream = NULL; + return E_INVALIDARG; + } + + if(!params->ObjectFormat || + memcmp(params->ObjectFormat, &This->object_fmtex.Format, sizeof(*params->ObjectFormat) + params->ObjectFormat->cbSize)){ + *stream = NULL; + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + + obj = heap_alloc_zero(sizeof(SpatialAudioStreamImpl)); + + obj->ISpatialAudioObjectRenderStream_iface.lpVtbl = &ISpatialAudioObjectRenderStream_vtbl; + obj->ref = 1; + memcpy(&obj->params, params, sizeof(obj->params)); + + obj->update_frames = ~0; + + InitializeCriticalSection(&obj->lock); + list_init(&obj->objects); + + obj->sa_client = This; + SAC_AddRef(&This->ISpatialAudioClient_iface); + + obj->params.ObjectFormat = clone_fmtex(obj->params.ObjectFormat); + + DuplicateHandle(GetCurrentProcess(), obj->params.EventHandle, + GetCurrentProcess(), &obj->params.EventHandle, 0, FALSE, + DUPLICATE_SAME_ACCESS); + + if(obj->params.NotifyObject) + ISpatialAudioObjectRenderStreamNotify_AddRef(obj->params.NotifyObject); + + if(TRACE_ON(mmdevapi)){ + TRACE("ObjectFormat: {%s}\n", debugstr_fmtex(obj->params.ObjectFormat)); + TRACE("StaticObjectTypeMask: 0x%x\n", obj->params.StaticObjectTypeMask); + TRACE("MinDynamicObjectCount: 0x%x\n", obj->params.MinDynamicObjectCount); + TRACE("MaxDynamicObjectCount: 0x%x\n", obj->params.MaxDynamicObjectCount); + TRACE("Category: 0x%x\n", obj->params.Category); + TRACE("EventHandle: %p\n", obj->params.EventHandle); + TRACE("NotifyObject: %p\n", obj->params.NotifyObject); + } + + hr = activate_stream(obj); + if(FAILED(hr)){ + if(obj->params.NotifyObject) + ISpatialAudioObjectRenderStreamNotify_Release(obj->params.NotifyObject); + DeleteCriticalSection(&obj->lock); + heap_free((void*)obj->params.ObjectFormat); + CloseHandle(obj->params.EventHandle); + ISpatialAudioClient_Release(&obj->sa_client->ISpatialAudioClient_iface); + heap_free(obj); + *stream = NULL; + return hr; + } + + *stream = &obj->ISpatialAudioObjectRenderStream_iface; + }else{ + FIXME("Unsupported audio stream IID: %s\n", debugstr_guid(riid)); + *stream = NULL; + return E_NOTIMPL; + } + + return S_OK; +} + +static ISpatialAudioClientVtbl ISpatialAudioClient_vtbl = { + SAC_QueryInterface, + SAC_AddRef, + SAC_Release, + SAC_GetStaticObjectPosition, + SAC_GetNativeStaticObjectTypeMask, + SAC_GetMaxDynamicObjectCount, + SAC_GetSupportedAudioObjectFormatEnumerator, + SAC_GetMaxFrameCount, + SAC_IsAudioObjectFormatSupported, + SAC_IsSpatialAudioStreamAvailable, + SAC_ActivateSpatialAudioStream, +}; + +static HRESULT WINAPI SAOFE_QueryInterface(IAudioFormatEnumerator *iface, + REFIID riid, void **ppvObject) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + return SAC_QueryInterface(&This->ISpatialAudioClient_iface, riid, ppvObject); +} + +static ULONG WINAPI SAOFE_AddRef(IAudioFormatEnumerator *iface) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + return SAC_AddRef(&This->ISpatialAudioClient_iface); +} + +static ULONG WINAPI SAOFE_Release(IAudioFormatEnumerator *iface) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + return SAC_Release(&This->ISpatialAudioClient_iface); +} + +static HRESULT WINAPI SAOFE_GetCount(IAudioFormatEnumerator *iface, UINT32 *count) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + + TRACE("(%p)->(%p)\n", This, count); + + *count = 1; + + return S_OK; +} + +static HRESULT WINAPI SAOFE_GetFormat(IAudioFormatEnumerator *iface, + UINT32 index, WAVEFORMATEX **format) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + + TRACE("(%p)->(%u, %p)\n", This, index, format); + + if(index > 0) + return E_INVALIDARG; + + *format = &This->object_fmtex.Format; + + return S_OK; +} + +static IAudioFormatEnumeratorVtbl IAudioFormatEnumerator_vtbl = { + SAOFE_QueryInterface, + SAOFE_AddRef, + SAOFE_Release, + SAOFE_GetCount, + SAOFE_GetFormat, +}; + +HRESULT SpatialAudioClient_Create(IMMDevice *mmdev, ISpatialAudioClient **out) +{ + SpatialAudioImpl *obj; + IAudioClient *aclient; + WAVEFORMATEX *closest; + HRESULT hr; + + obj = heap_alloc_zero(sizeof(*obj)); + + obj->ref = 1; + obj->ISpatialAudioClient_iface.lpVtbl = &ISpatialAudioClient_vtbl; + obj->IAudioFormatEnumerator_iface.lpVtbl = &IAudioFormatEnumerator_vtbl; + + obj->object_fmtex.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + obj->object_fmtex.Format.nChannels = 1; + obj->object_fmtex.Format.nSamplesPerSec = 48000; + obj->object_fmtex.Format.wBitsPerSample = sizeof(float) * 8; + obj->object_fmtex.Format.nBlockAlign = (obj->object_fmtex.Format.nChannels * obj->object_fmtex.Format.wBitsPerSample) / 8; + obj->object_fmtex.Format.nAvgBytesPerSec = obj->object_fmtex.Format.nSamplesPerSec * obj->object_fmtex.Format.nBlockAlign; + obj->object_fmtex.Format.cbSize = 0; + + hr = IMMDevice_Activate(mmdev, &IID_IAudioClient, + CLSCTX_INPROC_SERVER, NULL, (void**)&aclient); + if(FAILED(hr)){ + WARN("Activate failed: %08x\n", hr); + heap_free(obj); + return hr; + } + + hr = IAudioClient_IsFormatSupported(aclient, AUDCLNT_SHAREMODE_SHARED, &obj->object_fmtex.Format, &closest); + + IAudioClient_Release(aclient); + + if(hr == S_FALSE){ + if(sizeof(WAVEFORMATEX) + closest->cbSize > sizeof(obj->object_fmtex)){ + ERR("Returned format too large: %s\n", debugstr_fmtex(closest)); + CoTaskMemFree(closest); + heap_free(obj); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + }else if(!((closest->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + (closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&((WAVEFORMATEXTENSIBLE *)closest)->SubFormat, + &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))) && + closest->wBitsPerSample == 32)){ + ERR("Returned format not 32-bit float: %s\n", debugstr_fmtex(closest)); + CoTaskMemFree(closest); + heap_free(obj); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + WARN("The audio stack doesn't support 48kHz 32bit float. Using the closest match. Audio may be glitchy. %s\n", debugstr_fmtex(closest)); + memcpy(&obj->object_fmtex, + closest, + sizeof(WAVEFORMATEX) + closest->cbSize); + CoTaskMemFree(closest); + } else if(hr != S_OK){ + WARN("Checking supported formats failed: %08x\n", hr); + heap_free(obj); + return hr; + } + + obj->mmdev = mmdev; + IMMDevice_AddRef(mmdev); + + *out = &obj->ISpatialAudioClient_iface; + + return S_OK; +} diff --git a/include/spatialaudioclient.idl b/include/spatialaudioclient.idl index 16a1541fd1d..08c84965566 100644 --- a/include/spatialaudioclient.idl +++ b/include/spatialaudioclient.idl @@ -43,6 +43,47 @@ typedef [v1_enum] enum AudioObjectType AudioObjectType_BackCenter = 0x00020000, } AudioObjectType; +cpp_quote("#define SPTLAUDCLNT_E_DESTROYED AUDCLNT_ERR(0x100)") +cpp_quote("#define SPTLAUDCLNT_E_OUT_OF_ORDER AUDCLNT_ERR(0x101)") +cpp_quote("#define SPTLAUDCLNT_E_RESOURCES_INVALIDATED AUDCLNT_ERR(0x102)") +cpp_quote("#define SPTLAUDCLNT_E_NO_MORE_OBJECTS AUDCLNT_ERR(0x103)") +cpp_quote("#define SPTLAUDCLNT_E_PROPERTY_NOT_SUPPORTED AUDCLNT_ERR(0x104)") +cpp_quote("#define SPTLAUDCLNT_E_ERRORS_IN_OBJECT_CALLS AUDCLNT_ERR(0x105)") +cpp_quote("#define SPTLAUDCLNT_E_METADATA_FORMAT_NOT_SUPPORTED AUDCLNT_ERR(0x106)") +cpp_quote("#define SPTLAUDCLNT_E_STREAM_NOT_AVAILABLE AUDCLNT_ERR(0x107)") +cpp_quote("#define SPTLAUDCLNT_E_INVALID_LICENSE AUDCLNT_ERR(0x108)") +cpp_quote("#define SPTLAUDCLNT_E_STREAM_NOT_STOPPED AUDCLNT_ERR(0x10a)") +cpp_quote("#define SPTLAUDCLNT_E_STATIC_OBJECT_NOT_AVAILABLE AUDCLNT_ERR(0x10b)") +cpp_quote("#define SPTLAUDCLNT_E_OBJECT_ALREADY_ACTIVE AUDCLNT_ERR(0x10c)") +cpp_quote("#define SPTLAUDCLNT_E_INTERNAL AUDCLNT_ERR(0x10d)") + +interface ISpatialAudioObjectRenderStreamBase; + +[ + object, + uuid(dddf83e6-68d7-4c70-883f-a1836afb4a50), + pointer_default(unique), + local +] +interface ISpatialAudioObjectRenderStreamNotify : IUnknown +{ + HRESULT OnAvailableDynamicObjectCountChange( + [in] ISpatialAudioObjectRenderStreamBase *stream, + [in] LONGLONG deadline, + [in] UINT32 object_count); +} + +typedef struct tagSpatialAudioObjectRenderStreamActivationParams +{ + const WAVEFORMATEX *ObjectFormat; + AudioObjectType StaticObjectTypeMask; + UINT32 MinDynamicObjectCount; + UINT32 MaxDynamicObjectCount; + AUDIO_STREAM_CATEGORY Category; + HANDLE EventHandle; + ISpatialAudioObjectRenderStreamNotify *NotifyObject; +} SpatialAudioObjectRenderStreamActivationParams; + [ object, uuid(dcdaa858-895a-4a22-a5eb-67bda506096d), @@ -98,3 +139,83 @@ interface ISpatialAudioClient : IUnknown [in] REFIID riid, [out, iid_is(riid)] void **stream); } + +[ + object, + uuid(cce0b8f2-8d4d-4efb-a8cf-3d6ecf1c30e0), + pointer_default(unique), + local +] +interface ISpatialAudioObjectBase : IUnknown +{ + HRESULT GetBuffer( + [out] BYTE **buffer, + [out] UINT32 *bytes); + + HRESULT SetEndOfStream( + [in] UINT32 frames); + + HRESULT IsActive( + [out] BOOL *active); + + HRESULT GetAudioObjectType( + [out] AudioObjectType *type); +} + +[ + object, + uuid(dde28967-521b-46e5-8f00-bd6f2bc8ab1d), + pointer_default(unique), + local +] +interface ISpatialAudioObject : ISpatialAudioObjectBase +{ + HRESULT SetPosition( + [in] float x, + [in] float y, + [in] float z); + + HRESULT SetVolume( + [in] float vol); +} + +[ + object, + uuid(feaaf403-c1d8-450d-aa05-e0ccee7502a8), + pointer_default(unique), + local +] +interface ISpatialAudioObjectRenderStreamBase : IUnknown +{ + HRESULT GetAvailableDynamicObjectCount( + [out] UINT32 *count); + + HRESULT GetService( + [in] REFIID riid, + [out] void **service); + + HRESULT Start(); + + HRESULT Stop(); + + HRESULT Reset(); + + HRESULT BeginUpdatingAudioObjects( + [out] UINT32 *count, + [out] UINT32 *frames); + + HRESULT EndUpdatingAudioObjects(); +} + +[ + object, + uuid(bab5f473-b423-477b-85f5-b5a332a04153), + pointer_default(unique), + local +] +interface ISpatialAudioObjectRenderStream : ISpatialAudioObjectRenderStreamBase +{ + HRESULT ActivateSpatialAudioObject( + [in] AudioObjectType type, + [out] ISpatialAudioObject **object); +}