]> Repos - portaudio/commitdiff
wasapi: Support multiple devices in the device list on Windows UWP/WinRT platform...
authordmitrykos <dmitrykos@neutroncode.com>
Mon, 26 Oct 2020 14:40:25 +0000 (16:40 +0200)
committerdmitrykos <dmitrykos@neutroncode.com>
Mon, 26 Oct 2020 14:40:25 +0000 (16:40 +0200)
include/pa_win_wasapi.h
src/hostapi/wasapi/pa_win_wasapi.c

index 9b827d1ea25f443541d609a9b883ba1f72e7af2c..e8b93b67bc84d81c442a355a550602d6129a5765 100644 (file)
@@ -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<const UINT16 *> ids, names;
+        std::vector<PaWasapiDeviceRole> 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
index 866d372f89f51619c0dd54c2a86b7a1b2c13301a..c155f1f17569c40c622ed3b5e1977f554e92d9d3 100644 (file)
@@ -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