Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo explica o streaming e o buffering ACX, que são essenciais para uma experiência de áudio sem falhas. Descreve como o driver comunica o estado do fluxo e gere o buffer do fluxo. Para obter uma lista de termos comuns de áudio ACX e uma introdução ao ACX, consulte 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.
O ACX suporta dois tipos de fluxos. O primeiro tipo de fluxo, o RT Packet Stream, permite-lhe alocar pacotes RT e usá-los para transferir dados de áudio para ou do hardware do dispositivo, juntamente com transições de estado do fluxo. O segundo tipo de fluxo, o fluxo básico, suporta apenas transições de estado de fluxo.
Num único ponto final de circuito, o circuito é um circuito de streaming que cria um Fluxo de Pacotes RT. Se dois ou mais circuitos se ligarem para criar um ponto final, o primeiro circuito nesse ponto final é o circuito de streaming e cria um Fluxo de Pacotes RT. Circuitos ligados criam Fluxos Básicos para receber eventos relacionados com transições de estado de fluxo.
Para obter mais informações, consulte Fluxo ACX em Resumo de objetos ACX. Os DDIs para fluxos estão definidos no cabeçalho acxstreams.h .
Pilha de comunicações de streaming ACX
Existem dois tipos de comunicações para o streaming ACX. Um caminho de comunicação controla o comportamento de streaming. Por exemplo, comandos como Iniciar, Criar e Alocar, que usam comunicações ACX padrão. O framework ACX utiliza filas de IO e encaminha pedidos WDF através das filas. O comportamento da fila é ocultado do código real do driver através do uso de callbacks de eventos e funções ACX. O condutor também tem a oportunidade de pré-processar todos os pedidos do WDF.
O segundo caminho de comunicação, mais interessante, trata da sinalização em streaming de áudio. A sinalização envolve informar o driver quando um pacote está pronto, receber dados e quando o driver termina de processar um pacote.
Principais requisitos para sinalização em streaming:
- Suporta reprodução sem falhas
- Baixa latência
- Quaisquer eclusas necessárias estão limitadas ao fluxo em questão
- Facilidade de utilização para o 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. Estas técnicas são descritas a seguir.
Buffer compartilhado
Um buffer partilhado e um evento comunicam do driver para o cliente. O evento e o buffer partilhado garantem que o cliente não precisa de esperar ou interrogar. O cliente pode determinar tudo o que precisa para continuar a transmitir, reduzindo ou eliminando a necessidade de chamadas IRP diretas.
O driver de dispositivo usa um buffer partilhado para comunicar ao cliente qual pacote está a ser renderizado ou capturado. Este buffer partilhado inclui a contagem de pacotes (baseado em um) do último pacote concluído, juntamente com o valor QPC (QueryPerformanceCounter) relativo ao tempo de conclusão. Para o controlador do dispositivo, deve indicar esta informação chamando AcxRtStreamNotifyPacketComplete. Quando o driver do dispositivo chama AcxRtStreamNotifyPacketComplete, o framework ACX atualiza o buffer partilhado com a nova contagem de pacotes e QPC e sinaliza um evento partilhado com o cliente para indicar que este pode ler a nova contagem de pacotes.
Chamadas diretas de IRP
As chamadas IRP diretas comunicam entre o cliente e o controlador.
O cliente pode solicitar a contagem atual de pacotes ou indicar a contagem atual ao controlador do dispositivo a qualquer momento. Estes pedidos chamam os handlers de eventos do driver de dispositivos EvtAcxStreamGetCurrentPacket e EvtAcxStreamSetRenderPacket. O cliente também pode solicitar o pacote de captura atual, que chama o manipulador de eventos do driver de dispositivo EvtAcxStreamGetCapturePacket.
Semelhanças com PortCls
A combinação de chamadas IRP diretas e buffer partilhado que a ACX utiliza é semelhante à forma como o PortCls comunica o tratamento da conclusão do buffer.
Para evitar falhas, os condutores devem garantir que não fazem nada que exija acesso a bloqueios que também são usados nos caminhos de controlo de fluxo.
Grande suporte de buffer para reprodução de baixa potência
Para reduzir o consumo de energia durante a reprodução, reduza o tempo que a APU permanece num estado de alta potência. Como a reprodução normal de áudio usa buffers de 10 ms, a APU mantém-se ativa. Os drivers ACX podem anunciar suporte para buffers maiores, na faixa de 1–2 segundos, para permitir que a APU entre num estado de menor potência.
Nos modelos de streaming existentes, a reprodução por descarga suporta a reprodução de baixo consumo. Um controlador de áudio anuncia suporte para reprodução delegada ao expor um nó de AudioEngine num filtro de onda para um ponto de extremidade. O nó AudioEngine oferece um meio de controlar o motor DSP que o driver utiliza para renderizar o áudio a partir dos grandes buffers, com o processamento desejado.
O nó AudioEngine oferece estas funcionalidades:
- A Descrição do Motor de Áudio informa a pilha de áudio sobre quais pinos no filtro de onda fornecem suporte para offload e loopback (e suporte de reprodução no host).
- A Gama de Tamanho do Buffer indica à pilha de áudio os tamanhos mínimos e máximos de buffer que podem ser suportados para transferência. reprodução. O intervalo de tamanho do buffer pode mudar dinamicamente com base na atividade do sistema.
- Suporte a formatos, incluindo formatos suportados, o formato atual de combinação de dispositivos e o formato do dispositivo.
- Volume, incluindo suporte em progressão, já que, com buffers maiores, o volume de software não será responsivo.
- Proteção de loopback, que diz ao driver para silenciar o pino de loopback do AudioEngine se um ou mais dos fluxos descarregados contiverem conteúdo protegido.
- Estado FX global, para ativar ou desativar o GFX no AudioEngine.
Quando crias um stream no pino de offload, o stream suporta proteção contra volume, efeitos locais e loopback.
Reprodução de baixo consumo 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 streaming de host, offload e loopback, juntamente com um elemento ACXAUDIOENGINE que descreve quais desses pinos são usados para o host, offload e loopback. O driver adiciona os pinos e o elemento ACXAUDIOENGINE ao ACXCIRCUIT aquando da criação do circuito.
Criação de fluxo descarregado
O driver também adiciona um elemento ACXAUDIOENGINE aos streams criados para offload, permitindo controlo sobre volume, mute e 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, que pode vir de um fornecedor diferente. O ACX fornece uma interface de streaming de kernel compatível para que as aplicações corram sem alterações.
Pinos de fluxo
Cada ACXCIRCUIT tem, pelo menos, um pino de entrada e um pino de saída. Estes pinos são usados pela estrutura ACX para expor as ligações do circuito ao stack 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 do coletor é usado para controlar o comportamento de captura de qualquer fluxo criado a partir do circuito.
ACXPIN é o objeto usado para controlar a transmissão no caminho de áudio. O ACXCIRCUIT de streaming é responsável por criar os objetos ACXPIN apropriados para o Caminho de Áudio do Endpoint no momento da criação do circuito e por registar os ACXPINs com o ACX. O ACXCIRCUIT cria apenas os pinos de renderização ou captura para o circuito. O framework ACX cria o outro pino necessário para ligar e comunicar com o circuito.
Circuito de streaming
Quando um ponto final é composto por um único circuito, esse circuito é o circuito de streaming.
Quando um endpoint é composto por mais do que um circuito criado por um ou mais drivers de dispositivo, o ACXCOMPOSITETEMPLATE que descreve o endpoint composto determina a ordem específica que liga os circuitos. O primeiro circuito no ponto final é o circuito de streaming para o ponto final.
O circuito de streaming deve usar AcxRtStreamCreate para criar um RT Packet Stream em resposta a EvtAcxCircuitCreateStream. O ACXSTREAM criado com o AcxRtStreamCreate permite ao controlador do circuito de streaming alocar o buffer usado para o streaming e controlar o fluxo de streaming em resposta às necessidades do cliente e do hardware.
Os circuitos seguintes no ponto de extremidade devem usar AcxStreamCreate para criar um Basic Stream em resposta a EvtAcxCircuitCreateStream. Os objetos ACXSTREAM criados com o AcxStreamCreate pelos circuitos seguintes permitem que os drivers configurem o hardware em resposta a mudanças no estado do fluxo, como Pausa ou Executar.
O ACXCIRCUIT em streaming recebe o primeiro pedido 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 cria um objeto ACXSTREAM que representa a instância de fluxo do circuito. O framework ACX liga os objetos ACXSTREAM entre si, de forma semelhante à forma como liga os objetos ACXCIRCUIT.
Circuitos a montante e a jusante
A criação do fluxo começa no circuito de streaming e é encaminhada para cada circuito a jusante na ordem em que os circuitos estão conectados. As conexões são feitas entre pinos de ponte criados com Comunicação igual a AcxPinCommunicationNone. A estrutura ACX cria um ou mais pinos de ponte para um circuito se o controlador não os adicionar no momento da criação do circuito.
Para cada circuito que começa pelo circuito de streaming, o pino da ponte AcxPinTypeSource liga-se ao próximo circuito a jusante. O circuito final tem um pino de extremidade que descreve o hardware do terminal de áudio (como se o terminal é um microfone ou um altifalante e se a tomada está ligada).
Para cada circuito subsequente ao circuito de streaming, o pino de ligação AcxPinTypeSink conecta-se ao circuito a montante seguinte.
Negociação de formatos de fluxo
O driver anuncia os formatos suportados para a criação de fluxo, adicionando os formatos suportados por modo ao ACXPIN usado para criação de fluxo com AcxPinAssignModeDataFormatList e AcxPinGetRawDataFormatList. Para pontos finais de vários circuitos, um ACXSTREAMBRIDGE pode ser utilizado para coordenar o modo e o suporte de formato entre circuitos ACX. Os ACXPINs de streaming criados pelo circuito de streaming determinam os formatos de streaming suportados para o endpoint. Os formatos utilizados pelos circuitos seguintes são determinados pelo pino de ponte do circuito anterior no ponto final.
Por padrão, o framework ACX cria uma ponte ACXSTREAMBRIDGE entre cada circuito num ponto final multicircuito. A ACXSTREAMBRIDGE por defeito utiliza o formato padrão do modo RAW do pino da ponte do circuito a montante ao encaminhar o pedido de criação de fluxo para o circuito a jusante. Se o pino da ponte do circuito a montante não tiver formatos, é utilizado o formato original do fluxo. Caso o pino ligado do circuito descendente não suporte o formato em uso, a criação do fluxo falhará.
Se um circuito de dispositivo estiver executando uma alteração de formato de fluxo, o driver de dispositivo deve adicionar o formato downstream ao pino da ponte a jusante.
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 chama o EvtAcxCircuitCreateStream de cada circuito. ACX começa pelo circuito de entrada e chama o EvtAcxCircuitCreateStream de cada circuito por ordem, terminando com o circuito de saída. A ordem pode ser invertida ao especificar o flag AcxStreamBridgeInvertChangeStateSequence (definido em ACX_STREAM_BRIDGE_CONFIG_FLAGS) para a Stream Bridge. Depois de todos os circuitos criarem um objeto de fluxo, os objetos de fluxo tratam da lógica de transmissão.
A Solicitação de Criação de Fluxo é enviada ao PIN apropriado, gerado como parte da geração de topologia do circuito principal, através da chamada do 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
- Cria quaisquer 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
Em seguida, o ACX encaminha a criação do fluxo para o próximo circuito descendente.
- Ele atualiza a estrutura ACXSTREAM_INIT, atribuindo AcxStreamCallbacks
- Ele cria o objeto ACXSTREAM usando AcxStreamCreate
- Ele cria quaisquer elementos específicos do fluxo
- 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 num caminho de áudio utiliza objetos ACXTARGETSTREAM. Cada circuito tem acesso a uma Fila de E/S para o circuito à sua frente e para o circuito atrás dele no Caminho de Áudio do Endpoint. O Caminho de Áudio do Endpoint é linear e bidirecional. O framework ACX gere o processamento real da IO Queue.
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
Criação de um fluxo de renderização em um caminho de áudio de ponto final composto por três circuitos: DSP, CODEC e AMP. O circuito DSP funciona como o circuito de streaming e tem um manipulador EvtAcxPinCreateStream. O circuito DSP também funciona como circuito de filtro: dependendo do modo de fluxo e da configuração, pode aplicar processamento de sinal aos dados de áudio. O circuito CODEC representa o DAC, fornecendo a funcionalidade de dissipador de áudio. O circuito AMP representa o hardware analógico entre o DAC e o alto-falante. O circuito AMP pode lidar com a deteção de jack ou outros detalhes de hardware do ponto final.
- AudioKSE chama NtCreateFile para criar um fluxo.
- Este processo filtra através do ACX e termina chamando o EvtAcxPinCreateStream do circuito DSP com o pino, formato de dados (incluindo modo) e informações do dispositivo.
- O circuito DSP valida as informações do formato 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 associa-a ao ACXSTREAM.
- O circuito DSP retorna o fluxo de execução para a estrutura ACX, que então chama o próximo circuito no Endpoint Audio Path, o circuito CODEC.
- O circuito CODEC valida as informações do formato de dados para confirmar que ele pode lidar com a renderização dos dados.
- O circuito CODEC aloca uma estrutura de contexto privado e associa-a ao ACXSTREAM.
- O circuito CODEC adiciona-se como um coletor de fluxo para o ACXSTREAM.
- O circuito CODEC retorna o fluxo de execução para a estrutura ACX, que então chama o próximo circuito no Endpoint Audio Path, o circuito AMP.
- O circuito AMP aloca uma estrutura de contexto privado e associa-a 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.
Grandes fluxos tampão
Grandes fluxos de buffer são criados no ACXPIN designado para Offload pelo elemento ACXAUDIOENGINE do ACXCIRCUIT.
Para suportar fluxos de offload, o driver de dispositivo deve realizar as seguintes ações durante a criação do circuito de streaming:
- Crie os objetos Host, Offload e Loopback ACXPIN e adicione-os ao ACXCIRCUIT.
- Crie elementos ACXVOLUME, ACXMUTE e ACXPEAKMETER. Estes não serão adicionados 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 precisam de realizar passos semelhantes para adicionar um elemento ACXSTREAMAUDIOENGINE ao criar um stream no pin Offload.
Alocação de recursos em cursos de corrente
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 utilizados no streaming. Para suportar o Rebalanceamento, a memória alocada deve ser a memória do sistema em vez da memória do dispositivo mapeada no sistema. O driver pode usar funções WDF existentes para realizar a alocação e devolver um array de ponteiros para as alocações do buffer. Se o driver requer um único bloco contíguo, pode alocar ambos os pacotes como um único buffer. O segundo pacote tem WdfMemoryDescriptorTypeInvalid e o deslocamento do segundo pacote situa-se na área de buffer descrita pelo primeiro pacote.
Se um único pacote for alocado, o driver deve alocar um buffer alinhado à página com um comprimento que seja divisível por uma página. O deslocamento para o pacote único também deve ser 0. O framework ACX mapeia este pacote para o modo utilizador duas vezes, uma atrás da outra:
| pacote 0 | pacote 0 |
Isto permite ao GetBuffer devolver um ponteiro para um único buffer de memória contíguo que pode abranger desde o fim do buffer até ao início sem que a aplicação trate do wrapping do acesso à memória.
Se dois pacotes forem alocados, são mapeados para o modo utilizador:
| pacote 0 | pacote 1 |
Com o streaming de pacotes ACX inicial, há apenas dois pacotes alocados no início. Após a alocação e o mapeamento serem realizados, o mapeamento da memória virtual do cliente mantém-se válido, sem alterações durante toda a vida útil do fluxo. Há um evento associado ao fluxo que indica a conclusão dos dois pacotes. Existe também um buffer partilhado que o framework ACX usa para comunicar qual pacote terminou com o evento.
Para PacketCount=1, quando a aplicação pedir 10 ms de dados, a pilha de áudio envia um pedido para um único buffer de 10 ms ao driver (não duplica o tamanho do buffer enviado ao driver).
O driver aloca um buffer alinhado à página com pelo menos 10 ms de duração. Para um fluxo de 48k com 2 canais e 2 bytes por amostra, o menor buffer controlado por temporizador que pode ser alocado é de 1.024 amostras (uma página de memória), o que corresponde a 21.333 ms. Para um fluxo de 48k 8 canais de 2 bytes por amostra, o menor buffer controlado por temporizador que pode ser alocado é de 512 amostras (uma página de memória) ou 10,667 ms. Para um fluxo de amostras de 48 kHz, 6ch, e 2 bytes por amostra, o menor buffer controlado pelo temporizador ainda é de 1.024 amostras (três páginas de memória, para garantir que o fim de uma amostra se alinha com o final do buffer), o que corresponde a 21.333 ms.
O framework ACX mapeia este buffer alinhado com a página para o processo em modo de utilizador duas vezes, uma após a outra. O processo do modo utilizador pode então escrever até um buffer de dados no mapeamento do modo utilizador, começando em qualquer parte do buffer sem ter de fazer qualquer envolvimento.
O driver chama o NotifyPacketComplete depois de ler o pacote completo da memória do sistema, para que o sistema saiba que pode escrever o próximo pacote de dados de áudio no buffer do pacote.
Há um atraso entre o NotifyPacketComplete e o momento em que a última amostra desse pacote é renderizada. Este atraso é expresso como resultado de EvtAcxStreamGetHwLatency.
Amortecedores de pingue-pongue
Podem ser usados buffers de pingue-pongue, onde um buffer é lido (ping), enquanto o outro está a ser preenchido (pong). Isto permite que um buffer seja processado enquanto o outro recolhe o próximo conjunto de dados. No ACX, o driver trata internamente da comutação quando um buffer está preenchido. Depois de o buffer de ping estar preenchido, é notificado com um callback registado. No callback, o endereço do buffer processado é obtido e o buffer é reenviado. Entretanto, o buffer Pong recolhe dados em segundo plano. Este mecanismo assegura um processamento contínuo de dados sem interrupções.
Para um buffer ping-pong, o tamanho do pacote solicitado refere-se a um único buffer (seja ping ou pong), e a contagem de pacotes é de dois.
Ao partilhar um único buffer entre dois pacotes, configure o segundo pacote conforme descrito na função de callback EVT_ACX_STREAM_ALLOCATE_RTPACKETS. A parte do buffer descrita pelo primeiro pacote (memória, offset e comprimento) é o ping buffer, enquanto a parte descrita pelo segundo pacote (sem memória que indique que o buffer é partilhado com o primeiro pacote, mais o offset que aponta para o buffer logo após o primeiro pacote) é o pong buffer.
Adicionar informação adicional ao cabeçalho do pacote
Só é possível adicionar informação adicional à informação do cabeçalho do pacote, por exemplo para registos ou contabilidade, no início do pacote para fluxos orientados a eventos ping/pong (onde o número de pacotes = 2). Para fluxos gerados por temporizador com apenas um pacote, o pacote deve estar totalmente alinhado à página (começando e terminando num limite de página) porque o pacote é mapeado no modo de utilizador duas vezes.
Neste caso, a aplicação pode escrever após o fim do primeiro mapeamento no segundo mapeamento, que escreve no final do buffer do sistema e em seguida no início desse mesmo buffer.
O buffer único alocado deve estar alinhado com a página, pois o mapeamento da memória virtual para o modo utilizador ocorre por página.
Buffers controlados por temporizador
Buffers controlados por temporizador no ACX podem ser usados para garantir uma experiência de áudio sem falhas, mantendo uma temporização e sincronização precisas. Para buffers controlados por temporizador no ACX:
- O cliente utiliza o valor de EvtAcxStreamGetPresentationPosition para determinar quantos frames podem ser escritos.
- A posição da apresentação precisa de ser atualizada mais do que uma vez por passagem pelo buffer. O cliente escreve no buffer começando na posição onde escreveu pela última vez até à posição reportada pelo driver (que devem ser os dados consumidos pelo hardware desde a última consulta da posição).
- Quanto mais detalhada for a posição, menos provável é que experimente falhas.
- Em buffers controlados por temporizador, o DSP não pode simplesmente consumir todo o buffer antes de atualizar a posição.
- No modo controlado por temporizador, o driver poderia potencialmente dividir o buffer controlado por temporizador em múltiplos buffers DSP, atualizando a posição à medida que o DSP avança em cada buffer (por exemplo, um buffer controlado por temporizador de 20 ms dividido em 10 buffers de 2 ms comportar-se-ia razoavelmente bem no modo controlado por temporizador).
Tamanhos de pacotes de grandes fluxos de buffer
Ao expor o suporte para Large Buffers, o driver também fornecerá um retorno de chamada que é utilizado para determinar os tamanhos mínimo e máximo de pacotes para a reprodução com Large Buffer.
O tamanho de pacote para alocação de buffer de transmissão é determinado com base no mínimo e no máximo.
Como os tamanhos mínimos e máximos dos buffers podem ser voláteis, o driver pode falhar a chamada de alocação de pacotes se houver alterações nos tamanhos mínimos e máximos dos buffers.
Especificando restrições de buffer ACX
Para especificar restrições de buffer ACX, os drivers ACX podem usar a definição das 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 da 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, ou seja, os valores das propriedades de áudio KS na interface de áudio para o 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 no estado do fluxo
Quando ocorre uma alteração no estado do fluxo, cada objeto do fluxo no Caminho de Áudio do Endpoint para o fluxo recebe um evento de notificação do framework ACX. A ordem em que isso acontece depende da mudança de estado e do fluxo do córrego.
Para fluxos de renderização que passam de um estado menos ativo para um estado mais ativo, o circuito de streaming (que registou o SINK) recebe o evento primeiro. Uma vez que o circuito trata o evento, o circuito seguinte na Via de Áudio do Endpoint recebe o evento.
Para fluxos de renderização que passam de um estado mais ativo para um menos ativo, o circuito de streaming recebe o evento por último.
Para streams de Capture que passam de um estado menos ativo para um estado mais ativo, o circuito de streaming recebe o evento por último.
Para fluxos de captura que passam de um estado mais ativo para um estado menos ativo, o circuito de transmissão recebe o evento primeiro.
A ordenação é o padrão fornecido pelo framework 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.
Dados de áudio em streaming
Depois de criar o stream e alocar os buffers apropriados, o stream fica em estado de pausa e espera que o stream comece. Quando o cliente coloca o stream em estado de Reprodução, o framework ACX chama todos os objetos ACXSTREAM associados ao fluxo para indicar que o estado do fluxo está em Reprodução. O ACXPIN é então colocado no estado Play e os dados começam a fluir.
Renderização de dados de áudio
Depois de criar o stream e alocar os recursos, a aplicação invoca Iniciar no stream para começar a reprodução. A aplicação deve chamar o GetBuffer/ReleaseBuffer antes de iniciar a transmissão para garantir que o primeiro pacote que começa a ser reproduzido tem dados de áudio válidos.
O cliente inicia prerrolando um buffer. Quando o cliente chama ReleaseBuffer, isto equivale a uma chamada no AudioKSE que se encaminha para a camada ACX, que chama EvtAcxStreamSetRenderPacket na ACXSTREAM ativa. A propriedade inclui o índice de pacotes (baseado em zero) e, se aplicável, um indicador EOS com o deslocamento, em bytes, do final do fluxo presente no pacote atual.
Depois que o circuito de transmissão termina com um pacote, ele aciona a notificação de conclusão do buffer, que libera os clientes que aguardam 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 PacketCount de 1 ao chamar o callback EvtAcxStreamAllocateRtPackets do driver.
Captura de dados de áudio
Quando o fluxo corre, o circuito de origem preenche o pacote de captura com dados de áudio. Depois de o primeiro pacote ser preenchido, o circuito fonte liberta o pacote para o framework ACX. Neste ponto, o framework ACX sinaliza o evento de notificação do fluxo.
Depois de a notificação do fluxo ser sinalizada, o cliente pode enviar KSPROPERTY_RTAUDIO_GETREADPACKET para obter o índice (baseado em zero) do pacote que terminou de capturar. Quando o cliente envia o pacote GETCAPTURE, o driver pode assumir que todos os pacotes anteriores foram processados e estão disponíveis para preenchimento.
Para captura em rajada, o circuito de origem pode liberar um novo pacote para a estrutura ACX assim que GETREADPACKET é chamado.
O cliente também pode usar KSPROPERTY_RTAUDIO_PACKETVREGISTER para obter um ponteiro para a estrutura RTAUDIO_PACKETVREGISTER do fluxo. O framework ACX atualiza esta estrutura antes de sinalizar o pacote completo.
Comportamento legado de streaming do kernel KS
Por vezes, como quando um driver implementa captura de rajada (como um detetor de palavras-chave), é necessário usar o comportamento legado de processamento de pacotes do kernel em vez do PacketVRegister. Para usar o comportamento anterior baseado em pacotes, o driver devolve STATUS_NOT_SUPPORTED para KSPROPERTY_RTAUDIO_PACKETVREGISTER.
O exemplo a seguir mostra como fazer isso no AcxStreamInitAssignAcxRequestPreprocessCallback para um ACXSTREAM. Para 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 curso de água
O framework ACX chama o callback EvtAcxStreamGetPresentationPosition para obter a posição atual do fluxo. A posição atual do stream inclui o PlayOffset e o WriteOffset.
O modelo de streaming WaveRT permite que o driver de áudio exponha um registo de posição de hardware ao cliente. O modelo de streaming ACX não suporta a exposição de registos de hardware, pois estes impediriam que ocorra um rebalanceamento.
Cada vez que o circuito de streaming completa um pacote, chama AcxRtStreamNotifyPacketComplete com o índice de pacotes baseado em zero e o valor QPC tomado o mais próximo possível da conclusão do pacote (por exemplo, a Rotina de Serviço de Interrupção pode calcular o valor QPC). Os clientes podem obter esta informação através do KSPROPERTY_RTAUDIO_PACKETVREGISTER, que devolve um ponteiro para uma estrutura que contém o CompletedPacketCount, o CompletedPacketQPC e um valor que combina os dois (para que o cliente possa verificar se o CompletedPacketCount e o CompletedPacketQPC vêm 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:
- O EvtAcxStreamPrepareHardware faz a transição do fluxo do estado AcxStreamStateStop para o estado AcxStreamStatePause. O driver deve reservar o hardware necessário, como DMA Engines, quando recebe EvtAcxStreamPrepareHardware.
- O EvtAcxStreamRun faz a transição do fluxo do estado AcxStreamStatePause para o estado AcxStreamStateRun.
- O EvtAcxStreamPause faz a transição do fluxo do estado AcxStreamStateRun para o estado AcxStreamStatePause.
- O EvtAcxStreamReleaseHardware faz a transição do fluxo do estado AcxStreamStatePause para o estado AcxStreamStateStop. O driver deverá libertar o hardware necessário, como motores DMA, quando receber EvtAcxStreamReleaseHardware.
O fluxo pode receber o callback EvtAcxStreamPrepareHardware após receber o callback EvtAcxStreamReleaseHardware. Isto faz a transição do fluxo de volta para o estado AcxStreamStatePause.
A alocação de pacotes com o EvtAcxStreamAllocateRtPackets normalmente ocorre antes da primeira chamada ao EvtAcxStreamPrepareHardware. Os pacotes alocados são normalmente libertados com o EvtAcxStreamFreeRtPackets após a última chamada ao EvtAcxStreamReleaseHardware. Esta ordem não é garantida.
O estado AcxStreamStateAcquire não é utilizado. O ACX elimina a necessidade de o driver manter o estado de aquisição, pois este estado está implícito nos callbacks de preparação do hardware (EvtAcxStreamPrepareHardware) e libertação do hardware (EvtAcxStreamReleaseHardware).
Grandes fluxos de buffer e suporte ao motor de descarga
O ACX usa o elemento ACXAUDIOENGINE para designar um ACXPIN que manipulará a criação de fluxo de descarga e os diferentes elementos necessários para o volume de fluxo de descarga, mudo e estado do medidor de pico. Isto é semelhante ao nó do motor de áudio existente nos controladores WaveRT.
Processo de encerramento de fluxo de dados
Quando o cliente fecha o fluxo, o driver recebe EvtAcxStreamPause e EvtAcxStreamReleaseHardware antes de o objeto ACXSTREAM ser eliminado pelo framework ACX. O driver pode fornecer a entrada padrão WDF EvtCleanupCallback na estrutura WDF_OBJECT_ATTRIBUTES ao chamar AcxStreamCreate para realizar a limpeza final do ACXSTREAM. O WDF chama o EvtCleanupCallback quando o framework tenta eliminar o objeto. Não uses o EvtDestroyCallback, que só é chamado depois de todas as referências ao objeto serem libertadas, o que é indeterminado.
O driver deve limpar os recursos de memória do sistema associados ao objeto ACXSTREAM no EvtCleanupCallback se os recursos ainda não estiverem limpos no EvtAcxStreamReleaseHardware.
O driver não deve limpar os recursos que suportam o stream até que o cliente o solicite.
O estado AcxStreamStateAcquire não é utilizado. A eliminação da necessidade do estado acquire no driver pelo ACX ocorre porque este estado está implícito nas callbacks de preparação de hardware (EvtAcxStreamPrepareHardware) e de liberação de hardware (EvtAcxStreamReleaseHardware).
Remoção e invalidação de eventos inesperados no stream
Se o driver determinar que o fluxo é inválido (por exemplo, o conector estiver desligado), o circuito desliga todos os fluxos.
Limpeza de memória de fluxo de dados
O descarte dos recursos do fluxo pode ser feito na limpeza do contexto do fluxo do controlador, sem destruir. Não coloque o descarte de tudo o que é partilhado no contexto de um objeto para destruir o callback. Esta orientação aplica-se a todos os objetos ACX.
O callback de destruição é invocado após a última referência desaparecer, o que é indeterminado.
Em geral, o callback de limpeza do fluxo é chamado quando o identificador é fechado. Uma exceção ocorre quando o driver cria o fluxo durante a sua execução de callback. Se o ACX falhar em adicionar este fluxo à sua ponte de fluxo antes de retornar da operação de criação de fluxo, o fluxo é cancelado de forma assíncrona, e o encadeamento atual devolve um erro ao cliente de criação de fluxo. O stream não deveria ter alocações de memória neste momento. Para obter mais informações, consulte EVT_ACX_STREAM_RELEASE_HARDWARE Callback.
Sequência de limpeza da memória do fluxo
O buffer de fluxo é um recurso do sistema e deve libertá-lo apenas quando o cliente em modo de utilizador fechar o handle do fluxo. O buffer (que é diferente dos recursos de hardware do dispositivo) tem a mesma vida útil do handle do stream. Quando o cliente fecha o handle, o ACX invoca o callback de limpeza do objeto do fluxo e, em seguida, o callback de exclusão do objeto do fluxo quando a contagem de referências no objeto chega a zero.
É possível que o ACX adie a eliminação de um objeto STREAM para uma tarefa de processamento quando o driver cria um objeto de fluxo e depois falha na execução do callback de criação de fluxo. Para evitar um deadlock com uma thread WDF de encerramento, o ACX adia a exclusão para uma 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 libertar os buffers de áudio quando o ACX invocar o callback EVT_ACX_STREAM_FREE_RTPACKETS. Este callback ocorre quando o utilizador fecha os handles do fluxo.
Como os buffers RT são mapeados em modo utilizador, a vida útil do buffer é igual à vida útil da alça. O driver não deve libertar os buffers de áudio antes do ACX invocar este callback.
EVT_ACX_STREAM_FREE_RTPACKETS callback deve ser chamado após o EVT_ACX_STREAM_RELEASE_HARDWARE callback e terminar antes do EvtDeviceReleaseHardware.
Este callback pode ocorrer depois de o driver processar o callback de libertação de hardware WDF porque o cliente em modo de utilizador pode manter os seus handles durante muito tempo. O condutor não deve esperar que estas maçanetas desapareçam. Esta ação cria uma verificação de erro 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 chamada AcxDeviceRemoveCircuit e depois da libertação da 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:
- Libertação de hardware do dispositivo WDF: libertar os recursos de hardware do dispositivo.
- AcxStreamFreeRtPackets: liberte ou liberte o buffer de áudio associado ao handle.
Para mais informações sobre a gestão de WDF e objetos de circuito, consulte ACX WDF Driver Lifetime Management.
Transmissão de DDIs
Estruturas de streaming
estrutura ACX_RTPACKET
Esta estrutura representa um único pacote alocado. O PacketBuffer pode ser um manipulador WDFMEMORY, um MDL ou um buffer. Tem uma função de inicialização associada, ACX_RTPACKET_INIT.
ACX_STREAM_CALLBACKS
Esta estrutura identifica os callbacks do driver para streaming no âmbito do framework ACX. Esta estrutura faz parte da estrutura ACX_PIN_CONFIG.
Retorno de chamada de streaming
EvtAcxStreamAllocateRtPackets
O evento EvtAcxStreamAllocateRtPackets diz ao driver para alocar RtPackets para streaming. Um AcxRtStream recebe PacketCount = 2 para streaming orientado a eventos ou PacketCount = 1 para streaming baseado em temporizador. 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 = pacote[1]. RtPacketOffset+pacote[1]. RtPacketSize).
EvtAcxStreamFreeRtPackets
O evento EvtAcxStreamFreeRtPackets instrui o controlador a libertar os RtPackets que foram alocados numa chamada anterior para EvtAcxStreamAllocateRtPackets. Os mesmos pacotes dessa chamada estão incluídos.
EvtAcxStreamGetHwLatency
O evento EvtAcxStreamGetHwLatency instrui o driver a fornecer a latência do fluxo para o circuito específico deste fluxo (a latência total será a soma das latências 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 lançado pelo cliente. Se não houver falhas, esse pacote deve ser (CurrentRenderPacket + 1), onde CurrentRenderPacket é o pacote do qual o driver está transmitindo no momento.
Os flags podem ser 0 ou KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM = 0x200, indicando que o pacote de dados é o último no fluxo de dados, e que EosPacketLength é um comprimento válido em bytes para o pacote. Para mais informações, consulte OptionsFlags na estrutura KSSTREAM_HEADER (ks.h).
O driver continua a aumentar o CurrentRenderPacket à medida que os pacotes são renderizados, em vez de alterar o seu CurrentRenderPacket para corresponder a este valor.
EvtAcxStreamGetCurrentPacket
O EvtAcxStreamGetCurrentPacket indica ao driver que indique qual o pacote (baseado em zero) que está atualmente a ser renderizado no hardware ou que está a ser preenchido pelo hardware de captura.
EvtAcxStreamGetCapturePacket
O EvtAcxStreamGetCapturePacket indica ao controlador que indique qual o pacote (baseado em zero) que foi preenchido mais recentemente, incluindo o valor QPC no momento em que o driver começou a preencher o pacote.
EvtAcxStreamGetPresentationPosition
O EvtAcxStreamGetPresentationPosition instrui o driver a indicar a posição atual juntamente com o valor do QPC no momento em que essa posição foi calculada.
TRANSMITIR EVENTOS DE ESTADO
As seguintes APIs gerem o estado de streaming de um ACXSTREAM.
- EVT_ACX_STREAM_PREPARE_HARDWARE
- EVT_ACX_STREAM_RELEASE_HARDWARE
- EVT_ACX_STREAM_RUN
- EVT_ACX_STREAM_PAUSE
APIs de Streaming ACX
AcxStreamCreate
AcxStreamCreate cria um fluxo ACX que pode ser usado para controlar o comportamento de streaming.
AcxRtStreamCreate
AcxRtStreamCreate cria um fluxo 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 ACX quando um pacote é concluído. O tempo de conclusão dos pacotes e o índice de pacotes baseados em zero são incluídos para melhorar o desempenho do cliente. O framework ACX define quaisquer eventos de notificação associados ao fluxo.