File indexing completed on 2024-04-21 04:43:14

0001 /*  This file is part of the KDE project
0002     Copyright (C) 2005-2006 Matthias Kretz <kretz@kde.org>
0003 
0004     This library is free software; you can redistribute it and/or
0005     modify it under the terms of the GNU Lesser General Public
0006     License as published by the Free Software Foundation; either
0007     version 2.1 of the License, or (at your option) version 3, or any
0008     later version accepted by the membership of KDE e.V. (or its
0009     successor approved by the membership of KDE e.V.), Nokia Corporation
0010     (or its successors, if any) and the KDE Free Qt Foundation, which shall
0011     act as a proxy defined in Section 6 of version 3 of the license.
0012 
0013     This library is distributed in the hope that it will be useful,
0014     but WITHOUT ANY WARRANTY; without even the implied warranty of
0015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016     Lesser General Public License for more details.
0017 
0018     You should have received a copy of the GNU Lesser General Public
0019     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0020 */
0021 
0022 #include "audiooutput.h"
0023 #include "audiooutput_p.h"
0024 
0025 #include "audiooutputinterface.h"
0026 #include "factory_p.h"
0027 #include "globalconfig.h"
0028 #include "objectdescription.h"
0029 #include "phononconfig_p.h"
0030 #include "phononnamespace_p.h"
0031 #include "platform_p.h"
0032 #include "pulsesupport.h"
0033 #ifdef HAVE_PULSEAUDIO
0034 #  include "pulsestream_p.h"
0035 #endif
0036 
0037 #include <QUuid>
0038 #include <qmath.h>
0039 
0040 #define PHONON_CLASSNAME AudioOutput
0041 #define IFACES10 AudioOutputInterface410
0042 #define IFACES9 AudioOutputInterface49
0043 #define IFECES7 AudioOutputInterface47
0044 #define IFACES2 AudioOutputInterface42
0045 #define IFACES1 IFACES2
0046 #define IFACES0 AudioOutputInterface40, IFACES1, IFECES7, IFACES9, IFACES10
0047 #define PHONON_INTERFACENAME IFACES0
0048 
0049 namespace Phonon
0050 {
0051 
0052 static inline bool callSetOutputDevice(AudioOutputPrivate *const d, const AudioOutputDevice &dev)
0053 {
0054     PulseSupport *pulse = PulseSupport::getInstance();
0055     if (pulse->isActive())
0056         return pulse->setOutputDevice(d->getStreamUuid(), dev.index());
0057 
0058     if (!d->backendObject())
0059         return false;
0060 
0061     Iface<IFACES2> iface(d);
0062     if (iface) {
0063         return iface->setOutputDevice(dev);
0064     }
0065     return Iface<IFACES0>::cast(d)->setOutputDevice(dev.index());
0066 }
0067 
0068 AudioOutput::AudioOutput(Phonon::Category category, QObject *parent)
0069     : AbstractAudioOutput(*new AudioOutputPrivate, parent)
0070 {
0071     P_D(AudioOutput);
0072     d->init(category);
0073 }
0074 
0075 AudioOutput::AudioOutput(QObject *parent)
0076     : AbstractAudioOutput(*new AudioOutputPrivate, parent)
0077 {
0078     P_D(AudioOutput);
0079     d->init(NoCategory);
0080 }
0081 
0082 void AudioOutputPrivate::init(Phonon::Category c)
0083 {
0084     P_Q(AudioOutput);
0085 
0086     category = c;
0087 #ifndef QT_NO_QUUID_STRING
0088     streamUuid = QUuid::createUuid().toString();
0089 #endif
0090 
0091     createBackendObject();
0092 
0093 #ifdef HAVE_PULSEAUDIO
0094     PulseSupport *pulse = PulseSupport::getInstance();
0095     if (pulse->isActive()) {
0096         PulseStream *stream = pulse->registerOutputStream(streamUuid, category);
0097         if (stream) {
0098             q->connect(stream, SIGNAL(usingDevice(int)), SLOT(_k_deviceChanged(int)));
0099             q->connect(stream, SIGNAL(volumeChanged(qreal)), SLOT(_k_volumeChanged(qreal)));
0100             q->connect(stream, SIGNAL(muteChanged(bool)), SLOT(_k_mutedChanged(bool)));
0101 
0102             AudioOutputInterface47 *iface = Iface<AudioOutputInterface47>::cast(this);
0103             if (iface)
0104                 iface->setStreamUuid(streamUuid);
0105             else
0106                 pulse->setupStreamEnvironment(streamUuid);
0107         }
0108     }
0109 #endif
0110 
0111     q->connect(Factory::sender(), SIGNAL(availableAudioOutputDevicesChanged()), SLOT(_k_deviceListChanged()));
0112 }
0113 
0114 QString AudioOutputPrivate::getStreamUuid()
0115 {
0116     return streamUuid;
0117 }
0118 
0119 void AudioOutputPrivate::createBackendObject()
0120 {
0121     if (m_backendObject)
0122         return;
0123     P_Q(AudioOutput);
0124     m_backendObject = Factory::createAudioOutput(q);
0125     // (cg) Is it possible that PulseAudio initialisation means that the device here is not valid?
0126     // User reports seem to suggest this possibility but I can't see how :s.
0127     // See other comment and check for isValid() in handleAutomaticDeviceChange()
0128     device = AudioOutputDevice::fromIndex(GlobalConfig().audioOutputDeviceFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices));
0129     if (m_backendObject) {
0130         setupBackendObject();
0131     }
0132 }
0133 
0134 QString AudioOutput::name() const
0135 {
0136     P_D(const AudioOutput);
0137     return d->name;
0138 }
0139 
0140 void AudioOutput::setName(const QString &newName)
0141 {
0142     P_D(AudioOutput);
0143     if (d->name == newName) {
0144         return;
0145     }
0146     d->name = newName;
0147     PulseSupport *pulse = PulseSupport::getInstance();
0148     if (pulse->isActive())
0149         pulse->setOutputName(d->getStreamUuid(), newName);
0150     else
0151         setVolume(Platform::loadVolume(newName));
0152 }
0153 
0154 static const qreal LOUDNESS_TO_VOLTAGE_EXPONENT = qreal(0.67);
0155 static const qreal VOLTAGE_TO_LOUDNESS_EXPONENT = qreal(1.0/LOUDNESS_TO_VOLTAGE_EXPONENT);
0156 
0157 void AudioOutput::setVolume(qreal volume)
0158 {
0159     P_D(AudioOutput);
0160     d->volume = volume;
0161     PulseSupport *pulse = PulseSupport::getInstance();
0162     if (k_ptr->backendObject()) {
0163         if (pulse->isActive()) {
0164             pulse->setOutputVolume(d->getStreamUuid(), volume);
0165         } else if (!d->muted) {
0166             // using Stevens' power law loudness is proportional to (sound pressure)^0.67
0167             // sound pressure is proportional to voltage:
0168             // p² \prop P \prop V²
0169             // => if a factor for loudness of x is requested
0170             INTERFACE_CALL(setVolume(pow(volume, VOLTAGE_TO_LOUDNESS_EXPONENT)));
0171       } else {
0172           emit volumeChanged(volume);
0173       }
0174     } else {
0175         emit volumeChanged(volume);
0176     }
0177     if (!pulse->isActive())
0178         Platform::saveVolume(d->name, volume);
0179 }
0180 
0181 qreal AudioOutput::volume() const
0182 {
0183     P_D(const AudioOutput);
0184     if (d->muted || !d->m_backendObject || PulseSupport::getInstance()->isActive())
0185         return d->volume;
0186     return pow(INTERFACE_CALL(volume()), LOUDNESS_TO_VOLTAGE_EXPONENT);
0187 }
0188 
0189 #ifndef PHONON_LOG10OVER20
0190 #define PHONON_LOG10OVER20
0191 static const qreal log10over20 = qreal(0.1151292546497022842); // ln(10) / 20
0192 #endif // PHONON_LOG10OVER20
0193 
0194 qreal AudioOutput::volumeDecibel() const
0195 {
0196     P_D(const AudioOutput);
0197     if (d->muted || !d->m_backendObject || PulseSupport::getInstance()->isActive())
0198         return log(d->volume) / log10over20;
0199     return 0.67 * log(INTERFACE_CALL(volume())) / log10over20;
0200 }
0201 
0202 void AudioOutput::setVolumeDecibel(qreal newVolumeDecibel)
0203 {
0204     setVolume(exp(newVolumeDecibel * log10over20));
0205 }
0206 
0207 bool AudioOutput::isMuted() const
0208 {
0209     P_D(const AudioOutput);
0210     return d->muted;
0211 }
0212 
0213 void AudioOutput::setMuted(bool mute)
0214 {
0215     P_D(AudioOutput);
0216 
0217     if (d->muted == mute) {
0218         return;
0219     }
0220     d->muted = mute;
0221 
0222     if (!k_ptr->backendObject()) {
0223         return;
0224     }
0225 
0226     PulseSupport *pulse = PulseSupport::getInstance();
0227     if (pulse->isActive()) {
0228         pulse->setOutputMute(d->getStreamUuid(), mute);
0229     } else {
0230         // When interface 9 is implemented we always default to it.
0231         Iface<IFACES9> iface9(d);
0232         if (iface9) {
0233             iface9->setMuted(mute);
0234             // iface9 is fully async, we let the backend emit the state change.
0235             return;
0236         }
0237 
0238         if (mute) {
0239             INTERFACE_CALL(setVolume(0.0));
0240         } else {
0241             INTERFACE_CALL(setVolume(pow(d->volume, VOLTAGE_TO_LOUDNESS_EXPONENT)));
0242         }
0243     }
0244     emit mutedChanged(mute);
0245 }
0246 
0247 Category AudioOutput::category() const
0248 {
0249     P_D(const AudioOutput);
0250     return d->category;
0251 }
0252 
0253 AudioOutputDevice AudioOutput::outputDevice() const
0254 {
0255     P_D(const AudioOutput);
0256     return d->device;
0257 }
0258 
0259 bool AudioOutput::setOutputDevice(const AudioOutputDevice &newAudioOutputDevice)
0260 {
0261     P_D(AudioOutput);
0262     if (!newAudioOutputDevice.isValid()) {
0263         d->outputDeviceOverridden = d->forceMove = false;
0264         const int newIndex = GlobalConfig().audioOutputDeviceFor(d->category);
0265         if (newIndex == d->device.index()) {
0266             return true;
0267         }
0268         d->device = AudioOutputDevice::fromIndex(newIndex);
0269     } else {
0270         d->outputDeviceOverridden = d->forceMove = true;
0271         if (d->device == newAudioOutputDevice) {
0272             return true;
0273         }
0274         d->device = newAudioOutputDevice;
0275     }
0276     if (k_ptr->backendObject()) {
0277         return callSetOutputDevice(d, d->device);
0278     }
0279     return true;
0280 }
0281 
0282 bool AudioOutputPrivate::aboutToDeleteBackendObject()
0283 {
0284     if (m_backendObject) {
0285         volume = pINTERFACE_CALL(volume());
0286     }
0287     return AbstractAudioOutputPrivate::aboutToDeleteBackendObject();
0288 }
0289 
0290 void AudioOutputPrivate::setupBackendObject()
0291 {
0292     P_Q(AudioOutput);
0293     Q_ASSERT(m_backendObject);
0294     AbstractAudioOutputPrivate::setupBackendObject();
0295 
0296     QObject::connect(m_backendObject, SIGNAL(volumeChanged(qreal)), q, SLOT(_k_volumeChanged(qreal)));
0297     QObject::connect(m_backendObject, SIGNAL(audioDeviceFailed()), q, SLOT(_k_audioDeviceFailed()));
0298     if (Iface<IFACES9>(this)) {
0299         QObject::connect(m_backendObject, SIGNAL(mutedChanged(bool)),
0300                          q, SLOT(_k_mutedChanged(bool)));
0301     }
0302 
0303     Iface<IFACES10> iface10(this);
0304     if (iface10) {
0305         iface10->setCategory(category);
0306     }
0307 
0308     if (!PulseSupport::getInstance()->isActive()) {
0309         // set up attributes
0310         pINTERFACE_CALL(setVolume(pow(volume, VOLTAGE_TO_LOUDNESS_EXPONENT)));
0311 
0312 #ifndef QT_NO_PHONON_SETTINGSGROUP
0313         // if the output device is not available and the device was not explicitly set
0314         // There is no need to set the output device initially if PA is used as
0315         // we know it will not work (stream doesn't exist yet) and that this will be
0316         // handled by _k_deviceChanged()
0317         if (!callSetOutputDevice(this, device) && !outputDeviceOverridden) {
0318             // fall back in the preference list of output devices
0319             QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices);
0320             if (deviceList.isEmpty()) {
0321                 return;
0322             }
0323             for (int i = 0; i < deviceList.count(); ++i) {
0324                 const AudioOutputDevice &dev = AudioOutputDevice::fromIndex(deviceList.at(i));
0325                 if (callSetOutputDevice(this, dev)) {
0326                     handleAutomaticDeviceChange(dev, AudioOutputPrivate::FallbackChange);
0327                     return; // found one that works
0328                 }
0329             }
0330             // if we get here there is no working output device. Tell the backend.
0331             const AudioOutputDevice none;
0332             callSetOutputDevice(this, none);
0333             handleAutomaticDeviceChange(none, FallbackChange);
0334         }
0335 #endif //QT_NO_PHONON_SETTINGSGROUP
0336     }
0337 }
0338 
0339 void AudioOutputPrivate::_k_volumeChanged(qreal newVolume)
0340 {
0341     volume = pow(newVolume, LOUDNESS_TO_VOLTAGE_EXPONENT);
0342     if (!muted) {
0343         P_Q(AudioOutput);
0344         emit q->volumeChanged(volume);
0345     }
0346 }
0347 
0348 void AudioOutputPrivate::_k_mutedChanged(bool newMuted)
0349 {
0350     muted = newMuted;
0351     P_Q(AudioOutput);
0352     emit q->mutedChanged(newMuted);
0353 }
0354 
0355 void AudioOutputPrivate::_k_revertFallback()
0356 {
0357     if (deviceBeforeFallback == -1) {
0358         return;
0359     }
0360     device = AudioOutputDevice::fromIndex(deviceBeforeFallback);
0361     callSetOutputDevice(this, device);
0362     P_Q(AudioOutput);
0363     emit q->outputDeviceChanged(device);
0364 }
0365 
0366 void AudioOutputPrivate::_k_audioDeviceFailed()
0367 {
0368     if (PulseSupport::getInstance()->isActive())
0369         return;
0370 
0371 #ifndef QT_NO_PHONON_SETTINGSGROUP
0372 
0373     pDebug() << Q_FUNC_INFO;
0374     // outputDeviceIndex identifies a failing device
0375     // fall back in the preference list of output devices
0376     const QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices);
0377     for (int i = 0; i < deviceList.count(); ++i) {
0378         const int devIndex = deviceList.at(i);
0379         // if it's the same device as the one that failed, ignore it
0380         if (device.index() != devIndex) {
0381             const AudioOutputDevice &info = AudioOutputDevice::fromIndex(devIndex);
0382             if (callSetOutputDevice(this, info)) {
0383                 handleAutomaticDeviceChange(info, FallbackChange);
0384                 return; // found one that works
0385             }
0386         }
0387     }
0388 #endif //QT_NO_PHONON_SETTINGSGROUP
0389     // if we get here there is no working output device. Tell the backend.
0390     const AudioOutputDevice none;
0391     callSetOutputDevice(this, none);
0392     handleAutomaticDeviceChange(none, FallbackChange);
0393 }
0394 
0395 void AudioOutputPrivate::_k_deviceListChanged()
0396 {
0397     if (PulseSupport::getInstance()->isActive())
0398         return;
0399 
0400 #ifndef QT_NO_PHONON_SETTINGSGROUP
0401     pDebug() << Q_FUNC_INFO;
0402     // Check to see if we have an override and do not change to a higher priority device if the overridden device is still present.
0403     if (outputDeviceOverridden && device.property("available").toBool()) {
0404         return;
0405     }
0406     // let's see if there's a usable device higher in the preference list
0407     const QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings);
0408     DeviceChangeType changeType = HigherPreferenceChange;
0409     for (int i = 0; i < deviceList.count(); ++i) {
0410         const int devIndex = deviceList.at(i);
0411         const AudioOutputDevice &info = AudioOutputDevice::fromIndex(devIndex);
0412         if (!info.property("available").toBool()) {
0413             if (device.index() == devIndex) {
0414                 // we've reached the currently used device and it's not available anymore, so we
0415                 // fallback to the next available device
0416                 changeType = FallbackChange;
0417             }
0418             pDebug() << devIndex << "is not available";
0419             continue;
0420         }
0421         pDebug() << devIndex << "is available";
0422         if (device.index() == devIndex) {
0423             // we've reached the currently used device, nothing to change
0424             break;
0425         }
0426         if (callSetOutputDevice(this, info)) {
0427             handleAutomaticDeviceChange(info, changeType);
0428             break; // found one with higher preference that works
0429         }
0430     }
0431 #endif //QT_NO_PHONON_SETTINGSGROUP
0432 }
0433 
0434 void AudioOutputPrivate::_k_deviceChanged(int deviceIndex)
0435 {
0436     // NB that this method is only used by PulseAudio at present.
0437 
0438     // 1. Check to see if we are overridden. If we are, and devices do not match,
0439     //    then try and apply our own device as the output device.
0440     //    We only do this the first time
0441     if (outputDeviceOverridden && forceMove) {
0442         forceMove = false;
0443         const AudioOutputDevice &currentDevice = AudioOutputDevice::fromIndex(deviceIndex);
0444         if (currentDevice != device) {
0445             if (!callSetOutputDevice(this, device)) {
0446                 // What to do if we are overridden and cannot change to our preferred device?
0447             }
0448         }
0449     }
0450     // 2. If we are not overridden, then we need to update our perception of what
0451     //    device we are using. If the devices do not match, something lower in the
0452     //    stack is overriding our preferences (e.g. a per-application stream preference,
0453     //    specific application move, priority list changed etc. etc.)
0454     else if (!outputDeviceOverridden) {
0455         const AudioOutputDevice &currentDevice = AudioOutputDevice::fromIndex(deviceIndex);
0456         if (currentDevice != device) {
0457             // The device is not what we think it is, so lets say what is happening.
0458             handleAutomaticDeviceChange(currentDevice, SoundSystemChange);
0459         }
0460     }
0461 }
0462 
0463 static struct
0464 {
0465     int first;
0466     int second;
0467 } g_lastFallback = { 0, 0 };
0468 
0469 void AudioOutputPrivate::handleAutomaticDeviceChange(const AudioOutputDevice &device2, DeviceChangeType type)
0470 {
0471     P_Q(AudioOutput);
0472     deviceBeforeFallback = device.index();
0473     device = device2;
0474     emit q->outputDeviceChanged(device2);
0475     const AudioOutputDevice &device1 = AudioOutputDevice::fromIndex(deviceBeforeFallback);
0476     switch (type) {
0477     case FallbackChange:
0478         if (g_lastFallback.first != device1.index() || g_lastFallback.second != device2.index()) {
0479 #ifndef QT_NO_PHONON_PLATFORMPLUGIN
0480             const QString &text = //device2.isValid() ?
0481                 AudioOutput::tr("<html>The audio playback device <b>%1</b> does not work.<br/>"
0482                         "Falling back to <b>%2</b>.</html>").arg(device1.name()).arg(device2.name()) /*:
0483                 AudioOutput::tr("<html>The audio playback device <b>%1</b> does not work.<br/>"
0484                         "No other device available.</html>").arg(device1.name())*/;
0485             Platform::notification("AudioDeviceFallback", text);
0486 #endif //QT_NO_PHONON_PLATFORMPLUGIN
0487             g_lastFallback.first = device1.index();
0488             g_lastFallback.second = device2.index();
0489         }
0490         break;
0491     case HigherPreferenceChange:
0492         {
0493 #ifndef QT_NO_PHONON_PLATFORMPLUGIN
0494         const QString text = AudioOutput::tr("<html>Switching to the audio playback device <b>%1</b><br/>"
0495                 "which just became available and has higher preference.</html>").arg(device2.name());
0496         Platform::notification("AudioDeviceFallback", text,
0497                 QStringList(AudioOutput::tr("Revert back to device '%1'").arg(device1.name())),
0498                 q, SLOT(_k_revertFallback()));
0499 #endif //QT_NO_PHONON_PLATFORMPLUGIN
0500         g_lastFallback.first = 0;
0501         g_lastFallback.second = 0;
0502         }
0503         break;
0504     case SoundSystemChange:
0505         {
0506 #ifndef QT_NO_PHONON_PLATFORMPLUGIN
0507         // If device1 is not "valid" this indicates that the preferences used to select
0508         // a device was perhaps not available when this object was created (although
0509         // I can't quite work out how that would be....)
0510         if (device1.isValid()) {
0511             if (device1.property("available").toBool()) {
0512                 const QString text = AudioOutput::tr("<html>Switching to the audio playback device <b>%1</b><br/>"
0513                         "which has higher preference or is specifically configured for this stream.</html>").arg(device2.name());
0514                 Platform::notification("AudioDeviceFallback", text,
0515                         QStringList(AudioOutput::tr("Revert back to device '%1'").arg(device1.name())),
0516                         q, SLOT(_k_revertFallback()));
0517             } else {
0518                 const QString &text =
0519                     AudioOutput::tr("<html>The audio playback device <b>%1</b> does not work.<br/>"
0520                             "Falling back to <b>%2</b>.</html>").arg(device1.name()).arg(device2.name());
0521                 Platform::notification("AudioDeviceFallback", text);
0522             }
0523         }
0524 #endif //QT_NO_PHONON_PLATFORMPLUGIN
0525         //outputDeviceOverridden = true;
0526         g_lastFallback.first = 0;
0527         g_lastFallback.second = 0;
0528         }
0529         break;
0530     }
0531 }
0532 
0533 AudioOutputPrivate::~AudioOutputPrivate()
0534 {
0535     PulseSupport *pulse = PulseSupport::getInstanceOrNull(true);
0536     if (pulse) {
0537         pulse->clearStreamCache(streamUuid);
0538     }
0539 }
0540 
0541 } //namespace Phonon
0542 
0543 #include "moc_audiooutput.cpp"
0544 
0545 #undef PHONON_CLASSNAME
0546 #undef PHONON_INTERFACENAME
0547 #undef IFECES7
0548 #undef IFACES2
0549 #undef IFACES1
0550 #undef IFACES0