Топологии устройств

API DeviceTopology предоставляет клиентам управление различными внутренними функциями звуковых адаптеров, к которым они не могут обращаться через API MMDevice, WASAPI или API EndpointVolume.

Как описано ранее, API MMDevice, WASAPI и API EndpointVolume представляют микрофоны, динамики, наушники и другие устройства ввода звука и вывода для клиентов в качестве конечных точек звука. Модель устройства конечной точки предоставляет клиентам удобный доступ к элементам управления громкостью и отключением звука на звуковых устройствах. Клиенты, требующие только этих простых элементов управления, могут избежать обхода внутренних топологий аппаратных устройств в аудиоадаптерах.

В Windows Vista звуковой модуль автоматически настраивает топологии звуковых устройств для использования звуковыми приложениями. Таким образом, приложения редко, если когда-либо, должны использовать API DeviceTopology для этой цели. Например, предположим, что звуковой адаптер содержит входной мультиплексер, который может записывать поток из строковых входных данных или микрофона, но не может одновременно записывать потоки с обоих конечных точек. Предположим, что пользователь включил приложения в монопольном режиме для предварительного использования устройства аудио конечной точки приложениями общего режима, как описано в разделе "Монопольный режим" Потоки. Если приложение общего режима записывает поток из входных данных строки в то время, когда приложение монопольного режима начинает запись потока с микрофона, звуковой модуль автоматически переключает мультиплексер из ввода строки на микрофон. В более ранних версиях Windows, включая Windows XP, приложение монопольного режима в этом примере будет использовать функции mixerXxx в мультимедийном API Windows для обхода топологий устройств адаптера, обнаружения мультиплексера и настройки мультиплексера для выбора входных данных микрофона. В Windows Vista эти действия больше не требуются.

Однако некоторым клиентам может потребоваться явный контроль над типами звуковых аппаратных элементов управления, к которым не удается получить доступ через API MMDevice, WASAPI или API EndpointVolume. Для этих клиентов API DeviceTopology предоставляет возможность обхода топологий устройств адаптера для обнаружения и управления звуковыми элементами управления на устройствах. Приложения, использующие API DeviceTopology, должны быть разработаны с осторожностью, чтобы избежать вмешательства в политику звука Windows и беспокоить внутренние конфигурации звуковых устройств, совместно используемых с другими приложениями. Дополнительные сведения о политике звука Windows см. в разделе "Компоненты аудио в режиме пользователя".

API DeviceTopology предоставляет интерфейсы для обнаружения и управления следующими типами звуковых элементов управления в топологии устройств:

  • Автоматическое управление получением
  • Элемент управления "Бас"
  • Селектор ввода (мультиплексер)
  • Контроль громкости
  • Элемент управления Midrange
  • Отключить элемент управления
  • Селектор вывода (demultiplexer)
  • Пиковый метр
  • Элемент управления Treble
  • Элемент управления томом

Кроме того, API DeviceTopology позволяет клиентам запрашивать устройства адаптеров для получения сведений о поддерживаемых форматах потоков. Файл заголовка Devicetopology.h определяет интерфейсы в API DeviceTopology.

На следующей схеме показан пример нескольких топологий подключенных устройств для части адаптера PCI, который записывает звук с микрофона, входных строк и проигрывателя КОМПАКТ-дисков.

example of four connected device topologies

На предыдущей схеме показаны пути к данным, которые приводят от аналоговых входных данных к системной шине. Каждое из следующих устройств представлено как объект топологии устройств с интерфейсом IDeviceTopology :

  • Устройство захвата волн
  • Устройство мультиплексера ввода
  • Устройство конечной точки A
  • Устройство конечной точки B

Обратите внимание, что схема топологии объединяет устройства адаптера (устройства захвата волн и мультиплексера ввода) с конечными точками. Через подключения между устройствами звуковые данные передаются от одного устройства к следующему. На каждой стороне подключения используется соединитель (помеченный con на схеме), с помощью которого данные входят или покидают устройство.

В левой части диаграммы сигналы от входных линий и микрофонов вставляют устройства конечных точек.

Внутри устройства захвата волн и устройства с многомерным мультиплексером используются функции потоковой обработки, которые в терминологии API DeviceTopology называются подъединениями. На предыдущей схеме отображаются следующие типы вложенных элементов:

  • Управление громкости (помеченный vol)
  • Отключить элемент управления (помеченный отключение звука)
  • Мультиплексер (или селектор ввода; помеченный мультиплексор)
  • Аналогово-цифровой преобразователь (помеченный ADC)

