diff options
Diffstat (limited to 'src/plugins/pulseaudio/qaudiooutput_pulse.cpp')
-rw-r--r-- | src/plugins/pulseaudio/qaudiooutput_pulse.cpp | 739 |
1 files changed, 0 insertions, 739 deletions
diff --git a/src/plugins/pulseaudio/qaudiooutput_pulse.cpp b/src/plugins/pulseaudio/qaudiooutput_pulse.cpp deleted file mode 100644 index 708c3e0e3..000000000 --- a/src/plugins/pulseaudio/qaudiooutput_pulse.cpp +++ /dev/null @@ -1,739 +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$ -** -****************************************************************************/ - -#include <QtCore/qcoreapplication.h> -#include <QtCore/qdebug.h> -#include <QtCore/qmath.h> -#include <private/qaudiohelpers_p.h> - -#include "qaudiooutput_pulse.h" -#include "qaudiodeviceinfo_pulse.h" -#include "qpulseaudioengine.h" -#include "qpulsehelpers.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.cpp" |