summaryrefslogtreecommitdiffstats
path: root/src/multimedia/windows/qwindowsaudiosink.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/windows/qwindowsaudiosink.cpp')
-rw-r--r--src/multimedia/windows/qwindowsaudiosink.cpp186
1 files changed, 69 insertions, 117 deletions
diff --git a/src/multimedia/windows/qwindowsaudiosink.cpp b/src/multimedia/windows/qwindowsaudiosink.cpp
index 5d41e6ed6..404496970 100644
--- a/src/multimedia/windows/qwindowsaudiosink.cpp
+++ b/src/multimedia/windows/qwindowsaudiosink.cpp
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
//
// W A R N I N G
@@ -49,33 +13,31 @@
//
#include "qwindowsaudiosink_p.h"
-#include "qwindowsaudiodevice_p.h"
#include "qwindowsaudioutils_p.h"
-#include <QtEndian>
+#include "qwindowsmultimediautils_p.h"
+#include "qcomtaskresource_p.h"
+
#include <QtCore/QDataStream>
#include <QtCore/qtimer.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qpointer.h>
#include <private/qaudiohelpers_p.h>
-#include <qloggingcategory.h>
-
-#include <system_error>
#include <audioclient.h>
#include <mmdeviceapi.h>
-
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(qLcAudioOutput, "qt.multimedia.audiooutput")
+static Q_LOGGING_CATEGORY(qLcAudioOutput, "qt.multimedia.audiooutput")
-const IID IID_IAudioClient = __uuidof(IAudioClient);
-const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
+using namespace QWindowsMultimediaUtils;
class OutputPrivate : public QIODevice
{
Q_OBJECT
public:
- OutputPrivate(QWindowsAudioSink& audio) : audioDevice(audio) {}
+ OutputPrivate(QWindowsAudioSink &audio) : QIODevice(&audio), audioDevice(audio) {}
~OutputPrivate() override = default;
qint64 readData(char *, qint64) override { return 0; }
@@ -85,41 +47,25 @@ private:
QWindowsAudioSink &audioDevice;
};
-std::optional<quint32> audioClientFramesInUse(IAudioClient *client)
-{
- Q_ASSERT(client);
- UINT32 framesPadding = 0;
- if (SUCCEEDED(client->GetCurrentPadding(&framesPadding)))
- return framesPadding;
- return {};
-}
-
-std::optional<quint32> audioClientFramesAllocated(IAudioClient *client)
-{
- Q_ASSERT(client);
- UINT32 bufferFrameCount = 0;
- if (SUCCEEDED(client->GetBufferSize(&bufferFrameCount)))
- return bufferFrameCount;
- return {};
-}
-
std::optional<quint32> audioClientFramesAvailable(IAudioClient *client)
{
- auto framesAllocated = audioClientFramesAllocated(client);
- auto framesInUse = audioClientFramesInUse(client);
+ auto framesAllocated = QWindowsAudioUtils::audioClientFramesAllocated(client);
+ auto framesInUse = QWindowsAudioUtils::audioClientFramesInUse(client);
if (framesAllocated && framesInUse)
return *framesAllocated - *framesInUse;
return {};
}
-QWindowsAudioSink::QWindowsAudioSink(QWindowsIUPointer<IMMDevice> device) :
+QWindowsAudioSink::QWindowsAudioSink(ComPtr<IMMDevice> device, QObject *parent) :
+ QPlatformAudioSink(parent),
+ m_timer(new QTimer(this)),
m_pushSource(new OutputPrivate(*this)),
m_device(std::move(device))
{
m_pushSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
- m_timer.setSingleShot(true);
- m_timer.setTimerType(Qt::PreciseTimer);
+ m_timer->setSingleShot(true);
+ m_timer->setTimerType(Qt::PreciseTimer);
}
QWindowsAudioSink::~QWindowsAudioSink()
@@ -129,7 +75,7 @@ QWindowsAudioSink::~QWindowsAudioSink()
qint64 QWindowsAudioSink::remainingPlayTimeUs()
{
- auto framesInUse = audioClientFramesInUse(m_audioClient.get());
+ auto framesInUse = QWindowsAudioUtils::audioClientFramesInUse(m_audioClient.Get());
return m_resampler.outputFormat().durationForFrames(framesInUse ? *framesInUse : 0);
}
@@ -141,13 +87,16 @@ void QWindowsAudioSink::deviceStateChange(QAudio::State state, QAudio::Error err
qCDebug(qLcAudioOutput) << "Audio client started";
} else if (deviceState == QAudio::ActiveState) {
- m_timer.stop();
+ m_timer->stop();
m_audioClient->Stop();
qCDebug(qLcAudioOutput) << "Audio client stopped";
}
+ QPointer<QWindowsAudioSink> thisGuard(this);
deviceState = state;
emit stateChanged(deviceState);
+ if (!thisGuard)
+ return;
}
if (error != errorState) {
@@ -190,7 +139,7 @@ void QWindowsAudioSink::pullSource()
deviceStateChange(QAudio::IdleState, m_pullSource->atEnd() ? QAudio::NoError : QAudio::UnderrunError);
} else {
deviceStateChange(QAudio::ActiveState, QAudio::NoError);
- m_timer.start(playTimeUs / 2000);
+ m_timer->start(playTimeUs / 2000);
}
}
@@ -200,6 +149,9 @@ void QWindowsAudioSink::start(QIODevice* device)
if (deviceState != QAudio::StoppedState)
close();
+ if (device == nullptr)
+ return;
+
if (!open()) {
errorState = QAudio::OpenError;
emit errorChanged(QAudio::OpenError);
@@ -209,9 +161,9 @@ void QWindowsAudioSink::start(QIODevice* device)
m_pullSource = device;
connect(device, &QIODevice::readyRead, this, &QWindowsAudioSink::pullSource);
- m_timer.disconnect();
- m_timer.callOnTimeout(this, &QWindowsAudioSink::pullSource);
- m_timer.start(0);
+ m_timer->disconnect();
+ m_timer->callOnTimeout(this, &QWindowsAudioSink::pullSource);
+ pullSource();
}
qint64 QWindowsAudioSink::push(const char *data, qint64 len)
@@ -222,7 +174,7 @@ qint64 QWindowsAudioSink::push(const char *data, qint64 len)
qint64 written = write(data, len);
if (written > 0) {
deviceStateChange(QAudio::ActiveState, QAudio::NoError);
- m_timer.start(remainingPlayTimeUs() /1000);
+ m_timer->start(remainingPlayTimeUs() /1000);
}
return written;
@@ -242,8 +194,8 @@ QIODevice* QWindowsAudioSink::start()
deviceStateChange(QAudio::IdleState, QAudio::NoError);
- m_timer.disconnect();
- m_timer.callOnTimeout([&](){
+ m_timer->disconnect();
+ m_timer->callOnTimeout(this, [this](){
deviceStateChange(QAudio::IdleState, QAudio::UnderrunError);
});
@@ -252,23 +204,27 @@ QIODevice* QWindowsAudioSink::start()
bool QWindowsAudioSink::open()
{
- HRESULT hr = m_device->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER,
- nullptr, (void**)m_audioClient.address());
+ if (m_audioClient)
+ return true;
+
+ HRESULT hr = m_device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
+ nullptr, (void**)m_audioClient.GetAddressOf());
if (FAILED(hr)) {
- qCWarning(qLcAudioOutput) << "Failed to activate audio device";
+ qCWarning(qLcAudioOutput) << "Failed to activate audio device" << errorString(hr);
return false;
}
- WAVEFORMATEX *pwfx = nullptr;
- hr = m_audioClient->GetMixFormat(&pwfx);
+ auto resetClient = qScopeGuard([this](){ m_audioClient.Reset(); });
+
+ QComTaskResource<WAVEFORMATEX> pwfx;
+ hr = m_audioClient->GetMixFormat(pwfx.address());
if (FAILED(hr)) {
- qCWarning(qLcAudioOutput) << "Format unsupported" << hr;
+ qCWarning(qLcAudioOutput) << "Format unsupported" << errorString(hr);
return false;
}
if (!m_resampler.setup(m_format, QWindowsAudioUtils::waveFormatExToFormat(*pwfx))) {
- qCWarning(qLcAudioOutput) << "Failed to setup resampler";
- CoTaskMemFree(pwfx);
+ qCWarning(qLcAudioOutput) << "Failed to set up resampler";
return false;
}
@@ -277,22 +233,15 @@ bool QWindowsAudioSink::open()
REFERENCE_TIME requestedDuration = m_format.durationForBytes(m_bufferSize) * 10;
- hr = m_audioClient->Initialize(
- AUDCLNT_SHAREMODE_SHARED,
- 0,
- requestedDuration,
- 0,
- pwfx,
- nullptr);
-
- CoTaskMemFree(pwfx);
+ hr = m_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, requestedDuration, 0, pwfx.get(),
+ nullptr);
if (FAILED(hr)) {
- qCWarning(qLcAudioOutput) << "Failed to initialize audio client" << hr;
+ qCWarning(qLcAudioOutput) << "Failed to initialize audio client" << errorString(hr);
return false;
}
- auto framesAllocated = audioClientFramesAllocated(m_audioClient.get());
+ auto framesAllocated = QWindowsAudioUtils::audioClientFramesAllocated(m_audioClient.Get());
if (!framesAllocated) {
qCWarning(qLcAudioOutput) << "Failed to get audio client buffer size";
return false;
@@ -301,12 +250,15 @@ bool QWindowsAudioSink::open()
m_bufferSize = m_format.bytesForDuration(
m_resampler.outputFormat().durationForFrames(*framesAllocated));
- hr = m_audioClient->GetService(IID_IAudioRenderClient, (void**)m_renderClient.address());
+ hr = m_audioClient->GetService(__uuidof(IAudioRenderClient), (void**)m_renderClient.GetAddressOf());
if (FAILED(hr)) {
- qCWarning(qLcAudioOutput) << "Failed to obtain audio client rendering service" << hr;
+ qCWarning(qLcAudioOutput) << "Failed to obtain audio client rendering service"
+ << errorString(hr);
return false;
}
+ resetClient.dismiss();
+
return true;
}
@@ -320,8 +272,8 @@ void QWindowsAudioSink::close()
if (m_pullSource)
disconnect(m_pullSource, &QIODevice::readyRead, this, &QWindowsAudioSink::pullSource);
- m_audioClient.reset();
- m_renderClient.reset();
+ m_audioClient.Reset();
+ m_renderClient.Reset();
m_pullSource = nullptr;
}
@@ -330,7 +282,7 @@ qsizetype QWindowsAudioSink::bytesFree() const
if (!m_audioClient)
return 0;
- auto framesAvailable = audioClientFramesAvailable(m_audioClient.get());
+ auto framesAvailable = audioClientFramesAvailable(m_audioClient.Get());
if (framesAvailable)
return m_resampler.inputBufferSize(*framesAvailable * m_resampler.outputFormat().bytesPerFrame());
return 0;
@@ -357,7 +309,7 @@ qint64 QWindowsAudioSink::write(const char *data, qint64 len)
qCDebug(qLcAudioOutput) << "write()" << len;
- auto framesAvailable = audioClientFramesAvailable(m_audioClient.get());
+ auto framesAvailable = audioClientFramesAvailable(m_audioClient.Get());
if (!framesAvailable)
return -1;
@@ -371,8 +323,7 @@ qint64 QWindowsAudioSink::write(const char *data, qint64 len)
quint8 *buffer = nullptr;
HRESULT hr = m_renderClient->GetBuffer(writeFramesNum, &buffer);
if (FAILED(hr)) {
- qCWarning(qLcAudioOutput) << "Failed to get buffer"
- << std::system_category().message(hr).c_str();
+ qCWarning(qLcAudioOutput) << "Failed to get buffer" << errorString(hr);
return -1;
}
@@ -384,8 +335,7 @@ qint64 QWindowsAudioSink::write(const char *data, qint64 len)
DWORD flags = writeBytes.isEmpty() ? AUDCLNT_BUFFERFLAGS_SILENT : 0;
hr = m_renderClient->ReleaseBuffer(writeFramesNum, flags);
if (FAILED(hr)) {
- qCWarning(qLcAudioOutput) << "Failed to return buffer"
- << std::system_category().message(hr).c_str();
+ qCWarning(qLcAudioOutput) << "Failed to return buffer" << errorString(hr);
return -1;
}
@@ -399,9 +349,7 @@ void QWindowsAudioSink::resume()
if (m_pullSource) {
pullSource();
} else {
- // FIXME: Set IdleState to be consistent with implementations on other platforms
- // even when playing the audio part that was in the buffer when suspended
- deviceStateChange(QAudio::IdleState, QAudio::NoError);
+ deviceStateChange(suspendedInState, QAudio::NoError);
if (remainingPlayTimeUs() > 0)
m_audioClient->Start();
}
@@ -411,8 +359,10 @@ void QWindowsAudioSink::resume()
void QWindowsAudioSink::suspend()
{
qCDebug(qLcAudioOutput) << "suspend()";
- if (deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState)
+ if (deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState) {
+ suspendedInState = deviceState;
deviceStateChange(QAudio::SuspendedState, QAudio::NoError);
+ }
}
void QWindowsAudioSink::setVolume(qreal v)
@@ -423,12 +373,14 @@ void QWindowsAudioSink::setVolume(qreal v)
m_volume = qBound(qreal(0), v, qreal(1));
}
+void QWindowsAudioSink::stop() {
+ // TODO: investigate and find a way to drain and stop instead of closing
+ close();
+}
+
void QWindowsAudioSink::reset()
{
- if (m_audioClient) {
- m_audioClient->Stop();
- m_audioClient->Reset();
- }
+ close();
}
QT_END_NAMESPACE