Partager via


ACX en streaming

Ce sujet aborde la diffusion audio en continu ACX et la mise en mémoire tampon associée, qui est essentielle pour une expérience audio sans interruption. Il décrit les mécanismes utilisés par le pilote pour communiquer sur l’état du flux et gérer la mémoire tampon pour le flux. Pour obtenir la liste des termes audio ACX courants et une présentation d’ACX, consultez la vue d’ensemble des extensions de classe audio ACX.

Types de streaming ACX

AcxStream représente un flux audio sur le matériel d’un circuit spécifique. AcxStream peut agréger un ou plusieurs objets de type AcxElements.

L’infrastructure ACX prend en charge deux types de flux. Le premier type de flux, le flux de paquets RT, prend en charge l’allocation de paquets RT et l’utilisation de paquets RT pour transférer des données audio vers ou depuis le matériel de l’appareil, ainsi que des transitions d’état de flux. Le deuxième type de flux, le flux de base, fournit uniquement la prise en charge des transitions d’état de flux.

Dans un point de terminaison de circuit unique, le circuit doit être un circuit de streaming qui crée un flux de paquets RT. Si deux circuits ou plus sont connectés pour créer un point de terminaison, le premier circuit du point de terminaison est le circuit de diffusion en continu et crée un flux de paquets RT ; Les circuits connectés créent des flux de base pour recevoir des événements liés aux transitions d’état de flux.

Pour plus d’informations, consultez Flux ACX dans résumé des objets ACX. Les DDIS pour le flux sont définies dans l’en-tête acxstreams.h .

Architecture de communication en streaming ACX

Il existe deux types de communications pour ACX Streaming. Un chemin de communication est utilisé pour contrôler le comportement de diffusion en continu, pour les commandes telles que Start, Create et Allocate, qui utiliseront les communications ACX standard. L’infrastructure ACX utilise des queues de E/S et transmet les requêtes WDF par l’intermédiaire des queues. Le comportement de file d’attente est masqué du code de pilote réel par le biais de l’utilisation des rappels Evt et des fonctions ACX, bien que le pilote ait également la possibilité de prétraiter toutes les requêtes WDF.

Le deuxième chemin de communication plus intéressant est utilisé pour la signalisation de streaming audio. Cela implique d’indiquer au pilote lorsqu’un paquet est prêt et de recevoir des données lorsque le pilote a terminé le traitement d’un paquet. 

Les principales exigences en matière de signalisation de diffusion en continu :

  • Prise en charge de la lecture Glitch-Free
    • Latence faible
    • Les verrous nécessaires sont limités au flux en question
  • Facilité d’utilisation pour le développeur de pilotes

Pour communiquer avec le pilote pour signaler l’état de diffusion en continu, ACX utilise des événements avec une mémoire tampon partagée et des appels IRP directs. Celles-ci sont décrites ci-dessous.

Mémoire tampon partagée

Pour communiquer du pilote au client, une mémoire tampon partagée et un événement sont utilisés. Cela garantit que le client n’a pas besoin d’attendre ou d’interroger, et que le client peut déterminer tout ce qu’il doit continuer à diffuser en continu tout en réduisant ou en éliminant la nécessité d’appels IRP directs.

Le pilote de périphérique utilise une mémoire tampon partagée pour communiquer avec le client vers lequel le paquet est restitué ou capturé. Cette mémoire tampon partagée inclut le nombre de paquets (basé sur 1) du dernier paquet terminé, ainsi que la valeur QPC (QueryPerformanceCounter) de l’heure d’achèvement. Pour le pilote de périphérique, il doit indiquer ces informations en appelant AcxRtStreamNotifyPacketComplete. Lorsque le pilote de périphérique appelle AcxRtStreamNotifyPacketComplete, l’infrastructure ACX met à jour la mémoire tampon partagée avec le nouveau nombre de paquets et QPC et signale un événement partagé avec le client pour indiquer que le client peut lire le nouveau nombre de paquets.

Appels IRP directs

Pour communiquer du client au pilote, les appels IRP directs sont utilisés. Cela réduit les complexités autour de l’assurance que les demandes WDF sont gérées en temps opportun et ont été prouvées pour fonctionner correctement dans l’architecture existante.

Le client peut à tout moment demander le nombre de paquets actuel ou indiquer le nombre de paquets actuel au pilote de périphérique. Ces demandes appellent les gestionnaires d’événements du pilote de périphérique EvtAcxStreamGetCurrentPacket et EvtAcxStreamSetRenderPacket . Le client peut également demander le paquet de capture actuel qui appelle le gestionnaire d’événements du pilote de périphérique EvtAcxStreamGetCapturePacket .

Similarités avec PortCls

La combinaison d’appels IRP directs et de mémoire tampon partagée utilisée par ACX est similaire à la façon dont la gestion de la complétion de la mémoire tampon est communiquée dans PortCls. Les irps sont très similaires et la mémoire tampon partagée introduit la possibilité pour le pilote de communiquer directement le nombre de paquets et le minutage sans compter sur les IRPs.   Les pilotes devront s'assurer de ne pas effectuer d'actions nécessitant un accès aux verrous qui sont également utilisés dans les chemins de contrôle de flux, ce qui est nécessaire pour éviter les dysfonctionnements. 

