summaryrefslogtreecommitdiffstats
path: root/src/multimedia/pulseaudio
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/pulseaudio')
-rw-r--r--src/multimedia/pulseaudio/qaudioengine_pulse.cpp413
-rw-r--r--src/multimedia/pulseaudio/qaudioengine_pulse_p.h40
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiodevice.cpp40
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiodevice_p.h42
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiomediadevices.cpp62
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiomediadevices_p.h47
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiosink.cpp518
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiosink_p.h71
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiosource.cpp448
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiosource_p.h60
-rw-r--r--src/multimedia/pulseaudio/qpulsehelpers.cpp122
-rw-r--r--src/multimedia/pulseaudio/qpulsehelpers_p.h111
12 files changed, 800 insertions, 1174 deletions
diff --git a/src/multimedia/pulseaudio/qaudioengine_pulse.cpp b/src/multimedia/pulseaudio/qaudioengine_pulse.cpp
index 7b3cf57b1..5fac7234a 100644
--- a/src/multimedia/pulseaudio/qaudioengine_pulse.cpp
+++ b/src/multimedia/pulseaudio/qaudioengine_pulse.cpp
@@ -1,101 +1,139 @@
-/****************************************************************************
-**
-** 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/qdebug.h>
#include <qaudiodevice.h>
+#include <QGuiApplication>
+#include <QIcon>
#include <QTimer>
#include "qaudioengine_pulse_p.h"
#include "qpulseaudiodevice_p.h"
#include "qpulsehelpers_p.h"
#include <sys/types.h>
#include <unistd.h>
+#include <mutex> // for lock_guard
QT_BEGIN_NAMESPACE
+template<typename Info>
+static bool updateDevicesMap(QReadWriteLock &lock, QByteArray defaultDeviceId,
+ QMap<int, QAudioDevice> &devices, QAudioDevice::Mode mode,
+ const Info &info)
+{
+ QWriteLocker locker(&lock);
+
+ bool isDefault = defaultDeviceId == info.name;
+ auto newDeviceInfo = std::make_unique<QPulseAudioDeviceInfo>(info.name, info.description, isDefault, mode);
+ newDeviceInfo->channelConfiguration = QPulseAudioInternal::channelConfigFromMap(info.channel_map);
+ newDeviceInfo->preferredFormat = QPulseAudioInternal::sampleSpecToAudioFormat(info.sample_spec);
+ newDeviceInfo->preferredFormat.setChannelConfig(newDeviceInfo->channelConfiguration);
+
+ auto &device = devices[info.index];
+ if (device.handle() && *newDeviceInfo == *device.handle())
+ return false;
+
+ device = newDeviceInfo.release()->create();
+ return true;
+}
+
+static bool updateDevicesMap(QReadWriteLock &lock, QByteArray defaultDeviceId,
+ QMap<int, QAudioDevice> &devices)
+{
+ QWriteLocker locker(&lock);
+
+ bool result = false;
+
+ for (QAudioDevice &device : devices) {
+ auto deviceInfo = device.handle();
+ const auto isDefault = deviceInfo->id == defaultDeviceId;
+ if (deviceInfo->isDefault != isDefault) {
+ Q_ASSERT(dynamic_cast<const QPulseAudioDeviceInfo *>(deviceInfo));
+ auto newDeviceInfo = std::make_unique<QPulseAudioDeviceInfo>(
+ *static_cast<const QPulseAudioDeviceInfo *>(deviceInfo));
+ newDeviceInfo->isDefault = isDefault;
+ device = newDeviceInfo.release()->create();
+ result = true;
+ }
+ }
+
+ return result;
+};
+
static void serverInfoCallback(pa_context *context, const pa_server_info *info, void *userdata)
{
+ using namespace Qt::Literals;
+ using namespace QPulseAudioInternal;
+
if (!info) {
- qWarning() << QString::fromLatin1("Failed to get server information: %s").arg(QString::fromUtf8(pa_strerror(pa_context_errno(context))));
+ qWarning() << "Failed to get server information:" << currentError(context);
return;
}
-#ifdef DEBUG_PULSE
- char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
-
- pa_sample_spec_snprint(ss, sizeof(ss), &info->sample_spec);
- pa_channel_map_snprint(cm, sizeof(cm), &info->channel_map);
-
- qDebug() << QString("User name: %1\n"
- "Host Name: %2\n"
- "Server Name: %3\n"
- "Server Version: %4\n"
- "Default Sample Specification: %5\n"
- "Default Channel Map: %6\n"
- "Default Sink: %7\n"
- "Default Source: %8\n").arg(
- info->user_name,
- info->host_name,
- info->server_name,
- info->server_version,
- ss,
- cm,
- info->default_sink_name,
- info->default_source_name);
-#endif
+ if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg))) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_sample_spec_snprint(ss, sizeof(ss), &info->sample_spec);
+ pa_channel_map_snprint(cm, sizeof(cm), &info->channel_map);
+
+ qCDebug(qLcPulseAudioEngine)
+ << QStringLiteral("User name: %1\n"
+ "Host Name: %2\n"
+ "Server Name: %3\n"
+ "Server Version: %4\n"
+ "Default Sample Specification: %5\n"
+ "Default Channel Map: %6\n"
+ "Default Sink: %7\n"
+ "Default Source: %8\n")
+ .arg(QString::fromUtf8(info->user_name),
+ QString::fromUtf8(info->host_name),
+ QString::fromUtf8(info->server_name),
+ QLatin1StringView(info->server_version), QLatin1StringView(ss),
+ QLatin1StringView(cm), QString::fromUtf8(info->default_sink_name),
+ QString::fromUtf8(info->default_source_name));
+ }
QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata);
- pulseEngine->m_serverLock.lockForWrite();
- pulseEngine->m_defaultSink = info->default_sink_name;
- pulseEngine->m_defaultSource = info->default_source_name;
- // ### ensure the QAudioDevices are updated if default changes and emit changed signal in the device manager
- pulseEngine->m_serverLock.unlock();
+
+ bool defaultSinkChanged = false;
+ bool defaultSourceChanged = false;
+
+ {
+ QWriteLocker locker(&pulseEngine->m_serverLock);
+
+ if (pulseEngine->m_defaultSink != info->default_sink_name) {
+ pulseEngine->m_defaultSink = info->default_sink_name;
+ defaultSinkChanged = true;
+ }
+
+ if (pulseEngine->m_defaultSource != info->default_source_name) {
+ pulseEngine->m_defaultSource = info->default_source_name;
+ defaultSourceChanged = true;
+ }
+ }
+
+ if (defaultSinkChanged
+ && updateDevicesMap(pulseEngine->m_sinkLock, pulseEngine->m_defaultSink,
+ pulseEngine->m_sinks))
+ emit pulseEngine->audioOutputsChanged();
+
+ if (defaultSourceChanged
+ && updateDevicesMap(pulseEngine->m_sourceLock, pulseEngine->m_defaultSource,
+ pulseEngine->m_sources))
+ emit pulseEngine->audioInputsChanged();
pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
}
static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int isLast, void *userdata)
{
- QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata);
+ using namespace Qt::Literals;
+ using namespace QPulseAudioInternal;
+
+ QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine *>(userdata);
if (isLast < 0) {
- qWarning() << QString::fromLatin1("Failed to get sink information: %s").arg(QString::fromUtf8(pa_strerror(pa_context_errno(context))));
+ qWarning() << "Failed to get sink information:" << currentError(context);
return;
}
@@ -106,36 +144,32 @@ static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int
Q_ASSERT(info);
-#ifdef DEBUG_PULSE
- QMap<pa_sink_state, QString> stateMap;
- stateMap[PA_SINK_INVALID_STATE] = "n/a";
- stateMap[PA_SINK_RUNNING] = "RUNNING";
- stateMap[PA_SINK_IDLE] = "IDLE";
- stateMap[PA_SINK_SUSPENDED] = "SUSPENDED";
-
- qDebug() << QString("Sink #%1\n"
- "\tState: %2\n"
- "\tName: %3\n"
- "\tDescription: %4\n"
- ).arg(QString::number(info->index),
- stateMap.value(info->state),
- info->name,
- info->description);
-#endif
-
- QWriteLocker locker(&pulseEngine->m_sinkLock);
- bool isDefault = pulseEngine->m_defaultSink == info->name;
- auto *dinfo = new QPulseAudioDeviceInfo(info->name, info->description, isDefault, QAudioDevice::Output);
- dinfo->channelMap = info->channel_map;
- dinfo->channelConfiguration = QPulseAudioInternal::channelConfigFromMap(info->channel_map);
- dinfo->preferredFormat = QPulseAudioInternal::sampleSpecToAudioFormat(info->sample_spec);
- dinfo->preferredFormat.setChannelConfig(dinfo->channelConfiguration);
- pulseEngine->m_sinks.insert(info->index, dinfo->create());
- emit pulseEngine->audioOutputsChanged();
+ if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg))) {
+ static const QMap<pa_sink_state, QString> stateMap{
+ { PA_SINK_INVALID_STATE, u"n/a"_s }, { PA_SINK_RUNNING, u"RUNNING"_s },
+ { PA_SINK_IDLE, u"IDLE"_s }, { PA_SINK_SUSPENDED, u"SUSPENDED"_s },
+ { PA_SINK_UNLINKED, u"UNLINKED"_s },
+ };
+
+ qCDebug(qLcPulseAudioEngine)
+ << QStringLiteral("Sink #%1\n"
+ "\tState: %2\n"
+ "\tName: %3\n"
+ "\tDescription: %4\n")
+ .arg(QString::number(info->index), stateMap.value(info->state),
+ QString::fromUtf8(info->name),
+ QString::fromUtf8(info->description));
+ }
+
+ if (updateDevicesMap(pulseEngine->m_sinkLock, pulseEngine->m_defaultSink, pulseEngine->m_sinks,
+ QAudioDevice::Output, *info))
+ emit pulseEngine->audioOutputsChanged();
}
static void sourceInfoCallback(pa_context *context, const pa_source_info *info, int isLast, void *userdata)
{
+ using namespace Qt::Literals;
+
Q_UNUSED(context);
QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata);
@@ -146,35 +180,30 @@ static void sourceInfoCallback(pa_context *context, const pa_source_info *info,
Q_ASSERT(info);
-#ifdef DEBUG_PULSE
- QMap<pa_source_state, QString> stateMap;
- stateMap[PA_SOURCE_INVALID_STATE] = "n/a";
- stateMap[PA_SOURCE_RUNNING] = "RUNNING";
- stateMap[PA_SOURCE_IDLE] = "IDLE";
- stateMap[PA_SOURCE_SUSPENDED] = "SUSPENDED";
-
- qDebug() << QString("Source #%1\n"
- "\tState: %2\n"
- "\tName: %3\n"
- "\tDescription: %4\n"
- ).arg(QString::number(info->index),
- stateMap.value(info->state),
- info->name,
- info->description);
-#endif
-
- QWriteLocker locker(&pulseEngine->m_sourceLock);
+ if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg))) {
+ static const QMap<pa_source_state, QString> stateMap{
+ { PA_SOURCE_INVALID_STATE, u"n/a"_s }, { PA_SOURCE_RUNNING, u"RUNNING"_s },
+ { PA_SOURCE_IDLE, u"IDLE"_s }, { PA_SOURCE_SUSPENDED, u"SUSPENDED"_s },
+ { PA_SOURCE_UNLINKED, u"UNLINKED"_s },
+ };
+
+ qCDebug(qLcPulseAudioEngine)
+ << QStringLiteral("Source #%1\n"
+ "\tState: %2\n"
+ "\tName: %3\n"
+ "\tDescription: %4\n")
+ .arg(QString::number(info->index), stateMap.value(info->state),
+ QString::fromUtf8(info->name),
+ QString::fromUtf8(info->description));
+ }
+
// skip monitor channels
if (info->monitor_of_sink != PA_INVALID_INDEX)
return;
- bool isDefault = pulseEngine->m_defaultSink == info->name;
- auto *dinfo = new QPulseAudioDeviceInfo(info->name, info->description, isDefault, QAudioDevice::Input);
- dinfo->channelMap = info->channel_map;
- dinfo->channelConfiguration = QPulseAudioInternal::channelConfigFromMap(info->channel_map);
- dinfo->preferredFormat = QPulseAudioInternal::sampleSpecToAudioFormat(info->sample_spec);
- dinfo->preferredFormat.setChannelConfig(dinfo->channelConfiguration);
- pulseEngine->m_sources.insert(info->index, dinfo->create());
- emit pulseEngine->audioInputsChanged();
+
+ if (updateDevicesMap(pulseEngine->m_sourceLock, pulseEngine->m_defaultSource,
+ pulseEngine->m_sources, QAudioDevice::Input, *info))
+ emit pulseEngine->audioInputsChanged();
}
static void event_cb(pa_context* context, pa_subscription_event_type_t t, uint32_t index, void* userdata)
@@ -189,27 +218,23 @@ static void event_cb(pa_context* context, pa_subscription_event_type_t t, uint32
case PA_SUBSCRIPTION_EVENT_CHANGE:
switch (facility) {
case PA_SUBSCRIPTION_EVENT_SERVER: {
- pa_operation *op = pa_context_get_server_info(context, serverInfoCallback, userdata);
- if (op)
- pa_operation_unref(op);
- else
- qWarning("PulseAudioService: failed to get server info");
+ PAOperationUPtr op(pa_context_get_server_info(context, serverInfoCallback, userdata));
+ if (!op)
+ qWarning() << "PulseAudioService: failed to get server info";
break;
}
case PA_SUBSCRIPTION_EVENT_SINK: {
- pa_operation *op = pa_context_get_sink_info_by_index(context, index, sinkInfoCallback, userdata);
- if (op)
- pa_operation_unref(op);
- else
- qWarning("PulseAudioService: failed to get sink info");
+ PAOperationUPtr op(
+ pa_context_get_sink_info_by_index(context, index, sinkInfoCallback, userdata));
+ if (!op)
+ qWarning() << "PulseAudioService: failed to get sink info";
break;
}
case PA_SUBSCRIPTION_EVENT_SOURCE: {
- pa_operation *op = pa_context_get_source_info_by_index(context, index, sourceInfoCallback, userdata);
- if (op)
- pa_operation_unref(op);
- else
- qWarning("PulseAudioService: failed to get source info");
+ PAOperationUPtr op(pa_context_get_source_info_by_index(context, index,
+ sourceInfoCallback, userdata));
+ if (!op)
+ qWarning() << "PulseAudioService: failed to get source info";
break;
}
default:
@@ -218,16 +243,16 @@ static void event_cb(pa_context* context, pa_subscription_event_type_t t, uint32
break;
case PA_SUBSCRIPTION_EVENT_REMOVE:
switch (facility) {
- case PA_SUBSCRIPTION_EVENT_SINK:
- pulseEngine->m_sinkLock.lockForWrite();
+ case PA_SUBSCRIPTION_EVENT_SINK: {
+ QWriteLocker locker(&pulseEngine->m_sinkLock);
pulseEngine->m_sinks.remove(index);
- pulseEngine->m_sinkLock.unlock();
break;
- case PA_SUBSCRIPTION_EVENT_SOURCE:
- pulseEngine->m_sourceLock.lockForWrite();
+ }
+ case PA_SUBSCRIPTION_EVENT_SOURCE: {
+ QWriteLocker locker(&pulseEngine->m_sourceLock);
pulseEngine->m_sources.remove(index);
- pulseEngine->m_sourceLock.unlock();
break;
+ }
default:
break;
}
@@ -240,9 +265,10 @@ static void event_cb(pa_context* context, pa_subscription_event_type_t t, uint32
static void contextStateCallbackInit(pa_context *context, void *userdata)
{
Q_UNUSED(context);
-#ifdef DEBUG_PULSE
- qDebug() << QPulseAudioInternal::stateToQString(pa_context_get_state(context));
-#endif
+
+ if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg)))
+ qCDebug(qLcPulseAudioEngine) << pa_context_get_state(context);
+
QPulseAudioEngine *pulseEngine = reinterpret_cast<QPulseAudioEngine*>(userdata);
pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
}
@@ -252,9 +278,8 @@ static void contextStateCallback(pa_context *c, void *userdata)
QPulseAudioEngine *self = reinterpret_cast<QPulseAudioEngine*>(userdata);
pa_context_state_t state = pa_context_get_state(c);
-#ifdef DEBUG_PULSE
- qDebug() << QPulseAudioInternal::stateToQString(state);
-#endif
+ if (Q_UNLIKELY(qLcPulseAudioEngine().isEnabled(QtDebugMsg)))
+ qCDebug(qLcPulseAudioEngine) << state;
if (state == PA_CONTEXT_FAILED)
QMetaObject::invokeMethod(self, "onContextFailed", Qt::QueuedConnection);
@@ -279,17 +304,18 @@ QPulseAudioEngine::~QPulseAudioEngine()
void QPulseAudioEngine::prepare()
{
+ using namespace QPulseAudioInternal;
bool keepGoing = true;
bool ok = true;
m_mainLoop = pa_threaded_mainloop_new();
if (m_mainLoop == nullptr) {
- qWarning("PulseAudioService: unable to create pulseaudio mainloop");
+ qWarning() << "PulseAudioService: unable to create pulseaudio mainloop";
return;
}
if (pa_threaded_mainloop_start(m_mainLoop) != 0) {
- qWarning("PulseAudioService: unable to start pulseaudio mainloop");
+ qWarning() << "PulseAudioService: unable to start pulseaudio mainloop";
pa_threaded_mainloop_free(m_mainLoop);
m_mainLoop = nullptr;
return;
@@ -299,10 +325,19 @@ void QPulseAudioEngine::prepare()
lock();
- m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtPulseAudio:%1")).arg(::getpid()).toLatin1().constData());
+ pa_proplist *proplist = pa_proplist_new();
+ if (!QGuiApplication::applicationDisplayName().isEmpty())
+ pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, qUtf8Printable(QGuiApplication::applicationDisplayName()));
+ if (!QGuiApplication::desktopFileName().isEmpty())
+ pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, qUtf8Printable(QGuiApplication::desktopFileName()));
+ if (const QString windowIconName = QGuiApplication::windowIcon().name(); !windowIconName.isEmpty())
+ pa_proplist_sets(proplist, PA_PROP_WINDOW_ICON_NAME, qUtf8Printable(windowIconName));
+
+ m_context = pa_context_new_with_proplist(m_mainLoopApi, nullptr, proplist);
+ pa_proplist_free(proplist);
if (m_context == nullptr) {
- qWarning("PulseAudioService: Unable to create new pulseaudio context");
+ qWarning() << "PulseAudioService: Unable to create new pulseaudio context";
pa_threaded_mainloop_unlock(m_mainLoop);
pa_threaded_mainloop_free(m_mainLoop);
m_mainLoop = nullptr;
@@ -312,8 +347,8 @@ void QPulseAudioEngine::prepare()
pa_context_set_state_callback(m_context, contextStateCallbackInit, this);
- if (pa_context_connect(m_context, nullptr, (pa_context_flags_t)0, nullptr) < 0) {
- qWarning("PulseAudioService: pa_context_connect() failed");
+ if (pa_context_connect(m_context, nullptr, static_cast<pa_context_flags_t>(0), nullptr) < 0) {
+ qWarning() << "PulseAudioService: pa_context_connect() failed";
pa_context_unref(m_context);
pa_threaded_mainloop_unlock(m_mainLoop);
pa_threaded_mainloop_free(m_mainLoop);
@@ -332,9 +367,7 @@ void QPulseAudioEngine::prepare()
break;
case PA_CONTEXT_READY:
-#ifdef DEBUG_PULSE
- qDebug("Connection established.");
-#endif
+ qCDebug(qLcPulseAudioEngine) << "Connection established.";
keepGoing = false;
break;
@@ -346,8 +379,8 @@ void QPulseAudioEngine::prepare()
case PA_CONTEXT_FAILED:
default:
- qCritical() << QString::fromLatin1("PulseAudioService: Connection failure: %1")
- .arg(QString::fromUtf8(pa_strerror(pa_context_errno(m_context))));
+ qCritical() << "PulseAudioService: Connection failure:"
+ << currentError(m_context);
keepGoing = false;
ok = false;
}
@@ -360,15 +393,13 @@ void QPulseAudioEngine::prepare()
pa_context_set_state_callback(m_context, contextStateCallback, this);
pa_context_set_subscribe_callback(m_context, event_cb, this);
- pa_operation *op = pa_context_subscribe(m_context,
- pa_subscription_mask_t(PA_SUBSCRIPTION_MASK_SINK |
- PA_SUBSCRIPTION_MASK_SOURCE |
- PA_SUBSCRIPTION_MASK_SERVER),
- nullptr, nullptr);
- if (op)
- pa_operation_unref(op);
- else
- qWarning("PulseAudioService: failed to subscribe to context notifications");
+ PAOperationUPtr op(pa_context_subscribe(
+ m_context,
+ pa_subscription_mask_t(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE
+ | PA_SUBSCRIPTION_MASK_SERVER),
+ nullptr, nullptr));
+ if (!op)
+ qWarning() << "PulseAudioService: failed to subscribe to context notifications";
} else {
pa_context_unref(m_context);
m_context = nullptr;
@@ -408,39 +439,34 @@ void QPulseAudioEngine::release()
void QPulseAudioEngine::updateDevices()
{
- lock();
+ std::lock_guard lock(*this);
// Get default input and output devices
- pa_operation *operation = pa_context_get_server_info(m_context, serverInfoCallback, this);
+ PAOperationUPtr operation(pa_context_get_server_info(m_context, serverInfoCallback, this));
if (operation) {
- while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
+ while (pa_operation_get_state(operation.get()) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(m_mainLoop);
- pa_operation_unref(operation);
} else {
- qWarning("PulseAudioService: failed to get server info");
+ qWarning() << "PulseAudioService: failed to get server info";
}
// Get output devices
- operation = pa_context_get_sink_info_list(m_context, sinkInfoCallback, this);
+ operation.reset(pa_context_get_sink_info_list(m_context, sinkInfoCallback, this));
if (operation) {
- while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
+ while (pa_operation_get_state(operation.get()) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(m_mainLoop);
- pa_operation_unref(operation);
} else {
- qWarning("PulseAudioService: failed to get sink info");
+ qWarning() << "PulseAudioService: failed to get sink info";
}
// Get input devices
- operation = pa_context_get_source_info_list(m_context, sourceInfoCallback, this);
+ operation.reset(pa_context_get_source_info_list(m_context, sourceInfoCallback, this));
if (operation) {
- while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
+ while (pa_operation_get_state(operation.get()) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(m_mainLoop);
- pa_operation_unref(operation);
} else {
- qWarning("PulseAudioService: failed to get source info");
+ qWarning() << "PulseAudioService: failed to get source info";
}
-
- unlock();
}
void QPulseAudioEngine::onContextFailed()
@@ -451,7 +477,7 @@ void QPulseAudioEngine::onContextFailed()
release();
// Try to reconnect later
- QTimer::singleShot(3000, this, SLOT(prepare()));
+ QTimer::singleShot(3000, this, &QPulseAudioEngine::prepare);
}
QPulseAudioEngine *QPulseAudioEngine::instance()
@@ -461,24 +487,17 @@ QPulseAudioEngine *QPulseAudioEngine::instance()
QList<QAudioDevice> QPulseAudioEngine::availableDevices(QAudioDevice::Mode mode) const
{
- QList<QAudioDevice> devices;
- QByteArray defaultDevice;
-
- m_serverLock.lockForRead();
-
if (mode == QAudioDevice::Output) {
QReadLocker locker(&m_sinkLock);
- devices = m_sinks.values();
- defaultDevice = m_defaultSink;
- } else {
- QReadLocker locker(&m_sourceLock);
- devices = m_sources.values();
- defaultDevice = m_defaultSource;
+ return m_sinks.values();
}
- m_serverLock.unlock();
+ if (mode == QAudioDevice::Input) {
+ QReadLocker locker(&m_sourceLock);
+ return m_sources.values();
+ }
- return devices;
+ return {};
}
QByteArray QPulseAudioEngine::defaultDevice(QAudioDevice::Mode mode) const
diff --git a/src/multimedia/pulseaudio/qaudioengine_pulse_p.h b/src/multimedia/pulseaudio/qaudioengine_pulse_p.h
index 79a3d8d3d..2ed1fa0b1 100644
--- a/src/multimedia/pulseaudio/qaudioengine_pulse_p.h
+++ b/src/multimedia/pulseaudio/qaudioengine_pulse_p.h
@@ -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
#ifndef QPULSEAUDIOENGINE_H
#define QPULSEAUDIOENGINE_H
diff --git a/src/multimedia/pulseaudio/qpulseaudiodevice.cpp b/src/multimedia/pulseaudio/qpulseaudiodevice.cpp
index c25467a8f..487b88f6d 100644
--- a/src/multimedia/pulseaudio/qpulseaudiodevice.cpp
+++ b/src/multimedia/pulseaudio/qpulseaudiodevice.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 "qpulseaudiodevice_p.h"
#include "qaudioengine_pulse_p.h"
diff --git a/src/multimedia/pulseaudio/qpulseaudiodevice_p.h b/src/multimedia/pulseaudio/qpulseaudiodevice_p.h
index 0175efca1..b44c71e0d 100644
--- a/src/multimedia/pulseaudio/qpulseaudiodevice_p.h
+++ b/src/multimedia/pulseaudio/qpulseaudiodevice_p.h
@@ -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
#ifndef QAUDIODEVICEINFOPULSE_H
#define QAUDIODEVICEINFOPULSE_H
@@ -69,8 +33,6 @@ class QPulseAudioDeviceInfo : public QAudioDevicePrivate
public:
QPulseAudioDeviceInfo(const char *device, const char *description, bool isDefault, QAudioDevice::Mode mode);
~QPulseAudioDeviceInfo() {}
-
- pa_channel_map channelMap;
};
QT_END_NAMESPACE
diff --git a/src/multimedia/pulseaudio/qpulseaudiomediadevices.cpp b/src/multimedia/pulseaudio/qpulseaudiomediadevices.cpp
index 75058a887..4deee6033 100644
--- a/src/multimedia/pulseaudio/qpulseaudiomediadevices.cpp
+++ b/src/multimedia/pulseaudio/qpulseaudiomediadevices.cpp
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 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) 2021 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 "qpulseaudiomediadevices_p.h"
#include "qmediadevices.h"
@@ -52,6 +16,13 @@ QPulseAudioMediaDevices::QPulseAudioMediaDevices()
: QPlatformMediaDevices()
{
pulseEngine = new QPulseAudioEngine();
+
+ // TODO: it might make sense to connect device changing signals
+ // to each added QMediaDevices
+ QObject::connect(pulseEngine, &QPulseAudioEngine::audioInputsChanged,
+ this, &QPulseAudioMediaDevices::audioInputsChanged, Qt::DirectConnection);
+ QObject::connect(pulseEngine, &QPulseAudioEngine::audioOutputsChanged,
+ this, &QPulseAudioMediaDevices::audioOutputsChanged, Qt::DirectConnection);
}
QPulseAudioMediaDevices::~QPulseAudioMediaDevices()
@@ -69,19 +40,16 @@ QList<QAudioDevice> QPulseAudioMediaDevices::audioOutputs() const
return pulseEngine->availableDevices(QAudioDevice::Output);
}
-QList<QCameraDevice> QPulseAudioMediaDevices::videoInputs() const
-{
- return {};
-}
-
-QPlatformAudioSource *QPulseAudioMediaDevices::createAudioSource(const QAudioDevice &deviceInfo)
+QPlatformAudioSource *QPulseAudioMediaDevices::createAudioSource(const QAudioDevice &deviceInfo,
+ QObject *parent)
{
- return new QPulseAudioSource(deviceInfo.id());
+ return new QPulseAudioSource(deviceInfo.id(), parent);
}
-QPlatformAudioSink *QPulseAudioMediaDevices::createAudioSink(const QAudioDevice &deviceInfo)
+QPlatformAudioSink *QPulseAudioMediaDevices::createAudioSink(const QAudioDevice &deviceInfo,
+ QObject *parent)
{
- return new QPulseAudioSink(deviceInfo.id());
+ return new QPulseAudioSink(deviceInfo.id(), parent);
}
QT_END_NAMESPACE
diff --git a/src/multimedia/pulseaudio/qpulseaudiomediadevices_p.h b/src/multimedia/pulseaudio/qpulseaudiomediadevices_p.h
index dd811f550..094dc3907 100644
--- a/src/multimedia/pulseaudio/qpulseaudiomediadevices_p.h
+++ b/src/multimedia/pulseaudio/qpulseaudiomediadevices_p.h
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 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) 2021 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
#ifndef QPULSEAUDIOMEDIADEVICES_H
#define QPULSEAUDIOMEDIADEVICES_H
@@ -67,9 +31,10 @@ public:
QList<QAudioDevice> audioInputs() const override;
QList<QAudioDevice> audioOutputs() const override;
- QList<QCameraDevice> videoInputs() const override;
- QPlatformAudioSource *createAudioSource(const QAudioDevice &deviceInfo) override;
- QPlatformAudioSink *createAudioSink(const QAudioDevice &deviceInfo) override;
+ QPlatformAudioSource *createAudioSource(const QAudioDevice &deviceInfo,
+ QObject *parent) override;
+ QPlatformAudioSink *createAudioSink(const QAudioDevice &deviceInfo,
+ QObject *parent) override;
private:
QPulseAudioEngine *pulseEngine;
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
diff --git a/src/multimedia/pulseaudio/qpulseaudiosink_p.h b/src/multimedia/pulseaudio/qpulseaudiosink_p.h
index 1a7b6d212..cf0b181ec 100644
--- a/src/multimedia/pulseaudio/qpulseaudiosink_p.h
+++ b/src/multimedia/pulseaudio/qpulseaudiosink_p.h
@@ -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
#ifndef QAUDIOOUTPUTPULSE_H
#define QAUDIOOUTPUTPULSE_H
@@ -60,8 +24,10 @@
#include "qaudio.h"
#include "qaudiodevice.h"
-#include <private/qaudiosystem_p.h>
+#include "pulseaudio/qpulsehelpers_p.h"
+#include <private/qaudiosystem_p.h>
+#include <private/qaudiostatemachine_p.h>
#include <pulse/pulseaudio.h>
QT_BEGIN_NAMESPACE
@@ -72,7 +38,7 @@ class QPulseAudioSink : public QPlatformAudioSink
Q_OBJECT
public:
- QPulseAudioSink(const QByteArray &device);
+ QPulseAudioSink(const QByteArray &device, QObject *parent);
~QPulseAudioSink();
void start(QIODevice *device) override;
@@ -100,9 +66,8 @@ protected:
void timerEvent(QTimerEvent *event) override;
private:
- void setState(QAudio::State state);
- void setError(QAudio::Error error);
- void startReading();
+ void startPulling();
+ void stopTimer();
bool open();
void close();
@@ -112,7 +77,11 @@ private Q_SLOTS:
void userFeed();
void onPulseContextFailed();
+ PAOperationUPtr exchangeDrainOperation(pa_operation *newOperation);
+
private:
+ qsizetype defaultBufferSize() const;
+
pa_sample_spec m_spec = {};
// calculate timing manually, as pulseaudio doesn't give us good enough data
mutable timeval lastTimingInfo = {};
@@ -126,7 +95,7 @@ private:
QIODevice *m_audioSource = nullptr;
pa_stream *m_stream = nullptr;
- char *m_audioBuffer = nullptr;
+ std::vector<char> m_audioBuffer;
qint64 m_totalTimeValue = 0;
qint64 m_elapsedTimeOffset = 0;
@@ -134,15 +103,15 @@ private:
mutable qint64 lastProcessedUSecs = 0;
qreal m_volume = 1.0;
- QAudio::Error m_errorState = QAudio::NoError;
- QAudio::State m_deviceState = QAudio::StoppedState;
- int m_periodSize = 0;
- int m_bufferSize = 0;
- int m_maxBufferSize = 0;
- int m_periodTime = 0;
+ std::atomic<pa_operation *> m_drainOperation = nullptr;
+ qsizetype m_bufferSize = 0;
+ std::optional<qsizetype> m_userBufferSize = std::nullopt;
+ int m_pullingPeriodSize = 0;
+ int m_pullingPeriodTime = 0;
bool m_pullMode = true;
bool m_opened = false;
- bool m_resuming = false;
+
+ QAudioStateMachine m_stateMachine;
};
class PulseOutputPrivate : public QIODevice
diff --git a/src/multimedia/pulseaudio/qpulseaudiosource.cpp b/src/multimedia/pulseaudio/qpulseaudiosource.cpp
index 4db294b55..488daa48b 100644
--- a/src/multimedia/pulseaudio/qpulseaudiosource.cpp
+++ b/src/multimedia/pulseaudio/qpulseaudiosource.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>
@@ -48,10 +12,11 @@
#include "qpulsehelpers_p.h"
#include <sys/types.h>
#include <unistd.h>
+#include <mutex> // for lock_guard
QT_BEGIN_NAMESPACE
-const int PeriodTimeMs = 50;
+const int SourcePeriodTimeMs = 50;
static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata)
{
@@ -64,37 +29,38 @@ static void inputStreamReadCallback(pa_stream *stream, size_t length, void *user
static void inputStreamStateCallback(pa_stream *stream, void *userdata)
{
+ using namespace QPulseAudioInternal;
+
Q_UNUSED(userdata);
pa_stream_state_t state = pa_stream_get_state(stream);
-#ifdef DEBUG_PULSE
- qDebug() << "Stream state: " << QPulseAudioInternal::stateToQString(state);
-#endif
+ qCDebug(qLcPulseAudioIn) << "Stream state: " << state;
switch (state) {
- case PA_STREAM_CREATING:
+ case PA_STREAM_CREATING:
break;
- case PA_STREAM_READY: {
-#ifdef DEBUG_PULSE
- QPulseAudioSource *audioInput = static_cast<QPulseAudioSource*>(userdata);
+ case PA_STREAM_READY:
+ if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
+ QPulseAudioSource *audioInput = static_cast<QPulseAudioSource *>(userdata);
const pa_buffer_attr *buffer_attr = pa_stream_get_buffer_attr(stream);
- qDebug() << "*** maxlength: " << buffer_attr->maxlength;
- qDebug() << "*** prebuf: " << buffer_attr->prebuf;
- qDebug() << "*** fragsize: " << buffer_attr->fragsize;
- qDebug() << "*** minreq: " << buffer_attr->minreq;
- qDebug() << "*** tlength: " << buffer_attr->tlength;
-
- pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(audioInput->format());
- qDebug() << "*** bytes_to_usec: " << pa_bytes_to_usec(buffer_attr->fragsize, &spec);
-#endif
- }
- break;
- 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;
+ qCDebug(qLcPulseAudioIn) << "*** maxlength: " << buffer_attr->maxlength;
+ qCDebug(qLcPulseAudioIn) << "*** prebuf: " << buffer_attr->prebuf;
+ qCDebug(qLcPulseAudioIn) << "*** fragsize: " << buffer_attr->fragsize;
+ qCDebug(qLcPulseAudioIn) << "*** minreq: " << buffer_attr->minreq;
+ qCDebug(qLcPulseAudioIn) << "*** tlength: " << buffer_attr->tlength;
+
+ pa_sample_spec spec =
+ QPulseAudioInternal::audioFormatToSampleSpec(audioInput->format());
+ qCDebug(qLcPulseAudioIn)
+ << "*** bytes_to_usec: " << pa_bytes_to_usec(buffer_attr->fragsize, &spec);
+ }
+ break;
+ case PA_STREAM_TERMINATED:
+ break;
+ case PA_STREAM_FAILED:
+ default:
+ qWarning() << "Stream error: " << currentError(stream);
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+ break;
}
}
@@ -118,71 +84,49 @@ static void inputStreamSuccessCallback(pa_stream *stream, int success, void *use
Q_UNUSED(userdata);
Q_UNUSED(success);
- //if (!success)
- //TODO: Is cork success? i->operation_success = success;
+ // if (!success)
+ // TODO: Is cork success? i->operation_success = success;
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
}
-QPulseAudioSource::QPulseAudioSource(const QByteArray &device)
- : m_totalTimeValue(0)
- , m_audioSource(nullptr)
- , m_errorState(QAudio::NoError)
- , m_deviceState(QAudio::StoppedState)
- , m_volume(qreal(1.0f))
- , m_pullMode(true)
- , m_opened(false)
- , m_bytesAvailable(0)
- , m_bufferSize(0)
- , m_periodSize(0)
- , m_periodTime(PeriodTimeMs)
- , m_stream(nullptr)
- , m_device(device)
+QPulseAudioSource::QPulseAudioSource(const QByteArray &device, QObject *parent)
+ : QPlatformAudioSource(parent),
+ m_totalTimeValue(0),
+ m_audioSource(nullptr),
+ m_volume(qreal(1.0f)),
+ m_pullMode(true),
+ m_opened(false),
+ m_bufferSize(0),
+ m_periodSize(0),
+ m_periodTime(SourcePeriodTimeMs),
+ m_stream(nullptr),
+ m_device(device),
+ m_stateMachine(*this)
{
- m_timer = new QTimer(this);
- connect(m_timer, SIGNAL(timeout()), SLOT(userFeed()));
}
QPulseAudioSource::~QPulseAudioSource()
{
- close();
- disconnect(m_timer, SIGNAL(timeout()));
- QCoreApplication::processEvents();
- delete m_timer;
-}
-
-void QPulseAudioSource::setError(QAudio::Error error)
-{
- if (m_errorState == error)
- return;
-
- m_errorState = error;
- emit errorChanged(error);
+ // TODO: Investigate draining the stream
+ if (auto notifier = m_stateMachine.stop())
+ close();
}
QAudio::Error QPulseAudioSource::error() const
{
- return m_errorState;
-}
-
-void QPulseAudioSource::setState(QAudio::State state)
-{
- if (m_deviceState == state)
- return;
-
- m_deviceState = state;
- emit stateChanged(state);
+ return m_stateMachine.error();
}
QAudio::State QPulseAudioSource::state() const
{
- return m_deviceState;
+ return m_stateMachine.state();
}
void QPulseAudioSource::setFormat(const QAudioFormat &format)
{
- if (m_deviceState == QAudio::StoppedState)
+ if (!m_stateMachine.isActiveOrIdle())
m_format = format;
}
@@ -193,15 +137,7 @@ QAudioFormat QPulseAudioSource::format() const
void QPulseAudioSource::start(QIODevice *device)
{
- setState(QAudio::StoppedState);
- setError(QAudio::NoError);
-
- if (!m_pullMode && m_audioSource) {
- delete m_audioSource;
- m_audioSource = nullptr;
- }
-
- close();
+ reset();
if (!open())
return;
@@ -209,20 +145,12 @@ void QPulseAudioSource::start(QIODevice *device)
m_pullMode = true;
m_audioSource = device;
- setState(QAudio::ActiveState);
+ m_stateMachine.start();
}
QIODevice *QPulseAudioSource::start()
{
- setState(QAudio::StoppedState);
- setError(QAudio::NoError);
-
- if (!m_pullMode && m_audioSource) {
- delete m_audioSource;
- m_audioSource = nullptr;
- }
-
- close();
+ reset();
if (!open())
return nullptr;
@@ -231,20 +159,15 @@ QIODevice *QPulseAudioSource::start()
m_audioSource = new PulseInputPrivate(this);
m_audioSource->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
- setState(QAudio::IdleState);
+ m_stateMachine.start(false);
return m_audioSource;
}
void QPulseAudioSource::stop()
{
- if (m_deviceState == QAudio::StoppedState)
- return;
-
- close();
-
- setError(QAudio::NoError);
- setState(QAudio::StoppedState);
+ if (auto notifier = m_stateMachine.stop())
+ close();
}
bool QPulseAudioSource::open()
@@ -254,9 +177,9 @@ bool QPulseAudioSource::open()
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
- if (!pulseEngine->context() || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) {
- setError(QAudio::FatalError);
- setState(QAudio::StoppedState);
+ if (!pulseEngine->context()
+ || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) {
+ m_stateMachine.stopOrUpdateError(QAudio::FatalError);
return false;
}
@@ -265,27 +188,27 @@ bool QPulseAudioSource::open()
Q_ASSERT(spec.channels == channel_map.channels);
if (!pa_sample_spec_valid(&spec)) {
- setError(QAudio::OpenError);
- setState(QAudio::StoppedState);
+ m_stateMachine.stopOrUpdateError(QAudio::OpenError);
return false;
}
m_spec = spec;
-#ifdef DEBUG_PULSE
-// QTime now(QTime::currentTime());
-// qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
-#endif
+ //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg)) {
+ // QTime now(QTime::currentTime());
+ // qCDebug(qLcPulseAudioIn) << now.second() << "s " << now.msec() << "ms :open()";
+ //}
if (m_streamName.isNull())
- m_streamName = QString(QLatin1String("QtmPulseStream-%1-%2")).arg(::getpid()).arg(quintptr(this)).toUtf8();
-
-#ifdef DEBUG_PULSE
- qDebug() << "Format: " << QPulseAudioInternal::sampleFormatToQString(spec.format);
- qDebug() << "Rate: " << spec.rate;
- qDebug() << "Channels: " << spec.channels;
- qDebug() << "Frame size: " << pa_frame_size(&spec);
-#endif
+ m_streamName =
+ QStringLiteral("QtmPulseStream-%1-%2").arg(::getpid()).arg(quintptr(this)).toUtf8();
+
+ if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
+ qCDebug(qLcPulseAudioIn) << "Format: " << spec.format;
+ qCDebug(qLcPulseAudioIn) << "Rate: " << spec.rate;
+ qCDebug(qLcPulseAudioIn) << "Channels: " << spec.channels;
+ qCDebug(qLcPulseAudioIn) << "Frame size: " << pa_frame_size(&spec);
+ }
pulseEngine->lock();
@@ -297,37 +220,41 @@ bool QPulseAudioSource::open()
pa_stream_set_underflow_callback(m_stream, inputStreamUnderflowCallback, this);
pa_stream_set_overflow_callback(m_stream, inputStreamOverflowCallback, this);
- m_periodSize = pa_usec_to_bytes(PeriodTimeMs*1000, &spec);
+ m_periodSize = pa_usec_to_bytes(SourcePeriodTimeMs * 1000, &spec);
int flags = 0;
pa_buffer_attr buffer_attr;
- buffer_attr.maxlength = (uint32_t) -1;
- buffer_attr.prebuf = (uint32_t) -1;
- buffer_attr.tlength = (uint32_t) -1;
- buffer_attr.minreq = (uint32_t) -1;
+ buffer_attr.maxlength = static_cast<uint32_t>(-1);
+ buffer_attr.prebuf = static_cast<uint32_t>(-1);
+ buffer_attr.tlength = static_cast<uint32_t>(-1);
+ buffer_attr.minreq = static_cast<uint32_t>(-1);
flags |= PA_STREAM_ADJUST_LATENCY;
if (m_bufferSize > 0)
- buffer_attr.fragsize = (uint32_t) m_bufferSize;
+ buffer_attr.fragsize = static_cast<uint32_t>(m_bufferSize);
else
- buffer_attr.fragsize = (uint32_t) m_periodSize;
+ buffer_attr.fragsize = static_cast<uint32_t>(m_periodSize);
+
+ flags |= PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING;
- flags |= PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING;
- if (pa_stream_connect_record(m_stream, m_device.data(), &buffer_attr, (pa_stream_flags_t)flags) < 0) {
+ int connectionResult = pa_stream_connect_record(m_stream, m_device.data(), &buffer_attr,
+ static_cast<pa_stream_flags_t>(flags));
+ if (connectionResult < 0) {
qWarning() << "pa_stream_connect_record() failed!";
pa_stream_unref(m_stream);
m_stream = nullptr;
pulseEngine->unlock();
- setError(QAudio::OpenError);
- setState(QAudio::StoppedState);
+ m_stateMachine.stopOrUpdateError(QAudio::OpenError);
return false;
}
-// auto *ss = pa_stream_get_sample_spec(m_stream);
-// qDebug() << "connected stream:";
-// qDebug() << " channels" << ss->channels << spec.channels;
-// qDebug() << " format" << ss->format << spec.format;
-// qDebug() << " rate" << ss->rate << spec.rate;
+ //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
+ // auto *ss = pa_stream_get_sample_spec(m_stream);
+ // qCDebug(qLcPulseAudioIn) << "connected stream:";
+ // qCDebug(qLcPulseAudioIn) << " channels" << ss->channels << spec.channels;
+ // qCDebug(qLcPulseAudioIn) << " format" << ss->format << spec.format;
+ // qCDebug(qLcPulseAudioIn) << " rate" << ss->rate << spec.rate;
+ //}
while (pa_stream_get_state(m_stream) != PA_STREAM_READY)
pa_threaded_mainloop_wait(pulseEngine->mainloop());
@@ -335,15 +262,16 @@ bool QPulseAudioSource::open()
const pa_buffer_attr *actualBufferAttr = pa_stream_get_buffer_attr(m_stream);
m_periodSize = actualBufferAttr->fragsize;
m_periodTime = pa_bytes_to_usec(m_periodSize, &spec) / 1000;
- if (actualBufferAttr->tlength != (uint32_t)-1)
+ if (actualBufferAttr->tlength != static_cast<uint32_t>(-1))
m_bufferSize = actualBufferAttr->tlength;
pulseEngine->unlock();
- connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioSource::onPulseContextFailed);
+ connect(pulseEngine, &QPulseAudioEngine::contextFailed, this,
+ &QPulseAudioSource::onPulseContextFailed);
m_opened = true;
- m_timer->start(m_periodTime);
+ m_timer.start(m_periodTime, this);
m_elapsedTimeOffset = 0;
m_totalTimeValue = 0;
@@ -356,12 +284,12 @@ void QPulseAudioSource::close()
if (!m_opened)
return;
- m_timer->stop();
+ m_timer.stop();
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_read_callback(m_stream, nullptr, nullptr);
@@ -371,11 +299,10 @@ void QPulseAudioSource::close()
pa_stream_disconnect(m_stream);
pa_stream_unref(m_stream);
m_stream = nullptr;
-
- pulseEngine->unlock();
}
- disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioSource::onPulseContextFailed);
+ disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this,
+ &QPulseAudioSource::onPulseContextFailed);
if (!m_pullMode && m_audioSource) {
delete m_audioSource;
@@ -384,32 +311,31 @@ void QPulseAudioSource::close()
m_opened = false;
}
-int QPulseAudioSource::checkBytesReady()
+qsizetype QPulseAudioSource::bytesReady() const
{
- if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState) {
- m_bytesAvailable = 0;
- } else {
- m_bytesAvailable = pa_stream_readable_size(m_stream);
- }
+ using namespace QPulseAudioInternal;
- return m_bytesAvailable;
-}
+ if (!m_stateMachine.isActiveOrIdle())
+ return 0;
-qsizetype QPulseAudioSource::bytesReady() const
-{
- return qMax(m_bytesAvailable, 0);
+ std::lock_guard lock(*QPulseAudioEngine::instance());
+
+ int bytes = pa_stream_readable_size(m_stream);
+ if (bytes < 0) {
+ qWarning() << "pa_stream_readable_size() failed:" << currentError(m_stream);
+ return 0;
+ }
+
+ return static_cast<qsizetype>(bytes);
}
qint64 QPulseAudioSource::read(char *data, qint64 len)
{
- Q_ASSERT(data != nullptr || len == 0);
+ using namespace QPulseAudioInternal;
- m_bytesAvailable = checkBytesReady();
-
- setError(QAudio::NoError);
- if (state() == QAudio::IdleState)
- setState(QAudio::ActiveState);
+ Q_ASSERT(data != nullptr || len == 0);
+ m_stateMachine.updateActiveOrIdle(true, QAudio::NoError);
int readBytes = 0;
if (!m_pullMode && !m_tempBuffer.isEmpty()) {
@@ -429,21 +355,22 @@ qint64 QPulseAudioSource::read(char *data, qint64 len)
while (pa_stream_readable_size(m_stream) > 0) {
size_t readLength = 0;
-#ifdef DEBUG_PULSE
- qDebug() << "QPulseAudioSource::read -- " << pa_stream_readable_size(m_stream) << " bytes available from pulse audio";
-#endif
+ if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
+ auto readableSize = pa_stream_readable_size(m_stream);
+ qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- " << readableSize
+ << " bytes available from pulse audio";
+ }
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pulseEngine->lock();
const void *audioBuffer;
- // Second and third parameters (audioBuffer and length) to pa_stream_peek are output parameters,
- // the audioBuffer pointer is set to point to the actual pulse audio data,
- // and the length is set to the length of this data.
+ // Second and third parameters (audioBuffer and length) to pa_stream_peek are output
+ // parameters, the audioBuffer pointer is set to point to the actual pulse audio data, and
+ // the length is set to the length of this data.
if (pa_stream_peek(m_stream, &audioBuffer, &readLength) < 0) {
- qWarning() << QString::fromLatin1("pa_stream_peek() failed: %1")
- .arg(QString::fromUtf8(pa_strerror(pa_context_errno(pa_stream_get_context(m_stream)))));
+ qWarning() << "pa_stream_peek() failed:" << currentError(m_stream);
pulseEngine->unlock();
return 0;
}
@@ -456,10 +383,7 @@ qint64 QPulseAudioSource::read(char *data, qint64 len)
if (actualLength < qint64(readLength)) {
pulseEngine->unlock();
-
- setError(QAudio::UnderrunError);
- setState(QAudio::IdleState);
-
+ m_stateMachine.updateActiveOrIdle(false, QAudio::UnderrunError);
return actualLength;
}
} else {
@@ -467,18 +391,19 @@ qint64 QPulseAudioSource::read(char *data, qint64 len)
applyVolume(audioBuffer, data + readBytes, actualLength);
}
-#ifdef DEBUG_PULSE
- qDebug() << "QPulseAudioSource::read -- wrote " << actualLength << " to client";
-#endif
+ qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- wrote " << actualLength
+ << " to client";
if (actualLength < qint64(readLength)) {
-#ifdef DEBUG_PULSE
- qDebug() << "QPulseAudioSource::read -- appending " << readLength - actualLength << " bytes of data to temp buffer";
-#endif
int diff = readLength - actualLength;
int oldSize = m_tempBuffer.size();
+
+ qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- appending " << diff
+ << " bytes of data to temp buffer";
+
m_tempBuffer.resize(m_tempBuffer.size() + diff);
- applyVolume(static_cast<const char *>(audioBuffer) + actualLength, m_tempBuffer.data() + oldSize, diff);
+ applyVolume(static_cast<const char *>(audioBuffer) + actualLength,
+ m_tempBuffer.data() + oldSize, diff);
QMetaObject::invokeMethod(this, "userFeed", Qt::QueuedConnection);
}
@@ -492,9 +417,8 @@ qint64 QPulseAudioSource::read(char *data, qint64 len)
break;
}
-#ifdef DEBUG_PULSE
- qDebug() << "QPulseAudioSource::read -- returning after reading " << readBytes << " bytes";
-#endif
+ qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- returning after reading " << readBytes
+ << " bytes";
return readBytes;
}
@@ -510,22 +434,18 @@ void QPulseAudioSource::applyVolume(const void *src, void *dest, int len)
void QPulseAudioSource::resume()
{
- if (m_deviceState == QAudio::SuspendedState || m_deviceState == QAudio::IdleState) {
- QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
- pa_operation *operation;
-
- pulseEngine->lock();
-
- operation = pa_stream_cork(m_stream, 0, inputStreamSuccessCallback, nullptr);
- pulseEngine->wait(operation);
- pa_operation_unref(operation);
+ if (auto notifier = m_stateMachine.resume()) {
+ {
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
- pulseEngine->unlock();
+ std::lock_guard lock(*pulseEngine);
- m_timer->start(m_periodTime);
+ PAOperationUPtr operation(
+ pa_stream_cork(m_stream, 0, inputStreamSuccessCallback, nullptr));
+ pulseEngine->wait(operation.get());
+ }
- setState(QAudio::ActiveState);
- setError(QAudio::NoError);
+ m_timer.start(m_periodTime, this);
}
}
@@ -559,81 +479,69 @@ qint64 QPulseAudioSource::processedUSecs() const
pa_usec_t usecs = 0;
int result = pa_stream_get_time(m_stream, &usecs);
Q_UNUSED(result);
-// if (result != 0)
-// qWarning() << "no timing info from pulse";
+ //if (result != 0)
+ // qWarning() << "no timing info from pulse";
return usecs;
}
void QPulseAudioSource::suspend()
{
- if (m_deviceState == QAudio::ActiveState) {
- setError(QAudio::NoError);
- setState(QAudio::SuspendedState);
-
- m_timer->stop();
+ if (auto notifier = m_stateMachine.suspend()) {
+ m_timer.stop();
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
- pa_operation *operation;
- pulseEngine->lock();
+ std::lock_guard lock(*pulseEngine);
- operation = pa_stream_cork(m_stream, 1, inputStreamSuccessCallback, nullptr);
- pulseEngine->wait(operation);
- pa_operation_unref(operation);
-
- pulseEngine->unlock();
+ PAOperationUPtr operation(pa_stream_cork(m_stream, 1, inputStreamSuccessCallback, nullptr));
+ pulseEngine->wait(operation.get());
}
}
-void QPulseAudioSource::userFeed()
+void QPulseAudioSource::timerEvent(QTimerEvent *event)
{
- if (m_deviceState == QAudio::StoppedState || m_deviceState == QAudio::SuspendedState)
- return;
-#ifdef DEBUG_PULSE
-// QTime now(QTime::currentTime());
-// qDebug()<< now.second() << "s " << now.msec() << "ms :userFeed() IN";
-#endif
- deviceReady();
+ if (event->timerId() == m_timer.timerId())
+ userFeed();
+
+ QPlatformAudioSource::timerEvent(event);
}
-bool QPulseAudioSource::deviceReady()
+void QPulseAudioSource::userFeed()
{
- if (m_pullMode) {
+ if (!m_stateMachine.isActiveOrIdle())
+ return;
+
+ //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg)) {
+ // QTime now(QTime::currentTime());
+ // qCDebug(qLcPulseAudioIn) << now.second() << "s " << now.msec() << "ms :userFeed() IN";
+ //}
+
+ if (m_pullMode) {
// reads some audio data and writes it to QIODevice
read(nullptr,0);
- } else {
+ } else if (m_audioSource != nullptr) {
// emits readyRead() so user will call read() on QIODevice to get some audio data
- if (m_audioSource != nullptr) {
- PulseInputPrivate *a = qobject_cast<PulseInputPrivate*>(m_audioSource);
- a->trigger();
- }
+ PulseInputPrivate *a = qobject_cast<PulseInputPrivate*>(m_audioSource);
+ a->trigger();
}
- m_bytesAvailable = checkBytesReady();
-
- if (m_deviceState != QAudio::ActiveState)
- return true;
-
- return true;
}
void QPulseAudioSource::reset()
{
- stop();
- m_bytesAvailable = 0;
+ if (auto notifier = m_stateMachine.stopOrUpdateError())
+ close();
}
void QPulseAudioSource::onPulseContextFailed()
{
- close();
-
- setError(QAudio::FatalError);
- setState(QAudio::StoppedState);
+ if (auto notifier = m_stateMachine.stopOrUpdateError(QAudio::FatalError))
+ close();
}
PulseInputPrivate::PulseInputPrivate(QPulseAudioSource *audio)
{
- m_audioDevice = qobject_cast<QPulseAudioSource*>(audio);
+ m_audioDevice = qobject_cast<QPulseAudioSource *>(audio);
}
qint64 PulseInputPrivate::readData(char *data, qint64 len)
diff --git a/src/multimedia/pulseaudio/qpulseaudiosource_p.h b/src/multimedia/pulseaudio/qpulseaudiosource_p.h
index 7a9f047dd..d652f81a0 100644
--- a/src/multimedia/pulseaudio/qpulseaudiosource_p.h
+++ b/src/multimedia/pulseaudio/qpulseaudiosource_p.h
@@ -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
@@ -61,6 +25,7 @@
#include "qaudio.h"
#include "qaudiodevice.h"
#include <private/qaudiosystem_p.h>
+#include <private/qaudiostatemachine_p.h>
#include <pulse/pulseaudio.h>
@@ -73,7 +38,7 @@ class QPulseAudioSource : public QPlatformAudioSource
Q_OBJECT
public:
- QPulseAudioSource(const QByteArray &device);
+ QPulseAudioSource(const QByteArray &device, QObject *parent);
~QPulseAudioSource();
qint64 read(char *data, qint64 len);
@@ -99,38 +64,35 @@ public:
qint64 m_totalTimeValue;
QIODevice *m_audioSource;
QAudioFormat m_format;
- QAudio::Error m_errorState;
- QAudio::State m_deviceState;
qreal m_volume;
+protected:
+ void timerEvent(QTimerEvent *event) override;
+
private slots:
void userFeed();
- bool deviceReady();
void onPulseContextFailed();
private:
- void setState(QAudio::State state);
- void setError(QAudio::Error error);
-
void applyVolume(const void *src, void *dest, int len);
- int checkBytesReady();
bool open();
void close();
bool m_pullMode;
bool m_opened;
- int m_bytesAvailable;
int m_bufferSize;
int m_periodSize;
unsigned int m_periodTime;
- QTimer *m_timer;
+ QBasicTimer m_timer;
qint64 m_elapsedTimeOffset;
pa_stream *m_stream;
QByteArray m_streamName;
QByteArray m_device;
QByteArray m_tempBuffer;
pa_sample_spec m_spec;
+
+ QAudioStateMachine m_stateMachine;
};
class PulseInputPrivate : public QIODevice
@@ -138,7 +100,7 @@ class PulseInputPrivate : public QIODevice
Q_OBJECT
public:
PulseInputPrivate(QPulseAudioSource *audio);
- ~PulseInputPrivate() {};
+ ~PulseInputPrivate() override = default;
qint64 readData(char *data, qint64 len) override;
qint64 writeData(const char *data, qint64 len) override;
diff --git a/src/multimedia/pulseaudio/qpulsehelpers.cpp b/src/multimedia/pulseaudio/qpulsehelpers.cpp
index 0136fd125..bc03e133f 100644
--- a/src/multimedia/pulseaudio/qpulsehelpers.cpp
+++ b/src/multimedia/pulseaudio/qpulsehelpers.cpp
@@ -1,47 +1,13 @@
-/****************************************************************************
-**
-** 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 "qpulsehelpers_p.h"
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(qLcPulseAudioOut, "qt.multimedia.pulseaudio.output")
+Q_LOGGING_CATEGORY(qLcPulseAudioIn, "qt.multimedia.pulseaudio.input")
+Q_LOGGING_CATEGORY(qLcPulseAudioEngine, "qt.multimedia.pulseaudio.engine")
namespace QPulseAudioInternal
{
@@ -233,6 +199,86 @@ QAudioFormat sampleSpecToAudioFormat(const pa_sample_spec &spec)
return format;
}
+QUtf8StringView currentError(const pa_context *context)
+{
+ return pa_strerror(pa_context_errno(context));
+}
+
+QUtf8StringView currentError(const pa_stream *stream)
+{
+ return currentError(pa_stream_get_context(stream));
+}
+
+} // namespace QPulseAudioInternal
+
+static QLatin1StringView stateToQStringView(pa_stream_state_t state)
+{
+ using namespace Qt::StringLiterals;
+ switch (state)
+ {
+ case PA_STREAM_UNCONNECTED: return "Unconnected"_L1;
+ case PA_STREAM_CREATING: return "Creating"_L1;
+ case PA_STREAM_READY: return "Ready"_L1;
+ case PA_STREAM_FAILED: return "Failed"_L1;
+ case PA_STREAM_TERMINATED: return "Terminated"_L1;
+ default: Q_UNREACHABLE_RETURN("Unknown stream state"_L1);
+ }
+}
+
+static QLatin1StringView sampleFormatToQStringView(pa_sample_format format)
+{
+ using namespace Qt::StringLiterals;
+ switch (format)
+ {
+ case PA_SAMPLE_U8: return "Unsigned 8 Bit PCM."_L1;
+ case PA_SAMPLE_ALAW: return "8 Bit a-Law "_L1;
+ case PA_SAMPLE_ULAW: return "8 Bit mu-Law"_L1;
+ case PA_SAMPLE_S16LE: return "Signed 16 Bit PCM, little endian (PC)."_L1;
+ case PA_SAMPLE_S16BE: return "Signed 16 Bit PCM, big endian."_L1;
+ case PA_SAMPLE_FLOAT32LE: return "32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0"_L1;
+ case PA_SAMPLE_FLOAT32BE: return "32 Bit IEEE floating point, big endian, range -1.0 to 1.0"_L1;
+ case PA_SAMPLE_S32LE: return "Signed 32 Bit PCM, little endian (PC)."_L1;
+ case PA_SAMPLE_S32BE: return "Signed 32 Bit PCM, big endian."_L1;
+ case PA_SAMPLE_S24LE: return "Signed 24 Bit PCM packed, little endian (PC)."_L1;
+ case PA_SAMPLE_S24BE: return "Signed 24 Bit PCM packed, big endian."_L1;
+ case PA_SAMPLE_S24_32LE: return "Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC)."_L1;
+ case PA_SAMPLE_S24_32BE: return "Signed 24 Bit PCM in LSB of 32 Bit words, big endian."_L1;
+ case PA_SAMPLE_MAX: return "Upper limit of valid sample types."_L1;
+ case PA_SAMPLE_INVALID: return "Invalid sample format"_L1;
+ default: Q_UNREACHABLE_RETURN("Unknown sample format"_L1);
+ }
+}
+
+static QLatin1StringView stateToQStringView(pa_context_state_t state)
+{
+ using namespace Qt::StringLiterals;
+ switch (state)
+ {
+ case PA_CONTEXT_UNCONNECTED: return "Unconnected"_L1;
+ case PA_CONTEXT_CONNECTING: return "Connecting"_L1;
+ case PA_CONTEXT_AUTHORIZING: return "Authorizing"_L1;
+ case PA_CONTEXT_SETTING_NAME: return "Setting Name"_L1;
+ case PA_CONTEXT_READY: return "Ready"_L1;
+ case PA_CONTEXT_FAILED: return "Failed"_L1;
+ case PA_CONTEXT_TERMINATED: return "Terminated"_L1;
+ default: Q_UNREACHABLE_RETURN("Unknown context state"_L1);
+ }
+}
+
+
+QDebug operator<<(QDebug dbg, pa_stream_state_t state)
+{
+ return dbg << stateToQStringView(state);
+}
+
+QDebug operator<<(QDebug dbg, pa_sample_format format)
+{
+ return dbg << sampleFormatToQStringView(format);
+}
+
+QDebug operator<<(QDebug dbg, pa_context_state_t state)
+{
+ return dbg << stateToQStringView(state);
}
QT_END_NAMESPACE
diff --git a/src/multimedia/pulseaudio/qpulsehelpers_p.h b/src/multimedia/pulseaudio/qpulsehelpers_p.h
index 00f26757e..d271fde48 100644
--- a/src/multimedia/pulseaudio/qpulsehelpers_p.h
+++ b/src/multimedia/pulseaudio/qpulsehelpers_p.h
@@ -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
#ifndef QPULSEHELPER_H
#define QPULSEHELPER_H
@@ -55,10 +19,20 @@
#include <qaudioformat.h>
#include <pulse/pulseaudio.h>
#include <QtCore/QLoggingCategory>
+#include <QtCore/qdebug.h>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(qLcPulseAudioOut)
+Q_DECLARE_LOGGING_CATEGORY(qLcPulseAudioIn)
+Q_DECLARE_LOGGING_CATEGORY(qLcPulseAudioEngine)
+
+struct PAOperationDeleter
+{
+ void operator()(pa_operation *op) const { pa_operation_unref(op); }
+};
+
+using PAOperationUPtr = std::unique_ptr<pa_operation, PAOperationDeleter>;
namespace QPulseAudioInternal
{
@@ -67,63 +41,14 @@ QAudioFormat sampleSpecToAudioFormat(const pa_sample_spec &spec);
pa_channel_map channelMapForAudioFormat(const QAudioFormat &format);
QAudioFormat::ChannelConfig channelConfigFromMap(const pa_channel_map &map);
-static inline QString stateToQString(pa_stream_state_t state)
-{
- using namespace Qt::StringLiterals;
- switch (state)
- {
- case PA_STREAM_UNCONNECTED: return "Unconnected"_L1;
- case PA_STREAM_CREATING: return "Creating"_L1;
- case PA_STREAM_READY: return "Ready"_L1;
- case PA_STREAM_FAILED: return "Failed"_L1;
- case PA_STREAM_TERMINATED: return "Terminated"_L1;
- }
-
- return u"Unknown state: %0"_s.arg(int(state));
-}
+QUtf8StringView currentError(const pa_context *);
+QUtf8StringView currentError(const pa_stream *);
-static inline QString sampleFormatToQString(pa_sample_format format)
-{
- using namespace Qt::StringLiterals;
- switch (format)
- {
- case PA_SAMPLE_U8: return "Unsigned 8 Bit PCM."_L1;
- case PA_SAMPLE_ALAW: return "8 Bit a-Law "_L1;
- case PA_SAMPLE_ULAW: return "8 Bit mu-Law"_L1;
- case PA_SAMPLE_S16LE: return "Signed 16 Bit PCM, little endian (PC)."_L1;
- case PA_SAMPLE_S16BE: return "Signed 16 Bit PCM, big endian."_L1;
- case PA_SAMPLE_FLOAT32LE: return "32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0"_L1;
- case PA_SAMPLE_FLOAT32BE: return "32 Bit IEEE floating point, big endian, range -1.0 to 1.0"_L1;
- case PA_SAMPLE_S32LE: return "Signed 32 Bit PCM, little endian (PC)."_L1;
- case PA_SAMPLE_S32BE: return "Signed 32 Bit PCM, big endian."_L1;
- case PA_SAMPLE_S24LE: return "Signed 24 Bit PCM packed, little endian (PC)."_L1;
- case PA_SAMPLE_S24BE: return "Signed 24 Bit PCM packed, big endian."_L1;
- case PA_SAMPLE_S24_32LE: return "Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC)."_L1;
- case PA_SAMPLE_S24_32BE: return "Signed 24 Bit PCM in LSB of 32 Bit words, big endian."_L1;
- case PA_SAMPLE_MAX: return "Upper limit of valid sample types."_L1;
- case PA_SAMPLE_INVALID: return "Invalid sample format"_L1;
- }
-
- return u"Invalid value: %0"_s.arg(int(format));
-}
-
-static inline QString stateToQString(pa_context_state_t state)
-{
- using namespace Qt::StringLiterals;
- switch (state)
- {
- case PA_CONTEXT_UNCONNECTED: return "Unconnected"_L1;
- case PA_CONTEXT_CONNECTING: return "Connecting"_L1;
- case PA_CONTEXT_AUTHORIZING: return "Authorizing"_L1;
- case PA_CONTEXT_SETTING_NAME: return "Setting Name"_L1;
- case PA_CONTEXT_READY: return "Ready"_L1;
- case PA_CONTEXT_FAILED: return "Failed"_L1;
- case PA_CONTEXT_TERMINATED: return "Terminated"_L1;
- }
+} // namespace QPulseAudioInternal
- return u"Unknown state: %0"_s.arg(int(state));
-}
-}
+QDebug operator<<(QDebug, pa_stream_state_t);
+QDebug operator<<(QDebug, pa_sample_format);
+QDebug operator<<(QDebug, pa_context_state_t);
QT_END_NAMESPACE