From 970128036ab6850ba292465806afe5b799851ac1 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 3 May 2016 12:47:57 +0200 Subject: [PATCH] PulseAudio: keep device list up to date. The list of devices was cached on startup and was never updated when a device was plugged, unplugged or if the default device changed. We now use the event subscription mechanism in PulseAudio to get notified of these changes and update the list accordingly. Change-Id: I5fe1c81494702aa9d38b91009621629dc9606323 Reviewed-by: Karim Pinter Reviewed-by: Christian Stromme --- src/plugins/pulseaudio/qpulseaudioengine.cpp | 118 +++++++++++++++---- src/plugins/pulseaudio/qpulseaudioengine.h | 9 +- 2 files changed, 103 insertions(+), 24 deletions(-) diff --git a/src/plugins/pulseaudio/qpulseaudioengine.cpp b/src/plugins/pulseaudio/qpulseaudioengine.cpp index 9b9da6b6..e9510fd6 100644 --- a/src/plugins/pulseaudio/qpulseaudioengine.cpp +++ b/src/plugins/pulseaudio/qpulseaudioengine.cpp @@ -75,8 +75,10 @@ static void serverInfoCallback(pa_context *context, const pa_server_info *info, #endif QPulseAudioEngine *pulseEngine = static_cast(userdata); + pulseEngine->m_serverLock.lockForWrite(); pulseEngine->m_defaultSink = info->default_sink_name; pulseEngine->m_defaultSource = info->default_source_name; + pulseEngine->m_serverLock.unlock(); pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); } @@ -84,11 +86,6 @@ static void serverInfoCallback(pa_context *context, const pa_server_info *info, static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int isLast, void *userdata) { QPulseAudioEngine *pulseEngine = static_cast(userdata); - QMap stateMap; - stateMap[PA_SINK_INVALID_STATE] = "n/a"; - stateMap[PA_SINK_RUNNING] = "RUNNING"; - stateMap[PA_SINK_IDLE] = "IDLE"; - stateMap[PA_SINK_SUSPENDED] = "SUSPENDED"; if (isLast < 0) { qWarning() << QString("Failed to get sink information: %s").arg(pa_strerror(pa_context_errno(context))); @@ -103,6 +100,12 @@ static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int Q_ASSERT(info); #ifdef DEBUG_PULSE + QMap stateMap; + stateMap[PA_SINK_INVALID_STATE] = "n/a"; + stateMap[PA_SINK_RUNNING] = "RUNNING"; + stateMap[PA_SINK_IDLE] = "IDLE"; + stateMap[PA_SINK_SUSPENDED] = "SUSPENDED"; + qDebug() << QString("Sink #%1\n" "\tState: %2\n" "\tName: %3\n" @@ -114,8 +117,10 @@ static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int #endif QAudioFormat format = QPulseAudioInternal::sampleSpecToAudioFormat(info->sample_spec); + + QWriteLocker locker(&pulseEngine->m_sinkLock); pulseEngine->m_preferredFormats.insert(info->name, format); - pulseEngine->m_sinks.append(info->name); + pulseEngine->m_sinks.insert(info->index, info->name); } static void sourceInfoCallback(pa_context *context, const pa_source_info *info, int isLast, void *userdata) @@ -123,12 +128,6 @@ static void sourceInfoCallback(pa_context *context, const pa_source_info *info, Q_UNUSED(context) QPulseAudioEngine *pulseEngine = static_cast(userdata); - QMap stateMap; - stateMap[PA_SOURCE_INVALID_STATE] = "n/a"; - stateMap[PA_SOURCE_RUNNING] = "RUNNING"; - stateMap[PA_SOURCE_IDLE] = "IDLE"; - stateMap[PA_SOURCE_SUSPENDED] = "SUSPENDED"; - if (isLast) { pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); return; @@ -137,6 +136,12 @@ static void sourceInfoCallback(pa_context *context, const pa_source_info *info, Q_ASSERT(info); #ifdef DEBUG_PULSE + QMap stateMap; + stateMap[PA_SOURCE_INVALID_STATE] = "n/a"; + stateMap[PA_SOURCE_RUNNING] = "RUNNING"; + stateMap[PA_SOURCE_IDLE] = "IDLE"; + stateMap[PA_SOURCE_SUSPENDED] = "SUSPENDED"; + qDebug() << QString("Source #%1\n" "\tState: %2\n" "\tName: %3\n" @@ -148,8 +153,57 @@ static void sourceInfoCallback(pa_context *context, const pa_source_info *info, #endif QAudioFormat format = QPulseAudioInternal::sampleSpecToAudioFormat(info->sample_spec); + + QWriteLocker locker(&pulseEngine->m_sourceLock); pulseEngine->m_preferredFormats.insert(info->name, format); - pulseEngine->m_sources.append(info->name); + pulseEngine->m_sources.insert(info->index, info->name); +} + +static void event_cb(pa_context* context, pa_subscription_event_type_t t, uint32_t index, void* userdata) +{ + QPulseAudioEngine *pulseEngine = static_cast(userdata); + + int type = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK; + int facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; + + switch (type) { + case PA_SUBSCRIPTION_EVENT_NEW: + case PA_SUBSCRIPTION_EVENT_CHANGE: + switch (facility) { + case PA_SUBSCRIPTION_EVENT_SERVER: + pa_operation_unref(pa_context_get_server_info(context, serverInfoCallback, userdata)); + break; + case PA_SUBSCRIPTION_EVENT_SINK: + pa_operation_unref(pa_context_get_sink_info_by_index(context, index, sinkInfoCallback, userdata)); + break; + case PA_SUBSCRIPTION_EVENT_SOURCE: + pa_operation_unref(pa_context_get_source_info_by_index(context, index, sourceInfoCallback, userdata)); + break; + default: + break; + } + break; + case PA_SUBSCRIPTION_EVENT_REMOVE: + switch (facility) { + case PA_SUBSCRIPTION_EVENT_SINK: + pulseEngine->m_sinkLock.lockForWrite(); + pulseEngine->m_preferredFormats.remove(pulseEngine->m_sinks.value(index)); + pulseEngine->m_sinks.remove(index); + pulseEngine->m_sinkLock.unlock(); + break; + case PA_SUBSCRIPTION_EVENT_SOURCE: + pulseEngine->m_sourceLock.lockForWrite(); + pulseEngine->m_preferredFormats.remove(pulseEngine->m_sources.value(index)); + pulseEngine->m_sources.remove(index); + pulseEngine->m_sourceLock.unlock(); + break; + default: + break; + } + break; + default: + break; + } } static void contextStateCallbackInit(pa_context *context, void *userdata) @@ -272,6 +326,13 @@ void QPulseAudioEngine::prepare() if (ok) { pa_context_set_state_callback(m_context, contextStateCallback, this); + + pa_context_set_subscribe_callback(m_context, event_cb, this); + pa_operation_unref(pa_context_subscribe(m_context, + pa_subscription_mask_t(PA_SUBSCRIPTION_MASK_SINK | + PA_SUBSCRIPTION_MASK_SOURCE | + PA_SUBSCRIPTION_MASK_SERVER), + NULL, NULL)); } else { pa_context_unref(m_context); m_context = 0; @@ -332,14 +393,6 @@ void QPulseAudioEngine::updateDevices() pa_operation_unref(operation); unlock(); - - // 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::onContextFailed() @@ -360,7 +413,28 @@ QPulseAudioEngine *QPulseAudioEngine::instance() QList QPulseAudioEngine::availableDevices(QAudio::Mode mode) const { - return mode == QAudio::AudioOutput ? m_sinks : m_sources; + QList devices; + QByteArray defaultDevice; + + m_serverLock.lockForRead(); + + if (mode == QAudio::AudioOutput) { + QReadLocker locker(&m_sinkLock); + devices = m_sinks.values(); + defaultDevice = m_defaultSink; + } else { + QReadLocker locker(&m_sourceLock); + devices = m_sources.values(); + defaultDevice = m_defaultSource; + } + + m_serverLock.unlock(); + + // Swap the default device to index 0 + devices.removeOne(defaultDevice); + devices.prepend(defaultDevice); + + return devices; } QT_END_NAMESPACE diff --git a/src/plugins/pulseaudio/qpulseaudioengine.h b/src/plugins/pulseaudio/qpulseaudioengine.h index 3374ed77..0143d406 100644 --- a/src/plugins/pulseaudio/qpulseaudioengine.h +++ b/src/plugins/pulseaudio/qpulseaudioengine.h @@ -47,6 +47,7 @@ #include #include +#include #include #include #include "qpulsehelpers.h" @@ -98,13 +99,17 @@ private: void release(); public: - QList m_sinks; - QList m_sources; + QMap m_sinks; + QMap m_sources; QMap m_preferredFormats; QByteArray m_defaultSink; QByteArray m_defaultSource; + mutable QReadWriteLock m_sinkLock; + mutable QReadWriteLock m_sourceLock; + mutable QReadWriteLock m_serverLock; + private: pa_mainloop_api *m_mainLoopApi; pa_threaded_mainloop *m_mainLoop;