Sample: find out if your default audio playback and audio capture devices are on the same hardware

Sample: find out if your default audio playback and audio capture devices are on the same hardware

  • Comments 5

vbBretty on the audio forums asked how to tell if a given audio output and a given audio input were on the same physical audio card.

I found the exercise interesting enough to share here:

// main.cpp

#define INITGUID

#include <windows.h>
#include <tchar.h>

const GUID GUID_NULL = { 0 };

#include <atlstr.h>
#include <mmdeviceapi.h>
#include <devicetopology.h>
#include <functiondiscoverykeys.h>

#define LOG(formatstring, ...) _tprintf(formatstring _T("\n"), __VA_ARGS__)

// helper class to CoInitialize/CoUninitialize
class CCoInitialize {
private:
    HRESULT m_hr;
public:
    CCoInitialize(PVOID pReserved, HRESULT &hr)
        : m_hr(E_UNEXPECTED) { hr = m_hr = CoInitialize(pReserved); }
    ~CCoInitialize() { if (SUCCEEDED(m_hr)) { CoUninitialize(); } }
};

// helper class to CoTaskMemFree
class CCoTaskMemFreeOnExit {
private:
    PVOID m_p;
public:
    CCoTaskMemFreeOnExit(PVOID p) : m_p(p) {}
    ~CCoTaskMemFreeOnExit() { CoTaskMemFree(m_p); }
};

// helper class to PropVariantClear
class CPropVariantClearOnExit {
private:
    PROPVARIANT *m_p;
public:
    CPropVariantClearOnExit(PROPVARIANT *p) : m_p(p) {}
    ~CPropVariantClearOnExit() { PropVariantClear(m_p); }
};

