diff options
Diffstat (limited to 'src/plugins/wasapi/qwasapiutils.cpp')
-rw-r--r-- | src/plugins/wasapi/qwasapiutils.cpp | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/src/plugins/wasapi/qwasapiutils.cpp b/src/plugins/wasapi/qwasapiutils.cpp new file mode 100644 index 000000000..bd1795aee --- /dev/null +++ b/src/plugins/wasapi/qwasapiutils.cpp @@ -0,0 +1,317 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasapiutils.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/private/qeventdispatcher_winrt_p.h> + +// For Desktop Win32 support +#ifdef CLASSIC_APP_BUILD +#define Q_OS_WINRT +#endif +#include <QtCore/qfunctions_winrt.h> + +#include <QtMultimedia/QAudioDeviceInfo> +#include <Audioclient.h> +#include <windows.devices.enumeration.h> +#include <windows.foundation.collections.h> +#include <windows.media.devices.h> + +#include <functional> + +using namespace ABI::Windows::Devices::Enumeration; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Media::Devices; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; + +#define RETURN_EMPTY_LIST_IF_FAILED(msg) RETURN_IF_FAILED(msg, return QList<QByteArray>()) + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcMmAudioInterface, "qt.multimedia.audiointerface") +Q_LOGGING_CATEGORY(lcMmUtils, "qt.multimedia.utils") + +#ifdef CLASSIC_APP_BUILD +// Opening bracket has to be in the same line as MSVC2013 and 2015 complain on +// different lines otherwise +#pragma warning (suppress: 4273) +HRESULT QEventDispatcherWinRT::runOnXamlThread(const std::function<HRESULT()> &delegate, bool waitForRun) { + Q_UNUSED(waitForRun) + return delegate(); +} +#endif + +namespace QWasapiUtils { +struct DeviceMapping { + QList<QByteArray> outputDeviceNames; + QList<QString> outputDeviceIds; + QList<QByteArray> inputDeviceNames; + QList<QString> inputDeviceIds; +}; +Q_GLOBAL_STATIC(DeviceMapping, gMapping) +} + +AudioInterface::AudioInterface() +{ + qCDebug(lcMmAudioInterface) << __FUNCTION__; + m_currentState = Initialized; +} + +AudioInterface::~AudioInterface() +{ + qCDebug(lcMmAudioInterface) << __FUNCTION__; +} + +HRESULT AudioInterface::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *op) +{ + qCDebug(lcMmAudioInterface) << __FUNCTION__; + + IUnknown *aInterface; + HRESULT hr; + HRESULT hrActivate; + hr = op->GetActivateResult(&hrActivate, &aInterface); + if (FAILED(hr) || FAILED(hrActivate)) { + qCDebug(lcMmAudioInterface) << __FUNCTION__ << "Could not query activate results."; + m_currentState = Error; + return hr; + } + + hr = aInterface->QueryInterface(IID_PPV_ARGS(&m_client)); + if (FAILED(hr)) { + qCDebug(lcMmAudioInterface) << __FUNCTION__ << "Could not access AudioClient interface."; + m_currentState = Error; + return hr; + } + + WAVEFORMATEX *format; + hr = m_client->GetMixFormat(&format); + if (FAILED(hr)) { + qCDebug(lcMmAudioInterface) << __FUNCTION__ << "Could not get mix format."; + m_currentState = Error; + return hr; + } + + QWasapiUtils::convertFromNativeFormat(format, &m_mixFormat); + + m_currentState = Activated; + return S_OK; +} + +bool QWasapiUtils::convertToNativeFormat(const QAudioFormat &qt, WAVEFORMATEX *native) +{ + if (!native + || !qt.isValid() + || qt.codec() != QStringLiteral("audio/pcm") + || qt.sampleRate() <= 0 + || qt.channelCount() <= 0 + || qt.sampleSize() <= 0 + || qt.byteOrder() != QAudioFormat::LittleEndian) { + return false; + } + + native->nSamplesPerSec = qt.sampleRate(); + native->wBitsPerSample = qt.sampleSize(); + native->nChannels = qt.channelCount(); + native->nBlockAlign = (native->wBitsPerSample * native->nChannels) / 8; + native->nAvgBytesPerSec = native->nBlockAlign * native->nSamplesPerSec; + native->cbSize = 0; + + if (qt.sampleType() == QAudioFormat::Float) + native->wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + else + native->wFormatTag = WAVE_FORMAT_PCM; + + return true; +} + +bool QWasapiUtils::convertFromNativeFormat(const WAVEFORMATEX *native, QAudioFormat *qt) +{ + if (!native || !qt) + return false; + + qt->setByteOrder(QAudioFormat::LittleEndian); + qt->setChannelCount(native->nChannels); + qt->setCodec(QStringLiteral("audio/pcm")); + qt->setSampleRate(native->nSamplesPerSec); + qt->setSampleSize(native->wBitsPerSample); + qt->setSampleType(native->wFormatTag == WAVE_FORMAT_IEEE_FLOAT ? QAudioFormat::Float : QAudioFormat::SignedInt); + + return true; +} + +QList<QByteArray> QWasapiUtils::availableDevices(QAudio::Mode mode) +{ + qCDebug(lcMmUtils) << __FUNCTION__ << mode; + + ComPtr<IDeviceInformationStatics> statics; + HRESULT hr; + + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), + &statics); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IMediaDeviceStatics> mediaDeviceStatics; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Media_Devices_MediaDevice).Get(), &mediaDeviceStatics); + Q_ASSERT_SUCCEEDED(hr); + + HString defaultAudioRender; + quint32 dARSize = 0; + hr = mediaDeviceStatics->GetDefaultAudioRenderId(AudioDeviceRole_Default, defaultAudioRender.GetAddressOf()); + const wchar_t *darWStr = defaultAudioRender.GetRawBuffer(&dARSize); + const QString defaultAudioDeviceId = QString::fromWCharArray(darWStr, dARSize); + + DeviceClass dc = mode == QAudio::AudioInput ? DeviceClass_AudioCapture : DeviceClass_AudioRender; + + QList<QByteArray> &deviceNames = mode == QAudio::AudioInput ? gMapping->inputDeviceNames : gMapping->outputDeviceNames; + QList<QString> &deviceIds = mode == QAudio::AudioInput ? gMapping->inputDeviceIds : gMapping->outputDeviceIds; + + // We need to refresh due to plugable devices (ie USB) + deviceNames.clear(); + deviceIds.clear(); + + ComPtr<IAsyncOperation<ABI::Windows::Devices::Enumeration::DeviceInformationCollection *>> op; + hr = statics->FindAllAsyncDeviceClass(dc, &op ); + RETURN_EMPTY_LIST_IF_FAILED("Could not query audio devices."); + + ComPtr<IVectorView<DeviceInformation *>> resultVector; + hr = QWinRTFunctions::await(op, resultVector.GetAddressOf()); + RETURN_EMPTY_LIST_IF_FAILED("Could not receive audio device list."); + + quint32 deviceCount; + hr = resultVector->get_Size(&deviceCount); + RETURN_EMPTY_LIST_IF_FAILED("Could not access audio device count."); + qCDebug(lcMmUtils) << "Found " << deviceCount << " audio devices for" << mode; + + for (quint32 i = 0; i < deviceCount; ++i) { + ComPtr<IDeviceInformation> item; + hr = resultVector->GetAt(i, &item); + if (FAILED(hr)) { + qErrnoWarning(hr, "Could not access audio device item."); + continue; + } + + HString hString; + quint32 size; + + hr = item->get_Name(hString.GetAddressOf()); + if (FAILED(hr)) { + qErrnoWarning(hr, "Could not access audio device name."); + continue; + } + const wchar_t *nameWStr = hString.GetRawBuffer(&size); + const QString deviceName = QString::fromWCharArray(nameWStr, size); + + hr = item->get_Id(hString.GetAddressOf()); + if (FAILED(hr)) { + qErrnoWarning(hr, "Could not access audio device id."); + continue; + } + const wchar_t *idWStr = hString.GetRawBuffer(&size); + const QString deviceId = QString::fromWCharArray(idWStr, size); + + boolean def; + hr = item->get_IsDefault(&def); + if (FAILED(hr)) { + qErrnoWarning(hr, "Could not access audio device default."); + continue; + } + + // At least on desktop no device is marked as default + // Hence use the default audio device string from above + if (!def && !defaultAudioDeviceId.isEmpty()) + def = defaultAudioDeviceId == deviceId; + + boolean enabled; + hr = item->get_IsEnabled(&enabled); + if (FAILED(hr)) { + qErrnoWarning(hr, "Could not access audio device enabled."); + continue; + } + + qCDebug(lcMmUtils) << "Audio Device:" << deviceName << " ID:" << deviceId + << " Enabled:" << enabled << " Default:" << def; + if (def) { + deviceNames.prepend(deviceName.toLocal8Bit()); + deviceIds.prepend(deviceId); + } else { + deviceNames.append(deviceName.toLocal8Bit()); + deviceIds.append(deviceId); + } + } + return deviceNames; +} + +Microsoft::WRL::ComPtr<AudioInterface> QWasapiUtils::createOrGetInterface(const QByteArray &dev, QAudio::Mode mode) +{ + qCDebug(lcMmUtils) << __FUNCTION__ << dev << mode; + Q_ASSERT((mode == QAudio::AudioInput ? gMapping->inputDeviceNames.indexOf(dev) : gMapping->outputDeviceNames.indexOf(dev)) != -1); + + Microsoft::WRL::ComPtr<AudioInterface> result; + HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([dev, mode, &result]() { + HRESULT hr; + QString id = mode == QAudio::AudioInput ? gMapping->inputDeviceIds.at(gMapping->inputDeviceNames.indexOf(dev)) : + gMapping->outputDeviceIds.at(gMapping->outputDeviceNames.indexOf(dev)); + + result = Make<AudioInterface>(); + + ComPtr<IActivateAudioInterfaceAsyncOperation> op; + + // We cannot use QWinRTFunctions::await here as that will return + // E_NO_INTERFACE. Instead we leave the lambda and wait for the + // status to get out of Activating + result->setState(AudioInterface::Activating); + hr = ActivateAudioInterfaceAsync(reinterpret_cast<LPCWSTR>(id.utf16()), __uuidof(IAudioClient), NULL, result.Get(), op.GetAddressOf()); + if (FAILED(hr)) { + qErrnoWarning(hr, "Could not invoke audio interface activation."); + result->setState(AudioInterface::Error); + } + return hr; + }); + qCDebug(lcMmUtils) << "Activation stated:" << hr; + while (result->state() == AudioInterface::Activating) { + QThread::yieldCurrentThread(); + } + qCDebug(lcMmUtils) << "Activation done:" << hr; + return result; +} + +QT_END_NAMESPACE |