Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье объясняется потоковая передача и буферизация ACX, которые критически важны для работы с звуком без сбоев. В нем описывается, как драйвер взаимодействует с состоянием потока и управляет буфером потока. Список распространенных терминов звука ACX и введение в ACX см. в обзоре расширений аудиоклассов ACX.
Типы потоковой передачи ACX
AcxStream представляет аудиопоток на оборудовании определенного канала. AcxStream может агрегировать один или несколько объектов, похожих на AcxElements.
ACX поддерживает два типа потоков. Первый тип потока, RT Packet Stream, позволяет выделять RT пакеты и использовать их для передачи звуковых данных в оборудование устройства и обратно, а также включает управление состоянием потока. Второй тип потока, базовый поток, поддерживает только переходы состояния потока.
В одной конечной точке канала канал — это канал потоковой передачи, который создает поток пакетов RT. Если два или более каналов подключаются к созданию конечной точки, первый канал в конечной точке является каналом потоковой передачи и создает поток пакетов RT. Подключенные каналы создают базовые потоки для получения событий, связанных с переходами состояния потока.
Дополнительные сведения см. в разделе "Поток ACX " в сводке по объектам ACX. DDIs для потоков определяются в заголовке acxstreams.h .
Стек потоковой передачи данных ACX
Существует два типа обмена данными для потоковой передачи ACX. Один путь связи управляет поведением потоковой передачи. Например, такие команды, как "Запуск," "Создание" и "Распределение," которые используют стандартные связи ACX. Платформа ACX использует очереди ввода-вывода и передает с их помощью запросы WDF. Поведение очереди скрыто от фактического кода драйвера с помощью обратных вызовов событий и функций ACX. Драйверу также предоставляется возможность предварительно обработать все запросы WDF.
Второй и более интересный путь связи обрабатывает сигнал потоковой передачи звука. Сигнал включает в себя указание драйверу, когда пакет готов и получает данные, и когда драйвер завершает обработку пакета.
Основные требования для потоковой передачи сигналов:
- Поддержка воспроизведения без сбоев
- Низкая задержка
- Все необходимые блокировки ограничены потоком, о котором идет речь.
- Простота использования для разработчика драйверов
Чтобы сообщить драйверу о состоянии потоковой передачи, ACX использует события с общим буфером и прямые вызовы IRP. Эти методы описаны далее.
Общий буфер
Общий буфер и событие передаются от драйвера клиенту. Событие и общий буфер гарантируют, что клиенту не нужно ждать или опрашивать. Клиент может определить все, что необходимо для продолжения потоковой передачи, уменьшая или устраняя необходимость прямых вызовов IRP.
Драйвер устройства использует общий буфер для передачи клиенту информации о том, какой пакет в данный момент отрисовывается или захватывается. Этот общий буфер включает количество пакетов (на основе одного) последнего завершенного пакета вместе со значением QPC (QueryPerformanceCounter) времени завершения. Для драйвера устройства необходимо указать эти сведения путем вызова AcxRtStreamNotifyPacketComplete. Когда драйвер устройства вызывает AcxRtStreamNotifyPacketComplete, платформа ACX обновляет общий буфер с новым числом пакетов и QPC и сигнализирует о событии, которое предоставляется клиенту, чтобы указать, что клиент может считывать новое число пакетов.
Прямые вызовы IRP
Прямые вызовы IRP передаются от клиента к драйверу.
Клиент может запросить текущее число пакетов или указать текущее число пакетов драйверу устройства в любое время. Эти запросы вызывают обработчики событий драйвера устройства EvtAcxStreamGetCurrentPacket и EvtAcxStreamSetRenderPacket. Клиент также может запросить текущий пакет захватывания, который вызывает обработчик событий драйвера устройства EvtAcxStreamGetCapturePacket.
Сходство с PortCls
Сочетание прямых вызовов IRP и общего буфера, используемого ACX, аналогично тому, как PortCls управляет завершением буфера.
Чтобы предотвратить глитч, драйверы должны гарантировать, что они не выполняют действий, требующих доступа к блокировкам, которые также используются в каналах управления потоками.
Поддержка большого буфера для воспроизведения с низкой мощностью
Чтобы уменьшить потребление энергии во время воспроизведения, уменьшите время, которое APU тратит в состоянии высокой мощности. Так как обычное воспроизведение звука использует 10 мс буферов, APU остается активным. Драйверы ACX могут объявлять поддержку больших буферов в диапазоне от 1 до 2 секунд, чтобы разрешить APU входить в более низкое состояние питания.
В существующих моделях потоковой передачи поддержка отсроченного воспроизведения обеспечивает воспроизведение с низким энергопотреблением. Аудиодрайвер указывает на поддержку упрощенной обработки воспроизведения, предоставляя узел AudioEngine в волновом фильтре для конечной точки. Узел AudioEngine предоставляет средства управления движком DSP, используемым драйвером для воспроизведения звука из больших буферов с требуемой обработкой.
Узел AudioEngine предоставляет следующие функции:
- Описание аудиодвижка сообщает аудиостеку, какие штыри на волновом фильтре обеспечивают поддержку разгрузки и обратного цикла (а также поддержку воспроизведения хоста).
- Диапазон размеров буфера сообщает звуковому стеку о минимальных и максимальных размерах буфера, которые можно поддерживать для разгрузки аудио. воспроизведение. Диапазон размера буфера может динамически изменяться на основе системного действия.
- Поддержка формата, включая поддерживаемые форматы, текущий формат сочетания устройств и формат устройства.
- Том, включая поддержку расширения, так как с большим объемом буферов программный том не будет реагировать.
- Loopback Protection, который сообщает драйверу отключить пин-код AudioEngine Loopback, если один или несколько отключенных потоков содержит защищенное содержимое.
- Глобальное состояние FX, чтобы включить или отключить GFX в AudioEngine.
При создании потока на пин-коде разгрузки поток поддерживает том, локальную защиту FX и защиту от циклов.
Низкое энергопотребление при воспроизведении с ACX
Платформа ACX использует ту же модель для воспроизведения с низкой мощностью. Драйвер создает три отдельных объекта ACXPIN для потоковой передачи хоста, разгрузки и обратной петли, а также элемент ACXAUDIOENGINE, описывающий, какие из этих штырей используются для хоста, разгрузки и обратной петли. Драйвер добавляет выводы и элемент ACXAUDIOENGINE в ACXCIRCUIT во время создания схемы.
Создание внезагрузного потока
Драйвер также добавляет элемент ACXAUDIOENGINE в потоки, созданные для разгрузки, чтобы обеспечить возможность управления громкостью, выключением звука и пиковым индикатором.
Схема потоковой передачи
На этой схеме показан драйвер ACX с несколькими стеками.
Каждый драйвер ACX управляет отдельной частью звукового оборудования, который может поступать от другого поставщика. ACX предоставляет совместимый интерфейс потоковой передачи ядра, чтобы приложения выполнялись без изменений.
Потоковые закрепления
Каждый ACXCIRCUIT имеет по крайней мере один приемный контакт и один исходный контакт. Эти пин-коды используются платформой ACX для предоставления подключений канала к стеку звука. Для канала отрисовки исходный пин-код используется для управления поведением отрисовки любого потока, созданного из канала. Для цепи захвата разъем потребителя используется для управления поведением захвата любого потока, создаваемого цепью.
ACXPIN — это объект, используемый для управления потоковой передачей в звуковом пути. Поток ACXCIRCUIT отвечает за создание соответствующих объектов ACXPIN для аудиопути конечной точки во время создания цепи и регистрации ACXPIN в 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. После того как все схемы создадут объект потока, объекты потока обрабатывают логику потоковой передачи.
Запрос на создание потока отправляется на соответствующий PIN, сгенерированный как часть генерации топологии головной схемы, вызывая функцию 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 может обрабатывать обнаружение джека или другие сведения о оборудовании конечной точки.
- AudioKSE вызывает NtCreateFile для создания потока.
- Это проходит через ACX и заканчивается вызовом EvtAcxPinCreateStream схемы DSP с пином, форматом данных (включая режим) и сведениями об устройстве.
- Канал DSP проверяет сведения о формате данных, чтобы убедиться, что он может обрабатывать созданный поток.
- Канал DSP создает объект ACXSTREAM для представления потока.
- Канал DSP выделяет структуру частного контекста и связывает ее с ACXSTREAM.
- Схема DSP возвращает поток выполнения во фреймворк ACX, который затем вызывает следующую схему в аудиотракте оконечной точки, схему CODEC.
- Цепь CODEC проверяет информацию о формате данных, чтобы подтвердить, что она может обрабатывать визуализацию данных.
- Канал CODEC выделяет структуру частного контекста и связывает ее с ACXSTREAM.
- Контур CODEC добавляет себя как приемник потока для ACXSTREAM.
- Канал CODEC возвращает последовательность выполнения в платформу ACX, которая затем передает управление следующему каналу в конечном звуковом тракте, каналу AMP.
- Канал AMP выделяет структуру частного контекста и связывает ее с ACXSTREAM.
- Канал AMP возвращает поток выполнения в платформу ACX. На этом этапе создание потока завершено.
Большие потоки буфера
Большие потоки буферов создаются в ACXPIN, назначенном для разгрузки элементом ACXCIRCUIT ACXAUDIOENGINE.
Чтобы поддерживать потоки разгрузки, драйвер устройства должен выполнить следующие действия во время создания потоковой схемы:
- Создайте объекты ACXPIN Host, Offload и Loopback и добавьте их в ACXCIRCUIT.
- Создание элементов ACXVOLUME, ACXMUTE и ACXPEAKMETER. Они не будут добавлены непосредственно в ACXCIRCUIT.
- Инициализирует структуру ACX_AUDIOENGINE_CONFIG, назначая объекты HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement и PeakMeterElement.
- Создайте элемент ACXAUDIOENGINE.
Драйверы должны выполнять аналогичные действия, чтобы добавить элемент ACXSTREAMAUDIOENGINE при создании потока на разгрузочном пине.
Распределение ресурсов
Модель потоковой передачи для ACX основана на пакетах с поддержкой одного или двух пакетов для потока. Для оператора рендеринга или захвата ACXPIN в потоковой схеме подаётся запрос на выделение пакетов памяти, используемых в потоке. Для поддержки повторного балансировки выделенная память должна быть системной памятью, а не памятью устройства, сопоставленной с системой. Драйвер может использовать существующие функции WDF для выполнения выделения и возвращать массив указателей на выделенные буферы. Если драйверу требуется один непрерывный блок, он может выделить оба пакета в виде одного буфера. Второй пакет имеет WdfMemoryDescriptorTypeInvalid, и смещение второго пакета находится в буфере, котором описан первый пакет.
Если выделяется один пакет, драйвер должен выделить буфер, выровненный по границе страницы, с длиной, кратной размеру страницы. Смещение для одного пакета также должно быть 0. Платформа ACX дважды подряд сопоставляет этот пакет в пользовательском режиме.
| пакет 0 | пакет 0 |
Это позволяет GetBuffer возвращать указатель на один непрерывный буфер памяти, который может охватывать конец буфера до начала, не требуя от приложения обрабатывать оболочку доступа к памяти.
Если выделено два пакета, они сопоставляются с пользовательским режимом:
| пакет 0 | пакет 1 |
При первоначальной потоковой передаче пакетов ACX в начале выделяется только два пакета. После произведения выделения и сопоставления, отображение виртуальной памяти клиента остается актуальным и не меняется на протяжении всего времени жизни потока. Событие, связанное с потоком, указывает на завершение обоих пакетов. Также существует общий буфер, который фреймворк ACX использует для передачи информации о пакете, завершившемся в связке с событием.
При PacketCount=1, если приложение запрашивает 10 мс данных, звуковой стек отправляет запрос на один 10-мс буфер драйверу (он не удваивает размер буфера, отправляемого драйверу).
Драйвер выделяет выровненный по страницам буфер, длина которого составляет не менее 10 мс. Для потока с параметрами 48k 2ch 2 байта на выборку, наименьший буфер, управляемый таймером, который можно выделить, составляет 1024 выборок (одна страница памяти), что соответствует 21,333 мс. Для потока с параметрами 48k, 8 каналов и 2 байта на образец наименьший буфер на основе таймера, который можно выделить, составляет 512 выборок (одна страница памяти) или 10.667 мс. Для потока с параметрами 48k 6ch и 2 байта на выборку наименьший управляемый таймером буфер по-прежнему содержит 1,024 образца (три страницы памяти, чтобы конец образца совпадал с концом буфера), что составляет 21,333 мс.
Фреймворк ACX дважды подряд отображает этот буфер, выровненный по странице, в процессе в пользовательском режиме. Затем процесс в пользовательском режиме может записывать данные в объемах до размера буфера в сопоставление пользовательского режима, начиная с любого места в буфере, без необходимости выполнения обертывания.
Драйвер вызывает NotifyPacketComplete после того, как он считывает весь пакет из системной памяти, поэтому система знает, что может записать следующий пакет звуковых данных в буфер пакета.
Существует задержка между NotifyPacketComplete и моментом, когда воспроизводится последний семпл этого пакета. Эта задержка представлена как результат EvtAcxStreamGetHwLatency.
Буферы ping-pong
Буферы ping-pong можно использовать, где один буфер считывается (ping), а другой заполняется (pong). Это позволяет обрабатывать один буфер, а другой собирает следующий набор данных. В ACX драйвер внутренне заботится о переключении при заполнении буфера. После заполнения буфера ping происходит уведомление через зарегистрированный обратный вызов. В обратном вызове получается адрес буфера для обработки, и буфер повторно отправляется. Между тем буфер pong собирает данные в фоновом режиме. Этот механизм обеспечивает непрерывную обработку данных без прерываний.
Для буфера ping-pong запрошенный размер пакета предназначен для одного буфера (ping или pong), а число пакетов — два.
При совместном использовании одного буфера между двумя пакетами настройте второй пакет, как описано в функции обратного вызова EVT_ACX_STREAM_ALLOCATE_RTPACKETS. Часть буфера, описанного первым пакетом (память, смещение и длина), является буфером ping, а часть, описанная вторым пакетом (без памяти, указывающая, что буфер совместно используется с первым пакетом, а также смещение, указывающее на буфер сразу после первого пакета) является буфером pong.
Добавление дополнительных сведений в заголовок пакета
В заголовок пакета можно добавить дополнительные сведения, например, для логирования или учета, в начале пакета для потоков, управляемых событиями ping/pong (где число пакетов = 2). Для потоков, управляемых таймером, которые содержат только один пакет, пакет должен быть полностью выровнен относительно границ страницы (начиная и заканчивая на границе страницы), так как он сопоставляется в пользовательском режиме дважды.
В этом случае приложение может записать за пределы первого сопоставления во второе сопоставление, что приводит к записи в конец системного буфера, а затем в начало того же системного буфера.
Выделенный буфер должен быть выровнен по границе страниц, так как сопоставление виртуальной памяти в пользовательском режиме происходит на уровне страниц.
Буферы, управляемые таймером
Буферы в ACX, управляемые таймером, можно использовать для гарантии безглючного аудио за счёт поддержания точной синхронизации и тайминга. Для буферов, управляемых таймером, в ACX:
- Клиент использует значение из EvtAcxStreamGetPresentationPosition, чтобы определить, сколько кадров можно записать.
- Позиция презентации должна обновляться несколько раз за один проход через буфер. Клиент записывает данные в буфер, начиная с позиции, на которой он записал в последний раз, до позиции, о которой сообщает драйвер (что должно соответствовать данным, потребленным оборудованием с момента последнего запроса позиции).
- Чем более детализирована позиция, тем меньше вероятность того, что вы испытываете сбой.
- В буферах, управляемых таймером, DSP не может просто использовать весь буфер перед обновлением позиции.
- В режиме таймерного управления драйвер может при необходимости разделить один буфер, управляемый таймером, на несколько буферов DSP, обновляя положение по мере работы DSP с каждым буфером (например, буфер, управляемый таймером 20 мс, разделенный на 10 буферов по 2 мс, будет вести себя достаточно хорошо в режиме таймерного управления).
Размеры пакетов в больших потоках буферов данных
При предоставлении поддержки больших буферов драйвер также предоставит коллбек, используемый для определения минимальных и максимальных размеров пакетов для воспроизведения больших буферов.
Размер пакета для выделения буфера потока определяется на основе минимального и максимального размера.
Так как минимальный и максимальный размер буфера могут быть переменными, драйвер может завершить вызов выделения пакетов, если есть изменения в минимальных и максимальных размерах буфера.
Указание ограничений буфера 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. Порядок, в котором это происходит, зависит от изменения состояния и направления потока.
Для потоков рендеринга, когда они переходят из менее активного состояния в более активное, контур потоковой передачи (который зарегистрировал SINK) получает событие первым. Когда канал обрабатывает событие, следующий канал в путь к конечной точке получает событие.
Для визуализации потоков, переходящих из более активного состояния в менее активное, потоковая схема получает событие последней.
Для потоков записи, поступающих от менее активного состояния к более активному состоянию, канал потоковой передачи получает событие последним.
Для потоков записи, поступающих из более активного состояния в менее активное состояние, канал потоковой передачи получает событие первым.
Порядок по умолчанию задается фреймворком ACX. Драйвер может запрашивать противоположное поведение, задав AcxStreamBridgeInvertChangeStateSequence (определённый в ACX_STREAM_BRIDGE_CONFIG_FLAGS) при создании ACXSTREAMBRIDGE, который драйвер добавляет в канал потоковой передачи.
Потоковая передача звуковых данных
После создания потока и выделения соответствующих буферов поток находится в состоянии Приостановки и ожидает запуска потока. Когда клиент переводит поток в состояние 'Play', платформа ACX вызывает все объекты ACXSTREAM, связанные с этим потоком, для указания состояния 'Play' потока. Затем ACXPIN переводится в состояние воспроизведения, и данные начинают передаваться.
Обработка звуковых данных
После создания потока и выделения ресурсов приложение вызывает Start в потоке, чтобы начать воспроизведение. Приложение должно вызвать GetBuffer/ReleaseBuffer перед запуском потока, чтобы убедиться, что первый пакет, который запускает воспроизведение, имеет допустимые звуковые данные.
Клиент начинает с того, что выполняет прероллинг буфера. Когда клиент вызывает ReleaseBuffer, это переводится в вызов в AudioKSE, который обращается к слою ACX и вызывает EvtAcxStreamSetRenderPacket на активном ACXSTREAM. Свойство включает индекс пакета (от нуля) и, при необходимости, флаг EOS с указанием смещения байта для конца потока в текущем пакете.
После того как стриминговая схема завершает работу с пакетом, она активирует уведомление о завершении заполнения буфера, которое освобождает клиентов, ожидающих возможности заполнить следующий пакет данными для воспроизведения звука.
Режим потоковой передачи с управляемым таймером поддерживается и указывается с помощью значения PacketCount 1 при вызове обратного вызова EvtAcxStreamAllocateRtPackets драйвера.
Запись звуковых данных
При запуске потока исходный канал заполняет пакет записи звуковыми данными. После заполнения первого пакета исходный канал освобождает пакет в платформу ACX. На этом этапе фреймворк ACX сигнализирует о событии уведомления потока.
После сигнала уведомления о потоке клиент может отправить KSPROPERTY_RTAUDIO_GETREADPACKET, чтобы получить индекс (от нуля) пакета, завершившего захват. Когда клиент отправляет 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 с отсчитываемым от нуля индексом пакетов и значением 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. Драйвер может предоставить стандартную запись WDF EvtCleanupCallback в структуре WDF_OBJECT_ATTRIBUTES при вызове AcxStreamCreate для выполнения окончательной очистки для ACXSTREAM. WDF вызывает EvtCleanupCallback, когда платформа пытается удалить объект. Не используйте EvtDestroyCallback, который вызывается только после освобождения всех ссылок на объект, время чего не определено.
Драйвер должен очистить ресурсы системной памяти, связанные с объектом ACXSTREAM в EvtCleanupCallback, если ресурсы еще не очищаются в EvtAcxStreamReleaseHardware.
Драйвер не должен очищать ресурсы, поддерживающие поток, пока клиент не запрашивает его.
Состояние AcxStreamStateAcquire не используется. ACX устраняет необходимость для драйвера иметь состояние получения, поскольку это состояние подразумевается вызовами обратных функций подготовки оборудования (EvtAcxStreamPrepareHardware) и выпуска оборудования (EvtAcxStreamReleaseHardware).
Удаление потока внезапным образом и аннулирование
Если драйвер определяет, что поток недопустим (например, джек отключен), цепь завершает работу всех потоков.
Очистка памяти потоковой передачи
Удаление ресурсов потока можно сделать в очистке контекста потока драйвера (а не уничтожено). Не помещайте в контекст объекта удаление каких-либо общих элементов в контексте объекта, чтобы уничтожить обратный вызов. Это руководство относится ко всем объектам ACX.
Обратный вызов уничтожения вызывается после удаления последней ссылки, которая является неопределенной.
Как правило, обратный вызов очистки потока вызывается при закрытии дескриптора. Когда драйвер создает поток в обратном вызове, это одно из исключений. Если ACX не удается добавить этот поток в его stream bridge до завершения операции создания потока, поток отменяется асинхронно, а текущий поток возвращает ошибку клиенту, создающему поток. В этом моменте поток не должен иметь выделения памяти. Дополнительные сведения см. в EVT_ACX_STREAM_RELEASE_HARDWARE обратном вызове.
Последовательность очистки памяти потоковой передачи
Буфер потока — это системный ресурс, и его следует освободить только в том случае, если клиент пользовательского режима закрывает дескриптор потока. Буфер (который отличается от аппаратных ресурсов устройства) имеет то же время существования, что и дескриптор потока. Когда клиент закрывает дескриптор, ACX вызывает обратный вызов очистки объекта потока, а затем обратный вызов удаления объекта потока, когда счетчик ссылок объекта достигает нуля.
Для ACX можно отложить удаление объекта потока через рабочий элемент, если драйвер создал объект потока, а затем произошел сбой обратного вызова 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, а затем освобождения памяти, используемой для потоковой передачи оборудования.
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: освобождение аппаратных ресурсов устройства.
- 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, соответствующим концу первого пакета (packet[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 по мере отрисовки пакетов, вместо изменения его значения, чтобы соответствовать этому.
EvtAcxStreamGetCurrentPacket
EvtAcxStreamGetCurrentPacket сообщает драйверу, какой пакет (отсчитывается от нуля) в настоящее время воспроизводится на оборудование или заполняется оборудованием захвата.
EvtAcxStreamGetCapturePacket
EvtAcxStreamGetCapturePacket сообщает драйверу предоставить информацию, какой пакет (с нуля) был заполнен последним, включая значение QPC в момент начала заполнения пакета драйвером.
EvtAcxStreamGetPresentationPosition
EvtAcxStreamGetPresentationPosition сообщает драйверу указывать текущую позицию вместе со значением QPC во время вычисления текущей позиции.
СОБЫТИЯ СОСТОЯНИЯ ПОТОКА
Следующие API управляют состоянием потоковой передачи для ACXSTREAM.
- 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 при завершении пакета. Время завершения пакета и отсчитываемый от нуля индекс пакетов включаются для повышения производительности клиента. Платформа ACX задает все события уведомлений, связанные с потоком.
См. также
- Общие сведения о расширениях аудиоклассов ACX
- Справочная документация по ACX
- Сводка объектов ACX