Partager via


Streaming ACX

Cette rubrique couvre le streaming ACX et la mise en mémoire tampon associée, qui sont essentiels pour une expérience audio sans interruption. Elle 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 une liste des termes audio ACX courants et une introduction à ACX, veuillez consulter la section vue d’ensemble des extensions de classe audio ACX.

Types de streaming ACX

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

Le cadre ACX prend en charge deux types de flux. Le premier type de flux, le RT Packet Stream, permet d’allouer des paquets RT et d’utiliser des 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 Basic Stream, ne prend en charge que les 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 RT Packet Stream. 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 streaming et crée un RT Packet Stream ; les circuits connectés créeront des Basic Streams pour recevoir les événements liés aux transitions d’état de flux.

Pour plus d’informations, veuillez consulter la section ACX Stream dans Résumé des objets ACX. Les DDIs pour le flux sont définis dans l’en-tête acxstreams.h.

Pile de communications de streaming ACX

Il existe deux types de communications pour le streaming ACX. Un chemin de communication est utilisé pour contrôler le comportement de streaming, pour des commandes telles que Start, Create et Allocate, qui utiliseront les communications standard ACX. Le cadre ACX utilise des files d’attente IO et transmet les requêtes WDF en utilisant les files d’attente. Le comportement de la file d’attente est caché du code réel du pilote par l’utilisation de callbacks Evt et de 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 le signalement de streaming audio. Cela implique de dire au pilote quand un paquet est prêt et de recevoir des données sur le moment où le pilote a fini de traiter un paquet. 

Les principales exigences pour le signalement de streaming :

  • Prise en charge de la lecture sans interruption
    • Faible latence
    • 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 streaming, ACX utilise des événements avec une mémoire tampon partagée et des appels IRP directs. Ces éléments sont décrits ci-après.

Mémoire tampon partagée

Pour communiquer du pilote vers le 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 de scruter, et que le client peut déterminer tout ce dont il a besoin pour continuer le streaming tout en réduisant ou en éliminant le besoin d’appels IRP directs.

Le pilote de l’appareil utilise une mémoire tampon partagée pour communiquer au client quel paquet est en cours de rendu ou de capture. Cette mémoire tampon partagée inclut le nombre de paquets (à partir de 1) du dernier paquet terminé ainsi que la valeur QPC (QueryPerformanceCounter) du temps de complétion. Pour le pilote de l’appareil, il doit indiquer cette information en appelant AcxRtStreamNotifyPacketComplete. Lorsque le pilote de l’appareil appelle AcxRtStreamNotifyPacketComplete, le cadre ACX mettra à jour la mémoire tampon partagée avec le nouveau nombre de paquets et le QPC, et signalera 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 vers le pilote, des appels IRP directs sont utilisés. Cela réduit les complexités autour de l’assurance que les requêtes WDF sont traitées en temps opportun et a prouvé son efficacité 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 l’appareil. Ces requêtes appelleront les gestionnaires d’événements EvtAcxStreamGetCurrentPacket et EvtAcxStreamSetRenderPacket du pilote de l’appareil. Le client peut également demander le paquet de capture actuel, ce qui appellera le gestionnaire d’événements EvtAcxStreamGetCapturePacket du pilote de l’appareil.

Similitudes 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 l’achèvement de la mémoire tampon est communiquée dans PortCls. Les IRP sont très similaires et la mémoire tampon partagée introduit la capacité pour le pilote de communiquer directement le nombre de paquets et le timing sans dépendre des IRP.   Les pilotes devront s’assurer de ne rien faire qui nécessite l’accès à des verrous également utilisés dans les chemins de contrôle du flux : ceci est nécessaire pour éviter les interruptions. 

Prise en charge de grandes mémoires tampons pour une lecture à faible consommation