Prise en charge des mémoires tampons volumineuses pour la lecture à faible puissance

Pour réduire la quantité de puissance consommée lors de la lecture du contenu multimédia, il est important de réduire le temps passé par l’APU dans un état à haute puissance. Étant donné que la lecture audio normale utilise des mémoires tampons de 10 ms, l’APU doit toujours être actif. Pour donner au processeur APU le temps nécessaire pour réduire l'état, les pilotes ACX sont autorisés à annoncer la prise en charge de mémoires tampons beaucoup plus volumineuses, dans une plage de taille de 1 à 2 secondes. Cela signifie que l’APU peut se réveiller une fois toutes les 1 à 2 secondes, effectuer les opérations requises à la vitesse maximale pour préparer la mémoire tampon de 1 à 2 secondes suivante, puis atteindre l’état d’alimentation le plus bas possible jusqu’à ce que la mémoire tampon suivante soit nécessaire. 

Dans les modèles de diffusion en continu existants, la lecture à faible puissance est prise en charge grâce à la lecture en déchargement. Un pilote audio indique la prise en charge de la lecture en déchargement en exposant un nœud AudioEngine sur le filtre de flux audio pour une extrémité. Le nœud AudioEngine fournit un moyen de contrôler le moteur DSP que le pilote utilise pour afficher l’audio à partir des mémoires tampons volumineuses avec le traitement souhaité.

Le nœud AudioEngine fournit ces fonctionnalités :

  • La description du moteur audio, qui indique à la pile audio quels pins sur le filtre d'onde offrent la prise en charge du déchargement et du bouclage (et la lecture hôte). 
  • Plage de tailles de mémoire tampon, qui indique à la pile audio les tailles de mémoire tampon minimale et maximale qui peuvent être prises en charge pour le déchargement. Lecture. La plage de tailles de mémoire tampon peut changer dynamiquement en fonction de l’activité système. 
  • Prise en charge du format, y compris les formats pris en charge, le format de combinaison d’appareils actuel et le format de l’appareil. 
  • Le volume, y compris la prise en charge de la montée en charge, étant donné que le volume logiciel des mémoires tampons plus volumineuses ne sera pas réactif.
  • Loopback Protection, qui indique au pilote de désactiver la broche de bouclage AudioEngine si un ou plusieurs flux déchargés contiennent du contenu protégé. 
  • État GLOBAL FX, pour activer ou désactiver GFX sur l’AudioEngine. 

Lorsqu'un flux est créé sur la broche de déchargement, le flux prend en charge le volume, les effets locaux et la protection contre le bouclage. 

Lecture à faible consommation d'énergie avec ACX

L’infrastructure ACX utilise le même modèle pour la lecture à faible alimentation. Le pilote crée trois objets ACXPIN distincts pour l’hôte, le déchargement et le streaming de retour, ainsi qu’un élément ACXAUDIOENGINE qui décrit lesquelles de ces broches sont utilisées pour l’hôte, le déchargement et le retour. Le pilote ajoute les broches et l’élément ACXAUDIOENGINE au ACXCIRCUIT lors de la création du circuit.

Création de flux externalisé

Le pilote ajoute également un élément ACXAUDIOENGINE aux flux créés pour le déchargement afin d’autoriser le contrôle sur le volume, la sourdine et le compteur de crête.

Diagramme de diffusion en continu

Ce diagramme montre un pilote ACX multi stack.

Diagramme illustrant les zones DSP, CODEC et AMP avec une interface de diffusion en continu du noyau en haut.

Chaque pilote ACX contrôle une partie distincte du matériel audio et peut être fourni par un autre fournisseur. ACX fournit une interface de diffusion en continu de noyau compatible pour permettre aux applications de s’exécuter comme c’est le cas.

Épingles de streaming

Chaque ACXCIRCUIT a au moins une broche de récepteur et une broche de source. Ces broches sont utilisées par l’infrastructure ACX pour exposer les connexions du circuit à la pile audio. Pour un circuit de rendu, la broche source est utilisée pour contrôler le comportement de rendu de n’importe quel flux créé à partir du circuit. Pour un circuit de capture, la broche du récepteur est utilisée pour contrôler le comportement de capture d’un flux créé à partir du circuit.   ACXPIN est l’objet utilisé pour contrôler la diffusion en continu dans le chemin audio. Le streaming ACXCIRCUIT est chargé de créer les objets ACXPIN appropriés pour le chemin audio du point de terminaison au moment de la création du circuit et d’inscrire les ACXPIN auprès d’ACX. L'ACXCIRCUIT doit uniquement créer la ou les broches de rendu ou de capture pour le circuit ; l'infrastructure ACX créera l'autre broche nécessaire pour se connecter au circuit et communiquer avec lui.   

Circuit de diffusion en continu

Lorsqu’un point de terminaison est composé d’un seul circuit, ce circuit est le circuit de diffusion en continu.

Lorsqu’un point de terminaison est composé de plusieurs circuits créés par un ou plusieurs pilotes de périphérique, les circuits sont connectés dans l’ordre spécifique déterminé par ACXCOMPOSITETEMPLATE qui décrit le point de terminaison composé. Le premier circuit du point de terminaison est le circuit de streaming du point de terminaison.

