Compartilhar via


Streaming do ACX

Este tópico discute o streaming do ACX e o buffer associado, que é fundamental para uma experiência de áudio sem falhas. Ele descreve os mecanismos usados pelo driver para se comunicar sobre o estado do fluxo e gerenciar o buffer para o fluxo. Para obter uma lista de termos de áudio do ACX comuns e uma introdução ao ACX, consulte Visão geral de extensões de classe de áudio ACX.

Tipos de streaming do ACX

Um AcxStream representa um fluxo de áudio no hardware de um circuito específico. Um AcxStream pode agregar um ou mais objetos do tipo AcxElements.

A estrutura do ACX oferece suporte a dois tipos de fluxo. O primeiro tipo de fluxo, o Fluxo de Pacotes RT, fornece suporte para alocar pacotes RT e usar pacotes RT para transferir dados de áudio de ou para o hardware do dispositivo, juntamente com transições de estado de fluxo. O segundo tipo de fluxo, o Fluxo Básico, fornece suporte apenas para transições de estado de fluxo.

Em um ponto de extremidade de circuito único, o circuito deve ser um circuito de streaming que cria um Fluxo de Pacotes RT. Se dois ou mais circuitos estiverem conectados para criar um ponto de extremidade, o primeiro circuito no ponto de extremidade será o circuito de streaming e criará um Fluxo de Pacotes RT; os circuitos conectados criarão Fluxos Básicos para receber eventos relacionados a transições de estado de fluxo.

Para obter mais informações, consulte Fluxo do ACX no Resumo de objetos ACX. As DDIs para fluxo são definidas no cabeçalho acxstreams.h.

Pilha de comunicações de streaming ACX

Existem dois tipos de comunicações para o Streaming do ACX. Um caminho de comunicação é usado para controlar o comportamento de streaming, para comandos como Iniciar, Criar e Alocar, que usarão as comunicações ACX padrão. A estrutura ACX usa filas de E/S e transmite Solicitações WDF usando as filas. O comportamento da fila é oculto do código do driver real por meio do uso de retornos de chamada Evt e funções ACX, embora o driver também possa pré-processar todas as Solicitações WDF.

O segundo e mais interessante caminho de comunicação é usado para a sinalização de streaming de áudio. Isso envolve dizer ao driver quando um pacote está pronto e receber dados sobre quando o driver terminou de processar um pacote. 

Os principais requisitos para a sinalização de streaming:

  • Suporte a reprodução sem falhas
    • Baixa Latência
    • Quaisquer bloqueios necessários são limitados ao fluxo em questão
  • Facilidade de uso para desenvolvedor de drivers

Para se comunicar com o driver para sinalizar o estado de streaming, o ACX usa eventos com um buffer compartilhado e chamadas IRP diretas. Estes são descritos a seguir.

Buffer compartilhado

Para comunicação a partir do driver para o cliente, são usados um buffer compartilhado e um evento. Isso garante que o cliente não precise esperar ou iniciar um poll e que o cliente possa determinar tudo o que precisa para continuar transmitindo, reduzindo ou eliminando a necessidade de chamadas IRP diretas.

O driver de dispositivo usa um buffer compartilhado para comunicar ao cliente qual pacote está sendo renderizado ou capturado. Esse buffer compartilhado inclui a contagem de pacotes (com base em 1) do último pacote concluído junto com o valor QPC (QueryPerformanceCounter) do tempo de conclusão. Para o driver de dispositivo, ele deve indicar essas informações chamando AcxRtStreamNotifyPacketComplete. Quando o driver de dispositivo chama AcxRtStreamNotifyPacketComplete, a estrutura ACX atualiza o buffer compartilhado com a nova contagem de pacotes e QPC e sinaliza um evento compartilhado com o cliente para indicar que ele pode ler a nova contagem de pacotes.

Direcionar chamadas IRP

Para comunicação a partir do cliente para o driver, são usadas chamadas IRP diretas. Isso reduz as complexidades para garantir que as solicitações do WDF sejam tratadas em tempo hábil e comprovadamente funcionem bem na arquitetura existente.

O cliente pode, a qualquer momento, solicitar ou indicar a contagem de pacotes atual para o driver de dispositivo. Essas solicitações chamarão os manipuladores de eventos de driver de dispositivo EvtAcxStreamGetCurrentPacket e EvtAcxStreamSetRenderPacket. O cliente também pode solicitar o pacote de captura atual que chamará o manipulador de eventos do driver de dispositivo EvtAcxStreamGetCapturePacket.

Semelhanças com PortCls

A combinação de chamadas IRP diretas e buffer compartilhado usada pelo ACX é semelhante à forma como o tratamento de conclusão de buffer é comunicado em PortCls. Os IRPs são muito semelhantes e o buffer compartilhado permite ao driver comunicar diretamente a contagem de pacotes e o tempo, sem depender de IRPs.   Os drivers precisarão garantir que não haja nenhuma ação que exija acesso a bloqueios também usados nos caminhos de controle de fluxo – isso é necessário para evitar falhas. 

