summaryrefslogtreecommitdiff
path: root/audio/SoundWire/Samples/SdcaVad/SdcaVDsp
diff options
context:
space:
mode:
Diffstat (limited to 'audio/SoundWire/Samples/SdcaVad/SdcaVDsp')
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp1715
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.h297
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AudioModule.cpp384
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AudioModule.h216
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.cpp1467
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.h135
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.cpp1053
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.h237
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionClock.h38
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.cpp140
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.h76
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj373
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters21
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx79
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVDsp.inx159
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.cpp106
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.h51
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.cpp329
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.h93
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/Trace.h34
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/WaveReader.cpp772
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/WaveReader.h196
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/capture.cpp1465
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/circuitstream.cpp820
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/device.cpp1631
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/driver.cpp172
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp606
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.h172
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/private.h803
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/render.cpp2730
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/renderAudioEngine.cpp502
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/resources.rc12
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/savedata.cpp1043
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/savedata.h257
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/streamengine.cpp1053
-rw-r--r--audio/SoundWire/Samples/SdcaVad/SdcaVDsp/streamengine.h391
36 files changed, 19628 insertions, 0 deletions
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp
new file mode 100644
index 00000000..6a223c30
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp
@@ -0,0 +1,1715 @@
+/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ AcpiReader.cpp
+
+Abstract:
+
+ Implements Acpi reader module.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+
+#include <stdunk.h>
+
+#include "AcpiReader.h"
+
+#ifndef __INTELLISENSE__
+#include "AcpiReader.tmh"
+#endif
+
+namespace ACPIREADER
+{
+ RECORDER_LOG AcpiReader::s_AcpiReaderLog { nullptr };
+ ULONG AcpiReader::s_MemoryTag { 0 };
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::_CreateAndInitialize(_In_ WDFDEVICE Device, _In_ RECORDER_LOG Log, _In_ ULONG MemoryTag)
+ {
+ NTSTATUS status = STATUS_SUCCESS;
+ WDF_OBJECT_ATTRIBUTES attributes;
+ AcpiReader * This{ nullptr };
+ VOID * contextAddress;
+
+ PAGED_CODE();
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, AcpiReader);
+ attributes.EvtDestroyCallback = EvtContextDestroy;
+
+ status = WdfObjectAllocateContext(Device, &attributes, &contextAddress);
+ if (!NT_SUCCESS(status))
+ {
+ goto exit;
+ }
+
+ s_AcpiReaderLog = Log;
+ s_MemoryTag = MemoryTag;
+
+ This = new (contextAddress) AcpiReader(Device);
+
+ exit:
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::ParseGuid(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_writes_bytes_(BufferLength) PVOID Buffer,
+ _In_ ULONG BufferLength
+ )
+ /*++
+
+ Routine Description:
+
+ This function parses the content of an ACPI method argument into a GUID.
+
+ Arguments:
+
+ Argument - Supplies the ACPI argument to parse.
+
+ Buffer - Supplies a pointer to the buffer to store the GUID.
+
+ BufferLength - Supplies the buffer size in bytes.
+
+ Return Value:
+
+ NTSTATUS
+
+ --*/
+ {
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ if (Argument->Type != ACPI_METHOD_ARGUMENT_BUFFER)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected: %lu, Actual: %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_BUFFER, Argument->Type, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ if (BufferLength < sizeof(GUID))
+ {
+ status = STATUS_BUFFER_TOO_SMALL;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Buffer too small. Expected: %lu, Actual: %lu, %!STATUS!", sizeof(GUID), BufferLength, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ if (Argument->DataLength != sizeof(GUID))
+ {
+ status = STATUS_ACPI_INVALID_ARGTYPE;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument DataLength. Expected: %lu, Actual: %lu, %!STATUS!", sizeof(GUID), Argument->DataLength, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ RtlCopyMemory((PUCHAR)Buffer, Argument->Data, Argument->DataLength);
+
+ exit:
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::ParseULongLong(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_ PULONGLONG Value
+ )
+ /*++
+ Routine Description:
+
+ This function parses the content of an ACPI method argument into a ULONGLONG.
+
+ Arguments:
+
+ Argument - Supplies the ACPI argument to parse.
+
+ Value - Supplies a pointer to the buffer to store the ULONGLONG value.
+
+ Return Value:
+
+ NTSTATUS
+
+ --*/
+ {
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected: %lu, Actual: %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_INTEGER, Argument->Type, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ if (Argument->DataLength != sizeof(ULONGLONG))
+ {
+ status = STATUS_ACPI_INVALID_ARGTYPE;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument DataLength. Expected: %lu, Actual: %lu, %!STATUS!", sizeof(ULONGLONG), Argument->DataLength, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ RtlCopyMemory(Value, Argument->Data, Argument->DataLength);
+
+ exit:
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::ParseULong(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_ PULONG Value
+ )
+ /*++
+ Routine Description:
+
+ This function parses the content of an ACPI method argument into a ULONG.
+
+ Arguments:
+
+ Argument - Supplies the ACPI argument to parse.
+
+ Value - Supplies a pointer to the buffer to store the ULONG value.
+
+ Return Value:
+
+ NTSTATUS
+
+ --*/
+ {
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected: %lu, Actual: %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_INTEGER, Argument->Type, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ // Even though we are looking for a ULONG value, the DataLength will be set to ULONGLONG since we always use IOCTL_ACPI_EVAL_METHOD_EX
+ if (Argument->DataLength != sizeof(ULONGLONG))
+ {
+ status = STATUS_ACPI_INVALID_ARGTYPE;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument DataLength. Expected: %lu, Actual: %lu, %!STATUS!", sizeof(ULONGLONG), Argument->DataLength, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ *Value = (ULONG)Argument->Argument;
+
+ exit:
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::ParseString(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_writes_opt_z_(ValueStringSize) char * ValueString,
+ _In_ ULONG ValueStringSize,
+ _Out_ PULONG PropertyValueSize
+ )
+ /*++
+ Routine Description:
+
+ This function parses the content of an ACPI method argument into a string.
+
+ Arguments:
+
+ Argument - Supplies the ACPI argument to parse.
+
+ ValueString - Buffer that will hold property value if found.
+
+ ValueStringSize - Size of the output buffer.
+
+ PropertyValueSize - Actual length of the property value.
+
+ Return Value:
+
+ NTSTATUS
+
+ --*/
+ {
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (Argument->Type != ACPI_METHOD_ARGUMENT_STRING)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected %lu, Actual %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_STRING, Argument->Type, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ *PropertyValueSize = Argument->DataLength;
+
+ if (ValueStringSize == 0)
+ {
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto exit;
+ }
+ else if (ValueStringSize < Argument->DataLength)
+ {
+ status = STATUS_BUFFER_TOO_SMALL;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Output buffer too small. Required %lu, Actual %lu, %!STATUS!", Argument->DataLength, ValueStringSize, status);
+ goto exit;
+ }
+#pragma prefast(suppress:__WARNING_PRECONDITION_NULLTERMINATION_VIOLATION, "ACPI driver returns a NULL-terminated string.")
+ status = RtlStringCbCopyA(ValueString, ValueStringSize, (char *)Argument->Data);
+
+ exit:
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::ParseBuffer(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_writes_bytes_(ValueBufferSize) PVOID ValueBuffer,
+ _In_ ULONG ValueBufferSize,
+ _Out_ PULONG PropertyValueSize
+ )
+ /*++
+ Routine Description:
+
+ This function parses the content of an ACPI method argument into a buffer.
+
+ Arguments:
+
+ Argument - Supplies the ACPI argument to parse.
+
+ ValueBuffer - Buffer that will hold property value if found.
+
+ ValueBufferSize - Size of the output buffer.
+
+ PropertyValueSize - Actual length of the property value.
+
+ Return Value:
+
+ NTSTATUS
+
+ --*/
+ {
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (Argument->Type != ACPI_METHOD_ARGUMENT_BUFFER)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected %lu, Actual %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_BUFFER, Argument->Type, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ *PropertyValueSize = Argument->DataLength;
+
+ if (ValueBufferSize < Argument->DataLength)
+ {
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ // DrvLogVerbose if ValueBufferSize is 0, which means it's being called to determine size. Otherwise, DrvLogError.
+ if (ValueBufferSize == 0)
+ {
+ DrvLogVerbose(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Output buffer too small. Required %lu, Actual %lu, %!STATUS!", Argument->DataLength, ValueBufferSize, status);
+ }
+ else
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Output buffer too small. Required %lu, Actual %lu, %!STATUS!", Argument->DataLength, ValueBufferSize, status);
+ }
+
+ goto exit;
+ }
+
+ RtlCopyMemory(ValueBuffer, Argument->Data, ValueBufferSize);
+
+ exit:
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::ParseULongArray(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_writes_(ValueArrayCount) ULONG * ValueArray,
+ _In_ ULONG ValueArrayCount,
+ _Out_ PULONG PropertyValueArrayCount
+ )
+ {
+ NTSTATUS status = STATUS_SUCCESS;
+ PACPI_METHOD_ARGUMENT currentArgument;
+ ULONG argumentIndex;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (Argument->Type != ACPI_METHOD_ARGUMENT_PACKAGE)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected %lu, Actual %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_PACKAGE, Argument->Type, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ // Initialize everything to 0
+ for (ULONG i = 0; i < ValueArrayCount; ++i)
+ {
+ ValueArray[i] = 0;
+ }
+
+ *PropertyValueArrayCount = 0;
+ currentArgument = (PACPI_METHOD_ARGUMENT)Argument->Data;
+
+ for (argumentIndex = 0; (PUCHAR)currentArgument < (PUCHAR)Argument->Data + Argument->DataLength; argumentIndex++)
+ {
+#pragma prefast(suppress:26014, "Incorrect Validation: ACPI driver returns well-formed data that doesn't extend past known length.")
+ if (currentArgument->Type != ACPI_METHOD_ARGUMENT_INTEGER)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected argument in an array, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ (*PropertyValueArrayCount)++;
+ currentArgument = ACPI_METHOD_NEXT_ARGUMENT(currentArgument);
+ }
+
+ if (ValueArrayCount == 0)
+ {
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto exit;
+ }
+ else if (ValueArrayCount < *PropertyValueArrayCount)
+ {
+ status = STATUS_BUFFER_TOO_SMALL;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Output array too small. Required elements %lu, Actual %lu, %!STATUS!", *PropertyValueArrayCount, ValueArrayCount, status);
+ goto exit;
+ }
+
+ currentArgument = (PACPI_METHOD_ARGUMENT)Argument->Data;
+ for (argumentIndex = 0; (PUCHAR)currentArgument < (PUCHAR)Argument->Data + Argument->DataLength && argumentIndex < ValueArrayCount; argumentIndex++)
+ {
+ status = ParseULong(currentArgument, &ValueArray[argumentIndex]);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected argument in an array, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ currentArgument = ACPI_METHOD_NEXT_ARGUMENT(currentArgument);
+ }
+
+ exit:
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::EnumChildren(
+ _Out_ WDFMEMORY * EnumChildrenOutput
+ )
+ /*++
+ Routine Description:
+
+ This function sends IOCTL_ACPI_ENUM_CHILDREN to ACPI to enumerate child devices.
+
+ Arguments:
+
+ EnumChildrenOutput - Supplies a resulting memory object.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+ {
+ NTSTATUS status;
+ WDFMEMORY inputMem{ WDF_NO_HANDLE };
+ PACPI_ENUM_CHILDREN_INPUT_BUFFER inputBuf;
+ size_t inputBufSize;
+ WDF_MEMORY_DESCRIPTOR inputMemDesc;
+ WDFMEMORY outputMem{ WDF_NO_HANDLE };
+ PACPI_ENUM_CHILDREN_OUTPUT_BUFFER outputBuf;
+ size_t outputBufSize;
+ WDF_MEMORY_DESCRIPTOR outputMemDesc;
+ WDF_OBJECT_ATTRIBUTES attr;
+ ULONG attempts;
+ WDFIOTARGET acpiIoTarget;
+ WDF_REQUEST_SEND_OPTIONS sendOptions;
+
+ PAGED_CODE();
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ ASSERT(m_AcpiDevice);
+
+ acpiIoTarget = WdfDeviceGetIoTarget(m_AcpiDevice);
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&attr);
+ attr.ParentObject = m_AcpiDevice;
+
+ inputBufSize = sizeof(*inputBuf);
+ status = WdfMemoryCreate(
+ &attr,
+ NonPagedPoolNx,
+ s_MemoryTag,
+ inputBufSize,
+ &inputMem,
+ (PVOID*)&inputBuf);
+
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! WdfMemoryCreate failed for inputBuf, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ RtlZeroMemory(inputBuf, inputBufSize);
+ inputBuf->Signature = ACPI_ENUM_CHILDREN_INPUT_BUFFER_SIGNATURE;
+ inputBuf->Flags = ENUM_CHILDREN_IMMEDIATE_ONLY;
+
+ WDF_MEMORY_DESCRIPTOR_INIT_HANDLE(&inputMemDesc, inputMem, nullptr);
+
+ //
+ // The initial output buffer allows one child only. It will be re-allocated
+ // with the returning "NumberOfChildren" bytes when IOCTL_ACPI_ENUM_CHILDREN
+ // fails with STATUS_BUFFER_OVERFLOW. The returning "NumberOfChildren" is
+ // not the number of children, but the required size in bytes.
+ //
+ outputBufSize = sizeof(*outputBuf);
+ attempts = 0;
+
+ do
+ {
+ WDF_OBJECT_ATTRIBUTES_INIT(&attr);
+ attr.ParentObject = m_AcpiDevice;
+
+ status = WdfMemoryCreate(
+ &attr,
+ NonPagedPoolNx,
+ s_MemoryTag,
+ outputBufSize,
+ &outputMem,
+ (PVOID*)&outputBuf);
+
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! WdfMemoryCreate failed for outputBuf, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ WDF_MEMORY_DESCRIPTOR_INIT_HANDLE(&outputMemDesc, outputMem, nullptr);
+
+ WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, 0);
+ WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(ACPI_REQUEST_TIMEOUT_SEC));
+
+ status = WdfIoTargetSendIoctlSynchronously(
+ acpiIoTarget,
+ NULL,
+ IOCTL_ACPI_ENUM_CHILDREN,
+ &inputMemDesc,
+ &outputMemDesc,
+ &sendOptions,
+ nullptr);
+
+ if (NT_SUCCESS(status))
+ {
+ if (outputBuf->Signature != ACPI_ENUM_CHILDREN_OUTPUT_BUFFER_SIGNATURE)
+ {
+ status = STATUS_ACPI_INVALID_DATA;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid data in ACPI_ENUM_CHILDREN_OUTPUT_BUFFER, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ //
+ // There must be at least one, because this device is included in the list.
+ // When IOCTL_ACPI_ENUM_CHILDREN succeeds, "NumberOfChildren" does have
+ // the number of children. (When the IOCTL fails, it's the required size
+ // in bytes.)
+ //
+ if (outputBuf->NumberOfChildren < 1)
+ {
+ status = STATUS_ACPI_INVALID_DATA;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! No child devices in ACPI_ENUM_CHILDREN_OUTPUT_BUFFER, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ //
+ // Return the output memory object.
+ //
+ *EnumChildrenOutput = outputMem;
+ outputMem = WDF_NO_HANDLE;
+
+ break;
+ }
+
+ if (status != STATUS_BUFFER_OVERFLOW)
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! IOCTL_ACPI_ENUM_CHILDREN _BUFFER, %!STATUS!", status);
+ // No assert since this is common in sdca bringup
+ goto exit;
+ }
+
+ if (outputBuf->Signature != ACPI_ENUM_CHILDREN_OUTPUT_BUFFER_SIGNATURE)
+ {
+ status = STATUS_ACPI_INVALID_DATA;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid data in ACPI_ENUM_CHILDREN_OUTPUT_BUFFER, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ //
+ // When IOCTL_ACPI_ENUM_CHILDREN fails with STATUS_BUFFER_OVERFLOW,
+ // "NumberOfChildren" is not the number of children, but the required
+ // size in bytes.
+ //
+ outputBufSize = outputBuf->NumberOfChildren;
+ WdfObjectDelete(outputMem);
+ outputMem = WDF_NO_HANDLE;
+ attempts++;
+ } while (attempts < 2);
+
+ exit:
+
+ if (inputMem != WDF_NO_HANDLE)
+ {
+ WdfObjectDelete(inputMem);
+ inputMem = WDF_NO_HANDLE;
+ }
+
+ if (outputMem != WDF_NO_HANDLE)
+ {
+ WdfObjectDelete(outputMem);
+ outputMem = WDF_NO_HANDLE;
+ }
+
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::EvaluateMethod(
+ _In_ LPCSTR MethodName,
+ _Out_ WDFMEMORY * ReturnMemory
+ )
+ /*++
+ Routine Description:
+
+ This function sends IOCTL_ACPI_EVAL_METHOD_EX to ACPI to evaluate a method.
+
+ Arguments:
+
+ MethodName - Supplies a packed string identifying the method.
+
+ ReturnMemory - Supplies the resulting memory object.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+ {
+ const ULONG InitialControlMethodOutputSize = 0x200; // 512 bytes
+ UCHAR attempts;
+ WDF_MEMORY_DESCRIPTOR inputDesc;
+ WDFMEMORY outputMem{ WDF_NO_HANDLE };
+ PACPI_EVAL_OUTPUT_BUFFER outputBuf;
+ ULONG outputBufLength;
+ WDF_MEMORY_DESCRIPTOR outputDesc;
+ ULONG_PTR sizeReturned;
+ ACPI_EVAL_INPUT_BUFFER_EX inputBuf;
+ WDF_OBJECT_ATTRIBUTES attr;
+ WDFIOTARGET acpiIoTarget;
+ NTSTATUS status;
+ WDF_REQUEST_SEND_OPTIONS sendOptions;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ ASSERT(m_AcpiDevice);
+
+ acpiIoTarget = WdfDeviceGetIoTarget(m_AcpiDevice);
+
+ //
+ // Prepare an input buffer.
+ //
+ inputBuf.Signature = ACPI_EVAL_INPUT_BUFFER_SIGNATURE_EX;
+
+ status = RtlStringCchCopyA(
+ inputBuf.MethodName,
+ sizeof(inputBuf.MethodName),
+ MethodName);
+
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! RtlStringCchCopyA failed to copy ACPI method name, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(
+ &inputDesc,
+ (PVOID)&inputBuf,
+ sizeof(ACPI_EVAL_INPUT_BUFFER_EX));
+
+ //
+ // Set the initial size for the output buffer to be allocated.
+ //
+ outputBuf = NULL;
+ outputBufLength = InitialControlMethodOutputSize;
+ attempts = 0;
+
+ do
+ {
+ WDF_OBJECT_ATTRIBUTES_INIT(&attr);
+ attr.ParentObject = m_AcpiDevice;
+
+ status = WdfMemoryCreate(
+ &attr,
+ NonPagedPoolNx,
+ s_MemoryTag,
+ outputBufLength,
+ &outputMem,
+ (PVOID*)&outputBuf);
+
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! WdfMemoryCreate failed for %Iu bytes, %!STATUS!", outputBufLength, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ RtlZeroMemory(outputBuf, outputBufLength);
+
+ WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(
+ &outputDesc,
+ (PVOID)outputBuf,
+ outputBufLength);
+
+ WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, 0);
+ WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(ACPI_REQUEST_TIMEOUT_SEC));
+
+ status = WdfIoTargetSendIoctlSynchronously(
+ acpiIoTarget,
+ NULL,
+ IOCTL_ACPI_EVAL_METHOD_EX,
+ &inputDesc,
+ &outputDesc,
+ &sendOptions,
+ &sizeReturned);
+
+ if (NT_SUCCESS(status))
+ {
+ //
+ // IOCTL_ACPI_EVAL_METHOD_EX succeeded.
+ //
+ if (sizeReturned == 0)
+ {
+ status = STATUS_UNSUCCESSFUL;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! IOCTL_ACPI_EVAL_METHOD_EX returned 0 byte, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ if (outputBuf->Signature != ACPI_EVAL_OUTPUT_BUFFER_SIGNATURE)
+ {
+ status = STATUS_ACPI_INVALID_DATA;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! ACPI_EVAL_OUTPUT_BUFFER signature (0x%x) is incorrect, %!STATUS!", outputBuf->Signature, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ //
+ // Return the output memory object.
+ //
+ *ReturnMemory = outputMem;
+ outputMem = WDF_NO_HANDLE;
+
+ break;
+ }
+
+ if (status != STATUS_BUFFER_OVERFLOW)
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "!FUNC! IOCTL_ACPI_EVAL_METHOD_EX failed , %!STATUS!", status);
+ // Failure is common when used alongside virtual stack
+ goto exit;
+ }
+
+ //
+ // If the output buffer was insufficient, then re-allocate one with
+ // appropriate size and retry.
+ //
+ outputBufLength = outputBuf->Length;
+ WdfObjectDelete(outputMem);
+ outputMem = WDF_NO_HANDLE;
+ attempts++;
+
+ if (attempts == 2)
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! IOCTL_ACPI_EVAL_METHOD_EX has failed for %u times. Stopped retrying., %!STATUS!", attempts, status);
+ ASSERT(FALSE);
+ }
+ } while (attempts < 2);
+
+ exit:
+
+ if (outputMem != WDF_NO_HANDLE)
+ {
+ WdfObjectDelete(outputMem);
+ outputMem = WDF_NO_HANDLE;
+ }
+
+ DrvLogExit(s_AcpiReaderLog);
+
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::EvaluateAdr(
+ _In_opt_ LPCSTR ChildDeviceName,
+ _Out_ PULONGLONG Address
+ )
+ /*++
+ Routine Description:
+
+ This function evaluates a _ADR method.
+
+ Arguments:
+
+ ChildDeviceName - Supplies a child device name. If Null, evaluate
+ the _ADR for the current device instead.
+
+ Address - Returning the device address from _ADR.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+ {
+ NTSTATUS status;
+ CHAR fullMethodName[MAX_PATH];
+ WDFMEMORY outputMem{ WDF_NO_HANDLE };
+ PACPI_EVAL_OUTPUT_BUFFER outputBuf;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (!Address)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ goto exit;
+ }
+
+ if (ChildDeviceName != nullptr)
+ {
+ status = RtlStringCchPrintfA(
+ fullMethodName,
+ sizeof(fullMethodName),
+ "%s._ADR",
+ ChildDeviceName);
+ }
+ else
+ {
+ status = RtlStringCchCopyA(
+ fullMethodName,
+ sizeof(fullMethodName),
+ "_ADR");
+ }
+
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! RtlStringCchPrintfA for creating method name _BUFFER, %!STATUS!", status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ status = EvaluateMethod(
+ fullMethodName,
+ &outputMem);
+
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! EvaluateMethod failed on [%s], %!STATUS!", fullMethodName, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ outputBuf = (PACPI_EVAL_OUTPUT_BUFFER)WdfMemoryGetBuffer(outputMem, NULL);
+
+ if (outputBuf->Count < 1)
+ {
+ status = STATUS_ACPI_INVALID_DATA;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! _ADR of [%s] didn't return anything, %!STATUS!", fullMethodName, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ if (outputBuf->Argument[0].Type != ACPI_METHOD_ARGUMENT_INTEGER)
+ {
+ status = STATUS_ACPI_INVALID_DATA;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! _ADR of [%s] returned an unexpected argument of type %hu, %!STATUS!", fullMethodName, outputBuf->Argument[0].Type, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ status = ParseULongLong(outputBuf->Argument, Address);
+
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected argument from _ADR of [%s], %!STATUS!", fullMethodName, status);
+ ASSERT(FALSE);
+ goto exit;
+ }
+
+ exit:
+ if (outputMem != WDF_NO_HANDLE)
+ {
+ WdfObjectDelete(outputMem);
+ outputMem = WDF_NO_HANDLE;
+ }
+
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::EvaluateAdr(
+ _Out_ PULONGLONG Address
+ )
+ /*++
+ Routine Description:
+
+ This function evaluates the _ADR method for the current device.
+
+ Arguments:
+
+ Address - Returning the device address from _ADR.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+
+ {
+ NTSTATUS status;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ status = EvaluateAdr(nullptr, Address);
+
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::GetPropertyString(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_writes_opt_z_(ValueStringSize) char * ValueString,
+ _In_ ULONG ValueStringSize,
+ _Out_ PULONG PropertyValueSize
+ )
+ /*++
+ Routine Description:
+
+ This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns
+ string value for the property if found.
+
+ Arguments:
+
+ PropertyName - Property name to search for.
+
+ PropertySection - Specifies if property is under device property or hierarchical
+ data extension section.
+
+ AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in
+ which property needs to be searched.
+
+ ValueString - Buffer that will hold property value if found.
+
+ ValueStringSize - Size of the output buffer.
+
+ PropertyValueSize - Actual length of the property value.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+ {
+ NTSTATUS status = STATUS_NOT_FOUND;
+ PACPI_METHOD_ARGUMENT propValueArg = NULL;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (!PropertyName || !PropertyValueSize || (AcpiEvalOutputBuf == WDF_NO_HANDLE))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValueSize or AcpiEvalOutputBuf, %!STATUS!", status);
+ goto exit;
+ }
+ if (ValueStringSize > 0 && !ValueString)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Null ValueString with non-zero ValueStringSize, %!STATUS!", status);
+ goto exit;
+ }
+
+ status = GetProperty(PropertyName, PropertySection, AcpiEvalOutputBuf, &propValueArg);
+
+ if (NT_SUCCESS(status))
+ {
+ status = ParseString(propValueArg, ValueString, ValueStringSize, PropertyValueSize);
+ }
+
+ exit:
+ return status;
+ }
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::GetPropertyULongLong(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_ PULONGLONG PropertyValue
+ )
+ /*++
+ Routine Description:
+
+ This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns
+ ULONGLONG value for the property if found.
+
+ Arguments:
+
+ PropertyName - Property name to search for.
+
+ PropertySection - Specifies if property is under device property or hierarchical
+ data extension section.
+
+ AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in
+ which property needs to be searched.
+
+ PropertyValue - Value of the property.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+ {
+ NTSTATUS status = STATUS_NOT_FOUND;
+ PACPI_METHOD_ARGUMENT propValueArg = NULL;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (!PropertyName || !PropertyValue || (AcpiEvalOutputBuf == WDF_NO_HANDLE))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValue or AcpiEvalOutputBuf, %!STATUS!", status);
+ goto exit;
+ }
+
+ status = GetProperty(PropertyName, PropertySection, AcpiEvalOutputBuf, &propValueArg);
+
+ if (NT_SUCCESS(status))
+ {
+ status = ParseULongLong(propValueArg, PropertyValue);
+ }
+
+ exit:
+ return status;
+ }
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::GetPropertyULong(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_ PULONG PropertyValue
+ )
+ /*++
+ Routine Description:
+
+ This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns
+ ULONG value for the property if found.
+
+ Arguments:
+
+ PropertyName - Property name to search for.
+
+ PropertySection - Specifies if property is under device property or hierarchical
+ data extension section.
+
+ AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in
+ which property needs to be searched.
+
+ PropertyValue - Value of the property.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+ {
+ NTSTATUS status = STATUS_NOT_FOUND;
+ PACPI_METHOD_ARGUMENT propValueArg = NULL;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (!PropertyName || !PropertyValue || (AcpiEvalOutputBuf == WDF_NO_HANDLE))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValue or AcpiEvalOutputBuf, %!STATUS!", status);
+ goto exit;
+ }
+
+ status = GetProperty(PropertyName, PropertySection, AcpiEvalOutputBuf, &propValueArg);
+
+ if (NT_SUCCESS(status))
+ {
+ status = ParseULong(propValueArg, PropertyValue);
+ }
+
+ exit:
+ DrvLogExit(s_AcpiReaderLog);
+
+ return status;
+ }
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::GetPropertyBuffer(
+ _In_ LPCSTR PropertyName,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_writes_bytes_(ValueBufferSize) PVOID ValueBuffer,
+ _In_ ULONG ValueBufferSize,
+ _Out_ PULONG PropertyValueSize
+ )
+ /*++
+ Routine Description:
+
+ This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns
+ buffer value for the property if found.
+
+ Arguments:
+
+ PropertyName - Property name to search for.
+
+ AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in
+ which property needs to be searched.
+
+ ValueBuffer - Buffer that will hold property value if found.
+
+ ValueBufferSize - Size of the output buffer.
+
+ PropertyValueSize - Actual length of the property value.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+ {
+ NTSTATUS status = STATUS_NOT_FOUND;
+ PACPI_METHOD_ARGUMENT propValueArg = NULL;
+ char methodName[MAX_PATH];
+ ULONG valueSize = 0;
+ WDFMEMORY bufferBlock = nullptr;
+ PACPI_EVAL_OUTPUT_BUFFER buffer;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (!PropertyName || !PropertyValueSize || (AcpiEvalOutputBuf == WDF_NO_HANDLE))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValueSize or AcpiEvalOutputBuf, %!STATUS!", status);
+ goto exit;
+ }
+ // Property we are searching is expected to return a buffer,
+ // so this property will be under Buffer UUID section
+ // ToUUID("EDB12DD0-363D-4085-A3D2-49522CA160C4"),
+ // Package() {
+ // Package { Property, "BUF0"}
+ // }
+ status = GetProperty(PropertyName, ACPI_METHOD_SECTION_BUFFER, AcpiEvalOutputBuf, &propValueArg);
+
+ if (!NT_SUCCESS(status))
+ {
+ // No need to log an error as it may be an optional property and not expected to be present all the time.
+ goto exit;
+ }
+
+ // Value of the property will be method name
+ status = ParseString(propValueArg, methodName, sizeof(methodName), &valueSize);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Failed to retrieve method name for %hs, %!STATUS!", PropertyName, status);
+ goto exit;
+ }
+
+ // Evaluate method which will return contents of the buffer
+ // e.g. Evaluate method "BUF0"
+ status = EvaluateMethod(methodName, &bufferBlock);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Failed to evaluate method %hs, %!STATUS!",
+ methodName,
+ status);
+ goto exit;
+ }
+
+ buffer = (PACPI_EVAL_OUTPUT_BUFFER)WdfMemoryGetBuffer(bufferBlock, NULL);
+ // This method must contain only one ACPI argument
+ if (buffer->Count != 1)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Method %hs has argument count %d, expected 1, %!STATUS!",
+ methodName,
+ buffer->Count,
+ status);
+ goto exit;
+ }
+
+ status = ParseBuffer(buffer->Argument, ValueBuffer, ValueBufferSize, PropertyValueSize);
+
+ exit:
+ if (bufferBlock != nullptr)
+ {
+ WdfObjectDelete(bufferBlock);
+ bufferBlock = nullptr;
+ }
+
+ DrvLogExit(s_AcpiReaderLog);
+
+ return status;
+ }
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::GetPropertyULongArray(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_writes_(ValueArrayCount) ULONG * ValueArray,
+ _In_ ULONG ValueArrayCount,
+ _Out_ PULONG PropertyValueArrayCount
+ )
+ /*++
+ Routine Description:
+
+ This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns
+ an array of ULONGs for the property if found.
+
+ Arguments:
+
+ PropertyName - Property name to search for.
+
+ PropertySection - Specifies if property is under device property or hierarchical
+ data extension section.
+
+ AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in
+ which property needs to be searched.
+
+ ValueArray - ULONG array that will hold property values if found.
+
+ ValueArrayCount - Total count of elements in ValueArray.
+
+ PropertyValueArrayCount - Valid count of elements in ValueArray.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+ {
+ NTSTATUS status = STATUS_NOT_FOUND;
+ PACPI_METHOD_ARGUMENT PropValueArg = NULL;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ if (!PropertyName || !PropertyValueArrayCount || (AcpiEvalOutputBuf == WDF_NO_HANDLE))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValueArrayCount or AcpiEvalOutputBuf, %!STATUS!", status);
+ goto exit;
+ }
+
+ status = GetProperty(PropertyName, PropertySection, AcpiEvalOutputBuf, &PropValueArg);
+
+ if (NT_SUCCESS(status))
+ {
+ status = ParseULongArray(PropValueArg, ValueArray, ValueArrayCount, PropertyValueArrayCount);
+ }
+
+ exit:
+ DrvLogExit(s_AcpiReaderLog);
+
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::GetProperty(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue
+ )
+ /*++
+ Routine Description:
+
+ This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns
+ ACPI_METHOD_ARGUMENT for the property if found.
+
+ Arguments:
+
+ PropertyName - Property name to search for.
+
+ PropertySection - Specifies if property is under device property, hierarchical
+ data extension or buffer section.
+
+ AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in
+ which property needs to be searched.
+
+ PropertyValue - ACPI_MEDHOD_ARGUMENT pointer to property value if the property was found.
+
+ Return Value:
+
+ NTSTATUS code.
+
+ --*/
+ {
+ NTSTATUS status = STATUS_NOT_FOUND;
+ PACPI_EVAL_OUTPUT_BUFFER Buffer;
+ PACPI_METHOD_ARGUMENT currentArgument;
+ ULONG argumentIndex;
+ GUID guid;
+ ACPI_METHOD_SECTION section = ACPI_METHOD_SECTION_UNKNOWN;
+ BOOL found = FALSE;
+ size_t PropertyLength;
+ size_t BufferLength;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ Buffer = (PACPI_EVAL_OUTPUT_BUFFER)WdfMemoryGetBuffer(AcpiEvalOutputBuf, &BufferLength);
+
+ if (BufferLength < FIELD_OFFSET(ACPI_EVAL_OUTPUT_BUFFER, Argument))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! WdfMemoryBuffer length %llu too short, %!STATUS!", BufferLength, status);
+ ASSERT(FALSE);
+ return status;
+ }
+
+ if (Buffer->Length > BufferLength)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! ACPI_EVAL_OUTPUT_BUFFER length %lu exceeds WdfMemoryBuffer length %llu, %!STATUS!", Buffer->Length, BufferLength, status);
+ ASSERT(FALSE);
+ return status;
+ }
+
+ // Method structure
+ // Name (Method, Package() {
+ // ToUUID("DAFFD814-6EBA-4D8C-8A91-BC9BBF4AA301"),
+ // Package () {
+ // Package (2) { Property, Value }
+ // :
+ // Package (2) { Property, Value }
+ // },
+ // ToUUID("DBB8E3E6-5886-4BA6-8795-1319F52A966B"),
+ // Package() {
+ // Package { Property, Sub-Package}
+ // :
+ // Package { Property, Sub-Package}
+ // }
+ // ToUUID("EDB12DD0-363D-4085-A3D2-49522CA160C4"),
+ // Package() {
+ // Package { Property, Sub-Package}
+ // :
+ // Package { Property, Sub-Package}
+ // }
+ // }
+
+ PropertyLength = strlen(PropertyName) + 1; // Add one for NULL terminator as ACPI_METHOD_ARGUMENT Datalength includes it.
+
+ currentArgument = ACPI_EVAL_OUTPUT_BUFFER_ARGUMENTS_BEGIN(Buffer);
+ for (argumentIndex = 0; argumentIndex < Buffer->Count && !found; argumentIndex++)
+ {
+ if (((PUCHAR)currentArgument + ACPI_METHOD_ARGUMENT_LENGTH(0) > (PUCHAR)ACPI_EVAL_OUTPUT_BUFFER_ARGUMENTS_END(Buffer)) ||
+ ((PUCHAR)currentArgument + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(currentArgument) > (PUCHAR)ACPI_EVAL_OUTPUT_BUFFER_ARGUMENTS_END(Buffer)))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! ACPI_METHOD_ARGUMENT outside of ACPI_EVAL_OUTPUT_BUFFER length, %!STATUS!", status);
+ ASSERT(FALSE);
+ return status;
+ }
+
+ switch (currentArgument->Type)
+ {
+ case ACPI_METHOD_ARGUMENT_BUFFER:
+
+ status = ParseGuid(currentArgument, &guid, sizeof(GUID));
+ if (NT_SUCCESS(status) && guid == DSD_DEVICE_PROPERTIES_GUID)
+ {
+ section = ACPI_METHOD_SECTION_DEVICE_PROPERTIES;
+ }
+ else if (NT_SUCCESS(status) && guid == DSD_HIERARCHICAL_DATA_EXTENSION_GUID)
+ {
+ section = ACPI_METHOD_SECTION_HIERARCHICAL_DATA_EXTENSION;
+ }
+ else if (NT_SUCCESS(status) && guid == DSD_BUFFER_GUID)
+ {
+ section = ACPI_METHOD_SECTION_BUFFER;
+ }
+ else
+ {
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Skipping unexpected ACPI_METHOD_ARGUMENT_BUFFER argument, %!STATUS!", status);
+ ASSERT(FALSE);
+ section = ACPI_METHOD_SECTION_UNKNOWN;
+ }
+ break;
+
+ case ACPI_METHOD_ARGUMENT_PACKAGE:
+
+ // Caller specified the section in which to search property
+ // so further search only if this package is under that section
+ if (section == PropertySection)
+ {
+ // Parse sub-packages
+ status = ParsePropertiesPackage(PropertyName, PropertyLength, currentArgument, PropertyValue);
+ if (NT_SUCCESS(status))
+ {
+ found = TRUE;
+ }
+ }
+ break;
+
+ default:
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Skipping unexpected argument[%d] of type[%d]", argumentIndex, currentArgument->Type);
+ break;
+ }
+ currentArgument = ACPI_METHOD_NEXT_ARGUMENT(currentArgument);
+ }
+
+ if (!found)
+ {
+ status = STATUS_NOT_FOUND;
+ }
+
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::ParsePropertiesPackage(
+ _In_ LPCSTR PropertyName,
+ _In_ size_t PropertyLength,
+ _In_ PACPI_METHOD_ARGUMENT Package,
+ _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue
+ )
+ /*++
+ Routine Description:
+
+ This function searches for PropertyName in a package that contains
+ property packages.
+
+ Arguments:
+
+ PropertyName - Property name to search for.
+
+ Package - Pointer to ACPI_METHOD_ARGUMENT containing package under
+ device property or hierarchical data extension section.
+
+ PropertyValue - ACPI_MEDHOD_ARGUMENT pointer to property value if the property was found.
+
+ Return Value:
+
+ NTSTATUS code.
+ --*/
+ {
+ NTSTATUS status = STATUS_NOT_FOUND;
+ PACPI_METHOD_ARGUMENT currentArgument;
+ ULONG argumentIndex;
+ BOOL found = FALSE;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ // Package structure
+ // Package () {
+ // Package (2) { Property, Value }
+ // :
+ // Package (2) { Property, Value }
+ // }
+
+ if (Package->DataLength < ACPI_METHOD_ARGUMENT_LENGTH(0))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Package ACPI_METHOD_ARGUMENT too small, %!STATUS!", status);
+ ASSERT(FALSE);
+ return status;
+ }
+
+ currentArgument = (PACPI_METHOD_ARGUMENT)Package->Data;
+ for (argumentIndex = 0 ; ((PUCHAR)currentArgument < (PUCHAR)Package->Data + Package->DataLength) && !found; argumentIndex++)
+ {
+ if (((PUCHAR)currentArgument + ACPI_METHOD_ARGUMENT_LENGTH(0) > (PUCHAR)Package->Data + Package->DataLength) ||
+ ((PUCHAR)currentArgument + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(currentArgument) > (PUCHAR)Package->Data + Package->DataLength))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! ACPI_METHOD_ARGUMENT outside of package length, %!STATUS!", status);
+ ASSERT(FALSE);
+ return status;
+ }
+
+ switch (currentArgument->Type)
+ {
+ case ACPI_METHOD_ARGUMENT_PACKAGE:
+ status = FindProperty(PropertyName, PropertyLength, currentArgument, PropertyValue);
+ if (NT_SUCCESS(status))
+ {
+ found = TRUE;
+ }
+ break;
+
+ default:
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Skipping unexpected argument[%d] of type[%d]", argumentIndex, currentArgument->Type);
+ break;
+ }
+ currentArgument = ACPI_METHOD_NEXT_ARGUMENT(currentArgument);
+ }
+
+ if (!found)
+ {
+ status = STATUS_NOT_FOUND;
+ }
+
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ NTSTATUS
+ AcpiReader::FindProperty(
+ _In_ LPCSTR PropertyName,
+ _In_ size_t PropertyLength,
+ _In_ PACPI_METHOD_ARGUMENT Package,
+ _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue
+ )
+ /*++
+ Routine Description:
+
+ This function searches for PropertyName in a package that contains
+ a property name and value.
+
+ Arguments:
+
+ PropertyName - Property name to search for.
+
+ Package - Pointer to ACPI_METHOD_ARGUMENT containing package that has
+ property name and value.
+
+ PropertyValue - ACPI_MEDHOD_ARGUMENT pointer to property value if the property was found.
+
+ Return Value:
+
+ NTSTATUS code.
+ --*/
+ {
+ NTSTATUS status = STATUS_NOT_FOUND;
+ PACPI_METHOD_ARGUMENT propNameArgument;
+ PACPI_METHOD_ARGUMENT propValArgument;
+
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ // Property package structure
+ // Package (2) { Property, Value }
+
+ propNameArgument = (PACPI_METHOD_ARGUMENT)Package->Data;
+ propValArgument = ACPI_METHOD_NEXT_ARGUMENT(propNameArgument);
+
+ if (Package->DataLength < ACPI_METHOD_ARGUMENT_LENGTH(0))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Package ACPI_METHOD_ARGUMENT too small, %!STATUS!", status);
+ ASSERT(FALSE);
+ return status;
+ }
+
+ if ((PUCHAR)propNameArgument + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(propNameArgument) >
+ (PUCHAR)Package + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(Package))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! prop name ACPI_METHOD_ARGUMENT outside of package length, %!STATUS!", status);
+ ASSERT(FALSE);
+ return status;
+ }
+
+ if (propNameArgument->Type == ACPI_METHOD_ARGUMENT_STRING)
+ {
+ if ((PropertyLength == propNameArgument->DataLength) &&
+ !_strnicmp(PropertyName, (char*)propNameArgument->Data, propNameArgument->DataLength))
+ {
+ if ((PUCHAR)propValArgument + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(propValArgument) >
+ (PUCHAR)Package + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(Package))
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! prop val ACPI_METHOD_ARGUMENT outside of package length, %!STATUS!", status);
+ ASSERT(FALSE);
+ return status;
+ }
+
+ *PropertyValue = propValArgument;
+ status = STATUS_SUCCESS;
+ }
+ }
+
+ DrvLogExit(s_AcpiReaderLog);
+ return status;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ VOID
+ AcpiReader::FreeBuffer(
+ _Inout_ WDFMEMORY * AcpiEvalOutputBuf
+ )
+ /*++
+ Routine Description:
+
+ This function frees memory object.
+
+ Arguments:
+
+ AcpiEvalOutputBuf - Memory object to be freed.
+
+ Return Value:
+
+ VOID
+
+ --*/
+ {
+ DrvLogEnter(s_AcpiReaderLog);
+
+ PAGED_CODE();
+
+ ASSERT(*AcpiEvalOutputBuf);
+ if ((*AcpiEvalOutputBuf) != WDF_NO_HANDLE)
+ {
+ WdfObjectDelete(*AcpiEvalOutputBuf);
+ *AcpiEvalOutputBuf = WDF_NO_HANDLE;
+ }
+
+ DrvLogExit(s_AcpiReaderLog);
+ return;
+ }
+
+ _Use_decl_annotations_
+ PAGED_CODE_SEG
+ VOID
+ AcpiReader::EvtContextDestroy(WDFOBJECT Object)
+ {
+ PAGED_CODE();
+
+ AcpiReader * context = GetAcpiReaderDeviceContext(Object);
+ context->~AcpiReader();
+ }
+} // namespace ACPIREADER
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.h
new file mode 100644
index 00000000..5c9ceee8
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.h
@@ -0,0 +1,297 @@
+/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ AcpiReader.h
+
+Abstract:
+
+ Contains ACPI reader module.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#pragma once
+
+#ifndef _ACPIREADER_H_
+#define _ACPIREADER_H_
+
+/* make prototypes usable from C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <acpiioct.h>
+
+// Number of seconds for ACPI request timeout.
+#define ACPI_REQUEST_TIMEOUT_SEC 5
+
+//
+// Device properties UUID in the ACPI methods.
+// {DAFFD814-6EBA-4D8C-8A91-BC9BBF4AA301}
+//
+DEFINE_GUID(DSD_DEVICE_PROPERTIES_GUID,
+ 0xDAFFD814, 0x6EBA, 0x4D8C, 0x8A, 0x91, 0xBC, 0x9B, 0xBF, 0x4A, 0xA3, 0x01);
+
+//
+// Hierarchical data extension UUID in the ACPI methods.
+// {DBB8E3E6-5886-4BA6-8795-1319F52A966B}
+//
+
+DEFINE_GUID(DSD_HIERARCHICAL_DATA_EXTENSION_GUID,
+ 0xDBB8E3E6, 0x5886, 0x4BA6, 0x87, 0x95, 0x13, 0x19, 0xF5, 0x2A, 0x96, 0x6B);
+
+//
+// Buffer UUID in ACPI methods.
+// {EDB12DD0-363D-4085-A3D2-49522CA160C4}
+//
+
+DEFINE_GUID(DSD_BUFFER_GUID,
+ 0xEDB12DD0, 0x363D, 0x4085, 0xA3, 0xD2, 0x49, 0x52, 0x2C, 0xA1, 0x60, 0xC4);
+
+namespace ACPIREADER
+{
+ typedef enum
+ {
+ ACPI_METHOD_SECTION_UNKNOWN = 0,
+ ACPI_METHOD_SECTION_DEVICE_PROPERTIES = 1,
+ ACPI_METHOD_SECTION_HIERARCHICAL_DATA_EXTENSION = 2,
+ ACPI_METHOD_SECTION_BUFFER = 3
+ } ACPI_METHOD_SECTION;
+
+ class AcpiReader
+ {
+ private:
+ WDFDEVICE m_AcpiDevice{ nullptr };
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ParseULongLong(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_ PULONGLONG Value);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ParseULong(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_ PULONG Value);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ParseString(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_writes_opt_z_(ValueStringSize) char * ValueString,
+ _In_ ULONG ValueStringSize,
+ _Out_ PULONG PropertyValueSize);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ParseBuffer(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_writes_bytes_(ValueBufferSize) PVOID ValueBuffer,
+ _In_ ULONG ValueBufferSize,
+ _Out_ PULONG PropertyValueSize);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ParseGuid(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_writes_bytes_(BufferLength) PVOID Buffer,
+ _In_ ULONG BufferLength);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetProperty(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ParsePropertiesPackage(
+ _In_ LPCSTR PropertyName,
+ _In_ size_t PropertyLength,
+ _In_ PACPI_METHOD_ARGUMENT Package,
+ _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ FindProperty(
+ _In_ LPCSTR PropertyName,
+ _In_ size_t PropertyLength,
+ _In_ PACPI_METHOD_ARGUMENT Package,
+ _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ParseULongArray(
+ _In_ PACPI_METHOD_ARGUMENT Argument,
+ _Out_writes_(ValueArrayCount) ULONG * ValueArray,
+ _In_ ULONG ValueArrayCount,
+ _Out_ PULONG PropertyValueArrayCount);
+
+ public:
+ static
+ _Must_inspect_result_
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ _CreateAndInitialize(_In_ WDFDEVICE Device, _In_ RECORDER_LOG Log, _In_ ULONG MemoryTag);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ EnumChildren(
+ _Out_ WDFMEMORY * EnumChildrenOutput);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ EvaluateMethod(
+ _In_ LPCSTR MethodName,
+ _Out_ WDFMEMORY * ReturnMemory);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ EvaluateAdr(
+ _In_opt_ LPCSTR ChildDeviceName,
+ _Out_ PULONGLONG Address);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ EvaluateAdr(
+ _Out_ PULONGLONG Address);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetPropertyULongLong(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_ PULONGLONG PropertyValue);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetPropertyULong(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_ PULONG PropertyValue);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetPropertyString(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_writes_opt_z_(ValueStringSize) char * ValueString,
+ _In_ ULONG ValueStringSize,
+ _Out_ PULONG PropertyValueSize);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetPropertyBuffer(
+ _In_ LPCSTR PropertyName,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_writes_bytes_(ValueBufferSize) PVOID ValueBuffer,
+ _In_ ULONG ValueBufferSize,
+ _Out_ PULONG PropertyValueSize);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetPropertyULongArray(
+ _In_ LPCSTR PropertyName,
+ _In_ ACPI_METHOD_SECTION PropertySection,
+ _In_ WDFMEMORY AcpiEvalOutputBuf,
+ _Out_writes_(ValueArrayCount) ULONG * ValueArray,
+ _In_ ULONG ValueArrayCount,
+ _Out_ PULONG PropertyValueArrayCount);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ VOID
+ FreeBuffer(
+ _Inout_ WDFMEMORY* AcpiEvalOutputBuf);
+
+ protected:
+
+ static
+ RECORDER_LOG s_AcpiReaderLog;
+
+ static
+ ULONG s_MemoryTag;
+
+ static
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ EVT_WDF_OBJECT_CONTEXT_DESTROY
+ EvtContextDestroy;
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ AcpiReader(_In_ WDFDEVICE Device) : m_AcpiDevice(Device) { PAGED_CODE(); }
+
+ // Placement-new to construct the object inside the WDF context space.
+ static
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ void*
+ operator new (
+ _In_ size_t /* SizeInBytes */,
+ _In_ void* ContextMemory
+ )
+ {
+ PAGED_CODE();
+ // We already have the memory courtesy of WDF so we don't have to allocate anything.
+ return ContextMemory;
+ }
+
+ static
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ void
+ operator delete (
+ _In_ void* /* ContextMemory */
+ )
+ {
+ PAGED_CODE();
+ // Since we didn't allocate the memory, don't try to deallocate it.
+ }
+
+ };
+
+ WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(AcpiReader, GetAcpiReaderDeviceContext)
+}
+/* make internal prototypes usable from C++ */
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _ACPIREADER_H_
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AudioModule.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AudioModule.cpp
new file mode 100644
index 00000000..79764cdc
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AudioModule.cpp
@@ -0,0 +1,384 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ AudioModule.cpp
+
+Abstract:
+
+ Implementation of general purpose audio module property handlers
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include "audiomodule.h"
+#include "stdunk.h"
+#include <ks.h>
+
+AUDIOMODULE_PARAMETER_INFO AudioModule0_ParameterInfo[] =
+{
+ {
+ ACX_PROPERTY_ITEM_FLAG_GET | ACX_PROPERTY_ITEM_FLAG_SET | ACX_PROPERTY_ITEM_FLAG_BASICSUPPORT,
+ AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION,
+ (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE0_CONTEXT, Parameter1),
+ VT_UI4,
+ AudioModule0_ValidParameterList,
+ SIZEOF_ARRAY(AudioModule0_ValidParameterList)
+ },
+ {
+ ACX_PROPERTY_ITEM_FLAG_GET | ACX_PROPERTY_ITEM_FLAG_BASICSUPPORT,
+ 0,
+ (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE0_CONTEXT, Parameter2),
+ VT_UI1,
+ NULL,
+ 0
+ },
+};
+
+AUDIOMODULE_PARAMETER_INFO AudioModule1_ParameterInfo[] =
+{
+ {
+ KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT,
+ AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION,
+ (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE1_CONTEXT, Parameter1),
+ VT_UI1,
+ AudioModule1_ValidParameterList,
+ SIZEOF_ARRAY(AudioModule1_ValidParameterList)
+ },
+ {
+ KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_BASICSUPPORT,
+ 0,
+ (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE1_CONTEXT, Parameter2),
+ VT_UI8,
+ NULL,
+ 0
+ },
+ {
+ KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT,
+ AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION,
+ (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE1_CONTEXT, Parameter3),
+ VT_UI4,
+ NULL,
+ 0
+ },
+};
+
+AUDIOMODULE_PARAMETER_INFO AudioModule2_ParameterInfo[] =
+{
+ {
+ KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT,
+ AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION,
+ (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE2_CONTEXT, Parameter1),
+ VT_UI4,
+ AudioModule2_ValidParameterList,
+ SIZEOF_ARRAY(AudioModule2_ValidParameterList)
+ },
+ {
+ KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_BASICSUPPORT,
+ 0,
+ (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE2_CONTEXT, Parameter2),
+ VT_UI2,
+ NULL,
+ 0
+ },
+};
+
+#pragma code_seg("PAGE")
+NTSTATUS
+AudioModule_GenericHandler_BasicSupport(
+ _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo,
+ _Out_writes_bytes_opt_(*BufferCb) PVOID Buffer,
+ _Inout_ ULONG * BufferCb
+ )
+{
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+ ULONG cbFullProperty = 0;
+ ULONG cbDataListSize = 0;
+
+ PAGED_CODE();
+
+ ASSERT(ParameterInfo);
+ ASSERT(BufferCb);
+
+ //
+ // Compute total size of property.
+ //
+ ntStatus = RtlULongMult(ParameterInfo->Size,
+ ParameterInfo->ValidSetCount,
+ &cbDataListSize);
+ if (!NT_SUCCESS(ntStatus))
+ {
+ ASSERT(FALSE);
+ *BufferCb = 0;
+ return ntStatus;
+ }
+
+ ntStatus = RtlULongAdd(cbDataListSize,
+ (ULONG)(sizeof(KSPROPERTY_DESCRIPTION) +
+ sizeof(KSPROPERTY_MEMBERSHEADER)),
+ &cbFullProperty);
+
+ if (!NT_SUCCESS(ntStatus))
+ {
+ ASSERT(FALSE);
+ *BufferCb = 0;
+ return ntStatus;
+ }
+
+ //
+ // Return the info the caller is asking for.
+ //
+ if (*BufferCb == 0)
+ {
+ // caller wants to know the size of the buffer.
+ *BufferCb = cbFullProperty;
+ ntStatus = STATUS_BUFFER_OVERFLOW;
+ }
+ else if (*BufferCb >= (sizeof(KSPROPERTY_DESCRIPTION)))
+ {
+ PKSPROPERTY_DESCRIPTION propDesc = PKSPROPERTY_DESCRIPTION(Buffer);
+
+ propDesc->AccessFlags = ParameterInfo->AccessFlags;
+ propDesc->DescriptionSize = cbFullProperty;
+ propDesc->PropTypeSet.Set = KSPROPTYPESETID_General;
+ propDesc->PropTypeSet.Id = ParameterInfo->VtType;
+ propDesc->PropTypeSet.Flags = 0;
+ propDesc->MembersListCount = 1;
+ propDesc->Reserved = 0;
+
+ // if return buffer can also hold a list description, return it too
+ if(*BufferCb >= cbFullProperty)
+ {
+ // fill in the members header
+ PKSPROPERTY_MEMBERSHEADER members =
+ PKSPROPERTY_MEMBERSHEADER(propDesc + 1);
+
+ members->MembersFlags = KSPROPERTY_MEMBER_VALUES;
+ members->MembersSize = ParameterInfo->Size;
+ members->MembersCount = ParameterInfo->ValidSetCount;
+ members->Flags = KSPROPERTY_MEMBER_FLAG_DEFAULT;
+
+ // fill in valid array.
+ BYTE* array = (BYTE*)(members + 1);
+
+ RtlCopyMemory(array, ParameterInfo->ValidSet, cbDataListSize);
+
+ // set the return value size
+ *BufferCb = cbFullProperty;
+ }
+ else
+ {
+ *BufferCb = sizeof(KSPROPERTY_DESCRIPTION);
+ }
+ }
+ else if(*BufferCb >= sizeof(ULONG))
+ {
+ // if return buffer can hold a ULONG, return the access flags
+ PULONG accessFlags = PULONG(Buffer);
+
+ *BufferCb = sizeof(ULONG);
+ *accessFlags = ParameterInfo->AccessFlags;
+ }
+ else
+ {
+ *BufferCb = 0;
+ ntStatus = STATUS_BUFFER_TOO_SMALL;
+ }
+
+ return ntStatus;
+}
+
+#pragma code_seg("PAGE")
+BOOLEAN
+IsAudioModuleParameterValid(
+ _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo,
+ _In_reads_bytes_opt_(BufferCb) PVOID Buffer,
+ _In_ ULONG BufferCb
+ )
+{
+ PAGED_CODE();
+
+ ULONG i = 0;
+ ULONG j = 0;
+ BOOLEAN validParam = FALSE;
+
+ //
+ // Validate buffer ptr and size.
+ //
+ if (Buffer == NULL || BufferCb == 0)
+ {
+ validParam = FALSE;
+ goto exit;
+ }
+
+ //
+ // Check its size.
+ //
+ if (BufferCb < ParameterInfo->Size)
+ {
+ validParam = FALSE;
+ goto exit;
+ }
+
+ //
+ // Check the valid list.
+ //
+ if (ParameterInfo->ValidSet && ParameterInfo->ValidSetCount)
+ {
+ BYTE* buffer = (BYTE*)ParameterInfo->ValidSet;
+ BYTE* pattern = (BYTE*)Buffer;
+
+ //
+ // Scan the valid list.
+ //
+ for (i = 0; i < ParameterInfo->ValidSetCount; ++i)
+ {
+ for (j=0; j < ParameterInfo->Size; ++j)
+ {
+ if (buffer[j] != pattern[j])
+ {
+ break;
+ }
+ }
+
+ if (j == ParameterInfo->Size)
+ {
+ // got a match.
+ break;
+ }
+
+ buffer += ParameterInfo->Size;
+ }
+
+ //
+ // If end of list, we didn't find the value.
+ //
+ if (i == ParameterInfo->ValidSetCount)
+ {
+ validParam = FALSE;
+ goto exit;
+ }
+ }
+ else
+ {
+ //
+ // Negative-testing support. Fail request if value is -1.
+ //
+ BYTE* buffer = (BYTE*)Buffer;
+
+ for (i = 0; i < ParameterInfo->Size; ++i)
+ {
+ if (buffer[i] != 0xFF)
+ {
+ break;
+ }
+ }
+
+ //
+ // If value is -1, return error.
+ //
+ if (i == ParameterInfo->Size)
+ {
+ validParam = FALSE;
+ goto exit;
+ }
+ }
+
+ validParam = TRUE;
+
+exit:
+ return validParam;
+}
+
+#pragma code_seg("PAGE")
+NTSTATUS
+AudioModule_GenericHandler(
+ _In_ ULONG Verb,
+ _In_ ULONG ParameterId,
+ _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo,
+ _Inout_updates_bytes_(ParameterInfo->Size) PVOID CurrentValue,
+ _In_reads_bytes_opt_(InBufferCb) PVOID InBuffer,
+ _In_ ULONG InBufferCb,
+ _Out_writes_bytes_opt_(*OutBufferCb) PVOID OutBuffer,
+ _Inout_ ULONG * OutBufferCb,
+ _In_ BOOL * ParameterChanged
+ )
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(ParameterId);
+
+ *ParameterChanged = FALSE;
+
+ // Handle KSPROPERTY_TYPE_BASICSUPPORT query
+ if (Verb & KSPROPERTY_TYPE_BASICSUPPORT)
+ {
+ return AudioModule_GenericHandler_BasicSupport(ParameterInfo, OutBuffer, OutBufferCb);
+ }
+
+ ULONG cbMinSize = ParameterInfo->Size;
+
+ if (Verb & KSPROPERTY_TYPE_GET)
+ {
+ // Verify module parameter supports 'get'.
+ if (!(ParameterInfo->AccessFlags & KSPROPERTY_TYPE_GET))
+ {
+ *OutBufferCb = 0;
+ return STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ // Verify value size
+ if (*OutBufferCb == 0)
+ {
+ *OutBufferCb = cbMinSize;
+ return STATUS_BUFFER_OVERFLOW;
+ }
+ if (*OutBufferCb < cbMinSize)
+ {
+ *OutBufferCb = 0;
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+ else
+ {
+ RtlCopyMemory(OutBuffer, CurrentValue, ParameterInfo->Size);
+ *OutBufferCb = cbMinSize;
+ return STATUS_SUCCESS;
+ }
+ }
+ else if (Verb & KSPROPERTY_TYPE_SET)
+ {
+ *OutBufferCb = 0;
+
+ // Verify it is a write prop.
+ if (!(ParameterInfo->AccessFlags & KSPROPERTY_TYPE_SET))
+ {
+ return STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ // Validate parameter.
+ if (!IsAudioModuleParameterValid(ParameterInfo, InBuffer, InBufferCb))
+ {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (ParameterInfo->Size !=
+ RtlCompareMemory(CurrentValue, InBuffer, ParameterInfo->Size))
+ {
+ RtlCopyMemory(CurrentValue, InBuffer, ParameterInfo->Size);
+ *ParameterChanged = TRUE;
+ }
+
+ return STATUS_SUCCESS;
+ }
+
+ return STATUS_INVALID_DEVICE_REQUEST;
+}
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AudioModule.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AudioModule.h
new file mode 100644
index 00000000..9369f835
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AudioModule.h
@@ -0,0 +1,216 @@
+/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ AudioModule.h
+
+Abstract:
+
+ Contains audio modules definitions and function prototypes private to
+ the driver.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#ifndef _AUDIOMODULE_H_
+#define _AUDIOMODULE_H_
+
+/* make prototypes usable from C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Audio module definitions
+
+//
+// Audio module instance defintion.
+// This sample driver generates an instance id by combinding the
+// configuration set # for a class module id with the instance of that
+// configuration. Real driver should use a more robust scheme, such as
+// an indirect mapping from/to an instance id to/from a configuration set
+// + location in the pipeline + any other info the driver needs.
+//
+// top 8 bits reserved for use by aggregation
+// next 12 bits are the config id mask
+// bottom 12 bits instance id
+#define AUDIOMODULE_CLASS_CFG_ID_MASK 0xFFF
+#define AUDIOMODULE_CLASS_CFG_INSTANCE_ID_MASK 0xFFF
+
+#define AUDIOMODULE_INSTANCE_ID(ClassCfgId, ClassCfgInstanceId) \
+ ((ULONG(ClassCfgId & AUDIOMODULE_CLASS_CFG_ID_MASK) << 12) | \
+ (ULONG(ClassCfgInstanceId & AUDIOMODULE_CLASS_CFG_INSTANCE_ID_MASK)))
+
+#define AUDIOMODULE_GET_CLASSCFGID(InstanceId) \
+ (ULONG(InstanceId) >> 12 & AUDIOMODULE_CLASS_CFG_ID_MASK)
+
+enum AudioModule_Parameter {
+ AudioModuleParameter1 = 0,
+ AudioModuleParameter2,
+ AudioModuleParameter3
+};
+
+typedef struct _AUDIOMODULE_CUSTOM_COMMAND {
+ ULONG Verb; // get, set and support
+ AudioModule_Parameter ParameterId;
+} AUDIOMODULE_CUSTOM_COMMAND, *PAUDIOMODULE_CUSTOM_COMMAND;
+
+enum AudioModule_Notification_Type {
+ AudioModuleParameterChanged = 0,
+};
+
+typedef struct _AUDIOMODULE_CUSTOM_NOTIFICATION {
+ ULONG Type;
+ union {
+ struct {
+ ULONG ParameterId;
+ } ParameterChanged;
+ };
+} AUDIOMODULE_CUSTOM_NOTIFICATION, *PAUDIOMODULE_CUSTOM_NOTIFICATION;
+
+#define AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION 0x00000001
+
+typedef struct _DSP_AUDIOMODULE0_CONTEXT {
+ ACXPNPEVENT Event;
+ ULONG Parameter1;
+ BYTE Parameter2;
+ ULONG InstanceId;
+} DSP_AUDIOMODULE0_CONTEXT, *PDSP_AUDIOMODULE0_CONTEXT;
+
+typedef struct _DSP_AUDIOMODULE1_CONTEXT {
+ ACXPNPEVENT Event;
+ BYTE Parameter1;
+ ULONGLONG Parameter2;
+ DWORD Parameter3;
+ ULONG InstanceId;
+} DSP_AUDIOMODULE1_CONTEXT, *PDSP_AUDIOMODULE1_CONTEXT;
+
+typedef struct _DSP_AUDIOMODULE2_CONTEXT {
+ ACXPNPEVENT Event;
+ ULONG Parameter1;
+ USHORT Parameter2;
+ ULONG InstanceId;
+} DSP_AUDIOMODULE2_CONTEXT, *PDSP_AUDIOMODULE2_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_AUDIOMODULE0_CONTEXT, GetDspAudioModule0Context);
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_AUDIOMODULE1_CONTEXT, GetDspAudioModule1Context);
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_AUDIOMODULE2_CONTEXT, GetDspAudioModule2Context);
+
+typedef struct _AUDIOMODULE_PARAMETER_INFO
+{
+ USHORT AccessFlags; // get/set/basic-support attributes.
+ USHORT Flags;
+ ULONG Size;
+ DWORD VtType;
+ PVOID ValidSet;
+ ULONG ValidSetCount;
+} AUDIOMODULE_PARAMETER_INFO, *PAUDIOMODULE_PARAMETER_INFO;
+
+//
+// Module 0 definitions
+//
+#define AUDIOMODULE0DESCRIPTION L"Generic system module"
+#define AUDIOMODULE0_MAJOR 0x1
+#define AUDIOMODULE0_MINOR 0X0
+
+// {BD7CDC7F-F52E-4A95-B026-586926056128}
+static const GUID AudioModule0Id =
+{ 0xbd7cdc7f, 0xf52e, 0x4a95, { 0xb0, 0x26, 0x58, 0x69, 0x26, 0x5, 0x61, 0x28 } };
+
+EVT_ACX_AUDIOMODULE_PROCESSCOMMAND DspR_EvtProcessCommand0;
+
+static
+ULONG AudioModule0_ValidParameterList[] =
+{
+ 1, 2, 5
+};
+
+extern AUDIOMODULE_PARAMETER_INFO AudioModule0_ParameterInfo[2];
+
+//
+// Module 1 definitions
+//
+static
+BYTE AudioModule1_ValidParameterList[] =
+{
+ 0, 1, 2
+};
+
+extern AUDIOMODULE_PARAMETER_INFO AudioModule1_ParameterInfo[3];
+
+#define AUDIOMODULE1DESCRIPTION L"Module 1"
+#define AUDIOMODULE1_MAJOR 0x2
+#define AUDIOMODULE1_MINOR 0X1
+
+// {2803D255-6175-40A4-A572-ECF9FF6F07A9}
+static const GUID AudioModule1Id =
+{ 0x2803d255, 0x6175, 0x40a4, { 0xa5, 0x72, 0xec, 0xf9, 0xff, 0x6f, 0x7, 0xa9 } };
+
+EVT_ACX_AUDIOMODULE_PROCESSCOMMAND DspR_EvtProcessCommand1;
+
+//
+// Module 2 definitions
+//
+static
+ULONG AudioModule2_ValidParameterList[] =
+{
+ 1, 0xfffffffe
+};
+
+extern AUDIOMODULE_PARAMETER_INFO AudioModule2_ParameterInfo[2];
+
+#define AUDIOMODULE2DESCRIPTION L"Module 2"
+#define AUDIOMODULE2_MAJOR 0x2
+#define AUDIOMODULE2_MINOR 0X0
+
+// {2225578F-DF3B-40D8-BE80-031E1649DCC4}
+static const GUID AudioModule2Id =
+{ 0x2225578f, 0xdf3b, 0x40d8, { 0xbe, 0x80, 0x3, 0x1e, 0x16, 0x49, 0xdc, 0xc4 } };
+
+
+EVT_ACX_AUDIOMODULE_PROCESSCOMMAND DspR_EvtProcessCommand2;
+
+// General purpose helper functions
+
+NTSTATUS
+AudioModule_GenericHandler_BasicSupport(
+ _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo,
+ _Out_writes_bytes_opt_(*BufferCb) PVOID Buffer,
+ _Inout_ ULONG * BufferCb
+ );
+
+BOOLEAN
+IsAudioModuleParameterValid(
+ _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo,
+ _In_reads_bytes_opt_(BufferCb) PVOID Buffer,
+ _In_ ULONG BufferCb
+ );
+
+NTSTATUS
+AudioModule_GenericHandler(
+ _In_ ULONG Verb,
+ _In_ ULONG ParameterId,
+ _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo,
+ _Inout_updates_bytes_(ParameterInfo->Size) PVOID CurrentValue,
+ _In_reads_bytes_opt_(InBufferCb) PVOID InBuffer,
+ _In_ ULONG InBufferCb,
+ _Out_writes_bytes_opt_(*OutBufferCb) PVOID OutBuffer,
+ _Inout_ ULONG * OutBufferCb,
+ _In_ BOOL * ParameterChanged
+ );
+
+/* make internal prototypes usable from C++ */
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _AUDIOMODULE_H_
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.cpp
new file mode 100644
index 00000000..94d94843
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.cpp
@@ -0,0 +1,1467 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ CircuitHelper.cpp
+
+Abstract:
+
+ This module contains helper functions for render.cpp and capture.cpp files.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include "CircuitHelper.h"
+#include "TestProperties.h"
+#include "AudioFormats.h"
+
+#ifndef __INTELLISENSE__
+#include "CircuitHelper.tmh"
+#endif
+
+PAGED_CODE_SEG
+NTSTATUS CreateCaptureCircuit(
+ _In_ PACXCIRCUIT_INIT CircuitInit,
+ _In_ UNICODE_STRING CircuitName,
+ _In_ WDFDEVICE Device,
+ _Out_ ACXCIRCUIT* Circuit
+)
+{
+
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ // Circuit Component ID already assigned by the device handler
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignName(CircuitInit, &CircuitName));
+
+ //
+ // Add circuit type.
+ //
+ AcxCircuitInitSetCircuitType(CircuitInit, AcxCircuitTypeCapture);
+
+ //
+ // Assign the circuit's pnp-power callbacks.
+ //
+ {
+ ACX_CIRCUIT_PNPPOWER_CALLBACKS powerCallbacks;
+ ACX_CIRCUIT_PNPPOWER_CALLBACKS_INIT(&powerCallbacks);
+ powerCallbacks.EvtAcxCircuitPowerUp = DspC_EvtCircuitPowerUp;
+ powerCallbacks.EvtAcxCircuitPowerDown = DspC_EvtCircuitPowerDown;
+ AcxCircuitInitSetAcxCircuitPnpPowerCallbacks(CircuitInit, &powerCallbacks);
+ }
+
+ //
+ // Assign the circuit's composite callbacks.
+ //
+ {
+ ACX_CIRCUIT_COMPOSITE_CALLBACKS compositeCallbacks;
+ ACX_CIRCUIT_COMPOSITE_CALLBACKS_INIT(&compositeCallbacks);
+ compositeCallbacks.EvtAcxCircuitCompositeCircuitInitialize = DspC_EvtCircuitCompositeCircuitInitialize;
+ compositeCallbacks.EvtAcxCircuitCompositeInitialize = DspC_EvtCircuitCompositeInitialize;
+ AcxCircuitInitSetAcxCircuitCompositeCallbacks(CircuitInit, &compositeCallbacks);
+ }
+
+
+ //
+ // Add pre-process callbacks.
+ //
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXPIN_01
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback(
+ CircuitInit,
+ Dsp_EvtStreamGetStreamCountRequestPreprocess,
+ (ACXCONTEXT)Device,
+ AcxRequestTypeProperty,
+ &KSPROPSETID_Pin,
+ KSPROPERTY_PIN_CINSTANCES));
+#endif // ACX_WORKAROUND_ACXPIN_01
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback(
+ CircuitInit,
+ DspC_EvtCircuitRequestPreprocess,
+ (ACXCONTEXT)AcxRequestTypeAny, // dbg only
+ AcxRequestTypeAny,
+ NULL,
+ AcxItemIdNone));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxCreateStreamCallback(
+ CircuitInit,
+ DspC_EvtCircuitCreateStream));
+
+ /*
+ //
+ // Add properties, events and methods.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(CircuitInit,
+ CircuitProperties,
+ CircuitPropertiesCount));
+ */
+
+ //
+ // Create the circuit.
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_CIRCUIT_CONTEXT);
+ attributes.EvtCleanupCallback = DspC_EvtCircuitContextCleanup;
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitCreate(Device, &attributes, &CircuitInit, Circuit));
+
+ return status;
+}
+
+PAGED_CODE_SEG
+VOID Dsp_EvtPropertyResourceGroup(
+ _In_ ACXOBJECT Circuit,
+ _In_ WDFREQUEST Request
+)
+{
+ PAGED_CODE();
+
+ ACX_REQUEST_PARAMETERS params;
+ ACX_REQUEST_PARAMETERS_INIT(&params);
+ AcxRequestGetParameters(Request, &params);
+
+ PAUDIORESOURCEMANAGEMENT_RESOURCEGROUP resourceGroup =
+ (PAUDIORESOURCEMANAGEMENT_RESOURCEGROUP)params.Parameters.Property.Value;
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Circuit %p received KSPROPERTY_AUDIORESOURCEMANAGEMENT_RESOURCEGROUP with group \"%ls\" %ls",
+ Circuit, resourceGroup->ResourceGroupName, resourceGroup->ResourceGroupAcquired ? L"Acquired" : L"Released");
+
+ WdfRequestComplete(Request, STATUS_SUCCESS);
+}
+
+
+PAGED_CODE_SEG
+NTSTATUS CreateRenderCircuit(
+ _In_ PACXCIRCUIT_INIT CircuitInit,
+ _In_ UNICODE_STRING CircuitName,
+ _In_ WDFDEVICE Device,
+ _Out_ ACXCIRCUIT* Circuit
+)
+{
+
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ // Circuit Component ID already assigned by the device handler
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignName(CircuitInit, &CircuitName));
+
+ //
+ // Add circuit type.
+ //
+ AcxCircuitInitSetCircuitType(CircuitInit, AcxCircuitTypeRender);
+
+ //
+ // Assign the circuit's pnp-power callbacks.
+ //
+ {
+ ACX_CIRCUIT_PNPPOWER_CALLBACKS powerCallbacks;
+ ACX_CIRCUIT_PNPPOWER_CALLBACKS_INIT(&powerCallbacks);
+ powerCallbacks.EvtAcxCircuitPowerUp = DspR_EvtCircuitPowerUp;
+ powerCallbacks.EvtAcxCircuitPowerDown = DspR_EvtCircuitPowerDown;
+ AcxCircuitInitSetAcxCircuitPnpPowerCallbacks(CircuitInit, &powerCallbacks);
+ }
+
+ //
+ // Assign the circuit's composite callbacks.
+ //
+ {
+ ACX_CIRCUIT_COMPOSITE_CALLBACKS compositeCallbacks;
+ ACX_CIRCUIT_COMPOSITE_CALLBACKS_INIT(&compositeCallbacks);
+ compositeCallbacks.EvtAcxCircuitCompositeCircuitInitialize = DspR_EvtCircuitCompositeCircuitInitialize;
+ compositeCallbacks.EvtAcxCircuitCompositeInitialize = DspR_EvtCircuitCompositeInitialize;
+ AcxCircuitInitSetAcxCircuitCompositeCallbacks(CircuitInit, &compositeCallbacks);
+ }
+
+ //
+ // Assign properties handled by the circuit.
+ //
+ {
+ ACX_PROPERTY_ITEM RenderCircuitProperties[] =
+ {
+ {
+ &KSPROPSETID_AudioResourceManagement,
+ KSPROPERTY_AUDIORESOURCEMANAGEMENT_RESOURCEGROUP,
+ ACX_PROPERTY_ITEM_FLAG_SET,
+ Dsp_EvtPropertyResourceGroup,
+ nullptr,
+ 0,
+ sizeof(AUDIORESOURCEMANAGEMENT_RESOURCEGROUP),
+ 0
+ },
+ };
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(CircuitInit, RenderCircuitProperties, ARRAYSIZE(RenderCircuitProperties)));
+ }
+ //
+ // Add pre-process callbacks.
+ //
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXPIN_01
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback(
+ CircuitInit,
+ Dsp_EvtStreamGetStreamCountRequestPreprocess,
+ (ACXCONTEXT)Device,
+ AcxRequestTypeProperty,
+ &KSPROPSETID_Pin,
+ KSPROPERTY_PIN_CINSTANCES));
+#endif
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXPIN_02
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback(
+ CircuitInit,
+ Dsp_EvtStreamProposeDataFormatRequestPreprocess,
+ (ACXCONTEXT)Device,
+ AcxRequestTypeProperty,
+ &KSPROPSETID_Pin,
+ KSPROPERTY_PIN_PROPOSEDATAFORMAT));
+#endif // ACX_WORKAROUND_ACXPIN_02
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback(
+ CircuitInit,
+ DspR_EvtCircuitRequestPreprocess,
+ (ACXCONTEXT)AcxRequestTypeAny, // dbg only
+ AcxRequestTypeAny,
+ NULL,
+ AcxItemIdNone));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxCreateStreamCallback(
+ CircuitInit,
+ DspR_EvtCircuitCreateStream));
+
+ /*
+ //
+ // Add properties, events and methods.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(CircuitInit,
+ CircuitProperties,
+ CircuitPropertiesCount));
+ */
+
+ //
+ // Disable ACX remote stream handling.
+ // This is for testing only b/c by creating an explicit stream-bridge below,
+ // the default ACX behavior for stream-bridge is automatically disabled.
+ //
+ AcxCircuitInitDisableDefaultStreamBridgeHandling(CircuitInit);
+
+ //
+ // Create the circuit.
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_CIRCUIT_CONTEXT);
+ attributes.EvtCleanupCallback = DspR_EvtCircuitContextCleanup;
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitCreate(Device, &attributes, &CircuitInit, Circuit));
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS AllocateFormat(
+ _In_ KSDATAFORMAT_WAVEFORMATEXTENSIBLE WaveFormat,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ WDFDEVICE Device,
+ _Out_ ACXDATAFORMAT* Format
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ ACX_DATAFORMAT_CONFIG formatCfg;
+ ACX_DATAFORMAT_CONFIG_INIT_KS(&formatCfg, &WaveFormat);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_FORMAT_CONTEXT);
+ attributes.ParentObject = Circuit;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatCreate(Device, &attributes, &formatCfg, Format));
+
+ ASSERT((*Format) != NULL);
+ DSP_FORMAT_CONTEXT* formatCtx;
+ formatCtx = GetDspFormatContext(*Format);
+ ASSERT(formatCtx);
+ UNREFERENCED_PARAMETER(formatCtx);
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS CreatePin(
+ _In_ ACX_PIN_TYPE PinType,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACX_PIN_COMMUNICATION Communication,
+ _In_ const GUID* Category,
+ _In_ ACX_PIN_CALLBACKS* PinCallbacks,
+ _In_ ULONG PinStreamCount,
+ _In_ bool Mic,
+ _Out_ ACXPIN* Pin
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ ACX_PIN_CONFIG pinCfg;
+ ACX_PIN_CONFIG_INIT(&pinCfg);
+ pinCfg.Type = PinType;
+ pinCfg.Communication = Communication;
+ pinCfg.Category = Category;
+ pinCfg.PinCallbacks = PinCallbacks;
+
+// See description in private.h
+#ifndef ACX_WORKAROUND_ACXPIN_01
+ pinCfg->MaxStreams = PinStreamCount;
+#endif
+
+ ACX_MICROPHONE_CONFIG micCfg;
+ ACX_INTERLEAVED_AUDIO_FORMAT_INFORMATION InterleavedFormat;
+
+ if (Mic)
+ {
+ ACX_MICROPHONE_CONFIG_INIT(&micCfg);
+ ACX_INTERLEAVED_AUDIO_FORMAT_INFORMATION_INIT(&InterleavedFormat);
+
+ InterleavedFormat.PrimaryChannelCount = 2;
+ InterleavedFormat.PrimaryChannelStartPosition = 0;
+ InterleavedFormat.PrimaryChannelMask = 0;
+ InterleavedFormat.InterleavedChannelCount = 2;
+ InterleavedFormat.InterleavedChannelStartPosition = 2;
+ InterleavedFormat.InterleavedChannelMask = KSAUDIO_SPEAKER_STEREO;
+
+ micCfg.InterleavedFormat = &InterleavedFormat;
+
+ pinCfg.Flags |= AcxPinConfigMicrophoneConfigSpecified;
+ pinCfg.u.MicrophoneConfig = &micCfg;
+ }
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PIN_CONTEXT);
+ attributes.EvtCleanupCallback = DspR_EvtPinContextCleanup;
+ attributes.ParentObject = Circuit;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(Circuit, &attributes, &pinCfg, Pin));
+ ASSERT(Pin != NULL);
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXPIN_01
+ {
+ PDSP_PIN_CONTEXT pinCtx = GetDspPinContext(*Pin);
+ pinCtx->MaxStreams = PinStreamCount;
+ pinCtx->CurrentStreamsCount = 0;
+ }
+#endif
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS RetrieveProperties(
+ _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig,
+ _Out_ PULONG EndpointID
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(EndpointId);
+ DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(DataPortNumber);
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ // Create object bag from the CompositeProperties
+ ACXOBJECTBAG compositeProperties;
+ ACX_OBJECTBAG_CONFIG propConfig;
+ ACX_OBJECTBAG_CONFIG_INIT(&propConfig);
+ propConfig.Handle = CircuitConfig->CompositeProperties;
+ propConfig.Flags |= AcxObjectBagConfigOpenWithHandle;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &propConfig, &compositeProperties));
+
+ auto cleanupCompositeProperties = scope_exit([=]() {
+ WdfObjectDelete(compositeProperties);
+ }
+ );
+
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveUI4(compositeProperties, &EndpointId, EndpointID));
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DetermineSpecialStreamDetailsFromVendorProperties(
+ _In_ ACXCIRCUIT Circuit,
+ _In_ AcpiReader * Acpi,
+ _In_ HANDLE CircuitPropertiesHandle
+ )
+{
+ DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(VendorPropertiesBlock);
+ WDFMEMORY vendorPropertiesBlock = NULL;
+ DSP_CIRCUIT_CONTEXT* circuitCtx;
+ NTSTATUS status = STATUS_SUCCESS;
+ PSDCA_PATH_DESCRIPTORS2 pPathDesc2 = nullptr;
+
+ PAGED_CODE();
+
+ ACX_OBJECTBAG_CONFIG propConfig;
+ ACXOBJECTBAG circuitProperties;
+ ACX_OBJECTBAG_CONFIG_INIT(&propConfig);
+ propConfig.Handle = CircuitPropertiesHandle;
+ propConfig.Flags |= AcxObjectBagConfigOpenWithHandle;
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &propConfig, &circuitProperties));
+
+ auto cleanupPropConfig = scope_exit([=]()
+ {
+ WdfObjectDelete(circuitProperties);
+ });
+
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveBlob(circuitProperties, &VendorPropertiesBlock, NULL, &vendorPropertiesBlock));
+
+ auto cleanup1 = scope_exit([&vendorPropertiesBlock] ()
+ {
+ if (vendorPropertiesBlock != NULL)
+ {
+ WdfObjectDelete(vendorPropertiesBlock);
+ vendorPropertiesBlock = NULL;
+ }
+ });
+
+ //
+ // The below code would be replaced in a real DSP driver (or modified to use vendor-specific properties)
+ //
+ circuitCtx = GetDspCircuitContext(Circuit);
+ ASSERT(circuitCtx);
+
+ for (ULONG i = (UINT)SpecialStreamTypeUltrasoundRender; i < (UINT)SpecialStreamType_Count; i++)
+ {
+ SDCA_PATH path = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE)i);
+ ULONG propertyValue = 0;
+ char propertyName[256];
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-size", path));
+
+ // Sample driver uses this proeprty to determine whether to use PathDescriptor2 or PathDescriptor
+ NTSTATUS tempStatus = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue);
+ if (!NT_SUCCESS(tempStatus))
+ {
+ // This special stream is either not supported or does not use PathDescriptor2
+ continue;
+ }
+
+ pPathDesc2 = (PSDCA_PATH_DESCRIPTORS2)ExAllocatePool2(
+ POOL_FLAG_NON_PAGED,
+ propertyValue,
+ DRIVER_TAG);
+ if (pPathDesc2 == nullptr)
+ {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto exit;
+ }
+
+ auto cleanup2 = scope_exit([&pPathDesc2]()
+ {
+ if (pPathDesc2 != NULL)
+ {
+ ExFreePool(pPathDesc2);
+ pPathDesc2 = NULL;
+ }
+ });
+
+ pPathDesc2->Size = propertyValue;
+ pPathDesc2->Version = SDCA_PATH_DESCRIPTOR2_VERSION_1;
+ pPathDesc2->SdcaPath = path;
+
+ // Since we found one specialstream property, all others are required to be present
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-endpoint-id", path));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->EndpointId = propertyValue;
+
+ pPathDesc2->SpecialPathFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-channels", path));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->SpecialPathFormat.Format.nChannels = (WORD)propertyValue;
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-bits-per-sample", path));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->SpecialPathFormat.Format.wBitsPerSample = (WORD)propertyValue;
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-samples-per-sec", path));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->SpecialPathFormat.Format.nSamplesPerSec = propertyValue;
+ pPathDesc2->SpecialPathFormat.Format.nBlockAlign = pPathDesc2->SpecialPathFormat.Format.nChannels * pPathDesc2->SpecialPathFormat.Format.wBitsPerSample;
+ pPathDesc2->SpecialPathFormat.Format.nAvgBytesPerSec = pPathDesc2->SpecialPathFormat.Format.nSamplesPerSec * pPathDesc2->SpecialPathFormat.Format.nBlockAlign;
+ pPathDesc2->SpecialPathFormat.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-valid-bits-per-sample", path));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->SpecialPathFormat.Samples.wValidBitsPerSample = (WORD)propertyValue;
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-channel-mask", path));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->SpecialPathFormat.dwChannelMask = propertyValue;
+ pPathDesc2->SpecialPathFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-count", path));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->DescriptorCount = propertyValue;
+
+ for (ULONG j = 0; j < pPathDesc2->DescriptorCount; j++)
+ {
+ pPathDesc2->Descriptor[j].Size = sizeof(pPathDesc2->Descriptor[0]);
+ pPathDesc2->Descriptor[j].Version = SDCA_PATH_DESCRIPTOR2_VERSION_1;
+
+
+ // In this sample, we are getting the function informaiton id from audio composition data, however, this id is
+ // generated at runtime so the real drivers would have information like function number, peripheral id etc. in
+ // its composition data and then use that to map it to a function information id by querying down stream circuit.
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-func-info-id", path, j));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->Descriptor[j].FunctionInformationId = propertyValue;
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-terminal-id", path, j));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->Descriptor[j].TerminalEntityId = propertyValue;
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-dp-map", path, j));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->Descriptor[j].DataPortMap = propertyValue;
+
+ // DataPortMap indicates which DPIndex entries are used, in this sample we'll only use
+ // a single data port and that will be DPIndex_A.
+ pPathDesc2->Descriptor[j].DataPortConfig[0].Size = sizeof(pPathDesc2->Descriptor[0].DataPortConfig);
+ pPathDesc2->Descriptor[j].DataPortConfig[0].EndpointId = pPathDesc2->EndpointId;
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-dp-index-0x0-dp-number", path, j));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->Descriptor[j].DataPortConfig[0].DataPortNumber = propertyValue;
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-dp-index-0x0-dp-modes", path, j));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->Descriptor[j].DataPortConfig[0].Modes = propertyValue;
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-dp-index-0x0-dp-channel-mask", path, j));
+ RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue));
+ pPathDesc2->Descriptor[j].DataPortConfig[0].ChannelMask = propertyValue;
+ }
+
+ // Now save it to circuitCtx
+ circuitCtx->SpecialStreamPathDescriptors2[i] = pPathDesc2;
+ cleanup2.release();
+ }
+
+exit:
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS CreateStreamBridge(
+ _In_ ACX_STREAM_BRIDGE_CONFIG StreamCfg,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACXPIN Pin,
+ _In_ DSP_PIN_CONTEXT* PinCtx,
+ _In_ ULONG BridgeDataPortNumber,
+ _In_ ULONG BridgeEndpointId,
+ _In_opt_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors,
+ _In_ BOOL Render
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(EndpointId);
+ DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(DataPortNumber);
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ attributes.ParentObject = Pin;
+
+ ACX_OBJECTBAG_CONFIG objBagCfg;
+ ACXOBJECTBAG objBag = NULL;
+ ACX_OBJECTBAG_CONFIG_INIT(&objBagCfg);
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = Circuit;
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagCreate(&attributes, &objBagCfg, &objBag));
+
+ DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, TestUI4);
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(objBag, &TestUI4, _DSP_STREAM_PROPERTY_UI4_VALUE));
+
+ // EndpointId, DataPortNumber, and DPNo included for backwards compatibility.
+ // If SdcaPropertyPathDescriptors2 is included in the object bag, these will be ignored.
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(objBag, &EndpointId, BridgeEndpointId));
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(objBag, &DataPortNumber, BridgeDataPortNumber));
+
+ DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(DPNo);
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(objBag, &DPNo, BridgeDataPortNumber));
+
+ if (PathDescriptors && PathDescriptors->Size >= sizeof(SDCA_PATH_DESCRIPTORS2))
+ {
+ // For uniform aggregated devices and non-aggregated devices, we can save the SdcaPropertyPathDescriptors2
+ // now to the stream bridge.
+
+ // If the aggregated devices have different configurations (such as a different Channel Mask) the
+ // SdcaPropertyPathDescriptors2 should be added to the stream bridge when the pin is connected, since the
+ // FunctionInformationId is determined at run time based on the order that the aggregated devices are discovered.
+
+ // Apply the EndpointID to the PathDescriptors structures
+ PathDescriptors->EndpointId = BridgeEndpointId;
+
+ // The PathDescriptors->Descriptor[n].DataPortConfig[m].EndpointId value is ignored
+
+ WDFMEMORY pathDescriptorsMemory;
+ RETURN_NTSTATUS_IF_FAILED(WdfMemoryCreatePreallocated(WDF_NO_OBJECT_ATTRIBUTES, PathDescriptors, PathDescriptors->Size, &pathDescriptorsMemory));
+ auto memory_free = scope_exit([&pathDescriptorsMemory]()
+ {
+ WdfObjectDelete(pathDescriptorsMemory);
+ pathDescriptorsMemory = nullptr;
+ });
+
+ // For sample simplicity we always add the path descriptors here.
+ // If the EvtPinConnected discovers connected aggregated audio functions it will overwrite this.
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddBlob(objBag, &SdcaPropertyPathDescriptors2, pathDescriptorsMemory));
+ }
+
+ // Save the Object Bag that's being assigned to the stream bridge
+ // This will be updated at Pin Connect time if the connected endpoint is aggregated and uses
+ // different data ports for each of the aggregated audio functions
+ // The AcxObjectBag's lifetime is tied to the Circuit, so the Pin will be able to access it
+ // for the Pin's entire lifetime.
+ PinCtx->HostStreamObjBag = objBag;
+
+ //
+ // Add a stream BRIDGE.
+ //
+ PCGUID inModes[] =
+ {
+ &AUDIO_SIGNALPROCESSINGMODE_RAW,
+ &AUDIO_SIGNALPROCESSINGMODE_DEFAULT,
+ };
+
+ if (Render) {
+ StreamCfg.InModesCount = SIZEOF_ARRAY(inModes);
+ StreamCfg.InModes = inModes;
+ }
+
+ // Do not specify InModes for capture - this will prevent the ACX framework from adding created streams to this stream
+ // bridge automatically. We want to add the stream bridges manually since we don't want KWS streams added.
+ StreamCfg.OutMode = &AUDIO_SIGNALPROCESSINGMODE_RAW;
+ StreamCfg.OutStreamVarArguments = objBag;
+
+ // Uncomment this line to reverse the change-state sequence notifications.
+ //streamCfg.Flags |= AcxStreamBridgeInvertChangeStateSequence;
+
+ ACXSTREAMBRIDGE streamBridge = NULL;
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamBridgeCreate(Circuit, &attributes, &StreamCfg, &streamBridge));
+
+ if (!Render) {
+ PinCtx->HostStreamBridge = streamBridge;
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(AcxPinAddStreamBridges(Pin, &streamBridge, 1));
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS ConnectCaptureCircuitElements(
+ _In_ ULONG ElementCount,
+ _In_reads_(ElementCount) ACXELEMENT* Elements,
+ _In_ ACXCIRCUIT Circuit
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ // connection between each element, plus the connection to the circuit,
+ // and an extra connection for the kws pin.
+ const int numElements = 3;
+ const int numConnections = numElements + 2;
+
+ //
+ // Explicitly connect the circuit/elements. Note that driver doens't
+ // need to perform this step when circuit/elements are connected in the
+ // same order as they were added to the circuit. By default ACX connects
+ // the elements starting from the sink circuit pin and ending with the
+ // source circuit pin for devices.
+ //
+ // circuit.pin[default_sink] -> 1st element.pin[default_in]
+ // 1st element.pin[default_out] -> 2nd element.pin[default_in]
+ // 2nd element.pin[default_out] -> circuit.pin[default_source]
+ //
+
+ ACX_CONNECTION connections[numConnections];
+ ACX_CONNECTION_INIT(&connections[0], Circuit, Elements[0]);
+
+ ACX_CONNECTION_INIT(&connections[1], Elements[0], Elements[ElementCount-2]);
+ ACX_CONNECTION_INIT(&connections[2], Elements[ElementCount-2], Elements[ElementCount-1]);
+ ACX_CONNECTION_INIT(&connections[3], Elements[ElementCount-1], Circuit);
+ ACX_CONNECTION_INIT(&connections[4], Elements[ElementCount-1], Circuit);
+ connections[4].ToPin.Id = 1;
+
+ //
+ // Add the connections linking circuit to elements.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddConnections(Circuit, connections, SIZEOF_ARRAY(connections)));
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS ConnectRenderCircuitElements(
+ _In_ ACXAUDIOENGINE AudioEngineElement,
+ _In_ ACXCIRCUIT Circuit
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ //
+ // Explicitly connect the circuit/elements. Note that driver doesn't
+ // need to perform this step when circuit/elements are connected in the
+ // same order as they were added to the circuit. By default ACX connects
+ // the elements starting from the sink circuit pin and ending with the
+ // source circuit pin for both render and capture devices.
+ //
+ // Circuit layout
+ // -----------------------------------------
+ // | |
+ // | -------------------- |
+ // Host -0->|-----1->| |-0-------->|-3-> Bridge Pin
+ // | | Audio Engine | |
+ // Offload -1->|-----2->| Node |-3--| |
+ // | |------------------| | |
+ // | | |
+ // Loopback <-2-|<------------------------------ | |
+ // | |
+ // | |
+ // |---------------------------------------|
+ //
+
+ ACX_CONNECTION connections[4];
+
+ ACX_CONNECTION_INIT(&connections[0], Circuit, AudioEngineElement);
+ connections[0].FromPin.Id = DspPinTypeHost;
+ connections[0].ToPin.Id = 1;
+
+ ACX_CONNECTION_INIT(&connections[1], Circuit, AudioEngineElement);
+ connections[1].FromPin.Id = DspPinTypeOffload;
+ connections[1].ToPin.Id = 2;
+
+ ACX_CONNECTION_INIT(&connections[2], AudioEngineElement, Circuit);
+ connections[2].ToPin.Id = DspPinTypeLoopback;
+ connections[2].FromPin.Id = 3;
+
+ ACX_CONNECTION_INIT(&connections[3], AudioEngineElement, Circuit);
+ connections[3].ToPin.Id = DspPinTypeBridge;
+ connections[3].FromPin.Id = 0;
+
+ //
+ // Add the connections linking circuit to elements.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddConnections(Circuit, connections, SIZEOF_ARRAY(connections)));
+
+ return status;
+
+}
+
+PAGED_CODE_SEG
+NTSTATUS CreateAudioEngine(
+ _In_ ACXCIRCUIT Circuit,
+ _In_reads_(DspPinType_Count) ACXPIN* Pins,
+ _Out_ ACXAUDIOENGINE* AudioEngineElement
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ /////////////////////////////////////////////////////////
+ //
+ // Create two elements to handle volume and mute for the audioengine
+ // element
+
+ // Mute
+ ACX_MUTE_CALLBACKS muteCallbacks;
+ ACX_MUTE_CALLBACKS_INIT(&muteCallbacks);
+ muteCallbacks.EvtAcxMuteAssignState = DspR_EvtMuteAssignState;
+ muteCallbacks.EvtAcxMuteRetrieveState = DspR_EvtMuteRetrieveState;
+
+ ACX_MUTE_CONFIG muteCfg;
+ ACX_MUTE_CONFIG_INIT(&muteCfg);
+ muteCfg.ChannelsCount = MAX_CHANNELS;
+ muteCfg.Name = &KSAUDFNAME_WAVE_MUTE;
+ muteCfg.Callbacks = &muteCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_MUTE_ELEMENT_CONTEXT);
+ attributes.ParentObject = Circuit;
+
+ ACXMUTE muteElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxMuteCreate(Circuit, &attributes, &muteCfg, &muteElement));
+
+ // Volume
+ ACX_VOLUME_CALLBACKS volumeCallbacks;
+ ACX_VOLUME_CALLBACKS_INIT(&volumeCallbacks);
+ volumeCallbacks.EvtAcxRampedVolumeAssignLevel = DspR_EvtRampedVolumeAssignLevel;
+ volumeCallbacks.EvtAcxVolumeRetrieveLevel = DspR_EvtVolumeRetrieveLevel;
+
+ ACX_VOLUME_CONFIG volumeCfg;
+ ACX_VOLUME_CONFIG_INIT(&volumeCfg);
+ volumeCfg.ChannelsCount = MAX_CHANNELS;
+ volumeCfg.Minimum = VOLUME_LEVEL_MINIMUM;
+ volumeCfg.Maximum = VOLUME_LEVEL_MAXIMUM;
+ volumeCfg.SteppingDelta = VOLUME_STEPPING;
+ volumeCfg.Name = &KSAUDFNAME_VOLUME_CONTROL;
+ volumeCfg.Callbacks = &volumeCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_VOLUME_ELEMENT_CONTEXT);
+ attributes.ParentObject = Circuit;
+
+ ACXVOLUME volumeElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxVolumeCreate(Circuit, &attributes, &volumeCfg, &volumeElement));
+
+ //
+ // Create peakmeter element for Audio engine
+ //
+ ACX_PEAKMETER_CALLBACKS peakmeterCallbacks;
+ ACX_PEAKMETER_CALLBACKS_INIT(&peakmeterCallbacks);
+ peakmeterCallbacks.EvtAcxPeakMeterRetrieveLevel = DspR_EvtPeakMeterRetrieveLevelCallback;
+
+ ACX_PEAKMETER_CONFIG peakmeterCfg;
+ ACX_PEAKMETER_CONFIG_INIT(&peakmeterCfg);
+ peakmeterCfg.ChannelsCount = MAX_CHANNELS;
+ peakmeterCfg.Minimum = PEAKMETER_MINIMUM;
+ peakmeterCfg.Maximum = PEAKMETER_MAXIMUM;
+ peakmeterCfg.SteppingDelta = PEAKMETER_STEPPING_DELTA;
+ peakmeterCfg.Callbacks = &peakmeterCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PEAKMETER_ELEMENT_CONTEXT);
+ attributes.ParentObject = Circuit;
+
+ ACXPEAKMETER peakmeterElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxPeakMeterCreate(Circuit, &attributes, &peakmeterCfg, &peakmeterElement));
+ ASSERT(peakmeterElement != NULL);
+
+ PDSP_PEAKMETER_ELEMENT_CONTEXT peakmeterCtx;
+ peakmeterCtx = GetDspPeakMeterElementContext(peakmeterElement);
+ ASSERT(peakmeterCtx);
+ peakmeterCtx->peakMeter = GetDspCircuitContext(Circuit)->peakMeter;
+
+ GetDspCircuitContext(Circuit)->PeakMeterElement = peakmeterElement;
+
+ //
+ // Create Audio Engine
+ //
+ ACX_AUDIOENGINE_CALLBACKS audioEngineCallbacks;
+ ACX_AUDIOENGINE_CALLBACKS_INIT(&audioEngineCallbacks);
+ audioEngineCallbacks.EvtAcxAudioEngineRetrieveBufferSizeLimits = DspR_EvtAcxAudioEngineRetrieveBufferSizeLimits;
+ audioEngineCallbacks.EvtAcxAudioEngineAssignEffectsState = DspR_EvtAcxAudioEngineAssignEffectsState;
+ audioEngineCallbacks.EvtAcxAudioEngineRetrieveEffectsState = DspR_EvtAcxAudioEngineRetrieveEffectsState;
+ audioEngineCallbacks.EvtAcxAudioEngineRetrieveEngineMixFormat = DspR_EvtAcxAudioEngineRetrieveEngineMixFormat;
+ audioEngineCallbacks.EvtAcxAudioEngineAssignEngineDeviceFormat = DspR_EvtAcxAudioEngineAssignEngineDeviceFormat;
+
+ ACX_AUDIOENGINE_CONFIG audioEngineCfg;
+ ACX_AUDIOENGINE_CONFIG_INIT(&audioEngineCfg);
+ audioEngineCfg.HostPin = Pins[DspPinTypeHost];
+ audioEngineCfg.OffloadPin = Pins[DspPinTypeOffload];
+ audioEngineCfg.LoopbackPin = Pins[DspPinTypeLoopback];
+ audioEngineCfg.VolumeElement = volumeElement;
+ audioEngineCfg.MuteElement = muteElement;
+ audioEngineCfg.PeakMeterElement = peakmeterElement;
+ audioEngineCfg.Callbacks = &audioEngineCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ENGINE_CONTEXT);
+ attributes.ParentObject = Circuit;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxAudioEngineCreate(Circuit, &attributes, &audioEngineCfg, AudioEngineElement));
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS SendProperty(
+ _In_ WDFOBJECT AcxTarget,
+ _Inout_ PACX_REQUEST_PARAMETERS PropertyParameters,
+ _Out_opt_ PULONG_PTR Information
+)
+{
+ PAGED_CODE();
+
+ if (Information)
+ {
+ *Information = 0;
+ }
+
+ //
+ // First step: Determine the WDFIOTARGET to which the property request will be sent
+ //
+ WDFIOTARGET ioTarget = nullptr;
+ if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypePin)
+ {
+ ioTarget = AcxTargetPinGetWdfIoTarget((ACXTARGETPIN)AcxTarget);
+ }
+ else if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypeElement)
+ {
+ ioTarget = AcxTargetElementGetWdfIoTarget((ACXTARGETELEMENT)AcxTarget);
+ }
+ else if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypeCircuit)
+ {
+ ioTarget = AcxTargetCircuitGetWdfIoTarget((ACXTARGETCIRCUIT)AcxTarget);
+ }
+ else
+ {
+ RETURN_NTSTATUS(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Create the request
+ //
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = AcxTarget;
+
+ WDFREQUEST request;
+ RETURN_NTSTATUS_IF_FAILED(WdfRequestCreate(&attributes, ioTarget, &request));
+ auto request_free = scope_exit([&request]()
+ {
+ WdfObjectDelete(request);
+ request = nullptr;
+ });
+
+ //
+ // ACX framework will format the request properly depending on the type of the target
+ //
+ if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypePin)
+ {
+ RETURN_NTSTATUS_IF_FAILED(AcxTargetPinFormatRequestForProperty((ACXTARGETPIN)AcxTarget, request, PropertyParameters));
+ }
+ else if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypeElement)
+ {
+ RETURN_NTSTATUS_IF_FAILED(AcxTargetElementFormatRequestForProperty((ACXTARGETELEMENT)AcxTarget, request, PropertyParameters));
+ }
+ else if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypeCircuit)
+ {
+ RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitFormatRequestForProperty((ACXTARGETCIRCUIT)AcxTarget, request, PropertyParameters));
+ }
+
+ //
+ // Send the request synchronously, with a timeout
+ //
+ WDF_REQUEST_SEND_OPTIONS sendOptions;
+ 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));
+
+ if (!WdfRequestSend(request, ioTarget, &sendOptions))
+ {
+ //
+ // The framework failed to send the request.
+ //
+ RETURN_NTSTATUS_IF_FAILED(WdfRequestGetStatus(request));
+ }
+
+ //
+ // The request was successfully delivered and handled. The status will be based on the target's handling
+ //
+ if (Information)
+ {
+ *Information = WdfRequestGetInformation(request);
+ }
+
+ return WdfRequestGetStatus(request);
+}
+
+// Nonpaged, since this will be called in power up situations
+#pragma code_seg()
+VOID CircuitRequestPreprocess(
+ _In_ ACXOBJECT Object,
+ _In_ ACXCONTEXT DriverContext,
+ _In_ WDFREQUEST Request
+ )
+/*++
+
+Routine Description:
+
+ This Circuit Request Preprocess routine will forward any Volume
+ or Mute requests to the appropriate downstream circuit, if we've
+ discovered a downstream circuit that handles Volume and Mute
+
+--*/
+{
+ NTSTATUS status = STATUS_NOT_SUPPORTED;
+ ACX_REQUEST_PARAMETERS params;
+ ACX_REQUEST_PARAMETERS targetParams;
+ PDSP_CIRCUIT_CONTEXT circuitCtx;
+ ACXELEMENT element;
+ ULONG_PTR information = 0;
+ ACXTARGETELEMENT targetElement = nullptr;
+ GUID propertySet;
+ ULONG propertyId;
+ BOOLEAN isMute = FALSE;
+ BOOLEAN isVolume = FALSE;
+
+ // Preprocess will be called very frequently. Don't trace enter/exit.
+ //DrvLogEnter(g_SDCAVDspLog);
+
+ UNREFERENCED_PARAMETER(DriverContext);
+
+ ASSERT(Object != NULL);
+ ASSERT(DriverContext);
+ ASSERT(Request);
+
+ ACX_REQUEST_PARAMETERS_INIT(&params);
+ AcxRequestGetParameters(Request, &params);
+
+ propertySet = params.Parameters.Property.Set;
+ propertyId = params.Parameters.Property.Id;
+ circuitCtx = GetDspCircuitContext(Object);
+
+ if (circuitCtx == nullptr ||
+ params.Parameters.Property.ItemType != AcxItemTypeElement)
+ {
+ // We only handle requests for our render circuit (which must have our context)
+ // We only forward element requests to the child paths
+ (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request);
+ return;
+ }
+
+ if (IsEqualGUID(propertySet, KSPROPSETID_Audio) && propertyId == KSPROPERTY_AUDIO_VOLUMELEVEL)
+ {
+ isVolume = TRUE;
+ }
+ else if (IsEqualGUID(propertySet, KSPROPSETID_Audio) && propertyId == KSPROPERTY_AUDIO_MUTE)
+ {
+ isMute = TRUE;
+ }
+ // Do not forward KSPROPERTY_AUDIOENGINE_VOLUMELEVEL - that is only valid for a stream property.
+
+ if (!isVolume && !isMute)
+ {
+ // Only handle Volume and Mute requests
+ (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request);
+ return;
+ }
+
+ element = AcxCircuitGetElementById((ACXCIRCUIT)Object, params.Parameters.Property.ItemId);
+ if (!element)
+ {
+ // We only handle requests for the volume or mute elements, and this isn't an element
+ (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request);
+ return;
+ }
+
+ if (isVolume)
+ {
+ targetElement = circuitCtx->TargetVolumeHandler;
+ }
+ else if (isMute)
+ {
+ targetElement = circuitCtx->TargetMuteHandler;
+ }
+
+ if (targetElement == nullptr)
+ {
+ // We only handle requests for the volume or mute elements if we have a target.
+ (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request);
+ return;
+ }
+
+ if (isVolume && (GetDspVolumeElementContext(element) == nullptr && GetDspEngineContext(element) == nullptr))
+ {
+ // Volume request that isn't for our volume or audioengine element?
+ (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request);
+ return;
+ }
+
+ if (isMute && (GetDspMuteElementContext(element) == nullptr && GetDspEngineContext(element) == nullptr))
+ {
+ // Mute request that isn't for our mute or audioengine element?
+ (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request);
+ return;
+ }
+
+ propertySet = params.Parameters.Property.Set;
+ propertyId = params.Parameters.Property.Id;
+
+ ACX_REQUEST_PARAMETERS_INIT_PROPERTY(&targetParams,
+ propertySet,
+ propertyId,
+ params.Parameters.Property.Verb,
+ params.Parameters.Property.ItemType,
+ AcxTargetElementGetId(targetElement),
+ params.Parameters.Property.Control,
+ params.Parameters.Property.ControlCb,
+ params.Parameters.Property.Value,
+ params.Parameters.Property.ValueCb);
+
+ status = SendProperty(targetElement, &targetParams, &information);
+
+ WdfRequestCompleteWithInformation(Request, status, information);
+}
+
+PAGED_CODE_SEG
+NTSTATUS CreateTargetCircuit(
+ _In_ ACXCIRCUIT Circuit,
+ _In_ PKSPIN_PHYSICALCONNECTION Connection,
+ _In_ ULONG ConnectionSize,
+ _Out_ ACXTARGETCIRCUIT * TargetCircuit
+)
+{
+ PAGED_CODE();
+
+ // We have the physical connection. Create a target circuit for it.
+ size_t symbolicLinkSize;
+ // Size of the string is no more than the total size of the value returned, less the size of the physicalconnection struct,
+ // plus the first character of the link (which is included in the physicalconnection struct)
+ symbolicLinkSize = ConnectionSize - sizeof(KSPIN_PHYSICALCONNECTION) + sizeof(WCHAR);
+ if (symbolicLinkSize > USHORT_MAX)
+ {
+ // Symbolic Link has to fit in UNICODE_STRING which uses USHORT to hold Length/MaximumLength
+ RETURN_NTSTATUS_MSG(STATUS_UNSUCCESSFUL, L"Physical connection too large for unicode_string %lld", symbolicLinkSize);
+ }
+
+ UNICODE_STRING symbolicLink{ 0 };
+ symbolicLink.MaximumLength = (USHORT)symbolicLinkSize;
+ symbolicLink.Buffer = Connection->SymbolicLinkName;
+ // preload the length
+ (void)RtlStringCbLengthW(symbolicLink.Buffer, symbolicLink.MaximumLength, &symbolicLinkSize);
+ symbolicLink.Length = (USHORT)symbolicLinkSize;
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = Circuit;
+
+ WDFSTRING link;
+ RETURN_NTSTATUS_IF_FAILED(WdfStringCreate(&symbolicLink, &attributes, &link));
+ auto link_free = scope_exit([&link]()
+ {
+ if (link)
+ {
+ WdfObjectDelete(link);
+ link = nullptr;
+ }
+ });
+
+ ACX_TARGET_CIRCUIT_CONFIG targetCktCfg;
+ ACX_TARGET_CIRCUIT_CONFIG_INIT(&targetCktCfg);
+ targetCktCfg.SymbolicLinkName = link;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = Circuit;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitCreate(AcxCircuitGetWdfDevice(Circuit), &attributes, &targetCktCfg, TargetCircuit));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS FindDownstreamVolumeMute(
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACXTARGETCIRCUIT TargetCircuit
+)
+{
+ NTSTATUS status;
+ PDSP_CIRCUIT_CONTEXT circuitCtx;
+ ACX_REQUEST_PARAMETERS params;
+
+ PAGED_CODE();
+
+ 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(&params,
+ KSPROPSETID_Pin,
+ KSPROPERTY_PIN_DATAFLOW,
+ AcxPropertyVerbGet,
+ AcxItemTypePin,
+ AcxTargetPinGetId(targetPin),
+ nullptr, 0,
+ &targetPinFlow,
+ sizeof(targetPinFlow));
+
+ RETURN_NTSTATUS_IF_FAILED(SendProperty(targetPin, &params, 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(&params,
+ KSPROPSETID_Pin,
+ KSPROPERTY_PIN_PHYSICALCONNECTION,
+ AcxPropertyVerbGet,
+ AcxItemTypePin,
+ AcxTargetPinGetId(targetPin),
+ nullptr, 0,
+ pinConnection,
+ pinConnectionSize);
+
+ status = SendProperty(targetPin, &params, &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)
+ {
+ circuitCtx->TargetCircuitToDelete = 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;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+ReplicateFormatsForAudioEngine(
+ _In_ ACXAUDIOENGINE AudioEngine,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ ULONG TargetPinId
+)
+{
+ PAGED_CODE();
+
+ ACXTARGETPIN targetPin;
+ targetPin = AcxTargetCircuitGetTargetPin(TargetCircuit, TargetPinId);
+ if (!targetPin)
+ {
+ RETURN_NTSTATUS(STATUS_UNSUCCESSFUL);
+ }
+
+ ACXDATAFORMATLIST targetFormatList;
+ // We expect at least Raw format in SDCA downstream circuits
+ RETURN_NTSTATUS_IF_FAILED(AcxTargetPinRetrieveModeDataFormatList(targetPin, &AUDIO_SIGNALPROCESSINGMODE_RAW, &targetFormatList));
+
+ ACXDATAFORMATLIST localFormatList = AcxAudioEngineGetDeviceFormatList(AudioEngine);
+
+ RETURN_NTSTATUS_IF_FAILED(SdcaVad_ClearDataFormatList(localFormatList));
+
+ ULONG formatCount = 0;
+ RETURN_NTSTATUS_IF_FAILED(SdcaVad_CopyFormats(targetFormatList, localFormatList, &formatCount));
+
+ if (formatCount == 0)
+ {
+ RETURN_NTSTATUS(STATUS_NO_MATCH);
+ }
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+ReplicateFormatsForPin(
+ _In_ ACXPIN Pin,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ ULONG TargetPinId
+)
+{
+ PAGED_CODE();
+
+ ACXTARGETPIN targetPin;
+ targetPin = AcxTargetCircuitGetTargetPin(TargetCircuit, TargetPinId);
+ if (!targetPin)
+ {
+ RETURN_NTSTATUS(STATUS_UNSUCCESSFUL);
+ }
+
+ // Don't delete the target pin - it will be cleaned up when the target circuit is cleaned up by ACX
+
+ GUID targetModes[] =
+ {
+ AUDIO_SIGNALPROCESSINGMODE_RAW,
+ AUDIO_SIGNALPROCESSINGMODE_DEFAULT,
+ AUDIO_SIGNALPROCESSINGMODE_COMMUNICATIONS,
+ AUDIO_SIGNALPROCESSINGMODE_SPEECH
+ };
+
+ ULONG totalFormats = 0;
+
+ for (ULONG modeIdx = 0; modeIdx < ARRAYSIZE(targetModes); ++modeIdx)
+ {
+ ACXDATAFORMATLIST targetFormatList;
+ ACXDATAFORMATLIST localFormatList = nullptr;
+
+ NTSTATUS status = AcxTargetPinRetrieveModeDataFormatList(targetPin, targetModes + modeIdx, &targetFormatList);
+ if (!NT_SUCCESS(status))
+ {
+ // If the downstream pin doesn't support any formats for this mode, make sure we clear out our pin's
+ // formats for this mode as well.
+ if (modeIdx == 0)
+ {
+ localFormatList = AcxPinGetRawDataFormatList(Pin);
+ }
+ else
+ {
+ // Ignore the status
+ AcxPinRetrieveModeDataFormatList(Pin, targetModes + modeIdx, &localFormatList);
+ }
+ if (localFormatList)
+ {
+ RETURN_NTSTATUS_IF_FAILED(SdcaVad_ClearDataFormatList(localFormatList));
+ }
+ continue;
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(SdcaVad_RetrieveOrCreateDataFormatList(Pin, targetModes + modeIdx, &localFormatList));
+
+ RETURN_NTSTATUS_IF_FAILED(SdcaVad_ClearDataFormatList(localFormatList));
+
+ ULONG formatCount = 0;
+ RETURN_NTSTATUS_IF_FAILED(SdcaVad_CopyFormats(targetFormatList, localFormatList, &formatCount));
+
+ totalFormats += formatCount;
+ }
+
+ if (totalFormats == 0)
+ {
+ return STATUS_NO_MATCH;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.h
new file mode 100644
index 00000000..9324e7eb
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.h
@@ -0,0 +1,135 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ CircuitHelper.h
+
+Abstract:
+
+ This module contains helper functions for render.cpp and capture.cpp files.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "AcpiReader.h"
+
+using namespace ACPIREADER;
+
+PAGED_CODE_SEG
+NTSTATUS CreateCaptureCircuit(
+ _In_ PACXCIRCUIT_INIT CircuitInit,
+ _In_ UNICODE_STRING CircuitName,
+ _In_ WDFDEVICE Device,
+ _Out_ ACXCIRCUIT* Circuit
+);
+
+PAGED_CODE_SEG
+NTSTATUS CreateRenderCircuit(
+ _In_ PACXCIRCUIT_INIT CircuitInit,
+ _In_ UNICODE_STRING CircuitName,
+ _In_ WDFDEVICE Device,
+ _Out_ ACXCIRCUIT* Circuit
+);
+
+PAGED_CODE_SEG
+NTSTATUS AllocateFormat(
+ _In_ KSDATAFORMAT_WAVEFORMATEXTENSIBLE WaveFormat,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ WDFDEVICE Device,
+ _Out_ ACXDATAFORMAT* Format
+);
+
+PAGED_CODE_SEG
+NTSTATUS CreatePin(
+ _In_ ACX_PIN_TYPE PinType,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACX_PIN_COMMUNICATION Communication,
+ _In_ const GUID* Category,
+ _In_ ACX_PIN_CALLBACKS* PinCallbacks,
+ _In_ ULONG PinStreamCount,
+ _In_ bool Mic,
+ _Out_ ACXPIN* Pin
+);
+
+PAGED_CODE_SEG
+NTSTATUS RetrieveProperties(
+ _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig,
+ _In_ PULONG EndpointID
+);
+
+PAGED_CODE_SEG
+NTSTATUS
+DetermineSpecialStreamDetailsFromVendorProperties(
+ _In_ ACXCIRCUIT Circuit,
+ _In_ AcpiReader * Acpi,
+ _In_ HANDLE CircuitPropertiesHandle
+);
+
+PAGED_CODE_SEG
+NTSTATUS CreateStreamBridge(
+ _In_ ACX_STREAM_BRIDGE_CONFIG StreamCfg,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACXPIN Pin,
+ _In_ DSP_PIN_CONTEXT* PinCtx,
+ _In_ ULONG BridgeDataPortNumber,
+ _In_ ULONG BridgeEndpointId,
+ _In_opt_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors,
+ _In_ BOOL Render
+);
+
+PAGED_CODE_SEG
+NTSTATUS ConnectCaptureCircuitElements(
+ _In_ ULONG ElementCount,
+ _In_reads_(ElementCount) ACXELEMENT* Elements,
+ _In_ ACXCIRCUIT Circuit
+);
+
+PAGED_CODE_SEG
+NTSTATUS ConnectRenderCircuitElements(
+ _In_ ACXAUDIOENGINE AudioEngineElement,
+ _In_ ACXCIRCUIT Circuit
+);
+
+PAGED_CODE_SEG
+NTSTATUS CreateAudioEngine(
+ _In_ ACXCIRCUIT Circuit,
+ _In_reads_(DspPinType_Count) ACXPIN* Pins,
+ _Out_ ACXAUDIOENGINE* AudioEngineElement
+);
+
+#pragma code_seg()
+VOID CircuitRequestPreprocess(
+ _In_ ACXOBJECT Object,
+ _In_ ACXCONTEXT DriverContext,
+ _In_ WDFREQUEST Request
+);
+
+PAGED_CODE_SEG
+NTSTATUS FindDownstreamVolumeMute(
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACXTARGETCIRCUIT TargetCircuit
+);
+
+PAGED_CODE_SEG
+NTSTATUS
+ReplicateFormatsForAudioEngine(
+ _In_ ACXAUDIOENGINE AudioEngine,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ ULONG TargetPinId
+);
+
+PAGED_CODE_SEG
+NTSTATUS
+ReplicateFormatsForPin(
+ _In_ ACXPIN Pin,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ ULONG TargetPinId
+);
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.cpp
new file mode 100644
index 00000000..3611a2c1
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.cpp
@@ -0,0 +1,1053 @@
+/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ KeywordDetector.cpp
+
+Abstract:
+
+ Sample keyword detector management.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include "stdunk.h"
+#include <ks.h>
+#include <mmsystem.h>
+#include <ksmedia.h>
+#include "streamengine.h"
+
+#include "KeywordDetector.h"
+
+#ifndef __INTELLISENSE__
+#include "KeywordDetector.tmh"
+#endif
+
+
+#pragma code_seg("PAGE")
+CKeywordDetector::CKeywordDetector(
+ _In_ WDFDEVICE Device,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ WAVEFORMATEXTENSIBLE *DetectionFormat
+)
+ :
+ m_streamRunning(FALSE),
+ m_qpcStartCapture(0),
+ m_nLastQueuedPacket(-1),
+ m_SoundDetectorArmed1(FALSE),
+ m_SoundDetectorArmed2(FALSE),
+ m_SoundDetectorData1(0),
+ m_SoundDetectorData2(0),
+ m_ullKeywordStartTimestamp(0),
+ m_ullKeywordStopTimestamp(0),
+ m_Device(Device),
+ m_Circuit(Circuit),
+ m_Prepared(FALSE),
+ m_Suspended(FALSE),
+ m_dispatchThread(nullptr),
+ m_FunctionInformation(nullptr),
+ m_Initialized(FALSE)
+{
+ PAGED_CODE();
+ DSP_CIRCUIT_CONTEXT *circuitCtx;
+
+ memcpy(&(m_PrepareParams.DetectionFormat), DetectionFormat, sizeof(WAVEFORMATEXTENSIBLE));
+ circuitCtx = GetDspCircuitContext(m_Circuit);
+ m_PrepareParams.EndpointId = circuitCtx->EndpointId;
+
+ // Assume streaming (bypass) mode
+ m_PrepareParams.VadMode = VadModeStreaming;
+
+ KeInitializeEvent(&(m_Events.Suspend), SynchronizationEvent, FALSE);
+ KeInitializeEvent(&(m_Events.Resume), SynchronizationEvent, FALSE);
+
+ // Initialize our pool of packets and the list structures
+ // The packet spin locks protect the producer/consumer relationship
+ // between the dpc routine and GetReadPacket
+ KeInitializeSpinLock(&m_PacketPoolSpinLock);
+ KeInitializeSpinLock(&m_PacketFifoSpinLock);
+
+ // The buffering state spin lock protects the state variables
+ // shared between the arm/disarm and the dpc routine
+ KeInitializeSpinLock(&m_BufferingStateSpinLock);
+
+ // current state is disarmed
+ // reset fifo and buffering state
+ UpdateBufferingState();
+}
+
+#pragma code_seg("PAGE")
+CKeywordDetector::~CKeywordDetector()
+{
+ PAGED_CODE();
+
+ m_threadExitEvent.set();
+ m_threadExitedEvent.wait();
+
+ if (m_FunctionInformation)
+ {
+ ExFreePool(m_FunctionInformation);
+ }
+
+ m_Initialized = FALSE;
+}
+
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS CKeywordDetector::Initialize()
+{
+ PAGED_CODE();
+ HANDLE handle;
+ LARGE_INTEGER qpcFrequency;
+
+ KeQueryPerformanceCounter(&qpcFrequency);
+ m_qpcFrequency = qpcFrequency.QuadPart;
+
+ // TODO: currently ignoring the results of these calls as to
+ // not break anything (since they all currently fail)
+
+ // Retrieve capabilities to know ivad/evad, and
+ // entity id's
+ RETURN_NTSTATUS_IF_FAILED(GetDeviceKwsCapabilityDescriptor(&m_CapabilityDescriptor));
+ RETURN_NTSTATUS_IF_FAILED(GetDeviceFunctionInformation(&m_FunctionInformation));
+ RETURN_NTSTATUS_IF_TRUE(0 == m_CapabilityDescriptor.DataPathsSupported, STATUS_NOT_SUPPORTED);
+
+ RETURN_NTSTATUS_IF_FAILED(GetVadDescriptor(&m_VadDescriptor));
+ RETURN_NTSTATUS_IF_FAILED(GetVadEntities(&m_VadEntities));
+
+ // create worker thread to handle suspended access
+ RETURN_NTSTATUS_IF_FAILED(PsCreateSystemThread(&handle, THREAD_ALL_ACCESS, 0, 0, 0, CKeywordDetector::s_HandleNotifications, this));
+
+ auto scope_exit([&handle]() {
+ ZwClose(handle);
+ });
+
+ RETURN_NTSTATUS_IF_FAILED(ObReferenceObjectByHandleWithTag(handle, THREAD_ALL_ACCESS, nullptr, KernelMode, KEYWORDDETECTOR_POOLTAG, (PVOID*)&m_dispatchThread, nullptr));
+
+ // set notification events
+ RETURN_NTSTATUS_IF_FAILED(SetSuspendAccessEvent(&m_Events));
+
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+void CKeywordDetector::s_HandleNotifications(PVOID context)
+{
+ PAGED_CODE();
+ auto kws = static_cast<CKeywordDetector*>(context);
+ kws->HandleNotifications();
+}
+
+_IRQL_requires_(PASSIVE_LEVEL)
+void
+CKeywordDetector::HandleNotifications()
+{
+ PAGED_CODE();
+ NTSTATUS status{ STATUS_SUCCESS };
+ PVOID waitObjects[] = { &m_Events.Suspend, &m_Events.Resume, m_threadExitEvent.get() };
+
+ // start with the even reset to indicate that the thread is running
+ m_threadExitedEvent.clear();
+ while (true)
+ {
+ status = KeWaitForMultipleObjects(3, waitObjects, WaitAny, Executive, KernelMode, FALSE, nullptr, nullptr);
+ if (STATUS_WAIT_0 == status)
+ {
+ auto lock = m_csLock.acquire();
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_Suspended = TRUE;
+#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ UpdateVadStreamState();
+ continue;
+ }
+ if (STATUS_WAIT_1 == status)
+ {
+ auto lock = m_csLock.acquire();
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_Suspended = FALSE;
+#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ UpdateVadStreamState();
+ continue;
+ }
+
+ else // consider as exit event
+ {
+ break;
+ }
+ }
+ m_threadExitedEvent.set();
+ PsTerminateSystemThread(status);
+}
+
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS CKeywordDetector::ReadKeywordTimestampRegistry()
+{
+ PAGED_CODE();
+
+ UNICODE_STRING parametersPath;
+
+ RTL_QUERY_REGISTRY_TABLE paramTable[] = {
+ // QueryRoutine Flags Name EntryContext DefaultType DefaultData DefaultLength
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"KeywordDetectorStartTimestamp", &m_ullKeywordStartTimestamp, (REG_QWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_QWORD, &m_ullKeywordStartTimestamp, sizeof(ULONGLONG) },
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"KeywordDetectorStopTimestamp", &m_ullKeywordStopTimestamp, (REG_QWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_QWORD, &m_ullKeywordStopTimestamp, sizeof(ULONGLONG) },
+ { NULL, 0, NULL, NULL, 0, NULL, 0 }
+ };
+
+ RtlInitUnicodeString(&parametersPath, NULL);
+
+ // The sizeof(WCHAR) is added to the maximum length, for allowing a space for null termination of the string.
+ parametersPath.MaximumLength =
+ g_RegistryPath.Length + sizeof(L"\\Parameters") + sizeof(WCHAR);
+
+#pragma prefast(suppress:__WARNING_ALIASED_MEMORY_LEAK, "memory is freed by scope_exit")
+ parametersPath.Buffer = (PWCH)ExAllocatePool2(PagedPool, parametersPath.MaximumLength, KEYWORDDETECTOR_POOLTAG);
+ RETURN_NTSTATUS_IF_TRUE(parametersPath.Buffer == NULL, STATUS_INSUFFICIENT_RESOURCES);
+ auto parametersPath_free = scope_exit([&parametersPath]() {
+ PAGED_CODE();
+ ExFreePool(parametersPath.Buffer);
+ });
+
+ RtlAppendUnicodeToString(&parametersPath, g_RegistryPath.Buffer);
+ RtlAppendUnicodeToString(&parametersPath, L"\\Parameters");
+
+ RETURN_NTSTATUS_IF_FAILED(RtlQueryRegistryValues(
+ RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL,
+ parametersPath.Buffer,
+ &paramTable[0],
+ NULL,
+ NULL
+ ));
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS CKeywordDetector::ResetDetector(_In_ GUID eventId)
+{
+ PAGED_CODE();
+
+ RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 &&
+ eventId != CONTOSO_KEYWORD2 &&
+ eventId != GUID_NULL,
+ STATUS_INVALID_PARAMETER);
+
+ // Initialize detector on first use, which is going to be the
+ // initial reset of the detector.
+ if(!m_Initialized)
+ {
+ RETURN_NTSTATUS_IF_FAILED(Initialize());
+ m_Initialized = TRUE;
+ }
+
+ auto lock = m_csLock.acquire();
+
+ if (eventId == CONTOSO_KEYWORD1)
+ {
+ m_SoundDetectorData1 = 0;
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_SoundDetectorArmed1 = FALSE;
+ }
+ else if(eventId == CONTOSO_KEYWORD2)
+ {
+ m_SoundDetectorData2 = 0;
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_SoundDetectorArmed2 = FALSE;
+ }
+ else if(eventId == GUID_NULL)
+ {
+ // When DownloadDetectorData is called to set the pattern for multiple keywords
+ // at once, all keyword detectors must be reset. Also used during keyword detector
+ // initialization and cleanup to restore it back to initial state and power down.
+ m_SoundDetectorData1 = 0;
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_SoundDetectorArmed1 = FALSE;
+ m_SoundDetectorData2 = 0;
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_SoundDetectorArmed2 = FALSE;
+ }
+
+#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ RETURN_NTSTATUS_IF_FAILED(UpdateVadStreamState());
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS CKeywordDetector::DownloadDetectorData(_In_ GUID eventId, _In_ LONGLONG Data)
+{
+ PAGED_CODE();
+
+ RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 &&
+ eventId != CONTOSO_KEYWORD2 &&
+ eventId != GUID_NULL,
+ STATUS_INVALID_PARAMETER);
+
+
+ // reset the detector for this event Id
+ ResetDetector(eventId);
+
+ // In this example, the driver supports detection data
+ // set with a single call for both detectors, or each
+ // detector set individually.
+ if (eventId == CONTOSO_KEYWORD1)
+ {
+ m_SoundDetectorData1 = Data;
+ }
+ else if(eventId == CONTOSO_KEYWORD2)
+ {
+ m_SoundDetectorData2 = Data;
+ }
+ else if(eventId == GUID_NULL)
+ {
+ // in this simplified example "Data" is set on both detectors,
+ // however in a real system "Data" could be a data structure which
+ // contains different values for each detector.
+ m_SoundDetectorData1 = m_SoundDetectorData2 = Data;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+// The following function is only applicable to single keyword detection systems,
+// and assumes keyword detector #1.
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS CKeywordDetector::GetDetectorData(_In_ GUID eventId, _Out_ LONGLONG *Data)
+{
+ PAGED_CODE();
+
+ RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 &&
+ eventId != CONTOSO_KEYWORD2 &&
+ eventId != GUID_NULL,
+ STATUS_INVALID_PARAMETER);
+
+ *Data = 0;
+
+ if (eventId == CONTOSO_KEYWORD1)
+ {
+ *Data = m_SoundDetectorData1;
+ }
+ else if(eventId == CONTOSO_KEYWORD2)
+ {
+ *Data = m_SoundDetectorData2;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+ULONGLONG CKeywordDetector::GetStartTimestamp()
+{
+ PAGED_CODE();
+
+ return m_ullKeywordStartTimestamp;
+}
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+ULONGLONG CKeywordDetector::GetStopTimestamp()
+{
+ PAGED_CODE();
+
+ return m_ullKeywordStopTimestamp;
+}
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS CKeywordDetector::SetArmed(_In_ GUID eventId, _In_ BOOLEAN Arm)
+{
+ PAGED_CODE();
+
+ BOOLEAN previousDetector1State = FALSE;
+ BOOLEAN previousDetector2State = FALSE;
+
+ RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 &&
+ eventId != CONTOSO_KEYWORD2 &&
+ eventId != GUID_NULL,
+ STATUS_INVALID_PARAMETER);
+
+ // lock scope enter
+ {
+ auto lock = m_csLock.acquire();
+
+ // the previous state is "armed" if either detector is armed.
+ // this reflects the fact that both detectors are sharing the
+ // same stream.
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ previousDetector1State = m_SoundDetectorArmed1;
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ previousDetector2State = m_SoundDetectorArmed2;
+
+ auto revertOnFailure = scope_exit([&]() {
+ PAGED_CODE();
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_SoundDetectorArmed1 = previousDetector1State;
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_SoundDetectorArmed2 = previousDetector2State;
+#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ UpdateVadStreamState();
+ });
+
+ if (eventId == CONTOSO_KEYWORD1)
+ {
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_SoundDetectorArmed1 = Arm;
+ }
+ else if(eventId == CONTOSO_KEYWORD2)
+ {
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ m_SoundDetectorArmed2 = Arm;
+ }
+
+#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ RETURN_NTSTATUS_IF_FAILED(UpdateVadStreamState());
+
+ revertOnFailure.release();
+ }
+
+ // Change buffering state if needed
+ UpdateBufferingState();
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS CKeywordDetector::GetArmed(_In_ GUID eventId, _Out_ BOOLEAN *Arm)
+{
+ PAGED_CODE();
+
+ RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 &&
+ eventId != CONTOSO_KEYWORD2 &&
+ eventId != GUID_NULL,
+ STATUS_INVALID_PARAMETER);
+
+ auto lock = m_csLock.acquire();
+
+ *Arm = FALSE;
+
+ if (eventId == CONTOSO_KEYWORD1)
+ {
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ *Arm = m_SoundDetectorArmed1;
+ }
+ else if(eventId == CONTOSO_KEYWORD2)
+ {
+#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ *Arm = m_SoundDetectorArmed2;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID CKeywordDetector::Run()
+{
+ PAGED_CODE();
+ m_streamRunning = TRUE;
+ UpdateBufferingState();
+}
+
+#pragma code_seg("PAGE")
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID CKeywordDetector::Stop()
+{
+ PAGED_CODE();
+ m_streamRunning = FALSE;
+ UpdateBufferingState();
+}
+
+#pragma code_seg()
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID CKeywordDetector::UpdateBufferingState()
+{
+ BOOL armed = FALSE;
+ KIRQL irql = PASSIVE_LEVEL;
+
+ {
+ auto lock = m_csLock.acquire();
+#pragma prefast(suppress:__WARNING_RACE_CONDITION, "wil::fast_mutex lacks required SAL annotation, lock is held")
+ armed = m_SoundDetectorArmed1 | m_SoundDetectorArmed2;
+ }
+
+ // acquire buffering state spin lock to synchronize state changes with the running dpc routine
+ KeAcquireSpinLock(&m_BufferingStateSpinLock, &irql);
+
+ if (armed || m_streamRunning)
+ {
+ // if we're armed or stream running, and not buffering, start buffering
+ // if m_qpcStartCapture is not 0, then it's already buffering
+ if (m_qpcStartCapture == 0)
+ {
+ m_qpcStartCapture = KeQueryPerformanceCounter(NULL).QuadPart;
+ }
+ }
+ else
+ {
+ // if we're disarmed and no stream running, reset buffering
+ m_qpcStartCapture = 0;
+ m_nLastQueuedPacket = (-1);
+ InitializeListHead(&m_PacketPoolHead);
+ InitializeListHead(&m_PacketFifoHead);
+
+ for (int i = 0; i < ARRAYSIZE(m_PacketPool); i++)
+ {
+ InsertTailList(&m_PacketPoolHead, &m_PacketPool[i].ListEntry);
+ }
+ }
+
+ KeReleaseSpinLock(&m_BufferingStateSpinLock, irql);
+
+ return;
+}
+
+#pragma code_seg()
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID CKeywordDetector::NotifyDetection()
+{
+ KIRQL irql = PASSIVE_LEVEL;
+
+ // Because we are modifying shared buffer state to simulate a notification,
+ // we need to acquire the spin lock to synchronize this with the dpc routine
+ KeAcquireSpinLock(&m_BufferingStateSpinLock, &irql);
+
+ // A detection will only happen if armed and the
+ // stream is already running. If there isn't a client
+ // running, then set the stream start time to align
+ // with this detection.
+ if (!m_streamRunning)
+ {
+ // the start capture time is now.
+ m_qpcStartCapture = KeQueryPerformanceCounter(NULL).QuadPart;
+
+ // The following code is for testing purposes only.
+ // m_qpcFrequency is defined to be the number of ticks in 1 second.
+ // Use the stream start time (the current time retrieved in StartBufferStream) to
+ // mark when the keyword ended, and the start time minus 1 second worth of ticks
+ // to mark when the keyword started. Also, adjust the stream start time to align
+ // to this new keyword start time, so that the simulated stream contains the full keyword.
+
+ m_ullKeywordStopTimestamp = m_qpcStartCapture; // stop time is the current time
+ m_qpcStartCapture = m_qpcStartCapture - m_qpcFrequency; // buffer start time is 1 second ago
+ m_ullKeywordStartTimestamp = m_qpcStartCapture; // buffer start time = keyword start time
+
+ }
+ else
+ {
+ // The following code is for testing purposes only.
+ // If the stream is running, we cannot modify qpcStartCapture to be in
+ // the past, so instead make the keyword start & stop times fit within the
+ // time period that the keyword has been running. If it has been running
+ // for more than 1 second, then set the keyword start time to be 1 second back
+ // into the stream, as though we just figured out there was a keyword there.
+ // If it has been running less than one second, then the keyword size ends
+ // up being however long the stream has been running.
+
+ LARGE_INTEGER qpc;
+ qpc = KeQueryPerformanceCounter(NULL);
+
+ m_ullKeywordStopTimestamp = qpc.QuadPart; // stop time is the current time
+
+ if (m_qpcStartCapture < (qpc.QuadPart - m_qpcFrequency))
+ {
+ m_ullKeywordStartTimestamp = (qpc.QuadPart - m_qpcFrequency);
+ }
+ else
+ {
+ m_ullKeywordStartTimestamp = m_qpcStartCapture;
+ }
+ }
+
+ KeReleaseSpinLock(&m_BufferingStateSpinLock, irql);
+
+ return;
+}
+
+#pragma code_seg()
+_IRQL_requires_min_(DISPATCH_LEVEL)
+VOID CKeywordDetector::DpcRoutine(
+ _In_ LONGLONG PerformanceCounter,
+ _In_ LONGLONG PerformanceFrequency,
+ _Out_ BOOLEAN *isRealtime,
+ _Out_ LONGLONG *NewPacketNumber,
+ _Out_ ULONGLONG *NewPerformanceCount)
+{
+ LONGLONG currentPacket;
+ LONGLONG packetsToQueue;
+
+ KIRQL irql = PASSIVE_LEVEL;
+
+ // used to synchronize buffering state variables with stream state changes,
+ // arming changes, etc.
+ KeAcquireSpinLock(&m_BufferingStateSpinLock, &irql);
+
+ *isRealtime = FALSE;
+ *NewPacketNumber = 0;
+ *NewPerformanceCount = 0;
+
+ // TODO: the timer only runs when the stream is open, but really for KWS it should be building up a collection of burst data
+ // in the queue from 1.5 sec before the trigger happens. Is there some way to simulate that behavior here? Without doing that,
+ // there isn't really a burst that happens, just a trickle because while the timestamps will be right, the queue won't contain
+ // anything until the timer fires at the normal rate.
+
+ if (m_qpcStartCapture > 0)
+ {
+ currentPacket = (PerformanceCounter - m_qpcStartCapture) * (SamplesPerSecond / SamplesPerPacket) / PerformanceFrequency;
+ packetsToQueue = currentPacket - m_nLastQueuedPacket;
+
+ // If the fifo is empty, and we're going to add something, then we are realtime
+ *isRealtime = IsListEmpty(&m_PacketFifoHead) && packetsToQueue > 0;
+
+ *NewPacketNumber = m_nLastQueuedPacket+1;
+ *NewPerformanceCount = m_qpcStartCapture + (*NewPacketNumber * m_qpcFrequency * SamplesPerPacket / SamplesPerSecond);
+
+ while (packetsToQueue > 0)
+ {
+ LIST_ENTRY* packetListEntry;
+ PACKET_ENTRY* packetEntry;
+
+ do
+ {
+ packetListEntry = ExInterlockedRemoveHeadList(&m_PacketPoolHead, &m_PacketPoolSpinLock);
+ if (packetListEntry != NULL) break;
+
+ // Pool is empty, no room to buffer more, an overrun is occurring. Drop and reuse the
+ // oldest packet from head of fifo.
+
+ // Since the pool is empty, the fifo should be full. However, although unlikely, the
+ // driver might empty the fifo before this routine removes a packet. In that case, the
+ // pool should have packets available again. Therefore this is a retry loop.
+ packetListEntry = ExInterlockedRemoveHeadList(&m_PacketFifoHead, &m_PacketFifoSpinLock);
+ if (packetListEntry != NULL) break;
+ } while (TRUE);
+
+ packetEntry = CONTAINING_RECORD(packetListEntry, PACKET_ENTRY, ListEntry);
+
+ packetEntry->PacketNumber = ++m_nLastQueuedPacket;
+ packetEntry->QpcWhenSampled = m_qpcStartCapture + (packetEntry->PacketNumber * PerformanceFrequency * SamplesPerPacket / SamplesPerSecond);
+
+ // TODO: this should really put something real in the buffer. Use the sine tone generator maybe?
+ RtlZeroMemory(&packetEntry->Samples[0], sizeof(packetEntry->Samples));
+
+ ExInterlockedInsertTailList(&m_PacketFifoHead, packetListEntry, &m_PacketFifoSpinLock);
+
+ packetsToQueue -= 1;
+ }
+ }
+
+ KeReleaseSpinLock(&m_BufferingStateSpinLock, irql);
+}
+
+#pragma code_seg()
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS CKeywordDetector::GetFifoStart(_Out_ ULONG *PacketNumber, _Out_ ULONGLONG *PerformanceCount)
+{
+ NTSTATUS status = STATUS_DEVICE_NOT_READY;
+ KIRQL irql = PASSIVE_LEVEL;
+
+ // acquire the fifo spin lock in order to safely inspect the head of the fifo
+ KeAcquireSpinLock(&m_PacketFifoSpinLock, &irql);
+
+ // peek at the first entry in the list, and retrieve the required packet number and qpc for it
+ if (!IsListEmpty(m_PacketFifoHead.Flink))
+ {
+ PACKET_ENTRY *packetEntry;
+
+ packetEntry = CONTAINING_RECORD(m_PacketFifoHead.Flink, PACKET_ENTRY, ListEntry);
+
+ status = RtlLongLongToULong(packetEntry->PacketNumber, PacketNumber);
+ if (NT_SUCCESS(status))
+ {
+ *PerformanceCount = packetEntry->QpcWhenSampled;
+ status = STATUS_SUCCESS;
+ }
+ }
+
+ KeReleaseSpinLock(&m_PacketFifoSpinLock, irql);
+
+ return status;
+}
+
+#pragma code_seg()
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS CKeywordDetector::GetReadPacket
+(
+ _In_ ULONG PacketCount,
+ _In_ ULONG PacketSize,
+ _In_reads_(PacketSize) PVOID *Packets,
+ _Out_ ULONG *PacketNumber,
+ _Out_ ULONG64 *PerformanceCounterValue,
+ _Out_ BOOLEAN *MoreData,
+ _Out_ ULONG *NextPacketNumber,
+ _Out_ ULONGLONG *NextPerformanceCount
+)
+{
+ NTSTATUS status = STATUS_DEVICE_NOT_READY;
+ LIST_ENTRY *packetListEntry = NULL;
+
+ // This call is synchronized with the dpc routine through the packet list
+ // spin locks, as a producer consumer relationship.
+ // buffering state variables are not available, and taking the buffering state
+ // lock here would introduce lock contention between the producer and the consumer.
+ *PacketNumber = 0;
+ *PerformanceCounterValue = 0;
+ *MoreData = FALSE;
+ *NextPacketNumber = 0;
+ *NextPerformanceCount = 0;
+
+ packetListEntry = ExInterlockedRemoveHeadList(&m_PacketFifoHead, &m_PacketFifoSpinLock);
+ if (packetListEntry != NULL)
+ {
+ BYTE *packetData;
+ PACKET_ENTRY *packetEntry;
+
+ packetEntry = CONTAINING_RECORD(packetListEntry, PACKET_ENTRY, ListEntry);
+
+ status = RtlLongLongToULong(packetEntry->PacketNumber, PacketNumber);
+ if (NT_SUCCESS(status))
+ {
+ packetData = (PBYTE) Packets[(*PacketNumber) % PacketCount];
+
+ *PerformanceCounterValue = packetEntry->QpcWhenSampled;
+
+ if (NT_SUCCESS(GetFifoStart(NextPacketNumber, NextPerformanceCount)))
+ {
+ *MoreData = TRUE;
+ }
+
+ // TODO: the packet size here needs to line up to the packet size allocated.
+ // Also, handle the first packet offset
+ RtlCopyMemory(packetData, packetEntry->Samples, min(sizeof(packetEntry->Samples), PacketSize));
+ }
+
+ ExInterlockedInsertTailList(&m_PacketPoolHead, packetListEntry, &m_PacketPoolSpinLock);
+ }
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+CKeywordDetector::SendPropertyTo
+(
+ _In_ GUID PropertySet,
+ _In_ ULONG PropertyId,
+ _In_ ACX_PROPERTY_VERB Verb,
+ _In_ PVOID Control,
+ _In_ ULONG ControlCb,
+ _Inout_ PVOID Value,
+ _In_ ULONG ValueCb,
+ _Out_ ULONG_PTR* Information
+)
+{
+ PAGED_CODE();
+
+ ACXPIN pin;
+ pin = AcxCircuitGetPinById(m_Circuit, DspCapturePinTypeBridge);
+ RETURN_NTSTATUS_IF_TRUE(pin == NULL, STATUS_INVALID_PARAMETER);
+
+ DSP_PIN_CONTEXT* pinCtx = GetDspPinContext(pin);
+ RETURN_NTSTATUS_IF_TRUE(pinCtx == NULL, STATUS_INVALID_PARAMETER);
+
+ RETURN_NTSTATUS_IF_TRUE(pinCtx->TargetCircuit == NULL, STATUS_INVALID_DEVICE_STATE);
+
+ ACX_REQUEST_PARAMETERS requestParams;
+ ACX_REQUEST_PARAMETERS_INIT_PROPERTY(
+ &requestParams,
+ PropertySet,
+ PropertyId,
+ Verb,
+ AcxItemTypeCircuit,
+ 0,
+ Control, ControlCb,
+ Value, ValueCb
+ );
+
+ WDFREQUEST request;
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = m_Device;
+ RETURN_NTSTATUS_IF_FAILED(WdfRequestCreate(&attributes, AcxTargetCircuitGetWdfIoTarget(pinCtx->TargetCircuit), &request));
+
+ auto request_free = scope_exit([&request]() {
+ WdfObjectDelete(request);
+ });
+
+ RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitFormatRequestForProperty(pinCtx->TargetCircuit, request, &requestParams));
+
+ WDF_REQUEST_SEND_OPTIONS sendOptions;
+ WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS);
+ WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(5));
+
+ RETURN_NTSTATUS_IF_TRUE(!WdfRequestSend(request, AcxTargetCircuitGetWdfIoTarget(pinCtx->TargetCircuit), &sendOptions), STATUS_INVALID_DEVICE_REQUEST);
+
+ NTSTATUS status = (WdfRequestGetStatus(request));
+
+ if (Information)
+ {
+ *Information = WdfRequestGetInformation(request);
+ }
+ if (status == STATUS_BUFFER_OVERFLOW && ValueCb == 0)
+ {
+ // Don't trace this error, it's normal
+ return status;
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(status);
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+CKeywordDetector::GetDeviceFunctionInformation
+(
+ _Out_ PSDCA_FUNCTION_INFORMATION_LIST *FunctionInfo
+)
+{
+ PAGED_CODE();
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG_PTR requiredBufferSize = 0;
+
+ RETURN_NTSTATUS_IF_TRUE(nullptr == FunctionInfo, STATUS_INVALID_PARAMETER);
+
+ status = SendPropertyTo(KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_FUNCTION_INFORMATION,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ nullptr, 0,
+ &requiredBufferSize);
+
+ if (status == STATUS_BUFFER_OVERFLOW)
+ {
+ // expect a buffer overflow error, confirm size is valid
+ if (requiredBufferSize >= sizeof(SDCA_FUNCTION_INFORMATION_LIST))
+ {
+ // size is valid, allocate and retrieve
+ *FunctionInfo = (PSDCA_FUNCTION_INFORMATION_LIST) ExAllocatePool2(POOL_FLAG_NON_PAGED, requiredBufferSize, DRIVER_TAG);
+ RETURN_NTSTATUS_IF_TRUE(nullptr == *FunctionInfo, STATUS_INSUFFICIENT_RESOURCES);
+ status = SendPropertyTo(KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_FUNCTION_INFORMATION,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ *FunctionInfo, sizeof(SDCA_FUNCTION_INFORMATION_LIST),
+ nullptr);
+ }
+ else
+ {
+ // correct buffer overflow error, but size is wrong
+ RETURN_NTSTATUS_IF_FAILED(STATUS_UNSUCCESSFUL);
+ }
+ }
+ else if (NT_SUCCESS(status))
+ {
+ // call should not succeeded with a null buffer pointer
+ RETURN_NTSTATUS_IF_FAILED(STATUS_INVALID_DEVICE_REQUEST);
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(status);
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+CKeywordDetector::GetDeviceKwsCapabilityDescriptor
+(
+ _Out_ PDEVICE_KWS_CAPABILITY_DESCRIPTOR Descriptor
+)
+{
+ PAGED_CODE();
+
+ memset(Descriptor, 0, sizeof(DEVICE_KWS_CAPABILITY_DESCRIPTOR));
+ RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws,
+ KSPROPERTY_SDCAKWS_DEVICE_CAPABILITY,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ Descriptor, sizeof(DEVICE_KWS_CAPABILITY_DESCRIPTOR),
+ nullptr));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+CKeywordDetector::GetVadDescriptor(
+ _Out_ PVAD_DESCRIPTOR_FORMAT Descriptor
+ )
+{
+ PAGED_CODE();
+
+ // For simplicity, this sample code uses a static-sized VAD_DESCRIPTOR
+ // with room for 11 total formats. This still has the potential to
+ // fail if the target device supports more than that many formats for VAD
+ memset(Descriptor, 0, sizeof(VAD_DESCRIPTOR_FORMAT));
+ RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws,
+ KSPROPERTY_SDCAKWS_VAD_CAPABILITY,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ Descriptor, sizeof(VAD_DESCRIPTOR_FORMAT),
+ nullptr));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+CKeywordDetector::GetVadEntities(
+ _Out_ PVAD_ENTITIES_EXTRA Entities
+ )
+{
+ PAGED_CODE();
+
+ // For simplicity, this sample code uses a static-sized VAD_ENTITIES
+ // with room for 25 total elements. This still has the potential to
+ // fail if the target device has more than 25 elements.
+ memset(Entities, 0, sizeof(VAD_ENTITIES_EXTRA));
+ RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws,
+ KSPROPERTY_SDCAKWS_VAD_ENTITIES,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ Entities, sizeof(VAD_ENTITIES_EXTRA),
+ nullptr));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+CKeywordDetector::SetSuspendAccessEvent
+(
+ _In_ PSDCA_KWS_NOTIFICATIONS Events
+)
+{
+ PAGED_CODE();
+
+ RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws,
+ KSPROPERTY_SDCAKWS_ACCESS_EVENTS,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ Events, sizeof(SDCA_KWS_NOTIFICATIONS),
+ nullptr));
+
+ return STATUS_SUCCESS;
+}
+
+// There are three states. Disarmed, Armed and Suspended,
+// and Armed and Prepared.
+
+// If we're Disarmed, we need to clean up the vad stream, suspend/resume
+// state doesn't matter.
+
+// if we're Armed and Suspended, we should be detecting but are experiencing
+// a period of deafness due to the codec driver needing to access the hardware.
+// So, we need to clean up the vad stream and wait for the resume notification
+// to recreate the vad stream
+
+// if we're Armed and Prepared, then we're actively detecting, so we
+// need the stream prepared.
+PAGED_CODE_SEG
+_Requires_lock_held_(m_csLock)
+NTSTATUS
+CKeywordDetector::UpdateVadStreamState()
+{
+ PAGED_CODE();
+
+ if (m_SoundDetectorArmed1 || m_SoundDetectorArmed2)
+ {
+ // if we're armed, not prepared, and not suspended, then
+ // we need to move to the armed and prepared state.
+ if (!m_Prepared && !m_Suspended)
+ {
+ // To move into the Armed and Prepared state
+ // we need to prepare the vad stream
+ RETURN_NTSTATUS_IF_FAILED(ConfigureVadPort(&m_PrepareParams));
+ }
+ // if we are armed, prepared, and suspended, then
+ // we need to move to the armed and suspended state
+ else if (m_Prepared && m_Suspended)
+ {
+ // To move into the Armed and Suspended state
+ // we need to cleanup the VAD stream
+ RETURN_NTSTATUS_IF_FAILED(CleanupVadPort());
+ }
+ // else
+ // if we are armed, prepared, and not suspended, then we are in
+ // the armed and prepared state, nothing else to do.
+
+ // Or, if we are armed, not prepared, and suspended, then we are in
+ // the armed and suspended state, nothing else to do.
+ }
+ else
+ {
+ if (m_Prepared)
+ {
+ // moving into the disarmed state
+ RETURN_NTSTATUS_IF_FAILED(CleanupVadPort());
+ }
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+PAGED_CODE_SEG
+_Requires_lock_held_(m_csLock)
+NTSTATUS
+CKeywordDetector::ConfigureVadPort
+(
+ _In_ PSDCA_KWS_PREPARE_PARAMS PrepareParams
+)
+{
+ PAGED_CODE();
+
+ RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws,
+ KSPROPERTY_SDCAKWS_CONFIGURE_VAD_PORT,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ PrepareParams, sizeof(SDCA_KWS_PREPARE_PARAMS),
+ nullptr));
+
+ m_Prepared = TRUE;
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+_Requires_lock_held_(m_csLock)
+NTSTATUS
+CKeywordDetector::CleanupVadPort( )
+{
+ PAGED_CODE();
+
+ RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws,
+ KSPROPERTY_SDCAKWS_CLEANUP_VAD_PORT,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ NULL, 0,
+ nullptr));
+
+ m_Prepared = FALSE;
+
+ return STATUS_SUCCESS;
+}
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.h
new file mode 100644
index 00000000..9ebc3a5e
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.h
@@ -0,0 +1,237 @@
+/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ KeywordDetector.h
+
+Abstract:
+
+ Sample Keyword Detector.
+
+
+--*/
+
+#pragma once
+
+#include <wil\resource.h>
+#include "ContosoEventDetector.h"
+#include "soundwirecontroller.h"
+#include "sdcastreaming.h"
+
+#define KEYWORDDETECTOR_POOLTAG 'KWS0'
+
+typedef struct
+{
+ VAD_DESCRIPTOR Descriptor;
+ WAVEFORMATEXTENSIBLE ExtraFormats[10];
+} VAD_DESCRIPTOR_FORMAT, * PVAD_DESCRIPTOR_FORMAT;
+
+typedef struct
+{
+ ULONG EntitiesCount;
+ ENTITY_INFO ExtraEntities[25];
+} VAD_ENTITIES_EXTRA, * PVAD_ENTITIES_EXTRA;
+
+class CKeywordDetector
+{
+public:
+ CKeywordDetector(_In_ WDFDEVICE Device, _In_ ACXCIRCUIT Circuit, _In_ PWAVEFORMATEXTENSIBLE Format);
+
+ ~CKeywordDetector();
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ NTSTATUS Initialize();
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ NTSTATUS ResetDetector(_In_ GUID eventId);
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ NTSTATUS DownloadDetectorData(_In_ GUID eventId, _In_ LONGLONG Data);
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ NTSTATUS GetDetectorData(_In_ GUID eventId, _Out_ LONGLONG *Data);
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ ULONGLONG GetStartTimestamp();
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ ULONGLONG GetStopTimestamp();
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ NTSTATUS SetArmed(_In_ GUID eventId, _In_ BOOLEAN Arm);
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ VOID NotifyDetection();
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ NTSTATUS GetArmed(_In_ GUID eventId, _Out_ BOOLEAN *Arm);
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ VOID Run();
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ VOID Stop();
+
+ _IRQL_requires_min_(DISPATCH_LEVEL)
+ VOID DpcRoutine(_In_ LONGLONG PerformanceCounter, _In_ LONGLONG PerformanceFrequency, _Out_ BOOLEAN *isRealtime, _Out_ LONGLONG *NewPacketNumber, _Out_ ULONGLONG *NewPerformanceCount);
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ NTSTATUS GetReadPacket(_In_ ULONG PacketCount, _In_ ULONG PacketSize, _In_reads_(PacketSize) PVOID *Packets, _Out_ ULONG *PacketNumber,
+ _Out_ ULONGLONG *PerformanceCount, _Out_ BOOLEAN *MoreData, _Out_ ULONG *NextPacketNumber, _Out_ ULONGLONG *NextPerformanceCount);
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ NTSTATUS GetFifoStart(_Out_ ULONG *PacketNumber, _Out_ ULONGLONG *PerformanceCount);
+
+private:
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ VOID UpdateBufferingState();
+
+ _IRQL_requires_max_(PASSIVE_LEVEL)
+ NTSTATUS ReadKeywordTimestampRegistry();
+
+ PAGED_CODE_SEG
+ NTSTATUS
+ SendPropertyTo
+ (
+ _In_ GUID PropertySet,
+ _In_ ULONG PropertyId,
+ _In_ ACX_PROPERTY_VERB Verb,
+ _In_ PVOID Control,
+ _In_ ULONG ControlCb,
+ _Inout_ PVOID Value,
+ _In_ ULONG ValueCb,
+ _Out_ ULONG_PTR* Information
+ );
+
+
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetDeviceFunctionInformation(
+ _Out_ PSDCA_FUNCTION_INFORMATION_LIST *FunctionInfo
+ );
+
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetDeviceKwsCapabilityDescriptor(
+ _Out_ PDEVICE_KWS_CAPABILITY_DESCRIPTOR Descriptor
+ );
+
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetVadDescriptor(
+ _Out_ PVAD_DESCRIPTOR_FORMAT Descriptor
+ );
+
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetVadEntities (
+ _Out_ PVAD_ENTITIES_EXTRA Entities
+ );
+
+ PAGED_CODE_SEG
+ NTSTATUS
+ SetSuspendAccessEvent(
+ _In_ PSDCA_KWS_NOTIFICATIONS Events
+ );
+
+ PAGED_CODE_SEG
+ _Requires_lock_held_(m_csLock)
+ NTSTATUS
+ UpdateVadStreamState();
+
+ PAGED_CODE_SEG
+ _Requires_lock_held_(m_csLock)
+ NTSTATUS
+ ConfigureVadPort(
+ _In_ PSDCA_KWS_PREPARE_PARAMS PrepareParams
+ );
+
+ PAGED_CODE_SEG
+ _Requires_lock_held_(m_csLock)
+ NTSTATUS
+ CleanupVadPort(
+ );
+
+ static KSTART_ROUTINE s_HandleNotifications;
+
+ PAGED_CODE_SEG
+ void
+ HandleNotifications();
+
+ // The Contoso keyword detector processes 10ms packets of 16KHz 16-bit PCM
+ // audio samples
+ static const int SamplesPerSecond = 16000;
+ static const int SamplesPerPacket = (10 * SamplesPerSecond / 1000);
+
+ typedef struct
+ {
+ LIST_ENTRY ListEntry;
+ LONGLONG PacketNumber;
+ LONGLONG QpcWhenSampled;
+ UINT16 Samples[SamplesPerPacket];
+ } PACKET_ENTRY;
+
+ // set at initialization, safe to use in all threads
+ WDFDEVICE m_Device;
+ ACXCIRCUIT m_Circuit;
+ LONGLONG m_qpcFrequency;
+ BOOLEAN m_Initialized;
+ SDCA_KWS_PREPARE_PARAMS m_PrepareParams;
+ PSDCA_FUNCTION_INFORMATION_LIST m_FunctionInformation;
+ DEVICE_KWS_CAPABILITY_DESCRIPTOR m_CapabilityDescriptor;
+ VAD_DESCRIPTOR_FORMAT m_VadDescriptor;
+ SDCA_KWS_NOTIFICATIONS m_Events;
+ PACKET_ENTRY m_PacketPool[1 * SamplesPerSecond / SamplesPerPacket]; // Enough storage for 1 second of audio data
+ VAD_ENTITIES_EXTRA m_VadEntities;
+
+ // single thread access, no lock necessary
+ LONGLONG m_SoundDetectorData1;
+ LONGLONG m_SoundDetectorData2;
+ ULONGLONG m_ullKeywordStartTimestamp;
+ ULONGLONG m_ullKeywordStopTimestamp;
+ BOOLEAN m_streamRunning;
+
+ // the following state variables are shared between dpc and stream state
+ KSPIN_LOCK m_BufferingStateSpinLock;
+ _Guarded_by_(m_BufferingStateSpinLock)
+ LONGLONG m_qpcStartCapture;
+ _Guarded_by_(m_BufferingStateSpinLock)
+ LONGLONG m_nLastQueuedPacket;
+
+ // protected through interlocked access to the packet pool
+ KSPIN_LOCK m_PacketPoolSpinLock;
+ LIST_ENTRY m_PacketPoolHead;
+
+ // protected through interlocked access to the packet pool
+ KSPIN_LOCK m_PacketFifoSpinLock;
+ LIST_ENTRY m_PacketFifoHead;
+
+
+ // the following variables are shared with the sdca notification event
+ // handler thread, and are protected by m_csLock
+ mutable wil::fast_mutex_with_critical_region m_csLock;
+ PETHREAD m_dispatchThread;
+ mutable wil::kernel_event_auto_reset m_threadExitEvent;
+ mutable wil::kernel_event_manual_reset m_threadExitedEvent{ true };
+
+ _Guarded_by_(m_csLock)
+ BOOLEAN m_Prepared;
+
+ _Guarded_by_(m_csLock)
+ BOOLEAN m_Suspended;
+
+ _Guarded_by_(m_csLock)
+ BOOLEAN m_SoundDetectorArmed1;
+
+ _Guarded_by_(m_csLock)
+ BOOLEAN m_SoundDetectorArmed2;
+};
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionClock.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionClock.h
new file mode 100644
index 00000000..d57bb522
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionClock.h
@@ -0,0 +1,38 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ PositionClock.h
+
+Abstract:
+
+ Simulated clock Interface for keeping track of stream position.
+
+Environment:
+
+ Kernel mode
+
+--*/
+#pragma once
+
+class IPositionClock
+{
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ virtual void Pause() = 0;
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ virtual void Run() = 0;
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ virtual void Stop() = 0;
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ virtual ULONGLONG GetElapsedTime(_Out_ PULONGLONG pQpcTimeStamp) = 0;
+};
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.cpp
new file mode 100644
index 00000000..db0f2aee
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.cpp
@@ -0,0 +1,140 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ PositionSimClock.cpp
+
+Abstract:
+
+ Simulated clock for keeping track of stream position.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include "PositionSimClock.h"
+
+#ifndef __INTELLISENSE__
+#include "positionsimclock.tmh"
+#endif
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CPositionSimClock::CPositionSimClock()
+{
+ PAGED_CODE();
+
+ m_State = SIM_CLOCK_STATE_STOP;
+ m_ElapsedTimeWhenPaused = 0;
+ m_StartTime = 0;
+ m_QpcTimeStamp = 0;
+
+ KeInitializeSpinLock(&m_Lock);
+}
+
+#pragma code_seg()
+_Use_decl_annotations_
+CPositionSimClock::~CPositionSimClock()
+{
+}
+
+#pragma code_seg()
+_Use_decl_annotations_
+void CPositionSimClock::Run()
+{
+ KIRQL irql = PASSIVE_LEVEL;
+ KeAcquireSpinLock(&m_Lock, &irql);
+
+ if (m_State == SIM_CLOCK_STATE_STOP ||
+ m_State == SIM_CLOCK_STATE_PAUSE)
+ {
+ m_StartTime = KeQueryInterruptTimePrecise(&m_QpcTimeStamp);
+ }
+
+ m_State = SIM_CLOCK_STATE_RUN;
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CPositionSimClock::Run SIM_CLOCK_STATE_RUN : %lld", m_StartTime);
+
+ KeReleaseSpinLock(&m_Lock, irql);
+}
+
+#pragma code_seg()
+_Use_decl_annotations_
+void CPositionSimClock::Pause()
+{
+ KIRQL irql = PASSIVE_LEVEL;
+ KeAcquireSpinLock(&m_Lock, &irql);
+
+ if (m_State == SIM_CLOCK_STATE_RUN)
+ {
+ m_ElapsedTimeWhenPaused = GetElapsedTimeUnlocked();
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CPositionSimClock::Pause ElapsedTimeWhenPaused : %lld", m_ElapsedTimeWhenPaused);
+ }
+
+ m_State = SIM_CLOCK_STATE_PAUSE;
+
+ KeReleaseSpinLock(&m_Lock, irql);
+}
+
+#pragma code_seg()
+_Use_decl_annotations_
+void CPositionSimClock::Stop()
+{
+ KIRQL irql = PASSIVE_LEVEL;
+ KeAcquireSpinLock(&m_Lock, &irql);
+
+ m_State = SIM_CLOCK_STATE_STOP;
+ m_StartTime = 0;
+ m_ElapsedTimeWhenPaused = 0;
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CPositionSimClock::Stop");
+
+ KeReleaseSpinLock(&m_Lock, irql);
+}
+
+#pragma code_seg()
+_Use_decl_annotations_
+ULONGLONG CPositionSimClock::GetElapsedTimeUnlocked()
+{
+ ULONGLONG current_time = KeQueryInterruptTimePrecise(&m_QpcTimeStamp);
+
+ ULONGLONG elapsedTime = (current_time - m_StartTime) + m_ElapsedTimeWhenPaused;
+
+ return elapsedTime;
+}
+
+#pragma code_seg()
+_Use_decl_annotations_
+ULONGLONG CPositionSimClock::GetElapsedTime(PULONGLONG pQpcTimeStamp)
+{
+ KIRQL irql = PASSIVE_LEVEL;
+ KeAcquireSpinLock(&m_Lock, &irql);
+
+ ULONGLONG elapsedTime = 0;
+ if (m_State == SIM_CLOCK_STATE_RUN)
+ {
+ elapsedTime = GetElapsedTimeUnlocked();
+ }
+ else
+ {
+ elapsedTime = m_ElapsedTimeWhenPaused;
+ }
+
+ if (pQpcTimeStamp)
+ {
+ *pQpcTimeStamp = m_QpcTimeStamp;
+ }
+
+ KeReleaseSpinLock(&m_Lock, irql);
+
+ return elapsedTime;
+}
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.h
new file mode 100644
index 00000000..2d1844fa
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.h
@@ -0,0 +1,76 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ PositionSimClock.h
+
+Abstract:
+
+ Simulated clock for keeping track of stream position.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#pragma once
+
+#include "PositionClock.h"
+
+#define HNSTIME_PER_MILLISECOND 10000
+
+class CPositionSimClock : public IPositionClock
+{
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ CPositionSimClock();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ ~CPositionSimClock();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ void Pause();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ void Run();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ void Stop();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ ULONGLONG GetElapsedTime(_Out_ PULONGLONG pQpcTimeStamp);
+
+protected:
+ ULONGLONG m_StartTime;
+ ULONGLONG m_ElapsedTimeWhenPaused;
+ ULONGLONG m_QpcTimeStamp;
+
+ KSPIN_LOCK m_Lock;
+
+ typedef enum _SimClockState_t
+ {
+ SIM_CLOCK_STATE_STOP,
+ SIM_CLOCK_STATE_PAUSE,
+ SIM_CLOCK_STATE_RUN,
+
+ SIM_CLOCK_STATE_Count
+ }SimClockState;
+
+ SimClockState m_State;
+
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ ULONGLONG GetElapsedTimeUnlocked();
+};
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj
new file mode 100644
index 00000000..5661777a
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj
@@ -0,0 +1,373 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|ARM">
+ <Configuration>Debug</Configuration>
+ <Platform>ARM</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|ARM">
+ <Configuration>Release</Configuration>
+ <Platform>ARM</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|ARM64">
+ <Configuration>Debug</Configuration>
+ <Platform>ARM64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|ARM64">
+ <Configuration>Release</Configuration>
+ <Platform>ARM64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{5FDD3888-48B5-496C-83B0-E107CFFF46BC}</ProjectGuid>
+ <RootNamespace>$(MSBuildProjectName)</RootNamespace>
+ <KMDF_VERSION_MAJOR>1</KMDF_VERSION_MAJOR>
+ <KMDF_VERSION_MINOR>31</KMDF_VERSION_MINOR>
+ <ACX_VERSION_MAJOR>1</ACX_VERSION_MAJOR>
+ <ACX_VERSION_MINOR>0</ACX_VERSION_MINOR>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
+ <SupportsPackaging>false</SupportsPackaging>
+ <RequiresPackageProject>true</RequiresPackageProject>
+ <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
+ <Platform Condition="'$(Platform)' == ''">Win32</Platform>
+ <WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ <DriverType>KMDF</DriverType>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ <DriverType>KMDF</DriverType>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ <DriverType>KMDF</DriverType>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ <DriverType>KMDF</DriverType>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ <DriverType>KMDF</DriverType>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ <DriverType>KMDF</DriverType>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ <DriverType>KMDF</DriverType>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ <DriverType>KMDF</DriverType>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <PropertyGroup>
+ <OutDir>$(IntDir)</OutDir>
+ </PropertyGroup>
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
+ <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
+ <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
+ <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
+ <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Link>
+ <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies>
+ </Link>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions>
+ <WppEnabled>true</WppEnabled>
+ <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData>
+ <WppAdditionalOptions>-km \
+-DENABLE_WPP_RECORDER=1 \
+-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \
+-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \
+-p:SDCAVCodec</WppAdditionalOptions>
+ <WppRecorderEnabled>true</WppRecorderEnabled>
+ </ClCompile>
+ <DriverSign>
+ <FileDigestAlgorithm>sha256</FileDigestAlgorithm>
+ </DriverSign>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Link>
+ <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies>
+ </Link>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions>
+ <WppEnabled>true</WppEnabled>
+ <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData>
+ <WppAdditionalOptions>-km \
+-DENABLE_WPP_RECORDER=1 \
+-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \
+-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \
+-p:SDCAVCodec</WppAdditionalOptions>
+ <WppRecorderEnabled>true</WppRecorderEnabled>
+ </ClCompile>
+ <DriverSign>
+ <FileDigestAlgorithm>sha256</FileDigestAlgorithm>
+ </DriverSign>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Link>
+ <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies>
+ </Link>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions>
+ <WppEnabled>true</WppEnabled>
+ <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData>
+ <WppAdditionalOptions>-km \
+-DENABLE_WPP_RECORDER=1 \
+-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \
+-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \
+-p:SDCAVCodec</WppAdditionalOptions>
+ <WppRecorderEnabled>true</WppRecorderEnabled>
+ </ClCompile>
+ <DriverSign>
+ <FileDigestAlgorithm>sha256</FileDigestAlgorithm>
+ </DriverSign>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Link>
+ <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies>
+ </Link>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions>
+ <WppEnabled>true</WppEnabled>
+ <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData>
+ <WppAdditionalOptions>-km \
+-DENABLE_WPP_RECORDER=1 \
+-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \
+-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \
+-p:SDCAVCodec</WppAdditionalOptions>
+ <WppRecorderEnabled>true</WppRecorderEnabled>
+ </ClCompile>
+ <DriverSign>
+ <FileDigestAlgorithm>sha256</FileDigestAlgorithm>
+ </DriverSign>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
+ <Link>
+ <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies>
+ </Link>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions>
+ <WppEnabled>true</WppEnabled>
+ <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData>
+ <WppAdditionalOptions>-km \
+-DENABLE_WPP_RECORDER=1 \
+-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \
+-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \
+-p:SDCAVCodec</WppAdditionalOptions>
+ <WppRecorderEnabled>true</WppRecorderEnabled>
+ </ClCompile>
+ <DriverSign>
+ <FileDigestAlgorithm>sha256</FileDigestAlgorithm>
+ </DriverSign>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
+ <Link>
+ <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies>
+ </Link>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions>
+ <WppEnabled>true</WppEnabled>
+ <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData>
+ <WppAdditionalOptions>-km \
+-DENABLE_WPP_RECORDER=1 \
+-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \
+-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \
+-p:SDCAVCodec</WppAdditionalOptions>
+ <WppRecorderEnabled>true</WppRecorderEnabled>
+ </ClCompile>
+ <DriverSign>
+ <FileDigestAlgorithm>sha256</FileDigestAlgorithm>
+ </DriverSign>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
+ <Link>
+ <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies>
+ </Link>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions>
+ <WppEnabled>true</WppEnabled>
+ <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData>
+ <WppAdditionalOptions>-km \
+-DENABLE_WPP_RECORDER=1 \
+-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \
+-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \
+-p:SDCAVCodec</WppAdditionalOptions>
+ <WppRecorderEnabled>true</WppRecorderEnabled>
+ </ClCompile>
+ <DriverSign>
+ <FileDigestAlgorithm>sha256</FileDigestAlgorithm>
+ </DriverSign>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
+ <Link>
+ <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies>
+ </Link>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions>
+ <WppEnabled>true</WppEnabled>
+ <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData>
+ <WppAdditionalOptions>-km \
+-DENABLE_WPP_RECORDER=1 \
+-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \
+-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \
+-p:SDCAVCodec</WppAdditionalOptions>
+ <WppRecorderEnabled>true</WppRecorderEnabled>
+ </ClCompile>
+ <DriverSign>
+ <FileDigestAlgorithm>sha256</FileDigestAlgorithm>
+ </DriverSign>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <FilesToPackage Include="$(TargetPath)" />
+ </ItemGroup>
+ <ItemGroup>
+ <Inf Exclude="@(Inx)" Include="*.inx" />
+ <FilesToPackage Include="$(TargetPath)" Condition="'$(ConfigurationType)'=='Driver' or '$(ConfigurationType)'=='DynamicLibrary'" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="AcpiReader.h" />
+ <ClInclude Include="CircuitHelper.h" />
+ <ClInclude Include="KeywordDetector.h" />
+ <ClInclude Include="..\inc\NewDelete.h" />
+ <ClInclude Include="offloadStreamEngine.h" />
+ <ClInclude Include="PositionClock.h" />
+ <ClInclude Include="PositionSimClock.h" />
+ <ClInclude Include="private.h" />
+ <ClInclude Include="savedata.h" />
+ <ClInclude Include="SimPeakMeter.h" />
+ <ClInclude Include="streamengine.h" />
+ <ClInclude Include="ToneGenerator.h" />
+ <ClInclude Include="Trace.h" />
+ <ClInclude Include="WaveReader.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="AcpiReader.cpp" />
+ <ClCompile Include="AudioModule.cpp" />
+ <ClCompile Include="capture.cpp" />
+ <ClCompile Include="CircuitHelper.cpp" />
+ <ClCompile Include="circuitstream.cpp" />
+ <ClCompile Include="device.cpp" />
+ <ClCompile Include="driver.cpp" />
+ <ClCompile Include="KeywordDetector.cpp" />
+ <ClCompile Include="..\common\NewDelete.cpp" />
+ <ClCompile Include="offloadStreamEngine.cpp" />
+ <ClCompile Include="PositionSimClock.cpp" />
+ <ClCompile Include="render.cpp" />
+ <ClCompile Include="renderAudioEngine.cpp" />
+ <ClCompile Include="savedata.cpp" />
+ <ClCompile Include="SimPeakMeter.cpp" />
+ <ClCompile Include="streamengine.cpp" />
+ <ClCompile Include="ToneGenerator.cpp" />
+ <ClCompile Include="WaveReader.cpp" />
+ <ResourceCompile Include="resources.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+</Project>
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters
new file mode 100644
index 00000000..44bc3f92
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ <Filter Include="Driver Files">
+ <UniqueIdentifier>{8E41214B-6785-4CFE-B992-037D68949A14}</UniqueIdentifier>
+ <Extensions>inf;inv;inx;mof;mc;</Extensions>
+ </Filter>
+ </ItemGroup>
+</Project>
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx
new file mode 100644
index 00000000..5b1b39d5
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx
@@ -0,0 +1,79 @@
+[Version]
+Signature = "$WINDOWS NT$"
+Class = AudioProcessingObject
+ClassGuid = {5989fce8-9cd0-467d-8a6a-5419e31529d4}
+Provider = %ProviderName%
+DriverVer = 02/22/2016,1.0.0.1
+CatalogFile = sdcavad.cat
+PnpLockDown = 1
+
+[Manufacturer]
+%MfgName% = ApoComponents,NT$ARCH$.10.0...19041
+
+[ApoComponents.NT$ARCH$.10.0...19041]
+%Apo.ComponentDesc% = ApoComponent_Install,SWC\VEN_SDCAV_SMPL&CID_APO
+
+[ApoComponent_Install]
+CopyFiles = Apo_CopyFiles
+AddReg = Apo_AddReg
+
+[Apo_CopyFiles]
+sdcavkwsapo.dll
+
+[Apo_AddReg]
+; Keyword Spotter Endpoint effect APO COM registration
+HKR,Classes\CLSID\%KWS_FX_ENDPOINT_CLSID%,,,%KWS_FriendlyName%
+HKR,Classes\CLSID\%KWS_FX_ENDPOINT_CLSID%\InProcServer32,,0x00020000,%13%\sdcavKWSApo.dll
+HKR,Classes\CLSID\%KWS_FX_ENDPOINT_CLSID%\InProcServer32,ThreadingModel,,"Both"
+
+; Keyword Spotter APO registration
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"FriendlyName", ,%KWS_FriendlyName%
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"Copyright", ,%Copyright%
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MajorVersion", 0x00010001, 1
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MinorVersion", 0x00010001, 1
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"Flags", 0x00010001, 0xC
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MinInputConnections", 0x00010001, 1
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MaxInputConnections", 0x00010001, 1
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MinOutputConnections", 0x00010001, 1
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MaxOutputConnections", 0x00010001, 1
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MaxInstances", 0x00010001, 0xffffffff
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"NumAPOInterfaces", 0x00010001, 1
+HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"APOInterface0", ,"{FD7F2B29-24D0-4B5C-B177-592C39F9CA10}"
+
+[ApoComponent_Install.HW]
+AddReg = FriendlyName_AddReg
+
+[FriendlyName_AddReg]
+HKR,,FriendlyName,,%Apo.ComponentDesc%
+
+[ApoComponent_Install.Services]
+AddService=,2 ; no function driver, install a null driver.
+
+[SourceDisksNames]
+1 = Disk
+
+[SourceDisksFiles]
+sdcavkwsapo.dll = 1
+
+[DestinationDirs]
+Apo_CopyFiles = 13 ; 13=Package's DriverStore directory
+
+[SignatureAttributes]
+sdcavkwsapo.dll = SignatureAttributes.PETrust
+
+[SignatureAttributes.PETrust]
+PETrust = true
+
+[Strings]
+MfgName = "TODO-Set-Manufacturer"
+ProviderName = "TODO-Set-Provider"
+Apo.ComponentDesc = "Audio SDCAV APO Sample"
+
+; Driver developers would replace these CLSIDs with those of their own APOs
+KWS_FX_ENDPOINT_CLSID = "{9D89F614-F9D6-40DD-9F21-5E69FA3981ED}"
+
+; see audioenginebaseapo.idl for APO_FLAG enum values
+APO_FLAG_DEFAULT = 0x0000000e
+
+KWS_FriendlyName = "Keyword Spotter APO Sample (endpoint effect)"
+Copyright = "Sample"
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVDsp.inx b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVDsp.inx
new file mode 100644
index 00000000..a0e0a654
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVDsp.inx
@@ -0,0 +1,159 @@
+;/*++
+;
+;Copyright (c) Microsoft Corporation. All rights reserved.
+;
+;Module Name:
+;
+; SDCAVDsp.INF
+;
+;--*/
+
+[Version]
+Signature="$WINDOWS NT$"
+Class=MEDIA
+ClassGuid={4d36e96c-e325-11ce-bfc1-08002be10318}
+Provider=%ProviderName%
+DriverVer=06/13/2016, 1.0.0.1
+CatalogFile=SDCAVad.cat
+PnpLockdown=1
+
+[DestinationDirs]
+DefaultDestDir = 13
+
+;*****************************************
+; Audio Device Install Section
+;*****************************************
+[ControlFlags]
+ExcludeFromSelect = {4DCB0606-6415-4A36-BDC5-9B1792117DC9}\Render
+ExcludeFromSelect = {4DCB0606-6415-4A36-BDC5-9B1792117DC9}\Capture
+
+[Manufacturer]
+%StdMfg%=Standard,NT$ARCH$.10.0...19041
+
+[Standard.NT$ARCH$.10.0...19041]
+%WdfDspDevice.DeviceDesc%=Audio_Device, SOUNDWIRETEST\DSP
+%WdfDspDevice.DeviceDesc%=Audio_Child_Device, {4DCB0606-6415-4A36-BDC5-9B1792117DC9}\Render
+%WdfDspDevice.DeviceDesc%=Audio_Child_Device, {4DCB0606-6415-4A36-BDC5-9B1792117DC9}\Capture
+
+[Audio_Device.NT]
+CopyFiles=Audio_Device.NT.Copy
+AddReg=EVENTDETECTORCONTOSOADAPTER.AddReg
+
+[Audio_Child_Device.NT]
+CopyFiles=Audio_Device.NT.Copy
+
+[Audio_Device.NT.Copy]
+SDCAVDsp.sys
+EventDetectorContosoAdapter.dll
+
+;-------------- Service installation
+
+[Audio_Device.NT.Services]
+AddService = SDCAVDsp, %SPSVCINST_ASSOCSERVICE%, Audio_Service_Inst
+
+[Audio_Child_Device.NT.Services]
+;NULL Driver
+AddService = , %SPSVCINST_ASSOCSERVICE%
+
+[Audio_Service_Inst]
+DisplayName = %WdfDspDevice.DeviceDesc%
+ServiceType = 1 ; SERVICE_KERNEL_DRIVER
+StartType = 3 ; SERVICE_DEMAND_START
+ErrorControl = 1 ; SERVICE_ERROR_NORMAL
+ServiceBinary = %13%\SDCAVDsp.sys
+
+[SourceDisksNames]
+1 = %DiskId1%,,,""
+
+[SourceDisksFiles]
+SDCAVDsp.sys = 1,,
+EventDetectorContosoAdapter.dll = 1,,
+
+[Audio_Device.NT.Wdf]
+KmdfService = SDCAVDsp, Audio_wdfsect
+[Audio_wdfsect]
+KmdfLibraryVersion = $KMDFVERSION$
+
+[EVENTDETECTORCONTOSOADAPTER.AddReg]
+HKCR,CLSID\%EVENTDETECTORCONTOSOADAPTER_CLSID2%,,,"EventDetectorContosoAdapter2 Class"
+HKCR,CLSID\%EVENTDETECTORCONTOSOADAPTER_CLSID2%\InProcServer32,,0x00020000,%13%\eventdetectorcontosoadapter.dll
+HKCR,CLSID\%EVENTDETECTORCONTOSOADAPTER_CLSID2%\InProcServer32,ThreadingModel,,"Apartment"
+HKCR,CLSID\%EVENTDETECTORCONTOSOADAPTER_CLSID2%\Version,,,"1.0"
+
+;
+; render interfaces: speaker
+;
+[Audio_Device.I.Speaker]
+AddReg=Audio_Device.I.Speaker.AddReg
+[Audio_Device.I.Speaker.AddReg]
+HKR,,CLSID,,%Proxy.CLSID%
+HKR,,FriendlyName,,%Audio_Device.Speaker.szPname%
+; The following lines opt-in to pull mode.
+HKR,EP\0,%PKEY_AudioEndpoint_Association%,,%KSNODETYPE_ANY%
+HKR,EP\0,%PKEY_AudioEndpoint_Supports_EventDriven_Mode%,0x00010001,0x1
+
+;
+; capture interfaces: microphone
+;
+[Audio_Device.I.Microphone]
+AddReg=Audio_Device.I.Microphone.AddReg
+[Audio_Device.I.Microphone.AddReg]
+HKR,,CLSID,,%Proxy.CLSID%
+HKR,,FriendlyName,,%Audio_Device.Microphone.szPname%
+; The following lines opt-in to pull mode.
+HKR,EP\0,%PKEY_AudioEndpoint_Association%,,%KSNODETYPE_ANY%
+HKR,EP\0,%PKEY_AudioEndpoint_Supports_EventDriven_Mode%,0x00010001,0x1
+
+;
+; PnP add interface directives for dynamic enumerated audio endpoints.
+;
+[Audio_Child_Device.NT.Interfaces]
+; Interfaces for render endpoint. capture is used for loopback.
+AddInterface=%KSCATEGORY_AUDIO%, %KSNAME_Speaker%, Audio_Device.I.Speaker
+AddInterface=%KSCATEGORY_RENDER%, %KSNAME_Speaker%, Audio_Device.I.Speaker
+AddInterface=%KSCATEGORY_REALTIME%, %KSNAME_Speaker%, Audio_Device.I.Speaker
+;AddInterface=%KSCATEGORY_CAPTURE%, %KSNAME_Speaker%, Audio_Device.I.Speaker
+
+; Interfaces for mic capture endpoint
+AddInterface=%KSCATEGORY_AUDIO%, %KSNAME_Microphone%, Audio_Device.I.Microphone
+AddInterface=%KSCATEGORY_CAPTURE%, %KSNAME_Microphone%, Audio_Device.I.Microphone
+AddInterface=%KSCATEGORY_REALTIME%, %KSNAME_Microphone%, Audio_Device.I.Microphone
+
+[Strings]
+;
+;Non-localizable
+;
+KSNAME_Speaker="Speaker0"
+KSNAME_Microphone="Microphone0"
+
+SPSVCINST_ASSOCSERVICE = 0x00000002
+ProviderName = "VS_Microsoft"
+
+Proxy.CLSID = "{17CCA71B-ECD7-11D0-B908-00A0C9223196}"
+KSCATEGORY_AUDIO = "{6994AD04-93EF-11D0-A3CC-00A0C9223196}"
+KSCATEGORY_RENDER = "{65E8773E-8F56-11D0-A3B9-00A0C9223196}"
+KSCATEGORY_CAPTURE = "{65E8773D-8F56-11D0-A3B9-00A0C9223196}"
+KSCATEGORY_REALTIME = "{EB115FFC-10C8-4964-831D-6DCB02E6F23F}"
+
+MediaCategories="SYSTEM\CurrentControlSet\Control\MediaCategories"
+KSNODETYPE_ANY = "{00000000-0000-0000-0000-000000000000}"
+
+PKEY_AudioEndpoint_ControlPanelPageProvider = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},1"
+PKEY_AudioEndpoint_Association = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},2"
+PKEY_AudioEndpoint_Supports_EventDriven_Mode = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},7"
+PKEY_AudioEndpoint_Default_VolumeInDb = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},9"
+
+; Driver developers would replace this CLSID with their own keyword detector OEM adapter
+EVENTDETECTORCONTOSOADAPTER_CLSID2 = {207F3D0C-5C79-496F-A94C-D3D2934DBFA9}
+
+;
+;Localizable
+;
+StdMfg = "SDCA Virtual Dsp Audio Device"
+DiskId1 = "SDCA Virtual Dsp Audio Driver Installation Disk"
+WdfDspDevice.DeviceDesc = "SDCA Virtual Dsp Audio Driver"
+
+;; friendly names
+Audio_Device.Speaker.szPname="SDCA Virtual DSP Speaker"
+Audio_Device.Microphone.szPname="SDCA Virtual DSP Microphone"
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.cpp
new file mode 100644
index 00000000..0dfbd4e5
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.cpp
@@ -0,0 +1,106 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ SimPeakMeter.cpp
+
+Abstract:
+
+ Virtual Peakmeter - aggregates all streams
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include "SimPeakMeter.h"
+
+#ifndef __INTELLISENSE__
+#include "SimPeakMeter.tmh"
+#endif
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CSimPeakMeter::CSimPeakMeter()
+{
+ PAGED_CODE();
+ m_NumStreams = 0;
+ m_PeakMeterIndex = 0;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CSimPeakMeter::~CSimPeakMeter()
+{
+ PAGED_CODE();
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+LONG CSimPeakMeter::GetValue(ULONG Channel)
+{
+ PAGED_CODE();
+
+ // Ignore channel
+ UNREFERENCED_PARAMETER(Channel);
+
+#define PEAKMETER_VALUE_FULL (PEAKMETER_MAXIMUM / PEAKMETER_STEPPING_DELTA * PEAKMETER_STEPPING_DELTA)
+#define PEAKMETER_VALUE_HALF (PEAKMETER_MAXIMUM / 2 / PEAKMETER_STEPPING_DELTA * PEAKMETER_STEPPING_DELTA)
+#define PEAKMETER_VALUE_QUARTER (PEAKMETER_MAXIMUM / 4 / PEAKMETER_STEPPING_DELTA * PEAKMETER_STEPPING_DELTA)
+#define PEAKMETER_VALUE_ONE_EIGTH (PEAKMETER_MAXIMUM / 8 / PEAKMETER_STEPPING_DELTA * PEAKMETER_STEPPING_DELTA)
+
+ LONG PeakMeterValues[] = {
+ PEAKMETER_VALUE_ONE_EIGTH,
+ PEAKMETER_VALUE_QUARTER,
+ PEAKMETER_VALUE_HALF,
+ PEAKMETER_VALUE_FULL,
+ PEAKMETER_VALUE_HALF,
+ PEAKMETER_VALUE_QUARTER
+ };
+
+ if (m_NumStreams)
+ {
+ LONG pmi = InterlockedIncrement(&m_PeakMeterIndex);
+ if (pmi == ARRAYSIZE(PeakMeterValues))
+ {
+ pmi = 0;
+ InterlockedExchange(&m_PeakMeterIndex, 0);
+ }
+
+ return PeakMeterValues[pmi];
+ }
+
+ //
+ // No active streams. Peak meter = 0
+ //
+ return 0;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CSimPeakMeter::StartStream()
+{
+ PAGED_CODE();
+ InterlockedIncrement(&m_NumStreams);
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CSimPeakMeter::StopStream()
+{
+ PAGED_CODE();
+
+ ASSERT(m_NumStreams);
+ InterlockedDecrement(&m_NumStreams);
+
+ return STATUS_SUCCESS;
+}
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.h
new file mode 100644
index 00000000..7203f299
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.h
@@ -0,0 +1,51 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ SimPeakMeter.h
+
+Abstract:
+
+ Virtual Peakmeter - aggregates all streams
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#pragma once
+
+class CSimPeakMeter
+{
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ CSimPeakMeter();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ ~CSimPeakMeter();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ LONG GetValue(_In_ ULONG Channel);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS StartStream();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS StopStream();
+
+private:
+ LONG m_NumStreams;
+ LONG m_PeakMeterIndex;
+};
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.cpp
new file mode 100644
index 00000000..f98067c2
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.cpp
@@ -0,0 +1,329 @@
+/*++
+
+Copyright (c) Microsoft Corporation All Rights Reserved
+
+Module Name:
+
+ ToneGenerator
+
+Abstract:
+
+ Implementation of a generic sine wave generator.
+
+--*/
+
+#include <ntdef.h>
+#include <wdm.h>
+#include <minwindef.h>
+#define NOBITMAP
+#include <mmreg.h>
+#undef NOBITMAP
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <limits.h>
+
+#include <ToneGenerator.h>
+
+#define TONEGENERATOR_POOLTAG 'TGMP'
+
+const double TWO_PI = M_PI * 2;
+
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#define IF_FAILED_JUMP(result, tag) do {if (!NT_SUCCESS(result)) {goto tag;}} while(false)
+#define IF_TRUE_JUMP(result, tag) do {if (result) {goto tag;}} while(false)
+#define IF_TRUE_ACTION_JUMP(result, action, tag) do {if (result) {action; goto tag;}} while(false)
+
+//
+// Double to long conversion.
+//
+__drv_maxIRQL(DISPATCH_LEVEL)
+#pragma code_seg()
+long ConvertToLong(double Value)
+{
+ return (long)(Value * _I32_MAX);
+};
+
+//
+// Double to short conversion.
+//
+__drv_maxIRQL(DISPATCH_LEVEL)
+#pragma code_seg()
+short ConvertToShort(double Value)
+{
+ return (short)(Value * _I16_MAX);
+};
+
+//
+// Double to char conversion.
+//
+__drv_maxIRQL(DISPATCH_LEVEL)
+#pragma code_seg()
+unsigned char ConvertToUChar(double Value)
+{
+ const double F_127_5 = 127.5;
+ return (unsigned char)(Value * F_127_5 + F_127_5);
+};
+
+
+//
+// Ctor: basic init.
+//
+_Use_decl_annotations_
+__declspec(code_seg("PAGE"))
+ToneGenerator::ToneGenerator()
+: m_Frequency(0),
+ m_ChannelCount(0),
+ m_BitsPerSample(0),
+ m_SamplesPerSecond(0),
+ m_Mute(false),
+ m_PartialFrame(NULL),
+ m_PartialFrameBytes(0),
+ m_FrameSize(0)
+{
+ // Theta (double) and SampleIncrement (double) are init in the Init() method
+ // after saving the floating point state.
+}
+
+//
+// Dtor: free resources.
+//
+_Use_decl_annotations_
+__declspec(code_seg("PAGE"))
+ToneGenerator::~ToneGenerator()
+{
+ if (m_PartialFrame)
+ {
+ ExFreePoolWithTag(m_PartialFrame, TONEGENERATOR_POOLTAG);
+ m_PartialFrame = NULL;
+ m_PartialFrameBytes = 0;
+ }
+}
+
+//
+// Init a new frame.
+// Note: caller will save and restore the floatingpoint state.
+//
+#pragma warning(push)
+// Caller wraps this routine between KeSaveFloatingPointState/KeRestoreFloatingPointState calls.
+#pragma warning(disable: 28110)
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID ToneGenerator::InitNewFrame
+(
+ _Out_writes_bytes_(FrameSize) BYTE* Frame,
+ _In_ DWORD FrameSize
+)
+{
+ double sinValue = m_ToneDCOffset + m_ToneAmplitude * sin( m_Theta );
+
+ if (FrameSize != (DWORD)m_ChannelCount * m_BitsPerSample/8)
+ {
+ ASSERT(FALSE);
+ RtlZeroMemory(Frame, FrameSize);
+ return;
+ }
+
+ for(ULONG i = 0; i < m_ChannelCount; ++i)
+ {
+ if (m_BitsPerSample == 8)
+ {
+ unsigned char *dataBuffer = reinterpret_cast<unsigned char *>(Frame);
+ dataBuffer[i] = ConvertToUChar(sinValue);
+ }
+ else if (m_BitsPerSample == 16)
+ {
+ short *dataBuffer = reinterpret_cast<short *>(Frame);
+ dataBuffer[i] = ConvertToShort(sinValue);
+ }
+ else if (m_BitsPerSample == 24)
+ {
+ BYTE *dataBuffer = Frame;
+ long val = ConvertToLong(sinValue);
+ val = val >> 8;
+ RtlCopyMemory(dataBuffer, &val, 3);
+ }
+ else if (m_BitsPerSample == 32)
+ {
+ long *dataBuffer = reinterpret_cast<long *>(Frame);
+ dataBuffer[i] = ConvertToLong(sinValue);
+ }
+ }
+
+ m_Theta += m_SampleIncrement;
+ if (m_Theta >= TWO_PI)
+ {
+ m_Theta -= TWO_PI;
+ }
+}
+#pragma warning(pop)
+
+//
+// GenerateSamples()
+//
+// Generate a sine wave that fits into the specified buffer.
+//
+// Buffer - Buffer to hold the samples
+// BufferLength - Length of the buffer.
+//
+//
+_Use_decl_annotations_
+#pragma code_seg()
+void ToneGenerator::GenerateSine
+(
+ _Out_writes_bytes_(BufferLength) BYTE *Buffer,
+ _In_ size_t BufferLength
+)
+{
+ NTSTATUS status;
+ KFLOATING_SAVE saveData;
+ BYTE * buffer;
+ size_t length;
+ size_t copyBytes;
+
+ // if muted, or tone generator disabled via registry,
+ // we deliver silence.
+ if (m_Mute)
+ {
+ goto ZeroBuffer;
+ }
+
+ status = KeSaveFloatingPointState(&saveData);
+ if (!NT_SUCCESS(status))
+ {
+ goto ZeroBuffer;
+ }
+
+ buffer = Buffer;
+ length = BufferLength;
+
+ //
+ // Check if we have any residual frame bytes from the last time.
+ //
+ if (m_PartialFrameBytes)
+ {
+ ASSERT(m_FrameSize > m_PartialFrameBytes);
+ DWORD offset = m_FrameSize - m_PartialFrameBytes;
+ copyBytes = MIN(m_PartialFrameBytes, length);
+ RtlCopyMemory(buffer, m_PartialFrame + offset, copyBytes);
+ RtlZeroMemory(m_PartialFrame + offset, copyBytes);
+ length -= copyBytes;
+ buffer += copyBytes;
+ m_PartialFrameBytes = 0;
+ }
+
+ IF_TRUE_JUMP(length == 0, Done);
+
+ //
+ // Copy all the aligned frames.
+ //
+
+ size_t frames = length/m_FrameSize;
+
+ for (size_t i = 0; i < frames; ++i)
+ {
+ InitNewFrame(buffer, m_FrameSize);
+ buffer += m_FrameSize;
+ length -= m_FrameSize;
+ }
+
+ IF_TRUE_JUMP(length == 0, Done);
+
+ //
+ // Copy any partial frame at the end.
+ //
+ ASSERT(m_FrameSize > length);
+ InitNewFrame(m_PartialFrame, m_FrameSize);
+ RtlCopyMemory(buffer, m_PartialFrame, length);
+ RtlZeroMemory(m_PartialFrame, length);
+ m_PartialFrameBytes = m_FrameSize - (DWORD)length;
+
+Done:
+ KeRestoreFloatingPointState(&saveData);
+ return;
+
+ZeroBuffer:
+ RtlZeroMemory(Buffer, BufferLength);
+ return;
+}
+
+_Use_decl_annotations_
+__declspec(code_seg("PAGE"))
+NTSTATUS ToneGenerator::Init
+(
+ _In_ DWORD ToneFrequency,
+ _In_ double ToneAmplitude,
+ _In_ double ToneDCOffset,
+ _In_ double ToneInitialPhase,
+ _In_ PWAVEFORMATEXTENSIBLE WfExt
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ KFLOATING_SAVE saveData;
+
+ //
+ // This sample supports PCM formats only.
+ //
+ if ((WfExt->Format.wFormatTag != WAVE_FORMAT_PCM &&
+ !(WfExt->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+ IsEqualGUIDAligned(WfExt->SubFormat, KSDATAFORMAT_SUBTYPE_PCM))))
+ {
+ status = STATUS_NOT_SUPPORTED;
+ }
+ IF_FAILED_JUMP(status, Done);
+
+ //
+ // Save floating state (just in case).
+ //
+ status = KeSaveFloatingPointState(&saveData);
+ IF_FAILED_JUMP(status, Done);
+
+ //
+ // Basic init.
+ //
+ m_Theta = ToneInitialPhase;
+ m_Frequency = ToneFrequency;
+ m_ToneAmplitude = ToneAmplitude;
+ m_ToneDCOffset = ToneDCOffset;
+
+ m_ChannelCount = WfExt->Format.nChannels; // # channels.
+ m_BitsPerSample = WfExt->Format.wBitsPerSample; // bits per sample.
+ m_SamplesPerSecond = WfExt->Format.nSamplesPerSec; // samples per sec.
+ m_Mute = false;
+ m_SampleIncrement = (m_Frequency * TWO_PI) / (double)m_SamplesPerSecond;
+ m_FrameSize = (DWORD)m_ChannelCount * m_BitsPerSample/8;
+ ASSERT(m_FrameSize == WfExt->Format.nBlockAlign);
+
+ //
+ // Restore floating state.
+ //
+ KeRestoreFloatingPointState(&saveData);
+
+ //
+ // Allocate a buffer to hold a partial frame.
+ //
+ m_PartialFrame = (BYTE*)ExAllocatePool2(
+ POOL_FLAG_NON_PAGED,
+ m_FrameSize,
+ TONEGENERATOR_POOLTAG);
+
+ IF_TRUE_ACTION_JUMP(m_PartialFrame == NULL, status = STATUS_INSUFFICIENT_RESOURCES, Done);
+
+ status = STATUS_SUCCESS;
+
+Done:
+ return status;
+}
+
+_Use_decl_annotations_
+__declspec(code_seg("PAGE"))
+NTSTATUS ToneGenerator::Init
+(
+ _In_ DWORD ToneFrequency,
+ _In_ PWAVEFORMATEXTENSIBLE WfExt
+)
+{
+ return Init(ToneFrequency, 0.5, 0, 0, WfExt);
+}
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.h
new file mode 100644
index 00000000..c9fd08fc
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.h
@@ -0,0 +1,93 @@
+/*++
+
+Copyright (c) Microsoft Corporation All Rights Reserved
+
+Module Name:
+
+ ToneGenerator.h
+
+Abstract:
+
+ Declaration of a generic sine wave generator.
+
+--*/
+#ifndef _SAMPLE_TONEGENERATOR_H
+#define _SAMPLE_TONEGENERATOR_H
+
+class ToneGenerator
+{
+public:
+ DWORD m_Frequency;
+ WORD m_ChannelCount;
+ WORD m_BitsPerSample;
+ DWORD m_SamplesPerSecond;
+ double m_Theta;
+ double m_SampleIncrement;
+ bool m_Mute;
+ BYTE* m_PartialFrame;
+ DWORD m_PartialFrameBytes;
+ DWORD m_FrameSize;
+ double m_ToneAmplitude;
+ double m_ToneDCOffset;
+
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ __declspec(code_seg("PAGE"))
+ ToneGenerator();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ __declspec(code_seg("PAGE"))
+ ~ToneGenerator();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ __declspec(code_seg("PAGE"))
+ NTSTATUS
+ Init
+ (
+ _In_ DWORD ToneFrequency,
+ _In_ double ToneAmplitude,
+ _In_ double ToneDCOffset,
+ _In_ double ToneInitialPhase,
+ _In_ PWAVEFORMATEXTENSIBLE WfExt
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ __declspec(code_seg("PAGE"))
+ NTSTATUS
+ Init
+ (
+ _In_ DWORD ToneFrequency,
+ _In_ PWAVEFORMATEXTENSIBLE WfExt
+ );
+
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ GenerateSine
+ (
+ _Out_writes_bytes_(BufferLength) BYTE *Buffer,
+ _In_ size_t BufferLength
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ __declspec(code_seg("PAGE"))
+ VOID
+ SetMute
+ (
+ _In_ bool Value
+ )
+ {
+ m_Mute = Value;
+ }
+
+private:
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID InitNewFrame
+ (
+ _Out_writes_bytes_(FrameSize) BYTE* Frame,
+ _In_ DWORD FrameSize
+ );
+};
+
+#endif // _SAMPLE_TONEGENERATOR_H
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/Trace.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/Trace.h
new file mode 100644
index 00000000..e029aad0
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/Trace.h
@@ -0,0 +1,34 @@
+/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+Trace.h
+
+--*/
+
+#pragma once
+
+#include <WppRecorder.h>
+#include <evntrace.h> // For TRACE_LEVEL definitions
+
+#define WPP_TOTAL_BUFFER_SIZE (PAGE_SIZE)
+#define WPP_ERROR_PARTITION_SIZE (WPP_TOTAL_BUFFER_SIZE/4)
+
+// {CDB67DAC-8621-4E28-97A9-9C6EAFAA4A64}
+#define WPP_CONTROL_GUIDS \
+WPP_DEFINE_CONTROL_GUID(DrvLogger,(cdb67dac,8621,4e28,97a9,9c6eafaa4a64), \
+ WPP_DEFINE_BIT(FLAG_DEVICE_ALL) /* bit 0 = 0x00000001 */ \
+ WPP_DEFINE_BIT(FLAG_FUNCTION) /* bit 1 = 0x00000002 */ \
+ WPP_DEFINE_BIT(FLAG_INFO) /* bit 2 = 0x00000004 */ \
+ WPP_DEFINE_BIT(FLAG_PNP) /* bit 3 = 0x00000008 */ \
+ WPP_DEFINE_BIT(FLAG_POWER) /* bit 4 = 0x00000010 */ \
+ WPP_DEFINE_BIT(FLAG_STREAM) /* bit 5 = 0x00000020 */ \
+ WPP_DEFINE_BIT(FLAG_INIT) /* bit 6 = 0x00000040 */ \
+ WPP_DEFINE_BIT(FLAG_DDI) /* bit 7 = 0x00000080 */ \
+ WPP_DEFINE_BIT(FLAG_GENERIC) /* bit 8 = 0x00000100 */ \
+ )
+
+#include "trace_macros.h"
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/WaveReader.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/WaveReader.cpp
new file mode 100644
index 00000000..d7b04892
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/WaveReader.cpp
@@ -0,0 +1,772 @@
+/*++
+
+Copyright (c) Microsoft Corporation All Rights Reserved
+
+Module Name:
+ WaveReader.cpp
+
+Abstract:
+ Implementation of ACX DSP Test Driver wave file reader
+
+ To read data from disk, this class maintains a circular data buffer. This buffer is segmented into multiple chunks of
+ big buffer (Though we only need two, so it is set to two now). Initially we fill first two chunks and once a chunk gets emptied
+ by OS, we schedule a workitem to fill the next available chunk.
+
+
+--*/
+
+#pragma warning (disable : 4127)
+#pragma warning (disable : 26165)
+
+#include "private.h"
+#include <devguid.h>
+#include "stdunk.h"
+#include <ks.h>
+#include <mmsystem.h>
+#include <ksmedia.h>
+#include "WaveReader.h"
+
+#define FILE_NAME_BUFFER_TAG 'WRT1'
+#define WAVE_DATA_BUFFER_TAG 'WRT2'
+#define WORK_ITEM_BUFFER_TAG 'WRT3'
+
+#define MAX_READ_WORKER_ITEM_COUNT 15
+
+#define IF_FAILED_JUMP(result, tag) do {if (!NT_SUCCESS(result)) {goto tag;}} while(false)
+#define IF_TRUE_JUMP(result, tag) do {if (result) {goto tag;}} while(false)
+#define IF_TRUE_ACTION_JUMP(result, action, tag) do {if (result) {action; goto tag;}} while(false)
+
+PREADWORKER_PARAM CWaveReader::m_pWorkItems = NULL;
+PDEVICE_OBJECT CWaveReader::m_pDeviceObject = NULL;
+
+
+/*++
+
+Routine Description:
+ Ctor: basic init.
+
+--*/
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CWaveReader::CWaveReader()
+: m_ChannelCount(0),
+ m_BitsPerSample(0),
+ m_SamplesPerSecond(0),
+ m_Mute(false),
+ m_FileHandle(NULL)
+{
+ PAGED_CODE();
+ m_WaveDataQueue.pWavData = NULL;
+ KeInitializeMutex(&m_FileSync, 0);
+}
+
+/*++
+
+Routine Description:
+ Dtor: free resources.
+
+--*/
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CWaveReader::~CWaveReader()
+{
+ PAGED_CODE();
+ if (STATUS_SUCCESS == KeWaitForSingleObject
+ (
+ &m_FileSync,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL
+ ))
+ {
+ if (m_WaveDataQueue.pWavData != NULL)
+ {
+ ExFreePoolWithTag(m_WaveDataQueue.pWavData, WAVE_DATA_BUFFER_TAG);
+ m_WaveDataQueue.pWavData = NULL;
+ }
+
+ FileClose();
+ KeReleaseMutex(&m_FileSync, FALSE);
+ }
+
+}
+
+/*++
+
+Routine Description:
+ - Initializing the workitems. These workitems will be scheduled asynchronously by the OS.
+ - When these work items will be scheduled the wave file will be read and the data
+ - will be put inside the big chunks.
+
+Arguments:
+ Device object
+
+Return Value:
+ NT status code.
+
+--*/
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CWaveReader::InitializeWorkItems(PDEVICE_OBJECT DeviceObject)
+{
+ PAGED_CODE();
+
+ ASSERT(DeviceObject);
+
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ if (m_pWorkItems != NULL)
+ {
+ return ntStatus;
+ }
+
+ m_pWorkItems = (PREADWORKER_PARAM)
+ ExAllocatePool2
+ (
+ POOL_FLAG_NON_PAGED,
+ sizeof(READWORKER_PARAM) * MAX_READ_WORKER_ITEM_COUNT,
+ 'RDPT'
+ );
+ if (m_pWorkItems)
+ {
+ for (int i = 0; i < MAX_READ_WORKER_ITEM_COUNT; i++)
+ {
+
+ m_pWorkItems[i].WorkItem = IoAllocateWorkItem(DeviceObject);
+ if (m_pWorkItems[i].WorkItem == NULL)
+ {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ KeInitializeEvent
+ (
+ &m_pWorkItems[i].EventDone,
+ NotificationEvent,
+ TRUE
+ );
+ }
+ }
+ else
+ {
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return ntStatus;
+}
+
+/*++
+
+Routine Description:
+- Wait for all the scheduled workitems to finish.
+
+--*/
+
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+void CWaveReader::WaitAllWorkItems()
+{
+ PAGED_CODE();
+
+ for (int i = 0; i < MAX_READ_WORKER_ITEM_COUNT; i++)
+ {
+ KeWaitForSingleObject
+ (
+ &(m_pWorkItems[i].EventDone),
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL
+ );
+ }
+}
+
+/*++
+
+Routine Description:
+ - Deallocating the workitems.
+
+--*/
+
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+VOID CWaveReader::DestroyWorkItems()
+{
+ PAGED_CODE();
+
+ if (m_pWorkItems)
+ {
+ for (int i = 0; i < MAX_READ_WORKER_ITEM_COUNT; i++)
+ {
+ if (m_pWorkItems[i].WorkItem != NULL)
+ {
+ IoFreeWorkItem(m_pWorkItems[i].WorkItem);
+ m_pWorkItems[i].WorkItem = NULL;
+ }
+ }
+ ExFreePoolWithTag(m_pWorkItems, WORK_ITEM_BUFFER_TAG);
+ m_pWorkItems = NULL;
+ }
+}
+
+/*++
+
+Routine Description:
+ - Get a free work item to schedule a file read operation.
+
+--*/
+_Use_decl_annotations_
+#pragma code_seg()
+PREADWORKER_PARAM CWaveReader::GetNewWorkItem()
+{
+ LARGE_INTEGER timeOut = { 0 };
+ NTSTATUS ntStatus;
+
+ for (int i = 0; i < MAX_READ_WORKER_ITEM_COUNT; i++)
+ {
+ ntStatus =
+ KeWaitForSingleObject
+ (
+ &m_pWorkItems[i].EventDone,
+ Executive,
+ KernelMode,
+ FALSE,
+ &timeOut
+ );
+ if (STATUS_SUCCESS == ntStatus)
+ {
+ if (m_pWorkItems[i].WorkItem)
+ return &(m_pWorkItems[i]);
+ else
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+/*++
+Routine Description:
+- This routine will enqueue a workitem for reading wave file and putting
+- the data into the chunk buffer.
+
+Arguments:
+ Chunk descriptor for the chunk to be filled.
+--*/
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID CWaveReader::ReadWavChunk(PCHUNKDESCRIPTOR pChunkDescriptor)
+{
+ PREADWORKER_PARAM pParam = NULL;
+
+ pParam = GetNewWorkItem();
+ if (pParam)
+ {
+ pParam->PtrWaveReader = this;
+ pParam->PtrChunkDescriptor = pChunkDescriptor;
+ KeResetEvent(&pParam->EventDone);
+ IoQueueWorkItem(pParam->WorkItem, ReadFrameWorkerCallback,
+ DelayedWorkQueue, (PVOID)pParam);
+ }
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+IO_WORKITEM_ROUTINE ReadFrameWorkerCallback;
+/*
+Routine Description:
+- This routine will be called by the OS. It will fill the chunk buffer, defined by the chunk descriptor
+- If end of file is reached it will mark the end of file as true.
+
+Arguments:
+ pDeviceObject - Device object
+ Context - pointer to reader worker params
+*/
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+VOID ReadFrameWorkerCallback
+(
+ PDEVICE_OBJECT pDeviceObject,
+ PVOID Context
+)
+{
+ PAGED_CODE();
+ UNREFERENCED_PARAMETER(pDeviceObject);
+ pWaveReader pWavRd;
+ PREADWORKER_PARAM pParam = (PREADWORKER_PARAM)Context;
+
+ if (NULL == pParam)
+ {
+ // This is completely unexpected, assert here.
+ //
+ ASSERT(pParam);
+ goto exit;
+ }
+
+ pWavRd = pParam->PtrWaveReader;
+
+ if (pWavRd == NULL)
+ {
+ goto exit;
+ }
+ if (STATUS_SUCCESS == KeWaitForSingleObject
+ (
+ &pWavRd->m_FileSync,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL
+ ))
+ {
+
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ ASSERT(Context);
+
+ IO_STATUS_BLOCK ioStatusBlock;
+
+ if (pParam->WorkItem)
+ {
+ if (pWavRd->m_WaveDataQueue.bEofReached || pWavRd->m_WaveDataQueue.pWavData == NULL)
+ {
+ KeReleaseMutex(&pWavRd->m_FileSync, FALSE);
+ goto exit;
+ }
+
+ if (pParam->PtrChunkDescriptor->pStartAddress != NULL)
+ {
+ ntStatus = ZwReadFile(pWavRd->m_FileHandle,
+ NULL,
+ NULL,
+ NULL,
+ &ioStatusBlock,
+ pParam->PtrChunkDescriptor->pStartAddress,
+ pParam->PtrChunkDescriptor->ulChunkLength,
+ NULL,
+ NULL);
+
+ pParam->PtrChunkDescriptor->bIsChunkEmpty = false;
+
+ if (ioStatusBlock.Information != pParam->PtrChunkDescriptor->ulChunkLength)
+ {
+ pWavRd->m_WaveDataQueue.bEofReached = true;
+ }
+ }
+ }
+
+ KeReleaseMutex(&pWavRd->m_FileSync, FALSE);
+ }
+
+exit:
+ KeSetEvent(&pParam->EventDone, 0, FALSE);
+}
+
+/*++
+
+Routine Description:
+- If all the chunks are empty this resturn true.
+
+--*/
+
+_Use_decl_annotations_
+#pragma code_seg()
+bool CWaveReader::IsAllChunkEmpty()
+{
+ for (int i = 0; i < NUM_OF_CHUNK_FOR_FILE_READ; i++)
+ {
+ if (!m_WaveDataQueue.sChunkDescriptor[i].bIsChunkEmpty)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*++
+Routine Description:
+ - This routine does the actual copy of data from the chunk buffer to the buffer provided by OS.
+ - If it empties the current chunk buffer, then it sets it state to empty and then enqueue a workitem
+ - to read data from the wave file and put it to the next available chunk buffer.
+
+Arguments:
+ Buffer - Pointer to the OS buffer
+ BufferLength - Length of the data to be filled (in bytes)
+
+--*/
+_Use_decl_annotations_
+#pragma code_seg()
+VOID CWaveReader::CopyDataFromRingBuffer
+(
+ BYTE *Buffer,
+ ULONG BufferLength
+)
+{
+ if (IsAllChunkEmpty())
+ {
+ RtlZeroMemory(Buffer, BufferLength);
+ }
+ else
+ {
+ ULONG prevChunk = (m_WaveDataQueue.ulReadPtr*NUM_OF_CHUNK_FOR_FILE_READ )/ m_WaveDataQueue.ulLength;
+
+ /////////////////
+ BYTE *currentBuf = Buffer;
+ ULONG length = BufferLength;
+ while (length > 0)
+ {
+ ULONG runWrite = min(length, m_WaveDataQueue.ulLength - m_WaveDataQueue.ulReadPtr);
+
+ // Copy the wave buffer data to OS buffer
+ RtlCopyMemory(currentBuf, m_WaveDataQueue.pWavData + m_WaveDataQueue.ulReadPtr, runWrite);
+ // Zero out the wave buffer, so that if wave end of file is reached we should copy only zeros
+ RtlZeroMemory(m_WaveDataQueue.pWavData + m_WaveDataQueue.ulReadPtr, runWrite);
+ // Update the read pointer
+ m_WaveDataQueue.ulReadPtr = (m_WaveDataQueue.ulReadPtr + runWrite) % m_WaveDataQueue.ulLength;
+ currentBuf += runWrite;
+ length = length - runWrite;
+ }
+
+ ULONG curChunk = (m_WaveDataQueue.ulReadPtr*NUM_OF_CHUNK_FOR_FILE_READ) / m_WaveDataQueue.ulLength;
+
+ if (curChunk != prevChunk)
+ {
+ m_WaveDataQueue.currentExecutedChunk++;
+ // Schedule a workitem to read data from the wave file
+ ULONG chunkNo = m_WaveDataQueue.currentExecutedChunk % NUM_OF_CHUNK_FOR_FILE_READ;
+ m_WaveDataQueue.sChunkDescriptor[chunkNo].bIsChunkEmpty = true;
+ if (!m_WaveDataQueue.bEofReached)
+ {
+ ReadWavChunk(&m_WaveDataQueue.sChunkDescriptor[chunkNo]);
+ }
+ }
+ }
+}
+
+/*++
+Routine Description:
+ - Just a high level read buffer call.
+
+ Arguments:
+ Buffer - Pointer to the OS buffer
+ BufferLength - Length of the data to be filled (in bytes)
+--*/
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID CWaveReader::ReadWaveData
+(
+ BYTE *Buffer,
+ ULONG BufferLength
+)
+{
+ if (m_Mute)
+ {
+ RtlZeroMemory(Buffer, BufferLength);
+ }
+ else
+ {
+ CopyDataFromRingBuffer(Buffer, BufferLength);
+ }
+}
+
+/*++
+Routine Description:
+- initialization for the wavereader member variables,
+- Allocating memory for the 1 second buffer
+- Preread the one second buffer data, so that when OS comes to read the data we have it available in the memory.
+
+Arguments:
+ WfExt - Format which should be used for capture
+ fileNameString - name of the file to be read
+
+Return:
+ NTStatus
+--*/
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CWaveReader::Init
+(
+ PWAVEFORMATEXTENSIBLE WfExt,
+ PUNICODE_STRING puiFileName
+)
+{
+ PAGED_CODE();
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+ KFLOATING_SAVE saveData;
+
+ // Save floating state (just in case).
+ ntStatus = KeSaveFloatingPointState(&saveData);
+ if (!NT_SUCCESS(ntStatus))
+ {
+ return ntStatus;
+ }
+
+ //
+ // This sample supports PCM 16bit formats only.
+ //
+ if ((WfExt->Format.wFormatTag != WAVE_FORMAT_PCM &&
+ !(WfExt->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+ IsEqualGUIDAligned(WfExt->SubFormat, KSDATAFORMAT_SUBTYPE_PCM))) ||
+ (WfExt->Format.wBitsPerSample != 16 &&
+ WfExt->Format.wBitsPerSample != 8))
+ {
+ ntStatus = STATUS_NOT_SUPPORTED;
+ }
+ IF_FAILED_JUMP(ntStatus, Done);
+
+ // Basic init.
+ m_ChannelCount = WfExt->Format.nChannels; // # channels.
+ m_BitsPerSample = WfExt->Format.wBitsPerSample; // bits per sample.
+ m_SamplesPerSecond = WfExt->Format.nSamplesPerSec; // samples per sec.
+ m_Mute = false;
+
+ // Wave data queue initialization
+ m_WaveDataQueue.ulLength = WfExt->Format.nAvgBytesPerSec;
+ m_WaveDataQueue.bEofReached = false;
+ m_WaveDataQueue.ulReadPtr = 0;
+
+ // Mark all the chunk empty
+ for (int i = 0; i < NUM_OF_CHUNK_FOR_FILE_READ; i++)
+ {
+ m_WaveDataQueue.sChunkDescriptor[i].bIsChunkEmpty = true;
+ }
+
+ ntStatus = OpenWaveFile(puiFileName);
+ IF_FAILED_JUMP(ntStatus, Done);
+
+ ntStatus = AllocateBigBuffer();
+ IF_FAILED_JUMP(ntStatus, Done);
+
+ ntStatus = ReadHeaderAndFillBuffer();
+
+Done:
+ (void)KeRestoreFloatingPointState(&saveData);
+ return ntStatus;
+}
+
+/*++
+Routine Description:
+ This function read the wave header file and compare the header info with the
+ stream info. Currently we are using only number of channel, sampling frequency
+ and bits per sample as the primary parameters for the wave file to compare against
+ stream params. If the params don't match we return success but streams zeros.
+
+Return:
+ NTStatus
+--*/
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CWaveReader::ReadHeaderAndFillBuffer()
+{
+ PAGED_CODE();
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+ ntStatus = FileReadHeader();
+
+ if(NT_SUCCESS(ntStatus))
+ {
+ if (m_WaveHeader.numChannels != m_ChannelCount ||
+ m_WaveHeader.bitsPerSample != m_BitsPerSample ||
+ m_WaveHeader.sampleRate != m_SamplesPerSecond)
+ {
+ // If the wave file format don't match we wont treat this as error
+ // and we will stream zeros. So we return from here and will not read the
+ // wave file and wont fill the buffers.
+ return STATUS_SUCCESS;
+ }
+ }
+
+ if (NT_SUCCESS(ntStatus))
+ {
+ // If the wave file format is same as the stream format we will stream the data
+ // else we will just stream zeros.
+ ReadWavChunk(&m_WaveDataQueue.sChunkDescriptor[0]); // Fill the first chunk
+ ReadWavChunk(&m_WaveDataQueue.sChunkDescriptor[1]); // Fill the second chunk
+ // Set the current executed chunk to 1. Once OS finishs the data for the first chunk
+ // use the currentExecutedChunk to find the next chunk and schedule a workitem to fill the
+ // data into the next chunk
+ m_WaveDataQueue.currentExecutedChunk = 1;
+ }
+
+ return ntStatus;
+}
+
+/*++
+Routine Description:
+ This function allocates 1 second buffer.
+ Segments the buffer into multiple (currently two) chunks. Assigns the start pointer and length
+ for each chunk.
+
+Return:
+ NTStatus
+--*/
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CWaveReader::AllocateBigBuffer()
+{
+ PAGED_CODE();
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ m_WaveDataQueue.pWavData = (PBYTE)
+ ExAllocatePool2
+ (
+ POOL_FLAG_NON_PAGED,
+ m_WaveDataQueue.ulLength,
+ WAVE_DATA_BUFFER_TAG
+ );
+ if (!m_WaveDataQueue.pWavData)
+ {
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ else
+ {
+ // ExAllocatePool2 zeros memory.
+
+ ULONG chunklLength = m_WaveDataQueue.ulLength / NUM_OF_CHUNK_FOR_FILE_READ;
+ for (int i = 0; i < NUM_OF_CHUNK_FOR_FILE_READ; i++)
+ {
+ m_WaveDataQueue.sChunkDescriptor[i].pStartAddress = m_WaveDataQueue.pWavData + chunklLength*i;
+ m_WaveDataQueue.sChunkDescriptor[i].ulChunkLength = chunklLength;
+ }
+ }
+ return ntStatus;
+}
+
+/*++
+Routine Description:
+ This function opens wave file.
+
+Arguments:
+ fileNameString - Name of the wave file
+
+Return:
+ NTStatus
+--*/
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CWaveReader::OpenWaveFile(PUNICODE_STRING puiFileName)
+{
+ PAGED_CODE();
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ if (NT_SUCCESS(ntStatus) && puiFileName->Buffer != NULL)
+ {
+ // Create data file.
+ InitializeObjectAttributes
+ (
+ &m_objectAttributes,
+ puiFileName,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ NULL,
+ NULL
+ );
+
+ // Open Wave File
+ ntStatus = FileOpen();
+ }
+
+ return ntStatus;
+}
+
+/*++
+Routine Description:
+ This function closes wave file handle.
+
+Return:
+ NTStatus
+--*/
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CWaveReader::FileClose()
+{
+ PAGED_CODE();
+
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ if (m_FileHandle)
+ {
+ ntStatus = ZwClose(m_FileHandle);
+ m_FileHandle = NULL;
+ }
+
+ return ntStatus;
+}
+
+/*++
+Routine Description:
+ Reads the wave file file header information
+
+Return:
+ NTStatus
+--*/
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CWaveReader::FileReadHeader()
+{
+ PAGED_CODE();
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+ IO_STATUS_BLOCK ioStatusBlock;
+
+
+ ntStatus = ZwReadFile(m_FileHandle,
+ NULL,
+ NULL,
+ NULL,
+ &ioStatusBlock,
+ &m_WaveHeader,
+ sizeof(WAVEHEADER),
+ NULL,
+ NULL);
+
+ return ntStatus;
+}
+
+/*++
+Routine Description:
+ This function opens wave file.
+
+Return:
+ NTStatus
+--*/
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS CWaveReader::FileOpen()
+{
+
+ PAGED_CODE();
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+ IO_STATUS_BLOCK ioStatusBlock;
+
+ if (!m_FileHandle)
+ {
+ ntStatus =
+ ZwCreateFile
+ (
+ &m_FileHandle,
+ GENERIC_READ,
+ &m_objectAttributes,
+ &ioStatusBlock,
+ NULL,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ,
+ FILE_OPEN,
+ FILE_SYNCHRONOUS_IO_NONALERT,
+ NULL,
+ 0
+ );
+ }
+
+ return ntStatus;
+}
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/WaveReader.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/WaveReader.h
new file mode 100644
index 00000000..2d5c537f
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/WaveReader.h
@@ -0,0 +1,196 @@
+/*++
+
+Copyright (c) Microsoft Corporation All Rights Reserved
+
+Module Name:
+
+ WaveReader.h
+
+Abstract:
+
+ Declaration of ACX DSP Test Driver wave reader.
+
+
+--*/
+#pragma once
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <limits.h>
+
+#define NUM_OF_CHUNK_FOR_FILE_READ 2
+
+class CWaveReader;
+
+// Wave header structure decleration
+typedef CWaveReader *pWaveReader;
+typedef struct _WAVEHEADER
+{
+ BYTE chunkId[4];
+ ULONG chunkSize;
+ BYTE format[4];
+ BYTE subChunkId[4];
+ ULONG subChunkSize;
+ WORD audioFormat;
+ WORD numChannels;
+ ULONG sampleRate;
+ ULONG bytesPerSecond;
+ WORD blockAlign;
+ WORD bitsPerSample;
+ BYTE dataChunkId[4];
+ ULONG dataSize;
+}WAVEHEADER;
+
+typedef struct _CHUNKDESCRIPTOR
+{
+ PBYTE pStartAddress; // Starting address of the chunk
+ ULONG ulChunkLength; // Length of the chunk
+ bool bIsChunkEmpty; // If the chunk is empty
+}CHUNKDESCRIPTOR;
+typedef CHUNKDESCRIPTOR *PCHUNKDESCRIPTOR;
+
+/*
+ The idea here is to allocate one second long worth of buffer and divide it into NUM_OF_CHUNK_FOR_FILE_READ chunks.
+ In one file read operation we read and fill one chunk data . The chunk will be emptied every 10 ms by OS.
+ Once the OS empties one chunk data we schedule a workitem to read and fill next available chunk.
+*/
+
+typedef struct _WAVEDATAQUEUE
+{
+ PBYTE pWavData; // Pointer to the temporary buffer for reading one second worth of data from wave file
+ ULONG ulLength; // length of pWavData in bytes
+ ULONG ulReadPtr; // current reading position in pWavData in bytes
+ bool bEofReached; // This will be set once the eof is reached.
+ WORD currentExecutedChunk;
+ CHUNKDESCRIPTOR sChunkDescriptor[NUM_OF_CHUNK_FOR_FILE_READ];
+}WAVEDATAQUEUE;
+typedef WAVEDATAQUEUE *PWAVEDATAQUEUE;
+
+// Parameter to workitem.
+#include <pshpack1.h>
+typedef struct _READWORKER_PARAM {
+ PIO_WORKITEM WorkItem; // Pointer to the workitem
+ KEVENT EventDone; // Used for synchronizing a workitem for scheduling.
+ pWaveReader PtrWaveReader; // pointer to the wavereader class.
+ PCHUNKDESCRIPTOR PtrChunkDescriptor; // chunk descriptor for the chunk, which needs to be filled after file read
+} READWORKER_PARAM;
+typedef READWORKER_PARAM *PREADWORKER_PARAM;
+#include <poppack.h>
+
+__drv_maxIRQL(PASSIVE_LEVEL)
+PAGED_CODE_SEG
+IO_WORKITEM_ROUTINE ReadFrameWorkerCallback;
+
+// Wave Reader class
+
+class CWaveReader
+{
+
+public:
+ HANDLE m_FileHandle; // Wave File handle.
+ WORD m_ChannelCount; // Number of Channels for the stream during stream init
+ WORD m_BitsPerSample; // Number of Bits per sample for the stream during stream init
+ DWORD m_SamplesPerSecond; // Number of Sample per second for the stream during stream init
+ bool m_Mute; // Capture Zero buffer if mute
+ OBJECT_ATTRIBUTES m_objectAttributes; // Used for opening file.
+ WAVEDATAQUEUE m_WaveDataQueue; // Big buffer data object and its current state
+ KMUTEX m_FileSync; // Synchronizes file access
+ WAVEHEADER m_WaveHeader;
+
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ CWaveReader();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ ~CWaveReader();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS Init
+ (
+ _In_ PWAVEFORMATEXTENSIBLE WfExt,
+ _In_ PUNICODE_STRING puiFileName
+ );
+
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID ReadWaveData
+ (
+ _Out_writes_bytes_(BufferLength) BYTE *Buffer,
+ _In_ ULONG BufferLength
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ VOID SetMute(_In_ bool Value)
+ {
+ PAGED_CODE();
+
+ m_Mute = Value;
+ }
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ void WaitAllWorkItems();
+
+ // Static allocation totally related to the workitems for reading data from wavefile and putting it to chunk buffer
+ static PDEVICE_OBJECT m_pDeviceObject;
+ static PREADWORKER_PARAM m_pWorkItems;
+ PAGED_CODE_SEG
+ static NTSTATUS InitializeWorkItems(_In_ PDEVICE_OBJECT DeviceObject);
+
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ static PREADWORKER_PARAM GetNewWorkItem();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ static VOID DestroyWorkItems();
+
+private:
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID ReadWavChunk(PCHUNKDESCRIPTOR PtrChunkDescriptor);
+
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ bool IsAllChunkEmpty();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS OpenWaveFile(PUNICODE_STRING puiFileName);
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS FileClose();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS FileReadHeader();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS FileOpen();
+
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID CopyDataFromRingBuffer
+ (
+ _Out_writes_bytes_(BufferLength) BYTE *Buffer,
+ _In_ ULONG BufferLength
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS AllocateBigBuffer();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS ReadHeaderAndFillBuffer();
+
+ friend IO_WORKITEM_ROUTINE ReadFrameWorkerCallback;
+};
+
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/capture.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/capture.cpp
new file mode 100644
index 00000000..eccd5738
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/capture.cpp
@@ -0,0 +1,1465 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ capture.cpp
+
+Abstract:
+
+ capture factory and circuit
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include <devguid.h>
+#include "stdunk.h"
+#include <ks.h>
+#include <mmsystem.h>
+#include <ksmedia.h>
+#include "streamengine.h"
+#include "CircuitHelper.h"
+
+#include "TestProperties.h"
+#include "KeywordDetector.h"
+#include "sdcastreaming.h"
+
+#include "AudioFormats.h"
+
+#ifndef __INTELLISENSE__
+#include "capture.tmh"
+#endif
+
+//
+// max # of streams.
+//
+#define DSPC_MAX_OUTPUT_SYSTEM_STREAMS 1
+#define DSPC_MAX_OUTPUT_KEYWORDDETECTOR_STREAMS 1
+
+//
+// Factory circuit IDs.
+//
+#define CAPTURE_DEVICE_ID_STR L"{4DCB0606-6415-4A36-BDC5-9B1792117DC9}\\Capture&CP_%wZ"
+DECLARE_CONST_UNICODE_STRING(CaptureHardwareId, L"{4DCB0606-6415-4A36-BDC5-9B1792117DC9}\\Capture");
+
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtAcxKeywordSpotterRetrieveArm(
+ _In_ ACXKEYWORDSPOTTER KeywordSpotter,
+ _In_ GUID * EventId,
+ _Out_ BOOLEAN * Arm
+)
+{
+ PAGED_CODE();
+ PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx;
+ CKeywordDetector * keywordDetector = NULL;
+
+ keywordSpotterCtx = GetDspKeywordSpotterContext(KeywordSpotter);
+
+ keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector;
+
+ RETURN_NTSTATUS_IF_FAILED(keywordDetector->GetArmed(*EventId, Arm));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtAcxKeywordSpotterAssignArm(
+ _In_ ACXKEYWORDSPOTTER KeywordSpotter,
+ _In_ GUID * EventId,
+ _In_ BOOLEAN Arm
+)
+{
+ PAGED_CODE();
+ PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx;
+ CKeywordDetector * keywordDetector = NULL;
+
+ keywordSpotterCtx = GetDspKeywordSpotterContext(KeywordSpotter);
+
+ keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector;
+
+ RETURN_NTSTATUS_IF_FAILED(keywordDetector->SetArmed(*EventId, Arm));
+
+ // the following code is for example only, after arming the
+ // requested keyword we immediately trigger a detection
+ // so that the automated tests do not block.
+ if (Arm)
+ {
+ CONTOSO_KEYWORDDETECTIONRESULT detectionResult;
+
+ // notify the keyword detector that we have a notification, to populate
+ // timestamp information for this detection.
+ keywordDetector->NotifyDetection();
+
+ // fill in the detection specific information
+ detectionResult.EventId = *EventId;
+ detectionResult.Header.Size = sizeof(CONTOSO_KEYWORDDETECTIONRESULT);
+ detectionResult.Header.PatternType = CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2;
+ detectionResult.KeywordStartTimestamp = keywordDetector->GetStartTimestamp();
+ detectionResult.KeywordStopTimestamp = keywordDetector->GetStopTimestamp();
+ keywordDetector->GetDetectorData(*EventId, &(detectionResult.ContosoDetectorResultData));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(keywordSpotterCtx->Event, &detectionResult, sizeof(CONTOSO_KEYWORDDETECTIONRESULT)));
+ }
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtAcxKeywordSpotterAssignPatterns(
+ _In_ ACXKEYWORDSPOTTER KeywordSpotter,
+ _In_ GUID * EventId,
+ _In_ PVOID Pattern,
+ _In_ ULONG PatternSize
+ )
+{
+ KSMULTIPLE_ITEM * itemsHeader = nullptr;
+ SOUNDDETECTOR_PATTERNHEADER * patternHeader;
+ CONTOSO_KEYWORDCONFIGURATION * pattern;
+ ULONG cbRemaining = 0;
+ PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx;
+ CKeywordDetector * keywordDetector = NULL;
+
+ PAGED_CODE();
+
+ keywordSpotterCtx = GetDspKeywordSpotterContext(KeywordSpotter);
+
+ keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector;
+
+ cbRemaining = PatternSize;
+
+ // The SYSVADPROPERTY_ITEM for this property ensures the value size is at
+ // least sizeof KSMULTIPLE_ITEM.
+ RETURN_NTSTATUS_IF_TRUE(cbRemaining < sizeof(KSMULTIPLE_ITEM), STATUS_INVALID_PARAMETER);
+
+ itemsHeader = (KSMULTIPLE_ITEM*)Pattern;
+
+ // Verify property value is large enough to include the items
+ RETURN_NTSTATUS_IF_TRUE(itemsHeader->Size > cbRemaining, STATUS_INVALID_PARAMETER);
+
+ // No items so clear the configuration.
+ if (itemsHeader->Count == 0)
+ {
+ keywordDetector->ResetDetector(*EventId);
+ }
+ else
+ {
+ // This sample supports only 1 pattern type.
+ RETURN_NTSTATUS_IF_TRUE(itemsHeader->Count > 1, STATUS_NOT_SUPPORTED);
+
+ // Bytes remaining after the items header
+ cbRemaining = itemsHeader->Size - sizeof(*itemsHeader);
+
+ // Verify the property value is large enough to include the pattern header.
+ RETURN_NTSTATUS_IF_TRUE(cbRemaining < sizeof(SOUNDDETECTOR_PATTERNHEADER), STATUS_INVALID_PARAMETER);
+
+ patternHeader = (SOUNDDETECTOR_PATTERNHEADER*)(itemsHeader + 1);
+
+ // Verify the pattern type is supported.
+ RETURN_NTSTATUS_IF_TRUE(patternHeader->PatternType != CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2, STATUS_NOT_SUPPORTED);
+
+ // Verify the property value is large enough for the pattern.
+ RETURN_NTSTATUS_IF_TRUE(cbRemaining < patternHeader->Size, STATUS_INVALID_PARAMETER);
+
+ // Verify the pattern is large enough.
+ RETURN_NTSTATUS_IF_TRUE(patternHeader->Size != sizeof(CONTOSO_KEYWORDCONFIGURATION), STATUS_INVALID_PARAMETER);
+
+ pattern = (CONTOSO_KEYWORDCONFIGURATION*)(patternHeader);
+
+ RETURN_NTSTATUS_IF_FAILED(keywordDetector->DownloadDetectorData(*EventId, pattern->ContosoDetectorConfigurationData));
+ }
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtAcxKeywordSpotterAssignReset(
+ _In_ ACXKEYWORDSPOTTER KeywordSpotter,
+ _In_ GUID * EventId
+ )
+{
+ PAGED_CODE();
+ PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx;
+ CKeywordDetector * keywordDetector = NULL;
+
+ keywordSpotterCtx = GetDspKeywordSpotterContext(KeywordSpotter);
+
+ keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector;
+
+ RETURN_NTSTATUS_IF_FAILED(keywordDetector->ResetDetector(*EventId));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtAcxFactoryCircuitCreateCircuitDevice(
+ _In_ WDFDEVICE Parent,
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig,
+ _Out_ WDFDEVICE * Device
+)
+{
+ PAGED_CODE();
+
+ DrvLogEnter(g_SDCAVDspLog);
+
+ NTSTATUS status = STATUS_SUCCESS;
+ WDF_OBJECT_ATTRIBUTES attributes;
+
+ UNREFERENCED_PARAMETER(Factory);
+
+ *Device = NULL;
+
+ // Allocate a generic buffer to hold a PnP ID of this device.
+ // MAX_DEVICE_ID_LEN is the count of wchar in the device ID name.
+ C_ASSERT(NTSTRSAFE_UNICODE_STRING_MAX_CCH >= MAX_DEVICE_ID_LEN);
+ C_ASSERT(USHORT_MAX >= MAX_DEVICE_ID_LEN * sizeof(WCHAR));
+ WCHAR *wstrBuffer = NULL;
+ const USHORT wstrBufferCch = MAX_DEVICE_ID_LEN;
+ wstrBuffer = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) WCHAR[wstrBufferCch];
+ RETURN_NTSTATUS_IF_TRUE(wstrBuffer == NULL, STATUS_INSUFFICIENT_RESOURCES);
+ auto wstrBufferFree = scope_exit([&wstrBuffer]() {
+ delete[] wstrBuffer;
+ wstrBuffer = NULL;
+ });
+
+ RtlZeroMemory(wstrBuffer, sizeof(WCHAR) * wstrBufferCch);
+
+ //
+ // Create a child audio device for this circuit.
+ //
+ PWDFDEVICE_INIT devInit = NULL;
+ devInit = WdfPdoInitAllocate(Parent);
+ RETURN_NTSTATUS_IF_TRUE(NULL == devInit, STATUS_INSUFFICIENT_RESOURCES);
+ auto devInitFree = scope_exit([&devInit]() {
+ WdfDeviceInitFree(devInit);
+ devInit = NULL;
+ });
+
+ //
+ // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId
+ //
+
+ //
+ // Create the PnP Device ID.
+ //
+ // Retrieve the unique id of this composite. This logic uses this unique id to
+ // make the device id unique. Using a deterministic value for the pnp device id, guarantees
+ // that the KS properties associated with this audio device interface stay the same across
+ // reboots, even when the circuit factory is used in several ACX composites.
+ //
+ {
+ DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(UniqueID);
+
+ ACX_OBJECTBAG_CONFIG objBagCfg;
+ ACX_OBJECTBAG_CONFIG_INIT(&objBagCfg);
+ objBagCfg.Handle = CircuitConfig->CompositeProperties;
+ objBagCfg.Flags |= AcxObjectBagConfigOpenWithHandle;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ ACXOBJECTBAG objBag = NULL;
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &objBagCfg, &objBag));
+ auto objBagFree = scope_exit([&objBag]() {
+ WdfObjectDelete(objBag);
+ objBag = NULL;
+ });
+
+ GUID uniqueId = { 0 };
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveGuid(objBag, &UniqueID, &uniqueId));
+
+ UNICODE_STRING uniqueIdStr = { 0 };
+ RETURN_NTSTATUS_IF_FAILED(RtlStringFromGUID(uniqueId, &uniqueIdStr));
+
+ // Init the deviceId unicode string.
+ UNICODE_STRING pnpDeviceId = {0};
+ pnpDeviceId.Buffer = wstrBuffer;
+ pnpDeviceId.Length = 0;
+ pnpDeviceId.MaximumLength = (USHORT)(sizeof(WCHAR) * wstrBufferCch);
+
+ status = RtlUnicodeStringPrintf(&pnpDeviceId, CAPTURE_DEVICE_ID_STR, &uniqueIdStr);
+
+ RtlFreeUnicodeString(&uniqueIdStr);
+
+ RETURN_NTSTATUS_IF_FAILED(status);
+
+ // This is the device ID and the first H/W ID.
+ // This ID is used to create a unique audio device interface.
+ // Note that this ID is NOT the match with this driver's INF.
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignDeviceID(devInit, &pnpDeviceId));
+
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &pnpDeviceId));
+ }
+
+ // This H/W ID is the match with this driver's INF.
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &CaptureHardwareId));
+
+ /*
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddCompatibleID(devInit, &CaptureCompatibleId));
+
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignInstanceID(devInit, &CaptureInstanceId));
+
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignContainerID(devInit, &CaptureContainerId));
+
+ //
+ // You can call WdfPdoInitAddDeviceText multiple times, adding device
+ // text for multiple locales. When the system displays the text, it
+ // chooses the text that matches the current locale, if available.
+ // Otherwise it will use the string for the default locale.
+ // The driver can specify the driver's default locale by calling
+ // WdfPdoInitSetDefaultLocale.
+ //
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddDeviceText(devInit,
+ &CaptureDeviceLocation,
+ &CaptureDeviceLocation,
+ 0x409));
+ */
+
+ WdfPdoInitSetDefaultLocale(devInit, 0x409);
+
+ //
+ // Allow ACX to add any pre-requirement it needs on this device.
+ //
+ ACX_DEVICEINIT_CONFIG devInitCfg;
+ ACX_DEVICEINIT_CONFIG_INIT(&devInitCfg);
+ devInitCfg.Flags |= AcxDeviceInitConfigRawDevice;
+ RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(devInit, &devInitCfg));
+
+ //
+ // Initialize the pnpPowerCallbacks structure. Callback events for PNP
+ // and Power are specified here. If you don't supply any callbacks,
+ // the Framework will take appropriate default actions based on whether
+ // DeviceInit is initialized to be an FDO, a PDO or a filter device
+ // object.
+ //
+ WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
+ WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
+ pnpPowerCallbacks.EvtDevicePrepareHardware = DspC_EvtDevicePrepareHardware;
+ pnpPowerCallbacks.EvtDeviceReleaseHardware = DspC_EvtDeviceReleaseHardware;
+ pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = DspC_EvtDeviceSelfManagedIoInit;
+ WdfDeviceInitSetPnpPowerEventCallbacks(devInit, &pnpPowerCallbacks);
+
+ //
+ // Specify a context for this capture device.
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_CAPTURE_DEVICE_CONTEXT);
+ attributes.EvtCleanupCallback = DspC_EvtDeviceContextCleanup;
+ attributes.ExecutionLevel = WdfExecutionLevelPassive;
+
+ WDFDEVICE device;
+ RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&devInit, &attributes, &device));
+ devInitFree.release();
+
+ //
+ // Init capture's device context.
+ //
+ PDSP_CAPTURE_DEVICE_CONTEXT devCtx;
+ devCtx = GetCaptureDeviceContext(device);
+ ASSERT(devCtx != NULL);
+
+ //
+ // Set device capabilities.
+ //
+ {
+ WDF_DEVICE_PNP_CAPABILITIES pnpCaps;
+ WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps);
+
+ pnpCaps.SurpriseRemovalOK = WdfTrue;
+ pnpCaps.UniqueID = WdfFalse;
+
+ WdfDeviceSetPnpCapabilities(device, &pnpCaps);
+ }
+
+ //
+ // Allow ACX to add any post-requirement it needs on this device.
+ //
+ ACX_DEVICE_CONFIG devCfg;
+ ACX_DEVICE_CONFIG_INIT(&devCfg);
+ RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(device, &devCfg));
+
+ *Device = device;
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspC_CreateKeywordSpotterElement(
+ _In_ WDFDEVICE Device,
+ _In_ ACXCIRCUIT Circuit,
+ _Out_ ACXKEYWORDSPOTTER * Element
+)
+{
+ WDF_OBJECT_ATTRIBUTES attributes;
+ ACX_KEYWORDSPOTTER_CALLBACKS keywordSpotterCallbacks;
+ ACX_KEYWORDSPOTTER_CONFIG keywordSpotterCfg;
+ PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx;
+ ACX_PNPEVENT_CONFIG keywordEventCfg;
+ ACXPNPEVENT keywordEvent;
+
+ PAGED_CODE();
+
+ ACX_KEYWORDSPOTTER_CALLBACKS_INIT(&keywordSpotterCallbacks);
+ keywordSpotterCallbacks.EvtAcxKeywordSpotterRetrieveArm = DspC_EvtAcxKeywordSpotterRetrieveArm;
+ keywordSpotterCallbacks.EvtAcxKeywordSpotterAssignArm = DspC_EvtAcxKeywordSpotterAssignArm;
+ keywordSpotterCallbacks.EvtAcxKeywordSpotterAssignPatterns = DspC_EvtAcxKeywordSpotterAssignPatterns;
+ keywordSpotterCallbacks.EvtAcxKeywordSpotterAssignReset = DspC_EvtAcxKeywordSpotterAssignReset;
+
+ ACX_KEYWORDSPOTTER_CONFIG_INIT(&keywordSpotterCfg);
+ keywordSpotterCfg.Pattern = &CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2;
+ keywordSpotterCfg.Callbacks = &keywordSpotterCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_KEYWORDSPOTTER_CONTEXT);
+ attributes.ParentObject = Circuit;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxKeywordSpotterCreate(Circuit, &attributes, &keywordSpotterCfg, Element));
+
+ keywordSpotterCtx = GetDspKeywordSpotterContext(*Element);
+ ASSERT(keywordSpotterCtx);
+
+ keywordSpotterCtx->KeywordDetector = (PVOID) new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CKeywordDetector(Device, Circuit, &(Pcm44100c1.WaveFormatExt));
+ RETURN_NTSTATUS_IF_TRUE(keywordSpotterCtx->KeywordDetector == NULL, STATUS_INSUFFICIENT_RESOURCES);
+
+ ACX_PNPEVENT_CONFIG_INIT(&keywordEventCfg);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT);
+ attributes.ParentObject = *Element;
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, *Element, &attributes, &keywordEventCfg, &keywordEvent));
+
+ keywordSpotterCtx->Event = keywordEvent;
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtAcxFactoryCircuitCreateCircuit(
+ _In_ WDFDEVICE Parent,
+ _In_ WDFDEVICE Device,
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig,
+ _In_ PACXCIRCUIT_INIT CircuitInit,
+ _In_ ULONG DataPortNumber,
+ _In_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors
+)
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(Factory);
+
+ DrvLogEnter(g_SDCAVDspLog);
+
+ NTSTATUS status = STATUS_SUCCESS;
+ WDF_OBJECT_ATTRIBUTES attributes;
+
+ DECLARE_CONST_UNICODE_STRING(circuitName, L"Microphone0");
+
+ //
+ // Init output value.
+ //
+ ASSERT(Device);
+
+ DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(EndpointId);
+ ULONG endpointId = 0;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ RETURN_NTSTATUS_IF_FAILED(RetrieveProperties(CircuitConfig, &endpointId));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Create a circuit.
+ //
+
+ ACXCIRCUIT circuit;
+ RETURN_NTSTATUS_IF_FAILED(CreateCaptureCircuit(CircuitInit, circuitName, Device, &circuit));
+
+ AcpiReader * acpiReader = GetAcpiReaderDeviceContext(Parent);
+ RETURN_NTSTATUS_IF_FAILED(DetermineSpecialStreamDetailsFromVendorProperties(circuit, acpiReader, CircuitConfig->CircuitProperties));
+
+ ASSERT(circuit != NULL);
+ DSP_CIRCUIT_CONTEXT *circuitCtx;
+ circuitCtx = GetDspCircuitContext(circuit);
+ ASSERT(circuitCtx);
+
+ circuitCtx->EndpointId = endpointId;
+ circuitCtx->DataPortNumber = DataPortNumber;
+
+ //
+ // Post circuit creation initialization.
+ //
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Add two custom circuit elements. Note that driver doesn't need to
+ // perform this step if it doesn't want to expose any circuit elements.
+ //
+
+ //
+ // Create 1st custom circuit-element.
+ //
+ ACX_ELEMENT_CONFIG elementCfg;
+ ACX_ELEMENT_CONFIG_INIT(&elementCfg);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT);
+ attributes.ParentObject = circuit;
+
+ const int numElements = 3;
+ ACXELEMENT elements[numElements] = {0};
+
+ RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[0]));
+
+ ASSERT(elements[0] != NULL);
+ DSP_ELEMENT_CONTEXT *elementCtx;
+ elementCtx = GetDspElementContext(elements[0]);
+ ASSERT(elementCtx);
+ UNREFERENCED_PARAMETER(elementCtx);
+
+ //
+ // Create 2nd custom circuit-element.
+ //
+ ACX_ELEMENT_CONFIG_INIT(&elementCfg);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT);
+ attributes.ParentObject = circuit;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[1]));
+
+ ASSERT(elements[1] != NULL);
+ elementCtx = GetDspElementContext(elements[1]);
+ ASSERT(elementCtx);
+ UNREFERENCED_PARAMETER(elementCtx);
+
+ //
+ // Create 3rd circuit-element, keyword spotter
+ //
+ RETURN_NTSTATUS_IF_FAILED(DspC_CreateKeywordSpotterElement(Device, circuit, (ACXKEYWORDSPOTTER *) &elements[2]));
+ circuitCtx->KeywordSpotter = (ACXKEYWORDSPOTTER)elements[2];
+
+ //
+ // Add the circuit elements
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(circuit, elements, SIZEOF_ARRAY(elements)));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Allocate the formats this circuit supports.
+ //
+ // PCM:44100 channel:2 24in32
+ ACXDATAFORMAT formatPcm44100c2nomask;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm44100c2_24in32_nomask, circuit, Device, &formatPcm44100c2nomask));
+
+ // PCM:48000 channel:2 24in32 (Needed for SDCA class driver bring up)
+ // The No-Mask version matches what real drivers use for multi-channel capture
+ ACXDATAFORMAT formatPcm48000c2nomask;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm48000c2_24in32_nomask, circuit, Device, &formatPcm48000c2nomask));
+
+ // PCM:16000 channel:4 - this is used solely for the KeywordSpotterPin
+ // The No-Mask version matches what real drivers use for multi-channel capture
+ ACXDATAFORMAT formatPcm16000c4nomask;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm16000c4nomask, circuit, Device, &formatPcm16000c4nomask));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Create capture pin. AcxCircuit creates the other pin by default.
+ //
+ ACXPIN pin;
+ ACX_PIN_CALLBACKS pinCallbacks;
+
+ ACX_PIN_CALLBACKS_INIT(&pinCallbacks);
+ pinCallbacks.EvtAcxPinSetDataFormat = DspC_EvtAcxPinSetDataFormat;
+
+ RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSource,
+ circuit,
+ AcxPinCommunicationSink,
+ &KSCATEGORY_AUDIO,
+ &pinCallbacks,
+ DSPC_MAX_OUTPUT_SYSTEM_STREAMS,
+ false,
+ &pin));
+ ASSERT(pin != NULL);
+
+ DSP_PIN_CONTEXT* pinCtx;
+ pinCtx = GetDspPinContext(pin);
+ ASSERT(pinCtx);
+ pinCtx->CapturePinType = DspCapturePinTypeHost;
+
+ //
+ // Don't add any supported formats here, those will be added when this circuit
+ // connects to the downstream circuit
+ //
+
+ //
+ // Add capture pin, using default pin id (0)
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Create keyword streaming pin.
+ //
+ ACX_PIN_CALLBACKS_INIT(&pinCallbacks);
+ pinCallbacks.EvtAcxPinSetDataFormat = DspC_EvtAcxPinSetDataFormat;
+
+
+ pin = NULL;
+ RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSource,
+ circuit,
+ AcxPinCommunicationSink,
+ &KSNODETYPE_AUDIO_KEYWORDDETECTOR,
+ &pinCallbacks,
+ DSPC_MAX_OUTPUT_KEYWORDDETECTOR_STREAMS,
+ true,
+ &pin));
+
+ ASSERT(pin != NULL);
+ pinCtx = GetDspPinContext(pin);
+ ASSERT(pinCtx);
+ pinCtx->CapturePinType = DspCapturePinTypeKeyword;
+
+ //
+ // Add our supported formats to the raw mode for the circuit
+ //
+ ACXDATAFORMATLIST formatList = AcxPinGetRawDataFormatList(pin);
+ RETURN_NTSTATUS_IF_TRUE(NULL == formatList, STATUS_INSUFFICIENT_RESOURCES);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm16000c4nomask));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Create bridge pin.
+ //
+ ACX_PIN_CALLBACKS_INIT(&pinCallbacks);
+ pinCallbacks.EvtAcxPinConnected = DspC_EvtPinConnected;
+ pinCallbacks.EvtAcxPinDisconnected = DspC_EvtPinDisconnected;
+
+ pin = NULL;
+ RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSink,
+ circuit,
+ AcxPinCommunicationNone,
+ &KSCATEGORY_AUDIO,
+ &pinCallbacks,
+ 0, // max streams.
+ false,
+ &pin));
+
+ ASSERT(pin != NULL);
+ pinCtx = GetDspPinContext(pin);
+ ASSERT(pinCtx);
+ pinCtx->CapturePinType = DspCapturePinTypeBridge;
+
+ //
+ // Add a stream BRIDGE.
+ //
+
+ ACX_STREAM_BRIDGE_CONFIG streamCfg;
+ ACX_STREAM_BRIDGE_CONFIG_INIT(&streamCfg);
+ RETURN_NTSTATUS_IF_FAILED(CreateStreamBridge(streamCfg, circuit, pin, pinCtx, DataPortNumber, endpointId, PathDescriptors, false));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1));
+
+ RETURN_NTSTATUS_IF_FAILED(ConnectCaptureCircuitElements(3, elements, circuit));
+
+ //
+ // Store the circuit handle in the capture device context.
+ //
+ PDSP_CAPTURE_DEVICE_CONTEXT captureDevCtx = NULL;
+ captureDevCtx = GetCaptureDeviceContext(Device);
+ ASSERT(captureDevCtx);
+ captureDevCtx->Circuit = circuit;
+ captureDevCtx->FirstTimePrepareHardware = TRUE;
+
+ return status;
+}
+
+//
+// This callback is called when the Circuit bridge pin is connected to
+// bridge pin of another circuit.
+//
+// This will happen when the composite circuit is fully initialized.
+// From this point onwards the TargetCircuit can be used to send
+// KSPROPERTY requests
+//
+// params:
+// TargetCircuit - ACX wrapper for WDFIOTARGET for the connected circuit
+// TargetPinId - The pin on the connected circuit. This can be used to
+// send pin specific KSPROPERTY requests.
+//
+PAGED_CODE_SEG
+VOID
+DspC_EvtPinConnected (
+ _In_ ACXPIN Pin,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ ULONG TargetPinId
+ )
+{
+ PAGED_CODE();
+
+ DSP_PIN_CONTEXT *pinCtx;
+ pinCtx = GetDspPinContext(Pin);
+ pinCtx->TargetCircuit = TargetCircuit;
+ pinCtx->TargetPinId = TargetPinId;
+
+ // For this sample driver, we're only adding formats to the host pin that the downstream
+ // pin supports. For a real DSP driver, the AUDIO_SIGNALPROCESSINGMODE_RAW data format list
+ // would probably include all the downstream formats, but the _DEFAULT and possibly _SPEECH
+ // or _COMMUNICATIONS modes would contain different formats.
+ // As an example, a Microphone Array's _RAW mode formats should match the channel count of the
+ // number of microphones in the array, whereas the _DEFAULT mode formats would be the processed
+ // stream in Stereo.
+ NTSTATUS status;
+ ACXPIN hostPin = AcxCircuitGetPinById(AcxPinGetCircuit(Pin), DspCapturePinTypeHost);
+ status = ReplicateFormatsForPin(hostPin, TargetCircuit, TargetPinId);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"Failed to replicate downstream formats to host pin, %!STATUS!",
+ status);
+ }
+
+ // The ACX Framework will maintain the TargetCircuit until after it's called EvtPinDisconnect.
+}
+
+//
+// This callback is called when the Circuit bridge pin is disconnected
+// from the bridge pin of another circuit.
+//
+// This will happen when the composite circuit is deinitialized.
+// From this point onwards the TargetCircuit cannnot be used to send
+// KSPROPERTY requests.
+// TargetCircuit should only be used to access the attached context.
+//
+// params:
+// TargetCircuit - ACX wrapper for WDFIOTARGET for the connected circuit
+// TargetPinId - The pin on the connected circuit.
+//
+PAGED_CODE_SEG
+VOID
+DspC_EvtPinDisconnected (
+ _In_ ACXPIN Pin,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ ULONG TargetPinId
+ )
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(TargetPinId);
+ UNREFERENCED_PARAMETER(TargetCircuit);
+
+ DSP_PIN_CONTEXT *pinCtx;
+ pinCtx = GetDspPinContext(Pin);
+
+ if (pinCtx->TargetCircuit)
+ {
+ // After calling EvtPinDisconnected, the ACX framework will clean up
+ // the TargetCircuit.
+ pinCtx->TargetCircuit = NULL;
+ pinCtx->TargetPinId = (ULONG)(-1);
+ }
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtDevicePrepareHardware(
+ _In_ WDFDEVICE Device,
+ _In_ WDFCMRESLIST ResourceList,
+ _In_ WDFCMRESLIST ResourceListTranslated
+)
+/*++
+
+Routine Description:
+
+ In this callback, the driver does whatever is necessary to make the
+ hardware ready to use.
+
+Arguments:
+
+ Device - handle to a device
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DrvLogEnter(g_SDCAVDspLog);
+
+ UNREFERENCED_PARAMETER(ResourceList);
+ UNREFERENCED_PARAMETER(ResourceListTranslated);
+
+ PAGED_CODE();
+
+ PDSP_CAPTURE_DEVICE_CONTEXT devCtx;
+ devCtx = GetCaptureDeviceContext(Device);
+ ASSERT(devCtx != NULL);
+
+ if (!devCtx->FirstTimePrepareHardware)
+ {
+ //
+ // This is a rebalance. Validate the circuit resources and
+ // if needed, delete and re-create the circuit.
+ // The sample driver doens't use resources, thus the existing
+ // circuits are kept.
+ //
+ status = STATUS_SUCCESS;
+ return status;
+ }
+
+ //
+ // Set child's power policy.
+ //
+ RETURN_NTSTATUS_IF_FAILED(DspC_SetPowerPolicy(Device));
+
+ //
+ // Add circuit to child's list.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuit(Device, devCtx->Circuit));
+
+ //
+ // Keep track this is not the first time this callback was called.
+ //
+ devCtx->FirstTimePrepareHardware = FALSE;
+
+ DrvLogExit(g_SDCAVDspLog);
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtDeviceReleaseHardware(
+ _In_ WDFDEVICE Device,
+ _In_ WDFCMRESLIST ResourceListTranslated
+)
+/*++
+
+Routine Description:
+
+ In this callback, the driver releases the h/w resources allocated in the
+ prepare h/w callback.
+
+Arguments:
+
+ Device - handle to a device
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ DrvLogEnter(g_SDCAVDspLog);
+
+ UNREFERENCED_PARAMETER(Device);
+ UNREFERENCED_PARAMETER(ResourceListTranslated);
+
+ PAGED_CODE();
+
+ PDSP_CAPTURE_DEVICE_CONTEXT devCtx;
+ devCtx = GetCaptureDeviceContext(Device);
+ ASSERT(devCtx != NULL);
+
+ DrvLogExit(g_SDCAVDspLog);
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtDeviceSelfManagedIoInit(
+ _In_ WDFDEVICE Device
+)
+/*++
+
+Routine Description:
+
+ In this callback, the driver does one-time init of self-managed I/O data.
+
+Arguments:
+
+ Device - handle to a device
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ PAGED_CODE();
+
+ PDSP_CAPTURE_DEVICE_CONTEXT devCtx;
+ devCtx = GetCaptureDeviceContext(Device);
+ ASSERT(devCtx != NULL);
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg()
+VOID DspC_EvtDeviceContextCleanup(
+ _In_ WDFOBJECT WdfDevice
+)
+/*++
+
+Routine Description:
+
+ In this callback, it cleans up device context.
+
+Arguments:
+
+ WdfDevice - WDF device object
+
+Return Value:
+
+ NULL
+
+--*/
+{
+ WDFDEVICE device;
+ PDSP_CAPTURE_DEVICE_CONTEXT devCtx;
+
+ device = (WDFDEVICE)WdfDevice;
+ devCtx = GetCaptureDeviceContext(device);
+ ASSERT(devCtx != NULL);
+
+ // only clean up the circuit if it was
+ // successfully created, else it'll crash
+ if (devCtx->Circuit != NULL)
+ {
+ DspC_CircuitCleanup(devCtx->Circuit);
+ devCtx->Circuit = NULL;
+ }
+}
+
+#pragma code_seg()
+VOID
+DspC_EvtCircuitContextCleanup(
+ _In_ WDFOBJECT Circuit
+ )
+/*++
+
+Routine Description:
+
+ In this callback, it cleans up circuit context.
+
+Arguments:
+
+ WdfDevice - WDF device object
+
+Return Value:
+
+ NULL
+
+--*/
+{
+ PDSP_CIRCUIT_CONTEXT circuitCtx;
+
+ circuitCtx = GetDspCircuitContext(Circuit);
+ ASSERT(circuitCtx != NULL);
+
+ // clean up the path context information in case it wasn't cleaned up
+ // by pin disconnection.
+ circuitCtx->SpecialStreamAvailablePaths = 0;
+
+ for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++)
+ {
+ if (circuitCtx->SpecialStreamPathDescriptors[i])
+ {
+ ExFreePool(circuitCtx->SpecialStreamPathDescriptors[i]);
+ circuitCtx->SpecialStreamPathDescriptors[i] = nullptr;
+ }
+ }
+
+ for (ULONG i = (UINT)SpecialStreamTypeUltrasoundRender; i < (UINT)SpecialStreamType_Count; i++)
+ {
+ if (circuitCtx->SpecialStreamPathDescriptors2[i])
+ {
+ ExFreePool(circuitCtx->SpecialStreamPathDescriptors2[i]);
+ circuitCtx->SpecialStreamPathDescriptors2[i] = nullptr;
+ }
+ }
+
+ if (circuitCtx->SpecialStreamTargetCircuit)
+ {
+ WdfObjectDereferenceWithTag(circuitCtx->SpecialStreamTargetCircuit, (PVOID)DRIVER_TAG);
+ circuitCtx->SpecialStreamTargetCircuit = nullptr;
+ }
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Circuit Cleanup %p", Circuit);
+}
+
+#pragma code_seg()
+_Use_decl_annotations_
+NTSTATUS DspC_EvtCircuitPowerUp (
+ WDFDEVICE,
+ ACXCIRCUIT,
+ WDF_POWER_DEVICE_STATE
+)
+{
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+_Use_decl_annotations_
+NTSTATUS DspC_EvtCircuitPowerDown (
+ WDFDEVICE,
+ ACXCIRCUIT,
+ WDF_POWER_DEVICE_STATE
+)
+{
+ PAGED_CODE();
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtCircuitCompositeCircuitInitialize(
+ WDFDEVICE,
+ ACXCIRCUIT,
+ ACXOBJECTBAG
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS DspC_EvtCircuitCompositeInitialize(
+ WDFDEVICE,
+ ACXCIRCUIT,
+ ACXOBJECTBAG
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ return status;
+}
+
+PAGED_CODE_SEG
+VOID DspC_EvtCircuitRequestPreprocess(
+ _In_ ACXOBJECT Object,
+ _In_ ACXCONTEXT DriverContext,
+ _In_ WDFREQUEST Request
+)
+/*++
+
+Routine Description:
+
+ This function is an example of a preprocess routine.
+
+--*/
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(DriverContext);
+
+ ASSERT(Object != NULL);
+ ASSERT(DriverContext);
+ ASSERT(Request);
+
+ //
+ // Just give the request back to ACX.
+ //
+ (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request);
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspC_EvtCircuitCreateStream(
+ _In_ WDFDEVICE Device,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACXPIN Pin,
+ _In_ PACXSTREAM_INIT StreamInit,
+ _In_ ACXDATAFORMAT StreamFormat,
+ _In_ const GUID * SignalProcessingMode,
+ _In_ ACXOBJECTBAG VarArguments
+)
+/*++
+
+Routine Description:
+
+ This routine create a stream for the specified circuit.
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(SignalProcessingMode);
+ UNREFERENCED_PARAMETER(VarArguments);
+
+ DrvLogEnter(g_SDCAVDspLog);
+
+ NTSTATUS status = STATUS_SUCCESS;
+ BOOLEAN kwsStream = FALSE;
+
+ DSP_PIN_CONTEXT * pinCtx;
+ pinCtx = GetDspPinContext(Pin);
+ ASSERT(pinCtx != NULL);
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXPIN_01
+ {
+ ASSERT(pinCtx->CurrentStreamsCount != (ULONG)-1);
+ RETURN_NTSTATUS_IF_TRUE_MSG(
+ pinCtx->CurrentStreamsCount >= pinCtx->MaxStreams,
+ STATUS_INSUFFICIENT_RESOURCES,
+ L"ACXCIRCUIT %p ACXPIN %p cannot create another ACXSTREAM, max count is %d, %!STATUS!",
+ Circuit, Pin, pinCtx->MaxStreams, status);
+ }
+#endif
+
+ //
+ // Request a Vendor-Specific property from the Controller
+ //
+ Dsp_SendVendorSpecificProperties(
+ Device,
+ Circuit,
+ FALSE);
+
+ //
+ // Set circuit-callbacks.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRequestPreprocessCallback(
+ StreamInit,
+ DspC_EvtStreamRequestPreprocess,
+ (ACXCONTEXT)AcxRequestTypeAny, // dbg only
+ AcxRequestTypeAny,
+ NULL,
+ AcxItemIdNone));
+
+ /*
+ //
+ // Add properties, events and methods.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignProperties(StreamInit,
+ StreamProperties,
+ StreamPropertiesCount));
+ */
+
+ //
+ // Init streaming callbacks.
+ //
+ ACX_STREAM_CALLBACKS streamCallbacks;
+ ACX_STREAM_CALLBACKS_INIT(&streamCallbacks);
+ streamCallbacks.EvtAcxStreamPrepareHardware = Dsp_EvtStreamPrepareHardware;
+ streamCallbacks.EvtAcxStreamReleaseHardware = Dsp_EvtStreamReleaseHardware;
+ streamCallbacks.EvtAcxStreamRun = Dsp_EvtStreamRun;
+ streamCallbacks.EvtAcxStreamPause = Dsp_EvtStreamPause;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxStreamCallbacks(StreamInit, &streamCallbacks));
+
+ //
+ // Init RT streaming callbacks.
+ //
+ ACX_RT_STREAM_CALLBACKS rtCallbacks;
+ ACX_RT_STREAM_CALLBACKS_INIT(&rtCallbacks);
+ rtCallbacks.EvtAcxStreamGetHwLatency = Dsp_EvtStreamGetHwLatency;
+ rtCallbacks.EvtAcxStreamAllocateRtPackets = Dsp_EvtStreamAllocateRtPackets;
+ rtCallbacks.EvtAcxStreamFreeRtPackets = Dsp_EvtStreamFreeRtPackets;
+ rtCallbacks.EvtAcxStreamGetCapturePacket = DspC_EvtStreamGetCapturePacket;
+ rtCallbacks.EvtAcxStreamGetCurrentPacket = Dsp_EvtStreamGetCurrentPacket;
+ rtCallbacks.EvtAcxStreamGetPresentationPosition = Dsp_EvtStreamGetPresentationPosition;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRtStreamCallbacks(StreamInit, &rtCallbacks));
+
+ //
+ // Buffer notifications are supported.
+ //
+ AcxStreamInitSetAcxRtStreamSupportsNotifications(StreamInit);
+
+ //
+ // Create the stream.
+ //
+ WDF_OBJECT_ATTRIBUTES attributes;
+ ACXSTREAM stream;
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_STREAM_CONTEXT);
+ attributes.EvtDestroyCallback = Dsp_EvtStreamContextDestroy;
+ attributes.EvtCleanupCallback = Dsp_EvtStreamContextCleanup;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxRtStreamCreate(Device, Circuit, &attributes, &StreamInit, &stream));
+
+ DSP_STREAM_CONTEXT* streamCtx;
+ streamCtx = GetDspStreamContext(stream);
+ ASSERT(streamCtx);
+
+ streamCtx->CapturePinType = pinCtx->CapturePinType;
+
+ DSP_CIRCUIT_CONTEXT * circuitCtx;
+ circuitCtx = GetDspCircuitContext(Circuit);
+ ASSERT(circuitCtx != NULL);
+
+ CCaptureStreamEngine *streamEngine = NULL;
+
+ if (pinCtx->CapturePinType == DspCapturePinTypeKeyword)
+ {
+ PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx;
+ keywordSpotterCtx = GetDspKeywordSpotterContext(circuitCtx->KeywordSpotter);
+ ASSERT(keywordSpotterCtx);
+
+ streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CBufferedCaptureStreamEngine(stream, StreamFormat, (CKeywordDetector *) keywordSpotterCtx->KeywordDetector);
+ kwsStream = TRUE;
+ }
+ else
+ {
+ streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CCaptureStreamEngine(stream, StreamFormat);
+ }
+
+ RETURN_NTSTATUS_IF_TRUE(NULL == streamEngine, STATUS_INSUFFICIENT_RESOURCES);
+
+ streamCtx->StreamEngine = (PVOID)streamEngine;
+ streamEngine = NULL;
+
+ //
+ // Post stream creation initialization.
+ //
+
+ //
+ // Create 1st custom stream-elements.
+ //
+ ACX_ELEMENT_CONFIG elementCfg;
+ ACX_ELEMENT_CONFIG_INIT(&elementCfg);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT);
+ attributes.ParentObject = stream;
+
+ ACXELEMENT elements[2] = { 0 };
+ RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[0]));
+
+ ASSERT(elements[0] != NULL);
+ DSP_ELEMENT_CONTEXT *elementCtx;
+ elementCtx = GetDspElementContext(elements[0]);
+ ASSERT(elementCtx);
+ UNREFERENCED_PARAMETER(elementCtx);
+
+ //
+ // Create 2nd custom stream-elements.
+ //
+ ACX_ELEMENT_CONFIG_INIT(&elementCfg);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT);
+ attributes.ParentObject = stream;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[1]));
+
+ ASSERT(elements[1] != NULL);
+ elementCtx = GetDspElementContext(elements[1]);
+ ASSERT(elementCtx);
+ UNREFERENCED_PARAMETER(elementCtx);
+
+ //
+ // Add stream elements
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, elements, SIZEOF_ARRAY(elements)));
+
+ ACXPIN bridgePin = AcxCircuitGetPinById(Circuit, (ULONG)DspCapturePinTypeBridge);
+ RETURN_NTSTATUS_IF_TRUE(bridgePin == NULL, STATUS_UNSUCCESSFUL);
+ PDSP_PIN_CONTEXT bridgePinCtx = GetDspPinContext(bridgePin);
+ if (!kwsStream)
+ {
+ // KWS Streams are handled in the DSP. Only add non-KWS streams to the StreamBridge, which
+ // will forward the stream creation to downlevel circuits (i.e. Xu and Codec drivers)
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamBridgeAddStream(bridgePinCtx->HostStreamBridge, stream));
+ }
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXPIN_01
+ {
+ ASSERT(pinCtx->CurrentStreamsCount != (ULONG)-1);
+ InterlockedIncrement(PLONG(&pinCtx->CurrentStreamsCount));
+ streamCtx->StreamIsCounted = TRUE;
+ }
+#endif
+
+ streamCtx->Pin = Pin;
+ WdfObjectReferenceWithTag(Pin, (PVOID)DRIVER_TAG);
+
+ return status;
+}
+
+PAGED_CODE_SEG
+VOID
+DspC_EvtStreamRequestPreprocess(
+ _In_ ACXOBJECT Object,
+ _In_ ACXCONTEXT DriverContext,
+ _In_ WDFREQUEST Request
+)
+/*++
+
+Routine Description:
+
+ This function is an example of a preprocess routine.
+
+--*/
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_NOT_SUPPORTED;
+ ACX_REQUEST_PARAMETERS params;
+ ULONG_PTR outDataCb = 0;
+ PDSP_STREAM_CONTEXT streamCtx;
+
+ UNREFERENCED_PARAMETER(DriverContext);
+
+ ASSERT(Object != NULL);
+ ASSERT(DriverContext);
+ ASSERT(Request);
+
+ ACX_REQUEST_PARAMETERS_INIT(&params);
+ AcxRequestGetParameters(Request, &params);
+
+ streamCtx = GetDspStreamContext(Object);
+ if (streamCtx && streamCtx->CapturePinType == DspCapturePinTypeKeyword)
+ {
+ if (IsEqualGUID(params.Parameters.Property.Set, KSPROPSETID_RtAudio) &&
+ params.Parameters.Property.Id == KSPROPERTY_RTAUDIO_PACKETVREGISTER)
+ {
+ status = STATUS_NOT_SUPPORTED;
+ outDataCb = 0;
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"DSP Capture Stream for Keyword Overriding PACKETVREGISTER request, %!STATUS!",
+ status);
+
+ WdfRequestCompleteWithInformation(Request, status, outDataCb);
+ return;
+ }
+ }
+
+ //
+ // Just give the request back to ACX.
+ //
+ (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request);
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspC_SetPowerPolicy(
+ _In_ WDFDEVICE Device
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings;
+ //WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings;
+
+ PAGED_CODE();
+
+ //
+ // Init the idle policy structure.
+ //
+ //WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCanWakeFromS0);
+ WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0);
+ idleSettings.IdleTimeout = 10000; // 10-sec
+
+ status = WdfDeviceAssignS0IdleSettings(Device, &idleSettings);
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspC_CircuitCleanup(
+ _In_ ACXCIRCUIT Circuit
+ )
+{
+ PDSP_CIRCUIT_CONTEXT circuitCtx;
+ PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx;
+ CKeywordDetector * keywordDetector = NULL;
+
+ PAGED_CODE();
+
+ // Remove the static capture circuit
+ circuitCtx = GetDspCircuitContext(Circuit);
+ ASSERT(circuitCtx != NULL);
+
+ keywordSpotterCtx = GetDspKeywordSpotterContext(circuitCtx->KeywordSpotter);
+ ASSERT(keywordSpotterCtx != NULL);
+
+ keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector;
+ keywordSpotterCtx->KeywordDetector = NULL;
+ delete keywordDetector;
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspC_EvtAcxPinSetDataFormat(
+ _In_ ACXPIN Pin,
+ _In_ ACXDATAFORMAT DataFormat
+)
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(Pin);
+ UNREFERENCED_PARAMETER(DataFormat);
+
+
+ return STATUS_NOT_SUPPORTED;
+}
+
+#pragma code_seg()
+VOID
+DspC_EvtPinContextCleanup(
+ _In_ WDFOBJECT WdfPin
+)
+/*++
+
+Routine Description:
+
+ In this callback, it cleans up pin context.
+
+Arguments:
+
+ WdfDevice - WDF device object
+
+Return Value:
+
+ NULL
+
+--*/
+{
+ DSP_PIN_CONTEXT *pinCtx;
+ pinCtx = GetDspPinContext(WdfPin);
+
+ if (pinCtx->TargetCircuit)
+ {
+ WdfObjectDereferenceWithTag(pinCtx->TargetCircuit, (PVOID)DRIVER_TAG);
+
+ pinCtx->TargetCircuit = NULL;
+ pinCtx->TargetPinId = (ULONG)(-1);
+ }
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspC_EvtStreamGetCapturePacket(
+ _In_ ACXSTREAM Stream,
+ _Out_ ULONG* LastCapturePacket,
+ _Out_ ULONGLONG* QPCPacketStart,
+ _Out_ BOOLEAN* MoreData
+)
+{
+ PDSP_STREAM_CONTEXT ctx;
+ CCaptureStreamEngine* streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = static_cast<CCaptureStreamEngine*>(ctx->StreamEngine);
+
+ return streamEngine->GetCapturePacket(LastCapturePacket, QPCPacketStart, MoreData);
+}
+
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/circuitstream.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/circuitstream.cpp
new file mode 100644
index 00000000..d7d500c2
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/circuitstream.cpp
@@ -0,0 +1,820 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ circuitstream.cpp
+
+Abstract:
+
+ Circuit Stream callbacks
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include <devguid.h>
+#include "stdunk.h"
+#include <ks.h>
+#include <mmsystem.h>
+#include <ksmedia.h>
+#include "streamengine.h"
+
+#pragma code_seg()
+VOID
+Dsp_EvtStreamContextDestroy(
+ _In_ WDFOBJECT Object
+)
+{
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ ctx = GetDspStreamContext((ACXSTREAM)Object);
+
+ streamEngine = (CStreamEngine*)ctx->StreamEngine;
+ ctx->StreamEngine = NULL;
+ delete streamEngine;
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_EvtStreamContextCleanup(
+ _In_ WDFOBJECT Object
+)
+{
+ PDSP_STREAM_CONTEXT streamCtx = GetDspStreamContext((ACXSTREAM)Object);
+
+ PAGED_CODE();
+
+ if (streamCtx->Pin != NULL)
+ {
+#ifdef ACX_WORKAROUND_ACXPIN_01
+
+ PDSP_PIN_CONTEXT pinCtx = GetDspPinContext(streamCtx->Pin);
+
+ if (streamCtx->StreamIsCounted)
+ {
+ ASSERT(pinCtx->CurrentStreamsCount > 0);
+ InterlockedDecrement(PLONG(&pinCtx->CurrentStreamsCount));
+ streamCtx->StreamIsCounted = FALSE;
+ }
+#endif // ACX_WORKAROUND_ACXPIN_01
+
+ WdfObjectDereferenceWithTag(streamCtx->Pin, (PVOID)DRIVER_TAG);
+ streamCtx->Pin = NULL;
+ }
+
+ if (streamCtx->SpecialStreamTargetCircuit)
+ {
+ WdfObjectDereferenceWithTag(streamCtx->SpecialStreamTargetCircuit, (PVOID)DRIVER_TAG);
+ streamCtx->SpecialStreamTargetCircuit = nullptr;
+ }
+}
+
+#ifdef ACX_WORKAROUND_ACXPIN_01
+PAGED_CODE_SEG
+VOID
+Dsp_EvtStreamGetStreamCountRequestPreprocess(
+ _In_ ACXOBJECT Object,
+ _In_ ACXCONTEXT DriverContext,
+ _In_ WDFREQUEST Request
+)
+/*++
+
+Routine Description:
+
+ This function is a preprocess routine.
+
+--*/
+{
+ NTSTATUS status = STATUS_NOT_SUPPORTED;
+ ACXCIRCUIT circuit = (ACXCIRCUIT)Object;
+ ULONG_PTR outDataCb = 0;
+ ACX_REQUEST_PARAMETERS params;
+
+ UNREFERENCED_PARAMETER(DriverContext);
+
+ PAGED_CODE();
+
+ ACX_REQUEST_PARAMETERS_INIT(&params);
+ AcxRequestGetParameters(Request, &params);
+
+ //
+ // Make sure this is a pin property request.
+ //
+ if ((params.Type != AcxRequestTypeProperty) ||
+ (params.Parameters.Property.ItemType != AcxItemTypePin))
+ {
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ goto exit;
+ }
+
+ //
+ // Handle only the 'get' verb.
+ //
+ if (params.Parameters.Property.Verb == AcxPropertyVerbGet)
+ {
+ ACXPIN pin = NULL;
+ KSPIN_CINSTANCES * value = NULL;
+ ULONG valueCb = 0;
+ ULONG minSize = sizeof(KSPIN_CINSTANCES);
+
+ value = (KSPIN_CINSTANCES*)params.Parameters.Property.Value;
+ valueCb = params.Parameters.Property.ValueCb;
+
+ //
+ // Get the associated pin object.
+ //
+ pin = AcxCircuitGetPinById(circuit, params.Parameters.Property.ItemId);
+ if (pin == NULL)
+ {
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ goto exit;
+ }
+
+ if (valueCb == 0)
+ {
+ outDataCb = minSize;
+ status = STATUS_BUFFER_OVERFLOW;
+ goto exit;
+ }
+ else if (valueCb < minSize)
+ {
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto exit;
+ }
+ else
+ {
+ PDSP_PIN_CONTEXT pinCtx = GetDspPinContext(pin);
+ value->PossibleCount = pinCtx->MaxStreams;
+ value->CurrentCount = pinCtx->CurrentStreamsCount; // Aligned dword reads are atomic.
+ outDataCb = minSize;
+ }
+ }
+ else
+ {
+ //
+ // Just give it back to ACX. After this call the request is gone.
+ //
+ (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request);
+ Request = NULL;
+ goto exit;
+ }
+
+ status = STATUS_SUCCESS;
+
+exit:
+ if (Request != NULL)
+ {
+ WdfRequestCompleteWithInformation(Request, status, outDataCb);
+ }
+}
+#endif // ACX_WORKAROUND_ACXPIN_01
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXPIN_02
+PAGED_CODE_SEG
+VOID
+Dsp_EvtStreamProposeDataFormatRequestPreprocess(
+ _In_ ACXOBJECT Object,
+ _In_ ACXCONTEXT DriverContext,
+ _In_ WDFREQUEST Request
+)
+/*++
+
+Routine Description:
+
+ This function is a preprocess routine.
+
+--*/
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(DriverContext);
+
+ {
+ ACXCIRCUIT circuit = (ACXCIRCUIT)Object;
+ ACXPIN pin = NULL;
+ PDSP_PIN_CONTEXT pinCtx = NULL;
+
+ ACX_REQUEST_PARAMETERS params;
+
+ ACX_REQUEST_PARAMETERS_INIT(&params);
+ AcxRequestGetParameters(Request, &params);
+
+ if ((params.Type != AcxRequestTypeProperty) ||
+ (params.Parameters.Property.ItemType != AcxItemTypePin) ||
+ (params.Parameters.Property.Verb != AcxPropertyVerbSet))
+ {
+ goto forward_request;
+ }
+
+ //
+ // Get the associated pin object.
+ //
+ pin = AcxCircuitGetPinById(circuit, params.Parameters.Property.ItemId);
+ if (pin == NULL)
+ {
+ goto forward_request;
+ }
+
+ //
+ // Check if this is the offload pin.
+ //
+ pinCtx = GetDspPinContext(pin);
+ if (!pinCtx || (pinCtx->PinType != DspPinTypeOffload))
+ {
+ goto forward_request;
+ }
+
+ //
+ // This is an offload pin, check # of streams.
+ //
+ if (pinCtx->CurrentStreamsCount >= pinCtx->MaxStreams)
+ {
+ // Cannot create any more streams, error out.
+ WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES);
+ return;
+ }
+ }
+
+ //
+ // Just give it back to ACX. After this call the request is gone.
+ //
+forward_request:
+ (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request);
+}
+#endif // ACX_WORKAROUND_ACXPIN_02
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtStreamGetHwLatency(
+ _In_ ACXSTREAM Stream,
+ _Out_ ULONG * FifoSize,
+ _Out_ ULONG * Delay
+)
+{
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = (CStreamEngine*)ctx->StreamEngine;
+
+ return streamEngine->GetHWLatency(FifoSize, Delay);
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtStreamAllocateRtPackets(
+ _In_ ACXSTREAM Stream,
+ _In_ ULONG PacketCount,
+ _In_ ULONG PacketSize,
+ _Out_ PACX_RTPACKET *Packets
+)
+{
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = (CStreamEngine*)ctx->StreamEngine;
+
+ return streamEngine->AllocateRtPackets(PacketCount, PacketSize, Packets);
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_EvtStreamFreeRtPackets(
+ _In_ ACXSTREAM Stream,
+ _In_ PACX_RTPACKET Packets,
+ _In_ ULONG PacketCount
+)
+{
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = (CStreamEngine*)ctx->StreamEngine;
+
+ return streamEngine->FreeRtPackets(Packets, PacketCount);
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_PrepareSpecialStreamForStream(
+ _In_ ACXSTREAM Stream,
+ _In_ SDCA_SPECIALSTREAM_TYPE SpecialStreamType,
+ _In_ ULONG FunctionBitMask
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ SDCA_PATH specialStreamPath = SdcaPathFromSpecialStreamType(SpecialStreamType);
+ PDSP_STREAM_CONTEXT ctx = GetDspStreamContext(Stream);
+ ACXCIRCUIT circuit = AcxPinGetCircuit(ctx->Pin);
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit);
+ PSDCA_PATH_DESCRIPTORS pathDescriptors = nullptr;
+ BOOLEAN activeStreamCountIncremented = FALSE;
+
+ PAGED_CODE();
+
+ if (circuitCtx->SpecialStreamAvailablePaths & specialStreamPath &&
+ !ctx->SpecialStreamInUse[SpecialStreamType])
+ {
+ ULONG streamCount = InterlockedIncrement(PLONG(&(circuitCtx->SpecialStreamActive[SpecialStreamType])));
+ activeStreamCountIncremented = TRUE;
+
+ if (1 == streamCount)
+ {
+ // TODO: The above ensures that the global special stream usage counts are protected, however if a special stream
+ // were destroyed at near the same time as another one created, then there could be a timing issue between the
+ // timing of this call to create the path and the timing of the ReleaseHardware call destroying the path.
+ // i.e. ReleaseHardware performs an interlocked decrement to 0 and then a context switch. PrepareHardware runs and does an interlocked increment
+ // back to 1, and performs the CreatePath call as it appears to be the first and the path is not created.
+ // Then, ReleaseHardware resumes and calls DestroyPath.
+ // So, can a PrepareHardware and a ReleaseHardware for two different streams on the same pin, happen at the same time?
+
+ // Sample driver uses audio composition data to determine if it is going to use pathdescriptor2 or pathdescriptor
+ // to prepare special stream. Audio composition will also provide the entire pathdescriptor2 to be used.
+ if (circuitCtx->SpecialStreamPathDescriptors2[SpecialStreamType])
+ {
+ status = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ ctx->SpecialStreamTargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_CREATE_PATH2,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ circuitCtx->SpecialStreamPathDescriptors2[SpecialStreamType],
+ circuitCtx->SpecialStreamPathDescriptors2[SpecialStreamType]->Size,
+ nullptr);
+ if (!NT_SUCCESS(status))
+ {
+ goto exit;
+ }
+ }
+ else
+ {
+ // for simplicity, we take a copy of the entire path descriptors returned from downstream, and then adjust that copy
+ // to have the requested data port & format, leaving the remaining, if there are any, unused.
+ pathDescriptors = (PSDCA_PATH_DESCRIPTORS)ExAllocatePool2(POOL_FLAG_NON_PAGED, circuitCtx->SpecialStreamPathDescriptors[SpecialStreamType]->Size, DRIVER_TAG);
+ if (pathDescriptors == nullptr)
+ {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto exit;
+ }
+ RtlCopyMemory(pathDescriptors, circuitCtx->SpecialStreamPathDescriptors[SpecialStreamType], circuitCtx->SpecialStreamPathDescriptors[SpecialStreamType]->Size);
+
+ // walk the descriptors and adjust each entry to have 1 format, preferred one if available,
+ // and a single data port. The remaining entries beyond the first are there and accounted for
+ // by the size, but are unused.
+ PSDCA_PATH_DESCRIPTOR currentDescriptor = (PSDCA_PATH_DESCRIPTOR)(pathDescriptors + 1);
+ // In some cases we will only use some of the functions; targetDescriptor will receive the next descriptor if we skip any
+ PSDCA_PATH_DESCRIPTOR targetDescriptor = currentDescriptor;
+ ULONG descriptorCount = 0;
+ for (ULONG j = 0; j < pathDescriptors->DescriptorCount; j++)
+ {
+ PSDCA_PATH_DESCRIPTOR nextDescriptor = (PSDCA_PATH_DESCRIPTOR)(((BYTE*)currentDescriptor) + currentDescriptor->Size);
+
+ if (((1 << currentDescriptor->FunctionInformationId) & FunctionBitMask) == 0)
+ {
+ // This audio function isn't included in what should be started
+ currentDescriptor = nextDescriptor;
+ continue;
+ }
+
+ if (currentDescriptor != targetDescriptor)
+ {
+ RtlCopyMemory(targetDescriptor, currentDescriptor, currentDescriptor->Size);
+ }
+
+ PSDCA_PATH_DESCRIPTOR nextTargetDescriptor = (PSDCA_PATH_DESCRIPTOR)(((BYTE*)targetDescriptor) + targetDescriptor->Size);
+
+ for (ULONG i = 0; i < targetDescriptor->FormatCount; i++)
+ {
+ if (48000 == targetDescriptor->Formats[i].Format.nSamplesPerSec)
+ {
+ RtlCopyMemory(&(targetDescriptor->Formats[0]), &(targetDescriptor->Formats[i]), sizeof(targetDescriptor->Formats[0]));
+ break;
+ }
+ }
+
+ targetDescriptor->FormatCount = min(targetDescriptor->FormatCount, 1);
+ targetDescriptor->DataPortCount = min(targetDescriptor->DataPortCount, 1);
+
+ targetDescriptor = nextTargetDescriptor;
+ currentDescriptor = nextDescriptor;
+
+ ++descriptorCount;
+ }
+
+ if (descriptorCount == 0)
+ {
+ // No target functions were chosen.
+ status = STATUS_INVALID_PARAMETER;
+ goto exit;
+ }
+
+ pathDescriptors->DescriptorCount = descriptorCount;
+
+ // A real DSP driver would choose a suitable EndpointID. This is a placeholder.
+ pathDescriptors->EndpointId = 0xaa;
+
+ status = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ ctx->SpecialStreamTargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_CREATE_PATH,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ pathDescriptors, pathDescriptors->Size,
+ nullptr);
+ if (!NT_SUCCESS(status))
+ {
+ goto exit;
+ }
+ }
+ }
+
+ // If we succeeded in creating the path, set the tracking variable for the stream type
+ ctx->SpecialStreamInUse[SpecialStreamType] = TRUE;
+ }
+
+exit:
+ if (!NT_SUCCESS(status) && activeStreamCountIncremented)
+ {
+ // if we failed to create it, this call is going to fail, undo the circuit context tracking
+ InterlockedDecrement(PLONG(&(circuitCtx->SpecialStreamActive[SpecialStreamType])));
+ }
+
+ if (pathDescriptors)
+ {
+ ExFreePool(pathDescriptors);
+ pathDescriptors = nullptr;
+ }
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_ReleaseSpecialStreamsForStream(
+ _In_ ACXSTREAM Stream
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSP_STREAM_CONTEXT ctx = GetDspStreamContext(Stream);
+ ACXCIRCUIT circuit = AcxPinGetCircuit(ctx->Pin);
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit);
+
+ PAGED_CODE();
+
+ for (ULONG streamType = 0; streamType < ARRAYSIZE(ctx->SpecialStreamInUse); ++streamType)
+ {
+ if (!ctx->SpecialStreamInUse[streamType])
+ {
+ continue;
+ }
+
+ // As the special stream hardware is potentially shared across multiple streams,
+ // special stream state is tracked in the circuit context.
+ // Decrement shared circuit context tracking to indicate that this stream is no longer using this special stream path
+ ULONG streamCount = InterlockedDecrement(PLONG(&(circuitCtx->SpecialStreamActive[streamType])));
+
+ // if this was the last user of it, destroy the special stream path
+ if (0 == streamCount)
+ {
+ SDCA_PATH path = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE)streamType);
+ NTSTATUS sendStatus;
+
+ sendStatus = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ ctx->SpecialStreamTargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_DESTROY_PATH,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ &path, sizeof(path),
+ nullptr);
+
+ status = !NT_SUCCESS(status) ? status : sendStatus;
+ }
+
+ // if the path has been destroyed, it also cannot be running,
+ // so update the special stream state for both.
+ ctx->SpecialStreamInUse[streamType] = FALSE;
+ ctx->SpecialStreamRunning[streamType] = FALSE;
+ }
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtStreamPrepareHardware(
+ _In_ ACXSTREAM Stream
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = (CStreamEngine*)ctx->StreamEngine;
+
+ // prepare the stream engine hardware
+ status = streamEngine->PrepareHardware();
+
+ // For a host or offload pin, start the Sense stream
+ // if it isn't already running
+ if (NT_SUCCESS(status) &&
+ (DspPinTypeHost == ctx->PinType || DspPinTypeOffload == ctx->PinType))
+ {
+ status = Dsp_PrepareSpecialStreamForStream(Stream, SpecialStreamTypeIvSense);
+ }
+
+ // If this is loopback, we may be able to use reference
+ // stream hardware, check
+ if (NT_SUCCESS(status) &&
+ DspPinTypeLoopback == ctx->PinType)
+ {
+ // for sample purposes, we're using the same stream engine for loopback with
+ // reference stream as without. The only difference is whether the special stream
+ // properties are being used to create, destroy, start, and stop the reference stream
+ // hardware when the loopback stream is used.
+
+ status = Dsp_PrepareSpecialStreamForStream(Stream, SpecialStreamTypeReferenceStream);
+ }
+
+ if (!NT_SUCCESS(status))
+ {
+ (void)Dsp_ReleaseSpecialStreamsForStream(Stream);
+ }
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtStreamReleaseHardware(
+ _In_ ACXSTREAM Stream
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = (CStreamEngine*)ctx->StreamEngine;
+
+ status = Dsp_ReleaseSpecialStreamsForStream(Stream);
+
+ NTSTATUS engineStatus = streamEngine->ReleaseHardware();
+
+ return NT_SUCCESS(engineStatus)?status:engineStatus;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_StopSpecialStreamsForStream(
+ _In_ ACXSTREAM Stream
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSP_STREAM_CONTEXT ctx = GetDspStreamContext(Stream);
+ ACXCIRCUIT circuit = AcxPinGetCircuit(ctx->Pin);
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit);
+
+ PAGED_CODE();
+
+ for (ULONG streamType = 0; streamType < ARRAYSIZE(ctx->SpecialStreamInUse); ++streamType)
+ {
+ if (ctx->SpecialStreamRunning[streamType])
+ {
+ // As the special stream hardware is potentially shared across multiple streams,
+ // special stream state is tracked in the circuit context.
+ // Decrement shared circuit context tracking to indicate that this stream is no longer running
+ ULONG streamCount = InterlockedDecrement(PLONG(&(circuitCtx->SpecialStreamRunning[streamType])));
+
+ // if this was the last stream using it, stop the path
+ if (0 == streamCount)
+ {
+ SDCA_PATH path = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE)streamType);
+ NTSTATUS sendStatus;
+
+ sendStatus = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ ctx->SpecialStreamTargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_STOP_PATH,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ &path, sizeof(path),
+ nullptr);
+
+ status = !NT_SUCCESS(status) ? status : sendStatus;
+ }
+
+ // update special stream state
+ ctx->SpecialStreamRunning[streamType] = FALSE;
+ }
+ }
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_StartSpecialStreamsForStream(
+ _In_ ACXSTREAM Stream
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSP_STREAM_CONTEXT ctx = GetDspStreamContext(Stream);
+ ACXCIRCUIT circuit = AcxPinGetCircuit(ctx->Pin);
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit);
+
+ PAGED_CODE();
+
+ for (ULONG streamType = 0; streamType < ARRAYSIZE(ctx->SpecialStreamInUse); ++streamType)
+ {
+ if (ctx->SpecialStreamInUse[streamType] &&
+ !ctx->SpecialStreamRunning[streamType])
+ {
+ // As the special stream hardware is potentially shared across multiple streams,
+ // special stream state is tracked in the circuit context.
+ // Increment shared circuit context tracking to indicate that this stream is running
+ ULONG streamCount = InterlockedIncrement(PLONG(&(circuitCtx->SpecialStreamRunning[streamType])));
+
+ // if we are the first to use it, start the path
+ if (1 == streamCount)
+ {
+ SDCA_PATH path = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE)streamType);
+ status = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ ctx->SpecialStreamTargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_START_PATH,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ &path, sizeof(path),
+ nullptr);
+ }
+
+ // if we succeeded in starting the path, update set our tracking
+ if (NT_SUCCESS(status))
+ {
+ ctx->SpecialStreamRunning[streamType] = TRUE;
+ }
+ else
+ {
+ // if we failed to set the state, so clear the state tracking
+ InterlockedDecrement(PLONG(&(circuitCtx->SpecialStreamRunning[streamType])));
+
+ // If we failed, exit early so we can clean up
+ break;
+ }
+ }
+ }
+
+ if (!NT_SUCCESS(status))
+ {
+ // If this stream has more than one special stream, it's possible we failed after starting
+ // one or more special streams. As such, make sure all streams are stopped.
+ (void)Dsp_StopSpecialStreamsForStream(Stream);
+ }
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtStreamRun(
+ _In_ ACXSTREAM Stream
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = (CStreamEngine*)ctx->StreamEngine;
+
+ status = streamEngine->Run();
+
+ // if we're using reference stream and aren't already running,
+ // set our state to running.
+ if (NT_SUCCESS(status))
+ {
+ status = Dsp_StartSpecialStreamsForStream(Stream);
+ }
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtStreamPause(
+ _In_ ACXSTREAM Stream
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = (CStreamEngine*)ctx->StreamEngine;
+
+ // if any special streams are running (which can only happen if they're being used)
+ // and we're pausing, update tracking
+ status = Dsp_StopSpecialStreamsForStream(Stream);
+
+ NTSTATUS engineStatus = streamEngine->Pause();
+
+ return NT_SUCCESS(engineStatus)?status:engineStatus;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtStreamAssignDrmContentId(
+ _In_ ACXSTREAM Stream,
+ _In_ ULONG DrmContentId,
+ _In_ PACXDRMRIGHTS DrmRights
+)
+{
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = (CStreamEngine*)ctx->StreamEngine;
+
+ return streamEngine->AssignDrmContentId(DrmContentId, DrmRights);
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtStreamGetCurrentPacket(
+ _In_ ACXSTREAM Stream,
+ _Out_ PULONG CurrentPacket
+)
+{
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = static_cast<CStreamEngine*>(ctx->StreamEngine);
+
+ return streamEngine->GetCurrentPacket(CurrentPacket);
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtStreamGetPresentationPosition(
+ _In_ ACXSTREAM Stream,
+ _Out_ PULONGLONG PositionInBlocks,
+ _Out_ PULONGLONG QPCPosition
+)
+{
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = static_cast<CStreamEngine*>(ctx->StreamEngine);
+
+ return streamEngine->GetPresentationPosition(PositionInBlocks, QPCPosition);
+}
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/device.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/device.cpp
new file mode 100644
index 00000000..36c0e95a
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/device.cpp
@@ -0,0 +1,1631 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ Device.cpp
+
+Abstract:
+
+ Plug and Play module. This file contains routines to handle pnp requests.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include "streamengine.h"
+#include "AcpiReader.h"
+#include <devguid.h>
+
+
+#ifndef __INTELLISENSE__
+#include "device.tmh"
+#endif
+
+using namespace ACPIREADER;
+
+UNICODE_STRING g_RegistryPath = {0}; // This is used to store the registry settings path for the driver
+
+DEFINE_GUID(DSP_CIRCUIT_RENDER_GUID,
+0x9e4f4968, 0x4dd0, 0x4aaa, 0x93, 0x0e, 0xcd, 0xc4, 0xe2, 0x8f, 0xf5, 0xb1);
+
+DEFINE_GUID(DSP_CIRCUIT_CAPTURE_GUID,
+0xe813215a, 0xfb5e, 0x4c9d, 0xb8, 0x99, 0x91, 0x18, 0x56, 0xb6, 0xde, 0x81);
+
+// {17F5B19F-C2C7-4B53-AFB9-49A0283D0DCE}
+DEFINE_GUID(DSP_CIRCUIT_SPEAKER_GUID,
+ 0x17f5b19f, 0xc2c7, 0x4b53, 0xaf, 0xb9, 0x49, 0xa0, 0x28, 0x3d, 0xd, 0xce);
+
+// {6F9EACF7-CD2D-4030-9E49-7CC4ADEFF192}
+DEFINE_GUID(DSP_CIRCUIT_MICROPHONE_GUID,
+ 0x6f9eacf7, 0xcd2d, 0x4030, 0x9e, 0x49, 0x7c, 0xc4, 0xad, 0xef, 0xf1, 0x92);
+
+// {9B5AEA69-F6E5-4BA3-9968-37FA548F5503}
+DEFINE_GUID(DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID,
+ 0x9b5aea69, 0xf6e5, 0x4ba3, 0x99, 0x68, 0x37, 0xfa, 0x54, 0x8f, 0x55, 0x3);
+
+// {3D405590-9368-4706-88E1-B69AD80C8969}
+DEFINE_GUID(DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID,
+ 0x3d405590, 0x9368, 0x4706, 0x88, 0xe1, 0xb6, 0x9a, 0xd8, 0xc, 0x89, 0x69);
+
+// {4DCB0606-6415-4A36-BDC5-9B1792117DC9}
+DEFINE_GUID(DSP_FACTORY_GUID,
+ 0x4dcb0606, 0x6415, 0x4a36, 0xbd, 0xc5, 0x9b, 0x17, 0x92, 0x11, 0x7d, 0xc9);
+
+DEFINE_GUID(SYSTEM_CONTAINER_GUID,
+0x00000000, 0x0000, 0x0000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+//
+// Factory class method: KSMETHODSETID_AcxFactoryCircuit
+//
+#define STATIC_KSMETHODSETID_AcxFactoryCircuit\
+ 0xc09a3089L, 0x3eee, 0x47e0, 0xb9, 0x37, 0x4a, 0x74, 0x66, 0xae, 0xed, 0x6b
+DEFINE_GUIDSTRUCT("c09a3089-3eee-47e0-b937-4a7466aeed6b", KSMETHODSETID_AcxFactoryCircuit);
+#define KSMETHODSETID_AcxFactoryCircuit DEFINE_GUIDNAMED(KSMETHODSETID_AcxFactoryCircuit)
+
+typedef enum {
+ KSMETHOD_ACXFACTORYCIRCUIT_ADDCIRCUIT = 1,
+ KSMETHOD_ACXFACTORYCIRCUIT_REMOVECIRCUIT = 2,
+} KSMETHOD_ACXFACTORYCIRCUIT;
+#endif // ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+
+#pragma code_seg()
+
+__drv_requiresIRQL(PASSIVE_LEVEL)
+PAGED_CODE_SEG
+NTSTATUS
+CopyRegistrySettingsPath(
+ _In_ PUNICODE_STRING RegistryPath
+ )
+/*++
+
+Routine Description:
+
+Copies the following registry path to a global variable.
+
+\REGISTRY\MACHINE\SYSTEM\ControlSetxxx\Services\<driver>\Parameters
+
+Arguments:
+
+RegistryPath - Registry path passed to DriverEntry
+
+Returns:
+
+NTSTATUS - SUCCESS if able to configure the framework
+
+--*/
+
+{
+ PAGED_CODE();
+
+ // Initializing the unicode string, so that if it is not allocated it will not be deallocated too.
+ RtlInitUnicodeString(&g_RegistryPath, NULL);
+
+ g_RegistryPath.MaximumLength = RegistryPath->Length + sizeof(WCHAR);
+
+ g_RegistryPath.Buffer = (PWCH)ExAllocatePool2(POOL_FLAG_PAGED, g_RegistryPath.MaximumLength, DRIVER_TAG);
+
+ if (g_RegistryPath.Buffer == NULL)
+ {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // ExAllocatePool2 zeros memory.
+
+ RtlAppendUnicodeToString(&g_RegistryPath, RegistryPath->Buffer);
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_AddAudioSensorsDevice(
+ _In_ WDFCHILDLIST DeviceList,
+ _In_ PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER IdentificationDescription,
+ _In_ PWDFDEVICE_INIT ChildInit
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ WDF_OBJECT_ATTRIBUTES attributes;
+ PAUDIO_SENSORS_DEVICE_CONTEXT audioSensorsDevCtx;
+ PDSP_DEVICE_CONTEXT dspDevCtx;
+ WDFDEVICE sensorsDevice = nullptr;
+
+ WDFDEVICE Device = WdfChildListGetDevice(DeviceList);
+
+ DECLARE_CONST_UNICODE_STRING(buffer, L"SOUNDWIRE\\AUDIOSENSORS");
+ DECLARE_UNICODE_STRING_SIZE(buffer2, 128);
+ DECLARE_CONST_UNICODE_STRING(AudioSensorsDeviceText, L"Audio Sensors Device");
+
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(IdentificationDescription);
+
+ //
+ // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId
+ //
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(ChildInit, &buffer));
+
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignDeviceID(ChildInit, &buffer));
+
+ RETURN_NTSTATUS_IF_FAILED(RtlUnicodeStringPrintf(&buffer2, L"%08x", 12345));
+
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignInstanceID(ChildInit, &buffer2));
+
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddDeviceText(ChildInit, &AudioSensorsDeviceText, &AudioSensorsDeviceText, 0x409));
+
+ WdfPdoInitSetDefaultLocale(ChildInit, 0x409);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, AUDIO_SENSORS_DEVICE_CONTEXT);
+ RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&ChildInit, &attributes, &sensorsDevice));
+
+ dspDevCtx = GetDspDeviceContext(Device);
+ ASSERT(dspDevCtx!=NULL);
+
+ dspDevCtx->AudioSensorsDevice = sensorsDevice;
+
+ audioSensorsDevCtx = GetAudioSensorsDeviceContext(sensorsDevice);
+ ASSERT(audioSensorsDevCtx != NULL);
+
+ //
+ // Set device capabilities.
+ //
+ {
+ WDF_DEVICE_PNP_CAPABILITIES pnpCaps;
+ WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps);
+
+ pnpCaps.SurpriseRemovalOK = WdfTrue;
+ pnpCaps.UniqueID = WdfFalse;
+
+ WdfDeviceSetPnpCapabilities(sensorsDevice, &pnpCaps);
+ }
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_INIT, "Successfully Created Audio Sensors Device.");
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_CreateChildList(
+ _In_ WDFDEVICE Device
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ WDF_CHILD_LIST_CONFIG config;
+ WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER description;
+ PDSP_DEVICE_CONTEXT dspDevCtx;
+
+ PAGED_CODE();
+
+ dspDevCtx = GetDspDeviceContext(Device);
+ ASSERT(dspDevCtx != NULL);
+
+ //
+ // Init a new child list so that we can enumerate Audio Sensors PDO
+ //
+ WDF_CHILD_LIST_CONFIG_INIT(
+ &config,
+ sizeof(WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER),
+ Dsp_AddAudioSensorsDevice // callback to create a child device.
+ );
+
+ RETURN_NTSTATUS_IF_FAILED(WdfChildListCreate(
+ Device,
+ &config,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &dspDevCtx->ChildList));
+
+ WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&description, sizeof(description));
+ RETURN_NTSTATUS_IF_FAILED(WdfChildListAddOrUpdateChildDescriptionAsPresent(
+ dspDevCtx->ChildList,
+ &description,
+ NULL));
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_INIT, "Successfully created new child list");
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtBusDeviceAdd(
+ _In_ WDFDRIVER Driver,
+ _Inout_ PWDFDEVICE_INIT DeviceInit
+ )
+/*++
+Routine Description:
+
+ EvtDeviceAdd is called by the framework in response to AddDevice
+ call from the PnP manager. We create and initialize a device object to
+ represent a new instance of the device. All the software resources
+ should be allocated in this callback.
+
+Arguments:
+
+ Driver - Handle to a framework driver object created in DriverEntry
+
+ DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ UNREFERENCED_PARAMETER(Driver);
+
+ //
+ // Initialize the pnpPowerCallbacks structure. Callback events for PNP
+ // and Power are specified here. If you don't supply any callbacks,
+ // the Framework will take appropriate default actions based on whether
+ // DeviceInit is initialized to be an FDO, a PDO or a filter device
+ // object.
+ //
+ WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
+ WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
+ pnpPowerCallbacks.EvtDevicePrepareHardware = Dsp_EvtDevicePrepareHardware;
+ pnpPowerCallbacks.EvtDeviceReleaseHardware = Dsp_EvtDeviceReleaseHardware;
+ WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
+
+ //
+ // Specify the type of context needed.
+ // Use default locking, i.e., none.
+ //
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_DEVICE_CONTEXT);
+ attributes.EvtCleanupCallback = Dsp_EvtDeviceContextCleanup;
+
+ //
+ // Allow ACX to add any pre-requirement it needs on this device.
+ //
+ ACX_DEVICEINIT_CONFIG devInitCfg;
+ ACX_DEVICEINIT_CONFIG_INIT(&devInitCfg);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(DeviceInit, &devInitCfg));
+
+ //
+ // Create the device.
+ //
+ WDFDEVICE device = NULL;
+ RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&DeviceInit, &attributes, &device));
+
+ //
+ // Init Dsp's device context.
+ //
+ PDSP_DEVICE_CONTEXT devCtx;
+ devCtx = GetDspDeviceContext(device);
+ ASSERT(devCtx != NULL);
+ devCtx->Render = NULL;
+ devCtx->Capture = NULL;
+ //
+ // Allow ACX to add any post-requirement it needs on this device.
+ //
+ ACX_DEVICE_CONFIG devCfg;
+ ACX_DEVICE_CONFIG_INIT(&devCfg);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(device, &devCfg));
+
+ //
+ // Tell the framework to set the SurpriseRemovalOK in the DeviceCaps so
+ // that you don't get the popup in usermode (on Win2K) when you surprise
+ // remove the device.
+ //
+ WDF_DEVICE_PNP_CAPABILITIES pnpCaps;
+ WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps);
+ pnpCaps.SurpriseRemovalOK = WdfTrue;
+ WdfDeviceSetPnpCapabilities(device, &pnpCaps);
+
+ //
+ // Default child list is owned by ACX and can only contain PDOs that
+ // ACX is aware of so create new child list that will contain Audio Sensors PDO.
+ //
+ RETURN_NTSTATUS_IF_FAILED(Dsp_CreateChildList(device));
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtDevicePrepareHardware(
+ _In_ WDFDEVICE Device,
+ _In_ WDFCMRESLIST ResourceList,
+ _In_ WDFCMRESLIST ResourceListTranslated
+)
+/*++
+
+Routine Description:
+
+ In this callback, the driver does whatever is necessary to make the
+ hardware ready to use.
+
+Arguments:
+
+ Device - handle to a device
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ UNREFERENCED_PARAMETER(ResourceList);
+ UNREFERENCED_PARAMETER(ResourceListTranslated);
+
+ PDSP_DEVICE_CONTEXT devCtx;
+ devCtx = GetDspDeviceContext(Device);
+ ASSERT(devCtx != NULL);
+
+
+ RETURN_NTSTATUS_IF_FAILED(Dsp_SetPowerPolicy(Device));
+
+ RETURN_NTSTATUS_IF_FAILED(CSaveData::SetDeviceObject(WdfDeviceWdmGetDeviceObject(Device)));
+
+ RETURN_NTSTATUS_IF_FAILED(CSaveData::InitializeWorkItems(WdfDeviceWdmGetDeviceObject(Device)));
+
+ RETURN_NTSTATUS_IF_FAILED(CWaveReader::InitializeWorkItems(WdfDeviceWdmGetDeviceObject(Device)));
+
+ RETURN_NTSTATUS_IF_FAILED(AcpiReader::_CreateAndInitialize(Device, g_SDCAVDspLog, DRIVER_TAG));
+
+ //
+ // Add a circuit factory that will handle all different devices
+ //
+ if (!devCtx->Factory)
+ {
+ RETURN_NTSTATUS_IF_FAILED(Dsp_AddFactoryCircuit(Device));
+ }
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtDeviceReleaseHardware(
+ _In_ WDFDEVICE Device,
+ _In_ WDFCMRESLIST ResourceListTranslated
+ )
+/*++
+
+Routine Description:
+
+ In this callback, the driver releases the h/w resources allocated in the
+ prepare h/w callback.
+
+Arguments:
+
+ Device - handle to a device
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ UNREFERENCED_PARAMETER(Device);
+ UNREFERENCED_PARAMETER(ResourceListTranslated);
+
+ PDSP_DEVICE_CONTEXT devCtx;
+ devCtx = GetDspDeviceContext(Device);
+ ASSERT(devCtx != NULL);
+
+ //
+ // Note that we don't remove the factory circuit here (AcxDeviceRemoveFactoryCircuit).
+ // If the factory circuit is removed here, any circuit devices created through it could
+ // be destroyed without ACX knowledge resulting in a Duplicate PDO bugcheck.
+ //
+
+
+ CSaveData::DestroyWorkItems();
+ CWaveReader::DestroyWorkItems();
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_SetPowerPolicy(
+ _In_ WDFDEVICE Device
+ )
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ //
+ // Init the idle policy structure.
+ //
+ WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings;
+ WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0);
+ idleSettings.IdleTimeout = 10000; // 10-sec
+
+ status = WdfDeviceAssignS0IdleSettings(Device, &idleSettings);
+
+ return status;
+}
+
+#pragma code_seg()
+VOID
+Dsp_EvtDeviceContextCleanup(
+ _In_ WDFOBJECT WdfDevice
+)
+/*++
+
+Routine Description:
+
+ In this callback, it cleans up device context.
+
+Arguments:
+
+ WdfDevice - WDF device object
+
+Return Value:
+
+ NULL
+
+--*/
+{
+ WDFDEVICE device;
+ PDSP_DEVICE_CONTEXT devCtx;
+
+ device = (WDFDEVICE)WdfDevice;
+ devCtx = GetDspDeviceContext(device);
+ ASSERT(devCtx != NULL);
+
+ if (devCtx->Capture)
+ {
+ DspC_CircuitCleanup(devCtx->Capture);
+ devCtx->Capture = NULL;
+ }
+
+ if (devCtx->AudioSensorsDevice)
+ {
+ devCtx->AudioSensorsDevice = nullptr;
+ }
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_DetermineCircuitDetailsFromVendorProperties(
+ _In_ AcpiReader * Acpi,
+ _In_ ACXOBJECTBAG CircuitProperties,
+ _Out_ PGUID CircuitId,
+ _Out_opt_ ULONG * DataPortNumber = nullptr,
+ _In_ ULONG MaxPathDescriptors = 0,
+ _Out_opt_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors = nullptr
+)
+{
+ DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(VendorPropertiesBlock);
+ WDFMEMORY vendorPropertiesBlock = NULL;
+ char* vendorPropertiesBuffer = NULL;
+ ULONG vendorPropertiesSize;
+ NTSTATUS status = STATUS_NOT_FOUND;
+
+ PAGED_CODE();
+
+ if (PathDescriptors != nullptr && MaxPathDescriptors == 0)
+ {
+ RETURN_NTSTATUS(STATUS_INVALID_PARAMETER);
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveBlob(CircuitProperties, &VendorPropertiesBlock, NULL, &vendorPropertiesBlock));
+
+ auto cleanup1 = scope_exit([&vendorPropertiesBlock] ()
+ {
+ if (vendorPropertiesBlock != NULL)
+ {
+ WdfObjectDelete(vendorPropertiesBlock);
+ vendorPropertiesBlock = NULL;
+ }
+ });
+
+ RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED(
+ Acpi->GetPropertyString("acpi-vendor-config-type", ACPI_METHOD_SECTION_DEVICE_PROPERTIES,
+ vendorPropertiesBlock, NULL, 0, &vendorPropertiesSize),
+ STATUS_BUFFER_TOO_SMALL);
+
+ vendorPropertiesBuffer = (char*)ExAllocatePool2(POOL_FLAG_PAGED, vendorPropertiesSize, DRIVER_TAG);
+
+ auto cleanup2 = scope_exit([&vendorPropertiesBuffer] ()
+ {
+ if (vendorPropertiesBuffer != NULL)
+ {
+ ExFreePool(vendorPropertiesBuffer);
+ vendorPropertiesBuffer = NULL;
+ }
+ });
+
+ RETURN_NTSTATUS_IF_FAILED(
+ Acpi->GetPropertyString("acpi-vendor-config-type", ACPI_METHOD_SECTION_DEVICE_PROPERTIES,
+ vendorPropertiesBlock, vendorPropertiesBuffer, vendorPropertiesSize, &vendorPropertiesSize));
+
+ // use ACPI methods to parse for DataPortNumber
+ if (DataPortNumber)
+ {
+ *DataPortNumber = 0;
+ }
+ if (PathDescriptors)
+ {
+ RtlZeroMemory(PathDescriptors, sizeof(*PathDescriptors) + (MaxPathDescriptors - 1) * sizeof(PathDescriptors->Descriptor[0]));
+ }
+
+ *CircuitId = NULL_GUID;
+
+ // This code also assumes Data Port number based on type of endpoint, which is not correct
+ // for real hardware. Data Port number should come from ACPI.
+ if (sizeof("Streaming_Speaker") <= vendorPropertiesSize && sizeof("Streaming_Speaker") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_Speaker", sizeof("Streaming_Speaker")))
+ {
+ *CircuitId = DSP_CIRCUIT_SPEAKER_GUID;
+ if (DataPortNumber)
+ {
+ // Speaker connects to DP 1
+ *DataPortNumber = 1;
+ }
+ status = STATUS_SUCCESS;
+ }
+ else if (sizeof("Streaming_MicrophoneArray") <= vendorPropertiesSize && sizeof("Streaming_MicrophoneArray") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_MicrophoneArray", sizeof("Streaming_MicrophoneArray")))
+ {
+ *CircuitId = DSP_CIRCUIT_MICROPHONE_GUID;
+ if (DataPortNumber)
+ {
+ // Raw capture path connects to DP 6
+ *DataPortNumber = 6;
+ }
+ status = STATUS_SUCCESS;
+ }
+ else if (sizeof("Streaming_Headphones") <= vendorPropertiesSize && sizeof("Streaming_Headphones") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_Headphones", sizeof("Streaming_Headphones")))
+ {
+ *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID;
+ if (DataPortNumber)
+ {
+ // UAJ Output uses DP 3
+ *DataPortNumber = 3;
+ }
+ status = STATUS_SUCCESS;
+ }
+ else if (sizeof("Streaming_LineOut") <= vendorPropertiesSize && sizeof("Streaming_LineOut") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_LineOut", sizeof("Streaming_LineOut")))
+ {
+ *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID;
+ if (DataPortNumber)
+ {
+ // UAJ Output uses DP 3
+ *DataPortNumber = 3;
+ }
+ status = STATUS_SUCCESS;
+ }
+ else if (sizeof("Streaming_HeadsetOutput") <= vendorPropertiesSize && sizeof("Streaming_HeadsetOutput") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_HeadsetOutput", sizeof("Streaming_HeadsetOutput")))
+ {
+ *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID;
+ if (DataPortNumber)
+ {
+ // UAJ Output uses DP 3
+ *DataPortNumber = 3;
+ }
+ status = STATUS_SUCCESS;
+ }
+ else if (sizeof("Streaming_Microphone") <= vendorPropertiesSize && sizeof("Streaming_Microphone") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_Microphone", sizeof("Streaming_Microphone")))
+ {
+ *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID;
+ if (DataPortNumber)
+ {
+ // UAJ Input uses DP 2
+ *DataPortNumber = 2;
+ }
+ status = STATUS_SUCCESS;
+ }
+ else if (sizeof("Streaming_LineIn") <= vendorPropertiesSize && sizeof("Streaming_LineIn") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_LineIn", sizeof("Streaming_LineIn")))
+ {
+ *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID;
+ if (DataPortNumber)
+ {
+ // UAJ Input uses DP 2
+ *DataPortNumber = 2;
+ }
+ status = STATUS_SUCCESS;
+ }
+ else if (sizeof("Streaming_HeadsetMic") <= vendorPropertiesSize && sizeof("Streaming_HeadsetMic") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_HeadsetMic", sizeof("Streaming_HeadsetMic")))
+ {
+ *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID;
+ if (DataPortNumber)
+ {
+ // UAJ Input uses DP 2
+ *DataPortNumber = 2;
+ }
+ status = STATUS_SUCCESS;
+ }
+
+ //
+ // The below code would be replaced in a real DSP driver (or modified to use vendor-specific properties)
+ //
+ ULONG vendorAggCount = 0;
+ NTSTATUS aggCountStatus = Acpi->GetPropertyULong("acpi-vendor-mstest-aggregateddevice-count", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &vendorAggCount);
+ if (NT_SUCCESS(aggCountStatus) && vendorAggCount > 0 && vendorAggCount <= MaxPathDescriptors && PathDescriptors != nullptr)
+ {
+ //
+ // This endpoint supports aggregation. If we find the necessary properties for SDCA_PATH_DESCRIPTORS2 for each aggregated device
+ // we'll use the PathDescriptors for the endpoint. The PathDescriptors allows each aggregated device to use different channel masks.
+ //
+ const ULONG MAX_PROPERTY_SIZE = ARRAYSIZE("acpi-vendor-mstest-aggregateddevice-%d-dp-channel-mask");
+ ULONG peripheralSuccessCount = 0;
+ size_t descriptorsSize = sizeof(*PathDescriptors) + sizeof(PathDescriptors->Descriptor[0]) * (vendorAggCount - 1);
+
+ for (ULONG i = 0; i < vendorAggCount && i < MAX_AGGREGATED_DEVICES; ++i)
+ {
+ char propertyName[MAX_PROPERTY_SIZE];
+
+ PathDescriptors->Descriptor[i].Size = sizeof(PathDescriptors->Descriptor[1]);
+ PathDescriptors->Descriptor[i].Version = SDCA_PATH_DESCRIPTOR2_VERSION_1;
+ PathDescriptors->Descriptor[i].FunctionInformationId = i;
+ PathDescriptors->Descriptor[i].DataPortMap = SdcaDataPortMapIndexA;
+ PathDescriptors->Descriptor[i].DataPortConfig[0].Size = sizeof(SOUNDWIRE_DATAPORT_CONFIGURATION);
+ // EndpointId will be supplied during CreateStreamBridge
+ PathDescriptors->Descriptor[i].DataPortConfig[0].EndpointId = 0;
+ PathDescriptors->Descriptor[i].DataPortConfig[0].Modes = SoundWireDataPortModeIsochronous;
+
+ // Values from the vendor blob of a partner's DSP driver
+ status = RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-aggregateddevice-%d-unique-id", i);
+ if (!NT_SUCCESS(status))
+ {
+ break;
+ }
+
+ // We need to be able to match each Descriptor we find with a specific aggregated device. The aggregated device ordering at runtime
+ // can be different, so we need to save the UniqueID for the audio function now.
+ // At pin connection, we will discover the aggregated devices and replace the Uniquie ID with the appropriate FunctionInformationId
+ status = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &PathDescriptors->Descriptor[i].FunctionInformationId);
+ if (!NT_SUCCESS(status))
+ {
+ break;
+ }
+
+ status = RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-aggregateddevice-%d-terminal-id", i);
+ if (!NT_SUCCESS(status))
+ {
+ break;
+ }
+
+ status = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &PathDescriptors->Descriptor[i].TerminalEntityId);
+ if (!NT_SUCCESS(status))
+ {
+ break;
+ }
+
+ status = RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-aggregateddevice-%d-dp-number", i);
+ if (!NT_SUCCESS(status))
+ {
+ break;
+ }
+
+ status = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &PathDescriptors->Descriptor[i].DataPortConfig[0].DataPortNumber);
+ if (!NT_SUCCESS(status))
+ {
+ break;
+ }
+
+ status = RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-aggregateddevice-%d-dp-channel-mask", i);
+ if (!NT_SUCCESS(status))
+ {
+ break;
+ }
+
+ status = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &PathDescriptors->Descriptor[i].DataPortConfig[0].ChannelMask);
+ if (!NT_SUCCESS(status))
+ {
+ break;
+ }
+
+ ++peripheralSuccessCount;
+ }
+
+ if (peripheralSuccessCount == vendorAggCount)
+ {
+ // Found all the data we wanted for each of the aggregated devices
+ PathDescriptors->Size = (ULONG)descriptorsSize;
+ PathDescriptors->Version = SDCA_PATH_DESCRIPTOR2_VERSION_1;
+ PathDescriptors->DescriptorCount = vendorAggCount;
+ PathDescriptors->SdcaPath = SdcaPathDefault;
+ }
+
+ // Ignore failures retrieving optional properties
+ status = STATUS_SUCCESS;
+ }
+
+ ULONG vendorDataPortNumber = ULONG_MAX;
+ ULONG vendorChannelMask = ULONG_MAX;
+ ULONG vendorTerminalId = ULONG_MAX;
+
+
+ // GetPropertyULong will leave the value as is (ULONG_MAX) if it isn't found
+ Acpi->GetPropertyULong("acpi-vendor-mstest-device-terminal-id", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &vendorTerminalId);
+ Acpi->GetPropertyULong("acpi-vendor-mstest-device-dp-number", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &vendorDataPortNumber);
+ Acpi->GetPropertyULong("acpi-vendor-mstest-device-dp-channel-mask", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &vendorChannelMask);
+
+ // DataPortNumber by itself is retained to validate back compat with systems that don't support the
+ // new PathDescriptors2 structure
+ if (DataPortNumber)
+ {
+ // Example vendor property for a streaming device
+ if (vendorDataPortNumber != ULONG_MAX)
+ {
+ *DataPortNumber = vendorDataPortNumber;
+ }
+ }
+
+ // Only fill out the PathDescriptors here if we didn't already fill it out with aggregated information
+ if (PathDescriptors && PathDescriptors->Size == 0)
+ {
+ // Example code if the vendor values have been discovered for a single non-aggregated endpoint
+ if ((vendorTerminalId != ULONG_MAX) && (vendorDataPortNumber != ULONG_MAX) && (vendorChannelMask != ULONG_MAX))
+ {
+ // We have enough information to fill out the PathDescriptors structure
+ PathDescriptors->Size = sizeof(*PathDescriptors);
+ PathDescriptors->Version = SDCA_PATH_DESCRIPTOR2_VERSION_1;
+ PathDescriptors->SdcaPath = SdcaPathDefault;
+ // EndpointId will be filled in later
+ PathDescriptors->EndpointId = 0;
+ PathDescriptors->DescriptorCount = 1;
+ PathDescriptors->Descriptor[0].Size = sizeof(PathDescriptors->Descriptor[0]);
+ PathDescriptors->Descriptor[0].Version = SDCA_PATH_DESCRIPTOR2_VERSION_1;
+ PathDescriptors->Descriptor[0].FunctionInformationId = 0;
+ PathDescriptors->Descriptor[0].TerminalEntityId = vendorTerminalId;
+ // DataPortMap indicates which DPIndex entries are used, in this sample we'll only use
+ // a single data port and that will be DPIndex_A.
+ PathDescriptors->Descriptor[0].DataPortMap = SdcaDataPortMapIndexA;
+ PathDescriptors->Descriptor[0].DataPortConfig[0].Size = sizeof(PathDescriptors->Descriptor[0].DataPortConfig[0]);
+ PathDescriptors->Descriptor[0].DataPortConfig[0].DataPortNumber = vendorDataPortNumber;
+ // The Descriptor-specific EndpointId is ignored
+ PathDescriptors->Descriptor[0].DataPortConfig[0].EndpointId = 0;
+ // Mode may be specified as something other than Isochronous depending on hardware and configuration
+ PathDescriptors->Descriptor[0].DataPortConfig[0].Modes = SoundWireDataPortModeIsochronous;
+ PathDescriptors->Descriptor[0].DataPortConfig[0].ChannelMask = vendorChannelMask;
+ }
+ }
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtAcxFactoryCircuitCreateCircuitDevice(
+ _In_ WDFDEVICE Parent,
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig,
+ _Out_ WDFDEVICE * Device
+)
+{
+ ACXOBJECTBAG circuitProperties;
+
+ DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, CircuitId);
+
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+ WDF_OBJECT_ATTRIBUTES attributes;
+
+ *Device = NULL;
+
+ // Create object bag from the CircuitProperties
+ ACX_OBJECTBAG_CONFIG propConfig;
+ ACX_OBJECTBAG_CONFIG_INIT(&propConfig);
+ propConfig.Handle = CircuitConfig->CircuitProperties;
+ propConfig.Flags |= AcxObjectBagConfigOpenWithHandle;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &propConfig, &circuitProperties));
+
+ auto cleanupPropConfig = scope_exit([=]() {
+ WdfObjectDelete(circuitProperties);
+ }
+ );
+
+ // Retrieve the intended Circuit ID from the object bag
+ GUID circuitId;
+ AcpiReader * acpiReader = GetAcpiReaderDeviceContext(Parent);
+
+ RETURN_NTSTATUS_IF_TRUE(acpiReader == NULL, STATUS_INVALID_PARAMETER);
+
+ RETURN_NTSTATUS_IF_FAILED(Dsp_DetermineCircuitDetailsFromVendorProperties(acpiReader, circuitProperties, &circuitId));
+
+ // Call the appropriate CreateCircuitDevice based on the Circuit ID
+ if (IsEqualGUID(circuitId, DSP_CIRCUIT_MICROPHONE_GUID) || IsEqualGUID(circuitId, DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID))
+ {
+ status = DspC_EvtAcxFactoryCircuitCreateCircuitDevice(Parent, Factory, CircuitConfig, Device);
+ }
+ else if (IsEqualGUID(circuitId, DSP_CIRCUIT_SPEAKER_GUID) || IsEqualGUID(circuitId, DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID))
+ {
+ status = DspR_EvtAcxFactoryCircuitCreateCircuitDevice(Parent, Factory, CircuitConfig, Device);
+ }
+ else
+ {
+ status = STATUS_NOT_SUPPORTED;
+ DrvLogError(g_SDCAVDspLog, FLAG_INIT, L"Unexpected CircuitId %!GUID!, %!STATUS!", &circuitId, status);
+ }
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+ //
+ // On success, cache this device info.
+ //
+ if (NT_SUCCESS(status))
+ {
+ status = Dsp_AddChildDeviceToCache(Factory, &CircuitConfig->CircuitUniqueId, *Device);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(g_SDCAVDspLog, FLAG_INIT,
+ L"Dsp_AddChildDeviceToCache(Factory=%p, ID=%!GUID!, WDFDEVICE=%p) failed, %!STATUS!",
+ Factory, &CircuitConfig->CircuitUniqueId, *Device, status);
+
+ WdfObjectDelete(*Device);
+ *Device = NULL;
+ }
+ }
+#endif
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_EvtAcxFactoryCircuitCreateCircuit(
+ _In_ WDFDEVICE Parent,
+ _In_ WDFDEVICE Device,
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig,
+ _In_ PACXCIRCUIT_INIT CircuitInit
+)
+{
+ ACXOBJECTBAG circuitProperties;
+
+ DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, CircuitId);
+
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DrvLogEnter(g_SDCAVDspLog);
+
+ // Create object bag from the CompositeProperties
+ ACX_OBJECTBAG_CONFIG propConfig;
+ ACX_OBJECTBAG_CONFIG_INIT(&propConfig);
+ propConfig.Handle = CircuitConfig->CircuitProperties;
+ propConfig.Flags |= AcxObjectBagConfigOpenWithHandle;
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &propConfig, &circuitProperties));
+
+ auto cleanupPropConfig = scope_exit([=]() {
+ WdfObjectDelete(circuitProperties);
+ }
+ );
+
+ // Retrieve the intended Circuit ID from the object bag
+ GUID circuitId;
+ ULONG dataPortNumber = 0;
+
+ PSDCA_PATH_DESCRIPTORS2 pathDescriptors = (PSDCA_PATH_DESCRIPTORS2)ExAllocatePool2(
+ POOL_FLAG_NON_PAGED,
+ sizeof(SDCA_PATH_DESCRIPTORS2) + sizeof(SDCA_PATH_DESCRIPTOR2)*(MAX_AGGREGATED_DEVICES-1),
+ DRIVER_TAG);
+ if (pathDescriptors == nullptr)
+ {
+ RETURN_NTSTATUS_IF_FAILED(STATUS_INSUFFICIENT_RESOURCES);
+ }
+ auto descriptors_free = scope_exit([&pathDescriptors]()
+ {
+ ExFreePool(pathDescriptors);
+ });
+
+ AcpiReader * acpiReader = GetAcpiReaderDeviceContext(Parent);
+
+ RETURN_NTSTATUS_IF_TRUE(acpiReader == NULL, STATUS_INVALID_PARAMETER);
+
+ RETURN_NTSTATUS_IF_FAILED(Dsp_DetermineCircuitDetailsFromVendorProperties(
+ acpiReader,
+ circuitProperties,
+ &circuitId,
+ &dataPortNumber,
+ MAX_AGGREGATED_DEVICES,
+ pathDescriptors));
+
+ AcxCircuitInitSetComponentId(CircuitInit, &circuitId);
+
+ // Call the appropriate CreateCircuitDevice based on the Circuit ID
+ if (IsEqualGUID(circuitId, DSP_CIRCUIT_MICROPHONE_GUID) || IsEqualGUID(circuitId, DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID))
+ {
+ return DspC_EvtAcxFactoryCircuitCreateCircuit(Parent, Device, Factory, CircuitConfig, CircuitInit, dataPortNumber, pathDescriptors);
+ }
+ else if (IsEqualGUID(circuitId, DSP_CIRCUIT_SPEAKER_GUID) || IsEqualGUID(circuitId, DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID))
+ {
+ return DspR_EvtAcxFactoryCircuitCreateCircuit(Parent, Device, Factory, CircuitConfig, CircuitInit, dataPortNumber, pathDescriptors);
+ }
+
+ status = STATUS_NOT_SUPPORTED;
+ DrvLogError(g_SDCAVDspLog, FLAG_INIT, L"Unexpected CircuitId %!GUID!, %!STATUS!", &circuitId, status);
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_AddFactoryCircuit(
+ _In_ WDFDEVICE Device
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSP_DEVICE_CONTEXT devCtx = GetDspDeviceContext(Device);
+ PDSP_FACTORY_CONTEXT factoryCtx = NULL;
+
+ ASSERT(devCtx != NULL);
+
+ DECLARE_CONST_UNICODE_STRING(dspFactoryName, L"VirtualDspFactoryCircuit");
+ DECLARE_CONST_UNICODE_STRING(dspFactoryUri, L"acpi:obj-path:\\_SB.PC00.HDAS");
+
+ //
+ // Get a FactoryCircuitInit structure.
+ //
+ PACXFACTORYCIRCUIT_INIT factoryInit = NULL;
+ factoryInit = AcxFactoryCircuitInitAllocate(Device);
+
+ //
+ // Add factory identifiers.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxFactoryCircuitInitAssignComponentUri(factoryInit, &dspFactoryUri));
+ RETURN_NTSTATUS_IF_FAILED(AcxFactoryCircuitInitAssignName(factoryInit, &dspFactoryName));
+
+ //
+ // Add properties, events and methods.
+ //
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+ //
+ // Set circuit-callbacks.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxFactoryCircuitInitAssignAcxRequestPreprocessCallback(
+ factoryInit,
+ Dsp_EvtFactoryRemoveCircuitRequestPreprocess,
+ (ACXCONTEXT)Device,
+ AcxRequestTypeMethod,
+ &KSMETHODSETID_AcxFactoryCircuit,
+ KSMETHOD_ACXFACTORYCIRCUIT_REMOVECIRCUIT));
+#endif
+
+ //
+ // Assign the circuit's operation-callbacks.
+ //
+ ACX_FACTORY_CIRCUIT_OPERATION_CALLBACKS operationCallbacks;
+ ACX_FACTORY_CIRCUIT_OPERATION_CALLBACKS_INIT(&operationCallbacks);
+ operationCallbacks.EvtAcxFactoryCircuitCreateCircuitDevice = Dsp_EvtAcxFactoryCircuitCreateCircuitDevice;
+ operationCallbacks.EvtAcxFactoryCircuitCreateCircuit = Dsp_EvtAcxFactoryCircuitCreateCircuit;
+ AcxFactoryCircuitInitSetOperationCallbacks(factoryInit, &operationCallbacks);
+
+ //
+ // Create the factory circuit.
+ //
+ WDF_OBJECT_ATTRIBUTES attributes;
+ ACXFACTORYCIRCUIT factory;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_FACTORY_CONTEXT);
+ attributes.ParentObject = Device;
+ attributes.EvtCleanupCallback = Dsp_EvtFactoryContextCleanup;
+ attributes.EvtDestroyCallback = Dsp_EvtFactoryContextDestroy;
+
+ ASSERT(devCtx->Factory == NULL);
+ RETURN_NTSTATUS_IF_FAILED(AcxFactoryCircuitCreate(Device, &attributes, &factoryInit, &factory));
+ ASSERT(factory != NULL);
+
+ factoryCtx = GetDspFactoryContext(factory);
+ factoryCtx->Device = Device;
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+ RETURN_NTSTATUS_IF_FAILED(Dsp_InitializeChildDevicesCache(factory));
+#endif // ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+
+ //
+ // Add circuit factory to device.
+ // It will remain added until the Device is cleaned up by WDF due to removal.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddFactoryCircuit(Device, factory));
+ devCtx->Factory = factory;
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_SendTestPropertyTo(
+ _In_ WDFDEVICE Device,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ GUID PropertySet,
+ _In_ ULONG PropertyId,
+ _In_ ACX_PROPERTY_VERB Verb,
+ _In_ PVOID Control,
+ _In_ ULONG ControlCb,
+ _Inout_ PVOID Value,
+ _In_ ULONG ValueCb,
+ _Out_ ULONG_PTR* Information
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ if (Information)
+ {
+ *Information = 0;
+ }
+
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(Circuit);
+
+ ACXPIN pin;
+ if (circuitCtx->IsRenderCircuit)
+ {
+ pin = AcxCircuitGetPinById(Circuit, DspPinTypeBridge);
+ }
+ else
+ {
+ pin = AcxCircuitGetPinById(Circuit, DspCapturePinTypeBridge);
+ }
+ ASSERT(pin);
+
+ DSP_PIN_CONTEXT* pinCtx = GetDspPinContext(pin);
+ ASSERT(pinCtx);
+
+ RETURN_NTSTATUS_IF_TRUE(pinCtx->TargetCircuit == NULL, STATUS_INVALID_DEVICE_STATE);
+
+ ACX_REQUEST_PARAMETERS requestParams;
+ ACX_REQUEST_PARAMETERS_INIT_PROPERTY(
+ &requestParams,
+ PropertySet,
+ PropertyId,
+ Verb,
+ AcxItemTypeCircuit,
+ 0,
+ Control, ControlCb,
+ Value, ValueCb
+ );
+
+ WDFREQUEST request;
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = Device;
+ RETURN_NTSTATUS_IF_FAILED(WdfRequestCreate(&attributes, AcxTargetCircuitGetWdfIoTarget(pinCtx->TargetCircuit), &request));
+ auto request_free = scope_exit([&request]()
+ {
+ WdfObjectDelete(request);
+ });
+
+ RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitFormatRequestForProperty(pinCtx->TargetCircuit, request, &requestParams));
+
+ WDF_REQUEST_SEND_OPTIONS sendOptions;
+ WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS);
+ WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(5));
+
+ RETURN_NTSTATUS_IF_TRUE(!WdfRequestSend(request, AcxTargetCircuitGetWdfIoTarget(pinCtx->TargetCircuit), &sendOptions), STATUS_INVALID_DEVICE_REQUEST);
+ status = WdfRequestGetStatus(request);
+ if (Information)
+ {
+ *Information = WdfRequestGetInformation(request);
+ }
+ if (status == STATUS_BUFFER_OVERFLOW && ValueCb == 0)
+ {
+ // Don't trace this error, it's normal
+ return status;
+ }
+ RETURN_NTSTATUS_IF_FAILED(status);
+
+ return status;
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_SendVendorSpecificProperties(
+ _In_ WDFDEVICE Device,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ BOOLEAN SetValue
+)
+{
+ VIRTUAL_STACK_VENDOR_SPECIFIC_CONTROL control = { 0 };
+ VIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA data = { 0 };
+ ULONG_PTR info;
+
+ PAGED_CODE();
+
+ control.VendorSpecificId = VirtualStackVendorSpecificRequestGetTestData;
+ control.VendorSpecificSize = sizeof(control);
+ control.Data.DataPort = 0;
+ control.Data.EndpointId = 0;
+
+ NTSTATUS status = Dsp_SendTestPropertyTo(
+ Device,
+ Circuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_VENDOR_SPECIFIC,
+ AcxPropertyVerbGet,
+ &control,
+ sizeof(control),
+ nullptr,
+ 0,
+ &info);
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"KSPROPERTY_SDCA_VENDOR_SPECIFIC GetTestData for size request returns %!STATUS! (%p)", status, (void*)info);
+
+ status = Dsp_SendTestPropertyTo(
+ Device,
+ Circuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_VENDOR_SPECIFIC,
+ AcxPropertyVerbGet,
+ &control,
+ sizeof(control),
+ &data,
+ sizeof(data),
+ &info);
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"KSPROPERTY_SDCA_VENDOR_SPECIFIC GetTestData returns %#x : %#x, %!STATUS!", data.Test1, data.Test2, status);
+
+ RtlZeroMemory(&control, sizeof(control));
+ control.VendorSpecificId = VirtualStackVendorSpecificRequestSetTestConfig;
+ control.VendorSpecificSize = sizeof(control);
+ control.Config.IsScatterGather = SetValue;
+ status = Dsp_SendTestPropertyTo(
+ Device,
+ Circuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_VENDOR_SPECIFIC,
+ AcxPropertyVerbSet,
+ &control,
+ sizeof(control),
+ &data,
+ sizeof(data),
+ &info);
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"KSPROPERTY_SDCA_VENDOR_SPECIFIC SetTestParam returned %!STATUS!", status);
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_EvtFactoryContextCleanup(
+ _In_ WDFOBJECT Factory
+ )
+{
+ PAGED_CODE();
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+ Dsp_CleanupChildDevicesCache((ACXFACTORYCIRCUIT)Factory);
+#else
+ UNREFERENCED_PARAMETER(Factory);
+#endif
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_EvtFactoryContextDestroy(
+ _In_ WDFOBJECT Factory
+ )
+{
+ PAGED_CODE();
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+ Dsp_DeleteChildDevicesCache((ACXFACTORYCIRCUIT)Factory);
+#else
+ UNREFERENCED_PARAMETER(Factory);
+#endif
+}
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_InitializeChildDevicesCache(
+ _In_ ACXFACTORYCIRCUIT Factory
+ )
+{
+ PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory);
+
+ PAGED_CODE();
+
+ RETURN_NTSTATUS_IF_FAILED(WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &factoryCtx->CacheLock));
+ RETURN_NTSTATUS_IF_FAILED(WdfCollectionCreate(WDF_NO_OBJECT_ATTRIBUTES, &factoryCtx->Cache));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_CleanupChildDevicesCache(
+ _In_ ACXFACTORYCIRCUIT Factory
+ )
+{
+ PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory);
+ WDFOBJECT child = NULL;
+
+ PAGED_CODE();
+
+ //
+ // Factory is going away, cleanup child devices cache.
+ //
+ if (factoryCtx->Cache == NULL || factoryCtx->CacheLock == NULL)
+ {
+ return; // Nothing to do.
+ }
+
+ WdfWaitLockAcquire(factoryCtx->CacheLock, NULL);
+
+ while ((child = WdfCollectionGetFirstItem(factoryCtx->Cache)) != NULL)
+ {
+ PDSP_DEVICEID_CONTEXT idCtx = GetDspDeviceIdContext(child);
+
+ //
+ // - zero out ID.
+ // - remove the item from the cache.
+ //
+ idCtx->UniqueID = NULL_GUID;
+ WdfCollectionRemoveItem(factoryCtx->Cache, 0);
+ }
+
+ WdfWaitLockRelease(factoryCtx->CacheLock);
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_DeleteChildDevicesCache(
+ _In_ ACXFACTORYCIRCUIT Factory
+ )
+{
+ PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory);
+
+ PAGED_CODE();
+
+ if (factoryCtx->Cache != NULL)
+ {
+ WdfObjectDelete(factoryCtx->Cache);
+ factoryCtx->Cache = NULL;
+ }
+
+ if (factoryCtx->CacheLock != NULL)
+ {
+ WdfObjectDelete(factoryCtx->CacheLock);
+ factoryCtx->CacheLock = NULL;
+ }
+}
+
+PAGED_CODE_SEG
+bool
+Dsp_IsChildDeviceInCacheLocked(
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ const GUID * UniqueId
+ )
+{
+ PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory);
+ ULONG count = WdfCollectionGetCount(factoryCtx->Cache);
+ bool isPresent = false;
+
+ PAGED_CODE();
+
+ for (ULONG i = 0; i < count; i++)
+ {
+ WDFDEVICE child = NULL;
+ PDSP_DEVICEID_CONTEXT idCtx = NULL;
+
+ child = (WDFDEVICE)WdfCollectionGetItem(factoryCtx->Cache, i);
+ idCtx = GetDspDeviceIdContext(child);
+
+ if ((idCtx != 0) && IsEqualGUID(idCtx->UniqueID, *UniqueId))
+ {
+ // Found it.
+ isPresent = true;
+ break;
+ }
+ }
+
+ return isPresent;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_AddChildDeviceToCache(
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ const GUID * UniqueId,
+ _In_ WDFDEVICE Device
+ )
+{
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory);
+
+ PAGED_CODE();
+
+ WdfWaitLockAcquire(factoryCtx->CacheLock, NULL);
+
+ //
+ // Make sure there is not another device with the same ID.
+ //
+ if (Dsp_IsChildDeviceInCacheLocked(Factory, UniqueId))
+ {
+ status = STATUS_DEVICE_ALREADY_ATTACHED;
+ }
+ else
+ {
+ //
+ // Attach a device ID context if not already present.
+ //
+ PDSP_DEVICEID_CONTEXT idCtx = GetDspDeviceIdContext(Device);
+ if (idCtx == NULL)
+ {
+ // Add the device ID context.
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_DEVICEID_CONTEXT);
+ attributes.EvtCleanupCallback = Dsp_EvtDeviceIdContextCleanup;
+
+ status = WdfObjectAllocateContext(Device, &attributes, (PVOID*)&idCtx);
+ if (!NT_SUCCESS(status))
+ {
+ idCtx = NULL; // just in case.
+ DrvLogError(g_SDCAVDspLog, FLAG_INIT,
+ "Failed to allocate a DSP_DEVICEID_CONTEXT on WDFDEVICE %p, %!STATUS!",
+ Device, status);
+ }
+ }
+ else
+ {
+ // This should not happen, but just in case, cleanup the context.
+ ASSERT(FALSE);
+ idCtx->UniqueID = NULL_GUID;
+ if (idCtx->Factory != NULL)
+ {
+ WdfObjectDereferenceWithTag(idCtx->Factory, (PVOID)DRIVER_TAG);
+ idCtx->Factory = NULL;
+ }
+ }
+
+ if (idCtx != NULL)
+ {
+ //
+ // Store the unique ID of this device.
+ //
+ idCtx->UniqueID = *UniqueId;
+
+ //
+ // Take a strong ref on the factory object.
+ // Ref is removed on context cleanup.
+ //
+ idCtx->Factory = Factory;
+ WdfObjectReferenceWithTag(Factory, (PVOID)DRIVER_TAG);
+
+ //
+ // Add the device to our cache.
+ //
+ status = WdfCollectionAdd(factoryCtx->Cache, Device);
+ }
+ }
+
+ WdfWaitLockRelease(factoryCtx->CacheLock);
+
+ return status;
+}
+
+PAGED_CODE_SEG
+WDFDEVICE
+Dsp_RemoveChildDeviceFromCache(
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ const GUID * UniqueId
+ )
+{
+ PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory);
+ WDFDEVICE child = NULL;
+ ULONG count;
+
+ PAGED_CODE();
+
+ WdfWaitLockAcquire(factoryCtx->CacheLock, NULL);
+
+ count = WdfCollectionGetCount(factoryCtx->Cache);
+
+ for (ULONG i = 0; i < count; i++)
+ {
+ PDSP_DEVICEID_CONTEXT idCtx = NULL;
+ WDFDEVICE device = NULL;
+
+ device = (WDFDEVICE)WdfCollectionGetItem(factoryCtx->Cache, i);
+ idCtx = GetDspDeviceIdContext(device);
+
+ if ((idCtx != 0) && IsEqualGUID(idCtx->UniqueID, *UniqueId))
+ {
+ // Found it.
+ // - zero out ID.
+ // - add a ref for the caller.
+ // - remove the item from the cache.
+ idCtx->UniqueID = NULL_GUID;
+ WdfObjectReferenceWithTag(device, (PVOID)DRIVER_TAG);
+ WdfCollectionRemoveItem(factoryCtx->Cache, i);
+ child = device;
+ break;
+ }
+ }
+
+ WdfWaitLockRelease(factoryCtx->CacheLock);
+
+ return child;
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_PurgeChildDeviceFromCache(
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ WDFDEVICE Device
+ )
+{
+ PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory);
+ PDSP_DEVICEID_CONTEXT idCtx = GetDspDeviceIdContext(Device);
+ WDFDEVICE child = NULL;
+
+ PAGED_CODE();
+
+ WdfWaitLockAcquire(factoryCtx->CacheLock, NULL);
+
+ //
+ // Scan the cache only if the device's unique-id is not null.
+ //
+ if (idCtx != NULL && !IsEqualGUID(NULL_GUID, idCtx->UniqueID))
+ {
+ ULONG count = WdfCollectionGetCount(factoryCtx->Cache);
+
+ for (ULONG i = 0; i < count; i++)
+ {
+ child = (WDFDEVICE)WdfCollectionGetItem(factoryCtx->Cache, i);
+ if (child == Device)
+ {
+ //
+ // Found it.
+ // - zero out ID.
+ // - remove the item from the cache.
+ //
+ idCtx->UniqueID = NULL_GUID;
+ WdfCollectionRemoveItem(factoryCtx->Cache, i);
+ break;
+ }
+ }
+ }
+
+ WdfWaitLockRelease(factoryCtx->CacheLock);
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_EvtDeviceIdContextCleanup(
+ _In_ WDFOBJECT Device
+ )
+{
+ PDSP_DEVICEID_CONTEXT idCtx = GetDspDeviceIdContext(Device);
+
+ PAGED_CODE();
+
+ Dsp_PurgeChildDeviceFromCache(idCtx->Factory, (WDFDEVICE)Device);
+ WdfObjectDereferenceWithTag(idCtx->Factory, (PVOID)DRIVER_TAG);
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_EvtFactoryCircuitRemoveCircuitCallback
+(
+ _In_ WDFOBJECT Object,
+ _In_ WDFREQUEST Request
+ )
+{
+ NTSTATUS status = STATUS_NOT_SUPPORTED;
+ ACXFACTORYCIRCUIT factory = (ACXFACTORYCIRCUIT)Object;
+ PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(factory);
+ WDFDEVICE child = NULL;
+ PACX_FACTORY_CIRCUIT_REMOVE_CIRCUIT args;
+ ULONG argsCb = sizeof(ACX_FACTORY_CIRCUIT_REMOVE_CIRCUIT);
+ ACX_REQUEST_PARAMETERS params;
+
+ PAGED_CODE();
+
+ ACX_REQUEST_PARAMETERS_INIT(&params);
+ AcxRequestGetParameters(Request, &params);
+
+ ASSERT(params.Type == AcxRequestTypeMethod);
+ ASSERT(params.Parameters.Method.Verb == AcxMethodVerbSend);
+ ASSERT(params.Parameters.Method.ArgsCb >= argsCb);
+
+ args = (PACX_FACTORY_CIRCUIT_REMOVE_CIRCUIT)params.Parameters.Method.Args;
+ argsCb = params.Parameters.Method.ArgsCb; // use real value.
+
+ if (args->Size < argsCb)
+ {
+ status = STATUS_INVALID_PARAMETER;
+ DrvLogError(g_SDCAVDspLog, FLAG_GENERIC,
+ "ACX_FACTORY_CIRCUIT_REMOVE_CIRCUIT.Size %d is invalid, it should be >= %d, %!STATUS!",
+ args->Size, argsCb, status);
+ goto exit;
+ }
+
+ //
+ // Remove the circut/circuit-device.
+ // If found, there is a pending WDF ref on the object.
+ //
+ child = Dsp_RemoveChildDeviceFromCache(factory, &args->CircuitUniqueId);
+ if (child == NULL)
+ {
+ // Device is gone. Nothing to do.
+ status = STATUS_SUCCESS;
+ goto exit;
+ }
+
+ //
+ // Tell ACX not to enum this child device anymore.
+ //
+ status = AcxDeviceRemoveCircuitDevice(factoryCtx->Device, child);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(g_SDCAVDspLog, FLAG_GENERIC,
+ "Parent %p, ACXFACTORYCIRCUIT %p, Child %p, AcxDeviceRemoveCircuitDevice failed, %!STATUS!",
+ factoryCtx->Device, factory, child, status);
+ goto exit;
+ }
+
+ status = STATUS_SUCCESS;
+
+exit:
+ if (child != NULL)
+ {
+ WdfObjectDereferenceWithTag(child, (PVOID)DRIVER_TAG);
+ }
+
+ WdfRequestComplete(Request, status);
+}
+
+PAGED_CODE_SEG
+VOID
+Dsp_EvtFactoryRemoveCircuitRequestPreprocess(
+ _In_ ACXOBJECT Object,
+ _In_ ACXCONTEXT DriverContext,
+ _In_ WDFREQUEST Request
+)
+/*++
+
+Routine Description:
+
+ This function is an example of a preprocess routine.
+
+--*/
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(DriverContext);
+ ASSERT(Object);
+ ASSERT(Request);
+
+ Dsp_EvtFactoryCircuitRemoveCircuitCallback(Object, Request);
+}
+#endif //ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/driver.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/driver.cpp
new file mode 100644
index 00000000..98cc6d73
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/driver.cpp
@@ -0,0 +1,172 @@
+/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+Module Name:
+
+ Driver.cpp
+
+Abstract:
+
+ Sample Soundwire DSP Driver.
+
+Environment:
+
+ Kernel mode only
+
+--*/
+
+#include "private.h"
+#include "trace.h"
+
+#ifndef __INTELLISENSE__
+#include "driver.tmh"
+#endif
+
+RECORDER_LOG g_SDCAVDspLog{ nullptr };
+
+PAGED_CODE_SEG
+void Dsp_DriverUnload (_In_ WDFDRIVER Driver)
+{
+ PAGED_CODE();
+
+ if (!Driver)
+ {
+ return;
+ }
+
+ if (g_RegistryPath.Buffer != NULL)
+ {
+ ExFreePool(g_RegistryPath.Buffer);
+ RtlZeroMemory(&g_RegistryPath, sizeof(g_RegistryPath));
+ }
+
+ WPP_CLEANUP(WdfDriverWdmGetDriverObject(Driver));
+
+ return;
+}
+
+INIT_CODE_SEG
+NTSTATUS
+DriverEntry(
+ _In_ PDRIVER_OBJECT DriverObject,
+ _In_ PUNICODE_STRING RegistryPath
+ )
+/*++
+
+Routine Description:
+ DriverEntry initializes the driver and is the first routine called by the
+ system after the driver is loaded.
+
+Parameters Description:
+
+ DriverObject - represents the instance of the function driver that is loaded
+ into memory. DriverEntry must initialize members of DriverObject before it
+ returns to the caller. DriverObject is allocated by the system before the
+ driver is loaded, and it is released by the system after the system unloads
+ the function driver from memory.
+
+ RegistryPath - represents the driver specific path in the Registry.
+ The function driver can use the path to store driver related data between
+ reboots. The path does not store hardware instance specific data.
+
+Return Value:
+
+ STATUS_SUCCESS if successful,
+ STATUS_UNSUCCESSFUL otherwise.
+
+--*/
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ WPP_INIT_TRACING(DriverObject, RegistryPath);
+
+ auto exit = scope_exit([&status, &DriverObject]() {
+ if (!NT_SUCCESS(status))
+ {
+ if (g_RegistryPath.Buffer != NULL)
+ {
+ ExFreePool(g_RegistryPath.Buffer);
+ RtlZeroMemory(&g_RegistryPath, sizeof(g_RegistryPath));
+ }
+
+ WPP_CLEANUP(DriverObject);
+ }
+ else
+ {
+ DrvLogInfo(g_SDCAVDspLog, FLAG_INIT, "ACX SDCA Virtual DSP Driver Init complete, %!STATUS!", status);
+ }
+ });
+
+ RETURN_NTSTATUS_IF_FAILED(CopyRegistrySettingsPath(RegistryPath));
+
+ //
+ // Initiialize driver config to control the attributes that
+ // are global to the driver. Note that framework by default
+ // provides a driver unload routine. If you create any resources
+ // in the DriverEntry and want to be cleaned in driver unload,
+ // you can override that by manually setting the EvtDriverUnload in the
+ // config structure. In general xxx_CONFIG_INIT macros are provided to
+ // initialize most commonly used members.
+ //
+
+ WDF_DRIVER_CONFIG wdfCfg;
+ WDF_DRIVER_CONFIG_INIT(&wdfCfg, Dsp_EvtBusDeviceAdd);
+ wdfCfg.EvtDriverUnload = Dsp_DriverUnload;
+
+ //
+ // Add a driver context. (for illustration purposes only).
+ //
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_DRIVER_CONTEXT);
+
+ //
+ // Create a framework driver object to represent our driver.
+ //
+ WDFDRIVER driver;
+ RETURN_NTSTATUS_IF_FAILED(WdfDriverCreate(
+ DriverObject,
+ RegistryPath,
+ &attributes, // Driver Attributes
+ &wdfCfg, // Driver Config Info
+ &driver // hDriver
+ ));
+
+ RECORDER_CONFIGURE_PARAMS recorderConfig;
+ RECORDER_CONFIGURE_PARAMS_INIT(&recorderConfig);
+ recorderConfig.CreateDefaultLog = FALSE;
+ WppRecorderConfigure(&recorderConfig);
+
+ RECORDER_LOG_CREATE_PARAMS recorderLogCreateParams;
+ RECORDER_LOG_CREATE_PARAMS_INIT(&recorderLogCreateParams, NULL);
+ recorderLogCreateParams.TotalBufferSize = WPP_TOTAL_BUFFER_SIZE;
+ recorderLogCreateParams.ErrorPartitionSize = WPP_ERROR_PARTITION_SIZE;
+
+ RtlStringCbPrintfA(recorderLogCreateParams.LogIdentifier,
+ RECORDER_LOG_IDENTIFIER_MAX_CHARS,
+ "SDCAVDsp");
+
+ RECORDER_LOG logHandle = NULL;
+ status = WppRecorderLogCreate(&recorderLogCreateParams, &logHandle);
+ if (!NT_SUCCESS(status))
+ {
+ logHandle = NULL;
+
+ // Non fatal failure
+ status = STATUS_SUCCESS;
+ }
+
+ g_SDCAVDspLog = logHandle;
+
+ //
+ // Post init.
+ //
+ ACX_DRIVER_CONFIG acxCfg;
+ ACX_DRIVER_CONFIG_INIT(&acxCfg);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDriverInitialize(driver, &acxCfg));
+
+ return status;
+}
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp
new file mode 100644
index 00000000..c86af13a
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp
@@ -0,0 +1,606 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ offloadStreamEngine.cpp
+
+Abstract:
+
+ Virtual Streaming Engine - this module controls offload streaming logic for
+ the device.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include <ks.h>
+#include <mmsystem.h>
+#include <ksmedia.h>
+#include "offloadStreamEngine.h"
+
+#ifndef __INTELLISENSE__
+#include "offloadStreamEngine.tmh"
+#endif
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+COffloadStreamEngine::COffloadStreamEngine(
+ ACXSTREAM Stream,
+ ACXDATAFORMAT StreamFormat,
+ CSimPeakMeter *circuitPeakmeter
+) :CStreamEngine(Stream, StreamFormat, circuitPeakmeter)
+{
+ PAGED_CODE();
+
+ m_BufferReadTimer = NULL;
+ m_LastBufferTimer = NULL;
+ m_PacketsWritten = 0;
+ m_PacketsRead = 0;
+ m_SinglePacketPosition = 0;
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+COffloadStreamEngine::~COffloadStreamEngine()
+{
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+COffloadStreamEngine::PrepareHardware()
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+ RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware());
+ // CStreamEngine::PrepareHardware will update state to Pause, but we don't
+ // want to be in Pause state if any of the below actions fail.
+ m_CurrentState = AcxStreamStateStop;
+
+ //
+ // Buffer read callbacks
+ //
+ WDF_TIMER_CONFIG timerConfig;
+ LONG period = (LONG)((ULONGLONG)m_PacketSize * HNS_PER_SEC / (ULONGLONG)GetBytesPerSecond());
+ WDF_TIMER_CONFIG_INIT_PERIODIC(
+ &timerConfig,
+ COffloadStreamEngine::s_EvtBufferReadTimerCallback,
+ period / HNSTIME_PER_MILLISECOND
+ );
+ timerConfig.UseHighResolutionTimer = WdfTrue;
+
+ WDF_OBJECT_ATTRIBUTES timerAttributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&timerAttributes, STREAM_TIMER_CONTEXT);
+ timerAttributes.ParentObject = m_Stream;
+
+ RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate(
+ &timerConfig,
+ &timerAttributes,
+ &m_BufferReadTimer
+ ));
+
+ auto bt_free = scope_exit([this]() {
+ WdfObjectDelete(m_BufferReadTimer);
+ m_BufferReadTimer = NULL;
+ });
+
+ PSTREAM_TIMER_CONTEXT timerCtx;
+ timerCtx = GetStreamTimerContext(m_BufferReadTimer);
+ timerCtx->StreamEngine = this;
+
+ //
+ // Last Buffer read callback
+ //
+ WDF_TIMER_CONFIG_INIT(
+ &timerConfig,
+ COffloadStreamEngine::s_EvtLastBufferTimerCallback
+ );
+ timerConfig.UseHighResolutionTimer = WdfTrue;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&timerAttributes, STREAM_TIMER_CONTEXT);
+ timerAttributes.ParentObject = m_Stream;
+
+ RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate(
+ &timerConfig,
+ &timerAttributes,
+ &m_LastBufferTimer
+ ));
+
+ auto lbt_free = scope_exit([this]() {
+ WdfObjectDelete(m_LastBufferTimer);
+ m_LastBufferTimer = NULL;
+ });
+
+ timerCtx = GetStreamTimerContext(m_LastBufferTimer);
+ timerCtx->StreamEngine = this;
+
+ RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetDataFormat((PKSDATAFORMAT)AcxDataFormatGetKsDataFormat(m_StreamFormat)));
+
+ RETURN_NTSTATUS_IF_FAILED(m_SaveData.Initialize(TRUE));
+
+ RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetMaxWriteSize(m_PacketSize * m_PacketsCount * MAX_FILE_WRITE_FRAMES));
+
+ m_CurrentState = AcxStreamStatePause;
+
+ bt_free.release();
+ lbt_free.release();
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+COffloadStreamEngine::ReleaseHardware()
+{
+ PAGED_CODE();
+
+ m_SaveData.WaitAllWorkItems();
+ m_SaveData.Cleanup();
+
+ if (m_BufferReadTimer)
+ {
+ WdfTimerStop(m_BufferReadTimer, TRUE);
+ WdfObjectDelete(m_BufferReadTimer);
+ m_BufferReadTimer = NULL;
+ }
+
+ if (m_LastBufferTimer)
+ {
+ WdfTimerStop(m_LastBufferTimer, TRUE);
+ WdfObjectDelete(m_LastBufferTimer);
+ m_LastBufferTimer = NULL;
+ }
+
+ m_LinearBufferClock.Stop();
+
+ m_PacketsWritten = 0;
+ m_PacketsRead = 0;
+
+ CStreamEngine::ReleaseHardware();
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+COffloadStreamEngine::Run()
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Run");
+
+ if (m_CurrentState != AcxStreamStatePause)
+ {
+ status = STATUS_INVALID_STATE_TRANSITION;
+ return status;
+ }
+
+ ULONGLONG bytesPerSec = GetBytesPerSecond();
+
+ ULONGLONG elapsedTimeWhenPaused = m_LinearBufferClock.GetElapsedTime(NULL);
+ if (elapsedTimeWhenPaused)
+ {
+ // Stream has resumed from pause
+ // Calculate remaining buffer for next notification
+
+ // Remaining buffer from when stream was paused
+ // Hardware might have cycled more than the bytes written
+ // This can happen if there was a glitch and hardware was
+ // starved
+ ULONGLONG packetTime = (ULONGLONG)m_PacketSize * HNS_PER_SEC / bytesPerSec;
+ LONG remainingTime = (LONG)((ULONGLONG)elapsedTimeWhenPaused % packetTime);
+ WdfTimerStart(m_BufferReadTimer, WDF_REL_TIMEOUT_IN_MS(remainingTime / HNSTIME_PER_MILLISECOND));
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Run - Notification Timer Started - first timeout :%d ms", (ULONG)(remainingTime / HNSTIME_PER_MILLISECOND));
+ }
+ else
+ {
+ // Run has been called first time on this stream
+ LONG period = (LONG)((ULONGLONG)m_PacketSize * HNS_PER_SEC / (ULONGLONG)bytesPerSec);
+ WdfTimerStart(m_BufferReadTimer, WDF_REL_TIMEOUT_IN_MS(period / HNSTIME_PER_MILLISECOND));
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Run - Notification Timer Started - first timeout :%d ms", (ULONG)(period / HNSTIME_PER_MILLISECOND));
+ }
+
+ m_LinearBufferClock.Run();
+ m_CurrentState = AcxStreamStateRun;
+
+ m_PeakMeter.StartStream();
+ m_pCircuitPeakmeter->StartStream();
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+COffloadStreamEngine::Pause()
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Pause - from %d", m_CurrentState);
+
+ RETURN_NTSTATUS_IF_TRUE(m_CurrentState != AcxStreamStateRun, STATUS_INVALID_STATE_TRANSITION);
+
+ WdfTimerStop(m_BufferReadTimer, TRUE);
+
+ m_LinearBufferClock.Pause();
+
+ m_PeakMeter.StopStream();
+ m_pCircuitPeakmeter->StopStream();
+
+ m_CurrentState = AcxStreamStatePause;
+
+ return status;
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+NTSTATUS
+COffloadStreamEngine::GetPresentationPosition(
+ PULONGLONG PositionInBlocks,
+ PULONGLONG QPCPosition
+)
+{
+ DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::GetPresentationPosition");
+
+ ULONG blockAlign;
+ LARGE_INTEGER qpc;
+
+ blockAlign = AcxDataFormatGetBlockAlign(m_StreamFormat);
+ qpc = KeQueryPerformanceCounter(NULL);
+
+ ULONGLONG streamPosition = m_LinearBufferClock.GetElapsedTime(NULL);
+
+ // Simulate Presentation position lag by 20 ms
+ if (streamPosition > (OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS * HNSTIME_PER_MILLISECOND))
+ {
+ streamPosition -= (OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS * HNSTIME_PER_MILLISECOND);
+ }
+ else
+ {
+ streamPosition = 0;
+ }
+
+ *PositionInBlocks = (streamPosition * GetBytesPerSecond() / HNS_PER_SEC) / blockAlign;
+
+ *QPCPosition = (ULONGLONG)qpc.QuadPart;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+NTSTATUS
+COffloadStreamEngine::GetLinearBufferPosition(
+ PULONGLONG Position
+)
+{
+ DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::GetLinearBufferPosition");
+
+ ULONGLONG bytesPerSecond = GetBytesPerSecond();
+ ULONGLONG currentTime = m_LinearBufferClock.GetElapsedTime(NULL);
+
+ // Update position
+ *Position = currentTime * bytesPerSecond / HNS_PER_SEC;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+COffloadStreamEngine::SetCurrentWritePosition(
+ ULONG Position
+)
+{
+ PAGED_CODE();
+
+ if (m_PacketsCount == 1)
+ {
+ return SetCurrentWritePositionSinglePacket(Position);
+ }
+
+ // Determine buffer
+ // Designed for ping-pong
+ // Position == m_PacketSize, ping buffer was just filled
+ // Position == m_PacketSize * 2, pong buffer was just filled
+ ULONG packetIndex = Position == m_PacketSize ? 0 : 1;
+
+ DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::SetCurrentWritePosition, Position = 0x%08x, PacketIndex :%d", Position, packetIndex);
+
+ //
+ // Detect if the packet just written is incorrect
+ //
+ ULONG packetsRead = (ULONG)InterlockedCompareExchange((LONG *)&m_PacketsRead, -1, -1);
+
+ if (m_CurrentState == AcxStreamStateRun)
+ {
+ ULONG expectedPacketIndex = (packetsRead % 2) ? 0 : 1;
+ if (packetIndex != expectedPacketIndex)
+ {
+ DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine incorrect packet write: %d, Packets Read: %08d", packetIndex, packetsRead);
+ // \TODO: ACX doesn't recover from this error
+ // Continuing automatically recovers with next call
+ //return STATUS_DATA_OVERRUN;
+ }
+ }
+
+ //
+ // Catch up to packets read + 1
+ // This is to recover from condition when OS was not writing enough data
+ //
+ (ULONG)InterlockedExchange((LONG *)&m_PacketsWritten, packetsRead + 1);
+
+ PBYTE packetBuffer = NULL;
+ packetBuffer = (PBYTE)m_Packets[packetIndex];
+ // Packet 0 starts at an offset if the size isn't a multiple of page_size
+ if (packetIndex == 0)
+ {
+ packetBuffer += m_FirstPacketOffset;
+ }
+
+ m_SaveData.WriteData(packetBuffer, m_PacketSize);
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+COffloadStreamEngine::SetCurrentWritePositionSinglePacket(
+ ULONG Position
+)
+{
+ PAGED_CODE();
+
+ // Offload streams are almost always 2-packet. However, it is possible to create an offload stream
+ // as a timer-driven (single packet) stream. This code will ensure correct behavior in this case.
+ ULONG packetIndex = 0;
+
+ DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::SetCurrentWritePositionSinglePacket, Position = 0x%08x, PacketIndex :%d", Position, packetIndex);
+
+ //
+ // Detect if the packet just written is incorrect
+ //
+ ULONG packetsRead = (ULONG)InterlockedCompareExchange((LONG *)&m_PacketsRead, -1, -1);
+
+ // Position has wrapped, can increment the written count.
+ if (Position < m_SinglePacketPosition)
+ {
+ //
+ // Catch up to packets read + 1
+ // This is to recover from condition when OS was not writing enough data
+ //
+ (ULONG)InterlockedExchange((LONG*)&m_PacketsWritten, packetsRead + 1);
+ }
+
+ PBYTE packetBuffer = NULL;
+ packetBuffer = (PBYTE)m_Packets[packetIndex];
+ // Packet 0 starts at an offset if the size isn't a multiple of page_size
+ // For single-packet the offset should be 0.
+ packetBuffer += m_FirstPacketOffset;
+
+ // AudioKSE adds 1 to the position
+ Position -= 1;
+ Position %= m_PacketSize;
+
+ if (Position <= m_SinglePacketPosition)
+ {
+ // Handle the case of wraparound by copying from the last position to the end of the buffer
+ m_SaveData.WriteData(packetBuffer + m_SinglePacketPosition, m_PacketSize - m_SinglePacketPosition);
+ m_SinglePacketPosition = 0;
+ }
+ // Write from the last position (0 in the case of wraparound) to the new Position
+ m_SaveData.WriteData(packetBuffer + m_SinglePacketPosition, Position);
+ m_SinglePacketPosition = Position;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+COffloadStreamEngine::SetLastBufferPosition(
+ ULONG Position
+)
+{
+ PAGED_CODE();
+
+ ULONG bytesPerSec = GetBytesPerSecond();
+
+ // Determine buffer
+ ULONG packetIndex = Position < m_PacketSize ? 0 : 1;
+ ULONG lastBufferSize = Position <= m_PacketSize ? Position : Position - m_PacketSize;
+
+ // time for rendering last buffer
+ ULONGLONG lastBufferTime = (ULONGLONG)lastBufferSize * HNS_PER_SEC / (ULONGLONG)bytesPerSec;
+
+ ULONGLONG totalStreamTime = (ULONGLONG)m_PacketsWritten* (ULONGLONG)m_PacketSize* HNS_PER_SEC / (ULONGLONG)bytesPerSec;
+ totalStreamTime += (ULONGLONG)lastBufferTime;
+
+ // Simulate Presentation position lag by 20 ms
+ ULONGLONG presentationTime = m_LinearBufferClock.GetElapsedTime(NULL) - (OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS * HNSTIME_PER_MILLISECOND);
+
+ lastBufferTime = totalStreamTime - presentationTime;
+
+ //
+ // Start last buffer timer
+ //
+ RETURN_NTSTATUS_IF_TRUE_MSG(NULL == m_LastBufferTimer, STATUS_INVALID_PARAMETER, L"Set Last Buffer Position called out of sequence - without calling prepare hardware");
+ WdfTimerStart(m_LastBufferTimer, WDF_REL_TIMEOUT_IN_MS(lastBufferTime / HNSTIME_PER_MILLISECOND));
+
+ PBYTE packetBuffer = NULL;
+ packetBuffer = (PBYTE)m_Packets[packetIndex];
+ // Packet 0 starts at an offset if the size isn't a multiple of page_size
+ if (packetIndex == 0)
+ {
+ packetBuffer += m_FirstPacketOffset;
+ }
+
+ m_SaveData.WriteData(packetBuffer, lastBufferSize);
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+COffloadStreamEngine::AssignDrmContentId(
+ ULONG,
+ PACXDRMRIGHTS DrmRights
+)
+{
+ PAGED_CODE();
+
+ //
+ // At this point the driver should enforce the new DrmRights.
+ // The sample driver handles DrmRights per stream basis, and
+ // stops writing the stream to disk, if CopyProtect = TRUE.
+ //
+ // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP.
+ // Loopback: if CopyProtect is true, disable loopback stream.
+ //
+
+ //
+ // Sample writes each stream seperately to disk. If the rights for this
+ // stream indicates that the stream is CopyProtected, stop writing to disk.
+ //
+ m_SaveData.Disable(DrmRights->CopyProtect);
+
+ //
+ // From MSDN:
+ //
+ // This sample doesn't forward protected content, but if your driver uses
+ // lower layer drivers or a different stack to properly work, please see the
+ // following info from MSDN:
+ //
+ // "Before allowing protected content to flow through a data path, the system
+ // verifies that the data path is secure. To do so, the system authenticates
+ // each module in the data path beginning at the upstream end of the data path
+ // and moving downstream. As each module is authenticated, that module gives
+ // the system information about the next module in the data path so that it
+ // can also be authenticated. To be successfully authenticated, a module's
+ // binary file must be signed as DRM-compliant.
+ //
+ // Two adjacent modules in the data path can communicate with each other in
+ // one of several ways. If the upstream module calls the downstream module
+ // through IoCallDriver, the downstream module is part of a WDM driver. In
+ // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject
+ // function to provide the system with the device object representing the
+ // downstream module. (If the two modules communicate through the downstream
+ // module's content handlers, the upstream module calls AcxDrmAddContentHandlers
+ // instead.)
+ //
+ // For more information, see MSDN's DRM Functions and Interfaces.
+ //
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+COffloadStreamEngine::s_EvtBufferReadTimerCallback(
+ WDFTIMER Timer
+)
+{
+ COffloadStreamEngine * This;
+ PSTREAM_TIMER_CONTEXT timerCtx;
+
+ // Get our stream engine pointer from the timer context
+ timerCtx = GetStreamTimerContext(Timer);
+ This = (COffloadStreamEngine *)timerCtx->StreamEngine;
+
+ // Call the BufferReadCallback for the engine
+ This->BufferReadCallback();
+}
+
+// Callback indicating buffer read complete
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+COffloadStreamEngine::BufferReadCallback()
+{
+ // Save the time at which we moved to the next packet
+ ULONGLONG qpcCompleted;
+ qpcCompleted = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart;
+
+ ULONG packetsWritten = (ULONG)InterlockedCompareExchange((LONG*)&m_PacketsWritten, -1, -1);
+
+ // We've completed a packet! Increment our currently active packet
+ ULONG packetsRead = (ULONG)InterlockedIncrement((LONG *)&m_PacketsRead) - 1;
+
+ //
+ // \TODO
+ // Detect if hardware has cycled more than the OS.
+ // Can happen if application doesn't write data on time
+ //
+ if(packetsRead > packetsWritten)
+ {
+ DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine starved PacketsWritten: %08d, Packets Read: %08d", packetsWritten, packetsRead);
+ }
+
+ // Tell ACX we've completed the packet.
+ // 0 based packet count
+ (void)AcxRtStreamNotifyPacketComplete(m_Stream, (ULONGLONG)packetsRead, qpcCompleted);
+ DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::BufferReadCallback packet complete - %d", packetsRead);
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+COffloadStreamEngine::s_EvtLastBufferTimerCallback(
+ WDFTIMER Timer
+)
+{
+ COffloadStreamEngine * This;
+ PSTREAM_TIMER_CONTEXT timerCtx;
+
+ // Get our stream engine pointer from the timer context
+ timerCtx = GetStreamTimerContext(Timer);
+ This = (COffloadStreamEngine *)timerCtx->StreamEngine;
+
+ // Call the LastBufferRenderComplete for the engine
+ This->LastBufferRenderComplete();
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+COffloadStreamEngine::LastBufferRenderComplete()
+{
+ // Save the time at which we moved to the next packet
+ ULONGLONG qpcCompleted;
+ qpcCompleted = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart;
+
+ ULONGLONG completedPacket;
+ completedPacket = (ULONG)InterlockedIncrement((LONG*)&m_PacketsRead) - 1;
+
+ // Tell ACX we've completed the packet.
+ (void)AcxRtStreamNotifyPacketComplete(m_Stream, completedPacket, qpcCompleted);
+ DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::LastBufferRenderComplete packet complete - %d", (ULONG)completedPacket);
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+COffloadStreamEngine::ProcessPacket()
+{
+}
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.h
new file mode 100644
index 00000000..4c325dda
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.h
@@ -0,0 +1,172 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ offloadStreamEngine.h
+
+Abstract:
+
+ Virtual Streaming Engine - this module controls offload streaming logic for
+ the device.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#pragma once
+
+#include "streamengine.h"
+#include "PositionSimClock.h"
+
+#define MAX_FILE_WRITE_FRAMES (16)
+#define OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS (20)
+
+class COffloadStreamEngine : public CStreamEngine
+{
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ COffloadStreamEngine(
+ _In_ ACXSTREAM Stream,
+ _In_ ACXDATAFORMAT StreamFormat,
+ _In_ CSimPeakMeter *circuitPeakmeter
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ #pragma code_seg()
+ ~COffloadStreamEngine();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ PrepareHardware();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ReleaseHardware();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ Run();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ Pause();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ #pragma code_seg()
+ NTSTATUS
+ GetPresentationPosition(
+ _Out_ PULONGLONG PositionInBlocks,
+ _Out_ PULONGLONG QPCPosition
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AssignDrmContentId(
+ _In_ ULONG DrmContentId,
+ _In_ PACXDRMRIGHTS DrmRights
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ #pragma code_seg()
+ NTSTATUS
+ GetLinearBufferPosition(
+ _Out_ PULONGLONG Position
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ SetCurrentWritePosition(
+ _In_ ULONG Position
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ SetLastBufferPosition(
+ _In_ ULONG Position
+ );
+
+protected:
+ WDFTIMER m_BufferReadTimer;
+ WDFTIMER m_LastBufferTimer;
+
+ CPositionSimClock m_LinearBufferClock;
+
+ CSaveData m_SaveData;
+
+ // Number of packets written by OS
+ ULONG m_PacketsWritten;
+
+ // Number of packets read by hardware
+ ULONG m_PacketsRead;
+
+ ULONG m_SinglePacketPosition;
+
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ ProcessPacket();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ SetCurrentWritePositionSinglePacket(
+ _In_ ULONG Position
+ );
+
+ static
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ _Function_class_(EVT_WDF_TIMER)
+ #pragma code_seg()
+ VOID s_EvtBufferReadTimerCallback(
+ _In_ WDFTIMER Timer
+ );
+
+ static
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ _Function_class_(EVT_WDF_TIMER)
+ #pragma code_seg()
+ VOID s_EvtLastBufferTimerCallback(
+ _In_ WDFTIMER Timer
+ );
+
+ // Callback indicating buffer read complete
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ BufferReadCallback();
+
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ LastBufferRenderComplete();
+};
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/private.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/private.h
new file mode 100644
index 00000000..065b3375
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/private.h
@@ -0,0 +1,803 @@
+/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ private.h
+
+Abstract:
+
+ Contains structure definitions and function prototypes private to
+ the driver.
+
+Environment:
+
+ Kernel mode
+
+Notes:
+
+ Workarounds:
+
+ ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+ enables the logic to workaround an ACX v1.0 issue related to ACXFACTORYCIRCUIT race with Add/Remove child WDFDEVICE.
+ this issue has been fixed in ACX v1.1.
+
+ ACX_WORKAROUND_ACXPIN_01
+ enables the logic to workaround an ACX v1.0 issue related to ACXPIN's KSPROPERTY_PIN_CINSTANCES to return the pin's
+ stream instances vs. returning the total # of streams on a circuit. This # is not the same when circuit supports an
+ audio engine node, and client has instantiated streams on several pins (host/loopback/offload).
+ this issue has been fixed in ACX v1.1.
+
+ ACX_WORKAROUND_ACXPIN_02
+ enables the logic to workaround an ACX v1.1 issue related to ACXPIN's KSPROPERTY_PIN_PROPOSEDATAFORMAT set requests
+ directed to an 'offload' pin of an audio engine. The workaround fails the request if there are no enough resources
+ (streams). ACX will be enhanced in the future to automatically check this when the pin is tagged as 'offload' pin.
+ ACX_WORKAROUND_ACXPIN_01 must be enabled as well for ACX_WORKAROUND_ACXPIN_02 to work.
+
+--*/
+
+#ifndef _PRIVATE_H_
+#define _PRIVATE_H_
+
+#include "cpp_utils.h"
+
+#include "stdunk.h"
+#include <mmsystem.h>
+#include <ks.h>
+#include <ksmedia.h>
+
+#include "NewDelete.h"
+
+/* make prototypes usable from C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#pragma warning(disable:4200) //
+#pragma warning(disable:4201) // nameless struct/union
+#pragma warning(disable:4214) // bit field types other than int
+
+#include <initguid.h>
+#include <ntddk.h>
+#include <ntstrsafe.h>
+#include <ntintsafe.h>
+#include <TestProperties.h>
+
+#pragma warning(default:4200)
+#pragma warning(default:4201)
+#pragma warning(default:4214)
+
+#include <wdf.h>
+#include <acx.h>
+
+#include "AudioAggregation.h"
+#include "soundwirecontroller.h"
+#include "sdcastreaming.h"
+
+#include "trace.h"
+
+#define PAGED_CODE_SEG __declspec(code_seg("PAGE"))
+#define INIT_CODE_SEG __declspec(code_seg("INIT"))
+
+extern RECORDER_LOG g_SDCAVDspLog;
+
+// Check for workaround dependencies.
+#ifdef ACX_WORKAROUND_ACXPIN_02
+ #ifndef ACX_WORKAROUND_ACXPIN_01
+ #error ACX_WORKAROUND_ACXPIN_02 requires ACX_WORKAROUND_ACXPIN_01.
+ #endif
+#endif
+
+// Copied from cfgmgr32.h
+#if !defined(MAX_DEVICE_ID_LEN)
+#define MAX_DEVICE_ID_LEN 200
+#endif
+
+// Define a NULL GUID if not already defined.
+#if !defined(NULL_GUID)
+#define NULL_GUID { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } }
+#endif
+
+// SDCA Sample driver
+
+#define DRIVER_TAG (ULONG) 'Dcds'
+
+// Number of millisecs per sec.
+#define MS_PER_SEC 1000
+
+// Number of hundred nanosecs per sec.
+#define HNS_PER_SEC 10000000
+
+// Compatible ID for render/capture
+#define ACX_DSP_RENDER_COMPATIBLE_ID L"{ad164f4d-4149-41ed-82e8-99732ed7371a}"
+
+// Container ID for render/capture
+#define ACX_DSP_SYSTEM_CONTAINER_ID L"{00000000-0000-0000-ffff-ffffffffffff}"
+
+
+// Compatible ID for render/capture
+#define ACX_DSP_TEST_COMPATIBLE_ID L"{ad164f4d-4149-41ed-82e8-99732ed7371a}"
+// Container ID for render/capture
+#define ACX_DSP_TEST_CONTAINER_ID L"{00000000-0000-0000-ffff-ffffffffffff}"
+
+extern const GUID DSP_CIRCUIT_SPEAKER_GUID;
+extern const GUID DSP_CIRCUIT_MICROPHONE_GUID;
+extern const GUID DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID;
+extern const GUID DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID;
+
+extern const GUID SYSTEM_CONTAINER_GUID;
+
+#undef MIN
+#undef MAX
+#define MIN(a,b) ((a) > (b) ? (b) : (a))
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+
+#define REQUEST_TIMEOUT_SECONDS 5
+
+#ifndef SIZEOF_ARRAY
+#define SIZEOF_ARRAY(ar) (sizeof(ar)/sizeof((ar)[0]))
+#endif // !defined(SIZEOF_ARRAY)
+
+//
+// Define DSP driver context.
+//
+typedef struct _DSP_DRIVER_CONTEXT {
+ BOOLEAN Dummy;
+} DSP_DRIVER_CONTEXT, *PDSP_DRIVER_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_DRIVER_CONTEXT, GetDspDriverContext)
+
+#define ALL_CHANNELS_ID UINT32_MAX
+#define MAX_CHANNELS 2
+#define CHANNEL_MASK_INVALID UINT32_MAX
+
+//
+// Define DSP device context.
+//
+typedef struct _DSP_DEVICE_CONTEXT {
+ ACXCIRCUIT Render;
+ ACXCIRCUIT Capture;
+ ACXFACTORYCIRCUIT Factory;
+ WDFDEVICE AudioSensorsDevice;
+ WDFCHILDLIST ChildList;
+} DSP_DEVICE_CONTEXT, *PDSP_DEVICE_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_DEVICE_CONTEXT, GetDspDeviceContext)
+
+//
+// Define Audio Sensors device context.
+//
+typedef struct _AUDIO_SENSORS_DEVICE_CONTEXT
+{
+ WDFDEVICE Device;
+} AUDIO_SENSORS_DEVICE_CONTEXT, *PAUDIO_SENSORS_DEVICE_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(AUDIO_SENSORS_DEVICE_CONTEXT, GetAudioSensorsDeviceContext)
+
+//
+// Define DSP factory context.
+//
+typedef struct _DSP_FACTORY_CONTEXT {
+ WDFDEVICE Device;
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+ WDFWAITLOCK CacheLock;
+ WDFCOLLECTION Cache;
+#endif
+} DSP_FACTORY_CONTEXT, *PDSP_FACTORY_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_FACTORY_CONTEXT, GetDspFactoryContext)
+
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+//
+// Define DSP device ID context.
+//
+typedef struct _DSP_DEVICEID_CONTEXT {
+ ACXFACTORYCIRCUIT Factory;
+ GUID UniqueID;
+} DSP_DEVICEID_CONTEXT, *PDSP_DEVICEID_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_DEVICEID_CONTEXT, GetDspDeviceIdContext)
+#endif // ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+
+//
+// Define RENDER device context.
+//
+typedef struct _DSP_RENDER_DEVICE_CONTEXT {
+ ACXCIRCUIT Circuit;
+ BOOLEAN FirstTimePrepareHardware;
+} DSP_RENDER_DEVICE_CONTEXT, *PDSP_RENDER_DEVICE_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_RENDER_DEVICE_CONTEXT, GetRenderDeviceContext)
+
+// Special stream definitions
+typedef enum _SDCA_SPECIALSTREAM_TYPE
+{
+ SpecialStreamTypeNotSupported = 0,
+ SpecialStreamTypeUltrasoundRender = 1,
+ SpecialStreamTypeUltrasoundCapture = 2,
+ SpecialStreamTypeReferenceStream = 3,
+ SpecialStreamTypeIvSense = 4,
+ SpecialStreamType_Count = 5,
+} SDCA_SPECIALSTREAM_TYPE, *PSDCA_SPECIALSTREAM_TYPE;
+
+inline const SDCA_SPECIALSTREAM_TYPE SpecialStreamTypeFromSdcaPath(SDCA_PATH path)
+{
+ switch(path)
+ {
+ case SdcaPathUltrasoundRender:
+ return SpecialStreamTypeUltrasoundRender;
+ case SdcaPathUltrasoundCapture:
+ return SpecialStreamTypeUltrasoundCapture;
+ case SdcaPathReferenceStream:
+ return SpecialStreamTypeReferenceStream;
+ case SdcaPathIvSense:
+ return SpecialStreamTypeIvSense;
+ }
+ return SpecialStreamTypeNotSupported;
+}
+
+inline const SDCA_PATH SdcaPathFromSpecialStreamType(SDCA_SPECIALSTREAM_TYPE type)
+{
+ switch(type)
+ {
+ case SpecialStreamTypeUltrasoundRender:
+ return SdcaPathUltrasoundRender;
+ case SpecialStreamTypeUltrasoundCapture:
+ return SdcaPathUltrasoundCapture;
+ case SpecialStreamTypeReferenceStream:
+ return SdcaPathReferenceStream;
+ case SpecialStreamTypeIvSense:
+ return SdcaPathIvSense;
+ }
+ return (SDCA_PATH) 0;
+}
+
+// Maximum of 8 devices chosen for the purpose of making this sample simpler
+#define MAX_AGGREGATED_DEVICES (8)
+
+//
+// Define circuit context.
+//
+typedef struct _DSP_CIRCUIT_CONTEXT {
+ ULONG EndpointId;
+ ULONG DataPortNumber;
+ ACXAUDIOENGINE AudioEngineElement;
+ ACXPEAKMETER PeakMeterElement;
+ PVOID peakMeter;
+ ACXKEYWORDSPOTTER KeywordSpotter;
+
+ // If the VolumeMuteHandler is set, we will forward any
+ // Volume/Mute requests for the current circuit to this
+ // target circuit. If the target circuit was allocated
+ // by this driver, it will also be copied to
+ // TargetCircuitToDelete
+ ACXTARGETCIRCUIT TargetCircuitToDelete;
+ ACXTARGETCIRCUIT TargetVolumeMuteCircuit;
+ ACXTARGETELEMENT TargetVolumeHandler;
+ ACXTARGETELEMENT TargetMuteHandler;
+
+ BOOLEAN IsRenderCircuit;
+
+ // This will contain information on the aggregated devices we're connected to
+ BOOLEAN Aggregated;
+ ULONG AggregatedDeviceCount;
+ SDCA_AGGREGATION_DEVICE AggregatedDevices[MAX_AGGREGATED_DEVICES];
+ PSDCA_PATH_DESCRIPTORS2 AggregatedPathDescriptors;
+
+ ULONG SpecialStreamAvailablePaths;
+ PSDCA_PATH_DESCRIPTORS SpecialStreamPathDescriptors[SpecialStreamType_Count];
+ PSDCA_PATH_DESCRIPTORS2 SpecialStreamPathDescriptors2[SpecialStreamType_Count];
+ ULONG SpecialStreamActive[SpecialStreamType_Count];
+ ULONG SpecialStreamRunning[SpecialStreamType_Count];
+ ACXTARGETCIRCUIT SpecialStreamTargetCircuit;
+ // The ConnectedFunctionInformation will be used with SpecialStream logic and also
+ // for determining appropriate data ports to be used with each connected audio function
+ PSDCA_FUNCTION_INFORMATION_LIST ConnectedFunctionInformation;
+
+} DSP_CIRCUIT_CONTEXT, * PDSP_CIRCUIT_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_CIRCUIT_CONTEXT, GetDspCircuitContext)
+
+//
+// Define CAPTURE device context.
+//
+typedef struct _DSP_CAPTURE_DEVICE_CONTEXT {
+ ACXCIRCUIT Circuit;
+ BOOLEAN FirstTimePrepareHardware;
+} DSP_CAPTURE_DEVICE_CONTEXT, *PDSP_CAPTURE_DEVICE_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_CAPTURE_DEVICE_CONTEXT, GetCaptureDeviceContext)
+
+//
+// Define DSP circuit/stream element context.
+//
+typedef struct _DSP_ELEMENT_CONTEXT {
+ BOOLEAN Dummy;
+} DSP_ELEMENT_CONTEXT, *PDSP_ELEMENT_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_ELEMENT_CONTEXT, GetDspElementContext)
+
+//
+// Define DSP format context.
+//
+typedef struct _DSP_FORMAT_CONTEXT {
+ BOOLEAN Dummy;
+} DSP_FORMAT_CONTEXT, *PDSP_FORMAT_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_FORMAT_CONTEXT, GetDspFormatContext)
+
+typedef enum _DSP_PIN_TYPE {
+ DspPinTypeHost,
+ DspPinTypeOffload,
+ DspPinTypeLoopback,
+ DspPinTypeBridge,
+ DspPinType_Count
+} DSP_PIN_TYPE, * PDSP_PIN_TYPE;
+
+typedef enum _DSP_CAPTURE_PIN_TYPE {
+ DspCapturePinTypeHost,
+ DspCapturePinTypeKeyword,
+ DspCapturePinTypeBridge,
+ DspCapturePinType_Count
+} DSP_CAPTURE_PIN_TYPE, * PDSP_CAPTURE_PIN_TYPE;
+
+typedef struct _DSP_PIN_CONTEXT {
+ ACXTARGETCIRCUIT TargetCircuit;
+ ULONG TargetPinId;
+ DSP_PIN_TYPE PinType;
+ DSP_CAPTURE_PIN_TYPE CapturePinType;
+
+ // The stream bridge below will only be valid for the Capture circuit Bridge Pin
+
+ // Host stream bridge will be used to ensure host stream creations are passed
+ // to the downlevel circuits. Since the HostStreamBridge won't have InModes set,
+ // the ACX framework will not add streams automatically. We will add streams for
+ // non KWS pin.
+ ACXSTREAMBRIDGE HostStreamBridge;
+ ACXOBJECTBAG HostStreamObjBag;
+
+#ifdef ACX_WORKAROUND_ACXPIN_01
+ ULONG MaxStreams;
+ ULONG CurrentStreamsCount;
+#endif
+} DSP_PIN_CONTEXT, *PDSP_PIN_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_PIN_CONTEXT, GetDspPinContext)
+
+//
+// Define DSP render/capture stream context.
+//
+typedef struct _DSP_STREAM_CONTEXT {
+ PVOID StreamEngine;
+ DSP_PIN_TYPE PinType;
+ DSP_CAPTURE_PIN_TYPE CapturePinType;
+ ACXPIN Pin; // used by acx workaround, and reference streams
+
+#ifdef ACX_WORKAROUND_ACXPIN_01
+ BOOLEAN StreamIsCounted; // TRUE = stream is counted on the pin.
+#endif
+
+ ACXTARGETCIRCUIT SpecialStreamTargetCircuit;
+ BOOLEAN SpecialStreamInUse[SpecialStreamType_Count];
+ BOOLEAN SpecialStreamRunning[SpecialStreamType_Count];
+} DSP_STREAM_CONTEXT, *PDSP_STREAM_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_STREAM_CONTEXT, GetDspStreamContext)
+
+typedef struct _DSP_ENGINE_CONTEXT {
+ ACXDATAFORMAT MixFormat;
+ BOOLEAN GFxEnabled;
+} DSP_ENGINE_CONTEXT, * PDSP_ENGINE_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_ENGINE_CONTEXT, GetDspEngineContext)
+
+typedef struct _DSP_STREAMAUDIOENGINE_CONTEXT {
+ BOOLEAN LFxEnabled;
+} DSP_STREAMAUDIOENGINE_CONTEXT, * PDSP_STREAMAUDIOENGINE_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_STREAMAUDIOENGINE_CONTEXT, GetDspStreamAudioEngineContext)
+
+//
+// Define DSP keyword spotter context
+//
+typedef struct _DSP_KEYWORDSPOTTER_CONTEXT {
+ ACXPNPEVENT Event;
+ PVOID KeywordDetector;
+} DSP_KEYWORDSPOTTER_CONTEXT, *PDSP_KEYWORDSPOTTER_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_KEYWORDSPOTTER_CONTEXT, GetDspKeywordSpotterContext)
+
+typedef struct _DSP_PNPEVENT_CONTEXT {
+ BOOLEAN Dummy;
+} DSP_PNPEVENT_CONTEXT, *PDSP_PNPEVENT_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_PNPEVENT_CONTEXT, GetDspPnpEventContext)
+
+//
+// Define DSP peakmeter element context.
+//
+typedef struct _DSP_PEAKMETER_ELEMENT_CONTEXT {
+ PVOID peakMeter;
+} DSP_PEAKMETER_ELEMENT_CONTEXT, * PDSP_PEAKMETER_ELEMENT_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_PEAKMETER_ELEMENT_CONTEXT, GetDspPeakMeterElementContext)
+
+#define PEAKMETER_STEPPING_DELTA 0x1000
+#define PEAKMETER_MAXIMUM LONG_MAX
+#define PEAKMETER_MINIMUM LONG_MIN
+
+//
+// Define DSP circuit/stream element context.
+//
+typedef struct _DSP_MUTE_ELEMENT_CONTEXT {
+ BOOL MuteState[MAX_CHANNELS];
+} DSP_MUTE_ELEMENT_CONTEXT, * PDSP_MUTE_ELEMENT_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_MUTE_ELEMENT_CONTEXT, GetDspMuteElementContext)
+
+//
+// Define DSP circuit/stream element context.
+//
+typedef struct _DSP_VOLUME_ELEMENT_CONTEXT {
+ LONG VolumeLevel[MAX_CHANNELS];
+} DSP_VOLUME_ELEMENT_CONTEXT, * PDSP_VOLUME_ELEMENT_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_VOLUME_ELEMENT_CONTEXT, GetDspVolumeElementContext)
+
+#define VOLUME_STEPPING 0x8000
+#define VOLUME_LEVEL_MAXIMUM 0x00000000
+#define VOLUME_LEVEL_MINIMUM (-96 * 0x10000)
+
+//
+// Driver prototypes.
+//
+DRIVER_INITIALIZE DriverEntry;
+EVT_WDF_DRIVER_UNLOAD Dsp_DriverUnload;
+EVT_WDF_DRIVER_DEVICE_ADD Dsp_EvtBusDeviceAdd;
+EVT_WDF_CHILD_LIST_CREATE_DEVICE Dsp_AddAudioSensorsDevice;
+
+// Device callbacks.
+
+EVT_WDF_DEVICE_PREPARE_HARDWARE Dsp_EvtDevicePrepareHardware;
+EVT_WDF_DEVICE_RELEASE_HARDWARE Dsp_EvtDeviceReleaseHardware;
+EVT_WDF_DEVICE_CONTEXT_CLEANUP Dsp_EvtDeviceContextCleanup;
+
+EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUITDEVICE Dsp_EvtAcxFactoryCircuitCreateCircuitDevice;
+EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUIT Dsp_EvtAcxFactoryCircuitCreateCircuit;
+EVT_WDF_DEVICE_CONTEXT_CLEANUP Dsp_EvtFactoryContextCleanup;
+EVT_WDF_DEVICE_CONTEXT_DESTROY Dsp_EvtFactoryContextDestroy;
+
+// Stream callbacks shared between Capture and Render
+
+EVT_WDF_OBJECT_CONTEXT_DESTROY Dsp_EvtStreamContextDestroy;
+EVT_ACX_STREAM_GET_HW_LATENCY Dsp_EvtStreamGetHwLatency;
+EVT_ACX_STREAM_ALLOCATE_RTPACKETS Dsp_EvtStreamAllocateRtPackets;
+EVT_ACX_STREAM_FREE_RTPACKETS Dsp_EvtStreamFreeRtPackets;
+EVT_ACX_STREAM_PREPARE_HARDWARE Dsp_EvtStreamPrepareHardware;
+EVT_ACX_STREAM_RELEASE_HARDWARE Dsp_EvtStreamReleaseHardware;
+EVT_ACX_STREAM_RUN Dsp_EvtStreamRun;
+EVT_ACX_STREAM_PAUSE Dsp_EvtStreamPause;
+EVT_ACX_STREAM_GET_CURRENT_PACKET Dsp_EvtStreamGetCurrentPacket;
+EVT_ACX_STREAM_ASSIGN_DRM_CONTENT_ID Dsp_EvtStreamAssignDrmContentId;
+EVT_ACX_STREAM_GET_PRESENTATION_POSITION Dsp_EvtStreamGetPresentationPosition;
+EVT_ACX_OBJECT_PREPROCESS_REQUEST DspC_EvtStreamRequestPreprocess;
+
+
+// Render callbacks.
+EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUITDEVICE DspR_EvtAcxFactoryCircuitCreateCircuitDevice;
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+NTAPI
+DspR_EvtAcxFactoryCircuitCreateCircuit(
+ _In_
+ WDFDEVICE Parent,
+ _In_
+ WDFDEVICE Device,
+ _In_
+ ACXFACTORYCIRCUIT Factory,
+ _In_
+ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT Config,
+ _In_
+ PACXCIRCUIT_INIT CircuitInit,
+ _In_
+ ULONG DataPortNumber,
+ _In_opt_
+ PSDCA_PATH_DESCRIPTORS2 PathDescriptors
+);
+
+EVT_ACX_CIRCUIT_COMPOSITE_CIRCUIT_INITIALIZE DspR_EvtCircuitCompositeCircuitInitialize;
+EVT_ACX_CIRCUIT_COMPOSITE_INITIALIZE DspR_EvtCircuitCompositeInitialize;
+
+EVT_WDF_DEVICE_CONTEXT_CLEANUP DspR_EvtCircuitContextCleanup;
+EVT_WDF_DEVICE_PREPARE_HARDWARE DspR_EvtDevicePrepareHardware;
+EVT_WDF_DEVICE_RELEASE_HARDWARE DspR_EvtDeviceReleaseHardware;
+EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT DspR_EvtDeviceSelfManagedIoInit;
+EVT_WDF_DEVICE_CONTEXT_CLEANUP DspR_EvtDeviceContextCleanup;
+EVT_ACX_OBJECT_PREPROCESS_REQUEST DspR_EvtCircuitRequestPreprocess;
+EVT_ACX_CIRCUIT_CREATE_STREAM DspR_EvtCircuitCreateStream;
+EVT_ACX_CIRCUIT_POWER_UP DspR_EvtCircuitPowerUp;
+EVT_ACX_CIRCUIT_POWER_DOWN DspR_EvtCircuitPowerDown;
+EVT_ACX_STREAM_SET_RENDER_PACKET DspR_EvtStreamSetRenderPacket;
+EVT_ACX_PIN_SET_DATAFORMAT DspR_EvtAcxPinSetDataFormat;
+EVT_WDF_DEVICE_CONTEXT_CLEANUP DspR_EvtPinContextCleanup;
+EVT_ACX_PIN_CONNECTED DspR_EvtPinConnected;
+EVT_ACX_PIN_DISCONNECTED DspR_EvtPinDisconnected;
+
+//Render Audio Engine
+EVT_ACX_MUTE_ASSIGN_STATE DspR_EvtMuteAssignState;
+EVT_ACX_MUTE_RETRIEVE_STATE DspR_EvtMuteRetrieveState;
+EVT_ACX_VOLUME_ASSIGN_LEVEL DspR_EvtVolumeAssignLevel;
+EVT_ACX_VOLUME_RETRIEVE_LEVEL DspR_EvtVolumeRetrieveLevel;
+EVT_ACX_PEAKMETER_RETRIEVE_LEVEL DspR_EvtPeakMeterRetrieveLevelCallback;
+EVT_ACX_RAMPED_VOLUME_ASSIGN_LEVEL DspR_EvtRampedVolumeAssignLevel;
+EVT_ACX_AUDIOENGINE_RETRIEVE_BUFFER_SIZE_LIMITS DspR_EvtAcxAudioEngineRetrieveBufferSizeLimits;
+EVT_ACX_AUDIOENGINE_RETRIEVE_EFFECTS_STATE DspR_EvtAcxAudioEngineRetrieveEffectsState;
+EVT_ACX_AUDIOENGINE_ASSIGN_EFFECTS_STATE DspR_EvtAcxAudioEngineAssignEffectsState;
+EVT_ACX_AUDIOENGINE_RETRIEVE_ENGINE_FORMAT DspR_EvtAcxAudioEngineRetrieveEngineMixFormat;
+EVT_ACX_AUDIOENGINE_ASSIGN_ENGINE_FORMAT DspR_EvtAcxAudioEngineAssignEngineDeviceFormat;
+EVT_ACX_STREAMAUDIOENGINE_RETRIEVE_EFFECTS_STATE DspR_EvtAcxStreamAudioEngineRetrieveEffectsState;
+EVT_ACX_STREAMAUDIOENGINE_ASSIGN_EFFECTS_STATE DspR_EvtAcxStreamAudioEngineAssignEffectsState;
+EVT_ACX_STREAMAUDIOENGINE_RETRIEVE_PRESENTATION_POSITION DspR_EvtAcxStreamAudioEngineRetrievePresentationPosition;
+EVT_ACX_STREAMAUDIOENGINE_ASSIGN_CURRENT_WRITE_POSITION DspR_EvtAcxStreamAudioEngineAssignCurrentWritePosition;
+EVT_ACX_STREAMAUDIOENGINE_RETRIEVE_LINEAR_BUFFER_POSITION DspR_EvtAcxStreamAudioEngineRetrieveLinearBufferPosition;
+EVT_ACX_STREAMAUDIOENGINE_ASSIGN_LAST_BUFFER_POSITION DspR_EvtAcxStreamAudioEngineAssignLastBufferPosition;
+EVT_ACX_STREAMAUDIOENGINE_ASSIGN_LOOPBACK_PROTECTION DspR_EvtAcxStreamAudioEngineAssignLoopbackProtection;
+
+// Capture callbacks.
+EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUITDEVICE DspC_EvtAcxFactoryCircuitCreateCircuitDevice;
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+NTAPI
+DspC_EvtAcxFactoryCircuitCreateCircuit(
+ _In_
+ WDFDEVICE Parent,
+ _In_
+ WDFDEVICE Device,
+ _In_
+ ACXFACTORYCIRCUIT Factory,
+ _In_
+ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT Config,
+ _In_
+ PACXCIRCUIT_INIT CircuitInit,
+ _In_
+ ULONG DataPortNumber,
+ _In_
+ PSDCA_PATH_DESCRIPTORS2 PathDescriptors
+);
+
+EVT_ACX_CIRCUIT_COMPOSITE_CIRCUIT_INITIALIZE DspC_EvtCircuitCompositeCircuitInitialize;
+EVT_ACX_CIRCUIT_COMPOSITE_INITIALIZE DspC_EvtCircuitCompositeInitialize;
+
+EVT_WDF_DEVICE_CONTEXT_CLEANUP DspC_EvtCircuitContextCleanup;
+EVT_WDF_DEVICE_PREPARE_HARDWARE DspC_EvtDevicePrepareHardware;
+EVT_WDF_DEVICE_RELEASE_HARDWARE DspC_EvtDeviceReleaseHardware;
+EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT DspC_EvtDeviceSelfManagedIoInit;
+EVT_WDF_DEVICE_CONTEXT_CLEANUP DspC_EvtDeviceContextCleanup;
+EVT_ACX_OBJECT_PREPROCESS_REQUEST DspC_EvtCircuitRequestPreprocess;
+EVT_ACX_CIRCUIT_CREATE_STREAM DspC_EvtCircuitCreateStream;
+EVT_ACX_CIRCUIT_POWER_UP DspC_EvtCircuitPowerUp;
+EVT_ACX_CIRCUIT_POWER_DOWN DspC_EvtCircuitPowerDown;
+EVT_ACX_STREAM_GET_CAPTURE_PACKET DspC_EvtStreamGetCapturePacket;
+EVT_ACX_PIN_SET_DATAFORMAT DspC_EvtAcxPinSetDataFormat;
+EVT_WDF_DEVICE_CONTEXT_CLEANUP DspC_EvtPinContextCleanup;
+EVT_ACX_PIN_CONNECTED DspC_EvtPinConnected;
+EVT_ACX_PIN_DISCONNECTED DspC_EvtPinDisconnected;
+EVT_ACX_KEYWORDSPOTTER_RETRIEVE_ARM DspC_EvtAcxKeywordSpotterRetrieveArm;
+EVT_ACX_KEYWORDSPOTTER_ASSIGN_ARM DspC_EvtAcxKeywordSpotterAssignArm;
+EVT_ACX_KEYWORDSPOTTER_ASSIGN_PATTERNS DspC_EvtAcxKeywordSpotterAssignPatterns;
+EVT_ACX_KEYWORDSPOTTER_ASSIGN_RESET DspC_EvtAcxKeywordSpotterAssignReset;
+
+// Property testing, todo: remove them.
+
+EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinCInstancesCallback;
+EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinCTypesCallback;
+EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinDataFlowCallback;
+EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinDataRangesCallback;
+EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinDataIntersectionCallback;
+EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinPhysicalConnectionCallback;
+
+
+EVT_ACX_OBJECT_PREPROCESS_REQUEST DspR_EvtStreamRequestPreprocess;
+EVT_WDF_OBJECT_CONTEXT_CLEANUP Dsp_EvtStreamContextCleanup;
+
+#ifdef ACX_WORKAROUND_ACXPIN_01
+EVT_ACX_OBJECT_PREPROCESS_REQUEST Dsp_EvtStreamGetStreamCountRequestPreprocess;
+#endif // ACX_WORKAROUND_ACXPIN_01
+
+#ifdef ACX_WORKAROUND_ACXPIN_02
+EVT_ACX_OBJECT_PREPROCESS_REQUEST Dsp_EvtStreamProposeDataFormatRequestPreprocess;
+#endif // ACX_WORKAROUND_ACXPIN_02
+
+/* make internal prototypes usable from C++ */
+#ifdef __cplusplus
+}
+#endif
+
+//
+// Used to store the registry settings path for the driver
+//
+extern UNICODE_STRING g_RegistryPath;
+
+__drv_requiresIRQL(PASSIVE_LEVEL)
+PAGED_CODE_SEG
+NTSTATUS
+CopyRegistrySettingsPath(
+ _In_ PUNICODE_STRING RegistryPath
+ );
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_CreateChildList(
+ _In_ WDFDEVICE Device
+);
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_AddFactoryCircuit(
+ _In_ WDFDEVICE Device
+);
+
+PAGED_CODE_SEG
+VOID
+Dsp_RemoveFactoryCircuit(
+ _In_ WDFDEVICE Device
+);
+
+#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_InitializeChildDevicesCache(
+ _In_ ACXFACTORYCIRCUIT Factory
+ );
+
+PAGED_CODE_SEG
+VOID
+Dsp_CleanupChildDevicesCache(
+ _In_ ACXFACTORYCIRCUIT Factory
+ );
+
+PAGED_CODE_SEG
+VOID
+Dsp_DeleteChildDevicesCache(
+ _In_ ACXFACTORYCIRCUIT Factory
+ );
+
+PAGED_CODE_SEG
+bool
+Dsp_IsChildDeviceInCacheLocked(
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ const GUID * UniqueId
+ );
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_AddChildDeviceToCache(
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ const GUID * UniqueId,
+ _In_ WDFDEVICE Device
+ );
+
+PAGED_CODE_SEG
+WDFDEVICE
+Dsp_RemoveChildDeviceFromCache(
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ const GUID * UniqueId
+ );
+
+PAGED_CODE_SEG
+VOID
+Dsp_PurgeChildDeviceFromCache(
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ WDFDEVICE Device
+ );
+
+EVT_ACX_OBJECT_PREPROCESS_REQUEST Dsp_EvtFactoryRemoveCircuitRequestPreprocess;
+EVT_WDF_DEVICE_CONTEXT_CLEANUP Dsp_EvtDeviceIdContextCleanup;
+EVT_ACX_OBJECT_PROCESS_REQUEST Dsp_EvtFactoryCircuitRemoveCircuitCallback;
+#endif // ACX_WORKAROUND_ACXFACTORYCIRCUIT_01
+
+PAGED_CODE_SEG
+NTSTATUS
+DspC_CircuitCleanup(
+ _In_ ACXCIRCUIT Device
+ );
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_SetPowerPolicy(
+ _In_ WDFDEVICE Device
+ );
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_SetPowerPolicy(
+ _In_ WDFDEVICE Device
+ );
+
+PAGED_CODE_SEG
+NTSTATUS
+DspC_SetPowerPolicy(
+ _In_ WDFDEVICE Device
+ );
+
+PAGED_CODE_SEG
+NTSTATUS
+DSP_SendPropertyTo
+(
+ _In_ WDFDEVICE Device,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ GUID PropertySet,
+ _In_ ULONG PropertyId,
+ _In_ ACX_PROPERTY_VERB Verb,
+ _In_ PVOID Control,
+ _In_ ULONG ControlCb,
+ _Inout_ PVOID Value,
+ _In_ ULONG ValueCb,
+ _Out_ ULONG_PTR* Information
+);
+
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_SendTestPropertyTo(
+ _In_ WDFDEVICE Device,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ GUID PropertySet,
+ _In_ ULONG PropertyId,
+ _In_ ACX_PROPERTY_VERB Verb,
+ _In_ PVOID Control,
+ _In_ ULONG ControlCb,
+ _Inout_ PVOID Value,
+ _In_ ULONG ValueCb,
+ _Out_ ULONG_PTR* Information
+ );
+
+PAGED_CODE_SEG
+VOID
+Dsp_SendVendorSpecificProperties(
+ _In_ WDFDEVICE Device,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ BOOLEAN SetValue
+ );
+
+
+// Create a single SDCA_PATH for special streaming associated
+// with the given ACXSTREAM
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_PrepareSpecialStreamForStream(
+ _In_ ACXSTREAM Stream,
+ _In_ SDCA_SPECIALSTREAM_TYPE SpecialStreamType,
+ _In_ ULONG FunctionBitMask = 0xffffffff
+ );
+
+// Destroy all SDCA_PATHs associated with the given ACXSTREAM
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_ReleaseSpecialStreamsForStream(
+ _In_ ACXSTREAM Stream
+ );
+
+// Start all SDCA_PATHs associated with the given ACXSTREAM
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_StartSpecialStreamsForStream(
+ _In_ ACXSTREAM Stream
+ );
+
+// Stop all SDCA_PATHs associated with the given ACXSTREAM
+PAGED_CODE_SEG
+NTSTATUS
+Dsp_StopSpecialStreamsForStream(
+ _In_ ACXSTREAM Stream
+ );
+
+#endif // _PRIVATE_H_
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/render.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/render.cpp
new file mode 100644
index 00000000..fe16d5c4
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/render.cpp
@@ -0,0 +1,2730 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ render.cpp
+
+Abstract:
+
+ Render factory and circuit
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include <devguid.h>
+#include "stdunk.h"
+#include <ks.h>
+#include <mmsystem.h>
+#include <ksmedia.h>
+#include "streamengine.h"
+#include "offloadStreamEngine.h"
+#include "SimPeakMeter.h"
+#include "CircuitHelper.h"
+#include "AcpiReader.h"
+
+#include "TestProperties.h"
+#include "AudioFormats.h"
+
+#ifndef __INTELLISENSE__
+#include "render.tmh"
+#endif
+
+#include "audiomodule.h"
+
+using namespace ACPIREADER;
+
+//
+// max # of streams for each pin type.
+//
+#define DSPR_MAX_INPUT_HOST_STREAMS 2
+#define DSPR_MAX_INPUT_OFFLOAD_STREAMS 3
+#define DSPR_MAX_OUTPUT_LOOPBACK_STREAMS 1
+
+//
+// Factory circuit IDs.
+//
+#define RENDER_DEVICE_ID_STR L"{4DCB0606-6415-4A36-BDC5-9B1792117DC9}\\Render&CP_%wZ"
+DECLARE_CONST_UNICODE_STRING(RenderHardwareId, L"{4DCB0606-6415-4A36-BDC5-9B1792117DC9}\\Render");
+
+DECLARE_CONST_UNICODE_STRING(RenderCompatibleId, ACX_DSP_TEST_COMPATIBLE_ID);
+DECLARE_CONST_UNICODE_STRING(RenderContainerId, ACX_DSP_TEST_CONTAINER_ID);
+DECLARE_CONST_UNICODE_STRING(RenderDeviceLocation, L"SDCAVDsp Dynamic Enum Speaker");
+
+PAGED_CODE_SEG
+VOID
+DspR_EvtPinCInstancesCallback(
+ _In_ WDFOBJECT Object,
+ _In_ WDFREQUEST Request
+ )
+{
+ PAGED_CODE();
+
+ // TEMP: for testing only.
+ UNREFERENCED_PARAMETER(Object);
+ WdfRequestComplete(Request, STATUS_UNSUCCESSFUL);
+}
+
+PAGED_CODE_SEG
+VOID
+DspR_EvtPinCTypesCallback(
+ _In_ WDFOBJECT Object,
+ _In_ WDFREQUEST Request
+ )
+{
+ PAGED_CODE();
+
+ // TEMP: for testing only.
+ UNREFERENCED_PARAMETER(Object);
+ WdfRequestComplete(Request, STATUS_UNSUCCESSFUL);
+}
+
+PAGED_CODE_SEG
+VOID
+DspR_EvtPinDataFlowCallback(
+ _In_ WDFOBJECT Object,
+ _In_ WDFREQUEST Request
+ )
+{
+ PAGED_CODE();
+
+ // TEMP: for testing only.
+ UNREFERENCED_PARAMETER(Object);
+ WdfRequestComplete(Request, STATUS_UNSUCCESSFUL);
+}
+
+PAGED_CODE_SEG
+VOID
+DspR_EvtPinDataRangesCallback(
+ _In_ WDFOBJECT Object,
+ _In_ WDFREQUEST Request
+ )
+{
+ PAGED_CODE();
+
+ // TEMP: for testing only.
+ UNREFERENCED_PARAMETER(Object);
+ WdfRequestComplete(Request, STATUS_UNSUCCESSFUL);
+}
+
+PAGED_CODE_SEG
+VOID
+DspR_EvtPinDataIntersectionCallback(
+ _In_ WDFOBJECT Object,
+ _In_ WDFREQUEST Request
+ )
+{
+ PAGED_CODE();
+
+ // TEMP: for testing only.
+ UNREFERENCED_PARAMETER(Object);
+ WdfRequestComplete(Request, STATUS_UNSUCCESSFUL);
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxPinSetDataFormat (
+ _In_ ACXPIN Pin,
+ _In_ ACXDATAFORMAT DataFormat
+ )
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(Pin);
+ UNREFERENCED_PARAMETER(DataFormat);
+
+
+ return STATUS_NOT_SUPPORTED;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DSP_SendPropertyTo
+(
+ _In_ WDFDEVICE Device,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ GUID PropertySet,
+ _In_ ULONG PropertyId,
+ _In_ ACX_PROPERTY_VERB Verb,
+ _In_ PVOID Control,
+ _In_ ULONG ControlCb,
+ _Inout_ PVOID Value,
+ _In_ ULONG ValueCb,
+ _Out_ ULONG_PTR* Information
+)
+{
+ PAGED_CODE();
+
+ ACX_REQUEST_PARAMETERS requestParams;
+ ACX_REQUEST_PARAMETERS_INIT_PROPERTY(
+ &requestParams,
+ PropertySet,
+ PropertyId,
+ Verb,
+ AcxItemTypeCircuit,
+ 0,
+ Control, ControlCb,
+ Value, ValueCb
+ );
+
+ WDFREQUEST request;
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = Device;
+ RETURN_NTSTATUS_IF_FAILED(WdfRequestCreate(&attributes, AcxTargetCircuitGetWdfIoTarget(TargetCircuit), &request));
+
+ auto request_free = scope_exit([&request]() {
+ WdfObjectDelete(request);
+ });
+
+ RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitFormatRequestForProperty(TargetCircuit, request, &requestParams));
+
+ WDF_REQUEST_SEND_OPTIONS sendOptions;
+ WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS);
+ WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(5));
+
+ RETURN_NTSTATUS_IF_TRUE(!WdfRequestSend(request, AcxTargetCircuitGetWdfIoTarget(TargetCircuit), &sendOptions), STATUS_INVALID_DEVICE_REQUEST);
+
+ NTSTATUS status = WdfRequestGetStatus(request);
+ if (Information)
+ {
+ *Information = WdfRequestGetInformation(request);
+ }
+ if (status == STATUS_BUFFER_OVERFLOW && ValueCb == 0)
+ {
+ // Don't trace this error, it's normal
+ return status;
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(status);
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_AssignAggregatedDataPorts(
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACXPIN Pin
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ const ULONG MAX_EXPECTED_AGGREGATED_DEVICES = 16;
+ // There will be one data port in this array for each aggregated device.
+ ULONG dataPortPerFunction[MAX_EXPECTED_AGGREGATED_DEVICES];
+ ULONG dataPortPerFunctionCount = 0;
+ struct DataPortMap
+ {
+ ULONG FunctionId;
+ ULONG DataPortNumber;
+ };
+
+ // This is an example of one way the Function ID could be used to determine which data port should be used
+ // Note that the order of the Audio Functions is not determistic. We will recalculate the data port array
+ // each time our circuit's pin is connected to the aggregator's pin.
+ // Note that until the pin connection is made there is no way to determine what order the audio functions
+ // will be indexed by.
+ //
+ // In most or all cases for real-world drivers, this information should be loaded from the ACPI audio composition
+ // tables as an array of mappings between Function ID and Data Port. In the case of conflicting Function IDs the
+ // streaming driver could also include FunctionManufacturerId when determining which data port to use.
+ DataPortMap dataPortMapping[] =
+ {
+ {0x6798, 0x1}, // Example Function ID of 6798
+ {0x5037, 0x3}, // Example Function ID of 5037
+ };
+
+ DSP_PIN_CONTEXT* pinCtx = GetDspPinContext(Pin);
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(Circuit);
+
+ PAGED_CODE();
+
+ if (circuitCtx->ConnectedFunctionInformation &&
+ circuitCtx->ConnectedFunctionInformation->FunctionCount >= 1)
+ {
+ // The DataPortNumbers entry is required for aggregated systems that use different data port numbers for each
+ // connected audio function.
+ // The DataPortNumbers entry can also be used for non-aggregated systems, where the single value will be used
+ // instead of DPNo.
+ // For aggregated systems that use the same data port number for each connected audio function, DataPortNumbers
+ // must still have one entry for each audio function if it is used.
+ for (ULONG i = 0; i < circuitCtx->ConnectedFunctionInformation->FunctionCount; ++i)
+ {
+ // For each of the devices that's being aggregated, we will determine if we have a data port for the device
+ // in the mapping. If so, we will assign that data port to the device's index in the array of data ports we
+ // will add to the VarArguments for the stream bridge.
+
+ for (ULONG mapIdx = 0; mapIdx < ARRAYSIZE(dataPortMapping); ++mapIdx)
+ {
+ if (dataPortMapping[mapIdx].FunctionId == circuitCtx->ConnectedFunctionInformation->FunctionInfoList[i].FunctionId)
+ {
+ // The Audio Function at index 'i' has the same Function Id as this mapping entry.
+ dataPortPerFunction[i] = dataPortMapping[mapIdx].DataPortNumber;
+
+ // We want to ensure we have a data port in our map for each audio function
+ ++dataPortPerFunctionCount;
+ break;
+ }
+ }
+ }
+ }
+
+ ASSERT((dataPortPerFunctionCount == 0) || (dataPortPerFunctionCount == circuitCtx->ConnectedFunctionInformation->FunctionCount));
+
+ if ((dataPortPerFunctionCount > 0) && (dataPortPerFunctionCount == circuitCtx->ConnectedFunctionInformation->FunctionCount))
+ {
+ // The SdcaAggregator driver will override DPNo for each aggregated device with the value in that device's index in the
+ // DataPortNumbers array.
+ DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(DataPortNumbers);
+ WDFMEMORY dataPortMemory = nullptr;
+
+ RETURN_NTSTATUS_IF_FAILED(WdfMemoryCreatePreallocated(nullptr, dataPortPerFunction, sizeof(ULONG)* dataPortPerFunctionCount, &dataPortMemory));
+ auto dataPortMemory_free = scope_exit([&dataPortMemory]()
+ {
+ WdfObjectDelete(dataPortMemory);
+ });
+
+ // Add the DataPortNumbers to the AcxObjectBag that was assigned to the Stream Bridge during circuit creation.
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddBlob(pinCtx->HostStreamObjBag, &DataPortNumbers, dataPortMemory));
+ }
+ else if (dataPortPerFunctionCount > 0)
+ {
+ status = STATUS_DEVICE_CONFIGURATION_ERROR;
+ DrvLogError(g_SDCAVDspLog, FLAG_INFO, L"Found aggregated data port entry, but not for every audio function, %!STATUS!", status);
+ }
+ // If dataPortPerFunctionCount is 0, there aren't specific data ports per audio function and SdcaAggregator can leave DPNo as is for
+ // each of the different audio functions.
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_AssignAggregatedPathDescriptors(
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACXPIN Pin
+)
+{
+ WDFMEMORY descriptorsMemory = nullptr;
+ PSDCA_PATH_DESCRIPTORS2 descriptorsBuffer = nullptr;
+
+ DSP_PIN_CONTEXT* pinCtx = GetDspPinContext(Pin);
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(Circuit);
+
+ PAGED_CODE();
+
+ if (circuitCtx->AggregatedPathDescriptors == nullptr)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ // Note that in the companion amp scenario some DSP drivers may not include aggregated path descriptor information
+ // for the companion amps. In that case, the connected function information count (which includes companions)
+ // will be more than the desciptor count.
+ if (!circuitCtx->ConnectedFunctionInformation ||
+ circuitCtx->ConnectedFunctionInformation->FunctionCount < circuitCtx->AggregatedPathDescriptors->DescriptorCount)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(WdfMemoryCreate(WDF_NO_OBJECT_ATTRIBUTES, NonPagedPoolNx, DRIVER_TAG, circuitCtx->AggregatedPathDescriptors->Size, &descriptorsMemory, (PVOID*)&descriptorsBuffer));
+ auto free_memory = scope_exit([&descriptorsMemory]()
+ {
+ WdfObjectDelete(descriptorsMemory);
+ });
+
+ // Copy over entirely; we'll fix up the Function Information Id inplace
+ RtlCopyMemory(descriptorsBuffer, circuitCtx->AggregatedPathDescriptors, circuitCtx->AggregatedPathDescriptors->Size);
+
+ ULONG fixedUpDescriptors = 0;
+ for (ULONG i = 0; i < circuitCtx->AggregatedPathDescriptors->DescriptorCount; ++i)
+ {
+ // For each of the aggregated devices, we need look it up by the UniqueID in the list of path descriptors
+ // we have.
+ for (ULONG connected = 0; connected < circuitCtx->ConnectedFunctionInformation->FunctionCount; ++connected)
+ {
+ // When saving the aggregated path descriptors, we stored the Function Info Unique ID in the descriptor's FunctionInformationId
+ // We use the Function Info Unique ID here to determine the correct FunctionInformationId for the aggregated device.
+ // The order of the aggregated devices can change depending on a lot of factors, so we need to use the Unique ID to get the right
+ // FunctionInformationId for each device.
+ if (circuitCtx->AggregatedPathDescriptors->Descriptor[i].FunctionInformationId == circuitCtx->ConnectedFunctionInformation->FunctionInfoList[connected].UniqueId)
+ {
+ descriptorsBuffer->Descriptor[i].FunctionInformationId = circuitCtx->ConnectedFunctionInformation->FunctionInfoList[connected].FunctionInformationId;
+ ++fixedUpDescriptors;
+ break;
+ }
+ }
+ }
+
+ if (fixedUpDescriptors != descriptorsBuffer->DescriptorCount)
+ {
+ RETURN_NTSTATUS_IF_FAILED(STATUS_DEVICE_CONFIGURATION_ERROR);
+ }
+
+ descriptorsBuffer->EndpointId = circuitCtx->EndpointId;
+
+ // Add the DataPortNumbers to the AcxObjectBag that was assigned to the Stream Bridge during circuit creation.
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddBlob(pinCtx->HostStreamObjBag, &SdcaPropertyPathDescriptors2, descriptorsMemory));
+
+ return STATUS_SUCCESS;
+}
+
+
+
+//
+// This callback is called when the Circuit bridge pin is connected to
+// bridge pin of another circuit.
+//
+// This will happen when the composite circuit is fully initialized.
+// From this point onwards the TargetCircuit can be used to send
+// KSPROPERTY requests
+//
+// params:
+// TargetCircuit - ACX wrapper for WDFIOTARGET for the connected circuit
+// TargetPinId - The pin on the connected circuit. This can be used to
+// send pin specific KSPROPERTY requests.
+//
+PAGED_CODE_SEG
+VOID
+DspR_EvtPinConnected (
+ _In_ ACXPIN Pin,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ ULONG TargetPinId
+ )
+{
+ PAGED_CODE();
+
+ DSP_PIN_CONTEXT *pinCtx;
+ pinCtx = GetDspPinContext(Pin);
+ pinCtx->TargetCircuit = TargetCircuit;
+ pinCtx->TargetPinId = TargetPinId;
+
+ // The bridge pin should support the same formats that are supported by the downstream circuit
+ // We could also change the formats supported by the host pin here, but a DSP will typically determine
+ // those formats and do appropriate processing.
+ ACXPIN bridgePin = AcxCircuitGetPinById(AcxPinGetCircuit(Pin), DspPinTypeBridge);
+ NTSTATUS status = ReplicateFormatsForPin(bridgePin, TargetCircuit, TargetPinId);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"Failed to replicate downstream formats to bridge pin, %!STATUS!",
+ status);
+ }
+
+ ACXAUDIOENGINE audioEngine = GetDspCircuitContext(AcxPinGetCircuit(Pin))->AudioEngineElement;
+ status = ReplicateFormatsForAudioEngine(audioEngine, TargetCircuit, TargetPinId);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"Failed to replicate downstream formats to audio engine, %!STATUS!",
+ status);
+ }
+
+ // The ACX framework will maintain the TargetCircuit until after it's called EvtPinDisconnected
+
+ ACXCIRCUIT circuit = AcxPinGetCircuit(Pin);
+ status = FindDownstreamVolumeMute(circuit, TargetCircuit);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogWarning(g_SDCAVDspLog, FLAG_INIT, L"Unable to find downstream volume/mute elements. Volume and Mute forwarding will be disabled. %!STATUS!", status);
+ }
+
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit);
+
+ // Preallocate enough room to hold information for maximum expected aggregated devices.
+ struct AggregationDevices
+ {
+ SDCA_AGGREGATION_DEVICES Devices;
+ SDCA_AGGREGATION_DEVICE DeviceExtra[MAX_AGGREGATED_DEVICES-1];
+ };
+ AggregationDevices aggDevices{ 0 };
+ aggDevices.Devices.Size = sizeof(aggDevices);
+ // The DSP driver should know from the ACPI composition tables whether
+ // this circuit is connected to an aggregated endpoint. However, in the
+ // meantime, we will just ask the target circuit.
+ status = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ TargetCircuit,
+ KSPROPERTYSETID_SdcaAgg,
+ KSPROPERTY_SDCAAGG_AGGREGATED_DEVICES,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ &aggDevices,
+ sizeof(aggDevices),
+ nullptr
+ );
+
+ if (NT_SUCCESS(status))
+ {
+ circuitCtx->Aggregated = TRUE;
+ circuitCtx->AggregatedDeviceCount = aggDevices.Devices.FunctionCount;
+ for (ULONG i = 0; i < aggDevices.Devices.FunctionCount; ++i)
+ {
+ RtlCopyMemory(&circuitCtx->AggregatedDevices[i], &aggDevices.Devices.FunctionIds[i], sizeof(SDCA_AGGREGATION_DEVICE));
+ }
+ }
+
+ // Delete previous ConnectedFunctionInformation if any is already allocated
+ if (circuitCtx->ConnectedFunctionInformation)
+ {
+ ExFreePool(circuitCtx->ConnectedFunctionInformation);
+ circuitCtx->ConnectedFunctionInformation = nullptr;
+ }
+
+ ULONG_PTR requiredBufferSize;
+
+ // retrieve the function information for this device. We'll use this information if the device
+ // has special stream capabilities.
+ // We'll also use this information if this is an aggregated device that has different Data Port requirements for the audio functions.
+ status = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ TargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_FUNCTION_INFORMATION,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ nullptr, 0,
+ &requiredBufferSize);
+
+ if (status == STATUS_BUFFER_OVERFLOW &&
+ requiredBufferSize >= sizeof(SDCA_FUNCTION_INFORMATION_LIST))
+ {
+ circuitCtx->ConnectedFunctionInformation = (PSDCA_FUNCTION_INFORMATION_LIST)ExAllocatePool2(POOL_FLAG_NON_PAGED, requiredBufferSize, DRIVER_TAG);
+ if (!circuitCtx->ConnectedFunctionInformation)
+ {
+ return;
+ }
+
+ status = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ TargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_FUNCTION_INFORMATION,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ circuitCtx->ConnectedFunctionInformation, (ULONG)requiredBufferSize,
+ nullptr);
+ }
+
+ if (circuitCtx->ConnectedFunctionInformation && circuitCtx->ConnectedFunctionInformation->FunctionCount > 1)
+ {
+ // Since FunctionCount is > 1, this is an aggregated system. As such we should update the stream bridge's VarArguments Bag
+ // to include a list of data ports based on the devices being aggregated.
+ // This is necessary if the aggregated audio functions are not uniform and use different data ports for their inputs.
+ status = DspR_AssignAggregatedDataPorts(circuit, Pin);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogWarning(g_SDCAVDspLog, FLAG_INIT, L"Unable to assign data ports for aggregated connection. %!STATUS!", status);
+ }
+
+ // To specify channel mask or more information, the path descriptors structure needs to be used
+ status = DspR_AssignAggregatedPathDescriptors(circuit, Pin);
+ if (!NT_SUCCESS(status))
+ {
+ DrvLogWarning(g_SDCAVDspLog, FLAG_INIT, L"Unable to assign path descriptors for aggregated connection. %!STATUS!", status);
+ }
+ }
+
+ // retrieve the special stream capabilities for the downstream device
+ status = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ TargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_FUNCTION_CAPABILITY,
+ AcxPropertyVerbGet,
+ NULL, 0,
+ &circuitCtx->SpecialStreamAvailablePaths, sizeof(SDCA_PATH),
+ nullptr);
+
+ if (NT_SUCCESS(status))
+ {
+ // we have path information for a target circuit which supports
+ // special paths. collect/refresh our cached information.
+ if (circuitCtx->SpecialStreamTargetCircuit)
+ {
+ // ACX will not call EvtPinConnected more than once without
+ // calling EvtPinDisconnected between, so SpecialStreamTargetCircuit
+ // should be NULL here.
+ ASSERT(FALSE);
+ }
+
+ // Since we'll clean this up in EvtPinDisconnected we do not
+ // need to perform WdfObjectReference on the TargetCircuit here.
+ circuitCtx->SpecialStreamTargetCircuit = TargetCircuit;
+
+ for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++)
+ {
+ if (circuitCtx->SpecialStreamPathDescriptors[i])
+ {
+ ExFreePool(circuitCtx->SpecialStreamPathDescriptors[i]);
+ circuitCtx->SpecialStreamPathDescriptors[i] = nullptr;
+ }
+ }
+ }
+
+ // go through the capabilities and query each that is supported for the descriptors
+ for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++)
+ {
+ SDCA_PATH currentPath = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE) i);
+
+ if ((circuitCtx->SpecialStreamAvailablePaths & currentPath) != 0)
+ {
+ // The descriptor is a variable length structure, so
+ // we need to first determine the size required
+ status = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ TargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_PATH_DESCRIPTORS,
+ AcxPropertyVerbGet,
+ &currentPath, sizeof(SDCA_PATH),
+ nullptr, 0,
+ &requiredBufferSize);
+
+ // buffer overflow indicates that the descriptorSize has been filled in with
+ // the required buffer size. It should be at least a SDCA_PATH_DESCRIPTORS worth
+ // of data, more depending on formats supported.
+ if (status == STATUS_BUFFER_OVERFLOW &&
+ requiredBufferSize >= sizeof(SDCA_PATH_DESCRIPTORS))
+ {
+ // now that we know the size, allocate and retrieve it.
+ circuitCtx->SpecialStreamPathDescriptors[i] = (PSDCA_PATH_DESCRIPTORS) ExAllocatePool2(POOL_FLAG_NON_PAGED, requiredBufferSize, DRIVER_TAG);
+ if (circuitCtx->SpecialStreamPathDescriptors[i])
+ {
+ status = DSP_SendPropertyTo(
+ AcxCircuitGetWdfDevice(circuit),
+ TargetCircuit,
+ KSPROPERTYSETID_Sdca,
+ KSPROPERTY_SDCA_PATH_DESCRIPTORS,
+ AcxPropertyVerbGet,
+ &currentPath, sizeof(SDCA_PATH),
+ circuitCtx->SpecialStreamPathDescriptors[i], (ULONG) requiredBufferSize,
+ nullptr);
+ }
+ }
+ }
+ }
+}
+
+//
+// This callback is called when the Circuit bridge pin is disconnected
+// from the bridge pin of another circuit.
+//
+// This will happen when the composite circuit is deinitialized.
+// From this point onwards the TargetCircuit cannnot be used to send
+// KSPROPERTY requests.
+// TargetCircuit should only be used to access the attached context.
+//
+// params:
+// TargetCircuit - ACX wrapper for WDFIOTARGET for the connected circuit
+// TargetPinId - The pin on the connected circuit.
+//
+PAGED_CODE_SEG
+VOID
+DspR_EvtPinDisconnected (
+ _In_ ACXPIN Pin,
+ _In_ ACXTARGETCIRCUIT TargetCircuit,
+ _In_ ULONG TargetPinId
+ )
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(TargetPinId);
+ UNREFERENCED_PARAMETER(TargetCircuit);
+
+ DSP_PIN_CONTEXT *pinCtx;
+ pinCtx = GetDspPinContext(Pin);
+
+ // We cannot use the TargetCircuit after returning from EvtPinDisconnected
+ if (pinCtx->TargetCircuit)
+ {
+ pinCtx->TargetCircuit = NULL;
+ pinCtx->TargetPinId = (ULONG)(-1);
+ }
+
+ ACXCIRCUIT circuit = AcxPinGetCircuit(Pin);
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit);
+ if (circuitCtx->TargetVolumeMuteCircuit)
+ {
+ circuitCtx->TargetMuteHandler = nullptr;
+ circuitCtx->TargetVolumeHandler = nullptr;
+ circuitCtx->TargetVolumeMuteCircuit = nullptr;
+ }
+ if (circuitCtx->TargetCircuitToDelete)
+ {
+ WdfObjectDelete(circuitCtx->TargetCircuitToDelete);
+ circuitCtx->TargetCircuitToDelete = nullptr;
+ }
+
+ circuitCtx->SpecialStreamAvailablePaths = 0;
+
+ for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++)
+ {
+ if (circuitCtx->SpecialStreamPathDescriptors[i])
+ {
+ ExFreePool(circuitCtx->SpecialStreamPathDescriptors[i]);
+ circuitCtx->SpecialStreamPathDescriptors[i] = nullptr;
+ }
+ }
+
+ if (circuitCtx->SpecialStreamTargetCircuit)
+ {
+ circuitCtx->SpecialStreamTargetCircuit = nullptr;
+ }
+
+ if (circuitCtx->ConnectedFunctionInformation)
+ {
+ ExFreePool(circuitCtx->ConnectedFunctionInformation);
+ circuitCtx->ConnectedFunctionInformation = nullptr;
+ }
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtDevicePrepareHardware(
+ _In_ WDFDEVICE Device,
+ _In_ WDFCMRESLIST ResourceList,
+ _In_ WDFCMRESLIST ResourceListTranslated
+ )
+/*++
+
+Routine Description:
+
+ In this callback, the driver does whatever is necessary to make the
+ hardware ready to use.
+
+Arguments:
+
+ Device - handle to a device
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSP_RENDER_DEVICE_CONTEXT devCtx;
+
+ UNREFERENCED_PARAMETER(ResourceList);
+ UNREFERENCED_PARAMETER(ResourceListTranslated);
+
+ PAGED_CODE();
+
+ devCtx = GetRenderDeviceContext(Device);
+ ASSERT(devCtx != NULL);
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP %p Prepare Hardware, First Time %d", Device, devCtx->FirstTimePrepareHardware);
+
+ if (!devCtx->FirstTimePrepareHardware)
+ {
+ //
+ // This is a rebalance. Validate the circuit resources and
+ // if needed, delete and re-create the circuit.
+ // The sample driver doens't use resources, thus the existing
+ // circuits are kept.
+ //
+ status = STATUS_SUCCESS;
+ return status;
+ }
+
+ //
+ // Set child's power policy.
+ //
+ RETURN_NTSTATUS_IF_FAILED(DspR_SetPowerPolicy(Device));
+
+ //
+ // Add circuit to child's list.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuit(Device, devCtx->Circuit));
+
+ //
+ // Keep track this is not the first time this callback was called.
+ //
+ devCtx->FirstTimePrepareHardware = FALSE;
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtDeviceReleaseHardware(
+ _In_ WDFDEVICE Device,
+ _In_ WDFCMRESLIST ResourceListTranslated
+ )
+/*++
+
+Routine Description:
+
+ In this callback, the driver releases the h/w resources allocated in the
+ prepare h/w callback.
+
+Arguments:
+
+ Device - handle to a device
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ NTSTATUS status;
+ PDSP_RENDER_DEVICE_CONTEXT devCtx;
+
+ UNREFERENCED_PARAMETER(ResourceListTranslated);
+
+ PAGED_CODE();
+
+ devCtx = GetRenderDeviceContext(Device);
+ ASSERT(devCtx != NULL);
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP %p Release Hardware", Device);
+
+ status = STATUS_SUCCESS;
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtDeviceSelfManagedIoInit(
+ _In_ WDFDEVICE Device
+ )
+/*++
+
+Routine Description:
+
+ In this callback, the driver does one-time init of self-managed I/O data.
+
+Arguments:
+
+ Device - handle to a device
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ PDSP_RENDER_DEVICE_CONTEXT devCtx;
+
+ PAGED_CODE();
+
+ devCtx = GetRenderDeviceContext(Device);
+ ASSERT(devCtx != NULL);
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg()
+VOID
+DspR_EvtDeviceContextCleanup(
+ _In_ WDFOBJECT WdfDevice
+ )
+/*++
+
+Routine Description:
+
+ In this callback, it cleans up device context.
+
+Arguments:
+
+ WdfDevice - WDF device object
+
+Return Value:
+
+ NULL
+
+--*/
+{
+ WDFDEVICE device;
+ PDSP_RENDER_DEVICE_CONTEXT devCtx;
+
+ device = (WDFDEVICE)WdfDevice;
+ devCtx = GetRenderDeviceContext(device);
+ ASSERT(devCtx != NULL);
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Device Cleanup %p", WdfDevice);
+}
+
+#pragma code_seg()
+VOID
+DspR_EvtCircuitContextCleanup(
+ _In_ WDFOBJECT Circuit
+ )
+/*++
+
+Routine Description:
+
+ In this callback, it cleans up circuit context.
+
+Arguments:
+
+ WdfDevice - WDF device object
+
+Return Value:
+
+ NULL
+
+--*/
+{
+ PDSP_CIRCUIT_CONTEXT circuitCtx;
+
+ circuitCtx = GetDspCircuitContext(Circuit);
+ ASSERT(circuitCtx != NULL);
+
+ if (circuitCtx->peakMeter)
+ {
+ CSimPeakMeter* peakMeter = (CSimPeakMeter *)circuitCtx->peakMeter;
+ delete peakMeter;
+ circuitCtx->peakMeter = NULL;
+ }
+
+ // clean up the path context information in case it wasn't cleaned up
+ // by pin disconnection.
+ circuitCtx->SpecialStreamAvailablePaths = 0;
+
+ for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++)
+ {
+ if (circuitCtx->SpecialStreamPathDescriptors[i])
+ {
+ ExFreePool(circuitCtx->SpecialStreamPathDescriptors[i]);
+ circuitCtx->SpecialStreamPathDescriptors[i] = nullptr;
+ }
+ }
+
+ for (ULONG i = (UINT)SpecialStreamTypeUltrasoundRender; i < (UINT)SpecialStreamType_Count; i++)
+ {
+ if (circuitCtx->SpecialStreamPathDescriptors2[i])
+ {
+ ExFreePool(circuitCtx->SpecialStreamPathDescriptors2[i]);
+ circuitCtx->SpecialStreamPathDescriptors2[i] = nullptr;
+ }
+ }
+
+ if (circuitCtx->SpecialStreamTargetCircuit)
+ {
+ circuitCtx->SpecialStreamTargetCircuit = nullptr;
+ }
+
+ if (circuitCtx->ConnectedFunctionInformation)
+ {
+ ExFreePool(circuitCtx->ConnectedFunctionInformation);
+ circuitCtx->ConnectedFunctionInformation = nullptr;
+ }
+
+ if (circuitCtx->AggregatedPathDescriptors)
+ {
+ ExFreePool(circuitCtx->AggregatedPathDescriptors);
+ circuitCtx->AggregatedPathDescriptors = nullptr;
+ }
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Circuit Cleanup %p", Circuit);
+}
+
+#pragma code_seg()
+VOID
+DspR_EvtPinContextCleanup(
+ _In_ WDFOBJECT WdfPin
+ )
+/*++
+
+Routine Description:
+
+ In this callback, it cleans up pin context.
+
+Arguments:
+
+ WdfDevice - WDF device object
+
+Return Value:
+
+ NULL
+
+--*/
+{
+ DSP_PIN_CONTEXT *pinCtx;
+ pinCtx = GetDspPinContext(WdfPin);
+
+ if (pinCtx->TargetCircuit)
+ {
+ pinCtx->TargetCircuit = NULL;
+ pinCtx->TargetPinId = (ULONG)(-1);
+ }
+}
+
+#pragma code_seg()
+VOID
+DspR_EvtCircuitRequestPreprocess(
+ _In_ ACXOBJECT Object,
+ _In_ ACXCONTEXT DriverContext,
+ _In_ WDFREQUEST Request
+)
+/*++
+
+Routine Description:
+
+ This function is an example of a preprocess routine.
+
+--*/
+{
+ CircuitRequestPreprocess(Object, DriverContext, Request);
+}
+
+PAGED_CODE_SEG
+VOID
+DspR_EvtStreamRequestPreprocess(
+ _In_ ACXOBJECT Object,
+ _In_ ACXCONTEXT DriverContext,
+ _In_ WDFREQUEST Request
+ )
+/*++
+
+Routine Description:
+
+ This function is an example of a preprocess routine.
+
+--*/
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(DriverContext);
+
+ ASSERT(Object != NULL);
+ ASSERT(DriverContext);
+ ASSERT(Request);
+
+
+ //
+ // Just give the request back to ACX.
+ //
+ (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request);
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_SetPowerPolicy(
+ _In_ WDFDEVICE Device
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ //
+ // Init the idle policy structure.
+ //
+ WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings;
+ WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0);
+ idleSettings.IdleTimeout = 10000; // 10-sec
+
+ RETURN_NTSTATUS_IF_FAILED(WdfDeviceAssignS0IdleSettings(Device, &idleSettings));
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS DspR_EvtAcxFactoryCircuitCreateCircuitDevice(
+ _In_ WDFDEVICE Parent,
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig,
+ _Out_ WDFDEVICE * Device
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+
+ UNREFERENCED_PARAMETER(Factory);
+
+ *Device = NULL;
+
+ // Allocate a generic buffer to hold a PnP ID of this device.
+ // MAX_DEVICE_ID_LEN is the count of wchar in the device ID name.
+ C_ASSERT(NTSTRSAFE_UNICODE_STRING_MAX_CCH >= MAX_DEVICE_ID_LEN);
+ C_ASSERT(USHORT_MAX >= MAX_DEVICE_ID_LEN * sizeof(WCHAR));
+ WCHAR *wstrBuffer = NULL;
+ const USHORT wstrBufferCch = MAX_DEVICE_ID_LEN;
+ wstrBuffer = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) WCHAR[wstrBufferCch];
+ RETURN_NTSTATUS_IF_TRUE(NULL == wstrBuffer, STATUS_INSUFFICIENT_RESOURCES);
+ auto wstrBuffer_free = scope_exit([&wstrBuffer](){
+ delete [] wstrBuffer;
+ });
+
+ RtlZeroMemory(wstrBuffer, sizeof(WCHAR) * wstrBufferCch);
+
+ //
+ // Create a child audio device for this circuit.
+ //
+ PWDFDEVICE_INIT devInit = NULL;
+ devInit = WdfPdoInitAllocate(Parent);
+ RETURN_NTSTATUS_IF_TRUE(NULL == devInit, STATUS_INSUFFICIENT_RESOURCES);
+ auto devInit_free = scope_exit([&devInit]() {
+ WdfDeviceInitFree(devInit);
+ });
+
+ //
+ // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId
+ //
+
+ //
+ // Create the PnP Device ID.
+ //
+ // Retrieve the unique id of this composite. This logic uses this unique id to
+ // make the device id unique. Using a deterministic value for the pnp device id, guarantees
+ // that the KS properties associated with this audio device interface stay the same across
+ // reboots, even when the circuit factory is used in several ACX composites.
+ //
+ {
+ GUID uniqueId = { 0 };
+ UNICODE_STRING uniqueIdStr = { 0 };
+ UNICODE_STRING pnpDeviceId = { 0 };
+ ACX_OBJECTBAG_CONFIG objBagCfg;
+
+ DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(UniqueID);
+
+ ACX_OBJECTBAG_CONFIG_INIT(&objBagCfg);
+ objBagCfg.Handle = CircuitConfig->CompositeProperties;
+ objBagCfg.Flags |= AcxObjectBagConfigOpenWithHandle;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ ACXOBJECTBAG objBag = NULL;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &objBagCfg, &objBag));
+ auto objBag_free = scope_exit([&objBag]() {
+ WdfObjectDelete(objBag);
+ });
+
+ RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveGuid(objBag, &UniqueID, &uniqueId));
+
+ RETURN_NTSTATUS_IF_FAILED(RtlStringFromGUID(uniqueId, &uniqueIdStr));
+
+ // Init the deviceId unicode string.
+ pnpDeviceId.Buffer = wstrBuffer;
+ pnpDeviceId.Length = 0;
+ pnpDeviceId.MaximumLength = (USHORT)(sizeof(WCHAR) * wstrBufferCch);
+
+ status = RtlUnicodeStringPrintf(&pnpDeviceId, RENDER_DEVICE_ID_STR, &uniqueIdStr);
+
+ RtlFreeUnicodeString(&uniqueIdStr);
+
+ RETURN_NTSTATUS_IF_FAILED(status);
+
+ // This is the device ID and the first H/W ID.
+ // This ID is used to create a unique audio device interface.
+ // Note that this ID is NOT the match with this driver's INF.
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignDeviceID(devInit, &pnpDeviceId));
+
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &pnpDeviceId));
+ }
+
+ // This H/W ID is the match with this driver's INF.
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &RenderHardwareId));
+
+ /*
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddCompatibleID(devInit, &RenderCompatibleId));
+
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignInstanceID(devInit, &RenderInstanceId));
+
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignContainerID(devInit, &RenderContainerId));
+
+ //
+ // You can call WdfPdoInitAddDeviceText multiple times, adding device
+ // text for multiple locales. When the system displays the text, it
+ // chooses the text that matches the current locale, if available.
+ // Otherwise it will use the string for the default locale.
+ // The driver can specify the driver's default locale by calling
+ // WdfPdoInitSetDefaultLocale.
+ //
+ RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddDeviceText(devInit,
+ &RenderDeviceLocation,
+ &RenderDeviceLocation,
+ 0x409));
+ */
+
+ WdfPdoInitSetDefaultLocale(devInit, 0x409);
+
+ //
+ // Allow ACX to add any pre-requirement it needs on this device.
+ //
+ ACX_DEVICEINIT_CONFIG devInitCfg;
+ ACX_DEVICEINIT_CONFIG_INIT(&devInitCfg);
+ devInitCfg.Flags |= AcxDeviceInitConfigRawDevice;
+ RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(devInit, &devInitCfg));
+
+ //
+ // Initialize the pnpPowerCallbacks structure. Callback events for PNP
+ // and Power are specified here. If you don't supply any callbacks,
+ // the Framework will take appropriate default actions based on whether
+ // DeviceInit is initialized to be an FDO, a PDO or a filter device
+ // object.
+ //
+ WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
+ WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
+ pnpPowerCallbacks.EvtDevicePrepareHardware = DspR_EvtDevicePrepareHardware;
+ pnpPowerCallbacks.EvtDeviceReleaseHardware = DspR_EvtDeviceReleaseHardware;
+ pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = DspR_EvtDeviceSelfManagedIoInit;
+ WdfDeviceInitSetPnpPowerEventCallbacks(devInit, &pnpPowerCallbacks);
+
+ //
+ // Specify a context for this render device.
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_RENDER_DEVICE_CONTEXT);
+ attributes.EvtCleanupCallback = DspR_EvtDeviceContextCleanup;
+ attributes.ExecutionLevel = WdfExecutionLevelPassive;
+
+ WDFDEVICE device = NULL;
+ RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&devInit, &attributes, &device));
+
+ devInit_free.release();
+
+ //
+ // Init render's device context.
+ //
+ PDSP_RENDER_DEVICE_CONTEXT devCtx;
+ devCtx = GetRenderDeviceContext(device);
+ ASSERT(devCtx != NULL);
+
+ //
+ // Set device capabilities.
+ //
+ {
+ WDF_DEVICE_PNP_CAPABILITIES pnpCaps;
+ WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps);
+
+ pnpCaps.SurpriseRemovalOK = WdfTrue;
+ pnpCaps.UniqueID = WdfFalse;
+
+ WdfDeviceSetPnpCapabilities(device, &pnpCaps);
+ }
+
+ //
+ // Allow ACX to add any post-requirement it needs on this device.
+ //
+ ACX_DEVICE_CONFIG devCfg;
+ ACX_DEVICE_CONFIG_INIT(&devCfg);
+ RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(device, &devCfg));
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Parent %p Create Circuit Device %p", Parent, device);
+
+ *Device = device;
+
+ return status;
+}
+
+
+// {3CE41646-9BF2-4A9E-B851-D711CAE9AEA8}
+DEFINE_GUID(SDCAVADPropsetId,
+ 0x3ce41646, 0x9bf2, 0x4a9e, 0xb8, 0x51, 0xd7, 0x11, 0xca, 0xe9, 0xae, 0xa8);
+
+typedef enum {
+ SDCAVAD_PROPERTY_TEST1,
+ SDCAVAD_PROPERTY_TEST2,
+ SDCAVAD_PROPERTY_TEST3,
+ SDCAVAD_PROPERTY_TEST4,
+ SDCAVAD_PROPERTY_TEST5,
+ SDCAVAD_PROPERTY_TEST6,
+} SDCAVAD_Properties;
+
+
+#pragma code_seg("PAGE")
+NTSTATUS
+DspR_EvtProcessCommand0(
+ _In_ ACXAUDIOMODULE AudioModule,
+ _In_ PVOID InBuffer,
+ _In_ ULONG InBufferCb,
+ _In_ PVOID OutBuffer,
+ _Inout_ PULONG OutBufferCb
+ )
+{
+ BOOL fNewValue = FALSE;
+ PVOID currentValue = nullptr;
+ PVOID inBuffer = nullptr;
+ ULONG inBufferCb = 0;
+ PDSP_AUDIOMODULE0_CONTEXT audioModuleCtx;
+ AUDIOMODULE_PARAMETER_INFO * parameterInfo = nullptr;
+ AUDIOMODULE_CUSTOM_COMMAND * command = nullptr;
+
+ PAGED_CODE();
+
+ audioModuleCtx = GetDspAudioModule0Context(AudioModule);
+ RETURN_NTSTATUS_IF_TRUE(nullptr == audioModuleCtx, STATUS_INTERNAL_ERROR);
+
+ //
+ // Basic parameter validation (module specific).
+ //
+ RETURN_NTSTATUS_IF_TRUE(InBuffer == nullptr || InBufferCb == 0, STATUS_INVALID_PARAMETER);
+ RETURN_NTSTATUS_IF_TRUE(InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND), STATUS_INVALID_PARAMETER);
+
+ command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer;
+
+ RETURN_NTSTATUS_IF_TRUE(command->ParameterId >= SIZEOF_ARRAY(AudioModule0_ParameterInfo), STATUS_INVALID_PARAMETER);
+
+ //
+ // Validate the parameter referenced in the command.
+ //
+ switch (command->ParameterId)
+ {
+ case AudioModuleParameter1:
+ currentValue = &audioModuleCtx->Parameter1;
+ parameterInfo = &AudioModule0_ParameterInfo[AudioModuleParameter1];
+ break;
+ case AudioModuleParameter2:
+ currentValue = &audioModuleCtx->Parameter2;
+ parameterInfo = &AudioModule0_ParameterInfo[AudioModuleParameter2];
+ break;
+ default:
+ RETURN_NTSTATUS(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Update input buffer ptr/size.
+ //
+ inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND));
+ inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND);
+
+ if (inBufferCb == 0)
+ {
+ inBuffer = nullptr;
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(AudioModule_GenericHandler(
+ command->Verb,
+ command->ParameterId,
+ parameterInfo,
+ currentValue,
+ inBuffer,
+ inBufferCb,
+ OutBuffer,
+ OutBufferCb,
+ &fNewValue));
+
+ if (fNewValue &&
+ (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION))
+ {
+ AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0};
+
+ customNotification.Type = AudioModuleParameterChanged;
+ customNotification.ParameterChanged.ParameterId = command->ParameterId;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification)));
+ }
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg("PAGE")
+NTSTATUS
+DspR_EvtProcessCommand1(
+ _In_ ACXAUDIOMODULE AudioModule,
+ _In_ PVOID InBuffer,
+ _In_ ULONG InBufferCb,
+ _In_ PVOID OutBuffer,
+ _Inout_ PULONG OutBufferCb
+ )
+{
+ BOOL fNewValue = FALSE;
+ PVOID currentValue = nullptr;
+ PVOID inBuffer = nullptr;
+ ULONG inBufferCb = 0;
+ PDSP_AUDIOMODULE1_CONTEXT audioModuleCtx;
+ AUDIOMODULE_PARAMETER_INFO * parameterInfo = nullptr;
+ AUDIOMODULE_CUSTOM_COMMAND * command = nullptr;
+
+ PAGED_CODE();
+
+ audioModuleCtx = GetDspAudioModule1Context(AudioModule);
+ RETURN_NTSTATUS_IF_TRUE(nullptr == audioModuleCtx, STATUS_INTERNAL_ERROR);
+
+ //
+ // Basic parameter validation (module specific).
+ //
+ RETURN_NTSTATUS_IF_TRUE(InBuffer == nullptr || InBufferCb == 0, STATUS_INVALID_PARAMETER);
+ RETURN_NTSTATUS_IF_TRUE(InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND), STATUS_INVALID_PARAMETER);
+
+ command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer;
+
+ RETURN_NTSTATUS_IF_TRUE(command->ParameterId >= SIZEOF_ARRAY(AudioModule1_ParameterInfo), STATUS_INVALID_PARAMETER);
+
+ //
+ // Validate the parameter referenced in the command.
+ //
+ switch (command->ParameterId)
+ {
+ case AudioModuleParameter1:
+ currentValue = &audioModuleCtx->Parameter1;
+ parameterInfo = &AudioModule1_ParameterInfo[AudioModuleParameter1];
+ break;
+ case AudioModuleParameter2:
+ currentValue = &audioModuleCtx->Parameter2;
+ parameterInfo = &AudioModule1_ParameterInfo[AudioModuleParameter2];
+ break;
+ case AudioModuleParameter3:
+ currentValue = &audioModuleCtx->Parameter3;
+ parameterInfo = &AudioModule1_ParameterInfo[AudioModuleParameter3];
+ break;
+ default:
+ RETURN_NTSTATUS(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Update input buffer ptr/size.
+ //
+ inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND));
+ inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND);
+
+ if (inBufferCb == 0)
+ {
+ inBuffer = nullptr;
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(AudioModule_GenericHandler(
+ command->Verb,
+ command->ParameterId,
+ parameterInfo,
+ currentValue,
+ inBuffer,
+ inBufferCb,
+ OutBuffer,
+ OutBufferCb,
+ &fNewValue));
+
+ if (fNewValue &&
+ (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION))
+ {
+ AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0};
+
+ customNotification.Type = AudioModuleParameterChanged;
+ customNotification.ParameterChanged.ParameterId = command->ParameterId;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification)));
+ }
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg("PAGE")
+NTSTATUS
+DspR_EvtProcessCommand2(
+ _In_ ACXAUDIOMODULE AudioModule,
+ _In_ PVOID InBuffer,
+ _In_ ULONG InBufferCb,
+ _In_ PVOID OutBuffer,
+ _Inout_ PULONG OutBufferCb
+ )
+{
+ BOOL fNewValue = FALSE;
+ PVOID currentValue = nullptr;
+ PVOID inBuffer = nullptr;
+ ULONG inBufferCb = 0;
+ PDSP_AUDIOMODULE2_CONTEXT audioModuleCtx;
+ AUDIOMODULE_PARAMETER_INFO * parameterInfo = nullptr;
+ AUDIOMODULE_CUSTOM_COMMAND * command = nullptr;
+
+ PAGED_CODE();
+
+ audioModuleCtx = GetDspAudioModule2Context(AudioModule);
+ RETURN_NTSTATUS_IF_TRUE(nullptr == audioModuleCtx, STATUS_INTERNAL_ERROR);
+
+ //
+ // Basic parameter validation (module specific).
+ //
+ RETURN_NTSTATUS_IF_TRUE(InBuffer == nullptr || InBufferCb == 0, STATUS_INVALID_PARAMETER);
+ RETURN_NTSTATUS_IF_TRUE(InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND), STATUS_INVALID_PARAMETER);
+
+ command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer;
+
+ RETURN_NTSTATUS_IF_TRUE(command->ParameterId >= SIZEOF_ARRAY(AudioModule2_ParameterInfo), STATUS_INVALID_PARAMETER);
+
+ //
+ // Validate the parameter referenced in the command.
+ //
+ switch (command->ParameterId)
+ {
+ case AudioModuleParameter1:
+ currentValue = &audioModuleCtx->Parameter1;
+ parameterInfo = &AudioModule2_ParameterInfo[AudioModuleParameter1];
+ break;
+ case AudioModuleParameter2:
+ currentValue = &audioModuleCtx->Parameter2;
+ parameterInfo = &AudioModule2_ParameterInfo[AudioModuleParameter2];
+ break;
+ default:
+ RETURN_NTSTATUS(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Update input buffer ptr/size.
+ //
+ inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND));
+ inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND);
+
+ if (inBufferCb == 0)
+ {
+ inBuffer = nullptr;
+ }
+
+ RETURN_NTSTATUS_IF_FAILED(AudioModule_GenericHandler(
+ command->Verb,
+ command->ParameterId,
+ parameterInfo,
+ currentValue,
+ inBuffer,
+ inBufferCb,
+ OutBuffer,
+ OutBufferCb,
+ &fNewValue));
+
+ if (fNewValue &&
+ (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION))
+ {
+ AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0};
+
+ customNotification.Type = AudioModuleParameterChanged;
+ customNotification.ParameterChanged.ParameterId = command->ParameterId;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification)));
+ }
+
+ return STATUS_SUCCESS;
+}
+
+#pragma code_seg("PAGE")
+NTSTATUS
+DspR_CreateCircuitModules(
+ _In_ WDFDEVICE Device,
+ _In_ ACXCIRCUIT Circuit
+ )
+/*++
+
+Routine Description:
+
+ This routine creates all of the audio module elements and adds them to the circuit
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ WDF_OBJECT_ATTRIBUTES attributes;
+ ACX_AUDIOMODULE_CALLBACKS audioModuleCallbacks;
+ ACX_AUDIOMODULE_CONFIG audioModuleCfg;
+ ACXAUDIOMODULE audioModuleElement;
+ PDSP_AUDIOMODULE0_CONTEXT audioModule0Ctx;
+ PDSP_AUDIOMODULE1_CONTEXT audioModule1Ctx;
+ PDSP_AUDIOMODULE2_CONTEXT audioModule2Ctx;
+ ACX_PNPEVENT_CONFIG audioModuleEventCfg;
+ ACXPNPEVENT audioModuleEvent;
+
+ PAGED_CODE();
+
+ // Now add audio modules to the circuit
+ // module 0
+
+ ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks);
+ audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand0;
+
+ ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg);
+ audioModuleCfg.Name = &AudioModule0Id;
+ audioModuleCfg.Descriptor.ClassId = AudioModule0Id;
+ audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(0,0);
+ audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE0_MAJOR;
+ audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE0_MINOR;
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name,
+ ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE,
+ AUDIOMODULE0DESCRIPTION,
+ wcslen(AUDIOMODULE0DESCRIPTION)));
+
+ audioModuleCfg.Callbacks = &audioModuleCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE0_CONTEXT);
+ attributes.ParentObject = Circuit;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement));
+
+ audioModule0Ctx = GetDspAudioModule0Context(audioModuleElement);
+ ASSERT(audioModule0Ctx);
+
+ ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT);
+ attributes.ParentObject = audioModuleElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent));
+
+ audioModule0Ctx->Event = audioModuleEvent;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1));
+
+ // module 1
+
+ ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks);
+ audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand1;
+
+ ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg);
+ audioModuleCfg.Name = &AudioModule1Id;
+ audioModuleCfg.Descriptor.ClassId = AudioModule1Id;
+ audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(0,0);
+ audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE1_MAJOR;
+ audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE1_MINOR;
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name,
+ ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE,
+ AUDIOMODULE1DESCRIPTION,
+ wcslen(AUDIOMODULE1DESCRIPTION)));
+
+ audioModuleCfg.Callbacks = &audioModuleCallbacks;
+
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE1_CONTEXT);
+ attributes.ParentObject = Circuit;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement));
+
+ audioModule1Ctx = GetDspAudioModule1Context(audioModuleElement);
+ ASSERT(audioModule1Ctx);
+
+ ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT);
+ attributes.ParentObject = audioModuleElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent));
+
+ audioModule1Ctx->Event = audioModuleEvent;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1));
+
+ // module 2
+
+ ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks);
+ audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand2;
+
+ ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg);
+ audioModuleCfg.Name = &AudioModule2Id;
+ audioModuleCfg.Descriptor.ClassId = AudioModule2Id;
+ audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0);
+ audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE2_MAJOR;
+ audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE2_MINOR;
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name,
+ ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE,
+ AUDIOMODULE2DESCRIPTION,
+ wcslen(AUDIOMODULE2DESCRIPTION)));
+
+ audioModuleCfg.Callbacks = &audioModuleCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE2_CONTEXT);
+ attributes.ParentObject = Circuit;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement));
+
+ audioModule2Ctx = GetDspAudioModule2Context(audioModuleElement);
+ ASSERT(audioModule2Ctx);
+
+ ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT);
+ attributes.ParentObject = audioModuleElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent));
+
+ audioModule2Ctx->Event = audioModuleEvent;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_AddOffloadFormats(
+ _In_ ACXPIN Pin
+)
+{
+ PAGED_CODE();
+
+ ACXCIRCUIT circuit = AcxPinGetCircuit(Pin);
+ WDFDEVICE device = AcxCircuitGetWdfDevice(circuit);
+ // PCM:44100 channel:2 24in32
+ ACXDATAFORMAT formatPcm44100c2_24in32;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm44100c2_24in32, circuit, device, &formatPcm44100c2_24in32));
+
+ // PCM:48000 channel:2 24in32
+ ACXDATAFORMAT formatPcm48000c2_24in32;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm48000c2_24in32, circuit, device, &formatPcm48000c2_24in32));
+
+ // PCM:96000 channel:2 24in32
+ ACXDATAFORMAT formatPcm96000c2_24in32;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm96000c2_24in32, circuit, device, &formatPcm96000c2_24in32));
+
+ // PCM:192000 channel:2 24in32
+ ACXDATAFORMAT formatPcm192000c2_24in32;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm192000c2_24in32, circuit, device, &formatPcm192000c2_24in32));
+
+ // PCM:44100 channel:2 16
+ ACXDATAFORMAT formatPcm44100c2;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm44100c2, circuit, device, &formatPcm44100c2));
+
+ // PCM:48000 channel:2 16
+ ACXDATAFORMAT formatPcm48000c2;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm48000c2, circuit, device, &formatPcm48000c2));
+
+ // PCM:96000 channel:2 16
+ ACXDATAFORMAT formatPcm96000c2;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm96000c2, circuit, device, &formatPcm96000c2));
+
+ // PCM:192000 channel:2 16
+ ACXDATAFORMAT formatPcm192000c2;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm192000c2, circuit, device, &formatPcm192000c2));
+
+ //
+ // Add our supported formats to the raw mode for the circuit
+ //
+ ACXDATAFORMATLIST formatList = AcxPinGetRawDataFormatList(Pin);
+ RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES);
+
+ //
+ // For Offload scenarios, Windows will use 16 bit per sample offload only
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm96000c2));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2));
+
+ // Include the formats supported by the host pin as well.
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm48000c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm96000c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32));
+
+ //
+ // Set up supported Default Mode formats
+ //
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = circuit;
+
+ ACX_DATAFORMAT_LIST_CONFIG dflCfg;
+ ACX_DATAFORMAT_LIST_CONFIG_INIT(&dflCfg);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListCreate(device, &attributes, &dflCfg, &formatList));
+
+ //
+ // For Offload scenarios, Windows will use 16 bit per sample offload only
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm96000c2));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2));
+
+ // Include the formats supported by the host pin as well.
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm48000c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm96000c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxPinAssignModeDataFormatList(Pin, &AUDIO_SIGNALPROCESSINGMODE_DEFAULT, formatList));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxFactoryCircuitCreateCircuit(
+ _In_ WDFDEVICE Parent,
+ _In_ WDFDEVICE Device,
+ _In_ ACXFACTORYCIRCUIT Factory,
+ _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig,
+ _In_ PACXCIRCUIT_INIT CircuitInit,
+ _In_ ULONG DataPortNumber,
+ _In_opt_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors
+)
+{
+
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(Parent);
+ UNREFERENCED_PARAMETER(Factory);
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DrvLogEnter(g_SDCAVDspLog);
+
+ DECLARE_CONST_UNICODE_STRING(circuitName, L"Speaker0");
+
+ WDF_OBJECT_ATTRIBUTES attributes;
+
+ //
+ // Init output value.
+ //
+ ASSERT(Device);
+
+ DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(EndpointId);
+ ULONG endpointId = 0;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+
+ RETURN_NTSTATUS_IF_FAILED(RetrieveProperties(CircuitConfig, &endpointId));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Create a circuit.
+ //
+
+ ACXCIRCUIT circuit;
+ RETURN_NTSTATUS_IF_FAILED(CreateRenderCircuit(CircuitInit, circuitName, Device, &circuit));
+ AcpiReader * acpiReader = GetAcpiReaderDeviceContext(Parent);
+
+ RETURN_NTSTATUS_IF_FAILED(DetermineSpecialStreamDetailsFromVendorProperties(circuit, acpiReader, CircuitConfig->CircuitProperties));
+
+ ASSERT(circuit != NULL);
+ DSP_CIRCUIT_CONTEXT *circuitCtx;
+ circuitCtx = GetDspCircuitContext(circuit);
+ ASSERT(circuitCtx);
+
+ circuitCtx->EndpointId = endpointId;
+ circuitCtx->DataPortNumber = DataPortNumber;
+ circuitCtx->IsRenderCircuit = TRUE;
+
+ //
+ // Sim Peakmeter
+ //
+ circuitCtx->peakMeter = (PVOID)new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CSimPeakMeter();
+ RETURN_NTSTATUS_IF_TRUE(NULL == circuitCtx->peakMeter, STATUS_INSUFFICIENT_RESOURCES);
+
+ //
+ // Post circuit creation initialization.
+ //
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Allocate the formats this circuit supports.
+ //
+ // PCM:44100 channel:2 24in32
+ ACXDATAFORMAT formatPcm44100c2_24in32;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm44100c2_24in32, circuit, Device, &formatPcm44100c2_24in32));
+
+ // PCM:48000 channel:2 24in32
+ ACXDATAFORMAT formatPcm48000c2_24in32;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm48000c2_24in32, circuit, Device, &formatPcm48000c2_24in32));
+
+ // PCM:96000 channel:2 24in32
+ ACXDATAFORMAT formatPcm96000c2_24in32;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm96000c2_24in32, circuit, Device, &formatPcm96000c2_24in32));
+
+ // PCM:192000 channel:2 24in32
+ ACXDATAFORMAT formatPcm192000c2_24in32;
+ RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm192000c2_24in32, circuit, Device, &formatPcm192000c2_24in32));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Create Pins
+ //
+ ACXPIN pins[DspPinType_Count];
+
+ //
+ // Create host render pin.
+ //
+
+ ACX_PIN_CALLBACKS pinCallbacks;
+ ACX_PIN_CALLBACKS_INIT(&pinCallbacks);
+ pinCallbacks.EvtAcxPinSetDataFormat = DspR_EvtAcxPinSetDataFormat;
+
+ RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSink,
+ circuit,
+ AcxPinCommunicationSink,
+ &KSCATEGORY_AUDIO,
+ &pinCallbacks,
+ DSPR_MAX_INPUT_HOST_STREAMS,
+ false,
+ &pins[DspPinTypeHost]));
+ ASSERT(pins[DspPinTypeHost] != NULL);
+
+ PDSP_PIN_CONTEXT pinCtx;
+ pinCtx = GetDspPinContext(pins[DspPinTypeHost]);
+ ASSERT(pinCtx);
+ pinCtx->PinType = DspPinTypeHost;
+
+ //
+ // A DSP driver could add the formats it supports here, or it could wait until
+ // the downstream pin is connected and discover the supported formats to use
+ // formats supported by the SdcaClass driver for this endpoint based on the
+ // DisCo data for the endpoint (e.g. supported data port widths, supported clock
+ // sample rates, etc.)
+ //
+ ACXDATAFORMATLIST formatList;
+ formatList = AcxPinGetRawDataFormatList(pins[DspPinTypeHost]);
+ RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32));
+
+ //
+ // Set up supported Default Mode formats
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = circuit;
+ ACX_DATAFORMAT_LIST_CONFIG dflCfg;
+ ACX_DATAFORMAT_LIST_CONFIG_INIT(&dflCfg);
+ AcxDataFormatListCreate(Device, &attributes, &dflCfg, &formatList);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxPinAssignModeDataFormatList(pins[DspPinTypeHost], &AUDIO_SIGNALPROCESSINGMODE_DEFAULT, formatList));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Create Offload Render Pin.
+ //
+
+ ACX_PIN_CALLBACKS_INIT(&pinCallbacks);
+ pinCallbacks.EvtAcxPinSetDataFormat = DspR_EvtAcxPinSetDataFormat;
+
+ RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSink,
+ circuit,
+ AcxPinCommunicationSink,
+ &KSCATEGORY_AUDIO,
+ &pinCallbacks,
+ DSPR_MAX_INPUT_OFFLOAD_STREAMS,
+ false,
+ &pins[DspPinTypeOffload]));
+ ASSERT(pins[DspPinTypeOffload] != NULL);
+
+ pinCtx = GetDspPinContext(pins[DspPinTypeOffload]);
+ ASSERT(pinCtx);
+ pinCtx->PinType = DspPinTypeOffload;
+
+ RETURN_NTSTATUS_IF_FAILED(DspR_AddOffloadFormats(pins[DspPinTypeOffload]));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Create loopback Pin.
+ //
+
+ ACX_PIN_CALLBACKS_INIT(&pinCallbacks);
+ pinCallbacks.EvtAcxPinSetDataFormat = DspR_EvtAcxPinSetDataFormat;
+
+ RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSource,
+ circuit,
+ AcxPinCommunicationSink,
+ &KSNODETYPE_AUDIO_LOOPBACK,
+ &pinCallbacks,
+ DSPR_MAX_OUTPUT_LOOPBACK_STREAMS,
+ false,
+ &pins[DspPinTypeLoopback]));
+ ASSERT(pins[DspPinTypeLoopback] != NULL);
+
+ pinCtx = GetDspPinContext(pins[DspPinTypeLoopback]);
+ ASSERT(pinCtx);
+ pinCtx->PinType = DspPinTypeLoopback;
+
+ //
+ // Add our supported formats to the raw mode for the circuit
+ //
+ formatList = AcxPinGetRawDataFormatList(pins[DspPinTypeLoopback]);
+ RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32));
+
+ //
+ // Create Audio Engine
+ //
+ ACXAUDIOENGINE audioEngineElement;
+ RETURN_NTSTATUS_IF_FAILED(CreateAudioEngine(circuit, pins, &audioEngineElement));
+ circuitCtx->AudioEngineElement = audioEngineElement;
+
+ PDSP_ENGINE_CONTEXT audioEngineCtx;
+ audioEngineCtx = GetDspEngineContext(audioEngineElement);
+
+ //
+ // Add our supported formats to the audio engine device format list
+ //
+ formatList = AcxAudioEngineGetDeviceFormatList(audioEngineElement);
+ RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32));
+
+ // Create a new format to use for Engine Mix format
+ AllocateFormat(Pcm48000c2_24in32, circuit, Device, &formatPcm48000c2_24in32);
+ audioEngineCtx->MixFormat = formatPcm48000c2_24in32;
+
+ // Set the global efects as disabled
+ audioEngineCtx->GFxEnabled = FALSE;
+
+ //
+ // Add AudioEngine to the circuit
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(circuit, (ACXELEMENT*)&audioEngineElement, 1));
+
+ //
+ // Create and add the audio modules
+ //
+ RETURN_NTSTATUS_IF_FAILED(DspR_CreateCircuitModules(Device, circuit));
+
+ ///////////////////////////////////////////////////////////
+ //
+ // Create bridge pin.
+ //
+ ACX_PIN_CALLBACKS_INIT(&pinCallbacks);
+ pinCallbacks.EvtAcxPinConnected = DspR_EvtPinConnected;
+ pinCallbacks.EvtAcxPinDisconnected = DspR_EvtPinDisconnected;
+
+ RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSource,
+ circuit,
+ AcxPinCommunicationNone,
+ &KSCATEGORY_AUDIO,
+ &pinCallbacks,
+ 0,
+ false,
+ &pins[DspPinTypeBridge]));
+ ASSERT(pins[DspPinTypeBridge] != NULL);
+
+ pinCtx = GetDspPinContext(pins[DspPinTypeBridge]);
+ ASSERT(pinCtx);
+ pinCtx->PinType = DspPinTypeBridge;
+
+ //
+ // Add our supported formats to the raw mode for the bridge pin.
+ // This is required for ACX to retrieve Device format
+ //
+ formatList = AcxPinGetRawDataFormatList(pins[DspPinTypeBridge]);
+ RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES);
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32));
+
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32));
+ RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32));
+
+ if (PathDescriptors != nullptr && PathDescriptors->Size > 0)
+ {
+ circuitCtx->AggregatedPathDescriptors = (PSDCA_PATH_DESCRIPTORS2)ExAllocatePool2(POOL_FLAG_NON_PAGED, PathDescriptors->Size, DRIVER_TAG);
+ if (circuitCtx->AggregatedPathDescriptors == nullptr)
+ {
+ RETURN_NTSTATUS_IF_FAILED(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ RtlCopyMemory(circuitCtx->AggregatedPathDescriptors, PathDescriptors, PathDescriptors->Size);
+ }
+
+ //
+ // Add a stream BRIDGE.
+ //
+
+ ACX_STREAM_BRIDGE_CONFIG streamCfg;
+ ACX_STREAM_BRIDGE_CONFIG_INIT(&streamCfg);
+
+ RETURN_NTSTATUS_IF_FAILED(CreateStreamBridge(streamCfg, circuit, pins[DspPinTypeBridge], pinCtx, DataPortNumber, endpointId, PathDescriptors, true));
+
+ //
+ // Add bridge pin
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, pins, DspPinType_Count));
+
+ RETURN_NTSTATUS_IF_FAILED(ConnectRenderCircuitElements(audioEngineElement, circuit));
+
+ //
+ // Store the circuit handle in the render device context.
+ //
+ PDSP_RENDER_DEVICE_CONTEXT renderDevCtx = NULL;
+ renderDevCtx = GetRenderDeviceContext(Device);
+ ASSERT(renderDevCtx);
+ renderDevCtx->Circuit = circuit;
+ renderDevCtx->FirstTimePrepareHardware = TRUE;
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Circuit Device %p Create Circuit %p", Device, circuit);
+
+ return status;
+}
+
+#pragma code_seg()
+_Use_decl_annotations_
+NTSTATUS
+DspR_EvtCircuitPowerUp (
+ WDFDEVICE,
+ ACXCIRCUIT,
+ WDF_POWER_DEVICE_STATE
+ )
+{
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+_Use_decl_annotations_
+NTSTATUS
+DspR_EvtCircuitPowerDown (
+ WDFDEVICE Device,
+ ACXCIRCUIT Circuit,
+ WDF_POWER_DEVICE_STATE TargetState
+ )
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(Device);
+ UNREFERENCED_PARAMETER(Circuit);
+ UNREFERENCED_PARAMETER(TargetState);
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtCircuitCompositeCircuitInitialize(
+ WDFDEVICE Device,
+ ACXCIRCUIT Circuit,
+ ACXOBJECTBAG CircuitProperties
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(Device);
+ UNREFERENCED_PARAMETER(Circuit);
+ UNREFERENCED_PARAMETER(CircuitProperties);
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtCircuitCompositeInitialize(
+ WDFDEVICE Device,
+ ACXCIRCUIT Circuit,
+ ACXOBJECTBAG CompositeProperties
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(Device);
+ UNREFERENCED_PARAMETER(Circuit);
+ UNREFERENCED_PARAMETER(CompositeProperties);
+
+ return status;
+}
+
+#pragma code_seg("PAGE")
+NTSTATUS
+DspR_CreateStreamModules(
+ _In_ WDFDEVICE Device,
+ _In_ ACXSTREAM Stream
+ )
+/*++
+
+Routine Description:
+
+ This routine creates all of the audio module elements and adds them to the stream
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ WDF_OBJECT_ATTRIBUTES attributes;
+ ACX_AUDIOMODULE_CALLBACKS audioModuleCallbacks;
+ ACX_AUDIOMODULE_CONFIG audioModuleCfg;
+ ACXAUDIOMODULE audioModuleElement;
+ PDSP_AUDIOMODULE0_CONTEXT audioModule0Ctx;
+ PDSP_AUDIOMODULE1_CONTEXT audioModule1Ctx;
+ PDSP_AUDIOMODULE2_CONTEXT audioModule2Ctx;
+ ACX_PNPEVENT_CONFIG audioModuleEventCfg;
+ ACXPNPEVENT audioModuleEvent;
+
+ PAGED_CODE();
+
+ // Now add audio modules to the stream
+ // module 0
+ // for simplicity of the example, we implement the same modules on the stream as is
+ // on the circuit
+ ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks);
+ audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand0;
+
+ ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg);
+ audioModuleCfg.Name = &AudioModule0Id;
+ audioModuleCfg.Descriptor.ClassId = AudioModule0Id;
+ audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0);
+ audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE0_MAJOR;
+ audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE0_MINOR;
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name,
+ ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE,
+ AUDIOMODULE0DESCRIPTION,
+ wcslen(AUDIOMODULE0DESCRIPTION)));
+
+ audioModuleCfg.Callbacks = &audioModuleCallbacks;
+
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE0_CONTEXT);
+ attributes.ParentObject = Stream;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Stream, &attributes, &audioModuleCfg, &audioModuleElement));
+
+ audioModule0Ctx = GetDspAudioModule0Context(audioModuleElement);
+ ASSERT(audioModule0Ctx);
+
+ ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT);
+ attributes.ParentObject = audioModuleElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent));
+
+ audioModule0Ctx->Event = audioModuleEvent;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(Stream, (ACXELEMENT *) &audioModuleElement, 1));
+
+ // module 1
+
+ ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks);
+ audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand1;
+
+ ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg);
+ audioModuleCfg.Name = &AudioModule1Id;
+ audioModuleCfg.Descriptor.ClassId = AudioModule1Id;
+ audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0);
+ audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE1_MAJOR;
+ audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE1_MINOR;
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name,
+ ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE,
+ AUDIOMODULE1DESCRIPTION,
+ wcslen(AUDIOMODULE1DESCRIPTION)));
+
+ audioModuleCfg.Callbacks = &audioModuleCallbacks;
+
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE1_CONTEXT);
+ attributes.ParentObject = Stream;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Stream, &attributes, &audioModuleCfg, &audioModuleElement));
+
+ audioModule1Ctx = GetDspAudioModule1Context(audioModuleElement);
+ ASSERT(audioModule1Ctx);
+
+ ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT);
+ attributes.ParentObject = audioModuleElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent));
+
+ audioModule1Ctx->Event = audioModuleEvent;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(Stream, (ACXELEMENT *) &audioModuleElement, 1));
+
+ // module 2
+
+ ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks);
+ audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand2;
+
+ ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg);
+ audioModuleCfg.Name = &AudioModule2Id;
+ audioModuleCfg.Descriptor.ClassId = AudioModule2Id;
+ audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(2,0);
+ audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE2_MAJOR;
+ audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE2_MINOR;
+ RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name,
+ ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE,
+ AUDIOMODULE2DESCRIPTION,
+ wcslen(AUDIOMODULE2DESCRIPTION)));
+
+ audioModuleCfg.Callbacks = &audioModuleCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE2_CONTEXT);
+ attributes.ParentObject = Stream;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Stream, &attributes, &audioModuleCfg, &audioModuleElement));
+
+ audioModule2Ctx = GetDspAudioModule2Context(audioModuleElement);
+ ASSERT(audioModule2Ctx);
+
+ ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT);
+ attributes.ParentObject = audioModuleElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent));
+
+ audioModule2Ctx->Event = audioModuleEvent;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(Stream, (ACXELEMENT *) &audioModuleElement, 1));
+
+ return STATUS_SUCCESS;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtCircuitCreateStream(
+ _In_ WDFDEVICE Device,
+ _In_ ACXCIRCUIT Circuit,
+ _In_ ACXPIN Pin,
+ _In_ PACXSTREAM_INIT StreamInit,
+ _In_ ACXDATAFORMAT DataFormat,
+ _In_ const GUID* SignalProcessingMode,
+ _In_ ACXOBJECTBAG VarArguments
+)
+/*++
+
+Routine Description:
+
+ This routine create a stream for the specified circuit.
+
+Return Value:
+
+ NT status value
+
+--*/
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(SignalProcessingMode);
+ UNREFERENCED_PARAMETER(VarArguments);
+
+ DrvLogEnter(g_SDCAVDspLog);
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PDSP_PIN_CONTEXT pinCtx = GetDspPinContext(Pin);
+ ASSERT(pinCtx);
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXPIN_01
+ {
+ ASSERT(pinCtx->CurrentStreamsCount != (ULONG)-1);
+ RETURN_NTSTATUS_IF_TRUE_MSG(
+ pinCtx->CurrentStreamsCount >= pinCtx->MaxStreams,
+ STATUS_INSUFFICIENT_RESOURCES,
+ L"ACXCIRCUIT %p ACXPIN %p cannot create another ACXSTREAM, max count is %d, %!STATUS!",
+ Circuit, Pin, pinCtx->MaxStreams, status);
+ }
+#endif
+
+ // Check incorrect pin instantiation.
+ RETURN_NTSTATUS_IF_TRUE_MSG(NULL == pinCtx, STATUS_INVALID_PARAMETER, L"Incorrect pin is being instantiated");
+ RETURN_NTSTATUS_IF_TRUE_MSG(
+ NULL == pinCtx ||
+ (pinCtx->PinType != DspPinTypeHost &&
+ pinCtx->PinType != DspPinTypeOffload &&
+ pinCtx->PinType != DspPinTypeLoopback),
+ STATUS_INVALID_PARAMETER, L"Incorrect pin is being instantiated");
+
+ //
+ // TEST sending KS Property to connected circuits
+ //
+ ULONG testValue = 7;
+ status = Dsp_SendTestPropertyTo(
+ Device,
+ Circuit,
+ SDCAVADPropsetId,
+ SDCAVAD_PROPERTY_TEST1,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ &testValue, sizeof(ULONG),
+ nullptr);
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST1 SET :%!STATUS!, Value = %d", status, testValue);
+
+ status = Dsp_SendTestPropertyTo(
+ Device,
+ Circuit,
+ SDCAVADPropsetId,
+ SDCAVAD_PROPERTY_TEST2,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ &testValue, sizeof(ULONG),
+ nullptr);
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST2 GET :%!STATUS!, Value = %d", status, testValue);
+
+ testValue = 8;
+ status = Dsp_SendTestPropertyTo(
+ Device,
+ Circuit,
+ SDCAVADPropsetId,
+ SDCAVAD_PROPERTY_TEST3,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ &testValue, sizeof(ULONG),
+ nullptr);
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST3 SET :%!STATUS!, Value = %d", status, testValue);
+
+ status = Dsp_SendTestPropertyTo(
+ Device,
+ Circuit,
+ SDCAVADPropsetId,
+ SDCAVAD_PROPERTY_TEST4,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ &testValue, sizeof(ULONG),
+ nullptr);
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST4 GET :%!STATUS!, Value = %d", status, testValue);
+
+ testValue = 9;
+ status = Dsp_SendTestPropertyTo(
+ Device,
+ Circuit,
+ SDCAVADPropsetId,
+ SDCAVAD_PROPERTY_TEST5,
+ AcxPropertyVerbSet,
+ nullptr, 0,
+ &testValue, sizeof(ULONG),
+ nullptr);
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST5 SET :%!STATUS!, Value = %d", status, testValue);
+
+ status = Dsp_SendTestPropertyTo(
+ Device,
+ Circuit,
+ SDCAVADPropsetId,
+ SDCAVAD_PROPERTY_TEST6,
+ AcxPropertyVerbGet,
+ nullptr, 0,
+ &testValue, sizeof(ULONG),
+ nullptr);
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST6 GET :%!STATUS!, Value = %d", status, testValue);
+
+ status = STATUS_SUCCESS;
+
+ if (pinCtx->PinType != DspPinTypeOffload)
+ {
+ //
+ // Set circuit-callbacks.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRequestPreprocessCallback(
+ StreamInit,
+ DspR_EvtStreamRequestPreprocess,
+ (ACXCONTEXT)AcxRequestTypeAny, // dbg only
+ AcxRequestTypeAny,
+ NULL,
+ AcxItemIdNone));
+ }
+
+ //
+ // Request a Vendor-Specific property from the Controller
+ //
+ Dsp_SendVendorSpecificProperties(
+ Device,
+ Circuit,
+ TRUE);
+
+ /*
+ //
+ // Add properties, events and methods.
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignProperties(StreamInit,
+ StreamProperties,
+ StreamPropertiesCount));
+ */
+
+ //
+ // Init streaming callbacks.
+ //
+ ACX_STREAM_CALLBACKS streamCallbacks;
+ ACX_STREAM_CALLBACKS_INIT(&streamCallbacks);
+ streamCallbacks.EvtAcxStreamPrepareHardware = Dsp_EvtStreamPrepareHardware;
+ streamCallbacks.EvtAcxStreamReleaseHardware = Dsp_EvtStreamReleaseHardware;
+ streamCallbacks.EvtAcxStreamRun = Dsp_EvtStreamRun;
+ streamCallbacks.EvtAcxStreamPause = Dsp_EvtStreamPause;
+ streamCallbacks.EvtAcxStreamAssignDrmContentId = Dsp_EvtStreamAssignDrmContentId;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxStreamCallbacks(StreamInit, &streamCallbacks));
+
+ //
+ // Init RT streaming callbacks.
+ //
+ ACX_RT_STREAM_CALLBACKS rtCallbacks;
+ ACX_RT_STREAM_CALLBACKS_INIT(&rtCallbacks);
+ rtCallbacks.EvtAcxStreamGetHwLatency = Dsp_EvtStreamGetHwLatency;
+ rtCallbacks.EvtAcxStreamAllocateRtPackets = Dsp_EvtStreamAllocateRtPackets;
+ rtCallbacks.EvtAcxStreamFreeRtPackets = Dsp_EvtStreamFreeRtPackets;
+ rtCallbacks.EvtAcxStreamSetRenderPacket = DspR_EvtStreamSetRenderPacket;
+ rtCallbacks.EvtAcxStreamGetCurrentPacket = Dsp_EvtStreamGetCurrentPacket;
+ rtCallbacks.EvtAcxStreamGetPresentationPosition = Dsp_EvtStreamGetPresentationPosition;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRtStreamCallbacks(StreamInit, &rtCallbacks));
+
+ //
+ // Buffer notifications are supported.
+ //
+ AcxStreamInitSetAcxRtStreamSupportsNotifications(StreamInit);
+
+ //
+ // Create the stream.
+ //
+ WDF_OBJECT_ATTRIBUTES attributes;
+ ACXSTREAM stream;
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_STREAM_CONTEXT);
+ attributes.EvtDestroyCallback = Dsp_EvtStreamContextDestroy;
+ attributes.EvtCleanupCallback = Dsp_EvtStreamContextCleanup;
+
+
+ RETURN_NTSTATUS_IF_FAILED(AcxRtStreamCreate(Device, Circuit, &attributes, &StreamInit, &stream));
+
+ PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(Circuit);
+ ASSERT(circuitCtx);
+
+ CStreamEngine* streamEngine = NULL;
+ if (pinCtx->PinType == DspPinTypeOffload)
+ {
+ streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) COffloadStreamEngine(stream, DataFormat, (CSimPeakMeter *)circuitCtx->peakMeter);
+ }
+ else
+ {
+ streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CRenderStreamEngine(stream, DataFormat, (CSimPeakMeter *)circuitCtx->peakMeter);
+ }
+ RETURN_NTSTATUS_IF_TRUE(NULL == streamEngine, STATUS_INSUFFICIENT_RESOURCES);
+
+ DSP_STREAM_CONTEXT* streamCtx;
+ streamCtx = GetDspStreamContext(stream);
+ ASSERT(streamCtx);
+ streamCtx->StreamEngine = (PVOID)streamEngine;
+ streamEngine = NULL;
+ streamCtx->PinType = pinCtx->PinType;
+
+ if (DspPinTypeLoopback == pinCtx->PinType &&
+ circuitCtx->SpecialStreamAvailablePaths & SdcaPathReferenceStream)
+ {
+ WdfObjectReferenceWithTag(circuitCtx->SpecialStreamTargetCircuit, (PVOID)DRIVER_TAG);
+ streamCtx->SpecialStreamTargetCircuit = circuitCtx->SpecialStreamTargetCircuit;
+ }
+
+ if ((DspPinTypeHost == pinCtx->PinType || DspPinTypeOffload == pinCtx->PinType) &&
+ circuitCtx->SpecialStreamAvailablePaths & SdcaPathIvSense)
+ {
+ WdfObjectReferenceWithTag(circuitCtx->SpecialStreamTargetCircuit, (PVOID)DRIVER_TAG);
+ streamCtx->SpecialStreamTargetCircuit = circuitCtx->SpecialStreamTargetCircuit;
+ }
+
+ //
+ // Post stream creation initialization.
+ //
+
+ if (circuitCtx->AudioEngineElement != nullptr)
+ {
+ //
+ // The circuit has an Audio Engine element, so all streams created for the circuit
+ // also require an Audio Engine element to allow the OS to
+ // * Adjust per-stream volume and mute
+ // * Monitor per-stream peakmeter values
+ // * Retrieve stream position
+ // * Set stream effects state
+ //
+
+ //
+ // Volume Element
+ //
+ ACX_VOLUME_CALLBACKS volumeCallbacks;
+ ACX_VOLUME_CALLBACKS_INIT(&volumeCallbacks);
+ volumeCallbacks.EvtAcxRampedVolumeAssignLevel = DspR_EvtRampedVolumeAssignLevel;
+ volumeCallbacks.EvtAcxVolumeRetrieveLevel = DspR_EvtVolumeRetrieveLevel;
+
+ // Create Volume element for the audio engine to use
+ ACX_VOLUME_CONFIG volumeCfg;
+ ACX_VOLUME_CONFIG_INIT(&volumeCfg);
+ volumeCfg.ChannelsCount = MAX_CHANNELS;
+ volumeCfg.Minimum = VOLUME_LEVEL_MINIMUM;
+ volumeCfg.Maximum = VOLUME_LEVEL_MAXIMUM;
+ volumeCfg.SteppingDelta = VOLUME_STEPPING;
+ volumeCfg.Name = &KSAUDFNAME_VOLUME_CONTROL;
+ volumeCfg.Callbacks = &volumeCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_VOLUME_ELEMENT_CONTEXT);
+ attributes.ParentObject = stream;
+
+ ACXVOLUME volumeElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxVolumeCreate(stream, &attributes, &volumeCfg, &volumeElement));
+
+ //
+ // Mute Element
+ //
+ ACX_MUTE_CALLBACKS muteCallbacks;
+ ACX_MUTE_CALLBACKS_INIT(&muteCallbacks);
+ muteCallbacks.EvtAcxMuteAssignState = DspR_EvtMuteAssignState;
+ muteCallbacks.EvtAcxMuteRetrieveState = DspR_EvtMuteRetrieveState;
+
+ ACX_MUTE_CONFIG muteCfg;
+ ACX_MUTE_CONFIG_INIT(&muteCfg);
+ muteCfg.ChannelsCount = MAX_CHANNELS;
+ muteCfg.Name = &KSAUDFNAME_WAVE_MUTE;
+ muteCfg.Callbacks = &muteCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_MUTE_ELEMENT_CONTEXT);
+ attributes.ParentObject = stream;
+
+ ACXMUTE muteElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxMuteCreate(stream, &attributes, &muteCfg, &muteElement));
+
+ //
+ // Peakmeter Element
+ //
+ ACX_PEAKMETER_CALLBACKS peakmeterCallbacks;
+ ACX_PEAKMETER_CALLBACKS_INIT(&peakmeterCallbacks);
+ peakmeterCallbacks.EvtAcxPeakMeterRetrieveLevel = DspR_EvtPeakMeterRetrieveLevelCallback;
+
+ ACX_PEAKMETER_CONFIG peakmeterCfg;
+ ACX_PEAKMETER_CONFIG_INIT(&peakmeterCfg);
+ peakmeterCfg.ChannelsCount = MAX_CHANNELS;
+ peakmeterCfg.Minimum = PEAKMETER_MINIMUM;
+ peakmeterCfg.Maximum = PEAKMETER_MAXIMUM;
+ peakmeterCfg.SteppingDelta = PEAKMETER_STEPPING_DELTA;
+ peakmeterCfg.Callbacks = &peakmeterCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PEAKMETER_ELEMENT_CONTEXT);
+ attributes.ParentObject = stream;
+
+ ACXPEAKMETER peakmeterElement;
+ RETURN_NTSTATUS_IF_FAILED(AcxPeakMeterCreate(stream, &attributes, &peakmeterCfg, &peakmeterElement));
+
+ PDSP_PEAKMETER_ELEMENT_CONTEXT peakmeterCtx;
+ ASSERT(peakmeterElement != NULL);
+ peakmeterCtx = GetDspPeakMeterElementContext(peakmeterElement);
+ ASSERT(peakmeterCtx);
+ peakmeterCtx->peakMeter = ((CStreamEngine*)streamCtx->StreamEngine)->GetPeakMeter();
+
+ //
+ // Stream Audio Engine Node
+ //
+ ACX_STREAMAUDIOENGINE_CALLBACKS streamAudioEngineCallbacks;
+ // Create the AudioEngine element to control offloaded streaming.
+ ACX_STREAMAUDIOENGINE_CALLBACKS_INIT(&streamAudioEngineCallbacks);
+ streamAudioEngineCallbacks.EvtAcxStreamAudioEngineAssignEffectsState = DspR_EvtAcxStreamAudioEngineAssignEffectsState;
+ streamAudioEngineCallbacks.EvtAcxStreamAudioEngineRetrieveEffectsState = DspR_EvtAcxStreamAudioEngineRetrieveEffectsState;
+ streamAudioEngineCallbacks.EvtAcxStreamAudioEngineRetrievePresentationPosition = DspR_EvtAcxStreamAudioEngineRetrievePresentationPosition;
+ streamAudioEngineCallbacks.EvtAcxStreamAudioEngineAssignCurrentWritePosition = DspR_EvtAcxStreamAudioEngineAssignCurrentWritePosition;
+ streamAudioEngineCallbacks.EvtAcxStreamAudioEngineRetrieveLinearBufferPosition = DspR_EvtAcxStreamAudioEngineRetrieveLinearBufferPosition;
+ streamAudioEngineCallbacks.EvtAcxStreamAudioEngineAssignLastBufferPosition = DspR_EvtAcxStreamAudioEngineAssignLastBufferPosition;
+ streamAudioEngineCallbacks.EvtAcxStreamAudioEngineAssignLoopbackProtection = DspR_EvtAcxStreamAudioEngineAssignLoopbackProtection;
+
+ ACX_STREAMAUDIOENGINE_CONFIG audioEngineCfg;
+ ACX_STREAMAUDIOENGINE_CONFIG_INIT(&audioEngineCfg);
+ audioEngineCfg.VolumeElement = volumeElement;
+ audioEngineCfg.MuteElement = muteElement;
+ audioEngineCfg.PeakMeterElement = peakmeterElement;
+ audioEngineCfg.Callbacks = &streamAudioEngineCallbacks;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_STREAMAUDIOENGINE_CONTEXT);
+ attributes.ParentObject = stream;
+
+ ACXELEMENT streamAudioEngine;
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamAudioEngineCreate(stream, circuitCtx->AudioEngineElement, &attributes, &audioEngineCfg, (ACXSTREAMAUDIOENGINE*)&streamAudioEngine));
+
+ // Set local effects as disabled
+ PDSP_STREAMAUDIOENGINE_CONTEXT pStreamAudioEngineCtx;
+ pStreamAudioEngineCtx = GetDspStreamAudioEngineContext(streamAudioEngine);
+ pStreamAudioEngineCtx->LFxEnabled = FALSE;
+
+
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, &streamAudioEngine, 1));
+
+ // Add our stream audio modules
+ RETURN_NTSTATUS_IF_FAILED(DspR_CreateStreamModules(Device, stream));
+ }
+ else
+ {
+ //
+ // Create 1st custom stream-elements.
+ //
+ ACX_ELEMENT_CONFIG elementCfg;
+ ACX_ELEMENT_CONFIG_INIT(&elementCfg);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT);
+ attributes.ParentObject = stream;
+
+ ACXELEMENT elements[2] = { 0 };
+ RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[0]));
+
+ ASSERT(elements[0] != NULL);
+ DSP_ELEMENT_CONTEXT* elementCtx;
+ elementCtx = GetDspElementContext(elements[0]);
+ ASSERT(elementCtx);
+ UNREFERENCED_PARAMETER(elementCtx);
+
+ //
+ // Create 2nd custom stream-elements.
+ //
+ ACX_ELEMENT_CONFIG_INIT(&elementCfg);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT);
+ attributes.ParentObject = stream;
+
+ RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[1]));
+
+ ASSERT(elements[1] != NULL);
+ elementCtx = GetDspElementContext(elements[1]);
+ ASSERT(elementCtx);
+ UNREFERENCED_PARAMETER(elementCtx);
+
+ //
+ // Add stream elements
+ //
+ RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, elements, SIZEOF_ARRAY(elements)));
+
+ // Add our stream audio modules
+ RETURN_NTSTATUS_IF_FAILED(DspR_CreateStreamModules(Device, stream));
+ }
+
+// See description in private.h
+#ifdef ACX_WORKAROUND_ACXPIN_01
+ {
+ ASSERT(pinCtx->CurrentStreamsCount != (ULONG)-1);
+ InterlockedIncrement(PLONG(&pinCtx->CurrentStreamsCount));
+ streamCtx->StreamIsCounted = TRUE;
+ }
+#endif
+
+ streamCtx->Pin = Pin;
+ WdfObjectReferenceWithTag(Pin, (PVOID)DRIVER_TAG);
+
+ return status;
+}
+
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtStreamSetRenderPacket(
+ _In_ ACXSTREAM Stream,
+ _In_ ULONG Packet,
+ _In_ ULONG Flags,
+ _In_ ULONG EosPacketLength
+ )
+{
+ PDSP_STREAM_CONTEXT ctx;
+ CRenderStreamEngine * streamEngine = NULL;
+
+ PAGED_CODE();
+
+ ctx = GetDspStreamContext(Stream);
+
+ streamEngine = static_cast<CRenderStreamEngine*>(ctx->StreamEngine);
+
+ return streamEngine->SetRenderPacket(Packet, Flags, EosPacketLength);
+}
+
+//
+//#pragma code_seg()
+//NTSTATUS
+//DspR_EvtAcxCircuitProcess(
+// _In_ ACXCIRCUIT Circuit,
+// _In_ ACXSTREAMIO Stream
+// )
+//{
+// UNREFERENCED_PARAMETER(Circuit);
+// UNREFERENCED_PARAMETER(Stream);
+//
+// return STATUS_SUCCESS;
+//}
+//
+
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/renderAudioEngine.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/renderAudioEngine.cpp
new file mode 100644
index 00000000..e9bb9496
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/renderAudioEngine.cpp
@@ -0,0 +1,502 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ renderaudioengine.cpp
+
+Abstract:
+
+ Render Audio Engine - callbacks for Audio Engine Node
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include <devguid.h>
+#include "stdunk.h"
+#include <ks.h>
+#include <mmsystem.h>
+#include <ksmedia.h>
+#include "offloadStreamEngine.h"
+#include "SimPeakMeter.h"
+
+#include "TestProperties.h"
+#include "AudioFormats.h"
+
+#ifndef __INTELLISENSE__
+#include "renderaudioengine.tmh"
+#endif
+
+// Sizes for min/max for audioengine buffers
+// Buffer duration is for both ping and pong buffers combined
+// so multiply it by 2
+#define MIN_AUDIOENGINE_BUFFER_DURATION_IN_MS (10 * 2)
+#define MAX_AUDIOENGINE_BUFFER_DURATION_IN_MS (2000 * 2)
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxAudioEngineRetrieveBufferSizeLimits(
+ ACXAUDIOENGINE,
+ ACXDATAFORMAT DataFormat,
+ PULONG MinBufferBytes,
+ PULONG MaxBufferBytes
+ )
+{
+ PAGED_CODE();
+
+ ULONG bytesPerSecond = AcxDataFormatGetAverageBytesPerSec(DataFormat);
+
+ *MinBufferBytes = (ULONG) (MIN_AUDIOENGINE_BUFFER_DURATION_IN_MS * bytesPerSecond / 1000);
+ *MaxBufferBytes = (ULONG) (MAX_AUDIOENGINE_BUFFER_DURATION_IN_MS * bytesPerSecond / 1000);
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxAudioEngineRetrieveEffectsState(
+ ACXAUDIOENGINE AudioEngine,
+ PULONG State
+)
+{
+ PAGED_CODE();
+
+ PDSP_ENGINE_CONTEXT pAudioEngineCtx;
+ pAudioEngineCtx = GetDspEngineContext(AudioEngine);
+
+ *State = pAudioEngineCtx->GFxEnabled;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxAudioEngineAssignEffectsState(
+ ACXAUDIOENGINE AudioEngine,
+ ULONG State
+)
+{
+ PAGED_CODE();
+
+ PDSP_ENGINE_CONTEXT pAudioEngineCtx;
+ pAudioEngineCtx = GetDspEngineContext(AudioEngine);
+
+ pAudioEngineCtx->GFxEnabled = (BOOLEAN)State;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxStreamAudioEngineRetrieveEffectsState(
+ ACXSTREAMAUDIOENGINE StreamAudioEngine,
+ PULONG State
+)
+{
+ PAGED_CODE();
+
+ PDSP_STREAMAUDIOENGINE_CONTEXT pStreamAudioEngineCtx;
+ pStreamAudioEngineCtx = GetDspStreamAudioEngineContext(StreamAudioEngine);
+
+ *State = pStreamAudioEngineCtx->LFxEnabled;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxStreamAudioEngineAssignEffectsState(
+ ACXSTREAMAUDIOENGINE StreamAudioEngine,
+ ULONG State
+)
+{
+ PAGED_CODE();
+
+ PDSP_STREAMAUDIOENGINE_CONTEXT pStreamAudioEngineCtx;
+ pStreamAudioEngineCtx = GetDspStreamAudioEngineContext(StreamAudioEngine);
+
+ pStreamAudioEngineCtx->LFxEnabled = (BOOLEAN)State;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxAudioEngineRetrieveEngineMixFormat(
+ ACXAUDIOENGINE AudioEngine,
+ ACXDATAFORMAT * Format
+ )
+{
+ PDSP_ENGINE_CONTEXT audioEngineCtx;
+ PAGED_CODE();
+
+ audioEngineCtx = GetDspEngineContext(AudioEngine);
+
+ if (!audioEngineCtx->MixFormat)
+ {
+ return STATUS_INVALID_DEVICE_STATE;
+ }
+
+ *Format = audioEngineCtx->MixFormat;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxAudioEngineAssignEngineDeviceFormat(
+ _In_ ACXAUDIOENGINE AudioEngine,
+ _In_ ACXDATAFORMAT Format
+ )
+{
+ PAGED_CODE();
+
+ // Get the downstream pin
+ ACXCIRCUIT parentCircuit = (ACXCIRCUIT)AcxElementGetContainer((ACXELEMENT)AudioEngine);
+
+ ACXPIN downstreamPin = AcxCircuitGetPinById(parentCircuit, DspPinTypeBridge);
+ if (!downstreamPin)
+ {
+ RETURN_NTSTATUS(STATUS_INTERNAL_ERROR);
+ }
+
+ // Start by getting the list of formats for the raw mode
+ ACXDATAFORMATLIST formatList;
+ RETURN_NTSTATUS_IF_FAILED(AcxPinRetrieveModeDataFormatList(downstreamPin, &AUDIO_SIGNALPROCESSINGMODE_RAW, &formatList));
+
+ // Find the format we were given in that list.
+ NTSTATUS status = STATUS_NO_MATCH;
+
+ ACX_DATAFORMAT_LIST_ITERATOR formatListIter;
+ ACX_DATAFORMAT_LIST_ITERATOR_INIT(&formatListIter);
+ AcxDataFormatListBeginIteration(formatList, &formatListIter);
+
+ ACXDATAFORMAT listFormat;
+ while (NT_SUCCESS(AcxDataFormatListRetrieveNextFormat(formatList, &formatListIter, &listFormat)))
+ {
+ if (AcxDataFormatIsEqual(listFormat, Format))
+ {
+ // Assign the format as the default format.
+ // Note there is an existing ACX issue with default format assignment - assigning the default
+ // will only work if the format is already in the list (or is the first format added to the list).
+ AcxDataFormatListAssignDefaultDataFormat(formatList, listFormat);
+
+ // Use the format we pulled out of our list since it will have an appropriate lifetime
+ PDSP_ENGINE_CONTEXT audioEngineCtx;
+ audioEngineCtx = GetDspEngineContext(AudioEngine);
+ audioEngineCtx->MixFormat = listFormat;
+
+ status = STATUS_SUCCESS;
+
+ break;
+ }
+ }
+ AcxDataFormatListEndIteration(formatList, &formatListIter);
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtPeakMeterRetrieveLevelCallback(
+ ACXPEAKMETER PeakMeter,
+ ULONG Channel,
+ LONG * PeakMeterLevel
+ )
+{
+ PAGED_CODE();
+
+ ASSERT(PeakMeter);
+
+ if (Channel == ALL_CHANNELS_ID)
+ {
+ Channel = 0;
+ }
+
+ PDSP_PEAKMETER_ELEMENT_CONTEXT peakmeterCtx = GetDspPeakMeterElementContext(PeakMeter);
+ ASSERT(peakmeterCtx);
+ CSimPeakMeter* peakMeter = (CSimPeakMeter *)peakmeterCtx->peakMeter;
+ *PeakMeterLevel = peakMeter->GetValue(Channel);
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtMuteAssignState(
+ ACXMUTE Mute,
+ ULONG Channel,
+ ULONG State
+ )
+{
+ PDSP_MUTE_ELEMENT_CONTEXT muteCtx;
+ ULONG i;
+
+ PAGED_CODE();
+
+ muteCtx = GetDspMuteElementContext(Mute);
+ ASSERT(muteCtx);
+
+ if (Channel != ALL_CHANNELS_ID)
+ {
+ muteCtx->MuteState[Channel] = State;
+ }
+ else
+ {
+ for (i = 0; i < MAX_CHANNELS; ++i)
+ {
+ muteCtx->MuteState[i] = State;
+ }
+ }
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtMuteRetrieveState(
+ ACXMUTE Mute,
+ ULONG Channel,
+ ULONG * State
+ )
+{
+ PDSP_MUTE_ELEMENT_CONTEXT muteCtx;
+
+ PAGED_CODE();
+
+ muteCtx = GetDspMuteElementContext(Mute);
+ ASSERT(muteCtx);
+
+ // use first channel for all channels setting.
+ if (Channel != ALL_CHANNELS_ID)
+ {
+ *State = muteCtx->MuteState[Channel];
+ }
+ else
+ {
+ *State = muteCtx->MuteState[0];
+ }
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtRampedVolumeAssignLevel(
+ ACXVOLUME Volume,
+ ULONG Channel,
+ LONG VolumeLevel,
+ ACX_VOLUME_CURVE_TYPE,
+ ULONGLONG
+ )
+{
+ PDSP_VOLUME_ELEMENT_CONTEXT volumeCtx;
+ ULONG i;
+
+ PAGED_CODE();
+
+ volumeCtx = GetDspVolumeElementContext(Volume);
+ ASSERT(volumeCtx);
+
+ if (Channel != ALL_CHANNELS_ID)
+ {
+ volumeCtx->VolumeLevel[Channel] = VolumeLevel;
+ }
+ else
+ {
+ for (i = 0; i < MAX_CHANNELS; ++i)
+ {
+ volumeCtx->VolumeLevel[i] = VolumeLevel;
+ }
+ }
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtVolumeRetrieveLevel(
+ ACXVOLUME Volume,
+ ULONG Channel,
+ LONG * VolumeLevel
+)
+{
+ PDSP_VOLUME_ELEMENT_CONTEXT volumeCtx;
+
+ PAGED_CODE();
+
+ volumeCtx = GetDspVolumeElementContext(Volume);
+ ASSERT(volumeCtx);
+
+ if (Channel != ALL_CHANNELS_ID)
+ {
+ *VolumeLevel = volumeCtx->VolumeLevel[Channel];
+ }
+ else
+ {
+ *VolumeLevel = volumeCtx->VolumeLevel[0];
+ }
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxStreamAudioEngineRetrievePresentationPosition(
+ _In_ ACXSTREAMAUDIOENGINE StreamAudioEngine,
+ _Out_ PULONGLONG PositionInBlocks,
+ _Out_ PULONGLONG QPCPosition
+)
+{
+ NTSTATUS status = STATUS_INVALID_PARAMETER;
+ ACXSTREAM stream;
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine* streamEngine = NULL;
+
+ PAGED_CODE();
+
+ stream = AcxStreamAudioEngineGetStream(StreamAudioEngine);
+ if (stream)
+ {
+ ctx = GetDspStreamContext(stream);
+
+ streamEngine = static_cast<CStreamEngine*>(ctx->StreamEngine);
+
+ status = streamEngine->GetPresentationPosition(PositionInBlocks, QPCPosition);
+ }
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxStreamAudioEngineAssignCurrentWritePosition(
+ _In_ ACXSTREAMAUDIOENGINE StreamAudioEngine,
+ _In_ ULONG Position
+)
+{
+ NTSTATUS status = STATUS_INVALID_PARAMETER;
+ ACXSTREAM stream;
+ PDSP_STREAM_CONTEXT ctx;
+ COffloadStreamEngine* streamEngine = NULL;
+
+ PAGED_CODE();
+
+ stream = AcxStreamAudioEngineGetStream(StreamAudioEngine);
+ if (stream)
+ {
+ ctx = GetDspStreamContext(stream);
+
+ if (ctx->PinType == DspPinTypeOffload)
+ {
+ streamEngine = static_cast<COffloadStreamEngine*>(ctx->StreamEngine);
+
+ status = streamEngine->SetCurrentWritePosition(Position);
+ }
+ else
+ {
+ status = STATUS_NOT_SUPPORTED;
+ }
+ }
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxStreamAudioEngineRetrieveLinearBufferPosition(
+ _In_ ACXSTREAMAUDIOENGINE StreamAudioEngine,
+ _Out_ PULONGLONG Position
+)
+{
+ NTSTATUS status = STATUS_INVALID_PARAMETER;
+ ACXSTREAM stream;
+ PDSP_STREAM_CONTEXT ctx;
+ CStreamEngine* streamEngine = NULL;
+
+ PAGED_CODE();
+
+ stream = AcxStreamAudioEngineGetStream(StreamAudioEngine);
+ if (stream)
+ {
+ ctx = GetDspStreamContext(stream);
+
+ streamEngine = static_cast<CStreamEngine*>(ctx->StreamEngine);
+
+ status = streamEngine->GetLinearBufferPosition(Position);
+ }
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxStreamAudioEngineAssignLastBufferPosition(
+ _In_ ACXSTREAMAUDIOENGINE StreamAudioEngine,
+ _In_ ULONG Position
+)
+{
+ NTSTATUS status = STATUS_INVALID_PARAMETER;
+ ACXSTREAM stream;
+ PDSP_STREAM_CONTEXT ctx;
+ COffloadStreamEngine* streamEngine = NULL;
+
+ PAGED_CODE();
+
+ stream = AcxStreamAudioEngineGetStream(StreamAudioEngine);
+ if (stream)
+ {
+ ctx = GetDspStreamContext(stream);
+
+ if (ctx->PinType == DspPinTypeOffload)
+ {
+ streamEngine = static_cast<COffloadStreamEngine*>(ctx->StreamEngine);
+
+ status = streamEngine->SetLastBufferPosition(Position);
+ }
+ else
+ {
+ status = STATUS_NOT_SUPPORTED;
+ }
+ }
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+DspR_EvtAcxStreamAudioEngineAssignLoopbackProtection(
+ _In_ ACXSTREAMAUDIOENGINE,
+ _In_ ACX_CONSTRICTOR_OPTION
+)
+{
+ PAGED_CODE();
+
+ return STATUS_SUCCESS;
+}
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/resources.rc b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/resources.rc
new file mode 100644
index 00000000..a2cc093a
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/resources.rc
@@ -0,0 +1,12 @@
+#include <windows.h>
+
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DRV
+#define VER_FILESUBTYPE VFT2_DRV_SYSTEM
+#define VER_FILEDESCRIPTION_STR "ACX v1.0 DSP Audio Driver"
+#define VER_INTERNALNAME_STR "SDCAVDsp.sys"
+#define VER_ORIGINALFILENAME_STR "SDCAVDsp.sys"
+
+#include "common.ver"
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/savedata.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/savedata.cpp
new file mode 100644
index 00000000..4d70ad09
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/savedata.cpp
@@ -0,0 +1,1043 @@
+/*++
+
+Copyright (c) Microsoft Corporation All Rights Reserved
+
+Module Name:
+
+ savedata.cpp
+
+Abstract:
+
+ Implementation of ACX DSP Test Driver data saving class.
+
+ To save the playback data to disk, this class maintains a circular data
+ buffer, associated frame structures and worker items to save frames to
+ disk.
+ Each frame structure represents a portion of buffer. When that portion
+ of frame is full, a workitem is scheduled to save it to disk.
+
+
+
+--*/
+#pragma warning (disable : 4127)
+#pragma warning (disable : 26165)
+
+
+#include "private.h"
+#include <devguid.h>
+#include "stdunk.h"
+#include <ks.h>
+#include <mmsystem.h>
+#include <ksmedia.h>
+#include "savedata.h"
+#include <ntstrsafe.h> // This is for using RtlStringCbPrintf
+
+#define SAVEDATA_POOLTAG 'TDVS'
+#define SAVEDATA_POOLTAG1 '1DVS'
+#define SAVEDATA_POOLTAG2 '2DVS'
+#define SAVEDATA_POOLTAG3 '3DVS'
+#define SAVEDATA_POOLTAG4 '4DVS'
+#define SAVEDATA_POOLTAG5 '5DVS'
+#define SAVEDATA_POOLTAG6 '6DVS'
+#define SAVEDATA_POOLTAG7 '7DVS'
+
+//=============================================================================
+// Defines
+//=============================================================================
+#define RIFF_TAG 0x46464952;
+#define WAVE_TAG 0x45564157;
+#define FMT__TAG 0x20746D66;
+#define DATA_TAG 0x61746164;
+
+#define DEFAULT_FRAME_COUNT 4
+#define DEFAULT_FRAME_SIZE PAGE_SIZE * 4
+#define DEFAULT_BUFFER_SIZE DEFAULT_FRAME_SIZE * DEFAULT_FRAME_COUNT
+
+#define DEFAULT_FILE_NAME L"\\DosDevices\\C:\\STREAM"
+#define OFFLOAD_FILE_NAME L"OFFLOAD"
+#define HOST_FILE_NAME L"HOST"
+
+#define MAX_WORKER_ITEM_COUNT 15
+
+
+PSAVEWORKER_PARAM CSaveData::m_pWorkItems = NULL;
+PDEVICE_OBJECT CSaveData::m_pDeviceObject = NULL;
+
+//=============================================================================
+// Statics
+//=============================================================================
+ULONG CSaveData::m_ulStreamId = 0;
+ULONG CSaveData::m_ulOffloadStreamId = 0;
+
+//=============================================================================
+// CSaveData
+//=============================================================================
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CSaveData::CSaveData()
+: m_pDataBuffer(NULL),
+ m_FileHandle(NULL),
+ m_ulFrameCount(DEFAULT_FRAME_COUNT),
+ m_ulBufferSize(DEFAULT_BUFFER_SIZE),
+ m_ulFrameSize(DEFAULT_FRAME_SIZE),
+ m_ulBufferOffset(0),
+ m_ulFrameIndex(0),
+ m_fFrameUsed(NULL),
+ m_waveFormat(NULL),
+ m_pFilePtr(NULL),
+ m_fWriteDisabled(FALSE),
+ m_bInitialized(FALSE)
+{
+ PAGED_CODE();
+
+ m_FileHeader.dwRiff = RIFF_TAG;
+ m_FileHeader.dwFileSize = 0;
+ m_FileHeader.dwWave = WAVE_TAG;
+ m_FileHeader.dwFormat = FMT__TAG;
+ m_FileHeader.dwFormatLength = sizeof(WAVEFORMATEX);
+
+ m_DataHeader.dwData = DATA_TAG;
+ m_DataHeader.dwDataLength = 0;
+
+ RtlZeroMemory(&m_objectAttributes, sizeof(m_objectAttributes));
+} // CSaveData
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CSaveData::~CSaveData()
+{
+ PAGED_CODE();
+ Cleanup();
+} // CSaveData
+
+void
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CSaveData::Cleanup
+(
+ void
+)
+{
+ PAGED_CODE();
+
+ // Update the wave header in data file with real file size.
+ //
+ if(m_pFilePtr)
+ {
+ // RIFF header, whose size is the whole file size minus RIFF header.
+ m_FileHeader.dwFileSize =
+ (DWORD)m_pFilePtr->QuadPart - 2 * sizeof(DWORD);
+ // The data length is the size of all the audio that was written.
+ // It gets calculated by taking:
+ m_DataHeader.dwDataLength = (DWORD)m_pFilePtr->QuadPart - // the whole file size,
+ sizeof(m_FileHeader) - // minus the file header,
+ m_FileHeader.dwFormatLength - // minus the format,
+ sizeof(m_DataHeader); // minus the data header itself.
+
+ if (STATUS_SUCCESS == KeWaitForSingleObject
+ (
+ &m_FileSync,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL
+ ))
+ {
+ if (NT_SUCCESS(FileOpen(FALSE)))
+ {
+ FileWriteHeader();
+
+ FileClose();
+ }
+
+ KeReleaseMutex(&m_FileSync, FALSE);
+ }
+
+ m_FileHeader.dwRiff = RIFF_TAG;
+ m_FileHeader.dwFileSize = 0;
+ m_FileHeader.dwWave = WAVE_TAG;
+ m_FileHeader.dwFormat = FMT__TAG;
+ m_FileHeader.dwFormatLength = sizeof(WAVEFORMATEX);
+
+ m_DataHeader.dwData = DATA_TAG;
+ m_DataHeader.dwDataLength = 0;
+ m_pFilePtr = NULL;
+ }
+
+ if (m_waveFormat)
+ {
+ ExFreePoolWithTag(m_waveFormat, SAVEDATA_POOLTAG1);
+ m_waveFormat = NULL;
+ }
+
+ if (m_fFrameUsed)
+ {
+ ExFreePoolWithTag(m_fFrameUsed, SAVEDATA_POOLTAG2);
+ m_fFrameUsed = NULL;
+ }
+
+ if (m_FileName.Buffer)
+ {
+ ExFreePoolWithTag(m_FileName.Buffer, SAVEDATA_POOLTAG3);
+ m_FileName.Buffer = NULL;
+ }
+
+ if (m_pDataBuffer)
+ {
+ ExFreePoolWithTag(m_pDataBuffer, SAVEDATA_POOLTAG4);
+ m_pDataBuffer = NULL;
+ }
+}
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+void
+CSaveData::DestroyWorkItems
+(
+ void
+)
+{
+ PAGED_CODE();
+
+ if (m_pWorkItems)
+ {
+ for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++)
+ {
+ if (m_pWorkItems[i].WorkItem!=NULL)
+ {
+ IoFreeWorkItem(m_pWorkItems[i].WorkItem);
+ m_pWorkItems[i].WorkItem = NULL;
+ }
+ }
+ ExFreePoolWithTag(m_pWorkItems, SAVEDATA_POOLTAG);
+ m_pWorkItems = NULL;
+ }
+
+} // DestroyWorkItems
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+void
+CSaveData::Disable
+(
+ _In_ BOOL fDisable
+)
+{
+ PAGED_CODE();
+
+ m_fWriteDisabled = fDisable;
+} // Disable
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CSaveData::FileClose(void)
+{
+ PAGED_CODE();
+
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ if (m_FileHandle)
+ {
+ ntStatus = ZwClose(m_FileHandle);
+ m_FileHandle = NULL;
+ }
+
+ return ntStatus;
+} // FileClose
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CSaveData::FileOpen
+(
+ BOOL fOverWrite
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+ IO_STATUS_BLOCK ioStatusBlock;
+
+ if( FALSE == m_bInitialized )
+ {
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ if(!m_FileHandle)
+ {
+ ntStatus =
+ ZwCreateFile
+ (
+ &m_FileHandle,
+ GENERIC_WRITE | SYNCHRONIZE,
+ &m_objectAttributes,
+ &ioStatusBlock,
+ NULL,
+ FILE_ATTRIBUTE_NORMAL,
+ 0,
+ fOverWrite ? FILE_OVERWRITE_IF : FILE_OPEN_IF,
+ FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
+ NULL,
+ 0
+ );
+ }
+
+ return ntStatus;
+} // FileOpen
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CSaveData::FileWrite
+(
+ PBYTE pData,
+ ULONG ulDataSize
+)
+{
+ PAGED_CODE();
+
+ ASSERT(pData);
+ ASSERT(m_pFilePtr);
+
+ NTSTATUS ntStatus;
+
+ if (m_FileHandle)
+ {
+ IO_STATUS_BLOCK ioStatusBlock;
+
+ ntStatus = ZwWriteFile( m_FileHandle,
+ NULL,
+ NULL,
+ NULL,
+ &ioStatusBlock,
+ pData,
+ ulDataSize,
+ m_pFilePtr,
+ NULL);
+
+ if (NT_SUCCESS(ntStatus))
+ {
+ ASSERT(ioStatusBlock.Information == ulDataSize);
+
+ m_pFilePtr->QuadPart += ulDataSize;
+ }
+ }
+ else
+ {
+ ntStatus = STATUS_INVALID_HANDLE;
+ }
+
+ return ntStatus;
+} // FileWrite
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CSaveData::FileWriteHeader(void)
+{
+ PAGED_CODE();
+
+ NTSTATUS ntStatus;
+
+ if (m_FileHandle && m_waveFormat)
+ {
+ IO_STATUS_BLOCK ioStatusBlock;
+
+ m_pFilePtr->QuadPart = 0;
+
+ m_FileHeader.dwFormatLength = (m_waveFormat->wFormatTag == WAVE_FORMAT_PCM) ?
+ sizeof( PCMWAVEFORMAT ) :
+ sizeof( WAVEFORMATEX ) + m_waveFormat->cbSize;
+
+ ntStatus = ZwWriteFile( m_FileHandle,
+ NULL,
+ NULL,
+ NULL,
+ &ioStatusBlock,
+ &m_FileHeader,
+ sizeof(m_FileHeader),
+ m_pFilePtr,
+ NULL);
+
+ if (NT_SUCCESS(ntStatus))
+ {
+ m_pFilePtr->QuadPart += sizeof(m_FileHeader);
+
+ ntStatus = ZwWriteFile( m_FileHandle,
+ NULL,
+ NULL,
+ NULL,
+ &ioStatusBlock,
+ m_waveFormat,
+ m_FileHeader.dwFormatLength,
+ m_pFilePtr,
+ NULL);
+ }
+
+ if (NT_SUCCESS(ntStatus))
+ {
+ m_pFilePtr->QuadPart += m_FileHeader.dwFormatLength;
+
+ ntStatus = ZwWriteFile( m_FileHandle,
+ NULL,
+ NULL,
+ NULL,
+ &ioStatusBlock,
+ &m_DataHeader,
+ sizeof(m_DataHeader),
+ m_pFilePtr,
+ NULL);
+ }
+
+ if (NT_SUCCESS(ntStatus))
+ {
+ m_pFilePtr->QuadPart += sizeof(m_DataHeader);
+ }
+ }
+ else
+ {
+ ntStatus = STATUS_INVALID_HANDLE;
+ }
+
+
+ return ntStatus;
+} // FileWriteHeader
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CSaveData::SetDeviceObject
+(
+ PDEVICE_OBJECT DeviceObject
+)
+{
+ PAGED_CODE();
+
+ ASSERT(DeviceObject);
+
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ m_pDeviceObject = DeviceObject;
+ return ntStatus;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+PDEVICE_OBJECT
+CSaveData::GetDeviceObject
+(
+ void
+)
+{
+ PAGED_CODE();
+
+ return m_pDeviceObject;
+}
+
+//=============================================================================
+_Use_decl_annotations_
+#pragma code_seg()
+PSAVEWORKER_PARAM
+CSaveData::GetNewWorkItem
+(
+ void
+)
+{
+ LARGE_INTEGER timeOut = { 0 };
+ NTSTATUS ntStatus;
+
+ for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++)
+ {
+ ntStatus =
+ KeWaitForSingleObject
+ (
+ &m_pWorkItems[i].EventDone,
+ Executive,
+ KernelMode,
+ FALSE,
+ &timeOut
+ );
+ if (STATUS_SUCCESS == ntStatus)
+ {
+ if (m_pWorkItems[i].WorkItem)
+ return &(m_pWorkItems[i]);
+ else
+ return NULL;
+ }
+ }
+
+ return NULL;
+} // GetNewWorkItem
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CSaveData::Initialize
+(
+ BOOL _bOffloaded
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+ WCHAR szTemp[MAX_PATH];
+ size_t cLen;
+
+ if (_bOffloaded)
+ {
+ m_ulOffloadStreamId++;
+ }
+ else
+ {
+ m_ulStreamId++;
+ }
+
+ // Allocate data file name.
+ //
+ RtlStringCchPrintfW(szTemp, MAX_PATH, L"%s_%s_%d.wav", DEFAULT_FILE_NAME, _bOffloaded ? OFFLOAD_FILE_NAME : HOST_FILE_NAME, _bOffloaded ? m_ulOffloadStreamId : m_ulStreamId);
+ m_FileName.Length = 0;
+ ntStatus = RtlStringCchLengthW (szTemp, sizeof(szTemp)/sizeof(szTemp[0]), &cLen);
+ if (NT_SUCCESS(ntStatus))
+ {
+ m_FileName.MaximumLength = (USHORT)((cLen * sizeof(WCHAR)) + sizeof(WCHAR));//convert to wchar and add room for NULL
+ m_FileName.Buffer = (PWSTR)
+ ExAllocatePool2
+ (
+ POOL_FLAG_PAGED,
+ m_FileName.MaximumLength,
+ SAVEDATA_POOLTAG3
+ );
+ if (!m_FileName.Buffer)
+ {
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ // Allocate memory for data buffer.
+ //
+ if (NT_SUCCESS(ntStatus))
+ {
+ RtlStringCbCopyW(m_FileName.Buffer, m_FileName.MaximumLength, szTemp);
+ m_FileName.Length = (USHORT)wcslen(m_FileName.Buffer) * sizeof(WCHAR);
+
+ m_pDataBuffer = (PBYTE)
+ ExAllocatePool2
+ (
+ POOL_FLAG_NON_PAGED,
+ m_ulBufferSize,
+ SAVEDATA_POOLTAG4
+ );
+ if (!m_pDataBuffer)
+ {
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ else
+ {
+ // ExAllocatePool2 zeros memory.
+ }
+ }
+
+ // Allocate memory for frame usage flags and m_pFilePtr.
+ //
+ if (NT_SUCCESS(ntStatus))
+ {
+ m_fFrameUsed = (PBOOL)
+ ExAllocatePool2
+ (
+ POOL_FLAG_NON_PAGED,
+ m_ulFrameCount * sizeof(BOOL) +
+ sizeof(LARGE_INTEGER),
+ SAVEDATA_POOLTAG2
+ );
+ if (!m_fFrameUsed)
+ {
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ // Initialize the spinlock to synchronize access to the frames
+ //
+ KeInitializeSpinLock ( &m_FrameInUseSpinLock ) ;
+
+ // Initialize the file mutex
+ //
+ KeInitializeMutex( &m_FileSync, 1 ) ;
+
+ // Open the data file.
+ //
+ if (NT_SUCCESS(ntStatus))
+ {
+ // m_fFrameUsed has additional memory to hold m_pFilePtr
+ //
+ m_pFilePtr = (PLARGE_INTEGER)
+ (((PBYTE) m_fFrameUsed) + m_ulFrameCount * sizeof(BOOL));
+ RtlZeroMemory(m_fFrameUsed, m_ulFrameCount * sizeof(BOOL) + sizeof(LARGE_INTEGER));
+
+ // Create data file.
+ InitializeObjectAttributes
+ (
+ &m_objectAttributes,
+ &m_FileName,
+ OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
+ NULL,
+ NULL
+ );
+
+ m_bInitialized = TRUE;
+
+ // Write wave header information to data file.
+ ntStatus = KeWaitForSingleObject
+ (
+ &m_FileSync,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL
+ );
+
+ if (STATUS_SUCCESS == ntStatus)
+ {
+ ntStatus = FileOpen(TRUE);
+ if (NT_SUCCESS(ntStatus))
+ {
+ ntStatus = FileWriteHeader();
+
+ FileClose();
+ }
+
+ KeReleaseMutex( &m_FileSync, FALSE );
+ }
+ }
+
+ return ntStatus;
+} // Initialize
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CSaveData::InitializeWorkItems
+(
+ PDEVICE_OBJECT DeviceObject
+)
+{
+ PAGED_CODE();
+
+ ASSERT(DeviceObject);
+
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ if (m_pWorkItems != NULL)
+ {
+ return ntStatus;
+ }
+
+ m_pWorkItems = (PSAVEWORKER_PARAM)
+ ExAllocatePool2
+ (
+ POOL_FLAG_NON_PAGED,
+ sizeof(SAVEWORKER_PARAM) * MAX_WORKER_ITEM_COUNT,
+ SAVEDATA_POOLTAG
+ );
+ if (m_pWorkItems)
+ {
+ for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++)
+ {
+
+ m_pWorkItems[i].WorkItem = IoAllocateWorkItem(DeviceObject);
+ if(m_pWorkItems[i].WorkItem == NULL)
+ {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ KeInitializeEvent
+ (
+ &m_pWorkItems[i].EventDone,
+ NotificationEvent,
+ TRUE
+ );
+ }
+ }
+ else
+ {
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return ntStatus;
+} // InitializeWorkItems
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+VOID
+SaveFrameWorkerCallback
+(
+ PDEVICE_OBJECT pDeviceObject,
+ PVOID Context
+)
+{
+ UNREFERENCED_PARAMETER(pDeviceObject);
+
+ PAGED_CODE();
+
+ ASSERT(Context);
+
+ PSAVEWORKER_PARAM pParam = (PSAVEWORKER_PARAM) Context;
+ PCSaveData pSaveData;
+
+ if (NULL == pParam)
+ {
+ // This is completely unexpected, assert here.
+ //
+ ASSERT(pParam);
+ return;
+ }
+
+ ASSERT(pParam->pSaveData);
+ ASSERT(pParam->pSaveData->m_fFrameUsed);
+
+ if (pParam->WorkItem)
+ {
+ pSaveData = pParam->pSaveData;
+
+ if (STATUS_SUCCESS == KeWaitForSingleObject
+ (
+ &pSaveData->m_FileSync,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL
+ ))
+ {
+ if (NT_SUCCESS(pSaveData->FileOpen(FALSE)))
+ {
+ pSaveData->FileWrite(pParam->pData, pParam->ulDataSize);
+ pSaveData->FileClose();
+ }
+ InterlockedExchange( (LONG *)&(pSaveData->m_fFrameUsed[pParam->ulFrameNo]), FALSE );
+
+ KeReleaseMutex( &pSaveData->m_FileSync, FALSE );
+ }
+ }
+
+ KeSetEvent(&pParam->EventDone, 0, FALSE);
+} // SaveFrameWorkerCallback
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CSaveData::SetDataFormat
+(
+ PKSDATAFORMAT pDataFormat
+)
+{
+ PAGED_CODE();
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+
+ ASSERT(pDataFormat);
+
+ PWAVEFORMATEX pwfx = NULL;
+
+ if (IsEqualGUIDAligned(pDataFormat->Specifier,
+ KSDATAFORMAT_SPECIFIER_DSOUND))
+ {
+ pwfx =
+ &(((PKSDATAFORMAT_DSOUND) pDataFormat)->BufferDesc.WaveFormatEx);
+ }
+ else if (IsEqualGUIDAligned(pDataFormat->Specifier,
+ KSDATAFORMAT_SPECIFIER_WAVEFORMATEX))
+ {
+ pwfx = &((PKSDATAFORMAT_WAVEFORMATEX) pDataFormat)->WaveFormatEx;
+ }
+
+ if (pwfx)
+ {
+ // Free the previously allocated waveformat
+ if (m_waveFormat)
+ {
+ ExFreePoolWithTag(m_waveFormat, SAVEDATA_POOLTAG1);
+ }
+
+ m_waveFormat = (PWAVEFORMATEX)
+ ExAllocatePool2
+ (
+ POOL_FLAG_NON_PAGED,
+ (pwfx->wFormatTag == WAVE_FORMAT_PCM) ?
+ sizeof( PCMWAVEFORMAT ) :
+ sizeof( WAVEFORMATEX ) + pwfx->cbSize,
+ SAVEDATA_POOLTAG1
+ );
+
+ if(m_waveFormat)
+ {
+ RtlCopyMemory( m_waveFormat,
+ pwfx,
+ (pwfx->wFormatTag == WAVE_FORMAT_PCM) ?
+ sizeof( PCMWAVEFORMAT ) :
+ sizeof( WAVEFORMATEX ) + pwfx->cbSize);
+ }
+ else
+ {
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+ return ntStatus;
+} // SetDataFormat
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CSaveData::SetMaxWriteSize
+(
+ ULONG ulMaxWriteSize
+)
+{
+ PAGED_CODE();
+
+ NTSTATUS ntStatus = STATUS_SUCCESS;
+ ULONG bufferSize = 0;
+ PBYTE buffer = NULL;
+
+ //
+ // Compute new buffer size.
+ //
+ ntStatus = RtlULongMult(ulMaxWriteSize, DEFAULT_FRAME_COUNT, &bufferSize);
+ if (!NT_SUCCESS(ntStatus))
+ {
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto Done;
+ }
+
+ //
+ // Alloc memory for buffer.
+ //
+ buffer = (PBYTE)
+ ExAllocatePool2
+ (
+ POOL_FLAG_NON_PAGED,
+ bufferSize,
+ SAVEDATA_POOLTAG4
+ );
+ if (!buffer)
+ {
+ ntStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto Done;
+ }
+
+ // ExAllocatePool2 zeros memory.
+
+ //
+ // Free old one.
+ //
+ if (m_pDataBuffer)
+ {
+ ExFreePoolWithTag(m_pDataBuffer, SAVEDATA_POOLTAG4);
+ m_pDataBuffer = NULL;
+ }
+
+ //
+ // Init new buffer settings.
+ //
+ m_pDataBuffer = buffer;
+ m_ulBufferSize = bufferSize;
+ m_ulFrameSize = ulMaxWriteSize;
+
+ ntStatus = STATUS_SUCCESS;
+
+Done:
+ return ntStatus;
+} // SetDataFormat
+
+//=============================================================================
+_Use_decl_annotations_
+PAGED_CODE_SEG
+void
+CSaveData::ReadData
+(
+ PBYTE pBuffer,
+ ULONG ulByteCount
+)
+{
+ UNREFERENCED_PARAMETER(pBuffer);
+ UNREFERENCED_PARAMETER(ulByteCount);
+
+ PAGED_CODE();
+
+ // Not implemented yet.
+} // ReadData
+
+//=============================================================================
+_Use_decl_annotations_
+#pragma code_seg()
+void
+CSaveData::SaveFrame
+(
+ ULONG ulFrameNo,
+ ULONG ulDataSize
+)
+{
+ PSAVEWORKER_PARAM pParam = NULL;
+
+ pParam = GetNewWorkItem();
+ if (pParam)
+ {
+ pParam->pSaveData = this;
+ pParam->ulFrameNo = ulFrameNo;
+ pParam->ulDataSize = ulDataSize;
+ pParam->pData = m_pDataBuffer + ulFrameNo * m_ulFrameSize;
+ KeResetEvent(&pParam->EventDone);
+ IoQueueWorkItem(pParam->WorkItem, SaveFrameWorkerCallback,
+ CriticalWorkQueue, (PVOID)pParam);
+ }
+} // SaveFrame
+
+//=============================================================================
+void
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CSaveData::WaitAllWorkItems
+(
+ void
+)
+{
+ PAGED_CODE();
+
+ // Save the last partially-filled frame
+ if (m_ulBufferOffset > m_ulFrameIndex * m_ulFrameSize)
+ {
+ ULONG size;
+
+ size = m_ulBufferOffset - m_ulFrameIndex * m_ulFrameSize;
+ SaveFrame(m_ulFrameIndex, size);
+ }
+
+ for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++)
+ {
+ KeWaitForSingleObject
+ (
+ &(m_pWorkItems[i].EventDone),
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL
+ );
+ }
+} // WaitAllWorkItems
+
+//=============================================================================
+_Use_decl_annotations_
+#pragma code_seg()
+void
+CSaveData::WriteData
+(
+ PBYTE pBuffer,
+ ULONG ulByteCount
+)
+{
+ ASSERT(pBuffer);
+
+ BOOL fSaveFrame = FALSE;
+ ULONG ulSaveFrameIndex = 0;
+ KIRQL oldIrql;
+
+ // If stream writing is disabled, then exit.
+ //
+ if (m_fWriteDisabled)
+ {
+ return;
+ }
+
+ if( 0 == ulByteCount )
+ {
+ return;
+ }
+
+ // The logic below assumes that write size is <= than frame size.
+ if (ulByteCount > m_ulFrameSize)
+ {
+ ulByteCount = m_ulFrameSize;
+ }
+
+ // Check to see if this frame is available.
+ KeAcquireSpinLock(&m_FrameInUseSpinLock, &oldIrql);
+ if (!m_fFrameUsed[m_ulFrameIndex])
+ {
+ KeReleaseSpinLock(&m_FrameInUseSpinLock, oldIrql );
+
+ ULONG ulWriteBytes = ulByteCount;
+
+ if( (m_ulBufferSize - m_ulBufferOffset) < ulWriteBytes )
+ {
+ ulWriteBytes = m_ulBufferSize - m_ulBufferOffset;
+ }
+
+ RtlCopyMemory(m_pDataBuffer + m_ulBufferOffset, pBuffer, ulWriteBytes);
+ m_ulBufferOffset += ulWriteBytes;
+
+ // Check to see if we need to save this frame
+ if (m_ulBufferOffset >= ((m_ulFrameIndex + 1) * m_ulFrameSize))
+ {
+ fSaveFrame = TRUE;
+ }
+
+ // Loop the buffer, if we reached the end.
+ if (m_ulBufferOffset == m_ulBufferSize)
+ {
+ fSaveFrame = TRUE;
+ m_ulBufferOffset = 0;
+ }
+
+ if (fSaveFrame)
+ {
+ InterlockedExchange( (LONG *)&(m_fFrameUsed[m_ulFrameIndex]), TRUE );
+ ulSaveFrameIndex = m_ulFrameIndex;
+ m_ulFrameIndex = (m_ulFrameIndex + 1) % m_ulFrameCount;
+ }
+
+ // Write the left over if the next frame is available.
+ if (ulWriteBytes != ulByteCount)
+ {
+ KeAcquireSpinLock(&m_FrameInUseSpinLock, &oldIrql );
+ if (!m_fFrameUsed[m_ulFrameIndex])
+ {
+ KeReleaseSpinLock(&m_FrameInUseSpinLock, oldIrql );
+ RtlCopyMemory
+ (
+ m_pDataBuffer + m_ulBufferOffset,
+ pBuffer + ulWriteBytes,
+ ulByteCount - ulWriteBytes
+ );
+
+ m_ulBufferOffset += ulByteCount - ulWriteBytes;
+ }
+ else
+ {
+ KeReleaseSpinLock(&m_FrameInUseSpinLock, oldIrql);
+ }
+ }
+
+ if (fSaveFrame)
+ {
+ SaveFrame(ulSaveFrameIndex, m_ulFrameSize);
+ }
+ }
+ else
+ {
+ KeReleaseSpinLock(&m_FrameInUseSpinLock, oldIrql );
+ }
+
+} // WriteData
+
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/savedata.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/savedata.h
new file mode 100644
index 00000000..2cc1eb47
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/savedata.h
@@ -0,0 +1,257 @@
+/*++
+
+Copyright (c) Microsoft Corporation All Rights Reserved
+
+Module Name:
+
+ savedata.h
+
+Abstract:
+
+ Declaration of ACX DSP Test Driver data saving class. This class supplies services
+to save data to disk.
+
+
+--*/
+
+#pragma once
+
+//-----------------------------------------------------------------------------
+// Forward declaration
+//-----------------------------------------------------------------------------
+class CSaveData;
+typedef CSaveData *PCSaveData;
+
+
+//-----------------------------------------------------------------------------
+// Structs
+//-----------------------------------------------------------------------------
+
+// Parameter to workitem.
+#include <pshpack1.h>
+typedef struct _SAVEWORKER_PARAM {
+ PIO_WORKITEM WorkItem;
+ ULONG ulFrameNo;
+ ULONG ulDataSize;
+ PBYTE pData;
+ PCSaveData pSaveData;
+ KEVENT EventDone;
+} SAVEWORKER_PARAM;
+typedef SAVEWORKER_PARAM *PSAVEWORKER_PARAM;
+#include <poppack.h>
+
+// wave file header.
+#include <pshpack1.h>
+typedef struct _OUTPUT_FILE_HEADER
+{
+ DWORD dwRiff;
+ DWORD dwFileSize;
+ DWORD dwWave;
+ DWORD dwFormat;
+ DWORD dwFormatLength;
+} OUTPUT_FILE_HEADER;
+typedef OUTPUT_FILE_HEADER *POUTPUT_FILE_HEADER;
+
+typedef struct _OUTPUT_DATA_HEADER
+{
+ DWORD dwData;
+ DWORD dwDataLength;
+} OUTPUT_DATA_HEADER;
+typedef OUTPUT_DATA_HEADER *POUTPUT_DATA_HEADER;
+
+#include <poppack.h>
+
+//-----------------------------------------------------------------------------
+// Classes
+//-----------------------------------------------------------------------------
+
+///////////////////////////////////////////////////////////////////////////////
+// CSaveData
+// Saves the wave data to disk.
+//
+__drv_maxIRQL(PASSIVE_LEVEL)
+PAGED_CODE_SEG
+IO_WORKITEM_ROUTINE SaveFrameWorkerCallback;
+
+class CSaveData
+{
+protected:
+ UNICODE_STRING m_FileName; // DataFile name.
+ HANDLE m_FileHandle; // DataFile handle.
+ PBYTE m_pDataBuffer; // Data buffer.
+ ULONG m_ulBufferSize; // Total buffer size.
+
+ ULONG m_ulFrameIndex; // Current Frame.
+ ULONG m_ulFrameCount; // Frame count.
+ ULONG m_ulFrameSize;
+ ULONG m_ulBufferOffset; // index in buffer.
+ PBOOL m_fFrameUsed; // Frame usage table.
+ KSPIN_LOCK m_FrameInUseSpinLock; // Spinlock for synch.
+ KMUTEX m_FileSync; // Synchronizes file access
+
+ OBJECT_ATTRIBUTES m_objectAttributes; // Used for opening file.
+
+ OUTPUT_FILE_HEADER m_FileHeader;
+ PWAVEFORMATEX m_waveFormat;
+ OUTPUT_DATA_HEADER m_DataHeader;
+ PLARGE_INTEGER m_pFilePtr;
+
+ static PDEVICE_OBJECT m_pDeviceObject;
+ static ULONG m_ulStreamId;
+ static ULONG m_ulOffloadStreamId;
+ static PSAVEWORKER_PARAM m_pWorkItems;
+
+ BOOL m_fWriteDisabled;
+
+ BOOL m_bInitialized;
+
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ CSaveData();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ ~CSaveData();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ void
+ Cleanup(
+ void
+ );
+
+ static
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ InitializeWorkItems(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+ static
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ void
+ DestroyWorkItems(
+ void
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ void
+ Disable(
+ _In_ BOOL fDisable
+ );
+
+ static
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ PSAVEWORKER_PARAM
+ GetNewWorkItem(
+ void
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ Initialize(
+ _In_ BOOL _bOffloaded
+ );
+
+ static
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ SetDeviceObject(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+ static
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ PDEVICE_OBJECT
+ GetDeviceObject(
+ void
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ void
+ ReadData(
+ _Inout_updates_bytes_all_(ulByteCount) PBYTE pBuffer,
+ _In_ ULONG ulByteCount
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ SetDataFormat(
+ _In_ PKSDATAFORMAT pDataFormat
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ SetMaxWriteSize(
+ _In_ ULONG ulMaxWriteSize
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ void
+ WaitAllWorkItems(
+ void
+ );
+
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ void
+ WriteData(
+ _In_reads_bytes_(ulByteCount) PBYTE pBuffer,
+ _In_ ULONG ulByteCount
+ );
+
+private:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ FileClose(
+ void
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ FileOpen(
+ _In_ BOOL fOverWrite
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ FileWrite(
+ _In_reads_bytes_(ulDataSize) PBYTE pData,
+ _In_ ULONG ulDataSize
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ FileWriteHeader(
+ void
+ );
+
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ void
+ SaveFrame(
+ _In_ ULONG ulFrameNo,
+ _In_ ULONG ulDataSize
+ );
+
+ friend
+ IO_WORKITEM_ROUTINE SaveFrameWorkerCallback;
+};
+typedef CSaveData *PCSaveData;
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/streamengine.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/streamengine.cpp
new file mode 100644
index 00000000..a979a129
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/streamengine.cpp
@@ -0,0 +1,1053 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ StreamEngine.cpp
+
+Abstract:
+
+ Virtual Streaming Engine - this module controls streaming logic for
+ the device.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "private.h"
+#include <devguid.h>
+#include "stdunk.h"
+#include <ks.h>
+#include <mmsystem.h>
+#include <ksmedia.h>
+#include "streamengine.h"
+
+#ifndef __INTELLISENSE__
+#include "streamengine.tmh"
+#endif
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CStreamEngine::CStreamEngine(
+ ACXSTREAM Stream,
+ ACXDATAFORMAT StreamFormat,
+ CSimPeakMeter *circuitPeakmeter
+ )
+ : m_PacketsCount(0),
+ m_PacketSize(0),
+ m_FirstPacketOffset(0),
+ m_NotificationTimer(NULL),
+ m_CurrentState(AcxStreamStateStop),
+ m_CurrentPacket(0),
+ m_Position(0),
+ m_Stream(Stream),
+ m_StreamFormat(StreamFormat),
+ m_StartTime(0),
+ m_StartPosition(0),
+ m_GlitchAdjust(0),
+ m_pCircuitPeakmeter(circuitPeakmeter)
+{
+ PAGED_CODE();
+
+ KeQueryPerformanceCounter(&m_PerformanceCounterFrequency);
+ RtlZeroMemory(m_Packets, sizeof(m_Packets));
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+CStreamEngine::~CStreamEngine()
+{
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CStreamEngine::AllocateRtPackets(
+ ULONG PacketCount,
+ ULONG PacketSize,
+ PACX_RTPACKET * Packets
+ )
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PVOID packetBuffer = NULL;
+ PACX_RTPACKET packets = NULL;
+
+ auto exit = scope_exit([&]() {
+ if (packetBuffer)
+ {
+ ExFreePoolWithTag(packetBuffer, DRIVER_TAG);
+ }
+ if (packets)
+ {
+ FreeRtPackets(packets, PacketCount);
+ }
+ });
+
+ RETURN_NTSTATUS_IF_TRUE(PacketCount > MAX_PACKET_COUNT, STATUS_INVALID_PARAMETER);
+
+ size_t packetsSize = 0;
+ RETURN_NTSTATUS_IF_FAILED(RtlSizeTMult(PacketCount, sizeof(ACX_RTPACKET), &packetsSize));
+
+#pragma prefast(suppress:__WARNING_MEMORY_LEAK, "On error packets gets freed inside scope_exit.")
+ packets = (PACX_RTPACKET)ExAllocatePool2(POOL_FLAG_NON_PAGED, packetsSize, DRIVER_TAG);
+ RETURN_NTSTATUS_IF_TRUE(!packets, STATUS_NO_MEMORY);
+
+ // ExAllocatePool2 zeros memory.
+
+ // We need to allocate page-aligned buffers, to ensure no kernel memory leaks
+ // to user space. Round up the packet size to page aligned, then calculate
+ // the first packet's buffer offset so packet 0 ends on a page boundary and
+ // packet 1 begins on a page boundary.
+ ULONG packetAllocSizeInPages = 0;
+ ULONG packetAllocSizeInBytes = 0;
+ ULONG firstPacketOffset = 0;
+ RETURN_NTSTATUS_IF_FAILED(RtlULongAdd(PacketSize, PAGE_SIZE - 1, &packetAllocSizeInPages));
+
+ packetAllocSizeInPages = packetAllocSizeInPages / PAGE_SIZE;
+ packetAllocSizeInBytes = PAGE_SIZE * packetAllocSizeInPages;
+ firstPacketOffset = packetAllocSizeInBytes - PacketSize;
+
+ ULONG i;
+ for (i = 0; i < PacketCount; ++i)
+ {
+ PMDL pMdl = NULL;
+
+ ACX_RTPACKET_INIT(&packets[i]);
+
+ packetBuffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, packetAllocSizeInBytes, DRIVER_TAG);
+ RETURN_NTSTATUS_IF_TRUE(packetBuffer == NULL, STATUS_NO_MEMORY);
+
+ // ExAllocatePool2 zeros memory.
+
+ pMdl = IoAllocateMdl(packetBuffer, packetAllocSizeInBytes, FALSE, FALSE, NULL);
+ RETURN_NTSTATUS_IF_TRUE(pMdl == NULL, STATUS_NO_MEMORY);
+
+ MmBuildMdlForNonPagedPool(pMdl);
+
+ WDF_MEMORY_DESCRIPTOR_INIT_MDL(
+ &((packets)[i].RtPacketBuffer),
+ pMdl,
+ packetAllocSizeInBytes);
+
+ packets[i].RtPacketSize = PacketSize;
+ if (i == 0)
+ {
+ packets[i].RtPacketOffset = firstPacketOffset;
+ }
+ else
+ {
+ packets[i].RtPacketOffset = 0;
+ }
+ m_Packets[i] = packetBuffer;
+
+ packetBuffer = NULL;
+ }
+
+ *Packets = packets;
+ packets = NULL;
+ m_PacketsCount = PacketCount;
+ m_PacketSize = PacketSize;
+ m_FirstPacketOffset = firstPacketOffset;
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+VOID
+CStreamEngine::FreeRtPackets(
+ PACX_RTPACKET Packets,
+ ULONG PacketCount
+)
+{
+ ULONG i;
+ PVOID buffer;
+
+ PAGED_CODE();
+
+ for (i = 0; i < PacketCount; ++i)
+ {
+ if (Packets[i].RtPacketBuffer.u.MdlType.Mdl)
+ {
+ buffer = MmGetMdlVirtualAddress(Packets[i].RtPacketBuffer.u.MdlType.Mdl);
+ IoFreeMdl(Packets[i].RtPacketBuffer.u.MdlType.Mdl);
+ ExFreePool(buffer);
+ }
+ }
+
+ ExFreePool(Packets);
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CStreamEngine::PrepareHardware()
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ WDF_TIMER_CONFIG timerConfig;
+ WDF_OBJECT_ATTRIBUTES timerAttributes;
+ WDF_TIMER_CONFIG_INIT(&timerConfig, CStreamEngine::s_EvtStreamPassCallback);
+ timerConfig.AutomaticSerialization = TRUE;
+ timerConfig.UseHighResolutionTimer = WdfTrue;
+ timerConfig.Period = 0;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&timerAttributes, STREAM_TIMER_CONTEXT);
+ timerAttributes.ParentObject = m_Stream;
+
+ RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate(
+ &timerConfig,
+ &timerAttributes,
+ &m_NotificationTimer
+ ));
+
+ PSTREAM_TIMER_CONTEXT timerCtx;
+ timerCtx = GetStreamTimerContext(m_NotificationTimer);
+ timerCtx->StreamEngine = this;
+
+ m_CurrentState = AcxStreamStatePause;
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CStreamEngine::ReleaseHardware()
+{
+ PAGED_CODE();
+
+ if (m_NotificationTimer)
+ {
+ WdfTimerStop(m_NotificationTimer, TRUE);
+ WdfObjectDelete(m_NotificationTimer);
+ m_NotificationTimer = NULL;
+ }
+
+ KeFlushQueuedDpcs();
+
+ m_Position = 0;
+ m_GlitchAdjust = 0;
+ m_CurrentPacket = 0;
+
+ m_CurrentState = AcxStreamStateStop;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CStreamEngine::Pause()
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CStreamEngine::Pause - from %d", m_CurrentState);
+
+ RETURN_NTSTATUS_IF_TRUE(m_CurrentState != AcxStreamStateRun, STATUS_INVALID_STATE_TRANSITION);
+
+ m_PeakMeter.StopStream();
+ if (m_pCircuitPeakmeter)
+ {
+ m_pCircuitPeakmeter->StopStream();
+ }
+
+ WdfTimerStop(m_NotificationTimer, TRUE);
+
+ // Save the position we paused at.
+ UpdatePosition();
+
+ m_CurrentState = AcxStreamStatePause;
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CStreamEngine::Run()
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CStreamEngine::Run");
+
+ if (m_CurrentState != AcxStreamStatePause)
+ {
+ status = STATUS_INVALID_STATE_TRANSITION;
+ return status;
+ }
+
+ m_PeakMeter.StartStream();
+ if (m_pCircuitPeakmeter)
+ {
+ m_pCircuitPeakmeter->StartStream();
+ }
+
+ // Save the time and position - if we ran and paused previously, the StartTime and StartPosition will allow
+ // us to continue scheduling packet completions correctly, while still reporting absolute position from the
+ // start of the stream.
+ m_StartTime = KSCONVERT_PERFORMANCE_TIME(m_PerformanceCounterFrequency.QuadPart, KeQueryPerformanceCounter(NULL));
+ m_StartPosition = m_Position;
+
+ // Reset time we've lost to glitches
+ m_GlitchAdjust = 0;
+
+ ScheduleNextPass();
+
+ m_CurrentState = AcxStreamStateRun;
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CStreamEngine::GetPresentationPosition(
+ PULONGLONG PositionInBlocks,
+ PULONGLONG QPCPosition
+)
+{
+ PAGED_CODE();
+
+ DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"CStreamEngine::GetPresentationPosition");
+
+ ULONG blockAlign;
+ LARGE_INTEGER qpc;
+
+ blockAlign = AcxDataFormatGetBlockAlign(m_StreamFormat);
+ qpc = KeQueryPerformanceCounter(NULL);
+
+ // Update the position based on the current time
+ UpdatePosition();
+
+ *PositionInBlocks = m_Position / blockAlign;
+
+ *QPCPosition = (ULONGLONG)qpc.QuadPart;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CStreamEngine::AssignDrmContentId(
+ ULONG DrmContentId,
+ PACXDRMRIGHTS DrmRights
+)
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(DrmContentId);
+ UNREFERENCED_PARAMETER(DrmRights);
+
+ //
+ // At this point the driver should enforce the new DrmRights.
+ //
+ // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP.
+ //
+ // From MSDN:
+ //
+ // This sample doesn't forward protected content, but if your driver uses
+ // lower layer drivers or a different stack to properly work, please see the
+ // following info from MSDN:
+ //
+ // "Before allowing protected content to flow through a data path, the system
+ // verifies that the data path is secure. To do so, the system authenticates
+ // each module in the data path beginning at the upstream end of the data path
+ // and moving downstream. As each module is authenticated, that module gives
+ // the system information about the next module in the data path so that it
+ // can also be authenticated. To be successfully authenticated, a module's
+ // binary file must be signed as DRM-compliant.
+ //
+ // Two adjacent modules in the data path can communicate with each other in
+ // one of several ways. If the upstream module calls the downstream module
+ // through IoCallDriver, the downstream module is part of a WDM driver. In
+ // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject
+ // function to provide the system with the device object representing the
+ // downstream module. (If the two modules communicate through the downstream
+ // module's content handlers, the upstream module calls AcxDrmAddContentHandlers
+ // instead.)
+ //
+ // For more information, see MSDN's DRM Functions and Interfaces.
+ //
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CStreamEngine::GetHWLatency(
+ ULONG * FifoSize,
+ ULONG * Delay
+)
+{
+ PAGED_CODE();
+
+ *FifoSize = 128;
+ *Delay = 0;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CSimPeakMeter *
+CStreamEngine::GetPeakMeter()
+{
+ PAGED_CODE();
+
+ return &m_PeakMeter;
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+CStreamEngine::s_EvtStreamPassCallback(
+ WDFTIMER Timer
+)
+{
+ CStreamEngine * This;
+ PSTREAM_TIMER_CONTEXT timerCtx;
+
+ // Get our stream engine pointer from the timer context
+ timerCtx = GetStreamTimerContext(Timer);
+ This = timerCtx->StreamEngine;
+
+ // Call the StreamPassCallback for the engine
+ This->StreamPassCallback();
+}
+
+// This is run every time the stream timer fires
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+CStreamEngine::StreamPassCallback()
+{
+ ULONGLONG completedPacket;
+ ULONGLONG qpcCompleted;
+
+ // Save the time at which we moved to the next packet
+ qpcCompleted = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart;
+
+ // Process the packet (e.g. save render to file/generate capture data)
+ ProcessPacket();
+
+ // We've completed a packet! Increment our currently active packet
+ completedPacket = (ULONG)InterlockedIncrement((LONG*)&m_CurrentPacket) - 1;
+
+ InterlockedExchange64(&m_LastPacketStart.QuadPart, m_CurrentPacketStart.QuadPart);
+ InterlockedExchange64(&m_CurrentPacketStart.QuadPart, qpcCompleted);
+
+ // Tell ACX we've completed the packet.
+ (void)AcxRtStreamNotifyPacketComplete(m_Stream, completedPacket, qpcCompleted);
+
+ // Schedule when our new current packet will finish
+ ScheduleNextPass();
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+CStreamEngine::ScheduleNextPass()
+{
+ LONGLONG delay = 0;
+ ULONG bytesPerSecond;
+ ULONGLONG nextPacket = 0;
+ ULONGLONG nextPacketStartPosition = 0;
+ ULONGLONG nextPacketPositionFromLastPause = 0;
+ ULONGLONG nextPacketTimeFromLastPauseHns = 0;
+ ULONGLONG nextPacketTime = 0;
+ ULONGLONG currentTime;
+ BOOLEAN inTimerQueue = FALSE;
+
+ // Get the number of bytes per second from our stored stream format
+ bytesPerSecond = GetBytesPerSecond();
+
+ // Calculate the absolute position of the beginning of the next packet from the beginning of the stream
+ nextPacket = m_CurrentPacket + 1;
+ nextPacketStartPosition = nextPacket * m_PacketSize;
+
+ // Adjust next packet position to account for the last time we resumed from Pause
+ nextPacketPositionFromLastPause = nextPacketStartPosition - m_StartPosition;
+
+ // Convert from bytes to HNS (to prevent truncation, multiply first then divide)
+ nextPacketTimeFromLastPauseHns = nextPacketPositionFromLastPause * HNS_PER_SEC / bytesPerSecond;
+
+ // Next packet time is Time @ resume from Pause, offset for lost time due to glitch, with next packet time added
+ nextPacketTime = m_StartTime + m_GlitchAdjust + nextPacketTimeFromLastPauseHns;
+
+ currentTime = KSCONVERT_PERFORMANCE_TIME(m_PerformanceCounterFrequency.QuadPart, KeQueryPerformanceCounter(NULL));
+
+ // Determine how long we want to wait, in HNS. Negative since it's a relative wait
+ delay = -(LONGLONG)(nextPacketTime - currentTime);
+
+ // If the delay isn't negative, this means we lost some time (e.g. broken into kernel debugger). Update
+ // our glitch adjust to account for that lost time, and attempt to schedule again
+ if (delay >= 0)
+ {
+ // Glitch!!!
+ // Update the glitch adjustment and set the new delay.
+ m_GlitchAdjust += delay;
+
+ StreamPassCallback();
+
+ return;
+ }
+
+ // Start the timer for our next pass! Note the timer isn't periodic.
+ inTimerQueue = WdfTimerStart(m_NotificationTimer, delay);
+
+ // We shouldn't be scheduling our next pass if the timer was previously still pending
+ ASSERT(inTimerQueue == FALSE);
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+CStreamEngine::UpdatePosition()
+{
+ ULONGLONG currentTime;
+ ULONG bytesPerSecond;
+
+ if (m_CurrentState != AcxStreamStateRun)
+ {
+ return;
+ }
+ bytesPerSecond = GetBytesPerSecond();
+ currentTime = KSCONVERT_PERFORMANCE_TIME(m_PerformanceCounterFrequency.QuadPart, KeQueryPerformanceCounter(NULL));
+
+ // Update position
+ m_Position = m_StartPosition - m_GlitchAdjust + (currentTime - m_StartTime) * bytesPerSecond / HNS_PER_SEC;
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+ULONG
+CStreamEngine::GetBytesPerSecond()
+{
+ ULONG bytesPerSecond;
+
+ bytesPerSecond = AcxDataFormatGetAverageBytesPerSec(m_StreamFormat);
+
+ return bytesPerSecond;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CStreamEngine::GetCurrentPacket(
+ PULONG CurrentPacket
+ )
+{
+ ULONG currentPacket;
+ PAGED_CODE();
+
+ currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1);
+
+ *CurrentPacket = currentPacket;
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CRenderStreamEngine::CRenderStreamEngine(
+ ACXSTREAM Stream,
+ ACXDATAFORMAT StreamFormat,
+ CSimPeakMeter *circuitPeakmeter
+ )
+ : CStreamEngine(Stream, StreamFormat, circuitPeakmeter)
+{
+ PAGED_CODE();
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CRenderStreamEngine::~CRenderStreamEngine()
+{
+ PAGED_CODE();
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CRenderStreamEngine::PrepareHardware()
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware());
+
+ // ignore failure
+ RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetDataFormat((PKSDATAFORMAT)AcxDataFormatGetKsDataFormat(m_StreamFormat)));
+
+ // ignore failure
+ RETURN_NTSTATUS_IF_FAILED(m_SaveData.Initialize(FALSE));
+
+ // ignore failure
+ RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetMaxWriteSize(m_PacketSize * m_PacketsCount * 16));
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CRenderStreamEngine::ReleaseHardware()
+{
+ PAGED_CODE();
+
+ m_SaveData.WaitAllWorkItems();
+ m_SaveData.Cleanup();
+
+ return CStreamEngine::ReleaseHardware();
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CRenderStreamEngine::AssignDrmContentId(
+ ULONG DrmContentId,
+ PACXDRMRIGHTS DrmRights
+ )
+{
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(DrmContentId);
+
+ //
+ // At this point the driver should enforce the new DrmRights.
+ // The sample driver handles DrmRights per stream basis, and
+ // stops writing the stream to disk, if CopyProtect = TRUE.
+ //
+ // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP.
+ // Loopback: if CopyProtect is true, disable loopback stream.
+ //
+
+ //
+ // Sample writes each stream seperately to disk. If the rights for this
+ // stream indicates that the stream is CopyProtected, stop writing to disk.
+ //
+ m_SaveData.Disable(DrmRights->CopyProtect);
+
+ //
+ // From MSDN:
+ //
+ // This sample doesn't forward protected content, but if your driver uses
+ // lower layer drivers or a different stack to properly work, please see the
+ // following info from MSDN:
+ //
+ // "Before allowing protected content to flow through a data path, the system
+ // verifies that the data path is secure. To do so, the system authenticates
+ // each module in the data path beginning at the upstream end of the data path
+ // and moving downstream. As each module is authenticated, that module gives
+ // the system information about the next module in the data path so that it
+ // can also be authenticated. To be successfully authenticated, a module's
+ // binary file must be signed as DRM-compliant.
+ //
+ // Two adjacent modules in the data path can communicate with each other in
+ // one of several ways. If the upstream module calls the downstream module
+ // through IoCallDriver, the downstream module is part of a WDM driver. In
+ // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject
+ // function to provide the system with the device object representing the
+ // downstream module. (If the two modules communicate through the downstream
+ // module's content handlers, the upstream module calls AcxDrmAddContentHandlers
+ // instead.)
+ //
+ // For more information, see MSDN's DRM Functions and Interfaces.
+ //
+
+ return STATUS_SUCCESS;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CRenderStreamEngine::SetRenderPacket(
+ ULONG Packet,
+ ULONG Flags,
+ ULONG EosPacketLength
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG currentPacket;
+
+ UNREFERENCED_PARAMETER(Flags);
+ UNREFERENCED_PARAMETER(EosPacketLength);
+
+ PAGED_CODE();
+
+ currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1);
+
+ if (Packet <= currentPacket)
+ {
+ //ASSERT(FALSE);
+ status = STATUS_DATA_LATE_ERROR;
+ }
+ else if (Packet > currentPacket + 1)
+ {
+ //ASSERT(FALSE);
+ status = STATUS_DATA_OVERRUN;
+ }
+
+ return status;
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+NTSTATUS
+CRenderStreamEngine::GetLinearBufferPosition(
+ _Out_ PULONGLONG Position
+)
+{
+ NTSTATUS status;
+ ULONGLONG qpcIgnored = 0;
+
+ // For this sample, we're borrowing the Presentation Position.
+ // An actual device would return the position of the last byte
+ // read from the audio buffer, not the last byte presented to the user
+ status = GetPresentationPosition(Position, &qpcIgnored);
+ if (!NT_SUCCESS(status))
+ {
+ return status;
+ }
+
+ *Position *= AcxDataFormatGetBlockAlign(m_StreamFormat);
+
+ return STATUS_SUCCESS;
+
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+CRenderStreamEngine::ProcessPacket()
+{
+ ULONG currentPacket;
+ ULONG packetIndex;
+ PBYTE packetBuffer;
+
+ currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1);
+
+ packetIndex = currentPacket % m_PacketsCount;
+ packetBuffer = (PBYTE)m_Packets[packetIndex];
+ // Packet 0 starts at an offset if the size isn't a multiple of page_size
+ if (packetIndex == 0)
+ {
+ packetBuffer += m_FirstPacketOffset;
+ }
+
+ m_SaveData.WriteData(packetBuffer, m_PacketSize);
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CCaptureStreamEngine::CCaptureStreamEngine(
+ ACXSTREAM Stream,
+ ACXDATAFORMAT StreamFormat
+ )
+ : CStreamEngine(Stream, StreamFormat, nullptr),
+ m_EnableWaveCapture(0)
+{
+ PAGED_CODE();
+
+ m_CurrentPacketStart.QuadPart = 0;
+ m_LastPacketStart.QuadPart = 0;
+
+ RtlInitUnicodeString(&m_HostCaptureFileName, NULL);
+ RtlInitUnicodeString(&m_LoopbackCaptureFileName, NULL);
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CCaptureStreamEngine::~CCaptureStreamEngine()
+{
+ PAGED_CODE();
+
+ RtlFreeUnicodeString(&m_HostCaptureFileName);
+ RtlFreeUnicodeString(&m_LoopbackCaptureFileName);
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CCaptureStreamEngine::PrepareHardware()
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware());
+
+ RETURN_NTSTATUS_IF_FAILED(ReadRegistrySettings());
+
+ if (m_EnableWaveCapture)
+ {
+ status = m_WaveReader.Init((PWAVEFORMATEXTENSIBLE)AcxDataFormatGetWaveFormatExtensible(m_StreamFormat),
+ &m_HostCaptureFileName);
+ if (!NT_SUCCESS(status))
+ {
+ m_EnableWaveCapture = FALSE;
+ }
+ }
+
+ if (!m_EnableWaveCapture)
+ {
+ status = m_ToneGenerator.Init(DEFAULT_FREQUENCY, (PWAVEFORMATEXTENSIBLE)AcxDataFormatGetWaveFormatExtensible(m_StreamFormat));
+ }
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CCaptureStreamEngine::ReleaseHardware()
+{
+ PAGED_CODE();
+
+ if (m_EnableWaveCapture)
+ {
+ m_WaveReader.WaitAllWorkItems();
+ }
+
+ return CStreamEngine::ReleaseHardware();
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CCaptureStreamEngine::GetCapturePacket(
+ ULONG * LastCapturePacket,
+ ULONGLONG * QPCPacketStart,
+ BOOLEAN * MoreData
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG currentPacket;
+ LONGLONG qpcPacketStart;
+
+ PAGED_CODE();
+
+ currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1);
+ qpcPacketStart = InterlockedCompareExchange64(&m_LastPacketStart.QuadPart, -1, -1);
+
+ *LastCapturePacket = currentPacket - 1;
+ *QPCPacketStart = (ULONGLONG)qpcPacketStart;
+ *MoreData = FALSE;
+
+ return status;
+}
+
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+CCaptureStreamEngine::ProcessPacket()
+{
+ ULONG currentPacket;
+ ULONG packetIndex;
+ PBYTE packetBuffer;
+
+ currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1);
+
+ packetIndex = currentPacket % m_PacketsCount;
+ packetBuffer = (PBYTE)m_Packets[packetIndex];
+
+ // Packet 0 starts at an offset if the size isn't a multiple of page_size
+ if (packetIndex == 0)
+ {
+ packetBuffer += m_FirstPacketOffset;
+ }
+
+ if (m_EnableWaveCapture)
+ {
+ m_WaveReader.ReadWaveData(packetBuffer, m_PacketSize);
+ }
+ else
+ {
+ m_ToneGenerator.GenerateSine(packetBuffer, m_PacketSize);
+ }
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CCaptureStreamEngine::ReadRegistrySettings()
+{
+ PAGED_CODE();
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ // TRUE only on SUCCESS
+ m_EnableWaveCapture = FALSE;
+
+ RTL_QUERY_REGISTRY_TABLE paramTable[] = {
+ // QueryRoutine Flags Name EntryContext DefaultType DefaultData DefaultLength
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"EnableWaveCapture", &m_EnableWaveCapture, (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD, &m_EnableWaveCapture, sizeof(DWORD) },
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"HostCaptureFileName", &m_HostCaptureFileName, (REG_SZ << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_SZ, &m_HostCaptureFileName, sizeof(UNICODE_STRING) },
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"LoopbackCaptureFileName", &m_LoopbackCaptureFileName, (REG_SZ << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_SZ, &m_LoopbackCaptureFileName, sizeof(UNICODE_STRING) },
+ { NULL, 0, NULL, NULL, 0, NULL, 0 }
+ };
+
+ UNICODE_STRING parametersPath;
+ RtlInitUnicodeString(&parametersPath, NULL);
+
+ // The sizeof(WCHAR) is added to the maximum length, for allowing a space for null termination of the string.
+ parametersPath.MaximumLength = g_RegistryPath.Length + sizeof(L"\\Parameters") + sizeof(WCHAR);
+
+#pragma prefast(suppress:__WARNING_ALIASED_MEMORY_LEAK, "memory is freed by scope_exit")
+ parametersPath.Buffer = (PWCH)ExAllocatePool2(POOL_FLAG_PAGED, parametersPath.MaximumLength, DRIVER_TAG);
+ RETURN_NTSTATUS_IF_TRUE(parametersPath.Buffer == NULL, STATUS_INSUFFICIENT_RESOURCES);
+ auto parametersPath_free = scope_exit([&parametersPath]() {
+ ExFreePool(parametersPath.Buffer);
+ });
+
+ // ExAllocatePool2 zeros memory.
+
+ RtlAppendUnicodeToString(&parametersPath, g_RegistryPath.Buffer);
+ RtlAppendUnicodeToString(&parametersPath, L"\\Parameters");
+
+ RETURN_NTSTATUS_IF_FAILED(RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL,
+ parametersPath.Buffer,
+ &paramTable[0],
+ NULL,
+ NULL));
+
+ m_EnableWaveCapture = TRUE;
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CBufferedCaptureStreamEngine::CBufferedCaptureStreamEngine(
+ _In_ ACXSTREAM Stream,
+ _In_ ACXDATAFORMAT StreamFormat,
+ _In_ CKeywordDetector * KeywordDetector
+
+ )
+ : CCaptureStreamEngine(Stream, StreamFormat),
+ m_KeywordDetector(KeywordDetector)
+{
+ PAGED_CODE();
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+CBufferedCaptureStreamEngine::~CBufferedCaptureStreamEngine()
+{
+ PAGED_CODE();
+}
+
+// This is run every time the stream timer fires
+_Use_decl_annotations_
+#pragma code_seg()
+VOID
+CBufferedCaptureStreamEngine::StreamPassCallback()
+{
+ LARGE_INTEGER qpc;
+ LARGE_INTEGER qpcFrequency;
+ BOOLEAN isRealtime = FALSE;
+ ULONGLONG completedPacket;
+ LONGLONG NewPacketNumber;
+ ULONGLONG NewPerformanceCount;
+
+ qpc = KeQueryPerformanceCounter(&qpcFrequency);
+
+ // As this is a simulation, we still want the ScheduleNextPass to
+ // keep producing data. To that end, update the current packet
+ // information used for production.
+ completedPacket = (ULONG)InterlockedIncrement((LONG*)&m_CurrentPacket) - 1;
+ InterlockedExchange64(&m_LastPacketStart.QuadPart, m_CurrentPacketStart.QuadPart);
+ InterlockedExchange64(&m_CurrentPacketStart.QuadPart, qpc.QuadPart);
+
+
+ // Add the next packet to the fifo queue
+ m_KeywordDetector->DpcRoutine(qpc.QuadPart, qpcFrequency.QuadPart, &isRealtime, &NewPacketNumber, &NewPerformanceCount);
+
+ if (isRealtime && (m_CurrentState == AcxStreamStateRun))
+ {
+ // We are running real time and just completed a packet, so notify.
+ (void)AcxRtStreamNotifyPacketComplete(m_Stream, NewPacketNumber, NewPerformanceCount);
+ }
+
+ // Schedule when our new current packet will finish
+ ScheduleNextPass();
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CBufferedCaptureStreamEngine::Pause()
+{
+ PAGED_CODE();
+
+ m_KeywordDetector->Stop();
+ return CCaptureStreamEngine::Pause();
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CBufferedCaptureStreamEngine::Run()
+{
+ PAGED_CODE();
+ ULONG FrontCapturePacket;
+ ULONGLONG QPCFrontPacket;
+
+ m_KeywordDetector->Run();
+ NTSTATUS status = CCaptureStreamEngine::Run();
+
+ NTSTATUS fifoStatus = m_KeywordDetector->GetFifoStart(&FrontCapturePacket, &QPCFrontPacket);
+ if (NT_SUCCESS(fifoStatus))
+ {
+ // We just entered the run state, so we need to trigger the packet completion for the first
+ // buffer in the fifo
+ (void)AcxRtStreamNotifyPacketComplete(m_Stream, FrontCapturePacket, QPCFrontPacket);
+ }
+
+ return status;
+}
+
+_Use_decl_annotations_
+PAGED_CODE_SEG
+NTSTATUS
+CBufferedCaptureStreamEngine::GetCapturePacket(
+ _Out_ ULONG * LastCapturePacket,
+ _Out_ ULONGLONG * QPCPacketStart,
+ _Out_ BOOLEAN * MoreData
+ )
+{
+ PAGED_CODE();
+ ULONG nextPacketNumber;
+ ULONGLONG nextQPCCount;
+
+ // retrieve the packet from the fifo queue
+ NTSTATUS status = m_KeywordDetector->GetReadPacket(m_PacketsCount, m_PacketSize, m_Packets, LastCapturePacket, QPCPacketStart, MoreData, &nextPacketNumber, &nextQPCCount);
+
+ if (NT_SUCCESS(status) && MoreData)
+ {
+ (void)AcxRtStreamNotifyPacketComplete(m_Stream, nextPacketNumber, nextQPCCount);
+ }
+
+ return status;
+}
+
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/streamengine.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/streamengine.h
new file mode 100644
index 00000000..902bb007
--- /dev/null
+++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/streamengine.h
@@ -0,0 +1,391 @@
+/*++
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ streamengine.h
+
+Abstract:
+
+ Virtual Streaming Engine - this module controls streaming logic for
+ the device.
+
+Environment:
+
+ Kernel mode
+
+--*/
+#pragma once
+
+#include "savedata.h"
+#include "tonegenerator.h"
+#include "WaveReader.h"
+#include "SimPeakMeter.h"
+#include "KeywordDetector.h"
+
+#define HNSTIME_PER_MILLISECOND 10000
+
+#define MAX_PACKET_COUNT 2
+
+#define DEFAULT_FREQUENCY (220)
+#define LOOPBACK_FREQUENCY (500)
+#define DEFAULT_FREQUENCY (220)
+
+class CStreamEngine
+{
+public:
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AllocateRtPackets(
+ _In_ ULONG PacketCount,
+ _In_ ULONG PacketSize,
+ _Out_ PACX_RTPACKET * Packets
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ VOID
+ FreeRtPackets(
+ _Frees_ptr_ PACX_RTPACKET Packets,
+ _In_ ULONG PacketCount
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ PrepareHardware();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ReleaseHardware();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ Run();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ Pause();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetPresentationPosition(
+ _Out_ PULONGLONG PositionInBlocks,
+ _Out_ PULONGLONG QPCPosition
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetCurrentPacket(
+ _Out_ PULONG CurrentPacket
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AssignDrmContentId(
+ _In_ ULONG DrmContentId,
+ _In_ PACXDRMRIGHTS DrmRights
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetHWLatency(
+ _Out_ ULONG * FifoSize,
+ _Out_ ULONG * Delay
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ #pragma code_seg()
+ NTSTATUS
+ GetLinearBufferPosition(
+ _Out_ PULONGLONG Position
+ )
+ {
+ UNREFERENCED_PARAMETER(Position);
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ CSimPeakMeter *
+ GetPeakMeter();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ CStreamEngine(
+ _In_ ACXSTREAM Stream,
+ _In_ ACXDATAFORMAT StreamFormat,
+ _In_opt_ CSimPeakMeter *circuitPeakmeter
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ #pragma code_seg()
+ ~CStreamEngine();
+
+protected:
+ PVOID m_Packets[MAX_PACKET_COUNT]{ nullptr };
+ ULONG m_PacketsCount{ 0 };
+ ULONG m_PacketSize{ 0 };
+ ULONG m_FirstPacketOffset{ 0 };
+ WDFTIMER m_NotificationTimer{ nullptr };
+ ACX_STREAM_STATE m_CurrentState{ AcxStreamStateStop };
+ ULONG m_CurrentPacket{ 0 };
+ ULONGLONG m_Position{ 0 };
+ ACXSTREAM m_Stream{ nullptr };
+ ACXDATAFORMAT m_StreamFormat{ nullptr };
+ ULONGLONG m_StartTime{ 0 };
+ ULONGLONG m_StartPosition{ 0 };
+ ULONGLONG m_GlitchAdjust{ 0 };
+ LARGE_INTEGER m_PerformanceCounterFrequency{ 0 };
+ LARGE_INTEGER m_CurrentPacketStart{ 0 };
+ LARGE_INTEGER m_LastPacketStart{ 0 };
+ CSimPeakMeter m_PeakMeter;
+ CSimPeakMeter* m_pCircuitPeakmeter{ nullptr };
+
+ static
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ _Function_class_(EVT_WDF_TIMER)
+ #pragma code_seg()
+ VOID s_EvtStreamPassCallback(
+ _In_ WDFTIMER Timer
+ );
+
+ // This is run every time the stream timer fires
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ StreamPassCallback();
+
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ ScheduleNextPass();
+
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ UpdatePosition();
+
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ ULONG
+ GetBytesPerSecond();
+
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ ProcessPacket() = 0;
+};
+
+class CRenderStreamEngine : public CStreamEngine
+{
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ CRenderStreamEngine(
+ _In_ ACXSTREAM Stream,
+ _In_ ACXDATAFORMAT StreamFormat,
+ _In_ CSimPeakMeter *circuitPeakmeter
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ ~CRenderStreamEngine();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ PrepareHardware();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ReleaseHardware();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ AssignDrmContentId(
+ _In_ ULONG DrmContentId,
+ _In_ PACXDRMRIGHTS DrmRights
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ SetRenderPacket(
+ _In_ ULONG Packet,
+ _In_ ULONG Flags,
+ _In_ ULONG EosPacketLength
+ );
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ #pragma code_seg()
+ NTSTATUS
+ GetLinearBufferPosition(
+ _Out_ PULONGLONG Position
+ );
+
+protected:
+ CSaveData m_SaveData;
+
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ ProcessPacket();
+
+};
+
+class CCaptureStreamEngine : public CStreamEngine
+{
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ CCaptureStreamEngine(
+ _In_ ACXSTREAM Stream,
+ _In_ ACXDATAFORMAT StreamFormat
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ ~CCaptureStreamEngine();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ PrepareHardware();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ReleaseHardware();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetCapturePacket(
+ _Out_ ULONG * LastCapturePacket,
+ _Out_ ULONGLONG * QPCPacketStart,
+ _Out_ BOOLEAN * MoreData
+ );
+
+protected:
+ ToneGenerator m_ToneGenerator;
+ CWaveReader m_WaveReader;
+ DWORD m_EnableWaveCapture{ 0 };
+ UNICODE_STRING m_HostCaptureFileName{ 0 };
+ UNICODE_STRING m_LoopbackCaptureFileName{ 0 };
+
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ ProcessPacket();
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ ReadRegistrySettings();
+};
+
+class CBufferedCaptureStreamEngine : public CCaptureStreamEngine
+{
+public:
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ CBufferedCaptureStreamEngine(
+ _In_ ACXSTREAM Stream,
+ _In_ ACXDATAFORMAT StreamFormat,
+ _In_ CKeywordDetector * KeywordDetector
+ );
+
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ ~CBufferedCaptureStreamEngine();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ Run();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ Pause();
+
+ virtual
+ __drv_maxIRQL(PASSIVE_LEVEL)
+ PAGED_CODE_SEG
+ NTSTATUS
+ GetCapturePacket(
+ _Out_ ULONG * LastCapturePacket,
+ _Out_ ULONGLONG * QPCPacketStart,
+ _Out_ BOOLEAN * MoreData
+ );
+
+protected:
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ ProcessPacket() {}
+
+ // This is run every time the stream timer fires
+ virtual
+ __drv_maxIRQL(DISPATCH_LEVEL)
+ #pragma code_seg()
+ VOID
+ StreamPassCallback();
+
+ CKeywordDetector * m_KeywordDetector{ nullptr };
+};
+
+
+// Define DSP circuit/stream pin context.
+//
+typedef struct _STREAM_TIMER_CONTEXT {
+ CStreamEngine * StreamEngine;
+} STREAM_TIMER_CONTEXT, *PSTREAM_TIMER_CONTEXT;
+
+#pragma code_seg()
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(STREAM_TIMER_CONTEXT, GetStreamTimerContext)