Pour réduire la quantité de puissance consommée lors de la lecture de contenu multimédia, il est important de réduire le temps que l’APU passe dans un état de haute puissance. Étant donné que la lecture audio normale utilise des mémoires tampons de 10 ms, l’APU doit toujours être active. Pour donner à l’APU le temps dont il a besoin pour réduire son état, les pilotes ACX sont autorisés à annoncer la prise en charge de mémoires tampons significativement plus grandes, dans la plage de 1 à 2 secondes. Cela signifie que l’APU peut se réveiller une fois toutes les 1 à 2 secondes, effectuer les opérations nécessaires à la vitesse maximale pour préparer la mémoire tampon des 1 à 2 secondes suivantes, puis passer à l’état de consommation d’énergie le plus bas jusqu’à ce que la mémoire tampon suivante soit nécessaire. 

Dans les modèles de streaming existants, la lecture à faible consommation est prise en charge via la lecture en décharge. Un pilote audio annonce la prise en charge de la lecture en décharge en exposant un nœud AudioEngine sur le filtre d’onde pour un point de terminaison. Le nœud AudioEngine fournit un moyen de contrôler le moteur DSP que le pilote utilise pour rendre l’audio à partir des grandes mémoires tampons avec le traitement souhaité.

Le nœud AudioEngine offre les fonctionnalités suivantes :

  • Description du moteur audio, qui indique à la pile audio quelles broches sur le filtre d’onde fournissent une prise en charge de la décharge et du bouclage (et une prise en charge de la lecture hôte). 
  • Plage de taille de mémoire tampon, qui indique à la pile audio les tailles minimales et maximales de mémoire tampon pouvant être prises en charge pour la lecture en décharge. Lecture. La plage de taille de mémoire tampon peut changer dynamiquement en fonction de l’activité du système. 
  • Prise en charge du format, y compris les formats pris en charge, le format de mixage actuel de l’appareil et le format de l’appareil. 
  • Volume, y compris la prise en charge du ramping, car avec les grandes mémoires tampons, le volume logiciel ne sera pas réactif.
  • Protection contre le bouclage, qui indique au pilote de couper la broche de bouclage de l’AudioEngine si un ou plusieurs des flux déchargés contiennent du contenu protégé. 
  • État global des FX, pour activer ou désactiver les GFX sur l’AudioEngine. 

Lorsque qu’un flux est créé sur la broche de décharge, le flux prend en charge le volume, les FX locaux et la protection contre le bouclage. 

Lecture à faible consommation avec ACX

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

Création de flux déchargés

Le pilote ajoutera également un élément ACXAUDIOENGINE aux flux créés pour la décharge afin de permettre le contrôle du volume, de la coupure du son et du compteur de crête.

Diagramme de streaming

Ce diagramme montre un pilote ACX multi-pile.

Diagramme illustrant les boîtes DSP, CODEC et AMP avec une interface de streaming noyau au-dessus.

Chaque pilote ACX contrôle une partie distincte du matériel audio et pourrait être fourni par un fournisseur différent. ACX fournit une interface de streaming noyau compatible pour permettre aux applications de fonctionner telles quelles.

Broches de flux

Chaque ACXCIRCUIT a au moins une broche de Sink et une broche de Source. Ces broches sont utilisées par le cadre 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 tout flux créé à partir du circuit. Pour un circuit de capture, la broche Sink est utilisée pour contrôler le comportement de capture de tout flux créé à partir du circuit.   ACXPIN est l’objet utilisé pour contrôler le streaming dans le chemin audio. Le circuit de streaming ACX est responsable de créer les objets ACXPIN appropriés pour le chemin audio du point de terminaison lors de la création du circuit et d’enregistrer les ACXPIN avec ACX. Le circuit ACX ne doit créer que la ou les broches de rendu ou de capture pour le circuit ; le cadre ACX créera l’autre broche nécessaire pour se connecter et communiquer avec le circuit.   

Circuit de streaming

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

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

Le circuit de streaming doit utiliser AcxRtStreamCreate pour créer un RT Packet Stream en réponse à EvtAcxCircuitCreateStream. L’ACXSTREAM créé avec AcxRtStreamCreate permettra au pilote de circuit de streaming d’allouer la mémoire tampon utilisée pour le streaming 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 Basic Stream en réponse à EvtAcxCircuitCreateStream. Les objets ACXSTREAM créés avec AcxStreamCreate par les circuits suivants permettront aux pilotes de configurer le matériel en réponse aux changements d’état du flux tels que Pause ou Run.