Le circuit de streaming doit utiliser AcxRtStreamCreate pour créer un flux de paquets RT en réponse à EvtAcxCircuitCreateStream. AcXSTREAM créé avec AcxRtStreamCreate permet au pilote de circuit de diffusion en continu d’allouer la mémoire tampon utilisée pour la diffusion en continu et de contrôler le flux de streaming en réponse aux besoins du client et du matériel.

Les circuits suivants dans le point de terminaison doivent utiliser AcxStreamCreate pour créer un flux de base en réponse à EvtAcxCircuitCreateStream. Les objets ACXSTREAM créés avec AcxStreamCreate par les circuits suivants permettent aux pilotes de configurer du matériel en réponse aux modifications d’état de flux telles que Pause ou Exécution.

Le streaming ACXCIRCUIT est le premier circuit à recevoir les demandes de création d’un flux. La demande inclut l’appareil, la broche et le format de données (y compris le mode).

Chaque ACXCIRCUIT dans le chemin audio crée un objet ACXSTREAM qui représente l’instance de flux du circuit. L’infrastructure ACX lie les objets ACXSTREAM ensemble (de la même façon que les objets ACXCIRCUIT sont liés). 

Circuits en amont et en aval

La création de flux commence au circuit de diffusion en continu et est transférée à chaque circuit en aval dans l’ordre dans lequel les circuits sont connectés. Les connexions sont établies entre les broches de pont créées avec Communication égale à AcxPinCommunicationNone. Le cadre ACX créera une ou plusieurs broches de pont pour un circuit si le pilote ne les ajoute pas lors de la création du circuit.

Pour chaque circuit commençant par le circuit de diffusion en continu, la broche de pont AcxPinTypeSource se connecte au prochain circuit en aval. Le circuit final aura une broche de point de terminaison décrivant le matériel du point de terminaison audio (par exemple, si le point de terminaison est un microphone ou un haut-parleur et si le jack est branché).

Pour chaque circuit suivant le circuit de diffusion en continu, la broche de pont AcxPinTypeSink se connecte au prochain circuit en amont.

Négociation de format de flux

Le pilote publie les formats pris en charge pour la création de flux en ajoutant les formats pris en charge par mode à ACXPIN utilisé pour la création de flux avec AcxPinAssignModeDataFormatList et AcxPinGetRawDataFormatList. Pour les points de terminaison à plusieurs circuits, un ACXSTREAMBRIDGE peut être utilisé pour coordonner la prise en charge des modes et formats entre les circuits ACX. Les formats de flux pris en charge pour le point de terminaison sont déterminés par les ACXPIN de streaming créés par le circuit de streaming. Les formats utilisés par les circuits suivants sont déterminés par la broche de pont du circuit précédent dans le point de terminaison.

Par défaut, l’infrastructure ACX crée un ACXSTREAMBRIDGE entre chaque circuit d’un point de terminaison à plusieurs circuits. AcXSTREAMBRIDGE par défaut utilise le format par défaut du mode RAW de la broche de pont du circuit en amont lors du transfert de la demande de création de flux vers le circuit en aval. Si la broche de pont du circuit en amont n’a aucun format, le format de flux d’origine sera utilisé. Si la broche connectée du circuit en aval ne prend pas en charge le format utilisé, la création de flux échouera.

Si le circuit de l'appareil effectue une modification du format de flux, le pilote de périphérique doit ajouter le format en aval à la broche de pont en aval.  

Création de flux

La première étape de la création de flux consiste à créer l’instance ACXSTREAM pour chaque ACXCIRCUIT dans le chemin audio du point de terminaison. ACX appellera la fonction EvtAcxCircuitCreateStream pour chaque circuit. ACX commence par le circuit principal et appelle l’EvtAcxCircuitCreateStream de chaque circuit dans l’ordre, se terminant par le circuit de queue. L’ordre peut être inversé en spécifiant l’indicateur AcxStreamBridgeInvertChangeStateSequence (défini dans ACX_STREAM_BRIDGE_CONFIG_FLAGS) pour le pont Stream. Une fois que tous les circuits ont créé un objet de flux, les objets de flux gèrent la logique de diffusion en continu.

La demande de création de flux est envoyée au code confidentiel approprié généré dans le cadre de la génération de topologie du circuit principal en appelant l’EvtAcxCircuitCreateStream spécifié lors de la création du circuit principal. 

Le circuit de streaming est le circuit en amont qui gère initialement la demande de création de flux.

  • Il met à jour la structure ACXSTREAM_INIT, en affectant AcxStreamCallbacks et AcxRtStreamCallbacks
  • Il crée l’objet ACXSTREAM à l’aide d’AcxRtStreamCreate
  • Il crée des éléments spécifiques au flux (par exemple, ACXVOLUME ou ACXAUDIOENGINE)
  • Il ajoute les éléments à l’objet ACXSTREAM
  • Elle retourne l’objet ACXSTREAM qui a été créé dans l’infrastructure ACX

