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 <christian.stromme@digia.com>
This commit is contained in:
Yoann Lopes
2014-06-12 18:48:15 +02:00
parent 8da61153c9
commit 85f4b8177c
6 changed files with 353 additions and 259 deletions

View File

@@ -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<QPulseAudioEngine*>(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,64 +313,54 @@ 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);
pa_threaded_mainloop_unlock(m_mainLoop);
// 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);
// Swap the default sink to index 0
unlock();
// Swap the default output to index 0
m_sinks.removeOne(m_defaultSink);
m_sinks.prepend(m_defaultSink);
}
void QPulseAudioEngine::sources()
{
pa_operation *operation;
pa_threaded_mainloop_lock(m_mainLoop);
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);
// Swap the default source to index 0
// Swap the default input to index 0
m_sources.removeOne(m_defaultSource);
m_sources.prepend(m_defaultSource);
}
void QPulseAudioEngine::onContextFailed()
{
// Give a chance to the connected slots to still use the Pulse main loop before releasing it.
emit contextFailed();
release();
// Try to reconnect later
QTimer::singleShot(3000, this, SLOT(prepare()));
}
QPulseAudioEngine *QPulseAudioEngine::instance()
{
return pulseEngine();