diff options
Diffstat (limited to 'src/multimedia/windows/qwindowsaudiosink.cpp')
-rw-r--r-- | src/multimedia/windows/qwindowsaudiosink.cpp | 186 |
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 |