Suporte a buffer grande para reprodução de baixo consumo de energia

Para reduzir a quantidade de energia consumida ao reproduzir conteúdo de mídia, é importante reduzir a quantidade de tempo que a APU gasta em um estado de alta energia. Como a reprodução de áudio normal usa buffers de 10 ms, a APU sempre precisa estar ativa. Para dar à APU o tempo necessário para reduzir o estado, os drivers do ACX podem anunciar suporte para buffers significativamente maiores, na faixa de tamanho de 1 a 2 segundos. Isso significa que a APU pode reativar uma vez a cada 1 ou 2 segundos, fazer as operações necessárias na velocidade máxima para preparar o próximo buffer de 1 a 2 segundos e, em seguida, ir para o estado de energia mais baixo possível até que o próximo buffer seja necessário. 

Nos modelos de streaming existentes, a reprodução de baixo consumo de energia é suportada por meio da Reprodução de Descarregamento. Um driver de áudio anuncia o suporte para Reprodução de Descarregamento expondo um nó AudioEngine no filtro de onda para um ponto de extremidade. O nó AudioEngine fornece um meio de controlar o mecanismo DSP que o driver usa para renderizar o áudio dos buffers grandes com o processamento desejado.

O nó AudioEngine fornece estes recursos:

  • Descrição do Mecanismo de Áudio, que informa à pilha de áudio quais pinos no filtro de onda fornecem suporte a descarregamento e loopback (e suporte à reprodução do host). 
  • Intervalo de Tamanho do Buffer, que informa à pilha de áudio os tamanhos mínimo e máximo de buffer que podem ser suportados para descarregamento. reprodução. O Intervalo de Tamanho do Buffer pode ser alterado dinamicamente com base na atividade do sistema. 
  • Suporte a formatos, incluindo formatos suportados, o formato de combinação de dispositivos atual e o formato de dispositivo. 
  • Volume, incluindo suporte para incremento, já que com os buffers maiores o volume do software não será responsivo.
  • Proteção de loopback, que instrui o driver a silenciar o pino de loopback do AudioEngine se um ou mais fluxos descarregados contiverem conteúdo protegido. 
  • Estado FX global, para ativar ou desativar o GFX no AudioEngine. 

Quando um fluxo é criado no Pino de Descarregamento, o fluxo oferece suporte a Volume, FX Local e Proteção de Loopback. 

Reprodução de baixo consumo de energia com ACX

A estrutura do ACX usa o mesmo modelo para reprodução de baixo consumo de energia. O driver cria três objetos ACXPIN separados para streaming de host, descarregamento e loopback, juntamente com um elemento ACXAUDIOENGINE que descreve quais desses pinos são usados para host, descarregamento e loopback. O driver adiciona os pinos e o elemento ACXAUDIOENGINE ao ACXCIRCUIT, durante a criação do circuito.

Criação de fluxo de descarregamento

O driver também adicionará um elemento ACXAUDIOENGINE aos fluxos criados para descarregamento para permitir o controle sobre volume, som e medidor de pico.

Diagrama de streaming

Este diagrama mostra um driver ACX de pilha múltipla.

Diagrama ilustrando caixas DSP, CODEC e AMP com uma interface de streaming de kernel na parte superior.

Cada driver do ACX controla uma parte separada do hardware de áudio e pode ser fornecido por um fornecedor diferente. O ACX fornece uma interface de streaming de kernel compatível para permitir que os aplicativos sejam executados como estão.

Pinos de fluxo

Cada ACXCIRCUIT tem pelo menos um Pino de Coletor e um Pino de Origem. Esses Pinos são usados pela estrutura do ACX para expor as conexões do circuito à pilha de áudio. Para um circuito de Renderização, o Pino de Origem é usado para controlar o comportamento de renderização de qualquer fluxo criado a partir do circuito. Para um circuito de Captura, o Pino de Coleta é usado para controlar o comportamento de captura de qualquer fluxo criado a partir do circuito.   ACXPIN é o objeto usado para controlar o streaming no Caminho de Áudio. O ACXCIRCUIT de streaming é responsável por criar o(s) objeto(s) ACXPIN apropriado(s) para o Caminho de Áudio do Ponto de Extremidade no momento da criação do circuito e registrar os ACXPINs com ACX. O ACXCIRCUIT só precisa criar o pino de renderização ou captura ou pinos para o Circuito; a estrutura ACX criará o outro pino necessário para se conectar e se comunicar com o circuito.   

Circuito de streaming

Quando um ponto de extremidade é composto por um único circuito, esse circuito é o circuito de streaming.

Quando um ponto de extremidade é composto por mais de um circuito criado por um ou mais drivers de dispositivo, os circuitos são conectados na ordem específica determinada pelo ACXCOMPOSITETEMPLATE que descreve o ponto de extremidade composto. O primeiro circuito no ponto de extremidade é o circuito de streaming para o ponto de extremidade.

