Поделиться через


Потоковая передача ACX

В этом разделе рассматриваются потоковая передача ACX и связанная буферизация, которая имеет решающее значение для простого звука. В нем описываются механизмы, используемые драйвером для обмена данными о состоянии потока и управления буфером для потока. Список распространенных терминов звука ACX и введение в ACX см. в обзоре расширений аудиоклассов ACX.

Типы потоковой передачи ACX

AcxStream представляет аудиопоток на оборудовании определенного канала. AcxStream может агрегировать один или несколько объектов AcxElements, таких как.

Платформа ACX поддерживает два типа потоков. Первый тип потока , RT Packet Stream, обеспечивает поддержку выделения пакетов RT и использования пакетов RT для передачи звуковых данных в оборудование устройства или с переходов состояния потока. Второй тип потока, базовый поток, обеспечивает только поддержку переходов состояния потока.

В одной конечной точке канала канал должен быть потоком потоковой передачи, который создает поток пакетов RT. Если для создания конечной точки подключены два или более каналов, первый канал в конечной точке является каналом потоковой передачи и создает поток пакетов RT; подключенные каналы создают базовые потоки для получения событий, связанных с переходами состояния потока.

Дополнительные сведения см. в разделе "Поток ACX" в сводке по объектам ACX. DDIs для потока определяются в заголовке acxstreams.h .

Стек потоковой передачи ACX

Существует два типа обмена данными для потоковой передачи ACX. Один путь связи используется для управления поведением потоковой передачи для таких команд, как Start, Create и Allocate, который будет использовать стандартные связи ACX. Платформа ACX использует очереди ввода-вывода и передает запросы WDF с помощью очередей. Поведение очереди скрыто от фактического кода драйвера путем использования обратных вызовов Evt и функций ACX, хотя драйвер также может быть предоставлен возможность предварительно обработать все запросы WDF.

Второй и более интересный путь связи используется для сигналов потоковой передачи звука. Это включает в себя указание драйверу, когда пакет готов и получает данные о завершении обработки пакета. 

Основные требования для потоковой передачи сигналов:

  • Поддержка воспроизведения сбоем без сбоев
    • Низкая задержка
    • Все необходимые блокировки ограничены потоком в вопросе
  • Простота использования для разработчика драйверов

Чтобы взаимодействовать с драйвером для сигнала о состоянии потоковой передачи, ACX использует события с общим буфером и прямыми вызовами IRP. Далее описано.

Общий буфер

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

Драйвер устройства использует общий буфер для обмена данными с клиентом, из которого выполняется отрисовка или передача пакета. Этот общий буфер включает количество пакетов (на основе 1) последнего завершенного пакета вместе со значением QPC (QueryPerformanceCounter) времени завершения. Для драйвера устройства необходимо указать эти сведения путем вызова AcxRtStreamNotifyPacketComplete. Когда драйвер устройства вызывает AcxRtStreamNotifyPacketComplete, платформа ACX обновит общий буфер с новым числом пакетов и QPC и сигнализирует о событии, совместном с клиентом, чтобы указать, что клиент может считывать новое число пакетов.

Прямые вызовы IRP

Для взаимодействия с клиентом с драйвером используются прямые вызовы IRP. Это сокращает сложности при обеспечении своевременного обработки запросов WDF и хорошо работает в существующей архитектуре.

Клиент может в любое время запросить текущее число пакетов или указать текущее число пакетов драйверу устройства. Эти запросы вызывают обработчики событий драйвера драйвера устройств EvtAcxStreamGetCurrentPacket и EvtAcxStreamSetRenderPacket . Клиент также может запросить текущий пакет записи, который вызовет обработчик событий драйвера драйвера устройства EvtAcxStreamGetCapturePacket .

Сходство с PortCls

Сочетание прямых вызовов IRP и общего буфера, используемого ACX, аналогично обработке завершения буфера в PortCls. IrPs очень похожи, и общий буфер представляет возможность для драйвера напрямую обмениваться данными о количестве пакетов и времени без использования irPs.   Драйверы должны не делать ничего, что требует доступа к блокировкам, которые также используются в путях управления потоками. Это необходимо, чтобы предотвратить сбой. 

Поддержка большого буфера для воспроизведения с низкой мощностью

Чтобы уменьшить объем энергии, потребляемой при воспроизведении содержимого мультимедиа, важно сократить время, которое APU тратит в состоянии высокой мощности. Так как обычное воспроизведение звука использует 10 мс буферов, APU всегда должен быть активным. Чтобы предоставить APU время, необходимое для уменьшения состояния, драйверы ACX могут объявлять поддержку значительно больших буферов в диапазоне размером 1–2 секунды. Это означает, что APU может просыпаться каждые 1–2 секунды, выполнять операции, необходимые для подготовки следующего 1-2 секундного буфера, а затем переходить к наименьшему возможному состоянию питания до тех пор, пока следующий буфер не потребуется. 

В существующих моделях потоковой передачи воспроизведение с низкой мощностью поддерживается с помощью разгрузки воспроизведения. Звуковой драйвер объявляет поддержку разгрузки воспроизведения путем предоставления узла AudioEngine в фильтре волн для конечной точки. Узел AudioEngine предоставляет средства управления подсистемой DSP, используемой драйвером для отрисовки звука из больших буферов с требуемой обработкой.

