summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrandon Obermeyer <[email protected]>2021-03-29 11:21:17 -0700
committerGitHub <[email protected]>2021-03-29 11:21:17 -0700
commit24e0600eb31b9309ab2d6b69573c0c91343a1ad0 (patch)
tree866aa8277a78af83a7fc83b9e8fa6fa57d671fad
parentdb962644c3307b8da85667b3bc5e1d0b90e70e06 (diff)
Adds the HLK APO tests to the public github for windows-driver-samples. (#597)
This change is to add the HLK APO tests to the windows-driver-samples to allow for any partners to take a look at the tests to know what exactly is being tested when they run the HLK APO tests.
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.cpp1120
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.h180
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.cpp96
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.h38
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/APOStressTest.cpp777
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/APOStressTest.h50
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/CAPXHelper.cpp247
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/CAPXHelper.h33
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/CAPXLoggingFrameworkTests.cpp160
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/CAPXLoggingFrameworkTests.h73
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.cpp168
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.h84
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.cpp118
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.h73
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/FormatHelpers.h330
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/TestEffectsList.cpp225
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/TestMediaType.cpp227
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/TestMediaType.h163
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/TestWasapiUseFromAudioDg.cpp244
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/Wrappers.h121
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/audioconnectionutil.h129
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/sinewave.h60
-rw-r--r--audio/tests/HLK/ThirdPartyApoTests/stdafx.h123
23 files changed, 4839 insertions, 0 deletions
diff --git a/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.cpp b/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.cpp
new file mode 100644
index 00000000..ca3b3a7a
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.cpp
@@ -0,0 +1,1120 @@
+#include <stdafx.h>
+
+#include <initguid.h>
+#include "propkey.h"
+#include <AudioClient.h>
+#include <functiondiscoverykeys.h>
+#include <DevPKey.h>
+#include <AudioEngineBaseAPOP.h>
+#include "TestMediaType.h"
+#include "audioconnectionutil.h"
+#include <audioenginebaseapo.h>
+
+#include <WexTestClass.h>
+
+#include <list>
+#include <memory>
+
+#include "APODeviceTest.h"
+#include "Wrappers.h"
+#include "FormatHelpers.h"
+
+
+using namespace std;
+using namespace wil;
+using namespace WEX::Logging;
+using namespace WEX::Common;
+
+#define REGKEY_AUDIOSERVICE L"Software\\Microsoft\\Windows\\CurrentVersion\\Audio"
+#define REGVALUE_SKIPRTHEAP L"SkipRTHeap"
+
+// Forward Declarations
+bool SetupSkipRTHeap();
+bool CleanupSkipRTHeap();
+HRESULT VerifyCustomFormatSupport(CAPODevice * deviceUnderTest);
+HRESULT VerifyValidFrameCount(CAPODevice * deviceUnderTest);
+HRESULT VerifyAPODataStreaming(CAPODevice * deviceUnderTest);
+HRESULT VerifyActivateDeactivate(CAPODevice * deviceUnderTest);
+HRESULT FillConnection
+(
+ UNCOMPRESSEDAUDIOFORMAT* Format,
+ UINT_PTR pBuffer,
+ UINT32 u32ExtraFrameCount,
+ UINT32 u32MaxFrameCount,
+ APO_CONNECTION_DESCRIPTOR* pConnection
+);
+
+bool CApoDeviceTests::setUpMethod()
+{
+ // Populate a vector of each endpoint on the system and wrap it with
+ // an APO test wrapper which will save necessary APO information for
+ // test cases
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+ com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ com_ptr_nothrow<IMMDeviceCollection> spDevices;
+
+ UINT32 cDevices;
+ PROPVARIANT var;
+
+ PropVariantInit(&var);
+
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spEnumerator)));
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &spDevices));
+ VERIFY_SUCCEEDED(spDevices->GetCount(&cDevices));
+
+ // Initialize an APO device list for each device on the system
+ for(UINT32 i = 0; i < cDevices; i++)
+ {
+ com_ptr_nothrow<IMMDevice> spEndpoint;
+ com_ptr_nothrow<IPropertyStore> spPropertyStore;
+
+ VERIFY_SUCCEEDED(spDevices->Item(i, &spEndpoint));
+ VERIFY_SUCCEEDED(spEndpoint->OpenPropertyStore(STGM_READ, &spPropertyStore));
+ VERIFY_SUCCEEDED(spPropertyStore->GetValue(PKEY_Device_FriendlyName, &var));
+ VERIFY_ARE_EQUAL(VT_LPWSTR, var.vt);
+ LOG_OUTPUT(L"Setting Up Endpoint: %s", var.pwszVal);
+
+ unique_ptr<CAPODeviceList> pTestModule(new CAPODeviceList());
+ pTestModule->Initialize(wistd::move(spEndpoint), var.pwszVal);
+ m_testDeviceWrapperList.push_back(move(pTestModule));
+
+ PropVariantClear(&var);
+ }
+
+ return SetupSkipRTHeap();
+}
+
+bool CApoDeviceTests::tearDownMethod()
+{
+ m_testDeviceWrapperList.clear();
+ return CleanupSkipRTHeap();
+}
+
+void CApoDeviceTests::TestAPOInitialize()
+{
+ // In the event that one initialization fails fully, we do not want to fail
+ // the whole test. Instead, mark the test as failed and complete it on all
+ // devices.
+ bool testSucceeded = true;
+
+ m_testDeviceWrapperList.remove_if([&](auto & deviceWrapper) {
+
+ // Print Device Friendly name
+ LOG_OUTPUT(L"Endpoint Under Test: %ls", deviceWrapper->m_deviceName);
+
+ HRESULT hr = deviceWrapper->AddSysFxDevices();
+
+ if (hr == S_FALSE)
+ {
+ // No cause for failure here. Just remove the device from our test list
+ // as it appears to be inactive or has no third party APOs
+ return true;
+ }
+
+ if (FAILED(hr))
+ {
+ LOG_ERROR(L"Device initialization failed with error: %x", hr);
+ testSucceeded = false;
+ }
+ return false;
+
+ });
+
+ VERIFY_IS_TRUE(testSucceeded);
+}
+
+void CApoDeviceTests::TestCustomFormatSupport()
+{
+ bool testSucceeded = true;
+
+ TestAPOInitialize();
+
+ for(auto const& deviceWrapper : m_testDeviceWrapperList)
+ {
+ // Print Device Friendly name
+ LOG_OUTPUT(L"Endpoint Under Test: %ls", deviceWrapper->m_deviceName);
+
+ // Test over each SysFX device on the device
+ for (auto const& apo : deviceWrapper->m_DeviceList)
+ {
+ if (apo->IsProxyApo())
+ {
+ GUID proxyGuid;
+ unique_cotaskmem_string proxyString;
+
+ apo->GetClsID(&proxyGuid);
+ StringFromCLSID(proxyGuid, &proxyString);
+
+ LOG_OUTPUT(L"Proxy APO found. Skipping: %ls", proxyString.get());
+ continue;
+ }
+
+ HRESULT hr = VerifyCustomFormatSupport(apo.get());
+
+ if (hr == S_FALSE)
+ {
+ LOG_OUTPUT(L"\tAPO does not support custom formats. This is not an error");
+ }
+
+ if (FAILED(hr))
+ {
+ LOG_ERROR(L"\tTest Custom Format Support failed with error: (0x%08lx)", hr);
+ testSucceeded = false;
+ }
+ }
+ }
+
+ VERIFY_IS_TRUE(testSucceeded);
+}
+
+void CApoDeviceTests::TestValidFrameCount()
+{
+ bool testSucceeded = true;
+
+ TestAPOInitialize();
+
+ for(auto const& deviceWrapper : m_testDeviceWrapperList)
+ {
+ // Print Device Friendly name
+ LOG_OUTPUT(L"Endpoint Under Test: %ls", deviceWrapper->m_deviceName);
+
+ // Test over each SysFX device on the device
+ for (auto const& apo : deviceWrapper->m_DeviceList)
+ {
+ if (apo->IsProxyApo())
+ {
+ GUID proxyGuid;
+ unique_cotaskmem_string proxyString;
+
+ apo->GetClsID(&proxyGuid);
+ StringFromCLSID(proxyGuid, &proxyString);
+
+ LOG_OUTPUT(L"\tProxy APO found. Skipping: %ls", proxyString.get());
+ continue;
+ }
+
+ HRESULT hr = VerifyValidFrameCount(apo.get());
+
+ if (FAILED(hr))
+ {
+ LOG_ERROR(L"\tTest Valid Frame Count failed with error: (0x%08lx)", hr);
+ testSucceeded = false;
+ }
+ }
+ }
+
+ VERIFY_IS_TRUE(testSucceeded);
+}
+
+void CApoDeviceTests::TestAPODataStreaming()
+{
+ bool testSucceeded = true;
+
+ TestAPOInitialize();
+
+ for(auto const& deviceWrapper : m_testDeviceWrapperList)
+ {
+ // Print Device Friendly name
+ LOG_OUTPUT(L"Endpoint Under Test: %ls", deviceWrapper->m_deviceName);
+
+ // Test over each SysFX device on the device
+ for (auto const& apo : deviceWrapper->m_DeviceList)
+ {
+ if (apo->IsProxyApo())
+ {
+ GUID proxyGuid;
+ unique_cotaskmem_string proxyString;
+
+ apo->GetClsID(&proxyGuid);
+ StringFromCLSID(proxyGuid, &proxyString);
+
+ LOG_OUTPUT(L"\tProxy APO found. Skipping: %ls", proxyString.get());
+ continue;
+ }
+
+ HRESULT hr = VerifyAPODataStreaming(apo.get());
+
+ if (FAILED(hr))
+ {
+ LOG_ERROR(L"\tTest APO Data Streaming failed with error: (0x%08lx)", hr);
+ testSucceeded = false;
+ }
+ }
+ }
+
+ VERIFY_IS_TRUE(testSucceeded);
+}
+
+void CApoDeviceTests::TestActivateDeactivate()
+{
+ bool testSucceeded = true;
+
+ TestAPOInitialize();
+
+ for(auto const& deviceWrapper : m_testDeviceWrapperList)
+ {
+ // Print Device Friendly name
+ LOG_OUTPUT(L"Endpoint Under Test: %ls", deviceWrapper->m_deviceName);
+
+ // Test over each SysFX device on the device
+ for (auto const& apo : deviceWrapper->m_DeviceList)
+ {
+ if (apo->IsProxyApo())
+ {
+ GUID proxyGuid;
+ unique_cotaskmem_string proxyString;
+
+ apo->GetClsID(&proxyGuid);
+ StringFromCLSID(proxyGuid, &proxyString);
+
+ LOG_OUTPUT(L"\tProxy APO found. Skipping: %ls", proxyString.get());
+ continue;
+ }
+
+ HRESULT hr = VerifyActivateDeactivate(apo.get());
+
+ if (FAILED(hr))
+ {
+ LOG_ERROR(L"\tTest Activate and Deactivate failed with error: (0x%08lx)", hr);
+ testSucceeded = false;
+ }
+ }
+ }
+
+ VERIFY_IS_TRUE(testSucceeded);
+}
+
+HRESULT VerifyValidFrameCount(CAPODevice* deviceUnderTest)
+{
+ wil::com_ptr<IAudioProcessingObject> pIAPO;
+ wil::com_ptr<IAudioProcessingObjectRT> pIAPORT;
+ wil::com_ptr<IAudioProcessingObjectConfiguration> pIAPOConfig;
+
+ if (deviceUnderTest == nullptr)
+ {
+ LOG_ERROR(L"\tCould not instantiate SysFx object.\n");
+ return E_NOINTERFACE;
+ }
+
+ LOG_RETURN_IF_FAILED(deviceUnderTest->GetAPOInterfaces(&pIAPO, &pIAPORT, &pIAPOConfig), L"\tCould not instantiate IAudioProcessingObject interface.");
+
+ APO_CONNECTION_PROPERTY inputProperty, outputProperty;
+ APO_CONNECTION_DESCRIPTOR inputDescriptor, outputDescriptor;
+ APO_CONNECTION_PROPERTY *pInputConnProp = &inputProperty, *pOutputConnProp = &outputProperty;
+ APO_CONNECTION_DESCRIPTOR *pInputConnDesc = &inputDescriptor, *pOutputConnDesc = &outputDescriptor;
+ UNCOMPRESSEDAUDIOFORMAT defaultFormat {};
+
+ FillFormat(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 2, sizeof(float), sizeof(float) * 8, static_cast<float>(cFrameRate), KSAUDIO_SPEAKER_STEREO, &defaultFormat);
+
+ LOG_RETURN_IF_FAILED(SetupConnection(pInputConnDesc, pOutputConnDesc, pInputConnProp, pOutputConnProp, &defaultFormat),
+ L"\tSetupConnection failed");
+
+ LOG_RETURN_IF_FAILED(LockForProcess(pIAPOConfig.get(), 1, &pInputConnDesc, 1, &pOutputConnDesc),
+ L"\tLockForProcess failed");
+
+ // setup vectors
+ FLOAT32 *pf32Input, *pf32Output;
+ pf32Input = (FLOAT32*)pInputConnProp->pBuffer;
+ pf32Output = (FLOAT32*)pOutputConnProp->pBuffer;
+ for (UINT32 index = 0; index < cFrameCountToProcess; index++)
+ {
+ pf32Input[index] = 0.5f;
+ pf32Output[index] = 0.0f;
+ }
+
+ APOProcess(pIAPORT.get(), 1, &pInputConnProp, 1, &pOutputConnProp);
+
+ LOG_RETURN_HR_IF(pOutputConnProp->u32ValidFrameCount != cFrameCountToProcess,
+ E_FAIL,
+ L"\tFAIL: APOProcess output(%d) and input(%d) buffer valid frame counts does not match.\n", pOutputConnProp->u32ValidFrameCount, cFrameCountToProcess);
+
+ LOG_RETURN_IF_FAILED(UnlockForProcess(pIAPOConfig.get()),
+ L"\tUnlock for process failed");
+
+ LOG_RETURN_IF_FAILED(DestroyConnection(pInputConnDesc), L"\tFailed to DestroyConnection");
+ LOG_RETURN_IF_FAILED(DestroyConnection(pOutputConnDesc), L"\tFailed to DestroyConnection");
+
+ return S_OK;
+}
+
+HRESULT VerifyCustomFormatSupport(CAPODevice * deviceUnderTest)
+{
+ com_ptr_nothrow<IAudioProcessingObject> pIAPO = nullptr;
+ com_ptr_nothrow<IAudioSystemEffectsCustomFormats> pICustomFormats = nullptr;
+ com_ptr_nothrow<IEndpointUtility> pIEPUtility = nullptr;
+ IMMDevice * pIMMDevice = nullptr;
+ UINT iFormat;
+
+ if (deviceUnderTest == nullptr)
+ {
+ LOG_ERROR(L"\tCould not instantiate SysFx object.");
+ return E_NOINTERFACE;
+ }
+
+ if (nullptr == (pIMMDevice = deviceUnderTest->GetEndpoint()))
+ {
+ LOG_ERROR(L"\tUnable to get endpoint for device.");
+ return E_NOINTERFACE;
+ }
+
+ LOG_RETURN_IF_FAILED(pIMMDevice->Activate(__uuidof(IEndpointUtility), CLSCTX_INPROC_SERVER, NULL, (void**)&pIEPUtility),
+ L"\tUnable to activate utility interface");
+
+ LOG_RETURN_IF_FAILED(deviceUnderTest->GetAPOInterfaces(&pIAPO, nullptr, nullptr), L"\tCould not instantiate IAudioProcessingObject interface.");
+
+ if (pIAPO == nullptr)
+ {
+ LOG_ERROR(L"\tFAIL: Could not instantiate IAudioProcessingObject interface.");
+ return E_NOINTERFACE;
+ }
+
+ HRESULT hrQi = QIInternal(pIAPO.get(), __uuidof(IAudioSystemEffectsCustomFormats), (void**)&pICustomFormats);
+
+ if (E_NOINTERFACE == hrQi)
+ {
+ // This GFX does not support custom formats...
+ LOG_OUTPUT(L"\tSysFx does not support custom formats (QueryInterface for "
+ "IAudioSystemEffectsCustomFormats returns E_NOINTERFACE).");
+ return S_FALSE;
+ }
+
+ LOG_RETURN_IF_FAILED(hrQi, L"\tQueryInterface for IAudioSystemEffectsCustomFormats returned error.");
+
+
+ // Custom formats are available and supported
+ LOG_OUTPUT(L"\tIAudioSystemEffectsCustomFormats available on SysFx.");
+
+ LOG_RETURN_IF_FAILED(pICustomFormats->GetFormatCount(&iFormat),
+ L"\tIAudioSystemEffectsCustomFormats::GetFormatCount failed");
+
+ // No matter what we want to test all formats. However if one fails, make sure we fail
+ // the test as well
+ bool allFormatsSucceeded = true;
+
+ for (; iFormat; --iFormat)
+ {
+ com_ptr_nothrow<IAudioMediaType> pIFormat = nullptr;
+ com_ptr_nothrow<IAudioMediaType> pIFormatSuggested = nullptr;
+ LPWSTR strFormatName = NULL;
+ com_ptr_nothrow<IPartsList> pIParts = nullptr;
+ KSDATAFORMAT_WAVEFORMATEX * pKsFormat = NULL;
+ HRESULT hrFormatSupport = S_OK;
+
+ if (FAILED(hrFormatSupport = pICustomFormats->GetFormat(iFormat - 1, &pIFormat)))
+ {
+ LOG_ERROR(L"\tFAIL: IAudioSystemEffectsCustomFormats::GetFormat returned "
+ "error (0x%08lx).",
+ hrFormatSupport);
+ allFormatsSucceeded = false;
+
+ continue;
+ }
+
+ if (FAILED(hrFormatSupport = pICustomFormats->GetFormatRepresentation(iFormat - 1, &strFormatName)))
+ {
+ LOG_ERROR(L"\tFAIL: IAudioSystemEffectsCustomFormats::"
+ "GetFormatRepresentation returned error (0x%08lx)",
+ hrFormatSupport);
+ allFormatsSucceeded = false;
+
+ continue;
+ }
+
+ LOG_OUTPUT(L"\tVerifying format support for: [%ls]...", strFormatName);
+
+ // Custom format, must be supported on the output side of SysFx...
+ hrFormatSupport = IsOutputFormatSupported(pIAPO.get(), NULL, pIFormat.get(), &pIFormatSuggested);
+ const WAVEFORMATEX* original = pIFormat->GetAudioFormat();
+ const WAVEFORMATEX* suggested = pIFormatSuggested->GetAudioFormat();
+
+ LOG_OUTPUT(L"\tWaveFormatEx pIFormat - tag: %u, channels: %u, samplespersec: %u, avgbytespersec: %u, blockalign: %u, bitspersample: %u, cbsize: %u",
+ original->wFormatTag, original->nChannels, original->nSamplesPerSec, original->nAvgBytesPerSec, original->nBlockAlign,
+ original->wBitsPerSample, original->cbSize);
+
+ LOG_OUTPUT(L"\tWaveFormatEx pIFormatSuggested - tag: %u, channels: %u, samplespersec: %u, avgbytespersec: %u, blockalign: %u, bitspersample: %u, cbsize: %u",
+ suggested->wFormatTag, suggested->nChannels, suggested->nSamplesPerSec, suggested->nAvgBytesPerSec, suggested->nBlockAlign,
+ suggested->wBitsPerSample, suggested->cbSize);
+
+ if (S_FALSE == hrFormatSupport)
+ {
+ LOG_ERROR(L"\tFAIL: IsOutputFormatSupported returns S_FALSE with a suggested format");
+ allFormatsSucceeded = false;
+ }
+ else if (S_OK != hrFormatSupport)
+ {
+ LOG_ERROR(L"\tFAIL: IsOutputFormatSupported returns error (0x%08lx)",
+ hrFormatSupport);
+ allFormatsSucceeded = false;
+ }
+ else
+ {
+ if (pIFormatSuggested == nullptr)
+ {
+ LOG_ERROR(L"\tFAIL: IsOutputFormatSupported returns S_OK, but does not "
+ "suggest format.");
+ allFormatsSucceeded = false;
+ }
+ else
+ {
+ if (!IsEqualFormat(pIFormat.get(), pIFormatSuggested.get()))
+ {
+ LOG_ERROR(L"\tFAIL: IsOutputFormatSupported returns S_OK, but "
+ "returns a different format.");
+ allFormatsSucceeded = false;
+ }
+ else
+ {
+ LOG_OUTPUT(L"\tIsOutputFormatSupported supports custom format.");
+ }
+ }
+ }
+
+ pKsFormat = CreateKSDataFromWFX((WAVEFORMATEX*)pIFormat->GetAudioFormat());
+
+ if (pKsFormat == NULL)
+ {
+ LOG_ERROR(L"\tFAIL: Unable to create KSDATAFORMAT from PWAVEFORMATEX.");
+ allFormatsSucceeded = false;
+
+ continue;
+ }
+
+ {
+ unique_cotaskmem_ptr<KSDATAFORMAT_WAVEFORMATEX> formatBuffer(pKsFormat);
+
+ // Custom format, must be supported by endpoint...
+ hrFormatSupport = pIEPUtility->FindHostConnector(
+ (KSDATAFORMAT*)formatBuffer.get(),
+ formatBuffer->DataFormat.FormatSize,
+ FALSE,
+ &pIParts);
+
+ if (S_OK != hrFormatSupport)
+ {
+ LOG_ERROR(L"\tFAIL: Endpoint does not support custom format with error: (0x%08lx)", hrFormatSupport);
+ allFormatsSucceeded = false;
+ }
+ else
+ {
+ LOG_OUTPUT(L"\tEndpoint supports custom format.");
+ }
+ }
+ }
+
+ return allFormatsSucceeded;
+}
+
+HRESULT VerifyAPODataStreaming(CAPODevice* deviceUnderTest)
+{
+ wil::com_ptr<IAudioProcessingObject> pIAPO;
+ wil::com_ptr<IAudioProcessingObjectRT> pIAPORT;
+ wil::com_ptr<IAudioProcessingObjectConfiguration> pIAPOConfig;
+ APO_REG_PROPERTIES *pRegProperties = NULL;
+
+ if (deviceUnderTest == nullptr)
+ {
+ LOG_ERROR(L"\tCould not instantiate SysFx object.\n");
+ return E_NOINTERFACE;
+ }
+
+ LOG_RETURN_IF_FAILED(deviceUnderTest->GetAPOInterfaces(&pIAPO, &pIAPORT, &pIAPOConfig), L"\tCould not instantiate IAudioProcessingObject interface.");
+
+ APO_CONNECTION_PROPERTY inputProperty, outputProperty;
+ APO_CONNECTION_DESCRIPTOR inputDescriptor, outputDescriptor;
+ APO_CONNECTION_PROPERTY *pInputConnProp = &inputProperty, *pOutputConnProp = &outputProperty;
+ APO_CONNECTION_DESCRIPTOR *pInputConnDesc = &inputDescriptor, *pOutputConnDesc = &outputDescriptor;
+ UNCOMPRESSEDAUDIOFORMAT defaultFormat {};
+
+ FillFormat(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 2, sizeof(float), sizeof(float) * 8, static_cast<float>(cFrameRate), KSAUDIO_SPEAKER_STEREO, &defaultFormat);
+
+ LOG_RETURN_IF_FAILED(SetupConnection(pInputConnDesc, pOutputConnDesc, pInputConnProp, pOutputConnProp, &defaultFormat),
+ L"\tSetupConnection failed");
+
+ LOG_RETURN_IF_FAILED(LockForProcess(pIAPOConfig.get(), 1, &pInputConnDesc, 1, &pOutputConnDesc),
+ L"\tLockForProcess failed");
+
+ // setup vectors
+ FLOAT32 *pf32Input, *pf32Output;
+ pf32Input = (FLOAT32*)pInputConnProp->pBuffer;
+ pf32Output = (FLOAT32*)pOutputConnProp->pBuffer;
+ for (UINT32 index = 0; index < cFrameCountToProcess; index++)
+ {
+ pf32Input[index] = 0.5f;
+ pf32Output[index] = INFINITY;
+ }
+
+ APOProcess(pIAPORT.get(), 1, &pInputConnProp, 1, &pOutputConnProp);
+
+ GetRegistrationProperties(pIAPO.get(), &pRegProperties);
+
+ // Tests to make sure that in place APO have the correct data.
+ for (UINT32 index = 0; index < cFrameCountToProcess; index++)
+ {
+ // This will also check NaN along with INF
+ LOG_RETURN_HR_IF(!isfinite(pf32Output[index]), E_FAIL,
+ L"\tFAIL: APOProcess output(%f) is not a number or infinite.\n", pf32Output[index]);
+ }
+
+ LOG_RETURN_IF_FAILED(UnlockForProcess(pIAPOConfig.get()),
+ L"\tUnlock for process failed");
+
+ if (APO_FLAG_INPLACE == (pRegProperties->Flags & APO_FLAG_INPLACE))
+ {
+ LOG_RETURN_IF_FAILED(LockForProcess(pIAPOConfig.get(), 1, &pInputConnDesc, 1, &pInputConnDesc),
+ L"\tLockForProcess failed");
+
+ // setup vector
+ pf32Input = (FLOAT32 *)pInputConnProp->pBuffer;
+ for (UINT32 index = 0; index < cFrameCountToProcess; index++)
+ {
+ pf32Input[index] = 0.5f;
+ }
+
+ APOProcess(pIAPORT.get(), 1, &pInputConnProp, 1, &pInputConnProp);
+
+ LOG_RETURN_IF_FAILED(UnlockForProcess(pIAPOConfig.get()),
+ L"\tUnlock for process failed");
+ }
+
+ LOG_RETURN_IF_FAILED(DestroyConnection(pInputConnDesc), L"\tFailed to DestroyConnection");
+ LOG_RETURN_IF_FAILED(DestroyConnection(pOutputConnDesc), L"\tFailed to DestroyConnection");
+
+ return S_OK;
+}
+
+HRESULT VerifyActivateDeactivate(CAPODevice* deviceUnderTest)
+{
+ GUID clsid;
+ WCHAR ComRegistrationPath[1024];
+ WCHAR file[1024];
+ PWSTR end;
+ size_t cchRemaining;
+ DWORD dwSize = sizeof(WCHAR) * 1024;
+
+ if (deviceUnderTest == nullptr)
+ {
+ LOG_ERROR(L"\tCould not instantiate SysFx object.\n");
+ return E_NOINTERFACE;
+ }
+
+ deviceUnderTest->GetClsID(&clsid);
+
+ LOG_RETURN_IF_FAILED(StringCchPrintfEx(ComRegistrationPath, ARRAYSIZE(ComRegistrationPath), &end, &cchRemaining, 0, L"CLSID\\{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\\InprocServer32",
+ clsid.Data1, clsid.Data2, clsid.Data3, clsid.Data4[0], clsid.Data4[1], clsid.Data4[2], clsid.Data4[3], clsid.Data4[4], clsid.Data4[5], clsid.Data4[6], clsid.Data4[7]),
+ L"\tFailed to create string to find file name for DLL.");
+
+ LONG error = RegGetValue(HKEY_CLASSES_ROOT, ComRegistrationPath, nullptr, RRF_RT_REG_SZ, nullptr, file, &dwSize);
+
+ if (error != ERROR_SUCCESS)
+ {
+ LOG_RETURN_IF_FAILED(HRESULT_FROM_WIN32(error), L"\tFAILED to get the registry value.");
+ }
+
+ auto loadAPO = LoadLibrary(file);
+
+ if (loadAPO == NULL)
+ {
+ DWORD err = GetLastError();
+ HRESULT hr = HRESULT_FROM_WIN32(err);
+ if (SUCCEEDED(hr))
+ {
+ hr = E_FAIL;
+ }
+ LOG_RETURN_IF_FAILED(hr, L"\tFAILED to load the APO.");
+ }
+
+ BOOL bFreed = FreeLibrary(loadAPO);
+
+ if (!bFreed)
+ {
+ DWORD err = GetLastError();
+ HRESULT hr = HRESULT_FROM_WIN32(err);
+ if (SUCCEEDED(hr))
+ {
+ hr = E_FAIL;
+ }
+ LOG_RETURN_IF_FAILED(hr, L"\tFAILED to free the loaded APO.");
+ }
+
+ return S_OK;
+}
+
+struct APO_PROPERTIES {
+ PROPERTYKEY key;
+ LPARAM apoType;
+ LPCWSTR apoTypeName;
+ DWORD dwMajorVersion;
+ DWORD dwMinorVersion;
+ } PKEY_FX_EFFECTS[] = {
+ {PKEY_FX_PostMixEffectClsid, DT_GFX, L"GFX " ,6, 0},
+ {PKEY_FX_PreMixEffectClsid, DT_LFX, L"LFX ", 6, 0},
+ {PKEY_FX_StreamEffectClsid, DT_SFX, L"SFX ", 6, 3},
+ {PKEY_FX_ModeEffectClsid, DT_MFX, L"MFX ", 6, 3},
+ {PKEY_FX_EndpointEffectClsid, DT_EFX, L"EFX ", 6, 3},
+ // Composite effects
+ {PKEY_CompositeFX_StreamEffectClsid, DT_SFX, L"Composite SFX", 10, 0},
+ {PKEY_CompositeFX_ModeEffectClsid, DT_MFX, L"Composite MFX", 10, 0},
+ {PKEY_CompositeFX_EndpointEffectClsid, DT_EFX, L"Composite EFX", 10, 0},
+ // Offload effects
+ {PKEY_FX_Offload_StreamEffectClsid, DT_SFX, L"Offload SFX", 10, 0},
+ {PKEY_FX_Offload_ModeEffectClsid, DT_MFX, L"Offload MFX", 10, 0},
+ {PKEY_CompositeFX_Offload_StreamEffectClsid, DT_SFX, L"Composite Offload SFX", 10, 0},
+ {PKEY_CompositeFX_Offload_ModeEffectClsid, DT_MFX, L"Composite Offload MFX", 10, 0},
+ // Keyword Detector effects
+ {PKEY_FX_KeywordDetector_StreamEffectClsid, DT_SFX, L"KeywordDetector SFX", 10, 0},
+ {PKEY_FX_KeywordDetector_ModeEffectClsid, DT_MFX, L"KeywordDetector MFX", 10, 0},
+ {PKEY_FX_KeywordDetector_EndpointEffectClsid, DT_EFX, L"KeywordDetector EFX", 10, 0},
+ {PKEY_CompositeFX_KeywordDetector_StreamEffectClsid, DT_SFX, L"Composite KeywordDetector SFX", 10, 0},
+ {PKEY_CompositeFX_KeywordDetector_ModeEffectClsid, DT_MFX, L"Composite KeywordDetector MFX", 10, 0},
+ {PKEY_CompositeFX_KeywordDetector_EndpointEffectClsid, DT_EFX, L"Composite KeywordDetector EFX", 10, 0},
+ };
+
+CAPODevice::CAPODevice
+(
+ IMMDevice *pIEndpoint,
+ LPCWSTR pszClassId,
+ LPCWSTR pszEndpoint,
+ IPropertyStore *pIStoreDevice,
+ IPropertyStore *pIStoreFx,
+ LPARAM apoType,
+ LPCWSTR apoTypeName,
+ LPCWSTR pszAttachedDevice,
+ BOOL bProxyAPO
+)
+: m_pRegProps(NULL), m_fValid(FALSE)
+{
+ {
+ // Getting PnpId from property store...
+ PROPVARIANT pv;
+ com_ptr_nothrow<IMMDeviceEnumerator> pIDeviceEnumerator;
+ com_ptr_nothrow<IMMDevice> pIMMDevNode;
+ com_ptr_nothrow<IPropertyStore> pIDNPropStore;
+
+ LOG_RETURN_VOID_IF_FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pIDeviceEnumerator)),
+ L"\tCoCreateInstance for MMDeviceEnumerator returned error");
+
+ PropVariantInit(&pv);
+ auto clearPropVariantOnScopeExit = scope_exit([&]
+ {
+ PropVariantClear(&pv);
+ });
+
+ LOG_RETURN_VOID_IF_FAILED(pIStoreDevice->GetValue(PKEY_Endpoint_Devnode, &pv), L"\tFailed to get endpoint device");
+
+ if (VT_LPWSTR != pv.vt)
+ {
+ LOG_ERROR(L"\tEnpoint Devnode property key is of the incorrect type");
+ return;
+ }
+
+ LOG_RETURN_VOID_IF_FAILED(pIDeviceEnumerator->GetDevice(pv.pwszVal, &pIMMDevNode), L"\tGetting device (%s) from the enumerator failed", pv.pwszVal);
+
+ PropVariantClear(&pv);
+
+ LOG_RETURN_VOID_IF_FAILED(pIMMDevNode->OpenPropertyStore(STGM_READ, &pIDNPropStore), L"\tFailed to open device property store");
+
+ PropVariantInit(&pv);
+
+ LOG_RETURN_VOID_IF_FAILED(pIDNPropStore->GetValue((PROPERTYKEY&)DEVPKEY_Device_InstanceId, &pv), L"\tFailed to get device instance id from device prop store");
+
+ if (VT_LPWSTR != pv.vt)
+ {
+ LOG_ERROR(L"\tDevice instance ID is of the incorrect type");
+ return;
+ }
+
+ m_szPnPId = wil::make_cotaskmem_string_nothrow(pv.pwszVal, sizeof(m_szPnPId));
+ }
+
+ LOG_RETURN_VOID_IF_FAILED(CLSIDFromString((LPOLESTR)pszClassId, &m_gClsID), L"\tCLSIDFromString failed");
+
+ m_szAttachedDevice = wil::make_cotaskmem_string_nothrow(pszAttachedDevice, wcslen(pszAttachedDevice));
+ m_szEndpoint = wil::make_cotaskmem_string_nothrow(pszEndpoint, wcslen(pszEndpoint));
+
+ PROPVARIANT value;
+ PropVariantInit(&value);
+ if (SUCCEEDED(pIStoreFx->GetValue(PKEY_ItemNameDisplay, &value)))
+ {
+ if (value.vt == VT_LPWSTR)
+ {
+ m_sApoName = wil::make_cotaskmem_string_nothrow(value.pwszVal, wcslen(value.pwszVal));
+ }
+ PropVariantClear(&value);
+ }
+
+
+ LOG_RETURN_VOID_IF_FAILED(CoCreateInstance(m_gClsID, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_pIUnknown)), L"\tCoCreateInstance for IUnknown returned error");
+
+
+ LOG_RETURN_VOID_IF_FAILED(m_pIUnknown->QueryInterface(__uuidof(IAudioProcessingObject), (void**)&m_pIAPO),
+ L"\tCoCreateInstance for IAudioProcessingObject returned error");
+
+
+ // Getting DeviceCollection
+ {
+ PROPVARIANT pvFormat;
+
+ // the standard KSDATAFORMAT_WAVEFORMATEX only contains a
+ // WAVEFORMATEX, but we need the extensible format so
+ // we define this type here:
+ typedef struct {
+ KSDATAFORMAT DataFormat;
+ WAVEFORMATEXTENSIBLE WFXtensible;
+ } KS_WFExtensible;
+
+ KS_WFExtensible ksWfx;
+
+ // initialize the structure
+ PropVariantInit(&pvFormat);
+
+ // Clear the prop variant on scope exit
+ auto clearFormatOnExit = scope_exit([&] {
+ PropVariantClear(&pvFormat);
+ });
+
+ LOG_RETURN_VOID_IF_FAILED(pIStoreDevice->GetValue(PKEY_AudioEngine_DeviceFormat, &pvFormat), L"\tGet PKEY_AudioEngine_Device format returned error");
+
+ if (pvFormat.vt == VT_BLOB)
+ {
+ if (pvFormat.blob.cbSize != sizeof(WAVEFORMATEXTENSIBLE))
+ {
+ LOG_ERROR(L"\tpvFormat.blob.cbSize (%zu) != sizeof(WAVEFORMATEXTENSIBLE) (%zu)\n", pvFormat.blob.cbSize, sizeof(WAVEFORMATEXTENSIBLE));
+ return;
+ }
+ }
+ else
+ {
+ LOG_ERROR(L"\tFormat is not VT_BLOB, Q != %c\n", VARTYPE(pvFormat.vt));
+ return;
+ }
+
+
+ LPWAVEFORMATEX pbWfxDevice = NULL;
+ ULONG cbWfxDevice = 0;
+
+ pbWfxDevice = (WAVEFORMATEX*)pvFormat.blob.pBlobData;
+ cbWfxDevice = pvFormat.blob.cbSize;
+
+ ksWfx.DataFormat.FormatSize = sizeof(KSDATAFORMAT) + cbWfxDevice;
+ ksWfx.DataFormat.Flags = 0;
+ ksWfx.DataFormat.SampleSize = 0;
+ ksWfx.DataFormat.Reserved = 0;
+ ksWfx.DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO;
+ ksWfx.DataFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ ksWfx.DataFormat.Specifier = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX;
+ CopyMemory( &(ksWfx.WFXtensible), pbWfxDevice, cbWfxDevice );
+
+ com_ptr_nothrow<IEndpointUtility2> pIEPUtil;
+ LOG_RETURN_VOID_IF_FAILED(pIEndpoint->Activate(__uuidof(IEndpointUtility2), CLSCTX_ALL, NULL, (void**)&pIEPUtil), L"\tpIMMDevice->Activate returned error");
+
+ LOG_RETURN_VOID_IF_FAILED(pIEPUtil->GetHostConnectorDeviceCollection2((PKSDATAFORMAT)&ksWfx, sizeof(ksWfx), TRUE,
+ eHostProcessConnector, &m_pIDevCollection, &m_nSoftwareIoDeviceInCollection, &m_nSoftwareIoConnectorIndex),
+ L"\tGetting device collection returned error");
+ }
+
+ // Saved off for InitializeAPO()
+ m_pIEndpoint = pIEndpoint;
+ m_pIEPStore = pIStoreDevice;
+ m_pIFXStore = pIStoreFx;
+ m_lpType = apoType;
+ m_sApoTypeName = wil::make_cotaskmem_string_nothrow(apoTypeName, wcslen(apoTypeName));;
+ m_bProxyAPO = bProxyAPO;
+
+ // Initialize SysFx!!
+ LOG_RETURN_VOID_IF_FAILED(InitializeAPO(m_pIAPO.get()), L"\tpIAPO->Initialize returned error");
+
+ m_fValid = TRUE;
+}
+
+CAPODevice::~CAPODevice(void)
+{
+ if (NULL != m_pRegProps)
+ {
+ CoTaskMemFree(m_pRegProps);
+ m_pRegProps = NULL;
+ }
+}
+
+
+HRESULT CAPODevice::InitializeAPO
+(
+ IAudioProcessingObject *pIAPO
+)
+{
+ LOG_RETURN_HR_IF(pIAPO == nullptr, E_POINTER, L"IAudioProcessingObject is null");
+
+ if(DT_SMEFX & GetDeviceType())
+ {
+ APOInitSystemEffects2 sysfxInitParams2 = {0};
+ // Setup initialization struct
+ sysfxInitParams2.APOInit.cbSize = sizeof(APOInitSystemEffects2);
+ sysfxInitParams2.APOInit.clsid = m_gClsID;
+ sysfxInitParams2.pReserved = NULL;
+ sysfxInitParams2.pDeviceCollection = m_pIDevCollection.get();
+ sysfxInitParams2.pAPOEndpointProperties = m_pIEPStore.get();
+ sysfxInitParams2.pAPOSystemEffectsProperties = m_pIFXStore.get();
+ sysfxInitParams2.InitializeForDiscoveryOnly = TRUE;
+ sysfxInitParams2.AudioProcessingMode = AUDIO_SIGNALPROCESSINGMODE_DEFAULT;
+ sysfxInitParams2.nSoftwareIoDeviceInCollection = m_nSoftwareIoDeviceInCollection;
+ sysfxInitParams2.nSoftwareIoConnectorIndex = m_nSoftwareIoConnectorIndex;
+
+ return pIAPO->Initialize(sysfxInitParams2.APOInit.cbSize, (BYTE*)(&sysfxInitParams2));
+ }
+ else if (DT_LGFX & GetDeviceType())
+ {
+ APOInitSystemEffects sysfxInitParams = {0};
+ // Setup initialization struct
+ sysfxInitParams.APOInit.cbSize = sizeof(sysfxInitParams);
+ sysfxInitParams.APOInit.clsid = m_gClsID;
+ sysfxInitParams.pAPOEndpointProperties = m_pIEPStore.get();
+ sysfxInitParams.pAPOSystemEffectsProperties = m_pIFXStore.get();
+ sysfxInitParams.pDeviceCollection = m_pIDevCollection.get();
+ sysfxInitParams.pReserved = NULL;
+
+ return (pIAPO->Initialize(sizeof(sysfxInitParams), (BYTE*)&sysfxInitParams));
+ }
+
+ // We should never get here unless we are initializing a first party APO (which we shouldn't be)
+ LOG_ERROR(L"SFX is of unknown type. Type is: %d", GetDeviceType());
+ return E_FAIL;
+}
+
+HRESULT CAPODevice::GetAPOInterfaces
+(
+ IAudioProcessingObject **ppIAPO,
+ IAudioProcessingObjectRT **ppIAPORT,
+ IAudioProcessingObjectConfiguration **ppIAPOConfig
+)
+{
+ if (ppIAPO != nullptr)
+ {
+ LOG_RETURN_IF_FAILED(UnknownQIInternal(m_pIUnknown.get(), __uuidof(IAudioProcessingObject), (void**)ppIAPO),
+ L"\tCoCreateInstance for IAudioProcessingObject failed");
+ }
+
+ if (ppIAPORT != nullptr)
+ {
+ LOG_RETURN_IF_FAILED(UnknownQIInternal(m_pIUnknown.get(), __uuidof(IAudioProcessingObjectRT), (void**)ppIAPORT),
+ L"\tCoCreateInstance for IAudioProcessingObjectRT failed");
+ }
+
+ if (ppIAPOConfig != nullptr)
+ {
+ LOG_RETURN_IF_FAILED(UnknownQIInternal(m_pIUnknown.get(), __uuidof(IAudioProcessingObjectConfiguration), (void**)ppIAPOConfig),
+ L"\tCoCreateInstance for IAudioProcessingObjectConfiguration failed");
+ }
+
+ return S_OK;
+}
+
+//--------------------------------------------------------------------------
+// CAPODeviceList constructor
+CAPODeviceList::CAPODeviceList ()
+{
+}
+
+//--------------------------------------------------------------------------
+// ~CAPODeviceList destructor
+
+CAPODeviceList::~CAPODeviceList(VOID)
+{
+ m_DeviceList.clear();
+
+ RegDeleteKeyValue(HKEY_LOCAL_MACHINE,
+ REGKEY_AUDIO_SOFTWARE,
+ PREVENT_APOTEST_CRASH_OR_REPORT_ON_APO_EXCEPTION);
+}
+
+HRESULT CAPODeviceList::AddSysFxDevices()
+{
+ HRESULT hr = S_OK;
+ bool allApoDevicesValid = true;
+ com_ptr_nothrow<IMMEndpoint> pIMMEndpoint;
+ com_ptr_nothrow<IMMEndpointInternal> pIMMEndpointInt;
+ com_ptr_nothrow<IPropertyStore> pIPropertyStoreDevice;
+ com_ptr_nothrow<IPropertyStore> pIPropertyStoreFX;
+ com_ptr_nothrow<IDeviceTopology> pIDeviceTopology;
+ com_ptr_nothrow<IConnector> pIConnector;
+ LPCWSTR strConnectedDevice = NULL;
+ PROPVARIANT pvName;
+ PROPVARIANT pv;
+ PROPVARIANT pvProxy;
+ BOOL bAPOProxy;
+
+
+ if (FAILED(hr = m_spDevice->Activate(__uuidof(IDeviceTopology), CLSCTX_INPROC_SERVER, NULL, (void**)&pIDeviceTopology)))
+ {
+ if (E_NOINTERFACE == hr)
+ {
+ LOG_OUTPUT(L"\tDevice is not active. Removing from test cases");
+ // There's the possibility that this legit if device is not active.
+ return S_FALSE;
+ }
+
+ return hr;
+ }
+
+ LOG_RETURN_IF_FAILED(pIDeviceTopology->GetConnector(0, &pIConnector),
+ L"\tFailed to get device connector");
+
+ LOG_RETURN_IF_FAILED(pIConnector->GetDeviceIdConnectedTo((LPWSTR *)&strConnectedDevice),
+ L"\tFailed to get connected device ID");
+
+ LOG_RETURN_IF_FAILED(m_spDevice->QueryInterface(__uuidof(IMMEndpoint), (void**)&pIMMEndpoint),
+ L"\tQueryInterfacce for IMMEndpoint failed");
+
+ LOG_RETURN_IF_FAILED(pIMMEndpoint->QueryInterface(__uuidof(IMMEndpointInternal), (void**)&pIMMEndpointInt),
+ L"\tQueryInterface for IMMEndpointInternal failed");
+
+ LOG_RETURN_IF_FAILED(m_spDevice->OpenPropertyStore(STGM_READ, &pIPropertyStoreDevice),
+ L"\tFailed to open property store for device");
+
+ LOG_RETURN_IF_FAILED(pIMMEndpointInt->TryOpenFXPropertyStore(STGM_READ, &pIPropertyStoreFX),
+ L"\tFailed to Open SysFX property store for device");
+
+ if (!pIPropertyStoreFX)
+ {
+ // This device doesn't appear to have any SysFX
+ LOG_OUTPUT(L"\tThis device probably doesn't have any SysFx. Skipping and removing from test");
+ return S_FALSE;
+ }
+
+ PropVariantInit(&pvName);
+ if (SUCCEEDED(pIPropertyStoreDevice->GetValue(PKEY_Device_FriendlyName, &pvName)))
+ {
+ if (VT_LPWSTR != V_VT(&pvName))
+ {
+ LOG_OUTPUT(L"\tFriendlyName value is not a string.");
+ }
+ }
+ else
+ {
+ LOG_OUTPUT(L"\tIPropertyStore::GetValue returned error.");
+ }
+
+
+ OSVERSIONINFO ver = {0};
+ ver.dwOSVersionInfoSize = sizeof(ver);
+
+ GetVersionEx(&ver);
+
+ for(int i = 0; i < ARRAYSIZE(PKEY_FX_EFFECTS); i++)
+ {
+ if (ver.dwMajorVersion < PKEY_FX_EFFECTS[i].dwMajorVersion ||
+ (ver.dwMajorVersion == PKEY_FX_EFFECTS[i].dwMajorVersion &&
+ ver.dwMinorVersion < PKEY_FX_EFFECTS[i].dwMinorVersion))
+ {
+ continue;
+ }
+
+ PropVariantInit(&pv);
+ hr = pIPropertyStoreFX->GetValue(PKEY_FX_EFFECTS[i].key, &pv);
+ LOG_OUTPUT(L"\tPKEY_FX_EFFECTS[%i] - GetValue PKEY_FX_EFFECTS[%i].key - 0x%x", i, i, hr);
+ if (S_OK == hr)
+ {
+ LOG_OUTPUT(L"\tPKEY_FX_EFFECTS[%i] - V_VT(&pv) - %i", i, V_VT(&pv));
+ if (VT_LPWSTR == V_VT(&pv))
+ {
+ bAPOProxy = FALSE;
+ PropVariantInit(&pvProxy);
+
+ // Check if mode effect is proxy
+ if (PKEY_FX_EFFECTS[i].apoType & DT_MFX)
+ {
+ hr = pIPropertyStoreFX->GetValue(PKEY_MFX_ProcessingModes_Supported_For_Streaming, &pvProxy);
+ LOG_OUTPUT(L"\tPKEY_FX_EFFECTS[%i] - GetValue MFX - 0x%x", i, hr);
+ if (S_OK == hr)
+ {
+ bAPOProxy = (pvProxy.vt != (VT_VECTOR | VT_LPWSTR)) ? TRUE : FALSE;
+ }
+ }
+
+ // Check if endpoint effect is proxy
+ if (PKEY_FX_EFFECTS[i].apoType & DT_EFX)
+ {
+ hr = pIPropertyStoreFX->GetValue(PKEY_EFX_ProcessingModes_Supported_For_Streaming, &pvProxy);
+ LOG_OUTPUT(L"\tPKEY_FX_EFFECTS[%i] - GetValue EFX - 0x%x", i, hr);
+ if (S_OK == hr)
+ {
+ bAPOProxy = (pvProxy.vt != (VT_VECTOR | VT_LPWSTR)) ? TRUE : FALSE;
+ }
+ }
+
+ LOG_OUTPUT(L"\tLooking at Audio Processing Object %ls\n", pv.pwszVal);
+
+ unique_ptr<CAPODevice> pSysFxDevice(new CAPODevice(m_spDevice.get(), pv.pwszVal, pvName.pwszVal, pIPropertyStoreDevice.get(),
+ pIPropertyStoreFX.get(), PKEY_FX_EFFECTS[i].apoType, PKEY_FX_EFFECTS[i].apoTypeName,
+ strConnectedDevice, bAPOProxy));
+
+ if (NULL != pSysFxDevice)
+ {
+ if (pSysFxDevice->IsValid())
+ {
+ pSysFxDevice->m_fSelected = TRUE;
+ m_DeviceList.push_back(move(pSysFxDevice));
+ }
+ else
+ {
+ LOG_ERROR(L"\tInvalid sysFX device encountered");
+ }
+ }
+
+ PropVariantClear(&pvProxy);
+ }
+ else if ((VT_LPWSTR | VT_VECTOR) == V_VT(&pv) && pv.calpwstr.cElems > 0)
+ {
+ UINT32 cClsids = pv.calpwstr.cElems;
+ bAPOProxy = FALSE;
+
+ for (UINT32 j = 0; j < cClsids; j++)
+ {
+ LOG_OUTPUT(L"\tLooking at Audio Processing Object %ls\n", pv.calpwstr.pElems[j]);
+
+ unique_ptr<CAPODevice> pSysFxDevice(new CAPODevice(m_spDevice.get(), pv.calpwstr.pElems[j], pvName.pwszVal, pIPropertyStoreDevice.get(),
+ pIPropertyStoreFX.get(), PKEY_FX_EFFECTS[i].apoType, PKEY_FX_EFFECTS[i].apoTypeName,
+ strConnectedDevice, bAPOProxy));
+
+ if (NULL != pSysFxDevice)
+ {
+ if (pSysFxDevice->IsValid())
+ {
+ pSysFxDevice->m_fSelected = TRUE;
+ m_DeviceList.push_back(move(pSysFxDevice));
+ }
+ else
+ {
+ LOG_ERROR(L"\tInvalid sysFX device encountered");
+ }
+ }
+ }
+ }
+ }
+ PropVariantClear(&pv);
+ }
+
+ return allApoDevicesValid ? S_OK : E_FAIL;
+}
+
+HRESULT CAPODeviceList::Initialize(wil::com_ptr_nothrow<IMMDevice> pDevice, LPWSTR deviceName)
+{
+ if (pDevice == nullptr || deviceName == nullptr)
+ {
+ LOG_ERROR(L"\tIMMDevice is null");
+ return E_INVALIDARG;
+ }
+
+ m_spDevice = pDevice;
+
+ wcscat(m_deviceName, deviceName);
+
+ DWORD dwValue = 1;
+ // Set registry entry so AudioDG won't crash during IAudioProcessingObjectRT function calls
+ RETURN_IF_WIN32_ERROR(RegSetKeyValue(HKEY_LOCAL_MACHINE,
+ REGKEY_AUDIO_SOFTWARE,
+ PREVENT_APOTEST_CRASH_OR_REPORT_ON_APO_EXCEPTION,
+ REG_DWORD,
+ &dwValue,
+ sizeof(dwValue)));
+
+ return S_OK;
+}
diff --git a/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.h b/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.h
new file mode 100644
index 00000000..31225995
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.h
@@ -0,0 +1,180 @@
+#include <stdafx.h>
+
+class CApoDeviceTests;
+
+class CAPODevice
+{
+ public:
+ CAPODevice
+ (
+ IMMDevice *pIEndpoint,
+ LPCWSTR pszClassId,
+ LPCWSTR pszEndpoint,
+ IPropertyStore *pIStoreDevice,
+ IPropertyStore *pIStoreFx,
+ LPARAM apoType,
+ LPCWSTR apoName,
+ LPCWSTR pszAttachedDevice,
+ BOOL bProxyAPO
+ );
+ CAPODevice
+ (
+ IAudioProcessingObject *pIAPO,
+ LPCWSTR pszClassId
+ );
+ ~CAPODevice(void);
+ HRESULT InitializeAPO(IAudioProcessingObject *pIAPO);
+ DWORD GetDeviceType() { return (DWORD)(m_lpType); }
+ BOOL IsValid() { return (m_fValid); }
+ void GetClsID(GUID *pgClsID) { *pgClsID = m_gClsID; }
+ HRESULT GetAPOInterfaces
+ (
+ IAudioProcessingObject **pIAPO,
+ IAudioProcessingObjectRT **pIAPORT,
+ IAudioProcessingObjectConfiguration **pIAPOConfig
+ );
+ IUnknown *GetObject() { return ((IUnknown*)m_pIUnknown.get()); }
+ IMMDevice *GetEndpoint() { return m_pIEndpoint.get(); }
+ IPropertyStore *GetEndpointStore() { return m_pIEPStore.get(); }
+ IPropertyStore *GetFxStore() { return m_pIFXStore.get(); }
+ IMMDeviceCollection *GetDeviceCollection() { return m_pIDevCollection.get(); }
+ UINT GetSoftwareIoDeviceInCollection() { return m_nSoftwareIoDeviceInCollection; }
+ UINT GetSoftwareIoConnectorIndex() { return m_nSoftwareIoConnectorIndex; }
+ PAPO_REG_PROPERTIES GetProperties() { return (m_pRegProps); }
+ LPSTR GetAttachedDevice() { return (LPSTR)(m_szAttachedDevice.get()); }
+ BOOL IsProxyApo() { return m_bProxyAPO; }
+
+ BOOL m_fSelected; // if TRUE, this device will be selected
+ wil::unique_cotaskmem_string m_szEndpoint;
+ wil::unique_cotaskmem_string m_sApoName;
+ wil::unique_cotaskmem_string m_sApoTypeName;
+
+ private:
+ BOOL m_fValid;
+ GUID m_gClsID;
+ wil::com_ptr_nothrow<IUnknown> m_pIUnknown;
+ wil::com_ptr_nothrow<IAudioProcessingObject> m_pIAPO;
+ PAPO_REG_PROPERTIES m_pRegProps;
+
+ // These are only used for SysFx
+ wil::com_ptr_nothrow<IMMDevice> m_pIEndpoint;
+ wil::unique_cotaskmem_string m_szAttachedDevice;
+ wil::com_ptr_nothrow<IPropertyStore> m_pIEPStore;
+ wil::com_ptr_nothrow<IPropertyStore> m_pIFXStore;
+ wil::com_ptr_nothrow<IMMDeviceCollection> m_pIDevCollection;
+ UINT m_nSoftwareIoDeviceInCollection;
+ UINT m_nSoftwareIoConnectorIndex;
+ BOOL m_bProxyAPO;
+ wil::unique_cotaskmem_string m_szPnPId; // a string that represents the PnPId (or other description) of the device under test
+ LPARAM m_lpType; // flags that will be intersected with test case flags
+};
+
+
+// ----------------------------------------------------------
+// Stores a list of APO devices on an endpoint pDevice
+// The list is populated using AddSysFxDevices and accessed
+// using m_DeviceList
+// ----------------------------------------------------------
+class CAPODeviceList
+{
+public:
+ CAPODeviceList();
+ ~CAPODeviceList(VOID);
+
+ HRESULT Initialize(wil::com_ptr_nothrow<IMMDevice> pDevice, LPWSTR deviceName);
+ HRESULT AddSysFxDevices();
+
+protected:
+ friend class CApoDeviceTests;
+
+ std::list<std::unique_ptr<CAPODevice>> m_DeviceList;
+ wil::com_ptr_nothrow<IMMDevice> m_spDevice;
+ WCHAR m_deviceName[MAX_PATH] = { '\0' };
+};
+
+class CApoDeviceTests : public WEX::TestClass<CApoDeviceTests>
+{
+ CApoDeviceTests(){};
+ ~CApoDeviceTests(){};
+
+ BEGIN_TEST_CLASS(CApoDeviceTests)
+ START_APPVERIFIFER
+ TEST_CLASS_PROPERTY(L"Owner", L"auddev")
+ TEST_CLASS_PROPERTY(L"TestClassification", L"Feature")
+ TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"audiodg.exe")
+ TEST_CLASS_PROPERTY(L"ArtifactUnderTest", L"audioenginebaseapop.h")
+ TEST_CLASS_PROPERTY(L"ArtifactUnderTest", L"devicetopologyp.h")
+ TEST_CLASS_PROPERTY(L"RunAs", L"Elevated")
+ TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA")
+ TEST_CLASS_PROPERTY(L"Ignore", L"false")
+ END_APPVERIFIER
+ END_TEST_CLASS()
+
+ public:
+ TEST_METHOD_SETUP(setUpMethod);
+ TEST_METHOD_CLEANUP(tearDownMethod);
+
+ protected:
+ BEGIN_TEST_METHOD(TestAPOInitialize)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"ED37B03B-F091-4C8D-9EDD-FE7EC803BC8E")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"5B0F542E-719A-4949-A9DF-F8BBAC0E9912")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO - Verify APO Initializes - TestAPOInitialize")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"1")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"5")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party APO Test: TestAPOInitialize")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+
+ BEGIN_TEST_METHOD(TestCustomFormatSupport)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"571BD021-64F5-4CD1-9BA4-8ABD0857025F")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"EB313E34-CD9D-410B-9C6F-E7032C74F4D1")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO - Verify All Formats on Device are Valid - TestCustomFormatSupport")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"1")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"5")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party APO Test: TestCustomFormatSupport")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+
+ BEGIN_TEST_METHOD(TestValidFrameCount)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"B9E9F61A-2882-4256-ABA8-D57A46E54B76")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"ABE4D880-7F1A-434C-A7B9-248FFA96D2CF")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO - Verify Frame Count is Valid - TestValidFrameCount")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"1")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"5")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party APO Test: TestValidFrameCount")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+
+ BEGIN_TEST_METHOD(TestAPODataStreaming)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"D327B019-99E4-41BD-BF66-AF1A92801DAC")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"9EBF7FC3-7ADB-49A1-B0E0-C7501841E05A")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO - Verify APO Streams Data - TestAPODataStreaming")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"1")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"5")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party APO Test: TestAPODataStreaming")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+
+ BEGIN_TEST_METHOD(TestActivateDeactivate)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"D1C21041-9080-4990-B6A2-5F718649A3F2")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"F0556EDA-4757-409F-AE89-C5C1B0E31A84")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO - Verify Proper APO Activation and Deactivation - TestActivateDeactivate")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"1")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"5")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party APO Test: TestActivateDeactivate")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+
+ private:
+ std::list<std::unique_ptr<CAPODeviceList>> m_testDeviceWrapperList;
+};
+
+BEGIN_MODULE()
+ MODULE_PROPERTY(L"EtwLogger:WPRProfileFile", L"Audio-Tests.wprp")
+ MODULE_PROPERTY(L"EtwLogger:WPRProfile", L"MultimediaCategory.Verbose.File")
+END_MODULE()
diff --git a/audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.cpp b/audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.cpp
new file mode 100644
index 00000000..9fc5ae45
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.cpp
@@ -0,0 +1,96 @@
+#include <stdafx.h>
+
+#include <initguid.h>
+#include "propkey.h"
+#include <AudioClient.h>
+#include <SpatialAudioClient.h>
+#include <functiondiscoverykeys.h>
+#include <DevPKey.h>
+#include <AudioEngineBaseAPOP.h>
+#include "TestMediaType.h"
+#include <audioenginebaseapo.h>
+#include <MMDeviceAPI.h>
+#include <MMDeviceAPIP.h>
+
+#include <WexTestClass.h>
+
+#include <list>
+#include <memory>
+#include <ctime>
+#include <thread>
+#include <future>
+#include <mutex>
+#include <functional>
+#include <atlbase.h>
+
+#include "APOOSUpgradeTest.h"
+#include "APOStressTest.h"
+#include "sinewave.h"
+
+using namespace std;
+using namespace wil;
+using namespace WEX::Logging;
+using namespace WEX::Common;
+
+#define IF_FAILED_RETURN(hr) { HRESULT hrCode = hr; if(FAILED(hrCode)) { return hrCode; } }
+#define IF_SUCCEEDED(hr) { HRESULT hrCode = hr; if (!(hrCode == S_OK || hrCode == AUDCLNT_E_WRONG_ENDPOINT_TYPE)) { VERIFY_SUCCEEDED(hrCode); } }
+
+bool SetupSkipRTHeap();
+bool CleanupSkipRTHeap();
+
+bool CAPOUpgradeTest::setUpMethod()
+{
+ return SetupSkipRTHeap();
+}
+
+bool CAPOUpgradeTest::tearDownMethod()
+{
+ return CleanupSkipRTHeap();
+}
+
+void CAPOUpgradeTest::TestUpgrade()
+{
+ UINT cDevices = 0;
+ com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ com_ptr_nothrow<IMMDeviceCollection> spDevices;
+
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spEnumerator)));
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eAll, DEVICE_STATE_ACTIVE, &spDevices));
+ VERIFY_SUCCEEDED(spDevices->GetCount(&cDevices));
+
+ for (UINT i = 0; i < cDevices; i++)
+ {
+ com_ptr_nothrow<IMMDevice> pDevice = nullptr;
+ com_ptr_nothrow<IAudioClient2> pAudioClient = nullptr;
+ com_ptr_nothrow<IMMEndpoint> pEndpoint = nullptr;
+ BOOL bOffloadCapable = FALSE;
+ EDataFlow eFlow = eAll;
+
+ VERIFY_SUCCEEDED(spDevices->Item(i, &pDevice));
+
+ VERIFY_SUCCEEDED(com_query_to_nothrow(pDevice, &pEndpoint));
+ VERIFY_SUCCEEDED(pEndpoint->GetDataFlow(&eFlow));
+
+ VERIFY_SUCCEEDED(pDevice->Activate(__uuidof(IAudioClient2), CLSCTX_ALL, NULL, (void**)&pAudioClient));
+ VERIFY_SUCCEEDED(pAudioClient->IsOffloadCapable(AudioCategory_Media, &bOffloadCapable));
+
+ if (eFlow == eAll || eFlow == eRender)
+ {
+ VERIFY_SUCCEEDED(BasicAudioStreaming(pDevice.get()));
+
+ VERIFY_SUCCEEDED(BasicSpatialAudio(pDevice.get()));
+
+ VERIFY_SUCCEEDED(BasicAudioLoopback(pDevice.get()));
+
+ if (bOffloadCapable)
+ {
+ VERIFY_SUCCEEDED(BasicOffloadStreaming(pDevice.get()));
+ }
+ }
+
+ if (eFlow == eAll || eFlow == eCapture)
+ {
+ VERIFY_SUCCEEDED(BasicAudioCapture(pDevice.get()));
+ }
+ }
+}
diff --git a/audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.h b/audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.h
new file mode 100644
index 00000000..3c61390d
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.h
@@ -0,0 +1,38 @@
+#include <stdafx.h>
+
+class CAPOUpgradeTest
+{
+ CAPOUpgradeTest(){};
+ ~CAPOUpgradeTest(){};
+
+ BEGIN_TEST_CLASS(CAPOUpgradeTest)
+ START_OSUPGRADE
+ START_APPVERIFIFER_UPGRADE
+ TEST_CLASS_PROPERTY(L"Owner", L"auddev")
+ TEST_CLASS_PROPERTY(L"TestClassification", L"Feature")
+ TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"audiodg.exe")
+ TEST_CLASS_PROPERTY(L"ArtifactUnderTest", L"audioenginebaseapop.h")
+ TEST_CLASS_PROPERTY(L"RunAs", L"Elevated")
+ TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA")
+ TEST_CLASS_PROPERTY(L"Ignore", L"false")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfileFile", L"Audio-Tests.wprp")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfile", L"MultimediaCategory.Verbose.File")
+ END_APPVERIFIER
+ END_TEST_CLASS()
+
+public:
+ TEST_METHOD_SETUP(setUpMethod);
+ TEST_METHOD_CLEANUP(tearDownMethod);
+
+protected:
+ BEGIN_TEST_METHOD(TestUpgrade)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"B98FD3CB-2E6E-4152-BA85-3021149B8E86")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"C467E942-EB43-477F-8E2A-01D07FEF90C4")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO - Verify APOs Work After an OS Upgrade - TestUpgrade")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"60")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"240")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party APO Test: TestUpgrade")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+}; \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/APOStressTest.cpp b/audio/tests/HLK/ThirdPartyApoTests/APOStressTest.cpp
new file mode 100644
index 00000000..160f488b
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/APOStressTest.cpp
@@ -0,0 +1,777 @@
+#include <stdafx.h>
+
+#include <initguid.h>
+#include "propkey.h"
+#include <AudioClient.h>
+#include <SpatialAudioClient.h>
+#include <functiondiscoverykeys.h>
+#include <DevPKey.h>
+#include <AudioEngineBaseAPOP.h>
+#include "TestMediaType.h"
+#include <audioenginebaseapo.h>
+#include <MMDeviceAPI.h>
+#include <MMDeviceAPIP.h>
+
+#include <WexTestClass.h>
+
+#include <list>
+#include <memory>
+#include <ctime>
+#include <thread>
+#include <future>
+#include <mutex>
+#include <functional>
+#include <atlbase.h>
+
+#include "APOStressTest.h"
+#include "sinewave.h"
+#include "util.h"
+
+#include <psapi.h>
+#include <processthreadsapi.h>
+
+using namespace std;
+using namespace wil;
+using namespace WEX::Logging;
+using namespace WEX::Common;
+
+#define NUMBER_OF_TESTS 8
+#define THIRTY_MIN_IN_SEC 1800
+#define SIXTY_SECONDS 60
+#define ONE_SEC_IN_MS 1000
+#define THIRTY_MIN_IN_MS 1800000
+#define MAX_ALLOWED_PAGEFILE_USAGE_IN_BYTES 300000000
+
+#define IF_FAILED_RETURN(hr) { HRESULT hrCode = hr; if(FAILED(hrCode)) { return hrCode; } }
+
+#define REGKEY_AUDIOSERVICE L"Software\\Microsoft\\Windows\\CurrentVersion\\Audio"
+#define REGVALUE_SKIPRTHEAP L"SkipRTHeap"
+
+const DWORD WAIT_TIME_MIN_FOR_THREAD_OPERATION = 3u * 1000u; // 3 seconds
+const DWORD WAIT_TIME_MAX_FOR_THREAD_OPERATION = 10u * 1000u; // 10 seconds
+
+size_t maxAudioDGPagefileUsage = 0;
+std::mutex maxAudioDGPagefileUsage_mutex; // protects maxAudioDGPagefileUsage_mutex
+
+template <typename T>
+inline T RandInRange(T min, T max)
+{
+ // Treat the value span as a double right away. Otherwise if min = 0, and max
+ // is truly the max for the type, the value span could loop to zero.
+ double valueSpan = 1.0f + (double) (max - min);
+
+ // The following had a problem where doubles larger than 0x8000000000000000 were always truncating
+ // to 0x8000000000000000 when cast to a DWORD64. Multiplying by 0.5f and then (T) 2 seemed to
+ // get around this problem.
+ //return min + (T) ((double) (max - min + 1) * (double) rand() / (double) (RAND_MAX + 1));
+
+ // Then the following had a problem where RandInRange(0, 1) could never return 1.
+ //return min + (T) 2 * (T) (0.5f * valueSpan * ((double) rand() / (double) (RAND_MAX + 1)));
+
+ double offset = valueSpan * ((double) rand() / (double) (RAND_MAX + 1));
+
+ if (offset > (double) ((DWORD64) 0x8000000000000000))
+ {
+ return min + (T) 2 * (T) (0.5f * offset);
+ }
+
+ return min + (T) offset;
+}
+
+void WriteToAudioObjectBuffer(FLOAT* buffer, UINT frameCount, FLOAT frequency, UINT samplingRate)
+{
+ const double PI = 4 * atan2(1.0, 1.0);
+ static double _radPhase = 0.0;
+ double step = 2 * PI * frequency / samplingRate;
+
+ for (UINT i = 0; i < frameCount; i++)
+ {
+ double sample = sin(_radPhase);
+ buffer[i] = FLOAT(sample);
+ _radPhase += step; // next frame phase
+
+ if (_radPhase >= 2 * PI)
+ {
+ _radPhase -= 2 * PI;
+ }
+ }
+}
+
+bool SetupSkipRTHeap()
+{
+ DWORD dwValue = 1;
+ DWORD cbData = sizeof(dwValue);
+
+ DWORD result = RegSetKeyValueW(HKEY_LOCAL_MACHINE, REGKEY_AUDIOSERVICE, REGVALUE_SKIPRTHEAP, REG_DWORD, &dwValue, cbData);
+
+ RestartAudioService();
+
+ return (result == ERROR_SUCCESS);
+}
+
+bool CleanupSkipRTHeap()
+{
+ DWORD result = RegDeleteKeyValueW(HKEY_LOCAL_MACHINE, REGKEY_AUDIOSERVICE, REGVALUE_SKIPRTHEAP);
+
+ RestartAudioService();
+
+ return (result == ERROR_SUCCESS);
+}
+
+bool CAPOStressTest::setUpMethod()
+{
+ return SetupSkipRTHeap();
+}
+
+bool CAPOStressTest::tearDownMethod()
+{
+ return CleanupSkipRTHeap();
+}
+
+void CAPOStressTest::AudioAPOStressTest()
+{
+
+ UINT cDevices = 0;
+ com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ com_ptr_nothrow<IMMDeviceCollection> spDevices;
+ vector<com_ptr<IMMDevice>> sEndpoints;
+
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spEnumerator)));
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eAll, DEVICE_STATE_ACTIVE, &spDevices));
+ VERIFY_SUCCEEDED(spDevices->GetCount(&cDevices));
+
+ for (UINT i = 0; i < cDevices; i++)
+ {
+ wil::com_ptr<IMMDevice> pEndpoint = nullptr;
+
+ VERIFY_SUCCEEDED(spDevices->Item(i, &pEndpoint));
+
+ sEndpoints.push_back(pEndpoint);
+ }
+
+ RunStressTest(THIRTY_MIN_IN_SEC, sEndpoints);
+}
+
+std::wstring QueryProcessName(const DWORD pid)
+{
+ wil::unique_handle hProcess(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
+ if(NULL == hProcess)
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+ else
+ {
+ WCHAR szProcessName[1024];
+ DWORD cchSize = ARRAYSIZE(szProcessName);
+ if (0 == QueryFullProcessImageNameW(hProcess.get(), 0, szProcessName, &cchSize))
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+
+ PWSTR fileName = szProcessName + cchSize;
+ while (fileName != szProcessName && *fileName != L'\\') fileName--;
+
+ return (*fileName == L'\\') ? fileName + 1 : fileName;
+ }
+}
+
+DWORD GetAudioDGProcessID()
+{
+ // Get the list of process identifiers.
+
+ DWORD aProcesses[1024], cbNeeded, cProcesses;
+ unsigned int i;
+
+ if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )
+ {
+ return 0;
+ }
+
+ // Calculate how many process identifiers were returned.
+
+ cProcesses = cbNeeded / sizeof(DWORD);
+
+ // Look for AudioDG name and process identifier on each process.
+
+ for ( i = 0; i < cProcesses; i++ )
+ {
+ if( aProcesses[i] != 0 && 0 == _wcsicmp(QueryProcessName(aProcesses[i]).c_str(), L"AUDIODG.EXE"))
+ {
+ return aProcesses[i];
+ }
+ }
+
+ return 0;
+}
+
+void GetAudioDGMemoryUsageSample()
+{
+ // Get the AudioDG process ID
+ DWORD audioDGProcessID = GetAudioDGProcessID();
+
+ if (audioDGProcessID != 0)
+ {
+ std::lock_guard<std::mutex> autoLock(maxAudioDGPagefileUsage_mutex);
+
+ // Memory PagefileUsage snapshot
+ PROCESS_MEMORY_COUNTERS pmc;
+
+ HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
+ PROCESS_VM_READ,
+ FALSE, audioDGProcessID );
+
+ if (hProcess != NULL && GetProcessMemoryInfo( hProcess, &pmc, sizeof(pmc)) )
+ {
+ maxAudioDGPagefileUsage = max(maxAudioDGPagefileUsage, pmc.PagefileUsage);
+ }
+
+ VERIFY_IS_TRUE(maxAudioDGPagefileUsage < MAX_ALLOWED_PAGEFILE_USAGE_IN_BYTES);
+
+ CloseHandle( hProcess );
+ }
+ else
+ {
+ Log::Warning(L"Unable to find AudioDG PID");
+ }
+}
+
+HRESULT BasicAudioStreaming(IMMDevice* pEndpoint)
+{
+ com_ptr_nothrow<IAudioClient2> pAudioClient;
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> mixFormat;
+
+ if (pEndpoint == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+
+ IF_FAILED_RETURN(pEndpoint->Activate(__uuidof(IAudioClient2), CLSCTX_ALL, NULL, (void**)&pAudioClient));
+
+ AudioClientProperties clientProperties = { 0 };
+ clientProperties.cbSize = sizeof(AudioClientProperties);
+ clientProperties.bIsOffload = false;
+ clientProperties.eCategory = (AUDIO_STREAM_CATEGORY) RandInRange(0, 11);
+
+ IF_FAILED_RETURN(pAudioClient->SetClientProperties(&clientProperties));
+
+ IF_FAILED_RETURN(pAudioClient->GetMixFormat(wil::out_param_ptr<WAVEFORMATEX **>(mixFormat)));
+
+ IF_FAILED_RETURN(pAudioClient->Initialize(
+ AUDCLNT_SHAREMODE_SHARED,
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
+ 0,
+ 0,
+ mixFormat.get(),
+ nullptr));
+
+ com_ptr_nothrow<IAudioRenderClient> pRenderClient;
+ IF_FAILED_RETURN(pAudioClient->GetService(_uuidof(IAudioRenderClient), wil::out_param_ptr<void **>(pRenderClient)));
+
+ wil::unique_event bufferCompleteEvent;
+ bufferCompleteEvent.create();
+ IF_FAILED_RETURN(pAudioClient->SetEventHandle(bufferCompleteEvent.get()));
+
+ UINT32 bufferFrameCount;
+ REFERENCE_TIME duration = RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION);
+ IF_FAILED_RETURN(pAudioClient->GetBufferSize(&bufferFrameCount));
+
+ SineWave sw(mixFormat->nChannels, mixFormat->nSamplesPerSec);
+ for (unsigned i = 0; i < mixFormat->nChannels; ++i)
+ {
+ sw.SetChannel(i, 400, 0.25f);
+ }
+
+ BYTE* data{};
+ IF_FAILED_RETURN(pRenderClient->GetBuffer(bufferFrameCount, &data));
+
+ sw.FillFrames(data, bufferFrameCount);
+ IF_FAILED_RETURN(pRenderClient->ReleaseBuffer(bufferFrameCount, 0));
+
+ IF_FAILED_RETURN(pAudioClient->Start());
+
+ unsigned c = 0;
+ while (c++ < duration)
+ {
+ if (WaitForSingleObject(bufferCompleteEvent.get(), ONE_SEC_IN_MS) != WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ unsigned numFramesPadding{};
+ IF_FAILED_RETURN(pAudioClient->GetCurrentPadding(&numFramesPadding));
+ auto numFramesAvailable = bufferFrameCount - numFramesPadding;
+
+ IF_FAILED_RETURN(pRenderClient->GetBuffer(numFramesAvailable, &data));
+ sw.FillFrames(data, numFramesAvailable);
+ IF_FAILED_RETURN(pRenderClient->ReleaseBuffer(numFramesAvailable, 0));
+ }
+
+ // Get a sample of the current AudioDG memory usage
+ GetAudioDGMemoryUsageSample();
+
+ IF_FAILED_RETURN(pAudioClient->Stop());
+
+ return S_OK;
+}
+
+HRESULT DisableEnableEndpoint(IMMDevice* pEndpoint)
+{
+ com_ptr_nothrow<IMMEndpointManager> pManager = nullptr;
+
+ if (pEndpoint == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+
+ IF_FAILED_RETURN(CoCreateInstance(__uuidof(MMEndpointManager), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pManager)));
+
+ IF_FAILED_RETURN(pManager->SetEndpointState(pEndpoint, DEVICE_STATE_DISABLED));
+
+ Sleep(1000);
+
+ IF_FAILED_RETURN(pManager->SetEndpointState(pEndpoint, DEVICE_STATE_ACTIVE));
+
+ return S_OK;
+}
+
+HRESULT BasicAudioCapture(IMMDevice* pEndpoint)
+{
+ com_ptr_nothrow<IAudioClient> pAudioClient;
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> mixFormat;
+
+ if (pEndpoint == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+
+ IF_FAILED_RETURN(pEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioClient));
+
+ IF_FAILED_RETURN(pAudioClient->GetMixFormat(wil::out_param_ptr<WAVEFORMATEX **>(mixFormat)));
+
+ IF_FAILED_RETURN(pAudioClient->Initialize(
+ AUDCLNT_SHAREMODE_SHARED,
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
+ 0,
+ 0,
+ mixFormat.get(),
+ nullptr));
+
+ com_ptr_nothrow<IAudioCaptureClient> pCaptureClient;
+ IF_FAILED_RETURN(pAudioClient->GetService(_uuidof(IAudioCaptureClient), wil::out_param_ptr<void **>(pCaptureClient)));
+
+ wil::unique_event bufferCompleteEvent;
+ bufferCompleteEvent.create();
+ IF_FAILED_RETURN(pAudioClient->SetEventHandle(bufferCompleteEvent.get()));
+
+ UINT32 bufferFrameCount;
+ REFERENCE_TIME duration = RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION);
+ IF_FAILED_RETURN(pAudioClient->GetBufferSize(&bufferFrameCount));
+
+ BYTE* data{};
+ DWORD dwFlags = 0;
+ UINT32 numFramesInNextPacket = 0;
+
+ IF_FAILED_RETURN(pCaptureClient->GetNextPacketSize(&numFramesInNextPacket));
+ IF_FAILED_RETURN(pCaptureClient->GetBuffer(&data, &numFramesInNextPacket, &dwFlags, NULL, NULL));
+
+ IF_FAILED_RETURN(pAudioClient->Start());
+
+ unsigned c = 0;
+ while (c++ < duration)
+ {
+ if (WaitForSingleObject(bufferCompleteEvent.get(), ONE_SEC_IN_MS) != WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ IF_FAILED_RETURN(pCaptureClient->ReleaseBuffer(numFramesInNextPacket));
+
+ IF_FAILED_RETURN(pCaptureClient->GetBuffer(&data, &numFramesInNextPacket, &dwFlags, NULL, NULL));
+ }
+
+ // Get a sample of the current AudioDG memory usage
+ GetAudioDGMemoryUsageSample();
+
+ IF_FAILED_RETURN(pAudioClient->Stop());
+
+ return S_OK;
+}
+
+HRESULT BasicAudioLoopback(IMMDevice* pEndpoint)
+{
+ com_ptr_nothrow<IAudioClient> pAudioClient;
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> mixFormat;
+
+ if (pEndpoint == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+
+ IF_FAILED_RETURN(pEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioClient));
+
+ IF_FAILED_RETURN(pAudioClient->GetMixFormat(wil::out_param_ptr<WAVEFORMATEX **>(mixFormat)));
+
+ IF_FAILED_RETURN(pAudioClient->Initialize(
+ AUDCLNT_SHAREMODE_SHARED,
+ AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM,
+ 0,
+ 0,
+ mixFormat.get(),
+ nullptr));
+
+ com_ptr_nothrow<IAudioCaptureClient> pCaptureClient;
+ IF_FAILED_RETURN(pAudioClient->GetService(__uuidof(IAudioCaptureClient), wil::out_param_ptr<void **>(pCaptureClient)));
+
+ wil::unique_event bufferCompleteEvent;
+ bufferCompleteEvent.create();
+ IF_FAILED_RETURN(pAudioClient->SetEventHandle(bufferCompleteEvent.get()));
+
+ UINT32 bufferFrameCount;
+ IF_FAILED_RETURN(pAudioClient->GetBufferSize(&bufferFrameCount));
+
+ BYTE* data{};
+ DWORD dwFlags = 0;
+ UINT32 numFramesInNextPacket = 0;
+
+ IF_FAILED_RETURN(pCaptureClient->GetNextPacketSize(&numFramesInNextPacket));
+
+ IF_FAILED_RETURN(pAudioClient->Start());
+
+ while (numFramesInNextPacket != 0)
+ {
+ IF_FAILED_RETURN(pCaptureClient->GetBuffer(&data, &numFramesInNextPacket, &dwFlags, NULL, NULL));
+
+ IF_FAILED_RETURN(pCaptureClient->ReleaseBuffer(numFramesInNextPacket));
+
+ IF_FAILED_RETURN(pCaptureClient->GetNextPacketSize(&numFramesInNextPacket));
+ }
+
+ // Get a sample of the current AudioDG memory usage
+ GetAudioDGMemoryUsageSample();
+
+ IF_FAILED_RETURN(pAudioClient->Stop());
+
+ return S_OK;
+}
+
+HRESULT BasicSpatialAudio(IMMDevice* pEndpoint)
+{
+#ifdef LNM
+ EnableSpatialAudio();
+#endif
+
+ if (pEndpoint == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+
+ com_ptr_nothrow<ISpatialAudioClient> client;
+ IF_FAILED_RETURN(pEndpoint->Activate(__uuidof(ISpatialAudioClient), CLSCTX_ALL, nullptr, wil::out_param_ptr<void **>(client)));
+
+ com_ptr_nothrow<IAudioFormatEnumerator> enumer;
+ IF_FAILED_RETURN(client->GetSupportedAudioObjectFormatEnumerator(&enumer));
+
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> fmt;
+ IF_FAILED_RETURN(enumer->GetFormat(0, wil::out_param_ptr<WAVEFORMATEX **>(fmt)));
+
+ unique_event event;
+ event.create();
+ if (event == nullptr)
+ {
+ HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ unique_prop_variant pv;
+ auto params = reinterpret_cast<SpatialAudioObjectRenderStreamActivationParams *>(CoTaskMemAlloc(sizeof(SpatialAudioObjectRenderStreamActivationParams)));
+ *params = SpatialAudioObjectRenderStreamActivationParams{fmt.get(), (AudioObjectType)AudioObjectType_FrontLeft, 0, 0, AudioCategory_Other, event.get(), nullptr};
+ pv.vt = VT_BLOB;
+ pv.blob.cbSize = sizeof(*params);
+ pv.blob.pBlobData = reinterpret_cast<BYTE *>(params);
+
+ com_ptr_nothrow<ISpatialAudioObjectRenderStream> stream;
+ IF_FAILED_RETURN(client->ActivateSpatialAudioStream(&pv, __uuidof(ISpatialAudioObjectRenderStream), wil::out_param_ptr<void **>(stream)));
+
+ com_ptr_nothrow<ISpatialAudioObject> audioObjectFrontLeft;
+ IF_FAILED_RETURN(stream->ActivateSpatialAudioObject(AudioObjectType_FrontLeft, &audioObjectFrontLeft));
+
+ IF_FAILED_RETURN(stream->Start());
+
+ UINT totalFrameCount = fmt->nSamplesPerSec * 2;
+
+ bool isRendering = true;
+ while (isRendering)
+ {
+ // Wait for a signal from the audio-engine to start the next processing pass
+ if (WaitForSingleObject(event.get(), 100) != WAIT_OBJECT_0)
+ {
+ IF_FAILED_RETURN(stream->Reset());
+ }
+
+ UINT32 availableDynamicObjectCount = 0;
+ UINT32 frameCount = 0;
+
+ // Begin the process of sending object data and metadata
+ // Get the number of dynamic objects that can be used to send object-data
+ // Get the frame count that each buffer will be filled with
+ IF_FAILED_RETURN(stream->BeginUpdatingAudioObjects(&availableDynamicObjectCount, &frameCount));
+
+ BYTE* buffer = nullptr;
+ UINT32 bufferLength = 0;
+
+ if (audioObjectFrontLeft == nullptr)
+ {
+ IF_FAILED_RETURN(stream->ActivateSpatialAudioObject(AudioObjectType::AudioObjectType_FrontLeft, &audioObjectFrontLeft));
+ }
+
+ // Get the buffer to write audio data
+ IF_FAILED_RETURN(audioObjectFrontLeft->GetBuffer(&buffer, &bufferLength));
+
+ if (totalFrameCount >= frameCount)
+ {
+ // Write audio data to the buffer
+ WriteToAudioObjectBuffer(reinterpret_cast<float*>(buffer), frameCount, 200.0f, fmt->nSamplesPerSec);
+
+ totalFrameCount -= frameCount;
+ }
+ else
+ {
+ // Write audio data to the buffer
+ WriteToAudioObjectBuffer(reinterpret_cast<float*>(buffer), totalFrameCount, 750.0f, fmt->nSamplesPerSec);
+
+ // Get a sample of the current AudioDG memory usage
+ GetAudioDGMemoryUsageSample();
+
+ // Set end of stream for the last buffer
+ IF_FAILED_RETURN(audioObjectFrontLeft->SetEndOfStream(totalFrameCount));
+
+ audioObjectFrontLeft = nullptr; // Release the object
+
+ isRendering = false;
+ }
+
+ // Let the audio engine know that the object data are available for processing now
+ IF_FAILED_RETURN(stream->EndUpdatingAudioObjects());
+ }
+
+ return S_OK;
+}
+
+HRESULT BasicOffloadStreaming(IMMDevice* pEndpoint)
+{
+ com_ptr_nothrow<IAudioClient2> pAudioClient;
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> mixFormat;
+ BOOL bOffloadCapable = FALSE;
+
+ if (pEndpoint == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+
+ IF_FAILED_RETURN(pEndpoint->Activate(__uuidof(IAudioClient2), CLSCTX_ALL, NULL, (void**)&pAudioClient));
+ IF_FAILED_RETURN(pAudioClient->IsOffloadCapable(AudioCategory_Media, &bOffloadCapable));
+
+ AudioClientProperties clientProperties = { 0 };
+ clientProperties.cbSize = sizeof(AudioClientProperties);
+ clientProperties.bIsOffload = bOffloadCapable ? true : false;
+ clientProperties.eCategory = AudioCategory_Media;
+
+ if (bOffloadCapable)
+ {
+ clientProperties.bIsOffload = true;
+ }
+
+ IF_FAILED_RETURN(pAudioClient->SetClientProperties(&clientProperties));
+
+ IF_FAILED_RETURN(pAudioClient->GetMixFormat(wil::out_param_ptr<WAVEFORMATEX **>(mixFormat)));
+
+ IF_FAILED_RETURN(pAudioClient->Initialize(
+ AUDCLNT_SHAREMODE_SHARED,
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
+ 0,
+ 0,
+ mixFormat.get(),
+ nullptr));
+
+ com_ptr_nothrow<IAudioRenderClient> pRenderClient;
+ IF_FAILED_RETURN(pAudioClient->GetService(_uuidof(IAudioRenderClient), wil::out_param_ptr<void **>(pRenderClient)));
+
+ wil::unique_event bufferCompleteEvent;
+ bufferCompleteEvent.create();
+ IF_FAILED_RETURN(pAudioClient->SetEventHandle(bufferCompleteEvent.get()));
+
+ UINT32 bufferFrameCount;
+ REFERENCE_TIME duration = RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION);
+ IF_FAILED_RETURN(pAudioClient->GetBufferSize(&bufferFrameCount));
+
+ SineWave sw(mixFormat->nChannels, mixFormat->nSamplesPerSec);
+ for (unsigned i = 0; i < mixFormat->nChannels; ++i)
+ {
+ sw.SetChannel(i, 400, 0.25f);
+ }
+
+ BYTE* data{};
+ IF_FAILED_RETURN(pRenderClient->GetBuffer(bufferFrameCount, &data));
+
+ sw.FillFrames(data, bufferFrameCount);
+ IF_FAILED_RETURN(pRenderClient->ReleaseBuffer(bufferFrameCount, 0));
+
+ IF_FAILED_RETURN(pAudioClient->Start());
+
+ unsigned c = 0;
+ while (c++ < duration)
+ {
+ if (WaitForSingleObject(bufferCompleteEvent.get(), ONE_SEC_IN_MS) != WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ unsigned numFramesPadding{};
+ IF_FAILED_RETURN(pAudioClient->GetCurrentPadding(&numFramesPadding));
+ auto numFramesAvailable = bufferFrameCount - numFramesPadding;
+
+ IF_FAILED_RETURN(pRenderClient->GetBuffer(numFramesAvailable, &data));
+ sw.FillFrames(data, numFramesAvailable);
+ IF_FAILED_RETURN(pRenderClient->ReleaseBuffer(numFramesAvailable, 0));
+ }
+
+ // Get a sample of the current AudioDG memory usage
+ GetAudioDGMemoryUsageSample();
+
+ IF_FAILED_RETURN(pAudioClient->Stop());
+
+ return S_OK;
+}
+
+void RunStressTest(int timeInSeconds, std::vector<wil::com_ptr<IMMDevice>>& pEndpoints)
+{
+ const UINT num = static_cast<UINT>(pEndpoints.size()) * NUMBER_OF_TESTS;
+
+ std::vector<std::thread> threads(num);
+
+ for (UINT i = 0; i < pEndpoints.size(); i++)
+ {
+ threads[i * NUMBER_OF_TESTS + 0] = std::thread([=](){
+ BasicAudioStreaming(pEndpoints[i].get());
+ });
+
+ threads[i * NUMBER_OF_TESTS + 1] = std::thread([=](){
+ BasicAudioStreaming(pEndpoints[i].get());
+ });
+
+ threads[i * NUMBER_OF_TESTS + 2] = std::thread([=](){
+ BasicAudioStreaming(pEndpoints[i].get());
+ });
+
+ threads[i * NUMBER_OF_TESTS + 3] = std::thread([=](){
+ BasicAudioCapture(pEndpoints[i].get());
+ });
+
+ threads[i * NUMBER_OF_TESTS + 4] = std::thread([=](){
+ BasicAudioLoopback(pEndpoints[i].get());
+ });
+
+ threads[i * NUMBER_OF_TESTS + 5] = std::thread([=](){
+ DisableEnableEndpoint(pEndpoints[i].get());
+ });
+
+ threads[i * NUMBER_OF_TESTS + 6] = std::thread([=](){
+ BasicSpatialAudio(pEndpoints[i].get());
+ });
+
+ threads[i * NUMBER_OF_TESTS + 7] = std::thread([=](){
+ BasicOffloadStreaming(pEndpoints[i].get());
+ });
+ }
+
+ time_t timer;
+ time(&timer);
+
+ time_t endTime = timer + timeInSeconds;
+
+ while (timer < endTime)
+ {
+ for (UINT i = 0; i < num; i++)
+ {
+ if (threads[i].joinable())
+ {
+ UINT testNumber = i % NUMBER_OF_TESTS;
+ UINT k = i / NUMBER_OF_TESTS;
+
+ threads[i].join();
+
+ switch (testNumber)
+ {
+ case 0:
+ threads[i] = std::thread([=]() {
+ Sleep(RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION));
+ BasicAudioStreaming(pEndpoints[k].get());
+ });
+ break;
+ case 1:
+ threads[i] = std::thread([=]() {
+ Sleep(RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION));
+ BasicAudioStreaming(pEndpoints[k].get());
+ });
+ break;
+ case 2:
+ threads[i] = std::thread([=]() {
+ Sleep(RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION));
+ BasicAudioStreaming(pEndpoints[k].get());
+ });
+ break;
+ case 3:
+ threads[i] = std::thread([=]() {
+ Sleep(RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION));
+ BasicAudioCapture(pEndpoints[k].get());
+ });
+ break;
+ case 4:
+ threads[i] = std::thread([=]() {
+ Sleep(RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION));
+ BasicAudioLoopback(pEndpoints[k].get());
+ });
+ break;
+ case 5:
+ threads[i] = std::thread([=]() {
+ Sleep(RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION));
+ DisableEnableEndpoint(pEndpoints[k].get());
+ });
+ break;
+ case 6:
+ threads[i] = std::thread([=]() {
+ Sleep(RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION));
+ BasicSpatialAudio(pEndpoints[k].get());
+ });
+ break;
+ case 7:
+ threads[i] = std::thread([=]() {
+ Sleep(RandInRange(WAIT_TIME_MIN_FOR_THREAD_OPERATION, WAIT_TIME_MAX_FOR_THREAD_OPERATION));
+ BasicOffloadStreaming(pEndpoints[k].get());
+ });
+ break;
+ }
+ }
+ }
+
+ time(&timer);
+ }
+
+ for (UINT j = 0; j < num; j++)
+ {
+ if (threads[j].joinable())
+ {
+ threads[j].join();
+ }
+ }
+
+ std::lock_guard<std::mutex> autoLock(maxAudioDGPagefileUsage_mutex);
+
+ // If we were not able to open the AudioDG handle process we should have 0 as memory usage
+ VERIFY_IS_TRUE(maxAudioDGPagefileUsage > 0);
+
+ // Adding the maxPagefileUsageValue to the VERIFY as a string
+ wchar_t maxAudioDGPagefileUsageString[32];
+ swprintf_s(maxAudioDGPagefileUsageString, L"%u", maxAudioDGPagefileUsage);
+
+ VERIFY_IS_TRUE(maxAudioDGPagefileUsage < MAX_ALLOWED_PAGEFILE_USAGE_IN_BYTES, maxAudioDGPagefileUsageString);
+} \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/APOStressTest.h b/audio/tests/HLK/ThirdPartyApoTests/APOStressTest.h
new file mode 100644
index 00000000..07e74ea9
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/APOStressTest.h
@@ -0,0 +1,50 @@
+#include <stdafx.h>
+
+#define FIVE_MIN_IN_SEC 300
+
+class CAPOStressTest
+{
+ CAPOStressTest(){};
+ ~CAPOStressTest(){};
+
+ BEGIN_TEST_CLASS(CAPOStressTest)
+ START_APPVERIFIFER
+ TEST_CLASS_PROPERTY(L"Owner", L"auddev")
+ TEST_CLASS_PROPERTY(L"TestClassification", L"Feature")
+ TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"audiodg.exe")
+ TEST_CLASS_PROPERTY(L"ArtifactUnderTest", L"audioenginebaseapop.h")
+ TEST_CLASS_PROPERTY(L"RunAs", L"System")
+ TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA")
+ TEST_CLASS_PROPERTY(L"Ignore", L"false")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfileFile", L"Audio-Tests.wprp")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfile", L"MultimediaCategory.Verbose.File")
+ END_APPVERIFIER
+ END_TEST_CLASS()
+
+public:
+ TEST_METHOD_SETUP(setUpMethod);
+ TEST_METHOD_CLEANUP(tearDownMethod);
+
+protected:
+ BEGIN_TEST_METHOD(AudioAPOStressTest)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"5C35BC8F-FAE1-434B-B842-6B58353696AB")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"1EB5F7F0-CA62-411E-9006-53A23F488332")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO - Stress the APO - AudioAPOStressTest")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"30")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"60")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party APO Test: AudioAPOStressTest")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+};
+
+HRESULT BasicAudioStreaming(IMMDevice *pEndpoint);
+HRESULT DisableEnableEndpoint(IMMDevice *pEndpoint);
+HRESULT BasicAudioCapture(IMMDevice *pEndpoint);
+HRESULT BasicAudioLoopback(IMMDevice *pEndpoint);
+HRESULT BasicSpatialAudio(IMMDevice *pEndpoint);
+HRESULT BasicOffloadStreaming(IMMDevice *pEndpoint);
+void RunStressTest(int timeInSeconds, std::vector<wil::com_ptr<IMMDevice>>& pEndpoints);
+
+bool SetupSkipRTHeap();
+bool CleanupSkipRTHeap(); \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXHelper.cpp b/audio/tests/HLK/ThirdPartyApoTests/CAPXHelper.cpp
new file mode 100644
index 00000000..5600fb72
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXHelper.cpp
@@ -0,0 +1,247 @@
+#pragma once
+
+#include "CAPXHelper.h"
+
+bool IsCapXAPO(GUID clsid)
+{
+ bool bIsCapXAPO = false;
+
+ wil::com_ptr_nothrow<IAudioProcessingObject> apo;
+ if(SUCCEEDED(CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, __uuidof(IAudioProcessingObject), reinterpret_cast<void**>(&apo))))
+ {
+ wil::com_ptr_nothrow<IAudioSystemEffects3> capXAPO;
+ bIsCapXAPO = wil::try_com_query_to(apo, &capXAPO);
+ }
+
+ return bIsCapXAPO;
+}
+
+void GetClsidsFromVar(const PROPVARIANT& var, std::vector<GUID>& guids, _Outptr_ PWSTR* ppszFormattedClsids, std::vector<GUID>& capxGuids, _Outptr_ PWSTR* ppszCapXFormattedClsids)
+{
+ WCHAR formattedClsids[1024] = {};
+ PWSTR writeLocation = formattedClsids;
+ size_t remainingChars = ARRAYSIZE(formattedClsids);
+ const PWSTR formatSeparator = L"|%s";
+ bool bIsCapXAPO;
+ WCHAR capXformattedClsids[1024] = {};
+ PWSTR capXwriteLocation = capXformattedClsids;
+ size_t capXremainingChars = ARRAYSIZE(capXformattedClsids);
+
+ for (UINT i = 0; i < var.calpwstr.cElems; i++)
+ {
+ // populate vector
+ GUID clsid = {};
+ if (FAILED(CLSIDFromString(var.calpwstr.pElems[i], &clsid)))
+ {
+ return;
+ }
+
+ bIsCapXAPO = IsCapXAPO(clsid);
+ try
+ {
+ bIsCapXAPO ? capxGuids.push_back(clsid) : guids.push_back(clsid);
+ }
+ CATCH_LOG();
+
+ // build string to log
+ if (FAILED(bIsCapXAPO ? StringCchPrintfEx(capXwriteLocation, capXremainingChars, &capXwriteLocation, &capXremainingChars, 0, formatSeparator, var.calpwstr.pElems[i])
+ : StringCchPrintfEx(writeLocation, remainingChars, &writeLocation, &remainingChars, 0, formatSeparator, var.calpwstr.pElems[i])))
+ {
+ return;
+ }
+ }
+
+ wil::unique_cotaskmem_string spFormattedClsids = wil::make_cotaskmem_string_nothrow(formattedClsids+1);
+ if (spFormattedClsids)
+ {
+ *ppszFormattedClsids = spFormattedClsids.release();
+ }
+
+ wil::unique_cotaskmem_string spCapXFormattedClsids = wil::make_cotaskmem_string_nothrow(capXformattedClsids+1);
+ if (spCapXFormattedClsids)
+ {
+ *ppszCapXFormattedClsids = spCapXFormattedClsids.release();
+ }
+}
+
+
+bool EndpointContainsCapx(IMMDevice* pDevice)
+{
+ // some endpoints have an effects property store
+ // grab properties from that
+
+ GUID clsidSfx = {};
+ bool bIsCapXSfx = false;
+ GUID clsidMfx = {};
+ bool bIsCapXMfx = false;
+ GUID clsidEfx = {};
+ bool bIsCapXEfx = false;
+
+ // legacy drivers
+ GUID clsidLfx = {};
+ bool bIsCapXLfx = false;
+ GUID clsidGfx = {};
+ bool bIsCapXGfx = false;
+
+ // offload pin
+ GUID clsidOffloadSfx = {};
+ bool bIsCapXOffloadSfx = false;
+ GUID clsidOffloadMfx = {};
+ bool bIsCapXOffloadMfx = false;
+
+ // keyword spotter pin
+ GUID clsidKeywordSfx = {};
+ bool bIsCapXKeywordSfx = false;
+ GUID clsidKeywordMfx = {};
+ bool bIsCapXKeywordMfx = false;
+ GUID clsidKeywordEfx = {};
+ bool bIsCapXKeywordEfx = false;
+
+ // // composite effects
+ std::vector<GUID> clsidsCompositeSfx;
+ wil::unique_cotaskmem_string clsidsCompositeSfxStr;
+ std::vector<GUID> clsidsCompositeMfx;
+ wil::unique_cotaskmem_string clsidsCompositeMfxStr;
+ std::vector<GUID> clsidsCompositeEfx;
+ wil::unique_cotaskmem_string clsidsCompositeEfxStr;
+ // // CapX composite effects
+ std::vector<GUID> clsidsCapXCompositeSfx;
+ wil::unique_cotaskmem_string clsidsCapXCompositeSfxStr;
+ std::vector<GUID> clsidsCapXCompositeMfx;
+ wil::unique_cotaskmem_string clsidsCapXCompositeMfxStr;
+ std::vector<GUID> clsidsCapXCompositeEfx;
+ wil::unique_cotaskmem_string clsidsCapXCompositeEfxStr;
+
+ wil::com_ptr_nothrow<IMMEndpointInternal> spMMEndpointInternal;
+ if (wil::try_com_query_to(pDevice, &spMMEndpointInternal))
+ {
+ wil::com_ptr_nothrow<IPropertyStore> spFxProperties;
+ if ((S_OK == spMMEndpointInternal->TryOpenFXPropertyStore(STGM_READ, &spFxProperties)) && spFxProperties)
+ {
+ wil::unique_prop_variant varClsidSfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_StreamEffectClsid, &varClsidSfx)) && varClsidSfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidSfx.pwszVal, &clsidSfx);
+ bIsCapXSfx = IsCapXAPO(clsidSfx);
+ }
+
+ wil::unique_prop_variant varClsidMfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_ModeEffectClsid, &varClsidMfx)) && varClsidMfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidMfx.pwszVal, &clsidMfx);
+ bIsCapXMfx = IsCapXAPO(clsidMfx);
+ }
+
+ wil::unique_prop_variant varClsidEfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_EndpointEffectClsid, &varClsidEfx)) && varClsidEfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidEfx.pwszVal, &clsidEfx);
+ bIsCapXEfx = IsCapXAPO(clsidEfx);
+ }
+
+ // legacy drivers
+ wil::unique_prop_variant varClsidLfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_PreMixEffectClsid, &varClsidLfx)) && varClsidLfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidLfx.pwszVal, &clsidLfx);
+ bIsCapXLfx = IsCapXAPO(clsidLfx);
+ }
+
+ wil::unique_prop_variant varClsidGfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_PostMixEffectClsid, &varClsidGfx)) && varClsidGfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidGfx.pwszVal, &clsidGfx);
+ bIsCapXGfx = IsCapXAPO(clsidGfx);
+ }
+
+ // offload
+ wil::unique_prop_variant varClsidOffloadSfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_Offload_StreamEffectClsid, &varClsidOffloadSfx)) && varClsidOffloadSfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidOffloadSfx.pwszVal, &clsidOffloadSfx);
+ bIsCapXOffloadSfx = IsCapXAPO(clsidOffloadSfx);
+ }
+
+ wil::unique_prop_variant varClsidOffloadMfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_Offload_ModeEffectClsid, &varClsidOffloadMfx)) && varClsidOffloadMfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidOffloadMfx.pwszVal, &clsidOffloadMfx);
+ bIsCapXOffloadMfx = IsCapXAPO(clsidOffloadMfx);
+ }
+
+ // keyword
+ wil::unique_prop_variant varClsidKeywordSfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_KeywordDetector_StreamEffectClsid, &varClsidKeywordSfx)) && varClsidKeywordSfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidKeywordSfx.pwszVal, &clsidKeywordSfx);
+ bIsCapXKeywordSfx = IsCapXAPO(clsidKeywordSfx);
+ }
+
+ wil::unique_prop_variant varClsidKeywordMfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_KeywordDetector_ModeEffectClsid, &varClsidKeywordMfx)) && varClsidKeywordMfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidKeywordMfx.pwszVal, &clsidKeywordMfx);
+ bIsCapXKeywordMfx = IsCapXAPO(clsidKeywordMfx);
+ }
+
+ wil::unique_prop_variant varClsidKeywordEfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_FX_KeywordDetector_EndpointEffectClsid, &varClsidKeywordEfx)) && varClsidKeywordEfx.vt == VT_LPWSTR)
+ {
+ CLSIDFromString(varClsidKeywordEfx.pwszVal, &clsidKeywordEfx);
+ bIsCapXKeywordEfx = IsCapXAPO(clsidKeywordEfx);
+ }
+
+ // composite effects
+ wil::unique_prop_variant varClsidsCompositeSfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_CompositeFX_StreamEffectClsid, &varClsidsCompositeSfx))
+ && varClsidsCompositeSfx.vt == (VT_VECTOR | VT_LPWSTR)
+ && varClsidsCompositeSfx.calpwstr.cElems > 0)
+ {
+ GetClsidsFromVar(varClsidsCompositeSfx, clsidsCompositeSfx, &clsidsCompositeSfxStr, clsidsCapXCompositeSfx, &clsidsCapXCompositeSfxStr);
+ }
+
+ wil::unique_prop_variant varClsidsCompositeMfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_CompositeFX_ModeEffectClsid, &varClsidsCompositeMfx))
+ && varClsidsCompositeMfx.vt == (VT_VECTOR | VT_LPWSTR)
+ && varClsidsCompositeMfx.calpwstr.cElems > 0)
+ {
+ GetClsidsFromVar(varClsidsCompositeMfx, clsidsCompositeMfx, &clsidsCompositeMfxStr, clsidsCapXCompositeMfx, &clsidsCapXCompositeMfxStr);
+ }
+
+ wil::unique_prop_variant varClsidsCompositeEfx;
+ if (SUCCEEDED(spFxProperties->GetValue(PKEY_CompositeFX_EndpointEffectClsid, &varClsidsCompositeEfx))
+ && varClsidsCompositeEfx.vt == (VT_VECTOR | VT_LPWSTR)
+ && varClsidsCompositeEfx.calpwstr.cElems > 0)
+ {
+ GetClsidsFromVar(varClsidsCompositeEfx, clsidsCompositeEfx, &clsidsCompositeEfxStr, clsidsCapXCompositeEfx, &clsidsCapXCompositeEfxStr);
+ }
+ }
+ }
+
+ return ( bIsCapXSfx || bIsCapXMfx || bIsCapXEfx || bIsCapXLfx || bIsCapXGfx ||
+ bIsCapXOffloadSfx || bIsCapXOffloadMfx || bIsCapXKeywordSfx || bIsCapXKeywordMfx || bIsCapXKeywordEfx ||
+ clsidsCapXCompositeSfx.size() > 0 || clsidsCapXCompositeMfx.size() > 0 || clsidsCapXCompositeMfx.size() > 0 );
+}
+
+void EndpointsWithCapx(std::vector<wil::com_ptr<IMMDevice>>& capxEndpoints)
+{
+ UINT cDevices = 0;
+ com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ com_ptr_nothrow<IMMDeviceCollection> spDevices;
+
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spEnumerator)));
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eAll, DEVICE_STATE_ACTIVE, &spDevices));
+ VERIFY_SUCCEEDED(spDevices->GetCount(&cDevices));
+
+ for (UINT i = 0; i < cDevices; i++)
+ {
+ wil::com_ptr<IMMDevice> pEndpoint = nullptr;
+
+ VERIFY_SUCCEEDED(spDevices->Item(i, &pEndpoint));
+
+ if (EndpointContainsCapx(pEndpoint.get()))
+ {
+ capxEndpoints.push_back(pEndpoint.get());
+ }
+ }
+} \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXHelper.h b/audio/tests/HLK/ThirdPartyApoTests/CAPXHelper.h
new file mode 100644
index 00000000..62a5807e
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXHelper.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <stdafx.h>
+
+#include <wil\resultmacros.h>
+#include <wil\com.h>
+#include <audioclient.h>
+#include <audioclientp.h>
+#include <audiosessiontypesp.h>
+#include <mmdeviceapi.h>
+#include <mmdeviceapip.h>
+#include <endpointvolume.h>
+#include "etwlistener.h"
+#include <functiondiscoverykeys.h>
+#include <slpublic.h>
+
+#include <DevPKey.h>
+#include <AudioEngineBaseAPOP.h>
+#include "TestMediaType.h"
+#include <audioenginebaseapo.h>
+
+#include <list>
+#include <memory>
+
+using namespace std;
+using namespace wil;
+using namespace WEX::Logging;
+using namespace WEX::Common;
+
+bool IsCapXAPO(GUID clsid);
+void GetClsidsFromVar(const PROPVARIANT& var, std::vector<GUID>& guids, _Outptr_ PWSTR* ppszFormattedClsids, std::vector<GUID>& capxGuids, _Outptr_ PWSTR* ppszCapXFormattedClsids);
+bool EndpointContainsCapx(IMMDevice* pDevice);
+void EndpointsWithCapx(std::vector<wil::com_ptr<IMMDevice>>& capxEndpoints); \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXLoggingFrameworkTests.cpp b/audio/tests/HLK/ThirdPartyApoTests/CAPXLoggingFrameworkTests.cpp
new file mode 100644
index 00000000..85f14ad8
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXLoggingFrameworkTests.cpp
@@ -0,0 +1,160 @@
+#include "CAPXLoggingFrameworkTests.h"
+
+
+bool CAPOLoggingFrameworkTest::setUpMethod()
+{
+ SLGetWindowsInformationDWORD(POLICY_KERNEL_ONECORE_DEVICE_FAMILY_ID, &m_dwPlatform);
+
+ EndpointsWithCapx(m_capxEndpoints);
+
+ return SetupSkipRTHeap();
+}
+
+bool CAPOLoggingFrameworkTest::tearDownMethod()
+{
+ return CleanupSkipRTHeap();
+}
+
+using malloca_deleter = wil::function_deleter<decltype(&::_freea), ::_freea>;
+template <typename T>
+using unique_malloca_ptr = wistd::unique_ptr<T, malloca_deleter>;
+enum QueryState
+{
+ None = 0,
+ ApoLog = 1,
+ PumpYield = 2
+};
+
+class CEventParser : public IEtwEventHandler
+{
+public:
+ int GetCountOfAPOLogging() { return m_countOfAudioProcessingNotifications; }
+ bool WasRTTThreadDetected() { return m_bRTThreadDetected; }
+ DWORD GetCountOfCreateFiles() { return m_dCreateFilesCalled; }
+
+private:
+
+ int m_countOfAudioProcessingNotifications = 0;
+ DWORD m_dLastPid = 0;
+ DWORD m_dLastThreadId = 0;
+ DWORD m_dCreateFilesCalled = 0;
+ bool m_bRTThreadDetected = false;
+ QueryState m_currentState = QueryState::None;
+
+ std::wstring QueryProcessName(const DWORD pid)
+ {
+ wil::unique_handle hProcess(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
+ if(NULL == hProcess)
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+ else
+ {
+ WCHAR szProcessName[1024];
+ DWORD cchSize = ARRAYSIZE(szProcessName);
+ if (0 == QueryFullProcessImageNameW(hProcess.get(), 0, szProcessName, &cchSize))
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+
+ PWSTR fileName = szProcessName + cchSize;
+ while (fileName != szProcessName && *fileName != L'\\') fileName--;
+
+ return (*fileName == L'\\') ? fileName + 1 : fileName;
+ }
+ }
+
+ std::wstring QueryTraceLevel(PEVENT_RECORD pEventRecord)
+ {
+ UNREFERENCED_PARAMETER(pEventRecord);
+ return L"err";
+ }
+
+ void OnEvent(PEVENT_RECORD pEventRecord)
+ {
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+ [&]() {
+ ULONG bufferSize = 0;
+ if (TdhGetEventInformation(pEventRecord, 0, nullptr, nullptr, &bufferSize) == ERROR_INSUFFICIENT_BUFFER)
+ {
+ unique_malloca_ptr<TRACE_EVENT_INFO> eventInfo(reinterpret_cast<PTRACE_EVENT_INFO>(_malloca(bufferSize)));
+ VERIFY_IS_NOT_NULL(eventInfo);
+ RETURN_IF_WIN32_ERROR(TdhGetEventInformation(pEventRecord, 0, nullptr, eventInfo.get(), &bufferSize));
+
+ // ApoLog is the name that the Logging framework uses for each of the logs sent through by the APO
+ if (_wcsicmp(L"ApoLog", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE *>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ m_countOfAudioProcessingNotifications++;
+ if (m_currentState == QueryState::PumpYield)
+ {
+ if (m_dLastPid == pEventRecord->EventHeader.ProcessId &&
+ m_dLastThreadId == pEventRecord->EventHeader.ThreadId)
+ {
+ m_bRTThreadDetected = true;
+ }
+ }
+
+ m_dLastPid = pEventRecord->EventHeader.ProcessId;
+ m_dLastThreadId = pEventRecord->EventHeader.ThreadId;
+ m_currentState = QueryState::ApoLog;
+ }
+ if (_wcsicmp(L"PumpYield", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE *>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ if (m_currentState == QueryState::ApoLog)
+ {
+ if (m_dLastPid == pEventRecord->EventHeader.ProcessId &&
+ m_dLastThreadId == pEventRecord->EventHeader.ThreadId)
+ {
+ m_bRTThreadDetected = true;
+ }
+ }
+
+ m_dLastPid = pEventRecord->EventHeader.ProcessId;
+ m_dLastThreadId = pEventRecord->EventHeader.ThreadId;
+ m_currentState = QueryState::PumpYield;
+ }
+ if (_wcsicmp(L"CreateFile", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE*>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ m_dCreateFilesCalled++;
+ }
+ }
+ return S_OK;
+ }();
+ }
+};
+
+// {6e7b1892-5288-5fe5-8f34-e3b0dc671fd2}
+static const GUID AudioSesTelemetryProvider =
+{ 0x6e7b1892, 0x5288, 0x5fe5, 0x8f, 0x34, 0xe3, 0xb0, 0xdc, 0x67, 0x1f, 0xd2 };
+
+void CAPOLoggingFrameworkTest::LoggingFrameworkTest()
+{
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+
+ if (m_dwPlatform != DEVICEFAMILYINFOENUM_WINDOWS_CORE && m_capxEndpoints.size() == 0)
+ {
+ Log::Result(TestResults::Skipped, L"There are no CAPX APOs to test");
+ return;
+ }
+
+ CEtwListener listener;
+ CEventParser parser;
+
+ VERIFY_SUCCEEDED(listener.StartTraceSession(L"LoggingFrameworkTest", static_cast<IEtwEventHandler *>(&parser)));
+
+ // Enable Microsoft.Windows.Audio.Client
+ VERIFY_SUCCEEDED(listener.EnableProvider(AudioSesTelemetryProvider, TRACE_LEVEL_INFORMATION, 8));
+
+ auto stopListener = scope_exit([&] {
+ listener.StopTraceSession();
+ });
+
+ wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spEnumerator)));
+
+ RunStressTest(FIVE_MIN_IN_SEC, m_capxEndpoints);
+
+ VERIFY_IS_FALSE(parser.WasRTTThreadDetected());
+ VERIFY_IS_TRUE(parser.GetCountOfCreateFiles() == 0);
+ LOG_OUTPUT(L"Number of APO logs collected: %d", parser.GetCountOfAPOLogging());
+} \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXLoggingFrameworkTests.h b/audio/tests/HLK/ThirdPartyApoTests/CAPXLoggingFrameworkTests.h
new file mode 100644
index 00000000..939b7554
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXLoggingFrameworkTests.h
@@ -0,0 +1,73 @@
+#include <stdafx.h>
+
+
+#include <wil\resultmacros.h>
+#include <wil\com.h>
+#include <audioclient.h>
+#include <audioclientp.h>
+#include <audiosessiontypesp.h>
+#include <mmdeviceapi.h>
+#include <endpointvolume.h>
+#include "etwlistener.h"
+#include <functiondiscoverykeys.h>
+#include <slpublic.h>
+
+#include <DevPKey.h>
+#include <AudioEngineBaseAPOP.h>
+#include "TestMediaType.h"
+#include <audioenginebaseapo.h>
+
+#include <WexTestClass.h>
+
+#include <list>
+#include <memory>
+
+#include "APOStressTest.h"
+#include "CAPXHelper.h"
+
+using namespace std;
+using namespace wil;
+using namespace WEX::Logging;
+using namespace WEX::Common;
+
+#define POLICY_KERNEL_ONECORE_DEVICE_FAMILY_ID L"Kernel-OneCore-DeviceFamilyID"
+
+class CAPOLoggingFrameworkTest
+{
+ CAPOLoggingFrameworkTest(){};
+ ~CAPOLoggingFrameworkTest(){};
+
+ BEGIN_TEST_CLASS(CAPOLoggingFrameworkTest)
+ START_APPVERIFIFER
+ TEST_CLASS_PROPERTY(L"Owner", L"auddev")
+ TEST_CLASS_PROPERTY(L"TestClassification", L"Feature")
+ TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"audiodg.exe")
+ TEST_CLASS_PROPERTY(L"ArtifactUnderTest", L"audioenginebaseapop.h")
+ TEST_CLASS_PROPERTY(L"RunAs", L"Elevated")
+ TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA")
+ TEST_CLASS_PROPERTY(L"Ignore", L"false")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfileFile", L"Audio-Tests.wprp")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfile", L"MultimediaCategory.Verbose.File")
+ END_APPVERIFIER
+ END_TEST_CLASS()
+
+public:
+ TEST_METHOD_SETUP(setUpMethod);
+ TEST_METHOD_CLEANUP(tearDownMethod);
+
+protected:
+ BEGIN_TEST_METHOD(LoggingFrameworkTest)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"4FF3BA81-2539-4762-9F7D-40547D1DAD95")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"FEEC362D-673C-40B3-BB01-5063C4B33EA4")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO CAPX - Test the Logging Framework - LoggingFrameworkTest")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"30")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"60")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party CAPX APO Test: LoggingFrameworkTest")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+
+private:
+ DWORD m_dwPlatform = DEVICEFAMILYINFOENUM_UAP;
+ std::vector<wil::com_ptr<IMMDevice>> m_capxEndpoints;
+};
diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.cpp b/audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.cpp
new file mode 100644
index 00000000..e55f9764
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.cpp
@@ -0,0 +1,168 @@
+#include "CAPXNotificationFrameworkTests.h"
+
+
+bool CAPONotificationFrameworkTest::setUpMethod()
+{
+ SLGetWindowsInformationDWORD(POLICY_KERNEL_ONECORE_DEVICE_FAMILY_ID, &m_dwPlatform);
+
+ EndpointsWithCapx(m_capxEndpoints);
+
+ return true;
+}
+
+bool CAPONotificationFrameworkTest::tearDownMethod()
+{
+ return true;
+}
+
+using malloca_deleter = wil::function_deleter<decltype(&::_freea), ::_freea>;
+template <typename T>
+using unique_malloca_ptr = wistd::unique_ptr<T, malloca_deleter>;
+
+class CEventParser : public IEtwEventHandler
+{
+public:
+ int GetCountOfAPONotifications() { return m_countOfAudioProcessingNotifications; }
+ bool WereOldNotificationsDetected() { return m_bOldNotificationsDetected; }
+
+private:
+
+ int m_countOfAudioProcessingNotifications = 0;
+ DWORD m_dLastPid = 0;
+ DWORD m_dLastThreadId = 0;
+ bool m_bOldNotificationsDetected = false;
+
+ std::wstring QueryProcessName(const DWORD pid)
+ {
+ wil::unique_handle hProcess(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
+ if(NULL == hProcess)
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+ else
+ {
+ WCHAR szProcessName[1024];
+ DWORD cchSize = ARRAYSIZE(szProcessName);
+ if (0 == QueryFullProcessImageNameW(hProcess.get(), 0, szProcessName, &cchSize))
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+
+ PWSTR fileName = szProcessName + cchSize;
+ while (fileName != szProcessName && *fileName != L'\\') fileName--;
+
+ return (*fileName == L'\\') ? fileName + 1 : fileName;
+ }
+ }
+
+ void OnEvent(PEVENT_RECORD pEventRecord)
+ {
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+ [&]() {
+ ULONG bufferSize = 0;
+ if (TdhGetEventInformation(pEventRecord, 0, nullptr, nullptr, &bufferSize) == ERROR_INSUFFICIENT_BUFFER)
+ {
+ unique_malloca_ptr<TRACE_EVENT_INFO> eventInfo(reinterpret_cast<PTRACE_EVENT_INFO>(_malloca(bufferSize)));
+ VERIFY_IS_NOT_NULL(eventInfo);
+ RETURN_IF_WIN32_ERROR(TdhGetEventInformation(pEventRecord, 0, nullptr, eventInfo.get(), &bufferSize));
+
+ // Check for old notification types
+ if (_wcsicmp(L"IAudioProcessingObjectNotifications implemented", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE *>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ m_countOfAudioProcessingNotifications++;
+ m_dLastPid = pEventRecord->EventHeader.ProcessId;
+ m_dLastThreadId = pEventRecord->EventHeader.ThreadId;
+ }
+ if (_wcsicmp(L"OnPropertyValueChanged", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE *>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ if (m_dLastPid == pEventRecord->EventHeader.ProcessId &&
+ m_dLastThreadId == pEventRecord->EventHeader.ThreadId)
+ {
+ m_bOldNotificationsDetected = true;
+ }
+ }
+ if (_wcsicmp(L"OnEndpointVolumeChanged", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE *>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ if (m_dLastPid == pEventRecord->EventHeader.ProcessId &&
+ m_dLastThreadId == pEventRecord->EventHeader.ThreadId)
+ {
+ m_bOldNotificationsDetected = true;
+ }
+ }
+ if (_wcsicmp(L"RegisterEndpointNotificationCallback", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE *>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0 ||
+ _wcsicmp(L"RegisterEndpointNotificationCallbackWithVirtualEnumerator", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE *>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ if (m_dLastPid == pEventRecord->EventHeader.ProcessId &&
+ m_dLastThreadId == pEventRecord->EventHeader.ThreadId)
+ {
+ m_bOldNotificationsDetected = true;
+ }
+ }
+ if (_wcsicmp(L"UnregisterEndpointNotificationCallback", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE *>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0 ||
+ _wcsicmp(L"UnregisterEndpointNotificationCallbackWithVirtualEnumerator", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE *>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ if (m_dLastPid == pEventRecord->EventHeader.ProcessId &&
+ m_dLastThreadId == pEventRecord->EventHeader.ThreadId)
+ {
+ m_bOldNotificationsDetected = true;
+ }
+ }
+ }
+ return S_OK;
+ }();
+ }
+};
+
+// {6e7b1892-5288-5fe5-8f34-e3b0dc671fd2}
+static const GUID AudioSesTelemetryProvider =
+{ 0x6e7b1892, 0x5288, 0x5fe5, 0x8f, 0x34, 0xe3, 0xb0, 0xdc, 0x67, 0x1f, 0xd2 };
+
+void CAPONotificationFrameworkTest::NotificationFrameworkTest()
+{
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+
+ if (m_dwPlatform != DEVICEFAMILYINFOENUM_WINDOWS_CORE && m_capxEndpoints.size() == 0)
+ {
+ Log::Result(TestResults::Skipped, L"There are no CAPX APOs to test");
+ return;
+ }
+
+ CEtwListener listener;
+ CEventParser parser;
+
+ VERIFY_SUCCEEDED(listener.StartTraceSession(L"NotificationFrameworkTest", static_cast<IEtwEventHandler *>(&parser)));
+
+ // Enable Microsoft.Windows.Audio.Client
+ VERIFY_SUCCEEDED(listener.EnableProvider(AudioSesTelemetryProvider, TRACE_LEVEL_INFORMATION, 8));
+
+ auto stopListener = scope_exit([&] {
+ listener.StopTraceSession();
+ });
+
+ {
+ for (UINT i = 0; i < m_capxEndpoints.size(); i++)
+ {
+ wil::com_ptr_nothrow<IMMDevice> pEndpoint = m_capxEndpoints.at(i);
+
+ wil::unique_prop_variant var;
+ wil::com_ptr_nothrow<IPropertyStore> spPropertyStore;
+ VERIFY_SUCCEEDED(pEndpoint->OpenPropertyStore(STGM_READ, &spPropertyStore));
+ VERIFY_SUCCEEDED(spPropertyStore->GetValue(PKEY_Device_FriendlyName, &var));
+ VERIFY_ARE_EQUAL(VT_LPWSTR, var.vt);
+ LOG_OUTPUT(L"Testing endpoint: %s", var.pwszVal);
+
+ wil::com_ptr_nothrow<IAudioEndpointVolume> aev;
+ VERIFY_SUCCEEDED(pEndpoint->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void **)&aev));
+
+ VERIFY_SUCCEEDED(aev->SetMasterVolumeLevelScalar((0.7f), NULL));
+
+ Sleep(500);
+
+ VERIFY_SUCCEEDED(aev->SetMasterVolumeLevelScalar((1.0f), NULL));
+
+ LOG_OUTPUT(L"Number of APO notifications detected: %d", parser.GetCountOfAPONotifications());
+ }
+
+ VERIFY_IS_FALSE(parser.WereOldNotificationsDetected());
+ }
+} \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.h b/audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.h
new file mode 100644
index 00000000..0b7244f2
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.h
@@ -0,0 +1,84 @@
+#include <stdafx.h>
+#include <memory>
+#include <wil\resultmacros.h>
+#include <wil\com.h>
+#include <audioclient.h>
+#include <audioclientp.h>
+#include <audiosessiontypesp.h>
+#include <mmdeviceapi.h>
+#include "etwlistener.h"
+#include <functiondiscoverykeys.h>
+
+#include <wil\resultmacros.h>
+#include <wil\com.h>
+#include <audioclient.h>
+#include <audioclientp.h>
+#include <audiosessiontypesp.h>
+#include <mmdeviceapi.h>
+#include <endpointvolume.h>
+#include "etwlistener.h"
+#include <functiondiscoverykeys.h>
+#include <slpublic.h>
+
+#include <DevPKey.h>
+#include <AudioEngineBaseAPOP.h>
+#include "TestMediaType.h"
+#include <audioenginebaseapo.h>
+
+#include <WexTestClass.h>
+
+#include <list>
+#include <memory>
+
+#include "CAPXHelper.h"
+
+#include <initguid.h>
+#include <guiddef.h>
+DEFINE_GUID(CLSID_TestCapxAPO, 0xA43110A0,0x4D2B,0x4C5B,0x99,0xB0,0xC6,0xF8,0x62,0x08,0x28,0xB1);
+
+using namespace std;
+using namespace wil;
+using namespace WEX::Logging;
+using namespace WEX::Common;
+
+#define POLICY_KERNEL_ONECORE_DEVICE_FAMILY_ID L"Kernel-OneCore-DeviceFamilyID"
+
+class CAPONotificationFrameworkTest
+{
+ CAPONotificationFrameworkTest(){};
+ ~CAPONotificationFrameworkTest(){};
+
+ BEGIN_TEST_CLASS(CAPONotificationFrameworkTest)
+ START_APPVERIFIFER
+ TEST_CLASS_PROPERTY(L"Owner", L"auddev")
+ TEST_CLASS_PROPERTY(L"TestClassification", L"Feature")
+ TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"audiodg.exe")
+ TEST_CLASS_PROPERTY(L"ArtifactUnderTest", L"audioenginebaseapop.h")
+ TEST_CLASS_PROPERTY(L"RunAs", L"Elevated")
+ TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA")
+ TEST_CLASS_PROPERTY(L"Ignore", L"false")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfileFile", L"Audio-Tests.wprp")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfile", L"MultimediaCategory.Verbose.File")
+ END_APPVERIFIER
+ END_TEST_CLASS()
+
+public:
+ TEST_METHOD_SETUP(setUpMethod);
+ TEST_METHOD_CLEANUP(tearDownMethod);
+
+protected:
+ BEGIN_TEST_METHOD(NotificationFrameworkTest)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"30F6BEE7-2AC9-4434-B1AD-642055B2E871")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"C53BE546-1188-4167-B426-E291524BCFD9")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO CAPX - Test the Notification Framework - NotificationFrameworkTest")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"30")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"60")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party CAPX APO Test: NotificationFrameworkTest")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+
+private:
+ DWORD m_dwPlatform = DEVICEFAMILYINFOENUM_UAP;
+ std::vector<wil::com_ptr<IMMDevice>> m_capxEndpoints;
+};
diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.cpp b/audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.cpp
new file mode 100644
index 00000000..e670518c
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.cpp
@@ -0,0 +1,118 @@
+#include "CAPXThreadingFrameworkTests.h"
+
+
+bool CAPOThreadingFrameworkTest::setUpMethod()
+{
+ SLGetWindowsInformationDWORD(POLICY_KERNEL_ONECORE_DEVICE_FAMILY_ID, &m_dwPlatform);
+
+ EndpointsWithCapx(m_capxEndpoints);
+
+ return SetupSkipRTHeap();
+}
+
+bool CAPOThreadingFrameworkTest::tearDownMethod()
+{
+ return CleanupSkipRTHeap();
+}
+
+using malloca_deleter = wil::function_deleter<decltype(&::_freea), ::_freea>;
+template <typename T>
+using unique_malloca_ptr = wistd::unique_ptr<T, malloca_deleter>;
+int g_mmThreadCount = 0;
+
+class CEventParser : public IEtwEventHandler
+{
+public:
+ int GetCountOfAPOThreads() { return m_countOfAPOThreads; }
+
+private:
+
+ int m_countOfAPOThreads = 0;
+
+ std::wstring QueryProcessName(const DWORD pid)
+ {
+ wil::unique_handle hProcess(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
+ if(NULL == hProcess)
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+ else
+ {
+ WCHAR szProcessName[1024];
+ DWORD cchSize = ARRAYSIZE(szProcessName);
+ if (0 == QueryFullProcessImageNameW(hProcess.get(), 0, szProcessName, &cchSize))
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+
+ PWSTR fileName = szProcessName + cchSize;
+ while (fileName != szProcessName && *fileName != L'\\') fileName--;
+
+ return (*fileName == L'\\') ? fileName + 1 : fileName;
+ }
+ }
+
+ void OnEvent(PEVENT_RECORD pEventRecord)
+ {
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+ [&]() {
+ ULONG bufferSize = 0;
+ if (TdhGetEventInformation(pEventRecord, 0, nullptr, nullptr, &bufferSize) == ERROR_INSUFFICIENT_BUFFER)
+ {
+ unique_malloca_ptr<TRACE_EVENT_INFO> eventInfo(reinterpret_cast<PTRACE_EVENT_INFO>(_malloca(bufferSize)));
+ VERIFY_IS_NOT_NULL(eventInfo);
+ RETURN_IF_WIN32_ERROR(TdhGetEventInformation(pEventRecord, 0, nullptr, eventInfo.get(), &bufferSize));
+
+ // Threading implemented is the temporary name that the threading framework will be called until the final logs are created
+ if (_wcsicmp(L"Threading implemented", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE*>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ m_countOfAPOThreads++;
+ }
+ auto mmThreadMock = Mock10::Mock::Function(&AvSetMmThreadCharacteristics, [](LPCWSTR taskName, LPDWORD taskIndex) {
+ g_mmThreadCount++;
+ return AvSetMmThreadCharacteristics(taskName, taskIndex);
+ });
+ if (_wcsicmp(L"CreateThread", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE*>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ g_mmThreadCount++;
+ }
+ }
+ return S_OK;
+ }();
+ }
+};
+
+// {6e7b1892-5288-5fe5-8f34-e3b0dc671fd2}
+static const GUID AudioSesTelemetryProvider =
+{ 0x6e7b1892, 0x5288, 0x5fe5, 0x8f, 0x34, 0xe3, 0xb0, 0xdc, 0x67, 0x1f, 0xd2 };
+
+void CAPOThreadingFrameworkTest::ThreadingFrameworkTest()
+{
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+
+ if (m_dwPlatform != DEVICEFAMILYINFOENUM_WINDOWS_CORE && m_capxEndpoints.size() == 0)
+ {
+ Log::Result(TestResults::Skipped, L"There are no CAPX APOs to test");
+ return;
+ }
+
+ CEtwListener listener;
+ CEventParser parser;
+
+ VERIFY_SUCCEEDED(listener.StartTraceSession(L"ThreadingFrameworkTest", static_cast<IEtwEventHandler *>(&parser)));
+
+ // Enable Microsoft.Windows.Audio.Client
+ VERIFY_SUCCEEDED(listener.EnableProvider(AudioSesTelemetryProvider, TRACE_LEVEL_INFORMATION, 8));
+
+ auto stopListener = scope_exit([&] {
+ listener.StopTraceSession();
+ });
+
+ wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spEnumerator)));
+
+ RunStressTest(FIVE_MIN_IN_SEC, m_capxEndpoints);
+
+ LOG_OUTPUT(L"Number of APO threads created: %d", parser.GetCountOfAPOThreads());
+ VERIFY_IS_TRUE(g_mmThreadCount == 0);
+} \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.h b/audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.h
new file mode 100644
index 00000000..47b63766
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.h
@@ -0,0 +1,73 @@
+#include <stdafx.h>
+
+#include <avrt.h>
+#include <wil\resultmacros.h>
+#include <wil\com.h>
+#include <audioclient.h>
+#include <audioclientp.h>
+#include <audiosessiontypesp.h>
+#include <mmdeviceapi.h>
+#include <endpointvolume.h>
+#include "etwlistener.h"
+#include <functiondiscoverykeys.h>
+#include <slpublic.h>
+
+#include <DevPKey.h>
+#include <AudioEngineBaseAPOP.h>
+#include "TestMediaType.h"
+#include <audioenginebaseapo.h>
+
+#include <WexTestClass.h>
+
+#include <list>
+#include <memory>
+
+#include "APOStressTest.h"
+#include "CAPXHelper.h"
+
+using namespace std;
+using namespace wil;
+using namespace WEX::Logging;
+using namespace WEX::Common;
+
+#define POLICY_KERNEL_ONECORE_DEVICE_FAMILY_ID L"Kernel-OneCore-DeviceFamilyID"
+
+class CAPOThreadingFrameworkTest
+{
+ CAPOThreadingFrameworkTest(){};
+ ~CAPOThreadingFrameworkTest(){};
+
+ BEGIN_TEST_CLASS(CAPOThreadingFrameworkTest)
+ START_APPVERIFIFER
+ TEST_CLASS_PROPERTY(L"Owner", L"auddev")
+ TEST_CLASS_PROPERTY(L"TestClassification", L"Feature")
+ TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"audiodg.exe")
+ TEST_CLASS_PROPERTY(L"ArtifactUnderTest", L"audioenginebaseapop.h")
+ TEST_CLASS_PROPERTY(L"RunAs", L"Elevated")
+ TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA")
+ TEST_CLASS_PROPERTY(L"Ignore", L"false")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfileFile", L"Audio-Tests.wprp")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfile", L"MultimediaCategory.Verbose.File")
+ END_APPVERIFIER
+ END_TEST_CLASS()
+
+public:
+ TEST_METHOD_SETUP(setUpMethod);
+ TEST_METHOD_CLEANUP(tearDownMethod);
+
+protected:
+ BEGIN_TEST_METHOD(ThreadingFrameworkTest)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"392B0972-F7EA-42A7-BA24-6304C3C621DB")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"5A5448AF-9D13-44B2-BC0C-D352A9B37268")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO CAPX - Test the Threading Framework - ThreadingFrameworkTest")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"30")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"60")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party CAPX APO Test: ThreadingFrameworkTest")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+
+private:
+ DWORD m_dwPlatform = DEVICEFAMILYINFOENUM_UAP;
+ std::vector<wil::com_ptr<IMMDevice>> m_capxEndpoints;
+};
diff --git a/audio/tests/HLK/ThirdPartyApoTests/FormatHelpers.h b/audio/tests/HLK/ThirdPartyApoTests/FormatHelpers.h
new file mode 100644
index 00000000..781b9ffb
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/FormatHelpers.h
@@ -0,0 +1,330 @@
+//
+// FormatHelpers.h -- Copyright (c) Microsoft Corporation
+//
+// Author: zaneh
+//
+// Description:
+//
+// Format Helpers for test formats used in APODeviceTest
+//
+#pragma once
+
+#include <MMDeviceAPIp.h>
+#include <DeviceTopologyp.h>
+#include <mfapi.h>
+#include <AVEndpointKeys.h>
+#include <AudioCoreAPO32Params.h>
+#include <ksmedia.h>
+
+// Consts used for APO format creation
+static const int cFrameRate = 48000; // 48kHz
+static const int cProcessingPeriod = 10; // milliseconds
+static const int cMaxFrameCount = cFrameRate * cProcessingPeriod / 1000;
+static const int cFrameCountToProcess = cMaxFrameCount / 2;
+
+// Returns true if the two IAudioMediaType formats are equal.
+// False otherwise
+bool IsEqualFormat
+(
+ IAudioMediaType *pIAMTFormat1,
+ IAudioMediaType *pIAMTFormat2
+)
+{
+ UINT cbBuffer1, cbBuffer2;
+ WAVEFORMATEX const *pwfx1, *pwfx2;
+ UNCOMPRESSEDAUDIOFORMAT format1, format2;
+
+ pwfx1 = pIAMTFormat1->GetAudioFormat();
+ pwfx2 = pIAMTFormat2->GetAudioFormat();
+ cbBuffer1 = ((WAVE_FORMAT_PCM == pwfx1->wFormatTag)?sizeof(PCMWAVEFORMAT):(sizeof(WAVEFORMATEX) + pwfx1->cbSize));
+ cbBuffer2 = ((WAVE_FORMAT_PCM == pwfx2->wFormatTag)?sizeof(PCMWAVEFORMAT):(sizeof(WAVEFORMATEX) + pwfx2->cbSize));
+
+ if (cbBuffer1 != cbBuffer2)
+ {
+ return false;
+ }
+
+ // Two formats don't compare as WAVEFORMATEX...
+ if (0 != memcmp(pwfx1, pwfx2, cbBuffer1))
+ {
+ return false;
+ }
+
+ SecureZeroMemory(&format1, sizeof(format1));
+ SecureZeroMemory(&format2, sizeof(format2));
+
+ if (S_OK != pIAMTFormat1->GetUncompressedAudioFormat(&format1))
+ {
+ return false;
+ }
+
+ if (S_OK != pIAMTFormat2->GetUncompressedAudioFormat(&format2))
+ {
+ return false;
+ }
+
+ // Two formats don't compare as WAVEFORMATEX...
+ if (0 != memcmp(&format1, &format2, sizeof(UNCOMPRESSEDAUDIOFORMAT)))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+// Creates a KSData format from the given WaveFormatEx
+// Returns a struct containing both the KSData format and WaveFormatEx
+KSDATAFORMAT_WAVEFORMATEX *CreateKSDataFromWFX
+(
+ WAVEFORMATEX *pwfx
+)
+{
+ if (nullptr == pwfx)
+ {
+ return nullptr;
+ }
+
+ UINT cbAlloc;
+ cbAlloc = sizeof(KSDATAFORMAT_WAVEFORMATEX) + pwfx->cbSize;
+
+ wil::unique_cotaskmem_ptr<KSDATAFORMAT_WAVEFORMATEX> pKsFormat((KSDATAFORMAT_WAVEFORMATEX*)(CoTaskMemAlloc(cbAlloc)));
+
+ if (nullptr == pKsFormat)
+ {
+ return nullptr;
+ }
+
+ pKsFormat->DataFormat.FormatSize = cbAlloc;
+ pKsFormat->DataFormat.Flags = 0;
+ pKsFormat->DataFormat.Reserved = 0;
+ pKsFormat->DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO;
+ pKsFormat->DataFormat.Specifier = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX;
+
+ CopyMemory(&(pKsFormat->WaveFormatEx), pwfx, sizeof(WAVEFORMATEX) + pwfx->cbSize);
+
+ if (WAVE_FORMAT_EXTENSIBLE == pwfx->wFormatTag)
+ {
+ pKsFormat->DataFormat.SubFormat = ((WAVEFORMATEXTENSIBLE*)(pwfx))->SubFormat;
+ }
+ else
+ {
+ INIT_WAVEFORMATEX_GUID(&(pKsFormat->DataFormat.SubFormat), pwfx->wFormatTag);
+ }
+
+ return (pKsFormat.release());
+}
+
+// Creates an IAudioMediaType given an uncompressed audio format
+HRESULT AECreateAudioMediaTypeFromUncompressedAudioFormat(const UNCOMPRESSEDAUDIOFORMAT* pUncompressedAudioFormat,IAudioMediaType** ppIAudioMediaType)
+{
+ RETURN_IF_FAILED(CTestAudioMediaType::Create(pUncompressedAudioFormat, ppIAudioMediaType));
+
+ return S_OK;
+}
+
+// Creates an IAudioMediaType given a WaveFormatEx
+HRESULT AECreateAudioMediaType(const WAVEFORMATEX* pWfx, IAudioMediaType** ppIAudioMediaType)
+{
+ UNCOMPRESSEDAUDIOFORMAT audioFormat;
+
+ audioFormat.dwBytesPerSampleContainer = pWfx->nBlockAlign / pWfx->nChannels;
+ audioFormat.dwSamplesPerFrame = pWfx->nChannels;
+ if (WAVE_FORMAT_EXTENSIBLE == pWfx->wFormatTag)
+ {
+ audioFormat.dwValidBitsPerSample = ((WAVEFORMATEXTENSIBLE*)pWfx)->Samples.wValidBitsPerSample;
+ audioFormat.fFramesPerSecond = (float)(((WAVEFORMATEXTENSIBLE*)pWfx)->Format.nSamplesPerSec);
+ audioFormat.guidFormatType = ((WAVEFORMATEXTENSIBLE*)pWfx)->SubFormat;
+ }
+ else
+ {
+ audioFormat.dwValidBitsPerSample = pWfx->wBitsPerSample;
+ audioFormat.fFramesPerSecond = (float)(pWfx->nSamplesPerSec);
+ INIT_WAVEFORMATEX_GUID(&(audioFormat.guidFormatType), pWfx->wFormatTag);
+ }
+
+ RETURN_IF_FAILED(CTestAudioMediaType::Create(&audioFormat, ppIAudioMediaType));
+ return S_OK;
+}
+
+// Fills an APO connection using the provided uncompressed format.
+// If pBuffer is NULL, the connection will be created with an APO_CONNECTION_BUFFER_TYPE_ALLOCATED.
+// If pBuffer is not null, APO_CONNECTION_BUFFER_TYPE_EXTERNAL will be used.
+// Outptr pConnection returns an APO_CONNECTION_DESCRIPTOR
+HRESULT FillConnection
+(
+ UNCOMPRESSEDAUDIOFORMAT* Format,
+ UINT_PTR pBuffer,
+ UINT32 /* u32ExtraFrameCount */,
+ UINT32 u32MaxFrameCount,
+ APO_CONNECTION_DESCRIPTOR* pConnection
+)
+{
+ if (Format == nullptr)
+ {
+ LOG_ERROR(L"FillConnection recieved null format");
+ return E_INVALIDARG;
+ }
+ if (pConnection == nullptr)
+ {
+ LOG_ERROR(L"FillConnection recieved null APO connection descriptor");
+ return E_INVALIDARG;
+ }
+
+ IAudioMediaType *pFormat;
+
+ // Attempt to create the AudioMediaType using the standard API
+ HRESULT createFormatHr = CreateAudioMediaTypeFromUncompressedAudioFormat(
+ Format,
+ &pFormat);
+
+ if (FAILED(createFormatHr))
+ {
+ LOG_OUTPUT(L"Standard AudioMediaType creation failed. Attempting creation using TestMediaType");
+ RETURN_IF_FAILED(AECreateAudioMediaTypeFromUncompressedAudioFormat(Format, &pFormat));
+ }
+
+ // set connection type based on pBuffer
+ // this will get re(set) when the actual allocation occurs
+ if (NULL != pBuffer)
+ {
+ pConnection->Type = APO_CONNECTION_BUFFER_TYPE_EXTERNAL;
+ }
+ else
+ {
+ pConnection->Type = APO_CONNECTION_BUFFER_TYPE_ALLOCATED;
+ }
+
+ pConnection->pFormat = pFormat;
+ pConnection->pBuffer = pBuffer;
+ pConnection->u32MaxFrameCount = u32MaxFrameCount;
+ pConnection->u32Signature = APO_CONNECTION_DESCRIPTOR_SIGNATURE;
+
+ return createFormatHr;
+}
+
+// Creates a WaveFormatEx format
+void FillFormat
+(
+ WORD FormatType,
+ DWORD dwSamplesPerFrame,
+ DWORD /* dwBytesPerSampleContainer */,
+ DWORD dwValidBitsPerSample,
+ FLOAT32 fFramesPerSecond,
+ WAVEFORMATEX* pWfx
+)
+{
+ if (pWfx == nullptr)
+ {
+ LOG_ERROR(L"WaveformatEx is null");
+ }
+
+ if (dwSamplesPerFrame >= 0xffff)
+ {
+ LOG_ERROR(L"SamplesPerFrame too great");
+ }
+
+ if (dwValidBitsPerSample >= 0xffff)
+ {
+ LOG_ERROR(L"ValidBitsPerSample too great");
+ }
+
+ pWfx->wFormatTag = FormatType;
+ pWfx->nChannels = (WORD)(dwSamplesPerFrame & 0xffff);
+ pWfx->wBitsPerSample = (WORD)dwValidBitsPerSample;
+ pWfx->nBlockAlign = pWfx->nChannels * (pWfx->wBitsPerSample / 8);
+ pWfx->nSamplesPerSec = (DWORD)fFramesPerSecond;
+ pWfx->nAvgBytesPerSec = pWfx->nSamplesPerSec * pWfx->nBlockAlign;
+ pWfx->cbSize = 0;
+}
+
+// Creates an UncompressedAudioFormat
+void FillFormat
+(
+ GUID FormatType,
+ DWORD dwSamplesPerFrame,
+ DWORD dwBytesPerSampleContainer,
+ DWORD dwValidBitsPerSample,
+ FLOAT32 fFramesPerSecond,
+ DWORD dwChannelMask,
+ UNCOMPRESSEDAUDIOFORMAT* pFormat
+)
+{
+ if (pFormat == nullptr)
+ {
+ LOG_ERROR(L"WaveformatEx is null");
+ }
+
+ if (dwSamplesPerFrame >= 0xffff)
+ {
+ LOG_ERROR(L"SamplesPerFrame too great");
+ }
+
+ if (dwValidBitsPerSample >= 0xffff)
+ {
+ LOG_ERROR(L"ValidBitsPerSample too great");
+ }
+
+ pFormat->guidFormatType = FormatType;
+ pFormat->dwSamplesPerFrame = dwSamplesPerFrame;
+ pFormat->dwBytesPerSampleContainer = dwBytesPerSampleContainer;
+ pFormat->dwValidBitsPerSample = dwValidBitsPerSample;
+ pFormat->fFramesPerSecond = fFramesPerSecond;
+ pFormat->dwChannelMask = dwChannelMask;
+}
+
+HRESULT SetupConnection
+(
+ APO_CONNECTION_DESCRIPTOR* pInputConnDesc,
+ APO_CONNECTION_DESCRIPTOR* pOutputConnDesc,
+ APO_CONNECTION_PROPERTY* pInputConnProp,
+ APO_CONNECTION_PROPERTY* pOutputConnProp,
+ UNCOMPRESSEDAUDIOFORMAT* pDefaultFormat
+)
+{
+ if (pInputConnDesc == nullptr)
+ {
+ LOG_ERROR(L"SetupConnection recieved null APO connection descriptor for input");
+ return E_INVALIDARG;
+ }
+
+ if (pOutputConnDesc == nullptr)
+ {
+ LOG_ERROR(L"SetupConnection recieved null APO connection descriptor for output");
+ return E_INVALIDARG;
+ }
+
+ if (pInputConnProp == nullptr)
+ {
+ LOG_ERROR(L"SetupConnection recieved null APO connection property for input");
+ return E_INVALIDARG;
+ }
+
+ if (pOutputConnProp == nullptr)
+ {
+ LOG_ERROR(L"SetupConnection recieved null APO connection property for output");
+ return E_INVALIDARG;
+ }
+
+ if (pDefaultFormat == nullptr)
+ {
+ LOG_ERROR(L"SetupConnection received null format");
+ return E_INVALIDARG;
+ }
+
+ LOG_RETURN_IF_FAILED(FillConnection(pDefaultFormat, NULL, 0, cMaxFrameCount, pInputConnDesc),
+ L"\tFillConnection for input descriptor failed");
+
+ LOG_RETURN_IF_FAILED(CreateConnection(pInputConnDesc, pInputConnProp),
+ L"\tCreateConnection for input descriptor failed");
+
+ pInputConnProp->u32ValidFrameCount = cFrameCountToProcess;
+ pInputConnProp->u32BufferFlags = BUFFER_VALID;
+
+ LOG_RETURN_IF_FAILED(FillConnection(pDefaultFormat, NULL, 0, cMaxFrameCount, pOutputConnDesc),
+ L"\tFillConnection for output descriptor failed");
+
+ LOG_RETURN_IF_FAILED(CreateConnection(pOutputConnDesc, pOutputConnProp),
+ L"\tCreateConnection for output descriptor failed");
+
+ return S_OK;
+} \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/TestEffectsList.cpp b/audio/tests/HLK/ThirdPartyApoTests/TestEffectsList.cpp
new file mode 100644
index 00000000..7b670d28
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/TestEffectsList.cpp
@@ -0,0 +1,225 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+
+#include <stdafx.h>
+#include <memory>
+#include <wil\resultmacros.h>
+#include <wil\com.h>
+#include <wil\winrt.h>
+#include <wil\wrl.h>
+#include <audioclient.h>
+#include <mmdeviceapi.h>
+#include <functiondiscoverykeys.h>
+#include <windows.media.effects.h>
+#include "avdevqueryhelpers.h"
+#include <AudioStreamCategoryConversions.h>
+
+using namespace Windows::Media::Effects;
+
+class CTestEffectsList
+{
+ BEGIN_TEST_CLASS(CTestEffectsList)
+ START_APPVERIFIFER
+ TEST_CLASS_PROPERTY(L"Owner", L"auddev")
+ TEST_CLASS_PROPERTY(L"TestClassification", L"Feature")
+ TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"audiodg.exe")
+ TEST_CLASS_PROPERTY(L"ArtifactUnderTest", L"audioengineextensionapo.h")
+ TEST_CLASS_PROPERTY(L"RunAs", L"Elevated")
+ TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA")
+ TEST_CLASS_PROPERTY(L"Ignore", L"false")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfileFile", L"Audio-Tests.wprp")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfile", L"MultimediaCategory.Verbose.File")
+ END_APPVERIFIER
+ END_TEST_CLASS()
+
+protected:
+ BEGIN_TEST_METHOD(TestGetEffectsList)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"7E8037CA-8670-4170-8EC8-C7B02B22A7B4")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"FD1ED3C6-54E0-432B-B3C9-5EAC5DE2D3E1")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO - Test IAudioSystemEffects2 and IAudioSystemEffects3 usage from APOs - TestEffectsList")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"30")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"60")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party APO Test: TestEffectsList")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+};
+
+GUID AudioEffectTypeToGUID(Windows::Media::Effects::AudioEffectType type)
+{
+ switch(type)
+ {
+ case AudioEffectType_AcousticEchoCancellation:
+ return AUDIO_EFFECT_TYPE_ACOUSTIC_ECHO_CANCELLATION;
+ case AudioEffectType_NoiseSuppression:
+ return AUDIO_EFFECT_TYPE_NOISE_SUPPRESSION;
+ case AudioEffectType_AutomaticGainControl:
+ return AUDIO_EFFECT_TYPE_AUTOMATIC_GAIN_CONTROL;
+ case AudioEffectType_BeamForming:
+ return AUDIO_EFFECT_TYPE_BEAMFORMING;
+ case AudioEffectType_ConstantToneRemoval:
+ return AUDIO_EFFECT_TYPE_CONSTANT_TONE_REMOVAL;
+ case AudioEffectType_Equalizer:
+ return AUDIO_EFFECT_TYPE_EQUALIZER;
+ case AudioEffectType_LoudnessEqualizer:
+ return AUDIO_EFFECT_TYPE_LOUDNESS_EQUALIZER;
+ case AudioEffectType_BassBoost:
+ return AUDIO_EFFECT_TYPE_BASS_BOOST;
+ case AudioEffectType_VirtualSurround:
+ return AUDIO_EFFECT_TYPE_VIRTUAL_SURROUND;
+ case AudioEffectType_VirtualHeadphones:
+ return AUDIO_EFFECT_TYPE_VIRTUAL_HEADPHONES;
+ case AudioEffectType_SpeakerFill:
+ return AUDIO_EFFECT_TYPE_SPEAKER_FILL;
+ case AudioEffectType_RoomCorrection:
+ return AUDIO_EFFECT_TYPE_ROOM_CORRECTION;
+ case AudioEffectType_BassManagement:
+ return AUDIO_EFFECT_TYPE_BASS_MANAGEMENT;
+ case AudioEffectType_EnvironmentalEffects:
+ return AUDIO_EFFECT_TYPE_ENVIRONMENTAL_EFFECTS;
+ case AudioEffectType_SpeakerProtection:
+ return AUDIO_EFFECT_TYPE_SPEAKER_PROTECTION;
+ case AudioEffectType_SpeakerCompensation:
+ return AUDIO_EFFECT_TYPE_SPEAKER_COMPENSATION;
+ case AudioEffectType_DynamicRangeCompression:
+ return AUDIO_EFFECT_TYPE_DYNAMIC_RANGE_COMPRESSION;
+ case AudioEffectType_FarFieldBeamForming:
+ return AUDIO_EFFECT_TYPE_FAR_FIELD_BEAMFORMING;
+ case AudioEffectType_DeepNoiseSuppression:
+ return AUDIO_EFFECT_TYPE_DEEP_NOISE_SUPPRESSION;
+ }
+ return GUID_NULL;
+}
+
+void CTestEffectsList::TestGetEffectsList()
+{
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+
+ wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spEnumerator)));
+
+ EDataFlow dataFlows[] = { eRender, eCapture };
+
+ for (EDataFlow dataFlow : dataFlows)
+ {
+ wil::com_ptr_nothrow<IMMDeviceCollection> spDevices;
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(dataFlow, DEVICE_STATE_ACTIVE, &spDevices));
+
+ UINT cDevices = 0;
+ VERIFY_SUCCEEDED(spDevices->GetCount(&cDevices));
+
+ for (UINT i = 0; i < cDevices; i++)
+ {
+ wil::com_ptr_nothrow<IMMDevice> spEndpoint;
+ VERIFY_SUCCEEDED(spDevices->Item(i, &spEndpoint));
+
+ wil::unique_prop_variant var;
+ wil::com_ptr_nothrow<IPropertyStore> spPropertyStore;
+ VERIFY_SUCCEEDED(spEndpoint->OpenPropertyStore(STGM_READ, &spPropertyStore));
+ VERIFY_SUCCEEDED(spPropertyStore->GetValue(PKEY_Device_FriendlyName, &var));
+ VERIFY_ARE_EQUAL(VT_LPWSTR, var.vt);
+ LOG_OUTPUT(L"Testing endpoint: %s", var.pwszVal);
+
+ wil::unique_cotaskmem_string spDeviceId;
+ VERIFY_SUCCEEDED(spEndpoint->GetId(&spDeviceId));
+ wil::unique_cotaskmem_string spEndpointPnpInstanceId;
+ VERIFY_SUCCEEDED(mmdDevGetInterfaceIdFromMMDeviceId(spDeviceId.get(), &spEndpointPnpInstanceId));
+ HSTRING deviceId;
+ VERIFY_SUCCEEDED(WindowsCreateString(spEndpointPnpInstanceId.get(), (UINT32)wcslen(spEndpointPnpInstanceId.get()), &deviceId));
+
+ // Get effects list using IAudioSystemEffects2
+ wil::com_ptr_nothrow<IAudioEffectsManagerStatics> audioEffectsManagerStatics =
+ wil::GetActivationFactory<IAudioEffectsManagerStatics>(RuntimeClass_Windows_Media_Effects_AudioEffectsManager);
+
+ wil::com_ptr_nothrow<Windows::Foundation::Collections::IVectorView<AudioEffect*>> effects;
+ AUDIO_STREAM_CATEGORY audioCategory;
+
+ if (dataFlow == eRender)
+ {
+ wil::com_ptr_nothrow<IAudioRenderEffectsManager> audioRenderEffectsManager;
+ VERIFY_SUCCEEDED(audioEffectsManagerStatics->CreateAudioRenderEffectsManager(deviceId, Windows::Media::Render::AudioRenderCategory::AudioRenderCategory_Communications, &audioRenderEffectsManager));
+ VERIFY_IS_NOT_NULL(audioRenderEffectsManager);
+ VERIFY_SUCCEEDED(audioRenderEffectsManager->GetAudioRenderEffects(&effects));
+
+ VERIFY_SUCCEEDED(MediaRenderCategory_To_AUDIO_STREAM_CATEGORY(Windows::Media::Render::AudioRenderCategory::AudioRenderCategory_Communications, &audioCategory));
+ }
+ else
+ {
+ wil::com_ptr_nothrow<IAudioCaptureEffectsManager> audioCaptureEffectsManager;
+ VERIFY_SUCCEEDED(audioEffectsManagerStatics->CreateAudioCaptureEffectsManager(deviceId, Windows::Media::Capture::MediaCategory::MediaCategory_Communications, &audioCaptureEffectsManager));
+ VERIFY_IS_NOT_NULL(audioCaptureEffectsManager);
+ VERIFY_SUCCEEDED(audioCaptureEffectsManager->GetAudioCaptureEffects(&effects));
+
+ VERIFY_SUCCEEDED(MediaCaptureCategory_To_AUDIO_STREAM_CATEGORY(Windows::Media::Capture::MediaCategory::MediaCategory_Communications, &audioCategory));
+ }
+
+ UINT32 numEffects = 0;
+ effects->get_Size(&numEffects);
+
+ // Get effects lists using IAudioSystemEffects3
+ wil::com_ptr_nothrow<IAudioClient2> spAudioClient;
+ VERIFY_SUCCEEDED(spEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast<void**>(&spAudioClient)));
+
+ AudioClientProperties clientProperties = {};
+ clientProperties.cbSize = sizeof(AudioClientProperties);
+ clientProperties.bIsOffload = FALSE;
+ clientProperties.eCategory = audioCategory;
+ clientProperties.Options = AUDCLNT_STREAMOPTIONS_NONE;
+ VERIFY_SUCCEEDED(spAudioClient->SetClientProperties(&clientProperties));
+
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> mixFormat;
+ VERIFY_SUCCEEDED(spAudioClient->GetMixFormat(wil::out_param_ptr<WAVEFORMATEX**>(mixFormat)));
+
+ VERIFY_SUCCEEDED(spAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 20 * 10000, 0, mixFormat.get(), nullptr));
+
+ wil::com_ptr_nothrow<IAudioEffectsManager> audioEffectsManager;
+ VERIFY_SUCCEEDED(spAudioClient->GetService(IID_PPV_ARGS(&audioEffectsManager)));
+ VERIFY_IS_NOT_NULL(audioEffectsManager);
+
+ wil::unique_cotaskmem_array_ptr<AUDIO_EFFECT> controllableEffects;
+ UINT32 numControllableEffects;
+ VERIFY_SUCCEEDED(audioEffectsManager->GetAudioEffects(&controllableEffects, &numControllableEffects));
+
+ // Verify 'effects' is a subset of 'controllableEffects'
+ VERIFY_IS_GREATER_THAN_OR_EQUAL(numControllableEffects, numEffects);
+ for (UINT32 i = 0; i < numEffects; i++)
+ {
+ bool found = false;
+ for (UINT32 j = 0; j < numControllableEffects && !found; j++)
+ {
+ AudioEffectType effectType;
+ wil::com_ptr_nothrow<IAudioEffect> spEffect;
+ effects->GetAt(i, &spEffect);
+ spEffect->get_AudioEffectType(&effectType);
+ if (IsEqualGUID(AudioEffectTypeToGUID(effectType), controllableEffects[j].id))
+ {
+ found = true;
+ }
+ }
+ VERIFY_IS_TRUE(found);
+ }
+
+ // Verify effects not present in 'effects' are disabled
+ for (UINT32 i = 0; i < numControllableEffects; i++)
+ {
+ bool found = false;
+ for (UINT32 j = 0; j < numEffects && !found; j++)
+ {
+ AudioEffectType effectType;
+ wil::com_ptr_nothrow<IAudioEffect> spEffect;
+ effects->GetAt(j, &spEffect);
+ spEffect->get_AudioEffectType(&effectType);
+ if (IsEqualGUID(controllableEffects[i].id, AudioEffectTypeToGUID(effectType)))
+ {
+ found = true;
+ }
+ }
+ if (!found)
+ {
+ VERIFY_ARE_EQUAL(controllableEffects[i].state, AUDIO_EFFECT_STATE_OFF);
+ }
+ }
+ }
+ }
+}
diff --git a/audio/tests/HLK/ThirdPartyApoTests/TestMediaType.cpp b/audio/tests/HLK/ThirdPartyApoTests/TestMediaType.cpp
new file mode 100644
index 00000000..1627b728
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/TestMediaType.cpp
@@ -0,0 +1,227 @@
+// AudioMediaType.cpp -- Copyright (c) 2003 Microsoft Corporation
+//
+//
+// Description:
+//
+// Implementation of the TestMediaType
+//
+
+#include "TestMediaType.h"
+#include <memory>
+#include <wil\resultmacros.h>
+
+//-----------------------------------------------------------------------------
+// Description:
+//
+// Creates an CTestAudioMediaType object with the given format.
+//
+// Parameters:
+//
+// pFormat - [in] Format for the new AudioMediaType.
+// ppIAudioMediaType - [out] upon success returns the pointer to the
+// new interface.
+//
+// Return values:
+//
+// S_OK Success
+// E_OUTOFMEMORY On memory failure.
+// E_INVALIDARG Null parameter
+//
+// Remarks:
+//
+HRESULT CTestAudioMediaType::Create
+(
+ const UNCOMPRESSEDAUDIOFORMAT* pFormat,
+ IAudioMediaType** ppIAudioMediaType
+)
+{
+ if (!pFormat || !ppIAudioMediaType)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hResult = S_OK;
+
+ *ppIAudioMediaType = NULL;
+
+ std::unique_ptr<CTestAudioMediaType> pObj(new(std::nothrow) CTestAudioMediaType());
+
+ pObj->m_Format = *pFormat;
+
+ // TODO: iter8 fix this to use WAVEFORMATEXTENSIBLE (commented lines)
+ // after KSCommon lib is fixed
+ if (KSDATAFORMAT_SUBTYPE_PCM == pFormat->guidFormatType)
+ {
+ pObj->m_Wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
+ }
+ else
+ {
+ pObj->m_Wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+ }
+ pObj->m_Wfx.Format.nChannels = (WORD)pObj->m_Format.dwSamplesPerFrame;
+ pObj->m_Wfx.Format.nSamplesPerSec = (DWORD)pObj->m_Format.fFramesPerSecond;
+ pObj->m_Wfx.Format.nAvgBytesPerSec = (DWORD)(pObj->m_Format.dwBytesPerSampleContainer *
+ pObj->m_Format.fFramesPerSecond * pObj->m_Format.dwSamplesPerFrame);
+ pObj->m_Wfx.Format.nBlockAlign = (WORD)(pObj->m_Format.dwBytesPerSampleContainer * pObj->m_Format.dwSamplesPerFrame);
+ pObj->m_Wfx.Format.wBitsPerSample = (WORD)pObj->m_Format.dwValidBitsPerSample;
+ pObj->m_Wfx.Format.cbSize = 0;
+
+ hResult = pObj->QueryInterface(
+ __uuidof(IAudioMediaType),
+ (LPVOID *) ppIAudioMediaType );
+
+ return( hResult );
+} // Create
+
+//-----------------------------------------------------------------------------
+// Description:
+//
+// Used to addref an interface
+//
+// Results:
+//
+// New reference count
+//
+ULONG STDMETHODCALLTYPE CTestAudioMediaType::AddRef()
+{
+ return (ULONG) InterlockedIncrement( (LONG *) &m_cRef );
+} // AddRef
+
+//-----------------------------------------------------------------------------
+// Description:
+//
+// Used to release an interface
+//
+// Results:
+//
+// New reference count
+//
+ULONG STDMETHODCALLTYPE CTestAudioMediaType::Release()
+{
+ ULONG ulNewRefCount = (ULONG) InterlockedDecrement( (LONG *) &m_cRef );
+
+ //
+ // If our new refcount is zero, then we need to delete ourselves
+ //
+ if( 0 == ulNewRefCount )
+ {
+ delete this;
+ }
+
+ return( ulNewRefCount );
+} // Release
+
+//-----------------------------------------------------------------------------
+// Description:
+//
+// Used to query an interface
+//
+// Parameters:
+//
+// riid - [in] id to look for
+// ppvObject - [out] pointer to object
+//
+// Return Values:
+//
+// S_OK On success.
+// E_POINTER Invalid destination pointer
+// E_NOINTERFACE No interface of that type
+// error Other
+//
+STDMETHODIMP CTestAudioMediaType::QueryInterface(
+ REFIID riid, LPVOID *ppvObject )
+{
+ if (!ppvObject)
+ {
+ return E_POINTER;
+ }
+
+ HRESULT hResult;
+
+ hResult = S_OK;
+ *ppvObject = NULL;
+
+ if( ( riid == __uuidof(IAudioMediaType) ) ||
+ ( riid == __uuidof(IUnknown) ) )
+ {
+ *ppvObject = ( IAudioMediaType *)this;
+ }
+ else
+ {
+ hResult = E_NOINTERFACE;
+ }
+
+ if ( SUCCEEDED( hResult ) )
+ {
+ ((LPUNKNOWN) *ppvObject)->AddRef( );
+ }
+
+ return( hResult );
+} // QueryInterface
+
+//-----------------------------------------------------------------------------
+// Description:
+//
+// Not implemented
+//
+// Parameters:
+//
+// pAudioMediaType - [in] media type
+// pdwFlags - [out] flags
+//
+// Return Values:
+//
+// E_NOTIMPL Not implemented.
+//
+HRESULT STDMETHODCALLTYPE CTestAudioMediaType::IsEqual(
+ IAudioMediaType * /* pAudioMediaType */,
+ DWORD * /* pdwFlags */
+)
+{
+ return E_NOTIMPL;
+} // IsEqual
+
+//-----------------------------------------------------------------------------
+// Description:
+//
+// Returns pointer to internal member variable m_Wfx.
+//
+// Results:
+//
+// Returns pointer to WaveFormatEx representing the format.
+//
+const WAVEFORMATEX *STDMETHODCALLTYPE CTestAudioMediaType::GetAudioFormat( void)
+{
+ return reinterpret_cast<WAVEFORMATEX*>(&m_Wfx);
+} // GetAudioFormat
+
+//-----------------------------------------------------------------------------
+// Description:
+//
+// Copies the uncompressed data to the parameter.
+//
+// Parameters:
+//
+// pUncompressedAudioFormat - [in] format pointer
+//
+// Return Values:
+//
+// S_OK On success
+// E_POINTER Invalid pointer
+//
+HRESULT STDMETHODCALLTYPE CTestAudioMediaType::GetUncompressedAudioFormat(
+ UNCOMPRESSEDAUDIOFORMAT *pUncompressedAudioFormat
+)
+{
+ RETURN_HR_IF(E_FAIL, (KSDATAFORMAT_SUBTYPE_PCM != m_Format.guidFormatType) &&
+ (KSDATAFORMAT_SUBTYPE_IEEE_FLOAT != m_Format.guidFormatType));
+
+ if (!pUncompressedAudioFormat)
+ {
+ return E_POINTER;
+ }
+
+ CopyMemory(pUncompressedAudioFormat, &m_Format, sizeof(UNCOMPRESSEDAUDIOFORMAT));
+
+ return S_OK;
+} // GetUncompressedAudioFormat
diff --git a/audio/tests/HLK/ThirdPartyApoTests/TestMediaType.h b/audio/tests/HLK/ThirdPartyApoTests/TestMediaType.h
new file mode 100644
index 00000000..e213a746
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/TestMediaType.h
@@ -0,0 +1,163 @@
+// Copyright (c) Microsoft Corporation
+//
+// Author: Alper Selcuk
+//
+// Description:
+//
+// Declaration of the AudioMediaType
+// The original code is copied from MF tree.
+//
+// Note that this implementation is only useful for some unit tests that need
+// to pass bogus media types.
+//
+#pragma once
+
+#include <AudioMediaType.h>
+
+//-----------------------------------------------------------------------------
+// Description:
+//
+// Implements a stripped down version of IAudioMediaType.
+
+// Remarks:
+//
+// Most methods are not implemented, because they are not needed by the
+// processor.
+//
+// NOTE: [alper] If this is passed to third party APO's then we may be forced
+// to implement all the methods.
+//
+class CTestAudioMediaType : public IAudioMediaType
+{
+public:
+
+ //--------------------------------------------------------------------------
+ // Description:
+ //
+ // Used during creation
+ //
+ // Parameters:
+ //
+ // pFormat - [in] the format
+ // ppIAudioMediaType - [out] Pointer to the created IAudioMediaType.
+ //
+ // Return Values:
+ //
+ // S_OK On success
+ // error Otherwise
+ //
+ static HRESULT Create( const UNCOMPRESSEDAUDIOFORMAT* pFormat,
+ IAudioMediaType** ppIAudioMediaType);
+
+ // IUnknown methods
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ STDMETHOD( QueryInterface )( REFIID riid, LPVOID *ppvObject );
+
+ //--------------------------------------------------------------------------
+ // Description:
+ //
+ // Get Major Type
+ //
+ // Parameters:
+ //
+ // pguidMajorType - [out] pointer
+ //
+ // Return Values:
+ //
+ // E_NOTIMPL Not supported
+ //
+ virtual HRESULT STDMETHODCALLTYPE GetMajorType(GUID * /* pguidMajorType */)
+ {
+ return E_NOTIMPL;
+ }
+
+ //--------------------------------------------------------------------------
+ // Description:
+ //
+ // Checks to see if the format is compressed.
+ //
+ // Parameters:
+ //
+ // pfCompressed - [out] On S_OK, this is set TRUE.
+ //
+ // Return Values:
+ //
+ // S_OK On success
+ // E_POINTER Bad pointer
+ //
+ virtual HRESULT STDMETHODCALLTYPE IsCompressedFormat(
+ /* [out] */ BOOL* pfCompressed
+ )
+ {
+ if (NULL == pfCompressed)
+ {
+ return( E_POINTER );
+ }
+
+ *pfCompressed = TRUE;
+
+ return( S_OK );
+ }
+
+ //--------------------------------------------------------------------------
+ // Description:
+ //
+ // Checks to see if the media types are equal.
+ //
+ // Parameters:
+ //
+ // pAudioMediaType - [in] Media type.
+ // pdwFlags - [out] flags
+ //
+ // Return Values:
+ //
+ // S_OK On success
+ // error Otherwise
+ //
+ virtual HRESULT STDMETHODCALLTYPE IsEqual( IAudioMediaType *pAudioMediaType,DWORD *pdwFlags);
+
+ //--------------------------------------------------------------------------
+ // Description:
+ //
+ // Gets the Audio Format.
+ //
+ // Results:
+ //
+ // Returns a WAVEFORMATEX pointer.
+ //
+ virtual const WAVEFORMATEX *STDMETHODCALLTYPE GetAudioFormat( void);
+
+ //--------------------------------------------------------------------------
+ // Description:
+ //
+ // Gets the Uncompressed Audio Format.
+ //
+ // Parameters:
+ //
+ // pUncompressedAudioFormat - [out] gets the format.
+ //
+ // Return Values:
+ //
+ // S_OK On success
+ // error Otherwise
+ //
+ virtual HRESULT STDMETHODCALLTYPE GetUncompressedAudioFormat(
+ UNCOMPRESSEDAUDIOFORMAT* pUncompressedAudioFormat );
+
+protected:
+ CTestAudioMediaType();
+
+protected:
+ LONG m_cRef;
+ UNCOMPRESSEDAUDIOFORMAT m_Format;
+ WAVEFORMATEXTENSIBLE m_Wfx;
+};
+
+//-----------------------------------------------------------------------------
+// Called on creation
+inline CTestAudioMediaType::CTestAudioMediaType()
+{
+ m_cRef = 0;
+} // CTestAudioMediaType
+
diff --git a/audio/tests/HLK/ThirdPartyApoTests/TestWasapiUseFromAudioDg.cpp b/audio/tests/HLK/ThirdPartyApoTests/TestWasapiUseFromAudioDg.cpp
new file mode 100644
index 00000000..3b573f87
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/TestWasapiUseFromAudioDg.cpp
@@ -0,0 +1,244 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+
+#include <stdafx.h>
+#include <memory>
+#include <wil\resultmacros.h>
+#include <wil\com.h>
+#include <audioclient.h>
+#include <audioclientp.h>
+#include <audiosessiontypesp.h>
+#include <mmdeviceapi.h>
+#include "etwlistener.h"
+#include <functiondiscoverykeys.h>
+
+class CTestWasapiUsageFromAudioDg
+{
+ BEGIN_TEST_CLASS(CTestWasapiUsageFromAudioDg)
+ START_APPVERIFIFER
+ TEST_CLASS_PROPERTY(L"Owner", L"auddev")
+ TEST_CLASS_PROPERTY(L"TestClassification", L"Feature")
+ TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"audiodg.exe")
+ TEST_CLASS_PROPERTY(L"ArtifactUnderTest", L"audioenginebaseapop.h")
+ TEST_CLASS_PROPERTY(L"RunAs", L"Elevated")
+ TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA")
+ TEST_CLASS_PROPERTY(L"Ignore", L"false")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfileFile", L"Audio-Tests.wprp")
+ TEST_CLASS_PROPERTY(L"EtwLogger:WPRProfile", L"MultimediaCategory.Verbose.File")
+ END_APPVERIFIER
+ END_TEST_CLASS()
+
+protected:
+ BEGIN_TEST_METHOD(TestStreamCreation)
+ COMMON_ONECORE_TEST_PROPERTIES
+ TEST_METHOD_PROPERTY(L"Kits.TestId", L"50B80D35-5E61-4D91-B201-5F89D67E4A5A")
+ TEST_METHOD_PROPERTY(L"Kits.TestId2", L"216ADA42-25DC-4504-9032-16D63704FB08")
+ TEST_METHOD_PROPERTY(L"Kits.TestName", L"Audio APO - Test WASAPI usage from APOs - TestStreamCreation")
+ TEST_METHOD_PROPERTY(L"Kits.ExpectedRuntime", L"30")
+ TEST_METHOD_PROPERTY(L"Kits.TimeoutInMinutes", L"60")
+ TEST_METHOD_PROPERTY(L"Kits.Description", L"Third Party APO Test: Test WASAPI usage")
+ APO_TEST_PROPERTIES
+ END_TEST_METHOD()
+};
+
+using malloca_deleter = wil::function_deleter<decltype(&::_freea), ::_freea>;
+template <typename T>
+using unique_malloca_ptr = wistd::unique_ptr<T, malloca_deleter>;
+
+class CEventParser : public IEtwEventHandler
+{
+public:
+ int GetCountOfAudioClientInits() { return m_countOfAudioClientInits; }
+ int GetCountOfAudioClientInitsFromAudioDg() { return m_countOfAudioClientInitsFromAudioDg; }
+
+private:
+
+ int m_countOfAudioClientInits = 0;
+ int m_countOfAudioClientInitsFromAudioDg = 0;
+
+ std::wstring QueryProcessName(const DWORD pid)
+ {
+ wil::unique_handle hProcess(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
+ if(NULL == hProcess)
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+ else
+ {
+ WCHAR szProcessName[1024];
+ DWORD cchSize = ARRAYSIZE(szProcessName);
+ if (0 == QueryFullProcessImageNameW(hProcess.get(), 0, szProcessName, &cchSize))
+ {
+ return L"err " + std::to_wstring(GetLastError());
+ }
+
+ PWSTR fileName = szProcessName + cchSize;
+ while (fileName != szProcessName && *fileName != L'\\') fileName--;
+
+ return (*fileName == L'\\') ? fileName + 1 : fileName;
+ }
+ }
+
+ void OnEvent(PEVENT_RECORD pEventRecord)
+ {
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+ [&]() {
+ ULONG bufferSize = 0;
+ if (TdhGetEventInformation(pEventRecord, 0, nullptr, nullptr, &bufferSize) == ERROR_INSUFFICIENT_BUFFER)
+ {
+ unique_malloca_ptr<TRACE_EVENT_INFO> eventInfo(reinterpret_cast<PTRACE_EVENT_INFO>(_malloca(bufferSize)));
+ VERIFY_IS_NOT_NULL(eventInfo);
+ RETURN_IF_WIN32_ERROR(TdhGetEventInformation(pEventRecord, 0, nullptr, eventInfo.get(), &bufferSize));
+
+ // LOG_OUTPUT(L"Received event: %s", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE*>(eventInfo.get()) + eventInfo->EventNameOffset));
+ if (_wcsicmp(L"AudioClientInitialize", reinterpret_cast<PWSTR>(reinterpret_cast<BYTE*>(eventInfo.get()) + eventInfo->EventNameOffset)) == 0)
+ {
+ m_countOfAudioClientInits++;
+
+ try
+ {
+ // LOG_OUTPUT(L"Process name: %s", QueryProcessName(pEventRecord->EventHeader.ProcessId).c_str());
+ if (0 == _wcsicmp(QueryProcessName(pEventRecord->EventHeader.ProcessId).c_str(), L"audiodg.exe"))
+ {
+ m_countOfAudioClientInitsFromAudioDg++;
+ }
+ }
+ CATCH_LOG()
+ }
+ }
+ return S_OK;
+ }();
+ }
+};
+
+// {6e7b1892-5288-5fe5-8f34-e3b0dc671fd2}
+static const GUID AudioSesTelemetryProvider =
+{ 0x6e7b1892, 0x5288, 0x5fe5, 0x8f, 0x34, 0xe3, 0xb0, 0xdc, 0x67, 0x1f, 0xd2 };
+
+void CTestWasapiUsageFromAudioDg::TestStreamCreation()
+{
+ WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
+
+ CEtwListener listener;
+ CEventParser parser;
+
+ VERIFY_SUCCEEDED(listener.StartTraceSession(L"TestWasapiUsageFromAudioDg", static_cast<IEtwEventHandler *>(&parser)));
+
+ // Enable Microsoft.Windows.Audio.Client
+ VERIFY_SUCCEEDED(listener.EnableProvider(AudioSesTelemetryProvider, TRACE_LEVEL_INFORMATION, 8));
+
+ wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spEnumerator)));
+
+ // Test Render endpoints - all stream categories are valid except FarFieldSpeech, UniformSpeech, and VoiceTyping
+ {
+ wil::com_ptr_nothrow<IMMDeviceCollection> spDevices;
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &spDevices));
+
+ UINT cDevices = 0;
+ VERIFY_SUCCEEDED(spDevices->GetCount(&cDevices));
+
+ for (UINT i = 0; i < cDevices; i++)
+ {
+ wil::com_ptr_nothrow<IMMDevice> pEndpoint;
+ VERIFY_SUCCEEDED(spDevices->Item(i, &pEndpoint));
+
+ wil::unique_prop_variant var;
+ wil::com_ptr_nothrow<IPropertyStore> spPropertyStore;
+ VERIFY_SUCCEEDED(pEndpoint->OpenPropertyStore(STGM_READ, &spPropertyStore));
+ VERIFY_SUCCEEDED(spPropertyStore->GetValue(PKEY_Device_FriendlyName, &var));
+ VERIFY_ARE_EQUAL(VT_LPWSTR, var.vt);
+ LOG_OUTPUT(L"Testing endpoint: %s", var.pwszVal);
+
+ for (int category = ExtendedAudioCategory_Other; category < ExtendedAudioCategory_enum_count; category++)
+ {
+ if (!(category == ExtendedAudioCategory_FarFieldSpeech ||
+ category == ExtendedAudioCategory_UniformSpeech ||
+ category == ExtendedAudioCategory_VoiceTyping))
+ {
+ wil::com_ptr_nothrow<IAudioClient2> ac;
+ VERIFY_SUCCEEDED(pEndpoint->Activate(__uuidof(IAudioClient2), CLSCTX_ALL, NULL, (void **)&ac));
+
+ AudioClientPropertiesPrivate acpp = {0};
+ acpp.ClientProps.cbSize = sizeof(acpp);
+ acpp.extCategory = (AUDIO_STREAM_EXTENDED_CATEGORY)category;
+
+ VERIFY_SUCCEEDED(ac->SetClientProperties(&acpp.ClientProps));
+
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> mixFormat;
+ VERIFY_SUCCEEDED(ac->GetMixFormat(wil::out_param(mixFormat)));
+
+ LOG_OUTPUT(L"Testing category: %d", acpp.extCategory);
+ VERIFY_SUCCEEDED(ac->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, mixFormat.get(), nullptr));
+
+ Sleep(1000);
+ }
+ }
+ }
+ }
+
+ // Test Capture endpoints - only some stream categories are valid
+ {
+ constexpr AUDIO_STREAM_EXTENDED_CATEGORY captureCategories[] = {ExtendedAudioCategory_Other,
+ ExtendedAudioCategory_Communications,
+ ExtendedAudioCategory_Media,
+ ExtendedAudioCategory_GameChat,
+ ExtendedAudioCategory_Speech,
+ ExtendedAudioCategory_PersonalAssistant,
+ ExtendedAudioCategory_FarFieldSpeech,
+ ExtendedAudioCategory_UniformSpeech,
+ ExtendedAudioCategory_VoiceTyping,
+ };
+
+ wil::com_ptr_nothrow<IMMDeviceCollection> spDevices;
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &spDevices));
+
+ UINT cDevices = 0;
+ VERIFY_SUCCEEDED(spDevices->GetCount(&cDevices));
+
+ for (UINT i = 0; i < cDevices; i++)
+ {
+ wil::com_ptr_nothrow<IMMDevice> pEndpoint;
+ VERIFY_SUCCEEDED(spDevices->Item(i, &pEndpoint));
+
+ wil::unique_prop_variant var;
+ wil::com_ptr_nothrow<IPropertyStore> spPropertyStore;
+ VERIFY_SUCCEEDED(pEndpoint->OpenPropertyStore(STGM_READ, &spPropertyStore));
+ VERIFY_SUCCEEDED(spPropertyStore->GetValue(PKEY_Device_FriendlyName, &var));
+ VERIFY_ARE_EQUAL(VT_LPWSTR, var.vt);
+ LOG_OUTPUT(L"Testing endpoint: %s", var.pwszVal);
+
+ for (int idx = 0; idx < ARRAYSIZE(captureCategories); idx++)
+ {
+ wil::com_ptr_nothrow<IAudioClient2> ac;
+ VERIFY_SUCCEEDED(pEndpoint->Activate(__uuidof(IAudioClient2), CLSCTX_ALL, NULL, (void**)&ac));
+
+ AudioClientPropertiesPrivate acpp = { 0 };
+ acpp.ClientProps.cbSize = sizeof(acpp);
+ acpp.extCategory = captureCategories[idx];
+
+ VERIFY_SUCCEEDED(ac->SetClientProperties(&acpp.ClientProps));
+
+ wil::unique_cotaskmem_ptr<WAVEFORMATEX> mixFormat;
+ VERIFY_SUCCEEDED(ac->GetMixFormat(wil::out_param(mixFormat)));
+
+ LOG_OUTPUT(L"Testing category: %d", acpp.extCategory);
+ VERIFY_SUCCEEDED(ac->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, mixFormat.get(), nullptr));
+
+ Sleep(1000);
+ }
+ }
+ }
+
+ // Sleep a while to ensure we receive all the events from the above streams
+ Sleep(5000);
+
+ LOG_OUTPUT(L"Number of audio streams created: %d", parser.GetCountOfAudioClientInits());
+ LOG_OUTPUT(L"Number of audio streams created from AudioDg: %d", parser.GetCountOfAudioClientInitsFromAudioDg());
+ VERIFY_ARE_EQUAL(0, parser.GetCountOfAudioClientInitsFromAudioDg());
+
+ // Stop the trace
+ listener.StopTraceSession();
+
+}
diff --git a/audio/tests/HLK/ThirdPartyApoTests/Wrappers.h b/audio/tests/HLK/ThirdPartyApoTests/Wrappers.h
new file mode 100644
index 00000000..82ceedc0
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/Wrappers.h
@@ -0,0 +1,121 @@
+//
+// Wrappers.h -- Copyright (c) Microsoft Corporation
+//
+// Author: zaneh
+//
+// Description:
+//
+// Wrapper methods for APIs which can throw
+//
+
+// Collects exception data and sends it to Watson without crashing Audio DG.
+// This method can only be called from inside the __except condition statement
+// as a filter function.
+inline DWORD CollectExceptionDataAndContinue(LPEXCEPTION_POINTERS exceptionInfo)
+{
+ LONG lResult;
+ DWORD dwType, dwVal, dwSize=sizeof(DWORD);
+
+ // Check to see if we should allow the process create a crash dump
+ lResult = RegGetValue(HKEY_LOCAL_MACHINE, REGKEY_AUDIO_SOFTWARE, PREVENT_APOTEST_CRASH_OR_REPORT_ON_APO_EXCEPTION, RRF_RT_DWORD, &dwType, &dwVal, &dwSize);
+
+ // If we are allowing dumps, report an exception to Watson before continuing
+ if ((lResult != ERROR_SUCCESS) || (dwVal == 0))
+ {
+ RtlReportException(exceptionInfo->ExceptionRecord, exceptionInfo->ContextRecord, RTL_WER_NO_DEBUG | RTL_WER_NON_FATAL);
+ }
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+HRESULT QIInternal(IAudioProcessingObject* pIAPO, _In_ REFIID riid, _COM_Outptr_ void** ppvObject)
+{
+ __try
+ {
+ return pIAPO->QueryInterface(riid, ppvObject);
+ }
+ __except (CollectExceptionDataAndContinue(GetExceptionInformation()))
+ {
+ return __HRESULT_FROM_WIN32(GetExceptionCode());
+ }
+}
+
+HRESULT UnknownQIInternal(IUnknown* pIAPO, _In_ REFIID riid, _COM_Outptr_ void** ppvObject)
+{
+ __try
+ {
+ return pIAPO->QueryInterface(riid, ppvObject);
+ }
+ __except (CollectExceptionDataAndContinue(GetExceptionInformation()))
+ {
+ return __HRESULT_FROM_WIN32(GetExceptionCode());
+ }
+}
+
+HRESULT IsOutputFormatSupported(IAudioProcessingObject* pIAPO, IAudioMediaType* pInputFormat, IAudioMediaType* pRequestedOutputFormat, IAudioMediaType** ppSupportedOutputFormat)
+{
+ __try
+ {
+ return pIAPO->IsOutputFormatSupported(pInputFormat, pRequestedOutputFormat, ppSupportedOutputFormat);
+ }
+ __except (CollectExceptionDataAndContinue(GetExceptionInformation()))
+ {
+ return __HRESULT_FROM_WIN32(GetExceptionCode());
+ }
+}
+
+HRESULT LockForProcess(IAudioProcessingObjectConfiguration* pIAPOConfig,
+ _In_ UINT32 u32NumInputConnections,
+ _In_reads_(u32NumInputConnections) APO_CONNECTION_DESCRIPTOR** ppInputConnections,
+ _In_ UINT32 u32NumOutputConnections,
+ _In_reads_(u32NumOutputConnections) APO_CONNECTION_DESCRIPTOR** ppOutputConnections)
+{
+ __try
+ {
+ return pIAPOConfig->LockForProcess(u32NumInputConnections, ppInputConnections, u32NumOutputConnections, ppOutputConnections);
+ }
+ __except (CollectExceptionDataAndContinue(GetExceptionInformation()))
+ {
+ return __HRESULT_FROM_WIN32(GetExceptionCode());
+ }
+}
+
+HRESULT UnlockForProcess(IAudioProcessingObjectConfiguration* pIAPOConfig)
+{
+ __try
+ {
+ return pIAPOConfig->UnlockForProcess();
+ }
+ __except (CollectExceptionDataAndContinue(GetExceptionInformation()))
+ {
+ return __HRESULT_FROM_WIN32(GetExceptionCode());
+ }
+}
+
+void APOProcess(IAudioProcessingObjectRT* pIAPORT,
+ _In_ UINT32 u32NumInputConnections,
+ _In_reads_(u32NumInputConnections) APO_CONNECTION_PROPERTY** ppInputConnections,
+ _In_ UINT32 u32NumOutputConnections,
+ _Inout_updates_(u32NumOutputConnections) APO_CONNECTION_PROPERTY** ppOutputConnections)
+{
+ __try
+ {
+ pIAPORT->APOProcess(u32NumInputConnections, ppInputConnections, u32NumOutputConnections, ppOutputConnections);
+ }
+ __except (CollectExceptionDataAndContinue(GetExceptionInformation()))
+ {
+ return;
+ }
+}
+
+HRESULT GetRegistrationProperties(IAudioProcessingObject* pIAPO, APO_REG_PROPERTIES** ppRegProps)
+{
+ __try
+ {
+ return pIAPO->GetRegistrationProperties(ppRegProps);
+ }
+ __except (CollectExceptionDataAndContinue(GetExceptionInformation()))
+ {
+ return __HRESULT_FROM_WIN32(GetExceptionCode());
+ }
+} \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/audioconnectionutil.h b/audio/tests/HLK/ThirdPartyApoTests/audioconnectionutil.h
new file mode 100644
index 00000000..771d03de
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/audioconnectionutil.h
@@ -0,0 +1,129 @@
+#pragma once
+
+#define IS_2_POW_N(X) (((X)&(X-1)) == 0)
+
+enum class aligned_size_t : size_t {};
+
+void * AllocateAligned(size_t cb, aligned_size_t AlignmentMask)
+{
+ ASSERT(0 != cb);
+ //ASSERT(IS_2_POW_N(AlignmentMask + 1));
+
+ void *res = _aligned_malloc(cb, static_cast<size_t>(AlignmentMask) + 1);
+
+ return res;
+}
+
+void DeallocateAligned(void * p, aligned_size_t /* AlignmentMask */)
+{
+ if (NULL != p)
+ {
+ _aligned_free(p);
+ }
+}
+
+UINT32 CalculateAlignmentMask(APO_CONNECTION_DESCRIPTOR * /* pDescriptor */, UINT32 u32BytesPerFrame)
+{
+ const UINT32 u32AudioAlignment = sizeof(PVOID);
+ UINT32 u32Alignment = u32AudioAlignment - 1;
+
+ // Calculate the new alignment requirement for this buffer.
+ if (u32BytesPerFrame > u32AudioAlignment)
+ {
+ // Use the frame size directly if it is a power of two.
+ if (IS_2_POW_N(u32BytesPerFrame))
+ {
+ u32Alignment = u32BytesPerFrame - 1;
+ }
+ // The new alignment size should be greater than frame
+ // size and should be a power of 2.
+ else
+ {
+ u32Alignment = u32AudioAlignment;
+ while (u32BytesPerFrame / u32Alignment)
+ {
+ u32Alignment *= 2;
+ }
+ u32Alignment = u32Alignment - 1;
+ }
+ }
+
+ // Use default alignment if the frame size is less than the default
+ // alignment.
+ return u32Alignment;
+} // CalculateAlignmentMask
+
+HRESULT
+CreateConnection(APO_CONNECTION_DESCRIPTOR* pDescriptor, APO_CONNECTION_PROPERTY* pProperty)
+{
+ RETURN_HR_IF_NULL(E_POINTER, pDescriptor);
+ RETURN_HR_IF_NULL(E_POINTER, pDescriptor->pFormat);
+ RETURN_HR_IF_NULL(E_POINTER, pProperty);
+
+ UINT32 u32BytesPerFrame;
+ unsigned byteAlignmentMask;
+ BYTE* pAllocatedBuffer;
+ UNCOMPRESSEDAUDIOFORMAT Format;
+
+ RETURN_IF_FAILED(pDescriptor->pFormat->GetUncompressedAudioFormat(&Format));
+
+ u32BytesPerFrame =
+ Format.dwSamplesPerFrame * Format.dwBytesPerSampleContainer;
+
+ // Allocate buffer internally.
+ if (NULL == pDescriptor->pBuffer)
+ {
+ byteAlignmentMask = CalculateAlignmentMask(pDescriptor, u32BytesPerFrame);
+
+ pAllocatedBuffer = (BYTE *)AllocateAligned(pDescriptor->u32MaxFrameCount * u32BytesPerFrame, static_cast<aligned_size_t>(byteAlignmentMask));
+ RETURN_HR_IF_NULL(E_OUTOFMEMORY, pAllocatedBuffer);
+
+ pDescriptor->pBuffer = reinterpret_cast<UINT_PTR>(pAllocatedBuffer);
+ pDescriptor->Type = APO_CONNECTION_BUFFER_TYPE_ALLOCATED;
+ }
+ // Use the given buffer.
+ else
+ {
+ pDescriptor->Type = APO_CONNECTION_BUFFER_TYPE_EXTERNAL;
+ }
+
+ pProperty->pBuffer = pDescriptor->pBuffer;
+ pProperty->u32ValidFrameCount = 0;
+ pProperty->u32Signature = APO_CONNECTION_PROPERTY_SIGNATURE;
+
+ return S_OK;
+} // CreateConnection
+
+HRESULT
+DestroyConnection(APO_CONNECTION_DESCRIPTOR *pDescriptor)
+{
+ HRESULT hResult;
+ UINT32 u32BytesPerFrame;
+ size_t byteAlignmentMask;
+ BYTE* pAllocatedBuffer;
+ UNCOMPRESSEDAUDIOFORMAT Format;
+
+ hResult = pDescriptor->pFormat->GetUncompressedAudioFormat(&Format);
+ //IF_FAILED_JUMP(hResult, Exit);
+
+ if (APO_CONNECTION_BUFFER_TYPE_ALLOCATED == pDescriptor->Type)
+ {
+ u32BytesPerFrame =
+ Format.dwSamplesPerFrame * Format.dwBytesPerSampleContainer;
+
+ byteAlignmentMask = CalculateAlignmentMask(pDescriptor, u32BytesPerFrame);
+
+ if (pDescriptor->pBuffer)
+ {
+ pAllocatedBuffer = reinterpret_cast<BYTE*>(pDescriptor->pBuffer);
+ RETURN_HR_IF_NULL(E_OUTOFMEMORY, pAllocatedBuffer);
+
+ DeallocateAligned((void *) pAllocatedBuffer, static_cast<aligned_size_t>(byteAlignmentMask));
+ }
+ }
+
+ pDescriptor->pFormat->Release();
+ pDescriptor->pFormat = NULL;
+
+ return hResult;
+} // DestroyConnection
diff --git a/audio/tests/HLK/ThirdPartyApoTests/sinewave.h b/audio/tests/HLK/ThirdPartyApoTests/sinewave.h
new file mode 100644
index 00000000..8f0fc03c
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/sinewave.h
@@ -0,0 +1,60 @@
+#pragma once
+
+class SineWave
+{
+ struct wave
+ {
+ float amp{}, freq{}, phase{}, dc{};
+ };
+public:
+ SineWave() {}
+
+ template<typename _ty1, typename _ty2>
+ SineWave(_ty1 numChannels, _ty2 samplingRate) : _channel(unsigned(numChannels)), _sr(float(samplingRate)) {}
+
+ template<typename _ty1, typename _ty2>
+ inline void Initialize(_ty1 numChannels, _ty2 samplingRate)
+ {
+ _channel.resize(unsigned(numChannels));
+ _sr = unsigned(samplingRate);
+ }
+
+ template<typename _ty1>
+ inline void SetChannel(_ty1 channel, float freq, float amplitude = 0.f, float dc = 0.f, float phase = 0.f)
+ {
+ _channel[unsigned(channel)].amp = amplitude;
+ _channel[unsigned(channel)].freq = freq;
+ _channel[unsigned(channel)].phase = phase;
+ _channel[unsigned(channel)].dc = dc;
+ }
+
+
+ template<typename _ty1>
+ void FillFrames(void* buffer, _ty1 numFrames)
+ {
+ auto fb = reinterpret_cast<float*>(buffer);
+ for (unsigned i = 0; i < unsigned(numFrames); ++i)
+ {
+ for (unsigned ch = 0; ch < _channel.size(); ++ch)
+ {
+ fb[i * _channel.size() + ch] = Calculate(ch);
+ }
+ }
+ }
+
+protected:
+ float Calculate(unsigned ch)
+ {
+ auto& w = _channel[ch];
+ float sample = w.amp * sinf(w.phase) + w.dc;
+
+ w.phase += 2 * float(M_PI) * w.freq / _sr;
+ w.phase = fmodf(w.phase, 2 * float(M_PI));
+
+ return sample;
+ }
+
+private:
+ std::vector<wave> _channel;
+ float _sr{};
+}; \ No newline at end of file
diff --git a/audio/tests/HLK/ThirdPartyApoTests/stdafx.h b/audio/tests/HLK/ThirdPartyApoTests/stdafx.h
new file mode 100644
index 00000000..8a836cf0
--- /dev/null
+++ b/audio/tests/HLK/ThirdPartyApoTests/stdafx.h
@@ -0,0 +1,123 @@
+//
+// stdafx.h -- Copyright (c) Microsoft Corporation
+//
+// Author: zaneh
+//
+// Description:
+//
+// Include file for well-known headers
+//
+#pragma once
+
+#define _USE_MATH_DEFINES
+#include <cmath>
+#include <vector>
+
+#include <nt.h>
+#include <ntrtl.h>
+#include <nturtl.h>
+
+#define NO_MOCK_VALIDATION
+#include <mock.h>
+#include <Windows.Media.Devices.h>
+#include <windows.h>
+#include <versionhelpers.h>
+#include <WexTestClass.h>
+
+#include "wil\resource.h"
+#include "wil\com.h"
+
+
+// System Effect Specifiers. These help determine which type of system
+// effect the device is attempting to use in order to test it properly
+#define DT_GFX 0x00000002
+#define DT_LFX 0x00000004
+#define DT_LGFX (DT_LFX | DT_GFX)
+#define DT_SFX 0x00000008
+#define DT_MFX 0x00000010
+#define DT_EFX 0x00000020
+#define DT_SMEFX (DT_SFX | DT_MFX | DT_EFX)
+
+// Reg Key definitions for disabling AudioDG crashing on an APO exception
+#define PREVENT_APOTEST_CRASH_OR_REPORT_ON_APO_EXCEPTION L"PreventAPOTestCrashOrReportOnAPOException"
+#define REGKEY_AUDIO_SOFTWARE L"Software\\Microsoft\\Windows\\CurrentVersion\\Audio"
+#define REGKEY_DEFAULT L"(default)"
+
+// Test logging macros
+#ifndef LOG_ERROR
+#define LOG_ERROR(fmt, ...) WEX::Logging::Log::Error(WEX::Common::String().Format(fmt L"\n\t\tFunction: " \
+ __FUNCTION__ L" Line: %d", __VA_ARGS__, __LINE__))
+#endif
+
+#ifndef LOG_OUTPUT
+#define LOG_OUTPUT(fmt, ...) WEX::Logging::Log::Comment(WEX::Common::String().Format(fmt, __VA_ARGS__))
+#endif
+
+#define LOG_RETURN_IF_FAILED(hr, fmt, ...) if (FAILED(hr)) { LOG_ERROR(fmt, __VA_ARGS__); \
+ WEX::Logging::Log::Error(WEX::Common::String().Format(L"\t\tError Code: (0x%08lx)" \
+ , hr)); return hr; }
+#define LOG_RETURN_VOID_IF_FAILED(hr, fmt, ...) if(FAILED(hr)) { LOG_ERROR(fmt, __VA_ARGS__); \
+ WEX::Logging::Log::Error(WEX::Common::String().Format(L"\t\tError Code: (0x%08lx)" \
+ , hr)); return; }
+#define LOG_RETURN_HR_IF(condition, hr, fmt, ...) if (condition) { LOG_ERROR(fmt, __VA_ARGS__); \
+ WEX::Logging::Log::Error(WEX::Common::String().Format(L"\t\tError Code: (0x%08lx)" \
+ , hr)); return hr; }
+// End Test logging macros
+
+// Begin Metadata defines
+#define COMMON_ONECORE_TEST_PROPERTIES \
+ COMMON_TEST_PROPERTIES \
+ TEST_METHOD_PROPERTY(L"Kits.SupportedOS", L"Windows v10.0 Client x86") \
+ TEST_METHOD_PROPERTY(L"Kits.SupportedOS", L"Windows v10.0 Client x64") \
+ TEST_METHOD_PROPERTY(L"Kits.SupportedOS", L"Windows v10.0 Client ARM") \
+ TEST_METHOD_PROPERTY(L"Kits.SupportedOS", L"Windows v10.0 Client ARM64") \
+ TEST_METHOD_PROPERTY(L"Kits.MinRelease", L"RS5") \
+ TEST_METHOD_PROPERTY(L"Kits.CorePackageComposition", L"OneCoreUAP") \
+ TEST_METHOD_PROPERTY(L"Kits.CorePackageComposition", L"OneCore") \
+ TEST_METHOD_PROPERTY(L"Kits.CorePackageComposition", L"Full") \
+
+#define COMMON_TEST_PROPERTIES \
+ TEST_METHOD_PROPERTY(L"Kits.TestType", L"Development") \
+ TEST_METHOD_PROPERTY(L"Kits.RunElevated", L"True") \
+ TEST_METHOD_PROPERTY(L"Kits.RequiresReboot", L"False") \
+ TEST_METHOD_PROPERTY(L"Kits.PublishingOrganization", L"Microsoft Corporation") \
+ TEST_METHOD_PROPERTY(L"Kits.DevelopmentPhase", L"Development and Integration") \
+ TEST_METHOD_PROPERTY(L"Kits.HasSupplementalContent", L"False") \
+
+#define APO_TEST_PROPERTIES \
+ TEST_METHOD_PROPERTY(L"Kits.Specification", L"Device.Audio.APO.ThirdParty") \
+
+#define START_APPVERIFIFER \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask", L"AppVerifierStart") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.Stage", L"Setup") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.RunElevated", L"True") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.CommandLine", L"cmd.exe /c appverif -enable exceptions handles heaps leak locks memory srwlock threadpool tls -for te.processhost.exe te.exe audiodg.exe") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.Order", L"1") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.ExpectedExitCode", L"0") \
+
+#define END_APPVERIFIER \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask", L"AppVerifierEnd") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierEnd.Stage", L"Cleanup") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierEnd.RunElevated", L"True") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierEnd.CommandLine", L"cmd.exe /c appverif -disable exceptions handles heaps leak locks memory srwlock threadpool tls -for te.processhost.exe te.exe audiodg.exe") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierEnd.Order", L"1") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierEnd.ExpectedExitCode", L"0") \
+
+#define START_OSUPGRADE \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask", L"OSUpgrade") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.OSUpgrade.Stage", L"Setup") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.OSUpgrade.RunElevated", L"True") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.OSUpgrade.CommandLine", L"cmd.exe /c c:\\HLK\\ISO\\setup.exe /auto:upgrade /compat IgnoreWarning /noreboot") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.OSUpgrade.Order", L"1") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.OSUpgrade.ExpectedExitCode", L"0") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.OSUpgrade.TimeoutInMinutes", L"240") \
+
+#define START_APPVERIFIFER_UPGRADE \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask", L"AppVerifierStart") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.Stage", L"Setup") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.RunElevated", L"True") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.CommandLine", L"cmd.exe /c appverif -enable exceptions handles heaps leak locks memory srwlock threadpool tls -for te.processhost.exe te.exe audiodg.exe") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.Order", L"2") \
+ TEST_CLASS_PROPERTY(L"Kits.TestTask.AppVerifierStart.ExpectedExitCode", L"0") \
+
+// End Metadata defines