O circuito de streaming deve usar AcxRtStreamCreate para criar um Stream de Pacotes RT em resposta a EvtAcxCircuitCreateStream. O ACXSTREAM criado com AcxRtStreamCreate permitirá que o driver do circuito de streaming aloque o buffer usado para streaming e controle o fluxo de streaming em resposta às necessidades do cliente e do hardware.

Os circuitos a seguir no ponto de extremidade devem usar AcxStreamCreate para criar um Fluxo Básico em resposta a EvtAcxCircuitCreateStream. Os objetos ACXSTREAM criados com AcxStreamCreate pelos circuitos a seguir permitirão que os drivers configurem o hardware em resposta a alterações de estado de fluxo, como Pause ou Run.

O streaming ACXCIRCUIT é o primeiro circuito a receber as solicitações para criar um fluxo. A solicitação inclui o dispositivo, o pino e o formato de dados (incluindo o modo).

Cada ACXCIRCUIT no Caminho de Áudio criará um objeto ACXSTREAM que representa a instância de fluxo do circuito. A estrutura do ACX liga os objetos ACXSTREAM juntos (da mesma forma que os objetos ACXCIRCUIT estão vinculados). 

Circuitos a montante e a jusante

A criação do fluxo começa no circuito de streaming e é encaminhada para cada circuito downstream na ordem em que os circuitos são conectados. As conexões são feitas entre pinos de ponte criados com Comunicação igual a AcxPinCommunicationNone. A estrutura ACX criará um ou mais pinos de ponte para um circuito se o driver não adicioná-los no momento da criação do circuito.

Para cada circuito que começa com o circuito de streaming, o pino da ponte AcxPinTypeSource se conectará ao próximo circuito downstream. O circuito final terá um pino de ponto de extremidade descrevendo o hardware de ponto de extremidade de áudio (como se o ponto de extremidade é um microfone ou alto-falante e se o conector está conectado).

Para cada circuito após o circuito de streaming, o pino da ponte AcxPinTypeSink se conectará ao próximo circuito upstream.

Negociação de formato de fluxo

O driver anuncia os formatos suportados para a criação de fluxo adicionando os formatos suportados por modo ao ACXPIN usado para a criação de fluxo com AcxPinAssignModeDataFormatList e AcxPinGetRawDataFormatList. Para pontos de extremidade de vários circuitos, um ACXSTREAMBRIDGE pode ser usado para coordenar o modo e formatar o suporte entre circuitos ACX. Os formatos de fluxo suportados para o ponto de extremidade são determinados pelos ACXPINs de streaming criados pelo circuito de streaming. Os formatos usados pelos circuitos a seguir são determinados pelo pino da ponte do circuito anterior no ponto final.

Por padrão, a estrutura ACX criará uma ACXSTREAMBRIDGE entre cada circuito em um ponto de extremidade de vários circuitos. O ACXSTREAMBRIDGE padrão usará o formato padrão do pino de ponte do modo RAW do circuito upstream ao encaminhar a solicitação de criação de fluxo para o circuito downstream. Se o pino da ponte do circuito upstream não tiver formatos, o formato de fluxo original será usado. Se o pino conectado do circuito downstream não suportar o formato que está sendo usado, a criação do fluxo falhará.

Se um circuito de dispositivo estiver executando uma alteração de formato de fluxo, o driver de dispositivo deverá adicionar o formato downstream ao pino da ponte downstream.  

Criação de fluxo

A primeira etapa na Criação de Stream é criar a instância ACXSTREAM para cada ACXCIRCUIT no Caminho de Áudio do Ponto de Extremidade. O ACX chamará EvtAcxCircuitCreateStream de cada circuito. O ACX começará com o circuito principal e chamará o EvtAcxCircuitCreateStream de cada circuito em ordem, terminando com o circuito de cauda. A ordem pode ser invertida especificando o sinalizador AcxStreamBridgeInvertChangeStateSequence (definido em ACX_STREAM_BRIDGE_CONFIG_FLAGS) para a Ponte de Stream. Depois que todos os circuitos tiverem criado um objeto de fluxo, os objetos de fluxo manipularão a lógica de streaming.

A Solicitação de Criação de Stream é enviada para o PIN apropriado gerado como parte da geração da topologia do circuito principal chamando o EvtAcxCircuitCreateStream especificado durante a criação do circuito principal. 

O circuito de streaming é o circuito upstream que inicialmente lida com a solicitação de criação de fluxo.

  • Ele atualiza a estrutura ACXSTREAM_INIT, atribuindo AcxStreamCallbacks e AcxRtStreamCallbacks
  • Ele cria o objeto ACXSTREAM usando AcxRtStreamCreate
  • Ele cria quaisquer elementos específicos de fluxo (por exemplo, ACXVOLUME ou ACXAUDIOENGINE)
  • Ele adiciona os elementos ao objeto ACXSTREAM
  • Ele retorna o objeto ACXSTREAM que foi criado para a estrutura do ACX