Узел AudioEngine предоставляет следующие средства:

  • Описание звукового модуля, указывающее стек звука, который закрепляется в фильтре волн, обеспечивает разгрузку и поддержку обратного цикла (и поддержку воспроизведения узла). 
  • Диапазон размера буфера, который сообщает стеку звука минимальный и максимальный размер буфера, которые могут поддерживаться для разгрузки. воспроизведение. Диапазон размера буфера может динамически изменяться на основе системного действия. 
  • Поддержка формата, включая поддерживаемые форматы, текущий формат сочетания устройств и формат устройства. 
  • Том, включая поддержку расширения, так как с большим объемом буферов программный том не будет реагировать.
  • Loopback Protection, который сообщает драйверу отключить пин-код AudioEngine Loopback, если один или несколько отключенных потоков содержит защищенное содержимое. 
  • Глобальное состояние FX, чтобы включить или отключить GFX в AudioEngine. 

При создании потока на пин-коде разгрузки поток поддерживает volume, Local FX и Loopback Protection. 

Низкое питание воспроизведения с помощью ACX

Платформа ACX использует ту же модель для воспроизведения с низкой мощностью. Драйвер создает три отдельных объекта ACXPIN для потоковой передачи узла, разгрузки и обратной передачи, а также элемента ACXAUDIOENGINE, описывающего, какие из этих закреплений используются для узла, разгрузки и обратного цикла. Драйвер добавляет пин-коды и элемент ACXAUDIOENGINE в ACXCIRCUIT во время создания канала.

Создание внезагрузного потока

Драйвер также добавит элемент ACXAUDIOENGINE в потоки, созданные для разгрузки, чтобы разрешить контроль над томом, отключением звука и пиковых счетчиков.

Схема потоковой передачи

На этой схеме показан драйвер ACX с несколькими стеками.

Схема, иллюстрирующая поля DSP, CODEC и AMP с интерфейсом потоковой передачи ядра поверх.

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

Потоковые закрепления

Каждый ACXCIRCUIT имеет по крайней мере один пин-код приемника и один исходный пин-код. Эти пин-коды используются платформой ACX для предоставления подключений канала к стеку звука. Для канала отрисовки исходный пин-код используется для управления поведением отрисовки любого потока, созданного из канала. Для канала захвата пин-код приемника используется для управления поведением захвата любого потока, созданного из канала.   ACXPIN — это объект, используемый для управления потоковой передачей в звуковом пути. Потоковая передача ACXCIRCUIT отвечает за создание соответствующих объектов ACXPIN для конечной точки аудиопуть во время создания канала и регистрации ACXPINs в ACX. ACXCIRCUIT требуется только для создания пин-кода отрисовки или записи или закреплений для канала; Платформа ACX создаст другой пин-код, необходимый для подключения к каналу и взаимодействия с ним.   

Канал потоковой передачи

Если конечная точка состоит из одного канала, это канал потоковой передачи.

Если конечная точка состоит из нескольких каналов, созданных одним или несколькими драйверами устройств, каналы подключаются в определенном порядке, определяемом ACXCOMPOSITETEMPLATE, описывающей созданную конечную точку. Первый канал в конечной точке — это канал потоковой передачи для конечной точки.

Канал потоковой передачи должен использовать AcxRtStreamCreate для создания потока пакетов RT в ответ на EvtAcxCircuitCreateStream. ACXSTREAM, созданный с помощью AcxRtStreamCreate, позволит драйверу потоковой передачи выделить буфер, используемый для потоковой передачи, и управлять потоком потоковой передачи в ответ на потребности клиента и оборудования.

Следующие каналы в конечной точке должны использовать AcxStreamCreate для создания базового потока в ответ на EvtAcxCircuitCreateStream. Объекты ACXSTREAM, созданные с помощью AcxStreamCreate следующими каналами, позволяют драйверам настраивать оборудование в ответ на изменения состояния потока, такие как приостановка или запуск.

Потоковая передача ACXCIRCUIT — это первый канал для получения запросов на создание потока. Запрос включает устройство, пин-код и формат данных (включая режим).

Каждый ACXCIRCUIT в звуковом пути создаст объект ACXSTREAM, представляющий экземпляр потока канала. Платформа ACX связывает объекты ACXSTREAM вместе (так же, как объекты ACXCIRCUIT). 

Каналы вышестоящего и нижнего потока

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

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

Для каждого канала после канала потоковой передачи закрепление моста AcxPinTypeSink будет подключаться к следующему вышестоящему каналу.

Согласование формата потока

Драйвер объявляет поддерживаемые форматы для создания потока, добавляя поддерживаемые форматы в режим ACXPIN, используемый для создания потока с помощью AcxPinAssignModeDataFormatList и AcxPinGetRawDataFormatList. Для конечных точек с несколькими каналами можно использовать ACXSTREAMBRIDGE для координации режима и поддержки форматирования между каналами ACX. Поддерживаемые форматы потоков для конечной точки определяются потоками ACXPIN, созданными каналом потоковой передачи. Форматы, используемые следующими каналами, определяются закреплением моста предыдущего канала в конечной точке.

