Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este tópico discute o streaming ACX e o buffer associado, o 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 do fluxo. Para obter uma lista de termos de áudio ACX comuns e uma introdução ao ACX, consulte a visão geral das extensões de classe de áudio ACX.
Tipos de streaming ACX
Um AcxStream representa um fluxo de áudio no hardware de um circuito específico. Um AcxStream pode agregar um ou mais objetos semelhantes a AcxElements.
A estrutura ACX dá 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 único ponto de extremidade de circuito, 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; circuitos conectados criarão Fluxos Básicos para receber eventos relacionados a transições de estado de fluxo.
Para obter mais informações, consulte ACX Stream in Summary of ACX Objects. Os DDIs para fluxo são definidos no cabeçalho acxstreams.h .
Pilha de comunicações de streaming ACX
Há dois tipos de comunicações para o ACX Streaming. 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 IO e encaminha solicitações do WDF usando essas filas. O comportamento da fila é oculto do código de driver real por meio do uso de retornos de chamada Evt e funções ACX, embora o driver também tenha a chance de pré-processar todas as Solicitações do 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 quando o driver terminar de processar um pacote.
Os principais requisitos para a sinalização de streaming:
- Suporte para Reprodução Glitch-Free
- Baixa Latência
- Todos os bloqueios necessários são limitados ao fluxo em questão
- Facilidade de uso para desenvolvedores 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 em seguida.
Memória intermédia partilhada
Para se comunicar do driver para o cliente, um buffer compartilhado e um evento são usados. Isso garante que o cliente não precise aguardar ou sondar e que o cliente possa determinar tudo o que precisa para continuar transmitindo, reduzindo ou eliminando a necessidade de chamadas IRP diretas.
O driver do dispositivo usa um buffer compartilhado para se comunicar com o cliente para o qual o pacote está sendo renderizado ou capturado. Esse buffer compartilhado inclui a contagem de pacotes (com base em 1) do último pacote concluído, juntamente com o valor de QPC (QueryPerformanceCounter) do tempo de conclusão. Para o driver do dispositivo, ele deve indicar essas informações chamando AcxRtStreamNotifyPacketComplete. Quando o driver do dispositivo chama AcxRtStreamNotifyPacketComplete, a estrutura ACX atualizará o buffer compartilhado com a nova contagem de pacotes e QPC e sinalizará um evento compartilhado com o cliente para indicar que o cliente pode ler a nova contagem de pacotes.
Chamadas IRP diretas
Para comunicação do cliente com o driver, utilizam-se chamadas IRP diretas. Isso reduz as complexidades em torno da garantia de que as solicitações do WDF sejam tratadas em tempo hábil e tenha sido comprovadamente funcionando bem na arquitetura existente.
O cliente pode a qualquer momento solicitar a contagem de pacotes atual ou indicar a contagem de pacotes atual para o driver do 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 das chamadas IRP diretas e do buffer compartilhado usadas pelo ACX é semelhante à maneira como a conclusão do buffer é tratada e comunicada no PortCls. Os IRPs são muito semelhantes e o buffer compartilhado introduz a capacidade do driver de comunicar diretamente a contagem de pacotes e o tempo sem depender de IRPs. Os drivers precisarão garantir que não façam nada que exija acesso a bloqueios que também são usados nos caminhos de controle de fluxo– isso é necessário para evitar falhas.
Suporte a buffer grande para reprodução de baixa potência
Para reduzir a quantidade de energia consumida ao reproduzir conteúdo de mídia, é importante reduzir o tempo gasto pela APU em um estado de alta potência. Como a reprodução de áudio normal usa buffers de 10ms, a APU sempre precisa estar ativa. Para dar à APU o tempo necessário para reduzir o estado, os drivers ACX têm permissão para anunciar o suporte para buffers significativamente maiores, no intervalo de tamanho de 1 a 2 segundos. Isso significa que a APU pode acordar uma vez a cada 1 a 2 segundos, fazer as operações necessárias em 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, há suporte para reprodução de baixa potência por meio da Reprodução de Descarregamento. Um driver de áudio anuncia o suporte para a Reprodução de Descarregamento expondo um nó AudioEngine no filtro de onda para um ponto de extremidade. O nó AudioEngine oferece um meio de controlar o mecanismo DSP que o driver utiliza para renderizar o áudio a partir dos grandes buffers com o processamento desejado.
O nó AudioEngine fornece estas instalações:
- A Descrição do Mecanismo de Áudio informa à pilha de áudio quais pinos no filtro de onda fornecem suporte para descarregamento e loopback, além de suporte à reprodução do host.
- Intervalo de Tamanho do Buffer, que informa à pilha de áudio os tamanhos mínimos e máximos de buffer que podem ter suporte para descarregamento. reprodução. O Intervalo de Tamanho do Buffer pode ser alterado dinamicamente com base na atividade do sistema.
- Suporte para formatos, incluindo formatos compatíveis, o formato de combinação atual de dispositivos e o formato do dispositivo.
- Volume, incluindo suporte a rampas, pois o volume de software não será responsivo com buffers maiores.
- A Proteção de Loopback instrui o driver a silenciar o pino de loopback do AudioEngine caso um ou mais dos fluxos offloaded contenham conteúdo protegido.
- Estado fx global, para habilitar ou desabilitar o GFX no AudioEngine.
Quando um fluxo é criado no "Offload Pin", o fluxo dá suporte a Volume, Efeitos Locais e Proteção de Loopback.
Reprodução de mídia de baixa potência com ACX
A estrutura ACX usa o mesmo modelo para reprodução de baixa potência. O driver cria três objetos ACXPIN separados para host, offload e transmissão de loopback, juntamente com um elemento ACXAUDIOENGINE que descreve quais pinos são usados para host, offload e loopback. O driver adiciona os pinos e o elemento ACXAUDIOENGINE ao ACXCIRCUIT durante a criação do circuito.
Criação de fluxo descarregado
O driver também adicionará um elemento ACXAUDIOENGINE aos fluxos criados para descarregamento para permitir o controle sobre o volume, o mudo e o medidor de pico.
Diagrama de streaming
Este diagrama mostra um driver ACX de várias pilhas.
Cada driver 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 receptor e um pino emissor. Esses pinos são usados pela estrutura ACX para expor as conexões do circuito à pilha de áudio. Para um circuito de Renderização, o Pin de Origem é usado para controlar o comportamento de renderização de qualquer fluxo criado a partir do circuito. Para um circuito de captura, o Pin de Saída é usado para controlar o comportamento de captura de qualquer fluxo gerado pelo circuito. ACXPIN é o objeto usado para controlar o streaming no Caminho de Áudio. O ACXCIRCUIT de streaming é responsável por criar os objetos ACXPIN apropriados 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(s) pino(s) de renderização ou de captura 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 endpoint é 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 endpoint 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 Fluxo de Pacotes RT em resposta a EvtAcxCircuitCreateStream. O ACXSTREAM criado com AcxRtStreamCreate permitirá que o driver de 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 endpoint devem usar AcxStreamCreate para criar um Basic Stream 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 Pausar ou Executar.
O ACXCIRCUIT de streaming é o primeiro circuito a receber as solicitações para criar um fluxo. A solicitação inclui o dispositivo, o pin 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 ACX vincula os objetos ACXSTREAM (da mesma forma que os objetos ACXCIRCUIT são vinculados).
Circuitos upstream e downstream
A criação do fluxo começa no circuito de streaming e é encaminhada para cada circuito downstream na ordem em que os circuitos estão conectados. As conexões são feitas entre pinos de ponte criados com a comunicação configurada como 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 extremidade que descreve o hardware da terminação de áudio, indicando se a terminação é um microfone ou alto-falante e se o Jack está conectado.
Para cada circuito após o circuito de streaming, o pino da ponte AcxPinTypeSink se conectará ao próximo circuito a montante.
Negociação de formato de fluxo
O driver anuncia os formatos com suporte para a criação de stream adicionando os formatos com suporte por modos ao ACXPIN usado para a criação de stream 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 endpoint são determinados pelos ACXPINs de streaming criados pelo circuito de streaming. Os formatos usados pelos circuitos a seguir são determinados pelo pino de ponte do circuito anterior no ponto de extremidade.
Por padrão, a estrutura ACX criará um ACXSTREAMBRIDGE entre cada circuito em um ponto de extremidade com múltiplos circuitos. O ACXSTREAMBRIDGE padrão usará o formato padrão do modo RAW do pino de ponte do circuito upstream ao encaminhar a solicitação de criação de fluxo para o circuito downstream. Se o pino de ponte do circuito upstream não tiver formatos, o formato de fluxo original será usado. Se o pino conectado do circuito downstream não der suporte ao 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 do dispositivo deverá adicionar o formato downstream ao pino de ponte downstream.
Criação de fluxo
A primeira etapa na Criação de Fluxo é 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á EvtAcxCircuitCreateStream de cada circuito em ordem, terminando com o circuito final. A ordem pode ser revertida especificando o flag AcxStreamBridgeInvertChangeStateSequence (definido em ACX_STREAM_BRIDGE_CONFIG_FLAGS) para o Stream Bridge. Depois que todos os circuitos tiverem criado um objeto de fluxo, os objetos de fluxo lidarão com a lógica de streaming.
A Solicitação de Criação de Fluxo é enviada para o PIN apropriado gerado como parte da geração da topologia do circuito de cabeçalho mediante a chamada da função EvtAcxCircuitCreateStream especificada durante a criação do circuito de cabeçalho.
O circuito de streaming é o circuito upstream que inicialmente manipula a solicitação de criação do fluxo.
- Ele atualiza a estrutura ACXSTREAM_INIT, atribuindo AcxStreamCallbacks e AcxRtStreamCallbacks
- Ele cria o objeto ACXSTREAM usando AcxRtStreamCreate
- Ele cria elementos específicos do fluxo, por exemplo, ACXVOLUME ou ACXAUDIOENGINE
- Ele adiciona os elementos ao objeto ACXSTREAM
- Ele retorna o objeto ACXSTREAM que foi criado para a estrutura ACX
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 elementos específicos da transmissão
- Ele adiciona os elementos ao objeto ACXSTREAM
- Ele retorna o objeto ACXSTREAM que foi criado para a estrutura ACX
O canal de comunicação entre circuitos em um caminho de áudio faz uso de objetos ACXTARGETSTREAM. Neste exemplo, cada circuito terá acesso a uma Fila de E/S para o circuito na frente dele e o circuito por trás dele no Caminho de Áudio do Ponto de Extremidade. Além disso, um Caminho de Áudio do Endpoint é linear e bidirecional. O tratamento real da fila de E/S é executado 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 do Ponto de Extremidade composto por três circuitos: DSP, CODEC e AMP. O circuito DSP funciona como o circuito de streaming e possui um manipulador EvtAcxPinCreateStream. O circuito DSP também funciona como um circuito de filtro: dependendo do modo de fluxo e da configuração, ele pode aplicar o 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 tomadas ou outros detalhes de hardware do ponto de extremidade.
- O AudioKSE chama NtCreateFile para criar um fluxo.
- Faz a filtragem através do ACX e termina chamando o EvtAcxPinCreateStream do circuito DSP com o pino, a formatação de dados (incluindo o modo) e as informações do dispositivo.
- O circuito DSP valida as informações de formatação de dados para garantir que ele possa lidar com o fluxo criado.
- O circuito DSP cria o objeto ACXSTREAM para representar o fluxo.
- O circuito DSP aloca uma estrutura de contexto privado e a associa ao ACXSTREAM.
- O circuito DSP retorna o fluxo de execução do programa para o framework ACX, que, em seguida, chama o próximo circuito no Caminho de Áudio do Endpoint, o circuito CODEC.
- O circuito codec valida as informações de formatação de dados para confirmar que pode lidar com a renderização dos dados.
- O circuito CODEC aloca uma estrutura de contexto privado e a associa ao ACXSTREAM.
- O circuito CODEC se adiciona como um coletor de fluxo ao ACXSTREAM.
- O circuito CODEC retorna o fluxo de execução para a estrutura ACX, que, em seguida, chama o próximo circuito no Caminho de Áudio do Ponto de Extremidade, o circuito AMP.
- O circuito AMP aloca uma estrutura de contexto privada e a associa ao ACXSTREAM.
- O circuito AMP retorna o fluxo de execução para a estrutura ACX. Neste ponto, a criação do fluxo está concluída.
Fluxos de buffer grandes
Fluxos de buffer grandes são criados no ACXPIN designado para Descarregamento pelo elemento ACXAUDIOENGINE do ACXCIRCUIT.
Para dar suporte a fluxos de descarregamento, o driver do dispositivo deve fazer o seguinte durante a criação do circuito de streaming:
- Crie os objetos ACXPIN Host, Offload e Loopback e adicione-os ao ACXCIRCUIT.
- Crie elementos ACXVOLUME, ACXMUTE e ACXPEAKMETER. Elas não serão adicionadas diretamente ao ACXCIRCUIT.
- Inicialize uma estrutura ACX_AUDIOENGINE_CONFIG, atribuindo os objetos HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement e PeakMeterElement.
- Crie o elemento ACXAUDIOENGINE.
Os drivers precisarão executar etapas semelhantes para adicionar um elemento ACXSTREAMAUDIOENGINE ao criar um fluxo no pino Offload.
Alocação de recursos de transmissão
O modelo de streaming para ACX é baseado em pacotes, com suporte para um ou dois pacotes para um fluxo. O ACXPIN de Renderização ou Captura para o circuito de streaming recebe uma solicitação para alocar os pacotes de memória que são usados no fluxo. Para dar suporte ao reequilíbrio, a memória alocada deve ser a memória do sistema em vez da memória do dispositivo mapeada para o sistema. O driver pode usar funções existentes do WDF 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 será mapeado duas vezes no modo de usuário:
| pacote 0 | pacote 0 |
Isso permite que o 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 de pacote ACX inicial, há apenas dois pacotes alocados no início. O mapeamento de memória virtual do cliente permanecerá válido sem alterar a vida útil do fluxo depois que a alocação e o mapeamento forem 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.
Tamanhos de pacote de fluxos de buffer grandes
Ao expor o suporte para Buffer Grande, o driver fornecerá também uma callback que é usada para determinar os tamanhos mínimos e máximos de pacotes para a reprodução do Buffer Grande. O tamanho do pacote para alocação de buffer de fluxo é determinado com base nos valores mínimos e máximos.
Como os tamanhos mínimo e máximo de buffer podem ser voláteis, o driver pode falhar na chamada de alocação de pacotes se o mínimo e o máximo forem alterados.
Especificando restrições de buffer ACX
Para especificar as restrições de buffer do ACX, os drivers ACX podem usar as configurações 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 de 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
},
};
Posteriormente, 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 de 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 do 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 da corrente.
Para fluxos de renderização que vão de um estado menos ativo para um estado mais ativo, o circuito de streaming (que registrou o SINK) receberá o evento primeiro. Assim que o evento for manipulado, o próximo circuito na Via de Áudio do Ponto de Extremidade receberá o evento.
Para fluxos de renderização que vão de um estado mais ativo para um estado menos ativo, o circuito de streaming receberá o evento por último.
Para fluxos de captura que vão de um estado menos ativo para um estado mais ativo, o circuito de streaming receberá o evento por último.
Para fluxos de captura que vão de um estado mais ativo para um estado menos ativo, o circuito de streaming receberá o evento primeiro.
A ordenação 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 Pausar aguardando o início do fluxo. Quando o cliente coloca o fluxo no estado de Reprodução, a estrutura ACX chamará todos os objetos ACXSTREAM associados ao fluxo para indicar que o estado do fluxo está em Reprodução. Em seguida, o ACXPIN será colocado no estado De reprodução, momento em que os dados começarão a fluir.
Renderizando dados de áudio
Depois que o fluxo for criado e os recursos forem alocados, o aplicativo chamará Iniciar 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 traduz em uma chamada no AudioKSE, que aciona a camada ACX e por sua vez chama EvtAcxStreamSetRenderPacket no ACXSTREAM ativo. A propriedade incluirá o índice de pacote (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 de processar um pacote, ele emitirá a notificação de buffer completo, liberando os clientes que estão aguardando para preencher o próximo pacote com dados de áudio de renderização.
O modo de streaming "Timer Driven" é suportado e é indicado pelo uso de um valor de PacketCount igual a 1 ao chamar a função de retorno de chamada EvtAcxStreamAllocateRtPackets do driver.
Capturando dados de áudio
Depois que o fluxo for criado e os recursos forem alocados, o aplicativo chamará Iniciar 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. Depois 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 tiver sido sinalizada, o cliente poderá enviar KSPROPERTY_RTAUDIO_GETREADPACKET para obter o índice (baseado em 0) do pacote que terminou de capturar. Quando o cliente envia GETCAPTUREPACKET, o driver pode assumir que todos os pacotes anteriores foram processados e estão disponíveis para preenchimento.
Para captura de rajada, o circuito de origem pode liberar um novo pacote para o framework ACX assim que GETREADPACKET for chamado.
O cliente também pode usar KSPROPERTY_RTAUDIO_PACKETVREGISTER para obter um ponteiro para a estrutura RTAUDIO_PACKETVREGISTER do fluxo. Essa estrutura será atualizada pelo framework ACX antes do pacote de sinalização completo.
Comportamento de streaming herdado do KS no kernel
Pode haver situações, como quando um driver implementa a captura em rajada (como em uma implementação de detector de palavras-chave), em que o comportamento de manuseio de pacotes de streaming do kernel legado precisa ser usado em vez do PacketVRegister. Para usar o comportamento anterior baseado em pacotes, 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, consulte 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 atual do fluxo. A posição atual do fluxo incluirá o PlayOffset e o WriteOffset.
O modelo de streaming WaveRT permite que o driver de áudio exponha um registro de Posição de Hardware ao cliente. O modelo de streaming ACX não oferecerá suporte à exposição de registros de hardware, pois isso impediria que um reequilíbrio ocorresse.
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 levado 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 clientes por meio de KSPROPERTY_RTAUDIO_PACKETVREGISTER, que retorna um ponteiro para uma estrutura que contém CompletedPacketCount, CompletedPacketQPC e um valor que combina os dois (o que permite ao cliente garantir que CompletedPacketCount e CompletedPacketQPC sejam do mesmo pacote).
Transições de estado de fluxo
Depois que um fluxo for criado, o ACX fará a transição do fluxo para estados diferentes usando os seguintes retornos de chamada:
- EvtAcxStreamPrepareHardware fará a transição do fluxo do estado AcxStreamStateStop para o estado AcxStreamStatePause. O driver deve reservar hardware necessário, como mecanismos de DMA, quando receber EvtAcxStreamPrepareHardware.
- EvtAcxStreamRun fará a transição do fluxo do estado AcxStreamStatePause para o estado AcxStreamStateRun.
- EvtAcxStreamPause fará a transição do fluxo do estado AcxStreamStateRun para o estado AcxStreamStatePause.
- EvtAcxStreamReleaseHardware fará a transição do fluxo do estado AcxStreamStatePause para o estado AcxStreamStateStop. O driver deve liberar hardware necessário, como mecanismos DMA, quando receber EvtAcxStreamReleaseHardware.
O fluxo pode receber o retorno de chamada EvtAcxStreamPrepareHardware depois de receber o retorno de chamada EvtAcxStreamReleaseHardware. Isso fará a transição do fluxo de volta para o estado AcxStreamStatePause.
A alocação de pacotes com EvtAcxStreamAllocateRtPackets normalmente ocorrerá 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 remove a necessidade de o driver ter o estado de aquisição, pois esse estado está implícito com os retornos de chamada de preparação de hardware (EvtAcxStreamPrepareHardware) e liberação de hardware (EvtAcxStreamReleaseHardware).
Fluxos de buffer grandes e suporte ao motor 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 de 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 do fluxo
Quando o cliente fechar o fluxo, o driver receberá EvtAcxStreamPause e EvtAcxStreamReleaseHardware antes que o objeto ACXSTREAM seja excluído pelo ACX Framework. O driver pode fornecer a entrada WDF EvtCleanupCallback padrão na estrutura WDF_OBJECT_ATTRIBUTES ao chamar AcxStreamCreate para executar a limpeza final para o ACXSTREAM. O WDF chamará EvtCleanupCallback quando a estrutura tentar excluir o objeto. Não use EvtDestroyCallback, que é chamado apenas depois que todas as referências ao objeto tiverem sido 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 no EvtAcxStreamReleaseHardware.
É importante que o driver não limpe os recursos que dão suporte ao fluxo até que seja 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 nos retornos de chamada de preparação de hardware (EvtAcxStreamPrepareHardware) e liberação de hardware (EvtAcxStreamReleaseHardware).
Remoção e invalidação surpresa do stream
Se o driver determinar que o fluxo se tornou inválido (por exemplo, o jack fica desconectado), o circuito desligará todos os fluxos.
Limpeza de memória de fluxo
O descarte dos recursos do fluxo pode ser feito na limpeza de contexto do fluxo do driver (não destruir). Nunca coloque o descarte de nada que seja compartilhado no contexto de um objeto destruir o retorno de chamada. Essa orientação se aplica a todos os objetos ACX.
O retorno de chamada de destruição é invocado depois que o último ref se foi, quando é desconhecido.
Em geral, o callback de limpeza do fluxo é chamado quando o identificador é fechado. Uma exceção a isso é quando o driver criou o fluxo em seu retorno de chamada. Se o ACX não tiver adicionado esse fluxo à ponte de fluxo pouco antes de retornar da operação de criação de fluxo, o fluxo será cancelado como assíncrono e o thread atual retornará um erro para o cliente create-stream. O fluxo não deve ter alocações de mem alocadas neste momento. Para obter mais informações, consulte EVT_ACX_STREAM_RELEASE_HARDWARE retorno de chamada.
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 do modo de usuário fechar 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 manipulador, o ACX invoca o retorno de chamada para limpeza dos objetos de fluxo e, em seguida, o retorno de chamada para exclusão dos objetos de fluxo quando a referência no objeto vai para zero.
É possível que o ACX adie a exclusão de um objeto STREAM para um item de trabalho quando o driver criou um objeto stream e, em seguida, falhou na chamada de retorno "create-stream". Para evitar um impasse com uma linha de execução WDF em fase de desligamento, o ACX adia a exclusão para uma linha de execução diferente. Para evitar possíveis efeitos colaterais desse comportamento (liberação adiada de recursos), o driver pode liberar os recursos de fluxo de dados alocados antes de retornar um erro da criação de fluxo de dados.
O driver deve liberar os buffers de áudio quando o ACX chamar o callback EVT_ACX_STREAM_FREE_RTPACKETS. Este retorno de chamada é invocado quando o usuário fecha os manipuladores 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 invoque esse retorno de chamada.
EVT_ACX_STREAM_FREE_RTPACKETS retorno de chamada deve ser chamado após EVT_ACX_STREAM_RELEASE_HARDWARE retorno de chamada e terminar antes de EvtDeviceReleaseHardware.
Esse retorno de chamada pode acontecer depois que o driver processou o retorno de chamada de liberação de hardware do WDF, uma vez que o cliente no modo de usuário pode reter seus identificadores por um longo período. O driver não deve tentar esperar que esses handles desapareçam, isso apenas criará uma verificação de bug 0x9f DRIVER_POWER_STATE_FAILURE. Consulte EVT_WDF_DEVICE_RELEASE_HARDWARE função de retorno de chamada para obter mais informações.
Este código EvtDeviceReleaseHardware do driver ACX de exemplo mostra como chamar AcxDeviceRemoveCircuit e depois liberar a memória de hardware 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:
Liberação de hardware do dispositivo WDF –> liberar recursos de hardware do dispositivo
AcxStreamFreeRtPackets –> liberar buffer de áudio associado ao identificador
Para obter mais informações sobre como gerenciar objetos WDF e de circuito, consulte o Gerenciamento de Tempo de Vida do Driver WDF do ACX.
Streaming DDIs
Estruturas de streaming
estrutura ACX_RTPACKET
Essa estrutura representa um único pacote alocado. O PacketBuffer pode ser um identificador de WDF_MEMORY, um MDL ou um buffer. Ele tem uma função de inicialização associada, ACX_RTPACKET_INIT.
ACX_STREAM_CALLBACKS
Essa estrutura identifica os callbacks do driver para streaming no framework ACX. Essa estrutura faz parte da estrutura ACX_PIN_CONFIG.
Retornos de chamada de streaming
EvtAcxStreamAllocateRtPackets
O evento EvtAcxStreamAllocateRtPackets informa ao driver para alocar RtPackets para streaming. Um AcxRtStream receberá PacketCount = 2 para streaming controlado por eventos ou PacketCount = 1 para streaming baseado em temporizador. Se o driver usar um único buffer para ambos os pacotes, o segundo RtPacketBuffer deverá ter um WDF_MEMORY_DESCRIPTOR com Tipo = WdfMemoryDescriptorTypeInvalid com um RtPacketOffset que se alinha ao final do primeiro pacote (pacote[2].RtPacketOffset = pacote[1].RtPacketOffset+pacote[1].RtPacketSize).
EvtAcxStreamFreeRtPackets
O evento EvtAcxStreamFreeRtPackets informa 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 informa 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 Atraso está em unidades de 100 nanossegundos.
EvtAcxStreamSetRenderPacket
O evento EvtAcxStreamSetRenderPacket informa ao driver qual pacote foi 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 Pacote é o último pacote no fluxo e EosPacketLength é um comprimento válido em bytes para o pacote. Para obter mais informações, consulte OptionsFlags na estrutura de KSSTREAM_HEADER (ks.h).
O driver deve continuar a aumentar o CurrentRenderPacket à medida que 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 atualmente está sendo preenchido pelo hardware de captura.
EvtAcxStreamGetCapturePacket
O EvtAcxStreamGetCapturePacket instrui o driver a indicar qual pacote (com indexação a partir de 0) foi completamente preenchido mais recentemente, incluindo o valor QPC no momento em que o driver começou a preenchê-lo.
EvtAcxStreamGetPresentationPosition
O EvtAcxStreamGetPresentationPosition instrui o driver a indicar a posição atual e o valor de QPC no momento em que a posição atual foi calculada.
EVENTOS DE ESTADO DO STREAM
O estado de streaming de um ACXSTREAM é gerenciado pelas SEGUINTEs APIs.
EVT_ACX_STREAM_PREPARE_HARDWARE
EVT_ACX_STREAM_RELEASE_HARDWARE
APIs de Streaming ACX
AcxStreamCreate
O AcxStreamCreate cria um fluxo ACX que pode ser usado para controlar o comportamento de streaming.
AcxRtStreamCreate
O AcxRtStreamCreate cria um fluxo ACX que pode ser usado para controlar o comportamento de streaming e manipular a alocação de pacotes e comunicar o estado de streaming.
AcxRtStreamNotifyPacketComplete
O driver chama essa API 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 ACX definirá todos os eventos de notificação associados ao fluxo.
Consulte também
Visão geral de extensões de classe de áudio ACX