summaryrefslogtreecommitdiff
path: root/audio/tests/KSPosTst/pintest.cpp
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/pintest.cpp
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/pintest.cpp')
-rw-r--r--audio/tests/KSPosTst/pintest.cpp518
1 files changed, 518 insertions, 0 deletions
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);
+}