По умолчанию платформа ACX создаст ACXSTREAMBRIDGE между каждым каналом в конечной точке с несколькими каналами. По умолчанию ACXSTREAMBRIDGE будет использовать формат по умолчанию режима RAW для закрепления моста вышестоящего канала при пересылке запроса на создание потока в нижестоящий канал. Если пин-код моста вышестоящего канала не имеет форматов, будет использоваться исходный формат потока. Если подключенный пин-код нижнего канала не поддерживает используемый формат, создание потока завершится ошибкой.

Если канал устройства выполняет изменение формата потока, драйвер устройства должен добавить нижестоящий формат в пин-код нижестоящего моста.  

Создание потока

Первым шагом в создании потока является создание экземпляра ACXSTREAM для каждого ACXCIRCUIT в звуковом пути конечной точки. ACX вызывает evtAcxCircuitCreateStream каждого канала. ACX начнется с головного канала и вызовет каждый канал EvtAcxCircuitCreateStream в порядке, заканчивая хвостовой канал. Порядок можно изменить, указав флаг AcxStreamBridgeInvertChangeStateSequence (определенный в ACX_STREAM_BRIDGE_CONFIG_FLAGS) для моста Stream. После создания объекта потока все каналы будут обрабатывать логику потоковой передачи.

Запрос на создание потока отправляется в соответствующий ПИН-код, созданный в рамках создания топологии головного канала, вызывая EvtAcxCircuitCreateStream, указанный во время создания головного канала. 

Канал потоковой передачи — это вышестоящий канал, который изначально обрабатывает запрос на создание потока.

  • Он обновляет структуру ACXSTREAM_INIT, назначая acxStreamCallbacks и AcxRtStreamCallbacks
  • Он создает объект ACXSTREAM с помощью AcxRtStreamCreate
  • Он создает все элементы, относящиеся к потоку (например, ACXVOLUME или ACXAUDIOENGINE).
  • Он добавляет элементы в объект ACXSTREAM
  • Он возвращает объект ACXSTREAM, созданный в платформу ACX

Затем ACX перенаправит создание потока в следующий подчиненный канал.

  • Он обновляет структуру ACXSTREAM_INIT, назначая AcxStreamCallbacks
  • Он создает объект ACXSTREAM с помощью AcxStreamCreate
  • Он создает все элементы, относящиеся к потоку
  • Он добавляет элементы в объект ACXSTREAM
  • Он возвращает объект ACXSTREAM, созданный в платформу ACX

Канал связи между каналами в звуковом пути использует объекты ACXTARGETSTREAM. В этом примере каждый канал будет иметь доступ к очереди ввода-вывода для канала перед ним и каналом за ним в звуковом пути конечной точки. Кроме того, звуковой путь конечной точки является линейным и двунаправленным. Фактическая обработка очереди операций ввода-вывода выполняется платформой ACX.    При создании объекта ACXSTREAM каждый канал может добавлять сведения о контексте в объект ACXSTREAM для хранения и отслеживания частных данных для потока.

Пример потока отрисовки

Создание потока отрисовки в звуковом пути конечной точки, состоящего из трех каналов: DSP, CODEC и AMP. Канал DSP функционирует как канал потоковой передачи и предоставляет обработчик EvtAcxPinCreateStream. Канал DSP также работает в качестве канала фильтра: в зависимости от режима потока и конфигурации, он может применять обработку сигналов к звуковым данным. Канал CODEC представляет DAC, предоставляя функциональные возможности приемника звука. Канал AMP представляет аналоговое оборудование между DAC и динамиком. Канал AMP может обрабатывать обнаружение джека или другие сведения о оборудовании конечной точки.

  1. AudioKSE вызывает NtCreateFile для создания потока.
  2. Это фильтрует acX и заканчивается вызовом evtAcxPinCreateStream канала DSP с закреплением, форматом данных (включая режим) и сведениями об устройстве. 
  3. Канал DSP проверяет сведения о формате данных, чтобы убедиться, что он может обрабатывать созданный поток. 
  4. Канал DSP создает объект ACXSTREAM для представления потока. 
  5. Канал DSP выделяет структуру частного контекста и связывает ее с ACXSTREAM. 
  6. Канал DSP возвращает поток выполнения в платформу ACX, которая затем вызывает следующий канал в путь к конечной точке, канал CODEC. 
  7. Канал CODEC проверяет сведения о формате данных, чтобы убедиться, что он может обрабатывать отрисовку данных. 
  8. Канал CODEC выделяет структуру частного контекста и связывает ее с ACXSTREAM. 
  9. Канал CODEC добавляет себя в приемник потока в ACXSTREAM.
  10. Канал CODEC возвращает поток выполнения в платформу ACX, которая затем вызывает следующий канал в конечной аудиопуть, канал AMP. 
  11. Канал AMP выделяет структуру частного контекста и связывает ее с ACXSTREAM. 
  12. Канал AMP возвращает поток выполнения в платформу ACX. На этом этапе создание потока завершено. 

Большие потоки буфера