ACX transfère ensuite la création du flux vers le circuit en aval suivant.

  • Il met à jour la structure ACXSTREAM_INIT, en affectant AcxStreamCallbacks
  • Il crée l’objet ACXSTREAM à l’aide d’AcxStreamCreate
  • Il crée des éléments spécifiques au flux
  • Il ajoute les éléments à l’objet ACXSTREAM
  • Elle retourne l’objet ACXSTREAM qui a été créé dans l’infrastructure ACX

Le canal de communication entre les circuits d’un chemin audio utilise des objets ACXTARGETSTREAM. Dans cet exemple, chaque circuit aura accès à une file d’attente d’E/S pour le circuit devant lui et le circuit derrière celui-ci dans le chemin audio du point de terminaison. En outre, un chemin audio de point de terminaison est linéaire et bidirectionnel. La gestion réelle de la file d’attente d’E/S est effectuée par l’infrastructure ACX.    Lors de la création de l’objet ACXSTREAM, chaque circuit peut ajouter des informations de contexte à l’objet ACXSTREAM pour stocker et suivre les données privées du flux.

Exemple de flux de rendu

Création d’un flux de rendu sur un chemin audio de point de terminaison composé de trois circuits : DSP, CODEC et AMP. Le circuit DSP fonctionne comme un circuit de streaming, et a fourni un gestionnaire EvtAcxPinCreateStream. Le circuit DSP fonctionne également comme un circuit de filtre : en fonction du mode de flux et de la configuration, il peut appliquer le traitement du signal aux données audio. Le circuit CODEC représente la DAC, fournissant la fonctionnalité de récepteur audio. Le circuit AMP représente le matériel analogique entre le DAC et le haut-parleur. Le circuit AMP peut gérer la détection de jack ou d’autres détails matériels de point de terminaison.

  1. AudioKSE appelle NtCreateFile pour créer un flux.
  2. Ce processus passe par ACX et se termine par l'appel de la fonction EvtAcxPinCreateStream du circuit DSP avec la broche, le format de données (y compris le mode), et les informations sur le périphérique. 
  3. Le circuit DSP valide les informations de mise en forme des données pour s’assurer qu’elles peuvent gérer le flux créé. 
  4. Le circuit DSP crée l’objet ACXSTREAM pour représenter le flux. 
  5. Le circuit DSP alloue une structure de contexte privé et l’associe à ACXSTREAM. 
  6. Le circuit DSP retourne le flux d’exécution à l’infrastructure ACX, qui appelle ensuite le circuit suivant dans le chemin audio du point de terminaison, le circuit CODEC. 
  7. Le circuit CODEC valide les informations de mise en forme des données pour confirmer qu’elle peut gérer le rendu des données. 
  8. Le circuit CODEC alloue une structure de contexte privé et l’associe à ACXSTREAM. 
  9. Le circuit CODEC s’ajoute en tant que récepteur de flux à ACXSTREAM.
  10. Le circuit CODEC retourne le flux d’exécution à l’infrastructure ACX, qui appelle ensuite le circuit suivant dans le chemin audio du point de terminaison, le circuit AMP. 
  11. Le circuit AMP alloue une structure de contexte privé et l’associe à ACXSTREAM. 
  12. Le circuit AMP retourne le flux d’exécution vers l’infrastructure ACX. À ce stade, la création de flux est terminée. 

Flux de mémoires tampons volumineux

Les flux de mémoire tampon volumineux sont créés sur l’ÉLÉMENT ACXPIN désigné pour Offload par l’élément ACXAUDIOENGINE d’ACXCIRCUIT.

Pour prendre en charge les flux de déchargement, le pilote de périphérique doit effectuer les opérations suivantes lors de la création du circuit de diffusion en continu :

  1. Créez les objets ACXPIN Host, Offload et Loopback, puis ajoutez-les à ACXCIRCUIT.
  2. Créez des éléments ACXVOLUME, ACXMUTE et ACXPEAKMETER. Celles-ci ne seront pas ajoutées directement à l’ACXCIRCUIT.
  3. Initialisez une structure ACX_AUDIOENGINE_CONFIG, en affectant les objets HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement et PeakMeterElement.
  4. Créez l’élément ACXAUDIOENGINE.

Les pilotes devront effectuer des étapes similaires pour ajouter un élément ACXSTREAMAUDIOENGINE lors de la création d’un flux sur la broche Offload.

Allocation de ressources de flux

Le modèle de streaming pour ACX est basé sur des paquets, avec prise en charge d’un ou deux paquets pour un flux. Le rendu ou capture ACXPIN pour le circuit de streaming reçoit une requête d’allocation des paquets de mémoire utilisés dans le flux. Pour prendre en charge Le rééquilibrage, la mémoire allouée doit être la mémoire système au lieu de la mémoire de l’appareil mappée au système. Le pilote peut utiliser des fonctions WDF existantes pour effectuer l’allocation et retournera un tableau de pointeurs vers les allocations de mémoire tampon. Si le pilote nécessite un bloc contigu unique, il peut allouer les deux paquets en tant que mémoire tampon unique, en retournant un pointeur vers un décalage de la mémoire tampon en tant que deuxième paquet.

Si un seul paquet est alloué, le paquet doit être aligné sur la page et est mappé deux fois en mode utilisateur :

