From 85f4b8177ca10e7e16ccc3065370589630400f2b Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 12 Jun 2014 18:48:15 +0200 Subject: PulseAudio: make plugin more robust. Handle more thoroughly error cases, such as when the PulseAudio daemon does not respond or terminates while QAudioInput/QAudioOutput is active, in which cases it used to crash or hang. We now correctly emit the error signal and change the state when errors occur. Task-number: QTBUG-29742 Change-Id: I173d35aece60d96e578785e1522cf78b24dcb8b8 Reviewed-by: Christian Stromme --- src/plugins/pulseaudio/qaudioinput_pulse.cpp | 222 ++++++++++++++------------ src/plugins/pulseaudio/qaudioinput_pulse.h | 6 +- src/plugins/pulseaudio/qaudiooutput_pulse.cpp | 194 ++++++++++++---------- src/plugins/pulseaudio/qaudiooutput_pulse.h | 6 +- src/plugins/pulseaudio/qpulseaudioengine.cpp | 139 ++++++++-------- src/plugins/pulseaudio/qpulseaudioengine.h | 33 +++- 6 files changed, 347 insertions(+), 253 deletions(-) diff --git a/src/plugins/pulseaudio/qaudioinput_pulse.cpp b/src/plugins/pulseaudio/qaudioinput_pulse.cpp index 83075beda..89dc08612 100644 --- a/src/plugins/pulseaudio/qaudioinput_pulse.cpp +++ b/src/plugins/pulseaudio/qaudioinput_pulse.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Toolkit. @@ -54,10 +54,6 @@ QT_BEGIN_NAMESPACE const int PeriodTimeMs = 50; -// Map from void* (for userdata) to QPulseAudioInput instance -// protected by pulse mainloop lock -QMap QPulseAudioInput::s_inputsMap; - static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata) { Q_UNUSED(userdata); @@ -136,8 +132,8 @@ void QPulseAudioInput::sourceInfoCallback(pa_context *context, const pa_source_i Q_UNUSED(eol); Q_ASSERT(userdata); - QPulseAudioInput *that = QPulseAudioInput::s_inputsMap.value(userdata); - if (that && i) { + if (i) { + QPulseAudioInput *that = reinterpret_cast(userdata); that->m_volume = pa_sw_volume_to_linear(pa_cvolume_avg(&i->volume)); } } @@ -149,12 +145,11 @@ void QPulseAudioInput::inputVolumeCallback(pa_context *context, int success, voi if (!success) qWarning() << "QAudioInput: failed to set input volume"; - QPulseAudioInput *that = QPulseAudioInput::s_inputsMap.value(userdata); + QPulseAudioInput *that = reinterpret_cast(userdata); // Regardless of success or failure, we update the volume property - if (that && that->m_stream) { + if (that->m_stream) pa_context_get_source_info_by_index(context, pa_stream_get_device_index(that->m_stream), sourceInfoCallback, userdata); - } } QPulseAudioInput::QPulseAudioInput(const QByteArray &device) @@ -175,31 +170,39 @@ QPulseAudioInput::QPulseAudioInput(const QByteArray &device) { m_timer = new QTimer(this); connect(m_timer, SIGNAL(timeout()), SLOT(userFeed())); - - QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); - s_inputsMap.insert(this, this); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); } QPulseAudioInput::~QPulseAudioInput() { - QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); - s_inputsMap.remove(this); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); - close(); disconnect(m_timer, SIGNAL(timeout())); QCoreApplication::processEvents(); delete m_timer; } +void QPulseAudioInput::setError(QAudio::Error error) +{ + if (m_errorState == error) + return; + + m_errorState = error; + emit errorChanged(error); +} + QAudio::Error QPulseAudioInput::error() const { return m_errorState; } +void QPulseAudioInput::setState(QAudio::State state) +{ + if (m_deviceState == state) + return; + + m_deviceState = state; + emit stateChanged(state); +} + QAudio::State QPulseAudioInput::state() const { return m_deviceState; @@ -218,41 +221,45 @@ QAudioFormat QPulseAudioInput::format() const void QPulseAudioInput::start(QIODevice *device) { - if (m_deviceState != QAudio::StoppedState) - close(); + setState(QAudio::StoppedState); + setError(QAudio::NoError); - if (!m_pullMode && m_audioSource) + if (!m_pullMode && m_audioSource) { delete m_audioSource; + m_audioSource = 0; + } - m_pullMode = true; - m_audioSource = device; - - m_deviceState = QAudio::ActiveState; + close(); if (!open()) return; - emit stateChanged(m_deviceState); + m_pullMode = true; + m_audioSource = device; + + setState(QAudio::ActiveState); } QIODevice *QPulseAudioInput::start() { - if (m_deviceState != QAudio::StoppedState) - close(); + setState(QAudio::StoppedState); + setError(QAudio::NoError); - if (!m_pullMode && m_audioSource) + if (!m_pullMode && m_audioSource) { delete m_audioSource; + m_audioSource = 0; + } + + close(); + + if (!open()) + return Q_NULLPTR; m_pullMode = false; m_audioSource = new InputPrivate(this); m_audioSource->open(QIODevice::ReadOnly | QIODevice::Unbuffered); - m_deviceState = QAudio::IdleState; - - if (!open()) - return 0; - - emit stateChanged(m_deviceState); + setState(QAudio::IdleState); return m_audioSource; } @@ -262,40 +269,43 @@ void QPulseAudioInput::stop() if (m_deviceState == QAudio::StoppedState) return; - m_errorState = QAudio::NoError; - m_deviceState = QAudio::StoppedState; - close(); - emit stateChanged(m_deviceState); + + setError(QAudio::NoError); + setState(QAudio::StoppedState); } bool QPulseAudioInput::open() { if (m_opened) - return false; + return true; -#ifdef DEBUG_PULSE -// QTime now(QTime::currentTime()); -// qDebug()<context() || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) { + setError(QAudio::FatalError); + setState(QAudio::StoppedState); + return false; + } pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(m_format); if (!pa_sample_spec_valid(&spec)) { - m_errorState = QAudio::OpenError; - m_deviceState = QAudio::StoppedState; - emit stateChanged(m_deviceState); + setError(QAudio::OpenError); + setState(QAudio::StoppedState); return false; } m_spec = spec; +#ifdef DEBUG_PULSE +// QTime now(QTime::currentTime()); +// qDebug()<mainloop()); + pulseEngine->lock(); pa_channel_map channel_map; pa_channel_map_init_extend(&channel_map, spec.channels, PA_CHANNEL_MAP_DEFAULT); - if (!pa_channel_map_compatible(&channel_map, &spec)) { + if (!pa_channel_map_compatible(&channel_map, &spec)) qWarning() << "Channel map doesn't match sample specification!"; - } m_stream = pa_stream_new(pulseEngine->context(), m_streamName.constData(), &spec, &channel_map); @@ -338,13 +346,16 @@ bool QPulseAudioInput::open() if (pa_stream_connect_record(m_stream, m_device.data(), &buffer_attr, (pa_stream_flags_t)flags) < 0) { qWarning() << "pa_stream_connect_record() failed!"; - m_errorState = QAudio::FatalError; + pa_stream_unref(m_stream); + m_stream = 0; + pulseEngine->unlock(); + setError(QAudio::OpenError); + setState(QAudio::StoppedState); return false; } - while (pa_stream_get_state(m_stream) != PA_STREAM_READY) { + while (pa_stream_get_state(m_stream) != PA_STREAM_READY) pa_threaded_mainloop_wait(pulseEngine->mainloop()); - } const pa_buffer_attr *actualBufferAttr = pa_stream_get_buffer_attr(m_stream); m_periodSize = actualBufferAttr->fragsize; @@ -354,12 +365,16 @@ bool QPulseAudioInput::open() setPulseVolume(); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); + + connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioInput::onPulseContextFailed); m_opened = true; m_timer->start(m_periodTime); - m_errorState = QAudio::NoError; + m_clockStamp.restart(); + m_timeStamp.restart(); + m_elapsedTimeOffset = 0; m_totalTimeValue = 0; return true; @@ -367,21 +382,30 @@ bool QPulseAudioInput::open() void QPulseAudioInput::close() { + if (!m_opened) + return; + m_timer->stop(); + QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); + if (m_stream) { - QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); + pa_stream_set_state_callback(m_stream, 0, 0); pa_stream_set_read_callback(m_stream, 0, 0); + pa_stream_set_underflow_callback(m_stream, 0, 0); + pa_stream_set_overflow_callback(m_stream, 0, 0); pa_stream_disconnect(m_stream); pa_stream_unref(m_stream); m_stream = 0; - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); } + disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioInput::onPulseContextFailed); + if (!m_pullMode && m_audioSource) { delete m_audioSource; m_audioSource = 0; @@ -393,6 +417,7 @@ void QPulseAudioInput::close() void QPulseAudioInput::setPulseVolume() { QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); + Q_ASSERT(pulseEngine->context() != 0); pa_cvolume cvolume; @@ -434,11 +459,8 @@ qint64 QPulseAudioInput::read(char *data, qint64 len) { m_bytesAvailable = checkBytesReady(); - if (m_deviceState != QAudio::ActiveState) { - m_errorState = QAudio::NoError; - m_deviceState = QAudio::ActiveState; - emit stateChanged(m_deviceState); - } + setError(QAudio::NoError); + setState(QAudio::ActiveState); int readBytes = 0; @@ -463,7 +485,8 @@ qint64 QPulseAudioInput::read(char *data, qint64 len) #endif QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); + const void *audioBuffer; // Second and third parameters (audioBuffer and length) to pa_stream_peek are output parameters, @@ -471,7 +494,7 @@ qint64 QPulseAudioInput::read(char *data, qint64 len) // and the length is set to the length of this data. if (pa_stream_peek(m_stream, &audioBuffer, &readLength) < 0) { qWarning() << QString("pa_stream_peek() failed: %1").arg(pa_strerror(pa_context_errno(pa_stream_get_context(m_stream)))); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); return 0; } @@ -480,11 +503,10 @@ qint64 QPulseAudioInput::read(char *data, qint64 len) actualLength = m_audioSource->write(static_cast(audioBuffer), readLength); if (actualLength < qint64(readLength)) { - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); - m_errorState = QAudio::UnderrunError; - m_deviceState = QAudio::IdleState; - emit stateChanged(m_deviceState); + setError(QAudio::UnderrunError); + setState(QAudio::IdleState); return actualLength; } @@ -509,7 +531,7 @@ qint64 QPulseAudioInput::read(char *data, qint64 len) readBytes += actualLength; pa_stream_drop(m_stream); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); if (!m_pullMode && readBytes >= len) break; @@ -534,22 +556,18 @@ void QPulseAudioInput::resume() QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); pa_operation *operation; - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); operation = pa_stream_cork(m_stream, 0, inputStreamSuccessCallback, 0); - - while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) - pa_threaded_mainloop_wait(pulseEngine->mainloop()); - + pulseEngine->wait(operation); pa_operation_unref(operation); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); m_timer->start(m_periodTime); - m_deviceState = QAudio::ActiveState; - - emit stateChanged(m_deviceState); + setState(QAudio::ActiveState); + setError(QAudio::NoError); } } @@ -557,23 +575,23 @@ void QPulseAudioInput::setVolume(qreal vol) { if (vol >= 0.0 && vol <= 1.0) { QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); if (!qFuzzyCompare(m_volume, vol)) { m_volume = vol; if (m_opened) { setPulseVolume(); } } - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); } } qreal QPulseAudioInput::volume() const { QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); qreal vol = m_volume; - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); return vol; } @@ -614,23 +632,21 @@ qint64 QPulseAudioInput::processedUSecs() const void QPulseAudioInput::suspend() { if (m_deviceState == QAudio::ActiveState) { + setError(QAudio::NoError); + setState(QAudio::SuspendedState); + m_timer->stop(); - m_deviceState = QAudio::SuspendedState; - emit stateChanged(m_deviceState); QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); pa_operation *operation; - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); operation = pa_stream_cork(m_stream, 1, inputStreamSuccessCallback, 0); - - while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) - pa_threaded_mainloop_wait(pulseEngine->mainloop()); - + pulseEngine->wait(operation); pa_operation_unref(operation); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); } } @@ -685,6 +701,14 @@ void QPulseAudioInput::reset() m_bytesAvailable = 0; } +void QPulseAudioInput::onPulseContextFailed() +{ + close(); + + setError(QAudio::FatalError); + setState(QAudio::StoppedState); +} + InputPrivate::InputPrivate(QPulseAudioInput *audio) { m_audioDevice = qobject_cast(audio); diff --git a/src/plugins/pulseaudio/qaudioinput_pulse.h b/src/plugins/pulseaudio/qaudioinput_pulse.h index bb72628f8..e2c46c8e6 100644 --- a/src/plugins/pulseaudio/qaudioinput_pulse.h +++ b/src/plugins/pulseaudio/qaudioinput_pulse.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Toolkit. @@ -112,8 +112,12 @@ public: private slots: void userFeed(); bool deviceReady(); + void onPulseContextFailed(); private: + void setState(QAudio::State state); + void setError(QAudio::Error error); + int checkBytesReady(); bool open(); void close(); diff --git a/src/plugins/pulseaudio/qaudiooutput_pulse.cpp b/src/plugins/pulseaudio/qaudiooutput_pulse.cpp index 2b482ba40..64a080663 100644 --- a/src/plugins/pulseaudio/qaudiooutput_pulse.cpp +++ b/src/plugins/pulseaudio/qaudiooutput_pulse.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Toolkit. @@ -170,11 +170,29 @@ QPulseAudioOutput::~QPulseAudioOutput() 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; @@ -183,19 +201,15 @@ QAudio::State QPulseAudioOutput::state() const void QPulseAudioOutput::streamUnderflowCallback() { if (m_deviceState != QAudio::IdleState && !m_resuming) { - m_errorState = QAudio::UnderrunError; - emit errorChanged(m_errorState); - m_deviceState = QAudio::IdleState; - emit stateChanged(m_deviceState); + setError(QAudio::UnderrunError); + setState(QAudio::IdleState); } } void QPulseAudioOutput::start(QIODevice *device) { - if (m_deviceState != QAudio::StoppedState) - m_deviceState = QAudio::StoppedState; - - m_errorState = QAudio::NoError; + setState(QAudio::StoppedState); + setError(QAudio::NoError); // Handle change of mode if (m_audioSource && !m_pullMode) { @@ -205,22 +219,19 @@ void QPulseAudioOutput::start(QIODevice *device) close(); + if (!open()) + return; + m_pullMode = true; m_audioSource = device; - m_deviceState = QAudio::ActiveState; - - open(); - - emit stateChanged(m_deviceState); + setState(QAudio::ActiveState); } QIODevice *QPulseAudioOutput::start() { - if (m_deviceState != QAudio::StoppedState) - m_deviceState = QAudio::StoppedState; - - m_errorState = QAudio::NoError; + setState(QAudio::StoppedState); + setError(QAudio::NoError); // Handle change of mode if (m_audioSource && !m_pullMode) { @@ -230,15 +241,14 @@ QIODevice *QPulseAudioOutput::start() close(); + if (!open()) + return Q_NULLPTR; + m_audioSource = new OutputPrivate(this); m_audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered); m_pullMode = false; - m_deviceState = QAudio::IdleState; - - open(); - - emit stateChanged(m_deviceState); + setState(QAudio::IdleState); return m_audioSource; } @@ -246,33 +256,38 @@ QIODevice *QPulseAudioOutput::start() 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); return false; + } pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(m_format); if (!pa_sample_spec_valid(&spec)) { - m_errorState = QAudio::OpenError; - m_deviceState = QAudio::StoppedState; + setError(QAudio::OpenError); + setState(QAudio::StoppedState); return false; } m_spec = spec; m_totalTimeValue = 0; - m_elapsedTimeOffset = 0; - m_timeStamp.restart(); 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); + qDebug() << "Format: " << QPulseAudioInternal::sampleFormatToQString(spec.format); + qDebug() << "Rate: " << spec.rate; + qDebug() << "Channels: " << spec.channels; + qDebug() << "Frame size: " << pa_frame_size(&spec); #endif - QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); qint64 bytesPerSecond = m_format.sampleRate() * m_format.channelCount() * m_format.sampleSize() / 8; @@ -280,7 +295,7 @@ bool QPulseAudioOutput::open() if (!m_category.isNull()) pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, m_category.toLatin1().constData()); - m_stream = pa_stream_new_with_proplist(pulseEngine->context(), m_streamName.constData(), &spec, 0, propList); + m_stream = pa_stream_new_with_proplist(pulseEngine->context(), m_streamName.constData(), &m_spec, 0, propList); pa_proplist_free(propList); pa_stream_set_state_callback(m_stream, outputStreamStateCallback, this); @@ -312,15 +327,20 @@ bool QPulseAudioOutput::open() if (pa_stream_connect_playback(m_stream, m_device.data(), (m_bufferSize > 0) ? &requestedBuffer : NULL, (pa_stream_flags_t)0, &m_chVolume, NULL) < 0) { qWarning() << "pa_stream_connect_playback() failed!"; + pa_stream_unref(m_stream); + m_stream = 0; + pulseEngine->unlock(); + setError(QAudio::OpenError); + setState(QAudio::StoppedState); return false; } - while (pa_stream_get_state(m_stream) != PA_STREAM_READY) { + 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, &spec); + 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]; @@ -333,9 +353,12 @@ bool QPulseAudioOutput::open() qDebug() << "\tFragment size: " << buffer->fragsize; #endif - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); + + connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioOutput::onPulseContextFailed); m_opened = true; + m_tickTimer->start(m_periodTime); m_elapsedTimeOffset = 0; @@ -347,28 +370,35 @@ bool QPulseAudioOutput::open() void QPulseAudioOutput::close() { + if (!m_opened) + return; + m_tickTimer->stop(); + QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); + if (m_stream) { - QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); - pa_stream_set_write_callback(m_stream, NULL, NULL); + 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) { - qWarning() << QString("pa_stream_drain(): %1").arg(pa_strerror(pa_context_errno(pa_stream_get_context(m_stream)))); - } else { + if (o) pa_operation_unref(o); - } pa_stream_disconnect(m_stream); pa_stream_unref(m_stream); m_stream = NULL; - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); } + disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioOutput::onPulseContextFailed); + if (!m_pullMode && m_audioSource) { delete m_audioSource; m_audioSource = 0; @@ -430,17 +460,14 @@ qint64 QPulseAudioOutput::write(const char *data, qint64 len) { QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); len = qMin(len, static_cast(pa_stream_writable_size(m_stream))); pa_stream_write(m_stream, data, len, 0, 0, PA_SEEK_RELATIVE); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); m_totalTimeValue += len; - m_errorState = QAudio::NoError; - if (m_deviceState != QAudio::ActiveState) { - m_deviceState = QAudio::ActiveState; - emit stateChanged(m_deviceState); - } + setError(QAudio::NoError); + setState(QAudio::ActiveState); return len; } @@ -450,10 +477,10 @@ void QPulseAudioOutput::stop() if (m_deviceState == QAudio::StoppedState) return; - m_errorState = QAudio::NoError; - m_deviceState = QAudio::StoppedState; close(); - emit stateChanged(m_deviceState); + + setError(QAudio::NoError); + setState(QAudio::StoppedState); } int QPulseAudioOutput::bytesFree() const @@ -462,9 +489,9 @@ int QPulseAudioOutput::bytesFree() const return 0; QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); int writableSize = pa_stream_writable_size(m_stream); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); return writableSize; } @@ -509,30 +536,22 @@ void QPulseAudioOutput::resume() QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); pa_operation *operation = pa_stream_cork(m_stream, 0, outputStreamSuccessCallback, NULL); - - while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) - pa_threaded_mainloop_wait(pulseEngine->mainloop()); - + pulseEngine->wait(operation); pa_operation_unref(operation); operation = pa_stream_trigger(m_stream, outputStreamSuccessCallback, NULL); - - while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) - pa_threaded_mainloop_wait(pulseEngine->mainloop()); - + pulseEngine->wait(operation); pa_operation_unref(operation); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); - m_deviceState = QAudio::ActiveState; - - m_errorState = QAudio::NoError; m_tickTimer->start(m_periodTime); - emit stateChanged(m_deviceState); + setState(QAudio::ActiveState); + setError(QAudio::NoError); } } @@ -549,24 +568,21 @@ QAudioFormat QPulseAudioOutput::format() const void QPulseAudioOutput::suspend() { if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState) { + setError(QAudio::NoError); + setState(QAudio::SuspendedState); + m_tickTimer->stop(); - m_deviceState = QAudio::SuspendedState; - m_errorState = QAudio::NoError; - emit stateChanged(m_deviceState); QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); pa_operation *operation; - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); operation = pa_stream_cork(m_stream, 1, outputStreamSuccessCallback, NULL); - - while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) - pa_threaded_mainloop_wait(pulseEngine->mainloop()); - + pulseEngine->wait(operation); pa_operation_unref(operation); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); } } @@ -601,8 +617,8 @@ qint64 OutputPrivate::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)) { + 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) @@ -623,7 +639,7 @@ void QPulseAudioOutput::setVolume(qreal vol) m_volume = vol; if (m_opened) { QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance(); - pa_threaded_mainloop_lock(pulseEngine->mainloop()); + pulseEngine->lock(); pa_volume_t paVolume; if (qFuzzyCompare(vol, 0.0)) { pa_cvolume_mute(&m_chVolume, m_spec.channels); @@ -641,7 +657,7 @@ void QPulseAudioOutput::setVolume(qreal vol) qWarning()<<"QAudioOutput: Failed to set volume"; else pa_operation_unref(op); - pa_threaded_mainloop_unlock(pulseEngine->mainloop()); + pulseEngine->unlock(); } } } @@ -664,6 +680,14 @@ 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" diff --git a/src/plugins/pulseaudio/qaudiooutput_pulse.h b/src/plugins/pulseaudio/qaudiooutput_pulse.h index 5954b8975..fea151ba2 100644 --- a/src/plugins/pulseaudio/qaudiooutput_pulse.h +++ b/src/plugins/pulseaudio/qaudiooutput_pulse.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Toolkit. @@ -105,12 +105,16 @@ public: void streamUnderflowCallback(); private: + void setState(QAudio::State state); + void setError(QAudio::Error error); + bool open(); void close(); qint64 write(const char *data, qint64 len); private Q_SLOTS: void userFeed(); + void onPulseContextFailed(); private: QByteArray m_device; diff --git a/src/plugins/pulseaudio/qpulseaudioengine.cpp b/src/plugins/pulseaudio/qpulseaudioengine.cpp index b7a3e66b7..05c8be89e 100644 --- a/src/plugins/pulseaudio/qpulseaudioengine.cpp +++ b/src/plugins/pulseaudio/qpulseaudioengine.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Toolkit. @@ -170,15 +170,17 @@ static void contextStateCallbackInit(pa_context *context, void *userdata) pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); } -static void contextStateCallback(pa_context *context, void *userdata) +static void contextStateCallback(pa_context *c, void *userdata) { - Q_UNUSED(userdata); - Q_UNUSED(context); + QPulseAudioEngine *self = reinterpret_cast(userdata); + pa_context_state_t state = pa_context_get_state(c); #ifdef DEBUG_PULSE - pa_context_state_t state = pa_context_get_state(context); qDebug() << QPulseAudioInternal::stateToQString(state); #endif + + if (state == PA_CONTEXT_FAILED) + QMetaObject::invokeMethod(self, "onContextFailed", Qt::QueuedConnection); } Q_GLOBAL_STATIC(QPulseAudioEngine, pulseEngine); @@ -187,40 +189,59 @@ QPulseAudioEngine::QPulseAudioEngine(QObject *parent) : QObject(parent) , m_mainLoopApi(0) , m_context(0) + , m_prepared(false) +{ + prepare(); +} + +QPulseAudioEngine::~QPulseAudioEngine() +{ + if (m_prepared) + release(); +} +void QPulseAudioEngine::prepare() { bool keepGoing = true; bool ok = true; m_mainLoop = pa_threaded_mainloop_new(); if (m_mainLoop == 0) { - qWarning("Unable to create pulseaudio mainloop"); + qWarning("PulseAudioService: unable to create pulseaudio mainloop"); return; } if (pa_threaded_mainloop_start(m_mainLoop) != 0) { - qWarning("Unable to start pulseaudio mainloop"); + qWarning("PulseAudioService: unable to start pulseaudio mainloop"); pa_threaded_mainloop_free(m_mainLoop); + m_mainLoop = 0; return; } m_mainLoopApi = pa_threaded_mainloop_get_api(m_mainLoop); - pa_threaded_mainloop_lock(m_mainLoop); + lock(); - m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtmPulseContext:%1")).arg(::getpid()).toLatin1().constData()); - pa_context_set_state_callback(m_context, contextStateCallbackInit, this); + m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtPulseAudio:%1")).arg(::getpid()).toLatin1().constData()); - if (!m_context) { - qWarning("Unable to create new pulseaudio context"); + if (m_context == 0) { + qWarning("PulseAudioService: Unable to create new pulseaudio context"); + pa_threaded_mainloop_unlock(m_mainLoop); pa_threaded_mainloop_free(m_mainLoop); + m_mainLoop = 0; + onContextFailed(); return; } - if (pa_context_connect(m_context, NULL, (pa_context_flags_t)0, NULL) < 0) { - qWarning("Unable to create a connection to the pulseaudio context"); + pa_context_set_state_callback(m_context, contextStateCallbackInit, this); + + if (pa_context_connect(m_context, 0, (pa_context_flags_t)0, 0) < 0) { + qWarning("PulseAudioService: pa_context_connect() failed"); pa_context_unref(m_context); + pa_threaded_mainloop_unlock(m_mainLoop); pa_threaded_mainloop_free(m_mainLoop); + m_mainLoop = 0; + m_context = 0; return; } @@ -241,47 +262,49 @@ QPulseAudioEngine::QPulseAudioEngine(QObject *parent) break; case PA_CONTEXT_TERMINATED: - qCritical("Context terminated."); + qCritical("PulseAudioService: Context terminated."); keepGoing = false; ok = false; break; case PA_CONTEXT_FAILED: default: - qCritical() << QString("Connection failure: %1").arg(pa_strerror(pa_context_errno(m_context))); + qCritical() << QString("PulseAudioService: Connection failure: %1").arg(pa_strerror(pa_context_errno(m_context))); keepGoing = false; ok = false; } - if (keepGoing) { + if (keepGoing) pa_threaded_mainloop_wait(m_mainLoop); - } } if (ok) { pa_context_set_state_callback(m_context, contextStateCallback, this); } else { - if (m_context) { - pa_context_unref(m_context); - m_context = 0; - } + pa_context_unref(m_context); + m_context = 0; } - pa_threaded_mainloop_unlock(m_mainLoop); + unlock(); if (ok) { - serverInfo(); - sinks(); - sources(); + updateDevices(); + m_prepared = true; + } else { + pa_threaded_mainloop_free(m_mainLoop); + m_mainLoop = 0; + onContextFailed(); } } -QPulseAudioEngine::~QPulseAudioEngine() +void QPulseAudioEngine::release() { + if (!m_prepared) + return; + if (m_context) { - pa_threaded_mainloop_lock(m_mainLoop); pa_context_disconnect(m_context); - pa_threaded_mainloop_unlock(m_mainLoop); + pa_context_unref(m_context); m_context = 0; } @@ -290,62 +313,52 @@ QPulseAudioEngine::~QPulseAudioEngine() pa_threaded_mainloop_free(m_mainLoop); m_mainLoop = 0; } + + m_prepared = false; } -void QPulseAudioEngine::serverInfo() +void QPulseAudioEngine::updateDevices() { - pa_operation *operation; - - pa_threaded_mainloop_lock(m_mainLoop); - - operation = pa_context_get_server_info(m_context, serverInfoCallback, this); + lock(); + // Get default input and output devices + pa_operation *operation = pa_context_get_server_info(m_context, serverInfoCallback, this); while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(m_mainLoop); - pa_operation_unref(operation); - pa_threaded_mainloop_unlock(m_mainLoop); -} - -void QPulseAudioEngine::sinks() -{ - pa_operation *operation; - - pa_threaded_mainloop_lock(m_mainLoop); - + // Get output devices operation = pa_context_get_sink_info_list(m_context, sinkInfoCallback, this); - while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(m_mainLoop); + pa_operation_unref(operation); + // Get input devices + operation = pa_context_get_source_info_list(m_context, sourceInfoCallback, this); + while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(m_mainLoop); pa_operation_unref(operation); - pa_threaded_mainloop_unlock(m_mainLoop); + unlock(); - // Swap the default sink to index 0 + // Swap the default output to index 0 m_sinks.removeOne(m_defaultSink); m_sinks.prepend(m_defaultSink); + + // Swap the default input to index 0 + m_sources.removeOne(m_defaultSource); + m_sources.prepend(m_defaultSource); } -void QPulseAudioEngine::sources() +void QPulseAudioEngine::onContextFailed() { - pa_operation *operation; - - pa_threaded_mainloop_lock(m_mainLoop); - - operation = pa_context_get_source_info_list(m_context, sourceInfoCallback, this); + // Give a chance to the connected slots to still use the Pulse main loop before releasing it. + emit contextFailed(); - while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) - pa_threaded_mainloop_wait(m_mainLoop); - - pa_operation_unref(operation); + release(); - pa_threaded_mainloop_unlock(m_mainLoop); - - // Swap the default source to index 0 - m_sources.removeOne(m_defaultSource); - m_sources.prepend(m_defaultSource); + // Try to reconnect later + QTimer::singleShot(3000, this, SLOT(prepare())); } QPulseAudioEngine *QPulseAudioEngine::instance() diff --git a/src/plugins/pulseaudio/qpulseaudioengine.h b/src/plugins/pulseaudio/qpulseaudioengine.h index 1c6c2aaf4..04591d035 100644 --- a/src/plugins/pulseaudio/qpulseaudioengine.h +++ b/src/plugins/pulseaudio/qpulseaudioengine.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Toolkit. @@ -74,12 +74,36 @@ public: pa_threaded_mainloop *mainloop() { return m_mainLoop; } pa_context *context() { return m_context; } + inline void lock() + { + if (m_mainLoop) + pa_threaded_mainloop_lock(m_mainLoop); + } + + inline void unlock() + { + if (m_mainLoop) + pa_threaded_mainloop_unlock(m_mainLoop); + } + + inline void wait(pa_operation *op) + { + while (m_mainLoop && pa_operation_get_state(op) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(m_mainLoop); + } + QList availableDevices(QAudio::Mode mode) const; +Q_SIGNALS: + void contextFailed(); + +private Q_SLOTS: + void prepare(); + void onContextFailed(); + private: - void serverInfo(); - void sinks(); - void sources(); + void updateDevices(); + void release(); public: QList m_sinks; @@ -93,6 +117,7 @@ private: pa_mainloop_api *m_mainLoopApi; pa_threaded_mainloop *m_mainLoop; pa_context *m_context; + bool m_prepared; }; QT_END_NAMESPACE -- cgit v1.2.1