summaryrefslogtreecommitdiffstats
path: root/src/multimedia/audio
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2020-12-16 11:02:15 +0100
committerLars Knoll <lars.knoll@qt.io>2021-01-20 15:12:01 +0000
commitf16dbe174cee73308cae2aae512fe837e07e0f55 (patch)
treed991c5c4fc2f2ca2d9eb48d3fdc4c20a2ef3cffb /src/multimedia/audio
parenta019323edf4f2679f00eb3653e0f8791204cf12a (diff)
Remove the plugin infrastructure for audio plugins
Move the PulseAudio and ALSA code directly into Qt Multimedia. The other plugins will follow in the next commits. Change-Id: I59471fa65639ccc746e6fa182d34373f6f901199 Reviewed-by: Doris Verria <doris.verria@qt.io> Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/multimedia/audio')
-rw-r--r--src/multimedia/audio/alsa/alsa.json3
-rw-r--r--src/multimedia/audio/alsa/alsa.pri11
-rw-r--r--src/multimedia/audio/alsa/qalsaaudiodeviceinfo.cpp430
-rw-r--r--src/multimedia/audio/alsa/qalsaaudiodeviceinfo_p.h120
-rw-r--r--src/multimedia/audio/alsa/qalsaaudioinput.cpp872
-rw-r--r--src/multimedia/audio/alsa/qalsaaudioinput_p.h185
-rw-r--r--src/multimedia/audio/alsa/qalsaaudiooutput.cpp813
-rw-r--r--src/multimedia/audio/alsa/qalsaaudiooutput_p.h164
-rw-r--r--src/multimedia/audio/alsa/qalsainterface.cpp72
-rw-r--r--src/multimedia/audio/alsa/qalsainterface_p.h59
-rw-r--r--src/multimedia/audio/audio.pri13
-rw-r--r--src/multimedia/audio/pulseaudio/pulseaudio.pri15
-rw-r--r--src/multimedia/audio/pulseaudio/qaudiodeviceinfo_pulse.cpp103
-rw-r--r--src/multimedia/audio/pulseaudio/qaudiodeviceinfo_pulse_p.h90
-rw-r--r--src/multimedia/audio/pulseaudio/qaudioengine_pulse.cpp482
-rw-r--r--src/multimedia/audio/pulseaudio/qaudioengine_pulse_p.h128
-rw-r--r--src/multimedia/audio/pulseaudio/qaudioinput_pulse.cpp682
-rw-r--r--src/multimedia/audio/pulseaudio/qaudioinput_pulse_p.h161
-rw-r--r--src/multimedia/audio/pulseaudio/qaudiointerface_pulse.cpp85
-rw-r--r--src/multimedia/audio/pulseaudio/qaudiointerface_pulse_p.h77
-rw-r--r--src/multimedia/audio/pulseaudio/qaudiooutput_pulse.cpp739
-rw-r--r--src/multimedia/audio/pulseaudio/qaudiooutput_pulse_p.h166
-rw-r--r--src/multimedia/audio/pulseaudio/qpulsehelpers.cpp211
-rw-r--r--src/multimedia/audio/pulseaudio/qpulsehelpers_p.h71
-rw-r--r--src/multimedia/audio/qaudiodevicefactory.cpp100
-rw-r--r--src/multimedia/audio/qaudiodevicefactory_p.h2
-rw-r--r--src/multimedia/audio/qaudiodeviceinfo.cpp32
-rw-r--r--src/multimedia/audio/qaudiodeviceinfo.h3
-rw-r--r--src/multimedia/audio/qaudiosysteminterface.cpp (renamed from src/multimedia/audio/qaudiosystemplugin.cpp)56
-rw-r--r--src/multimedia/audio/qaudiosysteminterface_p.h (renamed from src/multimedia/audio/qaudiosystemplugin.h)30
30 files changed, 5827 insertions, 148 deletions
diff --git a/src/multimedia/audio/alsa/alsa.json b/src/multimedia/audio/alsa/alsa.json
new file mode 100644
index 000000000..c2b22dfec
--- /dev/null
+++ b/src/multimedia/audio/alsa/alsa.json
@@ -0,0 +1,3 @@
+{
+ "Keys": ["alsa"]
+}
diff --git a/src/multimedia/audio/alsa/alsa.pri b/src/multimedia/audio/alsa/alsa.pri
new file mode 100644
index 000000000..b3f5739c0
--- /dev/null
+++ b/src/multimedia/audio/alsa/alsa.pri
@@ -0,0 +1,11 @@
+QMAKE_USE_PRIVATE += alsa
+
+HEADERS += audio/alsa/qalsaaudiodeviceinfo_p.h \
+ audio/alsa/qalsaaudioinput_p.h \
+ audio/alsa/qalsaaudiooutput_p.h \
+ audio/alsa/qalsainterface_p.h
+
+SOURCES += audio/alsa/qalsaaudiodeviceinfo.cpp \
+ audio/alsa/qalsaaudioinput.cpp \
+ audio/alsa/qalsaaudiooutput.cpp \
+ audio/alsa/qalsainterface.cpp
diff --git a/src/multimedia/audio/alsa/qalsaaudiodeviceinfo.cpp b/src/multimedia/audio/alsa/qalsaaudiodeviceinfo.cpp
new file mode 100644
index 000000000..0d4aa11b5
--- /dev/null
+++ b/src/multimedia/audio/alsa/qalsaaudiodeviceinfo.cpp
@@ -0,0 +1,430 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// INTERNAL USE ONLY: Do NOT use for any other purpose.
+//
+
+#include "qalsaaudiodeviceinfo_p.h"
+
+#include <alsa/version.h>
+
+QT_BEGIN_NAMESPACE
+
+QAlsaAudioDeviceInfo::QAlsaAudioDeviceInfo(const QByteArray &dev, QAudio::Mode mode)
+{
+ handle = 0;
+
+ device = QLatin1String(dev);
+ this->mode = mode;
+
+ checkSurround();
+}
+
+QAlsaAudioDeviceInfo::~QAlsaAudioDeviceInfo()
+{
+ close();
+}
+
+bool QAlsaAudioDeviceInfo::isFormatSupported(const QAudioFormat& format) const
+{
+ return testSettings(format);
+}
+
+QAudioFormat QAlsaAudioDeviceInfo::preferredFormat() const
+{
+ QAudioFormat nearest;
+ if(mode == QAudio::AudioOutput) {
+ nearest.setSampleRate(44100);
+ nearest.setChannelCount(2);
+ nearest.setByteOrder(QAudioFormat::LittleEndian);
+ nearest.setSampleType(QAudioFormat::SignedInt);
+ nearest.setSampleSize(16);
+ nearest.setCodec(QLatin1String("audio/pcm"));
+ } else {
+ nearest.setSampleRate(8000);
+ nearest.setChannelCount(1);
+ nearest.setSampleType(QAudioFormat::UnSignedInt);
+ nearest.setSampleSize(8);
+ nearest.setCodec(QLatin1String("audio/pcm"));
+ if(!testSettings(nearest)) {
+ nearest.setChannelCount(2);
+ nearest.setSampleSize(16);
+ nearest.setSampleType(QAudioFormat::SignedInt);
+ }
+ }
+ return nearest;
+}
+
+QString QAlsaAudioDeviceInfo::deviceName() const
+{
+ return device;
+}
+
+QStringList QAlsaAudioDeviceInfo::supportedCodecs()
+{
+ updateLists();
+ return codecz;
+}
+
+QList<int> QAlsaAudioDeviceInfo::supportedSampleRates()
+{
+ updateLists();
+ return sampleRatez;
+}
+
+QList<int> QAlsaAudioDeviceInfo::supportedChannelCounts()
+{
+ updateLists();
+ return channelz;
+}
+
+QList<int> QAlsaAudioDeviceInfo::supportedSampleSizes()
+{
+ updateLists();
+ return sizez;
+}
+
+QList<QAudioFormat::Endian> QAlsaAudioDeviceInfo::supportedByteOrders()
+{
+ updateLists();
+ return byteOrderz;
+}
+
+QList<QAudioFormat::SampleType> QAlsaAudioDeviceInfo::supportedSampleTypes()
+{
+ updateLists();
+ return typez;
+}
+
+QByteArray QAlsaAudioDeviceInfo::defaultDevice(QAudio::Mode mode)
+{
+ const auto &devices = availableDevices(mode);
+ if (devices.size() == 0)
+ return QByteArray();
+
+ return devices.first();
+}
+
+bool QAlsaAudioDeviceInfo::open()
+{
+ int err = 0;
+ QString dev;
+
+ if (!availableDevices(mode).contains(device.toLocal8Bit()))
+ return false;
+
+#if SND_LIB_VERSION < 0x1000e // 1.0.14
+ if (device.compare(QLatin1String("default")) != 0)
+ dev = deviceFromCardName(device);
+ else
+#endif
+ dev = device;
+
+ if(mode == QAudio::AudioOutput) {
+ err=snd_pcm_open( &handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_PLAYBACK,0);
+ } else {
+ err=snd_pcm_open( &handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_CAPTURE,0);
+ }
+ if(err < 0) {
+ handle = 0;
+ return false;
+ }
+ return true;
+}
+
+void QAlsaAudioDeviceInfo::close()
+{
+ if(handle)
+ snd_pcm_close(handle);
+ handle = 0;
+}
+
+bool QAlsaAudioDeviceInfo::testSettings(const QAudioFormat& format) const
+{
+ // Set nearest to closest settings that do work.
+ // See if what is in settings will work (return value).
+ int err = -1;
+ snd_pcm_t* pcmHandle;
+ snd_pcm_hw_params_t *params;
+ QString dev;
+
+#if SND_LIB_VERSION < 0x1000e // 1.0.14
+ if (device.compare(QLatin1String("default")) != 0)
+ dev = deviceFromCardName(device);
+ else
+#endif
+ dev = device;
+
+ snd_pcm_stream_t stream = mode == QAudio::AudioOutput
+ ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE;
+
+ if (snd_pcm_open(&pcmHandle, dev.toLocal8Bit().constData(), stream, 0) < 0)
+ return false;
+
+ snd_pcm_nonblock(pcmHandle, 0);
+ snd_pcm_hw_params_alloca(&params);
+ snd_pcm_hw_params_any(pcmHandle, params);
+
+ // set the values!
+ snd_pcm_hw_params_set_channels(pcmHandle, params, format.channelCount());
+ snd_pcm_hw_params_set_rate(pcmHandle, params, format.sampleRate(), 0);
+
+ snd_pcm_format_t pcmFormat = SND_PCM_FORMAT_UNKNOWN;
+ switch (format.sampleSize()) {
+ case 8:
+ if (format.sampleType() == QAudioFormat::SignedInt)
+ pcmFormat = SND_PCM_FORMAT_S8;
+ else if (format.sampleType() == QAudioFormat::UnSignedInt)
+ pcmFormat = SND_PCM_FORMAT_U8;
+ break;
+ case 16:
+ if (format.sampleType() == QAudioFormat::SignedInt) {
+ pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
+ ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_S16_BE;
+ } else if (format.sampleType() == QAudioFormat::UnSignedInt) {
+ pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
+ ? SND_PCM_FORMAT_U16_LE : SND_PCM_FORMAT_U16_BE;
+ }
+ break;
+ case 32:
+ if (format.sampleType() == QAudioFormat::SignedInt) {
+ pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
+ ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_S32_BE;
+ } else if (format.sampleType() == QAudioFormat::UnSignedInt) {
+ pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
+ ? SND_PCM_FORMAT_U32_LE : SND_PCM_FORMAT_U32_BE;
+ } else if (format.sampleType() == QAudioFormat::Float) {
+ pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
+ ? SND_PCM_FORMAT_FLOAT_LE : SND_PCM_FORMAT_FLOAT_BE;
+ }
+ }
+
+ if (pcmFormat != SND_PCM_FORMAT_UNKNOWN)
+ err = snd_pcm_hw_params_set_format(pcmHandle, params, pcmFormat);
+
+ // For now, just accept only audio/pcm codec
+ if (!format.codec().startsWith(QLatin1String("audio/pcm")))
+ err = -1;
+
+ if (err >= 0 && format.channelCount() != -1) {
+ err = snd_pcm_hw_params_test_channels(pcmHandle, params, format.channelCount());
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_channels(pcmHandle, params, format.channelCount());
+ }
+
+ if (err >= 0 && format.sampleRate() != -1) {
+ err = snd_pcm_hw_params_test_rate(pcmHandle, params, format.sampleRate(), 0);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_rate(pcmHandle, params, format.sampleRate(), 0);
+ }
+
+ if (err >= 0 && pcmFormat != SND_PCM_FORMAT_UNKNOWN)
+ err = snd_pcm_hw_params_set_format(pcmHandle, params, pcmFormat);
+
+ if (err >= 0)
+ err = snd_pcm_hw_params(pcmHandle, params);
+
+ snd_pcm_close(pcmHandle);
+
+ return (err == 0);
+}
+
+void QAlsaAudioDeviceInfo::updateLists()
+{
+ // redo all lists based on current settings
+ sampleRatez.clear();
+ channelz.clear();
+ sizez.clear();
+ byteOrderz.clear();
+ typez.clear();
+ codecz.clear();
+
+ if(!handle)
+ open();
+
+ if(!handle)
+ return;
+
+ for(int i=0; i<(int)MAX_SAMPLE_RATES; i++) {
+ //if(snd_pcm_hw_params_test_rate(handle, params, SAMPLE_RATES[i], dir) == 0)
+ sampleRatez.append(SAMPLE_RATES[i]);
+ }
+ channelz.append(1);
+ channelz.append(2);
+ if (surround40) channelz.append(4);
+ if (surround51) channelz.append(6);
+ if (surround71) channelz.append(8);
+ sizez.append(8);
+ sizez.append(16);
+ sizez.append(32);
+ byteOrderz.append(QAudioFormat::LittleEndian);
+ byteOrderz.append(QAudioFormat::BigEndian);
+ typez.append(QAudioFormat::SignedInt);
+ typez.append(QAudioFormat::UnSignedInt);
+ typez.append(QAudioFormat::Float);
+ codecz.append(QLatin1String("audio/pcm"));
+ close();
+}
+
+QList<QByteArray> QAlsaAudioDeviceInfo::availableDevices(QAudio::Mode mode)
+{
+ QList<QByteArray> devices;
+ bool hasDefault = false;
+
+#if SND_LIB_VERSION >= 0x1000e // 1.0.14
+ QByteArray filter;
+
+ // Create a list of all current audio devices that support mode
+ void **hints, **n;
+ char *name, *descr, *io;
+
+ if(snd_device_name_hint(-1, "pcm", &hints) < 0) {
+ qWarning() << "no alsa devices available";
+ return devices;
+ }
+ n = hints;
+
+ if(mode == QAudio::AudioInput) {
+ filter = "Input";
+ } else {
+ filter = "Output";
+ }
+
+ while (*n != NULL) {
+ name = snd_device_name_get_hint(*n, "NAME");
+ if (name != 0 && qstrcmp(name, "null") != 0) {
+ descr = snd_device_name_get_hint(*n, "DESC");
+ io = snd_device_name_get_hint(*n, "IOID");
+
+ if ((descr != NULL) && ((io == NULL) || (io == filter))) {
+ devices.append(name);
+ if (strcmp(name, "default") == 0)
+ hasDefault = true;
+ }
+
+ free(descr);
+ free(io);
+ }
+ free(name);
+ ++n;
+ }
+ snd_device_name_free_hint(hints);
+#else
+ int idx = 0;
+ char* name;
+
+ while(snd_card_get_name(idx,&name) == 0) {
+ devices.append(name);
+ if (strcmp(name, "default") == 0)
+ hasDefault = true;
+ idx++;
+ }
+#endif
+
+ if (!hasDefault && devices.size() > 0)
+ devices.prepend("default");
+
+ return devices;
+}
+
+void QAlsaAudioDeviceInfo::checkSurround()
+{
+ surround40 = false;
+ surround51 = false;
+ surround71 = false;
+
+ void **hints, **n;
+ char *name, *descr, *io;
+
+ if(snd_device_name_hint(-1, "pcm", &hints) < 0)
+ return;
+
+ n = hints;
+
+ while (*n != NULL) {
+ name = snd_device_name_get_hint(*n, "NAME");
+ descr = snd_device_name_get_hint(*n, "DESC");
+ io = snd_device_name_get_hint(*n, "IOID");
+ if((name != NULL) && (descr != NULL)) {
+ QString deviceName = QLatin1String(name);
+ if (mode == QAudio::AudioOutput) {
+ if(deviceName.contains(QLatin1String("surround40")))
+ surround40 = true;
+ if(deviceName.contains(QLatin1String("surround51")))
+ surround51 = true;
+ if(deviceName.contains(QLatin1String("surround71")))
+ surround71 = true;
+ }
+ }
+ if(name != NULL)
+ free(name);
+ if(descr != NULL)
+ free(descr);
+ if(io != NULL)
+ free(io);
+ ++n;
+ }
+ snd_device_name_free_hint(hints);
+}
+
+QString QAlsaAudioDeviceInfo::deviceFromCardName(const QString &card)
+{
+ int idx = 0;
+ char *name;
+
+ QStringView shortName = QStringView{card}.mid(card.indexOf(QLatin1String("="), 0) + 1);
+
+ while (snd_card_get_name(idx, &name) == 0) {
+ if (shortName.compare(QLatin1String(name)) == 0)
+ break;
+ idx++;
+ }
+
+ return QString(QLatin1String("hw:%1,0")).arg(idx);
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/alsa/qalsaaudiodeviceinfo_p.h b/src/multimedia/audio/alsa/qalsaaudiodeviceinfo_p.h
new file mode 100644
index 000000000..cdf08bfab
--- /dev/null
+++ b/src/multimedia/audio/alsa/qalsaaudiodeviceinfo_p.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+
+#ifndef QALSAAUDIODEVICEINFO_H
+#define QALSAAUDIODEVICEINFO_H
+
+#include <alsa/asoundlib.h>
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qdebug.h>
+
+#include <QtMultimedia/qaudio.h>
+#include <QtMultimedia/qaudiodeviceinfo.h>
+#include <QtMultimedia/qaudiosystem.h>
+
+QT_BEGIN_NAMESPACE
+
+
+const unsigned int MAX_SAMPLE_RATES = 5;
+const unsigned int SAMPLE_RATES[] =
+ { 8000, 11025, 22050, 44100, 48000 };
+
+class QAlsaAudioDeviceInfo : public QAbstractAudioDeviceInfo
+{
+ Q_OBJECT
+public:
+ QAlsaAudioDeviceInfo(const QByteArray &dev,QAudio::Mode mode);
+ ~QAlsaAudioDeviceInfo();
+
+ bool testSettings(const QAudioFormat& format) const;
+ void updateLists();
+ QAudioFormat preferredFormat() const override;
+ bool isFormatSupported(const QAudioFormat& format) const override;
+ QString deviceName() const override;
+ QStringList supportedCodecs() override;
+ QList<int> supportedSampleRates() override;
+ QList<int> supportedChannelCounts() override;
+ QList<int> supportedSampleSizes() override;
+ QList<QAudioFormat::Endian> supportedByteOrders() override;
+ QList<QAudioFormat::SampleType> supportedSampleTypes() override;
+ static QByteArray defaultDevice(QAudio::Mode mode);
+ static QList<QByteArray> availableDevices(QAudio::Mode);
+ static QString deviceFromCardName(const QString &card);
+
+private:
+ bool open();
+ void close();
+
+ void checkSurround();
+ bool surround40;
+ bool surround51;
+ bool surround71;
+
+ QString device;
+ QAudio::Mode mode;
+ QAudioFormat nearest;
+ QList<int> sampleRatez;
+ QList<int> channelz;
+ QList<int> sizez;
+ QList<QAudioFormat::Endian> byteOrderz;
+ QStringList codecz;
+ QList<QAudioFormat::SampleType> typez;
+ snd_pcm_t* handle;
+ snd_pcm_hw_params_t *params;
+};
+
+QT_END_NAMESPACE
+
+
+#endif // QALSAAUDIODEVICEINFO_H
diff --git a/src/multimedia/audio/alsa/qalsaaudioinput.cpp b/src/multimedia/audio/alsa/qalsaaudioinput.cpp
new file mode 100644
index 000000000..2e598c5bf
--- /dev/null
+++ b/src/multimedia/audio/alsa/qalsaaudioinput.cpp
@@ -0,0 +1,872 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// INTERNAL USE ONLY: Do NOT use for any other purpose.
+//
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtMultimedia/private/qaudiohelpers_p.h>
+#include "qalsaaudioinput_p.h"
+#include "qalsaaudiodeviceinfo_p.h"
+
+QT_BEGIN_NAMESPACE
+
+//#define DEBUG_AUDIO 1
+
+QAlsaAudioInput::QAlsaAudioInput(const QByteArray &device)
+{
+ bytesAvailable = 0;
+ handle = 0;
+ access = SND_PCM_ACCESS_RW_INTERLEAVED;
+ pcmformat = SND_PCM_FORMAT_S16;
+ buffer_size = 0;
+ period_size = 0;
+ buffer_time = 100000;
+ period_time = 20000;
+ totalTimeValue = 0;
+ intervalTime = 1000;
+ errorState = QAudio::NoError;
+ deviceState = QAudio::StoppedState;
+ audioSource = 0;
+ pullMode = true;
+ resuming = false;
+
+ m_volume = 1.0f;
+
+ m_device = device;
+
+ timer = new QTimer(this);
+ connect(timer,SIGNAL(timeout()),SLOT(userFeed()));
+}
+
+QAlsaAudioInput::~QAlsaAudioInput()
+{
+ close();
+ disconnect(timer, SIGNAL(timeout()));
+ QCoreApplication::processEvents();
+ delete timer;
+}
+
+void QAlsaAudioInput::setVolume(qreal vol)
+{
+ m_volume = vol;
+}
+
+qreal QAlsaAudioInput::volume() const
+{
+ return m_volume;
+}
+
+QAudio::Error QAlsaAudioInput::error() const
+{
+ return errorState;
+}
+
+QAudio::State QAlsaAudioInput::state() const
+{
+ return deviceState;
+}
+
+void QAlsaAudioInput::setFormat(const QAudioFormat& fmt)
+{
+ if (deviceState == QAudio::StoppedState)
+ settings = fmt;
+}
+
+QAudioFormat QAlsaAudioInput::format() const
+{
+ return settings;
+}
+
+int QAlsaAudioInput::xrun_recovery(int err)
+{
+ int count = 0;
+ bool reset = false;
+
+ // ESTRPIPE is not available in all OSes where ALSA is available
+ int estrpipe = EIO;
+#ifdef ESTRPIPE
+ estrpipe = ESTRPIPE;
+#endif
+
+ if(err == -EPIPE) {
+ errorState = QAudio::UnderrunError;
+ err = snd_pcm_prepare(handle);
+ if(err < 0)
+ reset = true;
+ else {
+ bytesAvailable = checkBytesReady();
+ if (bytesAvailable <= 0)
+ reset = true;
+ }
+ } else if ((err == -estrpipe)||(err == -EIO)) {
+ errorState = QAudio::IOError;
+ while((err = snd_pcm_resume(handle)) == -EAGAIN){
+ usleep(100);
+ count++;
+ if(count > 5) {
+ reset = true;
+ break;
+ }
+ }
+ if(err < 0) {
+ err = snd_pcm_prepare(handle);
+ if(err < 0)
+ reset = true;
+ }
+ }
+ if(reset) {
+ close();
+ open();
+ snd_pcm_prepare(handle);
+ return 0;
+ }
+ return err;
+}
+
+int QAlsaAudioInput::setFormat()
+{
+ snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
+
+ if(settings.sampleSize() == 8) {
+ format = SND_PCM_FORMAT_U8;
+ } else if(settings.sampleSize() == 16) {
+ if(settings.sampleType() == QAudioFormat::SignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ format = SND_PCM_FORMAT_S16_LE;
+ else
+ format = SND_PCM_FORMAT_S16_BE;
+ } else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ format = SND_PCM_FORMAT_U16_LE;
+ else
+ format = SND_PCM_FORMAT_U16_BE;
+ }
+ } else if(settings.sampleSize() == 24) {
+ if(settings.sampleType() == QAudioFormat::SignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ format = SND_PCM_FORMAT_S24_LE;
+ else
+ format = SND_PCM_FORMAT_S24_BE;
+ } else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ format = SND_PCM_FORMAT_U24_LE;
+ else
+ format = SND_PCM_FORMAT_U24_BE;
+ }
+ } else if(settings.sampleSize() == 32) {
+ if(settings.sampleType() == QAudioFormat::SignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ format = SND_PCM_FORMAT_S32_LE;
+ else
+ format = SND_PCM_FORMAT_S32_BE;
+ } else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ format = SND_PCM_FORMAT_U32_LE;
+ else
+ format = SND_PCM_FORMAT_U32_BE;
+ } else if(settings.sampleType() == QAudioFormat::Float) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ format = SND_PCM_FORMAT_FLOAT_LE;
+ else
+ format = SND_PCM_FORMAT_FLOAT_BE;
+ }
+ } else if(settings.sampleSize() == 64) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ format = SND_PCM_FORMAT_FLOAT64_LE;
+ else
+ format = SND_PCM_FORMAT_FLOAT64_BE;
+ }
+
+ return format != SND_PCM_FORMAT_UNKNOWN
+ ? snd_pcm_hw_params_set_format( handle, hwparams, format)
+ : -1;
+}
+
+void QAlsaAudioInput::start(QIODevice* device)
+{
+ if(deviceState != QAudio::StoppedState)
+ close();
+
+ if(!pullMode && audioSource)
+ delete audioSource;
+
+ pullMode = true;
+ audioSource = device;
+
+ deviceState = QAudio::ActiveState;
+
+ if( !open() )
+ return;
+
+ emit stateChanged(deviceState);
+}
+
+QIODevice* QAlsaAudioInput::start()
+{
+ if(deviceState != QAudio::StoppedState)
+ close();
+
+ if(!pullMode && audioSource)
+ delete audioSource;
+
+ pullMode = false;
+ audioSource = new AlsaInputPrivate(this);
+ audioSource->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
+
+ deviceState = QAudio::IdleState;
+
+ if( !open() )
+ return 0;
+
+ emit stateChanged(deviceState);
+
+ return audioSource;
+}
+
+void QAlsaAudioInput::stop()
+{
+ if(deviceState == QAudio::StoppedState)
+ return;
+
+ deviceState = QAudio::StoppedState;
+
+ close();
+ emit stateChanged(deviceState);
+}
+
+bool QAlsaAudioInput::open()
+{
+#ifdef DEBUG_AUDIO
+ QTime now(QTime::currentTime());
+ qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
+#endif
+ clockStamp.restart();
+ timeStamp.restart();
+ elapsedTimeOffset = 0;
+
+ int dir;
+ int err = 0;
+ int count=0;
+ unsigned int sampleRate=settings.sampleRate();
+
+ if (!settings.isValid()) {
+ qWarning("QAudioInput: open error, invalid format.");
+ } else if (settings.sampleRate() <= 0) {
+ qWarning("QAudioInput: open error, invalid sample rate (%d).",
+ settings.sampleRate());
+ } else {
+ err = -1;
+ }
+
+ if (err == 0) {
+ errorState = QAudio::OpenError;
+ deviceState = QAudio::StoppedState;
+ emit errorChanged(errorState);
+ return false;
+ }
+
+
+ if (!QAlsaAudioDeviceInfo::availableDevices(QAudio::AudioInput).contains(m_device))
+ return false;
+
+ QString dev;
+#if SND_LIB_VERSION < 0x1000e // 1.0.14
+ if (m_device != "default")
+ dev = QAlsaAudioDeviceInfo::deviceFromCardName(m_device);
+ else
+#endif
+ dev = m_device;
+
+ // Step 1: try and open the device
+ while((count < 5) && (err < 0)) {
+ err=snd_pcm_open(&handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_CAPTURE,0);
+ if(err < 0)
+ count++;
+ }
+ if (( err < 0)||(handle == 0)) {
+ errorState = QAudio::OpenError;
+ deviceState = QAudio::StoppedState;
+ emit stateChanged(deviceState);
+ return false;
+ }
+ snd_pcm_nonblock( handle, 0 );
+
+ // Step 2: Set the desired HW parameters.
+ snd_pcm_hw_params_alloca( &hwparams );
+
+ bool fatal = false;
+ QString errMessage;
+ unsigned int chunks = 8;
+
+ err = snd_pcm_hw_params_any( handle, hwparams );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_any: err = %1").arg(err);
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_access( handle, hwparams, access );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_access: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = setFormat();
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_format: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_channels( handle, hwparams, (unsigned int)settings.channelCount() );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_channels: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_rate_near: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir);
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_buffer_time_near: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir);
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_period_time_near: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir);
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_periods_near: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params(handle, hwparams);
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params: err = %1").arg(err);
+ }
+ }
+ if( err < 0) {
+ qWarning()<<errMessage;
+ errorState = QAudio::OpenError;
+ deviceState = QAudio::StoppedState;
+ emit stateChanged(deviceState);
+ return false;
+ }
+ snd_pcm_hw_params_get_buffer_size(hwparams,&buffer_frames);
+ buffer_size = snd_pcm_frames_to_bytes(handle,buffer_frames);
+ snd_pcm_hw_params_get_period_size(hwparams,&period_frames, &dir);
+ period_size = snd_pcm_frames_to_bytes(handle,period_frames);
+ snd_pcm_hw_params_get_buffer_time(hwparams,&buffer_time, &dir);
+ snd_pcm_hw_params_get_period_time(hwparams,&period_time, &dir);
+
+ // Step 3: Set the desired SW parameters.
+ snd_pcm_sw_params_t *swparams;
+ snd_pcm_sw_params_alloca(&swparams);
+ snd_pcm_sw_params_current(handle, swparams);
+ snd_pcm_sw_params_set_start_threshold(handle,swparams,period_frames);
+ snd_pcm_sw_params_set_stop_threshold(handle,swparams,buffer_frames);
+ snd_pcm_sw_params_set_avail_min(handle, swparams,period_frames);
+ snd_pcm_sw_params(handle, swparams);
+
+ // Step 4: Prepare audio
+ ringBuffer.resize(buffer_size);
+ snd_pcm_prepare( handle );
+ snd_pcm_start(handle);
+
+ // Step 5: Setup timer
+ bytesAvailable = checkBytesReady();
+
+ if(pullMode)
+ connect(audioSource,SIGNAL(readyRead()),this,SLOT(userFeed()));
+
+ // Step 6: Start audio processing
+ chunks = buffer_size/period_size;
+ timer->start(period_time*chunks/2000);
+
+ errorState = QAudio::NoError;
+
+ totalTimeValue = 0;
+
+ return true;
+}
+
+void QAlsaAudioInput::close()
+{
+ timer->stop();
+
+ if ( handle ) {
+ snd_pcm_drop( handle );
+ snd_pcm_close( handle );
+ handle = 0;
+ }
+}
+
+int QAlsaAudioInput::checkBytesReady()
+{
+ if(resuming)
+ bytesAvailable = period_size;
+ else if(deviceState != QAudio::ActiveState
+ && deviceState != QAudio::IdleState)
+ bytesAvailable = 0;
+ else {
+ int frames = snd_pcm_avail_update(handle);
+ if (frames < 0) {
+ bytesAvailable = frames;
+ } else {
+ if((int)frames > (int)buffer_frames)
+ frames = buffer_frames;
+ bytesAvailable = snd_pcm_frames_to_bytes(handle, frames);
+ }
+ }
+ return bytesAvailable;
+}
+
+int QAlsaAudioInput::bytesReady() const
+{
+ return qMax(bytesAvailable, 0);
+}
+
+qint64 QAlsaAudioInput::read(char* data, qint64 len)
+{
+ // Read in some audio data and write it to QIODevice, pull mode
+ if ( !handle )
+ return 0;
+
+ int bytesRead = 0;
+ int bytesInRingbufferBeforeRead = ringBuffer.bytesOfDataInBuffer();
+
+ if (ringBuffer.bytesOfDataInBuffer() < len) {
+
+ // bytesAvaiable is saved as a side effect of checkBytesReady().
+ int bytesToRead = checkBytesReady();
+
+ if (bytesToRead < 0) {
+ // bytesAvailable as negative is error code, try to recover from it.
+ xrun_recovery(bytesToRead);
+ bytesToRead = checkBytesReady();
+ if (bytesToRead < 0) {
+ // recovery failed must stop and set error.
+ close();
+ errorState = QAudio::IOError;
+ deviceState = QAudio::StoppedState;
+ emit stateChanged(deviceState);
+ return 0;
+ }
+ }
+
+ bytesToRead = qMin<qint64>(len, bytesToRead);
+ bytesToRead = qMin<qint64>(ringBuffer.freeBytes(), bytesToRead);
+ bytesToRead -= bytesToRead % period_size;
+
+ int count=0;
+ int err = 0;
+ QVarLengthArray<char, 4096> buffer(bytesToRead);
+ while(count < 5 && bytesToRead > 0) {
+ int chunks = bytesToRead / period_size;
+ int frames = chunks * period_frames;
+ if (frames > (int)buffer_frames)
+ frames = buffer_frames;
+
+ int readFrames = snd_pcm_readi(handle, buffer.data(), frames);
+ bytesRead = snd_pcm_frames_to_bytes(handle, readFrames);
+ if (m_volume < 1.0f)
+ QAudioHelperInternal::qMultiplySamples(m_volume, settings,
+ buffer.constData(),
+ buffer.data(), bytesRead);
+
+ if (readFrames >= 0) {
+ ringBuffer.write(buffer.data(), bytesRead);
+#ifdef DEBUG_AUDIO
+ qDebug() << QString::fromLatin1("read in bytes = %1 (frames=%2)").arg(bytesRead).arg(readFrames).toLatin1().constData();
+#endif
+ break;
+ } else if((readFrames == -EAGAIN) || (readFrames == -EINTR)) {
+ errorState = QAudio::IOError;
+ err = 0;
+ break;
+ } else {
+ if(readFrames == -EPIPE) {
+ errorState = QAudio::UnderrunError;
+ err = snd_pcm_prepare(handle);
+#ifdef ESTRPIPE
+ } else if(readFrames == -ESTRPIPE) {
+ err = snd_pcm_prepare(handle);
+#endif
+ }
+ if(err != 0) break;
+ }
+ count++;
+ }
+
+ }
+
+ bytesRead += bytesInRingbufferBeforeRead;
+
+ if (bytesRead > 0) {
+ // got some send it onward
+#ifdef DEBUG_AUDIO
+ qDebug() << "frames to write to QIODevice = " <<
+ snd_pcm_bytes_to_frames( handle, (int)bytesRead ) << " (" << bytesRead << ") bytes";
+#endif
+ if (deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
+ return 0;
+
+ if (pullMode) {
+ qint64 l = 0;
+ qint64 bytesWritten = 0;
+ while (ringBuffer.bytesOfDataInBuffer() > 0) {
+ l = audioSource->write(ringBuffer.availableData(), ringBuffer.availableDataBlockSize());
+ if (l > 0) {
+ ringBuffer.readBytes(l);
+ bytesWritten += l;
+ } else {
+ break;
+ }
+ }
+
+ if (l < 0) {
+ close();
+ errorState = QAudio::IOError;
+ deviceState = QAudio::StoppedState;
+ emit stateChanged(deviceState);
+ } else if (l == 0 && bytesWritten == 0) {
+ if (deviceState != QAudio::IdleState) {
+ errorState = QAudio::NoError;
+ deviceState = QAudio::IdleState;
+ emit stateChanged(deviceState);
+ }
+ } else {
+ bytesAvailable -= bytesWritten;
+ totalTimeValue += bytesWritten;
+ resuming = false;
+ if (deviceState != QAudio::ActiveState) {
+ errorState = QAudio::NoError;
+ deviceState = QAudio::ActiveState;
+ emit stateChanged(deviceState);
+ }
+ }
+
+ return bytesWritten;
+ } else {
+ while (ringBuffer.bytesOfDataInBuffer() > 0) {
+ int size = ringBuffer.availableDataBlockSize();
+ memcpy(data, ringBuffer.availableData(), size);
+ data += size;
+ ringBuffer.readBytes(size);
+ }
+
+ bytesAvailable -= bytesRead;
+ totalTimeValue += bytesRead;
+ resuming = false;
+ if (deviceState != QAudio::ActiveState) {
+ errorState = QAudio::NoError;
+ deviceState = QAudio::ActiveState;
+ emit stateChanged(deviceState);
+ }
+
+ return bytesRead;
+ }
+ }
+
+ return 0;
+}
+
+void QAlsaAudioInput::resume()
+{
+ if(deviceState == QAudio::SuspendedState) {
+ int err = 0;
+
+ if(handle) {
+ err = snd_pcm_prepare( handle );
+ if(err < 0)
+ xrun_recovery(err);
+
+ err = snd_pcm_start(handle);
+ if(err < 0)
+ xrun_recovery(err);
+
+ bytesAvailable = buffer_size;
+ }
+ resuming = true;
+ deviceState = QAudio::ActiveState;
+ int chunks = buffer_size/period_size;
+ timer->start(period_time*chunks/2000);
+ emit stateChanged(deviceState);
+ }
+}
+
+void QAlsaAudioInput::setBufferSize(int value)
+{
+ buffer_size = value;
+}
+
+int QAlsaAudioInput::bufferSize() const
+{
+ return buffer_size;
+}
+
+int QAlsaAudioInput::periodSize() const
+{
+ return period_size;
+}
+
+void QAlsaAudioInput::setNotifyInterval(int ms)
+{
+ intervalTime = qMax(0, ms);
+}
+
+int QAlsaAudioInput::notifyInterval() const
+{
+ return intervalTime;
+}
+
+qint64 QAlsaAudioInput::processedUSecs() const
+{
+ qint64 result = qint64(1000000) * totalTimeValue /
+ (settings.channelCount()*(settings.sampleSize()/8)) /
+ settings.sampleRate();
+
+ return result;
+}
+
+void QAlsaAudioInput::suspend()
+{
+ if(deviceState == QAudio::ActiveState||resuming) {
+ snd_pcm_drain(handle);
+ timer->stop();
+ deviceState = QAudio::SuspendedState;
+ emit stateChanged(deviceState);
+ }
+}
+
+void QAlsaAudioInput::userFeed()
+{
+ if(deviceState == QAudio::StoppedState || deviceState == QAudio::SuspendedState)
+ return;
+#ifdef DEBUG_AUDIO
+ QTime now(QTime::currentTime());
+ qDebug()<<now.second()<<"s "<<now.msec()<<"ms :userFeed() IN";
+#endif
+ deviceReady();
+}
+
+bool QAlsaAudioInput::deviceReady()
+{
+ if(pullMode) {
+ // reads some audio data and writes it to QIODevice
+ read(0, buffer_size);
+ } else {
+ // emits readyRead() so user will call read() on QIODevice to get some audio data
+ AlsaInputPrivate* a = qobject_cast<AlsaInputPrivate*>(audioSource);
+ a->trigger();
+ }
+ bytesAvailable = checkBytesReady();
+
+ if(deviceState != QAudio::ActiveState)
+ return true;
+
+ if (bytesAvailable < 0) {
+ // bytesAvailable as negative is error code, try to recover from it.
+ xrun_recovery(bytesAvailable);
+ bytesAvailable = checkBytesReady();
+ if (bytesAvailable < 0) {
+ // recovery failed must stop and set error.
+ close();
+ errorState = QAudio::IOError;
+ deviceState = QAudio::StoppedState;
+ emit stateChanged(deviceState);
+ return 0;
+ }
+ }
+
+ if(intervalTime && (timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
+ emit notify();
+ elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
+ timeStamp.restart();
+ }
+ return true;
+}
+
+qint64 QAlsaAudioInput::elapsedUSecs() const
+{
+ if (deviceState == QAudio::StoppedState)
+ return 0;
+
+ return clockStamp.elapsed() * qint64(1000);
+}
+
+void QAlsaAudioInput::reset()
+{
+ if(handle)
+ snd_pcm_reset(handle);
+ stop();
+ bytesAvailable = 0;
+}
+
+void QAlsaAudioInput::drain()
+{
+ if(handle)
+ snd_pcm_drain(handle);
+}
+
+AlsaInputPrivate::AlsaInputPrivate(QAlsaAudioInput* audio)
+{
+ audioDevice = qobject_cast<QAlsaAudioInput*>(audio);
+}
+
+AlsaInputPrivate::~AlsaInputPrivate()
+{
+}
+
+qint64 AlsaInputPrivate::readData( char* data, qint64 len)
+{
+ return audioDevice->read(data,len);
+}
+
+qint64 AlsaInputPrivate::writeData(const char* data, qint64 len)
+{
+ Q_UNUSED(data);
+ Q_UNUSED(len);
+ return 0;
+}
+
+void AlsaInputPrivate::trigger()
+{
+ emit readyRead();
+}
+
+RingBuffer::RingBuffer() :
+ m_head(0),
+ m_tail(0)
+{
+}
+
+void RingBuffer::resize(int size)
+{
+ m_data.resize(size);
+}
+
+int RingBuffer::bytesOfDataInBuffer() const
+{
+ if (m_head < m_tail)
+ return m_tail - m_head;
+ else if (m_tail < m_head)
+ return m_data.size() + m_tail - m_head;
+ else
+ return 0;
+}
+
+int RingBuffer::freeBytes() const
+{
+ if (m_head > m_tail)
+ return m_head - m_tail - 1;
+ else if (m_tail > m_head)
+ return m_data.size() - m_tail + m_head - 1;
+ else
+ return m_data.size() - 1;
+}
+
+const char *RingBuffer::availableData() const
+{
+ return (m_data.constData() + m_head);
+}
+
+int RingBuffer::availableDataBlockSize() const
+{
+ if (m_head > m_tail)
+ return m_data.size() - m_head;
+ else if (m_tail > m_head)
+ return m_tail - m_head;
+ else
+ return 0;
+}
+
+void RingBuffer::readBytes(int bytes)
+{
+ m_head = (m_head + bytes) % m_data.size();
+}
+
+void RingBuffer::write(char *data, int len)
+{
+ if (m_tail + len < m_data.size()) {
+ memcpy(m_data.data() + m_tail, data, len);
+ m_tail += len;
+ } else {
+ int bytesUntilEnd = m_data.size() - m_tail;
+ memcpy(m_data.data() + m_tail, data, bytesUntilEnd);
+ if (len - bytesUntilEnd > 0)
+ memcpy(m_data.data(), data + bytesUntilEnd, len - bytesUntilEnd);
+ m_tail = len - bytesUntilEnd;
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qalsaaudioinput_p.cpp"
diff --git a/src/multimedia/audio/alsa/qalsaaudioinput_p.h b/src/multimedia/audio/alsa/qalsaaudioinput_p.h
new file mode 100644
index 000000000..62e1be039
--- /dev/null
+++ b/src/multimedia/audio/alsa/qalsaaudioinput_p.h
@@ -0,0 +1,185 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+
+#ifndef QAUDIOINPUTALSA_H
+#define QAUDIOINPUTALSA_H
+
+#include <alsa/asoundlib.h>
+
+#include <QtCore/qfile.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qiodevice.h>
+
+#include <QtMultimedia/qaudio.h>
+#include <QtMultimedia/qaudiodeviceinfo.h>
+#include <QtMultimedia/qaudiosystem.h>
+
+QT_BEGIN_NAMESPACE
+
+
+class AlsaInputPrivate;
+
+class RingBuffer
+{
+public:
+ RingBuffer();
+
+ void resize(int size);
+
+ int bytesOfDataInBuffer() const;
+ int freeBytes() const;
+
+ const char *availableData() const;
+ int availableDataBlockSize() const;
+ void readBytes(int bytes);
+
+ void write(char *data, int len);
+
+private:
+ int m_head;
+ int m_tail;
+
+ QByteArray m_data;
+};
+
+class QAlsaAudioInput : public QAbstractAudioInput
+{
+ Q_OBJECT
+public:
+ QAlsaAudioInput(const QByteArray &device);
+ ~QAlsaAudioInput();
+
+ qint64 read(char* data, qint64 len);
+
+ void start(QIODevice* device) override;
+ QIODevice* start() override;
+ void stop() override;
+ void reset() override;
+ void suspend() override;
+ void resume() override;
+ int bytesReady() const override;
+ int periodSize() const override;
+ void setBufferSize(int value) override;
+ int bufferSize() const override;
+ void setNotifyInterval(int milliSeconds) override;
+ int notifyInterval() const override;
+ qint64 processedUSecs() const override;
+ qint64 elapsedUSecs() const override;
+ QAudio::Error error() const override;
+ QAudio::State state() const override;
+ void setFormat(const QAudioFormat& fmt) override;
+ QAudioFormat format() const override;
+ void setVolume(qreal) override;
+ qreal volume() const override;
+ bool resuming;
+ snd_pcm_t* handle;
+ qint64 totalTimeValue;
+ QIODevice* audioSource;
+ QAudioFormat settings;
+ QAudio::Error errorState;
+ QAudio::State deviceState;
+
+private slots:
+ void userFeed();
+ bool deviceReady();
+
+private:
+ int checkBytesReady();
+ int xrun_recovery(int err);
+ int setFormat();
+ bool open();
+ void close();
+ void drain();
+
+ QTimer* timer;
+ QElapsedTimer timeStamp;
+ QElapsedTimer clockStamp;
+ qint64 elapsedTimeOffset;
+ int intervalTime;
+ RingBuffer ringBuffer;
+ int bytesAvailable;
+ QByteArray m_device;
+ bool pullMode;
+ int buffer_size;
+ int period_size;
+ unsigned int buffer_time;
+ unsigned int period_time;
+ snd_pcm_uframes_t buffer_frames;
+ snd_pcm_uframes_t period_frames;
+ snd_pcm_access_t access;
+ snd_pcm_format_t pcmformat;
+ snd_pcm_hw_params_t *hwparams;
+ qreal m_volume;
+};
+
+class AlsaInputPrivate : public QIODevice
+{
+ Q_OBJECT
+public:
+ AlsaInputPrivate(QAlsaAudioInput* audio);
+ ~AlsaInputPrivate();
+
+ qint64 readData( char* data, qint64 len) override;
+ qint64 writeData(const char* data, qint64 len) override;
+
+ void trigger();
+private:
+ QAlsaAudioInput *audioDevice;
+};
+
+QT_END_NAMESPACE
+
+
+#endif
diff --git a/src/multimedia/audio/alsa/qalsaaudiooutput.cpp b/src/multimedia/audio/alsa/qalsaaudiooutput.cpp
new file mode 100644
index 000000000..a6e80637c
--- /dev/null
+++ b/src/multimedia/audio/alsa/qalsaaudiooutput.cpp
@@ -0,0 +1,813 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// INTERNAL USE ONLY: Do NOT use for any other purpose.
+//
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtMultimedia/private/qaudiohelpers_p.h>
+#include "qalsaaudiooutput_p.h"
+#include "qalsaaudiodeviceinfo_p.h"
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcAlsaOutput, "qt.multimedia.alsa.output")
+//#define DEBUG_AUDIO 1
+
+QAlsaAudioOutput::QAlsaAudioOutput(const QByteArray &device)
+{
+ bytesAvailable = 0;
+ handle = 0;
+ access = SND_PCM_ACCESS_RW_INTERLEAVED;
+ pcmformat = SND_PCM_FORMAT_S16;
+ buffer_frames = 0;
+ period_frames = 0;
+ buffer_size = 0;
+ period_size = 0;
+ buffer_time = 100000;
+ period_time = 20000;
+ totalTimeValue = 0;
+ intervalTime = 1000;
+ audioBuffer = 0;
+ errorState = QAudio::NoError;
+ deviceState = QAudio::StoppedState;
+ audioSource = 0;
+ pullMode = true;
+ resuming = false;
+ opened = false;
+
+ m_volume = 1.0f;
+
+ m_device = device;
+
+ timer = new QTimer(this);
+ connect(timer,SIGNAL(timeout()),SLOT(userFeed()));
+}
+
+QAlsaAudioOutput::~QAlsaAudioOutput()
+{
+ close();
+ disconnect(timer, SIGNAL(timeout()));
+ QCoreApplication::processEvents();
+ delete timer;
+}
+
+void QAlsaAudioOutput::setVolume(qreal vol)
+{
+ m_volume = vol;
+}
+
+qreal QAlsaAudioOutput::volume() const
+{
+ return m_volume;
+}
+
+QAudio::Error QAlsaAudioOutput::error() const
+{
+ return errorState;
+}
+
+QAudio::State QAlsaAudioOutput::state() const
+{
+ return deviceState;
+}
+
+int QAlsaAudioOutput::xrun_recovery(int err)
+{
+ int count = 0;
+ bool reset = false;
+
+ // ESTRPIPE is not available in all OSes where ALSA is available
+ int estrpipe = EIO;
+#ifdef ESTRPIPE
+ estrpipe = ESTRPIPE;
+#endif
+
+ if(err == -EPIPE) {
+ errorState = QAudio::UnderrunError;
+ emit errorChanged(errorState);
+ err = snd_pcm_prepare(handle);
+ if(err < 0)
+ reset = true;
+
+ } else if ((err == -estrpipe)||(err == -EIO)) {
+ errorState = QAudio::IOError;
+ emit errorChanged(errorState);
+ while((err = snd_pcm_resume(handle)) == -EAGAIN){
+ usleep(100);
+ count++;
+ if(count > 5) {
+ reset = true;
+ break;
+ }
+ }
+ if(err < 0) {
+ err = snd_pcm_prepare(handle);
+ if(err < 0)
+ reset = true;
+ }
+ }
+ if(reset) {
+ close();
+ open();
+ snd_pcm_prepare(handle);
+ return 0;
+ }
+ return err;
+}
+
+int QAlsaAudioOutput::setFormat()
+{
+ snd_pcm_format_t pcmformat = SND_PCM_FORMAT_UNKNOWN;
+
+ if(settings.sampleSize() == 8) {
+ pcmformat = SND_PCM_FORMAT_U8;
+
+ } else if(settings.sampleSize() == 16) {
+ if(settings.sampleType() == QAudioFormat::SignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ pcmformat = SND_PCM_FORMAT_S16_LE;
+ else
+ pcmformat = SND_PCM_FORMAT_S16_BE;
+ } else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ pcmformat = SND_PCM_FORMAT_U16_LE;
+ else
+ pcmformat = SND_PCM_FORMAT_U16_BE;
+ }
+ } else if(settings.sampleSize() == 24) {
+ if(settings.sampleType() == QAudioFormat::SignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ pcmformat = SND_PCM_FORMAT_S24_LE;
+ else
+ pcmformat = SND_PCM_FORMAT_S24_BE;
+ } else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ pcmformat = SND_PCM_FORMAT_U24_LE;
+ else
+ pcmformat = SND_PCM_FORMAT_U24_BE;
+ }
+ } else if(settings.sampleSize() == 32) {
+ if(settings.sampleType() == QAudioFormat::SignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ pcmformat = SND_PCM_FORMAT_S32_LE;
+ else
+ pcmformat = SND_PCM_FORMAT_S32_BE;
+ } else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ pcmformat = SND_PCM_FORMAT_U32_LE;
+ else
+ pcmformat = SND_PCM_FORMAT_U32_BE;
+ } else if(settings.sampleType() == QAudioFormat::Float) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ pcmformat = SND_PCM_FORMAT_FLOAT_LE;
+ else
+ pcmformat = SND_PCM_FORMAT_FLOAT_BE;
+ }
+ } else if(settings.sampleSize() == 64) {
+ if(settings.byteOrder() == QAudioFormat::LittleEndian)
+ pcmformat = SND_PCM_FORMAT_FLOAT64_LE;
+ else
+ pcmformat = SND_PCM_FORMAT_FLOAT64_BE;
+ }
+
+ return pcmformat != SND_PCM_FORMAT_UNKNOWN
+ ? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat)
+ : -1;
+}
+
+void QAlsaAudioOutput::start(QIODevice* device)
+{
+ if(deviceState != QAudio::StoppedState)
+ deviceState = QAudio::StoppedState;
+
+ errorState = QAudio::NoError;
+
+ // Handle change of mode
+ if(audioSource && !pullMode) {
+ delete audioSource;
+ audioSource = 0;
+ }
+
+ close();
+
+ pullMode = true;
+ audioSource = device;
+
+ deviceState = QAudio::ActiveState;
+
+ open();
+
+ emit stateChanged(deviceState);
+}
+
+QIODevice* QAlsaAudioOutput::start()
+{
+ if(deviceState != QAudio::StoppedState)
+ deviceState = QAudio::StoppedState;
+
+ errorState = QAudio::NoError;
+
+ // Handle change of mode
+ if(audioSource && !pullMode) {
+ delete audioSource;
+ audioSource = 0;
+ }
+
+ close();
+
+ audioSource = new AlsaOutputPrivate(this);
+ audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
+ pullMode = false;
+
+ deviceState = QAudio::IdleState;
+
+ open();
+
+ emit stateChanged(deviceState);
+
+ return audioSource;
+}
+
+void QAlsaAudioOutput::stop()
+{
+ if(deviceState == QAudio::StoppedState)
+ return;
+ errorState = QAudio::NoError;
+ deviceState = QAudio::StoppedState;
+ close();
+ emit stateChanged(deviceState);
+}
+
+bool QAlsaAudioOutput::open()
+{
+ if(opened)
+ return true;
+
+#ifdef DEBUG_AUDIO
+ QTime now(QTime::currentTime());
+ qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
+#endif
+ timeStamp.restart();
+ elapsedTimeOffset = 0;
+
+ int dir;
+ int err = 0;
+ int count=0;
+ unsigned int sampleRate=settings.sampleRate();
+
+ if (!settings.isValid()) {
+ qWarning("QAudioOutput: open error, invalid format.");
+ } else if (settings.sampleRate() <= 0) {
+ qWarning("QAudioOutput: open error, invalid sample rate (%d).",
+ settings.sampleRate());
+ } else {
+ err = -1;
+ }
+
+ if (err == 0) {
+ errorState = QAudio::OpenError;
+ deviceState = QAudio::StoppedState;
+ emit errorChanged(errorState);
+ return false;
+ }
+
+ QString dev;
+#if SND_LIB_VERSION < 0x1000e // 1.0.14
+ if (m_device != "default")
+ dev = QAlsaAudioDeviceInfo::deviceFromCardName(m_device);
+ else
+#endif
+ dev = m_device;
+
+ // Step 1: try and open the device
+ while((count < 5) && (err < 0)) {
+ err=snd_pcm_open(&handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_PLAYBACK,0);
+ if(err < 0)
+ count++;
+ }
+ if (( err < 0)||(handle == 0)) {
+ errorState = QAudio::OpenError;
+ emit errorChanged(errorState);
+ deviceState = QAudio::StoppedState;
+ return false;
+ }
+ snd_pcm_nonblock( handle, 0 );
+
+ // Step 2: Set the desired HW parameters.
+ snd_pcm_hw_params_alloca( &hwparams );
+
+ bool fatal = false;
+ QString errMessage;
+ unsigned int chunks = 8;
+
+ err = snd_pcm_hw_params_any( handle, hwparams );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_any: err = %1").arg(err);
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_access( handle, hwparams, access );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_access: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = setFormat();
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_format: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_channels( handle, hwparams, (unsigned int)settings.channelCount() );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_channels: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 );
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_rate_near: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ unsigned int maxBufferTime = 0;
+ unsigned int minBufferTime = 0;
+ unsigned int maxPeriodTime = 0;
+ unsigned int minPeriodTime = 0;
+
+ err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &maxBufferTime, &dir);
+ if ( err >= 0)
+ err = snd_pcm_hw_params_get_buffer_time_min(hwparams, &minBufferTime, &dir);
+ if ( err >= 0)
+ err = snd_pcm_hw_params_get_period_time_max(hwparams, &maxPeriodTime, &dir);
+ if ( err >= 0)
+ err = snd_pcm_hw_params_get_period_time_min(hwparams, &minPeriodTime, &dir);
+
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: buffer/period min and max: err = %1").arg(err);
+ } else {
+ static unsigned user_buffer_time = qEnvironmentVariableIntValue("QT_ALSA_OUTPUT_BUFFER_TIME");
+ static unsigned user_period_time = qEnvironmentVariableIntValue("QT_ALSA_OUTPUT_PERIOD_TIME");
+ const bool outOfRange = maxBufferTime < buffer_time || buffer_time < minBufferTime || maxPeriodTime < period_time || minPeriodTime > period_time;
+ if (outOfRange || user_period_time || user_buffer_time) {
+ period_time = user_period_time ? user_period_time : minPeriodTime;
+ if (!user_buffer_time) {
+ chunks = maxBufferTime / period_time;
+ buffer_time = period_time * chunks;
+ } else {
+ buffer_time = user_buffer_time;
+ chunks = buffer_time / period_time;
+ }
+ }
+ qCDebug(lcAlsaOutput) << "buffer time: [" << minBufferTime << "-" << maxBufferTime << "] =" << buffer_time;
+ qCDebug(lcAlsaOutput) << "period time: [" << minPeriodTime << "-" << maxPeriodTime << "] =" << period_time;
+ qCDebug(lcAlsaOutput) << "chunks =" << chunks;
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir);
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_buffer_time_near: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir);
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_period_time_near: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir);
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_periods_near: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = snd_pcm_hw_params(handle, hwparams);
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params: err = %1").arg(err);
+ }
+ }
+ if( err < 0) {
+ qWarning()<<errMessage;
+ errorState = QAudio::OpenError;
+ emit errorChanged(errorState);
+ deviceState = QAudio::StoppedState;
+ return false;
+ }
+ snd_pcm_hw_params_get_buffer_size(hwparams,&buffer_frames);
+ buffer_size = snd_pcm_frames_to_bytes(handle,buffer_frames);
+ snd_pcm_hw_params_get_period_size(hwparams,&period_frames, &dir);
+ period_size = snd_pcm_frames_to_bytes(handle,period_frames);
+ snd_pcm_hw_params_get_buffer_time(hwparams,&buffer_time, &dir);
+ snd_pcm_hw_params_get_period_time(hwparams,&period_time, &dir);
+
+ // Step 3: Set the desired SW parameters.
+ snd_pcm_sw_params_t *swparams;
+ snd_pcm_sw_params_alloca(&swparams);
+ snd_pcm_sw_params_current(handle, swparams);
+ snd_pcm_sw_params_set_start_threshold(handle,swparams,period_frames);
+ snd_pcm_sw_params_set_stop_threshold(handle,swparams,buffer_frames);
+ snd_pcm_sw_params_set_avail_min(handle, swparams,period_frames);
+ snd_pcm_sw_params(handle, swparams);
+
+ // Step 4: Prepare audio
+ if(audioBuffer == 0)
+ audioBuffer = new char[snd_pcm_frames_to_bytes(handle,buffer_frames)];
+ snd_pcm_prepare( handle );
+ snd_pcm_start(handle);
+
+ // Step 5: Setup timer
+ bytesAvailable = bytesFree();
+
+ // Step 6: Start audio processing
+ timer->start(period_time/1000);
+
+ clockStamp.restart();
+ timeStamp.restart();
+ elapsedTimeOffset = 0;
+ errorState = QAudio::NoError;
+ totalTimeValue = 0;
+ opened = true;
+
+ return true;
+}
+
+void QAlsaAudioOutput::close()
+{
+ timer->stop();
+
+ if ( handle ) {
+ snd_pcm_drain( handle );
+ snd_pcm_close( handle );
+ handle = 0;
+ delete [] audioBuffer;
+ audioBuffer=0;
+ }
+ if(!pullMode && audioSource) {
+ delete audioSource;
+ audioSource = 0;
+ }
+ opened = false;
+}
+
+int QAlsaAudioOutput::bytesFree() const
+{
+ if(resuming)
+ return period_size;
+
+ if(deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
+ return 0;
+
+ int frames = snd_pcm_avail_update(handle);
+ if (frames == -EPIPE) {
+ // Try and handle buffer underrun
+ int err = snd_pcm_recover(handle, frames, 0);
+ if (err < 0)
+ return 0;
+ else
+ frames = snd_pcm_avail_update(handle);
+ } else if (frames < 0) {
+ return 0;
+ }
+
+ if ((int)frames > (int)buffer_frames)
+ frames = buffer_frames;
+
+ return snd_pcm_frames_to_bytes(handle, frames);
+}
+
+qint64 QAlsaAudioOutput::write( const char *data, qint64 len )
+{
+ // Write out some audio data
+ if ( !handle )
+ return 0;
+#ifdef DEBUG_AUDIO
+ qDebug()<<"frames to write out = "<<
+ snd_pcm_bytes_to_frames( handle, (int)len )<<" ("<<len<<") bytes";
+#endif
+ int frames, err;
+ int space = bytesFree();
+
+ if (!space)
+ return 0;
+
+ if (len < space)
+ space = len;
+
+ frames = snd_pcm_bytes_to_frames(handle, space);
+
+ if (m_volume < 1.0f) {
+ QVarLengthArray<char, 4096> out(space);
+ QAudioHelperInternal::qMultiplySamples(m_volume, settings, data, out.data(), space);
+ err = snd_pcm_writei(handle, out.constData(), frames);
+ } else {
+ err = snd_pcm_writei(handle, data, frames);
+ }
+
+ if(err > 0) {
+ totalTimeValue += err;
+ resuming = false;
+ errorState = QAudio::NoError;
+ if (deviceState != QAudio::ActiveState) {
+ deviceState = QAudio::ActiveState;
+ emit stateChanged(deviceState);
+ }
+ return snd_pcm_frames_to_bytes( handle, err );
+ } else
+ err = xrun_recovery(err);
+
+ if(err < 0) {
+ close();
+ errorState = QAudio::FatalError;
+ emit errorChanged(errorState);
+ deviceState = QAudio::StoppedState;
+ emit stateChanged(deviceState);
+ }
+ return 0;
+}
+
+int QAlsaAudioOutput::periodSize() const
+{
+ return period_size;
+}
+
+void QAlsaAudioOutput::setBufferSize(int value)
+{
+ if(deviceState == QAudio::StoppedState)
+ buffer_size = value;
+}
+
+int QAlsaAudioOutput::bufferSize() const
+{
+ return buffer_size;
+}
+
+void QAlsaAudioOutput::setNotifyInterval(int ms)
+{
+ intervalTime = qMax(0, ms);
+}
+
+int QAlsaAudioOutput::notifyInterval() const
+{
+ return intervalTime;
+}
+
+qint64 QAlsaAudioOutput::processedUSecs() const
+{
+ return qint64(1000000) * totalTimeValue / settings.sampleRate();
+}
+
+void QAlsaAudioOutput::resume()
+{
+ if(deviceState == QAudio::SuspendedState) {
+ int err = 0;
+
+ if(handle) {
+ err = snd_pcm_prepare( handle );
+ if(err < 0)
+ xrun_recovery(err);
+
+ err = snd_pcm_start(handle);
+ if(err < 0)
+ xrun_recovery(err);
+
+ bytesAvailable = (int)snd_pcm_frames_to_bytes(handle, buffer_frames);
+ }
+ resuming = true;
+
+ deviceState = pullMode ? QAudio::ActiveState : QAudio::IdleState;
+
+ errorState = QAudio::NoError;
+ timer->start(period_time/1000);
+ emit stateChanged(deviceState);
+ }
+}
+
+void QAlsaAudioOutput::setFormat(const QAudioFormat& fmt)
+{
+ if (deviceState == QAudio::StoppedState)
+ settings = fmt;
+}
+
+QAudioFormat QAlsaAudioOutput::format() const
+{
+ return settings;
+}
+
+void QAlsaAudioOutput::suspend()
+{
+ if(deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState || resuming) {
+ snd_pcm_drain(handle);
+ timer->stop();
+ deviceState = QAudio::SuspendedState;
+ errorState = QAudio::NoError;
+ emit stateChanged(deviceState);
+ }
+}
+
+void QAlsaAudioOutput::userFeed()
+{
+ if(deviceState == QAudio::StoppedState || deviceState == QAudio::SuspendedState)
+ return;
+#ifdef DEBUG_AUDIO
+ QTime now(QTime::currentTime());
+ qDebug()<<now.second()<<"s "<<now.msec()<<"ms :userFeed() OUT";
+#endif
+ if(deviceState == QAudio::IdleState)
+ bytesAvailable = bytesFree();
+
+ deviceReady();
+}
+
+bool QAlsaAudioOutput::deviceReady()
+{
+ if(pullMode) {
+ int l = 0;
+ int chunks = bytesAvailable/period_size;
+ if(chunks==0) {
+ bytesAvailable = bytesFree();
+ return false;
+ }
+#ifdef DEBUG_AUDIO
+ qDebug()<<"deviceReady() avail="<<bytesAvailable<<" bytes, period size="<<period_size<<" bytes";
+ qDebug()<<"deviceReady() no. of chunks that can fit ="<<chunks<<", chunks in bytes ="<<period_size*chunks;
+#endif
+ int input = period_frames*chunks;
+ if(input > (int)buffer_frames)
+ input = buffer_frames;
+ l = audioSource->read(audioBuffer,snd_pcm_frames_to_bytes(handle, input));
+
+ // reading can take a while and stream may have been stopped
+ if (!handle)
+ return false;
+
+ if(l > 0) {
+ // Got some data to output
+ if (deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
+ return true;
+ qint64 bytesWritten = write(audioBuffer,l);
+ if (bytesWritten != l)
+ audioSource->seek(audioSource->pos()-(l-bytesWritten));
+ bytesAvailable = bytesFree();
+
+ } else if(l == 0) {
+ // Did not get any data to output
+ bytesAvailable = bytesFree();
+ if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
+ // Underrun
+ if (deviceState != QAudio::IdleState) {
+ errorState = QAudio::UnderrunError;
+ emit errorChanged(errorState);
+ deviceState = QAudio::IdleState;
+ emit stateChanged(deviceState);
+ }
+ }
+
+ } else if(l < 0) {
+ close();
+ deviceState = QAudio::StoppedState;
+ errorState = QAudio::IOError;
+ emit errorChanged(errorState);
+ emit stateChanged(deviceState);
+ }
+ } else {
+ bytesAvailable = bytesFree();
+ if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
+ // Underrun
+ if (deviceState != QAudio::IdleState) {
+ errorState = QAudio::UnderrunError;
+ emit errorChanged(errorState);
+ deviceState = QAudio::IdleState;
+ emit stateChanged(deviceState);
+ }
+ }
+ }
+
+ if(deviceState != QAudio::ActiveState)
+ return true;
+
+ if(intervalTime && (timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
+ emit notify();
+ elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
+ timeStamp.restart();
+ }
+ return true;
+}
+
+qint64 QAlsaAudioOutput::elapsedUSecs() const
+{
+ if (deviceState == QAudio::StoppedState)
+ return 0;
+
+ return clockStamp.elapsed() * qint64(1000);
+}
+
+void QAlsaAudioOutput::reset()
+{
+ if(handle)
+ snd_pcm_reset(handle);
+
+ stop();
+}
+
+AlsaOutputPrivate::AlsaOutputPrivate(QAlsaAudioOutput* audio)
+{
+ audioDevice = qobject_cast<QAlsaAudioOutput*>(audio);
+}
+
+AlsaOutputPrivate::~AlsaOutputPrivate() {}
+
+qint64 AlsaOutputPrivate::readData( char* data, qint64 len)
+{
+ Q_UNUSED(data);
+ Q_UNUSED(len);
+
+ return 0;
+}
+
+qint64 AlsaOutputPrivate::writeData(const char* data, qint64 len)
+{
+ int retry = 0;
+ qint64 written = 0;
+ if((audioDevice->deviceState == QAudio::ActiveState)
+ ||(audioDevice->deviceState == QAudio::IdleState)) {
+ while(written < len) {
+ int chunk = audioDevice->write(data+written,(len-written));
+ if(chunk <= 0)
+ retry++;
+ written+=chunk;
+ if(retry > 10)
+ return written;
+ }
+ }
+ return written;
+
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qalsaaudiooutput_p.cpp"
diff --git a/src/multimedia/audio/alsa/qalsaaudiooutput_p.h b/src/multimedia/audio/alsa/qalsaaudiooutput_p.h
new file mode 100644
index 000000000..72b9c2e4c
--- /dev/null
+++ b/src/multimedia/audio/alsa/qalsaaudiooutput_p.h
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QAUDIOOUTPUTALSA_H
+#define QAUDIOOUTPUTALSA_H
+
+#include <alsa/asoundlib.h>
+
+#include <QtCore/qfile.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qiodevice.h>
+
+#include <QtMultimedia/qaudio.h>
+#include <QtMultimedia/qaudiodeviceinfo.h>
+#include <QtMultimedia/qaudiosystem.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAlsaAudioOutput : public QAbstractAudioOutput
+{
+ friend class AlsaOutputPrivate;
+ Q_OBJECT
+public:
+ QAlsaAudioOutput(const QByteArray &device);
+ ~QAlsaAudioOutput();
+
+ qint64 write( const char *data, qint64 len );
+
+ void start(QIODevice* device) override;
+ QIODevice* start() override;
+ void stop() override;
+ void reset() override;
+ void suspend() override;
+ void resume() override;
+ int bytesFree() const override;
+ int periodSize() const override;
+ void setBufferSize(int value) override;
+ int bufferSize() const override;
+ void setNotifyInterval(int milliSeconds) override;
+ int notifyInterval() const override;
+ qint64 processedUSecs() const override;
+ qint64 elapsedUSecs() const override;
+ QAudio::Error error() const override;
+ QAudio::State state() const override;
+ void setFormat(const QAudioFormat& fmt) override;
+ QAudioFormat format() const override;
+ void setVolume(qreal) override;
+ qreal volume() const override;
+
+
+ QIODevice* audioSource;
+ QAudioFormat settings;
+ QAudio::Error errorState;
+ QAudio::State deviceState;
+
+private slots:
+ void userFeed();
+ bool deviceReady();
+
+signals:
+ void processMore();
+
+private:
+ bool opened;
+ bool pullMode;
+ bool resuming;
+ int buffer_size;
+ int period_size;
+ int intervalTime;
+ qint64 totalTimeValue;
+ unsigned int buffer_time;
+ unsigned int period_time;
+ snd_pcm_uframes_t buffer_frames;
+ snd_pcm_uframes_t period_frames;
+ int xrun_recovery(int err);
+
+ int setFormat();
+ bool open();
+ void close();
+
+ QTimer* timer;
+ QByteArray m_device;
+ int bytesAvailable;
+ QElapsedTimer timeStamp;
+ QElapsedTimer clockStamp;
+ qint64 elapsedTimeOffset;
+ char* audioBuffer;
+ snd_pcm_t* handle;
+ snd_pcm_access_t access;
+ snd_pcm_format_t pcmformat;
+ snd_pcm_hw_params_t *hwparams;
+ qreal m_volume;
+};
+
+class AlsaOutputPrivate : public QIODevice
+{
+ friend class QAlsaAudioOutput;
+ Q_OBJECT
+public:
+ AlsaOutputPrivate(QAlsaAudioOutput* audio);
+ ~AlsaOutputPrivate();
+
+ qint64 readData( char* data, qint64 len) override;
+ qint64 writeData(const char* data, qint64 len) override;
+
+private:
+ QAlsaAudioOutput *audioDevice;
+};
+
+QT_END_NAMESPACE
+
+
+#endif
diff --git a/src/multimedia/audio/alsa/qalsainterface.cpp b/src/multimedia/audio/alsa/qalsainterface.cpp
new file mode 100644
index 000000000..361a6dc6d
--- /dev/null
+++ b/src/multimedia/audio/alsa/qalsainterface.cpp
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "qalsainterface_p.h"
+#include "qalsaaudiodeviceinfo_p.h"
+#include "qalsaaudioinput_p.h"
+#include "qalsaaudiooutput_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QByteArray QAlsaInterface::defaultDevice(QAudio::Mode mode) const
+{
+ return QAlsaAudioDeviceInfo::defaultDevice(mode);
+}
+
+QList<QByteArray> QAlsaInterface::availableDevices(QAudio::Mode mode) const
+{
+ return QAlsaAudioDeviceInfo::availableDevices(mode);
+}
+
+QAbstractAudioInput *QAlsaInterface::createInput(const QByteArray &device)
+{
+ return new QAlsaAudioInput(device);
+}
+
+QAbstractAudioOutput *QAlsaInterface::createOutput(const QByteArray &device)
+{
+ return new QAlsaAudioOutput(device);
+}
+
+QAbstractAudioDeviceInfo *QAlsaInterface::createDeviceInfo(const QByteArray &device, QAudio::Mode mode)
+{
+ return new QAlsaAudioDeviceInfo(device, mode);
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/alsa/qalsainterface_p.h b/src/multimedia/audio/alsa/qalsainterface_p.h
new file mode 100644
index 000000000..05ade0f68
--- /dev/null
+++ b/src/multimedia/audio/alsa/qalsainterface_p.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QALSAPLUGIN_H
+#define QALSAPLUGIN_H
+
+#include <private/qaudiosysteminterface_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAlsaInterface : public QAudioSystemInterface
+{
+public:
+ QByteArray defaultDevice(QAudio::Mode mode) const override;
+ QList<QByteArray> availableDevices(QAudio::Mode mode) const override;
+ QAbstractAudioInput *createInput(const QByteArray &device) override;
+ QAbstractAudioOutput *createOutput(const QByteArray &device) override;
+ QAbstractAudioDeviceInfo *createDeviceInfo(const QByteArray &device, QAudio::Mode mode) override;
+};
+
+QT_END_NAMESPACE
+
+#endif // QALSAPLUGIN_H
diff --git a/src/multimedia/audio/audio.pri b/src/multimedia/audio/audio.pri
index fcdfe3cd7..54da659b2 100644
--- a/src/multimedia/audio/audio.pri
+++ b/src/multimedia/audio/audio.pri
@@ -7,7 +7,6 @@ PUBLIC_HEADERS += \
audio/qaudioinput.h \
audio/qaudiooutput.h \
audio/qaudiodeviceinfo.h \
- audio/qaudiosystemplugin.h \
audio/qaudiosystem.h \
audio/qsoundeffect.h \
audio/qaudioprobe.h \
@@ -18,6 +17,7 @@ PRIVATE_HEADERS += \
audio/qaudiodevicefactory_p.h \
audio/qwavedecoder_p.h \
audio/qsamplecache_p.h \
+ audio/qaudiosysteminterface_p.h \
audio/qaudiohelpers_p.h \
SOURCES += \
@@ -26,7 +26,7 @@ SOURCES += \
audio/qaudiodeviceinfo.cpp \
audio/qaudiooutput.cpp \
audio/qaudioinput.cpp \
- audio/qaudiosystemplugin.cpp \
+ audio/qaudiosysteminterface.cpp \
audio/qaudiosystem.cpp \
audio/qaudiodevicefactory.cpp \
audio/qsoundeffect.cpp \
@@ -45,3 +45,12 @@ qtConfig(pulseaudio) {
PRIVATE_HEADERS += audio/qsoundeffect_qaudio_p.h
SOURCES += audio/qsoundeffect_qaudio_p.cpp
}
+
+#android:SUBDIRS += opensles
+#qnx:SUBDIRS += qnx-audio
+#win32:SUBDIRS += windowsaudio
+#darwin:!watchos:SUBDIRS += coreaudio
+
+qtConfig(pulseaudio): include(pulseaudio/pulseaudio.pri)
+qtConfig(alsa): include(alsa/alsa.pri)
+
diff --git a/src/multimedia/audio/pulseaudio/pulseaudio.pri b/src/multimedia/audio/pulseaudio/pulseaudio.pri
new file mode 100644
index 000000000..59ac66089
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/pulseaudio.pri
@@ -0,0 +1,15 @@
+QMAKE_USE_PRIVATE += pulseaudio
+
+HEADERS += audio/pulseaudio/qaudiointerface_pulse_p.h \
+ audio/pulseaudio/qaudiodeviceinfo_pulse_p.h \
+ audio/pulseaudio/qaudiooutput_pulse_p.h \
+ audio/pulseaudio/qaudioinput_pulse_p.h \
+ audio/pulseaudio/qaudioengine_pulse_p.h \
+ audio/pulseaudio/qpulsehelpers_p.h
+
+SOURCES += audio/pulseaudio/qaudiointerface_pulse.cpp \
+ audio/pulseaudio/qaudiodeviceinfo_pulse.cpp \
+ audio/pulseaudio/qaudiooutput_pulse.cpp \
+ audio/pulseaudio/qaudioinput_pulse.cpp \
+ audio/pulseaudio/qaudioengine_pulse.cpp \
+ audio/pulseaudio/qpulsehelpers.cpp
diff --git a/src/multimedia/audio/pulseaudio/qaudiodeviceinfo_pulse.cpp b/src/multimedia/audio/pulseaudio/qaudiodeviceinfo_pulse.cpp
new file mode 100644
index 000000000..2855a3218
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudiodeviceinfo_pulse.cpp
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "qaudiodeviceinfo_pulse_p.h"
+#include "qaudioengine_pulse_p.h"
+#include "qpulsehelpers_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QPulseAudioDeviceInfo::QPulseAudioDeviceInfo(const QByteArray &device, QAudio::Mode mode)
+ : m_device(device)
+ , m_mode(mode)
+{
+}
+
+bool QPulseAudioDeviceInfo::isFormatSupported(const QAudioFormat &format) const
+{
+ pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(format);
+ if (!pa_sample_spec_valid(&spec))
+ return false;
+
+ return true;
+}
+
+QAudioFormat QPulseAudioDeviceInfo::preferredFormat() const
+{
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ QAudioFormat format = pulseEngine->m_preferredFormats.value(m_device);
+ return format;
+}
+
+QString QPulseAudioDeviceInfo::deviceName() const
+{
+ return m_device;
+}
+
+QStringList QPulseAudioDeviceInfo::supportedCodecs()
+{
+ return QStringList() << "audio/pcm";
+}
+
+QList<int> QPulseAudioDeviceInfo::supportedSampleRates()
+{
+ return QList<int>() << 8000 << 11025 << 22050 << 44100 << 48000;
+}
+
+QList<int> QPulseAudioDeviceInfo::supportedChannelCounts()
+{
+ return QList<int>() << 1 << 2 << 4 << 6 << 8;
+}
+
+QList<int> QPulseAudioDeviceInfo::supportedSampleSizes()
+{
+ return QList<int>() << 8 << 16 << 24 << 32;
+}
+
+QList<QAudioFormat::Endian> QPulseAudioDeviceInfo::supportedByteOrders()
+{
+ return QList<QAudioFormat::Endian>() << QAudioFormat::BigEndian << QAudioFormat::LittleEndian;
+}
+
+QList<QAudioFormat::SampleType> QPulseAudioDeviceInfo::supportedSampleTypes()
+{
+ return QList<QAudioFormat::SampleType>() << QAudioFormat::SignedInt << QAudioFormat::UnSignedInt << QAudioFormat::Float;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/pulseaudio/qaudiodeviceinfo_pulse_p.h b/src/multimedia/audio/pulseaudio/qaudiodeviceinfo_pulse_p.h
new file mode 100644
index 000000000..1cec772c0
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudiodeviceinfo_pulse_p.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QAUDIODEVICEINFOPULSE_H
+#define QAUDIODEVICEINFOPULSE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qlist.h>
+
+#include "qaudio.h"
+#include "qaudiodeviceinfo.h"
+#include "qaudiosystem.h"
+
+QT_BEGIN_NAMESPACE
+
+class QPulseAudioDeviceInfo : public QAbstractAudioDeviceInfo
+{
+ Q_OBJECT
+
+public:
+ QPulseAudioDeviceInfo(const QByteArray &device, QAudio::Mode mode);
+ ~QPulseAudioDeviceInfo() {}
+
+ QAudioFormat preferredFormat() const override;
+ bool isFormatSupported(const QAudioFormat &format) const override;
+ QString deviceName() const override;
+ QStringList supportedCodecs() override;
+ QList<int> supportedSampleRates() override;
+ QList<int> supportedChannelCounts() override;
+ QList<int> supportedSampleSizes() override;
+ QList<QAudioFormat::Endian> supportedByteOrders() override;
+ QList<QAudioFormat::SampleType> supportedSampleTypes() override;
+
+private:
+ QByteArray m_device;
+ QAudio::Mode m_mode;
+};
+
+QT_END_NAMESPACE
+
+#endif
+
diff --git a/src/multimedia/audio/pulseaudio/qaudioengine_pulse.cpp b/src/multimedia/audio/pulseaudio/qaudioengine_pulse.cpp
new file mode 100644
index 000000000..299c20081
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudioengine_pulse.cpp
@@ -0,0 +1,482 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include <QtCore/qdebug.h>
+
+#include <qaudiodeviceinfo.h>
+#include "qaudioengine_pulse_p.h"
+#include "qaudiodeviceinfo_pulse_p.h"
+#include "qaudiooutput_pulse_p.h"
+#include "qpulsehelpers_p.h"
+#include <sys/types.h>
+#include <unistd.h>
+
+QT_BEGIN_NAMESPACE
+
+static void serverInfoCallback(pa_context *context, const pa_server_info *info, void *userdata)
+{
+ if (!info) {
+ qWarning() << QString("Failed to get server information: %s").arg(pa_strerror(pa_context_errno(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
+
+ QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata);
+ pulseEngine->m_serverLock.lockForWrite();
+ pulseEngine->m_defaultSink = info->default_sink_name;
+ pulseEngine->m_defaultSource = info->default_source_name;
+ pulseEngine->m_serverLock.unlock();
+
+ 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);
+
+ if (isLast < 0) {
+ qWarning() << QString("Failed to get sink information: %s").arg(pa_strerror(pa_context_errno(context)));
+ return;
+ }
+
+ if (isLast) {
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+ return;
+ }
+
+ 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
+
+ QAudioFormat format = QPulseAudioInternal::sampleSpecToAudioFormat(info->sample_spec);
+
+ QWriteLocker locker(&pulseEngine->m_sinkLock);
+ pulseEngine->m_preferredFormats.insert(info->name, format);
+ pulseEngine->m_sinks.insert(info->index, info->name);
+}
+
+static void sourceInfoCallback(pa_context *context, const pa_source_info *info, int isLast, void *userdata)
+{
+ Q_UNUSED(context);
+ QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata);
+
+ if (isLast) {
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+ return;
+ }
+
+ 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
+
+ QAudioFormat format = QPulseAudioInternal::sampleSpecToAudioFormat(info->sample_spec);
+
+ QWriteLocker locker(&pulseEngine->m_sourceLock);
+ pulseEngine->m_preferredFormats.insert(info->name, format);
+ pulseEngine->m_sources.insert(info->index, info->name);
+}
+
+static void event_cb(pa_context* context, pa_subscription_event_type_t t, uint32_t index, void* userdata)
+{
+ QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata);
+
+ int type = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
+ int facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
+
+ switch (type) {
+ case PA_SUBSCRIPTION_EVENT_NEW:
+ 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");
+ 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");
+ 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");
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ case PA_SUBSCRIPTION_EVENT_REMOVE:
+ switch (facility) {
+ case PA_SUBSCRIPTION_EVENT_SINK:
+ pulseEngine->m_sinkLock.lockForWrite();
+ pulseEngine->m_preferredFormats.remove(pulseEngine->m_sinks.value(index));
+ pulseEngine->m_sinks.remove(index);
+ pulseEngine->m_sinkLock.unlock();
+ break;
+ case PA_SUBSCRIPTION_EVENT_SOURCE:
+ pulseEngine->m_sourceLock.lockForWrite();
+ pulseEngine->m_preferredFormats.remove(pulseEngine->m_sources.value(index));
+ pulseEngine->m_sources.remove(index);
+ pulseEngine->m_sourceLock.unlock();
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void contextStateCallbackInit(pa_context *context, void *userdata)
+{
+ Q_UNUSED(context);
+#ifdef DEBUG_PULSE
+ qDebug() << QPulseAudioInternal::stateToQString(pa_context_get_state(context));
+#endif
+ QPulseAudioEngine *pulseEngine = reinterpret_cast<QPulseAudioEngine*>(userdata);
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+}
+
+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 (state == PA_CONTEXT_FAILED)
+ QMetaObject::invokeMethod(self, "onContextFailed", Qt::QueuedConnection);
+}
+
+Q_GLOBAL_STATIC(QPulseAudioEngine, pulseEngine);
+
+QPulseAudioEngine::QPulseAudioEngine(QObject *parent)
+ : QObject(parent)
+ , m_mainLoopApi(0)
+ , m_context(0)
+ , m_prepared(false)
+{
+ prepare();
+}
+
+QPulseAudioEngine::~QPulseAudioEngine()
+{
+ if (m_prepared)
+ release();
+}
+
+void QPulseAudioEngine::prepare()
+{
+ bool keepGoing = true;
+ bool ok = true;
+
+ m_mainLoop = pa_threaded_mainloop_new();
+ if (m_mainLoop == 0) {
+ qWarning("PulseAudioService: unable to create pulseaudio mainloop");
+ return;
+ }
+
+ if (pa_threaded_mainloop_start(m_mainLoop) != 0) {
+ qWarning("PulseAudioService: unable to start pulseaudio mainloop");
+ pa_threaded_mainloop_free(m_mainLoop);
+ m_mainLoop = 0;
+ return;
+ }
+
+ m_mainLoopApi = pa_threaded_mainloop_get_api(m_mainLoop);
+
+ lock();
+
+ m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtPulseAudio:%1")).arg(::getpid()).toLatin1().constData());
+
+ if (m_context == 0) {
+ qWarning("PulseAudioService: Unable to create new pulseaudio context");
+ pa_threaded_mainloop_unlock(m_mainLoop);
+ pa_threaded_mainloop_free(m_mainLoop);
+ m_mainLoop = 0;
+ onContextFailed();
+ return;
+ }
+
+ pa_context_set_state_callback(m_context, contextStateCallbackInit, this);
+
+ if (pa_context_connect(m_context, 0, (pa_context_flags_t)0, 0) < 0) {
+ qWarning("PulseAudioService: pa_context_connect() failed");
+ pa_context_unref(m_context);
+ pa_threaded_mainloop_unlock(m_mainLoop);
+ pa_threaded_mainloop_free(m_mainLoop);
+ m_mainLoop = 0;
+ m_context = 0;
+ return;
+ }
+
+ pa_threaded_mainloop_wait(m_mainLoop);
+
+ while (keepGoing) {
+ switch (pa_context_get_state(m_context)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY:
+#ifdef DEBUG_PULSE
+ qDebug("Connection established.");
+#endif
+ keepGoing = false;
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ qCritical("PulseAudioService: Context terminated.");
+ keepGoing = false;
+ ok = false;
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ qCritical() << QString("PulseAudioService: Connection failure: %1").arg(pa_strerror(pa_context_errno(m_context)));
+ keepGoing = false;
+ ok = false;
+ }
+
+ if (keepGoing)
+ pa_threaded_mainloop_wait(m_mainLoop);
+ }
+
+ if (ok) {
+ 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),
+ NULL, NULL);
+ if (op)
+ pa_operation_unref(op);
+ else
+ qWarning("PulseAudioService: failed to subscribe to context notifications");
+ } else {
+ pa_context_unref(m_context);
+ m_context = 0;
+ }
+
+ unlock();
+
+ if (ok) {
+ updateDevices();
+ m_prepared = true;
+ } else {
+ pa_threaded_mainloop_free(m_mainLoop);
+ m_mainLoop = 0;
+ onContextFailed();
+ }
+}
+
+void QPulseAudioEngine::release()
+{
+ if (!m_prepared)
+ return;
+
+ if (m_context) {
+ pa_context_disconnect(m_context);
+ pa_context_unref(m_context);
+ m_context = 0;
+ }
+
+ if (m_mainLoop) {
+ pa_threaded_mainloop_stop(m_mainLoop);
+ pa_threaded_mainloop_free(m_mainLoop);
+ m_mainLoop = 0;
+ }
+
+ m_prepared = false;
+}
+
+void QPulseAudioEngine::updateDevices()
+{
+ lock();
+
+ // Get default input and output devices
+ pa_operation *operation = pa_context_get_server_info(m_context, serverInfoCallback, this);
+ if (operation) {
+ while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(m_mainLoop);
+ pa_operation_unref(operation);
+ } else {
+ qWarning("PulseAudioService: failed to get server info");
+ }
+
+ // Get output devices
+ operation = pa_context_get_sink_info_list(m_context, sinkInfoCallback, this);
+ if (operation) {
+ while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(m_mainLoop);
+ pa_operation_unref(operation);
+ } else {
+ qWarning("PulseAudioService: failed to get sink info");
+ }
+
+ // Get input devices
+ operation = pa_context_get_source_info_list(m_context, sourceInfoCallback, this);
+ if (operation) {
+ while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(m_mainLoop);
+ pa_operation_unref(operation);
+ } else {
+ qWarning("PulseAudioService: failed to get source info");
+ }
+
+ unlock();
+}
+
+void QPulseAudioEngine::onContextFailed()
+{
+ // Give a chance to the connected slots to still use the Pulse main loop before releasing it.
+ emit contextFailed();
+
+ release();
+
+ // Try to reconnect later
+ QTimer::singleShot(3000, this, SLOT(prepare()));
+}
+
+QPulseAudioEngine *QPulseAudioEngine::instance()
+{
+ return pulseEngine();
+}
+
+QList<QByteArray> QPulseAudioEngine::availableDevices(QAudio::Mode mode) const
+{
+ QList<QByteArray> devices;
+ QByteArray defaultDevice;
+
+ m_serverLock.lockForRead();
+
+ if (mode == QAudio::AudioOutput) {
+ QReadLocker locker(&m_sinkLock);
+ devices = m_sinks.values();
+ defaultDevice = m_defaultSink;
+ } else {
+ QReadLocker locker(&m_sourceLock);
+ devices = m_sources.values();
+ defaultDevice = m_defaultSource;
+ }
+
+ m_serverLock.unlock();
+
+ // Swap the default device to index 0
+ devices.removeOne(defaultDevice);
+ devices.prepend(defaultDevice);
+
+ return devices;
+}
+
+QByteArray QPulseAudioEngine::defaultDevice(QAudio::Mode mode) const
+{
+ return (mode == QAudio::AudioOutput) ? m_defaultSink : m_defaultSource;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/pulseaudio/qaudioengine_pulse_p.h b/src/multimedia/audio/pulseaudio/qaudioengine_pulse_p.h
new file mode 100644
index 000000000..b6b0be996
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudioengine_pulse_p.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QPULSEAUDIOENGINE_H
+#define QPULSEAUDIOENGINE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qmap.h>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qreadwritelock.h>
+#include <pulse/pulseaudio.h>
+#include "qpulsehelpers_p.h"
+#include <qaudioformat.h>
+
+QT_BEGIN_NAMESPACE
+
+class QPulseAudioEngine : public QObject
+{
+ Q_OBJECT
+
+public:
+ QPulseAudioEngine(QObject *parent = 0);
+ ~QPulseAudioEngine();
+
+ static QPulseAudioEngine *instance();
+ pa_threaded_mainloop *mainloop() { return m_mainLoop; }
+ pa_context *context() { return m_context; }
+
+ inline void lock()
+ {
+ if (m_mainLoop)
+ pa_threaded_mainloop_lock(m_mainLoop);
+ }
+
+ inline void unlock()
+ {
+ if (m_mainLoop)
+ pa_threaded_mainloop_unlock(m_mainLoop);
+ }
+
+ inline void wait(pa_operation *op)
+ {
+ while (m_mainLoop && pa_operation_get_state(op) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(m_mainLoop);
+ }
+
+ QList<QByteArray> availableDevices(QAudio::Mode mode) const;
+ QByteArray defaultDevice(QAudio::Mode mode) const;
+
+Q_SIGNALS:
+ void contextFailed();
+
+private Q_SLOTS:
+ void prepare();
+ void onContextFailed();
+
+private:
+ void updateDevices();
+ void release();
+
+public:
+ QMap<int, QByteArray> m_sinks;
+ QMap<int, QByteArray> m_sources;
+ QMap<QByteArray, QAudioFormat> m_preferredFormats;
+
+ QByteArray m_defaultSink;
+ QByteArray m_defaultSource;
+
+ mutable QReadWriteLock m_sinkLock;
+ mutable QReadWriteLock m_sourceLock;
+ mutable QReadWriteLock m_serverLock;
+
+private:
+ pa_mainloop_api *m_mainLoopApi;
+ pa_threaded_mainloop *m_mainLoop;
+ pa_context *m_context;
+ bool m_prepared;
+ };
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/audio/pulseaudio/qaudioinput_pulse.cpp b/src/multimedia/audio/pulseaudio/qaudioinput_pulse.cpp
new file mode 100644
index 000000000..3a0b84fce
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudioinput_pulse.cpp
@@ -0,0 +1,682 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qmath.h>
+#include <private/qaudiohelpers_p.h>
+
+#include "qaudioinput_pulse_p.h"
+#include "qaudioengine_pulse_p.h"
+#include "qaudiodeviceinfo_pulse_p.h"
+#include "qpulsehelpers_p.h"
+#include <sys/types.h>
+#include <unistd.h>
+
+QT_BEGIN_NAMESPACE
+
+const int PeriodTimeMs = 50;
+
+static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata)
+{
+ Q_UNUSED(userdata);
+ Q_UNUSED(length);
+ Q_UNUSED(stream);
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+}
+
+static void inputStreamStateCallback(pa_stream *stream, void *userdata)
+{
+ Q_UNUSED(userdata);
+ pa_stream_state_t state = pa_stream_get_state(stream);
+#ifdef DEBUG_PULSE
+ qDebug() << "Stream state: " << QPulseAudioInternal::stateToQString(state);
+#endif
+ switch (state) {
+ case PA_STREAM_CREATING:
+ break;
+ case PA_STREAM_READY: {
+#ifdef DEBUG_PULSE
+ QPulseAudioInput *audioInput = static_cast<QPulseAudioInput*>(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("Stream error: %1").arg(pa_strerror(pa_context_errno(pa_stream_get_context(stream))));
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+ break;
+ }
+}
+
+static void inputStreamUnderflowCallback(pa_stream *stream, void *userdata)
+{
+ Q_UNUSED(userdata);
+ Q_UNUSED(stream);
+ qWarning() << "Got a buffer underflow!";
+}
+
+static void inputStreamOverflowCallback(pa_stream *stream, void *userdata)
+{
+ Q_UNUSED(stream);
+ Q_UNUSED(userdata);
+ qWarning() << "Got a buffer overflow!";
+}
+
+static void inputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
+{
+ Q_UNUSED(stream);
+ Q_UNUSED(userdata);
+ Q_UNUSED(success);
+
+ //if (!success)
+ //TODO: Is cork success? i->operation_success = success;
+
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+}
+
+QPulseAudioInput::QPulseAudioInput(const QByteArray &device)
+ : m_totalTimeValue(0)
+ , m_audioSource(0)
+ , 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_intervalTime(1000)
+ , m_periodTime(PeriodTimeMs)
+ , m_stream(0)
+ , m_device(device)
+{
+ m_timer = new QTimer(this);
+ connect(m_timer, SIGNAL(timeout()), SLOT(userFeed()));
+}
+
+QPulseAudioInput::~QPulseAudioInput()
+{
+ close();
+ disconnect(m_timer, SIGNAL(timeout()));
+ QCoreApplication::processEvents();
+ delete m_timer;
+}
+
+void QPulseAudioInput::setError(QAudio::Error error)
+{
+ if (m_errorState == error)
+ return;
+
+ m_errorState = error;
+ emit errorChanged(error);
+}
+
+QAudio::Error QPulseAudioInput::error() const
+{
+ return m_errorState;
+}
+
+void QPulseAudioInput::setState(QAudio::State state)
+{
+ if (m_deviceState == state)
+ return;
+
+ m_deviceState = state;
+ emit stateChanged(state);
+}
+
+QAudio::State QPulseAudioInput::state() const
+{
+ return m_deviceState;
+}
+
+void QPulseAudioInput::setFormat(const QAudioFormat &format)
+{
+ if (m_deviceState == QAudio::StoppedState)
+ m_format = format;
+}
+
+QAudioFormat QPulseAudioInput::format() const
+{
+ return m_format;
+}
+
+void QPulseAudioInput::start(QIODevice *device)
+{
+ setState(QAudio::StoppedState);
+ setError(QAudio::NoError);
+
+ if (!m_pullMode && m_audioSource) {
+ delete m_audioSource;
+ m_audioSource = 0;
+ }
+
+ close();
+
+ if (!open())
+ return;
+
+ m_pullMode = true;
+ m_audioSource = device;
+
+ setState(QAudio::ActiveState);
+}
+
+QIODevice *QPulseAudioInput::start()
+{
+ setState(QAudio::StoppedState);
+ setError(QAudio::NoError);
+
+ if (!m_pullMode && m_audioSource) {
+ delete m_audioSource;
+ m_audioSource = 0;
+ }
+
+ close();
+
+ if (!open())
+ return nullptr;
+
+ m_pullMode = false;
+ m_audioSource = new PulseInputPrivate(this);
+ m_audioSource->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
+
+ setState(QAudio::IdleState);
+
+ return m_audioSource;
+}
+
+void QPulseAudioInput::stop()
+{
+ if (m_deviceState == QAudio::StoppedState)
+ return;
+
+ close();
+
+ setError(QAudio::NoError);
+ setState(QAudio::StoppedState);
+}
+
+bool QPulseAudioInput::open()
+{
+ if (m_opened)
+ return true;
+
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+
+ if (!pulseEngine->context() || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) {
+ setError(QAudio::FatalError);
+ setState(QAudio::StoppedState);
+ return false;
+ }
+
+ pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(m_format);
+
+ if (!pa_sample_spec_valid(&spec)) {
+ setError(QAudio::OpenError);
+ setState(QAudio::StoppedState);
+ return false;
+ }
+
+ m_spec = spec;
+
+#ifdef DEBUG_PULSE
+// QTime now(QTime::currentTime());
+// qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
+#endif
+
+ 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
+
+ pulseEngine->lock();
+ pa_channel_map channel_map;
+
+ pa_channel_map_init_extend(&channel_map, spec.channels, PA_CHANNEL_MAP_DEFAULT);
+
+ if (!pa_channel_map_compatible(&channel_map, &spec))
+ qWarning() << "Channel map doesn't match sample specification!";
+
+ m_stream = pa_stream_new(pulseEngine->context(), m_streamName.constData(), &spec, &channel_map);
+
+ pa_stream_set_state_callback(m_stream, inputStreamStateCallback, this);
+ pa_stream_set_read_callback(m_stream, inputStreamReadCallback, this);
+
+ 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);
+
+ 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;
+ flags |= PA_STREAM_ADJUST_LATENCY;
+
+ if (m_bufferSize > 0)
+ buffer_attr.fragsize = (uint32_t) m_bufferSize;
+ else
+ buffer_attr.fragsize = (uint32_t) m_periodSize;
+
+ if (pa_stream_connect_record(m_stream, m_device.data(), &buffer_attr, (pa_stream_flags_t)flags) < 0) {
+ qWarning() << "pa_stream_connect_record() failed!";
+ pa_stream_unref(m_stream);
+ m_stream = 0;
+ pulseEngine->unlock();
+ setError(QAudio::OpenError);
+ setState(QAudio::StoppedState);
+ return false;
+ }
+
+ while (pa_stream_get_state(m_stream) != PA_STREAM_READY)
+ pa_threaded_mainloop_wait(pulseEngine->mainloop());
+
+ 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)
+ m_bufferSize = actualBufferAttr->tlength;
+
+ pulseEngine->unlock();
+
+ connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioInput::onPulseContextFailed);
+
+ m_opened = true;
+ m_timer->start(m_periodTime);
+
+ m_clockStamp.restart();
+ m_timeStamp.restart();
+ m_elapsedTimeOffset = 0;
+ m_totalTimeValue = 0;
+
+ return true;
+}
+
+void QPulseAudioInput::close()
+{
+ if (!m_opened)
+ return;
+
+ m_timer->stop();
+
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+
+ if (m_stream) {
+ pulseEngine->lock();
+
+ pa_stream_set_state_callback(m_stream, 0, 0);
+ pa_stream_set_read_callback(m_stream, 0, 0);
+ pa_stream_set_underflow_callback(m_stream, 0, 0);
+ pa_stream_set_overflow_callback(m_stream, 0, 0);
+
+ pa_stream_disconnect(m_stream);
+ pa_stream_unref(m_stream);
+ m_stream = 0;
+
+ pulseEngine->unlock();
+ }
+
+ disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioInput::onPulseContextFailed);
+
+ if (!m_pullMode && m_audioSource) {
+ delete m_audioSource;
+ m_audioSource = 0;
+ }
+ m_opened = false;
+}
+
+int QPulseAudioInput::checkBytesReady()
+{
+ if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState) {
+ m_bytesAvailable = 0;
+ } else {
+ m_bytesAvailable = pa_stream_readable_size(m_stream);
+ }
+
+ return m_bytesAvailable;
+}
+
+int QPulseAudioInput::bytesReady() const
+{
+ return qMax(m_bytesAvailable, 0);
+}
+
+qint64 QPulseAudioInput::read(char *data, qint64 len)
+{
+ m_bytesAvailable = checkBytesReady();
+
+ setError(QAudio::NoError);
+ setState(QAudio::ActiveState);
+
+ int readBytes = 0;
+
+ if (!m_pullMode && !m_tempBuffer.isEmpty()) {
+ readBytes = qMin(static_cast<int>(len), m_tempBuffer.size());
+ memcpy(data, m_tempBuffer.constData(), readBytes);
+ m_totalTimeValue += readBytes;
+
+ if (readBytes < m_tempBuffer.size()) {
+ m_tempBuffer.remove(0, readBytes);
+ return readBytes;
+ }
+
+ m_tempBuffer.clear();
+ }
+
+ while (pa_stream_readable_size(m_stream) > 0) {
+ size_t readLength = 0;
+
+#ifdef DEBUG_PULSE
+ qDebug() << "QPulseAudioInput::read -- " << pa_stream_readable_size(m_stream) << " bytes available from pulse audio";
+#endif
+
+ 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.
+ if (pa_stream_peek(m_stream, &audioBuffer, &readLength) < 0) {
+ qWarning() << QString("pa_stream_peek() failed: %1").arg(pa_strerror(pa_context_errno(pa_stream_get_context(m_stream))));
+ pulseEngine->unlock();
+ return 0;
+ }
+
+ qint64 actualLength = 0;
+ if (m_pullMode) {
+ QByteArray adjusted(readLength, Qt::Uninitialized);
+ applyVolume(audioBuffer, adjusted.data(), readLength);
+ actualLength = m_audioSource->write(adjusted);
+
+ if (actualLength < qint64(readLength)) {
+ pulseEngine->unlock();
+
+ setError(QAudio::UnderrunError);
+ setState(QAudio::IdleState);
+
+ return actualLength;
+ }
+ } else {
+ actualLength = qMin(static_cast<int>(len - readBytes), static_cast<int>(readLength));
+ applyVolume(audioBuffer, data + readBytes, actualLength);
+ }
+
+#ifdef DEBUG_PULSE
+ qDebug() << "QPulseAudioInput::read -- wrote " << actualLength << " to client";
+#endif
+
+ if (actualLength < qint64(readLength)) {
+#ifdef DEBUG_PULSE
+ qDebug() << "QPulseAudioInput::read -- appending " << readLength - actualLength << " bytes of data to temp buffer";
+#endif
+ int diff = readLength - actualLength;
+ int oldSize = m_tempBuffer.size();
+ m_tempBuffer.resize(m_tempBuffer.size() + diff);
+ applyVolume(static_cast<const char *>(audioBuffer) + actualLength, m_tempBuffer.data() + oldSize, diff);
+ QMetaObject::invokeMethod(this, "userFeed", Qt::QueuedConnection);
+ }
+
+ m_totalTimeValue += actualLength;
+ readBytes += actualLength;
+
+ pa_stream_drop(m_stream);
+ pulseEngine->unlock();
+
+ if (!m_pullMode && readBytes >= len)
+ break;
+
+ if (m_intervalTime && (m_timeStamp.elapsed() + m_elapsedTimeOffset) > m_intervalTime) {
+ emit notify();
+ m_elapsedTimeOffset = m_timeStamp.elapsed() + m_elapsedTimeOffset - m_intervalTime;
+ m_timeStamp.restart();
+ }
+ }
+
+#ifdef DEBUG_PULSE
+ qDebug() << "QPulseAudioInput::read -- returning after reading " << readBytes << " bytes";
+#endif
+
+ return readBytes;
+}
+
+void QPulseAudioInput::applyVolume(const void *src, void *dest, int len)
+{
+ if (m_volume < 1.f)
+ QAudioHelperInternal::qMultiplySamples(m_volume, m_format, src, dest, len);
+ else
+ memcpy(dest, src, len);
+}
+
+void QPulseAudioInput::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, 0);
+ pulseEngine->wait(operation);
+ pa_operation_unref(operation);
+
+ pulseEngine->unlock();
+
+ m_timer->start(m_periodTime);
+
+ setState(QAudio::ActiveState);
+ setError(QAudio::NoError);
+ }
+}
+
+void QPulseAudioInput::setVolume(qreal vol)
+{
+ if (qFuzzyCompare(m_volume, vol))
+ return;
+
+ m_volume = qBound(qreal(0), vol, qreal(1));
+}
+
+qreal QPulseAudioInput::volume() const
+{
+ return m_volume;
+}
+
+void QPulseAudioInput::setBufferSize(int value)
+{
+ m_bufferSize = value;
+}
+
+int QPulseAudioInput::bufferSize() const
+{
+ return m_bufferSize;
+}
+
+int QPulseAudioInput::periodSize() const
+{
+ return m_periodSize;
+}
+
+void QPulseAudioInput::setNotifyInterval(int ms)
+{
+ m_intervalTime = qMax(0, ms);
+}
+
+int QPulseAudioInput::notifyInterval() const
+{
+ return m_intervalTime;
+}
+
+qint64 QPulseAudioInput::processedUSecs() const
+{
+ pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(m_format);
+ qint64 result = pa_bytes_to_usec(m_totalTimeValue, &spec);
+
+ return result;
+}
+
+void QPulseAudioInput::suspend()
+{
+ if (m_deviceState == QAudio::ActiveState) {
+ setError(QAudio::NoError);
+ setState(QAudio::SuspendedState);
+
+ m_timer->stop();
+
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_operation *operation;
+
+ pulseEngine->lock();
+
+ operation = pa_stream_cork(m_stream, 1, inputStreamSuccessCallback, 0);
+ pulseEngine->wait(operation);
+ pa_operation_unref(operation);
+
+ pulseEngine->unlock();
+ }
+}
+
+void QPulseAudioInput::userFeed()
+{
+ 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();
+}
+
+bool QPulseAudioInput::deviceReady()
+{
+ if (m_pullMode) {
+ // reads some audio data and writes it to QIODevice
+ read(0,0);
+ } else {
+ // emits readyRead() so user will call read() on QIODevice to get some audio data
+ if (m_audioSource != 0) {
+ PulseInputPrivate *a = qobject_cast<PulseInputPrivate*>(m_audioSource);
+ a->trigger();
+ }
+ }
+ m_bytesAvailable = checkBytesReady();
+
+ if (m_deviceState != QAudio::ActiveState)
+ return true;
+
+ if (m_intervalTime && (m_timeStamp.elapsed() + m_elapsedTimeOffset) > m_intervalTime) {
+ emit notify();
+ m_elapsedTimeOffset = m_timeStamp.elapsed() + m_elapsedTimeOffset - m_intervalTime;
+ m_timeStamp.restart();
+ }
+
+ return true;
+}
+
+qint64 QPulseAudioInput::elapsedUSecs() const
+{
+ if (m_deviceState == QAudio::StoppedState)
+ return 0;
+
+ return m_clockStamp.elapsed() * qint64(1000);
+}
+
+void QPulseAudioInput::reset()
+{
+ stop();
+ m_bytesAvailable = 0;
+}
+
+void QPulseAudioInput::onPulseContextFailed()
+{
+ close();
+
+ setError(QAudio::FatalError);
+ setState(QAudio::StoppedState);
+}
+
+PulseInputPrivate::PulseInputPrivate(QPulseAudioInput *audio)
+{
+ m_audioDevice = qobject_cast<QPulseAudioInput*>(audio);
+}
+
+qint64 PulseInputPrivate::readData(char *data, qint64 len)
+{
+ return m_audioDevice->read(data, len);
+}
+
+qint64 PulseInputPrivate::writeData(const char *data, qint64 len)
+{
+ Q_UNUSED(data);
+ Q_UNUSED(len);
+ return 0;
+}
+
+void PulseInputPrivate::trigger()
+{
+ emit readyRead();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qaudioinput_pulse_p.cpp"
diff --git a/src/multimedia/audio/pulseaudio/qaudioinput_pulse_p.h b/src/multimedia/audio/pulseaudio/qaudioinput_pulse_p.h
new file mode 100644
index 000000000..dce212a25
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudioinput_pulse_p.h
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QAUDIOINPUTPULSE_H
+#define QAUDIOINPUTPULSE_H
+
+#include <QtCore/qfile.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qiodevice.h>
+
+#include "qaudio.h"
+#include "qaudiodeviceinfo.h"
+#include "qaudiosystem.h"
+
+#include <pulse/pulseaudio.h>
+
+QT_BEGIN_NAMESPACE
+
+class PulseInputPrivate;
+
+class QPulseAudioInput : public QAbstractAudioInput
+{
+ Q_OBJECT
+
+public:
+ QPulseAudioInput(const QByteArray &device);
+ ~QPulseAudioInput();
+
+ qint64 read(char *data, qint64 len);
+
+ void start(QIODevice *device) override;
+ QIODevice *start() override;
+ void stop() override;
+ void reset() override;
+ void suspend() override;
+ void resume() override;
+ int bytesReady() const override;
+ int periodSize() const override;
+ void setBufferSize(int value) override;
+ int bufferSize() const override;
+ void setNotifyInterval(int milliSeconds) override;
+ int notifyInterval() const override;
+ qint64 processedUSecs() const override;
+ qint64 elapsedUSecs() const override;
+ QAudio::Error error() const override;
+ QAudio::State state() const override;
+ void setFormat(const QAudioFormat &format) override;
+ QAudioFormat format() const override;
+
+ void setVolume(qreal volume) override;
+ qreal volume() const override;
+
+ qint64 m_totalTimeValue;
+ QIODevice *m_audioSource;
+ QAudioFormat m_format;
+ QAudio::Error m_errorState;
+ QAudio::State m_deviceState;
+ qreal m_volume;
+
+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;
+ int m_intervalTime;
+ unsigned int m_periodTime;
+ QTimer *m_timer;
+ qint64 m_elapsedTimeOffset;
+ pa_stream *m_stream;
+ QElapsedTimer m_timeStamp;
+ QElapsedTimer m_clockStamp;
+ QByteArray m_streamName;
+ QByteArray m_device;
+ QByteArray m_tempBuffer;
+ pa_sample_spec m_spec;
+};
+
+class PulseInputPrivate : public QIODevice
+{
+ Q_OBJECT
+public:
+ PulseInputPrivate(QPulseAudioInput *audio);
+ ~PulseInputPrivate() {};
+
+ qint64 readData(char *data, qint64 len) override;
+ qint64 writeData(const char *data, qint64 len) override;
+
+ void trigger();
+
+private:
+ QPulseAudioInput *m_audioDevice;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/audio/pulseaudio/qaudiointerface_pulse.cpp b/src/multimedia/audio/pulseaudio/qaudiointerface_pulse.cpp
new file mode 100644
index 000000000..5c4a804b7
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudiointerface_pulse.cpp
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include <qaudiodeviceinfo.h>
+
+#include "qaudiointerface_pulse_p.h"
+#include "qaudiodeviceinfo_pulse_p.h"
+#include "qaudiooutput_pulse_p.h"
+#include "qaudioinput_pulse_p.h"
+#include "qaudioengine_pulse_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QPulseAudioInterface::QPulseAudioInterface()
+ : QAudioSystemInterface()
+ , m_pulseEngine(QPulseAudioEngine::instance())
+{
+}
+
+QByteArray QPulseAudioInterface::defaultDevice(QAudio::Mode mode) const
+{
+ return m_pulseEngine->defaultDevice(mode);
+}
+
+QList<QByteArray> QPulseAudioInterface::availableDevices(QAudio::Mode mode) const
+{
+ return m_pulseEngine->availableDevices(mode);
+}
+
+QAbstractAudioInput *QPulseAudioInterface::createInput(const QByteArray &device)
+{
+ QPulseAudioInput *input = new QPulseAudioInput(device);
+ return input;
+}
+
+QAbstractAudioOutput *QPulseAudioInterface::createOutput(const QByteArray &device)
+{
+
+ QPulseAudioOutput *output = new QPulseAudioOutput(device);
+ return output;
+}
+
+QAbstractAudioDeviceInfo *QPulseAudioInterface::createDeviceInfo(const QByteArray &device, QAudio::Mode mode)
+{
+ QPulseAudioDeviceInfo *deviceInfo = new QPulseAudioDeviceInfo(device, mode);
+ return deviceInfo;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/pulseaudio/qaudiointerface_pulse_p.h b/src/multimedia/audio/pulseaudio/qaudiointerface_pulse_p.h
new file mode 100644
index 000000000..42f39d4c9
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudiointerface_pulse_p.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QPULSEAUDIOPLUGIN_H
+#define QPULSEAUDIOPLUGIN_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qaudiosysteminterface_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QPulseAudioEngine;
+
+class QPulseAudioInterface : public QAudioSystemInterface
+{
+public:
+ QPulseAudioInterface();
+
+ QByteArray defaultDevice(QAudio::Mode mode) const override;
+ QList<QByteArray> availableDevices(QAudio::Mode mode) const override;
+ QAbstractAudioInput *createInput(const QByteArray &device) override;
+ QAbstractAudioOutput *createOutput(const QByteArray &device) override;
+ QAbstractAudioDeviceInfo *createDeviceInfo(const QByteArray &device, QAudio::Mode mode) override;
+
+private:
+ QPulseAudioEngine *m_pulseEngine;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/audio/pulseaudio/qaudiooutput_pulse.cpp b/src/multimedia/audio/pulseaudio/qaudiooutput_pulse.cpp
new file mode 100644
index 000000000..0dc666c88
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudiooutput_pulse.cpp
@@ -0,0 +1,739 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qmath.h>
+#include <private/qaudiohelpers_p.h>
+
+#include "qaudiooutput_pulse_p.h"
+#include "qaudiodeviceinfo_pulse_p.h"
+#include "qaudioengine_pulse_p.h"
+#include "qpulsehelpers_p.h"
+#include <sys/types.h>
+#include <unistd.h>
+
+QT_BEGIN_NAMESPACE
+
+const int PeriodTimeMs = 20;
+const int LowLatencyPeriodTimeMs = 10;
+const int LowLatencyBufferSizeMs = 40;
+
+#define LOW_LATENCY_CATEGORY_NAME "game"
+
+static void outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
+{
+ Q_UNUSED(stream);
+ Q_UNUSED(length);
+ Q_UNUSED(userdata);
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+}
+
+static void outputStreamStateCallback(pa_stream *stream, void *userdata)
+{
+ Q_UNUSED(userdata);
+ pa_stream_state_t state = pa_stream_get_state(stream);
+#ifdef DEBUG_PULSE
+ qDebug() << "Stream state: " << QPulseAudioInternal::stateToQString(state);
+#endif
+ switch (state) {
+ case PA_STREAM_CREATING:
+ case PA_STREAM_READY:
+ case PA_STREAM_TERMINATED:
+ break;
+
+ case PA_STREAM_FAILED:
+ default:
+ qWarning() << QString("Stream error: %1").arg(pa_strerror(pa_context_errno(pa_stream_get_context(stream))));
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+ break;
+ }
+}
+
+static void outputStreamUnderflowCallback(pa_stream *stream, void *userdata)
+{
+ Q_UNUSED(stream);
+ ((QPulseAudioOutput*)userdata)->streamUnderflowCallback();
+}
+
+static void outputStreamOverflowCallback(pa_stream *stream, void *userdata)
+{
+ Q_UNUSED(stream);
+ Q_UNUSED(userdata);
+ qWarning() << "Got a buffer overflow!";
+}
+
+static void outputStreamLatencyCallback(pa_stream *stream, void *userdata)
+{
+ Q_UNUSED(stream);
+ Q_UNUSED(userdata);
+
+#ifdef DEBUG_PULSE
+ const pa_timing_info *info = pa_stream_get_timing_info(stream);
+
+ qDebug() << "Write index corrupt: " << info->write_index_corrupt;
+ qDebug() << "Write index: " << info->write_index;
+ qDebug() << "Read index corrupt: " << info->read_index_corrupt;
+ qDebug() << "Read index: " << info->read_index;
+ qDebug() << "Sink usec: " << info->sink_usec;
+ qDebug() << "Configured sink usec: " << info->configured_sink_usec;
+#endif
+}
+
+static void outputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
+{
+ Q_UNUSED(stream);
+ Q_UNUSED(success);
+ Q_UNUSED(userdata);
+
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
+}
+
+static void outputStreamDrainComplete(pa_stream *stream, int success, void *userdata)
+{
+ Q_UNUSED(stream);
+ Q_UNUSED(success);
+ Q_UNUSED(userdata);
+
+#ifdef DEBUG_PULSE
+ qDebug() << "Draining completed successfully: " << (bool)success;
+#endif
+}
+
+static void streamAdjustPrebufferCallback(pa_stream *stream, int success, void *userdata)
+{
+ Q_UNUSED(stream);
+ Q_UNUSED(success);
+ Q_UNUSED(userdata);
+
+#ifdef DEBUG_PULSE
+ qDebug() << "Adjust prebuffer completed successfully: " << (bool)success;
+#endif
+}
+
+
+QPulseAudioOutput::QPulseAudioOutput(const QByteArray &device)
+ : m_device(device)
+ , m_errorState(QAudio::NoError)
+ , m_deviceState(QAudio::StoppedState)
+ , m_pullMode(true)
+ , m_opened(false)
+ , m_audioSource(0)
+ , m_periodTime(0)
+ , m_stream(0)
+ , m_notifyInterval(1000)
+ , m_periodSize(0)
+ , m_bufferSize(0)
+ , m_maxBufferSize(0)
+ , m_totalTimeValue(0)
+ , m_tickTimer(new QTimer(this))
+ , m_audioBuffer(0)
+ , m_resuming(false)
+ , m_volume(1.0)
+{
+ connect(m_tickTimer, SIGNAL(timeout()), SLOT(userFeed()));
+}
+
+QPulseAudioOutput::~QPulseAudioOutput()
+{
+ close();
+ disconnect(m_tickTimer, SIGNAL(timeout()));
+ QCoreApplication::processEvents();
+}
+
+void QPulseAudioOutput::setError(QAudio::Error error)
+{
+ if (m_errorState == error)
+ return;
+
+ m_errorState = error;
+ emit errorChanged(error);
+}
+
+QAudio::Error QPulseAudioOutput::error() const
+{
+ return m_errorState;
+}
+
+void QPulseAudioOutput::setState(QAudio::State state)
+{
+ if (m_deviceState == state)
+ return;
+
+ m_deviceState = state;
+ emit stateChanged(state);
+}
+
+QAudio::State QPulseAudioOutput::state() const
+{
+ return m_deviceState;
+}
+
+void QPulseAudioOutput::streamUnderflowCallback()
+{
+ if (m_deviceState != QAudio::IdleState && !m_resuming) {
+ setError(QAudio::UnderrunError);
+ setState(QAudio::IdleState);
+ }
+}
+
+void QPulseAudioOutput::start(QIODevice *device)
+{
+ setState(QAudio::StoppedState);
+ setError(QAudio::NoError);
+
+ // Handle change of mode
+ if (m_audioSource && !m_pullMode) {
+ delete m_audioSource;
+ }
+ m_audioSource = 0;
+
+ close();
+
+ m_pullMode = true;
+ m_audioSource = device;
+
+ if (!open()) {
+ m_audioSource = 0;
+ return;
+ }
+
+ setState(QAudio::ActiveState);
+}
+
+QIODevice *QPulseAudioOutput::start()
+{
+ setState(QAudio::StoppedState);
+ setError(QAudio::NoError);
+
+ // Handle change of mode
+ if (m_audioSource && !m_pullMode) {
+ delete m_audioSource;
+ }
+ m_audioSource = 0;
+
+ close();
+
+ m_pullMode = false;
+
+ if (!open())
+ return nullptr;
+
+ m_audioSource = new PulseOutputPrivate(this);
+ m_audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
+
+ setState(QAudio::IdleState);
+
+ return m_audioSource;
+}
+
+bool QPulseAudioOutput::open()
+{
+ if (m_opened)
+ return true;
+
+ 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);
+ return false;
+ }
+
+ pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(m_format);
+
+ if (!pa_sample_spec_valid(&spec)) {
+ setError(QAudio::OpenError);
+ setState(QAudio::StoppedState);
+ emit stateChanged(m_deviceState);
+ return false;
+ }
+
+ m_spec = spec;
+ m_totalTimeValue = 0;
+
+ 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
+
+ pulseEngine->lock();
+
+ qint64 bytesPerSecond = m_format.sampleRate() * m_format.channelCount() * m_format.sampleSize() / 8;
+
+ pa_proplist *propList = pa_proplist_new();
+ if (!m_category.isNull())
+ pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, m_category.toLatin1().constData());
+
+ static const auto mapName = qEnvironmentVariable("QT_PA_CHANNEL_MAP");
+ pa_channel_map_def_t mapDef = PA_CHANNEL_MAP_DEFAULT;
+ if (mapName == QLatin1String("ALSA"))
+ mapDef = PA_CHANNEL_MAP_ALSA;
+ else if (mapName == QLatin1String("AUX"))
+ mapDef = PA_CHANNEL_MAP_AUX;
+ else if (mapName == QLatin1String("WAVEEX"))
+ mapDef = PA_CHANNEL_MAP_WAVEEX;
+ else if (mapName == QLatin1String("OSS"))
+ mapDef = PA_CHANNEL_MAP_OSS;
+ else if (!mapName.isEmpty())
+ qWarning() << "Unknown pulse audio channel mapping definition:" << mapName;
+
+ pa_channel_map m;
+ auto channelMap = pa_channel_map_init_extend(&m, m_spec.channels, mapDef);
+ if (!channelMap)
+ qWarning() << "QAudioOutput: pa_channel_map_init_extend() Could not initialize channel map";
+
+ m_stream = pa_stream_new_with_proplist(pulseEngine->context(), m_streamName.constData(), &m_spec, channelMap, propList);
+ if (!m_stream) {
+ qWarning() << "QAudioOutput: pa_stream_new_with_proplist() failed!";
+ pulseEngine->unlock();
+ setError(QAudio::OpenError);
+ setState(QAudio::StoppedState);
+ emit stateChanged(m_deviceState);
+ return false;
+ }
+
+ pa_proplist_free(propList);
+
+ pa_stream_set_state_callback(m_stream, outputStreamStateCallback, this);
+ pa_stream_set_write_callback(m_stream, outputStreamWriteCallback, this);
+
+ pa_stream_set_underflow_callback(m_stream, outputStreamUnderflowCallback, this);
+ pa_stream_set_overflow_callback(m_stream, outputStreamOverflowCallback, this);
+ pa_stream_set_latency_update_callback(m_stream, outputStreamLatencyCallback, this);
+
+ if (m_bufferSize <= 0 && m_category == LOW_LATENCY_CATEGORY_NAME) {
+ m_bufferSize = bytesPerSecond * LowLatencyBufferSizeMs / qint64(1000);
+ }
+
+ 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;
+
+ if (pa_stream_connect_playback(m_stream, m_device.data(), (m_bufferSize > 0) ? &requestedBuffer : NULL, (pa_stream_flags_t)0, NULL, NULL) < 0) {
+ qWarning() << "pa_stream_connect_playback() failed!";
+ pa_stream_unref(m_stream);
+ m_stream = 0;
+ pulseEngine->unlock();
+ setError(QAudio::OpenError);
+ setState(QAudio::StoppedState);
+ emit stateChanged(m_deviceState);
+ return false;
+ }
+
+ while (pa_stream_get_state(m_stream) != PA_STREAM_READY)
+ pa_threaded_mainloop_wait(pulseEngine->mainloop());
+
+ const pa_buffer_attr *buffer = pa_stream_get_buffer_attr(m_stream);
+ m_periodTime = (m_category == LOW_LATENCY_CATEGORY_NAME) ? LowLatencyPeriodTimeMs : 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];
+
+ 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, NULL);
+ if (o)
+ pa_operation_unref(o);
+ }
+
+#ifdef DEBUG_PULSE
+ qDebug() << "Buffering info:";
+ qDebug() << "\tMax length: " << buffer->maxlength;
+ qDebug() << "\tTarget length: " << buffer->tlength;
+ qDebug() << "\tPre-buffering: " << buffer->prebuf;
+ qDebug() << "\tMinimum request: " << buffer->minreq;
+ qDebug() << "\tFragment size: " << buffer->fragsize;
+#endif
+
+ pulseEngine->unlock();
+
+ connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioOutput::onPulseContextFailed);
+
+ m_opened = true;
+
+ m_tickTimer->start(m_periodTime);
+
+ m_elapsedTimeOffset = 0;
+ m_timeStamp.restart();
+ m_clockStamp.restart();
+
+ return true;
+}
+
+void QPulseAudioOutput::close()
+{
+ if (!m_opened)
+ return;
+
+ m_tickTimer->stop();
+
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+
+ if (m_stream) {
+ pulseEngine->lock();
+
+ pa_stream_set_state_callback(m_stream, 0, 0);
+ pa_stream_set_write_callback(m_stream, 0, 0);
+ pa_stream_set_underflow_callback(m_stream, 0, 0);
+ pa_stream_set_overflow_callback(m_stream, 0, 0);
+ pa_stream_set_latency_update_callback(m_stream, 0, 0);
+
+ pa_operation *o = pa_stream_drain(m_stream, outputStreamDrainComplete, NULL);
+ if (o)
+ pa_operation_unref(o);
+
+ pa_stream_disconnect(m_stream);
+ pa_stream_unref(m_stream);
+ m_stream = NULL;
+
+ pulseEngine->unlock();
+ }
+
+ disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioOutput::onPulseContextFailed);
+
+ if (!m_pullMode && m_audioSource) {
+ delete m_audioSource;
+ m_audioSource = 0;
+ }
+ m_opened = false;
+ if (m_audioBuffer) {
+ delete[] m_audioBuffer;
+ m_audioBuffer = 0;
+ }
+}
+
+void QPulseAudioOutput::userFeed()
+{
+ if (m_deviceState == QAudio::StoppedState || m_deviceState == QAudio::SuspendedState)
+ return;
+
+ m_resuming = false;
+
+ if (m_pullMode) {
+ 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;
+
+ int audioBytesPulled = m_audioSource->read(m_audioBuffer, input);
+ Q_ASSERT(audioBytesPulled <= input);
+ if (m_audioBuffer && audioBytesPulled > 0) {
+ if (audioBytesPulled > input) {
+ qWarning() << "QPulseAudioOutput::userFeed() - Invalid audio data size provided from user:"
+ << 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);
+ }
+ }
+ }
+
+ if (m_deviceState != QAudio::ActiveState)
+ return;
+
+ if (m_notifyInterval && (m_timeStamp.elapsed() + m_elapsedTimeOffset) > m_notifyInterval) {
+ emit notify();
+ m_elapsedTimeOffset = m_timeStamp.restart() + m_elapsedTimeOffset - m_notifyInterval;
+ }
+}
+
+qint64 QPulseAudioOutput::write(const char *data, qint64 len)
+{
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+
+ pulseEngine->lock();
+
+ len = qMin(len, static_cast<qint64>(pa_stream_writable_size(m_stream)));
+
+ if (m_volume < 1.0f) {
+ // Don't use PulseAudio volume, as it might affect all other streams of the same category
+ // or even affect the system volume if flat volumes are enabled
+ void *dest = NULL;
+ size_t nbytes = len;
+ if (pa_stream_begin_write(m_stream, &dest, &nbytes) < 0) {
+ qWarning("QAudioOutput(pulseaudio): pa_stream_begin_write, error = %s",
+ pa_strerror(pa_context_errno(pulseEngine->context())));
+ setError(QAudio::IOError);
+ return 0;
+ }
+
+ len = int(nbytes);
+ QAudioHelperInternal::qMultiplySamples(m_volume, m_format, data, dest, len);
+ data = reinterpret_cast<char *>(dest);
+ }
+
+ if (pa_stream_write(m_stream, data, len, NULL, 0, PA_SEEK_RELATIVE) < 0) {
+ qWarning("QAudioOutput(pulseaudio): pa_stream_write, error = %s",
+ pa_strerror(pa_context_errno(pulseEngine->context())));
+ setError(QAudio::IOError);
+ return 0;
+ }
+
+ pulseEngine->unlock();
+ m_totalTimeValue += len;
+
+ setError(QAudio::NoError);
+ setState(QAudio::ActiveState);
+
+ return len;
+}
+
+void QPulseAudioOutput::stop()
+{
+ if (m_deviceState == QAudio::StoppedState)
+ return;
+
+ close();
+
+ setError(QAudio::NoError);
+ setState(QAudio::StoppedState);
+}
+
+int QPulseAudioOutput::bytesFree() const
+{
+ if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState)
+ return 0;
+
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pulseEngine->lock();
+ int writableSize = pa_stream_writable_size(m_stream);
+ pulseEngine->unlock();
+ return writableSize;
+}
+
+int QPulseAudioOutput::periodSize() const
+{
+ return m_periodSize;
+}
+
+void QPulseAudioOutput::setBufferSize(int value)
+{
+ m_bufferSize = value;
+}
+
+int QPulseAudioOutput::bufferSize() const
+{
+ return m_bufferSize;
+}
+
+void QPulseAudioOutput::setNotifyInterval(int ms)
+{
+ m_notifyInterval = qMax(0, ms);
+}
+
+int QPulseAudioOutput::notifyInterval() const
+{
+ return m_notifyInterval;
+}
+
+qint64 QPulseAudioOutput::processedUSecs() const
+{
+ qint64 result = qint64(1000000) * m_totalTimeValue /
+ (m_format.channelCount() * (m_format.sampleSize() / 8)) /
+ m_format.sampleRate();
+
+ return result;
+}
+
+void QPulseAudioOutput::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, NULL);
+ pulseEngine->wait(operation);
+ pa_operation_unref(operation);
+
+ operation = pa_stream_trigger(m_stream, outputStreamSuccessCallback, NULL);
+ pulseEngine->wait(operation);
+ pa_operation_unref(operation);
+
+ pulseEngine->unlock();
+
+ m_tickTimer->start(m_periodTime);
+
+ setState(m_pullMode ? QAudio::ActiveState : QAudio::IdleState);
+ setError(QAudio::NoError);
+ }
+}
+
+void QPulseAudioOutput::setFormat(const QAudioFormat &format)
+{
+ m_format = format;
+}
+
+QAudioFormat QPulseAudioOutput::format() const
+{
+ return m_format;
+}
+
+void QPulseAudioOutput::suspend()
+{
+ if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState) {
+ setError(QAudio::NoError);
+ setState(QAudio::SuspendedState);
+
+ m_tickTimer->stop();
+
+ QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
+ pa_operation *operation;
+
+ pulseEngine->lock();
+
+ operation = pa_stream_cork(m_stream, 1, outputStreamSuccessCallback, NULL);
+ pulseEngine->wait(operation);
+ pa_operation_unref(operation);
+
+ pulseEngine->unlock();
+ }
+}
+
+qint64 QPulseAudioOutput::elapsedUSecs() const
+{
+ if (m_deviceState == QAudio::StoppedState)
+ return 0;
+
+ return m_clockStamp.elapsed() * qint64(1000);
+}
+
+void QPulseAudioOutput::reset()
+{
+ stop();
+}
+
+PulseOutputPrivate::PulseOutputPrivate(QPulseAudioOutput *audio)
+{
+ m_audioDevice = qobject_cast<QPulseAudioOutput*>(audio);
+}
+
+qint64 PulseOutputPrivate::readData(char *data, qint64 len)
+{
+ Q_UNUSED(data);
+ Q_UNUSED(len);
+
+ return 0;
+}
+
+qint64 PulseOutputPrivate::writeData(const char *data, qint64 len)
+{
+ int retry = 0;
+ 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));
+ if (chunk <= 0)
+ retry++;
+ written+=chunk;
+ if (retry > 10)
+ return written;
+ }
+ }
+
+ return written;
+}
+
+void QPulseAudioOutput::setVolume(qreal vol)
+{
+ if (qFuzzyCompare(m_volume, vol))
+ return;
+
+ m_volume = qBound(qreal(0), vol, qreal(1));
+}
+
+qreal QPulseAudioOutput::volume() const
+{
+ return m_volume;
+}
+
+void QPulseAudioOutput::setCategory(const QString &category)
+{
+ if (m_category != category) {
+ m_category = category;
+ }
+}
+
+QString QPulseAudioOutput::category() const
+{
+ return m_category;
+}
+
+void QPulseAudioOutput::onPulseContextFailed()
+{
+ close();
+
+ setError(QAudio::FatalError);
+ setState(QAudio::StoppedState);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qaudiooutput_pulse_p.cpp"
diff --git a/src/multimedia/audio/pulseaudio/qaudiooutput_pulse_p.h b/src/multimedia/audio/pulseaudio/qaudiooutput_pulse_p.h
new file mode 100644
index 000000000..e11f2ab2f
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qaudiooutput_pulse_p.h
@@ -0,0 +1,166 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QAUDIOOUTPUTPULSE_H
+#define QAUDIOOUTPUTPULSE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qfile.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qiodevice.h>
+
+#include "qaudio.h"
+#include "qaudiodeviceinfo.h"
+#include "qaudiosystem.h"
+
+#include <pulse/pulseaudio.h>
+
+QT_BEGIN_NAMESPACE
+
+class QPulseAudioOutput : public QAbstractAudioOutput
+{
+ friend class PulseOutputPrivate;
+ Q_OBJECT
+
+public:
+ QPulseAudioOutput(const QByteArray &device);
+ ~QPulseAudioOutput();
+
+ void start(QIODevice *device) override;
+ QIODevice *start() override;
+ void stop() override;
+ void reset() override;
+ void suspend() override;
+ void resume() override;
+ int bytesFree() const override;
+ int periodSize() const override;
+ void setBufferSize(int value) override;
+ int bufferSize() const override;
+ void setNotifyInterval(int milliSeconds) override;
+ int notifyInterval() const override;
+ qint64 processedUSecs() const override;
+ qint64 elapsedUSecs() const override;
+ QAudio::Error error() const override;
+ QAudio::State state() const override;
+ void setFormat(const QAudioFormat &format) override;
+ QAudioFormat format() const override;
+
+ void setVolume(qreal volume) override;
+ qreal volume() const override;
+
+ void setCategory(const QString &category) override;
+ QString category() const override;
+
+public:
+ void streamUnderflowCallback();
+
+private:
+ void setState(QAudio::State state);
+ void setError(QAudio::Error error);
+
+ bool open();
+ void close();
+ qint64 write(const char *data, qint64 len);
+
+private Q_SLOTS:
+ void userFeed();
+ void onPulseContextFailed();
+
+private:
+ QByteArray m_device;
+ QByteArray m_streamName;
+ QAudioFormat m_format;
+ QAudio::Error m_errorState;
+ QAudio::State m_deviceState;
+ bool m_pullMode;
+ bool m_opened;
+ QIODevice *m_audioSource;
+ QTimer m_periodTimer;
+ int m_periodTime;
+ pa_stream *m_stream;
+ int m_notifyInterval;
+ int m_periodSize;
+ int m_bufferSize;
+ int m_maxBufferSize;
+ QElapsedTimer m_clockStamp;
+ qint64 m_totalTimeValue;
+ QTimer *m_tickTimer;
+ char *m_audioBuffer;
+ QElapsedTimer m_timeStamp;
+ qint64 m_elapsedTimeOffset;
+ bool m_resuming;
+ QString m_category;
+
+ qreal m_volume;
+ pa_sample_spec m_spec;
+};
+
+class PulseOutputPrivate : public QIODevice
+{
+ friend class QPulseAudioOutput;
+ Q_OBJECT
+
+public:
+ PulseOutputPrivate(QPulseAudioOutput *audio);
+ virtual ~PulseOutputPrivate() {}
+
+protected:
+ qint64 readData(char *data, qint64 len) override;
+ qint64 writeData(const char *data, qint64 len) override;
+
+private:
+ QPulseAudioOutput *m_audioDevice;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/audio/pulseaudio/qpulsehelpers.cpp b/src/multimedia/audio/pulseaudio/qpulsehelpers.cpp
new file mode 100644
index 000000000..15bcc1074
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qpulsehelpers.cpp
@@ -0,0 +1,211 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "qpulsehelpers_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QPulseAudioInternal
+{
+pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
+{
+ pa_sample_spec spec;
+
+ spec.rate = format.sampleRate();
+ spec.channels = format.channelCount();
+ spec.format = PA_SAMPLE_INVALID;
+ const bool isBigEndian = (format.byteOrder() == QAudioFormat::BigEndian);
+
+ if (format.sampleType() == QAudioFormat::UnSignedInt) {
+ if (format.sampleSize() == 8)
+ spec.format = PA_SAMPLE_U8;
+ } else if (format.sampleType() == QAudioFormat::SignedInt) {
+ if (format.sampleSize() == 16) {
+ spec.format = isBigEndian ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
+ } else if (format.sampleSize() == 24) {
+ spec.format = isBigEndian ? PA_SAMPLE_S24BE : PA_SAMPLE_S24LE;
+ } else if (format.sampleSize() == 32) {
+ spec.format = isBigEndian ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
+ }
+ } else if (format.sampleType() == QAudioFormat::Float) {
+ if (format.sampleSize() == 32)
+ spec.format = isBigEndian ? PA_SAMPLE_FLOAT32BE : PA_SAMPLE_FLOAT32LE;
+ }
+
+ return spec;
+}
+
+#ifdef DEBUG_PULSE
+QString stateToQString(pa_stream_state_t state)
+{
+ switch (state)
+ {
+ case PA_STREAM_UNCONNECTED: return "Unconnected";
+ case PA_STREAM_CREATING: return "Creating";
+ case PA_STREAM_READY: return "Ready";
+ case PA_STREAM_FAILED: return "Failed";
+ case PA_STREAM_TERMINATED: return "Terminated";
+ }
+
+ return QString("Unknown state: %0").arg(state);
+}
+
+QString sampleFormatToQString(pa_sample_format format)
+{
+ switch (format)
+ {
+ case PA_SAMPLE_U8: return "Unsigned 8 Bit PCM.";
+ case PA_SAMPLE_ALAW: return "8 Bit a-Law ";
+ case PA_SAMPLE_ULAW: return "8 Bit mu-Law";
+ case PA_SAMPLE_S16LE: return "Signed 16 Bit PCM, little endian (PC).";
+ case PA_SAMPLE_S16BE: return "Signed 16 Bit PCM, big endian.";
+ case PA_SAMPLE_FLOAT32LE: return "32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0";
+ case PA_SAMPLE_FLOAT32BE: return "32 Bit IEEE floating point, big endian, range -1.0 to 1.0";
+ case PA_SAMPLE_S32LE: return "Signed 32 Bit PCM, little endian (PC).";
+ case PA_SAMPLE_S32BE: return "Signed 32 Bit PCM, big endian.";
+ case PA_SAMPLE_S24LE: return "Signed 24 Bit PCM packed, little endian (PC).";
+ case PA_SAMPLE_S24BE: return "Signed 24 Bit PCM packed, big endian.";
+ case PA_SAMPLE_S24_32LE: return "Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC).";
+ case PA_SAMPLE_S24_32BE: return "Signed 24 Bit PCM in LSB of 32 Bit words, big endian.";
+ case PA_SAMPLE_MAX: return "Upper limit of valid sample types.";
+ case PA_SAMPLE_INVALID: return "Invalid sample format";
+ }
+
+ return QString("Invalid value: %0").arg(format);
+}
+
+QString stateToQString(pa_context_state_t state)
+{
+ switch (state)
+ {
+ case PA_CONTEXT_UNCONNECTED: return "Unconnected";
+ case PA_CONTEXT_CONNECTING: return "Connecting";
+ case PA_CONTEXT_AUTHORIZING: return "Authorizing";
+ case PA_CONTEXT_SETTING_NAME: return "Setting Name";
+ case PA_CONTEXT_READY: return "Ready";
+ case PA_CONTEXT_FAILED: return "Failed";
+ case PA_CONTEXT_TERMINATED: return "Terminated";
+ }
+
+ return QString("Unknown state: %0").arg(state);
+}
+#endif
+
+QAudioFormat sampleSpecToAudioFormat(pa_sample_spec spec)
+{
+ QAudioFormat format;
+ format.setSampleRate(spec.rate);
+ format.setChannelCount(spec.channels);
+ format.setCodec("audio/pcm");
+
+ switch (spec.format) {
+ case PA_SAMPLE_U8:
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ format.setSampleType(QAudioFormat::UnSignedInt);
+ format.setSampleSize(8);
+ break;
+ case PA_SAMPLE_ALAW:
+ // TODO:
+ break;
+ case PA_SAMPLE_ULAW:
+ // TODO:
+ break;
+ case PA_SAMPLE_S16LE:
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleSize(16);
+ break;
+ case PA_SAMPLE_S16BE:
+ format.setByteOrder(QAudioFormat::BigEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleSize(16);
+ break;
+ case PA_SAMPLE_FLOAT32LE:
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ format.setSampleType(QAudioFormat::Float);
+ format.setSampleSize(32);
+ break;
+ case PA_SAMPLE_FLOAT32BE:
+ format.setByteOrder(QAudioFormat::BigEndian);
+ format.setSampleType(QAudioFormat::Float);
+ format.setSampleSize(32);
+ break;
+ case PA_SAMPLE_S32LE:
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleSize(32);
+ break;
+ case PA_SAMPLE_S32BE:
+ format.setByteOrder(QAudioFormat::BigEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleSize(32);
+ break;
+ case PA_SAMPLE_S24LE:
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleSize(24);
+ break;
+ case PA_SAMPLE_S24BE:
+ format.setByteOrder(QAudioFormat::BigEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleSize(24);
+ break;
+ case PA_SAMPLE_S24_32LE:
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleSize(24);
+ break;
+ case PA_SAMPLE_S24_32BE:
+ format.setByteOrder(QAudioFormat::BigEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleSize(24);
+ break;
+ case PA_SAMPLE_MAX:
+ case PA_SAMPLE_INVALID:
+ default:
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ format.setSampleType(QAudioFormat::Unknown);
+ format.setSampleSize(0);
+ }
+
+ return format;
+}
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/pulseaudio/qpulsehelpers_p.h b/src/multimedia/audio/pulseaudio/qpulsehelpers_p.h
new file mode 100644
index 000000000..279cecc2f
--- /dev/null
+++ b/src/multimedia/audio/pulseaudio/qpulsehelpers_p.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QPULSEHELPER_H
+#define QPULSEHELPER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qaudiodeviceinfo.h"
+#include <qaudioformat.h>
+#include <pulse/pulseaudio.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QPulseAudioInternal
+{
+pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format);
+QString stateToQString(pa_stream_state_t state);
+QString stateToQString(pa_context_state_t state);
+QString sampleFormatToQString(pa_sample_format format);
+QAudioFormat sampleSpecToAudioFormat(pa_sample_spec spec);
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/audio/qaudiodevicefactory.cpp b/src/multimedia/audio/qaudiodevicefactory.cpp
index b81a48eee..c5cd2c098 100644
--- a/src/multimedia/audio/qaudiodevicefactory.cpp
+++ b/src/multimedia/audio/qaudiodevicefactory.cpp
@@ -40,23 +40,13 @@
#include <QtCore/qdebug.h>
#include "qaudiosystem.h"
-#include "qaudiosystemplugin.h"
+#include "qaudiosysteminterface_p.h"
#include "qmediapluginloader_p.h"
#include "qaudiodevicefactory_p.h"
QT_BEGIN_NAMESPACE
-static QString defaultKey()
-{
- return QStringLiteral("default");
-}
-
-#if !defined (QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
-Q_GLOBAL_STATIC_WITH_ARGS(QMediaPluginLoader, audioLoader,
- (QAudioSystemFactoryInterface_iid, QLatin1String("audio"), Qt::CaseInsensitive))
-#endif
-
class QNullDeviceInfo : public QAbstractAudioDeviceInfo
{
public:
@@ -123,79 +113,41 @@ public:
QList<QAudioDeviceInfo> QAudioDeviceFactory::availableDevices(QAudio::Mode mode)
{
QList<QAudioDeviceInfo> devices;
-#if !defined (QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
- QMediaPluginLoader* l = audioLoader();
- const auto keys = l->keys();
- for (const QString& key : keys) {
- QAudioSystemFactoryInterface* plugin = qobject_cast<QAudioSystemFactoryInterface*>(l->instance(key));
- if (plugin) {
- const auto availableDevices = plugin->availableDevices(mode);
- for (const QByteArray& handle : availableDevices)
- devices << QAudioDeviceInfo(key, handle, mode);
- }
+ auto *iface = QAudioSystemInterface::instance();
+ if (iface) {
+ const auto availableDevices = iface->availableDevices(mode);
+ for (const QByteArray& handle : availableDevices)
+ devices << QAudioDeviceInfo(handle, mode);
}
-#endif
return devices;
}
QAudioDeviceInfo QAudioDeviceFactory::defaultDevice(QAudio::Mode mode)
{
-#if !defined (QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
- QMediaPluginLoader* l = audioLoader();
-
- // Check if there is a default plugin.
- QAudioSystemFactoryInterface *plugin = qobject_cast<QAudioSystemFactoryInterface *>(l->instance(defaultKey()));
- if (plugin) {
+ auto *iface = QAudioSystemInterface::instance();
+ if (iface) {
// Ask for the default device.
- const QByteArray &device = plugin->defaultDevice(mode);
+ const QByteArray &device = iface->defaultDevice(mode);
if (!device.isEmpty())
- return QAudioDeviceInfo(defaultKey(), device, mode);
+ return QAudioDeviceInfo(device, mode);
// If there were no default devices then just pick the first device that's available.
- const auto &devices = plugin->availableDevices(mode);
+ const auto &devices = iface->availableDevices(mode);
if (!devices.isEmpty())
- return QAudioDeviceInfo(defaultKey(), devices.first(), mode);
+ return QAudioDeviceInfo(devices.first(), mode);
}
- // If no plugin is marked as default, check the other plugins.
- // Note: We're going to prioritize plugins that report a default device.
- const auto &keys = l->keys();
- QAudioDeviceInfo fallbackDevice;
- for (const auto &key : keys) {
- if (key == defaultKey())
- continue;
- QAudioSystemFactoryInterface* plugin = qobject_cast<QAudioSystemFactoryInterface*>(l->instance(key));
- if (plugin) {
- // Check if the plugin has the extent-ion interface.
- const QByteArray &device = plugin->defaultDevice(mode);
- if (!device.isEmpty())
- return QAudioDeviceInfo(key, device, mode);
- if (fallbackDevice.isNull()) {
- const auto &devices = plugin->availableDevices(mode);
- if (!devices.isEmpty())
- fallbackDevice = QAudioDeviceInfo(key, devices.first(), mode);
- }
- }
- }
-
- return fallbackDevice;
-#else
return QAudioDeviceInfo();
-#endif
}
-QAbstractAudioDeviceInfo* QAudioDeviceFactory::audioDeviceInfo(const QString &realm, const QByteArray &handle, QAudio::Mode mode)
+QAbstractAudioDeviceInfo* QAudioDeviceFactory::audioDeviceInfo(const QByteArray &handle, QAudio::Mode mode)
{
QAbstractAudioDeviceInfo *rc = nullptr;
-#if !defined (QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
- QAudioSystemFactoryInterface* plugin =
- qobject_cast<QAudioSystemFactoryInterface*>(audioLoader()->instance(realm));
-
- if (plugin)
- rc = plugin->createDeviceInfo(handle, mode);
-#endif
+ auto *iface = QAudioSystemInterface::instance();
+ if (iface)
+ rc = iface->createDeviceInfo(handle, mode);
return rc == nullptr ? new QNullDeviceInfo() : rc;
}
@@ -215,16 +167,12 @@ QAbstractAudioInput* QAudioDeviceFactory::createInputDevice(QAudioDeviceInfo con
if (deviceInfo.isNull())
return new QNullInputDevice();
-#if !defined (QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
- QAudioSystemFactoryInterface* plugin =
- qobject_cast<QAudioSystemFactoryInterface*>(audioLoader()->instance(deviceInfo.realm()));
-
- if (plugin) {
- QAbstractAudioInput* p = plugin->createInput(deviceInfo.handle());
+ auto *iface = QAudioSystemInterface::instance();
+ if (iface) {
+ QAbstractAudioInput* p = iface->createInput(deviceInfo.handle());
if (p) p->setFormat(format);
return p;
}
-#endif
return new QNullInputDevice();
}
@@ -234,16 +182,12 @@ QAbstractAudioOutput* QAudioDeviceFactory::createOutputDevice(QAudioDeviceInfo c
if (deviceInfo.isNull())
return new QNullOutputDevice();
-#if !defined (QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
- QAudioSystemFactoryInterface* plugin =
- qobject_cast<QAudioSystemFactoryInterface*>(audioLoader()->instance(deviceInfo.realm()));
-
- if (plugin) {
- QAbstractAudioOutput* p = plugin->createOutput(deviceInfo.handle());
+ auto *iface = QAudioSystemInterface::instance();
+ if (iface) {
+ QAbstractAudioOutput* p = iface->createOutput(deviceInfo.handle());
if (p) p->setFormat(format);
return p;
}
-#endif
return new QNullOutputDevice();
}
diff --git a/src/multimedia/audio/qaudiodevicefactory_p.h b/src/multimedia/audio/qaudiodevicefactory_p.h
index 238be46a7..f5a7d0cde 100644
--- a/src/multimedia/audio/qaudiodevicefactory_p.h
+++ b/src/multimedia/audio/qaudiodevicefactory_p.h
@@ -73,7 +73,7 @@ public:
static QAudioDeviceInfo defaultDevice(QAudio::Mode mode);
- static QAbstractAudioDeviceInfo* audioDeviceInfo(const QString &realm, const QByteArray &handle, QAudio::Mode mode);
+ static QAbstractAudioDeviceInfo* audioDeviceInfo(const QByteArray &handle, QAudio::Mode mode);
static QAbstractAudioInput* createDefaultInputDevice(QAudioFormat const &format);
static QAbstractAudioOutput* createDefaultOutputDevice(QAudioFormat const &format);
diff --git a/src/multimedia/audio/qaudiodeviceinfo.cpp b/src/multimedia/audio/qaudiodeviceinfo.cpp
index 051ef8b3f..dcd2c24a9 100644
--- a/src/multimedia/audio/qaudiodeviceinfo.cpp
+++ b/src/multimedia/audio/qaudiodeviceinfo.cpp
@@ -61,30 +61,29 @@ public:
{
}
- QAudioDeviceInfoPrivate(const QString &r, const QByteArray &h, QAudio::Mode m):
- realm(r), handle(h), mode(m)
+ QAudioDeviceInfoPrivate(const QByteArray &h, QAudio::Mode m)
+ : handle(h), mode(m)
{
if (!handle.isEmpty())
- info = QAudioDeviceFactory::audioDeviceInfo(realm, handle, mode);
+ info = QAudioDeviceFactory::audioDeviceInfo(handle, mode);
else
info = nullptr;
}
QAudioDeviceInfoPrivate(const QAudioDeviceInfoPrivate &other):
QSharedData(other),
- realm(other.realm), handle(other.handle), mode(other.mode)
+ handle(other.handle), mode(other.mode)
{
- info = QAudioDeviceFactory::audioDeviceInfo(realm, handle, mode);
+ info = QAudioDeviceFactory::audioDeviceInfo(handle, mode);
}
QAudioDeviceInfoPrivate& operator=(const QAudioDeviceInfoPrivate &other)
{
delete info;
- realm = other.realm;
handle = other.handle;
mode = other.mode;
- info = QAudioDeviceFactory::audioDeviceInfo(realm, handle, mode);
+ info = QAudioDeviceFactory::audioDeviceInfo(handle, mode);
return *this;
}
@@ -93,7 +92,6 @@ public:
delete info;
}
- QString realm;
QByteArray handle;
QAudio::Mode mode;
QAbstractAudioDeviceInfo* info;
@@ -191,8 +189,7 @@ bool QAudioDeviceInfo::operator ==(const QAudioDeviceInfo &other) const
{
if (d == other.d)
return true;
- if (d->realm == other.d->realm
- && d->mode == other.d->mode
+ if (d->mode == other.d->mode
&& d->handle == other.d->handle
&& deviceName() == other.deviceName())
return true;
@@ -439,23 +436,12 @@ QList<QAudioDeviceInfo> QAudioDeviceInfo::availableDevices(QAudio::Mode mode)
/*!
\internal
*/
-QAudioDeviceInfo::QAudioDeviceInfo(const QString &realm, const QByteArray &handle, QAudio::Mode mode):
- d(new QAudioDeviceInfoPrivate(realm, handle, mode))
+QAudioDeviceInfo::QAudioDeviceInfo(const QByteArray &handle, QAudio::Mode mode):
+ d(new QAudioDeviceInfoPrivate(handle, mode))
{
}
/*!
- Returns the key that represents the audio plugin.
-
- \since 5.14
- \sa QAudioSystemPlugin
-*/
-QString QAudioDeviceInfo::realm() const
-{
- return d->realm;
-}
-
-/*!
\internal
*/
QByteArray QAudioDeviceInfo::handle() const
diff --git a/src/multimedia/audio/qaudiodeviceinfo.h b/src/multimedia/audio/qaudiodeviceinfo.h
index 015c8bad7..19556c430 100644
--- a/src/multimedia/audio/qaudiodeviceinfo.h
+++ b/src/multimedia/audio/qaudiodeviceinfo.h
@@ -87,7 +87,6 @@ public:
QList<int> supportedSampleSizes() const;
QList<QAudioFormat::Endian> supportedByteOrders() const;
QList<QAudioFormat::SampleType> supportedSampleTypes() const;
- QString realm() const;
static QAudioDeviceInfo defaultInputDevice();
static QAudioDeviceInfo defaultOutputDevice();
@@ -95,7 +94,7 @@ public:
static QList<QAudioDeviceInfo> availableDevices(QAudio::Mode mode);
private:
- QAudioDeviceInfo(const QString &realm, const QByteArray &handle, QAudio::Mode mode);
+ QAudioDeviceInfo(const QByteArray &handle, QAudio::Mode mode);
QByteArray handle() const;
QAudio::Mode mode() const;
diff --git a/src/multimedia/audio/qaudiosystemplugin.cpp b/src/multimedia/audio/qaudiosysteminterface.cpp
index 2df13b9ec..90861535d 100644
--- a/src/multimedia/audio/qaudiosystemplugin.cpp
+++ b/src/multimedia/audio/qaudiosysteminterface.cpp
@@ -37,14 +37,21 @@
**
****************************************************************************/
+#include <private/qtmultimediaglobal_p.h>
+#include "qaudiosysteminterface_p.h"
-#include "qaudiosystemplugin.h"
+#if QT_CONFIG(pulseaudio)
+#include <private/qaudiointerface_pulse_p.h>
+#elif QT_CONFIG(alsa)
+#include <private/qalsainterface_p.h>
+#endif
QT_BEGIN_NAMESPACE
/*!
- \class QAudioSystemPlugin
- \brief The QAudioSystemPlugin class provides an abstract base for audio plugins.
+ \class QAudioSystemInterface
+ \internal
+ \brief The QAudioSystemInterface class provides an abstract base for audio plugins.
\ingroup multimedia
\ingroup multimedia_audio
@@ -80,50 +87,45 @@ QT_BEGIN_NAMESPACE
QAudioDeviceInfo::availableDevices(QAudio::AudioOutput).size() = 0 (dummy backend)
*/
-/*!
- \fn QAudioSystemPlugin::QAudioSystemPlugin(QObject* parent)
-
- Constructs a new audio plugin with \a parent.
- This is invoked automatically by the Q_PLUGIN_METADATA() macro.
-*/
-
-QAudioSystemPlugin::QAudioSystemPlugin(QObject* parent) :
- QObject(parent)
-{}
+QAudioSystemInterface *QAudioSystemInterface::instance()
+{
+ static QAudioSystemInterface *system = nullptr;
+ if (!system) {
+#if QT_CONFIG(pulseaudio)
+ system = new QPulseAudioInterface();
+#elif QT_CONFIG(alsa)
+ system = new QAlsaInterface();
+#endif
+ }
+ return system;
+}
-/*!
- \fn QAudioSystemPlugin::~QAudioSystemPlugin()
-
- Destroys the audio plugin.
- You never have to call this explicitly. Qt destroys a plugin automatically when it is no longer used.
-*/
+QAudioSystemInterface::~QAudioSystemInterface()
+{
-QAudioSystemPlugin::~QAudioSystemPlugin()
-{}
+}
/*!
- \fn QList<QByteArray> QAudioSystemPlugin::availableDevices(QAudio::Mode mode) const
+ \fn QList<QByteArray> QAudioSystemInterface::availableDevices(QAudio::Mode mode) const
Returns a list of available audio devices for \a mode
*/
/*!
- \fn QAbstractAudioInput* QAudioSystemPlugin::createInput(const QByteArray& device)
+ \fn QAbstractAudioInput* QAudioSystemInterface::createInput(const QByteArray& device)
Returns a pointer to a QAbstractAudioInput created using \a device identifier
*/
/*!
- \fn QAbstractAudioOutput* QAudioSystemPlugin::createOutput(const QByteArray& device)
+ \fn QAbstractAudioOutput* QAudioSystemInterface::createOutput(const QByteArray& device)
Returns a pointer to a QAbstractAudioOutput created using \a device identifier
*/
/*!
- \fn QAbstractAudioDeviceInfo* QAudioSystemPlugin::createDeviceInfo(const QByteArray& device, QAudio::Mode mode)
+ \fn QAbstractAudioDeviceInfo* QAudioSystemInterface::createDeviceInfo(const QByteArray& device, QAudio::Mode mode)
Returns a pointer to a QAbstractAudioDeviceInfo created using \a device and \a mode
*/
QT_END_NAMESPACE
-
-#include "moc_qaudiosystemplugin.cpp"
diff --git a/src/multimedia/audio/qaudiosystemplugin.h b/src/multimedia/audio/qaudiosysteminterface_p.h
index 5c671f2b9..bc15cda81 100644
--- a/src/multimedia/audio/qaudiosystemplugin.h
+++ b/src/multimedia/audio/qaudiosysteminterface_p.h
@@ -41,6 +41,17 @@
#ifndef QAUDIOSYSTEMPLUGIN_H
#define QAUDIOSYSTEMPLUGIN_H
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
#include <QtCore/qstring.h>
#include <QtCore/qplugin.h>
@@ -53,8 +64,11 @@
QT_BEGIN_NAMESPACE
-struct Q_MULTIMEDIA_EXPORT QAudioSystemFactoryInterface
+struct QAudioSystemInterface
{
+ static QAudioSystemInterface *instance();
+
+ virtual ~QAudioSystemInterface();
virtual QList<QByteArray> availableDevices(QAudio::Mode) const = 0;
virtual QAbstractAudioInput* createInput(const QByteArray& device) = 0;
virtual QAbstractAudioOutput* createOutput(const QByteArray& device) = 0;
@@ -62,20 +76,6 @@ struct Q_MULTIMEDIA_EXPORT QAudioSystemFactoryInterface
virtual QByteArray defaultDevice(QAudio::Mode) const = 0;
};
-#define QAudioSystemFactoryInterface_iid \
- "org.qt-project.qt.audiosystemfactory/5.0"
-Q_DECLARE_INTERFACE(QAudioSystemFactoryInterface, QAudioSystemFactoryInterface_iid)
-
-class Q_MULTIMEDIA_EXPORT QAudioSystemPlugin : public QObject, public QAudioSystemFactoryInterface
-{
- Q_OBJECT
- Q_INTERFACES(QAudioSystemFactoryInterface)
-
-public:
- explicit QAudioSystemPlugin(QObject *parent = nullptr);
- ~QAudioSystemPlugin();
-};
-
QT_END_NAMESPACE
#endif // QAUDIOSYSTEMPLUGIN_H