| paquet 0 | paquet 0 |

Cela permet à GetBuffer de retourner un pointeur vers une mémoire tampon contiguë unique qui peut s’étendre de la fin de la mémoire tampon au début sans exiger que l’application gère l’habillage de l’accès à la mémoire. 

Si deux paquets sont alloués, ils sont mappés en mode utilisateur :

| paquet 0 | paquet 1 |

Avec le streaming initial des paquets ACX, il n’y a que deux paquets alloués au début. Le mappage de mémoire virtuelle du client restera valide sans modification tout au long de la durée du flux une fois l’allocation et le mappage effectués. Il existe un événement associé au flux pour indiquer l'achèvement des deux paquets. Il y aura également une mémoire tampon partagée que l’infrastructure ACX utilisera pour indiquer quel paquet a terminé l’événement.  

Tailles de paquets de flux de mémoire tampon volumineuses

Lors de l'activation du support des mémoires tampons volumineuses, le pilote fournit également un rappel utilisé pour déterminer les tailles de paquets minimales et maximales pour la lecture des mémoires tampons volumineuses.   La taille des paquets pour l’allocation de mémoire tampon de flux est déterminée en fonction du minimum et du maximum.

Étant donné que les tailles de mémoire tampon minimale et maximale peuvent être volatiles, le pilote peut échouer l’appel d’allocation de paquets si le minimum et le maximum ont changé.

Spécification des contraintes de mémoire tampon ACX

Pour spécifier des contraintes de mémoire tampon ACX, les pilotes ACX peuvent utiliser le paramètre de propriétés KS/PortCls : KSAUDIO_PACKETSIZE_CONSTRAINTS2 et la structure KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT.

L’exemple de code suivant montre comment définir des contraintes de taille de mémoire tampon pour les mémoires tampons WaveRT pour différents modes de traitement de signal.

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

Une structure DSP_DEVPROPERTY est utilisée pour stocker les contraintes.

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

Et un tableau de ces structures est créé.

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

Plus loin dans la fonction EvtCircuitCompositeCircuitInitialize, la fonction d’assistance AddPropertyToCircuitInterface est utilisée pour ajouter le tableau de propriétés d’interface au circuit.

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

La fonction d’assistance AddPropertyToCircuitInterface prend acxCircuitGetSymbolicLinkName pour le circuit, puis appelle IoGetDeviceInterfaceAlias pour localiser l’interface audio utilisée par le circuit.

Ensuite, la fonction SetDeviceInterfacePropertyDataMultiple appelle la fonction IoSetDeviceInterfacePropertyData pour modifier la valeur actuelle de la propriété de l’interface de l’appareil : les valeurs de propriété audio KS sur l’interface audio pour 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;
}

Modifications de l’état du flux

