Обмен данными между драйверами с несколькими стеками ACX
В этом разделе представлена сводка по обмену данными между драйверами для аудиоклассов eXtensions (ACX).
Общие сведения о расширениях аудиоклассов ACX см. в обзоре и сводке объектов ACX.
Основные сведения о целевых объектах ACX см. в статьях о целевых объектах ACX и синхронизации драйверов и синхронизации пакетов запросов операций ввода-вывода ACX.
Драйверы звука с одним стеком
Устаревшие драйверы аудиоклассов PortCls и KS поддерживают только драйверы звука с одним стеком. Устаревшая звуковая платформа взаимодействует и взаимодействует только с одним мини-драйвером. Это до мини-драйвера для управления взаимодействием и синхронизацией с другими стеками драйверов при необходимости.
ACX полностью поддерживает драйверы аудио с одним стеком. Разработчики аудио могут заменить текущие портклы и минипорт-драйвер KS драйвером на основе ACX, сохраняя такое же поведение в связи с другими стеками. Хотя если подсистема звука использует стеки с несколькими звуками, лучше использовать многоуровневый стек поддержку в ACX и позволить ACX синхронизировать все эти стеки вместе, как описано в следующем разделе этого раздела.
Драйверы аудио с несколькими стеками — компонентизация
Это очень распространено для звукового пути для прохождения нескольких аппаратных компонентов, обрабатываемых различными стеками драйверов, чтобы создать полный звуковой интерфейс. Обычно система имеет функции DSP, CODEC и AMP, реализованные различными поставщиками звуковых технологий, как показано на следующей схеме.
В архитектуре многоуровневый стек без четко определенного стандарта каждый поставщик вынужден определять собственный собственный собственный интерфейс и протокол связи. Это цель ACX для упрощения разработки многоуровневый стек звуковых драйверов путем владения синхронизацией между этими стеками и предоставления простого повторного использования шаблона для общения драйверов друг с другом.
С помощью ACX пример системы DSP, CODEC и аппаратного оборудования AMP можно поддерживать со следующей архитектурой программного обеспечения.
Обратите внимание, что любой тип типа компонента вместо показанного DSP, CODEC и AMP может использоваться, так как ACX не зависит от какого-либо конкретного типа компонента или конкретных договоренностей компонентов.
Сторонние драйверы взаимодействуют друг с другом через ACX с хорошо определенным протоколом. Одним из преимуществ этого подхода является то, что один стек можно заменить другим поставщиком, не требуя изменений в смежных стеках программного обеспечения. Одной из основных целей платформы расширений аудиоклассов (ACX) является упрощение усилий, необходимых для разработки многоуровневый стек звуковых драйверов, собранных из компонентов от разных поставщиков.
Пример связи с целевыми объектами ACX — канал
В этом примере кода показано использование AcxTargetCircuit и AcxTargetCircuitGetWdfIoTarget для взаимодействия с удаленным каналом, предоставляемым другим стеком. Дополнительные сведения о каналах ACX см . в acxcircuit.h.
Этот довольно сложный агрегат находит каналы, а затем создает ioTarget с помощью AcxTargetCircuitGetWdfIoTarget. Затем он задает настраиваемые параметры отправки WDF и асинхронно отправляет запрос. Наконец, он проверка состояние отправки для подтверждения отправки запроса.
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 — закрепление
В этом примере кода показано использование AcxTargetPin для взаимодействия с закреплением удаленного канала, предоставляемым другим стеком. Дополнительные сведения о пин-коде ACX см . в статье acxpin.h.
Он выбирает последние элементы тома и выключения, которые присутствуют в одном канале в пути конечной точки.
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 — Stream
В этом примере кода показано использование AcxTargetStream для взаимодействия с потоком удаленного канала. Дополнительные сведения о acX Потоки см. в статье 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 — элемент
В этом примере кода показано использование AcxTargetElement для взаимодействия с элементом канала. Дополнительные сведения о целевых объектах ACX см . в статье 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;
}
}
См. также
Общие сведения о расширениях аудиоклассов ACX
Справочная документация по ACX
Поставщики запросов операций ввода-вывода ACX