diff options
Diffstat (limited to 'src/plugins/wasapi/qwasapiaudiooutput.cpp')
-rw-r--r-- | src/plugins/wasapi/qwasapiaudiooutput.cpp | 568 |
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" |