Lorsqu’une modification d’état de flux se produit, chaque objet de flux dans le chemin audio du point de terminaison du flux reçoit un événement de notification du framework ACX. L’ordre dans lequel cela se produit dépend du changement d’état et du flux du flux.

  • Pour les flux de Render passant d’un état moins actif à un état plus actif, le circuit de diffusion en continu (qui s'est enregistré en tant que SINK) recevra d’abord l’événement. Une fois l’événement géré, le circuit suivant dans le parcours audio du point de terminaison recevra l’événement.

  • Pour les flux de rendu allant d’un état plus actif à un état moins actif, le circuit de diffusion en continu reçoit l’événement en dernier. 

  • Pour les flux de capture allant d’un état moins actif à un état plus actif, le circuit de diffusion en continu reçoit l’événement en dernier. 

  • Pour les flux de capture allant d’un état plus actif à un état moins actif, le circuit de diffusion en continu reçoit d’abord l’événement. 

L’ordre ci-dessus est la valeur par défaut fournie par l’infrastructure ACX. Un pilote peut demander le comportement opposé en définissant AcxStreamBridgeInvertChangeStateSequence (défini dans ACX_STREAM_BRIDGE_CONFIG_FLAGS) lors de la création de l’ACXSTREAMBRIDGE que le pilote ajoute au circuit de diffusion en continu.  

Diffusion en continu de données audio

Une fois le flux créé et les mémoires tampons appropriées allouées, le flux est dans l’état pause en attente de démarrage du flux. Lorsque le client place le flux dans l’état de lecture, l’infrastructure ACX appellera tous les objets ACXSTREAM associés au flux afin d’indiquer que l’état du flux est en lecture. L’ACXPIN sera ensuite placé dans l’état Play, à quel moment les données commenceront à circuler. 

Rendu des données audio

Une fois le flux créé et les ressources allouées, l’application appelle Démarrer sur le flux pour démarrer la lecture. Il convient de noter qu’une application doit appeler GetBuffer/ReleaseBuffer avant de démarrer le flux afin de garantir que le premier paquet qui jouera immédiatement aura des données audio valides. 

Le client commence par le pré-déploiement d’une mémoire tampon. Lorsque le client appelle ReleaseBuffer, cela entraîne un appel dans AudioKSE qui fait appel à la couche ACX, laquelle appelle EvtAcxStreamSetRenderPacket sur l'ACXSTREAM actif. La propriété inclut l’index de paquet (basé sur 0) et, le cas échéant, un indicateur EOS avec le décalage d’octet de la fin du flux dans le paquet actif.    Une fois le circuit de diffusion en continu terminé avec un paquet, il déclenche la notification de mise en mémoire tampon complète qui libère les clients en attente de remplir le paquet suivant avec des données audio de rendu. 

Le mode de diffusion piloté par le minuteur est pris en charge et est indiqué par l'utilisation d'une valeur de PacketCount égale à 1 lors de l'appel du rappel EvtAcxStreamAllocateRtPackets du pilote.

Capture de données audio

Une fois le flux créé et les ressources allouées, l’application appelle Démarrer sur le flux pour démarrer la lecture. 

Lorsque le flux est en cours d’exécution, le circuit source remplit le paquet de capture avec des données audio. Une fois le premier paquet rempli, le circuit source libère le paquet dans l’infrastructure ACX. À ce stade, l’infrastructure ACX signale l’événement de notification de flux. 

Une fois la notification de flux signalée, le client peut envoyer KSPROPERTY_RTAUDIO_GETREADPACKET pour obtenir l’index (basé sur 0) du paquet qui a terminé la capture. Lorsque le client a envoyé GETCAPTUREPACKET, le pilote peut supposer que tous les paquets précédents ont été traités et sont disponibles pour le remplissage. 

Pour la capture en rafale, le circuit source peut libérer un nouveau paquet dans l’infrastructure ACX dès que GETREADPACKET a été appelé.

Le client peut également utiliser KSPROPERTY_RTAUDIO_PACKETVREGISTER pour obtenir un pointeur vers la structure RTAUDIO_PACKETVREGISTER du flux. Cette structure sera mise à jour par l’infrastructure ACX avant la fin du paquet de signalisation.

Comportement de diffusion en continu du noyau KS d'origine

Il peut y avoir des situations, par exemple lorsqu’un pilote implémente la capture en rafale (comme dans une implémentation de détecteur de mots clés), où le comportement de gestion des paquets du flux du noyau hérité doit être utilisé au lieu de PacketVRegister. Pour utiliser le comportement précédent basé sur les paquets, le pilote doit retourner STATUS_NOT_SUPPORTED pour KSPROPERTY_RTAUDIO_PACKETVREGISTER.

L’exemple suivant montre comment procéder dans AcxStreamInitAssignAcxRequestPreprocessCallback pour un ACXSTREAM. Pour plus d’informations, consultez 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);
}

Position du flux

L’infrastructure ACX appelle le rappel EvtAcxStreamGetPresentationPosition pour obtenir la position actuelle du flux. La position actuelle du flux inclut PlayOffset et WriteOffset. 

Le modèle de streaming WaveRT permet au pilote audio d’exposer un registre de position HW au client. Le modèle de diffusion en continu ACX ne prend pas en charge l’exposition de registres HW, car cela empêcherait un rééquilibrage. 

