diff options
| author | Daniel Rugerio <[email protected]> | 2021-08-13 15:05:12 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-08-13 15:05:12 -0700 |
| commit | 7895dd22785ddba5e973662ed942be3b3452b89d (patch) | |
| tree | 5c4572d5bfd076d3776a88c5757e37f3ed50c0e7 /audio/tests/PinResourceHelpers | |
| parent | df47b2d284558fa9aacd19257153037e4ebba60e (diff) | |
audio: Updates for Windows 10 22000 (#655)
* Updates for Windows 10 22000.
* Change how WIL is consumed.
Diffstat (limited to 'audio/tests/PinResourceHelpers')
| -rw-r--r-- | audio/tests/PinResourceHelpers/HalfApp.cpp | 2 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/HalfApp.h | 233 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/PreComp.h | 2 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/PropertyHelper.cpp | 486 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/PropertyHelper.h | 260 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/TestResource.cpp | 9 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/TestResource.h | 68 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/TestResourceBuild.cpp | 562 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/TestResourceHelper.h | 11 |
9 files changed, 1623 insertions, 10 deletions
diff --git a/audio/tests/PinResourceHelpers/HalfApp.cpp b/audio/tests/PinResourceHelpers/HalfApp.cpp index 83c9c8f3..154c1e9a 100644 --- a/audio/tests/PinResourceHelpers/HalfApp.cpp +++ b/audio/tests/PinResourceHelpers/HalfApp.cpp @@ -1,6 +1,6 @@ // ------------------------------------------------------------------------------ // -// Copyright (C) Microsoft. All rights reserved. +// Copyright (C) Microsoft Corporation. All rights reserved. // // Module Name: // diff --git a/audio/tests/PinResourceHelpers/HalfApp.h b/audio/tests/PinResourceHelpers/HalfApp.h new file mode 100644 index 00000000..a30bdf50 --- /dev/null +++ b/audio/tests/PinResourceHelpers/HalfApp.h @@ -0,0 +1,233 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (C) Microsoft. All rights reserved. +// +// File Name: +// +// HalfApp.h +// +// Abstract: +// +// CHalfApp declarations +// +// ------------------------------------------------------------------------------- +#pragma once + +#define BUF_LEN_IN_MS 1000 // 1 second + +// ------------------------------------------------------------------------------ +// Data flow +enum STACKWISE_DATAFLOW { render, capture }; + + +// ------------------------------------------------------------------------------ +// Structs used to hold results of format records +typedef struct +{ + WAVEFORMATEXTENSIBLE wfxEx; + UINT32 fundamentalPeriodInFrames; + UINT32 defaultPeriodInFrames; + UINT32 minPeriodInFrames; + UINT32 maxPeriodInFrames; + UINT32 maxPeriodInFramesExtended; +} FORMAT_RECORD, * PFORMAT_RECORD; + +struct DeviceDescriptor +{ + IMMDevice* pDevice; + LPWSTR pwstrAudioEndpointId; + LPWSTR pwstrAudioEndpointFriendlyName; + STACKWISE_DATAFLOW dataFlow; + EndpointConnectorType eConnectorType; + UINT uConnectorId; + GUID mode; + ULONG cModes; + GUID* pModes; + ULONG cFormatRecords; + PFORMAT_RECORD pFormatRecords; + PWAVEFORMATEX pPreferredFormat; + UINT32 u32DefaultPeriodicityInFrames; + UINT32 u32FundamentalPeriodicityInFrames; + UINT32 u32MinPeriodicityInFrames; + UINT32 u32MaxPeriodicityInFrames; + bool bIsPortCls; + bool bIsAVStream; + bool bIsBluetooth; + bool bIsSideband; + bool bIsMVA; +}; + +class CHalfApp +{ +public: + // Basic pin infos + wil::com_ptr_nothrow<IMMDevice> m_pDevice; + wil::unique_cotaskmem_string m_pwstrDeviceId; + wil::unique_cotaskmem_string m_pwstrDeviceFriendlyName; + STACKWISE_DATAFLOW m_DataFlow; + EndpointConnectorType m_ConnectorType; + UINT m_uConnectorId; + GUID m_Mode; + ULONG m_cModes; + GUID* m_pModes; // All processing modes + bool m_bIsPortCls; + bool m_bIsAVStream; + bool m_bIsBluetooth; + bool m_bIsSideband; + bool m_bIsMVA; + + // Formats + wil::unique_cotaskmem_ptr<WAVEFORMATEX> m_pPreferredFormat; + UINT32 m_u32DefaultPeriodicityInFrames; + UINT32 m_u32FundamentalPeriodicityInFrames; + UINT32 m_u32MinPeriodicityInFrames; + UINT32 m_u32MaxPeriodicityInFrames; + + PFORMAT_RECORD m_pFormatRecords; + ULONG m_cFormatRecords; + + wil::unique_cotaskmem_ptr<WAVEFORMATEX> m_pCurrentFormat; + UINT32 m_u32CurrentDefaultPeriodicityInFrames; + UINT32 m_u32CurrentFundamentalPeriodicityInFrames; + UINT32 m_u32CurrentMinPeriodicityInFrames; + UINT32 m_u32CurrentMaxPeriodicityInFrames; + + // Audio endpoint interfaces + wil::com_ptr_nothrow<IAudioDeviceEndpoint> m_pAudioDeviceEndpoint; + wil::com_ptr_nothrow<IAudioEndpoint> m_pAudioEndpoint; + wil::com_ptr_nothrow<IAudioEndpointControl> m_pAudioEndpointControl; + wil::com_ptr_nothrow<IAudioEndpointRT> m_pAudioEndpointRT; + wil::com_ptr_nothrow<IAudioClock> m_pAudioClock; + + // Data buffer + wil::unique_hlocal_ptr<BYTE> m_pbSineToneDataBuffer; + DWORD m_dwSineToneDataBufferSize; + DWORD m_dwSineToneDataBufferPosition; + + // Stream + HNSTIME m_hnsPeriod = 0; + FLOAT32 m_f32EndpointFrameRate = 0.F; + HNSTIME m_hnsEndpointLatency = 0; + bool m_bIsEventCapable = false; + bool m_bStreamInitialized = false; + volatile bool m_bStreamThreadTerminate = false; + bool m_bYieldActive = false; + bool m_bIsBackupTimerRequired = true; + + wil::unique_handle m_hStreamThread; + + LPTHREAD_START_ROUTINE m_pStreamRoutine = nullptr; + wil::unique_event_nothrow m_hProcessThreadStartedEvent; + wil::unique_event_nothrow m_hTerminate; + wil::unique_event_nothrow m_hEndpointBufferCompleteEvent; + wil::unique_handle m_hTimer; + + wil::critical_section m_CritSec; + + typedef enum + { + ET_DO_NOTHING = 0x00, + ET_TERMINATE = 0x01, + ET_OBJECT_BUFFER_COMPLETE = 0x02, + ET_CLIENT_RELEASE_BUFFER = 0x04, + ET_TIMER = 0x08, + ET_PAUSE_PUMP = 0x10, + ET_RESUME_PUMP = 0x20, + } TEventType; + + CHalfApp( + _In_ DeviceDescriptor descriptor + ) + : m_DataFlow(descriptor.dataFlow) + , m_ConnectorType(descriptor.eConnectorType) + , m_uConnectorId(descriptor.uConnectorId) + , m_cModes(descriptor.cModes) + , m_cFormatRecords(descriptor.cFormatRecords) + , m_u32DefaultPeriodicityInFrames(descriptor.u32DefaultPeriodicityInFrames) + , m_u32FundamentalPeriodicityInFrames(descriptor.u32FundamentalPeriodicityInFrames) + , m_u32MinPeriodicityInFrames(descriptor.u32MinPeriodicityInFrames) + , m_u32MaxPeriodicityInFrames(descriptor.u32MaxPeriodicityInFrames) + , m_u32CurrentDefaultPeriodicityInFrames(descriptor.u32DefaultPeriodicityInFrames) + , m_u32CurrentFundamentalPeriodicityInFrames(descriptor.u32FundamentalPeriodicityInFrames) + , m_u32CurrentMinPeriodicityInFrames(descriptor.u32MinPeriodicityInFrames) + , m_u32CurrentMaxPeriodicityInFrames(descriptor.u32MaxPeriodicityInFrames) + , m_bIsPortCls(descriptor.bIsPortCls) + , m_bIsAVStream(descriptor.bIsAVStream) + , m_bIsBluetooth(descriptor.bIsBluetooth) + , m_bIsSideband(descriptor.bIsSideband) + , m_bIsMVA(descriptor.bIsMVA) + { + m_pDevice = descriptor.pDevice; + m_pAudioDeviceEndpoint = nullptr; + m_pAudioEndpointControl = nullptr; + + m_pwstrDeviceId = wil::make_cotaskmem_string_nothrow(descriptor.pwstrAudioEndpointId, wcslen(descriptor.pwstrAudioEndpointId)); + m_pwstrDeviceFriendlyName = wil::make_cotaskmem_string_nothrow(descriptor.pwstrAudioEndpointFriendlyName, wcslen(descriptor.pwstrAudioEndpointFriendlyName)); + m_Mode = descriptor.mode; + + m_pModes = new GUID[descriptor.cModes]; + memcpy(m_pModes, descriptor.pModes, sizeof(GUID)*(descriptor.cModes)); + m_pFormatRecords = new FORMAT_RECORD[descriptor.cFormatRecords]; + memcpy(m_pFormatRecords, descriptor.pFormatRecords, sizeof(FORMAT_RECORD)*(descriptor.cFormatRecords)); + + CloneWaveFormat(descriptor.pPreferredFormat, wil::out_param(m_pPreferredFormat)); + CloneWaveFormat(descriptor.pPreferredFormat, wil::out_param(m_pCurrentFormat)); // Use preferred format as current format + } + + ~CHalfApp(void) + { + if (m_pModes) + delete[] m_pModes; + if (m_pFormatRecords) + delete[] m_pFormatRecords; + } + + // Audio endpoint interfaces + HRESULT InitializeEndpoint(); + HRESULT ReleaseEndpoint(); + HRESULT StartEndpoint(); + HRESULT StopEndpoint(); + HRESULT ResetEndpoint(); + + // Sine tone data buffer + HRESULT CreateSineToneDataBuffer(WAVEFORMATEX* pWfx); + HRESULT ReleaseSineToneDataBuffer(); + + + + + // Stream + HRESULT InitializeAndSetBuffer(HNSTIME hnsPeriod, UINT32 u32LatencyCoefficient); + HRESULT InitializeStream(HNSTIME requestedPeriodicity, UINT32 u32LatencyCoefficient); + HRESULT CleanupStream(); + HRESULT StartStream(); + HRESULT StopStream(); + static DWORD CALLBACK RenderStreamRoutine(PVOID lpParameter); + static DWORD CALLBACK CaptureStreamRoutine(PVOID lpParameter); + void SignalAndWaitForThread(); + + + + HRESULT SetTimer(HANDLE timer, HNSTIME timeDuration, bool fireImmediatley); + void CancelTimer(HANDLE timer); + HRESULT GetPosition(UINT64* pu64Position, UINT64* pu64hnsQPCPosition); + + // Multiple pin instances + HRESULT GetCurrentAvailiablePinInstanceCount(UINT32* pAvailablePinInstanceCount); + HRESULT GetSecondHalfApp(AUDIO_SIGNALPROCESSINGMODE secondMode, CHalfApp** ppSecondHalfApp); + + // Only for loopback testcases + HRESULT GetHostHalfApp(CHalfApp** ppHostHalfApp); +}; + +// {571BF784-A9D6-420A-BCA0-C579A8710236} +DEFINE_GUID(IID_IHalfAppContainer, + 0x571bf784, 0xa9d6, 0x420a, 0xbc, 0xa0, 0xc5, 0x79, 0xa8, 071, 0x02, 0x36); + +DECLARE_INTERFACE_IID_( +IHalfAppContainer, +IUnknown, +"571BF784-A9D6-420A-BCA0-C579A8710236") +{ + STDMETHOD(GetHalfApp)(THIS_ CHalfApp ** ppHalfApp) PURE; +}; diff --git a/audio/tests/PinResourceHelpers/PreComp.h b/audio/tests/PinResourceHelpers/PreComp.h index 85fc4f83..d75f0e3f 100644 --- a/audio/tests/PinResourceHelpers/PreComp.h +++ b/audio/tests/PinResourceHelpers/PreComp.h @@ -1,6 +1,6 @@ // ------------------------------------------------------------------------------ // -// Copyright (C) Microsoft. All rights reserved. +// Copyright (C) Microsoft Corporation. All rights reserved. // // File Name: // diff --git a/audio/tests/PinResourceHelpers/PropertyHelper.cpp b/audio/tests/PinResourceHelpers/PropertyHelper.cpp index 6e85e53e..1d5f4548 100644 --- a/audio/tests/PinResourceHelpers/PropertyHelper.cpp +++ b/audio/tests/PinResourceHelpers/PropertyHelper.cpp @@ -1,6 +1,6 @@ // ------------------------------------------------------------------------------ // -// Copyright (C) Microsoft. All rights reserved. +// Copyright (C) Microsoft Corporation. All rights reserved. // // Module Name: // @@ -16,6 +16,11 @@ #include <Functiondiscoverykeys_devpkey.h> #include <devpkey.h> #include <PropertyHelper.h> +#include <imagehlp.h> + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; /////////////////////////////////////////////////////////////////////////////////////////////////////////// // @@ -1099,6 +1104,355 @@ HRESULT GetAvailiablePinInstanceCount /////////////////////////////////////////////////////////////////////////////////////////////////////////// // +// GetDriverPathViaService +// +// Get the full path to the driver .sys file by the service name +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetDriverPathViaService +( + LPWSTR ServiceName, + LPWSTR DriverFullPath, + UINT cchFullPath +) +{ + HRESULT hr = S_OK; + wil::unique_schandle scManager; + wil::unique_schandle scService; + + scManager = wil::unique_schandle(OpenSCManagerW(nullptr, nullptr, GENERIC_READ)); + if (!VERIFY_IS_NOT_NULL(scManager.get())) + { + hr = E_OUTOFMEMORY; + return hr; + } + + scService = wil::unique_schandle(OpenServiceW(scManager.get(), ServiceName, GENERIC_READ)); + if (!VERIFY_IS_NOT_NULL(scService.get())) + { + hr = E_OUTOFMEMORY; + return hr; + } + + DWORD BytesRequired; + if (!VERIFY_IS_TRUE(!QueryServiceConfigW(scService.get(), nullptr, 0, &BytesRequired) && ERROR_INSUFFICIENT_BUFFER == GetLastError())) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + wil::unique_cotaskmem_ptr<BYTE> pBuff; + pBuff.reset((BYTE*)CoTaskMemAlloc(BytesRequired)); + if (!VERIFY_IS_NOT_NULL(pBuff)) + { + hr = E_OUTOFMEMORY; + return hr; + } + + LPQUERY_SERVICE_CONFIGW ServiceConfig = (LPQUERY_SERVICE_CONFIGW)pBuff.get(); + DWORD BytesReturned; + + if (!VERIFY_IS_TRUE(QueryServiceConfigW(scService.get(), ServiceConfig, BytesRequired, &BytesReturned))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + if (!VERIFY_IS_NOT_NULL(ServiceConfig->lpBinaryPathName)) + { + hr = E_UNEXPECTED; + return hr; + } + + if (!VERIFY_IS_TRUE('\0' != ServiceConfig->lpBinaryPathName[0])) + { + hr = E_UNEXPECTED; + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = GetFullPathFromImagePath(ServiceConfig->lpBinaryPathName, DriverFullPath, cchFullPath))) + { + return hr; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetFullPathFromImagePath +// +// Get the full driver path from the image path +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetFullPathFromImagePath +( + LPWSTR ImagePath, + LPWSTR DriverFullPath, + UINT cchFullPath +) +{ + HRESULT hr = S_OK; + // + // First, check if the ImagePath uses either of the well-known kernel + // DosDevices prefixes. If so, skip over those first since + // GetFileAttributes would succeed, leading us to think it is a valid DOS + // path, and GetFullPathName handles these incorrectly anyways. Don't + // bother handling the user DosDevices formats since it would be invalid to + // specify that format for the ImagePath of a kernel module. + // + LPWSTR pImagePath = (LPWSTR)ImagePath; + + const wchar_t* DosDevicesPath = L"\\DosDevices\\"; + const wchar_t* QuestionPath = L"\\??\\"; + if (_wcsnicmp(pImagePath, + DosDevicesPath, + static_cast<int>(wcslen(DosDevicesPath))) == 0) + { + pImagePath += wcslen(DosDevicesPath); + } + else if (_wcsnicmp(pImagePath, + QuestionPath, + static_cast<int>(wcslen(QuestionPath))) == 0) + { + pImagePath += wcslen(QuestionPath); + } + + // + // Check if the ImagePath happens to be a valid full path. + // + if (GetFileAttributesW(pImagePath) != 0xFFFFFFFF) + { + if (!VERIFY_IS_TRUE(GetFullPathNameW(pImagePath, cchFullPath, DriverFullPath, nullptr))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + return hr; + } + + // + // If the ImagePath starts with "\SystemRoot" or "%SystemRoot%" then + // remove those values. + // + LPWSTR pRelativePath = (LPWSTR)pImagePath; + + const wchar_t* SystemRootPath = L"\\SystemRoot\\"; + const wchar_t* SystemRootVariablePath = L"%SystemRoot%\\"; + + if (_wcsnicmp(pRelativePath, + SystemRootPath, + static_cast<int>(wcslen(SystemRootPath))) == 0) + { + pRelativePath += wcslen(SystemRootPath); + } + else if (_wcsnicmp(pRelativePath, + SystemRootVariablePath, + static_cast<int>(wcslen(SystemRootVariablePath))) == 0) + { + pRelativePath += wcslen(SystemRootVariablePath); + } + + // + // At this point pRelativePath should point to the image path relative to + // the windows directory. + // + WCHAR WindowsPath[MAX_PATH]; + if (!VERIFY_IS_TRUE(GetSystemWindowsDirectoryW(WindowsPath, MAX_PATH))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = StringCchPrintfW(DriverFullPath, cchFullPath, L"%s\\%s", WindowsPath, pRelativePath))) + { + return hr; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CheckImports +// +// Check if the driver imports a specific module +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT CheckImports +( + LPWSTR DriverPath, + LPSTR ModuleNameToCheck, + LPSTR MethodNameToCheck, + bool* pIsImported +) +{ + HRESULT hr = S_OK; + + *pIsImported = FALSE; + + CHAR szDriverPath[MAX_PATH]; + size_t bytesConverted = 0; + if (!VERIFY_IS_TRUE(0 == wcstombs_s(&bytesConverted, szDriverPath, MAX_PATH, DriverPath, _TRUNCATE))) + { + hr = E_FAIL; + return hr; + } + + LOADED_IMAGE image; + //Load the image + if (!VERIFY_IS_TRUE(MapAndLoad(szDriverPath, NULL, &image, TRUE, TRUE))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + auto imageCleanup = wil::scope_exit([&]() + { + (VOID)UnMapAndLoad(&image); + }); + + //Get the Import Directory + ULONG importDescriptorSize; + PIMAGE_SECTION_HEADER sectionHeader; + PIMAGE_IMPORT_DESCRIPTOR importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(image.MappedAddress, + FALSE, + IMAGE_DIRECTORY_ENTRY_IMPORT, + &importDescriptorSize, + §ionHeader); + if (!VERIFY_IS_NOT_NULL(importDescriptor)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + //Iterate through each directory entry + while (!(importDescriptor->Characteristics == 0 && + importDescriptor->TimeDateStamp == 0 && + importDescriptor->ForwarderChain == 0 && + importDescriptor->Name == 0 && + importDescriptor->FirstThunk == 0)) + { + CHAR* str = (PCHAR)ImageRvaToVa(ImageNtHeader(image.MappedAddress), + image.MappedAddress, + importDescriptor->Name, + NULL); + if (!str || !*str) + { + continue; + } + + if (_stricmp(str, ModuleNameToCheck) == 0) + { + // If a specific method name is provided, check for the specific method + if (MethodNameToCheck) + { + PIMAGE_THUNK_DATA thunk; + //Iterate through the INT(Import Name Table) + if (importDescriptor->OriginalFirstThunk == 0) + { + thunk = nullptr; + } + else + { + thunk = (PIMAGE_THUNK_DATA)ImageRvaToVa(ImageNtHeader(image.MappedAddress), + image.MappedAddress, + importDescriptor->OriginalFirstThunk, + NULL); + } + + while (thunk && thunk->u1.Ordinal != 0) + { + if (!IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal)) + { + PIMAGE_IMPORT_BY_NAME importName = (PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(ImageNtHeader(image.MappedAddress), + image.MappedAddress, + (ULONG)thunk->u1.ForwarderString, + NULL); + if (importName && _stricmp(importName->Name, MethodNameToCheck) == 0) + { + *pIsImported = TRUE; + } + } + + thunk++; + } + } + else + { + *pIsImported = TRUE; + } + + break; + } + + importDescriptor++; + } + + return hr; + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// IsPortCls +// +// Check if audio device is PortCls +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT IsPortCls +( + IMMDevice* pDevice, + bool* pIsPortCls +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDevice> spDevnodeDevice; + wil::com_ptr_nothrow<IPropertyStore> spPnpProperties; + wil::unique_prop_variant varDeviceService; + + *pIsPortCls = false; + + // Find the associated PnP device + if (!VERIFY_SUCCEEDED(hr = GetPnpDevnodeFromMMDevice(pDevice, &spDevnodeDevice))) { + return hr; + } + + // Open pnp device property store + if (!VERIFY_SUCCEEDED(hr = spDevnodeDevice->OpenPropertyStore(STGM_READ, &spPnpProperties))) { + return hr; + } + + // Read the DEVPKEY_Device_Service + if (!VERIFY_SUCCEEDED(hr = spPnpProperties->GetValue((REFPROPERTYKEY)DEVPKEY_Device_Service, &varDeviceService))) { + return hr; + } + + // If DEVPKEY_Device_Service is empty, it likely means a raw PDO, which means the device is handled by the parent FDO. + // For practical purposes, this would most likely mean a non-PortCls pin, which is what we were trying to determine, so it is OK. + if (varDeviceService.vt == VT_EMPTY) { + return S_OK; + } + + if (!VERIFY_IS_TRUE(varDeviceService.vt == VT_LPWSTR && varDeviceService.pwszVal != nullptr)) { + hr = E_FAIL; + return hr; + } + + WCHAR FullPath[MAX_PATH]; + if (!VERIFY_SUCCEEDED(hr = GetDriverPathViaService(varDeviceService.pwszVal, FullPath, MAX_PATH))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = CheckImports(FullPath, "portcls.sys", "PcNewPort", pIsPortCls))) { + return hr; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// // IsAVStream // // Check if audio device is AVStream @@ -1132,13 +1486,24 @@ HRESULT IsAVStream return hr; } + // If DEVPKEY_Device_Service is empty, it likely means a raw PDO, which means the device is handled by the parent FDO. + // For practical purposes, this would most likely mean a non-AVStream pin, which is what we were trying to determine, so it is OK. + if (varDeviceService.vt == VT_EMPTY) { + return S_OK; + } + if (!VERIFY_IS_TRUE(varDeviceService.vt == VT_LPWSTR && varDeviceService.pwszVal != nullptr)) { hr = E_FAIL; return hr; } - if (0 == _wcsicmp(varDeviceService.pwszVal, L"usbaudio") || 0 == _wcsicmp(varDeviceService.pwszVal, L"BthHFAud") || 0 == _wcsicmp(varDeviceService.pwszVal, L"BthA2dp")) { - *pIsAVStream = true; + WCHAR FullPath[MAX_PATH]; + if (!VERIFY_SUCCEEDED(hr = GetDriverPathViaService(varDeviceService.pwszVal, FullPath, MAX_PATH))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = CheckImports(FullPath, "ks.sys", nullptr, pIsAVStream))) { + return hr; } return hr; @@ -1222,6 +1587,78 @@ HRESULT IsSideband /////////////////////////////////////////////////////////////////////////////////////////////////////////// // +// IsMVA +// +// Check if audio device is side band +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT IsMVA +( + EndpointConnectorType eConnectorType, + IMMDevice* pDevice, + bool* pIsMVA +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDevice> adapterDevice; + wil::com_ptr_nothrow<IKsControl> ksControl; + ULONG ulReturned = 0; + ULONG propertySupportFlags = 0; + KSSOUNDDETECTORPROPERTY vam2Property = {0}; + + if (!VERIFY_IS_TRUE(pDevice != nullptr)) { + hr = E_FAIL; + return hr; + } + + if (!VERIFY_IS_TRUE(pIsMVA != nullptr)) { + hr = E_FAIL; + return hr; + } + + // If it isn't a keyword connector, it's not MVA, we're done. + if (eConnectorType != eKeywordDetectorConnector) + { + *pIsMVA = FALSE; + return S_OK; + } + + if (!VERIFY_SUCCEEDED(hr = GetAudioFilterAsDevice(pDevice, &adapterDevice))) + { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = adapterDevice->Activate(__uuidof(IKsControl), CLSCTX_ALL, NULL, (VOID**) &ksControl))) { + return hr; + } + + // At this point we know that it is a keyword detector connector and we have the required interface to call into the driver + // to see if it's MVA. If it's not MVA, it's SVA, so mark it as SVA here. + *pIsMVA = false; + + vam2Property.Property.Set = KSPROPSETID_SoundDetector2; + vam2Property.Property.Id = KSPROPERTY_SOUNDDETECTOR_SUPPORTEDPATTERNS; + vam2Property.Property.Flags = KSPROPERTY_TYPE_BASICSUPPORT; + vam2Property.EventId = GUID_NULL; + + hr = ksControl->KsProperty( + (PKSPROPERTY)&vam2Property, + sizeof(vam2Property), + &propertySupportFlags, + sizeof(propertySupportFlags), + &ulReturned); + if (SUCCEEDED(hr) && + propertySupportFlags == (KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_BASICSUPPORT)) + { + // Success, we've identified this as a connector which supports MVA, mark it. + *pIsMVA = true; + } + + return S_OK; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// // GetPnpDevnodeFromMMDeivce // // Get pnp devnode from mm device @@ -1261,3 +1698,46 @@ HRESULT GetPnpDevnodeFromMMDevice return hr; } + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// VerifyAllEndpointsPluggedIn +// +// Verify there is no unplugged endpoint +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +void VerifyAllEndpointsPluggedIn() +{ + wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator; + wil::com_ptr_nothrow<IMMDeviceCollection> spEndpoints; + UINT cDevices = 0; + + SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures); + DisableVerifyExceptions disable; + + VERIFY_SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED)); + + // Create IMMDevice Enumerator + VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void **)&spEnumerator)); + + // Enumerate all unplugged endpoints + VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eAll, DEVICE_STATE_UNPLUGGED, &spEndpoints)); + VERIFY_SUCCEEDED(spEndpoints->GetCount(&cDevices)); + + if (!VERIFY_IS_TRUE(cDevices == 0)) + { + Log::Comment(L"Following unplugged audio device(s) found, please plug in unplugged device(s)."); + + for (UINT i = 0; i < cDevices; i++) { + wil::com_ptr_nothrow<IMMDevice> spEndpoint; + wil::unique_cotaskmem_string id; + wil::unique_cotaskmem_string friendlyName; + + VERIFY_SUCCEEDED(spEndpoints->Item(i, &spEndpoint)); + VERIFY_SUCCEEDED(spEndpoint->GetId(&id)); + VERIFY_SUCCEEDED(GetEndpointFriendlyName(spEndpoint.get(), &friendlyName)); + + Log::Comment(String().Format(L"Device: %s (%s) is unplugged.", friendlyName.get(), id.get())); + } + } +} diff --git a/audio/tests/PinResourceHelpers/PropertyHelper.h b/audio/tests/PinResourceHelpers/PropertyHelper.h new file mode 100644 index 00000000..5680283d --- /dev/null +++ b/audio/tests/PinResourceHelpers/PropertyHelper.h @@ -0,0 +1,260 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (C) Microsoft. All rights reserved. +// +// Module Name: +// +// PropertyHelpers.h +// +// Abstract: +// +// Header for common helpers/defines of property +// +// ------------------------------------------------------------------------------- +#pragma once + +#include "HalfApp.h" + +#define kSystemDefaultPeriod 100000 // 10 milliseconds +#define kSystemMinPeriodForNonRTCapableEndpoints 20000 // 2 milliseconds + + + + + + + + + + + + + + + + + + + + + + + + + + + +// ---------------------------------------------------------------------- +HRESULT GetConnectorId +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + bool* hasConnector, + UINT* pConnectorId +); + +// ---------------------------------------------------------------------- +HRESULT GetEndpointFriendlyName +( + IMMDevice* pDevice, + LPWSTR* ppwszEndpointName +); + +// ---------------------------------------------------------------------- +HRESULT GetAudioFilterAsDevice +( + IMMDevice* pDevice, + IMMDevice** ppAudioFilterAsDevice +); + +// ---------------------------------------------------------------------- +HRESULT GetCachedProcessingModes +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + ULONG *pCount, + AUDIO_SIGNALPROCESSINGMODE **ppModes +); + + + + + + + + + + + + +// ---------------------------------------------------------------------- +HRESULT GetProcessingModes +( + IMMDevice* pDevice, + UINT pinId, + ULONG *pCount, + AUDIO_SIGNALPROCESSINGMODE **ppModes +); + +// ---------------------------------------------------------------------- +HRESULT GetCachedSupportedFormatRecords +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + ULONG *pCount, + FORMAT_RECORD **ppFormatRecords +); + + + + + + + + + + + + + +// ---------------------------------------------------------------------- +HRESULT GetSupportedFormatRecords +( + IMMDevice* pDevice, + UINT pinId, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + STACKWISE_DATAFLOW dataFlow, + ULONG *pCount, + FORMAT_RECORD **ppFormatRecords +); + +// ---------------------------------------------------------------------- +HRESULT IsFormatSupported +( + IMMDevice* pDevice, + UINT pinId, + WAVEFORMATEX *pWfx, + BOOL* pbSupported +); + +// ---------------------------------------------------------------------- +HRESULT DiscoverPeriodicityCharacteristicsForFormat +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + WAVEFORMATEX *pWfx, + STACKWISE_DATAFLOW dataFlow, + UINT32 *pDefaultPeriodicityInFrames, + UINT32 *pFundamentalPeriodicityInFrames, + UINT32 *pMinPeriodicityInFrames, + UINT32 *pMaxPeriodicityInFrames, + UINT32 *pMaxPeriodicityInFramesExtended +); + +// ---------------------------------------------------------------------- +HRESULT CheckConnectorSupportForPeriodicity +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + WAVEFORMATEX *pWfx, + STACKWISE_DATAFLOW dataFlow, + HNSTIME RequestedPeriodicity, + UINT32 *pActualPeriodicityInFrames +); + +// ---------------------------------------------------------------------- +HRESULT GetCachedDefaultFormat +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + WAVEFORMATEX** ppDefaultFormat +); + +// ---------------------------------------------------------------------- +HRESULT GetProposedFormatForProcessingMode +( + IMMDevice* pDevice, + UINT pinId, + AUDIO_SIGNALPROCESSINGMODE mode, + WAVEFORMATEX **ppProposedFormat +); + +// ---------------------------------------------------------------------- +HRESULT GetAvailiablePinInstanceCount +( + IMMDevice* pDevice, + UINT pinId, + UINT32* pAvailablePinInstanceCount +); + +// ------------------------------------------------------------------- +HRESULT GetDriverPathViaService +( + LPWSTR ServiceName, + LPWSTR DriverFullPath, + UINT cchFullPath +); + +// ------------------------------------------------------------------- +HRESULT GetFullPathFromImagePath +( + LPWSTR ImagePath, + LPWSTR DriverFullPath, + UINT cchFullPath +); + +// ------------------------------------------------------------------- +HRESULT CheckImports +( + LPWSTR DriverPath, + LPSTR ModuleNameToCheck, + LPSTR MethodNameToCheck, + bool* pIsImported +); + +// ---------------------------------------------------------------------- +HRESULT IsPortCls +( + IMMDevice* pDevice, + bool* pIsPortCls +); + +// ---------------------------------------------------------------------- +HRESULT IsAVStream +( + IMMDevice* pDevice, + bool* pIsAVStream +); + +// ---------------------------------------------------------------------- +HRESULT IsBluetooth +( + IMMDevice* pDevice, + bool* pIsBluetooth +); + +// ---------------------------------------------------------------------- +HRESULT IsSideband +( + IMMDevice* pDevice, + bool* pIsSideband +); + +// ---------------------------------------------------------------------- +HRESULT IsMVA +( + EndpointConnectorType eConnectorType, + IMMDevice* pDevice, + bool* pIsMVA +); + +// ---------------------------------------------------------------------- +HRESULT GetPnpDevnodeFromMMDevice +( + IMMDevice* pDevice, + IMMDevice** pDevnodeDevice +); diff --git a/audio/tests/PinResourceHelpers/TestResource.cpp b/audio/tests/PinResourceHelpers/TestResource.cpp index f3762b84..c7e06728 100644 --- a/audio/tests/PinResourceHelpers/TestResource.cpp +++ b/audio/tests/PinResourceHelpers/TestResource.cpp @@ -1,6 +1,6 @@ // ------------------------------------------------------------------------------ // -// Copyright (C) Microsoft. All rights reserved. +// Copyright (C) Microsoft Corporation. All rights reserved. // // File Name: // @@ -95,7 +95,7 @@ CPinTestResource::Initialize } szMode = ModeName(m_spHalfApp->m_Mode); - szPin = PinName(m_spHalfApp->m_ConnectorType); + szPin = PinName(m_spHalfApp->m_ConnectorType, m_spHalfApp->m_bIsMVA); // Id: Combine device Id, pin type and mode m_szId.Attach(W2BSTR(m_spHalfApp->m_pwstrDeviceId.get())); @@ -300,6 +300,7 @@ static const struct { AUDIO_SIGNALPROCESSINGMODE_MEDIA, L"MEDIA" }, { AUDIO_SIGNALPROCESSINGMODE_MOVIE, L"MOVIE" }, { AUDIO_SIGNALPROCESSINGMODE_NOTIFICATION, L"NOTIFICATION" }, + { AUDIO_SIGNALPROCESSINGMODE_FAR_FIELD_SPEECH, L"FAR_FIELD_SPEECH" }, }; @@ -313,7 +314,7 @@ LPWSTR CPinTestResource::ModeName(REFGUID guidMode) return L"UNKNOWN"; } -LPWSTR CPinTestResource::PinName(EndpointConnectorType eConnectorType) +LPWSTR CPinTestResource::PinName(EndpointConnectorType eConnectorType, bool IsMVA) { switch (eConnectorType) { @@ -324,7 +325,7 @@ LPWSTR CPinTestResource::PinName(EndpointConnectorType eConnectorType) case eLoopbackConnector: return L"LOOPBACK"; case eKeywordDetectorConnector: - return L"KEYWORD"; + return IsMVA?L"MVAKEYWORD":L"SVAKEYWORD"; default: return L"UNKNOWN"; } diff --git a/audio/tests/PinResourceHelpers/TestResource.h b/audio/tests/PinResourceHelpers/TestResource.h new file mode 100644 index 00000000..7bc6a792 --- /dev/null +++ b/audio/tests/PinResourceHelpers/TestResource.h @@ -0,0 +1,68 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (C) Microsoft. All rights reserved. +// +// File Name: +// +// TestResource.h +// +// Abstract: +// +// TAEF Test Resource +// +// ------------------------------------------------------------------------------- +#pragma once + +#include "HalfApp.h" + +class CPinTestResource : + public WEX::TestExecution::ITestResource, + public IHalfAppContainer +{ +public: + //IUnknown + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + STDMETHODIMP QueryInterface( + __in REFIID riid, + __deref_out VOID **ppvObject + ); + // ITestResouce + STDMETHODIMP GetGuid(GUID* pGuid); + STDMETHODIMP SetGuid(GUID guid); + STDMETHODIMP GetValue(BSTR name, BSTR* pValue); + STDMETHODIMP SetValue(BSTR name, BSTR value); + + //IHalfAppContainer + STDMETHODIMP GetHalfApp(CHalfApp ** ppHalfApp); + + static HRESULT STDMETHODCALLTYPE CreateInstance( + CHalfApp * pHalf, + REFGUID guid, + WEX::TestExecution::ITestResource ** ppOut + ); + +private: + + CPinTestResource(); + ~CPinTestResource(); + + HRESULT STDMETHODCALLTYPE Initialize( + CHalfApp * pHalf, + REFGUID guid + ); + + LPWSTR ModeName(REFGUID guidMode); + LPWSTR PinName(EndpointConnectorType eConnectorType, bool IsMVA); + + CComBSTR m_szType; + CComBSTR m_szName; + CComBSTR m_szId; + CComBSTR m_szMode; + CComBSTR m_szPin; + + CAutoPtr<CHalfApp> m_spHalfApp; + + ULONG m_cRef; + GUID m_guid; +}; diff --git a/audio/tests/PinResourceHelpers/TestResourceBuild.cpp b/audio/tests/PinResourceHelpers/TestResourceBuild.cpp new file mode 100644 index 00000000..02c1691d --- /dev/null +++ b/audio/tests/PinResourceHelpers/TestResourceBuild.cpp @@ -0,0 +1,562 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// +// File Name: +// +// TestResourceBuild.cpp +// +// Abstract: +// +// TAEF BuildResourceList implementation +// +// ------------------------------------------------------------------------------ +#include "PreComp.h" + +#include <Functiondiscoverykeys_devpkey.h> +#include <PropertyHelper.h> +#include <TestResource.h> + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CreateTestResource +// +// Create the test resource with MMDevice, device id, device name, data flow, connector type, connector id, mode, +// list of formats +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT CreateTestResource +( + ResourceList& resourceList, + DeviceDescriptor descriptor +) +{ + HRESULT hr = S_OK; + CComHeapPtr<CHalfApp> spHalfApp; + wil::com_ptr_nothrow<ITestResource> spTestResource; + GUID ResourceGUID; + CComBSTR szResourceName; + + // Create HalfApp + spHalfApp.Attach(new CHalfApp(descriptor)); + if (!VERIFY_IS_NOT_NULL(spHalfApp)) { + hr = E_OUTOFMEMORY; + return hr; + } + + // Create PinTestResource + if (!VERIFY_SUCCEEDED(hr = CoCreateGuid(&ResourceGUID))) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = CPinTestResource::CreateInstance( + spHalfApp, ResourceGUID, &spTestResource))) { + return hr; + } + + // Add to resource list + if (!VERIFY_SUCCEEDED(hr = spTestResource->GetValue(CComBSTR(TestResourceProperty::c_szName), &szResourceName))) { + return hr; + } + spHalfApp.Detach(); + if (!VERIFY_SUCCEEDED(hr = resourceList.Add(spTestResource.get()))) { + return hr; + } + Log::Comment(String().Format(L"Test Resource (%s) added", (PCWSTR)szResourceName)); + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetProcessingModesForConnector +// +// For host/keyword detector pin, processing mode info is cached in property store and can be directly read. For offload +// pin, query for ks processing mode property. For loopback pin, it doesn't support any processing modes, return GUID_NULL. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetProcessingModesForConnector +( + IMMDevice* pDevice, + UINT uConnectorId, + EndpointConnectorType eConnectorType, + ULONG *pCount, + AUDIO_SIGNALPROCESSINGMODE **ppModes +) +{ + HRESULT hr = S_OK; + + *pCount = 0; + *ppModes = NULL; + + if (eConnectorType == eHostProcessConnector || eConnectorType == eKeywordDetectorConnector) { + if (!VERIFY_SUCCEEDED(hr = GetCachedProcessingModes(pDevice, eConnectorType, pCount, ppModes))) { + return hr; + } + } + else if (eConnectorType == eOffloadConnector) { + if (!VERIFY_SUCCEEDED(hr = GetProcessingModes(pDevice, uConnectorId, pCount, ppModes))) { + return hr; + } + } + else if (eConnectorType == eLoopbackConnector) { + // Loopback pin doesn't support any processing modes, so put 1 GUID_NULL + CComHeapPtr<AUDIO_SIGNALPROCESSINGMODE> spModes; + + if (!spModes.Allocate(1)) + return E_OUTOFMEMORY; + + spModes[0] = GUID_NULL; + + *pCount = 1; + *ppModes = spModes.Detach(); + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetDefaultFormatForConnector +// +// Read audio engine device format from property store as default format. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetDefaultFormatForConnector +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + WAVEFORMATEX **ppDefaultFormat +) +{ + HRESULT hr = S_OK; + + *ppDefaultFormat = NULL; + if (!VERIFY_SUCCEEDED(hr = GetCachedDefaultFormat(pDevice, eConnectorType, ppDefaultFormat))){ + return hr; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetSupportedFormatsForConnector +// +// For host/keyword detector pin, supported formats info is cached in property store and can be directly read. For offload +// pin, provide with a predefined list of formats and check whether format is supported. For loopback pin, it matches the format +// of host pin so put 0 format record. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetSupportedFormatRecordsForConnector +( + IMMDevice* pDevice, + UINT uConnectorId, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + STACKWISE_DATAFLOW dataFlow, + ULONG *pCount, + FORMAT_RECORD **ppFormatRecords +) +{ + HRESULT hr = S_OK; + + *pCount = 0; + *ppFormatRecords = NULL; + + if (eConnectorType == eHostProcessConnector || eConnectorType == eKeywordDetectorConnector) { + if (!VERIFY_SUCCEEDED(hr = GetCachedSupportedFormatRecords(pDevice, eConnectorType, mode, pCount, ppFormatRecords))) { + return hr; + } + } + else if (eConnectorType == eOffloadConnector) { + if (!VERIFY_SUCCEEDED(hr = GetSupportedFormatRecords(pDevice, uConnectorId, eConnectorType, mode, dataFlow, pCount, ppFormatRecords))) { + return hr; + } + } + else if (eConnectorType == eLoopbackConnector) { + *pCount = 0; + *ppFormatRecords = NULL; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetPreferredFormatForConnector +// +// If KSPROPERTY_PIN_PROPOSEDATAFORMAT2 is supported, use the proposed format for mode as preferred format for mode. +// Otherwise, use default format as preferred format. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetPreferredFormatForConnector +( + IMMDevice* pDevice, + UINT uConnectorId, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + WAVEFORMATEX **ppPreferredFormat +) +{ + HRESULT hr = S_OK; + wil::unique_cotaskmem_ptr<WAVEFORMATEX> pDefaultFormat; + wil::unique_cotaskmem_ptr<WAVEFORMATEX> pProposedFormat; + + *ppPreferredFormat = NULL; + + + // Get default format for connector + if (!VERIFY_SUCCEEDED(hr = GetDefaultFormatForConnector(pDevice, eConnectorType, wil::out_param(pDefaultFormat)))) { + return hr; + } + + // Return the processing mode specific format proposed by the driver + hr = GetProposedFormatForProcessingMode(pDevice, uConnectorId, mode, wil::out_param(pProposedFormat)); + if (hr == S_OK) { + CloneWaveFormat(pProposedFormat.get(), ppPreferredFormat); + } + else { + CloneWaveFormat(pDefaultFormat.get(), ppPreferredFormat); + hr = S_OK; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetPreferredFormatPeriodicityCharacteristicsForConnector +// +// Get periodicity characteristics for format. For host/keyword detector pin, periodicity info is cached along with format +// in property store and can be searched from supported format list. For offload pin, it should be calculate from +// DiscoverPeriodicityCharacteristicsForFormat. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetPreferredFormatPeriodicityCharacteristicsForConnector +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + STACKWISE_DATAFLOW dataFlow, + WAVEFORMATEX *pPreferredFormat, + ULONG cFormatRecords, + PFORMAT_RECORD pFormatRecords, + UINT32 *pDefaultPeriodicityInFrames, + UINT32 *pFundamentalPeriodicityInFrames, + UINT32 *pMinPeriodicityInFrames, + UINT32 *pMaxPeriodicityInFrames, + UINT32 *pMaxPeriodicityInFramesExtended +) +{ + HRESULT hr = S_OK; + bool bFormatInList = false; + + for (ULONG i = 0; i < cFormatRecords; i++) { + if (CompareWaveFormat((WAVEFORMATEX *)&pFormatRecords[i].wfxEx, pPreferredFormat)) { + bFormatInList = true; + *pDefaultPeriodicityInFrames = pFormatRecords[i].defaultPeriodInFrames; + *pFundamentalPeriodicityInFrames = pFormatRecords[i].fundamentalPeriodInFrames; + *pMinPeriodicityInFrames = pFormatRecords[i].minPeriodInFrames; + *pMaxPeriodicityInFrames = pFormatRecords[i].maxPeriodInFrames; + *pMaxPeriodicityInFramesExtended = pFormatRecords[i].maxPeriodInFramesExtended; + break; + } + } + + if (eConnectorType == eHostProcessConnector || eConnectorType == eKeywordDetectorConnector) { + if (!VERIFY_IS_TRUE(bFormatInList)) { + hr = E_NOTFOUND; + return hr; + } + } + else if (eConnectorType == eOffloadConnector || eConnectorType == eLoopbackConnector) { + if (!VERIFY_SUCCEEDED(hr = DiscoverPeriodicityCharacteristicsForFormat(pDevice, eConnectorType, mode, pPreferredFormat, dataFlow, pDefaultPeriodicityInFrames, pFundamentalPeriodicityInFrames, pMinPeriodicityInFrames, pMaxPeriodicityInFrames, pMaxPeriodicityInFramesExtended))) { + return hr; + } + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// AddTestResourceForConnector +// +// For each connector, read connector id, get all processing modes, identify default and preferred format for each mode +// and also enumerate a list of supported formats for each mode. Store all the infos in test resource. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT AddTestResourceForConnector +( + ResourceList& resourceList, + LPWSTR deviceId, + LPWSTR deviceName, + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + STACKWISE_DATAFLOW dataFlow +) +{ + HRESULT hr = S_OK; + bool bHasConnector = false; + UINT uConnectorId; + ULONG cModes = 0; + CComHeapPtr<AUDIO_SIGNALPROCESSINGMODE> spModes; + AUDIO_SIGNALPROCESSINGMODE mode; + ULONG cFormatRecords = 0; + CComHeapPtr<FORMAT_RECORD> spFormatRecords; + wil::unique_cotaskmem_ptr<WAVEFORMATEX> pPreferredFormat; + UINT32 u32DefaultPeriodicityInFrames; + UINT32 u32FundamentalPeriodicityInFrames; + UINT32 u32MinPeriodicityInFrames; + UINT32 u32MaxPeriodicityInFrames; + UINT32 u32MaxPeriodicityInFramesExtended; + bool isPortCls = false; + bool isAVStream = false; + bool isBluetooth = false; + bool isSideband = false; + bool isMVA = false; + + // Get connector id + if (!VERIFY_SUCCEEDED(hr = GetConnectorId(pDevice, eConnectorType, &bHasConnector, &uConnectorId))) { + return hr; + } + if (!bHasConnector) { + return hr; + } + + Log::Comment(String().Format(L"Adding Test Resource for pin [%u]:", (uConnectorId & PARTID_MASK))); + + + // Get all signal processing modes for connector + if (!VERIFY_SUCCEEDED(hr = GetProcessingModesForConnector(pDevice, uConnectorId, eConnectorType, &cModes, &spModes))) { + return hr; + } + + // Loop through each mode, get preferred format and list of formats and create test resource for each mode + for (ULONG i = 0; i < cModes; i++) { + + mode = spModes[i]; + + if (!VERIFY_SUCCEEDED(hr = GetSupportedFormatRecordsForConnector(pDevice, uConnectorId, eConnectorType, mode, dataFlow, &cFormatRecords, &spFormatRecords))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = GetPreferredFormatForConnector(pDevice, uConnectorId, eConnectorType, mode, wil::out_param(pPreferredFormat)))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = GetPreferredFormatPeriodicityCharacteristicsForConnector(pDevice, eConnectorType, mode, dataFlow, pPreferredFormat.get(), cFormatRecords, spFormatRecords, &u32DefaultPeriodicityInFrames, &u32FundamentalPeriodicityInFrames, &u32MinPeriodicityInFrames, &u32MaxPeriodicityInFrames, &u32MaxPeriodicityInFramesExtended))) { + return hr; + } + + // Check if audio endpoint is PortCls + if (!VERIFY_SUCCEEDED(hr = IsPortCls(pDevice, &isPortCls))) { + return hr; + } + + // Check if audio endpoint is AVStream + if (!VERIFY_SUCCEEDED(hr = IsAVStream(pDevice, &isAVStream))) { + return hr; + } + + // Check if audio endpoint is Bluetooth + if (!VERIFY_SUCCEEDED(hr = IsBluetooth(pDevice, &isBluetooth))) { + return hr; + } + + // Check if audio endpoint is side band + if (!VERIFY_SUCCEEDED(hr = IsSideband(pDevice, &isSideband))) { + return hr; + } + + // Check if audio endpoint is MVA + if (!VERIFY_SUCCEEDED(hr = IsMVA(eConnectorType, pDevice, &isMVA))) { + return hr; + } + + DeviceDescriptor descriptor = { 0 }; + descriptor.pDevice = pDevice; + descriptor.pwstrAudioEndpointId = deviceId; + descriptor.pwstrAudioEndpointFriendlyName = deviceName; + descriptor.dataFlow = dataFlow; + descriptor.eConnectorType = eConnectorType; + descriptor.uConnectorId = uConnectorId; + descriptor.mode = mode; + descriptor.cModes = cModes; + descriptor.pModes = spModes; + descriptor.cFormatRecords = cFormatRecords; + descriptor.pFormatRecords = spFormatRecords; + descriptor.pPreferredFormat = pPreferredFormat.get(); + descriptor.u32DefaultPeriodicityInFrames = u32DefaultPeriodicityInFrames; + descriptor.u32FundamentalPeriodicityInFrames = u32FundamentalPeriodicityInFrames; + descriptor.u32MinPeriodicityInFrames = u32MinPeriodicityInFrames; + descriptor.u32MaxPeriodicityInFrames = u32MaxPeriodicityInFrames; + descriptor.bIsPortCls = isPortCls; + descriptor.bIsAVStream = isAVStream; + descriptor.bIsBluetooth = isBluetooth; + descriptor.bIsSideband = isSideband; + descriptor.bIsMVA = isMVA; + + if (!VERIFY_SUCCEEDED(hr = CreateTestResource(resourceList, descriptor))) { + return hr; + } + + spFormatRecords.Free(); + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// AddTestResourcesForDevice +// +// Identify the existence of all pin types and add test resources for each pin +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT AddTestResourcesForDevice +( + ResourceList& resourceList, + LPWSTR deviceId, + LPWSTR deviceName, + STACKWISE_DATAFLOW dataFlow +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator; + wil::com_ptr_nothrow<IMMDevice> spDevice; + + Log::Comment(String().Format(L"Adding Test Resource for Device [%s]:", deviceName)); + + // Read the default device format from the property store + if (!VERIFY_SUCCEEDED(hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void **)&spEnumerator))) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = spEnumerator->GetDevice(deviceId, &spDevice))) { + return hr; + } + + // Identify existence of host pin. Add test resources for host pin. + if (!VERIFY_SUCCEEDED(hr = AddTestResourceForConnector(resourceList, deviceId, deviceName, spDevice.get(), eHostProcessConnector, dataFlow))) { + return hr; + } + + // Identify existence of offload pin. Add test resources for offload pin. + if (!VERIFY_SUCCEEDED(hr = AddTestResourceForConnector(resourceList, deviceId, deviceName, spDevice.get(), eOffloadConnector, dataFlow))) { + return hr; + } + + // Identify existence of loopback pin. Add test resources for loopback pin. + if (!VERIFY_SUCCEEDED(hr = AddTestResourceForConnector(resourceList, deviceId, deviceName, spDevice.get(), eLoopbackConnector, capture ))) { + return hr; + } + + // Identify existence of keyword detector pin. Add test resources for keyword detector pin. + if (!VERIFY_SUCCEEDED(hr = AddTestResourceForConnector(resourceList, deviceId, deviceName, spDevice.get(), eKeywordDetectorConnector, dataFlow))) { + return hr; + } + + return hr; +} + +HRESULT AddDevices(ResourceList& resourceList) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator; + wil::com_ptr_nothrow<IMMDeviceCollection> spEndpoints; + wil::com_ptr_nothrow<IMMDevice> spEndpoint; + UINT cDevices = 0; + UINT i = 0; + wil::unique_cotaskmem_string id; + wil::unique_cotaskmem_string friendlyName; + EDataFlow endpointDataFlow; + + SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures); + DisableVerifyExceptions disable; + + VERIFY_SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED)); + + Log::Comment(L"In BuildResourceList"); + + // Create IMMDevice Enumerator + VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void **)&spEnumerator)); + + // Enumerate all endpoints + VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eAll, DEVICE_STATE_ACTIVE, &spEndpoints)); + VERIFY_SUCCEEDED(spEndpoints->GetCount(&cDevices)); + + if (!VERIFY_IS_TRUE(cDevices)) + { + Log::Comment(L"No device was found!"); + return E_FAIL; + } + + // Check if a device ID was specified. + String instanceId; + BOOL isDeviceSelected = false; + HRESULT res = RuntimeParameters::TryGetValue(L"InstanceId", instanceId); + if (res == S_OK) + { + Log::Comment(String().Format(L"Selected device ID: %s", static_cast<LPCWSTR>(instanceId))); + isDeviceSelected = true; + } + + Log::Comment(String().Format(L"Found %d viable endpoint(s)!", cDevices)); + // Add test resources for endpoints + for (i = 0; i < cDevices; i++) { + VERIFY_SUCCEEDED(spEndpoints->Item(i, &spEndpoint)); + VERIFY_SUCCEEDED(spEndpoint->GetId(&id)); + VERIFY_SUCCEEDED(GetEndpointFriendlyName(spEndpoint.get(), &friendlyName)); + + // Check whether it is a render or capture endpoint. + CComPtr<IMMEndpoint> pMmEndpoint; + VERIFY_SUCCEEDED(spEndpoint->QueryInterface(__uuidof(IMMEndpoint), (void**)&pMmEndpoint)); + pMmEndpoint->GetDataFlow(&endpointDataFlow); + + // If there is a device selected, check if the current endpoint belongs to the device. + if (isDeviceSelected) + { + // Get the IDeviceTopology interface of the endpoint. + CComPtr<IDeviceTopology> pEndpointTopology; + if (!VERIFY_SUCCEEDED(hr = spEndpoint->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&pEndpointTopology))) { return hr; } + + // Get the connector inside the device topology object. + CComPtr<IConnector> spConnector; + if (!VERIFY_SUCCEEDED(hr = pEndpointTopology->GetConnector(0, &spConnector))) { return hr; } + + // Get the id of the device adapter this endpoint is connected to. + CComHeapPtr<WCHAR> szFilterId; + if (!VERIFY_SUCCEEDED(hr = spConnector->GetDeviceIdConnectedTo(&szFilterId))) { return hr; } + + // Get the IMMDevice object of this device adapter. + CComPtr<IMMDevice> spDevnode; + if (!VERIFY_SUCCEEDED(hr = spEnumerator->GetDevice(szFilterId, &spDevnode))) { return hr; } + + // Open the property store and get the instance ID of the device adapter. + PROPVARIANT varInstanceId; + PropVariantInit(&varInstanceId); + + CComPtr<IPropertyStore> spDevnodePropertyStore; + if (!VERIFY_SUCCEEDED(hr = spDevnode->OpenPropertyStore(STGM_READ, &spDevnodePropertyStore))) { return hr; } + + if (!VERIFY_SUCCEEDED(hr = spDevnodePropertyStore->GetValue(PKEY_Device_InstanceId, &varInstanceId))) { return hr; } + if (!VERIFY_ARE_EQUAL(varInstanceId.vt, VT_LPWSTR)) { return E_UNEXPECTED; } + + // If this endpoint does not belong to the selected device, skip it. + if (0 != _wcsicmp(instanceId, varInstanceId.pwszVal)) + { + continue; + } + } + Log::Comment(String().Format(L"\\\\ Device: %s (%s)", friendlyName.get(), id.get())); + VERIFY_SUCCEEDED(AddTestResourcesForDevice(resourceList, id.get(), friendlyName.get(), endpointDataFlow == eRender ? render : capture)); + } + + Log::Comment(String().Format(L"Enumerated %u resources", resourceList.Count())); + + return hr; +} diff --git a/audio/tests/PinResourceHelpers/TestResourceHelper.h b/audio/tests/PinResourceHelpers/TestResourceHelper.h index 08fafa23..df5b8720 100644 --- a/audio/tests/PinResourceHelpers/TestResourceHelper.h +++ b/audio/tests/PinResourceHelpers/TestResourceHelper.h @@ -1,6 +1,6 @@ // ------------------------------------------------------------------------------ // -// Copyright (C) Microsoft. All rights reserved. +// Copyright (C) Microsoft Corporation. All rights reserved. // // Module Name: // @@ -71,3 +71,12 @@ HRESULT GetSupportedFormatRecordsForConnector ULONG* pCount, FORMAT_RECORD** ppFormatRecords ); + +// ---------------------------------------------------------------------- +void VerifyAllEndpointsPluggedIn(); + +// ---------------------------------------------------------------------- +HRESULT AddDevices +( + WEX::TestExecution::ResourceList& resourceList +); |