Большие потоки буферов создаются в ACXPIN, назначенном для разгрузки элементом ACXCIRCUIT ACXAUDIOENGINE.

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

  1. Создайте объекты ACXPIN host, Offload и Loopback ACXPIN и добавьте их в ACXCIRCUIT.
  2. Создание элементов ACXVOLUME, ACXMUTE и ACXPEAKMETER. Они не будут добавлены непосредственно в ACXCIRCUIT.
  3. Инициализирует структуру ACX_AUDIOENGINE_CONFIG, назначая объекты HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement и PeakMeterElement.
  4. Создайте элемент ACXAUDIOENGINE.

Драйверам потребуется выполнить аналогичные действия, чтобы добавить элемент ACXSTREAMAUDIOENGINE при создании потока на закреплении разгрузки.

Потоковая передача выделения ресурсов

Модель потоковой передачи для ACX основана на пакетах с поддержкой одного или двух пакетов для потока. Для канала потоковой передачи предоставляется запрос на выделение пакетов памяти, используемых в потоке, или запись ACXPIN. Для поддержки повторного балансировки выделенная память должна быть системной памятью, а не памятью устройства, сопоставленной с системой. Драйвер может использовать существующие функции WDF для выполнения выделения и возвратить массив указателей на выделение буфера. Если драйверу требуется один непрерывный блок, он может выделить оба пакета в виде одного буфера, возвращая указатель на смещение буфера в качестве второго пакета.

Если выделен один пакет, пакет должен быть выровнен по страницам и сопоставляется дважды в пользовательском режиме:

| пакет 0 | пакет 0 |

Это позволяет GetBuffer возвращать указатель на один непрерывный буфер памяти, который может охватывать конец буфера до начала без необходимости обрабатывать оболочку доступа к памяти. 

Если выделено два пакета, они сопоставляются с пользовательским режимом:

| пакет 0 | пакет 1 |

При первоначальной потоковой передаче пакетов ACX в начале выделяется только два пакета. Сопоставление виртуальной памяти клиента остается допустимым без изменения жизни потока после выполнения выделения и сопоставления. Существует одно событие, связанное с потоком, чтобы указать завершение пакета для обоих пакетов. Также будет общий буфер, который платформа ACX будет использовать для обмена данными о том, какой пакет завершен с событием.  

Размеры пакетов больших буферных потоков

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

Так как минимальный и максимальный размер буфера могут быть переменными, драйвер может завершить вызов выделения пакетов, если минимальное и максимальное значение изменилось.

Указание ограничений буфера ACX

Чтобы указать ограничения буфера ACX, драйверы ACX могут использовать параметр свойств KS/PortCls — KSAUDIO_PACKETSIZE_CONSTRAINTS2 и структуру KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT.

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

//
// Describe buffer size constraints for WaveRT buffers
// Note: 10msec for each of the Modes is the default system behavior.
//
static struct
{
    KSAUDIO_PACKETSIZE_CONSTRAINTS2                 TransportPacketConstraints;         // 1
    KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT    AdditionalProcessingConstraints[4]; // + 4 = 5
} DspR_RtPacketSizeConstraints =
{
    {
        10 * HNSTIME_PER_MILLISECOND,                           // 10 ms minimum processing interval
        FILE_BYTE_ALIGNMENT,                                    // 1 byte packet size alignment
        0,                                                      // no maximum packet size constraint
        5,                                                      // 5 processing constraints follow
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_RAW,              // constraint for raw processing mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
    },
    {
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_DEFAULT,          // constraint for default processing mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_COMMUNICATIONS,   // constraint for movie communications mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_MEDIA,            // constraint for default media mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_MOVIE,            // constraint for movie movie mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
    }
};

Структура DSP_DEVPROPERTY используется для хранения ограничений.

typedef struct _DSP_DEVPROPERTY {
    const DEVPROPKEY   *PropertyKey;
    DEVPROPTYPE Type;
    ULONG BufferSize;
    __field_bcount_opt(BufferSize) PVOID Buffer;
} DSP_DEVPROPERTY, PDSP_DEVPROPERTY;

И создается массив этих структур.

const DSP_DEVPROPERTY DspR_InterfaceProperties[] =
{
    {
        &DEVPKEY_KsAudio_PacketSize_Constraints2,       // Key
        DEVPROP_TYPE_BINARY,                            // Type
        sizeof(DspR_RtPacketSizeConstraints),           // BufferSize
        &DspR_RtPacketSizeConstraints,                  // Buffer
    },
};

Далее в функции EvtCircuitCompositeCircuitInitialize функция AddPropertyToCircuitInterface используется для добавления массива свойств интерфейса в канал.

   // Set RT buffer constraints.
    //
    status = AddPropertyToCircuitInterface(Circuit, ARRAYSIZE(DspC_InterfaceProperties), DspC_InterfaceProperties);

Вспомогательная функция AddPropertyToCircuitInterface принимает функцию AcxCircuitGetSymbolicLinkName для канала, а затем вызывает IoGetDeviceInterfaceAlias , чтобы найти звуковой интерфейс, используемый каналом.

Затем функция SetDeviceInterfacePropertyDataMultiple вызывает функцию IoSetDeviceInterfacePropertyData, чтобы изменить текущее значение свойства интерфейса устройства — значения звуковых свойств KS в звуковом интерфейсе для ACXCIRCUIT.

