2025-11-28 00:35:46 +09:00

477 lines
14 KiB
C++

//-------------------------------------------------------------------------
// File: AecKsBinder.cpp
//
// Desciption: Definition of audio devices binding functions
//
// Copyright (c) 2004-2006, Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------------------
#include "AecKsbinder.h"
#include "strsafe.h"
#include "functiondiscoverykeys.h" // PKEY_Device_FriendlyName
#ifndef IF_FAILED_JUMP
#define IF_FAILED_JUMP(hr, label) if(FAILED(hr)) goto label;
#endif
#ifndef IF_FAILED_RETURN
#define IF_FAILED_RETURN(hr) if(FAILED(hr)) return hr;
#endif
///////////////////////////////////////////////////////////////////////////
//
// Function:
// DeviceBindTo
//
// Description:
// Bind device to an IAudioClient interface.
//
// Parameters:
// eDataFlow: eRender for render device, eCapture for capture device
// iDevIdx: Index of device in the enumeration list. If it is -1, use default device
// ppVoid: pointer pointer to IAudioClient interface.
// ppszEndpointDeviceId: Device ID. Caller is responsible for freeing memeory
// using CoTaskMemoryFree. If can be NULL if called doesn't need this info.
//
// Return:
// S_OK if successful
//
///////////////////////////////////////////////////////////////////////////////
HRESULT DeviceBindTo(
EDataFlow eDataFlow, // eCapture/eRender
INT iDevIdx, // Device Index. -1 - default device.
IAudioClient **ppAudioClient, // pointer pointer to IAudioClient interface
IAudioEndpointVolume **ppEndpointVolume,
WCHAR** ppszEndpointDeviceId) // Device ID. Need to be freed in caller with CoTaskMemoryFree if it is not NULL
{
HRESULT hResult;
CComPtr<IMMDeviceEnumerator> spEnumerator;
CComPtr<IMMDeviceCollection> spEndpoints;
CComPtr<IMMDevice> spDevice;
WCHAR *pszDeviceId = NULL;
if (ppAudioClient == NULL)
return E_POINTER;
*ppAudioClient = NULL;
hResult = spEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
IF_FAILED_JUMP(hResult, Exit);
// use default device
if (iDevIdx < 0 )
{
hResult = spEnumerator->GetDefaultAudioEndpoint(eDataFlow, eConsole, &spDevice);
IF_FAILED_JUMP(hResult, Exit);
}else{
// User selected device
hResult = spEnumerator->EnumAudioEndpoints(eDataFlow, DEVICE_STATE_ACTIVE, &spEndpoints);
IF_FAILED_JUMP(hResult, Exit);
hResult = spEndpoints->Item(iDevIdx, &spDevice);
IF_FAILED_JUMP(hResult, Exit);
}
// get device ID and format
hResult = spDevice->GetId(&pszDeviceId);
IF_FAILED_JUMP(hResult, Exit);
// Active device
hResult = spDevice->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)ppAudioClient);
IF_FAILED_JUMP(hResult, Exit);
if (ppEndpointVolume)
{
hResult = spDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (void **)ppEndpointVolume);
IF_FAILED_JUMP(hResult, Exit);
}
Exit:
if (ppszEndpointDeviceId)
*ppszEndpointDeviceId = pszDeviceId;
else if (pszDeviceId)
CoTaskMemFree(pszDeviceId);
return hResult;
}
///////////////////////////////////////////////////////////////////////////////
//
// Function:
// GetDeviceNum
//
// Description:
// Enumerate audio device and return the device information.
//
// Parameters:
// eDataFlow: eRender for render device, eCapture for capture device
// uDevCount: Number of device
//
// Return:
// S_OK if successful
//
///////////////////////////////////////////////////////////////////////////////
HRESULT GetDeviceNum(EDataFlow eDataFlow, UINT &uDevCount)
{
HRESULT hResult = S_OK;
CComPtr<IMMDeviceEnumerator> spEnumerator;
CComPtr<IMMDeviceCollection> spEndpoints;
hResult = spEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
IF_FAILED_RETURN(hResult);
hResult = spEnumerator->EnumAudioEndpoints(eDataFlow, DEVICE_STATE_ACTIVE, &spEndpoints);
IF_FAILED_RETURN(hResult);
hResult = spEndpoints->GetCount(&uDevCount);
IF_FAILED_RETURN(hResult);
return hResult;
}
///////////////////////////////////////////////////////////////////////////
//
// Function:
// EnumDevice
//
// Description:
// Enumerate audio device and return the device information.
//
// Parameters:
// eDataFlow: eRender for render device, eCapture for capture device
// uNumElements: Size of audio device info structure array.
// pDevicInfo: device info structure array. Caller is responsible to allocate and free
// memory. The array size is specified by uNumElements.
//
// Return:
// S_OK if successful
//
///////////////////////////////////////////////////////////////////////////////
HRESULT EnumDevice(EDataFlow eDataFlow, UINT uNumElements, AUDIO_DEVICE_INFO *pDevicInfo)
{
HRESULT hResult = S_OK;
WCHAR* pszDeviceId = NULL;
PROPVARIANT value;
UINT index, dwCount;
bool IsMicArrayDevice;
CComPtr<IMMDeviceEnumerator> spEnumerator;
CComPtr<IMMDeviceCollection> spEndpoints;
hResult = spEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
IF_FAILED_JUMP(hResult, Exit);
hResult = spEnumerator->EnumAudioEndpoints(eDataFlow, DEVICE_STATE_ACTIVE, &spEndpoints);
IF_FAILED_JUMP(hResult, Exit);
hResult = spEndpoints->GetCount(&dwCount);
IF_FAILED_JUMP(hResult, Exit);
if (dwCount != uNumElements)
return E_INVALIDARG;
ZeroMemory(pDevicInfo, sizeof(AUDIO_DEVICE_INFO)*uNumElements);
for (index = 0; index < dwCount; index++)
{
CComPtr<IMMDevice> spDevice;
CComPtr<IPropertyStore> spProperties;
PropVariantInit(&value);
hResult = spEndpoints->Item(index, &spDevice);
IF_FAILED_JUMP(hResult, Exit);
hResult = spDevice->GetId(&pszDeviceId);
IF_FAILED_JUMP(hResult, Exit);
hResult = spDevice->OpenPropertyStore(STGM_READ, &spProperties);
IF_FAILED_JUMP(hResult, Exit);
hResult = spProperties->GetValue(PKEY_Device_FriendlyName, &value);
IF_FAILED_JUMP(hResult, Exit);
EndpointIsMicArray(spDevice, IsMicArrayDevice);
StringCchCopy(pDevicInfo[index].szDeviceID, MAX_STR_LEN-1, pszDeviceId);
StringCchCopy(pDevicInfo[index].szDeviceName, MAX_STR_LEN-1, value.pwszVal);
pDevicInfo[index].bIsMicArrayDevice = IsMicArrayDevice;
PropVariantClear(&value);
CoTaskMemFree(pszDeviceId);
pszDeviceId = NULL;
}
Exit:
return hResult;
}
///////////////////////////////////////////////////////////////////////////////
// Function:
// DeviceIsMicArray
//
// Description:
// Determines if a given IMMDevice is a microphone array by device ID
//
// Returns:
// S_OK on success
///////////////////////////////////////////////////////////////////////////////
HRESULT DeviceIsMicArray(wchar_t szDeviceId[], bool &bIsMicArray)
{
HRESULT hr = S_OK;
if (szDeviceId == NULL)
return E_INVALIDARG;
CComPtr<IMMDeviceEnumerator> spEnumerator;
CComPtr<IMMDevice> spDevice;
hr = spEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
IF_FAILED_RETURN(hr);
hr = spEnumerator->GetDevice(szDeviceId, &spDevice);
IF_FAILED_RETURN(hr);
hr = EndpointIsMicArray(spDevice, bIsMicArray);
return hr;
}
///////////////////////////////////////////////////////////////////////////////
// Function:
// EndpointIsMicArray
//
// Description:
// Determines if a given IMMDevice is a microphone array by Endpoint pointer
//
// Returns:
// S_OK on success
///////////////////////////////////////////////////////////////////////////////
HRESULT EndpointIsMicArray(IMMDevice* pEndpoint, bool & isMicrophoneArray)
{
if (pEndpoint == NULL)
return E_POINTER;
GUID subType = {0};
HRESULT hr = GetJackSubtypeForEndpoint(pEndpoint, &subType);
isMicrophoneArray = (subType == KSNODETYPE_MICROPHONE_ARRAY) ? true : false;
return hr;
}// EndpointIsMicArray()
///////////////////////////////////////////////////////////////////////////////
// Function:
// GetJackSubtypeForEndpoint
//
// Description:
// Gets the subtype of the jack that the specified endpoint device
// is plugged into. e.g. if the endpoint is for an array mic, then
// we would expect the subtype of the jack to be
// KSNODETYPE_MICROPHONE_ARRAY
//
// Return:
// S_OK if successful
//
///////////////////////////////////////////////////////////////////////////////
HRESULT GetJackSubtypeForEndpoint(IMMDevice* pEndpoint, GUID* pgSubtype)
{
HRESULT hr = S_OK;
if (pEndpoint == NULL)
return E_POINTER;
CComPtr<IDeviceTopology> spEndpointTopology;
CComPtr<IConnector> spPlug;
CComPtr<IConnector> spJack;
CComQIPtr<IPart> spJackAsPart;
// Get the Device Topology interface
hr = pEndpoint->Activate(__uuidof(IDeviceTopology), CLSCTX_INPROC_SERVER,
NULL, (void**)&spEndpointTopology);
IF_FAILED_JUMP(hr, Exit);
hr = spEndpointTopology->GetConnector(0, &spPlug);
IF_FAILED_JUMP(hr, Exit);
hr = spPlug->GetConnectedTo(&spJack);
IF_FAILED_JUMP(hr, Exit);
spJackAsPart = spJack;
hr = spJackAsPart->GetSubType(pgSubtype);
Exit:
return hr;
}//GetJackSubtypeForEndpoint()
///////////////////////////////////////////////////////////////////////////////
// GetInputJack() -- Gets the IPart interface for the input jack on the
// specified device.
///////////////////////////////////////////////////////////////////////////////
HRESULT GetInputJack(IMMDevice* pDevice, CComPtr<IPart>& spPart)
{
HRESULT hr = S_OK;
if (pDevice == NULL)
return E_POINTER;
CComPtr<IDeviceTopology> spTopology;
CComPtr<IConnector> spPlug;
CComPtr<IConnector> spJack = NULL;
// Get the Device Topology interface
hr = pDevice->Activate(__uuidof(IDeviceTopology),
CLSCTX_INPROC_SERVER, NULL,
reinterpret_cast<void**>(&spTopology));
IF_FAILED_RETURN(hr);
hr = spTopology->GetConnector(0, &spPlug);
IF_FAILED_RETURN(hr);
hr = spPlug->GetConnectedTo(&spJack);
IF_FAILED_RETURN(hr);
// QI for the part
spPart = spJack;
if (spPart == NULL)
return E_NOINTERFACE;
return hr;
}// GetInputJack()
///////////////////////////////////////////////////////////////////////////////
// Function:
// GetMicArrayGeometry()
//
// Description:
// Obtains the geometry for the specified mic array.
//
// Parameters: szDeviceId -- The requested device ID, which can be obtained
// from calling EnumAudioCaptureDevices()
//
// ppGeometry -- Address of the pointer to the mic-array gemometry.
// Caller is ressponsible for calling CoTaskMemFree()
// if the call is successfull.
//
// cbSize -- size of the geometry structure
//
// Returns: S_OK on success
///////////////////////////////////////////////////////////////////////////////
HRESULT GetMicArrayGeometry(wchar_t szDeviceId[], KSAUDIO_MIC_ARRAY_GEOMETRY** ppGeometry, ULONG& cbSize)
{
HRESULT hr = S_OK;
if (szDeviceId == NULL)
return E_INVALIDARG;
if (ppGeometry == NULL)
return E_POINTER;
cbSize = 0;
CComPtr<IMMDeviceEnumerator> spEnumerator;
CComPtr<IMMDevice> spDevice;
CComQIPtr<IPart> spPart;
bool bIsMicArray;
hr = spEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
IF_FAILED_RETURN(hr);
hr = spEnumerator->GetDevice(szDeviceId, &spDevice);
IF_FAILED_RETURN(hr);
hr = EndpointIsMicArray(spDevice, bIsMicArray);
IF_FAILED_RETURN(hr);
if (!bIsMicArray)
return E_FAIL;
UINT nPartId = 0;
hr = GetInputJack(spDevice, spPart);
IF_FAILED_RETURN(hr);
hr = spPart->GetLocalId(&nPartId);
IF_FAILED_RETURN(hr);
CComPtr<IDeviceTopology> spTopology;
CComPtr<IMMDeviceEnumerator> spEnum;
CComPtr<IMMDevice> spJackDevice;
CComPtr<IKsControl> spKsControl;
wchar_t * pwstrDevice = 0;
// Get the topology object for the part
hr = spPart->GetTopologyObject(&spTopology);
IF_FAILED_RETURN(hr);
// Get the id of the IMMDevice that this topology object describes.
hr = spTopology->GetDeviceId(&pwstrDevice);
IF_FAILED_RETURN(hr);
// Get an IMMDevice pointer using the ID
hr = spEnum.CoCreateInstance(__uuidof(MMDeviceEnumerator));
IF_FAILED_JUMP(hr, Exit);
hr = spEnum->GetDevice(pwstrDevice, &spJackDevice);
IF_FAILED_JUMP(hr, Exit);
// Activate IKsControl on the IMMDevice
hr = spJackDevice->Activate(__uuidof(IKsControl), CLSCTX_INPROC_SERVER,
NULL, reinterpret_cast<void**>(&spKsControl));
IF_FAILED_JUMP(hr, Exit);
// At this point we can use IKsControl just as we would use DeviceIoControl
KSP_PIN ksp;
ULONG cbData = 0;
ULONG cbGeometry = 0;
// Inititialize the pin property
::ZeroMemory(&ksp, sizeof(ksp));
ksp.Property.Set = KSPROPSETID_Audio;
ksp.Property.Id = KSPROPERTY_AUDIO_MIC_ARRAY_GEOMETRY;
ksp.Property.Flags = KSPROPERTY_TYPE_GET;
ksp.PinId = nPartId & PARTID_MASK;
// Get data size by passing NULL
hr = spKsControl->KsProperty(reinterpret_cast<PKSPROPERTY>(&ksp),
sizeof(ksp), NULL, 0, &cbGeometry);
IF_FAILED_JUMP(hr, Exit);
// Allocate memory for the microphone array geometry
*ppGeometry = reinterpret_cast<KSAUDIO_MIC_ARRAY_GEOMETRY*>
(::CoTaskMemAlloc(cbGeometry));
if(*ppGeometry == 0)
{
hr = E_OUTOFMEMORY;
}
IF_FAILED_JUMP(hr, Exit);
// Now retriev the mic-array structure...
DWORD cbOut = 0;
hr = spKsControl->KsProperty(reinterpret_cast<PKSPROPERTY>(&ksp),
sizeof(ksp), *ppGeometry, cbGeometry,
&cbOut);
IF_FAILED_JUMP(hr, Exit);
cbSize = cbGeometry;
Exit:
if(pwstrDevice != 0)
{
::CoTaskMemFree(pwstrDevice);
}
return hr;
}//GetMicArrayGeometry()