summaryrefslogtreecommitdiffstats
path: root/src/plugins/wasapi/qwasapiaudiooutput.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/wasapi/qwasapiaudiooutput.cpp')
-rw-r--r--src/plugins/wasapi/qwasapiaudiooutput.cpp568
1 files changed, 0 insertions, 568 deletions
diff --git a/src/plugins/wasapi/qwasapiaudiooutput.cpp b/src/plugins/wasapi/qwasapiaudiooutput.cpp
deleted file mode 100644
index 79548011c..000000000
--- a/src/plugins/wasapi/qwasapiaudiooutput.cpp
+++ /dev/null
@@ -1,568 +0,0 @@
-/****************************************************************************
-**
-** 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)
- , m_volumeCache(qreal(1.))
- , 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;
- 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.");
- }
- }
-}
-
-qreal QWasapiAudioOutput::volume() const
-{
- qCDebug(lcMmAudioOutput) << __FUNCTION__;
- return m_volumeCache;
-}
-
-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)
-
- hr = m_interface->m_client->GetService(IID_PPV_ARGS(&m_volumeControl));
- if (FAILED(hr))
- qCDebug(lcMmAudioOutput) << "Could not acquire volume control.";
-
- 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"