PAGED_CODE_SEG
NTSTATUS AddPropertyToCircuitInterface(
    _In_ ACXCIRCUIT                                         Circuit,
    _In_ ULONG                                              PropertyCount,
    _In_reads_opt_(PropertyCount) const DSP_DEVPROPERTY   * Properties
)
{
    PAGED_CODE();

    NTSTATUS        status      = STATUS_UNSUCCESSFUL;
    UNICODE_STRING  acxLink     = {0};
    UNICODE_STRING  audioLink   = {0};
    WDFSTRING       wdfLink     = AcxCircuitGetSymbolicLinkName(Circuit);
    bool            freeStr     = false;

    // Get the underline unicode string.
    WdfStringGetUnicodeString(wdfLink, &acxLink);

    // Make sure there is a string.
    if (!acxLink.Length || !acxLink.Buffer)
    {
        status = STATUS_INVALID_DEVICE_STATE;
        DrvLogError(g_BthLeVDspLog, FLAG_INIT,
            L"AcxCircuitGetSymbolicLinkName failed, Circuit: %p, %!STATUS!",
            Circuit, status);
        goto exit;
    }

    // Get the audio interface.
    status = IoGetDeviceInterfaceAlias(&acxLink, &KSCATEGORY_AUDIO, &audioLink);
    if (!NT_SUCCESS(status))
    {
        DrvLogError(g_BthLeVDspLog, FLAG_INIT,
            L"IoGetDeviceInterfaceAlias failed, Circuit: %p, symbolic link name: %wZ, %!STATUS!",
            Circuit, &acxLink, status);
        goto exit;
    }

    freeStr = true;

    // Set specified properties on the audio interface for the ACXCIRCUIT.
    status = SetDeviceInterfacePropertyDataMultiple(&audioLink, PropertyCount, Properties);
    if (!NT_SUCCESS(status))
    {
        DrvLogError(g_BthLeVDspLog, FLAG_INIT,
            L"SetDeviceInterfacePropertyDataMultiple failed, Circuit: %p, symbolic link name: %wZ, %!STATUS!",
            Circuit, &audioLink, status);
        goto exit;
    }

    status = STATUS_SUCCESS;

exit:

    if (freeStr)
    {
        RtlFreeUnicodeString(&audioLink);
        freeStr = false;
    }

    return status;
}

Изменения состояния потока

При изменении состояния потока каждый объект потока в конечной точке аудиопуть для потока получит событие уведомления из платформы ACX. Порядок, в котором это происходит, зависит от изменения состояния и потока потока потока.

  • Для потоков отрисовки от менее активного состояния к более активному состоянию канал потоковой передачи (который зарегистрировал ПРИЕМНИК) будет получать событие первым. После обработки события будет получен следующий канал в путь к конечной точке.

  • Для потоков отрисовки, поступающих из более активного состояния в менее активное состояние, канал потоковой передачи получит событие последним. 

  • Для потоков записи, поступающих из менее активного состояния в более активное состояние, канал потоковой передачи получит событие последним. 

  • Для потоков записи, поступающих из более активного состояния в менее активное состояние, канал потоковой передачи получит событие первым. 

Приведенное выше упорядочение — это значение по умолчанию, предоставляемое платформой ACX. Драйвер может запрашивать противоположное поведение, задав acxStreamBridgeInvertChangeStateSequence (определенный в ACX_STREAM_BRIDGE_CONFIG_FLAGS) при создании ACXSTREAMBRIDGE, добавляемого драйвером в канал потоковой передачи.  

Потоковая передача звуковых данных

После создания потока и выделения соответствующих буферов поток находится в состоянии приостановки ожидания запуска потока. Когда клиент помещает поток в состояние воспроизведения, платформа ACX вызовет все объекты ACXSTREAM, связанные с потоком, чтобы указать, что состояние потока находится в Play. Затем ACXPIN будет помещен в состояние воспроизведения, в котором данные начнут передаваться. 

Отрисовка звуковых данных

После создания потока и выделения ресурсов приложение вызовет Start в потоке, чтобы начать воспроизведение. Обратите внимание, что приложение должно вызывать GetBuffer/ReleaseBuffer перед запуском потока, чтобы убедиться, что первый пакет, который начнет воспроизводиться немедленно, будет иметь допустимые звуковые данные. 

Клиент начинается с предварительного переката буфера. Когда клиент вызывает ReleaseBuffer, это приведет к вызову в AudioKSE, который будет вызываться на уровне ACX, который будет вызывать EvtAcxStreamSetRenderPacket в активном ACXSTREAM. Свойство будет включать индекс пакета (на основе 0) и, если это возможно, флаг EOS со смещением байтов конца потока в текущем пакете.    После завершения потоковой передачи с пакетом он активирует уведомление с полным буфером, которое освобождает клиентов, ожидающих заполнения следующего пакета с отрисовкой звуковых данных. 

Режим потоковой передачи с управляемым таймером поддерживается и указывается с помощью значения PacketCount 1 при вызове обратного вызова EvtAcxStreamAllocateRtPackets драйвера.

Запись звуковых данных