Le circuit de 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éera un objet ACXSTREAM représentant l’instance de flux du circuit. Le cadre ACX relie les objets ACXSTREAM ensemble (de la même manière que les objets ACXCIRCUIT sont reliés). 

Circuits en amont et en aval

La création de flux commence au niveau du circuit de streaming et est transférée à chaque circuit en aval dans l’ordre de connexion des circuits. Les connexions sont établies entre les broches de pont créées avec Communication égal à 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, en commençant par le circuit de streaming, la broche de pont AcxPinTypeSource se connectera au circuit en aval suivant. Le circuit final aura une broche de point de terminaison décrivant le matériel de point de terminaison audio (comme si le point de terminaison est un microphone ou un haut-parleur et si la prise est branchée).

Pour chaque circuit suivant le circuit de streaming, la broche de pont AcxPinTypeSink se connectera au circuit en amont précédent.

Négociation du format de flux

Le pilote annonce les formats pris en charge pour la création de flux en ajoutant les formats pris en charge par mode à l’ACXPIN utilisé pour la création de flux avec AcxPinAssignModeDataFormatList et AcxPinGetRawDataFormatList. Pour les points de terminaison multi-circuits, un ACXSTREAMBRIDGE peut être utilisé pour coordonner la prise en charge des modes et des 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, le cadre ACX créera un ACXSTREAMBRIDGE entre chaque circuit dans un point de terminaison multi-circuits. Le ACXSTREAMBRIDGE par défaut utilisera 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 au circuit en aval. Si la broche de pont du circuit en amont n’a pas de formats, le format de flux original sera utilisé. Si la broche connectée du circuit en aval ne prend pas en charge le format utilisé, la création du flux échouera.

Si un circuit de l’appareil effectue un changement de format de flux, le pilote de l’appareil 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 EvtAcxCircuitCreateStream de chaque circuit. ACX commencera par le circuit principal et appellera EvtAcxCircuitCreateStream de chaque circuit dans l’ordre, en terminant par le circuit final. L’ordre peut être inversé en spécifiant le drapeau AcxStreamBridgeInvertChangeStateSequence (défini dans ACX_STREAM_BRIDGE_CONFIG_FLAGS) pour le pont de flux. Après que tous les circuits ont créé un objet de flux, les objets de flux géreront la logique de streaming.

La demande de création de flux est envoyée à la broche appropriée générée dans le cadre de la génération de topologie du circuit principal en appelant 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, assignant AcxStreamCallbacks et AcxRtStreamCallbacks.
  • Il crée l’objet ACXSTREAM en utilisant AcxRtStreamCreate.
  • Il crée tous les éléments spécifiques au flux (par exemple, ACXVOLUME ou ACXAUDIOENGINE)
  • Il ajoute les éléments à l’objet ACXSTREAM
  • Il retourne l’objet ACXSTREAM créé au cadre ACX

ACX transfère ensuite la création du flux au circuit en aval suivant

  • Il met à jour la structure ACXSTREAM_INIT, assignant AcxStreamCallbacks
  • Il crée l’objet ACXSTREAM en utilisant AcxStreamCreate
  • Il crée tous les éléments spécifiques au flux
  • Il ajoute les éléments à l’objet ACXSTREAM
  • Il retourne l’objet ACXSTREAM créé au cadre ACX

