File indexing completed on 2025-02-09 05:31:45
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 ¤tDevice = 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 ¤tDevice = 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