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.

This commit is contained in:
dmitrykos 2020-10-26 16:40:25 +02:00
commit a5fb8d94aa
2 changed files with 360 additions and 135 deletions

View file

@ -516,28 +516,6 @@ PaError PaWasapi_GetJackCount( PaDeviceIndex device, int *pJackCount );
PaError PaWasapi_GetJackDescription( PaDeviceIndex device, int jackIndex, PaWasapiJackDescription *pJackDescription );
/** Set default interface Id.
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
Windows::Media::Devices::MediaDevice::GetDefaultAudioRenderId() or
Windows::Media::Devices::MediaDevice::GetDefaultAudioCaptureId() API.
@note UWP platform only.
@param pId Interface 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.
*/
PaError PaWasapi_SetDefaultInterfaceId( unsigned short *pId, int bOutput );
/** Set stream state handler.
@param pStream Pointer to PaStream object.
@ -549,6 +527,74 @@ PaError PaWasapi_SetDefaultInterfaceId( unsigned short *pId, int bOutput );
PaError PaWasapi_SetStreamStateHandler( PaStream *pStream, PaWasapiStreamStateCallback fnStateHandler, void *pUserData );
/** 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.
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 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 library is not compiled
for UWP/WinRT platform. If Id is longer than PA_WASAPI_DEVICE_ID_LEN characters paBufferTooBig will
be returned.
*/
PaError PaWasapiWinrt_SetDefaultDeviceId( const unsigned short *pId, int bOutput );
/** Populate the device list.
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.
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 PaWasapiWinrt_PopulateDeviceList( const unsigned short **pId, const unsigned short **pName,
const PaWasapiDeviceRole *pRole, unsigned int count, int bOutput );
/*
IMPORTANT:
@ -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

View 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;
}
for (j = 0; name[j] != 0; ++j)
{
if (j >= PA_WASAPI_DEVICE_NAME_LEN)
return paBufferTooBig;
}
}
else
// Set Id and Name (or reset to all 0 if NULL is provided)
for (i = 0; i < count; ++i)
{
memset(g_DefaultCaptureId, 0, sizeof(g_DefaultCaptureId));
if (pId != NULL)
{
for (i = 0; (pId[i] != 0) && (i < STATIC_ARRAY_SIZE(g_DefaultCaptureId)); ++i)
g_DefaultCaptureId[i] = pId[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