From a5fb8d94aa77c517022f9a6b112acb3cf61362dc Mon Sep 17 00:00:00 2001 From: dmitrykos Date: Mon, 26 Oct 2020 16:40:25 +0200 Subject: [PATCH] wasapi: Support multiple devices in the device list on Windows UWP/WinRT platform. All UWP/WinRT functions are now prefixed as PaWasapiWinrt_ to be clearly visible, therefore PaWasapi_SetDefaultInterfaceId() was renamed to PaWasapiWinrt_SetDefaultDeviceId(). Expanded Important notes regarding UWP/WinRT platform and how to make the fully-functional device list with the corresponding pseudo-code example for easier understanding of the functionality. --- include/pa_win_wasapi.h | 118 +++++++-- src/hostapi/wasapi/pa_win_wasapi.c | 377 ++++++++++++++++++++--------- 2 files changed, 360 insertions(+), 135 deletions(-) diff --git a/include/pa_win_wasapi.h b/include/pa_win_wasapi.h index 9b827d1..e8b93b6 100644 --- a/include/pa_win_wasapi.h +++ b/include/pa_win_wasapi.h @@ -516,37 +516,83 @@ PaError PaWasapi_GetJackCount( PaDeviceIndex device, int *pJackCount ); PaError PaWasapi_GetJackDescription( PaDeviceIndex device, int jackIndex, PaWasapiJackDescription *pJackDescription ); -/** Set default interface Id. +/** Set stream state handler. + + @param pStream Pointer to PaStream object. + @param fnStateHandler Pointer to state handling function. + @param pUserData Pointer to user data. + + @return Error code indicating success or failure. +*/ +PaError PaWasapi_SetStreamStateHandler( PaStream *pStream, PaWasapiStreamStateCallback fnStateHandler, void *pUserData ); + - By default PA implementation will use DEVINTERFACE_AUDIO_RENDER and - DEVINTERFACE_AUDIO_CAPTURE if device Id is not provided explicitly. These default Ids - will not allow to use Exclusive mode on UWP platform and thus you must provide - device Id explicitly via this API before calling Pa_OpenStream(). - Device Ids on UWP platform are obtainable via +/** Set default device Id. + + By default implementation will use the DEVINTERFACE_AUDIO_RENDER and + DEVINTERFACE_AUDIO_CAPTURE Ids if device Id is not provided explicitly. These default Ids + will not allow to use Exclusive mode on UWP/WinRT platform and thus you must provide + device Id explicitly via this API before calling the Pa_OpenStream(). + + Device Ids on UWP platform are obtainable via: Windows::Media::Devices::MediaDevice::GetDefaultAudioRenderId() or Windows::Media::Devices::MediaDevice::GetDefaultAudioCaptureId() API. - @note UWP platform only. + After the call completes, memory referenced by pointers can be freed, as implementation keeps its own copy. + + Call this function before calling Pa_IsFormatSupported() when Exclusive mode is requested. + + See an example in the IMPORTANT notes. + + @note UWP/WinRT platform only. - @param pId Interface Id, pointer to the 16-bit Unicode string (WCHAR). If NULL then device Id + @param pId Device Id, pointer to the 16-bit Unicode string (WCHAR). If NULL then device Id will be reset to the default, e.g. DEVINTERFACE_AUDIO_RENDER or DEVINTERFACE_AUDIO_CAPTURE. @param bOutput TRUE (1) for output (render), FALSE (0) for input (capture). - @return Error code indicating success or failure. Will return paIncompatibleStreamHostApi if PA is not compiled - for UWP platform. If Id is longer than 4096 characters paBufferTooBig will be returned. + @return Error code indicating success or failure. Will return paIncompatibleStreamHostApi if library is not compiled + for UWP/WinRT platform. If Id is longer than PA_WASAPI_DEVICE_ID_LEN characters paBufferTooBig will + be returned. */ -PaError PaWasapi_SetDefaultInterfaceId( unsigned short *pId, int bOutput ); +PaError PaWasapiWinrt_SetDefaultDeviceId( const unsigned short *pId, int bOutput ); -/** Set stream state handler. +/** Populate the device list. - @param pStream Pointer to PaStream object. - @param fnStateHandler Pointer to state handling function. - @param pUserData Pointer to user data. + By default the implementation will rely on DEVINTERFACE_AUDIO_RENDER and DEVINTERFACE_AUDIO_CAPTURE as + default devices. If device Id is provided by PaWasapiWinrt_SetDefaultDeviceId() then those + device Ids will be used as default and only devices for the device list. - @return Error code indicating success or failure. + By populating the device list you can provide an additional available audio devices of the system to PA + which are obtainable by: + Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(selector) where selector is obtainable by + Windows::Media::Devices::MediaDevice::GetAudioRenderSelector() or + Windows::Media::Devices::MediaDevice::GetAudioCaptureSelector() API. + + After the call completes, memory referenced by pointers can be freed, as implementation keeps its own copy. + + You must call PaWasapi_UpdateDeviceList() to update the internal device list of the implementation after + calling this function. + + See an example in the IMPORTANT notes. + + @note UWP/WinRT platform only. + + @param pId Array of device Ids, pointer to the array of pointers of 16-bit Unicode string (WCHAR). If NULL + and count is also 0 then device Ids will be reset to the default. Required. + @param pName Array of device Names, pointer to the array of pointers of 16-bit Unicode string (WCHAR). Optional. + @param pRole Array of device Roles, see PaWasapiDeviceRole and PaWasapi_GetDeviceRole() for more details. Optional. + @param count Number of devices, the number of array elements (pId, pName, pRole). Maximum count of devices + is limited by PA_WASAPI_DEVICE_MAX_COUNT. + @param bOutput TRUE (1) for output (render), FALSE (0) for input (capture). + + @return Error code indicating success or failure. Will return paIncompatibleStreamHostApi if library is not compiled + for UWP/WinRT platform. If Id is longer than PA_WASAPI_DEVICE_ID_LEN characters paBufferTooBig will + be returned. If Name is longer than PA_WASAPI_DEVICE_NAME_LEN characters paBufferTooBig will + be returned. */ -PaError PaWasapi_SetStreamStateHandler( PaStream *pStream, PaWasapiStreamStateCallback fnStateHandler, void *pUserData ); +PaError PaWasapiWinrt_PopulateDeviceList( const unsigned short **pId, const unsigned short **pName, + const PaWasapiDeviceRole *pRole, unsigned int count, int bOutput ); /* @@ -626,6 +672,44 @@ PaError PaWasapi_SetStreamStateHandler( PaStream *pStream, PaWasapiStreamStateCa 2) pXXX - pointer 3) fnXXX - pointer to function 4) structure members are never prefixed with a type distinguisher + + + UWP/WinRT: + + This platform has number of limitations which do not allow to enumerate audio devices without + an additional external help. Enumeration is possible though from C++/CX, check the related API + Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(). + + The main limitation is an absence of the device enumeration from inside the PA's implementation. + This problem can be solved by using the following functions: + + PaWasapiWinrt_SetDefaultDeviceId() - to set default input/output device, + PaWasapiWinrt_PopulateDeviceList() - to populate device list with devices. + + Here is an example of populating the device list which can also be updated dynamically depending on + whether device was removed from or added to the system: + + ---------------- + + std::vector ids, names; + std::vector role; + + ids.resize(count); + names.resize(count); + role.resize(count); + + for (UINT32 i = 0; i < count; ++i) + { + ids[i] = (const UINT16 *)device_ids[i].c_str(); + names[i] = (const UINT16 *)device_names[i].c_str(); + role[i] = eRoleUnknownFormFactor; + } + + PaWasapiWinrt_SetDefaultDeviceId((const UINT16 *)default_device_id.c_str(), !capture); + PaWasapiWinrt_PopulateDeviceList(ids.data(), names.data(), role.data(), count, !capture); + PaWasapi_UpdateDeviceList(); + + ---------------- */ #ifdef __cplusplus diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c index 866d372..c155f1f 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -309,10 +309,10 @@ PA_THREAD_FUNC ProcThreadPoll(void *param); #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 #endif -#define MAX_STR_LEN 512 - +#define PA_WASAPI_DEVICE_ID_LEN 256 +#define PA_WASAPI_DEVICE_NAME_LEN 128 #ifdef PA_WINRT - #define PA_WASAPI_DEVICE_ID_LEN 4096 + #define PA_WASAPI_DEVICE_MAX_COUNT 16 #endif enum { S_INPUT = 0, S_OUTPUT = 1, S_COUNT = 2, S_FULLDUPLEX = 0 }; @@ -430,8 +430,8 @@ typedef struct PaWasapiDeviceInfo IMMDevice *device; #endif - // from GetId - WCHAR szDeviceID[MAX_STR_LEN]; + // device Id + WCHAR deviceId[PA_WASAPI_DEVICE_ID_LEN]; // from GetState DWORD state; @@ -450,7 +450,7 @@ typedef struct PaWasapiDeviceInfo // Fields filled from IMMEndpoint'sGetDataFlow EDataFlow flow; - // Formfactor + // Form-factor EndpointFormFactor formFactor; } PaWasapiDeviceInfo; @@ -623,10 +623,44 @@ static void *PaWasapi_ReallocateMemory(void *prev, size_t size); static void PaWasapi_FreeMemory(void *ptr); static PaSampleFormat WaveToPaFormat(const WAVEFORMATEXTENSIBLE *fmtext); -// Local statics +// WinRT (UWP) device list #ifdef PA_WINRT -static OLECHAR g_DefaultRenderId[PA_WASAPI_DEVICE_ID_LEN] = { 0 }; -static OLECHAR g_DefaultCaptureId[PA_WASAPI_DEVICE_ID_LEN] = { 0 }; +typedef struct PaWasapiWinrtDeviceInfo +{ + WCHAR id[PA_WASAPI_DEVICE_ID_LEN]; + WCHAR name[PA_WASAPI_DEVICE_NAME_LEN]; + EndpointFormFactor formFactor; +} +PaWasapiWinrtDeviceInfo; +typedef struct PaWasapiWinrtDeviceListRole +{ + WCHAR defaultId[PA_WASAPI_DEVICE_ID_LEN]; + PaWasapiWinrtDeviceInfo devices[PA_WASAPI_DEVICE_MAX_COUNT]; + UINT32 deviceCount; +} +PaWasapiWinrtDeviceListRole; +typedef struct PaWasapiWinrtDeviceList +{ + PaWasapiWinrtDeviceListRole render; + PaWasapiWinrtDeviceListRole capture; +} +PaWasapiWinrtDeviceList; +static PaWasapiWinrtDeviceList g_DeviceListInfo = { 0 }; +#endif + +// WinRT (UWP) device list context +#ifdef PA_WINRT +typedef struct PaWasapiWinrtDeviceListContextEntry +{ + PaWasapiWinrtDeviceInfo *info; + EDataFlow flow; +} +PaWasapiWinrtDeviceListContextEntry; +typedef struct PaWasapiWinrtDeviceListContext +{ + PaWasapiWinrtDeviceListContextEntry devices[PA_WASAPI_DEVICE_MAX_COUNT * 2]; +} +PaWasapiWinrtDeviceListContext; #endif // ------------------------------------------------------------------------------------------ @@ -1593,65 +1627,72 @@ static HRESULT (STDMETHODCALLTYPE PaActivateAudioInterfaceCompletionHandler_Acti return hr; } -static IActivateAudioInterfaceCompletionHandler *CreateActivateAudioInterfaceCompletionHandler(const IID *iid, void **obj) +static IActivateAudioInterfaceCompletionHandler *CreateActivateAudioInterfaceCompletionHandler(const IID *iid, void **client) { PaActivateAudioInterfaceCompletionHandler *handler = PaUtil_AllocateMemory(sizeof(PaActivateAudioInterfaceCompletionHandler)); - ZeroMemory(handler, sizeof(*handler)); + + memset(handler, 0, sizeof(*handler)); + handler->parent.lpVtbl = PaUtil_AllocateMemory(sizeof(*handler->parent.lpVtbl)); handler->parent.lpVtbl->QueryInterface = &PaActivateAudioInterfaceCompletionHandler_QueryInterface; handler->parent.lpVtbl->AddRef = &PaActivateAudioInterfaceCompletionHandler_AddRef; handler->parent.lpVtbl->Release = &PaActivateAudioInterfaceCompletionHandler_Release; handler->parent.lpVtbl->ActivateCompleted = &PaActivateAudioInterfaceCompletionHandler_ActivateCompleted; - handler->refs = 1; + handler->refs = 1; handler->in.iid = iid; - handler->in.obj = obj; + handler->in.obj = client; + return (IActivateAudioInterfaceCompletionHandler *)handler; } #endif // ------------------------------------------------------------------------------------------ #ifdef PA_WINRT -static HRESULT ActivateAudioInterface_WINRT(EDataFlow flow, const IID *iid, void **obj) +static HRESULT WinRT_GetDefaultDeviceId(WCHAR *deviceId, UINT32 deviceIdMax, EDataFlow flow) { -#define PA_WASAPI_DEVICE_PATH_LEN 64 - - PaError result = paNoError; - HRESULT hr = S_OK; - IActivateAudioInterfaceAsyncOperation *asyncOp = NULL; - IActivateAudioInterfaceCompletionHandler *handler = CreateActivateAudioInterfaceCompletionHandler(iid, obj); - PaActivateAudioInterfaceCompletionHandler *handlerImpl = (PaActivateAudioInterfaceCompletionHandler *)handler; - OLECHAR tmp[PA_WASAPI_DEVICE_PATH_LEN] = { 0 }; - OLECHAR *devicePath = tmp; - - // Get device path in form L"{DEVICE_GUID}" switch (flow) { case eRender: - if (g_DefaultRenderId[0] != 0) - devicePath = g_DefaultRenderId; + if (g_DeviceListInfo.render.defaultId[0] != 0) + wcsncpy_s(deviceId, deviceIdMax, g_DeviceListInfo.render.defaultId, wcslen(g_DeviceListInfo.render.defaultId)); else - StringFromGUID2(&DEVINTERFACE_AUDIO_RENDER, devicePath, PA_WASAPI_DEVICE_PATH_LEN - 1); + StringFromGUID2(&DEVINTERFACE_AUDIO_RENDER, deviceId, deviceIdMax); break; case eCapture: - if (g_DefaultCaptureId[0] != 0) - devicePath = g_DefaultCaptureId; + if (g_DeviceListInfo.capture.defaultId[0] != 0) + wcsncpy_s(deviceId, deviceIdMax, g_DeviceListInfo.capture.defaultId, wcslen(g_DeviceListInfo.capture.defaultId)); else - StringFromGUID2(&DEVINTERFACE_AUDIO_CAPTURE, devicePath, PA_WASAPI_DEVICE_PATH_LEN - 1); + StringFromGUID2(&DEVINTERFACE_AUDIO_CAPTURE, deviceId, deviceIdMax); break; default: return S_FALSE; - } + } + + return S_OK; +} +#endif +// ------------------------------------------------------------------------------------------ +#ifdef PA_WINRT +static HRESULT WinRT_ActivateAudioInterface(const WCHAR *deviceId, const IID *iid, void **client) +{ + PaError result = paNoError; + HRESULT hr = S_OK; + IActivateAudioInterfaceAsyncOperation *asyncOp = NULL; + IActivateAudioInterfaceCompletionHandler *handler = CreateActivateAudioInterfaceCompletionHandler(iid, client); + PaActivateAudioInterfaceCompletionHandler *handlerImpl = (PaActivateAudioInterfaceCompletionHandler *)handler; + UINT32 sleepToggle = 0; + // Async operation will call back to IActivateAudioInterfaceCompletionHandler::ActivateCompleted // which must be an agile interface implementation - hr = ActivateAudioInterfaceAsync(devicePath, iid, NULL, handler, &asyncOp); + hr = ActivateAudioInterfaceAsync(deviceId, iid, NULL, handler, &asyncOp); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); // Wait in busy loop for async operation to complete // Use Interlocked API here to ensure that ->done variable is read every time through the loop while (SUCCEEDED(hr) && !InterlockedOr(&handlerImpl->done, 0)) { - Sleep(1); + Sleep(sleepToggle ^= 1); } hr = handlerImpl->out.hr; @@ -1662,8 +1703,6 @@ error: SAFE_RELEASE(handler); return hr; - -#undef PA_WASAPI_DEVICE_PATH_LEN } #endif @@ -1677,7 +1716,7 @@ static HRESULT ActivateAudioInterface(const PaWasapiDeviceInfo *deviceInfo, cons if (FAILED(hr = IMMDevice_Activate(deviceInfo->device, GetAudioClientIID(), CLSCTX_ALL, NULL, (void **)client))) return hr; #else - if (FAILED(hr = ActivateAudioInterface_WINRT(deviceInfo->flow, GetAudioClientIID(), (void **)client))) + if (FAILED(hr = WinRT_ActivateAudioInterface(deviceInfo->deviceId, GetAudioClientIID(), (void **)client))) return hr; #endif @@ -1766,37 +1805,41 @@ static PaError FillInactiveDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, P } // ------------------------------------------------------------------------------------------ -static PaError FillDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, void *pEndPoints, INT32 index, const WCHAR *defaultRenderer, - const WCHAR *defaultCapturer, PaDeviceInfo *deviceInfo, PaWasapiDeviceInfo *wasapiDeviceInfo) +static PaError FillDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, void *pEndPoints, INT32 index, const WCHAR *defaultRenderId, + const WCHAR *defaultCaptureId, PaDeviceInfo *deviceInfo, PaWasapiDeviceInfo *wasapiDeviceInfo +#ifdef PA_WINRT + , PaWasapiWinrtDeviceListContext *deviceListContext +#endif +) { HRESULT hr; PaError result; PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; +#ifdef PA_WINRT + PaWasapiWinrtDeviceListContextEntry *listEntry = &deviceListContext->devices[index]; + (void)pEndPoints; + (void)defaultRenderId; + (void)defaultCaptureId; +#endif #ifndef PA_WINRT hr = IMMDeviceCollection_Item((IMMDeviceCollection *)pEndPoints, index, &wasapiDeviceInfo->device); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - // getting ID + // Get device Id { - WCHAR *pszDeviceId; + WCHAR *deviceId; - hr = IMMDevice_GetId(wasapiDeviceInfo->device, &pszDeviceId); + hr = IMMDevice_GetId(wasapiDeviceInfo->device, &deviceId); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - wcsncpy(wasapiDeviceInfo->szDeviceID, pszDeviceId, MAX_STR_LEN - 1); - CoTaskMemFree(pszDeviceId); - - if ((defaultCapturer != NULL) && (lstrcmpW(wasapiDeviceInfo->szDeviceID, defaultCapturer) == 0)) - hostApi->info.defaultInputDevice = hostApi->info.deviceCount; - - if ((defaultRenderer != NULL) && (lstrcmpW(wasapiDeviceInfo->szDeviceID, defaultRenderer) == 0)) - hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; + wcsncpy(wasapiDeviceInfo->deviceId, deviceId, PA_WASAPI_DEVICE_ID_LEN - 1); + CoTaskMemFree(deviceId); } + // Get state of the device hr = IMMDevice_GetState(wasapiDeviceInfo->device, &wasapiDeviceInfo->state); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - if (wasapiDeviceInfo->state != DEVICE_STATE_ACTIVE) { PRINT(("WASAPI device: %d is not currently available (state:%d)\n", index, wasapiDeviceInfo->state)); @@ -1818,16 +1861,16 @@ static PaError FillDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, void *pEn hr = IPropertyStore_GetValue(pProperty, &PKEY_Device_FriendlyName, &value); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - if ((deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, MAX_STR_LEN + 1)) == NULL) + if ((deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, PA_WASAPI_DEVICE_NAME_LEN)) == NULL) { result = paInsufficientMemory; PropVariantClear(&value); goto error; } if (value.pwszVal) - WideCharToMultiByte(CP_UTF8, 0, value.pwszVal, (int)wcslen(value.pwszVal), (char *)deviceInfo->name, MAX_STR_LEN - 1, 0, 0); + WideCharToMultiByte(CP_UTF8, 0, value.pwszVal, (INT32)wcslen(value.pwszVal), (char *)deviceInfo->name, PA_WASAPI_DEVICE_NAME_LEN - 1, 0, 0); else - _snprintf((char *)deviceInfo->name, MAX_STR_LEN - 1, "baddev%d", index); + _snprintf((char *)deviceInfo->name, PA_WASAPI_DEVICE_NAME_LEN - 1, "baddev%d", index); PropVariantClear(&value); @@ -1879,24 +1922,39 @@ static PaError FillDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, void *pEn SAFE_RELEASE(pProperty); } #else - (void)pEndPoints; - (void)defaultRenderer; - (void)defaultCapturer; + // Set device Id + wcsncpy(wasapiDeviceInfo->deviceId, listEntry->info->id, PA_WASAPI_DEVICE_ID_LEN - 1); + + // Set device name + if ((deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, PA_WASAPI_DEVICE_NAME_LEN)) == NULL) + { + result = paInsufficientMemory; + goto error; + } + ((char *)deviceInfo->name)[0] = 0; + if (listEntry->info->name[0] != 0) + WideCharToMultiByte(CP_UTF8, 0, listEntry->info->name, (INT32)wcslen(listEntry->info->name), (char *)deviceInfo->name, PA_WASAPI_DEVICE_NAME_LEN - 1, 0, 0); + if (deviceInfo->name[0] == 0) // fallback if WideCharToMultiByte is failed, or listEntry is nameless + _snprintf((char *)deviceInfo->name, PA_WASAPI_DEVICE_NAME_LEN - 1, "WASAPI_%s:%d", (listEntry->flow == eRender ? "Output" : "Input"), index); + + // Form-factor + wasapiDeviceInfo->formFactor = listEntry->info->formFactor; + + // Set data flow + wasapiDeviceInfo->flow = listEntry->flow; #endif - // Getting a temporary IAudioClient for more fields - // we make sure NOT to call Initialize yet! + // Set default Output/Input devices + if ((defaultRenderId != NULL) && (wcsncmp(wasapiDeviceInfo->deviceId, defaultRenderId, PA_WASAPI_DEVICE_NAME_LEN - 1) == 0)) + hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; + if ((defaultCaptureId != NULL) && (wcsncmp(wasapiDeviceInfo->deviceId, defaultCaptureId, PA_WASAPI_DEVICE_NAME_LEN - 1) == 0)) + hostApi->info.defaultInputDevice = hostApi->info.deviceCount; + + // Get a temporary IAudioClient for more details { IAudioClient *tmpClient; WAVEFORMATEX *mixFormat; - #ifdef PA_WINRT - // Set flow as ActivateAudioInterface depends on it and selects corresponding - // direction for the Audio Client - wasapiDeviceInfo->flow = (index == 0 ? eRender : eCapture); - #endif - - // Create temp Audio Client instance to query additional details hr = ActivateAudioInterface(wasapiDeviceInfo, NULL, &tmpClient); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); @@ -1926,30 +1984,11 @@ static PaError FillDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, void *pEn #ifdef PA_WINRT if (SUCCEEDED(hr)) { - // Default device - if (index == 0) - hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; - else - hostApi->info.defaultInputDevice = hostApi->info.deviceCount; - - // State + // Set state wasapiDeviceInfo->state = DEVICE_STATE_ACTIVE; - // Default format is always a mix format + // Default format (Shared mode) is always a mix format wasapiDeviceInfo->DefaultFormat = wasapiDeviceInfo->MixFormat; - - // Form-factor - wasapiDeviceInfo->formFactor = UnknownFormFactor; - - // Name - if ((deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, MAX_STR_LEN + 1)) == NULL) - { - SAFE_RELEASE(tmpClient); - result = paInsufficientMemory; - goto error; - } - _snprintf((char *)deviceInfo->name, MAX_STR_LEN - 1, "WASAPI_%s:%d", (index == 0 ? "Output" : "Input"), index); - PA_DEBUG(("WASAPI:%d| name[%s]\n", index, deviceInfo->name)); } #endif @@ -2049,9 +2088,9 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; PaError result = paNoError; PaDeviceInfo *deviceInfoArray; - UINT i; - WCHAR *defaultRenderer = NULL; - WCHAR *defaultCapturer = NULL; + UINT32 i; + WCHAR *defaultRenderId = NULL; + WCHAR *defaultCaptureId = NULL; #ifndef PA_WINRT HRESULT hr; IMMDeviceCollection *pEndPoints = NULL; @@ -2059,6 +2098,9 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA #else void *pEndPoints = NULL; IAudioClient *tmpClient; + PaWasapiWinrtDeviceListContext deviceListContext = { 0 }; + PaWasapiWinrtDeviceInfo defaultRender = { 0 }; + PaWasapiWinrtDeviceInfo defaultCapture = { 0 }; #endif // Make sure device list empty @@ -2084,7 +2126,7 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA } else { - hr = IMMDevice_GetId(device, &defaultRenderer); + hr = IMMDevice_GetId(device, &defaultRenderId); IMMDevice_Release(device); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); } @@ -2099,7 +2141,7 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA } else { - hr = IMMDevice_GetId(device, &defaultCapturer); + hr = IMMDevice_GetId(device, &defaultCaptureId); IMMDevice_Release(device); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); } @@ -2113,16 +2155,52 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA hr = IMMDeviceCollection_GetCount(pEndPoints, &paWasapi->deviceCount); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); #else - // Determine number of available devices by activating AudioClient for render and capture data flows - if (SUCCEEDED(ActivateAudioInterface_WINRT(eRender, GetAudioClientIID(), &tmpClient))) + WinRT_GetDefaultDeviceId(defaultRender.id, STATIC_ARRAY_SIZE(defaultRender.id) - 1, eRender); + defaultRenderId = defaultRender.id; + + WinRT_GetDefaultDeviceId(defaultCapture.id, STATIC_ARRAY_SIZE(defaultCapture.id) - 1, eCapture); + defaultCaptureId = defaultCapture.id; + + if (g_DeviceListInfo.render.deviceCount == 0) { - paWasapi->deviceCount++; - SAFE_RELEASE(tmpClient); + if (SUCCEEDED(WinRT_ActivateAudioInterface(defaultRenderId, GetAudioClientIID(), &tmpClient))) + { + deviceListContext.devices[paWasapi->deviceCount].info = &defaultRender; + deviceListContext.devices[paWasapi->deviceCount].flow = eRender; + paWasapi->deviceCount++; + + SAFE_RELEASE(tmpClient); + } } - if (SUCCEEDED(ActivateAudioInterface_WINRT(eCapture, GetAudioClientIID(), &tmpClient))) + else { - paWasapi->deviceCount++; - SAFE_RELEASE(tmpClient); + for (i = 0; i < g_DeviceListInfo.render.deviceCount; ++i) + { + deviceListContext.devices[paWasapi->deviceCount].info = &g_DeviceListInfo.render.devices[i]; + deviceListContext.devices[paWasapi->deviceCount].flow = eRender; + paWasapi->deviceCount++; + } + } + + if (g_DeviceListInfo.capture.deviceCount == 0) + { + if (SUCCEEDED(WinRT_ActivateAudioInterface(defaultCaptureId, GetAudioClientIID(), &tmpClient))) + { + deviceListContext.devices[paWasapi->deviceCount].info = &defaultCapture; + deviceListContext.devices[paWasapi->deviceCount].flow = eCapture; + paWasapi->deviceCount++; + + SAFE_RELEASE(tmpClient); + } + } + else + { + for (i = 0; i < g_DeviceListInfo.capture.deviceCount; ++i) + { + deviceListContext.devices[paWasapi->deviceCount].info = &g_DeviceListInfo.capture.devices[i]; + deviceListContext.devices[paWasapi->deviceCount].flow = eCapture; + paWasapi->deviceCount++; + } } #endif @@ -2143,8 +2221,12 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA FillBaseDeviceInfo(deviceInfo, hostApiIndex); - if ((result = FillDeviceInfo(paWasapi, pEndPoints, i, defaultRenderer, defaultCapturer, - deviceInfo, &paWasapi->devInfo[i])) != paNoError) + if ((result = FillDeviceInfo(paWasapi, pEndPoints, i, defaultRenderId, defaultCaptureId, + deviceInfo, &paWasapi->devInfo[i] + #ifdef PA_WINRT + , &deviceListContext + #endif + )) != paNoError) { // Faulty device is made inactive if ((result = FillInactiveDeviceInfo(paWasapi, deviceInfo)) != paNoError) @@ -2182,8 +2264,8 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA done: #ifndef PA_WINRT - CoTaskMemFree(defaultRenderer); - CoTaskMemFree(defaultCapturer); + CoTaskMemFree(defaultRenderId); + CoTaskMemFree(defaultCaptureId); SAFE_RELEASE(pEndPoints); SAFE_RELEASE(pEnumerator); #endif @@ -5346,10 +5428,30 @@ PaError PaWasapi_GetAudioClient(PaStream *pStream, void **pAudioClient, int bOut } // ------------------------------------------------------------------------------------------ -PaError PaWasapi_SetDefaultInterfaceId( unsigned short *pId, int bOutput ) +#ifdef PA_WINRT +static void CopyNameOrIdString(WCHAR *dst, const UINT32 dstMaxCount, const WCHAR *src) +{ + UINT32 i; + + for (i = 0; i < dstMaxCount; ++i) + dst[i] = 0; + + if (src != NULL) + { + for (i = 0; (src[i] != 0) && (i < dstMaxCount); ++i) + dst[i] = src[i]; + } +} +#endif + +// ------------------------------------------------------------------------------------------ +PaError PaWasapiWinrt_SetDefaultDeviceId( const unsigned short *pId, int bOutput ) { #ifdef PA_WINRT - int i; + INT32 i; + PaWasapiWinrtDeviceListRole *role = (bOutput ? &g_DeviceListInfo.render : &g_DeviceListInfo.capture); + + assert(STATIC_ARRAY_SIZE(role->defaultId) == PA_WASAPI_DEVICE_ID_LEN); // Validate Id length if (pId != NULL) @@ -5362,27 +5464,66 @@ PaError PaWasapi_SetDefaultInterfaceId( unsigned short *pId, int bOutput ) } // Set Id (or reset to all 0 if NULL is provided) - if (bOutput) + CopyNameOrIdString(role->defaultId, STATIC_ARRAY_SIZE(role->defaultId), pId); + + return paNoError; +#else + return paIncompatibleStreamHostApi; +#endif +} + +// ------------------------------------------------------------------------------------------ +PaError PaWasapiWinrt_PopulateDeviceList( const unsigned short **pId, const unsigned short **pName, + const PaWasapiDeviceRole *pRole, unsigned int count, int bOutput ) +{ +#ifdef PA_WINRT + UINT32 i, j; + PaWasapiWinrtDeviceListRole *role = (bOutput ? &g_DeviceListInfo.render : &g_DeviceListInfo.capture); + + memset(&role->devices, 0, sizeof(role->devices)); + role->deviceCount = 0; + + if (count == 0) + return paNoError; + else + if (count > PA_WASAPI_DEVICE_MAX_COUNT) + return paBufferTooBig; + + // pName or pRole are optional + if (pId == NULL) + return paInsufficientMemory; + + // Validate Id and Name lengths + for (i = 0; i < count; ++i) { - memset(g_DefaultRenderId, 0, sizeof(g_DefaultRenderId)); - if (pId != NULL) + const unsigned short *id = pId[i]; + const unsigned short *name = pName[i]; + + for (j = 0; id[j] != 0; ++j) { - for (i = 0; (pId[i] != 0) && (i < STATIC_ARRAY_SIZE(g_DefaultRenderId)); ++i) - g_DefaultRenderId[i] = pId[i]; + if (j >= PA_WASAPI_DEVICE_ID_LEN) + return paBufferTooBig; } - } - else - { - memset(g_DefaultCaptureId, 0, sizeof(g_DefaultCaptureId)); - if (pId != NULL) + + for (j = 0; name[j] != 0; ++j) { - for (i = 0; (pId[i] != 0) && (i < STATIC_ARRAY_SIZE(g_DefaultCaptureId)); ++i) - g_DefaultCaptureId[i] = pId[i]; + if (j >= PA_WASAPI_DEVICE_NAME_LEN) + return paBufferTooBig; } } - return paNoError; + // Set Id and Name (or reset to all 0 if NULL is provided) + for (i = 0; i < count; ++i) + { + CopyNameOrIdString(role->devices[i].id, STATIC_ARRAY_SIZE(role->devices[i].id), pId[i]); + CopyNameOrIdString(role->devices[i].name, STATIC_ARRAY_SIZE(role->devices[i].name), pName[i]); + role->devices[i].formFactor = (pRole != NULL ? (EndpointFormFactor)pRole[i] : UnknownFormFactor); + + // Count device if it has at least the Id + role->deviceCount += (role->devices[i].id[0] != 0); + } + return paNoError; #else return paIncompatibleStreamHostApi; #endif -- 2.43.0