Le canal de communication entre les circuits dans un chemin audio utilise des objets ACXTARGETSTREAM. Dans cet exemple, chaque circuit aura accès à une file d’attente IO pour le circuit devant lui et le circuit derrière lui dans le chemin audio du point de terminaison. De plus, un chemin audio de point de terminaison est linéaire et bidirectionnel. La gestion réelle de la file d’attente IO est effectuée par le cadre 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 le circuit de streaming et a fourni un gestionnaire EvtAcxPinCreateStream. Le circuit DSP fonctionne également comme un circuit de filtrage : en fonction du mode et de la configuration du flux, il peut appliquer un traitement de signal aux données audio. Le circuit CODEC représente le DAC, fournissant la fonctionnalité de récepteurs 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 prise ou d’autres détails matériels du point de terminaison.

  1. AudioKSE appelle NtCreateFile pour créer un flux.
  2. Cela se filtre à travers ACX et se termine par l’appel du gestionnaire EvtAcxPinCreateStream du circuit DSP avec la broche, le format de données (y compris le mode) et les informations de l’appareil. 
  3. Le circuit DSP valide les informations de format de données pour s’assurer qu’il peut 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 à l’ACXSTREAM. 
  6. Le circuit DSP retourne le flux d’exécution au cadre 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 format de données pour confirmer qu’il peut gérer le rendu des données. 
  8. Le circuit CODEC alloue une structure de contexte privé et l’associe à l’ACXSTREAM. 
  9. Le circuit CODEC s’ajoute comme récepteurs de flux à l’ACXSTREAM.
  10. Le circuit CODEC retourne le flux d’exécution au cadre 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 à l’ACXSTREAM. 
  12. Le circuit AMP retourne le flux d’exécution au cadre ACX. À ce stade, la création du flux est terminée. 

Flux de grandes mémoires tampons

Les flux de grandes mémoires tampons sont créés sur l’ACXPIN désigné pour la décharge par l’élément ACXAUDIOENGINE du circuit ACXCIRCUIT.

Pour prendre en charge les flux de décharge, le pilote de l’appareil doit effectuer les actions suivantes lors de la création du circuit de streaming :

  1. Créer les objets ACXPIN pour l’hôte, la décharge et le bouclage et les ajouter au circuit ACXCIRCUIT.
  2. Créer les éléments ACXVOLUME, ACXMUTE et ACXPEAKMETER. Ceux-ci ne seront pas ajoutés directement au circuit ACXCIRCUIT.
  3. Initialiser une structure ACX_AUDIOENGINE_CONFIG, assignant les objets HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement et PeakMeterElement.
  4. Créer 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 de décharge.

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. La broche de rendu ou de capture pour le circuit de streaming reçoit une demande 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 de la mémoire système au lieu de la mémoire de l’appareil mappée dans le système. Le pilote peut utiliser les fonctions WDF existantes pour effectuer l’allocation et renverra 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 comme une seule mémoire tampon, renvoyant 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 renvoyer un pointeur vers une seule mémoire tampon contiguë qui peut s’étendre de la fin de la mémoire tampon au début sans nécessiter que l’application gère l’enroulement 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 de paquets ACX, il n’y a que deux paquets alloués au début. Le mapping de la mémoire virtuelle client restera valide sans changement pendant toute la durée du flux une fois que l’allocation et le mapping auront été effectués. Il y a un événement associé au flux pour indiquer l’achèvement du paquet pour les deux paquets. Il y aura également une mémoire tampon partagée que le cadre ACX utilisera pour communiquer quel paquet a terminé avec l’événement.  

Taille des paquets de flux de grandes mémoires tampons

Lorsqu’il expose la prise en charge de grandes mémoires tampons, le pilote fournira également un rappel utilisé pour déterminer les tailles de paquets minimales et maximales pour la lecture de grandes mémoires tampons.   La taille des paquets pour l’allocation de mémoire tampon de flux est déterminée en fonction des valeurs minimales et maximales.

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

Spécification des contraintes de mémoire tampon ACX

Pour spécifier les contraintes de mémoire tampon ACX, les pilotes ACX peuvent utiliser les propriétés de réglage 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 du 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 tard, dans la fonction EvtCircuitCompositeCircuitInitialize, la fonction d’assistance AddPropertyToCircuitInterface est utilisée pour ajouter le tableau des propriétés de l’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é d’interface de l’appareil : les valeurs de propriétés audio KS sur l’interface audio pour le circuit 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;
}

Changements d’état du flux