Параметры в томе, отключении и мультиплексоре могут контролироваться клиентами, а API DeviceTopology предоставляет интерфейсы управления клиентам для управления ими. В этом примере в подъединике ADC нет параметров управления. Таким образом, API DeviceTopology не предоставляет интерфейс управления для ADC.

В терминологии API DeviceTopology соединители и вложенные элементы относятся к одной и той же общей категории — частям. Все части, независимо от того, являются ли они соединителями или подъединениями, предоставляют общий набор функций. API DeviceTopology реализует интерфейс IPart для представления универсальных функций, которые являются общими для соединителей и вложенных элементов. API реализует интерфейсы I Подключение or и ISubunit для представления конкретных аспектов соединителей и вложенных элементов.

API DeviceTopology создает топологии устройства захвата волн и входных мультиплексорных устройств из фильтров потоковой передачи ядра (KS), которые звуковой драйвер предоставляет операционной системе для представления этих устройств. (Драйвер аудиоадаптера реализует Интерфейсы IMiniportWaveXxx иIMiniportTopology для представления аппаратных частей этих фильтров; дополнительные сведения об этих интерфейсах и о фильтрах KS см. в документации по Windows DDK.)

API DeviceTopology создает тривиальные топологии для представления конечных точек устройств A и B на предыдущей схеме. Топология устройства конечной точки состоит из одного соединителя. Эта топология является лишь заполнителем для устройства конечной точки и не содержит подсоединений для обработки звуковых данных. На самом деле устройства адаптера содержат все вложенные элементы, используемые клиентскими приложениями для управления обработкой звука. Топология устройства конечной точки служит в первую очередь отправной точкой для изучения топологий устройств адаптеров.

Внутренние подключения между двумя частями топологии устройства называются ссылками. API DeviceTopology предоставляет методы для обхода ссылок от одной части к следующей в топологии устройства. API также предоставляет методы для обхода подключений между топологиями устройств.

Чтобы начать изучение набора топологий подключенных устройств, клиентское приложение активирует интерфейс IDeviceTopology устройства аудио конечной точки. Соединитель на устройстве конечной точки подключается либо к соединителю в звуковом адаптере, либо к сети. Если конечная точка подключается к устройству на звуковом адаптере, методы в API DeviceTopology позволяют приложению выполнять шаги по подключению от конечной точки к адаптеру, получив ссылку на интерфейс IDeviceTopology устройства адаптера на другой стороне подключения. С другой стороны, сеть не имеет топологии устройств. Сетевое подключение передает аудиопоток клиенту, который удаленно обращается к системе.

API DeviceTopology предоставляет доступ только к топологиям аппаратных устройств в звуковом адаптере. Внешние устройства на левом крае схемы и компоненты программного обеспечения в правом крае находятся за пределами область API. Пунктирные линии на обеих сторонах диаграммы представляют ограничения API DeviceTopology. Клиент может использовать API для изучения пути к данным, который простирается от входного джека до системной шины, но API не может проникать за пределы этих границ.

Каждый соединитель на предыдущей схеме имеет связанный тип подключения, указывающий тип соединения, который делает соединитель. Таким образом, соединители на обеих сторонах соединения всегда имеют одинаковые типы соединений. Тип подключения указывается значением перечисления Подключение orType— Physical_External, Physical_Internal, Software_Fixed, Software_IO или Сетью. Подключения между входными мультиплексерными устройствами и конечными точками A и B имеют тип Physical_External, что означает, что подключение представляет физическое подключение к внешнему устройству (другими словами, доступное для пользователей звуковое подключение). Подключение к аналоговому сигналу с внутреннего проигрывателя CD имеет тип Physical_Internal, который указывает физическое подключение к вспомогательному устройству, установленному внутри системного корпуса. Соединение между устройством захвата волн и устройством многомерного ввода является типом Software_Fixed, что означает постоянное подключение, которое исправлено и не может быть настроено под управлением программного обеспечения. Наконец, подключение к системной шине справа от схемы имеет тип Software_IO, который указывает, что данные ввода-вывода для подключения реализуются подсистемой DMA под управлением программного обеспечения. (Схема не содержит пример типа сетевого подключения.)

Клиент начинает обход пути к данным на устройстве конечной точки. Во-первых, клиент получает интерфейс IMMDevice, представляющий устройство конечной точки, как описано в разделе Перечисление звуковых устройств. Чтобы получить интерфейс IDeviceTopology для устройства конечной точки, клиент вызывает метод IMMDevice::Activate с параметром iid set to REFIID IID_IDeviceTopology.

