From: dmitrykos Date: Fri, 9 Oct 2020 21:42:20 +0000 (+0300) Subject: wasapi: Refactored device list creation, if faulty audio device is reported as active... X-Git-Tag: v19.7.0~104^2 X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=522f982158d2c7cb3d5ead593eed018821c10d4c;p=portaudio wasapi: Refactored device list creation, if faulty audio device is reported as active by WASAPI then it will not break device list creation, silenced warnings when compiling for UWP platform. --- diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c index cdefe13..866d372 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -472,9 +472,6 @@ typedef struct // this is the REAL number of devices, whether they are usefull to PA or not! UINT32 deviceCount; - WCHAR defaultRenderer[MAX_STR_LEN]; - WCHAR defaultCapturer[MAX_STR_LEN]; - PaWasapiDeviceInfo *devInfo; // is TRUE when WOW64 Vista/7 Workaround is needed @@ -801,6 +798,9 @@ static BOOL SystemTimer_SetGranularity(SystemTimer *timer, UINT32 granularity) timer->granularity = 0; return FALSE; } +#else + (void)timer; + (void)granularity; #endif return TRUE; @@ -817,6 +817,8 @@ static void SystemTimer_RestoreGranularity(SystemTimer *timer) PRINT(("RestoreSystemTimer: timeEndPeriod(1) failed!\n")); } } +#else + (void)timer; #endif } @@ -829,6 +831,8 @@ static void SystemTimer_InitializeTimeGetter() //! Get high-resolution time in milliseconds (using QPC by default). static inline LONGLONG SystemTimer_GetTime(SystemTimer *timer) { + (void)timer; + // QPC: https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps if (g_SystemTimerUseQpc) { @@ -1739,116 +1743,279 @@ static void NotifyStateChanged(PaWasapiStream *stream, UINT32 flags, HRESULT hr) } // ------------------------------------------------------------------------------------------ -static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostApiIndex hostApiIndex) +static void FillBaseDeviceInfo(PaDeviceInfo *deviceInfo, PaHostApiIndex hostApiIndex) +{ + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; +} + +// ------------------------------------------------------------------------------------------ +static PaError FillInactiveDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, PaDeviceInfo *deviceInfo) +{ + if (deviceInfo->name == NULL) + deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, 1); + + if (deviceInfo->name != NULL) + { + ((char *)deviceInfo->name)[0] = 0; + } + else + return paInsufficientMemory; + + return paNoError; +} + +// ------------------------------------------------------------------------------------------ +static PaError FillDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, void *pEndPoints, INT32 index, const WCHAR *defaultRenderer, + const WCHAR *defaultCapturer, PaDeviceInfo *deviceInfo, PaWasapiDeviceInfo *wasapiDeviceInfo) { + HRESULT hr; + PaError result; PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; - PaError result = paNoError; - PaDeviceInfo *deviceInfoArray; - HRESULT hr = S_OK; - UINT i; + #ifndef PA_WINRT - IMMDeviceCollection* pEndPoints = NULL; - IMMDeviceEnumerator *pEnumerator = NULL; -#endif - IAudioClient *tmpClient; + hr = IMMDeviceCollection_Item((IMMDeviceCollection *)pEndPoints, index, &wasapiDeviceInfo->device); + IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - // Make sure device list empty - if ((paWasapi->deviceCount != 0) || (hostApi->info.deviceCount != 0)) - return paInternalError; + // getting ID + { + WCHAR *pszDeviceId; -#ifndef PA_WINRT - hr = CoCreateInstance(&pa_CLSID_IMMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, - &pa_IID_IMMDeviceEnumerator, (void **)&pEnumerator); - - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] + hr = IMMDevice_GetId(wasapiDeviceInfo->device, &pszDeviceId); + 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; + } + + hr = IMMDevice_GetState(wasapiDeviceInfo->device, &wasapiDeviceInfo->state); IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - // getting default device ids in the eMultimedia "role" + if (wasapiDeviceInfo->state != DEVICE_STATE_ACTIVE) + { + PRINT(("WASAPI device: %d is not currently available (state:%d)\n", index, wasapiDeviceInfo->state)); + } + + // Get basic device info { + IPropertyStore *pProperty; + IMMEndpoint *endpoint; + PROPVARIANT value; + + hr = IMMDevice_OpenPropertyStore(wasapiDeviceInfo->device, STGM_READ, &pProperty); + IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); + + // "Friendly" Name { - IMMDevice *defaultRenderer = NULL; - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, eRender, eMultimedia, &defaultRenderer); - if (hr != S_OK) + PropVariantInit(&value); + + 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 (hr != E_NOTFOUND) { - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - } + 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); else - { - WCHAR *pszDeviceId = NULL; - hr = IMMDevice_GetId(defaultRenderer, &pszDeviceId); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - wcsncpy(paWasapi->defaultRenderer, pszDeviceId, MAX_STR_LEN-1); - CoTaskMemFree(pszDeviceId); - IMMDevice_Release(defaultRenderer); - } + _snprintf((char *)deviceInfo->name, MAX_STR_LEN - 1, "baddev%d", index); + + PropVariantClear(&value); + + PA_DEBUG(("WASAPI:%d| name[%s]\n", index, deviceInfo->name)); } + // Default format { - IMMDevice *defaultCapturer = NULL; - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, eCapture, eMultimedia, &defaultCapturer); - if (hr != S_OK) - { - if (hr != E_NOTFOUND) { - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - } - } - else - { - WCHAR *pszDeviceId = NULL; - hr = IMMDevice_GetId(defaultCapturer, &pszDeviceId); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - wcsncpy(paWasapi->defaultCapturer, pszDeviceId, MAX_STR_LEN-1); - CoTaskMemFree(pszDeviceId); - IMMDevice_Release(defaultCapturer); - } + PropVariantInit(&value); + + hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEngine_DeviceFormat, &value); + IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); + + memcpy(&wasapiDeviceInfo->DefaultFormat, value.blob.pBlobData, min(sizeof(wasapiDeviceInfo->DefaultFormat), value.blob.cbSize)); + + PropVariantClear(&value); } - } - hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eAll, DEVICE_STATE_ACTIVE, &pEndPoints); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); + // Form factor + { + PropVariantInit(&value); - hr = IMMDeviceCollection_GetCount(pEndPoints, &paWasapi->deviceCount); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); + hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEndpoint_FormFactor, &value); + IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); + + // set + #if defined(DUMMYUNIONNAME) && defined(NONAMELESSUNION) + // avoid breaking strict-aliasing rules in such line: (EndpointFormFactor)(*((UINT *)(((WORD *)&value.wReserved3)+1))); + UINT v; + memcpy(&v, (((WORD *)&value.wReserved3) + 1), sizeof(v)); + wasapiDeviceInfo->formFactor = (EndpointFormFactor)v; + #else + wasapiDeviceInfo->formFactor = (EndpointFormFactor)value.uintVal; + #endif + + PA_DEBUG(("WASAPI:%d| form-factor[%d]\n", index, wasapiDeviceInfo->formFactor)); + + PropVariantClear(&value); + } + + // Data flow (Renderer or Capture) + hr = IMMDevice_QueryInterface(wasapiDeviceInfo->device, &pa_IID_IMMEndpoint, (void **)&endpoint); + if (SUCCEEDED(hr)) + { + hr = IMMEndpoint_GetDataFlow(endpoint, &wasapiDeviceInfo->flow); + SAFE_RELEASE(endpoint); + } + SAFE_RELEASE(pProperty); + } #else - // Determine number of available devices by activating AudioClient for render and capture data flows - if (SUCCEEDED(ActivateAudioInterface_WINRT(eRender, GetAudioClientIID(), &tmpClient))) + (void)pEndPoints; + (void)defaultRenderer; + (void)defaultCapturer; +#endif + + // Getting a temporary IAudioClient for more fields + // we make sure NOT to call Initialize yet! { - paWasapi->deviceCount++; + 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); + + // Get latency + hr = IAudioClient_GetDevicePeriod(tmpClient, &wasapiDeviceInfo->DefaultDevicePeriod, &wasapiDeviceInfo->MinimumDevicePeriod); + if (FAILED(hr)) + { + PA_DEBUG(("WASAPI:%d| failed getting min/default periods by IAudioClient::GetDevicePeriod() with error[%08X], will use 30000/100000 hns\n", index, (UINT32)hr)); + + // assign WASAPI common values + wasapiDeviceInfo->DefaultDevicePeriod = 100000; + wasapiDeviceInfo->MinimumDevicePeriod = 30000; + + // ignore error, let continue further without failing with paInternalError + hr = S_OK; + } + + // Get mix format + hr = IAudioClient_GetMixFormat(tmpClient, &mixFormat); + if (SUCCEEDED(hr)) + { + memcpy(&wasapiDeviceInfo->MixFormat, mixFormat, min(sizeof(wasapiDeviceInfo->MixFormat), (sizeof(*mixFormat) + mixFormat->cbSize))); + CoTaskMemFree(mixFormat); + } + + // Register WINRT device + #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 + wasapiDeviceInfo->state = DEVICE_STATE_ACTIVE; + + // Default format 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 + + // Release tmp client SAFE_RELEASE(tmpClient); + + if (hr != S_OK) + { + //davidv: this happened with my hardware, previously for that same device in DirectSound: + //Digital Output (Realtek AC'97 Audio)'s GUID: {0x38f2cf50,0x7b4c,0x4740,0x86,0xeb,0xd4,0x38,0x66,0xd8,0xc8, 0x9f} + //so something must be _really_ wrong with this device, TODO handle this better. We kind of need GetMixFormat + LogHostError(hr); + result = paInternalError; + goto error; + } } - if (SUCCEEDED(ActivateAudioInterface_WINRT(eCapture, GetAudioClientIID(), &tmpClient))) - { - paWasapi->deviceCount++; - SAFE_RELEASE(tmpClient); + + // Fill basic device data + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + deviceInfo->defaultSampleRate = wasapiDeviceInfo->MixFormat.Format.nSamplesPerSec; + switch (wasapiDeviceInfo->flow) + { + case eRender: { + deviceInfo->maxOutputChannels = wasapiDeviceInfo->MixFormat.Format.nChannels; + deviceInfo->defaultHighOutputLatency = nano100ToSeconds(wasapiDeviceInfo->DefaultDevicePeriod); + deviceInfo->defaultLowOutputLatency = nano100ToSeconds(wasapiDeviceInfo->MinimumDevicePeriod); + PA_DEBUG(("WASAPI:%d| def.SR[%d] max.CH[%d] latency{hi[%f] lo[%f]}\n", index, (UINT32)deviceInfo->defaultSampleRate, + deviceInfo->maxOutputChannels, (float)deviceInfo->defaultHighOutputLatency, (float)deviceInfo->defaultLowOutputLatency)); + break;} + case eCapture: { + deviceInfo->maxInputChannels = wasapiDeviceInfo->MixFormat.Format.nChannels; + deviceInfo->defaultHighInputLatency = nano100ToSeconds(wasapiDeviceInfo->DefaultDevicePeriod); + deviceInfo->defaultLowInputLatency = nano100ToSeconds(wasapiDeviceInfo->MinimumDevicePeriod); + PA_DEBUG(("WASAPI:%d| def.SR[%d] max.CH[%d] latency{hi[%f] lo[%f]}\n", index, (UINT32)deviceInfo->defaultSampleRate, + deviceInfo->maxInputChannels, (float)deviceInfo->defaultHighInputLatency, (float)deviceInfo->defaultLowInputLatency)); + break; } + default: + PRINT(("WASAPI:%d| bad Data Flow!\n", index)); + result = paInternalError; + goto error; } -#endif - paWasapi->devInfo = (PaWasapiDeviceInfo *)PaUtil_GroupAllocateMemory( - paWasapi->allocations, sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount); - if (paWasapi->devInfo == NULL) + return paNoError; + +error: + + PRINT(("WASAPI: failed filling device info for device index[%d] - error[%d|%s]\n", index, result, Pa_GetErrorText(result))); + + return result; +} + +// ------------------------------------------------------------------------------------------ +static PaDeviceInfo *AllocateDeviceListMemory(PaWasapiHostApiRepresentation *paWasapi) +{ + PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; + PaDeviceInfo *deviceInfoArray = NULL; + + if ((paWasapi->devInfo = (PaWasapiDeviceInfo *)PaUtil_GroupAllocateMemory(paWasapi->allocations, + sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount)) == NULL) { - result = paInsufficientMemory; - goto error; + return NULL; } memset(paWasapi->devInfo, 0, sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount); - if (paWasapi->deviceCount > 0) + if (paWasapi->deviceCount != 0) { UINT32 deviceCount = paWasapi->deviceCount; #if defined(PA_WASAPI_MAX_CONST_DEVICE_COUNT) && (PA_WASAPI_MAX_CONST_DEVICE_COUNT > 0) @@ -1856,314 +2023,182 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA deviceCount = PA_WASAPI_MAX_CONST_DEVICE_COUNT; #endif - hostApi->deviceInfos = (PaDeviceInfo **)PaUtil_GroupAllocateMemory( - paWasapi->allocations, sizeof(PaDeviceInfo *) * deviceCount); - if (hostApi->deviceInfos == NULL) + if ((hostApi->deviceInfos = (PaDeviceInfo **)PaUtil_GroupAllocateMemory(paWasapi->allocations, + sizeof(PaDeviceInfo *) * deviceCount)) == NULL) { - result = paInsufficientMemory; - goto error; + return NULL; } + for (UINT32 i = 0; i < deviceCount; ++i) + hostApi->deviceInfos[i] = NULL; - /* allocate all device info structs in a contiguous block */ - deviceInfoArray = (PaDeviceInfo *)PaUtil_GroupAllocateMemory( - paWasapi->allocations, sizeof(PaDeviceInfo) * deviceCount); - if (deviceInfoArray == NULL) + // Allocate all device info structs in a contiguous block + if ((deviceInfoArray = (PaDeviceInfo *)PaUtil_GroupAllocateMemory(paWasapi->allocations, + sizeof(PaDeviceInfo) * deviceCount)) == NULL) { - result = paInsufficientMemory; - goto error; + return NULL; } memset(deviceInfoArray, 0, sizeof(PaDeviceInfo) * deviceCount); + } - for (i = 0; i < paWasapi->deviceCount; ++i) - { - PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; - deviceInfo->structVersion = 2; - deviceInfo->hostApi = hostApiIndex; - - PA_DEBUG(("WASAPI: device idx: %02d\n", i)); - PA_DEBUG(("WASAPI: ---------------\n")); - - #ifndef PA_WINRT - hr = IMMDeviceCollection_Item(pEndPoints, i, &paWasapi->devInfo[i].device); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); + return deviceInfoArray; +} - // getting ID - { - WCHAR *pszDeviceId = NULL; - hr = IMMDevice_GetId(paWasapi->devInfo[i].device, &pszDeviceId); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hr, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - wcsncpy(paWasapi->devInfo[i].szDeviceID, pszDeviceId, MAX_STR_LEN-1); - CoTaskMemFree(pszDeviceId); +// ------------------------------------------------------------------------------------------ +static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostApiIndex hostApiIndex) +{ + PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; + PaError result = paNoError; + PaDeviceInfo *deviceInfoArray; + UINT i; + WCHAR *defaultRenderer = NULL; + WCHAR *defaultCapturer = NULL; +#ifndef PA_WINRT + HRESULT hr; + IMMDeviceCollection *pEndPoints = NULL; + IMMDeviceEnumerator *pEnumerator = NULL; +#else + void *pEndPoints = NULL; + IAudioClient *tmpClient; +#endif - if (lstrcmpW(paWasapi->devInfo[i].szDeviceID, paWasapi->defaultCapturer) == 0) - {// we found the default input! - hostApi->info.defaultInputDevice = hostApi->info.deviceCount; - } - if (lstrcmpW(paWasapi->devInfo[i].szDeviceID, paWasapi->defaultRenderer) == 0) - {// we found the default output! - hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; - } - } + // Make sure device list empty + if ((paWasapi->deviceCount != 0) || (hostApi->info.deviceCount != 0)) + return paInternalError; - hr = IMMDevice_GetState(paWasapi->devInfo[i].device, &paWasapi->devInfo[i].state); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); +#ifndef PA_WINRT + hr = CoCreateInstance(&pa_CLSID_IMMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, + &pa_IID_IMMDeviceEnumerator, (void **)&pEnumerator); + IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - if (paWasapi->devInfo[i].state != DEVICE_STATE_ACTIVE) - { - PRINT(("WASAPI device: %d is not currently available (state:%d)\n", i, paWasapi->devInfo[i].state)); - } + // Get default render and capture devices + { + IMMDevice *device; + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, eRender, eMultimedia, &device); + if (hr != S_OK) + { + if (hr != E_NOTFOUND) { - IPropertyStore *pProperty; - hr = IMMDevice_OpenPropertyStore(paWasapi->devInfo[i].device, STGM_READ, &pProperty); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - // "Friendly" Name - { - PROPVARIANT value; - PropVariantInit(&value); - hr = IPropertyStore_GetValue(pProperty, &PKEY_Device_FriendlyName, &value); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - if ((deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, MAX_STR_LEN + 1)) == NULL) - { - result = paInsufficientMemory; - goto error; - } - if (value.pwszVal) - WideCharToMultiByte(CP_UTF8, 0, value.pwszVal, (int)wcslen(value.pwszVal), (char *)deviceInfo->name, MAX_STR_LEN - 1, 0, 0); - else - _snprintf((char *)deviceInfo->name, MAX_STR_LEN - 1, "baddev%d", i); - PropVariantClear(&value); - PA_DEBUG(("WASAPI:%d| name[%s]\n", i, deviceInfo->name)); - } - - // Default format - { - PROPVARIANT value; - PropVariantInit(&value); - hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEngine_DeviceFormat, &value); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - memcpy(&paWasapi->devInfo[i].DefaultFormat, value.blob.pBlobData, min(sizeof(paWasapi->devInfo[i].DefaultFormat), value.blob.cbSize)); - // cleanup - PropVariantClear(&value); - } - - // Formfactor - { - PROPVARIANT value; - PropVariantInit(&value); - hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEndpoint_FormFactor, &value); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - // set - #if defined(DUMMYUNIONNAME) && defined(NONAMELESSUNION) - // avoid breaking strict-aliasing rules in such line: (EndpointFormFactor)(*((UINT *)(((WORD *)&value.wReserved3)+1))); - UINT v; - memcpy(&v, (((WORD *)&value.wReserved3)+1), sizeof(v)); - paWasapi->devInfo[i].formFactor = (EndpointFormFactor)v; - #else - paWasapi->devInfo[i].formFactor = (EndpointFormFactor)value.uintVal; - #endif - PA_DEBUG(("WASAPI:%d| form-factor[%d]\n", i, paWasapi->devInfo[i].formFactor)); - // cleanup - PropVariantClear(&value); - } - - SAFE_RELEASE(pProperty); } - - // Endpoint data - { - IMMEndpoint *endpoint = NULL; - hr = IMMDevice_QueryInterface(paWasapi->devInfo[i].device, &pa_IID_IMMEndpoint, (void **)&endpoint); - if (SUCCEEDED(hr)) - { - hr = IMMEndpoint_GetDataFlow(endpoint, &paWasapi->devInfo[i].flow); - SAFE_RELEASE(endpoint); - } - } - #endif + } + else + { + hr = IMMDevice_GetId(device, &defaultRenderer); + IMMDevice_Release(device); + IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); + } - // Getting a temporary IAudioClient for more fields - // we make sure NOT to call Initialize yet! + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, eCapture, eMultimedia, &device); + if (hr != S_OK) + { + if (hr != E_NOTFOUND) { - #ifdef PA_WINRT - // Set flow as ActivateAudioInterface depends on it and selects corresponding - // direction for the Audio Client - paWasapi->devInfo[i].flow = (i == 0 ? eRender : eCapture); - #endif - - // Create temp Audio Client instance to query additional details - hr = ActivateAudioInterface(&paWasapi->devInfo[i], NULL, &tmpClient); - // We need to set the result to a value otherwise we will return paNoError - // [IF_FAILED_JUMP(hResult, error);] IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); + } + } + else + { + hr = IMMDevice_GetId(device, &defaultCapturer); + IMMDevice_Release(device); + IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); + } + } - // Get latency - hr = IAudioClient_GetDevicePeriod(tmpClient, - &paWasapi->devInfo[i].DefaultDevicePeriod, - &paWasapi->devInfo[i].MinimumDevicePeriod); - if (FAILED(hr)) - { - PA_DEBUG(("WASAPI:%d| failed getting min/default periods by IAudioClient::GetDevicePeriod() with error[%08X], will use 30000/100000 hns\n", i, (UINT32)hr)); + // Get all currently active devices + hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eAll, DEVICE_STATE_ACTIVE, &pEndPoints); + IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - // assign WASAPI common values - paWasapi->devInfo[i].DefaultDevicePeriod = 100000; - paWasapi->devInfo[i].MinimumDevicePeriod = 30000; + // Get device count + 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))) + { + paWasapi->deviceCount++; + SAFE_RELEASE(tmpClient); + } + if (SUCCEEDED(ActivateAudioInterface_WINRT(eCapture, GetAudioClientIID(), &tmpClient))) + { + paWasapi->deviceCount++; + SAFE_RELEASE(tmpClient); + } +#endif - // ignore error, let continue further without failing with paInternalError - hr = S_OK; - } - - // Get mix format - { - WAVEFORMATEX *mixFormat; + // Allocate memory for the device list + if ((deviceInfoArray = AllocateDeviceListMemory(paWasapi)) == NULL) + { + result = paInsufficientMemory; + goto error; + } - hr = IAudioClient_GetMixFormat(tmpClient, &mixFormat); - if (SUCCEEDED(hr)) - { - memcpy(&paWasapi->devInfo[i].MixFormat, mixFormat, min(sizeof(paWasapi->devInfo[i].MixFormat), (sizeof(*mixFormat) + mixFormat->cbSize))); - CoTaskMemFree(mixFormat); - } - } + // Fill WASAPI device info + for (i = 0; i < paWasapi->deviceCount; ++i) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; - // Register WINRT device - #ifdef PA_WINRT - if (SUCCEEDED(hr)) - { - // Default device - if (i == 0) - hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; - else - hostApi->info.defaultInputDevice = hostApi->info.deviceCount; + PA_DEBUG(("WASAPI: device idx: %02d\n", i)); + PA_DEBUG(("WASAPI: ---------------\n")); - // State - paWasapi->devInfo[i].state = DEVICE_STATE_ACTIVE; + FillBaseDeviceInfo(deviceInfo, hostApiIndex); - // Default format is always a mix format - paWasapi->devInfo[i].DefaultFormat = paWasapi->devInfo[i].MixFormat; + if ((result = FillDeviceInfo(paWasapi, pEndPoints, i, defaultRenderer, defaultCapturer, + deviceInfo, &paWasapi->devInfo[i])) != paNoError) + { + // Faulty device is made inactive + if ((result = FillInactiveDeviceInfo(paWasapi, deviceInfo)) != paNoError) + goto error; + } - // Form-factor - paWasapi->devInfo[i].formFactor = UnknownFormFactor; + hostApi->deviceInfos[i] = deviceInfo; + ++hostApi->info.deviceCount; + } - // 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", (i == 0 ? "Output" : "Input"), i); - PA_DEBUG(("WASAPI:%d| name[%s]\n", i, deviceInfo->name)); - } - #endif + // Fill the remaining slots with inactive device info +#if defined(PA_WASAPI_MAX_CONST_DEVICE_COUNT) && (PA_WASAPI_MAX_CONST_DEVICE_COUNT > 0) + if (hostApi->info.deviceCount < PA_WASAPI_MAX_CONST_DEVICE_COUNT) + { + for (i = hostApi->info.deviceCount; i < PA_WASAPI_MAX_CONST_DEVICE_COUNT; ++i) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; - // Release tmp client - SAFE_RELEASE(tmpClient); + FillBaseDeviceInfo(deviceInfo, hostApiIndex); - if (hr != S_OK) - { - //davidv: this happened with my hardware, previously for that same device in DirectSound: - //Digital Output (Realtek AC'97 Audio)'s GUID: {0x38f2cf50,0x7b4c,0x4740,0x86,0xeb,0xd4,0x38,0x66,0xd8,0xc8, 0x9f} - //so something must be _really_ wrong with this device, TODO handle this better. We kind of need GetMixFormat - LogHostError(hr); - // We need to set the result to a value otherwise we will return paNoError - result = paInternalError; - goto error; - } - } - - // we can now fill in portaudio device data - deviceInfo->maxInputChannels = 0; - deviceInfo->maxOutputChannels = 0; - deviceInfo->defaultSampleRate = paWasapi->devInfo[i].MixFormat.Format.nSamplesPerSec; - switch (paWasapi->devInfo[i].flow) - { - case eRender: { - deviceInfo->maxOutputChannels = paWasapi->devInfo[i].MixFormat.Format.nChannels; - deviceInfo->defaultHighOutputLatency = nano100ToSeconds(paWasapi->devInfo[i].DefaultDevicePeriod); - deviceInfo->defaultLowOutputLatency = nano100ToSeconds(paWasapi->devInfo[i].MinimumDevicePeriod); - PA_DEBUG(("WASAPI:%d| def.SR[%d] max.CH[%d] latency{hi[%f] lo[%f]}\n", i, (UINT32)deviceInfo->defaultSampleRate, - deviceInfo->maxOutputChannels, (float)deviceInfo->defaultHighOutputLatency, (float)deviceInfo->defaultLowOutputLatency)); - break;} - case eCapture: { - deviceInfo->maxInputChannels = paWasapi->devInfo[i].MixFormat.Format.nChannels; - deviceInfo->defaultHighInputLatency = nano100ToSeconds(paWasapi->devInfo[i].DefaultDevicePeriod); - deviceInfo->defaultLowInputLatency = nano100ToSeconds(paWasapi->devInfo[i].MinimumDevicePeriod); - PA_DEBUG(("WASAPI:%d| def.SR[%d] max.CH[%d] latency{hi[%f] lo[%f]}\n", i, (UINT32)deviceInfo->defaultSampleRate, - deviceInfo->maxInputChannels, (float)deviceInfo->defaultHighInputLatency, (float)deviceInfo->defaultLowInputLatency)); - break; } - default: - PRINT(("WASAPI:%d| bad Data Flow!\n", i)); - // We need to set the result to a value otherwise we will return paNoError - result = paInternalError; - //continue; // do not skip from list, allow to initialize - break; - } + if ((result = FillInactiveDeviceInfo(paWasapi, deviceInfo)) != paNoError) + goto error; hostApi->deviceInfos[i] = deviceInfo; ++hostApi->info.deviceCount; } + } +#endif - #if defined(PA_WASAPI_MAX_CONST_DEVICE_COUNT) && (PA_WASAPI_MAX_CONST_DEVICE_COUNT > 0) - if (hostApi->info.deviceCount < PA_WASAPI_MAX_CONST_DEVICE_COUNT) - { - for (i = hostApi->info.deviceCount; i < PA_WASAPI_MAX_CONST_DEVICE_COUNT; ++i) - { - PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; - deviceInfo->structVersion = 2; - deviceInfo->hostApi = hostApiIndex; + // Clear any non-fatal errors + result = paNoError; - if ((deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, 1)) == NULL) - { - result = paInsufficientMemory; - goto error; - } - ((char *)deviceInfo->name)[0] = 0; + PRINT(("WASAPI: device list ok - found %d devices\n", paWasapi->deviceCount)); - hostApi->deviceInfos[i] = deviceInfo; - ++hostApi->info.deviceCount; - } - } - #endif - } +done: #ifndef PA_WINRT + CoTaskMemFree(defaultRenderer); + CoTaskMemFree(defaultCapturer); SAFE_RELEASE(pEndPoints); SAFE_RELEASE(pEnumerator); #endif - PRINT(("WASAPI: device list created ok\n")); - - return paNoError; + return result; error: - - PRINT(("WASAPI: failed %s error[%d|%s]\n", __FUNCTION__, result, Pa_GetErrorText(result))); - -#ifndef PA_WINRT - SAFE_RELEASE(pEndPoints); - SAFE_RELEASE(pEnumerator); -#endif - + // Safety if error was not set so that we do not think initialize was a success if (result == paNoError) result = paInternalError; - return result; + PRINT(("WASAPI: failed to create device list - error[%d|%s]\n", result, Pa_GetErrorText(result))); + + goto done; } // ------------------------------------------------------------------------------------------ @@ -5637,7 +5672,7 @@ static void FinishComPointers(PaWasapiStream *stream, BOOL threadComInitialized) PA_THREAD_FUNC ProcThreadEvent(void *param) { PaWasapiHostProcessor processor[S_COUNT]; - HRESULT hr; + HRESULT hr = S_OK; DWORD dwResult; PaWasapiStream *stream = (PaWasapiStream *)param; PaWasapiHostProcessor defaultProcessor; @@ -5931,7 +5966,7 @@ static inline INT32 GetNextSleepTime(SystemTimer *timer, ThreadIdleScheduler *sc PA_THREAD_FUNC ProcThreadPoll(void *param) { PaWasapiHostProcessor processor[S_COUNT]; - HRESULT hr; + HRESULT hr = S_OK; PaWasapiStream *stream = (PaWasapiStream *)param; PaWasapiHostProcessor defaultProcessor; INT32 i;