Lorsqu’un changement d’état de flux se produit, chaque objet de flux dans le chemin audio du point de terminaison pour le flux recevra un événement de notification du cadre ACX. L’ordre dans lequel cela se produit dépend du changement d’état et du flux du flux.

  • Pour les flux de rendu passant d’un état moins actif à un état plus actif, le circuit de streaming (qui a enregistré le SINK) recevra l’événement en premier. Une fois qu’il a traité l’événement, le circuit suivant dans le chemin audio du point de terminaison recevra l’événement.

  • Pour les flux de rendu passant d’un état plus actif à un état moins actif, le circuit de streaming recevra l’événement en dernier. 

  • Pour les flux de capture passant d’un état moins actif à un état plus actif, le circuit de streaming recevra l’événement en dernier. 

  • Pour les flux de capture passant d’un état plus actif à un état moins actif, le circuit de streaming recevra l’événement en premier. 

L’ordre ci-dessus est celui fourni par défaut par le cadre 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 streaming.  

Streaming des données audio

Une fois le flux créé et les tampons appropriés alloués, le flux est à l’état Pause en attente du démarrage du flux. Lorsque le client passe le flux à l’état Play, le cadre ACX appellera tous les objets ACXSTREAM associés au flux pour indiquer que l’état du flux est en lecture. L’ACXPIN sera alors placé en état de lecture, à ce moment-là, les données commenceront à circuler. 

Rendu des données audio

Une fois le flux créé et les ressources allouées, l’application appellera Start sur le flux pour démarrer la lecture. Notez qu’une application doit appeler GetBuffer/ReleaseBuffer avant de démarrer le flux pour s’assurer que le premier paquet qui commencera à jouer immédiatement contiendra des données audio valides. 

Le client commence par pré-remplir un tampon. Lorsque le client appelle ReleaseBuffer, cela se traduit par un appel dans AudioKSE qui appellera la couche ACX, laquelle appellera EvtAcxStreamSetRenderPacket sur l’ACXSTREAM actif. La propriété inclura l’index du paquet (à partir de 0) et, le cas échéant, un drapeau EOS avec le décalage en octets de la fin du flux dans le paquet actuel.    Après que le circuit de streaming a terminé avec un paquet, il déclenchera la notification de tampon complet qui libérera les clients en attente de remplir le prochain paquet avec les données audio de rendu. 

Le mode de streaming à base de temporisateur est pris en charge et est indiqué en utilisant une valeur de PacketCount de 1 lors de l’appel du rappel EvtAcxStreamAllocateRtPackets du pilote.

Capture des données audio

Une fois le flux créé et les ressources allouées, l’application appellera Start 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 au cadre ACX. À ce moment, le cadre ACX signale l’événement de notification de flux. 

