Comunicações entre drivers de pilha múltipla ACX
Este tópico fornece um resumo das comunicações entre drivers de pilha múltipla ACX (Audio Class eXtensions).
Para obter informações gerais sobre o ACX, consulte Visão geral das extensões de classe de áudio ACX e Resumo dos objetos ACX.
Para obter informações básicas sobre destinos ACX, consulte Destinos e sincronização de driver do ACX e IRPs de pacote de solicitação de E/S ACX.
Drivers de áudio de pilha única
Os drivers de classe de áudio PortCls e KS herdados suportam apenas drivers de áudio de "pilha única". A estrutura de áudio herdada se comunica e faz interface com apenas um driver de miniporta. Cabe ao driver de miniporta gerenciar a comunicação e a sincronização com outras pilhas de drivers quando necessário.
O ACX suporta totalmente drivers de áudio de pilha única. Os desenvolvedores de áudio podem substituir seus drivers de miniporta Portcls e KS atuais por um driver baseado em ACX, mantendo o mesmo comportamento em relação a outras pilhas. Embora o subsistema de áudio use pilhas de áudio múltiplo, uma abordagem melhor seria usar o suporte a pilha múltipla no ACX e permitir que o ACX sincronize todas essas pilhas juntas, conforme descrito na próxima seção deste tópico.
Drivers de áudio de pilha múltipla - componentização
É muito comum que o caminho de áudio passe por vários componentes de hardware manipulados por diferentes pilhas de drivers para criar uma experiência de áudio completa. É típico de um sistema ter a funcionalidade DSP, CODEC e AMP implementada por diferentes fornecedores de tecnologia de áudio, conforme mostrado no diagrama a seguir.
Em uma arquitetura de pilha múltipla sem um padrão bem definido, cada fornecedor é forçado a definir sua própria interface proprietária e protocolo de comunicação. É um objetivo do ACX facilitar o desenvolvimento de drivers de áudio de pilha múltipla assumindo a propriedade da sincronização entre essas pilhas e fornecendo um padrão simples reutilizável para os drivers se comunicarem uns com os outros.
Usando o ACX, o exemplo de projeto de hardware DSP, CODEC e AMP do sistema pode ser suportado com a seguinte arquitetura de software.
Observe que qualquer tipo de componente, em vez do DSP, CODEC e AMP mostrados, pode ser usado, pois o ACX não depende de nenhum tipo de componente específico ou de arranjos específicos de componentes.
Drivers de terceiros se comunicam entre si via ACX com um protocolo bem definido. Uma vantagem dessa abordagem é que uma única pilha pode ser substituída por outra de um fornecedor diferente sem exigir alterações nas pilhas de software adjacentes. Um dos principais objetivos da estrutura de extensões de classe de áudio (ACX) é simplificar o esforço necessário para desenvolver drivers de áudio de pilha múltipla montados com base em componentes de diferentes fornecedores.
Exemplo de comunicação de destinos ACX - Circuito
Esse código de exemplo mostra o uso de AcxTargetCircuit e AcxTargetCircuitGetWdfIoTarget para se comunicar com um circuito remoto exposto por uma pilha diferente. Para obter mais informações sobre Circuitos ACX, consulte acxcircuit.h.
Esse agregador bastante complexo localiza circuitos e, em seguida, cria um ioTarget usando AcxTargetCircuitGetWdfIoTarget. Em seguida, ele define opções de envio do WDF personalizadas e envia a solicitação de forma assíncrona. Por fim, ele verifica o status do envio para confirmar que a solicitação foi enviada.
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;
}
Exemplo de comunicação de destinos ACX - Pino
Esse código de exemplo mostra o uso de AcxTargetPin para se comunicar com o pino de um circuito remoto exposto por uma pilha diferente. Para obter mais informações sobre o pino do ACX, consulte acxpin.h.
Ele seleciona os últimos elementos Volume e Mute que estão presentes no mesmo circuito no caminho do ponto de extremidade.
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;
}
Exemplo de comunicação de destinos ACX - Fluxo
Esse código de exemplo mostra o uso de AcxTargetStream para se comunicar com o fluxo de um circuito remoto. Para obter mais informações sobre fluxos do ACX, consulte 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;
}
Exemplo de comunicação de destinos ACX - Elemento
Esse código de exemplo mostra o uso de AcxTargetElement para se comunicar com o elemento de um circuito. Para obter mais informações sobre destinos ACX, consulte 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;
}
}
Confira também
Visão geral de extensões de classe de áudio ACX
Documentação de referência da ACX
IRPs de pacote de solicitação de E/S ACX