В примере на предыдущей схеме входное мультиплексерное устройство содержит все аппаратные элементы управления (тома, выключения и мультиплексера) для потоков записи из встроенных и микрофонных разъемов. В следующем примере кода показано, как получить интерфейс IDeviceTopology для входного мультиплексера устройства из интерфейса IMMDevice для устройства конечной точки для ввода строки или микрофона:

//-----------------------------------------------------------
// The input argument to this function is a pointer to the
// IMMDevice interface of an endpoint device. The function
// outputs a pointer (counted reference) to the
// IDeviceTopology interface of the adapter device that
// connects to the endpoint device.
//-----------------------------------------------------------
#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const IID IID_IDeviceTopology = __uuidof(IDeviceTopology);
const IID IID_IPart = __uuidof(IPart);

HRESULT GetHardwareDeviceTopology(
            IMMDevice *pEndptDev,
            IDeviceTopology **ppDevTopo)
{
    HRESULT hr = S_OK;
    IDeviceTopology *pDevTopoEndpt = NULL;
    IConnector *pConnEndpt = NULL;
    IConnector *pConnHWDev = NULL;
    IPart *pPartConn = NULL;

    // Get the endpoint device's IDeviceTopology interface.

    hr = pEndptDev->Activate(
                      IID_IDeviceTopology, CLSCTX_ALL,
                      NULL, (void**)&pDevTopoEndpt);
    EXIT_ON_ERROR(hr)

    // The device topology for an endpoint device always
    // contains just one connector (connector number 0).

    hr = pDevTopoEndpt->GetConnector(0, &pConnEndpt);
    EXIT_ON_ERROR(hr)

    // Use the connector in the endpoint device to get the
    // connector in the adapter device.

    hr = pConnEndpt->GetConnectedTo(&pConnHWDev);
    EXIT_ON_ERROR(hr)

    // Query the connector in the adapter device for
    // its IPart interface.

    hr = pConnHWDev->QueryInterface(
                         IID_IPart, (void**)&pPartConn);
    EXIT_ON_ERROR(hr)

    // Use the connector's IPart interface to get the
    // IDeviceTopology interface for the adapter device.

    hr = pPartConn->GetTopologyObject(ppDevTopo);

Exit:
    SAFE_RELEASE(pDevTopoEndpt)
    SAFE_RELEASE(pConnEndpt)
    SAFE_RELEASE(pConnHWDev)
    SAFE_RELEASE(pPartConn)

    return hr;
}

Функция GetHardwareDeviceTopology в предыдущем примере кода выполняет следующие действия, чтобы получить интерфейс IDeviceTopology для входного мультиплексера устройства:

  1. Вызовите метод IMMDevice::Activate, чтобы получить интерфейс IDeviceTopology для устройства конечной точки.
  2. С помощью интерфейса IDeviceTopology, полученного на предыдущем шаге, вызовите метод IDeviceTopology::Get Подключение or, чтобы получить интерфейс I Подключение or одного соединителя (номер 0 соединителя) на устройстве конечной точки.
  3. С помощью интерфейса I Подключение or, полученного на предыдущем шаге, вызовите метод I Подключение or::Get Подключение edTo, чтобы получить интерфейс I Подключение or соединителя на устройстве с входным мультиплексером.
  4. Запросите интерфейс I Подключение or, полученный на предыдущем шаге для интерфейса IPart.
  5. С помощью интерфейса IPart, полученного на предыдущем шаге, вызовите метод IPart::GetTopologyObject, чтобы получить интерфейс IDeviceTopology для входного мультиплексера устройства.

Прежде чем пользователь сможет записать с микрофона на предыдущей схеме, клиентское приложение должно убедиться, что мультиплексор выбирает входные данные микрофона. В следующем примере кода показано, как клиент может пройти путь к данным с микрофона, пока он не найдет мультиплексер, который затем программа будет программировать для выбора входных данных микрофона:

//-----------------------------------------------------------
// The input argument to this function is a pointer to the
// IMMDevice interface for a capture endpoint device. The
// function traverses the data path that extends from the
// endpoint device to the system bus (for example, PCI)
// or external bus (USB). If the function discovers a MUX
// (input selector) in the path, it selects the MUX input
// that connects to the stream from the endpoint device.
//-----------------------------------------------------------
#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const IID IID_IDeviceTopology = __uuidof(IDeviceTopology);
const IID IID_IPart = __uuidof(IPart);
const IID IID_IConnector = __uuidof(IConnector);
const IID IID_IAudioInputSelector = __uuidof(IAudioInputSelector);

