Communications entre pilotes multi-piles ACX
Cette rubrique fournit un résumé des communications entre pilotes multiplateformes de la classe audio (ACX).
Pour obtenir des informations générales sur ACX, consultez la vue d’ensemble des extensions de classe audio ACX et résumé des objets ACX.
Pour obtenir des informations de base sur les cibles ACX, consultez les cibles ACX et la synchronisation des pilotes et les adresses IP de paquets de demandes d’E/S ACX.
Remarque
Les en-têtes et bibliothèques ACX ne sont pas inclus dans la version WDK 10.0.22621.2428 (publiée le 24 octobre 2023), mais sont disponibles dans les versions précédentes, ainsi que les dernières versions (25000) Insider Preview de wdK. Pour plus d’informations sur les préversions de WDK, consultez Installation des versions préliminaires du Kit de pilotes Windows (WDK).
Pilotes audio à pile unique
Les pilotes de classe audio PortCls et KS hérités prennent uniquement en charge les pilotes audio « à pile unique ». Le framework audio hérité communique et interface avec un seul pilote miniport. Il incombe au pilote miniport de gérer la communication et la synchronisation avec d’autres piles de pilotes si nécessaire.
ACX prend entièrement en charge les pilotes audio à pile unique. Les développeurs audio peuvent remplacer leur pilote portcls et KS miniport actuel par un pilote ACX tout en conservant le même comportement par rapport à d’autres piles. Bien que si le sous-système audio utilise des piles multi-audio, une meilleure approche serait utilisée pour utiliser la prise en charge des multi-pile dans ACX et permettre à ACX de synchroniser toutes ces piles ensemble, comme décrit dans la section suivante de cette rubrique.
Pilotes audio multi stack - composantisation
Il est très courant que le chemin audio passe par plusieurs composants matériels gérés par différentes piles de pilotes pour créer une expérience audio complète. Il est courant qu’un système dispose des fonctionnalités DSP, CODEC et AMP implémentées par différents fournisseurs de technologies audio, comme illustré dans le diagramme suivant.
Dans une architecture multi-pile sans norme bien définie, chaque fournisseur est obligé de définir son propre protocole d’interface et de communication propriétaire. Il s’agit d’un objectif d’ACX pour faciliter le développement de pilotes audio multi-pile en prenant possession de la synchronisation entre ces piles et en fournissant un modèle simple réutilisable pour les pilotes communiquer entre eux.
À l’aide d’ACX, l’exemple de conception matérielle DSP, CODEC et AMP système peut être pris en charge avec l’architecture logicielle suivante.
Notez que tout type de composant au lieu du DSP, codec et AMP affiché peut être utilisé, car ACX ne dépend pas d’un type de composant spécifique ou d’arrangements spécifiques de composants.
Les pilotes tiers communiquent entre eux via ACX avec un protocole bien défini. L’un des avantages de cette approche est qu’une pile unique peut être remplacée par une autre d’un autre fournisseur sans nécessiter de modifications apportées aux piles logicielles adjacentes. L’un des principaux objectifs de l’infrastructure ACX (Audio Class Extensions) est de simplifier l’effort nécessaire pour développer multi-pile pilotes audio assemblés à partir de composants provenant de différents fournisseurs.
ACX cible l’exemple de communication - Circuit
Cet exemple de code montre l’utilisation d’AcxTargetCircuit et AcxTargetCircuitGetWdfIoTarget pour communiquer avec un circuit distant exposé par une autre pile. Pour plus d’informations sur les circuits ACX, consultez acxcircuit.h.
Cet agrégateur assez complexe localise les circuits, puis crée un ioTarget à l’aide d’AcxTargetCircuitGetWdfIoTarget. Il définit ensuite les options d’envoi WDF personnalisées et envoie de manière asynchrone la requête. Enfin, il case activée l’état de l’envoi pour confirmer que la demande a été envoyée.
NTSTATUS
Aggregator_SendModuleCommand(
_In_ PAGGREGATOR_RENDER_CIRCUIT_CONTEXT CircuitCtx,
_In_ ACX_REQUEST_PARAMETERS Params,
_Out_ ULONG_PTR * OutSize
)
{
NTSTATUS status = STATUS_NOT_SUPPORTED;
PKSAUDIOMODULE_PROPERTY moduleProperty = nullptr;
ULONG aggregationDeviceIndex = 0;
PLIST_ENTRY ple;
*OutSize = 0;
moduleProperty = CONTAINING_RECORD(Params.Parameters.Property.Control, KSAUDIOMODULE_PROPERTY, ClassId);;
aggregationDeviceIndex = AUDIOMODULE_GET_AGGDEVICEID(moduleProperty->InstanceId);
ple = CircuitCtx->AggregatorCircuit->AggregatorEndpoint->AudioPaths[aggregationDeviceIndex]->TargetCircuitList.Flink;
while (ple != &CircuitCtx->AggregatorCircuit->AggregatorEndpoint->AudioPaths[aggregationDeviceIndex]->TargetCircuitList)
{
PAUDIO_CIRCUIT circuit = (PAUDIO_CIRCUIT)CONTAINING_RECORD(ple, AUDIO_CIRCUIT, ListEntry);
if (circuit->Modules)
{
for(ULONG i = 0; i < circuit->Modules->Count; i++)
{
PACX_AUDIOMODULE_DESCRIPTOR descriptor = ((PACX_AUDIOMODULE_DESCRIPTOR)(circuit->Modules + 1) + i);
// we've identified which aggregation device this call is targeting,
// now locate which circuit implements this module. Within an aggregated device,
// the module class id + instance id must uniquely identify a module. There should
// never be duplicates.
if (IsEqualGUIDAligned(descriptor->ClassId, moduleProperty->ClassId) &&
descriptor->InstanceId == moduleProperty->InstanceId)
{
WDFREQUEST request = NULL;
WDF_REQUEST_SEND_OPTIONS sendOptions;
WDF_OBJECT_ATTRIBUTES attributes;
WDFIOTARGET ioTarget;
// We've now identified which aggregated device this call is targeting.
// The cached module information contains the ID adjusted with the aggregation device
// index. remove the aggregation device index before forwarding the call to the aggregated circuit.
moduleProperty->InstanceId = AUDIOMODULE_GET_INSTANCEID(moduleProperty->InstanceId);
ioTarget = AcxTargetCircuitGetWdfIoTarget(circuit->AcxTargetCircuit);
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = CircuitCtx->AggregatorCircuit->Circuit;
status = WdfRequestCreate(&attributes, ioTarget, &request);
if (!NT_SUCCESS(status))
{
goto exit;
}
status = AcxTargetCircuitFormatRequestForProperty(circuit->AcxTargetCircuit, request, &Params);
if (!NT_SUCCESS(status))
{
goto exit;
}
WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS);
WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(REQUEST_TIMEOUT_SECONDS));
// Whether WdfRequestSend succeeds or fails, we return the status & information, so
// there's no need to inspect the result.
WdfRequestSend(request, ioTarget, &sendOptions);
status = WdfRequestGetStatus(request);
*OutSize = WdfRequestGetInformation(request);
WdfObjectDelete(request);
goto exit;
}
}
}
ple = ple->Flink;
}
status = STATUS_SUCCESS;
exit:
return status;
}
ACX cible l’exemple de communication - Épingler
Cet exemple de code montre l’utilisation d’AcxTargetPin pour communiquer avec la broche d’un circuit distant exposée par une autre pile. Pour plus d’informations sur le code pin ACX, consultez acxpin.h.
Il sélectionne les derniers éléments volume et muet qui sont tous les deux présents dans le même circuit dans le chemin d’accès du point de terminaison.
NTSTATUS FindDownstreamVolumeMute(
_In_ ACXCIRCUIT Circuit,
_In_ ACXTARGETCIRCUIT TargetCircuit
)
{
NTSTATUS status;
PDSP_CIRCUIT_CONTEXT circuitCtx;
ACX_REQUEST_PARAMETERS params;
WDF_REQUEST_SEND_OPTIONS sendOptions;
WDF_OBJECT_ATTRIBUTES attributes;
WDF_REQUEST_REUSE_PARAMS reuseParams;
circuitCtx = GetDspCircuitContext(Circuit);
//
// Note on behavior: This search algorithm will select the last Volume and Mute elements that are both
// present in the same circuit in the Endpoint Path.
// This logic could be updated to select the last Volume and Mute elements, or the first or last
// Volume or the first or last Mute element.
//
//
// First look through target's pins to determine if there's another circuit downstream.
// If there is, we'll look at that circuit for volume/mute.
//
for (ULONG pinIndex = 0; pinIndex < AcxTargetCircuitGetPinsCount(TargetCircuit); ++pinIndex)
{
ACXTARGETPIN targetPin = AcxTargetCircuitGetTargetPin(TargetCircuit, pinIndex);
ULONG targetPinFlow = 0;
ACX_REQUEST_PARAMETERS_INIT_PROPERTY(¶ms,
KSPROPSETID_Pin,
KSPROPERTY_PIN_DATAFLOW,
AcxPropertyVerbGet,
AcxItemTypePin,
AcxTargetPinGetId(targetPin),
nullptr, 0,
&targetPinFlow,
sizeof(targetPinFlow));
RETURN_NTSTATUS_IF_FAILED(SendProperty(targetPin, ¶ms, nullptr));
//
// Searching for the downstream pins. For Render, these are the dataflow out pins
//
if (circuitCtx->IsRenderCircuit && targetPinFlow != KSPIN_DATAFLOW_OUT)
{
continue;
}
else if (!circuitCtx->IsRenderCircuit && targetPinFlow != KSPIN_DATAFLOW_IN)
{
continue;
}
// Get the target pin's physical connection. We'll do this twice: first to get size and allocate, second to get the connection
PKSPIN_PHYSICALCONNECTION pinConnection = nullptr;
auto connection_free = scope_exit([&pinConnection]()
{
if (pinConnection)
{
ExFreePool(pinConnection);
pinConnection = nullptr;
}
});
ULONG pinConnectionSize = 0;
ULONG_PTR info = 0;
for (ULONG i = 0; i < 2; ++i)
{
ACX_REQUEST_PARAMETERS_INIT_PROPERTY(¶ms,
KSPROPSETID_Pin,
KSPROPERTY_PIN_PHYSICALCONNECTION,
AcxPropertyVerbGet,
AcxItemTypePin,
AcxTargetPinGetId(targetPin),
nullptr, 0,
pinConnection,
pinConnectionSize);
status = SendProperty(targetPin, ¶ms, &info);
if (status == STATUS_BUFFER_OVERFLOW)
{
// Pin connection already allocated, so how did this fail?
RETURN_NTSTATUS_IF_TRUE(pinConnection != nullptr, status);
pinConnectionSize = (ULONG)info;
pinConnection = (PKSPIN_PHYSICALCONNECTION)ExAllocatePool2(POOL_FLAG_NON_PAGED, pinConnectionSize, DRIVER_TAG);
// RETURN_NTSTATUS_IF_NULL_ALLOC causes compile errors
RETURN_NTSTATUS_IF_TRUE(pinConnection == nullptr, STATUS_INSUFFICIENT_RESOURCES);
}
else if (!NT_SUCCESS(status))
{
// There are no more connected circuits. Continue with processing this circuit.
break;
}
}
if (!NT_SUCCESS(status))
{
// There are no more connected circuits. Continue handling this circuit.
break;
}
ACXTARGETCIRCUIT nextTargetCircuit;
RETURN_NTSTATUS_IF_FAILED(CreateTargetCircuit(Circuit, pinConnection, pinConnectionSize, &nextTargetCircuit));
auto circuit_free = scope_exit([&nextTargetCircuit]()
{
if (nextTargetCircuit)
{
WdfObjectDelete(nextTargetCircuit);
nextTargetCircuit = nullptr;
}
});
RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED(FindDownstreamVolumeMute(Circuit, nextTargetCircuit), STATUS_NOT_FOUND);
if (circuitCtx->TargetVolumeMuteCircuit == nextTargetCircuit)
{
// The nextTargetCircuit is the owner of the volume/mute target elements.
// We will delete it when the pin is disconnected.
circuit_free.release();
// We found volume/mute. Return.
return STATUS_SUCCESS;
}
// There's only one downstream pin on the current targetcircuit, and we just processed it.
break;
}
//
// Search the target circuit for a volume or mute element.
// This sample code doesn't support downstream audioengine elements.
//
for (ULONG elementIndex = 0; elementIndex < AcxTargetCircuitGetElementsCount(TargetCircuit); ++elementIndex)
{
ACXTARGETELEMENT targetElement = AcxTargetCircuitGetTargetElement(TargetCircuit, elementIndex);
GUID elementType = AcxTargetElementGetType(targetElement);
if (IsEqualGUID(elementType, KSNODETYPE_VOLUME) &&
circuitCtx->TargetVolumeHandler == nullptr)
{
// Found Volume
circuitCtx->TargetVolumeHandler = targetElement;
}
if (IsEqualGUID(elementType, KSNODETYPE_MUTE) &&
circuitCtx->TargetMuteHandler == nullptr)
{
// Found Mute
circuitCtx->TargetMuteHandler = targetElement;
}
}
if (circuitCtx->TargetVolumeHandler && circuitCtx->TargetMuteHandler)
{
circuitCtx->TargetVolumeMuteCircuit = TargetCircuit;
return STATUS_SUCCESS;
}
//
// If we only found one of volume or mute, keep searching for both
//
if (circuitCtx->TargetVolumeHandler || circuitCtx->TargetMuteHandler)
{
circuitCtx->TargetMuteHandler = circuitCtx->TargetVolumeHandler = nullptr;
}
return STATUS_NOT_FOUND;
}
ACX cible l’exemple de communication - Stream
Cet exemple de code montre l’utilisation d’AcxTargetStream pour communiquer avec le flux d’un circuit distant. Pour plus d’informations sur acX Flux, consultez acxstreams.h.
NTSTATUS status;
PRENDER_DEVICE_CONTEXT devCtx;
WDF_OBJECT_ATTRIBUTES attributes;
ACXSTREAM stream;
STREAM_CONTEXT * streamCtx;
ACXELEMENT elements[2] = {0};
ACX_ELEMENT_CONFIG elementCfg;
ELEMENT_CONTEXT * elementCtx;
ACX_STREAM_CALLBACKS streamCallbacks;
ACX_RT_STREAM_CALLBACKS rtCallbacks;
CRenderStreamEngine * streamEngine = NULL;
PAGED_CODE();
UNREFERENCED_PARAMETER(Pin);
UNREFERENCED_PARAMETER(SignalProcessingMode);
UNREFERENCED_PARAMETER(VarArguments);
// This unit-test added support for RAW and DEFAULT.
ASSERT(IsEqualGUID(*SignalProcessingMode, AUDIO_SIGNALPROCESSINGMODE_RAW) ||
IsEqualGUID(*SignalProcessingMode, AUDIO_SIGNALPROCESSINGMODE_DEFAULT));
devCtx = GetRenderDeviceContext(Device);
ASSERT(devCtx != NULL);
//
// Init streaming callbacks.
//
ACX_STREAM_CALLBACKS_INIT(&streamCallbacks);
streamCallbacks.EvtAcxStreamPrepareHardware = EvtStreamPrepareHardware;
streamCallbacks.EvtAcxStreamReleaseHardware = EvtStreamReleaseHardware;
streamCallbacks.EvtAcxStreamRun = EvtStreamRun;
streamCallbacks.EvtAcxStreamPause = EvtStreamPause;
streamCallbacks.EvtAcxStreamAssignDrmContentId = EvtStreamAssignDrmContentId;
status = AcxStreamInitAssignAcxStreamCallbacks(StreamInit, &streamCallbacks);
if (!NT_SUCCESS(status))
{
ASSERT(FALSE);
goto exit;
}
//
// Init RT streaming callbacks.
//
ACX_RT_STREAM_CALLBACKS_INIT(&rtCallbacks);
rtCallbacks.EvtAcxStreamGetHwLatency = EvtStreamGetHwLatency;
rtCallbacks.EvtAcxStreamAllocateRtPackets = EvtStreamAllocateRtPackets;
rtCallbacks.EvtAcxStreamFreeRtPackets = EvtStreamFreeRtPackets;
rtCallbacks.EvtAcxStreamSetRenderPacket = R_EvtStreamSetRenderPacket;
rtCallbacks.EvtAcxStreamGetCurrentPacket = EvtStreamGetCurrentPacket;
rtCallbacks.EvtAcxStreamGetPresentationPosition = EvtStreamGetPresentationPosition;
status = AcxStreamInitAssignAcxRtStreamCallbacks(StreamInit, &rtCallbacks);
if (!NT_SUCCESS(status))
{
ASSERT(FALSE);
goto exit;
}
//
// Create the stream.
//
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, STREAM_CONTEXT);
attributes.EvtCleanupCallback = EvtStreamCleanup;
attributes.EvtDestroyCallback = EvtStreamDestroy;
status = AcxRtStreamCreate(Device, Circuit, &attributes, &StreamInit, &stream);
if (!NT_SUCCESS(status))
{
ASSERT(FALSE);
goto exit;
}
// START-TESTING: inverted create-stream sequence.
{
ACXSTREAMBRIDGE bridge = NULL;
ACXPIN bridgePin = NULL;
ACXTARGETSTREAM targetStream = NULL;
ACX_STREAM_BRIDGE_CONFIG bridgeCfg;
ACX_STREAM_BRIDGE_CONFIG_INIT(&bridgeCfg);
bridgeCfg.InModesCount = 0; // no in-modes. this stream-bridge is manually managed.
bridgeCfg.InModes = NULL;
bridgeCfg.OutMode = NULL; // no mode, i.e., default (1st) and raw (2nd).
bridgeCfg.Flags |= AcxStreamBridgeInvertChangeStateSequence;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = WdfGetDriver(); // bridge is deleted by driver obj in case of error.
status = AcxStreamBridgeCreate(Circuit, &attributes, &bridgeCfg, &bridge);
if (!NT_SUCCESS(status))
{
ASSERT(FALSE);
goto exit;
}
...
status = AcxStreamBridgeAddStream(bridge, stream);
if (!NT_SUCCESS(status))
{
ASSERT(FALSE);
goto exit;
}
// Get the Target Stream
targetStream = AcxStreamBridgeGetTargetStream(bridge, stream);
if (targetStream == NULL)
{
ASSERT(FALSE);
goto exit;
}
ACX cible l’exemple de communication - Élément
Cet exemple de code montre l’utilisation d’AcxTargetElement pour communiquer avec l’élément d’un circuit. Pour plus d’informations sur les cibles ACX, consultez acxtargets.h.
_In_ ACXCIRCUIT Circuit,
_In_ ACXTARGETCIRCUIT TargetCircuit
...
//
// Search the target circuit for a volume or mute element.
// This sample code doesn't support downstream audioengine elements.
//
for (ULONG elementIndex = 0; elementIndex < AcxTargetCircuitGetElementsCount(TargetCircuit); ++elementIndex)
{
ACXTARGETELEMENT targetElement = AcxTargetCircuitGetTargetElement(TargetCircuit, elementIndex);
GUID elementType = AcxTargetElementGetType(targetElement);
if (IsEqualGUID(elementType, KSNODETYPE_VOLUME) &&
circuitCtx->TargetVolumeHandler == nullptr)
{
// Found Volume
circuitCtx->TargetVolumeHandler = targetElement;
}
if (IsEqualGUID(elementType, KSNODETYPE_MUTE) &&
circuitCtx->TargetMuteHandler == nullptr)
{
// Found Mute
circuitCtx->TargetMuteHandler = targetElement;
}
}
Voir aussi
Vue d’ensemble des extensions de classe audio ACX
Documentation de référence ACX
IRP de demande de paquets d’E/S ACX
Commentaires
https://aka.ms/ContentUserFeedback.
Bientôt disponible : Tout au long de 2024, nous allons supprimer progressivement GitHub Issues comme mécanisme de commentaires pour le contenu et le remplacer par un nouveau système de commentaires. Pour plus d’informations, consultezEnvoyer et afficher des commentaires pour