Une fois que la notification de flux a été signalée, le client peut envoyer KSPROPERTY_RTAUDIO_GETREADPACKET pour obtenir l’index (à partir de 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 par rafale, le circuit source peut libérer un nouveau paquet au cadre 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 pour le flux. Cette structure sera mise à jour par le cadre ACX avant de signaler la fin du paquet.

Comportement de streaming du noyau KS hérité

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

L’exemple suivant montre comment faire cela dans le AcxStreamInitAssignAcxRequestPreprocessCallback pour un ACXSTREAM. Pour plus d’informations, veuillez consulter la section 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

Le cadre ACX appellera le rappel EvtAcxStreamGetPresentationPosition pour obtenir la position actuelle du flux. La position actuelle du flux inclura le PlayOffset et le WriteOffset. 

Le modèle de streaming WaveRT permet au pilote audio d’exposer un registre de position matérielle (HW Position) au client. Le modèle de streaming ACX ne prendra pas en charge l’exposition de registres matériels car cela empêcherait un rééquilibrage de se produire. 

Chaque fois que le circuit de streaming termine un paquet, il appelle AcxRtStreamNotifyPacketComplete avec l’index du paquet à partir de 0 et la valeur QPC prise aussi près que possible de la fin du paquet (à titre d’exemple, la valeur QPC peut être calculée par la routine de service d’interruption). Cette information est disponible pour les clients via KSPROPERTY_RTAUDIO_PACKETVREGISTER, qui renvoie un pointeur vers une structure contenant le CompletedPacketCount, le CompletedPacketQPC et une valeur qui combine les deux (ce qui permet au client de s’assurer que le CompletedPacketCount et le CompletedPacketQPC proviennent du même paquet).  

Transitions d’état du flux

Après la création d’un flux, ACX fera passer le flux à différents états en utilisant les rappels suivants :

  • EvtAcxStreamPrepareHardware fera passer le flux de l’état AcxStreamStateStop à l’état AcxStreamStatePause. Le pilote doit réserver le matériel requis tel que les moteurs DMA lorsqu’il reçoit EvtAcxStreamPrepareHardware.
  • EvtAcxStreamRun fera passer le flux de l’état AcxStreamStatePause à l’état AcxStreamStateRun.
  • EvtAcxStreamRun fera passer le flux de l’état AcxStreamStateRun à l’état AcxStreamStatePause.
  • EvtAcxStreamPrepareHardware fera passer le 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. Cela fera repasser le flux à l’état AcxStreamStatePause.

L’allocation de paquets avec EvtAcxStreamAllocateRtPackets se produira normalement avant le premier appel à EvtAcxStreamPrepareHardware. Les paquets alloués seront normalement libérés avec EvtAcxStreamFreeRtPackets après le dernier appel à EvtAcxStreamReleaseHardware. Cet ordre n’est pas garanti.

L’état AcxStreamStateAcquire n’est pas utilisé. ACX supprime le besoin 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).

Flux de grandes mémoires tampons et prise en charge du moteur de déchargement

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

Processus de fermeture du flux

Lorsque le client ferme le flux, le pilote recevra EvtAcxStreamPause et EvtAcxStreamReleaseHardware avant que l’objet ACXSTREAM ne soit supprimé par le cadre ACX. Le pilote peut fournir l’entrée standard WDF EvtCleanupCallback dans la structure WDF_OBJECT_ATTRIBUTES lors de l’appel à AcxStreamCreate pour effectuer le nettoyage final de l’ACXSTREAM. WDF appellera EvtCleanupCallback lorsque le cadre tentera de supprimer l’objet. Ne pas utiliser EvtDestroyCallback, qui n’est appelé qu’une 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, sauf si cela est demandé par le client.

L’état AcxStreamStateAcquire n’est pas utilisé. ACX supprime le besoin 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 surprise du flux et invalidation

Si le pilote détermine que le flux est devenu invalide (par exemple, si la prise est débranchée), le circuit arrêtera tous les flux. 

Nettoyage de la mémoire du flux

L’élimination des ressources du flux peut être effectuée dans le nettoyage du contexte du flux du pilote (pas la destruction). Ne jamais mettre l’élimination de quoi que ce soit de partagé dans le rappel de destruction du contexte d’un objet. Cette directive s’applique à tous les objets ACX.

Le rappel de destruction est invoqué après la disparition de la dernière référence, à un moment inconnu.

En général, le rappel de nettoyage du flux est appelé lorsque la poignée est fermée. Une exception à cela est lorsque le pilote a créé le flux dans son rappel. Si ACX a échoué à ajouter ce flux à son pont de flux juste avant de revenir de l’opération de création de flux, le flux est annulé de manière asynchrone, et le thread actuel renvoie une erreur au client de création de flux. Le flux ne devrait avoir aucune allocation de mémoire à ce point. Pour plus d’informations, veuillez consulter la section EVT_ACX_STREAM_RELEASE_HARDWARE callback.

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

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

Il est possible pour ACX de différer la suppression d’un objet STREAM à un élément de travail lorsque le pilote a créé un objet de flux et qu’il a ensuite échoué au rappel de création de flux. Pour éviter un deadlock avec un thread WDF d’arrêt, ACX reporte la suppression à un autre thread. Pour éviter tout effet secondaire possible 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 de la création de flux.

Le pilote doit libérer les tampons audio lorsque ACX invoque le rappel EVT_ACX_STREAM_FREE_RTPACKETS. Ce rappel est appelé lorsque l’utilisateur ferme les poignées du flux.

