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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user