Em seguida, o ACX encaminha a criação do fluxo para o próximo circuito downstream

  • Ele atualiza a estrutura ACXSTREAM_INIT, atribuindo AcxStreamCallbacks
  • Ele cria o objeto ACXSTREAM usando AcxStreamCreate
  • Ele cria quaisquer elementos específicos do stream
  • Ele adiciona os elementos ao objeto ACXSTREAM
  • Ele retorna o objeto ACXSTREAM que foi criado para a estrutura do ACX

O canal de comunicação entre circuitos em um caminho de áudio usa objetos ACXTARGETSTREAM. Neste exemplo, cada circuito terá acesso a uma Fila de E/S para o circuito à sua frente e o circuito atrás dele no Caminho de Áudio do Ponto de Extremidade. Além disso, um Caminho de Áudio do Ponto de Extremidade é linear e bidirecional. A manipulação real da Fila de E/S é executada pela estrutura ACX.    Ao criar o objeto ACXSTREAM, cada circuito pode adicionar informações de contexto ao objeto ACXSTREAM para armazenar e rastrear dados privados para o fluxo.

Exemplo de fluxo de renderização

Criando um fluxo de renderização em um Caminho de Áudio de Ponto de Extremidade composto por três circuitos: DSP, CODEC e AMP. O circuito DSP funciona como o circuito de streaming e forneceu um manipulador EvtAcxPinCreateStream. O circuito DSP também funciona como um circuito de filtro: dependendo do modo de fluxo e configuração, ele pode aplicar processamento de sinal aos dados de áudio. O circuito CODEC representa o DAC, fornecendo a funcionalidade do coletor de áudio. O circuito AMP representa o hardware analógico entre o DAC e o alto-falante. O circuito AMP pode lidar com a detecção de tomada ou outros detalhes de hardware de ponto de extremidade.

  1. AudioKSE chama NtCreateFile para criar um fluxo.
  2. Isso filtra o ACX e termina chamando o EvtAcxPinCreateStream do circuito DSP com o pino, o formato de dados (incluindo o modo) e as informações do dispositivo. 
  3. O circuito DSP valida as informações de formato de dados para garantir que ele possa manipular o fluxo criado. 
  4. O circuito DSP cria o objeto ACXSTREAM para representar o fluxo. 
  5. O circuito DSP aloca uma estrutura de contexto privado e a associa ao ACXSTREAM. 
  6. O circuito DSP retorna o fluxo de execução para a estrutura do ACX, que então chama o próximo circuito no Caminho de Áudio do Ponto de Extremidade, o circuito CODEC. 
  7. O circuito CODEC valida as informações de formato de dados para confirmar que ele pode lidar com a renderização dos dados. 
  8. O circuito CODEC aloca uma estrutura de contexto privado e a associa ao ACXSTREAM. 
  9. O circuito CODEC é adicionado como um coletor de fluxo ao ACXSTREAM.
  10. O CODEC retorna o fluxo de execução para a estrutura do ACX, que então chama o próximo circuito no Caminho de Áudio do Ponto de Extremidade, o circuito AMP. 
  11. O circuito AMP aloca uma estrutura de contexto privado e a associa ao ACXSTREAM. 
  12. O circuito AMP retorna o fluxo de execução para a estrutura ACX. Neste ponto, a criação do fluxo está concluída. 

Grandes fluxos de buffer

Grandes fluxos de buffer são criados no ACXPIN designado para Descarregamento pelo elemento ACXAUDIOENGINE do ACXCIRCUIT.

Para oferecer suporte a fluxos de descarregamento, o driver de dispositivo deve fazer o seguinte durante a criação do circuito de streaming:

  1. Crie os objetos ACXPIN Host, Offload e Loopback e adicione-os ao ACXCIRCUIT.
  2. Crie elementos ACXVOLUME, ACXMUTE e ACXPEAKMETER. Estes não serão adicionados diretamente ao ACXCIRCUIT.
  3. Inicialize uma estrutura ACX_AUDIOENGINE_CONFIG, atribuindo os objetos HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement e PeakMeterElement.
  4. Crie o elemento ACXAUDIOENGINE.

Os drivers precisarão executar etapas semelhantes para adicionar um elemento ACXSTREAMAUDIOENGINE ao criar um stream no pino Descarregamento.

Alocação de recursos do fluxo

O modelo de streaming para ACX é baseado em pacotes, com suporte para um ou dois pacotes para um fluxo. O ACXPIN Render ou Capture para o circuito de streaming recebe uma solicitação para alocar os pacotes de memória que são usados no fluxo. Para oferecer suporte ao Rebalanceamento, a memória alocada deve ser memória do sistema, e não a memória do dispositivo mapeada para o sistema. O driver pode usar funções WDF existentes para executar a alocação e retornará uma matriz de ponteiros para as alocações de buffer. Se o driver exigir um único bloco contíguo, ele poderá alocar ambos os pacotes como um único buffer, retornando um ponteiro para um deslocamento do buffer como o segundo pacote.

Se um único pacote for alocado, o pacote deverá ser alinhado à página e mapeado duas vezes no modo de usuário:

| pacote 0 | pacote 0 |

Isso permite que GetBuffer retorne um ponteiro para um único buffer de memória contíguo que pode se estender do final do buffer até o início sem exigir que o aplicativo manipule o encapsulamento do acesso à memória. 