// find the default capture and render audio devices
// determine whether they are on the same audio hardware
int _tmain() {
    HRESULT hr = S_OK;

    // initialize COM
    CCoInitialize ci(NULL, hr);
    if (FAILED(hr)) {
        LOG(_T("CoInitialize failed: hr = 0x%08x"), hr);
        return __LINE__;
    }

    // get enumerator
    CComPtr<IMMDeviceEnumerator> pMMDeviceEnumerator;
    hr = pMMDeviceEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
    if (FAILED(hr)) {
        LOG(_T("CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x"), hr);
        return __LINE__;
    }

    // get default render/capture endpoints
    CComPtr<IMMDevice> pRenderEndpoint;
    hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pRenderEndpoint);
    if (FAILED(hr)) {
        LOG(_T("GetDefaultAudioEndpoint(eRender, eConsole) failed: hr = 0x%08x"), hr);
        return __LINE__;
    }
    
    CComPtr<IMMDevice> pCaptureEndpoint;
    hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &pCaptureEndpoint);
    if (FAILED(hr)) {
        LOG(_T("GetDefaultAudioEndpoint(eCapture, eConsole) failed: hr = 0x%08x"), hr);
        return __LINE__;
    }

    // get endpoint device topologies
    CComPtr<IDeviceTopology> pRenderEndpointTopology;
    hr = pRenderEndpoint->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&pRenderEndpointTopology);
    if (FAILED(hr)) {
        LOG(_T("Render endpoint Activate(IDeviceTopology) failed: hr = 0x%08x"), hr);
        return __LINE__;
    }

    CComPtr<IDeviceTopology> pCaptureEndpointTopology;
    hr = pCaptureEndpoint->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&pCaptureEndpointTopology);
    if (FAILED(hr)) {
        LOG(_T("Capture endpoint Activate(IDeviceTopology) failed: hr = 0x%08x"), hr);
        return __LINE__;
    }

    // get KS filter device IDs
    LPWSTR szRenderFilterId;
    CComPtr<IConnector> pConnector;
    hr = pRenderEndpointTopology->GetConnector(0, &pConnector);
    if (FAILED(hr)) {
        LOG(_T("Render endpoint topology GetConnector(0) failed: hr = 0x%08x"), hr);
        return __LINE__;
    }

    hr = pConnector->GetDeviceIdConnectedTo(&szRenderFilterId);
    if (FAILED(hr)) {
        LOG(_T("Render connector GetDeviceIdConnectedTo() failed: hr = 0x%08x"), hr);
        return __LINE__;
    }
    CCoTaskMemFreeOnExit freeRender(szRenderFilterId);
    LOG(_T("KS filter ID for render endpoint:\n\t%ls"), szRenderFilterId);

    LPWSTR szCaptureFilterId;
    pConnector = NULL;
    hr = pCaptureEndpointTopology->GetConnector(0, &pConnector);
    if (FAILED(hr)) {
        LOG(_T("Capture endpoint topology GetConnector(0) failed: hr = 0x%08x"), hr);
        return __LINE__;
    }

    hr = pConnector->GetDeviceIdConnectedTo(&szCaptureFilterId);
    if (FAILED(hr)) {
        LOG(_T("Capture connector GetDeviceIdConnectedTo() failed: hr = 0x%08x"), hr);
        return __LINE__;
    }
    CCoTaskMemFreeOnExit freeCapture(szCaptureFilterId);
    LOG(_T("KS filter ID for capture endpoint:\n\t%ls"), szCaptureFilterId);

    // get IMMDevices for each associated devnode
    CComPtr<IMMDevice> pRenderDevnode;
    hr = pMMDeviceEnumerator->GetDevice(szRenderFilterId, &pRenderDevnode);
    if (FAILED(hr)) {
        LOG(_T("Getting render devnode via IMMDeviceEnumerator::GetDevice(\"%ls\") failed: hr = 0x%08x"), szRenderFilterId, hr);
        return __LINE__;
    }
    
    CComPtr<IMMDevice> pCaptureDevnode;
    hr = pMMDeviceEnumerator->GetDevice(szCaptureFilterId, &pCaptureDevnode);
    if (FAILED(hr)) {
        LOG(_T("Getting capture devnode via IMMDeviceEnumerator::GetDevice(\"%ls\") failed: hr = 0x%08x"), szCaptureFilterId, hr);
        return __LINE__;
    }
    LOG(_T(""));

    // open property set on each devnode
    CComPtr<IPropertyStore> pRenderDevnodePropertyStore;
    hr = pRenderDevnode->OpenPropertyStore(STGM_READ, &pRenderDevnodePropertyStore);
    if (FAILED(hr)) {
        LOG(_T("Getting render devnode property store failed: hr = 0x%08x"), hr);
        return __LINE__;
    }

    CComPtr<IPropertyStore> pCaptureDevnodePropertyStore;
    hr = pCaptureDevnode->OpenPropertyStore(STGM_READ, &pCaptureDevnodePropertyStore);
    if (FAILED(hr)) {
        LOG(_T("Getting capture devnode property store failed: hr = 0x%08x"), hr);
        return __LINE__;
    }

    // get PKEY_Device_InstanceId property
    PROPVARIANT varRenderInstanceId; PropVariantInit(&varRenderInstanceId);
    CPropVariantClearOnExit clearRender(&varRenderInstanceId);
    hr = pRenderDevnodePropertyStore->GetValue(PKEY_Device_InstanceId, &varRenderInstanceId);
    if (FAILED(hr)) {
        LOG(_T("Render devnode property store GetValue(PKEY_Device_InstanceId) failed: hr = 0x%08x"), hr);
        return __LINE__;
    }
    if (VT_LPWSTR != varRenderInstanceId.vt) {
        LOG(_T("Render instance id variant type is %u - expected VT_LPWSTR"), varRenderInstanceId.vt);
        return __LINE__;
    }
    LOG(_T("Instance Id of default render device: %ls"), varRenderInstanceId.pwszVal);

    PROPVARIANT varCaptureInstanceId; PropVariantInit(&varCaptureInstanceId);
    CPropVariantClearOnExit clearCapture(&varCaptureInstanceId);
    hr = pCaptureDevnodePropertyStore->GetValue(PKEY_Device_InstanceId, &varCaptureInstanceId);
    if (FAILED(hr)) {
        LOG(_T("Capture devnode property store GetValue(PKEY_Device_InstanceId) failed: hr = 0x%08x"), hr);
        return __LINE__;
    }
    if (VT_LPWSTR != varCaptureInstanceId.vt) {
        LOG(_T("Capture instance id variant type is %u - expected VT_LPWSTR"), varCaptureInstanceId.vt);
        return __LINE__;
    }
    LOG(_T("Instance Id of default capture device: %ls"), varCaptureInstanceId.pwszVal);

    LOG(_T(""));

    // paydirt
    if (0 == _wcsicmp(varRenderInstanceId.pwszVal, varCaptureInstanceId.pwszVal)) {
        LOG(_T("Default render and capture audio endpoints ARE on the same hardware."));
    } else {
        LOG(_T("Default render and capture audio endpoints ARE NOT on the same hardware."));
    }

    return 0;
}

Leave a Comment
  • Please add 1 and 2 and type the answer here:
  • Post
  • This is a perfect example of how something should be much simpler than it is.  That's a ridiculous amount of code!

  • Yes... I agree that the DeviceTopology "connector dance" is a little cumbersome, and the PKEY_Device_InstanceId trick starts to smack of voodoo.

    I considered writing a helper function

    HRESULT InstanceId_From_IMMDevice(IMMDevice *pEndpoint, LPWSTR *ppwszInstanceId);

    which would cut the length of the program by about a third, but I think the sample is more readable (though longer) the way I did it.

    On the other hand, the code posted is fully functional, under 200 lines, and that's with full logging, error checking, comments, and denormalization.

    What are the scenarios when you need to know what hardware is behind the audio endpoint?

  • can you please tell me .......from where to get this DeviceTopology.h header file cz it seems not to be present.....

  • Download the Windows SDK.

    http://blogs.msdn.com/windowssdk/

    On my machine devicetopology.h is in the following folder:

    C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include

  • > What are the scenarios when you need

    > to know what hardware is behind the audio endpoint?

    I'm just rambling here, but hey:

    Any app that provides voice communication, esp. games might be interested in knowing that. Users do care about their default output device, but a lot of errors in voice com stem from improperly set input configuration. If the user is already using e.g. a USB headset for output, the game can make a safe bet by using the headset's microphone. Or at least point the user to mismatching configs.

Page 1 of 1 (5 items)