diff options
Diffstat (limited to 'src/multimedia/platform/alsa/qalsaaudiosink.cpp')
-rw-r--r-- | src/multimedia/platform/alsa/qalsaaudiosink.cpp | 749 |
1 files changed, 0 insertions, 749 deletions
diff --git a/src/multimedia/platform/alsa/qalsaaudiosink.cpp b/src/multimedia/platform/alsa/qalsaaudiosink.cpp deleted file mode 100644 index 7157e9508..000000000 --- a/src/multimedia/platform/alsa/qalsaaudiosink.cpp +++ /dev/null @@ -1,749 +0,0 @@ -/**************************************************************************** -** -** 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 "qalsaaudiosink_p.h" -#include "qalsaaudiodevice_p.h" -#include <QLoggingCategory> - -QT_BEGIN_NAMESPACE - -Q_LOGGING_CATEGORY(lcAlsaOutput, "qt.multimedia.alsa.output") -//#define DEBUG_AUDIO 1 - -QAlsaAudioSink::QAlsaAudioSink(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; - 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())); -} - -QAlsaAudioSink::~QAlsaAudioSink() -{ - close(); - disconnect(timer, SIGNAL(timeout())); - 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_LE; - else - pcmformat = SND_PCM_FORMAT_S16_BE; - break; - case QAudioFormat::Int32: - if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian) - pcmformat = SND_PCM_FORMAT_S32_LE; - else - pcmformat = SND_PCM_FORMAT_S32_BE; - break; - case QAudioFormat::Float: - if constexpr (QSysInfo::ByteOrder == QSysInfo::BigEndian) - pcmformat = SND_PCM_FORMAT_FLOAT_LE; - else - pcmformat = SND_PCM_FORMAT_FLOAT_BE; - 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; - - 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 - timeStamp.restart(); - 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); - - timeStamp.restart(); - 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 = pullMode ? QAudio::ActiveState : QAudio::IdleState; - - errorState = QAudio::NoError; - timer->start(period_time/1000); - emit stateChanged(deviceState); - } -} - -void QAlsaAudioSink::setFormat(const QAudioFormat& fmt) -{ - if (deviceState == QAudio::StoppedState) - settings = fmt; -} - -QAudioFormat QAlsaAudioSink::format() const -{ - return settings; -} - -void QAlsaAudioSink::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 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 - 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; - - 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" |