Se dois pacotes forem alocados, eles serão mapeados para o modo de usuário:

| pacote 0 | pacote 1 |

Com o streaming inicial de pacotes ACX, há apenas dois pacotes alocados no início. O mapeamento de memória virtual do cliente permanecerá válido sem alterar durante a vida útil do fluxo depois que a alocação e o mapeamento tiverem sido executados. Há um evento associado ao fluxo para indicar a conclusão do pacote para ambos os pacotes. Também haverá um buffer compartilhado que a estrutura ACX usará para comunicar qual pacote terminou com o evento.  

Grandes tamanhos de pacotes de fluxo de buffer

Ao expor o suporte para buffers grandes, o driver também fornecerá um retorno de chamada que é usado para determinar os tamanhos mínimo e máximo de pacotes para reprodução de buffer grande.   O tamanho do pacote para alocação de buffer de fluxo é determinado com base no mínimo e no máximo.

Como os tamanhos mínimo e máximo do buffer podem ser voláteis, o driver pode falhar na chamada de alocação de pacotes se o mínimo e o máximo tiverem sido alterados.

Especificação de restrições de buffer ACX

Para especificar restrições de buffer do ACX, os drivers ACX podem usar a configuração de propriedades KS/PortCls – KSAUDIO_PACKETSIZE_CONSTRAINTS2 e a estrutura KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT.

O exemplo de código a seguir mostra como definir restrições de tamanho de buffer para buffers WaveRT para diferentes modos de processamento de sinal.

//
// 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
        },
    }
};

Uma estrutura DSP_DEVPROPERTY é usada para armazenar as restrições.

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

E uma matriz dessas estruturas é criada.

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

Mais tarde na função EvtCircuitCompositeCircuitInitialize, a função auxiliar AddPropertyToCircuitInterface é usada para adicionar a matriz de propriedades de interface ao circuito.

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

A função auxiliar AddPropertyToCircuitInterface usa o AcxCircuitGetSymbolicLinkName para o circuito e, em seguida, chama IoGetDeviceInterfaceAlias para localizar a interface de áudio usada pelo circuito.

Em seguida, a função SetDeviceInterfacePropertyDataMultiple chama a função IoSetDeviceInterfacePropertyData para modificar o valor atual da propriedade da interface do dispositivo – os valores da propriedade de áudio KS na interface de áudio do 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;
}

Alterações de estado do fluxo

Quando ocorre uma alteração de estado de fluxo, cada objeto de fluxo no Caminho de Áudio de Ponto de Ponto de Extremidade para o fluxo receberá um evento de notificação da estrutura ACX. A ordem em que isso acontece depende da mudança de estado e do fluxo do fluxo.

  • Para fluxos de Renderização que passam de um estado menos ativo para um estado mais ativo, o circuito de streaming (que registrou o SINK) receberá o evento primeiro. Depois de manipular o evento, o próximo circuito no Caminho de Áudio do Ponto de Extremidade receberá o evento.

  • Para fluxos de Renderização que passam de um estado mais ativo para um estado menos ativo, o circuito de streaming receberá o evento por último. 

  • Para fluxos de Captura que passam de um estado menos ativo para um estado mais ativo, o circuito de streaming receberá o evento por último. 

  • Para fluxos de Captura que passam de um estado mais ativo para um estado menos ativo, o circuito de streaming receberá o evento primeiro. 

A ordem acima é o padrão fornecido pela estrutura ACX. Um driver pode solicitar o comportamento oposto definindo AcxStreamBridgeInvertChangeStateSequence (definido em ACX_STREAM_BRIDGE_CONFIG_FLAGS) ao criar o ACXSTREAMBRIDGE que o driver adiciona ao circuito de streaming.  

Streaming de dados de áudio

Depois que o fluxo é criado e os buffers apropriados são alocados, o fluxo está no estado Pause aguardando o início do fluxo. Quando o cliente coloca o fluxo no estado Play, a estrutura do ACX chamará todos os objetos ACXSTREAM associados ao fluxo para indicar que o estado do fluxo está em Play. O ACXPIN será então colocado no estado Play, momento em que os dados começarão a fluir. 

Renderização de dados de áudio

Depois que o fluxo for criado e os recursos forem alocados, o aplicativo chamará Start no fluxo para iniciar a reprodução. Observe que um aplicativo deve chamar GetBuffer/ReleaseBuffer antes de iniciar o fluxo para garantir que o primeiro pacote que começará a ser reproduzido imediatamente terá dados de áudio válidos. 

O cliente começa pré-rolando um buffer. Quando o cliente chama ReleaseBuffer, isso se converte em uma chamada no AudioKSE que chamará a camada ACX, que chamará EvtAcxStreamSetRenderPacket no ACXSTREAM ativo. A propriedade incluirá o índice de pacotes (baseado em 0) e, se apropriado, um sinalizador EOS com o deslocamento de bytes do final do fluxo no pacote atual.    Depois que o circuito de streaming terminar com um pacote, ele acionará a notificação de buffer-complete que liberará os clientes que aguardam para preencher o próximo pacote com dados de áudio de renderização. 