HRESULT SelectCaptureDevice(IMMDevice *pEndptDev)
{
    HRESULT hr = S_OK;
    DataFlow flow;
    IDeviceTopology *pDeviceTopology = NULL;
    IConnector *pConnFrom = NULL;
    IConnector *pConnTo = NULL;
    IPart *pPartPrev = NULL;
    IPart *pPartNext = NULL;
    IAudioInputSelector *pSelector = NULL;

    if (pEndptDev == NULL)
    {
        EXIT_ON_ERROR(hr = E_POINTER)
    }

    // Get the endpoint device's IDeviceTopology interface.
    hr = pEndptDev->Activate(
                      IID_IDeviceTopology, CLSCTX_ALL, NULL,
                      (void**)&pDeviceTopology);
    EXIT_ON_ERROR(hr)

    // The device topology for an endpoint device always
    // contains just one connector (connector number 0).
    hr = pDeviceTopology->GetConnector(0, &pConnFrom);
    SAFE_RELEASE(pDeviceTopology)
    EXIT_ON_ERROR(hr)

    // Make sure that this is a capture device.
    hr = pConnFrom->GetDataFlow(&flow);
    EXIT_ON_ERROR(hr)

    if (flow != Out)
    {
        // Error -- this is a rendering device.
        EXIT_ON_ERROR(hr = AUDCLNT_E_WRONG_ENDPOINT_TYPE)
    }

    // Outer loop: Each iteration traverses the data path
    // through a device topology starting at the input
    // connector and ending at the output connector.
    while (TRUE)
    {
        BOOL bConnected;
        hr = pConnFrom->IsConnected(&bConnected);
        EXIT_ON_ERROR(hr)

        // Does this connector connect to another device?
        if (bConnected == FALSE)
        {
            // This is the end of the data path that
            // stretches from the endpoint device to the
            // system bus or external bus. Verify that
            // the connection type is Software_IO.
            ConnectorType  connType;
            hr = pConnFrom->GetType(&connType);
            EXIT_ON_ERROR(hr)

            if (connType == Software_IO)
            {
                break;  // finished
            }
            EXIT_ON_ERROR(hr = E_FAIL)
        }

        // Get the connector in the next device topology,
        // which lies on the other side of the connection.
        hr = pConnFrom->GetConnectedTo(&pConnTo);
        EXIT_ON_ERROR(hr)
        SAFE_RELEASE(pConnFrom)

        // Get the connector's IPart interface.
        hr = pConnTo->QueryInterface(
                        IID_IPart, (void**)&pPartPrev);
        EXIT_ON_ERROR(hr)
        SAFE_RELEASE(pConnTo)

        // Inner loop: Each iteration traverses one link in a
        // device topology and looks for input multiplexers.
        while (TRUE)
        {
            PartType parttype;
            UINT localId;
            IPartsList *pParts;

            // Follow downstream link to next part.
            hr = pPartPrev->EnumPartsOutgoing(&pParts);
            EXIT_ON_ERROR(hr)

            hr = pParts->GetPart(0, &pPartNext);
            pParts->Release();
            EXIT_ON_ERROR(hr)

            hr = pPartNext->GetPartType(&parttype);
            EXIT_ON_ERROR(hr)

            if (parttype == Connector)
            {
                // We've reached the output connector that
                // lies at the end of this device topology.
                hr = pPartNext->QueryInterface(
                                  IID_IConnector,
                                  (void**)&pConnFrom);
                EXIT_ON_ERROR(hr)

                SAFE_RELEASE(pPartPrev)
                SAFE_RELEASE(pPartNext)
                break;
            }

            // Failure of the following call means only that
            // the part is not a MUX (input selector).
            hr = pPartNext->Activate(
                              CLSCTX_ALL,
                              IID_IAudioInputSelector,
                              (void**)&pSelector);
            if (hr == S_OK)
            {
                // We found a MUX (input selector), so select
                // the input from our endpoint device.
                hr = pPartPrev->GetLocalId(&localId);
                EXIT_ON_ERROR(hr)

                hr = pSelector->SetSelection(localId, NULL);
                EXIT_ON_ERROR(hr)

                SAFE_RELEASE(pSelector)
            }

            SAFE_RELEASE(pPartPrev)
            pPartPrev = pPartNext;
            pPartNext = NULL;
        }
    }

Exit:
    SAFE_RELEASE(pConnFrom)
    SAFE_RELEASE(pConnTo)
    SAFE_RELEASE(pPartPrev)
    SAFE_RELEASE(pPartNext)
    SAFE_RELEASE(pSelector)
    return hr;
}