После создания потока и выделения ресурсов приложение вызовет Start в потоке, чтобы начать воспроизведение. 

При запуске потока исходный канал заполняет пакет записи звуковыми данными. После заполнения первого пакета исходный канал освобождает пакет в платформу ACX. На этом этапе платформа ACX сигнализирует о событии уведомления потока. 

После сигнала уведомления о потоке клиент может отправить KSPROPERTY_RTAUDIO_GETREADPACKET , чтобы получить индекс (на основе 0) пакета, завершив запись. Когда клиент отправил GETCAPTUREPACKET, драйвер может предположить, что все предыдущие пакеты обработаны и доступны для заполнения. 

Для записи с ускорением исходный канал может освободить новый пакет в платформу ACX сразу после вызова GETREADPACKET.

Клиент также может использовать KSPROPERTY_RTAUDIO_PACKETVREGISTER для получения указателя на структуру RTAUDIO_PACKETVREGISTER для потока. Эта структура будет обновлена платформой ACX перед завершением передачи сигнала пакета.

Устаревшее поведение потоковой передачи ядра KS

Могут возникнуть ситуации, например, когда драйвер реализует захват всплеска (как в реализации точечных элементов ключевого слова), где поведение обработки устаревших потоковых пакетов ядра необходимо использовать вместо PacketVRegister. Чтобы использовать предыдущее поведение на основе пакетов, драйвер должен вернуть STATUS_NOT_SUPPORTED для KSPROPERTY_RTAUDIO_PACKETVREGISTER.

В следующем примере показано, как это сделать в acxStreamInitAssignAcxRequestPreprocessCallback для ACXSTREAM. Дополнительные сведения см. в статье AcxStreamDispatchAcxRequest.

Circuit_EvtStreamRequestPreprocess(
    _In_  ACXOBJECT  Object,
    _In_  ACXCONTEXT DriverContext,
    _In_  WDFREQUEST Request)
{
    ACX_REQUEST_PARAMETERS params;
    PCIRCUIT_STREAM_CONTEXT streamCtx;

    streamCtx = GetCircuitStreamContext(Object);
    // The driver would define the pin type to track which pin is the keyword pin.
    // The driver would add this to the driver-defined context when the stream is created.
    // The driver would use AcxStreamInitAssignAcxRequestPreprocessCallback to set
    // the Circuit_EvtStreamRequestPreprocess callback for the stream.
    if (streamCtx && streamCtx->PinType == CapturePinTypeKeyword)
    {
        if (IsEqualGUID(params.Parameters.Property.Set, KSPROPSETID_RtAudio) &&
            params.Parameters.Property.Id == KSPROPERTY_RTAUDIO_PACKETVREGISTER)
        {
            status = STATUS_NOT_SUPPORTED;
            outDataCb = 0;

            WdfRequestCompleteWithInformation(Request, status, outDataCb);
            return;
        }
    }

    (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request);
}

Положение потока

Платформа ACX вызовет обратный вызов EvtAcxStreamGetPresentationPosition , чтобы получить текущую позицию потока. Текущая позиция потока будет включать PlayOffset и WriteOffset. 

Модель потоковой передачи WaveRT позволяет звуковому драйверу предоставлять клиенту регистр положения HW. Модель потоковой передачи ACX не поддерживает предоставление регистров HW, так как они препятствуют перебалансировки. 

Каждый раз, когда канал потоковой передачи завершает пакет, он вызывает AcxRtStreamNotifyPacketComplete с 0-м индексом пакетов и значением QPC, принятым как можно ближе к завершению пакета (например, значение QPC можно вычислить подпрограммой службы прерываний). Эта информация доступна клиентам через KSPROPERTY_RTAUDIO_PACKETVREGISTER, которая возвращает указатель на структуру, содержащую CompletedPacketCount, CompletedPacketQPC, и значение, объединяющее два (что позволяет клиенту гарантировать, что CompletedPacketCount и CompletedPacketQPC находятся в одном пакете).  

Переходы состояния потока

После создания потока ACX передаст поток в разные состояния с помощью следующих обратных вызовов:

  • EvtAcxStreamPrepareHardware передаст поток из состояния AcxStreamStateStop в состояние AcxStreamStatePause. Драйвер должен зарезервировать необходимое оборудование, например подсистемы DMA при получении EvtAcxStreamPrepareHardware.
  • EvtAcxStreamRun передаст поток из состояния AcxStreamStatePause в состояние AcxStreamStateRun.
  • EvtAcxStreamPause передаст поток из состояния AcxStreamStateRun в состояние AcxStreamStatePause.
  • EvtAcxStreamReleaseHardware передаст поток из состояния AcxStreamStatePause в состояние AcxStreamStateStop. Драйвер должен освободить необходимое оборудование, например подсистемы DMA при получении EvtAcxStreamReleaseHardware.

Поток может получить обратный вызов EvtAcxStreamPrepareHardware после получения обратного вызова EvtAcxStreamReleaseHardware. Это передаст поток обратно в состояние AcxStreamStatePause.

Выделение пакетов с помощью EvtAcxStreamAllocateRtPackets обычно происходит до первого вызова EvtAcxStreamPrepareHardware. Выделенные пакеты обычно освобождаются с помощью EvtAcxStreamFreeRtPackets после последнего вызова EvtAcxStreamReleaseHardware. Это упорядочение не гарантируется.

