diff options
Diffstat (limited to 'audio/tests/KSPosTst')
| -rw-r--r-- | audio/tests/KSPosTst/KsPosTestTaef.cpp | 215 | ||||
| -rw-r--r-- | audio/tests/KSPosTst/PreComp.h | 108 | ||||
| -rw-r--r-- | audio/tests/KSPosTst/TestResourceBuild.cpp | 522 | ||||
| -rw-r--r-- | audio/tests/KSPosTst/locallimits.h | 25 | ||||
| -rw-r--r-- | audio/tests/KSPosTst/pintest.cpp | 518 | ||||
| -rw-r--r-- | audio/tests/KSPosTst/tests.h | 18 | ||||
| -rw-r--r-- | audio/tests/KSPosTst/timetest.cpp | 50 |
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; +} |
