summaryrefslogtreecommitdiffstats
path: root/src/plugins/wasapi
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/wasapi')
-rw-r--r--src/plugins/wasapi/qwasapiaudiodeviceinfo.cpp167
-rw-r--r--src/plugins/wasapi/qwasapiaudiodeviceinfo.h85
-rw-r--r--src/plugins/wasapi/qwasapiaudioinput.cpp565
-rw-r--r--src/plugins/wasapi/qwasapiaudioinput.h123
-rw-r--r--src/plugins/wasapi/qwasapiaudiooutput.cpp578
-rw-r--r--src/plugins/wasapi/qwasapiaudiooutput.h124
-rw-r--r--src/plugins/wasapi/qwasapiplugin.cpp80
-rw-r--r--src/plugins/wasapi/qwasapiplugin.h73
-rw-r--r--src/plugins/wasapi/qwasapiutils.cpp317
-rw-r--r--src/plugins/wasapi/qwasapiutils.h146
-rw-r--r--src/plugins/wasapi/wasapi.json3
-rw-r--r--src/plugins/wasapi/wasapi.pro30
12 files changed, 2291 insertions, 0 deletions
diff --git a/src/plugins/wasapi/qwasapiaudiodeviceinfo.cpp b/src/plugins/wasapi/qwasapiaudiodeviceinfo.cpp
new file mode 100644
index 000000000..e1a46841d
--- /dev/null
+++ b/src/plugins/wasapi/qwasapiaudiodeviceinfo.cpp
@@ -0,0 +1,167 @@
+/****************************************************************************
+**
+** 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 "qwasapiaudiodeviceinfo.h"
+#include "qwasapiutils.h"
+
+#include <Audioclient.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcMmDeviceInfo, "qt.multimedia.deviceinfo")
+
+QWasapiAudioDeviceInfo::QWasapiAudioDeviceInfo(QByteArray dev, QAudio::Mode mode)
+ : m_deviceName(dev)
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__ << dev << mode;
+ m_interface = QWasapiUtils::createOrGetInterface(dev, mode);
+
+ QAudioFormat referenceFormat = m_interface->m_mixFormat;
+
+ const int rates[] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 192000};
+ for (int rate : rates) {
+ QAudioFormat f = referenceFormat;
+ f.setSampleRate(rate);
+ if (isFormatSupported(f))
+ m_sampleRates.append(rate);
+ }
+
+ for (int i = 1; i <= 18; ++i) {
+ QAudioFormat f = referenceFormat;
+ f.setChannelCount(i);
+ if (isFormatSupported(f))
+ m_channelCounts.append(i);
+ }
+
+ const int sizes[] = {8, 12, 16, 20, 24, 32, 64};
+ for (int s : sizes) {
+ QAudioFormat f = referenceFormat;
+ f.setSampleSize(s);
+ if (isFormatSupported(f))
+ m_sampleSizes.append(s);
+ }
+
+ referenceFormat.setSampleType(QAudioFormat::SignedInt);
+ if (isFormatSupported(referenceFormat))
+ m_sampleTypes.append(QAudioFormat::SignedInt);
+
+ referenceFormat.setSampleType(QAudioFormat::Float);
+ if (isFormatSupported(referenceFormat))
+ m_sampleTypes.append(QAudioFormat::Float);
+}
+
+QWasapiAudioDeviceInfo::~QWasapiAudioDeviceInfo()
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__;
+}
+
+bool QWasapiAudioDeviceInfo::isFormatSupported(const QAudioFormat& format) const
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__ << format;
+
+ WAVEFORMATEX nfmt;
+ if (!QWasapiUtils::convertToNativeFormat(format, &nfmt))
+ return false;
+
+ WAVEFORMATEX closest;
+ WAVEFORMATEX *pClosest = &closest;
+ HRESULT hr;
+ hr = m_interface->m_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &nfmt, &pClosest);
+
+ if (hr == S_OK) // S_FALSE is inside SUCCEEDED()
+ return true;
+
+ if (lcMmDeviceInfo().isDebugEnabled()) {
+ QAudioFormat f;
+ QWasapiUtils::convertFromNativeFormat(pClosest, &f);
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__ << hr << "Closest match is:" << f;
+ }
+
+ return false;
+}
+
+QAudioFormat QWasapiAudioDeviceInfo::preferredFormat() const
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__;
+ return m_interface->m_mixFormat;
+}
+
+QString QWasapiAudioDeviceInfo::deviceName() const
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__;
+ return m_deviceName;
+}
+
+QStringList QWasapiAudioDeviceInfo::supportedCodecs()
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__;
+ return QStringList() << QStringLiteral("audio/pcm");
+}
+
+QList<int> QWasapiAudioDeviceInfo::supportedSampleRates()
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__;
+ return m_sampleRates;
+}
+
+QList<int> QWasapiAudioDeviceInfo::supportedChannelCounts()
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__;
+ return m_channelCounts;
+}
+
+QList<int> QWasapiAudioDeviceInfo::supportedSampleSizes()
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__;
+ return m_sampleSizes;
+}
+
+QList<QAudioFormat::Endian> QWasapiAudioDeviceInfo::supportedByteOrders()
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__;
+ return QList<QAudioFormat::Endian>() << m_interface->m_mixFormat.byteOrder();
+}
+
+QList<QAudioFormat::SampleType> QWasapiAudioDeviceInfo::supportedSampleTypes()
+{
+ qCDebug(lcMmDeviceInfo) << __FUNCTION__;
+ return m_sampleTypes;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/wasapi/qwasapiaudiodeviceinfo.h b/src/plugins/wasapi/qwasapiaudiodeviceinfo.h
new file mode 100644
index 000000000..97966c171
--- /dev/null
+++ b/src/plugins/wasapi/qwasapiaudiodeviceinfo.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QWASAPIAUDIODEVICEINFO_H
+#define QWASAPIAUDIODEVICEINFO_H
+
+#include <QtCore/QList>
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QStringList>
+#include <QtMultimedia/QAbstractAudioDeviceInfo>
+#include <QtMultimedia/QAudio>
+#include <QtMultimedia/QAudioFormat>
+
+#include <wrl.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcMmDeviceInfo)
+
+class AudioInterface;
+class QWasapiAudioDeviceInfo : public QAbstractAudioDeviceInfo
+{
+ Q_OBJECT
+public:
+ explicit QWasapiAudioDeviceInfo(QByteArray dev,QAudio::Mode mode);
+ ~QWasapiAudioDeviceInfo();
+
+ QAudioFormat preferredFormat() const Q_DECL_OVERRIDE;
+ bool isFormatSupported(const QAudioFormat& format) const Q_DECL_OVERRIDE;
+ QString deviceName() const Q_DECL_OVERRIDE;
+ QStringList supportedCodecs() Q_DECL_OVERRIDE;
+ QList<int> supportedSampleRates() Q_DECL_OVERRIDE;
+ QList<int> supportedChannelCounts() Q_DECL_OVERRIDE;
+ QList<int> supportedSampleSizes() Q_DECL_OVERRIDE;
+ QList<QAudioFormat::Endian> supportedByteOrders() Q_DECL_OVERRIDE;
+ QList<QAudioFormat::SampleType> supportedSampleTypes() Q_DECL_OVERRIDE;
+
+private:
+ Microsoft::WRL::ComPtr<AudioInterface> m_interface;
+ QString m_deviceName;
+ QList<int> m_sampleRates;
+ QList<int> m_channelCounts;
+ QList<int> m_sampleSizes;
+ QList<QAudioFormat::SampleType> m_sampleTypes;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWASAPIAUDIODEVICEINFO_H
diff --git a/src/plugins/wasapi/qwasapiaudioinput.cpp b/src/plugins/wasapi/qwasapiaudioinput.cpp
new file mode 100644
index 000000000..22a77df38
--- /dev/null
+++ b/src/plugins/wasapi/qwasapiaudioinput.cpp
@@ -0,0 +1,565 @@
+/****************************************************************************
+**
+** 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 "qwasapiaudioinput.h"
+#include "qwasapiutils.h"
+
+#include <QtCore/QMutexLocker>
+#include <QtCore/QThread>
+#include <QtCore/qfunctions_winrt.h>
+
+#include <Audioclient.h>
+
+using namespace Microsoft::WRL;
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcMmAudioInput, "qt.multimedia.audioinput")
+
+class WasapiInputDevicePrivate : public QIODevice
+{
+ Q_OBJECT
+public:
+ WasapiInputDevicePrivate(QWasapiAudioInput* input);
+ ~WasapiInputDevicePrivate();
+
+ qint64 readData(char* data, qint64 len);
+ qint64 writeData(const char* data, qint64 len);
+
+private:
+ QWasapiAudioInput *m_input;
+};
+
+WasapiInputDevicePrivate::WasapiInputDevicePrivate(QWasapiAudioInput* input)
+ : m_input(input)
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+}
+
+WasapiInputDevicePrivate::~WasapiInputDevicePrivate()
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+}
+
+qint64 WasapiInputDevicePrivate::readData(char* data, qint64 len)
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+
+ const quint32 channelCount = m_input->m_currentFormat.channelCount();
+ const quint32 sampleBytes = m_input->m_currentFormat.sampleSize() / 8;
+
+ const quint32 requestedFrames = len / channelCount / sampleBytes;
+ quint32 availableFrames = 0;
+ HRESULT hr = m_input->m_capture->GetNextPacketSize(&availableFrames);
+
+ quint32 readFrames = qMin(requestedFrames, availableFrames);
+ const qint64 readBytes = readFrames * channelCount * sampleBytes;
+
+ BYTE* buffer;
+ DWORD flags;
+
+ QMutexLocker locker(&m_input->m_mutex);
+
+ quint64 devicePosition;
+ hr = m_input->m_capture->GetBuffer(&buffer, &readFrames, &flags, &devicePosition, NULL);
+ if (hr != S_OK) {
+ m_input->m_currentError = QAudio::FatalError;
+ emit m_input->errorChanged(m_input->m_currentError);
+ hr = m_input->m_capture->ReleaseBuffer(readFrames);
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << "Could not acquire input buffer.";
+ return 0;
+ }
+ if (Q_UNLIKELY(flags & AUDCLNT_BUFFERFLAGS_SILENT)) {
+ // In case this flag is set, user is supposed to ignore the content
+ // of the buffer and manually write silence
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << "AUDCLNT_BUFFERFLAGS_SILENT: "
+ << "Ignoring buffer and writing silence.";
+ memset(data, 0, readBytes);
+ } else {
+ memcpy(data, buffer, readBytes);
+ }
+
+ hr = m_input->m_capture->ReleaseBuffer(readFrames);
+ if (hr != S_OK)
+ qFatal("Could not release buffer");
+
+ if (m_input->m_interval && m_input->m_openTime.elapsed() - m_input->m_openTimeOffset > m_input->m_interval) {
+ emit m_input->notify();
+ m_input->m_openTimeOffset = m_input->m_openTime.elapsed();
+ }
+
+ m_input->m_bytesProcessed += readBytes;
+
+ if (m_input->m_currentState != QAudio::ActiveState) {
+ m_input->m_currentState = QAudio::ActiveState;
+ emit m_input->stateChanged(m_input->m_currentState);
+ }
+ return readBytes;
+}
+
+qint64 WasapiInputDevicePrivate::writeData(const char* data, qint64 len)
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ Q_UNUSED(data)
+ Q_UNUSED(len)
+ return 0;
+}
+
+QWasapiAudioInput::QWasapiAudioInput(const QByteArray &device)
+ : m_deviceName(device)
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900
+ , m_volumeCache(qreal(1.))
+#endif
+ , m_currentState(QAudio::StoppedState)
+ , m_currentError(QAudio::NoError)
+ , m_bufferFrames(0)
+ , m_bufferBytes(4096)
+ , m_eventThread(0)
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << device;
+ Q_UNUSED(device)
+}
+
+QWasapiAudioInput::~QWasapiAudioInput()
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ stop();
+}
+
+void QWasapiAudioInput::setVolume(qreal vol)
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << vol;
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900 // Volume is only supported MSVC2015 and beyond
+ m_volumeCache = vol;
+ if (m_volumeControl) {
+ quint32 channelCount;
+ HRESULT hr = m_volumeControl->GetChannelCount(&channelCount);
+ for (quint32 i = 0; i < channelCount; ++i) {
+ hr = m_volumeControl->SetChannelVolume(i, vol);
+ RETURN_VOID_IF_FAILED("Could not set audio volume.");
+ }
+ }
+#endif // defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900
+}
+
+qreal QWasapiAudioInput::volume() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900 // Volume is only supported MSVC2015 and beyond
+ return m_volumeCache;
+#else
+ return qreal(1.0);
+#endif
+}
+
+void QWasapiAudioInput::process()
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ DWORD waitRet;
+
+ m_processing = true;
+ do {
+ waitRet = WaitForSingleObjectEx(m_event, 2000, FALSE);
+ if (waitRet != WAIT_OBJECT_0) {
+ qFatal("Returned while waiting for event.");
+ return;
+ }
+
+ QMutexLocker locker(&m_mutex);
+
+ if (m_currentState != QAudio::ActiveState && m_currentState != QAudio::IdleState)
+ break;
+ QMetaObject::invokeMethod(this, "processBuffer", Qt::QueuedConnection);
+ } while (m_processing);
+}
+
+void QWasapiAudioInput::processBuffer()
+{
+ if (!m_pullMode) {
+ emit m_eventDevice->readyRead();
+ ResetEvent(m_event);
+ return;
+ }
+
+ QMutexLocker locker(&m_mutex);
+ const quint32 channelCount = m_currentFormat.channelCount();
+ const quint32 sampleBytes = m_currentFormat.sampleSize() / 8;
+ BYTE* buffer;
+ HRESULT hr;
+
+ quint32 packetFrames;
+ hr = m_capture->GetNextPacketSize(&packetFrames);
+
+ while (packetFrames != 0 && m_currentState == QAudio::ActiveState) {
+ DWORD flags;
+ quint64 devicePosition;
+ hr = m_capture->GetBuffer(&buffer, &packetFrames, &flags, &devicePosition, NULL);
+ if (hr != S_OK) {
+ m_currentError = QAudio::FatalError;
+ emit errorChanged(m_currentError);
+ // Also Error Buffers need to be released
+ hr = m_capture->ReleaseBuffer(packetFrames);
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << "Could not acquire input buffer.";
+ return;
+ }
+ const quint32 writeBytes = packetFrames * channelCount * sampleBytes;
+ if (Q_UNLIKELY(flags & AUDCLNT_BUFFERFLAGS_SILENT)) {
+ // In case this flag is set, user is supposed to ignore the content
+ // of the buffer and manually write silence
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << "AUDCLNT_BUFFERFLAGS_SILENT: "
+ << "Ignoring buffer and writing silence.";
+ buffer = new BYTE[writeBytes];
+ memset(buffer, 0, writeBytes);
+ }
+
+ const qint64 written = m_eventDevice->write(reinterpret_cast<const char *>(buffer), writeBytes);
+
+ if (Q_UNLIKELY(flags & AUDCLNT_BUFFERFLAGS_SILENT))
+ delete [] buffer;
+
+ if (written < static_cast<qint64>(writeBytes)) {
+ if (m_currentError != QAudio::UnderrunError) {
+ m_currentError = QAudio::UnderrunError;
+ emit errorChanged(m_currentError);
+ }
+ }
+ hr = m_capture->ReleaseBuffer(packetFrames);
+ if (hr != S_OK)
+ qFatal("Could not release buffer");
+
+ m_bytesProcessed += writeBytes;
+
+ hr = m_capture->GetNextPacketSize(&packetFrames);
+ }
+ ResetEvent(m_event);
+
+ if (m_interval && m_openTime.elapsed() - m_openTimeOffset > m_interval) {
+ emit notify();
+ m_openTimeOffset = m_openTime.elapsed();
+ }
+ m_processing = m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState;
+}
+
+bool QWasapiAudioInput::initStart(bool pull)
+{
+ if (m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState)
+ stop();
+
+ QMutexLocker locker(&m_mutex);
+
+ m_interface = QWasapiUtils::createOrGetInterface(m_deviceName, QAudio::AudioInput);
+ Q_ASSERT(m_interface);
+
+ m_pullMode = pull;
+ WAVEFORMATEX nFmt;
+ WAVEFORMATEX closest;
+ WAVEFORMATEX *pClose = &closest;
+
+ if (!m_currentFormat.isValid() || !QWasapiUtils::convertToNativeFormat(m_currentFormat, &nFmt)) {
+ m_currentError = QAudio::OpenError;
+ emit errorChanged(m_currentError);
+ return false;
+ }
+
+ HRESULT hr;
+
+ hr = m_interface->m_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &nFmt, &pClose);
+ if (hr != S_OK) {
+ m_currentError = QAudio::OpenError;
+ emit errorChanged(m_currentError);
+ return false;
+ }
+
+ REFERENCE_TIME t = ((10000.0 * 10000 / nFmt.nSamplesPerSec * 1024) + 0.5);
+ if (m_bufferBytes)
+ t = m_currentFormat.durationForBytes(m_bufferBytes) * 10;
+
+ const DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ hr = m_interface->m_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, t, 0, &nFmt, NULL);
+ EMIT_RETURN_FALSE_IF_FAILED("Could not initialize audio client.", QAudio::OpenError)
+
+ hr = m_interface->m_client->GetService(IID_PPV_ARGS(&m_capture));
+ EMIT_RETURN_FALSE_IF_FAILED("Could not acquire render service.", QAudio::OpenError)
+
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900 // Volume is only supported MSVC2015 and beyond for WinRT
+ hr = m_interface->m_client->GetService(IID_PPV_ARGS(&m_volumeControl));
+ if (FAILED(hr))
+ qCDebug(lcMmAudioInput) << "Could not acquire volume control.";
+#endif // defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900
+
+ hr = m_interface->m_client->GetBufferSize(&m_bufferFrames);
+ EMIT_RETURN_FALSE_IF_FAILED("Could not access buffer size.", QAudio::OpenError)
+
+ m_event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
+ m_eventThread = new QWasapiProcessThread(this, false);
+ m_eventThread->m_event = m_event;
+ hr = m_interface->m_client->SetEventHandle(m_event);
+ EMIT_RETURN_FALSE_IF_FAILED("Could not set event handle.", QAudio::OpenError)
+
+ if (!m_pullMode) {
+ m_eventDevice = new WasapiInputDevicePrivate(this);
+ m_eventDevice->open(QIODevice::ReadOnly|QIODevice::Unbuffered);
+ }
+
+ hr = m_interface->m_client->Start();
+ EMIT_RETURN_FALSE_IF_FAILED("Could not start audio render client.", QAudio::OpenError)
+
+ m_openTime.restart();
+ m_openTimeOffset = 0;
+ m_bytesProcessed = 0;
+
+ return true;
+}
+
+QAudio::Error QWasapiAudioInput::error() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << m_currentError;
+ return m_currentError;
+}
+
+QAudio::State QWasapiAudioInput::state() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ return m_currentState;
+}
+
+void QWasapiAudioInput::start(QIODevice* device)
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << device;
+ if (!initStart(true)) {
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << "failed";
+ return;
+ }
+ m_eventDevice = device;
+
+ m_mutex.lock();
+ m_currentState = QAudio::ActiveState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+ m_eventThread->start();
+}
+
+QIODevice *QWasapiAudioInput::start()
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ if (!initStart(false)) {
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << "failed";
+ return nullptr;
+ }
+
+ m_mutex.lock();
+ m_currentState = QAudio::IdleState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+ m_eventThread->start();
+ return m_eventDevice;
+}
+
+void QWasapiAudioInput::stop()
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ if (m_currentState == QAudio::StoppedState)
+ return;
+
+ m_mutex.lock();
+ m_currentState = QAudio::StoppedState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+
+ if (m_currentError != QAudio::NoError) {
+ m_mutex.lock();
+ m_currentError = QAudio::NoError;
+ m_mutex.unlock();
+ emit errorChanged(m_currentError);
+ }
+
+ HRESULT hr = m_interface->m_client->Stop();
+ hr = m_interface->m_client->Reset();
+
+ if (m_eventThread) {
+ SetEvent(m_eventThread->m_event);
+ while (m_eventThread->isRunning())
+ QThread::yieldCurrentThread();
+ m_eventThread->deleteLater();
+ m_eventThread = 0;
+ }
+}
+
+void QWasapiAudioInput::setFormat(const QAudioFormat& fmt)
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << fmt;
+ if (m_currentState != QAudio::StoppedState)
+ return;
+ m_currentFormat = fmt;
+}
+
+QAudioFormat QWasapiAudioInput::format() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ return m_currentFormat;
+}
+
+int QWasapiAudioInput::bytesReady() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ if (m_currentState != QAudio::IdleState && m_currentState != QAudio::ActiveState)
+ return 0;
+
+ const quint32 channelCount = m_currentFormat.channelCount();
+ const quint32 sampleBytes = m_currentFormat.sampleSize() / 8;
+
+ quint32 packetFrames;
+ HRESULT hr = m_capture->GetNextPacketSize(&packetFrames);
+
+ if (FAILED(hr))
+ return 0;
+ const quint32 writeBytes = packetFrames * channelCount * sampleBytes;
+
+ return writeBytes;
+}
+
+void QWasapiAudioInput::resume()
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ if (m_currentState != QAudio::SuspendedState)
+ return;
+
+ HRESULT hr = m_interface->m_client->Start();
+ EMIT_RETURN_VOID_IF_FAILED("Could not start audio render client.", QAudio::FatalError)
+
+ m_mutex.lock();
+ m_currentError = QAudio::NoError;
+ m_currentState = QAudio::ActiveState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+ m_eventThread->start();
+}
+
+void QWasapiAudioInput::setBufferSize(int value)
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << value;
+ if (m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState)
+ return;
+ m_bufferBytes = value;
+}
+
+int QWasapiAudioInput::bufferSize() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ if (m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState)
+ return m_bufferFrames * m_currentFormat.channelCount()* m_currentFormat.sampleSize() / 8;
+
+ return m_bufferBytes;
+}
+
+int QWasapiAudioInput::periodSize() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ REFERENCE_TIME defaultDevicePeriod;
+ REFERENCE_TIME minimumPeriod;
+ HRESULT hr = m_interface->m_client->GetDevicePeriod(&defaultDevicePeriod, &minimumPeriod);
+ if (FAILED(hr))
+ return 0;
+ const int res = m_currentFormat.bytesForDuration(minimumPeriod / 10);
+ return res;
+}
+
+void QWasapiAudioInput::setNotifyInterval(int ms)
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << ms;
+ m_interval = qMax(0, ms);
+}
+
+int QWasapiAudioInput::notifyInterval() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ return m_interval;
+}
+
+qint64 QWasapiAudioInput::processedUSecs() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ if (m_currentState == QAudio::StoppedState)
+ return 0;
+ qint64 res = qint64(1000000) * m_bytesProcessed /
+ (m_currentFormat.channelCount() * (m_currentFormat.sampleSize() / 8)) /
+ m_currentFormat.sampleRate();
+
+ return res;
+}
+
+void QWasapiAudioInput::suspend()
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ if (m_currentState != QAudio::ActiveState)
+ return;
+
+ m_mutex.lock();
+ m_currentState = QAudio::SuspendedState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+
+ HRESULT hr = m_interface->m_client->Stop();
+ EMIT_RETURN_VOID_IF_FAILED("Could not suspend audio render client.", QAudio::FatalError);
+
+ if (m_eventThread) {
+ SetEvent(m_eventThread->m_event);
+ while (m_eventThread->isRunning())
+ QThread::yieldCurrentThread();
+ }
+ qCDebug(lcMmAudioInput) << __FUNCTION__ << "Thread has stopped";
+}
+
+qint64 QWasapiAudioInput::elapsedUSecs() const
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ if (m_currentState == QAudio::StoppedState)
+ return 0;
+ return m_openTime.elapsed() * qint64(1000);
+}
+
+void QWasapiAudioInput::reset()
+{
+ qCDebug(lcMmAudioInput) << __FUNCTION__;
+ stop();
+}
+
+QT_END_NAMESPACE
+
+#include "qwasapiaudioinput.moc"
diff --git a/src/plugins/wasapi/qwasapiaudioinput.h b/src/plugins/wasapi/qwasapiaudioinput.h
new file mode 100644
index 000000000..3fd7315c1
--- /dev/null
+++ b/src/plugins/wasapi/qwasapiaudioinput.h
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QAUDIOINPUTWASAPI_H
+#define QAUDIOINPUTWASAPI_H
+
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QMutex>
+#include <QtCore/QTime>
+#include <QtMultimedia/QAbstractAudioInput>
+#include <QtMultimedia/QAudio>
+
+#include <wrl.h>
+
+struct IAudioCaptureClient;
+struct IAudioStreamVolume;
+
+QT_BEGIN_NAMESPACE
+
+class AudioInterface;
+class WasapiInputDevicePrivate;
+class QWasapiProcessThread;
+
+Q_DECLARE_LOGGING_CATEGORY(lcMmAudioInput)
+
+class QWasapiAudioInput : public QAbstractAudioInput
+{
+ Q_OBJECT
+public:
+ explicit QWasapiAudioInput(const QByteArray &device);
+ ~QWasapiAudioInput();
+
+ void start(QIODevice* device) Q_DECL_OVERRIDE;
+ QIODevice* start() Q_DECL_OVERRIDE;
+ void stop() Q_DECL_OVERRIDE;
+ void reset() Q_DECL_OVERRIDE;
+ void suspend() Q_DECL_OVERRIDE;
+ void resume() Q_DECL_OVERRIDE;
+ int bytesReady() const Q_DECL_OVERRIDE;
+ int periodSize() const Q_DECL_OVERRIDE;
+ void setBufferSize(int value) Q_DECL_OVERRIDE;
+ int bufferSize() const Q_DECL_OVERRIDE;
+ void setNotifyInterval(int milliSeconds) Q_DECL_OVERRIDE;
+ int notifyInterval() const Q_DECL_OVERRIDE;
+ qint64 processedUSecs() const Q_DECL_OVERRIDE;
+ qint64 elapsedUSecs() const Q_DECL_OVERRIDE;
+ QAudio::Error error() const Q_DECL_OVERRIDE;
+ QAudio::State state() const Q_DECL_OVERRIDE;
+ void setFormat(const QAudioFormat& fmt) Q_DECL_OVERRIDE;
+ QAudioFormat format() const Q_DECL_OVERRIDE;
+ void setVolume(qreal) Q_DECL_OVERRIDE;
+ qreal volume() const Q_DECL_OVERRIDE;
+
+ void process();
+public slots:
+ void processBuffer();
+private:
+ bool initStart(bool pull);
+ friend class WasapiInputDevicePrivate;
+ friend class WasapiInputPrivate;
+ QByteArray m_deviceName;
+ Microsoft::WRL::ComPtr<AudioInterface> m_interface;
+ Microsoft::WRL::ComPtr<IAudioCaptureClient> m_capture;
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900
+ Microsoft::WRL::ComPtr<IAudioStreamVolume> m_volumeControl;
+ qreal m_volumeCache;
+#endif
+ QMutex m_mutex;
+ QAudio::State m_currentState;
+ QAudio::Error m_currentError;
+ QAudioFormat m_currentFormat;
+ qint64 m_bytesProcessed;
+ QTime m_openTime;
+ int m_openTimeOffset;
+ int m_interval;
+ bool m_pullMode;
+ quint32 m_bufferFrames;
+ quint32 m_bufferBytes;
+ HANDLE m_event;
+ QWasapiProcessThread *m_eventThread;
+ QAtomicInt m_processing;
+ QIODevice *m_eventDevice;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/wasapi/qwasapiaudiooutput.cpp b/src/plugins/wasapi/qwasapiaudiooutput.cpp
new file mode 100644
index 000000000..1b0f64451
--- /dev/null
+++ b/src/plugins/wasapi/qwasapiaudiooutput.cpp
@@ -0,0 +1,578 @@
+/****************************************************************************
+**
+** 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 "qwasapiaudiooutput.h"
+#include "qwasapiutils.h"
+#include <QtCore/qfunctions_winrt.h>
+#include <QtCore/QMutexLocker>
+#include <QtCore/QThread>
+#include <QtCore/QTimer>
+
+#include <Audioclient.h>
+#include <functional>
+
+using namespace Microsoft::WRL;
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcMmAudioOutput, "qt.multimedia.audiooutput")
+
+class WasapiOutputDevicePrivate : public QIODevice
+{
+ Q_OBJECT
+public:
+ WasapiOutputDevicePrivate(QWasapiAudioOutput* output);
+ ~WasapiOutputDevicePrivate();
+
+ qint64 readData(char* data, qint64 len);
+ qint64 writeData(const char* data, qint64 len);
+
+private:
+ QWasapiAudioOutput *m_output;
+ QTimer m_timer;
+};
+
+WasapiOutputDevicePrivate::WasapiOutputDevicePrivate(QWasapiAudioOutput* output)
+ : m_output(output)
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+
+ m_timer.setSingleShot(true);
+ connect(&m_timer, &QTimer::timeout, [=](){
+ if (m_output->m_currentState == QAudio::ActiveState) {
+ m_output->m_currentState = QAudio::IdleState;
+ emit m_output->stateChanged(m_output->m_currentState);
+ m_output->m_currentError = QAudio::UnderrunError;
+ emit m_output->errorChanged(m_output->m_currentError);
+ }
+ });
+}
+
+WasapiOutputDevicePrivate::~WasapiOutputDevicePrivate()
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+}
+
+qint64 WasapiOutputDevicePrivate::readData(char* data, qint64 len)
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ Q_UNUSED(data)
+ Q_UNUSED(len)
+
+ return 0;
+}
+
+qint64 WasapiOutputDevicePrivate::writeData(const char* data, qint64 len)
+{
+ if (m_output->state() != QAudio::ActiveState && m_output->state() != QAudio::IdleState)
+ return 0;
+
+ QMutexLocker locker(&m_output->m_mutex);
+ m_timer.stop();
+
+ const quint32 channelCount = m_output->m_currentFormat.channelCount();
+ const quint32 sampleBytes = m_output->m_currentFormat.sampleSize() / 8;
+ const quint32 freeBytes = static_cast<quint32>(m_output->bytesFree());
+ const quint32 bytesToWrite = qMin(freeBytes, static_cast<quint32>(len));
+ const quint32 framesToWrite = bytesToWrite / (channelCount * sampleBytes);
+
+ BYTE *buffer;
+ HRESULT hr;
+ hr = m_output->m_renderer->GetBuffer(framesToWrite, &buffer);
+ if (hr != S_OK) {
+ m_output->m_currentError = QAudio::UnderrunError;
+ QMetaObject::invokeMethod(m_output, "errorChanged", Qt::QueuedConnection,
+ Q_ARG(QAudio::Error, QAudio::UnderrunError));
+ // Also Error Buffers need to be released
+ hr = m_output->m_renderer->ReleaseBuffer(framesToWrite, 0);
+ return 0;
+ }
+
+ memcpy_s(buffer, bytesToWrite, data, bytesToWrite);
+
+ hr = m_output->m_renderer->ReleaseBuffer(framesToWrite, 0);
+ if (hr != S_OK)
+ qFatal("Could not release buffer");
+
+ if (m_output->m_interval && m_output->m_openTime.elapsed() - m_output->m_openTimeOffset > m_output->m_interval) {
+ QMetaObject::invokeMethod(m_output, "notify", Qt::QueuedConnection);
+ m_output->m_openTimeOffset = m_output->m_openTime.elapsed();
+ }
+
+ m_output->m_bytesProcessed += bytesToWrite;
+
+ if (m_output->m_currentState != QAudio::ActiveState) {
+ m_output->m_currentState = QAudio::ActiveState;
+ emit m_output->stateChanged(m_output->m_currentState);
+ }
+ if (m_output->m_currentError != QAudio::NoError) {
+ m_output->m_currentError = QAudio::NoError;
+ emit m_output->errorChanged(m_output->m_currentError);
+ }
+
+ quint32 paddingFrames;
+ hr = m_output->m_interface->m_client->GetCurrentPadding(&paddingFrames);
+ const quint32 paddingBytes = paddingFrames * m_output->m_currentFormat.channelCount() * m_output->m_currentFormat.sampleSize() / 8;
+
+ m_timer.setInterval(m_output->m_currentFormat.durationForBytes(paddingBytes) / 1000);
+ m_timer.start();
+ return bytesToWrite;
+}
+
+QWasapiAudioOutput::QWasapiAudioOutput(const QByteArray &device)
+ : m_deviceName(device)
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900
+ , m_volumeCache(qreal(1.))
+#endif
+ , m_currentState(QAudio::StoppedState)
+ , m_currentError(QAudio::NoError)
+ , m_interval(1000)
+ , m_pullMode(true)
+ , m_bufferFrames(0)
+ , m_bufferBytes(4096)
+ , m_eventThread(0)
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << device;
+}
+
+QWasapiAudioOutput::~QWasapiAudioOutput()
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ stop();
+}
+
+void QWasapiAudioOutput::setVolume(qreal vol)
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << vol;
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900 // Volume is only supported MSVC2015 and beyond for WinRT
+ m_volumeCache = vol;
+ if (m_volumeControl) {
+ quint32 channelCount;
+ HRESULT hr = m_volumeControl->GetChannelCount(&channelCount);
+ for (quint32 i = 0; i < channelCount; ++i) {
+ hr = m_volumeControl->SetChannelVolume(i, vol);
+ RETURN_VOID_IF_FAILED("Could not set audio volume.");
+ }
+ }
+#endif // defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900
+}
+
+qreal QWasapiAudioOutput::volume() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900 // Volume is only supported MSVC2015 and beyond for WinRT
+ return m_volumeCache;
+#else
+ return qreal(1.0);
+#endif
+}
+
+void QWasapiAudioOutput::process()
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ DWORD waitRet;
+
+ m_processing = true;
+ do {
+ waitRet = WaitForSingleObjectEx(m_event, 2000, FALSE);
+ if (waitRet != WAIT_OBJECT_0) {
+ qFatal("Returned while waiting for event.");
+ return;
+ }
+
+ QMutexLocker locker(&m_mutex);
+
+ if (m_currentState != QAudio::ActiveState && m_currentState != QAudio::IdleState)
+ break;
+ QMetaObject::invokeMethod(this, "processBuffer", Qt::QueuedConnection);
+ } while (m_processing);
+}
+
+void QWasapiAudioOutput::processBuffer()
+{
+ QMutexLocker locker(&m_mutex);
+
+ const quint32 channelCount = m_currentFormat.channelCount();
+ const quint32 sampleBytes = m_currentFormat.sampleSize() / 8;
+ BYTE* buffer;
+ HRESULT hr;
+
+ quint32 paddingFrames;
+ hr = m_interface->m_client->GetCurrentPadding(&paddingFrames);
+
+ const quint32 availableFrames = m_bufferFrames - paddingFrames;
+ hr = m_renderer->GetBuffer(availableFrames, &buffer);
+ if (hr != S_OK) {
+ m_currentError = QAudio::UnderrunError;
+ emit errorChanged(m_currentError);
+ // Also Error Buffers need to be released
+ hr = m_renderer->ReleaseBuffer(availableFrames, 0);
+ ResetEvent(m_event);
+ return;
+ }
+
+ const quint32 readBytes = availableFrames * channelCount * sampleBytes;
+ const qint64 read = m_eventDevice->read((char*)buffer, readBytes);
+ if (read < static_cast<qint64>(readBytes)) {
+ // Fill the rest of the buffer with zero to avoid audio glitches
+ if (m_currentError != QAudio::UnderrunError) {
+ m_currentError = QAudio::UnderrunError;
+ emit errorChanged(m_currentError);
+ }
+ if (m_currentState != QAudio::IdleState) {
+ m_currentState = QAudio::IdleState;
+ emit stateChanged(m_currentState);
+ }
+ }
+
+ hr = m_renderer->ReleaseBuffer(availableFrames, 0);
+ if (hr != S_OK)
+ qFatal("Could not release buffer");
+ ResetEvent(m_event);
+
+ if (m_interval && m_openTime.elapsed() - m_openTimeOffset > m_interval) {
+ emit notify();
+ m_openTimeOffset = m_openTime.elapsed();
+ }
+
+ m_bytesProcessed += read;
+ m_processing = m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState;
+}
+
+bool QWasapiAudioOutput::initStart(bool pull)
+{
+ if (m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState)
+ stop();
+
+ QMutexLocker locker(&m_mutex);
+
+ m_interface = QWasapiUtils::createOrGetInterface(m_deviceName, QAudio::AudioOutput);
+ Q_ASSERT(m_interface);
+
+ m_pullMode = pull;
+ WAVEFORMATEX nFmt;
+ WAVEFORMATEX closest;
+ WAVEFORMATEX *pClose = &closest;
+
+ if (!m_currentFormat.isValid() || !QWasapiUtils::convertToNativeFormat(m_currentFormat, &nFmt)) {
+ m_currentError = QAudio::OpenError;
+ emit errorChanged(m_currentError);
+ return false;
+ }
+
+ HRESULT hr;
+
+ hr = m_interface->m_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &nFmt, &pClose);
+ if (hr != S_OK) {
+ m_currentError = QAudio::OpenError;
+ emit errorChanged(m_currentError);
+ return false;
+ }
+
+ REFERENCE_TIME t = ((10000.0 * 10000 / nFmt.nSamplesPerSec * 1024) + 0.5);
+ if (m_bufferBytes)
+ t = m_currentFormat.durationForBytes(m_bufferBytes) * 100;
+
+ DWORD flags = pull ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0;
+ hr = m_interface->m_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, t, 0, &nFmt, NULL);
+ EMIT_RETURN_FALSE_IF_FAILED("Could not initialize audio client.", QAudio::OpenError)
+
+ hr = m_interface->m_client->GetService(IID_PPV_ARGS(&m_renderer));
+ EMIT_RETURN_FALSE_IF_FAILED("Could not acquire render service.", QAudio::OpenError)
+
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900 // Volume is only supported MSVC2015 and beyond
+ hr = m_interface->m_client->GetService(IID_PPV_ARGS(&m_volumeControl));
+ if (FAILED(hr))
+ qCDebug(lcMmAudioOutput) << "Could not acquire volume control.";
+#endif // defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900
+
+ hr = m_interface->m_client->GetBufferSize(&m_bufferFrames);
+ EMIT_RETURN_FALSE_IF_FAILED("Could not access buffer size.", QAudio::OpenError)
+
+ if (m_pullMode) {
+ m_eventThread = new QWasapiProcessThread(this);
+ m_event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
+ m_eventThread->m_event = m_event;
+
+ hr = m_interface->m_client->SetEventHandle(m_event);
+ EMIT_RETURN_FALSE_IF_FAILED("Could not set event handle.", QAudio::OpenError)
+ } else {
+ m_eventDevice = new WasapiOutputDevicePrivate(this);
+ m_eventDevice->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
+ }
+ // Send some initial data, do not exit on failure, latest in process
+ // those an error will be caught
+ BYTE *pdata = nullptr;
+ hr = m_renderer->GetBuffer(m_bufferFrames, &pdata);
+ hr = m_renderer->ReleaseBuffer(m_bufferFrames, AUDCLNT_BUFFERFLAGS_SILENT);
+
+ hr = m_interface->m_client->Start();
+ EMIT_RETURN_FALSE_IF_FAILED("Could not start audio render client.", QAudio::OpenError)
+
+ m_openTime.restart();
+ m_openTimeOffset = 0;
+ m_bytesProcessed = 0;
+ return true;
+}
+
+QAudio::Error QWasapiAudioOutput::error() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << m_currentError;
+ return m_currentError;
+}
+
+QAudio::State QWasapiAudioOutput::state() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ return m_currentState;
+}
+
+void QWasapiAudioOutput::start(QIODevice *device)
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << device;
+ if (!initStart(true)) {
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << "failed";
+ return;
+ }
+ m_eventDevice = device;
+
+ m_mutex.lock();
+ m_currentState = QAudio::ActiveState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+ m_eventThread->start();
+}
+
+QIODevice *QWasapiAudioOutput::start()
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+
+ if (!initStart(false)) {
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << "failed";
+ return nullptr;
+ }
+
+ m_mutex.lock();
+ m_currentState = QAudio::IdleState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+
+ return m_eventDevice;
+}
+
+void QWasapiAudioOutput::stop()
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ if (m_currentState == QAudio::StoppedState)
+ return;
+
+ if (!m_pullMode) {
+ HRESULT hr;
+ hr = m_interface->m_client->Stop();
+ hr = m_interface->m_client->Reset();
+ }
+
+ m_mutex.lock();
+ m_currentState = QAudio::StoppedState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+ if (m_currentError != QAudio::NoError) {
+ m_mutex.lock();
+ m_currentError = QAudio::NoError;
+ m_mutex.unlock();
+ emit errorChanged(m_currentError);
+ }
+
+ HRESULT hr = m_interface->m_client->Stop();
+ if (m_currentState == QAudio::StoppedState) {
+ hr = m_interface->m_client->Reset();
+ }
+
+ if (m_eventThread) {
+ SetEvent(m_eventThread->m_event);
+ while (m_eventThread->isRunning())
+ QThread::yieldCurrentThread();
+ m_eventThread->deleteLater();
+ m_eventThread = 0;
+ }
+}
+
+int QWasapiAudioOutput::bytesFree() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ if (m_currentState != QAudio::ActiveState && m_currentState != QAudio::IdleState)
+ return 0;
+
+ quint32 paddingFrames;
+ HRESULT hr = m_interface->m_client->GetCurrentPadding(&paddingFrames);
+ if (FAILED(hr)) {
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << "Could not query padding frames.";
+ return bufferSize();
+ }
+
+ const quint32 availableFrames = m_bufferFrames - paddingFrames;
+ const quint32 res = availableFrames * m_currentFormat.channelCount() * m_currentFormat.sampleSize() / 8;
+ return res;
+}
+
+int QWasapiAudioOutput::periodSize() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ REFERENCE_TIME defaultDevicePeriod;
+ HRESULT hr = m_interface->m_client->GetDevicePeriod(&defaultDevicePeriod, NULL);
+ if (FAILED(hr))
+ return 0;
+ const QAudioFormat f = m_currentFormat.isValid() ? m_currentFormat : m_interface->m_mixFormat;
+ const int res = m_currentFormat.bytesForDuration(defaultDevicePeriod / 10);
+ return res;
+}
+
+void QWasapiAudioOutput::setBufferSize(int value)
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << value;
+ if (m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState)
+ return;
+ m_bufferBytes = value;
+}
+
+int QWasapiAudioOutput::bufferSize() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ if (m_currentState == QAudio::ActiveState || m_currentState == QAudio::IdleState)
+ return m_bufferFrames * m_currentFormat.channelCount()* m_currentFormat.sampleSize() / 8;
+
+ return m_bufferBytes;
+}
+
+void QWasapiAudioOutput::setNotifyInterval(int ms)
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << ms;
+ m_interval = qMax(0, ms);
+}
+
+int QWasapiAudioOutput::notifyInterval() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ return m_interval;
+}
+
+qint64 QWasapiAudioOutput::processedUSecs() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ if (m_currentState == QAudio::StoppedState)
+ return 0;
+ qint64 res = qint64(1000000) * m_bytesProcessed /
+ (m_currentFormat.channelCount() * (m_currentFormat.sampleSize() / 8)) /
+ m_currentFormat.sampleRate();
+
+ return res;
+}
+
+void QWasapiAudioOutput::resume()
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+
+ if (m_currentState != QAudio::SuspendedState)
+ return;
+
+ HRESULT hr = m_interface->m_client->Start();
+ EMIT_RETURN_VOID_IF_FAILED("Could not start audio render client.", QAudio::FatalError)
+
+ m_mutex.lock();
+ m_currentError = QAudio::NoError;
+ m_currentState = m_pullMode ? QAudio::ActiveState : QAudio::IdleState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+ if (m_eventThread)
+ m_eventThread->start();
+}
+
+void QWasapiAudioOutput::setFormat(const QAudioFormat& fmt)
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << fmt;
+ if (m_currentState != QAudio::StoppedState)
+ return;
+ m_currentFormat = fmt;
+}
+
+QAudioFormat QWasapiAudioOutput::format() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ return m_currentFormat;
+}
+
+void QWasapiAudioOutput::suspend()
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+
+ if (m_currentState != QAudio::ActiveState && m_currentState != QAudio::IdleState)
+ return;
+
+ m_mutex.lock();
+ m_currentState = QAudio::SuspendedState;
+ m_mutex.unlock();
+ emit stateChanged(m_currentState);
+
+ HRESULT hr = m_interface->m_client->Stop();
+ EMIT_RETURN_VOID_IF_FAILED("Could not suspend audio render client.", QAudio::FatalError);
+ if (m_eventThread) {
+ SetEvent(m_eventThread->m_event);
+ while (m_eventThread->isRunning())
+ QThread::yieldCurrentThread();
+ qCDebug(lcMmAudioOutput) << __FUNCTION__ << "Thread has stopped";
+ }
+}
+
+qint64 QWasapiAudioOutput::elapsedUSecs() const
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ if (m_currentState == QAudio::StoppedState)
+ return 0;
+ return m_openTime.elapsed() * qint64(1000);
+}
+
+void QWasapiAudioOutput::reset()
+{
+ qCDebug(lcMmAudioOutput) << __FUNCTION__;
+ stop();
+}
+
+QT_END_NAMESPACE
+
+#include "qwasapiaudiooutput.moc"
diff --git a/src/plugins/wasapi/qwasapiaudiooutput.h b/src/plugins/wasapi/qwasapiaudiooutput.h
new file mode 100644
index 000000000..118731af9
--- /dev/null
+++ b/src/plugins/wasapi/qwasapiaudiooutput.h
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QAUDIOOUTPUTWASAPI_H
+#define QAUDIOOUTPUTWASAPI_H
+
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QMutex>
+#include <QtCore/QTime>
+#include <QtMultimedia/QAbstractAudioOutput>
+#include <QtMultimedia/QAudio>
+
+#include <QReadWriteLock>
+#include <wrl.h>
+
+struct IAudioRenderClient;
+struct IAudioStreamVolume;
+
+QT_BEGIN_NAMESPACE
+
+class AudioInterface;
+class WasapiOutputDevicePrivate;
+class QWasapiProcessThread;
+
+Q_DECLARE_LOGGING_CATEGORY(lcMmAudioOutput)
+
+class QWasapiAudioOutput : public QAbstractAudioOutput
+{
+ Q_OBJECT
+public:
+ explicit QWasapiAudioOutput(const QByteArray &device);
+ ~QWasapiAudioOutput();
+
+ void start(QIODevice* device) Q_DECL_OVERRIDE;
+ QIODevice* start() Q_DECL_OVERRIDE;
+ void stop() Q_DECL_OVERRIDE;
+ void reset() Q_DECL_OVERRIDE;
+ void suspend() Q_DECL_OVERRIDE;
+ void resume() Q_DECL_OVERRIDE;
+ int bytesFree() const Q_DECL_OVERRIDE;
+ int periodSize() const Q_DECL_OVERRIDE;
+ void setBufferSize(int value) Q_DECL_OVERRIDE;
+ int bufferSize() const Q_DECL_OVERRIDE;
+ void setNotifyInterval(int milliSeconds) Q_DECL_OVERRIDE;
+ int notifyInterval() const Q_DECL_OVERRIDE;
+ qint64 processedUSecs() const Q_DECL_OVERRIDE;
+ qint64 elapsedUSecs() const Q_DECL_OVERRIDE;
+ QAudio::Error error() const Q_DECL_OVERRIDE;
+ QAudio::State state() const Q_DECL_OVERRIDE;
+ void setFormat(const QAudioFormat& fmt) Q_DECL_OVERRIDE;
+ QAudioFormat format() const Q_DECL_OVERRIDE;
+ void setVolume(qreal) Q_DECL_OVERRIDE;
+ qreal volume() const Q_DECL_OVERRIDE;
+
+ void process();
+public slots:
+ void processBuffer();
+private:
+ bool initStart(bool pull);
+ friend class WasapiOutputDevicePrivate;
+ friend class WasapiOutputPrivate;
+ QByteArray m_deviceName;
+ Microsoft::WRL::ComPtr<AudioInterface> m_interface;
+ Microsoft::WRL::ComPtr<IAudioRenderClient> m_renderer;
+#if defined(CLASSIC_APP_BUILD) || _MSC_VER >= 1900
+ Microsoft::WRL::ComPtr<IAudioStreamVolume> m_volumeControl;
+ qreal m_volumeCache;
+#endif
+ QMutex m_mutex;
+ QAudio::State m_currentState;
+ QAudio::Error m_currentError;
+ QAudioFormat m_currentFormat;
+ qint64 m_bytesProcessed;
+ QTime m_openTime;
+ int m_openTimeOffset;
+ int m_interval;
+ bool m_pullMode;
+ quint32 m_bufferFrames;
+ quint32 m_bufferBytes;
+ HANDLE m_event;
+ QWasapiProcessThread *m_eventThread;
+ QAtomicInt m_processing;
+ QIODevice *m_eventDevice;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/wasapi/qwasapiplugin.cpp b/src/plugins/wasapi/qwasapiplugin.cpp
new file mode 100644
index 000000000..1466347ba
--- /dev/null
+++ b/src/plugins/wasapi/qwasapiplugin.cpp
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** 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 "qwasapiplugin.h"
+#include "qwasapiaudiodeviceinfo.h"
+#include "qwasapiaudioinput.h"
+#include "qwasapiaudiooutput.h"
+#include "qwasapiutils.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcMmPlugin, "qt.multimedia.plugin")
+
+QWasapiPlugin::QWasapiPlugin(QObject *parent)
+ : QAudioSystemPlugin(parent)
+{
+ qCDebug(lcMmPlugin) << __FUNCTION__;
+}
+
+QList<QByteArray> QWasapiPlugin::availableDevices(QAudio::Mode mode) const
+{
+ qCDebug(lcMmPlugin) << __FUNCTION__ << mode;
+ return QWasapiUtils::availableDevices(mode);
+}
+
+QAbstractAudioInput *QWasapiPlugin::createInput(const QByteArray &device)
+{
+ qCDebug(lcMmPlugin) << __FUNCTION__ << device;
+ return new QWasapiAudioInput(device);
+}
+
+QAbstractAudioOutput *QWasapiPlugin::createOutput(const QByteArray &device)
+{
+ qCDebug(lcMmPlugin) << __FUNCTION__ << device;
+ return new QWasapiAudioOutput(device);
+}
+
+QAbstractAudioDeviceInfo *QWasapiPlugin::createDeviceInfo(const QByteArray &device, QAudio::Mode mode)
+{
+ qCDebug(lcMmPlugin) << __FUNCTION__ << device << mode;
+ return new QWasapiAudioDeviceInfo(device, mode);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/wasapi/qwasapiplugin.h b/src/plugins/wasapi/qwasapiplugin.h
new file mode 100644
index 000000000..fb9b6fc31
--- /dev/null
+++ b/src/plugins/wasapi/qwasapiplugin.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QWASAPIPLUGIN_H
+#define QWASAPIPLUGIN_H
+
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QList>
+#include <QtMultimedia/QAudioSystemPlugin>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcMmPlugin)
+
+class QWasapiPlugin : public QAudioSystemPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "org.qt-project.qt.audiosystemfactory/5.0" FILE "wasapi.json")
+
+public:
+ explicit QWasapiPlugin(QObject *parent = 0);
+ ~QWasapiPlugin() {}
+
+ QList<QByteArray> availableDevices(QAudio::Mode mode) const Q_DECL_OVERRIDE;
+ QAbstractAudioInput *createInput(const QByteArray &device) Q_DECL_OVERRIDE;
+ QAbstractAudioOutput *createOutput(const QByteArray &device) Q_DECL_OVERRIDE;
+ QAbstractAudioDeviceInfo *createDeviceInfo(const QByteArray &device, QAudio::Mode mode) Q_DECL_OVERRIDE;
+
+private:
+ QList<QByteArray> m_deviceNames;
+ QList<QByteArray> m_deviceIds;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWASAPIPLUGIN_H
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
diff --git a/src/plugins/wasapi/qwasapiutils.h b/src/plugins/wasapi/qwasapiutils.h
new file mode 100644
index 000000000..ff2d915b6
--- /dev/null
+++ b/src/plugins/wasapi/qwasapiutils.h
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QWASAPIUTILS_H
+#define QWASAPIUTILS_H
+#include "qwasapiaudiodeviceinfo.h"
+#include "qwasapiaudioinput.h"
+#include "qwasapiaudiooutput.h"
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QThread>
+#include <QtMultimedia/QAudio>
+#include <QtMultimedia/QAudioFormat>
+
+#include <wrl.h>
+#include <Audioclient.h>
+#include <mmdeviceapi.h>
+
+struct IAudioClient;
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcMmAudioInterface)
+Q_DECLARE_LOGGING_CATEGORY(lcMmUtils)
+
+#define EMIT_RETURN_FALSE_IF_FAILED(msg, err) \
+ if (FAILED(hr)) { \
+ m_currentError = err; \
+ emit errorChanged(m_currentError); \
+ } \
+ RETURN_FALSE_IF_FAILED(msg)
+
+#define EMIT_RETURN_VOID_IF_FAILED(msg, err) \
+ if (FAILED(hr)) { \
+ m_currentError = err; \
+ emit errorChanged(m_currentError); \
+ } \
+ RETURN_VOID_IF_FAILED(msg)
+
+class AudioInterface : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags
+ <Microsoft::WRL::Delegate>, Microsoft::WRL::FtmBase, IActivateAudioInterfaceCompletionHandler>
+{
+public:
+ enum State {
+ Initialized = 0,
+ Activating,
+ Activated,
+ Error
+ } ;
+
+ explicit AudioInterface();
+
+ ~AudioInterface();
+
+ virtual HRESULT STDMETHODCALLTYPE ActivateCompleted(IActivateAudioInterfaceAsyncOperation *op);
+
+ inline State state() const { return m_currentState; }
+ void setState(State s) { m_currentState = s; }
+
+ Microsoft::WRL::ComPtr<IAudioClient> m_client;
+ QWasapiAudioDeviceInfo *m_parent;
+ State m_currentState;
+ QAudioFormat m_mixFormat;
+};
+
+class QWasapiProcessThread : public QThread
+{
+public:
+ explicit QWasapiProcessThread(QObject *item, bool output = true) : QThread(),
+ m_endpoint(item),
+ m_output(output)
+ {
+ qCDebug(lcMmUtils) << __FUNCTION__ << item;
+ }
+
+ ~QWasapiProcessThread()
+ {
+ qCDebug(lcMmUtils) << __FUNCTION__;
+ CloseHandle(m_event);
+ }
+
+ void run()
+ {
+ qCDebug(lcMmUtils) << __FUNCTION__ << m_endpoint;
+ if (m_output) {
+ QWasapiAudioOutput *output = static_cast<QWasapiAudioOutput *>(m_endpoint);
+ output->process();
+ } else {
+ QWasapiAudioInput *input = static_cast<QWasapiAudioInput *>(m_endpoint);
+ input->process();
+ }
+ }
+
+ HANDLE m_event;
+private:
+ QObject *m_endpoint;
+ bool m_output;
+};
+
+namespace QWasapiUtils
+{
+ bool convertToNativeFormat(const QAudioFormat &qt, WAVEFORMATEX *native);
+ bool convertFromNativeFormat(const WAVEFORMATEX *native, QAudioFormat *qt);
+
+ QList<QByteArray> availableDevices(QAudio::Mode mode);
+ Microsoft::WRL::ComPtr<AudioInterface> createOrGetInterface(const QByteArray &dev, QAudio::Mode mode);
+}
+
+QT_END_NAMESPACE
+
+#endif // QWASAPIUTILS_H
diff --git a/src/plugins/wasapi/wasapi.json b/src/plugins/wasapi/wasapi.json
new file mode 100644
index 000000000..8d6876490
--- /dev/null
+++ b/src/plugins/wasapi/wasapi.json
@@ -0,0 +1,3 @@
+{
+ "Keys": ["wasapi"]
+}
diff --git a/src/plugins/wasapi/wasapi.pro b/src/plugins/wasapi/wasapi.pro
new file mode 100644
index 000000000..11dfd8abe
--- /dev/null
+++ b/src/plugins/wasapi/wasapi.pro
@@ -0,0 +1,30 @@
+TARGET = qtaudio_wasapi
+QT += core-private multimedia-private
+
+HEADERS += \
+ qwasapiplugin.h \
+ qwasapiaudiodeviceinfo.h \
+ qwasapiaudioinput.h \
+ qwasapiaudiooutput.h \
+ qwasapiutils.h
+
+SOURCES += \
+ qwasapiplugin.cpp \
+ qwasapiaudiodeviceinfo.cpp \
+ qwasapiaudioinput.cpp \
+ qwasapiaudiooutput.cpp \
+ qwasapiutils.cpp
+
+OTHER_FILES += \
+ wasapi.json
+
+LIBS += Mmdevapi.lib
+
+win32-* {
+ DEFINES += CLASSIC_APP_BUILD
+ LIBS += runtimeobject.lib
+}
+
+PLUGIN_TYPE = audio
+PLUGIN_CLASS_NAME = QWasapiPlugin
+load(qt_plugin)