diff --git a/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.cpp b/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.cpp new file mode 100644 index 000000000..ca3b3a7a4 --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.cpp @@ -0,0 +1,1120 @@ +#include + +#include +#include "propkey.h" +#include +#include +#include +#include +#include "TestMediaType.h" +#include "audioconnectionutil.h" +#include + +#include + +#include +#include + +#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 spEnumerator; + com_ptr_nothrow 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 spEndpoint; + com_ptr_nothrow 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 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 pIAPO; + wil::com_ptr pIAPORT; + wil::com_ptr 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(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 pIAPO = nullptr; + com_ptr_nothrow pICustomFormats = nullptr; + com_ptr_nothrow 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 pIFormat = nullptr; + com_ptr_nothrow pIFormatSuggested = nullptr; + LPWSTR strFormatName = NULL; + com_ptr_nothrow 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 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 pIAPO; + wil::com_ptr pIAPORT; + wil::com_ptr 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(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 pIDeviceEnumerator; + com_ptr_nothrow pIMMDevNode; + com_ptr_nothrow 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 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 pIMMEndpoint; + com_ptr_nothrow pIMMEndpointInt; + com_ptr_nothrow pIPropertyStoreDevice; + com_ptr_nothrow pIPropertyStoreFX; + com_ptr_nothrow pIDeviceTopology; + com_ptr_nothrow 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 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 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 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 000000000..312259955 --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/APODeviceTest.h @@ -0,0 +1,180 @@ +#include + +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 m_pIUnknown; + wil::com_ptr_nothrow m_pIAPO; + PAPO_REG_PROPERTIES m_pRegProps; + + // These are only used for SysFx + wil::com_ptr_nothrow m_pIEndpoint; + wil::unique_cotaskmem_string m_szAttachedDevice; + wil::com_ptr_nothrow m_pIEPStore; + wil::com_ptr_nothrow m_pIFXStore; + wil::com_ptr_nothrow 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 pDevice, LPWSTR deviceName); + HRESULT AddSysFxDevices(); + +protected: + friend class CApoDeviceTests; + + std::list> m_DeviceList; + wil::com_ptr_nothrow m_spDevice; + WCHAR m_deviceName[MAX_PATH] = { '\0' }; +}; + +class CApoDeviceTests : public WEX::TestClass +{ + 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> 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 000000000..9fc5ae451 --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.cpp @@ -0,0 +1,96 @@ +#include + +#include +#include "propkey.h" +#include +#include +#include +#include +#include +#include "TestMediaType.h" +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 spEnumerator; + com_ptr_nothrow 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 pDevice = nullptr; + com_ptr_nothrow pAudioClient = nullptr; + com_ptr_nothrow 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 000000000..3c61390d4 --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/APOOSUpgradeTest.h @@ -0,0 +1,38 @@ +#include + +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 000000000..160f488be --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/APOStressTest.cpp @@ -0,0 +1,777 @@ +#include + +#include +#include "propkey.h" +#include +#include +#include +#include +#include +#include "TestMediaType.h" +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "APOStressTest.h" +#include "sinewave.h" +#include "util.h" + +#include +#include + +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 +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 spEnumerator; + com_ptr_nothrow spDevices; + vector> 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 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 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 pAudioClient; + wil::unique_cotaskmem_ptr 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(mixFormat))); + + IF_FAILED_RETURN(pAudioClient->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + mixFormat.get(), + nullptr)); + + com_ptr_nothrow pRenderClient; + IF_FAILED_RETURN(pAudioClient->GetService(_uuidof(IAudioRenderClient), wil::out_param_ptr(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 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 pAudioClient; + wil::unique_cotaskmem_ptr 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(mixFormat))); + + IF_FAILED_RETURN(pAudioClient->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + mixFormat.get(), + nullptr)); + + com_ptr_nothrow pCaptureClient; + IF_FAILED_RETURN(pAudioClient->GetService(_uuidof(IAudioCaptureClient), wil::out_param_ptr(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 pAudioClient; + wil::unique_cotaskmem_ptr 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(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 pCaptureClient; + IF_FAILED_RETURN(pAudioClient->GetService(__uuidof(IAudioCaptureClient), wil::out_param_ptr(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 client; + IF_FAILED_RETURN(pEndpoint->Activate(__uuidof(ISpatialAudioClient), CLSCTX_ALL, nullptr, wil::out_param_ptr(client))); + + com_ptr_nothrow enumer; + IF_FAILED_RETURN(client->GetSupportedAudioObjectFormatEnumerator(&enumer)); + + wil::unique_cotaskmem_ptr fmt; + IF_FAILED_RETURN(enumer->GetFormat(0, wil::out_param_ptr(fmt))); + + unique_event event; + event.create(); + if (event == nullptr) + { + HRESULT_FROM_WIN32(GetLastError()); + } + + unique_prop_variant pv; + auto params = reinterpret_cast(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(params); + + com_ptr_nothrow stream; + IF_FAILED_RETURN(client->ActivateSpatialAudioStream(&pv, __uuidof(ISpatialAudioObjectRenderStream), wil::out_param_ptr(stream))); + + com_ptr_nothrow 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(buffer), frameCount, 200.0f, fmt->nSamplesPerSec); + + totalFrameCount -= frameCount; + } + else + { + // Write audio data to the buffer + WriteToAudioObjectBuffer(reinterpret_cast(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 pAudioClient; + wil::unique_cotaskmem_ptr 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(mixFormat))); + + IF_FAILED_RETURN(pAudioClient->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + mixFormat.get(), + nullptr)); + + com_ptr_nothrow pRenderClient; + IF_FAILED_RETURN(pAudioClient->GetService(_uuidof(IAudioRenderClient), wil::out_param_ptr(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>& pEndpoints) +{ + const UINT num = static_cast(pEndpoints.size()) * NUMBER_OF_TESTS; + + std::vector 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 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 000000000..07e74ea97 --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/APOStressTest.h @@ -0,0 +1,50 @@ +#include + +#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>& 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 000000000..5600fb720 --- /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 apo; + if(SUCCEEDED(CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, __uuidof(IAudioProcessingObject), reinterpret_cast(&apo)))) + { + wil::com_ptr_nothrow capXAPO; + bIsCapXAPO = wil::try_com_query_to(apo, &capXAPO); + } + + return bIsCapXAPO; +} + +void GetClsidsFromVar(const PROPVARIANT& var, std::vector& guids, _Outptr_ PWSTR* ppszFormattedClsids, std::vector& 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 clsidsCompositeSfx; + wil::unique_cotaskmem_string clsidsCompositeSfxStr; + std::vector clsidsCompositeMfx; + wil::unique_cotaskmem_string clsidsCompositeMfxStr; + std::vector clsidsCompositeEfx; + wil::unique_cotaskmem_string clsidsCompositeEfxStr; + // // CapX composite effects + std::vector clsidsCapXCompositeSfx; + wil::unique_cotaskmem_string clsidsCapXCompositeSfxStr; + std::vector clsidsCapXCompositeMfx; + wil::unique_cotaskmem_string clsidsCapXCompositeMfxStr; + std::vector clsidsCapXCompositeEfx; + wil::unique_cotaskmem_string clsidsCapXCompositeEfxStr; + + wil::com_ptr_nothrow spMMEndpointInternal; + if (wil::try_com_query_to(pDevice, &spMMEndpointInternal)) + { + wil::com_ptr_nothrow 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>& capxEndpoints) +{ + UINT cDevices = 0; + com_ptr_nothrow spEnumerator; + com_ptr_nothrow 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 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 000000000..62a5807e4 --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXHelper.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "etwlistener.h" +#include +#include + +#include +#include +#include "TestMediaType.h" +#include + +#include +#include + +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& guids, _Outptr_ PWSTR* ppszFormattedClsids, std::vector& capxGuids, _Outptr_ PWSTR* ppszCapXFormattedClsids); +bool EndpointContainsCapx(IMMDevice* pDevice); +void EndpointsWithCapx(std::vector>& 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 000000000..85f14ad87 --- /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; +template +using unique_malloca_ptr = wistd::unique_ptr; +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 eventInfo(reinterpret_cast(_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(reinterpret_cast(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(reinterpret_cast(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(reinterpret_cast(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(&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 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 000000000..939b75542 --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXLoggingFrameworkTests.h @@ -0,0 +1,73 @@ +#include + + +#include +#include +#include +#include +#include +#include +#include +#include "etwlistener.h" +#include +#include + +#include +#include +#include "TestMediaType.h" +#include + +#include + +#include +#include + +#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> m_capxEndpoints; +}; diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.cpp b/audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.cpp new file mode 100644 index 000000000..e55f97648 --- /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; +template +using unique_malloca_ptr = wistd::unique_ptr; + +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 eventInfo(reinterpret_cast(_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(reinterpret_cast(eventInfo.get()) + eventInfo->EventNameOffset)) == 0) + { + m_countOfAudioProcessingNotifications++; + m_dLastPid = pEventRecord->EventHeader.ProcessId; + m_dLastThreadId = pEventRecord->EventHeader.ThreadId; + } + if (_wcsicmp(L"OnPropertyValueChanged", reinterpret_cast(reinterpret_cast(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(reinterpret_cast(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(reinterpret_cast(eventInfo.get()) + eventInfo->EventNameOffset)) == 0 || + _wcsicmp(L"RegisterEndpointNotificationCallbackWithVirtualEnumerator", reinterpret_cast(reinterpret_cast(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(reinterpret_cast(eventInfo.get()) + eventInfo->EventNameOffset)) == 0 || + _wcsicmp(L"UnregisterEndpointNotificationCallbackWithVirtualEnumerator", reinterpret_cast(reinterpret_cast(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(&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 pEndpoint = m_capxEndpoints.at(i); + + wil::unique_prop_variant var; + wil::com_ptr_nothrow 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 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 000000000..0b7244f2e --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXNotificationFrameworkTests.h @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "etwlistener.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include "etwlistener.h" +#include +#include + +#include +#include +#include "TestMediaType.h" +#include + +#include + +#include +#include + +#include "CAPXHelper.h" + +#include +#include +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> m_capxEndpoints; +}; diff --git a/audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.cpp b/audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.cpp new file mode 100644 index 000000000..e670518c1 --- /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; +template +using unique_malloca_ptr = wistd::unique_ptr; +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 eventInfo(reinterpret_cast(_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(reinterpret_cast(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(reinterpret_cast(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(&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 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 000000000..47b63766c --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/CAPXThreadingFrameworkTests.h @@ -0,0 +1,73 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "etwlistener.h" +#include +#include + +#include +#include +#include "TestMediaType.h" +#include + +#include + +#include +#include + +#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> m_capxEndpoints; +}; diff --git a/audio/tests/HLK/ThirdPartyApoTests/FormatHelpers.h b/audio/tests/HLK/ThirdPartyApoTests/FormatHelpers.h new file mode 100644 index 000000000..781b9ffb6 --- /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 +#include +#include +#include +#include +#include + +// 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 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 000000000..7b670d289 --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/TestEffectsList.cpp @@ -0,0 +1,225 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "avdevqueryhelpers.h" +#include + +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 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 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 spEndpoint; + VERIFY_SUCCEEDED(spDevices->Item(i, &spEndpoint)); + + wil::unique_prop_variant var; + wil::com_ptr_nothrow 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 audioEffectsManagerStatics = + wil::GetActivationFactory(RuntimeClass_Windows_Media_Effects_AudioEffectsManager); + + wil::com_ptr_nothrow> effects; + AUDIO_STREAM_CATEGORY audioCategory; + + if (dataFlow == eRender) + { + wil::com_ptr_nothrow 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 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 spAudioClient; + VERIFY_SUCCEEDED(spEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast(&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 mixFormat; + VERIFY_SUCCEEDED(spAudioClient->GetMixFormat(wil::out_param_ptr(mixFormat))); + + VERIFY_SUCCEEDED(spAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 20 * 10000, 0, mixFormat.get(), nullptr)); + + wil::com_ptr_nothrow audioEffectsManager; + VERIFY_SUCCEEDED(spAudioClient->GetService(IID_PPV_ARGS(&audioEffectsManager))); + VERIFY_IS_NOT_NULL(audioEffectsManager); + + wil::unique_cotaskmem_array_ptr 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 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 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 000000000..1627b728b --- /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 +#include + +//----------------------------------------------------------------------------- +// 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 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(&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 000000000..e213a7463 --- /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 + +//----------------------------------------------------------------------------- +// 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 000000000..3b573f874 --- /dev/null +++ b/audio/tests/HLK/ThirdPartyApoTests/TestWasapiUseFromAudioDg.cpp @@ -0,0 +1,244 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include "etwlistener.h" +#include + +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; +template +using unique_malloca_ptr = wistd::unique_ptr; + +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 eventInfo(reinterpret_cast(_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(reinterpret_cast(eventInfo.get()) + eventInfo->EventNameOffset)); + if (_wcsicmp(L"AudioClientInitialize", reinterpret_cast(reinterpret_cast(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(&parser))); + + // Enable Microsoft.Windows.Audio.Client + VERIFY_SUCCEEDED(listener.EnableProvider(AudioSesTelemetryProvider, TRACE_LEVEL_INFORMATION, 8)); + + wil::com_ptr_nothrow 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 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 pEndpoint; + VERIFY_SUCCEEDED(spDevices->Item(i, &pEndpoint)); + + wil::unique_prop_variant var; + wil::com_ptr_nothrow 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 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 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 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 pEndpoint; + VERIFY_SUCCEEDED(spDevices->Item(i, &pEndpoint)); + + wil::unique_prop_variant var; + wil::com_ptr_nothrow 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 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 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 000000000..82ceedc0c --- /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 000000000..771d03de8 --- /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(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(byteAlignmentMask)); + RETURN_HR_IF_NULL(E_OUTOFMEMORY, pAllocatedBuffer); + + pDescriptor->pBuffer = reinterpret_cast(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(pDescriptor->pBuffer); + RETURN_HR_IF_NULL(E_OUTOFMEMORY, pAllocatedBuffer); + + DeallocateAligned((void *) pAllocatedBuffer, static_cast(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 000000000..8f0fc03cf --- /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 + SineWave(_ty1 numChannels, _ty2 samplingRate) : _channel(unsigned(numChannels)), _sr(float(samplingRate)) {} + + template + inline void Initialize(_ty1 numChannels, _ty2 samplingRate) + { + _channel.resize(unsigned(numChannels)); + _sr = unsigned(samplingRate); + } + + template + 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 + void FillFrames(void* buffer, _ty1 numFrames) + { + auto fb = reinterpret_cast(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 _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 000000000..8a836cf0c --- /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 +#include + +#include +#include +#include + +#define NO_MOCK_VALIDATION +#include +#include +#include +#include +#include + +#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