Skip to content

Add WASAPI loopback implementation #672

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 7, 2022

Conversation

WeekendWarrior1
Copy link
Contributor

@WeekendWarrior1 WeekendWarrior1 commented Nov 25, 2021

Re implements this patch: audacity/audacity@be7467c#diff-6ea9c8ba5ecef609fc4cfa9fbbb1bc11ad66acb997565df06ad6ce1562a10144 to add loopback for WASAPI devices.

I'm very new to C so I welcome any feedback or improvements to this PR.

Closes this:
#668

And would really help python-sounddevice users:
spatialaudio/portaudio-binaries#6
spatialaudio/python-sounddevice#287
tlecomte/friture#64
spatialaudio/python-sounddevice#281
spatialaudio/python-sounddevice#14

Also see this project which maintains a fork of portaudio with loopback support specifically for PyAudio: https://github.com/intxcc/pyaudio_portaudio

@WeekendWarrior1 WeekendWarrior1 marked this pull request as draft November 28, 2021 00:06
@RossBencina RossBencina requested a review from dmitrykos December 2, 2021 01:30
@RossBencina
Copy link
Collaborator

@dmitrykos could you review this please

@WeekendWarrior1
Copy link
Contributor Author

This was marked as a draft because shauneccles discovered some issues when using other devices after testing this patch, will bug hunt tonight.

@dmitrykos
Copy link
Collaborator

@RossBencina yes, I will do so once have a bit of free time, I am tracking this PR already since it was proposed. I also will wait for announced @WeekendWarrior1's improvements.

@dmitrykos dmitrykos added the src-wasapi MS WASAPI Host API /src/hostapi/wasapi label Dec 2, 2021
@RossBencina
Copy link
Collaborator

Awaiting response to Dmitry's change requests.

@dmitrykos
Copy link
Collaborator

dmitrykos commented Feb 1, 2022

@not-matt thank you for the changes.

The implementation can be improved further by calculating the number of available eRender devices in order to allocate memory once here:

if ((paWasapi->deviceCount != 0) && ((deviceInfoArray = AllocateDeviceListMemory(paWasapi)) == NULL))
Otherwise memory is wasted (not leaked completely but it is inefficient anyway) when you reassign it here:
if ((hostApi->deviceInfos = (PaDeviceInfo **)PaUtil_GroupAllocateMemory(paWasapi->allocations,

If memory is allocated once by AllocateDeviceListMemory then AddLoopbacksToDeviceList could be minimized to only fill the loopback-specific info inside the

for (i = 0; i < paWasapi->deviceCount; ++i)
loop.

It would make implementation much simpler.

Here is helper function which will return the number of eRender devices in the device list:

// ------------------------------------------------------------------------------------------
static UINT32 GetRenderDeviceCount(IMMDeviceCollection *pEndPoints)
{
    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 == eRender);

            SAFE_RELEASE(endpoint);
        }

        SAFE_RELEASE(device);
    }

    return ret;

error:

    return 0;
}

@not-matt
Copy link
Contributor

not-matt commented Feb 1, 2022

Hi @dmitrykos, thank you for the feedback. I've worked with @WeekendWarrior1 in the past but I've not been able to reach him to wrap up this PR.

I can make an attempt to implement your feedback, but I have no experience writing C. If you have the time, would you consider writing these changes yourself?

I'm in touch with @shauneccles and can get any changes you would make added to this PR very quickly.

@dmitrykos
Copy link
Collaborator

@not-matt ok, I will try to implement in the way I advised and let's see how it works then.

@not-matt
Copy link
Contributor

not-matt commented Feb 2, 2022

Nice, cheers. Do let me know if there's anything I can help you with, especially testing.

@nidefawl
Copy link

nidefawl commented Feb 3, 2022

I have been waiting for this feature to land upstream. I maintained my own version for a couple of years.
I will help testing once @dmitrykos is done with the implementation

@dmitrykos
Copy link
Collaborator

I made necessary cleanup changes, would you please test how it works on your side. I tested Win32 and UWP modes and it works quite well, no problems noted.

Loopback mode does not support Exclusive mode though that is quite natural because it is a proxy for a playback device.

If your test results confirm that all runs fine I think we could finally merge it.

@not-matt
Copy link
Contributor

not-matt commented Feb 4, 2022

Good stuff! I'm away this weekend, I'll take a look once I'm back.

@dmitrykos dmitrykos marked this pull request as ready for review February 5, 2022 09:22
@dmitrykos dmitrykos added the windows Affects MS Windows label Feb 5, 2022
@nidefawl
Copy link

nidefawl commented Feb 5, 2022

