forked from microsoft/vcpkg
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[portaudio] Add WASAPI loopback patch
- Loading branch information
Showing
3 changed files
with
367 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,365 @@ | ||
diff --git a/build/msvc/portaudio.def b/build/msvc/portaudio.def | ||
index 6ecb142..eabb0d1 100644 | ||
--- a/build/msvc/portaudio.def | ||
+++ b/build/msvc/portaudio.def | ||
@@ -56,3 +56,8 @@ PaWasapi_SetStreamStateHandler @68 | ||
PaWasapiWinrt_SetDefaultDeviceId @67 | ||
PaWasapiWinrt_PopulateDeviceList @69 | ||
PaWasapi_GetIMMDevice @70 | ||
+PaWasapi_IsLoopback @71 | ||
+PaWinMME_GetStreamInputHandleCount @72 | ||
+PaWinMME_GetStreamInputHandle @73 | ||
+PaWinMME_GetStreamOutputHandleCount @74 | ||
+PaWinMME_GetStreamOutputHandle @75 | ||
\ No newline at end of file | ||
diff --git a/cmake_support/template_portaudio.def b/cmake_support/template_portaudio.def | ||
index 9cf0dc3..0e6cb8b 100644 | ||
--- a/cmake_support/template_portaudio.def | ||
+++ b/cmake_support/template_portaudio.def | ||
@@ -59,3 +59,8 @@ PaUtil_SetDebugPrintFunction @55 | ||
@DEF_EXCLUDE_WASAPI_SYMBOLS@PaWasapiWinrt_SetDefaultDeviceId @67 | ||
@DEF_EXCLUDE_WASAPI_SYMBOLS@PaWasapiWinrt_PopulateDeviceList @69 | ||
@DEF_EXCLUDE_WASAPI_SYMBOLS@PaWasapi_GetIMMDevice @70 | ||
+@DEF_EXCLUDE_WASAPI_SYMBOLS@PaWasapi_IsLoopback @71 | ||
+@DEF_EXCLUDE_WMME_SYMBOLS@PaWinMME_GetStreamInputHandleCount @72 | ||
+@DEF_EXCLUDE_WMME_SYMBOLS@PaWinMME_GetStreamInputHandle @73 | ||
+@DEF_EXCLUDE_WMME_SYMBOLS@PaWinMME_GetStreamOutputHandleCount @74 | ||
+@DEF_EXCLUDE_WMME_SYMBOLS@PaWinMME_GetStreamOutputHandle @75 | ||
diff --git a/include/pa_win_wasapi.h b/include/pa_win_wasapi.h | ||
index c046afd..cf272e7 100644 | ||
--- a/include/pa_win_wasapi.h | ||
+++ b/include/pa_win_wasapi.h | ||
@@ -437,7 +437,7 @@ int PaWasapi_GetDeviceMixFormat( void *pFormat, unsigned int formatSize, PaDevic | ||
int/*PaWasapiDeviceRole*/ PaWasapi_GetDeviceRole( PaDeviceIndex device ); | ||
|
||
|
||
-/** Get device IMMDevice pointer | ||
+/** Get device IMMDevice pointer. | ||
|
||
@param device Device index. | ||
@param pAudioClient Pointer to pointer of IMMDevice. | ||
@@ -447,6 +447,20 @@ int/*PaWasapiDeviceRole*/ PaWasapi_GetDeviceRole( PaDeviceIndex device ); | ||
PaError PaWasapi_GetIMMDevice( PaDeviceIndex device, void **pIMMDevice ); | ||
|
||
|
||
+/** Get device loopback state: | ||
+ | ||
+ 0 - Not loopback, | ||
+ 1 - Loopback, | ||
+ negative - PaErrorCode. | ||
+ | ||
+ @param device Device index. | ||
+ | ||
+ @return Non-negative value indicating loopback state or, a PaErrorCode (which is always negative) | ||
+ if PortAudio is not initialized or an error is encountered. | ||
+*/ | ||
+int PaWasapi_IsLoopback( PaDeviceIndex device ); | ||
+ | ||
+ | ||
/** Boost thread priority of calling thread (MMCSS). | ||
|
||
Use it for Blocking Interface only inside the thread which makes calls to Pa_WriteStream/Pa_ReadStream. | ||
diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c | ||
index c76f302..b5829b2 100644 | ||
--- a/src/hostapi/wasapi/pa_win_wasapi.c | ||
+++ b/src/hostapi/wasapi/pa_win_wasapi.c | ||
@@ -1,7 +1,7 @@ | ||
/* | ||
* Portable Audio I/O Library WASAPI implementation | ||
* Copyright (c) 2006-2010 David Viens | ||
- * Copyright (c) 2010-2019 Dmitry Kostjuchenko | ||
+ * Copyright (c) 2010-2022 Dmitry Kostjuchenko | ||
* | ||
* Based on the Open Source API proposed by Ross Bencina | ||
* Copyright (c) 1999-2019 Ross Bencina, Phil Burk | ||
@@ -365,6 +365,7 @@ enum { WASAPI_PACKETS_PER_INPUT_BUFFER = 6 }; | ||
|
||
#define SAFE_CLOSE(h) if ((h) != NULL) { CloseHandle((h)); (h) = NULL; } | ||
#define SAFE_RELEASE(punk) if ((punk) != NULL) { (punk)->lpVtbl->Release((punk)); (punk) = NULL; } | ||
+#define SAFE_ADDREF(punk) if ((punk) != NULL) { (punk)->lpVtbl->AddRef((punk)); } | ||
|
||
// Mixer function | ||
typedef void (*MixMonoToStereoF) (void *__to, const void *__from, UINT32 count); | ||
@@ -476,12 +477,15 @@ typedef struct PaWasapiDeviceInfo | ||
|
||
// Form-factor | ||
EndpointFormFactor formFactor; | ||
+ | ||
+ // Loopback state (TRUE if device acts as loopback) | ||
+ BOOL loopBack; | ||
} | ||
PaWasapiDeviceInfo; | ||
|
||
// ------------------------------------------------------------------------------------------ | ||
/* PaWasapiHostApiRepresentation - host api datastructure specific to this implementation */ | ||
-typedef struct | ||
+typedef struct PaWasapiHostApiRepresentation | ||
{ | ||
PaUtilHostApiRepresentation inheritedHostApiRep; | ||
PaUtilStreamInterface callbackStreamInterface; | ||
@@ -2068,22 +2072,21 @@ error: | ||
} | ||
|
||
// ------------------------------------------------------------------------------------------ | ||
-static PaDeviceInfo *AllocateDeviceListMemory(PaWasapiHostApiRepresentation *paWasapi) | ||
+static PaDeviceInfo *AllocateDeviceListMemory(PaWasapiHostApiRepresentation *paWasapi, UINT32 deviceCount) | ||
{ | ||
PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; | ||
PaDeviceInfo *deviceInfoArray = NULL; | ||
|
||
if ((paWasapi->devInfo = (PaWasapiDeviceInfo *)PaUtil_GroupAllocateMemory(paWasapi->allocations, | ||
- sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount)) == NULL) | ||
+ sizeof(PaWasapiDeviceInfo) * deviceCount)) == NULL) | ||
{ | ||
return NULL; | ||
} | ||
- memset(paWasapi->devInfo, 0, sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount); | ||
+ memset(paWasapi->devInfo, 0, sizeof(PaWasapiDeviceInfo) * deviceCount); | ||
|
||
- if (paWasapi->deviceCount != 0) | ||
+ if (deviceCount != 0) | ||
{ | ||
UINT32 i; | ||
- UINT32 deviceCount = paWasapi->deviceCount; | ||
#if defined(PA_WASAPI_MAX_CONST_DEVICE_COUNT) && (PA_WASAPI_MAX_CONST_DEVICE_COUNT > 0) | ||
if (deviceCount < PA_WASAPI_MAX_CONST_DEVICE_COUNT) | ||
deviceCount = PA_WASAPI_MAX_CONST_DEVICE_COUNT; | ||
@@ -2109,13 +2112,101 @@ static PaDeviceInfo *AllocateDeviceListMemory(PaWasapiHostApiRepresentation *paW | ||
return deviceInfoArray; | ||
} | ||
|
||
+// ------------------------------------------------------------------------------------------ | ||
+#ifndef PA_WINRT | ||
+static UINT32 GetDeviceListDeviceCount(IMMDeviceCollection *pEndPoints, EDataFlow filterFlow) | ||
+{ | ||
+ HRESULT hr; | ||
+ UINT32 deviceCount; | ||
+ IMMDevice *device; | ||
+ IMMEndpoint *endpoint; | ||
+ EDataFlow flow; | ||
+ UINT32 ret = 0; | ||
+ | ||
+ hr = IMMDeviceCollection_GetCount(pEndPoints, &deviceCount); | ||
+ IF_FAILED_JUMP(hr, error); | ||
+ | ||
+ for (UINT32 i = 0; i < deviceCount; ++i) | ||
+ { | ||
+ hr = IMMDeviceCollection_Item((IMMDeviceCollection *)pEndPoints, i, &device); | ||
+ IF_FAILED_JUMP(hr, error); | ||
+ | ||
+ if (SUCCEEDED(hr = IMMDevice_QueryInterface(device, &pa_IID_IMMEndpoint, (void **)&endpoint))) | ||
+ { | ||
+ if (SUCCEEDED(hr = IMMEndpoint_GetDataFlow(endpoint, &flow))) | ||
+ ret += (flow == filterFlow); | ||
+ | ||
+ SAFE_RELEASE(endpoint); | ||
+ } | ||
+ | ||
+ SAFE_RELEASE(device); | ||
+ } | ||
+ | ||
+ return ret; | ||
+ | ||
+error: | ||
+ | ||
+ return 0; | ||
+} | ||
+#else | ||
+static UINT32 GetDeviceListDeviceCount(const PaWasapiHostApiRepresentation *paWasapi, | ||
+ const PaWasapiWinrtDeviceListContext *deviceListContext, EDataFlow filterFlow) | ||
+{ | ||
+ UINT32 i, ret = 0; | ||
+ | ||
+ for (i = 0; i < paWasapi->deviceCount; ++i) | ||
+ ret += (deviceListContext->devices[i].flow == filterFlow); | ||
+ | ||
+ return ret; | ||
+} | ||
+#endif | ||
+ | ||
+// ------------------------------------------------------------------------------------------ | ||
+static BOOL FillLooopbackDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, PaDeviceInfo *loopbackDeviceInfo, | ||
+ PaWasapiDeviceInfo *loopbackWasapiInfo, const PaDeviceInfo *deviceInfo, const PaWasapiDeviceInfo *wasapiInfo) | ||
+{ | ||
+// Loopback device name identificator | ||
+// note: Some projects depend on loopback device detection by device name, do not change! | ||
+#define PA_WASAPI_LOOPBACK_NAME_IDENTIFICATOR "[Loopback]" | ||
+ | ||
+ memcpy(loopbackDeviceInfo, deviceInfo, sizeof(*loopbackDeviceInfo)); | ||
+ memcpy(loopbackWasapiInfo, wasapiInfo, sizeof(*loopbackWasapiInfo)); | ||
+ | ||
+ // Append loopback device name identificator to the device name to provide possibility to find | ||
+ // loopback device by its name for some external projects | ||
+ loopbackDeviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, PA_WASAPI_DEVICE_NAME_LEN + 1); | ||
+ if (loopbackDeviceInfo->name == NULL) | ||
+ return FALSE; | ||
+ _snprintf((char *)loopbackDeviceInfo->name, PA_WASAPI_DEVICE_NAME_LEN - 1, | ||
+ "%s " PA_WASAPI_LOOPBACK_NAME_IDENTIFICATOR, deviceInfo->name); | ||
+ | ||
+ // Acquire ref to the device as it is already referenced as render (output) device | ||
+ // to avoid duplicate release in ReleaseWasapiDeviceInfoList() | ||
+#ifndef PA_WINRT | ||
+ SAFE_ADDREF(loopbackWasapiInfo->device); | ||
+#endif | ||
+ | ||
+ // Mark as loopback device | ||
+ loopbackWasapiInfo->loopBack = TRUE; | ||
+ | ||
+ // Input is the reverse of Output | ||
+ loopbackDeviceInfo->maxInputChannels = deviceInfo->maxOutputChannels; | ||
+ loopbackDeviceInfo->defaultHighInputLatency = deviceInfo->defaultHighOutputLatency; | ||
+ loopbackDeviceInfo->defaultLowInputLatency = deviceInfo->defaultLowOutputLatency; | ||
+ loopbackDeviceInfo->maxOutputChannels = 0; | ||
+ loopbackDeviceInfo->defaultHighOutputLatency = 0; | ||
+ loopbackDeviceInfo->defaultLowOutputLatency = 0; | ||
+ | ||
+ return TRUE; | ||
+} | ||
+ | ||
// ------------------------------------------------------------------------------------------ | ||
static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostApiIndex hostApiIndex) | ||
{ | ||
PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; | ||
PaError result = paNoError; | ||
PaDeviceInfo *deviceInfoArray = NULL; | ||
- UINT32 i; | ||
+ UINT32 i, j, loopbacks; | ||
WCHAR *defaultRenderId = NULL; | ||
WCHAR *defaultCaptureId = NULL; | ||
#ifndef PA_WINRT | ||
@@ -2181,6 +2272,9 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA | ||
// Get device count | ||
hr = IMMDeviceCollection_GetCount(pEndPoints, &paWasapi->deviceCount); | ||
IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); | ||
+ | ||
+ // Get loopback device count (e.g. all renders) | ||
+ loopbacks = GetDeviceListDeviceCount(pEndPoints, eRender); | ||
#else | ||
WinRT_GetDefaultDeviceId(defaultRender.id, STATIC_ARRAY_SIZE(defaultRender.id) - 1, eRender); | ||
defaultRenderId = defaultRender.id; | ||
@@ -2229,19 +2323,24 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA | ||
paWasapi->deviceCount++; | ||
} | ||
} | ||
+ | ||
+ // Get loopback device count (e.g. all renders) | ||
+ loopbacks = GetDeviceListDeviceCount(paWasapi, &deviceListContext, eRender); | ||
#endif | ||
|
||
// Allocate memory for the device list | ||
- if ((paWasapi->deviceCount != 0) && ((deviceInfoArray = AllocateDeviceListMemory(paWasapi)) == NULL)) | ||
+ if ((paWasapi->deviceCount != 0) && | ||
+ ((deviceInfoArray = AllocateDeviceListMemory(paWasapi, paWasapi->deviceCount + loopbacks)) == NULL)) | ||
{ | ||
result = paInsufficientMemory; | ||
goto error; | ||
} | ||
|
||
// Fill WASAPI device info | ||
- for (i = 0; i < paWasapi->deviceCount; ++i) | ||
+ for (i = 0, j = 0; i < paWasapi->deviceCount; ++i) | ||
{ | ||
PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; | ||
+ PaWasapiDeviceInfo *wasapiInfo = &paWasapi->devInfo[i]; | ||
|
||
PA_DEBUG(("WASAPI: device idx: %02d\n", i)); | ||
PA_DEBUG(("WASAPI: ---------------\n")); | ||
@@ -2249,7 +2348,7 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA | ||
FillBaseDeviceInfo(deviceInfo, hostApiIndex); | ||
|
||
if ((result = FillDeviceInfo(paWasapi, pEndPoints, i, defaultRenderId, defaultCaptureId, | ||
- deviceInfo, &paWasapi->devInfo[i] | ||
+ deviceInfo, wasapiInfo | ||
#ifdef PA_WINRT | ||
, &deviceListContext | ||
#endif | ||
@@ -2262,8 +2361,29 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA | ||
|
||
hostApi->deviceInfos[i] = deviceInfo; | ||
++hostApi->info.deviceCount; | ||
+ | ||
+ // Add loopback device for the render device | ||
+ if (paWasapi->devInfo[i].flow == eRender) | ||
+ { | ||
+ // Add loopback device to the end of the device list | ||
+ UINT32 loopbackIndex = paWasapi->deviceCount + j++; | ||
+ PaDeviceInfo *loopbackDeviceInfo = &deviceInfoArray[loopbackIndex]; | ||
+ PaWasapiDeviceInfo *loopbackWasapiInfo = &paWasapi->devInfo[loopbackIndex]; | ||
+ | ||
+ if (!FillLooopbackDeviceInfo(paWasapi, loopbackDeviceInfo, loopbackWasapiInfo, deviceInfo, wasapiInfo)) | ||
+ { | ||
+ result = paInsufficientMemory; | ||
+ goto error; | ||
+ } | ||
+ | ||
+ hostApi->deviceInfos[loopbackIndex] = loopbackDeviceInfo; | ||
+ ++hostApi->info.deviceCount; | ||
+ } | ||
} | ||
|
||
+ // Resize device list to accomodate inserted loopback devices | ||
+ paWasapi->deviceCount += loopbacks; | ||
+ | ||
// 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 != 0) && (hostApi->info.deviceCount < PA_WASAPI_MAX_CONST_DEVICE_COUNT)) | ||
@@ -2286,7 +2406,7 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA | ||
// Clear any non-fatal errors | ||
result = paNoError; | ||
|
||
- PRINT(("WASAPI: device list ok - found %d devices\n", paWasapi->deviceCount)); | ||
+ PRINT(("WASAPI: device list ok - found %d devices, %d loopbacks\n", paWasapi->deviceCount, loopbacks)); | ||
|
||
done: | ||
|
||
@@ -2630,6 +2750,29 @@ PaError PaWasapi_GetIMMDevice( PaDeviceIndex device, void **pIMMDevice ) | ||
#endif | ||
} | ||
|
||
+// ------------------------------------------------------------------------------------------ | ||
+int PaWasapi_IsLoopback( PaDeviceIndex device ) | ||
+{ | ||
+ PaError ret; | ||
+ PaDeviceIndex index; | ||
+ | ||
+ // Get API | ||
+ PaWasapiHostApiRepresentation *paWasapi = _GetHostApi(&ret); | ||
+ if (paWasapi == NULL) | ||
+ return paNotInitialized; | ||
+ | ||
+ // Get device index | ||
+ ret = PaUtil_DeviceIndexToHostApiDeviceIndex(&index, device, &paWasapi->inheritedHostApiRep); | ||
+ if (ret != paNoError) | ||
+ return ret; | ||
+ | ||
+ // Validate index | ||
+ if ((UINT32)index >= paWasapi->deviceCount) | ||
+ return paInvalidDevice; | ||
+ | ||
+ return paWasapi->devInfo[ index ].loopBack; | ||
+} | ||
+ | ||
// ------------------------------------------------------------------------------------------ | ||
PaError PaWasapi_GetFramesPerHostBuffer( PaStream *pStream, unsigned int *pInput, unsigned int *pOutput ) | ||
{ | ||
@@ -3299,7 +3442,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu | ||
if ((params->channelCount == 1) && (pSub->wavex.Format.nChannels == 2)) | ||
{ | ||
// select mixer | ||
- pSub->monoMixer = GetMonoToStereoMixer(&pSub->wavex, (pInfo->flow == eRender ? MIX_DIR__1TO2 : MIX_DIR__2TO1_L)); | ||
+ pSub->monoMixer = GetMonoToStereoMixer(&pSub->wavex, (output ? MIX_DIR__1TO2 : MIX_DIR__2TO1_L)); | ||
if (pSub->monoMixer == NULL) | ||
{ | ||
(*pa_error) = paInvalidChannelCount; | ||
@@ -3850,6 +3993,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, | ||
((inputStreamInfo != NULL) && (inputStreamInfo->flags & paWinWasapiAutoConvert))) | ||
stream->in.streamFlags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY); | ||
|
||
+ // Loopback device is a real output device, so we open stream with AUDCLNT_STREAMFLAGS_LOOPBACK | ||
+ // flag to make it work as input device | ||
+ if (info->loopBack) | ||
+ stream->in.streamFlags |= AUDCLNT_STREAMFLAGS_LOOPBACK; | ||
+ | ||
// Fill parameters for Audio Client creation | ||
stream->in.params.device_info = info; | ||
stream->in.params.stream_params = (*inputParameters); |