Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Niniejszy temat zawiera podsumowanie komunikacji międzywarstwowej w sterownikach wielostackowych rozbudowanych klas audio (ACX).
Aby uzyskać ogólne informacje na temat acX, zobacz AcX audio class extensions overview and Summary of ACX Objects ( Omówienie rozszerzeń klasy audio ACX ) i Summary of ACX Objects (Podsumowanie obiektów ACX).
Aby uzyskać podstawowe informacje na temat obiektów docelowych ACX, zobacz Obiekty docelowe ACX i synchronizacja sterowników oraz IRP żądań We/Wy pakietów ACX.
sterowniki audio Single-Stack
Starsze sterowniki klasy PortCls i KS obsługują tylko sterowniki audio jednowarstwowe. Starsza struktura audio komunikuje się i obsługuje tylko jeden sterownik miniportu. Do sterownika miniportu należy zarządzanie komunikacją i synchronizacją z innymi stosami sterowników, gdy jest to konieczne.
ACX w pełni obsługuje jednowarstowe sterowniki audio. Deweloperzy audio mogą zastąpić bieżące sterowniki Portcls i sterownik miniportu KS sterownikiem opartym na ACX, zachowując takie samo działanie w odniesieniu do innych stosów. Chociaż, jeśli podsystem audio używa wielostackowych architektur audio, lepszym podejściem byłoby użycie wsparcia wielostackowego w ACX i pozwolenie ACX zsynchronizować wszystkie te stosy razem, jak opisano w następnej części tego tematu.
Sterowniki audio z wieloma stosami — modularyzacja
Ścieżka audio bardzo często przechodzi przez wiele składników sprzętowych obsługiwanych przez różne stosy sterowników, aby stworzyć kompletne doświadczenie audio. Zazwyczaj system ma funkcje DSP, CODEC i AMP zaimplementowane przez różnych dostawców technologii audio, jak pokazano na poniższym diagramie.
W architekturze wielostosowej bez dobrze zdefiniowanego standardu każdy dostawca musi zdefiniować własny własny interfejs i protokół komunikacyjny. Celem ACX jest ułatwienie rozwoju sterowników audio z wieloma stosami poprzez zarządzanie synchronizacją między tymi stosami i zapewnienie prostego wzorca wielokrotnego użytku, aby sterowniki mogły się ze sobą komunikować.
Korzystając z ACX, można wspierać sprzętowy projekt przykładowego systemu DSP, CODEC i AMP, używając następującej architektury oprogramowania.
Należy pamiętać, że można użyć dowolnego typu składnika zamiast pokazanego typu DSP, CODEC i AMP, ponieważ ACX nie zależy od żadnego określonego typu składnika ani konkretnych ustaleń składników.
Zewnętrzne sterowniki komunikują się ze sobą za pośrednictwem ACX z dobrze zdefiniowanym protokołem. Jedną z zalet tego podejścia jest to, że jeden stos może zostać zastąpiony innym od innego dostawcy bez konieczności wprowadzania zmian w sąsiednich stosach oprogramowania. Jednym z głównych celów architektury rozszerzeń klasy audio (ACX) jest uproszczenie wysiłku wymaganego do opracowania sterowników audio dla wielu stosów, składających się z komponentów od różnych dostawców.
Przykład komunikacji docelowej ACX — obwód
W tym przykładowym kodzie pokazano użycie modułów AcxTargetCircuit i AcxTargetCircuitGetWdfIoTarget w celu komunikowania się z obwodem zdalnym udostępnianym przez inny stos. Aby uzyskać więcej informacji na temat obwodów ACX, zobacz acxcircuit.h.
Ten dość złożony agregator lokalizuje obwody, a następnie tworzy "ioTarget" przy użyciu AcxTargetCircuitGetWdfIoTarget. Następnie ustawia niestandardowe opcje wysyłania WDF i asynchronicznie wysyła żądanie. Na koniec sprawdza stan wysyłania, aby potwierdzić wysłanie żądania.
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;
}
Przykład komunikacji ACX — Pin
W tym przykładowym kodzie pokazano użycie AcxTargetPin do komunikacji z pinem zdalnego obwodu udostępnionym przez inny stos. Aby uzyskać więcej informacji na temat numeru PIN ACX, zobacz acxpin.h.
Wybiera ostatnie elementy głośności i wyciszenia, które są obecne w tym samym obwodzie na ścieżce punktu końcowego.
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;
}
Przykład komunikacji celowanej ACX — Stream
Ten przykładowy kod przedstawia użycie elementu AcxTargetStream do komunikowania się ze strumieniem obwodu zdalnego. Aby uzyskać więcej informacji na temat strumieni ACX, zobacz 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;
}
Przykład komunikacji ACX — Element
Ten przykładowy kod przedstawia użycie elementu AcxTargetElement do komunikowania się z elementem obwodu. Aby uzyskać więcej informacji na temat obiektów docelowych ACX, zobacz 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;
}
}
Zobacz także
Omówienie rozszerzeń klasy audio ACX
dokumentacja referencyjna ACX
cele ACX i synchronizacja sterowników
dokumentacja referencyjna ACX