summaryrefslogtreecommitdiff
path: root/audio/tests/KSPosTst
diff options
context:
space:
mode:
authorDaniel Rugerio <[email protected]>2020-05-13 16:23:40 -0700
committerGitHub <[email protected]>2020-05-13 16:23:40 -0700
commitbf0b0fe11d8b301cdfcc6d13f12a778cdd1c14b5 (patch)
tree55938bebdfe634226f0c1ee7822cbcdb4879f975 /audio/tests/KSPosTst
parentb78312cfe7990524cb46a1453ad2e013586857a0 (diff)
Update to SysVAD, Wave test and public sample of KS Position Test (#496)147237
* Update to Wave test and public sample of KS Position test. * Update to the SysVAD audio driver sample. * Add the PnpLockDown property.
Diffstat (limited to 'audio/tests/KSPosTst')
-rw-r--r--audio/tests/KSPosTst/KsPosTestTaef.cpp215
-rw-r--r--audio/tests/KSPosTst/PreComp.h108
-rw-r--r--audio/tests/KSPosTst/TestResourceBuild.cpp522
-rw-r--r--audio/tests/KSPosTst/locallimits.h25
-rw-r--r--audio/tests/KSPosTst/pintest.cpp518
-rw-r--r--audio/tests/KSPosTst/tests.h18
-rw-r--r--audio/tests/KSPosTst/timetest.cpp50
7 files changed, 1456 insertions, 0 deletions
diff --git a/audio/tests/KSPosTst/KsPosTestTaef.cpp b/audio/tests/KSPosTst/KsPosTestTaef.cpp
new file mode 100644
index 00000000..b92f9e4f
--- /dev/null
+++ b/audio/tests/KSPosTst/KsPosTestTaef.cpp
@@ -0,0 +1,215 @@
+// ------------------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//
+// File Name:
+//
+// KsPosTestTaef.cpp
+//
+// Abstract:
+//
+// Implementation file for KsPosTestTaef
+//
+// -------------------------------------------------------------------------------
+#include "PreComp.h"
+#include "KsPosTestTaef.h"
+#include "tests.h"
+
+using namespace WEX::Common;
+using namespace WEX::Logging;
+using namespace WEX::TestExecution;
+
+#define RUN_TEST_CASE(testfn) \
+{ \
+ SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures); \
+ testfn(); \
+}
+
+IBasicLog * g_pBasicLog = NULL;
+WDMAudio::KsPosTest* g_pKsPosTst = NULL;
+
+TIMER_MECHANISM g_Timer =
+{
+ tpQPC, "QueryPerformanceCounter"
+};
+
+void LogWaveFormat(WAVEFORMATEX* pwfx)
+{
+ WAVEFORMATEXTENSIBLE* wfex = (WAVEFORMATEXTENSIBLE*)pwfx;
+
+ switch (pwfx->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ LOG(g_pBasicLog, L" wFormatTag = WAVE_FORMAT_PCM");
+ break;
+ case WAVE_FORMAT_IEEE_FLOAT:
+ LOG(g_pBasicLog, L" wFormatTag = WAVE_FORMAT_IEEE_FLOAT");
+ break;
+ case WAVE_FORMAT_EXTENSIBLE:
+ LOG(g_pBasicLog, L" wFormatTag = WAVE_FORMAT_EXTENSIBLE");
+ if (wfex->SubFormat.Data1 == WAVE_FORMAT_PCM)
+ LOG(g_pBasicLog, L" SubFormat = WAVE_FORMAT_PCM");
+ if (wfex->SubFormat.Data1 == WAVE_FORMAT_IEEE_FLOAT)
+ LOG(g_pBasicLog, L" SubFormat = WAVE_FORMAT_IEEE_FLOAT");
+ break;
+ default:
+ LOG(g_pBasicLog, L" wFormatTag = UNKNOWN!");
+ }
+ LOG(g_pBasicLog, L" nChannel = %d", pwfx->nChannels);
+ LOG(g_pBasicLog, L" nSamplesPerSec = %d", pwfx->nSamplesPerSec);
+ LOG(g_pBasicLog, L" nAvgBytesPerSe = %d", pwfx->nAvgBytesPerSec);
+ LOG(g_pBasicLog, L" nBlockAlign = %d", pwfx->nBlockAlign);
+ LOG(g_pBasicLog, L" wBitsPerSample = %d", pwfx->wBitsPerSample);
+
+
+ if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
+ {
+ LOG(g_pBasicLog, L" wValidBitsPerSample = %d", wfex->Samples.wValidBitsPerSample);
+ LOG(g_pBasicLog, L" dwChannelMask = %hd", wfex->dwChannelMask);
+ }
+}
+
+// -------------------------------------------------------------------------------
+namespace WDMAudio
+{
+ BEGIN_MODULE()
+ MODULE_PROPERTY(L"Feature", L"PortCls HCK Tests")
+ MODULE_PROPERTY(L"TestResourceDependent", L"true")
+ END_MODULE()
+
+ // Create WEXBasicLog, Coinitialize Etc
+ MODULE_SETUP(WDMAudioSetup)
+
+ // Release stuff acquired in WDMAudioSetup
+ MODULE_CLEANUP(WDMAudioCleanup)
+};
+
+bool WDMAudio::WDMAudioSetup()
+{
+ if (NULL == g_pBasicLog)
+ {
+ if (!(VERIFY_SUCCEEDED(CreateWexBasicLog(&g_pBasicLog))))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool WDMAudio::WDMAudioCleanup()
+{
+ if (g_pBasicLog)
+ {
+ g_pBasicLog->Release();
+ }
+
+ return true;
+}
+
+// -------------------------------------------------------------------------------
+bool WDMAudio::KsPosTest::KsPosTestSetup()
+{
+ g_pKsPosTst = this;
+ m_pTimer = &g_Timer;
+ // The histograms can be displayed by using the command line parameter checked below
+ // If the parameter is not specified, the default is to not log anything
+ bool param = true;
+ WEX::TestExecution::RuntimeParameters::TryGetValue(L"logHistograms", param);
+ m_fLogHistograms = param;
+ return true;
+}
+
+bool WDMAudio::KsPosTest::KsPosTestCleanUp()
+{
+ g_pKsPosTst = NULL;
+
+ return true;
+}
+
+// -------------------------------------------------------------------------------
+bool WDMAudio::KsPosTest::TestCaseSetup()
+{
+ CComPtr<ITestResource> spResource;
+ CComQIPtr<IHalfAppContainer> spHalfContainer;
+
+ SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures);
+
+ Log::Comment(L"");
+ Log::Comment(L"------------------------------------------------------");
+ Log::Comment(L"Running test case setup");
+
+ size_t count = Resources::Count();
+ if (!VERIFY_ARE_EQUAL(count, (size_t)1)) { return false; }
+ if (!VERIFY_SUCCEEDED(Resources::Item(0, &spResource))) { return false; }
+
+ // 1. Assign m_pHalf
+ spHalfContainer = spResource;
+ if (!VERIFY_IS_NOT_NULL(spHalfContainer)) { return false; }
+ if (!VERIFY_SUCCEEDED(spHalfContainer->GetHalfApp(&m_pHalf))) { return false; }
+
+ // 2. Check if this is a loopback pin. If so, assign the host pin's HalfApp
+ if (m_pHalf->m_ConnectorType == eLoopbackConnector)
+ {
+ VERIFY_SUCCEEDED(m_pHalf->GetHostHalfApp(&m_pHostHalf));
+ }
+ else
+ {
+ m_pHostHalf = NULL;
+ }
+
+ Log::Comment(L"The test will run on the following format:");
+ if (m_pHostHalf != NULL)
+ {
+ LogWaveFormat(m_pHostHalf->m_pCurrentFormat.get());
+ }
+ else
+ {
+ LogWaveFormat(m_pHalf->m_pCurrentFormat.get());
+ }
+
+ return true;
+}
+
+bool WDMAudio::KsPosTest::TestCaseCleanUp()
+{
+ // Stop the timer here, in case any latency test fails
+ NtSetTimerResolution(0, FALSE, &actualTimerResolution);
+
+ if (!CompareWaveFormat(m_pHalf->m_pCurrentFormat.get(), m_pHalf->m_pPreferredFormat.get())) {
+ CloneWaveFormat(m_pHalf->m_pPreferredFormat.get(), wil::out_param(m_pHalf->m_pCurrentFormat));
+ m_pHalf->m_u32CurrentDefaultPeriodicityInFrames = m_pHalf->m_u32DefaultPeriodicityInFrames;
+ m_pHalf->m_u32CurrentFundamentalPeriodicityInFrames = m_pHalf->m_u32FundamentalPeriodicityInFrames;
+ m_pHalf->m_u32CurrentMinPeriodicityInFrames = m_pHalf->m_u32MinPeriodicityInFrames;
+ m_pHalf->m_u32CurrentMaxPeriodicityInFrames = m_pHalf->m_u32MaxPeriodicityInFrames;
+ }
+
+ if (m_pHostHalf != NULL) {
+ if (!VERIFY_SUCCEEDED(m_pHostHalf->CleanupStream())) { return false; }
+ if (!VERIFY_SUCCEEDED(m_pHostHalf->ReleaseEndpoint())) { return false; }
+ if (!VERIFY_SUCCEEDED(m_pHostHalf->ReleaseSineToneDataBuffer())) { return false; }
+ delete m_pHostHalf;
+ m_pHostHalf = NULL;
+ }
+
+ if (m_pHalf != NULL) {
+ if (!VERIFY_SUCCEEDED(m_pHalf->CleanupStream())) { return false; }
+ if (!VERIFY_SUCCEEDED(m_pHalf->ReleaseEndpoint())) { return false; }
+ if (!VERIFY_SUCCEEDED(m_pHalf->ReleaseSineToneDataBuffer())) { return false; }
+
+ m_pHalf = NULL;
+ }
+
+ return true;
+}
+
+// -------------------------------------------------------------------------------
+void WDMAudio::KsPosTest::TAEF_DriftAndJitter()
+{
+ RUN_TEST_CASE(Test_DriftAndJitter_Default);
+}
+
+void WDMAudio::KsPosTest::TAEF_Latency()
+{
+ RUN_TEST_CASE(Test_Latency_Default);
+}
diff --git a/audio/tests/KSPosTst/PreComp.h b/audio/tests/KSPosTst/PreComp.h
new file mode 100644
index 00000000..5fdfc9db
--- /dev/null
+++ b/audio/tests/KSPosTst/PreComp.h
@@ -0,0 +1,108 @@
+// ------------------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//
+// File Name:
+//
+// PreComp.h
+//
+// Abstract:
+//
+// Precompiled common headers
+//
+// -------------------------------------------------------------------------------
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// Number of milliseconds that the test will be delayed to wait for the stream to stabilize
+#define DELAY_FOR_STABILIZE 100
+
+// Number of milliseconds in a second
+#define MILLISECONDS_PER_SECOND 1000
+
+// Number of HNS units in one second
+#define HNSTIME_UNITS_PER_SECOND 10000000
+
+// Number of HNS units in one millisecond
+#define HNSTIME_UNITS_PER_MILLISECOND 10000
+
+typedef double (*TIMEPROC) (void);
+double tpQPC(void);
+
+typedef struct _tag_tm
+{
+ TIMEPROC Proc;
+ LPCSTR strRep;
+} TIMER_MECHANISM, * PTIMER_MECHANISM;
+
+// ----------------------------------------------------------
+// Timer functions
+extern "C" __kernel_entry NTSYSCALLAPI NTSTATUS NTAPI NtSetTimerResolution(
+ _In_ ULONG DesiredTime, _In_ BOOLEAN SetResolution, _Out_ PULONG ActualTime);
+
+// 1 ms timer resolution
+static ULONG timerResolution = (1 * 10000);
+static ULONG actualTimerResolution;
+
+// ------------------------------------------------------------------------------
+// structs used to hold results of perf tests
+#define DATA_POINTS 100
+
+typedef struct
+{
+ double testTime; // Time the sample was taken, in milliseconds, relative to the start of the sample collection
+ double positionTime; // Time of the reported position, in milliseconds, relative to the start of the sample collection
+ double position; // Position, in seconds, relative to the start of the sample collection
+} POSITION_SET, * PPOSITION_SET;
+
+typedef struct
+{
+ PPOSITION_SET argPosSets;
+ ULONG cPosSets;
+ ULONG ctLatencyMeas;
+ ULONG csBufferMeas;
+ double bytesPerSecond; // measured (in bytes/sec, NOT samples/sec)
+ double dOffset; // measured
+} PERF_RESULTS, * PPERF_RESULTS;
+
+typedef struct
+{
+ WAVEFORMATEXTENSIBLE wfxFormat;
+ PERF_RESULTS perfResults; //
+} FORMAT_ENTRY, * PFORMAT_ENTRY;
diff --git a/audio/tests/KSPosTst/TestResourceBuild.cpp b/audio/tests/KSPosTst/TestResourceBuild.cpp
new file mode 100644
index 00000000..93959089
--- /dev/null
+++ b/audio/tests/KSPosTst/TestResourceBuild.cpp
@@ -0,0 +1,522 @@
+// ------------------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//
+// File Name:
+//
+// TestResourceBuild.cpp
+//
+// Abstract:
+//
+// TAEF BuildResourceList implementation
+//
+// ------------------------------------------------------------------------------
+#include "PreComp.h"
+#include <TestResource.h>
+#include <PropertyHelper.h>
+
+using namespace WEX::Common;
+using namespace WEX::Logging;
+using namespace WEX::TestExecution;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// CreateTestResource
+//
+// Create the test resource with MMDevice, device id, device name, data flow, connector type, connector id, mode,
+// list of formats
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT CreateTestResource
+(
+ ResourceList& resourceList,
+ DeviceDescriptor descriptor
+)
+{
+ HRESULT hr = S_OK;
+
+ CComHeapPtr<CHalfApp> spHalfApp;
+ wil::com_ptr_nothrow<ITestResource> spTestResource;
+ GUID ResourceGUID;
+ CComBSTR szResourceName;
+
+ // Create HalfApp
+ spHalfApp.Attach(new CHalfApp(descriptor));
+ if (!VERIFY_IS_NOT_NULL(spHalfApp)) {
+ hr = E_OUTOFMEMORY;
+ return hr;
+ }
+
+ // Create PinTestResource
+ if (!VERIFY_SUCCEEDED(hr = CoCreateGuid(&ResourceGUID))) {
+ return hr;
+ }
+ if (!VERIFY_SUCCEEDED(hr = CPinTestResource::CreateInstance(
+ spHalfApp, ResourceGUID, &spTestResource))) {
+ return hr;
+ }
+
+ // Add to resource list
+ spHalfApp.Detach();
+ if (!VERIFY_SUCCEEDED(hr = resourceList.Add(spTestResource.get()))) {
+ return hr;
+ }
+ if (!VERIFY_SUCCEEDED(hr = spTestResource->GetValue(CComBSTR(TestResourceProperty::c_szName), &szResourceName))) {
+ return hr;
+ }
+ Log::Comment(String().Format(L"Test Resource (%s) added", (PCWSTR)szResourceName));
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// GetProcessingModesForConnector
+//
+// For host/keyword detector pin, processing mode info is cached in property store and can be directly read. For offload
+// pin, query for ks processing mode property. For loopback pin, it doesn't support any processing modes, return GUID_NULL.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT GetProcessingModesForConnector
+(
+ IMMDevice* pDevice,
+ UINT uConnectorId,
+ EndpointConnectorType eConnectorType,
+ ULONG *pCount,
+ AUDIO_SIGNALPROCESSINGMODE **ppModes
+)
+{
+ HRESULT hr = S_OK;
+
+ *pCount = 0;
+ *ppModes = NULL;
+
+ if (eConnectorType == eHostProcessConnector || eConnectorType == eKeywordDetectorConnector) {
+ if (!VERIFY_SUCCEEDED(hr = GetCachedProcessingModes(pDevice, eConnectorType, pCount, ppModes))) {
+ return hr;
+ }
+ }
+ else if (eConnectorType == eOffloadConnector) {
+ if (!VERIFY_SUCCEEDED(hr = GetProcessingModes(pDevice, uConnectorId, pCount, ppModes))) {
+ return hr;
+ }
+ }
+ else if (eConnectorType == eLoopbackConnector) {
+ // Loopback pin doesn't support any processing modes, so put 1 GUID_NULL
+ CComHeapPtr<AUDIO_SIGNALPROCESSINGMODE> spModes;
+
+ if (!spModes.Allocate(1))
+ return E_OUTOFMEMORY;
+
+ spModes[0] = GUID_NULL;
+
+ *pCount = 1;
+ *ppModes = spModes.Detach();
+ }
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// GetDefaultFormatForConnector
+//
+// Read audio engine device format from property store as default format.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT GetDefaultFormatForConnector
+(
+ IMMDevice* pDevice,
+ EndpointConnectorType eConnectorType,
+ WAVEFORMATEX **ppDefaultFormat
+)
+{
+ HRESULT hr = S_OK;
+
+ *ppDefaultFormat = NULL;
+ if (!VERIFY_SUCCEEDED(hr = GetCachedDefaultFormat(pDevice, eConnectorType, ppDefaultFormat))){
+ return hr;
+ }
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// GetSupportedFormatsForConnector
+//
+// For host/keyword detector pin, supported formats info is cached in property store and can be directly read. For offload
+// pin, provide with a predefined list of formats and check whether format is supported. For loopback pin, it matches the format
+// of host pin so put 0 format record.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT GetSupportedFormatRecordsForConnector
+(
+ IMMDevice* pDevice,
+ UINT uConnectorId,
+ EndpointConnectorType eConnectorType,
+ AUDIO_SIGNALPROCESSINGMODE mode,
+ STACKWISE_DATAFLOW dataFlow,
+ ULONG *pCount,
+ FORMAT_RECORD **ppFormatRecords
+)
+{
+ HRESULT hr = S_OK;
+
+ *pCount = 0;
+ *ppFormatRecords = NULL;
+
+ if (eConnectorType == eHostProcessConnector || eConnectorType == eKeywordDetectorConnector) {
+ if (!VERIFY_SUCCEEDED(hr = GetCachedSupportedFormatRecords(pDevice, eConnectorType, mode, pCount, ppFormatRecords))) {
+ return hr;
+ }
+ }
+ else if (eConnectorType == eOffloadConnector) {
+ if (!VERIFY_SUCCEEDED(hr = GetSupportedFormatRecords(pDevice, uConnectorId, eConnectorType, mode, dataFlow, pCount, ppFormatRecords))) {
+ return hr;
+ }
+ }
+ else if (eConnectorType == eLoopbackConnector) {
+ *pCount = 0;
+ *ppFormatRecords = NULL;
+ }
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// GetPreferredFormatForConnector
+//
+// If KSPROPERTY_PIN_PROPOSEDATAFORMAT2 is supported, use the proposed format for mode as preferred format for mode.
+// Otherwise, use default format as preferred format.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT GetPreferredFormatForConnector
+(
+ IMMDevice* pDevice,
+ UINT uConnectorId,
+ EndpointConnectorType eConnectorType,
+ AUDIO_SIGNALPROCESSINGMODE mode,
+ WAVEFORMATEX **ppPreferredFormat
+)
+{
+ HRESULT hr = S_OK;
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> pDefaultFormat;
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> pProposedFormat;
+
+ *ppPreferredFormat = NULL;
+
+
+ // Get default format for connector
+ if (!VERIFY_SUCCEEDED(hr = GetDefaultFormatForConnector(pDevice, eConnectorType, wil::out_param(pDefaultFormat)))) {
+ return hr;
+ }
+
+ // Return the processing mode specific format proposed by the driver
+ hr = GetProposedFormatForProcessingMode(pDevice, uConnectorId, mode, wil::out_param(pProposedFormat));
+ if (hr == S_OK) {
+ CloneWaveFormat(pProposedFormat.get(), ppPreferredFormat);
+ }
+ else {
+ CloneWaveFormat(pDefaultFormat.get(), ppPreferredFormat);
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// GetPreferredFormatPeriodicityCharacteristicsForConnector
+//
+// Get periodicity characteristics for format. For host/keyword detector pin, periodicity info is cached along with format
+// in property store and can be searched from supported format list. For offload pin, it should be calculate from
+// DiscoverPeriodicityCharacteristicsForFormat.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT GetPreferredFormatPeriodicityCharacteristicsForConnector
+(
+ IMMDevice* pDevice,
+ EndpointConnectorType eConnectorType,
+ AUDIO_SIGNALPROCESSINGMODE mode,
+ STACKWISE_DATAFLOW dataFlow,
+ WAVEFORMATEX *pPreferredFormat,
+ ULONG cFormatRecords,
+ PFORMAT_RECORD pFormatRecords,
+ UINT32 *pDefaultPeriodicityInFrames,
+ UINT32 *pFundamentalPeriodicityInFrames,
+ UINT32 *pMinPeriodicityInFrames,
+ UINT32 *pMaxPeriodicityInFrames,
+ UINT32 *pMaxPeriodicityInFramesExtended
+)
+{
+ HRESULT hr = S_OK;
+ bool bFormatInList = false;
+
+ for (ULONG i = 0; i < cFormatRecords; i++) {
+ if (CompareWaveFormat((WAVEFORMATEX *)&pFormatRecords[i].wfxEx, pPreferredFormat)) {
+ bFormatInList = true;
+ *pDefaultPeriodicityInFrames = pFormatRecords[i].defaultPeriodInFrames;
+ *pFundamentalPeriodicityInFrames = pFormatRecords[i].fundamentalPeriodInFrames;
+ *pMinPeriodicityInFrames = pFormatRecords[i].minPeriodInFrames;
+ *pMaxPeriodicityInFrames = pFormatRecords[i].maxPeriodInFrames;
+ *pMaxPeriodicityInFramesExtended = pFormatRecords[i].maxPeriodInFramesExtended;
+ break;
+ }
+ }
+
+ if (eConnectorType == eHostProcessConnector || eConnectorType == eKeywordDetectorConnector) {
+ if (!VERIFY_IS_TRUE(bFormatInList)) {
+ hr = E_NOTFOUND;
+ return hr;
+ }
+ }
+ else if (eConnectorType == eOffloadConnector || eConnectorType == eLoopbackConnector) {
+ if (!VERIFY_SUCCEEDED(hr = DiscoverPeriodicityCharacteristicsForFormat(pDevice, eConnectorType, mode, pPreferredFormat, dataFlow, pDefaultPeriodicityInFrames, pFundamentalPeriodicityInFrames, pMinPeriodicityInFrames, pMaxPeriodicityInFrames, pMaxPeriodicityInFramesExtended))) {
+ return hr;
+ }
+ }
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// AddTestResourceForConnector
+//
+// For each connector, read connector id, get all processing modes, identify default and preferred format for each mode
+// and also enumerate a list of supported formats for each mode. Store all the infos in test resource.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT AddTestResourceForConnector
+(
+ ResourceList& resourceList,
+ LPWSTR deviceId,
+ LPWSTR deviceName,
+ IMMDevice* pDevice,
+ EndpointConnectorType eConnectorType,
+ STACKWISE_DATAFLOW dataFlow
+)
+{
+ HRESULT hr = S_OK;
+ bool bHasConnector = false;
+ UINT uConnectorId;
+ ULONG cModes = 0;
+ CComHeapPtr<AUDIO_SIGNALPROCESSINGMODE> spModes;
+ AUDIO_SIGNALPROCESSINGMODE mode;
+ ULONG cFormatRecords = 0;
+ CComHeapPtr<FORMAT_RECORD> spFormatRecords;
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> pPreferredFormat;
+ UINT32 u32DefaultPeriodicityInFrames;
+ UINT32 u32FundamentalPeriodicityInFrames;
+ UINT32 u32MinPeriodicityInFrames;
+ UINT32 u32MaxPeriodicityInFrames;
+ UINT32 u32MaxPeriodicityInFramesExtended;
+ bool isAVStream = false;
+ bool isBluetooth = false;
+ bool isSideband = false;
+
+ // Get connector id
+ if (!VERIFY_SUCCEEDED(hr = GetConnectorId(pDevice, eConnectorType, &bHasConnector, &uConnectorId))) {
+ return hr;
+ }
+ if (!bHasConnector) {
+ return hr;
+ }
+
+ Log::Comment(String().Format(L"Adding Test Resource for pin [%u]:", (uConnectorId & PARTID_MASK)));
+
+
+ // Get all signal processing modes for connector
+ if (!VERIFY_SUCCEEDED(hr = GetProcessingModesForConnector(pDevice, uConnectorId, eConnectorType, &cModes, &spModes))) {
+ return hr;
+ }
+
+ // Loop through each mode, get preferred format and list of formats and create test resource for each mode
+ for (ULONG i = 0; i < cModes; i++) {
+
+ mode = spModes[i];
+
+ if (!VERIFY_SUCCEEDED(hr = GetSupportedFormatRecordsForConnector(pDevice, uConnectorId, eConnectorType, mode, dataFlow, &cFormatRecords, &spFormatRecords))) {
+ return hr;
+ }
+
+ if (!VERIFY_SUCCEEDED(hr = GetPreferredFormatForConnector(pDevice, uConnectorId, eConnectorType, mode, wil::out_param(pPreferredFormat)))) {
+ return hr;
+ }
+
+ if (!VERIFY_SUCCEEDED(hr = GetPreferredFormatPeriodicityCharacteristicsForConnector(pDevice, eConnectorType, mode, dataFlow, pPreferredFormat.get(), cFormatRecords, spFormatRecords, &u32DefaultPeriodicityInFrames, &u32FundamentalPeriodicityInFrames, &u32MinPeriodicityInFrames, &u32MaxPeriodicityInFrames, &u32MaxPeriodicityInFramesExtended))) {
+ return hr;
+ }
+
+ // Check if audio endpoint is AVStream
+ if (!VERIFY_SUCCEEDED(hr = IsAVStream(pDevice, &isAVStream))) {
+ return hr;
+ }
+
+ // Check if audio endpoint is Bluetooth
+ if (!VERIFY_SUCCEEDED(hr = IsBluetooth(pDevice, &isBluetooth))) {
+ return hr;
+ }
+
+ // Check if audio endpoint is side band
+ if (!VERIFY_SUCCEEDED(hr = IsSideband(pDevice, &isSideband))) {
+ return hr;
+ }
+
+ DeviceDescriptor descriptor = { 0 };
+ descriptor.pDevice = pDevice;
+ descriptor.pwstrAudioEndpointId = deviceId;
+ descriptor.pwstrAudioEndpointFriendlyName = deviceName;
+ descriptor.dataFlow = dataFlow;
+ descriptor.eConnectorType = eConnectorType;
+ descriptor.uConnectorId = uConnectorId;
+ descriptor.mode = mode;
+ descriptor.cModes = cModes;
+ descriptor.pModes = spModes;
+ descriptor.cFormatRecords = cFormatRecords;
+ descriptor.pFormatRecords = spFormatRecords;
+ descriptor.pPreferredFormat = pPreferredFormat.get();
+ descriptor.u32DefaultPeriodicityInFrames = u32DefaultPeriodicityInFrames;
+ descriptor.u32FundamentalPeriodicityInFrames = u32FundamentalPeriodicityInFrames;
+ descriptor.u32MinPeriodicityInFrames = u32MinPeriodicityInFrames;
+ descriptor.u32MaxPeriodicityInFrames = u32MaxPeriodicityInFrames;
+ descriptor.bIsAVStream = isAVStream;
+ descriptor.bIsBluetooth = isBluetooth;
+ descriptor.bIsSideband = isSideband;
+
+ if (!VERIFY_SUCCEEDED(hr = CreateTestResource(resourceList, descriptor))) {
+ return hr;
+ }
+
+ spFormatRecords.Free();
+ }
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// AddTestResourcesForDevice
+//
+// Identify the existence of all pin types and add test resources for each pin
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT AddTestResourcesForDevice
+(
+ ResourceList& resourceList,
+ LPWSTR deviceId,
+ LPWSTR deviceName,
+ STACKWISE_DATAFLOW dataFlow
+)
+{
+ HRESULT hr = S_OK;
+ wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ wil::com_ptr_nothrow<IMMDevice> spDevice;
+
+ Log::Comment(String().Format(L"Adding Test Resource for Device [%s]:", deviceName));
+
+ // Read the default device format from the property store
+ if (!VERIFY_SUCCEEDED(hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void **)&spEnumerator))) {
+ return hr;
+ }
+ if (!VERIFY_SUCCEEDED(hr = spEnumerator->GetDevice(deviceId, &spDevice))) {
+ return hr;
+ }
+
+ // Identify existence of host pin. Add test resources for host pin.
+ if (!VERIFY_SUCCEEDED(hr = AddTestResourceForConnector(resourceList, deviceId, deviceName, spDevice.get(), eHostProcessConnector, dataFlow))) {
+ return hr;
+ }
+
+ // Identify existence of offload pin. Add test resources for offload pin.
+ if (!VERIFY_SUCCEEDED(hr = AddTestResourceForConnector(resourceList, deviceId, deviceName, spDevice.get(), eOffloadConnector, dataFlow))) {
+ return hr;
+ }
+
+ // Identify existence of loopback pin. Add test resources for loopback pin.
+ if (!VERIFY_SUCCEEDED(hr = AddTestResourceForConnector(resourceList, deviceId, deviceName, spDevice.get(), eLoopbackConnector, capture ))) {
+ return hr;
+ }
+
+ /*
+ // Identify existence of keyword detector pin. Add test resources for keyword detector pin.
+ if (!VERIFY_SUCCEEDED(hr = AddTestResourceForConnector(resourceList, deviceId, deviceName, spDevice.get(), eKeywordDetectorConnector, dataFlow))) {
+ return hr;
+ }
+ */
+
+ return hr;
+}
+
+HRESULT __cdecl BuildResourceList(ResourceList& resourceList)
+{
+ HRESULT hr = S_OK;
+ wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ wil::com_ptr_nothrow<IMMDeviceCollection> spRenderEndpoints;
+ wil::com_ptr_nothrow<IMMDeviceCollection> spCaptureEndpoints;
+ wil::com_ptr_nothrow<IMMDevice> spEndpoint;
+ UINT cRenderDevices = 0;
+ UINT cCaptureDevices = 0;
+ UINT i = 0;
+ wil::unique_cotaskmem_string id;
+ wil::unique_cotaskmem_string friendlyName;
+
+ SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures);
+ DisableVerifyExceptions disable;
+
+ VERIFY_SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED));
+
+ Log::Comment(L"In BuildResourceList");
+
+ // Create IMMDevice Enumerator
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void **)&spEnumerator));
+
+ // Enumerate all render endpoints
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &spRenderEndpoints));
+ VERIFY_SUCCEEDED(spRenderEndpoints->GetCount(&cRenderDevices));
+
+ // Enumerate all capture endpoints
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &spCaptureEndpoints));
+ VERIFY_SUCCEEDED(spCaptureEndpoints->GetCount(&cCaptureDevices));
+
+ if (!VERIFY_IS_TRUE(cRenderDevices | cCaptureDevices))
+ {
+ hr = E_FAIL;
+ Log::Comment(L"No device was found!");
+ goto Exit;
+ }
+
+ Log::Comment(String().Format(L"Found %d viable rendering device!", cRenderDevices));
+ // Add test resources for render endpoints
+ if (cRenderDevices) {
+ for (i = 0; i < cRenderDevices; i++) {
+ VERIFY_SUCCEEDED(spRenderEndpoints->Item(i, &spEndpoint));
+ VERIFY_SUCCEEDED(spEndpoint->GetId(&id));
+ VERIFY_SUCCEEDED(GetEndpointFriendlyName(spEndpoint.get(), &friendlyName));
+
+ Log::Comment(String().Format(L"\\\\ Device: %s (%s)", friendlyName.get(), id.get()));
+ VERIFY_SUCCEEDED(AddTestResourcesForDevice(resourceList, id.get(), friendlyName.get(), render));
+ }
+ }
+
+ Log::Comment(String().Format(L"Found %d viable capture device!", cCaptureDevices));
+ // Add test resources for capture endpoints
+ if (cCaptureDevices) {
+ for (i = 0; i < cCaptureDevices; i++) {
+ VERIFY_SUCCEEDED(spCaptureEndpoints->Item(i, &spEndpoint));
+ VERIFY_SUCCEEDED(spEndpoint->GetId(&id));
+ VERIFY_SUCCEEDED(GetEndpointFriendlyName(spEndpoint.get(), &friendlyName));
+
+ Log::Comment(String().Format(L"\\\\ Device: %s (%s)", friendlyName.get(), id.get()));
+ VERIFY_SUCCEEDED(AddTestResourcesForDevice(resourceList, id.get(), friendlyName.get(), capture));
+ }
+ }
+
+ Log::Comment(L"Resource enumeration complete.");
+ Log::Comment(String().Format(L"Enumerated %u resources", resourceList.Count()));
+
+Exit:
+ return hr;
+}
diff --git a/audio/tests/KSPosTst/locallimits.h b/audio/tests/KSPosTst/locallimits.h
new file mode 100644
index 00000000..60336eb5
--- /dev/null
+++ b/audio/tests/KSPosTst/locallimits.h
@@ -0,0 +1,25 @@
+// ------------------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//
+// Module Name:
+//
+// locallimits.h
+//
+// Abstract:
+//
+// defines the tolerances for WHQL failures for this test app
+//
+// -------------------------------------------------------------------------------
+
+// max latency for rendering or capturing is 20 ms
+#define LATENCY_THRESHOLD 20.
+
+// Real time sampling rates must be within 1% of the theoretical rate,
+// per the format, as required by the PC98/PC99 HW Design Guide.
+#define SAMPLERATE_THRESHOLD 1.0
+
+// audio devices are required to be SAMPLEACCURATE (PC9x HW Design Guide)..
+// but for some reason we are making them ms accurate??
+#define JITTER_STD_THRESHOLD 1.0 // 1 ms max allowed for std-deviation
+#define JITTER_MAX_THRESHOLD 4.0 // 4 ms max allowed for max deviation
diff --git a/audio/tests/KSPosTst/pintest.cpp b/audio/tests/KSPosTst/pintest.cpp
new file mode 100644
index 00000000..57e35b07
--- /dev/null
+++ b/audio/tests/KSPosTst/pintest.cpp
@@ -0,0 +1,518 @@
+// ------------------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//
+// Module Name:
+//
+// pintest.cpp
+//
+// Abstract:
+//
+// Implementation file for test cases
+//
+// -------------------------------------------------------------------------------
+
+#include "PreComp.h"
+#include "KsPosTestTaef.h"
+#include "tests.h"
+
+#include <bestfit.h>
+#include <cmath>
+
+#define DEFAULT_PERIODICITY 0
+#define DEFAULT_LATENCY_COEFFICIENT 0
+#define xGetTime g_pKsPosTst->m_pTimer->Proc
+
+// ------------------------------------
+// Test_DriftAndJitter helper functions
+// ------------------------------------
+
+HRESULT CollectSampleData
+(
+ CHalfApp* pHalfApp,
+ PFORMAT_ENTRY pFormatEntry,
+ HNSTIME requestedPeriodicity
+)
+{
+ DWORD periodicityInMs = (DWORD)ceil(HNSTIME_TO_MILLISECONDS_DOUBLE(requestedPeriodicity));
+
+ if (!pFormatEntry)
+ {
+ ERR(g_pBasicLog, "FAIL: Current Format Entry is NULL.");
+ return E_INVALIDARG;
+ }
+
+ PPERF_RESULTS pResults = &pFormatEntry->perfResults;
+ PPOSITION_SET pPosSet = pResults->argPosSets;
+ double tStart = 0, tPre = 0, tPost = 0;
+ UINT i = 0;
+ UINT64 u64Position;
+ UINT64 positionHNS;
+ UINT64 u64Frequency;
+ double testStartHns;
+ LARGE_INTEGER liNow;
+ LARGE_INTEGER liFrequency;
+ QueryPerformanceFrequency(&liFrequency);
+
+ VERIFY_SUCCEEDED(pHalfApp->InitializeEndpoint());
+ VERIFY_SUCCEEDED(pHalfApp->CreateSineToneDataBuffer(pHalfApp->m_pCurrentFormat.get()));
+ VERIFY_SUCCEEDED(pHalfApp->InitializeStream(requestedPeriodicity, DEFAULT_LATENCY_COEFFICIENT));
+
+ // run the adapter sink pin
+ VERIFY_SUCCEEDED(pHalfApp->StartStream());
+
+ // Get a HNS reference from the begining of the test so the HNS from system and driver times won't be so large
+ QueryPerformanceCounter(&liNow);
+ testStartHns = (double)liNow.QuadPart * HNSTIME_UNITS_PER_SECOND / (double)liFrequency.QuadPart;
+
+ tStart = xGetTime();
+ Sleep(DELAY_FOR_STABILIZE);
+ // Get the stream frequency in order to determine the time offset
+ VERIFY_SUCCEEDED(pHalfApp->m_pAudioClock->GetFrequency(&u64Frequency));
+
+ // Start collecting the data
+ for (i = 0; i < DATA_POINTS;)
+ {
+ Sleep(periodicityInMs);
+
+ tPre = xGetTime();
+ VERIFY_SUCCEEDED(pHalfApp->GetPosition(&u64Position, &positionHNS));
+ QueryPerformanceCounter(&liNow);
+ tPost = xGetTime();
+
+ if (u64Position > 0)
+ {
+ // only count this result if the GetPosition and GetTime happened relatively atomically
+ if ((tPost - tPre) < 1.)
+ {
+ double testHNS = (double)liNow.QuadPart * HNSTIME_UNITS_PER_SECOND / (double)liFrequency.QuadPart;
+ // In both sampling rate and jitter calculation, the time used is in milliseconds, so we can convert it right here
+ pPosSet[i].testTime = HNSTIME_TO_MILLISECONDS_DOUBLE(testHNS - testStartHns);
+ pPosSet[i].positionTime = HNSTIME_TO_MILLISECONDS_DOUBLE(positionHNS - testStartHns);
+ // We store the values as seconds because it is easier to convert to bytes per second, on sampling rate calculation, and milliseconds, on jitter calculation.
+ pPosSet[i].position = (double)u64Position / (double)u64Frequency;
+
+ // Ignore repeated positions (that means they have the same QPC and should have the same position value)
+ if ((i > 0) && (pPosSet[i - 1].positionTime == pPosSet[i].positionTime))
+ {
+ // If the position is the same, decrement the count so that sample gets discarded by overwriting it
+ i--;
+ WARN(g_pBasicLog, "The same position was reported at %f ms: pos = %f seconds", pPosSet[i].testTime, pPosSet[i].position);
+ }
+ i++;
+ }
+ else
+ {
+ LOG(g_pBasicLog, "GetPosition function too slow");
+ }
+ }
+ // If a non-zero position has not been reported during one second, break the loop and check if there is enough data
+ else if ((tPost - tStart) > MILLISECONDS_PER_SECOND)
+ {
+ break;
+ }
+
+ pResults->cPosSets = i;
+ }
+
+ VERIFY_SUCCEEDED(pHalfApp->StopStream());
+
+ // Did we get enough data?
+ VERIFY_IS_TRUE(pResults->cPosSets >= 30);
+
+ return S_OK;
+}
+
+// --------------------------------------------------------------------------------------------------
+// CalculateSampleRate
+// --------------------------------------------------------------------------------------------------
+HRESULT CalculateSampleRate
+(
+ PFORMAT_ENTRY pFormatEntry
+)
+{
+ WAVEFORMATEX* pwfx = &pFormatEntry->wfxFormat.Format;
+ PPERF_RESULTS pResults = &pFormatEntry->perfResults;
+
+ double dError, dErrorPercent;
+
+ // Pair the positions with test HNS time to find the slope
+ // Alocate memory for this type-specific array
+ wil::unique_cotaskmem_ptr<DPOINT> pointArray((PDPOINT)CoTaskMemAlloc(DATA_POINTS * sizeof(DPOINT)));
+ PDPOINT pArray = pointArray.get();
+ // Get a pointer to the sample set
+ PPOSITION_SET posSet = pResults->argPosSets;
+ for (int i = 0; i < DATA_POINTS; i++)
+ {
+ pArray[i].x = posSet[i].positionTime;
+ pArray[i].y = posSet[i].position * pwfx->nAvgBytesPerSec; // Convert to bytes
+ }
+
+ ULONG cArray = pResults->cPosSets; // number of points
+
+ // calculate the slope and intercept (bytes per second and latency)
+ pResults->bytesPerSecond = SLOPE(pArray, cArray);
+ pResults->dOffset = INTERCEPT(pArray, cArray, pResults->bytesPerSecond);
+
+ // Convert from bytes/ms to bytes/sec
+ pResults->bytesPerSecond *= MILLISECONDS_PER_SECOND;
+
+ // The offset is used in the jitter calculation and it is used in milliseconds, so we can save it in those units
+ pResults->dOffset = pResults->dOffset / pResults->bytesPerSecond * MILLISECONDS_PER_SECOND;
+
+ // calculate errors
+ dError = pResults->bytesPerSecond - (double)pwfx->nAvgBytesPerSec;
+ dErrorPercent = (dError / (double)pwfx->nAvgBytesPerSec) * 100.0;
+
+ if (g_pKsPosTst->m_fLogHistograms)
+ {
+ LOG(g_pBasicLog, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+ LOG(g_pBasicLog, "SampleRate calculated from histogram (method of least squares)\n");
+
+ LOG(g_pBasicLog, "Collected %d data points", pResults->cPosSets);
+ LOG(g_pBasicLog, "Specified sample rate = %6.3f bytes/sec, %6.3f samples/sec", (double)(pwfx->nBlockAlign * pwfx->nSamplesPerSec), (double)pwfx->nSamplesPerSec);
+ LOG(g_pBasicLog, "Calculated sample rate = %6.3f bytes/sec, %6.3f samples/sec", pResults->bytesPerSecond, pResults->bytesPerSecond / (double)pwfx->nBlockAlign);
+ LOG(g_pBasicLog, "Error = %.3f %%", dErrorPercent);
+ LOG(g_pBasicLog, "Calculated time offset = %f ms", pResults->dOffset);
+ }
+
+ // evaluate the results
+ VERIFY_IS_TRUE(abs(dErrorPercent) <= SAMPLERATE_THRESHOLD);
+
+ return S_OK;
+}
+
+// --------------------------------------------------------------------------------------------------
+// CalculateJitter
+// --------------------------------------------------------------------------------------------------
+HRESULT CalculateJitter
+(
+ PFORMAT_ENTRY pFormatEntry
+)
+{
+ PPERF_RESULTS pResults = &pFormatEntry->perfResults;
+
+ double dError = 0.0;
+ double dErrorAve = 0.0;
+ double dErrorMax = 0.0;
+ double dStdDeviation = 0.0;
+ double reportedPosition;
+ double timeDifference;
+ ULONG i;
+ PPOSITION_SET pPosSet = pResults->argPosSets;
+
+ if (g_pKsPosTst->m_fLogHistograms)
+ {
+ LOG(g_pBasicLog, "Reported QPC Compensated Expected");
+ LOG(g_pBasicLog, "position adjust position position Error");
+ LOG(g_pBasicLog, "(ms) (ms) (ms) (ms) (ms)");
+ LOG(g_pBasicLog, "========== ========== ========== ========== ==========");
+ }
+
+ for (i = 0; i < pResults->cPosSets; i++)
+ {
+ // Convert the time offset from seconds to milliseconds
+ reportedPosition = pPosSet[i].position * MILLISECONDS_PER_SECOND; // milliseconds
+
+ // First, we compensate for the latency of the samples (they were taken 100 ms after stream start)
+ pPosSet[i].position = reportedPosition - pResults->dOffset; // The offset is already in ms
+
+ // Then, before comparing the expected and actual position, adjust the given sample to get the position corresponding to the test time.
+ timeDifference = pPosSet[i].testTime - pPosSet[i].positionTime; // milliseconds
+ // If the difference is positive, the time of the position is behind of what is expected and that position gets increased accordingly.
+ // If the difference is negative, the time of the position is ahead of what is expected and that position gets decreased accordingly.
+ // In the rare case when the QPC and time are the same, the difference is zero because we are on the spot and that is the position that should be compared.
+ pPosSet[i].position += timeDifference; // milliseconds
+
+ dError = pPosSet[i].testTime - pPosSet[i].position;
+ dErrorAve += dError;
+
+ if (g_pKsPosTst->m_fLogHistograms)
+ {
+ LOG(g_pBasicLog, "%10.2f, %10.2f, %10.2f, %10.2f, %10.2f", reportedPosition, timeDifference, pPosSet[i].position, pPosSet[i].testTime, dError);
+ }
+
+ if (dError < 0.)
+ dError = -dError;
+ if (dError > dErrorMax)
+ dErrorMax = dError;
+ }
+
+ dErrorAve /= (double)pResults->cPosSets;
+
+ VERIFY_IS_TRUE((ULONG)dErrorAve < 2); // if this is not true then, there's something wrong with our compensation...
+
+ for (i = 0; i < pResults->cPosSets; i++)
+ {
+ dError = pPosSet[i].testTime - pPosSet[i].position;
+ dStdDeviation += ((dError - dErrorAve) * (dError - dErrorAve));
+ }
+
+ dStdDeviation /= (double)pResults->cPosSets;
+ dStdDeviation = sqrt(dStdDeviation);
+
+ if (g_pKsPosTst->m_fLogHistograms)
+ {
+ LOG(g_pBasicLog, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+ LOG(g_pBasicLog, "Jitter calculated from histogram (with (samplerate drift and latency) compensation)\n");
+ LOG(g_pBasicLog, "Max deviation = %6.3f ms", dErrorMax);
+ LOG(g_pBasicLog, "Standard deviation = %6.3f ms", dStdDeviation);
+ }
+
+ // evaluate the results ~~~~~~~~~~~~~
+ VERIFY_IS_TRUE(abs(dErrorMax) < JITTER_MAX_THRESHOLD);
+ VERIFY_IS_TRUE(abs(dStdDeviation) < JITTER_STD_THRESHOLD);
+
+ return S_OK;
+}
+
+// ------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------
+void Test_DriftAndJitter_Main
+(
+ HNSTIME requestedPeriodicity
+)
+{
+ CHalfApp* pHalfApp = g_pKsPosTst->m_pHalf;
+ CHalfApp* pHostHalfApp = g_pKsPosTst->m_pHostHalf;
+ BOOL isLoopbackPin = pHalfApp->m_ConnectorType == eLoopbackConnector;
+
+ // Test will ignore Bluetooth devices until proper thresholds are in place.
+ if (pHalfApp->m_bIsBluetooth)
+ {
+ SKIP(g_pBasicLog, "Test is not supported for Bluetooth pins");
+ return;
+ }
+
+ // If this is a loobpack pin, start the host pin's stream
+ // This can be done before collecting the samples, since the host pin will keep playing until stopped
+ if (isLoopbackPin)
+ {
+ // Match loopback stream format with host stream format
+ CloneWaveFormat(pHostHalfApp->m_pCurrentFormat.get(), wil::out_param(pHalfApp->m_pCurrentFormat));
+ pHalfApp->m_u32CurrentDefaultPeriodicityInFrames = pHostHalfApp->m_u32CurrentDefaultPeriodicityInFrames;
+ pHalfApp->m_u32CurrentFundamentalPeriodicityInFrames = pHostHalfApp->m_u32CurrentFundamentalPeriodicityInFrames;
+ pHalfApp->m_u32CurrentMinPeriodicityInFrames = pHostHalfApp->m_u32CurrentMinPeriodicityInFrames;
+ pHalfApp->m_u32CurrentMaxPeriodicityInFrames = pHostHalfApp->m_u32CurrentMaxPeriodicityInFrames;
+
+ // Initialize and start host stream
+ VERIFY_SUCCEEDED(pHostHalfApp->InitializeEndpoint());
+ VERIFY_SUCCEEDED(pHostHalfApp->CreateSineToneDataBuffer(pHostHalfApp->m_pCurrentFormat.get()));
+ VERIFY_SUCCEEDED(pHostHalfApp->InitializeStream(requestedPeriodicity, DEFAULT_LATENCY_COEFFICIENT));
+ VERIFY_SUCCEEDED(pHostHalfApp->StartStream());
+ }
+
+ FORMAT_ENTRY results;
+ memset(&results, 0, sizeof(FORMAT_ENTRY));
+ results.wfxFormat.Format = *pHalfApp->m_pCurrentFormat.get();
+ // Alocate memory for the sample data
+ wil::unique_cotaskmem_ptr<POSITION_SET> pPositionSet((PPOSITION_SET)CoTaskMemAlloc(DATA_POINTS * sizeof(POSITION_SET)));
+ results.perfResults.argPosSets = pPositionSet.get();
+
+ // collect the data ~~~~~~~~~~~~~~~~~
+ VERIFY_SUCCEEDED(CollectSampleData(pHalfApp, &results, requestedPeriodicity));
+
+ // Stop and cleanup the host stream since we are done collecting samples
+ if (isLoopbackPin)
+ {
+ VERIFY_SUCCEEDED(pHostHalfApp->StopStream());
+ VERIFY_SUCCEEDED(pHostHalfApp->CleanupStream());
+ VERIFY_SUCCEEDED(pHostHalfApp->ReleaseEndpoint());
+ VERIFY_SUCCEEDED(pHostHalfApp->ReleaseSineToneDataBuffer());
+ }
+
+ // calculate drift ~~~~~~~~~~~~~~~~~~
+ VERIFY_SUCCEEDED(CalculateSampleRate(&results));
+
+ // calculate Jitter ~~~~~~~~~~~~~~~~~
+ VERIFY_SUCCEEDED(CalculateJitter(&results));
+}
+
+// ------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------
+void Test_DriftAndJitter_Default(void)
+{
+ HNSTIME requestedTime = FRAMES_TO_HNSTIME_DOUBLE(g_pKsPosTst->m_pHalf->m_u32CurrentDefaultPeriodicityInFrames, g_pKsPosTst->m_pHalf->m_pCurrentFormat->nSamplesPerSec);
+ if (g_pKsPosTst->m_fLogHistograms)
+ {
+ LOG(g_pBasicLog, "Periodicity: %d frames, %f ms", g_pKsPosTst->m_pHalf->m_u32CurrentDefaultPeriodicityInFrames, HNSTIME_TO_MILLISECONDS_DOUBLE(requestedTime));
+ }
+ Test_DriftAndJitter_Main(requestedTime);
+}
+
+/*
+ Perform a cycle of creating a pin, starting a stream, stoping and destroying.
+
+ This is done with the idea of putting the driver and audio hardware in an active state
+ before doing time sensitive tests.
+*/
+void ActivateDriver(CHalfApp* pHalfApp)
+{
+ VERIFY_SUCCEEDED(pHalfApp->InitializeEndpoint());
+ VERIFY_SUCCEEDED(pHalfApp->CreateSineToneDataBuffer(pHalfApp->m_pCurrentFormat.get()));
+ VERIFY_SUCCEEDED(pHalfApp->InitializeStream(DEFAULT_PERIODICITY, DEFAULT_LATENCY_COEFFICIENT));
+ VERIFY_SUCCEEDED(pHalfApp->StartStream());
+ Sleep(100);
+ VERIFY_SUCCEEDED(pHalfApp->StopStream());
+ VERIFY_SUCCEEDED(pHalfApp->CleanupStream());
+ VERIFY_SUCCEEDED(pHalfApp->ReleaseEndpoint());
+ VERIFY_SUCCEEDED(pHalfApp->ReleaseSineToneDataBuffer());
+}
+
+// ------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------
+void Test_Latency_Main (HNSTIME requestedPeriodicity)
+{
+ CHalfApp* pHalfApp = g_pKsPosTst->m_pHalf;
+ CHalfApp* pHostHalfApp = g_pKsPosTst->m_pHostHalf;
+ NTSTATUS lTimerResStatus;
+ BOOL isLoopbackPin = pHalfApp->m_ConnectorType == eLoopbackConnector;
+
+ // Test will ignore Bluetooth devices until proper thresholds are in place.
+ if (pHalfApp->m_bIsBluetooth)
+ {
+ SKIP(g_pBasicLog, "Test is not supported for Bluetooth pins");
+ return;
+ }
+
+ lTimerResStatus = NtSetTimerResolution(timerResolution, TRUE, &actualTimerResolution);
+
+ if (!NT_SUCCESS(lTimerResStatus))
+ {
+ SKIP(g_pBasicLog, "Fail to set timer precision. Skip the test.");
+ return;
+ }
+
+ // If it is a loopback pin, get the host pin and start the stream
+ if (isLoopbackPin)
+ {
+ // Match loopback stream format with host stream format
+ CloneWaveFormat(pHostHalfApp->m_pCurrentFormat.get(), wil::out_param(pHalfApp->m_pCurrentFormat));
+ pHalfApp->m_u32CurrentDefaultPeriodicityInFrames = pHostHalfApp->m_u32CurrentDefaultPeriodicityInFrames;
+ pHalfApp->m_u32CurrentFundamentalPeriodicityInFrames = pHostHalfApp->m_u32CurrentFundamentalPeriodicityInFrames;
+ pHalfApp->m_u32CurrentMinPeriodicityInFrames = pHostHalfApp->m_u32CurrentMinPeriodicityInFrames;
+ pHalfApp->m_u32CurrentMaxPeriodicityInFrames = pHostHalfApp->m_u32CurrentMaxPeriodicityInFrames;
+
+ // Initialize and start host stream
+ VERIFY_SUCCEEDED(pHostHalfApp->InitializeEndpoint());
+ VERIFY_SUCCEEDED(pHostHalfApp->CreateSineToneDataBuffer(pHostHalfApp->m_pCurrentFormat.get()));
+ VERIFY_SUCCEEDED(pHostHalfApp->InitializeStream(requestedPeriodicity, DEFAULT_LATENCY_COEFFICIENT));
+ VERIFY_SUCCEEDED(pHostHalfApp->StartStream());
+ }
+
+ // Perform a stream cycle to make sure the driver is on a good state
+ ActivateDriver(pHalfApp);
+
+ // Initialize the stream
+ VERIFY_SUCCEEDED(pHalfApp->InitializeEndpoint());
+ VERIFY_SUCCEEDED(pHalfApp->CreateSineToneDataBuffer(pHalfApp->m_pCurrentFormat.get()));
+ VERIFY_SUCCEEDED(pHalfApp->InitializeStream(requestedPeriodicity, DEFAULT_LATENCY_COEFFICIENT));
+
+ UINT64 u64Position = 0;
+ UINT64 driverHns = 0;
+
+ double tPosPre = 0.0;
+ double tPosPost = 0.0;
+
+ LARGE_INTEGER liFrequency = { 0 };
+ LARGE_INTEGER timeStamp = { 0 };
+ QueryPerformanceFrequency(&liFrequency);
+
+ // mark the time
+ double tRunPre = xGetTime();
+ // Start the stream
+ VERIFY_SUCCEEDED(pHalfApp->StartStream());
+ // mark the time
+ double tRunPost = xGetTime();
+
+ LOG(g_pBasicLog, "Starting the stream took %.03f ms", (tRunPost - tRunPre));
+
+ // loop until we get a non-zero position
+ while (u64Position == 0)
+ {
+ Sleep(1);
+ // timer before position call
+ tPosPre = xGetTime();
+ VERIFY_SUCCEEDED(pHalfApp->GetPosition(&u64Position, &driverHns));
+ QueryPerformanceCounter(&timeStamp);
+ // timer after position call
+ tPosPost = xGetTime();
+ if (g_pKsPosTst->m_fLogHistograms)
+ {
+ LOG(g_pBasicLog, "Testing first non-zero position reported: %u samples, %u HNS at %.03f ms", u64Position, driverHns, tPosPost - tRunPre);
+ }
+
+ // Fail if the cursor does not move within 500 ms
+ VERIFY_IS_TRUE((tPosPost - tRunPost) < 500.0);
+ }
+
+ // Before stopping, get the frequency of the stream
+ UINT64 u64Frequency;
+ VERIFY_SUCCEEDED(pHalfApp->m_pAudioClock->GetFrequency(&u64Frequency));
+
+ // the uncertainty in time is the max possible value minus the min possible value divided by 2 (for +/- purposes)
+ double tMax = tPosPost - tRunPre;
+ double tMin = tPosPre - tRunPost;
+ double tAve = (tMax + tMin) / 2.0;
+ double tUncertainty = (tMax - tMin) / 2.0;
+
+ // Convert the stream position to an offset of milliseconds
+ double tOffset = MILLISECONDS_PER_SECOND * (double)u64Position / (double)u64Frequency;
+
+ // Calculate the difference in time of the position time versus the test time
+ UINT64 testHns = 10000000 * timeStamp.QuadPart / liFrequency.QuadPart;
+ INT64 timeDiff = testHns - driverHns;
+
+ // Compensate the positon to take this difference into account
+ tOffset += (double)timeDiff / HNSTIME_UNITS_PER_MILLISECOND;
+
+ if (g_pKsPosTst->m_fLogHistograms)
+ {
+ LOG(g_pBasicLog, "Times (HNS): %u, %u", testHns, driverHns);
+ LOG(g_pBasicLog, "Time difference: %d HNS (%f ms)", timeDiff, (double)timeDiff / HNSTIME_UNITS_PER_MILLISECOND);
+ LOG(g_pBasicLog, "First non-zero position = %u at %g ms", u64Position, tAve);
+ LOG(g_pBasicLog, "Time offset / max time: %f ms / %f ms", tOffset, tMax);
+ LOG(g_pBasicLog, "Frequency: %lu", u64Frequency);
+ }
+
+ double tLatency;
+ tLatency = tAve - tOffset;
+ if (tLatency < 0)
+ {
+ // This means that the position is between the average and max times, which should be OK (as long as it is not above the max)
+ tLatency *= -1;
+ LOG(g_pBasicLog, "The position is greater than the average time");
+ }
+ LOG(g_pBasicLog, "Adjusted latency = %.03f ms (+/- %.3f ms)", tLatency, tUncertainty);
+
+ // if the reported position is outside the max time for this call (see tUncertainty calculation above), then fail
+ // temp fix until we know how far can be the data in front of the
+ // real time because of buffering
+ VERIFY_IS_FALSE(tOffset > tMax);
+
+ // Verify that the latency is within the threshold
+ VERIFY_IS_TRUE(tLatency <= LATENCY_THRESHOLD); // ms
+
+ // Stop the endpoint
+ VERIFY_SUCCEEDED(pHalfApp->StopStream());
+ // If this was a loopback pin, stop the host stream also
+ if (isLoopbackPin)
+ {
+ VERIFY_SUCCEEDED(pHostHalfApp->StopStream());
+ VERIFY_SUCCEEDED(pHostHalfApp->CleanupStream());
+ VERIFY_SUCCEEDED(pHostHalfApp->ReleaseEndpoint());
+ VERIFY_SUCCEEDED(pHostHalfApp->ReleaseSineToneDataBuffer());
+ }
+
+ // Turn off the higher resolution timer
+ NtSetTimerResolution(0, FALSE, &actualTimerResolution);
+}
+
+// ------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------
+void Test_Latency_Default(void)
+{
+ HNSTIME requestedTime = FRAMES_TO_HNSTIME_DOUBLE(g_pKsPosTst->m_pHalf->m_u32CurrentDefaultPeriodicityInFrames, g_pKsPosTst->m_pHalf->m_pCurrentFormat->nSamplesPerSec);
+ LOG(g_pBasicLog, "Periodicity: %d frames, %f ms", g_pKsPosTst->m_pHalf->m_u32CurrentDefaultPeriodicityInFrames, HNSTIME_TO_MILLISECONDS_DOUBLE(requestedTime));
+ Test_Latency_Main(requestedTime);
+}
diff --git a/audio/tests/KSPosTst/tests.h b/audio/tests/KSPosTst/tests.h
new file mode 100644
index 00000000..ec0a68cc
--- /dev/null
+++ b/audio/tests/KSPosTst/tests.h
@@ -0,0 +1,18 @@
+// ------------------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//
+// Module Name:
+//
+// tests.h
+//
+// Abstract:
+//
+// Header for testcases
+//
+// -------------------------------------------------------------------------------
+#pragma once
+
+void Test_DriftAndJitter_Default(void);
+
+void Test_Latency_Default(void);
diff --git a/audio/tests/KSPosTst/timetest.cpp b/audio/tests/KSPosTst/timetest.cpp
new file mode 100644
index 00000000..d8026716
--- /dev/null
+++ b/audio/tests/KSPosTst/timetest.cpp
@@ -0,0 +1,50 @@
+// ------------------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//
+// Module Name:
+//
+// timetest.cpp
+//
+// Abstract:
+//
+// Functions to precisely measure time.
+//
+// -------------------------------------------------------------------------------
+
+#include "PreComp.h"
+#include <math.h>
+
+// ------------------------------------------------------------------------------
+// returns millisecs
+double tpQPC (void)
+{
+ static LARGE_INTEGER liFrequency = { 0 };
+ static LARGE_INTEGER liStart = { 0 };
+ static bool bInitialized = false;
+
+ if (!bInitialized)
+ {
+ if (!QueryPerformanceFrequency(&liFrequency))
+ {
+ throw ("QueryPerformanceFrequency failed");
+ }
+
+ if (!QueryPerformanceCounter(&liStart))
+ {
+ throw ("QueryPerformanceCounter failed");
+ }
+
+ bInitialized = true;
+ }
+
+ LARGE_INTEGER liNow = { 0 };
+
+ if (!QueryPerformanceCounter(&liNow))
+ {
+ throw ("QueryPerformanceCounter failed");
+ }
+
+ // milliseconds since test start
+ return 1000.0 * (liNow.QuadPart - liStart.QuadPart) / liFrequency.QuadPart;
+}