Chaque fois que le circuit de diffusion en continu termine un paquet, il appelle AcxRtStreamNotifyPacketComplete avec l'index de paquet commençant à 0 et la valeur QPC prise aussi près de l'achèvement du paquet que possible (par exemple, la valeur QPC peut être calculée par la routine du service d'interruption). Ces informations sont disponibles pour les clients via KSPROPERTY_RTAUDIO_PACKETVREGISTER, qui retourne un pointeur vers une structure qui contient le CompletedPacketCount, le CompletedPacketQPC et une valeur qui combine les deux (ce qui permet au client de s’assurer que CompletedPacketCount et CompletedPacketQPC proviennent du même paquet).  

Transitions d’état de flux

Une fois qu’un flux a été créé, ACX transfère le flux vers différents états à l’aide des rappels suivants :

  • EvtAcxStreamPrepareHardware va passer du flux de l’état AcxStreamStateStop à l’état AcxStreamStatePause. Le pilote doit réserver du matériel requis tel que les moteurs DMA lorsqu’il reçoit EvtAcxStreamPrepareHardware.
  • EvtAcxStreamRun fait passer le flux de l’état AcxStreamStatePause à l’état AcxStreamStateRun.
  • EvtAcxStreamPause va passer du flux de l’état AcxStreamStateRun à l’état AcxStreamStatePause.
  • EvtAcxStreamReleaseHardware va passer du flux de l’état AcxStreamStatePause à l’état AcxStreamStateStop. Le pilote doit libérer le matériel requis tel que les moteurs DMA lorsqu’il reçoit EvtAcxStreamReleaseHardware.

Le flux peut recevoir le rappel EvtAcxStreamPrepareHardware après avoir reçu le rappel EvtAcxStreamReleaseHardware. Le flux revient à l’état AcxStreamStatePause.

L’allocation de paquets avec EvtAcxStreamAllocateRtPackets se produit normalement avant le premier appel à EvtAcxStreamPrepareHardware. Les paquets alloués seront normalement libérés avec EvtAcxStreamFreeRtPackets après le dernier appel à EvtAcxStreamReleaseHardware. Cette commande n’est pas garantie.

L’état AcxStreamStateAcquire n’est pas utilisé. ACX supprime la nécessité pour le pilote d’avoir l’état d’acquisition, car cet état est implicite avec les rappels de préparation du matériel (EvtAcxStreamPrepareHardware) et de libération du matériel (EvtAcxStreamReleaseHardware).

Prise en charge des flux de mémoire tampon volumineux et du moteur de déchargement

ACX utilise l’élément ACXAUDIOENGINE pour désigner un ACXPIN qui gérera la création du flux de déchargement et les différents éléments requis pour le volume de flux de déchargement, la sourdine, et l’état du mètre de crête. Il est similaire à un nœud de moteur audio existant dans les pilotes WaveRT.

Processus de fermeture de flux

Lorsque le client ferme le flux, le pilote reçoit EvtAcxStreamPause et EvtAcxStreamReleaseHardware avant la suppression de l’objet ACXSTREAM par ACX Framework. Le pilote peut fournir l'entrée standard WDF EvtCleanupCallback dans la structure WDF_OBJECT_ATTRIBUTES lors de l'appel d'AcxStreamCreate pour effectuer le nettoyage final pour l'ACXSTREAM. WDF appelle EvtCleanupCallback lorsque l’infrastructure tente de supprimer l’objet. N’utilisez pas EvtDestroyCallback, qui est appelé une seule fois que toutes les références à l’objet ont été libérées, ce qui est indéterminé.

Le pilote doit nettoyer les ressources de mémoire système associées à l’objet ACXSTREAM dans EvtCleanupCallback, si les ressources n’ont pas déjà été nettoyées dans EvtAcxStreamReleaseHardware.

Il est important que le pilote ne nettoie pas les ressources qui prennent en charge le flux, jusqu’à ce que le client le demande.

L’état AcxStreamStateAcquire n’est pas utilisé. ACX supprime la nécessité pour le pilote d’avoir l’état d’acquisition, car cet état est implicite avec les rappels de préparation du matériel (EvtAcxStreamPrepareHardware) et de libération du matériel (EvtAcxStreamReleaseHardware).

Suppression et invalidation des évènements inattendus dans le flux

Si le pilote détermine que le flux n’est pas valide (par exemple, le jack est déconnecté), le circuit arrête tous les flux. 

Nettoyage de mémoire de flux

L’élimination des ressources du flux peut être effectuée dans le nettoyage du contexte de flux du pilote (pas détruire). Ne mettez jamais à disposition tout ce qui est partagé dans le contexte d’un objet détruit le rappel. Cette aide s’applique à tous les objets ACX.

Le rappel de destruction est appelé après la dernière référence, lorsqu’il est inconnu.

En général, le callback de nettoyage est appelé lorsque le handle du flux est fermé. L’une des exceptions est que le pilote a créé le flux dans son rappel. Si ACX n’a pas pu ajouter ce flux à son pont de flux juste avant de retourner à partir de l’opération de création de flux, le flux est annulé asynchrone et le thread actuel retourne une erreur au client create-stream. Le flux ne doit pas avoir d’allocations mem allouées à ce stade. Pour plus d’informations, consultez EVT_ACX_STREAM_RELEASE_HARDWARE callback.

Séquence de nettoyage de la mémoire de flux

La mémoire tampon de flux est une ressource système et elle doit être libérée uniquement lorsque le client en mode utilisateur ferme le handle du flux. La mémoire tampon (différente des ressources matérielles de l’appareil) a la même durée de vie que le handle du flux. Lorsque le client ferme le handle, ACX invoque le rappel de nettoyage des objets de flux, puis le rappel de suppression de l'objet de flux lorsque la référence de l’objet atteint zéro.

Il est possible que ACX reporte la suppression d'un objet de flux à un élément de travail lorsque le pilote a créé un objet de flux, puis qu'il a échoué à exécuter le rappel de création de flux. Pour empêcher un interblocage avec un thread WDF en cours d'arrêt, ACX diffère la suppression vers un autre thread. Pour éviter les effets secondaires possibles de ce comportement (libération différée des ressources), le pilote peut libérer les ressources de flux allouées avant de renvoyer une erreur lors de la création de flux.

Le pilote doit libérer les mémoires tampons audio lorsque ACX appelle le rappel EVT_ACX_STREAM_FREE_RTPACKETS. Ce rappel est appelé lorsque l'utilisateur ferme les descripteurs de flux.

Étant donné que les mémoires tampons RT sont mappées en mode utilisateur, la durée de vie de la mémoire tampon est identique à la durée de vie du handle. Le pilote ne doit pas tenter de libérer les mémoires tampons audio avant que ACX appelle cette fonction de rappel.

Le rappel EVT_ACX_STREAM_FREE_RTPACKETS doit être appelé après le rappel EVT_ACX_STREAM_RELEASE_HARDWARE et doit se terminer avant le rappel EvtDeviceReleaseHardware.

Ce rappel peut se produire après que le pilote a traité le rappel matériel WDF, car le client en mode utilisateur peut conserver ses handles pendant longtemps. Le pilote ne doit pas tenter d’attendre que ces handles disparaissent, cela entraînera simplement une vérification de bogue 0x9f DRIVER_POWER_STATE_FAILURE. Pour plus d’informations, consultez la fonction de rappel EVT_WDF_DEVICE_RELEASE_HARDWARE.

Ce code EvtDeviceReleaseHardware de l’exemple de pilote ACX montre un exemple d’appel d’AcxDeviceRemoveCircuit , puis de libérer la mémoire 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();

En résumé :

libération du matériel de l'appareil wdf -> libération des ressources matérielles de l'appareil

AcxStreamFreeRtPackets -> libérer la mémoire tampon audio associée à un descripteur

Pour plus d’informations sur la gestion des objets WDF et des circuits, consultez ACX WDF Driver Lifetime Management.

DDIs de diffusion en continu

Structures de diffusion en continu

structure ACX_RTPACKET

Cette structure représente un paquet alloué unique. PacketBuffer peut être un handle WDFMEMORY, un MDL ou une mémoire tampon. Il a une fonction d’initialisation associée, ACX_RTPACKET_INIT.

ACX_STREAM_CALLBACKS

Cette structure identifie les rappels de pilote pour la diffusion en continu vers l’infrastructure ACX. Cette structure fait partie de la structure ACX_PIN_CONFIG.

Rappels de streaming

EvtAcxStreamAllocateRtPackets

L’événement EvtAcxStreamAllocateRtPackets indique au pilote d’allouer rtPackets pour la diffusion en continu. AcxRtStream reçoit PacketCount = 2 pour la diffusion en continu pilotée par les événements ou PacketCount = 1 pour la diffusion en continu basée sur le minuteur. Si le pilote utilise une mémoire tampon unique pour les deux paquets, le second RtPacketBuffer doit avoir un WDF_MEMORY_DESCRIPTOR avec Type = WdfMemoryDescriptorTypeInvalid avec un RtPacketOffset qui s’aligne sur la fin du premier paquet (paquet[2]. RtPacketOffset = packet[1]. RtPacketOffset+packet[1]. RtPacketSize).

EvtAcxStreamFreeRtPackets

L’événement EvtAcxStreamFreeRtPackets indique au pilote de libérer les RtPackets qui ont été alloués dans un appel précédent à EvtAcxStreamAllocateRtPackets. Les mêmes paquets de cet appel sont inclus.

EvtAcxStreamGetHwLatency

L’événement EvtAcxStreamGetHwLatency indique au pilote de fournir une latence de flux pour le circuit spécifique de ce flux (la latence globale sera une somme de la latence des différents circuits). La valeur FifoSize est en octets et le délai est en unités de 100 nanosecondes.

EvtAcxStreamSetRenderPacket

L’événement EvtAcxStreamSetRenderPacket indique au pilote quel paquet vient d’être libéré par le client. S’il n’y a pas de problèmes, ce paquet doit être (CurrentRenderPacket + 1), où CurrentRenderPacket est le paquet à partir duquel le pilote est en streaming.

Les indicateurs peuvent être 0 ou KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM = 0x200, indiquant que le paquet est le dernier paquet du flux, et EosPacketLength est une longueur valide en octets pour le paquet. Pour plus d’informations, consultez OptionsFlags dans KSSTREAM_HEADER structure (ks.h).

Le pilote doit continuer à augmenter le CurrentRenderPacket en tant que paquets affichés au lieu de modifier son CurrentRenderPacket pour qu’il corresponde à cette valeur.

EvtAcxStreamGetCurrentPacket

EvtAcxStreamGetCurrentPacket indique au pilote d’indiquer quel paquet (basé sur 0) est actuellement rendu sur le matériel ou est actuellement rempli par le matériel de capture.

EvtAcxStreamGetCapturePacket

EvtAcxStreamGetCapturePacket indique au pilote d’indiquer quel paquet (basé sur 0) a été entièrement rempli récemment, y compris la valeur QPC au moment où le pilote a commencé à remplir le paquet.

EvtAcxStreamGetPresentationPosition

EvtAcxStreamGetPresentationPosition indique au pilote d’indiquer la position actuelle avec la valeur QPC au moment où la position actuelle a été calculée.

ÉVÉNEMENTS D’ÉTAT DE FLUX

L’état de streaming d’un ACXSTREAM est géré par les API suivantes.

EVT_ACX_STREAM_PREPARE_HARDWARE

EVT_ACX_STREAM_RELEASE_HARDWARE

EVT_ACX_STREAM_RUN

EVT_ACX_STREAM_PAUSE

API ACX de streaming

AcxStreamCreate

AcxStreamCreate crée un flux ACX qui peut être utilisé pour contrôler le comportement de diffusion en continu.

AcxRtStreamCreate

AcxRtStreamCreate crée un flux ACX qui peut être utilisé pour contrôler le comportement de diffusion en continu et gérer l’allocation de paquets et communiquer l’état de diffusion en continu.

AcxRtStreamNotifyPacketComplete

Le pilote appelle cette API ACX lorsqu’un paquet est terminé. Le temps d’achèvement des paquets et l’index de paquets basés sur 0 sont inclus pour améliorer les performances du client. L’infrastructure ACX définit tous les événements de notification associés au flux.

Voir aussi

Vue d’ensemble des extensions de classe audio ACX

Documentation de référence relative à l’interface de ligne de commande

Résumé des objets ACX