O modo de streaming controlado por Timer é suportado e é indicado usando um valor PacketCount de 1 ao chamar o retorno de chamada EvtAcxStreamAllocateRtPackets do driver.

Capturando dados de áudio

Depois que o fluxo for criado e os recursos forem alocados, o aplicativo chamará Start no fluxo para iniciar a reprodução. 

Quando o fluxo está em execução, o circuito de origem preenche o pacote de captura com dados de áudio. Uma vez que o primeiro pacote é preenchido, o circuito de origem libera o pacote para a estrutura ACX. Neste ponto, a estrutura ACX sinaliza o evento de notificação de fluxo. 

Depois que a notificação de fluxo for sinalizada, o cliente poderá enviar KSPROPERTY_RTAUDIO_GETREADPACKET para obter o índice (baseado em 0) do pacote que concluiu a captura. Quando o cliente enviou GETCAPTUREPACKET, o driver pode assumir que todos os pacotes anteriores foram processados e estão disponíveis para preenchimento. 

Para captura Burst, o circuito de origem poderá liberar um novo pacote para a estrutura ACX assim que GETREADPACKET for chamado.

O cliente também pode usar KSPROPERTY_RTAUDIO_PACKETVREGISTER a fim de obter um ponteiro para a estrutura RTAUDIO_PACKETVREGISTER do fluxo. Essa estrutura será atualizada pela estrutura ACX antes de sinalizar o pacote completo.

Comportamento de streaming do kernel KS herdado

Pode haver situações, como quando um driver implementa a captura de intermitência (como em uma implementação de spotter de palavra-chave), em que o comportamento de manipulação de pacotes de streaming do kernel herdado precisa ser usado em vez do PacketVRegister. Para usar o comportamento baseado em pacote anterior, o driver deve retornar STATUS_NOT_SUPPORTED para KSPROPERTY_RTAUDIO_PACKETVREGISTER.

O exemplo a seguir mostra como fazer isso no AcxStreamInitAssignAcxRequestPreprocessCallback para um ACXSTREAM. Para obter mais informações, veja 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);
}

Posição do fluxo

A estrutura ACX chamará o retorno de chamada EvtAcxStreamGetPresentationPosition para obter a posição de fluxo atual. A posição de fluxo atual incluirá o PlayOffset e o WriteOffset. 

O modelo de streaming WaveRT permite que o driver de áudio exponha um registro de posição HW ao cliente. O modelo de streaming ACX não suportará a exposição de nenhum registro de HW, pois isso impediria que um rebalanceamento acontecesse. 

Cada vez que o circuito de streaming conclui um pacote, ele chama AcxRtStreamNotifyPacketComplete com o índice de pacote baseado em 0 e o valor QPC tomado o mais próximo possível da conclusão do pacote (por exemplo, o valor QPC pode ser calculado pela Rotina de Serviço de Interrupção). Essas informações estão disponíveis para os clientes por meio do KSPROPERTY_RTAUDIO_PACKETVREGISTER, que retorna um ponteiro para uma estrutura que contém o CompletedPacketCount, o CompletedPacketQPC e um valor que combina os dois (o que permite ao cliente garantir que o CompletedPacketCount e o CompletedPacketQPC sejam do mesmo pacote).  

Transições de estado de fluxo

Depois que um fluxo tiver sido criado, o ACX fará a transição do fluxo para diferentes estados usando os seguintes retornos de chamada:

  • EvtAcxStreamPrepareHardware fará a transição do fluxo do estado AcxStreamStateStop para o estado AcxStreamStatePause. O driver deverá reservar o hardware necessário, como DMA Engines, quando receber EvtAcxStreamPrepareHardware.
  • EvtAcxStreamRun fará a transição do fluxo do estado AcxStreamStatePause para o AcxStreamStateRun.
  • EvtAcxStreamPause fará a transição do fluxo do estado AcxStreamStateRun para o AcxStreamStatePause.
  • EvtAcxStreamReleaseHardware fará a transição do fluxo do estado AcxStreamStatePause para o AcxStreamStateStop. O driver deverá liberar o hardware necessário, como mecanismos DMA, quando receber EvtAcxStreamReleaseHardware.

O fluxo pode receber o retorno de chamada EvtAcxStreamPrepareHardware depois do EvtAcxStreamReleaseHardware. Isso fará a transição do fluxo de volta para o estado AcxStreamStatePause.

A alocação de pacotes com EvtAcxStreamAllocateRtPackets normalmente acontecerá antes da primeira chamada para EvtAcxStreamPrepareHardware. Os pacotes alocados normalmente serão liberados com EvtAcxStreamFreeRtPackets após a última chamada para EvtAcxStreamReleaseHardware. Essa ordenação não é garantida.

O estado AcxStreamStateAcquire não é usado. O ACX elimina a necessidade de o driver ter o estado de aquisição, pois esse estado está implícito com os retornos de chamada de hardware de preparação (EvtAcxStreamPrepareHardware) e hardware de lançamento (EvtAcxStreamReleaseHardware).

