From: Andrew Eikum aeikum@codeweavers.com
This makes car radio / ambient noises audible in Cyberpunk 2077.
Signed-off-by: Arkadiusz Hiler ahiler@codeweavers.com --- dlls/mmdevapi/Makefile.in | 3 +- dlls/mmdevapi/audiovolume.c | 1 + dlls/mmdevapi/devenum.c | 5 + dlls/mmdevapi/main.c | 1 + dlls/mmdevapi/mmdevapi.h | 1 + dlls/mmdevapi/spatialaudio.c | 988 +++++++++++++++++++++++++++++++++ include/spatialaudioclient.idl | 121 ++++ 7 files changed, 1119 insertions(+), 1 deletion(-) create mode 100644 dlls/mmdevapi/spatialaudio.c
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 <stdarg.h> + +#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); +}
Signed-off-by: Arkadiusz Hiler ahiler@codeweavers.com --- dlls/mmdevapi/tests/Makefile.in | 3 +- dlls/mmdevapi/tests/mmdevenum.c | 1 + dlls/mmdevapi/tests/spatialaudio.c | 445 +++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 dlls/mmdevapi/tests/spatialaudio.c
diff --git a/dlls/mmdevapi/tests/Makefile.in b/dlls/mmdevapi/tests/Makefile.in index 0f17ea31923..062ad413922 100644 --- a/dlls/mmdevapi/tests/Makefile.in +++ b/dlls/mmdevapi/tests/Makefile.in @@ -6,4 +6,5 @@ C_SRCS = \ dependency.c \ mmdevenum.c \ propstore.c \ - render.c + render.c \ + spatialaudio.c diff --git a/dlls/mmdevapi/tests/mmdevenum.c b/dlls/mmdevapi/tests/mmdevenum.c index 350d6474a46..1fd1c7d4aca 100644 --- a/dlls/mmdevapi/tests/mmdevenum.c +++ b/dlls/mmdevapi/tests/mmdevenum.c @@ -24,6 +24,7 @@ #include "endpointvolume.h" #include "mmdeviceapi.h" #include "audioclient.h" +#include "spatialaudioclient.h" #include "audiopolicy.h" #include "dshow.h" #include "dsound.h" diff --git a/dlls/mmdevapi/tests/spatialaudio.c b/dlls/mmdevapi/tests/spatialaudio.c new file mode 100644 index 00000000000..f2612499350 --- /dev/null +++ b/dlls/mmdevapi/tests/spatialaudio.c @@ -0,0 +1,445 @@ +/* + * Copyright 2021 Arkadiusz Hiler 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 + */ + +#include <math.h> +#include <stdio.h> + +#include "wine/test.h" + +#define COBJMACROS + +#ifdef STANDALONE +#include "initguid.h" +#endif + +#include "mmdeviceapi.h" +#include "spatialaudioclient.h" + +static IMMDeviceEnumerator *mme = NULL; +static IMMDevice *dev = NULL; +static ISpatialAudioClient *sac = NULL; +static UINT32 max_dyn_count; +static HANDLE event; +static WAVEFORMATEX format; + +static void test_formats(void) +{ + HRESULT hr; + IAudioFormatEnumerator *afe; + UINT32 format_count = 0; + WAVEFORMATEX *fmt = NULL; + + hr = ISpatialAudioClient_GetSupportedAudioObjectFormatEnumerator(sac, &afe); + ok(hr == S_OK, "Getting format enumerator failed: 0x%08x\n", hr); + + hr = IAudioFormatEnumerator_GetCount(afe, &format_count); + ok(hr == S_OK, "Getting format count failed: 0x%08x\n", hr); + ok(format_count == 1, "Got wrong format count, expected 1 got %u\n", format_count); + + hr = IAudioFormatEnumerator_GetFormat(afe, 0, &fmt); + ok(hr == S_OK, "Getting format failed: 0x%08x\n", hr); + ok(fmt != NULL, "Expected to get non-NULL format\n"); + + ok(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT, "Wrong format, expected WAVE_FORMAT_IEEE_FLOAT got %hx\n", fmt->wFormatTag); + ok(fmt->nChannels == 1, "Wrong number of channels, expected 1 got %hu\n", fmt->nChannels); + ok(fmt->nSamplesPerSec == 48000, "Wrong sample ret, expected 48000 got %u\n", fmt->nSamplesPerSec); + ok(fmt->wBitsPerSample == 32, "Wrong bits per sample, expected 32 got %hu\n", fmt->wBitsPerSample); + ok(fmt->nBlockAlign == 4, "Wrong block align, expected 4 got %hu\n", fmt->nBlockAlign); + ok(fmt->nAvgBytesPerSec == 192000, "Wrong avg bytes per sec, expected 192000 got %u\n", fmt->nAvgBytesPerSec); + ok(fmt->cbSize == 0, "Wrong cbSize for simple format, expected 0, got %hu\n", fmt->cbSize); + + memcpy(&format, fmt, sizeof(format)); + + IAudioFormatEnumerator_Release(afe); +} + +static void fill_activation_params(SpatialAudioObjectRenderStreamActivationParams *activation_params) +{ + activation_params->StaticObjectTypeMask = \ + AudioObjectType_FrontLeft | + AudioObjectType_FrontRight | + AudioObjectType_FrontCenter | + AudioObjectType_LowFrequency | + AudioObjectType_SideLeft | + AudioObjectType_SideRight | + AudioObjectType_BackLeft | + AudioObjectType_BackRight | + AudioObjectType_TopFrontLeft | + AudioObjectType_TopFrontRight | + AudioObjectType_TopBackLeft | + AudioObjectType_TopBackRight; + + activation_params->MinDynamicObjectCount = 0; + activation_params->MaxDynamicObjectCount = 0; + activation_params->Category = AudioCategory_GameEffects; + activation_params->EventHandle = event; + activation_params->NotifyObject = NULL; + + activation_params->ObjectFormat = &format; +} + +typedef struct NotifyObject +{ + ISpatialAudioObjectRenderStreamNotify ISpatialAudioObjectRenderStreamNotify_iface; + LONG ref; +} NotifyObject; + +static WINAPI HRESULT notifyobj_QueryInterface( + ISpatialAudioObjectRenderStreamNotify *This, + REFIID riid, + void **ppvObject) +{ + return S_OK; +} + +static WINAPI ULONG notifyobj_AddRef( + ISpatialAudioObjectRenderStreamNotify *This) +{ + NotifyObject *obj = CONTAINING_RECORD(This, NotifyObject, ISpatialAudioObjectRenderStreamNotify_iface); + ULONG ref = InterlockedIncrement(&obj->ref); + return ref; +} + +static WINAPI ULONG notifyobj_Release( + ISpatialAudioObjectRenderStreamNotify *This) +{ + NotifyObject *obj = CONTAINING_RECORD(This, NotifyObject, ISpatialAudioObjectRenderStreamNotify_iface); + ULONG ref = InterlockedDecrement(&obj->ref); + return ref; +} + +static WINAPI HRESULT notifyobj_OnAvailableDynamicObjectCountChange( + ISpatialAudioObjectRenderStreamNotify *This, + ISpatialAudioObjectRenderStreamBase *stream, + LONGLONG deadline, + UINT32 object_count) +{ + ok(FALSE, "Expected to never be notified of dynamic object count change\n"); + return S_OK; +} + +static const ISpatialAudioObjectRenderStreamNotifyVtbl notifyobjvtbl = +{ + notifyobj_QueryInterface, + notifyobj_AddRef, + notifyobj_Release, + notifyobj_OnAvailableDynamicObjectCountChange +}; + +static void test_stream_activation(void) +{ + HRESULT hr; + WAVEFORMATEX wrong_format; + ISpatialAudioObjectRenderStream *sas = NULL; + + SpatialAudioObjectRenderStreamActivationParams activation_params; + PROPVARIANT activation_params_prop; + NotifyObject notify_object; + + PropVariantInit(&activation_params_prop); + activation_params_prop.vt = VT_BLOB; + activation_params_prop.blob.cbSize = sizeof(activation_params); + activation_params_prop.blob.pBlobData = (BYTE*) &activation_params; + + /* correct params */ + fill_activation_params(&activation_params); + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08x\n", hr); + ok(ISpatialAudioObjectRenderStream_Release(sas) == 0, "Expected to release the last reference\n"); + + /* event handle */ + fill_activation_params(&activation_params); + activation_params.EventHandle = NULL; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == E_INVALIDARG, "Expected lack of no EventHandle to be invalid: 0x%08x\n", hr); + ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n"); + + activation_params.EventHandle = INVALID_HANDLE_VALUE; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == E_INVALIDARG, "Expected INVALID_HANDLE_VALUE to be invalid: 0x%08x\n", hr); + ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n"); + + /* must use only queried sample rate */ + fill_activation_params(&activation_params); + memcpy(&wrong_format, &format, sizeof(format)); + activation_params.ObjectFormat = &wrong_format; + wrong_format.nSamplesPerSec = 44100; + wrong_format.nAvgBytesPerSec = wrong_format.nSamplesPerSec * wrong_format.nBlockAlign; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == AUDCLNT_E_UNSUPPORTED_FORMAT, "Expected format to be unsupported: 0x%08x\n", hr); + ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n"); + + /* dynamic objects are not supported */ + if (max_dyn_count == 0) + { + fill_activation_params(&activation_params); + activation_params.StaticObjectTypeMask |= AudioObjectType_Dynamic; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == E_INVALIDARG, "Expected dynamic objects type be invalid: 0x%08x\n", hr); + ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n"); + } + + activation_params.MinDynamicObjectCount = max_dyn_count + 1; + activation_params.MaxDynamicObjectCount = max_dyn_count + 1; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + if (max_dyn_count) + ok(hr == AUDCLNT_E_UNSUPPORTED_FORMAT, "Expected dynamic object count exceeding max to be unsupported: 0x%08x\n", hr); + else + ok(hr == E_INVALIDARG, "Expected setting dynamic object count to be invalid: 0x%08x\n", hr); + + /* ISpatialAudioObjectRenderStreamNotify */ + fill_activation_params(&activation_params); + notify_object.ISpatialAudioObjectRenderStreamNotify_iface.lpVtbl = ¬ifyobjvtbl; + notify_object.ref = 0; + activation_params.NotifyObject = ¬ify_object.ISpatialAudioObjectRenderStreamNotify_iface; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08x\n", hr); + ok(notify_object.ref == 1, "Expected to get increased NotifyObject's ref count\n"); + ok(ISpatialAudioObjectRenderStream_Release(sas) == 0, "Expected to release the last reference\n"); + ok(notify_object.ref == 0, "Expected to get lowered NotifyObject's ref count\n"); +} + +static void test_audio_object_activation(void) +{ + HRESULT hr; + BOOL is_active; + ISpatialAudioObjectRenderStream *sas = NULL; + ISpatialAudioObject *sao1, *sao2; + + SpatialAudioObjectRenderStreamActivationParams activation_params; + PROPVARIANT activation_params_prop; + + PropVariantInit(&activation_params_prop); + activation_params_prop.vt = VT_BLOB; + activation_params_prop.blob.cbSize = sizeof(activation_params); + activation_params_prop.blob.pBlobData = (BYTE*) &activation_params; + + fill_activation_params(&activation_params); + activation_params.StaticObjectTypeMask &= ~AudioObjectType_FrontRight; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao1); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr); + hr = ISpatialAudioObject_IsActive(sao1, &is_active); + todo_wine ok(hr == S_OK, "Failed to check if spatial audio object is active: 0x%08x\n", hr); + if (hr == S_OK) + ok(is_active, "Expected spaital audio object to be active\n"); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao2); + ok(hr == SPTLAUDCLNT_E_OBJECT_ALREADY_ACTIVE, "Expected audio object to be already active: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontRight, &sao2); + ok(hr == SPTLAUDCLNT_E_STATIC_OBJECT_NOT_AVAILABLE, "Expected static object to be not available: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_Dynamic, &sao2); + ok(hr == SPTLAUDCLNT_E_NO_MORE_OBJECTS, "Expected to not have no more dynamic objects: 0x%08x\n", hr); + + ISpatialAudioObject_Release(sao1); + ISpatialAudioObjectRenderStream_Release(sas); +} + +static BOOL is_buffer_zeroed(const BYTE *buffer, UINT32 buffer_length) +{ + UINT32 i; + + for (i = 0; i < buffer_length; i++) + { + if (buffer[i] != 0) + return FALSE; + } + + return TRUE; +} + +static void test_audio_object_buffers(void) +{ + HRESULT hr; + ISpatialAudioObjectRenderStream *sas = NULL; + ISpatialAudioObject *sao[4]; + UINT32 dyn_object_count, frame_count, buffer_length; + BYTE *buffer; + INT i; + + SpatialAudioObjectRenderStreamActivationParams activation_params; + PROPVARIANT activation_params_prop; + + PropVariantInit(&activation_params_prop); + activation_params_prop.vt = VT_BLOB; + activation_params_prop.blob.cbSize = sizeof(activation_params); + activation_params_prop.blob.pBlobData = (BYTE*) &activation_params; + + fill_activation_params(&activation_params); + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao[0]); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_Start(sas); + ok(hr == S_OK, "Failed to activate spatial audio render stream: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontRight, &sao[1]); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr); + + hr = WaitForSingleObject(event, 200); + ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_SideLeft, &sao[2]); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count); + ok(hr == S_OK, "Failed to beging updating audio objects: 0x%08x\n", hr); + ok(dyn_object_count == 0, "Unexpected dynamic objects\n"); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_SideRight, &sao[3]); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr); + + for (i = 0; i < ARRAYSIZE(sao); i++) + { + hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length); + ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08x\n", hr); + ok(buffer != NULL, "Expected to get a non-NULL buffer\n"); + ok(buffer_length == frame_count * format.wBitsPerSample / 8, "Expected buffer length to be sample_size * frame_count = %hu but got %u\n", + frame_count * format.wBitsPerSample / 8, buffer_length); + ok(is_buffer_zeroed(buffer, buffer_length), "Expected audio object's buffer to be zeroed\n"); + } + + hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas); + ok(hr == S_OK, "Failed to end updating audio objects: 0x%08x\n", hr); + + hr = WaitForSingleObject(event, 200); + ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count); + ok(hr == S_OK, "Failed to beging updating audio objects: 0x%08x\n", hr); + ok(dyn_object_count == 0, "Unexpected dynamic objects\n"); + + /* one more iteration but not with every object */ + for (i = 0; i < ARRAYSIZE(sao) - 1; i++) + { + hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length); + ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08x\n", hr); + ok(buffer != NULL, "Expected to get a non-NULL buffer\n"); + ok(buffer_length == frame_count * format.wBitsPerSample / 8, "Expected buffer length to be sample_size * frame_count = %hu but got %u\n", + frame_count * format.wBitsPerSample / 8, buffer_length); + ok(is_buffer_zeroed(buffer, buffer_length), "Expected audio object's buffer to be zeroed\n"); + } + + hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas); + ok(hr == S_OK, "Failed to end updating audio objects: 0x%08x\n", hr); + + /* ending the stream */ + hr = ISpatialAudioObject_SetEndOfStream(sao[0], 0); + todo_wine ok(hr == SPTLAUDCLNT_E_OUT_OF_ORDER, "Expected that ending the stream at this point won't be allowed: 0x%08x\n", hr); + + hr = WaitForSingleObject(event, 200); + ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08x\n", hr); + + hr = ISpatialAudioObject_SetEndOfStream(sao[0], 0); + todo_wine ok(hr == SPTLAUDCLNT_E_OUT_OF_ORDER, "Expected that ending the stream at this point won't be allowed: 0x%08x\n", hr); + + hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count); + ok(hr == S_OK, "Failed to beging updating audio objects: 0x%08x\n", hr); + ok(dyn_object_count == 0, "Unexpected dynamic objects\n"); + + /* expect the object that was not updated last cycle to be invalidated */ + hr = ISpatialAudioObject_GetBuffer(sao[ARRAYSIZE(sao) - 1], &buffer, &buffer_length); + todo_wine ok(hr == SPTLAUDCLNT_E_RESOURCES_INVALIDATED, "Expected audio object to be invalidated: 0x%08x\n", hr); + + for (i = 0; i < ARRAYSIZE(sao) - 1; i++) + { + hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length); + ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08x\n", hr); + + hr = ISpatialAudioObject_SetEndOfStream(sao[i], 0); + todo_wine ok(hr == S_OK, "Failed to end the stream: 0x%08x\n", hr); + + hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length); + todo_wine ok(hr == SPTLAUDCLNT_E_RESOURCES_INVALIDATED, "Expected audio object to be invalidated: 0x%08x\n", hr); + } + + hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas); + ok(hr == S_OK, "Failed to end updating audio objects: 0x%08x\n", hr); + + for (i = 0; i < ARRAYSIZE(sao); i++) + { + ISpatialAudioObject_Release(sao[i]); + } + + ISpatialAudioObjectRenderStream_Release(sas); +} + +START_TEST(spatialaudio) +{ + HRESULT hr; + + event = CreateEventA(NULL, FALSE, FALSE, "spatial-audio-test-prog-event"); + ok(event != NULL, "Failed to create event, last error: 0x%08x\n", GetLastError()); + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme); + if (FAILED(hr)) + { + skip("mmdevapi not available: 0x%08x\n", hr); + goto cleanup; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eRender, eMultimedia, &dev); + ok(hr == S_OK || hr == E_NOTFOUND, "GetDefaultAudioEndpoint failed: 0x%08x\n", hr); + if (hr != S_OK || !dev) + { + if (hr == E_NOTFOUND) + skip("No sound card available\n"); + else + skip("GetDefaultAudioEndpoint returns 0x%08x\n", hr); + goto cleanup; + } + + hr = IMMDevice_Activate(dev, &IID_ISpatialAudioClient, CLSCTX_INPROC_SERVER, NULL, (void**)&sac); + ok(hr == S_OK || hr == E_NOINTERFACE, "ISpatialAudioClient Activation failed: 0x%08x\n", hr); + if (hr != S_OK || !dev) + { + if (hr == E_NOINTERFACE) + skip("ISpatialAudioClient interface not found\n"); + else + skip("ISpatialAudioClient Activation returns 0x%08x\n", hr); + goto cleanup; + } + + hr = ISpatialAudioClient_GetMaxDynamicObjectCount(sac, &max_dyn_count); + ok(hr == S_OK, "Failed to get max dynamic object count: 0x%08x\n", hr); + + /* that's the default, after manually enabling Windows Sonic it's possible to have max_dyn_count > 0 */ + /* ok(max_dyn_count == 0, "expected max dynamic object count to be 0 got %u\n", max_dyn_count); */ + + test_formats(); + test_stream_activation(); + test_audio_object_activation(); + test_audio_object_buffers(); + + ISpatialAudioClient_Release(sac); + +cleanup: + if (dev) + IMMDevice_Release(dev); + if (mme) + IMMDeviceEnumerator_Release(mme); + CoUninitialize(); + CloseHandle(event); +}
Signed-off-by: Andrew Eikum aeikum@codeweavers.com
On Thu, Feb 04, 2021 at 04:09:57PM +0200, Arkadiusz Hiler wrote:
Signed-off-by: Arkadiusz Hiler ahiler@codeweavers.com
dlls/mmdevapi/tests/Makefile.in | 3 +- dlls/mmdevapi/tests/mmdevenum.c | 1 + dlls/mmdevapi/tests/spatialaudio.c | 445 +++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 dlls/mmdevapi/tests/spatialaudio.c
diff --git a/dlls/mmdevapi/tests/Makefile.in b/dlls/mmdevapi/tests/Makefile.in index 0f17ea31923..062ad413922 100644 --- a/dlls/mmdevapi/tests/Makefile.in +++ b/dlls/mmdevapi/tests/Makefile.in @@ -6,4 +6,5 @@ C_SRCS = \ dependency.c \ mmdevenum.c \ propstore.c \
- render.c
- render.c \
- spatialaudio.c
diff --git a/dlls/mmdevapi/tests/mmdevenum.c b/dlls/mmdevapi/tests/mmdevenum.c index 350d6474a46..1fd1c7d4aca 100644 --- a/dlls/mmdevapi/tests/mmdevenum.c +++ b/dlls/mmdevapi/tests/mmdevenum.c @@ -24,6 +24,7 @@ #include "endpointvolume.h" #include "mmdeviceapi.h" #include "audioclient.h" +#include "spatialaudioclient.h" #include "audiopolicy.h" #include "dshow.h" #include "dsound.h" diff --git a/dlls/mmdevapi/tests/spatialaudio.c b/dlls/mmdevapi/tests/spatialaudio.c new file mode 100644 index 00000000000..f2612499350 --- /dev/null +++ b/dlls/mmdevapi/tests/spatialaudio.c @@ -0,0 +1,445 @@ +/*
- Copyright 2021 Arkadiusz Hiler 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
- */
+#include <math.h> +#include <stdio.h>
+#include "wine/test.h"
+#define COBJMACROS
+#ifdef STANDALONE +#include "initguid.h" +#endif
+#include "mmdeviceapi.h" +#include "spatialaudioclient.h"
+static IMMDeviceEnumerator *mme = NULL; +static IMMDevice *dev = NULL; +static ISpatialAudioClient *sac = NULL; +static UINT32 max_dyn_count; +static HANDLE event; +static WAVEFORMATEX format;
+static void test_formats(void) +{
- HRESULT hr;
- IAudioFormatEnumerator *afe;
- UINT32 format_count = 0;
- WAVEFORMATEX *fmt = NULL;
- hr = ISpatialAudioClient_GetSupportedAudioObjectFormatEnumerator(sac, &afe);
- ok(hr == S_OK, "Getting format enumerator failed: 0x%08x\n", hr);
- hr = IAudioFormatEnumerator_GetCount(afe, &format_count);
- ok(hr == S_OK, "Getting format count failed: 0x%08x\n", hr);
- ok(format_count == 1, "Got wrong format count, expected 1 got %u\n", format_count);
- hr = IAudioFormatEnumerator_GetFormat(afe, 0, &fmt);
- ok(hr == S_OK, "Getting format failed: 0x%08x\n", hr);
- ok(fmt != NULL, "Expected to get non-NULL format\n");
- ok(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT, "Wrong format, expected WAVE_FORMAT_IEEE_FLOAT got %hx\n", fmt->wFormatTag);
- ok(fmt->nChannels == 1, "Wrong number of channels, expected 1 got %hu\n", fmt->nChannels);
- ok(fmt->nSamplesPerSec == 48000, "Wrong sample ret, expected 48000 got %u\n", fmt->nSamplesPerSec);
- ok(fmt->wBitsPerSample == 32, "Wrong bits per sample, expected 32 got %hu\n", fmt->wBitsPerSample);
- ok(fmt->nBlockAlign == 4, "Wrong block align, expected 4 got %hu\n", fmt->nBlockAlign);
- ok(fmt->nAvgBytesPerSec == 192000, "Wrong avg bytes per sec, expected 192000 got %u\n", fmt->nAvgBytesPerSec);
- ok(fmt->cbSize == 0, "Wrong cbSize for simple format, expected 0, got %hu\n", fmt->cbSize);
- memcpy(&format, fmt, sizeof(format));
- IAudioFormatEnumerator_Release(afe);
+}
+static void fill_activation_params(SpatialAudioObjectRenderStreamActivationParams *activation_params) +{
- activation_params->StaticObjectTypeMask = \
AudioObjectType_FrontLeft |
AudioObjectType_FrontRight |
AudioObjectType_FrontCenter |
AudioObjectType_LowFrequency |
AudioObjectType_SideLeft |
AudioObjectType_SideRight |
AudioObjectType_BackLeft |
AudioObjectType_BackRight |
AudioObjectType_TopFrontLeft |
AudioObjectType_TopFrontRight |
AudioObjectType_TopBackLeft |
AudioObjectType_TopBackRight;
- activation_params->MinDynamicObjectCount = 0;
- activation_params->MaxDynamicObjectCount = 0;
- activation_params->Category = AudioCategory_GameEffects;
- activation_params->EventHandle = event;
- activation_params->NotifyObject = NULL;
- activation_params->ObjectFormat = &format;
+}
+typedef struct NotifyObject +{
- ISpatialAudioObjectRenderStreamNotify ISpatialAudioObjectRenderStreamNotify_iface;
- LONG ref;
+} NotifyObject;
+static WINAPI HRESULT notifyobj_QueryInterface(
ISpatialAudioObjectRenderStreamNotify *This,
REFIID riid,
void **ppvObject)
+{
- return S_OK;
+}
+static WINAPI ULONG notifyobj_AddRef(
ISpatialAudioObjectRenderStreamNotify *This)
+{
- NotifyObject *obj = CONTAINING_RECORD(This, NotifyObject, ISpatialAudioObjectRenderStreamNotify_iface);
- ULONG ref = InterlockedIncrement(&obj->ref);
- return ref;
+}
+static WINAPI ULONG notifyobj_Release(
ISpatialAudioObjectRenderStreamNotify *This)
+{
- NotifyObject *obj = CONTAINING_RECORD(This, NotifyObject, ISpatialAudioObjectRenderStreamNotify_iface);
- ULONG ref = InterlockedDecrement(&obj->ref);
- return ref;
+}
+static WINAPI HRESULT notifyobj_OnAvailableDynamicObjectCountChange(
ISpatialAudioObjectRenderStreamNotify *This,
ISpatialAudioObjectRenderStreamBase *stream,
LONGLONG deadline,
UINT32 object_count)
+{
- ok(FALSE, "Expected to never be notified of dynamic object count change\n");
- return S_OK;
+}
+static const ISpatialAudioObjectRenderStreamNotifyVtbl notifyobjvtbl = +{
- notifyobj_QueryInterface,
- notifyobj_AddRef,
- notifyobj_Release,
- notifyobj_OnAvailableDynamicObjectCountChange
+};
+static void test_stream_activation(void) +{
- HRESULT hr;
- WAVEFORMATEX wrong_format;
- ISpatialAudioObjectRenderStream *sas = NULL;
- SpatialAudioObjectRenderStreamActivationParams activation_params;
- PROPVARIANT activation_params_prop;
- NotifyObject notify_object;
- PropVariantInit(&activation_params_prop);
- activation_params_prop.vt = VT_BLOB;
- activation_params_prop.blob.cbSize = sizeof(activation_params);
- activation_params_prop.blob.pBlobData = (BYTE*) &activation_params;
- /* correct params */
- fill_activation_params(&activation_params);
- hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
- ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08x\n", hr);
- ok(ISpatialAudioObjectRenderStream_Release(sas) == 0, "Expected to release the last reference\n");
- /* event handle */
- fill_activation_params(&activation_params);
- activation_params.EventHandle = NULL;
- hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
- ok(hr == E_INVALIDARG, "Expected lack of no EventHandle to be invalid: 0x%08x\n", hr);
- ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n");
- activation_params.EventHandle = INVALID_HANDLE_VALUE;
- hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
- ok(hr == E_INVALIDARG, "Expected INVALID_HANDLE_VALUE to be invalid: 0x%08x\n", hr);
- ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n");
- /* must use only queried sample rate */
- fill_activation_params(&activation_params);
- memcpy(&wrong_format, &format, sizeof(format));
- activation_params.ObjectFormat = &wrong_format;
- wrong_format.nSamplesPerSec = 44100;
- wrong_format.nAvgBytesPerSec = wrong_format.nSamplesPerSec * wrong_format.nBlockAlign;
- hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
- ok(hr == AUDCLNT_E_UNSUPPORTED_FORMAT, "Expected format to be unsupported: 0x%08x\n", hr);
- ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n");
- /* dynamic objects are not supported */
- if (max_dyn_count == 0)
- {
fill_activation_params(&activation_params);
activation_params.StaticObjectTypeMask |= AudioObjectType_Dynamic;
hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
ok(hr == E_INVALIDARG, "Expected dynamic objects type be invalid: 0x%08x\n", hr);
ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n");
- }
- activation_params.MinDynamicObjectCount = max_dyn_count + 1;
- activation_params.MaxDynamicObjectCount = max_dyn_count + 1;
- hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
- if (max_dyn_count)
ok(hr == AUDCLNT_E_UNSUPPORTED_FORMAT, "Expected dynamic object count exceeding max to be unsupported: 0x%08x\n", hr);
- else
ok(hr == E_INVALIDARG, "Expected setting dynamic object count to be invalid: 0x%08x\n", hr);
- /* ISpatialAudioObjectRenderStreamNotify */
- fill_activation_params(&activation_params);
- notify_object.ISpatialAudioObjectRenderStreamNotify_iface.lpVtbl = ¬ifyobjvtbl;
- notify_object.ref = 0;
- activation_params.NotifyObject = ¬ify_object.ISpatialAudioObjectRenderStreamNotify_iface;
- hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
- ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08x\n", hr);
- ok(notify_object.ref == 1, "Expected to get increased NotifyObject's ref count\n");
- ok(ISpatialAudioObjectRenderStream_Release(sas) == 0, "Expected to release the last reference\n");
- ok(notify_object.ref == 0, "Expected to get lowered NotifyObject's ref count\n");
+}
+static void test_audio_object_activation(void) +{
- HRESULT hr;
- BOOL is_active;
- ISpatialAudioObjectRenderStream *sas = NULL;
- ISpatialAudioObject *sao1, *sao2;
- SpatialAudioObjectRenderStreamActivationParams activation_params;
- PROPVARIANT activation_params_prop;
- PropVariantInit(&activation_params_prop);
- activation_params_prop.vt = VT_BLOB;
- activation_params_prop.blob.cbSize = sizeof(activation_params);
- activation_params_prop.blob.pBlobData = (BYTE*) &activation_params;
- fill_activation_params(&activation_params);
- activation_params.StaticObjectTypeMask &= ~AudioObjectType_FrontRight;
- hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
- ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao1);
- ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr);
- hr = ISpatialAudioObject_IsActive(sao1, &is_active);
- todo_wine ok(hr == S_OK, "Failed to check if spatial audio object is active: 0x%08x\n", hr);
- if (hr == S_OK)
ok(is_active, "Expected spaital audio object to be active\n");
- hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao2);
- ok(hr == SPTLAUDCLNT_E_OBJECT_ALREADY_ACTIVE, "Expected audio object to be already active: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontRight, &sao2);
- ok(hr == SPTLAUDCLNT_E_STATIC_OBJECT_NOT_AVAILABLE, "Expected static object to be not available: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_Dynamic, &sao2);
- ok(hr == SPTLAUDCLNT_E_NO_MORE_OBJECTS, "Expected to not have no more dynamic objects: 0x%08x\n", hr);
- ISpatialAudioObject_Release(sao1);
- ISpatialAudioObjectRenderStream_Release(sas);
+}
+static BOOL is_buffer_zeroed(const BYTE *buffer, UINT32 buffer_length) +{
- UINT32 i;
- for (i = 0; i < buffer_length; i++)
- {
if (buffer[i] != 0)
return FALSE;
- }
- return TRUE;
+}
+static void test_audio_object_buffers(void) +{
- HRESULT hr;
- ISpatialAudioObjectRenderStream *sas = NULL;
- ISpatialAudioObject *sao[4];
- UINT32 dyn_object_count, frame_count, buffer_length;
- BYTE *buffer;
- INT i;
- SpatialAudioObjectRenderStreamActivationParams activation_params;
- PROPVARIANT activation_params_prop;
- PropVariantInit(&activation_params_prop);
- activation_params_prop.vt = VT_BLOB;
- activation_params_prop.blob.cbSize = sizeof(activation_params);
- activation_params_prop.blob.pBlobData = (BYTE*) &activation_params;
- fill_activation_params(&activation_params);
- hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
- ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao[0]);
- ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_Start(sas);
- ok(hr == S_OK, "Failed to activate spatial audio render stream: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontRight, &sao[1]);
- ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr);
- hr = WaitForSingleObject(event, 200);
- ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_SideLeft, &sao[2]);
- ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count);
- ok(hr == S_OK, "Failed to beging updating audio objects: 0x%08x\n", hr);
- ok(dyn_object_count == 0, "Unexpected dynamic objects\n");
- hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_SideRight, &sao[3]);
- ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr);
- for (i = 0; i < ARRAYSIZE(sao); i++)
- {
hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length);
ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08x\n", hr);
ok(buffer != NULL, "Expected to get a non-NULL buffer\n");
ok(buffer_length == frame_count * format.wBitsPerSample / 8, "Expected buffer length to be sample_size * frame_count = %hu but got %u\n",
frame_count * format.wBitsPerSample / 8, buffer_length);
ok(is_buffer_zeroed(buffer, buffer_length), "Expected audio object's buffer to be zeroed\n");
- }
- hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas);
- ok(hr == S_OK, "Failed to end updating audio objects: 0x%08x\n", hr);
- hr = WaitForSingleObject(event, 200);
- ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count);
- ok(hr == S_OK, "Failed to beging updating audio objects: 0x%08x\n", hr);
- ok(dyn_object_count == 0, "Unexpected dynamic objects\n");
- /* one more iteration but not with every object */
- for (i = 0; i < ARRAYSIZE(sao) - 1; i++)
- {
hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length);
ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08x\n", hr);
ok(buffer != NULL, "Expected to get a non-NULL buffer\n");
ok(buffer_length == frame_count * format.wBitsPerSample / 8, "Expected buffer length to be sample_size * frame_count = %hu but got %u\n",
frame_count * format.wBitsPerSample / 8, buffer_length);
ok(is_buffer_zeroed(buffer, buffer_length), "Expected audio object's buffer to be zeroed\n");
- }
- hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas);
- ok(hr == S_OK, "Failed to end updating audio objects: 0x%08x\n", hr);
- /* ending the stream */
- hr = ISpatialAudioObject_SetEndOfStream(sao[0], 0);
- todo_wine ok(hr == SPTLAUDCLNT_E_OUT_OF_ORDER, "Expected that ending the stream at this point won't be allowed: 0x%08x\n", hr);
- hr = WaitForSingleObject(event, 200);
- ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08x\n", hr);
- hr = ISpatialAudioObject_SetEndOfStream(sao[0], 0);
- todo_wine ok(hr == SPTLAUDCLNT_E_OUT_OF_ORDER, "Expected that ending the stream at this point won't be allowed: 0x%08x\n", hr);
- hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count);
- ok(hr == S_OK, "Failed to beging updating audio objects: 0x%08x\n", hr);
- ok(dyn_object_count == 0, "Unexpected dynamic objects\n");
- /* expect the object that was not updated last cycle to be invalidated */
- hr = ISpatialAudioObject_GetBuffer(sao[ARRAYSIZE(sao) - 1], &buffer, &buffer_length);
- todo_wine ok(hr == SPTLAUDCLNT_E_RESOURCES_INVALIDATED, "Expected audio object to be invalidated: 0x%08x\n", hr);
- for (i = 0; i < ARRAYSIZE(sao) - 1; i++)
- {
hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length);
ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08x\n", hr);
hr = ISpatialAudioObject_SetEndOfStream(sao[i], 0);
todo_wine ok(hr == S_OK, "Failed to end the stream: 0x%08x\n", hr);
hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length);
todo_wine ok(hr == SPTLAUDCLNT_E_RESOURCES_INVALIDATED, "Expected audio object to be invalidated: 0x%08x\n", hr);
- }
- hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas);
- ok(hr == S_OK, "Failed to end updating audio objects: 0x%08x\n", hr);
- for (i = 0; i < ARRAYSIZE(sao); i++)
- {
ISpatialAudioObject_Release(sao[i]);
- }
- ISpatialAudioObjectRenderStream_Release(sas);
+}
+START_TEST(spatialaudio) +{
- HRESULT hr;
- event = CreateEventA(NULL, FALSE, FALSE, "spatial-audio-test-prog-event");
- ok(event != NULL, "Failed to create event, last error: 0x%08x\n", GetLastError());
- CoInitializeEx(NULL, COINIT_MULTITHREADED);
- hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme);
- if (FAILED(hr))
- {
skip("mmdevapi not available: 0x%08x\n", hr);
goto cleanup;
- }
- hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eRender, eMultimedia, &dev);
- ok(hr == S_OK || hr == E_NOTFOUND, "GetDefaultAudioEndpoint failed: 0x%08x\n", hr);
- if (hr != S_OK || !dev)
- {
if (hr == E_NOTFOUND)
skip("No sound card available\n");
else
skip("GetDefaultAudioEndpoint returns 0x%08x\n", hr);
goto cleanup;
- }
- hr = IMMDevice_Activate(dev, &IID_ISpatialAudioClient, CLSCTX_INPROC_SERVER, NULL, (void**)&sac);
- ok(hr == S_OK || hr == E_NOINTERFACE, "ISpatialAudioClient Activation failed: 0x%08x\n", hr);
- if (hr != S_OK || !dev)
- {
if (hr == E_NOINTERFACE)
skip("ISpatialAudioClient interface not found\n");
else
skip("ISpatialAudioClient Activation returns 0x%08x\n", hr);
goto cleanup;
- }
- hr = ISpatialAudioClient_GetMaxDynamicObjectCount(sac, &max_dyn_count);
- ok(hr == S_OK, "Failed to get max dynamic object count: 0x%08x\n", hr);
- /* that's the default, after manually enabling Windows Sonic it's possible to have max_dyn_count > 0 */
- /* ok(max_dyn_count == 0, "expected max dynamic object count to be 0 got %u\n", max_dyn_count); */
- test_formats();
- test_stream_activation();
- test_audio_object_activation();
- test_audio_object_buffers();
- ISpatialAudioClient_Release(sac);
+cleanup:
- if (dev)
IMMDevice_Release(dev);
- if (mme)
IMMDeviceEnumerator_Release(mme);
- CoUninitialize();
- CloseHandle(event);
+}
2.30.0
Signed-off-by: Andrew Eikum aeikum@codeweavers.com
On Thu, Feb 04, 2021 at 04:09:56PM +0200, Arkadiusz Hiler wrote:
From: Andrew Eikum aeikum@codeweavers.com
This makes car radio / ambient noises audible in Cyberpunk 2077.
Signed-off-by: Arkadiusz Hiler ahiler@codeweavers.com
dlls/mmdevapi/Makefile.in | 3 +- dlls/mmdevapi/audiovolume.c | 1 + dlls/mmdevapi/devenum.c | 5 + dlls/mmdevapi/main.c | 1 + dlls/mmdevapi/mmdevapi.h | 1 + dlls/mmdevapi/spatialaudio.c | 988 +++++++++++++++++++++++++++++++++ include/spatialaudioclient.idl | 121 ++++ 7 files changed, 1119 insertions(+), 1 deletion(-) create mode 100644 dlls/mmdevapi/spatialaudio.c
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 <stdarg.h>
+#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);
+}
2.30.0