summaryrefslogtreecommitdiffstats
path: root/src/multimedia/alsa
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/alsa')
-rw-r--r--src/multimedia/alsa/alsa.json3
-rw-r--r--src/multimedia/alsa/qalsaaudiodevice.cpp71
-rw-r--r--src/multimedia/alsa/qalsaaudiodevice_p.h49
-rw-r--r--src/multimedia/alsa/qalsaaudiosink.cpp698
-rw-r--r--src/multimedia/alsa/qalsaaudiosink_p.h121
-rw-r--r--src/multimedia/alsa/qalsaaudiosource.cpp770
-rw-r--r--src/multimedia/alsa/qalsaaudiosource_p.h142
-rw-r--r--src/multimedia/alsa/qalsamediadevices.cpp130
-rw-r--r--src/multimedia/alsa/qalsamediadevices_p.h41
9 files changed, 2025 insertions, 0 deletions
diff --git a/src/multimedia/alsa/alsa.json b/src/multimedia/alsa/alsa.json
new file mode 100644
index 000000000..71abb213c
--- /dev/null
+++ b/src/multimedia/alsa/alsa.json
@@ -0,0 +1,3 @@
+{
+ "Keys": [ "alsa" ]
+}
diff --git a/src/multimedia/alsa/qalsaaudiodevice.cpp b/src/multimedia/alsa/qalsaaudiodevice.cpp
new file mode 100644
index 000000000..893375270
--- /dev/null
+++ b/src/multimedia/alsa/qalsaaudiodevice.cpp
@@ -0,0 +1,71 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// 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 "qalsaaudiodevice_p.h"
+
+#include <alsa/version.h>
+
+QT_BEGIN_NAMESPACE
+
+QAlsaAudioDeviceInfo::QAlsaAudioDeviceInfo(const QByteArray &dev, const QString &desc, QAudioDevice::Mode mode)
+ : QAudioDevicePrivate(dev, mode)
+{
+ description = desc;
+
+ checkSurround();
+
+ minimumChannelCount = 1;
+ maximumChannelCount = 2;
+ if (surround71)
+ maximumChannelCount = 8;
+ else if (surround40)
+ maximumChannelCount = 4;
+ else if (surround51)
+ maximumChannelCount = 6;
+
+ minimumSampleRate = 8000;
+ maximumSampleRate = 48000;
+
+ supportedSampleFormats = {
+ QAudioFormat::UInt8,
+ QAudioFormat::Int16,
+ QAudioFormat::Int32,
+ QAudioFormat::Float,
+ };
+
+ preferredFormat.setChannelCount(mode == QAudioDevice::Input ? 1 : 2);
+ preferredFormat.setSampleFormat(QAudioFormat::Float);
+ preferredFormat.setSampleRate(48000);
+}
+
+QAlsaAudioDeviceInfo::~QAlsaAudioDeviceInfo() = default;
+
+void QAlsaAudioDeviceInfo::checkSurround()
+{
+ if (mode != QAudioDevice::Output)
+ return;
+
+ surround40 = false;
+ surround51 = false;
+ surround71 = false;
+
+ if (id.startsWith(QLatin1String("surround40")))
+ surround40 = true;
+ if (id.startsWith(QLatin1String("surround51")))
+ surround51 = true;
+ if (id.startsWith(QLatin1String("surround71")))
+ surround71 = true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/alsa/qalsaaudiodevice_p.h b/src/multimedia/alsa/qalsaaudiodevice_p.h
new file mode 100644
index 000000000..dcbc9e692
--- /dev/null
+++ b/src/multimedia/alsa/qalsaaudiodevice_p.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// 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 <private/qaudiodevice_p.h>
+#include <private/qaudiosystem_p.h>
+
+QT_BEGIN_NAMESPACE
+
+
+class QAlsaAudioDeviceInfo : public QAudioDevicePrivate
+{
+public:
+ QAlsaAudioDeviceInfo(const QByteArray &dev, const QString &description, QAudioDevice::Mode mode);
+ ~QAlsaAudioDeviceInfo();
+
+private:
+ void checkSurround();
+ bool surround40{};
+ bool surround51{};
+ bool surround71{};
+};
+
+QT_END_NAMESPACE
+
+
+#endif // QALSAAUDIODEVICEINFO_H
diff --git a/src/multimedia/alsa/qalsaaudiosink.cpp b/src/multimedia/alsa/qalsaaudiosink.cpp
new file mode 100644
index 000000000..e515219a2
--- /dev/null
+++ b/src/multimedia/alsa/qalsaaudiosink.cpp
@@ -0,0 +1,698 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// 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 "qalsaaudiosink_p.h"
+#include "qalsaaudiodevice_p.h"
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+
+static Q_LOGGING_CATEGORY(lcAlsaOutput, "qt.multimedia.alsa.output")
+//#define DEBUG_AUDIO 1
+
+QAlsaAudioSink::QAlsaAudioSink(const QByteArray &device, QObject *parent)
+ : QPlatformAudioSink(parent)
+{
+ m_device = device;
+
+ timer = new QTimer(this);
+ connect(timer, &QTimer::timeout, this, &QAlsaAudioSink::userFeed);
+}
+
+QAlsaAudioSink::~QAlsaAudioSink()
+{
+ close();
+ disconnect(timer, &QTimer::timeout, this, &QAlsaAudioSink::userFeed);
+ QCoreApplication::processEvents();
+ delete timer;
+}
+
+void QAlsaAudioSink::setVolume(qreal vol)
+{
+ m_volume = vol;
+}
+
+qreal QAlsaAudioSink::volume() const
+{
+ return m_volume;
+}
+
+QAudio::Error QAlsaAudioSink::error() const
+{
+ return errorState;
+}
+
+QAudio::State QAlsaAudioSink::state() const
+{
+ return deviceState;
+}
+
+int QAlsaAudioSink::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 QAlsaAudioSink::setFormat()
+{
+ snd_pcm_format_t pcmformat = SND_PCM_FORMAT_UNKNOWN;
+
+ switch (settings.sampleFormat()) {
+ case QAudioFormat::UInt8:
+ pcmformat = SND_PCM_FORMAT_U8;
+ break;
+ case QAudioFormat::Int16:
+ if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
+ pcmformat = SND_PCM_FORMAT_S16_BE;
+ else
+ pcmformat = SND_PCM_FORMAT_S16_LE;
+ break;
+ case QAudioFormat::Int32:
+ if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
+ pcmformat = SND_PCM_FORMAT_S32_BE;
+ else
+ pcmformat = SND_PCM_FORMAT_S32_LE;
+ break;
+ case QAudioFormat::Float:
+ if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
+ pcmformat = SND_PCM_FORMAT_FLOAT_BE;
+ else
+ pcmformat = SND_PCM_FORMAT_FLOAT_LE;
+ break;
+ default:
+ break;
+ }
+
+ return pcmformat != SND_PCM_FORMAT_UNKNOWN
+ ? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat)
+ : -1;
+}
+
+void QAlsaAudioSink::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;
+
+ connect(audioSource, &QIODevice::readyRead, timer, [this] {
+ if (!timer->isActive()) {
+ timer->start(period_time / 1000);
+ }
+ });
+ deviceState = QAudio::ActiveState;
+
+ open();
+
+ emit stateChanged(deviceState);
+}
+
+QIODevice* QAlsaAudioSink::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 QAlsaAudioSink::stop()
+{
+ if(deviceState == QAudio::StoppedState)
+ return;
+ errorState = QAudio::NoError;
+ deviceState = QAudio::StoppedState;
+ close();
+ emit stateChanged(deviceState);
+}
+
+bool QAlsaAudioSink::open()
+{
+ if(opened)
+ return true;
+
+#ifdef DEBUG_AUDIO
+ QTime now(QTime::currentTime());
+ qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
+#endif
+ elapsedTimeOffset = 0;
+
+ int dir;
+ int err = 0;
+ int count=0;
+ unsigned int sampleRate = settings.sampleRate();
+
+ if (!settings.isValid()) {
+ qWarning("QAudioSink: open error, invalid format.");
+ } else if (settings.sampleRate() <= 0) {
+ qWarning("QAudioSink: 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;
+ }
+
+ // Step 1: try and open the device
+ while((count < 5) && (err < 0)) {
+ err=snd_pcm_open(&handle, m_device.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("QAudioSink: 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("QAudioSink: 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("QAudioSink: snd_pcm_hw_params_set_access: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = setFormat();
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioSink: 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("QAudioSink: 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("QAudioSink: 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("QAudioSink: 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("QAudioSink: 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("QAudioSink: 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("QAudioSink: 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("QAudioSink: 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);
+
+ elapsedTimeOffset = 0;
+ errorState = QAudio::NoError;
+ totalTimeValue = 0;
+ opened = true;
+
+ return true;
+}
+
+void QAlsaAudioSink::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;
+}
+
+qsizetype QAlsaAudioSink::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 QAlsaAudioSink::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;
+}
+
+void QAlsaAudioSink::setBufferSize(qsizetype value)
+{
+ if(deviceState == QAudio::StoppedState)
+ buffer_size = value;
+}
+
+qsizetype QAlsaAudioSink::bufferSize() const
+{
+ return buffer_size;
+}
+
+qint64 QAlsaAudioSink::processedUSecs() const
+{
+ return qint64(1000000) * totalTimeValue / settings.sampleRate();
+}
+
+void QAlsaAudioSink::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 = suspendedInState;
+ errorState = QAudio::NoError;
+ timer->start(period_time/1000);
+ emit stateChanged(deviceState);
+ }
+}
+
+void QAlsaAudioSink::setFormat(const QAudioFormat& fmt)
+{
+ settings = fmt;
+}
+
+QAudioFormat QAlsaAudioSink::format() const
+{
+ return settings;
+}
+
+void QAlsaAudioSink::suspend()
+{
+ if(deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState || resuming) {
+ suspendedInState = deviceState;
+ snd_pcm_drain(handle);
+ timer->stop();
+ deviceState = QAudio::SuspendedState;
+ errorState = QAudio::NoError;
+ emit stateChanged(deviceState);
+ }
+}
+
+void QAlsaAudioSink::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 QAlsaAudioSink::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
+ timer->stop();
+ snd_pcm_drain(handle);
+ bytesAvailable = bytesFree();
+ if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
+ // Underrun
+ if (deviceState != QAudio::IdleState) {
+ errorState = audioSource->atEnd() ? QAudio::NoError : 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;
+
+ return true;
+}
+
+void QAlsaAudioSink::reset()
+{
+ if(handle)
+ snd_pcm_reset(handle);
+
+ stop();
+}
+
+AlsaOutputPrivate::AlsaOutputPrivate(QAlsaAudioSink* audio)
+{
+ audioDevice = qobject_cast<QAlsaAudioSink*>(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_qalsaaudiosink_p.cpp"
diff --git a/src/multimedia/alsa/qalsaaudiosink_p.h b/src/multimedia/alsa/qalsaaudiosink_p.h
new file mode 100644
index 000000000..0f5a5aa5a
--- /dev/null
+++ b/src/multimedia/alsa/qalsaaudiosink_p.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// 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/qaudiodevice.h>
+#include <private/qaudiosystem_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAlsaAudioSink : public QPlatformAudioSink
+{
+ friend class AlsaOutputPrivate;
+ Q_OBJECT
+public:
+ QAlsaAudioSink(const QByteArray &device, QObject *parent);
+ ~QAlsaAudioSink();
+
+ 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;
+ qsizetype bytesFree() const override;
+ void setBufferSize(qsizetype value) override;
+ qsizetype bufferSize() const override;
+ qint64 processedUSecs() 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 = nullptr;
+ QAudioFormat settings;
+ QAudio::Error errorState = QAudio::NoError;
+ QAudio::State deviceState = QAudio::StoppedState;
+ QAudio::State suspendedInState = QAudio::SuspendedState;
+
+private slots:
+ void userFeed();
+ bool deviceReady();
+
+signals:
+ void processMore();
+
+private:
+ bool opened = false;
+ bool pullMode = true;
+ bool resuming = false;
+ int buffer_size = 0;
+ int period_size = 0;
+ qint64 totalTimeValue = 0;
+ unsigned int buffer_time = 100000;
+ unsigned int period_time = 20000;
+ 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 = nullptr;
+ QByteArray m_device;
+ int bytesAvailable = 0;
+ qint64 elapsedTimeOffset = 0;
+ char* audioBuffer = nullptr;
+ snd_pcm_t* handle = nullptr;
+ snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
+ snd_pcm_hw_params_t *hwparams = nullptr;
+ qreal m_volume = 1.0f;
+};
+
+class AlsaOutputPrivate : public QIODevice
+{
+ friend class QAlsaAudioSink;
+ Q_OBJECT
+public:
+ AlsaOutputPrivate(QAlsaAudioSink* audio);
+ ~AlsaOutputPrivate();
+
+ qint64 readData( char* data, qint64 len) override;
+ qint64 writeData(const char* data, qint64 len) override;
+
+private:
+ QAlsaAudioSink *audioDevice;
+};
+
+QT_END_NAMESPACE
+
+
+#endif
diff --git a/src/multimedia/alsa/qalsaaudiosource.cpp b/src/multimedia/alsa/qalsaaudiosource.cpp
new file mode 100644
index 000000000..ebf6e24e2
--- /dev/null
+++ b/src/multimedia/alsa/qalsaaudiosource.cpp
@@ -0,0 +1,770 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// 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 "qalsaaudiosource_p.h"
+
+QT_BEGIN_NAMESPACE
+
+//#define DEBUG_AUDIO 1
+
+QAlsaAudioSource::QAlsaAudioSource(const QByteArray &device, QObject *parent)
+ : QPlatformAudioSource(parent)
+{
+ 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;
+ 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, &QTimer::timeout, this, &QAlsaAudioSource::userFeed);
+}
+
+QAlsaAudioSource::~QAlsaAudioSource()
+{
+ close();
+ disconnect(timer, &QTimer::timeout, this, &QAlsaAudioSource::userFeed);
+ QCoreApplication::processEvents();
+ delete timer;
+}
+
+void QAlsaAudioSource::setVolume(qreal vol)
+{
+ m_volume = vol;
+}
+
+qreal QAlsaAudioSource::volume() const
+{
+ return m_volume;
+}
+
+QAudio::Error QAlsaAudioSource::error() const
+{
+ return errorState;
+}
+
+QAudio::State QAlsaAudioSource::state() const
+{
+ return deviceState;
+}
+
+void QAlsaAudioSource::setFormat(const QAudioFormat& fmt)
+{
+ if (deviceState == QAudio::StoppedState)
+ settings = fmt;
+}
+
+QAudioFormat QAlsaAudioSource::format() const
+{
+ return settings;
+}
+
+int QAlsaAudioSource::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 QAlsaAudioSource::setFormat()
+{
+ snd_pcm_format_t pcmformat = SND_PCM_FORMAT_UNKNOWN;
+
+ switch (settings.sampleFormat()) {
+ case QAudioFormat::UInt8:
+ pcmformat = SND_PCM_FORMAT_U8;
+ break;
+ case QAudioFormat::Int16:
+ if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
+ pcmformat = SND_PCM_FORMAT_S16_BE;
+ else
+ pcmformat = SND_PCM_FORMAT_S16_LE;
+ break;
+ case QAudioFormat::Int32:
+ if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
+ pcmformat = SND_PCM_FORMAT_S32_BE;
+ else
+ pcmformat = SND_PCM_FORMAT_S32_LE;
+ break;
+ case QAudioFormat::Float:
+ if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian)
+ pcmformat = SND_PCM_FORMAT_FLOAT_BE;
+ else
+ pcmformat = SND_PCM_FORMAT_FLOAT_LE;
+ break;
+ default:
+ break;
+ }
+
+ return pcmformat != SND_PCM_FORMAT_UNKNOWN
+ ? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat)
+ : -1;
+}
+
+void QAlsaAudioSource::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* QAlsaAudioSource::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 QAlsaAudioSource::stop()
+{
+ if(deviceState == QAudio::StoppedState)
+ return;
+
+ deviceState = QAudio::StoppedState;
+
+ close();
+ emit stateChanged(deviceState);
+}
+
+bool QAlsaAudioSource::open()
+{
+#ifdef DEBUG_AUDIO
+ QTime now(QTime::currentTime());
+ qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
+#endif
+ elapsedTimeOffset = 0;
+
+ int dir;
+ int err = 0;
+ int count=0;
+ unsigned int sampleRate=settings.sampleRate();
+
+ if (!settings.isValid()) {
+ qWarning("QAudioSource: open error, invalid format.");
+ } else if (settings.sampleRate() <= 0) {
+ qWarning("QAudioSource: 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;
+ }
+
+
+ // Step 1: try and open the device
+ while((count < 5) && (err < 0)) {
+ err = snd_pcm_open(&handle, m_device.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("QAudioSource: 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("QAudioSource: 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("QAudioSource: snd_pcm_hw_params_set_access: err = %1").arg(err);
+ }
+ }
+ if ( !fatal ) {
+ err = setFormat();
+ if ( err < 0 ) {
+ fatal = true;
+ errMessage = QString::fromLatin1("QAudioSource: 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("QAudioSource: 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("QAudioSource: 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("QAudioSource: 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("QAudioSource: 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("QAudioSource: 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("QAudioSource: 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, &QIODevice::readyRead, this, &QAlsaAudioSource::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 QAlsaAudioSource::close()
+{
+ timer->stop();
+
+ if ( handle ) {
+ snd_pcm_drop( handle );
+ snd_pcm_close( handle );
+ handle = 0;
+ }
+}
+
+int QAlsaAudioSource::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;
+}
+
+qsizetype QAlsaAudioSource::bytesReady() const
+{
+ return qMax(bytesAvailable, 0);
+}
+
+qint64 QAlsaAudioSource::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 QAlsaAudioSource::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 QAlsaAudioSource::setBufferSize(qsizetype value)
+{
+ buffer_size = value;
+}
+
+qsizetype QAlsaAudioSource::bufferSize() const
+{
+ return buffer_size;
+}
+
+qint64 QAlsaAudioSource::processedUSecs() const
+{
+ qint64 result = qint64(1000000) * totalTimeValue /
+ settings.bytesPerFrame() /
+ settings.sampleRate();
+
+ return result;
+}
+
+void QAlsaAudioSource::suspend()
+{
+ if(deviceState == QAudio::ActiveState||resuming) {
+ snd_pcm_drain(handle);
+ timer->stop();
+ deviceState = QAudio::SuspendedState;
+ emit stateChanged(deviceState);
+ }
+}
+
+void QAlsaAudioSource::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 QAlsaAudioSource::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;
+ }
+ }
+
+ return true;
+}
+
+void QAlsaAudioSource::reset()
+{
+ if(handle)
+ snd_pcm_reset(handle);
+ stop();
+ bytesAvailable = 0;
+}
+
+void QAlsaAudioSource::drain()
+{
+ if(handle)
+ snd_pcm_drain(handle);
+}
+
+AlsaInputPrivate::AlsaInputPrivate(QAlsaAudioSource* audio)
+{
+ audioDevice = qobject_cast<QAlsaAudioSource*>(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_qalsaaudiosource_p.cpp"
diff --git a/src/multimedia/alsa/qalsaaudiosource_p.h b/src/multimedia/alsa/qalsaaudiosource_p.h
new file mode 100644
index 000000000..87487a6ad
--- /dev/null
+++ b/src/multimedia/alsa/qalsaaudiosource_p.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// 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/qaudiodevice.h>
+#include <private/qaudiosystem_p.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 QAlsaAudioSource : public QPlatformAudioSource
+{
+ Q_OBJECT
+public:
+ QAlsaAudioSource(const QByteArray &device, QObject *parent);
+ ~QAlsaAudioSource();
+
+ 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;
+ qsizetype bytesReady() const override;
+ void setBufferSize(qsizetype value) override;
+ qsizetype bufferSize() const override;
+ qint64 processedUSecs() 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;
+ qint64 elapsedTimeOffset;
+ RingBuffer ringBuffer;
+ qsizetype bytesAvailable;
+ QByteArray m_device;
+ bool pullMode;
+ qsizetype 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(QAlsaAudioSource* audio);
+ ~AlsaInputPrivate();
+
+ qint64 readData( char* data, qint64 len) override;
+ qint64 writeData(const char* data, qint64 len) override;
+
+ void trigger();
+private:
+ QAlsaAudioSource *audioDevice;
+};
+
+QT_END_NAMESPACE
+
+
+#endif
diff --git a/src/multimedia/alsa/qalsamediadevices.cpp b/src/multimedia/alsa/qalsamediadevices.cpp
new file mode 100644
index 000000000..9466fa0cd
--- /dev/null
+++ b/src/multimedia/alsa/qalsamediadevices.cpp
@@ -0,0 +1,130 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qalsamediadevices_p.h"
+#include "qmediadevices.h"
+#include "qcameradevice_p.h"
+
+#include "private/qalsaaudiosource_p.h"
+#include "private/qalsaaudiosink_p.h"
+#include "private/qalsaaudiodevice_p.h"
+
+#include <alsa/asoundlib.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+struct free_char
+{
+ void operator()(char *c) const { ::free(c); }
+};
+
+using unique_str = std::unique_ptr<char, free_char>;
+
+bool operator==(const unique_str &str, std::string_view sv)
+{
+ return std::string_view{ str.get() } == sv;
+}
+bool operator!=(const unique_str &str, std::string_view sv)
+{
+ return !(str == sv);
+}
+
+} // namespace
+
+QAlsaMediaDevices::QAlsaMediaDevices()
+ : QPlatformMediaDevices()
+{
+}
+
+static QList<QAudioDevice> availableDevices(QAudioDevice::Mode mode)
+{
+ QList<QAudioDevice> devices;
+
+ // Create a list of all current audio devices that support mode
+ void **hints;
+ if (snd_device_name_hint(-1, "pcm", &hints) < 0) {
+ qWarning() << "no alsa devices available";
+ return devices;
+ }
+
+ std::string_view filter = (mode == QAudioDevice::Input) ? "Input" : "Output";
+
+ QAlsaAudioDeviceInfo *sysdefault = nullptr;
+
+ auto makeDeviceInfo = [&filter, mode](void *entry) -> QAlsaAudioDeviceInfo * {
+ unique_str name{ snd_device_name_get_hint(entry, "NAME") };
+ if (name && name != "null") {
+ unique_str descr{ snd_device_name_get_hint(entry, "DESC") };
+ unique_str io{ snd_device_name_get_hint(entry, "IOID") };
+
+ if (descr && (!io || (io == filter))) {
+ auto *infop = new QAlsaAudioDeviceInfo{
+ name.get(),
+ QString::fromUtf8(descr.get()),
+ mode,
+ };
+ return infop;
+ }
+ }
+ return nullptr;
+ };
+
+ bool hasDefault = false;
+ void **n = hints;
+ while (*n != NULL) {
+ QAlsaAudioDeviceInfo *infop = makeDeviceInfo(*n++);
+
+ if (infop) {
+ devices.append(infop->create());
+ if (!hasDefault && infop->id.startsWith("default")) {
+ infop->isDefault = true;
+ hasDefault = true;
+ }
+ if (!sysdefault && infop->id.startsWith("sysdefault"))
+ sysdefault = infop;
+ }
+ }
+
+ if (!hasDefault && sysdefault) {
+ // Make "sysdefault" the default device if there is no "default" device exists
+ sysdefault->isDefault = true;
+ hasDefault = true;
+ }
+ if (!hasDefault && devices.size() > 0) {
+ // forcefully declare the first device as "default"
+ QAlsaAudioDeviceInfo *infop = makeDeviceInfo(hints[0]);
+ if (infop) {
+ infop->isDefault = true;
+ devices.prepend(infop->create());
+ }
+ }
+
+ snd_device_name_free_hint(hints);
+ return devices;
+}
+
+QList<QAudioDevice> QAlsaMediaDevices::audioInputs() const
+{
+ return availableDevices(QAudioDevice::Input);
+}
+
+QList<QAudioDevice> QAlsaMediaDevices::audioOutputs() const
+{
+ return availableDevices(QAudioDevice::Output);
+}
+
+QPlatformAudioSource *QAlsaMediaDevices::createAudioSource(const QAudioDevice &deviceInfo,
+ QObject *parent)
+{
+ return new QAlsaAudioSource(deviceInfo.id(), parent);
+}
+
+QPlatformAudioSink *QAlsaMediaDevices::createAudioSink(const QAudioDevice &deviceInfo,
+ QObject *parent)
+{
+ return new QAlsaAudioSink(deviceInfo.id(), parent);
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/alsa/qalsamediadevices_p.h b/src/multimedia/alsa/qalsamediadevices_p.h
new file mode 100644
index 000000000..d9fbb7c97
--- /dev/null
+++ b/src/multimedia/alsa/qalsamediadevices_p.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QALSAMEDIADEVICES_H
+#define QALSAMEDIADEVICES_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/qplatformmediadevices_p.h>
+#include <qset.h>
+#include <qaudio.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAlsaEngine;
+
+class QAlsaMediaDevices : public QPlatformMediaDevices
+{
+public:
+ QAlsaMediaDevices();
+
+ QList<QAudioDevice> audioInputs() const override;
+ QList<QAudioDevice> audioOutputs() const override;
+ QPlatformAudioSource *createAudioSource(const QAudioDevice &deviceInfo,
+ QObject *parent) override;
+ QPlatformAudioSink *createAudioSink(const QAudioDevice &deviceInfo,
+ QObject *parent) override;
+};
+
+QT_END_NAMESPACE
+
+#endif