/* * Copyright 2020 Andrew Eikum for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #define COBJMACROS #define NONAMELESSUNION #include #include "windef.h" #include "winbase.h" #include "winnls.h" #include "winreg.h" #include "wine/heap.h" #include "wine/debug.h" #include "wine/list.h" #include "ole2.h" #include "mmdeviceapi.h" #include "mmsystem.h" #include "audioclient.h" #include "endpointvolume.h" #include "audiopolicy.h" #include "spatialaudioclient.h" #include "mmdevapi.h" WINE_DEFAULT_DEBUG_CHANNEL(mmdevapi); #define MAX_PERIODS 3 static UINT32 AudioObjectType_to_index(AudioObjectType type) { UINT32 o = 0; while(type){ type >>= 1; ++o; } return o - 2; } typedef struct SpatialAudioImpl SpatialAudioImpl; typedef struct SpatialAudioStreamImpl SpatialAudioStreamImpl; typedef struct SpatialAudioObjectImpl SpatialAudioObjectImpl; struct SpatialAudioObjectImpl { ISpatialAudioObject ISpatialAudioObject_iface; LONG ref; SpatialAudioStreamImpl *sa_stream; AudioObjectType type; UINT32 static_idx; float *buf; struct list entry; }; struct SpatialAudioStreamImpl { ISpatialAudioObjectRenderStream ISpatialAudioObjectRenderStream_iface; LONG ref; CRITICAL_SECTION lock; SpatialAudioImpl *sa_client; SpatialAudioObjectRenderStreamActivationParams params; IAudioClient *client; IAudioRenderClient *render; UINT32 period_frames, update_frames; WAVEFORMATEXTENSIBLE stream_fmtex; float *buf; UINT32 static_object_map[17]; struct list objects; }; struct SpatialAudioImpl { ISpatialAudioClient ISpatialAudioClient_iface; IAudioFormatEnumerator IAudioFormatEnumerator_iface; IMMDevice *mmdev; LONG ref; WAVEFORMATEXTENSIBLE object_fmtex; }; static inline SpatialAudioObjectImpl *impl_from_ISpatialAudioObject(ISpatialAudioObject *iface) { return CONTAINING_RECORD(iface, SpatialAudioObjectImpl, ISpatialAudioObject_iface); } static inline SpatialAudioStreamImpl *impl_from_ISpatialAudioObjectRenderStream(ISpatialAudioObjectRenderStream *iface) { return CONTAINING_RECORD(iface, SpatialAudioStreamImpl, ISpatialAudioObjectRenderStream_iface); } static inline SpatialAudioImpl *impl_from_ISpatialAudioClient(ISpatialAudioClient *iface) { return CONTAINING_RECORD(iface, SpatialAudioImpl, ISpatialAudioClient_iface); } static inline SpatialAudioImpl *impl_from_IAudioFormatEnumerator(IAudioFormatEnumerator *iface) { return CONTAINING_RECORD(iface, SpatialAudioImpl, IAudioFormatEnumerator_iface); } static HRESULT WINAPI SAO_QueryInterface(ISpatialAudioObject *iface, REFIID riid, void **ppv) { SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); if (!ppv) return E_POINTER; *ppv = NULL; if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_ISpatialAudioObjectBase) || IsEqualIID(riid, &IID_ISpatialAudioObject)) { *ppv = &This->ISpatialAudioObject_iface; } else return E_NOINTERFACE; IUnknown_AddRef((IUnknown *)*ppv); return S_OK; } static ULONG WINAPI SAO_AddRef(ISpatialAudioObject *iface) { SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); ULONG ref = InterlockedIncrement(&This->ref); TRACE("(%p) new ref %u\n", This, ref); return ref; } static ULONG WINAPI SAO_Release(ISpatialAudioObject *iface) { SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); ULONG ref = InterlockedDecrement(&This->ref); TRACE("(%p) new ref %u\n", This, ref); if(!ref){ EnterCriticalSection(&This->sa_stream->lock); list_remove(&This->entry); LeaveCriticalSection(&This->sa_stream->lock); ISpatialAudioObjectRenderStream_Release(&This->sa_stream->ISpatialAudioObjectRenderStream_iface); heap_free(This->buf); heap_free(This); } return ref; } static HRESULT WINAPI SAO_GetBuffer(ISpatialAudioObject *iface, BYTE **buffer, UINT32 *bytes) { SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); TRACE("(%p)->(%p, %p)\n", This, buffer, bytes); EnterCriticalSection(&This->sa_stream->lock); if(This->sa_stream->update_frames == ~0){ LeaveCriticalSection(&This->sa_stream->lock); return SPTLAUDCLNT_E_OUT_OF_ORDER; } *buffer = (BYTE *)This->buf; *bytes = This->sa_stream->update_frames * This->sa_stream->sa_client->object_fmtex.Format.nBlockAlign; LeaveCriticalSection(&This->sa_stream->lock); return S_OK; } static HRESULT WINAPI SAO_SetEndOfStream(ISpatialAudioObject *iface, UINT32 frames) { SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); FIXME("(%p)->(%u)\n", This, frames); return E_NOTIMPL; } static HRESULT WINAPI SAO_IsActive(ISpatialAudioObject *iface, BOOL *active) { SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); FIXME("(%p)->(%p)\n", This, active); return E_NOTIMPL; } static HRESULT WINAPI SAO_GetAudioObjectType(ISpatialAudioObject *iface, AudioObjectType *type) { SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); TRACE("(%p)->(%p)\n", This, type); *type = This->type; return S_OK; } static HRESULT WINAPI SAO_SetPosition(ISpatialAudioObject *iface, float x, float y, float z) { SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); FIXME("(%p)->(%f, %f, %f)\n", This, x, y, z); return E_NOTIMPL; } static HRESULT WINAPI SAO_SetVolume(ISpatialAudioObject *iface, float vol) { SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); FIXME("(%p)->(%f)\n", This, vol); return E_NOTIMPL; } static ISpatialAudioObjectVtbl ISpatialAudioObject_vtbl = { SAO_QueryInterface, SAO_AddRef, SAO_Release, SAO_GetBuffer, SAO_SetEndOfStream, SAO_IsActive, SAO_GetAudioObjectType, SAO_SetPosition, SAO_SetVolume, }; static HRESULT WINAPI SAORS_QueryInterface(ISpatialAudioObjectRenderStream *iface, REFIID riid, void **ppv) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); if (!ppv) return E_POINTER; *ppv = NULL; if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStreamBase) || IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStream)) { *ppv = &This->ISpatialAudioObjectRenderStream_iface; } else return E_NOINTERFACE; IUnknown_AddRef((IUnknown *)*ppv); return S_OK; } static ULONG WINAPI SAORS_AddRef(ISpatialAudioObjectRenderStream *iface) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); ULONG ref = InterlockedIncrement(&This->ref); TRACE("(%p) new ref %u\n", This, ref); return ref; } static ULONG WINAPI SAORS_Release(ISpatialAudioObjectRenderStream *iface) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); ULONG ref = InterlockedDecrement(&This->ref); TRACE("(%p) new ref %u\n", This, ref); if(!ref){ IAudioClient_Stop(This->client); if(This->update_frames != ~0 && This->update_frames > 0) IAudioRenderClient_ReleaseBuffer(This->render, This->update_frames, 0); IAudioRenderClient_Release(This->render); IAudioClient_Release(This->client); if(This->params.NotifyObject) ISpatialAudioObjectRenderStreamNotify_Release(This->params.NotifyObject); heap_free((void*)This->params.ObjectFormat); CloseHandle(This->params.EventHandle); DeleteCriticalSection(&This->lock); ISpatialAudioClient_Release(&This->sa_client->ISpatialAudioClient_iface); heap_free(This); } return ref; } static HRESULT WINAPI SAORS_GetAvailableDynamicObjectCount( ISpatialAudioObjectRenderStream *iface, UINT32 *count) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); FIXME("(%p)->(%p)\n", This, count); return E_NOTIMPL; } static HRESULT WINAPI SAORS_GetService(ISpatialAudioObjectRenderStream *iface, REFIID riid, void **service) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); FIXME("(%p)->(%s, %p)\n", This, debugstr_guid(riid), service); return E_NOTIMPL; } static HRESULT WINAPI SAORS_Start(ISpatialAudioObjectRenderStream *iface) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); HRESULT hr; TRACE("(%p)->()\n", This); hr = IAudioClient_Start(This->client); if(FAILED(hr)){ WARN("IAudioClient::Start failed: %08x\n", hr); return hr; } return S_OK; } static HRESULT WINAPI SAORS_Stop(ISpatialAudioObjectRenderStream *iface) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); HRESULT hr; TRACE("(%p)->()\n", This); hr = IAudioClient_Stop(This->client); if(FAILED(hr)){ WARN("IAudioClient::Stop failed: %08x\n", hr); return hr; } return S_OK; } static HRESULT WINAPI SAORS_Reset(ISpatialAudioObjectRenderStream *iface) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); FIXME("(%p)->()\n", This); return E_NOTIMPL; } static HRESULT WINAPI SAORS_BeginUpdatingAudioObjects(ISpatialAudioObjectRenderStream *iface, UINT32 *dyn_count, UINT32 *frames) { static BOOL fixme_once = FALSE; SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); SpatialAudioObjectImpl *object; UINT32 pad; HRESULT hr; TRACE("(%p)->(%p, %p)\n", This, dyn_count, frames); EnterCriticalSection(&This->lock); if(This->update_frames != ~0){ LeaveCriticalSection(&This->lock); return SPTLAUDCLNT_E_OUT_OF_ORDER; } hr = IAudioClient_GetCurrentPadding(This->client, &pad); if(FAILED(hr)){ WARN("GetCurrentPadding failed: %08x\n", hr); LeaveCriticalSection(&This->lock); return hr; } if(pad < This->period_frames * MAX_PERIODS){ This->update_frames = This->period_frames * MAX_PERIODS - pad; }else{ This->update_frames = 0; } if(This->update_frames > 0){ hr = IAudioRenderClient_GetBuffer(This->render, This->update_frames, (BYTE **)&This->buf); if(FAILED(hr)){ WARN("GetBuffer failed: %08x\n", hr); This->update_frames = ~0; LeaveCriticalSection(&This->lock); return hr; } LIST_FOR_EACH_ENTRY(object, &This->objects, SpatialAudioObjectImpl, entry){ memset(object->buf, 0, This->update_frames * This->sa_client->object_fmtex.Format.nBlockAlign); } }else if (!fixme_once){ fixme_once = TRUE; FIXME("Zero frame update.\n"); } *dyn_count = 0; *frames = This->update_frames; LeaveCriticalSection(&This->lock); return S_OK; } static void mix_static_object(SpatialAudioStreamImpl *stream, SpatialAudioObjectImpl *object) { float *in = object->buf, *out; UINT32 i; if(object->static_idx == ~0 || stream->static_object_map[object->static_idx] == ~0){ WARN("Got unmapped static object?! Not mixing. Type: 0x%x\n", object->type); return; } out = stream->buf + stream->static_object_map[object->static_idx]; for(i = 0; i < stream->update_frames; ++i){ *out += *in; ++in; out += stream->stream_fmtex.Format.nChannels; } } static HRESULT WINAPI SAORS_EndUpdatingAudioObjects(ISpatialAudioObjectRenderStream *iface) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); SpatialAudioObjectImpl *object; HRESULT hr; TRACE("(%p)->()\n", This); EnterCriticalSection(&This->lock); if(This->update_frames == ~0){ LeaveCriticalSection(&This->lock); return SPTLAUDCLNT_E_OUT_OF_ORDER; } if(This->update_frames > 0){ LIST_FOR_EACH_ENTRY(object, &This->objects, SpatialAudioObjectImpl, entry){ if(object->type != AudioObjectType_Dynamic) mix_static_object(This, object); else WARN("Don't know how to mix dynamic object yet. %p\n", object); } hr = IAudioRenderClient_ReleaseBuffer(This->render, This->update_frames, 0); if(FAILED(hr)) WARN("ReleaseBuffer failed: %08x\n", hr); } This->update_frames = ~0; LeaveCriticalSection(&This->lock); return S_OK; } static HRESULT WINAPI SAORS_ActivateSpatialAudioObject(ISpatialAudioObjectRenderStream *iface, AudioObjectType type, ISpatialAudioObject **object) { SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); SpatialAudioObjectImpl *obj; TRACE("(%p)->(0x%x, %p)\n", This, type, object); if(type == AudioObjectType_Dynamic) return SPTLAUDCLNT_E_NO_MORE_OBJECTS; if(type & ~This->params.StaticObjectTypeMask) return SPTLAUDCLNT_E_STATIC_OBJECT_NOT_AVAILABLE; LIST_FOR_EACH_ENTRY(obj, &This->objects, SpatialAudioObjectImpl, entry){ if(obj->static_idx == AudioObjectType_to_index(type)) return SPTLAUDCLNT_E_OBJECT_ALREADY_ACTIVE; } obj = heap_alloc_zero(sizeof(*obj)); obj->ISpatialAudioObject_iface.lpVtbl = &ISpatialAudioObject_vtbl; obj->ref = 1; obj->type = type; if(type == AudioObjectType_None){ FIXME("AudioObjectType_None not implemented yet!\n"); obj->static_idx = ~0; }else{ obj->static_idx = AudioObjectType_to_index(type); } obj->sa_stream = This; SAORS_AddRef(&This->ISpatialAudioObjectRenderStream_iface); obj->buf = heap_alloc_zero(This->period_frames * MAX_PERIODS * This->sa_client->object_fmtex.Format.nBlockAlign); EnterCriticalSection(&This->lock); list_add_tail(&This->objects, &obj->entry); LeaveCriticalSection(&This->lock); *object = &obj->ISpatialAudioObject_iface; return S_OK; } static ISpatialAudioObjectRenderStreamVtbl ISpatialAudioObjectRenderStream_vtbl = { SAORS_QueryInterface, SAORS_AddRef, SAORS_Release, SAORS_GetAvailableDynamicObjectCount, SAORS_GetService, SAORS_Start, SAORS_Stop, SAORS_Reset, SAORS_BeginUpdatingAudioObjects, SAORS_EndUpdatingAudioObjects, SAORS_ActivateSpatialAudioObject, }; static HRESULT WINAPI SAC_QueryInterface(ISpatialAudioClient *iface, REFIID riid, void **ppv) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); if (!ppv) return E_POINTER; *ppv = NULL; if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_ISpatialAudioClient)) { *ppv = &This->ISpatialAudioClient_iface; } else return E_NOINTERFACE; IUnknown_AddRef((IUnknown *)*ppv); return S_OK; } static ULONG WINAPI SAC_AddRef(ISpatialAudioClient *iface) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); ULONG ref = InterlockedIncrement(&This->ref); TRACE("(%p) new ref %u\n", This, ref); return ref; } static ULONG WINAPI SAC_Release(ISpatialAudioClient *iface) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); ULONG ref = InterlockedDecrement(&This->ref); TRACE("(%p) new ref %u\n", This, ref); if (!ref) { IMMDevice_Release(This->mmdev); heap_free(This); } return ref; } static HRESULT WINAPI SAC_GetStaticObjectPosition(ISpatialAudioClient *iface, AudioObjectType type, float *x, float *y, float *z) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); FIXME("(%p)->(0x%x, %p, %p, %p)\n", This, type, x, y, z); return E_NOTIMPL; } static HRESULT WINAPI SAC_GetNativeStaticObjectTypeMask(ISpatialAudioClient *iface, AudioObjectType *mask) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); FIXME("(%p)->(%p)\n", This, mask); return E_NOTIMPL; } static HRESULT WINAPI SAC_GetMaxDynamicObjectCount(ISpatialAudioClient *iface, UINT32 *value) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); FIXME("(%p)->(%p)\n", This, value); *value = 0; return S_OK; } static HRESULT WINAPI SAC_GetSupportedAudioObjectFormatEnumerator( ISpatialAudioClient *iface, IAudioFormatEnumerator **enumerator) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); TRACE("(%p)->(%p)\n", This, enumerator); *enumerator = &This->IAudioFormatEnumerator_iface; SAC_AddRef(iface); return S_OK; } static HRESULT WINAPI SAC_GetMaxFrameCount(ISpatialAudioClient *iface, const WAVEFORMATEX *format, UINT32 *count) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); /* FIXME: should get device period from the device */ static const REFERENCE_TIME period = 100000; TRACE("(%p)->(%p, %p)\n", This, format, count); *count = MulDiv(period, format->nSamplesPerSec, 10000000) * MAX_PERIODS; return S_OK; } static HRESULT WINAPI SAC_IsAudioObjectFormatSupported(ISpatialAudioClient *iface, const WAVEFORMATEX *format) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); FIXME("(%p)->(%p)\n", This, format); return E_NOTIMPL; } static HRESULT WINAPI SAC_IsSpatialAudioStreamAvailable(ISpatialAudioClient *iface, REFIID stream_uuid, const PROPVARIANT *info) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); FIXME("(%p)->(%s, %p)\n", This, debugstr_guid(stream_uuid), info); return E_NOTIMPL; } static WAVEFORMATEX *clone_fmtex(const WAVEFORMATEX *src) { WAVEFORMATEX *r = heap_alloc(sizeof(WAVEFORMATEX) + src->cbSize); memcpy(r, src, sizeof(WAVEFORMATEX) + src->cbSize); return r; } static const char *debugstr_fmtex(const WAVEFORMATEX *fmt) { static char buf[2048]; if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt; snprintf(buf, sizeof(buf), "tag: 0x%x (%s), ch: %u (mask: 0x%x), rate: %u, depth: %u", fmt->wFormatTag, debugstr_guid(&fmtex->SubFormat), fmt->nChannels, fmtex->dwChannelMask, fmt->nSamplesPerSec, fmt->wBitsPerSample); }else{ snprintf(buf, sizeof(buf), "tag: 0x%x, ch: %u, rate: %u, depth: %u", fmt->wFormatTag, fmt->nChannels, fmt->nSamplesPerSec, fmt->wBitsPerSample); } return buf; } static void static_mask_to_channels(AudioObjectType static_mask, WORD *count, DWORD *mask, UINT32 *map) { UINT32 out_chan = 0, map_idx = 0; *count = 0; *mask = 0; #define CONVERT_MASK(f, t) \ if(static_mask & f){ \ *count += 1; \ *mask |= t; \ map[map_idx++] = out_chan++; \ TRACE("mapping 0x%x to %u\n", f, out_chan - 1); \ }else{ \ map[map_idx++] = ~0; \ } CONVERT_MASK(AudioObjectType_FrontLeft, SPEAKER_FRONT_LEFT); CONVERT_MASK(AudioObjectType_FrontRight, SPEAKER_FRONT_RIGHT); CONVERT_MASK(AudioObjectType_FrontCenter, SPEAKER_FRONT_CENTER); CONVERT_MASK(AudioObjectType_LowFrequency, SPEAKER_LOW_FREQUENCY); CONVERT_MASK(AudioObjectType_SideLeft, SPEAKER_SIDE_LEFT); CONVERT_MASK(AudioObjectType_SideRight, SPEAKER_SIDE_RIGHT); CONVERT_MASK(AudioObjectType_BackLeft, SPEAKER_BACK_LEFT); CONVERT_MASK(AudioObjectType_BackRight, SPEAKER_BACK_RIGHT); CONVERT_MASK(AudioObjectType_TopFrontLeft, SPEAKER_TOP_FRONT_LEFT); CONVERT_MASK(AudioObjectType_TopFrontRight, SPEAKER_TOP_FRONT_RIGHT); CONVERT_MASK(AudioObjectType_TopBackLeft, SPEAKER_TOP_BACK_LEFT); CONVERT_MASK(AudioObjectType_TopBackRight, SPEAKER_TOP_BACK_RIGHT); CONVERT_MASK(AudioObjectType_BackCenter, SPEAKER_BACK_CENTER); } static HRESULT activate_stream(SpatialAudioStreamImpl *stream) { WAVEFORMATEXTENSIBLE *object_fmtex = (WAVEFORMATEXTENSIBLE *)stream->params.ObjectFormat; HRESULT hr; REFERENCE_TIME period; if(!(object_fmtex->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT || (object_fmtex->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && IsEqualGUID(&object_fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)))){ FIXME("Only float formats are supported for now\n"); return E_INVALIDARG; } hr = IMMDevice_Activate(stream->sa_client->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, (void**)&stream->client); if(FAILED(hr)){ WARN("Activate failed: %08x\n", hr); return hr; } hr = IAudioClient_GetDevicePeriod(stream->client, &period, NULL); if(FAILED(hr)){ WARN("GetDevicePeriod failed: %08x\n", hr); IAudioClient_Release(stream->client); return hr; } stream->stream_fmtex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; static_mask_to_channels(stream->params.StaticObjectTypeMask, &stream->stream_fmtex.Format.nChannels, &stream->stream_fmtex.dwChannelMask, stream->static_object_map); stream->stream_fmtex.Format.nSamplesPerSec = stream->params.ObjectFormat->nSamplesPerSec; stream->stream_fmtex.Format.wBitsPerSample = stream->params.ObjectFormat->wBitsPerSample; stream->stream_fmtex.Format.nBlockAlign = (stream->stream_fmtex.Format.nChannels * stream->stream_fmtex.Format.wBitsPerSample) / 8; stream->stream_fmtex.Format.nAvgBytesPerSec = stream->stream_fmtex.Format.nSamplesPerSec * stream->stream_fmtex.Format.nBlockAlign; stream->stream_fmtex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); stream->stream_fmtex.Samples.wValidBitsPerSample = stream->stream_fmtex.Format.wBitsPerSample; stream->stream_fmtex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; hr = IAudioClient_Initialize(stream->client, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, period * MAX_PERIODS, 0, &stream->stream_fmtex.Format, NULL); if(FAILED(hr)){ WARN("Initialize failed: %08x\n", hr); IAudioClient_Release(stream->client); return hr; } hr = IAudioClient_SetEventHandle(stream->client, stream->params.EventHandle); if(FAILED(hr)){ WARN("SetEventHandle failed: %08x\n", hr); IAudioClient_Release(stream->client); return hr; } hr = IAudioClient_GetService(stream->client, &IID_IAudioRenderClient, (void**)&stream->render); if(FAILED(hr)){ WARN("GetService(AudioRenderClient) failed: %08x\n", hr); IAudioClient_Release(stream->client); return hr; } stream->period_frames = MulDiv(period, stream->stream_fmtex.Format.nSamplesPerSec, 10000000); return S_OK; } static HRESULT WINAPI SAC_ActivateSpatialAudioStream(ISpatialAudioClient *iface, const PROPVARIANT *prop, REFIID riid, void **stream) { SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); SpatialAudioObjectRenderStreamActivationParams *params; HRESULT hr; TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), stream); if(IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStream)){ SpatialAudioStreamImpl *obj; if(prop && (prop->vt != VT_BLOB || prop->u.blob.cbSize != sizeof(SpatialAudioObjectRenderStreamActivationParams))){ WARN("Got invalid params\n"); *stream = NULL; return E_INVALIDARG; } params = (SpatialAudioObjectRenderStreamActivationParams*) prop->u.blob.pBlobData; if(params->StaticObjectTypeMask & AudioObjectType_Dynamic){ *stream = NULL; return E_INVALIDARG; } if(params->EventHandle == INVALID_HANDLE_VALUE || params->EventHandle == 0){ *stream = NULL; return E_INVALIDARG; } if(!params->ObjectFormat || memcmp(params->ObjectFormat, &This->object_fmtex.Format, sizeof(*params->ObjectFormat) + params->ObjectFormat->cbSize)){ *stream = NULL; return AUDCLNT_E_UNSUPPORTED_FORMAT; } obj = heap_alloc_zero(sizeof(SpatialAudioStreamImpl)); obj->ISpatialAudioObjectRenderStream_iface.lpVtbl = &ISpatialAudioObjectRenderStream_vtbl; obj->ref = 1; memcpy(&obj->params, params, sizeof(obj->params)); obj->update_frames = ~0; InitializeCriticalSection(&obj->lock); list_init(&obj->objects); obj->sa_client = This; SAC_AddRef(&This->ISpatialAudioClient_iface); obj->params.ObjectFormat = clone_fmtex(obj->params.ObjectFormat); DuplicateHandle(GetCurrentProcess(), obj->params.EventHandle, GetCurrentProcess(), &obj->params.EventHandle, 0, FALSE, DUPLICATE_SAME_ACCESS); if(obj->params.NotifyObject) ISpatialAudioObjectRenderStreamNotify_AddRef(obj->params.NotifyObject); if(TRACE_ON(mmdevapi)){ TRACE("ObjectFormat: {%s}\n", debugstr_fmtex(obj->params.ObjectFormat)); TRACE("StaticObjectTypeMask: 0x%x\n", obj->params.StaticObjectTypeMask); TRACE("MinDynamicObjectCount: 0x%x\n", obj->params.MinDynamicObjectCount); TRACE("MaxDynamicObjectCount: 0x%x\n", obj->params.MaxDynamicObjectCount); TRACE("Category: 0x%x\n", obj->params.Category); TRACE("EventHandle: %p\n", obj->params.EventHandle); TRACE("NotifyObject: %p\n", obj->params.NotifyObject); } hr = activate_stream(obj); if(FAILED(hr)){ if(obj->params.NotifyObject) ISpatialAudioObjectRenderStreamNotify_Release(obj->params.NotifyObject); DeleteCriticalSection(&obj->lock); heap_free((void*)obj->params.ObjectFormat); CloseHandle(obj->params.EventHandle); ISpatialAudioClient_Release(&obj->sa_client->ISpatialAudioClient_iface); heap_free(obj); *stream = NULL; return hr; } *stream = &obj->ISpatialAudioObjectRenderStream_iface; }else{ FIXME("Unsupported audio stream IID: %s\n", debugstr_guid(riid)); *stream = NULL; return E_NOTIMPL; } return S_OK; } static ISpatialAudioClientVtbl ISpatialAudioClient_vtbl = { SAC_QueryInterface, SAC_AddRef, SAC_Release, SAC_GetStaticObjectPosition, SAC_GetNativeStaticObjectTypeMask, SAC_GetMaxDynamicObjectCount, SAC_GetSupportedAudioObjectFormatEnumerator, SAC_GetMaxFrameCount, SAC_IsAudioObjectFormatSupported, SAC_IsSpatialAudioStreamAvailable, SAC_ActivateSpatialAudioStream, }; static HRESULT WINAPI SAOFE_QueryInterface(IAudioFormatEnumerator *iface, REFIID riid, void **ppvObject) { SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); return SAC_QueryInterface(&This->ISpatialAudioClient_iface, riid, ppvObject); } static ULONG WINAPI SAOFE_AddRef(IAudioFormatEnumerator *iface) { SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); return SAC_AddRef(&This->ISpatialAudioClient_iface); } static ULONG WINAPI SAOFE_Release(IAudioFormatEnumerator *iface) { SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); return SAC_Release(&This->ISpatialAudioClient_iface); } static HRESULT WINAPI SAOFE_GetCount(IAudioFormatEnumerator *iface, UINT32 *count) { SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); TRACE("(%p)->(%p)\n", This, count); *count = 1; return S_OK; } static HRESULT WINAPI SAOFE_GetFormat(IAudioFormatEnumerator *iface, UINT32 index, WAVEFORMATEX **format) { SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); TRACE("(%p)->(%u, %p)\n", This, index, format); if(index > 0) return E_INVALIDARG; *format = &This->object_fmtex.Format; return S_OK; } static IAudioFormatEnumeratorVtbl IAudioFormatEnumerator_vtbl = { SAOFE_QueryInterface, SAOFE_AddRef, SAOFE_Release, SAOFE_GetCount, SAOFE_GetFormat, }; HRESULT SpatialAudioClient_Create(IMMDevice *mmdev, ISpatialAudioClient **out) { SpatialAudioImpl *obj; IAudioClient *aclient; WAVEFORMATEX *closest; HRESULT hr; obj = heap_alloc_zero(sizeof(*obj)); obj->ref = 1; obj->ISpatialAudioClient_iface.lpVtbl = &ISpatialAudioClient_vtbl; obj->IAudioFormatEnumerator_iface.lpVtbl = &IAudioFormatEnumerator_vtbl; obj->object_fmtex.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; obj->object_fmtex.Format.nChannels = 1; obj->object_fmtex.Format.nSamplesPerSec = 48000; obj->object_fmtex.Format.wBitsPerSample = sizeof(float) * 8; obj->object_fmtex.Format.nBlockAlign = (obj->object_fmtex.Format.nChannels * obj->object_fmtex.Format.wBitsPerSample) / 8; obj->object_fmtex.Format.nAvgBytesPerSec = obj->object_fmtex.Format.nSamplesPerSec * obj->object_fmtex.Format.nBlockAlign; obj->object_fmtex.Format.cbSize = 0; hr = IMMDevice_Activate(mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, (void**)&aclient); if(FAILED(hr)){ WARN("Activate failed: %08x\n", hr); heap_free(obj); return hr; } hr = IAudioClient_IsFormatSupported(aclient, AUDCLNT_SHAREMODE_SHARED, &obj->object_fmtex.Format, &closest); IAudioClient_Release(aclient); if(hr == S_FALSE){ if(sizeof(WAVEFORMATEX) + closest->cbSize > sizeof(obj->object_fmtex)){ ERR("Returned format too large: %s\n", debugstr_fmtex(closest)); CoTaskMemFree(closest); heap_free(obj); return AUDCLNT_E_UNSUPPORTED_FORMAT; }else if(!((closest->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || (closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE && IsEqualGUID(&((WAVEFORMATEXTENSIBLE *)closest)->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))) && closest->wBitsPerSample == 32)){ ERR("Returned format not 32-bit float: %s\n", debugstr_fmtex(closest)); CoTaskMemFree(closest); heap_free(obj); return AUDCLNT_E_UNSUPPORTED_FORMAT; } WARN("The audio stack doesn't support 48kHz 32bit float. Using the closest match. Audio may be glitchy. %s\n", debugstr_fmtex(closest)); memcpy(&obj->object_fmtex, closest, sizeof(WAVEFORMATEX) + closest->cbSize); CoTaskMemFree(closest); } else if(hr != S_OK){ WARN("Checking supported formats failed: %08x\n", hr); heap_free(obj); return hr; } obj->mmdev = mmdev; IMMDevice_AddRef(mmdev); *out = &obj->ISpatialAudioClient_iface; return S_OK; }