diff options
| author | Daniel Rugerio <[email protected]> | 2020-05-13 16:23:40 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2020-05-13 16:23:40 -0700 |
| commit | bf0b0fe11d8b301cdfcc6d13f12a778cdd1c14b5 (patch) | |
| tree | 55938bebdfe634226f0c1ee7822cbcdb4879f975 /audio/tests/PinResourceHelpers | |
| parent | b78312cfe7990524cb46a1453ad2e013586857a0 (diff) | |
Update to SysVAD, Wave test and public sample of KS Position Test (#496)147237
* Update to Wave test and public sample of KS Position test.
* Update to the SysVAD audio driver sample.
* Add the PnpLockDown property.
Diffstat (limited to 'audio/tests/PinResourceHelpers')
| -rw-r--r-- | audio/tests/PinResourceHelpers/HalfApp.cpp | 1042 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/PreComp.h | 143 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/PropertyHelper.cpp | 1263 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/TestResource.cpp | 331 | ||||
| -rw-r--r-- | audio/tests/PinResourceHelpers/TestResourceHelper.h | 73 |
5 files changed, 2852 insertions, 0 deletions
diff --git a/audio/tests/PinResourceHelpers/HalfApp.cpp b/audio/tests/PinResourceHelpers/HalfApp.cpp new file mode 100644 index 00000000..83c9c8f3 --- /dev/null +++ b/audio/tests/PinResourceHelpers/HalfApp.cpp @@ -0,0 +1,1042 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (C) Microsoft. All rights reserved. +// +// Module Name: +// +// HalfApp.cpp +// +// Abstract: +// +// Implementation for CHalfApp class +// +// ------------------------------------------------------------------------------- + + + + + + + + + + + + +HRESULT +CHalfApp::InitializeEndpoint +( + void +) +{ + HRESULT hr = S_OK; + + + + wil::unique_prop_variant varActivationParameter; + + + + WAVEFORMATEX* pWfx = m_pCurrentFormat.get(); + + if (!VERIFY_IS_NOT_NULL(pWfx)) { + hr = E_NOTFOUND; + return hr; + } + + + + + + + + + + // Allocate a structure for endpoint creation. + varActivationParameter.vt = VT_BLOB; + + // Fill the endpoint creation structure... + + + + + + + + + + + + + + + + + + // Activate the endpoint + if (m_DataFlow == render) { + wil::com_ptr_nothrow<IAudioOutputEndpointRT> spAudioOutputEndpointRT; + if (!VERIFY_SUCCEEDED(hr = m_pDevice->Activate( + __uuidof(IAudioOutputEndpointRT), + CLSCTX_INPROC_SERVER, + &varActivationParameter, + reinterpret_cast<void**>(&spAudioOutputEndpointRT)))) { + return hr; + } + spAudioOutputEndpointRT.query_to(&m_pAudioDeviceEndpoint); + if (!VERIFY_IS_NOT_NULL(m_pAudioDeviceEndpoint)) { + hr = E_FAIL; + return hr; + } + } + else { + wil::com_ptr_nothrow<IAudioInputEndpointRT> spAudioInputEndpointRT; + if (!VERIFY_SUCCEEDED(hr = m_pDevice->Activate( + __uuidof(IAudioInputEndpointRT), + CLSCTX_INPROC_SERVER, + &varActivationParameter, + reinterpret_cast<void**>(&spAudioInputEndpointRT)))) { + return hr; + } + spAudioInputEndpointRT.query_to(&m_pAudioDeviceEndpoint); + if (!VERIFY_IS_NOT_NULL(m_pAudioDeviceEndpoint)) { + hr = E_FAIL; + return hr; + } + } + + // Initialize other endpoint interfaces + if (!VERIFY_SUCCEEDED(hr = m_pAudioDeviceEndpoint.query_to(&m_pAudioEndpointControl))) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = m_pAudioDeviceEndpoint.query_to(&m_pAudioEndpoint))) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = m_pAudioDeviceEndpoint.query_to(&m_pAudioEndpointRT))) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = m_pAudioDeviceEndpoint.query_to(&m_pAudioClock))) { + return hr; + } + + return hr; +} + +HRESULT +CHalfApp::ReleaseEndpoint +( + void +) +{ + HRESULT hr = S_OK; + + if (m_pAudioEndpointControl) { + m_pAudioEndpointControl->Reset(); + } + + m_pAudioDeviceEndpoint.reset(); + m_pAudioEndpointControl.reset(); + m_pAudioEndpoint.reset(); + m_pAudioEndpointRT.reset(); + m_pAudioClock.reset(); + + return hr; +} + +HRESULT +CHalfApp::StartEndpoint +( + void +) +{ + HRESULT hr = S_OK; + auto lock = m_CritSec.lock(); + + if (!VERIFY_SUCCEEDED(hr = m_pAudioEndpointControl->Start())) { + return hr; + } + + return hr; +} + +HRESULT +CHalfApp::StopEndpoint +( + void +) +{ + HRESULT hr = S_OK; + auto lock = m_CritSec.lock(); + + if (!VERIFY_SUCCEEDED(hr = m_pAudioEndpointControl->Stop())) { + return hr; + } + + return hr; +} + +HRESULT +CHalfApp::ResetEndpoint +( + void +) +{ + HRESULT hr = S_OK; + auto lock = m_CritSec.lock(); + + if (!VERIFY_SUCCEEDED(hr = m_pAudioEndpointControl->Reset())) { + return hr; + } + + return hr; +} + +//////////////////////////////////////////////////////////////////////////////////// +// +// Creates a pre-load data buffer used by the stream render and capture routines. +// +// If the current endpoint is a render one, the data buffer consists of a sine tone +// wich gets loaded into a stream render routine. +// +// In the case of a capture endpoint, the pre-load data consists of silence. +// +//////////////////////////////////////////////////////////////////////////////////// +HRESULT +CHalfApp::CreateSineToneDataBuffer +( + WAVEFORMATEX* pWfx +) +{ + HRESULT hr = S_OK; + + if (!VERIFY_IS_NOT_NULL(pWfx)) { + hr = E_FAIL; + return hr; + } + + m_dwSineToneDataBufferSize = (DWORD)(((ULONGLONG)BUF_LEN_IN_MS * pWfx->nSamplesPerSec / 1000) * pWfx->nBlockAlign); + m_pbSineToneDataBuffer.reset((BYTE*)LocalAlloc(LPTR, m_dwSineToneDataBufferSize)); + if (!VERIFY_IS_NOT_NULL(m_pbSineToneDataBuffer)) { + hr = E_OUTOFMEMORY; + return hr; + } + + if (m_DataFlow == render) // Sine tone for render + { + if (!VERIFY_SUCCEEDED(hr = FillBufferWithSineSignal( + g_pBasicLog, XFAIL, + pWfx, + TEST_AMPLITUDE, // amplitude + TEST_FREQUENCY, // frequency + 0.0, // initial phase, + 0.0, // dc + Method_NoDithering, + m_pbSineToneDataBuffer.get(), + m_dwSineToneDataBufferSize / pWfx->nBlockAlign, + m_dwSineToneDataBufferSize + ))) { + return hr; + } + + m_dwSineToneDataBufferPosition = 0; + } + else // Silence for capture + { + memset(m_pbSineToneDataBuffer.get(), 0, m_dwSineToneDataBufferSize); + m_dwSineToneDataBufferPosition = 0; + } + + return hr; +} + +////////////////////////////////// +// +// Clears the current data buffer. +// +////////////////////////////////// +HRESULT +CHalfApp::ReleaseSineToneDataBuffer +( + void +) +{ + m_pbSineToneDataBuffer = nullptr; + m_dwSineToneDataBufferSize = 0; + m_dwSineToneDataBufferPosition = 0; + + return S_OK; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +HRESULT +CHalfApp::InitializeAndSetBuffer +( + HNSTIME requestedPeriodicity, + UINT32 u32LatencyCoefficient +) +{ + HRESULT hr = S_OK; + BOOL bIsEventCapable = false; + + // If no requested periodicity is provided, use default. + if (requestedPeriodicity == 0) { + m_hnsPeriod = FRAMES_TO_HNSTIME_DOUBLE(m_u32CurrentDefaultPeriodicityInFrames, m_pCurrentFormat->nSamplesPerSec); + } + else { + m_hnsPeriod = requestedPeriodicity; + } + + if (m_ConnectorType == eOffloadConnector) { + bIsEventCapable = true; + } + else { + if (!VERIFY_SUCCEEDED(hr = m_pAudioDeviceEndpoint->GetEventDrivenCapable(&bIsEventCapable))) { + return hr; + } + } + + if (bIsEventCapable) + { + if (!VERIFY_SUCCEEDED(hr = m_pAudioEndpoint->SetStreamFlags(AUDCLNT_STREAMFLAGS_EVENTCALLBACK))) { + return hr; + } + } + + if (!VERIFY_SUCCEEDED(hr = m_pAudioDeviceEndpoint->SetBuffer(m_hnsPeriod, u32LatencyCoefficient))) { + return hr; + } + + return hr; +} + +HRESULT +CHalfApp::GetCurrentAvailiablePinInstanceCount +( + UINT32 *pAvailablePinInstanceCount +) +{ + HRESULT hr = S_OK; + + // Read the current pin instance counts + if (!VERIFY_SUCCEEDED(hr = GetAvailiablePinInstanceCount(m_pDevice.get(), m_uConnectorId, pAvailablePinInstanceCount))) { + return hr; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Gets a CHalfApp instance based on the current one, but with a different processing mode. +// +// The processing mode for the new instance is specified through the secondMode parameter. +// +/////////////////////////////////////////////////////////////////////////////////////////// +HRESULT +CHalfApp::GetSecondHalfApp +( + AUDIO_SIGNALPROCESSINGMODE secondMode, + CHalfApp** ppSecondHalfApp +) +{ + HRESULT hr = S_OK; + ULONG cFormatRecords = 0; + CComHeapPtr<FORMAT_RECORD> spFormatRecords; + wil::unique_cotaskmem_ptr<WAVEFORMATEX> pPreferredFormat; + UINT32 u32DefaultPeriodicityInFrames; + UINT32 u32FundamentalPeriodicityInFrames; + UINT32 u32MinPeriodicityInFrames; + UINT32 u32MaxPeriodicityInFrames; + UINT32 u32MaxPeriodicityInFramesExtended; + DeviceDescriptor descriptor = { 0 }; + + if (!VERIFY_SUCCEEDED(hr = GetSupportedFormatRecordsForConnector(m_pDevice.get(), m_uConnectorId, m_ConnectorType, secondMode, m_DataFlow, &cFormatRecords, &spFormatRecords))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = GetPreferredFormatForConnector(m_pDevice.get(), m_uConnectorId, m_ConnectorType, secondMode, wil::out_param(pPreferredFormat)))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = GetPreferredFormatPeriodicityCharacteristicsForConnector(m_pDevice.get(), m_ConnectorType, secondMode, m_DataFlow, pPreferredFormat.get(), cFormatRecords, spFormatRecords, &u32DefaultPeriodicityInFrames, &u32FundamentalPeriodicityInFrames, &u32MinPeriodicityInFrames, &u32MaxPeriodicityInFrames, &u32MaxPeriodicityInFramesExtended))) { + return hr; + } + + descriptor.pDevice = m_pDevice.get(); + descriptor.pwstrAudioEndpointId = m_pwstrDeviceId.get(); + descriptor.pwstrAudioEndpointFriendlyName = m_pwstrDeviceFriendlyName.get(); + descriptor.dataFlow = m_DataFlow; + descriptor.eConnectorType = m_ConnectorType; + descriptor.uConnectorId = m_uConnectorId; + descriptor.mode = secondMode; + descriptor.cModes = m_cModes; + descriptor.pModes = m_pModes; + descriptor.cFormatRecords = cFormatRecords; + descriptor.pFormatRecords = spFormatRecords; + descriptor.pPreferredFormat = pPreferredFormat.get(); + descriptor.u32DefaultPeriodicityInFrames = u32DefaultPeriodicityInFrames; + descriptor.u32FundamentalPeriodicityInFrames = u32FundamentalPeriodicityInFrames; + descriptor.u32MinPeriodicityInFrames = u32MinPeriodicityInFrames; + descriptor.u32MaxPeriodicityInFrames = u32MaxPeriodicityInFrames; + descriptor.bIsAVStream = m_bIsAVStream; + descriptor.bIsBluetooth = m_bIsBluetooth; + descriptor.bIsSideband = m_bIsSideband; + *ppSecondHalfApp = new CHalfApp(descriptor); + if (*ppSecondHalfApp == NULL) { + hr = E_OUTOFMEMORY; + return hr; + } + + return hr; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// Gets a CHalfApp instance corresponding to the host pin of the current audio endpoint. +// +// This function is usually called on a test method that involves a loopback pin, +// in order to be able to start and control a stream in the host pin and get loopback data. +// +//////////////////////////////////////////////////////////////////////////////////////////// +HRESULT +CHalfApp::GetHostHalfApp +( + CHalfApp** ppHostHalfApp +) +{ + HRESULT hr = S_OK; + bool bHasConnector = false; + UINT uConnectorId; + ULONG cModes = 0; + CComHeapPtr<AUDIO_SIGNALPROCESSINGMODE> spModes; + bool bRawSupport = false; + bool bDefaultSupport = false; + 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; + DeviceDescriptor descriptor = { 0 }; + + // It is only used when testing on loopback pin. When preparing loopback pin, we also need prepare the host pin. + VERIFY_IS_TRUE(m_ConnectorType == eLoopbackConnector); + + // Get host connector id + if (!VERIFY_SUCCEEDED(hr = GetConnectorId(m_pDevice.get(), eHostProcessConnector, &bHasConnector, &uConnectorId))) { + return hr; + } + if (!bHasConnector) { + return hr; + } + + // Get all signal processing modes for host connector + if (!VERIFY_SUCCEEDED(hr = GetProcessingModesForConnector(m_pDevice.get(), uConnectorId, eHostProcessConnector, &cModes, &spModes))) { + return hr; + } + + // It is possible that host pin support multiple processing modes so we pick up one mode. If host pin support default, use default mode. If host pin support raw, use raw mode. + for (ULONG i = 0; i < cModes; i++) { + if (spModes[i] == AUDIO_SIGNALPROCESSINGMODE_RAW) + bRawSupport = true; + if (spModes[i] == AUDIO_SIGNALPROCESSINGMODE_DEFAULT) + bDefaultSupport = true; + } + if (!VERIFY_IS_TRUE(bRawSupport || bDefaultSupport)) { + hr = E_FAIL; + return hr; + } + mode = bDefaultSupport ? AUDIO_SIGNALPROCESSINGMODE_DEFAULT : AUDIO_SIGNALPROCESSINGMODE_RAW; + + if (!VERIFY_SUCCEEDED(hr = GetSupportedFormatRecordsForConnector(m_pDevice.get(), uConnectorId, eHostProcessConnector, mode, render, &cFormatRecords, &spFormatRecords))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = GetPreferredFormatForConnector(m_pDevice.get(), uConnectorId, eHostProcessConnector, mode, wil::out_param(pPreferredFormat)))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = GetPreferredFormatPeriodicityCharacteristicsForConnector(m_pDevice.get(), eHostProcessConnector, mode, render, pPreferredFormat.get(), cFormatRecords, spFormatRecords, &u32DefaultPeriodicityInFrames, &u32FundamentalPeriodicityInFrames, &u32MinPeriodicityInFrames, &u32MaxPeriodicityInFrames, &u32MaxPeriodicityInFramesExtended))) { + return hr; + } + + descriptor.pDevice = m_pDevice.get(); + descriptor.pwstrAudioEndpointId = m_pwstrDeviceId.get(); + descriptor.pwstrAudioEndpointFriendlyName = m_pwstrDeviceFriendlyName.get(); + descriptor.dataFlow = render; + descriptor.eConnectorType = eHostProcessConnector; + descriptor.uConnectorId = uConnectorId; + descriptor.mode = mode; + descriptor.cModes = cModes; + descriptor.pModes = spModes; + descriptor.cFormatRecords = cFormatRecords; + descriptor.pFormatRecords = spFormatRecords; + descriptor.pPreferredFormat = pPreferredFormat.get(); + descriptor.u32DefaultPeriodicityInFrames = u32DefaultPeriodicityInFrames; + descriptor.u32FundamentalPeriodicityInFrames = u32FundamentalPeriodicityInFrames; + descriptor.u32MinPeriodicityInFrames = u32MinPeriodicityInFrames; + descriptor.u32MaxPeriodicityInFrames = u32MaxPeriodicityInFrames; + descriptor.bIsAVStream = m_bIsAVStream; + descriptor.bIsBluetooth = m_bIsBluetooth; + descriptor.bIsSideband = m_bIsSideband; + *ppHostHalfApp = new CHalfApp(descriptor); + if (*ppHostHalfApp == NULL) { + hr = E_OUTOFMEMORY; + return hr; + } + + return hr; +} + + +HRESULT +CHalfApp::InitializeStream +( + HNSTIME requestedPeriodicity, + UINT32 u32LatencyCoefficient +) +{ + HRESULT hr = S_OK; + BOOL bIsEventCapable; + wil::unique_cotaskmem_ptr<WAVEFORMATEX> pDeviceFormat; + + if (!VERIFY_IS_TRUE(!m_bStreamInitialized)) { + hr = AEERR_ALREADY_INITIALIZED; + } + + if (!VERIFY_SUCCEEDED(hr = InitializeAndSetBuffer(requestedPeriodicity, u32LatencyCoefficient))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = m_pAudioEndpoint->GetFrameFormat(out_param(pDeviceFormat)))) { + return hr; + } + m_f32EndpointFrameRate = (FLOAT32)pDeviceFormat->nAvgBytesPerSec / (FLOAT32)pDeviceFormat->nBlockAlign; + if (!VERIFY_SUCCEEDED(hr = m_pAudioEndpoint->GetLatency(&m_hnsEndpointLatency))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = m_hProcessThreadStartedEvent.create())) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = m_hTerminate.create(wil::EventOptions::ManualReset))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = m_pAudioDeviceEndpoint->GetEventDrivenCapable(&bIsEventCapable))) { + return hr; + } + + if (bIsEventCapable || m_ConnectorType == eOffloadConnector) { + m_bIsEventCapable = true; + } + + if (m_bIsEventCapable) + { + if (!VERIFY_SUCCEEDED(hr = m_hEndpointBufferCompleteEvent.create())) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = m_pAudioEndpoint->SetEventHandle(m_hEndpointBufferCompleteEvent.get()))) { + return hr; + } + } + + if (!m_bIsEventCapable) { + m_hTimer.reset(CreateWaitableTimerEx(NULL, NULL, 0, TIMER_ALL_ACCESS)); + } + + if (m_DataFlow == render) { + m_pStreamRoutine = RenderStreamRoutine; + } + else { + m_pStreamRoutine = CaptureStreamRoutine; + } + + // To-do: Adjust for offload streaming + + m_bStreamInitialized = true; + + return hr; +} + +HRESULT +CHalfApp::CleanupStream +( + void +) +{ + HRESULT hr = S_OK; + + // If the thread thread is still active, stop it + if (m_hStreamThread) + { + SignalAndWaitForThread(); + } + + m_hnsPeriod = 0; + m_f32EndpointFrameRate = 0.F; + m_hnsEndpointLatency = 0; + + m_pStreamRoutine = nullptr; + + m_hTerminate.reset(); + m_hEndpointBufferCompleteEvent.reset(); + m_hProcessThreadStartedEvent.reset(); + m_hTimer.reset(); + + m_bIsEventCapable = false; + m_bStreamInitialized = false; + m_bStreamThreadTerminate = false; + m_bYieldActive = false; + + return hr; +} + +DWORD CALLBACK +CHalfApp::RenderStreamRoutine +( + PVOID lpParameter +) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +DWORD CALLBACK +CHalfApp::CaptureStreamRoutine +( + PVOID lpParameter +) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +HRESULT +CHalfApp::StartStream +( + void +) +{ + HRESULT hr = S_OK; + + if (!VERIFY_IS_TRUE(m_bStreamInitialized)) { + hr = AEERR_NOT_INITIALIZED; + return hr; + } + if (!VERIFY_IS_TRUE(!m_hStreamThread)) { + hr = AEERR_ALREADY_RUNNING; + return hr; + } + + m_bStreamThreadTerminate = false; + m_hTerminate.ResetEvent(); + m_hProcessThreadStartedEvent.ResetEvent(); + + DWORD dwThreadID; + m_hStreamThread.reset(CreateThread(NULL, 0, m_pStreamRoutine, this, 0, &dwThreadID)); + if (!VERIFY_IS_NOT_NULL(m_hStreamThread)) { + DWORD dwError = GetLastError(); + hr = HRESULT_FROM_WIN32(dwError); + return hr; + } + if (m_hStreamThread) + { + SetThreadPriority(m_hStreamThread.get(), THREAD_PRIORITY_HIGHEST); + } + + // Wait for the processing thread to be started or to error out. To avoid the Start call from being blocked forever, give up waiting after 10 seconds, which is a duration quite unlikely to hit -- something must have gone wrong in the thread creation. + WaitForSingleObjectEx(m_hProcessThreadStartedEvent.get(), 10000, FALSE); + + // Set timer + if (!m_bIsEventCapable) { + SetTimer(m_hTimer.get(), m_hnsPeriod, true); + } + + if (!VERIFY_SUCCEEDED(hr = StartEndpoint())) { + return hr; + } + + return hr; +} + +HRESULT +CHalfApp::StopStream +( + void +) +{ + HRESULT hr = S_OK; + + m_bStreamThreadTerminate = true; + + if (!VERIFY_IS_TRUE(m_bStreamInitialized)) { + hr = AEERR_NOT_INITIALIZED; + return hr; + } + if (!VERIFY_IS_NOT_NULL(m_hStreamThread)) { + hr = AEERR_NOT_RUNNING; + return hr; + } + + SignalAndWaitForThread(); + + //Cancel timer + if (!m_bIsEventCapable) { + CancelTimer(m_hTimer.get()); + } + + if (!VERIFY_SUCCEEDED(hr = StopEndpoint())) { + return hr; + } + + return hr; +} + +void +CHalfApp::SignalAndWaitForThread +( + void +) +{ + DWORD dwReturn; + + m_bStreamThreadTerminate = true; + m_hTerminate.SetEvent(); + + dwReturn = WaitForSingleObjectEx(m_hStreamThread.get(), INFINITE, FALSE); + + VERIFY_IS_TRUE(WAIT_OBJECT_0 == dwReturn); + + m_hStreamThread.reset(); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +inline HRESULT CHalfApp::SetTimer(HANDLE timer, HNSTIME timeDuration, bool fireImmediatley) +{ + if (timer) + { + LARGE_INTEGER DueTime; + + if (fireImmediatley) + { + DueTime.QuadPart = 0; + } + else + { + // Negate this to mean relative in call to SetWaitableTimer + DueTime.QuadPart = -timeDuration; + } + + SetWaitableTimer(timer, &DueTime, + static_cast<UINT32>(MF_TO_MS(timeDuration)), + nullptr, nullptr, FALSE); + } + return S_OK; +} + +inline void CHalfApp::CancelTimer(HANDLE timer) +{ + if (timer) + { + // Cancel the waitable timer + CancelWaitableTimer(timer); + + // There is a race condition where the timer could have fired before it was canceled, which would leave the handle set to fire the next time events are waited upon. CancelWaitableTimer will not reset the state of the handle so + // wait for 0 ms for the timer to fire to clear it. + WaitForSingleObject(timer, 0); + } +} + +HRESULT CHalfApp::GetPosition(UINT64* pu64Position, UINT64* pu64hnsQPCPosition) +{ + HRESULT hr = S_OK; + auto lock = m_CritSec.lock(); + + if (!VERIFY_SUCCEEDED(m_pAudioClock->GetPosition(pu64Position, pu64hnsQPCPosition))) { + return hr; + } + + return S_OK; +} diff --git a/audio/tests/PinResourceHelpers/PreComp.h b/audio/tests/PinResourceHelpers/PreComp.h new file mode 100644 index 00000000..85fc4f83 --- /dev/null +++ b/audio/tests/PinResourceHelpers/PreComp.h @@ -0,0 +1,143 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (C) Microsoft. All rights reserved. +// +// File Name: +// +// PreComp.h +// +// Abstract: +// +// Precompiled common headers +// +// ------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// Define custom test resource properties +namespace WEX { + namespace TestExecution { + namespace TestResourceProperty + { + static const wchar_t c_szMode[] = L"Mode"; + static const wchar_t c_szPin[] = L"Pin"; + } + } +} + +// ---------------------------------------------------------- +// Parameters for test sine tone +#define TEST_AMPLITUDE 1.0f +#define TEST_FREQUENCY 200.0f + +// ---------------------------------------------------------- +// Predefined format list for offload streaming +#define COUNT_FORMATS 4 + +static WAVEFORMATEXTENSIBLE ListofFormats[COUNT_FORMATS] = +{ + // 1 + { + // Format + { + WAVE_FORMAT_EXTENSIBLE, // wFormatTag + 2, // nChannels + 44100, // nSamplesPerSec + 176400, // nAvgBytesPerSec + 4, // nBlockAlign + 16, // wBitsPerSample + 22 // cbSize + }, + // Samples + { + 16 // wValidBitsPerSample + }, + KSAUDIO_SPEAKER_STEREO, // dwChannelMask + KSDATAFORMAT_SUBTYPE_PCM // SubFormat + }, + // 2 + { + // Format + { + WAVE_FORMAT_EXTENSIBLE, // wFormatTag + 2, // nChannels + 48000, // nSamplesPerSec + 192000, // nAvgBytesPerSec + 4, // nBlockAlign + 16, // wBitsPerSample + 22 // cbSize + }, + // Samples + { + 16 // wValidBitsPerSample + }, + KSAUDIO_SPEAKER_STEREO, // dwChannelMask + KSDATAFORMAT_SUBTYPE_PCM // SubFormat + }, + // 3 + { + // Format + { + WAVE_FORMAT_EXTENSIBLE, // wFormatTag + 2, // nChannels + 88200, // nSamplesPerSec + 352800, // nAvgBytesPerSec + 4, // nBlockAlign + 16, // wBitsPerSample + 22 // cbSize + }, + // Samples + { + 16 // wValidBitsPerSample + }, + KSAUDIO_SPEAKER_STEREO, // dwChannelMask + KSDATAFORMAT_SUBTYPE_PCM // SubFormat + }, + // 4 + { + // Format + { + WAVE_FORMAT_EXTENSIBLE, // wFormatTag + 2, // nChannels + 96000, // nSamplesPerSec + 384000, // nAvgBytesPerSec + 4, // nBlockAlign + 16, // wBitsPerSample + 22 // cbSize + }, + // Samples + { + 16 // wValidBitsPerSample + }, + KSAUDIO_SPEAKER_STEREO, // dwChannelMask + KSDATAFORMAT_SUBTYPE_PCM // SubFormat + }, +}; diff --git a/audio/tests/PinResourceHelpers/PropertyHelper.cpp b/audio/tests/PinResourceHelpers/PropertyHelper.cpp new file mode 100644 index 00000000..6e85e53e --- /dev/null +++ b/audio/tests/PinResourceHelpers/PropertyHelper.cpp @@ -0,0 +1,1263 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (C) Microsoft. All rights reserved. +// +// Module Name: +// +// PropertyHelper.cpp +// +// Abstract: +// +// Implementation for common helpers of endpoint property +// +// ------------------------------------------------------------------------------- +#include "PreComp.h" +#include <AVEndpointKeys.h> +#include <Functiondiscoverykeys_devpkey.h> +#include <devpkey.h> +#include <PropertyHelper.h> + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetConnectorId +// +// Retrieve the connector id from the property store and checks if the connector exists +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetConnectorId +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + bool* hasConnector, + UINT* pConnectorId +) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetEndpointFriendlyName +// +// Retrieve the endpoint friendly name from the property store +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetEndpointFriendlyName +( + IMMDevice* pDevice, + LPWSTR* ppwszEndpointName +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IPropertyStore> spPropertyStore; + wil::unique_prop_variant var; + wil::unique_cotaskmem_string pwszName; + + *ppwszEndpointName = NULL; + + // Retrieve the endpoint friendly name from the propertey store + if (!VERIFY_SUCCEEDED(hr = pDevice->OpenPropertyStore(STGM_READ, &spPropertyStore))) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = spPropertyStore->GetValue(PKEY_Device_FriendlyName, &var))) { + return hr; + } + if (!VERIFY_IS_TRUE(var.vt == VT_LPWSTR)) { + hr = E_FAIL; + return hr; + } + + pwszName = wil::make_cotaskmem_string_nothrow(var.pwszVal); + if (!VERIFY_IS_TRUE(pwszName.get() && wcslen(pwszName.get()) != 0)) { + hr = E_FAIL; + return hr; + } + + *ppwszEndpointName = pwszName.release(); + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetAudioFilterAsDevice +// +// Get the audio filter from MMDevice. This method is called to activate IKsControl, IKsFormatSupport or IKsGetProposedFormat. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetAudioFilterAsDevice +( + IMMDevice* pDevice, + IMMDevice** ppAudioFilterAsDevice +) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetCachedProcessingModes +// +// Read the processing mode characteristics from the property store and get all processing modes. This method applies to host and keyword detector pins only. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetCachedProcessingModes +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + ULONG *pCount, + AUDIO_SIGNALPROCESSINGMODE **ppModes +) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetProcessingModes +// +// Query for connector's processing modes property +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetProcessingModes +( + IMMDevice* pDevice, + UINT pinId, + ULONG *pCount, + AUDIO_SIGNALPROCESSINGMODE **ppModes +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDevice> spAudioFilter; + wil::com_ptr_nothrow<IKsControl> spKsControl; + KSP_PIN pinProp; + ULONG cbNeeded = 0; + ULONG cbProperty; + PKSMULTIPLE_ITEM pProperty = NULL; + ULONG cbReturned = 0; + AUDIO_SIGNALPROCESSINGMODE* pModes = NULL; + CComHeapPtr<AUDIO_SIGNALPROCESSINGMODE> spModes; + + *pCount = 0; + *ppModes = NULL; + + // Get the audio filter + if (!VERIFY_SUCCEEDED(hr = GetAudioFilterAsDevice(pDevice, &spAudioFilter))) { + return hr; + } + + // Activate IKsControl + if (!VERIFY_SUCCEEDED(hr = spAudioFilter->Activate(__uuidof(IKsControl), CLSCTX_ALL, NULL, (VOID**)&spKsControl))) { + return hr; + } + + // Read the audio signal processing mode property + pinProp.Property.Set = KSPROPSETID_AudioSignalProcessing; + pinProp.Property.Id = KSPROPERTY_AUDIOSIGNALPROCESSING_MODES; + pinProp.Property.Flags = KSPROPERTY_TYPE_GET; + pinProp.PinId = pinId & PARTID_MASK; + pinProp.Reserved = 0; + + // Determined the needed size + if (!VERIFY_SUCCEEDED(hr = spKsControl->KsProperty(&pinProp.Property, sizeof(KSP_PIN), NULL, 0, &cbNeeded))) { + return hr; + } + if (!VERIFY_IS_TRUE(cbNeeded > 0) || !VERIFY_IS_TRUE((cbNeeded - sizeof(KSMULTIPLE_ITEM)) % sizeof(GUID) == 0)) { + hr = E_NOTFOUND; + return hr; + } + + cbProperty = cbNeeded; + pProperty = (PKSMULTIPLE_ITEM)CoTaskMemAlloc(cbNeeded); + + if (!VERIFY_IS_NOT_NULL(pProperty)) { + hr = E_OUTOFMEMORY; + cbProperty = 0; + return hr; + } + + // Query the processing mode property + if (!VERIFY_SUCCEEDED(hr = spKsControl->KsProperty(&pinProp.Property, sizeof(KSP_PIN), pProperty, cbNeeded, &cbReturned))) { + goto Exit; + } + + if (!VERIFY_IS_NOT_NULL(pProperty)) { + hr = E_NOTFOUND; + goto Exit; + } + + if (!VERIFY_IS_TRUE(cbProperty >= (sizeof(KSMULTIPLE_ITEM) + pProperty->Count * sizeof(GUID)))) { + hr = E_NOTFOUND; + goto Exit; + } + + pModes = reinterpret_cast<GUID*>(pProperty + 1); + + if (!VERIFY_IS_TRUE(spModes.Allocate(pProperty->Count))) + { + hr = E_OUTOFMEMORY; + goto Exit; + } + + memcpy(spModes, pModes, sizeof(AUDIO_SIGNALPROCESSINGMODE)*(pProperty->Count)); + + *pCount = pProperty->Count; + *ppModes = spModes.Detach(); + +Exit: + if (pProperty != NULL) { + CoTaskMemFree(pProperty); + } + cbProperty = 0; + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetCachedSupportedFormatRecords +// +// Read the processing mode characteristics from the property store and get all supported formats for processing mode. This method applies to host and keyword detector pins only. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetCachedSupportedFormatRecords +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + ULONG *pCount, + FORMAT_RECORD **ppFormatRecords +) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetSupportedFormatRecords +// +// For pins do not have cached supported formats information, such as offload pin, we provide our predefined format list and check whether each is supported by the pin. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetSupportedFormatRecords +( + IMMDevice* pDevice, + UINT pinId, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + STACKWISE_DATAFLOW dataFlow, + ULONG *pCount, + FORMAT_RECORD **ppFormatRecords +) +{ + HRESULT hr = S_OK; + FORMAT_RECORD *pFormatRecords; + UINT cFormatRecords = 0; + CComHeapPtr<FORMAT_RECORD> spFormatRecords; + BOOL isSupported = false; + WAVEFORMATEX *pWfx; + UINT32 u32DefaultPeriodicityInFrames = 0; + UINT32 u32FundamentalPeriodicityInFrames = 0; + UINT32 u32MinPeriodicityInFrames = 0; + UINT32 u32MaxPeriodicityInFrames = 0; + UINT32 u32MaxPeriodicityInFramesExtended = 0; + + // Init return + *pCount = 0; + *ppFormatRecords = NULL; + + pFormatRecords = new FORMAT_RECORD[COUNT_FORMATS]; + if (!VERIFY_IS_NOT_NULL(pFormatRecords)) { + hr = E_OUTOFMEMORY; + return hr; + } + memset(pFormatRecords, 0, sizeof(FORMAT_RECORD)*COUNT_FORMATS); + + for (ULONG i = 0; i < COUNT_FORMATS; i++) { + pWfx = (WAVEFORMATEX*)&ListofFormats[i]; + + // Check if format is supported + if (!VERIFY_SUCCEEDED(hr = IsFormatSupported(pDevice, pinId, pWfx, &isSupported))) { + return hr; + } + + // If the format is supported, we discover the periodicity characteristics for the format and store them along with the format in our FORMAT_RECORD struct. + if (isSupported) { + if (!VERIFY_SUCCEEDED(hr = DiscoverPeriodicityCharacteristicsForFormat(pDevice, eConnectorType, mode, pWfx, dataFlow, &u32DefaultPeriodicityInFrames, &u32FundamentalPeriodicityInFrames, &u32MinPeriodicityInFrames, &u32MaxPeriodicityInFrames, &u32MaxPeriodicityInFramesExtended))) { + return hr; + } + + pFormatRecords[cFormatRecords].defaultPeriodInFrames = u32DefaultPeriodicityInFrames; + pFormatRecords[cFormatRecords].fundamentalPeriodInFrames = u32FundamentalPeriodicityInFrames; + pFormatRecords[cFormatRecords].minPeriodInFrames = u32MinPeriodicityInFrames; + pFormatRecords[cFormatRecords].maxPeriodInFrames = u32MaxPeriodicityInFrames; + pFormatRecords[cFormatRecords].maxPeriodInFramesExtended = u32MaxPeriodicityInFramesExtended; + memcpy(&pFormatRecords[cFormatRecords].wfxEx, &ListofFormats[i], sizeof(WAVEFORMATEX) + ListofFormats[i].Format.cbSize); + cFormatRecords++; + } + } + + if (!VERIFY_IS_TRUE(spFormatRecords.Allocate(cFormatRecords))) { + hr = E_OUTOFMEMORY; + return hr; + } + + memcpy(spFormatRecords, pFormatRecords, sizeof(FORMAT_RECORD)*cFormatRecords); + delete[] pFormatRecords; + + *pCount = cFormatRecords; + *ppFormatRecords = spFormatRecords.Detach(); + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// IsFormatSupported +// +// Wrap up the IKsFormatSupport::IsFormatSupported method. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT IsFormatSupported +( + IMMDevice* pDevice, + UINT pinId, + WAVEFORMATEX *pWfx, + BOOL* pbSupported +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDevice> spAudioFilter; + wil::com_ptr_nothrow<IDeviceTopology> spAudioFilterTopology; + wil::com_ptr_nothrow<IPart> spPart; + wil::com_ptr_nothrow<IKsFormatSupport> spFormatSupport; + PKSDATAFORMAT_WAVEFORMATEXTENSIBLE pKsRequestedFormat = NULL; + PKSDATAFORMAT pKsFormat = NULL; + KSDATAFORMAT_WAVEFORMATEXTENSIBLE KsWfex = {}; + DWORD cbKsFormat = 0; + + // Init return + *pbSupported = FALSE; + + // Get the audio filter + if (!VERIFY_SUCCEEDED(hr = GetAudioFilterAsDevice(pDevice, &spAudioFilter))) { + return hr; + } + + // Activate an IDeviceTopology on the filter + if (!VERIFY_SUCCEEDED(hr = spAudioFilter->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&spAudioFilterTopology))) { + return hr; + } + + // Get the connector + if (!VERIFY_SUCCEEDED(hr = spAudioFilterTopology->GetPartById(pinId, &spPart))) { + return hr; + } + + // Activate IKsFormatSupport interface + if (!VERIFY_SUCCEEDED(hr = spPart->Activate(CLSCTX_ALL, __uuidof(IKsFormatSupport), (void**)&spFormatSupport))) { + return hr; + } + + // Convert to KSDATAFORMAT_WAVEFORMATEX + if (!VERIFY_SUCCEEDED(hr = CreateKSFormatFromWFXFormat(pWfx, (KSDATAFORMAT_WAVEFORMATEX**)&pKsRequestedFormat))) { + return hr; + } + + if (pKsRequestedFormat->DataFormat.FormatSize < sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE)) + { + memcpy(&KsWfex, pKsRequestedFormat, pKsRequestedFormat->DataFormat.FormatSize); + pKsFormat = reinterpret_cast<KSDATAFORMAT *>(&KsWfex); + cbKsFormat = sizeof(KsWfex); + } + else + { + pKsFormat = reinterpret_cast<KSDATAFORMAT *>(pKsRequestedFormat); + cbKsFormat = pKsRequestedFormat->DataFormat.FormatSize; + } + + // Expect that pbFormat is really a KSDATAFORMAT + if (!VERIFY_IS_TRUE(cbKsFormat >= sizeof(KSDATAFORMAT))) { + hr = E_INVALIDARG; + return hr; + } + + // validate return pointers (this is an externally callable method) + if (!VERIFY_IS_TRUE(pKsFormat)) { + hr = E_POINTER; + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = spFormatSupport->IsFormatSupported(pKsFormat, cbKsFormat, pbSupported))) { + return hr; + } + + CoTaskMemFree(pKsRequestedFormat); + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// DiscoverPeriodicityCharacteristicsForFormat +// +// Discover the periodicity characteristics for given format. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +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 hr = S_OK; + HNSTIME hnsDefaultPeriod = kSystemDefaultPeriod; + UINT32 ActualPeriodicityInFrames; + + // Verify format is not null + if (!VERIFY_IS_NOT_NULL(pWfx)) { + hr = E_INVALIDARG; + return hr; + } + + *pDefaultPeriodicityInFrames = + *pFundamentalPeriodicityInFrames = + *pMinPeriodicityInFrames = + *pMaxPeriodicityInFrames = HNSTIME_TO_FRAMES_DOUBLE(hnsDefaultPeriod, pWfx->nSamplesPerSec); + + // Check to see whether the endpoint supports the default periodicity and return the actual periodicity + if (!VERIFY_SUCCEEDED(hr = CheckConnectorSupportForPeriodicity(pDevice, eConnectorType, mode, pWfx, dataFlow, hnsDefaultPeriod, &ActualPeriodicityInFrames))) { + return hr; + } + + // Use what we get as the default, max, min and fundamental periodicity + *pDefaultPeriodicityInFrames = + *pFundamentalPeriodicityInFrames = + *pMinPeriodicityInFrames = + *pMaxPeriodicityInFrames = ActualPeriodicityInFrames; + + // Max periodicity is capped to the default periodicity for all clients except extended periodicity clients + *pMaxPeriodicityInFramesExtended = *pMaxPeriodicityInFrames; + if (*pMaxPeriodicityInFrames > *pDefaultPeriodicityInFrames) + { + *pMaxPeriodicityInFrames = *pDefaultPeriodicityInFrames; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CheckConnectorSupportForPeriodicity +// +// Check to see if the connector supports the specified periodicity and return the actual periodicity. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT CheckConnectorSupportForPeriodicity +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + WAVEFORMATEX *pWfx, + STACKWISE_DATAFLOW dataFlow, + HNSTIME RequestedPeriodicity, + UINT32 *pActualPeriodicityInFrames +) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetCachedDefaultFormat +// +// Read the default device format from the property store. Use audio engine device format for all other pin types. For Keyword Detector pin, it has a different default device format. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetCachedDefaultFormat +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + WAVEFORMATEX** ppDefaultFormat +) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetProposedFormatForProcessingMode +// +// Wrap up the IKsGetProposedFormat::GetProposedFormatForMode method. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetProposedFormatForProcessingMode +( + IMMDevice* pDevice, + UINT pinId, + AUDIO_SIGNALPROCESSINGMODE mode, + WAVEFORMATEX **ppProposedFormat +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDevice> spAudioFilter; + wil::com_ptr_nothrow<IDeviceTopology> spAudioFilterTopology; + wil::com_ptr_nothrow<IPart> spPart; + + + + PKSDATAFORMAT pKsProposedFormat = NULL; + WAVEFORMATEXTENSIBLE* pWfxEx = NULL; + + // Init return + *ppProposedFormat = NULL; + + if (!VERIFY_SUCCEEDED(hr = GetAudioFilterAsDevice(pDevice, &spAudioFilter))) { + return hr; + } + + // Activate an IDeviceTopology on the filter + if (!VERIFY_SUCCEEDED(hr = spAudioFilter->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&spAudioFilterTopology))) { + return hr; + } + + // Get the connector + if (!VERIFY_SUCCEEDED(hr = spAudioFilterTopology->GetPartById(pinId, &spPart))) { + return hr; + } + + // Retrieve the proposed format for a specific processing mode and store it in pKsProposedFormat... + + + + + + + + + + + + + // Allocate memory for ppOffloadDefaultFormat and copy pKsDataDeviceFormat to ppOffloadDefaultFormat + pWfxEx = (WAVEFORMATEXTENSIBLE *)CoTaskMemAlloc(sizeof(WAVEFORMATEX) + ((PKSDATAFORMAT_WAVEFORMATEX)pKsProposedFormat)->WaveFormatEx.cbSize); + if (!VERIFY_IS_NOT_NULL(pWfxEx)) { + CoTaskMemFree(pKsProposedFormat); + hr = E_OUTOFMEMORY; + return hr; + } + memcpy(pWfxEx, &(((PKSDATAFORMAT_WAVEFORMATEX)pKsProposedFormat)->WaveFormatEx), sizeof(WAVEFORMATEX) + ((PKSDATAFORMAT_WAVEFORMATEX)pKsProposedFormat)->WaveFormatEx.cbSize); + *ppProposedFormat = (WAVEFORMATEX*)pWfxEx; + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetCurrentAvailiablePinInstanceCount +// +// Query for the connector's pin instance count property. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetAvailiablePinInstanceCount +( + IMMDevice* pDevice, + UINT pinId, + UINT32* pAvailablePinInstanceCount +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDevice> spAudioFilter; + wil::com_ptr_nothrow<IKsControl> spKsControl; + KSP_PIN pinProp; + KSPIN_CINSTANCES pinInstances; + ULONG cbReturned = 0; + + *pAvailablePinInstanceCount = 0; + + if (!VERIFY_SUCCEEDED(hr = GetAudioFilterAsDevice(pDevice, &spAudioFilter))) { + return hr; + } + + if (!VERIFY_SUCCEEDED(hr = spAudioFilter->Activate(__uuidof(IKsControl), CLSCTX_ALL, NULL, (VOID**)&spKsControl))) { + return hr; + } + + // Read the pin instance count + pinProp.Property.Set = KSPROPSETID_Pin; + pinProp.Property.Id = KSPROPERTY_PIN_CINSTANCES; + pinProp.Property.Flags = KSPROPERTY_TYPE_GET; + pinProp.PinId = pinId & PARTID_MASK; + pinProp.Reserved = 0; + + if (!VERIFY_SUCCEEDED(hr = spKsControl->KsProperty(&pinProp.Property, sizeof(KSP_PIN), &pinInstances, sizeof(KSPIN_CINSTANCES), &cbReturned))) { + return hr; + } + + *pAvailablePinInstanceCount = pinInstances.PossibleCount - pinInstances.CurrentCount; + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// IsAVStream +// +// Check if audio device is AVStream +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT IsAVStream +( + IMMDevice* pDevice, + bool* pIsAVStream +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDevice> spDevnodeDevice; + wil::com_ptr_nothrow<IPropertyStore> spPnpProperties; + wil::unique_prop_variant varDeviceService; + + *pIsAVStream = 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 (!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; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// IsBluetooth +// +// Check if audio device is Bluetooth +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT IsBluetooth +( + IMMDevice* pDevice, + bool* pIsBluetooth +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IPropertyStore> spPropertyStore; + wil::unique_prop_variant varIsBluetooth; + + *pIsBluetooth = false; + + // Open pnp device property store + if (!VERIFY_SUCCEEDED(hr = pDevice->OpenPropertyStore(STGM_READ, &spPropertyStore))) { + return hr; + } + + // Read the PKEY_Endpoint_IsBluetooth + hr = spPropertyStore->GetValue((REFPROPERTYKEY)PKEY_Endpoint_IsBluetooth, &varIsBluetooth); + if (hr != S_OK) { + return hr; + } + + if (varIsBluetooth.vt == VT_BOOL) + { + *pIsBluetooth = varIsBluetooth.boolVal == VARIANT_TRUE ? true : false; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// IsSideband +// +// Check if audio device is side band +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT IsSideband +( + IMMDevice* pDevice, + bool* pIsSideband +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IPropertyStore> spPropertyStore; + wil::unique_prop_variant varIsSideband; + + *pIsSideband = false; + + // Open pnp device property store + if (!VERIFY_SUCCEEDED(hr = pDevice->OpenPropertyStore(STGM_READ, &spPropertyStore))) { + return hr; + } + + // Read the PKEY_Endpoint_IsBluetooth + hr = spPropertyStore->GetValue((REFPROPERTYKEY)PKEY_Endpoint_IsSideband, &varIsSideband); + if (hr != S_OK) { + return hr; + } + + if (varIsSideband.vt == VT_BOOL) + { + *pIsSideband = varIsSideband.boolVal == VARIANT_TRUE ? true : false; + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GetPnpDevnodeFromMMDeivce +// +// Get pnp devnode from mm device +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +HRESULT GetPnpDevnodeFromMMDevice +( + IMMDevice* pDevice, + IMMDevice** pDevnodeDevice +) +{ + HRESULT hr = S_OK; + wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator; + wil::com_ptr_nothrow<IPropertyStore> spProps; + wil::com_ptr_nothrow<IMMDevice> spDevnodeAsDevice; + wil::com_ptr_nothrow<IPropertyStore> spDevnodeProps; + wil::unique_prop_variant varDevnode; + + // Create an enumerator + if (!VERIFY_SUCCEEDED(hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void **)&spEnumerator))) { + return hr; + } + + // Read the PKEY_Endpoint_Devnode + if (!VERIFY_SUCCEEDED(hr = pDevice->OpenPropertyStore(STGM_READ, &spProps))) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = spProps->GetValue(PKEY_Endpoint_Devnode, &varDevnode))) { + return hr; + } + + // Get the devnode as an IMMDevice + if (!VERIFY_SUCCEEDED(hr = spEnumerator->GetDevice(varDevnode.pwszVal, &spDevnodeAsDevice))) { + return hr; + } + *pDevnodeDevice = spDevnodeAsDevice.detach(); + + return hr; +} diff --git a/audio/tests/PinResourceHelpers/TestResource.cpp b/audio/tests/PinResourceHelpers/TestResource.cpp new file mode 100644 index 00000000..f3762b84 --- /dev/null +++ b/audio/tests/PinResourceHelpers/TestResource.cpp @@ -0,0 +1,331 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (C) Microsoft. All rights reserved. +// +// File Name: +// +// TestResource.cpp +// +// Abstract: +// +// TAEF Test Resource implementation +// +// ------------------------------------------------------------------------------- + +#include "PreComp.h" +#include <HalfApp.h> +#include <TestResource.h> + +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +CPinTestResource::CPinTestResource() : + m_cRef(1) +{} + +CPinTestResource::~CPinTestResource() +{} + +HRESULT STDMETHODCALLTYPE +CPinTestResource::CreateInstance +( + CHalfApp * pHalf, + REFGUID guid, + ITestResource ** ppOut +) +{ + HRESULT hr = S_OK; + CPinTestResource * pTestResource; + SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures); + + if (!VERIFY_IS_NOT_NULL(pHalf)) + { + return E_INVALIDARG; + } + + if (!VERIFY_IS_NOT_NULL(ppOut)) + { + return E_POINTER; + } + + pTestResource = new CPinTestResource(); + if (!VERIFY_IS_NOT_NULL(pTestResource)) + { + return E_OUTOFMEMORY; + } + + if (!VERIFY_SUCCEEDED(hr = pTestResource->Initialize( + pHalf, guid))) { + pTestResource->Release(); + return hr; + } + + *ppOut = pTestResource; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +CPinTestResource::Initialize +( + CHalfApp * pHalf, + REFGUID guid +) +{ + HRESULT hr = S_OK; + + WCHAR szStr[MAX_PATH]; + LPWSTR szMode; + LPWSTR szPin; + + SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures); + + m_guid = guid; + m_spHalfApp.Attach(pHalf); + + // Dataflow + if (m_spHalfApp->m_DataFlow == capture) + { + m_szType.Attach(W2BSTR(L"Capture")); + if (!VERIFY_IS_NOT_NULL(m_szType)) { return E_OUTOFMEMORY; } + } + else + { + m_szType.Attach(W2BSTR(L"Render")); + if (!VERIFY_IS_NOT_NULL(m_szType)) { return E_OUTOFMEMORY; } + } + + szMode = ModeName(m_spHalfApp->m_Mode); + szPin = PinName(m_spHalfApp->m_ConnectorType); + + // Id: Combine device Id, pin type and mode + m_szId.Attach(W2BSTR(m_spHalfApp->m_pwstrDeviceId.get())); + if (!VERIFY_SUCCEEDED(hr = StringCchPrintfW( + szStr, MAX_PATH, L"#%s#%s", + szPin, + szMode))) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = m_szId.Append(szStr))) { + return hr; + } + if (!VERIFY_IS_NOT_NULL(m_szId)) { return E_OUTOFMEMORY; } + + // Name: Combine device friendly name, pin type, and mode + m_szName.Attach(W2BSTR(m_spHalfApp->m_pwstrDeviceFriendlyName.get())); + if (!VERIFY_SUCCEEDED(hr = StringCchPrintfW( + szStr, MAX_PATH, L" Pin %s Mode %s", + szPin, + szMode))) { + return hr; + } + if (!VERIFY_SUCCEEDED(hr = m_szName.Append(szStr))) { + return hr; + } + if (!VERIFY_IS_NOT_NULL(m_szName)) { return E_OUTOFMEMORY; } + + // Mode + m_szMode.Attach(W2BSTR(szMode)); + if (!VERIFY_IS_NOT_NULL(m_szMode)) { return E_OUTOFMEMORY; } + + // Pin + m_szPin.Attach(W2BSTR(szPin)); + if (!VERIFY_IS_NOT_NULL(m_szPin)) { return E_OUTOFMEMORY; } + + return hr; +} + +STDMETHODIMP_(ULONG) +CPinTestResource::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) +CPinTestResource::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + + return cRef; +} + +STDMETHODIMP +CPinTestResource::QueryInterface( + __in REFIID riid, + __deref_out void ** ppvObject +) +{ + HRESULT hr = S_OK; + + //IF_FALSE_EXIT_HR( (NULL != ppvObject), E_POINTER); + + if (IsEqualIID(riid, __uuidof(IUnknown))) + { + *ppvObject = static_cast<void *>(this); + } + else if (IsEqualIID(riid, __uuidof(ITestResource))) + { + *ppvObject = static_cast<ITestResource *>(this); + } + else if (IsEqualIID(riid, __uuidof(IHalfAppContainer))) + { + *ppvObject = static_cast<IHalfAppContainer *>(this); + } + else + { + *ppvObject = NULL; + hr = E_NOINTERFACE; + goto Exit; + } + + AddRef(); + +Exit: + return hr; +} + +STDMETHODIMP +CPinTestResource::GetGuid(GUID* pGuid) { + // don't want to see VERIFY spew + SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures); + + if (!VERIFY_IS_NOT_NULL(pGuid)) { return E_POINTER; } + + *pGuid = m_guid; + return S_OK; +} + +STDMETHODIMP +CPinTestResource::SetGuid(GUID guid) { + m_guid = guid; + return S_OK; +} + +STDMETHODIMP +CPinTestResource::GetValue(BSTR name, BSTR* pValue) { + // don't want to see VERIFY spew + SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures); + + if (!VERIFY_IS_NOT_NULL(name)) { return E_POINTER; } + if (!VERIFY_IS_NOT_NULL(pValue)) { return E_POINTER; } + + if (0 == wcscmp(name, TestResourceProperty::c_szName)) { + if (!VERIFY_IS_NOT_NULL(*pValue = SysAllocString(m_szName))) { return E_OUTOFMEMORY; } + return S_OK; + } + + if (0 == wcscmp(name, TestResourceProperty::c_szId)) { + if (!VERIFY_IS_NOT_NULL(*pValue = SysAllocString(m_szId))) { return E_OUTOFMEMORY; } + return S_OK; + } + + if (0 == wcscmp(name, TestResourceProperty::c_szType)) { + if (!VERIFY_IS_NOT_NULL(*pValue = SysAllocString(m_szType))) { return E_OUTOFMEMORY; } + return S_OK; + } + + if (0 == wcscmp(name, TestResourceProperty::c_szMode)) { + if (!VERIFY_IS_NOT_NULL(*pValue = SysAllocString(m_szMode))) { return E_OUTOFMEMORY; } + return S_OK; + } + + if (0 == wcscmp(name, TestResourceProperty::c_szPin)) { + if (!VERIFY_IS_NOT_NULL(*pValue = SysAllocString(m_szPin))) { return E_OUTOFMEMORY; } + return S_OK; + } + + Log::Error(L"CDevice::GetValue name not found"); + return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); +} + +STDMETHODIMP +CPinTestResource::SetValue(BSTR name, BSTR value) { + // don't want to see VERIFY spew + SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures); + + if (!VERIFY_IS_NOT_NULL(name)) { return E_POINTER; } + if (!VERIFY_IS_NOT_NULL(value)) { return E_POINTER; } + + if (0 == wcscmp(name, TestResourceProperty::c_szName)) { + return m_szName.AssignBSTR(value); + } + + if (0 == wcscmp(name, TestResourceProperty::c_szId)) { + return m_szId.AssignBSTR(value); + } + + if (0 == wcscmp(name, TestResourceProperty::c_szType)) { + return m_szType.AssignBSTR(value); + } + + if (0 == wcscmp(name, TestResourceProperty::c_szMode)) { + return m_szMode.AssignBSTR(value); + } + + if (0 == wcscmp(name, TestResourceProperty::c_szPin)) { + return m_szPin.AssignBSTR(value); + } + + Log::Error(L"CDevice::SetValue name not found"); + + return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); +} + +STDMETHODIMP +CPinTestResource::GetHalfApp(CHalfApp ** ppHalfApp) +{ + SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures); + + if (!VERIFY_IS_NOT_NULL(ppHalfApp)) { return E_POINTER; } + + *ppHalfApp = m_spHalfApp; + + return S_OK; +} + +static const struct +{ + GUID mode; + LPWSTR name; +}knownModes[] = +{ + { GUID_NULL, L"NO_MODE" }, + { AUDIO_SIGNALPROCESSINGMODE_RAW, L"RAW" }, + { AUDIO_SIGNALPROCESSINGMODE_DEFAULT, L"DEFAULT" }, + { AUDIO_SIGNALPROCESSINGMODE_COMMUNICATIONS, L"COMMUNICATIONS" }, + { AUDIO_SIGNALPROCESSINGMODE_SPEECH, L"SPEECH" }, + { AUDIO_SIGNALPROCESSINGMODE_MEDIA, L"MEDIA" }, + { AUDIO_SIGNALPROCESSINGMODE_MOVIE, L"MOVIE" }, + { AUDIO_SIGNALPROCESSINGMODE_NOTIFICATION, L"NOTIFICATION" }, +}; + + +LPWSTR CPinTestResource::ModeName(REFGUID guidMode) +{ + for (auto m : knownModes) + { + if (m.mode == guidMode) { return m.name; } + } + + return L"UNKNOWN"; +} + +LPWSTR CPinTestResource::PinName(EndpointConnectorType eConnectorType) +{ + switch (eConnectorType) + { + case eHostProcessConnector: + return L"HOST"; + case eOffloadConnector: + return L"OFFLOAD"; + case eLoopbackConnector: + return L"LOOPBACK"; + case eKeywordDetectorConnector: + return L"KEYWORD"; + default: + return L"UNKNOWN"; + } +} diff --git a/audio/tests/PinResourceHelpers/TestResourceHelper.h b/audio/tests/PinResourceHelpers/TestResourceHelper.h new file mode 100644 index 00000000..08fafa23 --- /dev/null +++ b/audio/tests/PinResourceHelpers/TestResourceHelper.h @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (C) Microsoft. All rights reserved. +// +// Module Name: +// +// TestResourceHelpers.h +// +// Abstract: +// +// Header for BuildResourceList helpers. +// +// ------------------------------------------------------------------------------- +#pragma once + +#include <HalfApp.h> + +// ---------------------------------------------------------------------- +HRESULT GetProcessingModesForConnector +( + IMMDevice* pDevice, + UINT uConnectorId, + EndpointConnectorType eConnectorType, + ULONG* pCount, + AUDIO_SIGNALPROCESSINGMODE** ppModes +); + +// ---------------------------------------------------------------------- +HRESULT GetDefaultFormatForConnector +( + IMMDevice* pDevice, + EndpointConnectorType eConnectorType, + WAVEFORMATEX** ppDefaultFormat +); + +// ---------------------------------------------------------------------- +HRESULT GetPreferredFormatForConnector +( + IMMDevice* pDevice, + UINT uConnectorId, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + WAVEFORMATEX** ppPreferredFormat +); + +// ---------------------------------------------------------------------- +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 GetSupportedFormatRecordsForConnector +( + IMMDevice* pDevice, + UINT uConnectorId, + EndpointConnectorType eConnectorType, + AUDIO_SIGNALPROCESSINGMODE mode, + STACKWISE_DATAFLOW dataFlow, + ULONG* pCount, + FORMAT_RECORD** ppFormatRecords +); |