I built and linked against this PR and the loopback input works nicely!

I haven't tested the PaWasapi_IsLoopback function yet

@nidefawl
Copy link

nidefawl commented Feb 5, 2022

PaWasapi_IsLoopback works as intended

@dmitrykos
Copy link
Collaborator

@nidefawl great, thank you for testing!

@shauneccles
Copy link

Can confirm it behaves as expected over here on Win 10/Win 11.

… allocations inside the CreateDeviceList(), cleaned up comment, added export for PaWasapi_IsLoopback into .def.
@dmitrykos
Copy link
Collaborator

@shauneccles thank you for testing. I added comments into the source code regarding [Loopback] loopback device name identificator usage. If the form of this identificator is unexpected by some of your projects, please comment. I assumed that Windows audio device names often have device description in (), so to differentiate identificator more I used [Loopback]. It can be adjusted of course if such change created some problems.

@shauneccles
Copy link

No issues with using square brackets that I can see/think of, it makes sense to keep them different to give people another signal that it is a loopback device.

@not-matt
Copy link
Contributor

not-matt commented Feb 7, 2022

+1, this works well for me on Windows. Haven't ran into any issues.

@dmitrykos
Copy link
Collaborator

dmitrykos commented Feb 7, 2022

Thank you for proposing the PR and tests, I think it can go into the main repo!

Closes #668

@dmitrykos dmitrykos merged commit 49fbdfb into PortAudio:master Feb 7, 2022
@Be-ing
Copy link
Collaborator

Be-ing commented Feb 7, 2022

Thanks for taking the time to do this right and get it upstreamed!

@bear101
Copy link
Contributor

bear101 commented Feb 7, 2022

After adding loopback devices to WASAPI it looks like PaHostApiInfo::defaultInputDevice and PaHostApiInfo::defaultOutputDevice are broken. I haven't quite figured out why the indexing is not matching.

#include <portaudio.h>
#include <iostream>

int main(int argc, char* args[])
{
    PaError err = Pa_Initialize();

    int n_devices = Pa_GetDeviceCount();

    for (int i=0;i<n_devices;++i)
    {
        const PaDeviceInfo* devinfo = Pa_GetDeviceInfo(i);
        std::cout << "#" << i << " " << devinfo->name << std::endl;
    }

    PaHostApiIndex hostapiIndex = Pa_HostApiTypeIdToHostApiIndex(paWASAPI);
    const PaHostApiInfo* hostapi = Pa_GetHostApiInfo(hostapiIndex);

    std::cout << "Default WASPI input #" << hostapi->defaultInputDevice << std::endl;
    std::cout << "Default WASPI output #" << hostapi->defaultOutputDevice << std::endl;
    return 0;
}

Output:

#0 Microsoft Sound Mapper - Input
#1 Microphone (High Definition Aud
#2 Microphone (HD Pro Webcam C920)
#3 Microsoft Sound Mapper - Output
#4 Headphones (High Definition Aud
#5 VE248 (Intel(R) Display Audio)
#6 Primary Sound Capture Driver
#7 Microphone (High Definition Audio Device)
#8 Microphone (HD Pro Webcam C920)
#9 Primary Sound Driver
#10 Headphones (High Definition Audio Device)
#11 VE248 (Intel(R) Display Audio)
#12 VE248 (Intel(R) Display Audio)
#13 Headphones (High Definition Audio Device)
#14 Microphone (High Definition Audio Device)
#15 Microphone (HD Pro Webcam C920)
#16 VE248 (Intel(R) Display Audio) [Loopback]
#17 Headphones (High Definition Audio Device) [Loopback]
Default WASPI input #16
Default WASPI output #14

Something must be going wrong with the indexing either here and here.

@dmitrykos
Copy link
Collaborator

@bear101 thank you for reporting, I will have a look shortly.

@dmitrykos
Copy link
Collaborator

@bear101 would you please check #688 PR regarding the problem you noticed.

@GiuseppeUFSM
Copy link

Hello Guys, is there any exemple of the loopback feature implementation?
I'm asking bc I didn't find it and, as I'm learning now how to use PortAudio, it would be extremely helpful!
Thanks!

@dechamps
Copy link
Contributor

@GiuseppeUFSM You don't need any special code to use this feature. Just use PortAudio normally and the "virtual" loopback devices will just naturally show up in the usual list of input devices. In fact it should even work in existing applications if you replace their PortAudio DLL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
src-wasapi MS WASAPI Host API /src/hostapi/wasapi windows Affects MS Windows
Projects
None yet
Development

Successfully merging this pull request may close these issues.