Состояние AcxStreamStateAcquire не используется. ACX удаляет необходимость получения состояния получения драйвера, так как это состояние неявно с оборудованием подготовки (EvtAcxStreamPrepareHardware) и выпуском аппаратных вызовов (EvtAcxStreamReleaseHardware).

Поддержка больших буферных потоков и подсистемы разгрузки

ACX использует элемент ACXAUDIOENGINE для обозначения ACXPIN, который будет обрабатывать создание потока разгрузки и различные элементы, необходимые для разгрузки тома потока, отключения и состояния пикового измерения. Это аналогично существующему узлу звукового модуля в драйверах WaveRT.

Поток закрытия процесса

Когда клиент закрывает поток, драйвер получит EvtAcxStreamPause и EvtAcxStreamReleaseHardware перед удалением объекта ACXSTREAM платформой ACX Framework. Драйвер может предоставить стандартную запись WDF EvtCleanupCallback в структуре WDF_OBJECT_ATTRIBUTES при вызове AcxStreamCreate для выполнения окончательной очистки для ACXSTREAM. WDF вызывает EvtCleanupCallback, когда платформа пытается удалить объект. Не используйте EvtDeskallback, который вызывается только после выпуска всех ссылок на объект, который является неопределенным.

Драйвер должен очистить ресурсы системной памяти, связанные с объектом ACXSTREAM в EvtCleanupCallback, если ресурсы еще не были удалены в EvtAcxStreamReleaseHardware.

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

Состояние AcxStreamStateAcquire не используется. ACX удаляет необходимость получения состояния получения драйвера, так как это состояние неявно с оборудованием подготовки (EvtAcxStreamPrepareHardware) и выпуском аппаратных вызовов (EvtAcxStreamReleaseHardware).

Удаление и недопустимое удаление потоковой передачи

Если драйвер определяет, что поток стал недействительным (например, джек не отключается), канал завершит работу всех потоков. 

Очистка памяти потоковой передачи

Удаление ресурсов потока можно сделать в очистке контекста потока драйвера (а не уничтожено). Никогда не помещайте в контекст объекта все, что совместно используется в контексте объекта, уничтожать обратный вызов. Это руководство относится ко всем объектам ACX.

Обратный вызов уничтожения вызывается после того, как последний ссылочный объект исчезает, когда он неизвестен.

Как правило, обратный вызов очистки потока вызывается при закрытии дескриптора. Одно из исключений заключается в том, что драйвер создал поток в обратном вызове. Если acX не удалось добавить этот поток в его поток-мост незадолго до возвращения из операции создания потока, поток отменяется асинхронный, а текущий поток возвращает ошибку клиенту create-stream. На данный момент поток не должен выделять выделения mem. Дополнительные сведения см. в разделе EVT_ACX_STREAM_RELEASE_HARDWARE обратном вызове.

Потоковая очистка памяти

Буфер потока — это системный ресурс, и он должен быть выпущен только в том случае, если клиент пользовательского режима закрывает дескриптор потока. Буфер (который отличается от аппаратных ресурсов устройства) имеет то же время существования, что и дескриптор потока. Когда клиент закрывает дескриптор ACX, вызывает обратный вызов очистки объектов потока, а затем обратный вызов удаления потока obj, когда ссылка на объект переходит к нулю.

Для ACX можно отложить удаление obj stream на рабочий элемент, когда драйвер создал stream-obj, а затем завершился сбоем обратного вызова create-stream. Чтобы предотвратить взаимоблокировку с потоком WDF завершения работы, ACX отложит удаление в другой поток. Чтобы избежать возможных побочных эффектов этого поведения (отложенный выпуск ресурсов), драйвер может освободить выделенные ресурсы потока, прежде чем он возвращает ошибку из потока создания.

Драйвер должен освободить звуковые буферы, когда ACX вызывает обратный вызов EVT_ACX_STREAM_FREE_RTPACKETS. Этот обратный вызов вызывается, когда пользователь закрывает дескриптор потока.

Так как буферы RT сопоставляются в пользовательском режиме, время существования буфера совпадает со временем существования дескриптора. Драйвер не должен пытаться освободить или освободить звуковые буферы, прежде чем ACX вызывает этот обратный вызов.

EVT_ACX_STREAM_FREE_RTPACKETS обратный вызов должен вызываться после EVT_ACX_STREAM_RELEASE_HARDWARE обратного вызова и завершения до EvtDeviceReleaseHardware.

Этот обратный вызов может произойти после того, как драйвер обработал аппаратный обратный вызов WDF, так как клиент пользовательского режима может держаться на его дескрипторах в течение длительного времени. Драйвер не должен пытаться ждать, пока эти дескрипторы уходят, это просто создаст 0x9f DRIVER_POWER_STATE_FAILURE проверку ошибок. Дополнительные сведения см . в EVT_WDF_DEVICE_RELEASE_HARDWARE функции обратного вызова.

