///////////////////////////////////////////////////////////////////////////////
// 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()