Skip to content

Commit

Permalink
[portaudio] Add WASAPI loopback patch
Browse files Browse the repository at this point in the history
  • Loading branch information
daschuer committed Apr 2, 2023
1 parent a952de2 commit 3989181
Show file tree
Hide file tree
Showing 3 changed files with 367 additions and 1 deletion.
1 change: 1 addition & 0 deletions overlay/ports/portaudio/portfile.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ vcpkg_from_github(
fix-library-can-not-be-found.patch
enable_jack_on_windows.patch
pa_initalize_recursive_guard.patch
wasapi_loopback.patch
)

string(COMPARE EQUAL ${VCPKG_LIBRARY_LINKAGE} dynamic PA_BUILD_SHARED)
Expand Down
2 changes: 1 addition & 1 deletion overlay/ports/portaudio/vcpkg.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "portaudio",
"version": "19.7",
"port-version": 1,
"port-version": 2,
"description": "PortAudio Portable Cross-platform Audio I/O API PortAudio is a free, cross-platform, open-source, audio I/O library. It lets you write simple audio programs in 'C' or C++ that will compile and run on many platforms including Windows, Macintosh OS X, and Unix (OSS/ALSA). It is intended to promote the exchange of audio software between developers on different platforms. Many applications use PortAudio for Audio I/O.",
"homepage": "https://app.assembla.com/spaces/portaudio/wiki",
"license": "MIT",
Expand Down
365 changes: 365 additions & 0 deletions overlay/ports/portaudio/wasapi_loopback.patch
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);

0 comments on commit 3989181

Please sign in to comment.