В этом коде EvtDeviceReleaseHardware из примера драйвера ACX показан пример вызова AcxDeviceRemoveCircuit и последующего выпуска потоковой памяти h/w.

    RETURN_NTSTATUS_IF_FAILED(AcxDeviceRemoveCircuit(Device, devCtx->Render));
    RETURN_NTSTATUS_IF_FAILED(AcxDeviceRemoveCircuit(Device, devCtx->Capture));

    // NOTE: Release streaming h/w resources here.

    CSaveData::DestroyWorkItems();
    CWaveReader::DestroyWorkItems();

Сводка:

Оборудование выпуска устройства wdf —> ресурсы h/w устройства выпуска

AcxStreamFreeRtPackets —> выпуск/бесплатный звуковой буфер, связанный с дескриптором

Дополнительные сведения об управлении объектами WDF и канала см. в статье ACX WDF Driver Lifetime Management.

DDIs потоковой передачи

Структуры потоковой передачи

структура ACX_RTPACKET

Эта структура представляет один выделенный пакет. PacketBuffer может быть дескриптором WDFMEMORY, MDL или буфером. Она имеет связанную функцию инициализации, ACX_RTPACKET_INIT.

ACX_STREAM_CALLBACKS

Эта структура определяет обратные вызовы драйвера для потоковой передачи в платформу ACX. Эта структура является частью ACX_PIN_CONFIG структуры.

Обратные вызовы потоковой передачи

EvtAcxStreamAllocateRtPackets

Событие EvtAcxStreamAllocateRtPackets сообщает драйверу выделить RtPackets для потоковой передачи. AcxRtStream получит PacketCount = 2 для потоковой передачи на основе событий или PacketCount = 1 для потоковой передачи на основе таймера. Если драйвер использует один буфер для обоих пакетов, второй rtPacketBuffer должен иметь WDF_MEMORY_DESCRIPTOR с Типом = WdfMemoryDescriptorTypeInvalid с rtPacketOffset, который соответствует концу первого пакета (пакет[2]. RtPacketOffset = packet[1]. RtPacketOffset+packet[1]. RtPacketSize).

EvtAcxStreamFreeRtPackets

Событие EvtAcxStreamFreeRtPackets сообщает драйверу, чтобы освободить RtPackets, выделенные в предыдущем вызове EvtAcxStreamAllocateRtPackets. Те же пакеты из этого вызова включены.

EvtAcxStreamGetHwLatency

Событие EvtAcxStreamGetHwLatency сообщает драйверу обеспечить задержку потока для конкретного канала данного потока (общая задержка будет суммой задержки различных каналов). FifoSize находится в байтах, и задержка находится в 100-наносекундах единиц.

EvtAcxStreamSetRenderPacket

Событие EvtAcxStreamSetRenderPacket сообщает драйверу, какой пакет был только что выпущен клиентом. Если сбои отсутствуют, этот пакет должен быть (CurrentRenderPacket + 1), где CurrentRenderPacket является пакетом, из которого драйвер в настоящее время выполняет потоковую передачу.

Флаги могут быть 0 или KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM = 0x200, указывая, что пакет является последним пакетом в потоке, и EosPacketLength является допустимой длиной в байтах для пакета. Дополнительные сведения см. в разделе OptionsFlags в структуре KSSTREAM_HEADER (ks.h).

Драйвер должен продолжать увеличивать CurrentRenderPacket, так как пакеты отрисовываются вместо изменения currentRenderPacket в соответствии с этим значением.

EvtAcxStreamGetCurrentPacket

EvtAcxStreamGetCurrentPacket сообщает драйверу, чтобы указать, какой пакет (на основе 0) в настоящее время отрисовывается на оборудование или в настоящее время заполняется оборудованием записи.

EvtAcxStreamGetCapturePacket

EvtAcxStreamGetCapturePacket сообщает драйверу, чтобы указать, какой пакет (0) был полностью заполнен в последнее время, включая значение QPC в момент начала заполнения пакета драйвером.

EvtAcxStreamGetPresentationPosition

EvtAcxStreamGetPresentationPosition сообщает драйверу указывать текущую позицию вместе со значением QPC во время вычисления текущей позиции.

СОБЫТИЯ СОСТОЯНИЯ ПОТОКОВОЙ ПЕРЕДАЧИ

Состояние потоковой передачи для ACXSTREAM управляется следующими API.

EVT_ACX_STREAM_PREPARE_HARDWARE

EVT_ACX_STREAM_RELEASE_HARDWARE

EVT_ACX_STREAM_RUN

EVT_ACX_STREAM_PAUSE

API потоковой передачи ACX

AcxStreamCreate

AcxStreamCreate создает поток ACX, который можно использовать для управления поведением потоковой передачи.

AcxRtStreamCreate

AcxRtStreamCreate создает поток ACX, который можно использовать для управления поведением потоковой передачи и обработки выделения пакетов и обмена данными о состоянии потоковой передачи.

AcxRtStreamNotifyPacketComplete

Драйвер вызывает этот API ACX при завершении пакета. Время завершения пакета и индекс пакета на основе 0 включены для повышения производительности клиента. Платформа ACX устанавливает любые события уведомлений, связанные с потоком.

См. также

Общие сведения о расширениях аудиоклассов ACX

Справочная документация по ACX

Сводка объектов ACX