Grandes fluxos de buffer e suporte ao mecanismo de descarregamento

O ACX usa o elemento ACXAUDIOENGINE para designar um ACXPIN que manipulará a criação do fluxo de descarregamento e os diferentes elementos necessários para o volume do fluxo de descarregamento, o mudo e o estado do medidor de pico. Isso é semelhante ao nó do mecanismo de áudio existente nos drivers WaveRT.

Processo de fechamento de fluxo

Quando o cliente fecha o fluxo, o driver receberá EvtAcxStreamPause e EvtAcxStreamReleaseHardware antes que o objeto ACXSTREAM seja excluído pela estrutura do ACX. O driver pode fornecer a entrada WDF EvtCleanupCallback padrão na estrutura WDF_OBJECT_ATTRIBUTES ao chamar AcxStreamCreate para executar a limpeza final do ACXSTREAM. O WDF chamará EvtCleanupCallback quando a estrutura tentar excluir o objeto. Não use EvtDestroyCallback, que só é chamado depois que todas as referências ao objeto foram liberadas, o que é indeterminado.

O driver deve limpar os recursos de memória do sistema associados ao objeto ACXSTREAM em EvtCleanupCallback, se os recursos ainda não tiverem sido limpos em EvtAcxStreamReleaseHardware.

É importante que o driver não limpe os recursos que suportam o fluxo, até que solicitado pelo cliente.

O estado AcxStreamStateAcquire não é usado. O ACX elimina a necessidade de o driver ter o estado de aquisição, pois esse estado está implícito com os retornos de chamada de hardware de preparação (EvtAcxStreamPrepareHardware) e hardware de lançamento (EvtAcxStreamReleaseHardware).

Remoção e invalidação surpresa do fluxo

Se o driver determinar que o fluxo se tornou inválido (por exemplo, a tomada for desconectada), o circuito desligará todos os fluxos. 

Limpeza de memória de fluxo

O descarte dos recursos do fluxo pode ser feito na limpeza do contexto do fluxo do driver (não destruir). Nunca descarte recursos que sejam compartilhados no retorno de chamada de destruição do contexto de um objeto. Essa orientação se aplica a todos os objetos ACX.

O retorno de chamada de destruição é invocado depois que a última ref desaparece, quando é desconhecida.

Em geral, o retorno de chamada de limpeza do fluxo é chamado quando o identificador é fechado. Uma exceção a isso é quando o driver criou o fluxo no seu retorno de chamada. Se o ACX não conseguiu adicionar esse fluxo à ponte antes de retornar da operação de criação de fluxo, o fluxo será cancelado de forma assíncrona e o thread atual retornará um erro ao cliente create-stream. O fluxo não deve ter nenhuma alocação de memória alocada neste ponto. Para obter mais informações, consulte retorno de chamada EVT_ACX_STREAM_RELEASE_HARDWARE.

Sequência de limpeza de memória de fluxo

O buffer de fluxo é um recurso do sistema e deve ser liberado somente quando o cliente de modo de usuário fecha o identificador do fluxo. O buffer (que é diferente dos recursos de hardware do dispositivo) tem o mesmo tempo de vida que o identificador do fluxo. Quando o cliente fecha o identificador, o ACX invoca o retorno de chamada de limpeza de objetos de fluxo e, em seguida, o retorno de chamada de exclusão do objeto do fluxo quando a referência no objeto vai para zero.

É possível para o ACX adiar uma exclusão do objeto STREAM para um item de trabalho quando o driver criou um stream-obj e, em seguida, falhou no retorno de chamada create-stream. Para evitar um deadlock com um thread WDF de desligamento, o ACX adia a exclusão para um thread diferente. Para evitar possíveis efeitos colaterais desse comportamento (liberação adiada de recursos), o driver pode liberar os recursos de fluxo alocados antes de retornar um erro do stream-create.

O driver deve liberar os buffers de áudio quando o ACX invoca o retorno de chamada EVT_ACX_STREAM_FREE_RTPACKETS. Esse retorno de chamada é chamado quando o usuário fecha os identificadores de fluxo.

Como os buffers RT são mapeados no modo de usuário, o tempo de vida do buffer é o mesmo que o tempo de vida do identificador. O driver não deve tentar liberar os buffers de áudio antes que o ACX chame esse retorno de chamada.

O EVT_ACX_STREAM_FREE_RTPACKETS deve ser chamado após EVT_ACX_STREAM_RELEASE_HARDWARE e terminar antes de EvtDeviceReleaseHardware.

Esse retorno de chamada pode acontecer depois que o driver processou o retorno de chamada de hardware da versão WDF, porque o cliente de modo de usuário pode manter seus identificadores por muito tempo. O driver não deve tentar esperar que esses identificadores desapareçam, isso apenas criará uma verificação de bugs 0x9f DRIVER_POWER_STATE_FAILURE. Consulte a função de retorno de chamada EVT_WDF_DEVICE_RELEASE_HARDWARE para obter mais informações.

