diff --git a/phonon/audiooutput.cpp b/phonon/audiooutput.cpp index e99c394..ddbf360 100644 --- a/phonon/audiooutput.cpp +++ b/phonon/audiooutput.cpp @@ -99,7 +99,9 @@ void AudioOutputPrivate::init(Phonon::Category c) category = c; streamUuid = QUuid::createUuid().toString(); - PulseSupport::getInstance()->setStreamPropList(category, streamUuid); + PulseSupport *pulse = PulseSupport::getInstance(); + pulse->setStreamPropList(category, streamUuid); + q->connect(pulse, SIGNAL(usingDevice(QString,int)), SLOT(_k_deviceChanged(QString,int))); createBackendObject(); @@ -232,14 +234,14 @@ bool AudioOutput::setOutputDevice(const AudioOutputDevice &newAudioOutputDevice) { K_D(AudioOutput); if (!newAudioOutputDevice.isValid()) { - d->outputDeviceOverridden = false; + d->outputDeviceOverridden = d->forceMove = false; const int newIndex = GlobalConfig().audioOutputDeviceFor(d->category); if (newIndex == d->device.index()) { return true; } d->device = AudioOutputDevice::fromIndex(newIndex); } else { - d->outputDeviceOverridden = true; + d->outputDeviceOverridden = d->forceMove = true; if (d->device == newAudioOutputDevice) { return true; } @@ -272,7 +274,10 @@ void AudioOutputPrivate::setupBackendObject() pINTERFACE_CALL(setVolume(pow(volume, VOLTAGE_TO_LOUDNESS_EXPONENT))); // if the output device is not available and the device was not explicitly set - if (!callSetOutputDevice(this, device) && !outputDeviceOverridden) { + // There is no need to set the output device initially if PA is used as + // we know it will not work (stream doesn't exist yet) and that this will be + // handled by _k_deviceChanged() + if (!PulseSupport::getInstance()->isActive() && !callSetOutputDevice(this, device) && !outputDeviceOverridden) { // fall back in the preference list of output devices QList deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices); if (deviceList.isEmpty()) { @@ -316,6 +321,9 @@ void AudioOutputPrivate::_k_revertFallback() void AudioOutputPrivate::_k_audioDeviceFailed() { + if (PulseSupport::getInstance()->isActive()) + return; + pDebug() << Q_FUNC_INFO; // outputDeviceIndex identifies a failing device // fall back in the preference list of output devices @@ -338,7 +346,14 @@ void AudioOutputPrivate::_k_audioDeviceFailed() void AudioOutputPrivate::_k_deviceListChanged() { + if (PulseSupport::getInstance()->isActive()) + return; + pDebug() << Q_FUNC_INFO; + // Check to see if we have an override and do not change to a higher priority device if the overridden device is still present. + if (outputDeviceOverridden && device.property("available").toBool()) { + return; + } // let's see if there's a usable device higher in the preference list QList deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings); DeviceChangeType changeType = HigherPreferenceChange; @@ -365,6 +380,36 @@ void AudioOutputPrivate::_k_deviceListChanged() } } +void AudioOutputPrivate::_k_deviceChanged(QString inStreamUuid, int deviceIndex) +{ + // Note that this method is only used by PulseAudio at present. + if (inStreamUuid == streamUuid) { + // 1. Check to see if we are overridden. If we are, and devices do not match, + // then try and apply our own device as the output device. + // We only do this the first time + if (outputDeviceOverridden && forceMove) { + forceMove = false; + const AudioOutputDevice ¤tDevice = AudioOutputDevice::fromIndex(deviceIndex); + if (currentDevice != device) { + if (!callSetOutputDevice(this, device)) { + // What to do if we are overridden and cannot change to our preferred device? + } + } + } + // 2. If we are not overridden, then we need to update our perception of what + // device we are using. If the devices do not match, something lower in the + // stack is overriding our preferences (e.g. a per-application stream preference, + // specific application move, priority list changed etc. etc.) + else if (!outputDeviceOverridden) { + const AudioOutputDevice ¤tDevice = AudioOutputDevice::fromIndex(deviceIndex); + if (currentDevice != device) { + // The device is not what we think it is, so lets say what is happening. + handleAutomaticDeviceChange(currentDevice, SoundSystemChange); + } + } + } +} + static struct { int first; @@ -409,6 +454,27 @@ void AudioOutputPrivate::handleAutomaticDeviceChange(const AudioOutputDevice &de g_lastFallback.second = 0; } break; + case SoundSystemChange: + { +#ifndef QT_NO_PHONON_PLATFORMPLUGIN + if (device1.property("available").toBool()) { + const QString text = AudioOutput::tr("Switching to the audio playback device %1
" + "which has higher preference or is specifically configured for this stream.").arg(device2.name()); + Platform::notification("AudioDeviceFallback", text, + QStringList(AudioOutput::tr("Revert back to device '%1'").arg(device1.name())), + q, SLOT(_k_revertFallback())); + } else { + const QString &text = + AudioOutput::tr("The audio playback device %1 does not work.
" + "Falling back to %2.").arg(device1.name()).arg(device2.name()); + Platform::notification("AudioDeviceFallback", text); + } +#endif //QT_NO_PHONON_PLATFORMPLUGIN + //outputDeviceOverridden = true; + g_lastFallback.first = 0; + g_lastFallback.second = 0; + } + break; } } diff --git a/phonon/audiooutput.h b/phonon/audiooutput.h index 4edf135..513a863 100644 --- a/phonon/audiooutput.h +++ b/phonon/audiooutput.h @@ -169,6 +169,7 @@ namespace Phonon Q_PRIVATE_SLOT(k_func(), void _k_revertFallback()) Q_PRIVATE_SLOT(k_func(), void _k_audioDeviceFailed()) Q_PRIVATE_SLOT(k_func(), void _k_deviceListChanged()) + Q_PRIVATE_SLOT(k_func(), void _k_deviceChanged(QString streamUuid, int device)) }; } //namespace Phonon diff --git a/phonon/audiooutput_p.h b/phonon/audiooutput_p.h index 0a90c3d..01dc48f 100644 --- a/phonon/audiooutput_p.h +++ b/phonon/audiooutput_p.h @@ -59,6 +59,7 @@ class AudioOutputPrivate : public AbstractAudioOutputPrivate #endif deviceBeforeFallback(-1), outputDeviceOverridden(false), + forceMove(false), muted(false) { } @@ -67,7 +68,8 @@ class AudioOutputPrivate : public AbstractAudioOutputPrivate enum DeviceChangeType { FallbackChange, - HigherPreferenceChange + HigherPreferenceChange, + SoundSystemChange }; void handleAutomaticDeviceChange(const AudioOutputDevice &newDev, DeviceChangeType type); @@ -75,6 +77,7 @@ class AudioOutputPrivate : public AbstractAudioOutputPrivate void _k_revertFallback(); void _k_audioDeviceFailed(); void _k_deviceListChanged(); + void _k_deviceChanged(QString streamUuid, int deviceIndex); private: QString name; @@ -87,6 +90,7 @@ class AudioOutputPrivate : public AbstractAudioOutputPrivate Category category; int deviceBeforeFallback; bool outputDeviceOverridden; + bool forceMove; bool muted; }; } //namespace Phonon diff --git a/phonon/pulsesupport.cpp b/phonon/pulsesupport.cpp index f102177..9d3dd82 100644 --- a/phonon/pulsesupport.cpp +++ b/phonon/pulsesupport.cpp @@ -145,13 +145,11 @@ static QMap s_outputDeviceIndexes; static QMap s_outputDevices; static QMap > s_outputDevicePriorities; // prio, device static QMap s_outputStreamIndexMap; -static QMap s_outputStreamMoveQueue; static QMap s_captureDeviceIndexes; static QMap s_captureDevices; static QMap > s_captureDevicePriorities; // prio, device static QMap s_captureStreamIndexMap; -static QMap s_captureStreamMoveQueue; static void createGenericDevices() { @@ -378,80 +376,6 @@ static void ext_device_manager_read_cb(pa_context *c, const pa_ext_device_manage } #endif -static void set_output_device(QString streamUuid) -{ - // If we only have one device, bail. This will be true if we are not using module-device-manager - if (s_outputDevices.size() < 2) - return; - - if (!s_outputStreamMoveQueue.contains(streamUuid)) - return; - - if (!s_outputStreamIndexMap.contains(streamUuid)) - return; - - if (s_outputStreamIndexMap[streamUuid] == PA_INVALID_INDEX) - return; - - int device = s_outputStreamMoveQueue[streamUuid]; - if (!s_outputDevices.contains(device)) - return; - - // We don't remove the uuid from the s_captureStreamMoveQueue - // as an application may reuse the phonon AudioOutput object - - uint32_t pulse_device_index = s_outputDevices[device].pulseIndex; - uint32_t pulse_stream_index = s_outputStreamIndexMap[streamUuid]; - - const QVariant var = s_outputDevices[device].properties["name"]; - logMessage(QString("Moving Pulse Sink Input %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.toString()).arg(pulse_device_index)); - - /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db. - pa_operation* o; - if (!(o = pa_context_move_sink_input_by_index(s_context, pulse_stream_index, pulse_device_index, NULL, NULL))) { - logMessage(QString("pa_context_move_sink_input_by_index() failed")); - return; - } - pa_operation_unref(o); -} - -static void set_capture_device(QString streamUuid) -{ - // If we only have one device, bail. This will be true if we are not using module-device-manager - if (s_captureDevices.size() < 2) - return; - - if (!s_captureStreamMoveQueue.contains(streamUuid)) - return; - - if (!s_captureStreamIndexMap.contains(streamUuid)) - return; - - if (s_captureStreamIndexMap[streamUuid] == PA_INVALID_INDEX) - return; - - int device = s_captureStreamMoveQueue[streamUuid]; - if (!s_captureDevices.contains(device)) - return; - - // We don't remove the uuid from the s_captureStreamMoveQueue - // as an application may reuse the phonon AudioCapture object (when it exists!) - - uint32_t pulse_device_index = s_captureDevices[device].pulseIndex; - uint32_t pulse_stream_index = s_captureStreamIndexMap[streamUuid]; - - const QVariant var = s_captureDevices[device].properties["name"]; - logMessage(QString("Moving Pulse Source Output %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.toString()).arg(pulse_device_index)); - - /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db. - pa_operation* o; - if (!(o = pa_context_move_source_output_by_index(s_context, pulse_stream_index, pulse_device_index, NULL, NULL))) { - logMessage(QString("pa_context_move_source_output_by_index() failed")); - return; - } - pa_operation_unref(o); -} - void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { Q_UNUSED(userdata); Q_ASSERT(c); @@ -474,8 +398,25 @@ void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *us if ((t = pa_proplist_gets(i->proplist, "phonon.streamid"))) { logMessage(QString("Found PulseAudio stream index %1 for Phonon Output Stream %2").arg(i->index).arg(t)); s_outputStreamIndexMap[QString(t)] = i->index; - // Process any pending moves... - set_output_device(QString(t)); + + // Find the sink's phonon index and notify whoever cares... + if (PA_INVALID_INDEX != i->sink) { + bool found = false; + int device; + QMap::iterator it; + for (it = s_outputDevices.begin(); it != s_outputDevices.end(); ++it) { + if ((*it).pulseIndex == i->sink) { + found = true; + device = it.key(); + break; + } + } + if (found) { + // OK so we just emit our signal + logMessage(QString("Letting the rest of phonon know about this")); + s_instance->emitUsingDevice(QString(t), device); + } + } } } @@ -501,8 +442,25 @@ void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, vo if ((t = pa_proplist_gets(i->proplist, "phonon.streamid"))) { logMessage(QString("Found PulseAudio stream index %1 for Phonon Capture Stream %2").arg(i->index).arg(t)); s_captureStreamIndexMap[QString(t)] = i->index; - // Process any pending moves... - set_capture_device(QString(t)); + + // Find the source's phonon index and notify whoever cares... + if (PA_INVALID_INDEX != i->source) { + bool found = false; + int device; + QMap::iterator it; + for (it = s_captureDevices.begin(); it != s_captureDevices.end(); ++it) { + if ((*it).pulseIndex == i->source) { + found = true; + device = it.key(); + break; + } + } + if (found) { + // OK so we just emit our signal + logMessage(QString("Letting the rest of phonon know about this")); + s_instance->emitUsingDevice(QString(t), device); + } + } } } @@ -520,7 +478,6 @@ static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t } else { logMessage(QString("Removing Phonon Output Stream %1 (it's gone!)").arg(phononid)); s_outputStreamIndexMap.remove(phononid); - s_outputStreamMoveQueue.remove(phononid); } } } else { @@ -543,7 +500,6 @@ static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t } else { logMessage(QString("Removing Phonon Capture Stream %1 (it's gone!)").arg(phononid)); s_captureStreamIndexMap.remove(phononid); - s_captureStreamMoveQueue.remove(phononid); } } } else { @@ -933,6 +889,11 @@ void PulseSupport::emitObjectDescriptionChanged(ObjectDescriptionType type) emit objectDescriptionChanged(type); } +void PulseSupport::emitUsingDevice(QString streamUuid, int device) +{ + emit usingDevice(streamUuid, device); +} + bool PulseSupport::setOutputDevice(QString streamUuid, int device) { #ifndef HAVE_PULSEAUDIO Q_UNUSED(streamUuid); @@ -949,13 +910,24 @@ bool PulseSupport::setOutputDevice(QString streamUuid, int device) { const QVariant var = s_outputDevices[device].properties["name"]; logMessage(QString("Attempting to set Output Device to '%1' for Output Stream %2").arg(var.toString()).arg(streamUuid)); - s_outputStreamMoveQueue[streamUuid] = device; // Attempt to look up the pulse stream index. if (s_outputStreamIndexMap.contains(streamUuid) && s_outputStreamIndexMap[streamUuid] != PA_INVALID_INDEX) { logMessage(QString("... Found in map. Moving now")); - set_output_device(streamUuid); + + uint32_t pulse_device_index = s_outputDevices[device].pulseIndex; + uint32_t pulse_stream_index = s_outputStreamIndexMap[streamUuid]; + + logMessage(QString("Moving Pulse Sink Input %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.toString()).arg(pulse_device_index)); + + /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db. + pa_operation* o; + if (!(o = pa_context_move_sink_input_by_index(s_context, pulse_stream_index, pulse_device_index, NULL, NULL))) { + logMessage(QString("pa_context_move_sink_input_by_index() failed")); + return false; + } + pa_operation_unref(o); } else { - logMessage(QString("... Not found in map. Saving move for when the stream appears")); + logMessage(QString("... Not found in map. We will be notified of the device when the stream appears and we can process any moves needed then")); } return true; #endif @@ -977,13 +949,24 @@ bool PulseSupport::setCaptureDevice(QString streamUuid, int device) { const QVariant var = s_captureDevices[device].properties["name"]; logMessage(QString("Attempting to set Capture Device to '%1' for Capture Stream %2").arg(var.toString()).arg(streamUuid)); - s_captureStreamMoveQueue[streamUuid] = device; // Attempt to look up the pulse stream index. if (s_captureStreamIndexMap.contains(streamUuid) && s_captureStreamIndexMap[streamUuid] == PA_INVALID_INDEX) { logMessage(QString("... Found in map. Moving now")); - set_capture_device(streamUuid); + + uint32_t pulse_device_index = s_captureDevices[device].pulseIndex; + uint32_t pulse_stream_index = s_captureStreamIndexMap[streamUuid]; + + logMessage(QString("Moving Pulse Source Output %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.toString()).arg(pulse_device_index)); + + /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db. + pa_operation* o; + if (!(o = pa_context_move_source_output_by_index(s_context, pulse_stream_index, pulse_device_index, NULL, NULL))) { + logMessage(QString("pa_context_move_source_output_by_index() failed")); + return false; + } + pa_operation_unref(o); } else { - logMessage(QString("... Not found in map. Saving move for when the stream appears")); + logMessage(QString("... Not found in map. We will be notified of the device when the stream appears and we can process any moves needed then")); } return true; #endif @@ -996,9 +979,7 @@ void PulseSupport::clearStreamCache(QString streamUuid) { #else logMessage(QString("Clearing stream cache for stream %1").arg(streamUuid)); s_outputStreamIndexMap.remove(streamUuid); - s_outputStreamMoveQueue.remove(streamUuid); s_captureStreamIndexMap.remove(streamUuid); - s_captureStreamMoveQueue.remove(streamUuid); #endif } diff --git a/phonon/pulsesupport.h b/phonon/pulsesupport.h index bba5fd5..c38bece 100644 --- a/phonon/pulsesupport.h +++ b/phonon/pulsesupport.h @@ -54,6 +54,7 @@ namespace Phonon void setStreamPropList(Category category, QString streamUuid); void emitObjectDescriptionChanged(ObjectDescriptionType); + void emitUsingDevice(QString streamUuid, int device); bool setOutputDevice(QString streamUuid, int device); bool setCaptureDevice(QString streamUuid, int device); @@ -61,6 +62,8 @@ namespace Phonon signals: void objectDescriptionChanged(ObjectDescriptionType); + void usingDevice(QString streamUuid, int device); + private: PulseSupport(); ~PulseSupport();