Signed-off-by: Andrew Eikum aeikum@codeweavers.com --- dlls/windows.media.devices/main.c | 85 +++++++- dlls/windows.media.devices/tests/devices.c | 223 ++++++++++++++++++++- 2 files changed, 303 insertions(+), 5 deletions(-)
diff --git a/dlls/windows.media.devices/main.c b/dlls/windows.media.devices/main.c index 9797644d71a..8d4c63f329a 100644 --- a/dlls/windows.media.devices/main.c +++ b/dlls/windows.media.devices/main.c @@ -25,9 +25,11 @@ #include "winbase.h" #include "winstring.h" #include "wine/debug.h" +#include "wine/heap.h" #include "objbase.h"
#include "activation.h" +#include "mmdeviceapi.h"
#define WIDL_using_Windows_Foundation #define WIDL_using_Windows_Foundation_Collections @@ -46,6 +48,16 @@ static const char *debugstr_hstring(HSTRING hstr) return wine_dbgstr_wn(str, len); }
+static ERole AudioDeviceRole_to_ERole(AudioDeviceRole role) +{ + switch(role){ + case AudioDeviceRole_Communications: + return eCommunications; + default: + return eMultimedia; + } +} + struct windows_media_devices { IActivationFactory IActivationFactory_iface; @@ -151,6 +163,71 @@ static const struct IActivationFactoryVtbl activation_factory_vtbl = windows_media_devices_ActivateInstance, };
+static HRESULT get_default_device_id(EDataFlow direction, AudioDeviceRole role, HSTRING *device_id_hstr) +{ + HRESULT hr; + WCHAR *devid, *s; + IMMDevice *dev; + IMMDeviceEnumerator *devenum; + ERole mmdev_role = AudioDeviceRole_to_ERole(role); + + static const WCHAR id_fmt_pre[] = L"\\?\SWD#MMDEVAPI#"; + static const WCHAR id_fmt_hash[] = L"#"; + static const size_t GUID_STR_LEN = 38; /* == strlen("{00000000-0000-0000-0000-000000000000}") */ + + *device_id_hstr = NULL; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, + CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum); + if (FAILED(hr)) + { + WARN("Failed to create MMDeviceEnumerator: %08x\n", hr); + return hr; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devenum, direction, mmdev_role, &dev); + if (FAILED(hr)) + { + WARN("GetDefaultAudioEndpoint failed: %08x\n", hr); + IMMDeviceEnumerator_Release(devenum); + return hr; + } + + hr = IMMDevice_GetId(dev, &devid); + if (FAILED(hr)) + { + WARN("GetId failed: %08x\n", hr); + IMMDevice_Release(dev); + IMMDeviceEnumerator_Release(devenum); + return hr; + } + + s = heap_alloc((sizeof(id_fmt_pre) - sizeof(WCHAR)) + + (sizeof(id_fmt_hash) - sizeof(WCHAR)) + + (wcslen(devid) + GUID_STR_LEN + 1 /* nul */) * sizeof(WCHAR)); + + wcscpy(s, id_fmt_pre); + wcscat(s, devid); + wcscat(s, id_fmt_hash); + + if(direction == eRender) + StringFromGUID2(&DEVINTERFACE_AUDIO_RENDER, s + wcslen(s), GUID_STR_LEN + 1); + else + StringFromGUID2(&DEVINTERFACE_AUDIO_CAPTURE, s + wcslen(s), GUID_STR_LEN + 1); + + hr = WindowsCreateString(s, wcslen(s), device_id_hstr); + if (FAILED(hr)) + WARN("WindowsCreateString failed: %08x\n", hr); + + heap_free(s); + + CoTaskMemFree(devid); + IMMDevice_Release(dev); + IMMDeviceEnumerator_Release(devenum); + + return hr; +} + static HRESULT WINAPI media_device_statics_QueryInterface(IMediaDeviceStatics *iface, REFIID riid, void **ppvObject) { @@ -215,15 +292,15 @@ static HRESULT WINAPI media_device_statics_GetVideoCaptureSelector(IMediaDeviceS static HRESULT WINAPI media_device_statics_GetDefaultAudioCaptureId(IMediaDeviceStatics *iface, AudioDeviceRole role, HSTRING *value) { - FIXME("iface %p, role 0x%x, value %p stub!\n", iface, role, value); - return E_NOTIMPL; + TRACE("iface %p, role 0x%x, value %p\n", iface, role, value); + return get_default_device_id(eCapture, role, value); }
static HRESULT WINAPI media_device_statics_GetDefaultAudioRenderId(IMediaDeviceStatics *iface, AudioDeviceRole role, HSTRING *value) { - FIXME("iface %p, role 0x%x, value %p stub!\n", iface, role, value); - return E_NOTIMPL; + TRACE("iface %p, role 0x%x, value %p\n", iface, role, value); + return get_default_device_id(eRender, role, value); }
static HRESULT WINAPI media_device_statics_add_DefaultAudioCaptureDeviceChanged( diff --git a/dlls/windows.media.devices/tests/devices.c b/dlls/windows.media.devices/tests/devices.c index 9c3019de773..f3777944848 100644 --- a/dlls/windows.media.devices/tests/devices.c +++ b/dlls/windows.media.devices/tests/devices.c @@ -42,6 +42,90 @@ static HRESULT (WINAPI *pRoInitialize)(RO_INIT_TYPE); static void (WINAPI *pRoUninitialize)(void); static HRESULT (WINAPI *pWindowsCreateString)(LPCWSTR, UINT32, HSTRING *); static HRESULT (WINAPI *pWindowsDeleteString)(HSTRING); +static WCHAR *(WINAPI *pWindowsGetStringRawBuffer)(HSTRING, UINT32 *); + +static HRESULT (WINAPI *pActivateAudioInterfaceAsync)(const WCHAR *path, + REFIID riid, PROPVARIANT *params, + IActivateAudioInterfaceCompletionHandler *done_handler, + IActivateAudioInterfaceAsyncOperation **op); + +static IMMDeviceEnumerator *g_mmdevenum; +static WCHAR *g_default_capture_id, *g_default_render_id; + +static struct { + LONG ref; + HANDLE evt; + CRITICAL_SECTION lock; + IActivateAudioInterfaceAsyncOperation *op; + char msg_pfx[128]; + IUnknown *result_iface; + HRESULT result_hr; +} async_activate_test; + +static HRESULT WINAPI async_activate_QueryInterface( + IActivateAudioInterfaceCompletionHandler *iface, + REFIID riid, + void **ppvObject) +{ + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAgileObject) || + IsEqualIID(riid, &IID_IActivateAudioInterfaceCompletionHandler)) + { + *ppvObject = iface; + IUnknown_AddRef((IUnknown*)*ppvObject); + return S_OK; + } + + *ppvObject = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI async_activate_AddRef( + IActivateAudioInterfaceCompletionHandler *iface) +{ + return InterlockedIncrement(&async_activate_test.ref); +} + +static ULONG WINAPI async_activate_Release( + IActivateAudioInterfaceCompletionHandler *iface) +{ + ULONG ref = InterlockedDecrement(&async_activate_test.ref); + if (ref == 1) + SetEvent(async_activate_test.evt); + return ref; +} + +static HRESULT WINAPI async_activate_ActivateCompleted( + IActivateAudioInterfaceCompletionHandler *iface, + IActivateAudioInterfaceAsyncOperation *op) +{ + HRESULT hr; + + EnterCriticalSection(&async_activate_test.lock); + ok(op == async_activate_test.op, + "%s: Got different completion operation\n", + async_activate_test.msg_pfx); + LeaveCriticalSection(&async_activate_test.lock); + + hr = IActivateAudioInterfaceAsyncOperation_GetActivateResult(op, + &async_activate_test.result_hr, &async_activate_test.result_iface); + ok(hr == S_OK, + "%s: GetActivateResult failed: %08x\n", + async_activate_test.msg_pfx, hr); + + return S_OK; +} + +static IActivateAudioInterfaceCompletionHandlerVtbl async_activate_vtbl = { + async_activate_QueryInterface, + async_activate_AddRef, + async_activate_Release, + async_activate_ActivateCompleted, +}; + +static IActivateAudioInterfaceCompletionHandler async_activate_done = { + &async_activate_vtbl +};
static void test_MediaDeviceStatics(void) { @@ -51,8 +135,11 @@ static void test_MediaDeviceStatics(void) IActivationFactory *factory = NULL; IInspectable *inspectable = NULL, *tmp_inspectable = NULL; IAgileObject *agile_object = NULL, *tmp_agile_object = NULL; + HANDLE done_evt = CreateEventW(NULL, FALSE, FALSE, NULL); + IMMDevice *mmdev; HSTRING str; HRESULT hr; + DWORD dr;
hr = pWindowsCreateString(media_device_statics_name, wcslen(media_device_statics_name), &str); ok(hr == S_OK, "WindowsCreateString failed, hr %#x\n", hr); @@ -85,13 +172,100 @@ static void test_MediaDeviceStatics(void) IInspectable_Release(inspectable); IActivationFactory_Release(factory);
+ InitializeCriticalSection(&async_activate_test.lock); + + /* test default capture device creation */ + hr = IMediaDeviceStatics_GetDefaultAudioCaptureId(media_device_statics, AudioDeviceRole_Default, &str); + ok(hr == S_OK, "GetDefaultAudioCaptureId failed: %08x\n", hr); + ok((!!g_default_capture_id) == (!!str), + "Presence of default capture device doesn't match expected state\n"); + + if (g_default_capture_id) + { + ok(wcsstr(pWindowsGetStringRawBuffer(str, NULL), g_default_capture_id) != NULL, + "Expected to find substring of default capture id in %s\n", + wine_dbgstr_w(pWindowsGetStringRawBuffer(str, NULL))); + + /* returned id does not work in GetDevice... */ + hr = IMMDeviceEnumerator_GetDevice(g_mmdevenum, pWindowsGetStringRawBuffer(str, NULL), &mmdev); + ok(hr == E_INVALIDARG, "GetDevice gave wrong error: %08x\n", hr); + + /* ...but does work in ActivateAudioInterfaceAsync */ + async_activate_test.ref = 1; + async_activate_test.evt = done_evt; + async_activate_test.op = NULL; + async_activate_test.result_iface = NULL; + async_activate_test.result_hr = 0; + strcpy(async_activate_test.msg_pfx, "capture_activate"); + + EnterCriticalSection(&async_activate_test.lock); + hr = pActivateAudioInterfaceAsync(pWindowsGetStringRawBuffer(str, NULL), + &IID_IAudioClient2, NULL, &async_activate_done, &async_activate_test.op); + ok(hr == S_OK, "ActivateAudioInterfaceAsync failed: %08x\n", hr); + LeaveCriticalSection(&async_activate_test.lock); + + IActivateAudioInterfaceAsyncOperation_Release(async_activate_test.op); + + dr = WaitForSingleObject(async_activate_test.evt, 1000); /* wait for all refs other than our own to be released */ + ok(dr == WAIT_OBJECT_0, "Timed out waiting for async activate to complete\n"); + ok(async_activate_test.result_hr == S_OK, "Got unexpected activation result: %08x\n", async_activate_test.result_hr); + ok(async_activate_test.result_iface != NULL, "Expected to get WASAPI interface, but got NULL\n"); + IUnknown_Release(async_activate_test.result_iface); + + pWindowsDeleteString(str); + } + + /* test default render device creation */ + hr = IMediaDeviceStatics_GetDefaultAudioRenderId(media_device_statics, AudioDeviceRole_Default, &str); + ok(hr == S_OK, "GetDefaultAudioRenderId failed: %08x\n", hr); + ok((!!g_default_render_id) == (!!str), + "Presence of default render device doesn't match expected state\n"); + + if (g_default_render_id) + { + ok(wcsstr(pWindowsGetStringRawBuffer(str, NULL), g_default_render_id) != NULL, + "Expected to find substring of default render id in %s\n", + wine_dbgstr_w(pWindowsGetStringRawBuffer(str, NULL))); + + /* returned id does not work in GetDevice... */ + hr = IMMDeviceEnumerator_GetDevice(g_mmdevenum, pWindowsGetStringRawBuffer(str, NULL), &mmdev); + ok(hr == E_INVALIDARG, "GetDevice gave wrong error: %08x\n", hr); + + /* ...but does work in ActivateAudioInterfaceAsync */ + async_activate_test.ref = 1; + async_activate_test.evt = done_evt; + async_activate_test.op = NULL; + async_activate_test.result_iface = NULL; + async_activate_test.result_hr = 0; + strcpy(async_activate_test.msg_pfx, "render_activate"); + + EnterCriticalSection(&async_activate_test.lock); + hr = pActivateAudioInterfaceAsync(pWindowsGetStringRawBuffer(str, NULL), + &IID_IAudioClient2, NULL, &async_activate_done, &async_activate_test.op); + ok(hr == S_OK, "ActivateAudioInterfaceAsync failed: %08x\n", hr); + LeaveCriticalSection(&async_activate_test.lock); + + IActivateAudioInterfaceAsyncOperation_Release(async_activate_test.op); + + dr = WaitForSingleObject(async_activate_test.evt, 1000); /* wait for all refs other than our own to be released */ + ok(dr == WAIT_OBJECT_0, "Timed out waiting for async activate to complete\n"); + ok(async_activate_test.result_hr == S_OK, "Got unexpected activation result: %08x\n", async_activate_test.result_hr); + ok(async_activate_test.result_iface != NULL, "Expected to get WASAPI interface, but got NULL\n"); + IUnknown_Release(async_activate_test.result_iface); + + pWindowsDeleteString(str); + } + /* cleanup */ + CloseHandle(done_evt); + DeleteCriticalSection(&async_activate_test.lock); IMediaDeviceStatics_Release(media_device_statics); }
START_TEST(devices) { - HMODULE combase; + HMODULE combase, mmdevapi; + IMMDevice *mmdev; HRESULT hr;
if (!(combase = LoadLibraryW(L"combase.dll"))) @@ -112,12 +286,59 @@ START_TEST(devices) LOAD_FUNCPTR(RoUninitialize); LOAD_FUNCPTR(WindowsCreateString); LOAD_FUNCPTR(WindowsDeleteString); + LOAD_FUNCPTR(WindowsGetStringRawBuffer); +#undef LOAD_FUNCPTR + + if (!(mmdevapi = LoadLibraryW(L"mmdevapi.dll"))) + { + win_skip("Failed to load mmdevapi.dll, skipping tests\n"); + return; + } + +#define LOAD_FUNCPTR(x) \ + if (!(p##x = (void*)GetProcAddress(mmdevapi, #x))) \ + { \ + win_skip("Failed to find %s in mmdevapi.dll, skipping tests.\n", #x); \ + return; \ + } + + LOAD_FUNCPTR(ActivateAudioInterfaceAsync); #undef LOAD_FUNCPTR
hr = pRoInitialize(RO_INIT_MULTITHREADED); ok(hr == S_OK, "RoInitialize failed, hr %#x\n", hr);
+ hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, + CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&g_mmdevenum); + ok(hr == S_OK, "Couldn't make MMDeviceEnumerator: %08x\n", hr); + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(g_mmdevenum, eCapture, eMultimedia, &mmdev); + if (hr == S_OK) + { + hr = IMMDevice_GetId(mmdev, &g_default_capture_id); + ok(hr == S_OK, "IMMDevice::GetId(capture) failed: %08x\n", hr); + + IMMDevice_Release(mmdev); + } + if (hr != S_OK) + g_default_capture_id = NULL; + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(g_mmdevenum, eRender, eMultimedia, &mmdev); + if (hr == S_OK) + { + hr = IMMDevice_GetId(mmdev, &g_default_render_id); + ok(hr == S_OK, "IMMDevice::GetId(render) failed: %08x\n", hr); + + IMMDevice_Release(mmdev); + } + if (hr != S_OK) + g_default_render_id = NULL; + test_MediaDeviceStatics();
+ CoTaskMemFree(g_default_capture_id); + CoTaskMemFree(g_default_render_id); + IMMDeviceEnumerator_Release(g_mmdevenum); + pRoUninitialize(); }