Este código EvtDeviceReleaseHardware do driver ACX de exemplo mostra um exemplo de chamar AcxDeviceRemoveCircuit e, em seguida, liberar a memória h/w de streaming.

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

Em resumo:

Hardware de lançamento do dispositivo WDF> – Liberar recursos H/W do dispositivo

AcxStreamFreeRtPackets –> Buffer de áudio liberado/livre associado ao identificador

Para obter mais informações sobre como gerenciar objetos WDF e de circuito, consulte ACX WDF Driver Lifetime Management.

DDIs de streaming

Estruturas de streaming

Estrutura ACX_RTPACKET

Essa estrutura representa um único pacote alocado. O PacketBuffer pode ser um identificador WDFMEMORY, um MDL ou um Buffer. Ele tem uma função de inicialização associada, ACX_RTPACKET_INIT.

ACX_STREAM_CALLBACKS

Essa estrutura identifica os retornos de chamada do driver para fazer streaming para a estrutura ACX. Essa estrutura faz parte da estrutura ACX_PIN_CONFIG.

Retornos de chamada de streaming

EvtAcxStreamAllocateRtPackets

O evento EvtAcxStreamAllocateRtPackets diz ao driver para alocar RtPackets para streaming. Um AcxRtStream receberá PacketCount = 2 para streaming controlado por eventos ou PacketCount = 1 para streaming baseado em timer. Se o driver usa um único buffer para ambos os pacotes, o segundo RtPacketBuffer deve ter um WDF_MEMORY_DESCRIPTOR com Type = WdfMemoryDescriptorTypeInvalid com um RtPacketOffset que se alinha com o final do primeiro pacote (packet[2].RtPacketOffset = packet[1].RtPacketOffset+packet[1].RtPacketSize).

EvtAcxStreamFreeRtPackets

O evento EvtAcxStreamFreeRtPackets diz ao driver para liberar os RtPackets que foram alocados em uma chamada anterior para EvtAcxStreamAllocateRtPackets. Os mesmos pacotes dessa chamada estão incluídos.

EvtAcxStreamGetHwLatency

O evento EvtAcxStreamGetHwLatency diz ao driver para fornecer latência de fluxo para o circuito específico desse fluxo (a latência geral será uma soma da latência dos diferentes circuitos). O FifoSize está em bytes e o Delay está em unidades de 100 nanossegundos.

EvtAcxStreamSetRenderPacket

O evento EvtAcxStreamSetRenderPacket informa ao driver qual pacote acabou de ser liberado pelo cliente. Se não houver falhas, esse pacote deverá ser (CurrentRenderPacket + 1), em que CurrentRenderPacket é o pacote do qual o driver está transmitindo no momento.

Os sinalizadores podem ser 0 ou KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM = 0x200, indicando que o Packet é o último pacote na transmissão, e EosPacketLength é um comprimento válido em bytes para o pacote. Para obter mais informações, consulte OptionsFlags na estrutura KSSTREAM_HEADER (ks.h).

O driver deve continuar aumentando o CurrentRenderPacket conforme os pacotes são renderizados em vez de alterar seu CurrentRenderPacket para corresponder a esse valor.

EvtAcxStreamGetCurrentPacket

O EvtAcxStreamGetCurrentPacket informa ao driver para indicar qual pacote (baseado em 0) está sendo renderizado no hardware ou está sendo preenchido pelo hardware de captura.

EvtAcxStreamGetCapturePacket

O EvtAcxStreamGetCapturePacket diz ao driver para indicar qual pacote (baseado em 0) foi completamente preenchido mais recentemente, incluindo o valor QPC no momento em que o driver começou a preencher o pacote.

EvtAcxStreamGetPresentationPosition

O EvtAcxStreamGetPresentationPosition diz ao driver para indicar a posição atual junto com o valor QPC no momento em que a posição atual foi calculada.

EVENTOS DE ESTADO DE STREAM

O estado de streaming de um ACXSTREAM é gerenciado pelas seguintes APIs.

EVT_ACX_STREAM_PREPARE_HARDWARE

EVT_ACX_STREAM_RELEASE_HARDWARE

EVT_ACX_STREAM_RUN

EVT_ACX_STREAM_PAUSE

APIs de ACX de streaming

AcxStreamCreate

AcxStreamCreate cria um Stream do ACX que pode ser usado para controlar o comportamento de streaming.

AcxRtStreamCreate

AcxRtStreamCreate cria um Stream do ACX que pode ser usado para controlar o comportamento de streaming e lidar com a alocação de pacotes e comunicar o estado de streaming.

AcxRtStreamNotifyPacketComplete

O driver chama essa API do ACX quando um pacote é concluído. O tempo de conclusão do pacote e o índice de pacote baseado em 0 são incluídos para melhorar o desempenho do cliente. A estrutura do ACX definirá todos os eventos de notificação associados ao fluxo.

Confira também

Visão geral de extensões de classe de áudio ACX

Documentação de referência da ACX

Resumo de objetos ACX