summaryrefslogtreecommitdiffstats
path: root/src/multimedia/pulseaudio/qpulseaudiosink.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/pulseaudio/qpulseaudiosink.cpp')
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiosink.cpp518
1 files changed, 246 insertions, 272 deletions
diff --git a/src/multimedia/pulseaudio/qpulseaudiosink.cpp b/src/multimedia/pulseaudio/qpulseaudiosink.cpp
index b3e39d746..610677eeb 100644
--- a/src/multimedia/pulseaudio/qpulseaudiosink.cpp
+++ b/src/multimedia/pulseaudio/qpulseaudiosink.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
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h>
@@ -43,21 +7,20 @@
#include <private/qaudiohelpers_p.h>
#include "qpulseaudiosink_p.h"
-#include "qpulseaudiodevice_p.h"
#include "qaudioengine_pulse_p.h"
#include "qpulsehelpers_p.h"
#include <sys/types.h>
#include <unistd.h>
-
-Q_DECLARE_LOGGING_CATEGORY(qLcPulseAudio)
+#include <mutex> // for std::lock_guard
QT_BEGIN_NAMESPACE
-const int PeriodTimeMs = 20;
+static constexpr uint SinkPeriodTimeMs = 20;
+static constexpr uint DefaultBufferLengthMs = 100;
#define LOW_LATENCY_CATEGORY_NAME "game"
-static void outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
+static void outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
{
Q_UNUSED(stream);
Q_UNUSED(userdata);
@@ -72,17 +35,19 @@ static void outputStreamStateCallback(pa_stream *stream, void *userdata)
pa_stream_state_t state = pa_stream_get_state(stream);
qCDebug(qLcPulseAudioOut) << "Stream state callback:" << state;
switch (state) {
- case PA_STREAM_CREATING:
- case PA_STREAM_READY:
- case PA_STREAM_TERMINATED:
- break;
-
- case PA_STREAM_FAILED:
- default:
- qWarning() << QString::fromLatin1("Stream error: %1").arg(QString::fromUtf8(pa_strerror(pa_context_errno(pa_stream_get_context(stream)))));
- QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
- pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
- break;
+ case PA_STREAM_CREATING:
+ case PA_STREAM_READY:
+ case PA_STREAM_TERMINATED:
+ break;
+
+ case PA_STREAM_FAILED:
+ default:
+ qWarning() << QStringLiteral("Stream error: %1")
+ .arg(QString::fromUtf8(pa_strerror(
+ pa_context_errno(pa_stream_get_context(stream)))));
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+ break;
}
}
@@ -133,90 +98,73 @@ static void outputStreamDrainComplete(pa_stream *stream, int success, void *user
{
Q_UNUSED(stream);
- qCDebug(qLcPulseAudioOut) << "Stream drained:" << bool(success) << userdata;
- if (success && userdata)
+ qCDebug(qLcPulseAudioOut) << "Stream drained:" << static_cast<bool>(success) << userdata;
+
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+
+ if (userdata && success)
static_cast<QPulseAudioSink *>(userdata)->streamDrainedCallback();
}
+static void outputStreamFlushComplete(pa_stream *stream, int success, void *userdata)
+{
+ Q_UNUSED(stream);
+
+ qCDebug(qLcPulseAudioOut) << "Stream flushed:" << static_cast<bool>(success) << userdata;
+}
+
static void streamAdjustPrebufferCallback(pa_stream *stream, int success, void *userdata)
{
Q_UNUSED(stream);
Q_UNUSED(success);
Q_UNUSED(userdata);
- qCDebug(qLcPulseAudioOut) << "Prebuffer adjusted:" << bool(success);
+ qCDebug(qLcPulseAudioOut) << "Prebuffer adjusted:" << static_cast<bool>(success);
}
-
-QPulseAudioSink::QPulseAudioSink(const QByteArray &device)
- : m_device(device)
+QPulseAudioSink::QPulseAudioSink(const QByteArray &device, QObject *parent)
+ : QPlatformAudioSink(parent), m_device(device), m_stateMachine(*this)
{
}
QPulseAudioSink::~QPulseAudioSink()
{
- close();
- QCoreApplication::processEvents();
-}
-
-void QPulseAudioSink::setError(QAudio::Error error)
-{
- if (m_errorState == error)
- return;
-
- m_errorState = error;
- emit errorChanged(error);
+ if (auto notifier = m_stateMachine.stop())
+ close();
}
QAudio::Error QPulseAudioSink::error() const
{
- return m_errorState;
-}
-
-void QPulseAudioSink::setState(QAudio::State state)
-{
- if (m_deviceState == state)
- return;
-
- m_deviceState = state;
- emit stateChanged(state);
+ return m_stateMachine.error();
}
QAudio::State QPulseAudioSink::state() const
{
- return m_deviceState;
+ return m_stateMachine.state();
}
void QPulseAudioSink::streamUnderflowCallback()
{
- if (m_audioSource && m_audioSource->atEnd()) {
+ bool atEnd = m_audioSource && m_audioSource->atEnd();
+ if (atEnd && m_stateMachine.state() != QAudio::StoppedState) {
qCDebug(qLcPulseAudioOut) << "Draining stream at end of buffer";
- pa_operation *o = pa_stream_drain(m_stream, outputStreamDrainComplete, this);
- if (o)
- pa_operation_unref(o);
- } else if (m_deviceState != QAudio::IdleState && !m_resuming) {
- setError(QAudio::UnderrunError);
- setState(QAudio::IdleState);
+ exchangeDrainOperation(pa_stream_drain(m_stream, outputStreamDrainComplete, this));
}
+
+ m_stateMachine.updateActiveOrIdle(
+ false, (m_pullMode && atEnd) ? QAudio::NoError : QAudio::UnderrunError);
}
void QPulseAudioSink::streamDrainedCallback()
{
- setError(QAudio::NoError);
- setState(QAudio::IdleState);
+ if (!exchangeDrainOperation(nullptr))
+ return;
}
void QPulseAudioSink::start(QIODevice *device)
{
- setState(QAudio::StoppedState);
- setError(QAudio::NoError);
-
- // Handle change of mode
- if (m_audioSource && !m_pullMode)
- delete m_audioSource;
- m_audioSource = nullptr;
-
- close();
+ reset();
m_pullMode = true;
m_audioSource = device;
@@ -230,27 +178,29 @@ void QPulseAudioSink::start(QIODevice *device)
gettimeofday(&lastTimingInfo, nullptr);
lastProcessedUSecs = 0;
- connect(m_audioSource, &QIODevice::readyRead, this, &QPulseAudioSink::startReading);
- setState(QAudio::ActiveState);
+ connect(m_audioSource, &QIODevice::readyRead, this, &QPulseAudioSink::startPulling);
+
+ m_stateMachine.start();
}
-void QPulseAudioSink::startReading()
+void QPulseAudioSink::startPulling()
{
- if (!m_tickTimer.isActive())
- m_tickTimer.start(m_periodTime, this);
+ Q_ASSERT(m_pullMode);
+ if (m_tickTimer.isActive())
+ return;
+
+ m_tickTimer.start(m_pullingPeriodTime, this);
}
-QIODevice *QPulseAudioSink::start()
+void QPulseAudioSink::stopTimer()
{
- setState(QAudio::StoppedState);
- setError(QAudio::NoError);
-
- // Handle change of mode
- if (m_audioSource && !m_pullMode)
- delete m_audioSource;
- m_audioSource = nullptr;
+ if (m_tickTimer.isActive())
+ m_tickTimer.stop();
+}
- close();
+QIODevice *QPulseAudioSink::start()
+{
+ reset();
m_pullMode = false;
@@ -258,13 +208,13 @@ QIODevice *QPulseAudioSink::start()
return nullptr;
m_audioSource = new PulseOutputPrivate(this);
- m_audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
+ m_audioSource->open(QIODevice::WriteOnly | QIODevice::Unbuffered);
// ensure we only process timing infos that are up to date
gettimeofday(&lastTimingInfo, nullptr);
lastProcessedUSecs = 0;
- setState(QAudio::IdleState);
+ m_stateMachine.start(false);
return m_audioSource;
}
@@ -276,10 +226,9 @@ bool QPulseAudioSink::open()
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
- if (!pulseEngine->context() || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) {
- setError(QAudio::FatalError);
- setState(QAudio::StoppedState);
- emit stateChanged(m_deviceState);
+ if (!pulseEngine->context()
+ || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) {
+ m_stateMachine.stopOrUpdateError(QAudio::FatalError);
return false;
}
@@ -288,9 +237,7 @@ bool QPulseAudioSink::open()
Q_ASSERT(spec.channels == channel_map.channels);
if (!pa_sample_spec_valid(&spec)) {
- setError(QAudio::OpenError);
- setState(QAudio::StoppedState);
- emit stateChanged(m_deviceState);
+ m_stateMachine.stopOrUpdateError(QAudio::OpenError);
return false;
}
@@ -298,11 +245,12 @@ bool QPulseAudioSink::open()
m_totalTimeValue = 0;
if (m_streamName.isNull())
- m_streamName = QString(QLatin1String("QtmPulseStream-%1-%2")).arg(::getpid()).arg(quintptr(this)).toUtf8();
+ m_streamName =
+ QStringLiteral("QtmPulseStream-%1-%2").arg(::getpid()).arg(quintptr(this)).toUtf8();
if (Q_UNLIKELY(qLcPulseAudioOut().isEnabled(QtDebugMsg))) {
qCDebug(qLcPulseAudioOut) << "Opening stream with.";
- qCDebug(qLcPulseAudioOut) << "\tFormat: " << QPulseAudioInternal::sampleFormatToQString(spec.format);
+ qCDebug(qLcPulseAudioOut) << "\tFormat: " << spec.format;
qCDebug(qLcPulseAudioOut) << "\tRate: " << spec.rate;
qCDebug(qLcPulseAudioOut) << "\tChannels: " << spec.channels;
qCDebug(qLcPulseAudioOut) << "\tFrame size: " << pa_frame_size(&spec);
@@ -310,7 +258,6 @@ bool QPulseAudioSink::open()
pulseEngine->lock();
-
pa_proplist *propList = pa_proplist_new();
#if 0
qint64 bytesPerSecond = m_format.sampleRate() * m_format.bytesPerFrame();
@@ -332,18 +279,18 @@ bool QPulseAudioSink::open()
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, r);
#endif
- m_stream = pa_stream_new_with_proplist(pulseEngine->context(), m_streamName.constData(), &m_spec, &channel_map, propList);
+ m_stream = pa_stream_new_with_proplist(pulseEngine->context(), m_streamName.constData(),
+ &m_spec, &channel_map, propList);
+ pa_proplist_free(propList);
+
if (!m_stream) {
qCWarning(qLcPulseAudioOut) << "QAudioSink: pa_stream_new_with_proplist() failed!";
pulseEngine->unlock();
- setError(QAudio::OpenError);
- setState(QAudio::StoppedState);
- emit stateChanged(m_deviceState);
+
+ m_stateMachine.stopOrUpdateError(QAudio::OpenError);
return false;
}
- pa_proplist_free(propList);
-
pa_stream_set_state_callback(m_stream, outputStreamStateCallback, this);
pa_stream_set_write_callback(m_stream, outputStreamWriteCallback, this);
@@ -352,21 +299,26 @@ bool QPulseAudioSink::open()
pa_stream_set_latency_update_callback(m_stream, outputStreamLatencyCallback, this);
pa_buffer_attr requestedBuffer;
- requestedBuffer.fragsize = (uint32_t)-1;
- requestedBuffer.maxlength = (uint32_t)-1;
- requestedBuffer.minreq = (uint32_t)-1;
- requestedBuffer.prebuf = (uint32_t)-1;
- requestedBuffer.tlength = m_bufferSize;
-
- pa_stream_flags flags = pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_ADJUST_LATENCY);
- if (pa_stream_connect_playback(m_stream, m_device.data(), (m_bufferSize > 0) ? &requestedBuffer : nullptr, flags, nullptr, nullptr) < 0) {
+ // Request a target buffer size
+ auto targetBufferSize = m_userBufferSize ? *m_userBufferSize : defaultBufferSize();
+ requestedBuffer.tlength =
+ targetBufferSize ? static_cast<uint32_t>(targetBufferSize) : static_cast<uint32_t>(-1);
+ // Rest should be determined by PulseAudio
+ requestedBuffer.fragsize = static_cast<uint32_t>(-1);
+ requestedBuffer.maxlength = static_cast<uint32_t>(-1);
+ requestedBuffer.minreq = static_cast<uint32_t>(-1);
+ requestedBuffer.prebuf = static_cast<uint32_t>(-1);
+
+ pa_stream_flags flags =
+ pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
+ if (pa_stream_connect_playback(m_stream, m_device.data(), &requestedBuffer, flags, nullptr,
+ nullptr)
+ < 0) {
qCWarning(qLcPulseAudioOut) << "pa_stream_connect_playback() failed!";
pa_stream_unref(m_stream);
m_stream = nullptr;
pulseEngine->unlock();
- setError(QAudio::OpenError);
- setState(QAudio::StoppedState);
- emit stateChanged(m_deviceState);
+ m_stateMachine.stopOrUpdateError(QAudio::OpenError);
return false;
}
@@ -374,20 +326,25 @@ bool QPulseAudioSink::open()
pa_threaded_mainloop_wait(pulseEngine->mainloop());
const pa_buffer_attr *buffer = pa_stream_get_buffer_attr(m_stream);
- m_periodTime = PeriodTimeMs;
- m_periodSize = pa_usec_to_bytes(m_periodTime * 1000, &m_spec);
m_bufferSize = buffer->tlength;
- m_maxBufferSize = buffer->maxlength;
- m_audioBuffer = new char[m_maxBufferSize];
+
+ if (m_pullMode) {
+ // Adjust period time to reduce chance of it being higher than amount of bytes requested by
+ // PulseAudio server
+ m_pullingPeriodTime =
+ qMin(SinkPeriodTimeMs, pa_bytes_to_usec(m_bufferSize, &m_spec) / 1000 / 2);
+ m_pullingPeriodSize = pa_usec_to_bytes(m_pullingPeriodTime * 1000, &m_spec);
+ }
+
+ m_audioBuffer.resize(buffer->maxlength);
const qint64 streamSize = m_audioSource ? m_audioSource->size() : 0;
if (m_pullMode && streamSize > 0 && static_cast<qint64>(buffer->prebuf) > streamSize) {
pa_buffer_attr newBufferAttr;
newBufferAttr = *buffer;
newBufferAttr.prebuf = streamSize;
- pa_operation *o = pa_stream_set_buffer_attr(m_stream, &newBufferAttr, streamAdjustPrebufferCallback, nullptr);
- if (o)
- pa_operation_unref(o);
+ PAOperationUPtr(pa_stream_set_buffer_attr(m_stream, &newBufferAttr,
+ streamAdjustPrebufferCallback, nullptr));
}
if (Q_UNLIKELY(qLcPulseAudioOut().isEnabled(QtDebugMsg))) {
@@ -401,11 +358,13 @@ bool QPulseAudioSink::open()
pulseEngine->unlock();
- connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioSink::onPulseContextFailed);
+ connect(pulseEngine, &QPulseAudioEngine::contextFailed, this,
+ &QPulseAudioSink::onPulseContextFailed);
m_opened = true;
- startReading();
+ if (m_pullMode)
+ startPulling();
m_elapsedTimeOffset = 0;
@@ -417,12 +376,12 @@ void QPulseAudioSink::close()
if (!m_opened)
return;
- m_tickTimer.stop();
+ stopTimer();
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
if (m_stream) {
- pulseEngine->lock();
+ std::lock_guard lock(*pulseEngine);
pa_stream_set_state_callback(m_stream, nullptr, nullptr);
pa_stream_set_write_callback(m_stream, nullptr, nullptr);
@@ -430,37 +389,37 @@ void QPulseAudioSink::close()
pa_stream_set_overflow_callback(m_stream, nullptr, nullptr);
pa_stream_set_latency_update_callback(m_stream, nullptr, nullptr);
- pa_operation *o = pa_stream_drain(m_stream, outputStreamDrainComplete, nullptr);
- if (o)
- pa_operation_unref(o);
+ if (auto prevOp = exchangeDrainOperation(nullptr))
+ // cancel draining operation to prevent calling draining callback after closing.
+ pa_operation_cancel(prevOp.get());
+
+ PAOperationUPtr operation(pa_stream_flush(m_stream, outputStreamFlushComplete, nullptr));
pa_stream_disconnect(m_stream);
pa_stream_unref(m_stream);
m_stream = nullptr;
-
- pulseEngine->unlock();
}
- disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioSink::onPulseContextFailed);
+ disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this,
+ &QPulseAudioSink::onPulseContextFailed);
if (m_audioSource) {
if (m_pullMode) {
disconnect(m_audioSource, &QIODevice::readyRead, this, nullptr);
+ m_audioSource->reset();
} else {
delete m_audioSource;
m_audioSource = nullptr;
}
}
+
m_opened = false;
- if (m_audioBuffer) {
- delete[] m_audioBuffer;
- m_audioBuffer = nullptr;
- }
+ m_audioBuffer.clear();
}
void QPulseAudioSink::timerEvent(QTimerEvent *event)
{
- if (event->timerId() == m_tickTimer.timerId())
+ if (event->timerId() == m_tickTimer.timerId() && m_pullMode)
userFeed();
QPlatformAudioSink::timerEvent(event);
@@ -468,54 +427,48 @@ void QPulseAudioSink::timerEvent(QTimerEvent *event)
void QPulseAudioSink::userFeed()
{
- if (m_deviceState == QAudio::StoppedState || m_deviceState == QAudio::SuspendedState)
+ int writableSize = bytesFree();
+
+ if (writableSize == 0) {
+ // PulseAudio server doesn't want any more data
+ m_stateMachine.activateFromIdle();
return;
+ }
- m_resuming = false;
+ // Write up to writableSize
+ const int inputSize =
+ std::min({ m_pullingPeriodSize, static_cast<int>(m_audioBuffer.size()), writableSize });
- if (m_pullMode) {
- setError(QAudio::NoError);
- setState(QAudio::ActiveState);
- int writableSize = bytesFree();
- int chunks = writableSize / m_periodSize;
- if (chunks == 0)
- return;
-
- int input = m_periodSize; // always request 1 chunk of data from user
- if (input > m_maxBufferSize)
- input = m_maxBufferSize;
-
- Q_ASSERT(m_audioBuffer);
- int audioBytesPulled = m_audioSource->read(m_audioBuffer, input);
- Q_ASSERT(audioBytesPulled <= input);
- if (audioBytesPulled > 0) {
- if (audioBytesPulled > input) {
- qCWarning(qLcPulseAudioOut) << "Invalid audio data size provided by pull source:"
- << audioBytesPulled << "should be less than" << input;
- audioBytesPulled = input;
- }
- qint64 bytesWritten = write(m_audioBuffer, audioBytesPulled);
- Q_ASSERT(bytesWritten == audioBytesPulled); //unfinished write should not happen since the data provided is less than writableSize
- Q_UNUSED(bytesWritten);
-
- if (chunks > 1) {
- // PulseAudio needs more data. Ask for it immediately.
- QMetaObject::invokeMethod(this, "userFeed", Qt::QueuedConnection);
- }
- } else if (audioBytesPulled == 0) {
- m_tickTimer.stop();
- qCDebug(qLcPulseAudioOut) << "No more data available, source is done:" << m_audioSource->atEnd();
- setError(m_audioSource->atEnd() ? QAudio::NoError : QAudio::UnderrunError);
- setState(QAudio::IdleState);
+ Q_ASSERT(!m_audioBuffer.empty());
+ int audioBytesPulled = m_audioSource->read(m_audioBuffer.data(), inputSize);
+ Q_ASSERT(audioBytesPulled <= inputSize);
+
+ if (audioBytesPulled > 0) {
+ if (audioBytesPulled > inputSize) {
+ qCWarning(qLcPulseAudioOut)
+ << "Invalid audio data size provided by pull source:" << audioBytesPulled
+ << "should be less than" << inputSize;
+ audioBytesPulled = inputSize;
}
+ auto bytesWritten = write(m_audioBuffer.data(), audioBytesPulled);
+ if (bytesWritten != audioBytesPulled)
+ qWarning() << "Unfinished write:" << bytesWritten << "vs" << audioBytesPulled;
+
+ m_stateMachine.activateFromIdle();
+
+ if (inputSize < writableSize) // PulseAudio needs more data.
+ QMetaObject::invokeMethod(this, &QPulseAudioSink::userFeed, Qt::QueuedConnection);
+ } else if (audioBytesPulled == 0) {
+ stopTimer();
+ const auto atEnd = m_audioSource->atEnd();
+ qCDebug(qLcPulseAudioOut) << "No more data available, source is done:" << atEnd;
}
-
- if (m_deviceState != QAudio::ActiveState)
- return;
}
qint64 QPulseAudioSink::write(const char *data, qint64 len)
{
+ using namespace QPulseAudioInternal;
+
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pulseEngine->lock();
@@ -524,9 +477,10 @@ qint64 QPulseAudioSink::write(const char *data, qint64 len)
void *dest = nullptr;
if (pa_stream_begin_write(m_stream, &dest, &nbytes) < 0) {
- qCWarning(qLcPulseAudioOut) << "pa_stream_begin_write error:"
- << pa_strerror(pa_context_errno(pulseEngine->context()));
- setError(QAudio::IOError);
+ pulseEngine->unlock();
+ qCWarning(qLcPulseAudioOut)
+ << "pa_stream_begin_write error:" << currentError(pulseEngine->context());
+ m_stateMachine.updateActiveOrIdle(false, QAudio::IOError);
return 0;
}
@@ -543,65 +497,76 @@ qint64 QPulseAudioSink::write(const char *data, qint64 len)
data = reinterpret_cast<char *>(dest);
if ((pa_stream_write(m_stream, data, len, nullptr, 0, PA_SEEK_RELATIVE)) < 0) {
- qCWarning(qLcPulseAudioOut) << "pa_stream_write error:"
- << pa_strerror(pa_context_errno(pulseEngine->context()));
- setError(QAudio::IOError);
+ pulseEngine->unlock();
+ qCWarning(qLcPulseAudioOut)
+ << "pa_stream_write error:" << currentError(pulseEngine->context());
+ m_stateMachine.updateActiveOrIdle(false, QAudio::IOError);
return 0;
}
pulseEngine->unlock();
m_totalTimeValue += len;
- setError(QAudio::NoError);
- setState(QAudio::ActiveState);
-
+ m_stateMachine.updateActiveOrIdle(true);
return len;
}
void QPulseAudioSink::stop()
{
- if (m_deviceState == QAudio::StoppedState)
- return;
+ if (auto notifier = m_stateMachine.stop()) {
+ {
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ std::lock_guard lock(*pulseEngine);
- close();
+ if (auto prevOp = exchangeDrainOperation(nullptr))
+ // cancel the draining callback that is not relevant already
+ pa_operation_cancel(prevOp.get());
- setError(QAudio::NoError);
- setState(QAudio::StoppedState);
+ PAOperationUPtr drainOp(pa_stream_drain(m_stream, outputStreamDrainComplete, nullptr));
+ pulseEngine->wait(drainOp.get());
+ }
+
+ close();
+ }
}
qsizetype QPulseAudioSink::bytesFree() const
{
- if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState)
+ if (!m_stateMachine.isActiveOrIdle())
return 0;
- QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
- pulseEngine->lock();
- int writableSize = pa_stream_writable_size(m_stream);
- pulseEngine->unlock();
- return writableSize;
+ std::lock_guard lock(*QPulseAudioEngine::instance());
+ return pa_stream_writable_size(m_stream);
}
void QPulseAudioSink::setBufferSize(qsizetype value)
{
- m_bufferSize = value;
+ m_userBufferSize = value;
}
qsizetype QPulseAudioSink::bufferSize() const
{
- return m_bufferSize;
+ if (m_bufferSize)
+ return m_bufferSize;
+
+ if (m_userBufferSize)
+ return *m_userBufferSize;
+
+ return defaultBufferSize();
}
static qint64 operator-(timeval t1, timeval t2)
{
constexpr qint64 secsToUSecs = 1000000;
- return (t1.tv_sec - t2.tv_sec)*secsToUSecs + (t1.tv_usec - t2.tv_usec);
+ return (t1.tv_sec - t2.tv_sec) * secsToUSecs + (t1.tv_usec - t2.tv_usec);
}
qint64 QPulseAudioSink::processedUSecs() const
{
- if (!m_stream || m_deviceState == QAudio::StoppedState)
+ const auto state = this->state();
+ if (!m_stream || state == QAudio::StoppedState)
return 0;
- if (m_deviceState == QAudio::SuspendedState)
+ if (state == QAudio::SuspendedState)
return lastProcessedUSecs;
auto info = pa_stream_get_timing_info(m_stream);
@@ -612,13 +577,16 @@ qint64 QPulseAudioSink::processedUSecs() const
if (info->timestamp - lastTimingInfo > 0) {
lastTimingInfo.tv_sec = info->timestamp.tv_sec;
lastTimingInfo.tv_usec = info->timestamp.tv_usec;
- averageLatency = 0; // also use that as long as we don't have valid data from the timing info
+ averageLatency =
+ 0; // also use that as long as we don't have valid data from the timing info
// Only use timing values when playing, otherwise the latency numbers can be way off
- if (info->since_underrun >= 0 && pa_bytes_to_usec(info->since_underrun, &m_spec) > info->sink_usec) {
+ if (info->since_underrun >= 0
+ && pa_bytes_to_usec(info->since_underrun, &m_spec) > info->sink_usec) {
latencyList.append(info->sink_usec);
// Average over the last X timing infos to keep numbers more stable.
- // 10 seems to be a decent number that keeps values relatively stable but doesn't make the list too big
+ // 10 seems to be a decent number that keeps values relatively stable but doesn't make
+ // the list too big
const int latencyListMaxSize = 10;
if (latencyList.size() > latencyListMaxSize)
latencyList.pop_front();
@@ -631,7 +599,8 @@ qint64 QPulseAudioSink::processedUSecs() const
}
const qint64 usecsRead = info->read_index < 0 ? 0 : pa_bytes_to_usec(info->read_index, &m_spec);
- const qint64 usecsWritten = info->write_index < 0 ? 0 : pa_bytes_to_usec(info->write_index, &m_spec);
+ const qint64 usecsWritten =
+ info->write_index < 0 ? 0 : pa_bytes_to_usec(info->write_index, &m_spec);
// processed data is the amount read by the server minus its latency
qint64 usecs = usecsRead - averageLatency;
@@ -659,27 +628,22 @@ qint64 QPulseAudioSink::processedUSecs() const
void QPulseAudioSink::resume()
{
- if (m_deviceState == QAudio::SuspendedState) {
- m_resuming = true;
-
- QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
-
- pulseEngine->lock();
-
- pa_operation *operation = pa_stream_cork(m_stream, 0, outputStreamSuccessCallback, nullptr);
- pulseEngine->wait(operation);
- pa_operation_unref(operation);
+ if (auto notifier = m_stateMachine.resume()) {
+ {
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
- operation = pa_stream_trigger(m_stream, outputStreamSuccessCallback, nullptr);
- pulseEngine->wait(operation);
- pa_operation_unref(operation);
+ std::lock_guard lock(*pulseEngine);
- pulseEngine->unlock();
+ PAOperationUPtr operation(
+ pa_stream_cork(m_stream, 0, outputStreamSuccessCallback, nullptr));
+ pulseEngine->wait(operation.get());
- m_tickTimer.start(m_periodTime, this);
+ operation.reset(pa_stream_trigger(m_stream, outputStreamSuccessCallback, nullptr));
+ pulseEngine->wait(operation.get());
+ }
- setState(m_pullMode ? QAudio::ActiveState : QAudio::IdleState);
- setError(QAudio::NoError);
+ if (m_pullMode)
+ startPulling();
}
}
@@ -695,33 +659,28 @@ QAudioFormat QPulseAudioSink::format() const
void QPulseAudioSink::suspend()
{
- if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState) {
- setError(QAudio::NoError);
- setState(QAudio::SuspendedState);
-
- m_tickTimer.stop();
+ if (auto notifier = m_stateMachine.suspend()) {
+ stopTimer();
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
- pa_operation *operation;
-
- pulseEngine->lock();
- operation = pa_stream_cork(m_stream, 1, outputStreamSuccessCallback, nullptr);
- pulseEngine->wait(operation);
- pa_operation_unref(operation);
+ std::lock_guard lock(*pulseEngine);
- pulseEngine->unlock();
+ PAOperationUPtr operation(
+ pa_stream_cork(m_stream, 1, outputStreamSuccessCallback, nullptr));
+ pulseEngine->wait(operation.get());
}
}
void QPulseAudioSink::reset()
{
- stop();
+ if (auto notifier = m_stateMachine.stopOrUpdateError())
+ close();
}
PulseOutputPrivate::PulseOutputPrivate(QPulseAudioSink *audio)
{
- m_audioDevice = qobject_cast<QPulseAudioSink*>(audio);
+ m_audioDevice = qobject_cast<QPulseAudioSink *>(audio);
}
qint64 PulseOutputPrivate::readData(char *data, qint64 len)
@@ -736,13 +695,13 @@ qint64 PulseOutputPrivate::writeData(const char *data, qint64 len)
{
qint64 written = 0;
- if ((m_audioDevice->m_deviceState == QAudio::ActiveState
- || m_audioDevice->m_deviceState == QAudio::IdleState)) {
- while(written < len) {
- int chunk = m_audioDevice->write(data+written, (len-written));
+ const auto state = m_audioDevice->state();
+ if (state == QAudio::ActiveState || state == QAudio::IdleState) {
+ while (written < len) {
+ int chunk = m_audioDevice->write(data + written, (len - written));
if (chunk <= 0)
return written;
- written+=chunk;
+ written += chunk;
}
}
@@ -764,10 +723,25 @@ qreal QPulseAudioSink::volume() const
void QPulseAudioSink::onPulseContextFailed()
{
- close();
+ if (auto notifier = m_stateMachine.stop(QAudio::FatalError))
+ close();
+}
- setError(QAudio::FatalError);
- setState(QAudio::StoppedState);
+PAOperationUPtr QPulseAudioSink::exchangeDrainOperation(pa_operation *newOperation)
+{
+ return PAOperationUPtr(m_drainOperation.exchange(newOperation));
+}
+
+qsizetype QPulseAudioSink::defaultBufferSize() const
+{
+ if (m_spec.rate > 0)
+ return pa_usec_to_bytes(DefaultBufferLengthMs * 1000, &m_spec);
+
+ auto spec = QPulseAudioInternal::audioFormatToSampleSpec(m_format);
+ if (pa_sample_spec_valid(&spec))
+ return pa_usec_to_bytes(DefaultBufferLengthMs * 1000, &spec);
+
+ return 0;
}
QT_END_NAMESPACE