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 +#include + +#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); +}