blob: 497e5fa6e5ac1ea3e13b34a992c39459082b0659 [file] [log] [blame]
/****************************************************************************
**
** 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)
}
struct CoInitializer
{
CoInitializer()
{
CoInitializeEx(NULL, COINIT_MULTITHREADED);
}
~CoInitializer()
{
CoUninitialize();
}
};
static void CoInitIfNeeded()
{
static CoInitializer initializer;
}
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;
}
QByteArray QWasapiUtils::defaultDevice(QAudio::Mode mode)
{
qCDebug(lcMmUtils) << __FUNCTION__ << mode;
CoInitIfNeeded();
QList<QByteArray> &deviceNames = mode == QAudio::AudioInput ? gMapping->inputDeviceNames : gMapping->outputDeviceNames;
QList<QString> &deviceIds = mode == QAudio::AudioInput ? gMapping->inputDeviceIds : gMapping->outputDeviceIds;
if (deviceNames.isEmpty() || deviceIds.isEmpty()) // Initialize
availableDevices(mode);
if (deviceNames.isEmpty() || deviceIds.isEmpty()) // No audio devices at all
return QByteArray();
ComPtr<IMediaDeviceStatics> mediaDeviceStatics;
HRESULT hr;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Media_Devices_MediaDevice).Get(), &mediaDeviceStatics);
Q_ASSERT_SUCCEEDED(hr);
HString defaultAudioDevice;
quint32 dADSize = 0;
if (mode == QAudio::AudioOutput)
hr = mediaDeviceStatics->GetDefaultAudioRenderId(AudioDeviceRole_Default, defaultAudioDevice.GetAddressOf());
else
hr = mediaDeviceStatics->GetDefaultAudioCaptureId(AudioDeviceRole_Default, defaultAudioDevice.GetAddressOf());
const wchar_t *dadWStr = defaultAudioDevice.GetRawBuffer(&dADSize);
const QString defaultAudioDeviceId = QString::fromWCharArray(dadWStr, dADSize);
Q_ASSERT(deviceIds.indexOf(defaultAudioDeviceId) != -1);
return deviceNames.at(deviceIds.indexOf(defaultAudioDeviceId));
}
QList<QByteArray> QWasapiUtils::availableDevices(QAudio::Mode mode)
{
qCDebug(lcMmUtils) << __FUNCTION__ << mode;
CoInitIfNeeded();
ComPtr<IDeviceInformationStatics> statics;
HRESULT hr;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(),
&statics);
Q_ASSERT_SUCCEEDED(hr);
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 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;
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);
CoInitIfNeeded();
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