Parce que les tampons RT sont mappés en mode utilisateur, la durée de vie du tampon est la même que celle de la poignée. Le pilote ne doit pas essayer de libérer/vider les tampons audio avant qu’ACX n’invoque ce rappel.

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

Ce rappel peut se produire après que le pilote a traité le rappel de libération de matériel WDF, car le client en mode utilisateur peut conserver ses poignées pendant longtemps. Le pilote ne doit pas essayer d’attendre que ces poignées disparaissent, cela créera juste un contrôle de bogue 0x9f DRIVER_POWER_STATE_FAILURE. Veuillez consulter la fonction de rappel EVT_WDF_DEVICE_RELEASE_HARDWARE pour plus d’informations.

Ce code EvtDeviceReleaseHardware provenant de l’exemple de pilote ACX montre un exemple d’appel à AcxDeviceRemoveCircuit puis de libération de la mémoire matérielle 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é :

wdf device release hardware :> libérer les ressources matérielles de l’appareil

AcxStreamFreeRtPackets :> libérer/vider la mémoire tampon audio associée à la poignée

Pour plus d’informations sur la gestion des objets WDF et de circuit, veuillez consulter la section ACX WDF Driver Lifetime Management.

DDIs de streaming

Structures de streaming

Structure ACX_RTPACKET

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

ACX_STREAM_CALLBACKS

Cette structure identifie les rappels de pilote pour le streaming au cadre ACX. Cette structure fait partie de la ACX_PIN_CONFIG structure.

Rappels de streaming

EvtAcxStreamAllocateRtPackets

Le EvtAcxStreamAllocateRtPackets événement indique au pilote d’allouer des RtPackets pour le streaming. Un AcxRtStream recevra PacketCount = 2 pour le streaming piloté par événement ou PacketCount = 1 pour le streaming basé sur un temporisateur. Si le pilote utilise un seul tampon pour les deux paquets, le second RtPacketBuffer doit avoir un WDF_MEMORY_DESCRIPTOR avec Type = WdfMemoryDescriptorTypeInvalid avec un RtPacketOffset qui s’aligne avec la fin du premier paquet (packet[2].RtPacketOffset = packet[1].RtPacketOffset+packet[1].RtPacketSize).

EvtAcxStreamFreeRtPackets

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

EvtAcxStreamGetHwLatency

Le EvtAcxStreamGetHwLatency événement indique au pilote de fournir la latence du flux pour le circuit spécifique de ce flux (la latence globale sera une somme des latences des différents circuits). Le FifoSize est en octets et le Delay est en unités de 100 nanosecondes.

EvtAcxStreamSetRenderPacket

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

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 en savoir plus, consultez OptionsFlags dans structure KSSTREAM_HEADER (ks.h).

Le pilote doit continuer à augmenter le CurrentRenderPacket à mesure que les paquets sont rendus au lieu de changer son CurrentRenderPacket pour correspondre à cette valeur.

EvtAcxStreamGetCurrentPacket

Le EvtAcxStreamGetCurrentPacket indique au pilote quel paquet (à partir de 0) est actuellement rendu au matériel ou est actuellement rempli par le matériel de capture.

EvtAcxStreamGetCapturePacket

Le EvtAcxStreamGetCapturePacket indique au pilote quel paquet (à partir de 0) a été complètement rempli le plus récemment, y compris la valeur QPC au moment où le pilote a commencé à remplir le paquet.

EvtAcxStreamGetPresentationPosition

Le EvtAcxStreamGetPresentationPosition indique au pilote la position actuelle ainsi que la valeur QPC au moment où la position actuelle a été calculée.

ÉVÉNEMENTS D’ÉTAT DU FLUX

L’état de streaming pour 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 de streaming ACX

AcxStreamCreate

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

AcxRtStreamCreate

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

AcxRtStreamNotifyPacketComplete

Le pilote appelle cette API ACX lorsqu’un paquet est terminé. Le temps de fin du paquet et l’index du paquet à partir de 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 de l’ACX

Résumé des objets ACX