Friday, June 13, 2008 4:53 PM
Maurits
Sample: find out if your default audio playback and audio capture devices are on the same hardware
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;
}