API DeviceTopology реализует интерфейс IAudioInputSelector для инкапсулирования мультиплексера , например в приведенной выше схеме. (An Интерфейс IAudioOutputSelector инкапсулирует демультиплексер .) В приведенном выше примере кода внутренний цикл функции SelectCaptureDevice запрашивает каждый вложенный элемент, который он находит, чтобы определить, является ли вложенный элемент мультиплексером. Если подсоединение является мультиплексером, функция вызывает метод IAudioInputSelector::SetSelection , чтобы выбрать входные данные, которые подключаются к потоку с устройства конечной точки.

В предыдущем примере кода каждая итерация внешнего цикла проходит по одной топологии устройства. При обходе топологий устройств на предыдущей схеме первая итерация проходит входное мультиплексерное устройство, а вторая итерация проходит устройство захвата волн. Функция завершится, когда она достигнет соединителя в правом краю схемы. Завершение происходит, когда функция обнаруживает соединитель с типом подключения Software_IO. Этот тип подключения определяет точку, в которой устройство адаптера подключается к системной шине.

Вызов метода IPart::GetPartType в предыдущем примере кода получает значение перечисления IPartType, указывающее, является ли текущая часть соединителем или подсоединение для обработки звука.

Внутренний цикл в приведенном выше примере кода выполняется по ссылке от одной части к следующей, вызвав метод IPart::EnumPartsOutgoing. (Есть также Метод IPart::EnumPartsIncoming для шага в противоположном направлении.) Этот метод извлекает объект IPartsList , содержащий список всех исходящих частей. Однако любая часть, с которой ожидается, что функция SelectCaptureDevice будет встречаться на устройстве захвата, всегда будет иметь ровно одну исходящую часть. Таким образом, последующий вызов IPartsList::GetPart всегда запрашивает первую часть списка, часть номер 0, так как функция предполагает, что это единственная часть списка.

Если функция SelectCaptureDevice встречает топологию, для которой недопустимо это предположение, функция может не настроить устройство правильно. Чтобы избежать такого сбоя, более общая версия функции может выполнять следующие действия:

  • Вызовите метод IPartsList::GetCount, чтобы определить количество исходящих частей.
  • Для каждой исходящей части вызовите IPartsList::GetPart , чтобы начать обход пути к данным, который ведет из части.

Некоторые части, но не обязательно все, имеют связанные аппаратные элементы управления, которые клиенты могут задать или получить. Определенная часть может иметь ноль, один или несколько аппаратных элементов управления. Аппаратный элемент управления представлен следующей парой интерфейсов:

  • Универсальный интерфейс управления IControlInterface с методами, общими для всех аппаратных элементов управления.
  • Интерфейс, зависящий от функции (например, IAudioVolumeLevel), предоставляющий параметры управления для определенного типа аппаратного элемента управления (например, элемент управления томом).

Чтобы перечислить аппаратные элементы управления для части, клиент сначала вызывает метод IPart::GetControlInterfaceCount , чтобы определить количество аппаратных элементов управления, связанных с частью. Затем клиент выполняет ряд вызовов метода IPart::GetControlInterface для получения интерфейса IControlInterface для каждого аппаратного элемента управления. Наконец, клиент получает интерфейс для каждого аппаратного элемента управления, вызывая метод IControlInterface::GetIID для получения идентификатора интерфейса. Клиент вызывает метод IPart::Activate с этим идентификатором, чтобы получить интерфейс для конкретной функции.

Часть, которая является соединителем, может поддерживать один из следующих интерфейсов управления для конкретной функции:

Часть, которая является вложенным элементом, может поддерживать один или несколько следующих интерфейсов управления для конкретной функции:

Часть поддерживает интерфейс IDeviceSpecificProperty только в том случае, если базовый аппаратный элемент управления имеет значение элемента управления для конкретного устройства, а элемент управления не может быть достаточно представлен любым другим интерфейсом, зависящим от функций в предыдущем списке. Как правило, свойство, зависящее от устройства, полезно только для клиента, который может определить значение значения свойства из сведений, таких как тип части, подтип части и имя части. Клиент может получить эти сведения, вызвав методы IPart::GetPartType, IPart::GetSubType и IPart::GetName.

Руководство по программированию